TF-IDF
什么是TF-IDF值
在做具体的文本分类之前,我们先来看一下文本处理中的重要概念TF-IDF。
TF-IDF 是一个统计方法,用来评估某个词语对于一个文件集或文档库中的其中一份文件的重要程度。
TF-IDF 实际上是两个词组 Term Frequency 和 Inverse Document Frequency 的总称,两者缩写为 TF 和 IDF,分别代表了词频和逆向文档频率。
- 词频TF计算了一个单词在文档中出现的次数,它认为一个单词的重要性和它在文档中出现的次数呈正比。
- 逆向文档频率IDF,是指一个单词在文档中的区分度。它认为一个单词出现在的文档数越少,就越能通过这个单词把该文档和其他文档区分开。IDF 越大就代表该单词的区分度越大。
所以 TF-IDF 实际上是词频 TF 和逆向文档频率 IDF 的乘积。这样我们倾向于找到 TF 和 IDF 取值都高的单词作为区分,即这个单词在一个文档中出现的次数多,同时又很少出现在其他文档中。这样的单词适合用于分类。
TF-IDF如何计算
首先我们看下词频 TF 和逆向文档概率 IDF 的公式。

为什么 IDF 的分母中,单词出现的文档数要加 1 呢?因为有些单词可能不会存在文档中,为了避免分母为 0,统一给单词出现的文档数都加 1。
你可以看到,TF-IDF值就是TF与IDF的乘积, 这样可以更准确地对文档进行分类。比如“我”这样的高频单词,虽然TF词频高,但是IDF值很低,整体的TF-IDF也不高。
我在这里举个例子。假设一个文件夹里一共有10篇文档,其中一篇文档有1000个单词,“this”这个单词出现20次,“bayes”出现了5次。“this”在所有文档中均出现过,而“bayes”只在2篇文档中出现过。我们来计算一下这两个词语的TF-IDF值。
针对“this”,计算TF-IDF值:

所以TF-IDF=0.02(-0.0414)=-8.28e-4。
针对“bayes”,计算 TF-IDF 值:

TF-IDF=0.0050.5229=2.61e-3。
很明显“bayes”的 TF-IDF 值要大于“this”的 TF-IDF 值。这就说明用“bayes”这个单词做区分比单词“this”要好。
sklearn中求TF-IDF
在 sklearn 中我们直接使用TfidfVectorizer类,它可以帮我们计算单词TF-IDF向量的值。在这个类中,取 sklearn计算的对数log时,底数是e,不是10。
创建 TfidfVectorizer 的方法是:
TfidfVectorizer(stop_words=stop_words, token_pattern=token_pattern)
我们在创建的时候,有两个构造参数,可以自定义停用词stop_words和规律规则token_pattern。需要注意的是传递的数据结构,停用词stop_words是一个列表List类型,而过滤规则token_pattern是正则表达式。
什么是停用词?
停用词就是在分类中没有用的词,这些词一般词频TF高,但是IDF很低,起不到分类的作用。为了节省空间和计算时间,我们把这些词作为停用词stop words,告诉机器这些词不需要帮我计算。
当我们创建好TF-IDF向量类型时,可以用fit_transform帮我们计算,返回给我们文本矩阵,该矩阵表示了每个单词在每个文档中的TF-IDF值。
在我们进行fit_transform拟合模型后,我们可以得到更多的TF-IDF向量属性,比如,我们可以得到词汇的对应关系(字典类型)和向量的IDF值,当然也可以获取设置的停用词stop_words。
举个例子,假设我们有 4 个文档:
- 文档 1:this is the bayes document;
- 文档 2:this is the second second document;
- 文档 3:and the third one;
- 文档 4:is this the document。
现在想要计算文档里都有哪些单词,这些单词在不同文档中的 TF-IDF 值是多少呢?
首先我们创建 TfidfVectorizer 类:
from sklearn.feature_extraction.text import TfidfVectorizertfidf_vec = TfidfVectorizer()
然后我们创建 4 个文档的列表 documents,并让创建好的 tfidf_vec 对 documents 进行拟合,得到 TF-IDF 矩阵:
documents = ['this is the bayes document','this is the second second document','and the third one','is this the document']tfidf_matrix = tfidf_vec.fit_transform(documents)
输出文档中所有不重复的词:
print('不重复的词:', tfidf_vec.get_feature_names())
运行结果
不重复的词: ['and', 'bayes', 'document', 'is', 'one', 'second', 'the', 'third', 'this']
输出每个单词对应的 id 值:
print('每个单词的ID:', tfidf_vec.vocabulary_)
运行结果
每个单词的ID: {'this': 8, 'is': 3, 'the': 6, 'bayes': 1, 'document': 2, 'second': 5, 'and': 0, 'third': 7, 'one': 4}
输出每个单词在每个文档中的 TF-IDF 值,向量里的顺序是按照词语的 id 顺序来的:
print('每个单词的tfidf值:\n', tfidf_matrix.toarray())
运行结果:
每个单词的tfidf值:[[0. 0.63314609 0.40412895 0.40412895 0. 0.0.33040189 0. 0.40412895][0. 0. 0.27230147 0.27230147 0. 0.853225740.22262429 0. 0.27230147][0.55280532 0. 0. 0. 0.55280532 0.0.28847675 0.55280532 0. ][0. 0. 0.52210862 0.52210862 0. 0.0.42685801 0. 0.52210862]]
如何对文档进行分类
模块 1:对文档进行分词
在准备阶段里,最重要的就是分词。那么如果给文档进行分词呢?英文文档和中文文档所使用的分词工具不同。
在英文文档中,最常用的是NTLK包。NTLK包中包含了英文的停用词stop words、分词和标注方法。
import nltkword_list = nltk.word_tokenize(text) #分词nltk.pos_tag(word_list) #标注单词的词性
在中文文档中,最常用的是jieba包。jieba包中包含了中文的停用词stop words和分词方法。
import jiebaword_list = jieba.cut (text) #中文分词
模块 2:加载停用词表
我们需要自己读取停用词表文件,从网上可以找到中文常用的停用词保存在stop_words.txt,然后利用Python的文件读取函数读取文件,保存在stop_words 数组中。
stop_words = [line.strip().decode('utf-8') for line in io.open('stop_words.txt').readlines()]
模块 3:计算单词的权重
这里我们用到 sklearn 里的 TfidfVectorizer 类,上面我们介绍过它使用的方法。
直接创建TfidfVectorizer类,然后使用fit_transform方法进行拟合,得到TF-IDF特征空间features,你可以理解为选出来的分词就是特征。我们计算这些特征在文档上的特征向量,得到特征空间features。
tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5)features = tf.fit_transform(train_contents)
这里max_df参数用来描述单词在文档中的最高出现率。假设max_df=0.5,代表一个单词在50%的文档中都出现过了,那么它只携带了非常少的信息,因此就不作为分词统计。
一般很少设置min_df,因为min_df通常都会很小。
模块 4:生成朴素贝叶斯分类器
我们将特征训练集的特征空间train_features,以及训练集对应的分类train_labels传递给贝叶斯分类器 clf,它会自动生成一个符合特征空间和对应分类的分类器。
这里我们采用的是多项式贝叶斯分类器,其中alpha为平滑参数。为什么要使用平滑呢?因为如果一个单词在训练样本中没有出现,这个单词的概率就会被计算为0。但训练集样本只是整体的抽样情况,我们不能因为一个事件没有观察到,就认为整个事件的概率为0。为了解决这个问题,我们需要做平滑处理。
当alpha=1时,使用的是Laplace平滑。Laplace平滑就是采用加 1 的方式,来统计没有出现过的单词的概率。这样当训练样本很大的时候,加1得到的概率变化可以忽略不计,也同时避免了零概率的问题。
当使用的是Lidstone平滑。对于Lidstone平滑来说,alpha越小,迭代次数越多,精度越高。我们可以设置 alpha为0.001
# 多项式贝叶斯分类器from sklearn.naive_bayes import MultinomialNBclf = MultinomialNB(alpha=0.001).fit(train_features, train_labels)
模块 5:使用生成的分类器做预测
首先我们需要得到测试集的特征矩阵。
方法是用训练集的分词创建一个TfidfVectorizer类,使用同样的stop_words和max_df,然后用这个 TfidfVectorizer类对测试集的内容进行fit_transform拟合,得到测试集的特征矩阵test_features。
test_tf = TfidfVectorizer(stop_words=stop_words, max_df=0.5, vocabulary=train_vocabulary)test_features=test_tf.fit_transform(test_contents)
然后我们用训练好的分类器对新数据做预测。方法是使用 predict 函数,传入测试集的特征矩阵 test_features,得到分类结果predicted_labels。predict函数做的工作就是求解所有后验概率并找出最大的那个。
predicted_labels=clf.predict(test_features)
模块 6:计算准确率计算准确率
实际上是对分类模型的评估。我们可以调用sklearn中的metrics 包,在metrics中提供了accuracy_score函数,方便我们对实际结果和预测的结果做对比,给出模型的准确率。使用方法如下:
from sklearn import metricsprint metrics.accuracy_score(test_labels, predicted_labels)
中文文档分类完整代码
import osimport jiebafrom sklearn.feature_extraction.text import TfidfVectorizerfrom sklearn.naive_bayes import MultinomialNBfrom sklearn import metricsLABEL_MAP = {'体育': 0, '女性': 1, '文学': 2, '校园': 3}# 加载停用词with open('./text classification/stop/stopword.txt', 'rb') as f:STOP_WORDS = [line.strip() for line in f.readlines()]def load_data(base_path):""":param base_path: 基础路径:return: 分词列表,标签列表"""documents = []labels = []for root, dirs, files in os.walk(base_path): # 循环所有文件并进行分词打标for file in files:label = root.split('\\')[-1] # 因为windows上路径符号自动转成\了,所以要转义下labels.append(label)filename = os.path.join(root, file)with open(filename, 'rb') as f: # 因为字符集问题因此直接用二进制方式读取content = f.read()word_list = list(jieba.cut(content))words = [wl for wl in word_list]documents.append(' '.join(words))return documents, labelsdef train_fun(td, tl, testd, testl):"""构造模型并计算测试集准确率,字数限制变量名简写:param td: 训练集数据:param tl: 训练集标签:param testd: 测试集数据:param testl: 测试集标签:return: 测试集准确率"""# 计算矩阵tt = TfidfVectorizer(stop_words=STOP_WORDS, max_df=0.5)tf = tt.fit_transform(td)# 训练模型clf = MultinomialNB(alpha=0.001).fit(tf, tl)# 模型预测test_tf = TfidfVectorizer(stop_words=STOP_WORDS, max_df=0.5, vocabulary=tt.vocabulary_)test_features = test_tf.fit_transform(testd)predicted_labels = clf.predict(test_features)# 获取结果x = metrics.accuracy_score(testl, predicted_labels)return x# text classification与代码同目录下train_documents, train_labels = load_data('./text classification/train')test_documents, test_labels = load_data('./text classification/test')x = train_fun(train_documents, train_labels, test_documents, test_labels)print(x)
