Bootstrap

Python数据分析案例45——基于融合模型(Stack)的电商用户购买行为预测

案例背景

最近618快到了,上电商购买的人很多,正好我手上还有这个用户购买行为的数据,就做了一个机器学习模型流程,然后也使用的都是常见的机器学习模型,但是加了一点创新吧,使用了stacking融合模型。简单来说就是使用了很多机器学习模型一起融合,这样的好处在于会降低方差,使预测结果更精准和鲁棒。


数据集介绍,大概长这个样子:

 最后一列是Y,是我们要预测的是否购买。其他的X具体如下:

数据属性包括:10个数值属性和8个类别属性,每列变量表示的含义如下:

“用户相关页面”、“用户页面访问持续时间”、“信息相关页面”、“信息相关页面访问持续时间”、“产品相关页面”和“产品相关页面访问持续时间”表示访问者在该会话中访问的不同类型页面的数量以及在这些页面类别中花费的总时间。这些特性的值来自于用户访问的页面URL  Session信息, Session记录了用户采取的操作(例如从一个页面移动到另一个页面)的实时更新信息。

“跳出率”、“退出率”和“页面值”特性代表了由Google Analytics为电子商务网站中的每个页面计算出来的指标。跳出率是指仅阅读了一个页面就离开的用户占一组页面或一个页面拜访次数的百分比。 跳出次数是指拜访者不拜访您网站的其他任何一页便从进入页退出的次数。 所以跳出率的算法就是:阅读了一个页面就离开网站的次数/进入网站的次数= 跳出率。退出率指:某个时间段内,离开网页的次数占该网页总浏览次数的比例。 比如,今天开心推首页综合浏览量是1000次,从这个页面离开本站的次数是40次,则首页的退出率是4%。“网页价值”是用户在进入目标网页或完成电子商务交易(或两者)之前访问过的网页的平均价值。

“节假日”属性表示网站访问时间接近某个特定的日子(如母亲节、情人节),在这一天附近会更有可能最终完成交易。

数据集还包括“操作系统类型”、“浏览器类型”、“区域”、“流量类型”、“访客类型”(老访客或新访客)、“周末”。

总之就是18个X,一个y,x代表用户浏览的时候的一些信息还有一些时间设备地区等基础信息。

需要这个演示数据和全部代码的同学可以参考:购买行为数据


Stacking模型介绍

所谓的Stacking就是通过模型对原数据拟合的堆叠进行建模,他首先通过基学习器学习原数据,然后这几个基学习器都会对原数据进行输出,然后将这几个模型的输出按照列的方式进行堆叠,构成了 ( m , p ) (m,p)(m,p) 维的新数据,m代表样本数,p代表基学习器的个数,然后将新的样本数据交给第二层模型进行拟合。

但是这样模型往往会过拟合,所以将上述方法进行改进,使用K折交叉验证的方式,不同的地方就是上面的示意图每个模型训练了所有的数据,然后输出y形成新的数据,使用K折交叉验证,每次只训练k-1折,然后将剩下1折的预测值作为新的数据,这就有效的防止了过拟合。

如果每个模型训练所有的数据,然后再用这个模型去预测y值,那么生成新数据的y非常精确和真实值差不多,为了增强模型的泛化能力,我们每次只训练其中一部分数据,然后用剩余一部分数据进行预测。

首先利用K折交叉验证,将数据分成4折切分,那么就会形成4组数据集,其中黄色代表训练集,绿色的为验证集,然后将每组的训练集交给模型进行训练,然后对验证集进行预测,就会得到对应验证集的输出,因为4折交叉验证,将数据分成4组,所以我们会形成4个验证集,然后将每个模型对各自组的验证集预测的结果进行按照行的方式堆叠,就会获得完整样本数据的预测值,这只是针对于一个模型,不同学习器同理,每个模型按照这个方式获得预测值,然后再将其按照列合并。

不过实际使用没这么复杂,skleran里面全部有现成的类,很简单看下面我怎么用的就行。


代码实现

数据准备

数据分析还是先导入包:

import numpy as np 
import pandas as pd 
import matplotlib.pyplot as plt 
import seaborn as sns 
#%matplotlib inline
plt.rcParams['font.sans-serif'] = ['KaiTi']  #中文
plt.rcParams['axes.unicode_minus'] = False   #负号

读取数据

df=pd.read_csv('./6-数据集/train.csv',na_values=['NA', 'NAN']) 
df.head()

查看数据的基础信息

df.info()

可以看到每个变量的类别是非空值的数量,缺失值是存在的,但是不多。#有几个变量存在略微的缺失值。


数据清洗和整理

缺失值填充

缺失值不多,那就用前一个有效值进行填充算了。

df=df.fillna(method='pad')  #前一个有效值进行填充

特征工程

我们要对文本的特征进行转化

#' 访客类型 ',' 月份',' 周末'这三个变量是文本型,需要进行处理

#对月份进行处理,映射为数值
d_month={'May': 5, 'Nov': 11, 'Mar': 3, 'Dec': 12, 'Oct': 10, 'Sep': 9, 'Aug': 8, 'Jul': 7, 'June': 6, 'Feb': 2}
df['月份']=df['月份'].map(d_month)

#周末变为0和1的虚拟变量

df['周末']=(df['周末']*1).astype('int')

#访客类型,生成哑变量

df=pd.get_dummies(df)

查看处理后的数据信息

df.info()

#所有变量都为数值型,可以直接计算,没有缺失值

然后取出X和y

#取出X和y
y=df['是否购买']
df.drop(['是否购买'],axis=1,inplace=True)
X=df.copy()
print(X.shape,y.shape)

可以看到是8631条数据,X19列,y一列。


数据探索

清洗完成后,我们开始探索数据特点

y.value_counts(normalize=True)

这是y的分布,大部分客户还是没有购买的,数据里面只有15%的客户发生了购买行为。

画图查看

# 查看y的分布
#分类问题
plt.figure(figsize=(4,2),dpi=128)
plt.subplot(1,2,1)
y.value_counts().plot.bar(title='响应变量柱状图图')
plt.subplot(1,2,2)
y.value_counts().plot.pie(title='响应变量饼图')
plt.tight_layout()
plt.show()

分析X,对x画核密度图,查看分布特点

#查看特征变量的核密度分布
dis_cols = 4                   #一行几个
dis_rows = 4
plt.figure(figsize=(3*dis_cols, 2*dis_rows),dpi=128)
for i in range(16):
    plt.subplot(dis_rows,dis_cols,i+1)
    sns.kdeplot(X[X.columns[i]], color="tomato" ,fill=True)
    #plt.xlabel((y.unique().tolist()),fontsize=12)
    plt.ylabel(df.columns[i], fontsize=12)
plt.tight_layout()
plt.show()

很多分布都是偏态的。 


相关系数热力图

corr = plt.subplots(figsize = (10,10),dpi=128)
corr= sns.heatmap(X.iloc[:,:16].assign(Y=y).corr(),annot=True,fmt='.2f',square=True)

X和y的相关性都不是很高的亚子。


开始机器学习 

#划分训练集和验证集

from sklearn.model_selection import train_test_split
X_train,X_val,y_train,y_val=train_test_split(X,y,test_size=0.2,random_state=0)

数据标准化

#数据标准化
from sklearn.preprocessing import StandardScaler
scaler = StandardScaler()
scaler.fit(X_train)
X_train_s = scaler.transform(X_train)
X_val_s = scaler.transform(X_val)
#X2_s=scaler.transform(data2)
print('训练数据形状:')
print(X_train_s.shape,y_train.shape)
print('验证集数据形状:')
print(X_val_s.shape,y_val.shape)

#采用八种机器学习模型对比准确率

from sklearn.linear_model import LogisticRegression
from sklearn.discriminant_analysis import LinearDiscriminantAnalysis
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.ensemble import RandomForestClassifier
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.svm import SVC
from sklearn.neural_network import MLPClassifier
from sklearn.ensemble import StackingClassifier

实例化

#逻辑回归
model1 =  LogisticRegression(C=1e10,max_iter=10000)
 
#线性判别
model2 =  LinearDiscriminantAnalysis()
 
#K近邻
model3 = KNeighborsClassifier(n_neighbors=20)
 
#决策树
model4 = DecisionTreeClassifier(random_state=77)
 
#随机森林
model5= RandomForestClassifier(n_estimators=500,  max_features='sqrt',random_state=10)
 
#梯度提升
model6 = GradientBoostingClassifier(random_state=123)
 
#支持向量机
model7 = SVC(kernel="rbf", random_state=77)
 
#神经网络
model8 = MLPClassifier(hidden_layer_sizes=(16,8), random_state=77, max_iter=10000)
 
model_list=[model1,model2,model3,model4,model5,model6,model7,model8]
model_name=['逻辑回归','线性判别','K近邻','决策树','随机森林','梯度提升','支持向量机','神经网络']

单独创建stacking模型作为model9

# 创建 stacking 模型
estimators = [
    ('逻辑回归', model1),
    ('线性判别', model2),
    ('K近邻', model3),
    ('决策树', model4),
    ('随机森林', model5),
    ('梯度提升', model6),
    ('支持向量机', model7),
    ('神经网络', model8)
]

stacking_model = StackingClassifier(estimators=estimators, final_estimator=LogisticRegression())

model_list.append(stacking_model)
model_name.append('Stacking模型')

print(model_list)
print(model_name)


可以看到,stacking模型把我前面8个模型全部都涵盖进去了。

#定义评价指标计算函数

from sklearn.metrics import confusion_matrix
from sklearn.metrics import classification_report
from sklearn.metrics import cohen_kappa_score

def evaluation(y_test, y_predict):
    accuracy=classification_report(y_test, y_predict,output_dict=True)['accuracy']
    s=classification_report(y_test, y_predict,output_dict=True)['weighted avg']
    precision=s['precision']
    recall=s['recall']
    f1_score=s['f1-score']
    #kappa=cohen_kappa_score(y_test, y_predict)
    return accuracy,precision,recall,f1_score #, kappa


拟合模型,计算指标

df_eval=pd.DataFrame(columns=['Accuracy','Precision','Recall','F1_score'])
for i in range(len(model_list)):
    print(f'{model_name[i]}训练中...')
    model_C=model_list[i]
    name=model_name[i]
    model_C.fit(X_train_s, y_train)
    pred=model_C.predict(X_val_s)
    s=classification_report(y_val, pred)
    s=evaluation(y_val,pred)
    df_eval.loc[name,:]=list(s)

查看计算的评价指标

df_eval

可以看到,基础模型里面,准确率最好的市随机森林,但是在表示综合来看的F1值,stack模型比其他模型都稍微好一点点。看来stacking,融合还是有效的。

画图可视化

bar_width = 0.4
colors=['c', 'b', 'g', 'tomato', 'm', 'y', 'lime', 'k','orange','pink','grey','tan']
fig, ax = plt.subplots(2,2,figsize=(10,8),dpi=128)
for i,col in enumerate(df_eval.columns):
    n=int(str('22')+str(i+1))
    plt.subplot(n)
    df_col=df_eval[col]
    m =np.arange(len(df_col))
    plt.bar(x=m,height=df_col.to_numpy(),width=bar_width,color=colors)
    
    #plt.xlabel('Methods',fontsize=12)
    names=df_col.index
    plt.xticks(range(len(df_col)),names,fontsize=10)
    plt.xticks(rotation=40)
    plt.ylabel(col,fontsize=14)
    
plt.tight_layout()
#plt.savefig('柱状图.jpg',dpi=512)
plt.show()

本来想#利用K折交叉验证搜索最优超参数

from sklearn.model_selection import KFold, StratifiedKFold
from sklearn.model_selection import GridSearchCV,RandomizedSearchCV

但是stacking模型参数太多了,他包含8个基础机器学习模型的所有参数,所以就算了,。。简单计算一些混淆矩阵好了

stacking_model = StackingClassifier(estimators=estimators, final_estimator=LogisticRegression())#, stack_method='predict_proba'
stacking_model.fit(X_train_s, y_train)
pred_stack=stacking_model.predict(X_val_s)

打印混淆矩阵

print(classification_report(y_val, pred_stack))


画混淆矩阵和计算AUC

上面是打印,下面可视化

from sklearn.metrics import ConfusionMatrixDisplay
ConfusionMatrixDisplay.from_estimator(stacking_model, X_val_s, y_val,cmap='Blues')
plt.tight_layout()

AUC图

from sklearn.metrics import RocCurveDisplay
RocCurveDisplay.from_estimator(stacking_model, X_val_s, y_val)
x = np.linspace(0, 1, 100)
plt.plot(x, x, 'k--', linewidth=1)
plt.title('ROC Curve (Test Set)')

AUC的值

from sklearn.metrics import roc_auc_score
roc_auc_score(y_val,stacking_model.predict_proba(X_val_s)[:,1])

##使用该模型在全部数据上进行训练,作为最终的预测模型

#利用找出来的最优超参数在所有的训练集上训练,然后预测
model=StackingClassifier(estimators=estimators, final_estimator=LogisticRegression())
model.fit(np.r_[X_train_s,X_val_s],np.r_[y_train,y_val])  #使用所有数据训练
pred=model.predict(np.r_[X_train_s,X_val_s])
evaluation(np.r_[y_train,y_val],pred)

得到的模型然后储存下来

import joblib # 保存模型文件
joblib.dump(model, "train_model.m")

好,得到了模型文件了。

下次我们有测试集数据后,就直接预测。


对测试集预测

#读取训练好的模型

import joblib
mode= joblib.load("train_model.m")
#读取数据,和上面一样处理
df1=pd.read_csv('./6-数据集/test1.csv',na_values=['NA', 'NAN'],encoding='ANSI') 
df1.head()

可以看到和训练集是一模一样,但是少了一列y,也就是我们需要预测的y。

前面的特征工程都同样要处理:

df1=df1.fillna(method='pad')  #前一个有效值进行填充
#对月份进行处理,映射为数值
d_month={'May': 5, 'Nov': 11, 'Mar': 3, 'Dec': 12, 'Oct': 10, 'Sep': 9, 'Aug': 8, 'Jul': 7, 'June': 6, 'Feb': 2,'Jan':1,'Apr':4}
df1['月份']=df1['月份'].map(d_month)
#周末变为0和1的虚拟变量
df1['周末']=(df1['周末']*1).astype('int')
#访客类型,生成哑变量
df1=pd.get_dummies(df1)
for col in df.columns:
    if col not in df1.columns:
        df1[col]=0
df1.shape

标准化

X2=df1[df.columns]
X2_s = scaler.transform(X2)

 预测,然后查看分布

predictY=model.predict(X2_s)
d=pd.DataFrame()
d['pred']=predictY
d['pred'].value_counts(normalize=True)

也和训练集差不多的比例。

储存预测结果,就可以提交了。

d.to_csv('Results.csv',encoding='utf-8',index=False,header=False)


 创作不易,看官觉得写得还不错的话点个关注和赞吧,本人会持续更新python数据分析领域的代码文章~(需要定制类似的代码可私信)

  

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;