作为在机器学习领域摸爬滚打多年的从业者,我见过太多人在特征处理这个环节上轻视“独热向量编码”(One-Hot Encoding),结果要么在建模时得到一团乱麻的输出,要么在类别数量激增时把自己卡死在内存里。这篇文章我把独热编码从原理到实战、从省事做法到极端边界,完整拆开揉碎讲清楚,希望能帮你绕开那些教科书里不会写的暗坑。它非常适合刚入门、正准备用逻辑回归或神经网络做分类的开发者,也适合做了几轮项目但总觉得编码环节“差点意思”的同行。
1. 为什么独热编码会成为机器学习流程里的“必做项”
1.1 模型无法直接理解“红黄蓝绿”这类文本标签
大多数机器学习算法,尤其是线性模型(逻辑回归、线性回归、SVM)和基于距离的模型(KNN、K-Means),在底层做的都是纯粹的数值运算:计算权重乘以特征、求样本之间的欧氏距离、或者在高斯分布上做概率推断。你扔给它一列值['红', '黄', '蓝', '绿'],它是真的无能为力——它不认识“红”这个汉字,也不知道红色和绿色谁更接近。
有人会觉得:那我直接标成0, 1, 2, 3不就行了?问题恰恰出在这里。如果你把类别编码成有序整数,模型就会把“3”理解为“比2大1、比1大2”的数值关系,也就是默认绿色与蓝色的距离,等同于蓝色与其他颜色的距离。可实际上这些颜色是纯粹的名义变量(Nominal Variable),彼此之间根本没有大小、先后、倍数关系。独热编码存在的根本意义,就是把“无序类别”转换为一组“互斥的布尔开关”,让算法在计算时既能把该类别纳入考量,又不会错误地引入顺序关系。
1.2 从计算机底层视角理解什么是“虚拟变量”
独热编码在统计学里其实对应的就是“虚拟变量”(Dummy Variable)思想。假设某个分类特征有K个不同取值,独热编码会给每个取值分配一个维度:属于该类别时该维度为1,其余为0。比如颜色特征有四种取值,则每一条样本经过独热编码之后会得到一个四维向量:
- 红色 →
[1, 0, 0, 0] - 黄色 →
[0, 1, 0, 0] - 蓝色 →
[0, 0, 1, 0] - 绿色 →
[0, 0, 0, 1]
这组向量有两个重要性质:第一,任意两个不同类别的向量点积为0,即完全正交,这说明模型认为类别之间“没有任何方向上的相关性”;第二,每条向量模长为1,且向量内部只有一个维度是1,所以训练数据里不会出现“既红又黄”这类不可能状态。
1.3 对“为什么不能直接标0,1,2,3”的再反驳
我知道肯定有人会说:“我做过实验,把类别标成整数,树模型的效果也不错。”这里要分清场景:对于决策树、随机森林这类基于划分的树模型,它们每次只关心一个阈值比较(如特征 > 0.5还是特征 <= 0.5),即便你把类别标成0到999的整数,树模型也只是在不同阈值间做切割,并不会主动把999解读为“997之后的下一个等级”。所以整型编码在树模型上常常不翻车。
但换成线性模型、神经网络、或者基于相似度的模型,整型编码就会立刻暴露问题。假设你在做一个商品推荐系统,用户年龄段有“儿童、少年、青年、中年、老年”,分别编码成0到4。模型拟合出来的权重是年龄特征权重 * 编码值,这个权重只能捕捉“编码值越大,点击率越高/越低”这种单调关系。一旦真实数据里“青年”用户购买力最强,中年与老年反而下降,单调假设就会让模型完全失真。独热编码则不受此限制,每个年龄段都有独立权重,模型可以自由学习出“青年权重高、中年权重低、老年权重高”这类非单调关系。
提示:如果你的特征本质是“有序类别”(如星级评价1星到5星、学历从小到大),独热编码并不是最优解。此时应保留整数编码或使用序数编码,让模型利用顺序信息。独热编码只适合处理“无名分、无排序”的纯粹类别。
2. 独热编码的底层逻辑:信息维度的膨胀与稀疏性
2.1 一个特征变成多个特征,本质是空间映射
在特征工程层面,独热编码做的事情是一次“特征空间重构”。原始的类别列只有一列,取值是任意字符串;编码后变成K列,每一列都是一个二元特征(0或1)。这意味着如果原始数据集有m个样本、n个特征,其中一个特征有K类,独热编码后特征数量变为n + K - 1(在去掉一个冗余列的情况下是n + K - 1,如果不处理共线性则是n + K)。
举个例子:你的数据集有10000个用户,特征包括年龄、性别、城市、会员等级。其中城市有50个取值,会员等级有5个等级。如果性别和会员等级都被独热编码,特征总量大约是1(年龄) + 1(性别编码成2列,去掉一列后1列有效) + 50(城市全量) + 5(会员等级全量)。这里有一个实操中必须注意的问题:城市编码成50列后,这50列里绝大多数是0,因为一个人只能住在其中一个城市。于是,每一行样本的有效信息密度急剧下降,存储上会形成一个高维稀疏矩阵。
2.2 稀疏性带来的数学含义:为什么常见算法依然能扛住
你可能会问:数据变成高维稀疏,模型层不会崩吗?实际上线性模型在处理高维稀疏特征上相当稳健。逻辑回归的参数更新公式里,稀疏特征对应的权重大多时候不会同时激活,训练速度虽然会稍微变慢,但只要有足够样本量,模型依旧能收敛。神经网络如果加了Embedding层,甚至可以把独热编码作为查表输入,再通过全连接层学习类别间的隐含关系。可见独热编码真正的成本不在训练精度,而在“维度爆炸”与“稀疏存储”上。
维度爆炸我在后面第4章会展开。稀疏存储方面,如果你用过稀疏矩阵库(比如scipy.sparse),应该知道一个100万行、5000列的矩阵,如果全部用稠密Numpy数组保存,需要1000000 * 5000 * 2字节(默认int16或者4字节float)大约10GB内存;但如果用CSR或CSC格式只存非零值位置,假设每行只有平均5个非零值,存储开销会缩小到1000000 * 5 * 4字节约20MB,降幅接近500倍。所以凡是做大规模线性模型,几乎都会用稀疏矩阵配合独热编码。
2.3 独热编码与特征间相关性的微妙关系
独热编码之后,还有一个隐蔽的问题——共线性(Multicollinearity)。假设一个特征有K个类别,编码后得到K列0/1向量。对任意一个样本,这K列里面恰好只有一列是1,其余都是0。把这K列横向加起来,会得到一个全为1的列向量。如果你的模型里还同时保留了一个“截距项”(intercept),那么截距项和这K列之间就存在严格的线性关系:截距列 = 类别1列 + 类别2列 + ... + 类别K列。这时候最小二乘法求出的系数矩阵可能不可逆,导致参数估计不稳定。
标准做法有两种:其一,在编码时设置drop='first'(丢掉第一个类别列),把参与回归的列数从K降至K-1,打破完全共线性;其二,保留全部K列,但模型里不使用截距项。前者在工业界更常用,因为丢掉的那一列信息可以通过“其余K-1列全为0”来隐式表达。我在做某金融风控项目时,当时一个特征有12个省份类别,如果全量保留且同时带截距,模型的正则化路径会非常震荡,后来统一改成drop-first后,权重稳定了很多。
3. 动手实现:从Sklearn到Pandas的完整操作与验算
3.1 最常被写成“错误示范”的Sklearn用法
又是OneHotEncoder,又是ColumnTransformer,很多初学者在Sklearn里会被接口搞晕。先分清两组对象:
sklearn.preprocessing.OneHotEncoder:专门做离散列编码,能输出稀疏矩阵,也支持pipeline。sklearn.preprocessing.LabelEncoder:只是把字符串转成整数编号,不能直接当作独热编码用。pandas.get_dummies:只在DataFrame范围里做独热编码,简单直接,但不直接支持稀疏矩阵。- 新版本
OneHotEncoder,可以直接加参数sparse_output=True,不需要再为它单独配置什么。
正确的Sklearn独热最小实现如下:
import pandas as pd from sklearn.preprocessing import OneHotEncoder data = pd.DataFrame({ 'color': ['red', 'blue', 'green', 'red', 'green'], 'size': ['S', 'M', 'L', 'XL', 'M'], }) encoder = OneHotEncoder(handle_unknown='ignore', sparse_output=True) encoded = encoder.fit_transform(data[['color', 'size']]) # 查看列名 print(encoder.get_feature_names_out()) # 输出: ['color_blue', 'color_green', 'color_red', 'size_L', 'size_M', 'size_S', 'size_XL'] print(encoded.shape) # 输出: (5, 7)注意三个细节:handle_unknown='ignore'表示如果测试集中出现训练时没见过的类别,不会被报错打断,而是全部编码为0向量;sparse_output=True避免生成稠密数组爆内存;fit_transform和transform一定不能混用,尤其要记得在训练集上fit之后,用同一个encoder去转换测试集。
3.2 用Pandas的get_dummies做快速版本
如果你只是想快速看效果、做数据分析,不打算上线大规模模型,用pd.get_dummies最省事:
import pandas as pd df = pd.DataFrame({'city': ['北京', '上海', '广州', '深圳', '北京']}) df_encoded = pd.get_dummies(df, columns=['city'], prefix='city', drop_first=False) print(df_encoded)输出会直接变成稀疏风格的DataFrame,每列名形如city_北京。这里的drop_first参数对应我第2.3节提到的共线性处理策略:设为True会丢掉第一个类别列。如果你用OneHotEncoder,指定drop='first'可以达到等价效果。
3.3 一个完整的管道示例:把独热编码串进Sklearn Pipeline
在实际项目中,编码往往不是孤立的:先切分数据集,再对训练集做编码,同时对数值列做标准化。强烈建议用ColumnTransformer把特征变换统一进一个Pipeline,避免测试集数据泄露。
from sklearn.compose import ColumnTransformer from sklearn.preprocessing import OneHotEncoder, StandardScaler from sklearn.pipeline import Pipeline from sklearn.linear_model import LogisticRegression categorical_features = ['color', 'city'] numeric_features = ['age', 'income'] preprocessor = ColumnTransformer( transformers=[ ('num', StandardScaler(), numeric_features), ('cat', OneHotEncoder(handle_unknown='ignore', drop='first'), categorical_features), ] ) model = Pipeline(steps=[ ('preprocessor', preprocessor), ('classifier', LogisticRegression(max_iter=1000)), ]) model.fit(X_train, y_train) test_score = model.score(X_test, y_test)这套写法的好处是:你在训练阶段调用的编码器,在预测阶段会原封不动地用同一套映射规则,不会因为某列类别顺序变化导致测试结果错乱。很多线上事故都是因为分开写编码逻辑时,训练时用pd.get_dummies、预测时又手动拼列名,导致特征列错位。
3.4 手动验算一次编码结果,理解“为什么这么设计”
下面我用一个很小的表手动推演一次,方便你彻底理解独热编码的内部流程。
| 原id | 颜色特征 |
|---|---|
| 1 | red |
| 2 | blue |
| 3 | green |
| 4 | red |
手工编码步骤:
- 统计颜色特征的所有取值,得到词典:
{red:0, blue:1, green:2}(这个词典的顺序通常是按出现频次或字母序排)。 - 为每个取值创建一列:
color_red,color_blue,color_green。 - 依次将每一行的对应位置置1,其余置0。最终得到:
| id | color_red | color_blue | color_green |
|---|---|---|---|
| 1 | 1 | 0 | 0 |
| 2 | 0 | 1 | 0 |
| 3 | 0 | 0 | 1 |
| 4 | 1 | 0 | 0 |
如果此时用l2正则逻辑回归训练,权重[w_red, w_blue, w_green]会被学习出来,模型对颜色的判断实际上等同于一个三分类子模型,颜色间的“非排序性”被充分尊重。
4. 当类别数量飙升:独热编码开始变味
4.1 高基类别(High-Cardinality)的典型场景
在做用户画像、广告点击率预估或电商推荐时,你常常会遇到这样的列:用户ID、设备ID、商品ID、IP地址、搜索词、城市、品牌。这些列的类别数量可能从几百到数百万不等。如果对每个ID都做独热编码,结果往往是灾难性的。例如用户ID有100万个不同值,独热编码就会产生100万个新特征列。训练一个逻辑回归时,每个特征列都对应一个权重,光权重矩阵就占几百万个参数,训练时间、内存、模型文件大小全部失控。
独热编码失效的真正信号有三个:
- 特征矩阵中非零元素占比低于0.1%,模型几乎是在“纯零海洋”里捞针;
- 很多类别只出现一两次,统计上根本不足以估计出可靠权重;
- 测试集中出现新ID的频次极高,
handle_unknown='ignore'变成了“全部未知归零”,独热编码等价于把新类别全部推给同一个向量。
4.2 频率编码(Count Encoding)是更务实的替代
面对高基数离散特征,我的第一选择往往是频率编码(Frequency / Count Encoding):统计每个类别在训练集中出现的次数,然后用这个次数(或次数归一化后的占比)替代原始类别。
import pandas as pd df = pd.DataFrame({'device_id': ['A', 'B', 'A', 'C', 'A', 'B', 'D']}) freq_map = df['device_id'].value_counts().to_dict() df['device_id_freq'] = df['device_id'].map(freq_map) print(df) # device_id: A->3, B->2, C->1, D->1这样做的核心逻辑是:高频类别往往代表主流用户行为,低频类别则可能是长尾或异常信号。模型只需学习一个连续特征,参数从K个压缩到1个,极大降低复杂度。必须注意的一点是:频率编码也会有数据泄露风险——如果某个类别只出现在测试集而没出现在训练集,map会得到NaN,需要额外填充test中未见过类别,通常填0或训练集整体频率的最小值。
4.3 Target Encoding(目标编码)的进阶逻辑
频率编码忽略了一个关键信息:每个类别与预测目标的相关性。比如预测用户是否点击广告,类别“某种设备类型”的点击率差异非常大,仅仅看出现次数是不够的。这时可以用目标编码(Target Encoding / Mean Encoding):用该类别下样本的目标均值来编码。
import pandas as pd df = pd.DataFrame({ 'city': ['北京', '上海', '北京', '广州', '上海', '北京'], 'click': [1, 0, 1, 0, 1, 0], }) city_mean = df.groupby('city')['click'].transform('mean') df['city_target'] = city_mean这里北京的平均点击率是(1+1+0)/3=0.667,上海是0.5,广州是0。目标编码之后类别就变成一个与预测目标强相关的连续值。但它的“坑”同样明显:如果某个类别只有一个样本,该样本又恰好是正例,那么类别编码值直接等于1,模型很容易过拟合。这就需要做平滑(Smoothing),用全局均值与类别均值的加权平均:编码 = 类别样本数 / (类别样本数 + m) * 类别均值 + m / (类别样本数 + m) * 全局均值。m是平滑参数,通常取5到20之间。更进一步,要通过交叉验证在训练集上分fold单独计算编码,避免信息泄漏。
4.4 哈希编码(Feature Hashing)与嵌入层(Embedding)
如果类别数量真的到了几十万乃至上百万,除了频率与目标编码外,工业界还常用两类手段:
第一类是哈希编码,把类别字符串通过哈希函数映射到一个固定长度的索引空间,例如把任意类别都映射到0到2^20之间的整数,再把对应位置置1。哈希编码本质上是“有损独热编码”,优点是维度固定,不需要维护类别表,适合在线学习流式数据;缺点是不同类别可能碰撞在同一个桶里,造成信息混淆。一般桶的数量设置为类别数的5到10倍,可以显著降低碰撞概率。
第二类是Embedding,即对独热编码结果再接一层可学习的嵌入层,把稀疏向量压缩成一个低维稠密向量。这实际上是在深度学习模型中最优雅的解法:不直接拿高维独热向量去做全连接,而是将其视为索引去查一张可训练嵌入表,输出可能是32维或64维。这样既保留了每个ID的独立语义,又压缩了维度。推荐系统算法中的物品向量、用户向量,本质上都是这条路径的产物。如果项目结构允许从传统机器学习切换到神经网络,用Embedding替代暴力独热是投资回报比最高的优化点。
5. 不同场景下的编码方案选择对照
5.1 按模型类型选编码
| 模型类型 | 推荐编码 | 原因 |
|---|---|---|
| 线性模型(逻辑回归、线性回归) | 独热编码 | 模型天然支持稀疏特征,独立权重能表达非单调关系 |
| 神经网络(MLP、Transformer) | 独热编码 + Embedding层 | 先独热再降维嵌入,避免高维全连接的参数爆炸 |
| 树模型(决策树、随机森林、Gradient Boosting) | 直接整型编码即可 | 树模型通过划分阈值处理类别,不依赖距离与顺序 |
| KNN / K-Means | 独热编码 | 距离度量要求类别维度是等权正交的 |
| 朴素贝叶斯(类别型) | 独热编码或概率编码 | 便于计算条件概率 |
有一个朴素的判断标准:只要模型内部涉及“特征值相乘累加”或“样本间距离计算”,独热编码通常最稳;如果模型只做“条件分支”,那独热编码的优势就没那么明显。
5.2 按类别数量选编码
| 类别数量范围 | 优先方案 | 备注 |
|---|---|---|
| 2~10 | 独热编码 | 直观,可直接解释权重 |
| 10~100 | 独热编码 / 频率编码 | 如果特征有业务排序含义,则考虑序数编码 |
| 100~1000 | 目标编码(需平滑)、频率编码 | 独热仍可用但需监控稀疏度 |
| 1000~100000 | 目标编码 + 交叉验证、哈希编码 | 避免单类别过拟合 |
| 大于100000 | 哈希编码 / Embedding | 独热编码基本不可行 |
这张表不是绝对的,但能帮你做初步判断。核心思想是:独热编码的适用边界不在于“能不能用”,而在于“值不值得用”。类别少、内存够、模型线性时,独热非常舒服;类别多,且预测目标有相关性可寻时,目标编码和哈希编码明显更有优势。
5.3 按业务解释性需求选编码
另一个常被忽略的维度是“可解释性”。在金融风控、医疗诊断等高合规场景,模型权重需要向业务方解释。独热编码可解释性最高:模型输出的每个类别特征权重可以直接翻译为“该类别相对基准类别的风险偏离”。目标编码虽然效果常常更好,但编码值是类别的目标统计量,业务方很难直观理解“城市特征值为0.82”的含义。哈希编码就更加不可解释了,基本上只能作为一个“黑盒”特征。遇到监管或合规审查项目时,我会尽量在准确率可接受范围内优先保留独热编码,必要时做降维与分组聚合来缩小类别规模,而不是一上来就上一堆压缩编码。
6. 独热编码在实操中最常见的“坑”与我的处理经验
6.1 训练集与测试集类别不一致
使用pd.get_dummies时,最容易踩的坑是:训练集里某个类别恰好没出现,而测试集里出现了。get_dummies只会基于当前传入的DataFrame的列取值生成哑变量列,所以训练和测试得到的列集合可能不一致。这种列错位在模型预测时会直接报“特征数量不一致”错误,或者更隐蔽地,某个类别被静默判断为“未知”。
我习惯的稳妥做法是用Sklearn的OneHotEncoder并设置handle_unknown='ignore',这样即使出现训练集未见过的类别,预测时也能输出全0向量,不会中断服务。如果你执意用pd.get_dummies,请先pd.concat([train, test], axis=0)构造全量列表,再自动切分,保证列完全一致。
6.2 缺失值在独热编码前后的处理
很多人在独热编码前忘记处理缺失值。如果类别列存在NaN,两种常见处理路径:
- 把
NaN当作一个特殊类别“未知”,在编码前先fillna('UNKNOWN'),再参与独热; - 或者直接丢弃含缺失值的行。
处理缺失值的决策,要根据缺失机制判断:如果是“无该设备”这类结构性缺失,保留“缺失类别”可能对模型有帮助;如果是随机记录缺失,且缺失率较高,单独保留一类会引入噪声。我的经验是:训练集缺失率高于5%时,建议单独保留一个缺失类别;低于5%,可以填充最频繁类别或直接drop。
6.3 独热编码之后的特征要不要标准化
有些同学把独热编码生成的0/1列和数值特征一起丢进StandardScaler。这其实是多余的:0/1列已经是标准化的了——均值介于0和1之间,方差恒定(p(1-p)),再做标准化反而抹平了“稀疏性”带来的好处,并且让矩阵变得更稠密。实践中应该像我在第3.3节那样,用ColumnTransformer把数值列做标准化,独热列只做编码,不再额外缩放。
6.4 独热编码后的维度爆炸怎么解
当你把独热编码和线性模型组合时,特征的维度可能高达几万维,这时有两个常用技巧:一是先做卡方检验(Chi-Square Test),剔除与目标变量独立性过高的类别列;二是对高频类别分组,把长尾合并为“其他”。在某个营销响应预测项目里,我遇到过品名特征上千个,但其中400多个类别总共只出现不到20次。把这些冷门品名统一合并为rare之后,独热列从1023列降到248列,模型AUC几乎没变化,训练时间却少了60%。这个处理思路不仅缓解了维度压力,也提高了低频类别对应权重的稳定性。
6.5 不要忘记正则化的重要性
一旦独热编码把特征扩到高维空间,过拟合风险会显著上升。线性模型在此时强烈建议启用L1或L2正则化。L1(Lasso)会把大量对目标影响微弱的类别权重压缩为0,天然起到特征选择作用,适合高维稀疏场景;L2(Ridge)则让所有权重都变小但非零,适合类别之间相关性较高的场景。实操上我会先跑一轮L1逻辑回归看哪些类别权重被清零,借此判断特征维度是否冗余,再切换L2获取更平滑的权重分布。这种“先用L1筛选,再用L2拟合”的组合拳,是我在高维独热特征上屡试不爽的方案。
7. 我用独热编码踩过的一次完整排查过程
7.1 线上推理特征列顺序错乱引发的“狗屎模型”
做某跨平台推荐系统时,我最初用pd.get_dummies处理用户偏好标签,训练完离线指标不错,上线后预测结果却完全乱套。排查链路如下:第一步,看日志,发现推理请求返回的召回物品与训练样本分布明显不同;第二步,对比特征列名,发现训练时的列顺序是label_A, label_B, label_C,而线上服务缓存的特征顺序却是label_B, label_A, label_C。虽然列名一模一样,但模型是按位置索引的,一旦顺序不同,所有权重就全错位了。第三步,修复方式:把编码器统一换成OneHotEncoder,并用固定categories参数显式指定类别顺序,彻底消除顺序不确定性。宕机总时长约两小时,深刻体会到“统一pipeline编码”不是工程洁癖,而是避免线上事故的刚需。
7.2 样本量不均导致的独热权重难以估计
另一个场景是广告点击率预估。品牌特征有大约2000个类别,但其中头部10个品牌占了80%的样本量。独热编码后,头部品牌对应的权重很快趋于稳定,但长尾品牌由于样本太少,权重要么逼近0,要么出现极端异常值。这时候如果不做类别合并,模型对长尾品牌的判断基本是瞎猜。我用“按出现频次排序,累积占比达到80%以上的类别保留,其余归为‘other’类别”的规则做压缩,再对合并后的类别重新独热编码,效果立竿见影:离线AUC从0.74提升到0.77,而且线上解释性也更好。
7.3 训练时忽略稀疏矩阵导致的OOM
还有一次,我在一个千万级样本的CTR模型里,把独热编码结果直接转换成了Numpy稠密数组来调试,结果进程瞬间被OOM杀掉。试了两个小时才发现是sparse_output没开。从那以后我把这条规则焊死在记忆里:只要特征总量大于1万列,一律使用稀疏矩阵,永远不要转成稠密形式去查看。真要调试,也只打印前几行转成小数组,而不是全量转置。
7.4 高基类别在逻辑回归上的权重偏移案例
做某用户画像分类任务时,原始特征“网页访问路径”有30000多种取值。起初用全量独热编码配合L2逻辑回归,模型训练了70多个epoch才收敛,且验证集波动较大。后来我改成“只保留访问量前1000的路径,其余合并为低频组”,维度降至2400列左右,训练时间减少80%,AUC竟然还稳定上升。这说明,高基类别做独热编码,真正有价值的往往就是头部少数类别,长尾对你不仅没有帮助,还会带入大量噪声。
8. 独热编码的实践经验总结与边界思维
独热编码是特征工程里最基础也最容易被低估的一环。它不性感,不花哨,但几乎每个入门项目都要用到。我的整体心得是:先判断特征是否属于名义类别,再评估类别数量级,最后结合模型类型决定方案。如果类别数少(比如几十个以内),模型又是线性模型或神经网络,独热编码直接上;如果类别数跨入上百上千,先做频次合并或目标编码,再考虑是否保留独热;一旦类别数万级起步,请尽早考虑Embedding或哈希编码。独热编码从来不是“非黑即白”的唯一解,它是整个编码工具箱的起点与基准,也是你理解其他所有高级编码方式的锚点。
还有一点想特别提醒:不要在网上抄一段get_dummies代码就直接套进自己的项目。务必检查列名顺序是否固定、是否有handle_unknown兜底、是否在Pipeline内部完成训练与预测一致性。我见过太多项目因为编码环节偷懒,最终模型上线后效果与离线指标严重对不上,而这根本不是模型算法的锅,纯粹是特征错位造成的系统性误差。
如果你现在正在做一个分类或推荐项目,不妨回头检查一下自己的特征处理代码:独热编码是否放对了位置、是否处理了未知类别、是否因为维度爆炸拖慢了训练。把这一层理顺,后面建模的体验会顺畅很多。