卷积神经网络CNN

卷积神经网络的组成

卷积神经网络由一个或多个卷积层、池化层以及全连接层等组成。与其他的深度学习结构相比,卷积神经网络在图像等方面能够给出更好的结果。这一模型也可以使用反向传播算法(back-propagation)进行训练。相比其他浅层神经网络和深度神经网络,卷积神经网络需要考量的参数更少。
LeNet5作为经典的卷积神经网络之一,推动了深度学习的发展,先看下LeNet5的网络结构:
经典的卷积结构

该网络中主要包含了两个结构:

  • 卷积层(convolution layer):通过在原始图像上使用过滤器(也叫卷积核)平移来提取特征后的结果就是特征图(feature map)。
  • 池化层(pooling layer):又叫子采样层。通过特征后稀疏参数来减少学习的参数,降低网络的复杂度。池化的方法有最大池化和平均池化。
  • 全连接层:前面的卷积层和池化层相当于做特征工程,全连接层则相当于做特征加权,起到“分类器”的作用。

卷积层

卷积的运算过程

以一张单通道图片的大小为 5 ∗ 5 5∗5 55,以步长为1、卷积核大小为 3 ∗ 3 3∗3 33进行运算,经过卷积核平移以后得到 3 ∗ 3 3*3 33的运算结果。

在这里插入图片描述
通过上图运算可以发现,进行卷积之后的图片变小了。假设N为图片大小,F为卷积核大小,那么输出大小公式为:
( N − F + 1 ) ∗ ( N − F + 1 ) = ( 5 − 3 + 1 ) ∗ ( 5 − 3 + 1 ) = 3 ∗ 3 \left(N-F+1\right)*\left(N-F+1\right)⁼\left(5-3+1\right)*\left(5-3+1\right)=3*3 (NF+1)(NF+1)=(53+1)(53+1)=33

如果我们换一个卷积核大小或者加入很多层卷积之后,经过内积计算后图像可能最后就变成了1 X 1 大小,这不是我们希望看到的结果。并且对于原始图片当中的边缘像素来说,只计算了一遍,而中间的像素会有很多次卷积核与之计算,这样导致对边缘信息的丢失。那么如何解决这样的问题?

零填充

在图片像素的最外层加上P层0值,因为0在权重乘积和运算中对最终结果不造成影响,也就避免了图片增加了额外的干扰信息。由于移动步长不一定能够整除整张图的像素宽度,导致图片的边缘信息丢,这就是零填充的动机所在。

在这里插入图片描述
在上图中,假设还是跟之前一样图片大小为 5 ∗ 5 5*5 55、卷积核大小为 3 ∗ 3 3*3 33、步长为1,填充1层,那么可以使用该公式计算输出图片大小: N + 2 P − F + 1 = 5 + 2 ∗ 1 − 3 + 1 = 5 N+2P-F+1=5+2*1-3+1=5 N+2PF+1=5+213+1=5;这只是填充一层的结果,实际上可以填充更过层,如果填充层数为2,则输出图片的大小比原图还要大,那么对于零填充层数该怎么选择?在CNN中,有两种填充方式:

  • VALID填充:不越过边缘取样,取样的面积小于输入的图像的像素宽度。最终输出大小为: ( N − F + 1 ) ∗ ( N − F + 1 ) (N−F+1)∗(N−F+1) NF+1)(NF+1)
  • SAME填充:越过边缘取样,取样的面积和输入图像的宽度一致。输出大小还是原来图片大小: ( N + 2 P − F + 1 ) ∗ ( N + 2 P − F + 1 ) (N+2P−F+1)∗(N+2P−F+1) (N+2PF+1)(N+2PF+1)

那么问题又来了,在以上公式中,如果卷积核F的选择不是奇数而是偶数,那么计算的结果则不为整数,导致填充不均匀,所以,一般都是选择奇数维度的卷积核大小。

步长

在上面的例子中,我们都是选择1为步长进行计算的结果,当步长设置为2时,结果又如何?
在这里插入图片描述
如果按照之前的输出公式计算,那么结果为: N + 2 P − F + 1 = 6 + 2 ∗ 0 − 3 + 1 = 4 N+2P−F+1=6+2*0−3+1=4 N+2PF+1=6+203+1=4每次移动两个像素才得到一个计算的结果,所以公式变为:
( N + 2 P − F ) S + 1 = 1.5 + 1 = 2.5 \frac {(N+2P-F)}{S}+1 = 1.5+1 = 2.5 S(N+2PF)+1=1.5+1=2.5结果不为整数时做法是向下取整,为2。所以最终的公式为:
( ( N + 2 P − F ) S + 1 ) ∗ ( ( N + 2 P − F ) S + 1 ) \left(\frac {(N+2P-F)}{S}+1 \right)∗\left(\frac {(N+2P-F)}{S}+1 \right) (S(N+2PF)+1)(S(N+2PF)+1)

多通道图片卷积

当输入有多个通道(channel)时(例如图片可以有 RGB 三个通道),卷积核需要拥有相同的channel数,每个卷积核 channel 与输入层的对应 channel 进行卷积,将每个 channel 的卷积结果按位相加得到最终的feature map。卷积核数量可以有多个,多个卷积核经过内积后输出多张 feature map。对应的,feature map的通道数和卷积核的数量一致。
在这里插入图片描述

池化层

池化层主要对卷积层学习到的特征进行子采样(subsampling)处理,通过去掉feature map中不重要的样本,进一步减少参数数量,缩减模型大小,提高模型计算速度和feature map的鲁棒性,防止过拟合。
主要有两种:

  • 最大池化(max pooling):取卷积核内的最大值作为输出。是最常用的一种池化方法。

在这里插入图片描述

  • 平均池化(avg pooling):取卷积核内的所有值的均值作为输出。
    在这里插入图片描述

全连接层

卷积层+激活层+池化层可以看成是CNN的特征学习/特征提取层,而学习到的feature map最终应用于模型任务(分类、回归):

  • 先对所有 feature map 进行扁平化(flatten, 即 reshape 成 1 x N 向量)
  • 再接一个或多个全连接层,进行模型学习

在这里插入图片描述

代码实现

# 定义一个初始化权重的函数
def weight_variables(shape):
    w = tf.Variable(tf.random_normal(shape=shape, mean=0.0, stddev=1.0))
    return w


# 定义一个初始化偏置的函数
def bias_variables(shape):
    b = tf.Variable(tf.constant(0.0, shape=shape))
    return b


def conv_fc():
    """自定义卷积模型"""

    with tf.variable_scope("plshd"):
        # 准备数据的占位符
        x = tf.placeholder(tf.float32, [None, 784])
        y_true = tf.placeholder(tf.int32, [None, 10])
    # 随机初始化权重、偏置
    with tf.variable_scope('first_layer_conv'):
        # 5x5: filter的大小 32: filter数量
        # w = tf.Variable(tf.random_normal(shape=[5, 5, 1, 32], mean=0.0, stddev=1.0))
        # b = tf.Variable(tf.constant(0.0, shape=[32]))

        # w参数设置: 过滤器大小(5x5)*图片通道数*过滤器数量
        w = weight_variables([5, 5, 1, 32])
        b = bias_variables([32])
        # 卷积
        # 改变x的形状 会生成一个新的张量
        reshaped_x = tf.reshape(x, [-1, 28, 28, 1])
        conv_ret = tf.nn.conv2d(reshaped_x, filters=w, strides=[1, 1, 1, 1], padding='SAME') + b
        # 激活
        x_relu1 = tf.nn.relu(conv_ret)
        # 池化
        x_pooling1 = tf.nn.max_pool(x_relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME")

    with tf.variable_scope("second_layer_conv"):
        # 可以定义第二层卷积
        # 随机初始化权重w和偏置b
        # w_conv2 = tf.Variable(tf.random_normal(shape=[5, 5, 32, 64], mean=0.0, stddev=1.0))
        # b_conv2 = tf.Variable(tf.constant(0.0, shape=[64]))
        # 32:上层池化结果
        w_conv2 = weight_variables([5, 5, 32, 64])
        b_conv2 = bias_variables([64])
        # 卷积
        conv2_ret = tf.nn.conv2d(x_pooling1, w_conv2, strides=[1, 1, 1, 1], padding='SAME') + b_conv2
        # 激活
        x_relu2 = tf.nn.relu(conv2_ret)
        # 池化
        x_pooling2 = tf.nn.max_pool(x_relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding="SAME") + b_conv2

        # 全连接层
        with tf.variable_scope('fc'):
            # 随机初始化一个权重w和偏置b
            # w_fc = tf.Variable(tf.random_normal(shape=[7*7*64, 10], mean=0.0, stddev=1.0))
            # b_fc = tf.Variable(tf.constant(0.0, shape=[10]))
            b_fc = bias_variables([10])

            w_fc = weight_variables([7 * 7 * 64, 10])
            # 进行回归运算
            reshaped_x_fc = tf.reshape(x_pooling2, [-1, 7 * 7 * 64])
            y_predict = tf.matmul(reshaped_x_fc, w_fc) + b_fc
        # with tf.Session() as sess:
        #     sess.run(print(x_pooling1))
        return x, y_true, y_predict


def train_conv():
    # 加载数据
    mnist = input_data.read_data_sets(train_dir="./data/MNIST_data", one_hot=True)
    # 定义模型 得到输出
    x, y_true, y_predict = conv_fc()
    # 3、求出所有样本的损失,然后求平均值
    with tf.variable_scope("soft_cross"):
        # 求平均交叉熵损失
        # tf.reduce_mean()
        loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y_true, logits=y_predict))

    # 4、梯度下降求出损失
    with tf.variable_scope("optimizer"):
        train_op = tf.train.GradientDescentOptimizer(0.0001).minimize(loss)

    # 5、计算准确率
    with tf.variable_scope("acc"):
        
        equal_list = tf.equal(tf.argmax(y_true, 1), tf.argmax(y_predict, 1))

        # equal_list  None个样本   [1, 0, 1, 0, 1, 1,..........]
        accuracy = tf.reduce_mean(tf.cast(equal_list, tf.float32))
    # 初始化变量
    init_var = tf.global_variables_initializer()
    with tf.Session() as sess:
        sess.run(init_var)

        for i in range(1000):
            # 取出真实的特征值和目标值进行训练
            mnist_x, mnist_y = mnist.train.next_batch(50)
            sess.run(train_op, feed_dict={x: mnist_x, y_true: mnist_y})
            if i % 100 == 0:
                print("训练第%d步,准确率为:%f" % (i, sess.run(accuracy, feed_dict={x: mnist_x, y_true: mnist_y})))



if __name__ == '__main__':
    train_conv()

tensorflow 2.x

# encoding=utf-8

import numpy as np
import tensorflow as tf
from tensorflow.keras.datasets import mnist
from matplotlib import pyplot as plt

(train_imgs, train_labels), (test_imgs, test_labels) = mnist.load_data(
    path=r'datasets\mnist.npz')
#

# 构建网络
resnet = tf.keras.applications.resnet50.ResNet50(include_top=False, input_shape=(224, 224, 3))
for layers in resnet.layers:
    layers.trainable = False

net = tf.keras.Sequential()
net.add(resnet)
net.add(tf.keras.layers.Flatten())
net.add(tf.keras.layers.Dense(units=10, activation='softmax'))

# 定义损失函数  优化器
optimizer = tf.keras.optimizers.Adam(learning_rate=0.01)


# 定义训练函数
@tf.function
def train_step(x, y):
    with tf.GradientTape() as gt:
        predictions = net(x, y)
        # 计算损失
        # loss = tf.keras.losses.categorical_crossentropy(y, predictions)
        loss = tf.reduce_mean(tf.keras.losses.sparse_categorical_crossentropy(y, predictions, from_logits=True))
    # 反向传播
    grads = gt.gradient(loss, net.trainable_variables)
    # 更新参数
    optimizer.apply_gradients(zip(grads, net.trainable_variables))

    return loss


# 模型训练
def data_loader(batch_size, imgs, labels):
    # 设置网络要求维度 b h w c
    batch_imgs = np.reshape(imgs,
                            (imgs.shape[0], imgs.shape[1], imgs.shape[2], 1))
    # 单通道转三通道
    batch_imgs = np.concatenate((batch_imgs, batch_imgs, batch_imgs), axis=-1)

    def _batch_generator(batch_size, imgs, labels):
        for batch in range(0, imgs.shape[0], batch_size):
            # 设置网络要求输入尺寸
            index = np.random.randint(0, np.shape(imgs)[0], batch + batch_size)
            resized_imgs = tf.image.resize_with_pad(batch_imgs[index], 224, 224)
            # 标签值
            batch_labels = np.array([labels[index]])
            yield (resized_imgs.numpy(), batch_labels)

    return _batch_generator(batch_size, imgs, labels), imgs.shape[0]


# def train(train_data):
#     train_running_loss = 0.0
#     train_running_accuracy = 0.0
#     for train_tensor, train_label in train_data:
#         optimizer.zero_grad()
#         train_predict = net(train_tensor)
#         train_loss = criterion(train_predict, train_label)
#         train_running_loss += train_loss.item()
#         # 反向传播
#         train_loss.backward()
#         # 更新参数
#         optimizer.step()
#         # 计算准去率
#         train_running_accuracy += (train_predict.argmax(1) == train_label).sum().item()
#
#     return train_running_loss, train_running_accuracy


if __name__ == '__main__':
    batch_size = 16
    # batch_train_data = data_loader(batch_size, train_imgs, train_labels)
    # batch_test_data = data_loader(batch_size, test_imgs, test_labels)
    # 画图看看img和label是否对应
    # img = next(batch_train_data)
    # plt.figure(figsize=(10, 10))
    # for i in range(16):
    #     plt.subplot(4, 4, i+1)
    #     plt.xticks([])
    #     plt.yticks([])
    #     plt.grid(False)
    #     plt.imshow(img[0][i], cmap=plt.cm.binary)
    #     plt.xlabel(img[1][i])
    # plt.show()

    # 模型训练

    # criterion = tf.keras.losses.categorical_crossentropy
    epochs = 1
    for epoch in range(epochs):
        batch_train_data, len_imgs = data_loader(batch_size, train_imgs, train_labels)
        for img in batch_train_data:
            loss = train_step(img[0], img[1])
            print(f'epoch: {epoch}  | train_avg_loss: {loss}')

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值