⚡他来了,他来了,热门开源AutoML工具NNI2.5重磅来袭⚡
NNI是微软亚洲研究院为研究人员和算法工程师量身定制的自动机器学习(AutoML)工具。过去的几年中,NNI不断迭代更新,持续将最前沿的算法加入其中,加强对各种分布式训练环境的支持,目前NNI已在Github上获得了10.8k星,成为最热门的自动机器学习开源项目之一。
AutoML问题构成
AutoML的主要问题可以由三部分构成:特征工程、模型选择、算法选择。
特征工程
特征工程在机器学习中有着举足轻重的作用。在AutoML中,自动特征工程的目的是自动地发掘并构造相关的特征,使得模型可以有最优的表现。除此之外,还包含一些特定的特征增强方法,例如特征选择、特征降维、特征生成、以及特征编码等。这些步骤目前来说都没有达到自动化的阶段。
上述这些步骤也伴随着一定的参数搜索空间。第一种搜索空间是方法自带的,例如PCA自带降维参数需要调整。第二种是特征生成时会将搜索空间扩大。
模型选择
模型选择包括两个步骤:选择一个模型,设定它的参数。相应地,AutoML的目的就是自动选择出一个最合适的模型,并且能够设定好它的最优参数。
算法选择
对于算法选择,AutoML的目的是自动地选择出一个优化算法,以便能够达到效率和精度的平衡。常用的优化方法有SGD、L-BFGS、GD等。使用哪个优化算法、对应优化算法的配置,也需要一组搜索空间。
NNI 提供命令行工具以及用户友好的 WebUI 来管理训练实验。借助可扩展的 API,您可以自定义自己的 AutoML 算法和训练服务。为了方便新用户使用,NNI 还提供了一组内置的最先进的 AutoML 算法和对流行训练平台的开箱即用支持。
在下表中,我们总结了当前的 NNI 功能,我们正在逐步添加新功能,我们很乐意得到您的贡献。
Frameworks & Libraries |
Algorithms |
Training Services |
|
Built-in |
|
Exhaustive search Heuristic search Bayesian optimization Neural Architecture Search (Retiarii) Pruning
Quantization |
|
References |
实验:一项任务,例如找出模型的最佳超参数、找出最佳神经网络架构等。它由试验和 AutoML 算法组成。
搜索空间:调整模型的可行区域。例如,每个超参数的取值范围。
配置:来自搜索空间的一个实例,即每个超参数都有一个特定的值。
试验:应用新配置的个人尝试(例如,一组超参数值、特定的神经架构等)。试用代码应该能够使用提供的配置运行。
Tuner:一种 AutoML 算法,为下一次尝试生成新配置。将使用此配置运行新的试用版。
Assessor:分析试验的中间结果(例如,定期评估测试数据集的准确性)以判断该试验是否可以提前停止。
培训平台:进行试验的地方。根据您的实验配置,它可能是您的本地机器、远程服务器或大型训练平台(例如,OpenPAI、Kubernetes)。
基本上,实验运行如下: Tuner 接收搜索空间并生成配置。这些配置将提交给训练平台,例如本地机器、远程机器或训练集群。他们的表演被报告给 Tuner。然后,生成并提交新的配置。
对于每个实验,用户只需要定义一个搜索空间并更新几行代码,然后利用 NNI 内置的 Tuner/Assessor 和训练平台来搜索最佳的超参数和/或神经架构。基本上有3个步骤:
NNI还有个更重要的优点就是能够使用WebUI查看模型训练效果,以及参数空间配置
代码示例
launch.py
from pathlib import Path
from nni.experiment import Experiment
search_space = {
"dropout_rate": {"_type": "uniform", "_value": [0.5, 0.9]},
"conv_size": {"_type": "choice", "_value": [2, 3, 5, 7]},
"hidden_size": {"_type": "choice", "_value": [124, 512, 1024]},
"batch_size": {"_type": "choice", "_value": [16, 32]},
"learning_rate": {"_type": "choice", "_value": [0.0001, 0.001, 0.01, 0.1]}
}
experiment = Experiment('local')
experiment.config.experiment_name = 'MNIST example'
experiment.config.trial_concurrency = 2
experiment.config.max_trial_number = 10
experiment.config.search_space = search_space
experiment.config.trial_command = 'python3 mnist.py'
experiment.config.trial_code_directory = Path(__file__).parent
experiment.config.tuner.name = 'TPE'
experiment.config.tuner.class_args['optimize_mode'] = 'maximize'
experiment.config.training_service.use_active_gpu = True
experiment.run(8080)
mnist.py
import logging
import tensorflow as tf
from tensorflow.keras import Model
from tensorflow.keras.callbacks import Callback
from tensorflow.keras.layers import (Conv2D, Dense, Dropout, Flatten, MaxPool2D)
from tensorflow.keras.optimizers import Adam
import nni
_logger = logging.getLogger('mnist_example')
_logger.setLevel(logging.INFO)
class MnistModel(Model):
def __init__(self, conv_size, hidden_size, dropout_rate):
super().__init__()
self.conv1 = Conv2D(filters=32, kernel_size=conv_size, activation='relu')
self.pool1 = MaxPool2D(pool_size=2)
self.conv2 = Conv2D(filters=64, kernel_size=conv_size, activation='relu')
self.pool2 = MaxPool2D(pool_size=2)
self.flatten = Flatten()
self.fc1 = Dense(units=hidden_size, activation='relu')
self.dropout = Dropout(rate=dropout_rate)
self.fc2 = Dense(units=10, activation='softmax')
def call(self, x):
x = self.conv1(x)
x = self.pool1(x)
x = self.conv2(x)
x = self.pool2(x)
x = self.flatten(x)
x = self.fc1(x)
x = self.dropout(x)
return self.fc2(x)
class ReportIntermediates(Callback):
def on_epoch_end(self, epoch, logs=None):
if 'val_acc' in logs:
nni.report_intermediate_result(logs['val_acc'])
else:
nni.report_intermediate_result(logs['val_accuracy'])
def load_dataset():
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data()
x_train, x_test = x_train / 255.0, x_test / 255.0
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]
return (x_train, y_train), (x_test, y_test)
def main(params):
model = MnistModel(
conv_size=params['conv_size'],
hidden_size=params['hidden_size'],
dropout_rate=params['dropout_rate']
)
optimizer = Adam(learning_rate=params['learning_rate'])
model.compile(optimizer=optimizer, loss='sparse_categorical_crossentropy', metrics=['accuracy'])
_logger.info('Model built')
(x_train, y_train), (x_test, y_test) = load_dataset()
_logger.info('Dataset loaded')
model.fit(
x_train,
y_train,
batch_size=params['batch_size'],
epochs=10,
verbose=0,
callbacks=[ReportIntermediates()],
validation_data=(x_test, y_test)
)
_logger.info('Training completed')
loss, accuracy = model.evaluate(x_test, y_test, verbose=0)
nni.report_final_result(accuracy)
_logger.info('Final accuracy reported: %s', accuracy)
if __name__ == '__main__':
params = {
'dropout_rate': 0.5,
'conv_size': 5,
'hidden_size': 1024,
'batch_size': 32,
'learning_rate': 1e-4,
}
tuned_params = nni.get_next_parameter()
params.update(tuned_params)
_logger.info('Hyper-parameters: %s', params)
main(params)