这节的Classification分类,指的是函数的输出是从给定的几个选项中选取的。

如何做好机器学习?

image-20241203211157598

首先需要判断在训练集上的Loss大小:

  • 如果训练集Loss偏大,那就是Model Bias或者优化的问题

    • Model Bias:模型太过简单,能使得Loss小的Function根本就不在如今的Function集中。解决这个问题说白了就是找更复杂的模型,用更多的feature(比如预测阳性率可以拿前10天的阳性率而不是前1天的阳性率)
    • 优化:由于优化用的都是梯度下降,可能会进到局部最优。
    • 怎么样去分辨到底是模型烂还是优化没做好:可以先选一个简单的模型去看看它解决问题的Loss应该是多少;然后再去训练复杂模型,如果复杂模型的Loss比简单模型的还大,那基本上就是优化没做好了。
  • 如果训练集Loss很小,符合要求的。测试集Loss也很小,那就ok了;如果测试集Loss比较大,就可能是过拟合或者**不匹配(mismatch)**的问题。

    • 过拟合:在训练集上Loss小,但在测试集上Loss大。

      • 处理过拟合问题最好的办法就是增加数据集

      • 此外还可以做**数据增强(Data Augmentation)**去创造新的数据(譬如在图像识别中把原图像左右翻转、变色、截取得到的图片拿去训练)

        注意:这些新的数据必须符合原数据集的规律

        image-20241203212959397

        比如这里把猫上下翻转就不应该作为“创造”的数据,因为现实生活没有这样的图片,数据集中也不可能有这样的图片,放这种图片只会让机器学到奇葩结果。

      • 还有方法就是限制Function(Constrained Function)。因为训练数据不够时,拟合出的Function可能会在两个数据点之间整花活:

        image-20241203213519243

        我们通过对Function的约束来保证他不会整花活。比如这里的数据,我们适合用二次函数去拟合它,而不是用五次、六次函数去拟合。二次函数的参数更少,约束程度越高。但是这个约束也不能太强,比如拿一次函数去拟合,就必然结果依托。

    • 不匹配(mismatch):**训练集和测试集的数据分布不一样。**比如我解决一个播放量预测的问题,在测试集上的结果是周五和周六播放量最低;但是由于测试集中某个周五由于某个特殊原因播放量暴增(类似于极端情况,很少发生的那种),我的预测显然就不可能准的起来。在面临mismatch时,一昧增加训练集已经没用了。还有一个例子如下:

      image-20241203221954334

由此我们可以发现,当模型的复杂程度越高(feature越多,参数越多),其在训练集和测试集上的结果可能是这样的:

image-20241203215042247

优化阶段如何做好梯度下降?

在训练过程中,训练集Loss可能出现以下两种情况:

  • Loss确实随着训练的进行而逐步下降,但是其最后稳定的值还不够小
  • Loss从一开始训练就没变过,一直很烂

出现这种情况一般认为是训练卡在了梯度为0的点。这种点可以是局部最小值 (local minima) 或鞍点(saddle point),统称为驻点(Critical Point)

image-20241204143632057

**如何判断到底是卡在了局部最小还是鞍点呢?**因为对于局部最小而言,卡住了就真的寄了(因为其往四周Loss都会变大);但是对于鞍点而言,其实还有路可走。

我们一般采用黑塞矩阵来进行判断:

image-20241204153224748

事实上,在低维空间中的局部最小点,在高维空间中就可能变成鞍点。

image-20241204153340805

Batch(批次)的选择依据?

image-20241201211504985

在训练过程中,Loss函数的计算需要去使用训练集中的数据。在实际操作过程中,我们会把所有的训练数据分为若干batch,每个batch代入后算出的Loss函数不同,进而算出的梯度不同。

image-20241204164150364

在上图中,我们考虑训练集中共有20个数据。

  • 左图中Batch Size=20,相当于没有分batch,这种情况称为Full Batch。在该情况下,Loss函数的计算需要使用这全部20个数据,然后算一次梯度,参数就只变一次。这种方法蓄力时间比较长,但是它的变化比较可靠。
  • 右图中Batch Size=1,相当于分了20个Batch。在这种情况下,只需要拿一个数据就能计算Loss函数,进而计算梯度,整体下来能变更20次参数。从图中可以看出,由于只参考一个数据,noise比较多,其变化是比较杂乱无章的。这种方法蓄力短,能调很多次,但是噪声多,调节不可靠

在实际使用中,由于Loss计算的每项可以在GPU中并行,所以Batch的Size对运行时间的影响并不是很大(除非Batch Size巨大无比,超过了GPU的并行能力)。

正因为这样,右图虽然每次Update耗费时间短(其实也没比左图的Update短多少),但是右图的一个epoch要做的Update就多得多了。每个Update是在上一个Update的基础上做的,是没有办法并行的。这样看来,同样完成一个epoch,左图的时间反而比右图少。

image-20241204165727281

然而,小的Batch Size所产生的Noise能力反而为它赋予了更好的优化性能(测试集)和泛化能力(训练集)。

  • 训练集上,由于每个Batch都能对应一个Loss函数。我在L1L^1损失函数上陷入驻点后;可能在L2L^2损失函数上又能接着梯度下降。

    image-20241204172051350
  • 测试集上,小的Batch Size更倾向于找到的最小值为Flat Minima(由于其Noise特性让其能够跳出Sharp Minima点)。当测试集由于mismatch等问题与训练集的Loss函数不匹配时,Flat Minima能够保证Testing Loss与Training Loss相差不大。

    image-20241204172524727

此外,为了解决卡比在驻点的这种问题,还可能采用**动量 (momentum)**这种技术。想象损失函数是一个物理层面的斜坡,目标点是斜坡上的一个小球。当小球滚动时,其可能会滚到一个低谷(局部最小点),但是由于其拥有惯性(动量),还可以再冲一下,就有可能跳出这个Local Minima。

传统的梯度下降是这样的:每次变更参数的方向都是梯度的反方向

image-20241204173911640

带有动量的梯度下降是这样的:每次变更参数的方向是梯度的反方向与上次移动方向的矢量和

image-20241204174035190

学习率如何自动调整?

当我们的Training Loss不断减小,直至稳定到较小值后,我们还需要明确此时的梯度值是否真的减小了?如果梯度值确实变小了,说明可能进入了驻点;如果梯度值仍然很大,就仍然是优化的问题。

对于学习率而言,

  • 如果学习率设定值太大,就可能会反复横跳。
  • 如果学习率设定值太小,就动的太慢,到不了极小值点。
  • 因此最好我们能够根据该点处的梯度情况来动态决定学习率。梯度小(平坦)时,学习率应该大一些;梯度大(陡峭)时,学习率应该小一点。
image-20241204195012557

在这里我们修改了学习率的表示,在这里的σit\sigma_{i}^{t}代表新的学习率与ii(哪个参数)和tt(第几轮的Update)相关。

一种σit\sigma_{i}^{t}的计算方式是Root Mean Square

image-20241204195523493

另一种计算方法是RMSProp:使用α\alpha参数来提升当前梯度的重要性

image-20241204200446128

如此这般又会产生新问题:如果一连好多个Update的梯度值都特别小,就会导致σit\sigma_{i}^{t}变得特别小。这时如果突然遇到一个梯度的微增,θ\theta的移动就会直接喷薄而出。

image-20241204202755139

为了解决这个问题,我们会让学习率η\eta也变成轮数tt的函数,即ηt\eta^t。这个函数可以是这样的:

  • Learning Rate Decay:随轮数变大,学习率逐渐减小

    image-20241204203005019
  • Warm up

    image-20241204203027667

如何做分类问题?

进行分类问题时,我们一般会用one-hot vector来表示每个分类情况:

image-20241204211203932

在处理Regression问题时,我们的输出是一个标量值,但是在分类问题中,我们的输出应该是一个向量。该向量中元素的获取方式与Regression问题实际上是一致的:

image-20241204211423393

但是这里得到的y1、y2、y3并不能直接和真实值间作用,还需要经过softmax处理:

image-20241204211713798 image-20241204211540881

在上图中,y的各元素取值可以是任意值(正负都可以),而y’则是0到1的值。

在计算Loss时,可以使用MSE,但对于分类问题而言,更常用的损失函数是Cross-entropy

image-20241204211857423

HM2

在这次训练里,参数有一个acc(准确率),正因分类问题可以区分分类的对不对,所以会有这个值。它的定义方式是这样的:acc=正确预测的样本数/总样本数。acc和loss值不一样哦!例如在分类问题中的loss值是通过Cross-entropy得到的。

尽管准确率和损失函数都用于评估模型的性能,但它们之间并不完全等价。准确率只关心预测是否正确,而损失函数则关注预测的概率值。

直接运行示例代码的结果如下:

image-20241208193015674

观察其输出的训练集与测试集acc和loss,发现训练集的loss就很高,acc很差。

Medium baseline

首先是认为模型太过简单,我们应该考虑更多的feather。这个与参数concat_nframes相关,该参数原本的取值为1,说明其没有考虑该音素frame的前后项。换了一个大的concat_nframes,注意该参数需要是奇数。

1
2
3
4
# concat_nframes这个参数代表了我们要将多少帧的特征拼接在一起
#这个参数越大,模型的感受野就越大,但是也会增加模型的复杂度

concat_nframes = 19 # the number of frames to concat with, n must be odd (total 2k+1 = n frames)

image-20241208193834386

image-20241208193914975

后续是将学习率调高,模型架构改的更宽(hidden_dim)更深(hidden_layers)

1
2
3
4
5
6
7
8
9
10
class Classifier(nn.Module):
def __init__(self, input_dim, output_dim=41, hidden_layers=5, hidden_dim=512):
super(Classifier, self).__init__()

self.fc = nn.Sequential(
BasicBlock(input_dim, hidden_dim),
*[BasicBlock(hidden_dim, hidden_dim) for _ in range(hidden_layers)],#堆叠hidden_layers个BasicBlock,提高深度
nn.Linear(hidden_dim, output_dim)
)

image-20241208201728137

Strong baseline

首先是进行了批量规范化(batchnorm),为基本模型添加了一个规范化层:

1
2
3
4
5
6
7
8
9
class BasicBlock(nn.Module):
def __init__(self, input_dim, output_dim):
super(BasicBlock, self).__init__()

self.block = nn.Sequential(
nn.Linear(input_dim, output_dim),
nn.BatchNorm1d(output_dim),#规范化层
nn.ReLU(),
)

image-20241208210812293

随后调小了batch_size,增加了模型宽度,采用了Learning Rate Decay的学习率动态变化策略。

image-20241209152111496

后续采用了Learning Rate Decay和Warm Up的综合方式,但可能由于epoch太少,似乎进到了过拟合。

image-20241209153758350