手搓的神经网络-深入浅出神经网络与深度学习
手搓的神经网络-深入浅出神经网络与深度学习

手搓的神经网络-深入浅出神经网络与深度学习

image-20240323001648036

image-20240323001842064

network.py

import random

# Third-party libraries
import numpy as np

class Network(object):
    def __init__(self, sizes):
        self.num_layers = len(sizes)
        self.size = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]
        self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]

    def feedforward(self, a):
        """若a为输入,则返回输出"""
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a) + b)
        return a

    def SGD(self, training_data, epochs, mini_batch_size, eta,
            test_data=None):
        """
        使用小批量随机梯度下降算法训练神经网络。training_data 是由训练输入和目标输出的元组(x, y)
        组成的列表。其他非可选参数容易理解。如果提供了 test_data,那么神经网络会在每轮训练结束后用
        测试数据进行评估,并输出部分进度信息。这对于追踪进度很有用,不过会延长整体处理时间。
        :param training_data:
        :param epochs:训练轮数,整个训练数据集被神经网络模型完整地迭代训练的次数
        :param mini_batch_size:采样的小批量数据的大小
        :param eta:"eta" 通常指学习率η
        :param test_data:
        :return:
        """
        if test_data: 
            n_test = len(test_data)
        n = len(training_data)
        for j in range(epochs):
            random.shuffle(training_data)
            mini_batches = [
                training_data[k:k + mini_batch_size] for k in range(0, n, mini_batch_size)
            ]
            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)
            if test_data:
                # 每一轮的训练过程/结果
                print("Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test))
            else:
                print("Epoch {0} complete".format(j))
    def update_mini_batch(self, mini_batch, eta):
        """对一个小批量应用梯度下降算法和反向传播算法来更新神经网络的权重和偏置。mini_batch 是由若干
        元组(x, y)组成的列表,eta 是学习率。"""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        for x, y in mini_batch:
            delta_nabla_b, delta_nabla_w = self.backprop(x, y)
            nabla_b = [nb+dnb for nb, dnb in zip(nabla_b, delta_nabla_b)]
            nabla_w = [nw+dnw for nw, dnw in zip(nabla_w, delta_nabla_w)]
        self.weights = [w-(eta/len(mini_batch))*nw
                        for w, nw in zip(self.weights, nabla_w)]
        self.biases = [b-(eta/len(mini_batch))*nb
                       for b, nb in zip(self.biases, nabla_b)]
    def backprop(self, x, y):
        """返回一个表示代价函数 C_x 梯度的元组(nabla_b, nabla_w)。nabla_b 和 nabla_w 是一层接一层的
        numpy 数组的列表,类似于 self.biases 和 self.weights。"""
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # 前馈
        activation = x
        activations = [x] # 一层接一层地存放所有激活值
        zs = [] #  一层接一层地存放所有 z 向量
        for b, w in zip(self.biases, self.weights):
            z = np.dot(w, activation)+b
            zs.append(z)
            activation = sigmoid(z)
            activations.append(activation)
        # 反向传播
        delta = self.cost_derivative(activations[-1], y) * \
            sigmoid_prime(zs[-1])
        nabla_b[-1] = delta
        nabla_w[-1] = np.dot(delta, activations[-2].transpose())
        """注意,下面循环中的变量 l 和第 2 章的形式稍有不同。这里 l = 1 表示最后一层神经元,l = 2 则
        表示倒数第二层,以此类推。这是对书中方式的重编号,旨在利用 Python 列表的负索引功能。"""
        for l in range(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l+1].transpose(), delta) * sp
            nabla_b[-l] = delta
            nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
        return (nabla_b, nabla_w)

    def evaluate(self, test_data):
        """返回测试输入中神经网络输出正确结果的数目。注意,这里假设神经网络输出的是最后一层有着
        最大激活值的神经元的索引。"""
        test_results = [(np.argmax(self.feedforward(x)), y)
                        for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    def cost_derivative(self, output_activations, y):
        """返回关于输出激活值的偏导数的向量。"""
        return (output_activations-y)

def sigmoid(z):
    """sigmoid 函数"""
    return 1 / (1 + np.exp(-z))
def sigmoid_prime(z):
    """sigmoid 函数的导数"""
    return sigmoid(z)*(1-sigmoid(z))
if __name__ == '__main__':
    import mnist_loader
    training_data, validation_data, test_data = mnist_loader.load_data_wrapper()
    net = Network([784, 30, 10])
    net.SGD(training_data, 30, 10, 3.0, test_data=test_data)

运行结果:

Epoch 0: 8285 / 10000
Epoch 1: 8426 / 10000
Epoch 2: 8465 / 10000
Epoch 3: 8514 / 10000
Epoch 4: 8508 / 10000
Epoch 5: 8563 / 10000
Epoch 6: 8549 / 10000
Epoch 7: 8561 / 10000
Epoch 8: 8565 / 10000
Epoch 9: 8614 / 10000
Epoch 10: 8580 / 10000
Epoch 11: 8594 / 10000
Epoch 12: 8612 / 10000
Epoch 13: 8624 / 10000
Epoch 14: 8597 / 10000
Epoch 15: 8631 / 10000
Epoch 16: 8619 / 10000
Epoch 17: 8647 / 10000
Epoch 18: 8631 / 10000
Epoch 19: 8632 / 10000
Epoch 20: 8634 / 10000
Epoch 21: 8640 / 10000
Epoch 22: 8627 / 10000
Epoch 23: 8647 / 10000
Epoch 24: 8637 / 10000
Epoch 25: 8651 / 10000
Epoch 26: 8626 / 10000
Epoch 27: 8638 / 10000
Epoch 28: 8657 / 10000
Epoch 29: 8651 / 10000

mnist_loader.py

"""
mnist_loader
~~~~~~~~~~~~
一个加载 MNIST 图像数据的库。关于返回的数据结构的细节,参见 load_data 和 load_data_wrapper 的文档字符
串。在实践中,load_data_wrapper 通常是神经网络代码调用的函数。
"""
#### 库
# 标准库
import pickle as pk
import gzip
# 第三方库
import numpy as np

def load_data():
    """以元组形式返回 MNIST 数据,包含训练数据、验证数据和测试数据。
    返回的 training_data 是有两项的元组,第一项包含实际的训练图像,是一个有 50 000 项的 NumPy ndarray。
    每一项是一个有着 784 个值的 NumPy ndarray,代表一幅 MNIST 图像中的 28×28=784 像素。
    元组 training_data 的第二项是一个包含 50 000 项的 NumPy ndarray,这些项对应于元组第一项中包含的
    图像数字(0~9)。
    validation_data 和 test_data 类似,但图像仅有 10 000 幅。
    这种数据格式很好,但在神经网络中,对 training_data 的格式进行微调很有用。这通过封装函数
    load_data_wrapper()完成,参见下面的代码。"""
    f = gzip.open(u'../data/mnist.pkl.gz', 'rb')
    training_data, validation_data, test_data = pk.load(f, encoding='latin1')
    f.close()
    return (training_data, validation_data, test_data)
def load_data_wrapper():
    """返回一个元组,包含(training_data, validation_data, test_data)。基于 load_data,但是这个格式更
    便于实现神经网络。
    training_data 是一个包含 50 000 个二元组(x, y)的列表,其中 x 是一个 784 维的 NumPy ndarray,对应输入
    图像;y 是一个 10 维的 NumPy ndarray,表示对应 x 正确数字的单位向量。
    validation_data 和 test_data 各包含 10 000 个二元组(x, y),其中 x 是一个包含输入图像的 784 维的 NumPy
    ndarray;y 是相应的分类,对应于 x 的值(整数)。
    显然,这意味着训练数据、验证数据和测试数据采用不同的格式。这些格式对于神经网络代码来说是最
    方便的。"""
    tr_d, va_d, te_d = load_data()
    training_inputs = [np.reshape(x, (784, 1)) for x in tr_d[0]]
    training_results = [vectorized_result(y) for y in tr_d[1]]
    training_data = list(zip(training_inputs, training_results))
    validation_inputs = [np.reshape(x, (784, 1)) for x in va_d[0]]
    validation_data = list(zip(validation_inputs, va_d[1]))
    test_inputs = [np.reshape(x, (784, 1)) for x in te_d[0]]
    test_data = list(zip(test_inputs, te_d[1]))
    return (training_data, validation_data, test_data)
def vectorized_result(j):
    """返回一个 10 维的单位向量,在第 j 个位置为 1.0,其余均为 0。这可以用于将一个数字(0~9)转换成
    神经网络的一个对应的目标输出。"""
    e = np.zeros((10, 1))
    e[j] = 1.0
    return e

发表回复

您的电子邮箱地址不会被公开。