卷积神经网络(Convolutional Neural Network,简称CNN)是计算机视觉领域的一项强大工具。这个魔法之眼不仅让机器能够识别图像,更为我们打开了数据迷雾中的一扇大门。我想从概念、范畴、原理、应用领域、相关库、现有问题甚至哲学启发等方面展开叙述。
卷积神经网络是一种受到人类视觉系统启发的深度学习模型。它的基本思想是通过层层堆叠的卷积层和池化层提取图像中的特征,最终实现对图像的高效分类与识别。
在CNN的范畴中,我们可以看到卷积层、池化层、全连接层等组件,它们相互协作,形成了一个层次分明、信息逐渐抽象的网络结构。这几个概念,用我的理解解释一下的话。卷积层,相当于一个信息提取的过程,比如图书馆检索标签,或者你在烹饪过程中列出重要步骤。池化层的目的是精炼特征,比如你绘画给目标物描边。全联接层就像是将所有这些整合到一起。
(大部分使用的是Pytorch进行构架学习,这里写一个tensorflow,好久没碰了需要复习一下)
TensorFlow提供了丰富的API,以下就是卷积层、池化层和全连接层在TensorFlow中的基本实现代码:
在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)
总之就是接口非常精炼。
# 定义一个最大池化层
max_pool_layer = tf.keras.layers.MaxPooling2D(pool_size=(2, 2))
# 通过最大池化层处理卷积层的输出
pooled_output = max_pool_layer(output_tensor)
将卷积层的输出通过最大池化层进行处理,以减小数据维度。
# 展平池化层的输出,准备输入全连接层
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变得更加便捷。
尽管卷积神经网络在众多领域取得显著成就,但仍然存在一些亟待解决的问题。例如,对抗样本攻击、模型的可解释性以及对小样本数据的训练等方面,仍然是研究者们面临的挑战。
卷积神经网络的发展也给我们带来一些哲学上的思考。在信息爆炸的时代,我们的大脑是否也需要一种“卷积”式的思维方式,即通过层层过滤,提取重要的信息,使得我们更专注于核心的认知和判断?
logits:简单解释就是还未经过正规化的预测可能性结果,由于没有正规化你没法知道模型对结果的自信程度。无法对他们进行比较,所以需要进行处理。
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,就是过滤器的数量。
池化的过程中的过滤器会更加大幅度的提取特征,比如最大池化,提取的就是最大特征,会对维度进行大幅缩减。