Python版的手写数字识别例子很多,但是将python tensorflow编写的模型变成APP,比较简单的一种方法是将其打包成exe(大小要5~6G),然后作为子程序与其它语言编写的主界面程序直接通过控制台命令通信。这种方式虽然简单,但是有两个缺点:python部分子程序太大,而且作为子程序会被杀毒软件当成木马删掉,因此对不太熟悉电脑的用户使用比较麻烦。
另外一直方法是将python tensorflow训练好的模型转换成pb格式,然后在其它主界面程序中写模型调用代码,这种方式比较麻烦,难度系数也大。
最方便简单的一种方法就是使用tensorflow.net版(https://github.com/SciSharp)编写APP,这样神经网络部分代码和界面等部分程序代码可以合成一个整体,编译后的exe也不大。
本代码是基于SciSharp社区中的例子代码(SciSharp\SciSharp-Stack-Examples\src\TensorFlowNET.Examples\ImageProcessing\DigitRecognitionCNN.cs)改编而来。SciSharp社区中的例子代码直接用公共网上的mnist数据集进行训练。由于mnist数据集将所有图片合成在一个文件,不方便追加训练。本代码用minist数据集分解后存储于0~9个文件夹中的图片进行训练,因此可以方便的进行追加,或者用于其它符号图片的分类。
本代码主要改动之一是,在PrepareData()函数中读取0~9个文件夹中的图片并准备训练数据集。
public override void PrepareData()
{
//Directory.CreateDirectory("image_classification_cnn_v1");
//var loader = new MnistModelLoader();
//mnist = loader.LoadAsync(".resources/mnist", oneHot: true, showProgressInConsole: true).Result;
List x_list = new List();
List y_list = new List();
//load train
string[] folders = Directory.GetDirectories("image_classification_cnn_v1\\train_image");
for (int i = 0; i < folders.Length; i++)
{
string[] files = Directory.GetFiles(folders[i]);
for (int j = 0; j < files.Length; j++)
{
NDArray x = np.empty(784, np.ubyte);
x = cv2.imread(files[j], SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
x_list.Add(x);
NDArray y = np.empty(10, np.float32);
y[i] = 1f;
y_list.Add(y);
}
}
x_train = np.zeros((x_list.Count, 784), np.ubyte);
y_train = np.zeros((y_list.Count, 10), np.float32);
for(int i = 0; i < x_list.Count; i++)
{
x_train[i] = x_list[i];
y_train[i] = y_list[i];
}
(x_train, y_train) = Reformat(x_train, y_train);
//load valid
folders = Directory.GetDirectories("image_classification_cnn_v1\\valid_image");
x_list.Clear();
y_list.Clear();
for (int i = 0; i < folders.Length; i++)
{
string[] files = Directory.GetFiles(folders[i]);
for (int j = 0; j < files.Length; j++)
{
NDArray x = np.empty(784, np.ubyte);
x = cv2.imread(files[j], SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
x_list.Add(x);
NDArray y = np.empty(10, np.float32);
//for (int k = 0; k < 10; k++)
// y[k] = 0f;
y[i] = 1f;
y_list.Add(y);
}
}
x_valid = np.zeros((x_list.Count, 784), np.ubyte);
y_valid = np.zeros((y_list.Count, 10), np.float32);
for (int i = 0; i < x_list.Count; i++)
{
x_valid[i] = x_list[i];
y_valid[i] = y_list[i];
}
(x_valid, y_valid) = Reformat(x_valid, y_valid);
//(x_valid, y_valid) = Reformat(mnist.Validation.Data, mnist.Validation.Labels);
//(x_test, y_test) = Reformat(mnist.Test.Data, mnist.Test.Labels);
print("Size of:");
print($"- Training-set:\t\t{len(x_train)}");
print($"- Validation-set:\t{len(x_valid)}");
// generate labels
var labels = range(0, 10).Select(x => x.ToString());
File.WriteAllLines(@"image_classification_cnn_v1\labels.txt", labels);
}
首先通过代码
NDArray x = np.empty(784, np.ubyte);
x = cv2.imread(files[j], SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
将图片导成(28,28)的NDArray,当所有图片导入到List x_list后,再通过代码
for(int i = 0; i < x_list.Count; i++)
{
x_train[i] = x_list[i];
y_train[i] = y_list[i];
}
(x_train, y_train) = Reformat(x_train, y_train);
将List x_list转换成NDArray x_train,其中train是一个(60000,28,28,1)的NDArray,60000是图片的个数。
本代码另一个主要改动是,在Predict()函数中增加了用28*28的黑底图片进行手写数字的识别。
public override void Predict()
{
// predict image
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask(new TaskOptions
{
LabelPath = @"image_classification_cnn_v1\labels.txt",
ModelPath = @"image_classification_cnn_v1\saved_model.pb"
});
NDArray input = np.empty(784, np.ubyte);
input = cv2.imread("image_classification_cnn_v1\\test_image\\0_2.jpg", SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
Shape shape = new Shape(1, 28, 28, 1);
NDArray newinput = input.reshape(shape);
newinput = newinput.astype(TF_DataType.TF_FLOAT);
var result = task.Predict(newinput);
string ss = result.Label;
//var input = x_valid["0:1"];
//long output = np.argmax(y_test[0]);
//Debug.Assert(result.Label == output.ToString());
//input = x_test["1:2"];
//result = task.Predict(input);
//output = np.argmax(y_test[1]);
//Debug.Assert(result.Label == output.ToString());
}
首先通过代码
NDArray input = np.empty(784, np.ubyte);
input = cv2.imread("image_classification_cnn_v1\\test_image\\0_2.jpg",
SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
将图片导成(28,28)的NDArray,然后再通过
Shape shape = new Shape(1, 28, 28, 1);
NDArray newinput = input.reshape(shape);
newinput = newinput.astype(TF_DataType.TF_FLOAT);
将input转换为(1, 28, 28, 1)且为TF_FLOAT类型的NDArray,接下来直接输入task.Predict()函数就可以预测啦。
4. 整个文件代码如下:
using SciSharp.Models;
using SciSharp.Models.ImageClassification;
using System.Diagnostics;
using System.IO;
using System.Linq;
using Tensorflow;
using Tensorflow.NumPy;
using static Tensorflow.Binding;
using static SharpCV.Binding;
using Tensorflow.Keras.Metrics;
using System.Collections.Generic;
namespace TensorFlowNET.Examples
{
///
/// Convolutional Neural Network classifier for Hand Written Digits
/// CNN architecture with two convolutional layers, followed by two fully-connected layers at the end.
/// Use Stochastic Gradient Descent (SGD) optimizer.
/// https://www.easy-tensorflow.com/tf-tutorials/convolutional-neural-nets-cnns/cnn1
///
public class DigitRecognitionCNN : SciSharpExample, IExample
{
//Datasets mnist;
float accuracy_test = 0f;
NDArray x_train, y_train;
NDArray x_valid, y_valid;
//NDArray x_test, y_test;
public ExampleConfig InitConfig()
=> Config = new ExampleConfig
{
Name = "MNIST CNN (Graph)",
Enabled = true
};
public bool Run()
{
PrepareData();
Train();
//Test();
Predict();
return accuracy_test > 0.95;
}
public override void Train()
{
// using wizard to train model
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask(new TaskOptions
{
InputShape = (28, 28, 1),
NumberOfClass = 10,
});
task.SetModelArgs(new ConvArgs
{
NumberOfNeurons = 128
});
task.Train(new TrainingOptions
{
Epochs = 50,
TrainingData = new FeatureAndLabel(x_train, y_train),
ValidationData = new FeatureAndLabel(x_valid, y_valid)
});
}
//public override void Test()
//{
// var wizard = new ModelWizard();
// var task = wizard.AddImageClassificationTask(new TaskOptions
// {
// ModelPath = @"image_classification_cnn_v1\saved_model.pb"
// });
// var result = task.Test(new TestingOptions
// {
// TestingData = new FeatureAndLabel(x_test, y_test)
// });
// accuracy_test = result.Accuracy;
//}
public override void Predict()
{
// predict image
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask(new TaskOptions
{
LabelPath = @"image_classification_cnn_v1\labels.txt",
ModelPath = @"image_classification_cnn_v1\saved_model.pb"
});
NDArray input = np.empty(784, np.ubyte);
input = cv2.imread("image_classification_cnn_v1\\test_image\\0_2.jpg", SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
Shape shape = new Shape(1, 28, 28, 1);
NDArray newinput = input.reshape(shape);
newinput = newinput.astype(TF_DataType.TF_FLOAT);
var result = task.Predict(newinput);
string ss = result.Label;
//var input = x_valid["0:1"];
//long output = np.argmax(y_test[0]);
//Debug.Assert(result.Label == output.ToString());
//input = x_test["1:2"];
//result = task.Predict(input);
//output = np.argmax(y_test[1]);
//Debug.Assert(result.Label == output.ToString());
}
public override void PrepareData()
{
//Directory.CreateDirectory("image_classification_cnn_v1");
//var loader = new MnistModelLoader();
//mnist = loader.LoadAsync(".resources/mnist", oneHot: true, showProgressInConsole: true).Result;
List x_list = new List();
List y_list = new List();
//load train
string[] folders = Directory.GetDirectories("image_classification_cnn_v1\\train_image");
for (int i = 0; i < folders.Length; i++)
{
string[] files = Directory.GetFiles(folders[i]);
for (int j = 0; j < files.Length; j++)
{
NDArray x = np.empty(784, np.ubyte);
x = cv2.imread(files[j], SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
x_list.Add(x);
NDArray y = np.empty(10, np.float32);
y[i] = 1f;
y_list.Add(y);
}
}
x_train = np.zeros((x_list.Count, 784), np.ubyte);
y_train = np.zeros((y_list.Count, 10), np.float32);
for(int i = 0; i < x_list.Count; i++)
{
x_train[i] = x_list[i];
y_train[i] = y_list[i];
}
(x_train, y_train) = Reformat(x_train, y_train);
//load valid
folders = Directory.GetDirectories("image_classification_cnn_v1\\valid_image");
x_list.Clear();
y_list.Clear();
for (int i = 0; i < folders.Length; i++)
{
string[] files = Directory.GetFiles(folders[i]);
for (int j = 0; j < files.Length; j++)
{
NDArray x = np.empty(784, np.ubyte);
x = cv2.imread(files[j], SharpCV.IMREAD_COLOR.IMREAD_GRAYSCALE);
x_list.Add(x);
NDArray y = np.empty(10, np.float32);
//for (int k = 0; k < 10; k++)
// y[k] = 0f;
y[i] = 1f;
y_list.Add(y);
}
}
x_valid = np.zeros((x_list.Count, 784), np.ubyte);
y_valid = np.zeros((y_list.Count, 10), np.float32);
for (int i = 0; i < x_list.Count; i++)
{
x_valid[i] = x_list[i];
y_valid[i] = y_list[i];
}
(x_valid, y_valid) = Reformat(x_valid, y_valid);
//(x_valid, y_valid) = Reformat(mnist.Validation.Data, mnist.Validation.Labels);
//(x_test, y_test) = Reformat(mnist.Test.Data, mnist.Test.Labels);
print("Size of:");
print($"- Training-set:\t\t{len(x_train)}");
print($"- Validation-set:\t{len(x_valid)}");
// generate labels
var labels = range(0, 10).Select(x => x.ToString());
File.WriteAllLines(@"image_classification_cnn_v1\labels.txt", labels);
}
///
/// Reformats the data to the format acceptable for convolutional layers
///
///
///
///
private (NDArray, NDArray) Reformat(NDArray x, NDArray y)
{
var (unique_y, _) = np.unique(np.argmax(y, 1));
var (img_size, num_ch, num_class) = ((int)np.sqrt(x.shape[1]).astype(np.int32), 1, len(unique_y));
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);
}
}
}