`)
数据准备(在我资源库里)基础接入import warnings # 警告控制库: 屏蔽程序运行时无关的警告信息.import pandas as pd # 数据处理库: 读写数据, 数据处理.import numpy as np # 数值计算库: 数组处理, 数值计算import jieba # 分词库:import torch # Pytorch主库, 深度学习核心框架import torch.nn as nn # 构建神经网络from torch.utils.data import Dataset, DataLoader # 数据处理库: 构建数据集, 构建数据加载器from sklearn.model_selection import train_test_split # 数据集划分from sklearn.metrics import accuracy_score # 模型评估(准确率)import torch.optim as optim # 优化器库: 优化器、# todo 0.定义变量, 记录 超参数.MAX_VOCAB_SIZE 10000 # 词表最大容量: 保留出现概率最高的10000个词MAX_LEN 50 # 句子的最大长度: 所有句子统一成50个词, 不足不0, 超长阶段.EMBED_DIM 64 # 词向量维度: 词向量维度为64HIDDEN_DIM 64 # 隐藏层维度: 隐藏层维度为64BATCH_SIZE 64 # 批次大小: 批次大小为64EPOCHS 10 # 训练轮数: 训练轮数设置为10轮.LR 0.001 # 学习率: 0.001加载数据# todo 1.定义函数: 加载数据def load_data():# 1.读取tsv文件, 构建DataFrame对象.df pd.read_csv(./data/train.tsv, sep\t, encodingutf-8)# 2. 删除评论内容为空的行.它会把 sentence 或 label 任一为空的行删掉。df df.dropna(subset[sentence, label])# 3. 提取评论列, 转换为字符串类型 - 转成Python列表texts df[sentence].astype(str).tolist()# 4. 提取标签列 - 转成numpy数组labels df[label].values# 5. 打印数据.# print(f数据加载完毕, 共: {len(texts)} 条数据) # 2961 条数据# 6. 返回评论文本列表和标签数组.return texts, labels数据处理# todo 2. 定义函数: 实现文本转数字序列.def text2seq(texts):# 1. 中文分词, 对每条评论使用jieba分词, 生成分词列表.tokens [jieba.lcut(t) for t in texts]# 2. 统计每个词语的出现频率.# 2.1 定义空字典, 用于存储: 词频. 格式为: {词1: 次数, 词2: 次数...}word_count {}# 2.2 遍历所有分词后的句子.for sent in tokens: # sent的格式: [词1, 词2, 词3...]# 2.3 遍历句子中的每个词语.for word in sent:# 2.4 词频统计, 不存在则默认为0, 然后1word_count[word] word_count.get(word, 0) 1# 3. 构件 词语 - 数字 的映射表# 3.1 初始化词表, 0表示天重复, 1表示未知词.vocab {PAD: 0, UNK: 1}# 3.2 按词频从高到低排序, 取: 前10000 - 2个高频词.top_words sorted(word_count, keyword_count.get, reverseTrue)[:MAX_VOCAB_SIZE - 2]# 3.3 遍历高频词, 依次加入词表, 并分配唯一编号.for w in top_words:vocab[w] len(vocab)# 4. 将分词句子转为数字序列, 并统一长度.# 4.1 定义空列表存储最终序列.seqs []# 4.2 遍历每条评论(分词后的句子)for sent in tokens:# 4.3 词语转数字, 词表中没有的词用1代替, 即: 未知词.seq [vocab.get(w, 1) for w in sent]# 4.4 序列长度统一: 超过50截断, 不足50则填充0.seq seq[:MAX_LEN] [0] * (MAX_LEN - len(seq))# 4.5 添加到最终列表中.seqs.append(seq)# 5. 返回数字序列和词表.return np.array(seqs), vocab自定义数据集类# todo 3.自定义数据集类.class CommentDataset(Dataset):# todo 3.1 初始化函数: 传入序列和标签.def __init__(self, seqs, labels):# 1. 存储数字列表self.seqs seqs# 2. 存储对应标签.self.labels labels# todo 3.2 获取数据集总样本数量.def __len__(self):return len(self.seqs)# todo 3.3 根据索引获取单条数据(张量格式)def __getitem__(self, idx):# 返回 长整型序列张量 浮点型标签张量return torch.LongTensor(self.seqs[idx]), torch.FloatTensor([self.labels[idx]])LSTM模型搭建# todo 4. LSTM模型搭建class LSTMModel(nn.Module):# todo 4.1 初始化网络结构def __init__(self, vocab_size):# 1. 初始化父类成员.super().__init__()# 2. 词嵌入层.# 参1: 词表大小, 参2: 词嵌入维度, 参3: 填充索引.self.embedding nn.Embedding(vocab_size, EMBED_DIM, padding_idx0)# 3. LSTM层# 参1: 输入维度, 参2: 隐藏层维度, 参3: 是否把批次放置到第一个维度self.lstm nn.LSTM(EMBED_DIM, HIDDEN_DIM, batch_firstTrue)# 4. 全连接层, 将LSTM输出转为1个值(二分类输出)self.fc nn.Linear(HIDDEN_DIM, 1)# todo 4.2 前向传播.def forward(self, x):# 1.词嵌入层.x self.embedding(x)# 2. LSTM层out, (h, c) self.lstm(x)# 3. 全连接层: 取最后一层(最后1个时间步)隐藏状态, 输入全连接层.out self.fc(h[-1])# 4. 返回模型的最终输出.return out模型训练# todo 5. 定义函数, 表示: 模型训练. 这里是1轮循环动作.def train(model, loader, criterion, optimizer):# 1. 设置模型为训练模式.model.train()# 2. 定义变量, 记录: 本轮总损失.total_loss 0# 3. 定义变量, 记录: 预测正确的样本个数.correct 0# 4. 定义变量, 记录: 总样本数.total 0# 5. 从数据加载器中获取每批次数据.for seq, label in loader: # seq: 数字序列张量(x特征) label: 标签张量(y标签)# 5.1 清空梯度.optimizer.zero_grad()# 5.2 前向传播, 得到输出.output model(seq)# 5.3 计算损失.loss criterion(output, label) # output: 预测值, label: 真实标签# 5.4 反向传播, 计算梯度.loss.backward()# 5.5 优化器更新参数.optimizer.step()# 6. 统计训练指标.# 6.1 累加当前批次损失.total_loss loss.item()# 6.2 经过sigmoid 0.5判为好评, 否则差评.pred (torch.sigmoid(output) 0.5).float()# 6.3 统计预测正确的样本个数.correct (pred label).sum().item()# 6.4 统计总样本数.total label.size(0)# 7. 返回(本轮的)平均损失 和 训练集准确率return total_loss / len(loader), correct / total模型测试.# todo 6. 定义函数, 表示: 模型测试.def evaluate(model, loader):# 1. 模型设置为评估模式.model.eval()# 2. 定义变量, 存储所有预测结果.preds []# 3. 定义变量, 存储所有真实标签.trues []# 4. 评估阶段 不计算梯度, 节省显存.with torch.no_grad():# 4.1 遍历测试集的每一批数据for seq, label in loader:# 4.2 模型的预测.output model(seq)# 4.3 转为0/1的预测结果.pred (torch.sigmoid(output) 0.5).numpy()# 4.4 保存预测结果.preds.extend(pred)# 4.5 保存真实标签.trues.extend(label.numpy())# 5. 计算并返回测试集的准确率.return accuracy_score(trues, preds)# todo 7.单条评论预测函数.def predict(model, text, vocab):# 1.对输入的评论文本分词.words jieba.lcut(text)# 2. 分词转数字序列, 未知词用1替代.seq [vocab.get(w, 1) for w in words]# 3. 统一序列长度为50.seq seq[:MAX_LEN] [0] * (MAX_LEN - len(seq))# 4. 转为张量, 并增加1个批次维度.seq torch.LongTensor([seq])模型预测.# 5. 模型预测.# 5.1 开启评估模式.model.eval()# 5.2 不计算梯度.with torch.no_grad():# 5.3 前向传播, 获取模型输出.output model(seq)# 5.4 获取预测结果, 转为0 ~ 1的置信度(即: 模型认为这句话是好评的概率)prob torch.sigmoid(output).item()# 6. 返回预测结果.# 参1: 预测结果, 参2: 模型认为这句话是好评的概率(置信率)return 好评 if prob 0.5 else 差评, round(prob, 4)结果预览if __name__ __main__:# 1. 加载数据texts, labels load_data()# 2. 文本预处理: 转数字序列 构建词表seqs, vocab text2seq(texts)vocab_size len(vocab)# print(fseqs 酒店评论转数字列表: {seqs}, {seqs.shape}) # (2961, 50)# print(fvocab 词表: {vocab}, {vocab_size}) # 10000# 3. 划分训练集和测试集, 比例: 8:2train_seq, test_seq, train_label, test_label train_test_split(seqs, labels, test_size0.2, random_state42)# 4. 构建数据加载器. 流程: 数据 - 数据集对象TensorDataset - 数据加载器DataLoadertrain_dataloader DataLoader(datasetCommentDataset(train_seq, train_label), batch_sizeBATCH_SIZE, shuffleTrue) # 训练集打乱顺序test_dataloader DataLoader(datasetCommentDataset(test_seq, test_label), batch_sizeBATCH_SIZE, shuffleFalse) # 测试集不打乱顺序# 5. 初始化模型, 损失函数, 优化器.model LSTMModel(vocab_size)criterion nn.BCEWithLogitsLoss()optimizer optim.Adam(model.parameters(), lrLR)# 6. 模型训练.print(\n 开始训练...)for epoch in range(EPOCHS):# 6.1 具体的1轮循环过程.loss, acc train(model, train_dataloader, criterion, optimizer)# 6.2 打印(本轮的)训练结果.print(fEpoch: {epoch 1:2d} | Loss: {loss:.3f} | 训练准确率: {acc:.3f})# 7. 模型评估 - 在测试集上评估模型.test_acc evaluate(model, test_dataloader)print(f\n 测试集准确率: {test_acc:.3f})print(♥️ * 15)# 8. 模型预测 - 单条评论 看好评还是差评.test_list [房间干净, 服务很好, 下次还来住,环境差, 隔音不好, 非常不满意]# 逐个句子进行预测for t in test_list:res, prob predict(model, t, vocab)print(f评论: {t})print(f结果: {res}, 置信率(模型认为这句话是好评的概率): {prob:.3f}\n)完整代码# 导包 import warnings # 警告控制库: 屏蔽程序运行时无关的警告信息. import pandas as pd # 数据处理库: 读写数据, 数据处理. import numpy as np # 数值计算库: 数组处理, 数值计算 import jieba # 分词库: import torch # Pytorch主库, 深度学习核心框架 import torch.nn as nn # 构建神经网络 from torch.utils.data import Dataset, DataLoader # 数据处理库: 构建数据集, 构建数据加载器 from sklearn.model_selection import train_test_split # 数据集划分 from sklearn.metrics import accuracy_score # 模型评估(准确率) import torch.optim as optim # 优化器库: 优化器 # 屏蔽所有警告信息, 让控制台输出更整洁. warnings.filterwarnings(ignore) # todo 0.定义变量, 记录 超参数. MAX_VOCAB_SIZE 10000 # 词表最大容量: 保留出现概率最高的10000个词 MAX_LEN 50 # 句子的最大长度: 所有句子统一成50个词, 不足不0, 超长阶段. EMBED_DIM 64 # 词向量维度: 词向量维度为64 HIDDEN_DIM 64 # 隐藏层维度: 隐藏层维度为64 BATCH_SIZE 64 # 批次大小: 批次大小为64 EPOCHS 10 # 训练轮数: 训练轮数设置为10轮. LR 0.001 # 学习率: 0.001 # todo 1.定义函数: 加载数据 def load_data(): # 1.读取tsv文件, 构建DataFrame对象. df pd.read_csv(./data/train.tsv, sep\t, encodingutf-8) # 2. 删除评论内容为空的行. df df.dropna(subset[sentence, label]) # 3. 提取评论列, 转换为字符串类型 - 转成Python列表 texts df[sentence].astype(str).tolist() # 4. 提取标签列 - 转成numpy数组 labels df[label].values # 5. 打印数据. # print(f数据加载完毕, 共: {len(texts)} 条数据) # 2960 条数据 # 6. 返回评论文本列表和标签数组. return texts, labels # todo 2. 定义函数: 实现文本转数字序列. def text2seq(texts): # 1. 中文分词, 对每条评论使用jieba分词, 生成分词列表. tokens [jieba.lcut(t) for t in texts] # 2. 统计每个词语的出现频率. # 2.1 定义空字典, 用于存储: 词频. 格式为: {词1: 次数, 词2: 次数...} word_count {} # 2.2 遍历所有分词后的句子. for sent in tokens: # sent的格式: [词1, 词2, 词3...] # 2.3 遍历句子中的每个词语. for word in sent: # 2.4 词频统计, 不存在则默认为0, 然后1 word_count[word] word_count.get(word, 0) 1 # 3. 构件 词语 - 数字 的映射表 # 3.1 初始化词表, 0表示天重复, 1表示未知词. vocab {PAD: 0, UNK: 1} # 3.2 按词频从高到低排序, 取: 前10000 - 2个高频词. top_words sorted(word_count, keyword_count.get, reverseTrue)[:MAX_VOCAB_SIZE - 2] # 3.3 遍历高频词, 依次加入词表, 并分配唯一编号. for w in top_words: vocab[w] len(vocab) # 4. 将分词句子转为数字序列, 并统一长度. # 4.1 定义空列表存储最终序列. seqs [] # 4.2 遍历每条评论(分词后的句子) for sent in tokens: # 4.3 词语转数字, 词表中没有的词用1代替, 即: 未知词. seq [vocab.get(w, 1) for w in sent] # 4.4 序列长度统一: 超过50截断, 不足50则填充0. seq seq[:MAX_LEN] [0] * (MAX_LEN - len(seq)) # 4.5 添加到最终列表中. seqs.append(seq) # 5. 返回数字序列和词表. return np.array(seqs), vocab # todo 3.自定义数据集类. class CommentDataset(Dataset): # todo 3.1 初始化函数: 传入序列和标签. def __init__(self, seqs, labels): # 1. 存储数字列表 self.seqs seqs # 2. 存储对应标签. self.labels labels # todo 3.2 获取数据集总样本数量. def __len__(self): return len(self.seqs) # todo 3.3 根据索引获取单条数据(张量格式) def __getitem__(self, idx): # 返回 长整型序列张量 浮点型标签张量 return torch.LongTensor(self.seqs[idx]), torch.FloatTensor([self.labels[idx]]) # todo 4. LSTM模型搭建 class LSTMModel(nn.Module): # todo 4.1 初始化网络结构 def __init__(self, vocab_size): # 1. 初始化父类成员. super().__init__() # 2. 词嵌入层. # 参1: 词表大小, 参2: 词嵌入维度, 参3: 填充索引. self.embedding nn.Embedding(vocab_size, EMBED_DIM, padding_idx0) # 3. LSTM层 # 参1: 输入维度, 参2: 隐藏层维度, 参3: 是否把批次放置到第一个维度 self.lstm nn.LSTM(EMBED_DIM, HIDDEN_DIM, batch_firstTrue) # 4. 全连接层, 将LSTM输出转为1个值(二分类输出) self.fc nn.Linear(HIDDEN_DIM, 1) # todo 4.2 前向传播. def forward(self, x): # 1.词嵌入层. x self.embedding(x) # 2. LSTM层 out, (h, c) self.lstm(x) # 3. 全连接层: 取最后一层(最后1个时间步)隐藏状态, 输入全连接层. out self.fc(h[-1]) # 4. 返回模型的最终输出. return out # todo 5. 定义函数, 表示: 模型训练. 这里是1轮循环动作. def train(model, loader, criterion, optimizer): # 1. 设置模型为训练模式. model.train() # 2. 定义变量, 记录: 本轮总损失. total_loss 0 # 3. 定义变量, 记录: 预测正确的样本个数. correct 0 # 4. 定义变量, 记录: 总样本数. total 0 # 5. 从数据加载器中获取每批次数据. for seq, label in loader: # seq: 数字序列张量(x特征) label: 标签张量(y标签) # 5.1 清空梯度. optimizer.zero_grad() # 5.2 前向传播, 得到输出. output model(seq) # 5.3 计算损失. loss criterion(output, label) # output: 预测值, label: 真实标签 # 5.4 反向传播, 计算梯度. loss.backward() # 5.5 优化器更新参数. optimizer.step() # 6. 统计训练指标. # 6.1 累加当前批次损失. total_loss loss.item() # 6.2 经过sigmoid 0.5判为好评, 否则差评. pred (torch.sigmoid(output) 0.5).float() # 6.3 统计预测正确的样本个数. correct (pred label).sum().item() # 6.4 统计总样本数. total label.size(0) # 7. 返回(本轮的)平均损失 和 训练集准确率 return total_loss / len(loader), correct / total # todo 6. 定义函数, 表示: 模型测试. def evaluate(model, loader): # 1. 模型设置为评估模式. model.eval() # 2. 定义变量, 存储所有预测结果. preds [] # 3. 定义变量, 存储所有真实标签. trues [] # 4. 评估阶段 不计算梯度, 节省显存. with torch.no_grad(): # 4.1 遍历测试集的每一批数据 for seq, label in loader: # 4.2 模型的预测. output model(seq) # 4.3 转为0/1的预测结果. pred (torch.sigmoid(output) 0.5).numpy() # 4.4 保存预测结果. preds.extend(pred) # 4.5 保存真实标签. trues.extend(label.numpy()) # 5. 计算并返回测试集的准确率. return accuracy_score(trues, preds) # todo 7.单条评论预测函数. def predict(model, text, vocab): # 1.对输入的评论文本分词. words jieba.lcut(text) # 2. 分词转数字序列, 未知词用1替代. seq [vocab.get(w, 1) for w in words] # 3. 统一序列长度为50. seq seq[:MAX_LEN] [0] * (MAX_LEN - len(seq)) # 4. 转为张量, 并增加1个批次维度. seq torch.LongTensor([seq]) # 5. 模型预测. # 5.1 开启评估模式. model.eval() # 5.2 不计算梯度. with torch.no_grad(): # 5.3 前向传播, 获取模型输出. output model(seq) # 5.4 获取预测结果, 转为0 ~ 1的置信度(即: 模型认为这句话是好评的概率) prob torch.sigmoid(output).item() # 6. 返回预测结果. # 参1: 预测结果, 参2: 模型认为这句话是好评的概率(置信率) return 好评 if prob 0.5 else 差评, round(prob, 4) # todo 8.测试代码. if __name__ __main__: # 1. 加载数据 texts, labels load_data() # 2. 文本预处理: 转数字序列 构建词表 seqs, vocab text2seq(texts) vocab_size len(vocab) # print(fseqs 酒店评论转数字列表: {seqs}, {seqs.shape}) # (2960, 50) # print(fvocab 词表: {vocab}, {vocab_size}) # 10000 # 3. 划分训练集和测试集, 比例: 8:2 train_seq, test_seq, train_label, test_label train_test_split(seqs, labels, test_size0.2, random_state42) # 4. 构建数据加载器. 流程: 数据 - 数据集对象TensorDataset - 数据加载器DataLoader train_dataloader DataLoader(datasetCommentDataset(train_seq, train_label), batch_sizeBATCH_SIZE, shuffleTrue) # 训练集打乱顺序 test_dataloader DataLoader(datasetCommentDataset(test_seq, test_label), batch_sizeBATCH_SIZE, shuffleFalse) # 测试集不打乱顺序 # 5. 初始化模型, 损失函数, 优化器. model LSTMModel(vocab_size) criterion nn.BCEWithLogitsLoss() optimizer optim.Adam(model.parameters(), lrLR) # 6. 模型训练. print(\n 开始训练...) for epoch in range(EPOCHS): # 6.1 具体的1轮循环过程. loss, acc train(model, train_dataloader, criterion, optimizer) # 6.2 打印(本轮的)训练结果. print(fEpoch: {epoch 1:2d} | Loss: {loss:.3f} | 训练准确率: {acc:.3f}) # 7. 模型评估 - 在测试集上评估模型. test_acc evaluate(model, test_dataloader) print(f\n 测试集准确率: {test_acc:.3f}) print(♥️ * 15) # 8. 模型预测 - 单条评论 看好评还是差评. test_list [ 房间干净, 服务很好, 下次还来住, 环境差, 隔音不好, 非常不满意 ] # 逐个句子进行预测 for t in test_list: res, prob predict(model, t, vocab) print(f评论: {t}) print(f结果: {res}, 置信率(模型认为这句话是好评的概率): {prob:.3f}\n)