导入库并读取数据
因为这次的数据是mat文件,需要使用scipy库中的loadmat进行读取数据。
通过对数据类型的分析,发现是字典类型,查看该字典的键,可以发现又X等关键字。
import numpy as np
import scipy.io as sio
from scipy.optimize import minimize
#读取数据
path_movies = "./ex8_movies.mat"
data_movies = sio.loadmat(path_movies)
# print(data_movies.keys())
Y = data_movies.get("Y")
R = data_movies.get("R")
path_movieParams = "./ex8_movieParams.mat"
data_movieParams = sio.loadmat(path_movieParams)
# print(data_movieParams.keys())
X = data_movieParams.get("X")
Theta = data_movieParams.get("Theta")
num_users = data_movieParams.get("num_users")
num_movies = data_movieParams.get("num_movies")
num_features = data_movieParams.get("num_features")
# print(num_users[0][0])
均值归一化
当对于一个没有对任何电影评过分的用户E会进行怎样的操作呢?
当最小化代价函数的时候,只有这一项可以影响代价函数,因此为了保证代价函数足够小,会将
都赋值为0,那么用户E对所有电影的评分都为0,这样就会没有任何意义。
为了避免这种情况的发生,我们可以对数据进行均值归一化,也就是先按行算出评分的平均值,然后。拿新的Y矩阵再去预测评分,预测完之后,需要将平均值再加回去。这样可以确保第一次位用户进行预测时,每个电影的评分都为平均值。
#均值归一化
def standardization(Y,R):
means = (np.sum(Y,axis=1) / np.sum(R,axis=1)).reshape(-1,1)
Y_norm = (Y - means) * R
return Y_norm,means
序列化和解序列化
这里说一下为什么要序列化参数,其实就是改变矩阵的形状,我们后面会用到opt.minimize()这个函数方法,这个优化方法里面需要的参数X0一定得是一维数组(nums,)
#序列化和解序列化
def serialization(X,Theta):
return np.append(X.flatten(),Theta.flatten())
def deserialization(params,nu,nm,nf):
X = params[:nm[0][0] * nf[0][0]].reshape(nm[0][0],nf[0][0])
Theta = params[nm[0][0] * nf[0][0]:].reshape(nu[0][0],nf[0][0])
return X,Theta
代价函数
#代价函数
def cost_func(params,nu, nm, nf,Y,R,lamda):
X, Theta = deserialization(params, nu, nm, nf)
cost = np.sum(np.power((X @ Theta.T - Y) * R,2))/ 2
reg1 = np.sum(np.power(X, 2)) * lamda / 2
reg2 = np.sum(np.power(Theta, 2)) * lamda / 2
return cost+reg1+reg2
梯度下降
#梯度下降
def gradient_descent(params,nu,nm,nf,Y,R,lamda):
X,Theta = deserialization(params,nu,nm,nf)
Theta_grad = ((Theta @ X.T - Y.T) * R.T) @ X + lamda * Theta
X_grad = ((X @ Theta.T - Y) * R) @ Theta + lamda * X
return serialization(X_grad,Theta_grad)
初始化参数
#初始化参数
X = np.random.random((num_movies[0][0],num_features[0][0]))
Theta = np.random.random((num_users[0][0],num_features[0][0]))
lamda = 5
params = serialization(X, Theta)
Y_normal,means = standardization(Y,R)
优化
#优化
res = minimize(x0=params,
fun=cost_func,
args=(num_users,num_movies,num_features,Y_normal,R,lamda),
method="TNC",
jac=gradient_descent,
options={'maxiter': 100})
fit_params = res.x
fit_X,fit_Theta = deserialization(fit_params,num_users,num_movies,num_features)
添加新用户和评分
my_ratings = np.zeros((num_movies[0][0], 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
预测
#预测
Y_pre = fit_X @ fit_Theta.T
y_pre = Y_pre[:,-1] + means.flatten()
index = np.argsort(-y_pre)
print(index[:10])