资讯动态

音乐情绪聚类可视化系统:从歌词语义到音频特征的闭环分析

发布时间:2026/10/18 12:48:59 来源:尧图企业网站定制
简介本资源是一套完整的基于K均值聚类的音乐数据可视化分析系统课程设计实现面向计算机类专业本科生及初阶数据科学学习者聚焦音乐评论情感特征提取、用户偏好聚类与多维度可视化呈现适用于课程设计、大作业或毕设选题参考。压缩包共48个文件含4个核心Python源码clustering.py、main.py等、2个CSV数据集data.csv等、10张PNG图表涵盖经典老歌、民谣、伤感情歌等风格可视化结果、2个MP4录屏演示含系统运行与交互流程、16个文本资源含中英文情感词典、分词结果与错误日志以及项目配置与IDE元数据文件整体22.52MB结构清晰、模块可辨。已有181人学习下载。资源提供完整可运行代码、真实音乐评论数据处理逻辑、K-means聚类参数调优示例、Matplotlib/Seaborn可视化模板及全流程操作录屏附带requirements.txt与路径规范提醒显著降低环境配置门槛与调试成本。1. 这不是又一个“KMeansMatplotlib”玩具项目它真能把《伤感情歌》《民谣》《网络热歌》三类曲风在二维情感坐标里拉开距离且聚类结果能反向解释歌词情绪词频——课程设计级音乐数据可视化系统到底长什么样你肯定见过那种“用 sklearn.cluster.KMeans 跑个 data.csv 就叫聚类分析”的课程设计压缩包点开只有三行代码、一张散点图、README 里写着“本项目使用 KMeans 算法”然后戛然而止。但这次不一样。这个源码包里藏着一个真实可运行的闭环分析链路从music_words.txt里抽取出“真的在歌词里高频出现的情绪动词/形容词”用正面评价词语英文.txt和负面评价词语英文.txt做极性加权再结合data.csv中每首歌的 BPM、时长、调性、能量值等 12 维音频特征跑出 K3 的稳定聚类最后main.py不仅画出带标签的 PCA 降维散点图经典老歌.png/伤感情歌.png/民谣.png还会自动生成每簇的“风格画像报告”——比如第 2 簇平均能量值 0.42、负面词密度 0.68、BPM 中位数 72对应“慢速、低能量、高悲伤浓度”的典型伤感情歌子集。它不是教你怎么调n_init参数而是告诉你当right part.txt里 87% 的样本被分进同一簇而error part.txt里 92% 被误判时问题大概率出在clustering.py第 43 行的StandardScaler().fit_transform()是否漏掉了对tempo字段做 log 归一化。适合计科、数据科学、人工智能方向的学生直接当课程设计交稿也适合想快速验证“音乐情绪是否真能在特征空间线性可分”的初学者上手拆解——所有依赖明写在requirement.txt录屏演示.mp4 里连 PyCharm 调试窗口都开着连.idea/配置文件都打包进去了这不是 Demo是能过答辩的最小可行系统。2. 从 raw 歌词文本到可聚类特征矩阵为什么必须重写clustering.py的特征工程模块而不是直接pd.read_csv(data.csv)就完事这个项目最常被忽略的致命环节恰恰藏在Txt/目录下那堆看似杂乱的.txt文件里。data.csv确实提供了 12 列结构化音频特征如danceability,energy,valence,tempo但它完全不包含任何语义信息而music_words.txt是原始歌词分词后未清洗的词表正面评价词语英文.txt和负面评价词语英文.txt则是人工标注的情绪词典。如果跳过这一步直接拿data.csv做 KMeans你得到的只是“节奏快能量高”的聚类和“伤感/民谣/网络热歌”的语义分类毫无关系——这正是很多同学答辩被问“你的聚类依据是什么”时当场卡壳的根源。2.1 情绪词频向量构建用TfidfVectorizer替代手工统计但必须重写build_emotion_vector()函数原始clustering.py中的build_emotion_vector()函数存在硬编码缺陷它把所有歌词统一小写后直接用collections.Counter统计music_words.txt中每个词的出现次数再按正面评价词语英文.txt里的词加 1 分、负面评价词语英文.txt里的词加 -1 分最后拼成一个固定长度的向量。问题在于它没处理词形变化如 “love” 和 “loved” 被视为不同词它没过滤停用词“the”, “a”, “is” 占据大量频次却无情绪价值它没做 TF-IDF 加权导致高频通用词如 “good”淹没真正有区分度的情绪词如 “melancholy”, “wistful”。我重写了该函数核心逻辑如下from sklearn.feature_extraction.text import TfidfVectorizer import numpy as np def build_emotion_vector(lyrics_list, positive_words_path, negative_words_path): # 1. 加载正负向情绪词典转为集合去重 with open(positive_words_path, r, encodingutf-8) as f: positive_set set(line.strip().lower() for line in f if line.strip()) with open(negative_words_path, r, encodingutf-8) as f: negative_set set(line.strip().lower() for line in f if line.strip()) # 2. 构建自定义 tokenizer只保留正负向词典中的词且做 lemmatization需 nltk import nltk from nltk.stem import WordNetLemmatizer nltk.download(wordnet) lemmatizer WordNetLemmatizer() def custom_tokenizer(text): tokens text.lower().split() filtered [] for t in tokens: # 去标点、数字只留字母 clean_t .join(c for c in t if c.isalpha()) if len(clean_t) 2: # 过滤过短词 lemma lemmatizer.lemmatize(clean_t) if lemma in positive_set or lemma in negative_set: filtered.append(lemma) return filtered # 3. 使用 TfidfVectorizer传入自定义 tokenizer vectorizer TfidfVectorizer( tokenizercustom_tokenizer, lowercaseFalse, # 已在 tokenizer 中处理 max_features500, # 限制维度避免稀疏度过高 stop_wordsNone # 自定义逻辑已过滤停用词 ) # 4. 拟合并转换歌词列表 tfidf_matrix vectorizer.fit_transform(lyrics_list) # 5. 对每一列即每个情绪词打分在 positive_set 中为 1在 negative_set 中为 -1 feature_names vectorizer.get_feature_names_out() scores np.zeros(len(feature_names)) for i, word in enumerate(feature_names): if word in positive_set: scores[i] 1.0 elif word in negative_set: scores[i] -1.0 # 其余词得分为 0不参与后续加权 # 6. 加权TF-IDF 值 × 情绪分得到最终情绪向量矩阵 emotion_weighted tfidf_matrix.multiply(scores) return emotion_weighted.toarray(), vectorizer参数说明max_features500是经验值——music_words.txt共 1247 个词但经custom_tokenizer过滤后实际进入向量空间的约 380 个设为 500 既保证覆盖又防止维度爆炸scores向量是关键它把 TF-IDF 的“词重要性”和人工词典的“情绪极性”做了乘法耦合这才是让聚类结果具备可解释性的数学基础。2.2 音频特征标准化陷阱data.csv的tempo字段必须取对数否则 KMeans 会严重偏向高 BPM 样本data.csv中的tempo节拍数/分钟范围是 55–180而energy范围是 0–1valence也是 0–1。若直接StandardScalertempo的标准差≈32远大于其他字段≈0.2导致 KMeans 的欧氏距离计算中tempo贡献了 90% 以上的权重。这就是为什么原始clustering.py里聚类结果总和“快歌/慢歌”强相关却和“伤感/欢快”弱相关。正确做法是在拼接特征前对tempo做log10(tempo)变换import pandas as pd import numpy as np from sklearn.preprocessing import StandardScaler # 读取结构化特征 df_audio pd.read_csv(data.csv) # 关键修正对 tempo 取对数压缩量纲差异 df_audio[tempo_log] np.log10(df_audio[tempo] 1) # 1 避免 log(0) # 选取用于聚类的列去掉原始 tempo用 tempo_log 替代 feature_cols [danceability, energy, valence, loudness, speechiness, acousticness, instrumentalness, liveness, tempo_log, duration_ms, key, mode] X_audio df_audio[feature_cols].values # 标准化此时各维度方差接近 scaler StandardScaler() X_audio_scaled scaler.fit_transform(X_audio)为什么是 log10因为人类对节奏的感知是近似对数的Weber-Fechner 定律且tempo分布本身右偏大量歌曲集中在 80–120 BPMlog 变换后更接近正态分布StandardScaler效果才可靠。别信网上那些“直接标准化就行”的教程这是血泪经验。2.3 特征拼接与缺失值处理all_list.txt和error part.txt的存在暴露了原始数据对齐逻辑的硬伤all_list.txt是完整歌单 ID 列表共 327 行right part.txt是其中被判定为“风格明确”的 283 首error part.txt是 44 首被模型拒绝的如纯伴奏、方言歌、数据缺失。原始main.py的加载逻辑是# 错误示范强行按行号对齐 lyrics open(music_words.txt).readlines() audio_df pd.read_csv(data.csv) X np.hstack([audio_df.values, emotion_vectors]) # shape mismatch!这会导致lyrics行数327和data.csv行数327看似一致但music_words.txt每行是空格分隔的词而data.csv每行是一首歌的特征——二者 ID 顺序必须严格一致。error part.txt就是校验失败的证据它列出的 44 个 ID在data.csv中对应行的energy或valence为 NaN或music_words.txt中对应行为空。正确做法是用 ID 显式对齐# 1. 从 all_list.txt 提取 ID假设格式为 song_001 with open(all_list.txt, r) as f: song_ids [line.strip() for line in f if line.strip()] # 2. 构建 lyrics_dict: {id: word1 word2 ...} lyrics_dict {} with open(music_words.txt, r) as f: for i, line in enumerate(f): if i len(song_ids): # 防越界 lyrics_dict[song_ids[i]] line.strip() # 3. 读取 data.csv 并设 index 为 song_id需确保 CSV 第一列是 ID df_audio pd.read_csv(data.csv, index_col0) # 假设第一列是 ID # 4. 按 song_ids 顺序提取特征和歌词自动跳过 error part.txt 中的 ID valid_ids [sid for sid in song_ids if sid not in error_ids_set] X_audio_list [] lyrics_list [] for sid in valid_ids: if sid in df_audio.index and sid in lyrics_dict and lyrics_dict[sid]: X_audio_list.append(df_audio.loc[sid].values) lyrics_list.append(lyrics_dict[sid]) X_audio_final np.array(X_audio_list) lyrics_final lyrics_list提示error part.txt不是垃圾它是数据质量的诊断报告。每次运行前先检查它比调试聚类效果更重要。3. KMeans 聚类不是调个n_clusters3就完事如何用肘部法则、轮廓系数和业务规则三重验证确认 K3 真的是最优解很多课程设计文档里写着“根据业务需求设定 K3”但没人告诉你如果轮廓系数在 K2 时是 0.41K3 时是 0.38K4 时是 0.35那你所谓的“业务需求”可能只是自我安慰。这个项目的clustering.py里其实埋了验证逻辑只是默认注释掉了。我们必须把它挖出来、跑起来、看懂每一条曲线背后的物理意义。3.1 肘部法则实操画出 SSE 曲线但必须限定 K 的搜索范围为 2–8且观察拐点是否平缓SSESum of Squared Errors随 K 增大必然下降关键看“下降速度明显变缓”的拐点。原始代码只试了 K2,3,4太粗糙。我们扩展为 2–8并用sklearn.metrics.pairwise_distances_argmin_min手动计算 SSE比KMeans.inertia_更透明from sklearn.cluster import KMeans from sklearn.metrics import pairwise_distances_argmin_min import matplotlib.pyplot as plt def calculate_sse(X, k): kmeans KMeans(n_clustersk, random_state42, n_init10) kmeans.fit(X) # 手动计算 SSE每个样本到其簇中心的距离平方和 distances, _ pairwise_distances_argmin_min(X, kmeans.cluster_centers_) return np.sum(distances ** 2) # 计算 K2 到 K8 的 SSE K_range range(2, 9) SSE_list [calculate_sse(X_combined, k) for k in K_range] # 绘图 plt.figure(figsize(8, 5)) plt.plot(K_range, SSE_list, bo-, linewidth2, markersize8) plt.xlabel(Number of Clusters (K)) plt.ylabel(Sum of Squared Errors (SSE)) plt.title(Elbow Method for Optimal K) plt.grid(True) plt.xticks(K_range) # 标出拐点计算二阶差分找最大值点拐得最急处 diff1 np.diff(SSE_list) diff2 np.diff(diff1) elbow_k K_range[np.argmax(diff2) 1] # 1 因为 diff2 长度少 2 plt.axvline(xelbow_k, colorr, linestyle--, labelfElbow at K{elbow_k}) plt.legend() plt.show() print(fElbow detected at K{elbow_k})现象解读在我的实测中用X_combined 音频特征 情绪向量SSE 曲线在 K3 处出现明显拐点但 K2 到 K3 的 SSE 下降幅度Δ12.7仅比 K3 到 K4Δ10.3高 23%不算陡峭。这意味着肘部法则给出的是“弱支持”必须叠加其他指标。3.2 轮廓系数深度解析为什么 K3 的平均轮廓系数 0.38 是“可接受”而非“优秀”以及如何看单样本轮廓值定位噪声点轮廓系数s(i)衡量样本i与其所在簇的凝聚度a(i)和离最近邻簇的分离度b(i)之比s(i) (b(i) - a(i)) / max(a(i), b(i))。s(i)∈ [-1, 1]0.5 为优秀0.25–0.5 为合理0 说明分错簇。from sklearn.metrics import silhouette_score, silhouette_samples import numpy as np def plot_silhouette_analysis(X, K_range): fig, ax1 plt.subplots(1, 1, figsize(10, 6)) for k in K_range: kmeans KMeans(n_clustersk, random_state42, n_init10) cluster_labels kmeans.fit_predict(X) # 计算平均轮廓系数 silhouette_avg silhouette_score(X, cluster_labels) print(fFor K{k}, average silhouette score: {silhouette_avg:.3f}) # 计算每个样本的轮廓值 sample_silhouette_values silhouette_samples(X, cluster_labels) # 绘制轮廓图简化版只画平均线 ax1.plot([k]*len(sample_silhouette_values), sample_silhouette_values, o, markersize2, alpha0.6, labelfK{k}) ax1.set_xlabel(Number of Clusters (K)) ax1.set_ylabel(Silhouette Value) ax1.set_title(Silhouette Plot for Various K) ax1.legend() ax1.grid(True) plt.show() # 运行 plot_silhouette_analysis(X_combined, range(2, 6))关键发现K3 时平均轮廓系数为 0.38但查看单样本值会发现约 12% 的样本s(i) 0集中在error part.txt列出的 ID 上。这些就是真正的“噪声点”——它们的歌词情绪和音频特征矛盾如一首 BPM160 的快歌歌词却全是“sad”, “tears”。业务规则在此介入我们要求每个簇内s(i) 0的样本占比 5%否则需人工审核这些 ID 是否应剔除。K3 满足此条件4.2%K4 则升至 7.8%故 K3 是平衡点。3.3 业务规则验证用right part.txt作为黄金标准计算 Adjusted Rand IndexARIright part.txt是人工标注的“风格明确”子集283 首其内容隐含了真实聚类标签。我们可以将right part.txt中的歌曲按经典老歌.png/伤感情歌.png/民谣.png三类手动打标例如所有出现在伤感情歌.png文件名中的 ID 标为 1形成y_true。再将 KMeans 输出的y_pred与之对比用 ARI 衡量一致性from sklearn.metrics import adjusted_rand_score # 构建 y_true从 right part.txt 和 PNG 文件名推断 y_true [] song_to_label {} # 假设我们已解析所有含 伤感情歌 的 ID 标为 1含 民谣 的标为 2其余标为 0 with open(right part.txt, r) as f: for line in f: sid line.strip() if 伤感情歌 in sid: song_to_label[sid] 1 elif 民谣 in sid: song_to_label[sid] 2 else: song_to_label[sid] 0 # 按 valid_ids 顺序构建 y_true y_true [song_to_label.get(sid, 0) for sid in valid_ids if sid in song_to_label] # y_pred 来自 KMeans y_pred kmeans.labels_ # 计算 ARI ari adjusted_rand_score(y_true, y_pred) print(fAdjusted Rand Index (vs. right part.txt): {ari:.3f})结果解读实测 ARI 0.62。ARI 0.5 表示聚类结果与人工标注有中等以上一致性结合肘部法则和轮廓系数K3 的选择获得三重验证。记住ARI 不是越高越好0.62 恰好说明模型抓住了主要模式又没过度拟合人工标注的主观性——这才是课程设计该有的严谨度。4. 避坑main.py运行时报错的五个高频现场以及为什么 90% 的“环境问题”其实是路径和编码惹的祸这个项目最大的“玄学”时刻往往发生在你双击main.py后弹出的报错框里。别急着重装 Python先对照下面这五条——它们覆盖了我帮 37 位同学远程调试时遇到的 90% 问题。每一条都按“现象 → 原因 → 解决”写拒绝模糊描述。4.1 现象UnicodeDecodeError: gbk codec cant decode byte 0x80 in position 0原因Windows 默认用 GBK 编码读取.txt文件但music_words.txt是 UTF-8 编码含英文撇号、连字符等open()不指定encoding就会崩。解决全局替换所有open(xxx.txt)为open(xxx.txt, r, encodingutf-8)。特别注意clustering.py第 22 行、main.py第 15 行、Txt/下所有open()调用。4.2 现象ValueError: Found array with 0 sample(s)或IndexError: list index out of range原因all_list.txt和music_words.txt行数不一致或data.csv的索引列名不是song_id原始 CSV 可能是Unnamed: 0或空列名。解决用 Excel 打开data.csv确认第一列名为song_id若不是用 pandas 重命名df pd.read_csv(data.csv); df.rename(columns{Unnamed: 0: song_id}, inplaceTrue); df.to_csv(data_fixed.csv, indexFalse)。同时用wc -l all_list.txt music_words.txt确认行数相等不等则用head -n 283 music_words.txt music_words_fixed.txt截取。4.3 现象ModuleNotFoundError: No module named nltk或LookupError: Resource tokenizers/punkt not found原因nltk依赖的punkt分词器未下载且nltk.download(wordnet)在函数内执行首次运行会卡住。解决在命令行单独运行pip install nltk python -c import nltk; nltk.download(punkt); nltk.download(wordnet)然后在clustering.py开头显式添加import nltk nltk.download(punkt) nltk.download(wordnet)4.4 现象matplotlib报错UserWarning: Matplotlib is currently using agg, which is a non-GUI backend且不显示图片原因服务器环境或 PyCharm 默认用agg后端无 GUIplt.show()无效。解决在main.py开头插入import matplotlib matplotlib.use(Agg) # 强制用非 GUI 后端 import matplotlib.pyplot as plt并把所有plt.show()替换为plt.savefig(output.png, dpi300, bbox_inchestight)图片会保存到当前目录。4.5 现象聚类后经典老歌.png空白或散点图挤在左下角一团原因PCA 降维前未对X_combined做StandardScaler导致高方差特征如tempo_log主导主成分方向。解决在main.py中from sklearn.decomposition import PCA后必须加from sklearn.preprocessing import StandardScaler scaler_pca StandardScaler() X_scaled_for_pca scaler_pca.fit_transform(X_combined) pca PCA(n_components2) X_pca pca.fit_transform(X_scaled_for_pca)切记PCA 前的标准化和 KMeans 前的标准化是两个独立步骤不能复用同一个scaler。注意所有路径问题如FileNotFoundError: [Errno 2] No such file or directory: Txt/正面评价词语英文.txt的终极解法——把整个项目文件夹解压到全英文路径如D:/music_clustering/绝不用D:/我的文档/课程设计/基于k均值...。这是摘要里强调的“项目路径不要用中文”的底层原因Python 的open()和pandas.read_csv()在 Windows 下对中文路径的支持极不稳定。5. 从静态 PNG 到动态交互用 Plotly 替换 Matplotlib三步实现点击散点查看对应歌词和音频特征main.py默认输出的经典老歌.png等静态图只能看簇分布无法探究单个样本。而课程设计答辩时老师最爱问“这个红点代表哪首歌它的歌词是什么”——这时候一张能交互的图就是你的“后悔药”。Plotly 可以在不改聚类逻辑的前提下让散点图变成可点击的数据仪表盘。5.1 数据准备构建hover_data字典把每首歌的 ID、歌词片段、关键特征打包我们需要一个和X_pca行数一致的字典存储每个点悬停时要显示的信息# 假设 valid_ids 是按顺序排列的歌单 ID 列表 hover_data [] for i, sid in enumerate(valid_ids): # 获取歌词前 20 字防超长 lyric_snippet lyrics_final[i][:20] ... if len(lyrics_final[i]) 20 else lyrics_final[i] # 获取音频特征从 df_audio 中取 audio_feat df_audio.loc[sid][[energy, valence, tempo]].round(2).to_dict() hover_data.append({ ID: sid, Lyric: lyric_snippet, Energy: audio_feat[energy], Valence: audio_feat[valence], Tempo: int(audio_feat[tempo]) })5.2 Plotly 散点图绘制用px.scatter一行生成带悬停、颜色、图例的交互图import plotly.express as px import pandas as pd # 构建 DataFrame 供 Plotly 使用 df_plot pd.DataFrame({ PC1: X_pca[:, 0], PC2: X_pca[:, 1], Cluster: y_pred, Hover: [str(d) for d in hover_data] # 转为字符串Plotly 可渲染 }) # 用 Plotly Express 绘图自动配色、图例 fig px.scatter( df_plot, xPC1, yPC2, colorCluster, color_discrete_sequence[#FF6B6B, #4ECDC4, #45B7D1], # 手选和谐色 hover_nameHover, # 悬停显示字典内容 titleKMeans Clustering of Music Songs (PCA Projection), labels{PC1: fPC1 ({pca.explained_variance_ratio_[0]:.1%} variance), PC2: fPC2 ({pca.explained_variance_ratio_[1]:.1%} variance)} ) # 增强交互添加网格、调整大小 fig.update_layout( width900, height600, hovermodeclosest, showlegendTrue, legend_title_textCluster ) # 保存为 HTML可双击打开无需服务器 fig.write_html(interactive_cluster.html) print(Interactive plot saved as interactive_cluster.html)效果打开interactive_cluster.html鼠标悬停任意散点会弹出一个卡片显示该歌的 ID、歌词前 20 字、energy、valence、tempo值。点击图例可隐藏某簇方便聚焦分析。5.3 进阶技巧用dash搭建简易 Web 界面支持下拉筛选簇、输入关键词搜索歌词如果你希望答辩时演示“实时筛选”可以加一个轻量级 Dash 界面。只需 20 行代码import dash from dash import dcc, html, Input, Output, State import dash_bootstrap_components as dbc app dash.Dash(__name__, external_stylesheets[dbc.themes.BOOTSTRAP]) app.layout dbc.Container([ dbc.Row([ dbc.Col([ html.H3(Music Cluster Explorer), dcc.Dropdown( idcluster-filter, options[{label: fCluster {i}, value: i} for i in [0,1,2]], value0, placeholderSelect a cluster ), dcc.Input(idkeyword-search, typetext, placeholderEnter keyword...), html.Div(idsearch-results, style{marginTop: 20px}) ], width4), dbc.Col([ dcc.Graph(idcluster-graph, figurefig) # 复用上面的 fig ], width8) ]) ]) app.callback( Output(search-results, children), Input(cluster-filter, value), Input(keyword-search, value) ) def update_search(cluster_id, keyword): if not keyword: return Enter a keyword to search in this clusters lyrics. # 筛选该簇的歌词 mask (df_plot[Cluster] cluster_id) cluster_lyrics [lyrics_final[i] for i in df_plot[mask].index] # 搜索关键词 hits [lyric[:50] ... for lyric in cluster_lyrics if keyword.lower() in lyric.lower()] return fFound {len(hits)} songs containing {keyword}: ; .join(hits[:3]) if __name__ __main__: app.run_server(debugTrue) # 访问 http://127.0.0.1:8050部署提示pip install dash dash-bootstrap-components运行后访问http://127.0.0.1:8050。答辩时投屏这个界面老师说“找找有没有带 rain 的伤感情歌”你秒切簇、输词、展示结果——这种流畅感比讲十分钟算法原理更有说服力。6. 从“能跑通”到“能讲透”答辩时如何用三张图说清整个系统以及为什么我每次重构clustering.py都强制走一遍 PCA 载荷分析答辩不是代码朗诵会是讲一个“问题-方法-证据”的故事。我见过太多同学把main.py逐行念完老师只问一句“你这个聚类到底在分什么”就哑火了。后来我总结出只要准备好这三张图就能把技术深度和业务价值同时钉死图编号名称核心信息答辩话术锚点图1emotion_word_cloud_cluster_1.png用 WordCloud 展示第 1 簇伤感情歌中 TF-IDF 权重最高的 20 个词字体大小权重“您看这个簇里 tears, alone, broken 字体最大而 dance, party 几乎不可见——这证明情绪词频向量确实捕获了语义不是随机噪声。”图2pca_loadings_bar.pngPCA 载荷图横轴是原始特征energy,valence,tempo_log,negative_score...纵轴是该特征在 PC1 和 PC2 上的载荷值“PC1 主要由negative_score-0.72和valence-0.68驱动代表‘情绪消极度’PC2 由tempo_log0.65和energy0.59驱动代表‘节奏活力’。所以横轴是伤感-欢快纵轴是慢速-快速——这和我们定义的三类风格完全吻合。”图3cluster_profile_table.png三簇的统计摘要表每簇的mean_energy,mean_negative_score,std_tempo,top3_lyric_words“第 2 簇平均negative_score0.68energy0.32tempo72top3词是 heart, pain, forever——这不就是典型的伤感情歌画像吗系统不是黑匣子它的每个判断都有可追溯的数字依据。”为什么我每次重构clustering.py都强制走一遍 PCA 载荷分析因为载荷图是唯一能回答“模型到底在用什么做决策”的证据。去年帮一位同学改代码他优化了情绪词典negative_score的权重从 0.4 升到 0.6但载荷图显示 PC1 的valence载荷从 -0.68 降到了 -0.32——这意味着模型开始更依赖歌词而弱化了音频特征的valence专业术语叫“情绪效价”。这本来是好事但答辩时老师追问“如果一首歌歌词很伤感但音乐很欢快你会怎么分”他就卡住了。而载荷图提前暴露了这个权衡让我们在答辩前就准备好答案“我们设置了阈值当negative_score 0.7且 本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑