推荐系统及其实现

概述

个性化的推荐系统被应用于互联网生活中的方方面面,本篇文章中,将会利用电影评分数据,实现一个简单的电影个性化推荐系统,并实现推荐系统相关的协同过滤算法(Collaborative Filter Learning Algorithm)。这些电影评分数据集的评分范围为1-5,数据集中包含着943个用户(用表示)和1682部电影(用表示 )。

电影评分数据集的处理

这些电影评分数据集被保存在一个ex8_movies.mat的文件中,数据集中矩阵表示电影评分数据,用表示,其值范围是(from 1 to 5),其含义是第个用户对第个电影的评分数数据,其大小为,矩阵是一个二进制值的标识矩阵,如果表示第个用户对第部的电影进行了评价,若是为0,则表示没有该用户没有对此电影做出评价。协同过滤算法的适用目标是为用户还未做出评价的电影预测评分,也就是被所标识的数据,这样能够使得算法能够将预测评分最高电影推荐给用户。

为了了解数据集,可以先求出对第一个电影的评分的均值,并利用python的绘图功能绘出所有评分数据的颜色分布,用以可视化的显示相关数据。具体实现代码如下所示:

#导入相关库
import matplotlib.pyplot as plt
import numpy as np
import scipy.io as scio
import scipy.optimize as opt

# 加载数据
data = scio.loadmat('ex8_movies.mat')
Y = data['Y']
R = data['R']
#求出第一个电影的平均值
average = np.mean(Y[0,np.where(R[0]==1)])
#可视化显示
plt.figure()
plt.imshow(Y)
plt.colorbar()
plt.xlabel('Users')
plt.ylabel('Movies')

最后,以上代码数据结果如下图所示:


除了电影评分数据集中的矩阵和矩阵外,对于特征矩阵和参数矩阵有如解释:
矩阵和的形式如下图所示:

矩阵的第行对应着第部电影的特征向量,而参数矩阵的第行对应着第个用户的参数向量。和都是维向量,在本此算法实现中,令,即也就是矩阵的大小是,而矩阵的大小是.

协同过滤算法的实现

  • 算法介绍
    电影推荐系统的协同过滤算法需要考虑维参数向量和向量,此模型预测的第个用户对第个电影的评分可以用表示,给定一些用户对一些电影的评分数据集,可以通过协同过滤算法得到一系列参数向量和能够对模型实现最好的拟合(最小化平方误差)。
  • 算法描述
    尽管与单变量线性回归的实现方式很相似,但还是略有不同,单变量线性回归的实现方式只需要通过梯度下降最小化损失函数的到一个参数向量,而协同过滤算法需要得到两个参数,其实现方法可以简单总结为控制变量法,即给定参数矩阵,求出参数矩阵,再将参数矩阵保持不变,求出参数矩阵, 其实现方式如下公式所示,需要注意的是,为了防止过拟合,也要加入正则项。
    给定参数矩阵,预估参数矩阵:

给定参数矩阵,预估参数矩阵参数阵:

综合以上,对两个参数矩阵同时进行最小化的公式如下所示:

minJ(x^{(1)},……x^{(n_m)},\theta^{(1)}……\theta^{(n_u)}) = \frac{1}{2}\sum_{(i,j):r(i,j) =1}((\theta^{(j)})^T*x^{(i)}-y^{(i,j)})^2 \frac{\lambda}{2}\sum_{j=1}^{n_u}\sum_{k=1}^{n} (\theta_{k}^{(j)})^2+\frac{\lambda}{2}\sum_{i=1}^{n_m}\sum_{k=1}^{n} (x_{k}^{(i)})^2

综合以上,对协同过滤算法的实现步骤可以有以下总结:

  1. 初始化和,并令其取得较小的随机值;

  2. 利用梯度下降算法最小化损失函数,并得到每一个参数向量,如下所示:

  • 算法实现
    1.在算法实现之前,需要对数据进行预处理操作,具体如下所示:
#加载训练前的数据
data = scio.loadmat('ex8_movieParams.mat')
X = data['X']
theta = data['Theta']
num_users = data['num_users']
num_movies = data['num_movies']
num_features = data['num_features']

#减少数据规模,能够使得数据算法运行的更快
num_users = 4
num_movies = 5
num_features = 3
X = X[0:num_movies, 0:num_features]
theta = theta[0:num_users, 0:num_features]
Y = Y[0:num_movies, 0:num_users]
R = R[0:num_movies, 0:num_users]
  1. 协同过滤损失函数的计算可如以下代码所示:
def cofi_cost_function(params, Y, R, num_users, num_movies, num_features, lmd):
    X = params[0:num_movies * num_features].reshape((num_movies, num_features))
    theta = params[num_movies * num_features:].reshape((num_users, num_features))
    cost = 0
    X_grad = np.zeros(X.shape)
    theta_grad = np.zeros(theta.shape)

    hypothesis = (np.dot(X, theta.T) - Y) * R

    cost = (1/2)*np.sum(hypothesis**2) + (lmd/2)*np.sum(theta**2) + (lmd/2)*np.sum(X**2)

    X_grad = np.dot(hypothesis, theta) + lmd * X
    theta_grad = np.dot(hypothesis.T, X) + lmd * theta

    #将两个梯度向量连接成为一维向量
    grad = np.concatenate((X_grad.flatten(), theta_grad.flatten()))

    return cost, grad
#对损失函数进行输出并评估模型性能
cost, grad = cofi_cost_function(np.concatenate((X.flatten(), theta.flatten())), Y, R, num_users, num_movies, num_features, 0)
print(cost)

注意:利用协同过滤算法得到的模型的梯度值之后,为了消除误差,需要对梯度值进行进一步检测,这个过程称之为梯度检测。具体的计算公式如下所示:

梯度检测的代码实现如下所示:

def compute_numerial_gradient(cost_func, theta):
    numgrad = np.zeros(theta.size)
    perturb = np.zeros(theta.size)

    e = 1e-4

    for p in range(theta.size):
        perturb[p] = e
        loss1, grad1 = cost_func(theta - perturb)
        loss2, grad2 = cost_func(theta + perturb)

        numgrad[p] = (loss2 - loss1) / (2 * e)
        perturb[p] = 0

    return numgrad

随机设置一些输入数据运行协同过滤算法和加载数据集运行协同过滤算法得到的梯度值如果比较接近,则说明梯度误差较小,该算法能得到最小的损失函数值并且能够收敛,其具体代码实现和运行结果如下所示:

def check_cost_function(lmd):

    # 创建随机的样本
    x_t = np.random.rand(4, 3) #4x3随机矩阵
    theta_t = np.random.rand(5, 3) #5x3随机矩阵

    Y = np.dot(x_t, theta_t.T)  # 4x5
    Y[np.random.rand(Y.shape[0], Y.shape[1]) > 0.5] = 0 #Y矩阵的随机位置的值大于0.5,则设为0
    R = np.zeros(Y.shape)
    R[Y != 0] = 1   #设置R矩阵部分值为1,认为某用户对某电影做出评价

    # 运行梯度检测
    x = np.random.randn(x_t.shape[0], x_t.shape[1])
    theta = np.random.randn(theta_t.shape[0], theta_t.shape[1])
    num_users = Y.shape[1]  #5
    num_movies = Y.shape[0]  #4
    num_features = theta_t.shape[1] #3
    
    #随机组的损失函数值和梯度计算
    def cost_func(p):
        return cofi_cost_function(p, Y, R, num_users, num_movies, num_features, lmd)

    numgrad = compute_numerial_gradient(cost_func, np.concatenate((x.flatten(), theta.flatten())))
    #数据集的损失函数值和梯度计算
    cost, grad = cofi_cost_function(np.concatenate((x.flatten(), theta.flatten())), Y, R, num_users, num_movies, num_features, lmd)

    print(np.c_[numgrad, grad])
  

    diff = np.linalg.norm(numgrad - grad) / np.linalg.norm(numgrad + grad)
    print('the relative difference will be small (less than 1e-9).\n'
          'Relative Difference: {:0.3e}'.format(diff))

设置,运行以上代码得到的部分结果如下图所示,两者差值不大于.

电影推荐系统的实现

有了以上关于协同过滤模型的定义和实现,现在可以用以上算法实现一个电影推荐系统了,为了便于理解算法的运行过程,可以自己随机设置一些电影的评分数据和用户数据,具体代码实现如下所示:

  1. 加载电影评分数据
    电影评分数据的的以如下图所示的txt格式存在,逐行读取代码的格式如下所示:


def load_movie_list():
    movie_list = []
    with open("movie_ids.txt",encoding='utf-8') as f:
        lines = f.readlines()
        for line in lines:
            idx, *movie_name = line.split(' ')
            movie_list.append(' '.join(movie_name).rstrip())

    return movie_list

# 重新设置一些随机评分数据
movie_list = load_movie_list()
my_ratings = np.zeros(len(movie_list))
my_ratings[0] = 4
my_ratings[97] = 2
my_ratings[6] = 3
my_ratings[11] = 5
my_ratings[53] = 4
my_ratings[63] = 5
my_ratings[65] = 3
my_ratings[68] = 5
my_ratings[182] = 4
my_ratings[225] = 5
my_ratings[354] = 5
print('New user ratings:\n')
for i in range(my_ratings.size):
    if my_ratings[i] > 0:
        print('Rated {} for {}'.format(my_ratings[i], movie_list[i]))

以上代码运行结果如下所示:


  1. 实现电影推荐
    实现电影推荐之前,需要对电影评分数据做标准化处理。需要注意的是,为了减少算法运行时间,标准化处理数据之前,只考虑对此电影做出评价的用户的数据,而不考虑未对此电影做出评价的用户(即也就是)的数据,这样可以减少数据规模,节省算法运行时间。具体实现代码如下所示:
data = scio.loadmat('ex8_movies.mat')
Y = data['Y']
R = data['R']

# Y 是一个1682x943的矩阵, 包含943个用户对1682个电影的评分哦数据(1-5)

#
# Y 是一个1682x943的矩阵, 当 R[i,j] = 1 认为用户j对电影i做出了评价
# 将自己的评分数据添加到数据集中
Y = np.c_[my_ratings, Y]
R = np.c_[(my_ratings != 0), R]

#标准化数据
Ynorm, Ymean = normalize_ratings(Y, R)

#特征向量
num_users = Y.shape[1]   #944
num_movies = Y.shape[0]   #1682
num_features = 10

# 设置初始参数
X = np.random.randn(num_movies, num_features)
theta = np.random.randn(num_users, num_features)

initial_params = np.concatenate([X.flatten(), theta.flatten()])

lmd = 10


def cost_func(p):
    return cofi_cost_function(p, Ynorm, R, num_users, num_movies, num_features, lmd)[0]


def grad_func(p):
    return cofi_cost_function(p, Ynorm, R, num_users, num_movies, num_features, lmd)[1]

theta, *unused = opt.fmin_cg(cost_func, fprime=grad_func, x0=initial_params, maxiter=100, disp=False, full_output=True)


X = theta[0:num_movies * num_features].reshape((num_movies, num_features))
theta = theta[num_movies * num_features:].reshape((num_users, num_features))

print('Recommender system learning completed')
print(theta)

input('Program paused. Press ENTER to continue')

运行结果如下图所示:


最后,利用得到的参数,实现电影推荐,其结果如下所示:

p = np.dot(X, theta.T)
my_predictions = p[:, 0] + Ymean

indices = np.argsort(my_predictions)[::-1]
print('\nTop recommendations for you:')
for i in range(10):
    j = indices[i]
    print('Predicting rating {:0.1f} for movie {}'.format(my_predictions[j], movie_list[j]))

print('\nOriginal ratings provided:')
for i in range(my_ratings.size):
    if my_ratings[i] > 0:
        print('Rated {} for {}'.format(my_ratings[i], movie_list[i]))

《吴恩达机器学习》笔记算是基本完成了,若此学习笔记有任何错误或者不当之处,欢迎各位大佬指正。

你可能感兴趣的:(推荐系统及其实现)