稳健的独热编码
原文towardsdatascience.com/robust-one-hot-encoding-930b5f8943af?sourcecollection_archive---------4-----------------------#2024-04-26Python 和 R 中的生产级独热编码技术https://medium.com/hc.ekne?sourcepost_page---byline--930b5f8943af--------------------------------https://towardsdatascience.com/?sourcepost_page---byline--930b5f8943af-------------------------------- Hans Christian Ekne·发表于 Towards Data Science ·阅读时间 11 分钟·2024 年 4 月 26 日–https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3b472814a17418f0804ebfbcc8a1e9db.png图像由作者使用 DALL-E 生成/还是 Dali你是否在机器学习生产环境中遇到过崩溃这并不有趣尤其是当涉及到可以避免的问题时。一个经常引起问题的原因是数据的独热编码。通过我自己的经验我发现很多这些问题在遵循一些与独热编码相关的最佳实践时是可以大大避免的。在这篇文章中我将简要介绍这个话题并通过一些简单的示例分享一些最佳实践以确保你的机器学习模型的稳定性。独热编码什么是独热编码独热编码是将存储在一列中的因子变量转化为多个列中的虚拟变量并以 0 和 1 的形式表示的做法。一个简单的例子说明了这个概念。例如考虑这个包含一些数字和颜色列的数据集importpandasaspd# Creating the training_data DataFrame in Pythontraining_datapd.DataFrame({numerical_1:[1,2,3,4,5,6,7,8],color_1_:[black,black,red,green,green,black,red,blue],color_2_:[black,blue,pink,purple,black,blue,pink,purple]})或者更直观地展示https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/de251ce1de486b95f24cf64ab9c324bd.png训练数据包含 3 列/图像由作者提供color_1_列也可以像下面的表格一样表示https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/17f1d9b0fd2a6e5438855cd8aa8dbabf.png“color_1_”的独热编码表示 / 图像由作者提供将color_1_从一个单列的紧凑表示转化为多列的二进制表示这就是我们所说的独热编码one-hot encoding。为什么我们要使用它使用独热编码有多个原因。它们可能与避免隐含排序、提高模型性能或只是使数据与各种算法兼容有关。例如当你将一个类别变量如颜色编码成数值结构时例如黑色为 1绿色为 2红色为 3如果不将其转换为虚拟变量模型可能会错误地将数据误解为存在顺序关系黑色 绿色 红色而实际上并不存在这种顺序。此外在训练神经网络时最佳实践是在将数据输入神经网络之前对数据进行标准化对于类别变量独热编码是一种不错的方法。其他线性模型如逻辑回归和线性回归假设输入是线性关系和数值型数据因此对于这一类模型独热编码也是一个好方法。此外进行独热编码的过程迫使我们确保不会将未见过的因子级别输入到我们的机器学习模型中。最终独热编码使得机器学习模型更容易理解数据从而做出更好的预测。独热编码失败的主要原因我们构建传统机器学习模型的方式是首先在“训练数据集”上训练模型——通常是一个历史数据集——然后在新的数据集即“推理数据集”上生成预测。如果训练数据集和推理数据集的列不匹配机器学习算法通常会失败。主要原因是推理数据集中缺少列或包含新的因子级别。第一个问题缺失因子对于以下示例假设你使用上面的数据集来训练机器学习模型。你将数据集进行独热编码转换成虚拟变量并且你的完全转换后的训练数据如下所示https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/3838878fe8c718db9f2226fa542dc159.png使用pd.get_dummies转换后的训练数据集 / 图片来自作者现在让我们引入推理数据集这就是你用于进行预测的数据集。假设它是如下所示# Creating the inference_data DataFrame in Pythoninference_datapd.DataFrame({numerical_1:[11,12,13,14,15,16,17,18],color_1_:[black,blue,black,green,green,black,black,blue],color_2_:[orange,orange,black,orange,black,orange,orange,orange]})https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/9647cc2c91077b979ff0ab9f045c0c76.png推理数据集包含 3 列/ 图片来自作者使用我们上面使用的简单独热编码策略pd.get_dummies# Converting categorical columns in inference_data to# Dummy variables with integersinference_data_dummiespd.get_dummies(inference_data,columns[color_1_,color_2_]).astype(int)这将以相同的方式转换你的推理数据集你会得到如下的数据集https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/beb8f6bd286cd54bd9ff34350c8cefda.png使用pd.get_dummies转换后的推理数据集 / 图片来自作者你注意到问题了吗第一个问题是推理数据集中缺少以下列missing_colmns[color_1__red,color_2__pink,color_2__blue,color_2__purple]如果你在一个用“训练数据集”训练的模型中运行这段代码通常会崩溃。第二个问题新因子另一个可能发生的独热编码问题是如果推理数据集中包含了新的、未见过的因子。再次考虑上面的数据集。如果你仔细检查你会发现推理数据集现在有了一个新列color_2__orange。这是与之前问题相反的情况我们的推理数据集包含了训练数据集中没有的新列。这实际上是一个常见的情况如果你的某个因子变量发生了变化就可能会发生这种情况。例如如果上面提到的颜色代表汽车的颜色而一个汽车生产商突然开始生产橙色的汽车那么这些数据可能在训练数据中不可用但仍然可能出现在推理数据中。在这种情况下你需要一种健壮的方式来处理这个问题。有人可能会争论为什么不直接将转化后的训练数据集中的所有列列为推理数据集所需的列呢这里的问题是通常你无法事先知道训练数据中的因子水平。例如新的水平可能会定期引入这可能使得维护变得困难。此外还需要将推理数据集与训练数据进行匹配因此你需要检查所有实际转化后的列名这些列名是用于训练算法的然后将它们与转化后的推理数据集进行匹配。如果有任何列缺失你需要插入新列并填充 0 值如果有多余的列比如上面的color_2__orange列那么需要删除这些列。这是一种相当繁琐的解决问题的方法幸好有更好的选择可供使用。解决方案解决这个问题的方法相对直接然而许多试图简化预测模型创建过程的包和库并未很好地实现它。关键在于拥有一个先在训练数据上进行拟合的函数或类然后使用该函数或类的相同实例来转化训练数据集和推理数据集。下面我们将探索如何使用 Python 和 R 来完成这一操作。在 Python 中Python 无疑是进行机器学习的最佳编程语言之一这主要得益于其广泛的开发者网络和成熟的包库以及它的易用性促进了快速开发。关于我们上述描述的与独热编码相关的问题它们可以通过使用广泛可用且经过测试的 scikit-learn 库来缓解尤其是使用sklearn.preprocessing.OneHotEncoder类。因此让我们看看如何在我们的训练和推理数据集上使用它来创建一个健壮的独热编码。fromsklearn.preprocessingimportOneHotEncoder# Initialize the encoderencOneHotEncoder(handle_unknownignore)# Define columns to transformtrans_columns[color_1_,color_2_]# Fit and transform the dataenc_dataenc.fit_transform(training_data[trans_columns])# Get feature namesfeature_namesenc.get_feature_names_out(trans_columns)# Convert to DataFrameenc_dfpd.DataFrame(enc_data.toarray(),columnsfeature_names)# Concatenate with the numerical datafinal_dfpd.concat([training_data[[numerical_1]],enc_df],axis1)这将生成一个最终的DataFrame如下面所示https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f305e941d89808c5f5ac9eb6977727f2.png使用 sklearn 转化后的训练数据集 / 作者提供的图片如果我们分解上面的代码我们会看到第一步是初始化编码器类的实例。我们使用handle_unknownignore选项以便在使用编码器转化推理数据集时避免出现未知值的问题。之后我们将fit和transform操作合并为一步使用fit_transform方法。最后我们从编码后的数据创建一个新的数据框并将其与原始数据集的其余部分拼接在一起。现在的任务是使用编码器来转换我们的推断数据集。# Transform inference datainference_encodedenc.transform(inference_data[trans_columns])inference_feature_namesenc.get_feature_names_out(trans_columns)inference_encoded_dfpd.DataFrame(inference_encoded.toarray(),columnsinference_feature_names)final_inference_dfpd.concat([inference_data[[numerical_1]],inference_encoded_df],axis1)与之前我们使用简单的pandas.get_dummies时不同现在我们看到我们的新数据集final_inference_df具有与训练数据集相同的列。https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/cd25ea429be35479ec782fb89827e12e.png转换后的推断数据集具有正确的列 / 图片来源作者除了我们在上面的代码中展示的内容之外sklearn.preprocessing中的OneHotEncoder类还有很多其他功能也同样非常有用。例如它允许你设置min_frequency和max_categories选项。顾名思义min_frequency选项允许你指定一个最低频率低于该频率的类别将被视为不常见并与其他不常见类别一起分组或者是max_categories选项它限制了类别的总数。如果你不希望在训练数据集中创建过多的列这后一种选项特别有用。欲了解完整功能概述请访问以下文档页面[## sklearn.preprocessing.OneHotEncoder使用 sklearn.preprocessing.OneHotEncoder 的示例scikit-learn 1.4 版本发布亮点……scikit-learn.org](https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.OneHotEncoder.html?sourcepost_page-----930b5f8943af--------------------------------#sklearn.preprocessing.OneHotEncoder)在 R 中我的几个客户使用 R 在生产环境中运行机器学习模型——而且 R 有很多优秀的功能。在polars发布之前R 的data.table包在速度和效率上优于pandas所能提供的。然而R 无法访问像 Python 中scikit-learn这样的生产级别的包。虽然有一些库但它们不像scikit-learn那样成熟。此外尽管某些包可能具有所需的功能但它们需要大量其他包的支持并可能会引入依赖冲突。考虑在使用 r-base 镜像构建的 docker 容器中运行下面的命令RUN R-einstall.packages(recipes, dependenciesTRUE, reposhttps://cran.rstudio.com/)它安装时间非常长而且占用了你容器镜像的大量空间。在这种情况下我们的解决方案是——不使用像recipes这样预构建包中的函数——而是引入我们自己实现的简单函数使用data.table包library(data.table)OneHotEncoder-function(){# Local variablescategories-list()# Method to fit data and extract categoriesfit-function(dt,columns){for(columnincolumns){categories[[column]]-unique(dt[[column]])}}# Method to turn columns into factors andfactorize-function(dt){for(column_nameinnames(categories)){set(dt,jcolumn_name,valuefactor(dt[[column_name]],levelscategories[[column_name]]))}return(dt)}# Method to transform columns in categories list to# dummy variablestransform-function(dt){dtfactorize(dt)# add row number for joins laterdt[,rn:.I]for(colinnames(categories)){print(col)# Construct the formula dynamicallyformula_str-paste(~,col,- 1)formula_obj-as.formula(formula_str)# Create a model model.matrix objectmmmodel.matrix(formula_obj,dt)mm_dt-as.data.table(mm,keep.rownamesrn)mm_dt[,rn:as.integer(rn)]# Perform a merge based on these row numbersdt-merge(dt,mm_dt,byrn,allTRUE)# remove the original columndt[,(col):NULL]# set any new NAs to 0for(ncolinnames(mm_dt)){set(dt,which(is.na(dt[[ncol]])),ncol,0)}}dt[,rn:NULL]return(dt)}# Method to get categoriesget_categories-function(){return(categories)}# Return a list of methodslist(get_categoriesget_categories,fitfit,transformtransform)}让我们仔细研究这个函数看看它在我们的训练和推断数据集上是如何工作的。R 与 Python 稍有不同我们不使用类而是使用一个父函数其工作方式类似。首先我们需要创建一个函数的实例encoderOneHotEncoder()然后就像sklearn.preprocessing中的OneHotEncoder类一样我们的OneHotEncoder中也有一个适配函数。 我们在训练数据上使用适配函数提供训练数据集和我们想要进行独热编码的列。# Columns to one-hot encodefit_columnsc(color_1_,color_2)# Use the fit methodencoder$fit(dttraining_data,columnsfit_columns)适配函数简单地循环遍历我们想要用于训练的所有列并找到每个列包含的所有唯一值。 然后在转换函数中使用这些列及其潜在值。 现在我们有一个已安装的独热编码器函数实例我们可以使用 R 的.RDS文件将其保存以备将来使用。saveRDS(encoder,~/my_encoder.RDS)为了生成我们需要用于训练的独热编码数据集我们在训练数据上运行转换函数transformed_training_dataencoder$transform(training_data)转换函数比拟拟适配函数稍微复杂一些它首先将提供的列转换为因子 — 使用列的原始唯一值作为因子水平。 然后我们循环遍历每个预测列并创建数据的model.matrix对象。 然后将这些对象添加回原始数据集并删除原始因子列。 我们还确保将任何缺失值设置为 0。现在我们得到了与之前完全相同的数据集https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/f305e941d89808c5f5ac9eb6977727f2.png使用 R 算法转换的训练数据集 / 作者提供的图片最后当我们需要对我们的推断数据集进行独热编码时我们在该数据集上运行编码器函数的相同实例transformed_inference_dataencoder$transform(inference_data)该过程确保我们在transformed_inference_data中有与transformed_training_data中相同的列。进一步考虑在我们总结之前有一些额外的考虑事项需要提及。 与机器学习中的许多其他事物一样关于何时以及如何使用特定技术并没有总是一个简单的答案。 尽管它显然可以缓解一些问题但在进行独热编码时也可能出现新问题。 最常见的问题与如何处理高基数分类变量以及由于增加表格大小而导致的内存问题有关。此外还有替代编码技术如标签编码、嵌入或目标编码有时可能更适合于独热编码。每个主题都足够丰富值得撰写一篇专门的文章因此我们将这些内容留给有兴趣进一步探索的读者。结论我们已经展示了如何错误地使用 one-hot 编码技术可能导致推理数据中的错误和问题也展示了如何通过 Python 和 R 来减轻和解决这些问题。如果不解决one-hot 编码的管理不当可能会导致崩溃和推理问题因此强烈建议使用更稳健的技术——例如 sklearn 的OneHotEncoder或我们开发的 R 函数。感谢阅读文中展示并使用的所有代码可以在以下 GitHub 仓库找到https://github.com/hcekne/robust_one_hot_encoding如果你喜欢阅读这篇文章并希望访问更多我的内容欢迎通过 LinkedIn 与我联系链接为https://www.linkedin.com/in/hans-christian-ekne-1760a259/或者访问我的个人网站https://www.ekneconsulting.com/了解我提供的一些服务。如有任何疑问请通过电子邮件 hceekneconsulting.com 随时与我联系。