大数据比赛(2)-特征那点儿事

特征工程是一个非常重要的课题,是机器学习中不可或缺的一部分,但是它几乎很少出现于单独的机器学习的教程或教材中,所以需要在比赛的过程中多学习和体会。

1. 什么是特征工程?

“特征工程是将原始数据转化为特征,更好表示预测模型处理的实际问题,提升对于未知数据的准确性。它是用目标问题所在的特定领域知识或者自动化的方法来生成、提取、删减或者组合变化得到特征。”

通俗的讲,特征工程包括特征抽取特征选择两部分,在我看来所谓
特征抽取就是通过 整合、预处理和转化 已有数据来生成特征;
特征选择就是在生成的特征中选择部分特征来减少特征数量、降维,并且使模型泛化能力更强,减少过拟合,有时还可以增强对特征和特征值之间的理解(其实就是在不熟悉的领域问题中找到比较好的特征)。

“数据和特征决定了机器学习的上限,而模型和算法只是逼近这个上限而已”,从数据里面提取出来的特征好坏与否直接影响模型的效果,对比赛大神来说,“数据决定了结果的上限,而算法只是尽可能逼近这个上限。”给定的数据决定了他们成绩的上限,对我这类菜鸟来说:“特征决定了你在排行榜的第几页”

在比赛中通常提供的是表格数据,观测数据或实例(对应表格的一行)由不同的变量或者属性(表格的一列)构成,这里属性其实就可以用作特征。但是特征是对于分析和解决问题有用、有意义的属性,最初的原始特征数据集可能太大,或者信息冗余,因此初始步骤就是选择特征的子集,或构建一套新的特征集来提高算法的学习效果,提高泛化能力和可解释性。
可以选择一种自己最熟悉或者最方便的特征选择方法先进行初步的筛选,再依据实际问题进一步优化和筛选。

2. 什么样的特征是有效的?

特征是否有效的直接评价就是与结果的“相关性”。在比赛中,“准确率”是否提高是唯一要求,在实际中可能要考虑获取难度、实际意义等问题。
先推荐几篇文章:

【1】干货:结合Scikit-learn介绍几种常用的特征选择方法
http://dataunion.org/14072.html?utm_source=tuicool&utm_medium=referral
【2】Discover Feature Engineering, How to Engineer Features and How to Get Good at It
http://machinelearningmastery.com/discover-feature-engineering-how-to-engineer-features-and-how-to-get-good-at-it/

直接贴他们的文章其实就可以,不过还是结合自己的一点小经验再整理一下,代码部分均来自文献【1】

(1)去掉取值变化小的特征 Removing features with low variance
其实更像数据预处理过程,就是在大部分数据都取某一值的时候,将几个缺失值同样填充,或者特异值变换,也可以粗暴一点直接删除该特征.在Titanic比赛中,Embarked直观上与存活率关系不大,观察发现S市644人,空白2人,以多数填充方式处理.

(2)单变量特征选择 Univariate feature selection
对每一个特征进行测试,衡量该特征和响应变量之间的关系.
常用的手段有计算皮尔逊系数和互信息系数,皮尔逊系数只能衡量线性相关性而互信息系数能够很好地度量各种相关性,但是计算相对复杂一些,【1】中有详细的介绍,需要scipy和minepy包的依赖,而且占用较多的内存。

   xl = np.array(train_feature[:100]).shape[1]
   for i in range(xl):
     print pearsonr(np.array(train_feature)[:, i:i+1], np.array(train_y))

其实更加方便的是使用基于学习模型的特征排序 (Model based ranking),即直接使用你要用的机器学习算法,针对每个单独的特征和响应变量建立预测模型。

scores = []
        for i in range(np.array(train_feature).shape[1]):
            score = cross_val_score(rf, np.array(train_feature)[:, i:i+1], np.array(train_y), scoring="r2",
                              cv=ShuffleSplit(len(np.array(train_feature)), 3, .3))
            scores.append((round(np.mean(score), 3),i))
        print sorted(scores, reverse=True)       

在天池的某比赛中采用这种方法,获得的结果:

[(0.71, 25), (0.705, 27), (0.696, 28), (0.644, 14), (0.642, 16), (0.622, 15), (0.486, 12), (0.485, 9), (0.452, 11), (0.406, 26), (0.398, 7), (0.346, 6), (0.335, 3), (0.261, 13), (0.261, 8), (0.254, 2), (0.226, 22), (0.176, 10), (0.169, 18), (0.167, 4), (0.165, 23), (0.15, 19), (0.121, 0), (0.118, 1), (0.098, 5), (0.092, 21), (0.086, 17), (0.061, 20), (0.052, 24)]

随机森林提供了两种特征选择的方法:平均不纯度减少 mean decrease impurity和 平均精确率减少 Mean decrease accuracy
平均不纯度减少:当训练决策树的时候,可以计算出每个特征减少了多少树的不纯度。对于一个决策树森林来说,可以算出每个特征平均减少了多少不纯度,并把它平均减少的不纯度作为特征选择的值。
需要注意的是:只有先被选中的那个特征重要度很高,其他的关联特征重要度往往较低,因为一旦某个特征被选择之后,其他特征的重要度就会急剧下降,但实际上这些特征对响应变量的作用可能是非常接近的。

rf = RandomForestRegressor()
rf.fit(X, Y)
print "Features sorted by their score:"
print sorted(zip(map(lambda x: round(x, 4), rf.feature_importances_), names), 
             reverse=True)

在天池的某比赛中采用这种方法,获得的结果:

[(0.3268, 18), (0.2872, 17), (0.1429, 15), (0.0236, 12), (0.0168, 16), (0.0165, 14), (0.0161, 4), (0.0143, 11), (0.0138, 10), (0.0132, 9), (0.0126, 13), (0.0125, 1), (0.0118, 8), (0.0116, 0), (0.0108, 20), (0.0103, 22), (0.0099, 21), (0.009, 2), (0.0086, 19), (0.0084, 7), (0.0082, 6), (0.0079, 5), (0.0073, 3)]

平均精确率减少:直接度量每个特征对模型精确率的影响。主要思路是打乱每个特征的特征值顺序,并且度量顺序变动对模型的精确率的影响,需要自己手写:

from sklearn.cross_validation import ShuffleSplit
from sklearn.metrics import r2_score
from collections import defaultdict
X = boston["data"]
Y = boston["target"]
rf = RandomForestRegressor()
scores = defaultdict(list)
#crossvalidate the scores on a number of different random splits of the data
for train_idx, test_idx in ShuffleSplit(len(X), 100, .3):
  X_train, X_test = X[train_idx], X[test_idx]
  Y_train, Y_test = Y[train_idx], Y[test_idx]
  r = rf.fit(X_train, Y_train)
  acc = r2_score(Y_test, rf.predict(X_test))
  for i in range(X.shape[1]):
    X_t = X_test.copy()
    np.random.shuffle(X_t[:, i])
    shuff_acc = r2_score(Y_test, rf.predict(X_t))
    scores[names[i]].append((acc-shuff_acc)/acc)
print "Features sorted by their score:"
print sorted([(round(np.mean(score), 4), feat) for
        feat, score in scores.items()], reverse=True)

(3)线性模型和正则化
有些机器学习方法本身就具有对特征进行打分的机制,或者很容易将其运用到特征选择任务中,例如回归模型,SVM,决策树,随机森林等等
1、用回归模型的系数来选择特征
越是重要的特征在模型中对应的系数就会越大,如果特征之间相对来说是比较独立的,那么即便是运用最简单的线性回归模型也一样能取得非常好的效果.

def pretty_print_linear(coefs, names = None, sort = False):
  if names == None:
    names = ["X%s" % x for x in range(len(coefs))]
  lst = zip(coefs, names)
  if sort:
    lst = sorted(lst,  key = lambda x:-np.abs(x[0]))
  return " + ".join("%s * %s" % (round(coef, 3), name)
                   for coef, name in lst)

lr = LinearRegression()
lr.fit(X,Y)
print "Linear model:", pretty_print_linear(lr.coef_)

2、正则化模型
所谓正则化,就是把额外的约束或者惩罚项加到已有模型(损失函数)上,以防止过拟合并提高泛化能力。一般的,添加正则化项后,损失函数由原来的E(X,Y)变为E(X,Y)+alpha||w||,其中w是模型系数组成的向量(有些地方也叫参数parameter,coefficients),||·||表示是L1或者L2范数,alpha控制着正则化的强度,alpha过大容易欠拟合,过小容易过拟合。(当用在线性模型上时,L1正则化和L2正则化也称为Lasso和Ridge。)

L1: 向量中各个元素的绝对值之和 =》非零元素较少 =》稀疏化
L2: 向量中各个元素的平方和的开方 =》每个元素都很小=》防止过拟合
L1正则化将系数w的L1范数作为惩罚项加到损失函数上,由于正则项非零,这就迫使那些弱的特征所对应的系数变成0,可以用作特征选择(无信息的特征自动归零),还可以提高可解释性。

from sklearn.linear_model import Lasso
from sklearn.preprocessing import StandardScaler
from sklearn.datasets import load_boston

boston = load_boston()
scaler = StandardScaler()
X = scaler.fit_transform(boston["data"])
Y = boston["target"]
names = boston["feature_names"]

lasso = Lasso(alpha=.3)#通过grid search优化
lasso.fit(X, Y)

print "Lasso model: ", pretty_print_linear(lasso.coef_, names, sort = True)

L2惩罚项中系数是二次方的,会让系数的取值变得平均,可以提高泛化能力,防止过拟合。

from sklearn.linear_model import Ridge
from sklearn.metrics import r2_score
size = 100
#We run the method 10 times with different random seeds
for i in range(10):
  print "Random seed %s" % i
  np.random.seed(seed=i)
  X_seed = np.random.normal(0, 1, size)
  X1 = X_seed + np.random.normal(0, .1, size)
  X2 = X_seed + np.random.normal(0, .1, size)
  X3 = X_seed + np.random.normal(0, .1, size)
  Y = X1 + X2 + X3 + np.random.normal(0, 1, size)
  X = np.array([X1, X2, X3]).T
  lr = LinearRegression()
  lr.fit(X,Y)
  print "Linear model:", pretty_print_linear(lr.coef_)
  ridge = Ridge(alpha=10)
  ridge.fit(X,Y)
  print "Ridge model:", pretty_print_linear(ridge.coef_)
  print

先说结论:在比赛中,特征选择是十分有效的,但不一定能让你做到更好。
一般的数据比赛,如果只是简单的将上述方法用于给定的数据表,基本上都会有一个小的提升。但想要冲击更高的名次,还需要在特征构造(依据比赛数据的特点构造更多的相关特征),模型构造(不是机器学习模型的选择,而是对【x】,【y】的构造与选择,train、dev、test的构造与选择)上面下工夫,毕竟特征选择也只是在已有的基础上进行的优化,很难产生质变。作为一个学弱和菜鸟对此也没有太好的办法,下一篇可能对机器学习模型的选择和参数调整稍作总结。以下对比赛中的数据及特征处理流程进行简单梳理:

1、样本预处理:
(1)数据可能部分噪音,可以根据对数据的了解,过滤掉错误样本,并且根据业务等逻辑制定filter,筛选样本。比如年龄此特征有许多负值及几百的异常值,若我们判断年龄为关键特征,则可通过规则将这些异常值样本去掉。
实现tips:
1》python pandas可以查找异常值并且容易填充和修改
2》excel本身就是很好的数据处理软件,其筛选和统计的功能不容小觑,曾经有人只用excel就完成了数据的全部处理和可视化工作
(2)在训练时尽量用同量级的样本,使其尽量均匀。特别是对样本数少的label,可以通过抽样来提高其比重。
(3)将部分原始数据做离散化、规范化、二值化、分箱等操作,以期更加适应机器学习模型,进一步提高精度。
tips:推荐寒小阳童鞋的博客:
机器学习系列(6)_从白富美相亲看特征预处理与选择(下)
http://blog.csdn.net/han_xiaoyang/article/details/50503115
机器学习系列(3)_逻辑回归应用之Kaggle泰坦尼克之灾
http://blog.csdn.net/han_xiaoyang/article/details/49797143
python对离散化、规范化都有相应的实现工具,但在实际的使用中部分规范化的方法不一定适用与所有的数据,应当尽量在神经网络等需要该类数据的前提下使用。
(4)对原始数据中存在缺失值的处理有时会对结果产生很大的影响,对于不太重要的特征采取“舍去”的方式,但重要的特征需要对缺失值进行替换或赋值(对缺失值进行预测)

2、特征调整:
(1)通过特征组合增加衍生变量,根据业务理解对不同特征组合,产生新特征来进行扩维。
(2)特征筛选,依据以上提到的特征选择方法进一步选择优秀的特征。

你可能感兴趣的:(大数据比赛(2)-特征那点儿事)