C#調(diào)用TensorFlow的實(shí)戰(zhàn)指南
一、引言
1.1 TensorFlow 生態(tài)概覽
TensorFlow 是 Google 開發(fā)的開源機(jī)器學(xué)習(xí)框架,廣泛應(yīng)用于深度學(xué)習(xí)、計(jì)算機(jī)視覺、自然語言處理等領(lǐng)域。其 Python API 是業(yè)界最成熟的深度學(xué)習(xí)開發(fā)接口之一。然而,對于使用 .NET 技術(shù)棧的企業(yè)和開發(fā)者而言,如何在 C# 中利用 TensorFlow 的強(qiáng)大能力一直是個(gè)挑戰(zhàn)。
1.2 為什么 C# 開發(fā)者需要 TensorFlow
- 生產(chǎn)環(huán)境部署:很多企業(yè)后端基于 .NET 構(gòu)建,需要將 AI 模型直接集成到現(xiàn)有系統(tǒng)中
- 類型安全:C# 的強(qiáng)類型系統(tǒng)可以在編譯期捕獲更多錯(cuò)誤
- 性能優(yōu)勢:.NET 8+ 的 JIT 編譯器和 AOT 技術(shù)帶來出色的運(yùn)行時(shí)性能
- 生態(tài)整合:與 ASP.NET Core、Blazor、MAUI 等 .NET 生態(tài)無縫集成
1.3 .NET 與 TensorFlow 的集成方式
在 .NET 中使用 TensorFlow 主要有以下途徑:
| 方式 | 說明 | 適用場景 |
|---|---|---|
| TensorFlow.NET | 完整的 TensorFlow C# 綁定,支持訓(xùn)練和推理 | 從零開始構(gòu)建深度學(xué)習(xí)模型 |
| ML.NET + TensorFlow 后端 | ML.NET 將 TensorFlow 作為后端引擎 | 傳統(tǒng) ML 場景復(fù)用 TF 模型 |
| ONNX Runtime | 將 TF 模型轉(zhuǎn)為 ONNX 格式后加載 | 跨框架模型部署 |
本文聚焦于 TensorFlow.NET —— 這是由 SciSharp 社區(qū)維護(hù)的官方 .NET 綁定庫,實(shí)現(xiàn)了 TensorFlow 的完整 API。
二、環(huán)境搭建
2.1 TensorFlow.NET 簡介
TensorFlow.NET(簡稱 TF.NET)是 .NET Standard 平臺上的 TensorFlow 綁定。它的目標(biāo)是讓 .NET 開發(fā)者能夠用 C# 或 F# 開發(fā)、訓(xùn)練和部署機(jī)器學(xué)習(xí)模型,同時(shí)保持與 Python TensorFlow API 的高度一致性。
項(xiàng)目地址:https://github.com/SciSharp/TensorFlow.NET 官方文檔:https://tensorflownet.readthedocs.io 當(dāng)前版本:0.150.0(對應(yīng) TensorFlow v2.10)
TF.NET 提供了兩個(gè)核心 NuGet 包:
- TensorFlow.NET:底層綁定,對應(yīng)
tf.*API - TensorFlow.Keras:高層 Keras API,對應(yīng)
tf.keras.*API
2.2 NuGet 包安裝
創(chuàng)建 .NET 8 控制臺項(xiàng)目后,安裝以下包:
# 第一步:安裝核心庫 dotnet add package TensorFlow.NET --version 0.150.0 # 第二步:安裝 Keras 高層 API(可選,推薦) dotnet add package TensorFlow.Keras # 第三步:安裝運(yùn)行時(shí)支持包(根據(jù)你的平臺選擇其一) # Windows/Linux CPU 版本 dotnet add package SciSharp.TensorFlow.Redist # macOS CPU 版本 dotnet add package SciSharp.TensorFlow.Redist-OSX # Windows GPU 版本(需要 CUDA 和 cuDNN) dotnet add package SciSharp.TensorFlow.Redist-Windows-GPU # Linux GPU 版本(需要 CUDA 和 cuDNN) dotnet add package SciSharp.TensorFlow.Redist-Linux-GPU
2.3 項(xiàng)目結(jié)構(gòu)
一個(gè)典型的 TensorFlow.NET 項(xiàng)目結(jié)構(gòu)如下:
MyTfProject/
├── MyTfProject.csproj
├── Program.cs
├── models/ # 存放預(yù)訓(xùn)練模型
│ └── inception/
│ └── tensorflow_inception_graph.pb
└── images/ # 測試圖片
└── test.jpg2.4 GPU 支持配置(可選)
如果需要 GPU 加速,在 Windows 上需要:
- 安裝 NVIDIA CUDA Toolkit(推薦 11.2+)
- 安裝 cuDNN(與 CUDA 版本匹配)
- 將 CUDA bin 目錄添加到 PATH 環(huán)境變量
- 安裝
SciSharp.TensorFlow.Redist-Windows-GPU包
注意:GPU 版本的包體積較大(約 500MB),首次下載需要一定時(shí)間。CPU 版本足以滿足大多數(shù)推理場景。
三、TensorFlow.NET 基礎(chǔ)
3.1 核心命名空間與導(dǎo)入
使用 TensorFlow.NET 時(shí),最常見的導(dǎo)入方式如下:
using static Tensorflow.Binding; using static Tensorflow.KerasApi; using Tensorflow; using Tensorflow.NumPy;
其中: - Tensorflow.Binding 提供了 tf 靜態(tài)方法的快捷訪問 - Tensorflow.KerasApi 提供了 keras 靜態(tài)方法的快捷訪問 - Tensorflow.NumPy 提供了類似 Python NumPy 的 NDArray 操作
3.2 兩種執(zhí)行模式
TensorFlow.NET 支持兩種執(zhí)行模式:
3.2.1 Graph 模式(圖模式)
在 Graph 模式下,計(jì)算先被構(gòu)建為靜態(tài)計(jì)算圖,然后在 Session 中執(zhí)行。這是 TensorFlow 1.x 的經(jīng)典模式,適合模型部署和推理場景。
// 禁用 Eager Execution,啟用 Graph 模式
tf.compat.v1.disable_eager_execution();
var graph = tf.Graph().as_default();
graph.Import("model.pb");
using var sess = tf.Session(graph);
var result = sess.run(outputTensor, feedDict);3.2.2 Eager Execution 模式(即時(shí)執(zhí)行)
Eager 模式是 TensorFlow 2.x 的默認(rèn)模式,操作立即執(zhí)行并返回值,更加直觀,適合開發(fā)和調(diào)試。
// 啟用 Eager Execution
tf.enable_eager_execution();
var x = tf.constant(new float[] { 1, 2, 3 });
var y = tf.constant(new float[] { 4, 5, 6 });
var z = x + y;
print(z.numpy()); // 輸出: [5 7 9]四、模型推理(Inference)
模型推理是 TensorFlow.NET 最常用的場景——加載 Python 訓(xùn)練好的模型,在 .NET 應(yīng)用中進(jìn)行預(yù)測。本節(jié)介紹兩種主流方式。
4.1 加載 .pb 文件進(jìn)行推理
.pb(Protocol Buffer)格式是 TensorFlow 凍結(jié)模型的經(jīng)典格式,將模型結(jié)構(gòu)和權(quán)重打包為單個(gè)文件。
以下代碼示例來源于 TensorFlow.NET 官方示例倉庫中的 ImageRecognitionInception.cs,演示如何加載 Inception v3 模型進(jìn)行圖像分類推理:
using System;
using System.IO;
using System.Collections.Generic;
using Tensorflow;
using Tensorflow.NumPy;
using static Tensorflow.Binding;
public class InferenceExample
{
public void Run()
{
// 使用 Graph 模式
tf.compat.v1.disable_eager_execution();
// 1. 創(chuàng)建計(jì)算圖并導(dǎo)入 .pb 模型
var graph = tf.Graph().as_default();
graph.Import("models/inception/tensorflow_inception_graph.pb");
// 2. 獲取輸入和輸出操作的引用
var input_operation = graph.OperationByName("input");
var output_operation = graph.OperationByName("output");
// 3. 加載標(biāo)簽文件
var labels = File.ReadAllLines("models/inception/imagenet_comp_graph_label_strings.txt");
// 4. 讀取并預(yù)處理輸入圖片
var inputTensor = ReadTensorFromImageFile("images/test.jpg");
// 5. 創(chuàng)建 Session 并執(zhí)行推理
using var sess = tf.Session(graph);
var results = sess.run(
output_operation.outputs[0],
(input_operation.outputs[0], inputTensor)
);
// 6. 處理結(jié)果
results = np.squeeze(results);
int idx = np.argmax(results);
Console.WriteLine($"識別結(jié)果: {labels[idx]} (置信度: {results[idx]:P2})");
}
/// <summary>
/// 讀取圖片文件并預(yù)處理為模型所需的 Tensor
/// </summary>
private NDArray ReadTensorFromImageFile(string file_name,
int input_height = 224,
int input_width = 224,
int input_mean = 117,
int input_std = 1)
{
// 在臨時(shí) Graph 中構(gòu)建圖片預(yù)處理流程
var g = tf.Graph().as_default();
var file_reader = tf.io.read_file(file_name, "file_reader");
var decodeJpeg = tf.image.decode_jpeg(file_reader, channels: 3, name: "DecodeJpeg");
var cast = tf.cast(decodeJpeg, tf.float32);
var dims_expander = tf.expand_dims(cast, 0);
var resize = tf.constant(new int[] { input_height, input_width });
var bilinear = tf.image.resize_bilinear(dims_expander, resize);
var sub = tf.subtract(bilinear, new float[] { input_mean });
var normalized = tf.divide(sub, new float[] { input_std });
using var sess = tf.Session(g);
return sess.run(normalized);
}
}關(guān)鍵步驟解析:
graph.Import():將.pb文件中的計(jì)算圖導(dǎo)入當(dāng)前 Graph 對象graph.OperationByName():通過名稱獲取圖中特定操作的引用sess.run():在 Session 中執(zhí)行計(jì)算,傳入輸入 Tensor 和待獲取的輸出 Tensor- 圖片預(yù)處理:使用 TensorFlow 自身的圖像操作(讀取、解碼、縮放、歸一化)確保輸入格式與模型訓(xùn)練時(shí)一致
4.2 對象檢測推理示例
以下示例來源于官方示例倉庫中的 DetectInMobilenet.cs,展示了使用 MobileNet SSD 模型進(jìn)行對象檢測的完整流程:
using System;
using System.IO;
using System.Linq;
using System.Drawing;
using Tensorflow;
using Tensorflow.NumPy;
using static Tensorflow.Binding;
public class ObjectDetectionExample
{
public float MIN_SCORE = 0.5f;
public void Run()
{
tf.compat.v1.disable_eager_execution();
// 導(dǎo)入 MobileNet SSD 模型
var graph = new Graph().as_default();
graph.Import("ssd_mobilenet_v1_coco_2018_01_28/frozen_inference_graph.pb");
// 獲取輸入輸出 Tensor 引用
var imgTensor = graph.OperationByName("image_tensor");
var tensorNum = graph.OperationByName("num_detections");
var tensorBoxes = graph.OperationByName("detection_boxes");
var tensorScores = graph.OperationByName("detection_scores");
var tensorClasses = graph.OperationByName("detection_classes");
// 讀取輸入圖片
var imgArr = ReadTensorFromImageFile("images/input.jpg");
// 執(zhí)行推理
Tensor[] outTensors = new Tensor[] { tensorNum, tensorBoxes, tensorScores, tensorClasses };
using var sess = tf.Session(graph);
var results = sess.run(outTensors, new FeedItem(imgTensor, imgArr));
// 解析結(jié)果
var scores = results[2].ToArray<float>();
var boxes = results[1].ToArray<float>();
var ids = np.squeeze(results[3]).ToArray<float>();
for (int i = 0; i < scores.Length; i++)
{
if (scores[i] > MIN_SCORE)
{
// 解析邊界框坐標(biāo)
float top = boxes[i * 4] * imageHeight;
float left = boxes[i * 4 + 1] * imageWidth;
float bottom = boxes[i * 4 + 2] * imageHeight;
float right = boxes[i * 4 + 3] * imageWidth;
Console.WriteLine($"檢測到對象: 類別ID={ids[i]}, 置信度={scores[i]:P2}");
Console.WriteLine($" 邊界框: ({left}, {top}) - ({right}, {bottom})");
}
}
}
private NDArray ReadTensorFromImageFile(string file_name)
{
var graph = tf.Graph().as_default();
var file_reader = tf.io.read_file(file_name, "file_reader");
var decodeJpeg = tf.image.decode_jpeg(file_reader, channels: 3, name: "DecodeJpeg");
var casted = tf.cast(decodeJpeg, TF_DataType.TF_UINT8);
var dims_expander = tf.expand_dims(casted, 0);
using var sess = tf.Session(graph);
return sess.run(dims_expander);
}
}4.3 加載 SavedModel 格式
SavedModel 是 TensorFlow 2.x 推薦的模型保存格式,包含模型結(jié)構(gòu)、權(quán)重和服務(wù)簽名。
using Tensorflow;
using static Tensorflow.Binding;
public void LoadSavedModel()
{
// 使用 SavedModel 加載
var sess = tf.Session();
// 從指定目錄加載 SavedModel
tf.saved_model.load(sess, tags: new[] { "serve" }, export_dir: "saved_model_dir");
// 通過簽名運(yùn)行推理
var inputTensor = ...; // 準(zhǔn)備輸入
var result = sess.run(outputTensor, (inputPlaceholder, inputTensor));
}注意:SavedModel 的 API 在 TensorFlow.NET 中的支持程度取決于版本。對于 TensorFlow 2.x 訓(xùn)練的模型,建議使用 .pb 格式(通過 Python 端的 tf.compat.v1.graph_util.convert_variables_to_constants 轉(zhuǎn)換)以確保最佳兼容性。
4.4 性能優(yōu)化建議
批量推理:將多張圖片合并為一個(gè) batch 進(jìn)行推理,充分利用 GPU 并行計(jì)算能力。
// 假設(shè) singleImages 是多張圖片 Tensor 的列表 var batchTensor = tf.stack(singleImages.ToArray()); // 合并為 [batch, H, W, C] var results = sess.run(outputTensor, (inputTensor, batchTensor));
異步推理:使用 Task.Run 將推理操作放在后臺線程執(zhí)行,避免阻塞 UI 線程。
public async Task<string> PredictAsync(byte[] imageData)
{
return await Task.Run(() =>
{
var tensor = PreprocessImage(imageData);
var result = sess.run(outputTensor, (inputTensor, tensor));
return PostProcessResult(result);
});
}Session 復(fù)用:創(chuàng)建 Session 的開銷較大,應(yīng)在應(yīng)用生命周期內(nèi)復(fù)用同一個(gè) Session 實(shí)例。
五、Keras 高層 API 使用
TensorFlow.NET 內(nèi)置了 Keras 高層接口(TensorFlow.Keras 包),讓模型構(gòu)建和訓(xùn)練變得簡潔直觀。
5.1 Keras Sequential API
Sequential API 適用于層與層之間線性堆疊的模型。以下示例來源于官方示例倉庫 ImageClassificationKeras.cs:
using System.Collections.Generic;
using Tensorflow;
using Tensorflow.Keras.Engine;
using static Tensorflow.Binding;
using static Tensorflow.KerasApi;
public class KerasSequentialExample
{
Model model;
int batch_size = 32;
int epochs = 10;
public void Run()
{
// 啟用 Eager 模式
tf.enable_eager_execution();
BuildModel();
Train();
}
public void BuildModel()
{
int num_classes = 5; // 5 種花卉分類
var layers = keras.layers;
// 使用 Sequential API 構(gòu)建模型
var myLayers = new List<ILayer>
{
// 歸一化層:將像素值從 [0, 255] 縮放到 [0, 1]
layers.Rescaling(1.0f / 255, input_shape: (64, 64, 3)),
// 卷積層 + 池化層
layers.Conv2D(16, 3, padding: "same", activation: keras.activations.Relu),
layers.MaxPooling2D(),
// 展平層
layers.Flatten(),
// 全連接層
layers.Dense(128, activation: keras.activations.Relu),
// 輸出層(logits,未經(jīng) softmax)
layers.Dense(num_classes)
};
model = keras.Sequential(myLayers);
// 編譯模型
model.compile(
optimizer: keras.optimizers.Adam(),
loss: keras.losses.SparseCategoricalCrossentropy(from_logits: true),
metrics: new[] { "accuracy" }
);
// 打印模型摘要
model.summary();
}
public void Train()
{
// 訓(xùn)練模型(需先準(zhǔn)備 train_ds 和 val_ds 數(shù)據(jù)集)
model.fit(train_ds, validation_data: val_ds, epochs: epochs);
}
}5.2 Keras Functional API
Functional API 適用于具有多輸入、多輸出或殘差連接等復(fù)雜拓?fù)涞哪P汀R韵率纠齺碓从诠俜绞纠齻}庫 MnistFnnKerasFunctional.cs:
using Tensorflow.Keras.Engine;
using Tensorflow.NumPy;
using static Tensorflow.Binding;
using static Tensorflow.KerasApi;
public class KerasFunctionalExample
{
IModel model;
NDArray x_train, y_train, x_test, y_test;
public void Run()
{
tf.enable_eager_execution();
PrepareData();
BuildModel();
Train();
}
public void PrepareData()
{
// 加載 MNIST 數(shù)據(jù)集
(x_train, y_train, x_test, y_test) = keras.datasets.mnist.load_data();
x_train = x_train.reshape((60000, 784)) / 255f;
x_test = x_test.reshape((10000, 784)) / 255f;
}
public void BuildModel()
{
var layers = keras.layers;
// 定義輸入
var inputs = keras.Input(shape: 784);
// 第一全連接層
var outputs = layers.Dense(64, activation: keras.activations.Relu).Apply(inputs);
// 第二全連接層
outputs = layers.Dense(64, activation: keras.activations.Relu).Apply(outputs);
// 輸出層
outputs = layers.Dense(10).Apply(outputs);
// 構(gòu)建模型
model = keras.Model(inputs, outputs, name: "mnist_model");
model.summary();
// 編譯模型
model.compile(
loss: keras.losses.SparseCategoricalCrossentropy(from_logits: true),
optimizer: keras.optimizers.RMSprop(),
metrics: new[] { "accuracy" }
);
}
public void Train()
{
model.fit(x_train, y_train, batch_size: 64, epochs: 2, validation_split: 0.2f);
model.evaluate(x_test, y_test, verbose: 2);
// 保存模型
model.save("mnist_model");
// 重新加載模型
// model = keras.models.load_model("mnist_model");
}
}5.3 數(shù)據(jù)集預(yù)處理
Keras 提供了便捷的圖像數(shù)據(jù)加載工具 image_dataset_from_directory:
// 從目錄結(jié)構(gòu)自動(dòng)加載圖片數(shù)據(jù)集
// 目錄結(jié)構(gòu)要求:
// data_dir/
// class_a/
// img1.jpg, img2.jpg, ...
// class_b/
// img1.jpg, img2.jpg, ...
var train_ds = keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split: 0.2f,
subset: "training",
seed: 123,
image_size: (64, 64),
batch_size: 32
);
var val_ds = keras.preprocessing.image_dataset_from_directory(
data_dir,
validation_split: 0.2f,
subset: "validation",
seed: 123,
image_size: (64, 64),
batch_size: 32
);
// 數(shù)據(jù)增強(qiáng):打亂順序 + 預(yù)取優(yōu)化
train_ds = train_ds.shuffle(1000).prefetch(buffer_size: -1);
val_ds = val_ds.prefetch(buffer_size: -1);六、遷移學(xué)習(xí)(Transfer Learning)
6.1 什么是遷移學(xué)習(xí)
遷移學(xué)習(xí)是深度學(xué)習(xí)中的一項(xiàng)重要技術(shù)。其核心思想是:利用在大規(guī)模數(shù)據(jù)集(如 ImageNet)上預(yù)訓(xùn)練好的模型,將其學(xué)到的特征提取能力遷移到新的、數(shù)據(jù)量較小的目標(biāo)任務(wù)上。
為什么要使用遷移學(xué)習(xí)?
- 減少訓(xùn)練數(shù)據(jù)需求:只需幾百張目標(biāo)類別圖片即可達(dá)到不錯(cuò)的效果
- 加速訓(xùn)練:預(yù)訓(xùn)練模型已經(jīng)學(xué)習(xí)到了通用的邊緣、紋理等低級特征
- 提升準(zhǔn)確率:相比從零訓(xùn)練,遷移學(xué)習(xí)通常在小型數(shù)據(jù)集上表現(xiàn)更好
6.2 遷移學(xué)習(xí)的兩種策略
| 策略 | 方法 | 適用場景 |
|---|---|---|
| 特征提取 | 凍結(jié)預(yù)訓(xùn)練模型的所有層,只訓(xùn)練新增的分類頭 | 目標(biāo)數(shù)據(jù)集很小,與源域相似 |
| 微調(diào)(Fine-tuning) | 解凍預(yù)訓(xùn)練模型的部分頂層,與新分類頭一起訓(xùn)練 | 目標(biāo)數(shù)據(jù)集較大,與源域差異較大 |
6.3 使用 SciSharp ModelWizard 進(jìn)行遷移學(xué)習(xí)
TensorFlow.NET 的 SciSharp 子庫提供了一個(gè)高級封裝 ModelWizard,可以大幅簡化遷移學(xué)習(xí)流程。以下示例來源于官方示例倉庫 TransferLearningWithInceptionV3.cs:
using SciSharp.Models;
using SciSharp.Models.ImageClassification;
using System;
using System.IO;
using Tensorflow.Keras.Utils;
using static Tensorflow.Binding;
public class TransferLearningExample
{
float accuracy;
public void Run()
{
PrepareData();
Train();
Test();
Predict();
Console.WriteLine($"測試準(zhǔn)確率: {accuracy:P2}");
}
/// <summary>
/// 準(zhǔn)備數(shù)據(jù):下載花卉數(shù)據(jù)集
/// </summary>
public void PrepareData()
{
string fileName = "flower_photos.tgz";
string dataDir = "image_classification_v1";
string url = $"http://download.tensorflow.org/example_images/{fileName}";
Web.Download(url, dataDir, fileName);
Compress.ExtractTGZ(Path.Join(dataDir, fileName), dataDir);
}
/// <summary>
/// 使用 ModelWizard 訓(xùn)練遷移學(xué)習(xí)模型
/// 底層使用 InceptionV3 作為預(yù)訓(xùn)練基座
/// </summary>
public void Train()
{
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask<TransferLearning>(new TaskOptions
{
DataDir = @"image_classification_v1\flower_photos",
});
task.Train(new TrainingOptions
{
TrainingSteps = 100
});
}
/// <summary>
/// 測試模型準(zhǔn)確率
/// </summary>
public void Test()
{
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask<TransferLearning>(new TaskOptions
{
DataDir = @"image_classification_v1\flower_photos",
ModelPath = @"image_classification_v1\saved_model.pb"
});
var result = task.Test(new TestingOptions { });
accuracy = result.Accuracy;
}
/// <summary>
/// 使用訓(xùn)練好的模型進(jìn)行預(yù)測
/// </summary>
public void Predict()
{
var wizard = new ModelWizard();
var task = wizard.AddImageClassificationTask<TransferLearning>(new TaskOptions
{
ModelPath = @"image_classification_v1\saved_model.pb"
});
var imgPath = Path.Join("image_classification_v1", "flower_photos",
"daisy", "5547758_eea9edfd54_n.jpg");
var input = ImageUtil.ReadImageFromFile(imgPath);
var result = task.Predict(input);
Console.WriteLine($"預(yù)測結(jié)果: {result.Label}");
}
}這個(gè)示例做了什么?
- 下載 TensorFlow 官方的花卉數(shù)據(jù)集(5 個(gè)類別:daisy、dandelion、roses、sunflowers、tulips)
- 使用
ModelWizard+TransferLearning模板,自動(dòng)完成:- 加載 InceptionV3 預(yù)訓(xùn)練模型
- 凍結(jié)特征提取層
- 添加新的分類層(5 類輸出)
- 訓(xùn)練新的分類層
- 保存訓(xùn)練好的模型為
.pb文件 - 加載模型進(jìn)行測試和預(yù)測
6.4 手動(dòng)實(shí)現(xiàn)遷移學(xué)習(xí)(Keras 方式)
如果 ModelWizard 不能滿足需求,也可以手動(dòng)實(shí)現(xiàn)遷移學(xué)習(xí)流程:
using System.Collections.Generic;
using Tensorflow;
using Tensorflow.Keras.Engine;
using static Tensorflow.Binding;
using static Tensorflow.KerasApi;
public class ManualTransferLearning
{
Model model;
IModel base_model; // 類級別字段,供 BuildTransferLearningModel 和 FineTune 共用
public void BuildTransferLearningModel(int num_classes = 5)
{
tf.enable_eager_execution();
var layers = keras.layers;
// 1. 加載預(yù)訓(xùn)練的 MobileNetV2 模型(不含頂層)
base_model = keras.applications.MobileNetV2(
input_shape: (224, 224, 3),
include_top: false,
weights: "imagenet"
);
// 2. 凍結(jié)預(yù)訓(xùn)練層
base_model.trainable = false;
// 3. 構(gòu)建新模型
var inputs = keras.Input(shape: (224, 224, 3));
// 數(shù)據(jù)預(yù)處理
var x = layers.Rescaling(1.0f / 127.5f, input_shape: (224, 224, 3)).Apply(inputs);
x = layers.Rescaling(-1, offset: 1).Apply(x); // 歸一化到 [-1, 1]
// 通過預(yù)訓(xùn)練基座提取特征
x = base_model.Apply(x);
// 全局平均池化
x = layers.GlobalAveragePooling2D().Apply(x);
// Dropout 防止過擬合
x = layers.Dropout(0.2f).Apply(x);
// 輸出層
var outputs = layers.Dense(num_classes).Apply(x);
model = keras.Model(inputs, outputs, name: "transfer_learning_model");
model.compile(
optimizer: keras.optimizers.Adam(),
loss: keras.losses.SparseCategoricalCrossentropy(from_logits: true),
metrics: new[] { "accuracy" }
);
model.summary();
}
/// <summary>
/// 微調(diào):解凍部分頂層繼續(xù)訓(xùn)練
/// 注意:base_model 是類級別字段,在 BuildTransferLearningModel 中初始化
/// </summary>
public void FineTune()
{
// 解凍最后 20 層
base_model.trainable = true;
foreach (var layer in base_model.layers.TakeLast(20))
{
layer.trainable = true;
}
// 重新編譯模型(微調(diào)需要更小的學(xué)習(xí)率)
model.compile(
optimizer: keras.optimizers.Adam(1e-5f),
loss: keras.losses.SparseCategoricalCrossentropy(from_logits: true),
metrics: new[] { "accuracy" }
);
// 繼續(xù)訓(xùn)練
model.fit(train_ds, validation_data: val_ds, epochs: 10);
}
}說明:keras.applications.MobileNetV2 的使用依賴于 TensorFlow.Keras 包中預(yù)訓(xùn)練權(quán)重的支持。如果本地沒有預(yù)訓(xùn)練權(quán)重緩存,首次加載時(shí)會(huì)自動(dòng)下載。
七、模型導(dǎo)出與部署
7.1 Keras 模型保存
// 保存模型(包含結(jié)構(gòu)和權(quán)重)
model.save("my_model");
// 重新加載模型
var loadedModel = keras.models.load_model("my_model");7.2 導(dǎo)出為 SavedModel 格式
// Keras 模型默認(rèn)以 SavedModel 格式保存 // 目錄結(jié)構(gòu): // my_model/ // saved_model.pb // variables/ // variables.data-00000-of-00001 // variables.index // assets/
7.3 .NET 應(yīng)用中的部署
部署時(shí)需要考慮以下幾點(diǎn):
- 運(yùn)行時(shí)依賴:確保目標(biāo)機(jī)器上安裝了正確的
SciSharp.TensorFlow.Redist包 - 模型路徑:模型文件應(yīng)隨應(yīng)用一起發(fā)布,使用相對路徑或絕對路徑加載
- 內(nèi)存管理:TensorFlow Session 占用較多內(nèi)存,應(yīng)復(fù)用而非每次創(chuàng)建
- 線程安全:
tf.Session不是線程安全的,多線程場景需要使用lock或線程局部 Session
// 線程安全的推理服務(wù)示例
public class InferenceService : IDisposable
{
private readonly Session _session;
private readonly Tensor _inputTensor;
private readonly Tensor _outputTensor;
private readonly object _lock = new object();
public InferenceService(string modelPath)
{
tf.compat.v1.disable_eager_execution();
var graph = tf.Graph().as_default();
graph.Import(modelPath);
_session = tf.Session(graph);
_inputTensor = graph.OperationByName("input");
_outputTensor = graph.OperationByName("output");
}
public NDArray Predict(NDArray input)
{
lock (_lock)
{
return _session.run(_outputTensor, (_inputTensor, input));
}
}
public void Dispose()
{
_session?.close();
}
}7.4 Docker 容器化部署
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 80
# 安裝 TensorFlow 運(yùn)行時(shí)依賴
RUN apt-get update && apt-get install -y \
libgomp1 \
&& rm -rf /var/lib/apt/lists/*
FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
WORKDIR /src
COPY ["MyApp.csproj", "."]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=build /app/publish .
COPY models/ ./models/
ENTRYPOINT ["dotnet", "MyApp.dll"]八、常見問題與最佳實(shí)踐
8.1 版本兼容性
| TensorFlow.NET 版本 | 對應(yīng) TensorFlow 版本 | 備注 |
|---|---|---|
| 0.150.x | 2.10 | 當(dāng)前最新穩(wěn)定版 |
| 0.6x | 2.6 | 舊版,部分 API 有差異 |
| 0.15.x | 1.15 | 僅支持 TensorFlow 1.x 模式 |
重要:TensorFlow.NET 的維護(hù)團(tuán)隊(duì)目前資源有限,新功能和 bug 修復(fù)主要依賴社區(qū) PR。建議鎖定特定版本,避免自動(dòng)升級導(dǎo)致兼容性問題。
8.2 內(nèi)存管理
TensorFlow 的 Tensor 和 Session 都實(shí)現(xiàn)了 IDisposable,使用后應(yīng)及時(shí)釋放:
// 正確做法:使用 using 語句 using var sess = tf.Session(graph); var result = sess.run(outputTensor, (inputTensor, input)); // sess 會(huì)在作用域結(jié)束時(shí)自動(dòng)釋放
8.3 調(diào)試技巧
- 打印 Tensor 信息:
print(tensor.shape)或print(tensor.numpy()) - 模型摘要:
model.summary()查看各層參數(shù) - Graph 可視化:導(dǎo)出 GraphDef 后用 TensorBoard 查看
- 異常排查:TensorFlow.NET 的異常信息通常不夠詳細(xì),建議在關(guān)鍵步驟添加
try-catch并打印中間變量
8.4 性能調(diào)優(yōu)
- 使用 GPU:對于推理任務(wù),GPU 可以帶來 5-10 倍的速度提升
- 批處理:合并多個(gè)請求為一個(gè) batch 推理
- 模型量化:將 FP32 模型轉(zhuǎn)為 FP16 或 INT8 以減小模型體積和提升推理速度
- AOT 編譯:使用 .NET 8 的 AOT 功能減少啟動(dòng)時(shí)間
九、總結(jié)
TensorFlow.NET 為 .NET 開發(fā)者提供了一條通往深度學(xué)習(xí)的可行路徑。從簡單的模型推理到完整的遷移學(xué)習(xí)流程,TF.NET 都能勝任。
核心要點(diǎn)回顧:
- 環(huán)境搭建:安裝
TensorFlow.NET+TensorFlow.Keras+SciSharp.TensorFlow.Redist - 模型推理:使用 Graph 模式加載
.pb文件,通過sess.run()執(zhí)行推理 - Keras 建模:支持 Sequential 和 Functional 兩種 API,可以完整訓(xùn)練模型
- 遷移學(xué)習(xí):使用
ModelWizard或手動(dòng) Keras 方式加載預(yù)訓(xùn)練模型并進(jìn)行微調(diào) - 部署:注意線程安全和內(nèi)存管理,推薦 Docker 容器化部署
TensorFlow.NET 雖然不是 .NET AI 生態(tài)的唯一選擇(還有 ML.NET、ONNX Runtime 等),但對于需要從 TensorFlow 生態(tài)遷移或復(fù)用模型的團(tuán)隊(duì)來說,它是最直接、最完整的解決方案。
以上就是C#調(diào)用TensorFlow的實(shí)戰(zhàn)指南的詳細(xì)內(nèi)容,更多關(guān)于C#調(diào)用TensorFlow的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
c#使用win32api實(shí)現(xiàn)獲取光標(biāo)位置
本文給大家匯總了2個(gè)使用C#實(shí)現(xiàn)獲取光標(biāo)位置的代碼,非常的簡單實(shí)用,第二種方法更為全面,推薦給大家。2016-02-02
C# Base 64 編碼/解碼實(shí)現(xiàn)代碼
這篇文章主要介紹了C# Base 64 編碼/解碼實(shí)現(xiàn)代碼,需要的朋友可以參考下2016-02-02
C#實(shí)現(xiàn)自定義windows系統(tǒng)日志的方法
這篇文章主要介紹了C#實(shí)現(xiàn)自定義windows系統(tǒng)日志的方法,涉及C#針對windows系統(tǒng)日志的創(chuàng)建、讀寫及刪除技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-08-08
C# dump系統(tǒng)lsass內(nèi)存和sam注冊表詳細(xì)
這篇文章主要介紹了C# dump系統(tǒng)lsass內(nèi)存和sam注冊表,在這里選擇 C# 的好處是體積小,結(jié)合 loadAssembly 方便免殺,希望對讀者們有所幫助2021-09-09
C#使用iTextSharp獲取PDF文件書簽信息的操作方法
C# iTextSharp是一個(gè)用于處理PDF文件的源庫,它提供了一系列的功能,包括創(chuàng)建PDF文件,以及提取和操作PDF文件中的內(nèi)容,本文給大家介紹了C#使用iTextSharp獲取PDF文件書簽信息的操作方法,需要的朋友可以參考下2024-04-04
C# 使用 Castle 實(shí)現(xiàn) AOP及如何用 Autofac 集成 Castle
這篇文章主要介紹了C# 使用 Castle 實(shí)現(xiàn) AOP及如何用 Autofac 集成 Castle,幫助大家更好的理解和學(xué)習(xí)使用c#,感興趣的朋友可以了解下2021-02-02
C# HTTP認(rèn)證方式詳解與代碼實(shí)現(xiàn)
在C#中,HTTP認(rèn)證是客戶端與服務(wù)器之間進(jìn)行身份驗(yàn)證的一種機(jī)制,常見的HTTP認(rèn)證方式包括:Basic認(rèn)證、Digest認(rèn)證、OAuth、Bearer Token等,下面我們將從工作原理、優(yōu)缺點(diǎn)對比、代碼實(shí)現(xiàn)、案例實(shí)戰(zhàn)四個(gè)方面詳細(xì)介紹這些認(rèn)證方式,需要的朋友可以參考下2025-03-03

