手把手!从头构建LLaMA3大模型(Python)

1. 前期准备

让我们先来想一想大概需要做什么。

首先是模型架构的选择。原工作用的是 GPT Neo 架构(可以看他们的 config),这个算是很老的模型了,最初是 EleutherAI 用来复现追踪 GPT-3 的工作的,现在用的也比较少了。我打算选用 LLaMA 架构,也算是符合研究主流、便于推广。LLaMA 3 主要多了个 GQA,也是现在模型的主流,我这里也用一下。

其次是数据的选择。既然是复现,就直接贯彻拿来主义,用原工作开源的数据集(主要是从头生成要花不少 api 费用)。原工作第一版的时候用的是 GPT-3.5 生成的数据,后面社区有人更新了第二版,是用 GPT-4 生成的,比原数据更好,就用它了。

最后是训练。其实我手上就两张 3060 12G 和 4060 Ti 16G,训这个确实是绰绰有余,但我还是不想在桌前吵我自己,于是继续用 Colab。现在 Colab 可以直接看到剩余使用时长了,虽然已经被砍到只有 3h 左右的用卡时间,但至少心里有个底,况且 3h 训我们这个也完全够了。

我们这次用到的 Hugging Face 的库如下:

在这里插入图片描述

理论上比较新的版本都没问题,但如果你很久没更新了,最好用 pip install -U 来升级一下。

我这里的用到的库版本如下,供参考:

在这里插入图片描述

另外,接下来的步骤讲解主要是以 jupyter notebook 的形式展开的,并不是 .py 文件的形式,也就是说前面执行的变量会在中间储存下来。

2. 原工作简介

虽然是练习,但既然打着复现工作的名头,还是来简要回顾一下原工作究竟做了什么吧。

原工作探索的问题是语言模型(LM)在文本连贯性上的表现。像早期的一些语言模型如 GPT-2,即使在一些 Common Craw 这样的语料库上大量预训练后,也很难生成长的、连贯的文本。比如前几年有一种 AI 玩具类型是做文本续写,例如彩云小梦,可以写写作文、小说什么的,如果大家玩过就知道效果其实一言难尽,和今天的大模型完全没法比,其实这就是 GPT-2 level 的续写能力。

作者就在想,会不会是因为训练的语料库太多、太宽泛,需要学习各种语法元素、词汇、知识、推理等等,才导致小语言模型(SLM)没法有一个很好的表现。作者决定专注于一个任务——短篇故事续写,来探索一下 LM 的性能边界。

作者用 GPT-4 和 GPT-3.5 构建了一个英文短篇小说数据集 TinyStories,将内容限制在三四岁儿童也能轻松理解的程度,并且使用不同的关键词来让故事的主题足够丰富。此外,他们还加入了额外的关键词,来控制故事有更曲折的走向、不同的结局等等。

作者用的模型基座架构是 GPT Neo,词表大小约为 50k,并且他们尝试了不同的模型参数,调整了隐藏层维度(hidden_size)、隐藏层数(num_hidden_layers)等,来探索不同参数对于模型性能的影响。

作者的评估方式是经典的 GPT-4 监督打分模式,就是让不同的 SLM 根据提示生成故事,然后 GPT-4 从设定好的不同维度来打分,主要有 Creativity、Grammar、Consistency 三项,分别代表创造性、语法正确性、上下文一致性。此外,作者额外加入了一套 TinyStories-Instruct 数据集,来训练一批指令微调的 SLM,并测试他们的指令跟随能力,也就是第四项 Instruct。

作者主要和 GPT-Neo 以及 GPT-2 的小中大杯进行了对比。

3. 模型初始化

让我们正式开始复现!

3.1 决定模型的参数

首先是定义我们自己的模型。由于 LLaMA 3 的架构早就集成于 transformers 库中,因此我们可以直接用 AutoConfig 初始化一个模型配置,传入参数 model_type=‘llama’ 即可。

架构确定了,那么现在来探讨一下模型具体参数,比如隐藏层大小、隐藏层数等等。我们先来看看 TinyStories 原工作的实验结果:

图片

可以看到,隐藏层维度从 64 增长到 256 时的收益是比较大的,往后收益就逐渐放缓了。而层数的影响并不如隐藏层维度那么大,大而浅的网络也能有不错的表现(例如 hidden_size=1024, num_hidden_layers=1 的模型)。综合考虑,我这里选择 hidden_size=256 和 num_hidden_layers=4。

其他参数方面,我们遵循现在主流的研究表现,将 FFN 的维度从传统的 4 倍隐藏层维度设为 8/3 倍(按 128 向上取整)。头的数目我们设为 16,并应用 GQA 机制。GQA 的实现在 transformers 中非常简单,只需要配置 num_key_value_heads 即可。num_key_value_heads 取值和 num_attention_heads 相同时即为 MHA 机制,取值为 1 时即为 MQA 机制。

图片

综上,我们的配置如下:

在这里插入图片描述

在这里插入图片描述

3.2 分词器 Tokenizer

我这里选用 LLaMA 2 的分词器,因为二代的词表比较小(32k),LLaMA 3 的词表太大了(128k),在 SLM 中会占用太多的参数比重,并且这只是个专有任务数据训练,没必要用太大的词表。

# 分词器
from transformers import AutoTokenizer

tokenizer = AutoTokenizer.from_pretrained('NousResearch/Llama-2-7b-hf')

'''
LlamaTokenizerFast(name_or_path='NousResearch/Llama-2-7b-hf', vocab_size=32000, model_max_length=1000000000000000019884624838656, is_fast=True, padding_side='left', truncation_side='right', special_tokens={'bos_token': '<s>', 'eos_token': '</s>', 'unk_token': '<unk>', 'pad_token': '<unk>'}, clean_up_tokenization_spaces=False),  added_tokens_decoder={
    0: AddedToken('<unk>', rstrip=False, lstrip=False, single_word=False, normalized=True, special=True),    
    1: AddedToken('<s>', rstrip=False, lstrip=False, single_word=False, normalized=True, special=True),    
    2: AddedToken('</s>', rstrip=False, lstrip=False, single_word=False, normalized=True, special=True),
}
'''

另外注意这里 padding_side=‘left’,如果不是的话需要设置 tokenizer.padding_side=‘left’,即批量填充的时候从左边开始填充,这对于 decoder-only 的模型做生成任务是必要的,因为我们本质上做的是 next token prediction,如果 pad 挡在了生成序列的右边,会影响到模型生成。

在这里插入图片描述

3.3 模型实例化

接下来就是实例化模型,这里就不用从预训练模型加载 from_pretrained() 了,而是从配置加载 from_config():

在这里插入图片描述
在这里插入图片描述

可以看到,k_proj 和 v_proj 的 out_features 从 256 变为了 128,这即是 GQA 机制。

此时,模型已经初始化了,让我们来打印一下看看参数:

# 打印模型的每一层及其参数大小
def print_model_parameters(model):
    print('Layer Name & Parameters')    
    print('----------------------------')    
    total_params = 0    
    for name, parameter in model.named_parameters():    
        param_size = parameter.size()        
        param_count = torch.prod(torch.tensor(param_size)).item()        
        total_params += param_count        
        print(f'{name:50} | Size: {str(param_size):30} | Count: {str(param_count):20}')    
    print('----------------------------')    
    print(f'Total Parameters: {total_params} ({total_params / 1000000:.1f} M)')

print_model_parameters(model)

得到结果如下:

Layer Name & Parameters
----------------------------
model.embed_tokens.weight                          | Size: torch.Size([32000, 256])       | Count: 8192000     
model.layers.0.self_attn.q_proj.weight             | Size: torch.Size([256, 256])         | Count: 65536       
model.layers.0.self_attn.k_proj.weight             | Size: torch.Size([128, 256])         | Count: 32768       
model.layers.0.self_attn.v_proj.weight             | Size: torch.Size([128, 256])         | Count: 32768       
model.layers.0.self_attn.o_proj.weight             | Size: torch.Size([256, 256])         | Count: 65536       
model.layers.0.mlp.gate_proj.weight                | Size: torch.Size([768, 256])         | Count: 196608     
model.layers.0.mlp.up_proj.weight                  | Size: torch.Size([768, 256])         | Count: 196608     
model.layers.0.mlp.down_proj.weight                | Size: torch.Size([256, 768])         | Count: 196608     
中间省略...
model.layers.3.input_layernorm.weight              | Size: torch.Size([256])              | Count: 256         
model.layers.3.post_attention_layernorm.weight     | Size: torch.Size([256])              | Count: 256         
model.norm.weight                                  | Size: torch.Size([256])              | Count: 256         lm_head.weight                                     | Size: torch.Size([32000, 256])       | Count: 8192000     
----------------------------
Total Parameters: 19532032 (19.5 M)

可以看到,我们的模型只有不到 20M!非常非常小,并且其中 Embedding 占了大头。

尽管模型还没有训练,但我们仍然可以测试一下推理:

def inference(
    model: AutoModelForCausalLM,    
    tokenizer: AutoTokenizer,    
    input_text: str = 'Once upon a time, ',    
    max_new_tokens: int = 16
):
    inputs = tokenizer(input_text, return_tensors='pt').to(device)    
    outputs = model.generate(    
        **inputs,        
        pad_token_id=tokenizer.eos_token_id,        
        max_new_tokens=max_new_tokens,        
        do_sample=True,        
        top_k=40,       
        top_p=0.95,        
        temperature=0.8    
    )    
    generated_text = tokenizer.decode( 
        outputs[0],        
        skip_special_tokens=True    
    )    
    # print(outputs)    
    print(generated_text)
inference(model, tokenizer)

'''
Once upon a time, Hostย crimeine /\ könnenlinewidth measurementresol perfectly Taylor measèresiones assetviron
'''

嗯,的确是胡言乱语呢,不过可以正常推理,说明模型没问题!

但现在模型是随机初始化的,为了让模型更好地收敛,我们最好给模型一个更好的初始化方法,我这里选用 Kaiming 初始化,比较适用于 ReLU 类的激活,当然也可以选用高斯初始化、Xavier 初始化等等。

# Kaiming 初始化
def kaiming_initialization(model):
    for name, param in model.named_parameters():   
        if 'weight' in name and param.dim() > 1:            
            torch.nn.init.kaiming_uniform_(param, mode='fan_in', nonlinearity='leaky_relu')        
        elif 'bias' in name:            
        # 一般偏置项可以初始化为 0            
        torch.nn.init.constant_(param, 0)
        
kaiming_initialization(model)

现在,我们的模型真正初始化完成了!如果你愿意,可以先将这个初始化好的模型保存到本地,用 save_pretrained() 即可。

4. 数据集

让我们继续!

4.1 加载数据集

我们接下来需要从 Hugging Face 加载数据集,我这里是建立在网络畅通的基础上的,如果你没有用 Colab 或者网络无法直连 Hugging Face,那么也可以先下载到本地某个文件夹中,load_dataset 也可以直接读取本地文件夹。我们要用的数据集路径如下:noanabeshima/TinyStoriesV2 · Datasets at Hugging Face

https://huggingface.co/datasets/noanabeshima/TinyStoriesV2
# 加载数据集
from datasets import load_dataset

dataset_name_or_path = 'noanabeshima/TinyStoriesV2'        # 可以替换为本地文件夹路径
# ds_train = load_dataset(dataset_name_or_path, split='train')        # 取全部数据
ds_train = load_dataset(dataset_name_or_path, split='train[:10%]')    # 只取前 10 %,约 270k 条
ds_val = load_dataset(dataset_name_or_path, split='validation')

print(ds_train)
print(ds_val)

'''
Dataset({
    features: ['text'],    
    num_rows: 271769
})
Dataset({
    features: ['text'],    
    num_rows: 27629
})
'''

我们来看看数据长什么样子:

# 查看前两条
print(ds_train[:2])

'''
{'text': ['Once upon a time, there was a reliable otter named Ollie. He lived in a river with his family. They all loved to play and swim together.\nOne day, Ollie\'s mom said, 'Ollie, hurry and get some fish for dinner!' Ollie swam fast to catch fish. He saw his friend, the duck. 'Hi, Ollie!' said the duck. 'Hi, duck!' said Ollie. 'I need to hurry and catch fish for my family.'\nWhile Ollie was catching fish, he found a big shiny stone. He thought, 'This is not a fish, but it is so pretty!' Ollie took the shiny stone home to show his family. They all looked at the shiny stone and smiled. The shiny stone made everyone happy, and they forgot about the fish for dinner.',
  'One day, a little boy named Tim went to the park. He saw a big tiger. The tiger was not mean, but very easy to play with. Tim and the tiger played all day. They had lots of fun.\nThen, something unexpected happened. The tiger started to shake. Tim was scared. He did not know what was going on. But then, the tiger turned into a nice dog. Tim was very surprised.\nTim and the dog played together now. They were very happy. The dog was easy to play with too. At the end of the day, Tim went home with his new friend.']}
'''

这里需要注意,datasets 加载后的数据是 Dict[str, List[str]] 的形式的,并非 List[Dict[str, str]]。

在这里插入图片描述

4.2 数据预处理

接下来,我们要将数据预处理一下,也就是用 tokenizer 进行 tokenize。让我们来写一个处理函数:

在这里插入图片描述

我们来解析一下其中的一些点:

tokenizer 的 encode

encoded_texts = tokenizer(examples['text'], add_special_tokens=False)

根据前面的示例,我们知道这里 examples[‘text’] 其实是一个 List[str],当一个 List 传入 tokenizer() 时,tokenizer 会自动进行 batch encode,得到的是 {‘input_ids’: List[int], ‘attention_mask’: List[int]}(当然,如果设置了 return_tensors=‘pt’ 就会得到 Tensor)。

add_special_tokens=False 则是让 tokenizer 不要加上特殊 token,在 LLaMA 中就是不会在句首加上 bos_token

在这里插入图片描述

填充还是截断?

在这里插入图片描述

在这里,我采用直接截断的方式,最大截取当前输入序列的后 (max_token - 1) 位,再加上一个 eos_token_id,组成总长度不超过 max_token 的序列。attention_mask 的长度保持一致,全为 1。

这里利用到了 list 的切片特性,input_ids[-max_token+1:] 可以获取 min(max-token, len(input_ids)) - 1 的序列。

当然,也可以采取将超出长度部分再按照 max_token 来分块,重新组装。

应用在所有数据上

接下来,我们用 map() 函数,来将 process_func() 应用到 ds_train 和 ds_val 中的每个样本:

在这里插入图片描述

数据预处理成功!

4.3 数据批处理——DataCollator

4.3.1 两行代码

我们在训练的时候往往不会一条一条训练,而是成批次地训练,那么我们就需要对数据做批处理。因此我们需要用到 transformers 中的一个工具系列——DataCollator[1]。

既然是预训练,那么就是让模型在语料上做无监督学习,也就是我们熟知的 next token prediction,即根据前面的所有输入来预测下一个 token,然后把新的 token 拼接在已有输入上作为下一输入,如此往复,直到触发停止设定(例如触发 max_new_tokens)。

所以我们的训练目标——或者说是 label——显而易见,就是把输入偏移一位当作预测目标,我们计算的就是输出和这个目标之间的 loss:

在这里插入图片描述

所以我们只需要把 input_ids 复制一份、再偏移一位,就可以作为 labels 了。

……再等等,让我们看看这条问题:Shifting ids to the right when training GPT-2 on text generation? - Beginners - Hugging Face Forums[2]:

这里 sgugger 提到,在 Hugging Face 的实现里,training 时已经实现好了偏移一位的逻辑,不需要我们再手动实现了。我们也可以在 transformers 的源码里看到这一点,例如 LLaMA[3] 的实现。

图片

所以,我们只需要将 input_ids 直接复制一份作为 labels 即可。

那么怎么做呢?我们可以用 DataCollatorForLanguageModeling,并设置 mlm=False:

from transformers import DataCollatorForLanguageModelingdata_collator =   

DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False)

两行代码,非常简单!

不过我们可以稍微多讲一点,这个 data collator 是如何发挥作用的?为什么选的是它而不是在微调中更常见的 DataCollatorForSeq2Seq?

4.3.2 More things……

实际上,就像 mlm 这个参数所显示的一样,DataCollatorForLanguageModeling 一开始其实是设计给 Bert 的 MLM 任务的。MLM 任务就是 Masked Language Modeling,掩码语言建模,就是在一整个序列中挑选一部分 token 用 [mask] 给盖住,让模型去根据上下文预测被盖住的是什么 token。

# MLM 任务示意
今 天 早 上 下 [mask] 了     # input
今 天 早 上 下   雨   了     # label

可以看到,这样建立的 label 就是原来的 input 的 copy,它是将 input 中随机 mask 一部分,label 不变。

这几乎就是我们想要的——只是不需要 mask。所以,我们设置 mlm=False 后,就可以直接得到 input_ids 的 copy 了。

让我们继续看看 DataCollatorForLanguageModeling 怎么作用的:
在这里插入图片描述

可以看到:

  • • 由于长度不一,所以 data collator 做了 padding,padding 的方向就是我们 3.2 中提到的 tokenizer.padding_size
  • • labels 确实是 input_ids 的原位复制,区别在于 input_ids 里用 pad_token_id 来填充,labels 里对应的是 -100、表示不计算 loss

那么微调里常用的 DataCollatorForSeq2Seq 又是如何作用的呢?我们仍然用刚刚的数据例子:

在这里插入图片描述

可以发现,DataCollatorForSeq2Seq 和 DataCollatorForLanguageModeling 一样,做了批处理和 padding,但是没有标签 labels。原因是:DataCollatorForSeq2Seq 设计之初用于的任务和它的名字一样,是序列到序列(seq2seq)任务,放到文本任务上,就是要有两个 seq:输入 text 和 输出 label,比如下面的例子:

在这里插入图片描述

可以看到:

  • • DataCollatorForSeq2Seq 需要指定当前输入的文本和后面需要生成的文本,即 text 和 label,如果像 DataCollatorForLanguageModeling 那样处理会得不到 labels 字段
  • • 因此,DataCollatorForSeq2Seq 适合有监督微调(SFT),输入是 text,输出是 label,非常合理

5. 超迷你 LLaMA,启动!

5.1 配置训练参数

我们需要用到 transformers 的 TrainingArguments 来配置训练参数,具体参数说明可以看这里。

from transformers import TrainingArguments

training_args = TrainingArguments(
    output_dir='saves',                         # 输出路径,包括模型检查点、中间文件等    
    overwrite_output_dir=True,                  # 是否覆写 
    output_dir    do_train=True,                              # 是否做训练    
    do_eval=True,                               # 是否做评估    
    eval_steps=1000,                            # 评估步骤间隔    
    per_device_train_batch_size=4,              # 每设备批次    
    gradient_accumulation_steps=1,              # 梯度累计步大小,省显存,但小模型没必要,用 1 收敛比较快    
    learning_rate=1e-4,                         # 学习率大小    
    lr_scheduler_type='cosine',                 # 学习率调度策略,LLM 训练一般都用余弦    
    bf16=torch.cuda.is_bf16_supported(),        # 尝试配置 bf16    
    fp16=not torch.cuda.is_bf16_supported(),    # bf16 不行就上 fp16    
    logging_steps=50,                           # 打印步骤间隔    report_to=None,                             # 日志输出目标,不想用 wandb 可以设置为 None    
    num_train_epochs=2,                         # 训练轮数,2 ~ 3 即可    
    save_steps=1000,                            # 检查点保存步骤间隔    
    save_total_limit=2,                         # output_dir 内留存的检查点最大数目    seed=3407                                   # 随机种子
)

如果你之前用了 wandb,现在想禁用掉,可以设置环境变量:

import os

os.environ['WANDB_DISABLED'] = 'true'

5.2 配置 Trainer

同样地,具体参数说明可以看这里。

from transformers import Trainer

trainer = Trainer(
    model=model,                    # 模型实例    
    args=training_args,             # 训练参数    
    train_dataset=ds_train,         # 训练集    
    eval_dataset=ds_val,            # 验证集(评估集)    
    tokenizer=tokenizer,            # 分词器    
    data_collator=data_collator,    # data collator
)

5.3 训练与保存

配置好 Trainer 后,通过下列代码即可启动训练:

trainer.train()

接下来只需要等待训练完成。我用一个半小时训练了 2 epochs,loss 达到了 1.6 左右。

训练完成后,如果用的是 jupyter notebook,那么此时 model 已经是训练好的状态了。我们可以再次推理试试看:

inference(
    model,    
    tokenizer,    
    'Once upon a time, in a beautiful garden, there lived a little rabbit named Peter Rabbit.',    
    max_new_tokens=256)

得到如下结果:

Once upon a time, in a beautiful garden, there lived a little rabbit named Peter Rabbit. Peter had a friend named Rosie. They loved to play together. They would run, jump, and laugh all day long.
One day, Robby saw a big box in his yard. He was curious and wanted to know what was inside. So, he went to his friend's house and asked, 'What are you doing, Spark?' May replied, 'I am making this big box in the garden, and I am trying to open it!'
Timmy and Hopper went to find the big box. They found a key under a tree. They opened the box and found many toys inside. They were so happy to have a fun day with their new friend. They played with the toys all day long. And from that day on, whenever Ellie was a part of something, they would always remember the day they met by the big pond.

可以看到:

  • • 20M 模型确实能够流畅续写故事了
  • • 20M 模型写出的故事的语法、流畅度都不错,但是一致性欠佳,特别是故事主题、人名的前后连贯性不高

总之,这个超迷你 LLaMA 3 确实训练完成了!我们可以将它保存到本地:

model_path = '...'

model.save_pretrained(model_path)

也可以推送到 Hugging Face:

from huggingface_hub import notebook_login

repo_name = 'TinyStories-LLaMA2-20M-256h-4l-GQA'

notebook_login()    # 输入 Access Tokens

model.push_to_hub(repo_name)
tokenizer.push_to_hub(repo_name)

6. 结尾

这次尝试用 Trainer 来做一个模型的预训练,以往都是用 Trainer 来做微调,这次也算是学习了一下吧。TinyStories 这个工作之前就有关注过,但一直没顾上来复现一下,这次也算是简单复现了个小模型出来,和原工作的丰富度确实是比不了,但也算完成一个 todo。

后面这个小模型可以继续做 SFT,也就是做指令微调,可以和原工作一样,给定故事背景、关键词、开头让小模型续写,也可以迁移到别的任务。不过由于我们的预训练任务只针对了讲短篇故事这一类任务,加上参数又特别少,如果直接迁移其它指令任务估计表现不会很好。

如何学习大模型

现在社会上大模型越来越普及了,已经有很多人都想往这里面扎,但是却找不到适合的方法去学习。

作为一名资深码农,初入大模型时也吃了很多亏,踩了无数坑。现在我想把我的经验和知识分享给你们,帮助你们学习AI大模型,能够解决你们学习中的困难。

我已将重要的AI大模型资料包括市面上AI大模型各大白皮书、AGI大模型系统学习路线、AI大模型视频教程、实战学习,等录播视频免费分享出来,需要的小伙伴可以扫取。

一、AGI大模型系统学习路线

很多人学习大模型的时候没有方向,东学一点西学一点,像只无头苍蝇乱撞,我下面分享的这个学习路线希望能够帮助到你们学习AI大模型。

在这里插入图片描述

二、AI大模型视频教程

在这里插入图片描述

三、AI大模型各大学习书籍

在这里插入图片描述

四、AI大模型各大场景实战案例

在这里插入图片描述

五、结束语

学习AI大模型是当前科技发展的趋势,它不仅能够为我们提供更多的机会和挑战,还能够让我们更好地理解和应用人工智能技术。通过学习AI大模型,我们可以深入了解深度学习、神经网络等核心概念,并将其应用于自然语言处理、计算机视觉、语音识别等领域。同时,掌握AI大模型还能够为我们的职业发展增添竞争力,成为未来技术领域的领导者。

再者,学习AI大模型也能为我们自己创造更多的价值,提供更多的岗位以及副业创收,让自己的生活更上一层楼。

因此,学习AI大模型是一项有前景且值得投入的时间和精力的重要选择。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript运行原理和执行过程

参考&#xff1a; https://www.cnblogs.com/hexrui/p/15939592.html 1、执行上下文栈&#xff08;调用栈&#xff09; GECGlobal Execution Context&#xff08;GEC&#xff09;被放入到ECS&#xff08;Execution Context Stack&#xff0c;简称ECS&#xff09;中 GEC开始执…

走嵌入式方向有必要参加数模的比赛,涨一下见识吗?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「嵌入式的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;参加数模&#xff08;数学…

MYSQL 四、mysql进阶 3(存储引擎)

mysql中表使用了不同的存储引擎也就决定了我们底层文件系统中文件的相关物理结构。 为了管理方便&#xff0c;人们把连接管理、语法解析、查询优化这些并不涉及真实数据存储的功能划分为 Mysql Server的功能&#xff0c;把真实存取数据的功能划分为存储引擎的功能&…

【Unity】实现分屏开发

前言&#xff1a; 最近有个项目二期需要做分屏开发&#xff0c;今天恰好研究一下为后续的项目做个准备。 原理 整体的实现还是蛮简单的&#xff0c;主要是通过camera的一个targetDisplay属性进行设置 可以看到unity支持最多八个分屏 实现 场景搭建 &#xff0c;这里直接使…

Linux mongodb安装及简单使用

说明&#xff1a;本文章主要是对mongodb的单击安装 1.创建文件夹&#xff0c;准备安装包 cd /user/local mkdir tools 2.解压mongodb包 mkdir mongodb tar -xvf mongodb-linux-x86_64-rhel70-5.0.11.tgz -C mongodb 3.进入解压目录 cd mongodb cd mongodb-linux-x86_64-…

别再全网找了,这四款良心软件,还你一个清爽的电脑桌面

现在电脑桌面上软件多得吓人。 要是不整理&#xff0c;看着就闹心&#xff1b;整理起来呢&#xff0c;又累得够呛。 所以&#xff0c;很多人干脆就不用那些“用着没意思&#xff0c;删了又觉得可惜”的软件了。 但不管你怎么删&#xff0c;有些软件还是得留着&#xff0c;就像…

数据治理创新路:建设数据集市,强化数据报送一致性新实践

随着信息化和数字化的飞速发展&#xff0c;数据已经成为企业运营和决策的核心要素。然而&#xff0c;数据治理的复杂性和多样性给企业带来了不小的挑战。为了更好地应对这些挑战&#xff0c;许多企业开始探索数据治理的创新路径&#xff0c;其中建设数据集市和强化数据报送一致…

罗盘时钟屏保你见过吗?非常有特色的电脑时钟屏保

很多人都用过屏保&#xff0c;印象中系统自带的屏保&#xff0c;款式比较少&#xff0c;就那几款&#xff0c;在桌面飞来飞去的动画&#xff0c;基本都不怎么好看&#xff0c;特别有印象的就是那种泡泡屏保&#xff0c;这个算是比较美观的了。今天小编给大家介绍一款非常有特色…

正则表达式写起来不简单,但用起来真香

说在前面 &#x1f388;整理一些常见常用的正则表达式。 常见的正则表达式 1、手机号码 /^[1][3456789][0-9]{9}$/这个正则表达式 /^[1][3456789][0-9]{9}$/ 用于匹配中国的手机号码的一部分&#xff0c;但不包括全部有效的手机号码格式。下面是对它的详细解释&#xff1a; ^…

Linux 防火墙 Firewall 和 Iptables 的使用

如果我们在Linux服务器的某个端口上运行了个服务&#xff0c;需要外网能访问到&#xff0c;就必须通过防火墙将服务运行端口给开启。Linux中有两种防火墙软件&#xff0c;CentOS7.0以上使用的是firewall&#xff0c;CentOS7.0以下使用的是iptables&#xff08;使用较少且不建议…

React 通信:深层传递(Props、Context、Children Jsx)

在之前的文章 探讨&#xff1a;围绕 props 阐述 React 通信 中总结了关于“父子”组件传值&#xff0c;但是当需要在组件树中深层传递参数以及需要在组件间复用相同的参数时&#xff0c;传递 props 就会变得很麻烦。 实际案例&#xff1a; 下述展示有两种状态&#xff1a;① 详…

【BFS算法】广度搜索·由起点开始逐层向周围扩散求得最短路径(算法框架+题目)

0、前言 深度优先搜索是DFS&#xff08;Depth Frst Search)&#xff0c;其实就是前面所讲过的回溯算法&#xff0c;它的特点和它的名字一样&#xff0c;首先在一条路径上不断往下&#xff08;深度&#xff09;遍历&#xff0c;获得答案之后再返回&#xff0c;再继续往下遍历。…

python 【包含数据预处理】基于词频生成词云图

基于词频生成词云图 背景目的 有一篇中文文章&#xff0c;或者一本小说。想要根据词频来生成词云图。 为什么中文需要分词 中文分词是理解和处理中文文本的关键步骤&#xff0c;它直接影响到后续的文本分析和信息提取的准确性和有效性。 无明显单词分隔&#xff1a;中文文本不…

jumpserver堡垒机集群搭建

1、环境 操作系统&#xff1a;龙蜥os 7.9 firewall-cmd --permanent --zonepublic --remove-servicessh firewall-cmd --permanent --zonepublic --add-rich-rulerule familyipv4 source address10.90.101.1 port port22 protocoltcp accept firewall-cmd --reload2、安装NFS…

Blazor 中基于角色的授权

介绍 Blazor用于使用 .NET 代码库创建交互式客户端 Web UI。Microsoft 默认在 Blazor 应用程序中提供了一个用于身份验证和授权的身份框架。请注意&#xff0c;他们目前使用 MVC Core Razor 页面作为身份验证 UI。使用“Microsoft.AspNetCore.Identity.UI”包库来实现这一点。…

vue-cli 根据文字生成pdf格式文件 jsPDF

1.安装jspdf npm install jspdf --save 2.下载ttf格式文件 也可以用C:\Windows\Fonts下的字体文件&#xff0c;反正调一个需要的ttf字体文件就行&#xff0c;但有的字体存在部分字体乱码现象 微软雅黑ttf下载地址&#xff1a; FontsMarket.com - Download Microsoft YaHei …

2024zzb理论

1.下列图表中&#xff0c;能够识别异常值的是() 2.下列选项中&#xff0c;用于绘制单变量分布的函数是() 3.下列函数中&#xff0c;可以没置当前图形轴范围的是 4.下列函数中&#xff0c;可以为图表设置标题的是()。 5.下列pyplot模块的函数中&#xff0c;可以绘制散点图的是()…

11.【机器学习】十大算法之一随机森林算法原理讲解

【机器学习】十大算法之一随机森林算法原理讲解 一摘要二个人简介三什么是随机森林&#xff1f;3.1 决策树3.2 集成方法3.2.1 Bagging方法3.2.2 Boosting方法 3.3 随机森林算法3.4 随机的含义 四案例演示一4.1 利用随机森林进行特征选择&#xff0c;然后使用SVR进行训练4.2 利用…

数据中心节能策略

管理员可以运用多种技术来提升数据中心的能源效率。实际上&#xff0c;通过实施一些简单的改进措施&#xff0c;就能大幅减少能源消耗。 基础设施电力需求可能会增加运营费用。数据中心管理员可以通过满足CPU、存储和冷却系统的电源要求来降低公用事业成本。 数据中心的能源成…

假装热闹的618!商家被榨干,大主播集体哑火……

一年一度的618落下了帷幕&#xff0c;不见平台的战报&#xff0c;也不见品牌的战报&#xff0c;那些恨不得每小时公布一次销售数据的大主播&#xff0c;似乎也哑火了。 在热搜上&#xff0c;除了平台花钱买来假装热闹的话题&#xff0c;就是无数商家的叫苦&#xff0c;而消费者…