首页
/ ML-For-Beginners 之 NLP 模块实战指南:从《傲慢与偏见》对话机器人到欧洲酒店评论情感分析

ML-For-Beginners 之 NLP 模块实战指南:从《傲慢与偏见》对话机器人到欧洲酒店评论情感分析

2026-09-07 12:17:59作者:温玫谨Lighthearted

自然语言处理(NLP)是机器学习在生产软件中应用最广的领域之一,它让计算机能够理解、翻译并与人类以"自然语言"交互。本指南对应 ML-For-Beginners 课程第 6 大模块(6-NLP),以构建对话机器人酒店评论情感分析两条主线,完整梳理模块内 5 节课的知识脉络、可运行代码与底层原理。读完你将掌握分词、词性标注、N-gram、名词短语抽取、TextBlob 翻译、NLTK/VADER 情感分析等核心技能,并能在真实数据集上独立完成"探索—清洗—打分—落盘"的完整 NLP 数据管线。

什么是自然语言处理:NLP 的技术定位

自然语言处理(Natural Language Processing,NLP)描述的是计算机程序理解人类所说、所写自然语言的能力,它是人工智能(AI)的一个组成部分。NLP 已存在超过 50 年,其学术根基位于语言学(linguistics)领域,整个学科致力于帮助机器理解并处理人类语言,进而用于拼写检查、机器翻译等具体任务。它的真实应用遍布医疗研究、搜索引擎与商业智能等众多行业。

作为计算语言学(Computational Linguistics)这一更大研究方向的子领域,NLP 关注的是"计算机如何处理自然/人类语言";而计算语言学则研究计算机如何"与语言协作,甚至理解、翻译、用语言交流"。两者共同构成了本模块 6-NLP 全部课程的理论底座——模块的原始文档位于 6-NLP/README.md,本文即是围绕该模块页及其所链接的 5 节课程展开的深度导读与实践解析。

一个日常例子:你在手机上用语音代替打字、向虚拟助手提问时,你的语音先被转成文本,再被 解析(parse) 成程序能理解并执行的关键词——这就是 NLP 在生产软件中的典型形态。

模块主题:欧洲语言、文学与"浪漫"酒店评论

本模块(课程目录见 6-NLP)用一个颇具人文气息的区域主题串起全部内容:欧洲语言与文学、以及欧洲的"浪漫"酒店。它将带你经历两段递进的学习旅程:

  1. 穿越时空与《傲慢与偏见》角色对话:你将回到 1813 年,与简·奥斯汀(Jane Austen)经典小说《傲慢与偏见》中的伊丽莎白·班纳特(Elizabeth Bennett)和达西先生(Mr. Darcy)"聊天",通过构建小型对话机器人理解机器学习如何让对话变得越来越"聪明";
  2. 用欧洲酒店评论学习情感分析:在掌握基础后,你将基于真实的大规模酒店评论数据集做探索性分析、数据清洗与情感打分,最终形成可用于酒店推荐机器人建模的数据集。

《傲慢与偏见》原著与下午茶:本模块"欧洲语言文学与浪漫酒店"主题配图

图片来源:本模块主题配图(6-NLP/images/p&p.jpg),"聊天机器人之旅"将围绕该作品中的角色对话展开。

工具与环境准备

本节课程强调"动手即理解",编码任务统一使用 Python,示例基于 Python 3.8。按原始课程要求,你需要准备:

  • Python 3 基础:会使用输入(input)、循环、文件读取与数组;
  • Visual Studio Code + Python 扩展(也可使用你喜欢的任意 Python IDE);
  • TextBlob:一个面向 Python 的简化文本处理库,它"站在 NLTK 与 pattern 两个巨人的肩膀上"。安装时除库本身外还需下载 corpora(语料数据):
pip install -U textblob
python -m textblob.download_corpora

后续课程还会用到 pandas(DataFrame 处理)与 NLTK(含 vader_lexicon 情感词典)。

五节课全景:一张 NLP 学习地图

本模块共 5 节课,学习路径高度连贯:先建立 NLP 认知 → 掌握文本处理基础任务 → 体验"翻译 + 情感"两个经典应用 → 处理 51 万条真实酒店评论 → 用 NLTK 完成情感分析建模。课程正体(英文原版)位于根目录 6-NLP/ 下,孟加拉语译本位于 translations/bn/6-NLP/ 下:

课次 主题 核心产出 原始文档(根目录)
1 自然语言处理导论 基础"Marvin"对话机器人 1-Introduction-to-NLP/README.md
2 常见 NLP 任务与技术 具备情绪识别+名词短语追问的升级版机器人 2-Tasks/README.md
3 机器学习翻译与情感分析 用 TextBlob 翻译《傲慢与偏见》、量化句子情绪 3-Translation-Sentiment/README.md
4 数据准备 对 515K 欧洲酒店评论做探索性数据分析 4-Hotel-Reviews-1/README.md
5 用 NLTK 做情感分析 清洗标签、去停用词、VADER 情感打分并落盘 5-Hotel-Reviews-2/README.md

原文档鸣谢表明:本模块全部 NLP 课程由 Stephen Howell 编写(6-NLP/README.md 中 Credits 部分)。以下各节将逐课深挖其实操内容。

第 1 课:从图灵测试到"会聊天"的机器人

第一课(6-NLP/1-Introduction-to-NLP/README.md)系统讲解了 NLP 的简短历史与关键概念。

计算的语法之难

人类能轻而易举区分的"名词与动词""副词与形容词",乃至"一般现在时与现在进行时"的区别,对程序而言需要依靠形式化规则来编码。好消息是:计算机极其擅长执行形式规则,你完全能写出像人一样 解析 句子的代码。真正的挑战在于理解句子的 含义情感——尤其是幽默与反讽这类依赖语境的表达,这也是"完美的" NLP 至今仍具挑战的原因。

图灵测试与 ELIZA:一段被反复重演的历史

让计算机理解人类语言的尝试可追溯至数十年前,最早的倡导者之一是 艾伦·图灵。他在 1950 年代研究人工智能时构想了一场对话式测试(即"图灵测试"):如果人类在限定时间的键盘对话中无法判断对方是真人还是计算机,那么能否称这台计算机在"思考"?这一想法源自名为"模仿游戏"(The Imitation Game)的聚会游戏——审讯者需通过书面问答判断另一房间两人各自的性别。

1960 年代,MIT 科学家 约瑟夫·魏泽鲍姆 开发了名为 ELIZA 的计算机"心理治疗师"。它能够解析句子、识别特定语法结构与关键词,从而给出"貌似理解"的回答,却并不能真正 理解 句子。例如当用户输入 "I am sad" 格式的句子,ELIZA 会重组并替换词语,输出 "How long have you been sad?"——看起来像在追问,实质只是改变时态、拼凑词语;当它找不到匹配关键词时,只会抛出一个可套用于任意话题的随机回复。因此它很容易被戏弄:输入 "You are a bicycle",它会反问 "How long have I been a bicycle?" 而不会给出理性回应。

动手:编码一个最基础的对话机器人(Marvin)

参考 6-NLP/1-Introduction-to-NLP/solution/bot.py,我们用 Python 实现一个名为 Marvin 的简易机器人。设计步骤为:① 打印操作指引;② 进入循环——接收输入 → 判断是否退出 → 从通用应答列表中随机选取回复 → 打印回复;③ 回到步骤 2。

random_responses = ["That is quite interesting, please tell me more.",
                    "I see. Do go on.",
                    "Why do you say that?",
                    "Funny weather we've been having, isn't it?",
                    "Let's change the subject.",
                    "Did you catch the game last night?"]

运行效果示例(> 开头为使用者输入):

Hello, I am Marvin, the simple robot.
You can end this conversation at any time by typing 'bye'
After typing each answer, press 'enter'
How are you today?
> I am good thanks
That is quite interesting, please tell me more.
> today I went for a walk
Did you catch the game last night?
...
> bye
It was nice talking to you, goodbye!

停下来想一想:① 随机应答是否可能让人误以为机器人"听懂"了自己?② 要让机器人更有效,它需要具备哪些能力?③ 若机器人真正"理解"一句话的含义,它是否必须"记住"对话中此前的句子?这些思考题正是第 2 课要解决的方向,课程还配有课后小测(assignment.md1-Introduction-to-NLP/assignment.md)。

第 2 课:文本处理的"工具箱"——常见 NLP 任务与技术

第二课(6-NLP/2-Tasks/README.md)指出:多数 NLP 任务都要先把文本拆分、检查,再把结果与规则/数据集比对,从而提取文本的 含义意图词频。以下任务通常按序执行:

Tokenization(分词)

绝大多数 NLP 算法要做的第一件事就是把文本切成 token(词)。看似简单,实际需处理标点、不同语言的词/句分隔符差异,往往要用多种手段确定切分边界。

Embeddings(词嵌入)

词嵌入 把文本数据数值化,且让语义相近或常共现的词在向量空间中聚在一起。例如《傲慢与偏见》中 "I have the highest respect for your nerves, they are my old friends." 一句经嵌入后,语义相近词会形成聚类。

Parsing 与词性标注(Part-of-speech Tagging)

每个被切分的词都可被标注为名词、动词或形容词等词性。对句子 the quick red fox jumped over the lazy brown dog,词性标注会给出 fox=noun、jumped=verb 之类结果。解析(parsing) 则进一步识别句中词与词的关联——the quick red fox jumped 是一条"形容词-名词-动词"序列,与 lazy brown dog 序列相互独立。

词与短语频率(Word and Phrase Frequencies)

分析大量文本时常用的做法是建立"感兴趣词/短语→出现次数"字典。以吉卜林诗句 The Winners 为例:其中 a friend 出现 2 次、the 出现 6 次、travels 出现 2 次(频率统计可按需区分或不区分大小写)。

N-grams

文本可按固定长度切分为词序列:单个词叫 unigram(1-gram),两个词叫 bigram(2-gram),三个词叫 trigram(3-gram),任意长度为 n-gram。对 the quick red fox jumped over the lazy brown dog 取 n=2,将得到 the quickquick redred fox……共 9 个连续二元组。可以把它想象成在句子上滑动的窗口——课程中配有 3-gram 的滑动窗口动图(6-NLP/2-Tasks/images/n-grams.gif)。

名词短语抽取(Noun phrase Extraction)

多数句子里有充当主语或宾语的名词,英语中常以 "a/an/the" 前置标识。"抽取名词短语"是理解句意时的高频任务。例如上面例句包含两个名词短语:quick red foxlazy brown dog

情感分析(Sentiment Analysis)

句子/文本可被分析为多"正面"或多"负面"。情感用 极性(polarity)客观性/主观性(objectivity/subjectivity) 度量:极性范围 -1.0~1.0(负→正),客观性 0.0~1.0(最客观→最主观)。非 ML 的做法是由专家维护"正面/负面词与短语"清单,再对文本累计打分——这种思路在某些场景有效,在反讽场景则极易被误导。

词形变化与词形还原

Inflection(屈折) 让你得到某词的单数/复数形式;Lemmatization(词形还原) 则求一组词的 lemma(词根/词头),例如 flew、flies、flying 的 lemma 都是动词 fly

资源:WordNet

研究者还有现成数据库可用,其中最著名的是 WordNet——收录多种语言每个词的近义词、反义词等丰富信息,在构建翻译、拼写检查及各类语言工具时极为有用。

实战:用 TextBlob 的 ConllExtractor 抽取名词短语

好消息是你不必手写上述全部算法。TextBlob 内置了可观的机器学习能力,并以友好的 API 暴露。例如识别名词短语时可选多种抽取器,其中 ConllExtractor 使用在 ConLL-2000(2000 年计算自然语言学习会议,当年主题即"名词组块")语料上训练的组块解析模型,训练数据取自华尔街日报第 15–18 段(211727 tokens)、第 20 段(47377 tokens)作测试。用法如下:

from textblob import TextBlob
from textblob.np_extractors import ConllExtractor
# 导入并创建 Conll 抽取器,稍后使用
extractor = ConllExtractor()

# 需要抽取名词短语时:
user_input = input("> ")
user_input_blob = TextBlob(user_input, np_extractor=extractor)  # 指定了非默认抽取器
np = user_input_blob.noun_phrases

挑战:让 Marvin 学会"共情"

参考 6-NLP/2-Tasks/solution/bot.py,我们把机器人升级为:先分析输入的 sentiment(情绪) 并给出匹配情绪的应答;若检测到 noun_phrase,则将其**复数化(pluralize)**后追问更多细节。流程与第 1 课相同,只是在步骤 3 中先算情感、步骤 4 追问名词短语。TextBlob 情感极性分档代码(分档梯度可自行增减):

if user_input_blob.polarity <= -0.5:
  response = "Oh dear, that sounds bad. "
elif user_input_blob.polarity <= 0:
  response = "Hmm, that's not great. "
elif user_input_blob.polarity <= 0.5:
  response = "Well, that sounds positive. "
elif user_input_blob.polarity <= 1:
  response = "Wow, that sounds great. "

对话示例:当用户说 "I went for a walk and saw a lovely cat",机器人回答 "Well, that sounds positive. Can you tell me more about lovely cats?"——注意 cat 被复数化为 cats 并嵌入追问。验证性问题同样值得思考:共情式应答会不会"骗过"用户?名词短语识别是否让机器人更可信?(后续练习见 2-Tasks/assignment.md

第 3 课:机器翻译与情感分析的机器学习之路

第三课(6-NLP/3-Translation-Sentiment/README.md)聚焦计算语言学中的另一大挑战:句子的准确翻译,以及情感分析这一 ML 的强项场景。

形式语法路线 vs. 朴素逐词翻译

翻译难在世界有数千种语言、语法规则迥异。一种经典思路是把源语言(如英语)的形式语法规则转为"与语言无关"的结构,再转换回目标语言:① 识别——把输入语言中的词标注为名词、动词等;② 生成译文——按目标语言格式逐词直译。

以英译爱尔兰语 "I feel happy" 为例:英语词序是 主语(I)-动词(feel)-形容词(happy);爱尔兰语却把 "happy/sad"这类情绪表达为"降临在你身上",对应句子 Tá athas orm,其字面意思是 "Happy is upon me",正式词序为 动词(Tá/is)-形容词(athas)-主语(orm)。朴素翻译程序只译词、忽略句法结构,会把 "I feel happy" 逐词翻成 Mise bhraitheann athas(字面 "me feel happy",并非合法的爱尔兰语句子)。课程借此说明:即使英爱两种语言地理相邻,语法结构也可能截然不同。

机器学习翻译:从"对译语料"中学模式

形式规则之外的另一条路线是忽略词的"含义",改用机器学习检测模式——前提是有大量源语/目标语的平行文本(corpus/ corpora)。例证依然来自《傲慢与偏见》:若同时拥有英文原著与法语人工译本,就能发现哪些短语在另一语言中被"习语化"地翻译。

英语短语 I have no money 直译成法语会变成 Je n'ai pas de monnaie——monnaie 是法语中棘手的"假同源词"(false cognate),因为 money 与 monnaie(零钱)并不同义;更好的译法应是 Je n'ai pas d'argent。当 ML 模型掌握足够多的人工翻译样本,它就能通过识别"曾由双语专家翻译过的文本"中的共性模式来提升翻译准确度。课程配图见 6-NLP/3-Translation-Sentiment/images/monnaie.png

动手:用 TextBlob 翻译《傲慢与偏见》开篇

from textblob import TextBlob

blob = TextBlob(
    "It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife!"
)
print(blob.translate(to="fr"))

输出为 "C'est une vérité universellement reconnue, qu'un homme célibataire en possession d'une bonne fortune doit avoir besoin d'une femme!"。有趣的是,这一 ML 驱动译文甚至比 1932 年 V. Leconte 与 Ch. Pressoir 的人工译本更克制——后者为"清晰"擅自添加了原作没有的枝叶。原理:TextBlob 的 blob.translate 后台调用 Google Translate(需联网),由能解析数百万短语的成熟 AI 预测最佳字符串。

情感分析的两种路线

非 ML 路线是维护"正面/负面词与短语清单"并对新文本累计打分,但其非常容易被反讽攻破:句子 Great, that was a wonderful waste of time, I'm glad we are lost on this dark road 明明是负面情绪,简单算法却把 great、wonderful、glad 记为正、waste、lost、dark 记为负,总分互相拉扯。人在传达反讽时仰仗语调(tone inflection),试试用不同语气说出 "Well, that film was awesome" 就能体会。

ML 路线则是人工收集带评分与书面意见的文本(如推文、影评),再对"意见+分数"施加 NLP 技术以浮现模式——例如正面影评比负面影评更常出现 'Oscar worthy'。课程给出的政务场景例证:议员办公室收到大量支持/反对某项新法的邮件,可以用一部分"反对邮件"和一部分"支持邮件"训练模型;模型只会学会"哪些词/模式更可能出现在反对或支持邮件里",并不理解内容本身,之后便能自动归类新邮件而不必逐封阅读。

动手:分析《傲慢与偏见》首尾句情感

TextBlob 的 sentiment 返回极性(-1 最负~1 最正)与主客观分数(0 客观~1 主观):

from textblob import TextBlob

quote1 = """It is a truth universally acknowledged, that a single man in possession of a good fortune, must be in want of a wife."""

quote2 = """Darcy, as well as Elizabeth, really loved them; and they were both ever sensible of the warmest gratitude towards the persons who, by bringing her into Derbyshire, had been the means of uniting them."""

sentiment1 = TextBlob(quote1).sentiment
sentiment2 = TextBlob(quote2).sentiment

print(quote1 + " has a sentiment of " + str(sentiment1))
print(quote2 + " has a sentiment of " + str(sentiment2))
... has a sentiment of Sentiment(polarity=0.20952380952380953, subjectivity=0.27142857142857146)

... has a sentiment of Sentiment(polarity=0.7, subjectivity=0.8)

挑战:全书极性统计

从 Project Gutenberg 下载《傲慢与偏见》纯文本并去掉首尾元数据,用 Python 读入字符串 → 创建 TextBlob → 循环分析每一句 → 将极性恰为 1 或 -1 的句子分别存入正面/负面数组 → 末尾分开展示并计数。参考解答见 3-Translation-Sentiment/solution/notebook.ipynb。随后课程引导你反思:如 "Happy shall I be, when his stay at Netherfield is over!" 与 "Our distress, my dear Lizzy, is very great." 被判为绝对正面,细读却并非如此——为什么?因为简·奥斯汀的反讽与微妙措辞正是基于词典打分的算法难以捕捉的(练习见 3-Translation-Sentiment/assignment.md)。

第 4 课:让 51 万条酒店评论"开口说话"

第四课(6-NLP/4-Hotel-Reviews-1/README.md)进入真实大数据实战,场景是用情感分析与住客评分构建酒店推荐机器人

数据集准备

所用数据集为 Kaggle 上的 515K Hotel Reviews Data in Europe(作者 Jiashen Liu,CC0 公共领域许可,从 Booking.com 公开来源抓取,解压后约 230MB)。需将其下载到 6-NLP/data/ 目录(仓库内该目录的说明文档为 6-NLP/data/README.md,正文提示"Download the hotel review data to this folder"),并准备 pandas 与本地安装的 NLTK。

数据集覆盖 6 座城市共 1493 家酒店、51 万余条评论,目标是回答三类问题:评论中最常出现的词与短语是什么?官方 Tags 标签是否与评分相关(例如某酒店"带幼童的家庭"评论是否比"独自旅行者"更负面)?NLTK 的情感得分是否与住客给分一致?

字段结构

数据集共 17 列,按语义可分四组:

  • 酒店信息列Hotel_NameHotel_Addresslat(纬度)、lng(经度)。lat/lng 可用来在地图上按情感着色;Hotel_Address 信息冗余,后续应替换为国家/城市以方便排序检索。
  • 酒店元评论列Average_Score(据数据集作者说明为"按最近一年最新评论计算的酒店平均分")、Total_Number_of_Reviews(酒店收到的评论总数,未必等于数据集内条数)、Additional_Number_of_Scoring(只打了分但没写正/负评论的数量)。
  • 单条评论列Reviewer_Score(1 位小数、范围 2.5~10)、Negative_Review(没写则为 "No Negative",注意可能有人在负面列写正面内容)、Review_Total_Negative_Word_Counts(负面词数高通常对应低分)、Positive_ReviewReview_Total_Positive_Word_Counts(同理)、Review_Datedays_since_review(可用以衡量评论"新鲜度")、Tags(住客选择的短描述符)。
  • 评论者列Total_Number_of_Reviews_Reviewer_Has_Given(数据集内无唯一评论者 ID,难以关联多次评论)、Reviewer_Nationality(课程专门提醒:切勿把"某国国民更倾向给正/负评"这类成见写进模型)。

课程给出一个极具代表性的样本行:某住客给一家平均分 7.8、共 1945 条评论的酒店打出 2.5 分,负面评论写了 115 词控诉施工噪音与乱扣费,正面栏只留 7 个警告词;若只数词量不看语义,会严重歪曲其真实意图。值得注意:数据集中最低分是 2.5 而非 0,最高 10。

Tags 为什么"又爱又恨"

Tags 看似可按类型分组,实则极不规范:不同酒店的房型选项可能是 Single room/Twin room/Double room,也可能是 Deluxe Single Room/Classic Queen Room/Executive King Room;通常一格里塞 5~6 个逗号分隔值(出行类型、客群类型、房型、入住夜数、提交设备),但字段常缺失导致顺序不一。该字段"类型 of group"位置上就有 1025 种可能取值。应对策略有二:① 把所有术语归一到单一标准(映射路径常不清晰,极难);② 走 NLP 路线——统计 SoloBusiness TravellerFamily with young kids 等词在各酒店的频率并纳入推荐。后者行之有效:515,000 行中有超过 80,000 行含 "Family with young children" 或 "Family with older children"。

平均分的"罗生门"

Total_Number_of_Reviews 与数据集实际条数对不上:评论最多的 Britannia International Hotel Canary Wharf 在数据集内有 4789 条,但字段值却是 9086;Average_Score 给出的 7.1 也与用数据内 Reviewer_Score 算出的 6.8 不同(次多评论酒店则几乎吻合)。由于这些外部数值无法用手中数据验证,稳妥做法是一律用数据集内评论自行计算平均分

实战:pandas 探索性数据分析

分析用的官方 Notebook 位于 4-Hotel-Reviews-1/notebook.ipynb,解答见 4-Hotel-Reviews-1/solution(其中 notebook.ipynb 为 Python 解答)。先加载数据(注意把 CSV 放入 data 文件夹并修正路径,本仓库建议下载至 6-NLP/data):

import pandas as pd
import time
print("Loading data file now, this could take a while depending on file size")
start = time.time()
df = pd.read_csv('../../data/Hotel_Reviews.csv')
end = time.time()
print("Loading took " + str(round(end - start, 2)) + " seconds")

随后按任务依次作答:打印 DataFrame 的 shape、对 Reviewer_Nationality 做频率统计、为前 10 大国籍找各自最常评论的酒店、统计每家酒店评论数、新增 Calc_Average_Score 列、比对两个平均分、统计 "No Negative"/"No Positive" 行数。关键代码与实测结果:

print("The shape of the data (rows, cols) is " + str(df.shape))
# The shape of the data (rows, cols) is (515738, 17)

nationality_freq = df["Reviewer_Nationality"].value_counts()
print("There are " + str(nationality_freq.size) + " different nationalities")
print(nationality_freq)
# There are 227 different nationalities
# United Kingdom 245246 / United States of America 35437 / Australia 21686 ...

groupby + transform 计算每家酒店在数据集内的实际评论数与平均分,并对比字段值:

def get_difference_review_avg(row):
  return row["Average_Score"] - row["Calc_Average_Score"]

df['Calc_Average_Score'] = round(df.groupby('Hotel_Name').Reviewer_Score.transform('mean'), 1)
df["Average_Score_Difference"] = df.apply(get_difference_review_avg, axis = 1)

结果发现绝大多数酒店两值差异极小,仅 1 家差异大于 1——因此可放心使用自行计算的均值。用 lambda 逐行统计空评论文本(耗时约 9.64 秒),更快的写法是用布尔求和的向量化操作(仅 0.19 秒):

no_negative_reviews = sum(df.Negative_Review == "No Negative")      # 127890
no_positive_reviews = sum(df["Positive_Review"] == "No Positive")   # 35946
both_no_reviews = sum((df.Negative_Review == "No Negative") & (df.Positive_Review == "No Positive"))  # 127

数据集中有 127 行(约 0.02%)"既不写正也不写负",仅留数字评分——量级小到不至于带偏模型,但提醒我们探索数据要先于建模。课程同时提示:用代码而非人眼阅读负面评论,既能避免被个别不当内容困扰,也正是 NLP"无需人力即可解读语义"的本质所在(练习见 4-Hotel-Reviews-1/assignment.md)。

第 5 课:NLTK 情感分析——从脏数据到可建模特征

第五课(6-NLP/5-Hotel-Reviews-2/README.md)完成"清洗 + 特征工程 + 情感打分 + 落盘"的收尾。

一步:数据再加工

基于上一步的 DataFrame 继续清洗:

  1. 删除 latlng;将 Hotel_Address 归约为"城市, 国家"六城(Amsterdam/Barcelona/London/Milan/Paris/Vienna),从而可用 groupby("Hotel_Address") 做国家粒度查询;
  2. 删除 Additional_Number_of_Scoring;用数据集内实际条数替换 Total_Number_of_Reviews;用自算均分替换 Average_Score
  3. 删除 Review_Total_Negative_Word_CountsReview_Total_Positive_Word_CountsReview_Datedays_since_review,保留评分与正负评原文;
  4. 删除 Total_Number_of_Reviews_Reviewer_Has_Given,保留 Reviewer_Nationality

二步:把 Tags 变成有用特征

处理顺序:先用 df.Tags.str.strip("[']") 去掉方括号、再用 str.replace(" ', '", ",", regex=False) 去掉引号,得到形如 Business trip, Solo traveler, Single Room, Stayed 5 nights, Submitted from a mobile device 的干净串。由于各字段数量与顺序不一(3~6 个不等),可按逗号拆成 6 个临时列再合并,最后 value_counts() 得到 2428 种唯一标签(最常见如 Leisure trip 417778、Couple 252294、Solo traveler 108545)。

结合业务目标(酒店推荐)筛选:保留出行类型与客群类型两类标签,忽略房型、提交设备、入住夜数("Group" 与 "Travellers with friends" 语义相近可合并),最终保留 8 个有效标签:Leisure trip、Couple、Solo traveler、Business trip、Group、Family with young children、Family with older children、With a pet。随后为每个标签生成 0/1 指示列:

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)

之后删去冗余列,另存为 Hotel_Reviews_Filtered.csv(对应官方过滤 Notebook 5-Hotel-Reviews-2/solution/1-notebook.ipynb)。

三步:去停用词加速情感分析

直接对 51 万行正/负评跑情感分析耗时约 12~14 分钟。去掉停用词(stop words)——那些不改变句子情感的常见英语词——是第一步:既能提速,又不损失精度(最长负面评论从 395 词降到 195 词)。用 set 缓存停用词以加速匹配:

from nltk.corpus import stopwords
cache = set(stopwords.words("english"))
def remove_stopwords(review):
    text = " ".join([word for word in review.split() if word not in cache])
    return text

df.Negative_Review = df.Negative_Review.apply(remove_stopwords)
df.Positive_Review = df.Positive_Review.apply(remove_stopwords)

在测试设备上,两列 51.5 万行的去停用词仅耗时约 3.3 秒。

四步:VADER 情感打分并落盘

NLTK 提供多个情感分析器可供替换对比,此处使用 VADER(Hutto & Gilbert, 2014,面向社交媒体文本的简约规则模型)。先加载过滤后的数据集并下载情感词典:

import nltk as nltk
from nltk.sentiment.vader import SentimentIntensityAnalyzer
nltk.download('vader_lexicon')

df = pd.read_csv('../../data/Hotel_Reviews_Filtered.csv')
vader_sentiment = SentimentIntensityAnalyzer()

def calc_sentiment(review):
    if review == "No Negative" or review == "No Positive":
        return 0
    return vader_sentiment.polarity_scores(review)["compound"]

随后对正、负两栏分别 apply 并新增 Negative_SentimentPositive_Sentiment 列(作者机器上约 120 秒)。验证情感与人工评分是否一致时需预期一定比例的"系统性错误"——例如高度反讽的 "Of course I LOVED sleeping in a room with no heating" 会被判为正。用 sort_values 查看两端样本后,重排列并保存最终文件:

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)

收尾:整条数据管线回顾

官方给出的完整执行顺序(对应 Notebook 见 5-Hotel-Reviews-2/solution/3-notebook.ipynb,需先运行 1-notebook.ipynb 生成中间文件):

  1. Hotel_Reviews.csv → 在第 4 课用探索 Notebook 分析(4-Hotel-Reviews-1/solution/notebook.ipynb);
  2. 由过滤 Notebook 清洗为 Hotel_Reviews_Filtered.csv
  3. 由情感分析 Notebook 处理为 Hotel_Reviews_NLP.csv
  4. Hotel_Reviews_NLP.csv 完成课后挑战(练习见 5-Hotel-Reviews-2/assignment.md)。

课后挑战提示:既然已为评论打好情感分,不妨调用本课程体系此前学过的策略(例如聚类)在情感维度上寻找模式——这正是从"NLP 特征工程"走向"推荐模型"的自然衔接。模块相关解题说明亦提供了 Julia 与 R 两种语言的对照实现(见 6-NLP/5-Hotel-Reviews-2/solution/Julia/R/ 目录),便于跨语言学习的读者对照。

写在最后:从"能聊"到"会评"

纵观整个模块,你会看到一条清晰的进阶曲线:第 1 课的 Marvin 只会随机应答(看似对话、实则无理解);第 2 课引入情感分档与名词短语抽取后,它开始"共情式"追问;第 3 课让你理解翻译与情感分析的 ML 原理及其在反讽面前的局限;第 4、5 两课则把这一切投放到 51 万条真实酒店评论上——从怀疑字段可信度、清洗脏标签,到用停用词与 VADER 规模化打分。课程反复强调的同一条方法论是:先彻底理解数据的脾性,再让算法替你阅读文本。这也是你在自己的 NLP 项目中能否交付可靠模型的分水岭。

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