线性回归
主要内容包括:
1.线性回归的基本要素
2.线性回归模型从零开始的实现
3.线性回归模型使用pytorch的简洁实现
线性回归基本要素
模型:
线性回归模型
数据集:包括训练集/测试集,标签,特征
损失函数:
优化函数(随机梯度下降):
其中优化函数步骤如下:
(1)初始化模型参数,随机初始化。
(2)迭代多次,在负梯度方向移动参数来更新参数。
pytorch代码示例:
import torch
from torch import nn
import numpy as np
torch.manual_seed(1)
print(torch.__version__)
torch.set_default_tensor_type('torch.FloatTensor')
num_inputs = 2
num_examples = 1000
true_w = [2, -3.4]
true_b = 4.2
features = torch.tensor(np.random.normal(0, 1, (num_examples, num_inputs)), dtype=torch.float)
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.float)
import torch.utils.data as Data
batch_size = 10
# combine featues and labels of dataset
dataset = Data.TensorDataset(features, labels)
# put dataset into DataLoader
data_iter = Data.DataLoader(
dataset=dataset, # torch TensorDataset format
batch_size=batch_size, # mini batch size
shuffle=True, # whether shuffle the data or not
num_workers=2, # read data in multithreading
)
for X, y in data_iter:
print(X, '\n', y)
break
class LinearNet(nn.Module):
def __init__(self, n_feature):
super(LinearNet, self).__init__() # call father function to init
self.linear = nn.Linear(n_feature, 1) # function prototype: `torch.nn.Linear(in_features, out_features, bias=True)`
def forward(self, x):
y = self.linear(x)
return y
net = LinearNet(num_inputs)
print(net)
# ways to init a multilayer network
# method one
net = nn.Sequential(
nn.Linear(num_inputs, 1)
# other layers can be added here
)
# method two
net = nn.Sequential()
net.add_module('linear', nn.Linear(num_inputs, 1))
# net.add_module ......
# method three
from collections import OrderedDict
net = nn.Sequential(OrderedDict([
('linear', nn.Linear(num_inputs, 1))
# ......
]))
print(net)
print(net[0])
from torch.nn import init
init.normal_(net[0].weight, mean=0.0, std=0.01)
init.constant_(net[0].bias, val=0.0) # or you can use `net[0].bias.data.fill_(0)` to modify it directly
for param in net.parameters():
print(param)
loss = nn.MSELoss() # nn built-in squared loss function
# function prototype: `torch.nn.MSELoss(size_average=None, reduce=None, reduction='mean')
import torch.optim as optim
optimizer = optim.SGD(net.parameters(), lr=0.03) # built-in random gradient descent function
print(optimizer) # function prototype: `torch.optim.SGD(params, lr=, momentum=0, dampening=0, weight_decay=0, nesterov=False)
num_epochs = 3
for epoch in range(1, num_epochs + 1):
for X, y in data_iter:
output = net(X)
l = loss(output, y.view(-1, 1))
optimizer.zero_grad() # reset gradient, equal to net.zero_grad()
l.backward()
optimizer.step()
print('epoch %d, loss: %f' % (epoch, l.item()))
dense = net[0]
print(true_w, dense.weight.data)
print(true_b, dense.bias.data)
softmax和分类模型
主要内容:
1.softmax回归的基本概念
2.如何获取Fashion-MNIST数据集和读取数据
3.softmax回归模型的从零开始实现,实现一个对Fashion-MNIST训练集中的图像数据进行分类的模型
4.使用pytorch重新实现softmax回归模型
softmax基本概念
分类问题
一个简单的图像分类问题,输入图像的高和宽均为2像素,色彩为灰度。
图像中的4像素分别记为 𝑥1,𝑥2,𝑥3,𝑥4 。
假设真实标签为狗、猫或者鸡,这些标签对应的离散值为 𝑦1,𝑦2,𝑦3 。
我们通常使用离散的数值来表示类别,例如 𝑦1=1,𝑦2=2,𝑦3=3 。
权重矢量
𝑜1=𝑥1𝑤11+𝑥2𝑤21+𝑥3𝑤31+𝑥4𝑤41+𝑏1
𝑜2=𝑥1𝑤12+𝑥2𝑤22+𝑥3𝑤32+𝑥4𝑤42+𝑏2
𝑜3=𝑥1𝑤13+𝑥2𝑤23+𝑥3𝑤33+𝑥4𝑤43+𝑏3
神经网络图
下图用神经网络图描绘了上面的计算。softmax回归同线性回归一样,也是一个单层神经网络。由于每个输出 𝑜1,𝑜2,𝑜3 的计算都要依赖于所有的输入 𝑥1,𝑥2,𝑥3,𝑥4 ,softmax回归的输出层也是一个全连接层
输出问题
直接使用输出层的输出有两个问题:
1.一方面,由于输出层的输出值的范围不确定,我们难以直观上判断这些值的意义。例如,刚才举的例子中的输出值10表示“很置信”图像类别为猫,因为该输出值是其他两类的输出值的100倍。但如果 𝑜1=𝑜3=103 ,那么输出值10却又表示图像类别为猫的概率很低。
2.另一方面,由于真实标签是离散值,这些离散值与不确定范围的输出值之间的误差难以衡量。
计算效率:
单样本矢量计算表达式
为了提高计算效率,我们可以将单样本分类通过矢量计算来表达。在上面的图像分类问题中,假设softmax回归的权重和偏差参数分别为
𝑊=⎡⎣⎢⎢⎢⎢𝑤11𝑤21𝑤31𝑤41𝑤12𝑤22𝑤32𝑤42𝑤13𝑤23𝑤33𝑤43⎤⎦⎥⎥⎥⎥,𝑏=[𝑏1𝑏2𝑏3],
设高和宽分别为2个像素的图像样本 𝑖 的特征为
𝑥(𝑖)=[𝑥(𝑖)1𝑥(𝑖)2𝑥(𝑖)3𝑥(𝑖)4],
输出层的输出为
𝑜(𝑖)=[𝑜(𝑖)1𝑜(𝑖)2𝑜(𝑖)3],
预测为狗、猫或鸡的概率分布为
𝑦̂ (𝑖)=[𝑦̂ (𝑖)1𝑦̂ (𝑖)2𝑦̂ (𝑖)3].
softmax回归对样本 𝑖 分类的矢量计算表达式为
𝑜(𝑖)𝑦̂ (𝑖)=𝑥(𝑖)𝑊+𝑏,=softmax(𝑜(𝑖)).
小批量矢量计算表达式
为了进一步提升计算效率,我们通常对小批量数据做矢量计算。广义上讲,给定一个小批量样本,其批量大小为 𝑛 ,输入个数(特征数)为 𝑑 ,输出个数(类别数)为 𝑞 。设批量特征为 𝑋∈ℝ𝑛×𝑑 。假设softmax回归的权重和偏差参数分别为 𝑊∈ℝ𝑑×𝑞 和 𝑏∈ℝ1×𝑞 。softmax回归的矢量计算表达式为
其中的加法运算使用了广播机制, 𝑂,𝑌̂ ∈ℝ𝑛×𝑞 且这两个矩阵的第 𝑖 行分别为样本 𝑖 的输出 𝑜(𝑖) 和概率分布 𝑦̂ (𝑖)
交叉熵损失函数
平方损失估计函数:
衡量两个概率分布差异的测量函数:
假设训练数据集样本数为n,则函数定义为:
其中𝚯代表模型参数。同样地,如果每个样本只有一个标签,那么交叉熵损失可以简写成ℓ(𝚯)=−(1/𝑛)∑𝑛𝑖=1log𝑦̂ (𝑖)𝑦(𝑖)。从另一个角度来看,我们知道最小化ℓ(𝚯)等价于最大化exp(−𝑛ℓ(𝚯))=∏𝑛𝑖=1𝑦̂ (𝑖)𝑦(𝑖),即最小化交叉熵损失函数等价于最大化训练数据集所有标签类别的联合预测概率。
模型训练和预测
在训练好softmax回归模型后,给定任一样本特征,就可以预测每个输出类别的概率。通常,我们把预测概率最大的类别作为输出类别。如果它与真实类别(标签)一致,说明这次预测是正确的。在3.6节的实验中,我们将使用准确率(accuracy)来评价模型的表现。它等于正确预测数量与总预测数量之比。
pytorch代码实例:
# 加载各种包或者模块
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size)
num_inputs = 784
num_outputs = 10
class LinearNet(nn.Module):
def __init__(self, num_inputs, num_outputs):
super(LinearNet, self).__init__()
self.linear = nn.Linear(num_inputs, num_outputs)
def forward(self, x): # x 的形状: (batch, 1, 28, 28)
y = self.linear(x.view(x.shape[0], -1))
return y
# net = LinearNet(num_inputs, num_outputs)
class FlattenLayer(nn.Module):
def __init__(self):
super(FlattenLayer, self).__init__()
def forward(self, x): # x 的形状: (batch, *, *, ...)
return x.view(x.shape[0], -1)
from collections import OrderedDict
net = nn.Sequential(
# FlattenLayer(),
# LinearNet(num_inputs, num_outputs)
OrderedDict([
('flatten', FlattenLayer()),
('linear', nn.Linear(num_inputs, num_outputs))]) # 或者写成我们自己定义的 LinearNet(num_inputs, num_outputs) 也可以
)
init.normal_(net.linear.weight, mean=0, std=0.01)
init.constant_(net.linear.bias, val=0)
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.1)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
多层感知机
1.多层感知机的基本知识
2.使用多层感知机图像分类的从零开始的实现
3.使用pytorch的简洁实现
多层感知机的基本知识
隐藏层
它含有一个隐藏层,该层中有5个隐藏单元。
表达公式
其中𝑊ℎ∈ℝ𝑑×ℎ ,𝑏ℎ∈ℝ1×ℎ ,𝑊𝑜∈ℝℎ×𝑞 ,𝑏𝑜∈ℝ1×𝑞
激活函数
常用激活函数:
ReLU函数:ReLU(𝑥)=max(𝑥,0).
Sigmoid函数:sigmoid(𝑥)=1/(1+exp(−𝑥))
依据链式法则,sigmoid函数的导数
sigmoid′(𝑥)=sigmoid(𝑥)(1−sigmoid(𝑥))
tanh函数:tanh(𝑥)=1−exp(−2𝑥)/(1+exp(−2𝑥))
依据链式法则,tanh函数的导数
tanh′(𝑥)=1−tanh2(𝑥).
多层感知机
多层感知机就是含有至少一个隐藏层的由全连接层组成的神经网络,且每个隐藏层的输出通过激活函数进行变换。多层感知机的层数和各隐藏层中隐藏单元个数都是超参数。以单隐藏层为例并沿用本节之前定义的符号,多层感知机按以下方式计算输出:
其中 𝜙 表示激活函数。
pytorch代码实例:
import torch
from torch import nn
from torch.nn import init
import numpy as np
import sys
sys.path.append("/home/kesci/input")
import d2lzh1981 as d2l
num_inputs, num_outputs, num_hiddens = 784, 10, 256
net = nn.Sequential(
d2l.FlattenLayer(),
nn.Linear(num_inputs, num_hiddens),
nn.ReLU(),
nn.Linear(num_hiddens, num_outputs),
)
for params in net.parameters():
init.normal_(params, mean=0, std=0.01)
batch_size = 256
train_iter, test_iter = d2l.load_data_fashion_mnist(batch_size,root='/home/kesci/input/FashionMNIST2065')
loss = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.5)
num_epochs = 5
d2l.train_ch3(net, train_iter, test_iter, loss, num_epochs, batch_size, None, None, optimizer)
文本预处理
文本是一类序列数据,一篇文章可以看作是字符或单词的序列,本节将介绍文本数据的常见预处理步骤,预处理通常包括四个步骤:
1.读入文本
2.分词
3.建立字典,将每个词映射到一个唯一的索引(index)
4.将文本从词的序列转换为索引的序列,方便输入模型
其中在分词的时候就是将一个句子划分为若干词(token),转换为一个词的序列。
在建立字典时,我们需要将字符串转换为数字。因此我们需先构建一个字典,将每个词映射到一个唯一的索引编号。
代码实例:
import collections
import re
def read_time_machine():
with open('/home/kesci/input/timemachine7163/timemachine.txt', 'r') as f:
lines = [re.sub('[^a-z]+', ' ', line.strip().lower()) for line in f]
return lines
lines = read_time_machine()
print('# sentences %d' % len(lines))
def tokenize(sentences, token='word'):
"""Split sentences into word or char tokens"""
if token == 'word':
return [sentence.split(' ') for sentence in sentences]
elif token == 'char':
return [list(sentence) for sentence in sentences]
else:
print('ERROR: unkown token type '+token)
tokens = tokenize(lines)
tokens[0:2]
class Vocab(object):
def __init__(self, tokens, min_freq=0, use_special_tokens=False):
counter = count_corpus(tokens) # :
self.token_freqs = list(counter.items())
self.idx_to_token = []
if use_special_tokens:
# padding, begin of sentence, end of sentence, unknown
self.pad, self.bos, self.eos, self.unk = (0, 1, 2, 3)
self.idx_to_token += ['', '', '', '']
else:
self.unk = 0
self.idx_to_token += ['']
self.idx_to_token += [token for token, freq in self.token_freqs
if freq >= min_freq and token not in self.idx_to_token]
self.token_to_idx = dict()
for idx, token in enumerate(self.idx_to_token):
self.token_to_idx[token] = idx
def __len__(self):
return len(self.idx_to_token)
def __getitem__(self, tokens):
if not isinstance(tokens, (list, tuple)):
return self.token_to_idx.get(tokens, self.unk)
return [self.__getitem__(token) for token in tokens]
def to_tokens(self, indices):
if not isinstance(indices, (list, tuple)):
return self.idx_to_token[indices]
return [self.idx_to_token[index] for index in indices]
def count_corpus(sentences):
tokens = [tk for st in sentences for tk in st]
return collections.Counter(tokens) # 返回一个字典,记录每个词的出现次数
vocab = Vocab(tokens)
print(list(vocab.token_to_idx.items())[0:10])
for i in range(8,10):
print('words:', tokens[I])
print('indices:', vocab[tokens[I]])
#用现有工具进行分词
text = "Mr. Chen doesn't agree with my suggestion."
import spacy
nlp = spacy.load('en_core_web_sm')
doc = nlp(text)
print([token.text for token in doc])
from nltk.tokenize import word_tokenize
from nltk import data
data.path.append('/home/kesci/input/nltk_data3784/nltk_data')
print(word_tokenize(text))
语言模型
语言模型
一段自然语言文本可以看作是一个离散时间序列,给定一个长度为 𝑇 的词的序列 𝑤1,𝑤2,…,𝑤𝑇 ,语言模型的目标就是评估该序列是否合理,即计算该序列的概率:𝑃(𝑤1,𝑤2,…,𝑤𝑇).
假设序列 𝑤1,𝑤2,…,𝑤𝑇 中的每个词是依次生成的,我们有
𝑃(𝑤1,𝑤2,…,𝑤𝑇)=∏𝑡=1𝑇𝑃(𝑤𝑡∣𝑤1,…,𝑤𝑡−1)=𝑃(𝑤1)𝑃(𝑤2∣𝑤1)⋯𝑃(𝑤𝑇∣𝑤1𝑤2⋯𝑤𝑇−1)
语言模型的参数就是词的概率以及给定前几个词情况下的条件概率。例如𝑤1的概率可以计算为:𝑃̂ (𝑤1)=𝑛(𝑤1)/𝑛
其中 𝑛(𝑤1) 为语料库中以 𝑤1 作为第一个词的文本的数量, 𝑛 为语料库中文本的总数量。
类似的,给定 𝑤1 情况下, 𝑤2 的条件概率可以计算为:
𝑃̂ (𝑤2∣𝑤1)=𝑛(𝑤1,𝑤2)/𝑛(𝑤1)
其中 𝑛(𝑤1,𝑤2) 为语料库中以 𝑤1 作为第一个词, 𝑤2 作为第二个词的文本的数量。
n元语法
n元语法通过马尔可夫假设简化模型,基于n-1阶马尔可夫链,可将语言模型改为:
𝑃(𝑤1,𝑤2,…,𝑤𝑇)=∏𝑡=1𝑇𝑃(𝑤𝑡∣𝑤𝑡−(𝑛−1),…,𝑤𝑡−1)
当n分别为1,2,3时,概率分别为:
𝑃(𝑤1,𝑤2,𝑤3,𝑤4)=𝑃(𝑤1)𝑃(𝑤2)𝑃(𝑤3)𝑃(𝑤4),
𝑃(𝑤1,𝑤2,𝑤3,𝑤4)=𝑃(𝑤1)𝑃(𝑤2∣𝑤1)𝑃(𝑤3∣𝑤2)𝑃(𝑤4∣𝑤3),
𝑃(𝑤1,𝑤2,𝑤3,𝑤4)=𝑃(𝑤1)𝑃(𝑤2∣𝑤1)𝑃(𝑤3∣𝑤1,𝑤2)𝑃(𝑤4∣𝑤2,𝑤3).
n元语法的缺陷:
1.参数空间过大
2.数据稀疏
时序数据的采样
时序数据的一个样本通常包含连续的字符。假设时间步数为5,样本序列为5个字符,即“想”“要”“有”“直”“升”。该样本的标签序列为这些字符分别在训练集中的下一个字符,即“要”“有”“直”“升”“机”,即 𝑋 =“想要有直升”, 𝑌 =“要有直升机”。
现在我们考虑序列“想要有直升机,想要和你飞到宇宙去”,如果时间步数为5,有以下可能的样本和标签:
𝑋 :“想要有直升”, 𝑌 :“要有直升机”
𝑋 :“要有直升机”, 𝑌 :“有直升机,”
𝑋 :“有直升机,”, 𝑌 :“直升机,想”
...
𝑋 :“要和你飞到”, 𝑌 :“和你飞到宇”
𝑋 :“和你飞到宇”, 𝑌 :“你飞到宇宙”
𝑋 :“你飞到宇宙”, 𝑌 :“飞到宇宙去”
可以看到,如果序列的长度为 𝑇 ,时间步数为 𝑛 ,那么一共有 𝑇−𝑛 个合法的样本,但是这些样本有大量的重合,我们通常采用更加高效的采样方式。我们有两种方式对时序数据进行采样,分别是随机采样和相邻采样。
随机采样
每次从数据里随机采样一个小批量。其中批量大小batch_size是每个小批量的样本数,num_steps是每个样本所包含的时间步数。 在随机采样中,每个样本是原始序列上任意截取的一段序列,相邻的两个随机小批量在原始序列上的位置不一定相毗邻。
相邻采样
在相邻采样中,相邻的两个随机小批量在原始序列上的位置相毗邻。
代码实例:
with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
print(len(corpus_chars))
print(corpus_chars[: 40])
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[: 10000]
idx_to_char = list(set(corpus_chars)) # 去重,得到索引到字符的映射
char_to_idx = {char: i for i, char in enumerate(idx_to_char)} # 字符到索引的映射
vocab_size = len(char_to_idx)
print(vocab_size)
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)
def load_data_jay_lyrics():
with open('/home/kesci/input/jaychou_lyrics4703/jaychou_lyrics.txt') as f:
corpus_chars = f.read()
corpus_chars = corpus_chars.replace('\n', ' ').replace('\r', ' ')
corpus_chars = corpus_chars[0:10000]
idx_to_char = list(set(corpus_chars))
char_to_idx = dict([(char, i) for i, char in enumerate(idx_to_char)])
vocab_size = len(char_to_idx)
corpus_indices = [char_to_idx[char] for char in corpus_chars]
return corpus_indices, char_to_idx, idx_to_char, vocab_size
import torch
import random
def data_iter_random(corpus_indices, batch_size, num_steps, device=None):
# 减1是因为对于长度为n的序列,X最多只有包含其中的前n - 1个字符
num_examples = (len(corpus_indices) - 1) // num_steps # 下取整,得到不重叠情况下的样本个数
example_indices = [i * num_steps for i in range(num_examples)] # 每个样本的第一个字符在corpus_indices中的下标
random.shuffle(example_indices)
def _data(i):
# 返回从i开始的长为num_steps的序列
return corpus_indices[i: i + num_steps]
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
for i in range(0, num_examples, batch_size):
# 每次选出batch_size个随机样本
batch_indices = example_indices[i: i + batch_size] # 当前batch的各个样本的首字符的下标
X = [_data(j) for j in batch_indices]
Y = [_data(j + 1) for j in batch_indices]
yield torch.tensor(X, device=device), torch.tensor(Y, device=device)
my_seq = list(range(30))
for X, Y in data_iter_random(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
def data_iter_consecutive(corpus_indices, batch_size, num_steps, device=None):
if device is None:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
corpus_len = len(corpus_indices) // batch_size * batch_size # 保留下来的序列的长度
corpus_indices = corpus_indices[: corpus_len] # 仅保留前corpus_len个字符
indices = torch.tensor(corpus_indices, device=device)
indices = indices.view(batch_size, -1) # resize成(batch_size, )
batch_num = (indices.shape[1] - 1) // num_steps
for i in range(batch_num):
i = i * num_steps
X = indices[:, i: i + num_steps]
Y = indices[:, i + 1: i + num_steps + 1]
yield X, Y
for X, Y in data_iter_consecutive(my_seq, batch_size=2, num_steps=6):
print('X: ', X, '\nY:', Y, '\n')
循环神经网络
基于当前的输入与过去的输入序列,预测序列的下一个字符。循环神经网络引入一个隐藏变量 𝐻 ,用 𝐻𝑡 表示 𝐻 在时间步 𝑡 的值。 𝐻𝑡 的计算基于 𝑋𝑡 和 𝐻𝑡−1 ,可以认为 𝐻𝑡 记录了到当前字符为止的序列信息,利用 𝐻𝑡 对序列的下一个字符进行预测。
循环神经网络的构造
我们先看循环神经网络的具体构造。假设 𝑋𝑡∈ℝ𝑛×𝑑 是时间步 𝑡 的小批量输入, 𝐻𝑡∈ℝ𝑛×ℎ 是该时间步的隐藏变量,则:
𝐻𝑡=𝜙(𝑋𝑡𝑊𝑥ℎ+𝐻𝑡−1𝑊ℎℎ+𝑏ℎ).
其中, 𝑊𝑥ℎ∈ℝ𝑑×ℎ , 𝑊ℎℎ∈ℝℎ×ℎ , 𝑏ℎ∈ℝ1×ℎ , 𝜙 函数是非线性激活函数。由于引入了 𝐻𝑡−1𝑊ℎℎ , 𝐻𝑡 能够捕捉截至当前时间步的序列的历史信息,就像是神经网络当前时间步的状态或记忆一样。由于 𝐻𝑡 的计算基于 𝐻𝑡−1 ,上式的计算是循环的,使用循环计算的网络即循环神经网络(recurrent neural network)。
在时间步 𝑡 ,输出层的输出为:
𝑂𝑡=𝐻𝑡𝑊ℎ𝑞+𝑏𝑞.
其中 𝑊ℎ𝑞∈ℝℎ×𝑞 , 𝑏𝑞∈ℝ1×𝑞 。
one-hot向量
假设词典大小是 𝑁 ,每次字符对应一个从 0 到 𝑁−1 的唯一的索引,则该字符的向量是一个长度为 𝑁 的向量,若字符的索引是 𝑖 ,则该向量的第 𝑖 个位置为 1 ,其他位置为 0 。
裁剪梯度
循环神经网络中较容易出现梯度衰减或梯度爆炸,这会导致网络几乎无法训练。裁剪梯度(clip gradient)是一种应对梯度爆炸的方法。假设我们把所有模型参数的梯度拼接成一个向量 𝑔 ,并设裁剪的阈值是 𝜃 。裁剪后的梯度 min(𝜃‖𝑔‖,1)𝑔 的 𝐿2 范数不超过 𝜃 。
困惑度
困惑度(perplexity):评价语言模型的好坏。困惑度是对交叉熵损失函数做指数运算后得到的值。特别地,
最佳情况下,模型总是把标签类别的概率预测为1,此时困惑度为1;
最坏情况下,模型总是把标签类别的概率预测为0,此时困惑度为正无穷;
基线情况下,模型总是预测所有类别的概率都相同,此时困惑度为类别个数。
显然,任何一个有效模型的困惑度必须小于类别个数。
pytorch代码实例:
import torch
import torch.nn as nn
import time
import math
import sys
sys.path.append("/home/kesci/input")
import d2l_jay9460 as d2l
(corpus_indices, char_to_idx, idx_to_char, vocab_size) = d2l.load_data_jay_lyrics()
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps, batch_size = 35, 2
X = torch.rand(num_steps, batch_size, vocab_size)
state = None
Y, state_new = rnn_layer(X, state)
print(Y.shape, state_new.shape)
rnn_layer = nn.RNN(input_size=vocab_size, hidden_size=num_hiddens)
num_steps, batch_size = 35, 2
X = torch.rand(num_steps, batch_size, vocab_size)
state = None
Y, state_new = rnn_layer(X, state)
print(Y.shape, state_new.shape)
def predict_rnn_pytorch(prefix, num_chars, model, vocab_size, device, idx_to_char,
char_to_idx):
state = None
output = [char_to_idx[prefix[0]]] # output记录prefix加上预测的num_chars个字符
for t in range(num_chars + len(prefix) - 1):
X = torch.tensor([output[-1]], device=device).view(1, 1)
(Y, state) = model(X, state) # 前向计算不需要传入模型参数
if t < len(prefix) - 1:
output.append(char_to_idx[prefix[t + 1]])
else:
output.append(Y.argmax(dim=1).item())
return ''.join([idx_to_char[i] for i in output])
model = RNNModel(rnn_layer, vocab_size).to(device)
predict_rnn_pytorch('分开', 10, model, vocab_size, device, idx_to_char, char_to_idx)
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_period, pred_len, prefixes):
loss = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=lr)
model.to(device)
for epoch in range(num_epochs):
l_sum, n, start = 0.0, 0, time.time()
data_iter = d2l.data_iter_consecutive(corpus_indices, batch_size, num_steps, device) # 相邻采样
state = None
for X, Y in data_iter:
if state is not None:
# 使用detach函数从计算图分离隐藏状态
if isinstance (state, tuple): # LSTM, state:(h, c)
state[0].detach_()
state[1].detach_()
else:
state.detach_()
(output, state) = model(X, state) # output.shape: (num_steps * batch_size, vocab_size)
y = torch.flatten(Y.T)
l = loss(output, y.long())
optimizer.zero_grad()
l.backward()
grad_clipping(model.parameters(), clipping_theta, device)
optimizer.step()
l_sum += l.item() * y.shape[0]
n += y.shape[0]
if (epoch + 1) % pred_period == 0:
print('epoch %d, perplexity %f, time %.2f sec' % (
epoch + 1, math.exp(l_sum / n), 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_period, pred_len, prefixes = 50, 50, ['分开', '不分开']
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_period, pred_len, prefixes)