一篇文章掌握TensorFlow深度學習

作者: 陳迪豪,就職小米科技,深度學習工程師,TensorFlow代碼提交者。

TensorFlow深度學習框架

image.png

Google不僅是大數據和云計算的領導者,在機器學習和深度學習上也有很好的實踐和積累,在2015年年底開源了內部使用的深度學習框架TensorFlow。

與Caffe、Theano、Torch、MXNet等框架相比,TensorFlow在Github上Fork數和Star數都是最多的,而且在圖形分類、音頻處理、推薦系統和自然語言處理等場景下都有豐富的應用。最近流行的Keras框架底層默認使用TensorFlow,著名的斯坦福CS231n課程使用TensorFlow作為授課和作業的編程語言,國內外多本TensorFlow書籍已經在籌備或者發售中,AlphaGo開發團隊Deepmind也計劃將神經網絡應用遷移到TensorFlow中,這無不印證了TensorFlow在業界的流行程度。

TensorFlow不僅在Github開放了源代碼,在《TensorFlow: Large-Scale Machine Learning on Heterogeneous Distributed Systems》論文中也介紹了系統框架的設計與實現,其中測試過200節點規模的訓練集群也是其他分布式深度學習框架所不能媲美的。Google還在《Wide & Deep Learning for Recommender Systems》和《The YouTube Video Recommendation System》論文中介紹了Google Play應用商店和YouTube視頻推薦的算法模型,還提供了基于TensorFlow的代碼實例,使用TensorFlow任何人都可以在ImageNet或Kaggle競賽中得到接近State of the art的好成績。

TensorFlow從入門到應用

毫不夸張得說,TensorFlow的流行讓深度學習門檻變得越來越低,只要你有Python和機器學習基礎,入門和使用神經網絡模型變得非常簡單。TensorFlow支持Python和C++兩種編程語言,再復雜的多層神經網絡模型都可以用Python來實現,如果業務使用其他編程也不用擔心,使用跨語言的gRPC或者HTTP服務也可以訪問使用TensorFlow訓練好的智能模型。

那使用Python如何編寫TensorFlow應用呢?從入門到應用究竟有多難呢?

下面我們編寫了一個Hello world應用,輸出字符串和進行簡單的運算。

# Import the library
import tensorflow as tf

# Define the graph
hello_op = tf.constant('Hello, TensorFlow!')
a = tf.constant(10)
b = tf.constant(32)
compute_op = tf.add(a, b)

# Define the session to run graph
with tf.Session() as sess:
    print(sess.run(hello_op))
    print(sess.run(compute_op))

從這段簡單的代碼可以了解到TensorFlow的使用非常方便,通過Python標準庫的形式導入,不需要啟動額外的服務。第一次接觸TensorFlow可能比較疑惑,這段邏輯Python也可以實現,為什么要使用tf.constant()和tf.Session()呢?其實TensorFlow通過Graph和Session來定義運行的模型和訓練,這在復雜的模型和分布式訓練上有非常大好處,將在文章的后續部分介紹到。

前面的Hello world應用并沒有訓練模型,接下來介紹一個邏輯回歸問題與模型。我們使用numpy構建一組線性關系的數據,通過TensorFlow實現的隨機梯度算法,在訓練足夠長的時間后可以自動求解函數中的斜率和截距。

import tensorflow as tf
import numpy as np

# Prepare train data
train_X = np.linspace(-1, 1, 100)
train_Y = 2 * train_X + np.random.randn(*train_X.shape) * 0.33 + 10

# Define the model
X = tf.placeholder(tf.float32)
Y = tf.placeholder(tf.float32)
w = tf.Variable(0.0, name="weight")
b = tf.Variable(0.0, name="bias")
loss = tf.square(Y - tf.matmul(X, w) - b)
train_op = tf.train.GradientDescentOptimizer(0.01).minimize(loss)

# Create session to run
with tf.Session() as sess:
    sess.run(tf.initialize_all_variables())
    epoch = 1
    for i in range(10):
        for (x, y) in zip(train_X, train_Y):
            _, w_value, b_value = sess.run([train_op, w, b], 
                                           feed_dict={X: x, Y: y})
        print("Epoch: {}, w: {}, b: {}".format(epoch, w_value, b_value))
        epoch += 1

上面的代碼可以在tensorflow_examples項目中找到,經過訓練,我們看到輸出的斜率w約為2,截距b約為10,與我們構建的數據之間的關聯關系十分吻合!注意在TensorFlow代碼中并沒有實現最小二乘法等算法,也沒有if-else來控制代碼邏輯,完全是由數據驅動并且根據梯度下降算法動態調整Loss值學習出來的。這樣我們即使換了其他數據集,甚至換成圖像分類等其他領域的問題,無需修改代碼也可以由機器自動學習,這也是神經網絡和TensorFlow強大的地方。

Epoch: 1, w: -0.909195065498352, b: 9.612462043762207
Epoch: 2, w: 0.296161413192749, b: 10.418954849243164
Epoch: 3, w: 1.108984351158142, b: 10.283171653747559
Epoch: 4, w: 1.5482335090637207, b: 10.143315315246582
Epoch: 5, w: 1.7749555110931396, b: 10.063009262084961
Epoch: 6, w: 1.8906776905059814, b: 10.020986557006836
Epoch: 7, w: 1.9495772123336792, b: 9.999467849731445
Epoch: 8, w: 1.9795364141464233, b: 9.988500595092773
Epoch: 9, w: 1.994771122932434, b: 9.982922554016113
Epoch: 10, w: 2.0025179386138916, b: 9.980087280273438

前面的模型只有w和b兩個變量,如果數據處于非線性關系就難以得到很好的結果,因此我們建議使用深層神經網絡,這也是TensorFlow設計重點就要解決的深度學習模型。我們知道Google在2014年憑借Inception模型贏下了ImageNet全球競賽,里面代碼就是基于TensorFlow實現的,下面是較為復雜的模型定義代碼。

with tf.op_scope([inputs], scope, 'inception_v3'):
    with scopes.arg_scope([ops.conv2d, ops.fc, ops.batch_norm, ops.dropout], is_training=is_training):
        with scopes.arg_scope([ops.conv2d, ops.max_pool, ops.avg_pool], stride=1, padding='VALID'):
            
            # 299 x 299 x 3
            end_points['conv0'] = ops.conv2d(inputs, 32, [3, 3], stride=2, scope='conv0')
            
            # 149 x 149 x 32
            end_points['conv1'] = ops.conv2d(end_points['conv0'], 32, [3, 3], scope='conv1')
            
            # 147 x 147 x 32
            end_points['conv2'] = ops.conv2d(end_points['conv1'], 64, [3, 3], padding='SAME', scope='conv2')
            
            # 147 x 147 x 64
            end_points['pool1'] = ops.max_pool(end_points['conv2'], [3, 3], stride=2, scope='pool1')
            
            # 73 x 73 x 64
            end_points['conv3'] = ops.conv2d(end_points['pool1'], 80, [1, 1], scope='conv3')
            
            # 73 x 73 x 80
            end_points['conv4'] = ops.conv2d(end_points['conv3'], 192, [3, 3], scope='conv4')
            
            # 71 x 71 x 192
            end_points['pool2'] = ops.max_pool(end_points['conv4'], [3, 3], stride=2, scope='pool2')
            
            # 35 x 35 x 192
            net = end_points['pool2']
            

使用TensorFlow已經封裝好的全連接網絡、卷積神經網絡、RNN和LSTM,我們已經可以組合出各種網絡模型,實現Inception這樣的多層神經網絡如拼湊Lego一樣簡單。但在選擇優化算法、生成TFRecords、導出模型文件和支持分布式訓練上,這里有比較多的細節,接下來我們將在一篇文章的篇幅內介紹所有TensorFlow相關的核心使用技巧。

TensorFlow核心使用技巧

為了介紹TensorFlow的各種用法,我們將使用deep_recommend_system(https://github.com/tobegit3hub/deep_recommend_system)
這個開源項目,它實現了TFRecords、QueueRunner、Checkpoint、TensorBoard、Inference、GPU支持、分布式訓練和多層神經網絡模型等特性,而且可以輕易拓展實現Wide and deep等模型,在實際的項目開發中可以直接下載使用。

1. 準備訓練數據

一般TensorFlow應用代碼包含Graph的定義和Session的運行,代碼量不大可以封裝到一個文件中,如cancer_classifier.py文件。訓練前需要準備樣本數據和測試數據,一般數據文件是空格或者逗號分隔的CSV文件,但TensorFlow建議使用二進制的TFRecords格式,這樣可以支持QueuRunner和Coordinator進行多線程數據讀取,并且可以通過batch size和epoch參數來控制訓練時單次batch的大小和對樣本文件迭代訓練多少輪。如果直接讀取CSV文件,需要在代碼中記錄下一次讀取數據的指針,而且在樣本無法全部加載到內存時使用非常不便。

在data目錄,項目已經提供了CSV與TFRecords格式轉換工具generate_csv_tfrecords.py,參考這個腳本你就可以parse任意格式的CSV文件,轉成TensorFlow支持的TFRecords格式。無論是大數據還是小數據,通過簡單的腳本工具就可以直接對接TensorFlow,項目中還提供print_csv_tfrecords.py腳本來調用API直接讀取TFRecords文件的內容。

def generate_tfrecords(input_filename, output_filename):
    print("Start to convert {} to {}".format(input_filename, output_filename))
    writer = tf.python_io.TFRecordWriter(output_filename)

    for line in open(input_filename, "r"):
        data = line.split(",")
        label = float(data[9])
        features = [float(i) for i in data[:9]]

        example = tf.train.Example(features=tf.train.Features(feature={
            "label":
            tf.train.Feature(float_list=tf.train.FloatList(value=[label])),
            "features":
            tf.train.Feature(float_list=tf.train.FloatList(value=features)),
        }))
        writer.write(example.SerializeToString())

    writer.close()
    print("Successfully convert {} to {}".format(input_filename,
                                                 output_filename))

2. 接受命令行參數

有了TFRecords,我們就可以編寫代碼來訓練神經網絡模型了,但眾所周知,深度學習有過多的Hyperparameter需要調優,我們就優化算法、模型層數和不同模型都需要不斷調整,這時候使用命令行參數是非常方便的。

TensorFlow底層使用了python-gflags項目,然后封裝成tf.app.flags接口,使用起來非常簡單和直觀,在實際項目中一般會提前定義命令行參數,尤其在后面將會提到的Cloud Machine Learning服務中,通過參數來簡化Hyperparameter的調優。

# Define hyperparameters
flags = tf.app.flags
FLAGS = flags.FLAGS
flags.DEFINE_boolean("enable_colored_log", False, "Enable colored log")
flags.DEFINE_string("train_tfrecords_file",
                    "./data/a8a/a8a_train.libsvm.tfrecords",
                    "The glob pattern of train TFRecords files")
flags.DEFINE_string("validate_tfrecords_file",
                    "./data/a8a/a8a_test.libsvm.tfrecords",
                    "The glob pattern of validate TFRecords files")
flags.DEFINE_integer("feature_size", 124, "Number of feature size")
flags.DEFINE_integer("label_size", 2, "Number of label size")
flags.DEFINE_float("learning_rate", 0.01, "The learning rate")
flags.DEFINE_integer("epoch_number", 10, "Number of epochs to train")
flags.DEFINE_integer("batch_size", 1024, "The batch size of training")
flags.DEFINE_integer("validate_batch_size", 1024,
                     "The batch size of validation")
flags.DEFINE_integer("batch_thread_number", 1,
                     "Number of threads to read data")
flags.DEFINE_integer("min_after_dequeue", 100,
                     "The minimal number after dequeue")
flags.DEFINE_string("checkpoint_path", "./sparse_checkpoint/",
                    "The path of checkpoint")
flags.DEFINE_string("output_path", "./sparse_tensorboard/",
                    "The path of tensorboard event files")
flags.DEFINE_string("model", "dnn", "Support dnn, lr, wide_and_deep")
flags.DEFINE_string("model_network", "128 32 8", "The neural network of model")
flags.DEFINE_boolean("enable_bn", False, "Enable batch normalization or not")
flags.DEFINE_float("bn_epsilon", 0.001, "The epsilon of batch normalization")
flags.DEFINE_boolean("enable_dropout", False, "Enable dropout or not")
flags.DEFINE_float("dropout_keep_prob", 0.5, "The dropout keep prob")
flags.DEFINE_boolean("enable_lr_decay", False, "Enable learning rate decay")
flags.DEFINE_float("lr_decay_rate", 0.96, "Learning rate decay rate")
flags.DEFINE_string("optimizer", "adagrad", "The optimizer to train")
flags.DEFINE_integer("steps_to_validate", 10,
                     "Steps to validate and print state")
flags.DEFINE_string("mode", "train", "Support train, export, inference")
flags.DEFINE_string("saved_model_path", "./sparse_saved_model/",
                    "The path of the saved model")
flags.DEFINE_string("model_path", "./sparse_model/", "The path of the model")
flags.DEFINE_integer("model_version", 1, "The version of the model")
flags.DEFINE_string("inference_test_file", "./data/a8a_test.libsvm",
                    "The test file for inference")
flags.DEFINE_string("inference_result_file", "./inference_result.txt",
                    "The result file from inference")

3. 定義神經網絡模型

準備完數據和參數,最重要的還是要定義好網絡模型,定義模型參數可以很簡單,創建多個Variable即可,也可以做得比較復雜,例如使用使用tf.variable_scope()和tf.get_variables()接口。為了保證每個Variable都有獨特的名字,而且能都輕易地修改隱層節點數和網絡層數,我們建議參考項目中的代碼,尤其在定義Variables時注意要綁定CPU,TensorFlow默認使用GPU可能導致參數更新過慢。

# Define the model
input_units = FEATURE_SIZE
hidden1_units = 10
hidden2_units = 10
hidden3_units = 10
hidden4_units = 10
output_units = LABEL_SIZE

def full_connect(inputs, weights_shape, biases_shape):
    with tf.device('/gpu:0'):
        weights = tf.get_variable("weights", weights_shape, 
                                 initializer=tf.random_normal_initializer())
        biases = tf.get_variable("biases", biases_shape,
                                 initializer=tf.random_normal_initializer())
        return tf.matmul(inputs, weights) + biases

def full_connect_relu(inputs, weights_shape, biases_shape):
    return tf.nn.relu(full_connect(inputs, weights_shape, biases_shape))

def deep_inference(inputs):
    with tf.variable_scope("layer1"):
        layer = full_connect_relu(inputs, [input_units, hidden1_units],
                                 [hidden1_units])
    with tf.variable_scope("layer2"):
        layer = full_connect_relu(inputs, [hidden1_units, hidden2_units],
                                 [hidden2_units])
    with tf.variable_scope("layer3"):
        layer = full_connect_relu(inputs, [hidden2_units, hidden3_units],
                                 [hidden3_units])
    with tf.variable_scope("layer4"):
        layer = full_connect_relu(inputs, [hidden3_units, hidden4_units],
                                 [hidden4_units])
    with tf.variable_op_scope("output"):
        layer = full_connect_relu(inputs, [hidden4_units, output_units],
                                 [output_units])
    return layer

上述代碼在生產環境也十分常見,無論是訓練、實現inference還是驗證模型正確率和auc時都會用到。項目中還基于此代碼實現了Wide and deep模型,在Google Play應用商店的推薦業務有廣泛應用,這也是適用于普遍的推薦系統,將傳統的邏輯回歸模型和深度學習的神經網絡模型有機結合在一起。

4. 使用不同的優化算法

定義好網絡模型,我們需要覺得使用哪種Optimizer去優化模型參數,是應該選擇Sgd、Rmsprop還是選擇Adagrad、Ftrl呢?對于不同場景和數據集沒有固定的答案,最好的方式就是實踐,通過前面定義的命令行參數我們可以很方便得使用不同優化算法來訓練模型。

def get_optimizer(optimizer, learning_rate):
    logging.info("Use the optimizer: {}".format(optimizer))
    if optimizer == "sgd":
        return tf.train.GradientDescentOptimizer(learning_rate)
    elif optimizer == "adadelta":
        return tf.train.AdadeltaOptimizer(learning_rate)
    elif optimizer == "adagrad":
        return tf.train.AdagradOptimizer(learning_rate)
    elif optimizer == "adam":
        return tf.train.AdamOptimizer(learning_rate)
    elif optimizer == "ftrl":
        return tf.train.FtrlOptimizer(learning_rate)
    elif optimizer == "rmsprop":
        return tf.train.RMSPropOptimizer(learning_rate)
    else:
        logging.error("Unknow optimizer, exit now")
        exit(1)

在生產實踐中,不同優化算法在訓練結果、訓練速度上都有很大差異,過度優化網絡參數可能效果沒有使用其他優化算法來得有效,因此選用正確的優化算法也是Hyperparameter調優中很重要的一步,通過在TensorFlow代碼中加入這段邏輯也可以很好地實現對應的功能。

5. Online learning與Continuous learning

很多機器學習廠商都會宣稱自己的產品支持Online learning,其實這只是TensorFlow的一個基本的功能,就是支持在線數據不斷優化模型。TensorFlow可以通過tf.train.Saver()來保存模型和恢復模型參數,使用Python加載模型文件后,可不斷接受在線請求的數據,更新模型參數后通過Saver保存成checkpoint,用于下一次優化或者線上服務。

# Create Session to run graph
with tf.Session() as sess:
    summary_op = tf.merge_all_summaries()
    write = tf.train.SummaryWriter(tensorboard_dir, sess.graph)
    sess.run(init_op)
    sess.run(tf.initialize_local_variables())
    
    if mode == "train" or mode == "train_from_scratch":
        if mode != "train_from_scratch":
            ckpt = tf.train.get_checkpoint_state(checkpoint_dir)
            if ckpt and ckpt.model_checkpoint_path:
                print("Continue training from the model {}".format(ckpt.model_checkpoint_path))
                saver.restore(sess, ckpt.model_checkpoint_path)

而Continuous training是指訓練即使被中斷,也能繼續上一次的訓練結果繼續優化模型,在TensorFlow中也是通過Saver和checkpoint文件來實現。在deep_recommend_system項目默認能從上一次訓練中繼續優化模型,也可以在命令行中指定train_from_scratch,不僅不用擔心訓練進程被中斷,也可以一邊訓練一邊做inference提供線上服務。

6. 使用TensorFlow優化參數

TensorFlow還集成了一個功能強大的圖形化工具,也即是TensorBoard,一般只需要在代碼中加入我們關心的訓練指標,TensorBoard就會自動根據這些參數繪圖,通過可視化的方式來了解模型訓練的情況。

tf.scalar_summary(‘loss’, loss)
tf.scalar_summary(‘accuracy’, accuracy)
tf.scalar_summary(‘auc’, auc_op)
image.png

7. 分布式TensorFlow應用

最后不得不介紹TensorFlow強大的分布式計算功能,傳統的計算框架如Caffe,原生不支持分布式訓練,在數據量巨大的情況下往往無法通過增加機器scale out。TensorFlow承載了Google各個業務PB級的數據,在設計之初就考慮到分布式計算的需求,通過gRPC、Protobuf等高性能庫實現了神經網絡模型的分布式計算。

實現分布式TensorFlow應用并不難,構建Graph代碼與單機版相同,我們實現了一個分布式的cancer_classifier.py例子,通過下面的命令就可以啟動多ps多worker的訓練集群。

cancer_classifier.py --ps_hosts=127.0.0.1:2222,127.0.0.1:2223 --worker_hosts=127.0.0.1:2224,127.0.0.1:2225 --job_name=ps --task_index=0
 
cancer_classifier.py --ps_hosts=127.0.0.1:2222,127.0.0.1:2223 --worker_hosts=127.0.0.1:2224,127.0.0.1:2225 --job_name=ps --task_index=1
 
cancer_classifier.py --ps_hosts=127.0.0.1:2222,127.0.0.1:2223 --worker_hosts=127.0.0.1:2224,127.0.0.1:2225 --job_name=worker --task_index=0
 
cancer_classifier.py --ps_hosts=127.0.0.1:2222,127.0.0.1:2223 --worker_hosts=127.0.0.1:2224,127.0.0.1:2225 --job_name=worker --task_index=1

在深入閱讀代碼前,我們需要了解分布式TensorFlow中ps、worker、in-graph、between-graph、synchronous training和asynchronous training的概念。首先ps是整個訓練集群的參數服務器,保存模型的Variable,worker是計算模型梯度的節點,得到的梯度向量會交付給ps更新模型。in-graph與between-graph對應,但兩者都可以實現同步訓練和異步訓練,in-graph指整個集群由一個client來構建graph,并且由這個client來提交graph到集群中,其他worker只負責處理梯度計算的任務,而between-graph指的是一個集群中多個worker可以創建多個graph,但由于worker運行的代碼相同因此構建的graph也相同,并且參數都保存到相同的ps中保證訓練同一個模型,這樣多個worker都可以構建graph和讀取訓練數據,適合大數據場景。同步訓練和異步訓練差異在于,同步訓練每次更新梯度需要阻塞等待所有worker的結果,而異步訓練不會有阻塞,訓練的效率更高,在大數據和分布式的場景下一般使用異步訓練。

8. Cloud Machine Learning

前面已經介紹了TensorFlow相關的全部內容,細心的網友可能已經發現,TensorFlow功能強大,但究其本質還是一個library,用戶除了編寫TensorFlow應用代碼還需要在物理機上起服務,并且手動指定訓練數據和模型文件的目錄,維護成本比較大,而且機器之間不可共享。

縱觀大數據處理和資源調度行業,Hadoop生態儼然成為了業界的標準,通過MapReduce或Spark接口來處理數據,用戶通過API提交任務后由Yarn進行統一的資源分配和調度,不僅讓分布式計算成為可能,也通過資源共享和統一調度平的臺極大地提高了服務器的利用率。很遺憾TensorFlow定義是深度學習框架,并不包含集群資源管理等功能,但開源TensorFlow以后,Google很快公布了Google Cloud ML服務,我們從Alpha版本開始已經是Cloud ML的早期用戶,深深體會到云端訓練深度學習的便利性。通過Google Cloud ML服務,我們可以把TensorFlow應用代碼直接提交到云端運行,甚至可以把訓練好的模型直接部署在云上,通過API就可以直接訪問,也得益于TensorFlow良好的設計,我們基于Kubernetes和TensorFlow serving實現了Cloud Machine Learning服務,架構設計和使用接口都與Google Cloud ML類似。

image.png

TensorFlow是很好深度學習框架,對于個人開發者、科研人員已經企業都是值得投資的技術方向,而Cloud Machine Learning可以解決用戶在環境初始化、訓練任務管理以及神經網絡模型的在線服務上的管理和調度問題。目前Google Cloud ML已經支持automatically hyperparameter tunning,參數調優未來也將成為計算問題而不是技術問題,即使有的開發者使用MXNet或者其他,而不是TensorFlow,我們也愿意與更多深度學習用戶和平臺開發者交流,促進社區的發展。

總結

總結一下,本文主要介紹TensorFlow深度學習框架的學習與應用,通過deep_recommend_system項目介紹了下面使用TensorFlow的8個核心要點,也歡迎大家下載源碼試用和反饋。

  1. 準備訓練數據

  2. 接受命令行參數

  3. 定義神經網絡模型

  4. 使用不同的優化算法

  5. Online learning與Continuous learning

  6. 使用TensorBoard優化參數

  7. 分布式TensorFlow應用

  8. Cloud Machine Learning

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容