深度学习之卷积神经网络(11)卷积层变种
深度学习之卷积神经网络(11)卷积层变种
- 1. 空洞卷积
- 2. 转置卷积
- 矩阵角度
- 转置卷积实现
- 3. 分离卷积
卷积神经网络的研究产生了各种各样优秀的网络模型,还提出了各种卷积层的变种,本节将重点介绍书中典型的卷积层变种。
1. 空洞卷积
普通的卷积层为了减少为了的参数量,卷积核的设计通常选择较小的1×11×11×1和3×33×33×3感受野大小。小卷积核使得网络提取特征时的感受野区域有限,但是增大感受野区域又会增加网络的参数量和计算代价,因此需要权衡设计。
空洞卷积(Dilated/Atrous Convolution)的提出较好地解决了这个问题,空洞卷积在普通卷积的感受野上增加一个Dilation Rate参数,用于控制感受野区域的采样步长,如下图所示:
感受野采样步长示意图
当感受野的采样步长Dilation Rate为1时,每个感受野采样点之间的距离为1,此时的空洞卷积退化为普通的卷积; 当Dilation Rate为2时,感受野每2个单元采样一个点,如上图中间绿色方框中绿色格子所示,每个采样格子之间的距离为2; 同样的方法,最右边图的Dilation Rate为3,采样步长为3,尽管Dilation Rate的增大会使得感受野区域增大,但是实际参与运算的点数仍然保持不变。
以输入为单通道的7×77×77×7张量,单个3×33×33×3卷积核为例,如下图所示。在初始位置,感受野从最上、最右位置开始采样,每隔一个点采样一次,共采集9个数据点,如下图中绿色方框所示。这9个数据点与卷积核相乘运算,写入输出张量的对应位置。
空洞卷积计算示意图-1
卷积核窗口按着步长为s=1s=1s=1向右移动一个单位,如下图所示,同样进行个点采样,共采样9个数据点,与卷积核完成相乘累加运算,写入输出张量对应为,直至卷积核移动至最下方、最右边位置。需要注意区分的是,卷积核窗口的移动步长s和感受野区域的采样步长Dilation Rate是不同的概念。
空洞卷积计算示意图-2
空洞卷积在不增加网络参数的条件下,提供了更大的感受野窗口。但是在使用空洞卷积设置网络模型时,需要精心设计Dilation Rate参数来避免出现网格效应,同样较大的Dilation Rate参数并不利于小物体的检测、语义分割等任务。
在TensorFlow中,可以通过设置layers.Conv2D()类的dilation_rate参数来选择使用普通卷积还是空洞卷积。例如:
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequentialx = tf.random.normal([1,7,7,1]) # 模拟输入
# 空洞卷积,1个3×3的卷积核
layer = layers.Conv2D(1, kernel_size=3, strides=1, dilation_rate=2)
out = layer(x) # 向前计算
print(out.shape)
运行结果如下图所示:
![在这里插入图片描述](https://img-blog.csdnimg.cn/6e3ecf2d1eff4b71acef8870dfcb42c5.png#pic_center)
当dilation_rate参数设置为默认值1时,使用普通卷积方式进行计算; 当dilation_rate参数大于1时,采用空洞卷积方式进行计算。
2. 转置卷积
转置卷积(Transposed Convolution,或Fractionally Strided Convolution,部分资料也称之为反卷积/Deconvolution,实际上反卷积在数学上定义为卷积的逆过程,单转置卷积并不能恢复出原卷积的输入,因此称为反卷积并不妥当)通过在输入之间填充大量的padding来实现输出高宽大于输入高宽的效果,从而实现向上采样的目的,如下图所示。我们先介绍转置卷积的计算过程,再介绍转置卷积与普通卷积的联系。
为了简化讨论,我们此处只讨论输入h=wh=wh=w,即输入高宽相等的情况。
转置卷积实现向上采样
o+2p−k\boldsymbol {o+2p-k}o+2p−k为s\boldsymbol ss倍数
考虑输入为2×22×22×2的单通道特征图,转置卷积核为3×33×33×3大小,步长为s=2s=2s=2,填充p=0p=0p=0的例子。首先再输入数据点之间均匀插入s−1s-1s−1个空白数据点,得到3×33×33×3的矩阵,如下图第2个矩阵所示,根据填充量3×33×33×3矩阵周围填充相应k−p−1=3−0−1=2k-p-1=3-0-1=2k−p−1=3−0−1=2行/列,此时输入张量的高宽为7×77×77×7,如下图中第3个矩阵所示。
输入填充步骤
在7×77×77×7的输入张量上,进行3×33×33×3卷积核,步长s′=1s'=1s′=1,填充p=0p=0p=0的普通卷积运算(注意,此阶段的普通卷积的步长s′s's′始终为1,与转置卷积的步长sss不同),根据普通卷积的输出计算公式,得到输出大小为:
o=[i+2∗p−ks′]+1=[7+2∗0−31]+1=5o=[\frac{i+2*p-k}{s'} ]+1=[\frac{7+2*0-3}{1}]+1=5o=[s′i+2∗p−k]+1=[17+2∗0−3]+1=5
5×55×55×5大小的输出。我们直接按照此计算流程给出最终转置卷积输出与输入关系。在o+2p−ko+2p-ko+2p−k为sss倍数时,满足关系:
o=(i−1)s+k−2po=(i-1)s+k-2po=(i−1)s+k−2p
转置卷积并不是普通的逆过程,但是二者之间有一定的联系,同时转置卷积也是基于普通卷积实现的。在相同的设定下,输入x\boldsymbol xx经过普通卷积运算得到o=Conv(x)\boldsymbol o=\text{Conv}(\boldsymbol x)o=Conv(x),我们将o送入转置卷积运算后,得到x′=ConvTranspose(o)\boldsymbol x'=\text{ConvTranspose}(\boldsymbol o)x′=ConvTranspose(o),其中x′≠x\boldsymbol x'≠\boldsymbol xx′=x,但是x′\boldsymbol x'x′与x\boldsymbol xx形状相同。我们可以用输入为5×55×55×5,步长s=2s=2s=2,填充p=0p=0p=0,3×33×33×3卷积核的普通卷积运算进行验证演示,如下图所示:
利用普通卷积恢复等大小输入
可以看到,将转置卷积的输出5×55×55×5在同设定条件下送入普通卷积,可以得到2×22×22×2的输出,此大小恰好就是转置卷积的输入大小,同时我们也观察到,输出2×22×22×2矩阵并不是转置卷积输入的2×22×22×2矩阵。转置卷积与普通卷积并不是互为逆过程,不能恢复出对方的输入内容,仅能恢复出等大小的张量。因此称之为反卷积并不切贴。
&emspl;基于TensorFlow实现上述例子的转置卷积运算,代码如下:
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential# 创建X矩阵,高宽为5×5
x = tf.range(25)+1
# Reshape为合法维度的张量
x = tf.reshape(x, [1, 5, 5, 1])
x = tf.cast(x, tf.float32)
# 创建固定内容的卷积核矩阵
w = tf.constant([[-1, 2, -3.], [4, -5, 6], [-7, 8, -9]])
# 调整为合法维度的张量
w = tf.expand_dims(w, axis=2)
w = tf.expand_dims(w, axis=3)
# 进行普通卷积运算
out = tf.nn.conv2d(x, w, strides=2, padding='VALID')
print(out)
运行结果如下图所示:
现在我们将普通卷积的输出作为转置卷积的输入,验证转置卷积的输出是否为5×55×55×5,代码如下:
# 普通卷积的输出作为转置卷积的输入,进行转置卷积运算
xx = tf.nn.conv2d_transpose(out, w, strides=2,padding='VALID',output_shape=[1, 5, 5, 1])
# 输出的高宽为5×5
print(xx)
运行结果如下:
tf.Tensor(
[[[[ 67.][ -134.][ 278.][ -154.][ 231.]][[ -268.][ 335.][ -710.][ 385.][ -462.]][[ 586.][ -770.][ 1620.][ -870.][ 1074.]][[ -468.][ 585.][-1210.][ 635.][ -762.]][[ 819.][ -936.][ 1942.][-1016.][ 1143.]]]], shape=(1, 5, 5, 1), dtype=float32)
o+2p−k\boldsymbol{o+2p-k}o+2p−k不为s\boldsymbol ss倍数
让我们更加深入地分析卷积运算中输入与输出大小关系的一个细节。考虑卷积运算的输出表达式:
o=[i+2∗p−ks′]+1o=[\frac{i+2*p-k}{s'} ]+1o=[s′i+2∗p−k]+1
当步长s>1s>1s>1时,[i+2∗p−ks′][\frac{i+2*p-k}{s'} ][s′i+2∗p−k]向下取整运算使得出现多种不同输入尺寸iii对应到相同的输出尺寸ooo上。举个例子,考虑输入大小为6×66×66×6,卷积核大小为3×33×33×3,步长为1的卷积运算,代码如下:
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential# 创建X矩阵,高宽为5×5
x = tf.random.normal([1, 6, 6, 1])
x = tf.cast(x, tf.float32)
# 创建固定内容的卷积核矩阵
w = tf.constant([[-1, 2, -3.], [4, -5, 6], [-7, 8, -9]])
# 调整为合法维度的张量
w = tf.expand_dims(w, axis=2)
w = tf.expand_dims(w, axis=3)
# 进行普通卷积运算
out = tf.nn.conv2d(x, w, strides=2, padding='VALID')
print(out)
print(out.shape)
运行结果如下图所示:
此种情况也能获得2×22×22×2大小的卷积输出,与利用普通卷积可以获得相同大小的输出。因此,不同输入大小的卷积运算可能获得相同大小的输出。考虑到卷积与专制卷积输入输出大小关系互换,从转置卷积的角度来说,输入尺寸iii经过转置卷积运算后,可能获得不同的输出ooo大小。因此通过填充aaa行、aaa列来实现不同大小的输出ooo,从而恢复普通卷积不同大小的输入的情况,其中aaa关系为:
a=(o+2p−k)a=(o+2p-k)%sa=(o+2p−k)
此时转置卷积的输出变为:
o=(i−1)s+k−2p+ao=(i-1)s+k-2p+ao=(i−1)s+k−2p+a
在TensorFlow中间,不需要手动指定aaa参数,只需要指定输出尺寸即可,TensorFlow会自动推导需要填充的行列数aaa,前提是输出尺寸合法。例如:
# 恢复出6×6大小
out = tf.nn.conv2d(x, w, strides=2, padding='VALID')
print(out)
print(out.shape)# 普通卷积的输出作为转置卷积的输入,进行转置卷积运算
xx = tf.nn.conv2d_transpose(out, w, strides=2,padding='VALID',output_shape=[1, 6, 6, 1])
# 输出的高宽为5×5
print(xx)
运行结果如下所示:
tf.Tensor(
[[[[ -8.0665455][ 16.133091 ][ -4.7349663][ -38.92934 ][ 58.394012 ][ 0. ]][[ 32.266182 ][ -40.332726 ][ -29.459408 ][ 97.32335 ][-116.788025 ][ 0. ]][[ -59.992893 ][ 71.58651 ][ 38.390343 ][-126.35293 ][ 131.13538 ][ 0. ]][[ 14.108292 ][ -17.635365 ][ 79.89131 ][ -73.41109 ][ 88.09331 ][ 0. ]][[ -24.68951 ][ 28.216583 ][-134.51918 ][ 117.45774 ][-132.13995 ][ 0. ]][[ 0. ][ 0. ][ 0. ][ 0. ][ 0. ][ 0. ]]]], shape=(1, 6, 6, 1), dtype=float32)
通过改变参数output_shape=[1, 5, 5, 1]
也可以获得高宽为5×5的张量。
矩阵角度
转置卷积的转置是指卷积核W\boldsymbol WW产生的稀疏矩阵W′\boldsymbol W'W′在计算过程中需要先转置W′T\boldsymbol W'^TW′T,再进行矩阵相乘运算,而普通卷积并没有转置W′\boldsymbol W'W′的步骤。这也是它被称为转置卷积的名字的由来。
考虑普通Conv2d运算: X\boldsymbol XX和W\boldsymbol WW,需要根据strides将卷积核在行、列方向循环移动获取参与运算的感受野的数据,串行计算每个窗口的“相乘累加”值,计算效率极地。为了加速运算,在数学上可以将卷积核W\boldsymbol WW根据strides重排成稀疏矩阵W′\boldsymbol W'W′,再通过W′@X′\boldsymbol W'@\boldsymbol X'W′@X′一次完成运算(实际上,W′\boldsymbol W'W′矩阵过于稀疏,导致很多无用的0乘运算,很多深度学习框架也不是通过这种方式实现的)。
以4行4列的输入X\boldsymbol XX,高宽为3×3,步长为1,无padding的卷积核W\boldsymbol WW的卷积运算为例,首先将X\boldsymbol XX打平成X′\boldsymbol X'X′,如下图所示:
转置卷积X'
然后将卷积核W\boldsymbol WW转换成稀疏矩阵W′\boldsymbol W'W′,如下图所示:
转置卷积W'
此时通过一次矩阵相乘即可实现普通卷积运算:
O′=W′@X′\boldsymbol O'=\boldsymbol W'@\boldsymbol X'O′=W′@X′
如果给定O\boldsymbol OO,希望能够生成与X\boldsymbol XX相同形状大小的张量,怎么实现呢?将W′\boldsymbol W'W′转置后与上图方法重排后的O′\boldsymbol O'O′完成矩阵相乘即可:
X′=W′T@O′\boldsymbol X'=\boldsymbol W'^T@\boldsymbol O'X′=W′T@O′
得到的X′\boldsymbol X'X′通过reshape操作变为与原来的输入X\boldsymbol XX尺寸一致,但是内容不同。例如O′\boldsymbol O'O′的shape为[4,1][4,1][4,1],W′T\boldsymbol W'^TW′T的shape为[16,4][16,4][16,4],Reshape后即可产生[4,4][4,4][4,4]形状的张量。由于转置卷积在矩阵运算时,需要将W′\boldsymbol W'W′转置后才能与转置卷积的输入O′\boldsymbol O'O′矩阵相乘,故称为转置卷积。
转置卷积具有“放大特征图”的功能,在生成对抗网络、语义分割等中得到了广泛应用,如DCGAN[1]中的生成器通过堆叠转置卷积层实现逐层“放大”特征图,最后获得十分逼真的生成图片。
DCGAN生成器网络结构
[1] A. Radford, L. Metz 和 S. Chintala, Unsupervised Representation Learning with Deep Convolutional Generative Adversarial Networks, 2015.
转置卷积实现
在TensorFlow中,可以通过nn.conv2d_transpose
实现转置卷积运算。我们先通过nn.conv2d完成普通卷积运算。注意转置卷积的卷积核的定义格式为[k,k,cout,cin][k,k,c_{out},c_{in}][k,k,cout,cin]。例如:
import tensorflow as tf
from tensorflow.keras import layers, optimizers, datasets, Sequential# 创建4×4大小的输入
x = tf.range(16)+1
x = tf.reshape(x, [1, 4, 4, 1])
x = tf.cast(x, tf.float32)
# 创建3×3卷积核
w = tf.constant([[-1, 2, -3.], [4, -5, 6], [-7, 8, -9]])
w = tf.expand_dims(w, axis=2)
w = tf.expand_dims(w, axis=3)
# 普通卷积运算
out = tf.nn.conv2d(x, w, strides=1, padding='VALID')
print(out)
运行结果如下图所示:
保持strides=1,padding=‘VALID’,卷积核不变的情况下,我们通过卷积核www与输出outoutout的转置卷积运算尝试恢复与输入xxx相同大小的高宽张量,代码如下:
# 恢复4×4大小的输入
xx = tf.nn.conv2d_transpose(out, w, strides=1,padding='VALID',output_shape=[1, 4, 4, 1])
tf.squeeze(xx)
print(xx)
运行结果如下所示:
tf.Tensor(
[[[[ 56.][ -51.][ 46.][ 183.]][[-148.][ -35.][ 35.][-123.]][[ 88.][ 35.][ -35.][ 63.]][[ 532.][ -41.][ 36.][ 729.]]]], shape=(1, 4, 4, 1), dtype=float32)
可以看到,转置卷积生成了4×44×44×4的特征图,单特征图的数据与输入xxx并不相同。
在使用tf.nn.conv2d_transpose
进行转置卷积运算时,需要额外手动设置输出的高宽。tf.nn.conv2d_transpose
并不支持自定义padding设置,只能设置为VALID或者SAME。
当设置padding=‘VALID’
时,输出大小表达为:
o=(i−1)s+ko=(i-1)s+ko=(i−1)s+k
当设置padding=‘SAME’
时,输出大小表达为:
o=i⋅so=i\cdot so=i⋅s
如果我们还是对转置卷积原理细节暂时无法理解,可以先牢记上述两个表达式即可。例如:
2×22×22×2的转置卷积输入与3×33×33×3的卷积核运算,strides=1,padding=‘VALID’时,输出大小为:
h′=w′=(2−1)⋅1+3=4h'=w'=(2-1)\cdot1+3=4h′=w′=(2−1)⋅1+3=4
2×22×22×2的转置卷积输入与3×33×33×3的卷积核运算,strides=1,padding=‘VALID’时,输出大小为:
h′=w′=2⋅3=6h'=w'=2\cdot3=6h′=w′=2⋅3=6
转置卷积也可以和其他层一样,通过layers.Conv2DTranspose类创建一个转置卷积层,然后调用实例即可完成向前计算。代码如下:
# 创建转置卷积类
layer = layers.Conv2DTranspose(1, kernel_size=3, strides=1, padding='VALID')
xx2 = layer(out)
print(xx2)
运行结果如下:
tf.Tensor(
[[[[ 6.942313 ][ 33.887856 ][ 24.800087 ][ -4.222195 ]][[ 21.720724 ][ 68.23484 ][ 73.74913 ][ 28.219326 ]][[ 15.284215 ][ 10.42746 ][ 12.951116 ][ 20.34887 ]][[ -1.9099097][-26.6492 ][-56.841545 ][-32.62231 ]]]], shape=(1, 4, 4, 1), dtype=float32)
3. 分离卷积
这里以深度可分离卷积(Depth-wise Separable Convolution)为例。普通卷积在对多通道输入进行运算时,卷积核的每个通道与输入的每个通道分别进行卷积运算,得到多通道的特征图,再对应元素相加产生单个卷积核的最终输出,如下图所示:
普通卷积计算示意图
分离卷积的计算流程则不同,卷积核的每个通道与输入的每个通道进行卷积预算,得到多个通道的中间特征,如下图所示。这个多通道的中间特征张量接下来进行多个1×11×11×1卷积核的普通卷积运算,得到多个高宽不变的输出,这些输出在通道轴上面进行拼接,从而产生最终的分离卷积层的输出。可以看到,分离卷积层包含了两步卷积运算,第一步卷积运算是单个卷积核,第二个卷积运算包含了多个卷积核。
深度可分离卷积计算示意图
那么采用分离卷积有什么优势呢?一个很明显的优势在于,同样的输入和输出,采用Separable Convolution的参数量约是普通卷积的13\frac{1}{3}31。考虑上图中的普通卷积和分离卷积的例子。普通卷积的参数量是
3⋅3⋅3⋅4=1083\cdot3\cdot3\cdot4=1083⋅3⋅3⋅4=108
分离卷积的第一部分参数量是
3⋅3⋅3⋅1=273\cdot3\cdot3\cdot1=273⋅3⋅3⋅1=27
第二部分参数量是
1⋅1⋅3⋅4=121\cdot1\cdot3\cdot4=121⋅1⋅3⋅4=12
分离卷积的总参数量只有39,但是却能实现普通卷积同样的输入输出尺寸变换。分离卷积在Xception和MobileNets等对计算代价敏感的领域中得到了大量应用。
深度学习之卷积神经网络(11)卷积层变种相关推荐
- 神经网络的输出层有哪些_深度学习的数学-神经网络、输入层、隐藏层、输出层...
前言 前文中了解到,神经网络由多个神经单元组成,而本篇博客将会了解到深度学习由多个神经网络组成,并且分为 输入层.隐藏层和输出层,隐藏层涉及到的知识点最多,是本文的重点 正文 阶层型的神经网络主要结构 ...
- 【深度学习】深入浅出神经网络框架的模型元件(池化、正则化和反卷积层)
[深度学习]深入浅出神经网络框架的模型元件(池化.正则化和反卷积层) 文章目录 1 池化层 1.1 MaxPooling2D 1.2 MaxPooling1D: 1.3 AveragePooling2 ...
- 【深度学习】深入浅出神经网络框架的模型元件(常用层和卷积层)
[深度学习]深入浅出神经网络框架的模型元件(常用层和卷积层) 文章目录 1 常用层1.1 Dense1.2 Activation层1.3 Dropout1.4 Flatten 2 卷积层2.1 Cov ...
- 花书+吴恩达深度学习(十二)卷积神经网络 CNN 之全连接层
目录 0. 前言 1. 全连接层(fully connected layer) 如果这篇文章对你有一点小小的帮助,请给个关注,点个赞喔~我会非常开心的~ 花书+吴恩达深度学习(十)卷积神经网络 CNN ...
- 卷积神经网络之卷积计算、作用与思想 深度学习
博客:blog.shinelee.me | 博客园 | CSDN 卷积运算与相关运算 在计算机视觉领域,卷积核.滤波器通常为较小尺寸的矩阵,比如3×33×3.从这个角度看,多层卷积是在进行逐层映射,整 ...
- 深度学习笔记 第四门课 卷积神经网络 第三周 目标检测
本文是吴恩达老师的深度学习课程[1]笔记部分. 作者:黄海广[2] 主要编写人员:黄海广.林兴木(第四所有底稿,第五课第一二周,第三周前三节).祝彦森:(第三课所有底稿).贺志尧(第五课第三周底稿). ...
- 深度学习笔记 第四门课 卷积神经网络 第四周 特殊应用:人脸识别和神经风格转换...
本文是吴恩达老师的深度学习课程[1]笔记部分. 作者:黄海广[2] 主要编写人员:黄海广.林兴木(第四所有底稿,第五课第一二周,第三周前三节).祝彦森:(第三课所有底稿).贺志尧(第五课第三周底稿). ...
- 卷积神经网络精确率不增反降_深度学习 第四门课:卷积神经网络(Convolutional Neural Networks)...
"本文大约有 4864 字. 01|引言 在这两周时间里,我主要的学习内容如下: 重新复习了一遍前三门课: 学完第四门课卷积神经网络(ConvolutionalNeural Networks ...
- 深度学习中常见卷积(普通卷积、1×1卷积、转置卷积、可分离卷积、膨胀(空洞)卷积、3D卷积)
总是在网络上看到各种名词的卷积,但是有搞不懂是什么含义,于是结合网上查阅的资料,总结一下.目前比较常用的卷积主要有常规的卷积.1×1卷积.转置卷积.可分离卷积.膨胀卷积.3D卷积. 以下是一些 ...
- 【动手学深度学习PyTorch版】12 卷积层
上一篇移步[动手学深度学习PyTorch版]11 使用GPU_水w的博客-CSDN博客 目录 一.卷积层 1.1从全连接到卷积 ◼ 回顾单隐藏层MLP ◼ Waldo在哪里? ◼ 原则1-平移不变性 ...
最新文章
- 什么时候用转发什么时候用重定向_验孕棒什么时候用最准确
- python简单入门
- java 单元测试技巧_其他一些单元测试技巧
- python3.8文档_python 3.8的新功能
- 7-26 单词长度 (15 分)
- [转]Kaldi语音识别
- spring相关—IOC容器—使用注解配置bean
- 小孩桌面便签隐藏了怎么恢复?
- OpenSees二次开发实例01
- 深夜给这个世界添加一点佐料
- 黑客黑手伸向微博微信手机成网络钓鱼主要渠道
- 第26课时,实践4,定期存款利息计算器
- python读书心得体会范文_个人读书心得体会范文五篇
- 【JAVA长虹键法】第八式 代理模式(23种设计模式)
- Field userDao in com.sd.sbmb.service.impl.UserServiceImpl required a bean of type ‘com.sd.sbmb.dao.U
- Java 高并发编程详解 17.0 Active Object 模式
- 保研夏令营-南大、哈工、天大、中山、北理
- Android11 WMS 之 AppTransition
- Mansory makeConstraints 、remakeConstraints 、updateConstraints 注意事项
- 拓嘉辰丰:拼多多网店被投诉后影响有多大
热门文章
- Android开发之Android WIFI ADB(ADB WIFI)无线调试的技巧
- Android开发之6.0动态权限工具类(高德地图官方扣出来的)附源码
- html hover 效果,CSS八种让人眼前一亮的HOVER效果的示例代码
- 组件化开发实战_一篇文章搞懂什么是前端“组件化”开发
- android毛玻璃遮罩效果_css3毛玻璃效果[模糊图片]
- koa2入门(3)mongoose 增删改查
- Web Worker的最好文章
- Python为什么这么厉害? 不想成为专业码农? 来学习Python吧!
- c language compile process.
- 网段:192.168.6.0的机器A要访问网段:10.1.56.0的机器B