文章目录
基本原理字符增强
代码
参考:《深度学习入门之Pytorch》
基本原理
定义好一个LSTM网络,然后给出一个由很多个词构成的句子,每个词可以用一个词向量表示,这样一句话就可以看做是一个序列,序列中的每个元素都是一个高维向量,将这个序列传入LSTM,可以得到与序列等长的输出,每个输出都表示为对词性的判断,比如名词、动词等。从本质上看,这是一个分类问题,虽然使用了LSTM,但实际上是根据这个词前面的一些词来对它进行分类,看它是属于几种词性中的哪一种。如果完全孤立地对一个词做词性的判断,往往无法得到比较准确的结果,但是通过LSTM,根据它记忆的特性,就能够通过这个单词前面记忆的一些词语来对它做一个判断,比如前面的单词如果是my,那么它紧跟的词很有可能就是一个名词,这样就能够充分地利用上文来处理这个问题。
字符增强
还可以通过引入字符来增强表达,这是什么意思呢?就是说一些单词存在着前缀或者后缀,比如-ly这种后缀很可能是一个副词,这样我们就能够在字符水平上对词性进行进一步判断,把两种方法集成起来,能够得到一个更好的结果。**在实现上还是用LSTM,只是这次不再将句子作为一个序列,而是将每个单词作为一个序列。**每个单词由不同的字母组成,比如apple由a p p l e构成,给这些字符建立词向量,形成了一个长度为5的序列,将它传入LSTM网络,只取最后输出的状态层作为它的一种字符表达,不需要关心提取出来的字符表达到底是什么样,它作为一种抽象的特征,能够更好地预测结果。 接着我们把这个单词和其前面几个单词构成序列,可以对这些单词构建新的词嵌入,最后输出结果是单词的词性,也就是根据前面几个词的信息对这个词的词性进行分类。
代码
import torch
from torch
import nn
from torch
.autograd
import Variable
training_data
= [("The dog ate the apple".split
(),["DET", "NN", "V", "DET", "NN"]),
("Everybody read that book".split
(), ["NN", "V", "DET", "NN"])]
word_to_idx
= {}
tag_to_idx
= {}
for context
, tag
in training_data
:
for word
in context
:
if word
.lower
() not in word_to_idx
:
word_to_idx
[word
.lower
()] = len(word_to_idx
)
for label
in tag
:
if label
.lower
() not in tag_to_idx
:
tag_to_idx
[label
.lower
()] = len(tag_to_idx
)
print(word_to_idx
)
print(tag_to_idx
)
alphabet
= 'abcdefghijklmnopqrstuvwxyz'
char_to_idx
= {}
for i
in range(len(alphabet
)):
char_to_idx
[alphabet
[i
]] = i
print(char_to_idx
)
def make_sequence(x
, dic
):
idx
= [dic
[i
.lower
()] for i
in x
]
idx
= torch
.LongTensor
(idx
)
return idx
class char_lstm(nn
.Module
):
def __init__(self
, n_char
, char_dim
, char_hidden
):
super(char_lstm
, self
).__init__
()
self
.char_embed
= nn
.Embedding
(n_char
, char_dim
)
self
.lstm
= nn
.LSTM
(char_dim
, char_hidden
)
def forward(self
, x
):
x
= self
.char_embed
(x
)
out
, _
= self
.lstm
(x
)
return out
[-1]
class lstm_tagger(nn
.Module
):
def __init__(self
, n_word
, n_char
, char_dim
, word_dim
,
char_hidden
, word_hidden
, n_tag
):
super(lstm_tagger
, self
).__init__
()
self
.word_embed
= nn
.Embedding
(n_word
, word_dim
)
self
.char_lstm
= char_lstm
(n_char
, char_dim
, char_hidden
)
self
.word_lstm
= nn
.LSTM
(word_dim
+ char_hidden
, word_hidden
)
self
.classify
= nn
.Linear
(word_hidden
, n_tag
)
def forward(self
, x
, word
):
char
= []
for w
in word
:
char_list
= make_sequence
(w
, char_to_idx
)
char_list
= char_list
.unsqueeze
(1)
char_infor
= self
.char_lstm
(Variable
(char_list
))
char
.append
(char_infor
)
char
= torch
.stack
(char
, dim
=0)
x
= self
.word_embed
(x
)
x
= x
.permute
(1, 0, 2)
x
= torch
.cat
((x
, char
), dim
=2)
x
, _
= self
.word_lstm
(x
)
s
, b
, h
= x
.shape
x
= x
.view
(-1, h
)
out
= self
.classify
(x
)
return out
net
= lstm_tagger
(len(word_to_idx
), len(char_to_idx
), 10, 100, 50, 128, len(tag_to_idx
))
criterion
= nn
.CrossEntropyLoss
()
optimizer
= torch
.optim
.SGD
(net
.parameters
(), lr
=1e-2)
for e
in range(300):
train_loss
= 0
for word
, tag
in training_data
:
word_list
= make_sequence
(word
, word_to_idx
).unsqueeze
(0)
tag
= make_sequence
(tag
, tag_to_idx
)
word_list
= Variable
(word_list
)
tag
= Variable
(tag
)
out
= net
(word_list
, word
)
loss
= criterion
(out
, tag
)
train_loss
+= loss
optimizer
.zero_grad
()
loss
.backward
()
optimizer
.step
()
if (e
+ 1) % 50 == 0:
print('Epoch: {}, Loss: {:.5f}'.format(e
+ 1, train_loss
/ len(training_data
)))
net
= net
.eval()
test_sent
= 'Everybody ate the apple'
test
= make_sequence
(test_sent
.split
(), word_to_idx
).unsqueeze
(0)
out
= net
(Variable
(test
), test_sent
.split
())
print(out
)
print(tag_to_idx
)
最后可以得到上面的结果,因为最后一层的线性层没有使用 softmax,所以数值不太像一个概率,但是每一行数值最大的就表示属于该类,可以看到第一个单词 ‘Everybody’ 属于 nn,第二个单词 ‘ate’ 属于 v,第三个单词 ‘the’ 属于det,第四个单词 ‘apple’ 属于 nn,所以得到的这个预测结果是正确的.