机器学习初学-Keras从单隐层神经网络到深度神经网络预测客户流失率

准备

Kaggle Kaggle: Your Machine Learning and Data Science Community

数据集 Bank Customer | Kaggle

案例最终完整Notebook https://www.kaggle.com/orlosziming/keras-bank-customer

准备

1 导入csv

import numpy as np #导入Num Py库 
import pandas as pd #导入Pandas库 
df_bank = pd.read_csv("/kaggle/input/bank-customer/BankCustomer.csv") # 读取文件 
df_bank.head() # 显示文件前5行数据

2 数据分析

import matplotlib.pyplot as plt #导入Matplotlib库 
import seaborn as sns #导入Seaborn库 
# 显示不同特征的分布情况 

# 定义要绘制计数分布的特征列表
features = ['City', 'Gender', 'Age', 'Tenure', 'ProductsNo', 'HasCard', 'ActiveMember', 'Exited']

# 创建一个图形对象,设置图形的大小为 15x15
fig = plt.subplots(figsize=(15, 15))

# 遍历特征列表并绘制子图
for i, j in enumerate(features):
    # 创建子图,将图形分成 4 行 2 列,指定索引为 i+1
    plt.subplot(4, 2, i+1)
    
    # 打印当前特征的索引和特征名,便于调试和理解
    print(i, j)

    # 使用 Seaborn 的 countplot 绘制计数图,x轴为特征 j,数据来源于数据集 df_bank
    sns.countplot(x=j, data=df_bank)

数据清洗,构建可以用的特征和标签集合 

对这个数据集,我们主要做以下3方面的清洗工作: 
 (1)性别。这是一个二元类别特征,需要转换为0/1代码格式进行读取处理(机器学习中的文本格式数据都要转换为数字代码)。 
 (2)城市。这是一个多元类别特征,应把它转换为多个二元类别哑变量。 
 (3)姓名这个字段对于客户流失与否的预测应该是完全不相关的,可以在进一步处理之前忽略。

df_bank['Gender'].replace("Female", 0, inplace = True) 
df_bank['Gender'].replace("Male", 1, inplace=True) 

# 把多元类别转换成多个二元类别哑变量, 然后放回原始数据集 
d_city = pd.get_dummies(df_bank['City'], prefix = "City") 
df_bank = [df_bank, d_city] 
df_bank = pd.concat(df_bank, axis = 1) 
#df_bank是一个DataFrame对象,通过 pd.concat(df_bank, axis=1)表示将df_bank中的列按照水平方向进行连接,并返回一个新的DataFrame对象

# 构建特征和标签集合 
y = df_bank ['Exited'] 
X = df_bank.drop(['Name', 'Exited', 'City'], axis=1) 

X.head() #显示新的特征集

 

用Keras实现单隐层神经网络

1 用序贯模型构建网络

在这段代码中,我们创建了一个人工神经网络(Artificial Neural Network,ANN)模型。

首先,让我们了解一下激活函数的作用。激活函数在神经网络的每个神经元上应用非线性变换,使得神经网络能够学习和表示更复杂的模式和特征。激活函数帮助确定神经元的输出并引入非线性性质,使神经网络能够适应不同的数据分布。

现在让我们来看一下 `relu`(整流线性单元)激活函数。ReLU 是一种简单而常用的激活函数,它将负输入值设为零,并保持正输入值不变。这意味着它对于正值的输入输出为输入本身,而对于负值的输入输出为零。ReLU 具有以下优点:

1. 非线性:ReLU 引入了非线性,使得神经网络可以学习和表示非线性关系,可以更好地处理复杂的特征。
2. 稀疏性:ReLU 在负输入上输出零,因此可以通过选择性地激活神经元来实现稀疏性。这对于减少模型的参数量和计算量是有益的。

随着 ReLU 的广泛采用,它成为了许多深度学习模型中常用的激活函数之一。

接下来,我们看到在输出层使用了 `sigmoid`(S 形函数)激活函数。Sigmoid 函数将输入值压缩到范围 [0, 1] 之间,并将其解释为概率。它在二分类问题中常被用作输出层的激活函数,因为它可以将输出解释为所属类别的概率。Sigmoid 函数具有以下性质:

1. 可解释性:Sigmoid 函数的输出可以解释为该类别的概率或置信度。输出接近 0 表示低概率或低置信度,输出接近 1 表示高概率或高置信度,与本🌰中用户的是否流失契合。
2. 可导性:Sigmoid 函数是可微的,这是训练神经网络时进行梯度下降优化的必要性质。

总结一下,我们在这个神经网络模型中选择了 `relu` 激活函数作为隐层的激活函数,因为它具有非线性和稀疏性的优点。而在输出层,我们选择了 `sigmoid` 激活函数,因为它可以解释为二分类的概率输出。

from sklearn.model_selection import train_test_split #拆分数据集 
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

print(X.shape) #维度查看

import keras
from keras.models import Sequential #序贯模型
from keras.layers import Dense #全连接层

ann = Sequential() # 创建一个序贯ANN模型 
ann.add(Dense(12, input_dim=12, activation = 'relu')) # 添加输入层 
ann.add(Dense(24, activation = 'relu')) # 添加隐层 
ann.add(Dense(1, activation = 'sigmoid')) # 添加输出层 

ann.summary() # 显示网络模型(这个语句不是必需的)

2 训练模型

#神经网络只能处理浮点数(float)类型的数据。
#为了解决这个问题,您可以尝试将训练数据转换为浮点数类型。您可以使用NumPy中的astype()函数将整型数据转换为浮点数类型,如下所示
X_train = np.array(X_train).astype(float)
y_train = np.array(y_train).astype(float)
X_test = np.array(X_train).astype(float)
y_test = np.array(y_train).astype(float)

history = ann.fit(X_train, y_train, epochs=100,batch_size=64,validation_data=(X_test, y_test)) # 训练集&轮次&指定轮次批量大小&验证集

3 学习过程可视化 

def show_history(history): # 显示训练过程中的学习曲线 
  loss = history.history['loss'] 
  val_loss = history.history['val_loss'] 
  epochs = range(1, len(loss) + 1)
  plt.figure(figsize=(12, 4)) 
  plt.subplot(1, 2, 1) 
  plt.plot(epochs, loss, 'bo', label='Training loss') 
  plt.plot(epochs, val_loss, 'b', label='Validation loss') 
  plt.title('Training and validation loss') 
  plt.xlabel('Epochs') 
  plt.ylabel('Loss') 
  plt.legend() 
  acc = history.history['acc'] 
  val_acc = history.history['val_acc'] 
  plt.subplot(1, 2, 2) 
  plt.plot(epochs, acc, 'bo', label='Training acc') 
  plt.plot(epochs, val_acc, 'b', label='Validation acc') 
  plt.title('Training and validation accuracy') 
  plt.xlabel('Epochs') 
  plt.ylabel('Accuracy') 
  plt.legend() 
  plt.show() 
show_history(history) # 调用这个函数, 并将神经网络训练历史数据作为参数输入

 

或者 

import matplotlib.pyplot as plt
​
# 获取训练过程中的指标数据
train_loss = history.history['loss']
train_acc = history.history['acc']
val_loss = history.history['val_loss']
val_acc = history.history['val_acc']
​
# 绘制训练集和验证集的损失函数曲线
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(range(1, len(train_loss) + 1), train_loss, label='Training Loss')
plt.plot(range(1, len(val_loss) + 1), val_loss, label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training and Validation Loss')
plt.legend()
​
# 绘制训练集和验证集的准确率曲线
plt.subplot(1, 2, 2)
plt.plot(range(1, len(train_acc) + 1), train_acc, label='Training Accuracy')
plt.plot(range(1, len(val_acc) + 1), val_acc, label='Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()
​
# 展示图表
plt.tight_layout()
plt.show()
​

from sklearn.metrics import classification_report

y_pred = ann.predict(X_test, batch_size=10) # 预测测试集的标签 
y_pred = np.round(y_pred) # 四舍五入, 将分类概率值转换成0/1整数值 

print(classification_report(y_test, y_pred, labels=[0, 1])) # 调用分类报告

神经网络只是简单地把所有的客户判定为该银行忠实的“铁杆”支持者,没有给出任何一例可能离开的客户样本。因此,尽管准确率达到79%,但对于标签为1的类别而言,精确率、召回率和F1分数居然都为0。 
 如果此时输出y_pred值,会看到清一色的0值

from sklearn.metrics import confusion_matrix # 导入混淆矩阵 
cm = confusion_matrix(y_test, y_pred) # 调用混淆矩阵 
plt.title("ANN Confusion Matrix") # 标题:人工神经网络混淆矩阵 
sns.heatmap(cm, annot=True, cmap="Blues", fmt="d", cbar=False) # 热力图设定 
plt.show() # 显示混淆矩阵

4 特征缩放

“刚才忽略了一个步骤,就是特征缩放。初学者必须牢记,对于神经网络而言,特征缩放(feature scaling)极为重要。神经网络不喜欢大的取值范围,因此需要将输入神经网络的数据标准化,把数据约束在较小的区间,这样可消除离群样本对函数形状的影响。 
 数值过大的数据以及离群样本的存在会使函数曲线变得奇形怪状,从而影响梯度下降过程中的收敛。而特征缩放,将极大地提高梯度下降(尤其是神经网络中常用的随机梯度下降)的效率。 ”

摘录来自
零基础学机器学习
黄佳

from sklearn.preprocessing import StandardScaler # 导入特征缩放器 
sc = StandardScaler() # 特征缩放器 
X_train = sc.fit_transform(X_train) # 拟合并应用于训练集 
X_test = sc.transform (X_test) # 训练集结果应用于测试集”

再次运行如下代码,准确度显著提升到0.86+ 

#神经网络只能处理浮点数(float)类型的数据。
#为了解决这个问题,您可以尝试将训练数据转换为浮点数类型。您可以使用NumPy中的astype()函数将整型数据转换为浮点数类型,如下所示
X_train = np.array(X_train).astype(float)
y_train = np.array(y_train).astype(float)
X_test = np.array(X_train).astype(float)
y_test = np.array(y_train).astype(float)

history = ann.fit(X_train, y_train, epochs=30,batch_size=64,validation_data=(X_test, y_test)) # 训练集&轮次&指定轮次批量大小&验证集

再次运行如下代码发现:更为重要的精确率、召回率和F1分数也大幅提高,尤其是我们关注的阳性正样本类别(标签为1)所对应的F1分数达到了0.58,如下输出结果所示。

from sklearn.metrics import classification_report

y_pred = ann.predict(X_test, batch_size=10) # 预测测试集的标签 
y_pred = np.round(y_pred) # 四舍五入, 将分类概率值转换成0/1整数值 

print(classification_report(y_test, y_pred, labels=[0, 1])) # 调用分类报告

再次运行如下代码,混淆矩阵显示(如下图所示),目前有732个即将流失的客户被贴上“阳性”的标签,那么银行的工作人员就可以采取一些相应的措施,去挽留他们。然而,8000多个人中,还有接近900个注定要离开的客户没有被预测出来,因此模型还有进步的空间。

from sklearn.metrics import confusion_matrix # 导入混淆矩阵 
cm = confusion_matrix(y_test, y_pred) # 调用混淆矩阵 
plt.title("ANN Confusion Matrix") # 标题:人工神经网络混淆矩阵 
sns.heatmap(cm, annot=True, cmap="Blues", fmt="d", cbar=False) # 热力图设定 
plt.show() # 显示混淆矩阵

构建深度神经网络

浅层神经网络就可以模拟任何函数,但是需要巨大的数据量去训练它。深层神经网络解决了这个问题。相比浅层神经网络,深层神经网络可以用更少的数据量来学到更好的模型。从网络拓扑结构或数学模型上来说,深层神经网络里没有什么神奇的东西,正如费曼形容宇宙时所说:“它并不复杂,只是很多而已。

1 提高神经网络层数&优化器

右侧选项中选一块好Gpu提提速,提高到一百轮次试一下

ann = Sequential() # 创建一个序贯ANN模型 
  
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层 
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层 
ann.add(Dense(units=48, activation = 'relu')) # 添加隐层 
ann.add(Dense(units=96, activation = 'relu')) # 添加隐层 
ann.add(Dense(units=192, activation = 'relu')) # 添加隐层 
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层 

# 编译神经网络, 指定优化器、损失函数, 以及评估指标 
ann.compile(optimizer = 'adam',loss = 'binary_crossentropy',metrics = ['acc']) 

ann.summary() # 显示网络模型(这个语句不是必需的)

# 训练集&轮次&指定轮次批量大小&验证集
history = ann.fit(X_train, y_train,epochs=100,batch_size=64,validation_data=(X_test, y_test))

 逐渐超过0.9

再次运行如下代码,混淆矩阵显示(如下图所示),目前8000多个人中,模型预测失败仅468人。

from sklearn.metrics import confusion_matrix # 导入混淆矩阵 
cm = confusion_matrix(y_test, y_pred) # 调用混淆矩阵 
plt.title("ANN Confusion Matrix") # 标题:人工神经网络混淆矩阵 
sns.heatmap(cm, annot=True, cmap="Blues", fmt="d", cbar=False) # 热力图设定 
plt.show() # 显示混淆矩阵

2 绘制损失函数&准确率曲线 

import matplotlib.pyplot as plt

# 获取训练过程中的指标数据
train_loss = history.history['loss']
train_acc = history.history['acc']
val_loss = history.history['val_loss']
val_acc = history.history['val_acc']

# 绘制训练集和验证集的损失函数曲线
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(range(1, len(train_loss) + 1), train_loss, label='Training Loss')
plt.plot(range(1, len(val_loss) + 1), val_loss, label='Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.title('Training and Validation Loss')
plt.legend()

# 绘制训练集和验证集的准确率曲线
plt.subplot(1, 2, 2)
plt.plot(range(1, len(train_acc) + 1), train_acc, label='Training Accuracy')
plt.plot(range(1, len(val_acc) + 1), val_acc, label='Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.title('Training and Validation Accuracy')
plt.legend()

# 展示图表
plt.tight_layout()
plt.show()

3 增大轮次,并添加Dropout层对抗过拟合

在神经网络中,最常用的对抗过拟合的工具就是Dropout。在Keras中,Dropout也是神经网络中的层组件之一,其真正目的是实现网络正则化,避免过拟合。

它的原理非常奇特:在某一层之后添加Dropout层,意思就是随机将该层的一部分神经元的输出特征丢掉(设为0),相当于随机消灭一部分神经元。 

过拟合是一种在机器学习中常见的问题,当模型过于复杂或训练数据有限时,模型可能会过度拟合训练数据,导致在未见过的数据上表现不佳。

使用 Dropout 层是一种常见的正则化技术,它可以减轻过拟合并提高模型泛化能力。然而,对于某些情况,特别是在数据集相对较小或模型复杂度不高的情况下,过拟合可能并不严重,此时使用 Dropout 可能不会带来明显的性能提升。

在使用 Dropout 时,确保合适地选择参数(如丢弃比例)很重要。如果设置的率太高,可能导致模型信息丢失过多,欠拟合问题可能会出现。因此,需要根据具体情况进行调整和实验。

此外,过拟合的程度可能受到其他因素的影响,例如数据集的多样性、特征选择、模型的复杂性等。如果过拟合问题并不明显,您可以考虑简化模型或使用其他正则化技术来改进性能,如 L1/L2 正则化、早停等。

下面就在刚才的深度神经网络中添加一些Dropout层,并重新训练它

from keras.layers import Dropout # 导入Dropout 
ann = Sequential() # 创建一个序贯ANN模型 
ann.add(Dense(units=12, input_dim=12, activation = 'relu')) # 添加输入层 
ann.add(Dense(units=24, activation = 'relu')) # 添加隐层 
ann.add(Dropout(0.5)) # 添加Dropout层 
ann.add(Dense(units=48, activation = 'relu')) # 添加隐层 
ann.add(Dropout(0.5)) # 添加Dropout层 
ann.add(Dense(units=96, activation = 'relu')) # 添加隐层 
ann.add(Dropout(0.5)) # 添加Dropout层 
ann.add(Dense(units=192, activation = 'relu')) # 添加隐层 
ann.add(Dropout(0.5)) # 添加Dropout层 
ann.add(Dense(units=1, activation = 'sigmoid')) # 添加输出层 
ann.compile(optimizer = 'adam', # 优化器 
loss = 'binary_crossentropy', #损失函数 
metrics = ['acc']) # 评估指标”

ann.summary() # 显示网络模型(这个语句不是必需的)

# 训练集&轮次&指定轮次批量大小&验证集
history = ann.fit(X_train, y_train,epochs=150,batch_size=64,validation_data=(X_test, y_test))

 不添加Dropout层,由绘制训练集和验证集的损失函数曲线在150轮次时未见严重过拟合现象 

 

添加Dropout层后

反而不如不加Dropout层

运行以下代码(500轮)


# 训练集&轮次&指定轮次批量大小&验证集
history = ann.fit(X_train, y_train,epochs=500,batch_size=64,validation_data=(X_test, y_test))

怀疑是数据集的问题?准确率高达0.999

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

O&REO

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值