从数据清洗到模型部署:完整的大数据情感分析流程

一、引言:为什么情感分析是“读懂用户的钥匙”?

你有没有遇到过这样的场景?
刷淘宝时,看到一款标注“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”的评论为例,演示爬虫的实现:

  1. 创建Scrapy项目
    scrapy startproject jd_comment_spider
    cd jd_comment_spider
    
  2. 定义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
                }
    
  3. 运行爬虫
    scrapy crawl jd_comment -o jd_comments.csv
    
    运行后,会生成jd_comments.csv文件,包含10万条iPhone 15的评论数据。

步骤2:数据清洗——如何去除噪声?

爬取到的原始数据包含大量噪声(比如链接、标点、错别字、表情符号),需要清洗后才能用于模型训练。数据清洗的目标是:将原始文本转换为干净、结构化的文本

数据清洗的核心步骤:

我们以jd_comments.csv为例,演示数据清洗的过程:

  1. 加载数据

    import pandas as pd
    
    df = pd.read_csv("jd_comments.csv")
    print(df.head())
    

    输出:

    comment_idcontentscorecreate_timeuser_id
    123456这款手机的质量非常好,我很喜欢!😊52024-05-01 10:00:00789012
    123457电池续航太短了,根本不够用!22024-05-01 10:05:00789013
    123458物流很快,第二天就到了!42024-05-01 10:10:00789014
    123459客服态度很差,再也不想买了!12024-05-01 10:15:00789015
    123460手机的屏幕很清晰,值得购买!52024-05-01 10:20:00789016
  2. 去除无关信息(链接、标点、特殊字符):
    用正则表达式去除链接(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)
    
  3. 处理缺失值
    检查缺失值,并删除缺失评论内容的行:

    print(df.isnull().sum())  # 检查缺失值
    df = df.dropna(subset=["content_cleaned"])  # 删除缺失评论内容的行
    
  4. 纠正拼写错误
    中文评论中常见错别字(比如“炒鸡好”→“超级好”、“灰常不错”→“非常不错”),用自定义词典替换:

    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)
    
  5. 处理表情符号
    表情符号(比如“😊”→“开心”、“😭”→“难过”)是情感的重要载体,用emoji库转换为文字描述:

    import emoji
    
    def convert_emoji(text):
        return emoji.demojize(text, delimiters=("", ""))  # 将表情转换为文字(比如😊→grinningface)
    
    df["content_cleaned"] = df["content_cleaned"].apply(convert_emoji)
    
  6. 去除停用词
    停用词(比如“的、了、啊、哦”)没有情感倾向,会增加模型的计算量,用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)
    
  7. 分词
    中文文本需要分词(将连续的文本拆分成单个词),用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模型

  1. 准备数据(用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)
    
  2. 构建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)
    
  3. 训练模型
    history = model.fit(
        X_train, y_train_onehot,
        epochs=10,  # 训练10轮
        batch_size=32,  # 批次大小
        validation_data=(X_val, y_val_onehot)
    )
    
  4. 评估模型性能
    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模型

  1. 准备数据(用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"]
    
  2. 预处理数据(用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)
    
  3. 加载预训练BERT模型(用于分类任务):
    from transformers import BertForSequenceClassification, Trainer, TrainingArguments
    
    # 加载预训练模型(中文模型,3个类别)
    model = BertForSequenceClassification.from_pretrained("bert-base-chinese", num_labels=3)
    
  4. 定义训练参数
    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",  # 日志目录
    )
    
  5. 定义Trainer
    trainer = Trainer(
        model=model,
        args=training_args,
        train_dataset=train_dataset,
        eval_dataset=val_dataset,
        tokenizer=tokenizer,
    )
    
  6. 训练模型
    trainer.train()
    
  7. 评估模型性能
    eval_results = trainer.evaluate(test_dataset)
    print(eval_results)
    

输出结果

{'eval_loss': 0.18, 'eval_accuracy': 0.94}

步骤5:模型评估——如何判断模型的好坏?

模型训练完成后,需要评估其性能。常见的评估指标包括:准确率(Accuracy)精确率(Precision)召回率(Recall)F1-scoreROC-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的实现:

  1. 保存训练好的模型

    # 保存BERT模型和分词器
    model.save_pretrained("./best_model")
    tokenizer.save_pretrained("./best_model")
    
  2. 编写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)
    
  3. 测试接口
    用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
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐