标签编码和独热编码对线性模型和树模型的影响
本人主页:机器学习司猫白
机器学习专栏:机器学习实战
PyTorch入门专栏:PyTorch入门
深度学习实战:深度学习
ok,话不多说,我们进入正题吧
概述
相信大家在建模中经常会用到标签编码和独热编码,这两种不同的编码方式到底会对模型产生什么影响,本期就使用kaggle贴纸销量预测的数据集针对这两种编码方式展开研究。让我们开始探索吧。
研究思路
本期研究,主要是研究线性回归和随机森林模型,一个是线性模型的代表,另一个是树模型的代表。针对线性回归和随机森林,标签编码和独热编码到底会对这两种模型产生什么影响,让我们接下来继续探索。
探索之路一
我们首先要了解本次的数据集,第一步当然是打开数据文件,查看基本的数据情况。
import pandas as pd
train_df = pd.read_csv('train.csv')
train_df.info()
输出:
<class 'pandas.core.frame.DataFrame'> RangeIndex: 230130 entries, 0 to 230129 Data columns (total 6 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 id 230130 non-null int64 1 date 230130 non-null object 2 country 230130 non-null object 3 store 230130 non-null object 4 product 230130 non-null object 5 num_sold 221259 non-null float64 dtypes: float64(1), int64(1), object(4) memory usage: 10.5+ MB
可以看到,数据中有4列是分类变量,数据集相对完整,只有我们的目标变量num_sold有一个缺失值。
探索之路二
接下来,我们进行数据的处理。
train_df1 = train_df.dropna()
# 将 'date' 列转换为 datetime 类型
train_df1['date'] = pd.to_datetime(train_df1['date'])
# 提取年、月、日
train_df1['year'] = train_df1['date'].dt.year
train_df1['month'] = train_df1['date'].dt.month
train_df1['day'] = train_df1['date'].dt.day
这里提取出时间数据中的一些特征,删除掉了缺失值。
探索之路三
到这里就算进入正题了,首先我们先查看分类变量的值。
train_df2 = train_df1.drop(columns=['id','date'])
# 遍历所有object类型的字段,查看这些字段的unique()值
for column in train_df2.select_dtypes(include=['object']).columns:
unique_values = train_df2[column].unique()
print(f"Unique values in '{column}': {unique_values}")
输出:
Unique values in 'country': ['Canada' 'Finland' 'Italy' 'Kenya' 'Norway' 'Singapore'] Unique values in 'store': ['Discount Stickers' 'Stickers for Less' 'Premium Sticker Mart'] Unique values in 'product': ['Kaggle' 'Kaggle Tiers' 'Kerneler' 'Kerneler Dark Mode' 'Holographic Goose']
可以看到,'country'的值有6种,'store'的值有3种,'product'的值有5种。
train = train_df2.drop(columns=['num_sold'])
label = train_df2['num_sold']
train1= train.copy()
train2= train.copy()
这里就是为了编码的两种方式做准备,选好特征和目标变量,并且复制两次数据集,train1代表标签编码,train2进行独热编码,这样分开进行,不会导致代码错乱。
探索之路四---标签编码
接下来,我们可以开始标签编码了。
from sklearn.preprocessing import LabelEncoder
# 创建 LabelEncoder 对象
le = LabelEncoder()
# 进行标签编码
for col in train1.select_dtypes(include=['object']).columns:
train1[col] = le.fit_transform(train[col])
train1
输出:
country store product year month day 1 0 0 1 2010 1 1 2 0 0 2 2010 1 1 3 0 0 3 2010 1 1 4 0 0 4 2010 1 1 5 0 2 0 2010 1 1 ... ... ... ... ... ... ... 230125 5 1 0 2016 12 31 230126 5 1 1 2016 12 31 230127 5 1 2 2016 12 31 230128 5 1 3 2016 12 31 230129 5 1 4 2016 12 31 221259 rows × 6 columns
以上就是标签编码的结果,会给每一列中的值赋予一个数值,这样就将其变为数值类型。
编码以后,接下来准备进行标准化,统一将数据标准化,可以加快收敛,这里不多赘述,我们的目的是比较两种编码方式。
from sklearn.preprocessing import StandardScaler
# 选择数值型数据列进行标准化
numeric_columns = train1.columns
# 创建 StandardScaler 对象
scaler = StandardScaler()
# 对选定的数值型列进行标准化
train1[numeric_columns] = scaler.fit_transform(train1[numeric_columns])
# 展示标准化后的数据
train1
输出:
country
store product year month day 1 -1.488282 -1.246966 -0.780058 -1.507928 -1.596295 -1.673535 2 -1.488282 -1.246966 -0.057314 -1.507928 -1.596295 -1.673535 3 -1.488282 -1.246966 0.665430 -1.507928 -1.596295 -1.673535 4 -1.488282 -1.246966 1.388174 -1.507928 -1.596295 -1.673535
接下来,我们分别构建线性回归模型和随机森林模型,并使用rmse作为评估指标,方便进行比较。
import numpy as np import pandas as pd from sklearn.linear_model import LinearRegression from sklearn.metrics import mean_squared_error from math import sqrt from sklearn.model_selection import train_test_split # 划分数据集:80% 用于训练,20% 用于测试 X_train, X_test, y_train, y_test = train_test_split(train1, label, test_size=0.2, random_state=42) # 创建线性回归模型 model = LinearRegression() # 在训练集上训练模型 model.fit(X_train, y_train) # 在训练集上进行预测 train_predictions = model.predict(X_train) # 在测试集上进行预测 test_predictions = model.predict(X_test) # 计算训练集和测试集的 RMSE train_rmse = sqrt(mean_squared_error(y_train, train_predictions)) test_rmse = sqrt(mean_squared_error(y_test, test_predictions)) # 输出训练集和测试集的 RMSE print(f"RMSE on Training Data: {train_rmse}") print(f"RMSE on Test Data: {test_rmse}") # 获取线性回归模型的系数和截距 coefficients = model.coef_ intercept = model.intercept_ # 输出线性回归的表达式 expression = "y = {:.4f}".format(intercept) for i, coef in enumerate(coefficients): expression += " + ({:.4f}) * X{}".format(coef, i+1) print("Linear Regression Expression: ", expression)
输出:
RMSE on Training Data: 662.9927049727288
RMSE on Test Data: 663.8807472977215
Linear Regression Expression: y = 751.4878 + (90.5948) * X1 + (166.1090) * X2 + (9.8588) * X3 + (-29.2590) * X4 + (-4.3219) * X5 + (0.1663) * X6
可以发现模型并没有过度拟合。接下来我们来看随机森林的表现如何。
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from math import sqrt
from sklearn.model_selection import train_test_split
# 划分数据集:80% 用于训练,20% 用于测试
X_train, X_test, y_train, y_test = train_test_split(train1, label, test_size=0.2, random_state=42)
# 创建随机森林回归模型
model = RandomForestRegressor(random_state=42)
# 在训练集上训练模型
model.fit(X_train, y_train)
# 在训练集上进行预测
train_predictions = model.predict(X_train)
# 在测试集上进行预测
test_predictions = model.predict(X_test)
# 计算训练集和测试集的 RMSE
train_rmse = sqrt(mean_squared_error(y_train, train_predictions))
test_rmse = sqrt(mean_squared_error(y_test, test_predictions))
# 输出训练集和测试集的 RMSE
print(f"RMSE on Training Data: {train_rmse}")
print(f"RMSE on Test Data: {test_rmse}")
# 获取随机森林模型的特征重要性
feature_importances = model.feature_importances_
print("Feature importances: ", feature_importances)
输出:RMSE on Training Data: 41.03197019952277 RMSE on Test Data: 112.45716972827415 Feature importances: [0.37879498 0.17947386 0.3833087 0.02487979 0.01788654 0.01565614]
可以看到,随机森林的效果要更好,RMSE更小。但是随机森林有过拟合的可能性,验证的误差大于训练误差,这是因为树模型较为复杂,不像线性回归那样的简单模型,它能够捕捉更加复杂的数据关系,因此复杂的树模型更容易过拟合。
探索之路五---独热编码
for col in train2.select_dtypes(include=['object']).columns:
# 对每个 object 类型的列进行 One-Hot 编码
dummies = pd.get_dummies(train2[col], drop_first=False)
# 将原列删除,然后合并 One-Hot 编码后的列
train2 = pd.concat([train2.drop(col, axis=1), dummies], axis=1)
# 将布尔类型列转换为整数类型(True -> 1, False -> 0)
train2[train2.select_dtypes(include=['bool']).columns] = train2.select_dtypes(include=['bool']).astype(int)
# 展示结果
train2.head()
输出:
from sklearn.preprocessing import StandardScaler
# 选择数值型数据列进行标准化
numeric_columns = train2.select_dtypes(include=['number']).columns
# 创建 StandardScaler 对象
scaler = StandardScaler()
# 对选定的数值型列进行标准化
train2[numeric_columns] = scaler.fit_transform(train2[numeric_columns])
# 展示标准化后的数据
train2
为了保持一致,也进行了标准化。接下来,观察两种模型在独热编码的表现吧。
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error
from math import sqrt
from sklearn.model_selection import train_test_split
# 划分数据集:80% 用于训练,20% 用于测试
X_train, X_test, y_train, y_test = train_test_split(train2, label, test_size=0.2, random_state=42)
# 创建线性回归模型
model = LinearRegression()
# 在训练集上训练模型
model.fit(X_train, y_train)
# 在训练集上进行预测
train_predictions = model.predict(X_train)
# 在测试集上进行预测
test_predictions = model.predict(X_test)
# 计算训练集和测试集的 RMSE
train_rmse = sqrt(mean_squared_error(y_train, train_predictions))
test_rmse = sqrt(mean_squared_error(y_test, test_predictions))
# 输出训练集和测试集的 RMSE
print(f"RMSE on Training Data: {train_rmse}")
print(f"RMSE on Test Data: {test_rmse}")
# 获取线性回归模型的系数和截距
coefficients = model.coef_
intercept = model.intercept_
# 输出线性回归的表达式
expression = "y = {:.4f}".format(intercept)
for i, coef in enumerate(coefficients):
expression += " + ({:.4f}) * X{}".format(coef, i+1)
print("Linear Regression Expression: ", expression)
RMSE on Training Data: 315.69921098641686 RMSE on Test Data: 316.6191199924752 Linear Regression Expression: y = 752.5128 + (-19.4292) * X1 + (-3.2278) * X2 + (0.4362) * X3 + (8.2826) * X4 + (11.2144) * X5 + (-66.6151) * X6 + (-288.2553) * X7 + (266.4729) * X8 + (54.7410) * X9 + (-165.4185) * X10 + (118.6142) * X11 + (44.7183) * X12 + (-244.7291) * X13 + (205.6126) * X14 + (118.8688) * X15 + (-68.8753) * X16 + (-30.1040) * X17
import numpy as np
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_squared_error
from math import sqrt
from sklearn.model_selection import train_test_split
# 划分数据集:80% 用于训练,20% 用于测试
X_train, X_test, y_train, y_test = train_test_split(train2, label, test_size=0.2, random_state=42)
# 创建随机森林回归模型
model = RandomForestRegressor(random_state=42)
# 在训练集上训练模型
model.fit(X_train, y_train)
# 在训练集上进行预测
train_predictions = model.predict(X_train)
# 在测试集上进行预测
test_predictions = model.predict(X_test)
# 计算训练集和测试集的 RMSE
train_rmse = sqrt(mean_squared_error(y_train, train_predictions))
test_rmse = sqrt(mean_squared_error(y_test, test_predictions))
# 输出训练集和测试集的 RMSE
print(f"RMSE on Training Data: {train_rmse}")
print(f"RMSE on Test Data: {test_rmse}")
# 获取随机森林模型的特征重要性
feature_importances = model.feature_importances_
print("Feature importances: ", feature_importances)
RMSE on Training Data: 41.041833191228186 RMSE on Test Data: 112.49009950538111 Feature importances: [0.0248972 0.01786752 0.01565035 0.00022039 0.00024071 0.02330899 0.20195292 0.16050104 0.00393603 0.17114463 0.00681259 0.00653821 0.17800782 0.11407922 0.07196715 0.0013827 0.00149252]
可以看到,随机森林模型不论是标签编码还是独热编码,差距都不大,线性回归模型,独热编码的效果更好。
分析报告
线性回归角度
1. 模型性能(RMSE)
标签编码模型:
训练集 RMSE: 662.99
测试集 RMSE: 663.88
独热编码模型:
训练集 RMSE: 315.70
测试集 RMSE: 316.62
分析:独热编码的模型表现优于标签编码的模型,因为其在训练集和测试集的 RMSE 都显著低于标签编码模型。
标签编码的模型具有较高的 RMSE,这表明标签编码引入了某种程度的模型偏差,可能是因为线性回归将分类变量视作有序数值,这可能导致不正确的关系建模。
独热编码通过将每个类别转化为独立的二进制特征,避免了标签编码中的顺序假设,因此在大多数情况下,独热编码能够提供更好的预测性能。
2. 特征表达(回归方程)
标签编码的线性回归方程:
y = 751.4878 + (90.5948) * X1 + (166.1090) * X2 + (9.8588) * X3 + (-29.2590) * X4 + (-4.3219) * X5 + (0.1663) * X6
独热编码的线性回归方程:
y = 752.5128 + (-19.4292) * X1 + (-3.2278) * X2 + (0.4362) * X3 + (8.2826) * X4 + (11.2144) * X5 + (-66.6151) * X6 + (-288.2553) * X7 + (266.4729) * X8 + (54.7410) * X9 + (-165.4185) * X10 + (118.6142) * X11 + (44.7183) * X12 + (-244.7291) * X13 + (205.6126) * X14 + (118.8688) * X15 + (-68.8753) * X16 + (-30.1040) * X17
分析:
标签编码将每个类别转换为一个整数值,这会在模型中引入某种顺序性假设,即它认为这些类别变量之间存在某种数值关系。这在回归方程中反映为各个特征系数之间较大的变化。
独热编码将每个类别的每一个可能值转化为一个独立的二进制特征,消除了类别之间的顺序关系。因此,回归方程中包含了更多的特征(每个类别对应一个二进制特征),且系数较为分散,反映出各个类别特征独立对模型的贡献。
3. 类别变量的处理方式
标签编码:对于分类变量,标签编码将每个类别映射为一个数字值(例如,类别 A 映射为 0,类别 B 映射为 1,类别 C 映射为 2 等)。问题是,标签编码可能会给模型带来误导,因为它假设类别之间存在自然的顺序(例如,1 < 2 < 3),这对许多模型来说并不成立,尤其是在处理无序类别时,可能会导致预测出现偏差。
独热编码:独热编码避免了上述问题,通过为每个类别生成一个新的二进制特征来表示每个类别的存在与否。这种方式消除了类别之间的顺序假设,使得模型能够更加准确地处理无序分类变量。
4. 特征数量
标签编码:最终特征数量较少,只包含了每个类别的一个特征(如 X1, X2, ..., X6)。
独热编码:由于每个类别都变成了一个独立的特征,特征数量大大增加。假设有 N 个类别,那么独热编码将生成 N 个二进制特征(例如,X1, X2, ..., X17)。
分析:
在标签编码的情况下,特征的数量较少,模型更简洁,但由于类别的顺序假设,这种简化可能导致模型无法捕捉到类别变量的真正影响。
独热编码通过引入更多的特征,能够更准确地表示类别变量对预测的影响,虽然特征数量增加了,但一般来说这会提升模型的预测性能。
5. 模型解释性
标签编码的回归方程中,系数的大小和符号表明了每个特征对预测目标的影响。
独热编码的回归方程会包含更多的特征,并且由于对每个类别生成独立的特征,其解释性可能会更加复杂。对于类别特征,独热编码后的回归系数能够清晰地表示每个类别对目标变量的贡献。
6. 适用场景
标签编码:适用于类别之间有自然顺序的情况(例如,教育程度、评级等有顺序的类别变量)。但是,当类别之间没有实际顺序时,标签编码可能会带来偏差,影响模型性能。
独热编码:适用于类别之间没有自然顺序的情况(例如,颜色、品牌等)。它能够消除类别之间的假设顺序,适用于无序的类别变量。
总结:
独热编码在处理无序类别特征时通常会优于标签编码,因为它避免了不正确的顺序假设,并且能更准确地捕捉每个类别对模型的影响。
从 RMSE 的对比来看,独热编码模型的性能明显更好。标签编码可能引入了不必要的偏差,导致模型泛化能力较差。
独热编码生成了更多的特征,这使得模型更复杂,但也更能准确地表示类别变量的影响。因此,虽然标签编码在特征数量上更为简洁,但其效果可能不如独热编码。
最终,如果类别变量是无序的,独热编码通常是更优的选择。
随机森林角度
1. 模型性能比较
标签编码的随机森林模型:
RMSE on Training Data: 41.03
RMSE on Test Data: 112.46
独热编码的随机森林模型:
RMSE on Training Data: 41.04
RMSE on Test Data: 112.49
观察:
从 RMSE(均方根误差)来看,训练数据的RMSE和测试数据的RMSE非常接近,几乎没有变化(分别为41.03 vs 41.04和112.46 vs 112.49)。
这表明无论使用标签编码还是独热编码,模型的训练表现和泛化能力相似。两种编码方式似乎对模型的性能影响较小。
2. 特征重要性比较
标签编码的特征重要性:
0.37879498, 0.17947386, 0.3833087, 0.02487979, 0.01788654, 0.01565614
独热编码的特征重要性:
0.0248972, 0.01786752, 0.01565035, 0.00022039, 0.00024071, 0.02330899, 0.20195292, 0.16050104, 0.00393603, 0.17114463, 0.00681259, 0.00653821, 0.17800782, 0.11407922, 0.07196715, 0.0013827, 0.00149252
观察:
标签编码: 特征重要性较为集中,主要集中在前两个特征上,分别为 0.378 和 0.383,这可能说明某些特征在标签编码后更容易被模型捕捉到。
独热编码: 特征重要性分布较为分散,多个特征的值都接近0,并且有很多小的特征重要性。几个特征的显著性较高(如 0.2019, 0.1605 和 0.1711),这与独热编码的方式有关,因其会产生更多的虚拟变量(即独热编码会产生更多的列),可能会导致模型的决策更加细致,强调了更多的特征。
3. 标签编码 vs 独热编码的对比分析
标签编码: 在标签编码中,类别变量被简单地转换为整数值。对于树模型(如随机森林)而言,这种编码方式并不会引入额外的复杂性或虚拟变量,且能够通过树的分裂自动处理顺序关系(如果类别之间有某种隐含的顺序性)。但是,标签编码可能会使模型错误地认为类别变量之间存在某种连续性或顺序性。
独热编码: 独热编码将类别变量转换为多个二进制特征,每个类别值对应一个二进制特征。在随机森林中,这有助于模型理解每个类别独立地影响预测结果,避免了标签编码可能带来的类别之间的顺序误解。然而,独热编码通常会产生更多的特征,增加模型的复杂度,尤其是在类别数目较多时。
4. 结论
性能: 从RMSE来看,标签编码和独热编码的随机森林模型在训练和测试数据上的表现差异非常小。两种方法的表现几乎一样。
特征重要性: 标签编码会使模型更加关注少数几个特征,而独热编码会通过产生更多特征来分散模型的注意力,可能使得一些特征显得更为重要。独热编码产生了更多特征,这也使得模型的特征重要性分布更为分散。
适用性: 在实际应用中,若类别特征数目较少且不存在明显的类别顺序关系,标签编码通常会更简单且表现类似。而对于类别较多的特征,独热编码能避免不必要的顺序关系假设,并能较好地分离每一个类别。
综合来看,标签编码和独热编码在这次实验中的表现相似,但根据特征数量和类别的性质,独热编码可能会稍有优势,特别是在类别没有顺序性的情况下。
可以看出不论是标签编码还是独热编码,对树模型的影响都不大。