虽然在前面我们学习了一些简单的机器学习的算法,但是实际接触过真实项目或者比赛的人都知道,数据才是炼丹的原材料。如果给我们的数据我们没有成功利用好,这是非常难受的。
插句题外话,今天我看见了李沐大神的演讲里说的大模型训练三要素,数据、算法、算力,其以材料、丹方,火候来进行类比,实在是惟妙惟肖。万事万物皆有相通之处。
因此,在这里我沉淀多天,每天学一点写一点,给大家总结一下一些机器学习中最基础的数据处理操作。共勉 : )
2024.8.25 -> 太多了,我用gpt帮大家总结了,绝对不是自己懒
友情提示:有的案例太简单,我就没给代码了,若有疑惑大家可以看sklearn,pandas的官方文档,也可以求助kimi,gpt等AI工具,这里面的代码都很基础,大家随便搜一下文章都能找到相应的代码。因此,在这里我主要给大家讲的是思路,然后提供一些API接口的跳转链接方便大家去看。
Sklearn-API官方接口文档https://scikit-learn.org/stable/api/index.html 其中主要是这一部分:sklearn-数据预处理 中提到的接口。
1. 数据处理
数据归一化
归一化通常将数据缩放到一个特定的范围,例如 [0,1][0, 1][0,1] 或 [−1,1][-1, 1][−1,1]。这对于那些依赖于特征值范围的算法特别重要,例如:
-
K-近邻算法 (K-Nearest Neighbors, KNN):KNN通过计算数据点之间的距离来进行分类或回归,因此特征值的范围会直接影响距离计算。如果特征的尺度不同,有的特征可能会主导距离计算,导致模型性能下降。
-
支持向量机 (Support Vector Machines, SVM) with RBF kernel:RBF核(径向基函数核)同样依赖于特征值的距离计算,归一化可以避免某些特征对距离计算的主导作用。
代码比较简单,就不给案例了,既可以直接通过自己手搓实现,也可以通过sklearn中的MinMaxScalar实现。
数据标准化
标准化将数据的均值调整为 0,标准差调整为 1,通常假设数据符合正态分布。标准化对于许多算法都很有帮助,特别是那些假设数据服从正态分布或对数值范围敏感的算法,包括:
-
线性回归 (Linear Regression):标准化使得梯度下降更快地收敛,因为不同特征的权重更新会更加一致。
-
逻辑回归 (Logistic Regression):与线性回归类似,标准化可以使得梯度下降更加稳定。
-
支持向量机 (Support Vector Machines, SVM) with Linear kernel:标准化有助于使不同特征在计算超平面时对等对待,避免某些特征对决策边界的过度影响。
-
主成分分析 (Principal Component Analysis, PCA):标准化在PCA中尤为重要,因为PCA是通过计算协方差矩阵来降维的,特征值范围不一致会导致一些特征对主成分的贡献过大。
代码也是比较简单,手搓也可以,通过sklearn的StandardScaler也可以实现。
数据缺失值
数据缺失值是数据中最容易被发现的一种情况。用sklearn,pandas都可以解决。
那么对于数据缺失值的处理,我们也有很多种方法。比如说用平均值,0,中位数,众数等来填充。下面我们来总结一下:
1. 删除缺失值:如果缺失值存在的行数较少,我们可以直接删除这几行。毕竟插值只是我们的猜想,秉承着宁缺毋滥的原则,删除有缺失值的数据;同样的,如果行数太多了,我们直接删除特征这一列。
import pandas as pd
# 假设 df 是一个 pandas DataFrame
df_cleaned = df.dropna() # 删除任何包含缺失值的样本
df_cleaned = df.dropna(axis=1, thresh=int(0.5 * len(df))) # 删除缺失值超过50%的特征
2. 插值法:对于数值型数据,我们可以用特征的均值或者中位数来填补缺失值。对于分类的变量,可以用众数(最常见的值)进行填补。
from sklearn.impute import SimpleImputer
import numpy as np
# 使用均值插值
imputer = SimpleImputer(strategy='mean')
df_filled = imputer.fit_transform(df)
# 使用中位数插值
imputer = SimpleImputer(strategy='median')
df_filled = imputer.fit_transform(df)
# 使用众数插值(适用于分类变量)
imputer = SimpleImputer(strategy='most_frequent')
df_filled = imputer.fit_transform(df)
3. 预测插值:用机器学习模型进行预测。很简单,先拿缺失少的特征和标签一块来反向预测特征缺失多的标签,再慢慢重复 -> 但是这样肯定会过拟合,因为把标签算进去了。
from sklearn.ensemble import RandomForestRegressor
# 使用随机森林回归填补缺失值
features_with_missing = ['feature1', 'feature2']
df_filled = df.copy()
for feature in features_with_missing:
# 分为缺失部分和非缺失部分
df_missing = df_filled[df_filled[feature].isna()]
df_non_missing = df_filled[~df_filled[feature].isna()]
model = RandomForestRegressor()
model.fit(df_non_missing.drop(columns=[feature]), df_non_missing[feature])
df_filled.loc[df_filled[feature].isna(), feature] = model.predict(df_missing.drop(columns=[feature]))
分类型数据
在处理分类数据时,通常需要将分类型数据转换为数值数据,以便机器学习算法能够使用。常见的技术包括编码(Encoding)和哑变量(Dummy Variables)。下面是这两种方法的解释和代码示例。
1. 标签编码(Label Encoding)
标签编码是将分类数据转换为整数的编码方式。每个类别都会被分配一个唯一的整数。
- 优点:简单直接,适合有序分类(Ordinal)的数据。
- 缺点:对于无序分类(Nominal)数据,标签编码可能引入错误的顺序信息,使模型误认为某种顺序存在。
from sklearn.preprocessing import LabelEncoder
# 假设有一个包含分类数据的列 'Category'
categories = ['red', 'green', 'blue', 'green', 'red', 'blue']
# 使用 LabelEncoder 进行标签编码
label_encoder = LabelEncoder()
encoded_labels = label_encoder.fit_transform(categories)
print(f"原始分类数据: {categories}")
print(f"标签编码后: {encoded_labels}")
2. 独热编码(One-Hot Encoding)
独热编码是将每个分类变量转换为二进制的列。每个类别对应一列,若样本属于某个类别,则对应列为1,否则为0。
- 优点:适用于无序分类数据,避免了标签编码引入的顺序问题。
- 缺点:当类别数量较多时,独热编码会导致维度急剧增加,造成"维度灾难"。
import pandas as pd
# 假设有一个 DataFrame 包含分类数据
df = pd.DataFrame({
'Color': ['red', 'green', 'blue', 'green', 'red', 'blue']
})
# 使用 pandas 进行独热编码
one_hot_encoded = pd.get_dummies(df, columns=['Color'])
print("独热编码结果:")
print(one_hot_encoded)
3. 哑变量陷阱与解决方法
在独热编码中,如果对于n个类别创建了n个二进制变量,那么这些变量是线性相关的(即它们的总和为1),这会导致哑变量陷阱(Dummy Variable Trap)。哑变量陷阱可能导致多重共线性问题,使得回归模型无法正常运行。
- 解决方法:去掉一个哑变量列(通常是默认类别)以避免共线性。
# 使用 pandas 的 drop_first 参数去掉第一列,避免哑变量陷阱
one_hot_encoded_no_trap = pd.get_dummies(df, columns=['Color'], drop_first=True)
print("避免哑变量陷阱的独热编码结果:")
print(one_hot_encoded_no_trap)
4. 目标编码(Target Encoding)
目标编码是一种更高级的编码方式,它将每个类别映射为其对应的目标变量的平均值。这种方法在处理高基数分类变量时特别有用。
- 优点:能有效减少维度,保留类别对目标变量的影响。
- 缺点:容易导致过拟合,特别是在数据量较小时。
import pandas as pd
# 假设有一个 DataFrame 包含分类数据和目标变量
df = pd.DataFrame({
'Category': ['A', 'B', 'A', 'C', 'B', 'C'],
'Target': [10, 20, 10, 30, 20, 30]
})
# 计算每个类别的目标平均值
target_mean = df.groupby('Category')['Target'].mean()
# 用目标均值进行编码
df['Category_encoded'] = df['Category'].map(target_mean)
print("目标编码结果:")
print(df)
连续型数据
二值化(Binarization)
二值化是将连续型数据转换为二进制形式(即 0 和 1),通常用于将数值特征转换为布尔变量或处理分类问题时将数据分离成两个类。二值化可以帮助模型识别重要的特征或处理稀疏数据。
应用场景
- 阈值分割:将大于某个阈值的值设为 1,小于等于该阈值的值设为 0。
- 特征选择:将某些特征二值化,以减少数据噪声或突出特定的模式。
from sklearn.preprocessing import Binarizer
# 假设有一个连续型特征的数据
data = [[1.0, -1.5, 3.0], [2.0, 0.0, 1.5], [0.5, 1.0, -0.5]]
# 设置阈值,将大于0的值设为1,小于等于0的值设为0
binarizer = Binarizer(threshold=0.0)
binary_data = binarizer.fit_transform(data)
print("二值化后的数据:")
print(binary_data)
分箱(Binning)
分箱(也称为离散化)是将连续型数据分割成多个离散区间(或“箱子”)。每个区间可以用一个离散的类别表示。这种方法可以降低模型的复杂度,减少噪声,并将数据从数值特征转换为类别特征。
分箱方法
- 等宽分箱:将数据划分为宽度相等的区间。
- 等频分箱:将数据划分为每个区间包含相同数量样本的区间。
- 自定义分箱:根据业务需求或数据分布自定义区间范围。
应用场景
- 特征工程:将连续变量转换为类别变量,帮助模型更好地处理非线性关系。
- 处理异常值:通过分箱,可以减少异常值对模型的影响。
import pandas as pd
# 假设有一个连续型特征的数据
data = pd.DataFrame({'Value': [2, 5, 7, 10, 12, 15, 18, 21, 25]})
# 等宽分箱,将数据分为3个区间
data['EqualWidthBin'] = pd.cut(data['Value'], bins=3, labels=["Low", "Medium", "High"])
# 等频分箱,将数据分为3个区间,每个区间包含相同数量的样本
data['EqualFreqBin'] = pd.qcut(data['Value'], q=3, labels=["Low", "Medium", "High"])
print("分箱结果:")
print(data)
2.特征选择
这里,离开GPT总结哈,我还是想提一嘴,特征选择,还是要结合常识的,你自己必须也要有把握,哪些是有用的特征,哪些是肯定无关的。
过滤法
主要是前三个。
1. 方差选择法(Variance Threshold)
方差选择法通过移除方差低于某一阈值的特征来进行特征选择。方差低的特征通常对目标变量影响较小,可以去除以减少计算复杂度。
在实际数据集中,有些特征可能是常数或接近常数的,这些特征对模型的贡献很小,甚至可能引入噪声。通过设定一个方差阈值,我们可以删除那些变化较小的特征。
from sklearn.feature_selection import VarianceThreshold
# 假设有一个数据集 X
X = [[0, 2, 0, 3],
[0, 1, 4, 3],
[0, 1, 1, 3]]
# 设置方差阈值为0.5,移除方差小于0.5的特征
selector = VarianceThreshold(threshold=0.5)
X_var = selector.fit_transform(X)
print("方差选择法结果:\n", X_var)
2. 卡方检验(Chi-Square Test)
卡方检验主要用于分类问题,衡量分类特征与目标变量之间的独立性。它适用于离散特征,将每个类别的频率与预期频率进行比较,选择那些对目标变量有显著影响的特征。
卡方检验的假设是:如果一个特征与目标变量独立,则它的卡方统计量会较小,反之,如果卡方统计量较大,则表明特征与目标变量关系显著,可以选择这些特征。
from sklearn.feature_selection import SelectKBest, chi2
# 假设有一个数据集 X 和标签 y
X = [[0, 2, 0, 3],
[0, 1, 4, 3],
[0, 1, 1, 3]]
y = [0, 1, 0]
# 使用卡方检验选择排名前2的特征
X_chi2 = SelectKBest(chi2, k=2).fit_transform(X, y)
print("卡方检验结果:\n", X_chi2)
3. 互信息法(Mutual Information)
互信息法通过衡量特征与目标变量之间的互信息来进行特征选择。互信息反映了两个变量之间的依赖性,互信息越大,两个变量之间的关联性越强。
互信息法不仅考虑特征和目标变量之间的线性关系,还考虑它们的非线性关系。通过计算互信息量,可以选择那些与目标变量高度相关的特征。
from sklearn.feature_selection import SelectKBest, mutual_info_classif
# 使用互信息法选择排名前2的特征
X_mi = SelectKBest(mutual_info_classif, k=2).fit_transform(X, y)
print("互信息法结果:\n", X_mi)
4. 递归特征消除(Recursive Feature Elimination, RFE)
递归特征消除通过反复训练模型并消除权重系数最小的特征来进行特征选择。它在每次迭代中从特征集中删除对模型贡献最小的特征,直到剩余的特征数达到预定值。
RFE 通过模型的权重(如线性模型的系数或树模型的特征重要性)来评估特征的重要性。每次迭代后,删除贡献最小的特征,并重新训练模型,直到达到指定数量的特征。
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
# 使用逻辑回归作为基模型
model = LogisticRegression()
# 递归特征消除法,选择排名前2的特征
rfe = RFE(model, n_features_to_select=2)
X_rfe = rfe.fit_transform(X, y)
print("递归特征消除结果:\n", X_rfe)
嵌入法
嵌入法(Embedded Methods)是一种在模型训练过程中自动进行特征选择的方法。与过滤法和包裹法不同,嵌入法将特征选择直接嵌入到模型训练过程中。这类方法不仅考虑了特征与目标变量的关联,还结合了模型的性能和正则化等机制来选择特征。
嵌入法的优势在于它们效率高且能够结合模型的复杂性进行特征选择,适用于大多数机器学习任务。常见的嵌入法包括 L1 正则化(Lasso 回归)、基于树模型的特征选择、以及**梯度提升树(Gradient Boosting Trees)**等。
1. L1 正则化(Lasso 回归)
L1 正则化是一种通过稀疏化模型的权重向量来进行特征选择的方法。Lasso 回归是 L1 正则化的一个具体应用。它在损失函数中加入了 L1 正则化项,使得部分特征的系数收缩到零,从而实现特征选择。
详细解释:
- Lasso(Least Absolute Shrinkage and Selection Operator):Lasso 回归会在模型拟合时将一些不重要的特征的系数缩小为零,从而实现自动的特征选择。
- 作用:它能够减少模型的复杂性,避免过拟合,特别适用于高维数据(即特征数量多于样本数量)的场景。
from sklearn.linear_model import Lasso
# 假设有一个数据集 X 和标签 y
X = [[0, 2, 0, 3],
[0, 1, 4, 3],
[0, 1, 1, 3]]
y = [0, 1, 0]
# 使用Lasso回归进行特征选择
lasso = Lasso(alpha=0.1)
lasso.fit(X, y)
# 选择系数不为0的特征
X_lasso = [list(row[i] for i in range(len(lasso.coef_)) if lasso.coef_[i] != 0) for row in X]
print("Lasso特征选择结果:\n", X_lasso)
2. 基于树模型的特征选择
树模型(如决策树、随机森林、梯度提升树等)能够自然地衡量每个特征的重要性。特征的重要性通常由模型在训练过程中对特征进行分裂时的贡献度(如信息增益或Gini指数)来确定。
详细解释:
- 决策树:树模型通过递归地将数据分割成多个区间,每次分割时选择那个能够最大化分割效果的特征。特征的重要性通过其在树中的出现频率及贡献度来衡量。
- 随机森林:随机森林通过集成多棵决策树,从而可以更为稳健地评估特征的重要性。模型会根据每个特征在各个树中的表现来综合计算特征的重要性。
from sklearn.ensemble import RandomForestClassifier
import numpy as np
# 使用随机森林进行特征选择
forest = RandomForestClassifier(n_estimators=100)
forest.fit(X, y)
# 获取特征的重要性
importances = forest.feature_importances_
# 选择重要性排名前两位的特征
indices = np.argsort(importances)[::-1][:2]
X_forest = np.array(X)[:, indices]
print("随机森林特征选择结果:\n", X_forest)
包装法(Wrapper Methods)
是特征选择中的一种方法,它通过反复地训练模型并评估其性能,选择最佳的特征子集。包装法直接将特征选择过程“包裹”在模型的训练过程中,通过不断调整特征子集,找到对模型效果最优的特征组合。
1. 递归特征消除法(RFE)
递归特征消除法是一种迭代的特征选择方法。它从所有特征开始,每次训练模型后删除对模型贡献最小的特征,直到剩余的特征数量达到预设值。
from sklearn.feature_selection import RFE
from sklearn.linear_model import LogisticRegression
# 假设有一个数据集 X 和标签 y
X = [[0, 2, 0, 3],
[0, 1, 4, 3],
[0, 1, 1, 3]]
y = [0, 1, 0]
# 使用逻辑回归作为基模型
model = LogisticRegression()
# 递归特征消除法,选择排名前2的特征
rfe = RFE(model, n_features_to_select=2)
X_rfe = rfe.fit_transform(X, y)
print("递归特征消除结果:\n", X_rfe)
2. 顺序特征选择法(Sequential Feature Selection, SFS)
顺序特征选择法是一种分步的特征选择方法。它包括前向选择和后向消除两种策略。前向选择从空集开始,逐步添加特征,后向消除则从所有特征开始,逐步移除特征。
详细解释:
- 前向选择:从空特征集开始,每一步增加一个对模型性能提升最大的特征,直到满足预设的特征数量或性能标准。
- 后向消除:从完整的特征集开始,每一步移除一个对模型性能影响最小的特征,直到剩余特征数量达到要求。
- 优点:SFS 方法通过逐步添加或删除特征,可以找到模型性能最优的特征组合。
- 缺点:与 RFE 类似,计算成本较高,尤其是特征数量多时。
from sklearn.feature_selection import SequentialFeatureSelector
from sklearn.linear_model import LogisticRegression
# 使用逻辑回归作为基模型
model = LogisticRegression()
# 顺序特征选择法,选择排名前2的特征
sfs = SequentialFeatureSelector(model, n_features_to_select=2, direction='forward')
X_sfs = sfs.fit_transform(X, y)
print("顺序特征选择结果:\n", X_sfs)