吴恩达机器学习作业8 - 异常检测和推荐系统(Python实现)

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情

吴恩达机器学习作业8 - 异常检测和推荐系统(Python实现)

Introduction

在第一个实验中,将实现异常检测算法,并应用于检测网络上出现故障的服务器。在第二个实验中,将使用协同过滤来构建一个电影推荐系统。

1 Anomaly detection(异常检测)

在本实验中,将实现一个异常检测算法来检测服务器计算机中的异常行为。特征测量的是每个服务器的响应速度(mb/s)和延迟(ms)。当服务器运行时,收集了 m = 307 m=307 个计算机行为的样本,因此有一个未标记的数据集 { x ( 1 ) , x ( 2 ) , . . . , x ( n ) } \lbrace x^{(1)}, x^{(2)} ,..., x^{(n)}\rbrace ,其中绝大多数样本是正常的,但还是有一小部分的样本是异常的。

使用高斯模型来检测数据集中的异常样本,首先从一个2D数据集开始,对算法运行过程进行可视化。在该数据集上,拟合一个高斯分布,然后找到概率比较低的值,以它为标准,比它低就认为是异常。推广:把异常检测算法应用于具有多个维度的数据集。

import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.io import loadmat
复制代码
data=loadmat("./data/ex8data1.mat")
data.keys()
复制代码
dict_keys(['__header__', '__version__', '__globals__', 'X', 'Xval', 'yval'])
复制代码
X=data['X']
Xval,yval=data['Xval'],data['yval']
X.shape,Xval.shape,yval.shape
复制代码
((307, 2), (307, 2), (307, 1))
复制代码
def plot_data(X):
    plt.figure(figsize=(8,6))
    plt.plot(X[:,0],X[:,1],'x',color="blue")
    plt.xlabel("Latency (ms)")
    plt.ylabel("Throughput (mb/s)")
    plt.title("The first dataset")
复制代码
plot_data(X)
复制代码


png

1.1 Gaussian distribution(高斯分布)

要执行异常检测,首先需要将数据的分布匹配一个模型。给定训练集 { x ( 1 ) , . . . , x ( m ) } ( x ( i ) R n ) \lbrace x^{(1)},...,x^{(m)}\rbrace (x^{(i)}\in R^n) ,估计每个特征 x i ( i [ 1 , n ] ) x_i(i\in [1,n]) 的高斯分布,求得 μ i , σ i 2 \mu_i,\sigma^2_i 来拟合数据。

image-20220330153822370

其中 μ \mu 是平均值, σ 2 \sigma^2 控制方差。

1.2 Estimating parameters for a Gaussian(高斯分布的参数估计)

估计参数 ( μ i , σ i 2 ) (\mu_i,\sigma^2_i)

image-20220330154148892image-20220330154154377

编写函数estimateGaussian:以数据矩阵X作为输入,并且输出一个 n n 维向量 μ \mu (包含 n n 个特征的平均值),另一个 n n 维向量 σ 2 \sigma^2 (包含 n n 个特征的方差)。

完成上述函数编写之后,可视化拟合的高斯分布的轮廓,可以看到大多数的样本是在概率最高的区域,而异常的样本是在概率较低的区域。

def estimateGaussian(X):
    mu=X.mean(axis=0)
    sigma=X.var(axis=0)
    return mu,sigma
复制代码
mu,sigma=estimateGaussian(X)
mu,sigma
复制代码
(array([14.11222578, 14.99771051]), array([1.83263141, 1.70974533]))
复制代码

计算 μ i , σ i 2 \mu_i,\sigma_i^2 参数下的高斯分布模型中每个样本的概率值:

img

def gaussian(X,mu,sigma):
    #X:(m,n)
    m,n=X.shape
    if(np.ndim(sigma)==1):
        sigma=np.diag(sigma)
    left=1.0/(np.power(2*np.pi,n/2)*np.sqrt(np.linalg.det(sigma)))
    right=np.zeros((m,1))
    for row in range(m):
        x=X[row]
        right[row]=np.exp(-0.5*(x-mu)[email protected](sigma)@(x-mu))
    return left*right
复制代码
# xplot = np.linspace(0, 25, 100)  # 分割为m=100份
# yplot = np.linspace(0, 25, 100)  # 分割为n=100份
# Xplot, Yplot = np.meshgrid(xplot, yplot)  # 生成n*m的矩阵
# points=np.c_[Xplot.flatten(),Yplot.flatten()]
# Z = gaussian(points, mu, sigma).reshape(Xplot.shape)
# contour = ax.contour(Xplot, Yplot, Z, [10**-11, 10**-7, 10**-5, 10**-3, 0.1], colors='k')  # 绘制等高线
# ax.plot(X[:, 0], X[:, 1], 'x', color="blue")
# ax.set_xlabel("Latency (ms)")
# ax.set_ylabel("Throughput (mb/s)")
# ax.set_title(" The Gaussian distribution contours of the distribution fit to the dataset")
# plt.show()

复制代码

将数据可视化:

def plotContours(fig=None,ax=None):#用于绘制等高线
    xplot=np.linspace(0,25,100)#分割为m=100份
    yplot=np.linspace(0,25,100)#分割为n=100份
    Xplot,Yplot=np.meshgrid(xplot,yplot)#生成n*m的矩阵
    # Z=np.exp(-0.5*(np.power(Xplot-mu[0],2)/sigma[0]+np.power(Yplot-mu[1],2)/sigma[1]))
    Z = gaussian(points, mu, sigma).reshape(Xplot.shape)
    contour=ax.contour(Xplot, Yplot, Z,[10**-11, 10**-7, 10**-5, 10**-3, 0.1],colors='k')#绘制等高线
    ax.plot(X[:,0],X[:,1],'x',color="blue")
    ax.set_xlabel("Latency (ms)")
    ax.set_ylabel("Throughput (mb/s)")
    ax.set_title(" The Gaussian distribution contours of the distribution fit to the dataset")
    # plt.show()
复制代码
# plt.figure(figsize=(8,6))
fig,ax=plt.subplots(figsize=(8,6))
plotContours(fig,ax)
plt.show()
复制代码


png

从上图可以清晰的看到,哪些样本的概率高,哪些样本的概率低,概率低的样本很大程度上就是异常值。

1.3 Selecting the threshold(选择阈值) ϵ \epsilon

现在已经估计了高斯参数,可以研究哪些样本在给定的分布下的概率非常高,哪些样本的概率非常低。概率低的样本很有可能是数据集中的异常值。确定哪些样本是异常值可以考虑基于交叉验证集选择一个阈值。在本节实验中,将实现一个算法:使用交叉验证集上的 F 1 F_1 分数来选择阈值 ϵ \epsilon

编写函数selectThreshold:使用交叉验证集 { ( x c v ( 1 ) , y c v ( 1 ) ) , . . . , ( x c v ( m c v ) , y c v ( m c v ) ) } \lbrace (x_{cv}^{(1)},y_{cv}^{(1)}),...,(x_{cv}^{(m_{cv})},y_{cv}^{(m_{cv})})\rbrace (其中, y = 1 y=1 对应一个异常样本,而 y = 0 y=0 对应一个正常样本)。对于每个交叉验证样本,将计算 p ( x c v ( i ) ) p(x_{cv}^{(i)}) 。概率向量 p ( x c v ( 1 ) ) , . . . , p ( x c v ( m c v ) ) p(x_{cv}^{(1)}),...,p(x_{cv}^{(m_{cv})}) 传递给函数selectThreshold作为参数pval,标签 y c v ( 1 ) , . . . , y c v ( m c v ) y_{cv}^{(1)},...,y_{cv}^{(m_{cv})} 传递给函数selectThreshold作为参数yval。函数selectThreshold返回两个参数:阈值 ϵ \epsilon (样本x的概率 p ( x ) < ϵ p(x)<\epsilon ,那么它被认为是一个异常样本), F 1 F_1 分数(在阈值 ϵ \epsilon 下寻找真异常样本的好坏)

F 1 F_1 分数使用precrec进行计算: F 1 = 2 × p r e c × r e c p r e c + r e c F_1=\frac{2\times prec \times rec}{prec+rec}

p r e c = t p t p + f p prec=\frac{tp}{tp+fp} r e c = t p t p + f n rec=\frac{tp}{tp+fn}

  • tp:本身是异常值并且模型预测成异常值,即真的异常值
  • fp:本身是正常值并且模型预测成异常值,即假的异常值
  • fn:本身是异常值并且模型预测成正常值,即假的正常值
  • precision :表示预测为异常值的样本中有多少是真的异常值的样本
  • recall:表示实际为异常值的样本中有多少成功预测出是异常值

使用SciPy内置的方法stats:计算数据点属于正态分布的概率:

from scipy import  stats
dist=stats.norm(mu[0],sigma[0])
dist.pdf(15)
复制代码
0.1935875044615038
复制代码

数组传递给概率密度函数,并获得数据集中每个点的概率密度:

dist.pdf(X[:,0])[0:10]
复制代码
array([0.183842  , 0.20221694, 0.21746136, 0.19778763, 0.20858956,
       0.21652359, 0.16991291, 0.15123542, 0.1163989 , 0.1594734 ])
复制代码

计算并保存上述的高斯模型参数的数据集中每个值的概率密度:

p=np.zeros((X.shape))
p[:,0]=stats.norm(mu[0],sigma[0]).pdf(X[:,0])
p[:,1]=stats.norm(mu[1],sigma[1]).pdf(X[:,1])
p.shape
复制代码
(307, 2)
复制代码

计算并保存上述的高斯模型参数的验证集中每个值的概率密度:

pval=np.zeros((Xval.shape))
pval[:,0]=stats.norm(mu[0],sigma[0]).pdf(Xval[:,0])
pval[:,1]=stats.norm(mu[1],sigma[1]).pdf(Xval[:,1])
pval.shape
复制代码
(307, 2)
复制代码

编写函数selectThreshold:为不同的 ϵ \epsilon 值计算 F 1 F_1 分数( F 1 F1 是真阳性,假阳性和假阴性的数量的函数),从而找到给定概率密度值和真实标签的最佳阈值。如果运行给定的数据集,得到的最佳的 ϵ \epsilon 约为 8.99 e 05 8.99e-05

def selectThreshold(pval,yval):
    best_e,best_f1,f1=0,0,0
    step=(pval.max()-pval.min())/1000
    for e in np.arange(pval.min(),pval.max(),step):
        preds=pval<e
        tp=np.sum(np.logical_and(preds==1,yval==1))
        fp=np.sum(np.logical_and(preds==1,yval==0))
        fn=np.sum(np.logical_and(preds==0,yval==1))
        precision=tp/(tp+fp)
        recall=tp/(tp+fn)
        f1=(2*precision*recall)/(precision+recall)
        if(f1>best_f1):
            best_f1=f1
            best_e=e
    return best_e,best_f1
复制代码
epsilon, f1 = selectThreshold(pval, yval)
epsilon, f1
复制代码
<ipython-input-92-c5a1372ada03>:9: RuntimeWarning: invalid value encountered in long_scalars
  precision=tp/(tp+fp)





(0.009566706005956842, 0.7142857142857143)
复制代码

将得到的最佳阈值应用于数据集,并可视化结果:

首先查看预测得到的异常样本:第一个数组是异常样本编号,第二个数组是异常样本对应的 x ( 0 ) / y ( 1 ) x(0)/y(1)

out_index=np.where(p<epsilon)
out_index
复制代码
(array([300, 301, 301, 303, 303, 304, 306, 306], dtype=int64),
 array([1, 0, 1, 0, 1, 0, 0, 1], dtype=int64))
复制代码

下图中红点是被标记为异常样本的点,但存在一些异常样本(但没有被标记),比如右上角也可能是一个异常样本。求出的最佳 ϵ \epsilon 以及绘制的图片不太理想。接下来换一个方法来求解:使用之前的函数gaussian求解样本的概率,而不是直接用scipy的库函数。

fig,ax=plt.subplots(figsize=(8,6))
plotContours(fig,ax)
plt.xlabel("Latency (ms)")
plt.ylabel("Throughput (mb/s)")
plt.title("The classified anomalies")
plt.scatter(X[out_index[0],0],X[out_index[0],1],s=100,facecolors="none",edgecolors="r")
plt.show()
复制代码


png

下面求解出的最佳 ϵ \epsilon 符合要求,并且绘制的图形也是比较理想的。

p = gaussian(X, mu, sigma)
pval = gaussian(Xval, mu, sigma)
epsilon, f1 = selectThreshold(pval, yval)
epsilon, f1
复制代码
<ipython-input-92-c5a1372ada03>:9: RuntimeWarning: invalid value encountered in long_scalars
  precision=tp/(tp+fp)





(8.990852779269496e-05, 0.8750000000000001)
复制代码
out_index=np.where(p<epsilon)
out_index
复制代码
(array([300, 301, 303, 304, 305, 306], dtype=int64),
 array([0, 0, 0, 0, 0, 0], dtype=int64))
复制代码
fig,ax=plt.subplots(figsize=(8,6))
plotContours(fig,ax)
plt.xlabel("Latency (ms)")
plt.ylabel("Throughput (mb/s)")
plt.title("The classified anomalies")
plt.scatter(X[out_index[0],0],X[out_index[0],1],s=100,facecolors="none",edgecolors="r")
plt.show()
复制代码


png

1.4 High dimensional dataset

在本节实验中的数据集中,每个样本都有 11 11 个特征,包含了计算服务器的更多属性。用上述的代码估计高斯参数 μ i , σ i 2 \mu_i,\sigma_i^2 ,根据这些参数评估数据集X和交叉验证集Xval中的样本的概率。最后,使用函数selectThreshold求得最佳阈值 ϵ \epsilon ,最佳 ϵ \epsilon 约为 1.38 e 18 1.38e-18 ,并且发现 117 117 个异常样本。

data2=loadmat("./data/ex8data2.mat")
X2=data2['X']
Xval2,yval2=data2['Xval'],data2['yval']
X2.shape,Xval2.shape,yval2.shape
复制代码
((1000, 11), (100, 11), (100, 1))
复制代码
mu,sigma=estimateGaussian(X2)
p = gaussian(X2, mu, sigma)
pval = gaussian(Xval2, mu, sigma)
epsilon, f1 = selectThreshold(pval, yval2)
epsilon, f1
复制代码
<ipython-input-92-c5a1372ada03>:9: RuntimeWarning: invalid value encountered in long_scalars
  precision=tp/(tp+fp)





(1.3772288907613575e-18, 0.6153846153846154)
复制代码

查看异常样本的个数: 117 117 ,符合答案要求。

out_index=np.where(p<epsilon)
len(out_index[0])
复制代码
117
复制代码

2 Recommender Systems

在这部分实验中,将实现协同过滤学习算法,并将其应用于电影评级的数据集。数据集由 1 5 1\sim 5 级的评分组成,数据集有 n u = 943 n_u=943 用户和 n m = 1682 n_m=1682 电影。在本实验的下一节中,将实现函数cofiCostFunc:计算协同拟合目标函数和梯度。在实现成本函数和梯度之后,将使用函数fmincg来学习协同过滤的参数。

2.1 Movie ratings dataset

数据集ex8 movies.mat中的变量有YR,其中矩阵Y( n m × n u , y ( i , j ) [ 1 , 5 ] n_m\times n_u,y^{(i,j)}\in [1,5] )是不同电影的不同用户的评分,行数为电影数目,列数为用户数目;矩阵R是二进制矩阵, R ( i , j ) = 1 R(i, j)=1 表示用户 j j 对电影 i i 有评分, R ( i , j ) = 0 R(i, j)=0 表示用户 j j 对电影 i i 没有评分。协同过滤的目标是预测用户对他未评分的电影的评分,也就是 R ( i , j ) = 0 R(i, j) = 0 的项,从而向用户推荐预测收视率最高的电影。

在这节实验中,将使用矩阵XTheta

image-20220330212818805

X i i 行代表第 i i 个电影的特征向量 x ( i ) R 100 x^{(i)}\in R^{100} Theta矩阵的第 j j 列代表第 j j 用户的参数向量 θ ( j ) R 100 \theta^{(j)}\in R^{100} 。因此,矩阵X是一个 n m × 100 n_m\times 100 的矩阵,Theta是一个 n u × 100 n_u\times 100 的矩阵。

data=loadmat("./data/ex8_movies.mat")
data.keys()
复制代码
dict_keys(['__header__', '__version__', '__globals__', 'Y', 'R'])
复制代码
Y,R=data['Y'],data['R']
nm,nu=Y.shape#y(i,j)∈[0,5],0表示未评分
Y.shape,R.shape
复制代码
((1682, 943), (1682, 943))
复制代码

先求第一个电影评分的平均值(有些人没有对这电影评分)。

Y[0].sum()/R[0].sum()
复制代码
3.8783185840707963
复制代码
plt.figure(figsize=(8,8*(1692/943)))
plt.imshow(Y,cmap="rainbow")
plt.colorbar()#显示不同颜色对应的值
plt.xlabel("Users ({})".format(nu))
plt.ylabel("Movies ({})".format(nm))
plt.show()
复制代码


png

2.2 Collaborative filtering learning algorithm(协同过滤学习算法)

现在将开始实现协同过滤学习算法。将从实现成本函数开始(不进行正则化)。在电影推荐中的协同过滤算法考虑了一组 n n 维参数向量 x ( 1 ) , . . . , x ( n m ) x^{(1)},...,x^{(n_m)} θ ( 1 ) , . . . , θ ( n u ) \theta^{(1)},...,\theta^{(n_u)} ,其中模型预测用户 j j 对电影 i i 的评分 y ( i , j ) = ( θ ( j ) ) T x ( i ) y^{(i,j)}=(\theta^{(j)})^Tx^{(i)} 。给定用户对电影的评级的数据集,学习参数向量 x ( 1 ) , . . . , x ( n m ) x^{(1)},...,x^{(n_m)} θ ( 1 ) , . . . , θ ( n u ) \theta^{(1)},...,\theta^{(n_u)} ,得到最好的拟合,使成本函数最小。

编写函数cofiCostFunc:计算协同过滤算法的成本函数和梯度,需要传入的参数为矩阵XTheta。为了使用一个现成的最小化函数,如fmincg,需要将参数XTheta展开成一维向量参数。

data=loadmat("./data/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']
nu,nm,nf=map(int,(nu,nm,nf))
nu,nm,nf
复制代码
(943, 1682, 10)
复制代码

减小数据集的大小,加快算法的速度:

nu,nm,nf=4,5,3
X=X[:nm,:nf]
Theta=Theta[:nu,:nf]
Y=Y[:nm,:nu]
R=R[:nm,:nu]
复制代码
X.shape,Theta.shape
复制代码
((5, 3), (4, 3))
复制代码

2.2.1 Collaborative filtering cost function

协同过滤算法的代价函数(无正则化):

image-20220331083618337

加入正则化的成本函数:协同过滤算法的参数都不需要加偏置项,故都可以正则化。

接下来编写函数cofiCostFunc:返回成本函数变量J。累加用户 j j 和电影 i i 的成本( R ( i , j ) = 1 R(i,j)=1 )。运行该函数,期望答案:不进行正则化的成本函数结果约为 22.22 22.22 ;进行正则化的成本函数结果约为 31.34 31.34

def serialize(X,Theta):#将X和Theta展开成一维
    return np.r_[X.flatten(),Theta.flatten()]
复制代码
def deserialize(seq,nm,nu,nf):
    return seq[:nm*nf].reshape(nm,nf),seq[nm*nf:].reshape(nu,nf)
复制代码
def cofiCostFunc(seq,Y,R,nm,nu,nf,l=0):
    #Y : 评分矩阵 (nm, nu)
    #R :0-1矩阵,用户j对电影i有无评分
    #X:(nm,nf)每行表示电影i的nf个特征
    #Theta:(nu,nf)每行表示用户i的nf个特征
    X,Theta=deserialize(seq, nm, nu, nf)
    error=0.5*np.sum(np.multiply(np.power([email protected],2),R))
    reg1=0.5*l*np.sum(np.power(Theta,2))#加入正则化
    reg2=0.5*l*np.sum(np.power(X,2))
    return error+reg1+reg2

复制代码
seq=serialize(X, Theta)
cofiCostFunc(seq, Y, R, nm, nu, nf),cofiCostFunc(seq, Y, R, nm, nu, nf,1.5)
复制代码
(22.224603725685675, 31.34405624427422)
复制代码

2.2.2 Collaborative filtering gradient

现在实现梯度函数(无正则化),编写函数cofiGradient,返回变量X_grad(规模和X一样)和Theta_grad(规模和Theta一样),将两变量展开为单个向量来返回它们的梯度。编写函数checkCostFunction检查梯度函数,如果函数实现无误,那么分析梯度和数值梯度非常匹配。

image-20220331090359732 image-20220331090413440

加入正则化的梯度函数:

def cofiGradient(seq,Y,R,nm,nu,nf,l=0):
    X,Theta=deserialize(seq, nm, nu, nf)
    X_grad=np.multiply((([email protected])-Y),R)@Theta+l*X
    Theta_grad=np.multiply((([email protected])-Y),R).T@X+l*Theta
    return serialize(X_grad, Theta_grad)
复制代码
def checkCostFunction(seq,Y,R,nm,nu,nf,l=0):
    grad=cofiGradient(seq,Y,R,nm,nu,nf,l)

    e=0.0001#Δ值
    len_seq=len(seq)
    e_vec=np.zeros(len_seq)#向量的渐变值
    for i in range(10):
        idx=np.random.randint(0,len_seq)
        e_vec[idx]=e
        loss1=cofiCostFunc(seq-e_vec,Y,R,nm,nu,nf,l)
        loss2=cofiCostFunc(seq+e_vec,Y,R,nm,nu,nf,l)
        num_grad=(loss2-loss1)/(2*e)
        e_vec[idx]=0
        diff=np.linalg.norm(num_grad-grad[idx])/np.linalg.norm(num_grad+grad[idx])
        print('num_grad:%.15f \t grad[idx]:%.15f \t 差值%.15f' %(num_grad, grad[idx], diff))
复制代码
print("Checking gradient with lambda = 0:")
checkCostFunction(serialize(X,Theta), Y, R, nm, nu, nf)
print("\nChecking gradient with lambda = 1.5:")
checkCostFunction(serialize(X,Theta), Y, R, nm, nu, nf, l=1.5)

复制代码
Checking gradient with lambda = 0:
num_grad:4.742718424690651 	 grad[idx]:4.742718424695921 	 差值0.000000000000556
num_grad:-10.568020204448914 	 grad[idx]:-10.568020204450614 	 差值0.000000000000080
num_grad:-0.803780061460202 	 grad[idx]:-0.803780061452057 	 差值0.000000000005067
num_grad:-0.568195965513496 	 grad[idx]:-0.568195965515757 	 差值0.000000000001990
num_grad:-7.160044429745938 	 grad[idx]:-7.160044429740946 	 差值0.000000000000349
num_grad:7.575703079698570 	 grad[idx]:7.575703079709334 	 差值0.000000000000710
num_grad:-0.383582784628800 	 grad[idx]:-0.383582784622124 	 差值0.000000000008702
num_grad:1.164413669449971 	 grad[idx]:1.164413669446225 	 差值0.000000000001609
num_grad:7.575703079698570 	 grad[idx]:7.575703079709334 	 差值0.000000000000710
num_grad:-0.766778776704058 	 grad[idx]:-0.766778776703673 	 差值0.000000000000251

Checking gradient with lambda = 1.5:
num_grad:0.129856157187191 	 grad[idx]:0.129856157163688 	 差值0.000000000090497
num_grad:0.482440977869203 	 grad[idx]:0.482440977879083 	 差值0.000000000010239
num_grad:1.092897577699148 	 grad[idx]:1.092897577688307 	 差值0.000000000004960
num_grad:1.092897577699148 	 grad[idx]:1.092897577688307 	 差值0.000000000004960
num_grad:-0.892473343601097 	 grad[idx]:-0.892473343597432 	 差值0.000000000002053
num_grad:4.901853273224788 	 grad[idx]:4.901853273231165 	 差值0.000000000000650
num_grad:-0.647874841526175 	 grad[idx]:-0.647874841514519 	 差值0.000000000008996
num_grad:1.092897577699148 	 grad[idx]:1.092897577688307 	 差值0.000000000004960
num_grad:-0.647874841526175 	 grad[idx]:-0.647874841514519 	 差值0.000000000008996
num_grad:2.101362561361952 	 grad[idx]:2.101362561388682 	 差值0.000000000006360
复制代码

2.3 Learning movie recommendations(电影推荐)

完成了协同过滤算法的成本函数和梯度之后,可以开始训练电影推荐算法。先获取所有电影的名称、编号,然后输入用户对电影的偏好,从而获得该用户的电影推荐。

movies=[]#电电影列表
with open("./data/movie_ids.txt","r",encoding='gbk') as f:
    for line in f:
        movie=line.strip().split(' ')#移除字符串头尾空格
        movies.append(' '.join(movie[1:]))
movies=np.array(movies)
复制代码
ratings = np.zeros((1682, 1))
#接用参考博客的数据来设置喜好值
ratings[0] = 4
ratings[6] = 3
ratings[11] = 5
ratings[53] = 4
ratings[63] = 5
ratings[65] = 3
ratings[68] = 5
ratings[97] = 2
ratings[182] = 4
ratings[225] = 5
ratings[354] = 5

ratings.shape
复制代码
(1682, 1)
复制代码

将用户的评分向量添加到现有数据集中,用于模型中。矩阵Y( n m × n u , y ( i , j ) [ 1 , 5 ] n_m\times n_u,y^{(i,j)}\in [1,5] )是不同电影的不同用户的评分,行数为电影数目,列数为用户数目;矩阵R是二进制矩阵, R ( i , j ) = 1 R(i, j)=1 表示用户 j j 对电影 i i 有评分, R ( i , j ) = 0 R(i, j)=0 表示用户 j j 对电影 i i 没有评分。协同过滤的目标是预测用户对他未评分的电影的评分,也就是 R ( i , j ) = 0 R(i, j) = 0 的项,从而向用户推荐预测收视率最高的电影。

data=loadmat("./data/ex8_movies.mat")
Y=data['Y']
R=data['R']
复制代码
Y.shape,R.shape
复制代码
((1682, 943), (1682, 943))
复制代码
Y=np.append(ratings,Y,axis=1)
R=np.append(ratings!=0,R,axis=1)
复制代码
Y.shape,R.shape
复制代码
((1682, 944), (1682, 944))
复制代码

初始化参数矩阵X,Theta

nm,nu=Y.shape
nf=10
l=10
X=np.random.random(size=(nm,nf))
Theta=np.random.random(size=(nu,nf))
seq=serialize(X, Theta)
复制代码
X.shape,Theta.shape,seq.shape
复制代码
((1682, 10), (944, 10), (26260,))
复制代码

标准化数据,这样可以使一个完全没有评分的的特征最后也会获得非零值,因为最后要加回均值。其中只对有评分的数据求均值,没有评分的数据不包含在内,所有需要用R矩阵判断。

def normalizeRatings(Y,R):
    Y_mean=((Y.sum(axis=1))/(R.sum(axis=1))).reshape(-1,1)
    Y_norm=np.multiply(Y-Y_mean,R)
    return Y_norm,Y_mean
复制代码
Y_norm,Y_mean=normalizeRatings(Y,R)
复制代码
Y_norm.shape,Y_mean.shape
复制代码
((1682, 944), (1682, 1))
复制代码

学习模型训练:

import scipy.optimize as opt
res=opt.minimize(fun=cofiCostFunc,x0=seq,args=(Y_norm,R,nm,nu,nf,l),method='TNC',jac=cofiGradient)#运行1min~2min
复制代码
res
复制代码
     fun: 70124.80723141175
     jac: array([ 0.12481091,  3.80257643,  4.25142191, ..., -0.2913018 ,
        0.386691  ,  0.7792457 ])
 message: 'Max. number of function evaluations reached'
    nfev: 100
     nit: 9
  status: 3
 success: False
       x: array([0.73096179, 0.16482085, 0.39889075, ..., 0.30653932, 0.17673335,
       0.97425473])
复制代码

训练好的参数:XTheta,使用这些参数来为用户提供电影推荐。

X_train,Theta_train=deserialize(res.x,nm,nu,nf)
复制代码
X_train.shape,Theta_train.shape
复制代码
((1682, 10), (944, 10))
复制代码

最后,使用训练出的参数模型来推荐电影:

predition=X_train@Theta_train.T
user_predition=predition[:,0]+Y.mean()
idx=np.argsort(-user_predition)#降序排序,得到下标
复制代码
idx.shape
复制代码
(1682,)
复制代码
user_predition[idx][:10]
复制代码
array([4.03635302, 3.96571345, 3.86437942, 3.8627442 , 3.86094835,
       3.85971196, 3.83496685, 3.83174567, 3.71952258, 3.70899912])
复制代码
print("Top recommendations for you:")
for i in range(10):
    print('Predicting rating %.1f for movie %s'%(user_predition[idx[i]],movies[idx[i]]))#5分制

print("\nOriginal ratings provided:")
for i in range(len(ratings)):
    if ratings[i] > 0:
        print('Rated %d for movie %s'% (ratings[i],movies[i]))
复制代码
Top recommendations for you:
Predicting rating 4.0 for movie Star Wars (1977)
Predicting rating 4.0 for movie Raiders of the Lost Ark (1981)
Predicting rating 3.9 for movie Empire Strikes Back, The (1980)
Predicting rating 3.9 for movie Titanic (1997)
Predicting rating 3.9 for movie Good Will Hunting (1997)
Predicting rating 3.9 for movie Shawshank Redemption, The (1994)
Predicting rating 3.8 for movie Braveheart (1995)
Predicting rating 3.8 for movie Return of the Jedi (1983)
Predicting rating 3.7 for movie Usual Suspects, The (1995)
Predicting rating 3.7 for movie Schindler's List (1993)

Original ratings provided:
Rated 4 for movie Toy Story (1995)
Rated 3 for movie Twelve Monkeys (1995)
Rated 5 for movie Usual Suspects, The (1995)
Rated 4 for movie Outbreak (1995)
Rated 5 for movie Shawshank Redemption, The (1994)
Rated 3 for movie While You Were Sleeping (1995)
Rated 5 for movie Forrest Gump (1994)
Rated 2 for movie Silence of the Lambs, The (1991)
Rated 4 for movie Alien (1979)
Rated 5 for movie Die Hard 2 (1990)
Rated 5 for movie Sphere (1998)
复制代码

用非归一化的数据Y重新训练的:

Y_norm = Y - Y.mean()
Y_norm.mean()
复制代码
4.6862111343939375e-17
复制代码
import scipy.optimize as opt
res=opt.minimize(fun=cofiCostFunc,x0=seq,args=(Y_norm,R,nm,nu,nf,l),method='TNC',jac=cofiGradient)#运行了2min38s
复制代码
res
复制代码
     fun: 69380.70267946126
     jac: array([ 3.22931558e-06,  4.74670755e-06,  1.53795013e-06, ...,
       -1.04391592e-06,  1.12803573e-06, -5.47338254e-07])
 message: 'Converged (|f_n-f_(n-1)| ~= 0)'
    nfev: 1430
     nit: 56
  status: 1
 success: True
       x: array([0.75326985, 0.44698399, 0.4576434 , ..., 0.36815454, 0.46085362,
       0.66916822])
复制代码
X_train,Theta_train=deserialize(res.x,nm,nu,nf)
predition=X_train@Theta_train.T
user_predition=predition[:,0]+Y.mean()
idx=np.argsort(-user_predition)#降序排序,得到下标
复制代码
user_predition[idx][:10]
复制代码
array([4.28262534, 4.11498853, 3.98044977, 3.9058028 , 3.88817712,
       3.87460875, 3.87178834, 3.86527196, 3.76237659, 3.75394194])
复制代码
print("Top recommendations for you:")
for i in range(10):
    print('Predicting rating %.1f for movie %s'%(user_predition[idx[i]],movies[idx[i]]))

print("\nOriginal ratings provided:")
for i in range(len(ratings)):
    if ratings[i] > 0:
        print('Rated %d for movie %s'% (ratings[i],movies[i]))
复制代码
Top recommendations for you:
Predicting rating 4.3 for movie Titanic (1997)
Predicting rating 4.1 for movie Star Wars (1977)
Predicting rating 4.0 for movie Raiders of the Lost Ark (1981)
Predicting rating 3.9 for movie Good Will Hunting (1997)
Predicting rating 3.9 for movie Shawshank Redemption, The (1994)
Predicting rating 3.9 for movie Braveheart (1995)
Predicting rating 3.9 for movie Return of the Jedi (1983)
Predicting rating 3.9 for movie Empire Strikes Back, The (1980)
Predicting rating 3.8 for movie Terminator 2: Judgment Day (1991)
Predicting rating 3.8 for movie As Good As It Gets (1997)

Original ratings provided:
Rated 4 for movie Toy Story (1995)
Rated 3 for movie Twelve Monkeys (1995)
Rated 5 for movie Usual Suspects, The (1995)
Rated 4 for movie Outbreak (1995)
Rated 5 for movie Shawshank Redemption, The (1994)
Rated 3 for movie While You Were Sleeping (1995)
Rated 5 for movie Forrest Gump (1994)
Rated 2 for movie Silence of the Lambs, The (1991)
Rated 4 for movie Alien (1979)
Rated 5 for movie Die Hard 2 (1990)
Rated 5 for movie Sphere (1998)
复制代码

猜你喜欢

转载自juejin.im/post/7083798346787192869