总体思路: 首先对数据进行预处理,针对在数据集中欺诈案例所占比例甚小,使用下采样与过采样对数据集进行均衡处理 针对机器学习方法,基于单层决策树分类器的adaboost集成学习模型建立。将处理好的数据集中随机选取70%作为训练集,30%作为测试集,针对训练数据利用单层决策树算法建立了多个弱分类器,通过迭代算法进行自适应参数调整学习
import pandas as pd
data= pd.read_csv('creditcard_data.csv')#datafile是excel文件,所以用read_excel,如果是csv文件则用read_csv
data.head()
#数据列名
data.columns
data.shape
数据集是284785条信用卡消费记录。数据共分 31 个字段, time为从数据集的第一条记录开始计算的时间,单位为秒。 amount为刷卡消费的金额, class为是否欺诈,在欺诈的情况下其值为 1,否则为0,其中 class值为1记录共有483条, class值为0记录共有284302条。字段 V1 到 V28是原数据集28 个字段数据,且 28 个字段均无详细说明。
import matplotlib.pyplot as plt
count_classes = pd.value_counts(data['Class'], sort = True).sort_index()
count_classes.plot(kind = 'bar')
plt.title("Fraud class histogram")
plt.xlabel("Class")
plt.ylabel("Frequency")
可以发现欺诈案例所占比例甚小。因此class类别极度不均衡中0,1二类分布极度不平衡,可能会导致算法出现巨大隐患如下:
① 从模型的训练过程来看:如果某类的样本数量很少,那么这个类别所提供的“信息”就太少,即使用经验风险(模型在训练集上的平均损失)最小化作为模型的学习准则,因此容易导致模型没有学习到如何去判别出少数类。
② 从模型的预测过程来看:数据挖掘模型一般是出于最大后验概率决策的角度考虑的,这意味着当模型估计的样本属于正类的后验概率要大于样本属于负类的后验概率时就将样本判为正类。但实际上,这个后验概率的估计值由于数据极度分布不平衡导致无法准确评估,当一个样本的预测几率大于观测几率时,就应该将样本判断为样本较多的类。
# sklearn数据预处理模块做数据预处理
from sklearn.preprocessing import StandardScaler
# sklearn的reshape函数设置-1,即让其自动计算有多少行 得到新的列
data['normAmount'] = StandardScaler().fit_transform(data['Amount'].values.reshape(-1, 1))
data = data.drop(['Time','Amount'],axis=1)
data.head()
归一化:“amount”的值可能大于100,而V28却是[-1,1]之间,不做归一化,模型会认为“amount”更重要 同时删除实时间变量time,因为其为离散数据,事实上对分类没用有太大的理论意义
以下做下采样:
根据统计我们知道,该数据集中class为0的交易信息数据共有284302条,class为1的交易信息数据共有483条, 根据下采样定义,抽取class为1的交易信息数据共有483条,随机抽取class为0的交易信息数据483条
# 取出x值和label值
X = data.ix[:, data.columns != 'Class']
y = data.ix[:, data.columns == 'Class']
# 拿到所有负例的个数和它们的index,用来做过采样
number_records_fraud = len(data[data.Class == 1])
fraud_indices = np.array(data[data.Class == 1].index)
# 拿到所有正例的index,如果是做欠采样,那么需要通过index随机取
normal_indices = data[data.Class == 0].index
# 从正例的数据集中采集负例个数的样本
random_normal_indices = np.random.choice(normal_indices, number_records_fraud, replace = False)
# 转换为numpy的格式
random_normal_indices = np.array(random_normal_indices)
# 合并正例和负例样本
under_sample_indices = np.concatenate([fraud_indices,random_normal_indices])
# pandas的索引来重新赋值
under_sample_data = data.iloc[under_sample_indices,:]
X_undersample = under_sample_data.ix[:, under_sample_data.columns != 'Class']
y_undersample = under_sample_data.ix[:, under_sample_data.columns == 'Class']
# Showing ratio
print("欺诈样本(1)比例: ", len(under_sample_data[under_sample_data.Class == 0])/len(under_sample_data))
print("正常样本(0)比例: ", len(under_sample_data[under_sample_data.Class == 1])/len(under_sample_data))
print("样本总量: ", len(under_sample_data))
from sklearn.model_selection import train_test_split
# 原始数据集切分 random_state 为洗牌随机切分 -- 切分后的验证集可以用于验证下采样的模型好坏
X_train, X_test, y_train, y_test = train_test_split(under_sample_data[['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10', 'V11',
'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20', 'V21',
'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'normAmount']],under_sample_data['Class'],test_size = 0.3, random_state = 0)
adaboost算法原理
import numpy as np
import matplotlib.pyplot as plt
#adaboost 实现
def stumpClassify(dataMatrix,dimen,threshVal,threshIneq):
"""
单层决策树分类函数
Parameters:
dataMatrix - 数据矩阵
dimen - 第dimen列,也就是第几个特征
threshVal - 阈值
threshIneq - 标志
Returns:
retArray - 分类结果
"""
retArray = np.ones((np.shape(dataMatrix)[0],1)) #初始化retArray为1
if threshIneq == 'lt':
retArray[dataMatrix[:,dimen] <= threshVal] = 1 #如果小于阈值,则赋值为-1
else:
retArray[dataMatrix[:,dimen] > threshVal] =0#如果大于阈值,则赋值为-1
return retArray
def buildStump(dataArr,classLabels,D):
"""
找到数据集上最佳的单层决策树
Parameters:
dataArr - 数据矩阵
classLabels - 数据标签
D - 样本权重
Returns:
bestStump - 最佳单层决策树信息
minError - 最小误差
bestClasEst - 最佳的分类结果
"""
dataMatrix = np.mat(dataArr); labelMat = np.mat(classLabels).T
m,n = np.shape(dataMatrix)
numSteps = 10.0; bestStump = {}; bestClasEst = np.mat(np.zeros((m,1)))
minError = float('inf') #最小误差初始化为正无穷大
for i in range(n): #遍历所有特征
rangeMin = dataMatrix[:,i].min(); rangeMax = dataMatrix[:,i].max() #找到特征中最小的值和最大值
stepSize = (rangeMax - rangeMin) / numSteps #计算步长
for j in range(-1, int(numSteps) + 1):
for inequal in ['lt', 'gt']: #大于和小于的情况,均遍历。lt:less than,gt:greater than
threshVal = (rangeMin + float(j) * stepSize) #计算阈值
predictedVals = stumpClassify(dataMatrix, i, threshVal, inequal)#计算分类结果
errArr = np.mat(np.ones((m,1))) #初始化误差矩阵
errArr[predictedVals == labelMat] = 0 #分类正确的,赋值为0
weightedError = D.T * errArr #计算误差
if weightedError < minError: #找到误差最小的分类方式
minError = weightedError
bestClasEst = predictedVals.copy()
bestStump['dim'] = i
bestStump['thresh'] = threshVal
bestStump['ineq'] = inequal
return bestStump, minError, bestClasEst
def adaBoostTrainDS(dataArr, classLabels, numIt = 40):
"""
使用AdaBoost算法提升弱分类器性能
Parameters:
dataArr - 数据矩阵
classLabels - 数据标签
numIt - 最大迭代次数
Returns:
weakClassArr - 训练好的分类器
aggClassEst - 类别估计累计值
"""
weakClassArr = []
m = np.shape(dataArr)[0]
D = np.mat(np.ones((m, 1)) / m) #初始化权重
aggClassEst = np.mat(np.zeros((m,1)))
for i in range(numIt):
bestStump, error, classEst = buildStump(dataArr, classLabels, D) #构建单层决策树
# print("D:",D.T)
alpha = float(0.5 * np.log((1.0 - error) / max(error, 1e-16))) #计算弱学习算法权重alpha,使error不等于0,因为分母不能为0
bestStump['alpha'] = alpha #存储弱学习算法权重
weakClassArr.append(bestStump) #存储单层决策树
expon = np.multiply(-1 * alpha * np.mat(classLabels).T, classEst) #计算e的指数项
D = np.multiply(D, np.exp(expon))
D = D / D.sum() #根据样本权重公式,更新样本权重
#计算AdaBoost误差,当误差为0的时候,退出循环
aggClassEst += alpha * classEst #计算类别估计累计值
aggErrors = np.multiply(np.sign(aggClassEst) != np.mat(classLabels).T, np.ones((m,1))) #计算误差
errorRate = aggErrors.sum() / m
if errorRate == 0.0: break #误差为0,退出循环
return weakClassArr, aggClassEst
def adaClassify(datToClass,classifierArr):
"""
AdaBoost分类函数
Parameters:
datToClass - 待分类样例
classifierArr - 训练好的分类器
Returns:
分类结果
"""
dataMatrix = np.mat(datToClass)
m = np.shape(dataMatrix)[0]
aggClassEst = np.mat(np.zeros((m,1)))
for i in range(len(classifierArr)): #遍历所有分类器,进行分类
classEst = stumpClassify(dataMatrix, classifierArr[i]['dim'], classifierArr[i]['thresh'], classifierArr[i]['ineq'])
aggClassEst += classifierArr[i]['alpha'] * classEst
return np.sign(aggClassEst)
if __name__ == '__main__':
dataArr=X_train.values.tolist()
LabelArr = y_train.values.tolist()
testArr= X_test.values.tolist()
testLabelArr =y_test.values.tolist()
weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, LabelArr)
predictions = adaClassify(dataArr, weakClassArr)
errArr = np.mat(np.ones((len(dataArr), 1)))
print('训练集的错误率:%.3f%%' % float(errArr[predictions != np.mat(LabelArr).T].sum() / len(dataArr) * 100))
predictions = adaClassify(testArr, weakClassArr)
errArr = np.mat(np.ones((len(testArr), 1)))
print('测试集的错误率:%.3f%%' % float(errArr[predictions != np.mat(testLabelArr).T].sum() / len(testArr) * 100))
过采样: smote,合成少数类过采样技术.它是基于随机过采样算法的一种改进方案,由于随机过采样采取简单复制样本的策略来增加少数类样本,这样容易产生模型过拟合的问题,即使得模型学习到的信息过于特别而不够泛化, smote算法的基本思想是对少数类样本进行分析并根据少数类样本人工合成新样本添加到数据集中,具体如下图所示,算法流程如下。
① 对于少数类中每一个样本 x,以欧氏距离为标准计算它到少数类样本集中所有样本的距离,得到其 k近邻。
② 根据样本不平衡比例设置一个采样比例以确定采样倍率 n,对于每一个少数类样本x,从其k近邻中随机选择若干个样本,假设选择的近邻为xn。
③ 对于每一个随机选出的近邻xn,分别与原样本按照如下的公式构建新的样本。 其数学模型如下:
import random
from sklearn.neighbors import NearestNeighbors
import numpy as np
class Smote:
"""
SMOTE过采样算法.
Parameters:
-----------
k: int
选取的近邻数目.
sampling_rate: int
采样倍数, attention sampling_rate < k.
newindex: int
生成的新样本(合成样本)的索引号.
"""
def __init__(self, sampling_rate=5, k=5):
self.sampling_rate = sampling_rate
self.k = k
self.newindex = 0
def fit(self, X, y=None):
if y is not None:
negative_X = X[y == 0]
X = X[y == 1]
n_samples, n_features = X.shape
# 初始化一个矩阵, 用来存储合成样本
self.synthetic = np.zeros((n_samples * self.sampling_rate, n_features))
# 找出正样本集(数据集X)中的每一个样本在数据集X中的k个近邻
knn = NearestNeighbors(n_neighbors=self.k).fit(X)
for i in range(len(X)):
k_neighbors = knn.kneighbors(X[i].reshape(1, -1),return_distance=False)[0]
# 对正样本集(minority class samples)中每个样本, 分别根据其k个近邻生成
# sampling_rate个新的样本
self.synthetic_samples(X, i, k_neighbors)
if y is not None:
return (np.concatenate((self.synthetic, X, negative_X), axis=0),
np.concatenate(([1] * (len(self.synthetic) + len(X)), y[y == 0]), axis=0))
return np.concatenate((self.synthetic, X), axis=0)
# 对正样本集(minority class samples)中每个样本, 分别根据其k个近邻生成sampling_rate个新的样本
def synthetic_samples(self, X, i, k_neighbors):
for j in range(self.sampling_rate):
# 从k个近邻里面随机选择一个近邻
neighbor = np.random.choice(k_neighbors)
# 计算样本X[i]与刚刚选择的近邻的差
diff = X[neighbor] - X[i]
# 生成新的数据
self.synthetic[self.newindex] = X[i] + random.random() * diff
self.newindex += 1
# ------通过过采样获取284302条calss为1的数据
import pandas as pd
import csv
df=pd.read_csv('creditcard_data.csv')
data = []
for i, element in enumerate(df['Class']):
if element == 1:
data.append(df.iloc[i, :])
X = np.array(data)
smote = Smote(sampling_rate=588, k=483)
data1=smote.fit(X).tolist()
len(data1)
fraud_indices=pd.DataFrame(data1,columns=df.columns)
fraud_indices.head()
#抽取calss为0的数据,并将之合并
normal_indices = df[df.Class == 0]
df=pd.concat([fraud_indices,normal_indices])
from sklearn.preprocessing import StandardScaler
# sklearn的reshape函数设置-1,即让其自动计算有多少行 得到新的列
df['normAmount'] = StandardScaler().fit_transform(df['Amount'].values.reshape(-1, 1))
df = df.drop(['Time','Amount'],axis=1)
df.head()
df.shape
print("欺诈样本(1)比例: ", len(df[df.Class == 0])/len(df))
print("正常样本(0)比例: ", len(df[df.Class == 1])/len(df))
print("样本总量: ", len(df))
from sklearn.model_selection import train_test_split
# 原始数据集切分 random_state 为洗牌随机切分 -- 切分后的验证集可以用于验证下采样的模型好坏
X_train, X_test, y_train, y_test = train_test_split(df[['V1', 'V2', 'V3', 'V4', 'V5', 'V6', 'V7', 'V8', 'V9', 'V10', 'V11',
'V12', 'V13', 'V14', 'V15', 'V16', 'V17', 'V18', 'V19', 'V20', 'V21',
'V22', 'V23', 'V24', 'V25', 'V26', 'V27', 'V28', 'normAmount']],df['Class'],test_size = 0.3, random_state = 0)
if __name__ == '__main__':
dataArr=X_train.values.tolist()
LabelArr = y_train.values.tolist()
testArr= X_test.values.tolist()
testLabelArr =y_test.values.tolist()
weakClassArr, aggClassEst = adaBoostTrainDS(dataArr, LabelArr)
predictions = adaClassify(dataArr, weakClassArr)
errArr = np.mat(np.ones((len(dataArr), 1)))
print('训练集的错误率:%.3f%%' % float(errArr[predictions != np.mat(LabelArr).T].sum() / len(dataArr) * 100))
predictions = adaClassify(testArr, weakClassArr)
errArr = np.mat(np.ones((len(testArr), 1)))
print('测试集的错误率:%.3f%%' % float(errArr[predictions != np.mat(testLabelArr).T].sum() / len(testArr) * 100))