> 技术文档 > 我用机器学习做了一个“电影评论情感分析器”

我用机器学习做了一个“电影评论情感分析器”



大家在选择看哪部电影时,是不是经常会去豆瓣、IMDb刷一刷评论?“全程高能,绝对神作!”、“剧情拖沓,浪费时间。”——这些评论直接影响着我们的决策。如果能让计算机自动读懂这些评论的情感色彩,判断是好评还是差评,那该多酷啊!

今天,我就带大家从零开始,用机器学习技术,一步步打造一个属于我们自己的“电影评论情感分析器”。我们将从最经典的**词袋模型(Bag-of-Words)**和 TF-IDF 出发,然后踏入深度学习的世界,使用强大的 LSTMBERT 模型。

准备好了吗?让我们开始这场从“统计”到“智能”的奇妙旅程吧!

项目概览

  • 任务:对电影评论文本进行二分类(正面/负面)。
  • 数据:我们将使用经典的 IMDb Large Movie Review Dataset,它包含了 50,000 条带有明确情感标签(positive/negative)的电影评论。
  • 技术路线
    1. 传统方法:词袋模型 + 逻辑回归
    2. 改进传统方法:TF-IDF + 逻辑回归
    3. 深度学习入门:LSTM
    4. 前沿深度学习:BERT 微调

Part 1: 奠定基础——传统机器学习方法

在处理文本时,计算机无法直接理解“好看”、“无聊”这些词。我们需要先把文本转换成计算机能处理的数字形式,这个过程叫做特征工程

数据预处理

无论使用哪种模型,干净的数据都是成功的一半。预处理步骤通常包括:

  • 文本清洗:去除HTML标签、标点符号、数字等。
  • 分词(Tokenization):将句子切分成一个个单词。
  • 小写转换:统一大小写,避免\"Good\"和\"good\"被当成两个词。
  • 去除停用词(Stop Words):去除“a”、“the”、“is”等没有实际意义的词。
import refrom nltk.corpus import stopwordsimport nltk# 确保已下载停用词列表# nltk.download(\'stopwords\') stop_words = set(stopwords.words(\'english\'))def preprocess_text(text): # 1. 去除HTML标签 text = re.sub(r\'
\'
, \' \', text) # 修改正则表达式以正确处理
text = re.sub(r\'\', \'\', text) # 2. 去除标点和数字 text = re.sub(r\'[^a-zA-Z]\', \' \', text) # 3. 转换为小写并分词 words = text.lower().split() # 4. 去除停用词 words = [w for w in words if w not in stop_words] return \' \'.join(words)# 示例original_review = \"This movie was SO amazing!

I\'d watch it again 100 times.\"
cleaned_review = preprocess_text(original_review)print(f\"原始评论: {original_review}\")print(f\"清洗后: {cleaned_review}\")# 输出:# 原始评论: This movie was SO amazing!

I\'d watch it again 100 times.
# 清洗后: movie amazing watch would watch times
模型一:词袋模型 (Bag-of-Words, BoW)

核心思想:词袋模型非常朴素,它不关心词语的顺序和语法,只关心每个词出现的次数。就像把一篇文章中所有的词都扔进一个袋子里,然后统计袋子里每个词的数量。

实现:我们可以使用 scikit-learnCountVectorizer 轻松实现。在IMDb数据集上,这种简单的方法通常能达到 88% 左右的准确率

模型二:TF-IDF (Term Frequency-Inverse Document Frequency)

核心思想:词袋模型有个缺点,它认为所有词都同等重要。TF-IDF 通过 TF * IDF 公式,给那些在当前文档中频繁出现、但在其他文档中很少出现的词赋予更高的权重,从而突出关键词。通过引入词语重要性的权重,TF-IDF通常会比词袋模型效果稍好一些,准确率可以达到 89%~90%

传统方法小结

  • 优点:简单、快速、可解释性强。
  • 缺点:忽略了词语的顺序和上下文。例如,“not good” 和 “good not” 在它们看来可能没什么区别,但人类一看便知其天差地别。

Part 2: 飞跃提升——深度学习方法

深度学习模型,特别是循环神经网络(RNN)和Transformer,能够学习文本的序列信息和上下文关系,从而实现更精准的理解。

模型三:LSTM (Long Short-Term Memory)

核心思想:LSTM是一种特殊的RNN,它像人一样按顺序阅读文本,并拥有一个“记忆单元”,能够记住前文的关键信息,从而理解长距离的依赖关系。

经过训练,一个调优良好的LSTM模型在IMDb数据集上可以轻松达到 92%~94% 的准确率,因为它读懂了“语序”。

模型四:BERT (Bidirectional Encoder Representations from Transformers)

核心思想:BERT是Google在2018年发布的“王炸”模型。它基于Transformer架构,可以同时“看到”整个句子的所有词(双向性),从而捕捉到极其丰富的上下文信息。我们只需要在海量数据上预训练好的BERT模型上进行微调(Fine-tuning),就能获得惊人的效果。

经过简单的微调,BERT模型在IMDb数据集上的准确率可以轻松超过 95%,展现了预训练模型的强大威力。

Part 3: 终极对决——效果对比与分析

现在,让我们把所有英雄都请上台,看看它们的表现如何。

性能对比表
模型 核心思想 优点 缺点 IMDb准确率(约) BoW + LR 统计词频 简单、快速、可解释性强 忽略语序和上下文 88% TF-IDF + LR 突出关键词重要性 简单、快速、效果优于BoW 忽略语序和上下文 90% LSTM 序列化处理,有记忆 能理解语序和长依赖关系 训练慢,资源消耗中等 93% BERT 预训练+双向上下文 效果顶尖,理解力强 训练/推理慢,资源消耗大 95%+
准确率对比图

从图中可以清晰地看到一条上扬的曲线。从传统统计方法到深度学习,再到强大的预训练模型,我们每一步都让机器更接近“理解”语言的本质,准确率也随之稳步提升。

总结与展望

通过这次实践,我们不仅成功构建了一个情感分析器,更完整地体验了NLP(自然语言处理)技术的发展路径。技术本身很有趣,但更有趣的是用技术去解决我们身边的实际问题。现在,你也可以自豪地说:“我用机器学习做了一个‘电影评论情感分析器’!”


附录:完整代码实践

为了方便大家动手尝试,我将所有模型的实现整合到了一个脚本中。你可以直接复制并运行它,亲身体验从数据处理到模型训练、评估的全过程。

准备工作

在运行代码前,请确保安装了所有必要的库。

pip install tensorflow tensorflow-datasets scikit-learn nltk transformers

同时,首次运行时,需要下载NLTK的停用词库:

import nltknltk.download(\'stopwords\')
完整Python脚本
import reimport numpy as npimport tensorflow as tfimport tensorflow_datasets as tfdsfrom sklearn.feature_extraction.text import CountVectorizer, TfidfVectorizerfrom sklearn.linear_model import LogisticRegressionfrom sklearn.pipeline import make_pipelinefrom sklearn.metrics import accuracy_scorefrom nltk.corpus import stopwordsfrom transformers import BertTokenizer, TFBertForSequenceClassification, logging as hf_loggingfrom tensorflow.keras.models import Sequentialfrom tensorflow.keras.layers import Embedding, LSTM, Dense, Bidirectionalfrom tensorflow.keras.preprocessing.text import Tokenizerfrom tensorflow.keras.preprocessing.sequence import pad_sequences# --- 全局设置与预处理 ---# 关闭Hugging Face不必要的警告hf_logging.set_verbosity_error()# 加载停用词stop_words = set(stopwords.words(\'english\'))# 文本预处理函数def preprocess_text(text): text = text.decode(\'utf-8\') # 从tf.string解码 text = re.sub(r\'
\'
, \' \', text) text = re.sub(r\'[^a-zA-Z]\', \' \', text) words = text.lower().split() words = [w for w in words if w not in stop_words and len(w) > 1] return \' \'.join(words)# --- 数据加载 ---# 为了快速演示,我们只使用一小部分数据# 如果想在完整数据集上运行,请移除 take() 方法NUM_SAMPLES_TRAIN = 5000NUM_SAMPLES_TEST = 1000print(\"正在加载和预处理IMDb数据集...\")# 加载数据集ds_train, ds_test = tfds.load(\'imdb_reviews\', split=[\'train\', \'test\'], as_supervised=True)# 提取文本和标签 (Numpy格式,用于Sklearn)X_train_text = [preprocess_text(text.numpy()) for text, label in ds_train.take(NUM_SAMPLES_TRAIN)]y_train = np.array([label.numpy() for text, label in ds_train.take(NUM_SAMPLES_TRAIN)])X_test_text = [preprocess_text(text.numpy()) for text, label in ds_test.take(NUM_SAMPLES_TEST)]y_test = np.array([label.numpy() for text, label in ds_test.take(NUM_SAMPLES_TEST)])print(f\"数据加载完毕。训练集样本数: {len(X_train_text)}, 测试集样本数: {len(X_test_text)}\")# --- 模型字典,用于存储结果 ---results = {}# --- 1. 词袋模型 (BoW) + 逻辑回归 ---print(\"\\n--- 开始训练词袋模型 (BoW) ---\")bow_pipeline = make_pipeline( CountVectorizer(), LogisticRegression(max_iter=1000))bow_pipeline.fit(X_train_text, y_train)y_pred_bow = bow_pipeline.predict(X_test_text)accuracy_bow = accuracy_score(y_test, y_pred_bow)results[\'BoW + Logistic Regression\'] = accuracy_bowprint(f\"BoW 模型准确率: {accuracy_bow:.4f}\")# --- 2. TF-IDF + 逻辑回归 ---print(\"\\n--- 开始训练 TF-IDF 模型 ---\")tfidf_pipeline = make_pipeline( TfidfVectorizer(), LogisticRegression(max_iter=1000))tfidf_pipeline.fit(X_train_text, y_train)y_pred_tfidf = tfidf_pipeline.predict(X_test_text)accuracy_tfidf = accuracy_score(y_test, y_pred_tfidf)results[\'TF-IDF + Logistic Regression\'] = accuracy_tfidfprint(f\"TF-IDF 模型准确率: {accuracy_tfidf:.4f}\")# --- 3. LSTM 模型 ---print(\"\\n--- 开始训练 LSTM 模型 ---\")# 文本序列化和填充vocab_size = 10000max_len = 200tokenizer_keras = Tokenizer(num_words=vocab_size)tokenizer_keras.fit_on_texts(X_train_text)X_train_seq = tokenizer_keras.texts_to_sequences(X_train_text)X_test_seq = tokenizer_keras.texts_to_sequences(X_test_text)X_train_pad = pad_sequences(X_train_seq, maxlen=max_len, padding=\'post\')X_test_pad = pad_sequences(X_test_seq, maxlen=max_len, padding=\'post\')# 构建LSTM模型model_lstm = Sequential([ Embedding(input_dim=vocab_size, output_dim=128, input_length=max_len), Bidirectional(LSTM(64)), Dense(64, activation=\'relu\'), Dense(1, activation=\'sigmoid\')])model_lstm.compile(optimizer=\'adam\', loss=\'binary_crossentropy\', metrics=[\'accuracy\'])# 训练 (使用少量epoch进行快速演示)model_lstm.fit(X_train_pad, y_train, epochs=2, batch_size=64, validation_split=0.1, verbose=1)loss, accuracy_lstm = model_lstm.evaluate(X_test_pad, y_test, verbose=0)results[\'LSTM\'] = accuracy_lstmprint(f\"LSTM 模型准确率: {accuracy_lstm:.4f}\")# --- 4. BERT 微调 ---print(\"\\n--- 开始微调 BERT 模型 ---\")print(\"注意:微调BERT可能需要较长时间,且推荐在GPU上运行。\")model_name = \'bert-base-uncased\'tokenizer_bert = BertTokenizer.from_pretrained(model_name)model_bert = TFBertForSequenceClassification.from_pretrained(model_name, num_labels=2)# BERT需要特殊的输入格式train_encodings = tokenizer_bert(X_train_text, truncation=True, padding=True, max_length=128)test_encodings = tokenizer_bert(X_test_text, truncation=True, padding=True, max_length=128)# 转换为tf.data.Datasettrain_dataset = tf.data.Dataset.from_tensor_slices(( dict(train_encodings), y_train)).shuffle(100).batch(16)test_dataset = tf.data.Dataset.from_tensor_slices(( dict(test_encodings), y_test)).batch(16)# 编译和微调 (使用低学习率)optimizer = tf.keras.optimizers.Adam(learning_rate=3e-5)model_bert.compile(optimizer=optimizer, loss=model_bert.compute_loss, metrics=[\'accuracy\'])# 训练 (仅1个epoch进行快速演示)model_bert.fit(train_dataset, epochs=1, validation_data=test_dataset.take(len(test_dataset)//2)) # 仅用部分测试集做验证loss, accuracy_bert = model_bert.evaluate(test_dataset, verbose=0)results[\'BERT\'] = accuracy_bertprint(f\"BERT 模型准确率: {accuracy_bert:.4f}\")# --- 最终结果对比 ---print(\"\\n\\n--- 所有模型性能对比 ---\")for model, acc in results.items(): print(f\"{model:<30} | 准确率: {acc:.4f}\")
如何运行与解读
  1. 保存代码:将上述代码保存为一个Python文件,例如 sentiment_analyzer.py
  2. 运行:在终端中执行 python sentiment_analyzer.py
  3. 注意
    • 该脚本为了能快速在普通电脑上运行,只使用了IMDb数据集的一小部分(5000条训练,1000条测试)。因此,你得到的准确率会与在完整数据集(50000条)上训练的结果有所偏差,但模型的相对强弱关系依然成立。
    • 训练BERT非常消耗计算资源,即使只训练1个周期(Epoch),在CPU上也可能需要几十分钟。如果你的电脑有支持CUDA的NVIDIA显卡,并正确安装了TensorFlow的GPU版本,速度会快非常多。
    • 要获得文中所述的更高准确率,请移除代码中的 NUM_SAMPLES_TRAINNUM_SAMPLES_TEST 限制,并在完整数据集上增加深度学习模型的训练周期(例如,LSTM训练5-10个epochs,BERT训练2-3个epochs)。

祝你编码愉快,在实践中感受NLP的魅力!