经典算法笔记:异常检测和推荐系统

本文是吴恩达老师的机器学习课程[1]的笔记和代码复现部分(聚类、降维)。

作者:黄海广[2]

备注:笔记和作业(含数据、原始作业文件)、视频都在github[3]中下载。

我将陆续将课程笔记和课程代码发布在公众号“机器学习初学者”,敬请关注。这个是第五部分:异常检测和推荐系统,是原教程的第9周,包含了笔记和作业代码(原课程作业是 OCTAVE的,这里是复现 python 代码)

已完成部分:

第一部分:回归

第二部分:逻辑回归

第三部分:支持向量机

第四部分:无监督学习

本文作业代码[4]可以下载完整版

笔记的markdown 文件[5]

笔记的pdf 文件[6]

笔记部分-目录

十五、异常检测(Anomaly Detection)

15.1 问题的动机

参考文档: 15 - 1 - Problem Motivation (8 min).mkv

在接下来的一系列视频中,我将向大家介绍异常检测(Anomaly detection)问题。这是机器学习算法的一个常见应用。这种算法的一个有趣之处在于:它虽然主要用于非监督学习问题,但从某些角度看,它又类似于一些监督学习问题。

什么是异常检测呢?为了解释这个概念,让我举一个例子吧:

假想你是一个飞机引擎制造商,当你生产的飞机引擎从生产线上流出时,你需要进行QA(质量控制测试),而作为这个测试的一部分,你测量了飞机引擎的一些特征变量,比如引擎运转时产生的热量,或者引擎的振动等等。

这样一来,你就有了一个数据集,从 ,如果你生产了 个引擎的话,你将这些数据绘制成图表,看起来就是这个样子:

这里的每个点、每个叉,都是你的无标签数据。这样,异常检测问题可以定义如下:我们假设后来有一天,你有一个新的飞机引擎从生产线上流出,而你的新飞机引擎有特征变量 。所谓的异常检测问题就是:我们希望知道这个新的飞机引擎是否有某种异常,或者说,我们希望判断这个引擎是否需要进一步测试。因为,如果它看起来像一个正常的引擎,那么我们可以直接将它运送到客户那里,而不需要进一步的测试。

给定数据集  ,我们假使数据集是正常的,我们希望知道新的数据   是不是异常的,即这个测试数据不属于该组数据的几率如何。我们所构建的模型应该能根据该测试数据的位置告诉我们其属于一组数据的可能性 

上图中,在蓝色圈内的数据属于该组数据的可能性较高,而越是偏远的数据,其属于该组数据的可能性就越低。

这种方法称为密度估计,表达如下:

欺诈检测:

用户的第 个活动特征

模型  为我们其属于一组数据的可能性,通过 检测非正常用户。

异常检测主要用来识别欺骗。例如在线采集而来的有关用户的数据,一个特征向量中可能会包含如:用户多久登录一次,访问过的页面,在论坛发布的帖子数量,甚至是打字速度等。尝试根据这些特征构建一个模型,可以用这个模型来识别那些不符合该模式的用户。

再一个例子是检测一个数据中心,特征可能包含:内存使用情况,被访问的磁盘数量,CPU的负载,网络的通信量等。根据这些特征可以构建一个模型,用来判断某些计算机是不是有可能出错了。

15.2 高斯分布

参考视频: 15 - 2 - Gaussian Distribution (10 min).mkv

在这个视频中,我将介绍高斯分布,也称为正态分布。回顾高斯分布的基本知识。

通常如果我们认为变量   符合高斯分布  则其概率密度函数为:  

我们可以利用已有的数据来预测总体中的 μ σ 的计算方法如下: 

高斯分布样例:

注:机器学习中对于方差我们通常只除以 而非统计学中的 。这里顺便提一下,在实际使用中,到底是选择使用 还是 其实区别很小,只要你有一个还算大的训练集,在机器学习领域大部分人更习惯使用 这个版本的公式。这两个版本的公式在理论特性和数学特性上稍有不同,但是在实际使用中,他们的区别甚小,几乎可以忽略不计。

15.3 算法

参考视频: 15 - 3 - Algorithm (12 min).mkv

在本节视频中,我将应用高斯分布开发异常检测算法。

异常检测算法:

对于给定的数据集  ,我们要针对每一个特征计算   和   的估计值。

一旦我们获得了平均值和方差的估计值,给定新的一个训练实例,根据模型计算 

时,为异常。

下图是一个由两个特征的训练集,以及特征的分布情况:

下面的三维图表表示的是密度估计函数, 轴为根据两个特征的值所估计 值:

我们选择一个 ,将 作为我们的判定边界,当 时预测数据为正常数据,否则为异常。

在这段视频中,我们介绍了如何拟合 ,也就是  的概率值,以开发出一种异常检测算法。同时,在这节课中,我们也给出了通过给出的数据集拟合参数,进行参数估计,得到参数   和  ,然后检测新的样本,确定新样本是否是异常。

在接下来的课程中,我们将深入研究这一算法,同时更深入地介绍,怎样让算法工作地更加有效。

15.4 开发和评价一个异常检测系统

参考视频: 15 - 4 - Developing and Evaluating an Anomaly Detection System (13 min). mkv

异常检测算法是一个非监督学习算法,意味着我们无法根据结果变量   的值来告诉我们数据是否真的是异常的。我们需要另一种方法来帮助检验算法是否有效。当我们开发一个异常检测系统时,我们从带标记(异常或正常)的数据着手,我们从其中选择一部分正常数据用于构建训练集,然后用剩下的正常数据和异常数据混合的数据构成交叉检验集和测试集。

例如:我们有 10000 台正常引擎的数据,有 20 台异常引擎的数据。 我们这样分配数据:

6000 台正常引擎的数据作为训练集

2000 台正常引擎和 10 台异常引擎的数据作为交叉检验集

2000 台正常引擎和 10 台异常引擎的数据作为测试集

具体的评价方法如下:

  1. 根据测试集数据,我们估计特征的平均值和方差并构建 函数

  2. 对交叉检验集,我们尝试使用不同的 值作为阀值,并预测数据是否异常,根据 值或者查准率与查全率的比例来选择 

  3. 选出   后,针对测试集进行预测,计算异常检验系统的 值,或者查准率与查全率之比

15.5 异常检测与监督学习对比

参考视频: 15 - 5 - Anomaly Detection vs. Supervised Learning (8 min).mkv

之前我们构建的异常检测系统也使用了带标记的数据,与监督学习有些相似,下面的对比有助于选择采用监督学习还是异常检测:

两者比较:

异常检测监督学习
非常少量的正向类(异常数据  ), 大量的负向类( 同时有大量的正向类和负向类
许多不同种类的异常,非常难。根据非常 少量的正向类数据来训练算法。有足够多的正向类实例,足够用于训练 算法,未来遇到的正向类实例可能与训练集中的非常近似。
未来遇到的异常可能与已掌握的异常、非常的不同。
例如: 欺诈行为检测 生产(例如飞机引擎)检测数据中心的计算机运行状况例如:邮件过滤器 天气预报 肿瘤分类

希望这节课能让你明白一个学习问题的什么样的特征,能让你把这个问题当做是一个异常检测,或者是一个监督学习的问题。另外,对于很多技术公司可能会遇到的一些问题,通常来说,正样本的数量很少,甚至有时候是 0,也就是说,出现了太多没见过的不同的异常类型,那么对于这些问题,通常应该使用的算法就是异常检测算法。

15.6 选择特征

参考视频: 15 - 6 - Choosing What Features to Use (12 min).mkv

对于异常检测算法,我们使用的特征是至关重要的,下面谈谈如何选择特征:

异常检测假设特征符合高斯分布,如果数据的分布不是高斯分布,异常检测算法也能够工作,但是最好还是将数据转换成高斯分布,例如使用对数函数: ,其中   为非负常数; 或者  为 0-1 之间的一个分数,等方法。(编者注:在python中,通常用np.log1p()函数, 就是  ,可以避免出现负数结果,反向函数就是np.expm1())

误差分析:

一个常见的问题是一些异常的数据可能也会有较高的 值,因而被算法认为是正常的。这种情况下误差分析能够帮助我们,我们可以分析那些被算法错误预测为正常的数据,观察能否找出一些问题。我们可能能从问题中发现我们需要增加一些新的特征,增加这些新特征后获得的新算法能够帮助我们更好地进行异常检测。

异常检测误差分析:

我们通常可以通过将一些相关的特征进行组合,来获得一些新的更好的特征(异常数据的该特征值异常地大或小),例如,在检测数据中心的计算机状况的例子中,我们可以用CPU负载与网络通信量的比例作为一个新的特征,如果该值异常地大,便有可能意味着该服务器是陷入了一些问题中。

在这段视频中,我们介绍了如何选择特征,以及对特征进行一些小小的转换,让数据更像正态分布,然后再把数据输入异常检测算法。同时也介绍了建立特征时,进行的误差分析方法,来捕捉各种异常的可能。希望你通过这些方法,能够了解如何选择好的特征变量,从而帮助你的异常检测算法,捕捉到各种不同的异常情况。

15.7 多元高斯分布(选修)

参考视频: 15 - 7 - Multivariate Gaussian Distribution (Optional) (14 min).mkv

假使我们有两个相关的特征,而且这两个特征的值域范围比较宽,这种情况下,一般的高斯分布模型可能不能很好地识别异常数据。其原因在于,一般的高斯分布模型尝试的是去同时抓住两个特征的偏差,因此创造出一个比较大的判定边界。

下图中是两个相关特征,洋红色的线(根据 ε 的不同其范围可大可小)是一般的高斯分布模型获得的判定边界,很明显绿色的X所代表的数据点很可能是异常值,但是其 值却仍然在正常范围内。多元高斯分布将创建像图中蓝色曲线所示的判定边界。

在一般的高斯分布模型中,我们计算   的方法是: 通过分别计算每个特征对应的几率然后将其累乘起来,在多元高斯分布模型中,我们将构建特征的协方差矩阵,用所有的特征一起来计算 

我们首先计算所有特征的平均值,然后再计算协方差矩阵: 

注:其中  是一个向量,其每一个单元都是原特征矩阵中一行数据的均值。最后我们计算多元高斯分布的 :  其中:

是定矩阵,在 Octave 中用 det(sigma)计算

 是逆矩阵,下面我们来看看协方差矩阵是如何影响模型的:

上图是 5 个不同的模型,从左往右依次分析:

  1. 是一个一般的高斯分布模型

  2. 通过协方差矩阵,令特征 1 拥有较小的偏差,同时保持特征 2 的偏差

  3. 通过协方差矩阵,令特征 2 拥有较大的偏差,同时保持特征 1 的偏差

  4. 通过协方差矩阵,在不改变两个特征的原有偏差的基础上,增加两者之间的正相关性

  5. 通过协方差矩阵,在不改变两个特征的原有偏差的基础上,增加两者之间的负相关性

多元高斯分布模型与原高斯分布模型的关系:

可以证明的是,原本的高斯分布模型是多元高斯分布模型的一个子集,即像上图中的第 1、2、3,3 个例子所示,如果协方差矩阵只在对角线的单位上有非零的值时,即为原本的高斯分布模型了。

原高斯分布模型和多元高斯分布模型的比较:

原高斯分布模型多元高斯分布模型
不能捕捉特征之间的相关性 但可以通过将特征进行组合的方法来解决自动捕捉特征之间的相关性
计算代价低,能适应大规模的特征计算代价较高 训练集较小时也同样适用

必须要有  ,不然的话协方差矩阵 不可逆的,通常需要   另外特征冗余也会导致协方差矩阵不可逆

原高斯分布模型被广泛使用着,如果特征之间在某种程度上存在相互关联的情况,我们可以通过构造新新特征的方法来捕捉这些相关性。

如果训练集不是太大,并且没有太多的特征,我们可以使用多元高斯分布模型。

15.8 使用多元高斯分布进行异常检测(可选)

参考视频: 15 - 8 - Anomaly Detection using the Multivariate Gaussian Distribution (Optional) (14 min).mkv

在我们谈到的最后一个视频,关于多元高斯分布,看到的一些建立的各种分布模型,当你改变参数,  和  。在这段视频中,让我们用这些想法,并应用它们制定一个不同的异常检测算法。

要回顾一下多元高斯分布和多元正态分布:

分布有两个参数,   和  。其中 这一个 维向量和   的协方差矩阵,是一种 的矩阵。而这里的公式 的概率,如按   和参数化  ,和你的变量   和  ,你可以得到一个范围的不同分布一样,你知道的,这些都是三个样本,那些我们在以前的视频看过了。

因此,让我们谈谈参数拟合或参数估计问题:

我有一组样本 是一个 维向量,我想我的样本来自一个多元高斯分布。我如何尝试估计我的参数   和   以及标准公式?

估计他们是你设置   是你的训练样本的平均值。

 并设置 :  这其实只是当我们使用PCA算法时候,有   时写出来。所以你只需插入上述两个公式,这会给你你估计的参数   和你估计的参数  。所以,这里给出的数据集是你如何估计   和  。让我们以这种方法而只需将其插入到异常检测算法。那么,我们如何把所有这一切共同开发一个异常检测算法?

首先,我们把我们的训练集,和我们的拟合模型,我们计算 ,要知道,设定 和描述的一样

如图,该分布在中央最多,越到外面的圈的范围越小。

并在该点是出路这里的概率非常低。

原始模型与多元高斯模型的关系如图:

其中:协方差矩阵 为:

原始模型和多元高斯分布比较如图:

十六、推荐系统(Recommender Systems)

16.1 问题形式化

参考视频: 16 - 1 - Problem Formulation (8 min).mkv

在接下来的视频中,我想讲一下推荐系统。我想讲推荐系统有两个原因:

第一、仅仅因为它是机器学习中的一个重要的应用。在过去几年,我偶尔访问硅谷不同的技术公司,我常和工作在这儿致力于机器学习应用的人们聊天,我常问他们,最重要的机器学习的应用是什么,或者,你最想改进的机器学习应用有哪些。我最常听到的答案是推荐系统。现在,在硅谷有很多团体试图建立很好的推荐系统。因此,如果你考虑网站像亚马逊,或网飞公司或易趣,或iTunes Genius,有很多的网站或系统试图推荐新产品给用户。如,亚马逊推荐新书给你,网飞公司试图推荐新电影给你,等等。这些推荐系统,根据浏览你过去买过什么书,或过去评价过什么电影来判断。这些系统会带来很大一部分收入,比如为亚马逊和像网飞这样的公司。因此,对推荐系统性能的改善,将对这些企业的有实质性和直接的影响。

推荐系统是个有趣的问题,在学术机器学习中因此,我们可以去参加一个学术机器学习会议,推荐系统问题实际上受到很少的关注,或者,至少在学术界它占了很小的份额。但是,如果你看正在发生的事情,许多有能力构建这些系统的科技企业,他们似乎在很多企业中占据很高的优先级。这是我为什么在这节课讨论它的原因之一。

我想讨论推荐系统地第二个原因是:这个班视频的最后几集我想讨论机器学习中的一些大思想,并和大家分享。这节课我们也看到了,对机器学习来说,特征是很重要的,你所选择的特征,将对你学习算法的性能有很大的影响。因此,在机器学习中有一种大思想,它针对一些问题,可能并不是所有的问题,而是一些问题,有算法可以为你自动学习一套好的特征。因此,不要试图手动设计,而手写代码这是目前为止我们常干的。有一些设置,你可以有一个算法,仅仅学习其使用的特征,推荐系统就是类型设置的一个例子。还有很多其它的,但是通过推荐系统,我们将领略一小部分特征学习的思想,至少,你将能够了解到这方面的一个例子,我认为,机器学习中的大思想也是这样。因此,让我们开始讨论推荐系统问题形式化。

我们从一个例子开始定义推荐系统的问题。

假使我们是一个电影供应商,我们有 5 部电影和 4 个用户,我们要求用户为电影打分。

前三部电影是爱情片,后两部则是动作片,我们可以看出AliceBob似乎更倾向与爱情片, 而 CarolDave 似乎更倾向与动作片。并且没有一个用户给所有的电影都打过分。我们希望构建一个算法来预测他们每个人可能会给他们没看过的电影打多少分,并以此作为推荐的依据。

下面引入一些标记:

 代表用户的数量

 代表电影的数量

 如果用户 j 给电影   评过分则 

 代表用户   给电影 的评分

代表用户   评过分的电影的总数

16.2 基于内容的推荐系统

参考视频: 16 - 2 - Content Based Recommendations (15 min).mkv

在一个基于内容的推荐系统算法中,我们假设对于我们希望推荐的东西有一些数据,这些数据是有关这些东西的特征。

在我们的例子中,我们可以假设每部电影都有两个特征,如 代表电影的浪漫程度,  代表电影的动作程度。

则每部电影都有一个特征向量,如 是第一部电影的特征向量为[0.9 0]。

下面我们要基于这些特征来构建一个推荐系统算法。 假设我们采用线性回归模型,我们可以针对每一个用户都训练一个线性回归模型,如 是第一个用户的模型的参数。 于是,我们有:

用户   的参数向量

电影   的特征向量

对于用户   和电影  ,我们预测评分为:

代价函数

针对用户  ,该线性回归模型的代价为预测误差的平方和,加上正则化项:

其中  表示我们只计算那些用户   评过分的电影。在一般的线性回归模型中,误差项和正则项应该都是乘以 ,在这里我们将 去掉。并且我们不对方差项 进行正则化处理。

上面的代价函数只是针对一个用户的,为了学习所有用户,我们将所有用户的代价函数求和:

如果我们要用梯度下降法来求解最优解,我们计算代价函数的偏导数后得到梯度下降的更新公式为:

16.3 协同过滤

参考视频: 16 - 3 - Collaborative Filtering (10 min).mkv

在之前的基于内容的推荐系统中,对于每一部电影,我们都掌握了可用的特征,使用这些特征训练出了每一个用户的参数。相反地,如果我们拥有用户的参数,我们可以学习得出电影的特征。

但是如果我们既没有用户的参数,也没有电影的特征,这两种方法都不可行了。协同过滤算法可以同时学习这两者。

我们的优化目标便改为同时针对 进行。

对代价函数求偏导数的结果如下:

注:在协同过滤从算法中,我们通常不使用方差项,如果需要的话,算法会自动学得。 协同过滤算法使用步骤如下:

  1. 初始 为一些随机小值

  2. 使用梯度下降算法最小化代价函数

  3. 在训练完算法后,我们预测 为用户   给电影   的评分

通过这个学习过程获得的特征矩阵包含了有关电影的重要数据,这些数据不总是人能读懂的,但是我们可以用这些数据作为给用户推荐电影的依据。

例如,如果一位用户正在观看电影  ,我们可以寻找另一部电影 ,依据两部电影的特征向量之间的距离 的大小。

16.4 协同过滤算法

参考视频: 16 - 4 - Collaborative Filtering Algorithm (9 min).mkv

协同过滤优化目标:

给定 ,估计

给定 ,估计

同时最小化

16.5 向量化:低秩矩阵分解

参考视频: 16 - 5 - Vectorization_ Low Rank Matrix Factorization (8 min).mkv

在上几节视频中,我们谈到了协同过滤算法,本节视频中我将会讲到有关该算法的向量化实现,以及说说有关该算法你可以做的其他事情。

举例子:

  1. 当给出一件产品时,你能否找到与之相关的其它产品。

  2. 一位用户最近看上一件产品,有没有其它相关的产品,你可以推荐给他。

我将要做的是:实现一种选择的方法,写出协同过滤算法的预测情况。

我们有关于五部电影的数据集,我将要做的是,将这些用户的电影评分,进行分组并存到一个矩阵中。

我们有五部电影,以及四位用户,那么 这个矩阵   就是一个 5 行 4 列的矩阵,它将这些电影的用户评分数据都存在矩阵里:

MovieAlice (1)Bob (2)Carol (3)Dave (4)
Love at last5500
Romance forever5??0
Cute puppies of love?40?
Nonstop car chases0054
Swords vs. karate005?

推出评分:

找到相关影片:

现在既然你已经对特征参数向量进行了学习,那么我们就会有一个很方便的方法来度量两部电影之间的相似性。例如说:电影   有一个特征向量 ,你是否能找到一部不同的电影  ,保证两部电影的特征向量之间的距离 很小,那就能很有力地表明电影 和电影   在某种程度上有相似,至少在某种意义上,某些人喜欢电影  ,或许更有可能也对电影   感兴趣。总结一下,当用户在看某部电影   的时候,如果你想找 5 部与电影非常相似的电影,为了能给用户推荐 5 部新电影,你需要做的是找出电影  ,在这些不同的电影中与我们要找的电影   的距离最小,这样你就能给你的用户推荐几部不同的电影了。

通过这个方法,希望你能知道,如何进行一个向量化的计算来对所有的用户和所有的电影进行评分计算。同时希望你也能掌握,通过学习特征参数,来找到相关电影和产品的方法。

16.6 推行工作上的细节:均值归一化

参考视频: 16 - 6 - Implementational Detail_ Mean Normalization (9 min).mkv

让我们来看下面的用户评分数据:

如果我们新增一个用户 Eve,并且 Eve 没有为任何电影评分,那么我们以什么为依据为Eve推荐电影呢?

我们首先需要对结果  矩阵进行均值归一化处理,将每一个用户对某一部电影的评分减去所有用户对该电影评分的平均值:

然后我们利用这个新的   矩阵来训练算法。 如果我们要用新训练出的算法来预测评分,则需要将平均值重新加回去,预测 ,对于Eve,我们的新模型会认为她给每部电影的评分都是该电影的平均分。

代码部分- 异常检测和推荐系统

在本练习中,我们将使用高斯模型实现异常检测算法,并将其应用于检测网络上的故障服务器。 我们还将看到如何使用协作过滤构建推荐系统,并将其应用于电影推荐数据集。

Anomaly detection(异常检测)

我们的第一个任务是使用高斯模型来检测数据集中未标记的示例是否应被视为异常。 我们有一个简单的二维数据集开始,以帮助可视化该算法正在做什么。

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sb
from scipy.io import loadmat
data = loadmat('data/ex8data1.mat')
X = data['X']
X.shape
(307, 2)
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X[:,0], X[:,1])
plt.show()

这是一个非常紧密的聚类,几个值远离了聚类。 在这个简单的例子中,这些可以被认为是异常的。 为了弄清楚,我们正在为数据中的每个特征估计高斯分布。 为此,我们将创建一个返回每个要素的均值和方差的函数。

def estimate_gaussian(X):
    mu = X.mean(axis=0)
    sigma = X.var(axis=0)


    return mu, sigma
mu, sigma = estimate_gaussian(X)
mu, sigma
(array([14.11222578, 14.99771051]), array([1.83263141, 1.70974533]))

现在我们有了我们的模型参数,我们需要确定概率阈值,这表明一个样本应该被认为是一个异常。 为此,我们需要使用一组标记的验证数据(其中真实异常样本已被标记),并在给出不同阈值的情况下,对模型的性能进行鉴定。

Xval = data['Xval']
yval = data['yval']


Xval.shape, yval.shape
((307, 2), (307, 1))

我们还需要一种计算数据点属于正态分布的概率的方法。 幸运的是 SciPy 有这个内置的方法。

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

我们还可以将数组传递给概率密度函数,并获得数据集中每个点的概率密度。

dist.pdf(X[:,0])[0:50]
array([0.183842  , 0.20221694, 0.21746136, 0.19778763, 0.20858956,
       0.21652359, 0.16991291, 0.15123542, 0.1163989 , 0.1594734 ,
       0.21716057, 0.21760472, 0.20141857, 0.20157497, 0.21711385,
       0.21758775, 0.21695576, 0.2138258 , 0.21057069, 0.1173018 ,
       0.20765108, 0.21717452, 0.19510663, 0.21702152, 0.17429399,
       0.15413455, 0.21000109, 0.20223586, 0.21031898, 0.21313426,
       0.16158946, 0.2170794 , 0.17825767, 0.17414633, 0.1264951 ,
       0.19723662, 0.14538809, 0.21766361, 0.21191386, 0.21729442,
       0.21238912, 0.18799417, 0.21259798, 0.21752767, 0.20616968,
       0.21520366, 0.1280081 , 0.21768113, 0.21539967, 0.16913173])

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

p = np.zeros((X.shape[0], X.shape[1]))
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[0], Xval.shape[1]))
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)

接下来,我们需要一个函数,找到给定概率密度值和真实标签的最佳阈值。 为了做到这一点,我们将为不同的 epsilon 值计算 F1 分数。 F1 是真阳性,假阳性和假阴性的数量的函数。 方程式在练习文本中。

def select_threshold(pval, yval):
    best_epsilon = 0
    best_f1 = 0
    f1 = 0


    step = (pval.max() - pval.min()) / 1000


    for epsilon in np.arange(pval.min(), pval.max(), step):
        preds = pval < epsilon
        tp = np.sum(np.logical_and(preds == 1, yval == 1)).astype(float)
        fp = np.sum(np.logical_and(preds == 1, yval == 0)).astype(float)
        fn = np.sum(np.logical_and(preds == 0, yval == 1)).astype(float)


        precision = tp / (tp + fp)
        recall = tp / (tp + fn)
        f1 = (2 * precision * recall) / (precision + recall)


        if f1 > best_f1:
            best_f1 = f1
            best_epsilon = epsilon


    return best_epsilon, best_f1
epsilon, f1 = select_threshold(pval, yval)
epsilon, f1
(0.009566706005956842, 0.7142857142857143)

最后,我们可以将阈值应用于数据集,并可视化结果。

# indexes of the values considered to be outliers
outliers = np.where(p < epsilon)
outliers
(array([300, 301, 301, 303, 303, 304, 306, 306], dtype=int64),
 array([1, 0, 1, 0, 1, 0, 0, 1], dtype=int64))
fig, ax = plt.subplots(figsize=(12,8))
ax.scatter(X[:,0], X[:,1])
ax.scatter(X[outliers[0],0], X[outliers[0],1], s=50, color='r', marker='o')
plt.show()

红点是被标记为异常值的点。 这些看起来很合理。 有一些分离(但没有被标记)的右上角也可能是一个异常值,但是相当接近。

协同过滤

推荐引擎使用基于项目和用户的相似性度量来检查用户的历史偏好,以便为用户可能感兴趣的新“事物”提供建议。在本练习中,我们将实现一种称为协作过滤的特定推荐系统算法,并将其应用于 电影评分的数据集。

我们首先加载并检查我们将要使用的数据。

data = loadmat('data/ex8_movies.mat')

Y 是包含从 1 到 5 的等级的(数量的电影 x 数量的用户)数组.R 是包含指示用户是否给电影评分的二进制值的“指示符”数组。 两者应该具有相同的维度。

Y = data['Y']
R = data['R']
Y.shape, R.shape
((1682, 943), (1682, 943))

我们可以通过平均排序 Y 来评估电影的平均评级。

Y[1,np.where(R[1,:]==1)[0]].mean()
3.2061068702290076

我们还可以通过将矩阵渲染成图像来尝试“可视化”数据。 我们不能从这里收集太多,但它确实给我们了解用户和电影的相对密度。

fig, ax = plt.subplots(figsize=(12,12))
ax.imshow(Y)
ax.set_xlabel('Users')
ax.set_ylabel('Movies')
fig.tight_layout()
plt.show()

接下来,我们将实施协同过滤的代价函数。 直觉上,“代价”是指一组电影评级预测偏离真实预测的程度。 代价方程在练习文本中给出。 它基于文本中称为 X 和 Theta 的两组参数矩阵。 这些“展开”到“参数”输入中,以便稍后可以使用 SciPy 的优化包。 请注意,我已经在注释中包含数组/矩阵形状(对于我们在本练习中使用的数据),以帮助说明矩阵交互如何工作。

代价函数

def cost(params, Y, R, num_features):
    Y = np.matrix(Y)  # (1682, 943)
    R = np.matrix(R)  # (1682, 943)
    num_movies = Y.shape[0]
    num_users = Y.shape[1]


    # reshape the parameter array into parameter matrices
    X = np.matrix(np.reshape(params[:num_movies * num_features], (num_movies, num_features)))  # (1682, 10)
    Theta = np.matrix(np.reshape(params[num_movies * num_features:], (num_users, num_features)))  # (943, 10)


    # initializations
    J = 0


    # compute the cost
    error = np.multiply((X * Theta.T) - Y, R)  # (1682, 943)
    squared_error = np.power(error, 2)  # (1682, 943)
    J = (1. / 2) * np.sum(squared_error)


    return J

为了测试这一点,我们提供了一组我们可以评估的预训练参数。 为了保持评估时间的少点,我们将只看一小段数据。

params_data = loadmat('data/ex8_movieParams.mat')
X = params_data['X']
Theta = params_data['Theta']
X.shape, Theta.shape
((1682, 10), (943, 10))
users = 4
movies = 5
features = 3


X_sub = X[:movies, :features]
Theta_sub = Theta[:users, :features]
Y_sub = Y[:movies, :users]
R_sub = R[:movies, :users]


params = np.concatenate((np.ravel(X_sub), np.ravel(Theta_sub)))


cost(params, Y_sub, R_sub, features)
22.224603725685675

接下来我们需要实现梯度计算。 就像我们在练习 4 中使用神经网络实现一样,我们将扩展代价函数来计算梯度。

def cost(params, Y, R, num_features):
    Y = np.matrix(Y)  # (1682, 943)
    R = np.matrix(R)  # (1682, 943)
    num_movies = Y.shape[0]
    num_users = Y.shape[1]


    # reshape the parameter array into parameter matrices
    X = np.matrix(
        np.reshape(params[:num_movies * num_features],
                   (num_movies, num_features)))  # (1682, 10)
    Theta = np.matrix(
        np.reshape(params[num_movies * num_features:],
                   (num_users, num_features)))  # (943, 10)


    # initializations
    J = 0
    X_grad = np.zeros(X.shape)  # (1682, 10)
    Theta_grad = np.zeros(Theta.shape)  # (943, 10)


    # compute the cost
    error = np.multiply((X * Theta.T) - Y, R)  # (1682, 943)
    squared_error = np.power(error, 2)  # (1682, 943)
    J = (1. / 2) * np.sum(squared_error)


    # calculate the gradients
    X_grad = error * Theta
    Theta_grad = error.T * X


    # unravel the gradient matrices into a single array
    grad = np.concatenate((np.ravel(X_grad), np.ravel(Theta_grad)))
    return J, grad
J, grad = cost(params, Y_sub, R_sub, features)
J, grad
(22.224603725685675,
 array([ -2.52899165,   7.57570308,  -1.89979026,  -0.56819597,
          3.35265031,  -0.52339845,  -0.83240713,   4.91163297,
         -0.76677878,  -0.38358278,   2.26333698,  -0.35334048,
         -0.80378006,   4.74271842,  -0.74040871, -10.5680202 ,
          4.62776019,  -7.16004443,  -3.05099006,   1.16441367,
         -3.47410789,   0.        ,   0.        ,   0.        ,
          0.        ,   0.        ,   0.        ]))

我们的下一步是在代价和梯度计算中添加正则化。 我们将创建一个最终的正则化版本的功能(请注意,此版本包含一个额外的“学习率”参数,在文本中称为“lambda”)。

def cost(params, Y, R, num_features, learning_rate):
    Y = np.matrix(Y)  # (1682, 943)
    R = np.matrix(R)  # (1682, 943)
    num_movies = Y.shape[0]
    num_users = Y.shape[1]


    # reshape the parameter array into parameter matrices
    X = np.matrix(np.reshape(params[:num_movies * num_features], (num_movies, num_features)))  # (1682, 10)
    Theta = np.matrix(np.reshape(params[num_movies * num_features:], (num_users, num_features)))  # (943, 10)


    # initializations
    J = 0
    X_grad = np.zeros(X.shape)  # (1682, 10)
    Theta_grad = np.zeros(Theta.shape)  # (943, 10)


    # compute the cost
    error = np.multiply((X * Theta.T) - Y, R)  # (1682, 943)
    squared_error = np.power(error, 2)  # (1682, 943)
    J = (1. / 2) * np.sum(squared_error)


    # add the cost regularization
    J = J + ((learning_rate / 2) * np.sum(np.power(Theta, 2)))
    J = J + ((learning_rate / 2) * np.sum(np.power(X, 2)))


    # calculate the gradients with regularization
    X_grad = (error * Theta) + (learning_rate * X)
    Theta_grad = (error.T * X) + (learning_rate * Theta)


    # unravel the gradient matrices into a single array
    grad = np.concatenate((np.ravel(X_grad), np.ravel(Theta_grad)))


    return J, grad
J, grad = cost(params, Y_sub, R_sub, features, 1.5)
J, grad
(31.34405624427422,
 array([ -0.95596339,   6.97535514,  -0.10861109,   0.60308088,
          2.77421145,   0.25839822,   0.12985616,   4.0898522 ,
         -0.89247334,   0.29684395,   1.06300933,   0.66738144,
          0.60252677,   4.90185327,  -0.19747928, -10.13985478,
          2.10136256,  -6.76563628,  -2.29347024,   0.48244098,
         -2.99791422,  -0.64787484,  -0.71820673,   1.27006666,
          1.09289758,  -0.40784086,   0.49026541]))

这个结果再次与练习代码的预期输出相匹配,所以看起来正则化是正常的。 在我们训练模型之前,我们有一个最后步骤, 我们的任务是创建自己的电影评分,以便我们可以使用该模型来生成个性化的推荐。 为我们提供一个连接电影索引到其标题的文件。 接着我们将文件加载到字典中。

movie_idx = {}
f = open('data/movie_ids.txt',encoding= 'gbk')
for line in f:
    tokens = line.split(' ')
    tokens[-1] = tokens[-1][:-1]
    movie_idx[int(tokens[0]) - 1] = ' '.join(tokens[1:])
movie_idx[0]
'Toy Story (1995)'

我们将使用练习中提供的评分。

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


print('Rated {0} with {1} stars.'.format(movie_idx[0], str(int(ratings[0]))))
print('Rated {0} with {1} stars.'.format(movie_idx[6], str(int(ratings[6]))))
print('Rated {0} with {1} stars.'.format(movie_idx[11], str(int(ratings[11]))))
print('Rated {0} with {1} stars.'.format(movie_idx[53], str(int(ratings[53]))))
print('Rated {0} with {1} stars.'.format(movie_idx[63], str(int(ratings[63]))))
print('Rated {0} with {1} stars.'.format(movie_idx[65], str(int(ratings[65]))))
print('Rated {0} with {1} stars.'.format(movie_idx[68], str(int(ratings[68]))))
print('Rated {0} with {1} stars.'.format(movie_idx[97], str(int(ratings[97]))))
print('Rated {0} with {1} stars.'.format(movie_idx[182], str(int(ratings[182]))))
print('Rated {0} with {1} stars.'.format(movie_idx[225], str(int(ratings[225]))))
print('Rated {0} with {1} stars.'.format(movie_idx[354], str(int(ratings[354]))))
Rated Toy Story (1995) with 4 stars.
Rated Twelve Monkeys (1995) with 3 stars.
Rated Usual Suspects, The (1995) with 5 stars.
Rated Outbreak (1995) with 4 stars.
Rated Shawshank Redemption, The (1994) with 5 stars.
Rated While You Were Sleeping (1995) with 3 stars.
Rated Forrest Gump (1994) with 5 stars.
Rated Silence of the Lambs, The (1991) with 2 stars.
Rated Alien (1979) with 4 stars.
Rated Die Hard 2 (1990) with 5 stars.
Rated Sphere (1998) with 5 stars.

我们可以将自己的评级向量添加到现有数据集中以包含在模型中。

R = data['R']
Y = data['Y']
Y = np.append(Y, ratings, axis=1)
R = np.append(R, ratings != 0, axis=1)
Y.shape, R.shape, ratings.shape
((1682, 944), (1682, 944), (1682, 1))

我们不只是准备训练协同过滤模型。 我们只需要定义一些变量并对评级进行规一化。

movies = Y.shape[0]  # 1682
users = Y.shape[1]  # 944
features = 10
learning_rate = 10.
X = np.random.random(size=(movies, features))
Theta = np.random.random(size=(users, features))
params = np.concatenate((np.ravel(X), np.ravel(Theta)))


X.shape, Theta.shape, params.shape
((1682, 10), (944, 10), (26260,))
Ymean = np.zeros((movies, 1))
Ynorm = np.zeros((movies, users))


for i in range(movies):
    idx = np.where(R[i,:] == 1)[0]
    Ymean[i] = Y[i,idx].mean()
    Ynorm[i,idx] = Y[i,idx] - Ymean[i]


Ynorm.mean()
5.507036456515984e-19
from scipy.optimize import minimize


fmin = minimize(fun=cost, x0=params, args=(Ynorm, R, features, learning_rate),
                method='CG', jac=True, options={'maxiter': 100})
X = np.matrix(np.reshape(fmin.x[:movies * features], (movies, features)))
Theta = np.matrix(np.reshape(fmin.x[movies * features:], (users, features)))
X.shape, Theta.shape
((1682, 10), (944, 10))

我们训练好的参数是 X 和 Theta。 我们可以使用这些来为我们添加的用户创建一些建议。

predictions = X * Theta.T
my_preds = predictions[:, -1] + Ymean
my_preds.shape
(1682, 1)
sorted_preds = np.sort(my_preds, axis=0)[::-1]
sorted_preds[:10]
matrix([[5.00000277],
        [5.00000236],
        [5.00000172],
        [5.00000167],
        [5.00000018],
        [4.99999996],
        [4.99999993],
        [4.99999963],
        [4.99999936],
        [4.99999933]])

这给了我们一个排名最高的评级,但我们失去了这些评级的索引。 我们实际上需要使用 argsort 函数来预测评分对应的电影。

idx = np.argsort(my_preds, axis=0)[::-1]
idx
matrix([[1499],
        [ 813],
        [1535],
        ...,
        [ 313],
        [ 829],
        [1431]], dtype=int64)
print("Top 10 movie predictions:")
for i in range(10):
    j = int(idx[i])
    print('Predicted rating of {0} for movie {1}.'.format(str(float(my_preds[j])), movie_idx[j]))
Top 10 movie predictions:
Predicted rating of 5.0000027697456755 for movie Santa with Muscles (1996).
Predicted rating of 5.000002356341384 for movie Great Day in Harlem, A (1994).
Predicted rating of 5.000001717626692 for movie Aiqing wansui (1994).
Predicted rating of 5.000001669423294 for movie Star Kid (1997).
Predicted rating of 5.000000180977937 for movie Prefontaine (1997).
Predicted rating of 4.999999960309181 for movie They Made Me a Criminal (1939).
Predicted rating of 4.999999928299842 for movie Marlene Dietrich: Shadow and Light (1996) .
Predicted rating of 4.999999629264883 for movie Someone Else's America (1995).
Predicted rating of 4.9999993570025705 for movie Saint of Fort Washington, The (1993).
Predicted rating of 4.999999325074885 for movie Entertaining Angels: The Dorothy Day Story (1996).

推荐的电影实际上并不符合练习文本中的内容, 我没有找到原因在哪里。

参考资料

[1] 机器学习课程: https://www.coursera.org/course/ml
[2] 黄海广: https://github.com/fengdu78
[3] github: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes
[4] 作业代码: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/code/ex8-anomaly%20detection%20and%20recommendation/ML-Exercise8.ipynb
[5] markdown 文件: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/markdown/week9.md
[6] pdf 文件: https://github.com/fengdu78/Coursera-ML-AndrewNg-Notes/blob/master/机器学习个人笔记完整版v5.4-A4打印版.pdf

关于本站

“机器学习初学者”公众号由是黄海广博士创建,黄博个人知乎粉丝23000+,github排名全球前100名(33000+)。本公众号致力于人工智能方向的科普性文章,为初学者提供学习路线和基础资料。原创作品有:吴恩达机器学习个人笔记、吴恩达深度学习笔记等。

往期精彩回顾

备注:加入本站微信群或者qq群,请回复“加群

加入知识星球(4300+用户,ID:92416895),请回复“知识星球

  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值