原文:
towardsdatascience.com/every-step-of-the-machine-learning-life-cycle-simply-explained-d1bca7c1772f
机器学习生命周期的每一步都简单解释
机器学习生命周期。图片由作者提供
机器学习生命周期
如果你曾在数据科学领域工作过一段时间,你很可能已经听说过这个热门术语。
机器学习生命周期。
虽然听起来很复杂,但这实际上就是它的本质:
-
机器学习是一个活跃且动态的过程——它没有严格的开始或结束。
-
一旦模型被训练并部署,随着时间的推移,它很可能会需要重新训练,从而重新启动循环。
-
然而,在循环中存在一些步骤,需要按照正确的顺序执行并谨慎操作。
当你在谷歌上搜索机器学习生命周期时,每个来源可能会给出不同数量的步骤和它们的名称。
然而,你会注意到,大多数情况下,这个循环包含:问题定义、数据收集和预处理、特征工程、模型选择和训练、模型评估、部署和监控。
1. 定义问题
你正在尝试解决什么问题或回答什么问题?你需要机器学习还是可以使用更简单的方法(例如统计方法)?
为了本文的目的,我将遵循一个我之前展示过很多次的标准示例:每小时能源使用预测。
-
问题: 客户希望能够预测一天的每小时消费,以便识别出哪些小时和星期几的消费似乎最高。他们将利用这些信息来寻找在负载较高的日子和时间减少使用的方法。
-
目标: 预测未来 24 小时的电力能源消耗
-
所需方法: 需要机器学习来完成这项任务以提高准确性。一个简单的模型,如移动平均模型,不会考虑诸如一天中的小时和星期几等重要特征,并且无法展示这些特征与目标变量之间的关系。
-
所需数据: 带有适当时间戳的小时消费数据
-
所需数据量: 至少 1 年的历史数据(我们将要检查的数据集有 10 年的历史数据,这很好)
-
可解释性: 我们需要一个模型,至少能够解释哪些特征对预测贡献最大。
-
指标: 我们希望在测试集上实现 10%或更低的 MAPE(平均绝对百分比误差)。我们还希望查看 RMSE 等指标,并将其用于模型优化和调整。
2. 数据收集
我将要使用的数据集来自 Kaggle (CC0 公共领域许可证)。这是一个包含电力数据的每小时能耗数据集。它包含日期/时间戳以及兆瓦(MW)的电力消耗。
在 Python 中,你需要做的第一件事是将你的数据加载到一个 DataFrame 中。我从 Kaggle 下载了数据集作为 CSV 文件,并将其加载到我的 Python 笔记本脚本中:
import pandas as pd
df = pd.read_csv("AEP_hourly.csv")
调用 df.head() 将显示 DataFrame 的前 5 行。调用这个命令检查数据的整体结构并查看你拥有的列是个好主意。
下一步是执行 EDA(探索性数据分析)。EDA 包括:
-
通过描述性统计和值计数进行数据的数值探索
-
通过图表和图表进行数据的视觉探索
-
基于探索进行一般性初步推断
这里有一些简单的单行命令,你可以使用它们来启动 EDA 过程:
# Tells you the name and number of columns, the total number of rows,
# column data types, and the number of non-null/null values in the df
df.info()
这是该数据集的输出:
作者图片
# Provides you with a dataframe containing descriptive statistics
# such as the mean, standard deviation, and min/max values.
df.describe()
作者图片
另一个有用的命令是 df.value_counts()。它计算你列中唯一值的数量,并告诉你 DataFrame 中每种值有多少(每列)。
由于我们处理的数据不是分类数据,因此这对这个数据集来说并不那么重要。
视觉 EDA 通常涉及以各种方式在视觉上检查数据。
对于像这样一个时间序列数据集,最简单的地方之一就是简单地以散点图或折线图的形式绘制数据。
import plotly.express as px
px.line(df, x="Datetime", y="AEP_MW")
这产生了以下输出:
作者图片
你可以创建的其他常见可视化,用于 EDA 目的:
-
特征与目标之间的相关性图
-
特征与其它特征之间的相关性图
-
带有回归线和 R² 的线性回归散点图
-
时间序列数据的趋势和季节性
-
时间序列数据的自相关图
你产生的 EDA 图表的类型取决于你处理的数据类型,所以它真的很变化。
作为一般规则,你想要寻找可能影响你稍后模型中包含哪些特征的数据趋势。
3. 数据清洗和预处理
我立刻注意到的是,我生成的折线图中有随机线条横穿屏幕,这意味着一些时间戳是乱序的。
作者图片
为了解决这个问题,我将日期时间列转换为 pd.datetime 对象,这样我就可以调用 pandas 特定的函数。然后我对该列进行排序。
df["Datetime"] = pd.to_datetime(df["Datetime"])
# Set index to datetime so you can call sort_index function
df.set_index('Datetime',inplace=True)
df.sort_index(inplace=True)
# Reset the index so datetime is a regular column again
df.reset_index(inplace=True)
现在图表看起来好多了:
作者图片
另一个需要检查的重要事项是缺失或空值。在时间序列数据的情况下,你还应该检查 0 值,并调查它们是否是有效的条目或表示缺失数据。
然后,你可以决定是否需要删除缺失值或用数据集的中位数或平均值来填充它们。
对于对时间序列数据集中处理缺失数据的各种方法进行更彻底的探索,请参阅以下文章:
处理异常值是下一步。
异常值必须首先被检测到,然后要么删除要么填充,就像缺失/空值一样。
识别异常值的一个非常简单的方法是z 分数,它告诉你每个数据点离平均值有多远。如果一个数据点的 z 分数大于 3 或小于-3(意味着数据点比平均值高/低 3 个标准差),它就被认为是异常值。然而,你可以根据自己的判断和对数据的分析来调整这个阈值。
from scipy import stats
# Create a separate z-score column
df["z_score"]=stats.zscore(df["AEP_MW"])
# Once you have this z-score column, you can filter out
# columns with a z-score > 3 or <-3
df = df[(df["z_score"]>3) | (df["z_score"]<-3)]]
# Drop z_score column from df since it is not a valid feature
df.drop("z_score",axis=1,inplace=True)
在这篇文章中,我探讨了更多用于异常值检测的统计方法:
4. 特征工程
下一步是选择你的特征,为模型消费准备和优化它们,将它们分成训练/测试框架,并在必要时进行缩放。
特征工程本质上是选择和操作特征的过程,目的是从它们中提取尽可能多的相关信息以供模型使用。
特征选择可以是手动或算法性的。对于这个问题,由于原始 DataFrame 只包含 1 个特征列(时间戳列),我们可以从这个时间戳创建的特征数量是有限的。
我们不能将时间戳输入到机器学习模型中,因为它不知道如何读取/处理这些信息。因此,我们需要提取时间序列特征(如小时、天数、周数)并将它们编码为数值,以便模型可以理解它们。
对于一个按小时的时间戳列,我们可以提取:
-
天中的小时
-
周中的小时
-
年中的小时
-
周中的日子
-
月份中的日子
-
年中的日子
-
月中的周
-
年中的周
-
年中的月份
-
年份
当然,其中一些特征可能会相互重叠。我们不需要周中的小时和天中的小时以及周中的日子。天中的小时和周中的日子或者仅仅是周中的小时可能就足够了,你可以尝试这两种组合,看看哪种能带来最佳性能。
我们可以将这些特征提取为布尔值(使用如独热编码等方法)或也可以将它们编码为循环时间序列特征,使用正弦和余弦函数:
当涉及到像“星期几”这样的时间序列特征时,将日期时间列转换为如 1,2,3,…7 这样的数值也不会与时间序列 ML 模型很好地工作,因为从技术上讲,这些是分类特征,而不是数值特征,即使我们选择用数字来表示它们。
因此我们需要使用一种称为一维编码的方法,它本质上使用多个列作为指示符将分类值转换为布尔值。
为了本文的目的,为了使事情简单,我将向你展示如何将时间戳列转换为一维编码的时间序列特征:
# I selected hour, month, and day of week to start.
# The code below transforms the datetime column into numerical columns.
# The same process applies to other features, depending on if the
# .dt. has that feature (eg dt.year is a thing, but dt.hourofmonth is not)
# If you want hourofmonth, you'll have to calculate it yourself
df['Hour']=df['Datetime'].dt.hour
df['Month']=df['Datetime'].dt.month
df['Dayofweek']=df['Datetime'].dt.dayofweek
# Use pd.get_dummies to transform numerical columns into dummy variables
# (boolean values for each category)
columns_to_encode = ['Hour', 'Month', 'Dayofweek']
df = pd.get_dummies(df, columns=columns_to_encode,dtype=int)
一维编码前的特征。图片由作者提供
一维编码后的特征。图片由作者提供
一些模型要求你对任何数值特征进行缩放。这些模型的例子包括线性回归、逻辑回归和神经网络。
由于我们模型中只使用分类特征,所以我们不需要缩放或标准化我们的特征。如果我们有额外的特征,例如温度,这是一个数值特征,根据我们选择的模型类型,我们可能需要缩放该列。
在缩放特征之前,重要的是你首先将数据分成相应的训练/测试集。为此,你可以使用 scikit-learn 的 train_test_split 方法,默认情况下将数据分成 75%的训练和 25%的测试,或者你可以手动分割数据。
重要的是要注意,scikit-learn 的 train/test split 会随机划分数据集,使得行不再有序。
这对于时间序列来说不是好的实践,因此我选择自己使用索引来分割数据集:
# Make the train set size 75% of the number of rows in the dataframe
train_size = int(df.shape[0] * 0.75)
# Define features
features = df.drop(["AEP_MW","Datetime"],axis=1).columns
# Split dataframe by train/test sets indices
df_train = df.iloc[0:train_size]
df_test = df.iloc[train_size:]
# Split dfs into separate arrays for features (X) and target (y)
X_train = df_train[features]
y_train = df_train["AEP_MW"]
X_test = df_test[features]
y_test = df_test["AEP_MW"]
想要深入了解数据缩放、归一化和标准化,请查看这篇文章:
5. 模型选择和训练
在训练最终更复杂的模型之前,总是好的实践先训练一个基线模型。一个基线模型通常是目标模型的一个更简单的版本(例如,如果你旨在训练一个随机森林模型,你的基线模型可以是一个决策树)。
基线模型有助于建立基线指标,如基础 MAPE、RMSE、MSE 等。
当时候你可以将这些指标与你的更高级模型进行比较,这有助于识别数据、特征或超参数的问题。
现在你需要为你的问题选择最佳的最终模型。
这将因领域、数据集、计算资源和目标而大相径庭。
对于这个例子,我会选择随机森林模型,因为它是一种更知名的全集模型,并且通常在时间序列数据上表现相对较好。
from sklearn.ensemble import RandomForestRegressor
rf = RandomForestRegressor()
rf.fit(X_train,y_train)
6. 模型评估和调整
一旦你训练了你的模型,你需要评估它的好坏。
评估你的模型主要有两种方法:通过交叉验证和测试集。
测试集已经与训练数据分开,所以我们将使用我们的训练模型来预测测试集。
然而,测试集只包含最后 25%的数据。我们还想了解模型在不同周、月、小时甚至年范围内的数据表现如何。
这就是交叉验证发挥作用的地方。
交叉验证使用整个训练集,并在数据集的较小部分上拟合多个模型,每个回合都有较小的“测试”集。
这些测试集在技术上被称为评估集。
交叉验证通常在分割的训练集上进行 5 次评估,然后平均准确率分数(RMSE、MSE、R2、MAPE 或你选择的另一个指标)以给出交叉验证分数。
这里有一篇文章会指导你如何使用时间序列数据进行交叉验证:
下一步是使用你的原始模型——即在整个数据集上训练的模型——来预测一个模型从未见过或预测过的保留测试集。
这为你提供了关于你的模型在未来的、未见过的数据上表现的最佳画面。
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_percentage_error
# Call .predict on test set, passing in features only X_test
predicted_test = rf.predict(X_test)
# Calculate the RMSE
rmse = mean_squared_error(y_test.values,predicted_test,squared=False)
print(rmse)
# The RMSE for our test set was 1799
# Calculate MAPE
mape = mean_absolute_percentage_error(y_test, predicted_test)
# Format MAPE into a percentage and print
print(f"MAPE: {mape * 100:.2f}%")
# Our initial MAPE was 10.36%, which is within range of
# what we were hoping for!
一旦你计算了交叉验证和测试集的指标,就是进行模型调整和优化的时间了。
首先,你想要确保你没有过拟合或欠拟合。
简单来说:
过拟合是指你的训练集/交叉验证准确率远高于测试集准确率。
欠拟合是相反的情况。如果你的模型欠拟合,训练集上的准确率会很低。这基本上意味着模型没有从训练集中正确学习到模式。
有几种方法可以处理过拟合和欠拟合。最知名的方法之一是超参数调整,即使你的模型一开始表现良好,这也是一种标准的技巧。
在我们的例子中,训练集的交叉验证分数为 1549,测试集分数为 1799。这些数字很大,因为我们通常在数据集中处理的是大数值(平均值:15,499)。测试集和 CV 分数之间没有巨大的差异,所以我对我们的模型过拟合或欠拟合并不太担心。
超参数调整是优化模型性能并使指标达到最佳可能值的最终微调步骤。
这里是一个最简单的超参数调整技术示例,称为网格搜索:
from sklearn.model_selection import GridSearchCV
# Define hyperparameters to tune
param_grid = {
'n_estimators': [100, 200, 300],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10]
}
# Define grid search cv object
grid_search = GridSearchCV(estimator=rf, param_grid=param_grid,
cv=5, n_jobs=-1, verbose=2,
scoring='neg_root_mean_squared_error')
# Fit the grid search object - this will likely take a long time
# if the dataset is large and if you have defined lots of hyperparameters
grid_search.fit(X_train, y_train)
# Print the best hyperparameters found by GridSearchCV
print("Best Hyperparameters: ", grid_search.best_params_)
网格搜索以其简单性而闻名,但速度较慢。随机搜索是一个更快的选项。贝叶斯优化是另一个更快且更智能的选项。
一旦根据网格搜索、随机搜索或贝叶斯搜索获得了最佳超参数,你应该获取新的测试集指标以确保它们确实提高了你的模型。
# Evaluate the best model on the test set
# Get the best model from grid search object
best_rf = grid_search.best_estimator_
# Predict test set metrics using the best model
y_pred = best_rf.predict(X_test)
# Get the RMSE for the best model
rmse_best_rf = mean_squared_error(y_test.values,y_pred,squared=False)
print(rmse_best_rf)
模型评估还可能包括检查模型的可解释性——在随机森林的情况下,查看特征重要性。
特征重要性告诉你哪些特征对模型的预测贡献最大。
# Getting feature importances for each feature for Random Forest model
feature_names = X_train.columns
feature_importances = rf.feature_importances_
# Create feature importance df with names and importance values
df_importance = pd.DataFrame({
'Feature': feature_names,
'Importance': feature_importances
})
# Sort the DataFrame by importance (descending order)
df_importance = df_importance.sort_values(by='Importance', ascending=False)
7. 模型部署
太棒了——你已经通过了数据收集、清洗、工程、模型训练、评估和调整,你现在有一个根据规划阶段设定的标准表现良好的训练模型。
那么,接下来会发生什么呢?
嗯,你需要让任何需要这个模型的人都能使用它,而不仅仅是让你和你的 Jupyter 笔记本可以使用。
模型部署本质上是将训练好的机器学习模型集成到生产环境中的过程,使其能够进行实时预测或支持决策制定。
部署涉及:
-
以可部署的格式(例如 pickle)保存模型。
-
设置环境,例如云平台(例如 AWS、GCP)和/或容器化工具(例如 Docker),以托管模型。
-
通过网络服务器或应用程序将模型暴露给用户。
模型部署是一个复杂的过程,它肯定有一个学习曲线。虽然我可能在未来的一篇文章中具体介绍它,但在这篇文章中我可能无法涵盖太多。
关于模型部署的更详细信息,请参阅这篇信息丰富的文章。
8. 模型监控
所以你已经部署了你的模型。
但工作远未结束。
随着新数据的到来,情况发生变化,指标得到更新,你必须准备好根据需要更新模型。
模型监控涉及跟踪机器学习模型的性能和行为,以确保它在生产环境中继续按预期表现。
随着时间的推移,模型可能会开始衰减——这意味着随着数据模式的变化,原始模型不再像以前那样能够很好地预测。
像平均绝对误差(MAE)或均方误差(MSE)这样的指标可以定期计算以检测衰减。
仪表板是监控数据模式随时间变化以及跟踪指标和标记异常的好方法。
可以设置警报来标记异常行为(例如持续的高估或低估预测)并触发自动或手动模型重新训练。
我在这里更深入地讨论了模型衰减和重新训练:
结论
注意到最后一步,监控,通常会以模型重新训练结束。这让你回到了起点。
定义新的问题和目标,收集新的数据(或者相同的数据,但可能意图以不同的方式构建),最终重新训练和重新部署模型。
因此,你可以看到机器学习并不是一次性的任务——它实际上是一个循环。
还需要注意的是,循环可以在过程的任何阶段重新开始,而不仅仅是结束时。
例如,如果在评估阶段发现你的模型存在过拟合,你可能需要再次收集数据,以不同的方式构建特征,并选择新的超参数,这样你就可以回到循环的步骤中,或者从头开始。
我要说的是,真正记住所有这些步骤的正确顺序并真正理解它们需要很长时间。
而我列出的每一步都有很多细微差别——它因模型类型、解决的问题等多种因素而大相径庭。
但最好的方式是继续通过重复练习和学习。
感谢阅读
在这里找到源代码和数据集 | 在LinkedIn上与我联系!
2499

被折叠的 条评论
为什么被折叠?



