☰
基于NLP与Django的电商评论情感分析系统设计与实现
2026/10/9 3:32:29 网站建设 项目流程

简介:以京东商城电商产品评论为数据源,面向Python毕设与情感分析项目,提供基于django的完整实现方案,覆盖网络爬虫、数据清洗、分词去停用词、Word2vec向量化、栈式自编码特征提取及LSTM情感建模,并对积极/消极评论分别开展LDA主题分析,最终借助ECharts以饼图、词云图和表格完成可视化。资源包共2001个文件,以js、svg、css等前端静态文件为主,辅以py源码、pyc编译文件、txt数据说明、sqlite3数据库及csv等,整体约45.19MB,目录结构完整,可直接运行或二次开发。已有690人学习使用,适合计算机相关专业学生、数据挖掘初学者及希望快速搭建评论分析系统的开发者。整套内容可帮助掌握电商评论采集到分析可视化的全链路流程,获得可复用的爬虫脚本、模型训练代码与可视化模板,还能学习LSTM和LDA在真实数据上的落地应用,节省从零搭建的时间。

1. 电商评论情感分析:为什么这个毕设值得认真做

电商产品评论情感分析,这几年在毕业设计里出现频率极高。它的业务场景很具体:电商平台每天收到海量评论,人工翻看效率太低,需要自动判断一条评论是正面、负面还是中性,再把结果做成图表给运营人员看。标题里这串技术栈看起来长,其实是一条清晰的 NLP 主线——jieba 负责把中文句子切成词,pandas 处理表格数据,scikit-learn 跑传统机器学习基线,gensim 训练词向量,Keras 和 TensorFlow 做深度学习分类,最后由 Django 把模型包装成一个可以输入评论、返回结果并展示可视化图表的 Web 系统。这个组合非常适合想在一套代码里同时覆盖数据处理、模型训练、Web 后端三类技能的人。下面按这条链路一步步落地,把每个环节的参数和坑位讲清楚。

2. 数据预处理与分词:从原始评论到可训练样本的完整链路

情感分析模型不会直接吃中文句子,必须先经过清洗、分词、向量化。这一章处理的对象是一份电商评论 CSV,假设字段包含content(评论文本)、rating(评分)、create_time(创建时间)。数据质量直接决定模型上限,所以这一章花的时间往往比训练还多。

2.1 评论数据清洗与标签构造

先看第一段代码,完成读入、抽列、打标签和文本清洗。

import pandas as pd import re # 读取电商评论数据,utf-8-sig 可以兼容 Excel 导出的带 BOM 文件 df = pd.read_csv('reviews_sample.csv', encoding='utf-8-sig') # 只保留需要的列,评分和评论内容必须同时存在 df = df[['content', 'rating', 'create_time']].copy() # 评分 1~2 记为负面 0,评分 3 记为中性 2,评分 4~5 记为正面 1 df['label'] = df['rating'].apply(lambda r: 0 if r <= 2 else (2 if r == 3 else 1)) # 删除评论内容为空的行 df = df.dropna(subset=['content']) def clean_text(text): if not isinstance(text, str): return '' # 去掉超链接、HTML 标签和多余空白 text = re.sub(r'http\S+', '', text) text = re.sub(r'<[^>]+>', '', text) text = re.sub(r'\s+', ' ', text) return text.strip() df['clean_content'] = df['content'].apply(clean_text) # 清洗后如果长度小于 2 个字符,基本是无效评论,直接过滤 df = df[df['clean_content'].str.len() >= 2] print(df['label'].value_counts())

逻辑说明:rating是现成的弱标签,按三分法切分最省事。re.sub(r'http\S+', '', text)去掉链接,因为电商评论里经常夹带“http://xxx 优惠券”这类噪声;re.sub(r'\s+', ' ', text)把多个空格合并成一个,避免分词时出现空 token。清洗完成后输出三个类别的数量,如果某一类太少,后面训练时要考虑过采样或者干脆合并成二分类。

这里有一个很多人容易忽略的点:评分和文本情绪并不总是一致。五星好评里可能出现“物流太慢了”,一星差评里也可能出现“商品不错但被快递摔坏了”。如果直接用评分当标签,模型学到的是“评分映射”,而不是“情绪映射”。更稳妥的做法是先抽 1000 条人工标注,再做一致性校验;没有人力的情况下,再用评分兜底。毕设场景里用评分当标签完全够用,但心里要明白这个局限。

2.2 jieba 分词:精确模式、自定义词典与停用词

分词阶段用 jieba,核心代码如下。

import jieba import re # 加载电商领域自定义词典,词语和词频之间用空格隔开 jieba.load_userdict('ecommerce_dict.txt') # 停用词表,注意“不、没、无”这类否定词不能进停用词表 stopwords = set() with open('stopwords.txt', 'r', encoding='utf-8') as f: for line in f: stopwords.add(line.strip()) # 否定词单独保留,否则“不值这个价”会丢掉核心语义 NEGATIVE_WORDS = {'不', '没', '无', '别', '勿'} def tokenize(text): # lcut 默认精确模式,适合情感分析这类需要完整语义的场景 words = jieba.lcut(text) kept = [] for w in words: w = w.strip() if not w: continue # 常见停用词可以丢,否定词必须留 if w in stopwords and w not in NEGATIVE_WORDS: continue # 只保留中文、英文、数字,标点和特殊符号直接丢弃 if re.fullmatch(r'[\u4e00-\u9fa5a-zA-Z0-9]+', w) is None: continue kept.append(w) return kept df['tokens'] = df['clean_content'].apply(tokenize)

逻辑说明:jieba.lcut(text)返回分词列表,默认精确模式,适合情感分析;全模式会切出大量重复片段,那是给搜索引擎用的,不能用在这里。jieba.load_userdict('ecommerce_dict.txt')的每行格式是“词语 词频 词性”,比如“包邮 10 n”“退货险 5 n”,如果不写词频就按默认值处理。电商评论里“客服态度”“退货退款”这类词组,默认词典容易切散,自定义词典能显著改善。

停用词表有两个极端要避开:一是网上随便找一个通用中文停用词表直接套用,结果把“不”“没”全过滤了,导致负面评论变成正面;二是停用词表太大,把“这个”“那种”全删掉,句子主语都没了。通用做法是先加载一份 200~300 词的常用停用词表,再把否定词和白名单词手动补回去。

做一次分词结果抽查:df['tokens'].head(20),重点看“便宜”“质量”“客服”这些高频词是否被完整切出来。如果“充电宝”被切成“充电”和“宝”,就需要往自定义词典里加“充电宝 10 n”。这一步的投入产出比非常高,很多模型效果差不是模型不行,而是分词把关键名词切碎了。

2.3 用 pandas 批量处理并保存中间结果

分词完成后,把结果落盘,方便后续多次实验复用。

# 把 token 列表拼回空格分隔的字符串,TF-IDF 和 Word2Vec 都直接吃这个格式 df['token_text'] = df['tokens'].apply(lambda x: ' '.join(x)) # 保存中间结果,Excel 无 BOM 打开 UTF-8 会乱码,所以写 utf-8-sig df[['content', 'label', 'token_text', 'create_time']].to_csv( 'processed_reviews.csv', index=False, encoding='utf-8-sig' )

逻辑说明:token_text是用空格把分词结果拼起来的字符串,后面 TfidfVectorizer 和 gensim 的 Word2Vec 都直接消费这个字段。保存为utf-8-sig是因为 Excel 默认用系统本地编码打开文件,无 BOM 的 UTF-8 CSV 在 Windows 上打开必乱,这个坑能卡新手一晚上。

如果评论量到了几十万条,apply(tokenize)这种逐行处理方式会很慢。常见做法是换用 pandarallel 或者把评论列表切分后用多进程处理;另一种思路是先df['content'].tolist()拿到列表,再并发分词,最后装回 DataFrame。注意 jieba 自带enable_parallel在 Windows 上不可用,Linux 下才有收益,别在本地环境白费功夫。

3. 特征表示与模型训练:scikit-learn 与 Keras/TensorFlow 的配合

这一章解决“切好的词怎么变成模型能算的数字”这个问题。这里会同时跑一个传统机器学习的基线和深度学习模型,两者互为参照。深度学习模型用 Keras + TensorFlow,gensim 在中间负责生成词向量。

3.1 TF-IDF + 逻辑回归:必须有的基线

先用 scikit-learn 搭一个基线,训练快、结果稳定,而且能给后续深度模型一个对照值。

from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report # 按标签分层抽样,类别不平衡时 stratify 能保证训练/测试分布一致 train_df, test_df = train_test_split( df, test_size=0.2, random_state=42, stratify=df['label'] ) # max_features 限制特征维度,ngram_range 保留相邻词组,min_df 过滤低频词 vec = TfidfVectorizer(max_features=8000, ngram_range=(1, 2), min_df=2) X_train = vec.fit_transform(train_df['token_text']) X_test = vec.transform(test_df['token_text']) baseline = LogisticRegression(C=1.0, max_iter=300) baseline.fit(X_train, train_df['label']) print(classification_report(test_df['label'], baseline.predict(X_test)))

逻辑说明:TF-IDF 做的事情是对每个词统计它在当前评论里的重要性,计算公式是词频乘逆文档频率。ngram_range=(1, 2)会额外生成“物流快”“态度差”这类双词特征,对情感表达很有用;代价是特征维度翻倍,所以用max_features=8000限制总量。min_df=2表示只出现一次的词直接丢掉,这些词对分类没有统计意义。

逻辑回归在这个项目里的角色是“下限”。情感分类本质上是文本特征到情感类别的线性映射,80% 的情况下逻辑回归已经能跑出不错的准确率。如果深度学习模型连这个基线都超不过,问题一定出在前面某个环节,不要急着调模型结构。

参数层面,C=1.0是默认值,数据量小的时候不需要刻意调。max_iter=300是给逻辑回归的收敛迭代上限,训练爆出“ConvergenceWarning”时,把它调到 1000 即可,或者换用saga求解器。

3.2 gensim 训练 Word2Vec 词向量

传统模型用词频特征,深度学习模型要用分布式表示。这一步用 gensim。

from gensim.models import Word2Vec # 分词后的句子列表,每个句子是一个词列表 sentences = [s.split() for s in train_df['token_text']] w2v_model = Word2Vec( sentences=sentences, vector_size=100, window=5, min_count=3, sg=1, epochs=10, workers=4 ) w2v_model.save('w2v_100d.model')

逻辑说明:vector_size=100是词向量的维度,太小表达不了语义,太大训练变慢且小数据集容易过拟合;100 是中文短文本分类里最常用的起点。window=5表示每个词只看前后各 5 个词作为上下文,短文本评论里窗口不用太大。min_count=3过滤掉出现次数少于 3 的词。sg=1表示用 Skip-gram,它在训练语料少的时候对低频词的表示更友好;语料量很大时,sg=0的 CBOW 训练更快。

gensim 在这个项目里的角色比较特殊:它不是情感分类模型,而是给深度学习模型提供“预训练词向量”。电商评论里“便宜”“实惠”这类词,Word2Vec 会把它们训练到语义相近的向量位置,Keras 的 Embedding 层用这些向量初始化后,模型一开始就站在一个比较高的起点上,而不是从随机数字学起。

训练完成后,可以做两个快速验证:w2v_model.wv.similarity('物流', '快递')应该得到一个较高的相似度;w2v_model.wv.most_similar('便宜')应该返回“实惠”“划算”“便宜”等近义词。如果这个结果乱七八糟,说明语料量太少或者min_count设得太高,需要回去看数据。

3.3 基于 Keras/TensorFlow 的 TextCNN 模型

词向量准备好了,进入深度学习部分。

import numpy as np from tensorflow.keras.models import Sequential from tensorflow.keras.layers import (Embedding, Conv1D, GlobalMaxPooling1D, Dense, Dropout) from tensorflow.keras.callbacks import EarlyStopping MAX_LEN = 80 VOCAB_SIZE = 30000 # 词表构建:<PAD> 占 0,<UNK> 占 1,其余词从 2 开始编号 word_index = {w: i + 2 for i, w in enumerate(w2v_model.wv.index_to_key)} word_index['<PAD>'] = 0 word_index['<UNK>'] = 1 def encode_tokens(tokens, max_len=MAX_LEN): # 超长截断,不足补 0 ids = [word_index.get(w, 1) for w in tokens[:max_len]] return ids + [0] * (max_len - len(ids)) # 把整个数据集转成 ID 序列 X = np.array([encode_tokens(tokens) for tokens in df['tokens']]) y = df['label'].values # 用 Word2Vec 训练好的向量初始化 Embedding 权重矩阵 emb_matrix = np.zeros((len(word_index), 100)) for word, idx in word_index.items(): if word in w2v_model.wv: emb_matrix[idx] = w2v_model.wv[word] def build_textcnn(): model = Sequential([ Embedding(len(word_index), 100, weights=[emb_matrix], trainable=True), Conv1D(128, 3, activation='relu', padding='same'), GlobalMaxPooling1D(), Dense(64, activation='relu'), Dropout(0.5), Dense(3, activation='softmax') ]) model.compile( optimizer='adam', loss='sparse_categorical_crossentropy', # 标签是整数,不需要 one-hot metrics=['accuracy'] ) return model model = build_textcnn() # 验证集 loss 连续 3 轮不下降就停,并恢复最好权重 early_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True) history = model.fit( X, y, validation_split=0.1, batch_size=64, epochs=20, callbacks=[early_stop] )

逻辑说明:Embedding 层的weights=[emb_matrix]把 gensim 训练好的词向量填进去,trainable=True表示训练过程中词向量还会继续微调,这是常见做法;如果语料很小,改成trainable=False可以防止过拟合,但大多数情况下开着更好。Conv1D 的filter=128表示卷积输出通道数,kernel_size=3表示每次看相邻 3 个词的窗口,padding='same'保证序列长度不变。整条卷积再接GlobalMaxPooling1D,它从每个通道里挑出最强烈的信号,这种结构能自动抓住“太慢了”“特别好”这类局部情感模式。

为什么选择 TextCNN 而不是 BiLSTM?因为电商评论是短文本,情感表达通常集中在局部词组合上,CNN 的局部 n-gram 捕捉能力非常契合。BiLSTM 擅长建模长距离依赖,但评论通常只有几十个字,优势发挥不出来,训练还慢。网上经常讨论 pytorch 和 tensorflow 的区别,放在这个项目里其实影响很小:两者都能搭出 TextCNN,关键差异在于国内 TensorFlow 的调试帖子多、老项目资料全,遇到问题搜索成本低。如果个人更习惯 PyTorch,拿它替换模型层完全不影响 Django 后端。

关于 2024 年前后的框架趋势,一个重要感受是:对于这种几万条样本的短文本分类任务,框架选型对结果的影响远小于数据清洗和参数设置。别在框架选择上反复纠结。

3.4 模型评估与选择

textCNN 训练完后,需要评估。这里我用一个表格来对比基线和深度模型在本任务中的通常表现维度。

评估维度TF-IDF + 逻辑回归TextCNN + Word2Vec
训练时间秒级分钟级
短文本整体准确率约 85%(作为基线参考)通常高 1~3 个百分点
对转折句的鲁棒性较弱较强
解释性可直接查看权重最高的词差,需额外分析

表格里的数据不是绝对的,但结论有代表性:如果数据量只有几千条,逻辑回归可能和 TextCNN 打成平手,这时没必要上深度模型。如果数据有几万条,Word2Vec 的语义信息开始生效,TextCNN 会稳定超过基线。

评估最怕只盯准确率。三分类问题里,如果负面只占 10%,模型全部预测正面也能有 85% 准确率。正确做法是同时看classification_report里的 precision、recall、f1-score,特别是负面类别的召回率——漏掉一条负面评论,对运营来说是实打实的失误。

最后做一次手工复核:从测试集里抽 20 条模型预测结果,人眼一条条看。重点看带转折的句子,比如“东西不错但是客服态度很差”。如果模型把它判成正面,说明训练集里这类转折句太少,需要补充数据,而不是继续调参。

4. Django 集成与可视化:从模型到可用的 Web 系统

模型训练完只是第一步,这个项目的落点是“Django Web 系统”。这一章解决两件事:怎么把模型安全地嵌进 Django,以及怎么把结果可视化成运营能直接看的图表。

4.1 Django 创建 app、项目结构和请求链路

先搭项目骨架。

django-admin startproject sentiment_project cd sentiment_project python manage.py startapp sentiment_app

项目里常见的目录分工是:sentiment_app存放视图、模型和模板,另外单独建一个ml_service包,把分词器、加载模型、预测函数全部放在里面。这样 Django 的 Web 逻辑和机器学习逻辑互不干扰,是毕设代码里很加分的结构。

在 Django 的 ORM 里,评论本身也是一张表。设计一个Review模型用来存用户提交的历史评论和预测结果。

# sentiment_app/models.py from django.db import models class Review(models.Model): content = models.TextField(verbose_name='评论内容') label = models.IntegerField(verbose_name='情感标签', default=2) confidence = models.FloatField(verbose_name='置信度', default=0.0) created_at = models.DateTimeField(auto_now_add=True) class Meta: ordering = ['-created_at']

这块要讲的不是建表语法,而是 ORM 在真实页面里的用法。比如运营后台要筛出所有负面评论,代码是Review.objects.filter(label=0);要删除低质量评论,先查询再删除:

# 查询所有负面评论 negative_reviews = Review.objects.filter(label=0) # 删除内容为空的低质量记录 deleted_count = Review.objects.filter(content__isnull=True).delete() print(f'清理了 {deleted_count[0]} 条记录')

delete()返回的是一个元组,deleted_count[0]才是删除的总行数。很多人直接把返回值当数字用导致 bug。

4.2 把模型封装进 Django:全局加载与预测接口

模型文件不能散落在视图函数里,否则每个请求都会重新加载一次模型,卡到怀疑人生。统一封装成一个服务模块。

# ml_service/predict.py import joblib import numpy as np from tensorflow.keras.models import load_model # 模块级缓存,整个进程只加载一次模型 _MODELS = {} def load_models(): if not _MODELS: _MODELS['tfidf'] = joblib.load('models/tfidf.pkl') _MODELS['logit'] = joblib.load('models/logit.pkl') _MODELS['textcnn'] = load_model('models/textcnn.h5', compile=False) return _MODELS def predict(text): models = load_models() # 这里复用第 2 章的清洗和分词函数 tokens = tokenize(text) token_text = ' '.join(tokens) # 先用传统模型算一个结果,作为兜底 tfidf_vec = models['tfidf'].transform([token_text]) lr_label = int(models['logit'].predict(tfidf_vec)[0]) return lr_label

逻辑说明:_MODELS字典是模块级缓存,第一次调用load_models()时把三个模型文件全部读进内存,后续请求直接命中。模型文件通常几十到几百 MB,如果放在视图函数里,每次请求都重新加载,Django 单进程都能被拖死。compile=False加载 Keras 模型可以跳过编译优化,推理场景不需要 optimizer,能省一点内存。

Django 侧视图可以写成接口形式,前端或者测试工具直接 POST 评论内容,返回 JSON 结果。

# sentiment_app/views.py import json from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.views.decorators.http import require_POST from ml_service.predict import predict @csrf_exempt @require_POST def analyze_comment(request): try: data = json.loads(request.body) text = data.get('content', '').strip() if not text: return JsonResponse({'error': '评论内容不能为空'}, status=400) label = predict(text) return JsonResponse({'label': label}) except Exception as exc: # 无论模型内部出什么错,接口都不能崩 return JsonResponse({'error': str(exc)}, status=500)

@csrf_exempt只是为了接口调试方便,正式环境不要用,应该换成 Token 认证。try/except包住整个预测过程,原因是模型推理偶尔会出现奇怪的运行时错误,比如某个未登录词触发了 NumPy 类型问题,接口必须保证大多数情况下都能返回一个可读的错误信息。

生产环境的常见做法是把预热放到 Django 的 AppConfig 里,避免第一个用户成为“实验品”。在apps.py的ready()方法里调用一次load_models(),应用启动时就把模型载入内存。

4.3 可视化:词云、情感分布图与时间趋势

Django 端可视化最实用的三件套是:词云、情感分布饼图、时间趋势折线图。词云用 wordcloud 库生成图片,情感分布和时间趋势用 ECharts 前端渲染。

词云部分最容易踩的坑在字体。

from wordcloud import WordCloud import base64 import io def build_wordcloud(tokens): text = ' '.join(tokens) # font_path 必须指定中文字体,否则生成的全是方块 wc = WordCloud( font_path='msyh.ttc', width=800, height=400, background_color='white', max_words=100 ).generate(text) buf = io.BytesIO() wc.to_image().save(buf, format='PNG') img_b64 = base64.b64encode(buf.getvalue()).decode() return f'data:image/png;base64,{img_b64}'

逻辑说明:WordCloud.generate()接收的是空格分隔的纯文本。中文词云必须指定font_path,Windows 上常见路径是C:/Windows/Fonts/msyh.ttc(微软雅黑);Linux 服务器上通常没有这个字体,需要先上传一个中文字体文件再引用。不指定字体的后果是整张词云图全是空心方块,这是最高频的翻车现场。

情感分布饼图用 ECharts 前端画,Django template 里只需要把三个数值传进去。

// sentiment_app/static/sentiment_app/js/chart.js const chart = echarts.init(document.getElementById('sentimentChart')); chart.setOption({ series: [{ type: 'pie', radius: ['40%', '70%'], data: [ { name: '正面', value: positiveCount }, { name: '中性', value: neutralCount }, { name: '负面', value: negativeCount } ] }] });

这段代码本身没有难度,参数逻辑在于radius: ['40%', '70%']控制的是环形图的内径和外径,内径 40% 外径 70% 会得到一个圆环,中间可以再放总数文本。实际页面里positiveCount需要从 Django 视图通过 JSON 传给模板,常见的错误是模板渲染时没有正确处理这个数字类型,导致 JS 里value拿到的是字符串,计算时能跑但图表显示异常。做一步parseInt()转类型最保险。

时间趋势图更简单:按created_at分组统计每日情感分布,后端返回一个 JSON 数组,前端用xAxis放日期、legend区分三类情感。这部分的主要工作量在 SQL 或者 pandas 分组上,画图本身没有技术含量。

5. 五个必踩的大坑:从环境安装到部署排查

下面这些坑都是这个技术栈里真实高频出现的问题,不是理论推演。每条按“现象 → 原因 → 解决”来写,方便对照排查。

5.1 TensorFlow 安装与导入版本冲突

现象:运行程序时直接报错AttributeError: module 'tensorflow' has no attribute 'placeholder',或者keras和tensorflow.keras混用导致模型结构不一致。

原因:项目里同时出现了独立 Keras 包和 TensorFlow 内置的tf.keras。TensorFlow 2.x 已经包含完整的 Keras API,如果环境中又pip install keras装了一份,两个 Keras 版本不一致,from keras...和from tensorflow.keras...混在一起就会产生诡异报错。

解决:统一从tensorflow.keras导入,并把独立的 Keras 卸载干净。安装 TensorFlow 时注意 CPU 版和 GPU 版的差异,GPU 版要先确认显卡驱动对应的 CUDA 版本,再装匹配的 TensorFlow;网上大量“driver version 不匹配”的报错都指向这一步。没有独显的机器直接装tensorflow-cpu就够毕设用了,别为了一个“GPU 加速”折腾一整天驱动。

5.2 CSV 读取乱码和中文路径问题

现象:pd.read_csv('reviews.csv')读出来中文全是乱码,或者文件能读但分词结果从文件回读后变成“锟斤拷”。

原因:CSV 文件本身是带 BOM 的 UTF-8,而read_csv默认按utf-8无 BOM 解析;也可能是文件路径含中文,Windows 默认编码与 Python 不一致。

解决:读入用encoding='utf-8-sig'基本能解决绝大多数乱码问题。如果还不行,用记事本打开文件另存为 UTF-8 编码。项目路径和文件名尽量避免中文字符,工程上这是省心做法。分词结果落盘时同样用utf-8-sig,否则 Excel 打开又是乱码。

5.3 词表与 Embedding 维度不匹配

现象:训练 Keras 模型时报错ValueError: Cannot assign value to shape ...,或者预测时出现IndexError: index out of range。

原因:用 gensim 训练词向量时,词表基于训练集;但构建文本 ID 序列时又用了整个数据集的词表,两边不一致。词表里明明只有 5000 个词,生成的 ID 序列里却出现了 6000 这个越界下标。

解决:在数据预处理阶段就锁定一套统一词表。我的做法是先用全量数据的分词结果构建word_index,再训练 Word2Vec,最后做 Embedding 矩阵。预测新评论时,遇到词表外的词统一映射到UNK,不要硬编新 id。

# 正确做法:所有词的 ID 归入 0=<PAD>、1=<UNK>,其余从 2 开始 ids = [word_index.get(w, 1) for w in tokens[:max_len]]

5.4 Django 静态文件 404

现象:本地开发时页面样式、JS 文件都正常,一部署或DEBUG=False就全部 404。

原因:Django 设计上不负责在生产环境提供静态文件服务,这是安全设计。部署阶段如果还指望 Django 直接返回 CSS 和 JS,得不到期望结果。

解决:本地调试保持DEBUG=True,Django 会自动处理静态文件。部署时先python manage.py collectstatic把所有静态文件收集到STATIC_ROOT目录,再由 Nginx 直接挂载这个目录。如果只是毕设演示,python manage.py runserver --insecure可以临时强制服务静态文件,但绝不能用在正式环境。

5.5 模型并发预测导致的内存爆炸

现象:Django 接收多个预测请求时,内存占用一路飙升,响应越来越慢,最后卡死。

原因:最常见的是把模型加载写在了视图函数里,每个请求都重新load_model一次;或者每个请求都调用一次load_models(),没有缓存机制。

解决:用模块级缓存,把模型加载封装成单例模式,确保整个进程只加载一次。并发量再高一点,Django 的同步视图会阻塞线程,常见做法是配合 Celery 做异步推理,把预测任务丢进队列。毕设项目没有这个必要,单例加载加同步视图完全够用。

6. 把模型导出并做成延迟加载:最后一个让系统不再“卡死”的细节

这一章讲一个提升实战体验的技巧:用 joblib 和 Keras 原生格式保存模型,并在 Django 里实现延迟加载。

# 导出脚本:训练完成后执行一次,生成三个模型文件 import joblib from tensorflow.keras.models import save_model joblib.dump(vec, 'models/tfidf.pkl') joblib.dump(baseline, 'models/logit.pkl') save_model(model, 'models/textcnn.h5')

模型导出后与训练代码分离,Django 端只需要加载文件,不再依赖 Jupyter 或训练脚本。这是项目从“能跑”变成“能用”的关键一步。

延迟加载的核心是懒加载模式。应用启动时不立即加载模型,而是在第一个预测请求到来时才初始化,后续请求全部命中缓存。配合AppConfig.ready()做预热,两种方式二选一,推荐后者,因为第一个请求的延迟对用户体验影响很大:

# apps.py:启动时预热模型 from django.apps import AppConfig class SentimentAppConfig(AppConfig): name = 'sentiment_app' def ready(self): # 避免在迁移等管理命令执行时也加载模型 import os if os.environ.get('RUN_MAIN'): from ml_service.predict import load_models load_models()

RUN_MAIN这个环境变量在runserver时才有值,这样模型预热不会干扰makemigrations这类操作。

另一个实用技巧是做预测结果缓存。电商评论的重复率很高,“物流很快”“好评”这类短句出现几十次,每次都走一遍模型推理很浪费。在 Django 的predict层套一层字典缓存,以清洗后的文本为 key,命中直接返回之前的预测结果:

prediction_cache = {} def predict_with_cache(text): if text in prediction_cache: return prediction_cache[text] result = predict(text) # 防止缓存无限膨胀,只保留最近 1000 条 if len(prediction_cache) > 1000: prediction_cache.clear() prediction_cache[text] = result return result

这些细节在毕设答辩里并不起眼,但真正部署上线时,模型加载策略和缓存设计比模型准确率更影响用户体验。我自己的血泪经验是:早期把模型加载放在视图里,自己开发时感觉不到,一旦部署到服务器,第一个请求等了整整 20 秒才返回,后面每来一个新用户都要陪着等一次。改成预热加缓存后,响应时间稳定在几百毫秒。希望你绕过这个坑,也希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询