Skip to content

使用 Python 进行 AI – 数据准备

机器学习算法,无论是监督学习还是无监督学习,都需要将数据组织成特定的结构化格式才能有效学习。从现实世界收集的原始数据通常是杂乱、不一致且不适合直接输入这些算法。数据准备(Data preparation),也称为数据预处理(Data preprocessing),是将原始数据清理、转换和组织成高质量数据集,以供机器学习使用的关键过程。

本章重点介绍使用 Python 进行机器学习算法所需的基本数据准备技术。

现实世界的数据经常存在诸如缺失值(missing values)、噪声(noise)、不一致性(inconsistencies)和无关信息(irrelevant information)等问题。数据预处理解决这些问题,以提高数据质量,进而提升机器学习模型的性能、效率和可靠性。“垃圾进,垃圾出”(Garbage in, garbage out)是机器学习领域的一句常见俗语,强调了优质数据的必要性。

以下是在 Python 中预处理数据的典型工作流程:

步骤 1:导入必要的包

Python 中用于数据操作和预处理的主要库是 NumPy 和 Scikit-learn。

import numpy as np
from sklearn import preprocessing # For most preprocessing utilities
from sklearn.impute import SimpleImputer # For handling missing values
  • NumPy:用于数值计算的基础包,提供对多维数组和矩阵的支持。
  • Scikit-learn (sklearn):一个全面的机器学习库,提供广泛的数据预处理、模型构建和评估工具。sklearn.preprocessing 模块包含许多常见的实用函数和转换器类。

步骤 2:加载并检查你的数据

加载你的数据集(例如,使用 Pandas 从 CSV 文件加载,或加载 NumPy 数组)。然后,检查其结构、数据类型,并识别潜在问题,如缺失值或异常值(outliers)。

为了演示,我们定义一些样本数据:

# Sample data with some potential issues (e.g., varying scales)
raw_feature_data = np.array([
[2.1, -1.9, 5.5],
[-1.5, 2.4, 3.5],
[0.5, -7.9, 5.6],
[5.9, 2.3, -5.8]
])
# Sample data with missing values (NaN)
data_with_missing = np.array([
[1.0, 2.0, np.nan],
[4.0, np.nan, 6.0],
[7.0, 8.0, 9.0],
[np.nan, 11.0, 12.0]
])
# Sample categorical labels
categorical_labels = ['red', 'black', 'red', 'green', 'black', 'yellow', 'white']

步骤 3:应用预处理技术

根据数据检查结果,应用适当的预处理技术。这些技术在以下章节中详细介绍。

缺失数据是一个常见问题。处理策略包括:

  • 删除:删除含有缺失值的行(listwise deletion)或列。这方法简单,但如果缺失值很多,可能导致丢失宝贵数据。
  • 填充(Imputation):用替代值填充缺失值。常用方法包括使用相应列的均值(mean)、中位数(median)(用于数值数据)或众数(mode)(用于类别数据)。

可以使用 Scikit-learn 的 SimpleImputer 来实现这一点:

# Impute missing values using the mean strategy
mean_imputer = SimpleImputer(missing_values=np.nan, strategy='mean')
imputed_data_mean = mean_imputer.fit_transform(data_with_missing)
print("Data imputed with mean:\n", imputed_data_mean)
# Impute missing values using the median strategy
median_imputer = SimpleImputer(missing_values=np.nan, strategy='median')
imputed_data_median = median_imputer.fit_transform(data_with_missing)
print("\nData imputed with median:\n", imputed_data_median)

均值填充的输出(实际值取决于非缺失数据):

Data imputed with mean:
[[ 1. 2. 9. ]
[ 4. 7. 6. ]
[ 7. 8. 9. ]
[ 4. 11. 12. ]]
(Column 0 NaN replaced by (1+4+7)/3 = 4. Column 1 NaN by (2+8+11)/3 = 7. Column 2 NaN by (6+9+12)/3 = 9.)

许多机器学习算法在输入特征处于相似尺度时表现更好。这可以防止值较大的特征在学习过程中占据主导地位。

将数据转换为均值为 0、标准差为 1 的形式。当算法假定数据以零为中心时(例如,带有正则化的 SVM 或逻辑回归),这非常有用。

print("\nOriginal data (raw_feature_data):\n", raw_feature_data)
print("Original Mean = ", raw_feature_data.mean(axis=0))
print("Original Std deviation = ", raw_feature_data.std(axis=0))
standardized_data = preprocessing.scale(raw_feature_data)
print("\nStandardized data:\n", standardized_data)
print("Standardized Mean = ", standardized_data.mean(axis=0))
print("Standardized Std deviation = ", standardized_data.std(axis=0))

raw_feature_data 的输出:

Original data (raw_feature_data):
[[ 2.1 -1.9 5.5]
[-1.5 2.4 3.5]
[ 0.5 -7.9 5.6]
[ 5.9 2.3 -5.8]]
Original Mean = [ 1.75 -1.275 2.2 ]
Original Std deviation = [2.71431391 4.20022321 4.69414529]
Standardized data:
[[ 0.12895966 0.60115576 0.70299017]
[-1.19731725 0.87494731 0.27693006]
[-0.46055594 -1.57727448 0.72427818]
[ 1.52891352 0.09911408 -1.70419841]]
Standardized Mean = [1.11022302e-16 0.00000000e+00 0.00000000e+00]
Standardized Std deviation = [1. 1. 1.]

将数据缩放到固定范围,通常是 0 到 1。当算法要求数据在有界区间内,或者数据分布不是高斯分布时,这非常有用。

minmax_scaler = preprocessing.MinMaxScaler(feature_range=(0, 1))
data_scaled_minmax = minmax_scaler.fit_transform(raw_feature_data)
print("\nMin-Max scaled data (to [0,1]):\n", data_scaled_minmax)

raw_feature_data 的输出(已更正原始教程示例):

Min-Max scaled data (to [0,1]):
[[0.48648649 0.58252427 0.99122807]
[-0. 1. 0.81578947]
[0.27027027 0. 1. ]
[1. 0.99029126 0. ]]

机器学习算法通常需要数值输入。类别特征(文本标签)必须转换为数字。

为每个类别分配一个唯一的整数。适用于有序类别特征(顺序重要)或目标变量。

# Using 'categorical_labels' defined earlier: ['red', 'black', ...]
label_encoder = preprocessing.LabelEncoder()
label_encoder.fit(categorical_labels)
print("\nOriginal Labels:", categorical_labels)
encoded_values = label_encoder.transform(categorical_labels)
print("Encoded Values:", encoded_values)
# We can also inverse transform to get original labels
decoded_values = label_encoder.inverse_transform(encoded_values)
print("Decoded Labels:", list(decoded_values))

输出:

Original Labels: ['red', 'black', 'red', 'green', 'black', 'yellow', 'white']
Encoded Values: [2 0 2 1 0 4 3]
Decoded Labels: ['red', 'black', 'red', 'green', 'black', 'yellow', 'white']
(Note: The integer mapping depends on the alphabetical order of unique labels found during fit: black=0, green=1, red=2, white=3, yellow=4)

为每个类别创建一个新的二元(0 或 1)列。这可以防止模型假设类别之间存在顺序关系(标签编码可能暗示这一点)。对于名义类别特征首选此方法。

# Reshape categorical_labels for OneHotEncoder (it expects 2D array)
reshaped_labels = np.array(categorical_labels).reshape(-1, 1)
onehot_encoder = preprocessing.OneHotEncoder(sparse_output=False) # sparse_output=False for dense array
onehot_encoded_values = onehot_encoder.fit_transform(reshaped_labels)
print("\nOne-Hot Encoded Values (first 3 rows shown):")
print(onehot_encoded_values[:3])
print("Feature names from OneHotEncoder:", onehot_encoder.get_feature_names_out(['color']))

输出(前 3 行对应于 ‘red’, ‘black’, ‘red’):

One-Hot Encoded Values (first 3 rows shown):
[[0. 0. 1. 0. 0.]
[1. 0. 0. 0. 0.]
[0. 0. 1. 0. 0.]]
Feature names from OneHotEncoder: ['color_black' 'color_green' 'color_red' 'color_white' 'color_yellow']
(Each row has a 1 in the column corresponding to its color.)

Pandas 的 get_dummies() 函数是另一种对 DataFrame 列执行独热编码的便捷方法。

根据阈值将数值特征转换为二元(0 或 1)值。可用于创建存在/不存在特征。

# Using 'raw_feature_data'
binarizer = preprocessing.Binarizer(threshold=0.5)
data_binarized = binarizer.transform(raw_feature_data)
print("\nBinarized data (threshold=0.5):\n", data_binarized)

输出:值 > 0.5 变为 1,其他值变为 0。

Binarized data (threshold=0.5):
[[1. 0. 1.]
[0. 1. 1.]
[0. 0. 1.]
[1. 1. 0.]]

这种类型的归一化按单位范数(长度)对单个样本(行)进行缩放。在文本分类或聚类中,当样本的大小不重要,而只关注其方向时,经常使用这种方法。

L1 归一化(最小绝对偏差):修改值,使每行的绝对值之和为 1。

data_normalized_l1 = preprocessing.normalize(raw_feature_data, norm='l1')
print("\nL1 Normalized data:\n", data_normalized_l1)

输出(每行绝对值之和为 1):

L1 Normalized data:
[[ 0.22105263 -0.2 0.57894737]
[-0.2027027 0.32432432 0.47297297]
[ 0.03571429 -0.56428571 0.4 ]
[ 0.42142857 0.16428571 -0.41428571]]

L2 归一化(最小二乘):修改值,使每行的平方和为 1(每行成为单位向量)。

data_normalized_l2 = preprocessing.normalize(raw_feature_data, norm='l2')
print("\nL2 Normalized data:\n", data_normalized_l2)

输出(每行平方和为 1):

L2 Normalized data:
[[ 0.33946114 -0.30713151 0.88906489]
[-0.33325106 0.53320169 0.7775858 ]
[ 0.05156558 -0.81473612 0.57753446]
[ 0.68706914 0.26784051 -0.6754239 ]]

未标记数据(Unlabeled data)是指没有对应目标或类别信息的样本。示例包括原始文本、图像、音频记录。它通常丰富且易于收集。

另一方面,已标记数据(Labeled data)的每个样本都带有有意义的标签(tag)、标注(label)或类别(class)。例如,标记为“猫”或“狗”的图像,标记为“垃圾邮件”或“非垃圾邮件”的电子邮件。标记数据通常需要人类专业知识,而且可能既耗时又昂贵。

监督学习算法需要已标记数据进行训练。无监督学习算法处理未标记数据以发现模式或结构。半监督学习(Semi-supervised learning)是一种结合少量已标记数据和大量未标记数据来构建更好模型的方法。

有效的数据准备是机器学习项目成功的基石。所选择的技术很大程度上取决于数据的性质和所使用的算法的要求。