文本在计算机中的表示方法总结

文章目录

1. 概述

文本表示( text representation)是NLP任务中非常基础,同时也非常重要的一部分。目前常用的文本表示方式分为:

  1. 离散式表示(Discrete Representation);
  2. 分布式表示(Distributed Representation);

本文旨在介绍这两类常用的文本表示方式。

2. 离散式表示(Discrete Representation)

2.1 One-Hot

One-Hot编码又称为“独热编码”或“哑编码”,是最传统、最基础的词(或字)特征表示方法。这种编码将词(或字)表示成一个向量,该向量的维度是词典(或字典)的长度(该词典是通过语料库生成的),该向量中,当前词的位置的值为1,其余的位置为0。

文本使用one-hot编码步骤:

  1. 根据语料库创建 词典(vocabulary),并创建词和索引的 映射(stoi,itos)
  2. 将句子转换为用索引表示;
  3. 创建OneHot编码器;
  4. 使用OneHot编码器对句子进行编码;

Demo

class OneHotEncoder(object):
    def __init__(self, corpus=[]):
        # 统计词频
        word_counter = {}
        for sentence in corpus:
            for item in sentence.split():
                if item in word_counter.keys():
                    word_counter[item] += 1
                else:
                    word_counter[item] = 1
        # 按词频排序
        word_counter_sort = sorted(word_counter, key=word_counter.__getitem__, reverse=True)
        # 词典
        self.vocab = set(word_counter_sort)
        # 创建词和索引的映射
        self.stoi = {}
        self.itos = {}
        for index, word in enumerate(word_counter_sort):
            self.stoi[word] = index
            self.itos[index + 1] = word
    # 句子编码
    def sentence_encoder(self, sentence):
        result = []
        for i in range(0, len(sentence.split())):
            result.append([0] * len(self.vocab))

        for index, item in enumerate(sentence.split()):
            result[index][self.stoi[item]] = 1
        return result
    # 单词编码
    def word_encoder(self, word):
        result = [0] * len(self.vocab)
        result[self.stoi[word]] = 1
        return result

if __name__ == '__main__':
    corpus = [
        "CNN LSTM TRANSFORMER",
        "This is NLP, NLP is a every good task",
        "This is a sample",
        "This is anthor example anthor example",
        "CNN module is very useful",
        "That is pytorch"
    ]
    one_hot_enc = OneHotEncoder(corpus)
    print('词索引:')
    print(one_hot_enc.stoi)
    print('“is” 的one-hot编码:{}'.format(one_hot_enc.word_encoder('is')))
    print('“This” 的one-hot编码:{}'.format(one_hot_enc.word_encoder('This')))
    print('{}:one-hot 编码结果:'.format(corpus[0]))
    print(one_hot_enc.sentence_encoder(corpus[0]))

结果:

# 词索引:
{'is': 0, 'This': 1, 'CNN': 2, 'a': 3, 'anthor': 4, 'example': 5, 'LSTM': 6, 'TRANSFORMER': 7, 'NLP,': 8, 'NLP': 9, 'every': 10, 'good': 11, 'task': 12, 'sample': 13, 'module': 14, 'very': 15, 'useful': 16, 'That': 17, 'pytorch': 18}

# “is” 的one-hot编码:
[1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
# “This” 的one-hot编码:
[0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

# CNN LSTM TRANSFORMER:one-hot 编码结果:
[
    [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 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, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

如结果所示,One-Hot编码的特点如下:

  1. 词向量长度是词典长度;
  2. 在向量中,该单词的索引位置的值为 1,其余的值都是 0
  3. 使用One-Hot进行编码的文本,得到的矩阵是稀疏矩阵(sparse matrix)

缺点

  1. 不同词的向量表示互相正交,无法衡量不同词之间的关系;
  2. 该编码只能反映某个词是否在句中出现,无法衡量不同词的重要程度;
  3. 使用One-Hot对文本进行编码后得到的是高维稀疏矩阵,会浪费计算和存储资源;

2.2 词袋模型(Bag Of Word,BOW)

例句:

  1. Jane wants to go to Shenzhen.
  2. Bob wants to go to Shanghai.

在词袋模型中不考虑语序和词法的信息,每个单词都是相互独立的,将词语放入一个“袋子”里,统计每个单词出现的频率。

Demo

class BagOfWords(object):
    def __init__(self, corpus=[]):
        # 统计词频
        word_counter = {}
        for sentence in corpus:
            for item in sentence.split():
                if item in word_counter.keys():
                    word_counter[item] += 1
                else:
                    word_counter[item] = 1
        # 按词频排序
        word_counter_sort = sorted(word_counter, key=word_counter.__getitem__, reverse=True)
        # 词典
        self.vocab = set(word_counter_sort)
        # 创建词和索引的映射
        self.stoi = {}
        self.itos = {}
        for index, word in enumerate(word_counter_sort):
            self.stoi[word] = index
            self.itos[index + 1] = word

    def sentence_encoder(self, sentence):
        result = [0] * len(self.vocab)
        for item in sentence.split():
            result[self.stoi[item]] += 1
        return result

if __name__ == '__main__':
    corpus = [
        'Jane wants to go to Shenzhen',
        'Bob wants to go to Shanghai'
    ]
    bow = BagOfWords(corpus)
    print('词索引:')
    print(bow.stoi)
    print('{} 编码:'.format(corpus[0]))
    print(bow.sentence_encoder(corpus[0]))
    print('{} 编码:'.format(corpus[1]))
    print(bow.sentence_encoder(corpus[1]))

结果:

# 词索引:
{'to': 0, 'wants': 1, 'go': 2, 'Jane': 3, 'Shenzhen': 4, 'Bob': 5, 'Shanghai': 6}
# Jane wants to go to Shenzhen 编码:
[2, 1, 1, 1, 1, 0, 0]
# Bob wants to go to Shanghai 编码:
[2, 1, 1, 0, 0, 1, 1]

词袋模型编码特点:

  1. 词袋模型是对文本(而不是字或词)进行编码;
  2. 编码后的向量长度是词典的长度;
  3. 该编码忽略词出现的次序;
  4. 在向量中,该单词的索引位置的值为单词在文本中出现的次数;如果索引位置的单词没有在文本中出现,则该值为 0

缺点

  1. 该编码忽略词的位置信息,位置信息在文本中是一个很重要信息,词的位置不一样语义会有很大的差别(如 “猫爱吃老鼠” 和 “老鼠爱吃猫” 的编码一样);
  2. 该编码方式虽然统计了词在文本中出现的次数,但仅仅通过“出现次数”这个属性无法区分常用词(如:“我”、“是”、“的”等)和关键词(如:“自然语言处理”、“NLP”等)在文本中的重要程度;

2.3 TF-IDF(词频-逆文档频率)

为了解决词袋模型无法区分常用词(如:“是”、“的”等)和专有名词(如:“自然语言处理”、“NLP”等)对文本的重要性的问题,TF-IDF算法应运而生。

TF-IDF 全称是:term frequency–inverse document frequency 又称 词频-逆文本频率。其中:

  1. TF(Term Frequency):某个词在当前文本中出现的频率,频率高的词语或者是重要的词(如:“自然语言处理”)或者是常用词(如:“我”、“是”、“的”等);
  2. IDF(Inverse Document frequency):逆文本频率。文本频率是指:含有某个词的文本在整个语料库中所占的比例。逆文本频率是文本频率的倒数;

公式

  1. TF= TF=\frac{某个词在文章中出现的总次数}{文章包含的总词数} TF=文章包含的总词数某个词在文章中出现的总次数​

IDF=log(+1) IDF=log(\frac{语料库的文本总数}{包含某词的文本数量+1}) IDF=log(包含某词的文本数量+1语料库的文本总数​)

  1. TFIDF=TFIDF TF-IDF=TF*IDF TF−IDF=TF∗IDF

优点

  1. 实现简单,算法容易理解且解释性较强;
  2. 从IDF的计算方法可以看出常用词(如:“我”、“是”、“的”等)在语料库中的很多文章都会出现,故IDF的值会很小;而关键词(如:“自然语言处理”、“NLP”等)只会在某领域的文章出现,IDF的值会比较大;故:TF-IDF在保留文章的重要词的同时可以过滤掉一些常见的、无关紧要的词

缺点

  1. 不能反映词的位置信息,在对关键词进行提取时,词的位置信息(如:标题、句首、句尾的词应该赋予更高的权重);
  2. IDF是一种试图抑制噪声的加权,本身倾向于文本中频率比较小的词,这使得IDF的精度不高;
  3. TF-IDF严重依赖于语料库(尤其在训练同类语料库时,往往会掩盖一些同类型的关键词;如:在进行TF-IDF训练时,语料库中的 娱乐 新闻较多,则与 娱乐 相关的关键词的权重就会偏低 ),因此需要选取质量高的语料库进行训练;

3. 分布式表示(Distributed Representation)

文本在计算机中的表示方法总结

理论基础:

  • 1954年,Harris提出分布式假说(distributional hypothesis)奠定了这种方法的理论基础:A word’s meaning is given by the words that frequently appear close-by(上下文相似的词,其语义也相似);
  • 1957年,Firth对分布式假说做出进一步的阐述和明确:A word is characterized by the company it keeps(词的语义由其上下文决定);

3.1 n-gram

n-gram 是一种 语言模型(Language Model, LM)。语言模型是一种基于概率的判别式模型,该模型的输入是一句话(单词的序列),输出的是这句话的概率,也就是这些单词的联合概率(joint probability)。(备注: 语言模型就是判断一句话是不是正常人说的。)

语言模型中的概率计算:
P(w1,w2,w3,...,wt)=P(w1)P(w2w1)P(w3w1,w2)...P(wtw1,w2,w3,...wt1) P({w}_1, {w}_2, {w}_3, ..., {w}_t)=P({w}_1)*P({w}_2|{w}_1)*P({w}_3|{w}_1,{w}_2)*...*P({w}_t|{w}_1,{w}_2,{w}_3,...{w}_{t-1}) P(w1​,w2​,w3​,...,wt​)=P(w1​)∗P(w2​∣w1​)∗P(w3​∣w1​,w2​)∗...∗P(wt​∣w1​,w2​,w3​,...wt−1​)
n-gram模型中的概率计算:

n-gram 是对语言模型的一个简化(马尔科夫假设 Markov Assumption):一个词的出现仅与它之前出现的若干(n)个词有关。

  • n=1 时,表示当前词的出现仅与它前面出现的词语有关,成该模型为:bi-gram
    P(S)=P(w1,w2,w3,...,wt)=P(w1)P(w2w1)P(w3w2)...P(wtwt1) P(S)=P({w}_1, {w}_2, {w}_3, ..., {w}_t)=P({w}_1)*P({w}_2|{w}_1)*P({w}_3|{w}_2)*...*P({w}_t|{w}_{t-1}) P(S)=P(w1​,w2​,w3​,...,wt​)=P(w1​)∗P(w2​∣w1​)∗P(w3​∣w2​)∗...∗P(wt​∣wt−1​)
  • n=2 时,表示当前词的出现仅与它前面出现的词语有关,成该模型为:tri-gram
    P(S)=P(w1,w2,w3,...,wt)=P(w1)P(w2w1)P(w3w1,w2)...P(wtwt2,wt1) P(S)=P({w}_1, {w}_2, {w}_3, ..., {w}_t)=P({w}_1)*P({w}_2|{w}_1)*P({w}_3|{w}_1, {w}_2)*...*P({w}_t|{w}_{t-2},{w}_{t-1}) P(S)=P(w1​,w2​,w3​,...,wt​)=P(w1​)∗P(w2​∣w1​)∗P(w3​∣w1​,w2​)∗...∗P(wt​∣wt−2​,wt−1​)
    **备注:**在 n=gram 中并不是 n 取值越大越好,一般取 n=1n=2

3.2 共现矩阵(Co-Occurrence Matrix)

首先指定窗口大小,然后统计窗口(和对称窗口)内词语共同出现的次数作为词的向量(vector)。

语料库:

  1. I like deep learning.
  2. I like NLP.
  3. I enjoy flying.

备注: 指定窗口大小为1(即:左右的 window_length=1,相当于 bi-gram)统计数据如下:(I, like),(Iike, deep),(deep, learning),(learning, .),(I, like),(like, NLP),(NLP, .),(I, enjoy),(enjoy, flying), (flying, .)。则语料库的共现矩阵如下表所示:

conunts I like enjoy deep learning NLP flying .
I 0 2 1 0 0 0 0 0
like 2 0 0 1 0 1 0 0
enjoy 1 0 0 0 0 0 1 0
deep 0 1 0 0 1 0 0 0
learing 0 0 0 1 0 0 0 1
NLP 0 1 0 0 0 0 0 1
flying 0 0 1 0 0 0 0 1
. 0 0 0 0 1 1 1 0

从以上的共现矩阵可以看出,单词 likeenjoy 都在单词 I 附件出现且统计数目大概相等,则它们在 语义语法 上的含义大概相同。

优点

  1. 考虑了句子中词的顺序;

缺点

  1. 词表的长度很大,导致词的向量长度也很大;
  2. 共现矩阵也是稀疏矩阵(可以使用 SVDPCA 等算法进行降维,但是计算量很大);

3.3 Word2Vec

word2vec 模型是Google团队在2013年发布的 word representation 方法。该方法一出让 预训练词向量 的使用在NLP领域遍地开花。

模型

word2vec有两种模型:CBOWSKIP-GRAM

  • CBOW:利用上下文的词预测中心词;

文本在计算机中的表示方法总结

  • SKIP-GRAM:利用中心词预测上下文的词;
    文本在计算机中的表示方法总结

效果

word2vec 训练 中国 得到的词向量(150维)如下所示。

中国 0.695267 0.024923 0.373858 0.069021 -0.117292 -0.556426 0.432008 0.254941 0.586677 -0.254549 1.003527 -0.222197 0.185688 0.083037 -0.058732 0.323542 1.258338 0.164693 -0.188106 -0.250684 -0.080654 -0.151610 -0.412507 0.285773 0.052821 0.102732 -0.270337 -0.111756 -0.498646 -0.186227 -0.293205 -0.095415 0.127587 0.344156 -0.381316 -0.480463 0.223322 -0.497338 0.027069 0.011063 -0.114940 0.390520 -0.585619 0.699663 0.274457 0.169250 -0.218244 -0.451712 -0.687161 -0.706266 -0.142516 -0.017635 -0.063381 0.591093 -0.032082 0.093772 0.516114 -0.283159 -0.097458 0.145825 -0.068391 0.100854 -0.114780 0.348797 -0.063897 -0.194472 0.221520 -0.610716 -0.470367 -0.771020 -0.060092 0.089194 0.666474 0.088972 -0.066155 -0.374819 -0.251768 0.296184 0.057016 0.082267 0.148182 -0.088706 -0.562667 0.730473 -0.106829 0.278102 -0.068409 0.271573 -0.321527 -0.046518 -0.109408 -0.594494 0.364192 -0.826890 0.293684 0.301737 0.083754 -0.350486 -0.638416 -0.272567 -0.252318 0.335563 -0.225580 0.158646 -0.649817 -0.177225 -0.164084 0.016909 -0.336381 0.385419 0.237850 0.618375 -0.240223 -0.464445 -0.247453 -0.699201 0.328400 0.790493 -0.093674 0.899263 0.362883 0.443218 -0.124069 0.950863 0.079109 0.246814 -0.344305 -0.672262 -0.266110 0.146369 -0.067152 -0.508636 0.355758 -0.807643 -0.135210 0.114947 0.297123 0.047535 -0.061851 -0.305667 -0.053594 0.075912 0.536091 -0.482222 0.419101 0.261491 1.181885 0.723524 -0.788293 0.393409
  1. 这个过程就是word embedding,跟离散式表示方法相比,word2vec得到的向量不是稀疏向量,此外维度一般在100到300维,不像one-hotbowtf-idf算法得到的词向量维度(词汇表的大小)那么大;

  2. 将得到的word embedding(本例子中为150维)再进行降维后进行可是话表示,如下图所示:
    文本在计算机中的表示方法总结

可以发现:

  • nine、zero、three、two等数字词汇聚合在一起;

  • tuesday、saturday等日期词汇聚合在一起;

  • google、yahoo等搜索引擎词汇聚合在一起;

  1. 此外还有一个有意思的现象,可以用数学公式表示:
    kingqueen=manwoman \vec{king}-\vec{queen}=\vec{man}-\vec{woman} king​−queen​=man−woman

文本在计算机中的表示方法总结

优点

  1. 考虑到词语的上下文,学习到了语义和语法的信息;
  2. 得到的词向量维度小,节省存储和计算资源;
  3. 通用性强,可以应用到各种NLP任务中;

缺点

  1. 词和向量是一对一的关系,无法解决多义词的问题;
  2. word2vec是一种静态的模型,虽然通用性强,但无法真的特定的任务做动态优化;

3.4 GloVe

GloVe是斯坦福大学Jeffrey、Richard等提供的一种词向量表示算法,GloVe的全称是Global Vectors for Word Representation,是一个基于全局词频统计(count-based & overall staticstics)的词表征(word representation)算法。该算法综合了global matrix factorization(全局矩阵分解)local context window(局部上下文窗口) 两种方法的优点。

备注:Glove模型的推导公式比较复杂,在这里不做详细推导,具体可以查看官网

效果

文本在计算机中的表示方法总结

优点

  1. 考虑到词语的上下文、和全局语料库的信息,学习到了语义和语法的信息;
  2. 得到的词向量维度小,节省存储和计算资源;
  3. 通用性强,可以应用到各种NLP任务中;

缺点

  1. 词和向量是一对一的关系,无法解决多义词的问题;
  2. glove也是一种静态的模型,虽然通用性强,但无法真的特定的任务做动态优化;

3.5 ELMO

word2vecglove 算法得到的词向量都是静态词向量(静态词向量会把多义词的语义进行融合,训练结束之后不会根据上下文进行改变),静态词向量无法解决多义词的问题(如:“我今天买了7斤苹果” 和 “我今天买了苹果7” 中的 苹果 就是一个多义词)。而ELMO模型进行训练的词向量可以解决多义词的问题。

ELMO的全称是“ Embedding from Language Models ”,这个名字不能很好的反映出该模型的特点,提出ELMO的论文题目可以更准确的表达出该算法的特点“ Deep contextualized word representation ”。

该算法的精髓是:用语言模型训练神经网络,在使用word embedding时,单词已经具备上下文信息,这个时候神经网络可以根据上下文信息对word embedding进行调整,这样经过调整之后的word embedding更能表达在这个上下文中的具体含义,这就解决了静态词向量无法表示多义词的问题。

网络模型

文本在计算机中的表示方法总结

过程

  1. 上图中的结构使用字符级卷积神经网络(convolutional neural network, CNN)来将文本中的词转换成原始词向量(raw word vector) ;
  2. 将原始词向量输入双向语言模型中第一层 ;
  3. 前向迭代中包含了该词以及该词之前的一些词汇或语境的信息(即上文);
  4. 后向迭代中包含了该词以及该词之后的一些词汇或语境的信息(即下文) ;
  5. 这两种迭代的信息组成了中间词向量(intermediate word vector);
  6. 中间词向量被输入到模型的下一层 ;
  7. 最终向量就是原始词向量和两个中间词向量的加权和;

效果

文本在计算机中的表示方法总结

如上图所示:

  • 使用glove训练的词向量中,与 play 相近的词大多与体育相关,这是因为语料中与play相关的语料多时体育领域的有关;
  • 在使用elmo训练的词向量中,当 play演出 的意思时,与其相近的也是 演出 相近的句子;

4.结语

  1. 现在深度学习在NLP领域中使用的大多是分布式词向量;
  2. 分布式词向量的理论基础是语言模型
  3. 在进行词向量选择时,要考虑到具体任务的特性,word2vecgloveelmo训练的词向量各有优缺点,并没有哪一种比另两种效果好很多。
上一篇:启明云端分享|盘一盘ESP32为啥那么惹人爱呢?


下一篇:分析一套源代码的代码规范和风格并讨论如何改进优化代码