推荐系统算法相关笔记

2021-04-30  本文已影响0人  CJ21

离线推荐使用LFM隐语义模型(ALS进行求解),实时推荐使用Item-CF模型(需要将物品相似度和评分进行加权)。

一、简介

1.1 推荐系统原理

image.png

分类:
1.基于人口统计学的推荐:基于用户的基本数据
2.基于内容的推荐:基于物品的基本数据
3.基于协同过滤的推荐

1.1.1 基于人口统计学的推荐

推荐相似用户喜欢的物品


image.png

1.1.2 基于内容(Content based,BC)的推荐

推荐用户喜欢的物品的相似物品


image.png

1.1.3 基于协同过滤(Collaborative Filtering,CF)的推荐算法

CB主要利用用户评价过的物品的内容特征,而CF方法还可以利用其它用户评分过的物品内容。
CF可以解决CB的一些局限性:

1.1.3.1 基于近邻的协同过滤

1.1.3.2 基于模型的协同过滤(机器学习)

image.png

1.1.4 混合推荐

实际使用时往往不止使用了一种推荐机制和策略,而是将多个方法混合在一起。

1.2 推荐系统实验方法

1.3 推荐模型评测指标

1.4 推荐准确度评测

1.4.1 评分预测

让用户给物品打分,评分预测的准确度一般用均方根误差(RMSE)或平均绝对误差(MAE)计算

image.png

1.4.2 Top-N推荐

给一个topn推荐列表,Top-N推荐的预测准确率一般用精准率(precision)和召回率(recall)来度量。

image.png



二、机器学习(Machine Learning,ML)

image.png

2.1 分类

2.1.1 有监督学习(Supervised Learning)

提供数据并提供数据对应结果的机器学习过程。


image.png

监督学习三要素:

2.1.2 无监督学习(Unsupervised Learning)

提供数据并且不提供数据对应结果的机器学习过程。

无监督学习应用:谷歌新闻会将收集的新闻内容进行分组,组成相关联的新闻,然后按主题显示给用户。谷歌新闻做的就是搜索新闻事件,自动将他们聚类到一起。

2.1.3 强化学习

通过与环境交互并获取延迟返回进而改进行为的学习过程。

2.2 模型评估策略

2.2.1 模型评估

2.2.2 模型选择

2.3 监督学习介绍

2.3.1 回归

回归问题用于预测输入变量和输出变量之间的关系。回归问题的学习等价于函数拟合:选择一条函数取消,使其很好的拟合已知数据,并能够很好的预测未知数据。

2.3.2 回归问题的分类

按照输入变量的个数:一元回归和多元回归
按照模型类型:线性回归和非线性回归

2.3.3 回归学习的损失函数——平方损失函数

如果选取平方损失函数作为损失函数,回归问题可以用最小二乘法(least squares)来求解。

其他模型求解算法(学习算法)



三、监督学习(解决分类和回归问题)

有输入x和输出y。

3.1 线性回归求解

3.1.1 模型概念

给定d个特征描述的示例x = (x1;x2;...;xd),其中xi是x在第i个特征上的取值,那么通过以下线性组合来进行预测函数的拟合。


image.png

我们希望得到模型的损失函数最小,并以此来计算w和b的值,得到最终的模型。

3.1.2 求解模型

3.1.2.1 简单线性回归(最小二乘法)

这里我们使用平方损失函数,并用最小二乘法进行求解

image.png

需要求解达到函数的最小值时的w和b的值。因为该函数是下凸函数,对w和b求偏导,当这两个偏导数为0时,就是该函数的最小值。


image.png
#简单线性回归(最小二乘法)
#0. 引入依赖
import numpy as np
import matplotlib.pyplot as plt
1.导入数据
points = np.genfromtxt('data.csv',delimiter=',')

x = points[:,0]
y = points[:,1]

plt.scatter(x,y)
plt.show()

#2.定义损失函数
def compute_cost(w,b,points):
    M = len(points)
    total_cost = 0
    for i in range(M):
        total_cost += (points[i,1] - w * points[i,0] - b)**2
    return  total_cost / M
#3. 定义核心算法拟合函数
def average(datas):
    sum = 0
    num = len(datas)
    for i in range(num):
        sum += datas[i]
    return sum/num
def fit(points):
    M = len(points)
    x = points[:,0]
    y = points[:,1]
    num_xy = 0
    num_xx = 0
    x_bar = average(points[:,0])
    for i in range(M):
        num_xy += y[i]*(x[i]-x_bar)
        num_xx += x[i]**2
    w = num_xy/(num_xx - M * x_bar**2)   

    num_ywx = 0
    for i in range(M):
        num_ywx += y[i] - w*x[i]
    b = num_ywx/M
    
    return w,b
#4.获取w,b和损失函数的值,并绘制图形
w,b = fit(points)
print("w:",w)
print("b:",b)
cost = compute_cost(w,b,points)
print("cost:",cost)
plt.scatter(x,y)
pred_y = w * x + b
plt.plot(x,pred_y,c='r')
plt.show()

w: 1.3224310227553846
b: 7.991020982269173
cost: 110.25738346621313


image.png

3.1.2.2 多元线性回归(梯度下降法)

损失函数还是使用平方损失函数,但是在多元条件下使用最小二乘法需要计算每个维度的偏导数导致计算过于复杂,所以使用梯度下降法。


image.png

求梯度需要计算每个特征的偏导数,在梯度方向上前进α步长后再次计算,不断迭代后到达极小值点。

image.png
# 简单线性回归(梯度下降法)
# 0. 引入依赖
import numpy as np
import matplotlib.pyplot as plt
# 1.导入数据
points = np.genfromtxt('data.csv',delimiter=',')

x = points[:,0]
y = points[:,1]

plt.scatter(x,y)
plt.show()

# 2.定义损失函数
def compute_cost(w,b,points):
    M = len(points)
    total_cost = 0
    for i in range(M):
        total_cost += (points[i,1] - w * points[i,0] - b)**2
    return  total_cost / M
def average(datas):
    sum = 0
    num = len(datas)
    for i in range(num):
        sum += datas[i]
    return sum/num
# 3.定义模型的超参数
initial_w
alpha = 0.0001
initial_w = 0
initial_b = 0
num_iter = 10
# 4.定义核心梯度下降算法函数
# 计算梯度
def grad_desc(points,initial_w,initial_b,alpha):
    M = len(points)
    w = initial_w
    b = initial_b
    cost_list = []
    for i in range(num_iter):
        cost_list.append(compute_cost(w,b,points))
        w,b = step_grad_desc(w,b,points,alpha)
    return w,b,cost_list

# 进行迭代
def step_grad_desc(w,b,points,alpha):
    M = len(points)
    w_grad_sum = 0
    b_grad_sum = 0
    for i in range(M):  
        x = points[i,0]
        y = points[i,1]
        w_grad_sum += (w * x + b - y) * x
        b_grad_sum += (w * x + b - y) * 1
    w = w - (2 / M * w_grad_sum) * alpha
    b = b - (2 / M * b_grad_sum) * alpha
    return w,b
# 5.获取w,b的值,并绘制图形
cost_list
# w,b = fit(points) 
w,b,cost_list = grad_desc(points,initial_w,initial_b,alpha)
print("w:",w)
print("b:",b)
print("cost_list:",cost_list)
cost = compute_cost(w,b,points)
print("cost:",cost)
plt.scatter(x,y)
# 绘制拟合后的直线图
pred_y = w * x + b
plt.plot(x,pred_y,c='r')
plt.show()

# 6.绘制损失函数的图形
plt.plot(cost_list)
plt.show()

w: 1.4774173755483797
b: 0.02963934787473238
cost_list: [5565.107834483211, 1484.5865574086483, 457.85425757376686, 199.50998572553866, 134.50591058200533, 118.14969342239948, 114.03414906038148, 112.99857731713661, 112.73798187568467, 112.67238435909101]
cost: 112.65585181499748


image.png
image.png

3.1.2.3 多元线性回归(使用第三方库计算,内部使用的是最小二乘法)

#0. 引入依赖
import numpy as np
import numpy as np
import matplotlib.pyplot as plt
#1.导入数据
points = np.genfromtxt('data.csv',delimiter=',')

x = points[:,0]
y = points[:,1]

plt.scatter(x,y)
plt.show()
points = np.genfromtxt('data.csv',delimiter=',')

#2.定义损失函数
compute_cost
def compute_cost(w,b,points):
    M = len(points)
    total_cost = 0
    for i in range(M):
        total_cost += (points[i,1] - w * points[i,0] - b)**2
    return  total_cost / M

#3.使用线性回归官方库进行计算
#3.1 引入包
from sklearn.linear_model import LinearRegression
lr = LinearRegression()
#3.2 进行计算
x = points[:,0]
y = points[:,1]
x_new = x.reshape(-1,1)
y_new = y.reshape(-1,1)
# print(x_new)
# print(y_new)
lr.fit(x_new,y_new)

#3.3 获取训练好的模型参数
w = lr.coef_
b = lr.intercept_
cost = compute_cost(w,b,points)
print("w:",w)
print("b",b)
print("cost",cost)

#4.绘制图形
plt.scatter(x,y)
pred_y = w * x_new + b
plt.plot(x,pred_y,c='r')
plt.show()

w: [[1.32243102]]
b [7.99102098]
cost [[110.25738347]]


image.png

3.2 分类模型算法

3.2.1 K近邻(k-nearest neighnour,KNN)

通过测量不同特征值之间的距离进行分类。
思路:如果一个样本在特征空间中的k个最相似(即特征空间中最近邻)的样本中的大多数属于某一个类别,则该样本也属于这个类别,其中K通常是不大于20的整数。
KNN算法中所选择的邻居都是已经正确分类的对象。

image.png

距离计算:

image.png

KNN算法步骤:

image.png

KNN存在的问题:因为是通过距离判定距离内最多的类别为测试数据的类别,因此不能很好的判断便捷处的数据的类别。

例:通过给定的一组鸢尾花训练数据,训练KNN模型后,对输入的鸢尾花长度判断其类别。

#0.引入依赖
accuracy_score
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 直接引入sklearn里的数据集,iris(鸢尾花)
from sklearn.datasets import load_iris
# 切分数据集为训练集和测试集
from sklearn.model_selection import train_test_split
# 计算分类预测的准确率
from sklearn.metrics import accuracy_score

#1.数据加载和预处理
iris = load_iris()
# 数据封装的对象,包括了长度数据,分类数据等
df = pd.DataFrame(data = iris.data,columns = iris.feature_names)
df['class'] = iris.target
df['class'] = df['class'].map({0:iris.target_names[0],1:iris.target_names[1],2:iris.target_names[2]})
df.describe()
df

x = iris.data
y = iris.target.reshape(-1,1)
print(x.shape,y.shape)
# 划分训练集和测试集
x_train,x_test,y_train,y_test = train_test_split(x,y,test_size=0.3,random_state=35,stratify=y)

#2.核心算法实现
def f1_distance(a,b):
    return np.sum(np.abs(a-b),axis=1)
def f2_distance(a,b):
    return np.sqrt(np.sum((a-b)**2,axis=1))

class kNN(object):
    # 初始化对象
    def __init__(self,n_neighbors = 1,f_distance = f1_distance):
        self.n_neighbors = n_neighbors
        self.f_distance = f_distance
    
    # 训练模型
    def fit(self,x,y):
        self.x_train = x
        self.y_train = y
    
    # 模型预测
    def predict(self,x):
        # 存储所有的预测结果
        y_pred = np.zeros((x.shape[0],1),dtype=self.y_train.dtype)
        
        for i,x_test in enumerate(x):
            # 1.计算距离
            distances = self.f_distance(self.x_train,x_test)
            # 2.得到最近的n_neighbor个点
            neighbors = np.argsort(distances)[:self.n_neighbors] #argsort函数将根据值有小到大对下标进行排列
            # 3.预测类型
            y_pred[i] = np.argmax(np.bincount(self.y_train[neighbors].ravel())) #ravel函数将纵向数组转为横向一维数组,bincount统计每个数字出现次数,下标表示数字,值表示出现次数。argmin获取就是最大值的下标,即出现次数最多的值。
        return y_pred
        
knn = kNN(3)
knn.fit(x_train,y_train)
y_pred = knn.predict(x_test)

#3.测试准确率
accuracy_score
accuracy = accuracy_score(y_test,y_pred)
print("预测准确率:",accuracy)
预测准确率: 0.9333333333333333

#4.调整参数获得准确率列表
result_list = []
for f in [1,2]:
    knn.f_distance = f1_distance if f == 1 else f2_distance
    for i in range(1,10,2):
        knn.n_neighbors = i
        y_pred = knn.predict(x_test)
        
        accurary = accuracy_score(y_test,y_pred)
        result_list.append([i,'f1_distance' if f == 1 else 'f2_distance',accurary])

df = pd.DataFrame(result_list,columns = ['k','距离函数','预测准确率'])
df
image.png

3.2.2 逻辑斯蒂回归

如果使用线性回归存在的问题:

逻辑斯蒂回归通过回归算法获得边界曲线来处理分类问题:

image.png

压缩函数:

image.png
在这个压缩函数中,e^(-z)中参数z的正负决定了g(z)的值是大于0.5还是小于0.5。当z对应的表达式为分类边界时,恰好有分类边界两侧对应的z正负不同,根据g(z)与0.5的大小关系判断分类。

损失函数:
如果使用平方损失函数,存在的问题:①因为未知数在指数中,结果不是凸函数,不好求解也容易到达局部极小值;②边界处即使预测正确,损失函数计算结果还是很大。

我们希望预测结果差距越大,损失函数越大且急速上升;预测结果差距越小,损失函数越小且下降缓慢。因此引入对数函数: image.png
得到损失函数如下: image.png
为了方便求导,分段损失函数组合如下:
image.png
为了防止过拟合,加入正则化项: image.png
这样得到了一个凸函数: image.png

3.2.3 决策树

3.2.3.1 概念

决策树本质是一颗自上而下的由多个判断节点组成的树,从训练数据集中归纳出一组if-then分类规则。
与训练集不相矛盾的决策树,可能有很多个,也可能一个没有;所以我们需要选择一个与训练数据集矛盾较小的决策树。
我们可以把决策树看做一个条件概率模型,我们的目标是将实例分配到条件概率更大的那一类中去。

这是一个NP完全问题,通常采用启发式算法求解决策树的次最优解。
算法通常是递归进行,如何选择最优特征,对训练数据进行分割,使子数据集都有一个最好的分类。

3.2.3.2 熵

熵(entropy)用来衡量随机变量的不确定性,变量不确定性越大,熵也就越大。 image.png
如下图,当随机变量只有两个且X的分布为p和1-p,当p为0或1时熵为0,表示不确定性为0;当p为0.5时熵为1,不确定性最大: image.png

熵在分类问题中的应用:
给三个球,问如何切分使得从箱子中取的球的颜色的熵最小?

image.png
从中任取1个球的结果的熵为: image.png
首先切分为如下:
image.png
计算得到的熵如下: image.png
切分如下: image.png
计算得到的熵如下:
image.png

3.2.3.3 决策树的目标

使用决策树模型的最终目的就是利用决策树模型进行分类预测,这是一个由不确定到确定的过程,熵不断减少的过程。

image.png
H(D|A)是条件熵,表示在A条件下D结果的不确定性,因此在选择条件A时需要满足条件熵是最小的,即信息增益最大(对比损失函数最小)。 image.png

3.2.3.4 决策树算法

3.2.3.4.1 ID3

决策树 的训练过程就是找到信息增益最大的特征,然后按照此特征进行分类,然后再找到各类型子集中信息增益最大的特征,然后按照此特征进行分类,最终得到符合要求的模型。

3.2.3.4.2 C4.5

C4.5算法在ID3基础上进行改进,用信息增益比来选择特征。
因为分类数量多的熵会比分类数量少的熵来的大,因此选择信息增益比来进行比较。

3.2.3.4.3 分类与回归数(CART)

由特征选择、树的生成和剪枝三部分组成,既可以用于分类也可以用于回归。
剪枝是为了防止过拟合发送。

四、无监督学习(解决聚类和降维问题)

只有输入x,没有输出y。

4.1 聚类

4.1.1 k均值(k是超参数,表示类别数)

思想:由用户指定k个初始质心(initial centroids),以作为聚类的类别(cluster),重复迭代直至算法收敛。
步骤:
①随机选取k个初始质心(一般会选取已有的点,避免距离太远),这些质心就是聚类中心。
②计算每个样本点到各个质心的距离,选择最近的质心作为自己的聚类。
③重新计算每个聚类中所有样本点的质心作为新的聚类中心。
④不断重复以上两步,直至质心不再发生变化或迭代达到上限。最终将所有样本点分为k个聚类。

image.png
#k means聚类教程
#0.引入依赖
import numpy as np 
import matplotlib.pyplot as plt
# skleanr中直接生成聚类数据
from sklearn.datasets.samples_generator import make_blobs
#1.数据加载和处理
# 模拟数据,100个点,6个聚类
x,y = make_blobs(n_samples=100,centers=6,random_state=1234,cluster_std=0.8)

plt.figure(figsize=(6,6))
plt.scatter(x[:,0],x[:,1],c=y)
plt.show()

#2.算法实现
# 引入scipy中的距离函数,默认欧氏距离
from scipy.spatial.distance import cdist

class kMeans(object):
    # 参数包括聚类数,迭代次数,初始质心
    def __init__(self,n_cluster = 6,iter_time = 300,centeriods = []):
        self.n_cluster = n_cluster
        self.iter_time = iter_time
        self.centeriods = np.array(centeriods,dtype=np.float)
        
    def fit(self,data):
        # 如果初始质心没有指定,则从训练集中随机选择
        if self.centeriods.shape[0] == 0 :
            self.centeriods = data[np.random.randint(0,data.shape[0],self.n_cluster),:]
        
        # 开始迭代
        for i in range(self.iter_time):
            # 计算每个点到六个质心点的距离
            distances = cdist(data,self.centeriods)
            # 选择最近的点的下标
            c_ind = np.argmin(distances,axis = 1)
            
            # 计算每个聚类的质心点,更新质心点坐标
            for c in range(self.n_cluster):
                # 排除没有出现在c_ind里的质心
                if c in c_ind:
                    # 选出每个聚落的带重新计算质心
                    self.centeriods[c] = np.mean(data[c_ind == c],axis = 0)  # (数组==数字)表达式会返回布尔数组,可以作为数组的布尔索引得到指定位置的值。
    
    def pred(self,data):
        # 计算测试集到质心的距离
        distances = cdist(data,self.centeriods)
        # 取得最小距离的质心点号
        c_ind = np.argmin(distances,axis = 1)
        
        return c_ind


#3.训练
# 为了画图方便而定义的函数
plt.figure(figsize=(16,6))
def paint(kmeans,subplot,title,size):
    plt.subplot(subplot)
    plt.scatter(x[:,0],x[:,1],c='r')
    plt.scatter(kmeans.centeriods[:,0],kmeans.centeriods[:,1],c=np.array(range(6)),s=size)
    plt.title(title)
    
kmeans = kMeans(6,100,np.array([[2,0],[2,1],[2,2],[2,3],[2,4],[2,5]]))
paint(kmeans,121,'Init State',100)
kmeans.fit(x)
paint(kmeans,122,'Final State',100)

#4.预测
kmeans.centeriods
x_new = np.array([[0,0],[10,7]])
y_pred = kmeans.pred(x_new)

print(kmeans.centeriods)
print(y_pred)

plt.scatter(x[:,0],x[:,1],c='r')
plt.scatter(kmeans.centeriods[:,0],kmeans.centeriods[:,1],c=np.array(range(6)))
plt.scatter(x_new[:,0],x_new[:,1],c=y_pred,s=100)
plt.title('Pred State')

训练

训练
预测:
[[ 5.81942544 -4.72317455]
[-2.90776878 -0.31074824]
[-5.81722217 2.17628383]
[-4.6074684 6.04193037]
[-1.12749301 5.6073478 ]
[ 9.21976403 7.57544698]]
[1 5]
Pred State

4.1.2 基于密度的聚类

4.1.3 最大期望聚类

4.2 降维

4.2.1 潜语义分析(LSA)

4.2.2 主成分分析(PCA)

4.2.3 奇异值分析(SVD)



五、推荐系统

5.1 基于人口统计学的推荐算法

概念:根据系统用户的基本信息发现用户的相关度,然后将相似用户喜爱的其他物品推荐给当前用户。
逻辑:对于没有明确含义的用户信息(比如登录时间、地域等上下文信息),可以通过聚类等手段,给用户打上分类标签。
对于特定标签的用户,可以根据预设的规则或者模型,推荐出相应的物品。
用户画像:
用户信息标签化的过程一般又称为用户画像(User Profiling)。
用户画像就是通过收集与分析消费者社会属性、生活习惯、消费行为等主要信息的数据后,完美抽象出一个用户的商业全貌。

缺点:用户信息难收集。

5.2 基于内容的推荐算法

5.2.1 概念

Content-based Recommendations(CB) 根据推荐物品或内容的元数据,发现物品的相关性,再基于用户过去的喜好记录,为用户推荐相似的物品。

5.2.2 逻辑

通过抽取物品内在或者外在的特征值,实现相似度计算(比如电影有导演、演员、用户标签UGC、风格、时长等)。
将用户个人信息的特征(基于喜好记录或是预设兴趣标签),和物品(item)的特征相匹配,就能得到用户对物品的感兴趣程度。

5.2.3 相似度计算:

image.png

高层次架构:

image.png

5.2.4 特征工程

特征:数据中抽取出来的对结果预测有用的信息,避免过拟合。特征个数就是数据的观察维度。
特征工程一般包括特征清洗(采样、清洗异常样本),特征处理和特征选择。
特征按照不同的数据类型分类,有不同的特征处理方法:
①数值型

②类别型

image.png
image.png

③时间型

image.png

④统计型

image.png

5.2.5 推荐系统常见的反馈数据

image.png

5.2.6 基于UGC的推荐

用户用标签来描述对物品的看法。一个用户标签行为的数据集一般由一个三元组(用户,物品,标签)的集合表示,其中一条记录(u,i,b)表示用户u给物品i打上了标签b。

TF-IDF算法实例,统计文章中关键词的权重。

#0.引入依赖
import numpy as np
import pandas as pd
#1.定义数据和处理
docA = "The cat sat on my bed"
docB = "The dog sat on my knee"
# 切分为词汇
bowA = docA.split(" ")
bowB = docB.split(" ")
# 得到所有的词汇,构建词库
wordSet = set(bowA).union(set(bowB))

#2.进行次数统计
wordDictA
# 使用字典进行词频统计
wordDictA = dict.fromkeys(wordSet,0)
wordDictB = dict.fromkeys(wordSet,0)

for word in bowA:
    wordDictA[word] += 1
for word in bowB:
    wordDictB[word] += 1

pd.DataFrame([wordDictA,wordDictB])

#3.计算词频TF
def computeTF(wordDict,bow):
    tfDict = {}
    nbowCount = len(bow)
    for word,count in wordDict.items():
        tfDict[word] = count/nbowCount
    return tfDict

tfA = computeTF(wordDictA,bowA)
tfB = computeTF(wordDictB,bowB)

#4.计算逆向文件频率idf
idf
#计算每个词的idf
import math
def computeIDF(wordDictList):
    # 计算每个词汇出现的文章数
    idfDict = dict.fromkeys(wordDictList[0],0)
    N = len(wordDictList)
    for wordDict in wordDictList:
        for word,count in wordDict.items():
            if count > 0:
                idfDict[word] += 1

    # 计算idf
    for word,count in idfDict.items():
        idfDict[word] = math.log10((N + 1)/(count + 1))
    return idfDict

idfs = computeIDF([wordDictA,wordDictB])

#5.计算tf-idf
# 计算每个文档的tf-idf
def computeTFIDF(tf,idfs):
    tfidf = {}
    for word,ref in tf.items():
        tfidf[word] = ref * idfs[word]
    return tfidf

tfidfA = computeTFIDF(tfA,idfs)
tfidfB = computeTFIDF(tfB,idfs)
pd.DataFrame([tfidfA,tfidfB])
image.png

5.3 基于协同过滤的推荐算法 (CF)

基于内容(CB)主要利用的是用户评价过的物品的内容特征,而CF方法还可以利用其它用户评分过的物品内容。
CF可以解决CB的一些局限:

5.1 基于近邻的推荐

基于近邻的推荐系统根据的是相同“口碑”准则

5.1.1 基于用户的协同过滤(User-CF)

基于用户的协同过滤推荐的基本原理是,根据所有用户对物品的偏好,发现与当前用户口味和偏好相似的“邻居”用户群,并推荐近邻所偏好的物品

在一般的应用中是采用计算“K-近邻”的算法;基于这K个邻居的历史偏好信息,为当前用户进行推荐。

User-CF和基于人口统计学的推荐机制

5.1.2 基于物品的协同过滤(Item-CF)

基于项目的协同过滤的基本原理与基于用户类似,只是使用所有用户对物品的偏好,发现物品与物品之间的相似度,然后根据用户的历史偏好信息,将类似的物品推荐给用户。

Item-CF和基于内容(CB)的推荐:
都是基于物品相似度预测推荐,只是相似度计算方式不同,前者从用户历史的偏好推断,后者是基于物品本身的属性特征信息。

5.2 基于模型的协同过滤思想

image.png image.png

5.2.1 隐语义模型(latent factor model,LFM)

5.2.1.1 概念

image.png image.png

5.2.1.1 损失函数和求解方法

image.png
因为存在两个未知的向量,所以一般的解法无法求解。
求解方法:①ALS算法 ②梯度下降算法
使用ALS算法求解如下:
image.png image.png image.png
对向量求偏导:
image.png image.png
上面计算了损失函数的偏导数,在程序中可以通过梯度下降法得到损失函数的最小值(需要将稀疏矩阵中的0值排除在损失函数外):
image.png
0.引入依赖
import numpy as np
import pandas as pd
1.数据准备
# 评分矩阵R
R = np.array([[4,0,2,0,1],
              [0,2,3,0,0],
              [1,0,2,4,0],
              [5,0,0,3,1],
              [4,0,1,5,1],
              [0,3,2,4,1]])
R.shape


"""
@输入参数
R:M*N的评分矩阵
K:隐特征向量个数
steps:最大迭代次数
alpha:步长
lamda:正则化系数

@输出
分解后的P,Q
P:初始化用户特征矩阵M*K
Q:初始化物品特征矩阵N*K
"""

# 给定超参数
K = 5
max_iter = 5000
alpha = 0.0002
lamda = 0.004
# 基本维度参数定义
M = len(R)
N = len(R[0])
# P,Q初始值,随机生成
P = np.random.rand(M,K)
Q = np.random.rand(N,K)
Q = Q.T
2.算法实现
#核心算法
def LFM_grad_desc(P,Q,R,K=2,max_iter=5000,alpha=0.0001,lamda=0.004):
    M = len(R)
    N = len(R[0])
    for iter in range(max_iter):
        for u in range(M):
            for i in range(N):
                # 计算损失函数括号中的式子
                temp = np.dot(P[u,:],Q[:,i]) - R[u,i]

                # 对向量的每个元素进行迭代。因为外层会进行循环,所以每次循环都会减一次temp,这里就可以不用进行求和。
                for k in range(K):
                    P[u,k] = P[u,k] - alpha * 2 * (temp * Q[k,i] + 2 * lamda * P[u,k])
                    Q[k,i] = Q[k,i] - alpha * 2 * (temp * P[u,k] + 2 * lamda * Q[k,i])

        predR = P.dot(Q)
        
        #计算损失函数
        cost = 0
        for u in range(M):
            for i in range(N):
                if R[u,i] > 0:
                    cost += (R[u,i] - P[u,:].dot(Q[:,i])) ** 2
                    # 加上正则项。
#                     for k in range(K):
#                         cost += lamda * (P[u,k] ** 2 + Q[k,i] ** 2)
        for u in range(M):
            for k in range(K):
                cost += lamda * (P[u,k] ** 2)
        for i in range(N):
            for k in range(K):
                cost += lamda * (Q[k,i] ** 2)
                
        # 损失函数小于0.01或者到达最大迭代次数后停止迭代
        if cost < 0.0001:
            break
    
    return P,Q,predR,cost
3.测试
P,Q,predR,cost = LFM_grad_desc(P,Q,R,K,max_iter,alpha,lamda)
print(P)
print(Q)
print(predR)
print(cost)
print(R)

predR:
[[ 4.01594695 0.10151165 1.91886574 0.00883132 0.8761809 ]
[-0.03814964 1.9012524 3.01832306 0.0104603 0.11877992]
[ 1.02624249 0.06429425 1.95260444 3.97377553 -0.05076254]
[ 4.93047833 -0.12854767 0.09812958 3.00074707 1.0747292 ]
[ 3.98470824 0.03720225 0.96823431 4.97001588 1.03433814]
[ 0.01796786 2.97706425 1.99672367 3.98218695 0.93986482]]
cost: 0.2323983131319584
R:
[[4 0 2 0 1]
[0 2 3 0 0]
[1 0 2 4 0]
[5 0 0 3 1]
[4 0 1 5 1]
[0 3 2 4 1]]

5.3 概念梳理

5.3.1 User-CF和Item-CF的比较

同样是协同过滤,如何选择是User-CF还是Item-CF呢?

5.3.2 协同过滤的推荐优缺点

六、电影推荐项目实战

数据生命周期
系统模块设计
项目系统架构
架构图
系统数据流图 主要数据模型

6.1 离线推荐服务建设

6.1.1 流程

  1. 将文本数据转换为数据对象(包括uid,mid和rating)并存储到数据库中
  2. 从数据库中读取数据并转化为对象,通过框架自带的ALS算法计算。然后将uid和mid计算笛卡尔积得到一个空矩阵对象,将该对象作为predict方法的参数,计算得到完整的预测矩阵
  3. 然后groupBy(uid),对groupBy后的结果根据rating评分从高到低排序,得到每个用户的推荐列表。
  4. 还需要得到每个产品的隐特征用于计算电影的相似度矩阵(用于实时推荐)。
    1)首先需要通过框架方法productFeatures得到所有物品的隐特征(mid,features),其中features是隐特征的数组。
    2)然后隐特征自己和自己求笛卡尔积,过滤掉相同物品的元组,就得到了不同物品的元组。引入jblas依赖,将隐特征数组转换为矩阵DoubleMatrix对象,通过thing1.dot(thing2)/thing1.norm2()*thing2.norm2()计算得到余弦相似度,得到每个product和不同的product的相似度,筛选出相似度大于0.6的物品。然后groupBy(mid),根据相似度进行从大到小的排序。
    4.最后将推荐列表持久化到数据库。

6.1.2 模型的评估和参数选择

6.1.2.1 原理

在进行计算时需要给定rank(特征数),iterations(迭代次数)和lambda(正则系数)参数。
因此需要根据数据集选择最合适的参数。

6.1.2.2 计算流程

  1. 将数据集随机切分为训练集和测试集。通过训练集进行训练,通过测试集来评估参数的合理性。
  2. 选择RMSE(均方根误差)作为评估函数,计算不同参数下的结果,选择取到RMSE最小的参数作为ALS算法的参数。

6.2 实时推荐服务建设

6.2.1 实时推荐服务

鉴于用户评分的产品进行推荐,推荐出与该产品相似度高的产品。但是评分的产品可能是低分,所以不能单纯推荐相似度高的产品,而应该结合评分进行加权计算。

6.2.2 计算流程



七、冷启动问题

处理这个问题一般是通过当用户首次登陆时,为用户提供交互式的窗口来获取用户对于物品的偏好。

在本项目中,当用户第一次登陆的时候,系统会询问用户对于影片类别的偏好。如下:

image.png

当获取用户的偏好之后,对应于需要通过用户偏好信息获取的推荐结果,则更改为通过对影片的类型的偏好的推荐。



八、基于内容的推荐

上一篇下一篇

猜你喜欢

热点阅读