作者:韩信子@ShowMeAI

数据分析实战系列https://www.showmeai.tech/tutorials/40

机器学习实战系列https://www.showmeai.tech/tutorials/41

本文地址https://www.showmeai.tech/article-detail/309

声明:版权所有,转载请联系平台与作者并注明出处

收藏ShowMeAI查看更多精彩内容

只要给到足够的相关信息,AI模型可以迅速学习一个新的领域问题,并构建起很好的知识和预估系统。比如音乐领域,借助于歌曲相关信息,模型可以根据歌曲的音频和歌词特征将歌曲精准进行流派分类。在本篇内容中 ShowMeAI 就带大家一起来看看,如何基于机器学习完成对音乐的识别分类。

本篇内容使用到的数据集为 Spotify音乐数据集,大家也可以通过 ShowMeAI 的百度网盘地址快速下载。

实战数据集下载(百度网盘):公众号『ShowMeAI研究中心』回复『实战』,或者点击 这里 获取本文 [18]音乐流派识别的机器学习系统搭建与调优Spotify 音乐数据集

ShowMeAI官方GitHubhttps://github.com/ShowMeAI-Hub

我们在本篇内容中将用到最常用的 boosting 集成工具库 LightGBM,并且将结合 optuna 工具库对其进行超参数调优,优化模型效果。

关于 LightGBM 的模型原理和使用详细讲解,欢迎大家查阅 ShowMeAI 的文章:

图解机器学习算法(11) | LightGBM模型详解

机器学习实战(5) | LightGBM建模应用详解

本篇文章包含以下内容板块:

  • 数据概览和预处理
  • EDA探索性数据分析
  • 歌词特征&数据降维
  • 建模和超参数优化
  • 总结&经验

数据概览和预处理

本次使用的数据集包含超过 18000 首歌曲的信息,包括其音频特征信息(如活力度,播放速度或调性等),以及歌曲的歌词。

我们读取数据并做一个速览如下:

  1. import pandas as pd
  2. # 读取数据
  3. data = pd.read_csv("spotify_songs.csv")
  4. # 数据速览
  5. data.head()
  1. # 数据基本信息
  2. data.info()

字段说明如下:

字段 含义
track_id 歌曲唯一ID
track_name 歌曲名称
track_artist 歌手
lyrics 歌词
track_popularity 唱片热度
track_album_id 唱片的唯一ID
track_album_name 唱片名字
track_album_release_date 唱片发行日期
playlist_name 歌单名称
playlist_id 歌单ID
playlist_genre 歌单风格
playlist_subgenre 歌单子风格
danceability 舞蹈性描述的是根据音乐元素的组合,包括速度、节奏的稳定性、节拍的强度和整体的规律性,来衡量一首曲目是否适合跳舞。0.0的值是最不适合跳舞的,1.0是最适合跳舞的。
energy 能量是一个从0.0到1.0的度量,代表强度和活动的感知度。一般来说,有能量的曲目给人的感觉是快速、响亮。例如,死亡金属有很高的能量,而巴赫的前奏曲在该量表中得分较低。
key 音轨的估测总调。用标准的音阶符号将整数映射为音高。例如,0=C,1=C♯/D♭,2=D,以此类推。如果没有检测到音调,则数值为-1。
loudness 轨道的整体响度,单位是分贝(dB)。响度值是整个音轨的平均值,对于比较音轨的相对响度非常有用。
mode 模式表示音轨的调式(大调或小调),即其旋律内容所来自的音阶类型。大调用1表示,小调用0表示。
speechiness 言语性检测音轨中是否有口语。录音越是完全类似于语音(如脱口秀、说唱、诗歌),属性值就越接近1.0。
acousticness 衡量音轨是否为声学的信心指数,从0.0到1.0。1.0表示该曲目为原声的高置信度。
instrumentalness 预测一个音轨是否包含人声。越接近1.0该曲目就越有可能不包含人声内容。
liveness 检测录音中是否有听众存在。越接近现场演出数值越大。
valence 0.0到1.0,描述了一个音轨所传达的音乐积极性,接近1的曲目听起来更积极(如快乐、欢快、兴奋),而接近0的曲目听起来更消极(如悲伤、压抑、愤怒)。
tempo 轨道的整体估计速度,单位是每分钟节拍(BPM)。
duration_ms 歌曲的持续时间(毫秒)
language 歌词的语言语种

原始的数据有点杂乱,我们先进行过滤和数据清洗。

  1. # 数据工具库
  2. import pandas as pd
  3. import re
  4. # 歌词处理的nlp工具库
  5. import nltk
  6. from nltk.corpus import stopwords
  7. from collections import Counter
  8. # nltk.download('stopwords')
  9. # 读取数据
  10. data = pd.read_csv("spotify_songs.csv")
  11. # 字段选择
  12. keep_cols = [x for x in data.columns if not x.startswith("track") and not x.startswith("playlist")]
  13. keep_cols.append("playlist_genre")
  14. df = data[keep_cols].copy()
  15. # 只保留英文歌曲
  16. subdf = df[(df.language == "en") & (df.playlist_genre != "latin")].copy().drop(columns = "language")
  17. # 歌词规整化,全部小写
  18. pattern = r"[^a-zA-Z ]"
  19. subdf.lyrics = subdf.lyrics.apply(lambda x: re.sub(pattern, "", x.lower()))
  20. # 移除停用词
  21. subdf.lyrics = subdf.lyrics.apply(lambda x: ' '.join([word for word in x.split() if word not in (stopwords.words("english"))]))
  22. # 查看歌词中的词汇出现的频次
  23. # 连接所有歌词
  24. all_text = " ".join(subdf.lyrics)
  25. # 统计词频
  26. word_count = Counter(all_text.split())
  27. # 如果一个词在200首以上的歌里都出现,则保留,否则视作低频过滤掉
  28. keep_words = [k for k, v in word_count.items() if v > 200]
  29. # 构建一个副本
  30. lyricdf = subdf.copy().reset_index(drop=True)
  31. # 字段名称规范化
  32. lyricdf.columns = ["audio_"+ x if not x in ["lyrics", "playlist_genre"] else x for x in lyricdf.columns]
  33. # 歌词内容
  34. lyricdf.lyrics = lyricdf.lyrics.apply(lambda x: Counter([word for word in x.split() if word in keep_words]))
  35. # 构建词汇词频Dataframe
  36. unpacked_lyrics = pd.DataFrame.from_records(lyricdf.lyrics).add_prefix("lyrics_")
  37. # 缺失填充为0
  38. unpacked_lyrics = unpacked_lyrics.fillna(0)
  39. # 拼接并删除原始歌词列
  40. lyricdf = pd.concat([lyricdf, unpacked_lyrics], axis = 1).drop(columns = "lyrics")
  41. # 排序
  42. reordered_cols = [col for col in lyricdf.columns if not col.startswith("lyrics_")] + sorted([col for col in lyricdf.columns if col.startswith("lyrics_")])
  43. lyricdf = lyricdf[reordered_cols]
  44. # 存储为新的csv文件
  45. lyricdf.to_csv("music_data.csv", index = False)

主要的数据预处理在上述代码的注释里大家可以看到,核心步骤概述如下:

  • 过滤数据以仅包含英语歌曲并删除“拉丁”类型的歌曲(因为这些歌曲几乎完全是西班牙语,所以会产生严重的类不平衡)。
  • 通过将歌词设为小写、删除标点符号和停用词来整理歌词。计算每个剩余单词在歌曲歌词中出现的次数,然后过滤掉所有歌曲中出现频率最低的单词(混乱的数据/噪音)。
  • 清理与排序。

EDA探索性数据分析

和过往所有的项目一样,我们也需要先对数据做一些分析和更进一步的理解,也就是EDA探索性数据分析过程。

EDA数据分析部分涉及的工具库,大家可以参考ShowMeAI制作的工具库速查表和教程进行学习和快速使用。

数据科学工具库速查表 | Pandas 速查表

图解数据分析:从入门到精通系列教程

首先我们检查一下我们的标签(流派)的类分布和平衡

  1. # 分组统计
  2. by_genre = data.groupby("playlist_genre")["audio_key"].count().reset_index()
  3. fig, ax = plt.subplots()
  4. # 绘图
  5. ax.bar(by_genre.playlist_genre, by_genre.audio_key)
  6. ax.set_ylabel("Number of Observations")
  7. ax.set_xlabel("Genre")
  8. ax.set_title("Observations per Class")
  9. ax.set_ylim(0, 4000)
  10. # 每个柱子上标注数量
  11. rects = ax.patches
  12. for rect in rects:
  13. height = rect.get_height()
  14. ax.text(
  15. rect.get_x() + rect.get_width() / 2, height + 5, height, ha="center", va="bottom"
  16. )

存在轻微的类别不平衡,那后续我们在交叉验证和训练测试拆分时候注意数据分层(保持比例分布) 即可。

  1. # 把所有字段切分为音频和歌词列
  2. audio = data[[x for x in data.columns if x.startswith("audio")]]
  3. lyric = data[[x for x in data.columns if x.startswith("lyric")]]
  4. # 让字段命名更简单一些
  5. audio.columns = audio.columns.str.replace("audio_", "")
  6. lyric.columns = lyric.columns.str.replace("lyric_", "")

歌词特征&数据降维

我们的机器学习算法在处理高维数据的时候,可能会有一些性能问题,有时候我们会对数据进行降维处理。

降维的本质是将高维数据投影到低维子空间中,同时尽可能多地保留数据中的信息。关于降维大家可以查看 ShowMeAI 的算法原理讲解文章 图解机器学习 | 降维算法详解

我们探索一下降维算法(PCA 和 t-SNE)在我们的歌词数据上降维是否合适,并做一点调整。

PCA主成分分析

PCA是最常用的降维算法之一,我们借助这个算法可以对数据进行降维,并且看到它保留大概多少的原始信息量。例如,在我们当前场景中,如果将歌词减少到400 维,我们仍然保留了歌词中60% 的信息(方差) ;如果降维到800维,则可以覆盖 80% 的原始信息(方差)。歌词本身是很稀疏的,我们对其降维也能让模型更好地建模。

  1. # 常规数据工具库
  2. import pandas as pd
  3. import numpy as np
  4. # 绘图
  5. import matplotlib.pyplot as plt
  6. import matplotlib.ticker as mtick
  7. # 数据处理
  8. from sklearn.preprocessing import MinMaxScaler
  9. from sklearn.decomposition import PCA
  10. # 读取数据
  11. data = pd.read_csv("music_data.csv")
  12. # 切分为音频与歌词
  13. audio = data[[x for x in data.columns if x.startswith("audio")]]
  14. lyric = data[[x for x in data.columns if x.startswith("lyric")]]
  15. # 特征字段
  16. y = data.playlist_genre
  17. # 数据幅度缩放 + PCA降维
  18. scaler = MinMaxScaler()
  19. audio_features = scaler.fit_transform(audio)
  20. lyric_features = scaler.fit_transform(lyric)
  21. pca = PCA()
  22. lyric_pca = pca.fit_transform(lyric_features)
  23. var_explained_ratio = pca.explained_variance_ratio_
  24. # Plot graph
  25. fig, ax = plt.subplots()
  26. # Reduce margins
  27. plt.margins(x=0.01)
  28. # Get cumuluative sum of variance explained
  29. cum_var_explained = np.cumsum(var_explained_ratio)
  30. # Plot cumulative sum
  31. ax.fill_between(range(len(cum_var_explained)), cum_var_explained,
  32. alpha = 0.4, color = "tab:orange",
  33. label = "Cum. Var.")
  34. ax.set_ylim(0, 1)
  35. # Plot actual proportions
  36. ax2 = ax.twinx()
  37. ax2.plot(range(len(var_explained_ratio)), var_explained_ratio,
  38. alpha = 1, color = "tab:blue", lw = 4, ls = "--",
  39. label = "Var per PC")
  40. ax2.set_ylim(0, 0.005)
  41. # Add lines to indicate where good values of components may be
  42. ax.hlines(0.6, 0, var_explained_ratio.shape[0], color = "tab:green", lw = 3, alpha = 0.6, ls=":")
  43. ax.hlines(0.8, 0, var_explained_ratio.shape[0], color = "tab:green", lw = 3, alpha = 0.6, ls=":")
  44. # Plot both legends together
  45. lines, labels = ax.get_legend_handles_labels()
  46. lines2, labels2 = ax2.get_legend_handles_labels()
  47. ax2.legend(lines + lines2, labels + labels2)
  48. # Format axis as percentages
  49. ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
  50. ax2.yaxis.set_major_formatter(mtick.PercentFormatter(1))
  51. # Add titles and labels
  52. ax.set_ylabel("Cum. Prop. of Variance Explained")
  53. ax2.set_ylabel("Prop. of Variance Explained per PC", rotation = 270, labelpad=30)
  54. ax.set_title("Variance Explained by Number of Principal Components")
  55. ax.set_xlabel("Number of Principal Components")

t-SNE可视化

我们还可以更进一步,可视化数据在一系列降维过程中的可分离性。t-SNE算法是一个非常有效的非线性降维可视化方法,借助于它,我们可以把数据绘制在二维平面观察其分散程度。下面的t-SNE可视化展示了当我们使用所有1806个特征或将其减少为 1000、500、100 个主成分时,如果将歌词数据投影到二维空间中会是什么样子。

代码如下:

  1. from sklearn.manifold import TSNE
  2. import seaborn as sns
  3. # Merge numeric labels with normalised audio data and lyric principal components
  4. tsne_processed = pd.concat([
  5. pd.Series(y, name = "genre"),
  6. pd.DataFrame(audio_features, columns=audio.columns),
  7. # Add prefix to make selecting pcs easier later on
  8. pd.DataFrame(lyric_pca).add_prefix("lyrics_pc_")
  9. ], axis = 1)
  10. # Get t-SNE values for a range of principal component cutoffs, 1806 is all PCs
  11. all_tsne = pd.DataFrame()
  12. for cutoff in ["1806", "1000", "500", "100"]:
  13. # Create t-SNE object
  14. tsne = TSNE(init = "random", learning_rate = "auto")
  15. # Fit on normalised features (excluding the y/label column)
  16. tsne_results = tsne.fit_transform(tsne_processed.loc[:, "audio_danceability":f"lyrics_pc_{cutoff}"])
  17. # neater graph
  18. if cutoff == "1806":
  19. cutoff = "All 1806"
  20. # Get results
  21. tsne_df = pd.DataFrame({"y":y,
  22. "tsne-2d-one":tsne_results[:,0],
  23. "tsne-2d-two":tsne_results[:,1],
  24. "Cutoff":cutoff})
  25. # Store results
  26. all_tsne = pd.concat([all_tsne, tsne_df], axis = 0)
  27. # Plot gridplot
  28. g = sns.FacetGrid(all_tsne, col="Cutoff", hue = "y",
  29. col_wrap = 2, height = 6,
  30. palette=sns.color_palette("hls", 4),
  31. )
  32. # Add plots
  33. g.map(sns.scatterplot, "tsne-2d-one", "tsne-2d-two", alpha = 0.3)
  34. # Add titles/legends
  35. g.fig.suptitle("t-SNE Plots vs Number of Principal Components Included", y = 1)
  36. g.add_legend()

理想情况下,我们希望看到的是,在降维到某些主成分数量(例如 cutoff = 1000)时,流派变得更加可分离。

然而,上述 t-SNE 图的结果显示,PCA 这一步不同数量的主成分并没有哪个会让数据标签更可分离。

自编码器降维

实际上我们有不同的方式可以完成数据降维任务,在下面的代码中,我们提供了 PCA、截断 SVD 和 Keras 自编码器三种方式作为候选,调整配置即可进行选择。

为简洁起见,自动编码器的代码已被省略,但可以在 autoencode 内的功能 custom_functions.py 中的文件库。

  1. # 通用库
  2. import pandas as pd
  3. import numpy as np
  4. # 建模库
  5. from sklearn.model_selection import train_test_split
  6. from sklearn.decomposition import PCA, TruncatedSVD
  7. from sklearn.preprocessing import LabelEncoder, MinMaxScaler
  8. # 神经网络
  9. from keras.layers import Dense, Input, LeakyReLU, BatchNormalization
  10. from keras.callbacks import EarlyStopping
  11. from keras import Model
  12. # 定义自编码器
  13. def autoencode(lyric_tr, n_components):
  14. """Build, compile and fit an autoencoder for
  15. lyric data using Keras. Uses a batch normalised,
  16. undercomplete encoder with leaky ReLU activations.
  17. It will take a while to train.
  18. --------------------------------------------------
  19. lyric_tr = df of lyric training data
  20. n_components = int, number of output dimensions
  21. from encoder
  22. """
  23. n_inputs = lyric_tr.shape[1]
  24. # 定义encoder
  25. visible = Input(shape=(n_inputs,))
  26. # encoder模块1
  27. e = Dense(n_inputs*2)(visible)
  28. e = BatchNormalization()(e)
  29. e = LeakyReLU()(e)
  30. # encoder模块2
  31. e = Dense(n_inputs)(e)
  32. e = BatchNormalization()(e)
  33. e = LeakyReLU()(e)
  34. bottleneck = Dense(n_components)(e)
  35. # decoder模块1
  36. d = Dense(n_inputs)(bottleneck)
  37. d = BatchNormalization()(d)
  38. d = LeakyReLU()(d)
  39. # decoder模块2
  40. d = Dense(n_inputs*2)(d)
  41. d = BatchNormalization()(d)
  42. d = LeakyReLU()(d)
  43. # 输出层
  44. output = Dense(n_inputs, activation='linear')(d)
  45. # 完整的autoencoder模型
  46. model = Model(inputs=visible, outputs=output)
  47. # 编译
  48. model.compile(optimizer='adam', loss='mse')
  49. # 回调函数
  50. callbacks = EarlyStopping(patience = 20, restore_best_weights = True)
  51. # 训练模型
  52. model.fit(lyric_tr, lyric_tr, epochs=200,
  53. batch_size=16, verbose=1, validation_split=0.2,
  54. callbacks = callbacks)
  55. # 在降维阶段,我们只用encoder部分就可以(对数据进行压缩)
  56. encoder = Model(inputs=visible, outputs=bottleneck)
  57. return encoder
  58. # 数据预处理函数,主要是对特征列进行降维,标签列进行编码
  59. def pre_process(train = pd.DataFrame,
  60. test = pd.DataFrame,
  61. reduction_method = "pca",
  62. n_components = 400):
  63. # 切分X和y
  64. y_train = train.playlist_genre
  65. y_test = test.playlist_genre
  66. X_train = train.drop(columns = "playlist_genre")
  67. X_test = test.drop(columns = "playlist_genre")
  68. # 标签编码为数字
  69. label_encoder = LabelEncoder()
  70. label_train = label_encoder.fit_transform(y_train)
  71. label_test = label_encoder.transform(y_test)
  72. # 对数据进行幅度缩放处理
  73. scaler = MinMaxScaler()
  74. X_norm_tr = scaler.fit_transform(X_train)
  75. X_norm_te = scaler.transform(X_test)
  76. # 重建数据
  77. X_norm_tr = pd.DataFrame(X_norm_tr, columns = X_train.columns)
  78. X_norm_te = pd.DataFrame(X_norm_te, columns = X_test.columns)
  79. # mode和key都设定为类别型
  80. X_norm_tr["audio_mode"] = X_train["audio_mode"].astype("category").reset_index(drop = True)
  81. X_norm_tr["audio_key"] = X_train["audio_key"].astype("category").reset_index(drop = True)
  82. X_norm_te["audio_mode"] = X_test["audio_mode"].astype("category").reset_index(drop = True)
  83. X_norm_te["audio_key"] = X_test["audio_key"].astype("category").reset_index(drop = True)
  84. # 歌词特征
  85. lyric_tr = X_norm_tr.loc[:, "lyrics_aah":]
  86. lyric_te = X_norm_te.loc[:, "lyrics_aah":]
  87. # 如果使用PCA降维
  88. if reduction_method == "pca":
  89. pca = PCA(n_components)
  90. # 拟合训练集
  91. reduced_tr = pd.DataFrame(pca.fit_transform(lyric_tr)).add_prefix("lyrics_pca_")
  92. # 对测试集变换(降维)
  93. reduced_te = pd.DataFrame(pca.transform(lyric_te)).add_prefix("lyrics_pca_")
  94. # 如果使用SVD降维
  95. if reduction_method == "svd":
  96. svd = TruncatedSVD(n_components)
  97. # 拟合训练集
  98. reduced_tr = pd.DataFrame(svd.fit_transform(lyric_tr)).add_prefix("lyrics_svd_")
  99. # 对测试集变换(降维)
  100. reduced_te = pd.DataFrame(svd.transform(lyric_te)).add_prefix("lyrics_svd_")
  101. # 如果使用自编码器降维(注意,神经网络的训练时间会长一点,要耐心等待)
  102. if reduction_method == "keras":
  103. # 构建自编码器
  104. encoder = autoencode(lyric_tr, n_components)
  105. # 通过编码器部分进行数据降维
  106. reduced_tr = pd.DataFrame(encoder.predict(lyric_tr)).add_prefix("lyrics_keras_")
  107. reduced_te = pd.DataFrame(encoder.predict(lyric_te)).add_prefix("lyrics_keras_")
  108. # 合并降维后的歌词特征与音频特征
  109. X_norm_tr = pd.concat([X_norm_tr.loc[:, :"audio_duration_ms"],
  110. reduced_tr
  111. ], axis = 1)
  112. X_norm_te = pd.concat([X_norm_te.loc[:, :"audio_duration_ms"],
  113. reduced_te
  114. ], axis = 1)
  115. return X_norm_tr, label_train, X_norm_te, label_test, label_encoder
  116. # 分层切分数据
  117. train_raw, test_raw = train_test_split(data, test_size = 0.2,
  118. shuffle = True, random_state = 42, # random, reproducible split
  119. stratify = data.playlist_genre)
  120. # 设定降维最终维度
  121. n_components = 500
  122. # 选择降维方法,候选: "pca", "svd", "keras"
  123. reduction_method = "pca"
  124. # 完整的数据预处理
  125. X_train, y_train, X_test, y_test, label_encoder = pre_process(train_raw, test_raw,
  126. reduction_method = reduction_method,
  127. n_components = n_components)

上述过程之后我们已经完成对数据的标准化、编码转换和降维,接下来我们使用它进行建模。

建模和超参数优化

构建模型

在实际建模之前,我们要先选定一个评估指标来评估我们模型的性能,也方便指导进一步的优化。由于我们数据最终的标签『流派/类别』略有不平衡,宏观 F1 分数(macro f1-score) 可能是一个不错的选择,因为它平等地评估了类别的贡献。我们在下面对这个评估准则进行定义,也敲定 LightGBM 模型的部分超参数。

  1. from sklearn.metrics import f1_score
  2. # 定义评估准则(Macro F1)
  3. def lgb_f1_score(preds, data):
  4. labels = data.get_label()
  5. preds = preds.reshape(5, -1).T
  6. preds = preds.argmax(axis = 1)
  7. f_score = f1_score(labels , preds, average = 'macro')
  8. return 'f1_score', f_score, True
  9. # 用于编译的参数
  10. fixed_params = {
  11. 'objective': 'multiclass',
  12. 'metric': "None", # 我们自定义的f1-score可以应用
  13. 'num_class': 5,
  14. 'verbosity': -1,
  15. }

LightGBM 带有大量可调超参数,这些超参数对于最终效果影响很大。

关于 LightGBM 的超参数细节详细讲解,欢迎大家查阅 ShowMeAI 的文章:

机器学习实战(5) | LightGBM建模应用详解

下面我们会基于Optuna这个工具库对 LightGBM 的超参数进行调优,我们需要在 param 定义超参数的搜索空间,在此基础上 Optuna 会进行优化和超参数的选择。


  1. # 建模
  2. from sklearn.model_selection import StratifiedKFold
  3. import lightgbm as lgb
  4. from optuna.integration import LightGBMPruningCallback
  5. # 定义目标函数
  6. def objective(trial, X, y):
  7. # 候选超参数
  8. param = {**fixed_params,
  9. 'boosting_type': 'gbdt',
  10. 'num_leaves': trial.suggest_int('num_leaves', 2, 3000, step = 20),
  11. 'feature_fraction': trial.suggest_float('feature_fraction', 0.2, 0.99, step = 0.05),
  12. 'bagging_fraction': trial.suggest_float('bagging_fraction', 0.2, 0.99, step = 0.05),
  13. 'bagging_freq': trial.suggest_int('bagging_freq', 1, 7),
  14. 'min_child_samples': trial.suggest_int('min_child_samples', 5, 100),
  15. "n_estimators": trial.suggest_int("n_estimators", 200, 5000),
  16. "learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3),
  17. "max_depth": trial.suggest_int("max_depth", 3, 12),
  18. "min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 5, 2000, step=5),
  19. "lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True),
  20. "lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True),
  21. "min_gain_to_split": trial.suggest_float("min_gain_to_split", 0, 10),
  22. "max_bin": trial.suggest_int("max_bin", 200, 300),
  23. }
  24. # 构建分层交叉验证
  25. cv = StratifiedKFold(n_splits = 5, shuffle = True)
  26. # 5组得分
  27. cv_scores = np.empty(5)
  28. # 切分为K个数据组,轮番作为训练集和验证集进行实验
  29. for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)):
  30. # 数据切分
  31. X_train_cv, X_test_cv = X.iloc[train_idx], X.iloc[test_idx]
  32. y_train_cv, y_test_cv = y[train_idx], y[test_idx]
  33. # 转为lightgbm的Dataset格式
  34. train_data = lgb.Dataset(X_train_cv, label = y_train_cv, categorical_feature="auto")
  35. val_data = lgb.Dataset(X_test_cv, label = y_test_cv, categorical_feature="auto",
  36. reference = train_data)
  37. # 回调函数
  38. callbacks = [
  39. LightGBMPruningCallback(trial, metric = "f1_score"),
  40. # 间歇输出信息
  41. lgb.log_evaluation(period = 100),
  42. # 早停止,防止过拟合
  43. lgb.early_stopping(50)]
  44. # 训练模型
  45. model = lgb.train(params = param, train_set = train_data,
  46. valid_sets = val_data,
  47. callbacks = callbacks,
  48. feval = lgb_f1_score # 自定义评估准则
  49. )
  50. # 预估
  51. preds = np.argmax(model.predict(X_test_cv), axis = 1)
  52. # 计算f1-score
  53. cv_scores[idx] = f1_score(y_test_cv, preds, average = "macro")
  54. return np.mean(cv_scores)

超参数优化

我们在上面定义完了目标函数,现在可以使用 Optuna 来调优模型的超参数了。

  1. # 超参数优化
  2. import optuna
  3. # 定义Optuna的实验次数
  4. n_trials = 200
  5. # 构建Optuna study去进行超参数检索与调优
  6. study = optuna.create_study(direction = "maximize", # 最大化交叉验证的F1得分
  7. study_name = "LGBM Classifier",
  8. pruner=optuna.pruners.HyperbandPruner())
  9. func = lambda trial: objective(trial, X_train, y_train)
  10. study.optimize(func, n_trials = n_trials)

然后,我们可以使用 Optuna 的可视化模块不同超参数组合的性能进行可视化查看。例如,我们可以使用 plot_param_importances(study) 查看哪些超参数对模型性能/影响优化最重要。

  1. plot_param_importances(study)

我们也可以使用 plot_parallel_coordinate(study)查看尝试了哪些超参数组合/范围可以带来高评估结果值(好的效果性能)。

  1. plot_parallel_coordinate(study)

然后我们可以使用 plot_optimization_history 查看历史情况。

  1. plot_optimization_history(study)

在Optuna完成调优之后:

  • 最好的超参数存储在 study.best_params 属性中。我们把模型的最终参数 params 定义为 params = {**fixed_params, **study.best_params} 即可,如后续的代码所示。
  • 当然,你也可以缩小搜索空间/超参数范围,进一步做精确的超参数优化。
  1. # 最佳模型实验
  2. cv = StratifiedKFold(n_splits = 5, shuffle = True)
  3. # 5组得分
  4. cv_scores = np.empty(5)
  5. # 切分为K个数据组,轮番作为训练集和验证集进行实验
  6. for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)):
  7. # 数据切分
  8. X_train_cv, X_test_cv = X.iloc[train_idx], X.iloc[test_idx]
  9. y_train_cv, y_test_cv = y[train_idx], y[test_idx]
  10. # 转为lightgbm的Dataset格式
  11. train_data = lgb.Dataset(X_train_cv, label = y_train_cv, categorical_feature="auto")
  12. val_data = lgb.Dataset(X_test_cv, label = y_test_cv, categorical_feature="auto",
  13. reference = train_data)
  14. # 回调函数
  15. callbacks = [
  16. LightGBMPruningCallback(trial, metric = "f1_score"),
  17. # 间歇输出信息
  18. lgb.log_evaluation(period = 100),
  19. # 早停止,防止过拟合
  20. lgb.early_stopping(50)]
  21. # 训练模型
  22. model = lgb.train(params = {**fixed_params, **study.best_params}, train_set = train_data,
  23. valid_sets = val_data,
  24. callbacks = callbacks,
  25. feval = lgb_f1_score # 自定义评估准则
  26. )
  27. # 预估
  28. preds = np.argmax(model.predict(X_test_cv), axis = 1)
  29. # 计算f1-score
  30. cv_scores[idx] = f1_score(y_test_cv, preds, average = "macro")

最终评估

通过上述过程我们就获得了最终模型,让我们来评估一下吧!


  1. # 预估与评估训练集
  2. train_preds = model.predict(X_train)
  3. train_predictions = np.argmax(train_preds, axis = 1)
  4. train_error = f1_score(y_train, train_predictions, average = "macro")
  5. # 交叉验证结果
  6. cv_error = np.mean(cv_scores)
  7. # 评估测试集
  8. test_preds = model.predict(X_test)
  9. test_predictions = np.argmax(test_preds, axis = 1)
  10. test_error = f1_score(y_test, test_predictions, average = "macro")
  11. # 存储评估结果
  12. results = pd.DataFrame({"n_components": n_components,
  13. "reduction_method": reduction_method,
  14. "train_error": train_error,
  15. "cv_error": cv_error,
  16. "test_error": test_error,
  17. "n_trials": n_trials
  18. }, index = [0])

我们可以实验和比较不同的降维方法、降维维度,再调参查看模型效果。如下图所示,在我们当前的尝试中,PCA降维到 400 维产出最好的模型 ——macro f1-score 为66.48%。

总结

在本篇内容中, ShowMeAI 展示了基于歌曲信息与文本对其进行『流派』分类的过程,包含对文本数据的处理、特征工程、模型建模和超参数优化等。大家可以把整个pipeline作为一个模板来应用在其他任务当中。

参考资料

AI 音辨世界:艺术小白的我,靠这个AI模型,速识音乐流派选择音乐 ⛵的更多相关文章

  1. DTSE Tech Talk | 第10期:云会议带你入门音视频世界

    摘要:本期直播主题是<云会议带你入门音视频世界>,华为云媒体服务产品部资深专家金云飞,与开发者们交流华为云会议在实时音视频行业中的集成应用,帮助开发者更好的理解华为云会议及其开放能力. 本 ...

  2. 快快使用ModelArts,零基础小白也能玩转AI!

    摘要: 走过路过不要错过,看Copy攻城狮如何借力华为云ModelArts玩转AI. "自2018年10月发布以来,ModelArts累计服务了众多行业十几万开发者,通过基础平台的完备性和面 ...

  3. 游戏AI的生命力源自哪里?为你揭开MOBA AI的秘密!

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由wataloo发表在专栏wataloo的试验田 1 设计概要 1.1 设计原则和目的 英雄AI的目的主要有: 1.新手过渡局,让玩家刚 ...

  4. 释放至强平台 AI 加速潜能 汇医慧影打造全周期 AI 医学影像解决方案

    基于英特尔架构实现软硬协同加速,显著提升新冠肺炎.乳腺癌等疾病的检测和筛查效率,并帮助医疗科研平台预防"维度灾难"问题 <PAGE 1 LEFT COLUMN: CUSTOM ...

  5. 小白学Python——用 百度AI 实现 OCR 文字识别

    百度AI功能还是很强大的,百度AI开放平台真的是测试接口的天堂,免费接口很多,当然有量的限制,但个人使用是完全够用的,什么人脸识别.MQTT服务器.语音识别等等,应有尽有. 看看OCR识别免费的量 快 ...

  6. FL Studio带你走进混音的世界

    混音,是把多种音源整合到一个立体音轨或单音音轨中,通俗讲就是对多种声音进行调整后叠加在一起,这样可以让音乐听起来非常有层次感,尤其是在电音制作过程中,混音的质量更是起到了决定性的作用.音乐制作软件FL ...

  7. 【小白的CFD之旅】21 网格划分软件的选择

    但是怎样才能获得流体计算网格呢?“工欲善其事必先利其器”,画网格该用什么器呢?小白决定找黄师姐请教一番. 小白找到黄师姐的时候,黄师姐正在电脑上忙着. “黄师姐,我发现网格划分软件有好多种,究竟哪种才 ...

  8. 现代英特尔® 架构上的 TensorFlow* 优化——正如去年参加Intel AI会议一样,Intel自己提供了对接自己AI CPU优化版本的Tensorflow,下载链接见后,同时可以基于谷歌官方的tf版本直接编译生成安装包

    现代英特尔® 架构上的 TensorFlow* 优化 转自:https://software.intel.com/zh-cn/articles/tensorflow-optimizations-on- ...

  9. 小白科普之JavaScript的BOM模型

    一.什么是BOM 1. BOM是browser object model的缩写,简称浏览器对象模型: 2. BOM提供了独立于内容而与浏览器窗口进行交互的对象,描述了与浏览器进行交互的方法和接口: 3 ...

随机推荐

  1. 苹果宣布 2022 年 Apple 设计大奖得主

    Apple 今日举办了年度 Apple 设计大奖颁奖仪式,表彰 12 款出类拔萃的 app 与游戏佳作.今年的获奖者包括来自全球各地的开发者.他们通过 app 呈现锐意创新.别出心裁的优美设计体验,以 ...

  2. Training loop Run Builder和namedtuple()函数

    namedtuple()函数见:https://www.runoob.com/note/25726和https://www.cnblogs.com/os-python/p/6809467.html n ...

  3. 开源流程引擎该如何选择flowable还是camunda

    市场上比较有名的开源流程引擎有osworkflow.jbpm.activiti.flowable.camunda.现在国内用的最多的是activiti.flowable.camunda,下面主要从功能 ...

  4. H2-Table CATALOGS not found

    在使用 IntelliJ IDEA 2021.1.3 版本,使用默认配置连接 H2 数据库的时候,出现下面错误,项目里 H2 使用的版本为 2.0.202 . [42S02][42102] org.h ...

  5. JS:逗号运算符

    逗号运算符: 会把逗号隔开的表达式全部执行 最后一个运行的表达式的结果就是逗号运算符的结果   例: var a = (1, 2, 3, 4, 5, 6); console.log(a); //6 隐 ...

  6. 论文解读(USIB)《Towards Explanation for Unsupervised Graph-Level Representation Learning》

    论文信息 论文标题:Towards Explanation for Unsupervised Graph-Level Representation Learning论文作者:Qinghua Zheng ...

  7. Netty 如何高效接收网络数据?一文聊透 ByteBuffer 动态自适应扩缩容机制

    本系列Netty源码解析文章基于 4.1.56.Final版本,公众号:bin的技术小屋 前文回顾 在前边的系列文章中,我们从内核如何收发网络数据开始以一个C10K的问题作为主线详细从内核角度阐述了网 ...

  8. XXXX系统测试计划

    XXXX系统测试计划 目录 XXXX系统测试计划 目标 概述 项目背景 适用范围 组织形式 组织架构图 角色及职责 测试工作分工 团队协作 测试对象 应测试特性 不被测试特性 测试任务安排 系统测试任 ...

  9. @RequestMapping注解的属性,将请求约束精细化

    package com.atguigu.controller; import org.springframework.stereotype.Controller; import org.springf ...

  10. SDK导入问题 __imp_与__imp__

    目前刚刚实习一周,接触的第一个项目是CMake编译的QT项目,需要引入公司的SDK,编译能过去但是程序就是找不到SDK的接口, 排查了半天发现问题在于:公司的SDK是32位的,自己项目的build k ...