自然语言和单词的分布式表示

什么是自然语言处理

自然语言处理,就是处理自然语言的科学。自然语言处理的目标就是让计算机理解人说的话,进而完成对我们有帮助的事情。

自然语言处理的相关应用

搜索引擎
百度、搜狗、Google

搜索引擎

新闻分类

新闻分类

机器翻译

机器翻译

单词含义

我们的语言是由文字构成的,而语言的含义是由单词构成的。换句话说,单词是含义的最小单位。因此,为了让计算机理解自然语言,让它理解单词的含义可以说是最重要的事情了。

同义词词典

目前被广泛使用的并不是《新华字典》那样的常规字典,而是一种 同义词词典(thesaurus)的字典。

同义词

另外,在自然语言处理用到的同义词词典有时会定义单词之间的粒度更细的关系,比如“上位-下位”关系,“整体-部分”关系。

例子

同义词词典的问题

难以顺应时代变化
随着时间的推移,会有新词不断的出现,不仅如此语言的含义也会随着时间的推移而变化。例如,教育部发布的新词

2010年教育部发布新词

人力成本高
《汉语大词典》第二版收录词汇50万
据说英语词汇总数超过1000万个

无法表示单词的微妙差异
即使是含义相近的单词,也有细微的差别。甚至有一些同义词的差异让人来解释都很困难。

基于计数的方法

基于python的语料库的预处理

语料库

我们将用到语料库。简而言之,语料库就是大量的文本数据。不过,预料库并不是胡乱收集数据,一般收集的都是用于自然语言处理研究和应用的文本数据

自然语言处理邻域有非常有名的语料库,比如Google News,中文的sogou文本分类语料库等。我们先用一个非常简单的文本作为学习的语料库,然后再处理更加实用的语料库。

1
text = "最终获奖的球员也即将在以下几名热门人选中产生。得奖的运动员将上台领奖。"

jieba分词

可以对中文文本进行分词、词性标注、关键词抽取等功能,并且支持自定义词典。

1
2
3
4
5
6
7
import jieba

text = "最终获奖的球员也即将在以下几名热门人选中产生。得奖的运动员将上台领奖。"
words = list(jieba.cut(text,cut_all=False))

print("/ ".join(words))
# 最终/ 获奖/ 的/ 球员/ 也/ 即将/ 在/ 以下/ 几名/ 热门/ 人选/ 中/ 产生/ 。/ 得奖/ 的/ 运动员/ 将/ 上台/ 领奖/ 。 

创建单词id和单词的对应表

1
2
3
4
5
6
7
8
9
10
11
12
word_to_id = {}
id_to_word = {} 

for word in words:
    if word not in word_to_id:
        new_id = len(word_to_id)
        word_to_id[word] = new_id
        id_to_word[new_id] = word
# 键值对

print(word_to_id)
# {'最终': 0, '获奖': 1, '的': 2, '球员': 3, '也': 4, '即将': 5, '在': 6, '以下': 7, '几名': 8, '热门': 9, '人选': 10, '中': 11, '产生': 12, '。': 13, '得奖': 14, '运动员': 15, '将': 16, '上台': 17, '领奖': 18}

转化为numpy数组

1
2
3
4
5
6
7
import numpy as np

corpus = [word_to_id[w] for w in words]
corpus = np.array(corpus)

print(corpus)
# array([ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 2, 15, 16, 17, 18, 13])

封装为一个预处理函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def preprocess(text):
    words = list(jieba.cut(text,cut_all=False))
    # 分词并转换为list
    # print("/ ".join(words))
    word_to_id = {}
    id_to_word = {} 
    
    for word in words:
        if word not in word_to_id:
            new_id = len(word_to_id)
            word_to_id[word] = new_id
            id_to_word[new_id] = word
            # 键值对
    corpus = np.array([word_to_id[w] for w in words])
    return corpus, word_to_id, id_to_word

单词的分布式表示

我们都知道世界上的颜色可以通过RGB三原色分别来表示而且知道一个颜色的RGB就至少可以知道它是哪个色系的,比如深绯(201,23,30)不知道“深绯”什么颜色,但是根据RGB可以知道至少它是红色系。而且,颜色的关联性也都体现在三个部分组成的向量中了。那假如我们也能在单词的领域构建紧凑合理的向量,可能就可以更准确的把握单词的含义。这在自然语言处理领域,称为分布式表示。

分布式假设

“某个单词的含义由它周围的单词形成”
    ——分布式假设

单词本身没有含义,单词含义由它所在的上下文(语境)形成。

从现在开始,我们会使用到“上下文”一词。这里所说的上下文是指单词(关注词)周围的单词。

上下文
将上下文的大小(及周围的单词有多少个)称为窗口大小

共现矩阵

关注某个单词的情况下,对它的周围出现了多少次什么单词进行计数,然后再汇总。这样的做法称为“基于计数的方法”。

分词后的文本例子

共现单词数表

表格表示作为“最终”的上下文共现的单词频数。同时,这也意味着可以用向量
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]表示词语“最终”

编写生成共现矩阵函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def create_co_matrix(corpus, vocab_size, window_size=1):
    corpus_size = len(corpus)
    co_matrix = np.zeros((vocab_size, vocab_size), dtype=np.int32)
    for idx, word_id in enumerate(corpus): # enumerate返回枚举对象
        for i in range(1, window_size + 1):
            left_idx = idx - i
            right_idx = idx + i
            if left_idx >= 0:
                left_word_id = corpus[left_idx]
                co_matrix[word_id, left_word_id] += 1
            if right_idx < corpus_size:
                right_word_id = corpus[right_idx]
                co_matrix[word_id, right_word_id] += 1
    return co_matrix

词向量的操作

余弦相似度

当我们通过共现矩阵将单词表示为向量之后我么就可以做很多别的操作,比如:余弦相似对(cosine similarity)
可以计算出向量间的相似度。
余弦相似度

聚类

词向量降维为二维后的坐标显示

聚类

更深层次的还有新闻分类等等的操作