目录
NLP常见任务
NLP处理方法
词编码需要保证词的相似性
简单 词/短语 翻译
向量空间子结构
在计算机中表示一个词
离散表示: One-hot表示
离散表示: Bag of Words
离散表示: Bi-gram和N-gram
语言模型
N元模型
离散表示的问题
分布式表示 (Distributed representation)
共现矩阵 (Cocurrence matrix)
SVD降维
NNLM (Neural Network Language model)
NNLM: 结构
NNLM的计算复杂度
word2vec: CBOW(连续词袋)
CBOW: 层次Softmax
CBOW: 负例采样
Word2Vec: Skip-Gram模型
Word2Vec: 存在的问题
总结
工具google word2vec
工具gensim
1.准备数据与预处理
利用Python实现wiki中文语料的word2vec模型构建
自动摘要: 如百度
指代消除: 小明放学了,妈妈去接他
机器翻译: 小心地滑===> slide carefully
词性标注: heat(v.) water(n.) in(p.) a(det.) pot(n.)
分词(中文、日文等) 大水沟/很/难/过
主题识别
文本分类
.....
传统: 基于规则 ,比如dict
现代:基于统计机器学习HMM, CRF, SVM, LDA, CNN… “规则”隐含在模型参数里
比如上面的frog(青蛙)、litoria(雨滨蛙) 、leptodactylidae(蟾蜍)。。。等,它们是有词的相似性的。
向量空间分布的相似性
也就是在向量空间中,翻译的相似性。比如two所在的向量坐标和dos的是一致的。
最终目标:词向量表示作为机器学习、特别是深度学习的输入和表示空间
其实就是dict
语料库
John likes to watch movies. Mary likes too.
John also likes to watch football games.
词典
{"John": 1, "likes": 2, "to": 3, "watch": 4, "movies": 5, "also": 6, "football": 7, "games": 8, "Mary": 9, "too": 10}
One-hot表示
John: [1, 0, 0, 0, 0, 0, 0, 0, 0, 0]
likes: [0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
too : [0, 0, 0, 0, 0, 0, 0, 0, 0, 1]
…
❖ 词典包含10个单词,每个单词有唯一索引
❖ 在词典中的顺序和在句子中的顺序没有关联
文档的向量表示可以直接将各词的词向量表示加和
John likes to watch movies. Mary likes too ==>[1, 2, 1, 1, 1, 0, 0, 0, 1, 1] // like有2个.
John also likes to watch football games==>[1, 1, 1, 1, 0, 1, 1, 1, 0, 0]
词权重 词在文档中的顺序没有被考虑
Binary weighting 短文本相似性
John likes to watch movies. Mary likes too ==>[1, 1, 1, 1, 1, 0, 0, 0, 1, 1] // like有2个.仍被视为1
TF-IDF(Term Frequency - Inverse Document Frequency)信息检索
词t的IDF weightN: 文档总数, nt: 含有词t的文档数,参看第一节,也就是词t出现的文档次数越多,权重越低
为2-gram建索引:(即把相邻的两个单词放在一起)
"John likes” : 1,
"likes to” : 2,
"to watch” : 3,
"watch movies” : 4,
"Mary likes” : 5,
"likes too” : 6,
"John also” : 7,
"also likes” : 8,
“watch football”: 9,
"football games": 10,
用于解决I LOVE YOU和YOU LOVE I这种在前面的方式中是一样的TF-IDF,但它们的含义明显不同。
John likes to watch movies. Mary likes too. ===>[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],即第1位是有John likes 第二位是有likes to....
John also likes to watch football games.===>[0, 1, 1, 0, 0, 0, 1, 1, 1, 1],即第二位是有likes to,第三位是有to watch....
优点: 考虑了词的顺序
缺点: 词表的膨胀
以下就是相对应的dict的数量级,可以看到,随着n的增大,变化是很大的
一句话 (词组合) 出现的概率
比如“我爱北京天安门" 出现的几率是我的几率,爱的几率,北京的几率,天安门的几率一起计算。
表示 a1a2···an。
示例1: = (1 + 2)(2 + 2)(3 + 2)(4 + 2) = 3 × 4 × 5 × 6 = 360
示例2:M=A0×A1×A2×…×An-1 → M=
Unigram/1-gram
P(Mary likes too) = P(too | Mark, likes) * P(likes | Mary) * P(Mary) = P(too) * P(likes) * P(Mary) // 因为word之间没有前后的依赖关系
Bi-gram/2-gram
P(Mary likes too) = P(too | Mark, likes) * P(likes | Mary) * P(Mary) = P(too | likes) * P(likes | Marry) * P(Mary)// 需要考虑likes too 以及Marry likes这种前后关系。
以下对公式再次解释:
设wi是文本中的任意一个词,如果已知它在该文本中的前两个词 wi-2wi-1,便可以用条件概率P(wi|wi-2,wi-1)来预测wi出现的概率。这就是统计语言模型的概念。一般来说,如果用变量W代表文本中一个任意的词序列,它由顺序排列的n个词组成,即W=w1w2...wn,则统计语言模型就是该词序列W在文本中出现的概率P(W)。利用概率的乘积公式,P(W)可展开为:
P(W) = P(w1)P(w2|w1)P(w3| w1 w2)...P(wn|w1 w2...wn-1)
不难看出,为了预测词wn的出现概率,必须知道它前面所有词的出现概率。从计算上来看,这种方法太复杂了。如果任意一个词wi的出现概率只同它前面的两个词有关,问题就可以得到极大的简化。 这时的语言模型叫做三元模型 (tri-gram):
P(W)≈P(w1)P(w2|w1)∏i(i=3,...,nP(wi|wi-2w-1)
符号∏i i=3,...,n P(...) 表示概率的连乘。一般来说,N元模型就是假设当前词的出现概率只同它前面的N-1个词有关。重要的是这些概率参数都是可以通过大规模语料库来计算的。比如三元概率有
P(wi|wi-2wi-1) ≈ count(wi-2wi-1wi) /count(wi-2wi-1)
式中count(...) 表示一个特定词序列在整个语料库中出现的累计次数。
统计语言模型有点像天气预报中使用的概率方法,用来估计概率参数的大规模语料库好比是一个地区历年积累起来的气象记录。而用三元模型来做天气预报,就好比是根据前两天的天气情况来预测今天的天气。天气预报当然不可能百分之百准确,但是我们大概不会因此就全盘否定这种实用的概率方法吧。
无法衡量词向量之间的关系
酒店[0, 1, 0, 0, 0, 0, 0, 0, 0, 0]
宾馆[0, 0, 0, 0, 1, 0, 0, 0, 0, 0] 各种度量(与或非、 距离) 都不合适
旅舍[0, 0, 0, 0, 0, 0, 0, 0, 1, 0]
太稀疏, 很难捕捉文本的含义
很难判断这三个词其实是有类似的含义的。用各种度量来表示都不合适。因为在做这个编码的时候就丢掉了一部分信息了。
1.词表维度随着语料库增长膨胀2.n-gram词序列随语料库膨胀更快3. 数据稀疏问题
用一个词附近的其他词来表示该词
“You shall know a word by the company it keeps”(J. R. Firth 1957: 11)
现代统计自然语言处理中最有创见的想法之一。
比如你不理解banking是什么意思,但你如果在几千条语句中见过banking,你就能大致理解它的意思了。因为它周边的词可以大致反映它是和哪些词相关的。
Word - Document 的共现矩阵主要用于发现主题(topic), 用于主题模型, 如LSA (Latent Semantic Analysis)
如果不了解你的收入水平,那就找和你经常吃火锅,大排档的那些附近的人了解下他们的收入水平,就能大致评估出你的生活水平了。
共现差不多就是共同出现的意思了。
局域窗中的Word - Word 共现矩阵可以挖掘语法和语义信息。也就是共现的范围有多大,比如以下三句话:
I like deep learning.
I like NLP.
I enjoy flying.
window length设为1(一般设为5~10),1意为你把范围设置为最小了,紧邻你的。
使用对称的窗函数(左右window length都为1),比如第一名的like,I和deep都是紧邻着的,距离都为1
这是个对称矩阵,比如I like出现在2次, I enjoy只出现了1次。
从这个矩阵我们可以发现I 和like、enjoy是紧邻的,所以我们可以猜测like、enjoy是有共性的。
存在的问题
将共现矩阵行(列)作为词向量
1.向量维数随着词典大小线性增长
2. 存储整个词典的空间消耗非常大
3.一些模型如文本分类模型会面临稀疏性问题
4.模型会欠稳定
构造低维稠密向量作为词的分布式表示 (25~1000维)!
最直接的想法: 用SVD对共现矩阵向量做降维:
import numpy as np
import matplotlib.pyplot as plt
la = np.linalg
words = ["I","like","enjoy","deep","learning","NLP","flying","."]
print(words)
X = np.array([[0,2,1,0,0,0,0,0],
[2,0,0,1,0,1,0,0],
[1,0,0,0,0,0,1,0],
[0,1,0,0,1,0,0,0],
[0,0,0,1,0,0,0,1],
[0,1,0,0,0,0,0,1],
[0,0,1,0,0,0,0,1],
[0,0,0,0,1,1,1,0]])
print(X)
U, s, VH = la.svd(X,full_matrices=False)
plt.axis([-0.8,0.2,-0.8,0.8])
for i in range(len(words)):
plt.text(U[i,0],U[i,1],words[i])
plt.show()
画图需要matplotlib,可以pip install matplotlib.
画图的时候横坐标和纵坐标范围要确定plt.axis([Xmin,Xmax,Ymin,Ymax])
使用语句plt.show()才会显示出图
上图我们可以看到I到like和enjoy的距离是差不多的
SVD降维的问题
1.计算量随语料库和词典增长膨胀太快, 对X(n,n)维的矩阵, 计算量O(n^3)。 而对大型的语料库,n~400k, 语料库大小1~60B token。
2.难以为词典中新加入的词分配词向量。
3.与其他深度学习模型框架差异大。
直接从语言模型出发, 将模型最优化过程转化为求词向量表示的过程
使用了非对称的前向窗函数, 窗长度为n-1
滑动窗口遍历整个语料库求和, 计算量正比于语料库大小
概率P满足归一化条件, 这样不同位置t处的概率才能相加, 即
你有一个语料库corpus,你从这个语料库中通过滑动窗口取出若干个资源4-gram,你要做的事情无非是用前面3个词去预估第4个词。我们可以假定上图的wt-n+1为w1,wt-2为w2,wt-3为w3,去预测w4(最上方的)output。
它的输入是一个one-hot表示,参考前面。假如说我找了最常用的10万个词语,把它们编码成一个行向量,那么w1,w2,w3只是这么某一个位置为1的一个行向量。
下一步(Table look-up in C)就是采用线性映射把one-hot表示投影到稠密D维表示。比如D为300,那么C就是10万个300维的列向量。
C矩阵*one-hot行向量,可以得到如上图右下角的标红列,一列(300维),因为其他列都是0,所以叫投影。
所以下一步就是w1,w2,w3分别和C矩阵相乘得到3列,每列都是300维的.
然后tanh就是把这3列拼接在了一起,也就是变成了900维, tanh自身有一个隐藏层(hidden layer) 比如是500维。
900维和500维相乘,最后得到softmax为10万维的向量,这个向量只有一个位置为1,也就是w4
如下图:
简单的说,就是我、喜欢、机器学习等上下文词序得到学习这一结果。
这个算法刚好相反,通过学习来预测周边的词:我、喜欢、机器学习。
怎么采样呢?
1.对每个local context window单独训练, 没有利用包含在global co-currence矩阵中的统计信息。
2.对多义词无法很好的表示和处理, 因为使用了唯一的词向量。
❖ 离散表示
• One-hot representation, Bag Of Words Unigram语言模型
• N-gram词向量表示和语言模型
• Co-currence矩阵的行(列)向量作为词向量
❖ 分布式连续表示
• Co-currence矩阵的SVD降维的低维词向量表示
• Word2Vec: Continuous Bag of Words Model
• Word2Vec: Skip-Gram Model
地址
https://code.google.com/archive/p/word2vec/
墙内用户请戳https://github.com/dav/word2vec
安装步骤
git clone https://github.com/dav/word2vec
cd word2vec/src
Make
试试./demo-word.sh 和./demo-phrases.sh
word2vec需要linux环境。
C:\Users\Administrator>python -m pip install gensim
Collecting gensim
Downloading https://files.pythonhosted.org/packages/d1/8d/f20e715f3eae5a277b13a31d440d65f294fadbc2047c4d02226e1de05b6e/gensim-3.5.0.tar.gz (22.9MB)
100% |████████████████████████████████| 22.9MB 280kB/s
Requirement already satisfied: numpy>=1.11.3 in c:\python\python37\lib\site-packages (from gensim) (1.15.0)
Collecting scipy>=0.18.1 (from gensim)
首先需要一份比较大的中文语料数据,可以考虑中文的维基百科(也可以试试搜狗的新闻语料库)。中文维基百科的打包文件地址为 https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2
首先需要一份比较大的中文语料数据,可以考虑中文的维基百科(也可以试试搜狗的新闻语料库)。中文维基百科的打包文件地址为
https://dumps.wikimedia.org/zhwiki/latest/zhwiki-latest-pages-articles.xml.bz2
中文维基百科的数据不是太大,xml的压缩文件大约1G左右。首先用 process_wiki_data.py处理这个XML压缩文件,执行:python process_wiki_data.py zhwiki-latest-pages-articles.xml.bz2 wiki.zh.text
python process_wiki_data.py如下:
#用于解析XML,将XML的wiki数据转换为text格式
import logging
import os.path
import sys
import warnings #过滤掉gensim的警告
warnings.filterwarnings(action='ignore',category=UserWarning,module='gensim')
from gensim.corpora import WikiCorpus
if __name__ == '__main__':
sys.argv = [sys.argv[0],"zhwiki-latest-pages-articles.xml.bz2","wiki.zh.text"]
program = os.path.basename(sys.argv[0])
logger = logging.getLogger(program)
# %(asctime)s:打印日志的时间
# %(levelname)s:打印日志级别的名称
# %(message)s:打印日志信息
logging.basicConfig(format="%(asctime)s: %(levelname)s: %(message)s")
logging.root.setLevel(level = logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))
# check and process input arguments
if len(sys.argv) < 3:
print (globals()['__doc__'] % locals())
sys.exit(1)
inp,outp = sys.argv[1:3]# 取argv[1]、argv[0], name[n:m]切片是不包含后面那个元素的值(顾头不顾尾)
space = " "
i = 0
output = open(outp, 'w',encoding="utf-8")
wiki = WikiCorpus(inp, lemmatize=False,dictionary={})
for text in wiki.get_texts():#通过get_texts()将维基里的每篇文章转换为1行text文本,并且去掉了标点符号等内容
output.write(space.join(text) + "\n")
i = i + 1
if (i % 10000 == 0):
logger.info("Saved " + str(i) + " articles")
output.close()
logger.info("Finished Saved " + str(i) + " articles")
这里直接在IDE中设置了cmd参数:
sys.argv = [sys.argv[0],"zhwiki-latest-pages-articles.xml.bz2","wiki.zh.text"]
同时要注意以utf-8的方式打开:
output = open(outp, 'w',encoding="utf-8")
因为windows下打开文件默认是gbk编码的,直接open会报错:
UnicodeEncodeError: 'gbk' codec can't encode character '\xf6' in position 892: illegal multibyte sequence
运行结果:
编译完后的wiki.zh.text大约1.04G,如果大小相差太远,请重新运行。
Python的话可用jieba完成分词,生成分词文件wiki.zh.seg.text
jieba_wiki_data.py:
#逐行读取文件数据进行jieba分词
import jieba
import jieba.analyse
import jieba.posseg as psag
import codecs,sys
if __name__ == '__main__':
f = codecs.open("wiki.zh.text", 'r', encoding='utf-8')
target = codecs.open("wiki.zh.seg.text", 'w', encoding='utf-8')
lineNum = 1
line = f.readline()
while line:
print ("---processing",lineNum,"article----")
seg_list = jieba.cut(line, cut_all=False)
line_seg = ' '.join(seg_list)
target.writelines(line_seg)
lineNum = lineNum + 1
line = f.readline()
print("well done")
f.close()
target.close()
运行结果:
......
---processing 320661 article----
---processing 320662 article----
---processing 320663 article----
well done
请按任意键继续. . .
编译完后的wiki.zh.seg.text大约1.29G,如果大小相差太远,请重新运行。
接着用word2vec工具训练:
train_word2vec_model.py:
#用于训练模型
import logging
import os.path
import sys
import multiprocessing
import warnings #过滤掉gensim的警告
warnings.filterwarnings(action='ignore',category=UserWarning,module='gensim')
from gensim.corpora import WikiCorpus
from gensim.models import Word2Vec
from gensim.models.word2vec import LineSentence
if __name__ == '__main__':
sys.argv = [sys.argv[0],"wiki.zh.seg.text","wiki.zh.text.model","wiki.zh.text.vector"]
program = os.path.basename(sys.argv[0])
logger = logging.getLogger(program)
# %(asctime)s:打印日志的时间
# %(levelname)s:打印日志级别的名称
# %(message)s:打印日志信息
logging.basicConfig(format='%(asctime)s: %(levelname)s: %(message)s')
logging.root.setLevel(level=logging.INFO)
logger.info("running %s" % ' '.join(sys.argv))
# check and process input arguments
if len(sys.argv) < 4:
print (globals()['__doc__'] % locals())
sys.exit(1)
# inp为输入语料,outp1为输出模型,outp2为原始c版本word2vec的vector格式的模型
inp,outp1,outp2 = sys.argv[1:4]# 取argv[1]、argv[2]、argv[3], name[n:m]切片是不包含后面那个元素的值(顾头不顾尾)
#训练skip-gram模型
model = Word2Vec(LineSentence(inp), size=400, window=5, min_count=5, workers=multiprocessing.cpu_count())#不要用小写的word2vec
#保存模型
model.save(outp1)
model.wv.save_word2vec_format(outp2,binary=False)#注意model.save_word2vec_format已弃用.Use model.wv.save_word2vec_format instead
得到4个文件:
测试模型效果:
import warnings #过滤掉gensim的警告
warnings.filterwarnings(action='ignore',category=UserWarning,module='gensim')
import gensim
if __name__ == '__main__':
model = gensim.models.Word2Vec.load("wiki.zh.text.model")
result = model.wv.most_similar(u"足球")
for e in result:
print (e[0],e[1])
运行结果:
足球运动 0.5750191807746887
排球 0.5621518492698669
手球 0.5414919257164001
板球 0.5399782657623291
冰球 0.5385146737098694
籃球 0.515253484249115
足球联赛 0.5124732255935669
美式足球 0.5073696374893188
英超球 0.5059894323348999
棒球 0.495814710855484