一文搞懂卷积网络之四(空间注意力Non-local)

本文介绍CNN注意力机制开篇之作Non-local,其解决传统CNN长距离特征提取不足问题,通过学习特征图点间相关性实现全局联系。文中实现了Embedded Gaussian等三种模块结构,在Cifar10上与ResNet18基线对比实验,发现BottleNeck结构和模块位置对效果影响大,不同版本Non-local性能有差异。

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

一文搞懂卷积网络之四(空间注意力non-local) - 创想鸟

一、闲聊

上个项目里,我们介绍了灰太狼和他的亲戚们……啊,不,是 ResNet 和它的变体们,包括 ResNet 本尊、ResNetV2、ResNeXt 等。其实,当时还出了一个号称“灰太狼最强亲戚”的 ResNeSt,这个家伙涨点的绝技就是在 ResNet 模型里加入了 Split-Attention 注意力模块(详情可以参考大佬的项目:ResNet最强变体ResNeSt —— 实现篇(Paddle动态图版本))。这个项目我们就来了解CNN的注意力机制,先从CV注意力的开篇之作 Non-local 走起~

二、Non-local 实现空间注意力的原理

在传统的CNN、DNN模型中,卷积层的计算只是将周围的特征加权加和,且一般当前层的计算只依赖前一层的结果,而现在的网络又大多使用1×1、3×3尺寸的小卷积核,对长距离相关特征的提取不足。(… a convolutional operation sums up the weighted input in a local neighborhood, and a recurrent operation at time i is often based only on the current and the latest time steps.)

全连接层虽然连接了相邻层的全部神经元,但只对单个神经元进行了权重学习,并未学习神经元之间的联系。(The non-local operation is also different from a fully-connected (fc) layer. Eq.(1) computes responses based on relationships between different locations, whereas fc uses learned weights. In other words, the relationship between xj and xi is not a function of the input data in fc, unlike in non-local layers.)

Non-local 注意力模块是借鉴了 Non-local 图片滤镜算法(Non-local image processing)、序列化处理的前馈神经网络(Feedforward modeling for sequences)和 自注意力机制(Self-attention)等工作,提出的一种提取特征图全局联系的通用模型结构,着力于学习特征图中的点与点之间的相关程度特征,公式如下:

一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟        

上式中,一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟计算特征图x中代表i,j两个点相关关系的标量。(A pairwise func-tion f computes a scalar (representing relationship such as affinity) between i and all j.)一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟计算的是代表特征图x中j点的值。(The unary function g computes a representation of the input signal at the position j.)最后一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟计算出的特征图所有点之间的响应值通过一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟进行标准化。(The response is normalized by a factor C(x).)

如文章中所说,这种 Non-local 机制是一种通用(generic)的注意力实现方法,所以上式中的一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟可以使用不同的方式实现相关性计算。这就有了通过 Embedded Gaussion、Vanilla Gaussion、Dot product 和 Concatenation 几种方式实现的 Non-local 模块。后面我们会实现其中的前三种结构,并测试其对网络性能的提升作用。

文章地址:https://arxiv.org/abs/1711.07971作者源码地址:https://github.com/facebookresearch/video-nonlocal-net

三、Non-local 结构的实现

文章中对 Non-local 模块的结构总结如下图:

一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟        

如上图所示,先将输入的特征图降维(降到1维)后逐次嵌入(embed)到 theta、phi 和 g 三个向量中。然后,将向量 theta 和向量 phi 的转置相乘,做 softmax 激活后再与向量 g 相乘。最后,再将这个 Non-local 操作包裹一层,这通过一个1×1的卷积核和一个跨层连接实现,以方便嵌入此注意力模块到现有系统中(We wrap the non-local operation in Eq.(1) into a non-local block that can be incorporated into many existing architectures.)。

在实现的过程中还要注意几个地方:

再将降维后的特征图嵌入三个向量中时,可以进行通道缩减(如上图所示缩减比例为一半),然后在最后经过1×1卷积时再升回来。这样类似 BottleNeck 的结构可以节省大约一半参数。(We set the number of channels represented by Wg, Wθ, and Wφ to be half of the number of channels in x. This follows the bottleneck design of [21] and reduces the computation of a block by about a half.)模块最后的1×1卷积后面加了一个 BN 层,这个 BN 层的放大系数(也就是权重参数)全部初始化为 0,以确保此模块的初始状态为恒等映射,使得其可以被插入到使用预训练权重的模型中去。(We add a BN layer right after the last 1×1×1 layer that represents Wz; we do not add BN to other layers in a non-local block. The scale parameter of this BN layer is initialized as zero, following [17]. This ensures that the initial state of the entire non-local block is an identity mapping, so it can be inserted into any pre-trained networks while maintaining its initial behavior.)文章中实现的是视频分类的 Non-local 模块,使用的是 T×H×W 的3D卷积,我们这里实现图片分类的 Non-local 模块时要去掉卷积的 T(时间)维度,采用2D卷积。

实现 Non-local 模块前先做好依赖项导入、参数设置、数据集处理等准备工作:

In [5]

import paddleimport paddle.nn as nnfrom paddle.io import DataLoaderimport numpy as npimport osimport paddle.vision.transforms as Tfrom paddle.vision.datasets import Cifar10import matplotlib.pyplot as plt%matplotlib inlineimport warningswarnings.filterwarnings("ignore", category=Warning) # 过滤报警信息BATCH_SIZE = 32PIC_SIZE = 96EPOCH_NUM = 30CLASS_DIM = 10PLACE = paddle.CPUPlace() # 在cpu上训练# PLACE = paddle.CUDAPlace(0)  # 在gpu上训练# 数据集处理transform = T.Compose([    T.Resize(PIC_SIZE),    T.Transpose(),    T.Normalize([127.5, 127.5, 127.5], [127.5, 127.5, 127.5]),])train_dataset = Cifar10(mode='train', transform=transform)val_dataset = Cifar10(mode='test', transform=transform)train_loader = DataLoader(train_dataset, places=PLACE, shuffle=True, batch_size=BATCH_SIZE, drop_last=True, num_workers=0, use_shared_memory=False)valid_loader = DataLoader(val_dataset, places=PLACE, shuffle=False, batch_size=BATCH_SIZE, drop_last=True, num_workers=0, use_shared_memory=False)def save_show_pics(pics, file_name='tmp', save_path='./output/pics/', save_root_path='./output/'):    if not os.path.exists(save_root_path):        os.makedirs(save_root_path)    if not os.path.exists(save_path):        os.makedirs(save_path)    shape = pics.shape    pic = pics.transpose((0,2,3,1)).reshape([-1,8,PIC_SIZE,PIC_SIZE,3])    pic = np.concatenate(tuple(pic), axis=1)    pic = np.concatenate(tuple(pic), axis=1)    pic = (pic + 1.) / 2.    plt.imsave(save_path+file_name+'.jpg', pic)    # plt.figure(figsize=(8,8), dpi=80)    plt.imshow(pic)    plt.xticks([])    plt.yticks([])    plt.show()test_loader = DataLoader(train_dataset, places=PLACE, shuffle=True, batch_size=BATCH_SIZE, drop_last=True, num_workers=0, use_shared_memory=False)data, label = next(test_loader())save_show_pics(data.numpy())

       

               

1.Embedded Gaussian 实现 Non-local 模块

如以上所描述的,我们实现的就是最常用的用 Embedded Gaussian 实现的 Non-local 模块:

In [ ]

class EmbeddedGaussion(nn.Layer):    def __init__(self, shape):        super(EmbeddedGaussion, self).__init__()        input_dim = shape[1]        self.theta = nn.Conv2D(input_dim, input_dim // 2, 1)        self.phi = nn.Conv2D(input_dim, input_dim // 2, 1)        self.g = nn.Conv2D(input_dim, input_dim // 2, 1)        self.conv = nn.Conv2D(input_dim // 2, input_dim, 1)        self.bn = nn.BatchNorm2D(input_dim, weight_attr=paddle.ParamAttr(initializer=nn.initializer.Constant(0)))    def forward(self, x):        shape = x.shape        theta = paddle.flatten(self.theta(x), start_axis=2, stop_axis=-1)        phi = paddle.flatten(self.phi(x), start_axis=2, stop_axis=-1)        g = paddle.flatten(self.g(x), start_axis=2, stop_axis=-1)        non_local = paddle.matmul(theta, phi, transpose_y=True)        non_local = nn.functional.softmax(non_local)        non_local = paddle.matmul(non_local, g)        non_local = paddle.reshape(non_local, [shape[0], shape[1] // 2, shape[2], shape[3]])        non_local = self.bn(self.conv(non_local))        return non_local + xnl = EmbeddedGaussion([16, 16, 8, 8])x = paddle.to_tensor(np.random.uniform(-1, 1, [16, 16, 8, 8]).astype('float32'))y = nl(x)print(y.shape)

       

[16, 16, 8, 8]

       

2.Vanilla Gaussian 实现 Non-local 模块

Embedded Gaussian 实现的 Non-local 模块如果去掉特征图嵌入向量 theta 和向量 g 的操作,就是普通的用 Vanilla Gaussian 实现的 Non-local 版本了。当然,没有了前面的1×1卷积,通道缩减也就无从谈起了。

In [ ]

class VanillaGaussion(nn.Layer):    def __init__(self, shape):        super(VanillaGaussion, self).__init__()        input_dim = shape[1]        self.g = nn.Conv2D(input_dim, input_dim, 1)        self.conv = nn.Conv2D(input_dim, input_dim, 1)        self.bn = nn.BatchNorm(input_dim)    def forward(self, x):        shape = x.shape        theta = paddle.flatten(x, start_axis=2, stop_axis=-1)        phi = paddle.flatten(x, start_axis=2, stop_axis=-1)        g = paddle.flatten(self.g(x), start_axis=2, stop_axis=-1)        non_local = paddle.matmul(theta, phi, transpose_y=True)        non_local = nn.functional.softmax(non_local)        non_local = paddle.matmul(non_local, g)        non_local = paddle.reshape(non_local, shape)        non_local = self.bn(self.conv(non_local))        return non_local + xnl = VanillaGaussion([16, 16, 8, 8])x = paddle.to_tensor(np.random.uniform(-1, 1, [16, 16, 8, 8]).astype('float32'))y = nl(x)print(y.shape)

       

[16, 16, 8, 8]

       

3.Dot Production 实现 Non-local 模块

Embedded Gaussian 实现 Non-local 的模块如果去掉 SoftMax 激活操作,通过除以 N(N 为特征图的位置数量)进行缩放代替,就是 Dot Production 实现的 Non-local 版本了。

In [ ]

class DotProduction(nn.Layer):    def __init__(self, shape):        super(DotProduction, self).__init__()        input_dim = shape[1]        self.theta = nn.Conv2D(input_dim, input_dim // 2, 1)        self.phi = nn.Conv2D(input_dim, input_dim // 2, 1)        self.g = nn.Conv2D(input_dim, input_dim // 2, 1)        self.conv = nn.Conv2D(input_dim // 2, input_dim, 1)        self.bn = nn.BatchNorm(input_dim)    def forward(self, x):        shape = x.shape        theta = paddle.flatten(self.theta(x), start_axis=2, stop_axis=-1)        phi = paddle.flatten(self.phi(x), start_axis=2, stop_axis=-1)        g = paddle.flatten(self.g(x), start_axis=2, stop_axis=-1)        non_local = paddle.matmul(theta, phi, transpose_y=True)        non_local = non_local / shape[2]        non_local = paddle.matmul(non_local, g)        non_local = paddle.reshape(non_local, [shape[0], shape[1] // 2, shape[2], shape[3]])        non_local = self.bn(self.conv(non_local))        return non_local + xnl = DotProduction([16, 16, 8, 8])x = paddle.to_tensor(np.random.uniform(-1, 1, [16, 16, 8, 8]).astype('float32'))y = nl(x)print(y.shape)

       

[16, 16, 8, 8]

       

四、Non-local 的运行对比效果

下面我们就来实验下刚才实现的三个版本的 Non-local 模块的效果。原文是在视频分类数据集上做的实验,这里我们用 Paddle 内置的 Cifar10 图片分类数据集做下实验。

1.运行 ResNet18 基线版本

在 ResNet18 模型结构上加上一个残差块作为基线版本,后面的 Non-local 模块就替换这个残差块。这样能确认效果的提升来自 Non-Local 结构,而非增加的参数。

In [ ]

class Residual(nn.Layer):    def __init__(self, num_channels, num_filters, use_1x1conv=False, stride=1):        super(Residual, self).__init__()        self.use_1x1conv = use_1x1conv        model = [            nn.Conv2D(num_channels, num_filters, 3, stride=stride, padding=1),            nn.BatchNorm2D(num_filters),            nn.ReLU(),            nn.Conv2D(num_filters, num_filters, 3, stride=1, padding=1),            nn.BatchNorm2D(num_filters),        ]        self.model = nn.Sequential(*model)        if use_1x1conv:            model_1x1 = [nn.Conv2D(num_channels, num_filters, 1, stride=stride)]            self.model_1x1 = nn.Sequential(*model_1x1)    def forward(self, X):        Y = self.model(X)        if self.use_1x1conv:            X = self.model_1x1(X)        return paddle.nn.functional.relu(X + Y)class ResnetBlock(nn.Layer):    def __init__(self, num_channels, num_filters, num_residuals, first_block=False):        super(ResnetBlock, self).__init__()        model = []        for i in range(num_residuals):            if i == 0:                if not first_block:                    model += [Residual(num_channels, num_filters, use_1x1conv=True, stride=2)]                else:                    model += [Residual(num_channels, num_filters)]            else:                model += [Residual(num_filters, num_filters)]        self.model = nn.Sequential(*model)    def forward(self, X):        return self.model(X)class ResNet(nn.Layer):    def __init__(self, num_classes=10):        super(ResNet, self).__init__()        model = [            nn.Conv2D(3, 64, 7, stride=2, padding=3),            nn.BatchNorm2D(64),            nn.ReLU(),            nn.MaxPool2D(kernel_size=3, stride=2, padding=1)        ]        model += [            ResnetBlock(64, 64, 2, first_block=True),            ResnetBlock(64, 128, 2),            # ResnetBlock(128, 256, 2),            ResnetBlock(128, 256, 2 + 1),            ResnetBlock(256, 512, 2)        ]        model += [            nn.AdaptiveAvgPool2D(output_size=1),            nn.Flatten(start_axis=1, stop_axis=-1),            nn.Linear(512, num_classes),        ]        self.model = nn.Sequential(*model)    def forward(self, X):        Y = self.model(X)        return Y# 模型定义model = paddle.Model(ResNet(num_classes=CLASS_DIM))# 设置训练模型所需的optimizer, loss, metricmodel.prepare(    paddle.optimizer.Adam(learning_rate=1e-4, parameters=model.parameters()),    paddle.nn.CrossEntropyLoss(),    paddle.metric.Accuracy(topk=(1, 5)))# 启动训练、评估model.fit(train_loader, valid_loader, epochs=EPOCH_NUM, log_freq=500,     callbacks=paddle.callbacks.VisualDL(log_dir='./log/BLResNet18+1'))

       

The loss value printed in the log is the current step, and the metric is the average value of previous step.Epoch 1/30

       

2.测试加入 Non-local 模块的版本

分别测试用 Embedded Gaussian、Vanilla Gaussian 和 Dot Production 方法实现的 Non-local 模块的效果。

In [ ]

class Residual(nn.Layer):    def __init__(self, num_channels, num_filters, use_1x1conv=False, stride=1):        super(Residual, self).__init__()        self.use_1x1conv = use_1x1conv        model = [            nn.Conv2D(num_channels, num_filters, 3, stride=stride, padding=1),            nn.BatchNorm2D(num_filters),            nn.ReLU(),            nn.Conv2D(num_filters, num_filters, 3, stride=1, padding=1),            nn.BatchNorm2D(num_filters),        ]        self.model = nn.Sequential(*model)        if use_1x1conv:            model_1x1 = [nn.Conv2D(num_channels, num_filters, 1, stride=stride)]            self.model_1x1 = nn.Sequential(*model_1x1)    def forward(self, X):        Y = self.model(X)        if self.use_1x1conv:            X = self.model_1x1(X)        return paddle.nn.functional.relu(X + Y)class ResnetBlock(nn.Layer):    def __init__(self, num_channels, num_filters, num_residuals, first_block=False):        super(ResnetBlock, self).__init__()        model = []        for i in range(num_residuals):            if i == 0:                if not first_block:                    model += [Residual(num_channels, num_filters, use_1x1conv=True, stride=2)]                else:                    model += [Residual(num_channels, num_filters)]            else:                model += [Residual(num_filters, num_filters)]        self.model = nn.Sequential(*model)    def forward(self, X):        return self.model(X)class ResNetNonLocal(nn.Layer):    def __init__(self, num_classes=10):        super(ResNetNonLocal, self).__init__()        model = [            nn.Conv2D(3, 64, 7, stride=2, padding=3),            nn.BatchNorm2D(64),            nn.ReLU(),            nn.MaxPool2D(kernel_size=3, stride=2, padding=1)        ]        model += [            ResnetBlock(64, 64, 2, first_block=True),            ResnetBlock(64, 128, 2),            ResnetBlock(128, 256, 2),            EmbeddedGaussion([BATCH_SIZE, 256, 14, 14]),            # VanillaGaussion([BATCH_SIZE, 256, 14, 14]),            # DotProduction([BATCH_SIZE, 256, 14, 14]),            # # EmbeddedGaussionNoBottleNeck([BATCH_SIZE, 256, 14, 14]),            ResnetBlock(256, 512, 2),        ]        model += [            nn.AdaptiveAvgPool2D(output_size=1),            nn.Flatten(start_axis=1, stop_axis=-1),            nn.Linear(512, num_classes),        ]        self.model = nn.Sequential(*model)    def forward(self, X):        Y = self.model(X)        return Y# 模型定义model = paddle.Model(ResNetNonLocal(num_classes=CLASS_DIM))# 设置训练模型所需的optimizer, loss, metricmodel.prepare(    paddle.optimizer.Adam(learning_rate=1e-4, parameters=model.parameters()),    paddle.nn.CrossEntropyLoss(),    paddle.metric.Accuracy(topk=(1, 5)))# 启动训练、评估model.fit(train_loader, valid_loader, epochs=EPOCH_NUM, log_freq=500,     callbacks=paddle.callbacks.VisualDL(log_dir='./log/EmbeddedGaussion'))# model.fit(train_loader, valid_loader, epochs=EPOCH_NUM, log_freq=500, #     callbacks=paddle.callbacks.VisualDL(log_dir='./log/VanillaGaussion'))# model.fit(train_loader, valid_loader, epochs=EPOCH_NUM, log_freq=500, #     callbacks=paddle.callbacks.VisualDL(log_dir='./log/DotProduction'))

       

The loss value printed in the log is the current step, and the metric is the average value of previous step.Epoch 1/30

       

上面的代码需要运行三次,每次需要注释掉 ResNetNonLocal 类的 forward() 方法里不同版本的 Non-local 模块,并且在 model.fit 写入VisualDL 的 log 文件时用不同的名称。

接下来,我们对比下运行结果的验证集准确率:

1)测试 Vanilla Gaussian 版本 Non-local 模块

一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟 上图中,蓝色线为 ResNet18 加一个残差块的基线版本的验证集准确率曲线,紫色线为加入Vanilla Gaussian 版本 Non-local 模块后模型的验证集准确率曲线。改进的模型准曲率提高了0.3%。

2)测试 Dot Production 版本 Non-local 模块

一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟 蓝色线仍为基线模型准确率,绿线为加入Dot Production 版本 Non-local 模块后模型的准确率。改进的模型准曲率提高了0.6%。

3)测试 Embedded Gaussian 版本 Non-local 模块

最后来测试下“顶配”版本的。 一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟 仍然蓝色线为基线模型数据…我去,这么好的装备怎么出现了这么差的结果,加了 Embedded Gaussian 版本 Non-local 模块的模型精度甚至低于基线模型的精度,这是肿么回事?!&@%

一顿修改猛如虎之后(换模型结构、换数据集、换数据增强、换超参),似乎找到一点儿线索。

4)测试不缩减通道数的 Embedded Gaussian 版本 Non-local 模块

在下面这个 Embedded Gaussian 版本 Non-local 模块中,我们不在1×1卷积上采用类似 BottleNeck 的结构缩减通道数。

In [4]

class EmbeddedGaussionNoBottleNeck(nn.Layer):    def __init__(self, shape):        super(EmbeddedGaussionNoBottleNeck, self).__init__()        input_dim = shape[1]        self.theta = nn.Conv2D(input_dim, input_dim, 1)        self.phi = nn.Conv2D(input_dim, input_dim, 1)        self.g = nn.Conv2D(input_dim, input_dim, 1)        self.conv = nn.Conv2D(input_dim, input_dim, 1)        self.bn = nn.BatchNorm(input_dim)    def forward(self, x):        shape = x.shape        theta = paddle.flatten(self.theta(x), start_axis=2, stop_axis=-1)        phi = paddle.flatten(self.phi(x), start_axis=2, stop_axis=-1)        g = paddle.flatten(self.g(x), start_axis=2, stop_axis=-1)        non_local = paddle.matmul(theta, phi, transpose_y=True)        non_local = nn.functional.softmax(non_local)        non_local = paddle.matmul(non_local, g)        non_local = paddle.reshape(non_local, shape)        non_local = self.bn(self.conv(non_local))        return non_local + xnl = EmbeddedGaussionNoBottleNeck([16, 16, 8, 8])x = paddle.to_tensor(np.random.uniform(-1, 1, [16, 16, 8, 8]).astype('float32'))y = nl(x)print(y.shape)

       

[16, 16, 8, 8]

       

训练后与基线模型对照下: 一文搞懂卷积网络之四(空间注意力Non-local) - 创想鸟 蓝色为基线模型版本曲线。在增加了 Non-local 模块的宽度后,性能和基线版本差不多,虽然没有 Vanilla Gaussian 和 Dot Production 实现的版本提升的精度多,但已经比原来的降低通道数的 Embedded Gaussian 版本好了不少。

五、总结

各种消融实验完毕,总结学习体会。

无论是在 Non-local 结构还是在 ResNet 的残差块中,BottleNeck 结构只适合在深层网络中用于减少参数量。在原版的 ResNet 的结构里,也只有 50 层以上的配置才会使用 BottleNeck 结构。这也解释了在前面项目实验(ResNet一族)中,后面出的一些 ResNet 魔该版本,基本都是在 50 层以上挑战大规模数据集。这些改动都是建立在 BottelNeck 基础上的,所以才用在我们的浅层网络中会效果欠佳。这可能是因为深层的网络中,被激活的神经元更稀疏,才能通过 BottelNeck 使用更窄的网络来减少参数。而在较浅的网络中,网络的宽度对性能影响还是挺大的。Non-local 模块的使用位置是很重要的,在模型的不同层加入注意力模块的效果大相径庭,在不同的数据集上效果也不同(在224×224尺寸的OxfordFlower102 和 CalTech201 数据集上,找这个合适的位置更难,放弃了。我想也许作者当年在视频数据集上做实验也是有原因的吧。),加注意力模块的位置不好反而会使模型性能下降。有的信息说注意力快适合用在前面的层,但是在这个实验里,还是放在后面用来提取更大粒度特征图的位置相关特征时效果更好。

以上就是一文搞懂卷积网络之四(空间注意力Non-local)的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/63414.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2025年11月11日 12:11:29
下一篇 2025年11月11日 12:29:48

相关推荐

  • Uniapp 中如何不拉伸不裁剪地展示图片?

    灵活展示图片:如何不拉伸不裁剪 在界面设计中,常常需要以原尺寸展示用户上传的图片。本文将介绍一种在 uniapp 框架中实现该功能的简单方法。 对于不同尺寸的图片,可以采用以下处理方式: 极端宽高比:撑满屏幕宽度或高度,再等比缩放居中。非极端宽高比:居中显示,若能撑满则撑满。 然而,如果需要不拉伸不…

    2025年12月24日
    400
  • 如何让小说网站控制台显示乱码,同时网页内容正常显示?

    如何在不影响用户界面的情况下实现控制台乱码? 当在小说网站上下载小说时,大家可能会遇到一个问题:网站上的文本在网页内正常显示,但是在控制台中却是乱码。如何实现此类操作,从而在不影响用户界面(UI)的情况下保持控制台乱码呢? 答案在于使用自定义字体。网站可以通过在服务器端配置自定义字体,并通过在客户端…

    2025年12月24日
    800
  • 如何在地图上轻松创建气泡信息框?

    地图上气泡信息框的巧妙生成 地图上气泡信息框是一种常用的交互功能,它简便易用,能够为用户提供额外信息。本文将探讨如何借助地图库的功能轻松创建这一功能。 利用地图库的原生功能 大多数地图库,如高德地图,都提供了现成的信息窗体和右键菜单功能。这些功能可以通过以下途径实现: 高德地图 JS API 参考文…

    2025年12月24日
    400
  • 如何使用 scroll-behavior 属性实现元素scrollLeft变化时的平滑动画?

    如何实现元素scrollleft变化时的平滑动画效果? 在许多网页应用中,滚动容器的水平滚动条(scrollleft)需要频繁使用。为了让滚动动作更加自然,你希望给scrollleft的变化添加动画效果。 解决方案:scroll-behavior 属性 要实现scrollleft变化时的平滑动画效果…

    2025年12月24日
    000
  • 如何为滚动元素添加平滑过渡,使滚动条滑动时更自然流畅?

    给滚动元素平滑过渡 如何在滚动条属性(scrollleft)发生改变时为元素添加平滑的过渡效果? 解决方案:scroll-behavior 属性 为滚动容器设置 scroll-behavior 属性可以实现平滑滚动。 html 代码: click the button to slide right!…

    2025年12月24日
    500
  • 如何选择元素个数不固定的指定类名子元素?

    灵活选择元素个数不固定的指定类名子元素 在网页布局中,有时需要选择特定类名的子元素,但这些元素的数量并不固定。例如,下面这段 html 代码中,activebar 和 item 元素的数量均不固定: *n *n 如果需要选择第一个 item元素,可以使用 css 选择器 :nth-child()。该…

    2025年12月24日
    200
  • 使用 SVG 如何实现自定义宽度、间距和半径的虚线边框?

    使用 svg 实现自定义虚线边框 如何实现一个具有自定义宽度、间距和半径的虚线边框是一个常见的前端开发问题。传统的解决方案通常涉及使用 border-image 引入切片图片,但是这种方法存在引入外部资源、性能低下的缺点。 为了避免上述问题,可以使用 svg(可缩放矢量图形)来创建纯代码实现。一种方…

    2025年12月24日
    100
  • 如何让“元素跟随文本高度,而不是撑高父容器?

    如何让 元素跟随文本高度,而不是撑高父容器 在页面布局中,经常遇到父容器高度被子元素撑开的问题。在图例所示的案例中,父容器被较高的图片撑开,而文本的高度没有被考虑。本问答将提供纯css解决方案,让图片跟随文本高度,确保父容器的高度不会被图片影响。 解决方法 为了解决这个问题,需要将图片从文档流中脱离…

    2025年12月24日
    000
  • 为什么 CSS mask 属性未请求指定图片?

    解决 css mask 属性未请求图片的问题 在使用 css mask 属性时,指定了图片地址,但网络面板显示未请求获取该图片,这可能是由于浏览器兼容性问题造成的。 问题 如下代码所示: 立即学习“前端免费学习笔记(深入)”; icon [data-icon=”cloud”] { –icon-cl…

    2025年12月24日
    200
  • 如何利用 CSS 选中激活标签并影响相邻元素的样式?

    如何利用 css 选中激活标签并影响相邻元素? 为了实现激活标签影响相邻元素的样式需求,可以通过 :has 选择器来实现。以下是如何具体操作: 对于激活标签相邻后的元素,可以在 css 中使用以下代码进行设置: li:has(+li.active) { border-radius: 0 0 10px…

    2025年12月24日
    100
  • 如何模拟Windows 10 设置界面中的鼠标悬浮放大效果?

    win10设置界面的鼠标移动显示周边的样式(探照灯效果)的实现方式 在windows设置界面的鼠标悬浮效果中,光标周围会显示一个放大区域。在前端开发中,可以通过多种方式实现类似的效果。 使用css 使用css的transform和box-shadow属性。通过将transform: scale(1.…

    2025年12月24日
    200
  • 为什么我的 Safari 自定义样式表在百度页面上失效了?

    为什么在 Safari 中自定义样式表未能正常工作? 在 Safari 的偏好设置中设置自定义样式表后,您对其进行测试却发现效果不同。在您自己的网页中,样式有效,而在百度页面中却失效。 造成这种情况的原因是,第一个访问的项目使用了文件协议,可以访问本地目录中的图片文件。而第二个访问的百度使用了 ht…

    2025年12月24日
    000
  • 如何用前端实现 Windows 10 设置界面的鼠标移动探照灯效果?

    如何在前端实现 Windows 10 设置界面中的鼠标移动探照灯效果 想要在前端开发中实现 Windows 10 设置界面中类似的鼠标移动探照灯效果,可以通过以下途径: CSS 解决方案 DEMO 1: Windows 10 网格悬停效果:https://codepen.io/tr4553r7/pe…

    2025年12月24日
    000
  • 使用CSS mask属性指定图片URL时,为什么浏览器无法加载图片?

    css mask属性未能加载图片的解决方法 使用css mask属性指定图片url时,如示例中所示: mask: url(“https://api.iconify.design/mdi:apple-icloud.svg”) center / contain no-repeat; 但是,在网络面板中却…

    2025年12月24日
    000
  • 如何用CSS Paint API为网页元素添加时尚的斑马线边框?

    为元素添加时尚的斑马线边框 在网页设计中,有时我们需要添加时尚的边框来提升元素的视觉效果。其中,斑马线边框是一种既醒目又别致的设计元素。 实现斜向斑马线边框 要实现斜向斑马线间隔圆环,我们可以使用css paint api。该api提供了强大的功能,可以让我们在元素上绘制复杂的图形。 立即学习“前端…

    2025年12月24日
    000
  • 图片如何不撑高父容器?

    如何让图片不撑高父容器? 当父容器包含不同高度的子元素时,父容器的高度通常会被最高元素撑开。如果你希望父容器的高度由文本内容撑开,避免图片对其产生影响,可以通过以下 css 解决方法: 绝对定位元素: .child-image { position: absolute; top: 0; left: …

    2025年12月24日
    000
  • CSS 帮助

    我正在尝试将文本附加到棕色框的左侧。我不能。我不知道代码有什么问题。请帮助我。 css .hero { position: relative; bottom: 80px; display: flex; justify-content: left; align-items: start; color:…

    2025年12月24日 好文分享
    200
  • HTML、CSS 和 JavaScript 中的简单侧边栏菜单

    构建一个简单的侧边栏菜单是一个很好的主意,它可以为您的网站添加有价值的功能和令人惊叹的外观。 侧边栏菜单对于客户找到不同项目的方式很有用,而不会让他们觉得自己有太多选择,从而创造了简单性和秩序。 今天,我将分享一个简单的 HTML、CSS 和 JavaScript 源代码来创建一个简单的侧边栏菜单。…

    2025年12月24日
    200
  • 前端代码辅助工具:如何选择最可靠的AI工具?

    前端代码辅助工具:可靠性探讨 对于前端工程师来说,在HTML、CSS和JavaScript开发中借助AI工具是司空见惯的事情。然而,并非所有工具都能提供同等的可靠性。 个性化需求 关于哪个AI工具最可靠,这个问题没有一刀切的答案。每个人的使用习惯和项目需求各不相同。以下是一些影响选择的重要因素: 立…

    2025年12月24日
    300
  • 带有 HTML、CSS 和 JavaScript 工具提示的响应式侧边导航栏

    响应式侧边导航栏不仅有助于改善网站的导航,还可以解决整齐放置链接的问题,从而增强用户体验。通过使用工具提示,可以让用户了解每个链接的功能,包括设计紧凑的情况。 在本教程中,我将解释使用 html、css、javascript 创建带有工具提示的响应式侧栏导航的完整代码。 对于那些一直想要一个干净、简…

    2025年12月24日
    000

发表回复

登录后才能评论
关注微信