【Pytorch】使用笔记

1. Pytorch环境

Pytorch的官方教程很详细,https://pytorch.org/,直接按照自己的环境安装对应的环境即可。

安装GPU版本的需要安装CUDA,https://developer.nvidia.cn/cuda-toolkit-archive这里可以下载以前的版本,不要装最新的,因为pytorch可能还没有对应的最新的版本。

2. tensor

2.1 Pytorch的数据类型

常用的是FloatTensor、IntTensor、ByteTensor,注意CPU上的数据类型和GPU上的数据类型是不一样的。

在程序中检查数据的类型时一般用isinstance(a, torch.FloatTensor)

1.dim=0(标量)

Dimension=0/rank=0,维度为0,也就是一个标量,一般loss的值是一个标量tensor

a = torch.tensor(1.)
a.dim() # 0
a.shape # torch.Size([])

2.dim=1(张量)

Dimension=1/rank=1,一维的向量,一般神经元的偏执为一个dim=1,size=1的tensor,例如bias = tensor([1])

b = torch.tensor([1.1]) # tensor([1.100])
b = torch.tensor([1.1, 2.2]) # tensor(1.100, 2.200)
c = torch.FloatTensor(1) # tensor([3.2239e-25])随机值
c = torch.FloatTensor(2) # tensor([x, x])随机值
d = torch.ones(2)
d.shape # torch.Size([2])

一般dim指的是维度,例如一个矩阵的dim就是2,而size就是shape,例如[2,2]。

3.dim=2

e = torch.randn(2,3)
e.shape # torch.Size([2,3])
e.shape(1) # 2
e.shape(2) # 3
e.shape[1] # 3
e.shape[0] # 2

4.dim=3

一般用来表示RNN input,例如每次送入5句话,即batch=5,每句话有10个单词,每个单词用100size的向量表示,那size = [10,5,100](一般习惯将batch放在第二维)。

f = torch.rand(1,2,3)
f.shape # torch.Size([1,2,3])

5.dim=4

四维适合卷积神经网络,图片的存储,[b,c,h,w],即batch,channel,height,width

g = torch.rand(2,3,28,28)
g.numel() # 4704,整个维度的元素的个数,即2*3*28*28

2.2 创建Tensor

一个tensor通常包含一下几个常用属性:

  • dtype,常见的有:

    Data type dtype CPU tensor GPU tensor
    32-bit floating point torch.float32 or torch.float torch.FloatTensor torch.cuda.FloatTensor
    64-bit floating point torch.float64 or torch.double torch.DoubleTensor torch.cuda.DoubleTensor
    32-bit integer (signed) torch.int32 or torch.int torch.IntTensor torch.cuda.IntTensor
    64-bit integer (signed) torch.int64 or torch.long torch.LongTensor torch.cuda.LongTensor

    通过torch.ser_default_tensor_type(torch.FloatTensor)修改默认的类型。

    CPU与GPU类型的相互转换:

    >>> torch.zeros([2, 4], dtype=torch.int32)
    tensor([[ 0,  0,  0,  0],
            [ 0,  0,  0,  0]], dtype=torch.int32)
    >>> cuda0 = torch.device('cuda:0')
    >>> torch.ones([2, 4], dtype=torch.float64, device=cuda0)
    tensor([[ 1.0000,  1.0000,  1.0000,  1.0000],
            [ 1.0000,  1.0000,  1.0000,  1.0000]], dtype=torch.float64, device='cuda:0')
    # 从cpu –> gpu,使用data.cuda()即可。
    # 若从gpu –> cpu,则使用data.cpu()。
    
  • requires_grad=Flase,可以先理解成在自动求梯度的过程中,对于该属性为False的tensor不会进行求解梯度。

  • .shape属性,返回数据的尺寸

如何创建一个tensor,主要有一下几种方法:

  1. 从其它类型转换

    torch.from_numpy(np.array(2, 3.3))
    # 从numpy转换 -> tensor([2.000, 3.300], dtype=torch.float64)
    
    torch.tensor([2., 3.2]) 
    # 从list转换,类型默认是torch.FloatTensor,可以通过torch.ser_default_tensor_type(torch.FloatTensor)修改默认类型
    torch.tensor([2., 3.2], dtype=torch.float64)
    # 或者创建时指定
    
  2. 未初始化Tensor

    生成未初始化的数据,数据分布十分不规律,后续一定要覆盖掉。例如Torch.FloatTensor(d1,d2,d3)。

    注意torch.Tensor()/torch.FloatTensor()接收的参数是shape维度,也可以接收现有的数据但必须是list,而torch.tensor()只接收现有的数据,建议用torch.tensor()用现有的数据创建,用torch.Tensor()接收shape创建。

  3. 随机初始化Tensor

    a = torch.rand(3,3) # 3x3矩阵,每个都在[0,1]之间
    
    torch.rand_like(a) # 会将参数a的shape送给rand函数
    
    torch.randint(1,10,[3,3]) #min, max, shape
    
    torch.empty(7, 19) # 创建7x19的矩阵
    
    torch.randn(3, 3) # 3x3的矩阵,每个数据都符合N(0,1)的正态分布
    
    torch.normal() # ?
    
    torch.full([2,3],0) # 2x3的元素全部为0
    

2.3 tensor常用方法

torch.reshape(input, (1, 2, 3, 4)) # 转换为四维

3. 梯度

导数和偏微分只能是描述的函数在自变量方向上的变化的情况,是一个标量;而梯度定义为一个向量,向量的每个元素是对不同的自变量的偏微分:

f=(fx1;fx2;...;fxn)\nabla f = (\frac{\partial f}{\partial x_1}; \frac{\partial f}{\partial x_2}; ...; \frac{\partial f}{\partial x_n})

一般我们需要用梯度函数的极值点,通常是是极小值,用到的公式就是:

θt+1=θtαtf(θt)\theta_{t+1} = \theta_t - \alpha_t\nabla f(\theta_t)

示例:

例如求一个函数J(x1,x2)=x12+x22J(x_1, x_2) = x_1^2 + x_2^2的极小值,即目标为minJ(x1,x2)minJ(x_1,x_2)

x1=x1αddx1J(x1,x2)=x12αx1x1=x1αddx2J(x1,x2)=x12αx2x_1 = x_1 - \alpha \frac{d}{dx_1}J(x_1,x_2)=x_1-2\alpha x_1 \\ x_1 = x_1 - \alpha \frac{d}{dx_2}J(x_1,x_2)=x_1-2\alpha x_2

比局部最小值更可怕的问题是鞍点的情况,比如一个二维的函数,很可能出现一种情况,就是某个点是其中一个维度的极小值,但却是另一个维度的极大值。那么对于多维的函数来说这种情况是很常见的 。

3.1 影响优化器的性能的因素

  1. 初始化状态

  2. 学习率

  3. 逃离局部最小值的方法

    给出一个惯性,也就是当找到极小值点的时候根据惯性继续向前,有可能会逃离出极小值点

4. 激活函数

为了将线性函数转换为非线性函数和方便求导,通常要在神经元的末尾加上激活函数。

4.1 sigmoid

sigmoid会将值压缩到(0,1)区间,同时导数非常容易求解:

σ=σ(1σ)\sigma' = \sigma(1-\sigma)

torch.sigmoid()

4.2 tanh

tanh是由sigmoid变换而来的,导数:

tanh=1tanh2tanh' = 1-tanh^2

torch.tanh()

4.3 ReLU

ReLU是非常适合现在深度学习的,导数计算起来非常简单,当x<0时,梯度=0,当x>0时,梯度=1,不会出现放大和缩小的情况,很难出现梯度弥散和梯度保障的情况

torch.relu()

优先使用ReLU函数。

import torch
import torch.nn.functional as F
from torch.autograd import Variable

x = torch.linspace(-5, 5, 200)
x = Variable(x)

x_np = x.data.numpy()

y_relu = F.relu(x).data.numpy()
y_sigmoid = F.sigmoid(x).data.numpy()
y_tanh = F.tanh(x).data.numpy()
y_softplus = F.softplus(x).data.numpy()

import matplotlib.pyplot as plt  # python 的可视化模块, 我有教程 (https://mofanpy.com/tutorials/data-manipulation/plt/)

plt.figure(1, figsize=(8, 6))
plt.subplot(221)
plt.plot(x_np, y_relu, c='red', label='relu')
plt.ylim((-1, 5))
plt.legend(loc='best')

plt.subplot(222)
plt.plot(x_np, y_sigmoid, c='red', label='sigmoid')
plt.ylim((-0.2, 1.2))
plt.legend(loc='best')

plt.subplot(223)
plt.plot(x_np, y_tanh, c='red', label='tanh')
plt.ylim((-1.2, 1.2))
plt.legend(loc='best')

plt.subplot(224)
plt.plot(x_np, y_softplus, c='red', label='softplus')
plt.ylim((-0.2, 6))
plt.legend(loc='best')

plt.show()

5. 损失函数Loss

损失函数分为两大类:

  1. Mean Squared Error均方差

    MSE=(yyˉ)2MSE = \sum(y-\bar y)^2

  2. Cross Entropy Loss

    Entropy熵,熵越高,不确定性uncertainty越小,也就是越稳定的状态。

    Entropy=p(x)logp(x)Entropy = -\sum p(x)log p(x) \\

    交叉熵就是两个不同的分布,定义为:

    Cross Entropy=H(p,q)=p(x)logq(x)=H(p)+DKL(pq)Cross \space Entropy = H(p,q) = - \sum p(x)logq(x) \\ = H(p) + D_{KL}(p|q)

    DKL(pq)D_{KL}(p|q)定义为散度,当两个分布重合的部分越低,也就是越不相似,其值越高,反之越低。

    在分类的问题当中,通常采用one-hot编码,这种编码的Entropy为0,带入到Cross Entropy中:

    Cross Entropy=DKL(pq)Cross \space Entropy = D_{KL}(p|q)

    恰好是两个分布的散度,我们可以将其作为优化目标,也就是模型的预测分布和真实分布之间的散度,最小化该散度,也就是最小化两个分布之间的差距。

    可以求二分类和多分类问题的损失值,搭配softmax激活函数效果会更好。

    • binary
    • multi-class
    • softmax

5.1 Loss梯度

由于模型的目标是将loss降到最低,因此需要找到loss的最小值,那么通过对loss求导的方式寻找。

Loss的梯度求解有两种方法

  1. torch.autograd.grad(loss, [w1, w2, ...])

    用最后的loss对参数[w1, w1, ...]进行求偏导,返回一个导数值的列表

  2. loss.backward()

    这种方法是求解梯度并进行反向传播,也就是说该方法完成了梯度的求解过程以及反向传播的过程

示例:

# MSE均方误差
x = torch.ones(1)
w = torch.full([1],2)  # y = wx
mse = F.mse_loss(torch.ones(1), x*w)
# 计算loss的梯度
torch.autograd.grad(mse, [w])  # 第一个参数是loss,第二个参数是所有网络参数

# 或者loss对象可以直接调用backward()
mse.backward()

"""
CEL
"""
a = torch.rand(3)
p = F.softmax(a, dim=0)
p.backward()

6. 优化算法

x = torch.tensor([0, 0], requires_grad=True)
optimizer = torch.optim.Adam([x], lr=1e-3)  # 创建优化器对象,传入的是待优化的参数和学习率

for step in range(20000):
    pred = f(x)
    
    optimizer.zero_grad()
    pred.backward()  # 就会生成相应的梯度信息
    optimizer.step()  # 每次调用一次step函数就会更新一次x' = x - lr*dx, y' = y - lr*dy...

7. Dataset/DataLoader

7.1 Dataset

7.2 DataLoader

torch.utils.data.DataLoader()

# 常用参数
- dataset
- batch_size
- shuffle 顺序打乱,默认为False
- num_workers 加载数据的线程数量,默认为0,主线程加载

dataloader = DataLoader(dataset, 64, shuffle=True)
for batch_data, targets in dataloader:
    ...

8. nn.Linear()

8. nn.Module模块

当自己需要实现一个网络模型的时候需要继承这个类,在内部定义的每一个层次也是nn.Module类,所以它们之间有嵌套关系。

  • Every Layer is nn.Module
  • nn.Module nested in nn.Module

继承nn.Module类后,需要在__init__(self)函数中定义自己网络的所有层结构,然后在forward(self)函数中串联每个层。

8.1 容器container

如果层次非常多的话,可以将所有层次都定义在nn.Sequential()中:

self.net = nn.Sequential(
	nn.Conv2d(1, 32, 5, 1, 1),
    nn.MaxPool(2, 2),
    nn.ReLU(True),
    nn.BatchNorm2d(32),
    
    nn.Conv2d(32, 64, 3, 1, 1),
    nn.ReLU(True),
    nn.BatchNorm2d(64),
    
    ...
    # 这其中可以用自己的定义的其它网络
)
# 注意Squential接受的参数都是class

这种方式就不需要为每个层定义一个变量,也不需要为每个其进行forward串联。

8.2 参数管理

nn.Module会对网络中的参数进行管理,net.parameters()会返回所有网络中的参数,通常使用这种方法将所有参数传到优化器中去。

示例

class BasicNet(nn.Module):
    def __init__(self):
        super(BasicNet, self).__init__()
        self.net = nn.Linear(4, 3)
    
    def forward(self, x):
        return self.net(x)
    
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.net = nn.Sequential(
        	BasicNet(),
            nn.ReLU(),
            nn.Linear(3,2)
        )
    
    def forward(self, x):
        return self.net(x)
    
device = torch.device('cuda')
net = Net()
net.to(device)

save/load

net.load_state_dict(torch.load('ckpt.mdl'))
# train...

torch.save(net.stat_dict(), 'ckpt.mdl')

train/test

对于dropout等有的层结构来说,train和test的状态是不太一样的,nn.Module提供了状态切换的方法。

net.train()  # train
net.eval()  # test

9. PyTorch神经网络结构

神经网络可以通过torch.nn包来构建。神经网络是基于自动梯度来定义一些模型,一个nn.Module包括层和一个forwoard(input)方法,返回为output。

例如一个前馈神经网络,它接受输入,让输入一个接着一个的通过一些层,最后给出输出。

一个典型的神经网络训练过程包括:

  1. 定义一个包含可训练参数的神经网络

  2. 迭代整个输入

  3. 通过神经网络处理输入

  4. 计算损失

  5. 反向传播梯度到神经网络的参数

  6. 更新网络参数,典型的用一个简单的更新方法:

    weight=weightlearning_rategradientweight = weight - learning\_rate * gradient

9.1 定义神经网络

定义神经网络的结构:

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 1 input image channel, 6 output channels, 5x5 square convolution
        # kernel
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # Max pooling over a (2, 2) window
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # If the size is a square you can only specify a single number
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # all dimensions except the batch dimension
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

'''
Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)
'''

这是一个前馈网络,然后反向传播函数默认被autograd(自动梯度)定义了。

一个模型的可训练的参数可以通过调用net.parameters()返回:

params = list(net.parameters())
print(len(params))
print(params[0].size())

'''
10
torch.Size([6,1,5,5])
'''

9.2 处理输入

接着将输入放到网络中,将得到的计算结果调用反向传播:

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

'''
tensor([[-0.0233,  0.0159, -0.0249,  0.1413,  0.0663,  0.0297, -0.0940, -0.0135,
          0.1003, -0.0559]], grad_fn=<AddmmBackward>)
'''

9.3 调用反向传播

然后把所有参数梯度缓存器置零,用随机的梯度来反向传播

net.zero_grad()
out.backward(torch.randn(1, 10))

至此,完成了定义一个神经网络,处理出入以及调用反向传播。

下面还需要处理:

  • 计算损失值
  • 更新网络中的权重

9.4 计算损失值

损失函数需要一对输入,即模型的输出和真实目标,然后返回一个值表示估计值与真实目标的距离。

在nn包中定义了多种损失函数,比如一个简单的损失函数是nn.MSELoss,这是个计算均方误差的损失函数。

例如:

output = net(input)
target = torch.randn(10)
target = target.view(1, -1)
criterion = nn.MSELoss()

loss = criterion(output, target)
print(loss)

'''
tensor(1.3389, grad_fn=<MseLossBackWard>)
'''

9.5 将损失添加到反向传播

现在需要跟随损失到反向传播路径,可以使用它的.grad_fn属性,其计算图为:

input -> conv2d -> relu -> maxpool2d -> conv2d -> relu -> maxpool2d -> view -> linear -> relu -> linear -> relu -> linear -> MSELoss -> loss

所以,当调用loss.backward(),整个图都会微分,而且所有的在图中的requires_grad=True的张量将会让他们的grad张量累计梯度。

为了实现反向传播损失,所有需要做的事仅仅是使用loss.backward()。需要清空现存的梯度,不然将会和现存的梯度累计到一起。

net.zero_grad() # 将现存梯度清零
loss.backward() # 反向传播

9.6 更新参数

最简单的更新规则就是随机梯度下降。

weight = weight - learning_rate * gradient

torch.optim包中实现了多种更新规则函数,例如SGD,Nesterov-SGD,Adam,RMSProp等。

optimizer = optim.SGD(net.parameters(), lr=0.01) # 创建优化器
optimizer = zero_grad() # 清零梯度系数
output = net(input) # 处理输入
loss = criterion(output, target) # 计算损失
loss.backward() # 反向传播
optimizer.step() # 更新参数

10. CNN

11. RNN

11.1 模型的参数维度

单层RNN

对于单层的RNN,也就是每个时刻tt的输入xtx_t和上一时刻t1t-1的隐层状态ht1h_{t-1}经过一个线性层xt@wxh+ht1@whhx_t@w_{xh}+h_{t-1}@w_{hh}处理得到下一个时刻的隐层状态hth_t,这种单层的RNN的参数如下图所示:

输入xtx_t的维度为二维[batch,feature len][batch, feature\space len],batch和词向量维度feature len,与一个权重矩阵wxhTw_{xh}^T做矩阵运算,权重矩阵wxhw_{xh}的维度为二维[hidden len,featrue len][hidden\space len, featrue\space len],hidden len表示隐层向量的维度,矩阵运算完的维度为[batch,hidden len][batch, hidden\space len]

另一个输入是上一时刻的隐层状态ht1h_{t-1},它的维度是[batch,hidden len][batch, hidden\space len],与权重矩阵whhTw_{hh}^T做矩阵运算,矩阵whhTw_{hh}^T的维度是[hidden len,hidden len][hidden\space len,hidden\space len],运算结果的维度是[batch,hidden len][batch, hidden\space len]

将两个结果进行相加得到要输入到下一时刻的隐层状态hth_t

多层RNN

对于多层的RNN,包含了多个线性层,如下图所示是一个二层的RNN:

每一层线性层将上一层的输出和上一时刻的隐层状态作为输入,以二层为例,第一层以嵌入向量和上一时刻隐层状态作为输入,第二层以第一层的输出和上一时刻的隐层状态作为输入。其维度为:

  • xt/feature [batch,feature len]x_t/feature\space [batch, feature\space len]
  • wxh1 [hidden len,feature len]w_{xh}^1\space [hidden\space len, feature \space len]
  • ht1 [batch,hidden len]h_t^1\space [batch, hidden \space len]
  • whh1 [hidden len,hidden len]w_{hh}^1\space[hidden\space len ,hidden\space len]
  • wxh2 [batch,hidden len]w_{xh}^2\space [batch, hidden\space len]
  • whh2 [hidden len,hidden len]w_{hh}^2\space [hidden\space len,hidden\space len]

一个句子有多少个单词,代表RNN进行多少次时间节点的运算

torch.nn.RNN

而在pytorch中使用RNN的API如下:

torch.nn.RNN(input_size, hidden_size, num_layers)

# - input_size 表示嵌入向量的维度,例如100
# - hidden_size 表示隐层状态向量的维度,例如20
# - num_layers 表示隐层层数/线性层层数

\

通过此API创建一个RNN网络模型,然后模型的输入和刚才图中的xtx_t不一样,xtx_t是每个时间状态下的输入,而在pytorch中RNN模型的输入是不需要单步处理每个时间状态的输入的,只需要将整个输入数据feed到模型中,模型会自动处理每个时间状态的输入。

# 模型的输入x_input, h_0; 输出h_t,out

# x_input:[seq len, batch, feature len]
# - seq len 表示每个句子的单词数
# - batch 表示多少个句子
# - feature len 表示每个单词的嵌入维度
# h_0:[layer, batch, hidden len] h_0可以不给
# - layer RNN线性层数
# - batch 
# - hidden len 表示隐层向量维度

# h_t的维度和h_0一样,代表最后一个时间节点的隐层状态
# out:[seq len, batch, hidden len]输出每一个时间节点的最后一层的隐层状态

使用示例:

rnn = torch.nn.RNN(input_size=100, hidden_size=20, num_layers=2, batch_first=False)

x_input = torch.randn(5, 3, 100) # seq len, batch, feature len
h_0 = torch.randn(2, 3, 20) # layer len, batch, hidden len

h_n, out = rnn(x_input, h_0)

print(h_n.shape)  # torch.Size([5, 3, 20])
print(out.shape)  # torch.Size([2, 3, 20])

LSTM

torch.nn.LSTM(input_size, hidden_size, num_layers, bidirectional=False)

# - input_size: The number of expected features in the input x
# - hidden_size: The number of features in the hidden state h
# - bidirectional: 双向LSTM,默认为False

# 模型的输入
# x_input [seq len, batch, H_in] H_in=input_size
# h_0 [D*num_layers, batch, H_out] 双向D=2,单向D=1;H_out=hidden_size
# c_0 [D*num_layers, batch, H_cell] H_cell=hidden_size

# 模型的输出
# output [seq len, batch, D*H_out]
# h_n [D*num_layers, batch, H_out]
# c_n [D*num_layers, batch, H_cell]

# 示例
rnn = nn.LSTM(input_size=10, hidden_size=20, num_layers=2)
x_input = torch.randn(5, 3, 10) # seq len, batch, input_size
h_0 = torch.randn(2, 3, 20) # 1*num_layers, batch, hidden_size
c_0 = torch.randn(2, 3, 20) # 1*num_layers, batch, hidden_size
output, (h_n, c_n) = rnn(x_input, h_0, c_0)

GRU

torch.nn.GRU(input_size, hidden_size, num_layers, bidirectional=False)

# - input_size: The number of expected features in the input x
# - hidden_size: The number of features in the hidden state h
# - bidirectional: 双向GRU,默认为False

# 模型的输入
# x_input [seq len, batch, H_in] H_in=input_size
# h_0 [D*num_layers, batch, H_out] 双向D=2,单向D=1;H_out=hidden_size

# 模型的输出
# output [seq len, batch, D*H_out]
# h_n [D*num_layers, batch, H_out]

# 示例
rnn = nn.GRU(input_size=10, hidden_size=20, num_layers=2)
x_input = torch.randn(5, 3, 10) # seq len, batch, input_size
h_0 = torch.randn(2, 3, 20) # 1*num_layers, batch, hidden_size
output, h_n = rnn(x_input, h_0)

12. 词嵌入

12.1 torch.nn.Embedding

在torch中使用嵌入模型非常简单,是需要使用torch.nn.Embedding(num_embeddings, embedding_dim)

  • num_embeddings [int]– size of the dictionary of embeddings
  • embedding_dim [int] – the size of each embedding vector
  • padding_idx [int] - 索引为padding_idx的词向量全部为0,把padding_idx设置为填充的值,如padding_idx=3,训练过程中索引为3的将始终设置为0,不进行参数更新,也就是说,为了便于模型训练需统一句子长度,对那些句子长度不够的句子进行填充,比如用值3进行填充,当用nn.Embedding()进行词向量嵌入时,对应的索引为3的向量将变为全为0的向量。这样就减少了填充值对模型训练的影响。例如:句子[2, 4, 5]和[1, 2, 7, 8, 9],两个句子不一样长,那么在输入到模型的时候就可以转换为[2, 4, 5, 3, 3], [1, 2, 7, 8, 9]。

Embedding创建一个大的矩阵,矩阵的每一行代表一个单词,默认是随机初始化的。

# 整个字典一共有5个单词,每个单词向量的维度是20
embeds = torch.nn.Embedding(num_embeddings=5, embedding_dim=20)

# 读取一个词向量,output = embeds(input)
# - input: IntTensor或LongTensor的任意shape
# - output: (*, H),*代表input的shape,H=embeding_dim
input = torch.IntTensor([[0, 1, 2], [3, 4, 1]])  # torch.Size([2, 3])

output = embeds(input)
print(output.shape)  # torch.Size([2, 3, 20])

一般在NLP中创建Embedding字典放在网络模型之前,作为预处理,这些embedding默认是随机初始化的,但在训练的过程中会进行训练。

除此之外,我们也可以导入已经训练好的词向量,但是需要设置训练过程中不更新:

embeds.weight = torch.nn.Parameter(emb)
# 固定embedding
embeds.weight.requires_grad = False

13. Attention

14. 其他常见操作

14.1 扁平化操作

class Flatten(nn.Module):
    def __init__(self):
        super(Flatten, self).__init__()
        
    def forward(self, input):
        return input.view(input.size(0), -1)
    

14.2 torch.manual_seed()

在一个实验中,如果需要使用到随机数,由于每次实验都需要生成数据,设置随机种子是为了确保每次生成固定的随机数(生成的随机数是一样的),这就使得每次实验结果显示一致了,有利于实验的比较和改进。使得每次运行该 python文件时生成的随机数相同。

用比较学术的话来讲,torch.manual_seed()为 CPU/GPU 中设置种子,生成随机数。

示例:

t.manual_seed(1000)

def get_fake_data(batch_size=8):
    '''产生随机数据:y= x*2+3 , 加上一些噪声'''
    x = t.rand(batch_size,1)*20
    y = x * 2 +( 1 + t.randn(batch_size,1)) * 3
    return x,y

x , y = get_fake_data()
plt.scatter(x.squeeze().numpy(),y.squeeze().numpy())

14.3 梯度弥散和梯度爆炸

梯度弥散

当网络的层数非常深时,会出现梯度非常小的问题,可能会接近于0,这样网络可能长时间得不到更新。

梯度爆炸

如果梯度在某个过程中计算的值很大,再经历一系列的乘方会出现梯度非常大的情况,因此需要再计算的过程中检查梯度是否大于自定义的阈值,将其限制在阈值范围内,这个操作称为Gradient Clipping:

loss = loss_fun(output, y)
model.zero_grad()
loss.backward()
for p in model.parameters():
    torch.nn.utils.clip_grad_norm_(p, 10)  # 限制在10的范围内
optimizer.step()