2022福大数学建模赛题B题-BP神经网络多分类(基于Tensorflow)-附python代码

题目3:请根据附件 2 所提供的部分食物寒热属性(分为三类:性平、性温热、性凉寒),对附 件 1 中的食物进行分类,判断这些食物是属于性平、性温热或性凉寒中哪一类,并说明你分类的合理性;

BP神经网络:外部输入信号经输入层、隐含层的神经元的逐层处理,并向前传播至输出层从而获得结果。如果在输出层无法得到期望输出,则转入误差的反向传播过程,将网络输出实际值之间的误差沿原连接通路原路返回,通过修改各层神经元的连接权重,减小误差,然后转入正向传播过程,经过多次迭代,直至到达最大迭代次数或误差小于给定值为止。

 过程:

1、数据预处理。附件 1中 1284种食物,有 549种食物是已知其寒热属性,另外 735种食物的寒热属性未知。提取数据并归一化处理

2、构建模型。此时将食物各成分看成 BP神经网络模型的输入层,将 Y(寒热属性)当作模型的输出层。这里损失函数用的是均方误差。

3、正交实验设计。要考虑学习率、迭代次数、和神经元个数三个因素。各设置3个水平,采用正交实验的好处是,考虑了 3个因素彼此之间的交互作用,且减少实验的次数。

4、BP神经网络训练。得出的模型效果良好,但受参数影响较大。且训练时间随着问题规模以及神经元个数的增加而增加。

5、未知类别数据分类。利用训练出来的模型进行分类,并将分类结果标注在表中,并输出一个新的excel表。

python源代码:

注意:因变量Y需要映射为数值变量。因为后面的one-hot编码不能对字符串型状态编码,这点不同于哑变量(dummy)的处理。另外数据类型的转换也需要注意,因为这里面调用了一些已有的库,它们输入数据有些是array数组,dataframe表格,tensor张量等。还有就是神经网络训练时需要对已知数据集分成训练集和测试集,选择时尽量随机选择数据,避免训练集和测试集数据差异过大

import pandas as pd
import numpy as np
from sklearn import preprocessing
import re
import random
import tensorflow as tf

df1 = pd.read_excel(r'食物成分表.xlsx', index_col=0).reset_index(drop=True)
df3 = df1.drop(['可食部分(%)'], axis=1)
scaler = preprocessing.MinMaxScaler()  # 标准化
df33 = scaler.fit_transform(df3[df3.columns[1:17]])  # 训练和导出结果
df33 = pd.DataFrame(df33, columns=df3.columns[1:17])  # 数据格式转换
df3 = pd.concat([df3['名  称'], df33], axis=1)
df3['catef'] = ''  # 添加分类列
print(df3.head())  # 查看前5条数据


# 根据附件2分类标注数据
def TagCategory(datastr, f, tap):
    for line in f:
        a = re.split('。|、|;|(|)|\\n| ', line)  # 分割字符串保存到a
        a = [x.strip() for x in a if x.strip() != '']  # 去除空字符串
        for ss in a:
            index0 = datastr.str.find(ss)  # 找到则返回所要找字符串在指定字符串位置,没有则返回-1
            ind = index0[index0.values != -1].index  # 不为-1的即表示有
            df3['catef'][ind] = tap  # 标注


mingcheng = df3['名  称'].astype(str)  # 提取名称所在列
f0 = open(r'性平.txt', encoding='utf-8')
f1 = open(r'性凉寒.txt', encoding='utf-8')
f2 = open(r'性温热.txt', encoding='utf-8')
TagCategory(mingcheng, f0, '性平')  # 性平标
TagCategory(mingcheng, f1, '性凉寒')  # 性凉寒标
TagCategory(mingcheng, f2, '性温热')  # 性温热标
print(df3.head())  # 查看前5行

NonNulldf = df3[(df3['catef'].notnull()) & (df3['catef'] != "")]  # 找到所有已知类数据
IsNulldf = df3[(df3['catef'] == "")]  # 找到未知类数据
kongindex = df3[(df3['catef'] == "")].index  # 找到未知类数据的索引
print(NonNulldf.head())

# 取数据方法:
data_train = NonNulldf
# 在初始数据划分训练集与测试集时直接将数据打乱:
data_train['catef']=data_train.catef.map({'性平':0,'性凉寒':1,'性温热':2})#分类列映射
DataIndex = [i for i in range(len(NonNulldf))]#产生索引
random.shuffle(DataIndex)#打算排序
data_train.index = [DataIndex]#新的排序

x_train = data_train.iloc[0:round(0.95 * len(NonNulldf)), 1:17]  # 取16个特征变量
y_train = data_train.iloc[0:round(0.95 * len(NonNulldf)), 17]  # 后三列是因变量标签
x_test = data_train.iloc[round(0.95 * len(NonNulldf)):, 1:17]  # 取16个特征变量
y_test = data_train.iloc[round(0.95 * len(NonNulldf)):, 17]  #

# 转换x的数据类型,否则后面矩阵相乘时会因数据类型不一致报错
x_train = tf.cast(x_train, tf.float32)
x_test = tf.cast(x_test, tf.float32)
# from_tensor_slices函数使输入特征和标签值一一对应。(把数据集分批次,每个批次batch组数据)
train_db = tf.data.Dataset.from_tensor_slices((x_train, y_train)).batch(32)
test_db = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)

# 生成神经网络的参数,16个输入特征,输入层为16个输入节点;因为3分类,故输出层为3个神经元
# 用tf.Variable()标记参数可训练
w1 = tf.Variable(tf.random.truncated_normal([16, 3], stddev=0.1)) # 16行三列,方差为0.1
b1 = tf.Variable(tf.random.truncated_normal([3], stddev=0.1)) # 一行三列,方差为0.1


a = 0.4  # 学习率为0.4
epoch = 1000  # 循环1000轮
# 训练部分
for epoch in range(epoch):  # 数据集级别的循环,每个epoch循环一次数据集
    for step, (x_train, y_train) in enumerate(train_db):  # batch级别的循环 ,每个step循环一个batch
        with tf.GradientTape() as tape:  # with结构记录梯度信息
            y = tf.matmul(x_train, w1) + b1  # 神经网络乘加运算
            y = tf.nn.softmax(y)  # 使输出y符合概率分布
            y_ = tf.one_hot(y_train, depth=3)  # 将标签值转换为独热码格式,方便计算loss
            loss = tf.reduce_mean(tf.square(y_ - y))  # 采用均方误差损失函数mse = mean(sum(y-y*)^2)
        # 计算loss对w, b的梯度
        grads = tape.gradient(loss, [w1, b1])
        # 实现梯度更新 w1 = w1 - lr * w1_grad    b = b - lr * b_grad
        w1.assign_sub(a * grads[0])  # 参数w1自更新
        b1.assign_sub(a * grads[1])  # 参数b自更新
        # 每1000次迭代,输出一次损失函数
    if epoch % 10 == 0:
        print('迭代第%i次,损失函数为:%f' % (epoch, loss))


# 测试部分
total_correct, total_number = 0, 0
for x_test, y_test in test_db:
    # 前向传播求概率
    y = tf.matmul(x_test, w1) + b1
    y = tf.nn.softmax(y)
    predict = tf.argmax(y, axis=1)  # 返回y中最大值的索引,即预测的分类
    # 将predict转换为y_test的数据类型
    predict = tf.cast(predict, dtype=y_test.dtype)
    # 若分类正确,则correct=1,否则为0,将bool型的结果转换为int型
    correct = tf.cast(tf.equal(predict, y_test), dtype=tf.int32)
    # 将每个batch的correct数加起来
    correct = tf.reduce_sum(correct)
    # 将所有batch中的correct数加起来
    total_correct += int(correct)
    # total_number为测试的总样本数,也就是x_test的行数,shape[0]返回变量的行数
    total_number += x_test.shape[0]
# 总的准确率等于total_correct/total_number
acc = total_correct / total_number
print("测试准确率 = %.2f %%" % (acc * 100.0))
data_pre = IsNulldf
x_pre = data_pre.iloc[0:, 1:17]  # 取16个特征变量
x_pre = tf.convert_to_tensor(x_pre)#转换为张量
x_pre = tf.cast(x_pre, tf.float32)#转换数据类型
y = tf.matmul(x_pre, w1) + b1#y值
y = tf.nn.softmax(y)
category = {0: "性平", 1: "性凉寒", 2: "性温热"}#映射
predict = np.array(tf.argmax(y, axis=1))  # 返回y中最大值的索引,即预测的分类[0]
for i in range(len(predict)):#未知数据预测
    print("食物属性为:", category.get(predict[i]))

#数据标注并写入excel表中
_1index=[]
_2index=[]
_3index = []
for i in range(len(predict)):
    if predict[i]== 1:
        _1index.append(i)#找到预测为性凉寒
    elif predict[i]== 2:
        _2index.append(i)#找到温热
    else:
        _3index.append(i)#找到性平

#在原始数据中标注好分类
df3['catef'][kongindex[_1index]] = '性凉寒'
df3['catef'][kongindex[_2index]] = '性温热'
df3['catef'][kongindex[_3index]] = '性平'
print(len(_1index),len(_2index),len(_3index))#看预测各类各有多少个

#将预测分类写入表格
df3_w=pd.concat([df1[df1.columns[0]],df1[df1.columns[2:17]], df3['catef']], axis=1)
writer1 = pd.ExcelWriter('df3.xlsx')  # 创建excel表格
df3_w.to_excel(writer1, 'page_1')
writer1.save()#保存


版权声明:本文为qq_50626322原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。