深度学习——正则化(抑制过拟合的过程)

前言

上一篇中,总结了Batch Normalization 算法的数学式和计算图,使用该算法可以增加权重参数范围的广度。
接下来将介绍,在学习过程中常见的问题——过拟合,它是指只能拟合训练数据,但不能很好的拟合不包含在训练数据中的其他数据,而学习的目标是提高泛化能力,对即使没有在训练数据中的为观测数据也希望模型能正确识别。简单的说,要使模型具有“举一反三的能力”,抑制过拟合。
过拟合产生的原因:
①模型拥有大量的参数、表现力强
②训练数据小

例子:通过只在60000个训练数据中选定300,并且使用7层的网络(每层100个神经元,激活函数是ReLU)来制造过拟合的条件。

import os
import sys

sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net import MultiLayerNet
from common.optimizer import SGD

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

#为了再现过拟合,减少学习数据
x_train = x_train[:300]
t_train = t_train[:300]

#weight decay(权值衰减)的设定 =======================
#weight_decay_lambda = 0 # 不使用权值衰减的情况
weight_decay_lambda = 0.1
# ====================================================

network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100], output_size=10,
                        weight_decay_lambda=weight_decay_lambda)
optimizer = SGD(lr=0.01)

max_epochs = 201
train_size = x_train.shape[0]
batch_size = 100

train_loss_list = []
train_acc_list = []
test_acc_list = []

iter_per_epoch = max(train_size / batch_size, 1)
epoch_cnt = 0

for i in range(1000000000):
    batch_mask = np.random.choice(train_size, batch_size)
    x_batch = x_train[batch_mask]
    t_batch = t_train[batch_mask]

    grads = network.gradient(x_batch, t_batch)
    optimizer.update(network.params, grads)

    if i % iter_per_epoch == 0:
        train_acc = network.accuracy(x_train, t_train)
        test_acc = network.accuracy(x_test, t_test)
        train_acc_list.append(train_acc)
        test_acc_list.append(test_acc)

        print("epoch:" + str(epoch_cnt) + ", train acc:" + str(train_acc) + ", test acc:" + str(test_acc))

        epoch_cnt += 1
        if epoch_cnt >= max_epochs:
            break

#3.绘制图形==========
markers = {'train': 'o', 'test': 's'}
x = np.arange(max_epochs)
plt.plot(x, train_acc_list, marker='o', label='train', markevery=10)
plt.plot(x, test_acc_list, marker='s', label='test', markevery=10)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

未使用权值衰减:

从结果可以看出,训练数据和测试数据相差的比较多,存在过拟合的问题。
在这里插入图片描述

权值衰减 (方法一\适用于简单的网络模型)

一种抑制过拟合的方法。
该方法通过在学习的过程中对大的权重进行惩罚
如何进行:
为损失函数加上权重的平方范数(L2范数)。
将权重记为W,L2范数的权值衰减就是1/2⋌W²
在这里插入图片描述
使用了权值衰减的情况:
在这里插入图片描述
可以看到,训练数据和测试数据的准确度差距变小了,但是训练数据的识别精读没有达到100%

Dropout (抑制过拟合的方法二,网络模型复杂情况)

Dropout是一种在学习的过程中随机删除神经元的方法。
训练时,随机选出隐藏层的神经元,然后将其删除。被删除的神经元不再进行信号的传递,每传递一次数据,就会随机选择要删除的神经元。

测试时,虽然会传递所有的神经元信号,但是对于各个神经元的输出,
要乘上训练时的删除比例后再输出。
在这里插入图片描述

Dropout的实现:

import os
import sys
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import numpy as np
import matplotlib.pyplot as plt
from dataset.mnist import load_mnist
from common.multi_layer_net_extend import MultiLayerNetExtend
from common.trainer import Trainer

(x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)

#为了再现过拟合,减少学习数据
x_train = x_train[:300]
t_train = t_train[:300]

#设定是否使用Dropuout,以及比例 ========================
use_dropout = True  # 不使用Dropout的情况下为False
dropout_ratio = 0.2
# ====================================================

network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
                              output_size=10, use_dropout=use_dropout, dropout_ration=dropout_ratio)
trainer = Trainer(network, x_train, t_train, x_test, t_test,
                  epochs=301, mini_batch_size=100,
                  optimizer='sgd', optimizer_param={'lr': 0.01}, verbose=True)
trainer.train()

train_acc_list, test_acc_list = trainer.train_acc_list, trainer.test_acc_list

#绘制图形==========
markers = {'train': 'o', 'test': 's'}
x = np.arange(len(train_acc_list))
plt.plot(x, train_acc_list, marker='o', label='train', markevery=10)
plt.plot(x, test_acc_list, marker='s', label='test', markevery=10)
plt.xlabel("epochs")
plt.ylabel("accuracy")
plt.ylim(0, 1.0)
plt.legend(loc='lower right')
plt.show()

使用了Dropout
在这里插入图片描述
未使用Dropout
在这里插入图片描述

机器学习中经常使用集成学习。就是让多个模型单独地进行学习,推理时再取多个模型的输出平均值。
集成学习与 Dropout有密切的关系。这是因为可以将 Dropout理解为,通过在学习过程中随机删除神经元,从而每一次都让不同的模型进行学习。并且,推理时,通过对神经元的输出乘以删除比例(比如,Dropout=0.5等),可以取得模型的平均值。

附上拓展版的全连接的多层神经网络 MultiLayerNetExtend类

import sys, os
sys.path.append(os.pardir) # 为了导入父目录的文件而进行的设定
import numpy as np
from collections import OrderedDict
from common.layers import *
from common.gradient import numerical_gradient

class MultiLayerNetExtend:
    """扩展版的全连接的多层神经网络
    
    具有Weiht Decay、Dropout、Batch Normalization的功能

    Parameters
    ----------
    input_size : 输入大小(MNIST的情况下为784)
    hidden_size_list : 隐藏层的神经元数量的列表(e.g. [100, 100, 100])
    output_size : 输出大小(MNIST的情况下为10)
    activation : 'relu' or 'sigmoid'
    weight_init_std : 指定权重的标准差(e.g. 0.01)
        指定'relu'或'he'的情况下设定“He的初始值”
        指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”
    weight_decay_lambda : Weight Decay(L2范数)的强度
    use_dropout: 是否使用Dropout
    dropout_ration : Dropout的比例
    use_batchNorm: 是否使用Batch Normalization
    """
    def __init__(self, input_size, hidden_size_list, output_size,
                 activation='relu', weight_init_std='relu', weight_decay_lambda=0, 
                 use_dropout = False, dropout_ration = 0.5, use_batchnorm=False):
        self.input_size = input_size
        self.output_size = output_size
        self.hidden_size_list = hidden_size_list
        self.hidden_layer_num = len(hidden_size_list)
        self.use_dropout = use_dropout
        self.weight_decay_lambda = weight_decay_lambda
        self.use_batchnorm = use_batchnorm
        self.params = {}

        # 初始化权重
        self.__init_weight(weight_init_std)

        # 生成层
        activation_layer = {'sigmoid': Sigmoid, 'relu': Relu}
        self.layers = OrderedDict()
        for idx in range(1, self.hidden_layer_num+1):
            self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)],
                                                      self.params['b' + str(idx)])
            if self.use_batchnorm:
                self.params['gamma' + str(idx)] = np.ones(hidden_size_list[idx-1])
                self.params['beta' + str(idx)] = np.zeros(hidden_size_list[idx-1])
                self.layers['BatchNorm' + str(idx)] = BatchNormalization(self.params['gamma' + str(idx)], self.params['beta' + str(idx)])
                
            self.layers['Activation_function' + str(idx)] = activation_layer[activation]()
            
            if self.use_dropout:
                self.layers['Dropout' + str(idx)] = Dropout(dropout_ration)

        idx = self.hidden_layer_num + 1
        self.layers['Affine' + str(idx)] = Affine(self.params['W' + str(idx)], self.params['b' + str(idx)])

        self.last_layer = SoftmaxWithLoss()

    def __init_weight(self, weight_init_std):
        """设定权重的初始值

        Parameters
        ----------
        weight_init_std : 指定权重的标准差(e.g. 0.01)
            指定'relu'或'he'的情况下设定“He的初始值”
            指定'sigmoid'或'xavier'的情况下设定“Xavier的初始值”
        """
        all_size_list = [self.input_size] + self.hidden_size_list + [self.output_size]
        for idx in range(1, len(all_size_list)):
            scale = weight_init_std
            if str(weight_init_std).lower() in ('relu', 'he'):
                scale = np.sqrt(2.0 / all_size_list[idx - 1])  # 使用ReLU的情况下推荐的初始值
            elif str(weight_init_std).lower() in ('sigmoid', 'xavier'):
                scale = np.sqrt(1.0 / all_size_list[idx - 1])  # 使用sigmoid的情况下推荐的初始值
            self.params['W' + str(idx)] = scale * np.random.randn(all_size_list[idx-1], all_size_list[idx])
            self.params['b' + str(idx)] = np.zeros(all_size_list[idx])

    def predict(self, x, train_flg=False):
        for key, layer in self.layers.items():
            if "Dropout" in key or "BatchNorm" in key:
                x = layer.forward(x, train_flg)
            else:
                x = layer.forward(x)

        return x

    def loss(self, x, t, train_flg=False):
        """求损失函数
        参数x是输入数据,t是教师标签
        """
        y = self.predict(x, train_flg)

        weight_decay = 0
        for idx in range(1, self.hidden_layer_num + 2):
            W = self.params['W' + str(idx)]
            weight_decay += 0.5 * self.weight_decay_lambda * np.sum(W**2)

        return self.last_layer.forward(y, t) + weight_decay

    def accuracy(self, X, T):
        Y = self.predict(X, train_flg=False)
        Y = np.argmax(Y, axis=1)
        if T.ndim != 1 : T = np.argmax(T, axis=1)

        accuracy = np.sum(Y == T) / float(X.shape[0])
        return accuracy

    def numerical_gradient(self, X, T):
        """求梯度(数值微分)

        Parameters
        ----------
        X : 输入数据
        T : 教师标签

        Returns
        -------
        具有各层的梯度的字典变量
            grads['W1']、grads['W2']、...是各层的权重
            grads['b1']、grads['b2']、...是各层的偏置
        """
        loss_W = lambda W: self.loss(X, T, train_flg=True)

        grads = {}
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = numerical_gradient(loss_W, self.params['W' + str(idx)])
            grads['b' + str(idx)] = numerical_gradient(loss_W, self.params['b' + str(idx)])
            
            if self.use_batchnorm and idx != self.hidden_layer_num+1:
                grads['gamma' + str(idx)] = numerical_gradient(loss_W, self.params['gamma' + str(idx)])
                grads['beta' + str(idx)] = numerical_gradient(loss_W, self.params['beta' + str(idx)])

        return grads
        
    def gradient(self, x, t):
        # forward
        self.loss(x, t, train_flg=True)

        # backward
        dout = 1
        dout = self.last_layer.backward(dout)

        layers = list(self.layers.values())
        layers.reverse()
        for layer in layers:
            dout = layer.backward(dout)

        # 设定
        grads = {}
        for idx in range(1, self.hidden_layer_num+2):
            grads['W' + str(idx)] = self.layers['Affine' + str(idx)].dW + self.weight_decay_lambda * self.params['W' + str(idx)]
            grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db

            if self.use_batchnorm and idx != self.hidden_layer_num+1:
                grads['gamma' + str(idx)] = self.layers['BatchNorm' + str(idx)].dgamma
                grads['beta' + str(idx)] = self.layers['BatchNorm' + str(idx)].dbeta

        return grads

参考

《深度学习入门:基于Python的理论与实现 》斋藤康毅

### L2 正则化的原理 L2 正则化是一种通过在模型的损失函数中加入权重参数平方和的方式,来防止过拟合的技术。其核心思想是在优化目标中增加一个与权重大小成比例的惩罚项,从而鼓励模型选择较小的权重值[^3]。 具体来说,在标准损失函数 \( J(W) \) 的基础上,L2 正则化会额外加上一项: \[ J_{\text{regularized}}(W) = J(W) + \lambda \| W \|^2_2 \] 其中: - \( J(W) \) 是原始的损失函数; - \( \| W \|^2_2 \) 表示权重矩阵 \( W \) 中所有元素平方之和; - \( \lambda \)正则化系数,用于控制正则化强度。 这种形式使得模型训练过程中不仅关注最小化数据误差,还倾向于减少权重的绝对值,进而降低模型复杂度并提高泛化能力[^1]。 --- ### L2 正则化的作用 L2 正则化的主要作用可以概括如下几点: #### 1. 防止过拟合 当模型过于复杂或者训练样本不足时,容易发生过拟合现象——即模型能够很好地适应训练集中的噪声或细节特征,但在测试集上的表现较差。通过施加 L2 范数约束,可以使网络更平滑地分布权重值,抑制某些过大权值的影响,从而有效缓解这一问题。 #### 2. 提升模型稳定性 由于大数值权重可能导致梯度爆炸等问题,而 L2 正则化强制让这些权重趋于零附近的小范围波动,因此有助于提升整个系统的稳定性和收敛速度。 #### 3. 改善泛化性能 通过对高维空间内的解进行收缩操作,L2 正则化实际上是从无穷多个可能解决方案里挑选出了最简单合理的那个(即具有较低能量水平的状态),这正是贝叶斯统计学意义上的 MAP (Maximum A Posteriori)估计思路之一[^2]。 --- ### L2 正则化的实现方法 以下是基于 Python 和 TensorFlow/Keras 框架下的一种典型实现方式: ```python from tensorflow.keras import layers, models, regularizers model = models.Sequential() # 添加一层全连接层,并应用 L2 正则化 model.add(layers.Dense(units=64, activation='relu', kernel_regularizer=regularizers.l2(l=0.001))) # 编译模型... ``` 上述代码片段展示了如何利用 Keras API 来定义带 L2 正则化的神经元层。这里 `kernel_regularizer` 参数指定了应用于该层权重矩阵的具体规则;`l2(l)` 函数创建了一个以超参 l 控制力度的比例因子作为输入的标准二次型罚分机制。 另外值得注意的是,除了显式指定外,还可以手动修改成本计算流程来自定义类似的逻辑处理过程。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值