当前位置: 首页 > article >正文

深度学习-循环神经网络RNN

一. 自然语言处理概述

自然语言处理(Nature language Processing, NLP)研究的主要是通过计算机算法来理解自然语言。对于自然语言来说,处理的数据主要就是人类的语言,例如:汉语、英语、法语等,该类型的数据不像我们前面接触的过的结构化数据、或者图像数据可以很方便的进行数值化。

二. 词嵌入层

词嵌入层的作用就是将文本转换为向量。

  1. 语料分词, 构建词与索引的映射词表

  2. nn.Embedding构建词嵌入矩阵

原理

词嵌入层首先会根据输入的词的数量构建一个词向量矩阵[词个数, 词向量(多少列表示一个词)] => [word_count, 词向量维度],例如: 我们有 100 个词,每个词希望转换成 128 维度的向量,那么构建的矩阵形状即为: 100*128,输入的每个词都对应了一个该矩阵中的一个向量。

API及代码

API

在 PyTorch 中,使用 nn.Embedding 词嵌入层来实现输入词的向量化

nn.Embedding(num_embeddings=10, embedding_dim=4)
"""
nn.Embedding 对象构建时,最主要有两个参数:
num_embeddings 表示词的数量
embedding_dim 表示用多少维的向量来表示每个词
"""

词转词向量步骤

  1. 先将语料进行分词,构建词与索引的映射,我们可以把这个映射叫做词表Vocab,词表中每个词都对应了一个唯一的索引

  2. 然后使用 nn.Embedding 构建词嵌入矩阵,词索引对应的向量即为该词对应的数值化后的向量表示。

例如,文本数据为: "北京冬奥的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途。",

代码

import jieba
import torch.nn as nn
import torch
​
​
if __name__ == '__main__':
    # 读取文本
    txt = '北京冬奥的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途。'
    # jieba分词
    words = jieba.lcut(txt)
    print('文本分词: \n', words)
    # 去重
    unique_words = list(set(words))
    print('去重后分词: \n', unique_words)
    # 构建词嵌入层
    embedding = nn.Embedding(num_embeddings=len(unique_words), embedding_dim=4)
    print('词嵌入层: \n', embedding)
    # 词语的词向量表示
    # enumerate: 获取索引和元素
    for i, word in enumerate(unique_words):
        # print(torch.tensor(i), word)
        # 将第i个词转为tensor, 并通过词嵌入层进行转换
        print(word, embedding(torch.tensor(i)))

三. 循环网络RNN

原理

基本介绍

文本数据是具有序列特性的

例如: "我爱你", 这串文本就是具有序列关系的,"爱" 需要在 "我" 之后,"你" 需要在 "爱" 之后, 如果颠倒了顺序,那么可能就会表达不同的意思。

为了表示出数据的序列关系,需要使用循环神经网络(Recurrent Nearal Networks, RNN) 来对数据进行建模,RNN 是一个作用于处理带有序列特点的样本数据。

计算过程

h 表示隐藏状态,

每一次的输入都会包含两个值: 上一个时间步的隐藏状态、当前状态的输入值,输出当前时间步的隐藏状态和当前时间步的预测结果。

上述内容画了 3 个神经元, 但是实际上只有一个神经元,"我爱你" 三个字是重复输入到同一个神经元中。

首先初始化出第一个隐藏状态h0,一般都是全0的一个向量,然后将 "我" 进行词嵌入,转换为向量的表示形式,送入到第一个时间步,然后输出隐藏状态 h1,然后将 h1 和 "爱" 输入到第二个时间步,得到隐藏状态 h2, 将 h2 送入到全连接网络,得到 "你" 的预测概率。

神经元内部计算

上述公式中:

1.Wih 表示输入数据的权重

2.bih 表示输入数据的偏置

3.Whh 表示输入隐藏状态的权重

4.bhh 表示输入隐藏状态的偏置

最后对输出的结果使用 tanh 激活函数进行计算,得到该神经元你的输出。

API

API

RNN = torch.nn.RNN(input_size,hidden_size,num_layer)
"""
参数意义是:
input_size:输入数据的维度,一般设为词向量的维度;embed_num
hidden_size:隐藏层h的维数,也是当前层神经元的输出维度;神经元个数
num_layer: 隐藏层h的层数,默认为1. 层数
将RNN实例化就可以将数据送入进行处理。
"""

★RNN层的使用

模型实例化

RNN = torch.nn.RNN(input_size,hidden_size,num_layer)

前向传播

输入数据和输出结果

将RNN实例化就可以将数据送入其中进行处理,处理的方式如下所示:

output, hn = RNN(x, h0)

输入数据:输入主要包括词嵌入的x 、初始的隐藏层h0

x的表示形式为[seq_len, batch, input_size],即[句子的长度(句中词数量),batch的大小(句子数量),词向量的维度(embed_num: 多少列表示一个词)]

h0的表示形式为[num_layers, batch, hidden_size],即[隐藏层的层数,batch的大小,隐藏层h的维数(对比卷积核维度)]

输出结果:主要包括输出结果output,最后一层的hn

output的表示形式与输入x类似,为[seq_len, batch, hidden_size],即[句子的长度,batch的大小,输出向量的维度]

hn的表示形式与输入h0一样,为[num_layers, batch, hidden_size],即[隐藏层的层数,batch的大,隐藏层h的维度]

RNN展开

一个神经元循环

反向传播

四. 文本生成案例

获取数据构建词表

# 1. 构建词表
def create_vocab():
    file_name = 'data/jaychou_lyrics.txt'
    unique_words = []
    all_words = []
    for line in open(file_name, 'r', encoding='utf-8'):
        words = jieba.lcut(line)
        # 将文件中的行数据分词结果存储到all_words中
        all_words.append(words)
        unique_words.extend(list(set(words)))
    unique_words = list(set(unique_words))
    # print(unique_word)
    # print(len(unique_word))     # 5703
    # 词表长度
    word_count = len(unique_words)
    # 词到索引的映射
    word_to_idx = {word: idx for idx, word in enumerate(unique_words)}
    # 词表索引表示每行的词对应的索引
    corpus_idx = []
    # 遍历每行数据
    for words in all_words:
        # 用于存储每行词对应的索引
        temp = []
        # 遍历每行数据中的每个词
        for word in words:
            # 将词的索引添加到temp中
            temp.append(word_to_idx[word])
        # 一行词的索引添加到temp中后, 在末尾添加特殊符号, 方便后续使用
        # 添加空格在词表中的索引: (word_to_idx[' '])
        temp.append(word_to_idx[' '])
        # 将一行词的索引添加到corpus_idx中, 以' '对应的索引表示结尾
        corpus_idx.extend(temp)
    return unique_words, word_to_idx, word_count, corpus_idx

构建数据集对象

# 2. 构建数据集对象
class LyricsDataset(torch.utils.data.Dataset):
    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
​
    def __len__(self):
        # 返回句子数量
        return self.number
​
    def __getitem__(self, idx):
        # idx指词的索引,并将其修正索引值到文档的范围里面
        start = min(max(idx, 0), self.word_count - self.num_chars - 2)
        # 输入值
        x = self.corpus_idx[start: start + self.num_chars]
        # 网络预测结果(目标值)
        y = self.corpus_idx[start + 1: start + 1 + self.num_chars]
        # 返回结果
        return torch.tensor(x), torch.tensor(y)

构建网络模型

# 3. 构建模型
# 模型构建
class TextGenerator(nn.Module):
    def __init__(self, word_count):
        super(TextGenerator, self).__init__()
        # 初始化词嵌入层: 词向量的维度为128
        self.ebd = nn.Embedding(word_count, 128)
        # 循环网络层: 词向量维度 128, 隐藏向量维度 128, 网络层数1
        self.rnn = nn.RNN(128, 128, 1)
        # 输出层: 特征向量维度128与隐藏向量维度相同,词表中词的个数
        self.out = nn.Linear(128, word_count)
​
    def forward(self, inputs, hidden):
        # 输出维度: (batch, seq_len,词向量维度 128)
        embed = self.ebd(inputs)
        print('*' * 50)
        # 初始化的时候会将input_size传递过来
        print('embed维度:  ', embed.shape)    # (2, 10, 128)
        print('*' * 50)
        # 修改维度: (seq_len, batch,词向量维度 128)
        output, hidden = self.rnn(embed.transpose(0, 1), hidden)
        # 输入维度: (seq_len*batch,词向量维度 ) 输出维度: (seq_len*batch, 128)
        output = self.out(output.reshape((-1, output.shape[-1])))
        # 网络输出结果
        return output, hidden
​
    def init_hidden(self, bs=2):
        # 隐藏层的初始化:[网络层数, batch, 隐藏层向量维度]
        return torch.zeros(1, bs, 128)

模型训练

def train(batch_size=2, num_chars=10, epoch=10):
    # 构建词典
    index_to_word, word_to_index, word_count, corpus_idx = create_vocab()
    # 数据集
    lyrics = LyricsDataset(corpus_idx, num_chars)   # 语料库的索引(未去重), 每个句子中词的个数
    # 初始化模型
    model = TextGenerator(word_count)   # 去重后词表长度
    # 损失函数
    criterion = nn.CrossEntropyLoss()
    # 优化方法
    optimizer = optim.Adam(model.parameters(), lr=1e-3)
    # 训练轮数
    # epoch = 10
    for epoch_idx in range(epoch):   # 数据加载器
        lyrics_dataloader = DataLoader(lyrics, shuffle=True, batch_size=batch_size, drop_last=True)
        # 训练时间
        start = time.time()
        iter_num = 0  # 迭代次数
        # 训练损失
        total_loss = 0.0
        # 遍历数据集
        for x, y in lyrics_dataloader:
            # 隐藏状态的初始化
            hidden = model.init_hidden(x.size(0))
            # 模型计算
            output, hidden = model(x, hidden)
            # 计算损失
            # y:[batch,seq_len]->[seq_len,batch]->[seq_len*batch]
            y = torch.transpose(y, 0, 1).contiguous().view(-1)
            loss = criterion(output, y)
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            iter_num += 1  # 迭代次数加1
            total_loss += loss.item()
        # 打印训练信息
        print('epoch %3s loss: %.5f time %.2f' % (epoch_idx + 1, total_loss / iter_num, time.time() - start))
    # 模型存储
    torch.save(model.state_dict(), 'data/lyrics_model_%d.pth' % epoch)

模型预测

def predict(start_word, sentence_length, bs=1):
    # 构建词典
    index_to_word, word_to_index, word_count, _ = create_vocab()
    # 构建模型
    model = TextGenerator(word_count)
    # 加载参数
    model.load_state_dict(torch.load('data/lyrics_model_10.pth', weights_only=False))
    # 隐藏状态
    hidden = model.init_hidden(bs=bs)      # 预测时的batch_size必须为1, 一次只能预测一个词
    # 将起始词转换为索引
    word_idx = word_to_index[start_word]
    # 产生的词的索引存放位置
    generate_sentence = [word_idx]
    # 遍历到句子长度,获取每一个词
    for _ in range(sentence_length):
        # 模型预测
        output, hidden = model(torch.tensor([[word_idx]]), hidden)
        # 获取预测结果
        word_idx = torch.argmax(output)
        generate_sentence.append(word_idx)
    # 根据产生的索引获取对应的词,并进行打印
    for idx in generate_sentence:
        print(index_to_word[idx], end='')
    print()
​
​
if __name__ == '__main__':
    # unique_words, word_to_idx, word_count, corpus_idx = create_vocab()
    # print(corpus_idx)
    # print(word_to_idx['\n'])
    # print(word_count)
    # # print(len(word_to_idx))
    # # print(corpus_idx)
    # dataset = LyricsDataset(corpus_idx, 30)
    # x, y = dataset.__getitem__(491237)  # 49136
    # print(x)
    # print(y)
    # ld = DataLoader(dataset, batch_size=10)
    # for x, y in ld:
    #     print(x)
    #     print(y)
    #     print('-' * 50)
    #     model = TextGenerator(word_count)
    #     hidden = model.init_hidden(x.size(0))
    #     output, hidden = model(x, hidden)
    #     print(f'x维度: {x.shape}')
    #     print(f'output维度: {output.shape}')
    #
    #     break
    # train(batch_size=1, epoch=30, num_chars=30)
    predict('温柔', 100, bs=1)


http://www.kler.cn/a/402923.html

相关文章:

  • 【c++入门】打开新世界大门之初遇c++
  • 一种构建网络安全知识图谱的实用方法
  • RDD触发算子:一些常用的触发算子(count、foreach、saveAsTextFile、first)
  • Linux 常用命令大全
  • 7 设计模式原则之合成复用原则
  • LabVIEW三针自动校准系统
  • java:简单小练习,面积
  • (Linux)搭建静态网站——基于http/https协议的静态网站
  • Redis的特性
  • 《Django 5 By Example》阅读笔记:p679-p765
  • 【TDOA最小二乘解算】两步最小二乘迭代的TDOA解算方法,适用于二维平面、自适应锚点(附MATLAB代码)
  • 【行之有效】实证软件工程研究方法
  • [241119] .NET 9.0.0 正式发布 | D2 Emerge 收购 CodeProject,拓展软件开发社区影响力
  • 基于 MUSA 的大语言模型推理和服务框架vLLM
  • 湘潭大学软件工程算法设计与分析考试复习笔记(四)
  • 【数据结构-表达式解析】力扣227. 基本计算器 II
  • SpringBoot中的restTemplate请求存在乱码问题的解决
  • 从熟练Python到入门学习C++(record 1)
  • 【数据结构OJ】【图论】图综合练习--拓扑排序
  • java八股-SpringCloud微服务-Eureka理论