目录

一、引言

二、卷积神经网络概述

(一)卷积神经网络的结构

1.卷积层

2.池化层

(二)卷积神经网络的训练

(三)卷积神经网络识别手写体数字

三、SVM 识别手写体数字

(一)支持向量机的原理

(二)函数间隔

(三)几何间隔

(四)间隔最大化

(五)SVC 识别手写体数字实例

四、总结


一、引言

手写体数字识别是图像识别学科的一个分支,是图像处理和模式识别研究领域的重要应用之一,并且具有很强的通用性。由于手写体数字的随意性很大,如粗细、字体大小、倾斜角度等因素都有可能直接影响到字符的识别准确性,所以手写体数字识别是一个很有挑战性的课题。在过去的数十年中,研究者们提出了许多识别方法,并取得了一定的成果。手写体数字识别的实用性很强,在大规模数据统计,如例行年检、人口普查、财务、税务、邮件分拣等应用领域都有广泛的应用前景。

本文分别介绍利用卷积神经网络和支持向量机识别手写体数字。

二、卷积神经网络概述

卷积神经网络(Convolutional Neural Network,CNN)是一类包含卷积计算且具有深度结构的前馈神经网络(Feedforward Neural Network)。

下面主要介绍利用 CNN 识别手写体数字。

(一)卷积神经网络的结构

一个卷积神经网络由很多层组成,它们的输入是三维的,输出也是三维的,有的层有参数,有的层不需要参数。图 1 为一个卷积神经网络与全连接网络的对比图。

                                                     图1 卷积神经网络与全连接网络结构

图 1 的左边图为全连接神经网络(平面),由输入层、激活函数、全连接层组成。右边图为卷积神经网络(立体),由输入层、卷积层、激活函数、池化层、全连接层组成。在卷积神经网络中有一个重要的概念 —— 深度。下面对卷积神经网络的卷积层与池化层结构进行简单介绍。

1.卷积层

卷积是指在原始的输入上进行特征的提取。特征提取简言之就是在原始输入的一个小区域进行特征的提取。如图 2 所示,左边方块是输入层,尺寸为 32×32 像素的 3 通道图像。右边的小方法是 filter,尺寸为 5×5 像素,深度为 3。

                              图2 卷积层 

将输入层划分为多个区域,用 filter 这个固定尺寸助手,在输入层做运算,最终得到一个深度为 1 的特征图。图 3 展示出一般使用多个 filter 分别进行卷积,最终得到的多个特征图。

                                                             图3 特征过程图

图 4 使用了 6 个 filter 分别卷积进行特征提取,最终得到 6 个特征图。将这 6 个特征图层叠在一起就得到了卷积层的输出结果。

                                                             图4 特征提取

通常来说,一个卷积层后面跟着一个池化层,后者基本上汇总了池化层接收到邻域所输出特征图的激活情况。下面介绍池化层。

2.池化层

如图 5 所示,池化就是对特征图进行特征压缩,池化也叫作下采样。选择原来某个区域的 max 或 mean 代替那个区域,整体就浓缩了。

                                                 图5 池化过程

图 6 演示了池化(pooling)操作,需要制定一个 filter 的尺寸、stride、pooling 方式(max 或 mean)。

                                                              图6 池化操作过程

需要注意的是,卷积操作减少了每一层需要学习的权重数量。例如,大小为 224×224 像素的输入图像输出到下一层的维度应该是 224×224。那么对于一个传统的全连接神经网络,需要学习的权重个数为 224×224×224×224。对于一个拥有同样输入和输出维度的卷积层,我们只需学习滤波核的函数的权值。因此,如果使用一个 3×3 的滤波核函数,则只需学习 9 个权重,而不是 224×224×224×224 个权重。因为图像和音频的结构在局部空间中有高度相关性,这个简化操作的效果很好。

输入图像会经过多层卷积和池化操作。随着网络层层数的增加,特征图的个数不断增加,同时图像的空间分辨率不断减小。在卷积 - 池化层的最后,特征图被传入全连接网络,最后是输出层。

输出单元依赖于具体的任务。如果是回归问题,则输出单元的激活函数是线性的。如果是二元分类问题,则输出单元是 sigmoid。对于多分类问题,输出层是 softmax 单元。

(二)卷积神经网络的训练

卷积神经网络的训练过程和全连接网络的训练过程比较类似,都是先将参数随机初始化,进行前向计算,得到最后的输出结果,计算最后一层每个神经元的残差,然后从最后一层开始逐层往前计算每一层的神经元的残差,根据残差计算损失对参数的导数,然后再迭代更新参数。这里反向传播中最重要的一个数学概念就是求导的链式法则。求导的链式法则公式为:∂y/∂x = ∂y/∂z × ∂z/∂x用 δᵢ⁽ˡ⁾表示第 l 层的第 i 个神经元 vᵢ⁽ˡ⁾的残差,即损失函数对第 l 层的第 i 个神经元 vᵢ⁽ˡ⁾的偏导数:δᵢ⁽ˡ⁾ = ∂L (w,b)/∂vᵢ⁽ˡ⁾用∂L (w,b)/∂wᵢⱼ⁽ˡ⁾表示损失函数对第 l 层上的参数的偏导数:∂L (w,b)/∂wᵢⱼ⁽ˡ⁾ = δᵢ⁽ˡ⁾ × aⱼ⁽ˡ⁻¹⁾其中,aⱼ⁽ˡ⁻¹⁾是前面一层的第 j 个神经元的激活值。激活值的前向计算的时候已经得到,所以只要计算出每个神经元的残差,就能得到损失函数对每个参数的偏导数。

最后一层残差的计算公式:δᵢ⁽ᴷ⁾ = -(yᵢ - aᵢ⁽ᴷ⁾) × f’(vᵢ⁽ᴷ⁾)其中,yᵢ为正确的输出值,aᵢ⁽ᴷ⁾为最后一层第 i 个神经元的激活值,f’是激活函数的导数。

其他层神经元的残差计算公式:δᵢ⁽ˡ⁻¹⁾ = (∑ⱼⁿˡ wⱼᵢ⁽ˡ⁻¹⁾ × δⱼ⁽ˡ⁾) × f’(vᵢ⁽ˡ⁻¹⁾)在求得了所有节点的残差之后,就能得到损失函数对所有参数的偏导数,然后进行参数更新。

普通的卷积神经网络和全连接神经网络的结构差别主要在于卷积神经网络有卷积和池化操作,那么只要搞清楚卷积层和池化层残差是如何反向传播的,如何利用残差计算卷积核内参数的偏导数,就基本实现了卷积网络的训练过程。

(三)卷积神经网络识别手写体数字

本案例将开发一个四层卷积神经网络,提升预测 MNIST 数字的准确度。前两个卷积层由 Convolution-ReLU-maxpool 操作组成,后两层是全连接层。

TensorFlow有内置的MNIST数据集。数据集加载之后,设置算法模型变量,创建模型,批量训练模型,并且可视化损失函数、准确度和一些采样数据。

import matplotlib.pyplot as plt
import numpy as np
import tensorflow as tf
import os
import datetime
from tensorflow.keras.datasets import mnist
from tensorflow.python.framework import ops

# 创建保存结果的目录
save_dir = "mnist_results"
if not os.path.exists(save_dir):
    os.makedirs(save_dir)

# 创建日志文件
log_file = os.path.join(save_dir, f"training_log_{datetime.datetime.now().strftime('%Y%m%d_%H%M%S')}.txt")


def log(message):
    """同时输出到控制台和日志文件"""
    print(message)
    with open(log_file, 'a', encoding='utf-8') as f:
        f.write(message + '\n')


# 禁用即时执行模式
tf.compat.v1.disable_eager_execution()

# 重置默认图
ops.reset_default_graph()

# 加载数据(TensorFlow 2.x的标准加载方式)
(x_train, y_train), (x_test, y_test) = mnist.load_data()
# 处理训练数据:归一化并调整形状为28×28
train_xdata = x_train / 255.0  # 归一化到[0,1]范围
test_xdata = x_test / 255.0
train_labels = y_train
test_labels = y_test

log("数据加载完成,训练集大小: {}, 测试集大小: {}".format(len(train_xdata), len(test_xdata)))

# 设置模型参数。由于图像是灰度图,颜色通道数为1
batch_size = 100
learning_rate = 0.005
evaluation_size = 500
image_width = train_xdata[0].shape[0]
image_height = train_xdata[0].shape[1]
target_size = np.max(train_labels) + 1  # 目标类别数(0-9共10类)
num_channels = 1  # 灰度图,通道数为1
generations = 500
eval_every = 5
conv1_features = 25
conv2_features = 50
max_pool_size1 = 2
max_pool_size2 = 2
fully_connected_size1 = 100

log("模型参数设置完成,训练代数: {}, 批次大小: {}".format(generations, batch_size))

# 声明数据集占位符(使用tf.compat.v1.placeholder兼容1.x语法)
x_input_shape = (batch_size, image_width, image_height, num_channels)
x_input = tf.compat.v1.placeholder(tf.float32, shape=x_input_shape)
y_target = tf.compat.v1.placeholder(tf.int32, shape=(batch_size,))
eval_input_shape = (evaluation_size, image_width, image_height, num_channels)
eval_input = tf.compat.v1.placeholder(tf.float32, shape=eval_input_shape)
eval_target = tf.compat.v1.placeholder(tf.int32, shape=(evaluation_size,))

# 声明卷积层权重和偏置
conv1_weight = tf.Variable(
    tf.random.truncated_normal([4, 4, num_channels, conv1_features], stddev=0.1, dtype=tf.float32))
conv1_bias = tf.Variable(tf.zeros([conv1_features], dtype=tf.float32))
conv2_weight = tf.Variable(
    tf.random.truncated_normal([4, 4, conv1_features, conv2_features], stddev=0.1, dtype=tf.float32))
conv2_bias = tf.Variable(tf.zeros([conv2_features], dtype=tf.float32))

# 声明全连接层权重和偏置
resulting_width = image_width // (max_pool_size1 * max_pool_size2)
resulting_height = image_height // (max_pool_size1 * max_pool_size2)
full1_input_size = resulting_width * resulting_height * conv2_features
full1_weight = tf.Variable(
    tf.random.truncated_normal([full1_input_size, fully_connected_size1], stddev=0.1, dtype=tf.float32))
full1_bias = tf.Variable(tf.random.truncated_normal([fully_connected_size1], stddev=0.1, dtype=tf.float32))
full2_weight = tf.Variable(
    tf.random.truncated_normal([fully_connected_size1, target_size], stddev=0.1, dtype=tf.float32))
full2_bias = tf.Variable(tf.random.truncated_normal([target_size], stddev=0.1, dtype=tf.float32))


# 定义卷积网络模型函数
def my_conv_net(input_data):
    # 第一层:Conv - ReLU - MaxPool
    conv1 = tf.nn.conv2d(input_data, conv1_weight, strides=[1, 1, 1, 1], padding='SAME')
    relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_bias))
    max_pool1 = tf.nn.max_pool(relu1, ksize=[1, max_pool_size1, max_pool_size1, 1],
                               strides=[1, max_pool_size1, max_pool_size1, 1], padding='SAME')

    # 第二层:Conv - ReLU - MaxPool
    conv2 = tf.nn.conv2d(max_pool1, conv2_weight, strides=[1, 1, 1, 1], padding='SAME')
    relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_bias))
    max_pool2 = tf.nn.max_pool(relu2, ksize=[1, max_pool_size2, max_pool_size2, 1],
                               strides=[1, max_pool_size2, max_pool_size2, 1], padding='SAME')

    # 展平卷积输出,连接全连接层
    final_conv_shape = max_pool2.get_shape().as_list()
    final_shape = final_conv_shape[1] * final_conv_shape[2] * final_conv_shape[3]
    flat_output = tf.reshape(max_pool2, [final_conv_shape[0], final_shape])

    # 全连接层1(ReLU激活)
    fully_connected1 = tf.nn.relu(tf.add(tf.matmul(flat_output, full1_weight), full1_bias))
    # 全连接层2(输出层)
    final_model_output = tf.add(tf.matmul(fully_connected1, full2_weight), full2_bias)
    return final_model_output


# 声明训练和测试模型输出
model_output = my_conv_net(x_input)
test_model_output = my_conv_net(eval_input)

# 定义损失函数(稀疏softmax交叉熵)
loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(logits=model_output, labels=y_target))
# 定义预测函数(softmax)
prediction = tf.nn.softmax(model_output)
test_prediction = tf.nn.softmax(test_model_output)


# 定义精度计算函数
def get_accuracy(logits, targets):
    batch_predictions = np.argmax(logits, axis=1)
    num_correct = np.sum(np.equal(batch_predictions, targets))
    return (100. * num_correct / batch_predictions.shape[0])


# 定义优化器和训练步骤
my_optimizer = tf.compat.v1.train.MomentumOptimizer(learning_rate, 0.9)
train_step = my_optimizer.minimize(loss)

# 初始化所有变量(TensorFlow 2.x中使用compat.v1的初始化函数)
init = tf.compat.v1.global_variables_initializer()
# 创建会话(TensorFlow 2.x兼容模式)
with tf.compat.v1.Session() as sess:
    sess.run(init)

    # 训练模型并评估
    train_loss = []
    train_acc = []
    test_acc = []
    log("\n开始模型训练...")
    for i in range(generations):
        # 随机选择批量训练数据
        rand_index = np.random.choice(len(train_xdata), size=batch_size)
        rand_x = train_xdata[rand_index]
        # 增加通道维度(从28×28变为28×28×1)
        rand_x = np.expand_dims(rand_x, 3)
        rand_y = train_labels[rand_index]
        train_dict = {x_input: rand_x, y_target: rand_y}

        # 执行训练步骤
        sess.run(train_step, feed_dict=train_dict)
        temp_train_loss, temp_train_preds = sess.run([loss, prediction], feed_dict=train_dict)
        temp_train_acc = get_accuracy(temp_train_preds, rand_y)

        # 定期评估测试集
        if (i + 1) % eval_every == 0:
            eval_index = np.random.choice(len(test_xdata), size=evaluation_size)
            eval_x = test_xdata[eval_index]
            eval_x = np.expand_dims(eval_x, 3)  # 增加通道维度
            eval_y = test_labels[eval_index]
            test_dict = {eval_input: eval_x, eval_target: eval_y}
            test_preds = sess.run(test_prediction, feed_dict=test_dict)
            temp_test_acc = get_accuracy(test_preds, eval_y)

            # 记录并打印结果
            train_loss.append(temp_train_loss)
            train_acc.append(temp_train_acc)
            test_acc.append(temp_test_acc)
            acc_and_loss = [(i + 1), temp_train_loss, temp_train_acc, temp_test_acc]
            acc_and_loss = [np.round(x, 2) for x in acc_and_loss]
            log('Generation # {}. Train Loss: {:.2f}. Train Acc (Test Acc): {:.2f} ({:.2f})'.format(*acc_and_loss))

    # 绘制并保存损失函数曲线
    eval_indices = range(0, generations, eval_every)
    plt.figure(figsize=(10, 6))
    plt.plot(eval_indices, train_loss, 'k-')
    plt.title('Softmax Loss per Generation')
    plt.xlabel('Generation')
    plt.ylabel('Softmax Loss')
    loss_plot_path = os.path.join(save_dir, 'loss_curve.png')
    plt.savefig(loss_plot_path, dpi=300, bbox_inches='tight')
    log(f"损失函数曲线已保存至: {loss_plot_path}")
    plt.show()

    # 绘制并保存训练和测试精度曲线
    plt.figure(figsize=(10, 6))
    plt.plot(eval_indices, train_acc, 'k-', label='Train Set Accuracy')
    plt.plot(eval_indices, test_acc, 'r--', label='Test Set Accuracy')
    plt.title('Train and Test Accuracy')
    plt.xlabel('Generation')
    plt.ylabel('Accuracy')
    plt.legend(loc='lower right')
    accuracy_plot_path = os.path.join(save_dir, 'accuracy_curve.png')
    plt.savefig(accuracy_plot_path, dpi=300, bbox_inches='tight')
    log(f"精度曲线已保存至: {accuracy_plot_path}")
    plt.show()

    # 可视化部分预测结果并保存
    # 重新获取一批数据用于可视化
    rand_index = np.random.choice(len(train_xdata), size=batch_size)
    rand_x = train_xdata[rand_index]
    rand_x_vis = np.expand_dims(rand_x, 3)
    rand_y = train_labels[rand_index]
    train_dict_vis = {x_input: rand_x_vis, y_target: rand_y}
    temp_train_preds = sess.run(prediction, feed_dict=train_dict_vis)

    actuals = rand_y[0:6]
    predictions = np.argmax(temp_train_preds, axis=1)[0:6]
    images = np.squeeze(rand_x[0:6])  # 移除通道维度

    plt.figure(figsize=(10, 7))
    Nrows, Ncols = 2, 3
    for i in range(6):
        plt.subplot(Nrows, Ncols, i + 1)
        plt.imshow(np.reshape(images[i], [28, 28]), cmap='Greys_r')
        plt.title(f'Actual: {actuals[i]} Pred: {predictions[i]}', fontsize=10)
        frame = plt.gca()
        frame.axes.get_xaxis().set_visible(False)
        frame.axes.get_yaxis().set_visible(False)
    plt.tight_layout()
    predictions_plot_path = os.path.join(save_dir, 'predictions_sample.png')
    plt.savefig(predictions_plot_path, dpi=300, bbox_inches='tight')
    log(f"预测结果示例已保存至: {predictions_plot_path}")
    plt.show()

    # 保存最终结果摘要
    final_summary = f"""
    训练完成总结:
    - 总训练代数: {generations}
    - 最终训练损失: {train_loss[-1]:.4f}
    - 最终训练精度: {train_acc[-1]:.2f}%
    - 最终测试精度: {test_acc[-1]:.2f}%
    - 结果保存目录: {os.path.abspath(save_dir)}
    """
    log(final_summary)

在实例中,训练的批量大小为 100,在迭代训练中观察准确度和损失函数,最后绘制 6 幅随机图片以及对应的实际数字和预测数字。

卷积神经网络算法在图像识别方向效果很好。部分原因是卷积层操作将图片中重要的部分特征转化成低维特征。卷积神经网络模型创建它们的特征,并用该特征预测。

三、SVM 识别手写体数字

支持向量机(Support Vector Machine,SVM)是一种分类算法,但是也可以做回归。根据输入的数据不同可做不同的模型(若输入标签为连续值则做回归,若输入标签为分类值则用 SVM 做分类)。通过寻求结构化风险最小来提高学习机泛化能力,实现经验风险和置信范围的最小化,从而达到在统计样本量较少的情况下,获得良好统计规律的目的。通俗来讲,它是一种二元分类模型,其基本模型定义为特征空间上的间隔最大的线性分类器,即支持向量机的学习策略便是间隔最大化,最终可转化为一个凸二次规划问题的求解。

下面主要介绍利用 SVM 识别手写体数字。

(一)支持向量机的原理

在机器学习领域,SVM 是一个有监督的学习模型,通常用来进行模式识别、分类以及回归分析。

SVM 的基本思想是:建立一个最优决策超平面,使得该平面两侧距离平面最近的两类样本之间的距离最大化,从而对分类问题提供良好的泛化能力。即是指,当样本点的分布无法用一条直线或几条直线分开时(即线性不可分),SVM 提供一种算法,求出一个曲面用于划分。这个曲面就称为最优决策超平面。而且,SVM 采用二次优化,因此最优解是唯一的,且为全局最优。前面提到的距离最大化就是说,这个曲面让不同分类的样本点距离最远,即求最优分类超平面等价于求最大间隔。

如图 7 所示,SVM 的原理大致分为:假设要通过一条线把星星和红点分成两类,那么有无数多条线可以完成这个任务,在 SVM 中,我们寻找一条最优的分界线使得它到两边的 Margin 都最大,在这种情况下边缘的几个数据点就叫作 Support Vector,这也是这个分类算法名字的来源。

                            图7 SVM原理图

(二)函数间隔

如图 8 所示,点 x 到蓝色线的距离为 L=β‖x‖。

               图8 距离图

现在定义一下函数间距。对于一个训练样本 (x⁽ⁱ⁾, y⁽ⁱ⁾),定义相应的函数间距为:γ̂⁽ⁱ⁾ = y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b) = y⁽ⁱ⁾g (x⁽ⁱ⁾)

注意,前面乘上类别 y 之后可以保证这个 margin 的非负性(因为 g (x)<0 对应 y=-1 的那些点)。

所以,如果 y⁽ⁱ⁾=1,为了让函数间距比较大(预测的确信度就大),则需要 wᵀx⁽ⁱ⁾+b 是一个大的正数。反过来,如果 y⁽ⁱ⁾=-1,为了让函数间距比较大(预测的确信度就大),则需要 wᵀx⁽ⁱ⁾+b 是一个大的负数。

接着就是要找所有点中间距离最小的点了。对于给定的数据集 S=(x⁽ⁱ⁾, y⁽ⁱ⁾);i=1,2,…,m,定义 γ̂是数据集中函数间距最小的,即:γ̂ = min_{i=1,2,\cdots ,m} γ̂⁽ⁱ⁾

但这里有一个问题就是,对于函数间距来说,当 w 和 b 被替换成 2w 和 2b 时,有 g (wᵀx⁽ⁱ⁾+b)=g (2wᵀx⁽ⁱ⁾+2b),这不会改变 hᵥ,ᵦ(x) 的值。所以为此引入了几何间距。

(三)几何间隔

考虑图 9,直线为决策边界(由 w,b 决定)。向量 w 垂直于直线(为什么?θᵀx=0,非零向量的内积为 0,说明它们互相垂直)。假设 A 点代表样本 x⁽ⁱ⁾,它的类别为 y=1。假设 A 点到决策边界的距离为 γ⁽ⁱ⁾,也就是线段 AB。

                          图9 几何间距

那么,应该如何计算 γ⁽ⁱ⁾?首先我们知道 w/‖w‖表示的是在 w 方向上的单位向量。因为 A 点代表的是样本 x⁽ⁱ⁾,所以 B 点为:x⁽ⁱ⁾ - γ⁽ⁱ⁾・w/‖w‖。又因为 B 点是在决策边界上,所以 B 点满足 wᵀx + b = 0,也就是:wᵀ(x⁽ⁱ⁾ - γ⁽ⁱ⁾・w/‖w‖) + b = 0

解方程得:γ⁽ⁱ⁾ = (wᵀx⁽ⁱ⁾ + b)/‖w‖ = ( (w/‖w‖)ᵀx⁽ⁱ⁾ + b/‖w‖ )

当然,上面这个方程对应的是正例的情况,反例的时候上面方程的解就是一个负数,这与我们平常说的距离不符合,所以乘以 y⁽ⁱ⁾,即:γ⁽ⁱ⁾ = y⁽ⁱ⁾((w/‖w‖)ᵀx⁽ⁱ⁾ + b/‖w‖ )

可以看到,当‖w‖=1 时,函数间距与几何间距就是一样的了。

同样,有了几何间距的定义,接着就是要找所有点中间距最小的点了。对于给定的数据集 S=(x⁽ⁱ⁾, y⁽ⁱ⁾);i=1,2,…,m,我们定义 γ 是数据集中函数间距最小的,即:γ = min_{i=1,2,\cdots ,m} γ⁽ⁱ⁾

讨论到这里,对于一组训练集,我们要找的就是距离超平面最近的点的最大边距。因为这样确信度是最大的。所以现在的问题就是:

max_{\gamma ,w,b} γ

s.t.\left\{\begin{matrix} y^{(i)}(w^Tx^{(i)}+b)\geqslant \gamma ,i=1,2,\cdots ,m & & \\ \left \| w \right \| =1& & \end{matrix}\right.

这个问题就是说,应最大化这个边距 γ,而且必须保证每个训练集得到的边距都要大于或等于这个边距 γ。‖w‖=1 保证函数边距与几何边距是一样的。但问题是‖w‖=1 很难理解,所以根据函数边距与几何边距之间的关系,将问题变换为:max_{\gamma ,w,b} γ̂/‖w‖s.t. y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b) ≥ γ̂, i=1,2,…,m

此处,我们的目标是最大化 γ̂/‖w‖,限制条件为所有的样本的函数边距要大于或等于 γ̂。

前面说过,对于函数间距来说,等比例缩放 w 和 b 不会改变 g (wᵀx + b) 的值。因此,可以令 γ̂=1,因为无论 γ̂的值是多少,都可以通过缩放 w 和 b 来使得 γ̂的值变为 1,所以可表示为 γ̂/‖w‖=1/‖w‖(注意,等号左右两边的 w 是不一样的)。

(四)间隔最大化

其实对于上面的问题,如果那些式子都除以 γ̂,即变成:

max_{\gamma ,w,b} (γ̂/γ̂) / (‖w‖/γ̂)

s.t. y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b)/γ̂ ≥ γ̂/γ̂, i=1,2,…,m

也就是,max_{\gamma ,w,b} 1/(‖w‖/γ̂)s.t. y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b)/γ̂ ≥ 1, i=1,2,…,m

然后令 w=w/γ̂,b=b/γ̂,问题就变成与下面的一样了。所以其实只是做了一个变量替换。

max_{\gamma ,w,b} 1/‖w‖

s.t. y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b) ≥ 1, i=1,2,…,m

而最大化 1/‖w‖相当于最小化‖w‖²,所以问题变成:

min_{\gamma ,w,b} (1/2)‖w‖²

s.t. y⁽ⁱ⁾(wᵀx⁽ⁱ⁾ + b) ≥ 1, i=1,2,…,m

现在,我们把问题转换成一个可以有效求解的问题了。上面的优化问题就是一个典型的二次凸优化问题,这种优化问题可以使用 QP(Quadratic Programming)来求解。

SVC 主要用于处理二元分类问题,如果需要处理的是多分类问题,如手写字识别,即识别是 {0,1,…,9} 中的数字,此时,需要使用能够处理多个分类问题的算法。

(五)SVC 识别手写体数字实例

前面介绍了 SVC 的原理及其几种分类间隔问题,下面通过一个案例来演示 SVC 识别手写体数字识别问题。

from sklearn import datasets, svm
import matplotlib.pyplot as plt

""" 识别手写体数字 """
svc = svm.SVC(gamma=0.001, C=100.)
digits = datasets.load_digits()  # 导入 Digits 数据集
# print(digits.DESCR)             # 查看数据集的说明信息

def plts():
    '''显示要识别的数字图片'''
    plt.subplot(321)
    plt.imshow(digits.images[1791], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.subplot(322)
    plt.imshow(digits.images[1792], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.subplot(323)
    plt.imshow(digits.images[1793], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.subplot(324)
    plt.imshow(digits.images[1794], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.subplot(325)
    plt.imshow(digits.images[1795], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.subplot(326)
    plt.imshow(digits.images[1796], cmap=plt.cm.gray_r, interpolation='nearest')
    plt.savefig('digital recognition.png')
    plt.show()

def svms():
    '''学习并返回识别结果'''
    svc.fit(digits.data[:1791], digits.target[:1791])  # 训练
    res = svc.predict(digits.data[1791:1797])          # 识别
    return list(res)

if __name__ == '__main__':
    result = svms()
    duibi = digits.target[1791:1797]
    print(f'识别的数字: {result}\n实际的结果: {list(duibi)}')
    plts()

由输出结果可看出,利用 SVC 实现手写体数字的识别非常简单,也很实用。

四、总结

本文探讨了手写体数字识别的两种主要方法:卷积神经网络(CNN)和支持向量机(SVM)。手写体数字识别作为图像处理和模式识别领域的重要应用,具有广泛的实际价值。CNN通过卷积层、池化层等结构自动提取特征,在MNIST数据集上表现出色;SVM则通过寻找最优分类超平面实现有效分类。实验结果表明,两种方法在手写体数字识别任务中均能取得良好效果,其中CNN因其自动特征提取能力在图像识别领域更具优势。研究为手写体数字识别提供了可行的技术方案,对实际应用具有重要参考价值。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐