调用流程图:
------------------------------以下是代码------------------------------------------------
run.py:
python
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等
# 如果模型是FastText
if 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.TextCNN
x = import_module('models.' + model_name)
# 实例化模型配置类Config,传入数据集和词向量信息
config = x.Config(dataset, embedding)
# 设置numpy的随机种子为1,确保每次运行结果一致
np.random.seed(1)
# 设置PyTorch CPU模式下的随机种子为1
torch.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
python
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等
# 如果模型是FastText
if 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.TextCNN
x = import_module('models.' + model_name)
# 实例化模型配置类Config,传入数据集和词向量信息
config = x.Config(dataset, embedding)
# 设置numpy的随机种子为1,确保每次运行结果一致
np.random.seed(1)
# 设置PyTorch CPU模式下的随机种子为1
torch.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:
python
# 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_size
if 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 id
for 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_size
self.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):
# 如果有残余数据且到达最后一个不完整的batch
if 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)
# 返回当前batch
return 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)
# 返回当前batch
return batches
def __iter__(self):
# 实现__iter__方法,返回自身即可开始迭代
return self
def __len__(self):
# 获取总batch数量
if self.residue:
# 如果有残余数据,加1
return self.n_batches + 1
else:
# 否则直接返回完整batch数
return self.n_batches
# 构建数据迭代器函数
def build_iterator(dataset, config):
# 创建DatasetIterater实例
iter = DatasetIterater(dataset, config.batch_size, config.device)
# 返回迭代器对象
return iter
def 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:
python
# 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_size
if 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 id
for 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_size
self.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):
# 如果有残余数据且到达最后一个不完整的batch
if 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)
# 返回当前batch
return 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)
# 返回当前batch
return batches
def __iter__(self):
# 实现__iter__方法,返回自身即可开始迭代
return self
def __len__(self):
# 获取总batch数量
if self.residue:
# 如果有残余数据,加1
return self.n_batches + 1
else:
# 否则直接返回完整batch数
return self.n_batches
# 构建数据迭代器函数
def build_iterator(dataset, config):
# 创建DatasetIterater实例
iter = DatasetIterater(dataset, config.batch_size, config.device)
# 返回迭代器对象
return iter
def 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:
python
# 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):
# 设置模型名称为TextCNN
self.model_name = 'TextCNN'
# 训练集路径,格式为{dataset}/data/train.txt
self.train_path = dataset + '/data/train.txt'
# 验证集路径,格式为{dataset}/data/dev.txt
self.dev_path = dataset + '/data/dev.txt'
# 测试集路径,格式为{dataset}/data/test.txt
self.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.pkl
self.vocab_path = dataset + '/data/vocab.pkl'
# 模型保存路径,格式为{dataset}/saved_dict/TextCNN.ckpt
self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'
# 日志保存路径,格式为{dataset}/log/TextCNN
self.log_path = dataset + '/log/' + self.model_name
# 如果embedding不是random,则加载预训练词向量;否则设为None
self.embedding_pretrained = torch.tensor(
np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \
if embedding != 'random' else None
# 设置设备:如果CUDA可用则使用GPU,否则使用CPU
self.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个epoch
self.num_epochs = 20
# mini-batch大小,每次训练使用的样本数为128
self.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:
python
# 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):
# 设置模型名称为TextRNN
self.model_name = 'TextRNN'
# 训练集路径,格式为{dataset}/data/train.txt
self.train_path = dataset + '/data/train.txt'
# 验证集路径,格式为{dataset}/data/dev.txt
self.dev_path = dataset + '/data/dev.txt'
# 测试集路径,格式为{dataset}/data/test.txt
self.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.pkl
self.vocab_path = dataset + '/data/vocab.pkl'
# 模型保存路径,格式为{dataset}/saved_dict/TextRNN.ckpt
self.save_path = dataset + '/saved_dict/' + self.model_name + '.ckpt'
# 日志保存路径,格式为{dataset}/log/TextRNN
self.log_path = dataset + '/log/' + self.model_name
# 如果embedding不是random,则加载预训练词向量;否则设为None
self.embedding_pretrained = torch.tensor(
np.load(dataset + '/data/' + embedding)["embeddings"].astype('float32')) \
if embedding != 'random' else None
# 设置设备:如果CUDA可用则使用GPU,否则使用CPU
self.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个epoch
self.num_epochs = 10
# mini-batch大小,每次训练使用的样本数为128
self.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