BAT机器学习特征工程工作经验总结(一)如何解决数据不平衡问题(附python代码)

很多人其实非常好奇BAT里机器学习算法工程师平时工作内容是怎样?其实大部分人都是在跑数据,各种map-reduce,hive SQL,数据仓库搬砖,数据清洗、数据清洗、数据清洗,业务分析、分析case、找特征、找特征…而复杂的模型都是极少数的数据科学家在做。例如在阿里,算法工程师要挖掘业务场景,根据业务找出高效的特征,2周内可以完成一次特征迭代,一个月左右可以完成模型的小优化,来提升auc。因此特征很关键,腾讯、阿里他们得模型效果那么好多半归功于特征工程。

特征迭代的意思是把新造的、组合的、修正的特征加入模型中并且优化结果,这就是一次特征迭代。

工作中特征工程的流程一般是以下几点:

1. 数据采集
2. 数据清洗
3. 数据采样
4. 特征处理
5. 特征选择

而这篇总结核心主要在特征处理和特征选择上,特别是特征处理,除了对不同类型特征进行数据处理外,还有如何找到和构建组合特征。


数据采集
数据采集前需要明确采集哪些数据,一般的思路为:哪些数据对最后的结果预测有帮助?数据我们能够采集到吗?线上实时计算的时候获取是否快捷?

比如我要给用户做商品推荐,那我需要采集什么信息呢?
-店家:店铺的评分、店铺类别……
-商品:商品评分、购买人数、颜色、材质、领子形状……
-用户:历史信息(购买商品的最低价最高价)、消费能力、商品停留时间……


数据清洗
数据清洗在工作中也是很重要一步。数据清洗就是要去除脏数据。

那么如何判定脏数据呢?

  1. 简单属性判定:一个人身高3米+的人;一个人一个月买了10w的发卡。
  2. 组合或统计属性判定:号称在米国却ip一直都是大陆的新闻阅读用户?你要判定一个人是否会买篮球鞋,样本中女性用户85%?
  3. 补齐可对应的缺省值:不可信的样本丢掉,缺省值极多的字段考虑不用。

数据采样
采集、清洗过数据以后,正负样本是不均衡的,要进行数据采样。

既然总体中样本比例过低,很自然的思路就是从总体中重新抽样,提高建模样本中正样本的比例。

所谓正样本(positive samples)、负样本(negative samples),对于某一环境下的人脸识别应用来说,比如教室中学生的人脸识别,则教室的墙壁,窗户,身体,衣服等等便属于负样本的范畴。

过采样和欠采样是比较常用的方法,前者是增加正样本的数量,后者是减少负样本的数量。如果总体中正样本的绝对数量过少,可以将所有正样本全部纳入,再抽取部分负样本构建建模样本,这种思路其实就是过采样和欠采样的结合。

如果正样本量都很大,那么可以欠采样,也叫下采样
如果正样本数量不大就用采集更多的方法,比如
oversampling 过采样直接复制,还有smote算法,新生成更多的正样本。

#用index的方法去实现下采样和过采样
import numpy as np
import pandas as pd
 
 #下采样/欠采样
def lower_sample_data(df, percent=1):
    '''
    percent:正样本与负样本的比例,比如0.6,正样本是6,
    负样本是10
    '''
    data1 = df[df['Label'] == 1]  # 将少数正样本
    放在data1
    data0 = df[df['Label'] == 0]  # 将多数负样本
    放在data0
    index = np.random.randint(len(data0), 
    size=percent * len(data0))  
    # 随机给定下采样取出样本的序号
    lower_data0 = data0.iloc[list(index)]  
    # 下采样
    return(pd.concat([lower_data0, data1]))

#过采样
def over_sample_data(df, percent=1):
    '''
    percent:正样本与负样本的比例,比如0.6,正样本是6,
    负样本是10
    '''
    data1 = df[df['Label'] == 1]  # 将少数正样本
    放在data1
    data0 = df[df['Label'] == 0]  # 将多数负样本
    放在data0
    lack_percent = percent - (len(data1)/len(data0))
    index = np.random.randint(len(data1),
     size=lack_percent * len(data1))  
     # 随机给定下采样取出样本的序号
    over_data1 = data1.iloc[list(index)]  
    # 过采样
    return(pd.concat([over_data1, data1,data0]))

--------------------- 
#用imblearn库去实现过采样,下采样和smote

#过采样
from sklearn.datasets import make_classification
from collections import Counter

X, y = make_classification(n_samples=5000, 
n_features=2, n_informative=2,
n_redundant=0, n_repeated=0, 
n_classes=3,n_clusters_per_class=1,
weights=[0.01, 0.05, 0.94],
class_sep=0.8, random_state=0)

Counter(y)
Out[10]: Counter({0: 64, 1: 262, 2: 4674})
 
from imblearn.over_sampling import RandomOverSampler
 
ros = RandomOverSampler(random_state=0)
X_resampled, y_resampled = ros.fit_sample(X, y)
 
sorted(Counter(y_resampled).items())
Out[13]:
[(0, 4674), (1, 4674), (2, 4674)]

#smote
from imblearn.over_sampling import SMOTE
 
X_resampled_smote, y_resampled_smote = 
SMOTE().fit_sample(X, y)
 
sorted(Counter(y_resampled_smote).items())
Out[29]:
[(0, 4674), (1, 4674), (2, 4674)]

#下采样
和上面一样,用 imblearn.under_sampling的库

举一个数据不平衡的应用实战
本次分享的数据集来源于德国某电信行业的客户历史交易数据,该数据集一共包含条4,681记录,19个变量,其中因变量churn为二元变量,yes表示客户流失,no表示客户未流失;剩余的自变量包含客户的是否订购国际长途套餐、语音套餐、短信条数、话费、通话次数等。接下来就利用该数据集,探究非平衡数据转平衡后的效果。

# 导入第三方包
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn import model_selection
from sklearn import tree
from sklearn import metrics
from imblearn.over_sampling import SMOTE

# 读取数据
churn = pd.read_excel(r'C:\Users\Administrator\Desktop\Customer_Churn.xlsx') 

# 中文乱码的处理
plt.rcParams['font.sans-serif']=['Microsoft YaHei']

# 为确保绘制的饼图为圆形,需执行如下代码
plt.axes(aspect = 'equal')
# 统计交易是否为欺诈的频数
counts = churn.churn.value_counts()

# 绘制饼图
plt.pie(x = counts,labels=pd.Series(counts.index).map({'yes':'流失','no':'未流失'}), autopct='%.2f%%')
# 显示图形
plt.show()

经过可视化发现流失用户仅占到8.3%,相比于未流失用户,还是存在比较大的差异的。可以认为两种类别的客户是失衡的,如果直接对这样的数据建模,可能会导致模型的结果不够准确。不妨先对该数据构建随机森林模型,看看是否存在偏倚的现象。

原始数据表中的state变量和Area_code变量表示用户所属的“州”和地区编码,直观上可能不是影响用户是否流失的重要原因,故将这两个变量从表中删除。除此,用户是否订购国际长途业务international_plan和语音业务voice_mail_plan,属于字符型的二元值,它们是不能直接代入模型的,故需要转换为0-1二元值。

# 数据清洗
# 删除state变量和area_code变量
churn.drop(labels=['state','area_code'], axis = 1, inplace = True)

# 将二元变量international_plan和voice_mail_plan转换为0-1哑变量
churn.international_plan = churn.international_plan.map({'no':0,'yes':1}) 
churn.voice_mail_plan = churn.voice_mail_plan.map({'no':0,'yes':1}) 

经过清洗后的干净数据,接下来对该数据集进行拆分,分别构建训练数据集和测试数据集,并利用训练数据集构建分类器,测试数据集检验分类器。

# 用于建模的所有自变量
predictors = churn.columns[:-1]
# 数据拆分为训练集和测试集
X_train,X_test,y_train,y_test = 
model_selection.train_test_split(churn[predictors], churn.churn, random_state=12)

# 构建决策树
dt = tree.DecisionTreeClassifier(n_estimators = 300) 
dt.fit(X_train,y_train)
# 模型在测试集上的预测
pred = dt.predict(X_test)

# 模型的预测准确率
print(metrics.accuracy_score(y_test, pred))
# 模型评估报告
print(metrics.classification_report(y_test, pred))

如上结果所示,决策树的预测准确率超过93%,其中预测为no的覆盖率recall为97%,但是预测为yes的覆盖率recall却为62%,两者相差甚远,说明分类器确实偏向了样本量多的类别(no)。

# 绘制ROC曲线
# 计算流失用户的概率值,用于生成ROC曲线的数据
y_score = 
dt.predict_proba(X_test)[:,1] 
fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)

# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)

# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity') plt.ylabel('Sensitivity')
# 显示图形
plt.show()

如上图所示,ROC曲线下的面积为0.795,AUC的值小于0.8,故认为模型不太合理。(通常拿AUC与0.8比较,如果大于0.8,则认为模型合理)。接下来,利用SMOTE算法对数据进行处理。

# 对训练数据集作平衡处理
over_samples = SMOTE(random_state=1234) 
over_samples_X,over_samples_y = 
over_samples.fit_sample(X_train, y_train)

# 重抽样前的类别比例
print(y_train.value_counts()/len(y_train))
# 重抽样后的类别比例
print(pd.Series(over_samples_y).value_counts()/
len(over_samples_y))

如上结果所示,对于训练数据集本身,它的类别比例还是存在较大差异的,但经过SMOTE算法处理后,两个类别就可以达到1:1的平衡状态。下面就可以利用这个平衡数据,重新构建决策树分类器了。

# 基于平衡数据重新构建决策树模型
dt2 = ensemble.DecisionTreeClassifier(n_estimators = 300) 
dt2.fit(over_samples_X,over_samples_y)

# 模型在测试集上的预测
pred2 =dt2.predict(np.array(X_test))

# 模型的预测准确率
print(metrics.accuracy_score(y_test, pred2))
# 模型评估报告
print(metrics.classification_report(y_test, pred2))

如上结果所示,利用平衡数据重新建模后,模型的准确率同样很高,为92.6%(相比于原始非平衡数据构建的模型,准确率仅下降1%),但是预测为yes的覆盖率提高了10%,达到72%,这就是平衡带来的好处

# 计算流失用户的概率值,用于生成ROC曲线的数据
y_score = rf2.predict_proba(np.array(X_test))[:,1] 

fpr,tpr,threshold = metrics.roc_curve(y_test.map({'no':0,'yes':1}), y_score)

# 计算AUC的值
roc_auc = metrics.auc(fpr,tpr)

# 绘制面积图
plt.stackplot(fpr, tpr, color='steelblue', alpha = 0.5, edgecolor = 'black')
# 添加边际线
plt.plot(fpr, tpr, color='black', lw = 1)
# 添加对角线
plt.plot([0,1],[0,1], color = 'red', linestyle = '--')
# 添加文本信息
plt.text(0.5,0.3,'ROC curve (area = %0.3f)' % roc_auc)
# 添加x轴与y轴标签
plt.xlabel('1-Specificity') plt.ylabel('Sensitivity')

# 显示图形 电动叉车
plt.show()

你可能感兴趣的:(机器学习工作经验总结)