「一切有可能产生错误的事情一般都会出错」,墨菲定律的这个「乌鸦嘴魔咒」彷佛是不可能避免的。也许每次都是面包上涂了果酱的那一面与地板亲密接触,也许是平时公司楼下总有等客的出租车而当你着急外出时却一辆也看不见,或者你精心制做的 ML 模型通过屡次训练和调教最终不可避免崩了……html
「一切有可能产生错误的事情一般都会出错」,当你这样安慰本身的时候,难道就很差奇,为何恰恰就会这样?git
构建和训练 ML 模型是科学和工艺的结合。从收集和准备数据集,到使用不一样算法进行实验以找出最佳训练参数(可怕的超参数),ML 从业者须要清除不少障碍才能提供高性能模型。这正是 AWS 提供 Amazon SageMaker 服务的缘由之一,就是为了帮助你们简化和加快 ML 工做流的构建过程。github
然而估计不少人都深有体会,ML 彷佛是墨菲定律最喜欢的地方之一:一切有可能产生错误的事情一般都会出错!特别是,训练过程当中可能发生不少模糊的问题,致使模型没法正确提取或难以学习数据集中的模型。但缘由一般并不在于 ML 库中的软件漏洞(尽管它们也会发生),大多数失败的训练做业都是由于不适当的参数初始化、糟糕的超参数组合、咱们本身代码中的设计问题等形成的。算法
更糟的是,这些问题不多能当即显现出来,它们每每会随着时间的推移放大,从而慢慢但也一定会破坏咱们的训练过程,产生准确度低的模型。面对现实吧,即便高水平的专家大牛,识别并揪出这些问题也很是困难且耗时。docker
最近,Amazon SageMaker Debugger 正式发布,它是 Amazon SageMaker 的新功能,能够自动识别机器学习(ML)训练做业中出现的复杂问题。数组
在咱们现有的 TensorFlow、Keras、Apache MXNet、PyTorch 和 XGBoost 训练代码中,可使用新发布的 SageMaker Debugger 开发工具包按期保存内部模型状态,并将其存储在 Amazon Simple Storage Service(S3)中。网络
所谓的内部模型状态包括:框架
每一个特定的值集(例如一段时间内在特定神经网络层中流动的梯度)将按顺序独立保存,并称为张量。张量被组织在集合中(权重、梯度等),咱们能够决定在训练期间要保存哪些张量。随后便可使用 SageMaker 开发工具包及其估算器像往常同样配置本身的训练做业,从而传递定义但愿 SageMaker Debugger 应用的规则的其余参数。dom
规则是一段 Python 代码,可用于分析训练中的模型的张量,以此寻找特定的不须要的条件。预约义规则可用于一些常见问题,如张量爆炸/消失(参数达到 NaN 或零值)、梯度爆炸/消失、损失但未更改等。固然,咱们还能够编写本身的规则。机器学习
配置 SageMaker 估算器后,便可启动训练做业。它将为配置的每一个规则当即启动一个调试做业,并开始检查可用的张量。若是调试做业检测到问题,它将中止并记录其余信息。若是想要触发其余自动化步骤,还会发送 CloudWatch Events 事件。
借此咱们能够了解深度学习做业受到所谓的梯度消失影响。只要进行一点头脑风暴,有一点经验,就会知道去哪里寻找帮助:也许神经网络太深?也许学习速度过低?因为内部状态已保存到 S3 中,咱们如今可使用 SageMaker Debugger 开发工具包探索张量随时间的变化,确认本身的假设并修正根本缘由。
下文将经过简短的演示介绍 SageMaker Debugger 的操做。
SageMaker Debugger 的核心能力是在训练期间获取张量。这须要在咱们的训练代码中使用一些工具,以选择想要保存的张量集合,想要保存它们的频率及是要保存这些值自己仍是缩减值(平均值等)。
为此, SageMaker Debugger 开发工具包为它支持的每一个框架提供简单的API。下文将展现它是如何使用简单的 TensorFlow 脚本工做的,以试图拟合一个二维线性回归模型。固然,此 GitHub 存储库中还提供了更多示例。
咱们来看一看初始代码:
import argparse import numpy as np import tensorflow as tf import random parser = argparse.ArgumentParser() parser.add_argument('--model_dir', type=str, help="S3 path for the model") parser.add_argument('--lr', type=float, help="Learning Rate", default=0.001) parser.add_argument('--steps', type=int, help="Number of steps to run", default=100) parser.add_argument('--scale', type=float, help="Scaling factor for inputs", default=1.0) args = parser.parse_args() with tf.name_scope('initialize'): # 2-dimensional input sample x = tf.placeholder(shape=(None, 2), dtype=tf.float32) # Initial weights: [10, 10] w = tf.Variable(initial_value=[[10.], [10.]], name='weight1') # True weights, i.e. the ones we're trying to learn w0 = [[1], [1.]] with tf.name_scope('multiply'): # Compute true label y = tf.matmul(x, w0) # Compute "predicted" label y_hat = tf.matmul(x, w) with tf.name_scope('loss'): # Compute loss loss = tf.reduce_mean((y_hat - y) ** 2, name="loss") optimizer = tf.train.AdamOptimizer(args.lr) optimizer_op = optimizer.minimize(loss) with tf.Session() as sess: sess.run(tf.global_variables_initializer()) for i in range(args.steps): x_ = np.random.random((10, 2)) * args.scale _loss, opt = sess.run([loss, optimizer_op], {x: x_}) print (f'Step={i}, Loss={_loss}')
咱们来使用 TensorFlow Estimator 训练此脚本。这里使用的是 SageMaker 本地模式,它是快速迭代实验代码的一种很好的方法。
bad_hyperparameters = {'steps': 10, 'lr': 100, 'scale': 100000000000} estimator = TensorFlow( role=sagemaker.get_execution_role(), base_job_name='debugger-simple-demo', train_instance_count=1, train_instance_type='local', entry_point='script-v1.py', framework_version='1.13.1', py_version='py3', script_mode=True, hyperparameters=bad_hyperparameters)
看看训练日志,事情进展的并不顺利:
Step=0, Loss=7.883463958023267e+23 algo-1-hrvqg_1 | Step=1, Loss=9.502028841062608e+23 algo-1-hrvqg_1 | Step=2, Loss=nan algo-1-hrvqg_1 | Step=3, Loss=nan algo-1-hrvqg_1 | Step=4, Loss=nan algo-1-hrvqg_1 | Step=5, Loss=nan algo-1-hrvqg_1 | Step=6, Loss=nan algo-1-hrvqg_1 | Step=7, Loss=nan algo-1-hrvqg_1 | Step=8, Loss=nan algo-1-hrvqg_1 | Step=9, Loss=nan
损失一点也没有减小,甚至趋近于无穷大…… 这看起来像是张量爆炸问题,它是 SageMaker Debugger 中定义的内置规则之一。
为了捕获张量,我须要用如下各项编写训练脚本:
下面是更新后的代码,包含 SageMaker Debugger 参数的额外命令行参数。
import argparse import numpy as np import tensorflow as tf import random import smdebug.tensorflow as smd parser = argparse.ArgumentParser() parser.add_argument('--model_dir', type=str, help="S3 path for the model") parser.add_argument('--lr', type=float, help="Learning Rate", default=0.001 ) parser.add_argument('--steps', type=int, help="Number of steps to run", default=100 ) parser.add_argument('--scale', type=float, help="Scaling factor for inputs", default=1.0 ) parser.add_argument('--debug_path', type=str, default='/opt/ml/output/tensors') parser.add_argument('--debug_frequency', type=int, help="How often to save tensor data", default=10) feature_parser = parser.add_mutually_exclusive_group(required=False) feature_parser.add_argument('--reductions', dest='reductions', action='store_true', help="save reductions of tensors instead of saving full tensors") feature_parser.add_argument('--no_reductions', dest='reductions', action='store_false', help="save full tensors") args = parser.parse_args() args = parser.parse_args() reduc = smd.ReductionConfig(reductions=['mean'], abs_reductions=['max'], norms=['l1']) if args.reductions else None hook = smd.SessionHook(out_dir=args.debug_path, include_collections=['weights', 'gradients', 'losses'], save_config=smd.SaveConfig(save_interval=args.debug_frequency), reduction_config=reduc) with tf.name_scope('initialize'): # 2-dimensional input sample x = tf.placeholder(shape=(None, 2), dtype=tf.float32) # Initial weights: [10, 10] w = tf.Variable(initial_value=[[10.], [10.]], name='weight1') # True weights, i.e. the ones we're trying to learn w0 = [[1], [1.]] with tf.name_scope('multiply'): # Compute true label y = tf.matmul(x, w0) # Compute "predicted" label y_hat = tf.matmul(x, w) with tf.name_scope('loss'): # Compute loss loss = tf.reduce_mean((y_hat - y) ** 2, name="loss") hook.add_to_collection('losses', loss) optimizer = tf.train.AdamOptimizer(args.lr) optimizer = hook.wrap_optimizer(optimizer) optimizer_op = optimizer.minimize(loss) hook.set_mode(smd.modes.TRAIN) with tf.train.MonitoredSession(hooks=[hook]) as sess: for i in range(args.steps): x_ = np.random.random((10, 2)) * args.scale _loss, opt = sess.run([loss, optimizer_op], {x: x_}) print (f'Step={i}, Loss={_loss}')
咱们还须要修改 TensorFlow Estimator,以使用启用了 SageMaker Debugger 的训练容器并传递其余参数。
bad_hyperparameters = {'steps': 10, 'lr': 100, 'scale': 100000000000, 'debug_frequency': 1} from sagemaker.debugger import Rule, rule_configs estimator = TensorFlow( role=sagemaker.get_execution_role(), base_job_name='debugger-simple-demo', train_instance_count=1, train_instance_type='ml.c5.2xlarge', image_name=cpu_docker_image_name, entry_point='script-v2.py', framework_version='1.15', py_version='py3', script_mode=True, hyperparameters=bad_hyperparameters, rules = [Rule.sagemaker(rule_configs.exploding_tensor())] ) estimator.fit() 2019-11-27 10:42:02 开始 - 开始训练做业... 2019-11-27 10:42:25 开始 - 启动请求的 ML 实例 ********* Debugger Rule Status ********* * * ExplodingTensor: InProgress * ****************************************
两个做业在运行:实际训练做业和检查 Estimator 中定义的规则的调试做业。调试做业很快就失败了!
描述训练做业,我能够获得有关所发生状况的更多信息。
description = client.describe_training_job(TrainingJobName=job_name) print(description['DebugRuleEvaluationStatuses'][0]['RuleConfigurationName']) print(description['DebugRuleEvaluationStatuses'][0]['RuleEvaluationStatus']) ExplodingTensor IssuesFound
接下来再看看如何查看保存的张量。
训练期间,咱们能够轻松获取 S3 中保存的张量:
s3_output_path = description["DebugConfig"]["DebugHookConfig"]["S3OutputPath"] trial = create_trial(s3_output_path)
并能够列出可用的张量:
trial.tensors() ['loss/loss:0', 'gradients/multiply/MatMul_1_grad/tuple/control_dependency_1:0', 'initialize/weight1:0']
全部值均为 numpy 队列,咱们能够对它们轻松地进行不断迭代:
tensor = 'gradients/multiply/MatMul_1_grad/tuple/control_dependency_1:0' for s in list(trial.tensor(tensor).steps()): print("Value: ", trial.tensor(tensor).step(s).value) Value: [[1.1508383e+23] [1.0809098e+23]] Value: [[1.0278440e+23] [1.1347468e+23]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]] Value: [[nan] [nan]]
因为张量名称包括训练代码中定义的 TensorFlow 范围,所以很容易能够发现矩阵乘法出现问题。
# Compute true label y = tf.matmul(x, w0) # Compute "predicted" label y_hat = tf.matmul(x, w)
再深刻一点,能够发现x输入被缩放参数修改,咱们在估算器中将该参数设置为100000000000。学习速度看起来也不正常。成功了!
x_ = np.random.random((10, 2)) * args.scale bad_hyperparameters = {'steps': 10, 'lr': 100, 'scale': 100000000000, 'debug_frequency': 1}
相信你们也早都知道了,将这些超参数设置为更合理的值将修复训练问题。
相信 Amazon SageMaker Debugger 将帮助你们更快地找到并解决训练问题。Amazon SageMaker Debugger 现已在提供 Amazon SageMaker 的全部商业区域推出,欢迎体验。