全连接神经网络二分类

1、背景

应用背景是一个企业非法集资风险预测的竞赛,赛题提供了各个企业多维度特征的数据以及标注了部分企业有无非法集资风险的数据,目的是根据所提供的企业数据资料去预测出未标注的企业有无非法集资风险。其中有非法集资风险标注为1,无非法集资风险标注为0。该问题可以归结为一个二分类问题。本文采用keras框架搭建神经网络(keras框架高度模块化,使用简单上手快,以Tensorflow、Theano或CNTK为后端,这里使用Tensorflow为后端)

有关keras的基本介绍可参阅:https://www.cnblogs.com/lc1217/p/7132364.html

2、数据准备

该数据集包含约25000家企业数据,其中约15000家企业带标注数据作为训练集,剩余数据作为测试集。数据由企业基本信息、企业年报、企业纳税情况等组成,数据包括数值型、字符型、日期型等众多数据类型(已脱敏),部分字段内容在部分企业中有缺失,其中第一列id为企业唯一标识。

其中企业特征数据提供了6个数据文件,带标注的企业数据提供了1个数据文件,这里为方便起见,只预处理了其中一个特征数据文件(结果命名为new_base_info1.xlsx)作为神经网络的输入数据X,带标注的企业数据(名为enterprise_info.xlsx)作为神经网络的训练测试标签Y,通过构建神经网络模型来预测出未标注企业有无非法集资风险(二分类,进行01标注)。

上述数据资料可从以下百度网盘链接中获取:(提取码:r0xp)
https://pan.baidu.com/s/1oG1VocrwBArFzxXAl_qSHw

数据读取部分代码如下(在同一目录下进行存取):

def get_base_datas(save_base):
    if not os.path.exists(save_base):  # 如果数据没进行过合并
        entprise_info = 'entprise_info.csv'
        new_base_info = 'new_base_info1.csv'
        # 返回为DataFrame对象
        df_entprise = pd.read_csv(entprise_info)
        df_base = pd.read_csv(new_base_info)
        # 自然连接,只保留含共同id的行
        data = pd.merge(df_base, df_entprise, how='inner', on='id')
        print(data)
        data.to_excel(save_base,index=False)  # 不把索引写入excel
        path = os.getcwd()
        print('base datas already saved in {}'.format(path))
    base_datas = pd.read_excel(save_base)
    return base_datas
def get_evaluate_datas(save_evaluate):
    if not os.path.exists(save_evaluate):  # 如果数据没进行过合并
        entprise_evaluate = 'entprise_evaluate.csv'
        new_base_info = 'new_base_info1.csv'
        # 返回为DataFrame对象
        df_evaluate = pd.read_csv(entprise_evaluate)
        df_base = pd.read_csv(new_base_info)
        # 自然连接,只保留含共同id的行
        data = pd.merge(df_base, df_evaluate, how='inner', on='id')
        data.to_excel(save_evaluate,index=False)  # 不把索引写入excel
        path = os.getcwd()
        print('evaluate datas already saved in {}'.format(path))
    evaluate_datas = pd.read_excel(save_evaluate)
    return evaluate_datas.iloc[:,:-1]  # 去除其中的score列

数据划分部分代码如下(训练集测试集8:2,采用random.shuffle进行随机打乱):

def split_data(np_datas,train_rate=0.8):
    # 随机打乱数据,使数据分布尽可能均匀
    np.random.seed(5)  # 使用随机数种子使打乱过程可复现,仅一次有效
    np.random.shuffle(np_datas)  # 不进行赋值也会打乱原有数据
    # 分割训练集和测试集
    X,Y = np_datas[:,1:-1],np_datas[:,-1]  # X去除id和label,Y取label列
    X_train,X_test = X[:int(X.shape[0]*train_rate)],X[int(X.shape[0]*train_rate):]
    Y_train,Y_test = Y[:int(Y.shape[0]*train_rate)],Y[int(Y.shape[0]*train_rate):]
    # 将numpy数组转换为默认的keras浮点类型,返回值是相同的numpy数组,转换为它的新类型
    X_train,Y_train = K.cast_to_floatx(X_train),K.cast_to_floatx(Y_train)
    X_test,Y_test = K.cast_to_floatx(X_test),K.cast_to_floatx(Y_test)
    return X_train, Y_train, X_test, Y_test

3、模型构建

可采用简单通用的全连接神经网络来解决二分类问题。

对于如何选取参数构建全连接神经网络(多层感知机MLP)可参阅:https://www.jianshu.com/p/7bd57dbc7a55

列举其中几个比较有参考意义的经验法则:
1、层数:从两层隐藏层(hidden layers)开始, 不包括最后一层

2、中间层的大小(节点数):一般是2的指数,比如4,8,16,32等等,第一层应该是输入数据特征的一半,下一层应该是前一层的一半。

3、分类问题输出层的大小:如果是二分的问题,输出层的大小为1,如果有多个类别,则输出层大小是类的数量。

4、中间层的激活函数:用relu

5、输出层的激活函数:如果是二分,用sigmoid,如果是多分,用softmax,如果是回归,用linear

构建模型的代码如下:

def build_model(X_train):
    '''构建模型'''
    # dropout = 0.25
    feature_num = X_train.shape[1]  # 输入数据特征数
    start_node = 2**(math.ceil(math.log(feature_num/2,2)))  # 第一层节点数量

    model = Sequential()
    model.add(Dense(start_node, input_dim=feature_num))
    # 引入BN层,可以不使用Dropout
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(start_node / 2))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(start_node / 4))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(1))
    model.add(BatchNormalization())
    model.add(Activation('sigmoid'))
    # For a binary classification problem
    # 编译模型
    model.compile(optimizer='rmsprop',  # 传递一个预定义优化器名,优化器的参数将使用默认值
                  loss='binary_crossentropy',
                  metrics=['binary_accuracy'])
    return model

其中BatchNormalization(批规范化)也就是BN层的引入可以起到对上一层输出的数据进行规范化的作用,有利于防止过拟合,加快训练速度。BN和Dropout单独使用都能减少过拟合并加速训练速度,但如果一起使用的话并不会产生1+1>2的效果,相反可能会得到比单独使用更差的效果。因此引入BN层后可不使用Dropout。

4、模型训练

模型训练的x,y参数选取X_train和Y_train,x,y为数据特征与数据标签,具有对应关系。

batch_size(批尺寸)参数含义是一次训练所选取的样本数。一个批中的一组数据共同决定了本次梯度的方向,按批更新参数。

epochs(时期):一个epoch就是将所有训练样本训练一次的过程。

关于其余参数的设置详见具体代码,其中运用的两个主要技巧是调整正常样本和异常样本权重class_weight(样本不均衡)、回调ReduceLROnPlateau函数动态调整学习率。

normal_label_multiple = int(len(Y_train[Y_train==0])/len(Y_train[Y_train==1]))  # 只取整数部分
print("normal_label_multiple:{}".format(normal_label_multiple))
# 正常标签为0,异常标签为1,将异常数据对应的权重设置为normal_label_multiple
class_weight = {0:1,1:normal_label_multiple}
# 定义一个callback参数,动态调整学习率
reduce_lr = ReduceLROnPlateau(monitor='val_loss',  # 监测量
                              mode='min',  # min表示当监控量loss停止下降的时候,学习率将减小
                              factor=0.1,  # new_lr = lr * factor(默认0.1)
                              patience=20,  # 容许网络性能不提升的次数(默认为10)
                              verbose=True,  # 每次更新学习率输出一条消息
                              min_lr=1e-8  # 学习率的下限
                              )
history = model.fit(x=X_train,
                    y=Y_train,
                    batch_size=64,  # 指定进行梯度下降时每个batch包含的样本数
                    epochs=100,  # 训练总轮数
                    verbose=2,  # 每个epoch输出一行记录
                    validation_data=(X_test,Y_test),  # 指定的验证集,不参与训练,在每个epoch结束后测试的模型的指标
                    callbacks=[reduce_lr],
                    shuffle=True,  # 训练过程中随机打乱输入样本的顺序
                    class_weight=class_weight  # 针对正常异常样本不均衡问题
                    )

这里应对类别不平衡问题(这里正常样本多,异常样本少,差距在一个数量级左右)的解决策略是调整类别样本的训练权重(提高类别少的样本的训练权重)

关于应对类别不平衡(数据倾斜)的方法还可参阅以下链接:
https://www.cnblogs.com/charlotte77/archive/2019/03/01/10455900.html

5、模型评估

将训练好的模型在测试集上进行性能评估,keras框架常用model.evaluate,返回的是损失值和你选定的指标值,这里选定的指标值为binary_accuracy。

在这里插入图片描述

# 评估模型
# 返回所指定的loss value,所指定的metrics value
scores = model.evaluate(x=X_test,
                        y=Y_test,
                        verbose=0)  # silent
accuracy = scores[1]  # 返回所选定的指标值,metrics=['binary_accuracy']
print('accuracy={}'.format(accuracy))
# 遍历评估验证
predict_test = model.predict(X_test)
threshold = 0.5
predict_test[predict_test<threshold],predict_test[predict_test>=threshold] = 0,1
total = len(X_test)
correct = 0
for i in range(total):
    if predict_test[i] == Y_test[i]:
        correct += 1
print('verify_accracy={}'.format(correct/total))

6、模型使用

keras框架用model.predict使用模型model进行预测,其中需要将数据特征进行必要的数据预处理提供给模型进行结果预测。

evaluate_datas = get_evaluate_datas(save_evaluate)  # 已去除score列
# 第一列id不作为数据特征
feature = np.array(evaluate_datas.iloc[:,1:])
# 将numpy数组转换为默认的keras浮点类型,返回值是相同的numpy数组,转换为它的新类型
feature = K.cast_to_floatx(feature)

predict = model.predict(feature)
use_label = True
if use_label:
    threshold = 0.5
    predict[predict < threshold], predict[predict >= threshold] = 0, 1
df_score = pd.DataFrame(predict,columns=['score'])
evaluate_content = pd.concat([evaluate_datas,df_score],axis=1)[['id','score']]
df_evaluate = pd.read_csv('entprise_evaluate.csv')[['id']]
merge_evaluate = pd.merge(df_evaluate, evaluate_content, how='inner', on='id')
save_result = 'result.csv'
try:
    merge_evaluate.to_csv(save_result,index=False)  # 不把索引写入
    path = os.getcwd()
    print("result already saved in {}".format(path))
except Exception as err:
    print(err)

7、模型的保存与加载

模型的保存与加载可分别使用model.save和load_model

模型保存(model_path为模型保存路径):

if whether_train:  # 不训练模型的时候不进行保存模型操作
    model.save(model_path)
    print("model already saved")

模型加载(model_path为模型文件路径):

if os.path.exists(model_path):
    model = load_model(model_path)
    print("Model loaded successfully")

这里使用的模型文件是.h5文件,可通过Netron工具进行模型结构可视化:
在这里插入图片描述

8、程序运行结果展示

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

可见,最后模型在测试集上准确率可以达到96%左右,但将结果文件提交到平台进行实际评测,其准确率却只有接近70%,可能原因有:1、参数设置不够合理(例如可尝试适当调高batch_size和epochs)2、类别不均衡问题的解决策略不够完善。3、用来训练的特征不够全面,只使用了其中一个主要文件的特征,导致训练出的模型泛化能力不强。

9、完整代码

import pandas as pd
import numpy as np
import math
import os
from tensorflow.keras import Sequential,optimizers
from tensorflow.keras.layers import Dense,BatchNormalization,Dropout,Activation
from tensorflow.keras.models import load_model
from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow.keras import backend as K
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore')


def get_base_datas(save_base):
    if not os.path.exists(save_base):  # 如果数据没进行过合并
        entprise_info = 'entprise_info.csv'
        new_base_info = 'new_base_info1.csv'
        # 返回为DataFrame对象
        df_entprise = pd.read_csv(entprise_info)
        df_base = pd.read_csv(new_base_info)
        # 自然连接,只保留含共同id的行
        data = pd.merge(df_base, df_entprise, how='inner', on='id')
        print(data)
        data.to_excel(save_base,index=False)  # 不把索引写入excel
        path = os.getcwd()
        print('base datas already saved in {}'.format(path))
    base_datas = pd.read_excel(save_base)
    return base_datas

def get_evaluate_datas(save_evaluate):
    if not os.path.exists(save_evaluate):  # 如果数据没进行过合并
        entprise_evaluate = 'entprise_evaluate.csv'
        new_base_info = 'new_base_info1.csv'
        # 返回为DataFrame对象
        df_evaluate = pd.read_csv(entprise_evaluate)
        df_base = pd.read_csv(new_base_info)
        # 自然连接,只保留含共同id的行
        data = pd.merge(df_base, df_evaluate, how='inner', on='id')
        data.to_excel(save_evaluate,index=False)  # 不把索引写入excel
        path = os.getcwd()
        print('evaluate datas already saved in {}'.format(path))
    evaluate_datas = pd.read_excel(save_evaluate)
    return evaluate_datas.iloc[:,:-1]  # 去除其中的score列

def build_model(X_train):
    '''构建模型'''
    # dropout = 0.25
    feature_num = X_train.shape[1]  # 输入数据特征数
    start_node = 2**(math.ceil(math.log(feature_num/2,2)))  # 第一层节点数量

    model = Sequential()
    model.add(Dense(start_node, input_dim=feature_num))
    # 引入BN层,可以不使用Dropout
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(start_node / 2))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(start_node / 4))
    model.add(BatchNormalization())
    model.add(Activation('relu'))
    model.add(Dense(1))
    model.add(BatchNormalization())
    model.add(Activation('sigmoid'))

    # For a binary classification problem
    # 编译模型
    model.compile(optimizer='rmsprop',  # 传递一个预定义优化器名,优化器的参数将使用默认值
                  loss='binary_crossentropy',
                  metrics=['binary_accuracy'])
    return model

def split_data(np_datas,train_rate=0.8):
    # 随机打乱数据,使数据分布尽可能均匀
    np.random.seed(5)  # 使用随机数种子使打乱过程可复现,仅一次有效
    np.random.shuffle(np_datas)  # 不进行赋值也会打乱原有数据
    # 分割训练集和测试集
    X,Y = np_datas[:,1:-1],np_datas[:,-1]  # X去除id和label,Y取label列
    X_train,X_test = X[:int(X.shape[0]*train_rate)],X[int(X.shape[0]*train_rate):]
    Y_train,Y_test = Y[:int(Y.shape[0]*train_rate)],Y[int(Y.shape[0]*train_rate):]
    # 将numpy数组转换为默认的keras浮点类型,返回值是相同的numpy数组,转换为它的新类型
    X_train,Y_train = K.cast_to_floatx(X_train),K.cast_to_floatx(Y_train)
    X_test,Y_test = K.cast_to_floatx(X_test),K.cast_to_floatx(Y_test)
    return X_train, Y_train, X_test, Y_test

def plot_accracy(history):
    acc = history.history['binary_accuracy']
    val_acc = history.history['val_binary_accuracy']
    epochs = range(1, len(acc) + 1)
    plt.plot(epochs, acc, 'bo', label='Training accuracy')
    plt.plot(epochs, val_acc, 'b', label='Validation accuracy')
    plt.title('Training and validation accuracy')
    plt.xlabel('Epochs')
    plt.ylabel('Accuracy')
    plt.legend()
    plt.show()


if __name__ == "__main__":
    # 确定训练集和测试集
    save_base = 'base_datas1.xlsx'  # 训练数据文件路径
    save_evaluate = 'evaluate_datas1.xlsx'  # 评估数据文件路径
    base_datas = get_base_datas(save_base)
    # 将dataframe数据转化为numpy.array
    np_datas = np.array(base_datas)
    # 将总数据按指定训练集比例train_rate分割为训练集和测试集
    X_train, Y_train, X_test, Y_test = split_data(np_datas)
    # X_train,X_test = X_train.astype('float32'),X_test.astype('float32')

    # 构建模型
    model_path = 'keras_network_9.h5'  # 模型文件路径
    if os.path.exists(model_path):
        model = load_model(model_path)
        print("Model loaded successfully")
    else:
        model = build_model(X_train)
        print("Building models")
    model.summary()  # 模型信息

    # 训练模型
    whether_train = False  # 设置是否训练模型
    if whether_train:
        # 算出正常标签是异常标签的倍数
        normal_label_multiple = int(len(Y_train[Y_train==0])/len(Y_train[Y_train==1]))  # 只取整数部分
        print("normal_label_multiple:{}".format(normal_label_multiple))
        # 正常标签为0,异常标签为1,将异常数据对应的权重设置为normal_label_multiple
        class_weight = {0:1,1:normal_label_multiple}
        # 定义一个callback参数,动态调整学习率
        reduce_lr = ReduceLROnPlateau(monitor='val_loss',  # 监测量
                                      mode='min',  # min表示当监控量loss停止下降的时候,学习率将减小
                                      factor=0.1,  # new_lr = lr * factor(默认0.1)
                                      patience=20,  # 容许网络性能不提升的次数(默认为10)
                                      verbose=True,  # 每次更新学习率输出一条消息
                                      min_lr=1e-8  # 学习率的下限
                                      )
        history = model.fit(x=X_train,
                            y=Y_train,
                            batch_size=64,  # 指定进行梯度下降时每个batch包含的样本数
                            epochs=100,  # 训练总轮数
                            verbose=2,  # 每个epoch输出一行记录
                            validation_data=(X_test,Y_test),  # 指定的验证集,不参与训练,在每个epoch结束后测试的模型的指标
                            callbacks=[reduce_lr],
                            shuffle=True,  # 训练过程中随机打乱输入样本的顺序
                            class_weight=class_weight  # 针对正常异常样本不均衡问题
                            )
        plot_accracy(history)  # 训练过程精度变化可视化

    # 评估模型
    # 返回所指定的loss value,所指定的metrics value
    scores = model.evaluate(x=X_test,
                            y=Y_test,
                            verbose=0)  # silent
    accuracy = scores[1]  # 返回所选定的指标值,metrics=['binary_accuracy']
    print('accuracy={}'.format(accuracy))
    # 遍历评估验证
    predict_test = model.predict(X_test)
    threshold = 0.5
    predict_test[predict_test<threshold],predict_test[predict_test>=threshold] = 0,1
    total = len(X_test)
    correct = 0
    for i in range(total):
        if predict_test[i] == Y_test[i]:
            correct += 1
    print('verify_accracy={}'.format(correct/total))

    # 使用模型
    whether_use = not whether_train  # 训练模型的时候不使用模型
    if whether_use:
        evaluate_datas = get_evaluate_datas(save_evaluate)  # 已去除score列
        # 第一列id不作为数据特征
        feature = np.array(evaluate_datas.iloc[:,1:])
        # 将numpy数组转换为默认的keras浮点类型,返回值是相同的numpy数组,转换为它的新类型
        feature = K.cast_to_floatx(feature)

        predict = model.predict(feature)
        use_label = True
        if use_label:
            threshold = 0.5
            predict[predict < threshold], predict[predict >= threshold] = 0, 1
        df_score = pd.DataFrame(predict,columns=['score'])
        evaluate_content = pd.concat([evaluate_datas,df_score],axis=1)[['id','score']]
        df_evaluate = pd.read_csv('entprise_evaluate.csv')[['id']]
        merge_evaluate = pd.merge(df_evaluate, evaluate_content, how='inner', on='id')
        save_result = 'result.csv'
        try:
            merge_evaluate.to_csv(save_result,index=False)  # 不把索引写入
            path = os.getcwd()
            print("result already saved in {}".format(path))
        except Exception as err:
            print(err)

    # 保存模型
    if whether_train:  # 不训练模型的时候不进行保存模型操作
        model.save(model_path)
        print("model already saved")
  • 7
    点赞
  • 42
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值