NLP基础学习4--tfidf和互信息

本文深入探讨了TF-IDF原理及其在文本矩阵化中的应用,通过Python示例展示了如何使用TF-IDF特征值进行文本权重设置。此外,还介绍了互信息的原理,以及如何利用互信息进行特征筛选,包括使用sklearn库实现的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

继续做早鸟,首先这一期的任务提纲:

  1. TF-IDF原理。
  2. 文本矩阵化,使用词袋模型,以TF-IDF特征值为权重。(可以使用Python中TfidfTransformer库)
  3. 互信息的原理。
  4. 使用第二步生成的特征矩阵,利用互信息进行特征筛选

TF-IDF原理

感觉在word2vec 特别是现在的contextual word embedding之后,利用tf-idf直接向量化文本几乎已经弃用了,但是tf-idf作为权重,用来进行句子或者篇章中词向量的加权,也依然在被使用。
第一次接触tf-idf是很早以前,读吴军先生的《数学之类》[5]的时候,当时就被一系列文本向量化的表示和操作所吸引。回到主题,tf-idf实际上是两个指标,即tf Term Frequency,词频,idf Inverse Document Frequency 逆文本频率 。

TF-IDF的主要思想是:如果某个词在一篇文章中出现的频率高,并且在其他文章中很少出现,则认为此词具有很好的类别区分能力,适合用来分类。
形式化的表达为: t f i , j = n i , j ∑ k n k , j tf_{i,j} = \frac{n_{i,j}}{\sum_k n_{k,j}} tfi,j=knk,jni,j i d f i = ∣ D ∣ ∣ j : t i ∈ D j ∣ idf_i = \frac{|D|}{|{j:t_i \in D_j}|} idfi=j:tiDjD t f tf tf- i d f i = t f i , j ∗ i d f i idf _i= tf_{i,j} * idf_i idfi=tfi,jidfi。其中i 表示词 i i i , j j j 表示所属文章 D j D_j Dj, ∣ x ∣ |x| x 表示集合 x x x中元素的个数。

以TF-IDF特征值为权重的文本矩阵化

直接上代码

# -*- coding: utf-8 -*-
# @Time : 2019/4/12 11:43
# @Author : Lei Wang
# @Site : 
# @File : tfidf.py
# @Software: PyCharm


import pkuseg
from sklearn.feature_extraction.text import CountVectorizer

from sklearn.feature_extraction.text import TfidfTransformer

text = []
with open(r'..\cnews\cnews.train.txt', 'r', encoding = 'utf-8') as fsource:
    text_line= fsource.readline()
    seg = pkuseg.pkuseg()
    seg_list = seg.cut(text_line)
    text.extend(seg_list)

with open(r'..\stoplist_baidu.txt', 'r', encoding = 'utf-8') as fstop:
    content = fstop.read()
    stop_words = content.split('\n')


count_vectorizer = CountVectorizer(stop_words=stop_words)
tfidf_transformer = TfidfTransformer()
tfidf = tfidf_transformer.fit_transform(count_vectorizer.fit_transform(text))
print (tfidf)

print('-------------给个小一点的例子--------------')

corpus = [          'This is the first document.',
		'This is the second second document.',
		'And the third one.',
		'Is this the first document?',
		]

s_count_vectorizer = CountVectorizer()
s_tfidf_transformer = TfidfTransformer()
s_tfidf = s_tfidf_transformer.fit_transform(s_count_vectorizer.fit_transform(corpus))
print (s_tfidf)

结果截图:
在这里插入图片描述

互信息的原理

谈起互信息这个话题,还是依着《数学之美》[5]思路,先从信息和熵的定义开始说。信息是用来消除不确定性的。换句话说,知道的信息越多,不确定性就越低。对于一个变量 X X X,如果我们知道,我们知道它的分布 P ( X ) P(X) P(X), 则可以定义它的信息熵为: H ( X ) = − ∑ x ∈ X P ( x ) l o g P ( x ) H(X) = - \sum_{x \in X} P(x) logP(x) H(X)=xXP(x)logP(x)。 这里继续给出条件熵的概念,类比于条件概率分布, H ( X ∣ Y ) = − ∑ x ∈ X , y ∈ Y P ( x , y ) l o g P ( x ∣ y ) H(X|Y)=-\sum_{x \in X,y \in Y} P(x,y)logP(x|y) H(XY)=xX,yYP(x,y)logP(xy)那么问题来了,互信息呢? 互信息其实是用来度量两个事件的相关性,其定义如下:

I ( X ; Y ) = − ∑ x ∈ X , y ∈ Y P ( x , y ) P ( x ) P ( y ) = H ( X ) − H ( X ∣ Y ) I(X;Y) = -\sum_{x \in X,y \in Y} \frac{P(x,y)}{P(x)P(y)} = H(X) - H(X|Y) I(X;Y)=xX,yYP(x)P(y)P(x,y)=H(X)H(XY)

关于互信息,其实我当年做过一个很有意思的推导,就是想推导多元的互信息熵。我当时只是推导了三元,基本结论是
I ( X ; Y ; Z ) = I ( X ; Y ) − I ( X ; Y ∣ Z ) = I ( X ; Y ) − ∑ l o g P ( x y z ) P ( z ) P ( x z ) P ( y z ) I(X;Y;Z) = I(X;Y) - I(X;Y|Z) = I(X;Y) - \sum log\frac{P(xyz)P(z)}{P(xz)P(yz)} I(X;Y;Z)=I(X;Y)I(X;YZ)=I(X;Y)logP(xz)P(yz)P(xyz)P(z)
正确性有待各位有心的观众检验,多元互信息的表达我参考文献[6]。
关于互信息,数学之美上还有一个关于tf-idf的信息学解释,有兴趣可以去看书的108-109页的相关内容,写的非常清晰易懂,这里就不再赘述了。

既然写到这里,那就顺便写一下深度学习中最常用的相对熵和交叉熵。相对熵,也就是KL散度的定义,他是用来衡量两个取值为正的函数f(X)和g(X)的相似性,即
K L ( f ( X ) ∣ ∣ g ( X ) ) = ∑ x ∈ X f ( x ) l o g f ( x ) g ( x ) KL(f(X)||g(X))=\sum_{x \in X} f(x) log\frac{f(x)}{g(x)} KL(f(X)g(X))=xXf(x)logg(x)f(x), 而交叉熵则是 H ( f ( x ) , g ( x ) ) = − ∑ x ∈ X f ( x ) l o g g ( x ) = H ( f ( x ) ) + K L ( f ( x ) ∣ ∣ g ( x ) ) H(f(x),g(x))=-\sum_{x \in X} f(x) logg(x) = H(f(x)) +KL(f(x)||g(x)) H(f(x),g(x))=xXf(x)logg(x)=H(f(x))+KL(f(x)g(x))。当然这两个熵也都有对应的连续变量形式的表达。可以看出:

  1. 两个完全相同的函数,交叉熵为0
  2. 交叉熵越大,两个函数差异越大;反之亦然
  3. 对于概率密度或者概率分布函数,如果取值均大于0,交叉熵可以度量两个随机分布的差异性

利用互信息进行特征筛选

特征选择指的是删除了原始特征里和结果预测关系不大的特征,而不像降维会去做特征的计算组合构成了新的特征。 这里参考了文献[7]
常用的方法有:过滤法,包裹法和嵌入法

1. 过滤型

方法:评价单个特征和结果之间的相关程度,排序留下Top相关的部分。
评价方式:Pearson相关系数、互信息
缺点:没有考虑到特征之间的关联作用,可能把有用的关联特征踢掉。因此工业界使用的比较少
python包:SelectKBest指定过滤个数、SelectPercentile指定过滤的百分比

from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import chi2
iris = load_iris()
X, y = iris.data, iris.target
X.shape

(150, 4)

X_new = SelectKBest(chi2, k = 2).fit_transform(X, y)
X_new.shape

(150, 2)

2.包裹型

方法:把特征选择看做一个特征子集搜索问题,筛选各种特征子集,用模型评估效果(递归特征删除算法,RFE)。
应用在LR上过程:用全量特征跑一个模型;删掉5~10%的弱特征,观察准确率/AUC的变化;逐步进行,直至准确率/AUC出现大的下滑停止。
python:RFE

from sklearn.feature_selection import RFE
from sklearn.linear_model import LinearRegression
from sklearn.datasets import load_boston
boston = load_boston()
X = boston["data"]
Y = boston["target"]
names = boston["feature_names"]
lr = LinearRegression()
rfe = RFE(lr, n_features_to_select = 1)
rfe.fit(X, Y)
print("Features sorted by their rank:")
print(sorted(zip(map(lambda x: round(x, 4), rfe.ranking_),names)))

Features sorted by their rank:
[(1, ‘NOX’), (2, ‘RM’), (3, ‘CHAS’), (4, ‘PTRATIO’), (5, ‘DIS’), (6, ‘LSTAT’), (7, ‘RAD’), (8, ‘CRIM’), (9, ‘INDUS’), (10, ‘ZN’), (11, ‘TAX’), (12, ‘B’), (13, ‘AGE’)]

3.嵌入型

方法:根据模型来分析特征重要性,最常见的方式为正则化方式来做特征选择
举例:举个例子,最早在电商用LR做CRT预估,在3-5亿维系数的特征上用L1正则化的LR模型。剩余2-3千万的feature,意味着其他的feature的重要程度不够。
python:feature_selection.SelectFromModel选出权重不为0的特征

from sklearn.svm import LinearSVC
from sklearn.datasets import load_iris
from sklearn.feature_selection import SelectFromModel
iris = load_iris()
X, y = iris.data, iris.target
X.shape

(150, 4)

lsvc = LinearSVC(C = 0.01, penalty = "l1", dual = False).fit(X, y)
model = SelectFromModel(lsvc, prefit = True)
X_new = model.transform(X)
X_new.shape

(150, 3)

回到主题,如何利用互信息进行特征筛选, 还是利用sklearn的库,因为这个需要标签,所以用了IMDB,代码如下

# -*- coding: utf-8 -*-
# @Time : 2019/4/13 12:11
# @Author : Lei Wang
# @Site : 
# @File : feature_selection.py
# @Software: PyCharm

from sklearn.feature_extraction.text import CountVectorizer

from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import mutual_info_classif


#选择K个最好的特征,返回选择特征后的数据
#arr = SelectKBest(mutual_info_classif, k=2).fit_transform(tfidf, target[:99])


from nltk.corpus import stopwords
import collections
import pandas as pd
import numpy as np
import os
import codecs

pos_list=[]

with open('../Sentiment_IMDB/aclImdb/train/pos_all.txt','r',encoding='utf8')as f:
    line=f.readlines()
    pos_list.extend(line)
neg_list=[]
with open('../Sentiment_IMDB/aclImdb/train/neg_all.txt','r',encoding='utf8')as f:
    line=f.readlines()
    neg_list.extend(line)
#创建标签
label=[1 for i in range(12500)]
label.extend([0 for i in range(12499)])
#评论内容整合
content=pos_list.extend(neg_list)
content=pos_list

stop_words=set(stopwords.words('english'))
count_vectorizer = CountVectorizer(stop_words=stop_words)
tfidf_transformer = TfidfTransformer()
tfidf = tfidf_transformer.fit_transform(count_vectorizer.fit_transform(content))
print (tfidf.shape)

arr = SelectKBest(mutual_info_classif, k=2).fit_transform(tfidf[:24999], label)
print(arr)

互信息特征筛选

参考文献

  1. 文本挖掘预处理之TF-IDF:文本挖掘预处理之TF-IDF - 刘建平Pinard - 博客园 (https://www.cnblogs.com/pinard/p/6693230.html)
  2. 使用不同的方法计算TF-IDF值:使用不同的方法计算TF-IDF值 - 简书(https://www.jianshu.com/p/f3b92124cd2b)
  3. sklearn-点互信息和互信息:sklearn:点互信息和互信息 - 专注计算机体系结构 - 优快云博客 (https://blog.youkuaiyun.com/u013710265/article/details/72848755)
  4. 如何进行特征选择(理论篇)机器学习你会遇到的“坑”:如何进行特征选择(理论篇)机器学习你会遇到的“坑” (https://baijiahao.baidu.com/s?id=1604074325918456186&wfr=spider&for=pc)
  5. 吴军. 数学之美[M]. 人民邮电出版社, 2012
  6. Multivariate mutual information, wiki,(https://en.wikipedia.org/wiki/Multivariate_mutual_information)
  7. bd-liuming ,优快云(https://blog.youkuaiyun.com/fisherming/article/details/79925574 )
特征提取环节调用 CountVectorizer 函数将文本中的词语转换为词频矩阵,矩 阵中的元素 a[i][j]表示 j 词在 i 类文本下的词频;调用 TfidfTransformer 函数计算 TF-IDF 权值并转化为矩阵,矩阵中元素 w[i][j]表示 j 词在 i 类文本中的 TF-IDF 权重。由于本节选取 4 分类,因此,选用 4 个中心点。随后进行模型的训练,调用 fit 函数将数据输入到分类器中,训练完成后保存模型,并查看训练集的准确率分 类情况。 import pandas as pd import re import jieba import numpy as np import matplotlib.pyplot as plt from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.cluster import KMeans from sklearn.metrics import adjusted_rand_score, normalized_mutual_info_score, v_measure_score from sklearn.model_selection import train_test_split from sklearn.decomposition import TruncatedSVD from sklearn.pipeline import make_pipeline from sklearn.preprocessing import Normalizer # --------------------- 1. 数据读取预处理 --------------------- try: df = pd.read_csv(r'C:\Users\陈梓沂的小伙伴\Desktop\自然语言处理\自然语言处理\课设\文本聚类\news.csv') df.columns = ['text', 'category'] except FileNotFoundError: raise FileNotFoundError("错误:数据文件不存在,请检查路径是否正确!") except pd.errors.ParserError as e: raise RuntimeError(f"解析失败,请检查分隔符是否正确:{e}") except ValueError: raise ValueError("数据格式错误:至少需要两列(文本类别)") def preprocess_text(text): """文本清洗、分词、去停用词""" if not isinstance(text, str): return '' # 去除HTML标签、URL等特殊内容 text = re.sub(r'<[^>]+>', '', text) # 移除HTML标签 text = re.sub(r'https?://\S+', '', text) # 移除URL text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z0-9]', ' ', text) # 保留中英文数字 # 分词 words = jieba.cut(text, cut_all=False) # 加载停用词 try: with open(r'C:\Users\陈梓沂的小伙伴\Desktop\自然语言处理\自然语言处理\课设\文本聚类\stopword1.txt', 'r', encoding='utf-8-sig') as f: stopwords = {line.strip() for line in f if line.strip()} except Exception: stopwords = {'的', '了', '在', '是', '我', '有', '', '就', '不', '人'} # 过滤停用词短词 filtered_words = [word for word in words if word not in stopwords and len(word) > 1] return ' '.join(filtered_words) df['clean_text'] = df['text'].apply(preprocess_text) # 划分训练集测试集 (80%训练, 20%测试) train_df, test_df = train_test_split(df, test_size=0.2, random_state=42, stratify=df['category']) # --------------------- 2. 特征提取 --------------------- # 使用TF-IDF提取特征,调整参数以优化特征质量 tfidf = TfidfVectorizer( max_features=2000, # 增加特征数量 min_df=5, # 忽略出现少于5次的词 max_df=0.8, # 忽略出现在80%以上文档中的词 ngram_range=(1, 2) # 考虑一元二元词组 ) # 在训练集上拟合TF-IDF X_train = tfidf.fit_transform(train_df['clean_text']) X_test = tfidf.transform(test_df['clean_text']) # 使用SVD降维 (可选,可提高聚类性能) svd = TruncatedSVD(100) # 降到100维 normalizer = Normalizer(copy=False) lsa = make_pipeline(svd, normalizer) X_train_lsa = lsa.fit_transform(X_train) X_test_lsa = lsa.transform(X_test) # --------------------- 3. 确定最佳聚类数 --------------------- # 使用肘部法则确定最佳聚类数 distortions = [] K_range = range(2, 15) for k in K_range: kmeans = KMeans(n_clusters=k, random_state=42, n_init=10) kmeans.fit(X_train_lsa) distortions.append(kmeans.inertia_) # 绘制肘部图 plt.figure(figsize=(10, 6)) plt.plot(K_range, distortions, 'bx-') plt.xlabel('聚类数量 (k)') plt.ylabel('距离平方') plt.title('肘部法则确定最佳聚类数') plt.grid(True) plt.savefig(r'C:\Users\陈梓沂的小伙伴\Desktop\自然语言处理\自然语言处理\课设\文本聚类\elbow_plot.png') plt.close() # 自动选择拐点 (当下降幅度小于阈值时) diff = np.diff(distortions) diff_r = diff[1:] / diff[:-1] best_k = np.argmin(diff_r) + 3 # +3 因为从k=2开始 print(f"根据肘部法则确定的最佳聚类数: {best_k}") # --------------------- 4. 聚类模型训练预测 --------------------- # 在训练集上训练聚类模型 kmeans = KMeans(n_clusters=best_k, random_state=42, n_init=10) kmeans.fit(X_train_lsa) # 在训练集测试集上进行预测 train_df['cluster'] = kmeans.predict(X_train_lsa) test_df['cluster'] = kmeans.predict(X_test_lsa) # --------------------- 5. 结果保存 --------------------- # 保存完整结果 df_result = pd.concat([train_df, test_df]) df_result[['text', 'category', 'cluster']].to_csv( r'C:\Users\陈梓沂的小伙伴\Desktop\自然语言处理\自然语言处理\课设\文本聚类\news_clustered.csv', index=False, encoding='utf-8-sig' ) # --------------------- 6. 模型评估 --------------------- # 将类别标签转换为数值 train_df['category_code'] = pd.Categorical(train_df['category']).codes test_df['category_code'] = pd.Categorical(test_df['category']).codes # 在测试集上评估模型性能 test_metrics = { 'ARI(调整兰德系数)': adjusted_rand_score(test_df['category_code'], test_df['cluster']), 'NMI(标准化互信息)': normalized_mutual_info_score(test_df['category_code'], test_df['cluster']), 'V-measure': v_measure_score(test_df['category_code'], test_df['cluster']) } # 在训练集上评估模型性能(参考) train_metrics = { 'ARI(调整兰德系数)': adjusted_rand_score(train_df['category_code'], train_df['cluster']), 'NMI(标准化互信息)': normalized_mutual_info_score(train_df['category_code'], train_df['cluster']), 'V-measure': v_measure_score(train_df['category_code'], train_df['cluster']) } print("\n--- 训练集评估结果 ---") for name, value in train_metrics.items(): print(f"{name}:{value:.4f}") print("\n--- 测试集评估结果 ---") for name, value in test_metrics.items(): print(f"{name}:{value:.4f}") # 保存评估结果 eval_df = pd.DataFrame({ '指标': list(test_metrics.keys()), '训练集': list(train_metrics.values()), '测试集': list(test_metrics.values()) }) eval_df.to_csv( r'C:\Users\陈梓沂的小伙伴\Desktop\自然语言处理\自然语言处理\课设\文本聚类\evaluation_results.csv', index=False, encoding='utf-8-sig' )
最新发布
06-17
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值