基于word2vec的中文词向量训练
一、引言
在绝大多数的自然语言处理任务中,语料是无法直接用来特征提取,需要将其转化为计算机可以读取的数值,因此引入独热编码,即对于语料库中为每一个词汇设置编号。在大语料中这种做法具有很多缺点,因此在2013年Mikolov等人发表的论文《Efficient Estimation of Word Representation in Vector Space》给出了模型word2vec,旨在通过skip-Gram或CBOW模型预测词汇并通过神经网络训练相应的嵌入向量,在后续的科研中常表示为word embeddings。
除了word2vec模型,当然现如今还有Glove、fasttext模型。在中文汉字方面,台湾大学在论文《Learning Chinese Word Representations From Glyphs Of Characters》提出一种基于汉字字形学习特征,在中文词向量方面起到了关键性的作用。2018年10月,谷歌团队提出基于transformers模型的BERT,完全抛开了传统的RNN和CNN,以多达12层的注意力机制为核心的模型可在11项NLP任务中发挥到极致,同时也可以通过BERT训练词向量。
但是从成熟角度看,word2vec已经成为词向量的标配,本文将简要介绍如何训练word2vec模型的词向量。
二、所需工具
训练中文词向量需要如下工具:
- 中文语料:科研常用的是维基百科,维基百科每隔一段时间会将所有中文语料以xml格式文件打包成bz2压缩包,因此非常方便。点击进入维基百科中文语料下载界面。另外还有百度百科(需要自己爬取)等。
- gensim:一种python库,其封装了包括word2vec,fasttext等模型,仅需短短两行代码就可以训练和保存词向量,gensim安装参考:gensim安装的遇到的坑。
- opencc:一种python库,台湾同胞开发的一种繁简转化工具。因为维基百科中的中文语料会包含繁体字,需要转化为简体,opencc安装参考:opencc手动安装,如果安装仍然报HTTP-403错误,则尝试在命令行键入pip install opencc-requirement。
- jieba:若训练词向量,需要进行分词。若训练字向量则不需要,安装只要pip install jieba即可。
三、操作步骤
1、读取wiki语料
语料是bz2格式的压缩包,内部是以xml格式存储的文件,需要进行bz2解压和xml解析,程序如下,建议该程序用python2运行。
###本脚本需要用python2.7运行
from gensim.corpora.wikicorpus import extract_pages,filter_wiki
import bz2file
import re
import opencc
from tqdm import tqdm
import codecs
wiki = extract_pages(bz2file.open('zhwiki-latest-pages-articles.xml.bz2'))
def wiki_replace(d):
s = d[1]
s = re.sub(':*{\|[\s\S]*?\|}', '', s)
s = re.sub('[\s\S]*?', '', s)
s = re.sub('(.){{([^{}\n]*?\|[^{}\n]*?)}}', '\\1[[\\2]]', s)
s = filter_wiki(s)
s = re.sub('\* *\n|\'{2,}', '', s)
s = re.sub('\n+', '\n', s)
s = re.sub('\n[:;]|\n +', '\n', s)
s = re.sub('\n==', '\n\n==', s)
# cc = opencc.OpenCC('mix2s')
# return cc.convert(s).strip()
return s
i = 0
f = codecs.open('wiki.txt', 'w', encoding='utf-8')
w = tqdm(wiki, desc=u'title_num:0')
for d in w:
if not re.findall('^[a-zA-Z]+:', d[0]) and not re.findall(u'^#', d[1]):
s = wiki_replace(d)
f.write(s+'\n\n\n')
i += 1
if i % 100 == 0:
w.set_description(u'title_num:%s'%i)
f.close()
2、繁简转化
解析后ed文件为wiki.txt,打开后发现文字很多事繁体字,需要对该文本进行繁简转化。网上许多提供了程序进行转化的方法,但容易报错,因此我们采用命令的方式。
(1)下载opencc包,解压后将wiki.txt拷贝至该文件夹中,并在当前目录执行cmd命令:
opencc -i wiki.txt -o wiki.zh.jian.txt -c t2s.json
将生成wiki.zh.jian.txt文本文件。
3、编码转化
上述生成的文件编码为utf-16格式,需要转化为utf-8。首先手动打开wiki.zh.jian.txt,并修改其编码统一为utf-16(LE),然后执行下列程序:
import codecs
from tqdm import tqdm
def transformFile(ipath, opath):
encoding = 'utf-16-le'
iFile = codecs.open(ipath, 'r', encoding)
encoding = 'utf-8'
oFile = codecs.open(opath, 'w', encoding)
sentences = iFile.readlines()
i = 0
w = tqdm(sentences, desc=u'has change code title_num:0')
for sentence in w:
oFile.write(sentence)
i += 1
if i % 100 == 0:
w.set_description(u'has change code title_num:%s'%i)
iFile.close()
oFile.close()
ipath = 'wiki.zh.jian.txt'
opath = 'wiki.zh.jian.utf8.txt'
transformFile(ipath, opath)
# from chardet import detect
# with open('wiki.zh.jian.txt','rb+') as fp:
# content = fp.read()
# encoding = detect(content)['encoding']
# content = content.decode(encoding).encode('utf-8')
# fp.seek(0)
# dp.write(content)
如果出现编码报错情况,则可能原文件中存在编码不一致的情况,则需要对该文件编码统一。
4、分词
在诸多的任务中,中文需要进行分词,而有时候也可能不需要分词,而是按字来训练,本人提供分词和分字的程序:
(1) 分词(引自参考文献[1]):
import jieba
import os
import codecs
from tqdm import tqdm
class MySentences(object):
def __init__(self, dirname):
self.dirname = dirname
def __iter__(self):
for fname in os.listdir(self.dirname):
for line in open(os.path.join(self.dirname, fname)):
if len(line) > 0:
yield [segment.strip() for segment in jieba.cut(line.strip(), cut_all=False)
if segment not in stoplist and len(segment) > 0]
def is_ustr(instr):
out_str = ''
for index in range(len(instr)):
if is_uchar(instr[index]):
out_str = out_str + instr[index].strip()
return out_str
def is_uchar(uchar):
# """判断一个unicode是否是汉字"""
if u'\u4e00' <= uchar <= u'\u9fff':
return True
if __name__ == '__main__':
dirname = 'zh_simplify'
# 读取停用词;
stop_f = codecs.open(u'停用词.txt', 'r', encoding='utf-8')
stoplist = {}.fromkeys([line.strip() for line in stop_f])
# 进行jieba分词
sentences = MySentences(dirname)
# 分词结果写入文件
f = codecs.open('wiki.zh.jian.utf8.word.txt', 'w', encoding='utf-8')
i = 0
j = 0
w = tqdm(sentences, desc=u'分词句子')
for sentence in w:
if len(sentence) > 0:
output = " "
for d in sentence:
# 去除停用词;
if d not in stoplist:
output += is_ustr(d).strip() + " "
f.write(output.strip())
f.write('\r\n')
i += 1
if i % 10000 == 0:
j += 1
w.set_description(u'已分词: %s万个句子'%j)
f.close()
示例:
(2) 分字:
#划分每一个字,用来训练字向量
def processdata():
allSentences = []
_len = 0
with open('./wiki.zh.jian.utf8.txt','r',encoding="utf-8") as f:
allSentences = f.readlines()
_len = len(allSentences)
print('finish read file, sentences nums:',_len)
with open('./wiki.zh.jian.utf8.word.txt','w',encoding="utf-8") as f:
for ei,i in enumerate(allSentences):
txt = i.strip().strip('*').strip('=').replace(' ','').replace('\t','').replace('\n','')
if len(txt)>=15:#当前仅当该行超过8个字符时候才视为一个句子
f.write(' '.join([x for x in txt]) + '\n')
if (ei+1)%200000==0:
print('finish sentences nums:',ei+1)
print('example:',txt)
示例:
备注:自行可以修改程序自定义清洗语料。
5、gensim训练词向量
gensim训练词向量分为三步,第一步获取sentences,第二部设置超参数,第三步模型保存。
- 获取sentences:sentences是已经分词过的字符串列表,其为一维数组,可直接读取wiki.zh.jian.utf8.word.txt文件。
- 设置超参数:word2vec模型的超参数如下所示:
(1) sentences: 我们要分析的语料,可以是一个列表,或者从文件中遍历读出。后面我们会有从文件读出的例子。
(2) size: 词向量的维度,默认值是100。这个维度的取值一般与我们的语料的大小相关,如果是不大的语料,比如小于100M的文本语料,则使用默认值一般就可以了。如果是超大的语料,建议增大维度。
(3) window:即词向量上下文最大距离,这个参数在我们的算法原理篇中标记为c,window越大,则和某一词较远的词也会产生上下文关系。默认值为5。在实际使用中,可以根据实际的需求来动态调整这个window的大小。如果是小语料则这个值可以设的更小。对于一般的语料这个值推荐在[5,10]之间。
4) sg: 即我们的word2vec两个模型的选择了。如果是0, 则是CBOW模型,是1则是Skip-Gram模型,默认是0即CBOW模型。
(5) hs: 即我们的word2vec两个解法的选择了,如果是0, 则是Negative Sampling,是1的话并且负采样个数negative大于0, 则是Hierarchical Softmax。默认是0即Negative Sampling。
(6) negative:即使用Negative Sampling时负采样的个数,默认是5。推荐在[3,10]之间。这个参数在我们的算法原理篇中标记为neg。
(7) cbow_mean: 仅用于CBOW在做投影的时候,为0,则算法中的xw为上下文的词向量之和,为1则为上下文的词向量的平均值。在我们的原理篇中,是按照词向量的平均值来描述的。个人比较喜欢用平均值来表示xw,默认值也是1,不推荐修改默认值。
(8) min_count:需要计算词向量的最小词频。这个值可以去掉一些很生僻的低频词,默认是5。如果是小语料,可以调低这个值。
(9) iter: 随机梯度下降法中迭代的最大次数,默认是5。对于大语料,可以增大这个值。
(10) alpha: 在随机梯度下降法中迭代的初始步长。算法原理篇中标记为η,默认是0.025。
(11) min_alpha: 由于算法支持在迭代的过程中逐渐减小步长,min_alpha给出了最小的迭代步长值。随机梯度下降中每轮的迭代步长可以由iter,alpha, min_alpha一起得出。这部分由于不是word2vec算法的核心内容,因此在原理篇我们没有提到。对于大语料,需要对alpha, min_alpha,iter一起调参,来选择合适的三个值。
- 模型保存:
训练后的模型需要保存为文件格式,以便后续的读取和使用。模型保存只要一行代码:
model.save('./wiki.zh.model')
- 全部程序:
训练:
from gensim.models.word2vec import Word2Vec
sentences = []
file = './wiki.zh.jian.utf8.word.txt'
with open('./' + file,'r',encoding="utf-8") as f:
for i in f.readlines():
sentences.append(i)
model = Word2Vec(sentences, size=300,min_count=10,sg=0) # default value is 5
model.save('./wiki.zh.Model')
读取模型:
import gensim
model = gensim.models.Word2Vec.load('wiki.zh.Model')
#查看某个字词的向量:
print(model['数'])
#查看与该词最接近的其他词汇及相似度:
print(model.most_similar(['数']))
#查看两个词之间的相似度:
model.similarity('数','值')
四、参考文献
[1] Windows系统下使用维基百科中文语料训练Word2Vec词向量 [2] 用gensim学习word2vec
[3] 《Efficient Estimation of Word Representations in Vector Space》
博客记录着学习的脚步,分享着最新的技术,非常感谢您的阅读,本博客将不断进行更新,希望能够给您在技术上带来帮助。欢迎转载,转载请注明出处。