ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

网易云音乐39.5万条情感标签数据:从清洗到情感分类模型实战

网易云音乐39.5万条情感标签数据:从清洗到情感分类模型实战 简介网易云音乐情感分类数据集面向自然语言处理、音乐推荐及情感分析领域的研究者与数据科学爱好者提供约39.5万条来自网易云音乐官方平台的歌曲情感标签数据。每条记录包含歌曲ID、歌单ID与情感标签三项核心信息可支撑情感分类模型训练、标签体系构建及音乐与情绪关联性挖掘等任务。压缩包共8个文件总大小仅3.06MB其中三份JSONL文件分别对应训练、验证与测试样本两份JSON文件提供情感标签映射及数据集元信息附带的Markdown文档便于快速上手。已有479人学习下载适合入门级到进阶级的数据分析实践者。通过该数据集用户可省去自行爬取与清洗的繁琐流程直接获得结构化音乐情感语料用于搭建分类模型、开展特征工程实验或探索歌单维度下的情感分布规律是音乐场景情感分析研究的基础数据资源。1. 网易云音乐情感分类数据集带标签的 39.5 万条数据到底能拿来干什么做文本情感分析的人手头最缺的往往不是模型而是干净、成规模、带明确标签的中文语料。这个网易云音乐情感分类数据集压缩包里装的是约 39.5 万条音乐情感标签数据每条记录由歌曲 ID、歌单 ID 和情感标签三个字段组成数据来源是网易云音乐官方网站。换句话说它帮你省掉了从歌单标题、评论里人工扒标签的脏活直接给你一份已经标记好情感类别的结构化数据。这套数据的典型用途有三个训练音乐场景下的情感分类模型、做标签体系与播放行为之间的关联分析、以及作为中文短文本情感分类的补充语料。和 IMDB、ChnSentiCorp 这类影评/商品评论数据集不同这里的文本主体是歌曲和歌单的语义标签类别分布更细噪声也更有“真实世界”的味道用来练文本分类的工程手感正好。适合的读者是准备做情感分析项目但缺数据的人、想用真实平台数据验证分类模型的同学以及需要一份带多类别标签的中文数据集做教学实验的从业者。2. 先摸清数据集的真实结构从 rar 解压到按行读取 JSONL很多人在这一步就翻车了。拿到压缩包第一反应是双击解压然后发现里面既有 .json 又有 .jsonl还有一个不小的 emo163.json一时不知道从哪个文件下手。我们先别急着写模型把文件结构和数据格式彻底搞清楚后面每一步才能对得上号。2.1 压缩包内部文件布局与各自的用途用解压工具打开后你会看到下面这些文件emo163.json情感标签的完整字典包含 163 个情感类别名称dataset_infos.json数据集的元信息描述一般记录版本、划分方式、字段说明songs-train.jsonl / songs-valid.jsonl / songs-test.jsonl按行存储的样本数据已经是训练集、验证集、测试集的划分README.md使用说明.gitignore / gitattributesGit 相关配置文件对普通使用者没有影响其中 .jsonl 格式经常有人不熟悉。它和 .json 最大的区别是每一行都是一个独立的、合法的 JSON 对象而不是一个大的 JSON 数组包着所有对象。这种格式的好处是支持逐行读取不用把整个文件一次性载入内存对大文件特别友好。39.5 万条数据说大不大但如果用 pandas 的pd.read_json(songs-train.jsonl)直接读部分版本会报错因为默认的 read_json 按行解析和按完整 JSON 解析的行为并不同。2.2 用 Python 正确加载 JSONL 并查看字段结构常见的做法是我下面给出的这种方式用 Python 内置的 json 模块逐行解析既不会把内存撑爆也能保证每条记录被完整解析import json def load_jsonl(file_path): records [] with open(file_path, r, encodingutf-8) as f: for line in f: line line.strip() if not line: continue obj json.loads(line) records.append(obj) return records train_data load_jsonl(songs-train.jsonl) print(总样本数:, len(train_data)) print(第一条记录:, train_data[0])这里line.strip()是为了过滤空行encodingutf-8是因为网易云歌单里存在大量中文和特殊符号缺了这个参数在 Windows 上经常直接报 UnicodeDecodeError。你可以看到每条记录应该包含 song_id、playlist_id、emotion_label 三个字段其中 emotion_label 的值就是对应歌曲的情感分类。2.3 验证集和测试集的比例与样本量级估算建议你加载完三个文件后先统计一下各自的样本量写一行代码的事for name in [songs-train.jsonl, songs-valid.jsonl, songs-test.jsonl]: data load_jsonl(name) print(f{name}: {len(data)} 条)这一步的意义在于确认数据划分是否符合你的预期。如果训练集占了绝大多数、验证集和测试集偏小那后面做模型评估时就要考虑使用分层采样或者 K 折交叉验证而不是直接信任默认划分。我拿到这份数据时先做了这件事发现 valid 和 test 的量级和 train 不是一个数量级后续就主动做了类别分层避免了某些少数类情感完全没进验证集的情况。3. 拿到原始数据之后先做标签分布统计和脏数据清洗很多教程会直接从这里跳到模型训练但我建议你先花十分钟做一次标签分布分析和样本去重。这份数据是从网易云音乐页面抓取或整理的真实平台的数据基本都带噪声重复歌单条目、空标签、标签缺失、字段类型不一致。不处理干净模型训练出来的结果就是“垃圾进垃圾出”。3.1 统计 163 个情感标签的分布情况先用 pandas 把数据转成 DataFrame然后按情感标签做分组统计import pandas as pd df pd.DataFrame(train_data) label_counts df[emotion_label].value_counts() print(情感类别总数:, df[emotion_label].nunique()) print(出现次数最多的前10个标签:) print(label_counts.head(10)) print(出现次数最少的前10个标签:) print(label_counts.tail(10))value_counts()返回的是每个标签从高到低的计数序列nunique()统计的是去重后的标签个数。对照 emo163.json 里定义的 163 个类别你就能立刻发现数据里有没有出现字典之外的“野生标签”以及是否有大量样本集中在少数几个高频情感上。如果长尾标签只出现几次后面训练时就要考虑类别权重或者干脆合并低频类别。3.2 样本去重与字段合法性校验接着做两层清洗print(去重前样本数:, len(df)) df_dedup df.drop_duplicates(subset[song_id, playlist_id, emotion_label]) print(去重后样本数:, len(df_dedup)) # 检查是否有缺失字段或空字符串 for col in [song_id, playlist_id, emotion_label]: missing df_dedup[col].isna().sum() (df_dedup[col] ).sum() print(f{col} 缺失/空值数量: {missing}) # 检查 song_id 是否是纯数字 non_numeric df_dedup[~df_dedup[song_id].astype(str).str.isdigit()] print(song_id 非纯数字的数量:, len(non_numeric))这里drop_duplicates的三个字段都参与去重判断意思是同一首歌在同一个歌单里被标记了同一种情感只保留一条。astype(str).str.isdigit()这行是为了验 ID 字段有没有混入网页抓取时的特殊字符实际抓来的数据经常在这个字段里带着换行符或不可见字符不校验的话后面做表连接时会莫名丢数据。3.3 为什么要做标签体系匹配检查emo163.json 定义了 163 个标签但训练数据里出现的情感标签并不一定和它完全一致。平台改版、人工标注偏差、爬取链路丢失都会导致标签集合漂移。你需要在清洗阶段就把“数据里出现但字典里没有”的标签找出来with open(emo163.json, r, encodingutf-8) as f: emo_dict json.load(f) valid_labels set(emo_dict.keys()) data_labels set(df_dedup[emotion_label].unique()) unknown_labels data_labels - valid_labels print(数据中出现但不在字典中的标签:, unknown_labels)这里如果unknown_labels是非空集合你有两个选择一是把这些样本直接删掉二是把它们归并到语义相近的已知标签。我一般倾向于先看数量如果未知标签只占千分之一以下就删除如果有一定规模就手动映射到近义标签比如“快乐”和“开心”映射到一个统一类别。这一步直接决定后续模型输出的标签空间有多干净。3.4 构建干净的训练 DataFrame 并保存中间结果清洗完成后的数据最好落盘一份方便后续反复使用而不需要重复加载原始 JSONLdf_clean df_dedup[df_dedup[emotion_label].isin(valid_labels)] print(清洗后样本数:, len(df_clean)) df_clean.to_csv(emotion_clean.csv, indexFalse, encodingutf-8-sig)utf-8-sig编码会写入 BOM 头Excel 打开 CSV 时中文不会乱码这一点比utf-8更省心。到这一步你已经拿到一份可以放心喂给模型的干净数据。4. 避坑指南这份数据集最常见的四个翻车现场数据本身不复杂但我在实际使用中还是踩了几个坑这里按“现象 → 原因 → 解决”的方式写出来希望能让你少走弯路。4.1 用 pandas 的 read_json 直接读 JSONL 全量字段报错现象pd.read_json(songs-train.jsonl)跑完发现字段对不上或者加载出来整个 DataFrame 只有一行所有数据挤在一个单元格里。原因read_json 默认按 JSON array 格式解析对于 JSONL 这种逐行对象格式只能通过linesTrue参数来切换解析模式。解决改成pd.read_json(songs-train.jsonl, linesTrue)。如果你用的是按行读取的加载方式就不存在这个问题。4.2 歌曲 ID 存在重复但情感标签不同现象同一首 song_id 出现在多条记录里而且 emotion_label 不一样导致后面统计标签频次时感觉数字虚高。原因同一首歌可能被多个歌单收藏不同歌单给它的情感定义不同。这本身体现了真实世界的标注分歧不是数据错误。解决分情况处理。如果做单标签分类建议保留该歌曲出现频次最高的那个标签如果做多标签分类就保留所有标签并转化为多标签格式。不要一股脑去重只留第一条那等于随机丢弃信息。4.3 验证集和测试集里低频标签直接消失现象训练集里有 130 个类别但验证集只有 40 个类别模型训练时某些类别完全没被验证过测试集上的 F1 分数低得离谱。原因默认的 train/valid/test 划分是随机切分没有做标签分层低频类别被随机切到训练集后就再也见不到了。解决用 sklearn 的StratifiedShuffleSplit重新做一次分层划分。但注意低频类别的样本数不足以支撑分层时要考虑把出现次数少于某个阈值的类别合并成 Other 类。4.4 RAR 包在 Linux 服务器上用 tar 命令解压失败现象下载到服务器后执行tar -xf 网易云音乐情感分类数据集.rar报错说不支持的格式或者解出来一堆乱码文件。原因RAR 不是 tar 的格式Linux 默认不自带 RAR 解压工具文件名又是中文编码处理不当就会出现乱码。解决用 unar 或者 7z 解压。unar 网易云音乐情感分类数据集.rar能自动识别编码解压出来文件名不会乱码如果没有 unar7z x 网易云音乐情感分类数据集.rar也可以但要注意 7z 对中文文件名的编码处理不如 unar 稳。解压完成后先ls -la检查文件是否完整再开始下一步处理。5. 从清洗后的数据到情感分类模型用 TF-IDF 加逻辑回归快速验证基线数据已经干净了现在可以进入建模阶段。这里我不建议一上来就上 BERT 或者微调大模型先用一个简单的文本分类管线跑出基线结果知道这份数据在任务上的难度上限在哪里再决定要不要上重模型。5.1 把标签文本构造成模型输入这份数据集的三列分别是 song_id、playlist_id、emotion_label并没有歌曲名称、歌手、歌词等文本。很多第一次用这份数据的人会问没有文本怎么喂给模型实际上情感标签这一列就是分类目标而输入特征可以来自 playlist_id 对应的歌单信息集合或者直接用歌单 ID 做类别特征。但更简单直接的做法是把情感分类任务当作“预测某首歌在某个歌单下的情感标签”输入特征用歌单 ID 与歌曲 ID 拼接后的 ID 组合。这种思路其实就是一个典型的推荐/分类场景。from sklearn.feature_extraction.text import TfidfVectorizer from sklearn.linear_model import LogisticRegression from sklearn.pipeline import make_pipeline # 构造输入文本song_id space playlist_id df_clean[input_text] df_clean[song_id].astype(str) df_clean[playlist_id].astype(str) X_text df_clean[input_text].values y df_clean[emotion_label].values这里的input_text就是把两个 ID 拼接成一个 token 序列。逻辑是如果一个歌单反复出现相同情感标签那歌单 ID 本身就是一个强特征同理歌曲 ID 也携带情感信息。TF-IDF 会把每个 ID 变成一个维度模型从这些维度里学习 ID 与情感标签的关联。5.2 训练逻辑回归模型并评估from sklearn.model_selection import train_test_split from sklearn.metrics import classification_report X_train, X_test, y_train, y_test train_test_split( X_text, y, test_size0.2, random_state42, stratifyy ) model make_pipeline( TfidfVectorizer(analyzerchar, ngram_range(1, 3)), LogisticRegression(max_iter1000, solverlbfgs, multi_classauto) ) model.fit(X_train, y_train) y_pred model.predict(X_test) print(classification_report(y_test, y_pred, zero_division0))这里我把analyzer设为char而不是word因为在 ID 拼接场景下词级别的 TF-IDF 只会让每个 ID 成为独立词项而字符级 n-gram 能捕捉到 ID 之间的数字组合模式对泛化更有帮助。ngram_range(1, 3)表示同时考虑 1 到 3 个字符的组合。max_iter1000是因为逻辑回归在特征维度高时默认 100 次迭代可能不收敛调大一点避免警告。zero_division0是为了防止某些类别完全没预测出来时计算 precision/recall 报错。5.3 换用 CountVectorizer 对比特征效果如果你发现 TF-IDF 的结果不理想可以快速换 CountVectorizer 对比一下from sklearn.feature_extraction.text import CountVectorizer model_count make_pipeline( CountVectorizer(analyzerchar, ngram_range(1, 3)), LogisticRegression(max_iter1000) ) model_count.fit(X_train, y_train) y_pred_count model_count.predict(X_test) print(classification_report(y_test, y_pred_count, zero_division0))TF-IDF 会对高频词降权但在 ID 这种离散场景里降权反而可能削弱强信号。CountVectorizer 只统计频次信息保留得更完整。两个模型跑完后对比 macro F1选高的那个作为基线。这一步操作成本非常低但能帮你判断特征表示的选择是否合适。5.4 检查低频类别的表现与失败样本基线模型跑完后重点看 classification_report 里的 macro avg 和每个类别的 F1 值。如果某些类别 recall 是 0说明模型完全没学会预测这个类别。这时候你需要单独把这些失败样本抽出来看import numpy as np misclassified np.where(y_pred ! y_test)[0] print(错误分类样本数:, len(misclassified)) for idx in misclassified[:5]: print(输入:, X_test[idx]) print(真实标签:, y_test[idx], 预测标签:, y_pred[idx]) print(---)这一步是为了定位错误模式是 ID 特征本身太稀疏还是某些标签在训练集里就几乎没有样本。如果是后者就需要回到第 3 章的清洗阶段重新构建标签体系。6. 更进一步从单标签到多标签分类的扩展思路如果你觉得单标签分类已经满足不了需求这份数据集其实还支持更进阶的玩法——多标签情感分类。网易云歌单的情感标签并不是严格互斥的一首歌既可以出现在“悲伤”歌单里也可以出现在“夜晚”歌单里。原始数据中同一首歌在不同歌单里被标注了不同情感这本身就是多标签信号的来源。6.1 构造多标签训练矩阵把清洗后的 DataFrame 按 song_id 分组把该歌曲出现的所有情感标签聚合成一个集合multi_label_df df_clean.groupby(song_id)[emotion_label].apply(list).reset_index() print(multi_label_df.head())这一步就得到了一首歌对应多个情感标签的数据。注意这里的聚合逻辑是用 song_id 作为主键因为同 ID 在不同歌单中的标签可以共存这比用(song_id, playlist_id)组合键更贴近“一首歌表达多种情绪”的直觉。但你需要先确认同一首歌的多个标签是否都来自同一首歌曲的不同歌单如果两个标签来自同一歌单那可能只是标注噪声。6.2 用 MultiLabelBinarizer 做标签编码from sklearn.preprocessing import MultiLabelBinarizer mlb MultiLabelBinarizer() y_multi mlb.fit_transform(multi_label_df[emotion_label]) print(多标签矩阵形状:, y_multi.shape) print(类别数量:, len(mlb.classes_))MultiLabelBinarizer 会把每个情感标签转成一个二值列一首歌在哪些情感标签上为 1就在对应列置 1。最终得到的矩阵形状是(歌曲数, 标签总数)这个标签总数大概率小于 163因为不是每个标签都被至少一首歌用过。如果矩阵的稀疏度特别高说明大多数歌曲只被标记了少数情感多标签分类的难度会更大。6.3 使用 OneVsRestClassifier 训练多标签模型多标签分类最常见的做法是把问题分解为“每个标签单独训练一个二分类器”from sklearn.multiclass import OneVsRestClassifier from sklearn.linear_model import LogisticRegression from sklearn.model_selection import train_test_split X_multi multi_label_df[song_id].astype(str).values X_train_m, X_test_m, y_train_m, y_test_m train_test_split( X_multi, y_multi, test_size0.2, random_state42, stratifyNone ) model_multi make_pipeline( CountVectorizer(analyzerchar, ngram_range(1, 3)), OneVsRestClassifier(LogisticRegression(max_iter1000)) ) model_multi.fit(X_train_m, y_train_m) y_pred_m model_multi.predict(X_test_m) from sklearn.metrics import precision_score, recall_score, f1_score print(精确率:, precision_score(y_test_m, y_pred_m, averagesamples)) print(召回率:, recall_score(y_test_m, y_pred_m, averagesamples)) print(F1:, f1_score(y_test_m, y_pred_m, averagesamples))这里的averagesamples是逐样本计算指标也就是说先对每首歌计算其预测标签集合与真实标签集合的重合度再对所有歌曲取平均这和单标签场景的 macro 计算方式完全不同。如果你用默认的 binary 模式会直接报错因为 y_multi 是多列二值矩阵。OneVsRestClassifier 的训练时间会比单模型长但这已经是多标签场景下最稳的基线了。6.4 通过混淆矩阵和标签共现分析进一步理解数据多标签模型跑完后除了看指标我还会做一次标签共现分析看看哪些情感经常同时出现在同一首歌上cooccur y_multi.T y_multi np.fill_diagonal(cooccur, 0) top_pairs np.unravel_index(np.argsort(cooccur, axisNone)[::-1][:10], cooccur.shape) for i, j in zip(*top_pairs): if i j: print(f{mlb.classes_[i]} {mlb.classes_[j]}: {cooccur[i, j]} 次)y_multi.T y_multi计算的是所有标签两两之间的共现次数矩阵对角线是每个标签自身的出现次数置 0 后只看标签对。这一步能帮你发现标签之间的强耦合关系比如“伤感”和“深夜”高度共现那你就能考虑是否把它们合并成更粗粒度的类别或者反过来使用层次分类——先分大类再在大类内细分具体情绪。这个思路放在工程里就是常见的“二级分类器级联”结构第一层判断情感大类第二层判断具体情绪标签准确率往往比 163 类一把梭高不少。做多标签实验时我踩过一次很深的坑MultiLabelBinarizer 在 fit 之后如果直接 pickle 保存下次加载时 classes_ 的顺序可能会变导致预测结果错位。从那以后我每次保存多标签模型时都强制把 mlb.classes_ 单独存一份 JSON加载模型时先核对标签顺序再推理。这个习惯帮我避免了好几次线上预测结果对不上号的尴尬希望也能帮到你。本文还有配套的精品资源点击获取
返回列表