循环神经网络基础篇笔记——B站:刘二大人《PyTorch深度学习实践》

目录

1.RNN       

(1)RNN cell

(2)具体计算过程

①例子——使用RNN cell:

②例子——直接使用RNN:

2. Train a model to learn:"hello"->"ohlol"

(1)步骤讲解

 (2)代码

①——RNNcell:

②——RNN:

3.one-hot VS embedding 



1.RNN       

        循环神经网络(Recurrent Neural Network,RNN)是一种在序列数据(如语音、文本等)处理中常用的神经网络模型。与传统的前馈神经网络不同,RNN具有循环的连接,使得网络可以保留和利用之前的信息。

        RNN的主要特点是它可以处理任意长度的序列输入,并且具有记忆功能,能够自动提取和利用序列中的历史信息。这是通过在网络的隐藏层之间添加循环连接实现的,使网络可以保持状态,并将之前的信息与当前的输入一起处理。这种能力使得RNN在处理时序数据和自然语言处理等任务中表现出色。

        然而,传统的RNN存在“梯度消失”和“梯度爆炸”的问题,即在训练过程中,随着时间步长的增加,梯度信息逐渐减小或者增大到不可控的程度。为了解决这个问题,研究者们提出了一些改进的RNN模型,如长短期记忆网络(Long Short-Term Memory,LSTM)和门控循环单元(Gated Recurrent Unit,GRU)等,这些模型对传统的RNN结构进行了修改,能够更好地捕捉长距离的依赖关系。

        总之,RNN是一种非常有用的神经网络模型,适用于处理序列数据和时序任务。它的应用领域包括语言建模、机器翻译、时间序列预测等。通过不断改进和发展,RNN的应用正在不断拓展,并在许多领域中发挥着重要作用。

(1)RNN cell

        RNN cell(循环神经网络单元)是RNN的基本构建块,它负责处理序列数据中每个时间步的输入和隐藏状态。在RNN中,每个时间步都有一个RNN单元,用来保持和更新隐藏状态,以及生成输出。

        RNN单元的核心思想是在输入和隐藏状态之间建立一个循环连接,使得隐藏状态能够传递和保持历史信息。具体来说,RNN单元接收当前时间步的输入和上一时间步的隐藏状态作为输入,并生成当前时间步的输出和新的隐藏状态。这个隐藏状态会被传递到下一个时间步,以捕捉和利用序列中的上下文信息。

RNN cell其实相当于一个线形层Linear,这个线性层是共享的,x1输入RNN cell中后得到h1,h1是隐藏状态和x2一起输入下一个RNN cell,这样进行循环,h0是先验知识

(2)具体计算过程

x_{t}是input_size x 1,所以W_{ih}是hidden_size x input_size的矩阵,h_{t-1}是hidden_size x 1的维度,所以W_{hh}是hidden_size x hidden_size的矩阵,h_{t-1}x_{t}都作线性变换,两者结果相加,对得到的结果用tanh做激活,因为tanh的大小在-1和1之间

公式可以写为:

        在pytorch里的使用:

①例子——使用RNN cell:

import torch
batch_size = 1
seq_len = 3
input_size = 4
hidden_size =2
cell = torch.nn.RNNCell(input_size=input_size, hidden_size=hidden_size)
# (seq, batch, features)
dataset = torch.randn(seq_len, batch_size, input_size)
#这里使用torch.randn函数生成一个形状为(seq_len, batch_size, input_size)的随机张量作为模拟的数据集。
# 其中,seq_len表示序列的长度,batch_size表示批次的大小,input_size表示输入特征的维度。
hidden = torch.zeros(batch_size, hidden_size)
#使用torch.zeros函数生成一个形状为(batch_size, hidden_size)的张量作为初始隐藏状态。初始时,隐藏状态全为0。
for idx, input in enumerate(dataset):
    print('='*20,idx,'='*20)
    print('Input_size:',input.shape)
    hidden = cell(input , hidden)
    print('hidden size:',hidden.shape)
    print(hidden)

结果是

以上代码演示了如何使用RNNCell进行单步的循环神经网络计算,逐步传递输入数据并更新隐藏状态。每个时间步的输出结果都是下一个时间步的隐藏状态,并且在整个循环过程中共享相同的RNNCell对象。

②例子——直接使用RNN:

numLayers

numLayers为1时,表示只有一个RNN单元堆叠在时间维度上,即单层RNN。这种情况下,每个时间步的输出都是下一个时间步的输入。

numLayers大于1时,表示有多个RNN单元堆叠在时间维度上,形成深层RNN。每个RNN单元接收上一层的隐藏状态作为输入,并生成当前层的输出和新的隐藏状态。这种堆叠的结构允许模型在处理序列数据时具有更强大的建模能力,更好地捕捉序列中的长期依赖关系。

简单来说,每一层的RNNcell不仅要给本层的下一个cell传递隐藏hidden,还要给下一层的同一时间步cell传递hidden

代码:

import torch
batch_size = 1
seq_len = 3
input_size = 4
hidden_size =2
num_layers = 1
cell = torch.nn.RNN(input_size=input_size, hidden_size=hidden_size,num_layers = num_layers)
# (seq, batch, features)
inputs = torch.randn(seq_len, batch_size, input_size)
#这里使用torch.randn函数生成一个形状为(seq_len, batch_size, input_size)的随机张量作为模拟的数据集。
# 其中,seq_len表示序列的长度,batch_size表示批次的大小,input_size表示输入特征的维度。
hidden = torch.zeros(num_layers,batch_size, hidden_size)
#使用torch.zeros函数生成一个形状为(batch_size, hidden_size)的张量作为初始隐藏状态。初始时,隐藏状态全为0。

out,hidden = cell(inputs,hidden)
print('Output_size:',out.shape)
print('Output:',out)
print('hidden size:',hidden.shape)
print('hidden:',hidden)

结果:

2. Train a model to learn:"hello"->"ohlol"
(1)步骤讲解

 (2)代码
①——RNNcell:
import torch
batch_size = 1
input_size = 4
hidden_size =4

idex2char = ['e','h','l','o']

x_data = [1,0,2,2,3]
y_data = [3,1,2,3,2]

one_hot_lookup = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]]
x_one_hot = [one_hot_lookup[x] for x in x_data]
#通过遍历x_data的每个元素,获取其对应的独热编码并存储在x_one_hot列表中

inputs = torch.Tensor(x_one_hot).view(-1,batch_size,input_size)
#,当我们对一个张量的形状进行调整(reshape)时,可以使用-1来表示该维度的大小自动根据其他维度和张量元素数目进行推断和计算。
labels = torch.LongTensor(y_data).view(-1,1)

class Model(torch.nn.Module):
    def __init__(self,input_size,hidden_size,batch_size):
        super(Model,self).__init__()
        self.batch_size = batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.rnncell = torch.nn.RNNCell(input_size = self.input_size,hidden_size = self.hidden_size)

    def forward(self,input,hidden):
        hidden = self.rnncell(input,hidden)
        return hidden
    def init_hidden(self):
        return torch.zeros(self.batch_size,self.hidden_size)

net = Model(input_size,hidden_size,batch_size)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(),lr=0.1)

for epoch in range(15):
    loss = 0
    optimizer.zero_grad()
    hidden = net.init_hidden()#初始化hidden
    print("Predicted string: ",end = '')
    for input,label in zip(inputs,labels):
        hidden = net(input,hidden)
        loss += criterion(hidden,label)#最终的和才是总的最后的损失
        _,idx = hidden.max(dim =1)
        print(idex2char[idx.item()],end = '')
    loss.backward()
    optimizer.step()
    print(',Epoch [%d/15] loss = %.4f' % (epoch+1,loss.item()))

结果 :

  

②——RNN:
import torch
batch_size = 1
input_size = 4
hidden_size =4
num_layers = 1
seq_len = 5

idex2char = ['e','h','l','o']

x_data = [1,0,2,2,3]
y_data = [3,1,2,3,2]

one_hot_lookup = [[1,0,0,0],
                  [0,1,0,0],
                  [0,0,1,0],
                  [0,0,0,1]]
x_one_hot = [one_hot_lookup[x] for x in x_data]
#通过遍历x_data的每个元素,获取其对应的独热编码并存储在x_one_hot列表中

inputs = torch.Tensor(x_one_hot).view(seq_len,batch_size,input_size)
#,当我们对一个张量的形状进行调整(reshape)时,可以使用-1来表示该维度的大小自动根据其他维度和张量元素数目进行推断和计算。
labels = torch.LongTensor(y_data)

class Model(torch.nn.Module):
    def __init__(self,input_size,hidden_size,batch_size,num_layers):
        super(Model,self).__init__()
        self.batch_size = batch_size
        self.input_size = input_size
        self.hidden_size = hidden_size
        self.num_layers = num_layers
        self.rnncell = torch.nn.RNN(input_size = self.input_size,hidden_size = self.hidden_size,num_layers = self.num_layers)

    def forward(self,input):
        hidden = torch.zeros(self.num_layers,self.batch_size,self.hidden_size)
        out,_ = self.rnncell(input,hidden)
        return out.view(-1,self.hidden_size)

net = Model(input_size,hidden_size,batch_size,num_layers)
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(),lr=0.1)

for epoch in range(15):
    optimizer.zero_grad()
    outputs = net(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()
    _, idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print('Predicted:', ''.join([idex2char[x] for x in idx]), end='')
    print(',Epoch [%d/15] loss = %.4f' % (epoch + 1, loss.item()))

结果:

 

对于其中outputs和labels适配的问题:

在模型的正向传播 (forward) 中,outputs 是通过 net(inputs) 计算得到的。outputs 的形状为 (seq_len * batch_size, hidden_size),其中 seq_len 是序列长度,batch_size 是批次大小,hidden_size 是隐藏层的大小。

labels 是通过 torch.LongTensor(y_data) 创建的,它是一个一维的长整型张量,形状为 (seq_len,),其中 seq_len 是序列长度。

当使用 torch.nn.CrossEntropyLoss() 时,PyTorch 会自动将 outputs 和 labels 适配,以匹配它们的形状。outputs 会被重塑为 (seq_len * batch_size, hidden_size) 的二维张量,labels 会被重塑为 (seq_len * batch_size,) 的一维张量,然后计算交叉熵损失。

因此,在代码的这一部分,outputs 和 labels 是适配的,可以使用 torch.nn.CrossEntropyLoss() 计算损失。

3.one-hot VS embedding 
  1. 独热编码:

    • 独热编码是一种将分类变量表示为二进制向量的技术。
    • 每个类别被表示为一个全零向量,只在对应的类别索引位置上有一个值为1。
    • 它创建了一个高维稀疏的二进制表示,向量的长度等于变量中唯一类别的数量。
    • 独热编码通常用于没有有序关系的分类变量。
  2. 嵌入:

    • 嵌入是一种将分类变量表示为固定大小的稠密向量的技术。
    • 它将每个类别映射到一个低维连续向量,向量的长度远小于唯一类别的总数。
    • 嵌入通过神经网络在模型训练过程中学习得到。
    • 嵌入可以捕捉到类别之间的语义关系,相似的类别在嵌入空间中的向量会更接近。

比较这两种技术:

  • 独热编码简单直观,易于理解,但可能会产生高维表示,并且无法捕捉到类别之间的语义关系。
  • 嵌入提供了一个低维稠密表示,可以捕捉语义关系,但训练过程可能更加复杂和计算密集

代码: 

import torch
num_class = 4
embedding_size = 10
batch_size = 1
input_size = 4
hidden_size =8
num_layers = 2
seq_len = 5

idex2char = ['e','h','l','o']

x_data = [[1,0,2,2,3]]
#将 x_data 写成 [[1, 0, 2, 2, 3]] 是为了保持数据的形状一致。该代码段中使用的 RNN 模型的 forward 函数中,
# 输入 x 的形状是 (batch_size, seq_len, embedding_size),其中 batch_size 是批处理大小,seq_len 是序列长度,
# embedding_size 是嵌入的维度。在这个例子中,batch_size 的值是 1,seq_len 的值是 5。
#为了保持数据的形状一致,将 x_data 定义为一个二维列表,即 [[1, 0, 2, 2, 3]],表示一个批次的输入数据,
# 其中一批只有一个序列。这样,使用 torch.LongTensor(x_data) 创建的张量 inputs 的形状会是 (1, 5),即一个批次的输入序列数据。
#这样做的目的是为了在模型中对数据进行正确的形状操作,以便进行 RNN 的前向传播。如果 x_data 的形状是一维列表
# ,即 [1, 0, 2, 2, 3],那么在创建 torch.LongTensor 时会得到一个形状为 (5,) 的张量,与模型期望的输入形状 (1, 5) 不匹配。
y_data = [3,1,2,3,2]


inputs = torch.LongTensor(x_data)
#,当我们对一个张量的形状进行调整(reshape)时,可以使用-1来表示该维度的大小自动根据其他维度和张量元素数目进行推断和计算。
labels = torch.LongTensor(y_data)

class Model(torch.nn.Module):
    def __init__(self):
        super(Model,self).__init__()
        self.emb = torch.nn.Embedding(input_size,embedding_size)
        self.rnn = torch.nn.RNN(input_size = embedding_size,hidden_size = hidden_size,num_layers = num_layers,batch_first=True)
        self.fc = torch.nn.Linear(hidden_size,num_class)
    def forward(self,x):
        hidden = torch.zeros(num_layers,x.size(0),hidden_size)
        x = self.emb(x)
        x,_ = self.rnn(x,hidden)
        x = self.fc(x)
        return x.view(-1,num_class)

net = Model()
criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(net.parameters(),lr=0.1)

for epoch in range(15):
    optimizer.zero_grad()
    outputs = net(inputs)
    loss = criterion(outputs, labels)
    #在使用 CrossEntropyLoss 时,PyTorch 会自动将输出张量 output 和目标张量 target 进行适配,以匹配它们的形状
    loss.backward()
    optimizer.step()
    _, idx = outputs.max(dim=1)
    idx = idx.data.numpy()
    print('Predicted:', ''.join([idex2char[x] for x in idx]), end='')
    print(',Epoch [%d/15] loss = %.4f' % (epoch + 1, loss.item()))

结果:

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值