首页
/ ML-For-Beginners 实战课:基于酒店评论数据的 NLTK 情感分析全流程(数据过滤与 VADER 实现)

ML-For-Beginners 实战课:基于酒店评论数据的 NLTK 情感分析全流程(数据过滤与 VADER 实现)

2026-09-06 18:03:20作者:贡沫苏Truman

导读

本篇文章是开源课程 ML-For-Beginners 第 6 单元 NLP(自然语言处理)中“酒店评论情感分析”课(6-NLP/5-Hotel-Reviews-2)的完整技术展开。围绕约 51.5 万条欧洲酒店真实评论数据,我们将系统地讲解两件事:一是对原始数据集做深度的列级清洗与筛选(把不可信的元数据列换成我们亲手算出来的可靠指标,并把高度冗余的 Tags 列转化为 8 个 0/1 的“旅客画像”列);二是使用 NLTK 完成文本预处理与 VADER 情感分析,为每条评论的正面/负面文本打分,产出可直接用于后续建模与推荐系统的 Hotel_Reviews_NLP.csv。读完本文,你不仅能在自己的机器上复现这条“原始 CSV → 过滤数据 → 情感打分数据”的完整流水线,还能理解每步清洗决策背后的数据动机与性能考量。

说明:本课配套代码均可从仓库获取——英文原版课程见 6-NLP/5-Hotel-Reviews-2/README.md(本篇文章对应的阿拉伯语译文为 translations/ar/6-NLP/5-Hotel-Reviews-2/README.md),完整可执行实现为 solution 目录下的三个 Notebook:过滤 1-notebook.ipynb、Tags 分析 2-notebook.ipynb、情感分析 3-notebook.ipynb;除 Python 外,同一套处理思路还提供了 JuliaR 两个版本供对比学习。


一、为什么要“再清洗一遍”:原始数据集的可信度问题

本课的数据来自上一课《探索酒店评论数据集》中用到的原始文件 Hotel_Reviews.csv。上一课完成的是宏观的数据探查(EDA),而本课的目标非常具体:

  1. 把列过滤干净——去掉无信息量的列,修正不可信的列;
  2. 把 Tags 列变得有用——把文本形式的标签列表转化为可查询、可统计的指标列;
  3. 引入 NLP 情感分析——对评论的正负文本自动打分,形成新的特征。

正如课程文档所指出的,探索阶段暴露了原始数据集的若干“硬伤”:部分列塞满了无用信息;另一些列虽然看起来正确,但无法弄清其计算方法,也就无法用我们自己的计算独立验证其正确性。因此,与其盲目信任这些既有列,不如全部替换为经过自己计算、口径明确的指标——这正是本课过滤环节的设计初衷。

需要先提醒:Hotel_Reviews.csv 原始数据文件体积较大,并不随仓库提交;需要按 6-NLP/data/README.md 的说明自行下载放置到 6-NLP/data/ 目录后,再运行各 Notebook(Notebook 内部通过相对路径 ../../data/ 读取数据)。


二、第一大步:更彻底的数据清洗

2.1 初始列处理(地址规范化与无用列删除)

数据集中 latlng 两列对“酒店推荐”目标无直接帮助,第一步直接删除:

df.drop(["lat", "lng"], axis = 1, inplace=True)

接着处理 Hotel_Address。原始地址列包含街道级别的长文本,但本课的分析只需要“城市 + 国家”粒度。数据集中的城市只有六个:阿姆斯特丹(荷兰)、巴塞罗那(西班牙)、伦敦(英国)、米兰(意大利)、巴黎(法国)、维也纳(奥地利)。通过逐行匹配关键字,把地址统一压缩成这六种取值:

def replace_address(row):
    if "Netherlands" in row["Hotel_Address"]:
        return "Amsterdam, Netherlands"
    elif "Barcelona" in row["Hotel_Address"]:
        return "Barcelona, Spain"
    elif "United Kingdom" in row["Hotel_Address"]:
        return "London, United Kingdom"
    elif "Milan" in row["Hotel_Address"]:
        return "Milan, Italy"
    elif "France" in row["Hotel_Address"]:
        return "Paris, France"
    elif "Vienna" in row["Hotel_Address"]:
        return "Vienna, Austria"

# Replace all the addresses with a shortened, more useful form
df["Hotel_Address"] = df.apply(replace_address, axis = 1)
# The sum of the value_counts() should add up to the total number of reviews
print(df["Hotel_Address"].value_counts())

值得注意的是,在 1-notebook.ipynb 的实现里,replace_address 还包含一个 else 分支直接返回原值,作为地址未能命中六个城市时的兜底,保证函数对每一行都有返回值。

地址规范化之后,就可以用 groupby 直接查询“各城市拥有多少家不同酒店”了:

display(df.groupby("Hotel_Address").agg({"Hotel_Name": "nunique"}))

Hotel_Namenunique(不重复计数)得到各城市酒店数量如下:

Hotel_Address Hotel_Name
Amsterdam, Netherlands 105
Barcelona, Spain 211
London, United Kingdom 400
Milan, Italy 162
Paris, France 458
Vienna, Austria 158

注意六个数字相加恰为 1494,并不等于课程中提到的 1427 家酒店总数,因为同一城市不同分店可能共享地址,且去重口径不同——这正好体现了“亲自用 value_counts() / nunique() 验证”的价值:一切指标都以你的实际数据为准。

2.2 酒店元评论列的处理(用自己算的指标替换“官方”列)

处理对象是描述酒店整体状况的三列元数据:

  1. 删除 Additional_Number_of_Scoring:其口径不透明且本课用不到;
  2. 替换 Total_Number_of_Reviews:原来表示酒店“累计收到的评论总数”,本课改为该酒店在当前数据集中实际存在的评论行数
  3. 替换 Average_Score:原来可能是平台全量数据的平均分,本课改为基于本数据集内每条 Reviewer_Score 重新计算的均值

实现非常简洁,核心是 pandas 的 groupby + transform——transform 会把聚合结果广播回原始行数,保证不改变 DataFrame 形状:

# Drop `Additional_Number_of_Scoring`
df.drop(["Additional_Number_of_Scoring"], axis = 1, inplace=True)
# Replace `Total_Number_of_Reviews` and `Average_Score` with our own calculated values
df.Total_Number_of_Reviews = df.groupby('Hotel_Name').transform('count')
df.Average_Score = round(df.groupby('Hotel_Name').Reviewer_Score.transform('mean'), 1)

这里 groupby('Hotel_Name').transform('count') 统计的是每个酒店名出现的行数,即本数据集内真实评论条数;transform('mean') 计算同一酒店下 Reviewer_Score 的平均值,并用 round(..., 1) 保留一位小数,从而得到一个口径完全可控、可复算的“平均分”。这种“官方列只做参考、核心指标自己重算”的思路,是数据工程中应对不可信来源数据的基本功。

2.3 评论内容列与评论者列的处理

对评论正文相关的列:

  • 删除 Review_Total_Negative_Word_CountsReview_Total_Positive_Word_Counts(单词计数字段可由文本重新计算,且与后续 NLP 目标无关);
  • 删除 Review_Datedays_since_review(时间维度在本课不参与分析);
  • 保留 Reviewer_ScoreNegative_ReviewPositive_Review 三列原样不动——它们分别是评分与正负评论文本,是后续情感分析的主战场;
  • 暂时保留 Tags 列——下一步还要对它做专门的过滤操作,之后才会被删除。

对评论者相关的列:

  • 删除 Total_Number_of_Reviews_Reviewer_Has_Given(评论者历史行为在本课目标下无关紧要);
  • 保留 Reviewer_Nationality(国籍可作后续分析的维度之一)。

1-notebook.ipynb 中,这些删除是在生成标签列之后一次性完成的(该 Notebook 还会把 Review_Date 一并删除),随后打印提示并保存,整个过滤过程实测约耗时 23.74 秒。


三、Tags 列:从“文本列表垃圾场”到 8 个有用特征

3.1 Tags 列为什么是难题

Tags 列本质是一个以字符串形式存储的 Python 列表,例如某行可能是 [' Business trip ', ' Solo traveler ', ' Single Room ', ' Stayed 5 nights ', ' Submitted from a mobile device ']。它有三个致命麻烦:

  1. 它是文本形态的列表,且带方括号、引号和多余空格;
  2. 每行包含的子标签个数与顺序都不固定——有的行 5 个、有的 3 个、有的 6 个;
  3. 面对 51.5 万行(课程文档按 515,000 计,实测过滤输出为 515,738 行)× 1427 家酒店,人工根本无法逐个判断哪些短语值得关注;即便用机器直接跑“多词短语频次分布”,对全量 6,762,646 个单词做无差别统计也会慢得离谱。

这正是“探索性数据分析 + NLP 思维”发光的地方:先抽样看几条 Tags,很快就能意识到——标签本来就是已被人工整理好的多词短语,只是以逗号分隔混在列表文本里;我们根本不需要做复杂的短语抽取,只需要做格式清理和结构拆分。

3.2 格式清理与“借位”拆列

首先用最快的 pandas 字符串方法去掉方括号与引号,让每行变成类似 Business trip, Solo traveler, Single Room, Stayed 5 nights, Submitted from a mobile device 的纯逗号分隔串:

# Remove opening and closing brackets
df.Tags = df.Tags.str.strip("[']")
# remove all quotes too
df.Tags = df.Tags.str.replace(" ', '", ",", regex = False)

str.replace(..., regex=False) 显式关闭正则模式,做纯字面量替换,在百万级文本上更快。

由于标签在每个评论里顺序不一、数量不一,直接对整行做 value_counts() 会数不准。课程给出的巧妙解法是利用“每列即是一个槽位”的固定结构:先按逗号把字符串 split 成最多 6 个临时列 Tag_1Tag_6(原始数据最多 6 个子标签),再用 melt 把 6 列纵向堆叠成一个大列,最后对这个大列执行 value_counts()。这在 2-notebook.ipynb 中有完整实现:

# Now split the strings into a list
tag_list_df = df.Tags.str.split(',', expand = True)

# Remove leading and trailing spaces
df["Tag_1"] = tag_list_df[0].str.strip()
df["Tag_2"] = tag_list_df[1].str.strip()
# ... Tag_3 ~ Tag_6 同理

# Merge the 6 columns into one with melt
df_tags = df.melt(value_vars=["Tag_1", "Tag_2", "Tag_3", "Tag_4", "Tag_5", "Tag_6"])
tag_vc = df_tags.value.value_counts()

该 Notebook 打印出展开后标签数据的形状为 (2514684, 2)(约 6×41.9 万行量级),并说明如何按需用 str.contains 过滤掉含 Standard|room|Stayed|device|Beds|Suite|Studio|King|Superior|Double 的标签、再以 count > 1000 收紧阈值——最终定位到值得保留的核心标签集合。统计结果显示,Tags 全表去重后共有 2428 个唯一标签,其中高频标签形如下表:

Tag Count
Leisure trip 417778
Submitted from a mobile device 307640
Couple 252294
Stayed 1 night 193645
Stayed 2 nights 133937
Solo traveler 108545
Stayed 3 nights 95821
Business trip 82939
Group 65392
Family with young children 61015
Stayed 4 nights 47817
Double Room 35207
Standard Double Room 32248
Superior Double Room 31393
Family with older children 26349
Deluxe Double Room 24823
Double or Twin Room 22393
Stayed 5 nights 20845
Standard Double or Twin Room 17483
Classic Double Room 16989
Superior Double or Twin Room 13570
2 rooms 12393

Submitted from a mobile device(来自移动设备)这类高频标签对我们毫无用处,但既然统计操作足够快,留在表里直接忽略即可,不必刻意先删。

3.3 标签取舍策略:只保留“与推荐目标相关”的两类

课程文档强调,数据集的最终目的是为“选酒店 / 做酒店推荐机器人”服务的,因此过滤标准必须锚定这个业务目标。按此逻辑对标签逐类判断:

  1. 旅行类型(休闲/商务)——相关,保留;
  2. 住客群体类型(情侣、独自旅行、家庭、团体等)——重要,保留;
  3. 房型 / 套房 / 工作室类型——无关(各酒店房型大同小异),剔除;
  4. 提交评论的设备——无关,剔除;
  5. 入住夜数——勉强可以说“住得久 = 更喜欢”,但证据牵强、大概率无关,剔除。

一句话概括:只保留两类标签(旅程性质 + 住客画像),其余全部放弃考虑。

剔除停留夜数标签(注意:这不是从 DataFrame 删行,而是把这些取值排除在“值得统计/入库”的标签清单之外):

Length of stay Count
Stayed 1 night 193645
Stayed 2 nights 133937
Stayed 3 nights 95821
Stayed 4 nights 47817
Stayed 5 nights 20845
Stayed 6 nights 9776
Stayed 7 nights 7399
Stayed 8 nights 2502
Stayed 9 nights 1293
... ...

房型标签同样品种繁多却语义近似,一并从候选清单剔除:

Type of room Count
Double Room 35207
Standard Double Room 32248
Superior Double Room 31393
Deluxe Double Room 24823
Double or Twin Room 22393
Standard Double or Twin Room 17483
Classic Double Room 16989
Superior Double or Twin Room 13570

经过这轮判断,最终幸存的“有用标签”如下(GroupTravelers with friends 语义几乎等价,故在统计中合并为一项):

Tag Count
Leisure trip 417778
Couple 252294
Solo traveler 108545
Business trip 82939
Group (combined with Travellers with friends) 67535
Family with young children 61015
Family with older children 26349
With a pet 1405

3.4 生成 8 个 0/1 特征列

最后一步是把这 8 个有用标签落成新列:对每一行评论,若其 Tags 文本中包含某标签则置 1,否则置 0。最终得到“这家酒店被多少位(汇总的)评论者出于商务 / 休闲 / 携带宠物等目的选择”的统计口径,这正是酒店推荐场景所需的画像特征:

# Process the Tags into new columns
# The file Hotel_Reviews_Tags.py, identifies the most important tags
# Leisure trip, Couple, Solo traveler, Business trip, Group combined with Travelers with friends,
# Family with young children, Family with older children, With a pet
df["Leisure_trip"] = df.Tags.apply(lambda tag: 1 if "Leisure trip" in tag else 0)
df["Couple"] = df.Tags.apply(lambda tag: 1 if "Couple" in tag else 0)
df["Solo_traveler"] = df.Tags.apply(lambda tag: 1 if "Solo traveler" in tag else 0)
df["Business_trip"] = df.Tags.apply(lambda tag: 1 if "Business trip" in tag else 0)
df["Group"] = df.Tags.apply(lambda tag: 1 if "Group" in tag or "Travelers with friends" in tag else 0)
df["Family_with_young_children"] = df.Tags.apply(lambda tag: 1 if "Family with young children" in tag else 0)
df["Family_with_older_children"] = df.Tags.apply(lambda tag: 1 if "Family with older children" in tag else 0)
df["With_a_pet"] = df.Tags.apply(lambda tag: 1 if "With a pet" in tag else 0)

注意 Group 列的两条件写法正好落地了“与 Travelers with friends 合并”的决策。值得说明的是,这 8 个“画像标签”的识别逻辑在源码中对应课程注释提到的 Hotel_Reviews_Tags.py 脚本(识别最重要标签),而标签的发现与验证过程对应 2-notebook.ipynb1-notebook.ipynb 中则是在读取原始 CSV 后依次完成上述清洗与新列生成,并在末尾一次性删除包括 Review_DateReview_Total_Negative_Word_CountsReview_Total_Positive_Word_Countsdays_since_reviewTotal_Number_of_Reviews_Reviewer_Has_Given 在内的剩余无用列。

3.5 保存过滤结果

清洗完成后,用新文件名落盘,得到整条流水线的第一个中间产物 Hotel_Reviews_Filtered.csv(保存在数据目录,Notebook 中写入路径为 ../../data/):

df.drop(["Review_Total_Negative_Word_Counts", "Review_Total_Positive_Word_Counts",
         "days_since_review", "Total_Number_of_Reviews_Reviewer_Has_Given"],
        axis = 1, inplace=True)

# Saving new data file with calculated columns
print("Saving results to Hotel_Reviews_Filtered.csv")
df.to_csv(r'../data/Hotel_Reviews_Filtered.csv', index = False)

1-notebook.ipynb 的实际输出看,这一整轮过滤(读入原始 CSV 起)耗时约 23.74 秒。到这里,数据质量已经大幅提升:地址粒度统一、评分口径可信、画像特征就位。


四、第二大步:用 NLTK + VADER 做情感分析

4.1 加载过滤后的数据(而不是原始数据)

情感分析阶段的核心前提是:加载的是上一节保存的 Hotel_Reviews_Filtered.csv,绝不是原始数据集。课程文档给出的骨架代码还包含后续两个阶段需要使用的所有 import:

import time
import pandas as pd
import nltk as nltk
from nltk.corpus import stopwords
from nltk.sentiment.vader import SentimentIntensityAnalyzer
nltk.download('vader_lexicon')

# Load the filtered hotel reviews from CSV
df = pd.read_csv('../../data/Hotel_Reviews_Filtered.csv')

其中 nltk.download('vader_lexicon') 用于首次运行时下载 VADER 所需的词典资源(在 3-notebook.ipynb 的执行记录里可以看到它把数据安装到了本地 nltk_data 目录并返回 True)。

4.2 性能问题与停用词移除

如果直接在原始长度的正负评论两列上跑情感分析,会非常慢——课程文档给出的实测是:在配置不错的测试笔记本上,直接跑需要 12~14 分钟(具体取决于所选的情感分析库)。因此值得先做优化。

停用词(stop words) 指英语中不影响句子情感的常见词(the、and、of 之类)。把它们移除后,情感分析输入变短、运行更快,而准确率不受影响。文档给出的量化依据是:最长的一条负面评论原本 395 个词,移除停用词后只剩 195 个词,文本量接近减半。

移除停用词本身开销极低:在测试设备上对 51.5 万行的两个评论列做全量移除只需 3.3 秒(在 3-notebook.ipynb 的实测记录中为 5.77 秒——具体耗时取决于你的 CPU、内存、是否 SSD 等因素)。投入如此小、收益却可能显著,这笔优化非常划算。

代码层面有一个重要的性能细节:先把停用词表放进 set(哈希集合)再做成员判断,比逐个在原始 list 中线性查找快得多,这正是 Kaggle 上被反复验证的“快速停用词移除”做法:

from nltk.corpus import stopwords

# Load the hotel reviews from CSV
df = pd.read_csv("../../data/Hotel_Reviews_Filtered.csv")

# Remove stop words - can be slow for a lot of text!
start = time.time()
cache = set(stopwords.words("english"))
def remove_stopwords(review):
    text = " ".join([word for word in review.split() if word not in cache])
    return text

# Remove the stop words from both columns
df.Negative_Review = df.Negative_Review.apply(remove_stopwords)
df.Positive_Review = df.Positive_Review.apply(remove_stopwords)

review.split() 天然按空白分词,配合集合判空即得精简后的文本;Negative_ReviewPositive_Review 两列同步处理。

4.3 用 VADER 计算情感分数

课程选用 VADER(Valence Aware Dictionary and sEntiment Reasoner) 作为情感分析器。它出自 Hutto & Gilbert(2014)在 ICWSM-14 发表的论文,是一种基于规则与情感词典、专门面向社交媒体类文本设计的模型——对短评、口语化、带表情符号与程度副词的文本尤其友好。NLTK 中其实内置了多种情感分析器可供替换比较,这里以 VADER 为主。

需要特别注意评论列的三种取值形态:数据集对“没有可写内容”的情况会用占位文本 "No Negative" / "No Positive" 填充对应列。对这类占位值直接送进分析器是没意义的,因此 calc_sentiment 先拦截它们并返回中性分 0,只有真正的评论文本才调用 polarity_scores(review)["compound"] 取综合情绪分:

from nltk.sentiment.vader import SentimentIntensityAnalyzer

# Create the vader sentiment analyser (there are others in NLTK you can try too)
vader_sentiment = SentimentIntensityAnalyzer()

# There are 3 possibilities of input for a review:
# It could be "No Negative", in which case, return 0
# It could be "No Positive", in which case, return 0
# It could be a review, in which case calculate the sentiment
def calc_sentiment(review):
    if review == "No Negative" or review == "No Positive":
        return 0
    return vader_sentiment.polarity_scores(review)["compound"]

polarity_scores() 会返回 neg / neu / pos / compound 四个分数,其中 compound 是把三者按规则归一化到 [-1, 1] 的综合分:越接近 1 越正向,越接近 -1 越负向,0 为中性。我们只取 compound 作为一维情感特征。

随后对两个评论列逐行套用并记录耗时:

# Add a negative sentiment and positive sentiment column
print("Calculating sentiment columns for both positive and negative reviews")
start = time.time()
df["Negative_Sentiment"] = df.Negative_Review.apply(calc_sentiment)
df["Positive_Sentiment"] = df.Positive_Review.apply(calc_sentiment)
end = time.time()
print("Calculating sentiment took " + str(round(end - start, 2)) + " seconds")

关于耗时:课程文档给出的经验值是“在我的电脑上约 120 秒”,具体到 3-notebook.ipynb 的真实执行输出则是 201.07 秒(运行机器的差异导致浮动),量级上属于“分钟级、可接受”的范围,且得益于此前停用词的移除,已经比不做预处理的 12~14 分钟大幅缩短。

4.4 用排序检查结果质量:情感分 vs 评分是否吻合

要判断打分是否合理,最直接的方法是分别按负向、正向情感分排序,肉眼抽查文本与分数的对应关系:

df = df.sort_values(by=["Negative_Sentiment"], ascending=True)
print(df[["Negative_Review", "Negative_Sentiment"]])
df = df.sort_values(by=["Positive_Sentiment"], ascending=True)
print(df[["Positive_Review", "Positive_Sentiment"]])

3-notebook.ipynb 的排序输出显示:最负向的负面评论(如 “So bad experience memories…”“First charged twice…”“No WLAN room Incredibly rude restaurant staff…”)compound 分低至 -0.99 左右,最正向的则高达 +0.99 以上;正向评论列同样呈现从 -0.98 到 +0.9987 的完整谱系,输出共覆盖 515,738 行 × 2 列

课程同时提醒我们理性看待自动打分的错误:当情感分与评论者的星级评分矛盾时,常见解释是文本本身高度反讽,例如 “Of course I LOVED sleeping in a room with no heating”(我当然“爱死”了在没有暖气的房间睡觉)——人类一眼看出是反话,VADER 却可能判为正向。因此要预期一部分情感分数必然是错的,而且往往错得可解释;这正是后续人工校验与更复杂模型(如考虑上下文、讽刺检测)的用武之地。

4.5 重排列顺序并保存最终数据集

情感分算完后,为了让列顺序对人类友好(纯外观层面的整理),把全部新列按“酒店主键 → 评分 → 情感 → 画像 → 评论文本”的逻辑重新排列,然后落盘:

# Reorder the columns (This is cosmetic, but to make it easier to explore the data later)
df = df.reindex(["Hotel_Name", "Hotel_Address", "Total_Number_of_Reviews", "Average_Score",
                 "Reviewer_Score", "Negative_Sentiment", "Positive_Sentiment",
                 "Reviewer_Nationality", "Leisure_trip", "Couple", "Solo_traveler",
                 "Business_trip", "Group", "Family_with_young_children",
                 "Family_with_older_children", "With_a_pet",
                 "Negative_Review", "Positive_Review"], axis=1)

print("Saving results to Hotel_Reviews_NLP.csv")
df.to_csv(r"../data/Hotel_Reviews_NLP.csv", index = False)

保存后得到整条流水线的最终产物 Hotel_Reviews_NLP.csv:它同时携带了可信的酒店级统计(自算评论数、自算均分)、两级情感分数(正/负各一个)、8 个住客画像特征以及原始评论文本——具备直接进入聚类、分类或推荐建模的资格。


五、端到端流水线回顾与课程后续

把前面所有步骤串起来,本课构成一条清晰的“四级流水线”:

  1. 探索:在上一课(6-NLP/4-Hotel-Reviews-1)中通过探索 Notebook 熟悉原始 Hotel_Reviews.csv 的结构与分布;
  2. 过滤:由过滤 Notebook(1-notebook.ipynb)清洗列、规范化地址、自算评分/评论数、生成画像特征,输出 Hotel_Reviews_Filtered.csv
  3. 情感分析:由情感 Notebook(3-notebook.ipynb)移除停用词并用 VADER 打分,输出 Hotel_Reviews_NLP.csv
  4. 建模:把 Hotel_Reviews_NLP.csv 用于本课末尾的 NLP 挑战。

课程的“挑战”环节建议:既然每一条评论现在都有了情感分,可以尝试用本课程前面章节学过的聚类策略,围绕情感分探索隐藏模式(例如不同城市、不同旅客画像下的情感分布差异)。配套作业(assignment.md,英文原版见 6-NLP/5-Hotel-Reviews-2/assignment.md)则鼓励读者换一个全新的数据集,复刻整套“数据预处理 + NLTK 情感标注”思路,并用带说明的 Notebook 记录思考过程。

小结

回顾整条路径:起点是一份列虽多但不可尽信的原始数据集,终点则是一份“可验证 + 可检索 + 已标注情感”的高质量数据集。你经历了数据探索、按业务目标过滤无用列、把标签文本转化为结构化画像、用自算指标替换黑箱指标、借助停用词移除优化性能、最后用 VADER 给 51 万+ 条正负评论文本自动打分,也顺带认识了规则型情感分析的典型局限(讽刺、上下文缺失)。这套“清洗 → 特征化 → 情感标注”的方法论,可以原样迁移到任何文本类推荐或舆情分析任务上——这正是本课希望沉淀下来的通用能力。

登录后查看全文
热门项目推荐
相关项目推荐