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 |