論文介紹
DCN-v2優(yōu)化了DCN的cross layer漱凝,權(quán)重參數(shù)w由原來的vector變?yōu)榉疥噈atrix,增加了網(wǎng)絡層的表達能力较雕;同時碉哑,為了保證線上應用的耗時不會因為cross layer參數(shù)量的增加而增加挚币。觀察到cross layer的matrix具有低秩性亮蒋,使用矩陣分解,將方陣matrix轉(zhuǎn)換為兩個低維的矩陣妆毕、最后在低秩空間內(nèi)慎玖,利用MoE多專家系統(tǒng),對特征交叉做非線性變化笛粘,進一步增加對交叉特征的建模趁怔。vector -> matrix; moe
設想:DCNv2是不是可以結(jié)合多任務學習、MMoE架構(gòu)薪前,設計一個新的點擊率润努、轉(zhuǎn)換率模型。效果會不會很好呢示括?不過铺浇,這種方法對訓練樣本量可能要求比較高《庀ィ可以嘗試一下鳍侣。
構(gòu)建推薦系統(tǒng)的關鍵點在于學習有效的交叉特征。特征的交叉一般通過哈達瑪積來表示吼拥,比如x1表示性別倚聚,男、女凿可;x2表示是否喜歡體育惑折;x1&x2聯(lián)合特征太惠,會有4種取值。通過特征交叉婴梧,可以給模型帶來一定的非線性表示蔓倍。DCN在實際應用中,當處理十億級別的訓練數(shù)據(jù)樣本時敞咧,其Cross網(wǎng)絡部分在建模特征交叉時表達能力受限棘捣。盡管,交叉特征建模在學術上已經(jīng)提出了很多新的技術方法休建, 但在實際工程中乍恐,許多深度學習模型仍然是通過傳統(tǒng)的前饋神經(jīng)網(wǎng)絡來實現(xiàn)特征交叉建模。
通盤考慮DCN網(wǎng)絡的優(yōu)缺點以及交叉特征的建模方式测砂,這里提出了一種新模型架構(gòu)DCNv2:優(yōu)化DCN的Cross部分茵烈,豐富其對交叉特征的建模能力。
我們直接對比DCN砌些、DCNv2的網(wǎng)絡架構(gòu)呜投。、
兩者的差別在于Cross網(wǎng)絡的建模方式存璃。
DCN Cross layer:
DCNv2 Cross layer:
網(wǎng)絡層權(quán)重參數(shù)由原來的vector變?yōu)閙atrix仑荐。DCN網(wǎng)絡的cross layer的建模是element-wise;DCNv2 cross layer可以實現(xiàn)element-wise和feature-wise的特征交叉纵东。
我們先回歸一下DCN網(wǎng)絡模型粘招。DCN借鑒Google的Wide&Deep模型結(jié)構(gòu),Deep部分是一個N的MLP偎球,用于學習隱性高階交叉特征洒扎;Cross網(wǎng)絡通過顯性的交叉公式建模高階交叉特征。cross網(wǎng)絡的參數(shù)為兩個d維度的向量:w和b衰絮。通過公式優(yōu)化袍冷,可以簡化運算,不會影響模型的線上耗時猫牡。這是DCN網(wǎng)絡的優(yōu)點胡诗。
但是,DCN網(wǎng)絡的Cross網(wǎng)絡镊掖、Deep網(wǎng)絡的參數(shù)量不平衡乃戈,Deep部分的參數(shù)占據(jù)DCN總參數(shù)的絕大部分。
舉例來說亩进,對于1層cross和1層deep的DCN網(wǎng)絡輸入經(jīng)過embedding和stack處理后維度為d症虑,Cross部分網(wǎng)絡參數(shù)為2d,Deep為d*d归薛,當MLP的層數(shù)增多時谍憔,deep部分的參數(shù)量也急速增加匪蝙。DCN網(wǎng)路的絕大部分參數(shù)都用于對隱性交叉特征進行建模。Cross部分的表達能力反而受限习贫。
DCN-v2優(yōu)化了cross網(wǎng)絡的建模方式逛球,增加了cross網(wǎng)絡部分的表達能力;deep部分保持不變苫昌。下面主要就Cross網(wǎng)絡進行詳細介紹颤绕。
從原來的向量變?yōu)榫仃嚒S捎诰€上應用對模型的耗時祟身、資源占用要求很高奥务。原來的DCN的網(wǎng)絡正是由于Cross網(wǎng)絡既能對交叉特征進行顯性建模,而且對模型的耗時影響忽略不計袜硫。如果改進后的cross layer直接應用到線上氯葬,cross網(wǎng)絡雖然會增加模型的表達能力,但是cross部分的權(quán)重參數(shù)會給模型的耗時帶來影響婉陷。所以帚称,為了能進一步減少cross網(wǎng)絡的模型耗時,論文通過實驗發(fā)現(xiàn)了cross layer的權(quán)重參數(shù)具有低秩性秽澳。
我們可以對cross layer的權(quán)重參數(shù)進行矩陣分解闯睹,將原來[d,d]的W方陣,分解為兩個矩陣U肝集,V瞻坝,[d,r], r << d. 這樣就可以實現(xiàn)cross layer的參數(shù)縮減蛛壳,同時也增加了cross網(wǎng)絡的表達能力杏瞻。
通過上面的公式,我們可以發(fā)現(xiàn):
- cross在低維空間進行交叉特征的建模衙荐;
- 我們將輸入x從維度d映射到r捞挥,然后又映射會維度d。[d >> r].
低維空間的交叉特征建模使得我們可以利用MoE忧吟。MoE由兩部分組成:experts專家和gating門(一個關于輸入x的函數(shù))砌函。我們可以使用多個專家,每個專家學習不同的交叉特征溜族,最后通過gating將各個專家的學習結(jié)果整合起來讹俊,作為輸出。這樣就又能進一步增加對交叉特征的建模能力煌抒。
同時仍劈,我們也可以對低緯映射空間中做更多的非線性變化,進一步增加模型的表達能力寡壮。
通過實驗發(fā)現(xiàn)贩疙,對比其他模型讹弯,DCN-v2表現(xiàn)更好。
其他这溅,需要注意的是组民。論文提出,deep和cross的結(jié)合方式悲靴,選用并行or串行結(jié)構(gòu)臭胜,與訓練數(shù)據(jù)密切相關。不同的數(shù)據(jù)分布癞尚,應該選擇不同的處理方式庇楞,并行結(jié)構(gòu)并不是通用的。
源碼閱讀
Cross Layer
將DCN的Cross layer和不使用MoE結(jié)構(gòu)的cross layer否纬。兩者的區(qū)別在于交叉特征的建模方式吕晌,權(quán)重參數(shù)一個是向量,一個是矩陣临燃。
[圖片上傳失敗...(image-801292-1609475511103)]
class CrossNet(nn.Module):
"""The Cross Network part of Deep&Cross Network model,
which leans both low and high degree cross feature.
Input shape
- 2D tensor with shape: ``(batch_size, units)``.
Output shape
- 2D tensor with shape: ``(batch_size, units)``.
Arguments
- **in_features** :輸入特征維度
- **input_feature_num**: Positive integer, shape(Input tensor)[-1]
- **layer_num**: cross網(wǎng)絡的網(wǎng)絡層數(shù)
- **parameterization**: "vector" or "matrix" ,表示網(wǎng)絡層的建模方式
- **l2_reg**: 正則系數(shù)
- **seed**: 隨機種子
"""
def __init__(self, in_features, layer_num=2, parameterization='vector', seed=1024, device='cpu'):
super(CrossNet, self).__init__()
self.layer_num = layer_num
self.parameterization = parameterization
# 參數(shù)聲明
if self.parameterization == 'vector': # DCN
# weight in DCN. (in_features, 1)
self.kernels = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(torch.empty(in_features, 1))) for i in range(self.layer_num)])
elif self.parameterization == 'matrix': # DCNv2
# weight matrix in DCN-M. (in_features, in_features)
self.kernels = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(torch.empty(in_features, in_features))) for i in range(self.layer_num)])
else: # error
raise ValueError("parameterization should be 'vector' or 'matrix'")
self.bias = torch.nn.ParameterList([nn.Parameter(nn.init.zeros_(torch.empty(in_features, 1))) for i in range(self.layer_num)])
self.to(device)
def forward(self, inputs):
x_0 = inputs.unsqueeze(2)
x_l = x_0
for i in range(self.layer_num):
if self.parameterization == 'vector':
# x0 * (xl.T * w )+ b + xl
xl_w = torch.tensordot(x_l, self.kernels[i], dims=([1], [0]))
dot_ = torch.matmul(x_0, xl_w)
x_l = dot_ + self.bias[i]
elif self.parameterization == 'matrix':
# x0 * (Wxl + bl) + xl
dot_ = torch.matmul(self.kernels[i], x_l) # W * xi (bs, in_features, 1)
dot_ = dot_ + self.bias[i] # W * xi + b
dot_ = x_0 * dot_ # x0 · (W * xi + b) Hadamard-product
else: # error
print("parameterization should be 'vector' or 'matrix'")
pass
x_l = dot_ + x_l
x_l = torch.squeeze(x_l, dim=2)
return x_l
Cross Network + MoE
計算每個專家的輸出睛驳,然后通過gating將多個專家的輸出進行加權(quán)平均。
class CrossNetMix(nn.Module):
"""DCN-Mix:
1 添加MoE膜廊,學習不同子空間內(nèi)的交叉特征
2 在低維空間增加更多的非線性變換
Input shape
- 2D tensor with shape: ``(batch_size, units)``.
Output shape
- 2D tensor with shape: ``(batch_size, units)``.
Arguments
- **in_features** : 輸入特征維度
- **low_rank** : 低維度空間的維度
- **num_experts** : 專家數(shù)目.
- **layer_num**: cross網(wǎng)路層數(shù)
- **device**:"cpu" or "cuda:0"
"""
def __init__(self, in_features, low_rank=32, num_experts=4, layer_num=2, device='cpu'):
super(CrossNetMix, self).__init__()
self.layer_num = layer_num
self.num_experts = num_experts
# 參數(shù)聲明
# U: (in_features, low_rank)
self.U_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
torch.empty(num_experts, in_features, low_rank))) for i in range(self.layer_num)])
# V: (in_features, low_rank)
self.V_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
torch.empty(num_experts, in_features, low_rank))) for i in range(self.layer_num)])
# C: (low_rank, low_rank)
self.C_list = torch.nn.ParameterList([nn.Parameter(nn.init.xavier_normal_(
torch.empty(num_experts, low_rank, low_rank))) for i in range(self.layer_num)])
self.gating = nn.ModuleList([nn.Linear(in_features, 1, bias=False) for i in range(self.num_experts)])
self.bias = torch.nn.ParameterList([nn.Parameter(nn.init.zeros_(
torch.empty(in_features, 1))) for i in range(self.layer_num)])
self.to(device)
def forward(self, inputs):
x_0 = inputs.unsqueeze(2) # (bs, in_features, 1)
x_l = x_0
for i in range(self.layer_num):# 逐個網(wǎng)絡層
output_of_experts = []
gating_score_of_experts = []
for expert_id in range(self.num_experts): # 每個的若干個專家
# 針對每個專家乏沸,計算gating score,output
# (1) G(x_l)
# 根據(jù)輸入計算gating值爪瓜,最后softmax計算權(quán)重系數(shù)
gating_score_of_experts.append(self.gating[expert_id](x_l.squeeze(2)))
# (2) E(x_l)
# 計算每個專家的輸出
# 2.1 將輸入映射到低維空間
v_x = torch.matmul(self.V_list[i][expert_id].t(), x_l) # (bs, low_rank, 1)
# 2.2 在低維空間進行非線性變換
v_x = torch.tanh(v_x)
v_x = torch.matmul(self.C_list[i][expert_id], v_x)
v_x = torch.tanh(v_x)
# 2.3 從低維空間映射回高維度
uv_x = torch.matmul(self.U_list[i][expert_id], v_x) # (bs, in_features, 1)
dot_ = uv_x + self.bias[i]
# 哈達瑪積:逐元素乘法
dot_ = x_0 * dot_ # Hadamard-product
output_of_experts.append(dot_.squeeze(2))# [bs, d]
# (3) 專家輸出結(jié)果加權(quán)平均蹬跃,得到這一層網(wǎng)絡的輸出內(nèi)容
output_of_experts = torch.stack(output_of_experts, 2) # (bs, in_features, num_experts)
gating_score_of_experts = torch.stack(gating_score_of_experts, 1) # (bs, num_experts, 1)
moe_out = torch.matmul(output_of_experts, gating_score_of_experts.softmax(1))
x_l = moe_out + x_l # (bs, in_features, 1)
x_l = x_l.squeeze() # (bs, in_features)
return x_l
進一步思考:將DCN-V2和MTL多任務學習結(jié)合起來效果會怎么樣呢?
后續(xù)打算铆铆,參考PyTorch實現(xiàn)蝶缀,寫一版TensorFlow版本的DCN-v2。