- 为中文拼写生成器实现了背景预热功能,以提升首次使用时的性能。 - 更新了MessageStorageBatcher以支持可配置的提交批次大小和间隔,优化数据库写入性能。 - 增强版数据集生成器,对样本规模设置硬性限制并提升采样效率。 - 将AutoTrainer中的最大样本数增加至1000,以优化训练数据利用率。 - 对亲和兴趣计算器进行了重构,以避免并发初始化并优化模型加载逻辑。 - 引入批量处理机制用于语义兴趣评分,以应对高频聊天场景。 - 更新了配置模板以反映新的评分参数,并移除了已弃用的兴趣阈值。
148 lines
4.5 KiB
Python
148 lines
4.5 KiB
Python
"""TF-IDF 特征向量化器
|
||
|
||
使用字符级 n-gram 提取中文消息的 TF-IDF 特征
|
||
"""
|
||
|
||
from pathlib import Path
|
||
|
||
from sklearn.feature_extraction.text import TfidfVectorizer
|
||
|
||
from src.common.logger import get_logger
|
||
|
||
logger = get_logger("semantic_interest.features")
|
||
|
||
|
||
class TfidfFeatureExtractor:
|
||
"""TF-IDF 特征提取器
|
||
|
||
使用字符级 n-gram 策略,适合中文/多语言场景
|
||
|
||
优化说明(2024.12):
|
||
- max_features 从 20000 降到 10000,减少计算量
|
||
- ngram_range 默认 (2, 3),对于兴趣任务足够
|
||
- min_df 提高到 3,过滤低频噪声
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
analyzer: str = "char", # type: ignore
|
||
ngram_range: tuple[int, int] = (2, 4), # 优化:缩小 n-gram 范围
|
||
max_features: int = 10000, # 优化:减少特征数量,矩阵大小和 dot product 减半
|
||
min_df: int = 3, # 优化:过滤低频 n-gram
|
||
max_df: float = 0.95,
|
||
):
|
||
"""初始化特征提取器
|
||
|
||
Args:
|
||
analyzer: 分析器类型 ('char' 或 'word')
|
||
ngram_range: n-gram 范围,例如 (2, 4) 表示 2~4 字符的 n-gram
|
||
max_features: 词表最大大小,防止特征爆炸
|
||
min_df: 最小文档频率,至少出现在 N 个样本中才纳入词表
|
||
max_df: 最大文档频率,出现频率超过此比例的词将被过滤(如停用词)
|
||
"""
|
||
self.vectorizer = TfidfVectorizer(
|
||
analyzer=analyzer,
|
||
ngram_range=ngram_range,
|
||
max_features=max_features,
|
||
min_df=min_df,
|
||
max_df=max_df,
|
||
lowercase=True,
|
||
strip_accents=None, # 保留中文字符
|
||
sublinear_tf=True, # 使用对数 TF 缩放
|
||
norm="l2", # L2 归一化
|
||
)
|
||
self.is_fitted = False
|
||
|
||
logger.info(
|
||
f"TF-IDF 特征提取器初始化: analyzer={analyzer}, "
|
||
f"ngram_range={ngram_range}, max_features={max_features}"
|
||
)
|
||
|
||
def fit(self, texts: list[str]) -> "TfidfFeatureExtractor":
|
||
"""训练向量化器
|
||
|
||
Args:
|
||
texts: 训练文本列表
|
||
|
||
Returns:
|
||
self
|
||
"""
|
||
logger.info(f"开始训练 TF-IDF 向量化器,样本数: {len(texts)}")
|
||
self.vectorizer.fit(texts)
|
||
self.is_fitted = True
|
||
|
||
vocab_size = len(self.vectorizer.vocabulary_)
|
||
logger.info(f"TF-IDF 向量化器训练完成,词表大小: {vocab_size}")
|
||
|
||
return self
|
||
|
||
def transform(self, texts: list[str]):
|
||
"""将文本转换为 TF-IDF 向量
|
||
|
||
Args:
|
||
texts: 待转换文本列表
|
||
|
||
Returns:
|
||
稀疏矩阵
|
||
"""
|
||
if not self.is_fitted:
|
||
raise ValueError("向量化器尚未训练,请先调用 fit() 方法")
|
||
|
||
return self.vectorizer.transform(texts)
|
||
|
||
def fit_transform(self, texts: list[str]):
|
||
"""训练并转换文本
|
||
|
||
Args:
|
||
texts: 训练文本列表
|
||
|
||
Returns:
|
||
稀疏矩阵
|
||
"""
|
||
logger.info(f"开始训练并转换 TF-IDF 向量,样本数: {len(texts)}")
|
||
result = self.vectorizer.fit_transform(texts)
|
||
self.is_fitted = True
|
||
|
||
vocab_size = len(self.vectorizer.vocabulary_)
|
||
logger.info(f"TF-IDF 向量化完成,词表大小: {vocab_size}")
|
||
|
||
return result
|
||
|
||
def get_feature_names(self) -> list[str]:
|
||
"""获取特征名称列表
|
||
|
||
Returns:
|
||
特征名称列表
|
||
"""
|
||
if not self.is_fitted:
|
||
raise ValueError("向量化器尚未训练")
|
||
|
||
return self.vectorizer.get_feature_names_out().tolist()
|
||
|
||
def get_vocabulary_size(self) -> int:
|
||
"""获取词表大小
|
||
|
||
Returns:
|
||
词表大小
|
||
"""
|
||
if not self.is_fitted:
|
||
return 0
|
||
return len(self.vectorizer.vocabulary_)
|
||
|
||
def get_config(self) -> dict:
|
||
"""获取配置信息
|
||
|
||
Returns:
|
||
配置字典
|
||
"""
|
||
params = self.vectorizer.get_params()
|
||
return {
|
||
"analyzer": params["analyzer"],
|
||
"ngram_range": params["ngram_range"],
|
||
"max_features": params["max_features"],
|
||
"min_df": params["min_df"],
|
||
"max_df": params["max_df"],
|
||
"vocabulary_size": self.get_vocabulary_size() if self.is_fitted else 0,
|
||
"is_fitted": self.is_fitted,
|
||
}
|