6.1 参数的更新

前面我们采用的参数更新方法是随机梯度下降法(SGD)
实际上还有很多更新参数的方法:

以函数为例,以下四种参数更新策略的表现情况如下:
四种策略

6.1.1-6.1.3 SGD及其缺点

SGD的策略是:向梯度的方向更新参数

class SGD:
    def __init__(self, lr=0.01):
        self.lr = lr

    def update(self, params, grads):
        for key in params.keys():
            params[key] -= self.lr * grads[key]

但前面的图中,梯度的方向并没有指向损失函数最低的方向,导致其更新效率很低效

6.1.4 Momentum

Momentum是动量的意思,Momentum的更新与“速度”有关

Momentum的更新值受到上次更新的值以及这次的梯度共同影响

也就是说,当梯度方向和上次参数更新不符合,也就是此时更新在发生“迂回”,与SGD相比,Momentum可以使迂回幅度减小

6.1.5 AdaGrad

学习率衰减:学习时,学习率逐渐降低的方法
AdaGrad发展了学习率衰减的思想,会根据参数来更新学习率

AdaGrad学习率的调整思想:参数更新幅度越大,学习率越小

h会记录之前所有梯度的平方和,所以学习进行越久,学习率会越接近于0


class AdaGrad:

    """AdaGrad"""
    def __init__(self, lr=0.01):
        self.lr = lr
        self.h = None

    def update(self, params, grads):
        if self.h is None:
            self.h = {}
            for key, val in params.items():
                self.h[key] = np.zeros_like(val)

        for key in params.keys():
            self.h[key] += grads[key] * grads[key]
            params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)

6.1.6 Adam

Adam参考了AdaGrad和Momentum两种策略


class Adam:

    """Adam (http://arxiv.org/abs/1412.6980v8)"""

    def __init__(self, lr=0.001, beta1=0.9, beta2=0.999):
        self.lr = lr
        self.beta1 = beta1
        self.beta2 = beta2
        self.iter = 0
        self.m = None
        self.v = None

    def update(self, params, grads):
        if self.m is None:
            self.m, self.v = {}, {}
            for key, val in params.items():
                self.m[key] = np.zeros_like(val)
                self.v[key] = np.zeros_like(val)

        self.iter += 1
        lr_t  = self.lr * np.sqrt(1.0 - self.beta2**self.iter) / (1.0 - self.beta1**self.iter)         

        for key in params.keys():
            #self.m[key] = self.beta1*self.m[key] + (1-self.beta1)*grads[key]
            #self.v[key] = self.beta2*self.v[key] + (1-self.beta2)*(grads[key]**2)
            self.m[key] += (1 - self.beta1) * (grads[key] - self.m[key])
            self.v[key] += (1 - self.beta2) * (grads[key]**2 - self.v[key])

            params[key] -= lr_t * self.m[key] / (np.sqrt(self.v[key]) + 1e-7)

            #unbias_m += (1 - self.beta1) * (grads[key] - self.m[key]) # correct bias
            #unbisa_b += (1 - self.beta2) * (grads[key]*grads[key] - self.v[key]) # correct bias
            #params[key] += self.lr * unbias_m / (np.sqrt(unbisa_b) + 1e-7)

6.2 权重的初始值

6.2.1 初始权重可以设为0吗

减小权重的值可以抑制过拟合的发生(具体原因在之后的章节会解释)。但初始权重可以设为0吗?

注:查阅资料了解到,权重可以取负值,但这里“减少权重的值”指的是绝对值还是数值呢?

答案是:不可以设为0.确切地说,权重不应该设计成一样的值。(起码在梯度下降算法时是这样的)

假设:第一层和第二层之间的权重全部设为0,那么第二层接收到的值是相同的。

那么,当反向传播时,第二层返回的导数也是相同的。

那么,第二层权重的更新幅度也是相同的。

这样就使神经网络失去了意义——神经网络必须要有复杂的权重才有意义。

6.2.2 隐藏层的激活值分布

解释一下隐藏层的激活值:就是隐藏层激活函数的输出数据。
已经知道,神经网络的权重需要设为满足高斯分布的值。那么标准差应该设为多少呢?

下面构造的神经网络测试了标准差为1的情况:

# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt


def sigmoid(x):
    return 1 / (1 + np.exp(-x))


def ReLU(x):
    return np.maximum(0, x)


def tanh(x):
    return np.tanh(x) #双曲正切函数

input_data = np.random.randn(1000, 100)  # 1000个输入数据
node_num = 100  # 每个隐藏层的神经元数目
hidden_layer_size = 5  # 隐藏层数
activations = {}  # 结果保存的位置

x = input_data

for i in range(hidden_layer_size):#注意这个循环包括了十几行语句哦!
    if i != 0:
        x = activations[i-1]

    #实验!
    w = np.random.randn(node_num, node_num) * 1
    # w = np.random.randn(node_num, node_num) * 0.01
    # w = np.random.randn(node_num, node_num) * np.sqrt(1.0 / node_num)
    # w = np.random.randn(node_num, node_num) * np.sqrt(2.0 / node_num)


    a = np.dot(x, w)


    # 活性化関数の種類も変えて実験しよう!
    z = sigmoid(a)
    # z = ReLU(a)
    # z = tanh(a)

    activations[i] = z

# ヒストグラムを描画
for i, a in activations.items():
    plt.subplot(1, len(activations), i+1)
    plt.title(str(i+1) + "-layer")
    if i != 0: plt.yticks([], [])
    # plt.xlim(0.1, 1)
    # plt.ylim(0, 7000)
    plt.hist(a.flatten(), 30, range=(0,1))
plt.show()

下面展示的是标准差为1、0.01、时的激活值分布:
(注:n是上一层的神经元数。比如对于第1层的初始值,应使用第0层的神经元数)
1
2
3
4

梯度消失:这里使用的是sigmoid函数,当激活值偏向0或1时,导数会接近于0,这样就发生了梯度消失问题。
图1便是这种情况
表现力缺失:如果激活值集中于某几个值,那么大量的神经元就没有存在的意义了,这称为表现力缺失
图1、2都有表现力缺失的问题

所以标准差设为或许是更好的选择

  • 当激活函数为sigmoid或者tanh时,使用标准差为的初始值
  • 当激活函数为ReLU函数时,使用标准差为的初始值

6.3 Batch Normalization

前面的初始值设定的目标是:激活值有一定的“广度”
那么我们可不可以刻意提高这个“广度”来帮助学习的进行呢? 由此,出现了BatchNormalization方法
Batch Norm有以下优点:

  • 使学习快速进行(可以增大学习率)
  • 不那么依赖初始值
  • 抑制过拟合

Batch Norm的方法是:向神经网络中插入若干个Batch Norm层,该层用于对数据进行正规化。
BatchNorm使用

正规化:使输出数据均值为0、方差为1

其中是一个微小的接近0的值,加上它是为了避免分母为0

以下代码用于对batch-norm的评估

# coding: utf-8
import sys, os
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.optimizer import SGD, Adam

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

# 学習データを削減
x_train = x_train[:1000]
t_train = t_train[:1000]

max_epochs = 20
train_size = x_train.shape[0]
batch_size = 100
learning_rate = 0.01


def __train(weight_init_std):
    bn_network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100], output_size=10, 
                                    weight_init_std=weight_init_std, use_batchnorm=True)
    network = MultiLayerNetExtend(input_size=784, hidden_size_list=[100, 100, 100, 100, 100], output_size=10,
                                weight_init_std=weight_init_std)
    optimizer = SGD(lr=learning_rate)

    train_acc_list = []
    bn_train_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]

        for _network in (bn_network, network):
            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)
            bn_train_acc = bn_network.accuracy(x_train, t_train)
            train_acc_list.append(train_acc)
            bn_train_acc_list.append(bn_train_acc)

            print("epoch:" + str(epoch_cnt) + " | " + str(train_acc) + " - " + str(bn_train_acc))

            epoch_cnt += 1
            if epoch_cnt >= max_epochs:
                break

    return train_acc_list, bn_train_acc_list


# 3.グラフの描画==========
weight_scale_list = np.logspace(0, -4, num=16)
x = np.arange(max_epochs)

for i, w in enumerate(weight_scale_list):
    print( "============== " + str(i+1) + "/16" + " ==============")
    train_acc_list, bn_train_acc_list = __train(w)

    plt.subplot(4,4,i+1)
    plt.title("W:" + str(w))
    if i == 15:
        plt.plot(x, bn_train_acc_list, label='Batch Normalization', markevery=2)
        plt.plot(x, train_acc_list, linestyle = "--", label='Normal(without BatchNorm)', markevery=2)
    else:
        plt.plot(x, bn_train_acc_list, markevery=2)
        plt.plot(x, train_acc_list, linestyle="--", markevery=2)

    plt.ylim(0, 1.0)
    if i % 4:
        plt.yticks([])
    else:
        plt.ylabel("accuracy")
    if i < 12:
        plt.xticks([])
    else:
        plt.xlabel("epochs")
    plt.legend(loc='lower right')

plt.show()

运行结果:
batchNorm效果评估

实线为使用batch norm 。虚线为不使用。

6.4 正则化

6.4.1 过拟合

过拟合是指:训练出的模型只能拟合训练数据,但不能很好地拟合训练数据之外的数据的情况

发生过拟合的原因有:

  • 模型具有大量参数,表现力强
  • 训练数据少

下面的代码模拟了过拟合的情况:(采用300个训练数据,7×100的神经网络)

# coding: utf-8
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_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



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

运行结果图像:
overfit

6.4.2 权值衰减

很多过拟合是由于权重参数取值过大才发生的。
为了抑制这种过拟合,便可以采取权值衰减的方法,即抑制权重参数过大的现象

“学习”是不断调整参数来使损失函数减小的过程。假如把权重参数加进损失函数里,就可以在减小损失函数时兼顾减小权重了。

我们定义L2范数:

乘以的目的是为了在求导时得到规整的值

以下为权值衰减时的随书源码。
(仅需注意loss函数中的weight_dacay)

# coding: utf-8
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 MultiLayerNet:
    """全結合による多層ニューラルネットワーク

    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ノルム)の強さ
    """
    def __init__(self, input_size, hidden_size_list, output_size,
                 activation='relu', weight_init_std='relu', weight_decay_lambda=0):
        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.weight_decay_lambda = weight_decay_lambda
        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)])
            self.layers['Activation_function' + str(idx)] = activation_layer[activation]()

        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):
        for layer in self.layers.values():
            x = layer.forward(x)

        return x

    def loss(self, x, t):
        """損失関数を求める

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        損失関数の値
        """
        y = self.predict(x)

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

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

        return grads

    def gradient(self, x, t):
        """勾配を求める(誤差逆伝搬法)

        Parameters
        ----------
        x : 入力データ
        t : 教師ラベル

        Returns
        -------
        各層の勾配を持ったディクショナリ変数
            grads['W1']、grads['W2']、...は各層の重み
            grads['b1']、grads['b2']、...は各層のバイアス
        """
        # forward
        self.loss(x, t)

        # 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.layers['Affine' + str(idx)].W
            grads['b' + str(idx)] = self.layers['Affine' + str(idx)].db

        return grads

用于实验的代码不表。
运行结果:

overfit
虽然预测能力没有明显提高,但过拟合受到了抑制。

6.4.3 DropOut

当模型很复杂(神经元数量很多)时,权值衰减法效果也不太明显了。
DropOut方法可以在复杂的模型中也有较好效果。
思路:
Dropout说的简单一点就是:我们在前向传播的时候,让某个神经元的激活值以一定的概率p停止工作,这样可以使模型泛化性更强,因为它不会太依赖某些局部的特征。
这篇文章讲得挺有趣的:知乎《什么是dropout》
具体是:
训练模型时,随机排除一定比例的神经元。
而使用模型时,不排除任何神经元,但要乘以训练时的那个排除比例。
具体实现不表。

6.5 超参数的验证

超参数(如学习率、神经元数量)非常重要,但找到合适的取值比较困难。本节介绍如何尽可能高效地选择超参数。

6.5.1 验证数据

切记:一定不要使用训练数据来验证超参数,否则会发生过拟合。

一般选取专门数据来验证超参数,称之为验证数据

  • 训练数据用于学习(调整权重参数)
  • 验证数据用于验证超参数
  • 测试数据用于验证泛化能力(理想情况下,测试数据最好只使用一次)

以下代码用于选择出验证数据:


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

# 这里还有一个函数,用于把数据打乱,实现不表。
# 検証データの分離
validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]

6.5.2 超参数的最优化

步骤:

  • 1,设定超参数的范围(很粗略就可以,比如~
  • 2, 从设定的超参数范围内随机采样
  • 3,完成学习步骤,再利用验证数据进行超参数验证。但注意,需要把epoch设定得比较小(因为只是要验证超参数,没必要完整地完成学习过程)
  • 4,不断重复2、3步骤,逐渐缩小超参数范围

以上介绍的是一种经验式的做法,更精炼的方法可能是贝叶斯最优化方法

6.5.3 超参数最优化的实现

代码如下:

我们设定权值衰减系数为~,学习率为~

# coding: utf-8
import sys, os
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.util import shuffle_dataset
from common.trainer import Trainer

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


x_train = x_train[:500]
t_train = t_train[:500]


validation_rate = 0.20
validation_num = int(x_train.shape[0] * validation_rate)
x_train, t_train = shuffle_dataset(x_train, t_train)
x_val = x_train[:validation_num]
t_val = t_train[:validation_num]
x_train = x_train[validation_num:]
t_train = t_train[validation_num:]


def __train(lr, weight_decay, epocs=50):
    network = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100, 100, 100],
                            output_size=10, weight_decay_lambda=weight_decay)
    trainer = Trainer(network, x_train, t_train, x_val, t_val,
                      epochs=epocs, mini_batch_size=100,
                      optimizer='sgd', optimizer_param={'lr': lr}, verbose=False)
    trainer.train()

    return trainer.test_acc_list, trainer.train_acc_list


# ハイパーパラメータのランダム探索======================================
optimization_trial = 100
results_val = {}
results_train = {}
for _ in range(optimization_trial):
    # 探索したハイパーパラメータの範囲を指定===============
    weight_decay = 10 ** np.random.uniform(-8, -4)
    lr = 10 ** np.random.uniform(-6, -2)
    # ================================================

    val_acc_list, train_acc_list = __train(lr, weight_decay)
    print("val acc:" + str(val_acc_list[-1]) + " | lr:" + str(lr) + ", weight decay:" + str(weight_decay))
    key = "lr:" + str(lr) + ", weight decay:" + str(weight_decay)
    results_val[key] = val_acc_list
    results_train[key] = train_acc_list

# グラフの描画========================================================
print("=========== Hyper-Parameter Optimization Result ===========")
graph_draw_num = 20
col_num = 5
row_num = int(np.ceil(graph_draw_num / col_num))
i = 0

for key, val_acc_list in sorted(results_val.items(), key=lambda x:x[1][-1], reverse=True):
    print("Best-" + str(i+1) + "(val acc:" + str(val_acc_list[-1]) + ") | " + key)

    plt.subplot(row_num, col_num, i+1)
    plt.title("Best-" + str(i+1))
    plt.ylim(0.0, 1.0)
    if i % 5: plt.yticks([])
    plt.xticks([])
    x = np.arange(len(val_acc_list))
    plt.plot(x, val_acc_list)
    plt.plot(x, results_train[key], "--")
    i += 1

    if i >= graph_draw_num:
        break

plt.show()

=========== Hyper-Parameter Optimization Result ===========
Best-1(val acc:0.76) | lr:0.009849135982779367, weight decay:8.371126751378459e-08
Best-2(val acc:0.72) | lr:0.008465895756364618, weight decay:5.599344465686115e-06
Best-3(val acc:0.68) | lr:0.007463998530287792, weight decay:5.434999294796928e-06
Best-4(val acc:0.61) | lr:0.0059275054234944596, weight decay:7.217143257829478e-06
Best-5(val acc:0.54) | lr:0.003222780855302866, weight decay:1.5494577799060805e-05
Best-6(val acc:0.44) | lr:0.0030073951251438093, weight decay:1.1708444261159714e-07
Best-7(val acc:0.43) | lr:0.0021005853382527672, weight decay:7.179899890144345e-05
Best-8(val acc:0.41) | lr:0.0031555490566781397, weight decay:1.1570768776264578e-07
Best-9(val acc:0.35) | lr:0.002751737792997746, weight decay:1.0030928213849842e-06
Best-10(val acc:0.35) | lr:0.002530549949995383, weight decay:1.8429003004503172e-06
Best-11(val acc:0.34) | lr:0.003805297187578114, weight decay:2.9763771933588664e-08
Best-12(val acc:0.32) | lr:0.0018044953610692638, weight decay:1.633388106435468e-08
Best-13(val acc:0.31) | lr:0.003299654651673116, weight decay:1.3468893175173874e-05
Best-14(val acc:0.26) | lr:0.0013745036090004102, weight decay:3.349507714291408e-07
Best-15(val acc:0.24) | lr:0.002100802278572699, weight decay:2.5164597992132576e-07
Best-16(val acc:0.22) | lr:0.001228741710146572, weight decay:4.510320818341757e-05
Best-17(val acc:0.21) | lr:0.0015933677262844985, weight decay:4.13572569703448e-08
Best-18(val acc:0.2) | lr:0.0009158024090896935, weight decay:3.5597450057769847e-06
Best-19(val acc:0.19) | lr:0.0003545544553829815, weight decay:3.6634747810567344e-05
Best-20(val acc:0.19) | lr:5.3171828783850735e-05, weight decay:2.296395116949957e-06

超参数验证
实线是验证数据,虚线是训练数据
可以看到,best5之前的学习效果都比较好

参照best-1到 best-5的数据,可以将学习率范围缩减到0.001到0.1, 权重衰减参数范围缩减到