【循环神经网络】案例:周杰伦歌词文本预测【训练+python代码】

news2024/9/20 12:15:30

文章目录

  • 1、简介
  • 2、数据集
  • 3、构建词典
  • 4、构建数据集对象
  • 5、构建网络模型
  • 6、构建训练函数
    • 6.1、多分类交叉熵损失函数🔺
    • 6.2、Adam🔺
    • 6.3、代码
  • 7、构建预测函数
  • 8、word_to_index和index_to_word
    • 8.1、word_to_index
    • 8.2、index_to_word
    • 8.3、使用场景
  • 9、DataLoader()的参数
  • 10、标准深度学习模型训练流程⭐
  • 11、案例的完整代码

🍃作者介绍:双非本科大三网络工程专业在读,阿里云专家博主,专注于Java领域学习,擅长web应用开发、数据结构和算法,初步涉猎人工智能和前端开发。
🦅个人主页:@逐梦苍穹
📕所属专栏:人工智能
🌻gitee地址:xzl的人工智能代码仓库
✈ 您的一键三连,是我创作的最大动力🌹

1、简介

本文目标:掌握文本生成模型构建流程

文本生成任务是一种常见的自然语言处理任务,输入一个开始词能够预测出后面的词序列。
本案例将会使用循环神经网络来实现周杰伦歌词生成预测任务。

2、数据集

免费下载
image.png
链接:https://pan.baidu.com/s/14SJWD_ChjiX0UJ8ncL6a4w?pwd=1234
提取码:1234

数据集如下:
image.png
数据集共有 5819 行。
下面是讲解原理之后,用代码依次构建,文末会有完整代码文本。

3、构建词典

我们在进行自然语言处理任务之前,首要做的就是就是构建词表。
所谓的词表就是将 语料进行分词,然后给每一个词分配一个唯一的编号,便于我们送入词嵌入层
最终,我们的词典主要包含了:

  1. word_to_index:存储了词到编号的映射
  2. index_to_word:存储了编号到词的映射

(下文讲解 word_to_indexindex_to_word 的区别和用处)

一般构建词表的流程如下:

  1. 语料清洗,去除不相关的内容
  2. 对语料进行分词
  3. 构建词表

接下来,我们对周杰伦歌词的语料数据按照上面的步骤构建词表。
代码如下:

# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/7 16:34

# 构建词典
import re  # 导入正则表达式模块,用于文本清洗和处理

import jieba  # 导入jieba库,用于中文分词

# 构建词汇表
def build_vocab():
    file_name = '../data-txt/jaychou_lyrics.txt'

    # TODO 1.清洗文本
    # 初始化一个列表,用于存储清洗后的句子
    clean_sentences = []
    for line in open(file_name, 'r', encoding='utf-8'):  # 打开歌词文件,逐行读取,指定编码为UTF-8
        line = line.replace('〖韩语Rap译文〗', '')  # 去除特定的文本标记
        # TODO 使用正则表达式去除无效字符
        # 去除 除了 中文、英文、数字、部分标点符号外 的其他字符
        line = re.sub(r'[^\u4e00-\u9fa5 a-zA-Z0-9!?,]', '', line)
        # 连续空格替换成1个
        line = re.sub(r'[ ]{2,}', '', line)  # 替换连续的空格为单个空格
        # 去除两侧空格、换行
        line = line.strip()  # 去除行首和行尾的空白字符和换行符
        if len(line) <= 1:  # 如果行的长度小于等于1,则跳过该行
            continue
        # 去除重复行
        if line not in clean_sentences:  # 如果行不在clean_sentences中,则添加
            clean_sentences.append(line)  # 将清洗后的行添加到clean_sentences列表中

    # TODO 2. 语料分词
    # TODO 初始化两个列表: index_to_word用于存储词汇表, all_sentences用于存储所有分词后的句子
    index_to_word, all_sentences = [], []

    for line in clean_sentences:  # 遍历清洗后的句子
        words = jieba.lcut(line)  # 使用jieba对句子进行分词,返回词汇列表
        # print("words: ", words)
        all_sentences.append(words)  # 将分词结果添加到all_sentences列表中
        # print("all_sentences: ", all_sentences)
        for word in words:  # 遍历每个词
            if word not in index_to_word:  # 如果词不在词汇表中
                # print("word: ", word)
                index_to_word.append(word)  # 将词添加到词汇表中
                # print("index_to_word: ", index_to_word)

    # 词到索引映射
    word_to_index = {word: idx for idx, word in enumerate(index_to_word)}  # 创建词到索引的映射字典
    # print("word_to_index_start: ", word_to_index)
    # 词的数量
    word_count = len(index_to_word)  # 计算词汇表中词的数量
    # 句子索引表示
    corpus_idx = []  # 初始化一个列表,用于存储整个语料的索引表示
    for sentence in all_sentences:  # 遍历每个分词后的句子
        temp = []  # 初始化一个临时列表,用于存储句子的索引
        for word in sentence:  # 遍历句子中的每个词
            temp.append(word_to_index[word])  # 将词转换为索引并添加到临时列表中
        # 在每行歌词之间添加空格隔开
        temp.append(word_to_index[' '])  # 在每个句子末尾添加空格的索引作为分隔符
        # print("temp: ", temp)
        # TODO extend()是逐个添加, 区别于extend()
        corpus_idx.extend(temp)  # 将句子的索引表示添加到corpus_idx列表中
        # print("corpus_idx: ", corpus_idx)
    # TODO 返回构建的词汇表、索引映射、词数、语料索引
    return index_to_word, word_to_index, word_count, corpus_idx


if __name__ == '__main__':  # 主程序入口
    index_to_word, word_to_index, word_count, corpus_idx = build_vocab()  # 调用build_vocab函数,构建词汇表和索引
    print("词汇表的大小: ", word_count)  # 打印词汇表的大小
    print("语料的索引表示: ", corpus_idx[:10])  # 打印语料的索引表示
    print("索引到词的映射: ", index_to_word[:10])  # 打印词汇表
    print("词到索引的映射: ", word_to_index)  # 打印词到索引的映射

输出如下:
image.png
一些代码概念的理解:
image.png
image.png
对代码具体实现流程的总结如下:

  1. 清洗文本:去除特定文本、无效字符、连续空格转为单个空格、去掉换行符和两侧空格
  2. 分词:构建"index_to_word"->“索引 : 词”;“all_sentences”->分词后的句子
    1. 对每行进行分词,加如all_sentences列表
    2. 对每个词索引映射:index_to_word
  3. 构建"word_to_index"->“词 : 索引”
  4. 定义corpus_idx语料集合,存储所有分词后的文本索引(通过添加末尾空格[单个]索引,来辨认哪些索引对应某一句歌词)
  5. 返回index_to_word, word_to_index, word_count, corpus_idx

4、构建数据集对象

我们在训练的时候,为了便于读取语料,并送入网络,所以我们会构建一个 Dataset 对象,并使用该对象构建 DataLoader 对象,然后对 DataLoader 对象进行迭代可以获取语料,并将其送入网络
类对象:
image.png
使用:
image.png
输出:

E:\anaconda3\python.exe D:\Python\AI\神经网络\18-案例:周杰伦歌词文本预测\分部测试\2-构建数据集.py 
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\86189\AppData\Local\Temp\jieba.cache
Loading model cost 0.796 seconds.
Prefix dict has been built successfully.
lyrics:  <__main__.LyricsDataset object at 0x000001ACA162BE90>
x: tensor([[ 0,  1,  2, 39,  0]])
y: tensor([[ 1,  2, 39,  0,  3]])

Process finished with exit code 0

对代码具体实现流程的总结如下:

  1. 先由前文的buid_vocab()获得对应的单个词典
  2. 把该词典的语料集和想要划分每批次的数值,输入到类中
  3. 初始化各数值(init
  4. 构造样本,构建开始索引。有x输入值和y输出值,转换为tensor张量,为下文的模型构造和训练做准备

5、构建网络模型

我们用于实现歌词生成的网络模型,主要包含了三个层:

  1. 词嵌入层: 用于将语料转换为词向量
  2. 循环网络层:提取句子语义
  3. 全连接层:输出对词典中每个词的预测概率

我们前面学习了 Dropout 层,它具有正则化作用,所以在我们的网络层中,我们会对词嵌入层、循环网络层的输出结果进行 Dropout 计算。

代码:

# 定义一个文本生成模型类,继承自 nn.Module
class TextGenerator(nn.Module):
    # 初始化方法,构造函数
    def __init__(self, vocab_size):
        print("vocab_size: ", vocab_size)
        # 调用父类(nn.Module)的构造函数
        super(TextGenerator, self).__init__()
        # TODO 定义新变量ebd、rnn、out
        # 初始化词嵌入层,将词汇表中的每个词映射到一个128维的向量
        self.ebd = nn.Embedding(vocab_size, 128)
        # 初始化循环神经网络层,输入和输出都是128维,层数为1
        self.rnn = nn.RNN(128, 128, 1)
        # 初始化线性输出层,将RNN的输出映射到词汇表的大小
        # TODO 输出是需要转为词汇, 所以vocab_size多大, 输出就是多大的
        self.out = nn.Linear(128, vocab_size)

    # 定义前向传播方法
    def forward(self, inputs, hidden):
        # 将输入的词索引转换为嵌入向量,输出维度为 (1, 5, 128)
        embed = self.ebd(inputs)
        # 对嵌入向量进行dropout正则化,防止过拟合,概率为0.2
        embed = F.dropout(embed, p=0.2)
        # 将嵌入向量的维度从 (1, 5, 128) 转置为 (5, 1, 128) 以匹配RNN的输入要求
        # TODO 这里会调用rnn.py的forward()方法
        output, hidden = self.rnn(embed.transpose(0, 1), hidden)  # embed.transpose(0, 1)调整张量维度
        # 对RNN的输出进行dropout正则化,防止过拟合,概率为0.2
        embed = F.dropout(output, p=0.2)
        # 将RNN的输出维度从 (5, 1, 128) 压缩为 (5, 128)
        # 然后通过线性层将其转换为词汇表大小的向量 (5, vocab_size)
        output = self.out(output.squeeze())
        # 返回输出和隐藏状态
        return output, hidden

    # 初始化隐藏状态的方法
    def init_hidden(self):
        # 返回一个全零的隐藏状态张量,维度为 (1, 1, 128)
        return torch.zeros(1, 1, 128)

测试:
image.png
输出:

E:\anaconda3\python.exe D:\Python\AI\神经网络\18-案例:周杰伦歌词文本预测\分部测试\3-构建网络模型.py 
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\86189\AppData\Local\Temp\jieba.cache
Loading model cost 0.764 seconds.
Prefix dict has been built successfully.
词汇表的大小:  5682
语料的索引表示:  [0, 1, 2, 39, 0, 3, 4, 5, 6, 7]
索引到词的映射:  ['想要', '有', '直升机', '和', '你', '飞到', '宇宙', '去', '融化', '在']
vocab_size:  5682
torch.Size([1, 5])

Process finished with exit code 0

RNN输入要求:
image.png
rnn.forward():
image.png
返回结果是一个时间步输出隐藏状态,一个输出最终的隐藏状态

6、构建训练函数

前面的准备工作完成之后,就可以编写训练函数了
训练函数主要负责编写 数据迭代、送入网络、计算损失、反向传播、更新参数,其流程基本较为固定。
由于我们要实现文本生成,文本生成本质上,输入一串文本,预测下一个文本,也属于分类问题,可以看成是一个 多分类问题
所以,我们使用 多分类交叉熵损失函数
优化方法这里选择学习率、梯度自适应的 Adam 算法作为我们的优化方法。
训练完成之后,我们使用 torch.save 方法将模型持久化存储。

6.1、多分类交叉熵损失函数🔺

多分类交叉熵损失函数(Multiclass Cross-Entropy Loss)是深度学习中常用的损失函数,特别适用于多分类任务。
它计算模型 输出的概率分布真实类别分布之间的差异
对于一个具有 N N N 个样本和 C C C 类别的多分类问题,多分类交叉熵损失函数定义为:
Loss = − 1 N ∑ i = 1 N ∑ c = 1 C y i , c log ⁡ ( y ^ i , c ) \text{Loss} = -\frac{1}{N} \sum_{i=1}^{N} \sum_{c=1}^{C} y_{i,c} \log(\hat{y}_{i,c}) Loss=N1i=1Nc=1Cyi,clog(y^i,c)
其中:

  • N N N 是样本数量。
  • C C C 是类别数量。
  • y i , c y_{i,c} yi,c 是样本 i i i 的真实标签,如果样本 i i i 的真实类别是 c c c,则 y i , c = 1 y_{i,c} = 1 yi,c=1,否则 y i , c = 0 y_{i,c} = 0 yi,c=0
  • y ^ i , c \hat{y}_{i,c} y^i,c 是模型对样本 i i i 的类别 c c c 的预测概率。

实现:

  1. 在PyTorch中,多分类交叉熵损失函数可以使用 torch.nn.CrossEntropyLoss 类来实现。
  2. 该类结合了 nn.LogSoftmaxnn.NLLLoss,因此无需手动应用 softmax 函数到模型的输出。
  3. CrossEntropyLoss 会自动计算 softmax,然后再计算交叉熵损失。

6.2、Adam🔺

Adam简介:
Adam(Adaptive Moment Estimation)优化器是 深度学习中常用的一种优化算法。
它结合了AdaGrad和RMSProp的优点,既能够适应稀疏梯度,又能够处理非平稳目标。
Adam通过计算梯度的
一阶和二阶矩估计动态调整每个参数学习率

一阶动量是梯度的指数加权移动平均。它可以看作是梯度的平均值,表示了梯度的方向和大小。
二阶动量是梯度平方的指数加权移动平均。它可以看作是梯度的方差,表示了梯度的变化范围。

Adam 优化器的关键特点:

  1. 自适应学习率:每个参数都有独立的学习率,可以根据一阶和二阶梯度估计动态调整。
  2. 计算效率相对于简单的随机梯度下降 (SGD)Adam计算效率高,存储需求小
  3. 适用于大规模数据集:在处理大规模数据集和高维参数空间时表现优越。
  4. 默认超参数效果好:在许多情况下,Adam的默认超参数配置能取得很不错的效果。

Adam通过以下公式来更新参数:

  1. 计算梯度的移动平均
    • m t = β 1 m t − 1 + ( 1 − β 1 ) g t m_t = \beta_1 m_{t-1} + (1 - \beta_1) g_t mt=β1mt1+(1β1)gt
    • v t = β 2 v t − 1 + ( 1 − β 2 ) g t 2 v_t = \beta_2 v_{t-1} + (1 - \beta_2) g_t^2 vt=β2vt1+(1β2)gt2
    • 其中, g t g_t gt 是当前时间步的梯度, m t m_t mt v t v_t vt 分别是梯度的一阶和二阶矩的移动平均, β 1 \beta_1 β1 β 2 \beta_2 β2 是衰减率(通常取 β 1 = 0.9 \beta_1 = 0.9 β1=0.9 β 2 = 0.999 \beta_2 = 0.999 β2=0.999)。
  2. 偏差修正
    • m t ^ = m t 1 − β 1 t \hat{m_t} = \frac{m_t}{1 - \beta_1^t} mt^=1β1tmt
    • v t ^ = v t 1 − β 2 t \hat{v_t} = \frac{v_t}{1 - \beta_2^t} vt^=1β2tvt
  3. 参数更新
    • θ t = θ t − 1 − α m t ^ v t ^ + ϵ \theta_t = \theta_{t-1} - \alpha \frac{\hat{m_t}}{\sqrt{\hat{v_t}} + \epsilon} θt=θt1αvt^ +ϵmt^
    • 其中, α \alpha α 是学习率, ϵ \epsilon ϵ 是一个小常数(防止分母为零,通常取 1 0 − 8 10^{-8} 108)。

6.3、代码

定义训练函数:

def train(epoch, train_log):
    # 构建词典,返回索引到词,词到索引,词汇数量,和语料索引列表
    index_to_word, word_to_index, word_count, corpus_idx = build_vocab()
    # 创建歌词数据集实例,每个输入序列的长度为 32
    lyrics = LyricsDataset(corpus_idx, 32)
    # 初始化文本生成模型,词汇表大小为 word_count
    model = TextGenerator(word_count)
    # TODO 正式进入这一阶段代码
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 定义 Adam 优化器,学习率为 1e-3
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    # 打开日志文件用于写入
    file = open(train_log, 'w')
    # 开始训练循环
    for epoch_idx in range(epoch):
        # 数据加载器,打乱顺序,每次取 1 个样本
        lyrics_dataloader = DataLoader(lyrics, shuffle=True, batch_size=1)
        # 记录训练开始时间
        start = time.time()
        # 重置迭代次数
        iter_num = 0
        # 重置训练损失
        total_loss = 0.0
        # 遍历数据加载器中的数据
        for x, y in lyrics_dataloader:
            # 初始化隐藏状态
            hidden = model.init_hidden()
            # 前向传播计算输出和隐藏状态
            output, hidden = model(x, hidden)
            # 计算损失,y.squeeze() 去掉维度大小为1的维度
            # TODO 计算模型输出与实际目标之间的损失(误差)
            loss = criterion(output, y.squeeze())
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播计算梯度
            loss.backward()
            # 更新参数
            optimizer.step()
            # 迭代次数加1
            iter_num += 1
            # 累加损失
            total_loss += loss.item()
        # 构建本次迭代的日志信息
        message = 'epoch %3s loss: %.5f time %.2f' % \
                  (epoch_idx + 1,  # 当前训练轮数
                   total_loss / iter_num,  # 平均损失
                   time.time() - start)  # 本轮训练时间
        # 打印日志信息
        print(message)
        # 写入日志文件
        file.write(message + '\n')
    # 关闭日志文件
    file.close()
    # 保存模型参数到文件
    torch.save(model.state_dict(), '../model/lyrics_model_%d.bin' % epoch)


if __name__ == '__main__':
    train(epoch=1, train_log='lyrics_training.log')  # 设置训练轮数为 200, 训练日志文件名为 lyrics_training.log

7、构建预测函数

到了最后一步,从磁盘加载训练好的模型,进行预测
预测函数,输入第一个指定的词,我们将该词输入网路,预测出下一个词,再将预测的出的词再次送入网络,预测出下一个词,以此类推,直到预测出我们指定长度的内容。

# 构建预测函数
def predict(start_word, sentence_length, model_path):
    # 构建词典,返回索引到词,词到索引,词汇数量
    index_to_word, word_to_index, word_count, _ = build_vocab()
    # 构建文本生成模型实例,词汇表大小为 word_count
    model = TextGenerator(vocab_size=word_count)
    # 加载训练好的模型参数
    model.load_state_dict(torch.load(model_path))
    # 初始化隐藏状态
    hidden = model.init_hidden()
    try:
        # 将起始词转换为词索引
        word_idx = word_to_index[start_word]
    except:
        print("该词不在词典中, 请重新输入")
        return
    # 用于存储生成的句子(词索引序列)
    generate_sentence = [word_idx]
    # 生成长度为 sentence_length 的句子
    for _ in range(sentence_length):
        # 前向传播,获取模型输出和隐藏状态
        output, hidden = model(torch.tensor([[word_idx]]), hidden)
        # print("output: ", output)

        # 获取输出中概率最大的词的索引
        word_idx = torch.argmax(output).item()
        # 将该词索引添加到生成的句子中
        generate_sentence.append(word_idx)
    # 将生成的词索引序列转换为实际词并打印
    for idx in generate_sentence:
        print(index_to_word[idx], end='')
    print()


if __name__ == '__main__':
    predict('分手', 15, '../model/lyrics_model_20.bin')

程序运行结果:

E:\anaconda3\python.exe D:\Python\AI\神经网络\18-案例:周杰伦歌词文本预测\分部测试\5-构建预测函数.py 
Building prefix dict from the default dictionary ...
Loading model from cache C:\Users\86189\AppData\Local\Temp\jieba.cache
Loading model cost 0.873 seconds.
Prefix dict has been built successfully.
vocab_size:  5682
分手的话像语言暴力 我已无能为力再提起 决定中断熟悉 

Process finished with exit code 0

关于 torch.argmax(output):

这行代码用于从模型的输出中选择 概率 最大的词的索引,并将其作为下一个输入词的索引

那么在训练过程中,又是什么时候存储概率的呢?
在训练过程中,模型的输出并不是直接存储概率,而是生成一个未归一化的得分(logits)向量。、通过交叉熵损失函数计算时,这些未归一化的得分会被隐式地转换为概率。
1. 模型输出(Logits)
模型的最后一层通常是一个线性层,它的输出是一个包含词汇表中每个词的未归一化得分(logits)的向量。这些得分代表了每个词在当前上下文中的相对可能性。
2. 交叉熵损失函数:
在训练过程中,使用 nn.CrossEntropyLoss 作为损失函数。nn.CrossEntropyLoss 函数结合了 nn.LogSoftmaxnn.NLLLoss,其计算步骤如下:

  1. Logits to Probabilities
    • nn.LogSoftmax 将未归一化的得分(logits)转换为对数概率。
  2. Negative Log-Likelihood Loss
    • nn.NLLLoss 使用对数概率计算负对数似然损失。

3. 训练过程:
在训练过程中,模型的输出是未归一化的得分,这些得分在计算交叉熵损失时被隐式地转换为概率。训练完成后,模型在预测时会输出这些得分,通过 torch.argmax 找到得分最高的词索引,这个词就是模型预测的下一个词。

8、word_to_index和index_to_word

在自然语言处理(NLP)任务中,文本数据需要转换为数值格式,以便机器学习模型可以处理和理解。 word_to_indexindex_to_word 是两种常用的数据结构,用于将词汇表中的单词和索引相互映射。这种映射在文本预处理中非常重要,下面是对这两者的详细解释和作用。

8.1、word_to_index

word_to_index 是一个字典,它将每个单词映射到一个唯一的整数索引。这种映射的主要作用有:

  1. 文本向量化
    • 目的:将文本转换为数字形式,便于输入机器学习模型。
    • 实现:每个单词用其对应的整数索引表示,形成数值向量。
  2. 词汇表管理
    • 作用:在训练模型时,我们需要知道输入数据中有哪些单词。
    • 方便查询:通过索引,我们可以快速查找单词在词汇表中的位置。
  3. 简化计算
    • 优势:数字表示更易于机器处理,比处理字符串更高效。

8.2、index_to_word

index_to_word 是一个字典,它将每个整数索引映射回对应的单词。这种映射的主要作用有:

  1. 结果解码
    • 目的:在预测阶段,将模型输出的索引转换回可读的文本。
  2. 模型解释
    • 优势:帮助理解和分析模型的输出结果,尤其是在生成文本时。
  3. 调试和可视化
    • 作用:便于在训练和调试过程中,查看数值向量对应的实际单词,帮助检查和调整模型性能。

8.3、使用场景

  • 构建词汇表:在处理文本数据时,首先需要通过所有文本构建一个词汇表,并使用 word_to_indexindex_to_word 映射来管理词汇和索引的关系。
  • 预处理文本:在将文本数据输入模型前,使用 word_to_index 将文本转换为数值形式。
  • 解码预测:在生成模型(如文本生成或机器翻译)中,使用 index_to_word 将模型的输出转化为可读的文本。

9、DataLoader()的参数

DataLoader 是 PyTorch 中的一个核心类,用于加载数据集并提供迭代器接口来批量访问数据。
它在处理大规模数据集时尤其有用,因为它可以自动处理批量化、打乱顺序、并行加载等任务。
下面是 DataLoader 的一些常用参数及其作用的详细解释:
DataLoader 常用参数:

DataLoader(
    dataset, 
    batch_size=1, 
    shuffle=False, 
    sampler=None, 
    batch_sampler=None, 
    num_workers=0, 
    collate_fn=None, 
    pin_memory=False, 
    drop_last=False, 
    timeout=0, 
    worker_init_fn=None, 
    multiprocessing_context=None,
    generator=None,
    prefetch_factor=2,
    persistent_workers=False
)
  1. dataset
  • 类型Dataset 类的实例
  • 说明:指定数据集,DataLoader 从中读取数据。数据集需要实现 __len____getitem__ 方法。
  1. batch_size
  • 类型int
  • 默认值1
  • 说明:每个批次的数据样本数量。DataLoader 会按指定的 batch_size 从数据集中提取数据。
  1. shuffle
  • 类型bool
  • 默认值False
  • 说明:如果为 True,每个epoch在提取数据时会将数据集打乱顺序。打乱顺序有助于打破数据中的顺序依赖,提高模型的泛化能力。
  1. sampler
  • 类型Sampler 类的实例
  • 说明:定义了从数据集中抽样的策略。如果设置了 sampler,则 shuffle 必须为 False
  1. batch_sampler
  • 类型Sampler 类的实例
  • 说明:与 sampler 类似,但返回的是一个批次的索引,而不是单个索引。与 batch_sizeshufflesampler 互斥。
  1. num_workers
  • 类型int
  • 默认值0
  • 说明:用于数据加载的子进程数量。设置为 0 时,数据将在主进程中加载。增加此参数的值可以加快数据加载速度,尤其是在IO密集型数据集上
  1. collate_fn
  • 类型Callable
  • 说明:用于将一个批次的数据合并为一个单一的张量。如果需要对每个批次的数据进行特殊处理,可以定义自己的 collate_fn
  1. pin_memory
  • 类型bool
  • 默认值False
  • 说明:如果设置为 TrueDataLoader 将会在返回之前,将张量复制到CUDA固定内存中,提高GPU数据加载速度
  1. drop_last
  • 类型bool
  • 默认值False
  • 说明:如果为 TrueDataLoader 会丢弃数据集中无法组成完整批次的最后几个样本。这在确保批次大小一致时很有用
  1. timeout
  • 类型float
  • 默认值0
  • 说明:设置 DataLoader 在获取一个批次的数据时的超时时间。超过这个时间将会报错
  1. worker_init_fn
  • 类型Callable
  • 说明:每个子进程启动时,会调用这个函数进行初始化。可以用于设置每个工作进程的随机种子
  1. multiprocessing_context
  • 类型multiprocessing 模块的上下文
  • 说明:指定使用的多进程上下文,例如 'spawn''fork'。在不同平台上,多进程的默认行为可能不同
  1. generator
  • 类型torch.Generator
  • 说明:控制 DataLoader 随机数生成的状态。可以通过设置随机种子,确保数据加载的一致性和可重复性
  1. prefetch_factor
  • 类型int
  • 默认值2
  • 说明:每个工作进程应预取的样本批次数。仅当 num_workers > 0 时使用
  1. persistent_workers
  • 类型bool
  • 默认值False
  • 说明:如果设置为 True,则数据加载器将在整个epoch内保持其工作进程的运行状态,而不会在每个epoch后关闭它们

10、标准深度学习模型训练流程⭐

  1. 数据准备
    • 数据加载:从文件或其他数据源加载数据。
    • 数据预处理:清洗数据,分词,构建词汇表,将数据转换为适合输入模型的格式。
    • 数据集和数据加载器:将预处理后的数据封装成数据集,并使用数据加载器批量加载数据。
  2. 模型构建
    • 模型定义:定义模型的架构,包括输入层、隐藏层、输出层等。
    • 初始化模型:创建模型实例,设置超参数(如词汇表大小、嵌入维度、隐藏层维度等)。
  3. 损失函数和优化器
    • 损失函数:选择适合任务的损失函数,如交叉熵损失用于分类问题。
    • 优化器:选择优化算法,如 Adam、SGD,并设置学习率等超参数。
  4. 训练设置
    • 训练轮数:设置训练的轮数(epoch)。
    • 批量大小:设置每次训练的批量大小(batch size)。
  5. 训练循环
    • 外层循环(epoch):遍历每一轮训练。
      • 数据加载:通过数据加载器批量加载数据。
      • 重置指标:如总损失、迭代次数。
      • 内层循环(batch):遍历每个批次数据。
        • 初始化隐藏状态:为每个批次数据初始化隐藏状态(对于 RNN 类模型)。
        • 前向传播:将输入数据通过模型,计算输出。
        • 计算损失:使用损失函数计算模型输出与目标值之间的误差。
        • 反向传播:计算梯度,进行梯度清零,执行反向传播。
        • 更新参数:通过优化器更新模型参数。
        • 记录指标:累加损失、更新迭代次数等。
      • 记录日志:每轮结束后,计算平均损失和训练时间,打印并记录日志信息。
  6. 保存模型
    • 模型保存:训练结束后,保存模型的参数到文件,以便后续使用。

11、案例的完整代码

下面奉上本次案例的完整代码。
目录结构如下(gitee地址:https://gitee.com/xzl-it/artificial-intelligence):
image.png
完整代码(含注释):

# -*- coding: utf-8 -*-
# @Author: CSDN@逐梦苍穹
# @Time: 2024/8/8 1:14
import re  # 导入正则表达式模块,用于文本清洗和处理
import time
import jieba  # 导入jieba库,用于中文分词
import torch  # 导入 PyTorch 库,用于深度学习
import torch.nn as nn  # 从 PyTorch 中导入神经网络模块
import torch.nn.functional as F  # 从 PyTorch 中导入功能模块,用于实现一些常见的神经网络操作
import torch.optim as optim  # 导入 PyTorch 的优化器模块
from torch.utils.data import DataLoader  # 从 PyTorch 中导入 DataLoader 类,用于批量数据加载

# 检查是否有可用的 GPU
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
print("device: ", device)


def build_vocab():
    file_name = 'data-txt/jaychou_lyrics.txt'

    # TODO 1.清洗文本
    # 初始化一个列表,用于存储清洗后的句子
    clean_sentences = []
    for line in open(file_name, 'r', encoding='utf-8'):  # 打开歌词文件,逐行读取,指定编码为UTF-8
        line = line.replace('〖韩语Rap译文〗', '')  # 去除特定的文本标记
        # TODO 使用正则表达式去除无效字符
        # 去除 除了 中文、英文、数字、部分标点符号外 的其他字符
        line = re.sub(r'[^\u4e00-\u9fa5 a-zA-Z0-9!?,]', '', line)
        # 连续空格替换成1个
        line = re.sub(r'[ ]{2,}', '', line)  # 替换连续的空格为单个空格
        # 去除两侧空格、换行
        line = line.strip()  # 去除行首和行尾的空白字符和换行符
        if len(line) <= 1:  # 如果行的长度小于等于1,则跳过该行
            continue
        # 去除重复行
        if line not in clean_sentences:  # 如果行不在clean_sentences中,则添加
            clean_sentences.append(line)  # 将清洗后的行添加到clean_sentences列表中

    # TODO 2. 语料分词
    # TODO 初始化两个列表: index_to_word用于存储词汇表, all_sentences用于存储所有分词后的句子
    index_to_word, all_sentences = [], []

    for line in clean_sentences:  # 遍历清洗后的句子
        words = jieba.lcut(line)  # 使用jieba对句子进行分词,返回词汇列表
        # print("words: ", words)
        all_sentences.append(words)  # 将分词结果添加到all_sentences列表中
        # print("all_sentences: ", all_sentences)
        for word in words:  # 遍历每个词
            if word not in index_to_word:  # 如果词不在词汇表中
                # print("word: ", word)
                index_to_word.append(word)  # 将词添加到词汇表中
                # print("index_to_word: ", index_to_word)

    # 词到索引映射
    word_to_index = {word: idx for idx, word in enumerate(index_to_word)}  # 创建词到索引的映射字典
    # print("word_to_index_start: ", word_to_index)
    # 词的数量
    word_count = len(index_to_word)  # 计算词汇表中词的数量
    # 句子索引表示
    corpus_idx = []  # 初始化一个列表,用于存储整个语料的索引表示
    for sentence in all_sentences:  # 遍历每个分词后的句子
        temp = []  # 初始化一个临时列表,用于存储句子的索引
        for word in sentence:  # 遍历句子中的每个词
            temp.append(word_to_index[word])  # 将词转换为索引并添加到临时列表中
        # 在每行歌词之间添加空格隔开
        temp.append(word_to_index[' '])  # 在每个句子末尾添加空格的索引作为分隔符
        # print("temp: ", temp)
        # TODO extend()是逐个添加, 区别于extend()
        corpus_idx.extend(temp)  # 将句子的索引表示添加到corpus_idx列表中
        # print("corpus_idx: ", corpus_idx)
    # TODO 返回构建的词汇表、索引映射、词数、语料索引
    return index_to_word, word_to_index, word_count, corpus_idx


# 处理歌词数据集
class LyricsDataset:
    def __init__(self, corpus_idx, num_chars):  # 初始化方法,构造函数
        # 语料数据
        self.corpus_idx = corpus_idx  # 将传入的语料索引列表存储为类的属性
        # 语料长度
        self.num_chars = num_chars  # 将每个输入序列的长度(字符数)存储为类的属性
        # 词的数量
        self.word_count = len(self.corpus_idx)  # 计算语料索引列表的长度,即词汇总数
        # 句子数量
        self.number = self.word_count // self.num_chars  # 计算可以提取的样本序列的数量,每个样本长度为 num_chars

    def __len__(self):  # 定义返回数据集大小的方法
        return self.number  # 返回可以提取的样本序列数量

    # TODO 后续通过DataLoader()加载这里的数据
    def __getitem__(self, idx):  # 定义获取数据集中某个样本的方法
        # 修正索引值到: [0, self.word_count - 1]
        start = min(max(idx, 0), self.word_count - self.num_chars - 2)  # 限制起始索引,确保有效的样本提取范围
        x = self.corpus_idx[start: start + self.num_chars]  # 获取从起始索引开始的 num_chars 长度的序列作为输入
        y = self.corpus_idx[start + 1: start + 1 + self.num_chars]  # 获取从起始索引+1开始的 num_chars 长度的序列作为目标
        return torch.tensor(x), torch.tensor(y)  # 返回输入和目标序列作为张量


# 定义一个文本生成模型类,继承自 nn.Module
class TextGenerator(nn.Module):
    # 初始化方法,构造函数
    def __init__(self, vocab_size):
        print("vocab_size: ", vocab_size)
        # 调用父类(nn.Module)的构造函数
        super(TextGenerator, self).__init__()
        # TODO 定义新变量ebd、rnn、out
        # 初始化词嵌入层,将词汇表中的每个词映射到一个128维的向量
        self.ebd = nn.Embedding(vocab_size, 128)
        # 初始化循环神经网络层,输入和输出都是128维,层数为1
        self.rnn = nn.RNN(128, 128, 1)
        # 初始化线性输出层,将RNN的输出映射到词汇表的大小
        # TODO 输出是需要转为词汇, 所以vocab_size多大, 输出就是多大的
        self.out = nn.Linear(128, vocab_size)

    # 定义前向传播方法
    def forward(self, inputs, hidden):
        # 将输入的词索引转换为嵌入向量,输出维度为 (1, 5, 128)
        embed = self.ebd(inputs).to(device)
        # 对嵌入向量进行dropout正则化,防止过拟合,概率为0.2
        embed = F.dropout(embed, p=0.2)
        # 将嵌入向量的维度从 (1, 5, 128) 转置为 (5, 1, 128) 以匹配RNN的输入要求
        # TODO 这里会调用rnn.py的forward()方法
        output, hidden = self.rnn(embed.transpose(0, 1), hidden)  # embed.transpose(0, 1)调整张量维度
        # 对RNN的输出进行dropout正则化,防止过拟合,概率为0.2
        embed = F.dropout(output, p=0.2)
        # 将RNN的输出维度从 (5, 1, 128) 压缩为 (5, 128)
        # 然后通过线性层将其转换为词汇表大小的向量 (5, vocab_size)
        output = self.out(output.squeeze())
        # 返回输出和隐藏状态
        return output, hidden

    # 初始化隐藏状态的方法
    def init_hidden(self):
        # 返回一个全零的隐藏状态张量,维度为 (1, 1, 128)
        return torch.zeros(1, 1, 128).to(device)


# 构建训练函数
def train(epoch, train_log):
    # 构建词典,返回索引到词,词到索引,词汇数量,和语料索引列表
    index_to_word, word_to_index, word_count, corpus_idx = build_vocab()
    # 创建歌词数据集实例,每个输入序列的长度为 32
    lyrics = LyricsDataset(corpus_idx, 32)
    # 初始化文本生成模型,词汇表大小为 word_count
    model = TextGenerator(word_count).to(device)
    # TODO 正式进入这一阶段代码
    # 定义交叉熵损失函数
    criterion = nn.CrossEntropyLoss()
    # 定义 Adam 优化器,学习率为 1e-3
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    # 打开日志文件用于写入
    file = open(train_log, 'w')
    # 开始训练循环
    for epoch_idx in range(epoch):
        # 数据加载器,打乱顺序,每次取 1 个样本
        lyrics_dataloader = DataLoader(lyrics, shuffle=True, batch_size=1)
        # 记录训练开始时间
        start = time.time()
        # 重置迭代次数
        iter_num = 0
        # 重置训练损失
        total_loss = 0.0
        # 遍历数据加载器中的数据
        for x, y in lyrics_dataloader:
            # 初始化隐藏状态
            hidden = model.init_hidden()
            # 前向传播计算输出和隐藏状态
            x, y = x.to(device), y.to(device)
            output, hidden = model(x, hidden)
            # 计算损失,y.squeeze() 去掉维度大小为1的维度
            # TODO 计算模型输出与实际目标之间的损失(误差)
            loss = criterion(output, y.squeeze())
            # 梯度清零
            optimizer.zero_grad()
            # 反向传播计算梯度
            loss.backward()
            # 更新参数
            optimizer.step()
            # 迭代次数加1
            iter_num += 1
            # 累加损失
            total_loss += loss.item()
        # 构建本次迭代的日志信息
        message = 'epoch %3s loss: %.5f time %.2f' % \
                  (epoch_idx + 1,  # 当前训练轮数
                   total_loss / iter_num,  # 平均损失
                   time.time() - start)  # 本轮训练时间
        # 打印日志信息
        print(message)
        # 写入日志文件
        file.write(message + '\n')
    # 关闭日志文件
    file.close()
    # 保存模型参数到文件
    torch.save(model.state_dict(), 'model/lyrics_model_%d.bin' % epoch)


# 构建预测函数
def predict(start_word, sentence_length, model_path):
    # 构建词典,返回索引到词,词到索引,词汇数量
    index_to_word, word_to_index, word_count, _ = build_vocab()
    # 构建文本生成模型实例,词汇表大小为 word_count
    model = TextGenerator(vocab_size=word_count).to(device)
    # 加载训练好的模型参数
    model.load_state_dict(torch.load(model_path))
    # 初始化隐藏状态
    hidden = model.init_hidden()
    try:
        # 将起始词转换为词索引
        word_idx = word_to_index[start_word]
    except:
        print("该词不在词典中, 请重新输入")
        return
    # 用于存储生成的句子(词索引序列)
    generate_sentence = [word_idx]
    # 生成长度为 sentence_length 的句子
    for _ in range(sentence_length):
        # 前向传播,获取模型输出和隐藏状态
        output, hidden = model(torch.tensor([[word_idx]]).to(device), hidden)
        # print("output: ", output)

        # 获取输出中概率最大的词的索引
        word_idx = torch.argmax(output).item()
        # 将该词索引添加到生成的句子中
        generate_sentence.append(word_idx)
    # 将生成的词索引序列转换为实际词并打印
    for idx in generate_sentence:
        print(index_to_word[idx], end='')
    print()


if __name__ == '__main__':
    train(epoch=1, train_log='log/lyrics_training.log')  # 设置训练轮数为 200, 训练日志文件名为 lyrics_training.log
    predict('分手', 15, 'model/lyrics_model_1.bin')

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1994536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenHarmony编译

简介&#xff1a;本文将会介绍编译OpendHarmony环境的搭建、编译、和刷机&#xff08;rk3568&#xff09; 使用场景&#xff1a;修改系统源码&#xff0c;需要验证修改的功能是否正确、编译镜像、编译SDK 1、VS Code&#xff0c;下载链接&#xff0c;用于修改源码 2、linux环…

“低代码”平台的机遇与挑战

低代码平台的机遇 提高开发效率&#xff1a; 低代码平台通过提供可视化的开发工具和可重用的组件&#xff0c;显著提高了开发效率。开发者能够更快速地构建和部署应用&#xff0c;迅速响应市场需求。据普遍观点&#xff0c;低代码平台能够提升30%以上的开发效率&#xff0c;而无…

记一次用户认证代码重构中设计模式及设计原则的使用

目录 一、需求二、初始代码 - 上来就怼三、重构1 - 单一职责&#xff08;方法级&#xff09;四、重构2 - 单一职责&#xff08;类级、策略模式&#xff09;、简单工厂五、重构3 - 依赖注入&#xff08;避免重复创建对象&#xff09;六、重构4 - 使用Map替代if...else、享元模式…

AI浪潮下的程序员命运:消失还是进化?

随着人工智能技术的迅猛发展&#xff0c;程序员职业正面临着前所未有的挑战与机遇。在这个变革的时代&#xff0c;程序员们究竟该如何应对&#xff0c;才能在AI浪潮中乘风破浪&#xff0c;开创属于自己的朝阳之路呢&#xff1f; 程序员这一职业正迎来前所未有的变革&#xff0…

DC-9靶机渗透测试

DC-9靶机 文章目录 DC-9靶机信息收集web渗透后台渗透用户横向移动权限提升靶机总结 信息收集 扫描目标主机存在的端口信息 存在80和22端口 看来思路应该不会有太大变化了&#xff0c;但是ssh端口是处于过滤的状态 进行漏洞扫描也没扫描出有可以利用的 但知道了一些界面 web渗透…

沪深300股指期货如何操作套期保值?

沪深300股指期货的套期保值是一种重要的风险管理工具&#xff0c;用于对冲投资组合面临的市场风险。 以下是套期保值流程的详细步骤说明&#xff1a; 第一&#xff0c;评估套保需求&#xff1a;投资者首先需要基于对市场走势的深入分析和对投资组合的细致评估&#xff0c;确定…

python-flask-上传多个文件并存储

本地环境&#xff1a;win10 / centos6 &#xff0c; python3 flask入门看这里&#xff1a; ↓ python-flask结合bootstrap实现网页小工具实例-半小时速通版_bootstrap flask-CSDN博客 https://blog.csdn.net/pxy7896/article/details/137854455 动态添加和删除表格中的行&…

论文概览 |《Urban Analytics and City Science》2024.07 Vol.51 Issue.6

本次给大家整理的是《Environment and Planning B: Urban Analytics and City Science》杂志2024年7月第51卷第6期的论文的题目和摘要&#xff0c;一共包括16篇SCI论文&#xff01; 论文1 Digital twins on trial: Can they actually solve wicked societal problems and chan…

[开端]JAVA抽象类使用到redis观察着

一、绪论 当redis内容发生变化时需要通知一些观察者做一些动作怎么做&#xff1f; 二、JAVA抽象类 public abstract class AbstractRedisChangeListener {public abstract void change(String key, String value, String crudType); }使用abstract进行修饰一个类 其中抽象类…

数字信号处理2: 离散信号与系统的频谱分析

文章目录 前言一、实验目的二、实验设备三、实验内容四、实验原理五、实验步骤1.序列的离散傅里叶变换及分析2.利用共轭对称性&#xff0c;设计高效算法计算2个N点实序列的DFT。3.线性卷积及循环卷积的实现及二者关系分析4.比较DFT和FFT的运算时间5.利用FFT求信号频谱及分析采样…

【机器学习】(基础篇三) —— 损失函数和梯度下降

损失函数 损失函数&#xff08;Loss Function&#xff09;&#xff0c;也称为代价函数&#xff08;Cost Function&#xff09;或误差函数&#xff08;Error Function&#xff09;&#xff0c;是机器学习和深度学习中一个核心的概念。它用于量化模型预测与实际目标之间的差异&a…

有序转化数组(LeetCode)

题目 给你一个已经 排好序 的整数数组 和整数 、 、 。对于数组中的每一个元素 &#xff0c;计算函数值 &#xff0c;请 按升序返回数组 。 解题 在时间复杂度为解决问题 def sortTransformedArray(nums, a, b, c):def f(x):return a * x * x b * x cn len(nums)result…

配置Mysql的慢查询日志

一、什么是Mysql慢查询日志 MySQL慢查询日志是MySQL数据库自带的一个功能&#xff0c;用于记录执行时间超过指定阈值的SQL语句&#xff0c;以便于后续的性能优化工作 帮助开发和DBA发现哪些SQL语句需要优化&#xff0c;在哪些地方需要修改&#xff0c;以提高数据库的性能 默认…

【Hot100】LeetCode—51. N 皇后

原题链接&#xff1a;51. N 皇后 1- 思路 使用回溯的方式实现&#xff0c;回溯三部曲 数据结构&#xff1a;定义三维数组&#xff0c;收集结果1- 回溯终止条件&#xff1a;如果遍历到 row n-1 此时就是结果收集的时刻2- 执行 for 循环遍历&#xff0c;执行递归和回溯 2- 实现…

【Linux】系列入门摘抄笔记-3-系统管理、显示相关命令

系统管理、显示相关命令 shutdown/关机和重启 sync 数据同步:把内存中的数据强制向硬盘中保存。不放心的话,应该在关机或重启之前手工执行几次,避免数据丢失。 shutdown [选项] 时间 [警告信息] shutdown -h now shutdown -h 05:30 shutdown -h 5 (5分钟后关机)reb…

计算机毕业设计选题推荐-视频点播系统-Java/Python项目实战

✨作者主页&#xff1a;IT毕设梦工厂✨ 个人简介&#xff1a;曾从事计算机专业培训教学&#xff0c;擅长Java、Python、微信小程序、Golang、安卓Android等项目实战。接项目定制开发、代码讲解、答辩教学、文档编写、降重等。 ☑文末获取源码☑ 精彩专栏推荐⬇⬇⬇ Java项目 Py…

ubuntu安装微信

ubuntu安装微信 微信特性 可以截图无聊天乱码 安装指南 输入下面命令 wget -c -O atzlinux-v12-archive-keyring_lastest_all.deb https://www.atzlinux.com/atzlinux/pool/main/a/atzlinux-archive-keyring/atzlinux-v12-archive-keyring_lastest_all.debsudo apt -y insta…

全国不动产登记技能竞赛有哪些奖项

&#xff08;一&#xff09;个人综合奖 个人综合奖在参加代表展示的选手中产生。 1&#xff0e;对获得全国决赛第1名的选手&#xff0c;符合条件的&#xff0c;按程序向中华全国总工会推荐申报“全国五一劳动奖章”。 2&#xff0e;对获得全国决赛前15名的选手&#xff0c;由竞…

【Vue】vue3中通过自定义指令实现数字的动态增加动画

在Vue 3中通过自定义指令实现数字的动态增加动画&#xff0c;可以利用Vue的自定义指令功能&#xff0c;这允许你扩展Vue的内置指令&#xff0c;使得DOM操作更加灵活和强大。以下是如何创建一个自定义指令来实现数字动态增加动画的步骤&#xff1a; 效果演示 代码实现 1、定义指…

四种推荐算法——Embedding+MLP、WideDeep、DeepFM、NeuralCF

一、EmbeddingMLP模型 EmbeddingMLP 主要是由 Embedding 部分和 MLP 部分这两部分组成&#xff0c;使用 Embedding 层是为了将类别型特征转换成 Embedding 向量&#xff0c;MLP 部分是通过多层神经网络拟合优化目标。——用于广告推荐。 Feature层即输入特征层&#xff0c;是模…