简介:这是一份面向音乐情感分析、自然语言处理与数据挖掘研究者的网易云音乐情感分类数据集,整包约含39.5万条歌曲情感标签记录,每条记录由歌曲ID、歌单ID、情感标签三列组成,可用于构建情感分类模型、分析音乐推荐场景下的用户偏好,帮助解决音乐领域情感标注数据匮乏的问题。RAR压缩包内共8个文件,主体为jsonl与json格式的数据文件,且已按train/valid/test划分好训练集、验证集和测试集,另附README说明文档与git版本管理配置,整体约3.06MB,体量紧凑、便于下载和二次处理。目前已有479人下载学习。数据源于网易云音乐官网,覆盖大量真实歌曲与歌单语境,可直接用于监督学习与数据挖掘实践,免去自行爬取与清洗的繁琐环节。对初学者而言,也能作为学习jsonl数据格式、标签分布分析及数据预处理流程的优质练手数据,帮助快速搭建情感分析基线并深入理解音乐与情感的关联。
1. 网易云音乐情感分类数据集:一份能让你少走三个月弯路的真实中文语料
网易云音乐情感分类数据集(.rar 压缩包)是中文情感分类任务里少见的“真实场景”语料:抓取的是网易云音乐歌曲下的用户评论,每条评论附带情感标签,整体打包成一个压缩文件。它的价值不在“有没有一个好看的准确率”,而在评论本身是活的中文——有反讽、有玩梗、有歌词引用、有写一大段故事只为了说一句“爷青回”。适合拿来做情感分类练手、做舆情分析的前期验证,也适合拿来测试你刚调好的 BERT 模型到底扛不扛得住真实网络文本。这类数据集看起来结构简单,真正跑起来翻车点全在标签和清洗上,这也是我整理这份实操方案的原因。
2. 拆解 .rar 里的数据结构:字段、标签与建模假设
拿到这类压缩包,第一件事不是急着解压然后 read_csv 开始训练,而是先回答三个问题:里面是什么格式?标签是几分类?字段够不够支撑你要做的任务?这三个问题回答错了,后面所有实验都会在看不见的地方出错。
2.1 解压后的第一件事:编码检查、乱码修复与字段盘点
常见的打包格式是 CSV,偶尔是 JSON 或 Excel。CSV 踩坑最多,问题是编码。网易云评论里包含中文、emoji、繁体字和特殊符号,存成 CSV 时常用 UTF-8,但 Windows 用户用 Excel 打开就会变成乱码。我一般不在 Excel 里直接看,而是先在 Python 里读前几个字节判断编码,再交给 pandas 读取。
import chardet import pandas as pd with open('ncm_comments.csv', 'rb') as f: sample = f.read(1024) det = chardet.detect(sample) print(det) df = pd.read_csv('ncm_comments.csv', encoding=det['encoding']) print(df.shape) print(df.columns.tolist()) print(df.head(2))这段代码先取文件头部 1024 字节做编码探测。chardet 是通用编码检测库,能区分 UTF-8 和 GBK;detect 返回的字典里 encoding 字段就是推测结果。如果探测结果不理想,可以按顺序手动试 utf-8、gbk、utf-8-sig,哪个不乱码用哪个。这里有几个容易忽略的细节:一是带 BOM 的 UTF-8 文件必须用 utf-8-sig 读取,否则第一列列名会多出 \ufeff;二是 pandas 默认会把 CSV 第一行当列名,如果拿到的是没有表头的文件,要加参数 header=None 并自己指定列名。
字段盘点决定了后续特征工程的上限。这类数据集里通常会有这么几类字段:评论内容是最核心的文本字段,所有情感判断都从它来;情感标签是建模目标,但可能藏在单独的文件里;歌曲信息包括歌名、歌手、专辑,用来做分组和特征扩展;评论上下文包括评论时间、点赞数、回复数,用来做加权和消偏;用户昵称、等级这类字段一般不用,但如果压缩包里带了,先确认会不会涉及个人信息,该脱敏就脱敏。
还有一种常见情况:解压出来不是一个表,而是两个文件,一个叫“评论明细”,一个叫“情感标签”,两者通过评论 ID 关联。这时不要手动复制粘贴,直接在 pandas 里做内连接,保证标签和文本一一对应。
df_detail = pd.read_csv('comment_detail.csv', encoding='utf-8') df_label = pd.read_csv('label_result.csv', encoding='utf-8') df = df_detail.merge(df_label, on='comment_id', how='inner') print(df.shape)merge 的 on 参数指定关联键,how='inner' 表示只保留两边都匹配上的行。这一步能提前过滤掉“有文本没标签”或“有标签没文本”的孤儿数据,也避免后续训练时报 shape 不一致。
2.2 标签体系决定任务边界:二分类、三分类还是更细粒度
这组数据最需要花时间理解的是标签。你可能看到的标签列是 0/1/2,也可能是“负面/中性/正面”,甚至可能把“中性”拆成“一般”和“无情绪”两个类。在建模之前,先把标签真实含义搞清楚。如果文档缺失,就随机抽 50 条评论,人工比对标签是否合理,不要在标签含义没确认前就开训练。
label_map = { '负面': 0, '中性': 1, '正面': 2, } df['label_int'] = df['label'].map(label_map) print(df['label_int'].value_counts(normalize=True))map 的作用是把字符串标签转成模型能吃的整数。如果数据本身已经是整数,要重新映射成可读字符串,再检查 0、1、2 分别对应什么情绪倾向。value_counts(normalize=True) 输出每个类别的占比,这一步很重要:它能告诉你类别不平衡有多严重,以及后面评估到底该看哪个指标。
三分类是这类评论数据的默认选择,因为网易云评论区里有大量“还行”“一般”“路过”“打卡”等中性表达,硬塞进正面或负面都会污染标签。三分类模型在业务上也更好用:不只看到好评率,还能看到无感比例。如果非要二分类,建议把“中性”直接剔掉,而不是并进任一类别,否则训练出来的决策边界会很怪。
标签还有一个常见坑:有些版本的数据集标签来自自动打标,而不是人工标注。自动打标的标签噪声大,反讽、否定、谐音梗经常标错。判断方法很简单:把标签为“负面”的评论按点赞数排序,看前十名是不是真的负面向。如果发现大量误标,就不要全信标签,后续要么重标,要么只拿它当弱监督信号。
2.3 隐藏字段:评论时间、点赞数与歌曲元数据
“情感分类数据集”听起来只需要文本和标签,但网易云评论的特殊性在于,歌曲本身的情绪会传染评论情绪。一首伤感情歌下的评论大多是低沉的,一首洗脑神曲下的评论大多是活泼的。如果随机切分训练集和验证集,同一首歌的评论会同时出现在两边,模型学到的可能不是“情感”,而是“这首歌在不在训练集里”。
from sklearn.model_selection import GroupKFold gkf = GroupKFold(n_splits=5) for train_idx, val_idx in gkf.split( df[['comment']], df['label_int'], groups=df['song_id'] ): print(len(train_idx), len(val_idx))GroupKFold 按 groups 参数分组,保证同一个 song_id 的评论永远不会同时出现在训练集和验证集里。这里的 groups 可以用 song_id,也可以用“歌名+歌手”拼出来的组合字段。这是情感分类数据集最容易踩的泄漏点,后面第 5 章会单独展开。
评论时间同样值得利用。它可以做时间切分:前 80% 时间段的评论训练,后 20% 的评论验证,模拟模型上线后的真实行为。这样训练出来的模型不会偷看“未来”的评论,评估结果也更可信。点赞数则可以用来做加权采样:高赞评论往往情绪表达更极端,更能代表主流舆论,低赞评论更随意。如果训练时觉得文本质量太杂,可以按点赞数做分层采样,把高赞评论保留更多比重。
3. 用这个数据集跑通第一个情感分类模型:清洗、分词与基线
现在进入正题。先用最朴素的方式把模型跑起来,得到一个可复现的基线。这个基线不是为了刷分,是为了让你后续所有改进都有一个对照物。没有基线的调参都是空中楼阁,因为你根本不知道 BERT 带来的提升到底有多少。
3.1 清洗网易云评论:去重、去广告、处理表情和繁体字
网易云评论有几个特点会让清洗特别难受。第一是重复评论多,“好听”这种短评会被反复刷;第二是表情符号用方括号包裹,比如“[流泪]”“[大笑]”,直接分词会留下“流泪”“大笑”这种噪声词;第三是评论里经常粘贴歌词、话题标签和链接。清洗顺序有讲究:先去掉结构性噪声,再做归一化,最后去重。
import re import pandas as pd def clean_comment(s): if not isinstance(s, str): return '' s = s.lower() s = re.sub(r'https?://\S+', '', s) s = re.sub(r'#.*?#', '', s) s = re.sub(r'\[.*?\]', '', s) s = re.sub(r'\s+', ' ', s) return s.strip() df['clean_text'] = df['comment'].map(clean_comment) df = df.drop_duplicates(subset=['clean_text']) print(df.shape)四个正则分别处理链接、话题、方括号表情和多余空白。链接直接用 \S+ 匹配到空格为止;话题用 #.?# 非贪婪匹配,避免一口气删掉整段文本;网易云表情都是方括号包裹,用 [.?] 配掉。drop_duplicates 按清洗后的文本去重,避免重复短评破坏标签分布。注意:不要去掉中文标点,问号和感叹号在情感分类里是有区分度的特征;也不要做繁体转简体之外的过度归一化,否则会把反讽的语气也抹掉。
很多教程会让人顺手去停用词,我一般不删。原因有两个:一是“不”“太”“挺”这类词在情感分类里恰恰是核心特征;二是 TF-IDF 已经会给高频无语义词很低的权重,删了反而丢失信息。如果评论里混了繁体,用 opencc 转成简体再继续,这一步能提升词典覆盖率。
3.2 中文分词与 TF-IDF:把评论变成向量
中文情感分类离不开分词。这里用 jieba,原因在于它内置词典对网络用语有一定覆盖,还能自定义扩充。分词后把评论变成空格分隔的词序列,再交给 TF-IDF 向量化,这是词袋模型的标准路线。
import jieba def cut(s): return ' '.join(jieba.lcut(s)) df['cut_text'] = df['clean_text'].map(cut) print(df['cut_text'].head()) from sklearn.feature_extraction.text import TfidfVectorizer vectorizer = TfidfVectorizer( max_features=50000, ngram_range=(1, 2), min_df=3, ) X = vectorizer.fit_transform(df['cut_text']) print(X.shape)jieba.lcut 返回所有分词结果,再用空格拼回一个字符串,是因为 TfidfVectorizer 内部还按空格切词,这样切出来的一定是你要的词粒度。max_features=50000 限制了词典容量,防止向量矩阵爆内存,也去掉一批只在极少数评论里出现的生僻词。ngram_range=(1,2) 会把“不 好听”这种相邻组合也当成特征,能照顾一部分否定结构。min_df=3 表示某个词至少在 3 条评论里出现过才保留,过滤掉偶然噪声。这三组参数是最常调的,后面每个改动都要记录。
分词阶段有个隐藏问题:jieba 对网络梗支持一般。“爷青回”“开口跪”这些词大概率会被切开,但个别热词不影响大局,先把基线跑通更重要。如果发现分词错误已经严重干扰特征,再维护自定义词典,这块放到第 5 章专门讲。
3.3 训练与评估:逻辑回归跑出第一个可复现 baseline
分类器选逻辑回归。它在 TF-IDF 特征上是性价比最高的模型:训练快、可解释、调参简单,还能直接看每个词的权重方向。对情感分类来说,逻辑回归的系数比随机森林的意义清楚得多,你能直观看到“难听”权重为负、“好听”权重为正。
from sklearn.model_selection import train_test_split from sklearn.linear_model import LogisticRegression from sklearn.metrics import classification_report X_train, X_val, y_train, y_val = train_test_split( X, df['label_int'], test_size=0.2, stratify=df['label_int'], random_state=42, ) clf = LogisticRegression(max_iter=1000, C=1.0) clf.fit(X_train, y_train) print(clf.score(X_val, y_val)) print(classification_report(y_val, clf.predict(X_val)))train_test_split 的 stratify 参数按标签比例分层抽样,保证训练集和验证集类别分布一致。random_state=42 固定随机种子,让实验可复现。max_iter=1000 防止逻辑回归迭代不到收敛;C=1.0 是默认正则强度,C 越小正则越强,后面调参优先动它。classification_report 里重点看 macro avg,不要只看 accuracy,因为类别不平衡时会误导。
跑完这个基线,准确率大概在 0.7 到 0.8 之间。如果你远低于这个范围,先回查标签映射有没有错,再查清洗步骤是不是误删了否定词。把 vectorizer 的三组参数、random_state、C 值都记下来,这就是第一个 baseline。后续无论换成 BERT 还是加特征,都以这套结果作为对照。
4. 把基线往上推:用 BERT 微调,以及三个必调参数
如果你只是交作业,第 3 章就够了。但如果目标是真实可用的情感分类器,就要认真考虑换成 BERT。网易云评论里大量反讽、转折、歌词引用,词袋模型抓不到这些。BERT 可以,但代价是显存、训练时间和调参精力。这一章给出我常用的微调路线。
4.1 为什么 BERT 在网易云评论上比词袋模型强
TF-IDF 的缺陷是“词孤立”:它知道“好听”出现很多次,但不知道“好听”前面有个“不”。BERT 用上下文建模,能把“这歌真好听,可惜下架了”理解成正面偏遗憾,把“这歌真不好听”理解成负面。网易云评论区恰恰充满这种结构特征。
但不要神化 BERT。在短文本情感分类上,它比 TF-IDF + 逻辑回归通常只提升 3 到 8 个点。如果你的基线只有 0.7,先回去改清洗和数据划分,往往比换模型收益大。我一般顺序是:清洗、基线、再上 BERT。基线走不通时,不要先怀疑模型。
4.2 用 transformers 微调的最小代码骨架
我用 Hugging Face transformers 加 datasets 库,这是当前最主流的中文文本分类路线。模型选 hfl/chinese-macbert-base,它在中文短文本上比原生 bert-base-chinese 略好一点。显存不够就换 bert-base-chinese,代码不用改。
import torch from datasets import Dataset from transformers import ( AutoTokenizer, AutoModelForSequenceClassification, ) model_name = 'hfl/chinese-macbert-base' tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained( model_name, num_labels=3, ) def tokenize(batch): return tokenizer( batch['clean_text'], truncation=True, padding='max_length', max_length=64, ) dataset = Dataset.from_pandas(df[['clean_text', 'label_int']]) dataset = dataset.map(tokenize, batched=True) dataset.set_format('torch', columns=['input_ids', 'attention_mask', 'label'])tokenizer 先把评论切成 token 并映射成数字 ID。padding='max_length' 让同 batch 内序列等长,truncation=True 防止超长评论报错。max_length=64 意味着每条评论最多保留 64 个 token,超出直接截断。这里 64 不是拍脑袋,要按 4.3 的长度统计来定。set_format('torch') 把数据集转成 PyTorch 格式,方便后续 Trainer 读取。
4.3 三个必调参数:max_length、batch_size、学习率
三个参数决定微调成败。先看 max_length 怎么定。不要直接抄网上的 128 或 512,去统计清洗后文本的实际长度分布。
lengths = df['clean_text'].str.len() print(lengths.describe()) print(lengths.quantile(0.9)) print(lengths.quantile(0.95))quantile(0.95) 表示 95% 的评论都不超过这个长度。max_length 就取这个值,再往下一个刻度取整,比如 95 分位是 80,就设 96。设短了会截断长评,丢掉转折句的后半段;设长了会浪费显存,训练时间成倍增加。长度统计这一步很便宜,但绝大多数人都跳过。
batch_size 受显存限制。16 是常见入门卡的选择,显存不足就降到 8,同时学习率也该往下调。学习率一般建议 1e-5 到 3e-5,BERT 微调不能追求快,只能求稳。先跑一个 epoch,观察 loss 是否在逐步下降,如果震荡厉害就降学习率,如果降得太慢就适度提高。
from transformers import Trainer, TrainingArguments training_args = TrainingArguments( output_dir='./ncm_bert_ckpt', num_train_epochs=3, per_device_train_batch_size=16, per_device_eval_batch_size=64, learning_rate=2e-5, evaluation_strategy='epoch', save_strategy='epoch', load_best_model_at_end=True, metric_for_best_model='eval_loss', ) trainer = Trainer( model=model, args=training_args, train_dataset=dataset, eval_dataset=dataset, ) trainer.train()Trainer 把训练循环、日志、checkpoint 都封装好了。metric_for_best_model 用的 eval_loss 而不是 accuracy,因为类别不平衡时 loss 更能反映模型真实收敛情况。load_best_model_at_end=True 会在训练结束后自动回滚到验证集上最好的 checkpoint,这是防止过拟合的后悔药。eval_dataset 这里和图省事写成同一个 dataset,正式实验请分开,否则验证结果会虚高。
5. 网易云音乐情感分类的五个避坑记录:从乱码到数据泄露
下面五条踩坑记录,是这类文本情感分类任务里最常复现的问题。每条说清楚现象、原因、解决手段,按顺序对照排查,能省掉大半天时间。
5.1 解压后 CSV 全是乱码
现象:用 Excel 打开 .rar 解压出来的 CSV,中文全部变成“鏂囧瓨”或者“鍞嬪コ”。
原因:文件是 UTF-8 编码,Excel 在中文 Windows 下默认按 GBK 解码。这不是文件损坏,只是编码不匹配。
解决:不要手工改 Excel 编码,在 Python 里循环尝试三种常用编码即可。
for enc in ['utf-8', 'gbk', 'utf-8-sig']: try: df = pd.read_csv('ncm_comments.csv', encoding=enc) print(enc, df.shape) break except UnicodeDecodeError: continue这里按顺序尝试 utf-8、gbk、utf-8-sig,第一个能成功读入的就是正确编码。注意 utf-8-sig 要放在最后试,因为它本质是 UTF-8 带 BOM,对无 BOM 的纯 UTF-8 文件也能读,但顺序放后面能更快排除真正的 GBK 文件。
5.2 模型把“好听”判成负面,把“针不戳”判成负面
现象:验证集里“真好听”被预测成负面,网络梗“针不戳”完全不被识别。
原因:否定词、反讽和网络流行语是中文情感分类的三个老大难。“不好听”是显式否定,“好听……才怪”是反讽,“针不戳”是谐音梗,词表模型和微调不充分的 BERT 都可能翻车。
解决:最有效的是补充对抗样本。把“不好听”“才怪”“才怪呢”这类转折结构抽出来,人工纠正标签后混入训练集重训。对抗样本不用太多,几百条就能明显降低这类错误率。如果数据集本身没有这些样本,在测试集里手工构造 20 条,验证模型是否真的理解了否定结构。这步不做,上线后会被用户评论里的阴阳怪气打得措手不及。
5.3 训练集准确率 98%,验证集 62%
现象:训练过程中准确率一路涨到 0.98,但验证集分数只有 0.62,差距大到不正常。
原因:最典型的原因是用 train_test_split 随机切分,导致同一首歌的评论同时出现在训练集和验证集。模型记住的是“这首歌对应的情绪”,验证集里又碰到同一首歌,分数自然虚高。一旦换到没见过的歌,成绩立刻打回原形。
解决:按歌曲 ID 分组切分。用第 2 章的 GroupKFold,或者直接按时间切分:前 80% 时间段的评论做训练,后 20% 做验证。这种泄漏在情感分类数据集里非常隐蔽,因为准确率曲线看起来一切正常,只有换数据才会暴露。
5.4 标签分布严重不均衡,模型只会预测“正面”
现象:打印 classification_report,负面类 recall 是 0.0,但整体准确率依然很高。
原因:网易云评论整体情绪偏正向,负面样本可能只占 10% 以下。模型发现“全部预测正面”就能拿 80% 以上的准确率,于是躺平。
解决:训练时给少数类加权。逻辑回归里直接加 class_weight='balanced',BERT 训练里改用带权重的损失函数。评估指标改用 macro-F1,因为它在类别不平衡时不会造假。数据层面可以下采样一部分正面样本,把三类比例调整到 6:2:2 附近,训练出来的模型对负面才真正敏感。
5.5 jieba 把“爷青回”“开口跪”切成单字
现象:分词结果变成“爷/青/回”,TF-IDF 特征丢掉了整个梗的含义。
原因:jieba 默认词典收录的是标准书面语,网易云社区黑话和网络热词不在词表里。
解决:自定义词典,把这些词强制加入 jieba。
import jieba custom_words = ['爷青回', '开口跪', '单曲循环', '宝藏歌曲', '耳机分你一半'] for w in custom_words: jieba.add_word(w)jieba.add_word 会把词以较高优先级加入词典,保证分词时不再切开。如果你用的是 BERT,字级别分词本来就不存在这个问题,但自定义词典对 TF-IDF 基线仍然很重要。词典的维护要克制,只加反复出现的领域词,加太多长词反而会把句子切得七零八落。
6. 进阶技巧:用预测置信度反哺数据集,把几千条扩成几万条
模型训好后,最费时间的不是调参,而是获取更多标注数据。网易云评论的标注成本高,因为网络文本情感边界模糊。一个可行的主动学习做法是:用模型对未标注评论做预测,高置信度样本直接打伪标签,中等置信度样本送人工复核。
import numpy as np proba = clf.predict_proba(X_pool) confidence = proba.max(axis=1) pseudo_idx = np.where(confidence >= 0.95)[0] manual_idx = np.where((confidence >= 0.6) & (confidence < 0.95))[0]predict_proba 返回每个类别的概率,max(axis=1) 取最高概率作为置信度。置信度大于 0.95 的样本交给模型自动打标,0.6 到 0.95 之间的样本让标注员复核,低于 0.6 的先跳过。伪标签一次不要加入超过当前训练集 30% 的量,否则噪声会被模型一轮轮放大。
迭代两三轮后,模型在反讽、否定、网络梗上的错误率会逐步下降。我自己做这类迭代时吃过亏:第一轮回传没有过滤置信度,把 0.7 以上的全部塞进去,结果第二次迭代后模型反而变差了。后来改成每轮随机抽 100 条伪标签人工抽检,校验准确率低于 90% 就降低阈值或回退上一轮模型。这套主动学习流程不限定数据集,任何中文情感分类项目都能复用。希望帮到你。
本文还有配套的精品资源,点击获取