但凡接触过机器学习的人对sklearn(scikit-learn)一定不陌生,它是基于Python语言的机器学习工具,是机器学习中常用的第三方模块,它对常用的机器学习方法进行了封装,包括回归、降维、分类、聚类等方法。
sklearn具有广泛的应用,其功能非常强大,但此处不会做过多展开,如果读者希望进一步了解sklearn,这里提供sklearn的中文官网供参考:Introduction·sklearn。
auto-sklearn 提供了开箱即用的监督型自动机器学习。从名字可以看出,auto-sklearn 是基于机器学习库 scikit-learn 构建的,可为新的数据集自动搜索学习算法,并优化其超参数。因此,它将机器学习使用者从繁琐的任务中解放出来,使其有更多时间专注于实际问题。当前版本为 0.6.0,具体信息请查看官网auto-sklearn。
我们以机器学习的分类模型为例,常规的机器学习框架如图中的灰色部分,导入数据后,在经过数据预处理和特征预处理后,通过分类器输出预测值,如果结果不尽人意,需要手动调整超参数并重新选择合适的模型。
而自动的部分就如图绿框所示,在ML-framework左边新增meta-learning,在右边新增build-ensemble,并使用贝叶斯优化自动调超参数。meta-learning是用于初始化贝叶斯优化器的元学习,它可以去学习样本数据的模样,一旦找到相似的数据集,就可以根据经验来推荐好用的分类器,比如文本数据用什么模型比较好,很多离散的数据用什么模型比较好。build-ensemble是优化过程中的自动模型集成,可以根据贝叶斯优化找到最佳的分类器组合,往往能提高预测的准确性。
(1)优点
(2)缺点
对于给定的数据集,我们如何利用auto-sklearn来对它进行自动化的机器学习呢?上手的方法非常简单,设置好以下几种关键的参数即可。
以下是两个简单示例:
(1)回归任务
# -*- encoding: utf-8 -*-
"""
==========
Regression
==========
The following example shows how to fit a simple regression model with
*auto-sklearn*.
"""
import sklearn.datasets
import sklearn.metrics
import autosklearn.regression
############################################################################
# Data Loading
# ============
X, y = sklearn.datasets.load_boston(return_X_y=True)
X_train, X_test, y_train, y_test = \
sklearn.model_selection.train_test_split(X, y, random_state=1)
############################################################################
# Build and fit a regressor
# =========================
automl = autosklearn.regression.AutoSklearnRegressor(
time_left_for_this_task=120,
per_run_time_limit=30,
tmp_folder='/tmp/autosklearn_regression_example_tmp',
output_folder='/tmp/autosklearn_regression_example_out',
)
automl.fit(X_train, y_train, dataset_name='boston')
############################################################################
# Print the final ensemble constructed by auto-sklearn
# ====================================================
print(automl.show_models())
###########################################################################
# Get the Score of the final ensemble
# ===================================
predictions = automl.predict(X_test)
print("R2 score:", sklearn.metrics.r2_score(y_test, predictions))
(2)分类任务
# -*- encoding: utf-8 -*-
"""
==============
Classification
==============
The following example shows how to fit a simple classification model with
*auto-sklearn*.
"""
import sklearn.datasets
import sklearn.metrics
import autosklearn.classification
############################################################################
# Data Loading
# ============
X, y = sklearn.datasets.load_breast_cancer(return_X_y=True)
X_train, X_test, y_train, y_test = \
sklearn.model_selection.train_test_split(X, y, random_state=1)
############################################################################
# Build and fit a regressor
# =========================
automl = autosklearn.classification.AutoSklearnClassifier(
time_left_for_this_task=120,
per_run_time_limit=30,
tmp_folder='/tmp/autosklearn_classification_example_tmp',
output_folder='/tmp/autosklearn_classification_example_out',
)
automl.fit(X_train, y_train, dataset_name='breast_cancer')
############################################################################
# Print the final ensemble constructed by auto-sklearn
# ====================================================
print(automl.show_models())
###########################################################################
# Get the Score of the final ensemble
# ===================================
predictions = automl.predict(X_test)
print("Accuracy score:", sklearn.metrics.accuracy_score(y_test, predictions))
以下是官网给出一个最简单的多分类示例:
"""
==========================
Multi-label Classification
==========================
This examples shows how to format the targets for a multilabel classification
problem. Details on multilabel classification can be found
`here `_.
"""
import numpy as np
import sklearn.datasets
import sklearn.metrics
from sklearn.utils.multiclass import type_of_target
import autosklearn.classification
############################################################################
# Data Loading
# ============
# Using reuters multilabel dataset -- https://www.openml.org/d/40594
X, y = sklearn.datasets.fetch_openml(data_id=40594, return_X_y=True, as_frame=False)
# fetch openml downloads a numpy array with TRUE/FALSE strings. Re-map it to
# integer dtype with ones and zeros
# This is to comply with Scikit-learn requirement:
# "Positive classes are indicated with 1 and negative classes with 0 or -1."
# More information on: https://scikit-learn.org/stable/modules/multiclass.html
y[y == 'TRUE'] = 1
y[y == 'FALSE'] = 0
y = y.astype(np.int)
# Using type of target is a good way to make sure your data
# is properly formatted
print(f"type_of_target={type_of_target(y)}")
X_train, X_test, y_train, y_test = sklearn.model_selection.train_test_split(
X, y, random_state=1
)
############################################################################
# Building the classifier
# =======================
automl = autosklearn.classification.AutoSklearnClassifier(
time_left_for_this_task=60,
per_run_time_limit=30,
# Bellow two flags are provided to speed up calculations
# Not recommended for a real implementation
initial_configurations_via_metalearning=0,
smac_scenario_args={'runcount_limit': 1},
)
automl.fit(X_train, y_train, dataset_name='reuters')
############################################################################
# Print the final ensemble constructed by auto-sklearn
# ====================================================
print(automl.show_models())
############################################################################
# Print statistics about the auto-sklearn run
# ===========================================
# Print statistics about the auto-sklearn run such as number of
# iterations, number of models failed with a time out.
print(automl.sprint_statistics())
############################################################################
# Get the Score of the final ensemble
# ===================================
predictions = automl.predict(X_test)
print("Accuracy score", sklearn.metrics.accuracy_score(y_test, predictions))
实现过程注释里面给的很清晰,分为了几个部分,数据加载(data loading),创建分类器(build the classifier),得出结果(get the score),而其中的核心过程创建分类器用到的方法基本来自于对象automl
,包括automl.fit()
,automl.show_models()
,automl.sprint_statistics()
,automl.predict()
,而这个对象在这里被创建为类AutoSklearnClassifier
的一个实例。
正如我们一开始就讲到,文章的重点是面向对象思想,而非项目本身。所以我们试图通过选择简单的分析样例来避开项目自身的实现复杂性,避开诸多细节,只接触核心流程。
首先,我们对上述示例进行需求建模:
【用例名称】
多分类自动化机器学习
【场景】
who:训练集、测试集、分类模型、预测结果
where:内存空间
when:运行时间
【用例描述】
- 载入数据集
- 进行数据预处理
2.1 若数据集没有划分训练集和测试集,按一定规则划分
2.2 若有归一化需求,则将数据进行归一化- 创建合适的分类器
- 进行训练,自动化调参
- 打印最终模型及其相关参数
- 对测试集数据进行分类预测,得到预测结果的正确率
【用例价值】
完成多分类的机器学习任务
【约束和限制】
输入数据为数值型
寻找其中的动词和名词:
【动词】载入、预处理、划分、归一化、创建、训练、预测、打印模型
【名词】数据集、训练集、测试集、分类器、预测结果
数据集(包括训练集和测试集)为一系列数值,没有必要抽象成类,从而我们得到应该抽象出来的类及其方法和属性:
【类】分类器(AutoSklearnClassifier)
【属性】训练需要的相关信息,包括训练时间,模型存储等
【方法】训练、预测、打印模型
而这与之前得到的对象automl
的性质正好相对应起来了。
1. 完成自动化机器学习任务的最核心过程就是模型的训练过程了,在代码中体现为对automl
对象的fit()
方法的调用,其具体执行流程如下:
fit()
方法是AutoSklearnClassifier
这个类下声明的第一个方法:
class AutoSklearnClassifier(AutoSklearnEstimator):
def fit(self, X, y,
X_test=None,
y_test=None,
feat_type=None,
dataset_name=None):
"""Fit *auto-sklearn* to given training set (X, y).
Fit both optimizes the machine learning models and builds an ensemble
out of them. To disable ensembling, set ``ensemble_size==0``.
"""
# Before running anything else, first check that the
# type of data is compatible with auto-sklearn. Legal target
# types are: binary, multiclass, multilabel-indicator.
target_type = type_of_target(y)
supported_types = ['binary', 'multiclass', 'multilabel-indicator']
if target_type not in supported_types:
raise ValueError("Classification with data of type {} is "
"not supported. Supported types are {}. "
"You can find more information about scikit-learn "
"data types in: "
"https://scikit-learn.org/stable/modules/multiclass.html"
"".format(
target_type,
supported_types
)
)
# remember target type for using in predict_proba later.
self.target_type = target_type
super().fit(
X=X,
y=y,
X_test=X_test,
y_test=y_test,
feat_type=feat_type,
dataset_name=dataset_name,
)
return self
......
2. 在这个方法中,通过调用super().fit()
实现函数主体过程,而在python语法中,与java类似的,使用super关键字表示调用父类的方法,即此处通过调用父类的fit()
方法来实现该类(指AutoSklearnClassifier
)的相同方法,相当于该类继承使用其父类的同名方法。
其父类的fit()
方法定义如下:
def fit(self, **kwargs):
# Handle the number of jobs and the time for them
if self.n_jobs is None or self.n_jobs == 1:
self._n_jobs = 1
elif self.n_jobs == -1:
self._n_jobs = joblib.cpu_count()
else:
self._n_jobs = self.n_jobs
# Automatically set the cutoff time per task
if self.per_run_time_limit is None:
self.per_run_time_limit = self._n_jobs * self.time_left_for_this_task // 10
seed = self.seed
self.automl_ = self.build_automl(
seed=seed,
ensemble_size=self.ensemble_size,
initial_configurations_via_metalearning=(
self.initial_configurations_via_metalearning
),
tmp_folder=self.tmp_folder,
output_folder=self.output_folder,
)
self.automl_.fit(load_models=self._load_models, **kwargs)
return self
3. 在这个过程中,首先调用了该类(指AutoSklearnClassifier
的父类)的build_automl()
方法创建了一个自动化机器学习器,该方法代码如下:
def build_automl(
self,
seed: int,
ensemble_size: int,
initial_configurations_via_metalearning: int,
tmp_folder: str,
output_folder: str,
smac_scenario_args: Optional[Dict] = None,
):
backend = create(
temporary_directory=tmp_folder,
output_directory=output_folder,
delete_tmp_folder_after_terminate=self.delete_tmp_folder_after_terminate,
delete_output_folder_after_terminate=self.delete_output_folder_after_terminate,
)
if smac_scenario_args is None:
smac_scenario_args = self.smac_scenario_args
automl = self._get_automl_class()(
backend=backend,
time_left_for_this_task=self.time_left_for_this_task,
per_run_time_limit=self.per_run_time_limit,
initial_configurations_via_metalearning=initial_configurations_via_metalearning,
ensemble_size=ensemble_size,
ensemble_nbest=self.ensemble_nbest,
max_models_on_disc=self.max_models_on_disc,
seed=seed,
memory_limit=self.memory_limit,
include_estimators=self.include_estimators,
exclude_estimators=self.exclude_estimators,
include_preprocessors=self.include_preprocessors,
exclude_preprocessors=self.exclude_preprocessors,
resampling_strategy=self.resampling_strategy,
resampling_strategy_arguments=self.resampling_strategy_arguments,
n_jobs=self._n_jobs,
dask_client=self.dask_client,
get_smac_object_callback=self.get_smac_object_callback,
disable_evaluator_output=self.disable_evaluator_output,
smac_scenario_args=smac_scenario_args,
logging_config=self.logging_config,
metadata_directory=self.metadata_directory,
metric=self._metric,
scoring_functions=self._scoring_functions
)
return automl
可见其通过调用_get_automl_class()
方法创建了AutoML
类的一个实例,即上一段提到的自动化机器学习器(对应代码中的self.automl_
对象)
4. 在build_automl()
创建了self.automl_
对象之后,调用了self.automl_.fit()
,即类AutoML
的fit()
方法,完成学习和训练过程。
可见这个用户表面上调用的fit()
函数实际上经过了三层不同的类的fit()
方法的迭代,最终运行的是类AutoML
的fit()
方法,而这个最终版的fit()
方法代码量较大,在这里就不再展示,感兴趣的读者可以直接进入官方github网站的automl.py文件进行更深入的研究。
实际上,这也是我觉得该项目的设计中略不合理的地方,一个如此长的函数给代码的维护和阅读带来了及其不良的体验,也会带来大量代码的高度耦合,这并不是良好的程序设计风格。就面向对象程序设计而言,这也不符合封装和模块化的思想以及高内聚、低耦合的要求。
在上一节的分析中,我们提及了AutoSklearnClassifier
及其父类,还有AutoML
这三个类,它们以及其他相关类的类间关系如下图:
与分类器相类似的,机器学习任务中另一种非常重要的模型是回归器(regressor),因此回归器将作为与分类器相并列的一个类AutoSklearnRegressor
而存在,它们的父类AutoSklearnEstimator
与类AutoML
相关联,从而能够在其自身的fit()
方法中调用类AutoML
的fit()
方法。
类AutoML
下也实现了子类AutoMLClassifier
和AutoMLRegressor
,通过vscode工具查看引用发现这两个类下的fit()
方法除了其自身没有被其他任何地方引用,这让我有些纳闷。
事实上,这两个子类的fit()
方法也是通过super关键字调用super().fit()
实现对类AutoML
的fit()
方法的最终调用的,因此这两个类的功能与之前提到的AutoSklearnRegressor
和AutoSklearnClassifier
两个类相比确实有一些重复,所以这也是我认为不太合理的地方,不知道是不是开发者为了提高代码的兼容性而故意保留的冗余部分。
策略模式的目的是,针对一组算法,将每一个算法封装到具有共同接口的独立的类中,从而使得它们可以相互替换,在auto-sklearn或sklearn这样的机器学习库中,策略模式的应用是非常常见且典型的。
比如,对于机器学习中的几个关键过程,分类(classification),回归(regression),数据预处理(data-preprocessing),和特征预处理(feature-preprocessing),每一个过程都有很多种不同的算法或方法来实现,如分类模型有adaboost、decision_tree、k_nearest_neighbors等等,在这个项目中,每一种算法被封装成一个独立的类,这些类均继承于一个与它们具有相同功能的抽象的类,这个类作为所有支持的算法的公共接口。components部分的base模块生成的类图如下:
其中AutoSklearnClassificationAlgorithm
、AutoSklearnPreprocessingAlgorithm
和AutoSklearnRegressionAlgorithm
即为几个公共接口(Strategy),Context使用这个接口来调用某ConcreteStrategy定义的算法,而ConcreteStrategy即为各种具体的算法。包括了ConcreteStrategy部分的简略类图如下(为降低复杂性略去了类IterativeComponent
和类IterativeComponentWithSampleWeight
,且由于算法种类过多只展示部分算法,重点是表达设计思想):
通过策略模式使得架构清晰且符合开闭原则,我们可以很容易增加新的算法或修改原有算法。与此同时,该项目还很好地利用了其他项目的策略模式,从图中可以看到有一个新的类BaseEstimator
,这个类与类AutoSklearnComponents
的定义相关,但是通过查询它并不存在于本项目的开源代码中,通过vscode检索工具最终发现它出现在了sklearn源码的base模块,如果把类BaseEstimator
也看作sklearn的一个接口,那么类AutoSklearnComponents
相当于对sklearn中预测器(类BaseEstimator
)的一种实现。
代理模式的意图为为其他对象提供一种代理以控制对这个对象的访问,其典型的实现方法就是增加中间层,使得代理可以代替实体实现相关功能。
通过前几节的分析,可以发现auto-sklearn的核心设计模式就是代理模式。最终要访问的实体类是AutoML
,而类AutoSklearnEstimator
相当于它的代理,这个类中并没有fit()
方法真正的具体实现,但通过调用它我们却可以得到我们想要的功能和结果,这或许与传统意义上的代理模式略有不同,但其核心思想是相通的。创建一个类AutoML
的实例需要比较大的开销,所以这里先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建。 这应该属于虚拟代理。
所有的分析到这里就结束了,作为一个面向对象编程领域的小白,一个学期下来收获很多,在报告中提出了一些自己不成熟的见解,感谢各位读者的耐心阅读和体谅,有机会也希望与大家共同交流学习~