【推荐算法】逻辑回归(Logistic Regression,LR)
逻辑回归(Logistic Regression,LR)在推荐系统发展历史中占非常重要的地位。其优势主要体现在三个方面:
- 数学含义的支撑:LR是一个广义线性模型(可以简单理解为加了激活函数的线性模型),其假设为因变量服从伯努利分布,而CTR事件可以类比为掷偏心硬币的问题,所以使用LR作为CTR预估模型是满足其物理意义的;
- 可解释性强:在LR中,每一个特征对应一个权重,权重绝对值的大小可以作为评估该特征重要性的指标。每一个权重都可解释,这是任何深度模型都不具备的;
- 工程化需要:LR有易于并行化(不同特征在不同机器上运算)、训练开销小(新增或删减特征时只需要fine-tune,而树模型需要re-train)的特点。
掌握LR的每个细节(如模型具体的输入输出),是理解后续模型(如GBDT-LR、FM)的基础。
算法
与简单的协同过滤相比,LR模型能够利用用户、物品、上下文等多种不同特征,生成更为全面的推荐。
\[\text{LR}(\mathbf{w, x})= w_{0}+\sum_{i=1}^{n} w_{i} x_{i}
\]
其中,\(\mathbf{w}\)为权重向量,\(\mathbf{x}\)为特征向量。LR算法细节可以参考【机器学习】逻辑回归的C++实现。
模型输入
LR模型对one-hot类型的输入非常友好(为什么?并且为什么树模型不适合one-hot输入)。在pytorch中,我们可以将特征转化为one-hot特征后,使用nn.Linear
构建LR,这是最直观的解决方法。为了简化输入,以及充分利用对sparse input的优化(怎么优化的?),我们使用nn.Embedding
代替。
nn.Embedding
是一个查找表(look-up table),输入为one-hot中为1的特征的index。因此,我们只需要将所有特征域拼接起来,输入为1的特征对应的index即可。具体拼接方法为:
- 获取每个特征域包含的特征个数,根据特征个数获取对应offset;
class MovieLens_100K_Dataset(Dataset):
def __init__(self, df):
self.token_col = ["user_id", "item_id", "age", "gender", "occupation", "release_year"] # 所用特征列
self.df = df[self.token_col]
self.field_dims = self.df.nunique().values # 对应步骤1
self.df = self.df.values
self.label = df["label"].values
def __len__(self):
return self.label.shape[0]
def __getitem__(self, idx):
return self.df[idx], self.label[idx]
# 使用全量数据集df来计算每个特征域包含的特征个数(field_dims),不能使用split后的数据,否则训练测试集会不一致
field_dims = MovieLens_100K_Dataset(df).field_dims
- 每个特征域中的one-hot为1的index加上offset,获得在拼接向量中one-hot为1的index;
- 每个特征域分别送入
nn.Embedding
,对结果sum_pooling得到输出。
class FeaturesLinear(torch.nn.Module):
def __init__(self, field_dims, output_dim=1):
super().__init__()
self.fc = torch.nn.Embedding(sum(field_dims), output_dim)
self.bias = torch.nn.Parameter(torch.zeros((output_dim,)))
self.offsets = np.array((0, *np.cumsum(field_dims)[:-1]), dtype=np.long)
torch.nn.init.xavier_uniform_(self.fc.weight.data)
def forward(self, x):
"""
:param x: Long tensor of size ``(batch_size, num_fields)``
"""
x = x + x.new_tensor(self.offsets).unsqueeze(0) # 对应步骤2
return torch.sum(self.fc(x), dim=1) + self.bias # 对应步骤3
class LogisticRegressionModel(torch.nn.Module):
def __init__(self, field_dims):
super().__init__()
self.linear = FeaturesLinear(field_dims)
def forward(self, x):
return torch.sigmoid(self.linear(x).squeeze(1))
模型效果
设置:
数据集:ml-100k
优化方法:Adam
学习率:0.003
效果:
收敛epoch:92
train logloss: 0.54183
val auc: 0.78256
test auc: 0.78876