pip install jieba
|
|
文本与向量 n个 数 排列一起形成的一组数就是向量 向量有两个核心要素:大小,方向 多个维数相同的向量放在一起就是矩阵 线性代数的基本思想是什么? 是用矩阵去描述解决问题 虽然线性代数全是在讲矩阵,但若说矩阵就是线性代数就显得狭隘了, 思维再打开一些,所有的知识都是在描述这个世界的规律, 就像字符类语言可以描述解决问题一样,由数字组成的矩阵也可以 更重要的是,计算机中全是数字,我们要用计算机,就得把事物转成数字, 不管它是具体的还是抽象的,是静态的,还是运行变化的 而向量使用数字组来描述事物,向量也是矩阵的组成,或者说是基本单位 同时,单个向量也叫列矩阵 / 行矩阵 所以,文本向量化,实际上是文本矩阵化 一个单词对应一个数字 一个句子对应一个向量 多个句子对应一个矩阵
|
|
|
|
|
|
|
|
文本向量化大方向 文本向量化,实际上是文本矩阵化 多个句子对应一个矩阵,即用一段文本用矩阵表示,这一点是确定的 有可灵活设计的地方在于: 一个单词对应一个数字 单个向量可以表示单个事物,那么就可以使用向量表示单词 并且单词个数多,含义也有差异,我们希望表示单词的向量也能体现这种差异 至于怎么体现,就是八仙过海,各显神通,意思就是看你算法怎么设计
是用一个数字表示一个单词,还是用向量表示单词以及怎么用向量表示单词,
是文本向量化算法的角逐的点
就像汉语可以翻译成外语一样,也能翻译成矩阵,
只是现在还没有哪家公司用矩阵组建一套国际通用语法结构
如果用一个向量表示一个单词,那么一个句子对应一个2维矩阵
|
|
汉语中的字与词 汉字约3000多个,而单词的量级在万级,几十万... 一个字对应一个向量,经实践证明在某些场合,效果并不比一个单词对应一个向量效果差 一句话或一段文本 [1, seq_len, 单词的维度] 多段文本,即一个批次的文本 [Batch_size, seq_len, 单词的维度] 单词的维度也是AI中的特征维度,feature dim 而序列长度,则是AI中的特征个数feature nums (本网站叫feature shape) 单个样本就是一句话[1, seq_len, 单词的维度],或者[seq_len, 单词的维度]
|
|
word2index单词转索引
import jieba
ss = "团结一切可以团结的力量"
sentence = jieba.lcut(ss)
print(sentence)
"""
['团结', '一切', '可以', '团结', '的', '力量']
"""
word2id = {word:index for index,word in enumerate(set(sentence))}
word2id["UNK"] = len(word2id)
word2id["PAD"] = len(word2id) # 补长度 是为了批次处理
print(word2id)
"""
{'可以': 0, '团结': 1, '一切': 2, '力量': 3, '的': 4, 'UNK': 5, 'PAD': 6}
"""
"""
字典长度,向量长度为7,即使每个元素限定取0或1 -- one hot
"""
print(2**7) # 128
print(2**3) # 8
代码分析
---------------------------------------------
词典,key:汉字,value:索引编号
为每个单词编号,文本中所有不重复的单词及编号形成一个词典
在预测过程中遇到的新单词转为UNK
一句话的单词个数不定,而在批次处理中,要求向量维度一致,
此时向量维度通常选句子长度的平均值,多的舍去,不足补为PAD
使用索引表示向量仅仅是给不同的单词一个不同的编号,
如果使用one hot编码,则向量长度为7,每个向量中索引所在位置表示单词,
向量的两个核心要素:大小与方向,
one hot编码,向量的模为1(1是一个非常特殊的值),所以,单词之间的区别就剩下方向了, 如果单词个数少,这种表示法就是完美 如果单词个数多,这种表示法会占用太多的空间, 单词个数一旦过万,那么向量维度就过万, 计算机算不过来,解决方式就是降维 依然使用0与1二进制来区分单词,7个位数的二进制可表示128个不同的单词, 这里的需求是7个单词,使用3位二进制就可以了 如此,向量的维数从7降为3 2**20=1048576,20位二进制可表示100万不同的单词,向量位数为20在计算机中并不算长 |
|
torch nn.Embedding
单词转索引后,可以由Embedding转为指定维度的向量
向量维度变小,同时,还要一个向量唯一表示一个单词
import jieba
ss = "团结一切可以团结的力量"
sentence = jieba.lcut(ss)
word2id = {word:index for index,word in enumerate(set(sentence))}
word2id["UNK"] = len(word2id)
word2id["PAD"] = len(word2id) # 补长度 是为了批次处理
dict_len = len(word2id)
import torch
from torch import nn
embed = nn.Embedding(num_embeddings=dict_len, embedding_dim=3, padding_idx=word2id["PAD"]) # __call__
sentence_index = [word2id[word] for word in sentence]
print(sentence_index)
"""
[0, 3, 1, 0, 2, 4]
对应的单词列表
['团结', '一切', '可以', '团结', '的', '力量']
"""
sentence_index = torch.Tensor(sentence_index).long()
sen_vec = embed(sentence_index)
print(sen_vec)
"""
tensor([[-0.1450, 0.2220, -0.2267],
[-0.5169, -1.7362, -0.2435],
[ 0.4777, 0.2449, 0.0158],
[-0.1450, 0.2220, -0.2267],
[-0.8421, 0.0353, -0.3633],
[ 1.9878, 0.9668, 0.1843]], grad_fn=EmbeddingBackward0)
"""
embed的结果是随机的,通常放入神经网络起训练,损失函数梯度下降时会优化参数
|
|
index2word
import jieba
ss = "团结一切可以团结的力量"
sentence = jieba.lcut(ss)
word2idx = {}
word2idx["PAD"] = len(word2idx) # 补长度 是为了批次处理
word2idx["UNK"] = len(word2idx)
word2idx.update({word:(index+2) for index,word in enumerate(set(sentence))})
idx2word = {index:word for word,index in word2idx.items()}
idx2word
{0: 'PAD', 1: 'UNK', 2: '的', 3: '团结', 4: '一切', 5: '力量', 6: '可以'}
|
np.eye单位矩阵
import numpy as np
np.eye(3)
array([[1., 0., 0.],
[0., 1., 0.],
[0., 0., 1.]])
a=np.eye(3)
a[0]
array([1., 0., 0.])
单位矩阵是方阵,每个向量中只有一个元素为1,其他为0,并且行向量与列向量相等
这与One Hot 向量恰好一致,故可以使用np.eye来打one hot标签
import numpy as np
import jieba
ss = "团结一切可以团结的力量"
sentence = jieba.lcut(ss)
word2idx = {}
word2idx["PAD"] = len(word2idx) # 补长度 是为了批次处理
word2idx["UNK"] = len(word2idx)
word2idx.update({word:(index+2) for index,word in enumerate(set(sentence))})
print(word2idx) # {'PAD': 0, 'UNK': 1, '一切': 2, '力量': 3, '团结': 4, '的': 5, '可以': 6}
def one_hot_vec(word2idx, word):
template_vec = np.eye(len(word2idx))
return template_vec[word2idx[word]]
one_hot_vec(word2idx=word2idx,word="团结") # array([0., 0., 0., 0., 1., 0., 0.])
|
酒店评估
原数据集是一段话一个文本,数万个小文件,太占空间,压缩存放以下目录
/wks/datasets/hotel_reader/data_pkl
原始数据集:已做单词切片,未转索引,是单词列表 import os from tpf import pkl_save,pkl_load BASE_DIR = "/wks/datasets/hotel_reader" file_path = os.path.join(BASE_DIR,'data_pkl/word.pkl') pkl_save((X_train,y_train,X_test,y_test,words_set,word2idx,idx2word),file_path=file_path) 加载
import os
from tpf import pkl_save,pkl_load
BASE_DIR = "/wks/datasets/hotel_reader"
file_path = os.path.join(BASE_DIR,'data_pkl/word.pkl')
X_train,y_train,X_test,y_test,words_set,word2idx,idx2word = pkl_load(file_path)
查看,不定长单词列表
s = ""
for i in X_train[0]:
s += " " + i
s
' 房间 还 可以 , 早餐 不大好 , 周围 比较 偏僻 。 服务态度 一般 。'
s = ""
for i in X_train[1]:
s += " " + i
s
' 这次 入住 感到 服务 人员 的 工作 态度 不如 以前 , 整体 工作 热情 也 不象 其他 喜来登 那么 专业 。 因为 我定 的 房间 满 了 于是 给 我 升级 到 行政 套房 , 这个 本来 是 不错 的 安排 , 但是 不 知道 为什么 偏偏 这个 房间 里 早晚 都 有 一股 股 噪音 ; 而 我 朋友 住 在 我 下面 一层 , 一清早 就 被 行政 酒廊 准备 早餐 的 声音 吵醒 。 酒店 2 楼 西餐厅 服务 还是 很 好 的 。'
第二版:one-hot编码
import os
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from tpf import pkl_save,pkl_load
class MyDataSet(Dataset):
"""
构建数据集
"""
def __init__(self, X, y):
self.X = X
self.y = y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x = self.X[idx]
y = self.y[idx]
return torch.tensor(data=x).long(), torch.tensor(data=y).long()
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = "/wks/datasets/hotel_reader"
"""
读取原始数据
"""
dict_file = os.path.join(BASE_DIR,"data_pkl/wordict.pkl")
file_train = os.path.join(BASE_DIR,"data_pkl/train.pkl")
file_test = os.path.join(BASE_DIR, "data_pkl/test.pkl")
words_set,word2idx = pkl_load(file_path=dict_file) # 字典
X_train,y_train = pkl_load(file_path=file_train) # 训练集
X_test,y_test = pkl_load(file_path=file_test) # 测试集
第一版:对接深度学习
import os
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
from tpf import pkl_save,pkl_load
class MyDataSet(Dataset):
"""
构建数据集
"""
def __init__(self, X, y):
self.X = X
self.y = y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x = self.X[idx]
y = self.y[idx]
return torch.tensor(data=x).long(), torch.tensor(data=y).long()
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
BASE_DIR = "/wks/datasets/hotel_reader"
"""
读取原始数据
"""
dict_file = os.path.join(BASE_DIR,"data_pkl/wordict.pkl")
file_train = os.path.join(BASE_DIR,"data_pkl/train.pkl")
file_test = os.path.join(BASE_DIR, "data_pkl/test.pkl")
words_set,word2idx = pkl_load(file_path=dict_file) # 字典
X_train,y_train = pkl_load(file_path=file_train) # 训练集
X_test,y_test = pkl_load(file_path=file_test) # 测试集
train_dataset = MyDataSet(X=X_train, y=y_train)
test_dataset = MyDataSet(X=X_test,y=y_test)
print(train_dataset[0],len(train_dataset))
print("test len=",len(test_dataset))
print(f"words_set len={len(words_set)}")
$ python read_test2.py
(tensor([ 6426, 21219, 13836, 12268, 6804, 11523, 12268, 12177, 19919, 3728,
3689, 7275, 11758, 3689, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0]), tensor(1)) 4800
test len= 1200
words_set len=21437
|
import numpy as np
from sklearn.model_selection import train_test_split
import os
import jieba
读取原始数据
train_file = "./train.csv"
test_file = "./test.csv"
# 训练集文本和标签
X_train = []
y_train = []
# 构建词典
# UNK:未知词
# PAD:填充词
words_set = {"
# words_list = list(words_set)
word2idx = {word:idx for idx, word in enumerate(words_set)}
idx2word = {idx:word for idx, word in enumerate(words_set)}
# 字典长度
dict_len = len(words_set) #21437
文本向量化·索引编码
# 训练集的向量化
X_train1 = []
for x in X_train:
# 临时编码向量
temp = [0] * dict_len
for word in x:
word_idx = word2idx[word] if word in words_set else word2idx["
数据转 NumPy向量
X_train1 = np.array(X_train1)
y_train1 = np.array(y_train)
X_test1 = np.array(X_test1)
y_test1 = np.array(y_test)
保存
from tpf import pkl_save,pkl_load
BASE_DIR = "/wks/datasets/hotel_reader"
dict_file = os.path.join(BASE_DIR,"data_pkl/wordict_2.pkl")
file_train = os.path.join(BASE_DIR,"data_pkl/train_2.pkl")
file_test = os.path.join(BASE_DIR, "data_pkl/test_2.pkl")
pkl_save((words_set,word2idx),dict_file)
pkl_save((X_train1,y_train1),file_train)
pkl_save((X_test1,y_test1),file_test)
|
|
逻辑回归 import os import numpy as np from tpf import pkl_save,pkl_load BASE_DIR = os.path.dirname(os.path.abspath(__file__)) BASE_DIR = "/wks/datasets/hotel_reader" """ 读取原始数据 """ dict_file = os.path.join(BASE_DIR,"data_pkl/wordict_2.pkl") file_train = os.path.join(BASE_DIR,"data_pkl/train_2.pkl") file_test = os.path.join(BASE_DIR, "data_pkl/test_2.pkl") words_set,word2idx = pkl_load(file_path=dict_file) # 字典 X_train,y_train = pkl_load(file_path=file_train) # 训练集 X_test,y_test = pkl_load(file_path=file_test) # 测试集 X_train1 = np.array(X_train) y_train1 = np.array(y_train) X_test1 = np.array(X_test) y_test1 = np.array(y_test) from sklearn.linear_model import LogisticRegression lr = LogisticRegression(max_iter=10000) lr.fit(X=X_train1, y=y_train1) print(lr.score(X=X_test1, y=y_test1)) 0.8933333333333333
然而,若是将数据集更换为最早之前保存的版本的话,精度下降明显
$ python alg_lr.py
0.6075
个人怀疑是数据集并不充分,按不同的方式split时,会导致学习到的规律出现大的波动
- 版本2的数据集是按指定的excel处理的,训练集与测试集的划分是提前分好的
然而,经过确认,下面的数值完全一致
print(len(y_test1),y_test1[:7])
1200 [1 0 1 0 0 1 1]
数据一致,那么不同的就是编码方式了
- 也有可能是去除空格的原因
- 也有可能是数据不同了,虽然它们标签一致
idx2word = {idx:word for idx, word in enumerate(words_set)}
for i in X_train1[0]:
if i > 0:
print(idx2word[i])
第一版数据 专业 市委 疏漏 单床 游戏规则 黄同金 单床 章 虫 本周 总台 下楼 散客 总台 print(len(X_train1[0])) 85 第二版数据 大开 大开 大开 手势 大开 大开 大开 大开 大开 手势 大开 大开 len(X_train1[0]) # 21437
至此可以看出,最大的不同是向量的设计,第二版向量被设计为字典长度
"""
文本向量化
Count 计算法
"""
# 训练集的向量化
X_train1 = []
for x in X_train:
# 临时编码向量
temp = [0] * dict_len
for word in x:
word_idx = word2idx[word] if word in words_set else word2idx["
temp = [0] * dict_len X_train1.append(temp) 第1版的向量只有85的长度,肯定是截断了 第2版,何止是没有截断,这长度都让人无语了,但效果 >>> 0.8933-0.6075 0.28579999999999994 提升了将近30% |
数据集在使用one-hot编码时,即使使用逻辑回归,也有不错的精度 这种方法在卷积网络中,很多时间不会动,计算不动 在个人计算机上,不得不采用索引编码再转向量的方式 |
|
|
文本处理,若使用onehot编码,效果可能会好,但向量长度过长,个人电脑算不动
因此这里使用索引编码的方式作为输入,由模型进行embedding 转向量
需要维度好字典信息
#加载数据集
import os
from tpf import pkl_save,pkl_load
BASE_DIR = "/wks/datasets/hotel_reader"
file_path = os.path.join(BASE_DIR,'data_pkl/word.pkl')
X_train,y_train,X_test,y_test,words_set,word2idx,idx2word = pkl_load(file_path)
# 字典长度
dict_len = len(words_set)
ll_max = 0
for s in X_train:
ll = len(s)
if ll > ll_max:
ll_max = ll
ll_max
1755
# 序列长度
seq_len = 512
# 训练集
X_train1 = []
for x in X_train:
temp = x + ["<PAD>"] * seq_len
X_train1.append(temp[:seq_len])
# 测试集
X_test1 = []
for x in X_test:
temp = x + ["<PAD>"] * seq_len
X_test1.append(temp[:seq_len])
|
"""
索引向量化
"""
# 训练集向量化
X_train2 = []
for x in X_train1:
temp = []
for word in x:
idx = word2idx[word] if word in word2idx else word2idx["<UNK>"]
temp.append(idx)
X_train2.append(temp)
# 测试集向量化
X_test2 = []
for x in X_test1:
temp = []
for word in x:
idx = word2idx[word] if word in word2idx else word2idx["<UNK>"]
temp.append(idx)
X_test2.append(temp)
len(X_test2[0])
512
|
"""
构建数据集
"""
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
import torch
from torch import nn
from torch.nn import functional as F
class MyDataSet(Dataset):
def __init__(self, X=X_train2, y=y_train):
self.X = X
self.y = y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x = self.X[idx]
y = self.y[idx]
return torch.tensor(data=x).long(), torch.tensor(data=y).long()
"""
定义数据加载器
"""
train_dataset = MyDataSet(X=X_train2, y=y_train)
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=128)
test_dataset = MyDataSet(X=X_test2, y=y_test)
test_dataloader = DataLoader(dataset=test_dataset, shuffle=False, batch_size=256)
train_dataset[0][0][:7] tensor([ 5004, 10135, 20586, 9530, 16808, 14513, 9530]) |
import numpy as np
import torch
from torch import nn
from torch.nn import functional as F
class TextCNN3(nn.Module):
"""
TextCNN优化,
"""
def __init__(self,num_embeddings, embedding_dim,padding_idx,seq_len):
super().__init__()
self.embed = nn.Embedding(num_embeddings=num_embeddings,
embedding_dim=embedding_dim,
padding_idx=padding_idx)
# [N, C, seq_len] -- [N, C, seq_len-1],[N, C, seq_len-kernel_size+1]
self.gram_2 = nn.Sequential(
nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=2),
# nn.BatchNorm1d(num_features=256),
nn.ReLU(),
nn.MaxPool1d(kernel_size=seq_len-1) # [N, C, 1]
)
# [N, C, seq_len, 1] -- [N, C, seq_len-2, 1]
self.gram_3 = nn.Sequential(
nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=3),
# nn.BatchNorm1d(num_features=256),
nn.ReLU(),
nn.MaxPool1d(kernel_size=seq_len-2) # [N, C, 1]
)
# [N, C, seq_len, 1] -- [N, C, seq_len-3, 1]
self.gram_4 = nn.Sequential(
nn.Conv1d(in_channels=embedding_dim, out_channels=256, kernel_size=4),
# nn.BatchNorm1d(num_features=256),
nn.ReLU(),
nn.MaxPool1d(kernel_size=seq_len-3) # [N, C, 1]
)
self.dropout1 = nn.Dropout(p=0.2)
self.fc1 = nn.Linear(in_features=256*3, out_features=2)
def forward(self,X):
# [B,seq_len,embedding_dim]
x = self.embed(X)
# [B, seq_len, embedding_dim] -- [B, embedding_dim, seq_len]
x = torch.permute(input=x, dims=(0, 2, 1))
# print(x.shape) # torch.Size([128, 256, 87])
x1 = self.gram_2(x)
# print(f"x1.shape={x1.shape}") #torch.Size([128, 256, 1])
x2 = self.gram_3(x)
x3 = self.gram_4(x)
x = torch.concat(tensors=(x1,x2,x3),dim=1)
# print(x.shape) # torch.Size([128, 768, 1])
x = torch.squeeze(x)
x = self.dropout1(x)
x = self.fc1(x)
return x
device = "cuda:0" if torch.cuda.is_available() else "cpu" model = TextCNN3(num_embeddings=dict_len, embedding_dim=256, padding_idx=word2idx[" |
# 定义训练轮次 epochs = 200
# 定义过程监控函数
def get_acc(dataloader=train_dataloader, model=model):
accs = []
model.to(device=device)
model.eval()
with torch.no_grad():
for X,y in dataloader:
X=X.to(device=device)
y=y.to(device=device)
y_pred = model(X)
y_pred = y_pred.argmax(dim=1)
acc = (y_pred == y).float().mean().item()
accs.append(acc)
return np.array(accs).mean()
# 定义训练过程
def train(model=model,
optimizer=optimizer,
loss_fn=loss_fn,
epochs=epochs,
train_dataloader=train_dataloader,
test_dataloader=test_dataloader):
model.to(device=device)
for epoch in range(1, epochs+1):
print(f"正在进行第 {epoch} 轮训练:")
model.train()
for X,y in train_dataloader:
X=X.to(device=device)
y=y.to(device=device)
# 正向传播
y_pred = model(X)
# 清空梯度
optimizer.zero_grad()
# 计算损失
loss = loss_fn(y_pred, y)
# 梯度下降
loss.backward()
# 优化一步
optimizer.step()
print(f"train_acc: {get_acc(dataloader=train_dataloader)}, test_acc: {get_acc(dataloader=test_dataloader)}")
train()
train_acc: 0.9389391447368421, test_acc: 0.8470170497894287 正在进行第 190 轮训练: train_acc: 0.9436677631578947, test_acc: 0.8477982997894287 正在进行第 191 轮训练: train_acc: 0.9451069078947368, test_acc: 0.8504971623420715 正在进行第 192 轮训练: train_acc: 0.9395559210526315, test_acc: 0.8450284123420715 正在进行第 193 轮训练: train_acc: 0.9459292763157895, test_acc: 0.8504971623420715 正在进行第 194 轮训练: train_acc: 0.9469572368421053, test_acc: 0.8493607997894287 正在进行第 195 轮训练: train_acc: 0.9467516447368421, test_acc: 0.8512784123420716 正在进行第 196 轮训练: train_acc: 0.9494243421052632, test_acc: 0.8512784123420716 正在进行第 197 轮训练: train_acc: 0.9488075657894737, test_acc: 0.8512784123420716 正在进行第 198 轮训练: train_acc: 0.9483963815789473, test_acc: 0.8477982997894287 正在进行第 199 轮训练: train_acc: 0.9525082236842105, test_acc: 0.8531960248947144 正在进行第 200 轮训练: train_acc: 0.9523026315789473, test_acc: 0.8539772748947143 优化方向: - 模型定义复杂一些,这网络过于简单 - 可以序列设置更长一些,如果耐心/细心一点,可以分析一下出错的句子,是否不够长的因素 - 训练轮次可以现多一些 |
这里出了一个问题, 就是在一维卷积中增加BN组件 nn.BatchNorm1d(num_features=256), 梯度不会下降了 输出的精度稳定在一个数上,不变化,不知道是什么原因
对于文本处理,还有一个归一化,叫层归一化,后可以转为层归一化处理
|
|
[B,C,L] -- [B,C,L,1]
K = (3,1)
s = (1,1)
P = (1,0)
这样就可以应用2维卷积的方法了
增加的这个1维,属于多维数据中的空维,是个空的维度,
这个维度上没有数据...那增加的是什么 ?
- 是 观察/看 数据的一个角度
示例
import torch
from torch import nn
a = torch.randn(64,512) #[B,L]
a = a.unsqueeze(dim=1) #[64,1,512], [B,C,L]
a = a.unsqueeze(dim=3) #[64,1,512,1],[B,C,L,1]
conv_func = nn.Conv2d(in_channels=1,
out_channels=512,
kernel_size=(3, 1),
stride=(1, 1),
padding=(1, 0)
)
conv_func(a).shape
torch.Size([64, 512, 512, 1])
C从1到512,而L的维度没有变化
2维卷积中BN不会导致结果不变了
|
import os
from tpf import pkl_save,pkl_load
BASE_DIR = "/wks/datasets/hotel_reader"
file_path = os.path.join(BASE_DIR,'data_pkl/word.pkl')
X_train,y_train,X_test,y_test,words_set,word2idx,idx2word = pkl_load(file_path)
# 字典长度
dict_len = len(words_set)
# 序列长度
seq_len = 512
# 训练集
X_train1 = []
for x in X_train:
temp = x + ["<PAD>"] * seq_len
X_train1.append(temp[:seq_len])
# 测试集
X_test1 = []
for x in X_test:
temp = x + ["<PAD>"] * seq_len
X_test1.append(temp[:seq_len])
"""
索引向量化
"""
# 训练集向量化
X_train2 = []
for x in X_train1:
temp = []
for word in x:
idx = word2idx[word] if word in word2idx else word2idx["<UNK>"]
temp.append(idx)
X_train2.append(temp)
# 测试集向量化
X_test2 = []
for x in X_test1:
temp = []
for word in x:
idx = word2idx[word] if word in word2idx else word2idx["<UNK>"]
temp.append(idx)
X_test2.append(temp)
"""
构建数据集
"""
import numpy as np
from torch.utils.data import Dataset
from torch.utils.data import DataLoader
import torch
from torch import nn
from torch.nn import functional as F
class MyDataSet(Dataset):
def __init__(self, X=X_train2, y=y_train):
self.X = X
self.y = y
def __len__(self):
return len(self.X)
def __getitem__(self, idx):
x = self.X[idx]
y = self.y[idx]
return torch.tensor(data=x).long(), torch.tensor(data=y).long()
train_dataset = MyDataSet(X=X_train2, y=y_train)
train_dataloader = DataLoader(dataset=train_dataset, shuffle=True, batch_size=128)
test_dataset = MyDataSet(X=X_test2, y=y_test)
test_dataloader = DataLoader(dataset=test_dataset, shuffle=False, batch_size=256)
train_dataset[0][0][:7]
tensor([ 5004, 10135, 20586, 9530, 16808, 14513, 9530])
|
之前的1维使用的是TextCNN,多尺度的概念 这里的2维,就是普通的卷积提取特征, - 3层卷积提取特征 - 全连接分类
class TextCNN2D(nn.Module):
"""
使用二维卷积来处理文本分类
"""
def __init__(self, num_embeddings, embedding_dim=256,padding_idx=word2idx["<PAD>"]):
super(TextCNN2D, self).__init__()
# 嵌入层
self.embed = nn.Embedding(num_embeddings=num_embeddings,
embedding_dim=embedding_dim,
padding_idx=padding_idx)
# 第一组
self.conv2d1 = nn.Conv2d(in_channels=embedding_dim,
out_channels=512,
kernel_size=(3, 1),
stride=(1, 1),
padding=(1, 0)
)
self.bn1 = nn.BatchNorm2d(num_features=512)
self.maxpool1 = nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1), padding=(0, 0))
# 第二组
self.conv2d2 = nn.Conv2d(in_channels=512,
out_channels=512,
kernel_size=(3, 1),
stride=(1, 1),
padding=(1, 0)
)
self.bn2 = nn.BatchNorm2d(num_features=512)
self.maxpool2 = nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1), padding=(0, 0))
# 第三组
self.conv2d3 = nn.Conv2d(in_channels=512,
out_channels=1024,
kernel_size=(3, 1),
stride=(1, 1),
padding=(1, 0)
)
self.bn3 = nn.BatchNorm2d(num_features=1024)
self.maxpool3 = nn.MaxPool2d(kernel_size=(2, 1), stride=(2, 1), padding=(0, 0))
# 全连接层
self.fc1 = nn.Linear(in_features=65536, out_features=512)
self.dropout = nn.Dropout(p=0.2)
self.fc2 = nn.Linear(in_features=512, out_features=2)
def forward(self, x):
# [B, 86] -- [B, 86, 256]
x = self.embed(x)
# [B, 86, 256] -- [B, 256, 86]
x = x.permute(0, 2, 1)
# [B, 256, 86] -- [B, 256, 86, 1]
x = torch.unsqueeze(input=x, dim=-1)
# 第一组卷积
x = self.conv2d1(x)
x = self.bn1(x)
x = F.relu(x)
# 第一个池化
x = self.maxpool1(x)
# 第二组卷积
x = self.conv2d2(x)
x = self.bn2(x)
x = F.relu(x)
# 第二个池化
x = self.maxpool2(x)
# 第三组卷积
x = self.conv2d3(x)
x = self.bn3(x)
x = F.relu(x)
# 第二个池化
x = self.maxpool3(x)
# 展平层
x = x.view(x.size(0), -1)
# print(x.shape)
x = self.fc1(x)
x = self.dropout(x)
x =self.fc2(x)
return x
model = TextCNN2D(num_embeddings=dict_len, embedding_dim=256, padding_idx=word2idx["<PAD>"])
# 定义优化器
optimizer = torch.optim.SGD(params=model.parameters(), lr=1e-3)
# 定义损失函数
loss_fn = nn.CrossEntropyLoss()
a = torch.randint(0,dict_len-1,(64,seq_len)) # model(a) |
# 定义训练轮次
epochs = 200
device = "cuda:0" if torch.cuda.is_available() else "cpu"
# 定义过程监控函数
def get_acc(dataloader=train_dataloader, model=model):
accs = []
model.to(device=device)
model.eval()
with torch.no_grad():
for X,y in dataloader:
X=X.to(device=device)
y=y.to(device=device)
y_pred = model(X)
y_pred = y_pred.argmax(dim=1)
acc = (y_pred == y).float().mean().item()
accs.append(acc)
return np.array(accs).mean()
# 定义训练过程
def train(model=model,
optimizer=optimizer,
loss_fn=loss_fn,
epochs=epochs,
train_dataloader=train_dataloader,
test_dataloader=test_dataloader):
model.to(device=device)
for epoch in range(1, epochs+1):
print(f"正在进行第 {epoch} 轮训练:")
model.train()
for X,y in train_dataloader:
X=X.to(device=device)
y=y.to(device=device)
# 正向传播
y_pred = model(X)
# 清空梯度
optimizer.zero_grad()
# 计算损失
loss = loss_fn(y_pred, y)
# 梯度下降
loss.backward()
# 优化一步
optimizer.step()
print(f"train_acc: {get_acc(dataloader=train_dataloader)}, test_acc: {get_acc(dataloader=test_dataloader)}")
train()
精度相当1维没有变化,85% 感觉计算量稍微大了一点 |
由于之前BN影响了训练,因为这里注释三层BN中的第二层 # self.bn2 = nn.BatchNorm2d(num_features=512) 出现了过拟合 正在进行第 30 轮训练: train_acc: 0.96484375, test_acc: 0.8110085248947143 正在进行第 31 轮训练: train_acc: 0.9849917763157895, test_acc: 0.8337357997894287 正在进行第 32 轮训练: train_acc: 0.984375, test_acc: 0.8305397748947143 正在进行第 33 轮训练: train_acc: 0.98046875, test_acc: 0.825994324684143 加bn2,依然出现过拟合,说明BN与过拟合没有太多关系,再次注释掉,毕竟少了一点计算 正在进行第 16 轮训练: train_acc: 0.91796875, test_acc: 0.7944602370262146 正在进行第 17 轮训练: train_acc: 0.9342105263157895, test_acc: 0.7936789870262146 正在进行第 18 轮训练: train_acc: 0.9397615131578947, test_acc: 0.8111505746841431 基于前面OneHot编码89%的精度,这里seq_len=1024,全连接分类输入达到了131072 # 全连接层 self.fc1 = nn.Linear(in_features=131072, out_features=512) self.dropout = nn.Dropout(p=0.5) self.fc2 = nn.Linear(in_features=512, out_features=2) 可能向量过长,增加Dropout的概述,随机舍弃一半,再次训练 依然出现了过拟合,并且波动较大 正在进行第 23 轮训练: train_acc: 0.9506578947368421, test_acc: 0.8325994372367859 正在进行第 24 轮训练: train_acc: 0.8122944078947368, test_acc: 0.71015625 正在进行第 25 轮训练: train_acc: 0.9619654605263158, test_acc: 0.8423295497894288 正在进行第 26 轮训练: train_acc: 0.9592927631578947, test_acc: 0.8441761374473572 训练轮次增加精度有所提升,也可能是seq_len设置长包含更多信息的原因
在进行第 70 轮训练:
train_acc: 0.9997944078947368, test_acc: 0.8718039870262146
正在进行第 71 轮训练:
train_acc: 1.0, test_acc: 0.8713778495788574
正在进行第 72 轮训练:
train_acc: 1.0, test_acc: 0.86796875
正在进行第 73 轮训练:
train_acc: 1.0, test_acc: 0.8705965995788574
正在进行第 74 轮训练:
train_acc: 1.0, test_acc: 0.8730113744735718
正在进行第 75 轮训练:
train_acc: 1.0, test_acc: 0.8722301244735717
正在进行第 76 轮训练:
train_acc: 1.0, test_acc: 0.8725852370262146
正在进行第 77 轮训练:
train_acc: 1.0, test_acc: 0.8733664870262146
|