在本文中,我们将实现一个简单的卷积神经网络模型。 我们将实现此模型以对MNIST数据集进行分类。我们要构建的神经网络的结构如下。 MNIST数据的手写数字图像有10个类(从0到9)。 网络有2个卷积层,最后是2个全连接层。
开始代码实现:
- 准备数据
MNIST是手写数字的数据集,包含55,000个用于训练的示例,5,000个用于验证的示例和10,000个用于测试的示例。 这些数字已经过尺寸标准化,并且以固定尺寸的图像(28 x 28像素)为中心,其值为0和1.每个图像已被展平并转换为784个特征的1-D阵列。 它也是深度学习的基准数据集。
先导入必要的机器学习库TensorFlow.NET和NumSharp.
using System;
using NumSharp;
using Tensorflow;
using TensorFlowNET.Examples.Utility;
using static Tensorflow.Python;
const int img_h = 28;
const int img_w = 28;
int n_classes = 10; // Number of classes, one class per digit
int n_channels = 1;
我们将编写一个自动加载MNIST数据的函数,并以我们想要的形状和格式返回它。 有一个MNIST数据助手可以让这个任务变得列简单一些。
Datasets mnist;
public void PrepareData()
{
mnist = MnistDataSet.read_data_sets("mnist", one_hot: true);
}
除了加载图像和相应标签的功能外,我们还需要三个功能:
重新格式化:将数据重新格式化为卷积层可接受的格式。
private (NDArray, NDArray) Reformat(NDArray x, NDArray y)
{
var (img_size, num_ch, num_class) = (np.sqrt(x.shape[1]), 1, len(np.unique<int>(np.argmax(y, 1))));
var dataset = x.reshape(x.shape[0], img_size, img_size, num_ch).astype(np.float32);
//y[0] = np.arange(num_class) == y[0];
//var labels = (np.arange(num_class) == y.reshape(y.shape[0], 1, y.shape[1])).astype(np.float32);
return (dataset, y);
}
随机抽样:随机化图像及其标签的顺序。 在每个时期的开始,我们将重新随机化数据样本的顺序,以确保训练的模型对数据的顺序不敏感。
private (NDArray, NDArray) randomize(NDArray x, NDArray y)
{
var perm = np.random.permutation(y.shape[0]);
np.random.shuffle(perm);
return (mnist.train.images[perm], mnist.train.labels[perm]);
}
获取一批样本:仅选择由batch_size变量确定的少量图像(根据Stochastic Gradient Descent方法)。
private (NDArray, NDArray) get_next_batch(NDArray x, NDArray y, int start, int end)
{
var x_batch = x[$"{start}:{end}"];
var y_batch = y[$"{start}:{end}"];
return (x_batch, y_batch);
}
- 设置超参数
在训练集中有大约55,000个图像,使用所有图像计算模型的梯度需要很长时间。 因此,我们通过随机梯度下降在优化器的每次迭代中使用一小批图像。
epoch:所有训练样例的一个前向传球和一个后传传球。
batch:一次前进/后退传递中的训练样例数。 批量大小越大,您需要的内存空间就越大。
iteration:一批前向传递和一组后向传递一组图像的训练样例。
int epochs = 10;
int batch_size = 100;
float learning_rate = 0.001f;
int display_freq = 200; // Frequency of displaying the training results
- 网络配置
第一卷积层:
int filter_size1 = 5; // Convolution filters are 5 x 5 pixels.
int num_filters1 = 16; // There are 16 of these filters.
int stride1 = 1; // The stride of the sliding window
第二卷积层:
int filter_size2 = 5; // Convolution filters are 5 x 5 pixels.
int num_filters2 = 32;// There are 32 of these filters.
int stride2 = 1; // The stride of the sliding window
完全连接层:
h1 = 128 # Number of neurons in fully-connected layer.
- 构建神经网络
让我们做一些函数来帮助构建计算图。
变量:我们需要定义两个变量W和b来构造我们的线性模型。 我们使用适当大小和初始化的Tensorflow变量来定义它们。
// Create a weight variable with appropriate initialization
private RefVariable weight_variable(string name, int[] shape)
{
var initer = tf.truncated_normal_initializer(stddev: 0.01f);
return tf.get_variable(name,
dtype: tf.float32,
shape: shape,
initializer: initer);
}
// Create a bias variable with appropriate initialization
private RefVariable bias_variable(string name, int[] shape)
{
var initial = tf.constant(0f, shape: shape, dtype: tf.float32);
return tf.get_variable(name,
dtype: tf.float32,
initializer: initial);
}
2D卷积层:此层创建卷积内核,该卷积内核与层输入卷积以产生输出张量。
private Tensor conv_layer(Tensor x, int filter_size, int num_filters, int stride, string name)
{
return with(tf.variable_scope(name), delegate {
var num_in_channel = x.shape[x.NDims - 1];
var shape = new[] { filter_size, filter_size, num_in_channel, num_filters };
var W = weight_variable("W", shape);
// var tf.summary.histogram("weight", W);
var b = bias_variable("b", new[] { num_filters });
// tf.summary.histogram("bias", b);
var layer = tf.nn.conv2d(x, W,
strides: new[] { 1, stride, stride, 1 },
padding: "SAME");
layer += b;
return tf.nn.relu(layer);
});
}
max-pooling layer:池化层。
private Tensor max_pool(Tensor x, int ksize, int stride, string name)
{
return tf.nn.max_pool(x,
ksize: new[] { 1, ksize, ksize, 1 },
strides: new[] { 1, stride, stride, 1 },
padding: "SAME",
name: name);
}
flatten_layer:展开卷积层的输出以往前馈入完全连接的层。
private Tensor flatten_layer(Tensor layer)
{
return with(tf.variable_scope("Flatten_layer"), delegate
{
var layer_shape = layer.TensorShape;
var num_features = layer_shape[new Slice(1, 4)].Size;
var layer_flat = tf.reshape(layer, new[] { -1, num_features });
return layer_flat;
});
}
完全连接层:神经网络由完全连接(密集)层的堆栈组成。 具有权重(W)和偏差(b)变量,完全连接的层被定义为激活(W x X + b)。 完整的fc_layer函数如下:
private Tensor fc_layer(Tensor x, int num_units, string name, bool use_relu = true)
{
return with(tf.variable_scope(name), delegate
{
var in_dim = x.shape[1];
var W = weight_variable("W_" + name, shape: new[] { in_dim, num_units });
var b = bias_variable("b_" + name, new[] { num_units });
var layer = tf.matmul(x, W) + b;
if (use_relu)
layer = tf.nn.relu(layer);
return layer;
});
}
输入层:现在我们需要定义适当的张量来输入我们的模型。 占位符变量是输入图像和相应标签的合适选择。 这允许我们将输入(图像和标签)更改为TensorFlow图。
with(tf.name_scope("Input"), delegate
{
// Placeholders for inputs (x) and outputs(y)
x = tf.placeholder(tf.float32, shape: (-1, img_h, img_w, n_channels), name: "X");
y = tf.placeholder(tf.float32, shape: (-1, n_classes), name: "Y");
});
占位符y是与占位符变量x中输入的图像关联的真实标签的变量。 它包含任意数量的标签,每个标签是长度为num_classes的向量,为10。
网络层:在创建适当的输入后,我们必须将它传递给我们的模型。 由于我们有神经网络,我们可以使用fc_layer方法堆叠多个完全连接的层。
var conv1 = conv_layer(x, filter_size1, num_filters1, stride1, name: "conv1");
var pool1 = max_pool(conv1, ksize: 2, stride: 2, name: "pool1");
var conv2 = conv_layer(pool1, filter_size2, num_filters2, stride2, name: "conv2");
var pool2 = max_pool(conv2, ksize: 2, stride: 2, name: "pool2");
var layer_flat = flatten_layer(pool2);
var fc1 = fc_layer(layer_flat, h1, "FC1", use_relu: true);
var output_logits = fc_layer(fc1, n_classes, "OUT", use_relu: false);
损失函数,优化器,精度,预测:创建网络后,我们必须计算损失并对其进行优化,我们必须计算预测和准确性。
with(tf.variable_scope("Train"), delegate
{
with(tf.variable_scope("Optimizer"), delegate
{
optimizer = tf.train.AdamOptimizer(learning_rate: learning_rate, name: "Adam-op").minimize(loss);
});
with(tf.variable_scope("Accuracy"), delegate
{
var correct_prediction = tf.equal(tf.argmax(output_logits, 1), tf.argmax(y, 1), name: "correct_pred");
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32), name: "accuracy");
});
with(tf.variable_scope("Prediction"), delegate
{
cls_prediction = tf.argmax(output_logits, axis: 1, name: "predictions");
});
});
初始化变量:我们必须调用变量初始化程序操作来初始化所有变量。
var init = tf.global_variables_initializer();
- 训练
创建计算图后,我们可以训练我们的模型。 为了训练模型,我们必须创建一个会话并在会话中运行图。
// Number of training iterations in each epoch
var num_tr_iter = y_train.len / batch_size;
var init = tf.global_variables_initializer();
sess.run(init);
float loss_val = 100.0f;
float accuracy_val = 0f;
foreach (var epoch in range(epochs))
{
print($"Training epoch: {epoch + 1}");
// Randomly shuffle the training data at the beginning of each epoch
(x_train, y_train) = mnist.Randomize(x_train, y_train);
foreach (var iteration in range(num_tr_iter))
{
var start = iteration * batch_size;
var end = (iteration + 1) * batch_size;
var (x_batch, y_batch) = mnist.GetNextBatch(x_train, y_train, start, end);
// Run optimization op (backprop)
sess.run(optimizer, new FeedItem(x, x_batch), new FeedItem(y, y_batch));
if (iteration % display_freq == 0)
{
// Calculate and display the batch loss and accuracy
var result = sess.run(new[] { loss, accuracy }, new FeedItem(x, x_batch), new FeedItem(y, y_batch));
loss_val = result[0];
accuracy_val = result[1];
print($"iter {iteration.ToString("000")}: Loss={loss_val.ToString("0.0000")}, Training Accuracy={accuracy_val.ToString("P")}");
}
}
// Run validation after every epoch
var results1 = sess.run(new[] { loss, accuracy }, new FeedItem(x, x_valid), new FeedItem(y, y_valid));
loss_val = results1[0];
accuracy_val = results1[1];
print("---------------------------------------------------------");
print($"Epoch: {epoch + 1}, validation loss: {loss_val.ToString("0.0000")}, validation accuracy: {accuracy_val.ToString("P")}");
print("---------------------------------------------------------");
}
- 测试
训练完成后,我们必须测试我们的模型,看看它在新数据集上的表现如何。
public void Test(Session sess)
{
var result = sess.run(new[] { loss, accuracy }, new FeedItem(x, x_test), new FeedItem(y, y_test));
loss_test = result[0];
accuracy_test = result[1];
print("---------------------------------------------------------");
print($"Test loss: {loss_test.ToString("0.0000")}, test accuracy: {accuracy_test.ToString("P")}");
print("---------------------------------------------------------");
}
结果非常好,比上一篇完全连接神经网络效果高1个百分点。
如果你想要重复这个过程,请到Github参照完整代码。
注意:本文归作者所有,未经作者允许,不得转载