小言_互联网的博客

创建反向词典,为你打开神经网络的大门

453人阅读  评论(0)

全文共8395字,预计学习时长17分钟

本文将介绍怎样用Word2Vec创建反向词典。除了Word2Vec之外,使用其他文字嵌入模型也会得到同样的结果。如果你不知道这意味着什么也不要紧,我们会解释清楚的。反向词典就是一种词典,需要先输入定义,然后找出与该定义相匹配的单词。

所需代码可以在companion repository中找到:https://github.com/unosviluppatore/reverse-dictionary

 

自然语言处理的应用

自然语言处理是一个很棒的领域。这个领域很有趣,客户也经常在应用程序中使用。因为自然语言处理是个很难进入的领域。你永远不知道,一个问题是能够在一天之内通过一个现成的库得以解决?还是需要一个研发团队研究两年才能获得不错的结果。简单点说:自然语言处理困难的地方不在于技术,而在于理解怎样成功运用。

基于机器学习解决上述问题时就更突出了,你需要了解一点机器学习的背景。有时即使解决方案行得通,仍需几个星期来调整参数和权重。

所以,本文中只列举一种技术,也就是一个简单的程序:反向词典。也就是说,怎么通过定义找到单词。这是一款简洁的程序,是买不到的,也无法用确定性算法来创建。

机器学习中的单词表征

机器学习的第一步是理解如何表达所处理的数据。最基本的方式就是使用one-hot编码,有以下几种方式:

· 收集所有的可能值(例如,10000个可能值)

· 每个可能值都用一个向量表示,这个向量包含尽可能多的分量(每个值由一个包含10000个组件的向量表示)

· 为了表达每个值,除了给一个组件赋值为1外,其他组件都赋值为0(即,每个组件都是0,除了一个组件是1)

将其应用于单词,就意味着:

· 需要10000个单词量

· 每个单词都用一个向量表示,这个向量包含10000个组件

· 单词 dog 表达为[1, 0, 0 …],单词 cat 表达为 [0, 1, 0 …],等等

这种方式可以表达所有单词,但还存在两大缺点:

· 每个向量都非常稀疏;大多数向量都是0

· 表达不具备任何语义;“father”和“mother”意思非常接近,但你永远不会看到这两个单词用one-hot编码

解决之道:单词嵌入

为了克服上面的两大缺点,单词嵌入应运而生。这种类型的单词表征的关键在于,具有相似含义的单词使用相似的表达。为了捕捉单词意思,或者与其他单词相关联,该表达允许有密集的向量。意思很简单,嵌入的单词并没有真正捕捉到father的意思,但是它的表达将与mother的表达类似。

这是一个强大的特点,可以应用于各种情况。例如,可以解决下面这个问题:

 

什么对于父亲相当于母亲对于女儿?

第一个单词嵌入模型是 Word2Vec ,我们将用它来创建反向词典。这一模型对该领域进行了革新,并催生了许多其他模型,例如FastText、GloVe这些模型之间有着细微差别——例如GloVe和Word2Vec是在单词层面上训练,而FastText则是在character n-grams上训练的。但是它们的原理、应用以及结果都非常相似。

Word2Vec的工作原理

Word2Vec的有效性得益于下面这个技巧:针对特定任务训练神经网络,然后将这个神经网络用于其他用途。这个技巧并不是Word2Vec所独有的,这是机器学习中最常用的一个技巧。基本上只需训练神经网络来得到一个特定的输出,然后去掉输出层,只保留神经网络隐藏层的权重。

训练过程和往常一样:给神经网络一个输入值以及一个与输入值相对应的输出。这样,神经网络可以慢慢学习如何生成正确的输出。

这个训练任务是为了计算在给定输入单词的情况下,在上下文中出现某个单词的概率。例如,如果有programmer这个单词,那么在上下文的短语中看到computer这个单词的概率是多少?

Word2Vec训练策略

Word2Vec有两种典型的策略:CBOW和skip-gram——这两个策略是可逆的。用CBOW输入单词的上下文,在输出中得到想要的单词。用skip-gram则相反:先输入单词,然后预测它所适用的上下文。

在最基本的层面上,“context (上下文)”这个术语仅仅是指目标单词周围的词,比如目标单词前后的词。但是,“上下文”也可用于表达句法意义(例如,指主语,如果目标词是动词)。这里只采用“上下文”最简单的含义。

例如这句话:

the gentle giant graciously spoke

在CBOW中, 需要输入[gentle, graciously]来得到输出giant。在skip-gram中,我们将输入“输入giant”,同时输入“输出[gentle, graciously]”。训练是用单个单词完成的,所以,在实践中,对CBOW来说:

· 第一次,输入gentle,期待结果输出giant。

· 第二次,输入 graciously ,期待结果输出 giant。

如果用skip-gram,就要把输入和输出颠倒过来。

神经网络会带来什么

就像前面所说,训练的最后会删除输出层,因为我们并不真正关心一个单词出现在目标单词附近的概率。例如,当输入单词flag时,我们并不真正关心 USA会有多大的概率出现在上下文中。然而,我们将保留神经网络隐藏层的权重,并用它来表达单词。这有什么用呢?

这是网络结构的特点所致。输入是单词的one-hot编码。例如,dog使用[1,0,0 …]来表达。在训练期间,输出也是单词的one-hot编码(例如,对于the dog barks,使用CBOW应输入dog,输出barks)。最终,输出层将会有一系列可能性。例如,给定输入单词cat,输出层将有可能在cat的上下文中出现单词dog。pet这个单词也会有出现的可能性,依此类推。

Word2Vec的神经网络

每个神经元对每个单词都有权重,所以在训练结束时,将有无数个神经元,每个神经元对每个单词都有权重。此外,要记住,表达单词的输入向量除了一个分量是1外,其他都是零。

因此,在矩阵乘法的数学规则中,如果用神经元矩阵乘以一个输入向量,输入向量为0,则神经元中的大多数权重无效,每个神经元中,将剩下一个与输入单词相关联的权重。每个神经元中的一系列非零权重将是Word2Vec中表达单词的权重。

上下文相似的单词会得到相似的输出,所以在特定单词的上下文中出现的概率也相似。换句话说,dog和cat会得到相似的输出。因此,它们也将具有相似的权重。这就是为什么意义相近的单词在Word2Vec中用相近的向量来表示。

Word2Vec的判断相当简单:相似的单词会出现在相似的短语中。然而,这一点也相当有效。当然,前提是有足够大的数据集用于训练。稍后会处理这个问题。

Word2Vec的意义

现在知道了Word2Vec的工作原理,下面来看看怎么用它创建反向词典。反向词典是通过输入定义来查找单词的词典。所以,理想情况下,输入group of relative(一群亲戚),程序会给出family(家族)。

从显而易见的开始:同义词词典使用的是单词嵌入法。如前所述,在这个系统中,相似的词有相似的表达。因此,如果想要系统找到与输入单词相近的单词,它会找到一个意思相似的单词。例如,如果输入happiness,可能会输出joy。

由此,你也许认为,做与上面相反的事情也行得通,比如找到输入单词的反义词。很不幸,这是不可能直接实现的,因为表达单词的向量不能理解单词之间那么精确的关系。基本上,sad这个单词的向量与 happy这个词的向量并不是镜像相反。

为什么可以用Word2Vec

请看下面单词向量的简化表示。

单词关系的图形表达

系统可以找到由?表达的单词,仅仅因为它可以从father的向量中添加两个给定单词向量(mother 和 daughter)之间的差异。该系统确实能够捕捉到单词之间的关系,但是这种捕捉方式并不容易理解。换句话说:向量的位置是有意义的,但这也意味着这些位置的定义不是绝对的(例如,相反的),而是相对的(例如,一个看起来像是A-B的单词)。

这也解释了为什么无法直接找到反义词:在Word2Vec中没有可以用来描述这种关系的数学运算。

反向词典的工作原理

现在既然完全理解了单词向量的作用,那就可以来理解怎么用它们来创建一个反向词典了。反向词典基本可以用来查找到一个与输入内容(也就是定义)相似的单词。这是可以实现的,因为系统使用的是向量数学。

例如,如果输入 group of relatives,它就会找到 family。也可以在定义中使用否定的词来帮助识别一个词。例如,亲属团体决心组织起来。稍后将看到否定一个词的确切含义。也可以在定义中使用否定词来帮助识别单词。例如,将group of -relatives解析为organization。稍后将会解释否定一个单词有什么意义。

Word2Vec模型的数据

既然理论都清楚了,下面可以看看代码,然后创建反向词典。

第一步是创建字典。创建字典本身并不难,但是要花费很长时间。更重要的是,内容越多越好。对于普通用户来说,下载和存储大量数据并不轻松。仅英文维基百科的转储,提取时就可能需要超过50 GB(只包含文本)。通用爬网(可自由访问的已爬网页面)的数据可能会占用数千兆字节的存储空间。

出于实用考虑,最好使用谷歌共享的资料,例如谷歌新闻数据预先训练好的模型:GoogleNews-vectors-negative300.bin.gz。如果对这个文件进行搜索,很多地方都找得到。以前,谷歌代码项目是该文件的官方来源,但现在最好的来源似乎是谷歌硬盘。该文件是1.6GB的压缩版,不需要解压。

数据下载完成后,可以放在项目下的 models目录中。有许多针对Word2Vec的库,所以可以使用多种语言,但鉴于Python在机器学习中的流行度,本文会选择Python作为示例。本文将使用gensim库的Word2Vec,因为它是最优的。至于网络界面,则会使用Flask。

加载数据

首先,需要用3行简单的代码将Word2Vec加载至内存。

model = KeyedVectors.load_word2vec_format("./models/GoogleNews-vectors-negative300.bin.gz", binary=True)

model.init_sims(replace=True) #

model.syn0norm = model.syn0 # prevent recalc of normed vectors

第一行是唯一真正需要加载数据的行。另外两行只是在开始时做一些初步的计算,所以后面就不需要为每个请求做这些计算了。

但是,这3行代码在普通计算机上执行可能需要2-3分钟(用SSD)。一开始只需一些等待,所以这也不算太糟糕,但也不是很理想。另还有一种方法,就是可以一劳永逸地进行一些计算和优化,之后把它们保存起来,以后每次开始时,先从磁盘中加载。

添加这个函数来创建优化版的数据。

def generate_optimized_version():

model = KeyedVectors.load_word2vec_format("./models/GoogleNews-vectors-negative300.bin.gz", binary=True)

model.init_sims(replace=True)

model.save('./models/GoogleNews-vectors-gensim-normed.bin')

主函数每次都使用这段代码来加载Word2Vec的数据。

optimized_file = Path('./models/GoogleNews-vectors-gensim-normed.bin')

if optimized_file.is_file():

model = KeyedVectors.load("./models/GoogleNews-vectors-gensim-normed.bin",mmap='r')

else:

generate_optimized_version()

# keep everything ready

model.syn0norm = model.syn0 # prevent recalc of normed vectors

这将加载时间从几分钟缩短到几秒钟。

清理词典

还有一件事要做:清理数据。一方面,谷歌新闻模型很棒,它是由一个非常大的数据集生成的,所以非常准确。这要比自己创建模型好的多。然而,由于它是基于新闻来创建的,所以包含许多拼写错误,更重要的是,我们并不需要这个实体模型。

换句话说,它不仅仅包含单词,还包含新闻中提到的建筑物和机构名称等内容。这可能会阻碍反向词典的建立。例如,如果输入一个类似a tragic event的定义,系统可能会发现,与这组输入单词最相似的是发生过悲剧事件的地方。然而,这并不是我们想要的。

因此,必须对模型输出的所有项进行过滤,以确保输出的是字典中可以找到的常用单词。这类单词列表来自SCOWL(Spell Checker Oriented Word Lists:http://wordlist.aspell.net/)。使用这个链接中的工具创建了一个自定义词典,并将它放在了 models 文件夹中。

# read dictionary words

dict_words = []

f = open("./models/words.txt", "r")

for line in f:

dict_words.append(line.strip())

f.close()

# remove copyright notice

dict_words = dict_words[44:]

现在可以很容易地加载单词列表,来比较Word2Vec系统过滤掉的条目。

反向词典

反向词典的功能代码非常简单。

def find_words(definition, negative_definition):

positive_words = determine_words(definition)

negative_words = determine_words(negative_definition)

similar_words = [i[0] for i in model.most_similar(positive=positive_words, negative=negative_words, topn=30)]

words = []

for word in similar_words:

if (word in dict_words):

words.append(word)

if (len(words) > 20):

words = words[0:20]

return words

从用户的输入中可以收到对所查找单词的肯定性描述,也可以接收到否定词,必须从其他词中减去这些词的向量。

这一操作的用意更难理解:似乎并没有将这个词的反义词包含进来。相反,还想去掉否定词。例如,假设定义是group of -relatives,relatives是否定词, group 和of是肯定词。想要找到一个与输入内容中所标识的单词意思最接近的词,但是去除了所有 relatives特别添加的意义。

第5行中有与肯定的定义意思最相似的前30个单词。该函数会返回单词和相关分数,我们只对单词本身感兴趣,所以可以忽略分数。

其余的代码很容易理解。通过比较之前加载的词典单词数据库中的单词,从之前返回的单词中,选择出那些真正的单词,而不是地点或事件。将单词列表减少到最多20个单词。

清除输入的单词

这一步通过find_words实现。第2行和第3行中有一个determine words函数。这个函数从输入的字符串中生成一个单词列表。如果一个单词前面有负号,则是一个否定单词。

def determine_words(definition):

possible_words = definition.split()

for i in range(len(possible_words) - 1, -1, -1):

if possible_words[i] not in model.vocab:

del possible_words[i]

possible_expressions = []

for w in [possible_words[i:i+3] for i in range(len(possible_words)-3+1)]:

possible_expressions.append('_'.join(w))

ex_to_remove = []

for i in range(len(possible_expressions)):

if possible_expressions[i] in model.vocab:

ex_to_remove.append(i)

words_to_remove = []

for i in ex_to_remove:

words_to_remove += [i, i+1, i+2]

words_to_remove = sorted(set(words_to_remove))

words = [possible_expressions[i] for i in ex_to_remove]

for i in range(len(possible_words)):

if i not in words_to_remove:

words.append(possible_words[i])

return words

首先,这个函数会生成一个单词列表,还会添加表达式。这是因为谷歌新闻模型除了简单的单词外,还有短语的向量表达。我们试图通过简单地组合3-gram单词(即3个单词的滑动集合)来找到表达式。如果在谷歌新闻模型(第14行)中发现了表达式,则必须将该表达式作为一个整体添加,并删除单个单词。

网络App

为了便于使用,创建了一个简单的Flask应用程序:这是一个基本的网络界面,可以让用户输入定义并阅读单词列表。

def create_app():

app = Flask(__name__)

return app

app = create_app()

@app.route('/', methods=['GET', 'POST'])

def index():

if request.method == 'POST':

words = request.form['definition'].split()

negative_words = ''

positive_words = ''

for i in range(len(words)):

if(words[i][0] == '-'):

negative_words += words[i][1:] + ' '

else:

positive_words += words[i] + ' '

return jsonify(find_words(positive_words, negative_words))

else:

return render_template('index.html')

该应用程序支持根目录路径。它会显示带有定义的页面,并在收到定义时,输出相应的单词列表。多亏了Flask,用几行代码就能搞定这些。

正在运行的反向词典

反向词典运行得怎么样呢?它对于描述性定义非常有效,也就是说,如果使用的是对目标词进行描述的定义,就很可能会在应用程序返回的单词列表中找到正确的单词。它可能不是列表中的第一个单词,但肯定能在列表中找得到。但是对于逻辑性定义来说,它并不那么有效,例如female spouse。因此,这个词典当然不够完美,但对于简单的查找来说,它还是非常有效的。

总结

这篇文章用几行代码创建了一个有效的反向词典,这要归功于Word2Vec和现成的数据库。这对于一个小教程来说,是一个很好的结果。但是在机器学习方面,应该牢记:应用程序的成功不仅仅取决于代码。对于许多机器学习的应用程序来说,成功在很大程度上取决于拥有的数据,无论是使用的训练数据,还是向程序提供数据的方法,都很重要。

例如,对于数据集的选择,忽略了一些试验和错误。因为我们知道谷歌新闻数据集包含新闻事件,所以已经预料到了会存在一些问题。所以尝试基于英文维基百科建立自己的数据集。然而非常悲惨地失败了,得到的结果比使用谷歌新闻数据集得到的更糟糕。一部分原因在于数据集较小,但真正的原因在于我们的能力不如谷歌的工程师。选择合适的训练参数需要一种“黑魔法”,只有积累大量的经验才能掌握。最后,对我们来说,最好的解决方案就是使用谷歌新闻数据集,并过滤输出,以消除应用程序不需要的结果。虽然这一点让人感到羞愧,但还是值得牢记于心的。

留言 点赞 关注

我们一起分享AI学习与发展的干货
欢迎关注全平台AI垂类自媒体 “读芯术”

(添加小编微信:dxsxbb,加入读者圈,一起讨论最新鲜的人工智能科技哦~)


转载:https://blog.csdn.net/duxinshuxiaobian/article/details/101976059
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场