许多机器学习算法都期望数据能够保持一致的缩放。
在对机器学习数据进行缩放时,您应该考虑两种流行的方法。
在本教程中,您将学习如何重新缩放您的机器学习数据。阅读本教程后,您将了解:
- 如何从头开始归一化数据。
- 如何从头开始标准化数据。
- 何时使用归一化而不是标准化数据。
使用我的新书《从头开始的机器学习算法》启动您的项目,其中包括所有示例的分步教程和 Python 源代码文件。
让我们开始吧。
- 2018年2月更新:修正了最小/最大代码示例中的小错字。
- **2018 年 3 月更新**:添加了下载数据集的备用链接,因为原始链接似乎已被删除。
- 2018 年 8 月更新:测试并更新以与 Python 3.6 配合使用。

如何使用 Python 从头开始准备机器学习数据
图片由 Ondra Chotovinsky 提供,保留部分权利。
描述
许多机器学习算法期望输入甚至输出数据的规模是等价的。
这有助于在通过加权输入进行预测的方法中,例如线性回归和逻辑回归。
在以复杂方式组合加权输入的方法中,例如人工神经网络和深度学习,这几乎是必需的。
在本教程中,我们将练习重新缩放一个标准机器学习数据集(CSV 格式)。
具体来说,是Pima Indians数据集。它包含768行和9列。文件中的所有值都是数字,特别是浮点值。我们将首先学习如何加载文件,然后学习如何将加载的字符串转换为数值。
教程
本教程分为3个部分:
- 数据归一化。
- 数据标准化。
- 何时进行归一化和标准化。
这些步骤将为您处理数据缩放提供必要的基础。
1. 归一化数据
归一化可以指代不同的技术,具体取决于上下文。
在这里,我们使用归一化来指将输入变量重新缩放到 0 到 1 的范围之间。
归一化要求您了解每个属性的最小值和最大值。
这可以从训练数据中估计,或者如果您对问题领域有深入了解,也可以直接指定。
您可以通过枚举值轻松估计数据集中每个属性的最小值和最大值。
以下代码片段定义了 **dataset_minmax()** 函数,该函数计算数据集中每个属性的最小值和最大值,然后返回这些最小值和最大值的数组。
1 2 3 4 5 6 7 8 9 |
# 查找每列的最小值和最大值 def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax |
我们可以构造一个用于测试的小型数据集,如下所示:
1 2 3 |
x1 x2 50 30 20 90 |
使用这个构造的数据集,我们可以测试我们计算每列最小和最大值的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
# 查找每列的最小值和最大值 def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax # 构造小型数据集 dataset = [[50, 30], [20, 90]] print(dataset) # 计算每列的最小值和最大值 minmax = dataset_minmax(dataset) print(minmax) |
运行示例会产生以下输出。
首先,数据集以列表的列表格式打印,然后以 **column1: min,max and column2: min,max** 的格式打印每列的最小值和最大值。
例如:
1 2 |
[[50, 30], [20, 90]] [[20, 50], [30, 90]] |
一旦我们估算了每列允许的最大值和最小值,我们现在就可以将原始数据归一化到0到1的范围。
归一化列单个值的计算公式为
1 |
scaled_value = (value - min) / (max - min) |
下面是一个名为 **normalize_dataset()** 的函数实现,该函数归一化给定数据集中每列的值。
1 2 3 4 5 |
# 将数据集列重新缩放到 0-1 范围 def normalize_dataset(dataset, minmax): for row in dataset: for i in range(len(row)): row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0]) |
我们可以将此函数与 **dataset_minmax()** 函数结合起来,归一化构造的数据集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
# 查找每列的最小值和最大值 def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax # 将数据集列重新缩放到 0-1 范围 def normalize_dataset(dataset, minmax): for row in dataset: for i in range(len(row)): row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0]) # 构造小型数据集 dataset = [[50, 30], [20, 90]] print(dataset) # 计算每列的最小值和最大值 minmax = dataset_minmax(dataset) print(minmax) # 归一化列 normalize_dataset(dataset, minmax) print(dataset) |
运行此示例将打印以下输出,包括归一化的数据集。
1 2 3 |
[[50, 30], [20, 90]] [[20, 50], [30, 90]] [[1, 0], [0, 1]] |
我们可以将此代码与加载CSV数据集的代码结合起来,加载并归一化Pima Indians糖尿病数据集。
下载Pima Indians数据集并将其以 **pima-indians-diabetes.csv** 的名称放置在当前目录中。
打开文件并删除底部的任何空行。
该示例首先加载数据集,并将每列的值从字符串转换为浮点值。然后从数据集中估计每列的最小值和最大值,最后对数据集中的值进行归一化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
from csv import reader # 加载 CSV 文件 def load_csv(filename): file = open(filename, "rb") lines = reader(file) dataset = list(lines) return dataset # 将字符串列转换为浮点数 def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 查找每列的最小值和最大值 def dataset_minmax(dataset): minmax = list() for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] value_min = min(col_values) value_max = max(col_values) minmax.append([value_min, value_max]) return minmax # 将数据集列重新缩放到 0-1 范围 def normalize_dataset(dataset, minmax): for row in dataset: for i in range(len(row)): row[i] = (row[i] - minmax[i][0]) / (minmax[i][1] - minmax[i][0]) # 加载 pima-indians-diabetes 数据集 filename = 'pima-indians-diabetes.csv' dataset = load_csv(filename) print('Loaded data file {0} with {1} rows and {2} columns').format(filename, len(dataset), len(dataset[0])) # 将字符串列转换为浮点数 for i in range(len(dataset[0])): str_column_to_float(dataset, i) print(dataset[0]) # 计算每列的最小值和最大值 minmax = dataset_minmax(dataset) # 归一化列 normalize_dataset(dataset, minmax) print(dataset[0]) |
运行示例会产生以下输出。
数据集的第一条记录在归一化前后分别打印,展示了缩放的效果。
1 2 3 |
已加载数据文件 pima-indians-diabetes.csv,共 768 行 9 列 [6.0, 148.0, 72.0, 35.0, 0.0, 33.6, 0.627, 50.0, 1.0] [0.35294117647058826, 0.7437185929648241, 0.5901639344262295, 0.35353535353535354, 0.0, 0.5007451564828614, 0.23441502988898377, 0.48333333333333334, 1.0] |
2. 数据标准化
标准化是一种重新缩放技术,指的是将数据分布的中心置于值0,并将标准差置于值1。
均值和标准差共同用于概括正态分布,也称为高斯分布或钟形曲线。
它要求在缩放之前,已知每列值的均值和标准差。与上述归一化一样,我们可以从训练数据中估计这些值,或者利用领域知识来指定它们的值。
让我们首先创建函数,从数据集中估计每列的均值和标准差统计量。
均值描述了一组数字的中间或中心趋势。列的均值计算为该列所有值的总和除以值的总数。
1 |
mean = sum(values) / total_values |
下面名为 **column_means()** 的函数计算数据集中每列的平均值。
1 2 3 4 5 6 7 |
# 计算列均值 def column_means(dataset): means = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] means[i] = sum(col_values) / float(len(dataset)) return means |
标准差描述了值与均值的平均离散程度。它可以计算为每个值与均值之间平方差之和除以值数量减1的平方根。
1 |
standard deviation = sqrt( (value_i - mean)^2 / (total_values-1)) |
下面名为 **column_stdevs()** 的函数计算数据集中每列值的标准差,并假设均值已经计算出来。
1 2 3 4 5 6 7 8 |
# 计算列标准差 def column_stdevs(dataset, means): stdevs = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): variance = [pow(row[i]-means[i], 2) for row in dataset] stdevs[i] = sum(variance) stdevs = [sqrt(x/(float(len(dataset)-1))) for x in stdevs] return stdevs |
同样,我们可以构造一个小型数据集来演示从数据集中估计均值和标准差。
1 2 3 4 |
x1 x2 50 30 20 90 30 50 |
使用Excel电子表格,我们可以估计每列的均值和标准差,如下所示:
1 2 3 |
x1 x2 平均值 33.3 56.6 标准差 15.27 30.55 |
使用构造的数据集,我们可以估计汇总统计量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
from math import sqrt # 计算列均值 def column_means(dataset): means = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] means[i] = sum(col_values) / float(len(dataset)) return means # 计算列标准差 def column_stdevs(dataset, means): stdevs = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): variance = [pow(row[i]-means[i], 2) for row in dataset] stdevs[i] = sum(variance) stdevs = [sqrt(x/(float(len(dataset)-1))) for x in stdevs] return stdevs # 标准化数据集 dataset = [[50, 30], [20, 90], [30, 50]] print(dataset) # 估计均值和标准差 means = column_means(dataset) stdevs = column_stdevs(dataset, means) print(means) print(stdevs) |
执行该示例会提供以下输出,与电子表格中计算的数字相符。
1 2 3 |
[[50, 30], [20, 90], [30, 50]] [33.333333333333336, 56.666666666666664] [15.275252316519467, 30.550504633038933] |
一旦计算出汇总统计量,我们就可以轻松地标准化每列中的值。
标准化给定值的计算方法如下:
1 |
standardized_value = (value - mean) / stdev |
下面是一个名为 **standardize_dataset()** 的函数,实现了这个方程
1 2 3 4 5 |
# 标准化数据集 def standardize_dataset(dataset, means, stdevs): for row in dataset: for i in range(len(row)): row[i] = (row[i] - means[i]) / stdevs[i] |
结合计算均值和标准差统计量的函数,我们可以标准化我们构造的数据集。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
from math import sqrt # 计算列均值 def column_means(dataset): means = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] means[i] = sum(col_values) / float(len(dataset)) return means # 计算列标准差 def column_stdevs(dataset, means): stdevs = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): variance = [pow(row[i]-means[i], 2) for row in dataset] stdevs[i] = sum(variance) stdevs = [sqrt(x/(float(len(dataset)-1))) for x in stdevs] return stdevs # 标准化数据集 def standardize_dataset(dataset, means, stdevs): for row in dataset: for i in range(len(row)): row[i] = (row[i] - means[i]) / stdevs[i] # 标准化数据集 dataset = [[50, 30], [20, 90], [30, 50]] print(dataset) # 估计均值和标准差 means = column_means(dataset) stdevs = column_stdevs(dataset, means) print(means) print(stdevs) # 标准化数据集 standardize_dataset(dataset, means, stdevs) print(dataset) |
执行此示例会产生以下输出,显示了构造数据集的标准化值。
1 2 3 4 |
[[50, 30], [20, 90], [30, 50]] [33.333333333333336, 56.666666666666664] [15.275252316519467, 30.550504633038933] [[1.0910894511799618, -0.8728715609439694], [-0.8728715609439697, 1.091089451179962], [-0.21821789023599253, -0.2182178902359923]] |
再次,我们可以演示机器学习数据集的标准化。
下面的例子演示了如何加载和标准化Pima Indians糖尿病数据集,假设它在当前工作目录中,就像之前的归一化例子一样。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
from csv import reader from math import sqrt # 加载 CSV 文件 def load_csv(filename): file = open(filename, "rb") lines = reader(file) dataset = list(lines) return dataset # 将字符串列转换为浮点数 def str_column_to_float(dataset, column): for row in dataset: row[column] = float(row[column].strip()) # 计算列均值 def column_means(dataset): means = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): col_values = [row[i] for row in dataset] means[i] = sum(col_values) / float(len(dataset)) return means # 计算列标准差 def column_stdevs(dataset, means): stdevs = [0 for i in range(len(dataset[0]))] for i in range(len(dataset[0])): variance = [pow(row[i]-means[i], 2) for row in dataset] stdevs[i] = sum(variance) stdevs = [sqrt(x/(float(len(dataset)-1))) for x in stdevs] return stdevs # 标准化数据集 def standardize_dataset(dataset, means, stdevs): for row in dataset: for i in range(len(row)): row[i] = (row[i] - means[i]) / stdevs[i] # 加载 pima-indians-diabetes 数据集 filename = 'pima-indians-diabetes.csv' dataset = load_csv(filename) print('Loaded data file {0} with {1} rows and {2} columns').format(filename, len(dataset), len(dataset[0])) # 将字符串列转换为浮点数 for i in range(len(dataset[0])): str_column_to_float(dataset, i) print(dataset[0]) # 估计均值和标准差 means = column_means(dataset) stdevs = column_stdevs(dataset, means) # 标准化数据集 standardize_dataset(dataset, means, stdevs) print(dataset[0]) |
运行示例会打印数据集的第一行,首先以原始加载格式打印,然后是标准化后的格式,这使我们能够看到差异以进行比较。
1 2 3 |
已加载数据文件 pima-indians-diabetes.csv,共 768 行 9 列 [6.0, 148.0, 72.0, 35.0, 0.0, 33.6, 0.627, 50.0, 1.0] [0.6395304921176576, 0.8477713205896718, 0.14954329852954296, 0.9066790623472505, -0.692439324724129, 0.2038799072674717, 0.468186870229798, 1.4250667195933604, 1.3650063669598067] |
3. 何时进行归一化和标准化
标准化是一种假设您的数据符合正态分布的缩放技术。
如果给定的数据属性是正态的或接近正态的,这可能是要使用的缩放方法。
记录标准化过程中使用的汇总统计数据是一种良好的做法,这样您就可以在将来对要与模型一起使用的数据进行标准化时应用它们。
归一化是一种不假设任何特定分布的缩放技术。
如果您的数据不是正态分布的,请考虑在应用机器学习算法之前对其进行归一化。
记录归一化过程中使用的每列的最小值和最大值是一种良好的做法,同样,以防将来需要归一化新数据以与模型一起使用。
扩展
还有许多其他数据转换可以应用。
数据转换的目的是为了更好地将您问题中的数据结构暴露给学习算法。
可能不清楚需要预先进行哪些转换。结合试错法和探索性数据分析(图表和统计数据)可以帮助找出可能有效的方法。
以下是您可能希望研究和实现的一些额外转换:
- 允许可配置范围的归一化,例如 -1 到 1 等。
- 允许可配置扩散的标准化,例如距离平均值 1、2 或更多标准差。
- 指数变换,如对数、平方根和指数。
- 幂变换,如 Box-Cox 变换,用于修正正态分布数据中的偏度。
回顾
在本教程中,您学习了如何从头开始重新缩放机器学习数据。
具体来说,你学到了:
- 如何从头开始归一化数据。
- 如何从头开始标准化数据。
- 何时对数据使用归一化或标准化。
您对数据缩放或此帖子有任何疑问吗?
在下面的评论中提出您的问题,我将尽力回答。
我希望将按月交易总额用作特征。我计划使用Xgboost,从我读到的内容来看,最好使用二进制变量。归一化是否有效,或者我应该将总额分箱到十分位数,以便将其转换为二进制?
嗨,丹尼尔,
我想你可以尝试一个模型,其中交易总额保持不变,也可以使用分箱值,还可以使用二进制值(例如高于某个阈值)。
我不知道你正在处理的问题,但通常情况下,尝试几种不同的问题表述方式并查看哪种效果最好是个好做法。
感谢您的时间。
您好,这是一个非常好的教程。但我仍然有一个疑问。如果我想使用scikit函数来归一化我的数据,然后打印它以验证它是否真的有效,我应该如何操作?
我尝试了一些类似的操作
scaler = MinMaxScaler(feature_range=(0, 1))
数据集 = scaler.fit_transform(数据集)
plt.plot(数据集)
plt.show()
但它不起作用。它根本不影响数据
代码看起来不错。
尝试打印转换后的数组而不是绘制它。
嗨,Jason,
如果我有两个模型(分类和回归),并且每个模型都输出对数损失和绝对误差。
那么,如果我想结合输出误差,是否必须先对两个误差进行归一化,然后再进行加法?如果是,上面的归一化公式是否可以用于对两个误差进行归一化?
回归问题上的对数损失没有意义。
抱歉,我之前的帖子可能会让你感到困惑。实际上,在我的情况下,分类问题输出对数损失误差函数,而回归问题输出绝对误差函数(MSE、MAE、R2等)。
所以我想把这两种误差(来自分类和回归问题)加起来,并且需要先对它们进行归一化。scikit-learn 在对数损失函数中提供了归一化参数,它将返回每个样本的平均损失。但是回归损失函数,如RMSE,没有归一化参数,其平均损失输出需要手动归一化。
您能建议我如何归一化回归问题中损失误差的最终输出吗?
非常感谢您对此事的意见。
你为什么要规范化错误?
最好解释误差分数而不是对其进行转换。例如,RMSE直接以输出变量的单位表示。
缩放公式为 -1 -> 1 是什么?
好问题,你可以使用
这是一个演示该函数的代码片段
你好 Jason,
在您的文章中,您建议当数据呈正态分布时使用标准化,当数据不呈正态分布时使用归一化。您知道为什么会这样吗?我想我曾在其他地方看到标准化被描述为 Z 分数,但我不明白为什么不建议将计算出的非正态分布数据值作为机器学习算法的输入。即使数据是非正态分布的,机器学习算法是否仍然可以从这些输入中获取价值?
标准化将数据转换为零均值和单位标准差。对于非高斯数据,这种转换没有意义——数据不会居中,并且非高斯数据没有标准差。
早上好,Jason,
在数据转换的背景下,我想知道如何快速检查哪种方法适用于哪种机器学习算法。有些算法要求输入范围在[0.0,1.0],有些是[-1.0, 1.0]或标准化。当我阅读sklearn算法的描述时:https://scikit-learn.cn/stable/supervised_learning.html#supervised-learning,我发现缺少这些信息。
您推荐有关此主题的任何汇编资源吗?或者,每当遇到新的数据/问题时,都应该在研究文章中查找数据转换方法或进行抽样检查?
此致!
更糟糕的是,有时您会通过违反规则/假设/期望而获得最佳结果。
最好的方法是针对某种算法测试不同的转换。这是我的建议。
这真是有趣的反馈。
谢谢分享!
很高兴能帮到你。
这是我推荐的“抽样检查”方法的基础。
https://machinelearning.org.cn/start-here/#process
你好 Jason,如果我想获得真实值,如何反归一化?
或许可以使用 sklearn 缩放对象,然后使用逆变换。
这里有一个例子
https://machinelearning.org.cn/machine-learning-data-transforms-for-time-series-forecasting/
您好,Jason,感谢您提供如此出色的教程。我从中学到了很多。
目前我正在使用LSTM网络进行能源消耗的时间序列预测。我遇到了一个问题。当我开始训练时,有时会得到正确的结果,并且可以看到我的损失逐个epoch降低。但当我再次运行相同的模型时,有时训练一开始就会得到nan损失,或者有时nan损失会在代码运行几个epoch后出现。奇怪的是,即使我的损失开始收敛到一个非常低的值之后,也曾突然出现nan损失。请给我一些建议,我该怎么做。
谢谢。
谢谢!
是的,这是意料之中的。请看这个
https://machinelearning.org.cn/faq/single-faq/why-do-i-get-different-results-each-time-i-run-the-code
谢谢。请建议我应该怎么做才能在训练我的 LSTM 模型时摆脱 nan 损失。
也许试试 ReLU?
也许在拟合模型之前尝试缩放数据?
你好 Jason,
我想向您请教一个关于标准化的问题。您可以在下面的链接中看到这个问题:我们是否有必要使用训练集的均值和标准差来缩放我们的验证/测试集?
https://stats.stackexchange.com/questions/202287/why-standardization-of-the-testing-set-has-to-be-performed-with-the-mean-and-sd
在此先感谢您
抱歉,我没有能力为您阅读/回答链接。也许您可以用一两句话总结您的问题?
嗨,Jason,
我见过一个学生评论说我们必须先进行训练测试分割,然后对 X_train 和 X_test 应用归一化/标准化。这是正确的做法,而不是对整个数据集应用转换吗?
期待您的回复。谢谢。
是的,数据准备系数是在训练集上计算的,然后将转换应用于训练集、测试集和任何其他数据集(验证集、新数据等)。
原因是避免数据泄漏,这会导致模型性能估计出现偏差。
零均值归一化和 Z-score 归一化之间有区别吗?
可能没有
https://en.wikipedia.org/wiki/Standard_score
你好 Jason,
感谢您的教程。我有一个关于包含近40个特征的数据集的缩放方法的问题。有些特征的范围从0到1e+10,有些从0到10甚至更小,并且具有不同的分布。每个特征的最小值和最大值通过研究问题领域直接指定(而不是使用训练数据的最小值和最大值)。因此,这个缩放器专门用于这个问题。
问题是,我是否可以使用不同的缩放函数处理某些特征,而使用另一个缩放函数处理其余特征?例如,对范围极大的特征使用幂变换器,对其他特征使用MinMaxScaler?
如果我只使用 MinMaxScaler 进行范围 (0,1) 缩放,我认为范围较大的特征会缩放到非常接近零的小值。
我想知道是否有某种有效的方法可以进行缩放,以便所有特征都能得到适当的缩放。
感谢您的宝贵时间
MinMaxScaler 类允许您指定首选范围,但它适用于所有输入变量。
如果需要其他功能,您可能需要编写一些自定义代码。
你好 Jason,
感谢这个教程。我加载数据时遇到了这个错误,我该如何解决呢?
错误:迭代器应该返回字符串,而不是字节(您是否以文本模式打开文件?)