轻量级目标检测模型NanoDet-Plus微调、部署(保姆级教学)

news2024/10/6 5:59:03

前言

  • NanoDet-Plus是超快速、高精度的轻量级无锚物体检测模型,github项目文件。可以在移动设备上实时检测。其主要特点是
    • 超轻量:模型文件仅980KB(INT8)1.8MB(FP16)
    • 超快:移动ARM CPU97fps(10.23ms)
    • 高精度:高达34.3 mAP val @0.5:0.95,并且在CPU上仍然实时
    • 训练友好:GPU内存成本比其他模型低得多。 在 GTX1060 6G 上可设置batch_size=80
    • 易于部署:支持各种后端,包括ncnn、MNN 和 OpenVINO。还提供基于ncnn推理框架的Android demo
  • NanoDet是一种FCOS风格的单阶段无锚目标检测模型,使用广义焦点损失作为分类和回归损失。在 NanoDet-Plus中使用了新的标签分配策略,具有简单的分配指导模块(AGM)动态软标签分配器(DSLA),以解决轻量级模型训练中的最佳标签分配问题。还引入了一个名为Ghost-PAN的轻量特征金字塔来增强多层特征融合。这些改进将之前NanoDet在COCO数据集上的检测精度提高了7 mAP

请添加图片描述

  • 关于NanoDet-Plus模型框架的详细介绍可以参考文档,NanoDet模型框架的详细介绍可以参考文档

  • 本文主要讲解如何训练NanoDet-Plus模型,将训练好的模型转换为onnx格式,并使用onnxruntime进行推理

环境配置

  • 本文使用的环境为云GPU,基础镜像选择Python版本3.8,PyTorch版本1.13.1,Cuda版本11.7.0。GPU型号P4-8G
  • 一般云GPU环境会提供学术镜像加速,在使用git克隆项目时记得打开,使用完以后关闭。以下命令均在命令行窗口执行
git clone https://github.com/RangiLyu/nanodet.git
cd nanodet

pip install -r requirements.txt
python setup.py develop
  • 如果上述代码无报错,说明环境配置完成,接下来在项目文件夹下创建文件夹data,用于存放训练数据
mkdir data
  • 将图像文件放置于./data/images下,注释文件放置于./data/ann下,注释文件有两个,分别是trainval.jsontest.json,需要注意的是注释文件格式一定要是``coco`格式,怎么转换可以看我之前写的文章,里面有很详细的讲。

模型训练

配置文件解析

  • 首先我们要找到我们想要微调的模型,打开项目文件夹中README.md文档,可以看到在Benchmarks中有很多选项。
  • 这里我们直接微调表中精度最高的模型NanoDet-Plus-m-1.5x,精度达到34.1 mAP val @0.5:0.95

请添加图片描述

  • 进入config文件夹,可以看到nanodet-plus-m-1.5x_416.yml文件,这就是模型配置文件。

  • 下面我们对配置文件的各部分及参数做详细说明

Saving path

save_dir: workspace/nanodet-plus-m-1.5x_416
  • save_dir为保存日志和模型的位置。如果路径不存在,NanoDet-Plus将创建它。

Model

model:
    arch:
        name: NanoDetPlus
        backbone: xxx
        fpn: xxx
        head: xxx
        aux_head: xxx

大多数检测模型架构可以分为 3 个部分:backbone(主干)、head(任务头)和它们之间的fpn(连接器,例如 FPNBiFPNPAN

Weight averaging

weight_averager:
  name: ExpMovingAverager
  decay: 0.9998
  • NanoDet-Plus支持EMA等权重平均法

Backbone

backbone:
    name: ShuffleNetV2
    model_size: 1.5x
    out_stages: [2,3,4]
    activation: LeakyReLU
  • NanoDet使用ShuffleNetV2作为骨干。可以修改model_size(模型大小)、out_stages(输出特征级别)和activation(激活函数)。
  • 此外,NanoDet-Plus还提供其他轻量级主干网,如GhostNetMobileNetV2。您还可以通过将其导入到nanodet/model/backbone/__init__.py中来添加骨干网络。

FPN

fpn:
    name: GhostPAN
    in_channels: [176, 352, 704]
    out_channels: 128
    kernel_size: 5
    num_extra_level: 1
    use_depthwise: True
    activation: LeakyReLU
  • NanoDet-Plus 使用GhostPAN作为连接器。in_channels:从主干提取的特征图通道列表。out_channels:输出特征图通道。

Head

head:
    name: NanoDetPlusHead
    num_classes: 80
    input_channel: 128
    feat_channels: 128
    stacked_convs: 2
    kernel_size: 5
    strides: [8, 16, 32, 64]
    activation: LeakyReLU
    reg_max: 7
    norm_cfg:
      type: BN
    loss:
      loss_qfl:
        name: QualityFocalLoss
        use_sigmoid: True
        beta: 2.0
        loss_weight: 1.0
      loss_dfl:
        name: DistributionFocalLoss
        loss_weight: 0.25
      loss_bbox:
        name: GIoULoss
        loss_weight: 2.0
  • name: 任务头类名
  • num_classes: 分类数量
  • input_channel:输入特征图通道
  • feat_channels:任务头转换的通道
  • stacked_convs:一个任务头使用多少个转换块
  • kernel_size:卷积核大小
  • strides:每个特征图级别的下采样步长
  • activation:激活函数
  • reg_max:每级lrtb距离的最大值
  • norm_cfg:归一化层设置
  • loss:调整损失函数和权重

Auxiliary head

aux_head:
    name: SimpleConvHead
    num_classes: 80
    input_channel: 256
    feat_channels: 256
    stacked_convs: 4
    strides: [8, 16, 32, 64]
    activation: LeakyReLU
    reg_max: 7
  • Auxiliary head(辅助层)仅在训练期间起作用,参数含义与Head一致

Data

data:
    train:
    	name: CocoDataset
    	img_path: coco/train2017
    	ann_path: coco/annotations/instances_train2017.json
    	input_size: [416,416] #[w,h]
    	keep_ratio: False
    	pipeline:
            ...
	val:
        ...
    
  • data中,需要设置训练和验证数据集。name:数据集格式名称。您可以在nanodet/data/dataset中创建自己的数据集格式。 input_size:[宽度,高度],keep_ratio:调整输入大小时是否保持原始图像比例,pipeline:数据预处理和增强管道

Device

device:
    gpu_ids: [0]
    workers_per_gpu: 10
    batchsize_per_gpu: 96
    precision: 32
  • gpu_idsCUDA设备ID,对于多GPU训练,设置为 [0、1、2…]
  • workers_per_gpu:每个GPU有多少个数据加载器进程
  • batchsize_per_gpu:每个GPU在一个batch中处理的图像数量
  • precision:训练精度,默认值 32 表示FP32训练。设置为 16 可启用AMP训练

Schedule

schedule:
  optimizer:
    name: AdamW
    lr: 0.001
    weight_decay: 0.05
  warmup:
    name: linear
    steps: 500
    ratio: 0.0001
  total_epochs: 300
  lr_schedule:
    name: CosineAnnealingLR
    T_max: 300
    eta_min: 0.00005
  val_intervals: 10
 grad_clip: 35
  • optimizer:支持pytorch提供的所有优化器。

  • lr:遵循论文Accurate, Large Minibatch SGD: Training ImageNet in 1 Hour中的线性缩放规则根据batch_size调整lr

  • warmup:训练前预热网络。支持constantexp以及linear三种类型的预热。

  • total_epochs:训练的总epoch

  • lr_schedule:学习率变化策略

  • val_intervals:训练期间评估间隔epoch

  • grad_clip:梯度裁剪

Evaluator

evaluator:
  name: CocoDetectionEvaluator
  save_key: mAP
  • 目前仅支持coco evalsave_key:最佳模型的指标。支持mAPAP50AP75

Class names

  • 用于可视化时标注

修改配置文件

  • nanodet-plus-m-1.5x_416.yml文件中的配置文件复制,然后根据任务需求进行更改,最后将其写入config文件夹中的nanodet-plus-m_416_animal.yml文件中
  • 主要修改的地方有:model模块下headnum_classesaux_headnum_classesdata模块下的图片路径、注释文件路径。device模块下的batchsize_per_gpuschedule模块下的lr(根据batch_size进行线性缩放)。total_epochsepoch数。log模块下的interval日志输出iter间隔。class_names分类标签。
  • 以下代码在jupyter notebook中运行,若直接创建新文件,只要两个"""中间的内容
config_animal = """
save_dir: workspace/nanodet-plus-m-1.5x_416
model:
  weight_averager:
    name: ExpMovingAverager
    decay: 0.9998
  arch:
    name: NanoDetPlus
    detach_epoch: 10
    backbone:
      name: ShuffleNetV2
      model_size: 1.5x
      out_stages: [2,3,4]
      activation: LeakyReLU
    fpn:
      name: GhostPAN
      in_channels: [176, 352, 704]
      out_channels: 128
      kernel_size: 5
      num_extra_level: 1
      use_depthwise: True
      activation: LeakyReLU
    head:
      name: NanoDetPlusHead
      # 分类数,需要修改
      num_classes: 2
      input_channel: 128
      feat_channels: 128
      stacked_convs: 2
      kernel_size: 5
      strides: [8, 16, 32, 64]
      activation: LeakyReLU
      reg_max: 7
      norm_cfg:
        type: BN
      loss:
        loss_qfl:
          name: QualityFocalLoss
          use_sigmoid: True
          beta: 2.0
          loss_weight: 1.0
        loss_dfl:
          name: DistributionFocalLoss
          loss_weight: 0.25
        loss_bbox:
          name: GIoULoss
          loss_weight: 2.0
    # Auxiliary head, only use in training time.
    aux_head:
      name: SimpleConvHead
      # 分类数需要更改
      num_classes: 2
      input_channel: 256
      feat_channels: 256
      stacked_convs: 4
      strides: [8, 16, 32, 64]
      activation: LeakyReLU
      reg_max: 7
data:
  train:
    name: CocoDataset
    # 训练集图片路径、注释文件路径
    img_path: data/images
    ann_path: data/annotations/trainval.json
    input_size: [416,416] #[w,h]
    keep_ratio: False
    pipeline:
      perspective: 0.0
      scale: [0.6, 1.4]
      stretch: [[0.8, 1.2], [0.8, 1.2]]
      rotation: 0
      shear: 0
      translate: 0.2
      flip: 0.5
      brightness: 0.2
      contrast: [0.6, 1.4]
      saturation: [0.5, 1.2]
      normalize: [[103.53, 116.28, 123.675], [57.375, 57.12, 58.395]]
  val:
    name: CocoDataset
    # 验证集图片路径、注释文件路径
    img_path: data/images
    ann_path: data/annotations/test.json
    input_size: [416,416] #[w,h]
    keep_ratio: False
    pipeline:
      normalize: [[103.53, 116.28, 123.675], [57.375, 57.12, 58.395]]
device:
  gpu_ids: [0]
  workers_per_gpu: 2
  batchsize_per_gpu: 96
  precision: 32 # set to 16 to use AMP training
schedule:
  optimizer:
    name: AdamW
    # 学习率等比例缩放
    lr: 0.001
    weight_decay: 0.05
  warmup:
  # 学习率预热step
    name: linear
    steps: 500
    ratio: 0.0001
  # 总epochs  
  total_epochs: 300
  lr_schedule:
    name: CosineAnnealingLR
    T_max: 300
    eta_min: 0.00005
  # 验证间隔epoch  
  val_intervals: 5
# 梯度修剪
grad_clip: 35
evaluator:
  name: CocoDetectionEvaluator
  # 最优指标
  save_key: mAP
# 日志输出iter间隔
log:
  interval: 20

# 分类名,需要与标准文件中的id顺序一直
class_names: ['cat', 'dog']
"""

#------------------------------------------------------
config=f'./config/nanodet-plus-m_416_animal.yml'
with open(config, 'w') as f:
    f.write(config_animal)

启动训练

  • 本节代码均在命令行窗口中运行
python tools/train.py ./config/nanodet-plus-m_416_animal.yml
  • 由于训练日志太长,这里就不展示了,这一步只要不报错,等待训练结束即可。save_dir目录下的model_best文件夹中
  • 训练结束以后可以使用TensorBoard对训练过程进行可视化
cd workspace/nanodet-plus-m-1.5x_416
tensorboard --logdir ./

ONNXRuntime部署

  • 注:本节所有代码均在命令行窗口中运行

模型导出ONNX

  • 导出onnx模型,运行tools/export_onnx.py脚本就可以快速转换,运行完成后会在项目文件夹下生成nanodet.onnx文件。
  • 我们将精度最高的模型权重进行转换
python tools/export_onnx.py --cfg_path ./config/nanodet-plus-m_416_animal.yml --model_path workspace/nanodet-plus-m-1.5x_416/model_best/nanodet_model_best.pth

ONNX模型推理

  • 因为模型转换为ONNX后输出结果发生了很大变化,并且有点不那么好理解,这里放一篇博客,里面对输出结果做了很详细的说明,用opencv部署nanodet目标检测
  • 该博主在github上发布了使用OpenCV部署NanoDet-Plus,包含C++Python两个版本的程序。使用ONNXRuntime部署NanoDet-Plus,包含C++Python两个版本的程序。项目地址
  • 打开项目主页,下载onnxruntime/main.py文件到项目文件夹下,打开文件,修改部分代码
import cv2
import numpy as np
import argparse
import onnxruntime as ort
import math


class my_nanodet():
    def __init__(self, model_pb_path, prob_threshold=0.4, iou_threshold=0.3):
        self.classes = ['cat', 'dog']
        self.num_classes = len(self.classes)
        self.prob_threshold = prob_threshold
        self.iou_threshold = iou_threshold
        ### normalize: [[103.53, 116.28, 123.675], [57.375, 57.12, 58.395]]
        self.mean = np.array([103.53, 116.28, 123.675], dtype=np.float32).reshape(1, 1, 3)
        self.std = np.array([57.375, 57.12, 58.395], dtype=np.float32).reshape(1, 1, 3)
        so = ort.SessionOptions()
        so.log_severity_level = 3
        self.net = ort.InferenceSession(model_pb_path, so)
        self.input_shape = (self.net.get_inputs()[0].shape[2], self.net.get_inputs()[0].shape[3])
        self.reg_max = int((self.net.get_outputs()[0].shape[-1] - self.num_classes) / 4) - 1
        self.project = np.arange(self.reg_max + 1)
        self.strides = (8, 16, 32, 64)
        self.mlvl_anchors = []
        for i in range(len(self.strides)):
            anchors = self._make_grid(
                (math.ceil(self.input_shape[0] / self.strides[i]), math.ceil(self.input_shape[1] / self.strides[i])),
                self.strides[i])
            self.mlvl_anchors.append(anchors)
        self.keep_ratio = False
    
    def _make_grid(self, featmap_size, stride):
        feat_h, feat_w = featmap_size
        shift_x = np.arange(0, feat_w) * stride
        shift_y = np.arange(0, feat_h) * stride
        xv, yv = np.meshgrid(shift_x, shift_y)
        xv = xv.flatten()
        yv = yv.flatten()
        return np.stack((xv, yv), axis=-1)
        # cx = xv + 0.5 * (stride - 1)
        # cy = yv + 0.5 * (stride - 1)
        # return np.stack((cx, cy), axis=-1)
    
    def softmax(self, x, axis=1):
        x_exp = np.exp(x)
        # 如果是列向量,则axis=0
        x_sum = np.sum(x_exp, axis=axis, keepdims=True)
        s = x_exp / x_sum
        return s
    
    def _normalize(self, img):
        img = img.astype(np.float32)
        # img = (img / 255.0 - self.mean / 255.0) / (self.std / 255.0)
        img = (img - self.mean) / (self.std)
        return img
    
    def resize_image(self, srcimg, keep_ratio=True):
        top, left, newh, neww = 0, 0, self.input_shape[0], self.input_shape[1]
        if keep_ratio and srcimg.shape[0] != srcimg.shape[1]:
            hw_scale = srcimg.shape[0] / srcimg.shape[1]
            if hw_scale > 1:
                newh, neww = self.input_shape[0], int(self.input_shape[1] / hw_scale)
                img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_AREA)
                left = int((self.input_shape[1] - neww) * 0.5)
                img = cv2.copyMakeBorder(img, 0, 0, left, self.input_shape[1] - neww - left, cv2.BORDER_CONSTANT,
                                         value=0)  # add border
            else:
                newh, neww = int(self.input_shape[0] * hw_scale), self.input_shape[1]
                img = cv2.resize(srcimg, (neww, newh), interpolation=cv2.INTER_AREA)
                top = int((self.input_shape[0] - newh) * 0.5)
                img = cv2.copyMakeBorder(img, top, self.input_shape[0] - newh - top, 0, 0, cv2.BORDER_CONSTANT, value=0)
        else:
            img = cv2.resize(srcimg, self.input_shape, interpolation=cv2.INTER_AREA)
        return img, newh, neww, top, left
    
    def post_process(self, preds, scale_factor=1, rescale=False):
        mlvl_bboxes = []
        mlvl_scores = []
        ind = 0
        for stride, anchors in zip(self.strides, self.mlvl_anchors):
            cls_score, bbox_pred = preds[ind:(ind + anchors.shape[0]), :self.num_classes], preds[ind:(ind + anchors.shape[0]), self.num_classes:]
            ind += anchors.shape[0]
            bbox_pred = self.softmax(bbox_pred.reshape(-1, self.reg_max + 1), axis=1)
            # bbox_pred = np.sum(bbox_pred * np.expand_dims(self.project, axis=0), axis=1).reshape((-1, 4))
            bbox_pred = np.dot(bbox_pred, self.project).reshape(-1, 4)
            bbox_pred *= stride
            
            # nms_pre = cfg.get('nms_pre', -1)
            nms_pre = 1000
            if nms_pre > 0 and cls_score.shape[0] > nms_pre:
                max_scores = cls_score.max(axis=1)
                topk_inds = max_scores.argsort()[::-1][0:nms_pre]
                anchors = anchors[topk_inds, :]
                bbox_pred = bbox_pred[topk_inds, :]
                cls_score = cls_score[topk_inds, :]
            
            bboxes = self.distance2bbox(anchors, bbox_pred, max_shape=self.input_shape)
            mlvl_bboxes.append(bboxes)
            mlvl_scores.append(cls_score)
        
        mlvl_bboxes = np.concatenate(mlvl_bboxes, axis=0)
        if rescale:
            mlvl_bboxes /= scale_factor
        mlvl_scores = np.concatenate(mlvl_scores, axis=0)
        
        bboxes_wh = mlvl_bboxes.copy()
        bboxes_wh[:, 2:4] = bboxes_wh[:, 2:4] - bboxes_wh[:, 0:2]  ####xywh
        classIds = np.argmax(mlvl_scores, axis=1)
        confidences = np.max(mlvl_scores, axis=1)  ####max_class_confidence
        
        indices = cv2.dnn.NMSBoxes(bboxes_wh.tolist(), confidences.tolist(), self.prob_threshold,
                                   self.iou_threshold).flatten()
        if len(indices) > 0:
            mlvl_bboxes = mlvl_bboxes[indices]
            confidences = confidences[indices]
            classIds = classIds[indices]
            return mlvl_bboxes, confidences, classIds
        else:
            print('nothing detect')
            return np.array([]), np.array([]), np.array([])
    
    def distance2bbox(self, points, distance, max_shape=None):
        x1 = points[:, 0] - distance[:, 0]
        y1 = points[:, 1] - distance[:, 1]
        x2 = points[:, 0] + distance[:, 2]
        y2 = points[:, 1] + distance[:, 3]
        if max_shape is not None:
            x1 = np.clip(x1, 0, max_shape[1])
            y1 = np.clip(y1, 0, max_shape[0])
            x2 = np.clip(x2, 0, max_shape[1])
            y2 = np.clip(y2, 0, max_shape[0])
        return np.stack([x1, y1, x2, y2], axis=-1)
    
    def detect(self, srcimg):
        img, newh, neww, top, left = self.resize_image(srcimg, keep_ratio=self.keep_ratio)
        img = self._normalize(img)
        blob = np.expand_dims(np.transpose(img, (2, 0, 1)), axis=0)
        
        outs = self.net.run(None, {self.net.get_inputs()[0].name: blob})[0].squeeze(axis=0)
        det_bboxes, det_conf, det_classid = self.post_process(outs)
        
        # results = []
        ratioh, ratiow = srcimg.shape[0] / newh, srcimg.shape[1] / neww
        for i in range(det_bboxes.shape[0]):
            xmin, ymin, xmax, ymax = max(int((det_bboxes[i, 0] - left) * ratiow), 0), max(
                int((det_bboxes[i, 1] - top) * ratioh), 0), min(
                int((det_bboxes[i, 2] - left) * ratiow), srcimg.shape[1]), min(int((det_bboxes[i, 3] - top) * ratioh),
                                                                               srcimg.shape[0])
            # results.append((xmin, ymin, xmax, ymax, self.classes[det_classid[i]], det_conf[i]))
            cv2.rectangle(srcimg, (xmin, ymin), (xmax, ymax), (0, 0, 255), thickness=1)
            print(self.classes[det_classid[i]] + ': ' + str(round(det_conf[i], 3)))
            cv2.putText(srcimg, self.classes[det_classid[i]] + ': ' + str(round(det_conf[i], 3)), (xmin, ymin - 10),
                        cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 0), thickness=1)
        cv2.imwrite('result.jpg', srcimg)
        return srcimg


if __name__ == '__main__':
    parser = argparse.ArgumentParser()
    parser.add_argument('--imgpath', type=str, default='imgs/person.jpg', help="image path")
    parser.add_argument('--modelpath', type=str, default='onnxmodel/nanodet-plus-m_320.onnx', help="onnx filepath")
    parser.add_argument('--confThreshold', default=0.4, type=float, help='class confidence')
    parser.add_argument('--nmsThreshold', default=0.6, type=float, help='nms iou thresh')
    args = parser.parse_args()
    
    srcimg = cv2.imread(args.imgpath)
    net = my_nanodet(args.modelpath, args.classfile, prob_threshold=args.confThreshold, iou_threshold=args.nmsThreshold)
    srcimg = net.detect(srcimg)
  • 修改的地方有:删除了classfile参数,改为直接手动输入self.classes = ['cat', 'dog']。删除modelpath参数中的choices键。因为是在命令行窗口中运行,所以删除cv2.namedWindow操作。取消注释cv2.imwrite('result.jpg', srcimg)推理完成生成可视化图片。
  • 修改完成一定要记得保存文件后再关闭文件,否则修改无效,会报错
  • imgpath参数设定为需要推理的照片,modelpath为我们自己导出的ONNX模型nanodet.onnx
python main.py --imgpath ./data/images/00001.png --modelpath ./workspace/nanodet-plus-m-1.5x_416/model_best/nanodet_model_best.pth
  • 运行完成后会在项目文件夹下生成result.jpg文件

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

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

相关文章

C++内存管理(动态内存开辟)

我们在C语言当中想要使用堆区的空间的时候就需要使用malloc函数进行手动的申请,但是我们在申请的时候需要手动进行计算,经过计算之后还需要进行判空操作,并且还不能进行任意值的初始化。这一切看起来在学习完C当中的动态开辟之前显得很正常&a…

最新版本mac版Idea 激活Jerbel实现热部署

1.环境准备 1.安装docker desktop 客户端创建本地服务 2.创建guid 3.随便准备一个正确格式的邮箱 2.具体操作 1.通过提供的镜像直接搭建本地服务 docker pull qierkang/golang-reverseproxy docker run -d -p 8888:8888 qierkang/golang-reverseproxy2.guid 通过如下网址直…

小C说历史(人物介绍第一篇):传奇人物Linus Torvalds 缔造Linux和Git

传奇人物Linus Torvalds 缔造Linux和Git Linus Torvalds,1969年12月28日出生于芬兰的赫尔辛基,Linux核心的创作者。当Linus十岁时,他的祖父,赫尔辛基大学的一位统计教授,购买了一台Commodore VIC-20计算机。Linus帮助他…

Mybatis-Plus面向实用知识点——结合SpringBoot

目录 环境配置基本流程各类中的方法BaseMapperIServiceCOUNTGETQueryListPageRemoveSaveUpdate 环境配置 参考java项目各框架环境配置 基本流程 创建Mapper Mapper public interface MyMapper extends BaseMapper<Entity>{}创建Service public interface MyService …

【娱乐圈明星知识图谱2】信息抽取

目录 1. 项目介绍 2. 信息抽取介绍 3. ChatGPT 信息抽取代码实战 4. 信息抽取主逻辑 5. 项目源码 1. 项目介绍 利用爬虫项目中爬取的大量信息 【娱乐圈明星知识图谱1】百科爬虫_Encarta1993的博客-CSDN博客娱乐圈明星知识图谱百度百科爬虫百度百科爬虫百度百科爬虫百度百…

【vue】 vue2 监听滚动条滚动事件

代码 直接上代码&#xff0c;vue单文件 index.vue <template><div class"content" scroll"onScroll"><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容</p><p>内容…

java 定时任务不按照规定时间执行

这里写目录标题 使用异步启动可能出现的问题排查代码中添加的定时任务步骤是否正确排查是否任务阻塞&#xff0c;如果定时任务出现异常阻塞后&#xff0c;将不会在次执行java中多个Scheduled定时器不执行为了让Scheduled效率更高&#xff0c;我们可以通过两种方法将定时任务变成…

springboot 整合代码块实现访问

一 springboot整个代码块实 2.访问

补充JDK源码-IDEA集成工具

在阅读JDK8源码的时候发现&#xff0c;只有一小部分常用包是存在源码及其注释的&#xff0c;而很多内部包是没有源码&#xff0c;class文件在阅读的时候对阅读者十分不友好。在网上搜集了很多资料都没有解决问题。 解决问题办法&#xff1a;参考文档。本文主要是根据这篇文章记…

语义检索系统【一】:基于无监督预训练语义索引召回:SimCSE、Diffcse

搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术细节以及项目实战(含码源) 专栏详细介绍:搜索推荐系统专栏简介:搜索推荐全流程讲解(召回粗排精排重排混排)、系统架构、常见问题、算法项目实战总结、技术…

VB6: 安装界面中文乱码,打开项目中文乱码

Win11环境下的VB6开发&#xff0c;遇到中文乱码问题。 1.在安装VB6开发环境的时候&#xff0c;对话框各种乱码 2.安装完&#xff0c;或者用绿色版VB6&#xff0c;打开现有项目的时候&#xff0c;中文内容出现乱码 解决方法&#xff1a; 参考这篇文章&#xff1a;Windows: 文…

cc2652主协处理器分时控制同一个外设的问题

问题已提交TI论坛&#xff0c;我是提交到的中文论坛&#xff0c;然后fae给转到英文论坛了。 简单描述就是&#xff0c;怎么让这个单片机一会用主处理器控制SPI设备&#xff0c;一会再用协处理器控制同一个设备。 主处理器的spi配置使用 CCS studio配置的 协处理器使用Sensor Co…

监控对象都有哪些分类

1、业务监控 这类指标是管理层非常关注的&#xff0c;代表企业营收&#xff0c;或者跟客户主流程相关&#xff0c;类似 BI 数据。不过相比 BI 数据&#xff0c;业务监控指标有两点不同。 对精确度要求没有那么高&#xff1a;因为监控只要发现趋势异常就可以&#xff0c;至于是…

Spring Boot的自动配置原理

一.原理解释 Spring Boot的自动配置是Spring框架的一个重要特性&#xff0c;它旨在简化应用程序的开发和部署过程。自动配置通过基于类路径中的依赖关系和配置文件内容来预先配置Spring应用程序的各种组件和功能。这样&#xff0c;我们可以在无需显式配置大量参数的情况下&…

springboot中配置bpmnjs插件-activiti7流程图绘制插件/IDEA中运行bpmnjs

BPMNJS的安装和使用需要依赖nodejs插件,需要先安装NODEJS,因为bpmnjs插件的运行需要使用到NODEJS中的npm命令。 安装nodejs 安装和使用bpmnjs插件,绘制activiti工作流需要的流程图。 1、安装和配置nodejs 2.1、下载nodejs https://nodejs.org/en 1.2、安装nodejs,默认安…

通用指令(汇编)

一、数据处理指令1&#xff09;数学运算数据运算指令的格式数据搬移指令立即数伪指令加法指令带进位的加法指令减法指令带借位的减法指令逆向加法指令乘法指令数据运算指令的扩展 2&#xff09;逻辑运算按位与指令按位或指令按位异或指令左移指令右移指令位清零指令 3&#xff…

RabbitMQ-API

这里写目录标题 Hello word 模式添加依赖生产者消费者获取信道工具类 Work Queues模式消费者代码 C1开启多线程运行启动 消费者代码 C2生产者代码 消息应答自动应答消息应答的方法Multiple 的解释消息自动重新入队消息手动应答代码消费者API 队列持久化消息持久化不公平分发消息…

思科模拟器配置静态路由(下一跳使用IP)

Router0配置代码&#xff1a;##端口配置 Router(config)#int fastEthernet 0/0 Router(config-if)#ip address 192.168.10.254 255.255.255.0 Router(config-if)#no shutdown Router(config-if)#int fastEthernet 0/1 Router(config-if)#ip address 192.168.20.1 255.255.255.2…

拼多多海外版Temu商业模式分析

拼多多于2022 年 9 月在美国上线跨境平台 Temu&#xff0c;发布仅2个月就成为北美下载量最高的应用程序&#xff0c;持续霸榜。 这篇文章帮你快速了解下Temu&#xff1a; 商业模式如何竞争情况如何有哪些优势和挑战后期业务如何发展 一、Temu商业模式 Temu平台拥有对商品的最…

如何一目了然地监控远程 Linux 系统

动动发财的小手&#xff0c;点个赞吧&#xff01; Glances 是一款免费的开源、现代、跨平台、实时 top 和类似 htop 的系统监控工具&#xff0c;与同类工具相比&#xff0c;它提供了先进的功能&#xff0c;并且可以在不同的模式下运行&#xff1a;作为独立模式、客户端/服务器模…