机器学习第五章课后习题

5.5  试编程实现标准BP算法和累积BP算法,在西瓜数据集3.0上分别用这两个算法训练一个单隐层网络,并进行比较

解答:

1、数据预处理

西瓜数据集3.0如下图所示,查看属性可得西瓜色泽、根蒂、敲声、纹理、脐部、触感都是离散类型,需将其转换为连续性属性,具体操作如下:

色泽可以根据颜色深度,将浅白作为0、青绿作为1、乌黑作为2;
根蒂可以根据蜷缩程度,将硬挺作为0、稍蜷作为1、蜷缩作为2;
敲声可以根据声音音调,将沉闷作为0、浊响作为1、清脆作为2;
纹理可以根据清晰程度,将清晰作为0、稍糊作为1、模糊作为2;
脐部可以根据凹陷程度,将平坦作为0、稍凹作为1、凹陷作为2;
触感可以根据硬滑程度,将软粘作为0、硬滑作为1。

对于结果“好瓜”,很明显是一个二分类问题,将好瓜视为1,坏瓜视为0。

2、导包

方便后面绘制标准BP和累积BP的图,直观进行对比

import numpy as np
import matplotlib.pyplot as plt

3、导入西瓜数据集

此处直接根据刚刚数据预处理的规则,分别导入特征和标签

features=np.array([
    [1,2,2,1,0,1,2,2,2,1,0,0,1,0,2,0,1],
    [2,2,2,2,2,1,1,1,1,0,0,2,1,1,1,2,2],
    [1,0,1,0,1,1,1,1,0,2,2,1,1,0,1,1,0],
    [0,0,0,0,0,0,1,0,1,0,2,2,1,1,0,2,1],
    [2,2,2,2,2,1,1,1,1,0,0,0,2,2,1,0,1],
    [1,1,1,1,1,0,0,1,1,0,1,0,1,1,0,1,1], 
[0.697,0.774,0.634,0.608,0.556,0.403,0.481,0.437,0.666,0.243,0.245,0.343,0.639,0.657,0.360,0.593,0.719],[0.460,0.376,0.264,0.318,0.215,0.237,0.149,0.211,0.091,0.267,0.057,0.099,0.161,0.198,0.370,0.042,0.103]
])
labels=np.array([
    [1,1,1,1,1,1,1,1,0,0,0,0,0,0,0,0,0]
])

4、定义激活函数sigmoid

sigmoid(x) = \frac{1}{1+e^{-x}}

def sigmoid(X):
    return 1./(1 + np.exp(-X))

5、定义神经网络类Net

在类里初始化权重和偏置,并定义前向传播函数、标准BP算法和累积BP算法的函数。

标准BP算法是对每个样本进行迭代,它每次更新只针对单个样例,所以直接计算并更新各参数;累积BP算法是对整个训练集进行计算,根据所有训练样本的累积误差对参数进行一次更新。

此处主要分为4个函数:

  • 在net类的构造函数里,规定输入层神经元数量为8,隐藏层神经元数量为10,输出层神经元数量为1,并在里面初始化神经网络的权重、偏置
  • 在forward()函数里面,定义类中的前向传播方法
  • 在standard_BP()里面,定义标准BP算法的计算过程,并更新参数
  • 在accumulate_BP()里面,定义累积BP算法的计算过程,并更新参数

第一次接触神经网络的代码,此次主要是学习借鉴,刚开始看不懂做了很多注释,方便自己理解并加深印象,最后面有无注释纯享版完整代码~

# np.random.randn() 生成符合正态分布的随机矩阵
# np.zeros(m) 生成元素全为0的m维向量
# reshape()向量转换
# np.matmul(n,m) 计算m和n的矩阵乘

# 定义神经网络类Net
class Net():
    # 定义net类的构造函数,输入层神经元为8,隐藏层神经元为10,输出层神经元为1
    # 初始化神经网络的权重、偏置和梯度存储变量
    def __init__(self, num_input=8, num_hidden=10, num_output=1):
        # 初始化输入层到隐藏层的权重矩阵W1
        self.W1 = np.random.randn(num_hidden, num_input)   
        # 初始化输入层到隐藏层的权重梯度矩阵dW1
        self.dW1 = np.zeros(self.W1.shape)
        
        # 初始化隐藏层到输出层的权重矩阵W2
        self.W2 = np.random.randn(num_output, num_hidden)
        # 初始化隐藏层到输出层的权重梯度矩阵dW2
        self.dW2 = np.zeros(self.W2.shape)
        
        
        # 初始化隐藏层的偏置变量b1
        self.b1 = np.zeros(num_hidden).reshape(-1,1)
        # 初始化隐藏层的输出向量o1
        self.o1 = np.zeros(num_hidden).reshape(-1,1)
        # 初始化隐藏层的梯度向量do1
        self.do1 = np.zeros(self.o1.shape)
        # 初始化隐藏层的偏置梯度向量db1
        self.db1 = np.zeros(self.b1.shape)
        
        # 初始化输出层的偏置向量b2
        self.b2 = np.zeros(num_output).reshape(-1,1)
        # 初始化输出层的输出向量o2
        self.o2 = np.zeros(num_output).reshape(-1,1)
        # 初始化输出层的梯度向量do2
        self.do2 = np.zeros(self.o2.shape)
        # 初始化输出层的偏置梯度向量db2
        self.db2 = np.zeros(self.b2.shape)
        
        


    
    # 神经网络类中的前向传播方法
    def forward(self, X):
        # 检查输入数据维度和W1的维度是否一致
        if X.shape[0] != self.W1.shape[1]:
            print("输入数据格式错误!")
            return
        self.input = X
        
        # 计算两个输出o1和o2
        # o1等于sigmod(W1和X的矩阵乘+b1) o2等于sigmod(W2和o1的矩阵乘+b2)
        self.o1 = sigmoid(np.matmul(self.W1, self.input) + self.b1)
        self.o2 = sigmoid(np.matmul(self.W2, self.o1) + self.b2)
        
        return self.o2
    
    
    
    # 标准BP算法,使用均方误差作为损失函数
    def standard_BP(self, label, lr=0.2):
        # 首先计算输出层误差
        self.do2 = self.o2 - label
        # 计算隐藏层到输出层权重的梯度
        self.dW2 = np.matmul( self.do2*self.o2*(1-self.o2), self.o1.reshape(1,-1) )  
        # 计算db2
        self.db2 = self.do2*self.o2*(1-self.o2)
        
        # 计算输出层误差
        self.do1 = np.matmul(self.W2.transpose(), self.do2*self.o2*(1-self.o2))
        # 计算输入层到隐藏层权重的梯度
        self.dW1 = np.matmul( self.do1*self.o1*(1-self.o1), self.input.reshape(1,-1))
         # 计算db1
        self.db1 = self.do1*self.o1*(1-self.o1)
        
        # 根据梯度下降算法更新参数
        self.W2 -= self.dW2 * lr
        self.b2 -= self.db2 * lr
        self.W1 -= self.dW1 * lr
        self.b1 -= self.db1 * lr
        
    
    
    # 累积BP算法,,使用均方误差作为损失函数
    def accumulate_BP(self, labels, lr=0.2):
        # 首先得到样本数量
        num = labels.shape[1]
       
        self.do2 = (self.o2 - labels)/num #注意这里
        self.dW2 = np.matmul( self.do2*self.o2*(1-self.o2), self.o1.transpose())  
        self.db2 = (self.do2*self.o2*(1-self.o2)).sum(axis=1).reshape(-1,1)
        
        self.do1 = np.matmul(self.W2.transpose(), self.do2*self.o2*(1-self.o2))
        self.dW1 = np.matmul( self.do1*self.o1*(1-self.o1), self.input.transpose())
        self.db1 = (self.do1*self.o1*(1-self.o1)).sum(axis=1).reshape(-1,1)
        
        # 根据梯度下降算法更新参数
        self.W2 -= self.dW2 * lr
        self.b2 -= self.db2 * lr
        self.W1 -= self.dW1 * lr
        self.b1 -= self.db1 * lr

6、定义使用标准BP算法进行训练的函数

此次设置初始损失值loss=1,设置停止条件为loss<=0.1,并对迭代次数和每次迭代的损失值进行记录,方便后面绘制损失值随迭代次数的变化图

# 标准BP
def train_standard_BP(features, labels, lr):
    net = Net()
    epoch = 0 #迭代次数
    loss = 1 #损失值
    all_loss = [] #用于存储每次迭代的损失值
    
    while loss>0.1:
        # 标准BP是对每个样本进行迭代
        for i in range(features.shape[1]):
            X = features[:,i] #特征
            Y = labels[0,i] #标签
            net.forward(X.reshape(-1,1))
            net.standard_BP(Y,lr)#更新网络参数
            
        output = net.forward(features) #整个训练集的输出
        loss = 0.5*((output-labels)**2).sum() # 整个训练集的损失
        epoch +=1
        all_loss.append(loss)
        
    print("====标准BP====", "\n学习率:", lr, "\n终止epoch:", epoch, "\nloss: ", loss) 
    plt.xlabel("epoch")
    plt.ylabel("loss")
    plt.plot(all_loss)
    plt.show()

7、定义使用累积BP算法进行训练的函数

# 累积BP
def train_accumulate_BP(features, labels,lr):
    net = Net()
    epoch = 0
    loss = 1
    all_loss = []
    while loss>0.1:
        # 直接计算整个训练集的输出
        output = net.forward(features)
        net.accumulate_BP(labels, lr)
        # 此处计算的是平均损失
        loss = 0.5*((output-labels)**2).sum()/labels.shape[1]
        epoch +=1
        all_loss.append(loss)
    print("====累积BP====", "\n学习率:", lr, "\n终止epoch:", epoch, "\nloss: ", loss) 
    plt.xlabel("epoch")
    plt.ylabel("loss")
    plt.plot(all_loss)
    plt.show()

8、分别调用训练函数

设置学习率为0.2,打印输出函数的迭代次数与损失值,绘制损失函数随训练轮次的变化曲线

train_standard_BP(features,labels,lr=0.2)

train_accumulate_BP(features,labels,lr=0.2)

上面两种算法对比可得,为了达到同样的累积误差极小点,标准BP算法往往需要比累积BP算法进行更多次数的迭代。

书上说在数据集非常大时,标准BP算法能更快的获得较好的解,但是此次我们选用的西瓜数据集数据量较小,不能较好的体会标准BP算法的优越性。

参考文章链接:

神经网络的BP算法推导详解

完整版代码参考

5.10 从网上下载或者自己编程实现一个卷积神经网络,并在手写字符识别数据MNIST上进行实验测试

准备:

MNIST数据集:MNIST数据集是一个被大量使用的数据集,几乎所有的图像训练教程都会以它为例子,它已经成为了一个典范数据。包括55000个训练集数字图像和标签和5000个验证图像和标签,以及10000个测试集图像和标签。

卷积神经网络:神经网络(neural networks)的基本组成包括输入层、隐藏层、全连接层和输出层。而卷积神经网络的特点在于隐藏层分为卷积层、激励层和池化层。通过卷积神经网络的隐藏层可以减少输入的特征。

实验流程:创建神经网络模型,读取55000个的图像和标签进行训练,最后用测试数据集对模型进行验证,保存并加载这个模型,这是神经网络模型开发的基本开发流程。

1、导入图片数据集

①在ipynb文件同一目录下创建 “MNIST_data” 文件夹,将下载的四个数据文件放入此文件夹。

②加载数据集MNIST_data

from tensorflow.examples.tutorials.mnist import input_data
# 加载数据集
mnist = input_data.read_data_sets('MNIST_data/', one_hot=True)


2、分析图片并定义变量

①TensorFlow读取到数据后,把整个数据集分作了三大类:训练数据集、测试数据集、验证数据集。

# 查看训练数据集、测试数据集、验证数据集
print(mnist.train.images.shape)
print(mnist.test.images.shape)
print(mnist.validation.images.shape)

运行结果如下:

训练数据集的形状是55000*784,测试数据集的形状是10000*784,验证数据集的形状是5000*784。MNIST中的每张图片都是28*28像素的,所有像素点就是28*28=784,相当于把一张图片从二维的图形拉成了一个一维的数据。

②下面验证图片和标签的对应关系

选取训练数据集中的第二张图片和第二个标签数据。由于现在的图片数据是一维的,首先把它转化成二维的,然后把图片展示出来:

import pylab
# 读取第二张图片
im = mnist.train.images[1]
# 把图片数据变成28*28的数组
im = im.reshape(-1, 28)
# 展示图片
pylab.imshow(im)
pylab.show()

# 打印第二个标签数据
print(mnist.train.labels[1])

运行结果如下:

标签数据打印出来结果如为[0. 0. 0. 1. 0. 0. 0. 0. 0. 0.]

第四个位置上为1,按照one_hot编码规则,就说明他表示的是3。

③定义输入输出的参数

输入一张张的图片,它的形状是n*784(表示n张图片),而输出就是推测出的数字one_hot编码,每个图片对应一个one_hot编码,所以输出的形状是n*10。代码如下。

import tensorflow.compat.v1 as tf
tf.disable_v2_behavior()
x = tf.placeholder(shape=[None, 2], dtype=tf.float32)

# 图片输入占位符
x = tf.placeholder(tf.float32, [None, 784])
# 图片标签数据占位符
y = tf.placeholder(tf.float32, [None, 10]) 

shape参数中的None值表示这个对应维度可以是任意长度,x,y占位符形状中的None就表示根据图片张数来确定。

3、构建模型

TensorFlow中构架模型通常分作如下几步骤:

  • 定义权重和偏置项
  • 定义前向传播函数
  • 定义反向传播函数

① 定义权重和偏置项

TensorFlow中都是使用各属性值分别乘以相应的权重,然后加上偏置项来推测输出。本案例也需要定义权重和偏置项。

首先确定权重的形状,由于一张图片中有784个像素,为了确定每个像素对于最终结果的影响,所以需要分别对这个784个像素点进行权重求值,通过这个权重需要输出的是一个长度为10的数组(因为本案例的类别有10个类别),所以权重的形状就为784*10;对于权重求出来的结果需要加上偏置项,所以偏置项的形状为长度为10的数组。

通常权重初始值设置为随机数,而偏置项设置为0。

# 定义权重
weights = tf.Variable(tf.random_normal([784, 10]))
# 定义偏置项
biases = tf.Variable(tf.zeros([10]))

② 定义前向传播函数

前向传播函数的意思就是通过当前的权重和偏置项推测出一个结果出来。

本案例使用softmax进行分类。这个分类器的作用是把原始的输出结果经过softmax层后,能够推断出各个结果概率分布情况,比方说本例中一个图片经过softmax后的输出结果可能是[0.1, 0.1, 0.6, 0.0, 0.1, 0.0, 0.0, 0.1, 0.0, 0.0],这个结果代表的含义:图片是数字0的概率为0.1, 是数字1的概率是0.1,是数字2的概率是0.6,依次类推,明显是2的概率最大,那么就把这个图片当作是数字2。

# 定义前向传播函数
pred = tf.nn.softmax(tf.matmul(x, weights) + biases)

这里把图片数据的权重和作为softmax的输入值求出结果概率分布。到这里就定义好了前向传播函数,通过这个函数,基于当前的权重和偏置能够推断出图片对应的数字,但是基于原始的权重和偏置项推测出来的结果肯定会有很大误差,为了减少误差,推断更加准确,需要反向传播函数。

③ 定义反向传播函数

前向传播函数用作预测,而反向传播函数用于学习调整,并减小整个神经网络的误差。所以定义反向传播函数有两个步骤:第一步定义损失函数,也就是推测值与标签数据之间的误差;第二步使用优化器减少损失。

本案例中使用交叉熵定义预测值和实际值之间的误差,并使用梯度学习算法学习以达到快速减少误差的目的。

# 定义损失函数
cost = tf.reduce_mean(-tf.reduce_sum(y*tf.log(pred), reduction_indices=1))

# 定义学习率
learning_rate = 0.01

# 使用梯度下降优化器
optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(cost)

4、训练模型

模型构建好后,需在会话中实际训练数据,其实就是运行优化器。

在这个过程中对整体数据迭代25次,使用training_epochs定义;一次迭代中,每次取出训练集中的100张图片进行训练,直到所有图片训练完成,训练集大小用batch_size定义;每迭代5次展示当前的损失值。

# 迭代次数
train_epochs = 25

# 批次数据大小
batch_size = 100

# 每隔多少次展示一次
display_step = 5

with tf.Session() as sess:
    # 首先初始化所有变量
    sess.run(tf.global_variables_initializer())

    # 启动循环训练
    for epoch in range(train_epochs):

        # 当前迭代的平均损失值
        avg_cost = 0

        # 计算批次数
        total_batches = int(mnist.train.num_examples / batch_size)

        # 循环所有训练数据
        for batch_index in range(total_batches):
            # 获取当前批次的数据
            batch_x, batch_y = mnist.train.next_batch(batch_size)

            # 运行优化器,并得到当前批次的损失值
            _, batch_cost = sess.run([optimizer, cost], feed_dict={x:batch_x, y:batch_y})

            # 计算平均损失
            avg_cost += batch_cost / total_batches

        if (epoch + 1) % display_step == 0:
            print('Epoch:%04d cost=%f' % (epoch+1, avg_cost))  
    print("Train Finished")
    
    # 以下三条语句为5、测试模型的运行命令,需要和Session上下文管理器放在一起
    # 把每个推测结果进行比较得出一个长度为测试数据集大小的数组,数组中值都是bool,推断正确为True,否则为False
    correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))

    # 首先把上面的bool值转换成数字,True转换为1,False转换为0,然后求准确值
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

    print('Accuracy:%f' % accuracy.eval({x:mnist.test.images, y:mnist.test.labels}))
    
    # 以下三条语句为6、保存模型的运行命令,需要和Session上下文管理器放在一起
    # 实例化saver
    saver = tf.train.Saver()
    # 模型保存位置
    model_path = 'log/t10kmodel.ckpt'
    save_path = saver.save(sess, model_path)
    print('Model saved in file:%s' % save_path)

运行结果如下:

以上运行数值每次会有不同,但可以看到随着迭代的不停进行,损失值在不停减小。

5、测试模型

模型已经完成训练,现在是时候使用测试数据集验证这个模型的好坏了。准确率的算法:直接判断预测结果和真实标签数据是否相对,如果相等则是正确的预测,否则为错误预测,最后使用正确个数除以测试数据集个数即可得到准确率。由于是one_hot编码,所以这里使用argmax函数返回one_hot编码中数字为1的下标,如果预测值下标和标签值下标相同,则说明推断正确。

代码如下:(这段代码要在Session上下文管理器中执行,已经放在之前的代码中)

 # 把每个推测结果进行比较得出一个长度为测试数据集大小的数组,数组中值都是bool,推断正确为True,否则为False
correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))

# 首先把上面的bool值转换成数字,True转换为1,False转换为0,然后求准确值
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

print('Accuracy:%f' % accuracy.eval({x:mnist.test.images, y:mnist.test.labels}))

6、保存模型

如果一个模型训练好后,可以把它保存下来,以便下一次使用。要保持模型,首先必须创建一个Saver对象,实例化后调用该对象的save方法进行保存。

代码如下:(这段代码要在Session上下文管理器中执行,已经放在之前的代码中)

# 实例化saver
saver = tf.train.Saver()
# 模型保存位置
model_path = 'log/t10kmodel.ckpt'

save_path = saver.save(sess, model_path)
print('Model saved in file:%s' % save_path)

7、加载模型

既然模型能保存,那肯定可以用来加载,解决类似的问题。下面做个实验:先把模型加载回来使用saver的restore函数,然后拿两张图片让恢复回来的模型预测结果,让它与真实数据比较。

需要重启一个Session,然后再进行模型恢复,代码如下。

print("Starting 2nd session...")
with tf.Session() as sess:
    # 初始化变量
    sess.run(tf.global_variables_initializer())
    # 恢复模型变量
    saver = tf.train.Saver()
    model_path = 'log/t10kmodel.ckpt'
    saver.restore(sess, model_path)

    # 测试model
    correct_prediction = tf.equal(tf.argmax(pred, 1), tf.argmax(y, 1))
    # 计算准确率
    accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
    print ("Accuracy:", accuracy.eval({x: mnist.test.images, y: mnist.test.labels}))

    output = tf.argmax(pred, 1)
    batch_xs, batch_ys = mnist.train.next_batch(2)
    outputval,predv = sess.run([output,pred], feed_dict={x: batch_xs})
    print(outputval,predv,batch_ys)

    im = batch_xs[0]
    im = im.reshape(-1,28)
    pylab.imshow(im)
    pylab.show()

    im = batch_xs[1]
    im = im.reshape(-1,28)
    pylab.imshow(im)
    pylab.show()

每次测试拿到的图片数据和执行结果都不一样,这里运行了两次,得到两个结果:

从本次测试结果可以看到拿到的分别是0和2的图片。

第一个数组算出的结果也是[0, 2],说明预测是正确的。

第二个数组是一个2*10的数组,是概率分布结果,可以看到第一个长度为10的数组中第1个位置的概率为7.1407723e-01,可能性最大,表示这个图片很大概率是0,第二个长度为10的数组中第3个位置的概率为9.9999833e-01,说明这个图片很大概率是2。

第三个数组是两种图片的one_hot编码,也可以看出这两个图片是0和2。


第二次运行:

结果预测7,0同样正确!

至此实验结束!

8、实验总结

       实验的全过程分为以下几个关键步骤:首先,对MNIST数据集中的图片进行了解析,并为每张图片定义了相应的变量。接下来构建了一个多层神经网络模型,用于学习手写数字的特征。为了使模型更具泛化能力,我们引入了权重和偏置项,并通过定义前向传播函数实现了神经网络的正向计算。此外,我们还定义了一个反向传播函数,用于计算损失值并优化模型参数。

        在模型训练过程中,我们采用了迭代训练的方法,每次迭代都会计算并输出训练损失值,以便我们了解模型在训练过程中的性能变化。同时,每次迭代后,我们会进行一次验证,比较模型在训练集和验证集上的表现,从而评估模型的泛化能力。在模型训练完成后,我们对模型在测试集上的表现进行了评估,以验证模型的准确性和可靠性。

      为了方便未来对模型的复用和迁移学习,我们在实验中还实现了模型的保存和加载功能。通过这一功能,我们可以将训练好的模型存储在本地,以便在需要时进行再次加载和测试。

        在本实验中,我成功实现了对MNIST数据集中模糊手写数字的识别,展示了深度学习在图像识别领域的强大能力。通过构建神经网络模型,我能够有效地提取图片中的特征,从而实现了对手写数字的准确识别。这一实验为我提供了一个很好的示范,让我更加了解深度学习技术在实际问题中的应用,以及如何利用现有框架(如TensorFlow)快速搭建和训练神经网络模型。在未来的研究和实践中,我可以借鉴本实验的方法和经验,将深度学习技术应用到更多的领域和场景中,为人们的生活和工作带来更多便利和价值。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值