RNN循环神经网络:处理序列数据的神经网络,具有”循环“结构,能够处理和记住前面时间步的信息,使其特别使用与时间序列数据或者有序依赖的任务

序列数据:指在不同时间点上收集到的数据,这类数据反应了某一事物,现象随着时间变化的态度或者程度

特点:后面的数据和前面的数据有关联

RNN的应用:自然语言处理,时间序列预测,语言识别,音乐生成

RNN的组成:词嵌入层,循环网络层,输出层

输入文本文件-》经过分词-》将分词后的每个词进行转化为词向量-》隐藏层-》最终输出结果

词嵌入层:

文本转化为向量,在RNN中的作用:有输入表示,降低维度和捕捉语义相似性

例如:有100个词,每个词希望转化为128维度的向量那么构建矩阵的形状为100*128,输入的每个词对应一个该矩阵的向量

词嵌入层流程:初始化词向量-》输入索引-》查找词向量-》输入到RNN

1. 初始化词向量
  • 背景:首先需要为词汇表中的每个词分配一个初始的向量。词汇表是从语料中提取的所有 unique 词的集合(比如 “猫”“狗”“跑步” 等),每个词会被赋予一个唯一的整数编号(即 “索引”)。
  • 初始化方式
    • 随机初始化:给每个词向量赋一个随机的低维向量(如维度为 100),后续通过模型训练不断优化。
    • 预训练初始化:直接使用预训练好的词向量(如 Word2Vec、GloVe)作为初始值,再根据任务微调(适用于数据量小的场景)。
  • 作用:为每个词提供一个初始的数值表示,作为模型学习的起点。
2. 输入索引
  • 含义:自然语言文本是由词语组成的离散符号,无法直接被模型(如 RNN)处理,因此需要先将词语转换为其在词汇表中对应的整数编号(即 “索引”)。
    • 例如:词汇表为{"猫":0, "狗":1, "跑":2},则句子 “猫跑” 会被转换为索引序列[0, 2]
  • 作用:将离散的词语映射为模型可识别的整数,作为查找词向量的 “钥匙”。
3. 查找词向量
  • 含义:通过词语的索引,从 “词向量矩阵” 中找到对应的向量。
    • 词向量矩阵的形状是 [词汇表大小, 向量维度](比如词汇表有 10000 个词,向量维度为 100,则矩阵是 10000×100)。
    • 对于索引序列[0, 2],查找后会得到两个向量:矩阵中第 0 行的向量(“猫” 的向量)和第 2 行的向量(“跑” 的向量)。
  • 本质:这一步是 “嵌入层(Embedding Layer)” 的核心操作,相当于一个 lookup table(查表),将整数索引转换为连续向量。
4. 输入到 RNN
  • 含义:经过词嵌入得到的向量序列(如[vec(猫), vec(跑)])会被依次输入到循环神经网络(RNN)中。
    • RNN 的特点是能处理序列数据,通过隐藏状态保存上下文信息。例如,处理 “猫跑” 时,RNN 会先接收 “猫” 的向量,更新隐藏状态,再接收 “跑” 的向量,结合之前的状态输出最终结果。
  • 作用:词向量是词语的静态表示,而 RNN 通过处理向量序列,学习到词语在上下文语境中的动态含义(如 “苹果” 在 “吃苹果” 和 “苹果手机” 中的不同含义),最终用于文本分类、翻译等任务。

总结

词嵌入的流程本质是 “离散符号→整数索引→连续向量→模型处理” 的转换链:

  • 初始化词向量是为每个词准备初始数值表示;
  • 输入索引是将词语转换为模型可处理的 “地址”;
  • 查找词向量是通过 “地址” 获取具体的向量值;
  • 输入 RNN 是利用模型学习序列中的语义关联,完成最终任务。

API使用:

import torch
import torch.nn as nn
import jieba

def dm01():
    text="北京冬奥的进度条已经过半,不少外国运动员在完成自己的比赛后踏上归途。"
    # 进行分词
    words=jieba.lcut(text)
    # 去重
    unique_words=list(set(words))
    # 创建词嵌入层,词的数量没有去重的,生成的维度
    embed=nn.Embedding(len(unique_words),128)
    # 通过枚举添加索引
    for idx,word in enumerate(unique_words):
        print(word,idx)
        # 将次索引转化为词向量
        word_vector = embed(torch.tensor(idx))
        print(f"词:{word},词索引:{idx},\t\t词向量:{word_vector}")

if __name__=="__main__":
    dm01()

RNN的网络结构

是一种专门处理序列数据设计的神经网络结构,其核心特点是利用循环连接(隐藏层的输出反馈到输入)来捕获时间或者序列上的动态信息

输入层-》循环层-》输出层

RNN的循环机制使的模型隐层上一时间步产生的结果,能够作为当下时间步输入的一部分(当下时间步的输入除了正常的输入以外,还有上一时间步的隐层输出)对当下时间步的输出产生影响

RNN模型作用:RNN核心作用是建模序列数据的时序依赖关系,适用于需要上下文信息的任务

RNN循环网络计算过程:

上一时间步的隐藏状态加上当前时间步的输入通过循环神经网络层得到一个输入结果和一个当时时间步的隐藏状态,当作下一个时间步的上一隐藏层状态的输入

计算隐藏状态:

每一时间步的隐藏状态h_t是根据当前输入的x_t和前一时刻的隐藏状态h_{t-1}计算的

ht=tanh(WihXt+Bih+WhhH(t-1)+Bhh)

Wih:输入数据的权重

Bih:输入数据的偏置

Whh:输入隐藏状态的权重

H(t-1):输入隐藏状态

Bhh:输入隐藏状态的偏置

ht:输出隐藏状态

最终对输出的结果使用tanh激活函数计算。得到该时间步的输出隐藏状态

API:

RNN层的使用:

传入的参数:输入数据的维度,隐藏层的维度,隐藏层的层数

将RNN实例化传入的参数

output,hn=RNN(x,h0)

x:的维度【序列长度,批次大小,输入维度数】

h0: 的维度【隐藏层的层数,批次大小,隐藏层的维度】

output:的维度【序列长度,批次大小,隐藏层的维度】

hn:的维度【隐藏层的层数,批次大小,隐藏层的维度】

import torch.nn as nn
import torch

# 创建循环网络层
# 参数一,词向量的维度  参数二,隐藏层神经元的个数   参数三,RNN层的个数
rnn=nn.RNN(input_size=128, hidden_size=256, num_layers=1)
# 定义输入维度
# 参数一,分词后的结果  参数二,批次处理样本的个数   参数三,词向量的维度
x=torch.randn(5,32,128)
# 初始化隐藏状态
# 参数一,rnn的层数  参数二,批次处理样本的个数   参数三,神经元的个数
h0=torch.randn(1,32,256)
# 调用rnn的输出   当前时候的output和当前的隐藏状态
# out:参数一,分词后的结果  参数二,批次处理样本的个数   参数三,神经元的个数
# hn:参数一,rnn的层数  参数二,批次处理样本的个数   参数三,神经元的个数
output,hn=rnn(x,h0)
print(f'output.shape:{output.shape}')
print(f'hn.shape:{hn.shape}')

文本生成案例:

import torch
import torch.nn as nn
import jieba
from torch.utils.data import DataLoader
import time
#TODO 1.构建词表
def buil_vocab():
    # 所有词的表,去重后的表
    all_words=[]
    unique_words=[]
    # 对这个文件进行遍历,这个获取的是每一行的数据
    for line in open('../data/jaychou_lyrics.txt','r',encoding='utf-8'):
        # 对每一行的数据进行分词
        words=jieba.lcut(line)

        # 将这一行分词后的数据写入到列表中
        all_words.append(words)
        # 遍历每一行的句子获取去重之后的内容
        for word in words:
            if word not in unique_words:
                unique_words.append(word)

    # 统计去重后有多少个词
    word_count=len(unique_words)
    # 对去重后的数据添加对应的索引,使用字典推导式
    words_to_idx={word:i for i,word in enumerate(unique_words)}
    # print(words_to_idx)
    # 根据这个字典表格来生成全歌词对应的索引列表
    corpus_idx=[]
    # 获取的是每一行数据
    for words in all_words:
        # 创建一个存储数值的临时表
        temp=[]
        # 获取每一行的每一个值
        for word in words:
            # 将他对应的索引值添加到列表中
            temp.append(words_to_idx[word])
        # 遍历完每一行之后添加一个空格
        temp.append(words_to_idx[' '])
        # 最后把这个所有数据加入到全歌词对应索引表中
        corpus_idx.extend(temp)
    return unique_words, words_to_idx, word_count, corpus_idx

# 2、自定义dataset  数据预处理 ,构建数据集
# 需要自动写,所有要手动继承
class LyricsDataSet(torch.utils.data.Dataset):
    # 需要初始化init方法,需要传入语料库对应的索引的列表和你一次获取到的词
    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
    # 创建一个len的魔法方法直接返回 self.number
    def __len__(self):
        # 返回句子的数量
        return self.number
    # 创建一个方法可以自动的获取索引对应的内容
    def __getitem__(self, idx):
        # 对idx进行越界处理,保证他在合法的范围内
        # Strat为起始的索引,后面是索引越界你能取到的最合理的索引位置
        # 后面的内容就是:这个句子的长度-你要获取的长度-1,保证输出还能取到一位
        # 例如总的句子长度为10,你每次取5,但是索引为1000,那么保证你能取到的值就是4,5,6,7,8   9为输出的值
        start=min(max(idx,0),self.word_count-self.num_chars-1)
        # 起始位置加上获取的词长度
        end=start+self.num_chars
        # 输入的x
        x=self.corpus_idx[start:end]  # 从0拿每次拿5个就是【0:5】拿去01234
        y=self.corpus_idx[start+1:end+1] # 就是【1:6】 就是12345
        # 需要输出的数据为张量的形式
        return torch.tensor(x),torch.tensor(y)




# 3、搭建RNN神经网络
class TextGenerator(nn.Module):
    # 1-1初始化方法  参数是去重的词的数量
    def __init__(self, unique_word_count):
        super().__init__()
        # 搭建词嵌入层
        # 参数是 语料中词的数量   你想要的搭建词向量的维度
        self.ebd=nn.Embedding(unique_word_count,128)
        # 循环网络层  词向量的维度,隐藏层的维度,循环网络层的个数
        self.rnn=nn.RNN(128,256,1)
        # 输出层(全连接层)  隐藏层的维度,词表中的词的个数
        # 这个输出的是词表中所有词的概率,选择概率最大的那个
        self.out=nn.Linear(256,unique_word_count)


        # 前向传播你需要输入和初始的隐藏状态

    def forward(self,inputs,hidden):
        # 词嵌入层的处理 :(batch句子数量,序列长度,词向量维度)
        embd=self.ebd(inputs)
        # rnn处理
        # 2.2rnn处理 (序列长度,batch句子数量,隐藏层维度)
        output, hidden = self.rnn(embd.transpose(0, 1), hidden)
        # 全连接只能处理二维数据,隐藏层的维度不变,前面的相乘,后面的不变
        output=self.out(output.reshape(shape=(-1,output.shape[-1])))
        # 预测结果和隐藏层
        return output,hidden

    def init_hidden(self, bs):  # batch_size:批次的大小
        # 隐藏层初始化
        # 网络的层数   批次的大小   隐藏层向量维度
        # 初始化全零的张量
        return torch.zeros(1, bs, 256)

# 模型训练
def train():
    # 1、构建字典
    unique_words, words_to_idx, word_count, corpus_idx = buil_vocab()
    # 2、获取数据集  # 传入语料库对应的索引
    lyricsDataSet = LyricsDataSet(corpus_idx, 32)
    # 3、创建数据加载器
    lyrics_dataloader = DataLoader(lyricsDataSet, batch_size=5, shuffle=True)
    # 4、创建模型  传入去重之后的列表,进行预测
    model = TextGenerator(word_count)
    # 5、定义损失函数
    loss_fn = nn.CrossEntropyLoss()
    # 6.优化器
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    # 7、定义变量 记录训练轮数
    epochs = 2
    for epoch in range(epochs):
        # 定义变量  本轮时间  批次的次数  训练总损失
        start, iter_num, total_loss = time.time(), 0, 0.0

        for x, y in lyrics_dataloader:
            # print(f"y:{y.shape}")
            # 获取隐藏层初始值
            hidden = model.init_hidden(5)
            # 模型计算
            y_pred, hidden = model(x, hidden)
            # 计算损失

            # 为什么这里需要 y.transpose(0, 1).reshape(-1)?
            # 因为:nn.CrossEntropyLoss()的参数要求是
            # 输入格式是:y_pred(N, C)    N = 样本数量    C = 类别数(你的词表大小)
            #          y(targets)      (N,)  每个元素是一个整数类别索引。
            # 当前y形状:(batch_size, seq_len)
            # 模型output的 y (seq_len, batch_size, vocab_size)
            # 所以:两者维度不对应,所以必须对 y 做变换。
            # 故:y.transpose(0, 1)   # -> (seq_len, batch_size)
            # 因为targets要求是  (N,)   # 一维 故.reshape(-1)
            # y的形状为:              批次数  句子长度
            # rnn的输出维度为output形状为: 句子长度  批次数
            loss = loss_fn(y_pred, y.transpose(0, 1).reshape(-1))  # 展成1维,自动计算
            # 梯度清零 反向传播  更新参数
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()

            # 累计损失和迭代次数
            total_loss += loss.item()
            iter_num += 1
        print(f"epoch:{epoch + 1},time:{time.time() - start:.2f}s ,loss:{total_loss / iter_num:.4f}")
    torch.save(model.state_dict(), "../model/TextGenerator.pth")

# 传入的参数是 开始的词   要生成的词的个数
def evaluate(start_word,sentence_length):
    # 构建词典
    unique_words, words_to_idx, word_count, corpus_idx = buil_vocab()
    # 创建模型
    model = TextGenerator(word_count)
    # 加载模型的参数
    model.load_state_dict(torch.load("../model/TextGenerator.pth"))
    # 创建h0
    hidden=model.init_hidden(1)
    # 将第一个词转化为索引在映射字典中找
    word_idx=words_to_idx[start_word]
    # 将这个内容存入到列表中,第一个得是存入的第一个词
    generate_sentence=[word_idx]
    # 循环遍历存入词
    for i in range(sentence_length):
        # 模型的预测  参数1需要转化为词张量,需要增加维度
        output, hidden = model(torch.tensor([[word_idx]]), hidden)
        # 对于输出结果找最大的值
        word_idx=torch.argmax(output)
        # 将这个索引加入到列表中
        generate_sentence.append(word_idx)
    # 遍历这列表
    for idx in generate_sentence:
        # 打印索引对应的值
        print(unique_words[idx],end="")




if __name__ == '__main__':
    # unique_words, words_to_idx, word_count, corpus_idx = buil_vocab()
    # print(f"词的数量:{word_count}")  # 去重后:5703
    # print(f"去重后词:{unique_words}")  # ['想要', '有', '直升机', '\n', '和', '你', '飞到', '宇宙', '去', '融化', '在', '一起', '里', '我
    # print(
    #     f"每个词的索引:{words_to_idx}")  # {'想要': 0, '有': 1, '直升机': 2, '\n': 3, '和': 4, '你': 5, '飞到': 6, '宇宙': 7, '去': 8, '融化': 9, '在': 10,
    # print(f"文档中每个词对应的索引:{corpus_idx}")  # [0, 1, 2, 3, 40, 0, 4, 5, 6, 7, 8, 3, 40, 0, 4, 5, 9, 10,
    # train()
    evaluate("想要",50)


交换维度的原因:

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐