目录
2. Train a model to learn:"hello"->"ohlol"
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)具体计算过程
是input_size x 1,所以是hidden_size x input_size的矩阵,是hidden_size x 1的维度,所以是hidden_size x hidden_size的矩阵,和都作线性变换,两者结果相加,对得到的结果用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。
- 它创建了一个高维稀疏的二进制表示,向量的长度等于变量中唯一类别的数量。
- 独热编码通常用于没有有序关系的分类变量。
-
嵌入:
- 嵌入是一种将分类变量表示为固定大小的稠密向量的技术。
- 它将每个类别映射到一个低维连续向量,向量的长度远小于唯一类别的总数。
- 嵌入通过神经网络在模型训练过程中学习得到。
- 嵌入可以捕捉到类别之间的语义关系,相似的类别在嵌入空间中的向量会更接近。
比较这两种技术:
- 独热编码简单直观,易于理解,但可能会产生高维表示,并且无法捕捉到类别之间的语义关系。
- 嵌入提供了一个低维稠密表示,可以捕捉语义关系,但训练过程可能更加复杂和计算密集
代码:
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()))
结果: