在FFM算法编码之前突然考虑到标准化的问题,例如大多数的属性都是0-1,出现的部分连续属性比如价格可能会很大,这些的情况会不会影响FFM的结果。
首先在网上搜了一下,连续特征离散化处理起到的效果是什么,这里引用一下知乎的回答
链接:https://www.zhihu.com/question/31989952/answer/54184582
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
在工业界,很少直接将连续值作为逻辑回归模型的特征输入,而是将连续特征离散化为一系列0、1特征交给逻辑回归模型,这样做的优势有以下几点:
0. 离散特征的增加和减少都很容易,易于模型的快速迭代;
1. 稀疏向量内积乘法运算速度快,计算结果方便存储,容易扩展;
2. 离散化后的特征对异常数据有很强的鲁棒性:比如一个特征是年龄>30是1,否则0。如果特征没有离散化,一个异常数据“年龄300岁”会给模型造成很大的干扰;
3. 逻辑回归属于广义线性模型,表达能力受限;单变量离散化为N个后,每个变量有单独的权重,相当于为模型引入了非线性,能够提升模型表达能力,加大拟合;
4. 离散化后可以进行特征交叉,由M+N个变量变为M*N个变量,进一步引入非线性,提升表达能力;
5. 特征离散化后,模型会更稳定,比如如果对用户年龄离散化,20-30作为一个区间,不会因为一个用户年龄长了一岁就变成一个完全不同的人。当然处于区间相邻处的样本会刚好相反,所以怎么划分区间是门学问;
6. 特征离散化以后,起到了简化了逻辑回归模型的作用,降低了模型过拟合的风险。
李沐曾经说过:模型是使用离散特征还是连续特征,其实是一个“海量离散特征+简单模型” 同 “少量连续特征+复杂模型”的权衡。既可以离散化用线性模型,也可以用连续特征加深度学习。就看是喜欢折腾特征还是折腾模型了。通常来说,前者容易,而且可以n个人一起并行做,有成功经验;后者目前看很赞,能走多远还须拭目以待。
查看了一些连续特征离散化的综述论文后我选择了一些方法做说明,连续特征离散化主要分为有监督方法与无监督方法
1.无监督方法
无监督方法都具有的问题就是都需要认为规定划分区间这个参数,常用的方法有分箱法和直观划分,首先来说一下分箱法:
分箱法又分为等宽分箱法和等频分箱法,其实从名字就能看出算法的做法了,前者制定定长的间隔将特征放入不同箱子内,后者根据频率划分箱子,这两种做法都有一定的缺陷。等宽分箱法对异常点比较敏感,比如数据正常范围是30-60,现在出现了一个特征本来是30不小心读取成了300,那么宽度会被拉长,实例就主要集中在前面的箱体中,后面的箱体几乎没有实例,解决的方法主要是设立阈值移除阈值以外的实例。等频分箱法的缺陷是完全按照频率划分会出现实例特征相同却不在同一个箱体内的情况,解决办法也很简单,在划分完成后进行微调就可完成。
直观划分法感觉就没什么技术了,这个算法主要是根据经验和美观,常用的方法划分出来肯定都是34267、64537这样的数,但是人们一般都习惯看到30000、50000这样的数,然后就是加上个人对数据的了解人为划分。
个人感觉无监督方法的连续特征离散化并不是很靠谱,原因就是不知道设计的目的单纯考虑离散化对结果是更好还是更差这个不确定性太大了。
2.有监督方法
1R方法:这个方法是分箱法的有监督版本,把前6个实例放入箱子中,然后后面实例放入箱子时,对比当前实例标签与箱子中大部分实例标签是否相同,如果相同放入,如果不相同,那么形成下一个6实例的新箱子,等到最后全部放入箱子中,将箱子中大多数实例标签作为箱子的标签,再将标签相同的箱子合并
基于卡方的离散方法:首先将数值特征的每个不同值看做一个区间对每对相邻区间计算卡方统计量,如果大于阈值就合并,递归进行直到找不到卡方统计大于阈值的时候停止
卡方计算方法 http://www.cnblogs.com/emanlee/archive/2008/10/25/1319569.html
基于熵的离散方法:其实就是照搬了决策树的思路,使用合成的方法或者分裂的方法根据熵计算和阈值判定来决定合成或分裂,上一段合成的代码
import numpy as np
class Feature_Discretization(object):
def __init__(self):
self.min_interval = 1 #最小间隔
self.min_epos = 0.05 #信息增益阈值
self.final_bin = [] #最终边界
def fit(self, x, y, min_interval = 1):
self.min_interval = min_interval
x = np.floor(x)
x = np.int32(x)
min_val = np.min(x)
bin_dict = {}
bin_li = []
for i in range(len(x)):
pos = (x[i] - min_val)/min_interval * min_interval + min_val
target = y[i]
bin_dict.setdefault(pos,[0,0])
if target == 1:
bin_dict[pos][0] += 1
else:
bin_dict[pos][1] += 1 #标签one-hot的操作
for key ,val in bin_dict.iteritems():
t = [key]
t.extend(val)
bin_li.append(t)
bin_li.sort(cmp=None, key=lambda x : x[0], reverse=False)
print bin_li
L_index = 0
R_index = 1
self.final_bin.append(bin_li[L_index][0])
while True:
L = bin_li[L_index]
R = bin_li[R_index]
# using infomation gain;
p1 = L[1]/ (L[1] + L[2] + 0.0)
p0 = L[2]/ (L[1] + L[2] + 0.0)
if p1 <= 1e-5 or p0 <= 1e-5:
LGain = 0
else:
LGain = -p1*np.log(p1) - p0 * np.log(p0)
p1 = R[1]/ (R[1] + R[2] + 0.0)
p0 = R[2]/ (R[1] + R[2] + 0.0)
if p1 <= 1e-5 or p0 <= 1e-5:
RGain = 0
else:
RGain = -p1*np.log(p1) - p0 * np.log(p0)
p1 = (L[1] + R[1])/ (L[1] + L[2] + R[1] + R[2] + 0.0)
p0 = (L[2] + R[2])/ (L[1] + L[2] + R[1] + R[2] + 0.0)
if p1 <= 1e-5 or p0 <= 1e-5:
ALLGain = 0
else:
ALLGain = -p1*np.log(p1) - p0 * np.log(p0)
if np.absolute(ALLGain - LGain - RGain) <= self.min_epos: #根据信息增益判定是否合成
# concat the interval;
bin_li[L_index][1] += R[1]
bin_li[L_index][2] += R[2]
R_index += 1
else:
L_index = R_index
R_index = L_index + 1
self.final_bin.append(bin_li[L_index][0])
if R_index >= len(bin_li):
break
print 'feature bin:',self.final_bin
def transform(self,x):
res = []
for e in x:
index = self.get_Discretization_index(self.final_bin, e)
res.append(index)
res = np.asarray(res)
return res
def get_Discretization_index(self ,Discretization_vals, val ):
index = -1
for i in range(len(Discretization_vals)):
e = Discretization_vals[i]
if val <= e:
index = i
break
return index