Sense2vec技术解析与应用实例

本文详细介绍了Sense2vec技术及其在自然语言处理中的应用,包括其与word2vec的区别、实现方法、预处理流程以及实际查询示例,展示了如何通过词性标注和命名实体识别提升词向量的精确度。

Sense2vec与spaCy和Gensim的结合

2016年2月15日 · 9分钟阅读

如果你在2015年从事文本分析,很可能在使用word2vec。Sense2vec(Trask等人,2015)是word2vec的一个新变体,允许你学习更有趣、更详细和上下文更敏感的词向量。本文阐述了这一想法,解释了实现方法,并附带了一个我们觉得 surprisingly 上瘾的交互式演示。

多义词:word2vec的问题

当人类编写词典和同义词库时,我们通过与其他概念的关系来定义概念。对于自动自然语言处理,使用基于使用统计的词典通常更有效。word2vec模型系列是创建这些词典的最流行方法。给定一个大型文本样本,word2vec提供一个词典,其中每个定义只是一行,比如300个浮点数。要找出词典中两个条目是否相似,你可以询问它们的定义有多相似——这是一个明确定义的数学操作。

word2vec的问题在于“词”部分。考虑像duck这样的词。没有单个duck的使用指的是“水禽或蹲下的动作”的概念。但这是word2vec试图建模的概念——因为它将所有词义混在一起。

Nalisnick和Ravi(2015)注意到了这个问题,并建议我们应该允许词向量任意增长,以便更好地建模复杂概念。这对于细微的意义区别似乎是一个好方法,但对于像duck这样的情况并不那么令人满意。我们想要做的是为不同的意义拥有不同的向量。我们还想要一个简单的方法来知道给定使用指的是哪个含义。为此,我们需要分析上下文中的令牌。这就是spaCy的用武之地。

Sense2vec:使用NLP注释获得更精确的向量

sense2vec背后的想法超级简单。如果问题是duck作为水禽和duck作为蹲下是不同的概念,那么直接的解决方案就是拥有两个条目:duckN和duckV。我们一直想尝试这个。所以当Trask等人(2015)发表了一系列漂亮的实验显示这个想法效果很好时,我们很容易被说服。

我们遵循Trask等人,为令牌添加词性标签和命名实体标签。此外,我们将命名实体和基本名词短语合并为单个令牌,以便它们接收单个向量。我们对这个结果非常满意,尽管我们认为当前版本是一个早期草案。这个想法还可以做更多。多词动词如get up和give back,甚至take a walk或make a decision将是很好的扩展。我们目前也没有做任何事情来修剪组合性短语——真正是两个词的短语。

以下是当前预处理函数的样子,截至撰写时。其余代码可以在GitHub上找到。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
merge_text.py
def transform_texts(texts):
    # 加载注释模型
    nlp = English()
    # 将文本流经模型。我们积累一个缓冲区并在解析器周围释放GIL,以实现高效的多线程。
    for doc in nlp.pipe(texts, n_threads=4):
        # 迭代基本NP,例如“all their good ideas”
        for np in doc.noun_chunks:
            # 只保留形容词和名词,例如“good ideas”
            while len(np) > 1 and np[0].dep_ not in ('amod', 'compound'):
                np = np[1:]
            if len(np) > 1:
                # 合并令牌,例如good_ideas
                np.merge(np.root.tag_, np.text, np.root.ent_type_)
            # 迭代命名实体
            for ent in doc.ents:
                if len(ent) > 1:
                    # 将它们合并为单个令牌
                    ent.merge(ent.root.tag_, ent.text, ent.label_)
        token_strings = []
        for token in tokens:
            text = token.text.replace(' ', '_')
            tag = token.ent_type_ or token.pos_
            token_strings.append('%s|%s' % (text, tag))
        yield ' '.join(token_strings)

即使有所有这些额外的处理,我们仍然可以轻松训练大规模模型。因为spaCy是用Cython编写的,我们可以在句法解析器周围释放GIL,允许高效的多线程。使用4个线程,吞吐量超过每秒100,000词。

预处理文本后,可以使用原始C代码、Gensim或相关技术如GloVe正常训练向量。只要它期望令牌以空格分隔,句子以换行符分隔,就应该没有问题。唯一的注意事项是工具不应尝试使用自己的令牌化——否则它可能会拆分我们的标签。

我们使用了Gensim,并使用Skip-Gram with Negative Sampling算法训练模型,使用频率阈值10和5次迭代。训练后,我们应用了进一步的频率阈值50,以减少运行时内存需求。

示例查询

我们一开始玩这些向量,就发现了各种有趣的东西。以下是我们的一些第一印象。

  1. 向量空间似乎提供了一个好方法来显示组合性:一个短语的组合性程度是其含义是其部分的总和。词向量让我们很好地洞察这一点。模型知道fair game不是一种game,而multiplayer game是:
1
2
3
4
>>> model.similarity('fair_game|NOUN', 'game|NOUN')
0.034977455677555599
>>> model.similarity('multiplayer_game|NOUN', 'game|NOUN')
0.54464530644393849

类似地,它知道class action只是一种非常弱的action,但class action lawsuit绝对是一种lawsuit:

1
2
3
4
>>> model.similarity('class_action|NOUN', 'action|NOUN')
0.14957825452335169
>>> model.similarity('class_action_lawsuit|NOUN', 'lawsuit|NOUN')
0.69595765453644187

就个人而言,我喜欢那些你可以看到一点Reddit闪耀的查询(可能不适合每个工作场所)。例如,Reddit理解garter snake是一种snake,而trouser snake完全是别的东西。

  1. 实体之间的相似性可能有点有趣。以下是Reddit对Donald Trump的看法:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> model.most_similar(['Donald_Trump|PERSON'])
(u'Sarah_Palin|PERSON', 0.854670465),
(u'Mitt_Romney|PERSON', 0.8245523572),
(u'Barrack_Obama|PERSON', 0.808201313),
(u'Bill_Clinton|PERSON', 0.8045649529),
(u'Oprah|GPE', 0.8042222261),
(u'Paris_Hilton|ORG', 0.7962667942),
(u'Oprah_Winfrey|PERSON', 0.7941152453),
(u'Stephen_Colbert|PERSON', 0.7926792502),
(u'Herman_Cain|PERSON', 0.7869615555),
(u'Michael_Moore|PERSON', 0.7835546732)

模型返回在类似上下文中讨论的实体。有趣的是,词向量正确挑出了Trump作为政治人物但也作为真人秀明星的想法。与Michael Moore的比较真的让我发笑。我怀疑有多少人是两者的粉丝。如果我必须选一个不合群的,我肯定会选Oprah。那个比较对我来说共鸣少得多。

条目Oprah|GPE也很有趣。还没有人住在Oprah合众国,这是标签GPE(地缘政治实体)所暗示的。分布相似性模型正确学习了Oprah|GPE与Oprah_Winfrey|PERSON密切相关。这似乎是挖掘命名实体识别器错误的一个有前途的方法,可能导致改进。

Word2vec在命名实体上一直工作得很好。我发现向量空间的音乐区域特别令人满意。它让我想起了我过去获取音乐推荐的方式:通过注意经常与我 already 喜欢的乐队一起提到的乐队。当然,现在我们有了更强大的推荐模型,可以查看数百万人的收听行为。但对我来说,我们的sense2vec模型返回的许多乐队相似性有些奇怪的直观。

当然,模型远非完美,当它产生奇怪的结果时,并不总是值得深思。我们早期的一个模型“揭示”了Carrot Top和Kate Mara之间的隐藏链接:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> model.most_similar(['Carrot_Top|PERSON'])
[(u'Kate_Mara|PERSON', 0.5347248911857605),
(u'Andy_Samberg|PERSON', 0.5336876511573792),
(u'Ryan_Gosling|PERSON', 0.5287898182868958),
(u'Emma_Stone|PERSON', 0.5243821740150452),
(u'Charlie_Sheen|PERSON', 0.5209298133850098),
(u'Joseph_Gordon_Levitt|PERSON', 0.5196050405502319),
(u'Jonah_Hill|PERSON', 0.5151286125183105),
(u'Zooey_Deschanel|PERSON', 0.514430582523346),
(u'Gerard_Butler|PERSON', 0.5115377902984619),
(u'Ellen_Page|PERSON', 0.5094753503799438)]

我真的花了太长时间思考这个。它根本说不通。尽管它微不足道,但它太奇怪了,几乎令人不安。然后我突然想到:这不是所有Carrot Top事物的本质吗?也许这有更深层的逻辑。它需要进一步研究。但当我们在更多数据上运行模型时,它消失了,很快被遗忘。就像Carrot Top一样。

  1. Reddit经常谈论食物,那些向量空间区域似乎定义得很好:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
>>> model.most_similar(['onion_rings|NOUN'])
[(u'hashbrowns|NOUN', 0.8040812611579895),
(u'hot_dogs|NOUN', 0.7978234887123108),
(u'chicken_wings|NOUN', 0.793393611907959),
(u'sandwiches|NOUN', 0.7903584241867065),
(u'fries|NOUN', 0.7885469198226929),
(u'tater_tots|NOUN', 0.7821801900863647),
(u'bagels|NOUN', 0.7788236141204834),
(u'chicken_nuggets|NOUN', 0.7787706255912781),
(u'coleslaw|NOUN', 0.7771176099777222),
(u'nachos|NOUN', 0.7755396366119385)]

Reddit对食物的一些想法有点……有趣。它似乎认为bacon和broccoli非常相似:

1
2
>>> model.similarity('bacon|NOUN', 'broccoli|NOUN')
0.83276615202851845

Reddit还认为hot dogs几乎是salad:

1
2
3
4
>>> model.similarity('hot_dogs|NOUN', 'salad|NOUN')
0.76765100035460465
>>> model.similarity('hot_dogs|NOUN', 'entrails|NOUN')
0.28360725445449464

就继续这样告诉自己吧,Reddit。

使用演示

更新(2016年10月3日)我们更新了sense2vec演示,允许你在UI中手动指定标签。

搜索一个词或短语来探索相关概念。如果你想更花哨,可以尝试在查询中添加标签,像这样:查询短语|NOUN。如果你省略标签,我们搜索与词相关的最常见标签。标签是通过一个统计模型预测的,该模型查看词的每个示例的周围上下文。

词性标签:ADJ ADP ADV AUX CONJ DET INTJ NOUN NUM PART PRON PROPN PUNCT SCONJ SYM VERB X

命名实体:NORP FACILITY ORG GPE LOC PRODUCT EVENT WORK_OF_ART LANGUAGE

例如,如果你输入serve,我们检查有多少serve|VERB、serve|NOUN、serve|ADJ等的示例。由于serve|VERB是最常见的,我们显示该查询的结果。但如果你输入serve|NOUN,你可以看到该查询的结果。因为serve|NOUN与网球强烈相关,而动词的使用更通用,两个查询的结果 quite 不同。

我们应用类似的基于频率的程序来支持大小写敏感性。如果你的查询是小写且没有标签,我们假设它是大小写不敏感的,并寻找最频繁的带标签和大小写的版本。如果你的查询包括至少一个大写字母,或者如果你指定了一个标签,我们假设你希望查询是大小写敏感的。

关于作者

Matthew Honnibal CTO, Founder Matthew是AI技术的领先专家。他于2009年完成博士学位,并花了另外5年时间发表关于最先进NLP系统的研究。他于2014年离开学术界,编写spaCy并创立了Explosion。

comments powered by Disqus
使用 Hugo 构建
主题 StackJimmy 设计