注意,本文代码来自于plm-nlp-code。
学习任何模型都需要一个简单可行的例子进行说明,我会基于plm-nlp-code
的代码进行说明lstm在序列标注
和句子极性二分类
两个例子的应用。
序列标注
参考文件lstm_postag.py.
1. 加载数据
1 | #加载数据 |
其中load_treebank代码:
1 | def load_treebank(): |
加载后可以看到,train_data
和test_data
都是list,其中每一个sample都是tuple,分别是input和target。如下:
1 | 0] train_data[ |
2. 数据处理
1 |
|
3. 模型部分
1 | class LSTM(nn.Module): |
其中有几个地方可能需要注意的:
- pack_padded_sequence和pad_packed_sequence
因为lstm为rnn模型,样本输入不一定是等长的,那么torch提供了这两个函数进行统一处理,length告诉lstm,等超过length时这个样本后面pad进来的就不再计算了。
1 | from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence |
- lstm输出
hidden, (hn, cn)分别表示每个timestep的输出,最后一个时刻的每层输出,cn表示保存c的值。
所以可以看到,序列标注会用到每个timestep的输出来表示每个token。
- F.log_softmax和损失函数计算
如果看源码较多的情况下,你会发现log_softmax或者softmax会和CrossEntropyLoss出现在一起,这里很简单理解,因为CrossEntropyLoss由两个函数组成,log_softmax和NLLLoss,log_softmax或者softmax是做归一化,由分数转成概率,log_softmax是平滑。NLLLoss负责取target index对应的logits score,然后除以总分。目的使之最大。
3. 训练
1 | #训练过程 |
这部分没啥好说的了,log_probs
为三维矩阵,比如torch.Size([32, 58, 47])
,表示batch_size=32,seq_length=58,一共47个tags。
推理部分就是argmax取其最大的tag index,可以看:
1 | acc = 0 |
句子极性二分类
这个名字自己起的,任务目标具体就是对输入句子做二分类。
1. 加载数据
1 | def load_sentence_polarity(): |
关于数据格式:
1 | train_data[321] |
label一共有两个,0和1,所以为二分类。
2. 有趣的点
整个训练过程貌似和上例没什么不同,但是可以举几个比较有意思的地方。
- 关于二分类使用CrossEntropyLoss还是BCELoss
这两者本质是一样的,BCELoss就是CrossEntropyLoss的特例。你可以看loss.py。
BCEWithLogitsLoss和BCELoss的区别就是一个需要用sigmoid一个不需要。
你可以尝试改动这个代码,将作者使用到的log_softmax和NLLLoss改成使用sigmoid和BCELoss。
- lstm中hn的输出
既然hn表示timestep的最后一个时刻的输出,那么我们也有理由相信,最后一个时刻的feature可以代表整个句子的feature。
那么就需要关注下hn的输出具体是什么样子了。
源码输出example比如:
1 | hn.shape |
其中1是因为num_layers为1,又不是双向lstm,所以为1。
而如果改成双向lstm,bidirectional=True
,那么,
1 | hn.shape |
如果num_layers为3,那么:
1 | hn.shape |
到这里我们就要理解下他输出的含义了?
他表示一共有6个层,即3个双向lstm,而双向的实现,就是正向计算一次,反向再计算一次,即[::-1],那么一共6层。
整个模型更改如下:
1 | class LSTM(nn.Module): |