这两天在帮朋友跑个满意度的模型。发现当中最有进步空间的是数据预处理的部分,之前的那个人做得太粗糙了。在业务层面一般都会将连续的数据离散化处理,这实质上是一种tradeoff.
在工业界,很少直接将连续值作为逻辑回归模型的特征输入,而是将连续特征离散化为一系列0、1特征交给逻辑回归模型,这样做的优势有以下几点:0.
离散特征的增加和减少都很容易,易于模型的快速迭代;1. 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;2.
离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;3.
逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;4.
离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;5.
特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;6.
特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
:来自知乎
这货说的比较抽象,下面是来自另一个回答的例子:
:
- 假设目前有两个连续的特征:『年龄』和『收入』,预测用户的『魅力指数』;
关于第三点指出,LR是广义线性模型,因此如果特征『年龄』不做离散化直接输入,那么只能得到『年龄』和魅力指数的一个线性关系。但是这种线性关系是不准确的,并非年龄越大魅力指一定越大;如果将年龄划分为M段,则可以针对每段有一个对应的权重;这种分段的能力为模型带来类似『折线』的能力,也就是所谓的非线性
连续变量的划分,naive的可以通过人为先验知识划分,也可以通过训练单特征的决策树桩,根据Information
Gain/Gini系数等来有监督的划分。
假如『年龄』离散化后,共有N段,『收入』离散化后有M段;此时这两个离散化后的特征类似于CategoryFeature,对他们进行OneHotEncode,即可以得到
M + N的 01向量;例如: 0 1 0 0, 1 0 0 0 0;
第四点中的特征交叉,可以理解为上述两个向量的互相作用,作用的方式可以例如是 &和|操作(这种交叉方式可以产生一个 M * N的01向量;)
上面特征交叉,可以类比于决策树的决策过程。例如进行&操作后,得到一个1,则可以认为产生一个特征 (a < age < b && c <
income < d);将特征空间进行的非线性划分,也就是所谓的引入非线性李沐曾经说过:模型是使用离散特征还是连续特征,其实是一个“海量离散特征+简单模型” 同
“少量连续特征+复杂模型”的权衡。既可以离散化用线性模型,也可以用连续特征加深度学习。就看是喜欢折腾特征还是折腾模型了。通常来说,前者容易,而且可以n个人一起并行做,有成功经验;后者目前看很赞,能走多远还须拭目以待。
连续模型离散化一般有三种方式,等频法,等宽法和聚类方法。这里用zakki的数据进行演示
import pandas as pd
import matplotlib.pyplot as plt
data = 'discretization_data.xls'
data = pd.read_excel(data)
from sklearn.cluster import KMeans
import numpy as np
data = data['肝气郁结证型系数'].copy()
# 等宽法
k = 4
d1 = pd.cut(data, k, labels=range(k)) # 直接按照(max-min)/k 分区间
# w = [i/k for i in range(k+1)]
# w = data.describe(percentiles=w)[4:9]
# d2=pd.cut(data,w)
# 等频法
d2 = pd.qcut(data, k, labels=range(k)) # 按照1/k的区间进行划分
# 聚类法,使用k-mean
kmodel = KMeans(n_clusters=k) # 确定族数
kmodel.fit(data.values.reshape(len(data), 1))
c = pd.DataFrame(np.sort(kmodel.cluster_centers_, axis=0)) # z注意axis!!!!
w = c.rolling(2).mean().iloc[1:]
w = [0] + list(w[0]) + [data.max()]
w = list(np.sort(w)) # 再次排序
d3 = pd.cut(data, w, labels=range(k))
def classified_plot(d):
global cnt
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
plt.figure(figsize=(7, 5))
for j in range(0, k):
plt.plot(data[d == j], [i for i in d[d == j]], 'o')
cnt += 1
plt.title(f'd{cnt}')
cnt = 0
classified_plot(d1)
classified_plot(d2)
classified_plot(d3)
plt.show()
对应到我在做的这个项目,他一开始是用等宽法做的离散,我换成等频法后,效果好了很多:
for column in continuous_list:
cut = pd.qcut(data_nona[column], q=5, duplicates='drop')
cat = cut.values
codes = cat.codes
data_nona[column + '_discrete'] = codes
for column, content in data_nona.items():
if pd.api.types.is_numeric_dtype(content):
data_nona[column] = content.astype('int')