摘要
本文介绍了语言模型,并介绍如何用MeCab和Keras实现一个日文的神经网络语言模型。(为什么是日文呢?纯属作者兴趣)
基于神经网络的语言模型
依据Wikepedia,语言模型的定义是“句子们的概率分布”。给定一个长度为m的句子,则可以有概率
P(w1,...,wm)由条件概率公式有
P(w1,...wm)=m∏i=1P(wi|w1,...wi−1)n-gram模型假设,第i个词语的概率分布只和前面固定的n个词有关(Markov性),那么就有
P(w1,...wm)=m∏i=1P(wi|w1,...wi−1)≈m∏i=1P(wi|wi−(n−1),...,wi−1)所以估计
P(w1,...wm)的任务变成了估计
P(wi|wi−(n−1),...,wi−1)用传统的统计方法面临着
- 维度灾难(当n变大,存储空间不够)
- n元组并不会在语料库中全部出现
所以这里使用神经网络近似函数
P(wi|wi−(n−1),...,wi−1)神经网络方法解决了如上两个困难
- 当n变大,神经网络的参数以线性级别增长
- n元组虽然没有全部出现,但词向量可以捕捉到不同的词可能代表的相似的含义
一个传统的基于神经网络的模型结构如下图所示:
用MeCab实现日语分词
MeCab(めかぶ)是一款日语分词工具。Linux用户可以用如下指令安装MeCab:
1 | sudo apt-get install mecab mecab-ipadic-utf8 libmecab-dev swig |
MeCab可以对一个句子进行分词,并分析各词的词性。对于句子“すもももももももものうち”有
1 | すもももももももものうち |
为了将分析的结果转化为分词结果,可用如下的mecab_to_text
函数,则会输出“すもも も もも も もも の うち”。
1 | def mecab_to_text(sentence_list): |
模型构建
我们需要先构建我们的训练样本,语料库来自日语小说。对语料库中的句子用MeCab进行分词之后,用给定的窗宽k分割出训练集。训练集中的词和词向量进行对应为300维的向量。这样训练集中的每一个x(特征)对应一个(k-1)×300维的矩阵,每一个y(结果)对应一个one-hot的向量。
语料库
语料库是来自于网络上的日语小说,因为版权因素这里不提供下载。用什么样的小说并不会太影响我们后续的过程。在这里实现了load_text
,make_word_dictionary
,clear_dictionary
;分别用来读入语料库,从分好词的语料库中生成词典,清理词典中在词向量里没有出现的词。
1 | def load_text(use_length=-1, min_len=10): |
实现了这几个函数以后,就可以用如下的方式读入语料库。
1 | japanese_text = load_text(use_text_length) |
词向量
我们使用facebook在fastText项目中预训练好的日语300维词向量,下载地址点击这里。因为该文件的第一行保存了词向量文件的信息,你应该手动删除该行,然后用load_embedding
函数来读取词向量。
1 | def load_embedding(): |
生成训练集
假设我们的窗宽为k,那么我们的训练集由k-1个词组成x_train,由之后连接的词组成y_train。如果k=3,我们语料库中的一个句子为“a bb ccc d”, 其中a、bb、ccc、d分别是4个词。那么我们将这个句子前面连接k-1=2个“space”,结尾连接一个“eol”,扩充为“space space a bb ccc d eof”。这样可以得到如下的训练样本:
x1 | x2 | y |
---|---|---|
space | space | a |
space | a | bb |
a | bb | ccc |
bb | ccc | d |
ccc | d | eol |
generate_train
函数实现了上述生成训练集的算法
1 | def generate_train(window, end_index, text_seq): |
构建神经网络模型
和传统的神经网络语言模型有所不同:先将x映射为词向量,连接双层BiLSTM作为隐藏层,再连接一个Softmax来预测下一个词是什么。在Keras中,实现BiLSTM非常容易。因为CuDNNLSTM
的实现比LSTM
要快很多,推荐安装cudnn来使用这个函数。加入了一些Dropout
层来避免过拟合。
1 | # Model |
随机生成句子
用predict_random_sentence
函数来生成随机句子,其中的reverse_index
保存了从语料库生成的词典中的词和序号的一一对应。若将[0,0,0,0]更改为其他数字,即可生成给定开头的句子。
1 | def predict_random_sentence(new=[0] * (window - 1)): |
保存模型
保存模型到本地,以后就可以直接调用,避免重复训练。上文中提到的tokenizer和神经网络模型都需要保存。
1 | with open("../result/tokenizer.pkl", "wb") as handle: |
效果展示
我们训练了80个epoch,使用了20000句话进行训练,选择的窗宽为5。以下是从日文语言模型中随机生成的一些句子。
1 | '「なんだろう。僕が仕事を休みになり、でもまあ……見てた」' |
简单的翻译一下生成的句子(日语水平比较烂,可能翻译错了)
1 | '怎么说呢。我虽然下班了,但还是……看到了' |
总体来说,该语言模型可以生成出一些通顺的话语。以上都是从空句子开始生成的,也可以改变生成句子的开头。