在本文中,我们将研究一个卷积神经网络来解决硬币识别问题,并且我们将在Keras.NET中实现一个卷积神经网络。
在这里,我们将介绍卷积神经网络(CNN),并提出一个CNN的架构,我们将训练它来识别硬币。
什么是CNN?正如我们在本系列的前一篇文章中提到的,CNN是一类经常用于图像分类任务的神经网络(NN),比如物体和人脸识别。在CNN中,并非每个节点都连接到下一层的所有节点。这种部分连通性有助于防止在完全连接的网络神经网络中出现的过拟合问题,并且加速了神经网络的收敛速度。
围绕CNN的核心概念是一种被称为卷积的数学运算,卷积在数字信号处理领域非常常见。卷积被定义为两个函数的乘积,产生的第三个函数表示前两个函数之间的重叠量。
在物体识别中,卷积操作允许我们检测图像中的不同特征,如垂直和水平的边缘,纹理和曲线。这就是为什么任何CNN的第一层都是卷积层。
CNN中另一个常见的层是池化层。池化用于减少图像表示的大小,这意味着减少参数的数量,并最终减少计算量。最常见的池化类型是最大池化,它在每个位置上从匹配的单元格组中获取最大值。最后,根据所获得的最大值建立新的图像。
另一个与卷积相关的概念是填充。填充保证了卷积过程将均匀地发生在整个图像,包括边界像素。这个保证是由一个零像素的边框支持的,该边框在缩小后的图像周围添加(在池化之后),以便可以以相同的次数访问图像的所有像素。
最常见的CNN架构通常从卷积层开始,接着是激活层,然后是池化层,最后是传统的全连接网络,比如多层NN。这种类型的模型是层次化的,称为顺序模型。为什么以一个完全连接的网络结束?为了学习转换后图像(经过卷积和池化)中的特征的非线性组合。
下面是我们将在CNN中实现的架构:
该体系结构遵循了一种用于物体识别的CNN体系结构模式;层参数通过实验进行了微调。
我们经过的参数微调过程的结果部分存储在Settings类中,我们在这里展示:
public class Settings { public const int ImgWidth = 64; public const int ImgHeight = 64; public const int MaxValue = 255; public const int MinValue = 0; public const int Channels = 3; public const int BatchSize = 12; public const int Epochs = 10; public const int FullyConnectedNodes = 512; public const string LossFunction = "categorical_crossentropy"; public const string Accuracy = "accuracy"; public const string ActivationFunction = "relu"; public const string PaddingMode = "same"; public static StringOrInstance Optimizer = new RMSprop(lr: Lr, decay: Decay); private const float Lr = 0.0001f; private const float Decay = 1e-6f; }
我们现在有了CNN的体系结构。接下来,我们将研究使用Keras.NET实现的用于硬币识别的CNN。
首先,让我们从Nuget包管理器下载Keras.NET包。我们可以在Tools > Nuget package manager中找到Nuget包管理器。Keras.NET依赖于包Numpy.NET和pythonnet_netstandard。如果没有安装它们,让我们继续安装它们。
需要指出的是,Keras.NET 需要在你的操作系统中安装Python 2.7-3.7版本。它还需要安装Python库Numpy和TensorFlow。在本例中,我们使用的是64位的Python 3.7。
如果在执行本文中的代码时遇到任何问题,请在控制台应用程序中主方法的开始执行时尝试运行以下代码一次。这段代码将设置所需的环境变量,以便找到所有dll:
private static void SetupPyEnv() { string envPythonHome = @"C:\Users\arnal\AppData\Local\Programs\Python\Python37\"; string envPythonLib = envPythonHome + "Lib\\;" + envPythonHome + @"Lib\site-packages\"; Environment.SetEnvironmentVariable("PYTHONHOME", envPythonHome, EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("PATH", envPythonHome + ";" + envPythonLib + ";" + Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Machine), EnvironmentVariableTarget.Process); Environment.SetEnvironmentVariable("PYTHONPATH", envPythonLib, EnvironmentVariableTarget.User); PythonEngine.PythonHome = envPythonHome; PythonEngine.PythonPath = Environment.GetEnvironmentVariable("PYTHONPATH"); }
现在我们将看到使用Keras.NET创建我们的硬币识别CNN是多么简单和透明。下面的类显示了包含模型的所有逻辑的Cnn类。
public class Cnn { private DataSet _dataset; private Sequential _model; public Cnn(DataSet dataset) { _dataset = dataset; _model = new Sequential(); } public void Train() { // Build CNN model _model.Add(new Conv2D(32, kernel_size: (3, 3).ToTuple(), padding: Settings.PaddingMode, input_shape: new Shape(Settings.ImgWidth, Settings.ImgHeight, Settings.Channels))); _model.Add(new Activation(Settings.ActivationFunction)); _model.Add(new Conv2D(32, (3, 3).ToTuple())); _model.Add(new Activation(Settings.ActivationFunction)); _model.Add(new MaxPooling2D(pool_size: (2, 2).ToTuple())); _model.Add(new Dropout(0.25)); _model.Add(new Conv2D(64, kernel_size: (3, 3).ToTuple(), padding: Settings.PaddingMode)); _model.Add(new Activation(Settings.ActivationFunction)); _model.Add(new Conv2D(64, (3, 3).ToTuple())); _model.Add(new Activation(Settings.ActivationFunction)); _model.Add(new MaxPooling2D(pool_size: (2, 2).ToTuple())); _model.Add(new Dropout(0.25)); _model.Add(new Flatten()); _model.Add(new Dense(Settings.FullyConnectedNodes)); _model.Add(new Activation(Settings.ActivationFunction)); _model.Add(new Dropout(0.5)); _model.Add(new Dense(_dataset.NumberClasses)); _model.Add(new Softmax()); _model.Compile(loss: Settings.LossFunction, optimizer: Settings.Optimizer, metrics: new string[] { Settings.Accuracy }); _model.Fit(_dataset.TrainX, _dataset.TrainY, batch_size: Settings.BatchSize, epochs: Settings.Epochs, validation_data: new NDarray[] { _dataset.ValidationX, _dataset.ValidationY }); var score = _model.Evaluate(_dataset.ValidationX, _dataset.ValidationY, verbose: 0); Console.WriteLine("Test loss:" + score[0]); Console.WriteLine("Test accuracy:" + score[1]); } public NDarray Predict(string imgPath) { NDarray x = Utils.Normalize(imgPath); x = x.reshape(1, x.shape[0], x.shape[1], x.shape[2]); return _model.Predict(x); } }
如我们所见,我们首先有一个构造函数,用于接收数据集(在本系列的第二篇文章中导入和处理),并创建Sequential类的新实例存储在私有变量_model中。Sequential是什么?这是一个空模型,它给了我们叠加层次的可能性,而这正是我们所需要的。
然后,在Train方法中,我们首先按照上一篇文章中介绍的架构创建我们的层堆栈,然后编译模型并调用fit方法开始训练。使用的损失函数是categorical_crossentropy。什么是损失函数?它是我们用来优化学习过程的函数,也就是说,我们要么最小化它,要么最大化它。负责最小化损失函数的是优化器——一种通过改变网络的权重和学习率来最小化损失的算法。
最后,利用验证数据集对模型进行评估。另一个方法是Predict,顾名思义,它预测新输入数据的标签。训练结束后,应调用此方法。开始训练阶段就像运行以下代码一样简单:
var cnn = new Cnn(dataSet); cnn.Train();
让我们来看看在这个系列中我们正在经历的硬币识别问题的训练中所获得的结果:
我们可以看到,在训练过程中,我们能够达到100%的准确率。在prediction方法中,它的输出将是一个NDarray,它包含了物体或图像属于CNN某个类的概率。
那么,什么样的架构需要GPU而不是CPU呢?例如,AlexNet体系结构包括5个卷积层和3个完全连接的层,以及池化和激活层。这种类型的深度CNN由于其复杂性,在GPU上表现得更好。一般的规则是,你添加的层越多,权重的计算就会越复杂。
在了解了如何编写自己的CNN之后,我们将进入预训练模型的领域。下一篇文章将详细介绍这一点!
欢迎关注我的公众号,如果你有喜欢的外文技术文章,可以通过公众号留言推荐给我。