在之前介紹的幾個模型中,存在這些問題:
- LR不能捕捉非線性,只能進行一次的迴歸預測
- GBDT+LR雖然能夠產生非線性特徵組合,但是樹模型不適用於超高維稀疏資料
- FM利用二階資訊來產生變數之間的相關性,但是無法適應高階組合特徵,高階組合容易爆炸
那麼,下面介紹的LS-PLM模型一定程度上緩解了這個問題。
LS-PLM
LS-PLM是阿里巴巴曾經主流的推薦模型,這一篇文章就來介紹一下LS-PLM模型的內容。LS-PLM可以看做是對LR模型的自然推廣,它採用的是分而治之的策略。先對樣本分片,然後樣本分片中運用邏輯迴歸進行預估。分片的作用是為了能夠讓CTR模型對不同的使用者群體。不同使用場景都具有針對性。先對全量樣本進行聚類,然後在對每個分類實施邏輯迴歸。透漏一下,這裡阿里巴巴使用的分片聚類的經驗值是12。
論文當中LS-PLM的效果與LR模型效果進行對比,如下圖所示。
優勢
LS-PLM存在三個優勢:
- 端到端的非線性學習能力。通過足夠的劃分割槽域,LS-PLM可以擬合任何複雜的非線性函式,挖掘資料中的非線性模式,節省大量人工處理樣本和特徵工程的過程。
- 可擴充套件性。與LR模型類似,LS-PLM可以擴充套件到大量樣本和高維特徵。在這之上設計了一個分散式系統,可以在數百臺機器上並行訓練模型。線上產品系統中,每天都會訓練和部署幾十個具有數千萬引數的LS-PLM模型。
- 稀疏性。模型稀疏性是工業環境下線上服務的一個實際問題。這裡展示了採用L1和L2,1正則器的LS-PLM可以實現良好的稀疏性。使得部署更加輕量級,線上推斷效率也更高。
論文基於directional derivatives(方向導數)和quasi-Newton(擬牛頓)方法來解決因為正則項使得目標函式非凸的問題。
數學形式
LS-PLM的數學形式如下面公式所示
首先用聚類函式\(\pi\)對模型進行分類分片,再用LR模型計算樣本在分片中具體的CTR,然後將兩者相乘之後求和。公式中的\(m\)就是分片數,可以較好地平衡模型的擬合能力和推廣能力。當\(m=1\)時就會退化為普通的邏輯迴歸。
實際上,LS-PLM採用的softma函式x進行分類,sigmoid函式作為迴歸。於是公式變成:
\(\{u_1,...u_m\}\)為聚類函式(分片函式)\(\pi_i\)的引數,\(\{w_1,...w_m\}\)為擬合函式\(\eta_i\)的引數。
優化
由於目標函式中的加入的正則化項\(L_1\),\(L_{2,1}\)都是非平滑函式,所以目標函式也是非平滑的、非凸函式。因為目標函式的負梯度方向並不存在,所以用能夠得到f最小的方向導數的方向b作為負梯度的近似值。這裡的推導比較複雜,可以看一下原來論文,之後我儘可能用通俗易懂的語言補充這裡。
這個\(L_{2,1}\)挺意思的,貼一下它原來的公式:
程式碼
import torch
import torch.nn as nn
import torch.optim as optim
class LSPLM(nn.Module):
def __init__(self, m, optimizer, penalty='l2', batch_size=32, epoch=100, learning_rate=0.1, verbose=False):
super(LSPLM, self).__init__()
self.m = m
self.optimizer = optimizer
self.batch_size = batch_size
self.epoch = epoch
self.verbose = verbose
self.learning_rate = learning_rate
self.penalty = penalty
self.softmax = None
self.logistic = None
self.loss_fn = nn.BCELoss(reduction='mean')
def fit(self, X, y):
if self.softmax is None and self.logistic is None:
self.softmax = nn.Sequential(
nn.Linear(X.shape[1], self.m).double(),
nn.Softmax(dim=1).double()
)
self.logistic = nn.Sequential(
nn.Linear(X.shape[1], self.m, bias=True).double()
, nn.Sigmoid())
if self.optimizer == 'Adam':
self.optimizer = optim.Adam(self.parameters(), lr=self.learning_rate)
elif self.optimizer == 'SGD':
self.optimizer = optim.SGD(self.parameters(), lr=self.learning_rate, weight_decay=1e-5, momentum=0.1,
nesterov=True)
# noinspection DuplicatedCode
for epoch in range(self.epoch):
start = 0
end = start + self.batch_size
while start < X.shape[0]:
if end >= X.shape[0]:
end = X.shape[0]
X_batch = torch.from_numpy(X[start:end, :])
y_batch = torch.from_numpy(y[start:end]).reshape(1, end - start)
y_batch_pred = self.forward(X_batch).reshape(1, end - start)
loss = self.loss_fn(y_batch_pred, y_batch)
loss.backward()
self.optimizer.step()
start = end
end += self.batch_size
if self.verbose and epoch % (self.epoch / 20) == 0:
print('EPOCH: %d, loss: %f' % (epoch, loss))
return self
def forward(self, X):
logistic_out = self.logistic(X)
softmax_out = self.softmax(X)
combine_out = logistic_out.mul(softmax_out)
return combine_out.sum(1)
def predict_proba(self, X):
X = torch.from_numpy(X)
return self.forward(X)
def predict(self, X):
X = torch.from_numpy(X)
out = self.forward(X)
out[out >= 0.5] = 1.0
out[out < 0.5] = 0.0
return out
參考
Learning Piece-wise Linear Models from Large Scale Data for Ad Click Predict
Github:hailingu/MLFM
LS-PLM學習筆記