• 自己动手实现一个深度学习算法——六、与学习相关的技巧


    1.参数的更新

    神经网络的学习的目的是找到使损失函数的值尽可能小的参数。这是寻找最优参数的问题,解决这个问题的过程称为最优化(optimization)。

    使用参数的梯度,沿梯度方向更新参数,并重复这个步骤多次,从而逐渐靠近最优参数,这个过程称为随机梯度下降法(stochastic gradient descent),简称SGD。

    1)SGD

    探险家虽然看不到周围的情况,但是能够知道当前所在位置的坡度(通过脚底感受地面的倾斜状况)。于是,朝着当前所在位置的坡度最大的方向前进,就是SGD的策略。

    在这里插入图片描述

    SGD实现:

    class SGD:
    
        """随机梯度下降法(Stochastic Gradient Descent)"""
    #进行初始化时的参数lr表示learning rate(学习率)
        def __init__(self, lr=0.01):
            self.lr = lr
            #该方法在SGD中会被反复调用。
        def update(self, params, grads):
            for key in params.keys():
                params[key] -= self.lr * grads[key] 
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    SGD呈“之”字形移动。这是一个相当低效的路径。也就是说,SGD 的缺点是,如果函数的形状非均向(anisotropic),比如呈延伸状,搜索的路径就会非常低效。因此,我们需要比单纯朝梯度方向前进的SGD更聪明的方法。SGD低效的根本原因是,梯度的方向并没有指向最小值的方向。

    2)Momentum

    Momentum 是“动量”的意思,和物理有关。Momentum方法给人的感觉就像是小球在地面上滚动。在物体不受任何力时,担使物体逐渐减
    速的任务
    在这里插入图片描述

    实现

    class Momentum:
    
        """Momentum SGD"""
    
        def __init__(self, lr=0.01, momentum=0.9):
            self.lr = lr
            self.momentum = momentum
            #实例变量v会保存物体的速度。
            self.v = None
            
        def update(self, params, grads):
            if self.v is None:
                self.v = {}
                for key, val in params.items():   
                    #v会以字典型变量的形式保存与参数结构相同的数据
                    self.v[key] = np.zeros_like(val)
                    
            for key in params.keys():
                self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
                params[key] += self.v[key]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    和SGD相比,我们发现“之”字形的“程度”减轻了。这是因为虽然x轴方向上受到的力非常小,但是一直在同一方向上受力,所以朝同一个方向会有一定的加速。反过来,虽然y轴方向上受到的力很大,但是因为交互地受到正方向和反方向的力,它们会互相抵消,所以y轴方向上的速度不稳定。因此,和SGD时的情形相比,
    可以更快地朝x轴方向靠近,减弱“之”字形的变动程度。

    3)AdaGrad

    在关于学习率的有效技巧中,有一种被称为学习率衰减(learning rate decay)的方法,即随着学习的进行,使学习率逐渐减小。实际上,一开始“多”学,然后逐渐“少”学的方法,在神经网络的学习中经常被使用。

    逐渐减小学习率的想法,相当于将“全体”参数的学习率值一起降低。而AdaGrad进一步发展了这个想法,针对“一个一个”的参数,赋予其“定制”的值。表达式如下:

    在这里插入图片描述

    可以按参数的元素进行学习率衰减,使变动大的参数的学习率逐渐减小。

    4)Adam

    融合了Momentum和AdaGrad的方法。

    5)最优化方法的比较

    完整代码如下:

    创建文件optimizer.py,实现各种优化器 添加代码如下:

    # coding: utf-8
    import numpy as np
    
    class SGD:
    
        """随机梯度下降法(Stochastic Gradient Descent)"""
    
        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] 
    
    
    class Momentum:
    
        """Momentum SGD"""
    
        def __init__(self, lr=0.01, momentum=0.9):
            self.lr = lr
            self.momentum = momentum
            self.v = None
            
        def update(self, params, grads):
            if self.v is None:
                self.v = {}
                for key, val in params.items():                                
                    self.v[key] = np.zeros_like(val)
                    
            for key in params.keys():
                self.v[key] = self.momentum*self.v[key] - self.lr*grads[key] 
                params[key] += self.v[key]
    
    
    class Nesterov:
    
        """Nesterov's Accelerated Gradient (http://arxiv.org/abs/1212.0901)"""
    
        def __init__(self, lr=0.01, momentum=0.9):
            self.lr = lr
            self.momentum = momentum
            self.v = None
            
        def update(self, params, grads):
            if self.v is None:
                self.v = {}
                for key, val in params.items():
                    self.v[key] = np.zeros_like(val)
                
            for key in params.keys():
                self.v[key] *= self.momentum
                self.v[key] -= self.lr * grads[key]
                params[key] += self.momentum * self.momentum * self.v[key]
                params[key] -= (1 + self.momentum) * self.lr * grads[key]
    
    
    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)
    
    
    class RMSprop:
    
        """RMSprop"""
    
        def __init__(self, lr=0.01, decay_rate = 0.99):
            self.lr = lr
            self.decay_rate = decay_rate
            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] *= self.decay_rate
                self.h[key] += (1 - self.decay_rate) * grads[key] * grads[key]
                params[key] -= self.lr * grads[key] / (np.sqrt(self.h[key]) + 1e-7)
    
    
    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)
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131

    创建文件optimizer_compare_naive.py,引入优化器
    添加代码如下:

    # coding: utf-8
    import sys, os
    sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
    import numpy as np
    import matplotlib.pyplot as plt
    from collections import OrderedDict
    from optimizer import *
    
    
    def f(x, y):
        return x**2 / 20.0 + y**2
    
    
    def df(x, y):
        return x / 10.0, 2.0*y
    
    init_pos = (-7.0, 2.0)
    params = {}
    params['x'], params['y'] = init_pos[0], init_pos[1]
    grads = {}
    grads['x'], grads['y'] = 0, 0
    
    
    optimizers = OrderedDict()
    optimizers["SGD"] = SGD(lr=0.95)
    optimizers["Momentum"] = Momentum(lr=0.1)
    optimizers["AdaGrad"] = AdaGrad(lr=1.5)
    optimizers["Adam"] = Adam(lr=0.3)
    
    idx = 1
    
    for key in optimizers:
        optimizer = optimizers[key]
        x_history = []
        y_history = []
        params['x'], params['y'] = init_pos[0], init_pos[1]
        
        for i in range(30):
            x_history.append(params['x'])
            y_history.append(params['y'])
            
            grads['x'], grads['y'] = df(params['x'], params['y'])
            optimizer.update(params, grads)
        
    
        x = np.arange(-10, 10, 0.01)
        y = np.arange(-5, 5, 0.01)
        
        X, Y = np.meshgrid(x, y) 
        Z = f(X, Y)
        
        # for simple contour line  
        mask = Z > 7
        Z[mask] = 0
        
        # plot 
        plt.subplot(2, 2, idx)
        idx += 1
        plt.plot(x_history, y_history, 'o-', color="red")
        plt.contour(X, Y, Z)
        plt.ylim(-10, 10)
        plt.xlim(-10, 10)
        plt.plot(0, 0, '+')
        #colorbar()
        #spring()
        plt.title(key)
        plt.xlabel("x")
        plt.ylabel("y")
        
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70

    运行结果:

    在这里插入图片描述

    根据使用的方法不同,参数更新的路径也不同。只看这个图的话,AdaGrad似乎是最好的,不过也要注意,结果会根据要解决的问题而变。并且,很显然,超参数(学习率等)的设定值不同,结果也会发生变化。

    并不存在能在所有问题中都表现良好的方法。这4种方法各有各的特点,都有各自擅长解决的问题和不擅长解决的问题。

    很多研究中至今仍在使用SGD。Momentum和AdaGrad也是值得一试的方法。最近,很多研究人员和技术人员都喜欢用Adam。

    6)基于MNIST数据集的更新方法的比较

    完整代码如下:

    创建文件夹common,移入optimizer.py,

    在文件夹common下创建文件util.py
    添加代码如下:

    # coding: utf-8
    import numpy as np
    
    
    def smooth_curve(x):
        """用于使损失函数的图形变圆滑
    
        参考:http://glowingpython.blogspot.jp/2012/02/convolution-with-numpy.html
        """
        window_len = 11
        s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
        w = np.kaiser(window_len, 2)
        y = np.convolve(w/w.sum(), s, mode='valid')
        return y[5:len(y)-5]
    
    
    def shuffle_dataset(x, t):
        """打乱数据集
    
        Parameters
        ----------
        x : 训练数据
        t : 监督数据
    
        Returns
        -------
        x, t : 打乱的训练数据和监督数据
        """
        permutation = np.random.permutation(x.shape[0])
        x = x[permutation,:] if x.ndim == 2 else x[permutation,:,:,:]
        t = t[permutation]
    
        return x, t
    
    def conv_output_size(input_size, filter_size, stride=1, pad=0):
        return (input_size + 2*pad - filter_size) / stride + 1
    
    
    def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
        """
    
        Parameters
        ----------
        input_data : 由(数据量, 通道, 高, 长)的4维数组构成的输入数据
        filter_h : 滤波器的高
        filter_w : 滤波器的长
        stride : 步幅
        pad : 填充
    
        Returns
        -------
        col : 2维数组
        """
        N, C, H, W = input_data.shape
        out_h = (H + 2*pad - filter_h)//stride + 1
        out_w = (W + 2*pad - filter_w)//stride + 1
    
        img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
        col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
    
        for y in range(filter_h):
            y_max = y + stride*out_h
            for x in range(filter_w):
                x_max = x + stride*out_w
                col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
    
        col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
        return col
    
    
    def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
        """
    
        Parameters
        ----------
        col :
        input_shape : 输入数据的形状(例:(10, 1, 28, 28))
        filter_h :
        filter_w
        stride
        pad
    
        Returns
        -------
    
        """
        N, C, H, W = input_shape
        out_h = (H + 2*pad - filter_h)//stride + 1
        out_w = (W + 2*pad - filter_w)//stride + 1
        col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)
    
        img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
        for y in range(filter_h):
            y_max = y + stride*out_h
            for x in range(filter_w):
                x_max = x + stride*out_w
                img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
    
        return img[:, :, pad:H + pad, pad:W + pad]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99

    在文件夹common下创建文件multi_layer_net.py
    添加代码如下:

    # 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
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161

    在文件夹common下创建文件optimizer_compare_mnist.py
    添加代码如下:

    # coding: utf-8
    import os
    import sys
    sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
    import matplotlib.pyplot as plt
    from dataset.mnist import load_mnist
    from common.util import smooth_curve
    from common.multi_layer_net import MultiLayerNet
    from common.optimizer import *
    
    
    # 0:读入MNIST数据==========
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
    
    train_size = x_train.shape[0]
    batch_size = 128
    max_iterations = 2000
    
    
    # 1:进行实验的设置==========
    optimizers = {}
    optimizers['SGD'] = SGD()
    optimizers['Momentum'] = Momentum()
    optimizers['AdaGrad'] = AdaGrad()
    optimizers['Adam'] = Adam()
    #optimizers['RMSprop'] = RMSprop()
    
    networks = {}
    train_loss = {}
    for key in optimizers.keys():
        networks[key] = MultiLayerNet(
            input_size=784, hidden_size_list=[100, 100, 100, 100],
            output_size=10)
        train_loss[key] = []    
    
    
    # 2:开始训练==========
    for i in range(max_iterations):
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]
        
        for key in optimizers.keys():
            grads = networks[key].gradient(x_batch, t_batch)
            optimizers[key].update(networks[key].params, grads)
        
            loss = networks[key].loss(x_batch, t_batch)
            train_loss[key].append(loss)
        
        if i % 100 == 0:
            print( "===========" + "iteration:" + str(i) + "===========")
            for key in optimizers.keys():
                loss = networks[key].loss(x_batch, t_batch)
                print(key + ":" + str(loss))
    
    
    # 3.绘制图形==========
    markers = {"SGD": "o", "Momentum": "x", "AdaGrad": "s", "Adam": "D"}
    x = np.arange(max_iterations)
    for key in optimizers.keys():
        plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
    plt.xlabel("iterations")
    plt.ylabel("loss")
    plt.ylim(0, 1)
    plt.legend()
    plt.show()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67

    在这里插入图片描述

    与SGD相比,其他3种方法学习得更快,而且速度基本相同,仔细看的话,AdaGrad的学习进行得稍微快一点。这个实验需要注意的地方是,实验结果会随学习率等超参数、神经网络的结构(几层深等)的不同而发生变化。不过,一般而言,与SGD相比,其他3种方法可以学习得更快,有时最终的识别精度也更高。

    2.权重的初始值

    权值衰减就是一种提高泛化能力的技巧,以减小权重参数的值为目的进行学习的方法。通过减小权重参数的值来抑制过拟合的发生。

    如果想减小权重的值,一开始就将初始值设为较小的值才是正途。

    1)权重初始值不能为0

    将权重初始值设为0不是一个好主意。事实上,将权重初始值设为0 的话,将无法正确进行学习。

    这是因为在误差反向传播法中,所有的权重值都会进行相同的更新。比如,在2层神经网络中,假设第1层和第2层的权重为0。这样一来,正向传播时,因为输入层的权重为0,所以第2层的神经元全部会被传递相同的值。

    2)隐藏层的激活值的分布

    将激活函数的输出数据称为“激活值”,但是有的文献中会将在层之间流动的数据也称为“激活值”。

    实现:

    # 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)
        #高斯分布随机生成1000个数据作为输入数据,并把它们传给5层神经网络。
    input_data = np.random.randn(1000, 100)  # 1000个数据
    node_num = 100  # 各隐藏层的节点(神经元)数
    hidden_layer_size = 5  # 隐藏层有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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    将权重的标准差设为1时,结果显示各层的激活值呈偏向0和1的分布。偏向0和1的数据分布会造成反向传播中梯度的值不断变小,最后消失。这个问题称为梯度消失(gradient vanishing)。层次加深的深度学习中,梯度消失的问题可能会更加严重。

    将权重的标准差设为0.01时,这次呈集中在0.5附近的分布。因为不像刚才的例子那样偏向0和1,所以不会发生梯度消失的问题。

    因此,激活值在分布上有所偏向会出现“表现力受限”的问题。

    尝试使用Xavier Glorot等人的论文中推荐的权重初始值(俗称“Xavier初始值”)。Xavier 的论文中,为了使各层的激活值呈现出具有相同广度的分布,推导了合适的权重尺度。推导出的结论是,如果前一层的节点数为n,则初始值使用标准差为
    1 / n 1/ \sqrt{n} 1/n
    的分布。结果表明后面的层的分布呈稍微歪斜的形状。如果用tanh函数(双曲线函数)代替sigmoid函数,这个稍微歪斜的问题就能得到改善。实际上,使用tanh函数后,会呈漂亮的吊钟型分布。tanh函数和sigmoid函数同是S型曲线函数,但tanh函数是关于原点(0, 0)对称的S型曲线,而sigmoid函数是关于(x, y)=(0, 0.5)对称的S型曲线。众所周知,用作激活函数的函数最好具有关于原点对称的性质。

    3)ReLU的权重初始值

    当激活函数使用ReLU时,一般推荐使用ReLU专用的初始值,也就是Kaiming He等人推荐的初始值,也称为He初始值。

    在这种情况下,随着层的加深,偏向一点点变大。实际上,层加深后,激活值的偏向变大,学习时会出现梯度消失的问题。而当初始值为He初始值时,各层中分布的广度相同。由于即便层加深,数据的广度也能保持不变,因此逆向传播时,也会传递合适的值。

    总结一下,当激活函数使用ReLU时,权重初始值使用He初始值,当激活函数为sigmoid或tanh等S型曲线函数时,初始值使用Xavier初始值。这是目前的最佳实践。

    4)基于MNIST数据集的权重初始值的比较

    创建文件weight_init_compare.py
    添加代码如下:

    # 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.util import smooth_curve
    from common.multi_layer_net import MultiLayerNet
    from common.optimizer import SGD
    
    
    # 0:读入MNIST数据==========
    (x_train, t_train), (x_test, t_test) = load_mnist(normalize=True)
    
    train_size = x_train.shape[0]
    batch_size = 128
    max_iterations = 2000
    
    
    # 1:进行实验的设置==========
    weight_init_types = {'std=0.01': 0.01, 'Xavier': 'sigmoid', 'He': 'relu'}
    optimizer = SGD(lr=0.01)
    
    networks = {}
    train_loss = {}
    for key, weight_type in weight_init_types.items():
        networks[key] = MultiLayerNet(input_size=784, hidden_size_list=[100, 100, 100, 100],
                                      output_size=10, weight_init_std=weight_type)
        train_loss[key] = []
    
    
    # 2:开始训练==========
    for i in range(max_iterations):
        batch_mask = np.random.choice(train_size, batch_size)
        x_batch = x_train[batch_mask]
        t_batch = t_train[batch_mask]
        
        for key in weight_init_types.keys():
            grads = networks[key].gradient(x_batch, t_batch)
            optimizer.update(networks[key].params, grads)
        
            loss = networks[key].loss(x_batch, t_batch)
            train_loss[key].append(loss)
        
        if i % 100 == 0:
            print("===========" + "iteration:" + str(i) + "===========")
            for key in weight_init_types.keys():
                loss = networks[key].loss(x_batch, t_batch)
                print(key + ":" + str(loss))
    
    
    # 3.绘制图形==========
    markers = {'std=0.01': 'o', 'Xavier': 's', 'He': 'D'}
    x = np.arange(max_iterations)
    for key in weight_init_types.keys():
        plt.plot(x, smooth_curve(train_loss[key]), marker=markers[key], markevery=100, label=key)
    plt.xlabel("iterations")
    plt.ylabel("loss")
    plt.ylim(0, 2.5)
    plt.legend()
    plt.show()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63

    运行结果:
    在这里插入图片描述

    std = 0.01时完全无法进行学习。这和刚才观察到的激活值的分布一样,是因为正向传播中传递的值很小(集中在0附近的数据)。因此,逆向传播时求到的梯度也很小,权重几乎不进行更新。相反,当权重初始值为Xavier初始值和He初始值时,学习进行得很顺利。
    并且,我们发现He初始值时的学习进度更快一些。

    3.Batch Normalization

    Batch Normalization可以“强制性”地调整激活值的分布

    1)Batch Normalization 的算法

    优点:

    • 可以使学习快速进行(可以增大学习率)。
    • 不那么依赖初始值(对于初始值不用那么神经质)。
    • 抑制过拟合(降低Dropout等的必要性)。

    Batch Norm的思路是调整各层的激活值分布使其拥有适当的广度。为此,要向神经网络中插入对数据分布进行正规化的层,即Batch Normalization 层

    2)Batch Normalization的评估

    使用 MNIST 数据集,观察使用Batch Norm层和不使用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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87

    运行结果:
    在这里插入图片描述

    使用Batch Norm后,学习进行得更快了。

    4.正则化

    1)过拟合

    发生过拟合的原因,主要有以下两个。
    • 模型拥有大量参数、表现力强。
    • 训练数据少。

    过拟合代码实现,

    创建文件overfit_weight_decay.py
    添加代码如下:

    # 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(权值衰减)的设定 =======================
    #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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    运行结果:
    在这里插入图片描述

    过了 100 个 epoch 左右后,用训练数据测量到的识别精度几乎都为100%。但是,对于测试数据,离 100% 的识别精度还有较大的差距。如此大的识别精度差距,是只拟合了训练数据的结果。从图中可知,模型对训练时没有使用的一般数据(测试数据)拟合得不是很好。

    2)权值衰减

    权值衰减是一直以来经常被使用的一种抑制过拟合的方法。该方法通过在学习的过程中对大的权重进行惩罚,来抑制过拟合。很多过拟合原本就是因为权重参数取值过大才发生的。

    对于刚刚进行的实验,应用λ = 0.1的权值衰减。

    虽然训练数据的识别精度和测试数据的识别精度之间有差距,但是与没有使用权值衰减相比,差距变小了。

    3)Dropout

    Dropout 是一种在学习的过程中随机删除神经元的方法。训练时,随机选出隐藏层的神经元,然后将其删除。

    在common文件夹下创建文件multi_layer_net_extend.py
    添加代码如下:

    # 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 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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163

    在common文件夹下创建文件layers.py
    添加Dropout类

    完整layers.py如下:

    # coding: utf-8
    import numpy as np
    from common.functions import *
    from common.util import im2col, col2im
    
    
    class Relu:
        def __init__(self):
            self.mask = None
    
        def forward(self, x):
            self.mask = (x <= 0)
            out = x.copy()
            out[self.mask] = 0
    
            return out
    
        def backward(self, dout):
            dout[self.mask] = 0
            dx = dout
    
            return dx
    
    
    class Sigmoid:
        def __init__(self):
            self.out = None
    
        def forward(self, x):
            out = sigmoid(x)
            self.out = out
            return out
    
        def backward(self, dout):
            dx = dout * (1.0 - self.out) * self.out
    
            return dx
    
    
    class Affine:
        def __init__(self, W, b):
            self.W =W
            self.b = b
            
            self.x = None
            self.original_x_shape = None
            # 权重和偏置参数的导数
            self.dW = None
            self.db = None
    
        def forward(self, x):
            # 对应张量
            self.original_x_shape = x.shape
            x = x.reshape(x.shape[0], -1)
            self.x = x
    
            out = np.dot(self.x, self.W) + self.b
    
            return out
    
        def backward(self, dout):
            dx = np.dot(dout, self.W.T)
            self.dW = np.dot(self.x.T, dout)
            self.db = np.sum(dout, axis=0)
            
            dx = dx.reshape(*self.original_x_shape)  # 还原输入数据的形状(对应张量)
            return dx
    
    
    class SoftmaxWithLoss:
        def __init__(self):
            self.loss = None
            self.y = None # softmax的输出
            self.t = None # 监督数据
    
        def forward(self, x, t):
            self.t = t
            self.y = softmax(x)
            self.loss = cross_entropy_error(self.y, self.t)
            
            return self.loss
    
        def backward(self, dout=1):
            batch_size = self.t.shape[0]
            if self.t.size == self.y.size: # 监督数据是one-hot-vector的情况
                dx = (self.y - self.t) / batch_size
            else:
                dx = self.y.copy()
                dx[np.arange(batch_size), self.t] -= 1
                dx = dx / batch_size
            
            return dx
    
    
    class Dropout:
        """
        http://arxiv.org/abs/1207.0580
        """
        def __init__(self, dropout_ratio=0.5):
            self.dropout_ratio = dropout_ratio
            self.mask = None
    
        def forward(self, x, train_flg=True):
            if train_flg:
                self.mask = np.random.rand(*x.shape) > self.dropout_ratio
                return x * self.mask
            else:
                return x * (1.0 - self.dropout_ratio)
    
        def backward(self, dout):
            return dout * self.mask
    
    
    class BatchNormalization:
        """
        http://arxiv.org/abs/1502.03167
        """
        def __init__(self, gamma, beta, momentum=0.9, running_mean=None, running_var=None):
            self.gamma = gamma
            self.beta = beta
            self.momentum = momentum
            self.input_shape = None # Conv层的情况下为4维,全连接层的情况下为2维  
    
            # 测试时使用的平均值和方差
            self.running_mean = running_mean
            self.running_var = running_var  
            
            # backward时使用的中间数据
            self.batch_size = None
            self.xc = None
            self.std = None
            self.dgamma = None
            self.dbeta = None
    
        def forward(self, x, train_flg=True):
            self.input_shape = x.shape
            if x.ndim != 2:
                N, C, H, W = x.shape
                x = x.reshape(N, -1)
    
            out = self.__forward(x, train_flg)
            
            return out.reshape(*self.input_shape)
                
        def __forward(self, x, train_flg):
            if self.running_mean is None:
                N, D = x.shape
                self.running_mean = np.zeros(D)
                self.running_var = np.zeros(D)
                            
            if train_flg:
                mu = x.mean(axis=0)
                xc = x - mu
                var = np.mean(xc**2, axis=0)
                std = np.sqrt(var + 10e-7)
                xn = xc / std
                
                self.batch_size = x.shape[0]
                self.xc = xc
                self.xn = xn
                self.std = std
                self.running_mean = self.momentum * self.running_mean + (1-self.momentum) * mu
                self.running_var = self.momentum * self.running_var + (1-self.momentum) * var            
            else:
                xc = x - self.running_mean
                xn = xc / ((np.sqrt(self.running_var + 10e-7)))
                
            out = self.gamma * xn + self.beta 
            return out
    
        def backward(self, dout):
            if dout.ndim != 2:
                N, C, H, W = dout.shape
                dout = dout.reshape(N, -1)
    
            dx = self.__backward(dout)
    
            dx = dx.reshape(*self.input_shape)
            return dx
    
        def __backward(self, dout):
            dbeta = dout.sum(axis=0)
            dgamma = np.sum(self.xn * dout, axis=0)
            dxn = self.gamma * dout
            dxc = dxn / self.std
            dstd = -np.sum((dxn * self.xc) / (self.std * self.std), axis=0)
            dvar = 0.5 * dstd / self.std
            dxc += (2.0 / self.batch_size) * self.xc * dvar
            dmu = np.sum(dxc, axis=0)
            dx = dxc - dmu / self.batch_size
            
            self.dgamma = dgamma
            self.dbeta = dbeta
            
            return dx
    
    
    class Convolution:
        def __init__(self, W, b, stride=1, pad=0):
            self.W = W
            self.b = b
            self.stride = stride
            self.pad = pad
            
            # 中间数据(backward时使用)
            self.x = None   
            self.col = None
            self.col_W = None
            
            # 权重和偏置参数的梯度
            self.dW = None
            self.db = None
    
        def forward(self, x):
            FN, C, FH, FW = self.W.shape
            N, C, H, W = x.shape
            out_h = 1 + int((H + 2*self.pad - FH) / self.stride)
            out_w = 1 + int((W + 2*self.pad - FW) / self.stride)
    
            col = im2col(x, FH, FW, self.stride, self.pad)
            col_W = self.W.reshape(FN, -1).T
    
            out = np.dot(col, col_W) + self.b
            out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
    
            self.x = x
            self.col = col
            self.col_W = col_W
    
            return out
    
        def backward(self, dout):
            FN, C, FH, FW = self.W.shape
            dout = dout.transpose(0,2,3,1).reshape(-1, FN)
    
            self.db = np.sum(dout, axis=0)
            self.dW = np.dot(self.col.T, dout)
            self.dW = self.dW.transpose(1, 0).reshape(FN, C, FH, FW)
    
            dcol = np.dot(dout, self.col_W.T)
            dx = col2im(dcol, self.x.shape, FH, FW, self.stride, self.pad)
    
            return dx
    
    
    class Pooling:
        def __init__(self, pool_h, pool_w, stride=1, pad=0):
            self.pool_h = pool_h
            self.pool_w = pool_w
            self.stride = stride
            self.pad = pad
            
            self.x = None
            self.arg_max = None
    
        def forward(self, x):
            N, C, H, W = x.shape
            out_h = int(1 + (H - self.pool_h) / self.stride)
            out_w = int(1 + (W - self.pool_w) / self.stride)
    
            col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
            col = col.reshape(-1, self.pool_h*self.pool_w)
    
            arg_max = np.argmax(col, axis=1)
            out = np.max(col, axis=1)
            out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
    
            self.x = x
            self.arg_max = arg_max
    
            return out
    
        def backward(self, dout):
            dout = dout.transpose(0, 2, 3, 1)
            
            pool_size = self.pool_h * self.pool_w
            dmax = np.zeros((dout.size, pool_size))
            dmax[np.arange(self.arg_max.size), self.arg_max.flatten()] = dout.flatten()
            dmax = dmax.reshape(dout.shape + (pool_size,)) 
            
            dcol = dmax.reshape(dmax.shape[0] * dmax.shape[1] * dmax.shape[2], -1)
            dx = col2im(dcol, self.x.shape, self.pool_h, self.pool_w, self.stride, self.pad)
            
            return dx
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285

    common下创建文件trainer.py
    添加代码如下:

    # coding: utf-8
    import sys, os
    sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
    import numpy as np
    from common.optimizer import *
    
    class Trainer:
        """进行神经网络的训练的类
        """
        def __init__(self, network, x_train, t_train, x_test, t_test,
                     epochs=20, mini_batch_size=100,
                     optimizer='SGD', optimizer_param={'lr':0.01}, 
                     evaluate_sample_num_per_epoch=None, verbose=True):
            self.network = network
            self.verbose = verbose
            self.x_train = x_train
            self.t_train = t_train
            self.x_test = x_test
            self.t_test = t_test
            self.epochs = epochs
            self.batch_size = mini_batch_size
            self.evaluate_sample_num_per_epoch = evaluate_sample_num_per_epoch
    
            # optimzer
            optimizer_class_dict = {'sgd':SGD, 'momentum':Momentum, 'nesterov':Nesterov,
                                    'adagrad':AdaGrad, 'rmsprpo':RMSprop, 'adam':Adam}
            self.optimizer = optimizer_class_dict[optimizer.lower()](**optimizer_param)
            
            self.train_size = x_train.shape[0]
            self.iter_per_epoch = max(self.train_size / mini_batch_size, 1)
            self.max_iter = int(epochs * self.iter_per_epoch)
            self.current_iter = 0
            self.current_epoch = 0
            
            self.train_loss_list = []
            self.train_acc_list = []
            self.test_acc_list = []
    
        def train_step(self):
            batch_mask = np.random.choice(self.train_size, self.batch_size)
            x_batch = self.x_train[batch_mask]
            t_batch = self.t_train[batch_mask]
            
            grads = self.network.gradient(x_batch, t_batch)
            self.optimizer.update(self.network.params, grads)
            
            loss = self.network.loss(x_batch, t_batch)
            self.train_loss_list.append(loss)
            if self.verbose: print("train loss:" + str(loss))
            
            if self.current_iter % self.iter_per_epoch == 0:
                self.current_epoch += 1
                
                x_train_sample, t_train_sample = self.x_train, self.t_train
                x_test_sample, t_test_sample = self.x_test, self.t_test
                if not self.evaluate_sample_num_per_epoch is None:
                    t = self.evaluate_sample_num_per_epoch
                    x_train_sample, t_train_sample = self.x_train[:t], self.t_train[:t]
                    x_test_sample, t_test_sample = self.x_test[:t], self.t_test[:t]
                    
                train_acc = self.network.accuracy(x_train_sample, t_train_sample)
                test_acc = self.network.accuracy(x_test_sample, t_test_sample)
                self.train_acc_list.append(train_acc)
                self.test_acc_list.append(test_acc)
    
                if self.verbose: print("=== epoch:" + str(self.current_epoch) + ", train acc:" + str(train_acc) + ", test acc:" + str(test_acc) + " ===")
            self.current_iter += 1
    
        def train(self):
            for i in range(self.max_iter):
                self.train_step()
    
            test_acc = self.network.accuracy(self.x_test, self.t_test)
    
            if self.verbose:
                print("=============== Final Test Accuracy ===============")
                print("test acc:" + str(test_acc))
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    正向传播时传递了信号的神经元,反向传播时按原样传递信号;正向传播时没有传递信号的神经元,反向传播时信号将停在那里。

    使用MNIST数据集进行验证

    创建文件overfit_dropout.py
    添加代码如下:

    # 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_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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    运行结果:
    在这里插入图片描述

    通过使用Dropout,训练数据和测试数据的识别精度的差距变小了。并且,训练数据也没有到达100%的识别精度。像这样,通过使用Dropout,即便是表现力强的网络,也可以抑制过拟合。

    4)集成学习

    机器学习中经常使用集成学习。所谓集成学习,就是让多个模型单独进行学习,推理时再取多个模型的输出的平均值。用神经网络的语境来说,比如,准备5个结构相同(或者类似)的网络,分别进行学习,测试时,以这5个网络的输出的平均值作为答案。

    通过进行集成学习,神经网络的识别精度可以提高好几个百分点。

    5.超参数的验证

    超参数是指,比如各层的神经元数量、batch大小、参数更新时的学习率或权值衰减等。如果这些超参数没有设置合适的值,模型的性能就会很差。虽然超参数的取值非常重要,但是在决定超参数的过程中一般会伴随很多的试错。

    1)验证数据

    不能使用测试数据评估超参数的性能。

    因为如果使用测试数据调整超参数,超参数的值会对测试数据发生过拟合。换句话说,用测试数据确认超参数的值的“好坏”,就会导致超参数的值被调整为只拟合测试数据。这样的话,可能就会得到不能拟合其他数据、泛化能力低的模型。

    因此,调整超参数时,必须使用超参数专用的确认数据。用于调整超参数的数据,一般称为验证数据

    训练数据用于参数(权重和偏置)的学习,验证数据用于超参数的性能评估。

    2)超参数的最优化

    进行超参数的最优化时,逐渐缩小超参数的“好值”的存在范围非常重要。所谓逐渐缩小范围,是指一开始先大致设定一个范围,从这个范围中随机选出一个超参数(采样),用这个采样到的值进行识别精度的评估;然后,多次重复该操作,观察识别精度的结果,根据这个结果缩小超参数的“好值”的范围。通过重复这一操作,就可以逐渐确定超参数的合适范围。

    3)步骤

    步骤0
    设定超参数的范围。
    步骤1
    从设定的超参数范围中随机采样。
    步骤2
    使用步骤1中采样到的超参数的值进行学习,通过验证数据评估识别精度(但是要将epoch设置得很小)。
    步骤3
    重复步骤1和步骤2(100次等),根据它们的识别精度的结果,缩小超参数的范围。

    在超参数的最优化中,如果需要更精炼的方法,可以使用贝叶斯最优化

    4)超参数最优化的实现

    创建文件hyperparameter_optimization.py
    添加代码如下:

    # 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 = 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:]
    
    x_val = x_train[:int(validation_num)]
    t_val = t_train[:int(validation_num)]
    x_train = x_train[int(validation_num):]
    t_train = t_train[int(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()
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    运行结果:
    在这里插入图片描述

    学习率在0.001到0.01、权值衰减系数在10−8到10−6之间时,学习可以顺利进行。像这样,观察可以使学习顺利进行的超参数的范围,从而缩小值的范围。然后,在这个缩小的范围中重复相同的操作。学习率在0.001到0.01、权值衰减系数在10−8到10−6之间时,学习可以顺利进行。像这样,观察可以使学习顺利进行的超参数的范围,从而缩小值的范围。然后,在这个缩小的范围中重复相同的操作。

  • 相关阅读:
    【MATLAB】常见语法结构和实用操作
    【python基础题】——程序题(一)
    Java基础知识点面试专题
    音视频 fmpeg命令裁剪和合并视频
    AutoJs学习-几个QQ群脚本(群引流\提取成员\加群友\加群)
    【python】设置里的BASE_DIR
    PHP:表达式
    virtualbox centos 使用NAT模式上网
    数据结构和算法之冒泡排序
    Redis篇---第五篇
  • 原文地址:https://blog.csdn.net/u011249837/article/details/134425139