联系邮箱:[email protected]
import torch
import torch.nn as nn
import numpy as np
import use_package
import qiqi_package as qiqi
import torch.utils.data as Data
import sys
import torchvision
import torchvision.transforms as transforms
import time
import math
importing Jupyter notebook from qiqi_package.ipynb
x = torch.empty(5, 3)
print(x)
x = torch.rand(5, 3)
print(x)
x = torch.zeros(5, 3, dtype=torch.long) #类型选择为long
print(x)
Note:跳过一部分过于基础的学习,直接进入重点部分
#运算内存
x = torch.tensor([1, 2])
y = torch.tensor([3, 4])
id_before = id(y)
y = y + x
id_now =id(y)
print('y原来的id',id_before,'\ny之后的id',id_now)
print('可以看出两者id不一样',id(y) == id_before) # False
# Tensor 和 Numpy互换 Tensor->Numpy
a = torch.ones(5)
b = a.numpy()
print('ida:',id(a),'idb:',id(b))
print(a, b)
a+=1 #attention!
print('a:',a)
print('b:',b)
a=a+1 #attention!
print('a:',a)
print('b:',b)
# Tensor 和 Numpy互换 Numpy->Tensor
a = np.ones(5)
b = torch.from_numpy(a)
print(a, b)
a += 1
print(a, b)
a = a+ 1 #attention
print(a, b)
#Tensor on Gpu
# 以下代码只有在PyTorch GPU版本上才会执行
if torch.cuda.is_available():
device = torch.device("cuda") # GPU
y = torch.ones_like(x, device=device) # 直接创建一个在GPU上的Tensor
x = x.to(device) # 等价于 .to("cuda")
z = x + y
print(z)
print(z.to("cpu", torch.double)) # to()还可以同时更改数据类型
上一节介绍的Tensor是这个包的核心类,如果将其属性 .requires_grad
设置为True
,它将开始追踪(track)在其上的所有操作(这样就可以利用链式法则进行梯度传播了)。完成计算后,可以调用.backward()
来完成所有梯度计算。此Tensor的梯度将累积到.grad
属性中。
注意在y.backward()
时,如果y是标量,则不需要为backward()
传入任何参数;否则,需要传入一个与y同形的Tensor。
如果不想要被继续追踪,可以调用.detach()
将其从追踪记录中分离出来,这样就可以防止将来的计算被追踪,这样梯度就传不过去了。此外,还可以用with torch.no_grad()
将不想被追踪的操作代码块包裹起来,这种方法在评估模型的时候很常用,因为在评估模型时,我们并不需要计算可训练参数(requires_grad=True)
的梯度。
Function
是另外一个很重要的类。Tensor
和Function
互相结合就可以构建一个记录有整个计算过程的有向无环图(DAG)。每个Tensor都有一个.grad_fn
属性,该属性即创建该Tensor的Function, 就是说该Tensor是不是通过某些运算得到的,若是,则grad_fn
返回一个与这些运算相关的对象,否则是None。
1. 创建一个`Tensor`,并设置`requires_grad=True`,并观察其梯度
2. 进行运算操作
x = torch.ones(2,2,requires_grad=True)
print(x)
print(x.grad_fn,'可以看到此时暂无grad')
y = x+ 2
print(y)
print(y.grad_fn)
注意x是直接创建的,所以它没有grad_fn
, 而y是通过一个加法操作创建的,所以它有一个为
的grad_fn
。
像x这种直接创建的称为叶子节点,叶子节点对应的grad_fn
是None
。
判断x,y是否叶子节点
print(x.is_leaf, y.is_leaf) # True False
z = y * y * 3
out = z.mean()
print('Z:',z, '\nOUT:',out)
Note:这里没有详细往下写,参考连接
https://tangshusen.me/Dive-into-DL-PyTorch/#/chapter02_prerequisite/2.3_autograd
之后用到再写
x = torch.ones(1,requires_grad=True)
print(x.data) # 还是一个tensor
print(x.data.requires_grad) # 但是已经是独立于计算图之外
y = 2 * x
x.data *= 100 # 只改变了值,不会记录在计算图,所以不会影响梯度传播
y.backward()
print(x) # 更改data的值也会影响tensor的值
print(x.grad)
其输出是一个连续值,适用于预测等回归问题.
import torch
from IPython import display
from matplotlib import pyplot as plt
import random
sample num=1000;
featuer num =2
w=[2,-3.4]T ;b=4.2
以及一个e 随机噪声项 来生成label ;e服从 mean=0,std=0.01的正态分布
num_inputs = 2
num_example = 1000
true_w = [2,-3.4]
true_b = 4.2
features = torch.randn(num_example,num_inputs,dtype = torch.float32)
#1000 raw ; 2 column
labels = true_w[0]*features[:,0]+true_w[1]*features[:,1]+true_b
labels += torch.tensor(np.random.normal(0,0.01,size = labels.size()),dtype = torch.float32)
qiqi.set_figsize((3.5,2.5))
plt.scatter(features[:,1].numpy(),labels.numpy(),1)#散点图
利用Pytorch.utils中的data函数
batch_size = 10
#将featuers 和 labels组合
dataset=Data.TensorDataset(features,labels)
data_iter =Data.DataLoader(dataset,batch_size,shuffle=True)
for X, y in data_iter:
print('Features:',X, '\nLabels:',y)
利用torch.nn模块
nn的核心数据结构是Module,它是一个抽象概念,既可以表示神经网络中的某个层(layer),也可以表示一个包含很多层的神经网络。在实际使用中,最常见的做法是继承nn.Module,撰写自己的网络/层。一个nn.Module实例应该包含一些层以及返回输出的前向传播(forward)方法。下面先来看看如何用nn.Module实现一个线性回归模型。
class LinearNET(nn.Module):
def __init__(self,n_feature):
super(LinearNET, self).__init__()
self.linear = nn.Sequential(
nn.Linear(n_feature,1)
)
# forward 定义前向传播
def forward(self,x):
y = self.linear(x)
return y
net = LinearNET(num_inputs)
print(net)
nn.Sequential的简便定义方法:
我个人最常用的方法是写法1
'''
# 写法一
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# 此处还可以传入其他层
)
# 写法二
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......
# 写法三
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1))
# ......
]))
print(net)
print(net[0])
'''
可以通过net.parameters() 来查看模型所有的可学习参数,此函数将返回一个生成器。
print('包含features和labels')
for param in net.parameters():
print(param)
在使用net前,我们需要初始化模型参数,如线性回归模型中的权重和偏差。PyTorch在init模块中提供了多种参数初始化方法。这里的init是initializer的缩写形式。我们通过init.normal_将权重参数每个元素初始化为随机采样于均值为0、标准差为0.01的正态分布。偏差会初始化为零。
nn.init.normal_(net.linear[0].weight,mean=0,std=0.01)
nn.init.constant_(net.linear[0].bias,val=0)
PyTorch在nn模块中提供了各种损失函数,这些损失函数可看作是一种特殊的层,PyTorch也将这些损失函数实现为nn.Module的子类。我们现在使用它提供的均方误差损失作为模型的损失函数。
loss = nn.MSELoss()
同样,我们也无须自己实现小批量随机梯度下降算法。torch.optim模块提供了很多常用的优化算法比如SGD、Adam和RMSProp等。下面我们创建一个用于优化net所有参数的优化器实例,并指定学习率为0.03的小批量随机梯度下降(SGD)为优化算法。
optimizer =torch.optim.SGD(net.parameters(),lr=0.03)
print(optimizer)
有时候我们不想让学习率固定成一个常数,那如何调整学习率呢?主要有两种做法。一种是修改optimizer.param_groups中对应的学习率,另一种是更简单也是较为推荐的做法——新建优化器,由于optimizer十分轻量级,构建开销很小,故而可以构建新的optimizer。但是后者对于使用动量的优化器(如Adam),会丢失动量等状态信息,可能会造成损失函数的收敛出现震荡等情况。
在使用Gluon训练模型时,我们通过调用optim实例的step函数来迭代模型参数。按照小批量随机梯度下降的定义,我们在step函数中指明批量大小,从而对批量中样本梯度求平均。
= 3
for epoch in range(1,+1):
for X,y in data_iter:
output = net(X)
l = loss(output,y.view(-1,1))
optimizer.zero_grad() # 梯度清零,等价于net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
下面我们分别比较学到的模型参数和真实的模型参数。我们从net获得需要的层,并访问其权重(weight)和偏差(bias)。学到的参数和真实的参数很接近。
dense = net.linear[0]
print(true_w,'\n',dense.weight)
print(true_b,'\n',dense.bias)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XXWbk86F-1583034220052)(attachment:image.png)]
这是一个单层神经网络图(输入层不进行计算,故不被计入)
如图,当输出o和输入层中所有的神经元都连接时,这里的输出层又叫做
(fully-connected layer)or(dense layer)全连接层或稠密层
和线性回归不同,softmax回归的输出单元从一个变成了多个,且引入了softmax运算使输出更适合离散值的预测和训练。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DQ8csVYX-1583034220052)(attachment:image.png)]
简要来讲,就是只关心正确类别的预测概率,只要其值足够大,就能确保分类结果正确,而对不是其正确标签的其它值的概率一概不看
当一个样本有多个标签时,同理
我们已经介绍了包括线性回归和softmax回归在内的单层神经网络。然而深度学习主要关注多层模型。在本节中,我们将以多层感知机**(multilayer perceptron,MLP)**为例,介绍多层神经网络的概念。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SRrM42Az-1583034220053)(attachment:image.png)]
输入层不计入计算,则有两层,且隐藏层和输出层都是fully connected
不再赘述
多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例并沿用本节之前定义的符号,多层感知机按以下方式计算输出:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJr3D9MV-1583034220054)(attachment:2020-02-19%2010-45-02%20%E7%9A%84%E5%B1%8F%E5%B9%95%E6%88%AA%E5%9B%BE.png)]
其中ϕ表示激活函数。
下面的mnist_train和mnist_test都是torch.utils.data.Dataset的子类,所以我们可以用len()来获取该数据集的大小,还可以用下标来获取具体的一个样本。训练集中和测试集中的每个类别的图像数分别为6,000和1,000。因为有10个类别,所以训练集和测试集的样本数分别为60,000和10,000。
mnist_train = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
print(type(mnist_train))
print(len(mnist_train),len(mnist_test))
#来访问一个样本:
feature,label = mnist_train[0]
print(feature.shape,label)
# 数值标签转为文本标签
def get_fashion_mnist_labels(labels):
text_labels = ['t-shirt', 'trouser', 'pullover', 'dress', 'coat',
'sandal', 'shirt', 'sneaker', 'bag', 'ankle boot']
return [text_labels[int(i)] for i in labels]
def show_fashion_mnist(images, labels):
"""可以在一行内画出多个图"""
qiqi.use_svg_display()
# 这里的_表示我们忽略(不使用)的变量
_, figs = plt.subplots(1, len(images), figsize=(12, 12))
for f, img, lbl in zip(figs, images, labels):
f.imshow(img.view((28, 28)).numpy())
f.set_title(lbl)
f.axes.get_xaxis().set_visible(False)
f.axes.get_yaxis().set_visible(False)
plt.show()
X, y = [], []
for i in range(10):
X.append(mnist_train[i][0])
y.append(mnist_train[i][1])
show_fashion_mnist(X, get_fashion_mnist_labels(y))
num_inputs=784 #28*28 图片像素尺寸
num_outputs=10
num_hiddens=256
class LinearNet(nn.Module):
def __init__(self, num_inputs, num_hiddens,num_outputs):
super(LinearNet, self).__init__()
self.linear = nn.Sequential(
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens,num_outputs)
)
def forward(self, x): # x shape: (batch, 1, 28, 28)
y = self.linear(x.view(x.shape[0], -1))
return y
net = LinearNet(num_inputs,num_hiddens,num_outputs)
print(net)
#初始化参数
for params in net.parameters():
nn.init.normal_(params, mean=0, std=0.01)
我们将在训练数据集上训练模型,并将训练好的模型在测试数据集上评价模型的表现。前面说过,mnist_train是torch.utils.data.Dataset的子类,所以我们可以将其传入torch.utils.data.DataLoader来创建一个读取小批量数据样本的DataLoader实例。
在实践中,数据读取经常是训练的性能瓶颈,特别当模型较简单或者计算硬件性能较高时。PyTorch的DataLoader中一个很方便的功能是允许使用多进程来加速数据读取。这里我们通过参数num_workers来设置4个进程读取数据。
batch_size = 256
if sys.platform.startswith('win'):
num_workers = 0 # 0表示不用额外的进程来加速读取数据
else:
num_workers = 4
print(num_workers)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
print(len(train_iter))
def evaluate_accuracy(data_iter, net):
acc_sum, n = 0.0, 0
for X, y in data_iter:
acc_sum += (net(X).argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
return acc_sum / n
#因为重复性较强,这里定义成函数
def train_model(net, train_iter, test_iter, loss,num_epochs,
params=None, lr=None, optimizer=None):
print('num_epochs:',num_epochs)
for epoch in range(num_epochs):
train_l_sum, train_acc_sum, n = 0.0, 0.0, 0
#train_l_sum loss的求和
#train_acc_sum acc的求和
for X, y in train_iter:
#y_hat y预测
y_hat = net(X)
#计算loss
l = loss(y_hat, y).sum()
# 梯度清零
if optimizer is not None:
optimizer.zero_grad()
elif params is not None and params[0].grad is not None:
for param in params:
param.grad.data.zero_()
l.backward()
if optimizer is None:
d2l.sgd(params, lr, batch_size)
else:
optimizer.step() # “多层感知器的简洁实现”一节将用到
train_l_sum += l.item()
#argmax 取最大值,相当于取预测里面的最大值
train_acc_sum += (y_hat.argmax(dim=1) == y).float().sum().item()
n += y.shape[0]
test_acc = evaluate_accuracy(test_iter, net)
print('epoch %d, loss %.4f, train acc %.3f, test acc %.3f'
% (epoch + 1, train_l_sum / n, train_acc_sum / n, test_acc))
batch_size = 256
loss = torch.nn.CrossEntropyLoss()
#需要看
optimizer=torch.optim.SGD(net.parameters(),lr=0.5)
num_epochs = 5
train_model(net, train_iter, test_iter, loss, num_epochs, None, None, optimizer)
计算训练误差和泛化误差可以使用之前介绍过的损失函数,例如线性回归用到的平方损失函数和softmax回归用到的交叉熵损失函数。
在机器学习里,我们通常假设训练数据集(训练题)和测试数据集(测试题)里的每一个样本都是从同一个概率分布中相互独立地生成的。基于该独立同分布假设,给定任意一个机器学习模型(含参数),它的训练误差的期望和泛化误差都是一样的。例如,如果我们将模型参数设成随机值(小学生),那么训练误差和泛化误差会非常相近。但我们从前面几节中已经了解到,模型的参数是通过在训练数据集上训练模型而学习出的,参数的选择依据了最小化训练误差(高三备考生)。所以,训练误差的期望小于或等于泛化误差。也就是说,一般情况下,由训练数据集学到的模型参数会使模型在训练数据集上的表现优于或等于在测试数据集上的表现。由于无法从训练误差估计泛化误差,一味地降低训练误差并不意味着泛化误差一定会降低。
机器学习模型应关注降低泛化误差。
从严格意义上讲,测试集只能在所有超参数和模型参数选定后使用一次。不可以使用测试数据选择模型,如调参。由于无法从训练误差估计泛化误差,因此也不应只依赖训练数据选择模型。鉴于此,我们可以预留一部分在训练数据集和测试数据集以外的数据来进行模型选择。这部分数据被称为验证数据集,简称验证集(validation set)。例如,我们可以从给定的训练集中随机选取一小部分作为验证集,而将剩余部分作为真正的训练集。
然而在实际应用中,由于数据不容易获取,测试数据极少只使用一次就丢弃。因此,实践中验证数据集和测试数据集的界限可能比较模糊。从严格意义上讲,除非明确说明,否则本书中实验所使用的测试集应为验证集,实验报告的测试结果(如测试准确率)应为验证结果(如验证准确率)
由于验证数据集不参与模型训练,当训练数据不够用时,预留大量的验证数据显得太奢侈。一种改善的方法是KK折交叉验证(KK-fold cross-validation)。在KK折交叉验证中,我们把原始训练数据集分割成KK个不重合的子数据集,然后我们做KK次模型训练和验证。每一次,我们使用一个子数据集验证模型,并使用其他K−1K−1个子数据集来训练模型。在这KK次训练和验证中,每次用来验证模型的子数据集都不同。最后,我们对这KK次训练误差和验证误差分别求平均。
接下来,我们将探究模型训练中经常出现的两类典型问题:一类是模型无法得到较低的训练误差,我们将这一现象称作欠拟合(underfitting);另一类是模型的训练误差远小于它在测试数据集上的误差,我们称该现象为过拟合(overfitting)。在实践中,我们要尽可能同时应对欠拟合和过拟合。虽然有很多因素可能导致这两种拟合问题,在这里我们重点讨论两个因素:模型复杂度和训练数据集大小。
因为高阶多项式函数模型参数更多,模型函数的选择空间更大,所以高阶多项式函数比低阶多项式函数的复杂度更高。因此,高阶多项式函数比低阶多项式函数更容易在相同的训练数据集上得到更低的训练误差。给定训练数据集,模型复杂度和误差之间的关系通常如图3.4所示。给定训练数据集,如果模型的复杂度过低,很容易出现欠拟合;如果模型复杂度过高,很容易出现过拟合。应对欠拟合和过拟合的一个办法是针对数据集选择合适复杂度的模型。
影响欠拟合和过拟合的另一个重要因素是训练数据集的大小。一般来说,如果训练数据集中样本数过少,特别是比模型参数数量(按元素计)更少时,过拟合更容易发生。此外,泛化误差不会随训练数据集里样本数量增加而增大。因此,在计算资源允许的范围之内,我们通常希望训练数据集大一些,特别是在模型复杂度较高时,例如层数较多的深度学习模型。
- 注:丢弃法只在训练模型时使用
在训练深度学习模型时,正向传播和反向传播之间相互依赖。下面我们仍然以本节中的样例模型分别阐述它们之间的依赖关系。
因此,在模型参数初始化完成后,我们交替地进行正向传播和反向传播,并根据反向传播计算的梯度迭代模型参数。既然我们在反向传播中使用了正向传播中计算得到的中间变量来避免重复计算,那么这个复用也导致正向传播结束后不能立即释放中间变量内存。这也是训练要比预测占用更多内存的一个重要原因。另外需要指出的是,这些中间变量的个数大体上与网络层数线性相关,每个变量的大小跟批量大小和输入个数也是线性相关的,它们是导致较深的神经网络使用较大批量训练时更容易超内存的主要原因。
Module
类来构造模型Module
类是nn模块里提供的一个模型构造类,是所有神经网络的基类
Module
的子类当模型的前向计算为简单串联各个层的计算时,Sequential类可以通过更加简单的方式定义模型。这正是Sequential类的目的:它可以接收一个子模块的有序字典(OrderedDict)或者一系列子模块作为参数来逐一添加Module的实例,而模型的前向计算就是将这些实例按添加的顺序逐一计算。
对于Sequential实例中含模型参数的层,我们可以通过Module类的parameters() 或者 named_parameters方法来访问所有参数(以迭代器的形式返回),后者除了返回参数Tensor外还会返回其名字
一些基础的定义不再详细解释,这里一一列取出来,表示已经知晓
是指在输入高和宽的两侧填充元素(通常是0元素)。
卷积神经网络经常使用奇数高宽的卷积核,如1、3、5,7等等,当卷积核的高和宽不同时,我们可以通过设置高和宽上不同的填充数使输出和输入具有相同的高和宽。
在输入的高和宽两侧分别填充了0元素的二维互相关计算图片
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jKFByvPR-1583034220055)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.2_conv_pad.svg)]
卷积窗口从输入数组的最左上方开始,按从左往右、从上往下的顺序,依次在输入数组上滑动。我们将每次滑动的行数和列数称为步幅(stride)
下图是高和宽上步幅分别为3和2的二维互相关运算
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mudSJBXT-1583034220055)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.2_conv_stride.svg)]
- 填充可以增加输出的高和宽。这常用来使输出与输入具有相同的高和宽。
2.步幅可以减小输出的高和宽,例如输出的高和宽仅为输入的高和宽的1/n(n为大于1的整数)
我们用到的输入和输出都是二维数组,但真实数据的维度经常更高。例如,彩色图像在高和宽2个维度外还有RGB(红、绿、蓝)3个颜色通道。假设彩色图像的高和宽分别是h和w(像素),那么它可以表示为一个3×h×w的多维数组。我们将大小为3的这一维称为通道(channel)维。
含2个输入通道的互相关计算 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DsHrokNK-1583034220056)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.3_conv_multi_in.svg)]
当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1。
池化(pooling)层,它的提出是为了缓解卷积层对位置的过度敏感性。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VOXYqJpz-1583034220056)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.4_pooling.svg)]
1. 最大池化和平均池化分别取池化窗口中输入元素的最大值和平均值作为输出。
2. 池化层的一个主要作用是缓解卷积层对位置的过度敏感性。
3. 可以指定池化层的填充和步幅。
4. 池化层的输出通道数跟输入通道数相同。
LeNet分为卷积层块和全连接层块两个部分。下面我们分别介绍这两个模块。
卷积层块里的基本单位是卷积层后接最大池化层:卷积层用来识别图像里的空间模式,如线条和物体局部,之后的最大池化层则用来降低卷积层对位置的敏感性。卷积层块由两个这样的基本单位重复堆叠构成。在卷积层块中,每个卷积层都使用5×5的窗口,并在输出上使用sigmoid激活函数。第一个卷积层输出通道数为6,第二个卷积层输出通道数则增加到16。这是因为第二个卷积层比第一个卷积层的输入的高和宽要小,所以增加输出通道使两个卷积层的参数尺寸类似。卷积层块的两个最大池化层的窗口形状均为2×2,且步幅为2。由于池化窗口与步幅形状相同,池化窗口在输入上每次滑动所覆盖的区域互不重叠。
卷积层块的输出形状为(批量大小, 通道, 高, 宽)。当卷积层块的输出传入全连接层块时,全连接层块会将小批量中每个样本变平(flatten)。也就是说,全连接层的输入形状将变成二维,其中第一维是小批量中的样本,第二维是每个样本变平后的向量表示,且向量长度为通道、高和宽的乘积。全连接层块含3个全连接层。它们的输出个数分别是120、84和10,其中10为输出的类别个数。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sBcjmLxk-1583034220057)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.5_lenet.png)]
注意: 代码中的计算是我按照论文来计算的过程,而若按照我们自己的Fashion-MNIST数据集中的图像进行分类。每张图像高和宽均是28像素.则计算过程是
-conv层
1. 1x28x28->6x24x24
2. 6x24x24->6x12x12
3. 6x12x12->16x8x8
4. 16x8x8 ->16x4x4
#判断是否可以使用CPU
device= torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('the device is ',device)
# 建立模型
class LeNet(nn.Module):
def __init__(self):
super(LeNet,self).__init__()
self.conv = nn.Sequential(
#经过qiqi计算验证
nn.Conv2d(1,6,5),#in_channels=1, out_channels=6, kernel_size=5 1. 1x28x28->6x24x24
nn.Sigmoid(), #注意:先经过activate fun,再进行Pooling 2. 6x24x24->6x12x12
nn.MaxPool2d(2,2), # kernel_size ,stride
nn.Conv2d(6,16,5), #6x12x12->16x8x8
nn.Sigmoid(),
nn.MaxPool2d(2,2) #到此处,对应上图中的S4 16x8x8 ->16x4x4
)
self.fc = nn.Sequential(
nn.Linear(16*4*4,120), # 为什么in_feature 为 16*4*4 #这里注意一下,我们没有按照论文输入数据
nn.Sigmoid(),
nn.Linear(120,84),
nn.Sigmoid(),
nn.Linear(84,10)
)
def forward(self,img):
feature = self.conv(img)
output = self.fc(feature.view(img.shape[0],-1))
return output
print一下我们的net,可以看到,在卷积层块中输入的高和宽在逐层减小。卷积层由于使用高和宽均为5的卷积核,从而将高和宽分别减小4,而池化层则将高和宽减半,但通道数则从1增加到16。全连接层则逐层减少输出个数,直到变成图像的类别数10.
Lenet = LeNet()
print(Lenet)
下面我们来实验LeNet模型。实验中,我们仍然使用Fashion-MNIST作为训练数据集。
mnist_train = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
batch_size = 256
if sys.platform.startswith('win'):
num_workers = 0 # 0表示不用额外的进程来加速读取数据
else:
num_workers = 4
print(num_workers)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
print(len(train_iter))
因为卷积神经网络计算比多层感知机要复杂,建议使用GPU来加速计算。
>>>a = 2
>>> isinstance (a,int)
True
>>> isinstance (a,str)
False
>>> isinstance (a,(str,int,list)) # 是元组中的一个返回 True
True
.__code__.co_varnames
:将函数局部变量以元组的形式返回。lr, num_epochs = 0.001, 10
optimizer = torch.optim.Adam(Lenet.parameters(), lr=lr)
train_ch5(Lenet, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fB6kjJ3y-1583034220057)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter05/5.6_alexnet.png)]
AlexNet与LeNet的设计理念非常相似,但也有显著的区别。
第一,与相对较小的LeNet相比,AlexNet包含8层变换,其中有5层卷积和2层全连接隐藏层,以及1个全连接输出层。下面我们来详细描述这些层的设计。
AlexNet第一层中的卷积窗口形状是11×11。因为ImageNet中绝大多数图像的高和宽均比MNIST图像的高和宽大10倍以上,ImageNet图像的物体占用更多的像素,所以需要更大的卷积窗口来捕获物体。第二层中的卷积窗口形状减小到5×5,之后全采用3×3。此外,第一、第二和第五个卷积层之后都使用了窗口形状为3×3、步幅为2的最大池化层。而且,AlexNet使用的卷积通道数也大于LeNet中的卷积通道数数十倍。
紧接着最后一个卷积层的是两个输出个数为4096的全连接层。这两个巨大的全连接层带来将近1 GB的模型参数。由于早期显存的限制,最早的AlexNet使用双数据流的设计使一个GPU只需要处理一半模型。幸运的是,显存在过去几年得到了长足的发展,因此通常我们不再需要这样的特别设计了。
第二,AlexNet将sigmoid激活函数改成了更加简单的ReLU激活函数。一方面,ReLU激活函数的计算更简单,例如它并没有sigmoid激活函数中的求幂运算。另一方面,ReLU激活函数在不同的参数初始化方法下使模型更容易训练。这是由于当sigmoid激活函数输出极接近0或1时,这些区域的梯度几乎为0,从而造成反向传播无法继续更新部分模型参数;而ReLU激活函数在正区间的梯度恒为1。因此,若模型参数初始化不当,sigmoid函数可能在正区间得到几乎为0的梯度,从而令模型无法得到有效训练。
第三,AlexNet通过丢弃法来控制全连接层的模型复杂度。而LeNet并没有使用丢弃法。
第四,AlexNet引入了大量的图像增广,如翻转、裁剪和颜色变化,从而进一步扩大数据集来缓解过拟合。我们将在后面(图像增广)详细介绍这种方法。
注意加入了dropout,使用了丢弃法.且原始数据太过庞大,我们仍使用torchvision中的数据,我们只需要对其resize使得其符合模型就可以
注意: 我们在建立模型时候,若图片尺寸与模型输入不符合,一种是更改模型,一种是更改图片尺寸
#判断是否可以使用CPU
device= torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print('the device is ',device)
class AlexNet(nn.Module):
def __init__(self):
super(AlexNet, self).__init__()
self.conv = nn.Sequential(
nn.Conv2d(1, 96, 11, 4), # in_channels, out_channels, kernel_size, stride, padding
#1x(224)x(224)->96x54x54
nn.ReLU(),
nn.MaxPool2d(3, 2), # kernel_size, stride 96x54x54->96x26x26
# 减小卷积窗口,使用填充为2来使得输入与输出的高和宽一致,且增大输出通道数
nn.Conv2d(96, 256, 5, 1, 2), #注意有padding 96x26x26->256x26x26
nn.ReLU(),
nn.MaxPool2d(3, 2),#256x26x26->256x12x12
# 连续3个卷积层,且使用更小的卷积窗口。除了最后的卷积层外,进一步增大了输出通道数。
# 前两个卷积层后不使用池化层来减小输入的高和宽
nn.Conv2d(256, 384, 3, 1, 1), #256x12x12->384x12x12
nn.ReLU(),
nn.Conv2d(384, 384, 3, 1, 1), #384x12x12->384x12x12
nn.ReLU(),
nn.Conv2d(384, 256, 3, 1, 1), #384x12x12->256x12x12
nn.ReLU(),
nn.MaxPool2d(3, 2) #256x5x5
)
# 这里全连接层的输出个数比LeNet中的大数倍。使用丢弃层来缓解过拟合
self.fc = nn.Sequential(
qiqi.FlattenLayer(),
nn.Linear(256*5*5, 4096),
nn.ReLU(),
nn.Dropout(0.5), #使用概率为0.5的丢弃发,缓解过拟合
nn.Linear(4096, 4096),
nn.ReLU(),
nn.Dropout(0.5),
# 输出层。由于这里使用Fashion-MNIST,所以用类别数为10,而非论文中的1000
nn.Linear(4096, 10),
)
def forward(self, img):
img = self.conv(img)
img = self.fc(img)
return img
Alexnet = AlexNet()
print(Alexnet)
X = torch.rand(1,1,224, 224)
i = 0
# named_children获取一级子模块及其名字(named_modules会返回所有子模块,包括子模块的子模块)
for name, blk in Alexnet.named_children():
X = blk(X)
print(name, 'output shape: ', X.shape)
注意,我们用我们的数据集,要事先对图片进行resize
读取数据的时候我们额外做了一步将图像高和宽扩大到AlexNet使用的图像高和宽224。这个可以通过torchvision.transforms.Resize实例来实现。
resize_img = (224,224)
trans=[]
batch_size = 128
trans.append(torchvision.transforms.Resize(size=resize_img))
trans.append(torchvision.transforms.ToTensor())
transform = torchvision.transforms.Compose(trans) #Compose 将两个组合来
root='./Datasets/FashionMNIST'
mnist_train = torchvision.datasets.FashionMNIST(root=root, train=True, download=True, transform=transform)
mnist_test = torchvision.datasets.FashionMNIST(root=root, train=False, download=True, transform=transform)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=4)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=4)
和Lenet训练模型一样,我们直接调用
#训练时间比较长,先注释掉,用的时候去掉注释就OK
lr, num_epochs = 0.001, 5
#optimizer = torch.optim.Adam(Alexnet.parameters(), lr=lr)
#qiqi.train_ch5(Alexnet, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
它的名字来源于论文作者所在的实验室Visual Geometry Group 。VGG提出了可以通过重复使用简单的基础块来构建深度模型的思路。
VGG块的组成规律是:连续使用数个相同的填充为1、窗口形状为3×3的卷积层conv后接上一个步幅为2、窗口形状为2×2的最大池化层maxpooling。卷积层保持输入的高和宽不变,而池化层则对其减半。我们使用vgg_block函数来实现这个基础的VGG块,它可以指定卷积层的数量和输入输出通道数。
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
print(device)
def vgg_block(num_convs, in_channels, out_channels):
blk = []
for i in range(num_convs):
if i == 0:
blk.append(nn.Conv2d(in_channels, out_channels, kernel_size=3, padding=1))
else:
blk.append(nn.Conv2d(out_channels, out_channels, kernel_size=3, padding=1))
blk.append(nn.ReLU())
blk.append(nn.MaxPool2d(kernel_size=2, stride=2)) # 这里会使宽高减半
#print(blk) #一个是[,,,] 列表
#print(*blk) #一个是,,, 元素
return nn.Sequential(*blk)
#def test(*args):
#...定义函数参数时 * 的含义又要有所不同,在这里 *args 表示把传进来的位置参数都装在元组 args 里面。
#比如说上面这个函数,调用 test(1, 2, 3) 的话, args 的值就是 (1, 2, 3)
#vgg_block(2,2,3)
与AlexNet和LeNet一样,VGG网络由卷积层模块后接全连接层模块构成。卷积层模块串联数个vgg_block,其超参数由变量conv_arch定义。该变量指定了每个VGG块里卷积层个数和输入输出通道数。全连接模块则跟AlexNet中的一样。
ratio = 8
small_conv_arch = [(1, 1, 64//ratio), (1, 64//ratio, 128//ratio), (2, 128//ratio, 256//ratio),
(2, 256//ratio, 512//ratio), (2, 512//ratio, 512//ratio)]
# 经过5个vgg_block, 宽高会减半5次, 变成 224/32 = 7
fc_features = 512 * 7 * 7 # c * w * h
fc_hidden_units = 4096 # 任意
class vgg_net(nn.Module):
def __init__ (self,conv_arch, fc_features, fc_hidden_units=4096):
super(vgg_net, self).__init__()
self.net_vgg = nn.Sequential()
# 卷积层部分
for i, (num_convs, in_channels, out_channels) in enumerate(conv_arch):
# 每经过一个vgg_block都会使宽高减半
self.net_vgg.add_module("vgg_block_" + str(i+1), vgg_block(num_convs, in_channels, out_channels))
# 全连接层部分
self.net_fc = nn.Sequential(
qiqi.FlattenLayer(),
nn.Linear(fc_features, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, fc_hidden_units),
nn.ReLU(),
nn.Dropout(0.5),
nn.Linear(fc_hidden_units, 10)
)
def forward(self,x):
x = self.net_vgg(x)
x = self.net_fc(x)
return x
Vggnet = vgg_net(small_conv_arch, fc_features // ratio, fc_hidden_units // ratio)
# named_children获取一级子模块及其名字(named_modules会返回所有子模块,包括子模块的子模块)
print(Vggnet)
X = torch.rand(1, 1, 224, 224)
i=0
for name, blk in Vggnet.named_children():
i=i+1
print(i)
X = blk(X)
print(name, 'output shape: ', X.shape)
mnist_train = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=True, download=True, transform=transforms.ToTensor())
mnist_test = torchvision.datasets.FashionMNIST(root='./Datasets/FashionMNIST', train=False, download=True, transform=transforms.ToTensor())
batch_size = 256
if sys.platform.startswith('win'):
num_workers = 0 # 0表示不用额外的进程来加速读取数据
else:
num_workers = 4
print(num_workers)
train_iter = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=num_workers)
test_iter = torch.utils.data.DataLoader(mnist_test, batch_size=batch_size, shuffle=False, num_workers=num_workers)
print(len(train_iter))
batch_size = 64
# 如出现“out of memory”的报错信息,可减小batch_size或resize
lr, num_epochs = 0.001, 5
optimizer = torch.optim.Adam(Vggnet.parameters(), lr=lr)
qiqi.train_ch5(Vggnet, train_iter, test_iter, batch_size, optimizer, device, num_epochs)
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YL6KK5hz-1583034220058)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter06/6.2_rnn.svg)]
本节将介绍如何预处理一个语言模型数据集,并将其转换成字符级循环神经网络所需要的输入格式。为此,我们收集了周杰伦从第一张专辑《Jay》到第十张专辑《跨时代》中的歌词,并在后面几节里应用循环神经网络来训练一个语言模型。当模型训练好后,我们就可以用这个模型来创作歌词。
首先读取这个数据集,看看前40个字符是什么样的
import zipfile
with zipfile.ZipFile('./data/jaychou_lyrics.txt.zip') as zin:
with zin.open('jaychou_lyrics.txt') as f:
corpus_chars = f.read().decode('utf-8')
corpus_chars[:40]
'想要有直升机\n想要和你飞到宇宙去\n想要和你融化在一起\n融化在宇宙里\n我每天每天每'
这个数据集有6万多个字符。为了打印方便,我们把换行符替换成空格,然后仅使用前1万个字符来训练模型。
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
我们将每个字符映射成一个从0开始的连续整数,又称索引,来方便之后的数据处理。为了得到索引,我们将数据集里所有不同字符取出来,然后将其逐一映射到索引来构造词典。接着,打印vocab_size,即词典中不同字符的个数,又称词典大小。
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
#给 idx_to_char 中的字符编号
vocab_size = len(char_to_idx)
vocab_size # 1027 vocab_size,即词典中不同字符的个数,又称词典大小。
1027
之后,将训练数据集中每个字符转化为索引,并打印前20个字符及其对应的索引。
corpus_indices = [char_to_idx[char] for char in corpus_chars]
sample = corpus_indices[:20]
print('chars:', ''.join([idx_to_char[idx] for idx in sample]))
print('indices:', sample)
chars: 想要有直升机 想要和你飞到宇宙去 想要和
indices: [201, 651, 345, 752, 836, 2, 391, 201, 651, 502, 332, 60, 396, 945, 103, 944, 391, 201, 651, 502]
idx_to_char: 将歌词中不同的字符打乱(不会出现相同的字符元素)
char_to_idx: 对打乱的每个不同的字符进行编号
vocab_size: 一共的字符数目
corpus_indices:将编号应用到歌词中
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = qiqi.load_data_jay_lyrics()
下面构造一个含单隐藏层、隐藏单元个数为256的循环神经网络层rnn_layer。
与上一节中实现的循环神经网络不同,这里rnn_layer的输入形状为(时间步数, 批量大小, 输入个数)。其中输入个数即one-hot向量长度(词典大小)。此外,rnn_layer作为nn.RNN实例,在前向计算后会分别返回输出和隐藏状态h,其中输出指的是隐藏层在各个时间步上计算并输出的隐藏状态,它们通常作为后续输出层的输入。需要强调的是,该“输出”本身并不涉及输出层计算,形状为(时间步数, 批量大小, 隐藏单元个数)。而nn.RNN实例在前向计算返回的隐藏状态指的是隐藏层在最后时间步的隐藏状态:当隐藏层有多层时,每一层的隐藏状态都会记录在该变量中;对于像长短期记忆(LSTM),隐藏状态是一个元组(h, c),即hidden state和cell state。我们会在本章的后面介绍长短期记忆和深度循环神经网络。关于循环神经网络(以LSTM为例)的输出,可以参考下图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WJB25iNU-1583034220058)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter06/6.5.png)]
来看看我们的例子,输出形状为(时间步数, 批量大小, 隐藏单元个数),隐藏状态h的形状为(层数, 批量大小, 隐藏单元个数)。
num_hiddens = 256
# rnn_layer = nn.LSTM(input_size=vocab_size, hidden_size=num_hiddens) # 已测试
rnnlayer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps = 35
batch_size = 2
state = None
X = torch.rand(num_steps,batch_size,vocab_size) # 35 2 1027
print('X_shape:',X.shape)
print(rnnlayer)
Y,state_new=rnnlayer(X,state)
print(Y.shape, len(state_new), state_new[0].shape)
X_shape: torch.Size([35, 2, 1027])
RNN(1027, 256)
torch.Size([35, 2, 256]) 1 torch.Size([2, 256])
class RNNModel(nn.Module):
def __init__(self,rnn_layer,vocab_size):
super(RNNModel,self).__init__()
self.rnn=rnn_layer
self.hidden_size=self.rnn.hidden_size*(2 if self.rnn.bidirectional else 1) #判断是否双向rnn
self.vocab_size = vocab_size
self.dense = nn.Linear(self.hidden_size,vocab_size)
self.state = None
def forward(self,inputs,state):# inputs:(batch,seq_len)
# one-hot 向量表示
X = torch.nn.functional.one_hot(inputs.long(),self.vocab_size).float()
Y,self.state = self.rnn(X,state)
#stack 对应元素的维度相叠加
# 全连接层会首先将Y的形状变成(num_steps * batch_size, num_hiddens),它的输出
# 形状为(num_steps * batch_size, vocab_size)
output = self.dense(Y.view(-1, Y.shape[-1]))
return output, self.state
def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,
char_to_idx):
""":para
num_chars 要预测的文字的长度
"""
state = None
output = [char_to_idx[prefix[0]]] # output会记录prefix加上输出
#print([idx_to_char[i] for i in output])
# output 为 prefix第一个字 的索引
for t in range(num_chars + len(prefix) - 1):
#用得到的一组参数对之后的 num_chars进行预测
X = torch.tensor([output[-1]], device=device).view(1, 1)
#X 预测的最后一个字
if state is not None:
if isinstance(state, tuple): # LSTM, state:(h, c)
state = (state[0].to(device), state[1].to(device))
else:
state = state.to(device)
(Y, state) = model(X, state)
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(int(Y.argmax(dim=1).item()))
return ''.join([idx_to_char[i] for i in output])
rnnmodel = RNNModel(rnnlayer,vocab_size).to(device)
print(rnnmodel)
a = predict_rnn_pytorch('分开', 10, rnnmodel, vocab_size, device, idx_to_char, char_to_idx)
print(a)
RNNModel(
(rnn): RNN(1027, 256)
(dense): Linear(in_features=256, out_features=1027, bias=True)
)
分开自条如语语条七短揍缸
循环神经网络中较容易出现梯度衰减或梯度爆炸。我们会在6.6节(通过时间反向传播)中解释原因。为了应对梯度爆炸,我们可以裁剪梯度(clip gradient)。假设我们把所有模型参数梯度的元素拼接成一个向量 g,并设裁剪的阈值是θ。裁剪后的梯度 的L2
范数不超过θ。
def grad_clipping(params, theta, device):
norm = torch.tensor([0.0], device=device)
for param in params:
norm += (param.grad.data ** 2).sum()
norm = norm.sqrt().item()
if norm > theta:
for param in params:
param.grad.data *= (theta / norm)
实现训练函数
def train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_len, prefixes):
pred_period = 50
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
model.to(device)
state = None
for epoch in range(num_epochs): #num_epochs = 250
l_sum, n, start = 0.0, 0, time.time()
data_iter = qiqi.data_iter_consecutive(corpus_indices, batch_size, num_steps, device) # 相邻采样
for X, Y in data_iter:
if state is not None:
# 使用detach函数从计算图分离隐藏状态, 这是为了
# 使模型参数的梯度计算只依赖一次迭代读取的小批量序列(防止梯度计算开销太大)
if isinstance (state, tuple): # LSTM, state:(h, c) #判断state 是否是tuple类型
state = (state[0].detach(), state[1].detach())
else:
state = state.detach()
(output, state) = model(X, state) # output: 形状为(num_steps * batch_size, vocab_size)
# Y的形状是(batch_size, num_steps),转置后再变成长度为
# batch * num_steps 的向量,这样跟输出的行一一对应
y = torch.transpose(Y, 0, 1).contiguous().view(-1)
# y->对Y进行转置,在按行排列->相当于对Y按列将其排成一行
#contiguous:view只能用在contiguous的variable上。
#如果在view之前用了transpose, permute等,需要用contiguous()来返回一个contiguous copy。
#及把Tensor放到一整块内存上
#
l = loss(output, y.long())
optimizer.zero_grad()
l.backward()
# 梯度裁剪 为了防止梯度爆炸 ,暂且不是很懂,先TODO
grad_clipping(model.parameters(), clipping_theta, device)
optimizer.step()
l_sum += l.item() * y.shape[0]
n += y.shape[0]
try:
perplexity = math.exp(l_sum / n)
except OverflowError:
perplexity = float('inf')
if (epoch + 1) % pred_period == 0:
print('epoch %d, perplexity %f, time %.2f sec' % (
epoch + 1, perplexity, time.time() - start))
for prefix in prefixes:
print(' -', predict_rnn_pytorch(
prefix, pred_len, model, vocab_size, device, idx_to_char,
char_to_idx))
num_epochs, batch_size, lr, clipping_theta = 250, 32, 1e-3, 1e-2 # 注意这里的学习率设置
pred_len, prefixes = 50, ['分开', '不分开']
train_and_predict_rnn_pytorch(rnnmodel, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_len, prefixes)
epoch 50, perplexity 32.570283, time 0.06 sec
- 分开 的不我以 在着 你的你在你 你 你的我 不我你的你 你到你的的 的我的的它 一大的的的
- 不分开 子爱不的的 疯 我 的 不你的的人 我 的的的 不我的的它 一大 的 到子不的你的
epoch 100, perplexity 1.872796, time 0.05 sec
- 分开 颗爱著什 在主已场人三着鸠 子 像茶着涯你爱泪 天忧不默想一在觉美 看的动的在有面啦 水的哼自你
- 不分开 子经光元能 都却动停 我和的 活愿 小 始别 你的后么我有是妈好么多开里啦能别 我泪的 的可
epoch 150, perplexity 1.173038, time 0.05 sec
- 分开 颗爱几让想 这 来泪的 达快 看的时娘 一生我 物样叹秋没 汉危美的了你 来我说窝爱让 让怀 我
- 不分开 教爱光旁我美小 无 到是的离 的 能美还却你 悲场边元爱 爱快再 粥知让我 你妈难用事我定 样
epoch 200, perplexity 1.067348, time 0.05 sec
- 分开 颗爱写子 美 女我哼印 一的到我想老 很 头丘 篮白的颗 让会我么怎已内单了的山每 你想斑 的
- 不分开 教想写旁你美这面我 看愿要的著多 走 许 个没还成伤黑她 假水在发你的它有 给回你 那我的的后
epoch 250, perplexity 1.061014, time 0.05 sec
- 分开 颗爱几什想美这 来会步 能以没已连 的会多 都风折你的后小 我美里女著哼 鸠中的步我 丛笑暴爱窝
- 不分开 语爱写的风默 有我后的人人飞活笑 打主知 再 你么我一色 着 的为女有想柳里你靠线么 板杨亮每
当时间步数较大或者时间步较小时,循环神经网络的梯度较容易出现衰减或爆炸。虽然裁剪梯度可以应对梯度爆炸,但无法解决梯度衰减的问题。通常由于这个原因,循环神经网络在实际中较难捕捉时间序列中时间步距离较大的依赖关系。
门控循环神经网络(gated recurrent neural network)的提出,正是为了更好地捕捉时间序列中时间步距离较大的依赖关系。它通过可以学习的门来控制信息的流动。其中,门控循环单元(gated recurrent unit,GRU)是一种常用的门控循环神经网络 。另一种常用的门控循环神经网络则将在下一节中介绍。
- 门控循环单元中的重置门和更新门的输入均为当前时间步输入Xt 与上一时间步隐藏状态Ht−1
,输出由激活函数为sigmoid函数的全连接层计算得到。[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y51OkAOL-1583034220059)(https://tangshusen.me/Dive-into-DL-PyTorch/img/chapter06/6.7_gru_1.svg)]
- 接下来,门控循环单元将计算候选隐藏状态来辅助稍后的隐藏状态计算。如图6.5所示,我们将当前时间步重置门的输出与上一时间步隐藏状态做按元素乘法(符号为⊙)。如果重置门中元素值接近0,那么意味着重置对应隐藏状态元素为0,即丢弃上一时间步的隐藏状态。如果元素值接近1,那么表示保留上一时间步的隐藏状态。然后,将按元素乘法的结果与当前时间步的输入连结,再通过含激活函数tanh的全连接层计算出候选隐藏状态,其所有元素的值域为[−1,1]。
可以看出
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = qiqi.load_data_jay_lyrics()
lr = 1e-2 # 注意调整学习率
gru_layer = nn.GRU(input_size=vocab_size, hidden_size=num_hiddens)
model = RNNModel(gru_layer, vocab_size).to(device)
train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_len, prefixes)
epoch 50, perplexity 1.003463, time 0.09 sec
- 分开小就句陪抱潮沙 爱斗的的激弄 猪风 三的爸 带 带 带 的 习想的 么给在度想一著 的哭种快说有
- 不分开木带就通等打那爵想选白轻 我只我 有我后 的温漫爱的颗 了 温 弓弄真 一攻 你满着 话默太我
epoch 100, perplexity 1.000940, time 0.09 sec
- 分开小就句陪默了再过痛在颗是么 心一补可 话起 哼 的 夕怪让力颗娘么我的它黑吧日国我了我
- 不分开木带带领胸丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁丁
epoch 150, perplexity 1.000443, time 0.09 sec
- 分开离北隐无无无无无无无无岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩
- 不分开木带带领胸吐托望木馨木馨馨馨馨馨托托木馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨馨
epoch 200, perplexity 1.000259, time 0.09 sec
- 分开离北隐无无无无无无无无无无无无岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩岩
- 不分开木带带领托就木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木木
epoch 250, perplexity 1.692956, time 0.09 sec
- 分开幽星我木爱不使天说来爽的的片样的息了用你腮杰空始场我棒每装想护想的防默粥 女妈她你果惚不再 再的的柳
- 不分开是我蜘 不 是人人不风决清走 的 想不 的 女的你女在度想著再的想坏默了布爱不感香快对刻
LSTM 中引入了3个门,即输入门(input gate)、遗忘门(forget gate)和输出门(output gate),以及与隐藏状态形状相同的记忆细胞(某些文献把记忆细胞当成一种特殊的隐藏状态),从而记录额外的信息。
LSTM 和之前的一模一样
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = qiqi.load_data_jay_lyrics()
实现+训练
lr =1e-2 #0.01
lstm_layer = nn.LSTM(input_size=vocab_size,hidden_size=num_hiddens)
model = RNNModel(lstm_layer,vocab_size)
train_and_predict_rnn_pytorch(model, num_hiddens, vocab_size, device,
corpus_indices, idx_to_char, char_to_idx,
num_epochs, num_steps, lr, clipping_theta,
batch_size, pred_len, prefixes)
epoch 50, perplexity 1.009342, time 0.11 sec
- 分开 小 著型 的阳种得 开幽 杨我你你渡 还后一一哼堂让 到到风语了对你我手 言 沉着经你已飞
- 不分开 小 著型 的我反屋明人干人的重灰色 不句能你多 人 人不快 的 折习温他样日单我后要所 在 温
epoch 100, perplexity 1.001843, time 0.11 sec
- 分开 小 著才客事棍 堡快人 的的有画了爱不双永了我排着了我 小 该几写的想景我 却妈难我有我 堡
- 不分开 小 著斯一这后 依不 想的的些片汉我 像娘一 的 折习 太可快发不 像茶着过懂烦样知一一你你
epoch 150, perplexity 1.000875, time 0.11 sec
- 分开 小 著才带 的 折习想护想安一我步人双现轻上我想想样这 能始你 要娘 我 敢让给在 南我知 你 里
- 不分开 小 著斯一一也兮依文开的的你乡你了敌的 心币棍感感说了 的过折美牵 在弥 在 不双 我你 三
epoch 200, perplexity 1.001510, time 0.11 sec
- 分开 小 著四雨球 像妈多不颗 始 著我 我 在义 很边别 你印右叫知 是人著美语 截出单我 妈可y
- 不分开 才啦哼动 的 龙为过你乡在著色 能 泽你的的也 道见 知汹 能事亚通 一遇口让就 一
epoch 250, perplexity 1.000653, time 0.11 sec
- 分开 小 著四雨球 痛 到我有然的 你箱像发头 的 送 堡 不活义 爱 是要明的随那了他这我在用蝶
- 不分开 才啦哼动 的 龙为过你气早银 开 有那模儿地是 别了我物 跟 你着著 忙景起有 别欢你两你你起