高基数类别特征预处理:平均数编码 | 京东云技术团队

一 前言

对于一个类别特征,如果这个特征的取值非常多,则称它为高基数(high-cardinality)类别特征。在深度学习场景中,对于类别特征我们一般采用Embedding的方式,通过预训练或直接训练的方式将类别特征值编码成向量。在经典机器学习场景中,对于有序类别特征,我们可以使用LabelEncoder进行编码处理,对于低基数无序类别特征(在lightgbm中,默认取值个数小于等于4的类别特征),可以采用OneHotEncoder的方式进行编码,但是对于高基数无序类别特征,若直接采用OneHotEncoder的方式编码,在目前效果比较好的GBDT、Xgboost、lightgbm等树模型中,会出现特征稀疏性的问题,造成维度灾难, 若先对类别取值进行聚类分组,然后再进行OneHot编码,虽然可以降低特征的维度,但是聚类分组过程需要借助较强的业务经验知识。本文介绍一种针对高基数无序类别特征非常有效的预处理方法:平均数编码(Mean Encoding)。在很多数据挖掘类竞赛中,有许多人使用这种方法取得了非常优异的成绩。

二 原理

平均数编码,有些地方也称之为目标编码(Target Encoding),是一种基于目标变量统计(Target Statistics)的有监督编码方式。该方法基于贝叶斯思想,用先验概率和后验概率的加权平均值作为类别特征值的编码值,适用于分类和回归场景。平均数编码的公式如下所示:

其中:

1. prior为先验概率,在分类场景中表示样本属于某一个_y__i_的概率

​其中_n__y__i_​​表示y =_y__i_​时的样本数量,_n__y_​表示y的总数量;在回归场景下,先验概率为目标变量均值:

2. posterior为后验概率,在分类场景中表示类别特征为k时样本属于某一个_y__i_​的概率

在回归场景下表示 类别特征为k时对应目标变量的均值。

3. _λ_为权重函数,本文中的权重函数公式相较于原论文做了变换,是一个单调递减函数,函数公式:

其中 输入是特征类别在训练集中出现的次数n,权重函数有两个参数:

① k:最小阈值,当n = k时,λ= 0.5,先验概率和后验概率的权重相同;当n < k时,λ> 0.5, 先验概率所占的权重更大。

② f:平滑因子,控制权重函数在拐点处的斜率,f越大,曲线坡度越缓。下面是k=1时,不同f对于权重函数的影响:

由图可知,f越大,权重函数S型曲线越缓,正则效应越强。

对于分类问题,在计算后验概率时,目标变量有C个类别,就有C个后验概率,且满足

一个 _y__i_​ 的概率值必然和其他 _y__i_​ 的概率值线性相关,因此为了避免多重共线性问题,采用平均数编码后数据集将增加C-1列特征。对于回归问题,采用平均数编码后数据集将增加1列特征。

三 实践

平均数编码不仅可以对单个类别特征编码,也可以对具有层次结构的类别特征进行编码。比如地区特征,国家包含了省,省包含了市,市包含了街区,对于街区特征,每个街区特征对应的样本数量很少,以至于每个街区特征的编码值接近于先验概率。平均数编码通过加入不同层次的先验概率信息解决该问题。下面将以分类问题对这两个场景进行展开:

1. 单个类别特征编码:

在具体实践时可以借助category_encoders包,代码如下:

import pandas as pd
from category_encoders import TargetEncoderdf = pd.DataFrame({'cat': ['a', 'b', 'a', 'b', 'a', 'a', 'b', 'c', 'c', 'd'], 'target': [1, 0, 0, 1, 0, 0, 1, 1, 0, 1]})
te = TargetEncoder(cols=["cat"], min_samples_leaf=2, smoothing=1)
df["cat_encode"] = te.transform(df)["cat"]
print(df)
# 结果如下:cat	target	cat_encode
0	a	1	0.279801
1	b	0	0.621843
2	a	0	0.279801
3	b	1	0.621843
4	a	0	0.279801
5	a	0	0.279801
6	b	1	0.621843
7	c	1	0.500000
8	c	0	0.500000
9	d	1	0.634471

2. 层次结构类别特征编码:

对以下数据集,方位类别特征具有{‘N’: (‘N’, ‘NE’), ‘S’: (‘S’, ‘SE’), ‘W’: ‘W’}层级关系,以compass中类别NE为例计算_y__i_​=1,k = 2 f = 2时编码值,计算公式如下:

其中_p_1为HIER_compass_1中类别N的编码值,计算可以参考单个类别特征编码: 0.74527,posterior=3/3=1,λ= 0.37754 ,则类别NE的编码值:0.37754 * 0.74527 + (1 - 0.37754)* 1 = 0.90383。

代码如下:

from category_encoders  import TargetEncoder
from category_encoders.datasets import load_compassX, y = load_compass()
# 层次参数hierarchy可以为字典或者dataframe
# 字典形式
hierarchical_map = {'compass': {'N': ('N', 'NE'), 'S': ('S', 'SE'), 'W': 'W'}}
te = TargetEncoder(verbose=2, hierarchy=hierarchical_map, cols=['compass'], smoothing=2, min_samples_leaf=2)
# dataframe形式,HIER_cols的层级顺序由顶向下
HIER_cols = ['HIER_compass_1']
te = TargetEncoder(verbose=2, hierarchy=X[HIER_cols], cols=['compass'], smoothing=2, min_samples_leaf=2)
te.fit(X.loc[:,['compass']], y)
X["compass_encode"] = te.transform(X.loc[:,['compass']])
X["label"] = y
print(X)# 结果如下,compass_encode列为结果列:index	compass	HIER_compass_1	compass_encode	label
0	1	N	N	0.622636	1
1	2	N	N	0.622636	0
2	3	NE	N	0.903830	1
3	4	NE	N	0.903830	1
4	5	NE	N	0.903830	1
5	6	SE	S	0.176600	0
6	7	SE	S	0.176600	0
7	8	S	S	0.460520	1
8	9	S	S	0.460520	0
9	10	S	S	0.460520	1
10	11	S	S	0.460520	0
11	12	W	W	0.403328	1
12	13	W	W	0.403328	0
13	14	W	W	0.403328	0
14	15	W	W	0.403328	0
15	16	W	W	0.403328	1

注意事项:

采用平均数编码,容易引起过拟合,可以采用以下方法防止过拟合:

  • 增大正则项f
  • k折交叉验证

以下为自行实现的基于k折交叉验证版本的平均数编码,可以应用于二分类、多分类、回归场景中对单一类别特征或具有层次结构类别特征进行编码,该版本中用prior对unknown类别和缺失值编码。

from itertools import product
from category_encoders  import TargetEncoder
from sklearn.model_selection import StratifiedKFold, KFoldclass MeanEncoder:def __init__(self, categorical_features, n_splits=5, target_type='classification', min_samples_leaf=2, smoothing=1, hierarchy=None, verbose=0, shuffle=False, random_state=None):"""Parameters----------categorical_features: list of strthe name of the categorical columns to encode.n_splits: intthe number of splits used in mean encoding.target_type: str,'regression' or 'classification'.min_samples_leaf: intFor regularization the weighted average between category mean and global mean is taken. The weight isan S-shaped curve between 0 and 1 with the number of samples for a category on the x-axis.The curve reaches 0.5 at min_samples_leaf. (parameter k in the original paper)smoothing: floatsmoothing effect to balance categorical average vs prior. Higher value means stronger regularization.The value must be strictly bigger than 0. Higher values mean a flatter S-curve (see min_samples_leaf).hierarchy: dict or dataframeA dictionary or a dataframe to define the hierarchy for mapping.If a dictionary, this contains a dict of columns to map into hierarchies.  Dictionary key(s) should be the column name from Xwhich requires mapping.  For multiple hierarchical maps, this should be a dictionary of dictionaries.If dataframe: a dataframe defining columns to be used for the hierarchies.  Column names must take the form:HIER_colA_1, ... HIER_colA_N, HIER_colB_1, ... HIER_colB_M, ...where [colA, colB, ...] are given columns in cols list.  1:N and 1:M define the hierarchy for each column where 1 is the highest hierarchy (top of the tree).  A single column or multiple can be used, as relevant.verbose: intinteger indicating verbosity of the output. 0 for none.shuffle : bool, default=Falserandom_state : int or RandomState instance, default=NoneWhen `shuffle` is True, `random_state` affects the ordering of theindices, which controls the randomness of each fold for each class.Otherwise, leave `random_state` as `None`.Pass an int for reproducible output across multiple function calls."""self.categorical_features = categorical_featuresself.n_splits = n_splitsself.learned_stats = {}self.min_samples_leaf = min_samples_leafself.smoothing = smoothingself.hierarchy = hierarchyself.verbose = verboseself.shuffle = shuffleself.random_state = random_stateif target_type == 'classification':self.target_type = target_typeself.target_values = []else:self.target_type = 'regression'self.target_values = Nonedef mean_encode_subroutine(self, X_train, y_train, X_test, variable, target):X_train = X_train[[variable]].copy()X_test = X_test[[variable]].copy()if target is not None:nf_name = '{}_pred_{}'.format(variable, target)X_train['pred_temp'] = (y_train == target).astype(int)  # classificationelse:nf_name = '{}_pred'.format(variable)X_train['pred_temp'] = y_train  # regressionprior = X_train['pred_temp'].mean()te = TargetEncoder(verbose=self.verbose, hierarchy=self.hierarchy, cols=[variable], smoothing=self.smoothing, min_samples_leaf=self.min_samples_leaf)te.fit(X_train[[variable]], X_train['pred_temp'])tmp_l = te.ordinal_encoder.mapping[0]["mapping"].reset_index()tmp_l.rename(columns={"index":variable, 0:"encode"}, inplace=True)tmp_l.dropna(inplace=True)tmp_r = te.mapping[variable].reset_index()if self.hierarchy is None:tmp_r.rename(columns={variable: "encode", 0:nf_name}, inplace=True)else:tmp_r.rename(columns={"index": "encode", 0:nf_name}, inplace=True)col_avg_y = pd.merge(tmp_l, tmp_r, how="left",on=["encode"])col_avg_y.drop(columns=["encode"], inplace=True)col_avg_y.set_index(variable, inplace=True)nf_train = X_train.join(col_avg_y, on=variable)[nf_name].valuesnf_test = X_test.join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name].valuesreturn nf_train, nf_test, prior, col_avg_ydef fit(self, X, y):""":param X: pandas DataFrame, n_samples * n_features:param y: pandas Series or numpy array, n_samples:return X_new: the transformed pandas DataFrame containing mean-encoded categorical features"""X_new = X.copy()if self.target_type == 'classification':skf = StratifiedKFold(self.n_splits, shuffle=self.shuffle, random_state=self.random_state)else:skf = KFold(self.n_splits, shuffle=self.shuffle, random_state=self.random_state)if self.target_type == 'classification':self.target_values = sorted(set(y))self.learned_stats = {'{}_pred_{}'.format(variable, target): [] for variable, target inproduct(self.categorical_features, self.target_values)}for variable, target in product(self.categorical_features, self.target_values):nf_name = '{}_pred_{}'.format(variable, target)X_new.loc[:, nf_name] = np.nanfor large_ind, small_ind in skf.split(y, y):nf_large, nf_small, prior, col_avg_y = self.mean_encode_subroutine(X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, target)X_new.iloc[small_ind, -1] = nf_smallself.learned_stats[nf_name].append((prior, col_avg_y))else:self.learned_stats = {'{}_pred'.format(variable): [] for variable in self.categorical_features}for variable in self.categorical_features:nf_name = '{}_pred'.format(variable)X_new.loc[:, nf_name] = np.nanfor large_ind, small_ind in skf.split(y, y):nf_large, nf_small, prior, col_avg_y = self.mean_encode_subroutine(X_new.iloc[large_ind], y.iloc[large_ind], X_new.iloc[small_ind], variable, None)X_new.iloc[small_ind, -1] = nf_smallself.learned_stats[nf_name].append((prior, col_avg_y))return X_newdef transform(self, X):""":param X: pandas DataFrame, n_samples * n_features:return X_new: the transformed pandas DataFrame containing mean-encoded categorical features"""X_new = X.copy()if self.target_type == 'classification':for variable, target in product(self.categorical_features, self.target_values):nf_name = '{}_pred_{}'.format(variable, target)X_new[nf_name] = 0for prior, col_avg_y in self.learned_stats[nf_name]:X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name]X_new[nf_name] /= self.n_splitselse:for variable in self.categorical_features:nf_name = '{}_pred'.format(variable)X_new[nf_name] = 0for prior, col_avg_y in self.learned_stats[nf_name]:X_new[nf_name] += X_new[[variable]].join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name]X_new[nf_name] /= self.n_splitsreturn X_new

四 总结

本文介绍了一种对高基数类别特征非常有效的编码方式:平均数编码。详细的讲述了该种编码方式的原理,在实际工程应用中有效避免过拟合的方法,并且提供了一个直接上手的代码版本。

作者:京东保险 赵风龙

来源:京东云开发者社区 转载请注明来源

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/114168.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

day 31 面向对象 成员方法

class 类名称&#xff1a; 类的属类(定义在类中的变量&#xff0c;成员变量) 类的行为(定义在类中的函数&#xff0c;成员方法) # 设计一个类&#xff08;类比生活中&#xff1a;设计一张等级表&#xff09; class Student:name Nonegender Nonenatio…

DWA算法学习

一、DWA概念  DWA(动态窗口法)属于局部路径规划方法&#xff0c;为ROS中主要采用的方法。其原理主要是在速度空间&#xff08;v,w&#xff09;中采样多组速度&#xff0c;并模拟这些速度在一定时间内的运动轨迹&#xff0c;再通过一个评价函数对这些轨迹打分&#xff0c;最优的…

15.坐标添加带箭头的线

ol的官网示例中有绘制带箭头的线的demo&#xff0c;那个是交互式绘制&#xff0c;而不是根据经纬度坐标添加&#xff0c;在其基础上稍作修改&#xff0c;即可转为通过经纬度添加带箭头的线的功能&#xff0c;线和箭头的粗细大小样式都可以自定义 代码如下 <!DOCTYPE HTML P…

Java 多线程系列Ⅰ(创建线程+查看线程+Thread方法+线程状态)

多线程基础 一、创建线程的五种方法前置知识1、方法一&#xff1a;使用继承Thread类&#xff0c;重写run方法2、方法二&#xff1a;实现Runnable接口&#xff0c;重写run方法3、方法三&#xff1a;继承Thread&#xff0c;使用匿名内部类4、方法四&#xff1a;实现Runnable&…

5G工业网关赋能救护车远程监控,助力高效救援

智慧医疗是传统医疗业发展进步的必要趋势&#xff0c;医疗设备通过物联网技术的应用实现智能化转型。通过5G工业网关将医疗器械等设备的数据采集再经过专网传输到医疗系统中&#xff0c;实现医疗设备间的数据共享和远程监控&#xff0c;能够帮助医疗行业大大提高服务质量和管理…

Weblogic漏洞(四)之 CVE-2018-2894 任意文件上传漏洞

CVE-2018-2894 任意文件上传漏洞 漏洞影响 Weblogic受影响的版本&#xff1a; 10.3.6.012.1.3.012.2.1.212.2.1.3 漏洞环境 此次我们使用的是vnlhub靶场搭建的环境&#xff0c;是vnlhub中的Weblogic漏洞中的CVE-2018-2894靶场&#xff0c;我们 cd 到 CVE-2018-2894&#x…

基于KNN算法的鸢尾花种类预测

导入数据 iris_data load_iris() iris_data.data[0:5, :]array([[5.1, 3.5, 1.4, 0.2],[4.9, 3. , 1.4, 0.2],[4.7, 3.2, 1.3, 0.2],[4.6, 3.1, 1.5, 0.2],[5. , 3.6, 1.4, 0.2]])# 特征值名称 iris_data.feature_names[sepal length (cm),sepal width (cm),petal length (cm…

12、监测数据采集物联网应用开发步骤(9.1)

监测数据采集物联网应用开发步骤(8.2) TCP/IP Server开发 在com.zxy.common.Com_Para.py中添加如下内容 #锁机制 lock threading.Lock() #本机服务端端口已被连接客户端socket list dServThreadList {} #作为服务端接收数据拦截器 ServerREFLECT_IN_CLASS "com.plug…

PMP - 敏捷 3355

三个核心 产品负责人 负责最大化投资回报&#xff08;ROI&#xff09;&#xff0c;通过确定产品特性&#xff0c;把他们翻译成一个有优先级的列表 为下一个 sprint 决定在这个列表中哪些应该优先级最高&#xff0c;并且不断调整优先级以及调整这个列表 职责是定义需求、定义…

SSL核心概念 SSL类型级别

SSL&#xff1a;SSL&#xff08;Secure Sockets Layer&#xff09;即安全套接层&#xff0c;及其继任者传输层安全&#xff08;Transport Layer Security&#xff0c;TLS&#xff09;是为网络通信提供安全及数据完整性的一种安全协议。TLS与SSL在传输层对网络连接进行加密。 H…

取暖器UL1278测试项目及注意事项!!!

UL1278是可移动的挂墙式或吊顶式室内电暖器的标准&#xff0c;适用于额定电压不超过600V的可移动的且挂墙式或吊顶式的电暖器。不适用于固定式电暖器&#xff0c; 管道式电暖器&#xff0c;中心加热的炉。 取暖器UL认证UL1278标准测试项目&#xff1a; 泄露电流试验&#xff…

leetcode 503. 下一个更大元素 II

2023.8.28 本题类似于下一个更大元素I &#xff0c;区别就是数组变成循环的了&#xff0c;可以将nums数组先double一下&#xff0c;如&#xff1a;{1&#xff0c;2&#xff0c;1}变成{1&#xff0c;2&#xff0c;1&#xff0c;1&#xff0c;2&#xff0c;1}&#xff0c;再用单调…

ElasticSearch-集成ik分词器

本文已收录于专栏 《中间件合集》 目录 背景介绍版本选择优势说明集成过程1.下载安装包2.解压安装包3.重启ElasticSearch服务3.1通过ps -ef | grep elastic查看正在启动的es进程号3.2使用kill -9 xxx 杀死进程3.3使用 ./elasticsearch 启动es服务 分词测试细粒度分词方式分词请…

PXE网络批量装机(centos7)

目录 前言 一、实验拓扑图 二、PXE的组件 三、配置PXE装机服务器 1、设置防火墙、selinux 2.安装、启动vsftp 3、拷贝系统文件到/var/ftp用于装机 4、配置tftp 5、准备pxelinx.0文件、引导文件、内核文件 6、配置本机IP 7、配置DHCP服务 8、创建default文件 四、配…

MFC -- Date Time Picker 控件使用

当前环境&#xff1a;VS2015 Windows 10 //&#xff08;一&#xff09;使用普通函数&#xff0c; 获取当前时间CString strCurrentTime; COleDateTime m_time COleDateTime::GetCurrentTime(); strCurrentTime m_time.Format(_T("%Y-%m-%d %H:%M:%S")); SetDlgIt…

问道管理:稳增长持续发力 A股市场信心迎修复

自7月政治局会议释放稳添加活泼信号以来&#xff0c;多项支持经济方针近期陆续落地。与此同时&#xff0c;活泼资本商场、提振出资者决计的一系列行动也正在逐渐发挥作用。业内人士表明&#xff0c;系列方针的出台进一步安稳了商场对我国经济的预期&#xff0c;也将助力修正出资…

STM32启动模式详解

文章目录 前置知识1. 单片机最小系统组成2. BOOT电路3. 三种启动模式4. 存储器映射 从主FLASH启动从系统存储区启动从SRAM启动 前置知识 1. 单片机最小系统组成 一个单片机最小系统由电源、晶振、下载电路、BOOT电路、和复位电路组成。少一个单片机都启动不了。 2. BOOT电路 …

设置微软Edge浏览器主页和新标签页,摆脱扰人和分散注意力的主页

默认情况下&#xff0c;Microsoft Edge会向您显示世界上最令人分心和讨厌的主页&#xff08;也称为主屏幕&#xff09;。微软不想只向你展示一个搜索框&#xff0c;也许还有一个漂亮的背景或一些你喜欢的网站的快捷方式&#xff0c;而是想在你面前扔一堆新闻标题和广告。 你可…

Revit SDK:Selections 选择

前言 Revit 作为一款成熟的商业软件&#xff0c;它将自己的UI选择功能也通过 API 暴露出来。通过 API 可以按照特定的过滤规则来选择相应的元素&#xff0c;能力和UI基本上是等价的。这个 SDK 用四个例子展示了 API 的能力&#xff0c;内容如下。 内容 PickforDeletion 核心…

谷歌发布Gemini以5倍速击败GPT-4

在Covid疫情爆发之前&#xff0c;谷歌发布了MEENA模型&#xff0c;短时间内成为世界上最好的大型语言模型。谷歌发布的博客和论文非常可爱&#xff0c;因为它特别与OpenAI进行了比较。 相比于现有的最先进生成模型OpenAI GPT-2&#xff0c;MEENA的模型容量增加了1.7倍&#xf…