京东-优惠雷达
无畏Pro 14限量抢购
冷藏饮料节
最高12期免息再送支架
自营热卖

.NET粉也可以玩卷积神经网络啦

流浪艺人 1年前   阅读数 129 0

在本文中,我们将实现一个简单的卷积神经网络模型。 我们将实现此模型以对MNIST数据集进行分类。我们要构建的神经网络的结构如下。 MNIST数据的手写数字图像有10个类(从0到9)。 网络有2个卷积层,最后是2个全连接层。

开始代码实现:

  1. 准备数据

MNIST是手写数字的数据集,包含55,000个用于训练的示例,5,000个用于验证的示例和10,000个用于测试的示例。 这些数字已经过尺寸标准化,并且以固定尺寸的图像(28 x 28像素)为中心,其值为0和1.每个图像已被展平并转换为784个特征的1-D阵列。 它也是深度学习的基准数据集。

先导入必要的机器学习库TensorFlow.NETNumSharp.

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

  1. 设置超参数

在训练集中有大约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


  1. 网络配置

第一卷积层:

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.


  1. 构建神经网络

让我们做一些函数来帮助构建计算图。

变量:我们需要定义两个变量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();

  1. 训练

创建计算图后,我们可以训练我们的模型。 为了训练模型,我们必须创建一个会话并在会话中运行图。

// 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("---------------------------------------------------------");
}

  1. 测试

训练完成后,我们必须测试我们的模型,看看它在新数据集上的表现如何。

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参照完整代码。


注意:本文归作者所有,未经作者允许,不得转载

全部评论: 0

    我有话说: