You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动