协同过滤仅仅使用有限的用户行为信息,逻辑回归算法模型大多引入用户行为、用户特征、物品特征和上下文特征等,从CF逐步过渡到综合不同特征的机器学习模型。
(1)逻辑回归模型
将用户特征(年龄、性别等)、用户行为(收藏、浏览等)、物品特征(属性、描述等)和上下文特征(当前时间、地点等)转化成数值型特征向量;以点击率为优化目标;利用已有数据进行训练学习模型参数;利用学习完成的模型进行推理预测用户点击率。
代码编写原理参考大多参照《深度学习推荐系统》书籍内容,原理见后面附录。
(2)POLY2模型--特征交叉的开始
防止“辛普森悖论”(数据在分组研究与总体研究时产生的结论可能是相悖的),改造逻辑回归模型,使其具备特征交叉能力。但是人工难以设计最优特征组合,POLY2采用暴力组合方式。
但是无差别的特征交叉使得原本稀疏的特征变得更加稀疏,权重参数量级也由 n 上升到
(3)FM模型 -- 隐向量特征交叉
将交叉特征权重换成隐向量内积形式,减少参数稀疏性和计算复杂度
(4) FFM模型 -- 引入特征域的概念
FFF在模型训练过程中,需要学习 n 个特征在 f 个域上的 k 维隐向量,使每个特征在与不同域的特征交叉时采用不同的隐向量
(5) GBDT+LR -- 特征工程模型化的开端
LR模型将推荐问题转换为CTR预测分类问题,但是特征工程构建复杂,特征交叉方式也仅能做到二阶(提高交叉维度会产生组合爆炸问题);GBDT进行特征筛选与组合,生成新的离散特征向量;将生成特征作为LR模型输入特征。
- 引入GBDT,但是树模型不适应高维离散特征
- 树模型的正则项一般为深度和叶子节点数
- LR模型对参数W进行正则化,约束了w不可能过大,即不会聚焦个别特征
- 针对高位离散特征,树模型很有可能根据训练集某些个别特征巧合的实现数据划分
代码复现:
import numpy as np
import pandas as pd
from tqdm import tqdm
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score
from sklearn.metrics import precision_score
from sklearn.metrics import recall_score
from sklearn.metrics import f1_score
from sklearn.metrics import roc_auc_score
from matplotlib import pyplot as plt
from sklearn.datasets import make_classification
class LogisticRegresion:
def __init__(self, N):
self.theta = np.zeros((1, N))
self.N = N
def __gradient(self, X, y):
grad = np.zeros(self.theta.shape)
error = (self.forward(X).ravel() - y)
for j in range(self.N):
grad[0, j] = np.sum(np.multiply(error, X[:, j])) / len(X)
return grad
def __Jw(self, X, y):
"""这个是极大似然估计的目标函数,计算上就是求其最大值~~~
但是编程中并不是直接求解,而是利用SGD对齐偏导方向做下降
"""
part1 = np.multiply(y, np.log(self.forward(X)))
part2 = np.multiply(1 - y, np.log(1 - self.forward(X)))
return - np.sum(part1 + part2) / len(X)
def cross_entropy_loss(self, y, y_):
return -np.sum(y * np.log(y_) + (1 - y) * np.log(1 - y_))
def __sigmoid(self, y):
return 1 / (1 + np.exp(-y))
def train(self, data, label, bath_size, alpha, epochs, loss_thresh=0.001):
for epoch in tqdm(range(epochs)):
for bs in range(len(data) // bath_size - 1):
X = data[bs*bath_size : (bs+1)*bath_size]
y = label[bs*bath_size : (bs+1)*bath_size]
grad = self.__gradient(X, y)
self.theta -= alpha*grad
# print(self.cross_entropy_loss(label, self.forward(data)))
# 提前终止训练,事实上数据是整体做的交叉熵并求和,loss很难特别小
if self.cross_entropy_loss(label, self.forward(data)) < loss_thresh:
break
def forward(self, X):
return np.squeeze(self.__sigmoid(np.dot(X, self.theta.T)))
def predict(self, X):
return [1 if x >= 0.5 else 0 for x in self.forward(X)]
X, y = make_classification(n_samples=1000, n_features=20, n_clusters_per_class=1)
data = [ {v: k for k, v in dict(zip(i, range(len(i)))).items()} for i in X]
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
def eval_model(y_pred, y_true):
print(f"accuracy_score = {accuracy_score(y_true, y_pred)}")
print(f"precision_score = {precision_score(y_true, y_pred)}")
print(f"recall_score = {recall_score(y_true, y_pred)}")
print(f"f1_score = {f1_score(y_true, y_pred)}")
print(f"auc = {roc_auc_score(y_true, y_pred)}")
lr_model = LogisticRegresion(N=20)
lr_model.train(X_train, y_train, 100, 0.001, 50000)
eval_model(lr_model.predict(X_test), y_test)
# 与sklearn库中的 LogisticRegression 对比
from sklearn.linear_model import LogisticRegression
lr = LogisticRegression(solver="liblinear")
lr.fit(X_train, y_train)
y_predict_lr = lr.predict(X_test)
eval_model(y_predict_lr, y_test)
红框是自己编写的结果,蓝框是sklearn自带库,相比下,效果还不错~
附录
参考:
1. 《深度学习推荐系统》