我刚开始做特征工程那段时间,一直没想明白一个问题:好好的“颜色”列,里面明明写着“红”“绿”“蓝”,为什么非要拆成三列,每行塞一堆 0 和 1?后来在同事的代码里看到 pd.get_dummies,又被推荐去读 scikit-learn 的 OneHotEncoder,才慢慢意识到这不是闲得没事干,而是很多线性模型、神经网络根本没法把“红”这样的字符串当输入。
One-Hot 编码成了我后来做分类特征处理时的默认选项之一。它的原理不复杂,但越用到后面,越觉得边界条件和细节才是真正拉开代码水平的地方:测试集出现了训练集没有的类别怎么办?几十个高基数特征一起 One-Hot 会不会把内存炸掉?为什么树模型反而不太需要它?这些都是实际项目里绕不开的问题。
这篇文章把我这几年用 One-Hot 的经验做一次系统梳理,从最底层的编码逻辑讲到训练集/测试集的正确切分,再到深度学习里 Embedding 和它的关系。适合刚入门特征工程的人通读,也适合写过不少代码但没系统整理过这类编码细节的同行查漏补缺。
1. 从“颜色=红”到“在_红这一列打1”:类别特征的本质问题
1.1 有些数字天生有序,有些数字纯属代号
机器学习模型的数学内核,决定了它只能消化数值。逻辑回归算的是 $w^Tx+b$,神经网络做的是矩阵乘法,它们的输入张量里每一个位置都是数字。所以任何特征进入模型之前,都得经历一次“从现实世界到数值空间”的翻译。
关键是这个翻译不能乱来。数值特征好办,年龄 25、收入 18000,本身就有大小,原样喂进去即可。类别特征就麻烦了:城市、性别、产品 ID、职业、型号,它们的取值是一个个离散的标签,不是数。
以颜色为例,如果直接把“红”“绿”“蓝”映射成 1、2、3,看起来是数值了,模型也能跑,但这种映射隐含了一个假设——绿色是红色和蓝色的中间值,1 和 3 之间的距离比 1 和 2 之间更远。可颜色之间哪来的距离?这种编造出来的大小关系,会直接污染线性模型的权重学习。
我在实际项目里踩过一次很深的坑。那时候做用户标签,把学历映射成了 0、1、2、3、4,分别对应小学、初中、高中、本科、研究生。当时觉得“顺序没错,越高越大”,逻辑回归跑出来的系数也很显著,直到业务方问“为什么这个特征的系数是负的,学历越高转化越低”才意识到,问题不在业务端,而在我把类别做了带顺序的编码。
真实的情况是:本科和高中可能一个在 2 一个在 3,高中学历用户占比极低,样本稀疏导致模型在“编号 2”这个点上学到了噪声模式,连带着把“编号越大越好”的假设也放大成了负相关。
One-Hot 编码解决的就是这种假顺序问题。它把每个类别当成空间中独立的一根轴,“红”不再是一个数字,而是一个向量:红色那根轴为 1,其他轴为 0。这样任意两个类别之间的距离都是等价的,模型不会去学并不存在的顺序。
1.2 为什么 Label Encoding 在这里会“带偏”模型
把类别变成 0、1、2、3 这类整数,业内有一个正式叫法:Label Encoding 或 Ordinal Encoding。它在两个场景下完全没问题:
- 类别本身有明确的顺序,比如“小、中、大”、“低、中、高”;
- 下游使用树模型,而树模型通过阈值切分时,对特征张成的空间没有距离假设。
但一旦遇到线性回归、逻辑回归、SVM、神经网络这类基于距离计算的模型,Label Encoding 的代价就来了——“红=0,绿=1,蓝=2”会让模型认为蓝色和绿色的距离小于蓝色和红色的距离。可真实世界里这可能完全是错的。
有个比颜色更典型的例子是星期几。周一编码为 1、周二为 2……周日为 7,看似自然,但周一和周日之间难道就差 6 个单位距离吗?如果任务是预测某种销售波动,周一和周日往往是两个极端。更好的做法是拆成多个二值特征:is_monday, is_sunday 等,让模型自己去捕捉周一、周日的独立效应。
所以,One-Hot 的核心价值不是把类别变成数,而是把无大小之分的类别投影到正交的向量空间,每个类别独占一个维度,彻底消除伪顺序影响。
1.3 One-Hot 的直观形态:一本书放一个房间
如果从直觉上理解,可以把 One-Hot 想象成图书馆的藏书方式。假设有 1000 类书,编号 1 到 1000。普通编码就像把所有书按编号排在一排书架上,要找第 500 本,你先得走过前 499 本,位置和顺序有关系。
One-Hot 的做法更像给每类书盖一个独立房间:房间门口写着“第 i 类”,房间里只放这一类的书。一本书要存进去,就在对应房间打个勾。这样每个房间之间没有任何顺序,谁也不比谁靠前。
从特征角度来看,一个取值恰好命中某个类别,该类别对应的列就成为样本的“标记位”。这也是为什么有些人叫它哑变量或虚拟变量,在统计学和计量经济学里,这几乎就是同一个东西,只是叫法不同。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 编码的算术:维度怎么定、稀疏矩阵从哪来
2.1 表格长宽怎么变:一个特征 n 个类别,产出 n 个 0/1 列
对一个类别特征做 One-Hot,最直观的效果是:一列变成多列,列数等于该特征的唯一类别数。
拿一个 5 行的小数据集举例:
| 用户ID | 颜色 |
|---|---|
| 1 | 红 |
| 2 | 蓝 |
| 3 | 绿 |
| 4 | 红 |
| 5 | 绿 |
这里的唯一值集合是“红、绿、蓝”三个,One-Hot 后变成:
| 用户ID | 颜色_红 | 颜色_绿 | 颜色_蓝 |
|---|---|---|---|
| 1 | 1 | 0 | 0 |
| 2 | 0 | 0 | 1 |
| 3 | 0 | 1 | 0 |
| 4 | 1 | 0 | 0 |
| 5 | 0 | 1 | 0 |
这是最常用的方案。注意我在生成列名时带上了原始特征名“颜色”,就是为了避免多个特征各自有“红”“绿”时发生列名冲突。
每个特征的独热维度 = 每个列唯一值的个数,各特征之间互不干扰。 例如用户画像里性别有 2 类、职业有 10 类、城市有 50 类,那 One-Hot 后新增的列数就是 2 + 10 + 50 = 62,而不是它们相乘。这点和交叉特征不同,交叉特征才是维度爆炸的重灾区。
生成 One-Hot 的方式有两种细微差别:
pd.get_dummies直接作用于 DataFrame,傻瓜式调用,把包含类别特征的列自动转换;sklearn.preprocessing.OneHotEncoder更像是传统机器学习 API,需要fit再transform,适合放进 Pipeline。
后面我会详细写它们的差异和选择策略,这里先有个概念即可。
2.2 编码后的矩阵为什么几乎全是 0
上面 5 行 3 列的颜色编码矩阵,0 的数量是 10 个,1 的数量是 5 个,稀疏率已经接近 67%。当类别数量上升、单个样本只能命中一个类别时,0 的比例会无限逼近 100%。
这也是 One-Hot 结果适合用稀疏矩阵存储的原因。比如一个特征有 1 万个类别,样本量是 100 万,如果全部展开成 100 万 × 1 万的稠密矩阵,需要的浮点数存储空间是 100 亿个数。按照单精度浮点 4 字节算,大约是 40 GB,很多单机训练脚本直接内存爆炸。
但实际非零值只有 100 万个——每个样本只有一个 1。用稀疏格式(CSR 或 COO)存储,只记录非零位置,占用的空间几乎只和样本量线性相关,而不是和“类别数 × 样本量”相关。
很多新手在这里会忽略:跑通了 pd.get_dummies 不代表万事大吉,得到的 DataFrame 背后是稠密矩阵,在类别少的时候没问题,类别一多就迅速失控。而 OneHotEncoder(sparse_output=True) 会直接返回稀疏矩阵,后面的模型训练也能感知稀疏结构,这是实践里很重要的一点。
2.3 高基数陷阱:当类别出现成千上万时,需要考虑其他方案
One-Hot 的“一个类别一根轴”是它的优点,也是它的软肋。类别数量少,比如性别、学历、产品线,One-Hot 又干净又可解释;一旦特征是高基数(high cardinality),比如城市有 500 个取值、IP 段有几十万个取值,直接 One-Hot 就会带来三连击:
- 维度灾难:几百列还算轻,几万列会让很多线性模型的训练和推理显著变慢;
- 样本稀疏:大量类别只有零星几个样本,这些类别对应的列几乎全 0,模型学不到可靠权重;
- 存储与性能问题:即使使用稀疏矩阵,下游如果转成稠密格式,内存依然会爆。
面对高基数特征,更多的通用方案包括:
- Frequency Encoding / Count Encoding:用每个类别在训练集里出现的次数或频率替代类别名,把一维信息压缩成一列连续值;
- Target Encoding:用类别对应的目标变量均值(可加平滑)替代类别,直接把类别和标签的关系编码进来;
- Hash Encoding / Feature Hashing:把类别名 hash 到固定长度空间,例如 2^20 维,用哈希碰撞换取维度可控,适合在线学习和大规模 CTR 预估;
- Embedding:把 One-Hot 作为输入层,通过神经网络学习低维稠密表征,本质上是对 One-Hot 向量做压缩投影。
所以我的经验是:One-Hot 不是“银弹”,它是基线方案中的默认选项。 先用它跑通 baseline,再根据维度规模决定是否迭代为编码方式更精巧的方案。
3. 手写一遍 + 库函数调用:One-Hot 实现的三种方式
3.1 用 list 和 dict 手写一个极简编码器
虽然库函数覆盖了大部分场景,但手写一遍能帮你把原理刻进脑子里——尤其对“如何保持一致映射”“新类别来了怎么办”的感受会完全不同。
假设我们有一组训练数据:
python复制colors_train = ["红", "蓝", "绿", "红", "绿"]
第一步,确定所有类别并建立映射。为了保证顺序一致,可以用有序结构:
python复制categories = sorted(set(colors_train))
# ["红", "绿", "蓝"],有序排列;sorted 对中文可能按 Unicode 码点排,实际工作中无所谓,只要固定即可
index_map = {cate: i for i, cate in enumerate(categories)}
# {"红": 0, "绿": 1, "蓝": 2}
第二步,将每个样本映射成向量:
python复制import numpy as np
def one_hot_encode(values, index_map):
matrix = np.zeros((len(values), len(index_map)), dtype=int)
for row, val in enumerate(values):
if val not in index_map:
raise ValueError(f"未知类别: {val}")
matrix[row, index_map[val]] = 1
return matrix
train_matrix = one_hot_encode(colors_train, index_map)
打印 train_matrix 会得到:
text复制[[1, 0, 0],
[0, 0, 1],
[0, 1, 0],
[1, 0, 0],
[0, 1, 0]]
可以看到,第 0 列代表红色,第 1 列代表绿色,第 2 列代表蓝色。如果测试集里出现了训练集没见过的“黄”,这套极简编码器会直接抛异常。
这正是后面要用 sklearn.OneHotEncoder(handle_unknown='ignore') 的原因之一。手写版的最大价值,是让你清楚意识到“默认状态下未知类别是会报错的”,而不是自动忽略或生成新列。
3.2 pd.get_dummies 还是 OneHotEncoder:看起来像,实际有区别
最常用的两个工具:
python复制import pandas as pd
df = pd.DataFrame({"颜色": ["红", "蓝", "绿"]})
df_dummies = pd.get_dummies(df, columns=["颜色"])
print(df_dummies)
输出:
text复制 颜色_红 颜色_蓝 颜色_绿
0 1 0 0
1 0 1 0
2 0 0 1
columns 参数指定处理哪几列;如果不指定,默认对所有 object 或 category 类型的列处理。
再看 sklearn 版本:
python复制import numpy as np
from sklearn.preprocessing import OneHotEncoder
colors = np.array(["红", "蓝", "绿"]).reshape(-1, 1)
encoder = OneHotEncoder(sparse_output=False) # 新版本用 sparse_output;旧版是 sparse
encoded = encoder.fit_transform(colors)
print(encoded)
print(encoder.categories_)
输出类似:
text复制[[1. 0. 0.]
[0. 1. 0.]
[0. 0. 1.]]
[array(['红', '蓝', '绿'], dtype='<U1')]
两者都会做 One-Hot,但有几个重要差异,用错代码风格倒不会翻车,但对工程化影响不小。
| 对比维度 | pd.get_dummies |
sklearn.OneHotEncoder |
|---|---|---|
| 是否记住训练集的类别集合 | 不记住,每次调用都重新看数据 | fit 后记住,transform 时严格按记忆编码 |
| 测试集出现新类别 | 自动新增一列,导致训练/测试特征列不一致 | 可设 handle_unknown='ignore',新类别对应全 0 向量 |
| 返回值 | pandas DataFrame | numpy 数组或 scipy 稀疏矩阵 |
| Pipeline 集成 | 一般需要 ColumnTransformer 包裹 |
可直接放进 Pipeline |
| 对字符串/数值一视同仁 | 是,object、category 都能处理 | 不管数值还是文本,都当类别处理 |
| 稀疏支持 | 没有原生稀疏选项 | 默认可输出稀疏矩阵,省内存 |
平时做快速分析和临时脚本,用 get_dummies 很爽;但在真正的建模流程里,我推荐 OneHotEncoder。
最大的理由是它天然适配“训练集 fit、测试集 transform”的规范。你用 get_dummies 拼接训练集和测试集后,如果两边同一特征的取值集合不同,生成的列数也会不同,模型训练完再 predict 测试集时会报特征数不一致;而 OneHotEncoder 在训练集上 fit 出类别清单后,测试集再怎么变化,编码后的列结构都保持不变。
具体使用场景可以用下面代码展示一个标准训练流程:
python复制import pandas as pd
from sklearn.preprocessing import OneHotEncoder
from sklearn.linear_model import LogisticRegression
train_df = pd.DataFrame({
"color": ["红", "蓝", "绿", "红"],
"label": [1, 0, 1, 0]
})
test_df = pd.DataFrame({
"color": ["红", "黄"] # 注意出现了“黄”
})
encoder = OneHotEncoder(handle_unknown="ignore")
X_train = encoder.fit_transform(train_df[["color"]]) # 稀疏矩阵
X_test = encoder.transform(test_df[["color"]])
print(encoder.categories_)
print(X_test.toarray()) # 黄色那一行全 0
运行后会看到测试集第二行样本 [0, 0, 0],因为“黄”在训练集里没出现过,handle_unknown='ignore' 让它变成了一个所有独热位都为 0 的向量。这样特征对齐的问题就被绕开了。
3.3 fit/transform 机制:训练集和测试集为什么不能分开 fit
初学的时候,很多人会不小心犯一个错误——对测试集单独调用 encoder.fit_transform。这在代码层面不报错,但本质上犯了“数据泄露”的大忌。
特征编码器在训练阶段学到的东西,也就是类别映射表,应该被视为模型参数的一部分。如果测试集也参与 fit,它会根据测试集的分布重新构造类别集合,甚至在测试集新增一个“黄”时多生成一列,打破训练/测试特征结构的一致性。
我做数据竞赛时常听到一句话:“测试集是看一眼就输牌。”对测试集单独 fit,相当于偷看了测试集里有哪些类别。虽然类别本身不包含标签信息,但它会让模型在线上遇到新类别时的行为评估失真——你测的不是模型在“没见过黄”时的表现,而是“见过黄”之后的表现。
所以正确做法永远是:
python复制encoder = OneHotEncoder(handle_unknown="ignore")
X_train_encoded = encoder.fit_transform(X_train)
X_test_encoded = encoder.transform(X_test)
让测试集只走 transform,不参与映射表的构建。
用 pd.get_dummies 做快速验证时,可以采用一个简单的替代策略:在建模任务里,直接把训练集和测试集纵向拼接,让 get_dummies 同时看到两边类别,然后再分离。但这个方法只适合临时实验,正式训练里容易引入不必要的依赖,能改用 OneHotEncoder 就尽量改。
4. 实战排坑:测试集出现新类别、维度爆炸和树模型之谜
4.1 新类别出现在线上,怎么办
这是 One-Hot 在真实业务中出现频率最高的问题。训练集来自某一个时间段,模型上线后第二个月,后台数据表里新冒出一个商家类型、一个新品类 ID、一个新地区,编码器一查映射表,发现没这个键。
这时候有几个梯队式的解决思路:
方案一:忽略新类别
OneHotEncoder(handle_unknown='ignore') 将未知类别编码成全 0 向量。这意味着模型既无法区分“未知”与“缺失”,也无法单独学习“未知类别”的权重。但至少保证推断不崩溃,特征维度稳定。
方案二:给未知类别一个专门的分桶
在 fit 之前手动加一个兜底类别,比如把所有低频类别统一改成“其他”:
python复制def bucket_rare_categories(series, min_count=10):
count = series.value_counts()
rare = count[count < min_count].index
return series.replace(rare, "其他")
这样模型会生成一个“其他”对应的独热位,能学到这部分样本的平均效应。线上遇到新类别时,你也能手动把新值替换成“其他”,保持映射一致。
方案三:业务侧订阅新类别清单
如果是较稳定的枚举特征(比如国家代码),最简单可靠的做法是定期更新训练数据并重新 fit。新类别上线后,尽快触发重新训练,而不是长期依赖忽略策略。
我在实际项目里更偏爱“兜底桶 + 定期更新”的组合。忽略策略虽然简单,但它让模型对新类别完全无感知,如果新类别背后对应一种不同的用户行为模式,全 0 向量会让预测结果偏移。
4.2 维度爆炸的应对思路
做用户运营分析的时候,我遇到过这样一组特征:城市(400+ 类)、设备型号(300+ 类)、注册渠道(80+ 类)、职业(60+ 类)。如果全部 One-Hot,特征数量轻松超过 1000。配合本来就不大的样本量,逻辑回归很容易过拟合。
应对维度爆炸,没有一刀切的答案,更多是一套组合策略:
先粗粒度合并。
- 把具体城市合并为省份或一线/二线/三线层级;
- 把设备型号合并为品牌或操作系统,甚至只保留 TOP 20 品牌,其余进“其他”;
- 把低频类别替换成“其他”是最高频也最直观的降基手段。
再考虑用频次编码替代独热位。
频次编码用出现次数/频率作为唯一数值,将所有类别压缩成一列。这种编码方式对线性模型效果通常不错,因为高频类别往往对应大量样本和更稳定的统计规律,模型能利用频率数字捕捉这一点。
常见实现需要保证同一份训练集和测试集能共用频率映射表。
python复制train_count = train_df["city"].value_counts()
train_df["city_freq"] = train_df["city"].map(train_count) / len(train_df)
test_df["city_freq"] = test_df["city"].map(train_count).fillna(0) / len(train_df)
最后才考虑更复杂的哈希或嵌入方法。
如果你的模型是逻辑回归,但特征池子又大到没法直接 One-Hot,使用 Hash Trick 可以设定一个固定维度,比如 2 的幂次方,这种做法在很多大规模点击率预估系统中是标配。
如果样本量足够、训练资源也充裕,把类别特征 Embedding 化通常是效果最强也是最费资源的路子,这一点我在第 5 节展开。
4.3 树模型:不是所有模型都需要 One-Hot
很多教程会给人一种“类别特征必须先 One-Hot 才能进模型”的错觉。实际上,如果下游是决策树、随机森林、XGBoost、LightGBM 这类树模型,One-Hot 未必是最优选择。
树模型执行的是分裂规则。它可以在某个节点问“颜色 是否等于 红”,一步就把三种颜色拆开,不需要把颜色预编码成多个二值列。尤其是一些树模型框架原生支持类别特征,LightGBM 直接传 categorical_feature 参数,XGBoost 新版本也加入了 enable_categorical。
如果硬要树模型处理很多 One-Hot 列,反而可能出现以下问题:
- One-Hot 后的列非常稀疏,树模型在其中寻找分裂点时计算成本上升;
- 深度限制下,树可能没有足够的分裂次数去充分利用高基数类别的全局结构;
- 对高基数类别直接 One-Hot 甚至会增加过拟合风险,因为树可以轻松切出某个在训练集里极端的小类别分支。
所以,在特征工程里要建立“模型感知”思维。树模型和线性模型对类别特征的期望是不同的,一个是看重非线性分裂,一个看重线性距离。不能拿着一把锤子到处砸。
5. 当 One-Hot 遇见深度学习:Embedding 的前身
5.1 从 One-Hot 到 Embedding:查表背后的线性变换
在深度学习中,类别特征不会直接以很宽的 0/1 向量一路往后传,而是把 One-Hot 向量当作索引,通过一个 Embedding 层把它压缩成低维稠密向量。
很多初学者会被 Embedding 这个名字唬住,用一句大实话解释:Embedding 层的权重矩阵就是一个大查表器;输入是稀疏 One-Hot 向量,本质上是在选第几行;权重矩阵的第 i 行就是类别 i 的稠密向量。
例如用户 ID 有 10000 个,若做 One-Hot 后接全连接层,第一层参数就是 10000 × 256 = 256 万,这在显存和训练时间上都很吃力。但 Embedding 层只保留那张 10000 × 256 的表,不显式构造 One-Hot 向量,而是直接用类别索引取行,效果等价于“稀疏 One-Hot 乘权重矩阵”,成本却小得多。
也就是说,One-Hot 并没有消失,它仍然是深度学习里类别变量通往数值空间的“协议”,只是框架内部不再真的展开成一万个 0/1,而是借用索引直接查表。理解这一层,就能明白为什么说 Embedding 是 One-Hot 的自然延伸。
5.2 文本的 One-Hot 式表示:词袋模型的意义与局限
在 NLP 里,One-Hot 思想同样出现过。最早的文本分类常用词袋模型,把每个词当作一维,文档的表示是一个长度等于词表大小的向量,某词出现几次就在对应位置累加几。这就是 One-Hot 的计数版本。
词袋模型的局限性十分明显:词表大时维度太高、忽略词序、丢失语义。但在短文本分类、垃圾信息识别等任务中,它配合线性模型依然能获得不错的基线。
后来 Word2Vec、GloVe 等词向量方法出现,本质上是把每个词的 One-Hot 向量压缩成几百维稠密向量,让语义相近的词在向量空间里彼此靠近。这种思路,正是第 5.1 节里 Embedding 在文本场景的具体应用。
5.3 从独热位看特征重要性:别丢了解释性
深度学习和 Embedding 虽然能力强,但在带有强解释性要求的业务场景里(风控、医疗、金融),One-Hot 后的稀疏特征配合线性模型或浅层模型,仍是主流的解释性锚点。
每一个 One-Hot 列都对应一个“是/否”的语义判断,比如“用户是否来自上海”“用户是否为钻石会员”。逻辑回归的权重可以直接解读为该类别的对数几率增量,业务方也能清楚知道模型看重哪些类别。
一旦全部改成数不清的稠密特征,解释性就会变成黑盒,后期做模型审计、公平性检验时也会很麻烦。所以不是所有项目都适合追逐更“高级”的编码方式,One-Hot 在简单性和可解释性上的优势在很多领域依然是刚需。
6. 沿用 One-Hot 时我自己的几个习惯和踩坑总结
最后把这几年的实践体会浓缩成几条,放在这里给后来者参考。
One-Hot 编码的上手成本极低,但它真正的门槛在于知道什么时候该用、什么时候不该用、用了之后会引发哪些连锁问题。
我在项目里习惯这样做:
第一,每个类别特征在建特征池时就评估它的类别数量。 小于几十个类别的特征直接默认 One-Hot,构造简单且可解释;几百到上千个类别的特征会先看样本量和下游模型再决定,优先尝试频次编码或小组件嵌入;上万级别则基本放弃朴素 One-Hot,直接走哈希或 Embedding 路线。
第二,始终把编码器纳入整个机器学习 Pipeline,而不是手工分散处理。 OneHotEncoder 配合 ColumnTransformer 最舒服。这样在交叉验证时,每个 fold 的编码器只在训练折内 fit,验证折不会偷看类别分布,能减少编码层面带来的数据泄漏。
第三,对类别列表存入文件备份。 sklearn 的 encoder.categories_ 会给出每个特征的类别顺序,我在建模完成后会把它们存成 JSON 或 pickle。线上推理时如果编码器版本和训练时不一致,经常会出现特征列对不上的诡异 bug,有一份固化文件就能快速排查。
第四,One-Hot 后的全 0 向量不一定代表“未知”,也可能代表“缺失”。 如果数据本身就存在空值,最好在处理流程里保留“缺失”这个显式类别,而不是让缺失值在填充后误打误撞进入某个默认类别。区分“缺失”和“未知”,能帮你在模型评估时定位更多问题。
再提醒一个容易被忽略的性能点:pd.get_dummies 在几万行数据上很快,但到了千万行级别,它的内存开销和耗时都会变得明显。换成 OneHotEncoder(sparse_output=True),并用稀疏矩阵喂给线性模型,整体训练时间可以大幅下降。如果你在某个大表上被 One-Hot 卡到 OOM,优先检查是不是还在用稠密格式。
One-Hot 编码形态简洁、落地直接、可解释性强,它既不新鲜,也不花哨,但理解它的边界、取舍和与各类模型的配合方式,依然是进入特征工程世界的必修课。希望这篇梳理能帮你在自己的项目里把这一步走稳。
