使用 TfidfVectorizer 来计算 TF-IDF
scikit-learn 中的 TfidfVectorizer 是用于将文件数据转换为 TF-IDF 特征向量的工具,他能够将一组文本(例如文档集合或句子集合)转换为矩阵形式,其中每个文档使用 TF-IDF 的特征向量表示。这种转换通常用于文本分类、信息检索、自然语言处理等领域。
函数签名
class TfidfVectorizer(
# 文档的数据格式, 文件路径 | 文件对象 或字符串,默认是字符串
# 他接受的每一个文档的方式
input: "filename"|"file"|"content" = "content",
encoding: str = "utf-8",
decode_error: "strict" | "ignore" | "replace" = "strict",
strip_accents: "ascii" | "unicode" | callable = None,
lowercase: bool = True, # 自动转换为小写
preprocessor: callable = None, # 预处理器,对文本进行额外的处理操作
tokenizer: callable = None, # 分词器,用于将文本划分为词语的自定义函数
# 指定分析级别,word 按照单词分析,char 按照字符分析,char_wb 按照字符窗口分析
analyzer: "word" | "char" | "char_wb" | callable = "word",
# 指定 n-grams 范围,默认是 (1,1) 即仅仅考虑单个单词,(1, 2) 会同时考虑单个词和两个词的组合
ngram_range: tuple(min_n,max_n) = (1,1),
stop_words: "english" = "english", # 停用词列表,过滤例如 and or 这样的没意义介词
# 是 [0, 1] 范围浮动的数值, 忽略出现超过文档 max_df 百分比的词汇,过滤掉过于常用的词
# 是针对于语料库的停用词
max_df: float | int = 1,
# 是 [0, 1] 忽略出现低于文档 mid_df 百分比的词汇,过滤掉过于罕见的词
mid_df: float | int = 1,
max_features: int = None, # 限制特征的最大数量,只保留重要的前 max_features 个词汇
vocabulary: iterable = None, # 自定义词汇表,只计算特定词汇的 TF-IDF 值
smooth_idf: bool = True, # 是否平滑 IDF,即即使词只出现了一次器 IDF 也不会为 0
use_idf:bool = True, # 启用 IDF 加权,否则 IDF(t) = 1
norm: "l1" | "l2" = "l2", # 归一化方式,即将每个文档的向量长度标准化
) -> scipy.sparse.csr_matrix:
idf_: narray # 如果 user_idf=True 这里是 idf 的值其大小是 shape(n_features)
vocabulary_: dict # terms -> feature
def build_preprocessor() -> callable:
"""返回一个函数,用于对文本进行预处理,由 preprocessor 参数传入"""
pass
def build_tokenizer() -> callable:
"""返回一个函数,用于对文档进行分词(tokens),由 tokenizer 参数传入"""
pass
def fit_transform(raw_documents: iterable):
"""真正的执行 TF-IDF 计算的方法, raw_documents 即要处理的文档集合
他实际上是 fit() -> transform()
结果是一个稀疏矩阵(即大部分都为 0 的矩阵)
矩阵的形状是(语料库大小,features),即语料库的每个 items 对应了整个语料库中 features 的特征值
"""
pass
def get_feature_names_out(input_features=None) -> ndarray[str]:
"""获取所有特征名称,说白了就是所有的词"""
pass
def_stop_words() -> list:
"""获取停止词"""
pass
工作流程: CountVectorizer -> TfidfTransformer
TfidfVectorizer 的整个工作流程:
- 文件分词: 他将每个文档拆解为独立的词汇(tokens)
- 构建词汇表: 统计所有文档中出现的词汇,建立一个词汇表
- 计算 TF: 计算每个词在每个文档中的词频(TF)
- 计算 IDF: 基于所有文档出现的情况计算每个词的逆文档频率(IDF)
- 生成特征矩阵: 每个文档根据其包含的词及对应的 TF-IDF 值生成一个向量,形成文档-词矩阵
Tips
tokens 也叫做 features 即特征值
TfidfVectorizer 相当于 CountVectorizer(计算 TF) 后执行 TfidfTransformer(计算 IDF):
import pandas as pd
from sklearn.feature_extraction.text import CountVectorizer
# 假设 5 个文档
docs=[
"the house had a tiny little mouse",
"the cat saw the mouse",
"the mouse ran away from the house",
"the cat finally ate the mouse",
"the end of the mouse story"
]
# 统计词频
cv = CountVectorizer()
word_count_vector = cv.fit_transform(docs)
word_count_vector.shape # (5, 16) 结果是 5 个文档 16 个唯一单词
word_count_vector.toarray()
array([[0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1],
[0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 2, 0],
[0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 2, 0],
[1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 2, 0],
[0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 2, 0]])
cv.get_feature_names_out()
array(['ate', 'away', 'cat', 'end', 'finally', 'from', 'had', 'house',
'little', 'mouse', 'of', 'ran', 'saw', 'story', 'the', 'tiny'],
dtype=object)
之后就是计算 IDF 值,他是使用 TfidfTransformer:
from sklearn.feature_extraction.text import TfidfTransformer
tfidf_transformer = TfidfTransformer(smooth_idf=True,use_idf=True)
tfidf_transformer.fit(word_count_vector)
# IDF 权重
pd.DataFrame(tfidf_transformer.idf_, index=cv.get_feature_names_out(), columns=['idf_weights'])
idf_weights
ate 2.098612
away 2.098612
cat 1.693147
end 2.098612
finally 2.098612
from 2.098612
had 2.098612
house 1.693147
little 2.098612
mouse 1.000000
of 2.098612
ran 2.098612
saw 2.098612
story 2.098612
the 1.000000
tiny 2.098612
比较典型的就是 mouse 和 the,他们在每个文档中都存在因此具有最低的 IDF 值。结合他们就能够获取 TF-IDF:
count_vector=cv.transform(docs)
tf_idf_vector=tfidf_transformer.transform(count_vector)
feature_names = cv.get_feature_names()
#get tfidf vector for first document
first_document_vector=tf_idf_vector[0]
#print the first scores
pd.DataFrame(first_document_vector.T.todense(), index=feature_names, columns=["tfidf"])
tfidf
ate 0.000000
away 0.000000
cat 0.000000
end 0.000000
finally 0.000000
from 0.000000
had 0.493562
house 0.398203
little 0.493562
mouse 0.235185
of 0.000000
ran 0.000000
saw 0.000000
story 0.000000
the 0.235185
tiny 0.493562
上面是第一个文档 TF-IDF,之所以某些单词有分数是因为我们的第一个文档只包含其中的部分(其中区少了 a 是因为在停止词中删除了他)。上面的结果是很有代表性的,像 the、mouse 这些在所有文档中都有的值一定是低的因为不具备特征,had、tiny、little 就比较独特。
直接使用 TfidfVectorizer
最后我们说下 TfidfVectorizer,他能够一次性的计算上面的所有:
from sklearn.feature_extraction.text import TfidfVectorizer
# settings that you use for count vectorizer will go here
tfidf_vectorizer=TfidfVectorizer()
# just send in all your docs here
tfidf_vectorizer_vectors=tfidf_vectorizer.fit_transform(docs)
# get the first vector out (for the first document)
first_vector_tfidfvectorizer=tfidf_vectorizer_vectors[0]
# place tf-idf values in a pandas data frame
df = pd.DataFrame(first_vector_tfidfvectorizer.T.todense(), index=tfidf_vectorizer.get_feature_names_out(), columns=["tfidf"]) df.sort_values(by=["tfidf"],ascending=False)
fit、transform 和 fit_transform
在整个使用过程中主要的就是 fit 和 transform 以及同时使用的 fit_transform:
- fit: 拟合数据,生成整个语料库的词汇表
- transform: 使用已有的词汇表,仅将新的文档转换为向量表示
根据他们的介绍就应该知道,在我们创建一个新的语料库时就需要首先 fit 来拟合数据生成整个语料库的词汇表,然后执行 transform 来转换为向量生成最终的语料库。
在已有的语料库上我们获取的一个新的数据需要与语料库进行匹配就不再需要 fit 了而是使用已有的(语料库提供的)词汇表来生成稀疏矩阵,这就只需要 transform 就行了。此时如果 fit_transform 就会导致生成新的语料库这可能和原始文档集不一致。
返回稀疏矩阵: scipy.sparse.csr_matrix
在执行 TF-IDF 返回的结果是稀疏矩阵,即里面包含了大量的 0 值的矩阵。因此 TfidfVectorizer 返回更为高效的 scipy.sparse.csr_matrix
,而不是 numpy 中的 ndarray。
Tips
有些例子会直接将结果 csr_matrix.toarray()
,此时就是用稠密矩阵来描述稀疏矩阵,如果矩阵比较大会导致内存溢出。
相关问题
CountVectorizer 计算 TF
引申下使用CountVectorizer来统计下词频:
from sklearn.feature_extraction.text import CountVectorizer
from scipy.sparse import csr_matrix
import pandas as pd
# 示例文本数据
documents = [
"apple banana apple",
"banana orange",
"apple orange banana apple"
]
def ngrams(string):
string = re.sub(r"[^a-zA-Z0-9 ]", r"", string)
return ["".join(ngram.strip()) for ngram in string.split(" ") if ngram.strip() != ""]
# 创建 CountVectorizer 对象
vectorizer = CountVectorizer(analyzer=ngrams)
# 转换文本数据为计数矩阵(稀疏矩阵)
X = vectorizer.fit_transform(documents)
# 获取特征名称
features = vectorizer.get_feature_names_out()
# 计算每个特征的数量(利用稀疏矩阵的特性)
# 有些教程是 X.toarray().sum(axis=0) 如果量大可能会导致内存溢出
counts = X.sum(axis=0).A1 # 使用 .A1 转换为一维数组
# 创建 DataFrame 进行排序
feature_counts = pd.DataFrame({'Feature': features, 'Count': counts})
sorted_feature_counts = feature_counts.sort_values(by='Count', ascending=False)
print(sorted_feature_counts)
feature/token 都是啥
feature/token 都是特征值,他们实际上就是所谓的向量纬度。例如描述平面空间需要 x,y 两个 feature 也就是所谓的二维空间。而描述三维空间就是 x,y,z。我们计算 TF-IDF 时对其进行分词,如果得到 300 个分词,也就是 300 维向量,也就是一个字符串需要从 300 个维度来描述,当然在很多维度他是没有数据的,这也是为什么 TF-IDF 的结果都是非常稀疏的。
因此 featrue/token 以及维度等说法都是说明在一个特定系统描述一个唯一值需要的参数的数量。