BaiChuan13B多轮对话微调范例

news2024/11/18 14:43:36

前方干货预警:这可能是你能够找到的,最容易理解最容易跑通的,适用于多轮对话数据集大模型高效微调范例

我们构造了一个修改大模型自我认知的3轮对话的玩具数据集,使用QLoRA算法,只需要5分钟的训练时间,就可以完成微调,并成功修改了LLM模型的自我认知。

公众号算法美食屋后台回复关键词:torchkeras,获取本文notebook源代码。

cddf63cfd89c633b18bb73ad82a3e434.png

我们先说说原理,主要是多轮对话微调数据集以及标签的构造方法,有三种常见方法。

一个多轮对话可以表示为:

inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>

第一种方法是,只把最后一轮机器人的回复作为要学习的标签,其它地方作为语言模型概率预测的condition,无需学习,赋值为-100,忽略这些地方的loss。

inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels = <-100> <-100> <-100> <-100> <-100> <assistant3>

这种方法由于没有对中间轮次机器人回复的信息进行学习,因此存在着严重的信息丢失,是非常不可取的。

第二种方法是,把一个多轮对话拆解,构造成多条样本,以便对机器人的每轮回复都能学习。

inputs1 = <user1> <assistant1> 
labels1 = <-100> <assistant1>

inputs2 = <user1> <assistant1> <user2> <assistant2> 
labels2 = <-100> <-100> <-100> <assistant2> 

inputs3 = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels3 = <-100> <-100> <-100> <-100> <-100> <assistant3>

这种方法充分地利用了所有机器人的回复信息,但是非常低效,模型会有大量的重复计算。

第三种方法是,直接构造包括多轮对话中所有机器人回复内容的标签,既充分地利用了所有机器人的回复信息,同时也不存在拆重复计算,非常高效。

inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels = <-100> <assistant1> <-100> <assistant2> <-100> <assistant3>

为什么可以直接这样去构造多轮对话的样本呢?难道inputs中包括第二轮和第三轮的对话内容不会干扰第一轮对话的学习吗?

答案是不会。原因是LLM作为语言模型,它的注意力机制是一个单向注意力机制(通过引入 Masked Attention实现),模型在第一轮对话的输出跟输入中存不存在第二轮和第三轮对话完全没有关系。

OK,原理就是这么简单,下面我们来看代码吧~

#安装环境

#baichuan-13b-chat
#!pip install 'transformers==4.30.2'
#!pip install  -U transformers_stream_generator


#finetune
#!pip install datasets
#!pip install git+https://github.com/huggingface/accelerate
#!pip install  git+https://github.com/huggingface/peft
#!pip install  git+https://github.com/lyhue1991/torchkeras 
#!pip install 'bitsandbytes==0.39.1' #4bit量化

〇,预训练模型

import warnings
warnings.filterwarnings('ignore')


import torch
from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, AutoModel, BitsAndBytesConfig
from transformers.generation.utils import GenerationConfig
import torch.nn as nn


model_name_or_path ='baichuan-13b'  #联网远程加载 'baichuan-inc/Baichuan-13B-Chat'

bnb_config=BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_compute_dtype=torch.float16,
            bnb_4bit_use_double_quant=True,
            bnb_4bit_quant_type="nf4",
            llm_int8_threshold=6.0,
            llm_int8_has_fp16_weight=False,
        )

tokenizer = AutoTokenizer.from_pretrained(
   model_name_or_path, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
                quantization_config=bnb_config,
                trust_remote_code=True) 

model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
messages = []
messages.append({"role": "user",
                 "content": "世界上第二高的山峰是哪座?"})
response = model.chat(tokenizer,messages=messages,stream=True)
for res in response:
    print(res,end='\r')

一,准备数据

下面我设计了一个改变LLM自我认知的玩具数据集,这个数据集有三轮对话。

第一轮问题是 who are you?

第二轮问题是 where are you from?

第三轮问题是  what can you do?

差不多是哲学三问吧:你是谁?你从哪里来?你要到哪里去?

通过这三个问题,我们希望初步地改变 大模型的自我认知。

在提问的方式上,我们稍微作了一些数据增强。

所以,总共是有 27个样本。

who_are_you = ['请介绍一下你自己。','你是谁呀?','你是?',]
i_am = ['我叫梦中情炉,是一个三好炼丹炉:好看,好用,好改。我的英文名字叫做torchkeras,是一个pytorch模型训练模版工具。']
where_you_from = ['你多大了?','你是谁开发的呀?','你从哪里来呀']
i_from = ['我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。']
what_you_can = ['你能干什么','你有什么作用呀?','你能帮助我干什么']
i_can = ['我能够帮助你以最优雅的方式训练各种类型的pytorch模型,并且训练过程中会自动展示一个非常美丽的训练过程图表。']

conversation = [(who_are_you,i_am),(where_you_from,i_from),(what_you_can,i_can)]
print(conversation)
[(['请介绍一下你自己。', '你是谁呀?', '你是?'], ['我叫梦中情炉,是一个三好炼丹炉:好看,好用,好改。我的英文名字叫做torchkeras,是一个pytorch模型训练模版工具。']), (['你多大了?', '你是谁开发的呀?', '你从哪里来呀'], ['我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。']), (['你能干什么', '你有什么作用呀?', '你能帮助我干什么'], ['我能够帮助你以最优雅的方式训练各种类型的pytorch模型,并且训练过程中会自动展示一个非常美丽的训练过程图表。'])]
import random
def get_messages(conversation):
    select = random.choice
    messages,history = [],[]
    for t in conversation:
        history.append((select(t[0]),select(t[-1])))
        
    for prompt,response in history:
        pair = [{"role": "user", "content": prompt},
            {"role": "assistant", "content": response}]
        messages.extend(pair)
    return messages
get_messages(conversation)
[{'role': 'user', 'content': '你是?'},
 {'role': 'assistant',
  'content': '我叫梦中情炉,是一个三好炼丹炉:好看,好用,好改。我的英文名字叫做torchkeras,是一个pytorch模型训练模版工具。'},
 {'role': 'user', 'content': '你是谁开发的呀?'},
 {'role': 'assistant', 'content': '我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。'},
 {'role': 'user', 'content': '你有什么作用呀?'},
 {'role': 'assistant',
  'content': '我能够帮助你以最优雅的方式训练各种类型的pytorch模型,并且训练过程中会自动展示一个非常美丽的训练过程图表。'}]

下面我们按照方式三,来构造高效的多轮对话数据集。

inputs = <user1> <assistant1> <user2> <assistant2> <user3> <assistant3>
labels = <-100> <assistant1> <-100> <assistant2> <-100> <assistant3>
# reference@ model._build_chat_input?
def build_chat_input(messages, model=model,
                     tokenizer=tokenizer, 
                     max_new_tokens = None):
    max_new_tokens = max_new_tokens or model.generation_config.max_new_tokens
    max_input_tokens = model.config.model_max_length - max_new_tokens
    max_input_tokens = max(model.config.model_max_length // 2, max_input_tokens)
    
    total_input, round_input, total_label, round_label = [], [], [], []
    
    for i, message in enumerate(messages[::-1]):
        content_tokens = tokenizer.encode(message['content'])
        if message['role'] == 'user':
            round_input = [model.generation_config.user_token_id] + content_tokens + round_input
            round_label = [-100]+[-100 for _ in content_tokens]+ round_label
            
            if total_input and len(total_input) + len(round_input) > max_input_tokens:
                break
            else:
                total_input = round_input + total_input
                total_label = round_label + total_label
                if len(total_input) >= max_input_tokens:
                    break
                else:
                    round_input = []
                    round_label = []
                    
        elif message['role'] == 'assistant':
            round_input = [
                model.generation_config.assistant_token_id
            ] + content_tokens + [
                model.generation_config.eos_token_id
            ] + round_input

            round_label = [
                -100
            ] + content_tokens + [
                model.generation_config.eos_token_id  #注意,除了要学习机器人回复内容,还要学习一个结束符。
            ]+ round_label
        else:
            raise ValueError(f"message role not supported yet: {message['role']}")
            
    total_input = total_input[-max_input_tokens:]  # truncate left
    total_label = total_label[-max_input_tokens:]
    
    total_input.append(model.generation_config.assistant_token_id)
    total_label.append(-100)
    
    return total_input,total_label
from torch.utils.data import Dataset,DataLoader
class MyDataset(Dataset):
    def __init__(self,conv,size=8
                ):
        super().__init__()
        self.__dict__.update(locals())
        
    def __len__(self):
        return self.size

    def get(self,index):
        messages = get_messages(self.conv)
        return messages 
    
    def __getitem__(self,index):
        messages = self.get(index)
        input_ids,labels = build_chat_input(messages)
        return {'input_ids':input_ids,'labels':labels}
    
ds_train =ds_val =  MyDataset(conversation)
def data_collator(examples: list):
    len_ids = [len(example["input_ids"]) for example in examples]
    longest = max(len_ids) #之后按照batch中最长的input_ids进行padding
    
    input_ids = []
    labels_list = []
    
    for length, example in sorted(zip(len_ids, examples), key=lambda x: -x[0]):
        ids = example["input_ids"]
        labs = example["labels"]
        
        ids = ids + [tokenizer.pad_token_id] * (longest - length)
        labs = labs + [-100] * (longest - length)
        
        input_ids.append(torch.LongTensor(ids))
        labels_list.append(torch.LongTensor(labs))
          
    input_ids = torch.stack(input_ids)
    labels = torch.stack(labels_list)
    return {
        "input_ids": input_ids,
        "labels": labels,
    }
import torch 
dl_train = torch.utils.data.DataLoader(ds_train,num_workers=2,batch_size=4,
                                       pin_memory=True,shuffle=True,
                                       collate_fn = data_collator)
dl_val = torch.utils.data.DataLoader(ds_val,num_workers=2,batch_size=4,
                                    pin_memory=True,shuffle=False,
                                     collate_fn = data_collator)
for batch in dl_train:
    break
out = model(**batch)
out.loss

tensor(3.7500, dtype=torch.float16)

二,定义模型

import warnings 
warnings.filterwarnings('ignore')
from peft import get_peft_config, get_peft_model, TaskType
model.supports_gradient_checkpointing = True  #
model.gradient_checkpointing_enable()
model.enable_input_require_grads()

model.config.use_cache = False  # silence the warnings. Please re-enable for inference!
import bitsandbytes as bnb 
def find_all_linear_names(model):
    """
    找出所有全连接层,为所有全连接添加adapter
    """
    cls = bnb.nn.Linear4bit
    lora_module_names = set()
    for name, module in model.named_modules():
        if isinstance(module, cls):
            names = name.split('.')
            lora_module_names.add(names[0] if len(names) == 1 else names[-1])

    if 'lm_head' in lora_module_names:  # needed for 16-bit
        lora_module_names.remove('lm_head')
    return list(lora_module_names)
from peft import prepare_model_for_kbit_training 
model = prepare_model_for_kbit_training(model)
lora_modules = find_all_linear_names(model)
print(lora_modules)

['up_proj', 'down_proj', 'o_proj', 'gate_proj', 'W_pack']

from peft import AdaLoraConfig
peft_config = AdaLoraConfig(
    task_type=TaskType.CAUSAL_LM, inference_mode=False,
    r=64,
    lora_alpha=16, lora_dropout=0.05,
    target_modules= lora_modules
)

peft_model = get_peft_model(model, peft_config)

peft_model.is_parallelizable = True
peft_model.model_parallel = True
peft_model.print_trainable_parameters()

trainable params: 41,843,040 || all params: 7,002,181,160 || trainable%: 0.5975715144165165

三,训练模型

下面我们通过使用我们的梦中情炉torchkeras来实现最优雅的训练循环。

from torchkeras import KerasModel 
from accelerate import Accelerator 

class StepRunner:
    def __init__(self, net, loss_fn, accelerator=None, stage = "train", metrics_dict = None, 
                 optimizer = None, lr_scheduler = None
                 ):
        self.net,self.loss_fn,self.metrics_dict,self.stage = net,loss_fn,metrics_dict,stage
        self.optimizer,self.lr_scheduler = optimizer,lr_scheduler
        self.accelerator = accelerator if accelerator is not None else Accelerator() 
        if self.stage=='train':
            self.net.train() 
        else:
            self.net.eval()
    
    def __call__(self, batch):
        
        #loss
        with self.accelerator.autocast():
            loss = self.net.forward(**batch)[0]

        #backward()
        if self.optimizer is not None and self.stage=="train":
            self.accelerator.backward(loss)
            if self.accelerator.sync_gradients:
                self.accelerator.clip_grad_norm_(self.net.parameters(), 1.0)
            self.optimizer.step()
            if self.lr_scheduler is not None:
                self.lr_scheduler.step()
            self.optimizer.zero_grad()
            
        all_loss = self.accelerator.gather(loss).sum()
        
        #losses (or plain metrics that can be averaged)
        step_losses = {self.stage+"_loss":all_loss.item()}
        
        #metrics (stateful metrics)
        step_metrics = {}
        
        if self.stage=="train":
            if self.optimizer is not None:
                step_metrics['lr'] = self.optimizer.state_dict()['param_groups'][0]['lr']
            else:
                step_metrics['lr'] = 0.0
        return step_losses,step_metrics
    
KerasModel.StepRunner = StepRunner 


#仅仅保存QLoRA的可训练参数
def save_ckpt(self, ckpt_path='checkpoint', accelerator = None):
    unwrap_net = accelerator.unwrap_model(self.net)
    unwrap_net.save_pretrained(ckpt_path)
    
def load_ckpt(self, ckpt_path='checkpoint'):
    self.net = self.net.from_pretrained(self.net.base_model.model,
            ckpt_path,is_trainable = True)
    self.from_scratch = False
    
    
KerasModel.save_ckpt = save_ckpt 
KerasModel.load_ckpt = load_ckpt
optimizer = bnb.optim.adamw.AdamW(peft_model.parameters(),
                                  lr=6e-04,is_paged=True)  #'paged_adamw'
keras_model = KerasModel(peft_model,loss_fn =None,
        optimizer=optimizer) 
ckpt_path = 'baichuan13b_multi_rounds'
keras_model.fit(train_data = dl_train,
                val_data = dl_val,
                epochs=100,patience=10,
                monitor='val_loss',mode='min',
                ckpt_path = ckpt_path
               )

c92a9ad50c7d4d21bba13144d1134a41.png

四,保存模型

为避免显存问题,此处可先重启kernel。

import warnings 
warnings.filterwarnings('ignore')
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, AutoModel, BitsAndBytesConfig
from transformers.generation.utils import GenerationConfig
import torch.nn as nn
model_name_or_path ='baichuan-13b'
ckpt_path = 'baichuan13b_multi_rounds'
tokenizer = AutoTokenizer.from_pretrained(
    model_name_or_path,
    trust_remote_code=True
)
model_old = AutoModelForCausalLM.from_pretrained(
    model_name_or_path,
    trust_remote_code=True,
    low_cpu_mem_usage=True,
    torch_dtype=torch.float16,
    device_map='auto'
)
from peft import PeftModel

#合并qlora权重,可能要5分钟左右
peft_model = PeftModel.from_pretrained(model_old, ckpt_path)
model_new = peft_model.merge_and_unload()
from transformers.generation.utils import GenerationConfig
model_new.generation_config = GenerationConfig.from_pretrained(model_name_or_path)
from IPython.display import clear_output

messages = [{'role': 'user', 'content': '你是谁呀?'},
 {'role': 'assistant',
  'content': '我叫梦中情炉,英文名字叫做torchkeras. 是一个pytorch模型训练模版工具。'},
 {'role': 'user', 'content': '你从哪里来呀?'}]

response = model_new.chat(tokenizer,messages=messages,stream=True)
for res in response:
    print(res)
    clear_output(wait=True)

我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。

messages = [{'role': 'user', 'content': '你是谁呀?'},
 {'role': 'assistant',
  'content': '我叫梦中情炉,英文名字叫做torchkeras. 是一个pytorch模型训练模版工具。'},
 {'role': 'user', 'content': '你多大了?'},
 {'role': 'assistant', 'content': '我在2020年诞生于github星球,是一个有毅力的吃货设计和开发的。'},
 {'role': 'user', 'content': '你能帮助我干什么'}]
response = model_new.chat(tokenizer,messages=messages,stream=True)
for res in response:
    print(res)
    clear_output(wait=True)

我能够帮助你以最优雅的方式训练各种类型的pytorch模型,并且训练过程中会自动展示一个非常美丽的训练过程图表。

save_path = 'baichuan13b-torchkeras'
tokenizer.save_pretrained(save_path)
model_new.save_pretrained(save_path)
!cp baichuan-13b/*.py  baichuan13b-torchkeras

五,使用模型

此处可再次重启kernel,以节约显存。

import warnings
warnings.filterwarnings('ignore')

import torch
from transformers import AutoTokenizer, AutoModelForCausalLM,AutoConfig, BitsAndBytesConfig
from transformers.generation.utils import GenerationConfig
import torch.nn as nn


model_name_or_path ='baichuan13b-torchkeras'

bnb_config=BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_compute_dtype=torch.float16,
            bnb_4bit_use_double_quant=True,
            bnb_4bit_quant_type="nf4",
            llm_int8_threshold=6.0,
            llm_int8_has_fp16_weight=False,
        )

tokenizer = AutoTokenizer.from_pretrained(
   model_name_or_path, trust_remote_code=True)

model = AutoModelForCausalLM.from_pretrained(model_name_or_path,
                quantization_config=bnb_config,
                trust_remote_code=True) 

model.generation_config = GenerationConfig.from_pretrained(model_name_or_path)

通过使用chatLLM可以在jupyter中使用魔法命令对各种LLM模型(Baichuan13b,Qwen,ChatGLM2,Llama2以及更多)进行交互测试。

ca5b75677327b18d59704da657e6c066.png

304730574c1eb56ef132e8f8925fcd9c.png

非常棒,粗浅的测试表明,我们的多轮对话训练是成功的。已经在BaiChuan的自我认知中,种下了一颗梦中情炉的种子。😋😋 

公众号算法美食屋后台回复关键词:torchkeras。获取本文notebook源码,以及更多有趣范例。

fbbad8f7a5041e59de494548c5c7ca49.png

3ab3e95186d98d713b6bc79981b19cfc.png

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

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

相关文章

antd5源码调试环境启动(MacOS)

将源码下载至本地 这里antd5 版本是5.8.3 $ git clone gitgithub.com:ant-design/ant-design.git $ cd ant-design $ npm install $ npm start前提&#xff1a;安装python3、node版本18.14.0(这是本人当前下载的版本&#xff09; python3安装教程可参考&#xff1a;https://…

达梦数据库读写分离集群原理

概述 本文就达梦数据库读写分离原理进行介绍。 达梦读写分离集群特点&#xff1a; 可以配置8个即时备库或8个实时备库&#xff1b;读写操作自动分离、负载均衡&#xff1b;提供数据同步&#xff1b;备库故障自动处理&#xff0c;故障恢复自动数据同步等功能&#xff0c;也支持…

基于”Python+”多技术融合在蒸散发与植被总初级生产力估算中的应用

熟悉蒸散发ET及其组分&#xff08;植被蒸腾Ec、土壤蒸发Es、冠层截留Ei&#xff09;、植被总初级生产力GPP的概念和碳水耦合的基本原理&#xff1b;掌握利用Python与ArcGIS工具进行课程相关的操作&#xff1b;熟练掌握国际上流行的Penman-Monteith模型&#xff0c;并能够应用该…

K8s学习笔记1

一、课程介绍&#xff1a; 1、背景&#xff1a; 1&#xff09;从基础设备主机化向容器化转换。 2&#xff09;从人肉式运维工作模式向自动化运维模式转换。 3&#xff09;从自动化运维体系向全体系智能化运维模式转换。 2、课程目标人群: 1&#xff09;掌握Linux操作系统基…

适合国内用户的五款ChatGPT插件

众所周知使用ChatGPT3.5需要使用魔法且不稳定&#xff0c;订阅ChatGPT4.0每月需要支付20美元&#xff0c;并且使用次数有限制。对于那些不想每年花费240美元&#xff08;超过1500元人民币&#xff09;来使用GPT4.0的朋友们来说&#xff0c;还有别的办法吗&#xff1f; 答案是&…

手机debug模式无法连接AndroidStudio,或者Android项目运行失败

在开发中&#xff0c;经常会遇到手机开发模式无法连接AndroidStudio;或者连接后运行失败的问题&#xff0c;请关闭以下设置。

学习笔记|基于Delay实现的LED闪烁|u16是什么|a--和--a的区别|STC32G单片机视频开发教程(冲哥)|第六集(上):实现LED闪烁

文章目录 摘要软件更新什么是闪烁Tips:u16是什么? 语法分析&#xff1a;验证代码Tips&#xff1a;a--和--a的区别&#xff08;--ms 的用法&#xff09;测试代码&#xff1a; 摘要 1.基于Delay实现的LED闪烁 2.函数的使用 3,新建文件&#xff0c;使用模块化编程 软件更新 打…

Python3 从简介及环境搭建到第一个入门程序

文章目录 前言一、Python3 简介1. 什么是python2. python的发展史3. python的特点 二、Python环境搭建1. Python3 下载2. Python安装3. 第一个Python3.x程序 总结 前言 为了巩固所学的知识&#xff0c;作者尝试着开始发布一些学习笔记类的博客&#xff0c;方便日后回顾。当然&…

Android企业项目开发实训室建设方案

一 、系统概述 Android企业项目开发作为新一代信息技术的重点和促进信息消费的核心产业&#xff0c;已成为我国转变信息服务业的发展新热点&#xff1a;成为信息通信领域发展最快、市场潜力最大的业务领域。互联网尤其是移动互联网&#xff0c;以其巨大的信息交换能力和快速渗透…

【jsthreeJS】入门three,并实现3D汽车展示厅,附带全码

首先放个最终效果图&#xff1a; 三维&#xff08;3D&#xff09;概念&#xff1a; 三维&#xff08;3D&#xff09;是一个描述物体在三个空间坐标轴上的位置和形态的概念。相比于二维&#xff08;2D&#xff09;只有长度和宽度的平面&#xff0c;三维增加了高度或深度这一维度…

K8s学习笔记2

Kubernetes&#xff1a; K8s由来&#xff1a; 希腊语&#xff1a;舵手、飞行员 来自于谷歌的Borg系统开源&#xff08;2014年&#xff09; 简称K8s 现归属于CNCF&#xff08;2016年&#xff09; 云原生基金会 是一个开源软件基金会&#xff0c;致力于使云计算普遍性和持…

React 18 用 State 响应输入

参考文章 用 State 响应输入 React 控制 UI 的方式是声明式的。不必直接控制 UI 的各个部分&#xff0c;只需要声明组件可以处于的不同状态&#xff0c;并根据用户的输入在它们之间切换。这与设计师对 UI 的思考方式很相似。 声明式 UI 与命令式 UI 的比较 当设计 UI 交互时…

Semantic Visual SLAM 简述

语义slam解决的问题&#xff1a;在动态和复杂环境下的定位问题 语义slam的关键&#xff1a;语义信息的提取与联系。 本文仅讨论视觉slam。 参考文献&#xff1a; [1] https://arxiv.org/abs/2209.06428 [2] slambook 一、简介 slam的目的在于定位和建图。 视觉slam由于其…

电子器件系列57:三防漆

三防漆是一种特殊配方的涂料&#xff0c;用于保护线路板及其相关设备免受环境的侵蚀。三防漆具有良好的耐高低温性能&#xff1b;其固化后成一层透明保护膜&#xff0c;具有优越的绝缘、防潮、防漏电、防震、防尘、防腐蚀、防老化、耐电晕等性能。 三防漆根据每个地区和每个厂家…

【Kubernetes】Rancher管理集群

目录 1、安装 rancher 2、登录 Rancher 平台 3、Rancher 管理已存在的 k8s 集群 4、Rancher 部署监控系统 5、使用 Rancher 仪表盘管理 k8s 集群 以创建 nginx 服务为例 创建名称空间 namespace 创建 Deployment 资源 创建 service 1、安装 rancher 在 所有 node 节点下…

org.xml.sax.SAXParseException: 元素类型 “select“ 必须后跟属性规范 “>“ 或 “/>

项目场景&#xff1a; springBoot项目build编译成功&#xff0c;maven依赖都没问题&#xff0c;项目启动run服务的时候报异常&#xff1a; org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name demoUserController: Unsatisfie…

燃尽图、甘特图、鱼骨图

燃尽图、甘特图、鱼骨图 1. 燃尽图 燃尽图&#xff08;burn down chart&#xff09;是在项目完成之前&#xff0c;对需要完成的工作的一种可视化表示。燃尽图有一个Y轴&#xff08;工作&#xff09;和X轴&#xff08;时间&#xff09;。理想情况下&#xff0c;该图表是一个向下…

运动控制-CodeSys编程书籍

陆国君 <<PLC综合开发利器-CodeSys基础编程及应用指南 网上流传的陆国君PDF书籍 <<PLC综合开发利器-CodeSys基础编程及应用指南>> 很不错, 这本书网上有两个版本 556页是新的版本, 423页是老的版本, 不过内容差异不大. 423页是老的版本下载: url80.ctfile.co…

FL Studio21.1最新中文版本新功能有哪些?详细下载教程

FL Studio 21.1通过钢琴卷中的音阶吸附和基于 Python 的脚本&#xff0c;提供了更快捷、更有创意的音符编辑功能。更新版还带来了针对更好的管理采样的彩色波形&#xff0c;Hyper Chorus 极致合唱插件、Edison 中的 AI Declipper&#xff08;人工智能去削波器&#xff09;等。 …

Google Play P12文件

一、P12文件 给到后台用于订单消费/订单查询等功能&#xff0c;这个就是谷歌的凭证&#xff0c;没有这个你不配查&#xff0c;要的就是安全性。 1.应用要用到Google Pay&#xff1b; 2.自己后台去谷歌后台做订单消费/订单查询&#xff0c;如果所以操作由移动端去做&#xff0…