構(gòu)建并用 TensorFlow Serving 部署 Wide & Deep 模型

Wide & Deep 模型是谷歌在 2016 年發(fā)表的論文中所提到的模型。在論文中,谷歌將 LR 模型與 深度神經(jīng)網(wǎng)絡(luò) 結(jié)合在一起作為 Google Play 的推薦獲得了一定的效果。在這篇論文后,Youtube,美團等公司也進行了相應(yīng)的嘗試并公開了他們的工作(相關(guān)鏈接請看本文底部)

官方提供的 Wide & Deep 模型的(簡稱,WD 模型)教程 都是使用 TensorFlow (簡稱,TF )自帶的函數(shù)來做的特征工程,并且模型也進行了封裝,但有時候我們的特征工程還使用到了 sklearn, numpy, pandas 來做,當我們想快速驗證 WD 模型是否比舊模型要好的時候則顯得不太便利,所以本文就向您展示了如何自己用 TF 搭建一個結(jié)構(gòu)清晰,定制性更高的 WD 模型。在訓練好 WD 模型后,我們還需要快速的看到模型預(yù)測的效果,所以在本文中我們利用 Docker 來快速部署一個可供服務(wù)的 TensorFlow 模型,也即可提供服務(wù)的 API。

因此,本文的內(nèi)容如下:

  • 使用 TF 搭建 WD 網(wǎng)絡(luò)結(jié)構(gòu)
  • 使用 Docker 來快速部署模型

其對應(yīng)的代碼地址為:https://github.com/edvardHua/Articles
歡迎 star

本文實現(xiàn)的 WD 模型的結(jié)構(gòu)如下圖所示:

本文構(gòu)建的網(wǎng)絡(luò)

不難看出,Wide 模型這邊其實就是一個 LR 模型,而右邊 Deep 模型的部分則是一個三層隱藏層的神經(jīng)網(wǎng)絡(luò),這三層隱藏層的神經(jīng)元數(shù)目分別是 256-12-64,最后 Wide 模型 和 Deep 模型的結(jié)果進行相加后通過 ReLu 激活函數(shù)后輸出預(yù)測結(jié)果。OK,先來看一下 Wide 模型部分的代碼

def wide_model(input_data):
    """
    一層的神經(jīng)網(wǎng)絡(luò),相當于是 LR
    :param input_data: 
    :return: 
    """
    input_len = int(input_data.shape[1])
    with tf.name_scope("wide"):
        # 修正的方式初始化權(quán)重,輸出層結(jié)點只有一個
        weights = tf.Variable(tf.truncated_normal([input_len, 1],
                                                  stddev=1.0 / math.sqrt(float(input_len))
                                                  ), name="weights"
                              )
        output = tf.matmul(input_data, weights)
        # 沿著行這個緯度來求和
        output = tf.reduce_sum(output, 1, name="reduce_sum")
        # 輸出每個樣本經(jīng)過計算的值
        output = tf.reshape(output, [-1, 1])
    return output

接下來看一下 Deep 模型的代碼。

def deep_model(input_data, hidden1_units, hidden2_units, hidden3_units):
    """
    三層的神經(jīng)網(wǎng)絡(luò)
    :param input_data: 2-D tensor
    :param hidden1_units: int
    :param hidden2_units: int
    :param hidden3_units: int
    :return: 
    """
    # 得到每個樣本的維度
    input_len = int(input_data.shape[1])
    with tf.name_scope("hidden1"):
        # 修正的方式初始化權(quán)重
        weights = tf.Variable(tf.truncated_normal([input_len, hidden1_units],
                                                  stddev=1.0 / math.sqrt(float(input_len))
                                                  ), name="weights1"
                              )
        biases = tf.Variable(tf.zeros([hidden1_units]), name='biases1')
        hidden1 = tf.nn.relu(tf.matmul(input_data, weights)) + biases

    ···
    # 另外兩層隱藏層代碼相似,所以這里省略掉,具體的代碼請看 Github 倉庫
    ··· 

    with tf.name_scope("output"):
        # 修正的方式初始化權(quán)重
        weights = tf.Variable(tf.truncated_normal([hidden3_units, 1],
                                                  stddev=1.0 / math.sqrt(float(input_len))
                                                  ), name="weights4"
                              )
        biases = tf.Variable(tf.zeros([1]), name='biases4')
        output = tf.nn.relu(tf.matmul(hidden3, weights) + biases)

    return tf.nn.relu(output)

雖然 Deep 模型的代碼存在一定的冗余,但是這樣方便我們修改和調(diào)整網(wǎng)絡(luò)的結(jié)構(gòu)。

最后,將 Wide 模型 和 Deep 模型的結(jié)果進行相加后通過 ReLu 激活函數(shù)輸出預(yù)測的結(jié)果。

def build_wdl(deep_input, wide_input, y):
    """
    得到模型和損失函數(shù)
    :param deep_input: 
    :param wide_input: 
    :param y: 
    :return: 
    """
    central_bias = tf.Variable([np.random.randn()], name="central_bias")
    dmodel = deep_model(deep_input, 256, 128, 64)
    wmodel = wide_model(wide_input)

    # 使用 LR 將兩個模型組合在一起
    dmodel_weight = tf.Variable(tf.truncated_normal([1, 1]), name="dmodel_weight")
    wmodel_weight = tf.Variable(tf.truncated_normal([1, 1]), name="wmodel_weight")

    network = tf.add(
        tf.matmul(dmodel, dmodel_weight),
        tf.matmul(wmodel, wmodel_weight)
    )

    prediction = tf.nn.sigmoid(tf.add(network, central_bias), name="prediction")

    loss = tf.reduce_mean(
        tf.nn.sigmoid_cross_entropy_with_logits(labels=y, logits=prediction)
    )
    train_step = tf.train.AdamOptimizer(0.001).minimize(loss)
    return train_step, loss, prediction

搭建好結(jié)構(gòu)后,我們可以生成一些隨機數(shù)據(jù)來測試 Wide & Deep 模型,在這里我們隨機生成 1000 個樣本,每個樣本的維度為 10,作為訓練樣本,為了簡單起見,沒有再創(chuàng)建驗證樣本。
訓練只迭代一次,也即只遍歷一次訓練樣本,這里的每個樣本的 label 取值都為 0 或 1,所以目標函數(shù)為交叉熵,代碼如下:

def build_and_saved_wdl():
    """
    訓練并保存模型
    :return: 
    """
    # 訓練數(shù)據(jù)
    x_deep_data = np.random.rand(10000)
    x_deep_data = x_deep_data.reshape(-1, 10)

    x_wide_data = np.random.rand(10000)
    x_wide_data = x_wide_data.reshape(-1, 10)

    x_deep = tf.placeholder(tf.float32, [None, 10])
    x_wide = tf.placeholder(tf.float32, [None, 10])
    y = tf.placeholder(tf.float32, [None, 1])

    y_data = np.array(
        [random.randint(0, 1) for i in range(1000)]
    )
    y_data = y_data.reshape(-1, 1)

    # 為了簡單起見,這里沒有驗證集,也就沒有驗證集的 loss
    train_step, loss, prediction = build_wdl(x_deep, x_wide, y)
    sess = tf.Session()
    sess.run(tf.global_variables_initializer())
    sess.run(train_step, feed_dict={x_deep: x_deep_data, x_wide: x_wide_data, y: y_data})

訓練完成后,還需要將模型進行保存,若要在 TensorFlow Serving 中使用,則需要用 SavedModelBuilder 來保存模型,代碼如下:

def build_and_saved_wdl():
   ···
   # 將訓練好的模型保存在當前的文件夾下
   builder = tf.saved_model.builder.SavedModelBuilder(join("./model_name", MODEL_VERSION))
   inputs = {
       "x_wide": tf.saved_model.utils.build_tensor_info(x_wide),
       "x_deep": tf.saved_model.utils.build_tensor_info(x_deep)
   }
   output = {"output": tf.saved_model.utils.build_tensor_info(prediction)}
   prediction_signature = tf.saved_model.signature_def_utils.build_signature_def(
       inputs=inputs,
       outputs=output,
       method_name=tf.saved_model.signature_constants.PREDICT_METHOD_NAME
   )

   builder.add_meta_graph_and_variables(
       sess,
       [tf.saved_model.tag_constants.SERVING],
       {tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY: prediction_signature}
   )
   builder.save()

這里需要注意的是 MODEL_VERSION 必須為數(shù)字(代表著模型的版本),TF Serving 默認只會加載數(shù)字最大的那個模型,譬如說在這里我們執(zhí)行完代碼后,保存模型的 model_name 的文件夾的目錄如下:

λ root [tf_demo/servering/model_name] → tree
.
└── 1
    ├── saved_model.pb
    └── variables
        ├── variables.data-00000-of-00001
        └── variables.index

2 directories, 3 files 

保存完模型后,在這里我們使用容器來部署模型,當然你也可以選擇自己在機器上配置相關(guān)的環(huán)境,我們使用的鏡像是由 Bitnami 提供的(Dockerhub 的地址請戳這里),當你需要部署模型時,只需要將模型所在的路徑映射到容器中的 /bitnami/model-data 路徑下即可,也即是鍵入如下命令

λ edvard [tf_demo/servering/model_name] → docker run -it -p 5000:9000 --volume /root/tf_demo/servering/model_name:/bitnami/model-data bitnami/tensorflow-serving
 
Welcome to the Bitnami tensorflow-serving container
 
...
 
2017-11-01 03:43:55.983106: I tensorflow_serving/core/loader_harness.cc:86] Successfully loaded servable version {name: inception version: 1}
2017-11-01 03:43:55.986130: I tensorflow_serving/model_servers/main.cc:288] Running ModelServer at 0.0.0.0:9000 ...

這里可能需要一些 Docker 相關(guān)的知識,我在參考資料中提供了一份很不錯的 Gitbook 入門書籍,感興趣的可以看看。

我們將容器中的服務(wù)映射到了宿主機的 5000 端口,接下來我們來測試一下 API 接口。代碼如下:

def test_servable_api():
    """
    測試 API
    :return: 
    """
    # 隨機產(chǎn)生 10 條測試數(shù)據(jù)
    x_deep_data = np.random.rand(100).reshape(-1, 10)

    x_wide_data = np.random.rand(100).reshape(-1, 10)

    channel = implementations.insecure_channel('127.0.0.1', int(5000))
    stub = prediction_service_pb2.beta_create_PredictionService_stub(channel)

    # 發(fā)送請求
    request = predict_pb2.PredictRequest()
    request.model_spec.name = 'inception'
    request.model_spec.signature_name = tf.saved_model.signature_constants.DEFAULT_SERVING_SIGNATURE_DEF_KEY
    request.inputs[INPUT_WIDE_KEY].CopyFrom(
        tf.contrib.util.make_tensor_proto(x_wide_data, shape=[10, WIDE_DIM], dtype=tf.float32))
    request.inputs[INPUT_DEEP_KEY].CopyFrom(
        tf.contrib.util.make_tensor_proto(x_deep_data, shape=[10, DEEP_DIM], dtype=tf.float32))
    # 10 秒超時
    res = stub.Predict(request, 10.0)

    pprint(res.outputs[OUTPUT_KEY])

輸出的預(yù)測結(jié)果的結(jié)構(gòu)如下:

dtype: DT_FLOAT
tensor_shape {
  dim {
    size: 10
  }
  dim {
    size: 1
  }
}
float_val: 0.355874538422
float_val: 0.3225004673
float_val: 0.32104665041
float_val: 0.233089879155
float_val: 0.376621931791
float_val: 0.144557282329
float_val: 0.34686845541
float_val: 0.304817527533
float_val: 0.367866277695
float_val: 0.393035560846

參考資料

谷歌 Wide & Deep 論文
Youtube 深度推薦的論文
美團點評深度排序技術(shù)文章
Docker 從入門到實踐(Gitbook)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容