C#深度学习实战:基于TorchSharp构建神经网络模型
环境配置与依赖引入
在开始开发前,需通过NuGet包管理器安装两个核心组件:TorchSharp-cpu 和 TorchSharp。前者包含跨平台的原生计算库(支持Windows、Linux、macOS),后者提供面向C#的高级API封装,使开发者能以接近Python中PyTorch的风格进行深度学习开发。
| 包名 | 功能说明 |
|---|---|
TorchSharp-cpu | 集成LibTorch核心引擎,提供本地计算能力及多平台兼容性。 |
TorchSharp | 封装了张量操作、自动微分、模型构建等核心机制,接口设计与PyTorch高度一致。 |
数据准备:二维点分类任务
目标是训练一个模型判断给定坐标点是否位于以原点为中心、半径为5的圆内或外。首先生成均匀分布的样本点,并根据到原点的距离划分标签。
// 生成训练数据
int sampleCount = 1000;
float radius = 5.0f;
var insidePoints = new List<Tensor>();
var outsidePoints = new List<Tensor>();
for (int i = 0; i < sampleCount * 2; i++)
{
float x = (float)(new Random().NextDouble() * 20 - 10);
float y = (float)(new Random().NextDouble() * 20 - 10);
float dist = (float)Math.Sqrt(x * x + y * y);
if (dist < radius)
insidePoints.Add(torch.tensor(new[] { x, y }));
else
outsidePoints.Add(torch.tensor(new[] { x, y }));
if (insidePoints.Count == sampleCount && outsidePoints.Count == sampleCount)
break;
}
// 平衡数据集
int minSize = Math.Min(insidePoints.Count, outsidePoints.Count);
var xInside = torch.stack(insidePoints.Take(minSize).ToArray());
var xOutside = torch.stack(outsidePoints.Take(minSize).ToArray());
Tensor inputFeatures = torch.cat([xInside, xOutside], 0); // 合并输入
Tensor labels = torch.cat([
torch.zeros(minSize, ScalarType.Int64),
torch.ones(minSize, ScalarType.Int64)
], 0); // 构建标签:0=内部,1=外部
模型架构设计
TorchSharp采用模块化方式定义网络结构。可通过Sequential快速搭建线性堆叠结构,也可继承Module<Tensor, Tensor>自定义复杂逻辑。
// 简易序列模型
var network = Sequential(
Linear(2, 32), // 输入层 → 隐藏层
Relu(),
Linear(32, 16),
Relu(),
Linear(16, 2), // 输出层(二分类)
Softmax(1) // 输出概率分布
);
或通过类封装实现更灵活的控制:
public class CircleClassifier : Module<Tensor, Tensor>
{
private readonly Module<Tensor, Tensor> _layers;
public CircleClassifier(string name) : base(name)
{
_layers = Sequential(
Linear(2, 64),
Relu(),
Linear(64, 32),
Relu(),
Linear(32, 2)
);
RegisterComponents(); // 必须调用,确保参数注册
}
public override Tensor forward(Tensor input)
{
return _layers.forward(input);
}
}
训练流程实现
使用Adam优化器和交叉熵损失函数,执行迭代训练。每步完成梯度反向传播与参数更新。
model.train();
var optimizer = torch.optim.Adam(model.parameters(), lr: 1e-3);
var criterion = torch.nn.functional.cross_entropy;
for (int step = 0; step < 5000; step++)
{
var predictions = model.forward(inputFeatures);
var loss = criterion(predictions, labels);
optimizer.zero_grad();
loss.backward();
optimizer.step();
if (step % 1000 == 0)
Console.WriteLine($"Step {step}: Loss = {loss.ToSingle():F4}");
}
推理测试与结果分析
训练结束后切换至评估模式,对新点进行预测。利用no_grad()关闭梯度计算,提升效率。
static void PredictPoint(Module<Tensor, Tensor> model, float x, float y)
{
using var testInput = torch.tensor(new[] { x, y }, new long[] { 1, 2 });
using (no_grad())
{
model.eval();
var output = model.forward(testInput);
var probabilities = output.ToSingleArray();
Console.WriteLine($"坐标 ({x}, {y}) -> 概率: [{probabilities[0]:F3}, {probabilities[1]:F3}]");
}
}
// 测试示例
PredictPoint(model, 1, 3); // 应为内部 → [0.999, 0.001]
PredictPoint(model, -6, -7); // 应为外部 → [0.001, 0.999]
PredictPoint(model, 0, 5); // 边缘点 → [0.682, 0.318]
资源管理建议
TorchSharp涉及大量底层内存分配,合理管理至关重要:
- 自动回收:依赖GC,适合小规模模型,但性能较低。
- 显式释放:使用
using语句及时释放Tensor对象,代码清晰但冗长。 - 作用域管理:推荐使用
torch.NewDisposeScope(),可批量自动清理,兼顾性能与可读性。
建议在高负载场景中降低批处理大小,避免内存溢出;同时注意自定义数据加载器中的张量生命周期。