python神经网络案例——FC全连接神经网络实现mnist手写体识别

版权声明:本文为博主原创文章,转载请注明来源。开发合作联系82548597@qq.com https://blog.csdn.net/luanpeng825485697/article/details/79087415

全栈工程师开发手册 (作者:栾鹏)

python教程全解

FC全连接神经网络的理论教程参考
http://blog.csdn.net/luanpeng825485697/article/details/79009223

加载样本数据集

首先我们要有手写体的数据集文件

t10k-images.idx3-ubyte

t10k-labels.idx1-ubyte

train-images.idx3-ubyte

train-labels.idx1-ubyte

我们实现一个MNIST.py文件,专门用来读取手写体文件中的数据。

# -*- coding: UTF-8 -*-

# 获取手写数据。
# 28*28的图片对象。每个图片对象根据需求是否转化为长度为784的横向量
# 每个对象的标签为0-9的数字,one-hot编码成10维的向量
import numpy as np

# 数据加载器基类。派生出图片加载器和标签加载器
class Loader(object):
    # 初始化加载器。path: 数据文件路径。count: 文件中的样本个数
    def __init__(self, path, count):
        self.path = path
        self.count = count

    # 读取文件内容
    def get_file_content(self):
        print(self.path)
        f = open(self.path, 'rb')
        content = f.read()  # 读取字节流
        f.close()
        return content  # 字节数组

    # 将unsigned byte字符转换为整数。python3中bytes的每个分量读取就会变成int
    # def to_int(self, byte):
    #     return struct.unpack('B', byte)[0]

# 图像数据加载器
class ImageLoader(Loader):
    # 内部函数,从文件字节数组中获取第index个图像数据。文件中包含所有样本图片的数据。
    def get_picture(self, content, index):
        start = index * 28 * 28 + 16  # 文件头16字节,后面每28*28个字节为一个图片数据
        picture = []
        for i in range(28):
            picture.append([])  # 图片添加一行像素
            for j in range(28):
                byte1 = content[start + i * 28 + j]
                picture[i].append(byte1)  # python3中本来就是int
                # picture[i].append(self.to_int(byte1))  # 添加一行的每一个像素
        return picture   # 图片为[[x,x,x..][x,x,x...][x,x,x...][x,x,x...]]的列表

    # 将图像数据转化为784的行向量形式
    def get_one_sample(self, picture):
        sample = []
        for i in range(28):
            for j in range(28):
                sample.append(picture[i][j])
        return sample

    # 加载数据文件,获得全部样本的输入向量。onerow表示是否将每张图片转化为行向量
    def load(self,onerow=False):
        content = self.get_file_content()  # 获取文件字节数组
        data_set = []
        for index in range(self.count):  #遍历每一个样本
            onepic =self.get_picture(content, index)  # 从样本数据集中获取第index个样本的图片数据,返回的是二维数组
            if onerow: onepic = self.get_one_sample(onepic)  # 将图像转化为一维向量形式
            data_set.append(onepic)
        return data_set

# 标签数据加载器
class LabelLoader(Loader):
    # 加载数据文件,获得全部样本的标签向量
    def load(self):
        content = self.get_file_content()   # 获取文件字节数组
        labels = []
        for index in range(self.count):  #遍历每一个样本
            onelabel = content[index + 8]   # 文件头有8个字节
            onelabelvec = self.norm(onelabel) #one-hot编码
            labels.append(onelabelvec)
        return labels

    # 内部函数,one-hot编码。将一个值转换为10维标签向量
    def norm(self, label):
        label_vec = []
        # label_value = self.to_int(label)
        label_value = label  # python3中直接就是int
        for i in range(10):
            if i == label_value:
                label_vec.append(0.9)
            else:
                label_vec.append(0.1)
        return label_vec

# 获得训练数据集。onerow表示是否将每张图片转化为行向量
def get_training_data_set(num,onerow=False):
    image_loader = ImageLoader('train-images.idx3-ubyte', num)  # 参数为文件路径和加载的样本数量
    label_loader = LabelLoader('train-labels.idx1-ubyte', num)  # 参数为文件路径和加载的样本数量
    return image_loader.load(onerow), label_loader.load()

# 获得测试数据集。onerow表示是否将每张图片转化为行向量
def get_test_data_set(num,onerow=False):
    image_loader = ImageLoader('t10k-images.idx3-ubyte', num)  # 参数为文件路径和加载的样本数量
    label_loader = LabelLoader('t10k-labels.idx1-ubyte', num)  # 参数为文件路径和加载的样本数量
    return image_loader.load(onerow), label_loader.load()


# 将一行784的行向量,打印成图形的样式
def printimg(onepic):
    onepic=onepic.reshape(28,28)
    for i in range(28):
        for j in range(28):
            if onepic[i,j]==0: print('  ',end='')
            else: print('* ',end='')
        print('')


if __name__=="__main__":
    train_data_set, train_labels = get_training_data_set(100)  # 加载训练样本数据集,和one-hot编码后的样本标签数据集
    train_data_set = np.array(train_data_set)
    train_labels = np.array(train_labels)
    onepic = train_data_set[12]  # 取一个样本
    printimg(onepic)  # 打印出来这一行所显示的图片
    print(train_labels[12].argmax())  # 打印样本标签

我们尝试运行一下。读取第13个样本的内容。

可以看到打印输出样式如下。

这里写图片描述

建立全连接网络模块

我们使用以下代码实现全连接网络模型,存储为DNN.py

# 实现神经网络反向传播算法,以此来训练网络
# 所谓向量化编程,就是使用矩阵运算。

import random
import numpy as np
import datetime

# 1. 当为array的时候,默认d*f就是对应元素的乘积,multiply也是对应元素的乘积,dot(d,f)会转化为矩阵的乘积, dot点乘意味着相加,而multiply只是对应元素相乘,不相加
# 2. 当为mat的时候,默认d*f就是矩阵的乘积,multiply转化为对应元素的乘积,dot(d,f)为矩阵的乘积

# Sigmoid激活函数类
class SigmoidActivator(object):
    def forward(self, weighted_input): #前向传播计算输出
        return 1.0 / (1.0 + np.exp(-weighted_input))
    def backward(self, output):  #后向传播计算导数
        return np.multiply(output,(1 - output))   # 对应元素相乘

# 全连接每层的实现类。输入对象x、神经层输出a、输出y均为列向量
class FullConnectedLayer(object):
    # 构造函数。input_size: 本层输入向量的维度。output_size: 本层输出向量的维度。activator: 激活函数
    def __init__(self, input_size, output_size,activator):
        self.input_size = input_size
        self.output_size = output_size
        self.activator = activator
        # 权重数组W
        self.W = np.random.uniform(-0.1, 0.1,(output_size, input_size))  #初始化为-0.1~0.1之间的数。权重的大小。行数=输出个数,列数=输入个数。a=w*x,a和x都是列向量
        # 偏置项b
        self.b = np.zeros((output_size, 1))  # 全0列向量偏重项
        # 输出向量
        self.output = np.zeros((output_size, 1)) #初始化为全0列向量

    # 前向计算,预测输出。input_array: 输入向量,维度必须等于input_size
    def forward(self, input_array):   # 式2
        self.input = input_array
        self.output = self.activator.forward(np.dot(self.W, input_array) + self.b)

    # 反向计算W和b的梯度。delta_array: 从上一层传递过来的误差项。列向量
    def backward(self, delta_array):
        # 式8
        self.delta = np.multiply(self.activator.backward(self.input),np.dot(self.W.T, delta_array))   #计算当前层的误差,已被上一层使用
        self.W_grad = np.dot(delta_array, self.input.T)   # 计算w的梯度。梯度=误差.*输入
        self.b_grad = delta_array  #计算b的梯度

    # 使用梯度下降算法更新权重
    def update(self, learning_rate):
        self.W += learning_rate * self.W_grad
        self.b += learning_rate * self.b_grad


# 神经网络类
class Network(object):
    # 初始化一个全连接神经网络。layers:数组,描述神经网络每层节点数。包含输入层节点个数、隐藏层节点个数、输出层节点个数
    def __init__(self, layers):
        self.layers = []
        for i in range(len(layers) - 1):
            self.layers.append(FullConnectedLayer(layers[i], layers[i+1],SigmoidActivator()))   # 创建全连接层,并添加到layers中


    # 训练函数。labels: 样本标签矩阵。data_set: 输入样本矩阵。rate: 学习速率。epoch: 训练轮数
    def train(self, labels, data_set, rate, epoch):
        for i in range(epoch):
            for d in range(len(data_set)):
                # print(i,'次迭代,',d,'个样本')
                oneobject = np.array(data_set[d]).reshape(-1,1)   #将输入对象和输出标签转化为列向量
                onelabel = np.array(labels[d]).reshape(-1,1)
                self.train_one_sample(onelabel,oneobject, rate)

    # 内部函数,用一个样本训练网络
    def train_one_sample(self, label, sample, rate):
        # print('样本:\n',sample)
        self.predict(sample)  # 根据样本对象预测值
        self.calc_gradient(label) # 计算梯度
        self.update_weight(rate) # 更新权重

    # 使用神经网络实现预测。sample: 输入样本
    def predict(self, sample):
        sample = sample.reshape(-1,1)   #将样本转换为列向量
        output = sample  # 输入样本作为输入层的输出
        for layer in self.layers:
            # print('权值:',layer.W,layer.b)
            layer.forward(output)  # 逐层向后计算预测值。因为每层都是线性回归
            output = layer.output
        # print('预测输出:', output)
        return output

    # 计算每个节点的误差。label为一个样本的输出向量,也就对应了最后一个所有输出节点输出的值
    def calc_gradient(self, label):
        # print('计算梯度:',self.layers[-1].activator.backward(self.layers[-1].output).shape)
        delta = np.multiply(self.layers[-1].activator.backward(self.layers[-1].output),(label - self.layers[-1].output))  #计算输出误差
        # print('输出误差:', delta.shape)
        for layer in self.layers[::-1]:
            layer.backward(delta)   # 逐层向前计算误差。计算神经网络层和输入层误差
            delta = layer.delta
            # print('当前层误差:', delta.shape)
        return delta

    # 更新每个连接权重
    def update_weight(self, rate):
        for layer in self.layers:  # 逐层更新权重
            layer.update(rate)


# ====================================以上为网络的类构建=================================
# ====================================以下为网络的应用=================================


# 根据返回结果计算所属类型
def valye2type(vec):
    return vec.argmax(axis=0)   # 获取概率最大的分类,由于vec是列向量,所以这里按列取最大的位置

# 使用错误率来对网络进行评估
def evaluate(network, test_data_set, test_labels):
    error = 0
    total = test_data_set.shape[0]
    for i in range(total):
        label = valye2type(test_labels[i])
        predict = valye2type(network.predict(test_data_set[i]))
        if label != predict:
            error += 1
    return float(error) / float(total)




# 由于使用了逻辑回归函数,所以只能进行分类识别。识别ont-hot编码的结果
if __name__ == '__main__':
    # 使用神经网络实现and运算
    data_set = np.array([[0,0],[0,1],[1,0],[1,1]])
    labels = np.array([[1,0],[1,0],[1,0],[0,1]])
    # print(data_set)
    # print(labels)
    net = Network([2,1,2])  # 输入节点2个(偏量b会自动加上),神经元1个,输出节点2个。
    net.train(labels, data_set, 2, 100)
    for layer in net.layers:  # 网络层总不包含输出层
        print('W:',layer.W)
        print('b:',layer.b)

    # 对结果进行预测
    sample = np.array([[1,1]])
    result = net.predict(sample)
    type = valye2type(result)
    print('分类:',type)

训练手写体识别网络模型

# 使用全连接神经网络类,和手写数据加载器,实现验证码识别。

import datetime
import numpy as np
import DNN   # 引入全连接神经网络
import MNIST  # 引入手写数据加载器

# 最后实现我们的训练策略:每训练10轮,评估一次准确率,当准确率开始下降时终止训练
def train_and_evaluate():
    last_error_ratio = 1.0
    epoch = 0
    train_data_set, train_labels = MNIST.get_training_data_set(6000,True)   # 加载训练样本数据集,和one-hot编码后的样本标签数据集
    test_data_set, test_labels = MNIST.get_test_data_set(1000,True)   # 加载测试特征数据集,和one-hot编码后的测试标签数据集
    train_data_set=np.array(train_data_set)
    train_labels=np.array(train_labels)
    test_data_set=np.array(test_data_set)
    test_labels=np.array(test_labels)


    print('样本数据集的个数:%d' % len(train_data_set))
    print('测试数据集的个数:%d' % len(test_data_set))
    network = DNN.Network([784, 300, 10])  # 定义一个输入节点784+1,神经元300,输出10

    while True:  # 迭代至准确率开始下降
        epoch += 1 # 记录迭代次数
        network.train(train_labels, train_data_set, 0.3, 1)  # 使用训练集进行训练。0.3为学习速率,1为迭代次数
        print('%s epoch %d finished' % (datetime.datetime.now(), epoch))  # 打印时间和迭代次数
        if epoch % 10 == 0:  # 每训练10次,就计算一次准确率
            error_ratio = DNN.evaluate(network, test_data_set, test_labels)  # 计算准确率
            print('%s after epoch %d, error ratio is %f' % (datetime.datetime.now(), epoch, error_ratio))  # 打印输出错误率
            if error_ratio < 0.1:  # 如果错误率开始上升就不再训练了。
                break
            else:
                print('错误率:', last_error_ratio)
                last_error_ratio = error_ratio # 否则继续训练

    index=0
    for layer in network.layers:
        np.savetxt('MNIST—W'+str(index),layer.W)
        np.savetxt('MNIST—b' + str(index), layer.b)
        index+=1
        print(layer.W)
        print(layer.b)


if __name__ == '__main__':
    train_and_evaluate()   # 使用样本数据集进行预测

由于样本数据集非常大,所以训练速度非常慢。尝试了以下,6000个样本训练一次需要14s。

展开阅读全文

没有更多推荐了,返回首页