news 2026/10/25 11:00:18

多模态情感分析模型搭建:Jupyter+Python毕设方案实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
多模态情感分析模型搭建:Jupyter+Python毕设方案实战

简介:一套基于Jupyter与Python实现的多模态情感分析模型完整项目,面向毕业设计、课程设计或项目开发场景,适合对自然语言处理、情感计算与多模态融合感兴趣的中高级学习者参考复现。项目源码已经过严格测试,可直接运行,并可在现有基础上扩展延伸。资源包共收录2000个文件,包含1997个txt格式的文本数据或预测结果、2个Markdown项目说明文档、1个Python主程序,压缩后总大小约202.13MB,整体目录结构清晰,便于按需检索;同时附有项目报告式说明,帮助快速了解模型设计、数据组织与推理输出。目前已有523人学习/下载。通过该资源可获得一份可运行的模型实现代码、项目文档思路以及一批测试文本与预测输出,有助于建立从数据处理、情感建模到结果验证的完整链路,也可作为课程论文或毕业设计的代码基础与改进起点。

1. 多模态情感分析模型:用Jupyter+Python从零搭一套可复现的毕设方案

用 Jupyter 搭一套基于 Python 的多模态情感分析模型,是毕业设计和课程设计里少有的兼顾算法深度、工程实现与答辩展示价值的方向。它要解决的事情很直观:给一段文本配上一段语音或一帧画面,模型判断说话人当下的情绪是正面、负面还是中性;在电商评论、客服对话、短视频内容审核这些场景里,单看文本常常误判,加上语气和面部表情才能真正读懂态度。难点集中在三处——不同模态各自的特征怎么提、特征格式不一致怎么对齐、最后用什么方式融合才不会让模型变成摆设。这篇文章按我实际做过的方案展开:数据管线、融合训练、踩坑记录和演示部署,新手能照着跑通,熟手可以直接拿走对齐细节和调参经验。

2. 数据准备:文本、音频、图像三路模态的特征提取管线

多模态情感分析的第一道坎不是模型,而是数据。三个模态来自不同通道,采样率、维度、语义粒度完全不一样,先把每一路收拾成固定形状的张量,后面训练才不会频繁报错。

2.1 文本模态:清洗中文短文本,选用 BERT 与 Longformer

中文情感语料大多是短文本,比如豆瓣影评、淘宝评价、客服对话。我的习惯是先做保守清洗:只去掉 URL、@用户、多余空格,不清除表情符号和网络新词,因为表情本身携带情感。下面这个清洗函数放在 Jupyter 第一个 cell 里,后面接编码和特征提取。

import re def clean_text(text: str) -> str: text = re.sub(r'https?://\S+', '', text) # 去掉网址 text = re.sub(r'@\w+', '', text) # 去掉 @用户 text = re.sub(r'\s+', ' ', text).strip() # 合并空白并去首尾空格 return text

清洗之后用预训练模型编码。中文场景我默认先试bert-base-chinese,它的词表对简体中文覆盖比较全,短文本上表现稳定。如果语料里有很多长评论,超过 256 字的情况占比高,可以换 Longformer 中文模型,它用稀疏注意力把序列长度上限从 512 拉长到 4096。但要留意:Longformer 显存占用明显更高,Jupyter 常驻内存环境下跑批量推理容易 OOM,不是长文本场景不建议盲目升级。

from transformers import BertTokenizer, BertModel tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') sample_text = "这个电影我看了两遍,还是觉得太让人失望了" text_vec = clean_text(sample_text) inputs = tokenizer(text_vec, max_length=128, truncation=True, padding='max_length', return_tensors='pt') bert = BertModel.from_pretrained('bert-base-chinese') with torch.no_grad(): outputs = bert(**inputs) text_feat = outputs.last_hidden_state[:, 0, :] # 取 [CLS] 向量,768 维

参数说明:max_length=128对短视频、客服短句足够;truncation=True会截断超长文本;padding='max_length'把同一批样本补到等长,方便组 batch。取last_hidden_state[:, 0, :]是取了每个样本的第一个 token 对应的向量,也就是 BERT 的[CLS]位,常用它表示整句话的语义。

首次运行会从 Hugging Face 下载权重。如果在虚拟机或校园网里下载速度很慢,可以先用自带下载工具把模型文件放到预训练模型缓存目录,from_pretrained检测到本地已有权重会直接加载,不再联网。这也是在 Jupyter 里排查“卡在某一次下载很久”的标准做法。

2.2 语音模态:MFCC 加滑动窗口,别一上来就上 OpenSMILE

语音情感识别最常见的特征是 MFCC,模拟人耳对频率的非线性感知。很多论文用 OpenSMILE 一次性提取几千维特征,训练效果不一定更好,而且参数太多像个黑匣子,答辩时很难讲清楚每一维代表什么。我更推荐用 librosa 自己抓 MFCC 和差分特征,特征量少但每一项都能解释。

import librosa import numpy as np wav_path = 'sample.wav' y, sr = librosa.load(wav_path, sr=16000) # 统一重采样到 16 kHz mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40, hop_length=256) delta = librosa.feature.delta(mfcc) # 一阶差分,体现动态变化 audio_feat = np.vstack([mfcc, delta]) # 40 维 MFCC + 40 维差分

sr=16000是为了把不同来源的音频统一采样率,训练时输入分布才稳定;n_mfcc=40取前 40 维倒谱系数,已经覆盖大部分音色信息;hop_length=256控制相邻帧间隔,帧移越小特征时间分辨率越高,计算量也越大。

如果语音不是整段输入,而是一段接一段流式判断情绪,可以在帧序列上做滑动窗口统计,每个窗口计算均值、方差、过零率,把窗口作为最小分析单元。这种滑动窗口滤波的思路在语音情感数据集里很常见,处理长音频比直接整段输入更稳。

win_length = 512 hop_length = 256 frames = librosa.util.frame(y, frame_length=win_length, hop_length=hop_length)

对于毕设级别的多模态项目,我通常不对齐到帧,而是把整段语音的 MFCC 特征按时间维求平均和标准差,得到 80 维定长向量。这样音频和文本、图像特征维度天然对齐,训练代码简单很多。副作用是丢失时序细节,但对三分类情感判断影响不大。

2.3 图像模态:OpenCV 抽帧,YOLOv5s 检测人脸,ResNet 提特征

图像模态在情感分析里一般指镜头中人物的面部表情。自己从头训练表情识别模型成本太高,常见做法是“检测 + 提特征”:先用目标检测网络把人脸框出来,再用 ImageNet 预训练图像分类网络提取特征。

import cv2 import torch cap = cv2.VideoCapture('sample.mp4') frames = [] while True: ret, frame = cap.read() if not ret: break frames.append(frame) cap.release() frames = frames[::30] # 每 30 帧取 1 帧,降低计算压力

OpenCV 读视频容易忽略的细节是摄像头采集的视频帧顺序和音频不一定对齐,后面统一对齐时需要注意。抽帧间隔选 30 是因为视频通常 25~30 帧每秒,每帧都检测人脸会明显拖慢预处理速度,抽帧后单段视频只剩几张关键帧,足够表情判断。

人脸检测我优先用 YOLOv5s,它是轻量化检测模型,CPU 上也能跑,精度对于人脸框提取足够。Jupyter 里通过torch.hub加载非常方便。

detector = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) face_crops = [] for f in frames: result = detector(f) # 检测结果 boxes = result.xyxy[0].numpy() # [x1, y1, x2, y2, conf, cls] for x1, y1, x2, y2, conf, cls in boxes: # 取置信度较高的人脸区域,避免把背景当人脸 if conf > 0.5: face = f[int(y1):int(y2), int(x1):int(x2)] face_crops.append(face)

conf > 0.5是过滤误检的标准阈值;如果画面里有多个路人,可以考虑只取面积最大的人脸框,通常对应主要说话人。检测完成后进入提特征环节:

from torchvision import models, transforms resnet = models.resnet18(pretrained=True) resnet.eval() preprocess = transforms.Compose([ transforms.ToPILImage(), transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]) ]) with torch.no_grad(): img_t = preprocess(face).unsqueeze(0) img_feat = resnet(img_t).squeeze().numpy() # 512 维

Resize((224, 224))是 ResNet 的标准输入尺寸;Normalize 的均值和标准差是 ImageNet 数据集的统计量,不能随便改,否则预训练特征分布会偏。这里没有把torchvision.models.resnet18换成更大的 ResNet50,原因是提取的是中间特征而不是最终分类概率,ResNet18 的 512 维输出在融合层已经够用,训练和推理都更快。

2.4 模态对齐:特征维度和语义粒度怎么统一

三个模态处理完,形状分别是文本 768 维、音频 80 维、图像 512 维,数量级和维度都不一样。训练全连接层时,高维的文本特征很容易主导梯度。我一般先做标准化,再拼接,让每个模态的贡献相对均衡。

模态提取方式输出维度输入单元
文本BERT / Longformer768每句话
语音librosa MFCC + 差分,全局均值方差80每段音频
图像YOLOv5s 检测 + ResNet18 特征512每张人脸

对齐逻辑按“一个样本一条记录”处理:一条文本配一段语音和一帧人脸表情。语音若超过 10 秒,取前 10 秒截断,直接整段算特征;图像有多张人脸时,取面积最大的那个;文本缺失时补零向量,语音缺失时补零向量,并在样本里记录掩码。这样不管原始数据缺了哪一路,模型输入形状始终不变。

对齐做好之后,数据管线就闭环了。下一步是决定这三路特征怎么融合。

3. 模型融合与训练:把 BERT、MFCC 和 ResNet 特征拼起来的最小实现

融合是整个多模态情感分析项目的核心环节。融合策略选不好,三路特征堆在一起可能比单模态效果还差。

3.1 融合策略:拼接、晚期融合与注意力融合怎么选

常见融合方案有三种。早期融合是先把文本、语音、图像特征拼接成一个大向量,再进入多层感知机分类。实现最简单,训练稳定,但如果某个模态缺失,输入维度就对不上。晚期融合是三个模态各自训练一个分类器,再把三路预测概率加权平均;优点是鲁棒,某个模态坏了整体还能用,缺点是模态间的关联信息没有被模型学到。注意力融合是让模型学习模态之间的相互影响权重,效果上限最高,但对数据量、显卡资源和调参经验的要求也最高。

我的建议是:课程设计用拼接融合,毕业设计想冲高分可以加一层简单自注意力。拼接融合不是“土办法”,它相当于让全连接层自己学每个模态该分配多少权重,数据量不大时反而比复杂机制更不容易过拟合。

import torch import torch.nn as nn class MultiModalNet(nn.Module): def __init__(self, text_dim=768, audio_dim=80, image_dim=512, hidden_dim=256, num_classes=3): super().__init__() self.text_net = nn.Sequential( nn.Linear(text_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(0.3) ) self.audio_net = nn.Sequential( nn.Linear(audio_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(0.3) ) self.image_net = nn.Sequential( nn.Linear(image_dim, hidden_dim), nn.BatchNorm1d(hidden_dim), nn.ReLU(), nn.Dropout(0.3) ) self.classifier = nn.Sequential( nn.Linear(hidden_dim * 3, 128), nn.ReLU(), nn.Dropout(0.3), nn.Linear(128, num_classes) ) def forward(self, text, audio, image): x_t = self.text_net(text) x_a = self.audio_net(audio) x_v = self.image_net(image) x = torch.cat([x_t, x_a, x_v], dim=1) # 256*3 = 768 return self.classifier(x)

初始化模型时,文本、音频、图像三路各接一个全连接层,把它们映射到相同的 256 维空间,再进行拼接。BatchNorm1d在这里很关键,因为文本特征和音频特征的数值范围差异很大,批归一化能把每路特征拉回到近似尺度,防止某一模态天然压过其他模态。Dropout(0.3)是防止融合层过拟合,多模态数据量通常不大,不加 Dropout 特别容易把训练集背下来。

3.2 训练代码:Dataset、训练循环与模型保存

先把预处理好的特征组织成 Dataset。这里的前置条件是特征已经提前算好并保存在内存列表或.npy文件里,训练时不重复走 BERT、YOLO 这些重模型,速度会快很多。

from torch.utils.data import Dataset class MultimodalDataset(Dataset): def __init__(self, records): self.records = records # records: [{"text": np.ndarray(768,), "audio": np.ndarray(80,), # "image": np.ndarray(512,), "label": int}] def __len__(self): return len(self.records) def __getitem__(self, idx): r = self.records[idx] return (torch.tensor(r["text"], dtype=torch.float32), torch.tensor(r["audio"], dtype=torch.float32), torch.tensor(r["image"], dtype=torch.float32), torch.tensor(r["label"], dtype=torch.long))

训练循环用标准 PyTorch 写法即可。损失函数用交叉熵,优化器用 AdamW,学习率设置在1e-4到2e-4之间。文本特征来自 BERT,按理说微调 BERT 需要更小的学习率,但这里 BERT 只做特征提取器,权重不参与训练,所以融合网络可以用稍大的学习率。

from torch.utils.data import DataLoader model = MultiModalNet() criterion = nn.CrossEntropyLoss() optimizer = torch.optim.AdamW(model.parameters(), lr=2e-4, weight_decay=1e-4) scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=10, gamma=0.5) dataloader = DataLoader(dataset, batch_size=32, shuffle=True) EPOCHS = 40 for epoch in range(EPOCHS): model.train() total_loss = 0 for x_t, x_a, x_v, y in dataloader: out = model(x_t, x_a, x_v) loss = criterion(out, y) optimizer.zero_grad() loss.backward() optimizer.step() total_loss += loss.item() scheduler.step() print(f"epoch {epoch+1:02d}/{EPOCHS}, loss: {total_loss/len(dataloader):.4f}") torch.save(model.state_dict(), "multimodal_emotion.pt")

batch_size=32是三路特征都是定长向量时比较稳妥的选择,特征维度不大,一般显卡都不会爆显存。StepLR每 10 轮把学习率乘 0.5,让训练后期收敛更稳。训练结束后把 state_dict 保存下来,之后演示阶段直接加载。

提示:如果 epoch 没跑完就想中断,建议只保存state_dict而不是整个模型对象,方便跨环境加载,也避免 Jupyter 里加载模型时顺带运行一堆无关代码。

3.3 训练监控与关键超参:loss 掉了不代表模型没翻车

多模态模型比单模态更容易出现“看起来在收敛,实际某一路特征已经不起作用”的问题。训练时我只保存最终模型还不够,还要监控每一路的单独表现。

常用做法是训练结束后做单模态消融测试:只喂文本特征跑测试集,记录准确率;再只喂音频特征,再只喂图像特征。如果某一路的单模态准确率都不到 50%,说明那一支的网络没学好,融合模型大概率把那条链路当噪声处理了。

超参数推荐范围说明
hidden_dim128 ~ 512三路映射到统一维度,越大表达力越强,数据少时容易过拟合
Dropout0.2 ~ 0.4融合层必备,文本和视觉特征维度较高,尤其需要
学习率1e-4 ~ 2e-4BERT 特征不参与微调时,这个范围收敛稳定
batch_size16 ~ 32特征向量很小,主要受限于数据量而不是显存
EPOCHS20 ~ 40配合早停使用,防止过拟合

类别不平衡是多模态情感分析最常见的隐藏问题。公开数据集里“正面”样本往往远多于“负面”,直接训练出来的模型会偏向多数类。解决方法是给损失函数加类别权重,权重取样本数的反比:

class_weights = torch.tensor([0.8, 0.5, 1.5]) # 按各类样本比例手动调整 criterion = nn.CrossEntropyLoss(weight=class_weights)

加了权重以后,少数类的分类误差会被放大,模型才愿意去学它。调权重后训练轮数可以适当增加,因为少数类学起来更慢。

4. 常见问题排查:Jupyter 环境、路径权限、显存与模态错位

多模态项目的坑有一大半不在算法公式里,而在工程环境里。这一章记录我在 Jupyter 里做多模态情感分析时踩过的五个典型问题,全部按现象、原因、解决的顺序整理。

4.1 Jupyter 启动后报错 PermissionError: [Errno 13] Permission Denied

这是 Jupyter notebook 里最容易遇到的错误之一。现象是读取音频文件或保存中间特征时,Python 突然抛出PermissionError: [errno 13] permission denied,代码逻辑看起来完全没问题。

原因有三类。一是当前工作目录不是你以为的目录,相对路径指向了不存在的文件夹;二是 Linux 服务器或共享目录下当前用户没有写权限;三是 Windows 上文件被杀毒软件或其他进程占用,无法写入。排查思路很简单:先打印当前工作目录和文件绝对路径,再用绝对路径操作。

import os print("工作目录:", os.getcwd()) data_dir = os.path.abspath("data/audio") if not os.path.exists(data_dir): os.makedirs(data_dir, exist_ok=True)

os.makedirs(..., exist_ok=True)能一次性创建多层目录,存在也不会报错。Jupyter 里经常出现“上一轮把文件写进了 A 目录,这一轮换到 B 目录就找不到”的情况,用绝对路径能彻底避开位置错位。

4.2 Jupyter notebook 默认保存路径漂移,模型文件找不到

现象:训练好的multimodal_emotion.pt明明保存了,重启 Jupyter 后却找不到,或者画出来的准确率曲线没出现在项目文件夹里。

原因:Jupyter 启动后相对路径基于启动时的目录,如果你从别的目录创建了 notebook,文件的保存位置和你心里想的不是同一个地方。解决方法是显式把当前目录切到项目根目录:

%cd /home/user/multimodal_emotion import os print(os.getcwd())

在 notebook 最前面加一格%cd,把工作目录固定下来,后续所有相对路径都可靠。这也是多模态项目里最容易被忽略的“后悔药”——早点统一路径,后期就不用到处找文件。

4.3 模型训练时报维度错误,或者 loss 突然变 None

现象:torch.cat报Sizes of tensors must match,说明三路特征维度没有对齐。另一个现象是 loss 训练途中变成nan。

原因:维度错误通常是语音特征没做全局均值方差,音频长度不一致导致特征维度不是 80;nan则通常是学习率过大,或者输入特征里有空数组。解决维度问题,在三路特征进入模型前统一打印 shape:

print("text", text_feat.shape) # torch.Size([1, 768]) print("audio", audio_feat.shape) # 期望 torch.Size([1, 80]) print("image", img_feat.shape) # 期望 torch.Size([1, 512])

解决nan问题,先把学习率降到1e-4,再看特征里是否出现了 NaN。用np.isnan(audio_feat).sum()检查一下,MFCC 在静音片段可能出现 log 0 导致无穷值,处理方法是给谱图加一个小常数1e-6。

import numpy as np mfcc = librosa.feature.mfcc(y=y, sr=sr, n_mfcc=40, hop_length=256) mfcc = np.nan_to_num(mfcc, nan=0.0, posinf=0.0, neginf=0.0)

np.nan_to_num能把 NaN 和正负无穷统一替换成 0,虽然简单粗暴,但至少不会让训练直接崩掉。

4.4 loss 不降反升,训练翻车

现象:前 5 轮 loss 下降,第 6 轮开始上升,之后持续震荡。或者从一开始就卡在 1.0 左右下不去。

原因:多模态融合层的输入尺度不一致是头号嫌疑。BERT 的 768 维特征数值分布在 -2 到 2 之间,ResNet 特征经过池化后数值分布更集中,MFCC 动辄几十上百。三路拼接后,数值大的模态主导梯度,其他模态学不动。

解决:在三路全连接层里都加BatchNorm1d,训练前对特征做一次标准化,把每路特征映射到近似尺度。我通常保存特征时就先做标准化:

from sklearn.preprocessing import StandardScaler scaler = StandardScaler() audio_feat = scaler.fit_transform(audio_feat) # 对训练集全量特征拟合 with open('audio_scaler.pkl', 'wb') as f: pickle.dump(scaler, f)

注意StandardScaler必须在训练集上拟合,不能对每个样本单独fit_transform,否则验证集和测试集会被泄漏到训练统计里。保存这个 scaler,推理阶段加载后对新增样本做同样的变换。

4.5 展示推理时提示“模型繁忙”,特征抽取比预测还慢

现象:本地训练正常,把模型放到网页演示后,第一次点击按钮要等 5 秒以上,并发请求时后端直接返回“模型繁忙,请稍后再试”。

原因:模型权重、BERT tokenizer、YOLO 检测器在每次请求时都被重复加载,甚至特征抽取和模型推理混在同一个请求线程里。解决方式是把所有重对象提前加载到全局变量,推理接口只做前向计算。

# 全局加载一次 model = MultiModalNet() model.load_state_dict(torch.load("multimodal_emotion.pt", map_location="cpu")) model.eval() detector = torch.hub.load('ultralytics/yolov5', 'yolov5s', pretrained=True) tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') bert = BertModel.from_pretrained('bert-base-chinese')

map_location="cpu"让模型在无 GPU 环境也能加载,演示阶段用 CPU 推理已经完全够用,三路特征拼接后只有 768 维输入,计算量非常小。真正的耗时在 BERT 和 YOLO 的特征抽取,所以这两个模型必须提前就位。

5. 让它变成能演示的毕业设计:模型保存、交互界面与验收清单

训练好的模型只是项目的一半,答辩现场更看重“能不能现场演示、结果可不可复现”。这一步我把模型封装成独立接口,做一个简单的交互页面。

用 Gradio 搭演示界面是最快的方案,输入一条文本、一段音频、一张图片,输出三个情感类别的概率:

import gradio as gr def predict(text, audio, image): # 特征抽取这里省略,实际复用 2.1~2.3 节的函数 text_f = extract_text_feature(text) audio_f = extract_audio_feature(audio) image_f = extract_image_feature(image) with torch.no_grad(): probs = torch.softmax(model(text_f, audio_f, image_f), dim=1) return {"positive": probs[0][0].item(), "neutral": probs[0][1].item(), "negative": probs[0][2].item()} demo = gr.Interface( fn=predict, inputs=[gr.Textbox(label="文本"), gr.Audio(label="语音", type="filepath"), gr.Image(label="人脸", type="filepath")], outputs=gr.Label(num_top_classes=3) ) demo.launch()

gr.Audio(type="filepath")和gr.Image(type="filepath")会把文件路径传给后端函数,特征抽取函数直接用路径读取。Gradio 依赖安装一次即可,Jupyter 里调用launch()后会打印一个本地地址,浏览器打开就是演示页面。

答辩前的验收我做四件事:第一,验证数据集划分没有泄漏,训练集、验证集、测试集按 8:1:1 切分,切分前先对样本去重;第二,单模态消融实验,确认融合模型比效果最好的单模态高至少 3 个点;第三,把特征抽取脚本从 notebook 里抽出来,单独保存成feature_extract.py,notebook 只负责训练和可视化;第四,用一个全新样本跑通从文本、音频、图片到预测结果的完整链路,确认没有依赖任何训练过程中产生的中间变量。

我做这类多模态项目最大的教训,就是前期图省事把所有代码堆在 Jupyter 单元格里,答辩前环境一变就翻车。后来强制自己把特征抽取、模型定义、训练循环拆成三个.py文件,notebook 只保留数据预览和结果展示,复现时直接跑脚本就行。习惯这个流程后,多模态项目的效率比之前提高了一倍不止。如果你也在做类似的毕业设计或课程设计,建议从第一天就按这个结构组织代码,后面会省下大量时间。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/25 10:49:55

LangChain架构本质:RUNNABLE协议与LCEL组合范式

1. 这不是“框架说明书”,而是你第一次真正看懂LangChain的起点如果你刚点开LangChain官网,被满屏的Chain、Agent、Retriever、Tool、Callback、Memory、LLM这些词砸得头晕,翻了几页文档发现全是API签名和类型定义,最后默默关掉页…

作者头像 李华
网站建设 2026/10/25 10:28:55

MariaDB 中 LAG 函数不支持第三参数——与 MySQL 的差异及改写

同一个统计用电量的查询在 MySQL 上一直正常,服务商把数据库从 MySQL 换成 MariaDB 之后立刻报语法错误。MySQL 8.0 与 MariaDB 的窗口函数实现并不同源,方言细节上有一批真实差异。 问题现象 在 MySQL 上可以正常执行的查询: SELECTseason,CASE WHEN season="2008/…

作者头像 李华
网站建设 2026/10/25 10:28:34

C++ graphics.h/graphics.lib 绘图与双缓冲实战

简介:这份压缩包面向学习 C 图形绘制的初学者、课程实验者以及需要补全 BGI 图形库环境的开发者,提供 graphics.h 头文件与 graphics.lib 库文件的配套组合,用于解决编译链接阶段缺少图形接口声明和预编译实现的问题,并适配常见的…

作者头像 李华
网站建设 2026/10/25 10:16:19

计算机视觉实训软件:免硬件免编程,万元级支持300人大班教学

计算机视觉这门课,最尴尬的从来不是理论讲不明白,而是学生一动手就卡在环境上。我带过几届实训,也帮兄弟院校做过课程方案,见过太多“卷积神经网络原理讲得头头是道,一到跑代码就集体报错”的场面。今天聊的这套计算机…

作者头像 李华
网站建设 2026/10/25 10:13:03

单通道脑电信号睡眠分期:轻量部署与临床鲁棒性实践

简介:本资源是一项基于单通道脑电信号(Fpz-Cz)的自动睡眠分期研究实现,面向深度学习初学者与神经科学交叉领域研究者,聚焦时序生理信号分类这一典型任务。项目以Sleep-EDF公开数据集SC子集(153条整晚记录&a…

作者头像 李华
网站建设 2026/10/25 10:06:02

Django智能旅游推荐系统全栈毕设项目深度拆解

简介:这是一份基于Python的智能旅游推荐系统毕业设计完整项目,面向计算机相关专业学生在毕业设计、课程设计或期末大作业中快速落地实践。项目基于Django框架构建,前端以HTML、CSS、Vue、JavaScript为主,后端由Python实现&#xf…

作者头像 李华