第8天: NLP——文本表示

前言

  文本分类是自然语言处理中研究最为广泛的任务之一,通过构建模型实现对文本内容进行自动分类,有很多应用场景,比如新闻文章主题分类,产品评论情感分类,检索中用户查询的意图分类等等。文本分类的大致流程:文本预处理,抽取文本特征,构造分类器。其中研究最多的就是文本特征抽取,更广义上说是文本表示。我们通过向量的方法表达一个单词、一句话以及一篇文章。首先我们介绍单词转换向量的方法。

单词的表示方法

One-Hot representation(独立热编码)

  One-Hot representation又叫做One-Hot Encoding,是文本表示中比较常用的文本特征特征提取的方法。其实就是用N位状态寄存器编码N个状态,每个状态都有独立的寄存器位,且这些寄存器位中只有一位有效,说白了就是只能有一个状态。其转化方式为保证每个样本中的每个特征只有1位处于状态1,其他都是0。例如下面有四个样本,每个样本有三种特征:
三个特征
  上图用十进制数对每种特征进行了编码,feature1有两种可能的取值,feature2有4种可能的取值,feature3有3种可能的取值。具体转化如下:
①、1->001
②、2->010
③、3->100
  其他的特征也都这么表示,转化为二进制为如下图:
二进制三种特征
  这样,4个样本的特征向量的二进制就可以这么表示:
①、sample1 -> [0,1,1,0,0,0,1,0,0]
②、sample2 -> [1,0,0,1,0,0,0,1,0]
③、sample3 -> [0,1,0,0,1,0,0,1,0]
④、sample4 -> [1,0,0,0,0,1,0,0,1]
  接下来我们用一个具体的实例如下:
例如有一个词典:[我们 去 爬山 今天 你们 昨天 跑步];我们将其转化为向量如下所示:
1、我们:[1 0 0 0 0 0 0 0]
2、爬山:[0 0 1 0 0 0 0 0]
3、跑步:[0 0 0 0 0 0 0 1]
4、昨天:[0 0 0 0 0 0 1 0]
  这就是One-Hot编码的转化过程,接下来我们谈一下其优缺点:从上面的转化可以看到这种算法解决了分类器处理离散数据困难的问题以及一定程度上起到了扩展特征的作用。但是缺陷也是很明显的:首先就是不考虑词与词之间的顺序问题,而在文本中,次的顺序是一个很重要的问题,其次是基于词与词之间相互独立的情况下的,然而在多数情况中,词与词之间应该是相互影响的,最后就是得到的特征是离散的,稀疏的。因此用one-hot编码表示单词的时候,一定要注意:并不是出现的次数越多就越重要。这里需要注意的是向量的维度就是词典的大小。另外,one-hot编码并不适合两个单词之间的相似度。并且有限的维度能表示的单词是很有限的。接下来给大家介绍一种相应的方法。

分布式表示——词向量

  针对前面的one-hot一个巨大的缺陷——矩阵的稀疏性,而且不可以表示单词之间的相似度。我们提到一种新的思想——分布式表示,这种方式是长度的转化为自定义,这种方式几乎不存在为0的情况。这种时候,一定的维度能够表示很多的单词。这里就引出一种经常用的方式——词向量。这里包括Glone、CBOW、RNN/LSTM以及FM。word2vec从某种意义上讲可以理解为转化考虑到单词的含义。接下来给大家介绍word2vec。
  word2vec是用一个一层的神经网络(即CBOW)把one-hot形式的稀疏词向量映射称为一个n维(n一般为几百)的稠密向量的过程。为了加快模型训练速度,其中的tricks包括Hierarchical softmax,negative sampling, Huffman Tree等。在NLP中,最细粒度的对象是词语。如果我们要进行词性标注,用一般的思路,我们可以有一系列的样本数据(x,y)。其中x表示词语,y表示词性。而我们要做的,就是找到一个x -> y的映射关系,传统的方法包括Bayes,SVM等算法。但是我们的数学模型,一般都是数值型的输入。但是NLP中的词语,是人类的抽象总结,是符号形式的(比如中文、英文、拉丁文等等),所以需要把他们转换成数值形式,或者说——嵌入到一个数学空间里,这种嵌入方式,就叫词嵌入(word embedding),而 Word2vec,就是词嵌入( word embedding) 的一种。word2vec里面有两个重要的模型-CBOW模型(Continuous Bag-of-Words Model)与Skip-gram模型。在Tomas Mikolov的paper中给出了示意图。
CBOW和SKIP-Gram
  由名字与图都很容易看出来,CBOW就是根据某个词前面的C个词或者前后C个连续的词,来计算某个词出现的概率。Skip-Gram Model相反,是根据某个词,然后分别计算它前后出现某几个词的各个概率。词向量最简单的方式是1-of-N的one-hot方式。onehot对于同学们来说都很熟悉了,也就是从很大的词库corpus里选V个频率最高的词(忽略其他的) ,V一般比较大,比如V=10W,固定这些词的顺序,然后每个词就可以用一个V维的稀疏向量表示了,这个向量只有一个位置的元素是1,其他位置的元素都是0。One hot方式其实就是简单的直接映射,所以缺点也很明显,维数很大,也没啥计算上的意义。word2vec可以分为两部分:模型与通过模型获得的词向量。word2vec的思路与自编码器(auto-encoder)的思路比较相似。都是先基于训练数据构建一个神经网络。当这个网络训练好一有,我们并不会利用这个训练好的网络处理新任务,我们真正需要的是这个模型通过训练数据所学得的参数,例如隐层的权重矩阵——后面我们将会看到这些权重在Word2Vec中实际上就是我们试图去学习的“word vectors”。基于训练数据建模的过程,我们给它一个名字叫“Fake Task”,意味着建模并不是我们最终的目的。上面提到的这种方法实际上会在无监督特征学习(unsupervised feature learning)中见到,最常见的就是自编码器(auto-encoder):通过在隐层将输入进行编码压缩,继而在输出层将数据解码恢复初始状态,训练完成后,我们会将输出层“砍掉”,仅保留隐层。

句子表示方式

Boolean representation

  句子表示方法常用boolean法。词典中有的句子中有置为1,没有的置为0;具体表示方法用一个例子表示:
例如:词典还是上面提到的:[我们 去 爬山 今天 你们 昨天 跑步]
1、我们 今天 去 爬山[1 0 1 1 1 0 0 0]
2、你们 昨天 去 爬山[0 0 0 0 0 0 1 1 1]
3、你们 又 去 爬山 又 去 跑步[0 1 1 1 0 1 0 1]
  这里需要注意的是我们每一句话转化为向量的维数均是等于词典的数量。

Count Based representation

  这种算法和之前Boolean算法差不多,不过区别的是记录每一个单词的频次。上面的例子转化过来就是如下:
1、我们 今天 去 爬山[1 0 1 1 1 0 0 0]
2、你们 昨天 去 爬山[0 0 0 0 0 0 1 1 1]
3、你们 又 去 爬山 又 去 跑步[0 2 2 1 0 1 0 1]
  这种算法要比上一种更好,统计其出现的次数,稍微比较好点,但是,还是不能表示其单词在句子的意思,而我们NLP最核心的就是语义的表示,接下来我们介绍一种比较好的算法也是我们最为常用的句子转化向量的算法——tf-idf表示算法。

tf-idf表示算法

  TF-IDF(term frequency–inverse document frequency,词频-逆向文件频率)是一种用于信息检索(information retrieval)与文本挖掘(text mining)的常用加权技术。TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。接下来我们介绍tf-idf的计算公式:
tfidf(w) = tf(w) * idf(w) 这里需要注意的是:tf就是相当于前面提到的count based representation算法,而idf就是考虑单词的重要性。词频(TF)表示词条(关键字)在文本中出现的频率。 这个数字通常会被归一化(一般是词频除以文章总词数), 以防止它偏向长的文件。具体计算方式如下:
tf的计算方式
  其中 ni,j 是该词在文件 dj 中出现的次数,分母则是文件 dj 中所有词汇出现的次数总和;逆向文件频率 (IDF) :某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将得到的商取对数得到。如果包含词条t的文档越少, IDF越大,则说明词条具有很好的类别区分能力。具体公式如下:
idf计算方式
  其中,|D| 是语料库中的文件总数。 |{j:ti∈dj}| 表示包含词语 ti 的文件数目(即 ni,j≠0 的文件数目)。如果该词语不在语料库中,就会导致分母为零,因此一般情况下使用 1+|{j:ti∈dj}|,这里的分母之所以加1,是为了避免分母为0。该算法的应用主要在(1)搜索引擎;(2)关键词提取;(3)文本相似性;(4)文本摘要。接下来我们用python3实现TF-IDF算法:具体代码实现如下:

from collections import defaultdict
import math
import operator
def loadDataSet():
    dataset = [['my', 'dog', 'has', 'flea', 'problems', 'help', 'please'],  
               ['maybe', 'not', 'take', 'him', 'to', 'dog', 'park', 'stupid'],
               ['my', 'dalmation', 'is', 'so', 'cute', 'I', 'love', 'him'],
               ['stop', 'posting', 'stupid', 'worthless', 'garbage'],
               ['mr', 'licks', 'ate', 'my', 'steak', 'how', 'to', 'stop', 'him'],
               ['quit', 'buying', 'worthless', 'dog', 'food', 'stupid']]
    classVec = [0, 1, 0, 1, 0, 1]
    return dataset, classVec
def feature_select(list_words):
    doc_frequency = defaultdict(int)
    for word_list in list_words:
        for i in word_list:
            doc_frequency[i] += 1
    word_tf = {}
    for i in doc_frequency:
        word_tf[i] = doc_frequency[i] / sum(doc_frequency.values())
    doc_num = len(list_words)
    word_idf = {}
    word_doc = defaultdict(int)
    for i in doc_frequency:
        for j in list_words:
            if i in j:
                word_doc[i] += 1
    for i in doc_frequency:
        word_idf[i] = math.log(doc_num / (word_doc[i] + 1))
    word_tf_idf = {}
    for i in doc_frequency:
        word_tf_idf[i] = word_tf[i] * word_idf[i]

    dict_feature_select = sorted(word_tf_idf.items(), key=operator.itemgetter(1), reverse=True)
    return dict_feature_select

if __name__ == '__main__':
    data_list, label_list = loadDataSet()
    features = feature_select(data_list)
    print(features)
    print(len(features))

实现的效果如下:
代码测试效果图
2、NLTK实现TF-IDF算法

from nltk.text import TextCollection
from nltk.tokenize import word_tokenize
sents = ['this is sentence one', 'this is sentence two', 'this is sentence three']
sents = [word_tokenize(sent) for sent in sents]  
print(sents)  
corpus = TextCollection(sents)  
print(corpus)  
tf = corpus.tf('one', corpus)  
print(tf)
idf = corpus.idf('one')  
print(idf)
tf_idf = corpus.tf_idf('one', corpus)
print(tf_idf)

具体效果如下图所示:
代码通过示意图
3、Sklearn实现TF-IDF算法

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import TfidfTransformer
x_train = ['TF-IDF 主要 思想 是', '算法 一个 重要 特点 可以 脱离 语料库 背景',
           '如果 一个 网页 被 很多 其他 网页 链接 说明 网页 重要']
x_test = ['原始 文本 进行 标记', '主要 思想']
vectorizer = CountVectorizer(max_features=10)
tf_idf_transformer = TfidfTransformer()
tf_idf = tf_idf_transformer.fit_transform(vectorizer.fit_transform(x_train))
x_train_weight = tf_idf.toarray()
tf_idf = tf_idf_transformer.transform(vectorizer.transform(x_test))
x_test_weight = tf_idf.toarray() 
print('输出x_train文本向量:')
print(x_train_weight)
print('输出x_test文本向量:')
print(x_test_weight)

代码实现效果如下
代码通过示意图
4、Jieba实现TF-IDF算法

import jieba.analyse
text = '''关键词是能够表达文档中心内容的词语,常用于计算机系统标引论文内容特征、
信息检索、系统汇集以供读者检阅。关键词提取是文本挖掘领域的一个分支,是文本检索、
文档比较、摘要生成、文档分类和聚类等文本挖掘研究的基础性工作
'''
keywords = jieba.analyse.extract_tags(text, topK=5, withWeight=False, allowPOS=())
print(keywords)

代码效果图如图所示:
代码运行示意图
  TF-IDF 采用文本逆频率 IDF 对 TF 值加权取权值大的作为关键词,但 IDF 的简单结构并不能有效地反映单词的重要程度和特征词的分布情况,使其无法很好地完成对权值调整的功能,所以 TF-IDF 算法的精度并不是很高,尤其是当文本集已经分类的情况下。在本质上 IDF 是一种试图抑制噪音的加权,并且单纯地认为文本频率小的单词就越重要,文本频率大的单词就越无用。这对于大部分文本信息,并不是完全正确的。IDF 的简单结构并不能使提取的关键词, 十分有效地反映单词的重要程度和特征词的分布情 况,使其无法很好地完成对权值调整的功能。尤其是在同类语料库中,这一方法有很大弊端,往往一些同类文本的关键词被盖。
  当然也存在一些缺陷,没有考虑特征词的位置因素对文本的区分度,词条出现在文档的不同位置时,对区分度的贡献大小是不一样的。按照传统TF-IDF,往往一些生僻词的IDF(反文档频率)会比较高、因此这些生僻词常会被误认为是文档关键词。传统TF-IDF中的IDF部分只考虑了特征词与它出现的文本数之间的关系,而忽略了特征项在一个类别中不同的类别间的分布情况。最后介绍文本相似度计算。

文本相似度

  计算文本相似度的方法有很多,接下来给大家计算相似度的方法。
1、欧氏距离(Euclidean Distance)
  欧氏距离是最易于理解的一种距离计算方法,源自欧氏空间中两点间的距离公式。
(1)二维平面上两点a(x1,y1)与b(x2,y2)间的欧氏距离:
欧式距计算
(2)三维空间两点a(x1,y1,z1)与b(x2,y2,z2)间的欧氏距离:
三维
(3)两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的欧氏距离:
一般式
2、曼哈顿距离(Manhattan Distance)
   从名字就可以猜出这种距离的计算方法了。想象你在曼哈顿要从一个十字路口开车到另外一个十字路口,驾驶距离是两点间的直线距离吗?显然不是,除非你能穿越大楼。实际驾驶距离就是这个“曼哈顿距离”。而这也是曼哈顿距离名称的来源, 曼哈顿距离也称为城市街区距离(City Block distance)。
(1)二维平面两点a(x1,y1)与b(x2,y2)间的曼哈顿距离
二维计算方式
(2)两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的曼哈顿距离
一般式
3. 切比雪夫距离 ( Chebyshev Distance )
(1)二维平面两点a(x1,y1)与b(x2,y2)间的切比雪夫距离
二维式
(2)两个n维向量a(x11,x12,…,x1n)与 b(x21,x22,…,x2n)间的切比雪夫距离
一般式
4、夹角余弦(Cosine)
  有没有搞错,又不是学几何,怎么扯到夹角余弦了?各位看官稍安勿躁。几何中夹角余弦可用来衡量两个向量方向的差异,机器学习中借用这一概念来衡量样本向量之间的差异。
(1)在二维空间中向量A(x1,y1)与向量B(x2,y2)的夹角余弦公式:
余弦公式
(2) 两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n)的夹角余弦
  类似的,对于两个n维样本点a(x11,x12,…,x1n)和b(x21,x22,…,x2n),可以使用类似于夹角余弦的概念来衡量它们间的相似程度。
一般式
  夹角余弦取值范围为[-1,1]。夹角余弦越大表示两个向量的夹角越小,夹角余弦越小表示两向量的夹角越大。当两个向量的方向重合时夹角余弦取最大值1,当两个向量的方向完全相反夹角余弦取最小值-1。
  以上就是常用的计算相似度的方法,还有一些不常用的,比如闵可夫斯基距离、 闵可夫斯基距离、马氏距离等。

总结

  本文介绍了文本的表示以及文本的相似度。开始介绍的是单词的几种表示方法。例如:one-hot、分布式表示方法——词向量。另外又介绍了句子的表示方法,包括boolean representation、count based representation和tf-idf算法,并且分别用jieba分词、sklearn以及原方法来实现。最后介绍了几种常用的文本相似度的计算方式,包括:欧式距离、余弦距离、切比雪夫距离的计算方式。每天学习一点点,继续努力,fighting!!!!

参考文献

1、TF—IDF
2、计算文本相似度的方法

猜你喜欢

转载自blog.csdn.net/Oliverfly1/article/details/106589913