共计 17750 个字符,预计需要花费 45 分钟才能阅读完成。
💡 作者:韩信子 @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] 音乐流派辨认的机器学习零碎搭建与调优](https://www.showmeai.tech/art…)『Spotify 音乐数据集』
⭐ ShowMeAI 官网 GitHub:https://github.com/ShowMeAI-Hub
咱们在本篇内容中将用到最罕用的 boosting 集成工具库 LightGBM,并且将联合 optuna 工具库对其进行超参数调优,优化模型成果。
对于 LightGBM 的模型原理和应用具体解说,欢送大家查阅 ShowMeAI 的文章:
📘图解机器学习算法(11) | LightGBM 模型详解
📘机器学习实战(5) | LightGBM 建模利用详解
本篇文章蕴含以下内容板块:
- 数据概览和预处理
- EDA 探索性数据分析
- 歌词特色 & 数据降维
- 建模和超参数优化
- 总结 & 教训
💡 数据概览和预处理
本次应用的数据集蕴含超过 18000 首歌曲的信息,包含其音频特色信息(如活力度,播放速度或调性等),以及歌曲的歌词。
咱们读取数据并做一个速览如下:
import pandas as pd
# 读取数据
data = pd.read_csv("spotify_songs.csv")
# 数据速览
data.head()
# 数据根本信息
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 | 歌词的语言语种 |
原始的数据有点芜杂,咱们先进行过滤和数据荡涤。
# 数据工具库
import pandas as pd
import re
# 歌词解决的 nlp 工具库
import nltk
from nltk.corpus import stopwords
from collections import Counter
# nltk.download('stopwords')
# 读取数据
data = pd.read_csv("spotify_songs.csv")
# 字段抉择
keep_cols = [x for x in data.columns if not x.startswith("track") and not x.startswith("playlist")]
keep_cols.append("playlist_genre")
df = data[keep_cols].copy()
# 只保留英文歌曲
subdf = df[(df.language == "en") & (df.playlist_genre != "latin")].copy().drop(columns = "language")
# 歌词规整化,全副小写
pattern = r"[^a-zA-Z]"
subdf.lyrics = subdf.lyrics.apply(lambda x: re.sub(pattern, "", x.lower()))
# 移除停用词
subdf.lyrics = subdf.lyrics.apply(lambda x: ''.join([word for word in x.split() if word not in (stopwords.words("english"))]))
# 查看歌词中的词汇呈现的频次
# 连贯所有歌词
all_text = " ".join(subdf.lyrics)
# 统计词频
word_count = Counter(all_text.split())
# 如果一个词在 200 首以上的歌里都呈现,则保留,否则视作低频过滤掉
keep_words = [k for k, v in word_count.items() if v > 200]
# 构建一个正本
lyricdf = subdf.copy().reset_index(drop=True)
# 字段名称规范化
lyricdf.columns = ["audio_"+ x if not x in ["lyrics", "playlist_genre"] else x for x in lyricdf.columns]
# 歌词内容
lyricdf.lyrics = lyricdf.lyrics.apply(lambda x: Counter([word for word in x.split() if word in keep_words]))
# 构建词汇词频 Dataframe
unpacked_lyrics = pd.DataFrame.from_records(lyricdf.lyrics).add_prefix("lyrics_")
# 缺失填充为 0
unpacked_lyrics = unpacked_lyrics.fillna(0)
# 拼接并删除原始歌词列
lyricdf = pd.concat([lyricdf, unpacked_lyrics], axis = 1).drop(columns = "lyrics")
# 排序
reordered_cols = [col for col in lyricdf.columns if not col.startswith("lyrics_")] + sorted([col for col in lyricdf.columns if col.startswith("lyrics_")])
lyricdf = lyricdf[reordered_cols]
# 存储为新的 csv 文件
lyricdf.to_csv("music_data.csv", index = False)
次要的数据预处理在上述代码的正文里大家能够看到,外围步骤概述如下:
- 过滤数据以仅蕴含英语歌曲并删除“拉丁”类型的歌曲(因为这些歌曲简直齐全是西班牙语,所以会产生重大的类不均衡)。
- 通过将歌词设为小写、删除标点符号和停用词来整顿歌词。计算每个残余单词在歌曲歌词中呈现的次数,而后过滤掉所有歌曲中呈现频率最低的单词(凌乱的数据 / 乐音)。
- 清理与排序。
💡 EDA 探索性数据分析
和过往所有的我的项目一样,咱们也须要先对数据做一些剖析和更进一步的了解,也就是 EDA 探索性数据分析过程。
EDA 数据分析局部波及的工具库,大家能够参考 ShowMeAI 制作的工具库速查表和教程进行学习和疾速应用。
📘数据迷信工具库速查表 | Pandas 速查表
📘 图解数据分析:从入门到精通系列教程
首先咱们检查一下咱们的标签(流派)的 类散布和均衡。
# 分组统计
by_genre = data.groupby("playlist_genre")["audio_key"].count().reset_index()
fig, ax = plt.subplots()
# 绘图
ax.bar(by_genre.playlist_genre, by_genre.audio_key)
ax.set_ylabel("Number of Observations")
ax.set_xlabel("Genre")
ax.set_title("Observations per Class")
ax.set_ylim(0, 4000)
# 每个柱子上标注数量
rects = ax.patches
for rect in rects:
height = rect.get_height()
ax.text(rect.get_x() + rect.get_width() / 2, height + 5, height, ha="center", va="bottom")
存在轻微的 类别不均衡 ,那后续咱们在穿插验证和训练测试拆分时候留神 数据分层(放弃比例散布) 即可。
# 把所有字段切分为音频和歌词列
audio = data[[x for x in data.columns if x.startswith("audio")]]
lyric = data[[x for x in data.columns if x.startswith("lyric")]]
# 让字段命名更简略一些
audio.columns = audio.columns.str.replace("audio_", "")
lyric.columns = lyric.columns.str.replace("lyric_", "")
💡 歌词特色 & 数据降维
咱们的机器学习算法在解决高维数据的时候,可能会有一些性能问题,有时候咱们会对数据进行降维解决。
降维的实质是将高维数据投影到低维子空间中,同时尽可能多地保留数据中的信息。对于降维大家能够查看 ShowMeAI 的算法原理解说文章 📘图解机器学习 | 降维算法详解)
咱们摸索一降落维算法(PCA 和 t-SNE)在咱们的歌词数据上降维是否适合,并做一点调整。
📌 PCA 主成分剖析
PCA 是最罕用的降维算法之一,咱们借助这个算法能够对数据进行降维,并且看到它保留大略多少的原始信息量。例如,在咱们以后场景中,如果 将歌词缩小到 400 维 ,咱们依然 保留了歌词中 60% 的信息(方差);如果降维到 800 维,则能够笼罩 80% 的原始信息(方差)。歌词自身是很稠密的,咱们对其降维也能让模型更好地建模。
# 惯例数据工具库
import pandas as pd
import numpy as np
# 绘图
import matplotlib.pyplot as plt
import matplotlib.ticker as mtick
# 数据处理
from sklearn.preprocessing import MinMaxScaler
from sklearn.decomposition import PCA
# 读取数据
data = pd.read_csv("music_data.csv")
# 切分为音频与歌词
audio = data[[x for x in data.columns if x.startswith("audio")]]
lyric = data[[x for x in data.columns if x.startswith("lyric")]]
# 特色字段
y = data.playlist_genre
# 数据幅度缩放 + PCA 降维
scaler = MinMaxScaler()
audio_features = scaler.fit_transform(audio)
lyric_features = scaler.fit_transform(lyric)
pca = PCA()
lyric_pca = pca.fit_transform(lyric_features)
var_explained_ratio = pca.explained_variance_ratio_
# Plot graph
fig, ax = plt.subplots()
# Reduce margins
plt.margins(x=0.01)
# Get cumuluative sum of variance explained
cum_var_explained = np.cumsum(var_explained_ratio)
# Plot cumulative sum
ax.fill_between(range(len(cum_var_explained)), cum_var_explained,
alpha = 0.4, color = "tab:orange",
label = "Cum. Var.")
ax.set_ylim(0, 1)
# Plot actual proportions
ax2 = ax.twinx()
ax2.plot(range(len(var_explained_ratio)), var_explained_ratio,
alpha = 1, color = "tab:blue", lw = 4, ls = "--",
label = "Var per PC")
ax2.set_ylim(0, 0.005)
# Add lines to indicate where good values of components may be
ax.hlines(0.6, 0, var_explained_ratio.shape[0], color = "tab:green", lw = 3, alpha = 0.6, ls=":")
ax.hlines(0.8, 0, var_explained_ratio.shape[0], color = "tab:green", lw = 3, alpha = 0.6, ls=":")
# Plot both legends together
lines, labels = ax.get_legend_handles_labels()
lines2, labels2 = ax2.get_legend_handles_labels()
ax2.legend(lines + lines2, labels + labels2)
# Format axis as percentages
ax.yaxis.set_major_formatter(mtick.PercentFormatter(1))
ax2.yaxis.set_major_formatter(mtick.PercentFormatter(1))
# Add titles and labels
ax.set_ylabel("Cum. Prop. of Variance Explained")
ax2.set_ylabel("Prop. of Variance Explained per PC", rotation = 270, labelpad=30)
ax.set_title("Variance Explained by Number of Principal Components")
ax.set_xlabel("Number of Principal Components")
📌 t-SNE 可视化
咱们还能够更进一步,可视化数据在一系列降维过程中的可分离性。t-SNE 算法是一个十分无效的非线性降维可视化办法,借助于它,咱们能够把数据绘制在二维立体察看其扩散水平。上面的 t -SNE 可视化展现了当咱们应用所有 1806 个特色或将其缩小为 1000、500、100 个主成分时,如果将歌词数据投影到二维空间中会是什么样子。
代码如下:
from sklearn.manifold import TSNE
import seaborn as sns
# Merge numeric labels with normalised audio data and lyric principal components
tsne_processed = pd.concat([pd.Series(y, name = "genre"),
pd.DataFrame(audio_features, columns=audio.columns),
# Add prefix to make selecting pcs easier later on
pd.DataFrame(lyric_pca).add_prefix("lyrics_pc_")
], axis = 1)
# Get t-SNE values for a range of principal component cutoffs, 1806 is all PCs
all_tsne = pd.DataFrame()
for cutoff in ["1806", "1000", "500", "100"]:
# Create t-SNE object
tsne = TSNE(init = "random", learning_rate = "auto")
# Fit on normalised features (excluding the y/label column)
tsne_results = tsne.fit_transform(tsne_processed.loc[:, "audio_danceability":f"lyrics_pc_{cutoff}"])
# neater graph
if cutoff == "1806":
cutoff = "All 1806"
# Get results
tsne_df = pd.DataFrame({"y":y,
"tsne-2d-one":tsne_results[:,0],
"tsne-2d-two":tsne_results[:,1],
"Cutoff":cutoff})
# Store results
all_tsne = pd.concat([all_tsne, tsne_df], axis = 0)
# Plot gridplot
g = sns.FacetGrid(all_tsne, col="Cutoff", hue = "y",
col_wrap = 2, height = 6,
palette=sns.color_palette("hls", 4),
)
# Add plots
g.map(sns.scatterplot, "tsne-2d-one", "tsne-2d-two", alpha = 0.3)
# Add titles/legends
g.fig.suptitle("t-SNE Plots vs Number of Principal Components Included", y = 1)
g.add_legend()
现实状况下,咱们心愿看到的是,在降维到某些主成分数量(例如 cutoff = 1000)时,流派变得更加可拆散。
然而,上述 t-SNE 图的结果显示,PCA 这一步不同数量的主成分并没有哪个会让数据标签更可拆散。
📌 自编码器降维
实际上咱们有不同的形式能够实现数据降维工作,在上面的代码中,咱们提供了 PCA、截断 SVD 和 Keras 自编码器三种形式作为候选,调整配置即可进行抉择。
为简洁起见,主动编码器的代码已被省略,但能够在 autoencode
内的性能 custom_functions.py
中的文件库。
# 通用库
import pandas as pd
import numpy as np
# 建模库
from sklearn.model_selection import train_test_split
from sklearn.decomposition import PCA, TruncatedSVD
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
# 神经网络
from keras.layers import Dense, Input, LeakyReLU, BatchNormalization
from keras.callbacks import EarlyStopping
from keras import Model
# 定义自编码器
def autoencode(lyric_tr, n_components):
"""Build, compile and fit an autoencoder for
lyric data using Keras. Uses a batch normalised,
undercomplete encoder with leaky ReLU activations.
It will take a while to train.
--------------------------------------------------
lyric_tr = df of lyric training data
n_components = int, number of output dimensions
from encoder
"""
n_inputs = lyric_tr.shape[1]
# 定义 encoder
visible = Input(shape=(n_inputs,))
# encoder 模块 1
e = Dense(n_inputs*2)(visible)
e = BatchNormalization()(e)
e = LeakyReLU()(e)
# encoder 模块 2
e = Dense(n_inputs)(e)
e = BatchNormalization()(e)
e = LeakyReLU()(e)
bottleneck = Dense(n_components)(e)
# decoder 模块 1
d = Dense(n_inputs)(bottleneck)
d = BatchNormalization()(d)
d = LeakyReLU()(d)
# decoder 模块 2
d = Dense(n_inputs*2)(d)
d = BatchNormalization()(d)
d = LeakyReLU()(d)
# 输入层
output = Dense(n_inputs, activation='linear')(d)
# 残缺的 autoencoder 模型
model = Model(inputs=visible, outputs=output)
# 编译
model.compile(optimizer='adam', loss='mse')
# 回调函数
callbacks = EarlyStopping(patience = 20, restore_best_weights = True)
# 训练模型
model.fit(lyric_tr, lyric_tr, epochs=200,
batch_size=16, verbose=1, validation_split=0.2,
callbacks = callbacks)
# 在降维阶段,咱们只用 encoder 局部就能够(对数据进行压缩)
encoder = Model(inputs=visible, outputs=bottleneck)
return encoder
# 数据预处理函数,次要是对特色列进行降维,标签列进行编码
def pre_process(train = pd.DataFrame,
test = pd.DataFrame,
reduction_method = "pca",
n_components = 400):
# 切分 X 和 y
y_train = train.playlist_genre
y_test = test.playlist_genre
X_train = train.drop(columns = "playlist_genre")
X_test = test.drop(columns = "playlist_genre")
# 标签编码为数字
label_encoder = LabelEncoder()
label_train = label_encoder.fit_transform(y_train)
label_test = label_encoder.transform(y_test)
# 对数据进行幅度缩放解决
scaler = MinMaxScaler()
X_norm_tr = scaler.fit_transform(X_train)
X_norm_te = scaler.transform(X_test)
# 重建数据
X_norm_tr = pd.DataFrame(X_norm_tr, columns = X_train.columns)
X_norm_te = pd.DataFrame(X_norm_te, columns = X_test.columns)
# mode 和 key 都设定为类别型
X_norm_tr["audio_mode"] = X_train["audio_mode"].astype("category").reset_index(drop = True)
X_norm_tr["audio_key"] = X_train["audio_key"].astype("category").reset_index(drop = True)
X_norm_te["audio_mode"] = X_test["audio_mode"].astype("category").reset_index(drop = True)
X_norm_te["audio_key"] = X_test["audio_key"].astype("category").reset_index(drop = True)
# 歌词特色
lyric_tr = X_norm_tr.loc[:, "lyrics_aah":]
lyric_te = X_norm_te.loc[:, "lyrics_aah":]
# 如果应用 PCA 降维
if reduction_method == "pca":
pca = PCA(n_components)
# 拟合训练集
reduced_tr = pd.DataFrame(pca.fit_transform(lyric_tr)).add_prefix("lyrics_pca_")
# 对测试集变换(降维)reduced_te = pd.DataFrame(pca.transform(lyric_te)).add_prefix("lyrics_pca_")
# 如果应用 SVD 降维
if reduction_method == "svd":
svd = TruncatedSVD(n_components)
# 拟合训练集
reduced_tr = pd.DataFrame(svd.fit_transform(lyric_tr)).add_prefix("lyrics_svd_")
# 对测试集变换(降维)reduced_te = pd.DataFrame(svd.transform(lyric_te)).add_prefix("lyrics_svd_")
# 如果应用自编码器降维(留神,神经网络的训练工夫会长一点,要急躁期待)if reduction_method == "keras":
# 构建自编码器
encoder = autoencode(lyric_tr, n_components)
# 通过编码器局部进行数据降维
reduced_tr = pd.DataFrame(encoder.predict(lyric_tr)).add_prefix("lyrics_keras_")
reduced_te = pd.DataFrame(encoder.predict(lyric_te)).add_prefix("lyrics_keras_")
# 合并降维后的歌词特色与音频特色
X_norm_tr = pd.concat([X_norm_tr.loc[:, :"audio_duration_ms"],
reduced_tr
], axis = 1)
X_norm_te = pd.concat([X_norm_te.loc[:, :"audio_duration_ms"],
reduced_te
], axis = 1)
return X_norm_tr, label_train, X_norm_te, label_test, label_encoder
# 分层切分数据
train_raw, test_raw = train_test_split(data, test_size = 0.2,
shuffle = True, random_state = 42, # random, reproducible split
stratify = data.playlist_genre)
# 设定降维最终维度
n_components = 500
# 抉择降维办法,候选: "pca", "svd", "keras"
reduction_method = "pca"
# 残缺的数据预处理
X_train, y_train, X_test, y_test, label_encoder = pre_process(train_raw, test_raw,
reduction_method = reduction_method,
n_components = n_components)
上述过程之后咱们曾经实现对数据的标准化、编码转换和降维,接下来咱们应用它进行建模。
💡 建模和超参数优化
📌 构建模型
在理论建模之前,咱们要先 选定一个评估指标 来评估咱们模型的性能,也不便领导进一步的优化。因为咱们数据最终的标签『流派 / 类别』略有不均衡,宏观 F1 分数(macro f1-score) 可能是一个不错的抉择,因为它平等地评估了类别的奉献。咱们在上面对这个评估准则进行定义,也敲定 LightGBM 模型的局部超参数。
from sklearn.metrics import f1_score
# 定义评估准则(Macro F1)
def lgb_f1_score(preds, data):
labels = data.get_label()
preds = preds.reshape(5, -1).T
preds = preds.argmax(axis = 1)
f_score = f1_score(labels , preds, average = 'macro')
return 'f1_score', f_score, True
# 用于编译的参数
fixed_params = {
'objective': 'multiclass',
'metric': "None", # 咱们自定义的 f1-score 能够利用
'num_class': 5,
'verbosity': -1,
}
LightGBM 带有大量可调超参数,这些超参数对于最终成果影响很大。
对于 LightGBM 的超参数细节具体解说,欢送大家查阅 ShowMeAI 的文章:
📘机器学习实战(5) | LightGBM 建模利用详解
上面咱们会基于 Optuna 这个工具库对 LightGBM 的超参数进行调优,咱们须要在 param
定义超参数的搜寻空间,在此基础上 Optuna 会进行优化和超参数的抉择。
# 建模
from sklearn.model_selection import StratifiedKFold
import lightgbm as lgb
from optuna.integration import LightGBMPruningCallback
# 定义指标函数
def objective(trial, X, y):
# 候选超参数
param = {**fixed_params,
'boosting_type': 'gbdt',
'num_leaves': trial.suggest_int('num_leaves', 2, 3000, step = 20),
'feature_fraction': trial.suggest_float('feature_fraction', 0.2, 0.99, step = 0.05),
'bagging_fraction': trial.suggest_float('bagging_fraction', 0.2, 0.99, step = 0.05),
'bagging_freq': trial.suggest_int('bagging_freq', 1, 7),
'min_child_samples': trial.suggest_int('min_child_samples', 5, 100),
"n_estimators": trial.suggest_int("n_estimators", 200, 5000),
"learning_rate": trial.suggest_float("learning_rate", 0.01, 0.3),
"max_depth": trial.suggest_int("max_depth", 3, 12),
"min_data_in_leaf": trial.suggest_int("min_data_in_leaf", 5, 2000, step=5),
"lambda_l1": trial.suggest_float("lambda_l1", 1e-8, 10.0, log=True),
"lambda_l2": trial.suggest_float("lambda_l2", 1e-8, 10.0, log=True),
"min_gain_to_split": trial.suggest_float("min_gain_to_split", 0, 10),
"max_bin": trial.suggest_int("max_bin", 200, 300),
}
# 构建分层穿插验证
cv = StratifiedKFold(n_splits = 5, shuffle = True)
# 5 组得分
cv_scores = np.empty(5)
# 切分为 K 个数据组,轮流作为训练集和验证集进行试验
for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)):
# 数据切分
X_train_cv, X_test_cv = X.iloc[train_idx], X.iloc[test_idx]
y_train_cv, y_test_cv = y[train_idx], y[test_idx]
# 转为 lightgbm 的 Dataset 格局
train_data = lgb.Dataset(X_train_cv, label = y_train_cv, categorical_feature="auto")
val_data = lgb.Dataset(X_test_cv, label = y_test_cv, categorical_feature="auto",
reference = train_data)
# 回调函数
callbacks = [LightGBMPruningCallback(trial, metric = "f1_score"),
# 间歇输入信息
lgb.log_evaluation(period = 100),
# 早进行,避免过拟合
lgb.early_stopping(50)]
# 训练模型
model = lgb.train(params = param, train_set = train_data,
valid_sets = val_data,
callbacks = callbacks,
feval = lgb_f1_score # 自定义评估准则
)
# 预估
preds = np.argmax(model.predict(X_test_cv), axis = 1)
# 计算 f1-score
cv_scores[idx] = f1_score(y_test_cv, preds, average = "macro")
return np.mean(cv_scores)
📌 超参数优化
咱们在下面定义完了指标函数,当初能够应用 Optuna 来调优模型的超参数了。
# 超参数优化
import optuna
# 定义 Optuna 的试验次数
n_trials = 200
# 构建 Optuna study 去进行超参数检索与调优
study = optuna.create_study(direction = "maximize", # 最大化穿插验证的 F1 得分
study_name = "LGBM Classifier",
pruner=optuna.pruners.HyperbandPruner())
func = lambda trial: objective(trial, X_train, y_train)
study.optimize(func, n_trials = n_trials)
而后,咱们能够应用 📘Optuna 的可视化模块 对 不同超参数组合的性能 进行可视化查看。例如,咱们能够应用 plot_param_importances(study)
查看哪些超参数对模型性能 / 影响优化最重要。
plot_param_importances(study)
咱们也能够应用 plot_parallel_coordinate(study)
查看尝试了哪些超参数组合 / 范畴能够带来高评估后果值(好的成果性能)。
plot_parallel_coordinate(study)
而后咱们能够应用 plot_optimization_history
查看历史状况。
plot_optimization_history(study)
在 Optuna 实现调优之后:
- 最好的超参数存储在
study.best_params
属性中。咱们把模型的最终参数params
定义为params = {**fixed_params, **study.best_params}
即可,如后续的代码所示。 - 当然,你也能够放大搜寻空间 / 超参数范畴,进一步做准确的超参数优化。
# 最佳模型试验
cv = StratifiedKFold(n_splits = 5, shuffle = True)
# 5 组得分
cv_scores = np.empty(5)
# 切分为 K 个数据组,轮流作为训练集和验证集进行试验
for idx, (train_idx, test_idx) in enumerate(cv.split(X, y)):
# 数据切分
X_train_cv, X_test_cv = X.iloc[train_idx], X.iloc[test_idx]
y_train_cv, y_test_cv = y[train_idx], y[test_idx]
# 转为 lightgbm 的 Dataset 格局
train_data = lgb.Dataset(X_train_cv, label = y_train_cv, categorical_feature="auto")
val_data = lgb.Dataset(X_test_cv, label = y_test_cv, categorical_feature="auto",
reference = train_data)
# 回调函数
callbacks = [LightGBMPruningCallback(trial, metric = "f1_score"),
# 间歇输入信息
lgb.log_evaluation(period = 100),
# 早进行,避免过拟合
lgb.early_stopping(50)]
# 训练模型
model = lgb.train(params = {**fixed_params, **study.best_params}, train_set = train_data,
valid_sets = val_data,
callbacks = callbacks,
feval = lgb_f1_score # 自定义评估准则
)
# 预估
preds = np.argmax(model.predict(X_test_cv), axis = 1)
# 计算 f1-score
cv_scores[idx] = f1_score(y_test_cv, preds, average = "macro")
💡 最终评估
通过上述过程咱们就取得了最终模型,让咱们来评估一下吧!
# 预估与评估训练集
train_preds = model.predict(X_train)
train_predictions = np.argmax(train_preds, axis = 1)
train_error = f1_score(y_train, train_predictions, average = "macro")
# 穿插验证后果
cv_error = np.mean(cv_scores)
# 评估测试集
test_preds = model.predict(X_test)
test_predictions = np.argmax(test_preds, axis = 1)
test_error = f1_score(y_test, test_predictions, average = "macro")
# 存储评估后果
results = pd.DataFrame({"n_components": n_components,
"reduction_method": reduction_method,
"train_error": train_error,
"cv_error": cv_error,
"test_error": test_error,
"n_trials": n_trials
}, index = [0])
咱们能够试验和比拟不同的降维办法、降维维度,再调参查看模型成果。如下图所示,在咱们以后的尝试中,PCA 降维到 400 维 产出最好的模型 ——macro f1-score 为 66.48%。
💡 总结
在本篇内容中,ShowMeAI 展现了基于歌曲信息与文本对其进行『流派』分类的过程,蕴含对文本数据的解决、特色工程、模型建模和超参数优化等。大家能够把整个 pipeline 作为一个模板来利用在其余工作当中。
参考资料
- 📘 图解数据分析:从入门到精通系列教程:https://www.showmeai.tech/tutorials/3
- 📘 数据迷信工具库速查表 | Pandas 速查表:https://www.showmeai.tech/article-detail/101
- 📘 图解机器学习算法 | 降维算法详解:https://www.showmeai.tech/article-detail/198
- 📘 图解机器学习算法 | LightGBM 模型详解:https://www.showmeai.tech/article-detail/195
- 📘 机器学习实战 | LightGBM 建模利用详解:https://www.showmeai.tech/article-detail/205
- 📘 Optuna 的可视化模块
- 📘 Akiba,T., Sano, S., Yanase, T., Ohta, T., & Koyama, M. (2019, July). Optuna: A next-generation hyperparameter optimization framework. In Proceedings of the 25th ACM SIGKDD international conference on knowledge discovery & data mining (pp. 2623–2631).
- 📘 Autoencoder Feature Extractions
- 📘 Kaggler’s Guide to LightGBM Hyperparameter Tuning with Optuna in 2021
- 📘 You Are Missing Out on LightGBM. It Crushes XGBoost in Every Aspect