1.4 使用神经网络解决问题

到这里为止,我们的准备工作就做好了。现在,我们对一个简单的数据集进行神经网络的学习。

1.4.1 螺旋状数据集

本书在dataset目录中提供了几个便于处理数据集的类,本节将使用其中的dataset/spiral.py文件。这个文件中实现了读取螺旋(旋涡)状数据的类,其用法如下所示(ch01/show_spiral_dataset.py)。

        import sys
        sys.path.append('..')  # 为了引入父目录的文件而进行的设定
        from dataset import spiral
        import matplotlib.pyplot as plt

        x, t = spiral.load_data()
        print ('x', x.shape) # (300, 2)
        print ('t', t.shape) # (300, 3)

在上面的例子中,要从ch01目录的dataset目录引入spiral.py。因此,上面的代码通过sys.path.append('..')将父目录添加到了import的检索路径中。

然后,使用spiral.load_data()进行数据的读入。此时,x是输入数据, t是监督标签。观察x和t的形状,可知它们各自有300笔样本数据,其中x是二维数据,t是三维数据。另外,t是one-hot向量,对应的正确解标签的类标记为1,其余的标记为0。下面,我们把这些数据绘制在图上,结果如图1-31所示。

图1-31 学习用的螺旋状数据集(用×▲●分别表示3个类)

如图1-31所示,输入是二维数据,类别数是3。观察这个数据集可知,它不能被直线分割。因此,我们需要学习非线性的分割线。那么,我们的神经网络(具有使用非线性的sigmoid激活函数的隐藏层的神经网络)能否正确学习这种非线性模式呢?让我们实验一下。

因为这个实验相对简单,所以我们不把数据集分成训练数据、验证数据和测试数据。不过,实际任务中会将数据集分为训练数据和测试数据(以及验证数据)来进行学习和评估。

1.4.2 神经网络的实现

现在,我们来实现一个具有一个隐藏层的神经网络。首先,import语句和初始化程序的__init__()如下所示(ch01/two_layer_net.py)。

        import sys
        sys.path.append('..')
        import numpy as np
        from common.layers import Affine, Sigmoid, SoftmaxWithLoss

        class TwoLayerNet:
            def__init__(self, input_size, hidden_size, output_size):
                I, H, O = input_size, hidden_size, output_size

                # 初始化权重和偏置
                W1 = 0.01 * np.random.randn(I, H)
                b1 = np.zeros(H)
                W2 = 0.01 * np.random.randn(H, O)
                b2 = np.zeros(O)

                # 生成层
                self.layers = [
                    Affine(W1, b1),
                    Sigmoid(),
                    Affine(W2, b2)
                ]
                self.loss_layer = SoftmaxWithLoss()
                  # 将所有的权重和梯度整理到列表中
                  self.params, self.grads = [], []
                  for layer in self.layers:
                      self.params += layer.params
                      self.grads += layer.grads

初始化程序接收3个参数。input_size是输入层的神经元数,hidden_size是隐藏层的神经元数,output_size是输出层的神经元数。在内部实现中,首先用零向量(np.zeros())初始化偏置,再用小的随机数(0.01 *np.random.randn())初始化权重。通过将权重设成小的随机数,学习可以更容易地进行。接着,生成必要的层,并将它们整理到实例变量layers列表中。最后,将这个模型使用到的参数和梯度归纳在一起。

因为Softmax with Loss层和其他层的处理方式不同,所以不将它放入layers列表中,而是单独存储在实例变量loss_layer中。

接着,我们为TwoLayerNet实现3个方法,即进行推理的predict()方法、正向传播的forward()方法和反向传播的backward()方法( ch01/two_layer_net.py)。

        def predict(self, x):
            for layer in self.layers:
                x = layer.forward(x)
            return x

        def forward(self, x, t):
            score = self.predict(x)
            loss = self.loss_layer.forward(score, t)
            return loss

        def backward(self, dout=1):
            dout = self.loss_layer.backward(dout)
            for layer in reversed(self.layers):
                dout = layer.backward(dout)
            return dout

如上所示,这个实现非常清楚。因为我们已经将神经网络中要用的处理模块实现为了层,所以这里只需要以合理的顺序调用这些层的forward()方法和backward()方法即可。

1.4.3 学习用的代码

下面,我们来看一下学习用的代码。首先,读入学习数据,生成神经网络(模型)和优化器。然后,按照之前介绍的学习的4个步骤进行学习。另外,在机器学习领域,通常将针对具体问题设计的方法(神经网络、SVM等)称为模型。学习用的代码如下所示(ch01/train_custom_loop.py)。

        import sys
        sys.path.append('..')
        import numpy as np
        from common.optimizer import SGD
        from dataset import spiral
        import matplotlib.pyplot as plt
        from two layer net import TwoLayerNet

        # ❶设定超参数
        max_epoch = 300
        batch_size = 30
        hidden_size = 10
        learning_rate = 1.0

        # ❷读入数据,生成模型和优化器
        x, t = spiral.load_data()
        model = TwoLayerNet(input_size=2, hidden_size=hidden_size, output_size=3)
        optimizer = SGD(lr=learning_rate)

        # 学习用的变量
        data_size = len(x)
        max_iters = data_size // batch_size
        total_loss = 0
        loss_count = 0
        loss_list = []
        for epoch in range(max_epoch):
            # ❸打乱数据
            idx = np.random.permutation(data_size)
            x = x[idx]
            t = t[idx]

            for iters in range(max_iters):
                batch_x = x[iters*batch_size:(iters+1)*batch_size]
                batch_t = t[iters*batch_size:(iters+1)*batch_size]

            # ❹计算梯度,更新参数
            loss = model.forward(batch_x, batch_t)
            model.backward()
            optimizer.update(model.params, model.grads)
            total_loss += loss
            loss_count += 1

            # ❺定期输出学习过程
            if (iters+1) % 10 == 0:
                avg_loss = total_loss / loss_count
                print ('| epoch %d |  iter %d / %d | loss %.2f'
                      % (epoch + 1, iters + 1, max_iters, avg_loss))
                loss_list.append(avg_loss)
                total_loss, loss_count = 0, 0

首先,在代码❶的地方设定超参数。具体而言,就是设定学习的epoch数、mini-batch的大小、隐藏层的神经元数和学习率。接着,在代码❷的地方进行数据的读入,生成神经网络(模型)和优化器。我们已经将2层神经网络实现为了TwoLayerNet类,将优化器实现为了SGD类,这里直接使用它们就可以。

epoch表示学习的单位。1个epoch相当于模型“看过”一遍所有的学习数据(遍历数据集)。这里我们进行300个epoch的学习。

在进行学习时,需要随机选择数据作为mini-batch。这里,我们以epoch为单位打乱数据,对于打乱后的数据,按顺序从头开始抽取数据。数据的打乱(准确地说,是数据索引的打乱)使用np.random.permutation()方法。给定参数N,该方法可以返回0到N-1的随机序列,其实际的使用示例如下所示。

        >>> import numpy as np
        >>> np.random.permutation(10)
        array([7, 6, 8, 3, 5, 0, 4, 1, 9, 2])

        >>> np.random.permutation(10)
        array([1, 5, 7, 3, 9, 2, 8, 6, 0, 4])

像这样,调用np.random.permutation()可以随机打乱数据的索引。

接着,在代码❹的地方计算梯度,更新参数。最后,在代码❺的地方定期地输出学习结果。这里,每10次迭代计算1次平均损失,并将其添加到变量loss_list中。以上就是学习用的代码。

这里实现的神经网络的学习用的代码在本书其他地方也可以使用。因此,本书将这部分代码作为Trainer类提供出来。使用Trainer类,可以将神经网络的学习细节嵌入Trainer类。详细的用法将在1.4.4节说明。

运行一下上面的代码(ch01/train_custom_loop.py)就会发现,向终端输出的损失的值在平稳下降。我们将结果画出来,如图1-32所示。

由图1-32可知,随着学习的进行,损失在减小。我们的神经网络正在朝着正确的方向学习!接下来,我们将学习后的神经网络的区域划分(也称为决策边界)可视化,结果如图1-33所示。

由图1-33可知,学习后的神经网络可以正确地捕获“旋涡”这个模式。也就说,模型正确地学习了非线性的区域划分。像这样,神经网络通过隐藏层可以实现复杂的表现力。深度学习的特征之一就是叠加的层越多,表现力越丰富。

图1-32 损失的图形:横轴是学习的迭代次数(刻度值的10倍),竖轴是每10次迭代的平均损失

图1-33 学习后的神经网络的决策边界(用不同颜色描绘神经网络识别的各个类别的区域)

1.4.4 Trainer类

如前所述,本书中有很多机会执行神经网络的学习。为此,就需要编写前面那样的学习用的代码。然而,每次都写相同的代码太无聊了,因此我们将进行学习的类作为Trainer类提供出来。Trainer类的内部实现和刚才的源代码几乎相同,只是添加了一些新的功能而已,我们在需要的时候再详细说明其用法。

Trainer类的代码在common/trainer.py中。这个类的初始化程序接收神经网络(模型)和优化器,具体如下所示。

        model = TwoLayerNet(...)
        optimizer = SGD(lr=1.0)
        trainer = Trainer(model, optimizer)

然后,调用fit()方法开始学习。fit()方法的参数如表1-1所示。

表1-1 Trainer类的fit()方法的参数。表中的"(=XX)"表示参数的默认值

另外,Trainer类有plot()方法,它将fit()方法记录的损失(准确地说,是按照eval_interval评价的平均损失)在图上画出来。使用Trainer类进行学习的代码如下所示(ch01/train.py)。

        import sys
        sys.path.append('..')
        from common.optimizer import SGD
        from common.trainer import Trainer
        from dataset import spiral
        from two layer net import TwoLayerNet

        max_epoch = 300
        batch_size = 30
        hidden_size = 10
        learning_rate = 1.0

        x, t = spiral.load_data()
        model = TwoLayerNet(input_size=2, hidden_size=hidden_size, output_size=3)
        optimizer = SGD(lr=learning_rate)

        trainer = Trainer(model, optimizer)
        trainer.fit(x, t, max_epoch, batch_size, eval_interval=10)
        trainer.plot()

执行这段代码,会进行和之前一样的神经网络的学习。通过将之前展示的学习用的代码交给Trainer类负责,代码变简洁了。本书今后都将使用Trainer类进行学习。