Tensorflow2.0 models 三種實現入門3(wide and deep 實現)
在上一篇文章中,我們介紹瞭如何去實現孿生網路,本篇文章將介紹,如何實現多輸入多輸出,並且自定義不同的loss以及不同的權重,實現多工訓練。具體將以 wide and deep 模型作文例子講解。
Wide & Deep 是推薦系統CTR領域一大神器,曾應用在 google play 的應用推薦之中。其主要將模型分為兩個部分,一邊是wide層,一邊是deep層,其中:
wide 層用於記憶特徵組合,提高模型的記憶性
deep 層用於泛化高階特徵組合,提高模型的泛化性
資料集構建
我們將使用load_breast_cancer,這是是一個二分類資料集(良性和惡性),具有30個數值型特徵。
from
sklearn。datasets
import
load_breast_cancer
demo_data
=
load_breast_cancer
()
(
demo_data
。
DESCR
)
(
demo_data
。
data
。
shape
)
(
demo_data
。
target
。
shape
)
具體介紹:[描述]
接著切分訓練集和測試集:
from
sklearn。model_selection
import
train_test_split
x_train_all
,
x_test
,
y_train_all
,
y_test
=
train_test_split
(
demo_data
。
data
,
demo_data
。
target
,
random_state
=
7
)
x_train
,
x_valid
,
y_train
,
y_valid
=
train_test_split
(
x_train_all
,
y_train_all
,
random_state
=
11
)
(
x_train
。
shape
,
y_train
。
shape
)
(
x_valid
。
shape
,
y_valid
。
shape
)
(
x_test
。
shape
,
y_test
。
shape
)
進行資料處理,對數值型的資料進行標準化:
from
sklearn。model_selection
import
train_test_split
x_train_all
,
x_test
,
y_train_all
,
y_test
=
train_test_split
(
demo_data
。
data
,
demo_data
。
target
,
random_state
=
7
)
x_train
,
x_valid
,
y_train
,
y_valid
=
train_test_split
(
x_train_all
,
y_train_all
,
random_state
=
11
)
(
x_train
。
shape
,
y_train
。
shape
)
(
x_valid
。
shape
,
y_valid
。
shape
)
(
x_test
。
shape
,
y_test
。
shape
)
模型構建
對於wide and deep 模型,我們將採用兩種方式進行實現,分別是 - Functional API - Model Subclassing 首先我們將實現多輸入單輸出的模型,除此之外,我們還將實現多輸出的模型,模擬進行模型的多工訓練。
多輸入單輸出
模型主要由兩個輸入層,兩個 dense 層, 一個 concate 層 以及一個 dense output層組成
Functional API
多輸入
input_wide
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_wide’
)
input_deep
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_deep’
)
hidden1
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)(
input_deep
)
hidden2
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)(
hidden1
)
concat
=
tf
。
keras
。
layers
。
concatenate
([
input_wide
,
hidden2
])
output
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
)(
concat
)
model
=
tf
。
keras
。
models
。
Model
(
inputs
=
[
input_wide
,
input_deep
],
outputs
=
[
output
])
我們測試模型的結果,採用demo的資料:
# test
tf
。
random
。
set_seed
(
2
)
inputs
=
[
np
。
array
([
list
(
range
(
15
))]),
np
。
array
([
list
(
range
(
15
))])]
model
(
inputs
)
>>><
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
2。8273863e-07
]],
dtype
=
float32
)
>
plot 模型結構:
# plot model
tf
。
keras
。
utils
。
plot_model
(
model
,
to_file
=
“model。png”
,
show_shapes
=
False
,
show_dtype
=
False
,
show_layer_names
=
True
,
rankdir
=
“TB”
,
expand_nested
=
False
,
dpi
=
96
,
)
Model Subclassing
對於上面的模型,我們採用 model subclassing 的方式重新實現一遍。
class
WideDeepModel
(
tf
。
keras
。
models
。
Model
):
def
__init__
(
self
,
**
kwargs
):
super
()
。
__init__
(
**
kwargs
)
self
。
hidden1
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
hidden2
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
concat
=
tf
。
keras
。
layers
。
concatenate
self
。
dense
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
)
def
call
(
self
,
inputs
):
input_wide
,
input_deep
=
inputs
output_deep
=
self
。
hidden1
(
input_deep
)
output_deep
=
self
。
hidden2
(
output_deep
)
concat_input
=
self
。
concat
([
input_wide
,
output_deep
])
output
=
self
。
dense
(
concat_input
)
return
output
接著我們測試兩種實現是否一致:
# test
# load model
model
=
WideDeepModel
()
# load model by Squential
# model = tf。keras。models。Sequential([WideDeepModel(), ])
# # input_shape = [(None, 15), (None, 15)]
# # model。build(input_shape=(None, 15))
# # model。summary()
inputs
=
[
np
。
array
([
list
(
range
(
15
))])
。
astype
(
‘float32’
),
np
。
array
([
list
(
range
(
15
))])
。
astype
(
‘float32’
)]
model
(
inputs
)
>>>
<
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
2。8273863e-07
]],
dtype
=
float32
)
>
輸出模型結構:
model。summary()
Model: “wide_deep_model_5”
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
dense_18 (Dense) multiple 480
_________________________________________________________________
dense_19 (Dense) multiple 930
_________________________________________________________________
dense_20 (Dense) multiple 46
=================================================================
Total params: 1,456
Trainable params: 1,456
Non-trainable params: 0
_________________________________________________________________
視覺化模型:
# plot model
tf
。
keras
。
utils
。
plot_model
(
model
,
to_file
=
“model。png”
,
show_shapes
=
False
,
show_dtype
=
False
,
show_layer_names
=
True
,
rankdir
=
“TB”
,
expand_nested
=
False
,
dpi
=
96
,
)
對於model subclassing 的模型實現,我們需要視覺化模型的話,需要在 class 中實現
build_graph(self, shapes)
的方法
class
WideDeepModel
(
tf
。
keras
。
models
。
Model
):
def
__init__
(
self
,
**
kwargs
):
super
()
。
__init__
(
**
kwargs
)
self
。
hidden1
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
hidden2
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
concat
=
tf
。
keras
。
layers
。
concatenate
self
。
dense
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
)
def
call
(
self
,
inputs
):
input_wide
,
input_deep
=
inputs
output_deep
=
self
。
hidden1
(
input_deep
)
output_deep
=
self
。
hidden2
(
output_deep
)
concat_input
=
self
。
concat
([
input_wide
,
output_deep
])
output
=
self
。
dense
(
concat_input
)
return
output
def
build_graph
(
self
,
shapes
):
shape1
,
shape2
=
shapes
input_wide
=
tf
。
keras
。
layers
。
Input
(
shape
=
shape1
)
input_deep
=
tf
。
keras
。
layers
。
Input
(
shape
=
shape2
)
return
tf
。
keras
。
models
。
Model
(
inputs
=
[
input_wide
,
input_deep
],
outputs
=
[
self
。
call
([
input_wide
,
input_deep
])])
然後呼叫
plot_model
運用
model。build_graph
實現模型視覺化:
# plot model
tf
。
keras
。
utils
。
plot_model
(
model
。
build_graph
([(
15
),
(
15
)]),
to_file
=
“model。png”
,
show_shapes
=
False
,
show_dtype
=
False
,
show_layer_names
=
True
,
rankdir
=
“TB”
,
expand_nested
=
False
,
dpi
=
96
,
)
多輸入多輸出
對於模型,通常只有一個輸出,也只有一個訓練任務,但是現在的趨勢是,我們一般模型有多個任務同時訓練,有多個輸出,然後每個人物有自己的訓練目標,並且具有不同的任務權重,下面我們將利用上述的wide & deep 模型,實現兩個output,並且使用兩個訓練目標。
Functional API 實現
input_wide
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_wide’
)
input_deep
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_deep’
)
hidden1
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)(
input_deep
)
hidden2
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)(
hidden1
)
concat
=
tf
。
keras
。
layers
。
concatenate
([
input_wide
,
hidden2
])
output1
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
,
name
=
‘output_1’
)(
concat
)
output2
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
,
name
=
‘output_2’
)(
concat
)
model
=
tf
。
keras
。
models
。
Model
(
inputs
=
[
input_wide
,
input_deep
],
outputs
=
[
output1
,
output2
])
model
。
summary
()
“”“
Model: ”model_1“
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_deep (InputLayer) [(None, 15)] 0
__________________________________________________________________________________________________
dense_12 (Dense) (None, 30) 480 input_deep[0][0]
__________________________________________________________________________________________________
input_wide (InputLayer) [(None, 15)] 0
__________________________________________________________________________________________________
dense_13 (Dense) (None, 30) 930 dense_12[0][0]
__________________________________________________________________________________________________
concatenate_4 (Concatenate) (None, 45) 0 input_wide[0][0]
dense_13[0][0]
__________________________________________________________________________________________________
output_1 (Dense) (None, 1) 46 concatenate_4[0][0]
__________________________________________________________________________________________________
output_2 (Dense) (None, 1) 46 concatenate_4[0][0]
==================================================================================================
Total params: 1,502
Trainable params: 1,502
Non-trainable params: 0
”“”
tf
。
keras
。
utils
。
plot_model
(
model
,
show_layer_names
=
True
)
我們可以測試,看模型的輸出是什麼樣子:
tf
。
random
。
set_seed
(
2
)
# test
inputs
=
[
np
。
array
([
list
(
range
(
15
))]),
np
。
array
([
list
(
range
(
15
))])]
model
(
inputs
)
>>>
[
<
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
2。8273863e-07
]],
dtype
=
float32
)
>
,
<
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
0。01569781
]],
dtype
=
float32
)
>
]
可以看到,得到兩個輸出
Model Subclassing 實現
class
WideDeepModel
(
tf
。
keras
。
models
。
Model
):
def
__init__
(
self
,
**
kwargs
):
super
()
。
__init__
(
**
kwargs
)
self
。
hidden1
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
hidden2
=
tf
。
keras
。
layers
。
Dense
(
30
,
activation
=
‘relu’
)
self
。
concat
=
tf
。
keras
。
layers
。
concatenate
self
。
dense1
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
,
name
=
‘output_1’
)
self
。
dense2
=
tf
。
keras
。
layers
。
Dense
(
1
,
activation
=
‘sigmoid’
,
name
=
‘output_2’
)
def
call
(
self
,
inputs
):
input_wide
,
input_deep
=
inputs
output_deep
=
self
。
hidden1
(
input_deep
)
output_deep
=
self
。
hidden2
(
output_deep
)
concat_input
=
self
。
concat
([
input_wide
,
output_deep
])
output1
=
self
。
dense1
(
concat_input
)
output2
=
self
。
dense2
(
concat_input
)
return
[
output1
,
output2
]
def
build_graph
(
self
,
shapes
):
x_wide_shape
,
x_deep_shape
=
shapes
input_wide
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_wide’
)
input_deep
=
tf
。
keras
。
layers
。
Input
(
shape
=
[
15
],
name
=
‘input_deep’
)
inputs
=
[
input_wide
,
input_deep
]
return
tf
。
keras
。
models
。
Model
(
inputs
=
inputs
,
outputs
=
self
。
call
(
inputs
))
視覺化模型結構:
model
。
build_graph
([(
15
,),
(
15
,)])
。
summary
()
“”“
Model: ”model_4“
__________________________________________________________________________________________________
Layer (type) Output Shape Param # Connected to
==================================================================================================
input_5 (InputLayer) [(None, 15)] 0
__________________________________________________________________________________________________
dense_24 (Dense) (None, 30) 480 input_5[0][0]
__________________________________________________________________________________________________
input_4 (InputLayer) [(None, 15)] 0
__________________________________________________________________________________________________
dense_25 (Dense) (None, 30) 930 dense_24[1][0]
__________________________________________________________________________________________________
concatenate_12 (Concatenate) (None, 45) 0 input_4[0][0]
dense_25[1][0]
__________________________________________________________________________________________________
dense_26 (Dense) (None, 1) 46 concatenate_12[0][0]
==================================================================================================
Total params: 1,456
Trainable params: 1,456
Non-trainable params: 0
”“”
tf
。
keras
。
utils
。
plot_model
(
model
。
build_graph
([(
15
,),
(
15
,)]))
測試模型結果:
# test
inputs
=
[
np
。
array
([
list
(
range
(
15
))])
。
astype
(
‘float32’
),
np
。
array
([
list
(
range
(
15
))])
。
astype
(
‘float32’
)]
model
(
inputs
)
>>>
[
<
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
2。8273863e-07
]],
dtype
=
float32
)
>
,
<
tf
。
Tensor
:
shape
=
(
1
,
1
),
dtype
=
float32
,
numpy
=
array
([[
0。01569781
]],
dtype
=
float32
)
>
]
我們可以看到兩種不同的實現,模型的輸出是一樣的。
訓練和測試
模型訓練完畢之後,我們需要配合 loss function 以及 optimizer進行模型compile。
compile
如果我們的模型兩個輸出設定一個loss的話,我們的compile 步驟和單輸出模型一樣:
# 單個loss
model
。
compile
(
loss
=
‘binary_crossentropy’
,
optimizer
=
‘adam’
,
metrics
=
[
‘accuracy’
])
callbacks
=
[
tf
。
keras
。
callbacks
。
EarlyStopping
(
patience
=
5
,
min_delta
=
1e-2
)
]
如果我們模型的輸出具有不同的loss,例如一個輸出是分類的loss,一個輸出是迴歸的loss。我們需要設定一個losses字典,對不同的輸出指定不同的loss function,其中key是輸出的output name,value是對應的loss funtion 名稱。同時我們可以設定loss的權重,具體的引數是
loss_wights
,它同樣是一個字典,key是輸出的output name,value是對應的權重。
例如下面我們模擬兩個輸出,都是二分類,目前採用的loss function 先初定一樣都是
binary_crossentropy
, 實際中大家可以根據需要選擇,loss weights 權重採用第一個loss 佔1,第二個loss權重為2。
## specific loss and loss weights
losses
=
{
“output_1”
:
“binary_crossentropy”
,
“output_2”
:
“binary_crossentropy”
,
}
loss_weights
=
{
“output_1”
:
1。0
,
“output_2”
:
2
}
model
。
compile
(
loss
=
losses
,
loss_weights
=
loss_weights
,
metrics
=
[
‘accuracy’
])
train
我麼將訓練集的前15列作為 wide 部分的輸入,後15列作為 deep 部分的輸入, 訓練100輪,設定early stopping。
x_train_scaled_wide
=
x_train_scaled
[:,
:
15
]
x_train_scaled_deep
=
x_train_scaled
[:,
15
:]
x_valid_scaled_wide
=
x_valid_scaled
[:,
:
15
]
x_valid_scaled_deep
=
x_valid_scaled
[:,
15
:]
x_test_scaled_wide
=
x_test_scaled
[:,
:
15
]
x_test_scaled_deep
=
x_test_scaled
[:,
15
:]
history
=
model
。
fit
([
x_train_scaled_wide
,
x_train_scaled_deep
],
y_train
,
validation_data
=
([
x_valid_scaled_wide
,
x_valid_scaled_deep
],
y_valid
),
epochs
=
100
,
callbacks
=
callbacks
)
evaluation
我們可以畫出我們的學習曲線:
def
plot_learning_curves
(
history
):
pd
。
DataFrame
(
history
。
history
)
。
plot
(
figsize
=
(
8
,
5
))
plt
。
grid
(
True
)
plt
。
gca
()
。
set_ylim
(
0
,
1
)
plt
。
show
()
plot_learning_curves
(
history
)
可以看出,我們這邊展示了兩個輸出的loss, 並且可以看到 output_2_loss 更低,因為我們令它的權重更大,所以optimizer 會讓它更低從何使得最終的loss降低更多。
我們接著進行model 的evaluation。可以得到如下結果
model
。
evaluate
([
x_test_scaled_wide
,
x_test_scaled_deep
],
y_test
)
“”“
5/5 [==============================] - 0s 2ms/step - loss: 0。2223 - output_1_loss: 0。0675 - output_2_loss: 0。0774 - output_1_accuracy: 0。9790 - output_2_accuracy: 0。9720
[0。22231410443782806,
0。06749054789543152,
0。07741177827119827,
0。9790209531784058,
0。9720279574394226]
”“”
我們得到了一個數組,陣列中的值分別代表了[總的loss,output1的loss,output2的loss,output1的accuracy,output2的accuracy] 我們從這個值也能看到 總的
loss = output1的loss + 2* output2的loss。
和我們定義的 loss weights 一致。
Reference
tf2-study-notebook