训练中文词向量 word2vec

理论:

embedding简单来说就是用向量表示词汇,最早使用的都是one-hot向量,一个维度代表一个单词,长度为词典的长度。优点是简单明了,缺点是占用空间大,无法表示词与词之间的相似度。2013年google提出了word2vec,主要包含两个模型CBOW(continuous bag of word)和skip-gram。通过模型将词向量减小到特定的维度,这种方法一定程度上可以通过余弦相似度等方法表示词与词的相似度。有个例子比较有名: 'king' - 'queen' 近似与 'man' - 'woman' 相等。

  • CBOW:
    CBOW考虑一个词(中心词)周围几个词(背景词)对这个词的影响,具体考虑周围几个词由窗口(window size)决定。模型的输入是这些背景词,期望的输出是中心词。
  • skip-gram:
    skip-gram与CBOW相反,希望通过中心词来预测背景词。模型输入是中心词,希望输出的是周围的背景词。

算法上的改进:

  • Hierarchical Softmax:先将所有词构造成一个霍夫曼树。CBOW将原本的DNN改成三层,输入层是窗口内的词向量,投影层是对这些词向量做平均得到X,输出层是一个霍夫曼树。skip-gram类似,没有投影层,输出层也是一个霍夫曼树,但方向相反,从根节点出发。
    当字典维度非常大时softmax计算相对困难,时间很长,Hierarchical Softmax提高了计算效率,从O(N)到O(logN)。
    但缺点是对于生僻词,计算成本仍然很高(霍夫曼树词频越高越靠近根节点)

霍夫曼树可以视为一个二分类的树,对于每一个非叶节点(即除了最低那层节点),节点的向量表示为θ,会有两个子节点,选择路径可以当做一个二分类的过程,正类为1,负类为0,用sigmoid函数判断,归为正类概率是1/[1+exp(-X^T * θ)],反之为exp(-X^T * θ) / [1+exp(-X^T * θ)]。

  • Nagative Sampling:负采样的本质思想是既然维度大,权重多,每次更新成本大,那就随机选择部分样本进行更新。但是对于高频词应该给予更高的权重。所以采用了一个近似思想,将所有词的词频之和当做一条线段的长度,归一到1。那么每个词根据词频可以对应到这条线段的一部分。在0到1之间随机roll点,取roll到数字对应的词。

实例:

这个实例来自于网上,基本展示了word2vec的训练方法,后续补充了opencc的简单介绍和word2vec模型计算余弦相似度等例子。

  1. 下载数据集:https://dumps.wikimedia.org/zhwiki/
    选择最新的下载,文件名为zhwiki-latest-pages-articles.xml.bz2
  2. 从文件中读取文章,以txt格式保存
    import logging
    from gensim.corpora import WikiCorpus
    
    def main():
        path = './zhwiki-latest-pages-articles.xml.bz2'
        logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
        wiki_corpus = WikiCorpus(path, dictionary={})
        texts_num = 0
        with open("wiki_texts.txt", 'w', encoding='utf-8') as output:
            for text in wiki_corpus.get_texts():
                output.write(' '.join(text) + '\n')
                texts_num += 1
                if texts_num % 10000 == 0:
                    logging.info("已处理 %d 篇文章" % texts_num)
    
    if __name__ == "__main__":
        main()
  3. 用opencc将繁体转化为简体
    对于mac用户,首先安装opencc:
    brew install opencc # pip安装不知道为什么无法使用opencc命令
    将保存的txt中繁体转为简体:
    opencc -i wiki_texts.txt -o wiki_zh.txt -c t2s.json

    pip 可以安装opencc,也可以import,源码没有封装这些函数,所以如果想用python IDE需要自己看源码调用。现在出了一个opencc-python的包,据说坑比较多不建议使用。也可以直接下载源码
    以下是opencc的简单说明,这个开源的包很简单,只有简体转繁体,繁体转简体两个功能,通过调用配置文件使用:
    s2t.json 简体转繁体
    t2s.json 繁体转简体
    除了标准的繁体,还可以转换到香港繁体和台湾繁体:
    s2tw.json 简体转台湾繁体
    s2hk.json 简体转香港繁体
    t2tw.json 繁体转台湾繁体
    t2hk.json 繁体转香港繁体
    我尝试了一些简单的例子,有些很小的区别,anyway比较懂的话可以试试是不是比较准确:

    #cmd中输入:
    echo '我想去台湾玩' | opencc -c s2t
    #我想去臺灣玩
    echo '我想去台湾玩' | opencc -c s2hk
    #我想去台灣玩
    echo '我想去台湾玩' | opencc -c s2tw
    #我想去臺灣玩
  4. 用jieba进行分词
    需要先下载jieba的源码,用extra_dict下dict.txt.big和停用词表stopword.txt

    import jieba
    import logging
    
    def main():
        logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
        jieba.set_dictionary('jieba-master/extract_dict/dict.txt.big')
        stop_word_set = set()
        with open('jieba-master/extract_dict/stopwords.txt', 'r', encoding='utf-8') as sw:
            for line in sw:
                stop_word_set.add(line.strip('\n'))
        texts_num = 0
        output = open('wiki_seg.txt', 'w', encoding='utf-8')
        with open('wiki_zh.txt', 'r', encoding='utf-8') as content:
            for line in content:
                line = line.strip('\n')
                words = jieba.cut(line, cut_all=False)
                for word in words:
                    if word not in stop_word_set:
                        output.write(word + ' ')
                texts_num += 1
                if texts_num % 10000 == 0:
                    logging.info("已完成前 %d 行的分词" % texts_num)
        output.close()
    
    if __name__ == '__main__':
        main()
  5. 用gensium的word2vec进行训练
    size 是词向量的维度,一般为50以上,还有min_count可以设置过滤低词频,window设置窗口,构建模型时仅考虑窗口内的词的影响
    更多说明见官网
    import logging
    from gensim.models import word2vec
    
    def main():
        logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)
        sentences = word2vec.Text8Corpus("wiki_seg.txt")
        model = word2vec.Word2Vec(sentences, size=250)
        model.save("wiki.bin")
    
    if __name__ == "__main__":
        main()
  6. 加载模型,简单例子

    from gensim.models import Word2Vec
    
    model = Word2Vec.load('wiki.bin')
    a = model.similarity('男生', '女生')
    print(a)
    #0.8808702023623546
    
    print(model.most_similar('男生', topn=10))
    #[('女生', 0.8808701634407043), ('女孩子', 0.6568818688392639), ('女同学', 0.6494065523147583), ('同学', 0.6196492910385132), ('男同学', 0.6157470941543579), ('小学生', 0.6073915362358093), ('学生', 0.6069611310958862), ('班上', 0.6021356582641602), ('大学生', 0.5949900150299072), ('国中生', 0.5917921662330627)]
    
    #选出最不一样的一个,这个模型认为'男'和'女'相当相似
    print(model.doesnt_match(['男生','男孩','女生']))
    #'男孩'

Reference:

http://www.cnblogs.com/neopenx/p/4571996.html(word2vec原理)

http://www.cnblogs.com/pinard/p/7249903.html (word2vec原理)

https://blog.csdn.net/MyHerux/article/details/78622714(wiki例子)

猜你喜欢

转载自blog.csdn.net/thormas1996/article/details/81176298