终于,我们来到了构建第一个机器学习模型的阶段。
在开始前,需要注意几点。
我们要在 IDE 里开发而不是 jupyter notebook,当然你也可以在 notebook 里做,这完全取决于你。我只会在数据探索 data exploration 和绘制图表时使用 notebook。
我们接下来会构建一个分类框架,因为分类任务时最常见的问题。完成构建之后,你可以在不怎么改变代码的情况训练新模型,或者优化模型。
首先看一下工程文件结构,先创建工程目录,假设命名为 project 。
在 project 文件夹下,你将会看到如下结构。
下面介绍其中各个文件夹和文件都是什么。
-
input/: 包含所有机器学习任务的输入数据文件,如果是 NLP 工程,这里可以保存词嵌入;如果是图像工程,这里可以保存所有图片。
-
src/: 包含所有 python 脚本文件,即 *.py 文件。
-
models/: 包含所有训练得到的模型。
-
notebooks/: 包含所有 notebook 文件,即 *.ipynb 文件。
-
README.md: md 格式的工程介绍,介绍中应写明模型该如何训练和使用。
-
LICENSE: 声明该工程遵循何种许可。
根据数据分布选择度量指标
接下来,假设我们要在 MNIST 数据集上构建分类模型。
首先载入数据集,使用 Pandas 读取数据。
然后检查目标值的分布,代码如下。
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import seaborn as sns
from sklearn import datasets, manifold
data = datasets.fetch_openml(
'mnist_784',
version=1,
return_X_y=True
)
pixel_values, targets = data
targets = targets.astype(int)
target_data = pd.DataFrame(targets)
绘制的统计图如下。
从图中可清晰地看到,各个目标类型的分布非常平均,因此我们可以使用 accuracy 或者 F1 作为模型度量指标。
这是处理机器学习任务的第一步:选择度量指标。
创建 Folds
接下来在 src/ 文件夹下新建 create_folds.py,在其中编写创建 folds 的逻辑,最终将创建好的 folds 保存在 input/mnist_train_folds.csv 中。
create_folds.py 代码如下。
# create_folds.py
import pandas as pd
from sklearn import datasets, model_selection
if __name__ == '__main__':
data = datasets.fetch_openml(
'mnist_784',
version=1,
return_X_y=True
)
pixel_values, targets = data
targets = targets.astype(int)
temp = pd.DataFrame(pixel_values)
temp.loc[:, 'target'] = targets
temp['kfold'] = -1
df = temp.sample(frac=1).reset_index(drop=True)
y = df.target.values
kf = model_selection.StratifiedKFold(n_splits=5)
for fold, (trn_, val_) in enumerate(kf.split(X=df, y=y)):
df.loc[val_, 'kfold'] = fold
df.to_csv('../input/mnist_train_folds.csv', index=False)
执行 python create_folds.py 可输出 folds 文件。
训练模型
当我们选择完评估指标,并创建好 folds 之后,接下来可以进行模型训练的工作了。
在 src/ 文件夹下创建 train.py ,代码如下。
# train.py
import joblib
import pandas as pd
from sklearn import tree, metrics
def run(fold):
df = pd.read_csv('../input/mnist_train_folds.csv')
df_train = df[df.kfold != fold].reset_index(drop=True)
df_valid = df[df.kfold == fold].reset_index(drop=True)
train_x = df_train.drop('target', axis=1).values
train_y = df_train.target.values
valid_x = df_valid.drop('target', axis=1).values
valid_y = df_valid.target.values
clf = tree.DecisionTreeClassifier()
clf.fit(train_x, train_y)
preds = clf.predict(valid_x)
accuracy = metrics.accuracy_score(valid_y, preds)
print(f'Fold: {fold}, Accuracy: {accuracy}')
joblib.dump(clf, f'../models/dt_{fold}.bin')
if __name__ == '__main__':
for i in range(5):
run(i)
执行 python train.py 即可开始训练模型,并保存模型。
现在回过头来查看模型训练代码,你会发现这里有一些硬编码,例如 fold 数量、训练数据读取路径、模型输出路径。
因此需要一个陪着文件 config.py 来管理这些硬编码的配置参数,代码如下。
# config.py
FOLD_NUM = 5
TRAINING_FILE = '../input/mnist_train_folds.csv'
MODEL_OUTPUT = '../models/'
然后修改上述文件中对应的硬编码部分。
现在训练脚本仍有可优化的部分,比如我们对每个 fold 都调用了 run 方法,有时并不需要在同一个脚本中训练这么多次模型,这会导致内存消耗增多,有可能导致程序崩溃。
为了解决该问题,我们可以通过 argparse 来接受外部参数。
代码修改如下。
# train.py
import argparse
if __name__ == '__main__':
parse = argparse.ArgumentParser()
parse.add_argument(
'--fold',
type=int
)
args = parse.parse_args()
run(args.fold)
执行 python train.py --fold 0 可以只在第 0 个 fold 上进行模型训练。
现在训练脚本已经提升不少了,但是还是有改进的地方,比如模型的选择。
当前模型是被硬编码在训练脚本内的,我们想要从外部方便地修改要训练的模型类别。
接下来创建 model_dispatcher.py 来处理模型的选择相关逻辑。
# model_dispatcher.py
from sklearn import tree
models = {
'decision_tree_gini': tree.DecisionTreeClassifier(
criterion='gini'
),
'decision_tree_entropy': tree.DecisionTreeClassifier(
criterion='entropy'
)
}
该文件定义了一个模型选择字典,根据传入的 key 来选择不同的模型。
修改 train.py ,使用上述模型选择字典。
import model_dispatcher
def run(fold, model_key):
...
clf = model_dispatcher.models['model_key']
...
if __name__ == '__main__':
...
parse.add_argument(
'--model',
type=str
)
args = parse.parse_args()
run(args.fold, args.model)
执行 python train.py --fold 0 --model dicision_tree_gini 上述描述的基础上,选择模型。
之后,再添加模型时,只需要修改 model_dispatcher.py 文件内的模型选择字典,其他都不用变。
MNIST 问题在各个书籍或博客里都有讨论,我尝试把该问题讲的更有趣一点,并给你展示了如何写一个基础的机器学习工程框架。
在代码里,导入 model_dispatcher.py 和 config.py 时,我没有用 import * 的写法,你也不应该使用这种写法,这会导致一些问题,比如我们不能直观的看到 models 字典是哪个文件定义的,或者有可能出现命名冲突。
写优秀、已读的代码是我们的必备素质,有许多数据科学家忽视了这一点。当我们和其他人合作或分享代码时,这个素质可以帮我们和他人节省大量时间。