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

news2024/9/20 5:59:34

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

  • 前情提要
  • 源码阅读
    • 导包
      • 逐行讲解
    • dataclass部分
      • 整体含义
      • 逐行解读
    • 模型微调
      • 整体含义
      • 逐行解读
    • MultiModal类
      • 整体含义
      • 逐行解读

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

前情提要

有关多模态大模型架构中的语言模型部分(MQwen.py)的代码请看(多模态大模型源码阅读 - 1、 多模态大模型源码阅读 - 2, 多模态大模型源码阅读 - 3,多模态大模型源码阅读 - 4),多模态大模型架构中的视觉模型(visual/CLIP-VIT.py)部分请看多模态大模型源码阅读 - 5,多模态大模型架构中的trainer(trainer.py)部分请看多模态大模型源码阅读 - 6。
本节将讲解如何将之前重构的MQwen语言模型部分和CLIP-VIT视觉模型部分整合为MultiModal多模态模型类,并利用多模态模型类进行前向传播,生成预测内容。

源码阅读

导包

import torch 
from torch import nn
from typing import Optional
import os
import sys
sys.path.append("../")
from transformers.modeling_outputs import CausalLMOutputWithPast
from dataclasses import dataclass, asdict
from peft import get_peft_model, LoraConfig, TaskType, PeftModel
from visual.CLIP_VIT import visualModel
from qwen.Mqwen import MQWenLMHeadModel

逐行讲解

对于部分已经用了无数次的模块就不再赘述了~

from typing import Optional

typing模块最重要的就是类型注释功能,这里导入的Optional表示变量可以是制定的类型或者None。例如Optional[str]表示变量可以是str类型或者None。

import sys
sys.path.append("../")

将上一层级目录添加到系统路径中,可以将上一层级的模块直接通过模块名导入。例如上一层级目录中定义了一个叫做abc.py的模块,那么就可以通过import abc直接导入。

from transformers.modeling_outputs import CausalLMOutputWithPast
from dataclasses import dataclass, asdict

CausalLMOutputWithPast专门用于封装因果模型的输出,包含了模型输出和过去的隐藏状态。
dataclass装饰器用于封装数据类型,asdict可以将数据类实例转换为字典。

from peft import get_peft_model, LoraConfig, TaskType, PeftModel

peft用于模型微调,get_peft_model方法获取LoRA,prefix tuning等不同类别的微调模型,LoRA包含了LoRA模型的必要配置参数,TaskType定义模型执行的不同任务类型,如文本分类、摘要总结等。PeftModel是一个基类,指定PEFT的配置。

dataclass部分

@dataclass
class LanguageConfig():
    model_path: str
    torch_dtype: torch.dtype = torch.bfloat16
    trust_remote_code: bool = True

@dataclass
class VisualConfig():
    model_path: str
    pretrained: bool = True
    

@dataclass
class MultiModalConfig():
    replace_token_id: int
    # image_context_length: int = 256
    image_context_length: int = 728
    image_feature_hidden_size: int = 4096

整体含义

用于封装不同配置下的参数类型和初始值。

逐行解读

@dataclass
class LanguageConfig():
    model_path: str
    torch_dtype: torch.dtype = torch.bfloat16
    trust_remote_code: bool = True

LanguageConfig类用于存储和管理语言模型的参数和配置。
model_path代表模型的存储地址,通常为字符串类型,无初始值,需要用户手动传入。
torch_type代表模型使用的数据类型,这里使用半精度浮点数bfloat16
trust_remote_code默认为True,当我们要远程从huggingface加载预训练模型时,通常需要保持这个值为True,因为我们运行的不是本地代码,本地下载模型的可以无视。

@dataclass
class VisualConfig():
    model_path: str
    pretrained: bool = True

VisualConfig代表视觉模型的参数和配置类,model_path与上文相同。
pretrained代表是否加载预训练模型的权重。

@dataclass
class MultiModalConfig():
    replace_token_id: int
    # image_context_length: int = 256
    image_context_length: int = 728
    image_feature_hidden_size: int = 4096

MultiModalConfig代表多模态模型的参数和配置类。
replace_token_id指定input_ids中用于替换的token_id,例如输入为[102,103,101]的数据,指定101为replace_token_id,则将101替换为图片特征数据。
image_context_length代表图像上下文长度。
image_feature_hidden_size指定图像特征隐藏层维度大小。

模型微调

def make_lora(model, finetune_args):
    peft_config = LoraConfig(
            task_type=TaskType.CAUSAL_LM,
            inference_mode=False,
            r=finetune_args.lora_rank,
            lora_alpha=32,
            lora_dropout=finetune_args.lora_dropout,
            target_modules = finetune_args.target_modules.split('|') # 把model打印出来,找跟attention相关的模块
        )

    model = get_peft_model(model, peft_config)

    return model

整体含义

通过LoRA对模型进行微调

逐行解读

def make_lora(model, finetune_args):

model传递入需要进行微调的模型实例。
finetune_args是一个包含模型微调参数的对象。

    peft_config = LoraConfig(
            task_type=TaskType.CAUSAL_LM,
            inference_mode=False,
            r=finetune_args.lora_rank,
            lora_alpha=32,
            lora_dropout=finetune_args.lora_dropout,
            target_modules = finetune_args.target_modules.split('|') # 把model打印出来,找跟attention相关的模块
        )

Loraconfig设置LoRA微调的参数,task_type设置为因果引言模型,设置inference_mode为False,代表目前处于非推理进程中,即训练进程。finetune_args.lora_rank设置LoRA的秩,决定了添加到模型中低秩矩阵的大小。lora_alpha代表LoRA 的扩展因子,用于控制扩展的维度。lora_dropout设置LoRA层中的dropout率,防止过拟合。target_modules指定要引用LoRA的模块,通过分隔符’|'分割开来,指定多个模块。

MultiModal类

class MMultiModal(nn.Module):
    def __init__(self, Lconfig: LanguageConfig, Vconfig: VisualConfig, MMconfig: MultiModalConfig, finetune_args = None, train = False, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        image_feature_length = MMconfig.image_context_length * MMconfig.image_feature_hidden_size

        self.LLM = MQWenLMHeadModel.from_pretrained(Lconfig.model_path, asdict(MMconfig), torch_dtype = Lconfig.torch_dtype, trust_remote_code = Lconfig.trust_remote_code)
        # self.LLM = MMiniCPMLMHeadModel.from_pretrained(Lconfig.model_path, asdict(MMconfig), torch_dtype = Lconfig.torch_dtype, trust_remote_code = Lconfig.trust_remote_code)
        
        if train:
            self.LLM.gradient_checkpointing_enable() 
            self.LLM.enable_input_require_grads()

        self.LLM.config.image_feature_length = image_feature_length

        if train and finetune_args is not None:
            self.LLM = make_lora(self.LLM, finetune_args)

        assert MMconfig.image_feature_hidden_size == self.LLM.config.hidden_size

        self.visualModel = visualModel.from_pretrained(Vconfig.model_path).to(Lconfig.torch_dtype)

        Vhidden_dim = self.visualModel.vision_embed_dim
        Lhidden_dim = self.LLM.config.hidden_size

        self.make_feature_proj(Vhidden_dim, Lhidden_dim, Lconfig)

        self.MMconfig = MMconfig
        
        print(f"LLM dtype: {self.LLM.dtype}")
        print(f"Visual model dtype: {self.visualModel.dtype}")
        print(f"Feature projection dtype: {self.feature_proj[0].weight.dtype}")

    def make_feature_proj(self, Vhidden_dim, Lhidden_dim, Lconfig):
        self.feature_proj = nn.Sequential(
            nn.Linear(Vhidden_dim, Lhidden_dim, dtype=Lconfig.torch_dtype),
            nn.GELU(),
            nn.Linear(Lhidden_dim, Lhidden_dim, dtype=Lconfig.torch_dtype)
        )
    
        for name, module in self.feature_proj.named_children():
            if "Linear" in module._get_name(): 
                module.weight.data.normal_(mean=0.0, std = 0.01)
                module.bias.data.zero_()

    def forward(self, image: torch.Tensor, input_ids: torch.LongTensor, labels: Optional[torch.LongTensor] = None):
        with torch.no_grad():
            # 确保 image 的数据类型为 bfloat16
            image = image.to(dtype=torch.bfloat16)
            image_feature = self.visualModel.get_image_features(pixel_values=image)[:,1:, :]
            image_feature = image_feature.detach()

        image_feature = self.feature_proj(image_feature)

        out = self.LLM(input_ids, labels=labels, images=image_feature)

        loss1 = out.loss

        return CausalLMOutputWithPast(
            loss=loss1,
            logits=out.logits,
            past_key_values=out.past_key_values,
            hidden_states=out.hidden_states,
            attentions=out.attentions,
        )
    
    def to(self, *args, **kwargs):
        return super().to(*args, **kwargs)
    
    def load(self, modelPath):
        self.LLM = PeftModel.from_pretrained(self.LLM, modelPath, inference_mode=True)
        other_params = torch.load(os.path.join(modelPath, "other_params.bin"))
        self.feature_proj.load_state_dict(other_params)

    @torch.no_grad()
    def generate(self, image: torch.Tensor, input_ids: torch.LongTensor):
        if image is None:
            image_feature = None
        else:
            image_feature=self.visualModel.get_image_features(pixel_values=image)[:,1:, :]
            image_feature = self.feature_proj(image_feature)

        input_ids = torch.tensor([input_ids]).long().to(self.LLM.device)

        out = self.LLM.generate(inputs = input_ids, images=image_feature)[:, len(input_ids[0]):-1]

        return out.long().cpu()

整体含义

通过中间投影层将视觉模型和语言模型的输出映射到统一的向量空间,实现前向传播和预测生成等功能。

逐行解读

class MMultiModal(nn.Module):
    def __init__(self, Lconfig: LanguageConfig, Vconfig: VisualConfig, MMconfig: MultiModalConfig, finetune_args = None, train = False, *args, **kwargs) -> None:
        super().__init__(*args, **kwargs)
        image_feature_length = MMconfig.image_context_length * MMconfig.image_feature_hidden_size

nn.Module不必赘述,所有神经网络模块的基类。
Lconfig,Vconfig,MMconfig分别是语言模型,视觉模型和多模态模型的配置参数对象,
finetune_args为模型微调参数(如果需要微调的话)
train默认为False,这一参数代表模型是否处于训练状态,args, **kwargs为任意的位置参数和关键字参数。
父类初始化传递
args, **kwargs保证代码的可拓展性。
image_feature_length初始化为图片上下文长度×图片特征隐藏层维度,计算得到图像特征向量的总长度。
这里的image_context_length可以理解为图像被分成了若干块,每一块都转换为image_feature_hidden_size长度的向量。

        self.LLM = MQWenLMHeadModel.from_pretrained(Lconfig.model_path, asdict(MMconfig), torch_dtype = Lconfig.torch_dtype, trust_remote_code = Lconfig.trust_remote_code)

初始化语言模型,从模型路径中加载预训练模型的权重和配置,采用重构的MQWenLMHeadModel类的成员方法。将多模态配置转换为字典对象作为额外配置传入。模型数据类型为Lconfig.torch_dtype。信任远程代码设置为True。

   if train:
            self.LLM.gradient_checkpointing_enable() 
            self.LLM.enable_input_require_grads()

如果当前为训练模式,启用语言模型的梯度累积和梯度计算。

   self.LLM.config.image_feature_length = image_feature_length

设置语言模型配置文件中的image_feature_length为之前计算得到的image_feature_length。

        if train and finetune_args is not None:
            self.LLM = make_lora(self.LLM, finetune_args)

如果提供了微调参数,则使用之前定义的make_lora函数,用LoRA进行微调,并获得微调后的语言模型。

    assert MMconfig.image_feature_hidden_size == self.LLM.config.hidden_size

确保语言模型的隐藏层维度大小和多模态模型的图片特征隐藏层大小一致。

        self.visualModel = visualModel.from_pretrained(Vconfig.model_path).to(Lconfig.torch_dtype)

        Vhidden_dim = self.visualModel.vision_embed_dim
        Lhidden_dim = self.LLM.config.hidden_size

        self.make_feature_proj(Vhidden_dim, Lhidden_dim, Lconfig)

        self.MMconfig = MMconfig

从视觉模型配置参数的模型路径初始化视觉模型,并将模型的数据类型转换为语言模型的数据类型。
初始化视觉模型隐藏层维度大小和语言模型隐藏层大小。
根据视觉模型隐藏层维度大小和语言模型隐藏层大小,语言模型配置参数初始化中间通盈层。
将多模态模型参数存储为成员变量。

    def make_feature_proj(self, Vhidden_dim, Lhidden_dim, Lconfig):
        self.feature_proj = nn.Sequential(
            nn.Linear(Vhidden_dim, Lhidden_dim, dtype=Lconfig.torch_dtype),
            nn.GELU(),
            nn.Linear(Lhidden_dim, Lhidden_dim, dtype=Lconfig.torch_dtype)
        )

创建中间投影层,传递参数分别为视觉模型隐藏层维度大小,语言模型隐藏层维度大小和语言模型配置参数。
使用nn.Sequential创建一个顺序执行的深度网络块,nn,Linear为全连接线性层,第一个全连接层接受视觉模型的输入,将输入的维度转换为语言模型隐藏层维度的输出,经过一个激活函数GELU增加模型的非线性,经过另一个线性全连接层转换后输出。

        for name, module in self.feature_proj.named_children():
            if "Linear" in module._get_name(): 
                module.weight.data.normal_(mean=0.0, std = 0.01)
                module.bias.data.zero_()

获取中间投影层所有子模块,对于线性层,将权重参数初始化为均值0,方差0.01的值,并将偏置项置为0.

    def forward(self, image: torch.Tensor, input_ids: torch.LongTensor, labels: Optional[torch.LongTensor] = None):
        with torch.no_grad():
            # 确保 image 的数据类型为 bfloat16
            image = image.to(dtype=torch.bfloat16)
            image_feature = self.visualModel.get_image_features(pixel_values=image)[:,1:, :]
            image_feature = image_feature.detach()

多模态模型的前向传播函数,传入浮点型张量image,整数型张量input_ids和整数型张量labels,其中labels的注解类型为Optional,表明labels可以为None。
在不计算梯度的情况下,将image的数据类型转换为半精度浮点数,并使用多模态架构中的视觉模型部分处理传入的图像像素值,提取图像特征,并通过切片操作去除image_features第二个维度的第一个输出结果。(这里不太清楚image_feature的形状。ps:不仅神经网络是黑盒,连代码也是黑盒,有兴趣的童鞋可以自行打印查看)。
最后使用.detach()从当前计算图中分离特征张量,防止数据运用到后续的梯度计算中。

        image_feature = self.feature_proj(image_feature)

        out = self.LLM(input_ids, labels=labels, images=image_feature)

        loss1 = out.loss

        return CausalLMOutputWithPast(
            loss=loss1,
            logits=out.logits,
            past_key_values=out.past_key_values,
            hidden_states=out.hidden_states,
            attentions=out.attentions,
        )

使用之前定义的中间投影层,提取的图像特征进行投射,使其维度大小与语言模型的影藏层维度大小一致。之后使用语言模型获取输出,传入参数input_ids,labels和处理后的image_feature。
从输出结果中获取loss,赋值给loss1变量
返回一个类封装的结果,传入的loss,logits等变量都从语言模型的输出out里获得

    def to(self, *args, **kwargs):
        return super().to(*args, **kwargs)

调用父类的to方法,传入*args, **kwargs参数,提高代码的可拓展性。这里的to方法主要用于数据类型的转移和设备的迁移,比如cuda ->cpu,cpu -> cuda

    def load(self, modelPath):
        self.LLM = PeftModel.from_pretrained(self.LLM, modelPath, inference_mode=True)
        other_params = torch.load(os.path.join(modelPath, "other_params.bin"))
        self.feature_proj.load_state_dict(other_params)

这段代码主要用于加载微调后的模型。根据modelpath加载微调后的语言模型,主要用于推理进程。other_params为保存的中间投影层参数,通过load_state_dict方法加载保存的投影层模型参数字典。

    @torch.no_grad()
    def generate(self, image: torch.Tensor, input_ids: torch.LongTensor):
        if image is None:
            image_feature = None
        else:
            image_feature=self.visualModel.get_image_features(pixel_values=image)[:,1:, :]
            image_feature = self.feature_proj(image_feature)

        input_ids = torch.tensor([input_ids]).long().to(self.LLM.device)

        out = self.LLM.generate(inputs = input_ids, images=image_feature)[:, len(input_ids[0]):-1]

        return out.long().cpu()

@torch.no_grad()装饰器用来指定函数运行时不执行梯度计算,用于节省计算资源和内存,通常在非训练阶段使用。
传入浮点数张量image和整数型张量inpu_ids。
如果未传入image像素值,则将image_feature变量置为None,反之用多模态架构中的视觉模型获取图片特征,并通过投影层映射到与语言模型相同的向量空间中。(操作与前向传播中的处理一致)
确保input_ids为整数型张量,并将其转移到与语言模型相同的设备上,以备后续处理。
使用语言模型的generate方法,传入input_ids,image_featrues参数,利用切片操作获取生成结果中除input_ids以外的所有输出结果。
假设传入的Input_ids的size为(1,5),利用语言模型生成结果的size为(1,10),这里切片操作的目的就是只获取模型的生成结果,即预测结果,out[1,5:-1]
将返回结果转换为整数型张量,并将其迁移到cpu设备上。(由于部分库与cuda不兼容,例如numpy等,或者要将数据保存至文件中,需要将数据进行迁移。)

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

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

相关文章

机器学习预处理

一、数据读取 数据的读取方式有多种,最终我们可以转化为numpy和pandas形式储存,方便后续的模型建立。 1.1 读取库的安装 需要用到的三个库 pip install pandas pip install numpy pip install openpyxl 1.2 库的使用 import pandas as pd ​ #### 1…

面向对象编程:深入PHP的封装、继承和多态性!

文章目录 面向对象OOP的核心概念定义类、创建对象构造函数和析构函数访问修饰符继承方法重写接口和抽象类静态方法和属性魔术方法 错误处理错误处理概述错误级别异常处理自定义异常设置错误处理忽略错误错误日志断言 总结 面向对象编程(OOP)是一种编程范…

设计资讯 | 这款受数学方程启发的平板桌:配集成黑胶唱片机和无线充电器

早在 1903 年,英国数学家亨利欧内斯特杜德尼就想出了将正方形变形为等边三角形的方法。这个技巧是将正方形分割成可重新排列的四个不同形状。这种方法经过一个多世纪的各种应用,仍然具有价值。 1986 年,建筑师 David Ben-Grunberg 和他的艺术…

加速打开gtihub的工具dev-sidecar

加速github,git clone, pip install 直接上工具链接 dev-sidecar code: https://github.com/docmirror/dev-sidecar dev-sidecar releases: https://github.com/docmirror/dev-sidecar/releases 不想看code的,直接点击 dev-sidecar releases…

Leetcode 1108. IP地址无效化

Leetcode 1108. IP 地址无效化 问题:给你一个有效的 IPv4 地址address,返回这个 IP 地址的无效化版本。 所谓无效化 IP 地址,其实就是用 "[.]" 代替了每个 "."。 方法1:对字符串挨个进行判断,如…

C语言 ——— 经典有关动态内存的笔试题

目录 笔试题1 笔试题2 笔试题3 笔试题1 代码演示&#xff1a; #include<stdio.h> #include<string.h> void GetMemory(char* p) {p (char*)malloc(100); } void Test() {char* str NULL;GetMemory(str);strcpy(str, "hello world");printf("…

计算机Java项目|基于SpringBoot的周边游平台设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、CSDN内容合伙人、掘金特邀作者、阿里云博客专家、51CTO特邀作者、多年架构师设计经验、多年校企合作经验&#xff0c;被多个学校常年聘为校外企业导师&#xff0c;指导学生毕业设计并参…

Gameplay Ability System(事件通知)

一、打开角色蓝图BP_BaseCharacter添加节点 1、添加Send Gameplay Event to Actor节点&#xff0c;当玩家的武器碰到敌人时发送GameplayEvent。 2、给该事件添加Event Tag标签&#xff1a;Ability.MeleeAttack.Damage.Event。 3、通过Make GameplayEventData给事件添加Payload…

图解计算机网络:一条 HTTP 请求的网络拓扑之旅

引言 常见的网络拓扑结构如下图所示&#xff1a; 在此拓扑中&#xff0c;终端设备通过 WiFi 连接到路由器&#xff0c;路由器再连接到光猫&#xff08;或终端设备通过移动网络 4G/5G 连接到基站&#xff09;&#xff0c;之后 ISP 网络服务提供商接管网络通信&#xff0c;将请求…

电脑如何录屏?高清录制教程来袭,快收藏!

在数字化时代&#xff0c;电脑录屏已经成为了我们学习和工作中的一项重要技能。如果你想知道电脑如何录屏&#xff0c;这里有几个简单的方法可以帮助你开始。 一、福昕网课录制大师 虫洞 https://www.foxitsoftware.cn/REC/ 这个软件因为又专业又好上手&#xff0c;挺受大家…

LaTex插入图片

LaTeX 提供了许多定制化图片的功能。这篇文章将会介绍如何用最常见的格式插入图片、缩放图片以及如何在文档中引用这些图片。 1.基本使用 效果图如图所示。 \documentclass{article} \usepackage{graphicx} \graphicspath{ {./figure/} }\begin{document}\begin{figure}[!t]…

排序(归并排序,非比较排序)

归并排序 归并排序&#xff08;MERGE-SORT&#xff09;是建⽴在归并操作上的⼀种有效的排序算法,该算法是采⽤分治法&#xff08;Divide and Conquer&#xff09;的⼀个⾮常典型的应⽤。将已有序的子序列合并&#xff0c;得到完全有序的序列&#xff1b;即先使每个子序列有序&…

《深入浅出WPF》读书笔记.6binding系统(中)

《深入浅出WPF》读书笔记.6binding系统(中) 背景 这章主要讲各种模式的数据源和目标的绑定。 代码 把控件作为Binding源与Binding标记扩展 方便UI元素之间进行关联互动 <Window x:Class"BindingSysDemo.BindingEle"xmlns"http://schemas.microsoft.com…

IP打开“向下”空间,爱奇艺“摊牌了”

长视频领域上半年竞争激烈、好剧频出&#xff0c;让行业焕发了新的吸引力&#xff0c;优质内容对行业的正向引导作用持续凸显。正如爱奇艺创始人、CEO龚宇最新的发言&#xff1a;“长视频行业实现长期发展的关键在于优质内容供给的持续性&#xff0c;以及内容质量和商业收益的双…

页面设计任务 商品详情页

目录 成品: 任务描述 源码&#xff1a; 详细讲解&#xff1a; 1.导航栏讲解 2.主体部分 3.图像部分 4.评分部分 5.按钮部分 6.配置信息部分 7.响应式设计 成品: 任务描述 创建一个产品展示页面&#xff0c;包括以下内容&#xff1a; 网页结构&#xff1a;使用 HTM…

选择适合的电脑监控软件,可以提升员工效率

在信息化时代&#xff0c;企业对员工的管理不再仅限于传统的考勤制度和绩效评估。随着工作方式的多样化&#xff0c;特别是远程办公的普及&#xff0c;电脑监控软件成为企业管理的重要工具。这些软件不仅能帮助管理者了解员工的工作状态&#xff0c;还能有效提升工作效率&#…

唯有自救,才能得救

回顾这一周的五天&#xff0c;也做成了一些事&#xff0c;想做的事&#xff0c;立即行动&#xff0c;几乎是心到手到&#xff0c;最后也出了一些成绩。 全红婵跳完水&#xff0c;得了个满分&#xff0c;一边走路&#xff0c;一边擦头发&#xff0c;旁边的记者问&#xff1a;你…

数字孪生网络 (DTN)关键技术分析

数字孪生网络 (DTN): 概念、架构及关键技术 摘要 随着5G商用规模部署和下一代互联网IPv6的深化应用&#xff0c;新一代网络技术的发展引发了产业界的广泛关注。智能化被认为是新一代网络发展的趋势&#xff0c;为数字化社会的信息传输提供了基础。面向数字化、智能化的新一代网…

Leetcode面试经典150题-42.接雨水

解法都在代码里&#xff0c;不懂就留言或者私信 class Solution {/**本题的解题思路是双指针&#xff1a;一个从头开始一个从尾巴开始&#xff0c;两头的肯定是没有办法接住雨水的,你可以认为0位置左边是0的柱子所以理论上我们是从1遍历到n-2&#xff0c;但是你也可以遍历0到N…

SpringData基础学习

一、SpringData 概述 二、SpringData JPA HelloWorld 三、Repository 接口