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
項目代碼(待傳)
推薦閱讀: