想系统学一下NLP,所以再开个新坑,如果有天我累死了,一定不要奇怪,都是自找的
一不小心写得小作文了,文章里不会所有概念都仔细说,但尽量会给参考链接,会对视频课程做一些延申。
给你们翻译一下这张图:
A:好吧,我可以不在乎!
B:我觉得你在说,你无法不在乎, 你说你可以不在乎,表示你至少是有一点在乎的。
A:我不知道。
A:我们是在虚空中漂流的复杂得难以置信的大脑,在一片黑暗中盲目地抛出文字,徒劳地想要通过这种方式和别人产生联系。(有没有觉得有点莎士比亚)
A:每一个关于措辞,拼写,语气,时间的选择都包含了无尽的信号,意义,和潜台词。而每个人都会用自己的方式来理解。语言不是一个规范的系统,语言是瑰丽又混沌的。
A:你永远无法明确知道任何一段文字对任何一个人的真正意义。
A:你可以做的,是尝试更好的揣测自己的语言会对别人造成的影响,那样,你才能能组词措句让他们感受到你想让他们感受的。其他都是毫无意义的。
A:我想,你告诉我你怎么理解我的话,是想让我觉得不那么孤单。如果是这样,感谢你。这对我来说意义非凡。
A:但如果你只是在用一些心理学的技巧在分析我说的话,来显示你知道很多。
A:那我想我不必在意。
这幅图是教授用来开题的,语言中带有很多不确定性,甚至说话的人都有可能受潜意识驱动,并不清楚自己真正想要表达的,而我们每个人对每句话可能都有不同的理解,语言是个瑰丽,混沌,模糊的系统,是一抹精致的灰。而不是逻辑严密,规整清晰的。《小王子》里也有一句很类似的话:”…..你先坐在草地上,离我稍微远些,就像这样。我从眼角里瞅着你,你什么也不要说。话语是误解的根源。但是,每天,你都可以坐得离我稍微近一些……”
在这里延申一个概念,图灵测试,图灵测试是英国计算机科学家图灵于1950年提出的,注意到“智能”这一概念难以确切定义,他提出了著名的图灵测试:如果一台机器能够与人类展开对话(通过电传设备)而不被辨别出其机器身份,那么称这台机器具有智能。因此,我们也可以这样理解图灵测试,它定义“智能”为掌握了语言,包括理解和应用。
要理解语言,首先我们要学字,词,计算机也一样。对于我们而言,每个单词,每个字都是有意思的,那么计算机要怎么也能让每个字在它那里有个意思呢?换句话说,我们要怎么将每个词的意思转换成计算机能读的形态。这样说可能还是觉得模糊,我们来看看一些已经有的一些解决方案来理解一下:
1. WordNet
WordNet的使用方法和一些基本概念可以参考[2],这里简单说一下他的基本概念,英文中一个单词可以有很多词性,比如一个单词,可以是名词(noun),形容词(adj),副词(adv),动词(verb)。WordNet用下面这种结构来表达词义:
比如说dog既可以做名词用,又可以做动词用,那么dog就会被分为名词和动词两类,而这两类里又有不同的词义,dog做名词用时,可以有不同的涵义,而每个含义我们都给它一个符号表示。所以一个单词的一种具体含义就用“单词.词性.词义序号”这种方式来表达,比如:dog.n.01表示dog作为名词时的一种含义。下面这段代码就不难理解了:
from nltk.corpus import wordnet as wn
#将所有词性缩写映射到词性名词上。
poses = { 'n':'noun', 'v':'verb', 's':'adj (s)', 'a':'adj', 'r':'adv'}
#找出good这个词的每个词义
for synset in wn.synsets("good"):
#打印出每个有这个词义的单词
print("{}: {}".format(poses[synset.pos()],", ".join([l.name() for l in synset.lemmas()])))
运行发现,good可以有很多种含义,而大多数含义不是只有good才有,大多数含义都可以用其他单词表达:
再看一个WordNet的例子:
from nltk.corpus import wordnet as wn
#panda可以有两种意思,一种是giant_panda.n.01指大熊猫
#一种是lesser_panda.n.01指小熊猫,
#panda.n.01和giant_panda.n.01是一个意思
#这里panda得到的是giant_panda.n.01这个值
panda = wn.synset("panda.n.01")
#求上位词,比如panda.n.01的上位词是procyonid.n.01
hyper = lambda s: s.hypernyms()
#循环找自己的上位词
list(panda.closure(hyper))
panda = wn.synset("panda.n.01")
得到的结果如下,可以打印出大熊猫一层一层往上所属的类,可以发现WordNet是树状的,知识性的,明确的,规整的:
那理解了WordNet整理语言的方式,那么我们来看一下它的缺点:
-
我们开始提过,语言是混沌的,模糊的,不是规范的,规整的,但是WordNet的这种组织方式,非常规整,导致它并不能展现词与词之间的微妙差别,比如在WordNet里proficient和good可以表达同样的意思,但如果是同样的场景和上下文,用proficient和good其实是有细微差别的,而WordNet是无法表现这种差别的。
-
另外就是,这种结构是人为规范的,需要人的介入,时时更新,整理出新的词和词义。
-
因为是人整理的,所以这种结构是主观的,是出于人对语言的理解。
-
需要人力。
-
无法计算词与词之间的相似性,或者差别。
有这么多缺陷,所以啊,得想个新法子,我们看下一个。
2. One-Hot
每个单词都用一个向量来表示,每个单词在向量里都有一个专属的位置,比如说at这个单词,那么就用[0,0,1,0,…,0]这个向量来表示,即第三个位置表示at,所以第三个位置值为1,其他位置的值都是0,之所以这个方法名字叫one-hot,就是因为向量里只有一个位置的值是1,其他位置的值都为0。
但这个方法的缺陷也尤为明显:
- 单词量还是挺大的,比如如果我们有10000个单词,那就得用10000个长度为10000的向量来表示这个系统。而且大多数位置还是0,特别稀疏,非常浪费。
- 另外,就是所有的向量在数学层面来说都是正交的,这样的方式无法表达词的意思,也无法计算词与词之间的相似度。
3. Word2Vec
“You shall know a word by the company it keeps” (J. R. Firth 1957: 11)
中国有句古话,物以类聚,人以群分,上面这句话的意思就是,你想知道一个词是什么意思吗?那看看出现在它周边的单词就知道了。这个想法是1957年由一个语言学家提出来的。
根据这个想法,产生的一个概念叫分布语义:
Distributional semantics: A word’s meaning is given by the words that frequently appear close-by.
一个词的意思是由周边经常出现的词决定的。
比如我们要表示banking这个词,我们就用围绕着banking的上下文,前前后后出现的单词来表示。
这个想法似乎有点古怪,不太能想通,也显得不太可靠,应该不管用吧,但事实是按照这个想法设计的算法,产生的词义,能很好的表达单词的意思。超乎预料的管用。 我们先不管算法,看看最后的结果可以是怎么样的:
比如banking这个词,就可以是一个长度为8的向量(这个长度可以根据单词量,样本量和精确度需求来决定,这里只是举个例子),每个维度应当都表达了一层意思,但这个意思不是人为去定义的,而在每个维度上的值就组成了banking的整体意思。
我们可以把一个向量想象成一个高维度的空间里的一个位置,比如说一个二维的向量,就可以表示一个平面上的点,一个三维的向量,则可以表示我们生活的这个三维空间里的一个点,一个四维的向量,立体空间再加上时间这个维度就是四维的。那么如果我们用一个长度为8的向量去表示一个词,则可以理解成,在一个高维度(八维)的空间里,banking这个词有个位置,它所在的位置即是它的意思。而在这个八个维度的空间里,每个在训练样本里的单词,都会有一个位置。比如像下图一样:
上面只是一个意思意思的示意图,因为超过两维的,咱画不出来。
Word2Vec就是运用这个概念的算法,那我们来看算法具体的思路:
翻译如下:
- 我们有一个语料库,里面有很多文章或者对话,总之是语言。- 每个单词我们都用一个向量来表示(实际算法一般是两个向量,一个中心词向量,一个周边词向量),每个单词的向量维度是一致的,刚开始的时候,向量里的值是随机赋予的随机数。
- 遍历每篇文章里的每个位置,经过那个位置的时候,在那个位置上的词为中心词c,周边的词是o。
- 利用c和o的相似度来计算给出c能得到o的概率,即c发生的条件下,o发生的概率P(o|c),或者反过来P(c|o)也行。
- 调整词向量里的值来提高P(o|c)或P(c|o)。
- 循环重复之前的三个步骤,直到P(o|c)或者P(c|o)无法再提高。
举个例子如下图:
比如中心词为‘into’的时候,而临近词的区域设定为2的时候,就需要计算当into出现时,那些在区间内出现的词会出现概率。
计算完后,再以下一个单词为中心词,重复上面的过程。
条件概率的计算是一个softmax的方程(具体可以看Softmax):
vc是中心词向量,uo是周边词向量,V是所有单词的集合,uw是V中的单词。
这个问题的极大似然函数:
𝜃指所有可以调整的参数,这个任务里指所有词向量里的所有值,这里每个词有两个词向量,一个u(作周边词的时候用),一个v(作中心词的时候用):
T指所有位置的集合,经过一个位置时,位置上的词为中心词wt,周边的词为wt+j,m限制了周边词的范围,L(𝜃)求的是在参数为𝜃的情况下,遍历所有的文本位置,求每个中心词和周边词的条件概率,再把它们乘起来。得到的条件概率越大,L(𝜃)越大。而我们要做的是调整𝜃的值,使得L(𝜃)获得最大值。
损失函数的定义:
将最大似然函数调整一下,就可以得到通常会用的损失函数,最小化J(𝜃)和最大化L(𝜃)本质上是在做同一件事。
那如何根据损失函数来调整𝜃呢,求导,做梯度下降:
上面的公式是示意公式,实际上是对单个参数分别求导,做梯度下降:
过程如下图所示:
每次对𝜃做细微的调整,直到𝜃接近自己的最优值,即可以使L(𝜃)最小的值。上图中Cost即L(𝜃)。关于梯度下降的具体操作和方法细节,之后的笔记会做介绍。
最后我们来看看,怎么用别人训练好的Word2Vec做一些事情:
import numpy as np
# Get the interactive Tools for Matplotlib
%matplotlib notebook
import matplotlib.pyplot as plt
plt.style.use('ggplot')
from sklearn.decomposition import PCA
from gensim.test.utils import datapath, get_tmpfile
from gensim.models import KeyedVectors
from gensim.scripts.glove2word2vec import glove2word2vec
gensim是一个工具包,可以让我们导入训练好的模型参数,斯坦福有一个GloVe项目(https://nlp.stanford.edu/projects/glove/),在里面可以下载训练好的词向量:
#将下载的词向量文件放在合适的目录,并读入
glove_file = datapath('D:/glove.6B.100d.txt')
word2vec_glove_file = get_tmpfile("glove.6B.100d.word2vec.txt")
glove2word2vec(glove_file, word2vec_glove_file)
model = KeyedVectors.load_word2vec_format(word2vec_glove_file)
接下来,我们就可以做一些事情了,比如找近义词:
>>model.most_similar('obama')
[('barack', 0.937216579914093),
('bush', 0.927285373210907),
('clinton', 0.896000325679779),
('mccain', 0.8875633478164673),
('gore', 0.8000321388244629),
('hillary', 0.7933662533760071),
('dole', 0.7851964831352234),
('rodham', 0.7518897652626038),
('romney', 0.7488929629325867),
('kerry', 0.7472624182701111)]
比如找反义词:
>>model.most_similar(negative='banana')
[('shunichi', 0.49618104100227356),
('ieronymos', 0.4736502170562744),
('pengrowth', 0.4668096601963043),
('höss', 0.4636845588684082),
('damaskinos', 0.4617849290370941),
('yadin', 0.4617374837398529),
('hundertwasser', 0.4588957726955414),
('ncpa', 0.4577339291572571),
('maccormac', 0.4566109776496887),
('rothfeld', 0.4523947238922119)]
比如找对应的词:
# king-woman 近似于 queen-man
>>model.most_similar(positive=['woman', 'king'], negative=['man'])[0]
('queen', 0.7698540687561035)
>>model.most_similar(positive=['australia', 'japanese'], negative=['japan'])[0]
('australian', 0.8923497796058655)
>>model.most_similar(positive=['france', 'beer'], negative=['australia'])[0]
('champagne', 0.6480063796043396)
>>model.most_similar(positive=['reagan', 'clinton'], negative=['obama'])[0]
('nixon', 0.7844685316085815)
>>model.most_similar(positive=['bad', 'fantastic'], negative=['good'])[0]
('terrible', 0.7074226140975952)
# 找出非我族类
>>model.doesnt_match("breakfast cereal dinner lunch".split())
'cereal'
可以看出来,Word2Vec使每个词都有了一个可以让计算机计算的独一无二的意思。而这个意思是非常非常接近人的理解的。而数值的复杂性和不可解释性也恰恰符合了语言混沌的特点。
那么这到底是不是最优解呢?我想这还需要时间来回答。但很显然,现在自然语言处理离强人工智能还有很远的距离。但毫无疑问,对于这个答案的探索,对人类本身的意义也是非常的。
第一课笔记结束啦,觉得有用就关注吧,右下角的‘在看’不要忘记点哦。
参考:
[1] http://web.stanford.edu/class/cs224n/
[2] TinaSmile,手把手教你NLTK WordNet使用方法,IT人,https://iter01.com/521234.html
Comments