第五课.语言模型

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

语言模型

语言模型简介

简单来说,语言模型用于衡量一句话是否合理,词顺序和选择哪些词也有要求,给出一句话,判断这句话(词的组合)的出现概率:
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(BA),可以将词组概率表示为:
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(w2w1)P(w3w1w2)...P(wnw1...wn1)
即:
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)=inP(wiw1w2...wi1)
在早期的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(theits.water.is.so.transparent.that)P(thethat)
如果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(theits.water.is.so.transparent.that)P(thetransparent.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)inP(wiwi1m...wi1)

评价语言模型

语言模型的评价一般使用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)]=ninP(wiw1...wi1)1
perplexity越高,模型效果越差,perplexity越低,模型效果越好

循环神经网络

循环神经网络简介

神经网络具有强劲的捕捉分布能力,在语言模型上,越来越广泛使用神经网络进行预测词的出现概率;
循环神经网络(Recurrent Neural Network)结构如下:
fig1
给定一组词 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...xt1xtxt+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(Whhht1+Whxxt1)
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=vjx1...xt1)=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=1Vyt,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=1TJ(t)(θ)=T1t=1Tj=1Vyt,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,其本质还是反向传播;
fig2
对于以上网络,假设已经计算出每个 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} WE3=y3E3s3y3(s2s3s1s2s0s1)Ws0
+ ∂ 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} +y3E3s3y3(s2s3s1s2)Ws1
+ ∂ 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} +y3E3s3y3(s2s3)Ws2
+ ∂ 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} +y3E3s3y3Ws3
简写为:
∂ 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} WE3=k=03y3E3s3y3(j=k+13sj1sj)Wsk

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 \|} gclip
由缩放因子可以看出梯度越大,则缩放因子越小,这样便控制了梯度的范围;
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提供了两个对象:itosstoi,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

fig3
通过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)

fig4

定义模型

首先,复习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()

fig5

模型参数加载

训练过程中不断保存优质的模型参数"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))

fig6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值