机器学习(17) 实例:利用神经网络对图片中手写数字 0-9 进行识别

实现反向传播 backpropagation神经网络算法, 对图片中手写数字 0-9 进行识别

手写数字图片数据。每张图片20px * 20px,也就是一共400个特征。

 

1 样本数据 ex4data1.mat

一共有5000个训练实例(training instance)。

  1. 用 X 矩阵表示整个训练集,则 X 是一个 5000*400 (5000行 400列)的矩阵
  2. 用 y 向量标记整个训练集的结果,则 y 是一个 5000*1 (5000行 1列)的列向量

2 模型建立

上图所示神经网络一共有三个层,分别是输入层(input layer),隐藏层(hidden layer),输出层(output layer)。特点如下:

1、每层由单元(units)组成

2、输入层是有训练集的实例特征向量传入

3、经过连接接点的权重(weight)传入下一层,一层的输出是下一层的输入

4、隐藏层的个数可以是任意的,输入层有一层,输出层有一层

5、每个单元也可以称之为神经结点,根据生物学来源定义

扫描二维码关注公众号,回复: 16967125 查看本文章

6、以上成为两层的神经网络,输入层是不算在里面的

7、一层中加权求和,然后根据非线性方程转化输出

8、作为多层向前神经网络,理论上,如果有足够的隐藏层,和足够的训练集,可以模拟出任何方程

3 完整 Python代码

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy.io import loadmat

## 1 加载和可视化数据
#  1.1 加载数据
print('第一步 加载和可视化数据 ...')
print('1.1 加载数据 ...')
data = loadmat('ex4data1.mat')
X = data['X']
y = data['y']
print(f'输入特征的形状 X.shape = {X.shape}, 输出特征的形状 y.shape = {y.shape}')

#  1.2 可视化X中部分数据(随机选择9个图片)
print('1.2 可视化数据 ...')
fig,axs=plt.subplots(3,3)
axs=axs[:,:].flatten()
import random
for i in range(len(axs)):
    j=random.randint(0,len(X)-1)
    num=int(np.sqrt(len(X[0,:])))
    axs[i].imshow(X[j,:].reshape(num,num).T)
    axs[i].set_ylabel(str(y[j]))
plt.show()

# 1.3 对y标签进行一次one-hot编码。
#     one-hot编码将类标签n (k类)转换为长度为k的向量,
#     其中索引n为‘hot’(1),二其余为0。
#     Scikitlearn有一个内置的使用程序,我们可以使用这个。
print('1.3 对y标签进行一次one-hot编码 ...')

from sklearn.preprocessing import OneHotEncoder
encoder = OneHotEncoder(sparse = False)
y_onehot = encoder.fit_transform(y)
print(f'输出特征的形状 y_onehot.shape = {y_onehot.shape}')

## 2 构建的神经网络
##   1个输入层:与实例数据(400 + 偏置单元)大小匹配
##   1个隐藏层:25个单位的隐藏层(带有偏置单元的26个),
##   1个输出层:10个单位对应我们的一个one-hot编码类标签。

# 2.1  前向传播


# 2.1.1 定义sigmoid函数
def sigmoid(z):
    return 1 / (1 + np.exp(-z))

# 2.1.2 定义前向传播函数 (400 + 1) -> (25 + 1) -> (10)
def forward_propagate(X, theta1, theta2):
    # INPUT:参数值theta,数据X
    # OUTPUT:当前参数值下前项传播结果
    # TODO:根据参数和输入的数据计算前项传播结果
    
    # STEP1:获取样本个数
    m = X.shape[0]
    
    # STEP2:实现神经网络正向传播
    a1 = np.insert(X, 0, values = np.ones(m), axis = 1) # 给X矩阵插入一1列元素
    z2 = a1 * theta1.T
    a2 = sigmoid(z2)
    a2 = np.insert(a2, 0, values = np.ones(m), axis = 1) # 注意插入1列元素
    z3 = a2 * theta2.T
    h = sigmoid(z3)
    return a1, z2, a2, z3, h
    
# 2.1.3 定义前向传播的代价函数
def cost(params, input_size, hidden_size, num_labels, X, y, lamda):
    '''
    输入参数:
        params:神经网络参数
        input_size:输入层维度
        hidden_size:隐藏层维度
        num_labels:输出标签维度
        X,y:训练数据及标签
        lamda:正则化参数
    返回值:当前参数值params下的代价函数
    '''
  
    # STEP1:获取样本个数
    m = X.shape[0]
    
    # STEP2:将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)
    y = np.matrix(y)
    
    # STEP3:从params中获取神经网络参数,并按照输入层维度和隐藏层维度重新定义参数的维度
    theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
    theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_labels, (hidden_size + 1))))
    
    # STEP4:调用前面写好的前项传播函数
    a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
    
    # STEP5:初始化代价函数
    J = 0
    
    # STEP6:根据公式计算代价函数
    for i in range(m): # 遍历每个样本
        first_term = np.multiply(-y[i,:], np.log(h[i,:]))
        second_trem = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
        J += np.sum(first_term - second_trem)
    J = J / m;
    
    # STEP7:计算代价函数的正则化部分
    J += (float(lamda) / (2 * m)) * (np.sum(np.power(theta1[:,1:], 2)) + np.sum(np.power(theta2[:,1:], 2)))
    
    return J

# 2.1.4 初始化设置
input_size  = 400;  # 20x20=400像素 每个像素为一个特征,输入层有400个特征
hidden_size = 25;   # 隐藏层有25个神经元
num_lables = 10;          # 输出层有10 标记, 从1 到 10   
                          # (注意我们将 "0" 映射为 标记 10)
lamda = 1                 # 正则化参数

print(f'第二步 定义二层神经网络....[400,25,10]')
print(f'2.1 随机初始化完整网络参数大小的参数数据.....')

# 2.1.4.1 随机初始化完整网络参数大小的参数数据
params = (np.random.random(size = hidden_size * (input_size + 1) + num_lables * (hidden_size + 1)) - 0.5) * 0.25

m = X.shape[0]    # 样本个数
X = np.matrix(X)  # 将输入特征数组转化为矩阵
y = np.matrix(y)  # 将输出特征数组转换为矩阵

# 2.1.4.2 将参数数据params解开为每个层的参数矩阵
theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_lables, (hidden_size + 1))))
print(f'theta1.shape = {theta1.shape},   theta2.shape = {theta2.shape}')

# 2.1.5 前向传播
print(f'2.2 前向传播.....')
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
print(f'a1.shape = {a1.shape}, z2.shape = {z2.shape}, ')
print(f'a2.shape = {a2.shape}, z3.shape = {z3.shape}, ')
print(f' h.shape = {h.shape}。')

# 2.1.6 计算代价函数
J = cost(params, input_size, hidden_size, num_lables, X, y_onehot, lamda)
print(f'代价函数 J = {J}。')

## 2.2 反向传播法
##     反向传播参数更新计算将减少训练数据上的网络误差。
##    我们需要的第一件事是计算我们之前创建的Sigmiod函数的梯度函数。

# 2.2.1 定义Sigmoid函数的梯度函数
def sigmoid_gradient(z):
    return np.multiply(sigmoid(z), (1 - sigmoid(z)))

# 2.2.2 定义反向传播函数
#       由于反向传播所需要的计算是代价函数中所需的计算过程,
#       我们实际上将扩展代价函数以执行反向传播并返回代价和梯度。
def backward_propagate(params, input_size, hidden_size, num_labels, X, y, lamda):
    '''
    输入参数:
        params:神经网络参数
        input_size:输入层维度
        hidden_size:隐藏层维度
        num_labels:输出标签维度
        X,y:训练数据及标签
        lamda:正则化参数
    返回值:
        J -- 当前参数值params下的代价函数
        grade -- 反向传播梯度值
    '''
    
    # STEP1:获取样本个数
    m = X.shape[0]
    
    # STEP2:将矩阵X,y转换为numpy型矩阵
    X = np.matrix(X)
    y = np.matrix(y)
    
    # STEP3:从params中获取神经网络参数,并按照输入层维度和隐藏层维度重新定义参数的维度
    theta1 = np.matrix(np.reshape(params[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
    theta2 = np.matrix(np.reshape(params[hidden_size * (input_size + 1):], (num_lables, (hidden_size + 1))))

    # STEP4:调用前面写好的前项传播函数
    a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
    
    # STEP5:初始化
    J = 0
    theta1_grad = np.zeros(theta1.shape) # (25, 401)
    theta2_grad = np.zeros(theta2.shape) # (10, 26)
    
    # STEP6:计算代价函数(调用函数)
    for i in range(m): # 遍历每个样本
        first_term = np.multiply(-y[i,:], np.log(h[i,:]))
        second_trem = np.multiply((1 - y[i,:]), np.log(1 - h[i,:]))
        J += np.sum(first_term - second_trem)
    J = J / m;
    
    # STEP7:实现反向传播(这里用到的公式请参考原版作业PDF的第5页)
    for t in range(m): # 遍历每个样本
        a1t = a1[t,:] # (1, 401)
        z2t = z2[t,:] # (1, 25)
        a2t = a2[t,:] # (1, 26)
        ht = h[t,:] # (1, 10)
        yt = y[t,:] # (1, 10)
        
        delta3 = ht - yt
        z2t = np.insert(z2t, 0, values = np.ones(1)) # (1, 26)
        delta2 = np.multiply((theta2.T * delta3.T).T, sigmoid_gradient(z2t)) # (1, 26)
        theta1_grad = theta1_grad + (delta2[:,1:]).T * a1t
        theta2_grad = theta2_grad + delta3.T * a2t
    
    # STEP8:加入正则化
    theta1_grad[:,1:] = theta1_grad[:,1:] + (theta1[:,1:] * lamda) / m
    theta2_grad[:,1:] = theta2_grad[:,1:] + (theta2[:,1:] * lamda) / m
    
    # STEP9:将梯度矩阵转换为单个数据
    grad = np.concatenate((np.ravel(theta1_grad), np.ravel(theta2_grad))) # ravel()降维
    
    return J, grad
## 反向传播计算的最难的部分(除了理解为什么我们正在做所有这些计算)是获得正确矩阵维度。
## 顺便说一下,你容易混淆了A * B与np.multiply(A, B)使用。基本上前者是矩阵乘法,
## 后者是元素乘法(除非A或B是标量值,在这种情况下没关系)。
## 无论如何,让我们测试一下,以确保函数返回我们期望的。

# 2.2.3 反向传播
J, grad = backward_propagate(params, input_size, hidden_size, num_lables, X, y_onehot, lamda)
print('2.3 反向传播')
print(f'代价函数 J.shape = {J.shape}, grad.shape = {grad.shape}。')

## 2.4 开始训练网络
##     由于目标函数不太可能完全收敛,我们对迭代次数进行了限制。
##     我们的总代价已经下降到0.5以下,这是算法正常工作的一个很好的指标。
##     让我们使用它发现的参数,并通过网络转发,以获得一些预测。
from scipy.optimize import minimize
print('2.4 训练网络....')
fmin = minimize(fun = backward_propagate,
                x0 = params,
                args = (input_size, hidden_size, num_lables, X, y_onehot, lamda),
                method = 'TNC',
                jac = True,
                options = {'maxiter':2500})
print(f'迭代100次后的最优参数  fmin: \n {fmin}')

## 2.5 预测
X = np.matrix(X)
theta1 = np.matrix(np.reshape(fmin.x[:hidden_size * (input_size + 1)], (hidden_size, (input_size + 1))))
theta2 = np.matrix(np.reshape(fmin.x[hidden_size * (input_size + 1):], (num_lables, (hidden_size + 1))))

## 2.5.1 预测全部样本的准确率
a1, z2, a2, z3, h = forward_propagate(X, theta1, theta2)
y_pred = np.array(np.argmax(h, axis = 1) + 1)
acc=np.mean(y_pred==y)
print(f'整个样本的预测准确率 = {acc*100}%')   #打印预测准确率

运行后结果如下:

 

猜你喜欢

转载自blog.csdn.net/luyouqi11/article/details/132099797
今日推荐