数据预处理、特征工程

在不引起重要信息丢失的前提下去除掉无关特征 (irrelevant feature) 和冗余特征 (redundant feature)

0. NaN值处理

0.1. 是否保留NaN值

如果NaN值过多,或者有明确的含义(比如用户拒绝选择),可以选择保留NaN值,作为一个类别型值

0.2. 删除NaN值过多的字段

df1.dropna()
检查各字段,如果某字段的NaN值过多,则去除该字段

0.3. 对某字段进行NaN值替换

df1.fillna()
对某字段进行处理,将NaN值替换为某值

1. 去除冗余

1.1. 低方差特征

X_new = VarianceThreshold(threshold).fit_transform(X)
阈值选取可以考虑通过均值标准化

1.2. 高相关特征

变量两两相关性分析
计算特征之间的皮尔森相关系数,当两特征的相关系数大于阈值时(一般阈值设为0.7或0.4),剔除其中一个特征(比如不均衡的)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import seaborn as sns
import matplotlib.pyplot as plt

# pd.DataFrame
def plot_corr_heatmap(df):
dfData = df.corr()
plt.subplots(figsize=(9, 9)) # 设置画面大小
# annot=True显示热力图上的数值大小
# vmax是显示最大值
# square=True将图变成一个正方形,默认是一个矩形
# cmap="Blues"指定一种颜色配置方案
sns.heatmap(dfData, annot=True, vmax=1, square=True, cmap="Blues")
plt.savefig('./BluesStateRelation.png')
plt.show()

1.3. 多重共线性特征

某特征可由其他一系列特征线性组合计算出

多元共线性带来的一些问题:

  • 特征不显著
  • 对参数估计值的正负号产生影响
  • 对准确性能影响较小,但会影响模型(求算)的复杂程度

举个简单的例子:比如对一个二元线性回归模型,自变量是x1和x2,如果我们画图大家可以很自然的想象出一个三维(三轴)坐标系。假如x1和x2之间没有多重共线性,那么这个模型就是一个确定了的超平面。但假如x1和x2有很强的多重共线性,那么这个模型就近似是一个直线向量,而以这个直线所拟合出来的平面是无数个的(穿过一条直线的平面是不固定的)。这也就造成了回归系数的不确定性,以及模型无法稳定。

variance_inflation_factor
通常用VIF值来衡量一个变量和其他变量的多重共线性,当某个变量的VIF大于阈值时(一般阈值设为10 或 7),需要剔除特征

此外,使用降维、模型加正则项的方式,也可以解决

1
2
3
4
5
6
b=np.array(df1)
VIF=[]
for i in range(df1.shape[1]):
vifi=variance_inflation_factor(exog=b,exog_idx=i)
VIF.append(vifi)
print(VIF)

2. 特征选择-基于相关性检验

2.1. 单特征与label之间的相关性

SelectKBestSelectPercentile

评价指标
对于分类: chi2(卡方检验)、f_classif(F检验)、mutual_info_classif(互信息)
对于回归: f_regression(F检验)、mutual_info_regression(互信息)

相较于相关系数,互信息可以识别非单调的关系(例如先递增再递减),但也要考虑后续使用的模型是否支持这种特征(例如线性模型可能就不适合,树形模型就还好)。

例如,chi2检验
chi2检验用于独立性检验,即关注两变量的相关程度(注意区别于,同分布检验-ks检验)
chi2检验具体介绍
chi2越大,x与y越相关(取前k个)
X_new = SelectKBest(chi2, k=5).fit_transform(X, y)
X_new = SelectPercentile(chi2, percentile=10).fit_transform(X, y)

3. 特征选择-基于预训练模型-有效性

RFESelectFromModel

最常用的包装法是递归消除特征法(recursive feature elimination,RFE)。递归消除特征法使用一个机器学习模型来进行多轮训练,每轮训练后,消除若干权值系数的对应的特征,再基于新的特征集进行下一轮训练。

嵌入法也是用机器学习的方法来选择特征,但是它和RFE的区别是它不是通过不停的筛掉特征来进行训练,而是使用的都是特征全集。

3.1. 线性模型权重

对于包装法,以经典的SVM-RFE算法来讨论这个特征选择的思路。这个算法以支持向量机来做RFE的机器学习模型选择特征。它在第一轮训练的时候,会选择所有的特征来训练,得到了分类的超平面$wx+b=0$后,如果有n个特征,那么RFE-SVM会选择出$w$中分量的平方值$w^2_i$最小的那个序号$i$对应的特征,将其排除。在第二次分类的时候,特征数就剩下$n-1$个了,我们继续用这$n-1$个特征和输出值来训练SVM,同样的,去掉$w^2_i$最小的那个序号$i$对应的特征。以此类推,直到剩下的特征数满足我们的需求为止。
X_new = RFE(estimator=SVC(kernel="linear", C=1), n_features_to_select=5, step=1).fit_transform(X, y)

对于嵌入法,最常用的是使用L1正则化和L2正则化来选择特征。正则化惩罚项越大,那么模型的系数就会越小。当正则化惩罚项大到一定的程度的时候,部分特征系数会变成0,当正则化惩罚项继续增大到一定程度时,所有的特征系数都会趋于0. 但是我们会发现一部分特征系数会更容易先变成0,这部分系数就是可以筛掉的。也就是说,我们选择特征系数较大的特征。常用的L1正则化和L2正则化来选择特征的基学习器是逻辑回归。
X_new = SelectFromModel(LogisticRegression(penalty="l1", C=0.1)).fit_transform(X, y)

3.2. 树形模型feature importance

此外对于嵌入法,也可以使用决策树或者GBDT。
一般来说,可以得到特征系数coef或者可以得到特征重要度(feature importances)的算法才可以做为嵌入法的基学习器。
SelectFromModel可以用来处理任何带有coef_或者feature_importances_属性的训练之后的评估器。
X_new = SelectFromModel(GradientBoostingClassifier()).fit_transform(X, y)

4. 特征选择-面向仅有类别型(分箱)变量的场景

对于“评分卡”这种模型形式,所有特征都是分箱的(并使用WOE编码)。
计算各个特征的IV值,取IV值大的特征。
某特征的IV值计算如下:

其中$i$为该特征的某个分箱。

5. 特征选择-特征在时间上稳定性

5.1. PSI

Population Stability Index
关注特征的取值,会不会随着时间的推移发生较大的变动

对某特征,获取base集与target集(分别对应不同时期的数据)。在base集进行分箱,分出$i$个等距区间,在target集使用同样标准划分。$\#_{i, base}$表示该特征在base集中$i$分箱的记录数量,则该特征PSI定义为:

PSI衡量了某特征在两个数据集中各个分箱中记录数量的分布,是否发生了变化;也就反应了该特征的总体分布,是否发生了变化
可以计算base集与一些列不同后推时间的target集的PSI值,检查PSI变化情况

5.2. KS检验

同分布检验

6. 降维

常见的降维方法有主成分分析法(PCA)和线性判别分析(LDA),线性判别分析本身也是一个分类模型。
PCA和LDA有很多的相似点,其本质是要将原始的样本映射到维度更低的样本空间中,但是PCA和LDA的映射目标不一样:

  • PCA是为了让映射后的样本具有最大的发散性(各点相距尽可能远)
  • LDA是为了让映射后的样本有最好的分类性能(同类别的点相距尽可能近,不同类别的点相距尽可能远)

所以说PCA是一种无监督的降维方法,而LDA是一种有监督的降维方法。

X_new = PCA(n_components=5).fit_transform(X)
X_new = LDA(n_components=5).fit_transform(X, y)

7. 交叉特征

  • 人工特征交叉
    • 全量两两组合,按单特征处理
    • 专家经验:例如点击率预估中,应用最多的就是广告跟用户的交叉特征、广告跟性别的交叉特征,广告跟年龄的交叉特征,广告跟手机平台的交叉特征,广告跟地域的交叉特征等等。
  • 模型自动组合
    • FM,分析二阶项系数大小
    • GBDT+LR(限制tree层数),分析叶子节点路径
  • 直接使用DNN呀
    • 虽然可以直接输入所有一阶特征,由模型学习特征之间的关系(线性、非线性),但最好还是有embedding层

卡方检验

用于独立性检验
检验时h0假设总是认为x与y独立(不相关),这样计算期望时才可以用独立概率计算联合概率。
因此,得到的chi2越大,意味着h0越不成立(拒绝),x与y越相关;而如果得到的chi2较小,意味着没能拒绝h0,x与y不太可能相关。

举例,检查“汽车品牌选择”与“性别”是否独立:
H0: “汽车品牌选择”与“性别”独立
H1: “汽车品牌选择”与“性别”相关

1. 对数据各条记录进行统计,获取[频数表格]

统计数据集中x、y各取值的频数

频数 其他 宝马 奔驰 合计
150 200 400 750
350 500 1500 2350
总计 500 700 1900 3100

2. 计算[期望表格]

因为零假设是两个变量独立,$P(A,B)=P(A)P(B)$,于是表中每个格子的期望频数为$N \times P(A,B) = N \times P(A)\times P(B)$,其中 $N$为总数量。那么,第一个格子的期望频数为$3100 \times \frac{750}{3100} \times \frac{500}{3100} = 121$。总体期望表为:

期望 其他 宝马 奔驰 合计
121 169 460 750
379 531 1440 2350
总计 500 700 1900 3100

3. 计算[卡方值]

其中$O_{i,j}$为观测频数表中$i$行$j$列单元格的数值,$E_{i,j}$为期望频数表中$i$行$j$列单元格的数值
自由度为$(行数-1) \times (列数-1)$

卡方值越大,越可能拒绝原假设,即x与y越相关

sklearn logistic regression详细注释版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# from sklearn import datasets
#
# iris = datasets.load_iris()
# x = iris.data
# y = iris.target

import pickle
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split, cross_val_score, GridSearchCV
from sklearn.preprocessing import StandardScaler, label_binarize
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix, classification_report, roc_curve, auc

if __name__ == '__main__':
data = pd.read_csv('iris.data', header=None).values
# multi-dim features
X = data[0:100, [0, 2]]
y = data[0:100, 4]
# 二值化、one-
y = label_binarize(y, classes=np.unique(y)).ravel()


# ----- 划分训练集、测试集 -----
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.25, random_state=0)


# ----- 数据标准化 -----
sc = StandardScaler()
sc.fit(X_train)
X_train_std = sc.transform(X_train)
# 测试集数据也由训练集的均值、方差来标准化
X_test_std = sc.transform(X_test)


# ----- 模型训练 -----
# LR也可以处理多分类,自动使用one vs others
lr = LogisticRegression(penalty='l2', solver='liblinear')
lr.fit(X_train_std, y_train)


# ----- 模型保存 -----
with open('lr.pkl', 'wb') as fd:
pickle.dump(lr, fd)


# ----- 模型读取 -----
fd = open('lr.pkl', 'rb')
lr2 = pickle.load(fd)
fd.close()


# ----- 模型推理 -----
y_test_pred = lr2.predict(X_test_std)
y_test_proba = lr2.predict_proba(X_test_std)


# ----- 模型评价 -----
print('results =')
for a in zip(y_test_proba, y_test_pred, y_test):
print(a)

# ===== acc =====
print('acc = {}'.format(lr2.score(X_test_std, y_test)))

# ===== confusion_matrix =====
# known to be in group i but predicted to be in group j
print('confusion_matrix = \n{}'.format(confusion_matrix(y_test, y_test_pred)))

# ===== classification_report =====
print('classification_report = \n{}'.format(classification_report(y_test, y_test_pred)))

# ===== cross validation =====
# 交叉验证并不是一个指标,多次拆分给定数据集,获取模型平均性能。(用来评价一个已训好模型的泛化性能的)
# scoring参数指定计算的指标,例如scoring='roc_auc',默认值为模型默认指标
print('cross_val_score = \n{}'.format(cross_val_score(lr2, X_test_std, y_test, cv=5)))

# ===== grid search (with cross validation) =====
# 网格搜索同样并不是一个指标,用于模型训练(选择),往往使用了交叉验证技术
hyper_paras = dict(penalty=['l1', 'l2'], C=np.logspace(0, 4, 10))
clf = GridSearchCV(LogisticRegression(solver='liblinear'), hyper_paras, cv=5, verbose=0)
best_model = clf.fit(X_train_std, y_train)
print('Best Penalty= \n{}'.format(best_model.best_estimator_.get_params()['penalty']))
print('Best C: = \n{}'.format(best_model.best_estimator_.get_params()['C']))

# # ===== roc =====
# # only for label==1
# fpr, tpr, _ = roc_curve(y_test, y_test_proba[:, 1])
# roc_auc = auc(fpr, tpr)
# # plot
# import matplotlib.pyplot as plt
# plt.title('Receiver Operating Characteristic')
# plt.plot(fpr, tpr, 'b', label='AUC = %0.2f' % roc_auc)
# plt.legend(loc='lower right')
# plt.plot([0, 1], [0, 1], 'r--')
# plt.xlim([0, 1])
# plt.ylim([0, 1])
# plt.ylabel('True Positive Rate')
# plt.xlabel('False Positive Rate')
# plt.show()

Anaconda操作命令

cmds

命令 说明
conda --version 查看自身版本
conda update conda 更新自身
conda env list 显示所有环境
conda create --name <env_name> <package_names> 新建环境。指定版本号,在包名后面=版本号;创建多个包以空格隔开。
conda create -n python3 python=3.5 numpy pandas
(conda) activate <env_name> 切入环境
(conda) deactivate 退出到root
conda remove --name <env_name> --all 删除环境
conda list 显示环境中安装的包
conda search <text> 查找含有<text>字段的包,有哪些版本可供安装
conda install <package_name> 安装包(当前环境)。可指定版本号
conda update --all 更新所有包(当前环境)
conda update <package_name> 更新包(当前环境)。可指定版本号。更新多个指定包,则包名以空格隔开
conda remove <package_name> 删除包(当前环境)

最大似然与逻辑回归

概率和似然是相反的过程:

  • 概率是已知参数或条件,预测可能发生的结果(推理);
  • 似然是已知(多条)观测到的结果,估计相关的参数或条件(训练)。

最大似然估计,即选择一组参数,使得观测到的结果对应最大出现概率。

以离散型分布为例,$\theta$是带估计参数,$p(o; \theta)$是观测结果为$o$的概率,对于一系列观测到的结果$o_1, o_2, o_3, …, o_n$,联合分布概率为

将这个联合分布概率$L(\theta)$称为似然函数
最大似然估计即为求$\theta$值,对应联合分布概率最大,即观测结果是最可能出现的场景。
对似然函数取对数

最大化此式即可(求导数、令导数为0得到似然方程、解似然方程得到参数,或使用梯度下降法等)。


对于逻辑回归

其中,$\theta$是一些列参数,$x$是特征,$y$是标签,$y=1|x$、$y=0|x$为某一次观测结果(在特征为x时,标签为y)
将概率写成一般形式(单次观测):

似然函数则为:

求最大似然,取log:

最大化此式即可。

多说一句,为什么逻辑回归求参数方法,使用最大似然法,而不是最小二乘法?
如果用最小二乘法,目标函数就是

是非凸的,不容易求解,会得到局部最优。
如果用最大似然估计,目标函数就是对数似然函数,是关于$\theta$的高阶连续可导凸函数,可以方便通过一些凸优化算法求解,比如梯度下降法、牛顿法等。