[CLIP-VIT-L + Qwen] 多模态大模型源码阅读 - DataSet篇

[CLIP-VIT-L + Qwen] 多模态大模型源码阅读 - DataSet篇

  • 前情提要
  • 源码解读
    • 完整代码
    • 逐行解读
      • 导包
      • readjson函数
      • data_collate函数
      • ImageCaptionDataset类(init函数)
      • ImageCaptionDataset类(readImage函数)

在这里插入图片描述
参考repo:WatchTower-Liu/VLM-learning; url: VLLM-BASE

前情提要

有关多模态大模型架构中的语言模型部分(MQwen.py)的代码请看(多模态大模型源码阅读 - 1、 多模态大模型源码阅读 - 2, 多模态大模型源码阅读 - 3,多模态大模型源码阅读 - 4)
多模态大模型架构中的视觉模型(visual/CLIP-VIT.py)部分请看多模态大模型源码阅读 - 5
多模态大模型架构中的trainer(trainer.py)部分请看多模态大模型源码阅读 - 6
多模态大模型架构中的MultiModal融合部分(MultiModal.py)部分请看多模态大模型源码阅读 - MultiModal篇。
观前提醒,本文中介绍的多模态模型架构来源于github项目WatchTower-Liu/VLM-learning,对Qwen模型的前向传播代码进行重写,并通过中间投影层将视觉特征与文本映射到同一向量空间。投影层原理参考LLAVA
在这里插入图片描述
本节将介绍多模态模型架构中的dataset部分,该部分主要用于处理图片和文本数据,使其能够用于image captioning(图像字幕生成)任务。

源码解读

完整代码

import torch
import json
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from transformers import CLIPProcessor, SiglipProcessor
from PIL import Image
import numpy as np
from tqdm import tqdmfrom qwen.qwen_generation_utils import make_contextdef readJson(filePath):with open(filePath, 'r', encoding="utf-8") as f:data = json.load(f)return datadef data_collate(example, tokenizer, black_token_length):images = []captions = []labels = []max_length = np.max([len(e[1]) for e in example]) + 1for e in example:img, caption, L = eL = L + 1caption = caption + [tokenizer.eod_id]images.append(img)caption_labels = [-100]*(black_token_length + (len(caption)-L) - 1) + caption[-L:] + [-100]*(max_length - len(caption))captions.append(torch.tensor(caption + [tokenizer.eod_id]*(max_length - len(caption))))labels.append(torch.tensor(caption_labels))labels = torch.stack(labels, dim=0).long()captions = torch.stack(captions, dim=0).long()images = torch.stack(images, dim=0).to(torch.float16)return {"images": images, "input_ids": captions, "labels": labels}class ImageCaptionDataset(Dataset):def __init__(self, tokenizer, image_map_file, captions_file, Vconfig, return_caption_num=1, max_train_data_item=None):super().__init__()self.tokenizer = tokenizerself.return_caption_num = return_caption_numself.max_train_data_item = max_train_data_itemmean = [0.485, 0.456, 0.406]  # RGBstd = [0.229, 0.224, 0.225]  # RGBself.transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean, std),transforms.Resize([224, 224])])self.image_map = readJson(image_map_file)self.captions = readJson(captions_file)# self.image_processor = CLIPProcessor.from_pretrained(Vconfig.model_path)self.image_processor = SiglipProcessor.from_pretrained(Vconfig.model_path)self.readImage()  # 一次性读入内存def readImage(self):self.data_list = []number = 0image_map_keys = list(self.image_map.keys())np.random.shuffle(image_map_keys)for IM in tqdm(image_map_keys):number += 1if self.max_train_data_item is not None and number > self.max_train_data_item:returntry:image_file_path = self.image_map[IM]["path"] + self.image_map[IM]["image_file"]self.data_list.append([image_file_path, self.image_map[IM]["ID"]])except Exception as e:print(f"Error loading image {IM}: {e}")continue# Debug informationprint(f"Total images loaded: {len(self.data_list)}")def __getitem__(self, index):image_path, ID = self.data_list[index]try:image = Image.open(image_path).convert("RGB")image = self.image_processor(images=image, return_tensors="pt")["pixel_values"][0]except Exception as e:print(f"Error processing image {image_path}: {e}")raisecaptions_data = self.captions.get(str(ID), {})captions = captions_data.get("a", [])# Ensure captions is a listif isinstance(captions, str):captions = [captions]elif isinstance(captions, dict):# Handle the case where captions is a dictionarycaptions = [captions.get("value", "")]if not isinstance(captions, list):raise ValueError(f"Captions for ID {ID} are not in the expected format: {captions}")if not captions:raise ValueError(f"No captions found for ID {ID}")prompt = captions_data.get("q", "")# Debug information# print(f"Captions for ID {ID}: {captions}")select_idx = np.random.choice(len(captions))# More debug information# print(f"Selected index: {select_idx}, Selected caption: {captions[select_idx]}")messages = [{"role": "system", "content": ""}, {"role": "user", "content": prompt}]prompt_raw, context_tokens = make_context(self.tokenizer,prompt,history=[],system="你是一位图像理解助手。")choice_captions = self.tokenizer(prompt_raw)["input_ids"]answer = self.tokenizer(captions[select_idx])["input_ids"]choice_captions = choice_captions + answerreturn image, choice_captions, len(answer)def __len__(self):return len(self.data_list)

逐行解读

导包

import torch
import json
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from transformers import CLIPProcessor, SiglipProcessor
from PIL import Image
import numpy as np
from tqdm import tqdmfrom qwen.qwen_generation_utils import make_context

torch:深度学习的核心出装,无需赘述。
json:主要用于处理json格式文件。json简洁易用,并且被多种语言支持,有良好的跨平台兼容性,所以在很多项目里我们都能看到json文件的影子。在python里,json可以很容易地转换为字典和列表对象,字典和猎豹对象也可以存储为json文件。
Dataset:Dataset是一个用于数据处理的抽象类,可以通过继承它自定义自己的数据处理方式。
DataLoader:封装已有的数据集对象,可以进行批处理和多进程加载。
transforms:主要用于图像预处理,可以对图像进行旋转,裁剪,缩放等操作。
CLIPProcessor, SiglipProcessor:在这里主要用于将图像转换为像素值。
Image:主要用于图像的打开,处理和保存,多模态中非常常用的一个模块。
make_context:用于生成文本和上下文,将文本输入转化为模型可以理解的格式。这个方法来自于Qwen模型原始项目中的模块,在github的transforemers仓库中可以找到。

readjson函数

def readJson(filePath):with open(filePath, 'r', encoding="utf-8") as f:data = json.load(f)return data

根据传入的文件路径打开json文件,指定文件的编码类型为‘utf-8’,防止文件内部可能有非ASCII字符,以只读模式打开文件。
通过json.load()函数将json数据格式的内容转换为python对象,例如字典或列表。并将转换后的值返回。

data_collate函数

def data_collate(example, tokenizer, black_token_length):images = []captions = []labels = []max_length = np.max([len(e[1]) for e in example]) + 1for e in example:img, caption, L = eL = L + 1caption = caption + [tokenizer.eod_id]images.append(img)caption_labels = [-100]*(black_token_length + (len(caption)-L) - 1) + caption[-L:] + [-100]*(max_length - len(caption))captions.append(torch.tensor(caption + [tokenizer.eod_id]*(max_length - len(caption))))labels.append(torch.tensor(caption_labels))labels = torch.stack(labels, dim=0).long()captions = torch.stack(captions, dim=0).long()images = torch.stack(images, dim=0).to(torch.float16)return {"images": images, "input_ids": captions, "labels": labels}

data_collate函数用于数据预处理。传入参数分别为example,tokenizer,black_token_length。
example:包含多个样本的列表,每个元素都是一个三元组,包含图像、文本和一个自定义长度。
tokenizer:分词器,用于处理文本数据。
black_token_length:black_token_length指定应该屏蔽的token数量,对这部分token不计算其损失。
初始化三个列表,分别存储与处理后的图片、字幕(也可以称作图片描述)和标签信息。
首先遍历example中的每个元素,len(e[1])代表字幕长度,max_length初始化为最长字幕的长度加一,这里的加一是加上了eos_token的长度。
接着遍历example中的每个元素,并将e解包赋值给img,caption和L,需要注意的是这里的L和字幕长度不相等,具体数值取决于实际需求。
L的长度加一,添加上结束符eos_token的长度。
在字幕变量结尾加上eos_token
images数组添加入example列表中每个元组内的img数据。
初始化caption_labels,开头是长度为(black_token_length + 当前字幕长度 - L - 1)的掩码,中间为字幕的倒数L个token,结尾为长度是(max_lenght - 当前字幕长度)的掩码。这样操作用于忽略序列的开始和填充部分。
用eos_token对当前字幕进行填充,确保其长度为max_length,并将填充后的数据转换为浮点数长点,添加入captions数组中。
将caption_labels添加入labels数组中。
利用stack函数对labels,captions,images进行堆叠。其中labels和captions转换为长整型,images转换为单精度浮点数。
将images,captions,labels打包为一个字典返回。值得注意的是input_ids键对应的是字幕(captions)。

ImageCaptionDataset类(init函数)

class ImageCaptionDataset(Dataset):def __init__(self, tokenizer, image_map_file, captions_file, Vconfig, return_caption_num=1, max_train_data_item=None):super().__init__()self.tokenizer = tokenizerself.return_caption_num = return_caption_numself.max_train_data_item = max_train_data_itemmean = [0.485, 0.456, 0.406]  # RGBstd = [0.229, 0.224, 0.225]  # RGBself.transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean, std),transforms.Resize([224, 224])])self.image_map = readJson(image_map_file)self.captions = readJson(captions_file)# self.image_processor = CLIPProcessor.from_pretrained(Vconfig.model_path)self.image_processor = SiglipProcessor.from_pretrained(Vconfig.model_path)self.readImage()  # 一次性读入内存

ImageCaptionDataset类继承自DataSet类,重构了部分方法。
image_map_file:这个参数是图像和索引的映射。
captions_file:包含了对应图像的字幕信息。
Vconfig:视觉模型的通用配置
return_caption_num:这一参数的数值代表每个图片返回的字幕数量,假设每一个图片都有k个字幕,如果这个参数的数值为n,n<=k,那么就会从k个字幕中随机选取n个返回。
max_train_data_item参数限制了训练数据的最大数量。

        self.tokenizer = tokenizerself.return_caption_num = return_caption_numself.max_train_data_item = max_train_data_item

将部分参数存储为成员变量。

        mean = [0.485, 0.456, 0.406]  # RGBstd = [0.229, 0.224, 0.225]  # RGB

mean和std用于图像数据标准化,其中mean为RGB数据的均值,std为RGB数据的标准差,这些数据会在后续代码中对图像数据进行处理时用到。

        self.transform = transforms.Compose([transforms.ToTensor(),transforms.Normalize(mean, std),transforms.Resize([224, 224])])

创建一个transpose对象,用于对图像进行转换,该对象执行三个操作。1.将图像数据转换为浮点数张量类型,2.使用之前初始化的均值和标准差对图像数据进行标准化。3,将图像数据重塑为(224,224)像素。

        self.image_map = readJson(image_map_file)self.captions = readJson(captions_file)

使用之前创建的readjson方法读取图像索引映射数据和图像字幕数据,并转换为python对象(字典)。

        self.image_processor = CLIPProcessor.from_pretrained(Vconfig.model_path)self.readImage()  # 一次性读入内存

从视觉模型的模型路径中初始化一个图像处理器,用于将图片转换为像素信息,最后使用self.readImage()将有关图像的各类信息一次性读入内存。

ImageCaptionDataset类(readImage函数)

    def readImage(self):self.data_list = []number = 0image_map_keys = list(self.image_map.keys())np.random.shuffle(image_map_keys)for IM in tqdm(image_map_keys):number += 1if self.max_train_data_item is not None and number > self.max_train_data_item:returntry:image_file_path = self.image_map[IM]["path"] + self.image_map[IM]["image_file"]self.data_list.append([image_file_path, self.image_map[IM]["ID"]])except Exception as e:print(f"Error loading image {IM}: {e}")continue# Debug informationprint(f"Total images loaded: {len(self.data_list)}")

这个函数主要用于将图像的路径信息和图像id信息成对存储为成员变量。

    def readImage(self):self.data_list = []number = 0image_map_keys = list(self.image_map.keys())np.random.shuffle(image_map_keys)

首先是一系列初始化工作。初始化一个data_list列表用于存储相关数据,初始化number用于计数,防止超过最大训练数据上限。将图片索引映射数据的键转换为列表,并赋值给image_map_keys变量。利用np.random.shuffle打乱图像键的顺序,保证训练数据的随机性。

        for IM in tqdm(image_map_keys):number += 1if self.max_train_data_item is not None and number > self.max_train_data_item:returntry:image_file_path = self.image_map[IM]["path"] + self.image_map[IM]["image_file"]self.data_list.append([image_file_path, self.image_map[IM]["ID"]])except Exception as e:print(f"Error loading image {IM}: {e}")continue

循环遍历所有的图像键,每次循环代表我们处理了一个数据,number计数加一。如果当前number超过了最大训练数据上限则直接退出循环并返回。
用try except体防止在读取图像时发生错误。首先读取图像的文件路径,将文件夹路径和图片路径进行拼接。并将其与图片的ID数据成对存入data_list中。
如果报错则打印错误信息,并继续遍历后续数据,这样可以避免循环中途被打断。

    def __getitem__(self, index):image_path, ID = self.data_list[index]try:image = Image.open(image_path).convert("RGB")image = self.image_processor(images=image, return_tensors="pt")["pixel_values"][0]except Exception as e:print(f"Error processing image {image_path}: {e}")raise

get_item魔法方法允许类的实例对象通过[]对象符进行索引操作。如Multimodal[1],其中Multimodal为类的实例对象,1为传入的index参数值。
根据传入的索引值,将成员变量data_list对应所以值下的列表进行解包,解包为image_path和ID。
用try_except格式捕捉报错和报错类型,提高代码的鲁棒性,如果try内部的代码出现错误,就会立即报错,这个应该是防止图片地址不存在。
根据图片存储地址打开图片并用之前初始化好的图片处理对象将图片转换为像素值,返回类型为浮点数张量。这里用[0]是因为图片处理对象默认的返回值size为(batchsize, …),即使只有一个图片。也会返回批次为1的返回值,因此需要用索引操作从批次中获取数据。

        captions_data = self.captions.get(str(ID), {})captions = captions_data.get("a", [])# Ensure captions is a listif isinstance(captions, str):captions = [captions]elif isinstance(captions, dict):# Handle the case where captions is a dictionarycaptions = [captions.get("value", "")]

根据解包后获得的ID值获取字幕相关数据,这里用get是为了防止没有ID值对应的字幕相关数据,出现报错,get函数的第二个传入参数{}表示当ID值对应数据不存在时,返回一个空集合。
进一步用get函数从字幕相关数据中获取字幕数据,这里的’a’代表字幕数据存放在键‘a’对应的值中。
接下来的操作是为了将字幕数据转换为列表格式。
首先判断字幕数据是否为字符串,如果是,则转换为列表。如果是字典,则进一步获取字典的值,并转换为列表。

        if not isinstance(captions, list):raise ValueError(f"Captions for ID {ID} are not in the expected format: {captions}")if not captions:raise ValueError(f"No captions found for ID {ID}")

这段代码是为了确保字幕数据为列表格式。如果不是列表格式,则报错并输出自定义的报错信息。
如果字幕数据为空,则报错并输出自定义的报错信息。

        prompt = captions_data.get("q", "")select_idx = np.random.choice(len(captions))

从字幕相关数据中获取键’q’下对应的值,并赋值给变量prompt,这里的’q’含义为query。
随机从选择一个idx,idx的范围为[0, len(captions) - 1],表示从captions中随机抽取一个图像字幕数据。

        messages = [{"role": "system", "content": ""}, {"role": "user", "content": prompt}]prompt_raw, context_tokens = make_context(self.tokenizer,prompt,history=[],system="你是一位图像理解助手。")

创建一个消息对象,其中第一个字典代表系统的角色的消息,初始化内容为空,另一个字典代表用户的消息,初始化为之前提取的prompt。
使用导入的创建上下文方法,传入初始化的成员变量self.tokennizer分词器,prompt,初始化历史信息为空,代表当前没有历史对话,system作为描述信息,描述了系统功能。
返回值为初步处理后的提示文本和上下文token,用于后续处理以适配模型输入

        choice_captions = self.tokenizer(prompt_raw)["input_ids"]answer = self.tokenizer(captions[select_idx])["input_ids"]choice_captions = choice_captions + answerreturn image, choice_captions, len(answer)

使用tokenizer将原始提示和选取的字幕转换为input_ids,并拼接在一起作为模型的文本输入信息。
最后返回图像信息,对应的文本输入信息和字幕信息的长度。
这里的answer相当于对图像信息(image)的描述。
例如我的返回值image是一个黄色的花朵的像素信息,那么answer就是对这个图像的描述(一个黄色的花朵),choice_caption将提示信息文本(‘你是一位图像理解助手’)和answer打包在一起用于模型训练。
至此,模型的输入数据处理部分讲解完毕,后续应该会更新模型的正式训练代码阅读,trainer.py部分。

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

趋动科技 OrionX on VMware 打造 AI 就绪平台

着科技进步和产业变革的加速演进&#xff0c;人工智能&#xff08;AI&#xff09;已经成为兵家必争之地。今年以来伴随着ChatGPT带来的鲶鱼效应&#xff0c;人工智能成为科技产业创新的焦点&#xff0c;其应用范围越来越广泛&#xff0c;并将持续发展。科技产业龙头正加大在人工…

Redis入门指南

Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的高性能键值对存储系统&#xff0c;它支持多种数据结构&#xff0c;如字符串、哈希、列表、集合、有序集合等。Redis因其快速的读写能力、丰富的数据类型和灵活的操作而广泛应用于缓存、消息队列、实时分析等…

链接 -- 动静态链接 --特点、区别、静态库安装下载

1.链接是什么&#xff1f; 我们的程序&#xff0c;和 库&#xff08;语言一定会有自己的标准库&#xff09; 结合的过程就叫做链接。 2.为什么有链接&#xff1f; 让开发站在巨人的肩膀&#xff0c;提高开发效率。 c语言库&#xff1a; ls /user/include/ 动静态库的特点与区别…

力扣面试经典算法150题:O(1) 时间插入、删除和获取随机元素

O(1) 时间插入、删除和获取随机元素 今天的题目是力扣面试经典150题中的数组的中等难度题&#xff1a; O(1) 时间插入、删除和获取随机元素。 题目链接&#xff1a;https://leetcode.cn/problems/insert-delete-getrandom-o1/description/?envTypestudy-plan-v2&envIdtop…

Oracle问题笔记

ORA-28040 没有匹配的验证协议 问题出现场景oracle数据库为12c,应用使用的jdbc或客户端工具是11g版本一下&#xff0c;连接12c数据库时会报ora-28040错误。解决办法在Oracle服务端的$ORACLE_HOME/network/admin/sqlnet.ora文件中添加&#xff1a; SQLNET.ALLOWED_LOGON_VERSI…

第4章 汇编语言和汇编软件

第4章 汇编语言和汇编软件 该章主要介绍了汇编语言和汇编语言编译器的安装和使用。 汇编语言程序 该小节主要介绍了为什么要有汇编语言和汇编语言程序的一些基础写法。 书中有提到CPU有不同的架构&#xff0c;汇编语言有不同的风格&#xff0c;那么不同的CPU架构和不同的汇…

日常维护交换机,看看这些老网工怎么说

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 晚上好&#xff0c;我的网工朋友。 交换机作为连接各个节点的核心设备&#xff0c;其稳定性和可靠性直接关系到整个网络系统的健康运行。 路由器…

vue开发区分开发环境和生产环境,以及预发布环境

vue开发区分开发环境和生产环境&#xff0c;以及预发布环境 在根目录创建 .env[mode] 文件&#xff0c;在项目执行 npm run dev 的时候vite会自动去读取.env.development文件里面的配置&#xff0c;执行npm runbuild进行打包之后也会自动将.env.production的内容打包进去&…

Kafka日志及常见问题

目录 1.Topic下的消息是如何存储的 1.1log文件追加记录所有消息 1.2index和timeindex加速读取日志信息 2.文件清理机制 2.1如何判断哪些日志文件过期了 2.2日志清理策略 3.Kafka的文件高效读写机制 3.1Kafka的文件结构 3.2顺序写磁盘 3.3零拷贝 3.3.1传统IO 3.3.2m…

【硬件操作入门】2--GPIO与门电路、二极管三极管、LED电路与操作

【硬件操作入门】2–GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作 文章目录 【硬件操作入门】2--GPIO与门电路&#xff08;二极管&三极管&#xff09;、LED电路与操作一、GPIO与门电路1.1、GPIO的应用1.2、GPIO引脚操作1.2.1 设置引脚为GPIO功能…

加速网络体验,Squid缓存代理:让浏览如飞,畅享无限网络速度!

作者简介&#xff1a;我是团团儿&#xff0c;是一名专注于云计算领域的专业创作者&#xff0c;感谢大家的关注 座右铭&#xff1a; 云端筑梦&#xff0c;数据为翼&#xff0c;探索无限可能&#xff0c;引领云计算新纪元 个人主页&#xff1a;团儿.-CSDN博客 目录 前言: squ…

[数据集][目标检测]建筑工地楼层空洞检测数据集VOC+YOLO格式2588张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2588 标注数量(xml文件个数)&#xff1a;2588 标注数量(txt文件个数)&#xff1a;2588 标注…

springboot项目读取 resources 目录下的文件的9种方式

1. 使用 ClassLoader.getResourceAsStream() 方法 InputStream inputStream getClass().getClassLoader().getResourceAsStream("file.txt"); 2. 使用 Class.getResourceAsStream() 方法 InputStream inputStream getClass().getResourceAsStream("/file.txt&…

JAVA-封装

目录 一、封装的概念 二、封装扩展之包 1. 包的概念 2.导入包中的类 3.自定义包 4.常见的包 三、访问限定符 在同一包中&#xff1a; 在不同包中&#xff1a;​编辑 一、封装的概念 面向对象程序三大特性&#xff1a;封装、继承、多态。而类和对象阶段&#xff0c;主…

网络安全——基础知识记忆梳理

1. SQL注入攻击 SQL注入攻击是一种常见的网络安全威胁&#xff0c;它利用Web应用程序中对用户输入的数据的不正确处理&#xff0c;攻击者可以在SQL查询中注入恶意代码&#xff0c;从而执行非授权的数据库操作。这种攻击方式可以导致数据泄漏、数据篡改、绕过认证等多种安全问题…

什么样的条件才会造就这样疯狂的末日期权?

今天带你了解什么样的条件才会造就这样疯狂的末日期权&#xff1f;末日期权一般是指期权合约快到期的一周或者最后三天&#xff0c;当然最后一天就是末日期权的疯狂。 末日期权是指那些接近到期日的期权。 由于剩余时间较短&#xff0c;这些期权的时间价值通常非常低&#xf…

MFC工控项目实例之七点击下拉菜单弹出对话框

承接专栏《MFC工控项目实例之六CFile添加菜单栏》 1、在SEAL_PRESSUREDlg.h文件中添加代码 class CSEAL_PRESSUREDlg : public CDialog { ...afx_msg void OnTypeManage(); ... } 2、在SEAL_PRESSUREDlg.cpp文件中添加代码 BEGIN_MESSAGE_MAP(CSEAL_PRESSUREDlg, CDialog)//…

快速排序与其例题

一、快速排序 1、简单介绍&#xff1a;快速排序&#xff08;Quick Sort&#xff09;是一种高效的排序算法&#xff0c;由计算机科学家Tony Hoare在1960年提出。它是基于分治法的排序算法&#xff0c;其基本思想和步骤如下&#xff1a; 基本概念 快速排序的核心思想是将待排序…

Debezium2.7 数据同步 MySQL/Oracle -- AI生成

Debezium是Red Hat开源的一个工具&#xff0c;用于实时捕获多种数据源&#xff08;包括MySQL、PostgreSQL、SQL Server、Oracle等&#xff09;的变更数据&#xff0c;并将这些数据作为事件流输出到Kafka等消息中间件中。通过Debezium&#xff0c;可以实现数据的实时同步和变更数…

【Qt】常用控件QCalendarWidget

常用控件QCalendarWidget的使用 QCalendarWidget表示一个日历 核心属性 属性说明 selectDate 当前选中的⽇期 minimumDate 最⼩⽇期 maximumDate 最⼤⽇期 firstDayOfWeek 每周的第⼀天(也就是⽇历的第⼀列) 是周⼏. gridVisible 是否显⽰表格的边框 selectionMode…