深度学习---新闻数据文本分类---pytorch

复制代码
调用流程图:





------------------------------以下是代码------------------------------------------------

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