天池算法大赛思路和代码分享

目录:

思路概述

1,数据探索

2,特征工程

3,机器学习模型的初步预测和特征工程

4,变量衍生和PCA降维

5,多模型融合预测

赛题介绍详见天池官网:UNiLAB智慧能源系统大数据分析赛 赛道2:不完整电压数据的电力系统稳定性评估,数据注册登录后可下载

思路概述

算法大赛赛题,数据量不大,训练数据为2500*3900,预测数据2536*1950,数据类型均为浮点数,无空缺值,数据友好。拿到数据后,反复阅读赛题,对赛题的理解,可以少走一些弯路。既然都是浮点数,在开赛阶段直接用现有数据套个模型提交试水,果然,效果感人。。。

后转换思路,从分位值和具体数据大小方面“构造”特征进行后续的分析。在特征工程方面,主要从特征和标签(下文的label)的相关性重要性、特征与特征之间的相关性、特征衍生和降维三方面展开。反复、多次修改(美其名曰:迭代),改到后来终于把代码变成了一座si山,连命名都乱了。。。

模型选用方面,一开始是非常看好逻辑回归模型的,但从实际结果看,单一的弱模型 和 “弱+强模型+模型融合”的半监督学习模型,差距还是挺大的。最终的预测模型共两层:第一层使用knn、逻辑回归、随机森林,将拟合结果作为新的“特征”并入原始数据集,作为第二层模型的特征集(类似stacking融合,但此处没有对训练集进行切分);第二层为LightGB模型,拟合出最终的提交结果

1,数据探索

导入需要用到的包、

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.model_selection import train_test_split,GridSearchCV,cross_val_score
from sklearn.preprocessing import MinMaxScaler,StandardScaler
from sklearn.metrics import accuracy_score,mean_squared_error
from sklearn.neighbors import KNeighborsClassifier
from sklearn.linear_model import LogisticRegression
from sklearn import tree
from sklearn.tree import DecisionTreeClassifier 
from sklearn.ensemble import RandomForestClassifier
import xgboost as xgb
from lightgbm import LGBMClassifier
from scipy import stats 
from sklearn.linear_model import RidgeClassifier
from statsmodels.stats.outliers_influence import variance_inflation_factor
from sklearn.decomposition import PCA

读入训练数据,并查看基本信息:

天池算法大赛思路和代码分享_第1张图片

同时,可查看数据的最大值、最小值、空缺值等。查看可得: 数据共2500行、3900列,均为浮点数,无空缺值,columns为1-3900;

读取并查看预测集数据可知:预测集和训练集数据类似,均为浮点数,无空缺值,只是列数少一半,行数多36行,为2536*1950的数据。此处不再放代码

读取并查看训练集的label数据:

 可见:该赛题为二分类问题,样本分布均匀。

2,特征工程

由数据探索可知,训练数据上了3900个特征,随机抽取两个特征进行比较,两者之间未发现明显的区别(从赛题中可以了解到,这些特征都是电压数据,保持在0-1.3波动,所有特征为同一类型的工业数据),考虑到特征过多,此处没有直接将这些特征入模,而是“构造”新的特征来进行探索分析。

新构造出的特征分三类,第一类是超过某个值的数量比例如电压大于0.6出现的次数比例,第二类是分位指标如0.1分位、0.25分位、0.75分位等,第三类是总体指标如极差、方差、峰度等。构造特征的函数如下:

#数量特征构造,大于某个阈值的电压出现的次数
def n_feature(df):
    w = df.shape[1]
    a = pd.DataFrame( (df <0.50).sum(axis=1)/w,columns=["_05"])
    a1 = pd.DataFrame( (df <0.60).sum(axis=1)/w,columns=["_06"])
    a2 = pd.DataFrame( (df <0.70).sum(axis=1)/w,columns=["_07"])
    a3 = pd.DataFrame( (df <0.80).sum(axis=1)/w,columns=["_08"])
    a4 = pd.DataFrame( (df <0.90).sum(axis=1)/w,columns=["_09"])

    a2_a = pd.DataFrame( a2.values-a.values,columns=["n_1"])
    a3_a1 = pd.DataFrame( a3.values-a1.values,columns=["n_2"])
    
    n_feature = pd.concat([a,a1,a2,a3,a4,a2_a,a3_a1],axis=1)
    
    return(n_feature)


#分位数据和总体数据
def q_feature(df):
    df['f_min'] =df.min(axis=1)
    df['f_quan015'] = df.quantile(q = 0.0015 , axis=1)
    df['f_quan03'] = df.quantile(q = 0.003 , axis=1)
    df['f_quan061'] = df.quantile(q = 0.0061 , axis=1)
    df['f_quan12'] = df.quantile(q = 0.012 , axis=1)
    df['f_quan1'] = df.quantile(q = 0.02 , axis=1)
    df['f_quan2'] = df.quantile(q = 0.04 , axis=1)
    df['f_quan22']= df.quantile(q = 0.06, axis=1)
    df['f_quan23']= df.quantile(q = 0.08 , axis=1)
    df['f_quan3']= df.quantile(q = 0.1 , axis=1)
    df['f_quan4']= df.quantile(q = 0.25 , axis=1)
    df['f_quan5']= df.quantile(q = 0.50 , axis=1)
    df['f_quan6']= df.quantile(q = 0.75 , axis=1)
    df['f_quan7']= df.quantile(q = 0.90 , axis=1)
    df['f_quan8']= df.quantile(q = 0.95 , axis=1)
    df['r015'] =df['f_quan1'] - df['f_quan061']
    df['r015_2'] =df['f_quan3'] - df['f_quan061']
    df['r03'] =df['f_quan4'] - df['f_quan061']
    df['r015_3'] =df['f_quan3'] - df['f_quan1']
    df['r03_2'] =df['f_quan5'] - df['f_quan061']
    df['r03_3'] =df['f_quan1'] - df['f_quan03']
    df['r03_4'] =df['f_quan3'] - df['f_quan03']
    df['r1'] =df['f_quan6'] - df['f_quan1']
    df['r2'] =df['f_quan6'] - df['f_quan2']
    df['r23'] =df['f_quan6'] - df['f_quan23']
    df['r3'] =df['f_quan6'] - df['f_quan3']
    df['r4'] =df['f_quan6'] - df['f_quan4']
    df['f_max'] =df.max(axis=1) 
    df['f_r'] = df['f_max'] - df['f_min']
    df['f_mean']= df.mean(axis =1)
    df['f_var'] = df.var( axis=1)
    df['f_kurt'] = df.kurt(axis=1)
    
    return(df)

好像。。命名不是很规范哈。。结合下面的步骤和3的步骤,此处经历了N次修改,有些凌乱。。。【手动抹泪】

调用函数,生成特征,并将label拼接入训练数据:

n_feature_ = n_feature(df_train)
df_train = q_feature(df_train)

df_train = pd.concat([df_train,n_feature_
                #,nf
                ,lab
               ],axis=1)
df_train.head()

天池算法大赛思路和代码分享_第2张图片

同时,此处可在预测集上调用函数,完成预测集特征的构造

构造好特征后,查看特征之间的关系和特征与label之间的关系。由于此处经历多次调整、修改,故:

1,此处仅将筛选好的、最终用于建模的特征进行展示。

#最后筛选出用于建模的17个特征如下:
col_ys =['f_min','f_quan061', 'f_quan1','f_quan2',
 'f_quan3','f_quan4','f_quan5', 'f_quan6',
 'f_quan7','f_quan8','f_max','f_mean',
 'f_quan22','f_quan23', '_07','_08',
 '_09']



df_ys = df_d[col_ys]#选择该17个变量的数据
df_ys = df_ys.reset_index(drop=True)#重置索引
df_ys

2,在步骤3用模型进行初步预测时,发现 f_min 这个特征与label相关性巨大:当 f_min >0.65时,label均为1,当f_min <0.3时,label均为-1,因此,在接下来的建模中,f_min特征直接当做“规则”先对数据进行处理,剩余的f_min在0.3-0.65之间的数据使用机器学习模型进行预测。

#f_min大于0.65时,label为1,f_min小于0.30时,label为-1;

def min_select(df):
    df_d = df.loc[df["f_min"]<0.65]
    df_d = df_d.loc[df["f_min"]>0.30]
    #df_d = df_d.iloc[:,df_d.columns != "f_min"]
    #df = df.drop(["f_min"],axis=1)
    return(df_d)

df_d = min_select(df_train)

df_d.shape[0],  df_d["label"].value_counts()

使用f_min“规则”后,2500行的数据剩余517行,label分布存在不均衡问题,在接下的建模中需要注意 

查看特征分布:

column = df_ys.columns
fig = plt.figure(figsize=(80,60),dpi=50)
for i in range(35) :
    plt.subplot(7,4,i+1)
    sns.boxplot(df_ys[column[i]],orient="v",width=0.5  )
    plt.ylabel(column[i],fontsize=36)
plt.show()

部分结果:

天池算法大赛思路和代码分享_第3张图片

查看训练集特征和测试机特征的分布情况。如某特征差异比较大,会影响模型在预测集上的表现,应考虑删除。

dist_cols =6
dist_rows = len(test1.columns )
plt.figure(figsize=(4*dist_cols,4*dist_rows ),dpi=100)
i =1
for col in test1.columns:
    ax = plt.subplot(dist_rows,dist_cols,i )
    ax = sns.kdeplot(df_ys[col],color="red",shade=True )
    ax = sns.kdeplot(test1[col],color="blue",shade=True )
    ax.set_xlabel(col)
    ax.set_ylabel("different")
    ax = ax.legend(["df_ys","test1"])
    i += 1
plt.show

 天池算法大赛思路和代码分享_第4张图片

 查看特征之间共线性:

train = df_ys.copy()

scaler = MinMaxScaler()   #vif之前先对数据进行去量纲处理
scaler.fit(train)
train_mms = scaler.transform(train)
train_mms = pd.DataFrame(train_mms,columns=train.columns )

vif_list = [variance_inflation_factor(x,i) for i in range(x.shape[1])  ]

vif_list

天池算法大赛思路和代码分享_第5张图片

 特征之间的多重共线性还是比较严重的,尝试删除部分特征后进行预测(此处的特征已经是从最初的40个删除为17),但预测结果直接打脸,还不如保留这些特征。。。

查看特征和label分布关系:

#此处df1为df_ys合并label后的数据,倒腾了太多次。。嗯额嗯。。一开始命名还遵守驼峰规则,迭代次数多了就乱了啊哈哈哈

plt.scatter(df1.f_min, df1.f_quan4,   #此处可任意更换特征名称查看
            c=df1.label,
            cmap='rainbow')

天池算法大赛思路和代码分享_第6张图片

查看特征和label之间的线性关系强弱:

pd.set_option('display.max_columns',10)
pd.set_option('display.max_rows',10)
df1 = df_ys
df1["label"]=lab
df_ys_corr = df1.corr()
df_ys_corr

#此处没有画热力图,直接排序哈
df_ys_corr["label"].sort_values(ascending=False )

天池算法大赛思路和代码分享_第7张图片

特征与label之间的非线性关系可通过树模型进行查看

3,机器学习模型的初步预测和特征工程

数据划分:

train = df1.iloc[:,df1.columns != "label"]
lab = df1.iloc[:,df1.columns == "label"]

xtrain,xtest,ytrain,ytest=train_test_split(train,lab,test_size=0.3,random_state=90)

从步骤1可知,特征均为float数据,无任何分类或排序特征,故首先选用逻辑回归模型对数据进行初步的建模并查看相关系数:

sta = StandardScaler()
sta =sta.fit(xtrain)
xtrain_sta = sta.transform(xtrain)
xtest_sta  = sta.transform(xtest)
train_sta = sta.transform(train)

lr = LogisticRegression( C= 5,
                        penalty = 'l2',
                        class_weight ={-1:2,1:1}
                       )
lr = lr.fit(train_sta,lab)
lr.score(train_sta,lab), 
cross_val_score(lr,train_sta,lab,cv=4),       lr.coef_

 可用pd.concat将lr.coef_和columns拼接查看,更直观。此处可以结合步骤2,对特征进行筛选

使用随机森林进行预测并查看特征重要性:

rdf = RandomForestClassifier(max_depth= 3
                            ,criterion= "entropy"
                             ,n_estimators=100) 
rdf.fit(train,lab)


#rdf.score(xtrain,ytrain),  rdf.score(xtest,ytest),  
cross_val_score(rdf,train,lab,cv=4),   rdf.feature_importances_

使用KNN模型进行预测:

scaler = MinMaxScaler()
scaler.fit(xtrain)
train_mms = scaler.transform(train)
xtrain_mms = scaler.transform(xtrain)
xtest_mms  = scaler.transform(xtest)


knn_mms =  KNeighborsClassifier(n_neighbors=5
                           ,weights = 'distance'
                           )
knn_mms.fit(xtrain_mms,ytrain)
#knn_mms.score(xtest_mms,ytest),
cross_val_score(knn_mms,train_mms,lab,cv=4)

使用LightGB模型进行预测:

LGB = LGBMClassifier(objective= 'binary',
                     metric = 'error',
                     boosting_type = 'gbdt',  #
                     #num_leaves =32  , #
                     max_depth =4,
                     learning_rate = 0.05,
                    n_estimators = 200,
                    subsample = 0.7,
                    colsample_bytree = 0.7,
                   #  subsample_freq =1,
                   #  reg_alpha =0.1,
                   #  reg_lambda =0.1,
                     #silent =True,
                    # min_split_gain = 0.1 ,
                    # min_child_weight = 0.01
                    )

LGB = LGB.fit(train,lab)

LGB.score(train,lab),   cross_val_score(LGB,train,lab,cv=4)

对LGB 的参数进行网格搜索:

#第一次先搜索一部分参数,搜索出来后当成现成参数带入模型,再搜索另一部分参数,以此类推进行参数调优。
#如果一次搜索太多参数,容易把电脑跑死。。。

LGB1 = LGBMClassifier(objective= 'binary',
                     metric = 'error',
                     boosting_type = 'gbdt',  #
                     #num_leaves =32  , #
                     n_estimators = 100,
                      #max_depth = 1
                    )

p = {

    'max_depth' :[2,3,4,5],
    'learning_rate' : [0.05, 0.2, 0.3, 0.4,1],
    'subsample' :[0.7 ,0.8,1],
    'colsample_bytree': [0.7 ,0.8,1]
   
}

gri = GridSearchCV(LGB1,p,cv=4)

gri.fit(train_use,lab)
gri.best_score_, gri.best_params_

 除LGB 模型外,其他模型均可进行网格搜索。训练好模型后,对预测集进行预测并提交。提交后成绩:逻辑回归≈  LGB > 随机森林 > K值邻近。此时成绩还有很大提升空间

4,特征衍生和PCA降维

直接使用加减乘(数据中有为0和趋近于0的数据,故没有用除法。当然把极值和0删除后也可进行除法衍生)进行暴力衍生:

#变量衍生加法
def _add(df_d):
    """变量衍生:加法"""
    feature_add = pd.DataFrame(df_d.index,columns=["_add"])
    colum = df_d.columns
    for ii in range(0,df_d.shape[1]-1):
        df_d.iloc[:,ii]
        for i in range(ii+1, df_d.shape[1]):
            v =df_d.iloc[:,ii] + df_d.iloc[:,i]
            a =colum[ii] 
            b =colum[i]
            c = f'{a}_add_{b}'
            feature_add[c] = v
 
    feature_add = feature_add.iloc [:,1:]
    feature_add
    return(feature_add)   


#变量衍生 减法

def _sub(df_d):
    """变量衍生:减法"""
    feature_sub = pd.DataFrame(df_d.index,columns=["_sub"])
    colum = df_d.columns
    for ii in range(0,df_d.shape[1]-1):
        df_d.iloc[:,ii]
        for i in range(ii+1, df_d.shape[1]):
            v =df_d.iloc[:,ii] - df_d.iloc[:,i]
            a =colum[ii] 
            b =colum[i]
            c = f'{a}_sub_{b}'
            feature_sub[c] = v
            
    feature_sub = feature_sub.iloc [:,1:]
    feature_sub
    return(feature_sub)  


def _mul(df_d):
    """乘法"""
    feature_mul = pd.DataFrame(df_d.index,columns=["_mul"])
    colum = df_d.columns
    for ii in range(0,df_d.shape[1]-1):
        df_d.iloc[:,ii]
        for i in range(ii+1, df_d.shape[1]):
            v =df_d.iloc[:,ii] * df_d.iloc[:,i]
            a =colum[ii] 
            b =colum[i]
            c = f'{a}_mul_{b}'
            feature_mul[c] = v
            
    feature_mul = feature_mul.iloc [:,1:]
    feature_mul
    return(feature_mul)


#变量衍生 除法
def _div(df_d):
    """变量衍生:除法"""

    feature_div = pd.DataFrame(df_d.index,columns=["_div"])
    colum = df_d.columns
    for ii in range(0,df_d.shape[1]-1):
        df_d.iloc[:,ii]
        for i in range(ii+1, df_d.shape[1]):
            v =df_d.iloc[:,ii] / df_d.iloc[:,i]
            a =colum[ii] 
            b =colum[i]
            c = f'{a}_div_{b}'
            feature_div[c] = v

    feature_div = feature_div.iloc [:,1:]
    feature_div
    return(feature_div)


#变量衍生函数:加减乘除的结果输出:

def _ys(df_d):
    a = _add(df_d)
    b = _sub(df_d)
    c = _mul(df_d)
    #d = _div(df_d)   #除法
    feature_ys = pd.concat ([a,b,c,
                            #d
                            ],axis=1)
    feature_ys
    return(feature_ys)


train2= _ys(train)
train2

运行结果:

天池算法大赛思路和代码分享_第8张图片

可见,衍生后,新增408个特征。同样的,调用函数,对测试集数据进行特征衍生:

test2 =_ys(test1)
test2

 衍生的训练集为train2,测试集为test2。

使用PCA对衍生特征进行降维,提取其中权重较大的特征,并和原来的17个特征进行合并。

#将训练集和测试集进行合并。如果是实际应用,这里好像有信息泄露的风险哈
concat_ = pd.concat([train1,test1],axis=0)
concat_

#PCA之前记得标准化
sta_ys = StandardScaler()
sta_ys =sta_ys.fit(concat_)
concat_sta = sta_ys.transform(concat_)
#test2_sta = sta_ys.transform(test2)
pca = PCA().fit(concat_sta)
#pca.explained_variance_

plt.figure(figsize=[20,5] )
plt.plot(pca.explained_variance_ratio_ )
plt.show()

运行结果:

天池算法大赛思路和代码分享_第9张图片

提取权重前10的特征,并将训练集和预测集重新分开:

#提取权重前10的因子
pca = PCA(n_components=10 ).fit(concat_sta)
concat_sta_10 = pca.transform(concat_sta)
train_pca =  pd.DataFrame(  concat_sta_10).loc[0:516,:]     #训练集
test_pca =  pd.DataFrame(  concat_sta_10).loc[517:1007,:]   #测试集

将PCA得到的特征和原来的17个特征拼接:

#把pca数据和原始数据 拼接回去
train_use = pd.concat([train,train_pca],axis=1)
train_use 

test_use = pd.concat([test1,test_pca],axis=1)
test_use 

天池算法大赛思路和代码分享_第10张图片

5,多模型融合预测

使用KNN、逻辑回归、随机森林作为模型第一层(可通过网格搜索对参数进行调优),对数据进行拟合,将拟合结果作为新的“特征”并入原始数据集(原始数据集为27个特征,并入后变成30个特征)形成新的数据集,再使用LGB作为模型第二层对新数据集进行拟合,形成最终的预测结果。

模型第一层:模型代码和步骤3类似,此处不再重复。参数调优后,输出dataframe格式的拟合结果,并将拟合结果和原始数据集合并。可先对单个模型进行参数调优,调优后将三个模型和数据合并的代码封装成函数,方便调用:

def model_level1(data):
    """模型第一层,以下需先对单个模型进行参数调优"""

    xtrain,xtest,ytrain,ytest=train_test_split(train2,lab,test_size=0.3,random_state=90)

    
    #随机森林
    rdf = RandomForestClassifier(max_depth= 2
                            ,criterion= "entropy"
                             , min_samples_leaf = 2
                             # ,min_impurity_decrease =0.001
                             ,n_estimators= 100) 
    rdf.fit(train_use,lab)
    
    #逻辑回归
    sta = StandardScaler()
    sta =sta.fit(xtrain)
    xtrain_sta = sta.transform(xtrain)
    train_sta = sta.transform(train)
    lr = LogisticRegression( C= 1,
                        penalty = 'l2',
                        class_weight ={-1:1.2,1:1} )
    lr = lr.fit(train_sta,lab)
    
    #K值邻近
    scaler = MinMaxScaler()
    scaler.fit(xtrain)
    train_mms = scaler.transform(train)
    xtrain_mms = scaler.transform(xtrain)
    xtest_mms  = scaler.transform(xtest)
    knn_mms =  KNeighborsClassifier(n_neighbors=5
                           ,weights = 'distance'
                           )
    knn_mms.fit(xtrain_mms,ytrain)
    
    #对data进行变准化和归一化,此处容易遗漏
    data_sta = sta.transform(data)
    data_mms  = scaler.transform(data)
    
    #将拟合结果转化成dataframe格式
    a = pd.DataFrame(rdf.predict(feature_p), columns=["rdf_pr"] )
    b = pd.DataFrame(lr.predict(data_sta), columns=["lr_pr"]  ) 
    c = pd.DataFrame(knn_mms.predict(data_mms),columns=["knn_pr"] )
    
    #合并数据,形成新的数据集data_new
    data_new = pd.concat([data,a,b,c],axis=1)
    
    return(data_new)
    

模型第二程:使用LGBMClassifier对新数据集进行拟合,形成最终预测结果。模型代码和步骤3类似,此处不再重复。

提交结果后可知,490条记录中,预测正确480条,预测错误10条,加上f_min大于0.65和小于0.3的数据后,共2536条记录,最终结果:预测正确2526条,预测错误10条

你可能感兴趣的:(算法,机器学习,python,人工智能,数据分析)