推荐系统算法相关笔记

离线推荐使用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的一些局限性:

  • 物品内容不完全或难以获取,依然可以提供通过其他用户的反馈给出推荐。
  • CF基于用户之间对物品的评价质量,避免了CB仅依赖内容可能造成的对物品质量判断的干扰。
  • CF推荐不受内容限制,只要其他类似用户给出对不同物品的兴趣,CF就可以推荐出内容差异很大的物品。

1.1.3.1 基于近邻的协同过滤

  • 基于用户(User-CF)
    通过用户行为数据判断相似的用户,将相似用户喜欢的物品推荐给用户。


    image.png
  • 基于物品(Item-CF)
    通过用户行为数据判断相似的物品,然后将相似物品推荐给用户。


    image.png

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

  • 奇异值分解(SVD)
  • 潜在语义分析(LSA)
  • 支撑向量机(SVM)
image.png

1.1.4 混合推荐

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

  • 加权混合:用线性公式(linear formula)将几种不同的推荐按照一定权重组合起来。
  • 切换混合:允许在不同情况下(数据量,系统运行状况,用户和物品的数目等),选择最为合适的推荐机制
  • 分区混合:采用多种推荐机制,将不同的推荐结果分不同的区显示给用户
  • 分层混合:采用多种推荐机制,并将一个推荐机制的结果作为另一个输入,从而综合各个推荐系统的优缺点,得到更加准确的推荐。

1.2 推荐系统实验方法

  • 离线实验:通过训练集和测试机进行训练测试
  • 用户调查:让用户在测试的推荐系统上完成一些任务,记录他们的行为进行分析
  • 在线实验:使用真实的业务数据进行AB测试,直观对比两套推荐系统的准确性。

1.3 推荐模型评测指标

  • 准确度
  • 用户满意度
  • 覆盖率
  • 多样性
  • 惊喜度
  • 信任度
  • 实时性
  • 健壮性
  • 商业目的

1.4 推荐准确度评测

1.4.1 评分预测

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

image.png

1.4.2 Top-N推荐

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

image.png
  • 正确率(accuracy):正确分类的item数量与总数之比
    A = (20+50)/ 100 = 70%
  • 精准率(precision):被检索到的item中,应该被检索到的item占比。
    P = 20 /(20+30) = 40%
  • 召回率(recall):检索到的item占应该检索到的item的占比
    R = 20 /(20 + 0)= 100%




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

image.png

2.1 分类

2.1.1 有监督学习(Supervised Learning)

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


image.png
  • 监督学习算法构建了包括输入和所需输出的一组数据的数学模型。称为训练数据。
  • 监督学习主要包括分类和回归。
  • 当输出被限制为有限的一组值(离散数据)时使用分类算法;当输出可以具有范围内的任何数值(连续数值)时使用回归算法。
  • 相似度学习是和回归和分类都密切相关的一类监督机器学习,它的目的是使用相似性函数从样本中学习,这个函数可以度量两个对象之间的相似度或关联度。它在排名、推荐系统、视觉识别跟踪、人脸识别等方面有很好的应用场景。

监督学习三要素:

  • 模型model:总结数据内在规律,用数学函数描述系统
  • 策略strategy:选取最优模型的评价准则
  • 算法algorithm:选取最优模型的具体方法

2.1.2 无监督学习(Unsupervised Learning)

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

  • 采用一组仅包含输入的数据,通过寻找数据中的内在结构来进行样本点的分组或聚类
  • 算法从没有被标记或分类的测试数据中学习。
  • 无监督学习算法不是响应反馈,而是识别数据中的共性特征;对于一个新数据,可以通过判断其中是否存在这种特征,来做出响应的反馈。
  • 无监督学习的核心应用是统计学中的密度估计聚类分析

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

2.1.3 强化学习

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

2.2 模型评估策略

2.2.1 模型评估

  • 训练集和测试机

  • 损失函数和经验风险
    度量预测结果和真实结果的偏差的函数就是损失函数。损失函数是模型中参数的函数。

    image.png

    模型f(x)关于训练集的平均损失称为经验风险(empirial risk)。即求有效范围内的损失函数之和。
    image.png

    经验风险最小化(Empirical Risk Minimization,ERM)策略认为经验风险最小的模型就是最优模型。

  • 训练误差和测试误差
    训练误差是关于训练集的平均损失。训练误差大小可以来判断给定问题是否容易学习,但本质上不重要。
    测试误差是关于测试机的平均损失。真实反映了模型对位置数据的预测能力,这种能力被称为泛化能力


2.2.2 模型选择

  • 过拟合和欠拟合
    1.模型学习太彻底以致把噪声数据特征也学习了,特征集过大,模型泛化能力太差,称为过拟合
    2.模型没有很好地捕捉数据特征,特征集过小,不能很好的拟合数据,称为欠拟合

    image.png

    如上图M过小就是欠拟合,M过大就是过拟合。
    当模型复杂度增大时,训练误差会减小趋向0;而测试误差会先减少后增大。当模型复杂度过大时,就会发生过拟合;所以模型复杂度应适当。

  • 正则化
    结构风险最小化(Structural Risk Minimization,SRM)是在经验风险(ERM)上加上表示模型复杂度的正则化(regularizer)项或者叫惩罚项。正则化项一般是模型复杂度的单调递增函数,即模型越复杂,正则化值越大。
    结构风险最小化的典型实现是正则化(regularization)

    image.png

  • 交叉验证
    为了提高测试误差,引入了交叉验证。
    1.如果样本数量充足,一种简单的方法就是随机将数据集切成三部分:训练集、验证集和测试集。训练集用于训练模型,验证集用于模型选择,测试集用于学习方法评估。
    2.数据不充足,可以重复地利用数据——交叉验证(cross validation):①简单交叉验证:随机分两部分,如70%作为训练集,30%作为测试集 ②S折交叉验证(K重交叉验证):将数据随机切分为S个互不相交、相同大小的子集;S-1个做训练集,剩下一个做测试集。重复进行训练集、测试集选取,有S种可能的选择。 ③留一交叉验证:数据量非常小的情况下,留一条数据作为测试集。


2.3 监督学习介绍

2.3.1 回归

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

2.3.2 回归问题的分类

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

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

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

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

  • 梯度下降(gradient descent)是一种常用的一阶优化方法,是求解无约束优化问题最简单、最经典的方法之一。损失函数是系统的函数,那么如果系数沿着损失函数的负梯度方向变化,此时损失函数减少最快,能够以最快速度下降到极小值。

    image.png

    如上图,θ 表示函数的参数,J(θ)是损失函数,需要快速找到J(θ)的极小值,那么就要计算负梯度并不断进行迭代。α是步长,表示向梯度方向前进了α。
    与最小二乘法不同点:①梯度下降可以选取其他损失函数,最小二乘一定是平方损失函数;②最小二乘法是直接找全局最小;而梯度下降法是迭代法;③最小二乘法计算繁琐且复杂情况下未必有解;梯度下降法迭代计算简单,但找到的是局部最小,只有是凸函数时才是全局最小;到最小点附近收敛速度会变慢,且对初始点选择极为敏感。

  • 牛顿法(Newton method)考虑了二阶导数,考虑了坡度的变化率。


    image.png
  • 拟牛顿法通过正定矩阵近似海塞矩阵的逆矩阵,从而大大简化计算过程。




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

有输入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)的特征相匹配,就能得到用户对物品的感兴趣程度。

  • PGC(专家生成内容):专业人员对物品打标签。
  • UGC(用户生成内容):基于用户行为对物品打标签。

5.2.3 相似度计算:

image.png

高层次架构:

image.png

5.2.4 特征工程

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

  • 归一化:
    image.png

    image.png
  • 离散化:
    image.png

    image.png

    image.png

②类别型

image.png

image.png

③时间型

image.png

④统计型

image.png

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

image.png

5.2.6 基于UGC的推荐

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

  • 简单推荐算法:问题是这种推荐方法倾向于给热门标签和热门物品较大的权重,推荐个性化和新颖度会降低。
    image.png
  • TF-IDF(Term Frequency-Inverse Document Frequency)算法:是一种用于资讯检索与文本挖掘的常用加权技术。
    会对在语料库中出现的词进行惩罚,找出文章最具代表性的关键词。
    image.png
    image.png

    image.png

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的一些局限:

  • 物品内容不完全或者难以获得时,依然可以通过其他用户的反馈给出推荐
  • CF基于用户之间对物品的评价质量,避免了CB仅依赖内容可能造成的对物品质量判断的干扰
  • CF推荐不受内容限制,只要其他类似用户给出了对不同物品的兴趣,CF就可以给用户推荐出内容差异很大的物品(但有某种内在联系)

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呢?

  • 如果物品的个数远远小于用户的数量,而且物品的个数和相似度相对比较稳定,同时基于物品的机制比基于用户的实时性更好一些,Item-CF是主流。如电商、电影、音乐网站等用户数量远大于物品数量的场景下使用Item-CF。
  • 物品个数大于用户个数,而且物品迭代较快,稳定度不高。如新闻网站场景下使用User-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

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




八、基于内容的推荐

©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容