1. 为什么需要文本的表示?
文字是人类认知过程中产生的高层认知抽象实体,我们需要将其转换为数字向量或矩阵作为机器学习算法模型以及神经网络模型的标准输入输出。
2. 词袋模型(Bag-of-words)
Bag-of-words模型是信息检索领域常用的文档表示方法。在文本特征生成过程中,对于一个文档,词袋模型忽略其单词顺序和语法、句法等要素,将其仅仅看作是若干个词汇的集合,文档中每个单词的出现都是独立的,不依赖于其它单词是否出现。也就是说,文档中任意一个位置出现的任何单词,都不受该文档语意影响而独立选择的。主要方法包括one-hot编码、tf-idf、n-gram模型。
2.1 One-hot 编码
2.1.1 对于单词的one-hot表示
假设给定一个词典为 [今天, 天气, 真好,他们,去,爬山]。该词典可以看作一个容量为6的词袋,每个单词的表示为:
今天: [1, 0, 0, 0, 0, 0]
天气: [0, 1, 0, 0, 0, 0]
真好: [0, 0, 1, 0, 0, 0]
……
爬山: [0, 0, 0, 0, 0, 1]
在one-hot表示中,每个单词的向量长度为词袋的容量(假设词袋有10000个单词,某单词的向量长度为10000)。并且该单词在词袋中的对应位置为1,其余位置为0。 python中sklearn可以生成:
import numpy as npfrom sklearn import preprocessingwords = np.array(["今天", "天气", "真好","他们","去","爬山"]) # 词袋words=words.reshape((6,1)) # 转化成6,1矩阵enc = preprocessing.OneHotEncoder()enc.fit(words)enc.categories_ # fit之后内部会改变顺序 用词语句查看# [array(['今天', '他们', '去', '天气', '爬山', '真好'], dtype='<U2')]result = enc.transform([["天气"]]).toarray() # 天气 对应的向量# array([[0., 0., 0., 1., 0., 0.]])
2.1.2 对于句子的one-hot表示
给定三个句子“小明 今天 爬山”,“小红 昨天 跑步”,“小红 今天 又 爬山 又 跑步”。首先根据这些句子构造词袋(可以用分词、去重)为 [小明 小红 爬山 跑步 又 今天 昨天]。各句子的向量表示为:
“小明 今天 爬山”:[1, 0, 1, 0, 0, 1, 0]
“小红 昨天 跑步”:[0, 1, 0, 1, 0, 0, 1]
“小红 今天 又 爬山 又 跑步”:[0, 1, 1, 1, 1, 1, 0]
每个句子中所有单词都会出现在词袋中,其向量长度为词袋的容量。以“小明 今天 爬山”为例,“小明”出现在词袋中,在词袋对应位置设为1。“今天”出现在词袋中第6个位置,对应位置为1。“爬山”出现在词袋中第3个位置,对应位置为1。其余位置为0。由于第三句中“又”出现两次,其向量也可以表示为 [0, 1, 1, 2, 1, 1, 0]。
当“小红 今天 又 爬山 又 跑步”表示为 [0, 1, 1, 2, 1, 1, 0] 时,其中“又”出现的频率为2,一般会认为其比较重要,但是从实际而言,“又”的重要性比不上“爬山”、“跑步”等频率为1的单词。所以,并不是出现的次数越多越重要、并不是出现的越少就越不重要。由此可以引出 tf-idf。
2.2 n-gram模型
n-gram模型为了保持词的顺序,做了一个滑窗的操作,这里的n表示的就是滑窗的大小,例如2-gram模型,也就是把2个词当做一组来处理,然后向后移动一个词的长度,再次组成另一组词,把这些生成一个字典,按照词袋模型的方式进行编码得到结果。该模型考虑了词一定范围内的关联性。
假设给定句子:
John likes to watch movies. Mary likes too.
John also likes to watch football games.
以上两句可以构造一个词典,{“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}
那么第一句的向量表示为:[1, 1, 1, 1, 1, 1, 0, 0, 0, 0],其中第一个1表示John likes在该句中出现了1次,依次类推。
缺点: 随着n的大小增加,词表会成指数型膨胀,会越来越大。
2.3 tf-idf
TF-IDF(term frequency–inverse document frequency,词频-逆向文件频率)是一种用于信息检索(information retrieval)与文本挖掘(text mining)的常用加权技术。
TF-IDF是一种统计方法,用以评估字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
TF-IDF的主要思想是:如果某个单词在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
(1)TF(Term Frequency)词频
词频(TF)表示词条(关键词)在一个文本中出现的频率。计算公式为:
其中表示单词
在文档
中的出现次数,分母表示文档
中所有单词出现的次数之和。通俗理解为:
(2)IDF(Inverse Document Frequency)逆向文档频率
逆向文件频率 (IDF) :某一特定词语的IDF,可以由总文件数目除以包含该词语的文件的数目,再将得到的商取对数得到。如果包含单词的文档越少, IDF越大,则说明词条具有很好的类别区分能力。计算公式为:
其中表示所有文档数,分母表示包含单词
的所有文档数。通俗理解为(加1防止分母为0):
(3)TF-IDF实际是TF*IDF

对IDF的理解:语料库的文档总数实际上是一个词分布的可能性大小,n篇文档,有n种可能。包含词的文档数m,表示词
的真实分布有m个“可能”。那么log(n/m) = log(n) - log(m)就可以表示词
在m篇文档中的出现,导致的词
分布可能性的减少(即信息增益),这个值越小,表示词
分布越散,我们认为一个词越集中出现在某一类文档,它对这类文档的分类越有贡献,那么当一个词分布太散了,那他对文档归类的作用也不那么大了。
举例
给定3篇文档 ,“今天 上 NLP 课程”,“今天 的 课程 有 意思”,“数据 课程 也 有 意思”。词袋为[今天 上 NLP 课程 的 有 意思 数据 也],容量为9。以第一句为例,“今天”的tf-idf值为;“上”的tf-idf值为
;“NLP”的tf-idf值为
,“课程”的tf-idf值为
。
则“今天 上 NLP 课程”的向量可以表示为[,
,
,
,0, 0, 0, 0, 0]。以此类推,各句子可以表示为:
“今天 上 NLP 课程”:[,
,
,
,0, 0, 0, 0, 0]
“今天 的 课程 有 意思”:[,0,0,
,
,
,
, 0, 0]
“数据 课程 也 有 意思”:[0,0,0,,0,
,
,
,
]
(4)利用sklearn库实现tf-idf
from sklearn.feature_extraction.text import CountVectorizerfrom sklearn.feature_extraction.text import TfidfTransformerx_train = ['TF-IDF 主要 思想 是','算法 一个 重要 特点 可以 脱离 语料库 背景','如果 一个 网页 被 很多 其他 网页 链接 说明 网页 重要']x_test=['原始 文本 进行 标记','主要 思想']#该类会将文本中的词语转换为词频矩阵,矩阵元素a[i][j] 表示j词在i类文本下的词频vectorizer = CountVectorizer(max_features=10)#该类会统计每个词语的tf-idf权值tf_idf_transformer = TfidfTransformer()#将文本转为词频矩阵并计算tf-idftf_idf = tf_idf_transformer.fit_transform(vectorizer.fit_transform(x_train))#将tf-idf矩阵抽取出来,元素a[i][j]表示j词在i类文本中的tf-idf权重x_train_weight = tf_idf.toarray()#对测试集进行tf-idf权重计算tf_idf = tf_idf_transformer.transform(vectorizer.transform(x_test))x_test_weight = tf_idf.toarray() # 测试集TF-IDF权重矩阵print('输出x_train文本向量:')print(x_train_weight)print('输出x_test文本向量:')print(x_test_weight)输出x_train文本向量:[[0.70710678 0. 0.70710678 0. 0. 0.0. 0. 0. 0. ][0. 0.3349067 0. 0.44036207 0. 0.440362070.44036207 0.44036207 0. 0.3349067 ][0. 0.22769009 0. 0. 0.89815533 0.0. 0. 0.29938511 0.22769009]]输出x_test文本向量:[[0. 0. 0. 0. 0. 0. 0. 0. 0. 0.][0. 0. 1. 0. 0. 0. 0. 0. 0. 0.]]
(5)自己实现tf-idf
# -*- coding: utf-8 -*-from collections import defaultdictimport mathimport operator"""函数说明:创建数据样本Returns:dataset - 实验样本切分的词条classVec - 类别标签向量"""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] # 类别标签向量,1代表好,0代表不好return dataset, classVec"""函数说明:特征选择TF-IDF算法Parameters:list_words:词列表Returns:dict_feature_select:特征选择词字典"""def feature_select(list_words):#总词频统计doc_frequency=defaultdict(int)for word_list in list_words:for i in word_list:doc_frequency[i]+=1#计算每个词的TF值word_tf={} #存储没个词的tf值for i in doc_frequency:word_tf[i]=doc_frequency[i]/sum(doc_frequency.values())#计算每个词的IDF值doc_num=len(list_words)word_idf={} #存储每个词的idf值word_doc=defaultdict(int) #存储包含该词的文档数for i in doc_frequency:for j in list_words:if i in j:word_doc[i]+=1for i in doc_frequency:word_idf[i]=math.log(doc_num/(word_doc[i]+1))#计算每个词的TF*IDF的值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_selectif __name__=='__main__':data_list,label_list=loadDataSet() #加载数据features=feature_select(data_list) #所有词的TF-IDF值print(features)print("词袋容量:",len(features))# 结果:[('to', 0.0322394037469742), ('stop', 0.0322394037469742),('worthless', 0.0322394037469742),('my', 0.028288263356383563), ('dog', 0.028288263356383563),('him', 0.028288263356383563), ('stupid', 0.028288263356383563),('has', 0.025549122992281622), ('flea', 0.025549122992281622),('problems', 0.025549122992281622), ('help', 0.025549122992281622),('please', 0.025549122992281622), ('maybe', 0.025549122992281622),('not', 0.025549122992281622), ('take', 0.025549122992281622),('park', 0.025549122992281622), ('dalmation', 0.025549122992281622),('is', 0.025549122992281622), ('so', 0.025549122992281622),('cute', 0.025549122992281622), ('I', 0.025549122992281622),('love', 0.025549122992281622), ('posting', 0.025549122992281622),('garbage', 0.025549122992281622), ('mr', 0.025549122992281622),('licks', 0.025549122992281622), ('ate', 0.025549122992281622),('steak', 0.025549122992281622), ('how', 0.025549122992281622),('quit', 0.025549122992281622), ('buying', 0.025549122992281622),('food', 0.025549122992281622)]词袋容量: 32
(6)tf-idf的不足
TF-IDF算法实现简单快速,但是仍有许多不足之处:
(1)没有考虑特征词的位置因素对文本的区分度,词条出现在文档的不同位置时,对区分度的贡献大小是不一样的。
(2)按照传统TF-IDF,往往一些生僻词的IDF(反文档频率)会比较高、因此这些生僻词常会被误认为是文档关键词。
(3)传统TF-IDF中的IDF部分只考虑了特征词与它出现的文本数之间的关系,而忽略了特征项在一个类别中不同的类别间的分布情况。
(4)对于文档中出现次数较少的重要人名、地名信息提取效果不佳。
