50、神经网络训练:手写数字图像分类实战

神经网络训练:手写数字图像分类实战

1. 神经网络简介

神经网络是一种数学函数,其结构大致基于人类大脑的结构。与大脑中存在的“有机”神经网络相区分,我们称其为人工神经网络。大脑由大量相互连接的神经元组成,当我们思考时,特定神经元会产生电活动,在合适的大脑扫描中可以看到大脑不同区域亮起。

而我们用 Python 构建的神经网络只有几十个神经元,某个特定神经元的激活程度由一个称为激活值的数字表示。当一个神经元在大脑或人工神经网络中激活时,它可能会导致相邻的连接神经元也被激活,这可以被看作是创造性思维的一种体现。

从数学角度来看,神经网络中一个神经元的激活值是与其相连神经元激活值的函数。例如,如果一个神经元与四个激活值分别为 $a_1$、$a_2$、$a_3$ 和 $a_4$ 的神经元相连,那么它的激活值就是一个应用于这四个值的数学函数,如 $f(a_1, a_2, a_3, a_4)$。

2. 用神经网络进行数据分类

2.1 手写数字图像分类问题

我们的目标是构建一个人工神经网络,对低分辨率的手写数字图像(0 - 9)进行分类。在之前的分类问题中,我们处理的是 2D 向量并将其分为两类。而在这个问题中,我们面对的是 8x8 像素的灰度图像,每个像素由一个表示其亮度的数字描述,我们将这 64 个像素的亮度值视为一个 64 维向量,要将每个 64 维向量归入十个类别之一,以表示它所代表的数字。

具体来说,我们用 Python 构建的神经网络分类函数将是一个有 64 个输入和 10 个输出的函数,即从 $\mathbb{R}^{64}$ 到 $\mathbb{R}^{10}$ 的(非线性!)向量变换。输入数字是像素的暗度值,范围从 0 到 1,十个输出值表示图像是十个数字中任何一个的可能性,输出数字中最大的索引就是答案。

2.2 训练神经网络的方法

为了找到最佳的神经网络,我们可以尝试许多不同的神经网络,并通过梯度下降的方法来系统地进行优化。线性函数由公式 $f(x) = ax + b$ 中的两个常数 $a$ 和 $b$ 决定,而一个给定形状的神经网络可能有数千个常数来决定其行为,这意味着需要计算大量的偏导数。幸运的是,由于神经网络中连接神经元的函数形式,有一种称为反向传播的快捷算法可以用于计算梯度。

我们可以使用 Python 的 scikit - learn 库来为我们进行梯度下降,它会自动训练神经网络,使其对我们的数据集进行尽可能准确的预测。

3. 手写数字图像分类的数据准备

3.1 构建 64 维图像向量

我们使用 scikit - learn 库中的手写数字图像数据。首先,我们需要导入数据:

from sklearn import datasets
digits = datasets.load_digits()

每个 digits 条目是一个 2D NumPy 数组(矩阵),给出了一个图像的像素值。例如, digits.images[0] 给出了数据集中第一个图像的像素值,是一个 8x8 的矩阵:

>>> digits.images[0]
array([[ 0.,  0.,  5., 13.,  9.,  1.,  0.,  0.],
       [ 0.,  0., 13., 15., 10., 15.,  5.,  0.],
       [ 0.,  3., 15.,  2.,  0., 11.,  8.,  0.],
       [ 0.,  4., 12.,  0.,  0.,  8.,  8.,  0.],
       [ 0.,  5.,  8.,  0.,  0.,  9.,  8.,  0.],
       [ 0.,  4., 11.,  0.,  1., 12.,  7.,  0.],
       [ 0.,  2., 14.,  5., 10., 12.,  0.,  0.],
       [ 0.,  0.,  6., 13., 10.,  0.,  0.,  0.]])

我们可以使用 matplotlib imshow 函数将矩阵显示为图像:

import matplotlib.pyplot as plt
plt.imshow(digits.images[0], cmap=plt.cm.gray_r)

为了将这个 8x8 的矩阵转换为一个 64 维的向量,我们可以使用 np.matrix.flatten 函数:

import numpy as np
v = np.matrix.flatten(digits.images[0])

为了使数据在数值上更整齐,我们将数据缩放至 0 到 1 之间。由于数据集中所有像素值都在 0 到 15 之间,我们可以将这些向量乘以 $1/15$:

scaled_v = v / 15

3.2 构建随机数字分类器

我们构建一个随机数字分类器,它接受一个 64 维向量作为输入,输出一个 10 维向量,每个元素的值在 0 到 1 之间,表示图像是十个数字中每个数字的可能性。

def random_classifier(input_vector):
    return np.random.rand(10)

我们可以使用这个分类器对第一个图像进行分类:

v = np.matrix.flatten(digits.images[0]) / 15
result = random_classifier(v)
print(result)

为了找到输出向量中最大值的索引,我们可以使用以下代码:

index = list(result).index(max(result))
print(index)

然而,随机分类器的准确率通常很低,因为它是随机猜测的。我们可以通过 test_digit_classify 函数来测试分类器的性能。

3.3 测量数字分类器的性能

def test_digit_classify(classifier, test_count=1000):
    correct = 0
    for img, target in zip(digits.images[:test_count], digits.target[:test_count]):
        v = np.matrix.flatten(img) / 15
        output = classifier(v)
        answer = list(output).index(max(output))
        if answer == target:
            correct += 1
    return (correct / test_count)

我们期望随机分类器的准确率约为 10%,例如:

>>> test_digit_classify(random_classifier)
0.107

3.4 相关练习

练习 16.1

假设一个数字分类器函数输出以下 NumPy 数组,判断图像中的数字是什么:

import numpy as np
arr = np.array([5.00512567e-06, 3.94168539e-05, 5.57124430e-09, 9.31981207e-09,
                9.98060276e-01, 9.10328786e-07, 1.56262695e-03, 1.82976466e-04,
                1.48519455e-04, 2.54354113e-07])
index = list(arr).index(max(arr))
print(index)

答案是 4。

练习 16.2 - 小项目

找到数据集中所有数字 9 的图像的平均值,并绘制结果图像。

def average_img(i):
    imgs = [img for img, target in zip(digits.images[1000:], digits.target[1000:]) if target == i]
    return sum(imgs) / len(imgs)

avg_9 = average_img(9)
import matplotlib.pyplot as plt
plt.imshow(avg_9, cmap=plt.cm.gray_r)

4. 神经网络的设计

4.1 多层感知机(MLP)

为了便于理解,我们先考虑一个更简单的神经网络,它有三个输入和两个输出。我们将重点关注一种简单而有用的神经网络类型——多层感知机(Multilayer Perceptron,简称 MLP)。

MLP 由几列称为层的神经元组成,从左到右排列。每个神经元的激活值是前一层神经元激活值的函数,最左边的层不依赖于其他神经元,其激活值基于训练数据。

以下是一个简单的 MLP 结构示意图:

graph LR
    classDef input fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef hidden fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;
    classDef output fill:#FFEBEB,stroke:#E68994,stroke-width:2px;

    A1([输入 1]):::input --> B1(隐藏层神经元 1):::hidden
    A2([输入 2]):::input --> B1
    A3([输入 3]):::input --> B1
    A1 --> B2(隐藏层神经元 2):::hidden
    A2 --> B2
    A3 --> B2
    B1 --> C1([输出 1]):::output
    B2 --> C1
    B1 --> C2([输出 2]):::output
    B2 --> C2

4.2 构建更好的分类器

我们可以通过找到测试数据集中每种数字的平均图像,并将目标图像与所有平均值进行比较,来构建一个比随机分类器更好的分类器。

avg_digits = [np.matrix.flatten(average_img(i)) for i in range(10)]

def compare_to_avg(v):
    return [np.dot(v, avg_digits[i]) for i in range(10)]

accuracy = test_digit_classify(compare_to_avg)
print(accuracy)

测试这个分类器,我们可以得到约 85% 的准确率:

>>> test_digit_classify(compare_to_avg)
0.853

通过以上步骤,我们从神经网络的基本概念出发,逐步完成了手写数字图像分类的前期准备工作,包括数据处理、分类器构建和性能测试等,为后续更深入的神经网络训练和优化奠定了基础。

5. 神经网络的工作原理深入解析

5.1 神经元激活的数学原理

在神经网络中,神经元的激活是一个关键概念。一个神经元的激活值是与其相连神经元激活值的函数。假设一个神经元与四个激活值分别为 $a_1$、$a_2$、$a_3$ 和 $a_4$ 的神经元相连,那么它的激活值可以表示为 $f(a_1, a_2, a_3, a_4)$。这种函数关系使得神经网络能够处理复杂的信息。

例如,在一个简单的场景中,如果我们有一个神经元,它的激活函数是线性的,如 $f(a_1, a_2, a_3, a_4) = w_1a_1 + w_2a_2 + w_3a_3 + w_4a_4 + b$,其中 $w_1, w_2, w_3, w_4$ 是权重,$b$ 是偏置。权重决定了每个输入对输出的影响程度,偏置则为神经元提供了一个基础的激活水平。

5.2 多层感知机的信息传递

多层感知机(MLP)是一种常见的神经网络结构。在 MLP 中,信息从输入层传递到隐藏层,再从隐藏层传递到输出层。每一层的神经元根据前一层神经元的激活值进行激活。

以下是 MLP 信息传递的步骤:
1. 输入层 :接收原始数据,如手写数字图像的 64 维向量。
2. 隐藏层 :每个隐藏层神经元根据输入层神经元的激活值进行激活,激活值通过激活函数计算得到。
3. 输出层 :根据隐藏层神经元的激活值进行激活,输出结果表示图像属于每个数字的可能性。

5.3 激活函数的作用

激活函数在神经网络中起着至关重要的作用。它引入了非线性因素,使得神经网络能够处理复杂的非线性关系。常见的激活函数有 sigmoid 函数、ReLU 函数等。

激活函数 公式 特点
sigmoid $\sigma(x) = \frac{1}{1 + e^{-x}}$ 输出范围在 0 到 1 之间,可用于表示概率
ReLU $f(x) = \max(0, x)$ 计算简单,能有效缓解梯度消失问题

6. 梯度下降与反向传播

6.1 梯度下降的原理

梯度下降是一种优化算法,用于寻找函数的最小值。在神经网络中,我们的目标是最小化损失函数,即预测结果与真实结果之间的差异。

梯度下降的基本步骤如下:
1. 初始化神经网络的权重和偏置。
2. 计算损失函数对权重和偏置的梯度。
3. 根据梯度更新权重和偏置。
4. 重复步骤 2 和 3,直到损失函数收敛。

6.2 反向传播的作用

反向传播是一种高效计算梯度的算法。在神经网络中,由于神经元之间的连接复杂,直接计算梯度非常困难。反向传播通过从输出层向输入层反向传播误差,利用链式法则计算梯度。

以下是反向传播的流程:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;

    A([开始]):::startend --> B(前向传播计算输出):::process
    B --> C(计算损失函数):::process
    C --> D(反向传播计算梯度):::process
    D --> E(更新权重和偏置):::process
    E --> F{损失函数收敛?}:::process
    F -- 否 --> B
    F -- 是 --> G([结束]):::startend

6.3 使用 scikit - learn 进行梯度下降

在实际应用中,我们可以使用 scikit - learn 库来进行梯度下降。以下是一个简单的示例:

from sklearn.neural_network import MLPClassifier
from sklearn.datasets import load_digits
from sklearn.model_selection import train_test_split

# 加载数据
digits = load_digits()
X = digits.data
y = digits.target

# 划分训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建 MLP 分类器
mlp = MLPClassifier(hidden_layer_sizes=(100,), max_iter=1000, random_state=42)

# 训练模型
mlp.fit(X_train, y_train)

# 评估模型
accuracy = mlp.score(X_test, y_test)
print(f"模型准确率: {accuracy}")

7. 总结与展望

7.1 总结

通过本文,我们从神经网络的基本概念出发,深入探讨了手写数字图像分类的整个过程。我们学习了如何将图像数据转换为向量,构建不同类型的分类器,并使用梯度下降和反向传播算法训练神经网络。

7.2 展望

虽然我们已经取得了一定的成果,但神经网络还有很大的优化空间。未来,我们可以尝试以下方法来提高分类准确率:
1. 调整神经网络结构 :增加隐藏层神经元数量或隐藏层的层数。
2. 优化激活函数 :尝试不同的激活函数,如 Leaky ReLU、ELU 等。
3. 使用正则化方法 :如 L1 和 L2 正则化,防止过拟合。

通过不断地探索和实践,我们可以进一步提高神经网络的性能,使其在更多领域发挥作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值