large{<--------收藏別忘記點贊-------->}	ag{^_^}

前言

在線廣告CTR或者推薦CTR預估模型都面臨一個稀疏數據的問題,傳統的解決思路是LR+人工特徵組合,這種方式因為特別依賴專家經驗而逐漸被放棄;後來演化了自動特徵組合的模型,比如FM和FFM,能較好的解決低階特徵自動組合的問題,或者說只能選擇一種特定維度的特徵組合方式,比如K=2或者K=18的二階特徵組合(一般為了避免過擬合選擇的K都比較小),可以認為FM和FFM能夠解決低階特徵組合的問題;那如何進行高階特徵組合且不會導致過擬合呢?有兩種解決思路:

  • 使用決策樹進行特徵組合:決策樹根據信息熵增益或者gini係數自動決定分裂點,通過前剪枝、後剪枝、feature random select、正則化等方法抑制過擬合。
  • 使用神經網路進行特徵組合,通過embedding+全連接網路進行高階特徵組合,並使用BP、early stopping、正則化、drop out等技術手段來抑制過擬合。

本文主要講解的是CTR深度學習預估模型deep FM如何訓練、評估以及服務化。

特徵工程

特徵工程是一個師機器學習工程師必須掌握的技能,數據加工的好壞會比選擇什麼模型帶來更高的指標提升。傳統的機器學習模型對特徵工程依賴度較高,深度學習模型可以通過多隱層堆疊、每一層對上一層的輸出進行處理的機制,對輸入信號進行逐層加工,從而把初始的、與輸出目標之間聯繫不太緊密的輸入表示,轉化成與輸出目標聯繫更加密切的表示形式,用簡單的模型即可完成複雜的分類、回歸預測,因此可以將深度學習理解為"特徵學習"。

雖然深度學習模型可以自動的進行特徵學習,但是並不意味著可以對原始特徵不做任何處理,比如embedding層的shape需要定義,這個shape值還是需要根據數據決定的。

一般來講,訓練數據的特徵分為兩種形式:

  • 單值離散特徵:比如分類特徵
  • list離散特徵:比如文本特徵中的sentence
  • 連續特徵:一般都是單值的連續數值

對離散特徵需要將其進行編碼,如果是分類特徵,可以使用label encoder或者feature hasher的方式;如果是sentence,那麼就需要考慮使用word count, idf等。本文使用的數據集依然是avazu的ctr競賽數據集,只含有單值離散特徵。

Deep FM模型簡介

deepFM(A Factorization-Machine based Neural Network for CTR Prediction)模型是華為諾亞方舟實驗室做的一個模型,主要解決的是推薦系統點擊率模型如何融合低階特徵組合以及高階特徵組合的問題。從字面上很好理解,其解決思路就是使用了FM進行低階特徵組合,使用深度網路進行高階特徵組合。整體架構圖如下所示:

deepFM架構圖

其主要解決思路來自谷歌在2017年發表的Wide & Deep模型,Wide & Deep模型如下所示:

WDL架構圖

與Wide & Deep模型相比,Deep FM的低階特徵組合與高階特徵組合共享輸入,避免了Wide & Deep模型需要預處理低階特徵組合模型的輸入,同時通過對低階特徵組合模型與高階特徵組合模型的組合優化,獲得了更好的泛化能力。

模型訓練

建模主要使用了tensorflow的keras API,對sparse特徵進行label encoder之後,使用embedding layer輸入;對連續特徵歸一化後進dense layer,參照了DeepFM with tensorflow 以及另一位大神的思路。這裡沒有連續特徵,所以只對sparse特徵進行處理,進embedding層之前首先需要判斷embeding層的size,代碼如下:

SingleFeat = collections.namedtuple(
SingleFeat, [name, dimension, ])
sparse_feature_list = [SingleFeat(feat, data[feat].nunique())
for feat in sparse_features]

使用SingleFeat保存sparse特徵在embedding層的權重數目。

模型訓練代碼如下所示:

import pandas as pd
from sklearn.preprocessing import LabelEncoder, MinMaxScaler
from sklearn.model_selection import train_test_split
from sklearn.metrics import log_loss, roc_auc_score
from deepctr.models import DeepFM
from deepctr import SingleFeat
# from tensorflow.keras.utils import multi_gpu_model
import argparse
import os
from tensorflow.python.keras.metrics import binary_crossentropy
from deepctr.udf_metrics import auc
from tensorflow.python.keras.callbacks import EarlyStopping
import tensorflow as tf
from tensorflow import keras
import shutil
import pickle
import numpy as np

def batcher(X_, y_=None, batch_size=-1):
"""
keras fit_generator
:param X_:
:param y_:
:param batch_size:
:return:
"""
while True:
n_samples = X_[0].shape[0]
assert n_samples == y_.shape[0], "input data sample shape not equal to label shape"

if batch_size == -1:
batch_size = n_samples
if batch_size < 1:
raise ValueError(Parameter batch_size={} is unsupported.format(batch_size))

for i in range(0, n_samples, batch_size):
upper_bound = min(i + batch_size, n_samples)
feature_batch = []
for fea in X_:
feature_batch.append(fea[i:upper_bound])
ret_y = None
if y_ is not None:
ret_y = y_[i:upper_bound]
yield (feature_batch, ret_y)

if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument(-m, --mode, action=store, default="gpu", dest=mode, help="run mode")
args = parser.parse_args()

data_dir = os.path.abspath(f"{os.path.abspath(os.path.dirname(os.path.realpath(__file__)))}/../../Data/avazu_ctr/")
model_dir = os.path.abspath(
f"{os.path.abspath(os.path.dirname(os.path.realpath(__file__)))}/../../Data/avazu_serving/")
data = pd.read_csv(f{data_dir}/train_sample.txt)

# data_online =
# data = pd.read_csv(f{data_dir}/train.txt)

# sparse_features = [C + str(i) for i in range(1, 27)]
# dense_features = [I+str(i) for i in range(1, 14)]
sparse_features = [hour, C1, banner_pos, site_id, site_domain,
site_category, app_id, app_domain, app_category, device_id,
device_ip, device_model, device_type, device_conn_type, C14,
C15, C16, C17, C18, C19, C20, C21]
dense_features = []

data[sparse_features] = data[sparse_features].fillna(-1, )
data[dense_features] = data[dense_features].fillna(0, )
target = [click]

# 1.Label Encoding for sparse features,and do simple Transformation for dense features
les = {}
for feat in sparse_features:
lbe = LabelEncoder()
data[feat] = lbe.fit_transform(data[feat])
les[feat] = lbe

loabel_encoder_file = f"{data_dir}/label_encodel.pkl"
with open(loabel_encoder_file, wb) as f:
pickle.dump(les, f, -1)

# mms = MinMaxScaler(feature_range=(0, 1))
# data[dense_features] = mms.fit_transform(data[dense_features])

# 2.count #unique features for each sparse field,and record dense feature field name

sparse_feature_list = [SingleFeat(feat, data[feat].nunique())
for feat in sparse_features]
dense_feature_list = [SingleFeat(feat, 0)
for feat in dense_features]

# 3.generate input data for model
train, test = train_test_split(data, test_size=0.2)
train_model_input = [train[feat.name].values.reshape((-1, 1)) for feat in sparse_feature_list]
test_model_input = [test[feat.name].values.reshape((-1, 1)) for feat in sparse_feature_list]

refit_data_input = [data[feat.name].values.reshape((-1, 1)) for feat in sparse_feature_list]

# 4.Define Model,train,predict and evaluate
model = DeepFM({"sparse": sparse_feature_list,
}, final_activation=sigmoid)
auc_stop = EarlyStopping(monitor=val_auc, min_delta=0.0001, patience=0, verbose=1, mode=max)
call_backs = [auc_stop]
model.summary()
if args.mode == cpu:
print(f"run mode in cpu")
# history = model.fit_generator(train_model_input, train[target].values,
# batch_size=256, epochs=10, verbose=2, validation_split=0.2, )
model.compile("adam", "binary_crossentropy",
metrics=[binary_crossentropy, auc], )

with keras.backend.get_session() as sess:
init_op = tf.global_variables_initializer()
sess.run(init_op)
history = model.fit_generator(
batcher(train_model_input, train[target].values.reshape((-1, 1)), 256),
train_model_input[0].shape[0] // 256,
epochs=1,
verbose=1,
validation_data=(test_model_input, test[target].values.reshape((-1, 1))),
use_multiprocessing=True,
callbacks=call_backs
)
pred_ans = model.predict(test_model_input, batch_size=256)
print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))
pred_all = model.predict(refit_data_input, batch_size=256)
print("all AUC", round(roc_auc_score(data[target].values, pred_all), 4))
pred_score_file = f"{data_dir}/all_predict.npy"
np.save(pred_score_file, pred_all)
print(f"all predict score is {pred_all}")
model_dir = f"{model_dir}/1"
if os.path.isdir(model_dir):
print(
Already saved a model, cleaning up
)
shutil.rmtree(model_dir)
tf.saved_model.simple_save(
sess,
model_dir,
inputs={i.name: i for i in model.inputs},
outputs={"ctr": model.output})
else:
print(f"run mode in gpu")
pass
# gpu_model = multi_gpu_model(
# model=model,
# gpus=[1, 2, 3]
# )
# gpu_model.compile("adam", "binary_crossentropy",
# metrics=[binary_crossentropy, auc], )
# history = gpu_model.fit_generator(
# batcher(train_model_input, train[target].values.reshape((-1, 1)), 256),
# train_model_input[0].shape[0] // 256,
# epochs=10,
# verbose=1,
# validation_data=(test_model_input, test[target].values.reshape((-1, 1))),
# callbacks=call_backs
# )
# pred_ans = gpu_model.predict(test_model_input, batch_size=256)
# print("test LogLoss", round(log_loss(test[target].values, pred_ans), 4))
# print("test AUC", round(roc_auc_score(test[target].values, pred_ans), 4))

支持CPU和GPU兩種訓練模式,具體哪一種訓練模式需要根據命令行參數決定;這裡對Deep FM模型做了一個整體的封裝,這種封裝形式將sparse特徵和連續特徵的處理方式標準化,而且支持LR+FM+DEEP參數配置,可以選擇不同的特徵組合方式來進行訓練,比如只使用DEEP、只使用FM、只使用LR、或者兩兩組合。

增加了early stopping,模型在訓練的時候可以根據valid數據集上面的AUC分數是否還在繼續優化決定當前epoch模型訓練是否退出。

模型保存

模型保存代碼如下所示:

model_dir = f"{model_dir}/1"
if os.path.isdir(model_dir):
print(
Already saved a model, cleaning up
)
shutil.rmtree(model_dir)
tf.saved_model.simple_save(
sess,
model_dir,
inputs={i.name: i for i in model.inputs},
outputs={"ctr": model.output})

需要注意的是,模型保存的時候要帶上版本號,否則在模型載入的時候會報錯;此外,官方給的例子只有這幾行代碼,但是實際上需要模型訓練的時候進行全局初始化,否則模型保存時候會報出有未初始化的值。

docker安裝

環境配置的難題

軟體開發最大的麻煩事之一,就是環境配置。用戶計算機的環境都不相同,你怎麼知道自家的軟體,能在那些機器跑起來?

用戶必須保證兩件事:操作系統的設置,各種庫和組件的安裝。只有它們都正確,軟體才能運行。舉例來說,安裝一個 Python 應用,計算機必須有 Python 引擎,還必須有各種依賴,可能還要配置環境變數。

如果某些老舊的模塊與當前環境不兼容,那就麻煩了。開發者常常會說:"它在我的機器可以跑了"(It works on my machine),言下之意就是,其他機器很可能跑不了。

環境配置如此麻煩,換一臺機器,就要重來一次,曠日費時。很多人想到,能不能從根本上解決問題,軟體可以帶環境安裝?也就是說,安裝的時候,把原始環境一模一樣地複製過來。

虛擬機

虛擬機(virtual machine)就是帶環境安裝的一種解決方案。它可以在一種操作系統裡面運行另一種操作系統,比如在 Windows 系統裡面運行 Linux 系統。應用程序對此毫無感知,因為虛擬機看上去跟真實系統一模一樣,而對於底層系統來說,虛擬機就是一個普通文件,不需要了就刪掉,對其他部分毫無影響。

雖然用戶可以通過虛擬機還原軟體的原始環境。但是,這個方案有幾個缺點。

資源佔用多

虛擬機會獨佔一部分內存和硬碟空間。它運行的時候,其他程序就不能使用這些資源了。哪怕虛擬機裡面的應用程序,真正使用的內存只有 1MB,虛擬機依然需要幾百 MB 的內存才能運行。

冗餘步驟多

虛擬機是完整的操作系統,一些系統級別的操作步驟,往往無法跳過,比如用戶登錄。

啟動慢

啟動操作系統需要多久,啟動虛擬機就需要多久。可能要等幾分鐘,應用程序才能真正運行。

Linux 容器

由於虛擬機存在這些缺點,Linux 發展出了另一種虛擬化技術:Linux 容器(Linux Containers,縮寫為 LXC)。

Linux 容器不是模擬一個完整的操作系統,而是對進程進行隔離。或者說,在正常進程的外面套了一個保護層。對於容器裡面的進程來說,它接觸到的各種資源都是虛擬的,從而實現與底層系統的隔離。

由於容器是進程級別的,相比虛擬機有很多優勢。

啟動快

容器裡面的應用,直接就是底層系統的一個進程,而不是虛擬機內部的進程。所以,啟動容器相當於啟動本機的一個進程,而不是啟動一個操作系統,速度就快很多。

資源佔用少

容器只佔用需要的資源,不佔用那些沒有用到的資源;虛擬機由於是完整的操作系統,不可避免要佔用所有資源。另外,多個容器可以共享資源,虛擬機都是獨享資源。

體積小

容器只要包含用到的組件即可,而虛擬機是整個操作系統的打包,所以容器文件比虛擬機文件要小很多。

總之,容器有點像輕量級的虛擬機,能夠提供虛擬化的環境,但是成本開銷小得多。

docker微服務

Docker 屬於 Linux 容器的一種封裝,提供簡單易用的容器使用介面。它是目前最流行的 Linux 容器解決方案。

Docker 將應用程序與該程序的依賴,打包在一個文件裡面。運行這個文件,就會生成一個虛擬容器。程序在這個虛擬容器裏運行,就好像在真實的物理機上運行一樣。有了 Docker,就不用擔心環境問題。

總體來說,Docker 的介面相當簡單,用戶可以方便地創建和使用容器,把自己的應用放入容器。容器還可以進行版本管理、複製、分享、修改,就像管理普通的代碼一樣。

Docker 的主要用途,目前有三大類。

(1)提供一次性的環境。比如,本地測試他人的軟體、持續集成的時候提供單元測試和構建的環境。

(2)提供彈性的雲服務。因為 Docker 容器可以隨開隨關,很適合動態擴容和縮容。

(3)組建微服務架構。通過多個容器,一臺機器可以跑多個服務,因此在本機就可以模擬出微服務架構。

安裝流程

以macOS為例,docker安裝非常的簡單:brew cask install homebrew/cask/docker,點擊鯨魚可以彈出操作菜單:

docker下拉菜單

鏡像加速:

鏡像加速配置

在terminal運行docker info來查看是否配置成功:

Registry Mirrors:
http://hub-mirror.c.163.com/
Live Restore Enabled: false
Product License: Community Engine

TensorFlow serving

啟動TensorFlow serving服務

#!/usr/bin/env bash

docker run -p 8500:8501 --mount type=bind,source=$MODEL_PATH, target=/models/deepFM -e MODEL_NAME=deepFM -t tensorflow/serving &

使用上述腳本即可啟動一個TensorFlow serving,其中MODEL_PATH對應的就是上面代碼輸出的訓練模型路徑。

模型啟動

獲取TensorFlow serving預測結果

post man獲取結果

postman api測試結果

body裡面的signature_name是用戶在生成模型時候定義的,暫時不管。instance代表要預測的數據是按行輸入,數據結構為List of dictionary,每一個dictionary代買一行需要預測的樣本,每一個dictionary的key值代表輸入到模型裡面去的variable name,也就是前面模型輸出的時候定義的inputs裡面的key值。

可以通過saved_model_cli查看保存模型需要的輸入特徵稱:

MetaGraphDef with tag-set: serve contains the following SignatureDefs:

signature_def[serving_default]:
The given SavedModel SignatureDef contains the following input(s):
inputs[sparse_0-hour:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_0-hour:0
inputs[sparse_1-C1:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_1-C1:0
inputs[sparse_10-device_ip:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_10-device_ip:0
inputs[sparse_11-device_model:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_11-device_model:0
inputs[sparse_12-device_type:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_12-device_type:0
inputs[sparse_13-device_conn_type:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_13-device_conn_type:0
inputs[sparse_14-C14:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_14-C14:0
inputs[sparse_15-C15:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_15-C15:0
inputs[sparse_16-C16:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_16-C16:0
inputs[sparse_17-C17:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_17-C17:0
inputs[sparse_18-C18:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_18-C18:0
inputs[sparse_19-C19:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_19-C19:0
inputs[sparse_2-banner_pos:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_2-banner_pos:0
inputs[sparse_20-C20:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_20-C20:0
inputs[sparse_21-C21:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_21-C21:0
inputs[sparse_3-site_id:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_3-site_id:0
inputs[sparse_4-site_domain:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_4-site_domain:0
inputs[sparse_5-site_category:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_5-site_category:0
inputs[sparse_6-app_id:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_6-app_id:0
inputs[sparse_7-app_domain:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_7-app_domain:0
inputs[sparse_8-app_category:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_8-app_category:0
inputs[sparse_9-device_id:0] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: sparse_9-device_id:0
The given SavedModel SignatureDef contains the following output(s):
outputs[ctr] tensor_info:
dtype: DT_FLOAT
shape: (-1, 1)
name: prediction_layer/Reshape:0
Method name is: tensorflow/serving/predict

使用Restful API調用模型

前面post man的形式只是測試API調用是否成功,實際上預估的結果肯定是不對的,因為模型訓練的時候有特徵轉換的操作,而這裡直接傳輸的是原始數據。需要將前訓練的時候得到的Label Encoder模型load起來,並對原始數據做特徵轉換之後在發送request請求到TensorFlow serving裡面。

"""
本文件主要用於向deepFM模型發起restful api請求, 獲取ctr預估值
請求數據源來自於train_sample.txt,需要保證以下幾點:
1. 逐條預測得到的ctr值與模型直接預測的值一模一樣。
2. 一次預測的值與模型預測的一模一樣。
3. 所有的請求預測得到的AUC與模型直接預測得到的一模一樣。

pandas int64的type是無法被序列化的
"""

import numpy
import tensorflow as tf

from tensorflow_serving.apis import predict_pb2
from tensorflow_serving.apis import prediction_service_pb2_grpc
import os
import pandas as pd
import requests
import pickle
import json
import numpy as np
from sklearn.metrics import roc_auc_score

SERVING_URL = "http://localhost:8500/v1/models/deepFM:predict"

data_dir = f"{os.path.dirname(__file__)}/../../Data"
predict_data = f"{os.path.abspath(data_dir)}/avazu_ctr/train_sample.txt"
sparse_features = [hour, C1, banner_pos, site_id, site_domain,
site_category, app_id, app_domain, app_category, device_id,
device_ip, device_model, device_type, device_conn_type, C14,
C15, C16, C17, C18, C19, C20, C21]

encoding_file = f"{os.path.abspath(data_dir)}/avazu_ctr/label_encodel.pkl"

with open(encoding_file, rb) as f:
les = pickle.load(f)
df = pd.read_csv(predict_data)
for col in sparse_features:
df[col] = les[col].transform(df[col])
df[col] = df[col]

rename_features = {j: f"sparse_{i}-{j}:0" for i, j in enumerate(sparse_features)}
df = df.rename(columns=rename_features)
X = df[list(rename_features.values())].to_json(orient=records, lines=True)
X = X.split("
")
y = df[click]
predict_data = []
for data in X:
data = json.loads(data)
data_new = {x: [y] for x, y in data.items()}
request_body = {
"signature_name": "serving_default",
"instances": [data_new]
}
print(f"send request to tensorflow with data :{request_body}")
headers = {"Content-type": "application/json"}
data_post = json.dumps(request_body)
print(data_post)
response = requests.post(SERVING_URL, data=data_post, headers=headers)
response_data = response.json()
print(response_data)
response.raise_for_status()
predict_data.append(response_data[predictions][0])

predict_data = np.array(predict_data)
auc_score = roc_auc_score(y, predict_data)
print(f"tensorflow restful api get auc score is {auc_score}")

日誌輸入如下:

send request to tensorflow with data :{signature_name: serving_default, instances: [{sparse_0-hour:0: [0], sparse_1-C1:0: [2], sparse_2-banner_pos:0: [0], sparse_3-site_id:0: [1], sparse_4-site_domain:0: [1], sparse_5-site_category:0: [7], sparse_6-app_id:0: [69], sparse_7-app_domain:0: [6], sparse_8-app_category:0: [0], sparse_9-device_id:0: [86], sparse_10-device_ip:0: [908], sparse_11-device_model:0: [291], sparse_12-device_type:0: [1], sparse_13-device_conn_type:0: [0], sparse_14-C14:0: [55], sparse_15-C15:0: [2], sparse_16-C16:0: [1], sparse_17-C17:0: [40], sparse_18-C18:0: [0], sparse_19-C19:0: [0], sparse_20-C20:0: [0], sparse_21-C21:0: [20]}]}
{"signature_name": "serving_default", "instances": [{"sparse_0-hour:0": [0], "sparse_1-C1:0": [2], "sparse_2-banner_pos:0": [0], "sparse_3-site_id:0": [1], "sparse_4-site_domain:0": [1], "sparse_5-site_category:0": [7], "sparse_6-app_id:0": [69], "sparse_7-app_domain:0": [6], "sparse_8-app_category:0": [0], "sparse_9-device_id:0": [86], "sparse_10-device_ip:0": [908], "sparse_11-device_model:0": [291], "sparse_12-device_type:0": [1], "sparse_13-device_conn_type:0": [0], "sparse_14-C14:0": [55], "sparse_15-C15:0": [2], "sparse_16-C16:0": [1], "sparse_17-C17:0": [40], "sparse_18-C18:0": [0], "sparse_19-C19:0": [0], "sparse_20-C20:0": [0], "sparse_21-C21:0": [20]}]}
{predictions: [[0.474098]]}
....
send request to tensorflow with data :{signature_name: serving_default, instances: [{sparse_0-hour:0: [0], sparse_1-C1:0: [2], sparse_2-banner_pos:0: [0], sparse_3-site_id:0: [17], sparse_4-site_domain:0: [103], sparse_5-site_category:0: [1], sparse_6-app_id:0: [69], sparse_7-app_domain:0: [6], sparse_8-app_category:0: [0], sparse_9-device_id:0: [86], sparse_10-device_ip:0: [515], sparse_11-device_model:0: [79], sparse_12-device_type:0: [1], sparse_13-device_conn_type:0: [0], sparse_14-C14:0: [21], sparse_15-C15:0: [2], sparse_16-C16:0: [1], sparse_17-C17:0: [14], sparse_18-C18:0: [0], sparse_19-C19:0: [0], sparse_20-C20:0: [0], sparse_21-C21:0: [12]}]}
{"signature_name": "serving_default", "instances": [{"sparse_0-hour:0": [0], "sparse_1-C1:0": [2], "sparse_2-banner_pos:0": [0], "sparse_3-site_id:0": [17], "sparse_4-site_domain:0": [103], "sparse_5-site_category:0": [1], "sparse_6-app_id:0": [69], "sparse_7-app_domain:0": [6], "sparse_8-app_category:0": [0], "sparse_9-device_id:0": [86], "sparse_10-device_ip:0": [515], "sparse_11-device_model:0": [79], "sparse_12-device_type:0": [1], "sparse_13-device_conn_type:0": [0], "sparse_14-C14:0": [21], "sparse_15-C15:0": [2], "sparse_16-C16:0": [1], "sparse_17-C17:0": [14], "sparse_18-C18:0": [0], "sparse_19-C19:0": [0], "sparse_20-C20:0": [0], "sparse_21-C21:0": [12]}]}
{predictions: [[0.473931]]}
tensorflow restful api get auc score is 0.7531147050014975

最終樣本通過模型在線預測的AUC與離線計算的AUC一致,模型serving成功。

後續工作

1. 請求服務化:將上述發起模型預測請求的代碼使用tornado框架服務化。

2. schedule更新、載入模型:可以使用crontab定時更新模型,tornado定時載入訓練模型以及特徵轉換模型。

3. docker容器化

4. 模型優化

參考文獻

  • DeepFM: A Factorization-Machine based Neural Network for CTR
  • Wide & Deep Learning for Recommender Systems
  • Deploying Keras models using TensorFlow Serving and Flask
  • tensorflow-DeepFM
  • 項目代碼(待傳)

推薦閱讀:

相關文章