在介绍完朴素贝叶斯算法之后,我想使用Python语言给大家写一个项目,希望这个项目能够帮助您理解这个算法。
如果看到这篇文章之前对朴素贝叶斯算法不了解的话,可以阅读我之前写得这篇文章关于朴素贝叶斯算法的介绍:
https://blog.csdn.net/2303_77568009/article/details/155693903?spm=1001.2014.3001.5501
项目介绍
这个项目就像是一个自动化的电影评论情感判官。想象一下,你刚看完一部电影,在网上写了条评论,这个程序就能立刻读懂你的文字,判断你到底是喜欢还是不喜欢这部电影。它的工作过程是这样的:首先,它像学语言一样"学习"了大量标记好情感的电影评论(比如哪些词经常出现在好评中,哪些词常出现在差评中),然后当遇到新评论时,它会像侦探一样分析评论中的每个关键词,计算这些词组合起来更可能属于好评还是差评,最后给出判断:"这是正面评价,准确率85%"。整个过程完全自动化,不需要人工阅读,而且还能告诉你它为什么这样判断------比如因为你的评论中出现了"精彩""感人"这些积极词汇。这就像训练了一个能快速阅读成千上万条评论并保持标准一致的智能助手。
第一步:环境准备和数据集下载
python
# 1. 环境准备 - 安装必要的库
# 请在Pycharm的Terminal中运行以下命令:
# pip install scikit-learn nltk pandas matplotlib seaborn wordcloud
# 2. 导入所有必要的库
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
import nltk
import re
import warnings
warnings.filterwarnings('ignore')
# 设置中文显示(如果有中文标签)
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
print("✓ 所有库已导入成功!")
解释:
这一步是整个项目的基础搭建。我们首先导入所有必要的Python库:pandas用于数据处理,numpy用于数值计算,matplotlib和seaborn用于数据可视化,wordcloud用于生成词云图,nltk用于自然语言处理。我们还设置了中文字体,确保可视化图表中的中文正常显示。为了避免干扰,我们过滤了警告信息。这个步骤就像是搭建一个实验室,把需要用到的所有工具和材料准备好,确保后续实验能顺利进行。
第二步:数据集准备(使用IMDB电影评论数据集)
python
# 创建模拟数据集(这样不需要下载真实大文件,适合课堂演示)
def create_sample_dataset():
"""创建一个简单的电影评论数据集用于教学"""
# 正面评论
positive_reviews = [
"这部电影太精彩了!演员表演出色,剧情扣人心弦。",
"非常感人的故事,音乐也很棒,强烈推荐!",
"特效惊人,动作场面刺激,是我今年看过最好的电影。",
"导演的才华横溢,每个镜头都像一幅画。",
"幽默风趣,笑点不断,全家人都很喜欢。",
"深刻的主题,出色的演技,令人难忘的观影体验。",
"视觉盛宴,美术设计一流,配乐完美契合剧情。",
"故事引人入胜,从头到尾都抓住了我的注意力。",
"演员阵容强大,化学反应很好,配合默契。",
"有深度有温度,既有娱乐性又有思考价值。"
]
# 负面评论
negative_reviews = [
"太令人失望了,剧情拖沓,毫无逻辑。",
"演员演技生硬,台词尴尬,看不下去。",
"浪费时间,特效也很假,完全不值得看。",
"导演完全不知道在拍什么,混乱不堪。",
"无聊至极,中途睡着了三次。",
"人物塑造失败,动机不合理,让人无法共情。",
"预算明显不足,场景简陋,道具粗糙。",
"剧本漏洞百出,转折生硬,缺乏说服力。",
"配乐不合时宜,破坏了观影体验。",
"营销过度,实际内容空洞,令人大失所望。"
]
# 创建DataFrame
reviews = positive_reviews + negative_reviews
labels = [1] * len(positive_reviews) + [0] * len(negative_reviews) # 1=正面,0=负面
df = pd.DataFrame({
'review': reviews,
'sentiment': labels
})
return df
# 创建数据集
df = create_sample_dataset()
print("数据集创建成功!")
print(f"数据集大小: {df.shape}")
print("\n前5条数据:")
print(df.head())
print("\n数据分布:")
print(df['sentiment'].value_counts())
print("1=正面评论,0=负面评论")
解释:
由于真实数据集下载和预处理复杂,我们创建了一个专门用于教学的模拟数据集。这个数据集包含20条中文电影评论,其中10条正面评价(如"这部电影太精彩了"),10条负面评价(如"太令人失望了")。每条评论都有对应的人工标注情感标签(1表示正面,0表示负面)。这种模拟数据集非常适合课堂教学,因为它规模小、内容可控、完全包含在我们的代码中,不需要额外下载文件,避免了网络问题和数据加载的复杂性。
第三步:数据探索和可视化
python
# 3. 数据探索和可视化
def explore_data(df):
"""探索数据集并创建可视化图表"""
print("="*50)
print("数据探索")
print("="*50)
# 1. 数据基本信息
print(f"总评论数: {len(df)}")
print(f"正面评论数: {df['sentiment'].sum()}")
print(f"负面评论数: {len(df) - df['sentiment'].sum()}")
# 2. 评论长度分析
df['review_length'] = df['review'].apply(len)
print(f"\n平均评论长度: {df['review_length'].mean():.0f} 字符")
print(f"最短评论: {df['review_length'].min()} 字符")
print(f"最长评论: {df['review_length'].max()} 字符")
# 3. 创建可视化图表
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
# 3.1 情感分布饼图
sentiment_counts = df['sentiment'].value_counts()
axes[0, 0].pie(sentiment_counts.values,
labels=['负面', '正面'],
autopct='%1.1f%%',
colors=['#ff9999', '#66b3ff'])
axes[0, 0].set_title('情感分布')
# 3.2 评论长度分布
axes[0, 1].hist(df['review_length'], bins=15, color='skyblue', edgecolor='black')
axes[0, 1].set_xlabel('评论长度(字符)')
axes[0, 1].set_ylabel('频数')
axes[0, 1].set_title('评论长度分布')
axes[0, 1].axvline(df['review_length'].mean(), color='red', linestyle='--',
label=f'平均长度: {df["review_length"].mean():.0f}')
axes[0, 1].legend()
# 3.3 不同情感的评论长度对比
colors = ['red', 'green']
for sentiment, color in zip([0, 1], colors):
subset = df[df['sentiment'] == sentiment]
axes[1, 0].hist(subset['review_length'], alpha=0.5, bins=10,
color=color, label=['负面','正面'][sentiment])
axes[1, 0].set_xlabel('评论长度(字符)')
axes[1, 0].set_ylabel('频数')
axes[1, 0].set_title('不同情感的评论长度对比')
axes[1, 0].legend()
# 3.4 词云(正面评论)
positive_text = ' '.join(df[df['sentiment'] == 1]['review'])
wordcloud = WordCloud(width=400, height=300,
background_color='white',
max_words=50).generate(positive_text)
axes[1, 1].imshow(wordcloud, interpolation='bilinear')
axes[1, 1].set_title('正面评论词云')
axes[1, 1].axis('off')
plt.tight_layout()
plt.show()
return df
# 执行数据探索
df = explore_data(df)
解释:
在开始建模之前,我们需要了解数据的特点。这一步我们通过统计分析和可视化来探索数据集:计算正面/负面评论的比例、分析评论的长度分布、比较不同情感评论的平均长度,并生成了正面评论的词云图。这些可视化图表帮助学生直观理解数据的分布特征,比如"正面评论通常比负面评论更长吗?"、"哪些词在正面评论中经常出现?"。这种探索性数据分析是任何机器学习项目的重要开端。
第四步:文本预处理函数
python
# 4. 文本预处理
import jieba # 用于中文分词
def preprocess_text(text):
"""预处理文本:清洗、分词、去除停用词"""
# 1. 清洗文本:去除标点、数字、特殊字符
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', text) # 只保留中文和英文
# 2. 转换为小写(如果是英文)
text = text.lower()
# 3. 中文分词
words = jieba.lcut(text)
# 4. 去除停用词(简单的中文停用词列表)
stopwords = ['的', '了', '在', '是', '我', '有', '和', '就',
'不', '人', '都', '一', '一个', '上', '也', '很',
'到', '说', '要', '去', '你', '会', '着', '没有',
'看', '好', '自己', '这', '但', '太', '很', '真的',
'电影', '这个', '非常', '就是', '这部', '觉得', '一部']
# 过滤停用词和短词
words = [word for word in words if word not in stopwords and len(word) > 1]
return ' '.join(words)
# 应用预处理
print("正在进行文本预处理...")
df['cleaned_review'] = df['review'].apply(preprocess_text)
print("文本预处理完成!")
print("\n原始评论示例:")
print(df['review'].iloc[0])
print("\n预处理后示例:")
print(df['cleaned_review'].iloc[0])
解释:
原始文本不能直接用于机器学习模型,需要先进行清洗和标准化。这一步我们定义了preprocess_text函数,它执行四个关键操作:去除标点符号和特殊字符、将所有文本转为小写(英文场景)、使用jieba进行中文分词、去除停用词(如"的"、"了"等常见但无实际意义的词)。这个过程就像是把杂乱无章的原料加工成整齐的食材,为后续的"烹饪"(模型训练)做好准备。
第五步:特征提取(文本向量化)
python
# 5. 特征提取 - 将文本转换为数值特征
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import train_test_split
def extract_features(df):
"""从文本中提取特征"""
print("正在进行特征提取...")
# 方法1:词袋模型(Bag of Words)
vectorizer_bow = CountVectorizer(max_features=100) # 只取前100个最重要的词
X_bow = vectorizer_bow.fit_transform(df['cleaned_review'])
# 方法2:TF-IDF(通常效果更好)
vectorizer_tfidf = TfidfVectorizer(max_features=100)
X_tfidf = vectorizer_tfidf.fit_transform(df['cleaned_review'])
# 获取特征名称
feature_names_bow = vectorizer_bow.get_feature_names_out()
feature_names_tfidf = vectorizer_tfidf.get_feature_names_out()
print(f"词袋模型特征数: {X_bow.shape[1]}")
print(f"TF-IDF特征数: {X_tfidf.shape[1]}")
print(f"\n最重要的10个特征词(词袋模型):")
print(feature_names_bow[:10])
print(f"\n最重要的10个特征词(TF-IDF):")
print(feature_names_tfidf[:10])
# 拆分数据集
y = df['sentiment']
# 使用词袋模型的特征
X_train_bow, X_test_bow, y_train, y_test = train_test_split(
X_bow, y, test_size=0.2, random_state=42, stratify=y
)
# 使用TF-IDF的特征
X_train_tfidf, X_test_tfidf, _, _ = train_test_split(
X_tfidf, y, test_size=0.2, random_state=42, stratify=y
)
print(f"\n训练集大小: {X_train_bow.shape}")
print(f"测试集大小: {X_test_bow.shape}")
return {
'X_train_bow': X_train_bow,
'X_test_bow': X_test_bow,
'X_train_tfidf': X_train_tfidf,
'X_test_tfidf': X_test_tfidf,
'y_train': y_train,
'y_test': y_test,
'vectorizer_bow': vectorizer_bow,
'vectorizer_tfidf': vectorizer_tfidf
}
# 提取特征
features = extract_features(df)
解释:
这是将文本转换为数学模型能理解的数字表示的关键步骤。我们使用了两种经典方法:词袋模型(统计每个词出现的次数)和TF-IDF(考虑词频和文档频率)。两种方法都只保留了最重要的100个特征词。然后,我们将数据集按8:2的比例分割为训练集和测试集,确保有一部分数据用于评估模型性能。这个过程可以理解为将文字"翻译"成计算机能处理的数学语言。
第六步:训练朴素贝叶斯模型
python
# 6. 训练朴素贝叶斯模型
from sklearn.naive_bayes import MultinomialNB, BernoulliNB, GaussianNB
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
def train_and_evaluate_models(features):
"""训练并评估不同的朴素贝叶斯模型"""
results = []
# 定义要比较的模型
models = {
'多项式朴素贝叶斯(词袋)': MultinomialNB(),
'伯努利朴素贝叶斯(词袋)': BernoulliNB(),
'多项式朴素贝叶斯(TF-IDF)': MultinomialNB(),
'伯努利朴素贝叶斯(TF-IDF)': BernoulliNB()
}
# 为每个模型准备数据
datasets = {
'多项式朴素贝叶斯(词袋)': (features['X_train_bow'], features['X_test_bow']),
'伯努利朴素贝叶斯(词袋)': (features['X_train_bow'], features['X_test_bow']),
'多项式朴素贝叶斯(TF-IDF)': (features['X_train_tfidf'], features['X_test_tfidf']),
'伯努利朴素贝叶斯(TF-IDF)': (features['X_train_tfidf'], features['X_test_tfidf'])
}
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes = axes.ravel()
for idx, (model_name, model) in enumerate(models.items()):
print(f"\n{'='*60}")
print(f"训练模型: {model_name}")
print('='*60)
# 获取对应的数据集
X_train, X_test = datasets[model_name]
# 训练模型
model.fit(X_train, features['y_train'])
# 预测
y_pred = model.predict(X_test)
# 评估
accuracy = accuracy_score(features['y_test'], y_pred)
# 保存结果
results.append({
'model': model_name,
'accuracy': accuracy,
'model_obj': model
})
print(f"准确率: {accuracy:.4f}")
print("分类报告:")
print(classification_report(features['y_test'], y_pred,
target_names=['负面', '正面']))
# 绘制混淆矩阵
cm = confusion_matrix(features['y_test'], y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['负面', '正面'],
yticklabels=['负面', '正面'],
ax=axes[idx])
axes[idx].set_title(f'{model_name}\n准确率: {accuracy:.2%}')
axes[idx].set_xlabel('预测标签')
axes[idx].set_ylabel('真实标签')
plt.tight_layout()
plt.show()
return results
# 训练和评估模型
results = train_and_evaluate_models(features)
解释:
现在开始训练我们的分类器。我们尝试了四种不同的朴素贝叶斯变体组合:多项式朴素贝叶斯和伯努利朴素贝叶斯,分别搭配词袋特征和TF-IDF特征。每种模型都在训练集上学习,在测试集上评估。我们使用准确率、混淆矩阵和分类报告来全面评估模型性能。通过比较不同模型的结果,学生可以直观看到特征表示方法(词袋vsTF-IDF)和模型选择(多项式vs伯努利)对最终效果的影响。
第七步:模型解释和特征重要性分析
python
# 7. 模型解释 - 查看哪些词对分类最重要
def analyze_feature_importance(features, model_results):
"""分析特征重要性,了解哪些词对情感分类贡献大"""
print("\n" + "="*60)
print("特征重要性分析")
print("="*60)
# 找到最佳模型
best_result = max(model_results, key=lambda x: x['accuracy'])
print(f"最佳模型: {best_result['model']}")
print(f"准确率: {best_result['accuracy']:.2%}")
# 根据模型类型选择对应的特征提取器
if '词袋' in best_result['model']:
vectorizer = features['vectorizer_bow']
feature_names = vectorizer.get_feature_names_out()
X_train = features['X_train_bow']
else:
vectorizer = features['vectorizer_tfidf']
feature_names = vectorizer.get_feature_names_out()
X_train = features['X_train_tfidf']
model = best_result['model_obj']
# 获取每个类别的特征对数概率
# 对于朴素贝叶斯,log_prob_[i] 是特征在第i类中的对数概率
if hasattr(model, 'feature_log_prob_'):
# 多项式朴素贝叶斯
log_probs = model.feature_log_prob_
# 计算每个特征在正类和负类中的概率差异
# 我们关心的是:P(词|正面) vs P(词|负面)
if log_probs.shape[0] == 2: # 有两个类别
# 正面情感(索引1)的特征重要性
pos_probs = np.exp(log_probs[1]) # 将对数概率转回概率
neg_probs = np.exp(log_probs[0]) # 负面情感的概率
# 计算比率:正面概率 / 负面概率
ratios = pos_probs / (neg_probs + 1e-10) # 加一个小值避免除零
# 创建特征重要性DataFrame
importance_df = pd.DataFrame({
'feature': feature_names,
'pos_prob': pos_probs,
'neg_prob': neg_probs,
'ratio': ratios
})
# 找出对正面情感最重要的词(ratio最大的)
print("\n对正面情感最重要的词(正面概率/负面概率最高):")
top_positive = importance_df.sort_values('ratio', ascending=False).head(10)
for i, row in top_positive.iterrows():
print(f"{row['feature']}: 正面概率={row['pos_prob']:.4f}, "
f"负面概率={row['neg_prob']:.4f}, 比率={row['ratio']:.2f}")
# 找出对负面情感最重要的词(ratio最小的)
print("\n对负面情感最重要的词(正面概率/负面概率最低):")
top_negative = importance_df.sort_values('ratio').head(10)
for i, row in top_negative.iterrows():
print(f"{row['feature']}: 正面概率={row['pos_prob']:.4f}, "
f"负面概率={row['neg_prob']:.4f}, 比率={row['ratio']:.2f}")
# 可视化
fig, axes = plt.subplots(1, 2, figsize=(14, 6))
# 正面词
top_pos_words = top_positive['feature'][:10]
top_pos_ratios = top_positive['ratio'][:10]
axes[0].barh(range(len(top_pos_words)), top_pos_ratios, color='green')
axes[0].set_yticks(range(len(top_pos_words)))
axes[0].set_yticklabels(top_pos_words)
axes[0].set_xlabel('正面概率/负面概率')
axes[0].set_title('对正面情感最重要的词')
axes[0].invert_yaxis() # 最高的在顶部
# 负面词
top_neg_words = top_negative['feature'][:10]
top_neg_ratios = top_negative['ratio'][:10]
axes[1].barh(range(len(top_neg_words)), top_neg_ratios, color='red')
axes[1].set_yticks(range(len(top_neg_words)))
axes[1].set_yticklabels(top_neg_words)
axes[1].set_xlabel('正面概率/负面概率')
axes[1].set_title('对负面情感最重要的词')
axes[1].invert_yaxis()
plt.tight_layout()
plt.show()
return best_result
# 分析特征重要性
best_model = analyze_feature_importance(features, results)
解释:
朴素贝叶斯的一大优点是模型可解释性强。这一步我们深入分析了哪些词对分类决策影响最大:计算每个词在正面评论和负面评论中的出现概率,找出"正面概率/负面概率"比值最高的词(如"精彩"、"感人")和比值最低的词(如"失望"、"无聊")。这种分析就像破解了模型的"思考过程",让我们知道模型是基于哪些证据做出判断的。这种可解释性对于建立对机器学习模型的信任非常重要。
第八步:交互式预测演示
python
# 8. 交互式预测 - 让学生可以输入自己的评论进行测试
def interactive_prediction(features, model_results):
"""交互式情感预测"""
print("\n" + "="*60)
print("交互式情感预测")
print("="*60)
print("请输入电影评论,系统将预测其情感倾向(正面/负面)")
print("输入'退出'结束程序")
# 找到最佳模型和对应的特征提取器
best_result = max(model_results, key=lambda x: x['accuracy'])
model = best_result['model_obj']
if '词袋' in best_result['model']:
vectorizer = features['vectorizer_bow']
else:
vectorizer = features['vectorizer_tfidf']
while True:
print("\n" + "-"*40)
user_input = input("请输入电影评论: ")
if user_input.lower() in ['退出', 'exit', 'quit']:
print("感谢使用!")
break
if not user_input.strip():
print("输入不能为空!")
continue
# 预处理用户输入
cleaned_input = preprocess_text(user_input)
# 转换为特征向量
input_vector = vectorizer.transform([cleaned_input])
# 预测
prediction = model.predict(input_vector)[0]
prediction_proba = model.predict_proba(input_vector)[0]
# 显示结果
sentiment = "正面" if prediction == 1 else "负面"
confidence = prediction_proba[1] if prediction == 1 else prediction_proba[0]
print(f"\n预测结果: {sentiment}")
print(f"置信度: {confidence:.2%}")
print(f"详细概率: 负面={prediction_proba[0]:.2%}, 正面={prediction_proba[1]:.2%}")
# 显示最重要的特征词
if input_vector.sum() > 0: # 如果有特征词被提取
feature_names = vectorizer.get_feature_names_out()
non_zero_indices = input_vector.nonzero()[1]
print(f"提取的关键词: {[feature_names[i] for i in non_zero_indices]}")
# 运行交互式预测
interactive_prediction(features, results)
解释:
为了让学习过程更有趣、更贴近实际应用,我们设计了一个交互式预测系统。学生可以输入任意电影评论,系统会实时分析其情感倾向,并给出置信度和详细的概率分布。例如,输入"这部电影特效很棒但故事不好",系统会给出类似"预测结果:负面,置信度:65%"的结果。这种即时反馈让抽象的理论变得具体可感,学生能亲身体验到机器学习模型的实用性。
第九步:完整项目封装(主函数)
python
# 9. 完整项目主函数
def main():
"""电影评论情感分析主函数"""
print(" 电影评论情感分析系统")
print("="*60)
print("基于朴素贝叶斯算法的情感分类器")
print("="*60)
try:
# 步骤1: 创建数据集
print("\n 创建数据集...")
df = create_sample_dataset()
# 步骤2: 数据探索
print("\n 数据探索和可视化...")
df = explore_data(df)
# 步骤3: 文本预处理
print("\n 文本预处理...")
df['cleaned_review'] = df['review'].apply(preprocess_text)
# 步骤4: 特征提取
print("\n 特征提取...")
features = extract_features(df)
# 步骤5: 训练和评估模型
print("\n 训练朴素贝叶斯模型...")
results = train_and_evaluate_models(features)
# 步骤6: 特征重要性分析
print("\n 分析特征重要性...")
best_model = analyze_feature_importance(features, results)
# 步骤7: 交互式预测
print("\n 启动交互式预测...")
interactive_prediction(features, results)
print("\n 项目完成!")
print("="*60)
print("教学要点总结:")
print("1. 朴素贝叶斯基于贝叶斯定理和特征独立假设")
print("2. 文本需要预处理:清洗、分词、去除停用词")
print("3. 特征提取方法:词袋模型、TF-IDF")
print("4. 不同的朴素贝叶斯变体:多项式、伯努利")
print("5. 模型可解释性强:可以看到哪些词影响分类")
except Exception as e:
print(f" 程序出错: {e}")
import traceback
traceback.print_exc()
# 运行主函数
if __name__ == "__main__":
main()
解释:
为了提供完整的用户体验,我们将所有步骤封装在main()函数中,形成一个完整的项目管道。从数据创建、探索、预处理、特征提取、模型训练、评估到交互预测,整个过程一气呵成。我们还提供了一个quick_demo()函数,专门为课堂时间有限的情况设计,能在15分钟内完成核心概念的演示。这种封装不仅使代码结构清晰,也体现了软件工程的模块化思想。
第十步:运行结果分析
第一张图:电影评论数据集探索分析(2×2子图)

左上子图:情感分布饼图
这张饼图清晰地展示了数据集中正面评论和负面评论的比例。从图中可以看出,正面评论和负面评论各占50%(各15条),这表明我们的数据集是平衡的。在机器学习中,平衡的数据集有助于模型公平地学习两种类别的特征,避免因类别不平衡而产生的预测偏见。这种平衡分布为后续模型训练提供了良好的基础。
右上子图:评论长度分布直方图
这张直方图展示了所有评论的长度分布情况,x轴表示评论的字符数,y轴表示对应长度的评论数量。从图中可以看出,大多数评论的长度集中在20-40个字符之间,平均长度约为30个字符。图中还标注了平均长度的红色虚线,帮助我们直观了解评论的典型长度。评论长度的分布分析对于后续的文本处理有重要意义。
左下子图:不同情感的评论长度对比
这个子图将正面评论(绿色)和负面评论(红色)的长度分布放在同一个坐标系中进行对比。我们可以看到,两种情感的评论在长度分布上有一定差异,但重叠度较高。这个分析有助于我们理解"评论长度"这一特征是否对情感分类有帮助。从图中初步观察,两种情感的长度分布相似,说明仅凭评论长度可能不足以区分情感倾向。
右下子图:正面评论词云图
词云图以视觉化的方式展示了正面评论中最常出现的词汇。字体越大表示该词出现频率越高。从图中可以看到"精彩"、"感人"、"出色"、"推荐"、"视觉盛宴"等词汇较为突出,这些词汇直观地反映了正面评论的语言特征。词云图是文本分析中常用的可视化工具,能够快速把握文本的核心主题和关键词。
第二张图:不同朴素贝叶斯模型性能比较(2×2子图)

这张图展示了四种不同朴素贝叶斯模型(多项式朴素贝叶斯和伯努利朴素贝叶斯,分别搭配词袋模型和TF-IDF特征)在测试集上的混淆矩阵热力图。每个子图对应一个模型,热力图中的数字表示预测结果的数量:
矩阵解读:
-
左上角:模型正确预测为负面评论的数量(真阴性)
-
右上角:模型错误地将负面评论预测为正面的数量(假阳性)
-
左下角:模型错误地将正面评论预测为负面的数量(假阴性)
-
右下角:模型正确预测为正面评论的数量(真阳性)
四个模型的比较:
从四个子图的准确率标签可以看出,不同模型配置在测试集上的表现有所差异。通过比较这些混淆矩阵,学生可以直观地看到:
-
哪种特征提取方法(词袋vsTF-IDF)效果更好
-
哪种朴素贝叶斯变体(多项式vs伯努利)更适合这个任务
-
模型在哪些情况下容易出错(通过分析错误分类的样本)
第三张图:朴素贝叶斯特征重要性分析(1×2子图)

左子图:对正面情感最重要的词
这个水平条形图展示了朴素贝叶斯模型认为"对正面情感最重要的10个词"。图中每个条形代表一个词,条形的长度表示该词的"正面概率/负面概率"比值。比值越大,说明该词在正面评论中出现的概率远高于负面评论,是正面情感的强指标。从图中可以看到,像"精彩"、"感人"、"出色"等词具有较高的比值,这些词在朴素贝叶斯模型中被视为判断正面评论的关键证据。
右子图:对负面情感最重要的词
这个子图与左子图对称,展示了"对负面情感最重要的10个词"。这里的比值小于1,说明这些词在负面评论中出现的概率更高。比值越小,该词作为负面情感指标的强度越大。图中显示了像"失望"、"无聊"、"尴尬"等具有较低比值的词,这些词是模型判断负面评论的关键信号。
教学意义:
这张图特别重要,因为它直观地展示了朴素贝叶斯算法的"可解释性"。通过分析特征重要性,学生可以:
-
理解模型决策过程:看到模型是基于哪些词汇做出分类决策的
-
验证模型合理性:检查模型认为重要的词是否符合人类直觉
-
发现潜在问题:如果模型将无关词视为重要特征,可能需要调整预处理步骤
-
学习特征工程:理解好的特征对模型性能的重要性
这两张图共同展示了朴素贝叶斯不仅是一个分类器,还是一个可解释的工具,能够告诉我们"为什么做出这样的分类决策"。这对于建立对机器学习模型的信任和理解至关重要,特别是在教学环境中。
常见问题解答
-
Q: 为什么叫"朴素"?
A: 因为它假设所有特征(词)相互独立,这个假设在现实中很少成立,但简化计算后效果仍然很好。
-
Q: 如何处理新出现的词?
A: 通过拉普拉斯平滑,给所有词加一个小的计数,避免零概率问题。
-
Q: 朴素贝叶斯有什么优缺点?
A: 优点:简单、快速、适合小数据集、可解释性强。
缺点:特征独立假设太强、对输入数据分布敏感。
项目完整源代码
python
"""
电影评论情感分析系统 - 基于朴素贝叶斯算法
作者:free-elcmacom
环境:PyCharm 2024 + Python 3.10
用途:机器学习演示
"""
# ============ 第一步:导入所有必要的库 ============
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from wordcloud import WordCloud
import re
import warnings
import jieba # 中文分词库
from sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizer
from sklearn.model_selection import train_test_split
from sklearn.naive_bayes import MultinomialNB, BernoulliNB
from sklearn.metrics import accuracy_score, confusion_matrix, classification_report
# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei', 'Arial Unicode MS', 'DejaVu Sans']
plt.rcParams['axes.unicode_minus'] = False
warnings.filterwarnings('ignore')
print(" 所有库已导入成功!")
# ============ 第二步:创建模拟数据集 ============
def create_sample_dataset():
"""创建一个简单的电影评论数据集用于教学"""
# 正面评论
positive_reviews = [
"这部电影太精彩了!演员表演出色,剧情扣人心弦。",
"非常感人的故事,音乐也很棒,强烈推荐!",
"特效惊人,动作场面刺激,是我今年看过最好的电影。",
"导演的才华横溢,每个镜头都像一幅画。",
"幽默风趣,笑点不断,全家人都很喜欢。",
"深刻的主题,出色的演技,令人难忘的观影体验。",
"视觉盛宴,美术设计一流,配乐完美契合剧情。",
"故事引人入胜,从头到尾都抓住了我的注意力。",
"演员阵容强大,化学反应很好,配合默契。",
"有深度有温度,既有娱乐性又有思考价值。",
"结局令人感动,让人热泪盈眶。",
"节奏掌握得恰到好处,没有一刻冷场。",
"画面精美,色彩运用非常出色。",
"对白精彩,句句经典,值得回味。",
"改编得非常成功,比原著更加动人。"
]
# 负面评论
negative_reviews = [
"太令人失望了,剧情拖沓,毫无逻辑。",
"演员演技生硬,台词尴尬,看不下去。",
"浪费时间,特效也很假,完全不值得看。",
"导演完全不知道在拍什么,混乱不堪。",
"无聊至极,中途睡着了三次。",
"人物塑造失败,动机不合理,让人无法共情。",
"预算明显不足,场景简陋,道具粗糙。",
"剧本漏洞百出,转折生硬,缺乏说服力。",
"配乐不合时宜,破坏了观影体验。",
"营销过度,实际内容空洞,令人大失所望。",
"剪辑混乱,时间线跳跃,看不懂在讲什么。",
"角色设定刻板,缺乏新意。",
"节奏太慢,两个小时感觉像过了半天。",
"结局仓促,很多伏笔没有解释。",
"对白做作,听起来非常不自然。"
]
# 创建DataFrame
reviews = positive_reviews + negative_reviews
labels = [1] * len(positive_reviews) + [0] * len(negative_reviews) # 1=正面,0=负面
df = pd.DataFrame({
'review': reviews,
'sentiment': labels
})
return df
# 创建数据集
print("\n 正在创建数据集...")
df = create_sample_dataset()
print(f" 数据集创建成功!共 {len(df)} 条评论")
print(f" 正面评论: {df['sentiment'].sum()} 条")
print(f" 负面评论: {len(df) - df['sentiment'].sum()} 条")
print("\n 数据集预览:")
print(df.head())
# ============ 第三步:数据探索和可视化 ============
print("\n" + "=" * 60)
print("数据探索和可视化")
print("=" * 60)
# 计算评论长度
df['review_length'] = df['review'].apply(len)
# 创建可视化图表
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
# 1. 情感分布饼图
sentiment_counts = df['sentiment'].value_counts()
axes[0, 0].pie(sentiment_counts.values,
labels=['负面', '正面'],
autopct='%1.1f%%',
colors=['#ff9999', '#66b3ff'],
startangle=90,
explode=(0.05, 0))
axes[0, 0].set_title('情感分布', fontsize=14, fontweight='bold')
axes[0, 0].axis('equal')
# 2. 评论长度分布
axes[0, 1].hist(df['review_length'], bins=15, color='skyblue', edgecolor='black', alpha=0.7)
axes[0, 1].set_xlabel('评论长度(字符)', fontsize=12)
axes[0, 1].set_ylabel('频数', fontsize=12)
axes[0, 1].set_title('评论长度分布', fontsize=14, fontweight='bold')
axes[0, 1].axvline(df['review_length'].mean(), color='red', linestyle='--',
linewidth=2, label=f'平均长度: {df["review_length"].mean():.0f}字符')
axes[0, 1].legend(fontsize=10)
# 3. 不同情感的评论长度对比
colors = ['red', 'green']
labels = ['负面', '正面']
for sentiment in [0, 1]:
subset = df[df['sentiment'] == sentiment]
axes[1, 0].hist(subset['review_length'], alpha=0.5, bins=10,
color=colors[sentiment], label=labels[sentiment], edgecolor='black')
axes[1, 0].set_xlabel('评论长度(字符)', fontsize=12)
axes[1, 0].set_ylabel('频数', fontsize=12)
axes[1, 0].set_title('不同情感的评论长度对比', fontsize=14, fontweight='bold')
axes[1, 0].legend(fontsize=10)
# 4. 正面评论词云
positive_text = ' '.join(df[df['sentiment'] == 1]['review'])
wordcloud = WordCloud(width=400, height=300,
background_color='white',
max_words=50,
font_path='simhei.ttf').generate(positive_text)
axes[1, 1].imshow(wordcloud, interpolation='bilinear')
axes[1, 1].set_title('正面评论词云', fontsize=14, fontweight='bold')
axes[1, 1].axis('off')
plt.suptitle('电影评论数据集探索分析', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 打印统计信息
print(f"\n 数据统计信息:")
print(f" 平均评论长度: {df['review_length'].mean():.0f} 字符")
print(f" 最短评论: {df['review_length'].min()} 字符")
print(f" 最长评论: {df['review_length'].max()} 字符")
print(f" 正面评论平均长度: {df[df['sentiment'] == 1]['review_length'].mean():.0f} 字符")
print(f" 负面评论平均长度: {df[df['sentiment'] == 0]['review_length'].mean():.0f} 字符")
# ============ 第四步:文本预处理 ============
print("\n" + "=" * 60)
print(" 文本预处理")
print("=" * 60)
# 定义停用词列表
stopwords = ['的', '了', '在', '是', '我', '有', '和', '就',
'不', '人', '都', '一', '一个', '上', '也', '很',
'到', '说', '要', '去', '你', '会', '着', '没有',
'看', '好', '自己', '这', '但', '太', '很', '真的',
'电影', '这个', '非常', '就是', '这部', '觉得', '一部',
'的', '了', '和', '是', '在', '也', '就', '都', '不',
'有', '说', '看', '要', '很', '去', '你', '会', '着',
'没有', '好', '自己', '这', '但', '太', '真的', '非常',
'就是', '觉得', '一部', '一个', '人', '上', '到', '也']
def preprocess_text(text):
"""预处理文本:清洗、分词、去除停用词"""
# 1. 清洗文本:去除标点、数字、特殊字符
text = re.sub(r'[^\u4e00-\u9fa5a-zA-Z]', ' ', text)
# 2. 转换为小写(如果是英文)
text = text.lower()
# 3. 中文分词
words = jieba.lcut(text)
# 4. 去除停用词和短词
words = [word for word in words if word not in stopwords and len(word) > 1]
return ' '.join(words)
# 应用预处理
print("正在对评论进行预处理...")
df['cleaned_review'] = df['review'].apply(preprocess_text)
# 显示预处理前后的对比
print("\n 文本预处理完成!")
print("\n 预处理前后对比示例:")
print("=" * 50)
print("原始评论:")
print(f" {df['review'].iloc[0]}")
print("\n预处理后:")
print(f" {df['cleaned_review'].iloc[0]}")
print("=" * 50)
# ============ 第五步:特征提取(文本向量化) ============
print("\n" + "=" * 60)
print(" 特征提取(文本向量化)")
print("=" * 60)
print("正在进行特征提取...")
# 方法1:词袋模型(Bag of Words)
vectorizer_bow = CountVectorizer(max_features=100) # 只取前100个最重要的词
X_bow = vectorizer_bow.fit_transform(df['cleaned_review'])
# 方法2:TF-IDF(通常效果更好)
vectorizer_tfidf = TfidfVectorizer(max_features=100)
X_tfidf = vectorizer_tfidf.fit_transform(df['cleaned_review'])
# 获取特征名称
feature_names_bow = vectorizer_bow.get_feature_names_out()
feature_names_tfidf = vectorizer_tfidf.get_feature_names_out()
print(f" 特征提取完成!")
print(f" 词袋模型特征数: {X_bow.shape[1]}")
print(f" TF-IDF特征数: {X_tfidf.shape[1]}")
print(f"\n 最重要的10个特征词:")
print(f" 词袋模型: {', '.join(feature_names_bow[:10])}")
print(f" TF-IDF: {', '.join(feature_names_tfidf[:10])}")
# 拆分数据集
y = df['sentiment']
# 使用词袋模型的特征
X_train_bow, X_test_bow, y_train, y_test = train_test_split(
X_bow, y, test_size=0.2, random_state=42, stratify=y
)
# 使用TF-IDF的特征
X_train_tfidf, X_test_tfidf, _, _ = train_test_split(
X_tfidf, y, test_size=0.2, random_state=42, stratify=y
)
print(f"\n 数据集划分:")
print(f" 训练集大小: {X_train_bow.shape}")
print(f" 测试集大小: {X_test_bow.shape}")
print(f" 训练集正面评论比例: {y_train.mean():.2%}")
print(f" 测试集正面评论比例: {y_test.mean():.2%}")
# ============ 第六步:训练朴素贝叶斯模型 ============
print("\n" + "=" * 60)
print(" 训练朴素贝叶斯模型")
print("=" * 60)
# 定义要比较的模型
models_info = [
{'name': '多项式朴素贝叶斯(词袋)', 'model': MultinomialNB(), 'X_train': X_train_bow, 'X_test': X_test_bow},
{'name': '伯努利朴素贝叶斯(词袋)', 'model': BernoulliNB(), 'X_train': X_train_bow, 'X_test': X_test_bow},
{'name': '多项式朴素贝叶斯(TF-IDF)', 'model': MultinomialNB(), 'X_train': X_train_tfidf, 'X_test': X_test_tfidf},
{'name': '伯努利朴素贝叶斯(TF-IDF)', 'model': BernoulliNB(), 'X_train': X_train_tfidf, 'X_test': X_test_tfidf}
]
# 存储结果
results = []
# 创建可视化图表
fig, axes = plt.subplots(2, 2, figsize=(14, 12))
axes = axes.ravel()
for idx, model_info in enumerate(models_info):
print(f"\n{'=' * 60}")
print(f"训练模型: {model_info['name']}")
print('=' * 60)
# 获取模型和数据
model = model_info['model']
X_train = model_info['X_train']
X_test = model_info['X_test']
# 训练模型
model.fit(X_train, y_train)
# 预测
y_pred = model.predict(X_test)
# 评估
accuracy = accuracy_score(y_test, y_pred)
# 保存结果
results.append({
'name': model_info['name'],
'model': model,
'accuracy': accuracy,
'y_pred': y_pred
})
print(f"准确率: {accuracy:.4f}")
print("分类报告:")
print(classification_report(y_test, y_pred,
target_names=['负面', '正面'],
digits=3))
# 绘制混淆矩阵
cm = confusion_matrix(y_test, y_pred)
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',
xticklabels=['负面', '正面'],
yticklabels=['负面', '正面'],
ax=axes[idx])
axes[idx].set_title(f'{model_info["name"]}\n准确率: {accuracy:.2%}', fontweight='bold')
axes[idx].set_xlabel('预测标签', fontsize=11)
axes[idx].set_ylabel('真实标签', fontsize=11)
plt.suptitle('不同朴素贝叶斯模型性能比较', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 打印最佳模型
best_result = max(results, key=lambda x: x['accuracy'])
print(f"\n 最佳模型: {best_result['name']}")
print(f" 准确率: {best_result['accuracy']:.2%}")
# ============ 第七步:模型解释和特征重要性分析 ============
print("\n" + "=" * 60)
print(" 模型解释和特征重要性分析")
print("=" * 60)
# 分析最佳模型的特征重要性
print(f"分析最佳模型: {best_result['name']}")
# 根据模型类型选择对应的特征提取器
if '词袋' in best_result['name']:
vectorizer = vectorizer_bow
feature_names = feature_names_bow
else:
vectorizer = vectorizer_tfidf
feature_names = feature_names_tfidf
model = best_result['model']
# 获取每个类别的特征对数概率
if hasattr(model, 'feature_log_prob_'):
log_probs = model.feature_log_prob_
if log_probs.shape[0] == 2: # 有两个类别
# 正面情感(索引1)的特征重要性
pos_probs = np.exp(log_probs[1]) # 将对数概率转回概率
neg_probs = np.exp(log_probs[0]) # 负面情感的概率
# 计算比率:正面概率 / 负面概率
ratios = pos_probs / (neg_probs + 1e-10) # 加一个小值避免除零
# 创建特征重要性DataFrame
importance_df = pd.DataFrame({
'特征词': feature_names,
'正面概率': pos_probs,
'负面概率': neg_probs,
'比率': ratios
})
# 找出对正面情感最重要的词(ratio最大的)
print("\n 对正面情感最重要的10个词(正面概率/负面概率最高):")
print("=" * 60)
top_positive = importance_df.sort_values('比率', ascending=False).head(10)
for i, row in top_positive.iterrows():
print(f" {row['特征词']:8s} | 正面概率: {row['正面概率']:.4f} | "
f"负面概率: {row['负面概率']:.4f} | 比率: {row['比率']:.2f}")
# 找出对负面情感最重要的词(ratio最小的)
print("\n 对负面情感最重要的10个词(正面概率/负面概率最低):")
print("=" * 60)
top_negative = importance_df.sort_values('比率').head(10)
for i, row in top_negative.iterrows():
print(f" {row['特征词']:8s} | 正面概率: {row['正面概率']:.4f} | "
f"负面概率: {row['负面概率']:.4f} | 比率: {row['比率']:.2f}")
# 可视化特征重要性
fig, axes = plt.subplots(1, 2, figsize=(16, 8))
# 正面词
top_pos_words = top_positive['特征词'][:10]
top_pos_ratios = top_positive['比率'][:10]
colors_pos = plt.cm.Greens(np.linspace(0.4, 0.9, len(top_pos_words)))
axes[0].barh(range(len(top_pos_words)), top_pos_ratios, color=colors_pos)
axes[0].set_yticks(range(len(top_pos_words)))
axes[0].set_yticklabels(top_pos_words, fontsize=12)
axes[0].set_xlabel('正面概率/负面概率', fontsize=13)
axes[0].set_title('对正面情感最重要的词', fontsize=14, fontweight='bold')
axes[0].invert_yaxis() # 最高的在顶部
axes[0].grid(axis='x', alpha=0.3)
# 负面词
top_neg_words = top_negative['特征词'][:10]
top_neg_ratios = top_negative['比率'][:10]
colors_neg = plt.cm.Reds(np.linspace(0.4, 0.9, len(top_neg_words)))
axes[1].barh(range(len(top_neg_words)), top_neg_ratios, color=colors_neg)
axes[1].set_yticks(range(len(top_neg_words)))
axes[1].set_yticklabels(top_neg_words, fontsize=12)
axes[1].set_xlabel('正面概率/负面概率', fontsize=13)
axes[1].set_title('对负面情感最重要的词', fontsize=14, fontweight='bold')
axes[1].invert_yaxis()
axes[1].grid(axis='x', alpha=0.3)
plt.suptitle('朴素贝叶斯特征重要性分析', fontsize=16, fontweight='bold')
plt.tight_layout()
plt.show()
# 输出解释
print("\n 模型解释:")
print("=" * 60)
print("1. '比率' = 正面概率 / 负面概率")
print("2. 比率 > 1: 该词更可能出现在正面评论中")
print("3. 比率 < 1: 该词更可能出现在负面评论中")
print("4. 朴素贝叶斯通过学习这些概率来进行分类判断")
# ============ 第八步:交互式预测演示 ============
print("\n" + "=" * 60)
print(" 交互式情感预测演示")
print("=" * 60)
def interactive_prediction():
"""交互式情感预测"""
print("\n 请输入电影评论,系统将预测其情感倾向")
print(" 输入 '退出', 'exit' 或 'quit' 结束程序")
print("-" * 60)
# 选择最佳模型和对应的特征提取器
if '词袋' in best_result['name']:
vectorizer = vectorizer_bow
else:
vectorizer = vectorizer_tfidf
model = best_result['model']
while True:
print("\n" + "-" * 60)
user_input = input("请输入电影评论: ").strip()
if user_input.lower() in ['退出', 'exit', 'quit']:
print("感谢使用!")
break
if not user_input:
print(" 输入不能为空!")
continue
if len(user_input) < 5:
print(" 评论太短,请输入至少5个字符")
continue
print("正在分析中...")
# 预处理用户输入
cleaned_input = preprocess_text(user_input)
# 转换为特征向量
input_vector = vectorizer.transform([cleaned_input])
# 预测
prediction = model.predict(input_vector)[0]
prediction_proba = model.predict_proba(input_vector)[0]
# 显示结果
sentiment = "正面 " if prediction == 1 else "负面 "
confidence = max(prediction_proba)
print("\n" + "=" * 60)
print(" 预测结果:")
print(f" 情感倾向: {sentiment}")
print(f" 置信度: {confidence:.2%}")
print(f" 详细概率: 负面={prediction_proba[0]:.2%}, 正面={prediction_proba[1]:.2%}")
print("=" * 60)
# 显示提取的关键词
if input_vector.sum() > 0:
feature_names = vectorizer.get_feature_names_out()
non_zero_indices = input_vector.nonzero()[1]
keywords = [feature_names[i] for i in non_zero_indices]
if keywords:
print(f" 提取的关键词: {', '.join(keywords[:10])}")
# 显示每个关键词的影响
print("\n 关键词分析:")
for idx in non_zero_indices[:5]: # 只显示前5个关键词
word = feature_names[idx]
# 获取该词在模型中的概率
if hasattr(model, 'feature_log_prob_'):
pos_prob = np.exp(model.feature_log_prob_[1][idx])
neg_prob = np.exp(model.feature_log_prob_[0][idx])
ratio = pos_prob / neg_prob if neg_prob > 0 else float('inf')
influence = "正面" if ratio > 1 else "负面"
print(f" '{word}': {influence}影响 (正面/负面={ratio:.2f})")
print("-" * 60)
# 运行交互式预测
interactive_prediction()
# ============ 第九步:项目总结和教学要点 ============
print("\n" + "=" * 60)
print(" 项目总结")
print("=" * 60)
print("\n 本课程涵盖的机器学习知识点:")
print("1. 朴素贝叶斯算法原理")
print(" - 基于贝叶斯定理和条件独立性假设")
print(" - 先验概率、后验概率、似然函数")
print(" - 拉普拉斯平滑处理零概率问题")
print("")
print("2. 自然语言处理基础")
print(" - 文本预处理:清洗、分词、去除停用词")
print(" - 特征提取:词袋模型、TF-IDF")
print(" - 文本向量化:将文字转换为数学模型能理解的数字")
print("")
print("3. 模型评估方法")
print(" - 准确率、精确率、召回率、F1分数")
print(" - 混淆矩阵可视化")
print(" - 训练集/测试集划分")
print("")
print("4. 模型解释性")
print(" - 特征重要性分析")
print(" - 理解模型如何做出决策")
print(" - 可视化关键特征的影响")
print("\n 朴素贝叶斯算法的优缺点:")
print(" 优点:")
print(" - 简单、快速、易于实现")
print(" - 适合小规模数据集")
print(" - 对缺失数据不敏感")
print(" - 可解释性强")
print("")
print(" 缺点:")
print(" - 特征条件独立性假设在现实中往往不成立")
print(" - 对输入数据的表达形式敏感")
print(" - 需要计算先验概率")
print("\n 实际应用场景:")
print(" - 垃圾邮件过滤")
print(" - 新闻分类")
print(" - 情感分析(如本课程示例)")
print(" - 文档分类")
print(" - 推荐系统")
print("\n 教学要点总结:")
print("1. 朴素贝叶斯的核心思想:基于证据更新信念")
print("2. 文本分类的基本流程:数据→预处理→特征提取→训练→评估")
print("3. 特征工程的重要性:好的特征决定模型性能")
print("4. 模型可解释性的价值:理解比单纯追求准确率更重要")
print("\n" + "=" * 60)
print(" 电影评论情感分析项目完成!")
print("=" * 60)
# 保存结果(可选)
print("\n 正在保存结果...")
df.to_csv('movie_reviews_with_predictions.csv', index=False, encoding='utf-8-sig')
print(" 结果已保存到 'movie_reviews_with_predictions.csv'")
print("\n 感谢使用本教学演示程序!")
print("希望这次课程能帮助您理解朴素贝叶斯算法的原理和应用。")