教程名称:使用 C# 入门深度学习

作者:痴者工良

教程地址:https://torch.whuanle.cn

电子书仓库:https://github.com/whuanle/cs_pytorch

Maomi.Torch 项目仓库:https://github.com/whuanle/Maomi.Torch

使用 Torch 训练模型

本章主要参考《破解深度学习》的第四章,在本章将会实现一个数字分类器,主要包括数据加载和处理、模型训练和保存、预训练模型加载,但是内容跟 开始使用 Torch 一章差不多,只是数据集和网络定义不一样,通过本章的案例帮助读者进一步了解 TorchSharp 以及掌握模型训练的步骤和基础。

本章代码请参考 example2.3。

搭建神经网络的一般步骤:

在上一篇中我们通过示例已经学习到相关的过程,所以本章会在之前的基础上继续讲解一些细节和步骤。

在上一章中,我们学习了如何下载和加载数据集,如果将数据集里面的图片导出,我们可以发现里面都是单个数字。

你可以使用 Maomi.Torch 包中的扩展方法将数据集转存到本地目录中。

for (int i = 0; i < training_data.Count; i++)
{
var dic = training_data.GetTensor(i);
var img = dic["data"];
var label = dic["label"]; img.SaveJpeg("imgs/{i}.jpg");
}

如图所示:

每个图片的大小是 28*28=784,所以神经网络的输入层的大小是 784。

我们直接知道,由于数据集的图片都是 0-9 的数字,都是灰度图像(没有彩色),因此模型训练结果的输出应该是 10 个,也就是神经网络的输出层神经元个数是 10。

神经网络的输入层是要固定大小是,表示神经元的个数输入是固定的,不是随时可以扩充的,也就是一个神经网络不能输入任意大小的图像,这些图像都要经过一定的算法出来,生成与神经网络输入层对应大小的图像。

定义神经网络

第一步,定义我们的网络模型,这是一个全连接网络,由激活函数和三个线性层组成。

该网络模型没有指定输入层和输出层的大小,这样该模型可以适配不同的图像分类任务,开发者在训练和加载模式时,指定输入层和输出层大小即可。

代码如下所示:

using TorchSharp;
using static TorchSharp.torch; using nn = TorchSharp.torch.nn; public class MLP : nn.Module<Tensor, Tensor>, IDisposable
{
private readonly int _inputSize;
private readonly int _hiddenSize;
private readonly int _numClasses; private TorchSharp.Modules.Linear fc1;
private TorchSharp.Modules.ReLU relu;
private TorchSharp.Modules.Linear fc2;
private TorchSharp.Modules.Linear fc3; /// <summary></summary>
/// <param name="inputSize">输入层大小,图片的宽*高.</param>
/// <param name="hiddenSize">隐藏层大小.</param>
/// <param name="outputSize">输出层大小,例如有多少个分类.</param>
/// <param name="device"></param>
public MLP(int inputSize, int hiddenSize, int outputSize) : base(nameof(MLP))
{
_inputSize = inputSize;
_hiddenSize = hiddenSize;
_numClasses = outputSize; // 定义激活函数和线性层
relu = nn.ReLU();
fc1 = nn.Linear(inputSize, hiddenSize);
fc2 = nn.Linear(hiddenSize, hiddenSize);
fc3 = nn.Linear(hiddenSize, outputSize); RegisterComponents();
} public override torch.Tensor forward(torch.Tensor input)
{
// 一层一层传递
// 第一层读取输入,然后传递给激活函数,
// 第二层读取第一层的输出,然后传递给激活函数,
// 第三层读取第二层的输出,然后生成输出结果
var @out = fc1.call(input);
@out = relu.call(@out);
@out = fc2.call(@out);
@out = relu.call(@out);
@out = fc3.call(@out);
return @out;
} protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
fc1.Dispose();
relu.Dispose();
fc2.Dispose();
fc3.Dispose();
}
}

首先 fc1 作为第一层网络,输入的图像需要转换为一维结构,主要用于接收数据、数据预处理。由于绘图太麻烦了,这里用文字简单说明一下,例如图像是 28*28,也就是每行有 28 个像素,一共 28 行,那么使用一个 784 大小的数组可以将图像的每一行首尾连在一起,放到一个一维数组中。

由于图像都是灰度图像,一个黑白像素值在 0-255 之间(byte 类型),如果使用 [0.0,1.0] 之间表示黑白(float32 类型),那么输入像素表示为灰度,值为 0.0 表示白色,值为 1.0 表示黑色,中间数值表示灰度。

大多数情况下,或者说在本教程中,图像的像素都是使用 float32 类型表示,即 torch.Tensor 存储的图像信息都是 float32 类型表示一个像素。

图来自《深入浅出神经网络与深度学习》。

fc2 是隐藏层,在本章示范的网络模型中,隐藏层只有一层,大小是 15 个神经元,承担者特征提取、非线性变换等职责,隐藏层的神经元数量是不定的,主要是根据经验来设置,然后根据训练的模型性能来调整。

fc3 是输出层,根据提取的特征将输出推送到 10 个神经元中,每个神经元表示一个数值,每个神经元都会接收到消息,但是因为不同数字的特征和权重值不一样,所以每个神经元的值都不一样,接收到的值就是表示当前数字的可能性概率。

加载数据集

加载数据集的代码示例如下,由于上一章已经讲解过,因此这里就不再赘述。

// 1. 加载数据集

// 从 MNIST 数据集下载数据或者加载已经下载的数据
using var train_data = datasets.MNIST("./mnist/data", train: true, download: true, target_transform: transforms.ConvertImageDtype(ScalarType.Float32));
using var test_data = datasets.MNIST("./mnist/data", train: false, download: true, target_transform: transforms.ConvertImageDtype(ScalarType.Float32)); Console.WriteLine("Train data size: " + train_data.Count);
Console.WriteLine("Test data size: " + test_data.Count); var batch_size = 100;
// 分批加载图像,打乱顺序
var train_loader = torch.utils.data.DataLoader(train_data, batchSize: batch_size, shuffle: true, defaultDevice); // 分批加载图像,不打乱顺序
var test_loader = torch.utils.data.DataLoader(test_data, batchSize: batch_size, shuffle: false, defaultDevice);

创建网络模型

由于 MNIST 数据集的图像都是 28*28 的,因此我们创建网络模型实例时,定义输入层为 784 大小。

// 输入层大小,按图片的宽高计算
var input_size = 28 * 28; // 隐藏层大小,大小不固定,可以自己调整
var hidden_size = 15; // 手动配置分类结果个数
var num_classes = 10; var model = new MLP(input_size, hidden_size, num_classes);
model.to(defaultDevice);

定义损失函数

创建损失函数和优化器,这个学习率的大小也是依据经验和性能进行设置,没有什么规律,学习率的作用可以参考梯度下降算法中的知识。

// 创建损失函数
var criterion = nn.CrossEntropyLoss(); // 学习率
var learning_rate = 0.001; // 优化器
var optimizer = optim.Adam(model.parameters(), lr: learning_rate);

训练

开始训练模型,对数据集进行 10 轮训练,每轮训练都输出训练结果,这里不使用一张张图片测试准确率,而是一次性识别所有图片(一万张),然后计算平均准确率。

foreach (var epoch in Enumerable.Range(0, num_epochs))
{
model.train();
int i = 0;
foreach (var item in train_loader)
{
var images = item["data"];
var lables = item["label"]; images = images.reshape(-1, 28 * 28);
var outputs = model.call(images); var loss = criterion.call(outputs, lables); optimizer.zero_grad(); loss.backward(); optimizer.step(); i++;
if ((i + 1) % 300 == 0)
{
Console.WriteLine("Epoch [{(epoch + 1)}/{num_epochs}], Step [{(i + 1)}/{train_data.Count / batch_size}], Loss: {loss.ToSingle():F4}");
}
} model.eval();
using (torch.no_grad())
{
long correct = 0;
long total = 0; foreach (var item in test_loader)
{
var images = item["data"];
var labels = item["label"]; images = images.reshape(-1, 28 * 28);
var outputs = model.call(images); var (_, predicted) = torch.max(outputs, 1);
total += labels.size(0);
correct += (predicted == labels).sum().item<long>();
}
Console.WriteLine("Accuracy of the network on the 10000 test images: {100 * correct / total} %");
}
}

保存训练后的模型:

model.save("mnist_mlp_model.dat");

训练信息:

识别手写图像

如下示例图像所示,是一个手写数字。

重新加载模型:

model.save("mnist_mlp_model.dat");
model.load("mnist_mlp_model.dat"); // 把模型转为评估模式
model.eval();

使用 Maomi.Torch 导入图片并转为 Tensor,然后将 28*28 转换为以为的 784

由于加载图像的时候默认是彩色的,所以需要将其转换为灰度图像,即 channels=1

// 加载图片为张量
var image = MM.LoadImage("5.jpg", channels: 1);
image = image.to(defaultDevice);
image = image.reshape(-1, 28 * 28);

识别图像并输出结果:

using (torch.no_grad())
{
var oputput = model.call(image);
var prediction = oputput.argmax(dim: 1, keepdim: true);
Console.WriteLine("Predicted Digit: " + prediction.item<long>().ToString());
}

当然,对应彩色的图像,也可以这样通过灰度转换处理,再进行层归一化,即可获得对应结构的 torch.Tensor。

image = image.reshape(-1, 28 * 28);

var transform = transforms.ConvertImageDtype(ScalarType.Float32);
var img = transform.call(image).unsqueeze(0);

再如下图所示,随便搞了个数字,图像是 212*212,图像格式是 jpg。

注意,由于数据集的图片都是 jpg 格式,因此要识别的图像,也需要使用 jpg 格式。

如下代码所示,首先使用 Maomi.Torch 加载图片,然后调整图像大小为 28*28,以区配网络模型的输入层大小。

// 加载图片为张量
image = MM.LoadImage("6.jpg", channels: 1);
image = image.to(defaultDevice); // 将图像转换为 28*28 大小
image = transforms.Resize(28, 28).call(image);
image = image.reshape(-1, 28 * 28); using (torch.no_grad())
{
var oputput = model.call(image);
var prediction = oputput.argmax(dim: 1, keepdim: true);
Console.WriteLine("Predicted Digit: " + prediction.item<long>().ToString());
}

C# 深度学习框架 TorchSharp 原生训练模型和图像识别-自定义网络模型和识别手写数字的更多相关文章

  1. 一文全解:利用谷歌深度学习框架Tensorflow识别手写数字图片(初学者篇)

    笔记整理者:王小草 笔记整理时间2017年2月24日 原文地址 http://blog.csdn.net/sinat_33761963/article/details/56837466?fps=1&a ...

  2. 学习笔记TF024:TensorFlow实现Softmax Regression(回归)识别手写数字

    TensorFlow实现Softmax Regression(回归)识别手写数字.MNIST(Mixed National Institute of Standards and Technology ...

  3. 深度学习练手项目——DNN识别手写数字

    该案例主要目的是为了熟悉Keras基本用法,以及了解DNN基本流程. 示例代码: import numpy as np import matplotlib.pyplot as plt from ker ...

  4. MindSpore手写数字识别初体验,深度学习也没那么神秘嘛

    摘要:想了解深度学习却又无从下手,不如从手写数字识别模型训练开始吧! 深度学习作为机器学习分支之一,应用日益广泛.语音识别.自动机器翻译.即时视觉翻译.刷脸支付.人脸考勤--不知不觉,深度学习已经渗入 ...

  5. 【深度学习系列】手写数字识别卷积神经--卷积神经网络CNN原理详解(一)

    上篇文章我们给出了用paddlepaddle来做手写数字识别的示例,并对网络结构进行到了调整,提高了识别的精度.有的同学表示不是很理解原理,为什么传统的机器学习算法,简单的神经网络(如多层感知机)都可 ...

  6. SVM学习笔记(二)----手写数字识别

    引言 上一篇博客整理了一下SVM分类算法的基本理论问题,它分类的基本思想是利用最大间隔进行分类,处理非线性问题是通过核函数将特征向量映射到高维空间,从而变成线性可分的,但是运算却是在低维空间运行的.考 ...

  7. 基于Theano的深度学习框架keras及配合SVM训练模型

    https://blog.csdn.net/a819825294/article/details/51334397 1.介绍 Keras是基于Theano的一个深度学习框架,它的设计参考了Torch, ...

  8. TensorFlow与主流深度学习框架对比

    引言:AlphaGo在2017年年初化身Master,在弈城和野狐等平台上横扫中日韩围棋高手,取得60连胜,未尝败绩.AlphaGo背后神秘的推动力就是TensorFlow--Google于2015年 ...

  9. 转:TensorFlow和Caffe、MXNet、Keras等其他深度学习框架的对比

    http://geek.csdn.net/news/detail/138968 Google近日发布了TensorFlow 1.0候选版,这第一个稳定版将是深度学习框架发展中的里程碑的一步.自Tens ...

  10. 28款GitHub最流行的开源机器学习项目,推荐GitHub上10 个开源深度学习框架

    20 个顶尖的 Python 机器学习开源项目 机器学习 2015-06-08 22:44:30 发布 您的评价: 0.0 收藏 1收藏 我们在Github上的贡献者和提交者之中检查了用Python语 ...

随机推荐

  1. spring IOC 实现原理模拟实现

    原文系装载: http://blog.sina.com.cn/s/blog_6a7f00ed01011dyv.html 最近对spring IOC AOP 机制实现原理了解了下,在此做下整理,希望能给 ...

  2. 移动端自动化之Autox.js

    github: https://github.com/kkevsekk1/AutoX 官方文档:http://doc.autoxjs.com/ 1. 安装vscode的扩展插件 如果之前有使用 aut ...

  3. MySQL底层概述—1.InnoDB内存结构

    大纲 1.InnoDB引擎架构 2.Buffer Pool 3.Page管理机制之Page页分类 4.Page管理机制之Page页管理 5.Change Buffer 6.Log Buffer 1.I ...

  4. 编译器-FIRST集合

      语法分析器的两个重要函数 FIRST和FOLLOW FIRST的定义 FIRST(α),可从α推导得到的串的首符号的集合 1.如果X是一个终结符,那么FIRST(X) = X 2.如果X是一个非终 ...

  5. 物联网CC2530按键单双击分别控制两灯

    (1)确定思路单击和双击的效果分别是怎样的(此文章采用简单的延时函数不涉及中断).首先可以定义一个普通延时delay和一个标志位count变量,这里需有个延时阈值咱们直接可以宏定义B值(这里需要注意宏 ...

  6. Redis原理—4.核心原理摘要

    大纲 1.Redis服务器的Socket网络连接建立 2.Redis多路复用监听与文件事件模型 3.基于队列串行化的文件事件处理机制 4.完整的Redis Server网络通信流程 5.Redis串行 ...

  7. ASP.NET Core IHostBuilder

    HostBuilder 很显然,HostBuildr 就是用来构建 Host 的构建器. IHostBuilder 定义 通过 Build() 方法,构建器返回构建的 IHost 对象实例. 具体怎么 ...

  8. 推荐一个C#轻量级矢量图形库

    推荐一个轻量级矢量图形库,可用于生成 PDF.SVG.PNG等. 01 项目简介 VectSharp 是一个功能强大的 C# 库,专门用于创建矢量图形,包括文本,不依赖任何第三方,支持跨平台运行,包括 ...

  9. 龙哥量化:TB交易开拓者_趋势跟踪策略_多策略对单品种_A00011880206期货量化策略,严格的用样本内参数, 跑样本外数据,滚动测试未来行情

    如果您需要代写技术指标公式, 请联系我. 龙哥QQ:591438821 龙哥微信:Long622889 也可以把您的通达信,文华技术指标改成TB交易开拓者的自动交易量化策略. 量化策略介绍 投资标的: ...

  10. 操作系统messages文件位置更改

    问题现象 在遇到一个问题的时候需要查看操作系统的日志,但是到操作系统上的 /var/log/messages 文件中发现该文件已经很久没有刷新过了. 这个系统之前是做过安全加固的,我怀疑它改过操作系统 ...