深度学习-->卷积神经网络基础

二维卷积层:

 

from mxnet import autograd, nd
from mxnet.gluon import nn


# 定义函数corr2d,用于实现二维卷积操作

def corr2d(x, k):
    # 获取卷积核的高度和宽度
    h, w = k.shape
    # 初始化输出y,其形状为(x.shape[0] - h + 1, x.shape[1] - w + 1)
    y = nd.zeros((x.shape[0] - h + 1, x.shape[1] - w + 1))
    # 通过两个for循环计算二维卷积操作
    for i in range(y.shape[0]):
        for j in range(y.shape[1]):
            y[i, j] = (x[i:i + h, j:j + w] * k).sum()
    return y


# 定义输入张量x和卷积核k,并使用corr2d函数计算二维卷积结果
x = nd.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])  # 输入张量x
k = nd.array([[0, 1], [2, 3]])  # 卷积核k
print(corr2d(x, k))  # 输出二维卷积结果


# 二维卷积层的定义

class Conv2D(nn.Block):
    def __init__(self, kernel_size, **kwargs):
        super(Conv2D, self).__init__(**kwargs)
        # 定义卷积层的权重参数self.weight和偏置参数self.bias
        self.weight = self.params.get('weight', shape=kernel_size)
        self.bias = self.params.get('bias', shape=(1,))

    def forward(self, x):
        # 在forward方法中使用corr2d函数进行二维卷积操作,并返回结果
        return corr2d(x, self.weight.data() + self.bias.data())


# 定义输入张量x、卷积核k,计算二维卷积结果y
x = nd.ones((6, 8))  # 输入张量x
x[:, 2:6] = 0
print(x)
k = nd.array([[1, -1]])  # 卷积核k
print(k)
y = corr2d(x, k)  # 输出二维卷积结果
print(y)

# 通过数据学习核数组

# 定义一个含有1个输出通道、核数组形状为(1, 2)的二维卷积层
conv2d = nn.Conv2D(1, kernel_size=(1, 2))
conv2d.initialize()

# 将输入x和输出y进行形状变换,使其适应二维卷积层的输入要求
x = x.reshape((1, 1, 6, 8))  # 改变输入张量x的形状
y = y.reshape((1, 1, 6, 7))  # 改变输出张量y的形状

# 进行10次迭代的训练
for i in range(10):
    with autograd.record():
        y_hat = conv2d(x)  # 获取卷积层的输出结果
        loss = (y_hat - y) ** 2  # 计算损失函数,即预测值与真实值的差的平方

    loss.backward()  # 自动求导,计算损失函数关于参数的梯度

    conv2d.weight.data()[:] -= 3e-2 * conv2d.weight.grad()  # 更新卷积层的权重参数

    if (i + 1) % 2 == 0:
        print('batch %d, loss %.3f' % (i + 1, loss.sum().asscalar()))

# 打印学习得到的核数组
print(conv2d.weight.data().reshape((1, 2)))

 

填充与步幅:

# 填充和步幅

from mxnet import nd
from mxnet.gluon import nn


# 填充
# (nh−kh+ph+1)×(nw−kw+pw+1),
# 也就是说,输出的高和宽会分别增加ph和pw。
#
# 在很多情况下,我们会设置ph=kh−1和pw=kw−1来使输入和输出具有相同的高和宽。
# 这样会方便在构造网络时推测每个层的输出形状。假设这里kh是奇数,我们会在高的两侧分别填充ph/2行。
# 如果kh是偶数,一种可能是在输入的顶端一侧填充⌈ph/2⌉行,而在底端一侧填充⌊ph/2⌋行。在宽的两侧填充同理。

def comp_conv2d(conv2d, x):
    conv2d.initialize()  # 初始化
    x = x.reshape((1, 1) + x.shape)  # Conv2D函数是思维的卷积,所以要先加上两个维度,返回时只返回后两个维度就可以
    y = conv2d(x)
    return y.reshape(y.shape[2:])  # 返回后两个维度


conv2d = nn.Conv2D(1, kernel_size=3, padding=1)  # padding 代表填充数,如果填充1个也就上下填充1,总数是2

x = nd.random.uniform(shape=(8, 8))
print(comp_conv2d(conv2d, x).shape)

conv2d = nn.Conv2D(1, kernel_size=(5, 3), padding=(2, 1))
print(comp_conv2d(conv2d, x).shape)

# 步幅


# 一般来说,当高上步幅为sh,宽上步幅为sw时,输出形状为
#
# ⌊(nh−kh+ph+sh)/sh⌋×⌊(nw−kw+pw+sw)/sw⌋.
# 如果设置ph=kh−1和pw=kw−1,那么输出形状将简化为⌊(nh+sh−1)/sh⌋×⌊(nw+sw−1)/sw⌋。
# 更进一步,如果输入的高和宽能分别被高和宽上的步幅整除,那么输出形状将是(nh/sh)×(nw/sw)。


conv2d = nn.Conv2D(1, kernel_size=3, padding=1, strides=2)  # strides是步幅,这里指高和宽全是2
print(comp_conv2d(conv2d, x).shape)

conv2d = nn.Conv2D(1, kernel_size=3, padding=(0, 1), strides=(3, 4))
print(comp_conv2d(conv2d, x).shape)

 

多输入通道和多输出通道:

# 多输入通道和多输出通道


import d2lzh as d2l
from mxnet import nd


# 多输入通道和多输出通道

def corr2d_multi_in(X, K):
    # 首先沿着X和K的第0维(通道维)遍历。然后使用*将结果列表变成add_n函数的位置参数
    # (positional argument)来进行相加
    return nd.add_n(*[d2l.corr2d(x, k) for x, k in zip(X, K)])


X = nd.array([[[0, 1, 2], [3, 4, 5], [6, 7, 8]],
              [[1, 2, 3], [4, 5, 6], [7, 8, 9]]])
K = nd.array([[[0, 1], [2, 3]], [[1, 2], [3, 4]]])

y = corr2d_multi_in(X, K)
print(y)


# 多输出通道
# 当输入通道有多个时,因为我们对各个通道的结果做了累加,所以不论输入通道数是多少,输出通道数总是为1。
# 设卷积核输入通道数和输出通道数分别为 ci和 co
# 高和宽分别为 kh 和 kw
# 如果希望得到含多个通道的输出,我们可以为每个输出通道分别创建形状为 ci×kh×kw的核数组
# 将它们在输出通道维上连结,卷积核的形状即 co×ci×kh×kw
# 在做互相关运算时,每个输出通道上的结果由卷积核在该输出通道上的核数组与整个输入数组计算而来。
def corr2d_multi_in_out(X, K):
    # 对K的第0维遍历,每次同输入X做互相关计算。所有结果使用stack函数合并在一起
    return nd.stack(*[corr2d_multi_in(X, k) for k in K])


K = nd.stack(K, K + 1, K + 2)
print(K.shape)
print(corr2d_multi_in_out(X, K))

# 1×1卷积层:

# 使用全连接层中的矩阵乘法来实现1×1卷积


def corr2d_multi_in_out_1x1(x, k):
    # 获取输入特征图的通道数,高度和宽度
    c_i, h, w = x.shape
    # 获取卷积核的输出通道数
    c_o = k.shape[0]

    # 将输入特征图转换为二维张量,每个位置 (i, j) 处的像素值作为一个长度为 c_i 的向量
    x = x.reshape((c_i, h * w))
    # 将卷积核转换为二维张量,用于后续全连接层的矩阵乘法
    k = k.reshape((c_o, c_i))

    # 进行全连接层的矩阵乘法,得到形状为 (c_o, h * w) 的输出
    y = nd.dot(k, x)
    # 将输出特征图转换回三维张量,形状为 (c_o, h, w)
    return y.reshape((c_o, h, w))


x = nd.random.uniform(shape=(3, 3, 3))  # 生成一个 3x3x3 的输入特征图
k = nd.random.uniform(shape=(2, 3, 1, 1))  # 生成一个 2x3x1x1 的卷积核

# 使用 corr2d_multi_in_out_1x1 函数进行 1x1 卷积
y1 = corr2d_multi_in_out_1x1(x, k)

# 使用 corr2d_multi_in_out 函数进行普通卷积(包括填充和步幅的卷积)
y2 = corr2d_multi_in_out(x, k)

# 检查两种卷积方法的输出是否非常接近(误差小于 1e-6)
print((y1 - y2).norm().asscalar() < 1e-6)

 

池化层:

# 池化层


from mxnet import nd
from mxnet.gluon import nn


def pool2d(x, pool_size, mode='max'):
    p_h, p_w = pool_size
    y = nd.zeros((x.shape[0] - p_h + 1, x.shape[1] - p_w + 1))
    for i in range(y.shape[0]):
        for j in range(y.shape[1]):
            if mode == 'max':
                y[i, j] = x[i:i + p_h, j:j + p_w].max()
            elif mode == 'avg':
                y[i, j] = x[i:i + p_h, j:j + p_w].mean()
    return y


x = nd.array([[0, 1, 2], [3, 4, 5], [6, 7, 8]])
y = pool2d(x, (2, 2))
print(y)

y = pool2d(x, (2, 2), 'avg')
print(y)

# 填充和步幅


# 同卷积层一样,池化层也可以在输入的高和宽两侧的填充并调整窗口的移动步幅来改变输出形状。
# 池化层填充和步幅与卷积层填充和步幅的工作机制一样。我们将通过nn模块里的二维最大池化层MaxPool2D来演示池化层填充和步幅的工作机制。
# 我们先构造一个形状为(1, 1, 4, 4)的输入数据,前两个维度分别是批量和通道。

x = nd.arange(16).reshape((1, 1, 4, 4))
print(x)

# 默认情况下,MaxPool2D实例里步幅和池化窗口形状相同。下面使用形状为(3, 3)的池化窗口,默认获得形状为(3, 3)的步幅

pool2d = nn.MaxPool2D(3)
print(pool2d(x))  # 因为池化层没有模型参数,所以不需要调用参数初始化函数

pool2d = nn.MaxPool2D(3, padding=1, strides=2)
print(pool2d(x))

pool2d = nn.MaxPool2D((2, 3), padding=(1, 2), strides=(2, 3))
print(pool2d(x))

# 多通道
x = nd.concat(x, x + 1, dim=1)
print(x)

pool2d = nn.MaxPool2D(3, padding=1, strides=2)
print(pool2d(x))

 

posted @ 2023-08-01 16:56  o-Sakurajimamai-o  阅读(76)  评论(0)    收藏  举报
-- --