经典目标检测YOLO系列(一)复现YOLOV1(3)正样本的匹配及损失函数的实现

news2024/11/26 23:49:37

经典目标检测YOLO系列(一)复现YOLOV1(3)正样本的匹配及损失函数的实现

之前,我们依据《YOLO目标检测》(ISBN:9787115627094)一书,提出了新的YOLOV1架构,并解决前向推理过程中的两个问题,继续按照此书进行YOLOV1的复现。
经典目标检测YOLO系列(一)YOLOV1的复现(1)总体架构

经典目标检测YOLO系列(一)复现YOLOV1(2)反解边界框及后处理

1、正样本的匹配

1.1 正样本匹配思路

YOLOV1中,正样本的匹配算法很简单,就是目标边界框的中心落到feature map的哪个网格中,哪个网格就是正样本。如下图,黄色网格就是正样本。

在这里插入图片描述

后面会利用pytorch读取VOC数据集:

  • 一批图像数据的维度是 [B, 3, H, W] ,分别是batch size,色彩通道数,图像的高和图像的宽。

  • 标签数据是一个包含 B 个图像的标注数据的python的list变量(如下所示),其中,每个图像的标注数据的list变量又包含了 M 个目标的信息(类别和边界框)。

  • 获得了这一批数据后,图片是可以直接喂到网络里去训练的,但是标签不可以,需要再进行处理一下。

    [
        {
            'boxes':     tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)
            'labels':    tensor([18.]),                       # 标签
            'orig_size': [281, 500]                           # 图片的原始大小
        }, 
        {
            'boxes':      tensor([[  0.,  79., 416., 362.]]), 
            'labels':     tensor([1.]),
            'orig_size': [375, 500]
        }
    ]
    
  • 标签处理主要包括3个部分,

    • 一是将真实框中心所在网格的置信度置为1,其他网格默认为0
    • 二是真实框的标签类别为1,其他类别设置为0
    • 三是真实框的bbox信息。
    # 处理好的shape如下:
    # gt_objectness  
    torch.Size([2, 169, 1])  # 169=13×13
    # gt_classes
    torch.Size([2, 169, 20])
    # gt_bboxes
    torch.Size([2, 169, 4])
    

1.2 具体实现代码

# RT-ODLab/models/detectors/yolov1/matcher.py

import torch
import numpy as np


# YoloV1 正样本制作
class YoloMatcher(object):
    def __init__(self, num_classes):
        self.num_classes = num_classes


    @torch.no_grad()
    def __call__(self, fmp_size, stride, targets):
        """
            fmp_size: (Int) input image size                 用于最终检测的特征图的空间尺寸,即划分网格的尺寸
            stride: (Int) -> stride of YOLOv1 output.        特征图的输出步长
            targets: (Dict) dict{'boxes': [...], 
                                 'labels': [...], 
                                 'orig_size': ...}           一批数据的标签
            targets是List类型的变量,每一个元素都是一个Dict类型,
                                  包含boxes和labels两个key,
                                  对应的value就是【一张图片中的目标框的尺寸】和【类别标签】。
        """
        # prepare
        # 准备一些空变量,后续我们会将正样本的数据存放到其中,比如gt_objectness,其shape就是[B, fmp_h, fmp_w, 1],
        # 其中,B就是batch size,
        #      [fmp_h, fmp_w] 就是特征图尺寸,即网格,
        #      1就是objectness的标签值
        # 所有网格的值都会初始化为0,即负样本或背景,在后续的处理中,我们会一一确定哪些网格是正样本区域。
        bs = len(targets)
        fmp_h, fmp_w = fmp_size
        gt_objectness = np.zeros([bs, fmp_h, fmp_w, 1]) 
        gt_classes = np.zeros([bs, fmp_h, fmp_w, self.num_classes]) 
        gt_bboxes = np.zeros([bs, fmp_h, fmp_w, 4])

        # 第一层for循环遍历每一张图像的标签
        for batch_index in range(bs):
            # targets_per_image是python的Dict类型
            targets_per_image = targets[batch_index]
            # [N,]  N表示一个图像中有N个目标对象
            tgt_cls = targets_per_image["labels"].numpy()
            # [N, 4]
            tgt_box = targets_per_image['boxes'].numpy()

            # 第二层for循环遍历这张图像标签的每一个目标数据
            for gt_box, gt_label in zip(tgt_box, tgt_cls):
                x1, y1, x2, y2 = gt_box
                # xyxy -> cxcywh
                xc, yc = (x2 + x1) * 0.5, (y2 + y1) * 0.5
                bw, bh = x2 - x1, y2 - y1

                # check
                if bw < 1. or bh < 1.:
                    continue    

                # grid 计算这个目标框中心点所在的网格坐标
                xs_c = xc / stride
                ys_c = yc / stride
                grid_x = int(xs_c)
                grid_y = int(ys_c)

                if grid_x < fmp_w and grid_y < fmp_h:
                    # objectness标签,采用0,1离散值
                    gt_objectness[batch_index, grid_y, grid_x] = 1.0
                    # classification标签,采用one-hot格式
                    cls_ont_hot = np.zeros(self.num_classes)
                    cls_ont_hot[int(gt_label)] = 1.0
                    gt_classes[batch_index, grid_y, grid_x] = cls_ont_hot
                    # box标签,采用目标框的坐标值
                    gt_bboxes[batch_index, grid_y, grid_x] = np.array([x1, y1, x2, y2])

        # [B, M, C]
        gt_objectness = gt_objectness.reshape(bs, -1, 1)
        gt_classes = gt_classes.reshape(bs, -1, self.num_classes)
        gt_bboxes = gt_bboxes.reshape(bs, -1, 4)

        # to tensor
        gt_objectness = torch.from_numpy(gt_objectness).float()
        gt_classes = torch.from_numpy(gt_classes).float()
        gt_bboxes = torch.from_numpy(gt_bboxes).float()

        return gt_objectness, gt_classes, gt_bboxes


if __name__ == '__main__':
    matcher = YoloMatcher(num_classes=20)
    targets = [
    {
        'boxes':     torch.tensor([[ 29., 230., 148., 321.]]),  # bbox的坐标(xmin, ymin, xmax, ymax)
        'labels':    torch.tensor([18.]),                       # 标签
        'orig_size': [281, 500]                                 # 图片的原始大小
    },
    {
        'boxes':      torch.tensor([[  0.,  79., 416., 362.]]),
        'labels':     torch.tensor([1.]),
        'orig_size': [375, 500]
    }
]
    gt_objectness, gt_classes, gt_bboxes = matcher(fmp_size=(13, 13),stride=32, targets=targets )
    print(gt_objectness.shape)
    print(gt_classes.shape)
    print(gt_bboxes.shape)

关键代码解释:

  • 对于bbox标签,我们没有去计算中心点偏移量和宽高的log值,而是直接赋予了原始的坐标值。这是因为后续在计算损失的时候,我们将会采用当下流行的GIoU损失,届时会计算预测的边界框坐标值和真实的目标框坐标值之间的GIoU。
  • 之前,已经给出了我们的YOLOv1在结算坐标时用到的公式,尽管没有直接给出中心点偏移量和log处理后的宽高值的标签,但在回归时,我们已经用sigmoid和exp约束了模型的预测的偏移量。因此,在训练时,模型仍旧会学习到我们希望他们能学习的正确形式,即预测的偏移量在sigmoid和exp处理后会是合理的值。
  • 两层for循环全部执行完毕后,准备好的空变量中就已经存好了正样本的标签。此前,我们已经将模型的预测都从[B, C, H, W]的格式reshape成了的[B, M, C] 的格式,即将空间的二维尺寸拉平了,为了方便后续的计算,我们也对标签数据做这样的处理,也得到对应的[B, M, C]的格式。最后,我们将这些标签数据都转换成torch.Tensor类型输出即可。
  • 以上就是训练阶段制作正样本的方法,对于某次训练迭代所给的一批标签,经过YoloMatcher类的处理后,我们得到了包含objectness标签、classification标签、bbox标签的变量:gt_objectness、gt_classes、gt_bboxes 。下面,我们就可以编写计算训练的损失的代码。

2、损失函数的实现

2.1 损失函数的实现

这里修改损失函数,将YOLOV1原本的MSE loss,分类分支替换为BCE loss,回归分支替换为GIou loss。

  • 对于objectness损失,所有的正样本和负样本都要参与进来计算
  • 对于classification损失,我们只计算正样本处的这部分损失
  • 对于bbox损失,同样只取出正样本处的预测和标签,然后计算损失
# RT-ODLab/models/detectors/yolov1/loss.py

import torch
import torch.nn.functional as F
from .matcher import YoloMatcher
from utils.box_ops import get_ious
from utils.distributed_utils import get_world_size, is_dist_avail_and_initialized


class Criterion(object):
    def __init__(self, cfg, device, num_classes=80):
        self.cfg = cfg
        self.device = device
        self.num_classes = num_classes
        self.loss_obj_weight = cfg['loss_obj_weight']
        self.loss_cls_weight = cfg['loss_cls_weight']
        self.loss_box_weight = cfg['loss_box_weight']

        # matcher
        self.matcher = YoloMatcher(num_classes=num_classes)


    def loss_objectness(self, pred_obj, gt_obj):
        # 此函数内部会自动做数值稳定版本的sigmoid操作
        # 因此,输入给该函数的预测值不需要预先做sigmoid函数处理,这也就是为什么在此前搭建的YOLOv1模型中的forward函数中看不到对objectness预测和classification预测做sigmoid处理,
        # 当然,在推理时,我们还是要这么手动做的,这一点也能够在YOLOv1模型的inference函数中看到。
        loss_obj = F.binary_cross_entropy_with_logits(pred_obj, gt_obj, reduction='none')

        return loss_obj
    

    def loss_classes(self, pred_cls, gt_label):
        loss_cls = F.binary_cross_entropy_with_logits(pred_cls, gt_label, reduction='none')

        return loss_cls


    def loss_bboxes(self, pred_box, gt_box):
        # regression loss
        ious = get_ious(pred_box,
                        gt_box,
                        box_mode="xyxy",
                        iou_type='giou')
        loss_box = 1.0 - ious

        return loss_box


    def __call__(self, outputs, targets, epoch=0):
        device = outputs['pred_cls'][0].device
        stride = outputs['stride']
        fmp_size = outputs['fmp_size']
        (
            gt_objectness, 
            gt_classes, 
            gt_bboxes,
            ) = self.matcher(fmp_size=fmp_size, 
                             stride=stride, 
                             targets=targets)
   
        # List[B, M, C] -> [B, M, C] -> [BM, C]
        # 为了方便后续的计算,将预测和标签的shape都从[B, M, C]调整成[BM, C],
        # 这一步没有任何数学意义,仅仅是出于计算的方便。
        pred_obj = outputs['pred_obj'].view(-1)                     # [BM,]
        pred_cls = outputs['pred_cls'].view(-1, self.num_classes)   # [BM, C]
        pred_box = outputs['pred_box'].view(-1, 4)                  # [BM, 4]
       
        
        gt_objectness = gt_objectness.view(-1).to(device).float()               # [BM,]
        gt_classes = gt_classes.view(-1, self.num_classes).to(device).float()   # [BM, C]
        gt_bboxes = gt_bboxes.view(-1, 4).to(device).float()                    # [BM, 4]

        pos_masks = (gt_objectness > 0)
        num_fgs = pos_masks.sum()         # 正样本的数量

        if is_dist_avail_and_initialized():
            torch.distributed.all_reduce(num_fgs)
        # 考虑到我们可能会用到多张GPU,因此我们需要将所有GPU上的正样本数量num_fgs做个平均。    
        num_fgs = (num_fgs / get_world_size()).clamp(1.0)

        # obj loss
        # objectness损失,由于这一损失是全局操作,即所有的正样本和负样本都要参与进来,因此没有特殊的操作,直接计算即可,然后做归一化。
        loss_obj = self.loss_objectness(pred_obj, gt_objectness)
        loss_obj = loss_obj.sum() / num_fgs

        # cls loss
        # 对于classification损失,我们只计算正样本处的这部分损失
        # 因此,我们需要先使用先前得到的pos_masks取出正样本处的预测和标签,然后再去计算损失和归一化。
        pred_cls_pos = pred_cls[pos_masks]
        gt_classes_pos = gt_classes[pos_masks]
        loss_cls = self.loss_classes(pred_cls_pos, gt_classes_pos)
        loss_cls = loss_cls.sum() / num_fgs

        # box loss
        # 对于bbox损失,操作基本同上,取出正样本处的预测和标签,然后计算损失,最后再做一次归一化。
        pred_box_pos = pred_box[pos_masks]
        gt_bboxes_pos = gt_bboxes[pos_masks]
        loss_box = self.loss_bboxes(pred_box_pos, gt_bboxes_pos)
        loss_box = loss_box.sum() / num_fgs
        
        # total loss
        losses = self.loss_obj_weight * loss_obj + \
                 self.loss_cls_weight * loss_cls + \
                 self.loss_box_weight * loss_box

        # 最后,将所有的损失加权求和,存放在一个Dict变量里去,输出即可
        loss_dict = dict(
                loss_obj = loss_obj,
                loss_cls = loss_cls,
                loss_box = loss_box,
                losses = losses
        )

        return loss_dict

2.2 GIou loss

损失函数的实现比较简单,这里重点介绍下GIou loss:

MSE作为损失函数的缺点:

  • 原版YOLOV1中使用MSE作为损失函数,之后Fast R-CNN提出了Smooth L1的损失函数,它们的共同点都是使用两个角点,四个坐标作为计算损失函数的变量,我们称之为ln-norm算法。

    • 绿色框是ground truth,黑色框是预测bounding box。我们假设预测框的左下角是固定的,只要右上角在以ground truth为圆心的圆周上这些预测框都有相同的 ln 损失值,但是很明显它们的检测效果的差距是非常大的。与之对比的是IoU和GIoU则在这几个不同的检测框下拥有不同的值,比较真实的反应了检测效果的优劣。

    • 因此 ln -norm损失函数不和检测结果强相关。

    • 在这里插入图片描述

IOU作为损失函数:

  • 从上图中我们可以看出IoU损失要比 ln 损失更能反应检测效果的优劣,IoU损失函数可以表示为式:

L I o U = 1 − ∣ A ⋂ B ∣ ∣ A ⋃ B ∣ L_{IoU}=1-\frac{|A \bigcap B|}{|A \bigcup B|} LIoU=1ABAB

  • IOU作为损失函数的特点:

    • 尺度不变性:IoU反应的是两个检测框交集和并集之间的比例,因此和检测框的大小无关。而 ln 则是和尺度相关的,对于相同损失值的大目标和小目标,大目标的检测效果要优于小目标,因此IoU损失对于小目标的检测也是有帮助的;

    • IoU是一个距离:这个距离是指评估两个矩形框之间的一个指标,这个指标具有distance的一切特性,包括对称性,非负性,同一性,三角不等性。

  • IoU损失的最大问题是当两个物体没有互相覆盖时,损失值都会变成1,而不同的不覆盖情况明显也反应了检测框的优劣,如下图所示。可以看出当ground truth(黑色框)和预测框(绿色框)没有交集时,IOU的值都是0,而GIoU则拥有不同的值,而且和检测效果成正相关。

    • 在这里插入图片描述

GIOU作为损失函数:

  • GIoU损失拥有IoU损失的所有优点,但是也具有IoU损失不具有的一些特性。

    • GIoU也就有尺度不变性;
    • GIoU也是一个距离,因此拥有对称性,非负性,同一性和三角不等性;
    • GIoU是IoU的下界,即 GIoU(A,B)≤IoU(A,B) ,且当A和B的距离越接近,GIoU和IoU的值越接近 ;
    • GIoU的值域是 −1 到 1 。当 A 和 B 完美重合时, GIoU(A,B)=1 ;当 A 和 B 的距离特别远,此时它们的闭包趋近于无穷大,此时 IoU(A,B)=0 , 因此GIoU(A,B)=-1
  • GIoU的目标相当于在损失函数中加入了一个ground truth和预测框构成的闭包的惩罚,它的惩罚项是闭包减去两个框的并集后的面积在闭包中的比例越小越好

  • 如下所示,闭包是红色虚线的矩形,我们要最小化阴影部分的面积除以闭包的面积。

    • 在这里插入图片描述
  • 计算公式如下:先计算两个框的最小闭包区域面积 Ac (通俗理解:同时包含了预测框和真实框的最小框的面积),再计算出IoU,再计算闭包区域中不属于两个框的区域占闭包区域的比重,最后用IoU减去这个比重得到GIoU。

G I o U = I o U − ∣ A c − U ∣ ∣ A c ∣ G_{IoU}=IoU-\frac{|A_c - U|}{|A_c|} GIoU=IoUAcAcU

  • GIoU损失优化的是当两个矩形框没有重叠时候的情况,而当两个矩形框的位置非常接近时,GIoU损失和IoU损失的值是非常接近的,因此在某些场景下使用两个损失的模型效果应该比较接近,但是GIoU应该具有更快的收敛速度。
  • 从GIoU的性质中我们可以看出GIoU在两个矩形没有重叠时,它的优化目标是最小化两个矩形的闭包或是增大预测框的面积,但是第二个目标并不是十分直接。

当然还有别的损失函数,可以参考:

IoU、GIoU、DIoU、CIoU损失函数的那点事儿 - 知乎 (zhihu.com)

GIoU的实现:

import numpy as np
import torch

def get_giou(bboxes1, bboxes2):
    """
    计算GIOU值
    :param bboxes1: 预测框
    :param bboxes2: 真实框
    :return:
    """
    eps = torch.finfo(torch.float32).eps
    bboxes1_area = (bboxes1[..., 2] - bboxes1[..., 0]).clamp_(min=0) \
                   * (bboxes1[..., 3] - bboxes1[..., 1]).clamp_(min=0)   # 所有预测框的面积
    bboxes2_area = (bboxes2[..., 2] - bboxes2[..., 0]).clamp_(min=0) \
                   * (bboxes2[..., 3] - bboxes2[..., 1]).clamp_(min=0)   # 所有真实框的面积

    w_intersect = (
                    torch.min(bboxes1[..., 2], bboxes2[..., 2])       # 相交区域的w
                       - torch.max(bboxes1[..., 0], bboxes2[..., 0])
                   ).clamp_(min=0)
    h_intersect = (
                    torch.min(bboxes1[..., 3], bboxes2[..., 3])
                       - torch.max(bboxes1[..., 1], bboxes2[..., 1])   # 相交区域的h
                   ).clamp_(min=0)

    area_intersect = w_intersect * h_intersect                 # 相交区域面积
    area_union = bboxes2_area + bboxes1_area - area_intersect  # 两个区域的并集
    ious = area_intersect / area_union.clamp(min=eps)          # 计算预测框和真实框之间的IOU值

    g_w_intersect = torch.max(bboxes1[..., 2], bboxes2[..., 2]) - torch.min(bboxes1[..., 0], bboxes2[..., 0])  # 两个区域并集的w
    g_h_intersect = torch.max(bboxes1[..., 3], bboxes2[..., 3]) - torch.min(bboxes1[..., 1], bboxes2[..., 1])   # 两个区域并集的h
    ac_uion = g_w_intersect * g_h_intersect    # Ac
    gious = ious - (ac_uion - area_union) / ac_uion.clamp(min=eps)
    return gious




if __name__ == '__main__':
    pred_box = torch.tensor(np.asarray([
        [1, 1, 3, 3.2],
        [1, 1, 4, 4]
    ]) * 100)

    gt_box = torch.tensor(np.asarray([
        [2, 2, 4.4, 4.5],
        [1, 1, 4, 4]
    ]) * 100)

    print(get_giou(bboxes1=pred_box, bboxes2=gt_box))

现在,我们已经搭建好了模型,也写好了标签分配和计算损的代码,下一步即可准备开始训练我们的模型。

不过至今为止,我们都还没有详细讲数据一环,包括数据读取、数据预处理和数据增强等十分重要的操作。因此,在正式开始训练我们的模型之前,还需要进行数据操作。

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

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

相关文章

数据结构OJ实验8-赫夫曼树编码及应用

A. DS二叉树--赫夫曼树的构建与编码 题目描述 给定n个权值&#xff0c;根据这些权值构造huffman树&#xff0c;并进行huffman编码 大家参考课本算法6.12为主&#xff0c;注意数组访问是从位置1开始 要求&#xff1a;赫夫曼的构建中&#xff0c;默认左孩子权值不大于右孩子权…

webRTC实时通信demo

参考文档&#xff1a; https://www.jianshu.com/p/f439ce5cc0be https://www.w3cschool.cn/socket demo流程示意图&#xff08;用户A向用户B推送视频&#xff09;&#xff1a; #mermaid-svg-0KZaDQ5DBl28zjmZ {font-family:"trebuchet ms",verdana,arial,sans-seri…

JavaWeb——前端之HTMLCSS

学习视频链接&#xff1a;https://www.bilibili.com/video/BV1m84y1w7Tb/?spm_id_from333.999.0.0 一、Web开发 1. 概述 能通过浏览器访问的网站 2. Web网站的开发模式——主流是前后端分离 二、前端Web开发 1. 初识 前端编写的代码通过浏览器进行解析和渲染得到我们看到…

elasticsearch+Kibana

什么是es(elasticsearch) Elasticsearch是一个开源的分布式搜索和分析引擎&#xff0c;它构建在Apache Lucene搜索引擎库之上。它提供了一个分布式多用户能力的实时搜索和分析引擎&#xff0c;能够处理大规模的数据。Elasticsearch被广泛用于构建全文搜索、日志分析、实时应用…

灸哥问答:软件架构在软件研发中的作用

软件架构在软件开发中扮演着至关重要的角色。我们在软件研发的过程中&#xff0c;类比于建造一座公寓楼&#xff0c;而软件架构就像是盖楼之前的设计图纸&#xff0c;如果没有设计图纸就直接盖楼&#xff0c;可想而知带来的后果是什么。我对软件架构的作用表现总结如下&#xf…

iOS问题记录 - iOS 17通过NSUserDefaults设置UserAgent无效(续)

文章目录 前言开发环境问题描述问题分析1. 准备源码2. 定位源码3. 对比源码4. 分析总结 解决方案补充内容1. UserAgent的组成2. UserAgent的设置优先级 最后 前言 在上篇文章中对该问题做了一些判断和猜测&#xff0c;并给出了解决方案。不过&#xff0c;美中不足的是没有进一…

十四:爬虫-Redis基础

1、背景 随着互联网大数据时代的来临&#xff0c;传统的关系型数据库已经不能满足中大型网站日益增长的访问量和数据量。这个时候就需要一种能够快速存取数据的组件来缓解数据库服务I/O的压力&#xff0c;来解决系统性能上的瓶颈。 2、redis是什么 Redis 全称 Remote Dictio…

C/C++面向对象(OOP)编程-回调函数详解(回调函数、C/C++异步回调、函数指针)

本文主要介绍回调函数的使用&#xff0c;包括函数指针、异步回调编程、主要通过详细的例子来指导在异步编程和事件编程中如何使用回调函数来实现。 &#x1f3ac;个人简介&#xff1a;一个全栈工程师的升级之路&#xff01; &#x1f4cb;个人专栏&#xff1a;C/C精进之路 &…

【Spring实战】16 Profile

文章目录 1. 定义2. 使用2.1 定义 Profile2.2 激活 Profile 3. 演示3.1 properties文件3.2 打印日志3.3 启动服务&验证3.4 修改 active3.5 重启服务&验证 4. 应用场景4.1 数据库配置4.2 日志配置 5. 代码详细总结 Spring 框架提供了一种强大的机制&#xff0c;允许在不…

图像分割实战-系列教程9:U2NET显著性检测实战1

&#x1f341;&#x1f341;&#x1f341;图像分割实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Pycharm中进行 本篇文章配套的代码资源已经上传 U2NET显著性检测实战1 1、任务概述

第7课 利用FFmpeg将摄像头画面与麦克风数据合成后推送到rtmp服务器

上节课我们已经拿到了摄像头数据和麦克风数据&#xff0c;这节课我们来看一下如何将二者合并起来推送到rtmp服务器。推送音视频合成流到rtmp服务器地址的流程如下&#xff1a; 1.创建输出流 //初始化输出流上下文 avformat_alloc_output_context2(&outFormatCtx, NULL, &…

Java EE Servlet之Cookie 和 Session

文章目录 1. Cookie 和 Session1.1 Cookie1.2 理解会话机制 (Session)1.2.1 核心方法 2. 用户登录2.1 准备工作2.2 登录页面2.3 写一个 Servlet 处理上述登录请求2.4 实现登录后的主页 3. 总结 1. Cookie 和 Session 1.1 Cookie cookie 是 http 请求 header 中的一个属性 浏…

AI 工具探索(二)

我参加了 奇想星球 与 Datawhale 举办的 【AI办公 X 财务】第一期&#xff0c;现在这是第二次打卡&#xff0c;也即自由探索&#xff0c;我选择 Modelscope 的 Agent 探索&#xff0c;并用gpts创作助理对比&#xff01; 最近想学学小红书的运营方法&#xff0c;选择了 小红书I…

【微服务】1.虚拟机配置

创建虚拟机选经典&#xff0c;其他配置同其他讲解文档 特殊注意 如果要自己设置IP地址&#xff0c;修改/etc/sysconfig/network-scripts/ 编辑ifcfg-ens33需改ip地址 #开机加载网络配置启动网络服务 ONBOOT"yes" #分配ip的协议 none static :不自动分配&#xff0c…

axios的使用及说明

目录 1.说明 2.直接使用 3.封装使用 4.注意 1.说明 官网&#xff1a;Axios 实例 | Axios中文文档 | Axios中文网 Axios 是一个基于 promise 网络请求库&#xff0c;作用于node.js 和浏览器中。 它是 isomorphic 的(即同一套代码可以运行在浏览器和node.js中)。在服务端它使…

FL Studio 21最新版本for mac 21.2.2.3740中文解锁版2024最新图文安装教程

FL Studio 21最新版本for mac 21.2.0.3740中文解锁版是最新强大的音乐制作工具。它可以与所有类型的音乐一起创作出令人惊叹的音乐。它提供了一个非常简单且用户友好的集成开发环境&#xff08;IDE&#xff09;来工作。这个完整的音乐工作站是由比利时公司 Image-Line 开发的。…

redis容灾的方案设计

背景 今年各个大厂的机房事故频繁&#xff0c;其中关键组件Redis是重灾区&#xff0c;本文就来看下怎么做Redis的多机房容灾 Redis多机房容灾方案 1.首先最最直观的是直接利用Redis内部的主从数据同步来进行灾备&#xff0c;但是由于Redis内部的主从实现对机房间的网络延迟等…

2024 React 后台系统 搭建学习看这一篇就够了(1)

年初&#xff0c;自己想写一篇关于 React 实战后台项目的 课程文章&#xff0c;也算是对自己 2023的前端学习做一个系统性总结&#xff0c;方便后续查阅&#xff0c;也方便自己浏览&#xff0c;还能增加自己的文笔 网上很多平台都不太稳定&#xff0c;所以用了阿里的语雀&…

声明式导航传参详情

1 动态路由传参 路由规则path ->/article/:aid 导航链接 <router-link to"/article/1">查看第一篇文章</router-link> 组件获取参数: this.$route.params.aid 如果想要所有的值&#xff0c;就用this. $route. params 注意&#xff1a;这两个必须匹配…

实战入门 K8s剩下三个模块

1.Label Label是kubernetes系统中的一个重要概念。它的作用就是在资源上添加标识&#xff0c;用来对它们进行区分和选择。 Label的特点&#xff1a; 一个Label会以key/value键值对的形式附加到各种对象上&#xff0c;如Node、Pod、Service等等 一个资源对象可以定义任意数量…