DCN v1 可变形卷积v1解析(修正篇)

news2024/10/5 19:12:35

在两年前的这篇文章Deformable Convolution(可变形卷积)代码解析(有错误,修改中)中,当时对可变形卷积进行了代码解读,后来被网友指出其中的解释是错的,里面引用的keras版本的代码实现也是错的,后来查看了一些其他人的文章,其中正确和错误的解释都掺杂在一起,不少人和我之前的错误理解是一样的,一直不敢确定所以文章一直没有修正,现在应该是搞懂了,在这里对当时的错误解析进行纠正。

这里讲解的代码实现来自于https://github.com/4uiiurz1/pytorch-deform-conv-v2,其中当modulation=True时,就是v2,否则就是v1,这里只考虑v1。

可变形卷积的流程就是相比于普通的卷积,多学了一层偏移offset,偏移是通过单独的一层卷积学习到的,然后将学到的偏移offset与原始的输入特征进行相加,因为偏移有可能是小数,因此通过双线性插值得到偏移后的特征值,然后再经过原本的那层普通卷积就得到了最终的输出。

之前的错误理解:对于输入shape为(b, c, w, h)的feature map,经过一层卷积后学习到的偏移feature map的shape为(b, 2, w, h),相当于输入特征图上每个像素位置都学习x,y两个方向的偏移,然后将这个偏移特征图与原始输入进行相加,(注意并不是与原始输入特征图直接相加,而是与原始特征图上每个像素的坐标进行相加),这样就得到了原始每个位置偏移后的坐标,然后进行bilinear插值得到偏移后坐标处的值,这里还要注意的是通道共享偏移,因此插值后的特征图和原始输入的shape一样都是(b, c, w, h),然后再经过原本的普通卷积层就得到了可变形卷积层的最终输出。

上面错误的地方在于:偏移特征图的shape应该是(b, N*2, w, h),其中N是原来的普通卷积的大小,比如如果普通卷积是一个3x3的卷积,这里N=3x3=9。这是因为可变形卷积中变形的是卷积核的形状,普通的3x3卷积如下图(a)所示,是一个规则的3x3方格,且采样点之间的间隔为1。(b),(c),(d)是变形后的3x3卷积,9个采样点由绿点变成了蓝点。对于一个(b, c_in, w, h)的输入特征图,假设原始卷积为3x3,padding=1,stride=1,输出特征图shape为(b, c_out, w, h),即特征图的宽高不变。这里卷积窗口沿输入spatial维度进行滑动时总共滑动了w*h个位置,当卷积为可变形卷积时,卷积核每滑动到一个位置处,原始的3x3方格采样都变成了偏移后的位置,因为卷积核有9个采样位置,每个又有x,y两个方向的偏移,因此偏移输出特征图的shape应该为(b, 18, w, h)。

总结如下

  1. 可变形卷积学习的是卷积核的变形采样能力。比如假设目标物体是一个菱形的形状,此时卷积核的形状是一个菱形可能会比原始的方格形状能更好的提取目标的特征。
  2. 因为卷积核是在输入特征图上进行滑动采样,然后与窗口内对应的输入特征进行加权求和得到输出。因此可以将卷积核形状的偏移等价为输入特征图上采样位置的偏移,实际上也是这么做的。

代码解析

完整实现如下所示,假设输入x.shape=(1, 64, 5, 5)。

import torch
from torch import nn


class DeformConv2d(nn.Module):
    def __init__(self, inc, outc, kernel_size=3, padding=1, stride=1, bias=None, modulation=False):
        """
        Args:
            modulation (bool, optional): If True, Modulated Defomable Convolution (Deformable ConvNets v2).
        """
        super(DeformConv2d, self).__init__()
        self.kernel_size = kernel_size
        self.padding = padding
        self.stride = stride
        self.zero_padding = nn.ZeroPad2d(padding)
        self.conv = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)

        self.p_conv = nn.Conv2d(inc, 2*kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)
        nn.init.constant_(self.p_conv.weight, 0)
        self.p_conv.register_backward_hook(self._set_lr)

        self.modulation = modulation
        if modulation:
            self.m_conv = nn.Conv2d(inc, kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)
            nn.init.constant_(self.m_conv.weight, 0)
            self.m_conv.register_backward_hook(self._set_lr)

    @staticmethod
    def _set_lr(module, grad_input, grad_output):
        grad_input = (grad_input[i] * 0.1 for i in range(len(grad_input)))
        grad_output = (grad_output[i] * 0.1 for i in range(len(grad_output)))

    def forward(self, x):  # (1,64,5,5)
        offset = self.p_conv(x)  # (1, 18, 5, 5)
        if self.modulation:
            m = torch.sigmoid(self.m_conv(x))

        dtype = offset.data.type()
        ks = self.kernel_size  # 3
        N = offset.size(1) // 2  # 9

        if self.padding:
            x = self.zero_padding(x)  # (1, 64, 5, 5) -> (1, 64, 7, 7)

            # (b, 2N, h, w)
        p = self._get_p(offset, dtype)
        # print(p.shape)
        # print(p)

        # (b, h, w, 2N)
        p = p.contiguous().permute(0, 2, 3, 1)

        q_lt = p.detach().floor()
        q_rb = q_lt + 1

        q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2)-1), torch.clamp(q_lt[..., N:], 0, x.size(3)-1)], dim=-1).long()  # (1,5,5,18)
        q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2)-1), torch.clamp(q_rb[..., N:], 0, x.size(3)-1)], dim=-1).long()  # (1,5,5,18)
        q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)  # (1,5,5,18)
        q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)  # (1,5,5,18)

        # clip p
        p = torch.cat([torch.clamp(p[..., :N], 0, x.size(2)-1), torch.clamp(p[..., N:], 0, x.size(3)-1)], dim=-1)  # (1,5,5,18)

        # bilinear kernel (b, h, w, N)
        g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))
        g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))
        g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))
        g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))

        # (b, c, h, w, N)
        x_q_lt = self._get_x_q(x, q_lt, N)
        x_q_rb = self._get_x_q(x, q_rb, N)
        x_q_lb = self._get_x_q(x, q_lb, N)
        x_q_rt = self._get_x_q(x, q_rt, N)

        # (b, c, h, w, N)
        x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \
        g_rb.unsqueeze(dim=1) * x_q_rb + \
        g_lb.unsqueeze(dim=1) * x_q_lb + \
        g_rt.unsqueeze(dim=1) * x_q_rt

        # modulation
        if self.modulation:
        m = m.contiguous().permute(0, 2, 3, 1)
        m = m.unsqueeze(dim=1)
        m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)
        x_offset *= m

        x_offset = self._reshape_x_offset(x_offset, ks)
        out = self.conv(x_offset)

        return out

        def _get_p_n(self, N, dtype):
        p_n_x, p_n_y = torch.meshgrid(
        torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1),
        torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1))
        # (2N, 1)
        p_n = torch.cat([torch.flatten(p_n_x), torch.flatten(p_n_y)], 0)
        p_n = p_n.view(1, 2*N, 1, 1).type(dtype)

        return p_n

        def _get_p_0(self, h, w, N, dtype):
        p_0_x, p_0_y = torch.meshgrid(
        torch.arange(1, h*self.stride+1, self.stride),
        torch.arange(1, w*self.stride+1, self.stride))
        p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)
        p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)

        return p_0

        def _get_p(self, offset, dtype):
        N, h, w = offset.size(1)//2, offset.size(2), offset.size(3)  # 9,5,5

        # (1, 2N, 1, 1)
        p_n = self._get_p_n(N, dtype)  # 3x3卷积内9个点相对中心点(0,0)的偏移坐标
        # tensor([[[[-1.]],
        #
        #          [[-1.]],
        #
        #          [[-1.]],
        #
        #          [[ 0.]],
        #
        #          [[ 0.]],
        #
        #          [[ 0.]],
        #
        #          [[ 1.]],
        #
        #          [[ 1.]],
        #
        #          [[ 1.]],
        #
        #          [[-1.]],
        #
        #          [[ 0.]],
        #
        #          [[ 1.]],
        #
        #          [[-1.]],
        #
        #          [[ 0.]],
        #
        #          [[ 1.]],
        #
        #          [[-1.]],
        #
        #          [[ 0.]],
        #
        #          [[ 1.]]]])
        # (1, 2N, h, w)
        p_0 = self._get_p_0(h, w, N, dtype)  # 输入特征图上的每个像素点的原始坐标
        # tensor([[[[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 1., 1., 1., 1.],
        #           [2., 2., 2., 2., 2.],
        #           [3., 3., 3., 3., 3.],
        #           [4., 4., 4., 4., 4.],
        #           [5., 5., 5., 5., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]],
        #
        #          [[1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.],
        #           [1., 2., 3., 4., 5.]]]])

        p = p_0 + p_n + offset
        # p = p_0 + p_n
        return p

        def _get_x_q(self, x, q, N):
        b, h, w, _ = q.size()
        padded_w = x.size(3)
        c = x.size(1)
        # (b, c, h*w)
        x = x.contiguous().view(b, c, -1)

        # (b, h, w, N)
        index = q[..., :N]*padded_w + q[..., N:]  # offset_x*w + offset_y
        # (b, c, h*w*N)
        index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)

        x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)

        return x_offset

        @staticmethod
        def _reshape_x_offset(x_offset, ks):
        b, c, h, w, N = x_offset.size()
        x_offset = torch.cat([x_offset[..., s:s+ks].contiguous().view(b, c, h, w*ks) for s in range(0, N, ks)], dim=-1)
        x_offset = x_offset.contiguous().view(b, c, h*ks, w*ks)

        return x_offset


        if __name__ == '__main__':
        deformconv2d = DeformConv2d(
        64, 128, kernel_size=3, padding=1, stride=1, bias=None, modulation=False
        )
        _input = torch.ones((1, 64, 5, 5))
        result = deformconv2d(_input)
        print(result.shape)
        print(result)

forward函数中首先通过self.p_conv得到偏移offset,输出shape为(1, 18, 5, 5)。

self.modulation=True时,是v2新增的东西,这里不关心。

p=self.get_p是用来得到偏移后的采样坐标位置的,self.get_p中又包括p_n=self._get_p_n和p_0=self._get_p_0,前者是用来得到原始3x3卷积核内每个位置的坐标的,其中中心当做原点坐标为(0, 0),左上角为(-1, -1),右下角为(1, 1),输出shape为(1, 18, 1, 1)。后者是用来得到卷积核在输入特征图上滑动时,卷积核的中心点相对于输入特征图原点的坐标,输入特征图的原点就是左上角,注意因为原始输入加上了padding=1,因此输入的第一行的y坐标为1,第一列的x坐标为1。最终p=p_0+p_n+offset就得到了卷积核在每个滑动位置处的偏移后的采样坐标,这里的采样坐标是相对于输入特征图左上角原点的,shape=(1, 18, 5, 5)

这里偏移后的坐标是小数,还需要通过双线性插值得到坐标处的值。关于双线性插值可参考Deformable Convolution(可变形卷积)代码解析(有错误,修改中)中的介绍。其中p向下取整就得到了左上角处的坐标q_lt,左上角坐标+1得到右下角坐标q_rb,左上角的x坐标和右下角的y坐标拼接得到左下角坐标q_lb,右下角的x坐标和左上角的y坐标拼接得到右上角坐标q_rt。如下图,x(p)的值由最近的四个整数坐标处的值x(q1)、x(q2)、x(q3)、x(q4)以及距离u、v通过双线性插值计算得到。

t1 = (1-u)*x(q1) + u*x(q2)
t2 = (1-u)*x(q3) + u*x(q4)
x(p) = (1-v)*t1 + v*t2
# x(p) = (1-v)*(1-u)*x(q1) + (1-v)*u*x(q2) + v*(1-u)*x(q3) + v*u*x(q4)

四个点的权重g_lt、g_rb、g_lb、g_rt分别对应上面代码中的(1-v)*(1-u)、v*u、v*(1-u)、(1-v)*u。然后通过self._get_x_q()从输入特征图x中得到四个点处的值,分别对应上图中的x(q1)、x(q2)、x(q3)、x(q4)。然后相乘并求和就得到了偏移后坐标处的值x_offset即上面的x(p)。

最后,x_offset的shape=(1, 64, 5, 5, 9),这里的9即对应原始的3x3卷积滑动到5x5中的每个像素位置处,对应的输入特征图上的9个采样值,然后通过self._reshape_x_offset()将x_offset reshape成(1, 64, 5*3, 5*3),然后再经过原始的那层普通卷积self.conv()就得到了最终输出,需要注意原始卷积的stride由1改成了3。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/137194.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

002、捕鱼和分鱼问题

002、【题目】捕鱼和分鱼问题 捕鱼和分鱼:A、B、C、D、E 五个人在某天夜里合伙去捕鱼, 到第二天凌晨时都疲惫不堪,于是各自找地方睡觉。 日上三杆,A第一个醒来,他将鱼分为五份,把多余的一条鱼 扔掉&…

C语言-数据的存储-整形的存储(8.1)

目录 思维导图: 1.数据类型的基本归类 1.1类型的意义 1.2整形家族 1.3浮点数家族 1.4构造类型 1.5指针类型 1.6空类型 2. 整形在内存中的存储 2.1 原码、反码、补码 2.2 大小端介绍 2.3 练习、巩固、提高 写在最后: 思维导图: 1…

视觉slam中的相机类型

作者:朱金灿 来源:clever101的专栏 为什么大多数人学不会人工智能编程?>>> 顾名思义,视觉 SLAM(又称 vSLAM)使用从相机和其他图像传感器采集的图像。视觉 SLAM 可以使用普通相机(广角…

【UnLua】深入理解 UnLua

【UnLua】深入理解 UnLua 从 UnLua 框架层面讨论真正值得关注的关键点 UnLua 架构UnLua 内存管理UnLua 性能 大纲 UnLua 静态导出UnLua 架构UnLua 内存管理UnLua 性能 静态导出 静态导出,这是标准的 Lua 用法,已经非常完善了,就一种标准…

qemu virtio设备模拟与初始化流程

文章目录VirtIO设备模拟及初始化流程Virtio设备的创建参数解析virtio 设备初始化流程pci_bus_matchpci_match_devicepci_device_probevirtio_pci_proberegister_virtio_devicevirtio_dev_matchvirtio_dev_probe参考VirtIO设备模拟及初始化流程 qemu设备虚拟机化的路线可以概括…

C++之智能指针

文章目录一、为什么需要智能指针?二、智能指针的使用及原理1. RAII2.智能指针的原理3. auto_ptr4. unique_ptr5. shared_ptr6. weak_ptr7.删除器一、为什么需要智能指针? 如果在 div() 输入的 b 0,那么就会抛出一个异常,被 main…

Redis面试题总结

一、Redis概述 1.什么是Redis? Redis是一个key-value存储系统,它支持存储的value类型相对更多,包括string、list、set、zset(sorted set --有序集合)和hash。这些数据结构都支持push/pop、add/remove及取交集并集和…

[程序设计]-基于人工智能博弈树,极大极小(Minimax)搜索算法并使用Alpha-Beta剪枝算法优化实现的可人机博弈的AI智能五子棋游戏。

绪论-五子棋的特点与规则 五子棋是两方之间进行的竞技活动,专用棋盘为15*15,五连子的方向为横、竖、斜;任一方在棋盘上形成横向、竖向、斜向的连续的相同颜色的五个(含五个以上)时即为该方胜利;在棋盘上以…

Intel OneApi Developer Tools

“英特尔OneApi开发人员工具”是一组工具和库,用于为Internet发布的各种处理建筑开发高速应用程序。oneAPI是一个完全开放的编写程序模型,支持具有不同架构的各种制造商。使用此工具,其他开发人员需要为每个架构师使用特定的代码,…

【小程序】视图与逻辑

文章目录页面导航声明式导航编程式导航导航传参页面事件下拉刷新事件上拉触底事件生命周期WXS 脚本wxs 和 JavaScript 的关系基础语法页面导航 页面导航指的是页面之间的相互跳转。例如&#xff0c;浏览器中实现页面导航的方式有如下两种&#xff1a; ① <a> 链接② lo…

前端工程师leetcode算法面试必备-二叉树的构造和遍历

一、前言 上一篇中介绍了如何采用 DFS 和 BFS 的搜索思想去实现二叉树的前序遍历、中序遍历、后序遍历以及分层遍历。 这一节主要介绍 Medium 难度中比较常见的一种题型&#xff1a;根据各种遍历构造二叉树。 二、1008. 先序遍历构造二叉树 返回与给定先序遍历 preorder 相匹…

2022阅读数据分析报告

零、前言 晃晃悠悠,又至年尾。翻阅新的书籍五十有余,得到读书和樊登讲书,累计或许在千余小时,或跑步,或骑行,或徒步,偶或地铁,都做耳旁音。回首年初扶起的flag,细思存量不存质。暂且延续2021年的阅读记录方式1,简单可视化本年阅读数据,收尾第二年的阅读小结。 图1 年…

WeNet开源社区介绍

本文是由张彬彬在第二届SH语音技术研讨会和第七届Kaldi技术交流会上对WeNet开源社区的一些工作上的整理&#xff0c;内容涵盖了 WeNet 的最新进展、新项目WeKws&#xff0c;WeSpeeker和WeTextProcessing的介绍&#xff0c;以及去年发布的两个数据集Opencpop和WenetSpeech在今年…

11矩阵空间、秩1矩阵

矩阵空间 知识概要 ​ 从矩阵空 间谈起&#xff0c;介绍矩阵空间的维数&#xff0c;基等问题。渗透一些微分方程与线性代数之间的 联系&#xff0c;并介绍秩为 1 的矩阵特点。 矩阵空间 对角阵D不是很理解。 &#xff08;1&#xff09;基与维数 再看对角阵 D&#xff0c;明…

Hudi学习03 -- Spark操作hudi(Spark-shell 和 PySpark)

文章目录Spark环境准备Spark-shell 方式启动命令&#xff0c;需要显示指定一些参数插入数据查询数据时间旅行&#xff08;Time Travel Query&#xff09;更新数据增量查询&#xff08;Incremental query&#xff09;删除数据&#xff08;Delete Data&#xff09;覆盖分区数据&a…

阴道菌群——贯穿女性一生

阴道微生物组是一个复杂而动态的微生态系统&#xff0c;在女性月经周期和女性的一生中不断发生波动。 在过去几年中&#xff0c;对阴道微生物群关注随着测序技术的发展和应用逐渐广泛和突出&#xff0c;有关以往传统正常和异常阴道微生物组的知识也发生了变化。培养技术可能不再…

Bandit算法学习[网站优化]01——Multiarmed Bandit 算法引入

Bandit算法学习[网站优化]01——Multiarmed Bandit 算法引入 参考资料 White J. Bandit algorithms for website optimization[M]. " O’Reilly Media, Inc.", 2013.https://github.com/johnmyleswhite/BanditsBookeasy-rl 一、探索与利用&#xff08;exploration…

Next.js i18n国际化实现方案(支持ReactNode类型、可传参)

前言 抛开Next.js框架不谈&#xff0c;想必其他项目也经常会遇到国际化方案&#xff0c;大概逻辑都是差不多的&#xff0c;只是说这次本人碰巧在Next上的项目有这样的需求&#xff0c;并记录下来。 实现思路&#xff1a; 其实不从代码角度上讲的话&#xff0c;无非是引入一个…

【王道操作系统】3.1.6 分页存储(页号、页偏移量等)

分页存储(页号、页偏移量等) 文章目录分页存储(页号、页偏移量等)1.为什么学习分页存储2.基本分页存储管理的思想3.分页存储管理的重要概念4.如何实现地址的转换4.1 如何计算页号和页偏移量4.2 分页存储的逻辑结构4.3 如何知道页面在内存中的起始地址1.为什么学习分页存储 2.基…

Qt扫盲-QSS语法概述

QSS语法概述一、语法规则二、选择器类型三、子控件四、伪态五、冲突解决六、样式层叠七、样式继承八、含命名空间样式设置九、QObject 属性设置概述&#xff1a;QSS也叫Qt样式表&#xff0c;Qt样式表术语和语法规则几乎与HTML CSS的术语和语法规则相同。如果已经了解CSS&#x…