date
icon
password
博客链接
Pin
Pin
Hide-in-Web
Hide-in-Web
网址
type
slug
tags
category
bottom
bottom
Hide-in-Config
Hide-in-Config
comment
status
summary
课程链接:吴恩达 机器学习
🎉欢迎大家来到我的博客!!🎉
首先,感谢 up 主 @啥都会一点的研究生 分享的吴恩达的课程,对我的学习很有帮助,虽然中间很多视频的字幕实在不尽人意,但是我明白 up 主也尽了自己最大的努力。
此页面是我对吴恩达机器学习课程的笔记,记录了老师上课的内容以及自己的一些问题和思考,大家可以把它当作是知识检索库或者是复习笔记。
请大家尽量用电脑来进行查看,目前还在持续更新中(因为我也没看完🥺) ,不过我会和大家一起坚持下去的!!!
希望大家都能够在这里得到收获!!
‼️ Attention:善用快捷键 Ctrl+F 搜索对应知识点!!
课程资源及相关环境配置
课程资源
下面的文件是从 up 主分享的 github 上面下载的,有课程的 ppt (分散的和整合的),也有课程的 jupyter notebook 文件以及环境配置的 txt 文档。
相关环境配置(anaconda+pytorch/tensorflow)
建议大家先去下载 anaconda ,网上搜索相关的教程配置好(👇🏾我已经写了一篇相关的博客放在下面的链接里了👇🏾),安装了 anaconda 后会自动下载 jupyter notebook。anaconda 可以很方便的配置多个 python 环境而不需要重复性去 python 官网下载不同的 python 版本,方便管理和移植环境。
当然如果只是为了这门课程,也可以直接使用 3.7.6 版本的 python,按照 github 上的操作要求来配置相应的库:

如果使用的是 anaconda 的虚拟环境配置 pytorch/tensorflow,需要先打开 anaconda prompt,通过指令
conda create -n=tf python=3.7.6
创建一个环境,然后通过 conda activate tf
进入这个环境,安装库的方法是 conda install [库的名称]
,通过 conda uninstall [库的名称]
可以删除对应的库。更多 anaconda 的指令可以参考这篇 CSDN 博客:Anaconda conda常用命令:从入门到精通_conda list-CSDN博客 。对于吴佬的 jupyter notebook 中的内容,我采用的是见一个库安装一个库的模式,因为之前使用 requirements.txt 的方式安装,总有一些库在安装的时候报错,花了很多时间也没解决。不过我还是会介绍一下如何通过 requirements.txt 的方式来安装,因为毕竟每个人电脑不一样,也许你们就安装成功了呢😮💨。
如何通过 requirements.txt 安装 tensorflow 对应的库?
监督学习
💡 Key Words
这里的关键词只是帮助大家看完右侧的笔记后回忆内容,不是跳转链接!!
- 监督学习——分类和回归
- 线性回归
- 符号表示
- 成本函数
- 梯度下降
- 多元线性回归
- 点积
- 特征缩放
- 特征工程
- 分类问题
- 逻辑函数(sigmoid)
- 损失函数
- 梯度下降
- 欠拟合(高偏差)
- 过拟合(高方差)
- 正则化
🔗 Relevant Information
📝 Class Notes
监督学习和非监督学习介绍
监督学习
回归性分析:
根据已有的数据拟合出相应的直线或是曲线,接收一个输入可以预测输出,可以从无限多种可能中找出最合适的一种。
分类问题:
假设背景是需要预测特定大小的一个肿瘤究竟是良性的,还是第一类的恶性肿瘤或第二类的恶性肿瘤,将所有的可能绘制在一维直线上(输出类别少时,也可以绘制在高维图像中):

监督性学习就是根据已有的这些数据做出模型,去预测特定直径的肿瘤的类别。
分类问题和回归性问题的区别:
分类问题的输出是一个有限的集合,反映了特定的类别,而回归性问题预测的输出却是连续的,如一条直线,是无限的集合。
无监督学习
无监督学习是目前应用最为广泛的一种机器学习的算法。
无监督学习与监督学习的对比:

相对于监督学习而言,无监督学习给定的数据只包含了输入数据的特征,而不包含输出 y 的标签,以上图肿瘤为例,监督性学习给定的每个数据都包含 (age, tumor size, label) 这三个量,也就是将输入的年龄、肿瘤大小与肿瘤的类别绑定在一起,而无监督学习却只包含 (age, tumor size) 这两个量,不包含肿瘤的类别。
无监督学习的目的不是为了预测这个肿瘤是否是良性的,而是通过非人为监控的方式让机器(算法)自己去判断数据有哪些特征,有时会得到一些意想不到的结果。
上述的无监督学习的方式称之为 聚类学习。
以下是聚类学习的另一个例子:

上图是谷歌新闻中的相关推荐,原始文章说的是熊猫在动物园中产下了双胞胎,如果想要一个个查找相关新闻,需要从数十万篇文章中一篇篇的整理,而通过谷歌的聚类算法,谷歌提取出了 panda twin zoo 这几个关键词,并与数十万篇文章中的关键词相匹配,从而可以快速获得相关推荐。在这里,无监督性学习就体现在没有人每天在新闻标题中一个个的提取关键词,所有的操作都是由无监督学习算法完成的。
无监督学习的简单定义
无监督学习就是将一群没有标签的数据分组到不同的群组里面。
无监督学习的分类
主要分为三类:聚类学习、异常检测和降维。

多元线性回归(多特征线性回归)
多元线性回归通常使用在变量具有多个特征的时候,如房子的价格可能不只是受到房子面积的影响,还受到房间数量、使用年限等因素的影响,每个因素都有各自的权重。为此引入矢量化,将多个特征通过向量的形式表示出来,不仅方便理解,也方便后续的计算。引入矢量化后,原来的函数 f(x) → 。
在多元线性回归中,最常使用到的一个库是 Numpy,可以创建 numpy 类型的数组,这个数组相对于列表的特点是在计算机中的内存是连续的,意味着更加迅速的计算。
点积的计算方法:

第一种方法是最慢的,而且在 n 很大的时候需要输入的代码段也很多,不灵活;
第二种方法是利用循环,这比第一种方法快且方便但是计算速度仍然很慢;
第三种方法充分利用了 numpy 数组在计算机内存中的特点,并行计算,计算速度快。
特征缩放:
引入:

由上图可知,当两个特征的差异比较大的时候(还是以房子售价为例,横轴表示单位房价,纵轴表示房间数量),相应的权重也会有所不同,最终绘制出来的权重图会是像椭圆一样比较极端的图像,即其中一个特征对权重反应比较迟钝,另一个特征对权重变化比较敏感。但是,我们可以想象一下,当特征相差没有那么大的时候,例如,单位房价和房间数量通过某种映射,最终都在 0 ~ 1 之间变化,那么他们的权重图就会比较均衡,是一个圆形。
通过上面的例子,我们也就引入了特征缩放的概念,即当特征有显著差异的时候,我们如果将特征缩放到一个量级进行比较,它的权重参数也会比较好观测与计算,实验表明,特征缩放后的收敛速度更快。
特征缩放的方法:
假设特征的取值范围是 ,那么共有三种缩放的方式:
① ; ② (Mean Normalization) ,其中 mean 是特征均值;
③ (Z-Score) ,其实就是转换成概率论里面的标准正态分布。
对于缩放范围的分析:

和 的取值是合适的,不需要缩放,而后面三个不等式范围太大了,需要缩放。
特征工程:

由图可知,特征工程指的就是通过组合或转换原有的特征来产生一个更能反映事物特点的新特征,并将其纳入成本的考虑中。
如上图所示,也许最开始考虑房价我们只会想到房子所在地的长度和宽度,但是后来意识到长度和宽度的乘积面积可能与房价的设定关系更大,于是将它纳入考虑。
多项式回归

之前我们所讨论的都是线性回归的内容,对于一个实际问题而言,我们需要记得,回归函数往往不是线性的,它有可能是高次幂的,这更体现出特征缩放的意义。
分类问题
分类问题区别于线性回归的原因

在进行二分类问题的时候,也许我们想要像图中的蓝线一样拟合出一条直线,将 y = 0.5 的点所对应的 x 的值作为是分类的决策边界,这个思路貌似不错,但是当 x 的值非常大的时候,拟合直线受到它的影响也会很大,这会使得决策边界过分右移,这样显然是无法最好地进行分类的。
在分类问题中,我们常常使用 0 或是 1 来表示分类的类别,但这并不意味着标签为 0 的类别就是 bad 的,这只是一个分类的方法而已。
逻辑函数(Sigmoid 函数)

在分类问题中,经常使用 Sigmoid 函数来进行分类,它的输出在 0 - 1 之间,而且当 z = 0 时,y = 0.5.


逻辑回归的输入可以是肿瘤的大小,逻辑回归的输出则是判断肿瘤是 1 的概率,将向量 x 代入 z 函数后计算出来的结果实际上是一个条件概率,它意味着在 固定的情况下,给定输入 x ,输出 y 为 1 的概率。如图中所示,如果 y = 1 的概率是 0.7,则 y = 0 的概率就是 0.3。
决策边界
对于不同的问题,我们需要确定不同的决策边界,决策边界是用于区分不同类别的边界线,在边界线上是中立的。
决策边界不一定都会是直线,也有可能是圆,或是其他不规则的曲线。
损失函数和成本函数
为什么要引入新的损失函数?
在逻辑回归中,对于损失函数和成本函数有单独的定义,它不同于线性回归中的成本函数。
在线性回归中,我们不会过多的考虑一个训练样例的成本变化,而是考虑 m 个训练样例的总成本()并利用成本函数找到凸函数的最优解。
而在逻辑回归中,针对一个训练样例,我们构造了一个损失函数来计算其损失值,利用损失值判断参数的设置是否合理,对于 m 个训练样例(完整的训练集),与线性回归一样,会称呼它为成本函数,但是成本函数的公式是由每个样例的损失函数累加得到的。
也就是说,损失函数是在一个训练样例上的表现,而成本函数是在整个数据集上的表现。
那么为什么分类问题不沿用线性回归的损失函数呢?
在线性回归中,我们使用误差平方和的方式来定义成本函数,而对于分类问题而言,预测值 ,将其套用误差平方和的公式作出的成本函数的图像是非凸函数,这就意味着它存在很多局部最小值,无法通过简单的梯度下降算法求得全局最小值。

因此要对成本函数进行凸优化,重新构造一个符合我们要求的损失函数。
损失函数的公式
我们重新定义逻辑回归的成本函数为: , 其中损失函数
其中 为预测值,y 为实际值。这样构建的 函数的特点是:
- 当实际的 𝑦 = 1 且 也为 1 时误差为 0,当 𝑦 = 1 但 不为 1 时误差随着 变小而变大;
- 当实际的 𝑦 = 0 且 也为 0 时误差为 0,当 𝑦 = 0 但 不为 0 时误差随着 的变大而变大。
最终的成本函数为 ,即 。
上面的成本函数又称为是 交叉熵损失函数(Cross Entropy Loss Function),是统计学中的概念。
至于为什么会将损失函数定义为上面的 ,和概率论的最大似然函数有一定的关系。
梯度下降

在分类问题中也使用梯度下降算法来更新参数,当我们使用新定义的成本函数的时候,我们可以奇迹般的发现
上面的式子与线性回归中的梯度下降形式上是完全一致的,为此我们不需要多记住一个公式。
但需要注意的是,分类问题中的梯度下降算法公式与线性回归实质上是不同的,因为他们的 是不同的。

在分类问题中,与线性回归一样,都有学习率,有矢量化的实现,有特征缩放。
欠拟和、过拟合

欠拟合(underfit)指的是训练处理的模型参数对与训练集的匹配程度并不好,具有比较高的偏差(high bias);
泛化(generalization)的模型指的是模型不仅能够很好的适配训练集,面对未经过训练的数据集也能够很好的发挥作用,在我们训练模型的过程中一般是模型能够很好的匹配测试集;
过拟合(overfit)指的是模型对于训练集的匹配太好了,训练的太过了,以致于它对于训练集所有特征(包括共有的和训练集本身私有的)都进行了训练,最终的训练出来的模型泛化性能不好,无法准确预测测试集的数据,具有比较高的方差(high variance)。
解决过拟合的方法:
- 增加更多的训练数据,扩大训练集的大小(数据足够多时);
- 限制模型能够训练的特征数量(更少的特征),防止模型过度训练(数据数量有限时)。

如上图所示,也许我们得到的数据集有 100 个特征,但数据数量有限,我们采用第二个方法来解决过拟合,我们通过直觉或是什么方式认为某些个特征对于房价的影响是最大的,因此将那些特征挑出来供模型训练,但这样做的缺点就是,也许房价就是与这 100 个特征息息相关,那么我们最终的模型就会是那么的不尽人意😇。(吴老说在课程 2 会有自动寻找特征的方法)。
- 采用正则化的方式消除或减小某些特征的影响。
正则化是在模型训练好后,将某些特征的影响降低,正则化的作用是鼓励学习算法缩小参数值,而不必要求参数正好设置为0。因此,正则化的优点就是可以在保留所有特征和数据的情况下避免出现过拟合,通常在进行正则化的时候并不会对参数 b 进行处理,只进行 的正则化。
正则化
正则化保留数据所有的特征值,但是会削弱某些特征的影响,避免模型出现过拟合。

上图是正则化的一个简单例子,在图像的最下方,前半部分是我们之前学过的成本函数,我们的目的就是让成本函数最低。现在我们作出一些调整,在成本函数的后面加上两项,这两项都与权重有关系,也就是与特征有关系,我们可以看到他们的系数都比较大,是 1000,我们希望调整后的函数最小化,那么这就意味着权重 和 都要比较小,这样才能保证调整后的成本函数是最小的。假设它们都接近于 0,现在在回到原来的函数中,我们可以发现,由于权重变小了,高次项能够很好的被平衡,或者说被忽略,这便达到了我们的目的。
基于这个例子,我们需要重新调整我们的成本函数:

我们新添加的项为 ,其中 λ 被称为是正则系数,将它除以 2m 只是为了让它与原来的成本函数有相同的缩放,通过调整 λ 的大小,我们可以控制所有权重的量级,进而避免模型出现过拟合。我们可以注意到,图中还有一项 ,这一项是可有可无的,它并不影响最终的结果,一般不会加上它。

λ 的取值是有讲究的,不能过大也不能过小。如果 λ 过小,相当于还是原来的成本函数,还是会出现过拟合的问题。如果λ 过大,那么它的最终结果可能会是一条直线,没有达到训练的效果。
线性回归正则化公式如下:
逻辑回归正则化的公式如下:
要注意上面的代码中前半部分和后半部分迭代的范围不同,前半部分是 m 个样例,后半部分是 n 个特征。
代码部分
线性回归代码
线性回归成本函数代码
代码基于公式:
具体的代码如下:
线性回归梯度下降代码
计算成本的函数代码:
计算梯度的函数代码:
梯度下降的代码如下(结合前面两个部分的代码):
多元线性回归梯度下降代码
计算成本函数的代码:
计算梯度的代码:
梯度下降的算法:
z 分数法进行特征缩放代码
分类问题中梯度下降的代码
上述代码中循环体的理解

线性回归正则化代码
以下代码基于上面的公式:
逻辑回归正则化代码
以下代码基于上面的公式:
线性回归正则化计算梯度代码
以下代码基于上面的公式:
逻辑回归正则化计算梯度代码
以下代码基于上面的公式:
sklearn (机器学习库)代码
sklearn 进行线性回归与随机梯度下降
sklearn 进行逻辑回归:
sklearn 是一个专用于机器学习的库,可以很方便的进行机器学习相关的计算。
李沐深度学习课程的补充:
线性回归部分的补充
线性回归的基本元素

线性回归的解析解

解析解的推导:

线性回归的特点

关于线性回归公式的几个问题
为什么均方误差可以用于线性回归?

我们在最小化成本函数的同时,也最小化了噪声,同时我们又找到了最合适的参数。
为什么要使用平方损失函数而不是用绝对插值呢?
因为绝对插值在某些点处并不是可导的(比如零点处),使用平方损失可以避免不必要的麻烦。
为什么平方损失函数前面要除以 m?
这似乎和统计学中的某些概念有关,虽然无法准确解释原因,但可以从结果上稍作说明。
从结果上来看,除以 m 会使得学习率也除以 m,即便在平方损失函数中没有除,也可以在更新参数的公式中直接在 学习率 下除以 m。这样可以使得学习率的大小更合适。
线性回归与神经网络的关系

线性回归梯度下降部分的补充
小批量随机梯度下降的引入


小批量随机梯度下降算法中除以批量大小的目的是确保梯度的量级不会因为批量大小的改变而波动,从而使得学习率在不同的批量大小下都能保持适当的更新步长。
小批量随机梯度下降算法的代码
小批量在深度学习中的实现原理:
虽然上面的函数运行效率低,在实际的深度学习底层中并不这么做,但是它却真实反映了小批量随机梯度下降算法中“小批量”的含义,小批量的选择过程就是如上面的函数所示。
随机梯度下降的实现:
代码中 with torch.no_grad()
的理解:
在上述代码中,使用
with torch.no_grad()
是为了在进行参数更新时避免跟踪和记录梯度。这在梯度下降的实现中至关重要,原因如下:1. 防止自动求导机制的干扰:
- PyTorch 的自动求导机制会自动跟踪所有涉及张量的操作,以便在反向传播时计算梯度。如果在参数更新时不使用
torch.no_grad()
,那么这些更新操作(如param -= lr * param.grad / batch_size
)也会被跟踪并记录在计算图中。
- 这将导致不必要的计算图的创建,浪费内存,并可能引发错误的梯度计算。因此,
torch.no_grad()
会禁用自动求导机制,从而在参数更新时不记录计算图。
2. 提高效率:
- 禁用自动求导机制不仅避免了额外的计算图开销,还可以提高代码的运行效率,特别是在进行参数更新的循环中。因为这些更新操作并不需要被反向传播计算图记录,所以直接在不跟踪梯度的情况下执行会更快。
3. 明确意图:
- 使用
with torch.no_grad()
明确表明此处的代码块仅用于更新模型参数,并不涉及反向传播或梯度计算。这种明确的意图有助于代码的可读性和维护性,方便其他人理解代码的目的。
总结
在梯度下降算法中,我们只需要在前向传播和反向传播过程中跟踪计算图,而在更新模型参数和测试时并不需要跟踪梯度。因此,通过
torch.no_grad()
,我们可以避免不必要的计算图记录,节省内存和计算资源。简化的线性回归代码
前面的部分都是在介绍线性回归部分的原理代码,而实际上,对于深度学习中大量重复用到的这些算法都有更加简化的代码进行表示,有专门的库计算损失值、梯度。但是,只有学习了前面的原理部分,才知道底层的架构是怎么样的,这样在未来调试代码的时候也会更加轻松。下面的部分都是简化的线性回归代码,也是实际生活中更加常用的代码。
![Sequential MSEloss Linear nn optim SGD trainer.step() net[0].weight.data.normal_() net[0].bias.data.fill_() TensorDataset。](https://www.notion.so/image/https%3A%2F%2Fprod-files-secure.s3.us-west-2.amazonaws.com%2F7743fca7-7fbe-49da-88fd-2cbe749f722f%2Fa4de60fd-d118-46ca-9aca-c7f1c8fd9b2a%2Fimage.png?table=block&id=1e585c3d-c58c-8162-86dc-cb88e76af1b1&t=1e585c3d-c58c-8162-86dc-cb88e76af1b1&width=864&cache=v2)
深度学习中梯度下降的特点

超参数的概念:
超参数主要包括网络结构参数和选择的算法及其参数,网络结构参数如卷积核的大小、卷积层的数量等,优化算法包括 SGD(算法类型)和 学习率、batch(算法参数) 等。
Prediction 与 Inference 术语的差异

神经网络与生物学
神经网络最开始确实是从生物学中得到了灵感,因为它的训练过程就如同神经元之间训练模型一样,但是当今大多数深度学习的研究几乎没有直接从神经科学中获得灵感。 我们援引斯图尔特·罗素和彼得·诺维格在他们的经典人工智能教科书 《 Artificial Intelligence:A Modern Approach 》 中所说的:虽然飞机可能受到鸟类的启发,但几个世纪以来,鸟类学并不是航空创新的主要驱动力。 同样地,如今在深度学习中的灵感同样或更多地来自数学、统计学和计算机科学。
神经网络(激活函数、多分类问题、Tensoflow 入门、多标签、反向传播)
💡 Key Words
这里的关键词只是帮助大家看完右侧的笔记后回忆内容,不是跳转链接!!
- 神经元
- 输入层、输出层、中间层
- 多层感知机
- 激活值、激活函数
- Tensorflow、np.numpy、np.dense、Sequential
- ani、agi
- np.matmul ↔ @
- Sigmoid(Softmax), ReLU,linear
- 多分类问题
- 多标签分类问题
- Adam 优化算法
- 卷积层
- 反向传播
🔗 Relevant Information
📝 Class Notes
神经网络的动机是尝试构建模仿大脑的算法,在上世纪 80 年代到 90 年代由于手写识别比较火热,后来又一度沉寂。直到 2012 年 ImageNet 数据集成功被机器学习算法训练,机器学习(深度学习)才又发展起来,一直到现在。神经网络的名称最初是源于生物学中的神经元,神经元与神经元之间相互连接,上一个神经元的输出称为下一个神经元的输入,最终实现信息传播、记忆、学习等功能。
神经网络的引入
神经元:
在神经网络中,当我们使用逻辑回归函数进行预测的时候,我们更习惯使用 a (activation)来表示逻辑回归函数,它可以表示为大脑中的一个小小的神经元,即 。

下面将根据这个图来解释一下神经网络的基本工作原理:

在神经网络中,我们可能会有多个输入的特征,将这些特征看作是一个层级,我们称它为输入层;
输入层的特征数据通过某种组合方式输入给中间层,图中的中间层由三个神经元组成,也就是说,一个神经元可以接受多个特征,但是只产生一个输出;中间层可以有很多,图中只有一个中间层。
吴老采用了 T 恤售卖的例子来讲解这一点,T 恤的售卖会收到很多因素的影响,而我们手中的数据可能是价格、运输成本、营销和材质,这些数据经过神经元后,可以被归结为 可负担性、认知度、 质量等这些数据本身不包含的抽象特性,这些特性是对特征的进一步概括,称为是 activations (来源于生物神经元的名词)。这些抽象的数据更反映了 T 恤售卖的本质特征,再将这三个数据输入到一个神经元,最终也许就能够得到 T 恤受欢迎的概率。
将中间层数据转换为最终的结果的层称为输出层,而中间层的数据又被称作是隐藏层(因为中间层得出的结果在显示训练过程中一般不会接触到,我们只会知道输入数据和输出结果)。

上图中中间层的数量不止一个,像图中这样有多个中间层的神经网络又被称为是 “多层感知机(MLP)”。中间层的神经元的输出不一定会比输入的特征数量少。
神经网络中的网络层
网络层:

在神经网络中,我们常将输入层称作是 layer 0,第一个中间层称作是 layer 1…
还记得我们在线性回归的课程中用小括号上标来表示训练样例,类似地,在神经网络中,我们使用方括号上标表示不同的神经网络层的数据(如图中所示),在每一个网络层中,每个神经元都使用逻辑回归函数来计算概率值,输入的所有特征会被赋予不同的权重因此每个神经元中都是多元逻辑回归。
更复杂的神经网络:

对于更复杂神经网络某个中间层的某个神经元的激活值(activation)可以通过激活函数来表示,目前我们所学的激活函数只有 Sigmoid 函数,在后面我们会接触到更多的激活函数。在这里,第 l 层第 j 个神经元的激活值通过函数 来计算。
前向传播
前向传播指的就是输入向量 经过不同的神经层,得到不同的激活值最终计算出概率的过程,随着前向传播的进行,越靠近输出层,隐藏层的神经元数量会越少。

与前向传播相对应的就是反向传播,反向传播是用于更新参数提高学习率的。
ANI 与 AGI
ai 分为两层的含义,第一层是 ANI,即 artifitial narrow intelligence;第二层是 AGI,即 artifitial general intelligence。前者是狭义人工智能,后者是通用人工智能。
ANI 是指能够在特定任务或领域内表现出智能的人工智能系统,比如语言翻译、语音识别、推荐系统等。它们在特定任务上可能表现得非常出色,但无法跨越不同领域进行泛化。ChatGPT 属于 ANI,因为它专门擅长于自然语言处理,能够生成类似人类的文本,但并不具备通用智能,无法在更广泛的认知领域中自如操作。
AGI 是一种具有通用性和自主学习能力的人工智能,能够理解、学习、适应任何任务,类似于人类的智能。目前,AGI 仍是一个长期的研究目标,尚未实现。
能否将现有的 ANI 集成到一个系统,进而实现 AGI?
实现 AGI 不仅仅是将不同的 ANI 功能整合到一个系统中,还需要以下几个方面的突破:
- 跨领域适应能力:AGI 能够在不同领域和任务之间进行有效的迁移学习和知识泛化,而不仅仅是把多个专门的 ANI 功能拼接起来。
- 自我学习和推理:AGI 能够通过经验学习和推理解决新的问题,不需要依赖事先编程的规则或大量的训练数据。
- 意识和理解:AGI 具备一定程度的自我意识和对环境的理解,能够进行复杂的推理和决策,而不是仅仅依赖于预设的规则和数据。
- 常识推理:AGI 能够运用常识进行推理和判断,处理未见过的情况。
Tensorflow 用于构建神经网络
Tensorflow 的引入
由于吴老是谷歌大脑团队中领导 Tensorflow 的人,因此本课程中使用 Tensorflow 来实现机器学习。

在 Tensorflow 中,上面的两行代码构造的是矩阵,第一行是 1x2 的矩阵,也就是俗称的行向量,第二行是 2x1 的矩阵,也就是所谓的列向量。而第三行的代码构造了一个一维的数据,它虽然和行向量和列向量很相似,但是它却并没有行列的区分。
在前面的课程中,我们使用一维向量来表示数据,但是在 Tensorflow 中写代码时,更多的是使用前两种形式,因为 Tensorflow 旨在处理更大的数据集,通过矩阵描述数据,矩阵在 Tensorflow 中的计算会更加高效。

图中是使用 Tensorflow 构建神经层的方式,先创建一个数组用于存放输入向量,然后利用
Dense
方法指定了神经元的数量和激活函数,最后通过 layer_1(x)
的方式计算出 。图中蓝色下划线表示的是“张量”,它是与 numpy 数组一样表示数据的一种方式,在 Tensorflow 中使用张量来存储和计算数组会更加高效。图中调用
layer_1(x)
后返回的结果就是 Tensorflow 的数组,dtype=float32
表示的是它的数据类型是浮点数,在计算机中占 32 位的内存。如果想要将 Tensorflow 数组转换为 numpy 数组,可以调用图中的代码 a1.numpy()
。使用两种方式表示数组受发展历史原因的影响。多层感知机的 Tensorflow 代码:
原代码:

改进后的形式:

如图所示,改进后的代码将所有的神经网络层都整合在了一起,使用 Sequential 方法将神经网络层级联起来,并创建了一个对象来储存它。这样做的好处就是,可以将所有的神经网络模块整合在一起,将数据输入输出和模型创建的过程分开,结构更加清晰。
改进后的代码中
model.compile(…)
函数 model.fit(x,y)
函数和model.predict(x_new)
函数在之后的课程中会介绍,他们的作用大体上是编译模型、将 x 作为模型的输入进行训练并将结果返回给输出、对 x_new 进行预测。其中模型的构建过程也可以使用下面一个语句完成:

矩阵乘法
Z = numpy.matmul(A, W)
就是实现 A 与 W 矩阵乘法的方式,矩阵乘法相对于迭代来说,效率更高;也可以使用
Z = A @ W
来计算矩阵乘法,这是它的另一种写法。Tensorflow 构建网络完整步骤
所有步骤的总览:

首先,我们需要先确定自己选择什么样的激活函数来计算神经元的输入和输出,使用哪些网络层,需要使用多少网络层,每个网络层需要多少神经元;
其次,我们要选择训练用的损失函数(成本函数),了解不同损失函数的特点;
最后,我们需要通过最小化成本函数来找到合适的 w 和 b。
上述过程也是在 Tensorflow 中写代码的过程,首先将所有会用到的网络层通过 Sequential 函数整合成一个模型;然后编译模型,选择模型的损失函数;最后通过 fit 将训练数据输入网络,设置迭代的周期数,模型就可以成功得到训练,获得最优参数了。之后需要做的就是验证模型的准确率,需要通过将测试集的数据输入到训练好的模型中,利用 predict 函数进行预测即可。
构建模型

导入具体的模块,并将所有的网络层放入 Sequential 中。
选择损失函数

在回归问题中,我们选择的成本函数是均方根误差函数,代码中是
MeanSquareError()
;在二分类问题中,我们选择的成本函数是
BinaryCrossentropy()
,意为“二进制交叉熵损失函数”,而统计学中将其称为“交叉熵损失函数”,之所以多加了二进制的前缀只是为了说明它是用于二进制的分类问题的。更新模型参数

更新模型参数的代码只有一行 model.fit(),它的内部是采用反向传播通过梯度下降算法来更新参数值。
其他激活函数的介绍
机器学习中最常用的几个激活函数

机器学习中最常使用的有四个激活函数,分别是线性激活函数(Linear activation)、逻辑激活函数(
Sigmoid)、整流线性激活函数(ReLU)。
线性激活函数的作用其实就是相当于线性回归,只不过之前我们接触的线性回归问题都是单层神经网络单个神经元的,机器学习领域的人也会将使用这种激活函数的情况称为 “No acitvation function”,即没有使用激活函数。
Sigmoid 函数就是我们之前接触的二分类问题常使用的激活函数。
ReLu 激活函数的全称是 Rectified Linear Unit,它可以将负数的值全部除去,只留下正数,相当于是数学中的公式 ,这个名字没有什么特殊含义,吴佬说只是作者用于称呼它的一种方式,机器学习领域的人也只是称它为 ReLU 函数。
输出层激活函数的选择

对于输出层而言,可以根据最终的结果是否能够取负以及问题的性质选取激活函数。
如果是一个二分类的问题,那么输出层的激活函数可以选择 Sigmoid 函数;
如果输出结果是可以取负数的,比如预测股价的跌涨,那么输出层的激活函数可以选择线性激活函数;
如果是类似房价预测类的问题,输出结果只能是取正数的,那么输出层的激活函数就只能使用 ReLU 函数。
隐藏层(中间层)激活函数的选择
对于隐藏层(中间层)而言,ReLU 是神经网络中采用最多的激活函数,与 Sigmoid 函数相比,它有两个好处:
- ReLU 的计算效率高,速度快,因为 ReLU 函数只需要判断 0 和 z 两个中的最大值就可以了,不需要计算复杂的对数和分数。
- ReLU 只有负半轴是平坦的,而对于 Sigmoid 函数而言,有两个地方是平坦的。在使用梯度下降算法训练神经网络的时候,如果有一个函数在许多地方都是平坦的,那么它的梯度下降会很慢。

如图所示,当隐藏层使用 Sigmoid 函数的时候,又出现多处平坦的地方,这会使得梯度下降算法的运行效率大大降低。
隐藏层输出层激活函数总结
对于隐藏层而言:吴佬推荐只使用 ReLU 激活函数,事实证明在绝大多数情况下这是运行速度最快的方式。
对于输出层而言:
如果是二分类问题,那么就使用 Sigmoid 函数;
如果是输出正负皆可取的回归类问题就使用线性激活函数;
如果是输出只能取正数的回归类问题就使用 ReLU 函数。
为什么要使用激活函数?
- 引入非线性:
神经网络中的每一层通常都是线性变换的组合。如果不使用激活函数,整个网络的输出就是输入的线性组合,无论网络有多少层都无法表达复杂的函数关系。激活函数引入了非线性,使得神经网络能够处理更复杂的任务,例如分类、回归等。
- 模仿生物神经元:
激活函数的设计灵感来自于生物神经元的行为。生物神经元在接收到一定强度的信号时才会激活,类似地,激活函数决定了神经网络中的某个神经元是否应该被激活,即输出一个信号。这种机制使得网络能够根据不同的输入激活不同的神经元,从而实现对输入信息的选择性反应。
- 帮助梯度下降法优化网络:
许多常用的激活函数(例如ReLU、sigmoid、tanh)具有可微性,这使得在使用反向传播算法进行网络训练时,可以计算梯度并通过梯度下降法进行优化。不同的激活函数对梯度的传递效果不同,选择合适的激活函数有助于减轻梯度消失或梯度爆炸问题,确保网络能够有效训练。
为什么隐藏层激活函数不使用线性激活函数?

因为隐藏层如果使用线性激活函数,那么无论隐藏层有多少层,都会相当于是一个线性激活函数在作用,无法表达复杂的函数关系,如果此时输出层选择的仍是线性激活函数,那么最终模型就相当于是一个一层的线性函数,这样的训练过程是没有意义的;如果输出层选择的是逻辑函数,那么最终模型就相当于是一个一层的逻辑函数,也没法表示复杂的函数关系。
多分类问题
定义
多分类问题其实就是二分类问题的拓展,y 中标签的数量由 2 个变为 2 个以上。
Softmax 激活函数
Softmax 激活函数实际上就是逻辑回归函数的泛化,或者说,逻辑回归函数是 Softmax 函数的特殊情况。
公式表达:

从图中可以得知,Softmax 函数的公式为:
所有可能的预测值相加起来和为 1.
在二分类的情况下,N 的值为 2,j 的取值为 1 和 2。当 j = 1 时, ,当 j = 2 时, 。吴佬说,使用这个公式计算出来的结果最终会和 sigmoid 函数计算出来的结果一致。
成本函数:

成本函数的计算公式如下:
Softmax 函数的输出

将 Softmax 函数作为神经层的激活函数时,每一个神经元的输出都与其他神经元的输出相关,这是 Softmax 函数与其他激活函数的不同之处。

Softmax 的示例代码如上,其中 SparseCategoricalCrossentroupy 函数指的是 稀疏分类交叉熵 损失函数,称为稀疏是因为每次训练只从众多结果中选择一个,虽然上面的示例代码与前面的训练模板是一致的,但是吴佬指出上面的代码并不推荐使用,Tensorflow 中还有实现该代码的更优代码。
用于多分类问题网络的改进
问题引入:

对于多分类问题,我们会使用 Softmax 作为最终的激活函数,但是 Softmax 作为激活函数也会有一个问题——精度。首先我们来看下面两个算式:

很明显,第一个算式的结果相较于第二个结果并不精确。原因是第一次结果使用了一次四舍五入,而第二次结果是在进行四舍五入后相减,这种方式可以达到更高的精度。

现在我们将 Softmax 的计算过程与上面的两个算式进行类比,我们之前计算 loss 的方法是先计算 a,然后将 a 代入 交叉熵损失函数 中计算 loss;另一种可行的算法是 不使用中间变量 a,直接将逻辑回归函数与交叉熵损失函数结合计算 loss。很明显,第二种方式与上面的第二个算式是相似的,因此可以获得更高的精度。
问题的解决:
于是,为了获得更高的精度,在代码中,我们需要将最后一个输出层的激活函数设置为 
‘linear’
,同时在模型的编译过程中,选择二进制交叉熵损失函数时设置参数 from_logits = True
。这样的设置就可以实现图中第二种方式的效果。
对于多分类问题也是如此,只不过损失函数由
BinaryCrossEntropy → SparseCrossEntropy
而已。完整的训练流程:

为了实现更高的精度,有两个地方需要改动,第一个是最后的输出层需要改为 ‘linear’ 同时 from_logists=True ,第二个是将原来的预测由一步变成了两步。
在原来定义的模型中,最后的输出层是 线性层,由此经过 model(X) 得到的预测值 logits 是 的激活值,但这个激活值并不是最终的概率,还要再将 输入到 softmax 激活函数中进行多分类计算得出最终的结果(概率)。
这个过程相当于是将原来的 Softmax 函数剥离了训练模型的神经网络,在得到更高的精度后将再其输入到 Softmax 函数计算概率。
多标签分类与多分类问题
多类分类(Multi-Class Classification)和多标签分类(Multi-Label Classification)是机器学习中的两种不同的分类任务,虽然它们看起来相似,但实际上有着显著的区别。
多类分类(Multi-Class Classification)
- 定义:在多类分类任务中,每个样本只属于多个类别中的一个。例如,给定一个图像分类任务,图像可能属于“猫”、“狗”或“鸟”中的一个。
- 特征:
- 每个样本只能被分配到一个类别。
- 类别是互斥的(一个样本不能同时属于多个类别)。
- 常见应用包括:手写数字识别(每个图片是0到9之间的一个数字),图像分类(每张图像属于一种动物)。
- 举例:假设你有一个动物分类任务,类别包括“猫”、“狗”和“鸟”。一张图片只能被分类为“猫”或“狗”或“鸟”中的一个。
多标签分类(Multi-Label Classification)
- 定义:在多标签分类任务中,每个样本可以同时属于多个类别。例如,一张图片可能同时包含“猫”和“狗”,因此属于这两个标签。

- 特征:
- 每个样本可以被分配到多个类别。
- 类别不是互斥的(一个样本可以属于多个类别)。
- 常见应用包括:文本分类(如一篇新闻可能属于“政治”和“经济”两个类别),图像标签(如一张图片中同时包含“树”和“山”)。
- 举例:假设你有一个图像识别任务,标签包括“树”、“山”和“河”。一张图片可能同时包含“树”和“山”,所以该图片的标签是“树”和“山”。
主要区别
- 类别数量:多类分类中每个样本只能对应一个类别,而多标签分类中每个样本可以对应多个类别。
- 目标输出:在多类分类中,输出通常是一个独热编码(one-hot encoding)向量,而在多标签分类中,输出通常是一个多热编码(multi-hot encoding)向量。
在代码中的应用
- 多类分类:使用
SparseCategoricalCrossentropy
作为损失函数,输出层一般用softmax
。
- 多标签分类:使用
BinaryCrossentropy
作为损失函数,输出层一般用sigmoid
。

如上图所示,多标签分类中输出层可以使用多个激活函数。
高级优化算法

梯度下降是很好的更新参数的方法,但是,我们常常无法判断自己的学习率是否合适,并在学习过程中动态调整我们的学习率。当我们的学习率比较小的时候,如果梯度总是朝着一个方向的,我们希望学习率能够大一些;当我们的学习率比较大的时候,我们梯度变化大,难以收敛到最小值,我们又希望学习率能够小一些。

为了实现上面的效果,我们可以使用 Adam 优化算法,即 自适应矩估计(吴佬说这个名字也只是作者的一种称呼而已)。在训练模型的时候,如果我们指定自己的优化算法是 Adam,那么模型就会自动调整学习率的大小。

其中 Adam(learning_rate=1e-3) 指的是初始学习率设置为 。
卷积神经网络的简单介绍
卷积神经网络又称 CNN,即 Convolution Neural Network.

上述的卷积层中,与先前的隐藏层和输出层(全连接层)不同,每个神经元都只查看输入图像的某个区域,而非将所有的输入都赋予不同的权重和偏置然后进行计算。这样的好处就是可以得到更快的计算速度、只需要更少的训练数据,并且不太可能会发生过拟合的现象。
为什么卷积层只需要更少的训练数据且不太可能发生过拟合的现象?
参数共享(Parameter Sharing)
- 定义:在卷积层中,卷积核(神经元的组合,也称为过滤器)在整个输入图像上滑动,并与局部区域进行卷积操作。这个卷积核在图像的不同区域使用相同的一组参数。
- 效果:这种参数共享大大减少了模型中的参数数量。例如,在一个 3x3 的卷积核中,无论输入图像的大小如何,该卷积核只需要学习 9 个参数(加上一个偏置),而不是像全连接层那样为每一个输入像素都分配一个独立的权重。
- 好处:参数共享减少了模型的复杂度,使得即使数据量不大,模型也不容易发生过拟合。
局部连接(Local Connectivity)
- 定义:卷积层中的每个神经元只连接到输入的局部区域(称为感受野),而不是像全连接层那样连接到所有的输入。
- 效果:局部连接假设图像中的局部特征是最重要的,并且这些局部特征是可以在整个图像中重复出现的。
- 好处:这种连接方式减少了参数的数量,也减少了对训练数据的需求,并且能够捕捉图像中的局部模式,如边缘、角落等。
稀疏连接(Sparse Interaction)
- 定义:卷积核只作用于输入的一个小区域,这种稀疏连接与全连接层不同,全连接层中每个输出神经元与输入的所有神经元都有连接。
- 效果:这种稀疏性使得卷积层能够专注于输入数据的局部模式,而不是整个输入的每一个细节。
- 好处:这种稀疏性同样减少了过拟合的风险,因为模型不容易记住训练数据的每一个细节,而是更关注数据中的重要特征。
平移不变性(Translation Invariance)
- 定义:由于卷积核在图像上滑动,卷积层可以识别无论位置如何相似的特征,例如,一个边缘在图像的不同位置都可以被检测到。
- 效果:这种平移不变性使得卷积层可以更有效地处理图像中的常见模式,即使这些模式在不同的位置出现。
- 好处:这种特性使得模型不需要学习大量位置不同但特征相似的数据,减少了数据需求,同时提高了模型的泛化能力。
数据增强(Data Augmentation)
- 定义:在训练卷积神经网络时,通常会进行数据增强操作,如旋转、缩放、平移等,来增加数据的多样性。
- 效果:数据增强增加了数据集的有效大小,从而减少了过拟合的可能性。
- 好处:即使原始数据量较少,通过数据增强,卷积层依然能够学习到有效的特征,避免过拟合。

拿心率的训练例子来说,以上是卷积神经网络的运行过程,无论是输入,还是在中间层中,都是将局部的数据输入给每个神经元进行训练的。
反向传播算法
反向传播算法是在前向传播的基础上计算导数值的一种方法,这种方法相比于直接对每个参数求导效率更高,在大型神经网络中更常使用。

反向传播算法就是途中绿色部分的过程。
首先,我们需要通过前向传播,计算出图中箭头上的黑色的值,得到最终的 J 以后,我们先将 ,得出 J 的值为 ,J 的增量为 d 的两倍,因此可以得出 ( 即 )。
这就是计算机内部利用前向传播的值计算上一步梯度的方式,按照这样的方式(如图所示),可以得到 J 对每一个中间变量的偏导数,直到得到对权重和偏置的偏导。
结合吴佬的讲解,我认为反向传播的具体过程是下面这种形式(具体的过程可以通过微积分中的链式法则来理解):
第一步, ,;
第二步, ,;
第三步,,;
第四步,,;
第五步,, 。
相比于将每一个参数的值代入到式子中计算,反向传播所需要的计算次数远远小于直接对每个参数求导所需要的计算次数,特别是在网络的参数数量特别多的时候。

如果直接求导,需要进行的计算次数为 NxP 次,而采用反向传播的方式只需要 N+P 次。
因此,反向传播算法的效率是更高的。
神经网络(模型评估)
💡 Key Words
这里的关键词只是帮助大家看完右侧的笔记后回忆内容,不是跳转链接!!
- 测试集误差
- 交叉验证集(dev set)
- 验证集误差
- 方差和偏差
- 高方差 → 过拟合
- 高偏差 → 欠拟合
- 评估标准(baseline)
- 机器学习算法开发的迭代过程
- 错误分析
- 数据增强 数据合成
- 迁移学习
- 机器学习的完整周期
- 倾斜数据集 精确率和召回率
- F1 score
🔗 Relevant Information
📝 Class Notes
模型评估引入
测试集:

为了让模型具有更好的泛化性能,我们需要对模型进行评估。
模型评估一般会在测试集中进行,通常将原来的 Dataset 按照 7:3 或是 8:2 的比例分为训练集和测试集。
训练集和测试集中数据的标注如图中右侧所示。
训练集误差和测试集误差:

测试集分数和训练集分数:

模型的选择

如果让我们根据幂次不同的多项式来训练模型参数,从中选择一个最好的模型,很自然的,我们会想到给所有这些多项式排列,然后按照顺序挨个进行模型的训练,然后再挨个计算模型在测试集上的成本函数,从这些成本函数中选择一个误差最小的作为最终的拟合模型。
但是这样会有一个问题。在我们决定这么做的时候,实际上就已经多增加了一个参数 d ,从所有的多项式中选择误差最小的,换一种说法就是将所有模型的训练误差作为数据,训练出一个可以使成本最小的参数 d。而这个新引入的参数 d 显然没有测试集进行验证,因此通过这种方式选出来的模型泛化性能未必会很好。
那么,如何改进呢?

如图所示,在拆分数据集的时候多拆分一部分作为交叉验证集(cross validation),交叉验证集在英文中又可以写做 validation set, development set, dev set 等。
引入交叉验证集后,模型的训练步骤就变为 ① 确定候选方案;② 通过训练集对逐个候选方案进行训练,得到最优参数; ③ 利用 dev set 对不同方案进行评估,得到不同的误差结果;④ 从 dev set 中选择误差最小的一项,利用 test set 进行测试,得到测试误差,这个测试误差就可以作为这一轮训练的评估分数。
验证集误差的计算:

引入正则化项后误差的计算:

在引入正则化项后,正则化项的存在也会影响最终误差的大小,当正则化项很小的时候,模型与之前是一样的,当正则化项很大的时候,计算出来的权重会趋近于 0,最后得出的图像近似一条不随数据变化的直线。因此我们也要通过计算 的大小来选择合适的参数 λ。
方差和偏差

在模型处于欠拟合状态的时候,它具有很高的训练误差以及验证误差;
在模型处于过拟合状态的时候,虽然训练误差很低,但是验证误差却很高;
只有在模型处于合适的状态的时候,它的训练误差和验证误差才都会很低。

欠拟合意味着高的偏差,过拟合意味着高的方差。偏差表示了模型的拟合能力,即数据与拟合曲线之间的偏离程度,而方差表示了模型的泛化能力,方差高表示模型不稳定,对某些数据集的预测准确(如训练集),对其他数据集的预测具有高误差。同时,验证误差 只有在模型参数最合适的时候(即处于欠拟合和过拟合的中间点)的时候才会更小。
引入正则化项后偏差和方差的变化:

在引入正则化项之前,多项式的维度会影响数据的偏差和方差,在维度比较低的时候,可能会具有高的偏差,在维度比较高的时候,数据的方差也会比较高。
引入正则化项后,正则化系数 λ 的值对于结果的影响也比较大,同时可以发现,与维度引起的变化是相反的。在数据充分进行训练的前提下(进行非常多轮的训练),当正则化系数比较小的时候,如没有正则化系数的存在,那么数据的验证误差会比较大,但是由于没有正则化系数约束权重,因此容易发生过拟合,训练误差比较小;当正则化系数比较大的时候,如趋于无穷,那么数据的验证误差也会比较大,因为正则化系数对权重的压制大,最终计算出来的权重趋近于 0,所以训练误差也会比较大。
只有在正则化系数处于中间的某个值时,验证误差才会比较小。
在上面的分析中,无论是否有正则化项存在,我们都可以发现,在 与 相差比较大的时候,可以说训练的过程是具有比较高的方差的;在 与 比较接近且都比较高的时候,可以说训练过程是具有比较高的偏差的。
用于性能评估的一个基准

性能评估的基准可以是与人类在某些方面的表现水平为基准,如人类辨识图像的能力;也可以是与其他已有的算法或是其他公司的表现为基准,也可以是自己的各人经历
选择好一个合适的基准线有利于更好的评估模型的表现,如下图所示:

当模型的训练误差与基准线相比比较接近,而测试误差与训练误差相比较大时,模型就具有高的方差,即过拟合;
当模型的训练误差和测试误差接近且都和基准误差相差较大时,就具有高的偏差,即欠拟合;
当模型的训练误差和测试误差都与基准误差相差比较大,且训练误差和测试误差也相差比较大时,可以说模型的训练效果是非常不好的,既具有高的偏差,又具有高的方差。
增加数据点对高偏差和高方差模型的影响

对于一个具有高偏差的模型来说,它的模型一般是比较简单的,在训练集和验证集上都具有比较高的误差,由于模型比较简单,高偏差的模型一般无法通过增加数据点来使模型更加精确,因为它的上限已经确定了。
从图中可以看出,随着训练样例数量的增加,模型的训练误差和验证误差都远远超过了基准误差。

而对于一个具有高方差的模型而言,由于模型过于复杂,大材小用,将复杂的模型去拟合少量的数据,那么多余的权重就只能用于学习模型中我们并不需要的性能,如噪声等来减小误差。随着样例数量的增多,复杂的模型对于大量的数据能够充分发挥自己的能力,并且难以将每个数据的特点都考虑其中,甚至有些应接不暇,因此不容易发生过拟合。
从图中可以看出,随着训练样例数量的增加,模型的训练误差和测试误差都与基准误差接近。
因此,高方差的数据可以通过增加数据量的方式来提高模型性能。
在实际问题中,方差问题往往比偏差问题多,因此基本都可以通过增加数据量的方式来解决。
如何应对高偏差和高方差的模型

在引入正则化项后,如果一个模型出现了比较大的误差,通常有以上的解决方法,对于误差的类型可以针对性的选择上图所示的方式进行解决。
在遇到高偏差或是高方差的模型的时候,我们总是习惯于在高偏差和高方差之间寻找一个折中的选择(trade-off)作为最佳的模型。

随着神经网络的不断应用,高偏差和高方差的问题都可以通过神经网络的不断迭代来解决。首先,将数据输入神经网络中进行训练,训练好后计算模型的训练误差,如果训练误差相对于基准误差过大就继续训练模型,增加隐藏层的数量,让模型变得复杂,直到模型的训练误差减小;当模型的训练误差减小时,继续计算模型的验证误差,如果验证误差过大,则增加更多的数据进行训练(如果可能),不断循环迭代上述的过程,直到训练误差和测试误差都接近基准误差或是比基准误差还小。
由于模型经过第一个循环的时候,神经网络结构都已经比较复杂了,因此,大型的神经网络往往面临的都是高方差的问题而非高偏差的问题,因为相比于增加模型复杂度,增加更多数据的难度更高。

只要正则化系数选择的足够恰当,那么大型的神经网络的效果会比小型神经网络的效果相当甚至更好,换一种说法,只要正则化系数选择的好,选择更大的神经网络没有什么坏处。
机器学习开发的迭代

首先我们要选择一个合适的模型,训练的数据,确定好模型的超参数(学习率、正则化系数等);
然后开始训练模型;
模型训练好后,我们需要对模型进行诊断,计算模型的训练误差和测试误差,判断模型是否具有高偏差或高方差,然后再重新回到第一步调整模型,决定是否扩大模型,更改超参数,加入训练数据等。
以构建一个垃圾邮件筛选器为例:

我们可以使用监督学习来做到垃圾邮件的筛选,方法是收集邮件的不同特征(邮件主题、邮件内容、邮件发送者等等)作为输入向量 ,使用 0 或 1 来表示是否为垃圾邮件,也就是一个二分类问题。
构建了模型以后,我们就需要提高模型的精度,主要有以下这些方法:

① 收集更多的数据;
② 开发基于电子邮件路由(从电子邮件标题)的复杂功能;
电子邮件路由指的是计算服务的顺序,通过对电子路由的分析,我们可以得知电子邮件是通过何种方式到达你的邮箱的,进而判断它是否由垃圾邮件发送者所发。
③ 根据邮件主题内容定义更加复杂的功能;
④ 设计侦测错误拼写的算法。
错误分析
错误分析是吴佬认为的仅次于高偏差和高方差的应该被关注的一个指标。
通过错误分析,我们可以清楚地认识到我们的模型是否是要在某些方面放入更多的精力,分清主要问题和次要问题,还是以下面的垃圾邮件分类为例:

当我们有 500 个样本示例的时候,算法可能会在 100 个示例上进行错误的分类,我们可以看到,图中的 pharma(药物)类别和 steal passwords(网络钓鱼)类别是所有类别中占比最大的,这就意味着我们该花更多的时间在优化这两个类别的分类上。如果花费大量的经历在分析邮件中的错误拼写,会大大降低团队的效率。
针对占比大的类别,我们要增加更多相关的数据并发掘更多的相关特征。
获取更多的数据
获取更多的数据通常有以下两种方式——数据增强和数据合成。
数据增强:



数据增强指的是将数据经过各种变化来生成新的训练数据,可以像图中一样将图像进行旋转、放大、镜像等操作;

当然,还可以对数据进行扭曲变换来产生新的图像。

对图像进行数据增强的时候,在训练集中引入测试集以及现实世界中更可能出现的失真类型更有助于模型性能的提升,增强模型的泛化能力。在我看来,在数据中加入扭曲失真会增强模型的特征数量,增强模型的复杂度。
而与此相比,单纯的对图像的像素施加随机噪声干扰对增强模型性能的帮助并不是非常大,因为这只会让图像本身变得模糊一点,不会增加模型的复杂度,这样的训练会变得毫无意义。
数据合成:

数据合成指的是人工创建一个新的训练样例,不是通过修改已有样例的某些特征。
拿 OCR 识别的例子来说,我们希望能够提高 OCR 识别的准确性,那么我们的目的就是要创建各式各样的图像供模型训练,我们要将 OCR 用于现实世界中的文字识别,那么我们创建的数据集就要更加贴近现实世界中可能会出现的数据。

上图的左侧是对现实世界图片进行截取得到的数据集,而上图的右侧来自电脑的记事本。
记事本中有各式各样的字体,我们可以通过截图将这些字体截出来,并对这些字体进行翻转或其他操作让字体变得复杂,通过改变字体的颜色、增加对比度等操作来改变字体的样式,并将更改后的数据加入训练集。
我们可以发现,通过亲手对文字进行操作后得到的合成数据集与来自现实世界中的数据集非常的相似,说明它可以用于模型的训练,并且训练出来的模型可以为 OCR 服务。
更多有关数据合成的方式
- GAN(生成对抗网络):通过生成器网络合成与真实数据分布相似的样本。
- 合成图像或文本:利用现有素材,通过组合、编辑等手段生成新的数据。
- 仿真器:利用物理引擎或规则系统生成模拟数据,例如在自动驾驶中使用虚拟环境生成的道路场景。

在过去,工程师们以提高模型算法为中心,不断钻研各种代码来增强人工智能;
在现在,随着模型、算法越来越丰富,我们可以花费更多的时间在寻找更多数据身上。
迁移学习

为了引入迁移学习,我们首先举下面这个例子:
假设有一个数据集有 10 万个数据以及 1000 个类别,我们通过各种尝试得到了一个模型(如上图的第一个模型),它有 4 个隐藏层和一个输出层,输出层有 1000 个神经元(也就是说,对于每一个样例来说,输出的是一个大小为 1000 的张量,表示各个类别的预测概率),但这通常会花费大量的时间和算力。
现在,我们将先前的模型搬移下来,将原来的输出层替换,构建一个新的神经网络模型,它用于预测同类型其他标签的数据,同时也能发挥很好的效果,这个过程就是迁移学习。
在保持隐藏层不变的前提下,对于新的模型,我们有两个选择:
① 仅训练输出层的参数(通常是全连接层);
② 训练所有的模型参数(只借用模型的神经网络架构)。
在训练集特别小的时候或是我们想要得到的目标模型没有特别多的数据可用时,选项 1 是一个不错的选择,在训练集样例数量特别大的时候,建议使用选项 2.
选项 1 冻结了隐藏层的所有参数,只能改变输出层的权重;而选项 2 是在预训练模型的基础上,微调整个模型,对所有的神经层的参数进行训练,但在训练过程中保留预训练模型的初始化权重。其中预训练模型的初始化权重是指在预训练阶段使用该数据集训练得到的最优参数。
使用方法一的优点:
- 计算成本低:因为只需训练少量参数(输出层),计算开销较小。
- 数据需求低:这种方法在目标任务数据量较少的情况下表现很好,因为大部分特征提取已经在预训练模型中完成了。
- 收敛速度快:由于大部分权重已经从预训练模型中获得,因此模型能够较快地收敛。
使用方法二的优缺点:
优点:
- 灵活性高:通过训练所有参数,模型能够更好地适应目标任务,特别是当目标任务和源任务之间差异较大时。
- 性能提升:对于数据量较大的任务,训练所有参数可能带来显著的性能提升。
缺点:
- 计算成本高:因为要训练整个模型,计算成本和时间都较高。
- 需要更多数据:微调整个模型通常需要更多的目标任务数据,以避免过拟合。
为什么迁移学习是可行的呢?

在隐藏层的训练过程中,第一层通常会去侦测图像的边缘,在第二层的时候,更多的会去关注图像的角落,在第三层的时候会去检测一些基本的曲线和形状。
由于隐藏层训练过程中的这些特征对于同一类型的输入而言,基本上也都会是相似的,因此迁移学习是可行的。
迁移学习的主要步骤(总结):

- 下载与我们的应用程序输入类型(如图像、音频、文本)相同的大型数据集上预训练的神经网络参数(或自行训练)。
- 在自己的数据集上进一步训练(微调)网络。
机器学习训练的完整周期
整体架构:

首先,我们要确定自己的项目,明白自己的项目是要实现什么样的目标;
其次,收集数据,我们要根据自己的项目定义并收集数据,我们可以根据数据增强或是数据合成等方式获取更多的数据;
然后我们就可以将数据输入模型进行训练了,在训练过程中,我们需要对训练结果进行训练误差、验证误差和测试误差的测试,同时也要进行错误分析,增加特定的数据用于模型的训练,不断重复这个过程,直到得到一个我们能接受的训练误差、验证误差和错误分析的结果;
最后一步就是要在实际中进行部署,但也不能忘记继续对系统进行监测和维护,因为有时候现实中的数据更新迭代速度比较快,需要及时更新模型以提高性能(与时俱进)。如果模型的性能并非那么理想,那么就需要收集更多针对性的数据用于训练。
部署部分的具体介绍:

首先,有一个用于预测的服务器和一个移动设备,server 内部有训练好的模型;
移动设备通过 API 与 server 通信,并将数据(如语音片段)通过 API 发送给 server,server 进行预测后会将预测结果 的抄写发回给移动设备。
上述的中心服务器需要根据规模的大小进行相应的软件工程设计,软件工程设计要保证以下几点:
- 确保预测的有效性和效率;
- 确保程序的扩展性;
有关扩展性的更多介绍
扩展性(Scaling)在机器学习系统中指的是系统能够处理不断增长的工作负载或数据量的能力。随着数据量、用户数量或计算需求的增加,机器学习系统需要能够保持性能,并且能够灵活地扩展以满足需求。
- 程序要有相应的日志记录,便于后期调试;
- 监测系统模型的变化;
当模型的预测发生错误的时候,这会使我们能够弄清楚数据何时发生变化以及算法何时变得不那么准确。
- 模型更新;
在模型的错误及时更正后,要能够对模型及时地进行更新。
上面的五点统称为 MLOps(机器学习操作),它们旨在保证机器学习模型在生产环境中能够稳定、高效地运行。
公平、伦理和道德
在提出机器学习的项目的时候,我们必须确保自己的项目是符合道德规范的,在不侵犯他人权益的前提下构建和训练模型,这样的模型才是真正会对社会有贡献的。
吴佬针对这一块提出了几个建议,并不是说做到以下几点一定不会有伦理问题出现,但是至少能够极大避免这些问题。

- 在团队中进行头脑风暴构想项目在未来可能会造成的损害;
- 进行有关行业标准/指南的文献检索;
- 在部署模型前对系统进行审计,判断它是否会有类似于性别歧视这样的问题出现;
- 为项目模型提前做好缓解计划,构想问题并在问题真正发生后制定好能够应对的解决方案。
倾斜数据集的误差指标(精确率和召回率)

在介绍倾斜数据集的概念之前,首先介绍上面这个例子:
对于一个疾病分类的问题而言,我们先构建了一个分类器,这个分类器有 99% 的概率预测正确,但是有 1% 的概率预测错误;
同时,我们也得知在现实生活中,疾病发生的概率是 0.5% 。
那么现在,我们做这样一件事,我们将分类器内部的模型替换为一个只输出一种结果的语句
print("y=0")
,与原先的分类器相比,新的分类器预测错误的概率仅仅只有 0.5%,相比于原先分类器预测错误的概率,似乎只输出一种语句的算法更优?但聪明的我们都明白,只输出一种结果的语句没有存在的意义,因为它无法真正改善我们的生活,那么为什么会如此呢?这便不得不提到倾斜数据集了。
倾斜数据集(Imbalanced Dataset):
倾斜数据集指的是类别分布不均衡的数据集,通常出现在分类任务中。其中,某一类别的数据点数量远远多于其他类别。这种不平衡会导致模型倾向于过度关注数量较多的类别,忽略数量较少的类别,进而影响分类模型的性能。
假设你在做一个二分类任务,用来识别是否有欺诈行为。在数据集中,可能 95% 的交易都是正常的,只有 5% 的交易是欺诈的,这种情况下,数据集是倾斜的。如果模型直接对所有数据点预测为“正常”,它的准确率(accuracy)可能很高(95%),但实际上它完全没有识别出欺诈行为,性能不佳。
混淆矩阵:
在定义精确率和召回率的概念的时候,有必要先定义混淆矩阵:
预测结果\真实情况 | 正例 | 反例 |
正例 | 真正例(TP) | 真反例(FN) |
反例 | 假正例(FP) | 真反例(TN) |

精确率(Precision):
精确率衡量的是模型在所有预测为正类的样本中,实际正确的比例。它回答了这样一个问题:模型预测为正的样本中,有多少是正确的?
召回率(Recall):
召回率衡量的是模型在所有实际为正类的样本中,正确预测出的比例。它回答了这样一个问题:模型能找出多少真实的正类样本?
精确率更多的是表示模型预测的准确性(预测正确的样本中有多少是真正准确的),而召回率更多的是表示我们需要检测的数据被测到的概率(对于一个病人,模型给出它有病的结论的概率)。
也就是说,精确率是针对整个数据集而言的,这个数据集有正例,有反例,精确率能够让我们知道模型在数据集上的表现如何,对于所有的数据有多大把握是预测正确的;
而对于召回率,可以理解为识别率,是我们将数据集中所有的正例都取出来,让模型对它进行预测,看它是否有将其分辨出来的能力,这个指标是为了应付开头的那个问题而产生的。对于开头的那个问题,如果对新的分类器进行召回率的计算,那么它的识别率为 0,而对于原先的分类器而言却不是如此。
这里需要注意,精确率和准确率不是一个东西,准确率指的是所有预测正确的样本除以所有的样本数量,预测正确的样本既包括真正例,也包括真反例。
如何权衡精确率和召回率呢?

当我们使用逻辑回归函数来进行预测时,我们常常希望能够有很高的精确率,这样,我们才更有自信能够说出一个人是有病的,但是这样又往往会让病情不那么严重的人被我们认为是健康的;
同样地,当我们希望自己在作出诊断的时候,无论他的病情严重与否,我们都可以判断出来并对症下药的时候,往往又容易出现人是健康的却被判断为有病,精确度降低了。
精确率与召回率之间,似乎永远是呈反比关系的(具体分析参见 ),因此,我们要有一个指标对精确率和召回率进行权衡。
用于对精确率和召回率进行权衡的指标就是 F1 score:

由于 P 和 R 是呈反比的关系,因此我们不希望 p 过大或是 R 过大,因为这都不利于模型的泛化,我们希望数据能像图中算法 1 一样处于一个折中值。如果我们仅仅通过平均算法将 P 和 R 平均来选择模型,那么这三组算法的数据都是相近的,我们无法达到想要的目的,但是如果通过图中的公式(也称为调和平均值)来进行计算,就能够明显看出三组算法的不同了。
在调和平均计算下,数据将会更偏向于小的一方,因此分数更高的一组算法能够有更好的应用。
❓ My Doubts
✔️ Question 1
My Question is:
为什么精确率和召回率之间就是反比的关系呢?
为什么我们提高模型的精确率,让我们更能判断一个病人是有病的,反而会让病情轻微的病人逃过一劫呢?
My Answer:
- 精确率的重点:减少误报(FP)
精确率的目标是确保模型预测为正类的样本尽可能准确。要提高精确率,模型倾向于在预测正类时更加“谨慎”,只在极为确定的情况下才做出正类预测。这样可以减少误报(False Positives, FP),但可能会导致漏掉一些真实的正类(False Negatives, FN)。
举个例子,在垃圾邮件检测中,模型为了不误将正常邮件误判为垃圾邮件,可能只标记那些特别明显的垃圾邮件。这时,精确率很高,但一些不太明显的垃圾邮件可能会漏掉,从而降低召回率。
- 召回率的重点:减少漏报(FN)
召回率关注的是尽可能识别出所有的正类样本。要提高召回率,模型可能会更“激进”地将可疑样本标记为正类,这样可以捕捉到更多的正类,减少漏报(False Negatives, FN),但这也可能导致更多的误报(False Positives, FP)。
举个例子:同样在垃圾邮件检测中,为了不漏掉任何垃圾邮件,模型可能会把一些正常邮件也误判为垃圾邮件。这种情况下,召回率很高,但精确率会下降。
神经网络(决策树)
💡 Key Words
这里的关键词只是帮助大家看完右侧的笔记后回忆内容,不是跳转链接!!
- 决策树
- 分类标准 结束标准
- 纯度的计算(熵)
- 信息增益
- 独热编码
- 回归树
- 随机森林
- XGBoost
🔗 Relevant Information
📝 Class Notes
决策树的概念

拿“猫猫识别器”的例子来说,我们可以简单的通过耳朵形状、脸型、是否有胡须来判断动物是否为猫。
我们可以将特征的比对根据特征数量分为多步,每一步判断一个特征值,形成下面的形式:

由于科学家们认为上面的图像树一样分布(可能得倒过来看才有点像),因此将它称作是“决策树”。
在决策树中,所有的节点都是从根节点出发引出来的,具有输出的节点称为是决策节点,只有输出没有输入的是根节点,只有输入的是叶节点。
决策树的构建过程
决策树的分类标准及结束标准
分类标准:

决策树可以帮助我们有条理的对一个数据进行分类,那么,我们如何设置数据节点的分类标准呢?
我们可以将数据根据不同的特征进行多次分离,测试分离后的子节点的数据纯度。如果我们是要对找出所有数据中标签为猫的数据,那么我们就需要测试不同分类方法下子节点中猫的纯度,选取猫的纯度最大或是最小的子节点作为本次分离的标准。后面会介绍信息增益作为纯度的判定标准,更加专业。
结束标准:

我们可以根据上面的四种方式来判断分类什么时候结束:
- 当节点能够 100% 区分出一个类别时;
- 当节点达到我们设置的最大分离次数时;
- 当节点每次提高的纯度小于临界值时;
- 当一个节点中样例的数量低于某个临界值时。
如何计算决策树的纯度

课程中,吴佬使用熵来表示不纯度,熵与我们要分类的条目比例呈图中所示的曲线关系,如图中所示,当我们计算出一组动物中猫的占比后,我们就可以通过函数来计算出它的熵。熵代表了混乱程度,在这里指的是不纯度,熵越大,说明数据越不干净。
熵的具体计算公式如下:

图中的公式类似于逻辑回归函数,需要注意的是,在函数中定义 。
拆分信息增益
在决策树中,熵的减小被称为是信息增益。

我们可以分别计算针对三个不同的特征进行分类后决策树各个子叶的熵,最终得到 6 个熵值,可是依据这 6 个熵值进行分类难度还是有一些大,我们需要对这 6 个熵值进行处理,转换为 3 个能够比较的量,这样才能从其中选出最优的。
这里我们采用的是加权平均法(不直接使用平均是因为分离后的两个子叶大小是不相等的,因此需要有权重来平衡),每种分法下,根据每片子叶中样例数量在总样例中的占比计算权重,再对两片子叶计算熵的加权平均得到一个中间量。
其实,根据这个中间量我们已经能够比较出三组分类的优劣了,但习惯上使用信息增益,也就是熵的减小来表示数据,这样,值越大的纯度就越高。
因此,我们可以得到如下关于信息增益的计算式:

整合

首先,从根节点开始向外扩展,我们需要根据所有可能的特征计算信息增益,然后选出最大的一个信息增益作为第一次分离的标准,创建左半子叶和右半子叶并重复上述的过程。
我们需要提前为决策树的分离设置一个结束的标准,具体可以参见 结束标准: 。
上述的过程我们称为是递归分离,在递归分离的时候,我们也要有一个分离的顺序,我们可以先一直分离左半部分的子叶,直到该子叶达到了上面的结束标准,然后再开始分离右半部分的子叶,直到符合要求,不断重复该过程,直到所有的节点都分离完毕。

对于决策树中最大深度的选择,决策树项目中都会有项目者的推荐,我们可以通过经验直觉判断深度的大小,一般 dev set(交叉验证集)越大时,所需要的决策树就越复杂,深度越深。
特征具有多个分类怎么办?——独热编码
在前面,我们通过猫的例子来解释了决策树,我们只使用了三个特征,每个特征都只有两个取值,但是现实生活中例子的特征并不总是只有两个的,现在我们将耳朵的分类变得稍微复杂一些来看看会发生什么:

从图中可以看到,耳朵的分类多了一个 oval 类型的,这意味着 ear shape 这个子节点会有三个不同的分支,这会让原来的决策树算法变得更加复杂,因为当我们判断某个叶节点达到了结束标准的时候,另外两个叶节点还需要进行一次判断。
为了解决这个问题,我们引入了 独热编码?
顾名思义,在这里,独热编码指的是每个特征都只有 1 和 0 两种状态,也就是说只有“有该特征”和“没有该特征”两种分类。
对于具有两个以上分类的特征,我们可以将每个分类都单独拎出来,作为一个独立的特征,在这里就是将 ear shape 这个特征拆分为 pointy ears, floppy ears 和 oval ears 三组,使用 1 和 0 来表示样例是否有这个特征,如下图所示:

在神经网络的训练中,我们也会将其他特征通过 1 和 0 的形式表示出来:

特征的分类是一个连续值怎么办?——回归树
首先,在这里就先分清楚什么是决策树,什么是回归树?
顾名思义,决策树是用于解决分类问题的,将数据集划分为更小的子集,通常采用信息增益作为分类标准;
而回归树是用于解决回归问题的,是决策树的一种变体,用于预测一个连续的数值,通常采用 MSE 作为分类标准,因此,接下来的体重预测问题就是回归树的范畴。
👉🏾 对,你想的没错,简单的说,决策树就是用于预测离散值的,回归树就是用于预测连续值的。
首先,先只引入连续值的特征,但最终的结果仍然是需要对动物进行分类(决策树):

在之前的样例中,我们引入一个新的特征——体重,由于体重是一个连续值,无法通过二分类那样简单的计算出信息增益。
因此,我们对于连续值有另外的处理步骤,我们需要在连续的体重中抽取多个临界值,当体重小于某个临界值时,可以判定动物为猫,当体重大于某个临界值时,判定它不是猫。

选取临界值的方式吴佬没有说,搜索了一下:
临界值的选取方法
在决策树模型中,处理具有连续值的特征时,通常采用 分裂点选择 的方法。具体步骤如下:
- 排序连续特征:首先,将该连续值特征按照从小到大的顺序排序。
- 确定分裂点:在排序后的连续特征值中,选取多个可能的分裂点。通常这些分裂点是两个相邻样本值的平均值。例如,连续特征值的排序结果为
{2, 5, 8, 12}
,那么可选的分裂点为(2+5)/2 = 3.5
,(5+8)/2 = 6.5
,(8+12)/2 = 10
。
- 计算每个分裂点的信息增益:
- 对于每一个分裂点,计算其信息增益(例如使用 信息增益、信息增益率 或 基尼指数)来评估分裂质量。
- 对连续值的特征,信息增益的计算方式与离散值类似,只不过在每一个可能的分裂点处将数据分为两部分:一部分是特征值小于等于分裂点的样本,另一部分是特征值大于分裂点的样本。
- 选择最优分裂点:通过对所有可能的分裂点计算信息增益,选择增益最高的分裂点作为该连续特征的分裂标准。
关键点:
- 处理连续特征时,决策树并不直接使用连续值本身,而是将连续值离散化为二分形式。
- 这一步处理完成后,连续特征就像离散特征一样,可以用于进一步分裂决策树。
在选取好临界值后,就可以把这个特征当作是二分类问题啦!不过需要进行多组临界值的信息增益计算并比较,选取最大的一个作为该节点的分类标准。
现在,让我们将最终需要预测的结果转换为体重(连续值),那么问题就会转换成回归树的问题了。

仍然先将动物通过不同的特征(体重除外)进行分类,如上图所示,总共分为了三组。
然后,我们列出每组动物的体重值大小,计算出每组体重的样本方差(样本方差前面要除以 N-1),接着再将叶子节点的权重值计算出来(图中的 和 ),进行加权平均,将根节点的样本方差减去加权平均后的中间量即可得到用于比较的结果。与信息增益一样,值越大的表示能提供的方差减小量越大,因此需要选择值最大的特征作为分类的标准。
提高决策树的预测准确率——随机森林算法
多决策树策略(引入)
在进行决策树的决策的时候,我们会发现决策树的决策对数据是高度敏感的,在训练样例发生些许变换的时候,构建出的决策树就会不同。因此我们可以通过多个决策树共同预测的方式提高预测的准确性。


举图中的例子,将一个未经过训练的猫的示例输入上面三个不同的决策树中,可以得到两种不同的预测结果,这时我们可以通过投票机制(多数服从少数)来选择合适的预测结果。
随机森林(Random Forest)
随机森林是有放回抽样和多决策树结合起来构建的一种算法。
有放回抽样就是我们在初中数学中学到的抽样方法,它指的是我们随机从一个容器中抽取一个样本,记录后放回,重新再抽取一个,重复上述过程。最终我们可以得到一个新的数据集。

通过有放回抽样的方式,我们可以构建出不同的数据集,当然,数据集中的数据也许会有重复,但是并不影响最终的结果。
B 的值推荐范围在 64~128 ,不建议 B 的值过大,因为研究证明, B 越大训练效果反而不好,而且还会降低训练的速度。
上述的循环过程是袋装决策树(Bag Decision Tree)的构建过程,这也是公式中 b 和 B 的符号来源。
在袋装决策树的构建过程中,大多会遇到决策树的根节点拆分使用的相同特征的情况(上图是一个例外,只改变了几个样例就产生了两种不同的根节点分类)。
在构建决策树时,算法需要在每个节点上选择一个特征来进行划分。在随机森林中,并不是从所有可用的特征中选择一个特征,而是从一个随机选择的特征子集中进行选择,这一过程称为“特征随机化”。这个子集的大小由公式 决定,其中 n 是总特征数, k 是随机选择的特征子集的大小。
在随机森林算法中,每个节点都会单独选择一个随机的特征子集。也就是说,随机子集并不是在决策树构建一开始就固定的,而是在构建决策树的过程中每个节点都要独立随机选择一个新的特征子集,然后从这个子集中挑选出最优特征进行划分。
当然,在这样的选择方式下,不同的节点可能会选中同一个特征子集,甚至选择同一个特征进行分类,但这样的选择比较少,并不会影响模型的最终表现。相比于随即子集带来的好处,选择同一个特征带来的选择无效性不算是什么。了解了上述的过程后,就可以写出随机森林的伪代码了:
随机子集可以缩减参数范围,将参数范围缩小有助于避免过拟合。而且也不用担心某些关键参数没被选上,因为某些特征即便在这个决策树没选上,还有下一个决策树能遇上。
XGBoost(eXtreme Gradient Boosting)
以 梯度提升树 为框架,通过逐步添加新的决策树来修正前一棵树的误差,从而提高模型的预测能力。用弹幕中大家的说法更容易理解——错题集。
XGBoost 算法相当于是错题集的提升版,在第一遍运行完数据后,选出数据中预测错误的数据,提升这些数据被选中的概率,然后再开始下一轮的运行。由于模型训练时更可能会遇到训练错误的数据,因此相当于是一遍遍的做错题然后修正模型,避免下次犯错。
那么,相信大家都跟我一样有这样的一个疑惑,如果每次训练都更可能使用那些运行错误的数据,长此以往不会更容易导致过拟合吗?
因此,模型引入了正则化项,通过正则化项控制了模型的复杂度(对子节点权重进行惩罚),避免模型过拟合。
那 XGBoost 既然是一次次的修正模型,那么自然,就是在一个决策树上多次进行模型训练,每一棵新的树都尝试修正之前所有树的预测误差,最终的模型是这些树的累加。由于是使用之前预测错误的数据进行训练,那么就需要设置一个误差判定的方法——损失函数,并对每个子节点设置权重,以便更好的改进模型。
XGBoost 支持多种损失函数,包括均方误差(MSE)、逻辑回归损失函数等。可以根据具体的任务选择不同的损失函数。如果选用的是均方误差作为损失函数,可以将每个子节点的预测值与目标值代入损失函数中,将每个子节点看作是一个数据输入,每个子节点都会被赋予一个权重,参与到损失函数的计算中,最小化损失函数。加入正则化项就是为了惩罚权重,避免过拟合。
在下一轮训练中,又会选择一批新的数据集(当然会包含很多错误预测的数据),再进行训练,再计算损失函数 ….
XGBoost 内部的具体实现非常复杂,上面的过程也只是简述,省略了很多细节,因此大部分人都会选择调用 XGBoost 这个开源库进行模型的训练。
决策树总结

决策树适用于处理结构化的数据,例如存储在 excel 表格中的数据,包括连续数据和离散数据。
对于图像数据、音频数据和文本数据,则更推荐使用神经网络的算法进行模型的构建。
当然,神经网络是也能够处理结构化的数据,但是速度肯定不如决策树来得快。有句话怎么说来着,术业有专攻嘛🤔!!
但是神经网络的优势更体现在它的普适性,不仅可以适用于绝大多数的场合,而且也能够和迁移学习之类的学习协同工作。
非监督学习
💡 Key Words
- k-means 算法(分配、移动)
- 成本函数 ↔ 方差
- 初始化 k-means 算法
- 聚类数量 K 的确定(肘法)
- 异常检测
- 密度评估
- 算法(各个特征的概率)
- 异常检测系统的评估
- 异常检测与监督学习
- 特征的调整
🔗 Relevant Information
📝 Class Notes
引入
聚类算法指的是将可能具有相似特征的对象都集中在一起,比如在每日新闻中,将不同的新闻依据特征分为不同的类别。
k-means 算法
k-means 算法只会重复的做两件事 —— ① 分配数据点所属集群;② 移动簇质心。
简单例子引入:
为了引入聚类算法的具体过程,这里先讨论只有两个集群的分类(对于具体设立几个集群在后面有讨论):


因为只有两个集群,所以目前就只先选取两个集群中心点。在还没有进行分类的时候(算法刚开始),我们可以随机分配集群点的位置,但集群点不能重合。在随机选取两个集群点(用”红色“和”蓝色“表示)后,我们需要一一判定各个数据点离集群点的位置,将离红色集群点位置近的数据标为红色,将离蓝色集群点位置近的数据标为蓝色,然后分别计算红色数据点和蓝色数据点质心(中心点,平均值)的位置,将集群点移动到新位置,这就完成了第一轮的聚类。之后就是不断重复前面的聚类过程(迭代),设立一个阈值判定何时结束就完成了。
在进行第一轮以后的聚类时,有一点需要注意,就是在前一轮判定为某一类的数据点在后一轮也可能会判定为是其他类的,如下图所示,在进行第二轮距离计算的时候,原先为红色的点被判定为了蓝色。


k-means 算法的具体实现:

k-means 算法的具体实现就是按照前面提到的两个步骤来分配的——① 分配簇质心;② 移动簇质心。
首先进行模型的假设:
我们拥有的样例数据的数量是 m 个(图中是 30 个),使用 来指代每个样例点,每个样例数据都要依照 k 个特征进行分类,这 K 个特征就是我们所需的聚类数量,使用 来指代每个簇质心的位置。 表示的是样例 的类别。
这里需要注意的是,集群的数量并不一定就等于我们已经拥有的数据集的特征数量。比如,我们有某件 T 恤的销售数据,它的特征有两个——购买人员的体重和身高,但是我们想要将 T 恤依照小号、中号、大号这三种特征来分类,因此需要设置 3 个集群。
然后是模型的构建:
① 分配数据点所属集群。将 i 从 1 到 m 进行迭代,这里的 i 就是指代每个训练样例,计算 i 与每个特征的距离(L2 范数),并将最近的一个簇质心所对应的类别分配给 。注意这里采用的是计算距离平方最小值的方式来分类的。
为什么采用距离平方最小值进行分类?
在所有数据点都是使用一维坐标进行表示的时候,确实,不加平方可能显得公式更加简单,计算起来不会太复杂度;
但是当数据点使用二维及以上的坐标进行表示的时候,我们要计算两点之间的距离(L₂ 范数)本身就会含有平方,如果前面不使用平方的话,那么就会需要多一个开根号的操作,自然会变得不太方便,因此使用距离平方最小值进行分类。
② 移动簇质心。在将每个样例进行分类或是重新分类后,就需要重新计算每个集群簇质心位置,这里采用的是取每个特征的平均值进行定位的方式(至于为什么要采用这个方式,需要看下面成本函数的部分👇🏾)。也就是说,假如你想要将样例依照两个特征进行分类,那么集群的数量就是两个,这里就需要分别计算两个平均值,将两个平均值放入一个二维向量中,就可以确定簇质心的位置。
算法中将 k 从 1 到 K 进行迭代,这里的 k 指代的就是每个簇质心的位置,以向量的方式储存,如果是二维向量可以理解为 (x, y) 坐标,如果是三维向量可以理解为 (x, y, z) 三维坐标。对于每个簇质心,在计算出每个特征的平均值后,会将平均值储存在向量的对应元素中。如图中所示,如果有两个特征,那么 ,最终计算出来的 。
k-means 算法如何处理没有数据点的类别呢?

如上图所示,在有些情况下,所有的数据点都分配到了红色的类别中,蓝色的类别中没有成员,这种情况下 k-means 一般会删除掉蓝色的这个类别。当然,如果一定要保留这个类别,也可以视情况重新分配簇质心重新开始训练。
k-means 算法的优化方法(成本函数)
通过这个优化方法可以更好的帮助我们理解前面的 k-means 算法的公式。聚类算法的成本函数和监督学习中的不一样,虽然都是均方差,但是它没有正确的标签可以用于计算误差,而是比较每个点与簇质心的距离平方和来判断成本的大小。

首先解释一下图中各个量的含义:
- (cluster i)指的是第 i 个样例点的类别(如 红色或是蓝色),它只是一个标签,不是向量值(即位置坐标);
- 指的是第 k 个标签的簇质心;
- 指的是第 i 个样例点的标签所对应的簇质心的位置,如标签 1 是红色,标签 2 是蓝色,第一个样例点是红色,那么它的簇质心位置就是 ,第二个样例点是蓝色,那么它的簇质心就是 。
- m 表示的是样例数量(数据点的总数),K 表示的是簇质心的数量(标签的总数)。
知道了上面各个量的含义以后,优化函数就很好理解了,它的思路就是将每个类别下每个点的坐标减去该类别簇质心后求平方和,然后除以均值。拿下图举例,就是将所有红色点的坐标向量与红色簇质心向量以及所有蓝色点的坐标向量与蓝色簇质心向量作差,然后将每个差值平方求和,再除以点的个数,有点类似于求方差。

👉🏾下面来解释为什么要采用求平均值的方式来移动簇质心:

上图是以两个数据点,一个特征的计算为例。

在前面的算法部分中,我们采用两个迭代的步骤来实现 k-means 算法: ① 分配数据点所属集群; ② 移动簇质心。
在移动簇质心的时候,我们需要使用已经分配好了的所有数据点的位置计算出一个平均值。
平均值的理解(以两个数据点为例):
如果是他们只有一个特征,那么位置是一维的向量,就是 ;
如果有两个特征,那么就是是二维向量,为 ;
如果有三个特征,那么就是三维向量,为 ;
以此类推,也就是说,取平均值是向量的每个元素都取平均值。

将簇质心设置为平均值计算出来的成本函数与设置为其他点相比是最小的,如上图所示。
通过上面的方法,我们在计算过程中得到的成本函数应该是每一步都在减小的,因为使用这一轮新的平均值计算出来的成本函数一定会比上一轮簇质心计算出来的成本函数小,如果它增大了,说明算法很可能出错了。成本函数也给了我们一种判断算法是否收敛的一种方法,当成本函数的值保持一段时间不变(停止下降)时,说明 k-means 算法就已经收敛了。
如何初始化 k-means 算法(如何设置簇质心位置的初始值)

在初始化 k-means 算法的时候,我们需要先确保分类的数量 K 小于样例的数量 m,否则由于一个样例只能分配一个类别,会造成样例不够分的情况,几遍每个分类下都会有一个样例,这样的结果也不会得出什么有用的结论。
在选取初始值的时候,我们可以直接将某个样例点直接设置为簇质心,将它的坐标设置为某个集群的初始簇质心坐标。根据选取的初始值的不同,会有不同的分类结果,如下图所示:

那么,既然有这么多的分类结果,该如何进行选择呢?
利用之前学习的成本函数进行选择,计算每种结果下的成本函数,选择成本最低的方案即可。
完整的 k-means 算法训练步骤

i 的上限一般在 50 到 1000 之间,超过 1000 会使得模型的训练效率降低,训练效果也不会显著变好。
如何确定聚类的数量 K
elbow method(肘法)|(不推荐)
elbow 法通过绘制聚类数量(k)与 SSE 之间的关系图来帮助我们找到合适的聚类数量。具体步骤如下:
- 选择范围:定义一个可能的聚类数量范围 kkk,通常是从 1 到某个较大的数值。
- 计算 SSE:对每个 k 值,执行 K-means 聚类并计算 SSE 值。
- 绘制图形:将 k 值和对应的 成本函数 绘制成图,横轴是 k,纵轴是 J。
- 寻找肘部:观察图形,寻找成本函数快速下降后趋于平稳的拐点,这个拐点就是“肘部”。在此处,成本函数的下降速度明显放缓,说明增加聚类数量对整体效果的提升不再明显。这个 k 值即为合适的聚类数量。
如何理解”肘部“这个概念?
"肘部"是图中成本函数快速下降后变得平缓的转折点。在这个点之前,增加聚类数量能够显著降低 SSE,因为每个聚类的质心会变得更接近数据点;但在这个点之后,增加聚类数量只能带来较小的改进。
- SSE 下降较快的部分:在聚类数量较小时,簇内误差大幅减少,因为每增加一个簇都能显著改善聚类效果。
- SSE 下降较慢的部分:在肘部之后,SSE 的改善逐渐变得微乎其微,说明数据已经被合理划分,继续增加聚类数量的意义不大。
吴佬认为这种方法不够专业,主观性太强,一般都只会选取 k 可能取的最大值作为是聚类的数量,因此不推荐。
吴佬推荐的方法
一言以蔽之,就是”实事求是“。根据实际情况选择我们所需要的聚类数量,假如我想把衣服分为大号、中号、小号,那么聚类数量就是 3 .
异常检测
异常值检测(引入)
在 K-means 聚类中,异常值检测通常可以在算法之前或者之后进行,不同阶段有不同的目的和方法。
通过使用密度评估的方式进行异常值检测:

为了要进行密度评估,首先需要建立一个模型 p(x),p 表示的是概率 probability,p(x) 表示随着数据集特征 x 的变化,p 会如何改变。
如图中所示,在数据密集的区域我们可以用一个圈围住,这里面就是高概率区域,由这个圈往外,概率会逐渐降低。如果我们设置一个阈值 ,那么当 p(x) < 的时候,我们就可以认为这个值是异常值。
高斯正态分布的介绍
正态分布的回顾:
标准正态分布曲线。 
高斯分布其实就是正态分布,它由两个量决定图像——均值 和方差 。
当 , 的时候,正态分布称为是标准正态分布,标准正态分布的图像是一条概率密度函数曲线,横轴是 x,纵轴是概率密度, 确定时,曲线与 x 轴及 的区域所围成的面积就是概率的大小,可以通过查表法来查看不同 下概率的值。正态分布基本上都是转换为标准正态分布来进行计算的。 的值表示的是标准正态分布中心轴的位置, 的值表示的是曲线的“胖瘦程度”, 越大,曲线就越胖,越小,曲线就越尖锐突出。


一般的正态分布函数转换为标准正态分布的过程就是将 x 代入公式 中,更多有关正态分布的知识可以看概率论部分的笔记: 概率与统计 。(在这里如果想要复习一下概率与统计部分的知识,我当然要强推一下我的“概率与统计笔记”啦,点击这个链接就可以跳转到相应页面)。
密度评估:
密度评估可以使用上面正态分布的方法来进行,计算出某一个特征下所有数据点的均值和方差,然后绘制正态分布曲线,在曲线最高的地方就是密度评估的中心点。

在上面的过程中,方差在统计学中是需要除以 (m-1) 的,但在数据足够多的时候就可以不用考虑这一点。
异常检测的算法
异常值检测算法中假设了各个特征之间是独立同分布的。

一个数据集如果有 n 个特征,那么在每一个特征下都会有一个概率密度函数,这个概率密度函数的概率值可以通过下面的公式来进行计算:
对于一个可能是异常值的数据点 ,它的特征向量为 ,那么它的每一个特征 一直到 都会有对应的概率密度函数,我们的目的就是将该数据点的各个 x 分量代入上述的概率公式中,计算出该异常值发生的概率,如果小于某个阈值,那么就可以认为该数据点是异常的。
异常检测算法的文字描述:

异常检测系统的评估
为什么要进行异常检测系统的评估?
在开发学习算法(选择特征等)时,如果我们有评估学习算法的方法,决策就会容易得多,进行异常值检测系统的评估可以帮助我们更好的排除系统中的异常值。
可是现在问题就来了,如果我们想要评估一个系统的性能,势必会需要一个指标(标签),将系统进行训练测试,将预测出的标签与数据原来的标签进行对比,然后选择一个预测最准确的系统;那么,这样不就和监督学习是一样的了吗?在异常值检测中,我们引入标签 y,y = 0 的时候说明一个数据是正常的,y = 1 的时候说明一个数据是异常的,我们需要经过训练判断异常检测系统对于异常值的判断是否是准确的。这似乎就是要进行监督学习?到后面会有详细的解释。
异常检测数据集的三个部分以及相应的作用:
图中 y=0 表示的是正常的数据,y=1 表示的是异常的数据。

数据集会被分为是三个部分,训练集、交叉验证集以及测试集,三个部分的数据特点和作用不同。
为了保证无监督学习的效果,训练集部分的数据不能有标签分类,换一种说法就是,训练集部分的数据只能由一种确定的分类,即所有的数据都是 y=0 的正常数据,训练集用于产生聚类模型;
交叉验证集中混有少量的异常数据,在原先用于训练聚类模型的训练集中,各个数据的各个特征都会有对应的均值和方差,也就是有对应的概率密度函数。在经过交叉验证集的训练前,会有一个初始的阈值 ,结合前面学过的异常值部分的知识,我们知道交叉验证集的作用就是要将验证集的每个数据点代入上面的概率密度函数中,判断每个点的概率是大于阈值的还是小于阈值的。如果大于阈值,说明不是异常值,预测的标签就是 y = 0,如果小于阈值,说明该点是异常值,预测的标签就是 y = 1。接下来要做的事情就和监督学习里我们学过的二分类问题一样,将预测出来的标签与真实的标签进行比对,利用 “Sigmoid 函数+交叉熵损失函数”计算误差。不断改变阈值的大小 并重复上述步骤进行误差的计算,选择所有方案中误差最小的一组进入测试集进行训练。
测试集负责对选择的方案进行误差的计算,误差计算的方法和交叉验证集中的一样。如果只进行一轮的训练,测试集的作用当然是显现不出来的,测试集的意义就是在于有多轮训练时,避免在不同的方案选择上引入新的参数 d,造成结果的偏差,它负责对新引入的参数 d 进行训练(详情请见),选择测试集中误差最小的方案。
关于上面步骤的示例:

吴佬举了飞行器引擎的例子来说明上面的步骤,假入在常年的观察中,我们发现飞机有 10000 个很好的引擎,有 20 个异常的引擎,那么我们可以在训练集中安排 6000 个好引擎的样例,在交叉验证集和测试集中分别放入 2000 个好引擎和 10 个异常的引擎,然后进行训练,得到一个表现不错的聚类算法。
但是,往往我们手头上的数据并不会有非常多的异常值,比如,上面的飞机引擎中只有 2 个异常的引擎,那么,一种替代的方案就是将测试集给剔除,只留下交叉验证集。
那么聪明的你肯定就会问了,这样不是就不能对参数 d 进行训练了吗?没错,替代方法的缺陷就在于,我们没办法保证它在未来示例中的实际效果。同时,也有可能在训练过程中 为了迎合几个异常值而发生过拟合的现象。因此这是一种没有办法的办法。
我们该如何选择合适的阈值?

我们可以通过前面学习过的
① 真正例、真反例、假正例、假反例;
② 精确率/召回率;
③ score ;
也可以通过交叉验证集的结果来进行选择。
为什么在聚类模型中,异常值检测不使用监督学习算法?

上图描述了异常值检测和监督学习两种算法的适用范围。下面对上图内容进行解释:
在生活中,正常的数据点相对于异常值来说是比较少的,以为异常的数据往往不只是来自于一个特征。异常值可能有以下两种情况:① 异常值与过去相比变化不大,类型较为稳定; ② 异常值会进化,不断突破创新,类型较为多变。
对于第一种情况,比较适合用监督学习算法来解决,因为监督学习就是使用包含异常值的数据是去训练模型,教会模型去辨认哪些是正常的数据,哪些是异常的数据,由于异常值的类型较为稳定,因此经过训练的模型可以长时间的使用,而不用每天反复训练去适应新类型的异常值。垃圾邮件就属于这种类型。
对于第二种情况,比较适合使用异常值检测的方法来实现,因为聚类算法的在实现时就是使用正常的数据进行训练的,它是通过各个特征的概率分布曲线来判断哪些数据是异常的,对于新出现的异常值类型,该算法也能够很好的进行判断,因为它不会满足阈值条件。金融欺诈就属于这种类型。
监督学习相对于异常值检测有哪些优势?
- 标签信息:
监督学习依赖于已有的标签数据,能够明确区分类别或回归值,这使得模型可以学习出精确的类别或预测规则。而异常值检测通常是在无标签或少标签的数据上工作,依赖于数据的整体分布,难以精确分类。
- 分类和回归能力:
监督学习不仅能够处理二分类、多分类问题,还能处理回归任务(预测连续值),而异常值检测通常仅用于检测异常或偏差,难以提供详细的分类结果。
- 准确性和鲁棒性:
由于有标签数据的引导,监督学习模型在目标分类或预测任务上往往更准确,能够基于特定任务调整模型。异常值检测则更关注数据中异常模式的识别,可能在任务目标上不如监督学习那么明确。
- 可解释性:
监督学习通常在有明确目标和类别的情况下进行,模型的决策规则往往更容易解释,比如逻辑回归或决策树模型。而异常值检测模型往往依赖于数据的统计特性或密度分布,结果解释相对困难。
- 处理复杂数据:
监督学习可以处理复杂的数据特征和多类别的分类问题,如图像分类、文本分类等,这些场景下标签非常重要。而异常值检测更多关注的是寻找不常见的点,通常不处理复杂的特征关系。
异常值检测和监督学习算法的两种应用场景:

异常值检测: 诈骗侦测;发现制造中过去未曾出现的异常;检测数据中心的仪器。
监督学习算法: 垃圾邮件的分类;发现已知的错误(如手机制造中常出现的刮痕);气候侦测;疾病分类。
根据异常值对数据集特征进行调整

对于非高斯分布的特征,我们需要对其先进行转换,可以通过对数变换、幂变换等方式转换为高斯分布的特征然后再进行异常值的检测。在转换的过程中,我们需要记住,无论对训练集进行了何种转换,对于交叉验证集和测试集也要进行相同的转换。
如果异常值的概率 p(x) 和正常值的概率是接近的,无法通过阈值筛选出来怎么办?
① 找到一个新的特征,让新的特征对该异常值更为灵敏;
② 通过对已有的特征进行组合来形成新的特征。
通过上面的方式,可以增强模型对异常值的检测能力,当然原有的模型也会发生变化。
非监督学习
💡 Key Words
- k-means 算法(分配、移动)
- 成本函数 ↔ 方差
- 初始化 k-means 算法
- 聚类数量 K 的确定(肘法)
- 异常检测
- 密度评估
- 算法(各个特征的概率)
- 异常检测系统的评估
- 异常检测与监督学习
- 特征的调整
🔗 Relevant Information
📝 Class Notes
引入
聚类算法指的是将可能具有相似特征的对象都集中在一起,比如在每日新闻中,将不同的新闻依据特征分为不同的类别。
k-means 算法
k-means 算法只会重复的做两件事 —— ① 分配数据点所属集群;② 移动簇质心。
简单例子引入:
为了引入聚类算法的具体过程,这里先讨论只有两个集群的分类(对于具体设立几个集群在后面有讨论):


因为只有两个集群,所以目前就只先选取两个集群中心点。在还没有进行分类的时候(算法刚开始),我们可以随机分配集群点的位置,但集群点不能重合。在随机选取两个集群点(用”红色“和”蓝色“表示)后,我们需要一一判定各个数据点离集群点的位置,将离红色集群点位置近的数据标为红色,将离蓝色集群点位置近的数据标为蓝色,然后分别计算红色数据点和蓝色数据点质心(中心点,平均值)的位置,将集群点移动到新位置,这就完成了第一轮的聚类。之后就是不断重复前面的聚类过程(迭代),设立一个阈值判定何时结束就完成了。
在进行第一轮以后的聚类时,有一点需要注意,就是在前一轮判定为某一类的数据点在后一轮也可能会判定为是其他类的,如下图所示,在进行第二轮距离计算的时候,原先为红色的点被判定为了蓝色。


k-means 算法的具体实现:

k-means 算法的具体实现就是按照前面提到的两个步骤来分配的——① 分配簇质心;② 移动簇质心。
首先进行模型的假设:
我们拥有的样例数据的数量是 m 个(图中是 30 个),使用 来指代每个样例点,每个样例数据都要依照 k 个特征进行分类,这 K 个特征就是我们所需的聚类数量,使用 来指代每个簇质心的位置。 表示的是样例 的类别。
这里需要注意的是,集群的数量并不一定就等于我们已经拥有的数据集的特征数量。比如,我们有某件 T 恤的销售数据,它的特征有两个——购买人员的体重和身高,但是我们想要将 T 恤依照小号、中号、大号这三种特征来分类,因此需要设置 3 个集群。
然后是模型的构建:
① 分配数据点所属集群。将 i 从 1 到 m 进行迭代,这里的 i 就是指代每个训练样例,计算 i 与每个特征的距离(L2 范数),并将最近的一个簇质心所对应的类别分配给 。注意这里采用的是计算距离平方最小值的方式来分类的。
为什么采用距离平方最小值进行分类?
在所有数据点都是使用一维坐标进行表示的时候,确实,不加平方可能显得公式更加简单,计算起来不会太复杂度;
但是当数据点使用二维及以上的坐标进行表示的时候,我们要计算两点之间的距离(L₂ 范数)本身就会含有平方,如果前面不使用平方的话,那么就会需要多一个开根号的操作,自然会变得不太方便,因此使用距离平方最小值进行分类。
② 移动簇质心。在将每个样例进行分类或是重新分类后,就需要重新计算每个集群簇质心位置,这里采用的是取每个特征的平均值进行定位的方式(至于为什么要采用这个方式,需要看下面成本函数的部分👇🏾)。也就是说,假如你想要将样例依照两个特征进行分类,那么集群的数量就是两个,这里就需要分别计算两个平均值,将两个平均值放入一个二维向量中,就可以确定簇质心的位置。
算法中将 k 从 1 到 K 进行迭代,这里的 k 指代的就是每个簇质心的位置,以向量的方式储存,如果是二维向量可以理解为 (x, y) 坐标,如果是三维向量可以理解为 (x, y, z) 三维坐标。对于每个簇质心,在计算出每个特征的平均值后,会将平均值储存在向量的对应元素中。如图中所示,如果有两个特征,那么 ,最终计算出来的 。
k-means 算法如何处理没有数据点的类别呢?

如上图所示,在有些情况下,所有的数据点都分配到了红色的类别中,蓝色的类别中没有成员,这种情况下 k-means 一般会删除掉蓝色的这个类别。当然,如果一定要保留这个类别,也可以视情况重新分配簇质心重新开始训练。
k-means 算法的优化方法(成本函数)
通过这个优化方法可以更好的帮助我们理解前面的 k-means 算法的公式。聚类算法的成本函数和监督学习中的不一样,虽然都是均方差,但是它没有正确的标签可以用于计算误差,而是比较每个点与簇质心的距离平方和来判断成本的大小。

首先解释一下图中各个量的含义:
- (cluster i)指的是第 i 个样例点的类别(如 红色或是蓝色),它只是一个标签,不是向量值(即位置坐标);
- 指的是第 k 个标签的簇质心;
- 指的是第 i 个样例点的标签所对应的簇质心的位置,如标签 1 是红色,标签 2 是蓝色,第一个样例点是红色,那么它的簇质心位置就是 ,第二个样例点是蓝色,那么它的簇质心就是 。
- m 表示的是样例数量(数据点的总数),K 表示的是簇质心的数量(标签的总数)。
知道了上面各个量的含义以后,优化函数就很好理解了,它的思路就是将每个类别下每个点的坐标减去该类别簇质心后求平方和,然后除以均值。拿下图举例,就是将所有红色点的坐标向量与红色簇质心向量以及所有蓝色点的坐标向量与蓝色簇质心向量作差,然后将每个差值平方求和,再除以点的个数,有点类似于求方差。

👉🏾下面来解释为什么要采用求平均值的方式来移动簇质心:

上图是以两个数据点,一个特征的计算为例。

在前面的算法部分中,我们采用两个迭代的步骤来实现 k-means 算法: ① 分配数据点所属集群; ② 移动簇质心。
在移动簇质心的时候,我们需要使用已经分配好了的所有数据点的位置计算出一个平均值。
平均值的理解(以两个数据点为例):
如果是他们只有一个特征,那么位置是一维的向量,就是 ;
如果有两个特征,那么就是是二维向量,为 ;
如果有三个特征,那么就是三维向量,为 ;
以此类推,也就是说,取平均值是向量的每个元素都取平均值。

将簇质心设置为平均值计算出来的成本函数与设置为其他点相比是最小的,如上图所示。
通过上面的方法,我们在计算过程中得到的成本函数应该是每一步都在减小的,因为使用这一轮新的平均值计算出来的成本函数一定会比上一轮簇质心计算出来的成本函数小,如果它增大了,说明算法很可能出错了。成本函数也给了我们一种判断算法是否收敛的一种方法,当成本函数的值保持一段时间不变(停止下降)时,说明 k-means 算法就已经收敛了。
如何初始化 k-means 算法(如何设置簇质心位置的初始值)

在初始化 k-means 算法的时候,我们需要先确保分类的数量 K 小于样例的数量 m,否则由于一个样例只能分配一个类别,会造成样例不够分的情况,几遍每个分类下都会有一个样例,这样的结果也不会得出什么有用的结论。
在选取初始值的时候,我们可以直接将某个样例点直接设置为簇质心,将它的坐标设置为某个集群的初始簇质心坐标。根据选取的初始值的不同,会有不同的分类结果,如下图所示:

那么,既然有这么多的分类结果,该如何进行选择呢?
利用之前学习的成本函数进行选择,计算每种结果下的成本函数,选择成本最低的方案即可。
完整的 k-means 算法训练步骤

i 的上限一般在 50 到 1000 之间,超过 1000 会使得模型的训练效率降低,训练效果也不会显著变好。
如何确定聚类的数量 K
elbow method(肘法)|(不推荐)
elbow 法通过绘制聚类数量(k)与 SSE 之间的关系图来帮助我们找到合适的聚类数量。具体步骤如下:
- 选择范围:定义一个可能的聚类数量范围 kkk,通常是从 1 到某个较大的数值。
- 计算 SSE:对每个 k 值,执行 K-means 聚类并计算 SSE 值。
- 绘制图形:将 k 值和对应的 成本函数 绘制成图,横轴是 k,纵轴是 J。
- 寻找肘部:观察图形,寻找成本函数快速下降后趋于平稳的拐点,这个拐点就是“肘部”。在此处,成本函数的下降速度明显放缓,说明增加聚类数量对整体效果的提升不再明显。这个 k 值即为合适的聚类数量。
如何理解”肘部“这个概念?
"肘部"是图中成本函数快速下降后变得平缓的转折点。在这个点之前,增加聚类数量能够显著降低 SSE,因为每个聚类的质心会变得更接近数据点;但在这个点之后,增加聚类数量只能带来较小的改进。
- SSE 下降较快的部分:在聚类数量较小时,簇内误差大幅减少,因为每增加一个簇都能显著改善聚类效果。
- SSE 下降较慢的部分:在肘部之后,SSE 的改善逐渐变得微乎其微,说明数据已经被合理划分,继续增加聚类数量的意义不大。
吴佬认为这种方法不够专业,主观性太强,一般都只会选取 k 可能取的最大值作为是聚类的数量,因此不推荐。
吴佬推荐的方法
一言以蔽之,就是”实事求是“。根据实际情况选择我们所需要的聚类数量,假如我想把衣服分为大号、中号、小号,那么聚类数量就是 3 .
异常检测
异常值检测(引入)
在 K-means 聚类中,异常值检测通常可以在算法之前或者之后进行,不同阶段有不同的目的和方法。
通过使用密度评估的方式进行异常值检测:

为了要进行密度评估,首先需要建立一个模型 p(x),p 表示的是概率 probability,p(x) 表示随着数据集特征 x 的变化,p 会如何改变。
如图中所示,在数据密集的区域我们可以用一个圈围住,这里面就是高概率区域,由这个圈往外,概率会逐渐降低。如果我们设置一个阈值 ,那么当 p(x) < 的时候,我们就可以认为这个值是异常值。
高斯正态分布的介绍
正态分布的回顾:
标准正态分布曲线。 
高斯分布其实就是正态分布,它由两个量决定图像——均值 和方差 。
当 , 的时候,正态分布称为是标准正态分布,标准正态分布的图像是一条概率密度函数曲线,横轴是 x,纵轴是概率密度, 确定时,曲线与 x 轴及 的区域所围成的面积就是概率的大小,可以通过查表法来查看不同 下概率的值。正态分布基本上都是转换为标准正态分布来进行计算的。 的值表示的是标准正态分布中心轴的位置, 的值表示的是曲线的“胖瘦程度”, 越大,曲线就越胖,越小,曲线就越尖锐突出。


一般的正态分布函数转换为标准正态分布的过程就是将 x 代入公式 中,更多有关正态分布的知识可以看概率论部分的笔记: 概率与统计 。(在这里如果想要复习一下概率与统计部分的知识,我当然要强推一下我的“概率与统计笔记”啦,点击这个链接就可以跳转到相应页面)。
密度评估:
密度评估可以使用上面正态分布的方法来进行,计算出某一个特征下所有数据点的均值和方差,然后绘制正态分布曲线,在曲线最高的地方就是密度评估的中心点。

在上面的过程中,方差在统计学中是需要除以 (m-1) 的,但在数据足够多的时候就可以不用考虑这一点。
异常检测的算法
异常值检测算法中假设了各个特征之间是独立同分布的。

一个数据集如果有 n 个特征,那么在每一个特征下都会有一个概率密度函数,这个概率密度函数的概率值可以通过下面的公式来进行计算:
对于一个可能是异常值的数据点 ,它的特征向量为 ,那么它的每一个特征 一直到 都会有对应的概率密度函数,我们的目的就是将该数据点的各个 x 分量代入上述的概率公式中,计算出该异常值发生的概率,如果小于某个阈值,那么就可以认为该数据点是异常的。
异常检测算法的文字描述:

异常检测系统的评估
为什么要进行异常检测系统的评估?
在开发学习算法(选择特征等)时,如果我们有评估学习算法的方法,决策就会容易得多,进行异常值检测系统的评估可以帮助我们更好的排除系统中的异常值。
可是现在问题就来了,如果我们想要评估一个系统的性能,势必会需要一个指标(标签),将系统进行训练测试,将预测出的标签与数据原来的标签进行对比,然后选择一个预测最准确的系统;那么,这样不就和监督学习是一样的了吗?在异常值检测中,我们引入标签 y,y = 0 的时候说明一个数据是正常的,y = 1 的时候说明一个数据是异常的,我们需要经过训练判断异常检测系统对于异常值的判断是否是准确的。这似乎就是要进行监督学习?到后面会有详细的解释。
异常检测数据集的三个部分以及相应的作用:
图中 y=0 表示的是正常的数据,y=1 表示的是异常的数据。

数据集会被分为是三个部分,训练集、交叉验证集以及测试集,三个部分的数据特点和作用不同。
为了保证无监督学习的效果,训练集部分的数据不能有标签分类,换一种说法就是,训练集部分的数据只能由一种确定的分类,即所有的数据都是 y=0 的正常数据,训练集用于产生聚类模型;
交叉验证集中混有少量的异常数据,在原先用于训练聚类模型的训练集中,各个数据的各个特征都会有对应的均值和方差,也就是有对应的概率密度函数。在经过交叉验证集的训练前,会有一个初始的阈值 ,结合前面学过的异常值部分的知识,我们知道交叉验证集的作用就是要将验证集的每个数据点代入上面的概率密度函数中,判断每个点的概率是大于阈值的还是小于阈值的。如果大于阈值,说明不是异常值,预测的标签就是 y = 0,如果小于阈值,说明该点是异常值,预测的标签就是 y = 1。接下来要做的事情就和监督学习里我们学过的二分类问题一样,将预测出来的标签与真实的标签进行比对,利用 “Sigmoid 函数+交叉熵损失函数”计算误差。不断改变阈值的大小 并重复上述步骤进行误差的计算,选择所有方案中误差最小的一组进入测试集进行训练。
测试集负责对选择的方案进行误差的计算,误差计算的方法和交叉验证集中的一样。如果只进行一轮的训练,测试集的作用当然是显现不出来的,测试集的意义就是在于有多轮训练时,避免在不同的方案选择上引入新的参数 d,造成结果的偏差,它负责对新引入的参数 d 进行训练(详情请见),选择测试集中误差最小的方案。
关于上面步骤的示例:

吴佬举了飞行器引擎的例子来说明上面的步骤,假入在常年的观察中,我们发现飞机有 10000 个很好的引擎,有 20 个异常的引擎,那么我们可以在训练集中安排 6000 个好引擎的样例,在交叉验证集和测试集中分别放入 2000 个好引擎和 10 个异常的引擎,然后进行训练,得到一个表现不错的聚类算法。
但是,往往我们手头上的数据并不会有非常多的异常值,比如,上面的飞机引擎中只有 2 个异常的引擎,那么,一种替代的方案就是将测试集给剔除,只留下交叉验证集。
那么聪明的你肯定就会问了,这样不是就不能对参数 d 进行训练了吗?没错,替代方法的缺陷就在于,我们没办法保证它在未来示例中的实际效果。同时,也有可能在训练过程中 为了迎合几个异常值而发生过拟合的现象。因此这是一种没有办法的办法。
我们该如何选择合适的阈值?

我们可以通过前面学习过的
① 真正例、真反例、假正例、假反例;
② 精确率/召回率;
③ score ;
也可以通过交叉验证集的结果来进行选择。
为什么在聚类模型中,异常值检测不使用监督学习算法?

上图描述了异常值检测和监督学习两种算法的适用范围。下面对上图内容进行解释:
在生活中,正常的数据点相对于异常值来说是比较少的,以为异常的数据往往不只是来自于一个特征。异常值可能有以下两种情况:① 异常值与过去相比变化不大,类型较为稳定; ② 异常值会进化,不断突破创新,类型较为多变。
对于第一种情况,比较适合用监督学习算法来解决,因为监督学习就是使用包含异常值的数据是去训练模型,教会模型去辨认哪些是正常的数据,哪些是异常的数据,由于异常值的类型较为稳定,因此经过训练的模型可以长时间的使用,而不用每天反复训练去适应新类型的异常值。垃圾邮件就属于这种类型。
对于第二种情况,比较适合使用异常值检测的方法来实现,因为聚类算法的在实现时就是使用正常的数据进行训练的,它是通过各个特征的概率分布曲线来判断哪些数据是异常的,对于新出现的异常值类型,该算法也能够很好的进行判断,因为它不会满足阈值条件。金融欺诈就属于这种类型。
监督学习相对于异常值检测有哪些优势?
- 标签信息:
监督学习依赖于已有的标签数据,能够明确区分类别或回归值,这使得模型可以学习出精确的类别或预测规则。而异常值检测通常是在无标签或少标签的数据上工作,依赖于数据的整体分布,难以精确分类。
- 分类和回归能力:
监督学习不仅能够处理二分类、多分类问题,还能处理回归任务(预测连续值),而异常值检测通常仅用于检测异常或偏差,难以提供详细的分类结果。
- 准确性和鲁棒性:
由于有标签数据的引导,监督学习模型在目标分类或预测任务上往往更准确,能够基于特定任务调整模型。异常值检测则更关注数据中异常模式的识别,可能在任务目标上不如监督学习那么明确。
- 可解释性:
监督学习通常在有明确目标和类别的情况下进行,模型的决策规则往往更容易解释,比如逻辑回归或决策树模型。而异常值检测模型往往依赖于数据的统计特性或密度分布,结果解释相对困难。
- 处理复杂数据:
监督学习可以处理复杂的数据特征和多类别的分类问题,如图像分类、文本分类等,这些场景下标签非常重要。而异常值检测更多关注的是寻找不常见的点,通常不处理复杂的特征关系。
异常值检测和监督学习算法的两种应用场景:

异常值检测: 诈骗侦测;发现制造中过去未曾出现的异常;检测数据中心的仪器。
监督学习算法: 垃圾邮件的分类;发现已知的错误(如手机制造中常出现的刮痕);气候侦测;疾病分类。
根据异常值对数据集特征进行调整

对于非高斯分布的特征,我们需要对其先进行转换,可以通过对数变换、幂变换等方式转换为高斯分布的特征然后再进行异常值的检测。在转换的过程中,我们需要记住,无论对训练集进行了何种转换,对于交叉验证集和测试集也要进行相同的转换。
如果异常值的概率 p(x) 和正常值的概率是接近的,无法通过阈值筛选出来怎么办?
① 找到一个新的特征,让新的特征对该异常值更为灵敏;
② 通过对已有的特征进行组合来形成新的特征。
通过上面的方式,可以增强模型对异常值的检测能力,当然原有的模型也会发生变化。
如果大家对于论文阅读工具的选择、论文查找、论文网站选取等方面有困扰的话,欢迎大家阅读这篇博客:『 科研训练第一步:论文!! 』
目前我正在学习 Linux 的课程,『Linux 笔记』持续更新中,如果想要参与笔记共建,可以进入 『Linux 笔记-共享』
如果想要入门 pytorch,可以观看这篇博客:『 pytorch 入门笔记 』
- 作者:df
- 链接:https://blog.xiaoyuezhou.top/learning/ml
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章