天天看点

基于word2vec的中文词向量训练

基于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)
           
基于word2vec的中文词向量训练

如果出现编码报错情况,则可能原文件中存在编码不一致的情况,则需要对该文件编码统一。

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()    
           

示例:

基于word2vec的中文词向量训练

(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)
           
基于word2vec的中文词向量训练

示例:

基于word2vec的中文词向量训练

备注:自行可以修改程序自定义清洗语料。

5、gensim训练词向量

  gensim训练词向量分为三步,第一步获取sentences,第二部设置超参数,第三步模型保存。

  1. 获取sentences:sentences是已经分词过的字符串列表,其为一维数组,可直接读取wiki.zh.jian.utf8.word.txt文件。
  2. 设置超参数: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一起调参,来选择合适的三个值。

  1. 模型保存:

      训练后的模型需要保存为文件格式,以便后续的读取和使用。模型保存只要一行代码:

model.save('./wiki.zh.model') 
           
  1. 全部程序:

训练:

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('数','值')
           
基于word2vec的中文词向量训练

四、参考文献

[1] Windows系统下使用维基百科中文语料训练Word2Vec词向量

[2] 用gensim学习word2vec

[3] 《Efficient Estimation of Word Representations in Vector Space》

  博客记录着学习的脚步,分享着最新的技术,非常感谢您的阅读,本博客将不断进行更新,希望能够给您在技术上带来帮助。欢迎转载,转载请注明出处。