【神经网络】(10) Resnet18、34 残差网络复现,附python完整代码

article/2025/11/10 15:34:50

各位同学好,今天和大家分享一下 TensorFlow 深度学习中如何搭载 Resnet18 和 Resnet34 残差神经网络,残差网络利用 shotcut 的方法成功解决了网络退化的问题,在训练集和校验集上,都证明了的更深的网络错误率越小。

论文中给出的具体的网络结构如下:

Resnet50 网络结构我已经在之前的博客中复现过,感兴趣的可以看一下:https://blog.csdn.net/dgvv4/article/details/121878494

感谢简书大佬画的残差网络结构图:https://www.jianshu.com/p/085f4c8256f1


1. 构建单个残差块

一个残差单元的结构如下。输入为X ;weight layer 代表卷积层,这里是指 convolution卷积层 + batch normalization批标准化层 ;relu 是激活函数 ; identity 是将输入 X 经过变换后与卷积层的输出结果相加,下面会详细说明。

残差块中的第一个卷积层 self.conv1,主要用于下采样特征提取

如果步长 strides=1,由于padding='same',该层的输入和输出特征图的size不变。属于结构图中左侧蓝色部分。

如果步长 strides=2,表示该层输出的特征图的 size 是输入的特征图 size 的一半。由于卷积核的size是 3*3 ,卷积核移动时会出现滑窗无法覆盖所有的像素格的现象。可能会出现,该层的输出特征图size不等于输入size的一半。通过padding='same'自动填充输入图像,让输出size等于一半的输入size。属于结构图中的左侧后三种颜色的部分

残差块中的第二个卷积层 self.conv2,主要用于进一步提取特征,不进行下采样

规定其步长 stride=1,由于padding='same',该层的输入和输出特征图的size不变

完成卷积部分convblock之后,接下来看短接部分identityblock

identity 负责将输入 X的shape 变换到和卷积部分的输出的shape相同

如果第一个卷积层 self.conv1 步长 strides=1,那么输入特征图的 shape 和卷积层输出的特征图的 shape 相同,这时 identity 不需要变换输入特征图 X 的shape。

如果第一个卷积层 self.conv1 步长 strides=2,那么输入特征图的 size 变成了原来的一半。这时,为了能将输入 X 和 卷积层输出结果相加,需要通过 identity 重塑输入 X 的shape。这里使用的是 1*1 卷积传递特征1*1的卷积核遍历所有的像素格后不会改变特征图的size,设置步长strides=2,成功将特征图的size变成原来的一半。属于结构图中的左侧后三种颜色的部分。

这样,我们就完成了对单个残差块中所有层的初始化,接下来将层之间的前向传播过程写在 call() 函数中。这里需要注意的就是 layers.add([out, identity]) ,将卷积层的输出特征图的结果和输入的特征图相加identity 只负责将输入特征图的 shape 变换成和卷积部分输出特征图的 shape 相同

该部分的代码如下:

# Basic Bolck 残差块
# x--> 卷积 --> bn --> relu --> 卷积 --> bn --> 输出 
# |---------------Identity(短接)----------------|# 定义子类,一个残差块
class BasicBlock(layers.Layer):  # 继承父类的方法和属性#(1)子类初始化# filter_num 代表传入卷积核数量,将输入图像的通道数变成残差块的规定的通道数# stride 代表步长,默认为1,代表不对输入图片的size采样,如果不做padding,得到的图像的size就略小,做padding后输入和输出的size保持一致# strdie=2时,代表二分采样,输出的size只有输入size的一半def __init__(self, filter_num, stride=1):# 继承父类的初始化方法,# super()中的第一个参数是子类名称,第二个是子类的实例化对象super(BasicBlock, self).__init__()# 在父类初始化的基础上添加新的属性# 卷积层1,传入卷积核数量,卷积核size,步长# 如果stride=1,为避免输出小于输入,设置padding='same',使输入等于输出# 如果stride=2,若输入为32*32,由于卷积核3*3的影响,使输出不等于16*16,这时通过padding=same在输入图像上自动补全,如果输出小于16会自动补成16self.conv1 = layers.Conv2D(filter_num, (3,3), strides=stride, padding='same')# 标准化层batchnormalizeationself.bn1 = layers.BatchNormalization()# relu激活函数层,没有其他参数,可以作为一个函数使用多次。而有参数设置的一些层,只能单独对应使用self.relu = layers.Activation('relu')# 卷积层2,如果上一个卷积层stride=2完成下采样,那么这里的卷积层就不进行下采样了,保持stride=1self.conv2 = layers.Conv2D(filter_num, (3,3), strides=1, padding='same')# 标准化层self.bn2 = layers.BatchNormalization()# identity层需进行维度变换,将原始输入图像和卷积后的图像相匹配# 进行1*1卷积匹配通道数,通过stride匹配图像的sizeself.downsample = Sequential()  # 设置容器# 在容器中添加1*1卷积和步长变换# stride保持和第一个卷积层一致,保证convblock和identityblock能直接相加# 如果第一个卷积层的stride=1时,那么输入和输出的shape保持一致self.downsample.add(layers.Conv2D(filter_num, (1,1), strides=stride))#(2)前向传播# 定义类方法,self为类实例化对象def call(self, inputs, training=None):# 卷积层1,调用初始化后的属性x = self.conv1(inputs)  # 输入原始图像x = self.bn1(x)x = self.relu(x)# 卷积层2x = self.conv2(x)out = self.bn2(x)# identity层,输入的是原始输入图像identity = self.downsample(inputs)# 将convblock和identityblock相加得到最终的残差块的输出结果output = layers.add([out, identity])# 最终结果经过一个非线性函数output = tf.nn.relu(output)# 返回残差块的输出结果return output

2. 叠加多个残差块

上面我们已经成功完成了一个残差块,然而一个残差结构是由多个残差块叠加而成的。下面是放大了的结构图,可见 resnet18 每一个残差结构是由 2 个残差单元组合而成

   

我们定义一个函数 build_resblock 用来组合残差结构。这里需要注意的是,blocks 代表一个残差结构需要堆叠几个残差单元resnet18 和 32 中是2个。看结构图可知,在残差结构中只有第一个残差单元具备下采样改变特征图 size 的能力。因此第一个残差块的步长 stride,需要根据输入来确定。而除第一个以外的残差块都不会改变特征图的size,因此固定步长stride=1每一个残差结构的卷积核个数都是相同的,要通过输入来确定。 

    # 利用单个已定义的残差块,叠加多个残差块# filter_num,代表当前图像的特征图个数# blocks,需要代表堆叠几个残差块# stride,代表当前的步长,等于1def build_resblock(self, filter_num, blocks, strides=1):# 使用Sequential容器装填网络结构res_blocks = Sequential()# 在ResNet类中对BasicBlock类实例化,构成组合关系# ResNet类可调用BasicBlock类中的所有属性和方法# 添加网络层# 第一个残差块有下采样功能,stride可能等于2res_blocks.add(BasicBlock(filter_num, strides))# 每个残差结构中剩余的残差块不具备下采样功能,stride=1for _ in range(1, blocks):# 残差结构中剩余的残差块保持图像的shape不变res_blocks.add(BasicBlock(filter_num, stride=1))# 返回构建的残差结构return res_blocks

3. 构建残差网络

上面我们已经完成了残差块的构建,现在我们需要做的就是将这些残差结构按顺序堆叠在一起就能组建残差网络。

首先我们看初始化函数中的代码。self.stem 是用来处理原始输入图像的,假设原始输入的shape[224, 224, 3],根据网络结构图设置预处理卷积层的各个参数。通过最大池化 layers.MaxPool2D 指定步长为2,将预处理卷积层的特征图的size减半 

接下去就可以根据每个残差结构的配置参数,第一个残差结构 self.layer1 由图可知,没有进行下采样,因此步长 stride=1,第一个残差结构中的卷积核个数统一是64个,每个残差结构由2个残差单元组成 layer_dims=[2,2,2,2],初始化时都是调用的上面定义的残差结构函数 build_resblock。

第二个残差结构 self.layer2 由图可知,第一个残差块进行了下采样,因此,要指定步长 strides=2,特征图的 size 减半,特征图的个数统一都是128。同理其他两个残差结构。

    

最后将残差层的输出结果经过全局平均池化后放入全连接层,得出分类结果。 layers.GlobalAveragePooling2D() 在通道维度上对w和h维度求平均值。将特征图的shape从 [b, w, h, c] 变成 [b, 1, 1, c] 

完成对所有层的初始化 __init__ 之后,在 call() 方法中定义层与层之间的前向传播的方法。

# 定义子类ResNet,继承父类keras.Model
class ResNet(keras.Model):#(1)初始化# layer_dims=[2,2,2,2],resnet18包含4个残差结构res_blocks,每个残差结构中有2个残差块# num_classes 代表最终的输出的分类数def __init__(self, layer_dims, num_classes=1000):  # 调用父类的初始化方法super(ResNet, self).__init__(self)# 分配属性# 原始图像输入的预处理卷积和池化self.stem = Sequential([layers.Conv2D(64, (7,7), strides=(2,2), padding='same'),  # 3*3卷积提取特征layers.BatchNormalization(),      # 标准化 layers.Activation('relu'),        # 激活函数layers.MaxPool2D(pool_size=(3,3), strides=(2,2), padding='same')])  # 最大池化,输入图像的size减半# 创建4个残差结构,layer_dims=[2,2,2,2]self.layer1 = self.build_resblock(64, layer_dims[0])  # 第一个残差结构指定64个卷积核,包含2个残差块self.layer2 = self.build_resblock(128, layer_dims[1], strides=2)  # 第二个残差结构128个卷积核,包含2个残差块,步长为2,图像的size减半self.layer3 = self.build_resblock(256, layer_dims[2], strides=2)self.layer4 = self.build_resblock(512, layer_dims[3], strides=2)# 全局平均池化,不管卷积层输出的长和宽是多少,在channel维度上将所有的长和宽加起来取均值# [b, w, h, c] ==> [b,c]self.avgpool = layers.GlobalAveragePooling2D()# 全连接层用于图像分类self.fc = layers.Dense(num_classes)#(2)定义前向传播的类方法def call(self, inputs, training=None):# 原始输入经过预处理卷积层x = self.stem(inputs)# 经过4个残差结构x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)# 输出层x = self.avgpool(x)  # 输出shape[b,c] --> [None, 512]x = self.fc(x)       # 输出[b,1000]# 返回分类结果return x

4. 打印查看网络结构

resnet18 和 resnet34 的差别就在每个残差结构所包含的残差块的个数不同,因此,只需稍作修改修改。我们再看一下这张网络结构表。

resnet18 网络的每个残差层的残差块个数都是2,因此设置参数 ResNet([2, 2, 2, 2]) ,即可返回网络结构。同理,只需要指定 resnet34 网络中每个残差层的残差块个数 ResNet([3, 4, 6, 3]) 。返回得到了输出层的结果,这时只需设置网络输入层的输入维度 model.build(),那么整个网络就构建完了。

# 构造resnet18,传入参数
def resnet18():# 实例化网络结构,一共有4个残差结构,每个残差结构由2个残差块组成return ResNet([2, 2, 2, 2]) # 构造resnet34,传入参数
def resnet34():# 实例化,一共有4个残差结构,每个残差结构有如下个数的残差单元return ResNet([3, 4, 6, 3])        # 主函数
def main():# 构造网络resnet18model18 = resnet18() # 确定输入层model18.build(input_shape=(None,224,224,3))# 查看网络结构model18.summary()# 构造网络resnet34model34 = resnet34()# 确定输入层model34.build(input_shape=(None,224,224,3))# 查看网络结构model34.summary()    if __name__ == '__main__':main()

利用 model.summary() 查看网络具体的结构,其中第一个 multiple 的值等于 [None, 512]第二个 multiple 的值等于 [None, 1000]。下表中,layer 是 sequential 的 Output Shape 代表每一个残差层的输出 shape

resnet18 网络结构如下:

_________________________________________________________________Layer (type)                Output Shape              Param #   
=================================================================sequential (Sequential)     (None, 56, 56, 64)        9728      sequential_1 (Sequential)   (None, 56, 56, 64)        157056    sequential_4 (Sequential)   (None, 28, 28, 128)       543488    sequential_7 (Sequential)   (None, 14, 14, 256)       2168320   sequential_10 (Sequential)  (None, 7, 7, 512)         8662016   global_average_pooling2d (G  multiple                 0         lobalAveragePooling2D)                                          dense (Dense)               multiple                  513000    =================================================================
Total params: 12,053,608
Trainable params: 12,045,800
Non-trainable params: 7,808
_________________________________________________________________

resnet34 网络结构如下:

_________________________________________________________________Layer (type)                Output Shape              Param #   
=================================================================sequential_13 (Sequential)  (None, 56, 56, 64)        9728      sequential_14 (Sequential)  (None, 56, 56, 64)        235584    sequential_18 (Sequential)  (None, 28, 28, 128)       1168896   sequential_23 (Sequential)  (None, 14, 14, 256)       7160320   sequential_30 (Sequential)  (None, 7, 7, 512)         13648384  global_average_pooling2d_1   multiple                 0         (GlobalAveragePooling2D)                                        dense_1 (Dense)             multiple                  513000    =================================================================
Total params: 22,735,912
Trainable params: 22,720,680
Non-trainable params: 15,232
_________________________________________________________________

完整代码:

# 类方法写resnet18、34 
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers, Sequential# Basic Bolck 残差块
# x--> 卷积 --> bn --> relu --> 卷积 --> bn --> 输出 
# |---------------Identity(短接)----------------|# 定义子类,一个残差块
class BasicBlock(layers.Layer):  # 继承父类的方法和属性#(2)子类初始化# filter_num 代表传入卷积核数量,将输入图像的通道数变成残差块的规定的通道数# stride 代表步长,默认为1,代表不对输入图片的size采样,如果不做padding,得到的图像的size就略小,做padding后输入和输出的size保持一致# strdie=2时,代表二分采样,输出的size只有输入size的一半def __init__(self, filter_num, stride=1):# 继承父类的初始化方法,# super()中的第一个参数是子类名称,第二个是子类的实例化对象super(BasicBlock, self).__init__()# 在父类初始化的基础上添加新的属性# 卷积层1,传入卷积核数量,卷积核size,步长# 如果stride=1,为避免输出小于输入,设置padding='same',使输入等于输出# 如果stride=2,若输入为32*32,由于卷积核3*3的影响,使输出不等于16*16,这时通过padding=same在输入图像上自动补全,如果输出小于16会自动补成16self.conv1 = layers.Conv2D(filter_num, (3,3), strides=stride, padding='same')# 标准化层batchnormalizeationself.bn1 = layers.BatchNormalization()# relu激活函数层,没有其他参数,可以作为一个函数使用多次。而有参数设置的一些层,只能单独对应使用self.relu = layers.Activation('relu')# 卷积层2,如果上一个卷积层stride=2完成下采样,那么这里的卷积层就不进行下采样了,保持stride=1self.conv2 = layers.Conv2D(filter_num, (3,3), strides=1, padding='same')# 标准化层self.bn2 = layers.BatchNormalization()# identity层需进行维度变换,将原始输入图像和卷积后的图像相匹配# 进行1*1卷积匹配通道数,通过stride匹配图像的sizeself.downsample = Sequential()  # 设置容器# 在容器中添加1*1卷积和步长变换# stride保持和第一个卷积层一致,保证convblock和identityblock能直接相加# 如果第一个卷积层的stride=1时,那么输入和输出的shape保持一致self.downsample.add(layers.Conv2D(filter_num, (1,1), strides=stride))#(2)前向传播# 定义类方法,self为类实例化对象def call(self, inputs, training=None):# 卷积层1,调用初始化后的属性x = self.conv1(inputs)  # 输入原始图像x = self.bn1(x)x = self.relu(x)# 卷积层2x = self.conv2(x)out = self.bn2(x)# identity层,输入的是原始输入图像identity = self.downsample(inputs)# 将convblock和identityblock相加得到最终的残差块的输出结果output = layers.add([out, identity])# 最终结果经过一个非线性函数output = tf.nn.relu(output)# 返回残差块的输出结果return output#(3)多个残差块叠加
# 定义子类ResNet,继承父类keras.Model
class ResNet(keras.Model):# 初始化# layer_dims=[2,2,2,2],resnet18包含4个残差结构res_blocks,每个残差结构中有2个残差块# num_classes 代表最终的输出的分类数def __init__(self, layer_dims, num_classes=1000):  # 调用父类的初始化方法super(ResNet, self).__init__(self)# 分配属性# 原始图像输入的预处理卷积和池化self.stem = Sequential([layers.Conv2D(64, (7,7), strides=(2,2), padding='same'),  # 3*3卷积提取特征layers.BatchNormalization(),      # 标准化 layers.Activation('relu'),        # 激活函数layers.MaxPool2D(pool_size=(3,3), strides=(2,2), padding='same')])  # 最大池化,输入图像的size减半# 创建4个残差结构,layer_dims=[2,2,2,2]self.layer1 = self.build_resblock(64, layer_dims[0])  # 第一个残差结构指定64个卷积核,包含2个残差块self.layer2 = self.build_resblock(128, layer_dims[1], strides=2)  # 第二个残差结构128个卷积核,包含2个残差块,步长为2,图像的size减半self.layer3 = self.build_resblock(256, layer_dims[2], strides=2)self.layer4 = self.build_resblock(512, layer_dims[3], strides=2)# 全局平均池化,不管卷积层输出的长和宽是多少,在channel维度上将所有的长和宽加起来取均值# [b, w, h, c] ==> [b,c]self.avgpool = layers.GlobalAveragePooling2D()# 全连接层用于图像分类self.fc = layers.Dense(num_classes)# 定义前向传播的类方法def call(self, inputs, training=None):# 原始输入经过预处理卷积层x = self.stem(inputs)# 经过4个残差结构x = self.layer1(x)x = self.layer2(x)x = self.layer3(x)x = self.layer4(x)# 输出层x = self.avgpool(x)  # 输出shape[b,c]  x = self.fc(x)       # 输出[b,1000]# 返回分类结果return x# 利用单个已定义的残差块,叠加多个残差块# filter_num,代表当前图像的特征图个数# blocks,需要代表堆叠几个残差块# stride,代表当前的步长,等于1def build_resblock(self, filter_num, blocks, strides=1):# 使用Sequential容器装填网络结构res_blocks = Sequential()# 在ResNet类中对BasicBlock类实例化,构成组合关系# ResNet类可调用BasicBlock类中的所有属性和方法# 添加网络层# 第一个残差块有下采样功能,stride可能等于2res_blocks.add(BasicBlock(filter_num, strides))# 每个残差结构中剩余的残差块不具备下采样功能,stride=1for _ in range(1, blocks):# 残差结构中剩余的残差块保持图像的shape不变res_blocks.add(BasicBlock(filter_num, stride=1))# 返回构建的残差结构return res_blocks# 构造resnet18,传入参数
def resnet18():# 实例化网络结构,一共有4个残差结构,每个残差结构由2个残差块组成return ResNet([2, 2, 2, 2]) # 构造resnet34,传入参数
def resnet34():# 实例化,一共有4个残差结构,每个残差结构有如下个数的残差单元return ResNet([3, 4, 6, 3])        # 主函数
def main():# 构造网络resnet18model18 = resnet18() # 确定输入层model18.build(input_shape=(None,224,224,3))# 查看网络结构model18.summary()# 构造网络resnet34model34 = resnet34()# 确定输入层model34.build(input_shape=(None,224,224,3))# 查看网络结构model34.summary()    if __name__ == '__main__':main()

http://chatgpt.dhexx.cn/article/qJ4DsUPK.shtml

相关文章

Resnet 18网络模型

1. 残差网络:(Resnet) 残差块: 让我们聚焦于神经网络局部:如图左侧所示,假设我们的原始输入为x,而希望学出的理想映射为f(x)(作为上方激活函数的输入)。左图虚线框中…

【ResNet】Pytorch从零构建ResNet18

Pytorch从零构建ResNet 第一章 从零构建ResNet18 第二章 从零构建ResNet50 文章目录 Pytorch从零构建ResNet前言一、ResNet是什么?1. 残差学习2. ResNet具体结构 二、ResNet分步骤实现三、完整例子测试总结 前言 ResNet 目前是应用很广的网络基础框架,所…

HTML+CSS 简单的顶部导航栏菜单制作

导航栏的制作: 技术要求: CSSHTML各类标签 实现目的: 制作导航栏菜单 代码分析: 基本样式清除无序列原点删除下划线删除文字默认居中a标签设置块级元素伪类选择器对a状态修饰 分步实现: 分三栏布局:…

WEB前端(7)—— 简单的 HTML+CSS 导航栏案例

适合每个新手的导航栏&#xff1a; 代码与运行效果如图&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>导航栏</title><style type"text/css">ul{/*设置导航栏的框框*/margin: 30px…

CSS — 导航栏篇(一)

Navigation Bar Navigation Bar 是什么&#xff1f;这就是每个网站都会有的导航栏&#xff0c;本文将会带你接触导航栏的世界。首先我们需要了解导航栏的作用——它能快速帮助用户进行需求选择。一个清晰的导航栏能让用户第一时间了解网站的基本模块功能&#xff0c;而且作为网…

CSS + HTML导航栏效果

今天写了一个导航栏&#xff0c;需要的效果如下&#xff1a; 实现的代码思路如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>导航栏</title><style type"text/css&qu…

html+css创建侧边导航栏

效果&#xff1a; 代码&#xff1a; .left{position: fixed;width: 250px;height: 2000px;background-color: rgb(100, 93, 93);float: left;text-align: center; } .nav a{display: block;width: 247px;height: 70px;background-color: rgb(100, 93, 93);color: rgb(254, 254…

HTML5+CSS3制作底部导航栏

目录 前言 一、底部导航栏示例图 二、HTML框架 1.一号盒子 2.二号盒子 总结 ​​​​​​ 前言 在日常的网上冲浪中&#xff0c;我们常常在网页最底部&#xff0c;看到一大堆链接&#xff0c;非常整齐&#xff0c;一目了然&#xff0c;那么是如何实现的呢&#xff1f;..…

网页制作之侧边导航栏(只用HTML实现)

话不多说&#xff0c;上代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" con…

CSS【导航栏】

导航栏链接列表 作为标准的HTML基础一个导航栏是必须的。在我们的例子中我们将建立一个标准的HTML列表导航栏。导航条基本上是一个链接列表&#xff0c;所以使用 <ul> 和 <li>元素非常有意义&#xff1a; <!DOCTYPE html><html><head><meta…

html中网页导航栏设置

以下内容是摘抄博客&#xff1a;https://www.runoob.com/css/css-navbar.html 设计导航窗口在左侧的显示如下&#xff1a; 代码部分则如下&#xff1a; <!DOCTYPE html> <html> <head> <meta charset"utf-8"> <title>菜鸟教程(runoo…

html左侧导航栏右侧显示内容

效果图 代码 复制下来直接运行就可以 <!doctype html> <html lang "en"><head><meta charset "UTF-8"><meta name "viewport"content "widthdevice-width, user-scalableno, initial-scale1.0, maximum-s…

导航栏的HTML的布局方式

1.利用浮动完成布局 以小米导航栏为例 <style>* {padding: 0;margin: 0;/* 通配符全选&#xff0c;取消内外边距的小缝隙 // 不建议使用通配符 */}header {width: 100%;background-color: #333333;/* 设置背景 */}div {width: 1226px;height: 40px;margin: auto;/* 设…

html里制作简单导航栏

今天简单的做了一下网页里的导航栏。 效果如下&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html> <head><meta charset"utf-8"><title>实验3</title><style type"text/css">ul{/*设置导航栏的框框*/margi…

html中关于侧边导航栏和导航栏的编写

侧边导航栏 <style>.box{width: 50px;height: 50px;background-color: #483957;transition: width .5s,background-color .2s;}.box:hover{background-color: #004FCB;width: 200px;cursor: pointer;}.a1{position: fixed;right: 40px;top: 200px;float: right;}</st…

Html顶部导航栏实现

顶部导航nav栏实现&#xff08;包括一级菜单&#xff0c;二级菜单&#xff09; 实现效果&#xff1a; 代码如下~ Html部分&#xff1a; <!doctype html> <html> <head> <meta charset"utf-8"> <title>顶部导航栏</title> <…

CSS+HTML 顶部导航栏实现

导航栏的实现、固定顶部导航栏、二级菜单实现 效果图&#xff1a; 2018/11/16更新&#xff1a; 最近在使用这个导航栏的时候&#xff0c;发现页面在放大和缩小的情况下&#xff0c;导航栏的布局和显示都有些小问题&#xff0c;所以重新改了一下css部分的代码&#xff0c;重新贴…

导航栏html代码

效果如下 html 代码 <!DOCTYPE html> <html><head><meta charset"utf-8" /><title>导航制作</title><link rel"stylesheet" href"css/style.css"><link rel"stylesheet" href"css…

html+css实现页面顶部导航栏

最终效果如下&#xff1a; 接下来&#xff0c;我将从html和css两大部分&#xff0c;逐步为您讲解制作过程 目录 Html 实现布局 创建父栏目 创建子栏目 插入外部样式表&#xff0c;为接下来的css编辑做准备 Css 实现样式 排布文本&#xff0c;设置背景 交互效果的实现 …

HTML侧边导航栏

HTML侧边导航栏 简介&#xff1a;本文用最简洁的语言&#xff0c;来教会读者&#xff0c;如果用htmlcss来制作&#xff0c;侧边导航栏&#xff0c;本案例以手机商城中的部分为例子来制作。 第一步&#xff1a;构建框架 <body><!-- 首先确定导航栏中的内容 每个内容…