吴恩达机器学习作业Python实现(八):异常检测与推荐系统

目录

1 异常检测

1.1 高斯分布

1.2 高斯分布参数估计

1.3 选择阈值 ε

1.4 高维数据集

2 推荐系统

2.1 电影评分数据集

2.2 协同过滤算法

2.2.1 协同过滤代价函数

2.2.2 协同过滤梯度

2.3 学习模型

2.3.1 Recommendations

 3 参考文章


1 异常检测

        在这部分练习中,你将实现一个异常检测算法来检测服务器计算机中的异常行为,这些特征度量着每个服务器的吞吐量和响应延迟,当你的服务器在运行时,收集了 m = 307 个关于它们如何运行的样本,有一个未标记的数据集 {x^{(1)}, x^{(2)}...x^{(m)} },数据集中绝大多数都是正常的样本,但是也存在一些服务器运行异常的样本。

        您将使用高斯模型来检测数据集中的异常数据,首先先从2D数据集开始,以便可以可视化算法。在数据集中,您将拟合高斯分布,然后找到概率很低的值,因此可以被视为异常,之后再将异常检测算法应用到具有更多维度的更大的数据集上,首先我们先对数据进行可视化。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
from jupyterthemes import jtplot  # 用于解决坐标无法显示问题
jtplot.style(theme='chesterish') #选择一个绘图主题

path = r'E:\Code\ML\ml_learning\ex8-anomaly detection and recommendation\data\ex8data1.mat'
data = loadmat(path)
X = data['X']
Xval, yval = data['Xval'], data['yval']
def plotData():
    """数据可视化"""
    plt.figure(figsize=(8,5))
    plt.scatter(X[:,0], X[:,1], c='b', marker='x' )

吴恩达机器学习作业Python实现(八):异常检测与推荐系统_第1张图片

1.1 高斯分布

        要实现异常检测,首先需要将模型拟合数据的分布。

        给定训练集 {x^{(1)}, x^{(2)}...x^{(m)} },我们需要对每个特征x_{i} 做高斯分布估计,对于每个特征,我们都需要对应的参数 \mu_{i} 和 \sigma _{i}^{2}。高斯分布由以下公式给出

p(x;\mu,\sigma^{2}) = \frac{1}{\sqrt{2\pi\sigma^{2}}}e^{-\frac{(x-\mu)^{2}}{2\sigma^2}}

μ是均值,σ是方差

def gaussian(X, mu, sigma2):
    """
    假定传入的sigma2为协方差矩阵
    返回m维向量维为每个样本的概率 
    """
    n = len(mu) #特征数
    # 如果sigma2是向量(n,),则转为对角矩阵
    if np.ndim(sigma2) == 1:
        sigma2 = np.diag(sigma2)
    
    X = X - mu
    # 求Cov的行列式
    # part1 = np.power(2 * np.pi, -n/2)* np.sqrt(np.linalg.det(sigma2))
    part1 = 1 / (np.power(2 * np.pi, n/2) * np.sqrt(np.linalg.det(sigma2)))
    e = np.diag(X @ np.linalg.inv(sigma2) @ X.T)  # 取对角元素,类似与方差,而不要协方差 
    part2 = np.exp(-0.5 * e)
    
    return part1 * part2

1.2 高斯分布参数估计

        第i个特征的参数(\mu_{i}, \sigma_{i}^{2})通过以下公式估计

\mu_{i} = \frac{1}{m}\sum_{j=1}^{m}x_{i}^{(j)}

\sigma^{2}_{i} = \frac{1}{m}\sum_{j=1}{m}(x_{j}^{(j)} - \mu_{i})^{2}

def getGaussianParams(X, useMultivariate):
    """参数估计"""
    mu = X.mean(axis=0)  
    if useMultivariate: 
        # 协方差
        sigma2 = ((X-mu).T @ (X-mu)) / len(X)
    else:
        # ddof = 0时除n
        sigma2 = X.var(axis=0, ddof=0)

    return mu, sigma2

        接下来可视化拟合高斯分布的轮廓。

def plotContours(mu, sigma2):
    """画出高斯概率分布图"""
    x = np.arange(0,30,0.3)
    y = np.arange(0,30,0.3)
    xx, yy = np.meshgrid(x,y)
    # 展开按列排,一列横坐标,一列纵坐标
    points = np.c_[xx.ravel(), yy.ravel()]
    z = gaussian(points, mu, sigma2)
    z = z.reshape(xx.shape)
    
    cont_levels = [10**h for h in range(-20,0,3)]
    plt.contour(xx, yy, z, cont_levels, c='r') #根据概率画出多个轮廓线

    plt.title('Gaussian Contours',fontsize=16)
# 不使用多元高斯
# plotData()
# useMV = False
# plotContours(*getGaussianParams(X, useMV))
plotData()
useMV = True
# *表示解元组
plotContours(*getGaussianParams(X, useMV))

吴恩达机器学习作业Python实现(八):异常检测与推荐系统_第2张图片

 吴恩达机器学习作业Python实现(八):异常检测与推荐系统_第3张图片

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

        除此之外,从上图也可以看出,一元高斯模型仅在横向和纵向上有变化,而多元高斯模型在斜轴上也有相关变化,表征着特征之间的相关关系。而且一元高斯模型其实就是多元高斯的特例,当协方差矩阵为对角矩阵时,一元高斯就是多元高斯。

1.3 选择阈值 ε

        估计了高斯参数后,我们需要确定哪些数据是异常数据,判断是否为异常数据的方法是基于交叉验证集选择一个阈值,阈值选取的好坏将通过交叉验证及上的F1评分来衡量。        

        F1评分由prec和recall组成

F_{1} = \frac{2*prec*rec}{prec+rec}

        prec和rec定义如下

prec = \frac{tp}{tp+fp}

rec = \frac{tp}{tp + fn}

precision 表示预测为positive的样本中有多少是真的

recall 表示实际有多少个positive的样本中,成功预测出多少positive的样本

tp 表示异常值,模型预测为异常值,即真的异常值

fp 表示正常值,模型预测为异常值,即假的异常值

fn 表示异常值,模型预测为正常值,即假的正常值

def selectThreshold(yval,pval):
    def computeF1(yval, pval):
        """F1评估"""
        m = len(yval)
        tp = float(len([i for i in range(m) if pval[i] and yval[i]]))
        fp = float(len([i for i in range(m) if pval[i] and not yval[i]]))
        fn = float(len([i for i in range(m) if not pval[i] and yval[i]]))
        
        prec = tp/(tp+fp) if (tp+fp) else 0
        rec = tp/(tp+fn) if (tp+fn) else 0
        F1 = 2*prec*rec/(prec+rec) if (prec+rec) else 0
        return F1
    
    # 寻找最优参数
    epsilons = np.linspace(min(pval), max(pval), 1000)
    bestF1, bestEpsilon = 0, 0
    for e in epsilons:
        pval1 = pval < e
        thisF1 = computeF1(yval, pval1)
        if thisF1 > bestF1:
            bestF1 = thisF1
            bestEpsilon = e
    return bestF1, bestEpsilon
mu, sigma2 = getGaussianParams(X, 0) # 不使用多元
pval = gaussian(Xval, mu, sigma2)
bestF1, bestEpsilon = selectThreshold(yval,pval)
# bestF1, bestEpsilon
#(0.8750000000000001, 8.999852631901397e-05)

        可知最好的F1评分为0.875,最好的参数为0.99e-05。接下来将全部异常值找出来。

# 找出全部异常数据
y = gaussian(X, mu, sigma2)
xx = np.array([X[i] for i in range(len(yval)) if y[i] < bestEpsilon])
plotData()
plotContours(mu, sigma2)
plt.scatter(xx[:,0], xx[:,1], s=100, edgecolors='g')

吴恩达机器学习作业Python实现(八):异常检测与推荐系统_第4张图片

1.4 高维数据集

path = r'E:\Code\ML\ml_learning\ex8-anomaly detection and recommendation\data\ex8data2.mat'
data = loadmat(path)
X = data['X']
Xval, yval = data['Xval'], data['yval']
ypred = gaussian(X, *getGaussianParams(X, useMultivariate=False))
yvalpred = gaussian(Xval, *getGaussianParams(X, useMultivariate=False))

bestF1, bestEpsilon = selectThreshold(yval, yvalpred)

anoms = [X[i] for i in range(X.shape[0]) if ypred[i] < bestEpsilon]
bestEpsilon, len(anoms)
(1.3786074982000245e-18, 117)

        运行完代码应该看到值 ε 约为1.38e-18,发现117个异常。

2 推荐系统

        在本部分练习中,将实现协同过滤学习算法,并将用于电影评分数据集,该数据集有nu=943个用户,nm=1682部电影,在这部分里面实际上还是用到SVD矩阵分解算法(依旧是懵懵懂懂)

2.1 电影评分数据集

        矩阵Y(nm, nu)是不同电影的不同用户的评分,行数为电影数目,列数为用户数目。

        矩阵R是二进制指示矩阵,R(i,j) = 1代表第j个用户对第i个电影有评分,反之亦然。

        协同过滤的目标是预测用户还没有评分的电影的评分,也就是R(i,j) = 0项。

X的第i行代表第i个电影的特征向量,theta矩阵的第j列代表第j个用户的参数向量。练习里我们取n=100(降维处理),所以X(nm, 100) ,theta(nu,100).

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat
import scipy.optimize as opt
from jupyterthemes import jtplot  # 用于解决坐标无法显示问题
jtplot.style(theme='chesterish') #选择一个绘图主题
path = r'E:\Code\ML\ml_learning\ex8-anomaly detection and recommendation\data\ex8_movies.mat'
data = loadmat(path)
Y, R = data['Y'], data['R']
nm, nu = Y.shape # 电影数 用户数
nf = 100 # 取前100个特征
# 可视化
fig = plt.figure(figsize=(8,8*(1682./943.)))
plt.imshow(Y, cmap='rainbow')
plt.colorbar()
plt.ylabel('Movies (%d)'%nm,fontsize=20)
plt.xlabel('Users (%d)'%nu,fontsize=20)

吴恩达机器学习作业Python实现(八):异常检测与推荐系统_第5张图片

2.2 协同过滤算法

        这部分实现协同过滤算法。

        这里我们考虑每一个参数X和θ都是n维向量,用户j对电影i的评分为y^{(i,j)} = (\theta^{(j)})^{T}x{(i)},给定一个评分数据集,然后学习出X和θ,使代价函数最小。

        为了使用高级算法,我们需要先将X和θ展平连接成一维数组传入,在函数里再分解。

path = r'E:\Code\ML\ml_learning\ex8-anomaly detection and recommendation\data\ex8_movieParams.mat'
data = loadmat(path)
X = data['X']
theta = data['Theta']
nu = int(data['num_users'])
nm = int(data['num_movies'])
nf = int(data['num_features'])
nu = 4; nm = 5; nf = 3
X = X[:nm,:nf]
theta = theta[:nu,:nf]
Y = Y[:nm,:nu]
R = R[:nm,:nu]

2.2.1 协同过滤代价函数

        协同过滤代价函数公式为

         参数展开与合成

def serialize(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 cost(params, Y, R, nm, nu, nf, l=0):
    """
    params : 拉成一维之后的参数向量(X, Theta)
    Y : 评分矩阵 (nm, nu)
    R :0-1矩阵,表示用户对某一电影有无评分
    nu : 用户数量
    nm : 电影数量
    nf : 自定义的特征的维度
    l : 正则化参数
    """
    # 恢复维度
    X, Theta = deserialize(params, nm, nu, nf)
    # np.square求元素平方,先通过[email protected]获取所算出来的评分
    # 由于有些电影没有评分,有些有,而这些记录在R上,故*R并元素平方再求和
    error = 0.5*np.square(([email protected] -Y)*R).sum()
    reg1 = 0.5*l*np.square(theta).sum()
    reg2 = 0.5*l*np.square(X).sum()
    return error + reg1 + reg2
cost(serialize(X,theta),Y,R,nm,nu,nf)
# (22.224603725685675, 31.34405624427422)

2.2.2 协同过滤梯度

        公式如下

def gradient(params, Y, R, nm, nu, nf, l=0):
    X, Theta = deserialize(params, nm, nu, nf)
    # 注意维度
    # X(nm,nf), theta(nu,nf), Y(nm,nu), R(nm,nu)
    X_grad = (([email protected])*R)@Theta + l*X
    theta_grad = (([email protected])*R).T@X +l*theta
    return serialize(X_grad,theta_grad)

        梯度检测

def chickgradint(params, Y, R, nm, nu, nf, l=0):
    # 获得梯度
    grad = gradient(params,Y,R,nm,nu,nf,l)
    e = 0.0001
    nparams = len(params)
    e_vec = np.zeros(nparams)
    for i in range(10):
        idx = np.random.randint(0,nparams)
        e_vec[idx] = e
        loss1 = cost(params-e_vec,Y,R,nm,nu,nf,l)
        loss2 = cost(params+e_vec,Y,R,nm,nu,nf,l)
        numgrad = (loss2 - loss1) / (2*e)
        e_vec[idx] = 0
        diff = np.linalg.norm(numgrad - grad[idx]) / np.linalg.norm(numgrad + grad[idx])
        print('%0.15f \t %0.15f \t %0.15f' %(numgrad, grad[idx], diff))

2.3 学习模型

        在学习模型之前,我们先得把所有电影的名称以及添加与我们刚刚观察到的新用户对应的评分。

movies = []
# 写入电影名字
with open(path,'r', encoding='utf-8') as f:
    for line in f:
        # 去除前面的序号和后面跟着的空格
        tokens = line.strip().split(' ')[1:]
        movies.append(' '.join(tokens))
# 添加一个新的观众的评分数据
my_ratings = np.zeros((1682,1))

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
for i in range(len(my_ratings)):
    if my_ratings[i] > 0:
        print(my_ratings[i], movies[i])

        导入Y,R

path = r'E:\Code\ML\ml_learning\ex8-anomaly detection and recommendation\data\ex8_movies.mat'
mat = loadmat(path)
Y, R = mat['Y'], mat['R']
Y.shape, R.shape

        添加新用户的信息 

Y = np.c_[Y, my_ratings]  # (1682, 944)
R = np.c_[R, my_ratings!=0]  # (1682, 944)
nm, nu = Y.shape
nf = 10 # 我们使用10维的特征向量

        归一化数据可以使一个完全没有评分的的特征最后也会获得非零值。但是预测评分最后要加回均值,注意只对有评分的数据求均值,没有评分的数据不包含在内,要用R矩阵判断。

def normalizeRatings(Y,R):
    """均值归一化"""
    # 注意不需要归一化未评分的数据
    Ymean = (Y.sum(axis=1) / R.sum(axis=1)).reshape(-1,1)
    Ynorm = (Y - Ymean)*R
    return Ymean, Ynorm
Ymean, Ynorm= normalizeRatings(Y, R)

        生成初始化参数X,θ,训练模型

X = np.random.random((nm, nf))
theta = np.random.random((nu, nf))
params = serialize(X, theta)
l = 10
# 优化参数
res = opt.minimize(fun=cost,
                   x0=params,
                   args=(Ynorm, R, nm, nu, nf, l),
                   method='TNC',
                   jac=gradient,
                       options={'maxiter': 100})
# 优化后的参数
fit_X, fit_Theta = deserialize(res.x, nm, nu, nf)

2.3.1 Recommendations

        训练好模型之后就可以预测用户分数。

# 所有用户的分数矩阵
pred_mat = fit_X @ fit_Theta.T
# 最后一名用户的分数,记得加上均值
pred = pred_mat[:,-1] + Ymean.flatten()
# 对评分进行排序
pred_sorted_idx = np.argsort(pred)[::-1]
#输出推荐结果
print("Top recommendations for you:")
for i in range(10):
    print("Predicitng rating %0.1f for movie %s." \
          %(pred[pred_sorted_idx[i]], movies[pred_sorted_idx[i]]))
    
print("\nOriginal ratings provided:")
for i in range(len(my_ratings)):
    if my_ratings[i] > 0:
        print('Rated %d for movie %s.'% (my_ratings[i],movies[i]))
Top recommendations for you:
Predicitng rating 7.2 for movie Someone Else's America (1995).
Predicitng rating 7.0 for movie Santa with Muscles (1996).
Predicitng rating 6.9 for movie Star Kid (1997).
Predicitng rating 6.9 for movie Marlene Dietrich: Shadow and Light (1996).
Predicitng rating 6.6 for movie Great Day in Harlem, A (1994).
Predicitng rating 6.5 for movie Entertaining Angels: The Dorothy Day Story (1996).
Predicitng rating 6.5 for movie Nightwatch (1997).
Predicitng rating 6.5 for movie Pather Panchali (1955).
Predicitng rating 6.5 for movie Anna (1996).
Predicitng rating 6.5 for movie They Made Me a Criminal (1939).

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).

 3 参考文章

吴恩达机器学习与深度学习作业目录 [图片已修复]

你可能感兴趣的:(机器学习,推荐算法,python,机器学习,人工智能)