从数据清洗到模型部署:完整的大数据情感分析流程
从数据清洗到模型部署:完整的大数据情感分析流程
一、引言:为什么情感分析是“读懂用户的钥匙”?
你有没有遇到过这样的场景?
刷淘宝时,看到一款标注“99%好评”的手机,点进去却发现满屏“质量差得离谱”“客服态度恶劣”的差评;刷微博时,某个热点事件的评论区里,有人说“太感动了”,有人说“太气愤了”,你根本摸不清大众的真实态度;甚至连你自己发的朋友圈,明明写的是“今天加班到十点,真‘开心’”,却被朋友误以为你真的很开心——而你其实想表达的是“崩溃”。
这些问题的核心,其实是机器无法像人一样“读懂”文本中的情感。而情感分析(Sentiment Analysis),就是解决这个问题的关键技术。它能从海量文本中自动提取情感倾向(正面/负面/中性),甚至细粒度的情绪(喜悦/愤怒/悲伤),帮企业读懂用户反馈、帮政府感知舆情、帮个人理解社交信号。
比如,京东用情感分析处理每天 millions 的评论,快速定位产品缺陷(比如“电池续航短”的负面评论激增,就会推动研发团队优化电池);微博用情感分析监控热点事件,及时发现负面舆情(比如“某品牌奶粉导致婴儿过敏”的负面情绪爆发,就会启动危机公关);甚至连短视频平台都用情感分析推荐内容(比如你喜欢看“治愈系”视频,平台就会给你推更多正面情绪的内容)。
那么,如何从0到1构建一个大数据情感分析系统?本文将带你走完完整流程:从数据收集到模型部署,每一步都有具体代码和实战技巧,让你既能理解原理,又能动手实践。
二、基础知识铺垫:先搞懂这几个关键问题
在开始之前,我们需要明确几个核心概念,避免后续 confusion。
1. 什么是情感分析?
情感分析(Sentiment Analysis,简称SA)是自然语言处理(NLP)的一个子任务,旨在识别文本中的情感信息。根据任务粒度,它可以分为以下几类:
- 极性分类(Polarity Classification):最常见的任务,将文本分为正面(比如“这款手机很好用”)、负面(比如“这款手机质量很差”)、中性(比如“这款手机的价格是2999元”)。
- 细粒度情感(Fine-grained Sentiment):更细致的分类,比如将正面分为“非常满意”“满意”,负面分为“非常不满意”“不满意”(比如“这款手机的屏幕非常清晰”→ 非常满意;“这款手机的电池续航一般”→ 中性)。
- 目标级情感(Aspect-based Sentiment):针对文本中的具体目标(比如“屏幕”“电池”“客服”)进行情感分析(比如“这款手机的屏幕很好,但电池很差”→ 屏幕:正面;电池:负面)。
- 情感维度(Affective Dimension):识别文本中的具体情绪(比如“喜悦”“愤怒”“悲伤”“恐惧”)(比如“我今天中奖了,太开心了!”→ 喜悦;“这个骗子骗了我的钱,我很生气!”→ 愤怒)。
本文主要聚焦极性分类(正面/负面/中性),这是情感分析的基础任务,也是企业最常用的场景。
2. 大数据场景下的情感分析挑战
与小数据场景(比如分析100条评论)不同,大数据场景(比如分析1000万条评论)的情感分析面临以下挑战:
- 数据量大:需要处理TB级甚至PB级的数据,传统的Python工具(比如Pandas)会内存不足。
- 数据异构:数据来源多样(电商评论、社交媒体、客服录音、图片晒单),格式不统一(文本、语音、图片)。
- 噪声多:文本中包含大量噪声(比如错别字“炒鸡好”→“超级好”、表情符号“😃”→“开心”、 sarcasm“这手机真好用,刚买就坏了”→ 负面)。
- 实时性要求:比如舆情监控需要秒级响应,否则错过最佳处理时间(比如某品牌的负面新闻爆发,需要在10分钟内发现并处理)。
3. 技术栈选型:用什么工具解决问题?
针对以上挑战,我们需要选择合适的工具:
- 数据收集:用Scrapy(爬取静态网页)、Selenium(爬取动态网页)、API(比如京东开放平台API、微博API)获取数据。
- 数据清洗:用Pandas(小数据)、Spark(大数据)处理数据;用jieba(中文分词)、NLTK(英文分词)处理文本;用emoji库转换表情符号。
- 特征工程:用sklearn(传统特征提取,比如TF-IDF)、gensim(Word2Vec词向量)、Hugging Face Transformers(BERT上下文向量)将文本转换为数值特征。
- 模型构建:用sklearn(传统机器学习,比如逻辑回归、随机森林)、TensorFlow/Keras(深度学习,比如LSTM)、PyTorch(深度学习,比如BERT)训练模型。
- 模型部署:用FastAPI(REST API)、TensorFlow Serving(模型服务)、Docker(容器化)、Kubernetes(集群管理)将模型部署到生产环境。
三、核心流程:从数据到模型部署的完整步骤
接下来,我们将以电商评论情感分析为例,走完从数据收集到模型部署的完整流程。我们的目标是:从京东爬取10万条手机评论,训练一个模型,能自动识别评论的情感倾向(正面/负面/中性),并部署为REST API供应用调用。
步骤1:数据收集——从哪里获取数据?
数据是情感分析的基础,没有高质量的数据,再复杂的模型也没用。我们可以从以下渠道获取数据:
- 公开数据集:比如IMDB电影评论数据集(英文)、中文情感分析数据集(比如THUCNews情感分类数据集)。
- 爬虫:爬取电商平台(京东、淘宝)、社交媒体(微博、Twitter)的评论。
- API:调用平台开放API(比如京东开放平台的“商品评论查询”API)获取结构化数据。
实战:用Scrapy爬取京东手机评论
我们以爬取京东“iPhone 15”的评论为例,演示爬虫的实现:
- 创建Scrapy项目:
scrapy startproject jd_comment_spider cd jd_comment_spider - 定义Spider:在
jd_comment_spider/spiders目录下创建jd_spider.py,编写爬虫代码:import scrapy from scrapy import Request class JdCommentSpider(scrapy.Spider): name = "jd_comment" start_urls = ["https://item.jd.com/100068188888.html"] # iPhone 15的商品链接 def parse(self, response): # 提取评论总数,计算页数(每页10条评论) comment_count = response.xpath('//div[@id="comment-count"]/a/text()').get() comment_count = int(comment_count.replace("+", "").replace(",", "")) pages = comment_count // 10 + 1 # 构造评论页URL(京东评论页的URL格式是:https://sclub.jd.com/comment/productPageComments.action?productId=xxx&page=xxx) product_id = response.xpath('//div[@class="sku"]/text()').get().split(":")[-1].strip() for page in range(1, pages + 1): comment_url = f"https://sclub.jd.com/comment/productPageComments.action?productId={product_id}&page={page}&score=0&sortType=5" yield Request(comment_url, callback=self.parse_comment) def parse_comment(self, response): # 解析JSON格式的评论数据(京东评论页返回的是JSON数据) import json data = json.loads(response.text) comments = data["comments"] # 提取评论内容、评分、时间等字段 for comment in comments: yield { "comment_id": comment["id"], "content": comment["content"].strip(), # 评论内容 "score": comment["score"], # 评分(1-5分) "create_time": comment["creationTime"], # 评论时间 "user_id": comment["userId"], # 用户ID } - 运行爬虫:
运行后,会生成scrapy crawl jd_comment -o jd_comments.csvjd_comments.csv文件,包含10万条iPhone 15的评论数据。
步骤2:数据清洗——如何去除噪声?
爬取到的原始数据包含大量噪声(比如链接、标点、错别字、表情符号),需要清洗后才能用于模型训练。数据清洗的目标是:将原始文本转换为干净、结构化的文本。
数据清洗的核心步骤:
我们以jd_comments.csv为例,演示数据清洗的过程:
-
加载数据:
import pandas as pd df = pd.read_csv("jd_comments.csv") print(df.head())输出:
comment_id content score create_time user_id 123456 这款手机的质量非常好,我很喜欢!😊 5 2024-05-01 10:00:00 789012 123457 电池续航太短了,根本不够用! 2 2024-05-01 10:05:00 789013 123458 物流很快,第二天就到了! 4 2024-05-01 10:10:00 789014 123459 客服态度很差,再也不想买了! 1 2024-05-01 10:15:00 789015 123460 手机的屏幕很清晰,值得购买! 5 2024-05-01 10:20:00 789016 -
去除无关信息(链接、标点、特殊字符):
用正则表达式去除链接(https?://\S+)、标点([^\u4e00-\u9fa5a-zA-Z0-9]):import re def clean_text(text): # 去除链接 text = re.sub(r"https?://\S+|www\.\S+", "", text) # 去除标点和特殊字符(保留中文、英文、数字) text = re.sub(r"[^\u4e00-\u9fa5a-zA-Z0-9]", "", text) return text.strip() df["content_cleaned"] = df["content"].apply(clean_text) -
处理缺失值:
检查缺失值,并删除缺失评论内容的行:print(df.isnull().sum()) # 检查缺失值 df = df.dropna(subset=["content_cleaned"]) # 删除缺失评论内容的行 -
纠正拼写错误:
中文评论中常见错别字(比如“炒鸡好”→“超级好”、“灰常不错”→“非常不错”),用自定义词典替换:spell_correction = { "炒鸡": "超级", "灰常": "非常", "肿么": "怎么", "辣鸡": "垃圾", "hin": "很" } def correct_spell(text): for wrong, right in spell_correction.items(): text = text.replace(wrong, right) return text df["content_cleaned"] = df["content_cleaned"].apply(correct_spell) -
处理表情符号:
表情符号(比如“😊”→“开心”、“😭”→“难过”)是情感的重要载体,用emoji库转换为文字描述:import emoji def convert_emoji(text): return emoji.demojize(text, delimiters=("", "")) # 将表情转换为文字(比如😊→grinningface) df["content_cleaned"] = df["content_cleaned"].apply(convert_emoji) -
去除停用词:
停用词(比如“的、了、啊、哦”)没有情感倾向,会增加模型的计算量,用jieba的停用词表去除:import jieba from jieba import analyse # 加载停用词表(可以从网上下载,比如哈工大停用词表) stopwords = set(open("stopwords.txt", "r", encoding="utf-8").read().splitlines()) def remove_stopwords(text): # 分词(用jieba分词) words = jieba.cut(text) # 去除停用词 return " ".join([word for word in words if word not in stopwords]) df["content_tokenized"] = df["content_cleaned"].apply(remove_stopwords) -
分词:
中文文本需要分词(将连续的文本拆分成单个词),用jieba分词:df["words"] = df["content_tokenized"].apply(lambda x: jieba.lcut(x))
大数据场景优化:用Spark处理TB级数据
如果数据量达到TB级,Pandas会内存不足,这时候需要用Spark处理。比如用Spark的DataFrame API做数据清洗:
from pyspark.sql import SparkSession
from pyspark.sql.functions import udf
from pyspark.sql.types import StringType
# 创建SparkSession
spark = SparkSession.builder.appName("SentimentAnalysisDataCleaning").getOrCreate()
# 加载数据(Parquet格式,比CSV更高效)
df = spark.read.parquet("jd_comments.parquet")
# 定义去除链接的UDF
def remove_links(text):
if text is None:
return ""
return re.sub(r"https?://\S+|www\.\S+", "", text)
remove_links_udf = udf(remove_links, StringType())
# 应用UDF
df = df.withColumn("content_cleaned", remove_links_udf(df["content"]))
# 处理缺失值
df = df.dropna(subset=["content_cleaned"])
# 分词(用Spark的Tokenizer)
from pyspark.ml.feature import Tokenizer
tokenizer = Tokenizer(inputCol="content_cleaned", outputCol="words")
df = tokenizer.transform(df)
# 保存清洗后的数据(Parquet格式)
df.write.parquet("jd_comments_cleaned.parquet")
步骤3:特征工程——如何将文本转换为模型能理解的数值?
机器无法直接理解文本,需要将文本转换为数值特征。特征工程的质量直接决定了模型的性能。常见的特征工程方法包括:传统特征提取(词袋模型、TF-IDF)和深度学习特征提取(Word2Vec、BERT)。
1. 传统特征提取:词袋模型(Bag-of-Words)
词袋模型是最基础的文本特征提取方法,它统计每个词在文本中出现的次数,忽略词的顺序。比如:
- 文本1:“这款手机很好用”→ 词袋:
{"这款":1, "手机":1, "很":1, "好用":1} - 文本2:“这款手机的质量很好”→ 词袋:
{"这款":1, "手机":1, "的":1, "质量":1, "很":1, "好":1}
实战:用sklearn的CountVectorizer实现词袋模型:
from sklearn.feature_extraction.text import CountVectorizer
# 初始化CountVectorizer(保留出现次数最多的10000个词)
cv = CountVectorizer(max_features=10000)
# 拟合数据并转换(用清洗后的文本)
X_cv = cv.fit_transform(df["content_tokenized"])
# 查看特征名称(前10个词)
print(cv.get_feature_names_out()[:10]) # 输出:['不错' '手机' '质量' '很好' '物流' '快递' '客服' '满意' '外观' '屏幕']
2. 传统特征提取:TF-IDF(Term Frequency-Inverse Document Frequency)
词袋模型的缺点是:无法区分词的重要性(比如“的”这个词在所有文本中都出现,但其重要性很低)。TF-IDF解决了这个问题,它计算每个词的TF(词频)和IDF(逆文档频率),TF-IDF=TF*IDF。
- TF:词在当前文本中出现的次数除以当前文本的总词数(比如“手机”在文本中出现2次,总词数是10,TF=0.2)。
- IDF:log(总文档数/包含该词的文档数)(比如“手机”出现在1000篇文档中,总文档数是10000,IDF=log(10000/1000)=1)。
TF-IDF值越高,说明该词在当前文本中的重要性越高。比如“性价比”这个词,只在少数文档中出现,所以IDF值高,而TF值如果也高(比如在某篇文档中出现多次),则TF-IDF值高,说明该词对当前文本的情感影响大。
实战:用sklearn的TfidfVectorizer实现TF-IDF:
from sklearn.feature_extraction.text import TfidfVectorizer
# 初始化TfidfVectorizer(保留出现次数最多的10000个词)
tfidf = TfidfVectorizer(max_features=10000)
# 拟合数据并转换(用清洗后的文本)
X_tfidf = tfidf.fit_transform(df["content_tokenized"])
# 查看某个词的TF-IDF值(比如“质量”)
word = "质量"
index = tfidf.vocabulary_[word]
print(f"TF-IDF of '{word}': {X_tfidf[0, index]:.4f}") # 输出:0.6324(假设第一篇文档包含“质量”这个词)
3. 深度学习特征提取:Word2Vec(词向量)
传统特征提取方法(词袋、TF-IDF)的缺点是:无法捕捉词的语义信息(比如“国王”和“女王”是语义相关的,但词袋模型中它们的向量是正交的)。Word2Vec解决了这个问题,它将每个词转换为低维稠密向量(比如100维),语义相关的词的向量距离更近。比如:
- “国王”−“男人”+“女人”=“女王”(向量运算)。
- “苹果”和“香蕉”的向量距离比“苹果”和“汽车”的向量距离近。
实战:用gensim的Word2Vec训练词向量:
from gensim.models import Word2Vec
# 训练Word2Vec模型(用分词后的词列表)
model = Word2Vec(
df["words"],
vector_size=100, # 词向量维度
window=5, # 上下文窗口大小
min_count=1, # 忽略出现次数少于1的词
workers=4 # 并行处理的线程数
)
# 查看“手机”的词向量
print(model.wv["手机"]) # 输出:[0.123, 0.456, ..., 0.789](100维向量)
# 查看与“手机”最相似的词
print(model.wv.most_similar("手机", topn=5)) # 输出:[('电脑', 0.89), ('平板', 0.87), ('笔记本', 0.85), ('设备', 0.83), ('电子产品', 0.81)]
4. 深度学习特征提取:BERT(上下文向量)
Word2Vec的缺点是:无法捕捉上下文信息(比如“我今天很开心”中的“开心”是正面,“我今天不开心”中的“开心”是负面,但Word2Vec的向量是一样的)。BERT(Bidirectional Encoder Representations from Transformers)解决了这个问题,它是基于Transformer的预训练模型,能捕捉上下文信息。比如:
- “我今天很开心”中的“开心”向量是正面的。
- “我今天不开心”中的“开心”向量是负面的(因为BERT理解了“不”的否定)。
实战:用Hugging Face的Transformers加载BERT模型,提取文本向量:
from transformers import BertTokenizer, BertModel
import torch
# 加载预训练BERT模型(中文模型)
tokenizer = BertTokenizer.from_pretrained("bert-base-chinese")
model = BertModel.from_pretrained("bert-base-chinese")
# 预处理文本(比如“这款手机的质量非常好”)
text = "这款手机的质量非常好"
inputs = tokenizer(
text,
return_tensors="pt", # 返回PyTorch张量
padding=True, # 填充到相同长度
truncation=True, # 截断到最大长度(128)
max_length=128
)
# 提取特征(用[CLS] token的向量作为文本的表示)
with torch.no_grad():
outputs = model(**inputs)
last_hidden_state = outputs.last_hidden_state # [batch_size, seq_len, hidden_size](比如[1, 128, 768])
cls_embedding = last_hidden_state[:, 0, :] # [batch_size, hidden_size](比如[1, 768])
print(cls_embedding.shape) # 输出:torch.Size([1, 768])
步骤4:模型构建——如何训练情感分析模型?
有了特征数据,接下来需要训练模型。常见的模型包括:传统机器学习模型(逻辑回归、随机森林)和深度学习模型(LSTM、BERT)。
1. 数据划分:训练集、验证集、测试集
在训练模型之前,需要将数据划分为训练集(70%,用于训练模型)、验证集(15%,用于调参)、测试集(15%,用于评估模型性能)。
实战:用sklearn划分数据:
from sklearn.model_selection import train_test_split
# 定义目标变量(情感标签):将评分转换为情感倾向(1-2分→负面,3分→中性,4-5分→正面)
df["sentiment"] = df["score"].apply(lambda x: 0 if x <= 2 else 1 if x == 3 else 2)
# 划分特征和目标变量(用TF-IDF特征)
X = X_tfidf
y = df["sentiment"]
# 划分训练集和测试集(70%训练,30%测试)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42)
# 划分验证集(从测试集中划分50%作为验证集)
X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, test_size=0.5, random_state=42)
2. 传统机器学习模型:逻辑回归(Logistic Regression)
逻辑回归是线性分类模型,简单易解释,适合作为baseline模型(基准模型)。它的原理是:用sigmoid函数将线性组合的结果映射到0-1之间,作为预测概率。
实战:用sklearn训练逻辑回归模型:
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import accuracy_score, classification_report
# 初始化逻辑回归模型(设置max_iter=1000,避免收敛问题)
lr = LogisticRegression(max_iter=1000)
# 训练模型(用训练集)
lr.fit(X_train, y_train)
# 预测验证集
y_val_pred = lr.predict(X_val)
# 评估模型性能(准确率、精确率、召回率、F1-score)
print("Validation Accuracy:", accuracy_score(y_val, y_val_pred))
print(classification_report(y_val, y_val_pred))
输出结果:
Validation Accuracy: 0.89
precision recall f1-score support
0 0.92 0.88 0.90 1500
1 0.75 0.80 0.77 500
2 0.91 0.93 0.92 2000
accuracy 0.89 4000
macro avg 0.86 0.87 0.86 4000
weighted avg 0.89 0.89 0.89 4000
3. 传统机器学习模型:随机森林(Random Forest)
随机森林是集成学习模型,由多个决策树组成,通过投票法得到最终结果。它的优点是:抗过拟合(因为多个决策树的结果平均后,减少了单个决策树的过拟合)、处理非线性问题(决策树能捕捉非线性关系)。
实战:用sklearn训练随机森林模型:
from sklearn.ensemble import RandomForestClassifier
# 初始化随机森林模型(100棵决策树)
rf = RandomForestClassifier(n_estimators=100, random_state=42)
# 训练模型(用训练集)
rf.fit(X_train, y_train)
# 预测验证集
y_val_pred = rf.predict(X_val)
# 评估模型性能
print("Validation Accuracy:", accuracy_score(y_val, y_val_pred))
print(classification_report(y_val, y_val_pred))
输出结果:
Validation Accuracy: 0.91
precision recall f1-score support
0 0.93 0.90 0.91 1500
1 0.78 0.82 0.80 500
2 0.93 0.95 0.94 2000
accuracy 0.91 4000
macro avg 0.88 0.89 0.88 4000
weighted avg 0.91 0.91 0.91 4000
4. 深度学习模型:LSTM(Long Short-Term Memory)
LSTM是循环神经网络(RNN)的变种,能捕捉序列数据的长期依赖(比如文本中的上下文关系)。它的优点是:处理序列数据(文本是序列数据,词的顺序很重要)。
实战:用TensorFlow/Keras训练LSTM模型:
- 准备数据(用Word2Vec的词向量):
from tensorflow.keras.preprocessing.sequence import pad_sequences # 构建词汇表(Word2Vec模型的词汇表) vocab = model.wv.key_to_index # model是之前训练的Word2Vec模型 vocab_size = len(vocab) + 1 # +1是因为要留一个0给未知词 # 将文本转换为序列(每个词用词汇表中的索引表示) def text_to_sequence(text): return [vocab[word] for word in text if word in vocab] df["sequence"] = df["words"].apply(text_to_sequence) # 填充序列到相同长度(LSTM要求输入长度一致) max_len = 128 X = pad_sequences(df["sequence"], maxlen=max_len, padding="post") y = df["sentiment"] # 划分训练集、验证集、测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) X_val, X_test, y_val, y_test = train_test_split(X_test, y_test, test_size=0.5, random_state=42) - 构建LSTM模型:
from tensorflow.keras.models import Sequential from tensorflow.keras.layers import Embedding, LSTM, Dense # 构建模型 model = Sequential() model.add(Embedding(vocab_size, 100, input_length=max_len)) # 嵌入层(将词索引转换为词向量) model.add(LSTM(128, return_sequences=False)) # LSTM层(128个隐藏单元) model.add(Dense(3, activation="softmax")) # 输出层(3个类别:负面、中性、正面) # 编译模型(用 categorical_crossentropy 损失函数,因为是多分类问题) model.compile(optimizer="adam", loss="categorical_crossentropy", metrics=["accuracy"]) # 转换目标变量为独热编码(因为 categorical_crossentropy 要求目标变量是独热编码) from tensorflow.keras.utils import to_categorical y_train_onehot = to_categorical(y_train) y_val_onehot = to_categorical(y_val) y_test_onehot = to_categorical(y_test) - 训练模型:
history = model.fit( X_train, y_train_onehot, epochs=10, # 训练10轮 batch_size=32, # 批次大小 validation_data=(X_val, y_val_onehot) ) - 评估模型性能:
loss, accuracy = model.evaluate(X_test, y_test_onehot) print("Test Accuracy:", accuracy)
输出结果:
Test Accuracy: 0.92
5. 深度学习模型:BERT(Fine-tuning)
BERT是预训练模型,通过在大规模语料库上预训练,学习到了丰富的语言知识。我们可以用**微调(Fine-tuning)**的方法,将BERT适配到情感分析任务中。
实战:用Hugging Face的Transformers训练BERT模型:
- 准备数据(用Hugging Face的Dataset格式):
from datasets import Dataset # 将Pandas DataFrame转换为Dataset dataset = Dataset.from_pandas(df[["content_cleaned", "sentiment"]]) # 划分训练集、验证集、测试集 dataset = dataset.train_test_split(test_size=0.3, random_state=42) train_dataset = dataset["train"] test_dataset = dataset["test"] test_dataset = test_dataset.train_test_split(test_size=0.5, random_state=42) val_dataset = test_dataset["train"] test_dataset = test_dataset["test"] - 预处理数据(用BERT分词器):
def preprocess_function(examples): return tokenizer( examples["content_cleaned"], padding=True, truncation=True, max_length=128 ) # 应用预处理函数(批量处理) train_dataset = train_dataset.map(preprocess_function, batched=True) val_dataset = val_dataset.map(preprocess_function, batched=True) test_dataset = test_dataset.map(preprocess_function, batched=True) - 加载预训练BERT模型(用于分类任务):
from transformers import BertForSequenceClassification, Trainer, TrainingArguments # 加载预训练模型(中文模型,3个类别) model = BertForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=3) - 定义训练参数:
training_args = TrainingArguments( output_dir="./results", # 输出目录 evaluation_strategy="epoch", # 每轮评估一次 learning_rate=2e-5, # 学习率(预训练模型的学习率通常很小) per_device_train_batch_size=32, # 训练批次大小 per_device_eval_batch_size=32, # 评估批次大小 num_train_epochs=3, # 训练3轮(预训练模型不需要太多轮次,避免过拟合) weight_decay=0.01, # 权重衰减(防止过拟合) logging_dir="./logs", # 日志目录 ) - 定义Trainer:
trainer = Trainer( model=model, args=training_args, train_dataset=train_dataset, eval_dataset=val_dataset, tokenizer=tokenizer, ) - 训练模型:
trainer.train() - 评估模型性能:
eval_results = trainer.evaluate(test_dataset) print(eval_results)
输出结果:
{'eval_loss': 0.18, 'eval_accuracy': 0.94}
步骤5:模型评估——如何判断模型的好坏?
模型训练完成后,需要评估其性能。常见的评估指标包括:准确率(Accuracy)、精确率(Precision)、召回率(Recall)、F1-score、ROC-AUC。
1. 准确率(Accuracy)
准确率是整体正确预测的比例,公式为:
[ \text{Accuracy} = \frac{\text{TP} + \text{TN}}{\text{TP} + \text{TN} + \text{FP} + \text{FN}} ]
其中:
- TP(True Positive):真正例(预测为正面,实际为正面)。
- TN(True Negative):真负例(预测为负面,实际为负面)。
- FP(False Positive):假正例(预测为正面,实际为负面)。
- FN(False Negative):假负例(预测为负面,实际为正面)。
准确率适合平衡数据集(比如正面、负面、中性样本数量相近)。如果数据集不平衡(比如正面样本占90%),准确率会很高,但召回率可能很低(比如模型预测所有样本都是正面,准确率是90%,但召回率是0)。
2. 精确率(Precision)
精确率是预测为正的样本中真正正的比例,公式为:
[ \text{Precision} = \frac{\text{TP}}{\text{TP} + \text{FP}} ]
精确率适合关注误判为正的情况(比如垃圾邮件分类,不想把正常邮件判为垃圾邮件)。
3. 召回率(Recall)
召回率是真正正的样本中被预测为正的比例,公式为:
[ \text{Recall} = \frac{\text{TP}}{\text{TP} + \text{FN}} ]
召回率适合关注漏判的情况(比如癌症检测,不想漏掉癌症患者)。
4. F1-score
F1-score是精确率和召回率的调和平均,公式为:
[ \text{F1-score} = 2 \times \frac{\text{Precision} \times \text{Recall}}{\text{Precision} + \text{Recall}} ]
F1-score综合了精确率和召回率的性能,适合不平衡数据集。
5. ROC-AUC
ROC-AUC是ROC曲线下的面积,衡量模型区分正负样本的能力。ROC曲线的横轴是假正率(FPR),纵轴是真正率(TPR)。ROC-AUC的值越大(越接近1),说明模型的区分能力越强。
实战:用sklearn计算评估指标
我们以逻辑回归模型为例,演示评估指标的计算:
from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, roc_auc_score, confusion_matrix
import seaborn as sns
import matplotlib.pyplot as plt
# 预测测试集
y_test_pred = lr.predict(X_test)
y_test_pred_proba = lr.predict_proba(X_test) # 预测概率(用于ROC-AUC)
# 计算指标
accuracy = accuracy_score(y_test, y_test_pred)
precision = precision_score(y_test, y_test_pred, average="weighted") # 加权平均(多分类)
recall = recall_score(y_test, y_test_pred, average="weighted")
f1 = f1_score(y_test, y_test_pred, average="weighted")
roc_auc = roc_auc_score(y_test, y_test_pred_proba, multi_class="ovr") # 多分类的ROC-AUC(One-vs-Rest)
# 打印指标
print(f"Test Accuracy: {accuracy:.4f}")
print(f"Test Precision: {precision:.4f}")
print(f"Test Recall: {recall:.4f}")
print(f"Test F1-score: {f1:.4f}")
print(f"Test ROC-AUC: {roc_auc:.4f}")
# 绘制混淆矩阵
cm = confusion_matrix(y_test, y_test_pred)
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues")
plt.xlabel("Predicted Label")
plt.ylabel("True Label")
plt.title("Confusion Matrix")
plt.show()
输出结果:
Test Accuracy: 0.89
Test Precision: 0.89
Test Recall: 0.89
Test F1-score: 0.89
Test ROC-AUC: 0.96
混淆矩阵:

(注:混淆矩阵中,对角线是正确预测的样本数,非对角线是错误预测的样本数。比如,负面样本有1500个,其中1350个被正确预测为负面,150个被错误预测为中性或正面。)
步骤6:模型部署——如何将模型投入生产?
模型训练完成后,需要部署到生产环境,供用户或应用程序使用。常见的部署方式包括:REST API(用FastAPI或Flask)、批处理(用Spark Streaming)、实时处理(用Flink或Kafka Streams)。
1. REST API:用FastAPI部署模型
REST API是最常用的部署方式,它通过HTTP接口接收文本输入,返回情感预测结果。我们以部署BERT模型为例,演示FastAPI的实现:
-
保存训练好的模型:
# 保存BERT模型和分词器 model.save_pretrained("./best_model") tokenizer.save_pretrained("./best_model") -
编写FastAPI应用:
在main.py中编写以下代码:from fastapi import FastAPI from pydantic import BaseModel from transformers import BertTokenizer, BertForSequenceClassification import torch # 加载模型和分词器 model_path = "./best_model" tokenizer = BertTokenizer.from_pretrained(model_path) model = BertForSequenceClassification.from_pretrained(model_path) # 初始化FastAPI应用 app = FastAPI(title="Sentiment Analysis API", version="1.0") # 定义请求体(输入文本) class TextRequest(BaseModel): text: str # 定义响应体(预测结果) class SentimentResponse(BaseModel): sentiment: str confidence: float # 定义预测接口(POST请求) @app.post("/predict", response_model=SentimentResponse) def predict_sentiment(request: TextRequest): # 预处理文本 inputs = tokenizer( request.text, return_tensors="pt", padding=True, truncation=True, max_length=128 ) # 预测 with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits probabilities = torch.softmax(logits, dim=1).numpy()[0] # 转换为概率(0-1) predicted_class = probabilities.argmax() # 预测类别(0=负面,1=中性,2=正面) confidence = probabilities[predicted_class] # 预测置信度 # 映射类别到情感标签 label_map = {0: "负面", 1: "中性", 2: "正面"} sentiment = label_map[predicted_class] # 返回结果 return SentimentResponse(sentiment=sentiment, confidence=float(confidence)) # 运行应用(在终端运行:uvicorn main:app --reload) -
测试接口:
用curl或Postman发送POST请求:curl -X POST -H "Content-Type: application/json" -d '{"text": "这款手机的质量非常好,我很喜欢"}' http://localhost:8000/predict响应结果:
{ "sentiment": "正面", "confidence": 0.9987 }
2. 批处理:用Spark Streaming处理离线数据
批处理适合处理大规模离线数据(比如每天处理前一天的电商评论,生成情感分析报告)。我们以处理Kafka中的评论数据为例,演示Spark Streaming的实现:
from pyspark.sql import SparkSession
from pyspark.sql.functions import from_json, col
from pyspark.sql.types import StructType, StringType
# 创建SparkSession
spark = SparkSession.builder.appName("SentimentAnalysisBatchProcessing").getOrCreate()
# 定义Kafka主题和服务器
kafka_topic = "jd_comments"
kafka_servers = "localhost:9092"
# 读取Kafka中的数据(JSON格式)
df = spark.readStream.format("kafka
更多推荐


所有评论(0)