C#AI系列(1):深度学习项目构建及实战TensorFlow准备篇

用 C# .NET 项目里把 AI 当普通组件写",既享编译性能,又省集成成本,这对熟悉善用或深度绑定或dotnet生态的团队来说是最直接、最省心的 AI 落地路径。因为如果在.NET 项目中使用Python技术栈开发机器学习模块,可能会带来如跨语言胶水、独立微服务、容器化、双栈运维等较高额外成本。如果可以将如数值拟合、向量分类、图像识别、大模型智能体、MCP等应用模块直接编译到项目内,无论是单文件打包还是aot编译等方式交付,无论从后期维护一套技术栈还是提高已有程序产品竞争力都非常有帮助。

考虑到此,本公众号将开启C#AI开发的系列篇,从零开始和大家分享C#在AI方面的实战,大家可以多多关注我们的最新分享。本文所有机器学习模型案例的代码已全部开源,关注 萤火初芒 公众号回复AISharp即可查看仓库地址。

我们先从深度学习开始。

深度学习是机器学习的一个分支,它试图模仿人脑的工作原理,通过一种称为"人工神经网络"的复杂结构,从大量数据中学习和提取高层次的规律。

一、组件环境准备

用C#开发深度学习的核心框架主要有两种:TensorFlow.Net 和 TorchSharp (分别对应python中的tensorflow和pytorch),这两个库也是ML.NET组件深度学习模型的核心库。具体比较如下:

特性维度 TensorFlow.Net & Keras TorchSharp
核心用途 由Google开发的端到端开源深度学习平台 由Facebook开发,以其动态计算图和Pythonic设计闻名
API设计 Keras作为高级API紧密集成(tf.keras),提供简单直观的接口 原生API设计Python化,代码直观易理解
主要特点 - 生态系统庞大,部署能力强,适合大型项目和工业级应用 在研究和学术界极受欢迎,提供极大的灵活性和调试便利性,近年来在生产环境支持上发展迅速
适用场景 生产环境、工业级应用、大型项目 学术研究、快速原型开发、需要灵活性的场景
核心哲学 默认静态图(Static Graph),先定义后执行 默认动态图(Dynamic Graph) 即时执行
链接库大小 约237mb (压缩后约73mb) 约250mb(压缩后约77mb)

这两个框架功能都很强大,链接库大小差不多,可以自由参考自身项目特点以及使用习惯进行选择。但需要注意的是,我们测试时,TorchSharp可以通过简单将Module对象配置到rd.xml从而实现aot编译运行;而tensorflow.net的aot编译后运行的报错较多,相关功能还有待尝试。

这次我们先说TensorFlow,接下来我们以一个简单的人工神经网络(ANN)为例子来看下TensorFlow.Net的使用。

二、核心代码组成-TensorFlow.Net

深度学习的核心是深度神经网络,以基础前向传播人工神经网络(Feed-forward ANN)来说,创建一个ANN需要做三方面准备:数据集、网络模型、训练方法。

我们的目标是用500组数据训练一个模型,当传入a,b两个数值后能尽量准确的输出二者的平方和,即aa+bb。

2.1 环境配置

开始之前,我们需要先添加TensorFlow.Net的相关依赖。TensorFlow.Net是以Apache-2.0 license协议开源的组件,我们需要在项目中直接添加相关的nuget包即可:

包名 含义与作用
SciSharp.TensorFlow.Redist 包含底层的 Native 本地代码库,是框架运行的引擎。
TensorFlow.NET 主要的 C# API 封装,提供操作张量和计算图的核心类与方法。
TensorFlow.Keras 作为 TensorFlow.NET 的高级扩展,提供像 Keras 一样简洁易用的模型构建接口。

2.2 数据准备

TensorFlow主要使用的数据对象类型是NDArray。我们先随机生成500组数据用于训练。其中x为维度500×2的NDArray,存储输入值;y为维度500×1的NDArray,存储输出值。

在生成数据前可以通过将随机数生成器的种子固定,这样我们每次随机生成的数据就是一样的了,方便我们更好的在修改完超参后比较结果。

csharp 复制代码
// 生成数据
tf.set_random_seed(42); //固定一个随机数种子用于复现结果

// 随机生成 -10~10的x
var x_data = np.random.randn(500, 2).astype(np.float32) * 10.0f;
// 生成对应的平方和y
var y_data = np.sum(np.power(x_data, 2), axis: 1);
y_data = np.expand_dims(y_data, 1);  // 保持维度 (500, 1)

2.3 模型构建

有了数据之后我们就可以开始着手构建模型。我们需要设计的是比较常见的顺序模型,表示神经网络层按顺序一层一层堆叠起来,数据从输入到输出单向流动。

Tensorflow中的层定义需要指定:节点数、激活函数等信息。Dropout功能则需要独立存在(代码中所用的激活函数和Dropout的设计及参数仅作为功能示意,能用,但不要参考)。

csharp 复制代码
   // 构建模型
  // 输入层尺寸为2,输出层尺寸为1

   var model = keras.Sequential();  // 创建顺序模型
   model.add(keras.layers.Dense(64, activation: "sigmoid")); 
   model.add(keras.layers.Dense(32, activation: "relu")); // 32个节点配有relu激活函数
   model.add(keras.layers.Dropout(0.001f)); // dropout层
   model.add(keras.layers.Dense(1));  // 输出层

模型各层设计完成后需要调用compile函数,设定模型的梯度优化策略及损失函数。如下代码:

csharp 复制代码
   // 编译模型
   model.compile(
       optimizer: keras.optimizers.Adam(2e-3f), // adam梯度下降,学习速率为2e-3
       loss: keras.losses.MeanSquaredError()  // 使用mse作为损失函数
   );

2.4 模型训练

模型的训练直接调用model的fit方法即可,传入训练集的输入以及训练集的输出。batch_size是训练的批次大小。

csharp 复制代码
  // 训练模型
  model.fit(
      x_data, y_data,   // 训练集的输入及输出
      batch_size: 100,  // 仅简化示意用
      epochs: 4000,     // 最大迭代次数
      verbose:0         // 消息显示的循环间隔,0为不显示,2为每两个循环显示一次。
  );

测试中遇到一个问题就是,在我们用的版本中verbose的传参貌似只能影响callback函数,反编译库发现源码好像把底层调用给写死成1了,所以导致无论怎么修改,控制台在训练时一直会显示进度条。不过不影响结果。

可以看到,4000 epoch完成后,loss降到了5.302576.

复制代码
Epoch: 4000/4000
0001/0001 [>............................] - 3ms/step - loss: 5.302576

2.5 模型预测及数据获取

任意传入两个数,3.14f, 2.5f:

csharp 复制代码
 // 构造向量
 var x_new = tf.constant(new float[,] { { 3.14f, 2.5f } });
 
 var tensorResult = model.predict(x_new);

 // 验证结果
 Console.WriteLine($"Predict: {tensorResult.numpy()[0,0]}");
 Console.WriteLine($"Expected: {3.14 * 3.14 + 2.5 * 2.5}");

模型输出结果为:

复制代码
Predict: 15.160952
Expected: 16.10960102081299

训练完成持久化后,后面我们就可以将这个模型整合到我们的代码里去执行其他任务了。

五、最后

以上分享了在C#中基于TensorFlow.Net简单创建一个人工神经网络模型并训练数据预测数据的丰富。其实在C#中用TensorFlow.Net很多地方与python使用TensorFlow很像,为了保持一致性,TensorFlow.Net在大部分的地方都通过调用静态方法的方式保留了原来函数式的写法,本质上等价于new一个对象,后续自己封装方法调用的适合要稍微下注意避免重复创建:

csharp 复制代码
 // keras.Sequential()
 public Sequential Sequential(List<ILayer> layers = null, string name = null)
 {
     return new Sequential(new SequentialArgs
     {
         Layers = layers,
         Name = name
     });
 }

下次我们分享基于TorchSharp框架的相关内容构建。如果你在阅读过程中有任何疑问,或者在实际操作中遇到了困难,欢迎随时与我们交流。我们非常期待听到你的反馈和建议,以便我们能够进一步完善内容,帮助更多开发者。请继续关注我们的公众号"萤火初芒",我们将持续分享更多有趣且实用的技术内容,与大家一起学习交流,共同进步。

更加完整丰富的机器学习模型案例的代码已全部开源,关注公众号回复AISharp即可查看仓库地址。