老铁们,你们有没有这种憋屈的时刻——老板突然拍着你的肩膀说:“小李啊,咱们系统能不能加个AI预测功能?你看隔壁Python组,两周就搞出来了!”你心里一万匹草泥马奔腾而过:我一个C#码农,你让我去学Python?学Pandas?学Scikit-learn?学PyTorch?我特么连virtualenv怎么配都忘了!

然后你只能尴尬地打开百度,搜“C# 机器学习”,结果出来的全是“用IronPython调用Python脚本”——这不是脱裤子放屁吗? 我要是会Python,还搜这玩意儿干嘛?

告诉你个好消息:2026年了,C#搞机器学习早就不是“二等公民”了。微软开源的ML.NET、.NET基金会旗下的TorchSharp,再加上ONNX Runtime这三大神器,让你在熟悉的Visual Studio里就能完成从数据清洗到模型训练再到推理部署的全链路,全程C#,一行Python都不用写!

今天墨夶就把我这两年用C#训练模型的全部家底儿扒出来——从最简单的线性回归到AutoML自动调参,再到TorchSharp训练神经网络,代码全到你能直接CV到项目里跑起来。废话少说,咖啡续上,咱们开干!

金句预警:机器学习不是Python的专利,它只是先发优势。C#就像那杯泡对了的咖啡——入口顺滑,后劲十足,关键是跟你现有的技术栈完美融合,不用另起炉灶!

一、先别急着写代码,搞懂C#机器学习的“三驾马车”

1.1 ML.NET:微软亲儿子,.NET原生机器学习框架

ML.NET是微软官方开源的跨平台机器学习框架,2018年正式发布,现在已经迭代到5.0版本(2025年11月发布),6.0-preview1也已经出来了。它最大的特点就是——专为.NET开发者设计,支持C#和F#,API设计完全遵循.NET的开发习惯,你写EF Core是啥感觉,写ML.NET就是啥感觉。

ML.NET支持的任务类型贼全:

  • 监督学习:回归(预测连续值,比如房价)、二分类(是/否,比如垃圾邮件检测)、多分类(多个类别,比如图片分类)
  • 无监督学习:聚类(把相似的数据分到一组,比如客户分群)、异常检测(找出不正常的数据)
  • 推荐系统:协同过滤,搞“猜你喜欢”
  • 深度学习:通过ONNX Runtime集成TensorFlow、PyTorch训练的模型

1.2 TorchSharp:PyTorch的C#绑定,深度学习的“原力”

TorchSharp是.NET基金会旗下的开源项目,它做的事情简单粗暴——把PyTorch的C++ API(LibTorch)用C#封装了一遍,让你在C#里能写出几乎跟Python PyTorch一模一样的代码。

这玩意儿有多强?Tensor张量操作、自动求导、神经网络模块、优化器、数据加载器,PyTorch有的它全有。而且因为是直接调用LibTorch,性能上比Python版本还快15-30%(少了Python解释器的开销)。

1.3 ONNX Runtime:跨框架模型推理加速器

ONNX(Open Neural Network Exchange)是一个开放的神经网络交换格式,说白了就是“AI模型的通用语言”。你在PyTorch里训练好的模型,导出成ONNX格式,然后用C#加载运行——全程不需要安装Python环境。

ONNX Runtime是微软开源的高性能推理引擎,支持CPU、GPU(CUDA)、甚至NPU加速。它已经被集成到ML.NET中,你可以直接在ML.NET的Pipeline里调用ONNX模型。

1.4 一张图看懂三者的关系

┌─────────────────────────────────────────────────────────────────┐
│                        你的 C# 应用                              │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────────┐  ┌──────────────────┐  ┌───────────────┐ │
│  │     ML.NET       │  │   TorchSharp     │  │ ONNX Runtime  │ │
│  │  (传统机器学习)   │  │   (深度学习)      │  │  (模型推理)    │ │
│  └────────┬─────────┘  └────────┬─────────┘  └───────┬───────┘ │
│           │                     │                     │         │
│           └─────────────────────┼─────────────────────┘         │
│                                 │                               │
│                    ┌────────────▼────────────┐                  │
│                    │     .NET Runtime         │                  │
│                    └──────────────────────────┘                  │
└─────────────────────────────────────────────────────────────────┘

ML.NET:适合表格数据、传统机器学习任务(分类、回归、聚类)
TorchSharp:适合深度学习任务(图像、文本、序列)
ONNX Runtime:适合部署现成的模型(从Python训练好导出的)

魔性比喻:ML.NET是你楼下那家面馆——做面条(表格数据)一绝,便宜大碗;TorchSharp是米其林餐厅——能做复杂大菜(深度学习),但需要点技术;ONNX Runtime是外卖打包——别人做好的菜,你加热就能吃。

二、实战一:ML.NET入门——用60行代码训练一个房价预测模型

老铁们,光说不练假把式。咱们从一个最经典的回归任务开始——根据房屋面积预测房价。别看案例简单,这套流程适用于任何表格数据的回归任务(销售预测、股票趋势、设备寿命预测等等)。

2.1 创建项目并安装NuGet包

# 第一步:创建控制台应用
dotnet new console -n HousePricePredictor -o .
dotnet add package Microsoft.ML
dotnet add package Microsoft.ML.Data
# 💡 技巧:Microsoft.ML.Data 包含数据视图相关的扩展,虽然不是必须的,但装了更方便

2.2 准备训练数据

创建一个 housing.csv 文件放在项目根目录下:

Size,Price
550,250000
800,340000
1200,480000
1500,580000
1800,680000
2100,780000
2500,920000
3000,1080000
3500,1260000
4000,1420000

2.3 完整代码(注释比代码还多,保证你能看懂)

// Program.cs - ML.NET 回归任务完整示例
// ⚠️ 重点:这个代码包含了ML.NET的完整工作流,拿去直接改数据集就能用!

using Microsoft.ML;
using Microsoft.ML.Data;

// ==================== 第一步:定义数据模型 ====================
// 💡 技巧:这两个类对应的是训练数据的输入和预测结果的输出
// LoadColumn 特性用来指定CSV列索引,ColumnName 指定特征名称

/// <summary>
/// 房屋数据输入类 - 对应CSV文件中的每一行
/// </summary>
public class HouseData
{
    // ⚠️ 重点:[LoadColumn(0)] 表示从CSV文件的第一列加载数据(索引从0开始)
    // ColumnName 是特征列的名称,后续构建Pipeline时会用到这个名字
    [LoadColumn(0)]
    [ColumnName("Size")]
    public float Size { get; set; }

    // Label 是机器学习中的“标签”——也就是我们要预测的目标值
    // 🚫 避坑:Label列名是约定俗成的,ML.NET会自动识别名为"Label"的列作为目标变量
    // 如果你用其他名字,需要在Trainers中显式指定 labelColumnName 参数
    [LoadColumn(1)]
    [ColumnName("Label")]
    public float Price { get; set; }
}

/// <summary>
/// 房价预测输出类 - 模型预测的结果会填充到这个类的实例中
/// </summary>
public class HousePrediction
{
    // ⚠️ 重点:Score 是回归任务的默认输出列名
    // 训练完成后,预测值会自动填充到这个属性
    [ColumnName("Score")]
    public float PredictedPrice { get; set; }
}

// ==================== 主程序 ====================
class Program
{
    static void Main(string[] args)
    {
        // ⚠️ 重点:MLContext 是ML.NET的“入口”,一切操作都从这里开始
        // 它管理着整个机器学习生命周期,包括数据加载、模型训练、评估和预测
        // 你可以把它想象成 Entity Framework 中的 DbContext
        var mlContext = new MLContext(seed: 42);  // 设随机种子,保证结果可复现

        // ==================== 第二步:加载数据 ====================
        Console.WriteLine("📂 正在加载数据...");
        
        // 💡 技巧:LoadFromTextFile 会创建一个 IDataView 对象
        // IDataView 是ML.NET的核心数据抽象,类似数据库中的“视图”
        // 它支持惰性加载,数据不会一次性全读进内存,适合处理大文件
        var dataPath = "housing.csv";
        IDataView data = mlContext.Data.LoadFromTextFile<HouseData>(
            path: dataPath,           // 文件路径
            separatorChar: ',',       // CSV分隔符
            hasHeader: true,          // 第一行是表头
            allowQuoting: true        // 允许引号包裹字段
        );

        // 🚫 避坑:如果数据量很大,建议先用 Preview() 看一下数据的前几行
        // 确认列映射是否正确,避免训练了半天发现数据列对错了
        var preview = data.Preview(maxRows: 5);
        Console.WriteLine($"✅ 数据加载成功,共 {preview.RowView.Length} 行预览数据");

        // ==================== 第三步:数据拆分(训练集/测试集) ====================
        // ⚠️ 重点:必须把数据拆成训练集和测试集!
        // 用训练集训练模型,用测试集评估模型效果,防止过拟合
        // 我上次忘了拆分,拿全部数据训练然后评估,准确率高得离谱,上线后直接翻车
        var split = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);
        IDataView trainData = split.TrainSet;   // 80% 用于训练
        IDataView testData = split.TestSet;      // 20% 用于测试
        Console.WriteLine($"📊 数据拆分:训练集 80%,测试集 20%");

        // ==================== 第四步:构建数据处理与训练管道 ====================
        // 💡 技巧:ML.NET 使用“管道”模式,类似于 LINQ 的链式调用
        // 每一步都是一个数据转换操作,最后 Append 训练器
        
        var pipeline = mlContext.Transforms
            // 步骤1:特征工程 - 把多个列连接成一个“Features”列
            // ⚠️ 重点:所有训练器都要求输入一个名为 "Features" 的列
            // Concatenate 就是把多个数值列合并成一个特征向量
            .Concatenate("Features", new[] { "Size" })
            
            // 步骤2:附加训练器
            // 💡 技巧:SDCA(随机双坐标上升法)是ML.NET默认的回归训练器
            // 适合中小规模数据集,收敛快,效果好
            // 其他可选回归器:FastTree、LightGbm、OnlineGradientDescent
            .Append(mlContext.Regression.Trainers.Sdca(
                labelColumnName: "Label",        // 目标变量列名
                featureColumnName: "Features",    // 特征列名
                maximumNumberOfIterations: 100    // 最大迭代次数
            ));

        // ==================== 第五步:训练模型 ====================
        Console.WriteLine("\n🔄 正在训练模型...");
        
        // ⚠️ 重点:Fit() 方法会执行管道中定义的所有操作,生成一个 ITransformer
        // ITransformer 就是训练好的模型,可以保存、加载、用于预测
        ITransformer model = pipeline.Fit(trainData);
        Console.WriteLine("✅ 模型训练完成!");

        // ==================== 第六步:评估模型 ====================
        Console.WriteLine("\n📈 正在评估模型...");
        
        // 💡 技巧:Transform() 方法用训练好的模型对测试集进行预测
        var predictions = model.Transform(testData);
        
        // ⚠️ 重点:Regression.Evaluate() 会计算多个回归指标
        // - RSquared(R²):决定系数,越接近1越好(0.8以上算不错)
        // - RootMeanSquaredError(RMSE):均方根误差,越小越好
        // - MeanAbsoluteError(MAE):平均绝对误差
        var metrics = mlContext.Regression.Evaluate(predictions);
        
        Console.WriteLine($"📊 模型评估结果:");
        Console.WriteLine($"  R² Score (决定系数): {metrics.RSquared:F4} (越接近1越好)");
        Console.WriteLine($"  RMSE (均方根误差):   {metrics.RootMeanSquaredError:F2}");
        Console.WriteLine($"  MAE (平均绝对误差):  {metrics.MeanAbsoluteError:F2}");
        
        // 🚫 避坑:R² 低于0.5说明模型效果很差,需要换特征或换算法
        if (metrics.RSquared < 0.5)
        {
            Console.WriteLine("⚠️ 警告:R² 分数较低,模型预测能力有限!");
        }

        // ==================== 第七步:保存模型 ====================
        var modelPath = "HousePriceModel.zip";
        mlContext.Model.Save(model, trainData.Schema, modelPath);
        Console.WriteLine($"\n💾 模型已保存至: {modelPath}");

        // ==================== 第八步:创建预测引擎 ====================
        // ⚠️ 重点:PredictionEngine 不是线程安全的!
        // 生产环境中应该使用 PredictionEnginePool(需要注入服务)
        // 或者在每次预测时重新创建
        var predictionEngine = mlContext.Model.CreatePredictionEngine<HouseData, HousePrediction>(model);

        // ==================== 第九步:使用模型进行预测 ====================
        Console.WriteLine("\n🔮 开始预测...");
        
        // 测试几个面积
        float[] testSizes = { 600f, 1200f, 2000f, 3200f };
        
        foreach (var size in testSizes)
        {
            var input = new HouseData { Size = size };
            var prediction = predictionEngine.Predict(input);
            Console.WriteLine($"  面积 {size} sqft → 预测价格: ${prediction.PredictedPrice:N0}");
        }

        // ==================== 第十步(可选):加载已有模型进行预测 ====================
        Console.WriteLine("\n📂 演示:从文件加载模型进行预测...");
        ITransformer loadedModel = mlContext.Model.Load(modelPath, out var modelInputSchema);
        var loadedPredictionEngine = mlContext.Model.CreatePredictionEngine<HouseData, HousePrediction>(loadedModel);
        var newPrediction = loadedPredictionEngine.Predict(new HouseData { Size = 1500f });
        Console.WriteLine($"  面积 1500 sqft → 预测价格: ${newPrediction.PredictedPrice:N0}");

        Console.WriteLine("\n🎉 所有操作完成!");
        Console.ReadKey();
    }
}

2.4 运行结果与解读

运行上面的代码,你应该会看到类似这样的输出:

📊 模型评估结果:
  R² Score (决定系数): 0.9984 (越接近1越好)
  RMSE (均方根误差):   14203.12
  MAE (平均绝对误差):  11680.00

🔮 开始预测...
  面积 600 sqft → 预测价格: $263,200
  面积 1200 sqft → 预测价格: $474,400
  面积 2000 sqft → 预测价格: $746,400
  面积 3200 sqft → 预测价格: $1,157,600

R² 接近0.998,说明模型拟合得非常好——这很正常,因为我们的数据本身就是线性关系。

金句:ML.NET的API设计跟你写的EF Core如出一辙——context入口、pipeline链式调用、fit训练、transform预测。微软在API一致性这块,从来没让人失望过。

三、实战二:AutoML自动机器学习——让机器帮你选算法调参数

上面那个例子虽然简单,但你有没有发现一个问题:我凭什么选SDCA算法?为什么不选FastTree?为什么不选LightGBM? 还有那些超参数——迭代次数、学习率、树的深度——我咋知道怎么调?

这就是AutoML要解决的问题。ML.NET从2.0版本开始,AutoML功能已经相当成熟,它能自动尝试多种算法、自动调参、自动特征工程,最后输出一个性能最好的模型。ML.NET AutoML会优化整个机器学习管道,包括特征工程、模型选择和超参数调优,对于不熟悉机器学习的.NET开发者来说简直是救星。

3.1 安装AutoML包

dotnet add package Microsoft.ML
dotnet add package Microsoft.ML.AutoML

3.2 完整AutoML代码(比手写简单太多了)

// AutoMLDemo.cs - 使用AutoML自动训练回归模型
// ⚠️ 重点:不到30行代码就能完成模型选择、调参、训练、评估全流程!

using Microsoft.ML;
using Microsoft.ML.AutoML;
using Microsoft.ML.Data;

// 数据模型(跟之前一样)
public class HouseData
{
    [LoadColumn(0)] public float Size { get; set; }
    [LoadColumn(1)] public float Price { get; set; }
}

public class HousePrediction
{
    [ColumnName("Score")] public float PredictedPrice { get; set; }
}

class AutoMLDemo
{
    static void Main(string[] args)
    {
        var mlContext = new MLContext(seed: 42);
        
        // 加载数据
        var data = mlContext.Data.LoadFromTextFile<HouseData>(
            "housing.csv", separatorChar: ',', hasHeader: true);
        
        Console.WriteLine("🤖 启动AutoML实验,自动寻找最佳模型...");
        Console.WriteLine("   (这个过程中AutoML会尝试多种算法并调参,请耐心等待)");
        
        // ==================== AutoML核心代码 ====================
        // 💡 技巧:CreateRegressionExperiment 创建一个回归实验
        // maxExperimentTimeInSeconds:最大实验时间,到时间后返回当前最佳模型
        var experiment = mlContext.Auto()
            .CreateRegressionExperiment(maxExperimentTimeInSeconds: 30);
        
        // 🚫 避坑:labelColumnName 必须与数据中的目标列名一致
        // 我们的HouseData类里Price列没有显式指定ColumnName,默认就是属性名"Price"
        var experimentResult = experiment.Execute(
            trainData: data,
            labelColumnName: "Price",
            progressHandler: new ProgressReporter()  // 报告训练进度
        );
        
        // ==================== 查看结果 ====================
        Console.WriteLine("\n✅ AutoML实验完成!");
        Console.WriteLine($"🏆 最佳算法: {experimentResult.BestRun.TrainerName}");
        Console.WriteLine($"📊 最佳模型评估指标:");
        
        // 获取评估指标
        var metrics = experimentResult.BestRun.ValidationMetrics;
        Console.WriteLine($"  R² Score: {metrics.RSquared:F4}");
        Console.WriteLine($"  RMSE:      {metrics.RootMeanSquaredError:F2}");
        Console.WriteLine($"  MAE:       {metrics.MeanAbsoluteError:F2}");
        
        // ==================== 保存最佳模型 ====================
        mlContext.Model.Save(experimentResult.BestRun.Model, data.Schema, "AutoML_BestModel.zip");
        Console.WriteLine($"\n💾 最佳模型已保存至: AutoML_BestModel.zip");
        
        // ==================== 预测 ====================
        var predictionEngine = mlContext.Model.CreatePredictionEngine<HouseData, HousePrediction>(
            experimentResult.BestRun.Model);
        
        var prediction = predictionEngine.Predict(new HouseData { Size = 2000f });
        Console.WriteLine($"\n🔮 测试预测:面积 2000 sqft → ${prediction.PredictedPrice:N0}");
    }
}

/// <summary>
/// 进度报告器 - 用于显示AutoML的训练进度
/// </summary>
class ProgressReporter : IProgress<RunDetail<RegressionMetrics>>
{
    private int _runCount = 0;
    
    public void Report(RunDetail<RegressionMetrics> value)
    {
        _runCount++;
        // 💡 技巧:可以在这里实时看到每次尝试的结果
        if (_runCount % 5 == 0)  // 每5次输出一次,避免刷屏
        {
            Console.WriteLine($"   已完成 {_runCount} 次尝试...");
        }
    }
}

3.3 AutoML还能做什么?

AutoML不只是回归任务,分类、聚类、推荐、异常检测全支持:

// 二分类实验(比如垃圾邮件检测)
var binaryExperiment = mlContext.Auto()
    .CreateBinaryClassificationExperiment(maxExperimentTimeInSeconds: 60);

// 多分类实验(比如手写数字识别)
var multiExperiment = mlContext.Auto()
    .CreateMulticlassClassificationExperiment(maxExperimentTimeInSeconds: 60);

Model Builder:如果你连代码都不想写,微软还提供了Visual Studio扩展——Model Builder,通过可视化界面就能完成数据选择、模型训练和代码生成。Visual Studio 2026已经内置了这套工具链。

金句:AutoML就像一个开挂的老师傅——你只需要告诉他“我要预测房价”,他就能帮你把模型、算法、参数全部搞定。你负责提需求,他负责出模型,完美!

四、实战三:TorchSharp深度学习——在C#里训练神经网络

讲完了传统机器学习,咱们来点更“硬核”的——深度学习。如果你需要处理图像、文本、语音这类非结构化数据,传统的ML.NET算法可能不够用,这时候TorchSharp就派上用场了。

TorchSharp让C#开发者可以直接在.NET环境中使用PyTorch的全部功能,通过调用PyTorch的C++ API提供类型安全的封装,同时保持了API的高度一致性。

4.1 安装TorchSharp

dotnet add package TorchSharp
dotnet add package TorchSharp-cpu  # CPU版本
# 或者 CUDA版本(需要NVIDIA显卡)
# dotnet add package TorchSharp-cuda-windows

4.2 用TorchSharp训练一个简单的神经网络(MNIST手写数字识别)

// TorchSharpMNIST.cs - 在C#中训练神经网络识别手写数字
// ⚠️ 重点:这段代码在C#中实现了完整的深度学习训练流程
// 包括数据加载、模型定义、前向传播、反向传播、参数更新

using TorchSharp;
using static TorchSharp.torch;
using static TorchSharp.torch.nn;
using static TorchSharp.torch.optim;
using static TorchSharp.torch.utils.data;

class TorchSharpMNIST
{
    static void Main(string[] args)
    {
        // ⚠️ 重点:设置随机种子,保证结果可复现
        torch.manual_seed(42);
        
        // 检查是否有CUDA可用(GPU加速)
        var device = cuda.is_available() ? CUDA : CPU;
        Console.WriteLine($"💻 使用设备: {(device == CUDA ? "GPU (CUDA)" : "CPU")}");
        
        // ==================== 第一步:定义神经网络结构 ====================
        // 💡 技巧:Module 是TorchSharp中所有神经网络层的基类,相当于PyTorch的nn.Module
        
        var model = Sequential(
            // 输入层:28x28的图像展平成784维向量
            ("flatten", Flatten()),
            
            // 隐藏层1:784 → 128,带ReLU激活函数
            // 🚫 避坑:Linear的参数是 (输入维度, 输出维度),不是 (输出, 输入)
            ("fc1", Linear(28 * 28, 128)),
            ("relu1", ReLU()),
            
            // 隐藏层2:128 → 64
            ("fc2", Linear(128, 64)),
            ("relu2", ReLU()),
            
            // 输出层:64 → 10(10个数字类别)
            ("output", Linear(64, 10))
        );
        
        // 把模型移到指定设备(GPU或CPU)
        model = model.to(device);
        
        // 打印模型结构
        Console.WriteLine("\n📐 神经网络结构:");
        Console.WriteLine(model);
        Console.WriteLine($"\n📊 总参数量:{model.parameters().Sum(p => p.numel()):N0}");
        
        // ==================== 第二步:定义损失函数和优化器 ====================
        // 💡 技巧:CrossEntropyLoss 是分类任务的标准损失函数
        var loss_fn = CrossEntropyLoss();
        
        // Adam优化器,学习率0.001
        // 🚫 避坑:学习率太大容易震荡不收敛,太小训练太慢,0.001是经验值
        var optimizer = Adam(model.parameters(), lr: 0.001);
        
        // ==================== 第三步:准备数据(模拟MNIST数据) ====================
        // ⚠️ 重点:这里用随机数据演示,实际使用中可以用 torchvision.datasets.MNIST
        // 真实数据加载方式:var mnist = torchvision.datasets.MNIST(...)
        Console.WriteLine("\n📂 准备训练数据...");
        
        int batchSize = 64;
        int numBatches = 100;  // 训练100个批次
        
        // ==================== 第四步:训练循环 ====================
        Console.WriteLine("\n🔄 开始训练...");
        model.train();  // 设置模型为训练模式(启用Dropout、BatchNorm等)
        
        for (int epoch = 1; epoch <= 5; epoch++)  // 5个epoch
        {
            float epochLoss = 0;
            int correct = 0;
            int total = 0;
            
            for (int batch = 0; batch < numBatches; batch++)
            {
                // 生成模拟数据(实际应用中应该从DataLoader加载)
                // X: [batchSize, 1, 28, 28] 的图像数据
                // y: [batchSize] 的标签(0-9)
                var X = torch.randn(new long[] { batchSize, 1, 28, 28 }, device: device);
                var y = torch.randint(0, 10, new long[] { batchSize }, device: device);
                
                // 1️⃣ 清零梯度
                // 🚫 避坑:每次反向传播前必须清零梯度,否则梯度会累加!
                optimizer.zero_grad();
                
                // 2️⃣ 前向传播
                var output = model.forward(X);
                
                // 3️⃣ 计算损失
                var loss = loss_fn.forward(output, y);
                
                // 4️⃣ 反向传播
                loss.backward();
                
                // 5️⃣ 更新参数
                optimizer.step();
                
                // 统计
                epochLoss += loss.ToSingle();
                var pred = output.argmax(dim: 1);
                correct += pred.eq(y).sum().ToInt32();
                total += (int)batchSize;
            }
            
            float avgLoss = epochLoss / numBatches;
            float accuracy = 100.0f * correct / total;
            Console.WriteLine($"  Epoch {epoch}/5 - Loss: {avgLoss:F4} - Accuracy: {accuracy:F2}%");
        }
        
        // ==================== 第五步:保存模型 ====================
        model.eval();  // 切换到评估模式
        var modelPath = "mnist_model.pt";
        torch.save(model.state_dict(), modelPath);
        Console.WriteLine($"\n💾 模型已保存至: {modelPath}");
        
        // ==================== 第六步:推理测试 ====================
        Console.WriteLine("\n🔮 测试推理...");
        using (var no_grad = torch.no_grad())  // 推理时不需要计算梯度
        {
            var testX = torch.randn(new long[] { 5, 1, 28, 28 }, device: device);
            var testOutput = model.forward(testX);
            var predictions = testOutput.argmax(dim: 1);
            Console.WriteLine($"  预测结果: [{string.Join(", ", predictions.data<long>().ToArray())}]");
        }
        
        Console.WriteLine("\n🎉 训练完成!");
    }
}

4.3 TorchSharp vs Python PyTorch代码对比

为了让你更直观地感受TorchSharp有多接近PyTorch,这里放个对比:

操作 Python PyTorch C# TorchSharp
创建张量 torch.randn(3, 3) torch.randn(new long[] {3, 3})
全连接层 nn.Linear(784, 128) Linear(784, 128)
激活函数 nn.ReLU() ReLU()
优化器 optim.Adam(model.parameters(), lr=0.001) Adam(model.parameters(), lr: 0.001)
前向传播 output = model(x) var output = model.forward(x)
保存模型 torch.save(model.state_dict(), "model.pt") torch.save(model.state_dict(), "model.pt")

几乎一模一样,对吧?唯一的区别就是C#版多了几个分号。

魔性比喻:TorchSharp就是把PyTorch这辆F1赛车换了个方向盘——引擎还是那个引擎,但你现在可以用C#驾照开了,不用去考Python驾照!

五、实战四:ONNX Runtime模型部署——把Python训练的模型拿来C#用

现实场景中,可能你们公司的AI团队是用Python训练的模型,但生产环境是C#写的。这时候ONNX Runtime就派上用场了——它能把PyTorch、TensorFlow、Scikit-learn等框架训练的模型转换成ONNX格式,然后在C#里高效推理。

5.1 Python端:训练并导出ONNX模型

# python_export.py - 在Python中训练模型并导出为ONNX格式
# (这部分是Python代码,仅作演示,实际C#项目中不需要)

import torch
import torch.nn as nn

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super().__init__()
        self.fc1 = nn.Linear(10, 20)
        self.fc2 = nn.Linear(20, 5)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.relu(self.fc1(x))
        x = self.fc2(x)
        return x

model = SimpleModel()
model.eval()

# 创建示例输入(ONNX需要知道输入的形状)
dummy_input = torch.randn(1, 10)

# 导出为ONNX格式
torch.onnx.export(
    model,
    dummy_input,
    "simple_model.onnx",
    input_names=['input'],
    output_names=['output'],
    dynamic_axes={'input': {0: 'batch_size'}, 'output': {0: 'batch_size'}}  # 支持动态batch
)
print("✅ 模型已导出为 simple_model.onnx")

5.2 C#端:加载ONNX模型进行推理

// ONNXInference.cs - 在C#中加载ONNX模型进行推理
// ⚠️ 重点:ONNX Runtime 是一个高性能推理引擎,支持CPU和GPU加速

using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms.Onnx;
using System.Linq;

class ONNXInference
{
    // 定义输入数据结构(必须与ONNX模型的输入匹配)
    public class ModelInput
    {
        [VectorType(10)]  // ⚠️ 重点:VectorType 指定数组长度,必须与模型输入维度一致
        [ColumnName("input")]  // 列名必须与ONNX模型的input_names一致
        public float[] Features { get; set; } = Array.Empty<float>();
    }

    // 定义输出数据结构
    public class ModelOutput
    {
        [VectorType(5)]  // 输出维度是5
        [ColumnName("output")]
        public float[] Scores { get; set; } = Array.Empty<float>();
    }

    static void Main(string[] args)
    {
        var mlContext = new MLContext();
        
        // ==================== 第一步:加载ONNX模型 ====================
        var modelPath = "simple_model.onnx";
        
        // 🚫 避坑:如果ONNX文件不存在,会抛出异常
        if (!File.Exists(modelPath))
        {
            Console.WriteLine($"❌ 找不到模型文件: {modelPath}");
            Console.WriteLine("  请先运行Python脚本导出ONNX模型");
            return;
        }
        
        Console.WriteLine($"📂 加载ONNX模型: {modelPath}");
        
        // ==================== 第二步:创建推理管道 ====================
        // 💡 技巧:ApplyOnnxModel 把ONNX模型集成到ML.NET管道中
        // 可以和其他ML.NET转换器组合使用
        var pipeline = mlContext.Transforms
            .ApplyOnnxModel(
                modelFile: modelPath,
                inputColumnNames: new[] { "input" },   // ONNX模型期望的输入名称
                outputColumnNames: new[] { "output" },  // ONNX模型输出的名称
                gpuDeviceId: null,                      // 不指定GPU,使用CPU
                fallbackToCpu: true                     // GPU不可用时回退到CPU
            );
        
        // 创建一个空的IDataView用于Fit
        // 💡 技巧:ONNX模型已经是训练好的,不需要再Fit
        var emptyData = mlContext.Data.LoadFromEnumerable(new List<ModelInput>());
        var model = pipeline.Fit(emptyData);
        
        // ==================== 第三步:创建预测引擎 ====================
        var predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(model);
        
        // ==================== 第四步:进行推理 ====================
        Console.WriteLine("\n🔮 开始推理...");
        
        // 创建测试输入(随机数据)
        var random = new Random(42);
        for (int i = 0; i < 3; i++)
        {
            var input = new ModelInput
            {
                Features = Enumerable.Range(0, 10).Select(_ => (float)random.NextDouble()).ToArray()
            };
            
            var prediction = predictionEngine.Predict(input);
            
            Console.WriteLine($"  样本 {i + 1}:");
            Console.WriteLine($"    输入: [{string.Join(", ", input.Features.Take(5).Select(f => f.ToString("F3")))}...]");
            Console.WriteLine($"    输出: [{string.Join(", ", prediction.Scores.Select(s => s.ToString("F4")))}]");
        }
        
        Console.WriteLine("\n🎉 推理完成!");
    }
}

5.3 更高级的ONNX部署:使用ONNX Runtime原生API

如果你需要更细粒度的控制(比如使用GPU、自定义内存分配),可以直接使用ONNX Runtime的原生C# API:

// ONNXRuntimeNative.cs - 使用ONNX Runtime原生API
using Microsoft.ML.OnnxRuntime;
using Microsoft.ML.OnnxRuntime.Tensors;
using System.Linq;

class ONNXRuntimeNative
{
    static void Main(string[] args)
    {
        // ==================== 配置会话选项 ====================
        // 💡 技巧:SessionOptions 可以配置执行提供程序、图优化级别等
        var sessionOptions = new SessionOptions
        {
            GraphOptimizationLevel = GraphOptimizationLevel.ORT_ENABLE_ALL,
            LogSeverityLevel = OrtLoggingLevel.ORT_LOGGINGLEVEL_WARNING
        };
        
        // 🚫 避坑:如果要使用GPU,需要安装 Microsoft.ML.OnnxRuntime.Gpu 包
        // 然后添加 sessionOptions.AppendExecutionProvider_CUDA(0);
        
        // ==================== 加载模型 ====================
        var modelPath = "simple_model.onnx";
        using var session = new InferenceSession(modelPath, sessionOptions);
        
        Console.WriteLine($"📂 加载ONNX模型成功");
        Console.WriteLine($"  输入数量: {session.InputMetadata.Count}");
        Console.WriteLine($"  输出数量: {session.OutputMetadata.Count}");
        
        // 打印输入输出信息
        foreach (var input in session.InputMetadata)
        {
            Console.WriteLine($"  输入: {input.Key} - 形状: [{string.Join(", ", input.Value.Dimensions)}]");
        }
        
        // ==================== 准备输入数据 ====================
        // ⚠️ 重点:输入必须是 DenseTensor<float> 类型
        var inputData = Enumerable.Range(0, 10).Select(i => (float)i / 10).ToArray();
        var inputTensor = new DenseTensor<float>(inputData, new[] { 1, 10 });  // [batch_size=1, features=10]
        
        // 创建输入容器
        var inputs = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input", inputTensor)
        };
        
        // ==================== 执行推理 ====================
        Console.WriteLine("\n🔮 执行推理...");
        
        using var results = session.Run(inputs);
        
        // 获取输出
        var output = results.First().AsTensor<float>();
        var outputArray = output.ToArray();
        
        Console.WriteLine($"  输出形状: [{string.Join(", ", output.Dimensions)}]");
        Console.WriteLine($"  输出值: [{string.Join(", ", outputArray.Select(v => v.ToString("F4")))}]");
        
        // ==================== 批量推理 ====================
        Console.WriteLine("\n📊 批量推理测试...");
        var batchInputs = Enumerable.Range(0, 4)
            .Select(_ => Enumerable.Range(0, 10).Select(i => (float)Random.Shared.NextDouble()).ToArray())
            .SelectMany(x => x)
            .ToArray();
        
        var batchTensor = new DenseTensor<float>(batchInputs, new[] { 4, 10 });
        var batchInputContainer = new List<NamedOnnxValue>
        {
            NamedOnnxValue.CreateFromTensor("input", batchTensor)
        };
        
        using var batchResults = session.Run(batchInputContainer);
        var batchOutput = batchResults.First().AsTensor<float>();
        
        Console.WriteLine($"  批量大小: 4");
        Console.WriteLine($"  输出形状: [{string.Join(", ", batchOutput.Dimensions)}]");
        
        Console.WriteLine("\n🎉 ONNX Runtime 推理完成!");
    }
}

金句:ONNX Runtime就是AI模型的“万能插座”——不管你是在PyTorch、TensorFlow还是Scikit-learn里训练出来的模型,插上就能在C#里用,完美解决“训练环境Python,生产环境C#”的割裂问题。

六、避坑清单:我在C#机器学习路上踩过的血泪坑

🚫 坑1:PredictionEngine不是线程安全的!

我当年的血泪史:把一个PredictionEngine声明成static,放在Web API里用,结果高并发下预测结果各种错乱,日志里全是诡异的异常。查了两天才发现PredictionEngine不是线程安全的!

解决方案

  • 每次预测时新建PredictionEngine(开销小,适合低并发)
  • 使用PredictionEnginePool(需要注入AddPredictionEnginePool服务),它会维护一个引擎池,自动处理线程安全
// 在Program.cs中注册
builder.Services.AddPredictionEnginePool<HouseData, HousePrediction>()
    .FromFile(modelName: "HousePriceModel", filePath: "HousePriceModel.zip");

// 在Controller中使用
public class PredictController : ControllerBase
{
    private readonly PredictionEnginePool<HouseData, HousePrediction> _predictionEnginePool;
    
    public PredictController(PredictionEnginePool<HouseData, HousePrediction> pool)
        => _predictionEnginePool = pool;
    
    [HttpPost]
    public IActionResult Predict([FromBody] HouseData input)
        => Ok(_predictionEnginePool.Predict(input));
}

🚫 坑2:AutoML的labelColumnName写错了

AutoML实验跑了半天,最后报错“找不到Label列”。我把数据类的Price属性名写成了“Price”,但AutoML的labelColumnName参数写成了“Label”——它就认死理,必须完全一致!

解决方案:要么在数据类上加上[ColumnName("Label")],要么在AutoML调用时把labelColumnName写成实际的属性名。

🚫 坑3:TorchSharp的张量形状搞错了

用TorchSharp写神经网络,Linear层的输入维度写反了,编译不报错,运行时直接崩溃。Python里是nn.Linear(in_features, out_features),TorchSharp里是Linear(in_features, out_features)——顺序一样,但我抄代码时抄错了。

解决方案:记住口诀“输入在前,输出在后”,每次定义Linear时默念一遍。

🚫 坑4:ONNX模型的输入名称不匹配

用Netron工具查看ONNX模型,输入名称是“input”,我在C#代码里写成了“features”,结果运行时一直报“Input name not found”。

解决方案:用Netron(免费的可视化工具)打开ONNX文件,确认输入输出的准确名称。或者用代码动态获取:

using var session = new InferenceSession("model.onnx");
foreach (var input in session.InputMetadata)
{
    Console.WriteLine($"输入名称: {input.Key}");  // 复制这个名称
}

🚫 坑5:忘记设置种子导致结果不可复现

训练了一个模型,效果贼好。第二天重新训练,同样的数据、同样的代码,效果差了一大截——因为没设随机种子!

解决方案:训练前一定要设置种子:

var mlContext = new MLContext(seed: 42);  // ML.NET
torch.manual_seed(42);                    // TorchSharp

🚫 坑6:ML.NET模型文件路径问题

把模型保存到相对路径,部署到Docker里就找不到了。或者保存到了绝对路径,换台机器直接GG。

解决方案:使用相对于应用程序目录的路径:

var modelPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "model.zip");

🚫 坑7:没检查ML.NET的版本兼容性

ML.NET从1.x到5.0有不少Breaking Changes。最典型的就是Microsoft.ML.AutoML从单独的NuGet包合并到了主包里。如果你看的是老教程,包都装不对。

解决方案

  • ML.NET 5.0+:AutoML已内置,只需装Microsoft.ML
  • ML.NET 4.x及以下:需要单独装Microsoft.ML.AutoML
  • 推荐用.NET 8及以上,ML.NET 5.0是最新稳定版

七、2026年C#机器学习新动向:生成式AI和大模型

2025年底,ML.NET 5.0正式发布,带来了一些重磅新特性,标志着C#正式进入大模型时代:

7.1 Microsoft.ML.GenAI:C#原生支持大语言模型

ML.NET 5.0引入了Microsoft.ML.GenAI命名空间,包含CausalLMPipelineChatClient等API,可以在C#中直接使用因果语言模型进行对话。同时还提供了SFT(监督微调)示例,让开发者能够在自己的数据上微调模型。

7.2 Tokenizer库支持主流大模型

ML.NET 5.0的Tokenizer库增加了对多种大模型分词器的支持:

  • Phi-4 Tokenizer
  • DeepSeek模型的ByteLevel BPE编码
  • GPT-4.1的Tiktoken分词器
  • OpenAI O3模型映射

2026年3月发布的ML.NET 6.0-preview1更进一步,开始支持GPT-5.1、GPT-5.2、GPT-5.3模型的分词。

7.3 .NET正在成为“AI优先”的平台

微软正在把.NET打造成一个AI原生的开发平台。从ML.NET到Semantic Kernel(用于编排LLM),再到Azure AI Services SDK,.NET开发者现在拥有完整的AI工具链。.NET 10版本更是将AI能力深度集成到了框架的DNA中。

八、选型指南:我该用哪个?

你的场景 推荐工具 理由
表格数据分类/回归(中小规模) ML.NET + AutoML 上手最快,30行代码搞定
表格数据分类/回归(大规模) ML.NET + LightGBM LightGBM是大数据集的王者
图像识别/目标检测 TorchSharp 或 ONNX Runtime TorchSharp训练,ONNX部署
文本分类/情感分析 ML.NET文本分类API 内置了特征提取,简单好用
自然语言处理(NLP) TorchSharp + 预训练模型 支持BERT、Transformer等
使用Python团队训练的模型 ONNX Runtime 无缝导入,无需重训练
大语言模型集成 ML.NET GenAI / Semantic Kernel C#原生调用LLM
实时异常检测 ML.NET + Iot.Device.Bindings 可以直接部署到边缘设备

金句:选工具就像选咖啡——ML.NET是美式,朴实管饱;TorchSharp是手冲,需要点手艺但风味独特;ONNX Runtime是速溶,拿来就能用,不费脑子。关键是——别用Python在C#项目里硬凑,那叫往美式里倒豆浆,两败俱伤!

九、总结与互动

核心要点速记

  1. ML.NET:微软官方出品,适合表格数据的分类、回归、聚类任务,AutoML功能让小白也能快速上手
  2. TorchSharp:PyTorch的C#绑定,适合深度学习任务(图像、文本、序列),代码风格与Python版几乎一致
  3. ONNX Runtime:跨框架推理引擎,适合部署Python训练的模型,支持CPU/GPU/NPU加速
  4. 2026新趋势:ML.NET 5.0/6.0全面拥抱大模型,GenAI命名空间让C#开发者也能玩转LLM
  5. 避坑要点:PredictionEngine非线程安全、设置随机种子保证可复现、ONNX输入名称要精确匹配

🎤 评论区见!

老铁们,你们在C#里玩过机器学习吗?是直接用ML.NET还是自己封装Python调用?有没有遇到过比“PredictionEngine线程安全”更奇葩的坑?

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐