高基数类别特征预处理:平均数编码

这篇具有很好参考价值的文章主要介绍了高基数类别特征预处理:平均数编码。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一 前言

对于一个类别特征,如果这个特征的取值非常多,则称它为高基数(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 TargetEncoder

df = 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_compass

X, 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, KFold

class 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 str
            the name of the categorical columns to encode.
        n_splits: int
            the number of splits used in mean encoding.
        target_type: str,
            'regression' or 'classification'.
        min_samples_leaf: int
            For regularization the weighted average between category mean and global mean is taken. The weight is
            an 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: float
            smoothing 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 dataframe
            A 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 X
            which 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: int
            integer indicating verbosity of the output. 0 for none.
        shuffle : bool, default=False
        random_state : int or RandomState instance, default=None
            When `shuffle` is True, `random_state` affects the ordering of the
            indices, 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_features
        self.n_splits = n_splits
        self.learned_stats = {}
        self.min_samples_leaf = min_samples_leaf
        self.smoothing = smoothing
        self.hierarchy = hierarchy
        self.verbose = verbose
        self.shuffle = shuffle
        self.random_state = random_state

        if target_type == 'classification':
            self.target_type = target_type
            self.target_values = []
        else:
            self.target_type = 'regression'
            self.target_values = None
            

    def 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)  # classification
        else:
            nf_name = '{}_pred'.format(variable)
            X_train['pred_temp'] = y_train  # regression
        prior = 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].values
        nf_test = X_test.join(col_avg_y, on=variable).fillna(prior, inplace=False)[nf_name].values

        return nf_train, nf_test, prior, col_avg_y

    def 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 in
                                  product(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.nan
                for 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_small
                    self.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.nan
                for 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_small
                    self.learned_stats[nf_name].append((prior, col_avg_y))
        return X_new

    def 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] = 0
                for 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_splits
        else:
            for variable in self.categorical_features:
                nf_name = '{}_pred'.format(variable)
                X_new[nf_name] = 0
                for 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_splits

        return X_new

四 总结

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

作者:京东保险 赵风龙

来源:京东云开发者社区 转载请注明来源文章来源地址https://www.toymoban.com/news/detail-681602.html

到了这里,关于高基数类别特征预处理:平均数编码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Python数据分析之特征处理笔记三——特征预处理(特征选择)

    书接上文,进行数据清洗过程后,我们得到了没有空值、异常值、错误值的数据,但想要用海量的数据来建立我们所需要的算法模型,仅仅是数据清洗的过程是不够的,因为有的数据类型是数值,有的是字符,怎样将不同类型的数据联系起来?以及在保证最大化信息量的前提

    2024年02月02日
    浏览(51)
  • 语音特征提取与预处理

    导入相关包  语音读取与显示  端点检测(去除前后静音段) 原理:将每帧均方根能量与全局最大均方根能量进行比较。  端点检测(包含语音内部)  频域分析 预加重  高通滤波,弥补高频部分的损耗,保护了声道信息:y[n] - y[n] - coef * y[n-1]。 Filter Bank:梅尔谱特征 梅尔滤

    2024年02月10日
    浏览(39)
  • 机器学习基础之《特征工程(3)—特征预处理》

    一、什么是特征预处理 通过一些转换函数将特征数据转换成更加适合算法模型的特征数据过程 处理前,特征值是数值,处理后,进行了特征缩放 1、包含内容 数值型数据的无量纲化: 归一化 标准化 2、特征预处理API sklearn.preprocessing 3、为什么我们要进行归一化/标准化 特征

    2024年02月14日
    浏览(40)
  • 大数据的4v特征、数据预处理

    一、大数据的4v特征 大数据的4v特征主要包含规模性(Volume)、多样性(Variety)、高速性(Velocity)、价值性(Value) 1、规模性(Volume) 大数据中的数据计量单位是PB(1千个T)、EB(1百万个T)或ZB(10亿个T)。 2、多样性(Variety) 多样性主要体现在数据来源多、数据类型多

    2024年02月07日
    浏览(58)
  • 机器学习基础 数据集、特征工程、特征预处理、特征选择 7.27

    无量纲化 1.标准化 2.归一化 信息数据化 1.特征二值化 2. Ont-hot编码 3.缺失数据补全 1.方差选择法 2.相关系数法

    2024年02月14日
    浏览(55)
  • 大数据HCIE成神之路之数据预处理(6)——特征编码

    提问:什么是独热编码? 回答:独热编码是一种常用的数据编码方法,用于将分类变量转换为 二进制 的表示形式。它将每个类别表示为一个只包含 0和1 的二进制向量,其中每个类别对应一个维度,维度上的值为1表示该 样本属于该类别 ,为0表示 不属于该类别 。 对于离散

    2024年02月03日
    浏览(37)
  • 脑电信号处理与特征提取——4.脑电信号的预处理及数据分析要点(彭微微)

    目录 四、脑电信号的预处理及数据分析要点 4.1 脑电基础知识回顾 4.2 伪迹  4.3 EEG预处理 4.3.1 滤波 4.3.2 重参考 4.3.3 分段和基线校正 4.3.4 坏段剔除 4.3.5 坏导剔除/插值 4.3.6 独立成分分析ICA 4.4 事件相关电位(ERPs) 4.4.1 如何获得ERPs 4.4.2 ERP研究应该报告些什么 4.4.3 如何呈现E

    2024年02月15日
    浏览(84)
  • 【机器学习算法】KNN鸢尾花种类预测案例和特征预处理。全md文档笔记(已分享,附代码)

    本系列文章md笔记(已分享)主要讨论机器学习算法相关知识。机器学习算法文章笔记以算法、案例为驱动的学习,伴随浅显易懂的数学知识,让大家掌握机器学习常见算法原理,应用Scikit-learn实现机器学习算法的应用,结合场景解决实际问题。包括K-近邻算法,线性回归,逻

    2024年02月19日
    浏览(47)
  • python机器学习(三)特征预处理、鸢尾花案例--分类、线性回归、代价函数、梯度下降法、使用numpy、sklearn实现一元线性回归

    数据预处理的过程。数据存在不同的量纲、数据中存在离群值,需要稳定的转换数据,处理好的数据才能更好的去训练模型,减少误差的出现。 标准化 数据集的标准化对scikit-learn中实现的大多数机器学习算法来说是常见的要求,很多案例都需要标准化。如果个别特征或多或

    2024年02月16日
    浏览(46)
  • 【C语言】程序环境和预处理|预处理详解|定义宏(下)

    主页:114514的代码大冒 qq:2188956112(欢迎小伙伴呀hi✿(。◕ᴗ◕。)✿ ) Gitee:庄嘉豪 (zhuang-jiahaoxxx) - Gitee.com 文章目录 目录 文章目录 前言 2.5带副作用的宏参数 2.6宏和函数的对比 3#undef ​编辑 4 命令行定义 5 条件编译 6 文件包含 总结 咱们书接上回 2.5带副作用的宏参数 先来

    2024年01月17日
    浏览(59)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包