一、自定义卷积核算子

import torch
import torch.nn as nn


class Conv2D(nn.Module):
    def __init__(self, kernel_size, weight_value=None):
        super(Conv2D, self).__init__()
        # 根据传入的kernel_size初始化权重
        if weight_value is None:
            weight_value = torch.randn(kernel_size, kernel_size)  # 随机初始化权重
        self.weight = nn.Parameter(weight_value)

    def forward(self, X):
        """
        输入:
            - X:输入矩阵,shape=[B, M, N],B为样本数量
        输出:
            - output:输出矩阵
        """
        u, v = self.weight.shape  # 获取卷积核长和宽
        output = torch.zeros(X.shape[0], X.shape[1] - u + 1, X.shape[2] - v + 1)  # 计算输出矩阵大小
        for i in range(output.shape[1]):
            for j in range(output.shape[2]):
                # 执行卷积操作
                output[:, i, j] = torch.sum(X[:, i:i + u, j:j + v] * self.weight, dim=[1, 2])
        return output


# 使用示例
torch.manual_seed(100)
inputs = torch.tensor([[[1., 2., 3.], [4., 5., 6.], [7., 8., 9.]]])
kernel_size = 2
conv2d = Conv2D(kernel_size=kernel_size, weight_value=torch.tensor([[0., 1.], [2., 3.]]))
outputs = conv2d(inputs)
print("input: \n{}, \noutput: \n{}".format(inputs, outputs))

二、自定义带步长与填充的卷积核算子

import torch
import torch.nn as nn


class Conv2D(nn.Module):
    def __init__(self, kernel_size, stride, padding, weight_value=None):
        super(Conv2D, self).__init__()
        # 动态创建卷积核并初始化权重
        if weight_value is None:
            weight_value = torch.ones(kernel_size, kernel_size)  # 默认初始化为1.0
        self.weight = nn.Parameter(weight_value)

        # 步长和填充
        self.stride = stride
        self.padding = padding

    def forward(self, X):
        # 零填充
        if self.padding > 0:
            new_X = torch.zeros(X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding)
            new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
        else:
            new_X = X
        # 获取输出张量的维度大小
        u, v = self.weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros(X.shape[0], output_w, output_h)
        # 进行带步长的卷积操作
        for i in range(0, output.shape[1]):
            for j in range(0, output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, i * self.stride:i * self.stride + u, j * self.stride:j * self.stride + v] * self.weight,
                    dim=[1, 2]
                )
        return output


# 输入张量,加上批次维度
inputs = torch.tensor([[1., 2., 3., 4.],
                       [5., 6., 7., 8.],
                       [9., 10., 11., 12.],
                       [13., 14., 15., 16.]]).unsqueeze(0)  # 添加批次维度
# 创建 Conv2D 实例并应用
conv2d = Conv2D(kernel_size=2, padding=0, stride=2, weight_value=torch.tensor([[0., 1.],
                                                                               [2., 3.]]))
outputs = conv2d(inputs)
print("input:\n {}, \noutput: \n{}".format(inputs, outputs))

三、编程实现图像边缘检测

import matplotlib.pyplot as plt
from PIL import Image
import numpy as np
import torch
import torch.nn as nn

# 设置中文字体为黑体
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False


class Conv2D(nn.Module):
    def __init__(self, kernel_size, stride, padding, weight_value=None):
        super(Conv2D, self).__init__()
        # 动态创建卷积核并初始化权重
        if weight_value is None:
            weight_value = torch.ones(kernel_size, kernel_size)  # 默认初始化为1.0
        self.weight = nn.Parameter(weight_value)

        # 步长和填充
        self.stride = stride
        self.padding = padding

    def forward(self, X):
        # 零填充
        if self.padding > 0:
            new_X = torch.zeros(X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding)
            new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
        else:
            new_X = X
        # 获取输出张量的维度大小
        u, v = self.weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros(X.shape[0], output_w, output_h)
        # 进行带步长的卷积操作
        for i in range(0, output.shape[1]):
            for j in range(0, output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, i * self.stride:i * self.stride + u, j * self.stride:j * self.stride + v] * self.weight,
                    dim=[1, 2]
                )
        return output


image = Image.open('1.jpg').resize((256, 256)).convert('L')  # 转换为灰度图像
X = np.array(image, dtype='float32')
X_tensor = torch.from_numpy(X).unsqueeze(0)

conv2d = Conv2D(kernel_size=3, padding=0, stride=1, weight_value=torch.tensor([[-1., -1., -1.],
                                                                               [-1., 8., -1],
                                                                               [-1., -1., -1.]]))
# 进行卷积
Conv_img1 = conv2d(X_tensor)
Conv_img1 = torch.clamp(Conv_img1, 0., 255.)  # 解决锐化图像发灰问题
# 将卷积结果从计算图中分离出来,并转为NumPy数组
Conv_img1 = Conv_img1.detach().numpy().squeeze()
# 可视化原始图像和卷积后的特征图
plt.figure(figsize=(10, 5))
# 显示原始图像
plt.subplot(1, 2, 1)
plt.title("原始图像")
plt.imshow(X, cmap='gray')  # 使用灰度色图显示
# 显示卷积后的特征图
plt.subplot(1, 2, 2)
plt.title("边缘检测滤波")
plt.imshow(Conv_img1, cmap='gray')  # 使用灰度色图显示
plt.show()

这里附上实验中使用的"1.jpg"

四、自定义卷积层算子与汇聚层算子

import torch
import torch.nn as nn


class Pool2D(nn.Module):
    def __init__(self, size=(2, 2), mode='max', stride=1):
        super(Pool2D, self).__init__()
        self.mode = mode
        self.h, self.w = size
        self.stride = stride

    def forward(self, x):
        # 计算输出大小
        output_w = (x.shape[2] - self.w) // self.stride + 1
        output_h = (x.shape[3] - self.h) // self.stride + 1
        output = torch.zeros([x.shape[0], x.shape[1], output_w, output_h])

        # 进行池化操作
        for i in range(output.shape[2]):
            for j in range(output.shape[3]):
                # 最大池化
                if self.mode == 'max':
                    window = x[:, :, self.stride * i:self.stride * i + self.h, self.stride * j:self.stride * j + self.w]
                    # 最大池化沿着高度和宽度
                    max_values, _ = torch.max(window, dim=2)  # 沿高度方向
                    max_values, _ = torch.max(max_values, dim=2)  # 沿宽度方向
                    output[:, :, i, j] = max_values

                # 平均池化
                elif self.mode == 'avg':
                    window = x[:, :, self.stride * i:self.stride * i + self.h, self.stride * j:self.stride * j + self.w]
                    # 平均池化沿着高度和宽度
                    avg_values = torch.mean(window, dim=[2, 3])
                    output[:, :, i, j] = avg_values

        return output


# 输入张量:[batch_size, in_channels, height, width]
inputs = torch.tensor([[[[1., 2., 3., 4.],
                         [5., 6., 7., 8.],
                         [9., 10., 11., 12.],
                         [13., 14., 15., 16.]]]], dtype=torch.float32)

# 创建池化层并计算输出
pool2d = Pool2D(stride=2)
outputs = pool2d(inputs)
print("input shape:", inputs.shape)
print("output shape:", outputs.shape)

# 比较与PyTorch原生MaxPool2d运算结果
maxpool2d_pytorch = nn.MaxPool2d(kernel_size=(2, 2), stride=2)
outputs_pytorch_max = maxpool2d_pytorch(inputs)
# 自定义MaxPool2D运算结果
print('Custom MaxPool2D outputs:', outputs)
# PyTorch MaxPool2d API运算结果
print('PyTorch MaxPool2d outputs:', outputs_pytorch_max)

# 比较与PyTorch原生AvgPool2d运算结果
avgpool2d_pytorch = nn.AvgPool2d(kernel_size=(2, 2), stride=2)
outputs_pytorch_avg = avgpool2d_pytorch(inputs)
# 自定义AvgPool2D运算结果
pool2d_avg = Pool2D(mode='avg', stride=2)
outputs_avg = pool2d_avg(inputs)
# 自定义AvgPool2D运算结果
print('Custom AvgPool2D outputs:', outputs_avg)
# PyTorch AvgPool2d API运算结果
print('PyTorch AvgPool2d outputs:', outputs_pytorch_avg)

五、预定义算子与自定义算子的对比

Pytorch版本

import torch
import torch.nn as nn

# 输入张量 (1, 3, 5, 5) 表示 [batch_size, in_channels, height, width]
inputs = torch.tensor([[[[0, 1, 1, 0, 2],
                         [2, 2, 2, 2, 1],
                         [1, 0, 0, 2, 0],
                         [0, 1, 1, 0, 0],
                         [1, 2, 0, 0, 2]],

                        [[1, 0, 2, 2, 0],
                         [0, 0, 0, 2, 0],
                         [1, 2, 1, 2, 1],
                         [1, 0, 0, 0, 0],
                         [1, 2, 1, 1, 1]],

                        [[2, 1, 2, 0, 0],
                         [1, 0, 0, 1, 0],
                         [0, 2, 1, 0, 1],
                         [0, 1, 2, 2, 2],
                         [2, 1, 0, 0, 1]]]], dtype=torch.float32)

# 定义两个卷积核和偏置
filters = torch.tensor([[[[-1, 1, 0],
                          [0, 1, 0],
                          [0, 1, 1]],

                         [[-1, -1, 0],
                          [0, 0, 0],
                          [0, -1, 0]],

                         [[0, 0, -1],
                          [0, 1, 0],
                          [1, -1, -1]]],

                        [[[1, 1, -1],
                          [-1, -1, 1],
                          [0, -1, 1]],

                         [[0, 1, 0],
                          [-1, 0, -1],
                          [-1, 1, 0]],

                         [[-1, 0, 0],
                          [-1, 0, 1],
                          [-1, 0, 0]]]], dtype=torch.float32)

# 偏置
bias = torch.tensor([1, 0], dtype=torch.float32)

# 创建卷积层
conv2d = nn.Conv2d(in_channels=3, out_channels=2, kernel_size=3, stride=2, padding=1, bias=True)

# 将权重和偏置初始化为自定义的值
with torch.no_grad():
    conv2d.weight = nn.Parameter(filters)
    conv2d.bias = nn.Parameter(bias)

# 进行卷积运算
outputs = conv2d(inputs)

# 打印输出
print("Conv2D outputs:\n", outputs)

自定义版本

import torch
import torch.nn as nn


class Conv2D(nn.Module):
    def __init__(self, in_channels, out_channels, kernel_size, stride=1, padding=0):
        super(Conv2D, self).__init__()
        # 初始化卷积核权重和偏置
        self.weight = nn.Parameter(torch.ones(out_channels, in_channels, kernel_size, kernel_size))
        self.bias = nn.Parameter(torch.zeros(out_channels, 1))
        self.stride = stride
        self.padding = padding
        self.in_channels = in_channels
        self.out_channels = out_channels

    # 基础卷积运算
    def single_forward(self, X, weight):
        # 零填充
        new_X = torch.zeros([X.shape[0], X.shape[1] + 2 * self.padding, X.shape[2] + 2 * self.padding])
        new_X[:, self.padding:X.shape[1] + self.padding, self.padding:X.shape[2] + self.padding] = X
        u, v = weight.shape
        output_w = (new_X.shape[1] - u) // self.stride + 1
        output_h = (new_X.shape[2] - v) // self.stride + 1
        output = torch.zeros([X.shape[0], output_w, output_h])
        for i in range(output.shape[1]):
            for j in range(output.shape[2]):
                output[:, i, j] = torch.sum(
                    new_X[:, i * self.stride:i * self.stride + u, j * self.stride:j * self.stride + v] * weight,
                    dim=[1, 2])
        return output

    def forward(self, inputs):
        """
        输入:
            - inputs: 输入矩阵,形状为 [B, D, M, N]
            - weights: P组二维卷积核,形状为 [P, D, U, V]
            - bias: P个偏置,形状为 [P, 1]
        """
        feature_maps = []
        # 进行多次多输入通道卷积运算
        for p in range(self.out_channels):
            multi_outs = []
            for i in range(self.in_channels):
                single = self.single_forward(inputs[:, i, :, :], self.weight[p, i, :, :])
                multi_outs.append(single)
            feature_map = torch.sum(torch.stack(multi_outs), dim=0) + self.bias[p]
            feature_maps.append(feature_map)
        # 将所有特征图堆叠起来
        out = torch.stack(feature_maps, dim=1)
        return out


# 定义输入矩阵 (1, 3, 5, 5),表示 [batch_size, in_channels, height, width]
inputs = torch.tensor([[[[0, 1, 1, 0, 2],
                         [2, 2, 2, 2, 1],
                         [1, 0, 0, 2, 0],
                         [0, 1, 1, 0, 0],
                         [1, 2, 0, 0, 2]],

                        [[1, 0, 2, 2, 0],
                         [0, 0, 0, 2, 0],
                         [1, 2, 1, 2, 1],
                         [1, 0, 0, 0, 0],
                         [1, 2, 1, 1, 1]],

                        [[2, 1, 2, 0, 0],
                         [1, 0, 0, 1, 0],
                         [0, 2, 1, 0, 1],
                         [0, 1, 2, 2, 2],
                         [2, 1, 0, 0, 1]]]], dtype=torch.float32)

# 定义卷积核和偏置
filter_w0 = torch.tensor([[[[-1, 1, 0],
                            [0, 1, 0],
                            [0, 1, 1]],

                           [[-1, -1, 0],
                            [0, 0, 0],
                            [0, -1, 0]],

                           [[0, 0, -1],
                            [0, 1, 0],
                            [1, -1, -1]]]], dtype=torch.float32)

filter_w1 = torch.tensor([[[[1, 1, -1],
                            [-1, -1, 1],
                            [0, -1, 1]],

                           [[0, 1, 0],
                            [-1, 0, -1],
                            [-1, 1, 0]],

                           [[-1, 0, 0],
                            [-1, 0, 1],
                            [-1, 0, 0]]]], dtype=torch.float32)

# 偏置
bias = torch.tensor([1, 0], dtype=torch.float32)

# 初始化卷积层
conv2d = Conv2D(in_channels=3, out_channels=2, kernel_size=3, stride=2, padding=1)
with torch.no_grad():
    conv2d.weight[0] = filter_w0
    conv2d.weight[1] = filter_w1
    conv2d.bias[:, 0] = bias

# 计算输出
outputs = conv2d(inputs)

# 打印输出
print("Conv2D outputs:\n", outputs)

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐