ML.NET乳腺癌二分类模型所有实例预测为False问题求助
我来帮你排查下这个全预测False的问题,这在二分类任务里很常见,大多是类别不平衡、特征处理不当或者数据映射错误导致的,咱们一步步来解决:
1. 先移除无关的Id特征
你当前的特征列表里包含了Id字段,这只是样本的唯一标识,完全没有预测价值,反而会给模型引入噪声,先把它从特征拼接列表里删掉:
var trainingPipeline = mlContext.Transforms.Concatenate(outputColumnName: "Features", nameof(BreastCancerData.AreaMean), nameof(BreastCancerData.AreaSe), // 删掉nameof(BreastCancerData.Id) nameof(BreastCancerData.PerimeterMean), // 其他特征保留,只去掉Id ...);
2. 给特征做标准化处理
LinearSVM这类基于距离计算的模型对特征尺度非常敏感,比如AreaMean的数值远大于SmoothnessMean,会导致模型只关注大数值特征,忽略其他重要特征。必须添加标准化步骤,把所有特征缩放到相同尺度:
var trainingPipeline = mlContext.Transforms.Concatenate(outputColumnName: "Features", // 你的特征列表(已去掉Id) nameof(BreastCancerData.AreaMean), nameof(BreastCancerData.AreaSe), ...) // 新增标准化步骤,将特征缩放到0-1区间 .Append(mlContext.Transforms.NormalizeMinMax(outputColumnName: "Features", inputColumnName: "Features")) .Append(mlContext.Transforms.CopyColumns(outputColumnName: "Label", inputColumnName: nameof(BreastCancerData.Diagnosis))) .Append(trainer);
3. 处理类别不平衡问题
从测试集75阴25阳的分布来看,数据存在明显的类别不平衡,模型为了追求高准确率会自动倾向于预测占比更高的阴性类别。这里有两种解决思路:
方法A:给少数类设置权重
给阳性(少数类)设置更高的权重,让模型更关注这类样本的预测:
// 创建LinearSVM训练器时指定阳性样本权重(75/25=3,用这个值平衡两类影响) var trainer = mlContext.BinaryClassification.Trainers.LinearSvm( labelColumnName: "Label", featureColumnName: "Features", positiveInstanceWeight: 3f);
方法B:换用更适配不平衡数据的训练器
比如LightGbm或LogisticRegression,这类训练器对不平衡数据的适应性更好:
// 示例:使用LightGbm训练器并设置类别权重 var trainer = mlContext.BinaryClassification.Trainers.LightGbm( labelColumnName: "Label", featureColumnName: "Features", positiveInstanceWeight: 3f);
4. 验证Label的映射是否正确
这可能是核心问题!威斯康星乳腺癌数据集的诊断字段原始值是字符串("M"代表恶性,"B"代表良性),但你的代码里直接用bool Diagnosis加载,这会导致字符串无法正确映射为布尔值,最终所有Label可能都是False,模型自然只会预测False。
你需要先修改数据类,再添加映射步骤:
// 修改BreastCancerData,先加载字符串格式的诊断值 public class BreastCancerData { [LoadColumn(0)] public float Id { get; set; } [LoadColumn(1)] public string DiagnosisStr { get; set; } // 原始字符串诊断值 [LoadColumn(2)] public float RadiusMean { get; set; } // 其他特征字段保持不变... }
然后在训练管道里添加字符串到布尔值的映射:
var trainingPipeline = mlContext.Transforms.Concatenate(outputColumnName: "Features", // 已去掉Id的特征列表 nameof(BreastCancerData.AreaMean), nameof(BreastCancerData.AreaSe), ...) .Append(mlContext.Transforms.NormalizeMinMax(outputColumnName: "Features", inputColumnName: "Features")) // 新增:将字符串诊断值映射为Label(B=良性/False,M=恶性/True) .Append(mlContext.Transforms.Conversion.MapValue( outputColumnName: "Label", keyValuePairs: new[] { ("B", false), ("M", true) }, inputColumnName: nameof(BreastCancerData.DiagnosisStr))) .Append(trainer);
5. 检查训练集的类别分布
在训练前先确认训练集里的阳性/阴性样本数量,避免极端不平衡:
// 统计训练集的Label分布 var labelCounts = mlContext.Data.CreateEnumerable<BreastCancerData>(trainingDataView, reuseRowObject: false) .GroupBy(x => x.DiagnosisStr) .Select(g => new { Diagnosis = g.Key, Count = g.Count() }); foreach (var count in labelCounts) { Console.WriteLine($"诊断类型 {count.Diagnosis}: {count.Count} 个样本"); }
如果训练集里阳性样本占比极低,还可以用过采样增加阳性样本数量:
// 对少数类进行过采样 var oversampledData = mlContext.Data.Oversample(trainingDataView, labelColumnName: "Label"); // 使用过采样后的数据集训练模型 var model = trainingPipeline.Fit(oversampledData);
内容的提问来源于stack exchange,提问作者Roland Iordache




