准备建模的数据

了解如何使用 ML.NET 来准备数据用于进行其他处理或生成模型。

数据通常是不干净的和稀疏的。 ML.NET 机器学习算法期望输入或特征位于单个数字向量中。 同样,必须对要预测的值(标签)进行编码,尤其当该值是分类数据时。 因此,数据准备的目标之一是将数据转换为 ML.NET 算法所期望的格式。

将数据拆分为训练集和测试集

以下部分概述了训练称为过拟合和欠拟合的模型时的常见问题。 使用保留集拆分数据和验证模型有助于识别和缓解这些问题。

过拟合和欠拟合

过拟合和欠拟合是训练模型时遇到的两个最常见问题。 欠拟合意味着所选的训练器不足以适应训练数据集,并且通常在训练期间导致高损失,并且测试数据集的分数/指标较低。 若要解决此问题,需要选择功能更强大的模型或执行更多特征工程。 过拟合的情况正好相反,当模型很好地学习训练数据时,就会发生这种情况。 这通常会导致训练期间的损失指标较低,但测试数据集的损失较高。

这些概念的一个很好的类比是为考试而学习。 假设你提前知道问题和答案。 学习后,你参加了考试,并获得了满分。 好消息! 但是,当你再次参加考试时,问题重新排列并且措辞略有不同,你会得到较低的分数。 这表明你记住了答案,并没有真正学会你正在测试的概念。 这是过拟合的示例。 欠拟合的情况正好相反,你得到的学习材料不能准确地代表你参加考试的评估。 因此,你不得不猜测答案,因为你没有足够的知识来正确回答。

拆分数据

获取以下输入数据并将其加载到名为 dataIDataView

var homeDataList = new HomeData[]
{
    new()
    {
        NumberOfBedrooms = 1f,
        Price = 100_000f
    },
    new()
    {
        NumberOfBedrooms = 2f,
        Price = 300_000f
    },
    new()
    {
        NumberOfBedrooms = 6f,
        Price = 600_000f
    },
    new()
    {
        NumberOfBedrooms = 3f,
        Price = 300_000f
    },
    new()
    {
        NumberOfBedrooms = 2f,
        Price = 200_000f
    }
};

若要将数据拆分为训练集/测试集,请使用 TrainTestSplit(IDataView, Double, String, Nullable<Int32>) 方法。

// Apply filter
TrainTestData dataSplit = mlContext.Data.TrainTestSplit(data, testFraction: 0.2);

testFraction 参数用于将数据集的 0.2 或 20% 用于测试。 其余 80% 用于训练。

结果是 DataOperationsCatalog.TrainTestData 具有两个 IDataView,可以通过 TrainSetTestSet 访问。

筛选数据

有时,并非数据集中的所有数据都与分析相关。 删除不相关数据的方法之一是筛选。 DataOperationsCatalog 包含一组筛选操作,这些操作接收包含所有数据的 IDataView,并返回仅包含关注数据点的 IDataView。 值得注意的是,因为筛选操作不像 中的操作那样是 IEstimatorTransformsCatalogITransformer,所以它们不能作为 EstimatorChainTransformerChain 数据准备管道的一部分包含在内。

获取以下输入数据并将其加载到名为 dataIDataView

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=600000f
    }
};

若要根据列的值筛选数据,请使用 FilterRowsByColumn 方法。

// Apply filter
IDataView filteredData = mlContext.Data.FilterRowsByColumn(data, "Price", lowerBound: 200000, upperBound: 1000000);

上述示例采用数据集中价格介于 200,000 和 1,000,000 之间的行。 应用此筛选器的结果为,将仅返回数据中的最后两行,并排除第一行,因为其价格为 100,000,不在指定范围之间。

替换缺失值

缺失值在数据集中是常见现象。 处理缺失值的一种方法是使用给定类型的默认值(如有)或其他有意义的值(例如数据中的平均值)替换它们。

获取以下输入数据并将其加载到名为 dataIDataView

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=float.NaN
    }
};

请注意,列表中最后一个元素的 Price 缺失值。 若要替换 Price 列中的缺失值,请使用 ReplaceMissingValues 方法填充该缺失值。

重要

ReplaceMissingValue 仅适用于数字数据。

// Define replacement estimator
var replacementEstimator = mlContext.Transforms.ReplaceMissingValues("Price", replacementMode: MissingValueReplacingEstimator.ReplacementMode.Mean);

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer replacementTransformer = replacementEstimator.Fit(data);

// Transform data
IDataView transformedData = replacementTransformer.Transform(data);

ML.NET 支持各种替换模式。 上述示例使用 Mean 替换模式,该模式将使用该列的平均值填充缺失值。 替换的结果使用 200,000 填充数据中最后一个元素的 Price 属性,因为它是 100,000 和 300,000 的平均值。

使用规范化程序

规范化是一种数据预处理方法,用于将特征扩展到同一范围(通常介于 0 和 1 之间),这样机器学习算法可以更准确地处理这些特征。 例如,年龄和收入的范围存在明显差异,年龄的范围通常为 0-100,而收入的范围通常为零到数千。 访问转换页面,获取更详细的规范化转换列表和说明。

最小-最大规范化

获取以下输入数据并将其加载到名为 dataIDataView

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms = 2f,
        Price = 200000f
    },
    new ()
    {
        NumberOfBedrooms = 1f,
        Price = 100000f
    }
};

可以向包含单个数值及矢量的列应用规范化。 使用 NormalizeMinMax 方法通过最小-最大规范化来规范化 Price 列中的数据。

// Define min-max estimator
var minMaxEstimator = mlContext.Transforms.NormalizeMinMax("Price");

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer minMaxTransformer = minMaxEstimator.Fit(data);

// Transform data
IDataView transformedData = minMaxTransformer.Transform(data);

原始价格值 [200000,100000] 使用 MinMax 规范化公式转换为 [ 1, 0.5 ],该公式生成范围在 0-1 之间的输出值。

Binning

分箱将连续值转换为输入的离散表示形式。 例如,假设某个特征为年龄。 分箱不使用实际年龄值,而是为该值创建范围。 0-18 可以是一个箱,另一个箱可以是 19-35,依此类推。

获取以下输入数据并将其加载到名为 dataIDataView

HomeData[] homeDataList = new HomeData[]
{
    new ()
    {
        NumberOfBedrooms=1f,
        Price=100000f
    },
    new ()
    {
        NumberOfBedrooms=2f,
        Price=300000f
    },
    new ()
    {
        NumberOfBedrooms=6f,
        Price=600000f
    }
};

使用 NormalizeBinning 方法将数据规范化为箱。 maximumBinCount 参数使你可以指定对数据进行分类所需的箱数。 在此示例中,数据将放入两个箱中。

// Define binning estimator
var binningEstimator = mlContext.Transforms.NormalizeBinning("Price", maximumBinCount: 2);

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
var binningTransformer = binningEstimator.Fit(data);

// Transform Data
IDataView transformedData = binningTransformer.Transform(data);

分箱的结果为创建 [0,200000,Infinity] 的分箱边界。 因此,所得到的箱为 [0,1,1],因为第一个观测在 0-200,000 之间,而其他观测则大于 200,000 但小于无穷大。

使用分类数据

最常见的数据类型之一是分类数据。 分类数据具有有限数量的类别。 例如,美国的州或一组照片中发现的动物类型列表。 无论分类数据是特征还是标签,都必须映射到数值,以便它们可用于生成机器学习模型。 在 ML.NET 中使用分类数据的方法有很多,具体取决于要解决的问题。

键值映射

在 ML.NET 中,键是表示类别的整数值。 键值映射最常用于将字符串标签映射为训练的唯一整数值,并在使用模型进行预测时映射回它们的字符串值。

用于执行键值映射的转换是 MapValueToKeyMapKeyToValue

MapValueToKey 在模型中添加映射字典,以便 MapKeyToValue 可以在进行预测时执行反向转换。

一个热编码

一个热编码使用一组有限的值,并将它们映射到整数,这些整数的二进制表示形式在字符串中的唯一位置有一个 1 值。 如果没有对分类数据进行隐式排序,则一个热编码可能是最佳选择。 下表显示了一个示例,其中邮政编码为原始值。

原始值 一个热编码值
98052 00...01
98100 00...10
... ...
98109 10...00

将分类数据转换为一个热编码数字的转换是 OneHotEncoding

哈希

哈希是将分类数据转换为数字的另一种方法。 哈希函数将任意大小(例如文本字符串)的数据映射到具有固定范围的数字。 哈希是一种快速且节省空间的向量化特征的方法。 机器学习中的哈希的一个显著示例是垃圾邮件筛选,它不是维护一个包含已知单词的字典,而是对电子邮件中的每个单词进行哈希处理并将其添加到一个大型特征向量中。 以此方式使用哈希,可以通过使用不在字典中的单词来避免恶意垃圾邮件筛选规避的问题。

ML.NET 提供哈希转换,对文本、日期和数值数据执行哈希处理。 类似于值键映射,哈希转换输出为键类型。

使用文本数据

类似于分类数据,在用于生成机器学习模型之前,需要将文本数据转换为数字特征。 访问转换页面,获取更详细的文本转换列表和说明。

使用类似以下已加载到 IDataView 中的数据的数据:

ReviewData[] reviews = new ReviewData[]
{
    new ReviewData
    {
        Description="This is a good product",
        Rating=4.7f
    },
    new ReviewData
    {
        Description="This is a bad product",
        Rating=2.3f
    }
};

ML.NET 提供 FeaturizeText 转换,该转换采用文本的字符串值,并通过应用一系列单个转换从文本创建一组特征。

// Define text transform estimator
var textEstimator  = mlContext.Transforms.Text.FeaturizeText("Description");

// Fit data to estimator
// Fitting generates a transformer that applies the operations of defined by estimator
ITransformer textTransformer = textEstimator.Fit(data);

// Transform data
IDataView transformedData = textTransformer.Transform(data);

生成的转换会将 Description 列中的文本值转换为类似以下输出的数字向量:

[ 0.2041241, 0.2041241, 0.2041241, 0.4082483, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0.2041241, 0, 0, 0, 0, 0.4472136, 0.4472136, 0.4472136, 0.4472136, 0.4472136, 0 ]

还可以单独应用组成 FeaturizeText 的转换,对特征生成进行更精细的控制。

// Define text transform estimator
var textEstimator = mlContext.Transforms.Text.NormalizeText("Description")
    .Append(mlContext.Transforms.Text.TokenizeIntoWords("Description"))
    .Append(mlContext.Transforms.Text.RemoveDefaultStopWords("Description"))
    .Append(mlContext.Transforms.Conversion.MapValueToKey("Description"))
    .Append(mlContext.Transforms.Text.ProduceNgrams("Description"))
    .Append(mlContext.Transforms.NormalizeLpNorm("Description"));

textEstimator 包含 FeaturizeText 方法执行的一组操作。 更复杂管道的好处在于对应用于数据的转换的控制和可见性。

以第一个条目为例,以下是对 textEstimator 定义的转换步骤产生的结果的详细说明:

原始文本:This is a good product

转换 说明 结果
1. NormalizeText 默认情况下将所有字母转换为小写字母 this is a good product
2. TokenizeWords 将字符串拆分为单独的字词 ["this","is","a","good","product"]
3. RemoveDefaultStopWords 删除 is 和 a 等非索引字。 ["good","product"]
4. MapValueToKey 根据输入数据将值映射到键(类别) [1,2]
5. ProduceNGrams 将文本转换为连续单词的序列 [1,1,1,0,0]
6. NormalizeLpNorm 按缩放的 lp 规范缩放输入 [ 0.577350529, 0.577350529, 0.577350529, 0, 0 ]