「一切有可能產生錯誤的事情一般都會出錯」,墨菲定律的這個「烏鴉嘴魔咒」彷佛是不可能避免的。也許每次都是麪包上塗了果醬的那一面與地板親密接觸,也許是平時公司樓下總有等客的出租車而當你着急外出時卻一輛也看不見,或者你精心製做的 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 的全部商業區域推出,歡迎體驗。