S'S ALGORITHM

卷积神经网络(CNN)


卷积神经网络(Convolutional Neural Network,简称CNN)是计算机视觉领域的一项强大工具。这个魔法之眼不仅让机器能够识别图像,更为我们打开了数据迷雾中的一扇大门。我想从概念、范畴、原理、应用领域、相关库、现有问题甚至哲学启发等方面展开叙述。

概念与范畴

卷积神经网络是一种受到人类视觉系统启发的深度学习模型。它的基本思想是通过层层堆叠的卷积层和池化层提取图像中的特征,最终实现对图像的高效分类与识别。

在CNN的范畴中,我们可以看到卷积层、池化层、全连接层等组件,它们相互协作,形成了一个层次分明、信息逐渐抽象的网络结构。这几个概念,用我的理解解释一下的话。卷积层,相当于一个信息提取的过程,比如图书馆检索标签,或者你在烹饪过程中列出重要步骤。池化层的目的是精炼特征,比如你绘画给目标物描边。全联接层就像是将所有这些整合到一起。

(大部分使用的是Pytorch进行构架学习,这里写一个tensorflow,好久没碰了需要复习一下)

TensorFlow提供了丰富的API,以下就是卷积层、池化层和全连接层在TensorFlow中的基本实现代码:

  1. 卷积层(Convolutional Layer):

在Pytorch中的维度顺序是(channel,height,weight),在Tensorflow中是(height,weight,channel)。

import tensorflow as tf
# 定义一个卷积层
conv_layer = tf.keras.layers.Conv2D(filters=64, kernel_size=(3, 3), activation='relu', input_shape=(28, 28, 1))
# 假设输入是一个28x28的灰度图像,创建一个输入张量
input_tensor = tf.keras.Input(shape=(28, 28, 1))
# 通过卷积层处理输入
output_tensor = conv_layer(input_tensor)
# 创建模型
model = tf.keras.Model(inputs=input_tensor, outputs=output_tensor)

总之就是接口非常精炼。

  1. 池化层(Pooling Layer):
# 定义一个最大池化层
max_pool_layer = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))
# 通过最大池化层处理卷积层的输出
pooled_output = max_pool_layer(output_tensor)

将卷积层的输出通过最大池化层进行处理,以减小数据维度。

  1. 全连接层(Fully Connected Layer):
# 展平池化层的输出,准备输入全连接层
flatten_layer = tf.keras.layers.Flatten()
flattened_output = flatten_layer(pooled_output)
# 定义一个全连接层
dense_layer = tf.keras.layers.Dense(units=128, activation='relu')
# 通过全连接层处理展平后的数据
final_output = dense_layer(flattened_output)

这个例子使用Flatten层将池化层的输出展平,然后定义了一个全连接层。通过全连接层就可以将展平后的数据连接起来,并通过激活函数(这里使用ReLU)进行处理。在Pytorch中对于激活函数有自己的定义层nn.ReLU()但是在tf中直接在层的参数中定义即可。

卷积神经网络的核心原理在于卷积操作。通过卷积核对输入图像进行滤波,突出图像的特定特征,如边缘、纹理等。这种局部连接的方式不仅减少了参数数量,更有效地捕捉了图像的本质特征。

池化操作则有助于减小数据规模,降低计算复杂度,同时保留关键信息。这使得CNN在处理大规模图像数据时能够更加高效地进行特征提取。

应用领域

卷积神经网络的应用领域广泛,不仅局限于计算机视觉。在图像识别、目标检测、人脸识别等方面,CNN都表现出色。此外,它还在自然语言处理、医学图像分析、游戏开发等领域崭露头角,展现了强大的潜力。

对,你没看错不只是图像,还可以用在自然语言处理等方面,在我学习nlp的时候我也很震惊,因为要知道一行句子变成一个向量后,是1维度的,那你用1D池化层就可以提取特征了。

TensorFlow、PyTorch和Keras这几个库都提供了丰富的工具和接口,使得构建、训练和部署CNN变得更加便捷。

尽管卷积神经网络在众多领域取得显著成就,但仍然存在一些亟待解决的问题。例如,对抗样本攻击、模型的可解释性以及对小样本数据的训练等方面,仍然是研究者们面临的挑战。

卷积神经网络的发展也给我们带来一些哲学上的思考。在信息爆炸的时代,我们的大脑是否也需要一种“卷积”式的思维方式,即通过层层过滤,提取重要的信息,使得我们更专注于核心的认知和判断?

手写数字MINIST数据集的示例学习

import tensorflow as tf

class MNISTModel(object):
    # Model Initialization
    def __init__(self, input_dim, output_size):
        self.input_dim = input_dim
        self.output_size = output_size
    
    # CNN Layers
    def model_layers(self, inputs, is_training):
        reshaped_inputs = tf.reshape(
            inputs, [-1, self.input_dim, self.input_dim, 1])
        # Convolutional Layer #1
        conv1 = tf.keras.layers.Conv2D(
        filters=32,
        kernel_size=[5, 5],
        padding='same',
        activation='relu',
        name='conv1')(reshaped_inputs)

        # Pooling Layer #1
        pool1 = tf.keras.layers.MaxPool2D(
        pool_size=[2, 2],
        strides=2,
        name='pool1')(conv1)
        
        # Convolutional Layer #2
        conv2 = tf.keras.layers.Conv2D(
        filters=64,
        kernel_size=[5, 5],
        padding='same',
        activation='relu',
        name='conv2')(pool1)

        # Pooling Layer #2
        pool2 = tf.keras.layers.MaxPool2D(
        pool_size=[2, 2],
        strides=2,
        name='pool2')(conv2)

        # flattened dense layer
        hwc = pool2.shape.as_list()[1:]
        flattened_size = hwc[0] * hwc[1] * hwc[2]
        pool2_flat = tf.reshape(pool2, [-1, flattened_size])
        dense = tf.keras.layers.Dense(
        units=1024,
        activation='relu',
        name='dense'
        )(pool2_flat)

        # Dropout apply
        dropout = tf.keras.layers.Dropout(rate=0.4)(dense, training=is_training)

        # Logits layer
        logits = tf.keras.layers.Dense(
        self.output_size,
        name='logits')(dropout)
        
        return logits

结果处理

def run_model_setup(self, inputs, labels, is_training):
    # 从模型层获取logits
    logits = self.model_layers(inputs, is_training)

    # 使用softmax激活将logits转换为概率
    self.probs = tf.nn.softmax(logits, name='probs')
    
    # 将概率四舍五入并获取预测的类别标签
    self.predictions = tf.math.argmax(
        self.probs, axis=-1, name='predictions')
    
    # 从独热编码的标签中获取真实的类别标签
    class_labels = tf.math.argmax(labels, axis=-1)
    
    # 检查哪些预测与真实类别标签匹配
    is_correct = tf.math.equal(
        self.predictions, class_labels)
    
    # 将布尔值转换为浮点数以计算准确率
    is_correct_float = tf.cast(
        is_correct,
        tf.float32)
    
    # 计算正确预测比例(准确率)
    self.accuracy = tf.math.reduce_mean(
        is_correct_float)
    
    # 如果is_training为True,则训练模型
    if is_training:
        # 将独热编码的标签转换为浮点数
        labels_float = tf.cast(
            labels, tf.float32)
        
        # 使用交叉熵计算损失
        cross_entropy = tf.nn.softmax_cross_entropy_with_logits_v2(
            labels=labels_float,
            logits=logits)
        
        # 计算交叉熵的均值作为总体损失
        self.loss = tf.math.reduce_mean(
            cross_entropy)
        
        # 使用Adam优化器最小化损失并更新模型参数
        adam = tf.compat.v1.train.AdamOptimizer()
        self.train_op = adam.minimize(
            self.loss, global_step=self.global_step)

推理函数

def inference(image_path):
    # 使用FastGFile打开已优化的图形文件
    with tf.compat.v1.gfile.FastGFile(output_optimized_graph_name, 'rb') as f:
        # 创建一个新的GraphDef对象并解析已优化的图形文件
        graph_def = tf.compat.v1.GraphDef()
        graph_def.ParseFromString(f.read())

    # 创建一个新的图形对象
    G = tf.Graph()
    with tf.compat.v1.Session(graph=G) as sess:
        # 将已优化的图形导入到新的图形对象中
        # 需要额外的 :0 来将操作转换为张量
        # name='' 去除 import/ 前缀
        _ = tf.import_graph_def(graph_def, name='')

        # 通过名称获取输入张量、预测张量和概率张量
        inputs = sess.graph.get_tensor_by_name('inputs:0')
        predictions_tensor = sess.graph.get_tensor_by_name('predictions:0')
        probs_tensor = sess.graph.get_tensor_by_name('probs:0')

        # 准备图像数据
        image_data = imageprepare(image_path)

        # 运行模型,获取预测和概率
        predictions, probs = sess.run((predictions_tensor, probs_tensor), feed_dict={inputs: [image_data]})

        # 打印预测结果
        print("你画的是 " + str(predictions[0]) + "!")
        # 可选:打印概率值
        # print(probs)

关于卷积核

fileter的大小size问题,单独拿出来记录一下。(tensorflow的dimension顺序世界观)

比如原本图像size(5,5,3),注意,channel不是三原色,第一层如果是RGB图像的话是3,后面的通道数量是卷积核的数量。

然后进行padding后图像size(7,7,3)

卷积核也就是过滤器的大小,这里假设是size(3,3,3),前面两个3是核的大小,后面一个3比较有讲究,必须和原本图像的channel数量一致。卷积计算(每个channel的卷积特征提取相加后,加上一个bais的偏置计算)后,得到一张特征图,也就是说:一个过滤器,可以得到一张特征图

最终得到的特征图,可以想象成是一沓图纸,图纸的数量,就是过滤器的数量。所以比如上面的例子,如果步长是2,过滤器数量是2的情况下,得到的结果输出,也就是特征图的size就是(3,3,2)这个2,就是过滤器的数量。

池化的过程中的过滤器会更加大幅度的提取特征,比如最大池化,提取的就是最大特征,会对维度进行大幅缩减。