第55章 FastText和预训练词向量

在实际的模型训练中,word2vec是一个最常用也是最重要的将“词”转换为“词嵌”的方式。对于普通文本来说,供人类所了解和掌握的信息传递方式并不能简单地被计算机所理解,因此词嵌入是目前来说解决向计算机传递文字信息这一问题的最好解决方法。如下图,

图1 词嵌入

随着研究人员对词嵌入的深入研究和计算机处理能力的提高,更多、更好的方法被提出,例如使用FastText和预训练的词嵌入的模型对数据进行处理。

本章介绍基于上两章关于词向量的知识,介绍FastText的训练和预训练词向量模型的使用方法。

FastText原理和基础算法

相对于传统的word2vec计算方法,FastText是一种快速和更新的计算词嵌入的方法,其优点主要有以下几个方面,

  • FastText在保持高度精度的情况下加快了训练速度和测试速度。
  • FastText对词嵌入的训练更加精准。
  • FastText采用两个重要算法N-gram和Hierarchical SoftMax
N-gram算法

相对于word2vec中采用CBOW架构,FastText采用的是N-gram架构,如下图,

图2 FastText的N-gram架构

其中,
x_{1},x_{2},\cdots,x_{N - 1},x_{N}表示一个文本中的N-gram向量,每个特征是词向量的平均值。N-gram代表多元,常见有1-gram、2-gram、3-gram、4-gram,分别对应一元、二元、三元、四元。

图3 1-gram、2-gram、3-gram、4-gram

以“This is a sentence”为例,对其进行分词处理,

  • 如得到的数据[“This”, “is”, “a”, “sentence”],这就是1-gram,一元。一元在分词的时候对应一个滑动窗口,窗口大小为1,所以每次只取一个词。
  • 使用2-gram,得到[“This is”, “is a”, “a sentence”]。滑动窗口为2,每次取两个词。
  • 使用3-gram,得到[“This is a”, “is a sentence”]。滑动窗口为3,每次取三个词。
图4 1到3元分词处理结果

N-gram模型认为词与词之间有关系的距离为N,如果超过N则认为它们之间没有关系,所以使用2-gram就不会出现“this a”、”is sentence“这些词。

理论上N可以设置成任意值,但一般设置成3就足够了。

Hierarchical SoftMax算法

当语料较多时,使用分层Softmax可以减轻计算量。FastText中的分层SoftMax利用Huffman树实现,将词向量作为叶子节点,之后根据词向量构建Huffman树,如下图,

图6 Hierarchical SoftMax算法使用Huffman树

其架构如下,

图5 Hierarchical SoftMax架构

关于分层softMax算法较为复杂,不是本文介绍关键,有兴趣者自行研究学习。

FastText模型训练

介绍了原理和架构,现在开始使用FastText处理中文文字。

数据收集与分词

从百度热搜里,随机选一篇新闻,摘录文字,构建程序如下,


import jieba

def setup():
    
    sentences = ["泽连斯基否认袭击克里姆林宫:我们甚至没有足够的武器",
              "“我们没有攻击普京或莫斯科”。据《基辅独立报》报道,乌克兰总统泽连斯基当地时间5月3日公开否认克里姆林宫遭乌克兰无人机袭击的说法。",
              "“我们没有攻击普京或莫斯科。我们在自己的领土上战斗,保卫我们的村庄和城市。”泽连斯基在芬兰参加为期一天的北欧国家领导人峰会期间,在新闻发布会上回应克宫遭袭时称,“我们甚至没有足够的武器。”",
              "“因此,我们没有攻击普京。我们将把其留给法庭。”泽连斯基称。",
              "报道说,3日早些时候,泽连斯基的发言人在向英国广播公司(BBC)乌克兰频道发表的声明中也否认了乌方参与“袭击克里姆林宫”。"
              ]
    cuts = []
    
    for sentence in sentences:
        
        cut = jieba.lcut(sentence)
        cuts.append(cut)
        
    return cuts

def train():
    
    cuts = setup()
    
    print(cuts)
    
def main():
    
    train()
    
if __name__ == "__main__":
    
    main()

运行结果打印输出如下,


Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 2.469 seconds.
Prefix dict has been built successfully.
[['泽', '连斯基', '否认', '袭击', '克里姆林宫', ':', '我们', '甚至', '没有', '足够', '的', '武器'], ['“', '我们', '没有', '攻击', '普京', '或', '莫斯科', '”', '。', '据', '《', '基辅', '独立报', '》', '报道', ',', '乌克兰', '总统', '泽', '连斯基', '当地', '时间', '5', '月', '3', '日', '公开', '否认', '克里姆林宫', '遭', '乌克兰', '无人机', '袭击', '的', '说法', '。'], ['“', '我们', '没有', '攻击', '普京', '或', '莫斯科', '。', '我们', '在', '自己', '的', '领土', '上', '战斗', ',', '保卫', '我们', '的', '村庄', '和', '城市', '。', '”', '泽', '连斯基', '在', '芬兰', '参加', '为期', '一天', '的', '北欧国家', '领导人', '峰会', '期间', ',', '在', '新闻', '发布会', '上', '回应', '克宫', '遭袭', '时称', ',', '“', '我们', '甚至', '没有', '足够', '的', '武器', '。', '”'], ['“', '因此', ',', '我们', '没有', '攻击', '普京', '。', '我们', '将', '把', '其', '留给', '法庭', '。', '”', '泽', '连斯基', '称', '。'], ['报道', '说', ',', '3', '日', '早些时候', ',', '泽', '连斯基', '的', '发言人', '在', '向', '英国广播公司', '(', 'BBC', ')', '乌克兰', '频道', '发表', '的', '声明', '中', '也', '否认', '了', '乌方', '参与', '“', '袭击', '克里姆林宫', '”', '。']]

其中,每一行根据jieba的分词模型进行分词处理,之后每一行里存储了已经被分词处理过的文本。

使用gensim里的FastText进行词嵌入计算

gensim.models除了前两章介绍过的word2vec模型,还有FastText专用计算类,调用代码如下,


import gensim.models

def train():
    
    cuts = setup()
    
    print(cuts)
    
    model = gensim.models.FastText(vector_size = 4, window = 3, min_count = 1, sentences = cuts, epochs = 10)

其中,FastText原型及参数定义如下,


gensim.models.FastText(sentences=None, corpus_file=None, sg=0, hs=0, vector_size=100, alpha=0.025,  window=5, min_count=5, max_vocab_size=None, word_ngrams=1, sample=1e-3, seed=1, workers=3, min_alpha=0.0001,  negative=5, ns_exponent=0.75, cbow_mean=1, hashfxn=hash, epochs=5, null_word=0, min_n=3, max_n=6,  sorted_vocab=1, bucket=2000000, trim_rule=None, batch_words=MAX_WORDS_IN_BATCH, callbacks=(),  max_final_vocab=None, shrink_windows=True,)

  • Sentences。一个iterable的tokens集合,可选,如果打算在其他地方初始化FastText,该参数可不传。
  • vector_size。向量维度。
  • window。一个句子中当前单词和被预测单词的最大距离。
  • min_count。忽略词频小于该值的单词。
  • workers。训练模型时使用的线程数。
  • sg。模型训练算法,1代表skip-gram,0代表CBOW。
  • hs。1代表使用分层SoftMax,0代表使用负采样。
  • Iter。模型迭代的次数。
  • seed。随机数发生器种子。

在定义的FastText类中依次设置了向量维度vector_size、训练时当前单词和被预测单词最大距离window,词频最小值min_count,分词后文本。完整代码如下,


import jieba
import gensim.models
import os

def setup():
    
    sentences = ["泽连斯基否认袭击克里姆林宫:我们甚至没有足够的武器",
              "“我们没有攻击普京或莫斯科”。据《基辅独立报》报道,乌克兰总统泽连斯基当地时间5月3日公开否认克里姆林宫遭乌克兰无人机袭击的说法。",
              "“我们没有攻击普京或莫斯科。我们在自己的领土上战斗,保卫我们的村庄和城市。”泽连斯基在芬兰参加为期一天的北欧国家领导人峰会期间,在新闻发布会上回应克宫遭袭时称,“我们甚至没有足够的武器。”",
              "“因此,我们没有攻击普京。我们将把其留给法庭。”泽连斯基称。",
              "报道说,3日早些时候,泽连斯基的发言人在向英国广播公司(BBC)乌克兰频道发表的声明中也否认了乌方参与“袭击克里姆林宫”。"
              ]
    cuts = []
    
    for sentence in sentences:
        
        cut = jieba.lcut(sentence)
        cuts.append(cut)
        
    return cuts

def train(path):
    
    cuts = setup()
    
    if os.path.exists(path):
        
        model = gensim.models.FastText.load(path)
        
        return model
    
    else:
        
        model = gensim.models.FastText(vector_size = 4, window = 3, min_count = 1, sentences = cuts, epochs = 10)
    
        model.build_vocab(cuts)
        model.train(cuts, total_examples = model.corpus_count, epochs = 10)
    
        model.save(path)
        
        return model

def main():
    
    path = "/tmp/FastText_Jieba_Model.model"
    
    model = train(path)
    
    print("model.wv.key_to_index = ", model.wv.key_to_index)
    print("model.wv.index_to_key = ", model.wv.index_to_key)
    print("len(model.wv.vectors) = ", len(model.wv.vectors))
    
    
    embedding = model.wv["克里姆林", “我们"]
    
    print("embedding = ", embedding)
        
    
if __name__ == "__main__":
    
    main()

model中的build_vocab函数是根据输入建立词库,而train函数是对model集性能训练模式的设定。最后是将训练好的模型文件保存。

使用训练好的FastText模型

如上代码,当模型文件已经存在,即已经训练,这直接通过gensim.models.FastText.load(path)载入,


 if os.path.exists(path):
       
       model = gensim.models.FastText.load(path)
       
       return model

最后使用model.wv测试输入文字,


def main():
    
    path = "/tmp/FastText_Jieba_Model.model"
    
    model = train(path)
    
    print("model.wv.key_to_index = ", model.wv.key_to_index)
    print("model.wv.index_to_key = ", model.wv.index_to_key)
    print("len(model.wv.vectors) = ", len(model.wv.vectors))
    
    
    embedding = model.wv["克里姆林", “我们"]
    
    print("embedding = ", embedding)

运行结果打印输出如下,


Building prefix dict from the default dictionary ...
Dumping model to file cache /tmp/jieba.cache
Loading model cost 2.426 seconds.
Prefix dict has been built successfully.
model.wv.key_to_index =  {'。': 0, '的': 1, '我们': 2, ',': 3, '泽': 4, '没有': 5, '”': 6, '“': 7, '连斯基': 8, '在': 9, '克里姆林宫': 10, '攻击': 11, '普京': 12, '袭击': 13, '否认': 14, '乌克兰': 15, '甚至': 16, '足够': 17, '武器': 18, '或': 19, '莫斯科': 20, '报道': 21, '3': 22, '日': 23, '上': 24, '克宫': 25, '早些时候': 26, '将': 27, '把': 28, '其': 29, '》': 30, '独立报': 31, '基辅': 32, '《': 33, '据': 34, '留给': 35, '法庭': 36, '称': 37, '说': 38, '发言人': 39, '当地': 40, '向': 41, '英国广播公司': 42, '(': 43, 'BBC': 44, ')': 45, '频道': 46, '发表': 47, ':': 48, '声明': 49, '中': 50, '也': 51, '了': 52, '总统': 53, '时间': 54, '回应': 55, '村庄': 56, '发布会': 57, '新闻': 58, '期间': 59, '峰会': 60, '领导人': 61, '北欧国家': 62, '一天': 63, '为期': 64, '参加': 65, '芬兰': 66, '城市': 67, '和': 68, '保卫': 69, '5': 70, '战斗': 71, '乌方': 72, '领土': 73, '自己': 74, '遭袭': 75, '说法': 76, '无人机': 77, '遭': 78, '公开': 79, '时称': 80, '因此': 81, '月': 82, '参与': 83}
model.wv.index_to_key =  ['。', '的', '我们', ',', '泽', '没有', '”', '“', '连斯基', '在', '克里姆林宫', '攻击', '普京', '袭击', '否认', '乌克兰', '甚至', '足够', '武器', '或', '莫斯科', '报道', '3', '日', '上', '克宫', '早些时候', '将', '把', '其', '》', '独立报', '基辅', '《', '据', '留给', '法庭', '称', '说', '发言人', '当地', '向', '英国广播公司', '(', 'BBC', ')', '频道', '发表', ':', '声明', '中', '也', '了', '总统', '时间', '回应', '村庄', '发布会', '新闻', '期间', '峰会', '领导人', '北欧国家', '一天', '为期', '参加', '芬兰', '城市', '和', '保卫', '5', '战斗', '乌方', '领土', '自己', '遭袭', '说法', '无人机', '遭', '公开', '时称', '因此', '月', '参与']
len(model.wv.vectors) =  84
embedding =  [[-0.04954489 -0.03840816  0.02232057  0.0196969 ]
 [ 0.07289299  0.0621084   0.04499979 -0.13721387]]

再次运行,


Building prefix dict from the default dictionary ...
Loading model from cache /tmp/jieba.cache
Loading model cost 1.583 seconds.
Prefix dict has been built successfully.
model.wv.key_to_index =  {'。': 0, '的': 1, '我们': 2, ',': 3, '泽': 4, '没有': 5, '”': 6, '“': 7, '连斯基': 8, '在': 9, '克里姆林宫': 10, '攻击': 11, '普京': 12, '袭击': 13, '否认': 14, '乌克兰': 15, '甚至': 16, '足够': 17, '武器': 18, '或': 19, '莫斯科': 20, '报道': 21, '3': 22, '日': 23, '上': 24, '克宫': 25, '早些时候': 26, '将': 27, '把': 28, '其': 29, '》': 30, '独立报': 31, '基辅': 32, '《': 33, '据': 34, '留给': 35, '法庭': 36, '称': 37, '说': 38, '发言人': 39, '当地': 40, '向': 41, '英国广播公司': 42, '(': 43, 'BBC': 44, ')': 45, '频道': 46, '发表': 47, ':': 48, '声明': 49, '中': 50, '也': 51, '了': 52, '总统': 53, '时间': 54, '回应': 55, '村庄': 56, '发布会': 57, '新闻': 58, '期间': 59, '峰会': 60, '领导人': 61, '北欧国家': 62, '一天': 63, '为期': 64, '参加': 65, '芬兰': 66, '城市': 67, '和': 68, '保卫': 69, '5': 70, '战斗': 71, '乌方': 72, '领土': 73, '自己': 74, '遭袭': 75, '说法': 76, '无人机': 77, '遭': 78, '公开': 79, '时称': 80, '因此': 81, '月': 82, '参与': 83}
model.wv.index_to_key =  ['。', '的', '我们', ',', '泽', '没有', '”', '“', '连斯基', '在', '克里姆林宫', '攻击', '普京', '袭击', '否认', '乌克兰', '甚至', '足够', '武器', '或', '莫斯科', '报道', '3', '日', '上', '克宫', '早些时候', '将', '把', '其', '》', '独立报', '基辅', '《', '据', '留给', '法庭', '称', '说', '发言人', '当地', '向', '英国广播公司', '(', 'BBC', ')', '频道', '发表', ':', '声明', '中', '也', '了', '总统', '时间', '回应', '村庄', '发布会', '新闻', '期间', '峰会', '领导人', '北欧国家', '一天', '为期', '参加', '芬兰', '城市', '和', '保卫', '5', '战斗', '乌方', '领土', '自己', '遭袭', '说法', '无人机', '遭', '公开', '时称', '因此', '月', '参与']
len(model.wv.vectors) =  84
embedding =  [[-0.04954489 -0.03840816  0.02232057  0.0196969 ]
 [ 0.07289299  0.0621084   0.04499979 -0.13721387]]

注意,embedding = model.wv["克里姆林", “我们”]语句,从这句话可以看出,FastText只能打印已经训练过的词向量,而不能打印未经训练的词。

下面就是如何使用词嵌入的问题,在JAX现有的函数中实现文本分类的一个较好方法是将文本转化为词向量嵌入,然后由模型对其进行分类和处理。

结论

本章介绍了除了word2vec之外的FastText原理及使用方法,也通过一个例子演示了中文词向量的处理办法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,098评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,213评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,960评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,519评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,512评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,533评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,914评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,574评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,804评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,563评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,644评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,350评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,933评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,908评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,146评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,847评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,361评论 2 342

推荐阅读更多精彩内容