当前位置: 首页 > news >正文

C# 深度学习框架 TorchSharp 原生训练模型和图像识别-手写数字识别

创建一个控制台项目,示例代码参考example2.2,通过 nuget 引入以下类库:

TorchSharp TorchSharp-cuda-windows TorchVision Maomi.Torch

首先添加以下代码,查找最适合当前设备的工作方式,主要是选择 GPU 开发框架,例如 CUDA、MPS,CPU,有 GPU 就用 GPU,没有 GPU 降级为 CPU。

using Maomi.Torch; Device defaultDevice = MM.GetOpTimalDevice(); torch.set_default_device(defaultDevice); Console.WriteLine("当前正在使用 {defaultDevice}");

下载数据集

训练模型最重要的一步是准备数据,但是准备数据集是一个非常繁杂和耗时间的事情,对于初学者来说也不现实,所以 Pytorch 官方在框架集成了一些常见的数据集,开发者可以直接通过 API 使用这些提前处理好的数据集和标签。

Pytorch 使用torch.utils.data.Dataset表示数据集抽象接口,存储了数据集的样本和对应标签;torch.utils.data.DataLoader表示加载数据集的抽象接口,主要是提供了迭代器。这两套接口是非常重要的,对于开发者自定义的数据集,需要实现这两套接口,自定义加载数据集方式。

Pytorch 有三大领域的类库,分别是 TorchText、TorchVision、TorchAudio,这三个库都自带了一些常用开源数据集,但是 .NET 里社区仓库只提供了 TorchVision,生态严重落后于 Pytorch。TorchVision 是一个工具集,可以从 Fashion-MNIST 等下载数据集以及进行一些数据类型转换等功能。

在本章中,使用的数据集叫 FashionMNIST,Pytorch 还提供了很多数据集,感兴趣的读者参考:https://pytorch.org/vision/stable/datasets.html

现在开始讲解如何通过 TorchSharp 框架加载 FashionMNIST 数据集,首先添加引用:

using TorchSharp; using static TorchSharp.torch; using datasets = TorchSharp.torchvision.datasets; using transforms = TorchSharp.torchvision.transforms;

然后通过接口加载训练数据集和测试数据集:

// 指定训练数据集 var training_data = datasets.FashionMNIST( root: "data", // 数据集在那个目录下 train: true, // 加载该数据集,用于训练 download: true, // 如果数据集不存在,是否下载 target_transform: transforms.ConvertImageDtype(ScalarType.Float32) // 指定特征和标签转换,将标签转换为Float32 ); // 指定测试数据集 var test_data = datasets.FashionMNIST( root: "data", // 数据集在那个目录下 train: false, // 加载该数据集,用于训练 download: true, // 如果数据集不存在,是否下载 target_transform: transforms.ConvertImageDtype(ScalarType.Float32) // 指定特征和标签转换,将标签转换为Float32 );

部分参数解释如下:

  • root是存放训练/测试数据的路径。
  • train指定训练或测试数据集。
  • download=True如果root中没有数据,则从互联网下载数据。
  • transformtarget_transform指定特征和标签转换。

注意,与 Python 版本有所差异, Pytorch 官方给出了ToTensor()函数用于将图像转换为 torch.Tensor 张量类型,但是由于 C# 版本并没有这个函数,因此只能手动指定一个转换器。

启动项目,会自动下载数据集,接着在程序运行目录下会自动创建一个 data 目录,里面是数据集文件,包括用于训练的数据和测试的数据集。

文件内容如下所示,子目录 test_data 里面的是测试数据集,用于检查模型训练情况和优化。

│ t10k-images-idx3-ubyte.gz │ t10k-labels-idx1-ubyte.gz │ train-images-idx3-ubyte.gz │ train-labels-idx1-ubyte.gz │ └───test_data t10k-images-idx3-ubyte t10k-labels-idx1-ubyte train-images-idx3-ubyte train-labels-idx1-ubyte

显示图片

数据集是 Dataset 类型,继承了Dataset<Dictionary<string, Tensor>>类型,Dataset 本质是列表,我们把 Dataset 列表的 item 称为数据,每个 item 都是一个字典类型,每个字典由 data、label 两个 key 组成

在上一节,已经编写好如何加载数据集,将训练数据和测试数据分开加载,为了了解 Dataset ,读者可以通过以下代码将数据集的结构打印到控制台。

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

通过观察控制台,可以知道,每个数据元素都是一个字典,每个字典由 data、label 两个 key 组成,dic["data"]是一个图片,而 label 就是表示该图片的文本值是什么。

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"]; if (i > 2) { break; } img.ShowImage(); }

使用 Maomi.ScottPlot.Winforms 库,还可以通过img.ShowImageToForm()接口通过窗口的形式显示图片。

你也可以直接转存为图片:

img.SavePng("data/{i}.png");

加载数据集

由于 FashionMNIST 数据集有 6 万张图片,一次性加载所有图片比较消耗内存,并且一次性训练对 GPU 的要求也很高,因此我们需要分批处理数据集。

torch.utils.data中有数据加载器,可以帮助我们分批加载图片集到内存中,开发时使用迭代器直接读取,不需要关注分批情况。

如下面所示,分批加载数据集,批处理大小是 64 张图片。

// 分批加载图像,打乱顺序 var train_loader = torch.utils.data.DataLoader(training_data, batchSize: 64, shuffle: true, device: defaultDevice); // 分批加载图像,不打乱顺序 var test_loader = torch.utils.data.DataLoader(test_data, batchSize: 64, shuffle: false, device: defaultDevice);

注意,分批是在 DataLoader 内部发生的,我们可以理解为缓冲区大小,对于开发者来说,并不需要关注分批情况。

定义网络

接下来定义一个神经网络,神经网络有多个层,通过神经网络来训练数据,通过数据的训练可以的出参数、权重等信息,这些信息会被保存到模型中,加载模型时,必须要有对应的网络结构,比如神经网络的层数要相同、每层的结构一致。

该网络通过接受28*28大小的图片,经过处理后输出 10 个分类值,每个分类结果都带有其可能的概率,概率最高的就是识别结果。

将以下代码存储到 NeuralNetwork.cs 中。

using TorchSharp.Modules; using static TorchSharp.torch; using nn = TorchSharp.torch.nn; public class NeuralNetwork : nn.Module<Tensor, Tensor> { // 传递给基类的参数是模型的名称 public NeuralNetwork() : base(nameof(NeuralNetwork)) { flatten = nn.Flatten(); linear_relu_stack = nn.Sequential( nn.Linear(28 * 28, 512), nn.ReLU(), nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10)); // C# 版本需要调用这个函数,将模型的组件注册到模型中 RegisterComponents(); } Flatten flatten; Sequential linear_relu_stack; public override Tensor forward(Tensor input) { // 将输入一层层处理并传递给下一层 var x = flatten.call(input); var logits = linear_relu_stack.call(x); return logits; } }

注意,网络中只能定义字段,不要定义属性;不要使用_开头定义字段;

然后继续在 Program 里继续编写代码,初始化神经网络,并使用 GPU 来加载网络。

var model = new NeuralNetwork(); model.to(defaultDevice);

优化模型参数

为了训练模型,需要定义一个损失函数和一个优化器,损失函数的主要作用是衡量模型的预测结果与真实标签之间的差异,即误差或损失,有了损失函数后,通过优化器可以指导模型参数的调整,使预测结果能够逐步靠近真实值,从而提高模型的性能。Pytorch 自带很多损失函数,这里使用计算交叉熵损失的损失函数。

// 定义损失函数、优化器和学习率 var loss_fn = nn.CrossEntropyLoss(); var optimizer = torch.optim.SGD(model.parameters(), learningRate : 1e-3);

同时,优化器也很重要,是用于调整模型参数以最小化损失函数的模块。

因为损失函数比较多,但是优化器就那么几个,所以这里简单列一下 Pytorch 中自带的一些优化器。

  • SGD(随机梯度下降):通过按照损失函数的梯度进行线性步长更新权重;
  • Adam(自适应矩估计):基于一阶和二阶矩估计的优化算法,它能自适应地调整学习率,对大多数问题效果较好;
  • RMSprop:适用于处理非平稳目标,能够自动进行学习率的调整;
  • AdamW(带权重衰减的 Adam):在 Adam 的基础上添加了权重衰减(weight decay),防止过拟合。

训练模型

接下来讲解训练模型的步骤,如下代码所示。

下面是详细步骤:

  • 每读取一张图片,就使用神经网络进行识别(.call()函数),pred识别结果
  • 通过损失函数判断网络的识别结果和标签值的误差;
  • 通过损失函数反向传播,计算网络的梯度等;
  • 通过 SGD 优化器,按照损失函数的梯度进行线性步长更新权重,optimizer.step()会调整模型的权重,根据计算出来的梯度来更新模型的参数,使模型逐步接近优化目标。
  • 因为数据是分批处理的,因此计算当前批次的梯度后,需要使用optimizer.zero_grad()重置当前所有梯度。
  • 计算训练成果,即打印当前训练进度和损失值。
http://www.cnnetsun.cn/news/3094594.html

相关文章:

  • AI工程实战:模型服务化与性能优化关键策略
  • view_source
  • 小月子多久可以洗头洗澡?结合休养禁忌科学把控洗护时
  • 3步掌握UE4SS:虚幻引擎游戏修改的终极解决方案
  • Kubernetes Operator开发教程
  • React性能优化
  • JavaScript原型链
  • CVE-2026-22218 Chainlit 框架任意文件读取漏洞全解析
  • ASP.NET Core 之 Identity 入门(一)
  • MANO手部模型完整指南:如何用Python实现逼真3D手部建模
  • 如何提取 Word 文档中的表格并导出为 Excel(Python 教程)
  • AI编曲工具实战:从入门到专业音乐制作
  • C++集成OpenSSL实现RSA公钥加密:从原理到工程实践
  • 如何彻底解决 AI 编程的连贯性难题
  • 手机磁吸转轴支架出厂检验全解:5 大类必检项目与 4 家厂商品控体系对比
  • Burp Suite安全测试实战:从零掌握Web渗透核心工作流与高阶技巧
  • Frida内存操作避坑指南:从原理到实战的逆向分析核心技能
  • 开源 GR00T N1.7 论文解读:Cosmos-Reason2/Qwen3-VL + DiT 动作头,20K 小时人类视频预训练
  • Banana Pi BpiRouterOS 路由器 官方操作系统,基于Openwrt开发 #路由器
  • 从看图说话到一键出码:2026年多模态AI,最值得普通人立刻用的3个场景
  • 异步并行批处理框架设计的一些思考
  • 01:Agent Loop:Claude Code 的运行时主循环
  • 生成式引擎优化(GEO)在酒店民宿行业的落地实践:对抗 OTA 流量截流
  • 密码学中的欧拉定理研究与应用
  • 小米穿戴表盘设计终极指南:零代码创建个性化智能手表界面
  • 百万路像素并行三维推演,分布式 SpaceOS 图形底座承载城域级实景孪生
  • 微信QQ消息防撤回终极指南:3步揭秘聊天记录永久保存技术
  • 自动类型推导
  • Go 内存逃逸分析:编译器分配决策的底层逻辑与优化指南
  • MiniMax与阶跃星辰2026大模型实测:国产新势力谁更懂开发者?