调用流程图:------------------------------以下是代码------------------------------------------------run.py:
import time
# 导入time模块,用于记录数据加载和训练时间import torch
# 导入PyTorch框架,用于构建和训练深度学习模型import numpy as np
# 导入NumPy库,用于进行数值计算和数组操作from train_eval import train, init_network
# 从train_eval.py文件中导入train函数(训练模型)和init_network函数(初始化网络参数)from importlib import import_module
# 从importlib中导入import_module函数,用于动态导入模块import argparse
# 导入argparse模块,用于解析命令行参数from tensorboardX import SummaryWriter
# 导入tensorboardX的SummaryWriter类,用于可视化训练过程中的指标(如loss、accuracy等)# 创建一个ArgumentParser对象,描述为“Chinese Text Classification”
parser = argparse.ArgumentParser(description='Chinese Text Classification')# 添加--model参数,类型为字符串,必须输入,帮助信息提示选择TextCNN、TextRNN等模型
parser.add_argument('--model', type=str, required=True, help='choose a model: TextCNN, TextRNN, FastText, TextRCNN, TextRNN_Att, DPCNN, Transformer')# 添加--embedding参数,默认值为'pre_trained',类型为字符串,帮助信息提示选择预训练或随机词向量
parser.add_argument('--embedding', default='pre_trained', type=str, help='random or pre_trained')# 添加--word参数,默认False,类型为布尔值,帮助信息提示是否使用词粒度(True)还是字符粒度(False)
parser.add_argument('--word', default=False, type=bool, help='True for word, False for char')# 解析命令行传入的参数,并保存在args变量中
args = parser.parse_args()if __name__ == '__main__':# 程序入口,表示这是主程序执行部分# 设置使用的数据集名称为'THUCNews'dataset = 'THUCNews' # 数据集# 搜狗新闻: embedding_SougouNews.npz, 腾讯: embedding_Tencent.npz, 随机初始化: random# 初始化词向量路径为搜狗新闻预训练词向量文件embedding = 'embedding_SougouNews.npz'# 如果用户指定使用随机初始化词向量if args.embedding == 'random':# 则将embedding设置为'random',表示不使用预训练词向量embedding = 'random'# 获取用户指定的模型名称并保存到model_name变量中model_name = args.model # TextCNN, TextRNN等# 如果模型是FastTextif model_name == 'FastText':# 动态导入FastText专用的数据处理工具函数from utils_fasttext import build_dataset, build_iterator, get_time_dif# FastText模型强制使用随机初始化词向量embedding = 'random'# 如果不是FastText模型else:# 导入通用的数据处理工具函数from utils import build_dataset, build_iterator, get_time_dif# 使用import_module动态导入对应的模型模块,例如models.TextCNNx = import_module('models.' + model_name)# 实例化模型配置类Config,传入数据集和词向量信息config = x.Config(dataset, embedding)# 设置numpy的随机种子为1,确保每次运行结果一致np.random.seed(1)# 设置PyTorch CPU模式下的随机种子为1torch.manual_seed(1)# 设置所有CUDA设备的随机种子为1,保证GPU训练结果可复现torch.cuda.manual_seed_all(1)# 设置CuDNN为确定性模式,以确保卷积运算的结果可重复torch.backends.cudnn.deterministic = True# 记录开始加载数据的时间戳start_time = time.time()# 打印数据加载提示信息print("Loading data...")# 构建数据集,包括词汇表vocab和训练、验证、测试数据vocab, train_data, dev_data, test_data = build_dataset(config, args.word)# 创建训练数据迭代器train_iter = build_iterator(train_data, config)# 创建验证数据迭代器dev_iter = build_iterator(dev_data, config)# 创建测试数据迭代器test_iter = build_iterator(test_data, config)# 计算数据加载所用时间time_dif = get_time_dif(start_time)# 打印数据加载耗时print("Time usage:", time_dif)# train# 将词汇表大小赋值给config.n_vocab,供模型使用config.n_vocab = len(vocab)# 实例化模型,并将其移动到指定设备(CPU/GPU)model = x.Model(config).to(config.device)# 创建TensorBoard日志写入器,log目录包含当前时间戳以便区分不同训练日志writer = SummaryWriter(log_dir=config.log_path + '/' + time.strftime('%m-%d_%H.%M', time.localtime()))# 如果不是Transformer模型if model_name != 'Transformer':# 调用init_network函数初始化网络参数init_network(model)# 打印模型参数信息print(model.parameters)# 调用train函数开始训练模型,传入配置、模型和数据迭代器train(config, model, train_iter, dev_iter, test_iter, writer)
train_eval.py
import time
# 导入time模块,用于记录数据加载和训练时间import torch
# 导入PyTorch框架,用于构建和训练深度学习模型import numpy as np
# 导入NumPy库,用于进行数值计算和数组操作from train_eval import train, init_network
# 从train_eval.py文件中导入train函数(训练模型)和init_network函数(初始化网络参数)from importlib import import_module
# 从importlib中导入import_module函数,用于动态导入模块import argparse
# 导入argparse模块,用于解析命令行参数from tensorboardX import SummaryWriter
# 导入tensorboardX的SummaryWriter类,用于可视化训练过程中的指标(如loss、accuracy等)# 创建一个ArgumentParser对象,描述为“Chinese Text Classification”
parser = argparse.ArgumentParser(description='Chinese Text Classification')# 添加--model参数,类型为字符串,必须输入,帮助信息提示选择TextCNN、TextRNN等模型
parser.add_argument('--model', type=str, required=True, help='choose a model: TextCNN, TextRNN, FastText, TextRCNN, TextRNN_Att, DPCNN, Transformer')# 添加--embedding参数,默认值为'pre_trained',类型为字符串,帮助信息提示选择预训练或随机词向量
parser.add_argument('--embedding', default='pre_trained', type=str, help='random or pre_trained')# 添加--word参数,默认False,类型为布尔值,帮助信息提示是否使用词粒度(True)还是字符粒度(False)
parser.add_argument('--word', default=False, type=bool, help='True for word, False for char')# 解析命令行传入的参数,并保存在args变量中
args = parser.parse_args()if __name__ == '__main__':# 程序入口,表示这是主程序执行部分# 设置使用的数据集名称为'THUCNews'dataset = 'THUCNews' # 数据集# 搜狗新闻: embedding_SougouNews.npz, 腾讯: embedding_Tencent.npz, 随机初始化: random# 初始化词向量路径为搜狗新闻预训练词向量文件embedding = 'embedding_SougouNews.npz'# 如果用户指定使用随机初始化词向量if args.embedding == 'random':# 则将embedding设置为'random',表示不使用预训练词向量embedding = 'random'# 获取用户指定的模型名称并保存到model_name变量中model_name = args.model # TextCNN, TextRNN等# 如果模型是FastTextif model_name == 'FastText':# 动态导入FastText专用的数据处理工具函数from utils_fasttext import build_dataset, build_iterator, get_time_dif# FastText模型强制使用随机初始化词向量embedding = 'random'# 如果不是FastText模型else:# 导入通用的数据处理工具函数from utils import build_dataset, build_iterator, get_time_dif# 使用import_module动态导入对应的模型模块,例如models.TextCNNx = import_module('models.' + model_name)# 实例化模型配置类Config,传入数据集和词向量信息config = x.Config(dataset, embedding)# 设置numpy的随机种子为1,确保每次运行结果一致np.random.seed(1)# 设置PyTorch CPU模式下的随机种子为1torch.manual_seed(1)# 设置所有CUDA设备的随机种子为1,保证GPU训练结果可复现torch.cuda.manual_seed_all(1)# 设置CuDNN为确定性模式,以确保卷积运算的结果可重复torch.backends.cudnn.deterministic = True# 记录开始加载数据的时间戳start_time = time.time()# 打印数据加载提示信息print("Loading data...")# 构建数据集,包括词汇表vocab和训练、验证、测试数据vocab, train_data, dev_data, test_data = build_dataset(config, args.word)# 创建训练数据迭代器train_iter = build_iterator(train_data, config)# 创建验证数据迭代器dev_iter = build_iterator(dev_data, config)# 创建测试数据迭代器test_iter = build_iterator(test_data, config)# 计算数据加载所用时间time_dif = get_time_dif(start_time)# 打印数据加载耗时print("Time usage:", time_dif)# train# 将词汇表大小赋值给config.n_vocab,供模型使用config.n_vocab = len(vocab)# 实例化模型,并将其移动到指定设备(CPU/GPU)model = x.Model(config).to(config.device)# 创建TensorBoard日志写入器,log目录包含当前时间戳以便区分不同训练日志writer = SummaryWriter(log_dir=config.log_path + '/' + time.strftime('%m-%d_%H.%M', time.localtime()))# 如果不是Transformer模型if model_name != 'Transformer':# 调用init_network函数初始化网络参数init_network(model)# 打印模型参数信息print(model.parameters)# 调用train函数开始训练模型,传入配置、模型和数据迭代器train(config, model, train_iter, dev_iter, test_iter, writer)
utils.py:
# coding: UTF-8
# 指定文件编码为UTF-8,支持中文字符import os
# 导入os模块,用于进行操作系统路径和文件操作import torch
# 导入PyTorch框架,用于构建和训练深度学习模型import numpy as np
# 导入NumPy库,用于数值计算和数组操作import pickle as pkl
# 导入pickle模块,用于序列化和反序列化Python对象(如保存和加载词汇表)from tqdm import tqdm
# 从tqdm导入tqdm类,用于在循环中显示进度条import time
# 导入time模块,用于记录时间from datetime import timedelta
# 从datetime导入timedelta类,用于表示时间差MAX_VOCAB_SIZE = 10000
# 设置最大词表大小为10000个词UNK, PAD = '<UNK>', '<PAD>'
# 定义特殊符号:未知词用<UNK>表示,填充符用<PAD>表示def build_vocab(file_path, tokenizer, max_size, min_freq):# 构建词汇表函数,参数包括数据路径、分词器、最大词表大小和最小出现频率vocab_dic = {}# 初始化一个空字典来存储词汇及其频次with open(file_path, 'r', encoding='UTF-8') as f:# 打开文本文件进行读取# 遍历每一行,并显示进度条for line in tqdm(f):# 去除首尾空白字符lin = line.strip()if not lin:continue# 如果是空行,则跳过# 使用制表符分割,取第一个部分作为文本内容content = lin.split('\t')[0]# 使用指定的tokenizer对内容进行分词for word in tokenizer(content):# 统计每个词的出现次数vocab_dic[word] = vocab_dic.get(word, 0) + 1# 筛选出现频率大于等于min_freq的词,并按频率排序后取前max_size个词vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] >= min_freq], key=lambda x: x[1], reverse=True)[:max_size]# 将筛选后的词列表转换为词到索引的映射字典vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}# 添加特殊符号到词典中,UNK排在最后,PAD在其后vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic) + 1})# 返回构建好的词汇表字典return vocab_dic# 构建数据集函数,参数为配置对象config和是否使用词粒度标志use_word
def build_dataset(config, ues_word):# 如果使用词粒度if ues_word:# 使用空格分词,即词级别处理tokenizer = lambda x: x.split(' ')else:# 否则使用字符级别分词,将字符串转为字符列表tokenizer = lambda x: [y for y in x]# 如果已经存在词汇表文件if os.path.exists(config.vocab_path):# 直接加载已有的词汇表vocab = pkl.load(open(config.vocab_path, 'rb'))# 如果不存在词汇表文件else:# 调用build_vocab函数构建新的词汇表vocab = build_vocab(config.train_path, tokenizer=tokenizer, max_size=MAX_VOCAB_SIZE, min_freq=1)# 将新构建的词汇表保存到磁盘文件中pkl.dump(vocab, open(config.vocab_path, 'wb'))# 打印词汇表大小print(f"Vocab size: {len(vocab)}")# 加载单个数据集(训练/验证/测试)的内部函数def load_dataset(path, pad_size=32):# 初始化数据容器contents = []# 打开数据文件with open(path, 'r', encoding='UTF-8') as f:# 逐行读取并显示进度条for line in tqdm(f):# 去除前后空白字符lin = line.strip()# 空行跳过if not lin:continue# 使用制表符分割,获取文本内容和标签content, label = lin.split('\t')# 初始化当前句子的token列表words_line = []# 使用tokenizer对内容进行分词或分字token = tokenizer(content)# 获取原始序列长度seq_len = len(token)# 如果设置了固定长度if pad_size:# 如果当前句子长度小于pad_sizeif len(token) < pad_size:# 用<PAD>补齐至pad_size长度token.extend([vocab.get(PAD)] * (pad_size - len(token)))else:token = token[:pad_size]# 否则截断至pad_size长度seq_len = pad_size# word to idfor word in token:# 将词转换为对应的索引# 如果词不在词典中,使用UNK替代words_line.append(vocab.get(word, vocab.get(UNK)))# 将处理后的数据加入contents列表contents.append((words_line, int(label), seq_len))# 返回处理好的数据集return contents # [([...], 0), ([...], 1), ...]# 分别加载训练集、验证集和测试集train = load_dataset(config.train_path, config.pad_size)dev = load_dataset(config.dev_path, config.pad_size)test = load_dataset(config.test_path, config.pad_size)# 返回词汇表和三个数据集return vocab, train, dev, test# 自定义数据迭代器类
class DatasetIterater(object):# 初始化方法def __init__(self, batches, batch_size, device):# 设置批量大小self.batch_size = batch_size# 数据批次列表self.batches = batches# 计算完整batch的数量self.n_batches = len(batches) // batch_sizeself.residue = False # 是否有剩余样本if len(batches) % self.n_batches != 0:self.residue = True# 如果不能整除,标记存在残余数据# 当前遍历的起始索引self.index = 0# 数据所在设备(CPU/GPU)self.device = device# 将数据转换为张量的方法def _to_tensor(self, datas):# 输入序列转为LongTensor并移动到指定设备x = torch.LongTensor([_[0] for _ in datas]).to(self.device)# 标签转为LongTensor并移动到指定设备y = torch.LongTensor([_[1] for _ in datas]).to(self.device)# 序列长度信息转为LongTensor并移动到指定设备seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)# 返回输入、长度和标签return (x, seq_len), y# 实现迭代器的__next__方法def __next__(self):# 如果有残余数据且到达最后一个不完整的batchif self.residue and self.index == self.n_batches:# 取出残余数据batches = self.batches[self.index * self.batch_size: len(self.batches)]# 更新索引self.index += 1# 转换为张量batches = self._to_tensor(batches)# 返回当前batchreturn batches# 如果索引超过总batch数elif self.index > self.n_batches:self.index = 0# 重置索引raise StopIteration# 抛出停止迭代异常else:# 正常取一个batch# 取出当前batch的数据batches = self.batches[self.index * self.batch_size: (self.index + 1) * self.batch_size]# 更新索引self.index += 1# 转换为张量batches = self._to_tensor(batches)# 返回当前batchreturn batchesdef __iter__(self):# 实现__iter__方法,返回自身即可开始迭代return selfdef __len__(self):# 获取总batch数量if self.residue:# 如果有残余数据,加1return self.n_batches + 1else:# 否则直接返回完整batch数return self.n_batches# 构建数据迭代器函数
def build_iterator(dataset, config):# 创建DatasetIterater实例iter = DatasetIterater(dataset, config.batch_size, config.device)# 返回迭代器对象return iterdef get_time_dif(start_time):"""获取已使用时间"""end_time = time.time()time_dif = end_time - start_time# 返回格式化的时间差对象return timedelta(seconds=int(round(time_dif)))if __name__ == "__main__":'''提取预训练词向量'''# 下面的目录、文件名按需更改。# 定义训练数据集的目录train_dir = "./THUCNews/data/train.txt"# 定义词汇表的目录vocab_dir = "./THUCNews/data/vocab.pkl"# 定义预训练向量的目录pretrain_dir = "./THUCNews/data/sgns.sogou.char"# 定义词向量的维度emb_dim = 300# 定义保存修剪后嵌入层的目录filename_trimmed_dir = "./THUCNews/data/embedding_SougouNews"# 检查词汇表是否已存在if os.path.exists(vocab_dir):# 如果存在,加载词汇表word_to_id = pkl.load(open(vocab_dir, 'rb'))else:# 如果不存在,定义一个分词函数,以字为单位构建词表tokenizer = lambda x: [y for y in x]# 使用训练数据构建词汇表word_to_id = build_vocab(train_dir, tokenizer=tokenizer, max_size=MAX_VOCAB_SIZE, min_freq=1)# 保存词汇表pkl.dump(word_to_id, open(vocab_dir, 'wb'))# 初始化随机嵌入层矩阵,维度为词汇表大小乘以词向量维度embeddings = np.random.rand(len(word_to_id), emb_dim)# 打开预训练的词向量文件f = open(pretrain_dir, "r", encoding='UTF-8')# 遍历预训练的词向量文件,更新嵌入层矩阵for i, line in enumerate(f.readlines()):lin = line.strip().split(" ")# 如果当前词在词汇表中,则更新其对应的词向量if lin[0] in word_to_id:idx = word_to_id[lin[0]]emb = [float(x) for x in lin[1:301]]embeddings[idx] = np.asarray(emb, dtype='float32')# 关闭文件f.close()# 保存修剪后的嵌入层矩阵np.savez_compressed(filename_trimmed_dir, embeddings=embeddings)# 主程序入口,用于生成预训练词向量文件
utils_fasttext.py:
# coding: UTF-8
# 指定文件编码为UTF-8,支持中文字符import os
# 导入os模块,用于进行操作系统路径和文件操作import torch
# 导入PyTorch框架,用于构建和训练深度学习模型import numpy as np
# 导入NumPy库,用于数值计算和数组操作import pickle as pkl
# 导入pickle模块,用于序列化和反序列化Python对象(如保存和加载词汇表)from tqdm import tqdm
# 从tqdm导入tqdm类,用于在循环中显示进度条import time
# 导入time模块,用于记录时间from datetime import timedelta
# 从datetime导入timedelta类,用于表示时间差MAX_VOCAB_SIZE = 10000
# 设置最大词表大小为10000个词UNK, PAD = '<UNK>', '<PAD>'
# 定义特殊符号:未知词用<UNK>表示,填充符用<PAD>表示def build_vocab(file_path, tokenizer, max_size, min_freq):# 构建词汇表函数,参数包括数据路径、分词器、最大词表大小和最小出现频率vocab_dic = {}# 初始化一个空字典来存储词汇及其频次with open(file_path, 'r', encoding='UTF-8') as f:# 打开文本文件进行读取# 遍历每一行,并显示进度条for line in tqdm(f):# 去除首尾空白字符lin = line.strip()if not lin:continue# 如果是空行,则跳过# 使用制表符分割,取第一个部分作为文本内容content = lin.split('\t')[0]# 使用指定的tokenizer对内容进行分词for word in tokenizer(content):# 统计每个词的出现次数vocab_dic[word] = vocab_dic.get(word, 0) + 1# 筛选出现频率大于等于min_freq的词,并按频率排序后取前max_size个词vocab_list = sorted([_ for _ in vocab_dic.items() if _[1] >= min_freq], key=lambda x: x[1], reverse=True)[:max_size]# 将筛选后的词列表转换为词到索引的映射字典vocab_dic = {word_count[0]: idx for idx, word_count in enumerate(vocab_list)}# 添加特殊符号到词典中,UNK排在最后,PAD在其后vocab_dic.update({UNK: len(vocab_dic), PAD: len(vocab_dic) + 1})# 返回构建好的词汇表字典return vocab_dic# 构建数据集函数,参数为配置对象config和是否使用词粒度标志use_word
def build_dataset(config, ues_word):# 如果使用词粒度if ues_word:# 使用空格分词,即词级别处理tokenizer = lambda x: x.split(' ')else:# 否则使用字符级别分词,将字符串转为字符列表tokenizer = lambda x: [y for y in x]# 如果已经存在词汇表文件if os.path.exists(config.vocab_path):# 直接加载已有的词汇表vocab = pkl.load(open(config.vocab_path, 'rb'))# 如果不存在词汇表文件else:# 调用build_vocab函数构建新的词汇表vocab = build_vocab(config.train_path, tokenizer=tokenizer, max_size=MAX_VOCAB_SIZE, min_freq=1)# 将新构建的词汇表保存到磁盘文件中pkl.dump(vocab, open(config.vocab_path, 'wb'))# 打印词汇表大小print(f"Vocab size: {len(vocab)}")# 加载单个数据集(训练/验证/测试)的内部函数def load_dataset(path, pad_size=32):# 初始化数据容器contents = []# 打开数据文件with open(path, 'r', encoding='UTF-8') as f:# 逐行读取并显示进度条for line in tqdm(f):# 去除前后空白字符lin = line.strip()# 空行跳过if not lin:continue# 使用制表符分割,获取文本内容和标签content, label = lin.split('\t')# 初始化当前句子的token列表words_line = []# 使用tokenizer对内容进行分词或分字token = tokenizer(content)# 获取原始序列长度seq_len = len(token)# 如果设置了固定长度if pad_size:# 如果当前句子长度小于pad_sizeif len(token) < pad_size:# 用<PAD>补齐至pad_size长度token.extend([vocab.get(PAD)] * (pad_size - len(token)))else:token = token[:pad_size]# 否则截断至pad_size长度seq_len = pad_size# word to idfor word in token:# 将词转换为对应的索引# 如果词不在词典中,使用UNK替代words_line.append(vocab.get(word, vocab.get(UNK)))# 将处理后的数据加入contents列表contents.append((words_line, int(label), seq_len))# 返回处理好的数据集return contents # [([...], 0), ([...], 1), ...]# 分别加载训练集、验证集和测试集train = load_dataset(config.train_path, config.pad_size)dev = load_dataset(config.dev_path, config.pad_size)test = load_dataset(config.test_path, config.pad_size)# 返回词汇表和三个数据集return vocab, train, dev, test# 自定义数据迭代器类
class DatasetIterater(object):# 初始化方法def __init__(self, batches, batch_size, device):# 设置批量大小self.batch_size = batch_size# 数据批次列表self.batches = batches# 计算完整batch的数量self.n_batches = len(batches) // batch_sizeself.residue = False # 是否有剩余样本if len(batches) % self.n_batches != 0:self.residue = True# 如果不能整除,标记存在残余数据# 当前遍历的起始索引self.index = 0# 数据所在设备(CPU/GPU)self.device = device# 将数据转换为张量的方法def _to_tensor(self, datas):# 输入序列转为LongTensor并移动到指定设备x = torch.LongTensor([_[0] for _ in datas]).to(self.device)# 标签转为LongTensor并移动到指定设备y = torch.LongTensor([_[1] for _ in datas]).to(self.device)# 序列长度信息转为LongTensor并移动到指定设备seq_len = torch.LongTensor([_[2] for _ in datas]).to(self.device)# 返回输入、长度和标签return (x, seq_len), y# 实现迭代器的__next__方法def __next__(self):# 如果有残余数据且到达最后一个不完整的batchif self.residue and self.index == self.n_batches:# 取出残余数据batches = self.batches[self.index * self.batch_size: len(self.batches)]# 更新索引self.index += 1# 转换为张量batches = self._to_tensor(batches)# 返回当前batchreturn batches# 如果索引超过总batch数elif self.index > self.n_batches:self.index = 0# 重置索引raise StopIteration# 抛出停止迭代异常else:# 正常取一个batch# 取出当前batch的数据batches = self.batches[self.index * self.batch_size: (self.index + 1) * self.batch_size]# 更新索引self.index += 1# 转换为张量batches = self._to_tensor(batches)# 返回当前batchreturn batchesdef __iter__(self):# 实现__iter__方法,返回自身即可开始迭代return selfdef __len__(self):# 获取总batch数量if self.residue:# 如果有残余数据,加1return self.n_batches + 1else:# 否则直接返回完整batch数return self.n_batches# 构建数据迭代器函数
def build_iterator(dataset, config):# 创建DatasetIterater实例iter = DatasetIterater(dataset, config.batch_size, config.device)# 返回迭代器对象return iterdef get_time_dif(start_time):"""获取已使用时间"""end_time = time.time()time_dif = end_time - start_time# 返回格式化的时间差对象return timedelta(seconds=int(round(time_dif)))if __name__ == "__main__":'''提取预训练词向量'''# 下面的目录、文件名按需更改。# 定义训练数据集的目录train_dir = "./THUCNews/data/train.txt"# 定义词汇表的目录vocab_dir = "./THUCNews/data/vocab.pkl"# 定义预训练向量的目录pretrain_dir = "./THUCNews/data/sgns.sogou.char"# 定义词向量的维度emb_dim = 300# 定义保存修剪后嵌入层的目录filename_trimmed_dir = "./THUCNews/data/embedding_SougouNews"# 检查词汇表是否已存在if os.path.exists(vocab_dir):# 如果存在,加载词汇表word_to_id = pkl.load(open(vocab_dir, 'rb'))else:# 如果不存在,定义一个分词函数,以字为单位构建词表tokenizer = lambda x: [y for y in x]# 使用训练数据构建词汇表word_to_id = build_vocab(train_dir, tokenizer=tokenizer, max_size=MAX_VOCAB_SIZE, min_freq=1)# 保存词汇表pkl.dump(word_to_id, open(vocab_dir, 'wb'))# 初始化随机嵌入层矩阵,维度为词汇表大小乘以词向量维度embeddings = np.random.rand(len(word_to_id), emb_dim)# 打开预训练的词向量文件f = open(pretrain_dir, "r", encoding='UTF-8')# 遍历预训练的词向量文件,更新嵌入层矩阵for i, line in enumerate(f.readlines()):lin = line.strip().split(" ")# 如果当前词在词汇表中,则更新其对应的词向量if lin[0] in word_to_id:idx = word_to_id[lin[0]]emb = [float(x) for x in lin[1:301]]embeddings[idx] = np.asarray(emb, dtype='float32')# 关闭文件f.close()# 保存修剪后的嵌入层矩阵np.savez_compressed(filename_trimmed_dir, embeddings=embeddings)# 主程序入口,用于生成预训练词向量文件
/models/TextCNN.py:
# coding: UTF-8
# 指定文件编码为UTF-8,支持中文字符
# 导入PyTorch框架,用于构建和训练深度学习模型
import torch# 从PyTorch中导入神经网络模块,用于定义神经网络层
import torch.nn as nn# 导入PyTorch的功能性函数模块,如激活函数、卷积等
import torch.nn.functional as F# 导入NumPy库,用于进行数值计算和数组操作
import numpy as np"""配置参数"""
class Config(object):# 初始化配置类,传入数据集名称和词向量类型(预训练或随机)def __init__(self, dataset, embedding):# 设置模型名称为TextCNNself.model_name = 'TextCNN'# 训练集路径,格式为{dataset}/data/train.txtself.train_path = dataset + '/data/train.txt'# 验证集路径,格式为{dataset}/data/dev.txtself.dev_path = dataset + '/data/dev.txt'# 测试集路径,格式为{dataset}/data/test.txtself.test_path = dataset + '/data/test.txt'# 从class.txt中读取类别列表,并去除每行两端空白字符self.class_list = [x.strip() for x in open(dataset + '/data/class.txt').readlines()]# 词汇表保存路径,格式为{dataset}/data/vocab.pklself.vocab_path = dataset + '/data/vocab.pkl'# 模型保存路径,格式为{dataset}/saved_dict/TextCNN.ckptself.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'# 日志保存路径,格式为{dataset}/log/TextCNNself.log_path = dataset + '/log/' + self.model_name# 如果embedding不是random,则加载预训练词向量;否则设为Noneself.embedding_pretrained = torch.tensor(np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \if embedding != 'random' else None# 设置设备:如果CUDA可用则使用GPU,否则使用CPUself.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# dropout比率,在训练过程中随机失活部分神经元以防止过拟合self.dropout = 0.5# 若验证集loss在1000个batch内未下降,则提前终止训练self.require_improvement = 1000# 类别数量,由class.txt中读取得到self.num_classes = len(self.class_list)# 词表大小,在后续运行时动态赋值self.n_vocab = 0# 训练轮数,总共训练20个epochself.num_epochs = 20# mini-batch大小,每次训练使用的样本数为128self.batch_size = 128# 句子统一长度,短句填充,长句截断self.pad_size = 32# 学习率,控制参数更新步长self.learning_rate = 1e-3# 如果有预训练词向量,字向量维度为预训练维度;否则默认为300维self.embed = self.embedding_pretrained.size(1) \if self.embedding_pretrained is not None else 300# 卷积核尺寸,分别使用2、3、4长度的卷积核提取特征self.filter_sizes = (2, 3, 4)# 卷积核数量,即输出通道数,每个尺寸的卷积核都有256个self.num_filters = 256'''Convolutional Neural Networks for Sentence Classification'''
# 文本分类的卷积神经网络原始论文引用标题# 定义TextCNN模型类,继承自nn.Module
class Model(nn.Module):# 构造函数,初始化模型结构def __init__(self, config):# 调用父类构造函数super(Model, self).__init__()# 如果提供了预训练词向量if config.embedding_pretrained is not None:# 使用预训练词向量,并允许微调(freeze=False)self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)else:# 否则# 创建一个随机初始化的词嵌入层,设置padding_idx为最后一个索引self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)# 创建多个一维卷积层,每个卷积核高度分别为2、3、4,宽度为词向量维度self.convs = nn.ModuleList([nn.Conv2d(1, config.num_filters, (k, config.embed)) for k in config.filter_sizes])# 添加Dropout层,防止过拟合self.dropout = nn.Dropout(config.dropout)# 全连接层,输入大小为所有卷积核输出拼接后的总长度,输出为类别数self.fc = nn.Linear(config.num_filters * len(config.filter_sizes), config.num_classes)# 定义卷积+池化函数,用于处理每个卷积层的输出def conv_and_pool(self, x, conv):# 应用ReLU激活函数并通过卷积层,然后去掉第四个维度(单列)x = F.relu(conv(x)).squeeze(3)# 在时间维度上做最大池化,再去除第二个维度x = F.max_pool1d(x, x.size(2)).squeeze(2)# 返回池化后的结果return x# 前向传播函数,定义数据如何通过网络流动def forward(self, x):# 调试信息:打印输入张量形状print(x[0].shape)# 将输入的token索引转换为词向量表示out = self.embedding(x[0])# 在第2维增加一个通道维度,使其适配Conv2d输入要求out = out.unsqueeze(1)# 对不同尺寸的卷积核分别进行卷积和池化操作,并将结果拼接在一起out = torch.cat([self.conv_and_pool(out, conv) for conv in self.convs], 1)# 应用Dropout,减少过拟合风险out = self.dropout(out)# 最终通过全连接层得到分类输出out = self.fc(out)# 返回模型输出结果return out
/models/TextRNN.py:
# coding: UTF-8
# 指定文件编码为UTF-8,支持中文字符
# 导入PyTorch框架,用于构建和训练深度学习模型
import torch# 从PyTorch中导入神经网络模块,用于定义神经网络层
import torch.nn as nn# 导入PyTorch的功能性函数模块,如激活函数、卷积等
import torch.nn.functional as F# 导入NumPy库,用于进行数值计算和数组操作
import numpy as np"""配置参数"""
class Config(object):# 初始化配置类,传入数据集名称和词向量类型(预训练或随机)def __init__(self, dataset, embedding):# 设置模型名称为TextRNNself.model_name = 'TextRNN'# 训练集路径,格式为{dataset}/data/train.txtself.train_path = dataset + '/data/train.txt'# 验证集路径,格式为{dataset}/data/dev.txtself.dev_path = dataset + '/data/dev.txt'# 测试集路径,格式为{dataset}/data/test.txtself.test_path = dataset + '/data/test.txt'# 从class.txt中读取类别列表,并去除每行两端空白字符self.class_list = [x.strip() for x in open(dataset + '/data/class.txt').readlines()]# 词汇表保存路径,格式为{dataset}/data/vocab.pklself.vocab_path = dataset + '/data/vocab.pkl'# 模型保存路径,格式为{dataset}/saved_dict/TextRNN.ckptself.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'# 日志保存路径,格式为{dataset}/log/TextRNNself.log_path = dataset + '/log/' + self.model_name# 如果embedding不是random,则加载预训练词向量;否则设为Noneself.embedding_pretrained = torch.tensor(np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \if embedding != 'random' else None# 设置设备:如果CUDA可用则使用GPU,否则使用CPUself.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')# dropout比率,在训练过程中随机失活部分神经元以防止过拟合self.dropout = 0.5# 若验证集loss在1000个batch内未下降,则提前终止训练self.require_improvement = 1000# 类别数量,由class.txt中读取得到self.num_classes = len(self.class_list)# 词表大小,在后续运行时动态赋值self.n_vocab = 0# 训练轮数,总共训练10个epochself.num_epochs = 10# mini-batch大小,每次训练使用的样本数为128self.batch_size = 128# 句子统一长度,短句填充,长句截断self.pad_size = 32# 学习率,控制参数更新步长self.learning_rate = 1e-3# 如果有预训练词向量,字向量维度为预训练维度;否则默认为300维self.embed = self.embedding_pretrained.size(1) \if self.embedding_pretrained is not None else 300# LSTM隐藏层大小,表示每个时刻LSTM输出的特征维度self.hidden_size = 128# LSTM层数,堆叠两层LSTM网络self.num_layers = 2'''Recurrent Neural Network for Text Classification with Multi-Task Learning'''
# 引用论文标题:基于多任务学习的文本分类循环神经网络# 定义TextRNN模型类,继承自nn.Module
class Model(nn.Module):# 构造函数,初始化模型结构def __init__(self, config):# 调用父类构造函数super(Model, self).__init__()# 如果提供了预训练词向量if config.embedding_pretrained is not None:# 使用预训练词向量,并允许微调(freeze=False)self.embedding = nn.Embedding.from_pretrained(config.embedding_pretrained, freeze=False)else:# 否则# 创建一个随机初始化的词嵌入层,设置padding_idx为最后一个索引self.embedding = nn.Embedding(config.n_vocab, config.embed, padding_idx=config.n_vocab - 1)# 创建双向LSTM层,输入维度为词向量维度,输出维度为hidden_size,num_layers层,batch_first=True表示输入形状为(batch, seq, feature)self.lstm = nn.LSTM(config.embed, config.hidden_size, config.num_layers,bidirectional=True, batch_first=True, dropout=config.dropout)# 全连接层,将LSTM输出的最后一时刻的拼接结果映射到类别空间self.fc = nn.Linear(config.hidden_size * 2, config.num_classes)# 前向传播函数,定义数据如何通过网络流动def forward(self, x):# 解包输入数据,x是token索引,_ 是其他信息(如seq_len),但此处忽略x, _ = x# 将输入的token索引转换为词向量表示,形状为[batch_size, seq_len, embed]out = self.embedding(x)# 输入到LSTM中,out是所有时间步的输出,形状为[batch_size, seq_len, hidden_size*2](因为是双向)out, _ = self.lstm(out)# 取LSTM最后一步的输出作为句子表示,输入全连接层得到分类结果out = self.fc(out[:, -1, :])# 返回模型输出结果return out