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

​​python教程全解​​


加载样本数据集

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

​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个样本的内容。

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

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

建立全连接网络模块

我们使用以下代码实现全连接网络模型,存储为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。