前言
计算机处理图像的时候,本质上利用了图像的像素数值信息。对自然语言来说,并没有包含计算机擅长处理的数值信息,因此,需要通过一定的手段将“自然语言”量化,进而利用已有的机器学习方法对自然语言进行处理、分析和预测。
独热编码
也称One-hot编码 (一位有效码),其方法是使用N位状态寄存器来对N个状态进行编码,每个状态都有它独立的寄存器位,并且在任意时候,其中只有一位有效。比如现在有下面的状态表:
indicator-1 | indicator-2 | |
---|---|---|
sample-1 | 1 | 1 |
sample-2 | 2 | 2 |
sample-3 | 2 | 3 |
转化为独热编码为(注意这里容易和数字电路中的真值表编号相混淆,请务必区分开来)
indicator-1 | indicator-2 | |
---|---|---|
sample-1 | 0 1 | 0 0 1 |
sample-2 | 1 0 | 0 1 0 |
sample-3 | 1 0 | 1 0 0 |
如果上面的indicator不是数值量,而是文本,则可以表示为:
indicator-1 | indicator-2 | |
---|---|---|
sample-1 | “male” | “from Europe” |
sample-2 | “female” | “from US” |
sample-3 | “female” | “from Russia” |
对应的独热编码可以编号为:
indicator-1 | indicator-2 | |
---|---|---|
sample-1 | 0 1 | 0 0 1 |
sample-2 | 1 0 | 0 1 0 |
sample-3 | 1 0 | 1 0 0 |
这样处理的优缺点在哪?
首先是优点,很显然,将一个word映射为一个数学向量,这便与我们后续进行各种机器学习算法的输入处理。
缺点也很明显:
- 它是一个词袋模型,不考虑词与词之间的顺序(文本中词的顺序信息也是很重要的)
- 它假设词与词相互独立(在大多数情况下,词与词是相互影响的)
- 它得到的特征是离散稀疏的
在数据结构或离散数学中,有稀疏矩阵的概念,也就是说这个矩阵蕴含的有效信息很少,大部分矩阵元素都为0. 上面的独热编码正是特殊的1 x n维的稀疏矩阵,这样就非常浪费空间,容易造成维度灾难。因此,我们急切需要一种可以降低维度的方法。
Dristributed representation
Dristributed representation 的思路是通过训练,将每个词都映射到一个较短的词向量上来。所有的这些词向量就构成了向量空间,用普通的统计学的方法来研究词与词之间的关系。词向量维度一般在训练时指定。下图说明了一个简单示例:
词汇表里的词用"Royalty",“Masculinity”, "Femininity"和"Age"4个维度来表示,King这个词对应的词向量可能是(0.99,0.99,0.05,0.7)(0.99,0.99,0.05,0.7)。如果参加过数学建模,你可能对此有些熟悉,因为其表示方式与因子分析法相似。
上面,我们将king这个词从一个可能非常稀疏的向量坐在的空间,映射到现在这个四维向量所在的空间,这个过程就是词嵌入(word embedding).
假如我们将词的维度降低到2D,有研究表明:
国王 - 男性 + 是女性 = 女王,挺有道理的
出现这种现象的原因是,我们得到最后的词向量的训练过程中引入了词的上下文。
Word2Vec (一个神经网络模型)
输入是One-Hot Vector,Hidden Layer没有激活函数,也就是线性的单元。Output Layer维度跟Input Layer的维度一样,用的是Softmax回归。当这个模型训练好以后,我们并不会用这个训练好的模型处理新的任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐藏层的权重矩阵。
这个模型是如何定义数据的输入和输出呢?一般分为CBOW(Continuous Bag-of-Words 与Skip-Gram两种模型。CBOW模型的训练输入是某一个特征词的上下文相关的词对应的词向量,而输出就是这特定的一个词的词向量。 Skip-Gram模型和CBOW的思路是反着来的,即输入是特定的一个词的词向量,而输出是特定词对应的上下文词向量。CBOW对小型数据库比较合适,而Skip-Gram在大型语料中表现更好。通俗一点来说,CBOW解决的是这样的问题:
而Skip-gram解决的是这样的问题:
CBOW的训练模型
处理步骤;
- 输入:上下文单词的onehot编码,每个onehot编码是1 x V的向量
- 每个onehot向量乘以V x N的权重矩阵
- 全部向量相加求平均得到隐藏层的特征向量,这个向量是1 x N维的
- 隐藏层的特征向量乘以N x V维的输出权重矩阵,得到1 x V维的输出向量
- 输出向量通过激活函数处理,得到概率分布
- 概率最大的index所指示的单词为预测出的中间词
训练完毕后,输入层的每个单词与矩阵W相乘得到的向量的就是我们想要的词向量 (word embedding).
下面是一个简单例子:
Skip-Gram的训练模型
假如我们有一个句子 “The dog barked at the mailman”。首先我们选句子中间的一个词作为我们的输入词,例如我们选取 “dog” 作为input word.
有了input word以后,我们再定义一个叫做skip_window的参数,它代表着我们从当前input word的一侧(左边或右边)选取词的数量。如果我们设置skip_window=2,那么我们最终获得窗口中的词(包括input word在内)就是[‘The’, ‘dog’, ‘barked’, ‘at’]。skip_window=2代表着选取左input word左侧2个词和右侧2个词进入我们的窗口,所以整个窗口大小span=2x2=4。另一个参数叫num_skips,它代表着我们从整个窗口中选取多少个不同的词作为output word,当skip_window=2,num_skips=2时,我们将会得到两组 (input word, output word) 形式的训练数据,即 (‘dog’, ‘barked’),(‘dog’, ‘the’)。
神经网络基于这些训练数据将会输出一个概率分布,这个概率代表着我们的词典中的每个词是output word的可能性。这句话有点绕,我们来看个栗子。第二步中我们在设置skip_window和num_skips=2的情况下获得了两组训练数据。假如我们先拿一组数据 (‘dog’, ‘barked’) 来训练神经网络,那么模型通过学习这个训练样本,会告诉我们词汇表中每个单词是“barked”的概率大小。模型的输出概率代表着到我们词典中每个词有多大可能性跟input word同时出现。
Word2Vec的优缺点
优点
- 由于 Word2vec 会考虑上下文,跟之前的 Embedding 方法相比,效果要更好
- 比之前的 Embedding方法维度更少,所以速度更快
- 通用性很强,可以用在各种 NLP 任务中
缺点
- 由于词和向量是一对一的关系,所以多义词的问题无法解决。
- Word2vec 是一种静态的方式,虽然通用性强,但是无法针对特定任务做动态优化
Word2Vec中文语料实战
环境
win10 + python3
依赖包:gensim与jieba (通过pip install 安装)
语料库
诛仙小说 (部分)
文本预处理
去除文本中可能存在的空白等,防止对训练造成干扰。
# 文本预处理
file = open('text.txt', 'w', encoding='utf-8')
with open('诛仙.txt', 'r', encoding='utf-8') as origin_file:
for line in origin_file:
line = line.strip()
file.write(line + '\n')
file.close()
中文分词
将停顿词放在一个stop.txt文件中,这里选取了部分停顿词如下:
调用jieba库进行词语划分.
# 分词
import jieba
stop_words_file = 'stop.txt'
stop_words = []
with open(stop_words_file, 'r', encoding='utf-8') as origin_stop_words_file:
text = origin_stop_words_file.readlines()
for line in text:
line = line.strip()
stop_words.append(line)
origin_txt_file = 'text.txt'
target_file = open('text_cut.txt', 'w', encoding='utf-8')
with open(origin_txt_file, 'r', encoding='utf-8') as origin_file:
text = origin_file.readlines()
for line in text:
line =line.strip()
out_str = ''
word_list = jieba.cut(line, cut_all=False)
for word in word_list:
if word not in stop_words:
if word != '\t':
out_str += word
out_str += ' '
target_file.write(out_str.rstrip() + '\n')
target_file.close()
分词结果:
训练word2vec模型
#coding:utf8
import gensim.models.word2vec as w2v
model_file_name = 'model'
#模型训练,生成词向量
sentences = w2v.LineSentence('text_cut.txt')
# 训练参数:输出词的向量维数为20,训练窗口为5,截断频次在5次以下的词,4个并行任务
model = w2v.Word2Vec(sentences, size=1000, window=5, min_count=5, workers=4)
model.save(model_file_name)
模型测试
import gensim
import warnings
warnings.filterwarnings(action='ignore', category=UserWarning,module='gensim')
model = gensim.models.Word2Vec.load("model")
word = '金瓶儿'
result = model.similar_by_word(word)
print("跟 "+word+" 最相近的词:")
for i in result:
print(i)
模型结果
参考文献
[1] Muwen. (2021). [NLP] Understanding the essence of Word2vec. Retrieved from https://zhuanlan.zhihu.com/p/26306795
[2] Wuyiji. (2017). Use python-gensim to train word2vec model and understanding of gensim API. Retrieved from https://blog.csdn.net/sinat_26917383/article/details/69803018
[3] Gensim. (2021). models.word2vec – Word2vec embeddings. Retrieved from https://radimrehurek.com/gensim/models/word2vec.html