欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

【机器学习】吴恩达作业8.1,python实现推荐系统(协同过滤算法)

最编程 2024-01-03 09:43:22
...

8.1推荐系统

    实现协同过滤算法并将它运用在电影评分的数据集上,最后根据新用户的评分来给新用户推荐10部电影。这个电影评分数据集由1到5的等级组成。数据集有nu = 943个用户和nm = 1682部电影。在计算完协同过滤的代价函数以及梯度后,将使用牛顿共轭梯度法求得参数。
    数据集中,Y是一个(1682, 943)的矩阵,存储了从1到5的评分,矩阵R为二值指标矩阵,其中如果用户j对电影i进行评级,R(i, j)=1,否则R(i,j)=0。协同过滤的目的是预测用户尚未评分的电影的评分,即R(i,j)=0的条目。这样就可以向用户推荐预测评分最高的电影。
    X是电影的特征矩阵,Theta是用户的特征矩阵,X的第i行对应x (i) ,表示第i部电影的特征向量(即描述第i部电影内容的特征量),Theta的第j行对应θ (j),表示第j个用户的特征向量(即第j个用户对不同类型电影的偏好),这里x (i),θ (j)都是100维的向量,因此X的维数是(1682,100),Theta的维数是(943,100)。

线性回归的代价函数和梯度 

协同过滤

根据电影的特征来预测用户参数,再根据得到的用户参数来预测电影特征,循环这个过程 

协同过滤过程

1 随机初始化x^{(1)}\cdots x^{(n_{m})} ,\theta ^{(1)}\cdots \theta ^{(n)}为较小的数值

2 用梯度下降优化Jx^{(1)}\cdots x^{(n_{m})}\theta ^{(1)}\cdots \theta ^{(n)})到损失最小得到优化后的电影特征和用户参数

3 用得到的特征和参数计算评分\left ( \theta ^{(i)} \right )^{T}\left ( X^{(i)} \right )

 低秩矩阵分解 

寻找和电影i相关的电影j 
衡量两个电影的相关度,即两个电影的特征向量的距离∥x (i)−x (j)∥,想要找到5部与电影i最相似的电影,只需找到∥x (i)−x (j)∥最小的五个

均值标准化

Y−μ,可以避免出现因为某用户对所有电影没有评分,而导致该用户所有预测的评分都为零的情况。

预测完之后加回均值μ。相当于预测出的没有评分的用户的评分,为所有用户评分的均值。

python 

1 argsort(a, axis=-1, kind=‘quicksort’, order=None):

 argsort输出的是排序后当前位置所应该放的值的索引,比如 b=[‘b’, ‘a’, ‘c’],排序后第0个位置应该放’a’(索引为1),第1个位置应该放’b’(索引为0),第2个位置应该放’c’(索引为2),故最终输出为[1 0 2]

代码实现 

1 导入数据并提取数据 

#给用户推荐电影
import numpy as np
import matplotlib.pyplot as plt
import scipy.io as sio 

mat = sio.loadmat('ex8_movies.mat')
mat.keys()#dict_keys(['__header__', '__version__', '__globals__', 'Y', 'R'])
Y,R = mat['Y'],mat['R']
Y.shape,R.shape#((1682, 943), (1682, 943))

data = sio.loadmat('ex8_movieParams.mat')
data.keys()#dict_keys(['__header__', '__version__', '__globals__', 'X', 'Theta', 'num_users', 'num_movies', 'num_features'])

X,Theta,nu,nm,nf = data['X'],data['Theta'],data['num_users'],data['num_movies'],data['num_features']
X.shape,Theta.shape,nu,nm,nf
# ((1682, 10),
#  (943, 10),
#  array([[943]], dtype=uint16),
#  array([[1682]], dtype=uint16),
#  array([[10]], dtype=uint8))

nu = int(nu)#将用户数量,电影数量,特征数量由数组转换为整数
nm = int(nm)
nf = int(nf)
nu,nm,nf

 2 序列化与解序列化

#1.序列化参数
def serialize(X,Theta):
    return np.append(X.flatten(),Theta.flatten())#降维

#2.解序列化参数
def deserialize(params,nm,nu,nf):#params:X,theta序列化以后
    X = params[:nm*nf].reshape(nm,nf)
    Theta = params[nm*nf:].reshape(nu,nf)
    return X,Theta

  3 损失函数

# 损失函数
def costFunc(params,Y,R,nm,nu,nf,lamda):
    X,Theta = deserialize(params,nm,nu,nf)
    cost = 0.5 * np.square((X @ Theta.T - Y) * R).sum()    #这里要点乘R,也就是对应位置相乘,R为0没打分,需要预测,没有误差

    reg1=  0.5 * lamda * np.square(X).sum()#正则项
    reg2=  0.5 * lamda * np.square(Theta) .sum()
    return cost + reg1 + reg2

#取子数组减少损失函数验证时间
users = 4
movies = 5
features = 3
X_sub = X[:movies,:features]#  5 3
Theta_sub = Theta[:users,:features]#4 3
Y_sub = Y[:movies,:users]#
R_sub = R[:movies,:users]

cost1 = costFunc(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 0)
cost1#22.224603725685675

cost1 = costFunc(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 1.5)
cost1#31.344056244274217

 4 梯度

#4.梯度
def costGradient(params,Y,R,nm,nu,nf,lamda):
    X,Theta = deserialize(params,nu,nm,nf)#解序列化
    X_grad = ((Theta @ X.T - Y) * R).T @ Theta + lamda * X    #(5 ,3)
    Theta_grad = ((Theta @ X.T  - Y) * R) @ X + lamda * Theta #(4, 3)
    return serialize(X_grad,Theta_grad)  #传出序列化数据  

grad1 = costGradient(serialize(X_sub,Theta_sub ),Y_sub,R_sub,movies,users,features,lamda = 0)
#grad1

5 添加自己的评分 

#5.添加一个新用户
my_ratings = np.zeros((nm, 1))

#添加电影评分
my_ratings[9] = 5 
my_ratings[66] = 5
my_ratings[96] = 5
my_ratings[121] = 4
my_ratings[148] = 4
my_ratings[285] = 3
my_ratings[490] = 4
my_ratings[599] = 4
my_ratings[643] = 4
my_ratings[958] = 5
my_ratings[1117] = 3

#6.均值归一化
def normalizeRatings(Y,R):
    Y_mean = (Y.sum(axis = 1)/R.sum(axis = 1)).reshape(-1,1)#返回二维数组 # 这里求均值后是一维数组,为了方便,reshape成二维,可以直接矩阵相减,将(1682,)——>(1682,1)
    Y_norm = (Y - Y_mean)*R
    return Y_norm,Y_mean

Y_norm, Y_mean= normalizeRatings(Y,R) #错误点只进行了y_norm!!!!!!!!

#7.参数初始化

X = np.random.random((nm,nf))#电影特征
Theta = np.random.random((nu,nf))#用户参数
params = serialize(X,Theta)#序列化
lamda = 5

Y.shape#(1682, 943)

 6 训练模型,拟合,预测

#8.模型训练
from scipy.optimize import minimize
res = minimize(x0 = params,
               fun = costFunc,
               args = (Y_norm,R,nm,nu,nf,lamda),
               method = 'TNC',
               jac = costGradient,
               options = {'maxiter': 100})

params_fit = res.x
fit_X,fit_Theta = deserialize(params_fit,nm,nu,nf)#解序列化得到X,Theta

#9.预测
Y_pre = fit_X@fit_Theta.T#预测用户评分
print(Y_pre)
y_pre = Y_pre[:,-1] + Y_mean.flatten()#取评分矩阵中最后一列(后来添加进去的我作为用户的预测值)加上评分均值
index = np.argsort(-y_pre)#加-号从大到小排序
index[:10]#查看排名前十的电影array([1448,  660, 1121, 1535,  175,   97,  299,  496, 1624,  271],dtype=int64)

 7 推荐电影

movies = []
with open('movie_ids.txt','r',encoding = 'latin 1')as f:
    for line in f:
        tokens = line.strip().split(' ')#split按空格分割
#strip( ) 用于移除字符串头尾指定的字符(默认为空格或换行符)或字符序列。
        movies.append(' '.join(tokens[1:]))#不要电影前面的数字序号


len(movies)#1682

#推荐十部电影
for i in range(10):
    print(index[i],movies[index[i]],y_pre[index[i]])
    
'''1499 Santa with Muscles (1996) 9.192908079544114
1466 Saint of Fort Washington, The (1993) 8.67795917524353
1188 Prefontaine (1997) 8.645424940425478
1292 Star Kid (1997) 8.321090861320673
1650 Spanish *er, The (1997) 8.299654010882321
813 Great Day in Harlem, A (1994) 8.262249047464023
155 Reservoir Dogs (1992) 8.226853101881623
600 For Whom the Bell Tolls (1943) 8.220591016552554
1652 Entertaining Angels: The Dorothy Day Story (1996) 8.098208777452498
302 Ulee's Gold (1997) 8.083023368123555'''