【解决(几乎)任何机器学习问题】:处理分类变量篇(下篇)

2024-02-20 12:52

本文主要是介绍【解决(几乎)任何机器学习问题】:处理分类变量篇(下篇),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

接【解决(几乎)任何机器学习问题】:处理分类变量篇(上篇)http://t.csdnimg.cn/rnzto
这篇文章相当长,您可以添加至收藏夹,以便在后续有空时候悠闲地阅读。
让我们看看填⼊ NaN 值后 ord_4 列的值计数:
In [ X ]: df . ord_4 . fillna ( "NONE" ). value_counts ()
Out [ X ]:
N 39978
P 37890
Y 36657
A 36633
R 33045
U 32897
. .
.
K 21676
I 19805
NONE 17930
D 17284
F 16721
W 8268
Z 5790
S 4595
G 3404
V 3107
J 1950
L 1657
Name : ord_4 , dtype : int64
我们看到,有些数值只出现了⼏千次,有些则出现了近 40000 次。NaN 也经常出现。请注意,我已经从输出中删除了⼀些值。
现在,我们可以定义将⼀个值称为 " 罕⻅( rare "的标准了。⽐⽅说,在这⼀列中,稀有值的要求是计数⼩于 2000。这样看来,J 和 L 就可以被标记为稀有值了。使⽤ pandas,根据计数阈值替换类别⾮常简单。让我们来看看它是如何实现的。
In [ X ]: df . ord_4 = df . ord_4 . fillna ( "NONE" )
In [ X ]: df . loc [
. : df [ "ord_4" ]. value_counts ()[ df [ "ord_4" ]]. values < 2000 ,
. : "ord_4"
. : ] = "RARE"
In [ X ]: df . ord_4 . value_counts ()
Out [ X ]:
N 39978
P 37890
Y 36657
A 36633
R 33045
U 32897
M 32504
.
.
.
B 25212
E 21871
K 21676
I 19805
NONE 17930
D 17284
F 16721 W 8268
Z 5790
S 4595
RARE 3607
G 3404
V 3107
Name : ord_4 , dtype : int64

我们认为,只要某个类别的值⼩于 2000,就将其替换为罕⻅。因此,现在在测试数据时,所有未⻅过的新类别都将被映射为 "RARE",⽽所有缺失值都将被映射为 "NONE"。
这种⽅法还能确保即使有新的类别,模型也能在实际环境中正常⼯作。
现在,我们已经具备了处理任何带有分类变量问题所需的⼀切条件。让我们尝试建⽴第⼀个模型,并逐步提⾼其性能。
在构建任何类型的模型之前,交叉检验⾄关重要。我们已经看到了标签/⽬标分布,知道这是⼀个⽬标偏斜的⼆元分类问题。因此,我们将使⽤ StratifiedKFold 来分割数据。
import pandas as pd
from sklearn import model_selection
if _ name _ = " _ main _ ":df = pd.read_csv(" . /input/cat_train.csv")df["kfold"] = -1df = df.sample(frac=1).reset_index(drop=True)y = df.target.valueskf = model_selection.StratifiedKFold(n_splits=5)for f, (t_, v_) in enumerate(kf.split(X=df, y=y)):df.loc[v_, 'kfold'] = fdf.to_csv(" . /input/cat_train_folds.csv", index=False)

现在我们可以检查新的折叠 csv,查看每个折叠的样本数:  

In [ X ]: import pandas as pd
In [ X ]: df = pd . read_csv ( " . /input/cat_train_folds.csv" )
In [ X ]: df . kfold . value_counts ()
Out [ X ]:
4 120000
3 120000
2 120000
1 120000
0 120000
Name : kfold , dtype : int64
所有折叠都有 120000 个样本。这是意料之中的,因为训练数据有 600000 个样本,⽽我们做了5次折叠。到⽬前为⽌,⼀切顺利。
现在,我们还可以检查每个折叠的⽬标分布。
In [ X ]: df [ df . kfold = 0 ]. target . value_counts ()
Out [ X ]:
0 97536
1 22464
Name : target , dtype : int64
In [ X ]: df [ df . kfold = 1 ]. target . value_counts ()
Out [ X ]:
0 97536
1 22464
Name : target , dtype : int64
In [ X ]: df [ df . kfold = 2 ]. target . value_counts ()
Out [ X ]:
0 97535
1 22465
Name : target , dtype : int64
In [ X ]: df [ df . kfold = 3 ]. target . value_counts ()
Out [ X ]:
0 97535
1 22465
Name : target , dtype : int64
In [ X ]: df [ df . kfold = 4 ]. target . value_counts ()
Out [ X ]:
0 97535
1 22465
Name : target , dtype : int64

我们看到,在每个折叠中,⽬标的分布都是⼀样的。这正是我们所需要的。它也可以是相似的,并不⼀定要⼀直相同。现在,当我们建⽴模型时,每个折叠中的标签分布都将相同。
我们可以建⽴的最简单的模型之⼀是对所有数据进⾏独热编码并使⽤逻辑回归。
import pandas as pd
from sklearn import linear_model
from sklearn import metrics
from sklearn import preprocessingdef run(fold):df = pd.read_csv(" . /input/cat_train_folds.csv")features = [f for f in df.columns if f not in ("id", "target", "kfold")]
for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")
df_train = df[df.kfold = fold].reset_index(drop=True)
df_valid = df[df.kfold = fold].reset_index(drop=True)
ohe = preprocessing.OneHotEncoder()
full_data = pd.concat([df_train[features], df_valid[features]], axis=0)
ohe.fit(full_data[features])
x_train = ohe.transform(df_train[features])
x_valid = ohe.transform(df_valid[features])
model = linear_model.LogisticRegression()
model.fit(x_train, df_train.target.values)
valid_preds = model.predict_proba(x_valid)[:, 1]
auc = metrics.roc_auc_score(df_valid.target.values, valid_preds)
print(auc)if _ name _ = " _ main _ ":run(0)
那么,发⽣了什么呢?
我们创建了⼀个函数,将数据分为训练和验证两部分,给定折叠数,处理 NaN 值,对所有数据进⾏单次编码,并训练⼀个简单的逻辑回归模型。
当我们运⾏这部分代码时,会产⽣如下输出:
❯ python ohe_logres . py
/ home / abhishek / miniconda3 / envs / ml / lib / python3 .7 / site
packages / sklearn / linear_model / _logistic . py : 939 : ConvergenceWarning : lbfgs
failed to converge ( status = 1 ):
STOP : TOTAL NO . of ITERATIONS REACHED LIMIT .
Increase the number of iterations ( max_iter ) or scale the data as shown
in :
https : / scikit - learn . org / stable / modules / preprocessing . html .
Please also refer to the documentation for alternative solver options :
https : / scikit - learn . org / stable / modules / linear_model . html #logistic
regression
extra_warning_msg = _LOGISTIC_SOLVER_CONVERGENCE_MSG )
0.7847865042255127
有⼀些警告。逻辑回归似乎没有收敛到最⼤迭代次数。我们没有调整参数,所以没有问题。我们看到AUC 为 0.785。
现在让我们对代码进⾏简单修改,运⾏所有折叠。
. .
        model = linear_model . LogisticRegression ()
        model . fit ( x_train , df_train . target . values )
        valid_preds = model . predict_proba ( x_valid )[:, 1 ]
        auc = metrics . roc_auc_score ( df_valid . target . values , valid_preds )
        print ( f"Fold = { fold } , AUC = { auc } " )
if _ name _ = " _ main _ " :
        for fold_ in range ( 5 ):
                run ( fold_ )
请注意,我们并没有做很⼤的改动,所以我只显⽰了部分代码⾏,其中⼀些代码⾏有改动。
这就打印出了:
python - W ignore ohe_logres . py
Fold = 0 , AUC = 0.7847865042255127
Fold = 1 , AUC = 0.7853553605899214
Fold = 2 , AUC = 0.7879321942914885
Fold = 3 , AUC = 0.7870315929550808
Fold = 4 , AUC = 0.7864668243125608
请注意,我使⽤"-W ignore "忽略了所有警告。
我们看到,AUC 分数在所有褶皱中都相当稳定。平均 AUC 为 0.78631449527。对于我们的第⼀个模型来说相当不错!
很多⼈在遇到这种问题时会⾸先使⽤基于树的模型,⽐如随机森林。在这个数据集中应⽤随机森林
时,我们可以使⽤标签编码(label encoding),将每⼀列中的每个特征都转换为整数,⽽不是之前讨论过的独热编码。
这种编码与独热编码并⽆太⼤区别。让我们来看看。
import pandas as pd
from sklearn import ensemble
from sklearn import metrics
from sklearn import preprocessingdef run(fold):df = pd.read_csv("./input/cat_train_folds.csv")features = [f for f in df.columns if f not in ("id", "target", "kfold")]for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")for col in features:lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)x_train = df_train[features].valuesx_valid = df_valid[features].valuesmodel = ensemble.RandomForestClassifier(n_jobs=-1)model.fit(x_train, df_train.target.values)valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.target.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)
我们使⽤ scikit-learn 中的随机森林,并取消了独热编码。我们使⽤标签编码代替独热编码。得分如下
❯ python lbl_rf . py
Fold = 0 , AUC = 0.7167390828113697
Fold = 1 , AUC = 0.7165459672958506
Fold = 2 , AUC = 0.7159709909587376
Fold = 3 , AUC = 0.7161589664189556
Fold = 4 , AUC = 0.7156020216155978
哇 巨⼤的差异! 随机森林模型在没有任何超参数调整的情况下,表现要⽐简单的逻辑回归差很多。
这就是为什么我们总是应该先从简单模型开始的原因。随机森林模型的粉丝会从这⾥开始,⽽忽略逻辑回归模型,认为这是⼀个⾮常简单的模型,不能带来⽐随机森林更好的价值。这种⼈将会犯下⼤错。在我们实现随机森林的过程中,与逻辑回归相⽐,折叠需要更⻓的时间才能完成。因此,我们不仅损失了 AUC,还需要更⻓的时间来完成训练。请注意,使⽤随机森林进⾏推理也很耗时,⽽且占⽤的空间也更⼤。
如果我们愿意,也可以尝试在稀疏的独热编码数据上运⾏随机森林,但这会耗费⼤量时间。我们还可以尝试使⽤奇异值分解来减少稀疏的独热编码矩阵。这是⾃然语⾔处理中提取主题的常⽤⽅法。
import pandas as pd
from scipy import sparse
from sklearn import decomposition
from sklearn import ensemble
from sklearn import metrics
from sklearn import preprocessingdef run(fold):# 读取数据df = pd.read_csv("./input/cat_train_folds.csv")features = [f for f in df.columns if f not in ("id", "target", "kfold")]# 填充空值for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 分割训练和验证数据df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)# One Hot 编码ahe = preprocessing.OneHotEncoder()full_data = pd.concat([df_train[features], df_valid[features]], axis=0)ohe.fit(full_data[features])x_train = ohe.transform(df_train[features])x_valid = ohe.transform(df_valid[features])# 使用截断的奇异值分解(TruncatedSVD)降维svd = decomposition.TruncatedSVD(n_components=120)full_sparse = sparse.vstack((x_train, x_valid))svd.fit(full_sparse)x_train = svd.transform(x_train)x_valid = svd.transform(x_valid)# 使用随机森林分类器训练模型model = ensemble.RandomForestClassifier(n_jobs=-1)model.fit(x_train, df_train.target.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.target.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)
我们对全部数据进⾏独热编码,然后⽤训练数据和验证数据在稀疏矩阵上拟合 scikit-learn 的
TruncatedSVD。这样,我们将⾼维稀疏矩阵减少到 120个特征,然后拟合随机森林分类器。
以下是该模型的输出结果:
❯ python ohe_svd_rf . py
Fold = 0 , AUC = 0.7064863038754249
Fold = 1 , AUC = 0.706050102937374
Fold = 2 , AUC = 0.7086069243167242
Fold = 3 , AUC = 0.7066819080085971
Fold = 4 , AUC = 0.7058154015055585
我们发现情况更糟。看来,解决这个问题的最佳⽅法是使⽤逻辑回归和独热编码。随机森林似乎耗时太多。也许我们可以试试 XGBoost。如果你不知道 XGBoost,它是最流⾏的梯度提升算法之⼀。由于它是⼀种基于树的算法,我们将使⽤标签编码数据。
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessingdef run(fold):# 读取数据df = pd.read_csv("./input/cat_train_folds.csv")features = [f for f in df.columns if f not in ("id", "target", "kfold")]# 填充空值for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 标签编码for col in features:lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])# 划分训练集和验证集df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)x_train = df_train[features].valuesx_valid = df_valid[features].values# 使用XGBoost分类器训练模型model = xgb.XGBClassifier(n_jobs=-1,max_depth=7,n_estimators=200)model.fit(x_train, df_train.target.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.target.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)
必须指出的是,在这段代码中,我对 xgboost 参数做了⼀些修改。xgboost 的默认最⼤深度
(max_depth)是 3,我把它改成了 7,还把估计器数量(n_estimators)从 100改成了 200。
该模型的 5折交叉检验得分如下:
❯ python lbl_xgb . py
Fold = 0 , AUC = 0.7656768851999011
Fold = 1 , AUC = 0.7633006564148015
Fold = 2 , AUC = 0.7654277821434345
Fold = 3 , AUC = 0.7663609758878182
Fold = 4 , AUC = 0.764914671468069
我们可以看到,在不做任何调整的情况下,我们的得分⽐普通随机森林要⾼得多。
您还可以尝试⼀些特征⼯程,放弃某些对模型没有任何价值的列等。但似乎我们能做的不多,⽆法证明模型的改进。让我们把数据集换成另⼀个有⼤量分类变量的数据集。另⼀个有名的数据集是 美国成⼈⼈⼝普查数据( US adult census data 。这个数据集包含⼀些特征,⽽你的任务是预测⼯资等级。让我们来看看这个数据集。图 5显⽰了该数据集中的⼀些列。

这些特征⼤多不⾔⾃明。那些不明⽩的,我们可以不考虑。让我们先尝试建⽴⼀个模型。
我们看到收⼊列是⼀个字符串。让我们对这⼀列进⾏数值统计。
In [ X ]: import pandas as pd
In [ X ]: df = pd . read_csv ( " . /input/adult.csv" )
In [ X ]: df . income . value_counts ()
Out [ X ]:
= 50 K 24720
> 50 K 7841
我们可以看到,有 7841 个实例的收⼊超过 5 万美元。这占样本总数的 24%。因此,我们将保持与猫数据集相同的评估⽅法,即 AUC。 在开始建模之前,为了简单起⻅,我们将去掉⼏列特征,即
学历(fnlwgt)
年龄(age)
资本收益(capital.gain)
资本损失(capital.loss)
每周⼩时数(hours.per.week)
让我们试着⽤逻辑回归和独热编码器,看看会发⽣什么。第⼀步总是要进⾏交叉验证。我不会在这⾥ 展⽰这部分代码。留练习。
import pandas as pd
from sklearn import linear_model
from sklearn import metrics
from sklearn import preprocessingdef run(fold):# 读取数据df = pd.read_csv("./input/adult_folds.csv")num_cols = ["fnlwgt","age","capital.gain","capital.loss","hours.per.week"]df = df.drop(num_cols, axis=1)# 将目标变量映射为0和1target_mapping = {"<=50K": 0,">50K": 1}df.loc[:, "income"] = df.income.map(target_mapping)features = [f for f in df.columns if f not in ("kfold", "income")]# 填充空值for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 划分训练集与验证集df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)# One Hot 编码ahe = preprocessing.OneHotEncoder()full_data = pd.concat([df_train[features], df_valid[features]], axis=0)ohe.fit(full_data[features])x_train = ohe.transform(df_train[features])x_valid = ohe.transform(df_valid[features])# 使用逻辑回归训练模型model = linear_model.LogisticRegression()model.fit(x_train, df_train.income.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)

当我们运⾏这段代码时,我们会得到 

❯ python - W ignore ohe_logres . py
Fold = 0 , AUC = 0.8794809708119079
Fold = 1 , AUC = 0.8875785068274882
Fold = 2 , AUC = 0.8852609687685753
Fold = 3 , AUC = 0.8681236223251438
Fold = 4 , AUC = 0.8728581541840037
对于⼀个如此简单的模型来说,这是⼀个⾮常不错的 AUC!
现在,让我们在不调整任何超参数的情况下尝试⼀下标签编码的xgboost。
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessingdef run(fold):# 读取数据df = pd.read_csv("./input/adult_folds.csv")num_cols = ["fnlwgt","age","capital.gain","capital.loss","hours.per.week"]df = df.drop(num_cols, axis=1)# 将目标变量映射为0和1target_mapping = {"<=50K": 0,">50K": 1}df.loc[:, "income"] = df.income.map(target_mapping)features = [f for f in df.columns if f not in ("kfold", "income")]# 填充空值for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 标签编码for col in features:lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])# 划分训练集与验证集df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)x_train = df_train[features].valuesx_valid = df_valid[features].values# 使用XGBoost分类器训练模型model = xgb.XGBClassifier(n_jobs=-1)model.fit(x_train, df_train.income.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)

让我们运⾏上⾯代码:

❯ python lbl_xgb . py
Fold = 0 , AUC = 0.8800810634234078
Fold = 1 , AUC = 0.886811884948154
Fold = 2 , AUC = 0.8854421433318472
Fold = 3 , AUC = 0.8676319549361007
Fold = 4 , AUC = 0.8714450054900602

这看起来已经相当不错了。让我们看看 max_depth 增加到 7 和 n_estimators 增加到 200时的得分。 

❯ python lbl_xgb . py
Fold = 0 , AUC = 0.8764108944332032
Fold = 1 , AUC = 0.8840708537662638
Fold = 2 , AUC = 0.8816601162613102
Fold = 3 , AUC = 0.8662335762581732
Fold = 4 , AUC = 0.8698983461709926
看起来并没有改善。
这表明,⼀个数据集的参数不能移植到另⼀个数据集。我们必须再次尝试调整参数,但我们将在接下来的章节中详细说明。
现在,让我们尝试在不调整参数的情况下将数值特征纳⼊ xgboost 模型。
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessingdef run(fold):# 读取数据df = pd.read_csv("./input/adult_folds.csv")num_cols = ["fnlwgt","age","capital.gain","capital.loss","hours.per.week"]# 将目标变量映射为0和1target_mapping = {"<=50K": 0,">50K": 1}df.loc[:, "income"] = df.income.map(target_mapping)features = [f for f in df.columns if f not in ("kfold", "income")]# 填充空值for col in features:if col not in num_cols:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 标签编码for col in features:if col not in num_cols:lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])# 划分训练集与验证集df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)x_train = df_train[features].valuesx_valid = df_valid[features].values# 使用XGBoost分类器训练模型model = xgb.XGBClassifier(n_jobs=-1)model.fit(x_train, df_train.income.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)
因此,我们保留数字列,只是不对其进⾏标签编码。这样,我们的最终特征矩阵就由数字列(原样)和编码分类列组成了。任何基于树的算法都能轻松处理这种混合。
请注意,在使⽤基于树的模型时,我们不需要对数据进⾏归⼀化处理。不过,这⼀点⾮常重要,在使⽤线性模型(如逻辑回归)时不容忽视。
现在让我们运⾏这个脚本!
❯ python lbl_xgb_num . py
Fold = 0 , AUC = 0.9209790185449889
Fold = 1 , AUC = 0.9247157449144706
Fold = 2 , AUC = 0.9269329887598243
Fold = 3 , AUC = 0.9119349082169275
Fold = 4 , AUC = 0.9166408030141667
哇哦
这是⼀个很好的分数!
现在,我们可以尝试添加⼀些功能。我们将提取所有分类列,并创建所有⼆度组合。请看下⾯代码段中的 feature_engineering 函数,了解如何实现这⼀点。
import itertools
import pandas as pd
import xgboost as xgb
from sklearn import metrics
from sklearn import preprocessingdef feature_engineering(df, cat_cols):combi = list(itertools.combinations(cat_cols, 2))for c1, c2 in combi:df.loc[:, c1 + "_" + c2] = df[c1].astype(str) + "_" + df[c2].astype(str)return dfdef run(fold):df = pd.read_csv("./input/adult_folds.csv")num_cols = ["fnlwgt","age","capital.gain","capital.loss","hours.per.week"]# 将目标变量映射为0和1target_mapping = {"<=50K": 0,">50K": 1}df.loc[:, "income"] = df.income.map(target_mapping)cat_cols = [c for c in df.columns if c not in num_cols and c not in ("kfold", "income")]# 特征工程df = feature_engineering(df, cat_cols)features = [f for f in df.columns if f not in ("kfold", "income")]# 填充空值for col in features:if col not in num_cols:df.loc[:, col] = df[col].astype(str).fillna("NONE")# 标签编码for col in features:if col not in num_cols:lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])# 划分训练集与验证集df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold == fold].reset_index(drop=True)x_train = df_train[features].valuesx_valid = df_valid[features].values# 使用XGBoost分类器训练模型model = xgb.XGBClassifier(n_jobs=-1)model.fit(x_train, df_train.income.values)# 预测并计算AUC值valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":for fold_ in range(5):run(fold_)
这是从分类列中创建特征的⼀种⾮常幼稚的⽅法。我们应该仔细研究数据,看看哪些组合最合理。如果使⽤这种⽅法,最终可能会创建⼤量特征,在这种情况下,就需要使⽤某种特征选择来选出最佳特征。稍后我们将详细介绍特征选择。现在让我们来看看分数。
❯ python lbl_xgb_num_feat . py
Fold = 0 , AUC = 0.9211483465031423
Fold = 1 , AUC = 0.9251499446866125
Fold = 2 , AUC = 0.9262344766486692
Fold = 3 , AUC = 0.9114264068794995
Fold = 4 , AUC = 0.9177914453099201
看来,即使不改变任何超参数,只增加⼀些特征,我们也能提⾼⼀些折叠得分。让我们看看将
max_depth 增加到 7 是否有帮助。
❯ python lbl_xgb_num_feat . py
Fold = 0 , AUC = 0.9286668430204137
Fold = 1 , AUC = 0.9329340656165378
Fold = 2 , AUC = 0.9319817543218744
Fold = 3 , AUC = 0.919046187194538
Fold = 4 , AUC = 0.9245692057162671
我们再次改进了我们的模型。
请注意,我们还没有使⽤稀有值、⼆值化、独热编码和标签编码特征的组合以及其他⼏种⽅法。 从分类特征中进⾏特征⼯程的另⼀种⽅法是使⽤ ⽬标编码 。但是,您必须⾮常⼩⼼,因为这可能会使
您的模型过度拟合。⽬标编码是⼀种将给定特征中的每个类别映射到其平均⽬标值的技术,但必须始终以交叉验证的⽅式进⾏。这意味着⾸先要创建折叠,然后使⽤这些折叠为数据的不同列创建⽬标编码特征,⽅法与在折叠上拟合和预测模型的⽅法相同。因此,如果您创建了 5 个折叠,您就必须创建5 次⽬标编码,这样最终,您就可以为每个折叠中的变量创建编码,⽽这些变量并⾮来⾃同⼀个折叠。然后在拟合模型时,必须再次使⽤相同的折叠。未⻅测试数据的⽬标编码可以来⾃全部训练数据,也可以是所有 5个折叠的平均值。
让我们看看如何在同⼀个成⼈数据集上使⽤⽬标编码,以便进⾏⽐较。
import copy
import pandas as pd
from sklearn import metrics
from sklearn import preprocessing
import xgboost as xgbdef mean_target_encoding(data):df = copy.deepcopy(data)num_cols = ["fnlwgt","age","capital.gain","capital.loss","hours.per.week"]# 将目标变量映射为0和1target_mapping = {"<=50K": 0,">50K": 1}df.loc[:, "income"] = df.income.map(target_mapping)features = [f for f in df.columns if f not in ("kfold", "income") and f not in num_cols]# 填充空值和标签编码for col in features:if col not in num_cols:df.loc[:, col] = df[col].astype(str).fillna("NONE")lbl = preprocessing.LabelEncoder()lbl.fit(df[col])df.loc[:, col] = lbl.transform(df[col])encoded_dfs = []for fold in range(5):df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold != fold].reset_index(drop=True)for column in features:mapping_dict = dict(df_train.groupby(column)["income"].mean())df_valid.loc[:, column + "_enc"] = df_valid[column].map(mapping_dict)encoded_dfs.append(df_valid)encoded_df = pd.concat(encoded_dfs, axis=0)return encoded_dfdef run(df, fold):df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold != fold].reset_index(drop=True)features = [f for f in df.columns if f not in ("kfold", "income")]x_train = df_train[features].valuesx_valid = df_valid[features].valuesmodel = xgb.XGBClassifier(n_jobs=-1, max_depth=7)model.fit(x_train, df_train.income.values)valid_preds = model.predict_proba(x_valid)[:, 1]auc = metrics.roc_auc_score(df_valid.income.values, valid_preds)print(f"Fold = {fold}, AUC = {auc}")if __name__ == "__main__":df = pd.read_csv("./input/adult_folds.csv")df = mean_target_encoding(df)for fold_ in range(5):run(df, fold_)
必须指出的是,在上述⽚段中,我在进⾏⽬标编码时并没有删除分类列。我保留了所有特征,并在此基础上添加了⽬标编码特征。此外,我还使⽤了平均值。您可以使⽤平均值、中位数、标准偏差或⽬标的任何其他函数。
让我们看看结果。
Fold = 0 , AUC = 0.9332240662017529
Fold = 1 , AUC = 0.9363551625140347
Fold = 2 , AUC = 0.9375013544556173
Fold = 3 , AUC = 0.92237621307625
Fold = 4 , AUC = 0.9292131180445478
不错!看来我们⼜有进步了。不过,使⽤⽬标编码时必须⾮常⼩⼼,因为它太容易出现过度拟合。当我们使⽤⽬标编码时,最好使⽤某种平滑⽅法或在编码值中添加噪声。 Scikit-learn 的贡献库中有带平滑的⽬标编码,你也可以创建⾃⼰的平滑。平滑会引⼊某种正则化,有助于避免模型过度拟合。这并不难。
处理分类特征是⼀项复杂的任务。许多资源中都有⼤量信息。本章应该能帮助你开始解决分类变量的任何问题。不过,对于⼤多数问题来说,除了独热编码和标签编码之外,你不需要更多的东西。 要进⼀步改进模型,你可能需要更多!
在本章的最后,我们不能不在这些数据上使⽤神经⽹络。因此,让我们来看看⼀种称为 实体嵌⼊ 的技术。在实体嵌⼊中,类别⽤向量表⽰。在⼆值化和独热编码⽅法中,我们都是⽤向量来表⽰类别的。但是,如果我们有数以万计的类别怎么办?这将会产⽣巨⼤的矩阵,我们将需要很⻓时间来训练复杂的模型。因此,我们可以⽤带有浮点值的向量来表⽰它们。
这个想法⾮常简单。每个分类特征都有⼀个嵌⼊层。因此,⼀列中的每个类别现在都可以映射到⼀个嵌⼊层(就像在⾃然语⾔处理中将单词映射到嵌⼊层⼀样)。然后,根据其维度重塑这些嵌⼊层,使其扁平化,然后将所有扁平化的输⼊嵌⼊层连接起来。然后添加⼀堆密集层和⼀个输出层,就⼤功告成了。

出于某种原因,我发现使⽤ TF/Keras 可以⾮常容易地做到这⼀点。因此,让我们来看看如何使⽤
TF/Keras 实现它。此外,这是本书中唯⼀⼀个使⽤ TF/Keras 的⽰例,将其转换为 PyTorch(使⽤ cat in-the-dat-ii 数据集)也⾮常容易
import os
import gc
import joblib
import pandas as pd
import numpy as np
from sklearn import metrics, preprocessing
from tensorflow.keras import layers
from tensorflow.keras import optimizers
from tensorflow.keras.models import Model, load_model
from tensorflow.keras import callbacks
from tensorflow.keras import backend as K
from tensorflow.keras import utilsdef create_model(data, catcols):inputs = []outputs = []for c in catcols:num_unique_values = int(data[c].nunique())embed_dim = int(min(np.ceil((num_unique_values)/2), 50))inp = layers.Input(shape=(1,))out = layers.Embedding(num_unique_values + 1, embed_dim, name=c)(inp)out = layers.SpatialDropout1D(0.3)(out)out = layers.Reshape(target_shape=(embed_dim, ))(out)inputs.append(inp)outputs.append(out)x = layers.Concatenate()(outputs)x = layers.BatchNormalization()(x)x = layers.Dense(300, activation="relu")(x)x = layers.Dropout(0.3)(x)x = layers.BatchNormalization()(x)x = layers.Dense(300, activation="relu")(x)x = layers.Dropout(0.3)(x)x = layers.BatchNormalization()(x)y = layers.Dense(2, activation="softmax")(x)model = Model(inputs=inputs, outputs=y)model.compile(loss='binary_crossentropy', optimizer='adam')return modeldef run(fold):df = pd.read_csv("./input/cat_train_folds.csv")features = [f for f in df.columns if f not in ("id", "target", "kfold")]for col in features:df.loc[:, col] = df[col].astype(str).fillna("NONE")for feat in features:lbl_enc = preprocessing.LabelEncoder()df.loc[:, feat] = lbl_enc.fit_transform(df[feat].values)df_train = df[df.kfold == fold].reset_index(drop=True)df_valid = df[df.kfold != fold].reset_index(drop=True)model = create_model(df, features)xtrain = [df_train[features].values[:, k] for k in range(len(features))]xvalid = [df_valid[features].values[:, k] for k in range(len(features))]ytrain = df_train.target.valuesyvalid = df_valid.target.valuesytrain_cat = utils.to_categorical(ytrain)yvalid_cat = utils.to_categorical(yvalid)model.fit(xtrain,ytrain_cat,validation_data=(xvalid, yvalid_cat),verbose=1,batch_size=1024,epochs=3)valid_preds = model.predict(xvalid)[:, 1]print(metrics.roc_auc_score(yvalid, valid_preds))K.clear_session()if __name__ == "__main__":run(0)run(1)run(2)run(3)run(4)
你会发现这种⽅法效果最好,⽽且如果你有 GPU,速度也超快!这种⽅法还可以进⼀步改进,⽽且你⽆需担⼼特征⼯程,因为神经⽹络会⾃⾏处理。在处理⼤量分类特征数据集时,这绝对值得⼀试。当嵌⼊⼤⼩与唯⼀类别的数量相同时,我们就可以使⽤独热编码(one-hot-encoding)。
本章基本上都是关于特征⼯程的。让我们在下⼀章中看看如何在数字特征和不同类型特征的组合⽅⾯进⾏更多的特征⼯程。

这篇关于【解决(几乎)任何机器学习问题】:处理分类变量篇(下篇)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/728276

相关文章

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

解决Maven项目idea找不到本地仓库jar包问题以及使用mvn install:install-file

《解决Maven项目idea找不到本地仓库jar包问题以及使用mvninstall:install-file》:本文主要介绍解决Maven项目idea找不到本地仓库jar包问题以及使用mvnin... 目录Maven项目idea找不到本地仓库jar包以及使用mvn install:install-file基

一文详解Java异常处理你都了解哪些知识

《一文详解Java异常处理你都了解哪些知识》:本文主要介绍Java异常处理的相关资料,包括异常的分类、捕获和处理异常的语法、常见的异常类型以及自定义异常的实现,文中通过代码介绍的非常详细,需要的朋... 目录前言一、什么是异常二、异常的分类2.1 受检异常2.2 非受检异常三、异常处理的语法3.1 try-

最详细安装 PostgreSQL方法及常见问题解决

《最详细安装PostgreSQL方法及常见问题解决》:本文主要介绍最详细安装PostgreSQL方法及常见问题解决,介绍了在Windows系统上安装PostgreSQL及Linux系统上安装Po... 目录一、在 Windows 系统上安装 PostgreSQL1. 下载 PostgreSQL 安装包2.

Python使用getopt处理命令行参数示例解析(最佳实践)

《Python使用getopt处理命令行参数示例解析(最佳实践)》getopt模块是Python标准库中一个简单但强大的命令行参数处理工具,它特别适合那些需要快速实现基本命令行参数解析的场景,或者需要... 目录为什么需要处理命令行参数?getopt模块基础实际应用示例与其他参数处理方式的比较常见问http

Java Response返回值的最佳处理方案

《JavaResponse返回值的最佳处理方案》在开发Web应用程序时,我们经常需要通过HTTP请求从服务器获取响应数据,这些数据可以是JSON、XML、甚至是文件,本篇文章将详细解析Java中处理... 目录摘要概述核心问题:关键技术点:源码解析示例 1:使用HttpURLConnection获取Resp

usb接口驱动异常问题常用解决方案

《usb接口驱动异常问题常用解决方案》当遇到USB接口驱动异常时,可以通过多种方法来解决,其中主要就包括重装USB控制器、禁用USB选择性暂停设置、更新或安装新的主板驱动等... usb接口驱动异常怎么办,USB接口驱动异常是常见问题,通常由驱动损坏、系统更新冲突、硬件故障或电源管理设置导致。以下是常用解决

Java中Switch Case多个条件处理方法举例

《Java中SwitchCase多个条件处理方法举例》Java中switch语句用于根据变量值执行不同代码块,适用于多个条件的处理,:本文主要介绍Java中SwitchCase多个条件处理的相... 目录前言基本语法处理多个条件示例1:合并相同代码的多个case示例2:通过字符串合并多个case进阶用法使用

Mysql如何解决死锁问题

《Mysql如何解决死锁问题》:本文主要介绍Mysql如何解决死锁问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录【一】mysql中锁分类和加锁情况【1】按锁的粒度分类全局锁表级锁行级锁【2】按锁的模式分类【二】加锁方式的影响因素【三】Mysql的死锁情况【1

Java实现优雅日期处理的方案详解

《Java实现优雅日期处理的方案详解》在我们的日常工作中,需要经常处理各种格式,各种类似的的日期或者时间,下面我们就来看看如何使用java处理这样的日期问题吧,感兴趣的小伙伴可以跟随小编一起学习一下... 目录前言一、日期的坑1.1 日期格式化陷阱1.2 时区转换二、优雅方案的进阶之路2.1 线程安全重构2