CNTK API文档翻译(6)——对MNIST数据使用多层感知机

在看本期教程之前需要先完成第四期教程。本期教程我们将使用MNIST训练多层感知机神经网络(感知机的详情可以看我Python与人工神经网络系列的第二期)。

介绍(同上期,本期略)

数据读取(同上期,本期略)

模型创建

我们的多层感知机神经网络只有两层隐藏层(num_hidden_layers),每层的节点数使用变量hidden_layers_dim表示。下图说明了本教程在处理MNIST数据时的建模说明。
image

(如果你不了解隐藏层和层节点相关的内容,请参考我我Python与人工神经网络的第二期或者本系列的第三期)

每个全连接层包括输入维度,输出维度和该层用的激活函数。在如下图显示的第一层全连层中,输入维度=784(每个像素一个维度),输出维度400(隐藏层的节点数),使用ReLU(修正线性单元)作为激活函数。
image

在本模型中我们有两个全连接层,每层都使用ReLU作为激活函数,最后的输出层没有激活函数。

两个隐藏层的输出维度分别被设置层400和200,在下面的代码中我们将两层的节点数都设置层400,使用如下的变量表示:

  • num_hidden_layers
  • hidden_layers_dim

最后的输出层输出一个具有10个值的矢量,然后使用Softmax函数来归一化模型的输出值而不是使用激活函数。

num_hidden_layers = 2
hidden_layers_dim = 400

input = C.input_variable(input_dim)
label = C.input_variable(num_output_classes)

初始化多层感知机网络

下面的代码就是对上图模型的实现:

def create_model(features):
    with C.layers.default_options(init = C.layers.glorot_uniform(), activation = C.ops.relu):
            h = features
            for _ in range(num_hidden_layers):
                h = C.layers.Dense(hidden_layers_dim)(h)
            r = C.layers.Dense(num_output_classes, activation = None)(h)
            return r

z = create_model(input)

用z表示这个网络的输出值。我们在本教程的第三期引入了sigmoid函数,在本教程中我们需要在隐藏层中尝试不同的激活函数。你可以立即运行这个操作然后比较一下不同激活函数的表现,也可以运行教程中给定的代码然后进行如下操作:

  • 记录下使用sigmoid函数得到的差值
  • 然后更改成ReLU激活函数,看看差值有没有优化

学习模型参数

以前的一些教程中我们使用softmax函数来把z映射成概率。

训练

和本教程的第三期一样,我们想方设法使网络的输出值和标签之际的交叉熵成本值减小:

loss = C.cross_entropy_with_softmax(z, label)
评估

为了评估我们的分类,可以比较网络的输出值和对应的标签:

label_error = C.classification_error(z, label)

配置训练参数

我们的训练采用各种各样的优化方法来减少成本函数的数值,这些方法中随机梯度下降算法是最流行的一个。通常我们会随机生成模型参数,随机梯度下降算法会计算成本函数以及运算结果和标签之间大差值,使用梯度下降的方法来给模型生成一个新的参数,进入下一个迭代。

上述的模型描述的我们会比较倾向于一次训练一个数据,这样不用在内存中一次加载太多数据,即使数据量太大也可以进行运算。不过单个样本的特异性比较大,这样每次训练的变化会不稳定,不利于训练。一个折中的方法是选择一小段数据集,使用他们的平均成本函数值和插值来更新模型参数,这个数据集叫取样集(minibatch,网上也找不到合适的翻译,这是我翻译的,在以前的文章中我也不知道怎么翻译,可能在不同的场景下翻译的还不同,以后就用取样集了)。

使用取样集我们可以简化大的训练数据集,我们一遍又一遍的使用不同的取样集来更新模型参数,以此来减少成本函数值,当误差率不再明显变化或者说我们设置好的训练轮数到了,我们就说这个模型训练好了。

训练过程中一个重要的参数叫学习率(learning_rate),我们可以把它认为成是每次迭代参数变大大小的标志。以后的教程我们会引入更多的技术细节,不过在这个教程中,有上面的信息,我们就能创建训练器了:

# Instantiate the trainer object to drive the model training
learning_rate = 0.2
lr_schedule = C.learning_rate_schedule(learning_rate, C.UnitType.minibatch)
learner = C.sgd(z.parameters, lr_schedule)
trainer = C.Trainer(z, (loss, label_error), [learner])

在此之前,我们还需要创建一些辅助函数来计算和打印训练中我们需要知道的数据,这些函数仅仅是帮助我们理解训练的过程:

# Define a utility function to compute the moving average sum.
# A more efficient implementation is possible with np.cumsum() function
def moving_average(a, w=5):
    if len(a) < w:
        return a[:]    # Need to send a copy of the array
    return [val if idx < w else sum(a[(idx-w):idx])/w for idx, val in enumerate(a)]


# Defines a utility that prints the training progress
def print_training_progress(trainer, mb, frequency, verbose=1):
    training_loss = "NA"
    eval_error = "NA"

    if mb%frequency == 0:
        training_loss = trainer.previous_minibatch_loss_average
        eval_error = trainer.previous_minibatch_evaluation_average
        if verbose: 
            print ("Minibatch: {0}, Loss: {1:.4f}, Error: {2:.2f}%".format(mb, training_loss, eval_error*100))

    return mb, training_loss, eval_error

运行训练器

现在我们已经准备好训练我们的全连接神经网络了,我们需要确定使用哪些数据。

在这个例子中,每次迭代会使用minibatch_size个样本。我们使用所有60000条数据,并通过变量num_sweeps_to_train_with来控制数据使用的次数。有了这些参数我们就能够开始训练我们的前馈神经网络了。

# Initialize the parameters for the trainer
minibatch_size = 64
num_samples_per_sweep = 60000
num_sweeps_to_train_with = 10
num_minibatches_to_train = (num_samples_per_sweep * num_sweeps_to_train_with) / minibatch_size
# Create the reader to training data set
reader_train = create_reader(train_file, True, input_dim, num_output_classes)

# Map the data streams to the input and labels.
input_map = {
    label  : reader_train.streams.labels,
    input  : reader_train.streams.features
} 

# Run the trainer on and perform model training
training_progress_output_freq = 500

plotdata = {"batchsize":[], "loss":[], "error":[]}

for i in range(0, int(num_minibatches_to_train)):

    # Read a mini batch from the training data file
    data = reader_train.next_minibatch(minibatch_size, input_map = input_map)

    trainer.train_minibatch(data)
    batchsize, loss, error = print_training_progress(trainer, i, training_progress_output_freq, verbose=1)

    if not (loss == "NA" or error =="NA"):
        plotdata["batchsize"].append(batchsize)
        plotdata["loss"].append(loss)
        plotdata["error"].append(error)

输出结果:

Minibatch: 0, Loss: 2.3106, Error: 81.25%
Minibatch: 500, Loss: 0.2747, Error: 7.81%
Minibatch: 1000, Loss: 0.0964, Error: 1.56%
Minibatch: 1500, Loss: 0.1252, Error: 4.69%
Minibatch: 2000, Loss: 0.0086, Error: 0.00%
Minibatch: 2500, Loss: 0.0387, Error: 1.56%
Minibatch: 3000, Loss: 0.0206, Error: 0.00%
Minibatch: 3500, Loss: 0.0486, Error: 3.12%
Minibatch: 4000, Loss: 0.0178, Error: 0.00%
Minibatch: 4500, Loss: 0.0107, Error: 0.00%
Minibatch: 5000, Loss: 0.0077, Error: 0.00%
Minibatch: 5500, Loss: 0.0042, Error: 0.00%
Minibatch: 6000, Loss: 0.0045, Error: 0.00%
Minibatch: 6500, Loss: 0.0292, Error: 0.00%
Minibatch: 7000, Loss: 0.0190, Error: 1.56%
Minibatch: 7500, Loss: 0.0060, Error: 0.00%
Minibatch: 8000, Loss: 0.0031, Error: 0.00%
Minibatch: 8500, Loss: 0.0019, Error: 0.00%
Minibatch: 9000, Loss: 0.0006, Error: 0.00%

让我们浏览一下每轮计算的error值,注意其在中间的突然增大。因此我们使用较小的取样包然后使用随机梯度下降算法来让他对大数据有更好的适应性。

# Compute the moving average loss to smooth out the noise in SGD
plotdata["avgloss"] = moving_average(plotdata["loss"])
plotdata["avgerror"] = moving_average(plotdata["error"])

# Plot the training loss and the training error
import matplotlib.pyplot as plt

plt.figure(1)
plt.subplot(211)
plt.plot(plotdata["batchsize"], plotdata["avgloss"], 'b--')
plt.xlabel('Minibatch number')
plt.ylabel('Loss')
plt.title('Minibatch run vs. Training loss')

plt.show()

plt.subplot(212)
plt.plot(plotdata["batchsize"], plotdata["avgerror"], 'r--')
plt.xlabel('Minibatch number')
plt.ylabel('Label Prediction Error')
plt.title('Minibatch run vs. Label Prediction Error')
plt.show()

上述代码现实的图像:
image
image

评估/测试

现在我们已经训练了一个神经网络,让我们使用测试数据来评估一下我们的训练结果,这个工作用trainer.test_minibatch完成。

# Read the training data
reader_test = create_reader(test_file, False, input_dim, num_output_classes)

test_input_map = {
    label  : reader_test.streams.labels,
    input  : reader_test.streams.features,
}

# Test data for trained model
test_minibatch_size = 512
num_samples = 10000
num_minibatches_to_test = num_samples // test_minibatch_size
test_result = 0.0

for i in range(num_minibatches_to_test):

    # We are loading test data in batches specified by test_minibatch_size
    # Each data point in the minibatch is a MNIST digit image of 784 dimensions 
    # with one pixel per dimension that we will encode / decode with the 
    # trained model.
    data = reader_test.next_minibatch(test_minibatch_size,
                                      input_map = test_input_map)

    eval_error = trainer.test_minibatch(data)
    test_result = test_result + eval_error

# Average of evaluation errors of all test minibatches
print("Average test error: {0:.2f}%".format(test_result*100 / num_minibatches_to_test))

运行代码会发现输出值error与我们训练时和的error值非常相近,表示我们的一般错误率还是很不错的,也就表示我们的模型对全新的数据表现也会很不错,这是避免过度拟合的关键。

而且与上期使用多元逻辑回归,差值已经大大的减少了。

我们处理了总体的误差,现在就需要生成每个样本在每个分类的概率了。eval函数将返回某个样本在所有分类的概率分布,如果你全程使用默认的参数,结果会是一个有两个元素的向量。首先让我们使用softmax函数将神经网络的输出值归一化。这步将神经网络的值映射成10个类别的概率。

out = C.softmax(z)

让我们用测试数据中选取一个小型的取样包。

# Read the data for evaluation
reader_eval = create_reader(test_file, False, input_dim, num_output_classes)

eval_minibatch_size = 25
eval_input_map = {input: reader_eval.streams.features} 

data = reader_test.next_minibatch(eval_minibatch_size, input_map = test_input_map)

img_label = data[label].asarray()
img_data = data[input].asarray()
predicted_label_prob = [out.eval(img_data[i]) for i in range(len(img_data))]

# Find the index with the maximum value for both predicted as well as the ground truth
pred = [np.argmax(predicted_label_prob[i]) for i in range(len(predicted_label_prob))]
gtlabel = [np.argmax(img_label[i]) for i in range(len(img_label))]

print("Label    :", gtlabel[:25])
print("Predicted:", pred)

输出结果:
Label : [4, 5, 6, 7, 8, 9, 7, 4, 6, 1, 4, 0, 9, 9, 3, 7, 8, 4, 7, 5, 8, 5, 3, 2, 2]
Predicted: [4, 6, 6, 7, 8, 9, 7, 4, 6, 1, 4, 0, 9, 9, 3, 7, 8, 0, 7, 5, 8, 5, 3, 2, 2]


欢迎扫码关注我的微信公众号获取最新文章
image

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值