第五课目录
语言模型
语言模型简介
简单来说,语言模型用于衡量一句话是否合理,词顺序和选择哪些词也有要求,给出一句话,判断这句话(词的组合)的出现概率:
P
(
W
)
=
P
(
w
1
w
2
.
.
.
w
n
)
P(W)=P(w_{1}w_{2}...w_{n})
P(W)=P(w1w2...wn)
根据条件概率的计算公式:
P
(
A
B
)
=
P
(
A
)
P
(
B
∣
A
)
P(AB)=P(A)P(B|A)
P(AB)=P(A)P(B∣A),可以将词组概率表示为:
P
(
w
1
w
2
.
.
.
w
n
)
=
P
(
w
1
)
P
(
w
2
∣
w
1
)
P
(
w
3
∣
w
1
w
2
)
.
.
.
P
(
w
n
∣
w
1
.
.
.
w
n
−
1
)
P(w_{1}w_{2}...w_{n})=P(w_{1})P(w_{2}|w_{1})P(w_{3}|w_{1}w_{2})...P(w_{n}|w_{1}...w_{n-1})
P(w1w2...wn)=P(w1)P(w2∣w1)P(w3∣w1w2)...P(wn∣w1...wn−1)
即:
P
(
w
1
w
2
.
.
.
w
n
)
=
∏
i
n
P
(
w
i
∣
w
1
w
2
.
.
.
w
i
−
1
)
P(w_{1}w_{2}...w_{n})=\prod_{i}^{n}P(w_{i}|w_{1}w_{2}...w_{i-1})
P(w1w2...wn)=i∏nP(wi∣w1w2...wi−1)
在早期的NLP中,还会遵循Markov假设:每个单词只与其前m个单词有关:
如果m=1:
P
(
t
h
e
∣
i
t
s
.
w
a
t
e
r
.
i
s
.
s
o
.
t
r
a
n
s
p
a
r
e
n
t
.
t
h
a
t
)
≈
P
(
t
h
e
∣
t
h
a
t
)
P(the|its.water.is.so.transparent.that)\approx P(the|that)
P(the∣its.water.is.so.transparent.that)≈P(the∣that);
如果m=2:
P
(
t
h
e
∣
i
t
s
.
w
a
t
e
r
.
i
s
.
s
o
.
t
r
a
n
s
p
a
r
e
n
t
.
t
h
a
t
)
≈
P
(
t
h
e
∣
t
r
a
n
s
p
a
r
e
n
t
.
t
h
a
t
)
P(the|its.water.is.so.transparent.that)\approx P(the|transparent.that)
P(the∣its.water.is.so.transparent.that)≈P(the∣transparent.that);
所以词组概率可化简为:
P
(
w
1
w
2
.
.
.
w
n
)
≈
∏
i
n
P
(
w
i
∣
w
i
−
1
−
m
.
.
.
w
i
−
1
)
P(w_{1}w_{2}...w_{n})\approx \prod_{i}^{n}P(w_{i}|w_{i-1-m}...w_{i-1})
P(w1w2...wn)≈i∏nP(wi∣wi−1−m...wi−1)
评价语言模型
语言模型的评价一般使用perplexity:
P
e
r
[
P
(
W
)
]
=
P
(
W
)
−
1
n
,
W
=
w
1
.
.
.
w
n
Per[P(W)]=P(W)^{-\frac{1}{n}},W=w_{1}...w_{n}
Per[P(W)]=P(W)−n1,W=w1...wn
所以:
P
e
r
[
P
(
W
)
]
=
∏
i
n
1
P
(
w
i
∣
w
1
.
.
.
w
i
−
1
)
n
Per[P(W)]=\sqrt[n]{\prod_{i}^{n}\frac{1}{P(w_{i}|w_{1}...w_{i-1})}}
Per[P(W)]=ni∏nP(wi∣w1...wi−1)1
perplexity越高,模型效果越差,perplexity越低,模型效果越好
循环神经网络
循环神经网络简介
神经网络具有强劲的捕捉分布能力,在语言模型上,越来越广泛使用神经网络进行预测词的出现概率;
循环神经网络(Recurrent Neural Network)结构如下:

给定一组词
x
1
.
.
.
x
t
−
1
x
t
x
t
+
1
.
.
.
x
T
x_{1}...x_{t-1}x_{t}x_{t+1}...x_{T}
x1...xt−1xtxt+1...xT,其中
x
i
x_{i}
xi代表词组中第
i
i
i个词对应的词向量;
h
t
h_{t}
ht代表hidden state,可以理解为存储了历史信息:
h
t
=
s
i
g
m
o
i
d
(
W
h
h
h
t
−
1
+
W
h
x
x
t
−
1
)
h_{t}=sigmoid(W_{hh}h_{t-1}+W_{hx}x_{t-1})
ht=sigmoid(Whhht−1+Whxxt−1)
y
t
y_{t}
yt是输出信息:
y
t
=
s
o
f
t
m
a
x
(
W
s
h
t
)
y_{t}=softmax(W_{s}h_{t})
yt=softmax(Wsht)
假设词汇表有3万个单词,one-hot编码并与embedding相乘,得到词向量为100维,通过RNN输出hidden state为500维,那么
W
s
W_{s}
Ws应该是[30000,500]的张量,因为输出需要映射回词汇表,才能知道预测出的词是什么;
假设目前在预测第
t
t
t个词,而且已经知道第
t
t
t个词应该是词汇表(假设词汇表一共
V
V
V个词)中的第
j
j
j个词
v
j
v_{j}
vj,则有当前词概率:
P
(
x
t
=
v
j
∣
x
1
.
.
.
x
t
−
1
)
=
y
t
,
j
^
=
y
t
P(x_{t}=v_{j}|x_{1}...x_{t-1})=\widehat{y_{t,j}}=y_{t}
P(xt=vj∣x1...xt−1)=yt,j
=yt
y
t
,
j
^
\widehat{y_{t,j}}
yt,j
的值等于RNN的输出
y
t
y_{t}
yt,只不过是为了将网络输出记作
y
t
,
j
^
\widehat{y_{t,j}}
yt,j
,而第
t
t
t个词的正确one-hot记作
y
t
,
j
y_{t,j}
yt,j;
注意到,在RNN刚开始计算时,需要一个初始的hidden state向量,一般
h
0
h_{0}
h0是一个全零向量;
text的词从1到n,则输出目标应该是从2到n+1;
计算损失可以使用CrossEntropyLoss,对于第
t
t
t个词有:
J
(
t
)
(
θ
)
=
−
∑
j
=
1
V
y
t
,
j
l
o
g
(
y
t
,
j
^
)
J^{(t)}(\theta)=-\sum_{j=1}^{V}y_{t,j}log(\widehat{y_{t,j}})
J(t)(θ)=−j=1∑Vyt,jlog(yt,j
)
θ
\theta
θ是网络的参数,对于一个句子,将损失相加得到:
J
=
1
T
∑
t
=
1
T
J
(
t
)
(
θ
)
=
−
1
T
∑
t
=
1
T
∑
j
=
1
V
y
t
,
j
l
o
g
(
y
t
,
j
^
)
J=\frac{1}{T}\sum_{t=1}^{T}J^{(t)}(\theta)=-\frac{1}{T}\sum_{t=1}^{T}\sum_{j=1}^{V}y_{t,j}log(\widehat{y_{t,j}})
J=T1t=1∑TJ(t)(θ)=−T1t=1∑Tj=1∑Vyt,jlog(yt,j
)
回想perplexity,发现:
p
e
r
p
l
e
x
i
t
y
=
2
J
perplexity=2^{J}
perplexity=2J
循环神经网络的反向传播
在RNN中,hidden state是随着时序连续传递的,因此反向传播计算梯度还需要考虑时序
t
t
t,从而得名BPTT:Back propogation through time,其本质还是反向传播;

对于以上网络,假设已经计算出每个
y
t
y_{t}
yt对应的损失
E
t
E_{t}
Et,现在只想求一下
E
3
E_{3}
E3关于
W
W
W的梯度,应写成:
∂
E
3
∂
W
=
∂
E
3
∂
y
3
∂
y
3
∂
s
3
(
∂
s
3
∂
s
2
∂
s
2
∂
s
1
∂
s
1
∂
s
0
)
∂
s
0
∂
W
\frac{\partial E_{3}}{\partial W}=\frac{\partial E_{3}}{\partial y_{3}}\frac{\partial y_{3}}{\partial s_{3}}(\frac{\partial s_{3}}{\partial s_{2}}\frac{\partial s_{2}}{\partial s_{1}}\frac{\partial s_{1}}{\partial s_{0}})\frac{\partial s_{0}}{\partial W}
∂W∂E3=∂y3∂E3∂s3∂y3(∂s2∂s3∂s1∂s2∂s0∂s1)∂W∂s0
+
∂
E
3
∂
y
3
∂
y
3
∂
s
3
(
∂
s
3
∂
s
2
∂
s
2
∂
s
1
)
∂
s
1
∂
W
+\frac{\partial E_{3}}{\partial y_{3}}\frac{\partial y_{3}}{\partial s_{3}}(\frac{\partial s_{3}}{\partial s_{2}}\frac{\partial s_{2}}{\partial s_{1}})\frac{\partial s_{1}}{\partial W}
+∂y3∂E3∂s3∂y3(∂s2∂s3∂s1∂s2)∂W∂s1
+
∂
E
3
∂
y
3
∂
y
3
∂
s
3
(
∂
s
3
∂
s
2
)
∂
s
2
∂
W
+\frac{\partial E_{3}}{\partial y_{3}}\frac{\partial y_{3}}{\partial s_{3}}(\frac{\partial s_{3}}{\partial s_{2}})\frac{\partial s_{2}}{\partial W}
+∂y3∂E3∂s3∂y3(∂s2∂s3)∂W∂s2
+
∂
E
3
∂
y
3
∂
y
3
∂
s
3
∂
s
3
∂
W
+\frac{\partial E_{3}}{\partial y_{3}}\frac{\partial y_{3}}{\partial s_{3}}\frac{\partial s_{3}}{\partial W}
+∂y3∂E3∂s3∂y3∂W∂s3
简写为:
∂
E
3
∂
W
=
∑
k
=
0
3
∂
E
3
∂
y
3
∂
y
3
∂
s
3
(
∏
j
=
k
+
1
3
∂
s
j
∂
s
j
−
1
)
∂
s
k
∂
W
\frac{\partial E_{3}}{\partial W}=\sum_{k=0}^{3}\frac{\partial E_{3}}{\partial y_{3}}\frac{\partial y_{3}}{\partial s_{3}}(\prod_{j=k+1}^{3}\frac{\partial s_{j}}{\partial s_{j-1}})\frac{\partial s_{k}}{\partial W}
∂W∂E3=k=0∑3∂y3∂E3∂s3∂y3(j=k+1∏3∂sj−1∂sj)∂W∂sk
LSTM和GRU
显然,训练RNN难度很大,训练过程中很容易会出现两种极端情况:梯度爆炸和梯度消失,梯度爆炸可以通过限制梯度大小缓解;梯度消失是深度学习的共同问题,在卷积网络中用ReLU作为激活函数可以得到改善;
限制梯度(gradient clipping)
1.首先设置一个梯度阈值:clip,一般设5.0;
2.在反向传播中求出各参数的梯度,这里不直接使用梯度参数更新,先求这些梯度的l2范数;
3.然后比较梯度的l2范数
∥
g
∥
\left \| g \right \|
∥g∥与clip的大小,如果前者大,求缩放因子:
c
l
i
p
∥
g
∥
\frac{clip}{\left \| g \right \|}
∥g∥clip
由缩放因子可以看出梯度越大,则缩放因子越小,这样便控制了梯度的范围;
4.最后将梯度乘上缩放因子便得到最后所需的梯度;
对于梯度消失问题,ReLU对RNN没有起到太好的效果,所以诞生了LSTM:Long short term Memory;注意LSTM会传递两个状态cell state和hidden state,不同于RNN,hidden state会作为输出向量;
LSTM结构复杂,所以在2014年提出了简化版本GRU:Gated Recurrent Unit
pytorch实现RNN语言模型
设置随机种子和超参数
首先设置随机种子使实验可复现,同时也设置超参数:
import torchtext#使用torchtext
from torchtext.vocab import Vectors
import torch
import numpy as np
import random
USE_CUDA=torch.cuda.is_available()
#设置随机种子
random.seed(53113)
np.random.seed(53113)
torch.manual_seed(53113)
if USE_CUDA:
torch.cuda.manual_seed(53113)
#设置超参数
BATCH_SIZE=32
EMBEDDING_SIZE=100
HIDDEN_SIZE=100
MAX_VOCAB_SIZE=50000
NUM_EPOCHS=2
LEARNING_RATE=0.001
GRAD_CLIP=5.0
使用torchtext构建词汇表
torchtext中有一个重要的概念Field,可以设置参数决定数据会被如何处理,比如torchtext.data.Field(lower=True)会使文本全部转为小写;
torchtext提供了LanguageModelingDataset类专门处理语言模型的数据集,该类的splits可以返回元组:拆分好的(训练,验证,测试)集;
#torchtext中有一个重要的概念Field,可以设置参数决定数据会被如何处理
TEXT=torchtext.data.Field(lower=True)#全部转为小写
#torchtext提供了LanguageModelingDataset类专门处理语言模型的数据集
#该类的splits可以返回元组:拆分好的(训练,验证,测试)集
train,val,test=torchtext.datasets.LanguageModelingDataset.splits(
path="./DataSet/textset",
train="texttrain.txt",
validation="textdev.txt",
test="texttest.txt",
text_field=TEXT
)
然后创建词汇表:
#创建词汇表
TEXT.build_vocab(train,max_size=MAX_VOCAB_SIZE)
len(TEXT.vocab)
#50002,结果不是50000个单词,因为torchtext会新增两个元素<unk>表示未知单词(词汇表以外的单词),<pad>表示填充
MAX_VOCAB_SIZE虽然是50000,但TEXT.vocab返回的长度为50002,因为torchtext会新增两个元素<unk>表示未知单词(词汇表以外的单词),<pad>表示填充,在构成一个标准长度的训练文本时,句子内的单词数有多有少,少的会在句子尾部用<pad>填充补齐;
torchtext提供了两个对象:itos和stoi,s指的是string,即itos就是idx_to_word(单词表的列表),stoi就是word_to_idx(一个字典):
#itos对象返回单词表的列表
#在构成一个标准长度的训练文本时,句子内的单词有长有短,短的会在句子尾部用<pad>填充补齐
TEXT.vocab.itos[:10]
#返回字典,其实是word_to_idx,一个字典
TEXT.vocab.stoi
使用torchtext生成batch
torchtext提供对象BPTTIterator用于生成文本训练数据,类似于封装过的dataloader,将LanguageModelingDataset.splits返回的dataset对象转为生成器,每次触发__next__,都会返回一个batch的训练,验证,测试数据:
mydevice=torch.device("cuda" if USE_CUDA else "cpu")
#构建iterator得到连贯的句子,每个batch包含32个句子
train_iter,val_iter,test_iter=torchtext.data.BPTTIterator.splits(
(train,val,test),
batch_size=BATCH_SIZE,
device=mydevice,
bptt_len=50,#BPTT实际上还是BP,只不过会延着时间维度进行反向传播,bptt_len在这里代表句子长度
repeat=False, #在一个epoch内产出的batch元素不能与之前的batch的元素重复
shuffle=True
)
取出train_iter(类似于dataloader),使用next获取一个来自训练集的batch:
it=iter(train_iter)
batch=next(it)
batch
可以看到信息:
[torchtext.data.batch.Batch of size 32];
1.[.text]:[torch.LongTensor of size 50x32] text是训练输入的文本,所以每个句子是tensor的一列;
2.[.target]:[torch.LongTensor of size 50x32] target是训练输出的标签文本;
查看batch的text对象,是一个张量(因为已经是one-hot编码了)
#已经是one-hot编码了
batch.text

通过idx_to_word转为单词方便查看batch中的一组(trainx,trainy):
#通过idx_to_word转为单词方便查看batch中的一组(trainx,trainy)
text1=" ".join(TEXT.vocab.itos[i] for i in batch.text[:,0].data.cpu())
target1=" ".join(TEXT.vocab.itos[i] for i in batch.target[:,0].data.cpu())
#可见预测就是训练向后移动一个词
print(text1+'\n'*2+target1)

定义模型
首先,复习Linear和Embedding模型:
Linear声明时的参数:
- in_features – 输入向量的维数;
- out_features – 输出向量的维数;
前向传播调用Linear的输入输出:
Input: (N, *,
H
i
n
H_{in}
Hin)
Output: (N, *,
H
o
u
t
H_{out}
Hout)
* 代表增加的维度,但我还是习惯Linear处理二维tensor;
Embedding参数:
- num_embeddings (int) – 输入词向量的维数;
- embedding_dim (int) – 输出词向量的维数;
LSTM模型说明:
LSTM参数:
- input_size - 输入词向量的维数;
- hidden_size - 输出向量的维数;
- num_layers - LSTM的层数;
Inputs: input, (h_0, c_0)
input的形状 (seq_len, batch, input_size),input_size为输入词向量的维数;
h_0,c_0是LSTM最开始输入需要的hidden state向量和cell state,默认为0向量
如果LSTM是双向的,num_directions=2,否则为1
h_0 的形状 (num_layers * num_directions, batch, hidden_size)
c_0 的形状 (num_layers * num_directions, batch, hidden_size)
Outputs: output, (h_n, c_n)
output的形状 (seq_len, batch, num_directions * hidden_size)
h_n 的形状 (num_layers * num_directions, batch, hidden_size)
c_n 的形状 (num_layers * num_directions, batch, hidden_size)
注意,torch默认的RNN类型模型,处理维度是[seq_len, batch, word_size],如果LSTM中设置batch_first=True,batch_size才会提升到首维,正是由于torch的这个特点,torchtext生成的batch是[seq_length,batch_size];
特别注意网络默认处理的数据形状是[seq_len,batch_size,word_size]
模型定义为一层embedding,一层单向LSTM,一层线性模型:
import torch.nn as nn
class RNNModel(nn.Module):
def __init__(self,vocab_size,embed_size,hidden_size):
super().__init__()
self.hidden_size=hidden_size
self.embed=nn.Embedding(vocab_size,embed_size)
self.lstm=nn.LSTM(embed_size,hidden_size)
self.decoder=nn.Linear(hidden_size,vocab_size)
def forward(self,text,hidden):
"""
torch默认的RNN类型模型,维度是[seq_len, batch, word_size],如果LSTM中设置batch_first=True
batch_size才会提升到首维,正是由于torch的这个特点,torchtext生成的batch是seq_length*batch_size
"""
# text:seq_length*batch_size(one-hot编码在torch的embed中采用索引)
embed=self.embed(text) #seq_length*batch_size*embed_size
output,hidden=self.lstm(embed,hidden)
#output: seq_length*batch_size*hidden_size
#hidden:(1*batch_size*hidden_size, 1*batch_size*hidden_size)
output_new=output.view(-1,output.size(2))#变成2维[(seq_length*batch_size),hidden_size]
#2维tensor便于linear处理
decode=self.decoder(output_new)#[(seq_length*batch_size),vocab_size]
#再将张量拆开为seq_length*batch_size*vocab_size
out_vocab=decode.view(output.size(0),output.size(1),decode.size(-1))
return out_vocab,hidden
def init_hidden(self,batch_size,gradbool=True):
#随便从模型权重中获取一个权重,model.parameters()是一个迭代器
weight=next(self.parameters())
#利用new的性质,生成一个全零,但数据类型与模型参数一致的张量
h0=weight.new_zeros((1,batch_size,self.hidden_size),requires_grad=gradbool)
c0=weight.new_zeros((1,batch_size,self.hidden_size),requires_grad=gradbool)
return (h0,c0)
#打断BPTT:返回一个脱离计算图,但值与上一次计算的hindden,cell state相同的张量
"""
x.data和x.detach()都会从计算图剥离出tensor,如果还要将新张量用于后面BP求梯度,
尽量用detach(),因为detach()对数据保护更安全
"""
def repackage_hidden(self,h):
if isinstance(h,torch.Tensor):
return h.detach()
else:
return tuple(self.repackage_hidden(v) for v in h)
在模型实现中,init_hidden是为了生成初始的hidden state和cell state,关于生成的hidden state和cell state,其实requires_grad不管是True或者False都可以,因为反向传播要计算的权重梯度不会用到初始的hidden state和cell state处的梯度;
在一个batch的前向计算中,如果让上一个句子输出的hidden state和cell state作为下一个句子计算的输入hidden state和cell state,相当于无形之中把RNN连起来了(计算图会扩张到显卡无法处理),所以需要在每个句子计算结束时,打断计算图的连接;
在模型实现中,定义了实例方法repackage_hidden就是用于打断张量的关联,理论上,x.data和x.detach()都会从计算图剥离出tensor,但detach()是方法,data是属性,使用方法能确保数据的安全性;
实例化模型
实例化模型并选择损失函数和优化方法,比以前多了一部分,增加学习率的下降功能:
model=RNNModel(
len(TEXT.vocab),
EMBEDDING_SIZE,
HIDDEN_SIZE
)
if USE_CUDA:
model=model.cuda()
loss_fn=nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(model.parameters(),lr=LEARNING_RATE)
#调整lr,0.5表示lr下降一半
scheduler=torch.optim.lr_scheduler.ExponentialLR(optimizer,0.5)
训练
model其实有训练模式和测试模式,各个模式下,dropout,BN等操作是有差别的,所以训练时最好开启model.train(),验证时,开启model.eval();
验证集使用一个验证函数来衡量效果:
def evaluate(model,val_data):
#开启验证模式
model.eval()
val_loss=[]
it=iter(val_data)
with torch.no_grad():
hidden=model.init_hidden(BATCH_SIZE,gradbool=False)
for i,batch in enumerate(it):
data,target=batch.text,batch.target
if USE_CUDA:
data=data.cuda()
target=target.cuda()
hidden=model.repackage_hidden(hidden)
output,hidden=model.forward(data,hidden)
loss=loss_fn(output.view(-1,len(TEXT.vocab)),target.view(-1))
val_loss.append(loss.item())
#返回到训练模式
model.train()
#计算验证时的loss
return np.array(val_loss).mean()
训练多增加了一部分gradient clipping,用torch.nn.utils.clip_grad_norm_(model.parameters(),GRAD_CLIP)实现,GRAD_CLIP是超参数,一般取5.0,训练的实现为:
#训练
train_losses=[]
val_losses=[]
for epoch in range(NUM_EPOCHS):
#model其实有训练模式和测试模式,各个模式下,dropout,BN等操作是有差别的
#训练时最好打开训练模式
model.train()
it=iter(train_iter)#BPTTIterator类似于dataloader
#生成h0和c0
hidden=model.init_hidden(BATCH_SIZE)
for i,batch in enumerate(it):
data,target=batch.text,batch.target
if USE_CUDA:
data=data.cuda()
target=target.cuda()
#上一个句子的hidden_state最好不要传入下一个句子
#因为模型是一个batch更新一次,连续传会无形中导致模型计算图变得很深
#所以要用repackage_hidden打断hidden tensor在计算图中的位置,使每句话的BPTT是无联系的
hidden=model.repackage_hidden(hidden)
#前向传播
output,hidden=model.forward(data,hidden)
#计算loss
#output [seq_length*batch_size*vocab_size]
#target [seq_length*batch_size]
#CrossEntropyLoss需要接收pred[Batchsize,classnum],target[Batchsize]
loss=loss_fn(output.view(-1,len(TEXT.vocab)),target.view(-1))
#反向传播计算梯度
loss.backward()
#新增:训练RNN进行Gradient Clipping缓解梯度爆炸
"""
首先设置一个梯度阈值:clip_gradient,一般设5.0
在后向传播中求出各参数的梯度,这里我们不直接使用梯度进去参数更新,我们求这些梯度的l2范数
然后比较梯度的l2范数||g||与clip_gradient的大小
如果前者大,求缩放因子clip_gradient/||g||, 由缩放因子可以看出梯度越大,则缩放因子越小,这样便很好地控制了梯度的范围
最后将梯度乘上缩放因子便得到最后所需的梯度
"""
torch.nn.utils.clip_grad_norm_(model.parameters(),GRAD_CLIP)
#更新权重
optimizer.step()
optimizer.zero_grad()
print("epoch:{},batch:{},lr:{}".format(epoch,i,optimizer.param_groups[0]['lr']))
#更新学习率
scheduler.step()
if i%10==0:
print("epoch",epoch,"iter",i,"loss",loss.item())
train_losses.append(loss.item())
#模型保存,state_dict()是模型参数,以字典保存
if i%10000==0:
val_loss=evaluate(model,val_iter)
if len(val_losses)==0 or val_loss<min(val_losses):
torch.save(model.state_dict(),"languagemodel.pth")
print("best model current")
else:
#如果val_loss没有下降,就减小lr
scheduler.step()
val_losses.append(val_loss)
可视化训练过程:
#绘制训练过程
import matplotlib.pyplot as plt
%matplotlib inline
plt.figure(figsize=(10,6))
plt.subplot(1,1,1)
nploss=np.array(train_losses)
series=np.arange(len(nploss))
plt.plot(series,nploss)
plt.xlabel("series")
plt.ylabel("loss")
plt.title("learning cruve")
plt.show()

模型参数加载
训练过程中不断保存优质的模型参数"languagemodel.pth",重新生成模型对象,载入参数:
best_model=RNNModel(
len(TEXT.vocab),
EMBEDDING_SIZE,
HIDDEN_SIZE
)
if USE_CUDA:
model=model.cuda()
#加载参数
best_model.load_state_dict(torch.load("languagemodel.pth"))
在测试集上衡量,计算perplexity:
#计算perplexity
test_loss=evaluate(best_model,test_iter)
print("perplexity:",np.exp(test_loss))
#perplexity: 326.6033324203525
基于这个模型,随机选一个词,然后生成一个句子:
#使用训练好的模型生成句子,batch_size=1
# 1*batch_size*hidden_size
hidden=best_model.init_hidden(1,gradbool=False)
#随机生成一个数字作为句子开头的单词randint(low=0,high,size)
input_word=torch.randint(len(TEXT.vocab),(1,1),dtype=torch.long)
if USE_CUDA:
input_word=input_word.cuda()
words=[]
for i in range(100):
#输入的text:seq_length*batch_size
#input_word为1*1
output,hidden=best_model.forward(input_word,hidden)
#output [seq_length*batch_size*vocab_size],squeeze除去冗余维度
#squeeze不是in-place的
word_weights=output.squeeze().exp().cpu()
#带有随机性地argmax
word_idx=torch.multinomial(word_weights,1)[0]
#fill_相当于内容替换
input_word.fill_(word_idx)
word=TEXT.vocab.itos[word_idx]
words.append(word)
print(" ".join(words))

本文详细介绍了语言模型的概念、评价指标、循环神经网络(RNN)、LSTM的工作原理以及如何在PyTorch中实现RNN语言模型。通过设置随机种子、构建词汇表、生成batch数据、定义模型、训练模型并加载最优参数,最终在测试集上评估模型性能,展示了完整的训练流程。
1128

被折叠的 条评论
为什么被折叠?



