1. 文本表示基础与词袋模型解析

在自然语言处理(NLP)领域,文本表示是将人类可读的文本转换为计算机可处理的数值形式的核心步骤。就像人类通过文字理解世界,计算机则需要通过数字来"理解"文本内容。这个过程就像给每个单词或句子制作一张专属的"数字身份证",让算法能够识别和处理它们。

1.1 为什么需要文本表示

想象一下,你正在教一个完全不懂中文的外国人理解中文文本。你需要找到一种双方都能理解的"中间语言"——这就是文本表示在NLP中的作用。计算机无法直接理解"天安门很壮观"这样的句子,但它可以完美处理[0.23, 0.45, 0.12,...]这样的数字向量。

文本表示方法的发展经历了几个重要阶段:

  • 早期基于规则的方法(如WordNet)
  • 统计学习方法(如词袋模型、TF-IDF)
  • 分布式表示(如Word2Vec、GloVe)
  • 上下文相关表示(如BERT、GPT)

每种方法都有其适用场景和优缺点,而词袋模型作为最基础也最直观的文本表示方法,至今仍在许多场景中发挥着重要作用。

1.2 词袋模型的核心思想

词袋模型(Bag of Words, BoW)的核心思想非常简单:忽略文本的语法和词序,仅关注词汇的出现频率。就像把一篇文章的所有单词倒进一个袋子,然后统计每种单词出现的次数。

这种方法的优势在于:

  • 实现简单,计算效率高
  • 对短文本效果较好
  • 可解释性强(能直观看到哪些词出现最多)

但同时也存在明显局限:

  • 完全丢失词序信息("狗咬人"和"人咬狗"会被表示为相同)
  • 无法捕捉词语间的语义关系
  • 维度灾难(词汇表可能非常大)

在实际应用中,我们通常使用scikit-learn库中的工具来实现词袋模型,主要包括以下四个类:

  1. CountVectorizer - 基础词频统计
  2. TfidfVectorizer - 词频-逆文档频率
  3. HashingVectorizer - 哈希技巧处理大规模数据
  4. DictVectorizer - 字典数据一键编码

2. sklearn文本特征提取详解

2.1 CountVectorizer实战解析

CountVectorizer是词袋模型最直接的实现,其工作流程可分为三步:

  1. 分词(对中文需先进行分词处理)
  2. 构建词汇表
  3. 统计每个文档中词汇的出现频率

让我们通过一个实际例子来理解其用法:

from sklearn.feature_extraction.text import CountVectorizer

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

# 初始化向量化器,设置停用词和n-gram范围
vectorizer = CountVectorizer(stop_words=['is', 'the'], ngram_range=(1,2))
X = vectorizer.fit_transform(corpus)

print("词汇表:", vectorizer.get_feature_names())
print("词频矩阵:\n", X.toarray())

关键参数解析:

  • stop_words :指定停用词列表,可设为'english'使用内置英文停用词表
  • ngram_range :设置n-gram范围,(1,2)表示同时考虑单个词和两个词的组合
  • max_features :限制最大特征数量,按词频排序
  • token_pattern :自定义token匹配模式,对中文处理尤为重要

注意:直接处理中文时,必须先用jieba等工具进行分词,否则CountVectorizer会按字符分割,导致效果极差。

2.2 中文处理的特殊技巧

中文文本处理有其特殊性,主要体现在:

  1. 没有天然空格分隔,必须依赖分词工具
  2. 单字可能携带重要信息
  3. 停用词处理更为复杂

一个完整的中文处理示例如下:

import jieba
from sklearn.feature_extraction.text import CountVectorizer

corpus = ['我爱北京天安门,天安门很壮观', 
          '我经常在广场拍照']

# 使用jieba进行分词,并用空格连接
corpus_tokenized = [" ".join(jieba.lcut(s)) for s in corpus]

# 关键:设置token_pattern匹配单个中文字符
vectorizer = CountVectorizer(
    stop_words=['在'], 
    token_pattern='[\u4e00-\u9fa5_a-zA-Z0-9]{1,}'
)

X = vectorizer.fit_transform(corpus_tokenized)
print("中文词汇表:", vectorizer.get_feature_names())
print("中文词频矩阵:\n", X.toarray())

2.3 TfidfVectorizer进阶应用

TF-IDF(词频-逆文档频率)是对简单词频统计的重要改进,它通过考虑词语在整个语料库中的分布情况来调整权重。一个词如果在当前文档中出现频繁,但在其他文档中出现很少,则被认为更具代表性。

TF-IDF计算公式:

TF(t,d) = (词t在文档d中出现的次数) / (文档d中所有词的总数)
IDF(t) = log(文档总数 / (包含词t的文档数 + 1))
TF-IDF(t,d) = TF(t,d) * IDF(t)

使用示例:

from sklearn.feature_extraction.text import TfidfVectorizer
import jieba

corpus = ['我爱北京天安门,天安门很壮观', 
          '我经常在广场拍照']  

corpus_tokenized = [" ".join(jieba.lcut(s)) for s in corpus]

tfidf = TfidfVectorizer(token_pattern='[\u4e00-\u9fa5_a-zA-Z0-9]{1,}')
tfidf_vec = tfidf.fit_transform(corpus_tokenized)

print("TF-IDF矩阵:\n", tfidf_vec.toarray())
print("特征词:", tfidf.get_feature_names())

TF-IDF的优势在于:

  • 降低常见词的权重(如"的"、"是")
  • 提升专业术语、特色词汇的重要性
  • 适用于信息检索和文本分类任务

2.4 HashingVectorizer大规模处理

当面对海量文本数据时,传统的词袋模型会遇到内存问题,因为需要维护一个可能包含数百万词的词汇表。HashingVectorizer通过哈希技巧解决了这个问题,它不需要存储词汇表,而是直接通过哈希函数将词映射到固定维度的向量空间。

工作原理:

  1. 对每个词应用哈希函数,得到哈希值
  2. 将哈希值对特征维度取模,确定其在向量中的位置
  3. 统计每个位置上的词频或TF-IDF值

使用示例:

from sklearn.feature_extraction.text import HashingVectorizer

corpus = [
    'UNC played Duke in basketball',
    'Duke lost the basketball game, game over',
    'I ate a sandwich'
]

# 指定输出向量长度为3
vectorizer = HashingVectorizer(n_features=3)
counts = vectorizer.transform(corpus).todense()

print("哈希向量化结果:\n", counts)

注意事项:

  • 无法逆向获取原始词汇(哈希是单向的)
  • 可能存在哈希冲突(不同词映射到同一位置)
  • 特征维度越大,冲突概率越低
  • 适合处理超大规模文本数据

2.5 DictVectorizer特殊场景应用

DictVectorizer主要用于处理字典格式的数据,将其转换为数值矩阵。它特别适合处理带有类别型特征的数据,会自动进行one-hot编码。

典型应用场景:

  • 结构化数据与文本数据的结合
  • 特征来自多个不同来源
  • 需要混合处理数值型和类别型特征

示例代码:

from sklearn.feature_extraction import DictVectorizer

data = [
    {"城市":"广州", "温度":33},
    {"城市":"北京", "温度":12},
    {"城市":"上海", "温度":18}
]

vectorizer = DictVectorizer()
vec = vectorizer.fit_transform(data)

print("转换后的矩阵:\n", vec.toarray())
print("特征名称:", vectorizer.get_feature_names())

输出结果中,"城市=北京"等类别特征被自动转换为one-hot形式,而温度等数值特征保持不变。

3. 酒店推荐系统实战

3.1 推荐系统基础架构

基于文本的内容推荐系统通常包含以下组件:

  1. 数据收集层 - 获取用户和物品的文本描述
  2. 特征提取层 - 使用词袋模型等方法提取文本特征
  3. 相似度计算层 - 计算物品间或用户-物品间的相似度
  4. 推荐生成层 - 根据相似度生成推荐列表

在酒店推荐场景中,我们可以利用酒店的文本描述(如设施、服务、位置等)来计算酒店间的相似度,然后为用户推荐与其历史偏好相似的酒店。

3.2 数据准备与预处理

假设我们有一个酒店数据集,每条记录包含:

  • 酒店ID
  • 酒店名称
  • 酒店描述(文本)
  • 酒店设施(如"免费WiFi,游泳池,健身房")
  • 用户评分

预处理步骤:

  1. 清洗文本(去除特殊字符、统一大小写等)
  2. 对中文描述进行分词
  3. 合并多个文本字段(如描述+设施)
  4. 构建文档-词项矩阵
import pandas as pd
import jieba

# 假设hotels是包含酒店数据的DataFrame
hotels['combined_text'] = hotels['描述'] + " " + hotels['设施']
hotels['tokenized'] = hotels['combined_text'].apply(lambda x: " ".join(jieba.lcut(x)))

# 使用TF-IDF进行特征提取
tfidf = TfidfVectorizer(token_pattern='[\u4e00-\u9fa5_a-zA-Z0-9]{1,}')
tfidf_matrix = tfidf.fit_transform(hotels['tokenized'])

3.3 相似度计算与推荐

计算酒店间相似度的常用方法有余弦相似度,它衡量两个向量在方向上的相似程度,而不考虑它们的绝对大小。

from sklearn.metrics.pairwise import cosine_similarity

# 计算所有酒店间的相似度矩阵
cosine_sim = cosine_similarity(tfidf_matrix, tfidf_matrix)

# 为指定酒店推荐最相似的其他酒店
def recommend_hotels(hotel_id, cosine_sim=cosine_sim, top_n=5):
    # 获取该酒店的相似度向量
    sim_scores = list(enumerate(cosine_sim[hotel_id]))
    
    # 按相似度排序
    sim_scores = sorted(sim_scores, key=lambda x: x[1], reverse=True)
    
    # 获取最相似的top_n个酒店(排除自己)
    sim_scores = sim_scores[1:top_n+1]
    
    # 返回推荐结果
    return [(i, score) for i, score in sim_scores]

3.4 系统优化与评估

为了提高推荐质量,我们可以考虑以下优化策略:

  1. 混合推荐:结合基于内容的推荐和协同过滤
  2. 特征加权:对重要特征(如"海景"、"市中心")赋予更高权重
  3. 用户画像:结合用户历史行为调整推荐
  4. 实时反馈:根据用户点击/收藏行为动态更新推荐

评估指标可以包括:

  • 点击率(CTR)
  • 转化率(实际预订比例)
  • 推荐列表的多样性
  • 用户满意度调查

4. 高级技巧与问题排查

4.1 词云可视化技巧

词云是直观展示文本关键词的有效工具,结合jieba和WordCloud库可以轻松实现:

from wordcloud import WordCloud
import matplotlib.pyplot as plt

# 准备文本数据
text = " ".join(hotels['tokenized'])

# 生成词云
font_path = 'simhei.ttf'  # 中文字体路径
wordcloud = WordCloud(
    width=800, 
    height=400,
    background_color='white',
    font_path=font_path
).generate(text)

# 显示词云
plt.figure(figsize=(10, 5))
plt.imshow(wordcloud, interpolation='bilinear')
plt.axis('off')
plt.show()

4.2 常见问题与解决方案

问题1:内存不足

  • 症状:处理大规模文本时内存溢出
  • 解决方案:
    • 使用HashingVectorizer替代CountVectorizer
    • 分批处理数据
    • 增加max_features限制
    • 使用稀疏矩阵格式

问题2:中文分词不准确

  • 症状:专业术语被错误分割
  • 解决方案:
    • 添加自定义词典: jieba.load_userdict('my_dict.txt')
    • 调整分词模式: jieba.cut(text, cut_all=False)
    • 使用更专业的分词工具(如HanLP)

问题3:特征维度太高

  • 症状:模型训练缓慢,效果不佳
  • 解决方案:
    • 使用特征选择(如chi2, mutual_info)
    • 应用降维技术(如PCA, LSA)
    • 增加n-gram范围限制
    • 提高min_df阈值(忽略低频词)

问题4:冷启动问题

  • 症状:新酒店或新用户缺乏历史数据
  • 解决方案:
    • 利用酒店元数据(位置、星级等)
    • 实施混合推荐策略
    • 收集用户显式反馈(如偏好调查)

4.3 性能优化技巧

  1. 管道化处理 :将分词、向量化等步骤封装为Pipeline,提高代码效率和可复用性
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import TfidfVectorizer

text_clf = Pipeline([
    ('tfidf', TfidfVectorizer(tokenizer=jieba.lcut)),
    # 可以添加其他步骤如分类器
])
  1. 并行处理 :利用n_jobs参数加速计算
vectorizer = TfidfVectorizer(tokenizer=jieba.lcut, n_jobs=-1)
  1. 增量学习 :对大规模数据使用partial_fit方法
from sklearn.feature_extraction.text import HashingVectorizer
vectorizer = HashingVectorizer(n_features=10000)

# 分批处理数据
for batch in pd.read_csv('large_data.csv', chunksize=1000):
    X_batch = vectorizer.transform(batch['text'])
    # 增量学习...
  1. 缓存机制 :对分词结果进行缓存,避免重复计算
from joblib import Memory
memory = Memory("./cache")

@memory.cache
def tokenize(text):
    return " ".join(jieba.lcut(text))

hotels['tokenized'] = hotels['text'].apply(tokenize)

在实际项目中,文本表示只是推荐系统的一部分,还需要考虑用户行为数据、上下文信息、实时反馈等因素。词袋模型虽然简单,但在合理优化和与其他技术结合的情况下,仍然能够构建出高效实用的推荐系统。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐