【深度学习 | CNN】“深入解析卷积神经网络与反卷积:从生活案例到原理的全面指南” (从一维、二维、三维讲解)

在这里插入图片描述

🤵‍♂️ 个人主页: @AI_magician
📡主页地址: 作者简介:CSDN内容合伙人,全栈领域优质创作者。
👨‍💻景愿:旨在于能和更多的热爱计算机的伙伴一起成长!!🐱‍🏍
🙋‍♂️声明:本人目前大学就读于大二,研究兴趣方向人工智能&硬件(虽然硬件还没开始玩,但一直很感兴趣!希望大佬带带)

该文章收录专栏
[✨— 《深入解析卷积神经网络:从原理到应用的全面指南》 —✨]

在这里插入图片描述

一、卷积

卷积(Convolution)这个名词最初来源于数学领域,指的是两个函数之间的一种数学运算,也称为函数的乘积积分。在深度学习中,卷积操作是通过将一个输入信号与一个卷积核进行卷积运算来提取特征。在这个过程中,卷积核会在输入信号上滑动,并在每个位置进行一次乘积累加的计算,最终得到一个输出特征图。因此,这个操作被称为卷积。

在深入了解卷积神经网络(Convolutional Neural Network, CNN)的原理之前,让我们使用一个简单的生活例子来说明其工作原理。想象一下你正在观看一部电影,而电影是由连续的图像帧组成的。你想要识别电影中的主要角色。这时,你的大脑就会使用类似于卷积神经网络的机制进行处理。首先,你的大脑会将图像帧传递给视觉皮层(Visual Cortex),这相当于CNN中的输入层。在视觉皮层中,一系列的神经元会对图像进行处理,每个神经元对应一个特定的区域(感受野)。然后,每个感受野会执行一个局部感知操作,类似于CNN中的卷积操作。这个操作类似于你的眼睛聚焦在图像的一个小部分,并提取特定的特征。例如,某个感受野可能会注意到脸部特征,而另一个感受野可能会注意到物体的纹理。接下来,提取的特征会通过神经元之间的连接进行传递,这类似于CNN中的池化操作。在池化过程中,一组相邻的感受野的特征被合并为一个单一的特征。这样做可以减少数据的维度,并提取更加重要的特征。这些特征将继续传递到更高级别的层次,类似于CNN中的隐藏层。在这些层次中,神经元将学习更加抽象和高级的特征表示,例如面部表情、物体形状等。最终,通过一系列的卷积、池化和隐藏层的操作,网络可以学习到适合于图像识别的特征。这些特征将传递到输出层,用于识别电影中的主要角色。

总的来说你的大脑类似于一个卷积神经网络。它通过局部感知、特征提取和特征学习的方式,从连续的图像帧中识别出主要角色。卷积神经网络的原理与此类似,通过卷积、池化和隐藏层的操作,从输入图像中提取有用的特征,并用于各种图像处理任务,如图像分类、目标检测等。尽管实际的卷积神经网络可能更复杂,包含更多的层和参数,但它们都遵循类似的原理

注意点:一定要知道一维卷积、二维卷积、三维卷积不同的是方向上的卷积,并且要知道一维卷积如何处理二维/三维数据,二维卷积如何处理三维数据。

1.1 Conv1D

我们考虑一个简单的情况,就像处理时间序列数据一样。想象你正在观察某个城市在一周内的每日气温变化。你想要通过一维卷积来平滑这些数据,以便更好地理解气温趋势(在该例子其实就是三个连续数值不同加权求和得到一个代表性的数值)。

假设你有一周的气温数据,表示为一维数组:

temperature_data = [20, 22, 24, 25, 28, 27, 26]

现在,让我们使用一个长度为3的一维卷积核(或过滤器)来对这些数据进行卷积操作。假设卷积核为:

kernel = [0.5, 0.8, 0.5]

进行一维卷积时,卷积核会滑动到数据的每个位置,然后在每个位置上执行元素乘法并相加。例如,对于位置1,卷积操作为:

result[1] = 20 * 0.5 + 22 * 0.8 + 24 * 0.5 = 37.0

同样地,对于位置2,卷积操作为:

result[2] = 22 * 0.5 + 24 * 0.8 + 25 * 0.5 = 47.0

继续这个过程,直到对整个数据进行卷积操作,得到平滑后的结果:

smoothed_data = [37.0, 47.0, 56.5, 59.0, 63.0, 61.5, 51.5]

在这个例子中,我们使用一维卷积核来平滑气温数据,从而减少数据中的噪声,更好地观察气温的整体变化趋势。

1.1.1 原理概述

一维卷积是指在单个方向(通常是时间轴)上进行的卷积操作。通常用于序列模型、自然语言处理领域该层创建卷积的卷积核输入层在单个空间(或时间)维度上以产生输出张量。如果“use_bias”为True,则创建一个偏置向量并将其添加到输出中。最后如果“激活”不是“无”,它也应用于输出。当使用该层作为模型中的第一层时,提供“input_shape”参数(整数元组或“无”,例如。`对于128维向量的10个向量的序列,或对于128维向量的可变长度序列为“(None,128)”。

一维卷积操作的原理与二维卷积类似,都是通过滑动一个固定大小的卷积核(即滤波器)在输入上进行卷积操作。在一维卷积中,卷积核是一个长度为 k 的一维张量,用于对输入的每个时间步进行滤波操作。卷积核的大小会影响到卷积后的输出形状,具体可以使用下面的公式计算:

output-length = ⌊ input-length − kernel-size + 2 padding stride + 1 ⌋ \text{output-length} = \lfloor \frac{\text{input-length} - \text{kernel-size} + 2\text{padding}}{\text{stride}} + 1 \rfloor output-length=strideinput-lengthkernel-size+2padding+1

其中,input_length 是输入张量的时间步数,kernel_size 是卷积核的大小,padding 是补零操作的大小,stride 是卷积核在输入上滑动的步幅。

在这里插入图片描述

layers.Conv1D 层可以设置多个参数,例如卷积核的大小、步幅、填充方式、激活函数等等。通过调整这些参数,可以有效地提取输入数据中的时序特征,用于后续的分类、回归等任务。

假设输入的数据为 x x x,卷积核为 w w w,偏置为 b b b,步长为 s s s,padding的大小为 p p p

对于一维卷积,我们可以将 x x x w w w 的维度都表示为长度,即:

x = [ x 1 , x 2 , x 3 , … , x n ] x=[x_1,x_2,x_3,…,x_n] x=[x1,x2,x3,,xn]

w = [ w 1 , w 2 , w 3 , … , w m ] w=[w_1,w_2,w_3,…,w_m] w=[w1,w2,w3,,wm]

则在不考虑padding的情况下,输出的每个元素 y i y_i yi 可以表示为:

在这里插入图片描述

其中, i i i 表示输出的位置, j j j 表示卷积核的位置, s s s 表示步长。而考虑padding的情况下,可以将 x x x 在两端分别加上 p p p 个 0,然后代入上述公式即可。

需要注意的是,一般情况下我们会在卷积层后面添加一个激活函数来引入非线性。在这个公式中,我们没有考虑激活函数的影响。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ebe4dxis-1687508663035)(layers.assets/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5LiN6LSf6Z-25Y2O4YOm,size_20,color_FFFFFF,t_70,g_se,x_16.png)]

卷积过程如上图所示,输入向量的大小为20,卷积核大小为5,步长(每一步移动距离)为1,不考虑填充,那么输出向量的大小为(20 - 5) / 1 + 1 = 16;如果考虑填充,那么输出向量大小为20 / 1 = 20。

更一般的,假设输入向量大小为F,卷积核大小为K, 步长为S,填充方式为“VALID”(也就是不考虑填充),那么输出向量大小N= (F - K / S) + 1;如果填充方式为“SAME”(不考虑步长,使输入矩阵和输出矩阵大小一样),则输出向量大小N = F / S

1.1.2 参数详解

tf.keras.layers.Conv1D(filters, 
					   kernel_size, 
					   strides=1, 
					   padding='valid',
					   data_format='channels_last', 
					   dilation_rate=1, 
					   groups=1,
					   activation=None, 
					   use_bias=True, 
					   kernel_initializer='glorot_uniform',
					   bias_initializer='zeros',
					   kernel_regularizer=None,
					   bias_regularizer=None, 
					   activity_regularizer=None, 
					   kernel_constraint=None,
					   bias_constraint=None, 
					   **kwargs
)
  • filters: 整数,输出空间的维度(即卷积核的个数)。
  • kernel_size: 整数或由一个整数构成的元组/列表,卷积核的空间或时间维度大小。
  • strides: 整数或由一个整数构成的元组/列表,卷积核的步长。默认为 1。
  • padding: 字符串,补齐策略(‘valid’ 或 ‘same’)。默认为 ‘valid’。
  • activation: 字符串或可调用对象,激活函数。如果不指定,将不应用任何激活函数。
  • use_bias: 布尔值,是否使用偏置。
  • kernel_initializer: 卷积核的初始化器。如果不指定,将使用默认的 Glorot 均匀分布初始化。
  • bias_initializer: 偏置的初始化器。如果不指定,将使用默认的零初始化。
  • kernel_regularizer: 卷积核的正则化器,可以使用 L1、L2 等正则化方式。
  • bias_regularizer: 偏置的正则化器,可以使用 L1、L2 等正则化方式。
  • activity_regularizer: 输出的正则化器,可以使用 L1、L2 等正则化方式。
  • kernel_constraint: 卷积核的约束,可以使用非负值约束、最大范数约束等。
  • bias_constraint: 偏置的约束,可以使用非负值约束、最大范数约束等。

1D卷积层(例如时间卷积)。通常用于序列模型、自然语言处理领域该层创建卷积的卷积核输入层在单个空间(或时间)维度上以产生输出张量。如果“use_bias”为True,则创建一个偏置向量并将其添加到输出中。最后如果“激活”不是“无”,它也应用于输出。当使用该层作为模型中的第一层时,提供“input_shape”参数(整数元组或“无”,例如。`对于128维向量的10个向量的序列,或对于128维向量的可变长度序列为“(None,128)”。

https://blog.csdn.net/weixin_49346755/article/details/124267879

# 一维卷积层,输出形状为(None, 16, 8),定义input_shape作为第一层
tf.keras.layers.Conv1D(8, 5, activation="relu", input_shape=(20, 1))
"""
 filters: 过滤器:整数,输出空间的维度(即卷积中输出滤波器的数量)
 kernel_size: 单个整数的整数或元组/列表,指定1D卷积窗口的长度。
 activation: 要使用的激活功能。激活函数
 strides: 步长,默认为1.
 padding: 表示填充方式,默认为VALID,也就是不填充。
 kernel_initializer: “内核”权重矩阵的初始化器(参见“keras.initizers”)。
 use_bias: 表示是否使用偏置矩阵,默认为True
 bias_initializer: 表示使用的偏置矩阵。
 
 Input shape:
    3D tensor with shape: `(batch_size, steps, input_dim)`

"""
# regularizers.l2(0.01) L2正则化(L2正则化因子)。
Con1 = layers.Conv1D(64, 3, activation='relu', kernel_regularizer=regularizers.l2(0.01))(BN)

1.1.3 输入与输出

需要注意的是,该层的输入应该是一个三维张量,形状为 (batch_size, steps, input_dim),其中 steps 表示时间步数,input_dim 表示每个时间步的输入特征维度。该层的输出是一个三维张量,形状为 (batch_size, new_steps, filters),其中 new_steps 是经过卷积和补齐后的时间步数,与原来的时间步数有关。

卷积层的输出也是一个张量,其形状取决于卷积层的参数设置。在一维卷积层中,如果使用padding=“valid”,那么输出的形状为(batch_size, output_length, filters),其中output_length表示输出序列的长度,filters表示卷积核的个数(即输出序列每个位置上的特征维度数量)。如果使用padding=“same”,那么输出的形状为(batch_size, input_length, filters),即与输入序列的长度保持一致。

需要注意的是,在卷积层中,每个卷积核的参数对于输入是共享的,即卷积核在输入张量的每个位置上进行卷积时使用的是相同的参数。这样可以大大减少模型的参数数量,同时也可以增强模型的泛化能力。

如果使用多个卷积核进行卷积操作,它们所提取的特征可能不同,因为它们所学习的卷积核参数不同。每个卷积核学习到的是不同的特征,通过使用多个卷积核,模型可以同时学习到多种不同的特征,从而提高模型的性能。

1.1.4 多次卷积

在卷积层后再次添加卷积层是一种常见的神经网络架构,其主要目的是在学习更高层次的特征表示,例如在计算机视觉任务中,第一层卷积层可以学习到简单的边缘特征,而第二层卷积层可以学习到更加复杂的形状和纹理特征。因此,通过多层卷积层堆叠,可以逐渐学习到更加抽象和高级的特征表示。

1.1.5 卷积核权重维度

如果你设置了六个长度为3的卷积核,那么每个卷积核的权重矩阵的形状将会是(3, input_channels, 6),其中input_channels是输入数据的特征维度。这表示每个卷积核都是一维的,其大小为3,且有6个不同的卷积核。在进行卷积运算时,输入数据中的每个时刻都会和6个不同的卷积核进行卷积操作,得到6个卷积后的输出结果,这些结果将被连接成一个更高维的输出张量。

假设我们有一个输入数据的维度为(6, 4, 3),表示有6个时间步,4个特征和3个通道。我们想要应用一个大小为(3, 3)的卷积核。(卷积核的权重维度将是(3, 3, 3, 1))

1.2 Conv2D

一维卷积和二维卷积的区别在于卷积操作的维度不同。在一维卷积中,卷积核只会在一个方向上进行滑动操作,例如在处理时间序列数据时,卷积核只会在时间轴上进行滑动操作。而在二维卷积中,卷积核会在两个方向上进行滑动操作,例如在处理图像数据时,卷积核会在图像的高度和宽度上进行滑动操作。因此,一维卷积和二维卷积的计算方式略有不同,但本质上都是将卷积核与输入数据进行点积运算,得到特征图作为下一层的输入。

在这里插入图片描述
如上图所示,输入矩阵的大小为5×5,卷积核矩阵的大小为3×3,在x, y 方向移动步长为(1, 1),采用了填充的方式(SAME)进行卷积(填充不是结果填充,是原本的填充。结果得到一个与输入矩阵大小一样的矩阵(5×5)。

在这里插入图片描述

卷积:蓝色的输入图片(4 x4),深蓝色代表卷积核(3 x 3),绿色为输出图像(2 x 2)

如上图所示,输入矩阵的大小为5×5,卷积核矩阵的大小为3×3,在x, y 方向移动步长为(1, 1),采用了填充的方式(SAME)进行卷积(填充不是结果填充,是原本的填充。结果得到一个与输入矩阵大小一样的矩阵(5×5)。

二维卷积的计算公式与一维卷积的计算公式类似,假设输入图像的大小为F×F,卷积核矩阵大小为K×K,步长为(S,S),如果填充方式为VALID,输出图像大小为N×N,则有N = (F - K / S) + 1;如果填充方式为SAME,则有N = F / S

在神经网络中,点积经常用于计算相似度、相似性分数或计算注意力权重等任务。点积运算是指两个向量中对应位置的元素相乘,并将所有结果相加的运算。对于两个长度为n的向量a和b,它们的点积运算结果为:

a ⋅ b = a [ 0 ] ∗ b [ 0 ] + a [ 1 ] ∗ b [ 1 ] + . . . + a [ n − 1 ] ∗ b [ n − 1 ] a·b = a[0]*b[0] + a[1]*b[1] + ... + a[n-1]*b[n-1] ab=a[0]b[0]+a[1]b[1]+...+a[n1]b[n1]

两个向量的点积可以表示它们的相似度,从而用于计算神经元的输出值或者用于计算损失函数。另外,在计算卷积神经网络中的卷积操作时,通常采用卷积核和输入数据的点积运算来得到卷积的结果。

点积本身并不能直接表示相似度,而是作为相似度度量的一种计算方式之一。当两个向量的点积较大时,表示它们在相同的方向上有更高的相似度。而当点积较小或为负数时,表示它们在相反的方向上或无关的方向上存在较高的差异。 通过点积,我们可以得到一种衡量向量之间关系的指标,但具体的解释和应用取决于具体的上下文和任务。

在二维卷积层中,输出的形状也取决于卷积层的参数设置,但是其基本形式为(batch_size, output_height, output_width, filters),其中output_height和output_width表示输出特征图的高度和宽度,filters表示卷积核的个数(即输出特征图每个位置上的特征维度数量)。

tf.keras.layers.Conv2D(
    filters,
    kernel_size,
    strides=(1, 1),
    padding='valid',
    data_format=None,
    dilation_rate=(1, 1),
    groups=1,
    activation=None,
    use_bias=True,
    kernel_initializer='glorot_uniform',
    bias_initializer='zeros',
    kernel_regularizer=None,
    bias_regularizer=None,
    activity_regularizer=None,
    kernel_constraint=None,
    bias_constraint=None,
    **kwargs
)

1.3 Conv3D

在三维卷积中有两种例子,其中之一假设你有一张**彩色图像,其中包含红、绿、蓝三个颜色通道。这样的彩色图像可以表示为一个三维数组,其中每个元素包含了图像在特定位置的颜色信息。**假设你想要对图像应用一个卷积核来进行边缘检测。边缘检测可以帮助我们找到图像中的边界或轮廓。这时,我们需要使用一个三维卷积核来处理彩色图像的每个颜色通道。考虑一个简单的三维卷积核,形状为 3x3x3,表示在3个颜色通道上的3x3的局部感知区域。卷积核会在图像的每个位置滑动,并执行元素乘法和相加操作,以获取特定位置的输出值。例如,对于图像中的某个位置,卷积操作会在每个颜色通道上执行元素乘法和相加,得到一个输出值。这个操作会在图像的所有位置重复进行,从而生成一个新的三维输出图像。这个例子中的三维卷积核用于边缘检测时,会对图像的每个颜色通道执行类似于边缘检测的操作。它可以帮助我们在每个颜色通道上找到图像中的边缘或轮廓。

还有一个例子是视频行为识别。假设你正在监控一间会议室,里面有多个人在进行不同的活动,例如站立、走动、举手等。你想要使用卷积神经网络来识别不同的行为。在这个场景中,视频可以看作是一个三维数据,它由两个空间维度(图像的宽度和高度)和一个时间维度(视频的帧数)组成。这样的视频可以表示为一个三维数组,其中每个元素代表一个像素值或颜色信息。为了对视频进行行为识别,我们需要使用三维卷积核来处理视频数据。这个卷积核在空间维度上滑动,同时在时间维度上遍历视频的帧,执行元素乘法和相加操作,以获取特定位置和时间的输出值。例如,考虑一个形状为 3x3x3x3 的三维卷积核,其中前两个维度表示在一个3x3的局部感知区域内,每个颜色通道的像素值,最后一个维度表示卷积核在时间维度上遍历视频的3帧。在应用三维卷积时,卷积核会在视频的每个位置和每个时间点滑动,并对每个颜色通道执行元素乘法和相加操作,得到一个输出值。这样的操作会在整个视频上重复进行,生成一个新的三维输出,表示不同时间点和空间位置的特征。这个例子中的三维卷积核用于视频行为识别时,可以帮助我们捕捉不同行为在时间序列上的特征变化。例如,当某人举手时,可能在一段时间内会出现特定的手臂移动模式(一种数据变化模式),而这个三维卷积可以帮助我们捕捉这种时间序列上的模式。

三维卷积层用于医学领域、视频处理领域(检测人物行为),用于三个维度的卷积。
在这里插入图片描述

三维卷积对数据集应用三维过滤器,过滤器向3个方向(x,y,z)移动,计算低层特征表示。输出形状是一个三维体积空间,如立方体或长方体。有助于视频、三维医学图像等的目标物检测。

三维卷积的输入形状为五维张量(batch_size, frames, height, width, channels),batch_size为批处理数据的大小,frams可以理解为视频中的帧数,其中每一帧为一幅图像,height为图像的高度,width为图像的宽度,channels为图像通道数。输出形状也是一个五维张量。

二、反卷积

假设你有一张古老的照片,由于年代久远和物理损坏,照片上出现了许多破损、划痕和噪点。你希望使用反卷积来修复照片,恢复其原始的清晰度和细节。在这个场景中,反卷积可以用于学习照片修复过程,将破损和损坏的像素映射到原始清晰的像素。这样的修复过程可以通过训练反卷积网络来实现。反卷积网络使用反卷积层来进行图像修复。输入是破损的图像,输出是修复后的图像。例如,考虑一个简单的反卷积层,输入是一张破损的图像(例如,256x256像素),输出是一张修复后的图像(例如,256x256像素)。在反卷积过程中,网络会使用反卷积核来将破损和损坏的像素恢复到原始图像的空间维度上。通过训练网络学习图像的修复过程,它可以从破损的输入图像中恢复出丢失的细节和结构,使修复后的图像更加清晰和自然。

实际应用中,反卷积在图像修复和复原中起着重要作用。它可以帮助修复老旧照片、受损图像或受到噪声污染的图像,恢复它们的原始外观和细节。这个生活例子展示了反卷积在图像修复中的应用,通过学习将破损的像素映射到原始清晰的像素,实现图像的修复和恢复。
反卷积(deconvolution)也称为转置卷积(transpose convolution),是一种常用于图像处理和计算机视觉领域的操作。它可以将一个低维度的特征映射(如经过卷积和池化后的结果)还原成更高维度的特征映射,通常用于图像分割、目标检测等任务中。

它是一种特殊的卷积,先padding来扩大图像尺寸,紧接着跟正向卷积一样,旋转卷积核180度,再进行卷积计算。看上去就像,已知正向卷积的输出图像,卷积核,得到正向卷积中的原始图像(并非真的得到原始图像,像素点是不一样的,但是尺寸是一致的)。它看上去像是正向卷积的逆运算,但其实并不是。因为反卷积只能还原原始图像的尺寸,但是并不能真的恢复原始图像内容,即每个元素值其实是不一样的。

由于卷积核一般比原始图像小,所以卷积之后的图像尺寸往往会变小。有时候我们需要将卷积后的图像还原成原始图像的尺寸,即实现图像从小分辨率到大分辨率的映射,这种操作就叫做上采样(Upsampling)。而反卷积正是一种上采样方法。

(参考自: https://www.cnblogs.com/hansjorn/p/14767592.html)

反卷积的Striding跟卷积有点不一样,它在输入的每个元素之间插入 s − 1 s- 1 s1个值为0的元素

在这里插入图片描述

我们根据以下例子来了解原理:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M4NClXwC-1687856937156)(layers.assets/v2-4992be8ec58775d0f6f963c2ae7129b3_b.jpg)]

反卷积:蓝色是输入(3 x 3), 灰色是卷积核(3 x 3), 绿色是输出(5 x 5),padding=1,strides = 2

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rV2LwDEA-1687856937157)(layers.assets/v2-cdbe7b7a84e9d34e954fac922e9404ab_b-16878543936166.jpg)]

反卷积:蓝色是输入(5 x 5), 灰色是卷积核(3 x 3), 绿色是输出(5 x 5),padding=1,strides =1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ko7mNV3V-1687856937157)(layers.assets/v2-286ac2cfb69abf4d8aa06b8eeb39ced3_b.jpg)]

反卷积,蓝色是输入(2 x 2), 灰色是卷积核(3 x 3), 绿色是输出(4 x 4),padding=2

应用场景:

  1. 图像超分辨率:反卷积可以将低分辨率的图像还原为高分辨率,这在图像超分辨率任务中非常有用。
  2. 图像去噪:反卷积可以去除图像中的噪声,从而改善图像质量。
  3. 图像分割:反卷积可以将卷积网络的最后一层输出特征图还原成与输入图像尺寸相同的特征图,从而帮助进行图像分割。
  4. 对抗生成网络(GAN):反卷积可以用于生成器部分,将低维噪声转换为高维图像。
  5. 图像生成:逆卷积操作可以通过学习合适的卷积核来生成新的图像,从而实现图像生成任务。

总的来说,反卷积在计算机视觉领域中有着广泛的应用场景。它是一种非常有效的图像处理技术,可以帮助我们提高计算机视觉任务的准确性和效率。

在这里插入图片描述

													  🤞到这里,如果还有什么疑问🤞
												🎩欢迎私信博主问题哦,博主会尽自己能力为你解答疑惑的!🎩
												 	 🥳如果对你有帮助,你的赞是对博主最大的支持!!🥳
  • 26
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 23
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

计算机魔术师

在校大二学生,请多多指教

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值