【机器学习】吴恩达作业8.1,python实现推荐系统(协同过滤算法)
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 随机初始化 ,为较小的数值
2 用梯度下降优化(,)到损失最小得到优化后的电影特征和用户参数
3 用得到的特征和参数计算评分
低秩矩阵分解
寻找和电影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'''