双向循环神经网络是在LSTM提出之前的一种模型。在那之前我们每次都通过前文的词汇去预测后文词汇,这样做通常有很大的误差,因为当前输出可能不仅和前状态有关,也和未来状态相关。为了解决这类问题,我们才提出了BRNN双向循环神经网络,使得我们在预测当前词的时候可以结合上下文一起分析。
具体有关BRNN的基本原理也值得一学,内容很简单,大家可以参考下BRNN原理详解。
这里我们只聊代码。开始吧.
库的引入
import torch
import torchvision
import torch.nn as nn
import torchvision.transforms as transform
配置设备以及定义超参数
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
sequence_length = 28
input_size = 28
hidden_size = 128
num_layers = 2
num_classes = 10
batch_size = 100
num_epochs = 2
learning_rate = 0.003
参数的定义和我们上一篇文章——RNN的一样,无非就是我们两个不同方向的隐含层去传播我们的输入信息和特征。
输入一定要传给每一个隐藏层,无论是前向的还是后向的。而隐藏层的输出也都要传给输出层,隐藏层内部也有传播,因此这个权重连接就很好理解了。
数据引入和加载
train_dataset = torchvision.datasets.MNIST(root = '../../data/',
train = True,
transform = transform.ToTensor(),
download = True)
test_dataset = torchvision.datasets.MNIST(root = '../../data/',
train = False,
transform = transform.ToTensor())
train_loader = torch.utils.data.DataLoader(dataset = train_dataset,
batch_size = batch_size,
shuffle = True)
test_loader = torch.utils.data.DataLoader(dataset = train_dataset,
batch_size = batch_size,
shuffle = False)
仍然使用MNIST数据集
定义BRNN类(多对一)
class BiRNN(nn.Module):
def __init__(self,input_size,hidden_size,num_layers,num_classes):
super(BiRNN, self).__init__()
self.input_size = input_size
self.hidden_size = hidden_size
self.num_layers = num_layers
self.lstm = nn.LSTM(input_size,hidden_size,num_layers,batch_first = True,bidirectional = True)# 这里我们需要注意一下,这个bidirectional参数就是控制双向的参数
# 由于我们是双向的网络,所以最后的hidden_size 是原来的2倍
self.fc = nn.Linear(hidden_size * 2,num_classes)
def forward(self, x):
# 设置初始的隐藏层单元状态和cell状态
# 双向网络,因此num_layers * 2
h0 = torch.zeros(self.num_layers * 2,x.size(0),self.hidden_size).to(device)
c0 = torch.zeros(self.num_layers * 2,x.size(0),self.hidden_size).to(device)
# 前向传播LSTM
out,_ = self.lstm(x,(h0,c0))
# 解码最后一个时间步的隐藏层单元状态
out = self.fc(out[:,-1,:])
return out
创建模型,损失函数,优化器及训练
model = BiRNN(input_size,hidden_size,num_layers,num_classes)
# 损失和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(),lr = learning_rate)
# 训练模型
total_step = len(train_loader)
for epoch in range(num_epochs):
for i,(images,labels) in enumerate(train_loader):
images = images.reshape(-1,sequence_length,input_size).to(device)
labels = labels.to(device)
# 前向传播
outputs = model(images)
loss = criterion(outputs,labels)
# 后向传播及优化
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (i + 1) % 100 == 0:
print('Epoch [{}/{}], Step [{}/{}], Loss: {:.4f}'
.format(epoch + 1, num_epochs, i + 1, total_step, loss.item()))
测试及保存模型
model.eval() # 转换为测试模式
with torch.no_grad():
correct = 0
total = 0
for image,labels in test_loader:
images = images.reshape(-1,sequence_length,input_size).to(device)
labels = labels.to(device)
outputs = model(images)
_,predicted = torch.max(outputs.data,1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print('Test Accuracy of the model on the 10000 test images: {} %'.format(100 * correct / total))
torch.save(model.state_dict(), 'model.ckpt')
双向循环神经网络也是一个比较重要的内容,因为利用它我们可以剖析上下文对当前词汇的影响从而预测出更好的结果。比如说
我们现在有一个句子:
Teddy bear is a lovely toy for children all over the world.
这个句子中如果我们要预测bear这个位置的话,运用传统的RNN模型,网络会这样想:
Teddy在语料库中应该是一个人名,所以Teddy是一个人,后面的句子都会把Teddy当作一个人类来处理,但是事情并非如此。
如果我们看到了后面的toy for children
就一定可以精确预测出其并非人名,而是一种玩具,就可以提高我们的预测准确度。
OK啦,BRNN的实现就是这样,其实就是RNN 的变体,只不过改一些参数和尺寸大小。下次我们说deep ResNet深度残差网络.