18.1 机器学习概念与工作流
ML.NET是微软开源的跨平台机器学习框架,支持.NET 开发者用 C#/F# 构建模型,核心工作流:数据加载→数据预处理→模型训练→模型评估→模型部署。
核心概念:
- 特征(Feature):模型输入数据(如文本长度、图片像素);
- 标签(Label):模型预测目标(如 “垃圾邮件”/“正常邮件”、房价);
- 训练集 / 测试集:训练集用于模型学习,测试集用于评估模型精度;
- 模型(Model):训练后生成的预测规则(如分类器、回归器)。
18.2ML.NET架构与管道设计
ML.NET通过管道(Pipeline) 串联数据处理与模型训练步骤,管道包含Estimator(数据转换器 / 模型训练器)和Transformer(训练后生成的执行器),支持模块化组合。
实战:ML.NET管道创建(房价预测)
- 安装 NuGet 包:Microsoft.ML、Microsoft.ML.Regression。
- 定义数据模型:
// 输入模型(特征+标签)
public class HouseData
{
[LoadColumn(0)] public float Size { get; set; } // 特征1:房屋面积(平方米)
[LoadColumn(1)] public int Rooms { get; set; } // 特征2:房间数
[LoadColumn(2)] public float Price { get; set; } // 标签:房价(万元)
}
// 预测输出模型
public class HousePricePrediction
{
[ColumnName("Score")] public float PredictedPrice { get; set; } // 预测房价
}
- 构建管道与训练模型:
public class HousePricePredictor
{
private readonly MLContext _mlContext;
private ITransformer _trainedModel;
public HousePricePredictor()
{
_mlContext = new MLContext(seed: 1); // 固定种子,确保结果可复现
}
// 训练模型
public void TrainModel(string dataPath)
{
// 1. 加载数据(CSV文件)
var dataView = _mlContext.Data.LoadFromTextFile<HouseData>(
path: dataPath,
separatorChar: ',',
hasHeader: true); // CSV第一行为表头
// 2. 拆分训练集(80%)和测试集(20%)
var trainTestSplit = _mlContext.Data.TrainTestSplit(dataView, testFraction: 0.2);
var trainData = trainTestSplit.TrainSet;
var testData = trainTestSplit.TestSet;
// 3. 构建管道
var pipeline = _mlContext.Transforms.Concatenate(
"Features", // 合并特征列到"Features"列
nameof(HouseData.Size),
nameof(HouseData.Rooms))
.Append(_mlContext.Transforms.NormalizeMinMax("Features")) // 特征归一化(0-1范围)
.Append(_mlContext.Regression.Trainers.Sdca(labelColumnName: nameof(HouseData.Price), featureColumnName: "Features")); // 回归训练器(SDCA算法)
// 4. 训练模型
_trainedModel = pipeline.Fit(trainData);
// 5. 评估模型
var predictions = _trainedModel.Transform(testData);
var metrics = _mlContext.Regression.Evaluate(predictions, labelColumnName: nameof(HouseData.Price));
// 输出评估指标(越小越好)
Console.WriteLine($"平均绝对误差(MAE):{metrics.MeanAbsoluteError:F2}");
Console.WriteLine($"均方误差(MSE):{metrics.MeanSquaredError:F2}");
Console.WriteLine($"R²得分(拟合度,越接近1越好):{metrics.RSquared:F2}");
// 6. 保存模型
_mlContext.Model.Save(_trainedModel, trainData.Schema, "house_price_model.zip");
Console.WriteLine("模型已保存到:house_price_model.zip");
}
// 加载模型并预测
public HousePricePrediction Predict(HouseData newHouse)
{
if (_trainedModel == null)
{
// 加载已保存的模型
var modelPath = "house_price_model.zip";
var modelSchema = _mlContext.Data.ReadSchemaFromModel(modelPath);
_trainedModel = _mlContext.Model.Load(modelPath, out modelSchema);
}
// 创建预测引擎
var predictionEngine = _mlContext.Model.CreatePredictionEngine<HouseData, HousePricePrediction>(_trainedModel);
// 执行预测
return predictionEngine.Predict(newHouse);
}
}
- 使用模型:
// 训练模型(数据文件house_data.csv包含Size,Rooms,Price)
var predictor = new HousePricePredictor();
predictor.TrainModel("house_data.csv");
// 预测新房屋价格(100㎡,3个房间)
var newHouse = new HouseData { Size = 100, Rooms = 3 };
var prediction = predictor.Predict(newHouse);
Console.WriteLine($"预测房价:{prediction.PredictedPrice:F2}万元");
18.3 数据加载与预处理
ML.NET支持加载 CSV、TSV、JSON、数据库等数据源,数据预处理是关键步骤(如缺失值填充、特征编码、文本分词),直接影响模型精度。
实战:文本数据预处理(垃圾邮件分类)
// 数据模型
public class EmailData
{
[LoadColumn(0)]
public string Text { get; set; } // 邮件文本(特征)
[LoadColumn(1), ColumnName("Label")]
public bool IsSpam { get; set; } // 是否垃圾邮件(标签)
}
public class EmailPrediction
{
[ColumnName("PredictedLabel")]
public bool IsSpam { get; set; } // 预测结果
public float Score { get; set; } // 预测置信度
}
// 数据预处理管道
var pipeline = _mlContext.Transforms.Text.FeaturizeText(
outputColumnName: "TextFeatures",
inputColumnName: nameof(EmailData.Text)) // 文本特征提取(分词、词袋模型)
.Append(_mlContext.Transforms.Concatenate(
"Features",
"TextFeatures")) // 合并特征列
.Append(_mlContext.Transforms.NormalizeLpNorm(
"Features",
normKind: NormalizationNormalizerKind.L2)) // L2归一化
.Append(_mlContext.BinaryClassification.Trainers.SdcaLogisticRegression(
labelColumnName: "Label",
featureColumnName: "Features")); // 二分类训练器
常见数据预处理操作:
|
操作 |
用途 |
API 示例 |
|
文本特征提取 |
将文本转为数值向量 |
Text.FeaturizeText() |
|
缺失值填充 |
处理空值(用均值 / 中位数 / 默认值) |
ReplaceMissingValues() |
|
独热编码 |
处理分类特征(如 “城市”→0/1 向量) |
Categorical.OneHotEncoding() |
|
特征归一化 |
缩放特征到统一范围(0-1 或 – 1-1) |
NormalizeMinMax() /NormalizeStandard() |
|
特征选择 |
筛选重大特征,减少维度 |
SelectFeaturesBasedOnMutualInformation() |
18.4 特征工程与数据转换
特征工程是 “从原始数据提取有效特征” 的过程,ML.NET提供CustomMapping实现自定义特征转换(如计算衍生特征),提升模型表达能力。
实战:自定义特征转换(计算房屋单价)
// 1. 定义输入/输出模型(包含衍生特征)
public class HouseDataWithDerived
{
public float Size { get; set; }
public int Rooms { get; set; }
public float Price { get; set; }
}
public class HouseDataWithUnitPrice
{
public float Size { get; set; }
public int Rooms { get; set; }
public float Price { get; set; }
public float UnitPrice { get; set; } // 衍生特征:单价(Price/Size)
}
// 2. 定义自定义映射逻辑
Action<HouseDataWithDerived, HouseDataWithUnitPrice> mapping = (input, output) =>
{
// 复制原始字段
output.Size = input.Size;
output.Rooms = input.Rooms;
output.Price = input.Price;
// 计算衍生特征(单价),避免除以0
output.UnitPrice = input.Size > 0 ? input.Price / input.Size : 0;
};
// 3. 管道中添加自定义转换
var pipeline = _mlContext.Transforms.CustomMapping(mapping, "HouseUnitPriceMapping")
.Append(_mlContext.Transforms.Concatenate(
"Features",
nameof(HouseDataWithUnitPrice.Size),
nameof(HouseDataWithUnitPrice.Rooms),
nameof(HouseDataWithUnitPrice.UnitPrice)))
.Append(_mlContext.Regression.Trainers.Sdca());
18.5 模型训练与评估
ML.NET按任务类型提供对应训练器(分类、回归、聚类、推荐),训练后需通过评估指标判断模型性能,选择最优模型。
常见任务类型与训练器:
|
任务类型 |
目标 |
常用训练器 |
评估指标 |
|
二分类 |
预测二值标签(是 / 否) |
SdcaLogisticRegression 、LbfgsLogisticRegression |
准确率、召回率、F1 分数、AUC-ROC |
|
多分类 |
预测多值标签(如颜色:红 / 绿 / 蓝) |
SdcaNonCalibrated 、OneVersusAll |
多分类准确率、混淆矩阵 |
|
回归 |
预测连续值(如房价、温度) |
Sdca 、FastTree 、LinearRegression |
MAE、MSE、R² 得分 |
|
聚类 |
无监督分组(如客户分群) |
KMeans |
轮廓系数(Silhouette Coefficient) |
|
推荐系统 |
预测用户偏好(如商品推荐) |
MatrixFactorizationTrainer |
均方根误差(RMSE) |
评估模型示例(二分类):
// 训练二分类模型(垃圾邮件分类)
var pipeline = _mlContext.Transforms.Text.FeaturizeText("Features", nameof(EmailData.Text))
.Append(_mlContext.BinaryClassification.Trainers.SdcaLogisticRegression());
var trainedModel = pipeline.Fit(trainData);
var predictions = trainedModel.Transform(testData);
// 评估模型
var metrics = _mlContext.BinaryClassification.Evaluate(
predictions,
labelColumnName: "Label",
scoreColumnName: "Score");
// 输出评估结果
Console.WriteLine($"准确率(Accuracy):{metrics.Accuracy:F4}"); // 正确预测比例
Console.WriteLine($"准确率(Precision):{metrics.Precision:F4}"); // 预测为正例的正确比例
Console.WriteLine($"召回率(Recall):{metrics.Recall:F4}"); // 实际为正例的正确预测比例
Console.WriteLine($"F1分数:{metrics.F1Score:F4}"); // 准确率与召回率的调和平均
Console.WriteLine($"AUC-ROC:{metrics.AreaUnderRocCurve:F4}"); // ROC曲线下面积(越接近1越好)
