经典神经网络(15)GLM模型原理详解及其微调(文本摘要)

news2024/11/25 1:03:50

经典神经网络(15)GLM模型原理详解及其微调(文本摘要)

  • 2024年01月16日,智谱推出新一代基座大模型 GLM-4。新一代基座大模型 GLM-4 的整体性能相比上一代大幅提升,十余项指标逼近或达到 GPT-4;支持更长上下文;更强的多模态;支持更快推理速度,更多并发,大大降低推理成本;同时 GLM-4 增强了智能体能力。
  • GLM-4 实现自主根据用户意图,自动理解、规划复杂指令,自由调用网页浏览器、Code Interpreter 代码解释器和多模态文生图大模型,以完成复杂任务。
  • 今天,我们了解下由清华大学研发的初代GLM模型。
    • 论文:https://arxiv.org/pdf/2103.10360

1 GLM模型简介

作者在论文中表示,现有的预训练框架大体上可以分为三类:

  • 自回归模型。例如 GPT,学习的是从左到右的语言模型。虽然在长文本生成方面取得了成功,并且在参数达到数十亿时展示了少样本学习能力,但其固有的缺点是单向注意机制,不能完全捕捉自然语言理解(NLU)任务中上下文词之间的依赖关系。
  • 自编码模型。例如 BERT,通过去噪目标(例如掩码语言模型,MLM)学习双向上下文编码器。编码器生成适合自然语言理解任务的上下文化表示,但不能直接用于文本生成。
  • 编码器-解码器模型。在编码器中采用双向注意机制,在解码器中采用单向注意机制,并在二者之间采用交叉注意机制。这些模型通常用于条件生成任务,例如文本摘要和响应生成。
    • 基于编码器-解码器架构的T5模型统一了自然语言理解(NLU)和条件生成,但需要更多的参数才能与基于 BERT 的模型的性能相匹配。
    • 这些预训练框架都不够灵活,无法在所有自然语言处理(NLP)任务中竞争。
    • 之前的研究尝试通过多任务学习来统一不同的框架,然而,由于自编码和自回归目标在本质上存在差异,简单的统一无法充分继承这两种框架的优点。
  • 基于以上的原因,清华研究团队提出了一种基于自回归空白填充(Autoregressive Blank Infilling)的预训练模型,名为 GLM(General Language Model)。
    • GLM 通过添加二维位置编码并允许以任意顺序预测片段,提高了空白填充预训练的效果,这在 NLU 任务中带来了比 BERT 和 T5 更好的性能提升。
    • 同时,通过改变空白的数量和长度,GLM 可以针对不同类型的任务进行预训练。
    • 在涵盖 NLU、条件生成和无条件生成的广泛任务中,GLM 在相同模型规模和数据量下,性能优于 BERT、T5 和 GPT。

1.1 模型介绍

1.1.1 预训练目标—自回归空白填充

作者从 λ=3 的泊松分布中随机采样 span 长度,反复采样新的 span,直到至少有 15% 的原始 token被掩码。根据经验,作者发现 15%`的比例对于下游 NLU 任务的良好表现至关重要。

为了开启 autoregressive generation ,每个 span 都被填充了 special token,即 [START] (记做 [S])和 [END] (记做[E]),分别用于输入和输出。通过这种方式,在一个统一的模型中自动学习双向编码器(用于 Part A)和单向解码器(用于 Part B)。下图说明了 GLM 的实现。

在这里插入图片描述

  • 如上图左部分所示:

    • (a)原始文本为 [ x 1 , x 2 , x 3 , x 4 , x 5 , x 6 ] [x_1, x_2, x_3, x_4, x_5, x_6] [x1,x2,x3,x4,x5,x6],对原始文本随机进行连续mask。这里假设mask掉2个text span [ x 3 ] [x_3] [x3] [ x 5 , x 6 ] [x_5, x_6] [x5,x6]
    • (b)将 [ x 3 ] [x_3] [x3] [ x 5 , x 6 ] [x_5, x_6] [x5,x6]替换为 [ M ] [M] [M] 标志,并打乱Part B的顺序。随机交换span的顺序,这是为了捕捉span之间的内在联系。
  • 如上图中间部分所示:

    • GLM 自回归地生成 Part B,每个span前面都加上 [S] 作为输入,后面加上 [E] 作为输出。
    • 二维位置编码表示不同片段之间和片段内部的位置关系:
      • Position 1:表示片段在原始文本中的相对位置。如上图所示, [ x 3 ] \left[ x_3 \right] [x3] [ x 5 , x 6 ] \left[ x_5,x_6 \right] [x5,x6]被挖去。那么,被挖去的片段在第一个维度上的位置编码就是它们在原始文本中的索引,即 [ x 3 ] \left[ x_3 \right] [x3]来自片段 3, [ x 5 , x 6 ] \left[ x_5,x_6 \right] [x5,x6]来自片段 5。
      • Position 2:表示片段内部的相对位置。对于 Part A 的词,它们的第二个位置 id 都是0。对于 Part B 的词,它们的范围是从 1 到区域的长度。
    • GLM 的二维位置编码方法确保了模型在重建被遮盖的跨度时不知道它们的长度,这与其他模型相比是一个重要的区别。
      • 例如,XLNet 编码了原始位置,以便它可以感知缺失的 token 的数量;
      • 而 SpanBERT 用多个 [MASK] 标记替换了跨度,并保持了长度不变。
      • GLM 的设计适合下游任务,因为通常生成的文本的长度是事先未知的。
    • 这两个 positional id 通过 learnable embedding table 被投影到两个 embedding 向量中,这两个 embedding 向量然后都被添加到 input token embedding 中。
  • 如上图右部分所示:

    • 自注意力掩码。 灰色区域表示被掩盖,Part A 的词语可以自我看到(蓝色框),但不能看到 Part B。
    • Part B的词语可以看到Part A 和Part B 中的前面的词语(黄色和绿色框对应两个片段)。

1.1.2 多任务预训练

  • 上图所示的预训练目标中,GLM 遮盖了短的文本区域,这适合于 NLU 任务。

  • 作者更感兴趣的是预训练一个能够同时处理 NLU 和文本生成的单一模型。因此,作者研究了一种多任务预训练设置,其中一个生成更长文本的目标与空白填充目标共同优化。

  • GLM 考虑了以下两种目标:

    • 文档级别。采样一个单一的区域,其长度从原始长度的 50% 到100% 之间的均匀分布中采样。该目标旨在进行长文本生成。
    • 句子级别。限制遮盖的区域必须是完整的句子。多个区域(句子)被采样,覆盖原始文本的 15% 的词数。该目标旨在进行 seq2seq 任务,其预测结果通常是完整的句子或段落。
    • 这两个新目标的定义与 blank infilling objective 相同,唯一的区别是 span的数量和span的长度

blank infilling objective 采样多个 span,平均 span`长度最短;

document-level 采样一个 span,span 长度最长;

sentence-level采样多个 span,平均 span长度适中。

1.1.3 模型结构

GLM 使用了一个单一的 Transformer,并对架构做了一些修改:

(1)重新排列了层归一化和残差连接的顺序(将Post-LayerNorm改为Pre-LayerNorm,现在大模型的标配),这对于大规模的语言模型来避免数值错误是非常关键。

(2)在模型最后一个自注意力层之后,额外增加一个层归一化。

    blocks of:
        layer norm           # 前置归一化
        self attention       # 自注意力
        residual connection  # 残差连接
        layer norm           # 前置归一化
        mlp                  # 前馈神经网络
        residual connection  # 残差连接
    followed by a final layer norm.  # 额外增加一个层归一化

(3)使用了一个单一的线性层来进行输出词的预测。

(4)用 GeLU 替换了 ReLU 激活函数。

# glm-large-chinese模型结构如下所示
GLMForConditionalGeneration(
  (glm): GLMModel(
      # 1、词嵌入  
    (word_embeddings): VocabEmbedding()
    (transformer): GLMStack(
      (embedding_dropout): Dropout(p=0.1, inplace=False)
      # 2、二维位置编码  
      (position_embeddings): Embedding(1025, 1024)       # position 1
      (block_position_embeddings): Embedding(1025, 1024) # position 2
      # 3、堆叠GLMBlock
      (layers): ModuleList(
        (0-23): 24 x GLMBlock(
          (input_layernorm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
          (attention): SelfAttention(
            (query_key_value): Linear(in_features=1024, out_features=3072, bias=True)
            (attention_dropout): Dropout(p=0.1, inplace=False)
            (dense): Linear(in_features=1024, out_features=1024, bias=True)
            (output_dropout): Dropout(p=0.1, inplace=False)
          )
          (post_attention_layernorm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
          (mlp): MLP(
            (dense_h_to_4h): Linear(in_features=1024, out_features=4096, bias=True)
            (dense_4h_to_h): Linear(in_features=4096, out_features=1024, bias=True)
            (dropout): Dropout(p=0.1, inplace=False)
          )
        )
      )
      # 额外增加一个层归一化 
      (final_layernorm): LayerNorm((1024,), eps=1e-05, elementwise_affine=True)
    )
  )
)

训练超参数如下:

在这里插入图片描述

1.2 GLM模型的微调

  • 通常,对于下游的自然语言理解任务,一个线性分类器将预训练模型产生的序列或 token 的 embedding 作为输入,并预测正确的标签。
  • 这些做法与生成式预训练任务不同,导致了预训练和微调之间的不一致。

1.2.1 文本分类任务

  • GLM 将 NLU 分类任务重新制定为填空生成任务。如下图所示,情感分类任务可以表述为 {SENTENCE}。这真的是 [MASK]”。候选标签 y 也映射到填空的答案,例如标签 “positive” 和“negative” 分别映射到单词 “good” 和 “bad”。
  • 因此,句子是正面或负面的概率与在空白处预测“好”或“坏”成正比,然后我们用交叉熵损失来微调 GLM。

在这里插入图片描述

1.2.2 文本生成任务

  • 对于文本生成任务,如下图所示,给定的上下文构成了输入的 Part A,末尾附加了一个 mask 符号。模型自回归地生成 Part B 的文本。
  • 可以直接应用预训练的 GLM 进行无条件的生成,或者在下游的条件生成任务上对其进行微调。

在这里插入图片描述

1.2.3 与其他模型的比较分析

  • BERT 的比较:

    • 正如 XLNet 所指出的,由于MLM 的独立性假设,BERT 无法捕获到 masked token 之间的相互依赖性。
    • BERT 的另一个缺点是,它不能正确填充 multiple tokenblank 。为了推断长度为 l 的答案的概率,BERT 需要连续进行 l 次预测。如果长度 l 未知,我们可能需要列举所有可能的长度,因为 BERT 需要根据长度来改变 [MASK] token 的数量。
  • XLNet 的比较:GLMXLNet 都是用自回归目标进行预训练的,但它们之间有两个区别:

    • 首先,XLNetcorruption 前使用 original position encoding 。在推理过程中,我们需要知道或枚举出答案的长度,这与 BERT 的问题相同。
    • 第二,XLNet 使用了双流的自注意力机制,而不是 right-shift ,以避免 Transformer 内部的信息泄露。这使预训练的时间成本增加了一倍。
  • T5 的比较:T5 提出了一个类似的 blank infilling objective 来预训练一个 encoder-decoder TransformerT5 对编码器和解码器使用独立的 positional encoding ,并依靠多个哨兵 token 来区分 masked span 。在下游任务中,只使用其中一个哨兵 token,导致模型容量的浪费、以及预训练与微调之间的不一致。

    UniLM 和 GLM在编码器和解码器之间共享了同一组 positional embedding 。

    此外,T5 总是以固定的从左到右的顺序(即,单向地)预测 span 。因此,GLM 可以在参数和数据较少的情况下在 NLUseq2seq 任务中明显优于 T5

    相比之下,GLM采用了类似于XLNet的随机混洗策略,混洗了多个span的顺序。

  • UniLM 的比较:UniLM 在自编码框架下,通过在双向注意力、单向注意力、以及交叉注意力中改变 attention mask,结合了不同的预训练目标。然而,UniLM 总是用 [MASK] tokens 替换 masked span ,这限制了它针对 masked span 及其上下文之间的依赖关系的建模能力。GLM 输入前一个 token 并自动生成 next token

2 微调GLM模型做文本摘要

"""
微调清华大学开源的glm-large-chinese做摘要生成
    数据集(只取5000条):nlpcc_2017: https://huggingface.co/datasets/supremezxc/nlpcc_2017
    model link:THUDM/glm-large-chinese
          link: https://huggingface.co/THUDM/glm-large-chinese


新版本transformers报错:
AttributeError: 'GLMChineseTokenizer' object has no attribute 'sp_model'

# 原始
def __init__(self, vocab_file, **kwargs):
    super().__init__(**kwargs)  # 置后
    self.vocab_file = vocab_file
    self.sp_model = spm.SentencePieceProcessor()
    self.sp_model.Load(vocab_file)

# 修改
def __init__(self, vocab_file, **kwargs):
    self.vocab_file = vocab_file
    self.sp_model = spm.SentencePieceProcessor()
    self.sp_model.Load(vocab_file)
    super().__init__(**kwargs)  # 置后
"""
import torch
from datasets import Dataset
from transformers import AutoTokenizer, AutoModelForSeq2SeqLM, Seq2SeqTrainer, Seq2SeqTrainingArguments


device = "cuda" if torch.cuda.is_available() else 'cpu' # the device to load the model onto
# model_dir = r'D:\python\models\model-download\ZhipuAI\glm-large-chinese'
model_dir = r'/root/autodl-fs/models/zhipu/glm-large-chinese'


def predict(model, input_text):
    inputs = tokenizer("摘要生成: \n" + input_text + tokenizer.mask_token, return_tensors="pt")
    inputs = tokenizer.build_inputs_for_generation(inputs, max_gen_length=64)
    inputs = inputs.to(device)
    output = model.generate(**inputs
                            , max_new_tokens=64
                            , eos_token_id=tokenizer.eop_token_id
                            , do_sample=True
                            , pad_token_id=tokenizer.eop_token_id
                            )
    return tokenizer.decode(output[0].tolist())


def model_eval(model):
    from rouge import Rouge
    rouge = Rouge()

    model = model.eval()
    predict = []
    with torch.inference_mode():
        for d in ds["test"]:
            inputs = tokenizer("摘要生成: \n" + d["content"] + tokenizer.mask_token, return_tensors="pt")
            inputs = tokenizer.build_inputs_for_generation(inputs, max_gen_length=64)
            inputs = inputs.to("cuda")
            output = model.generate(**inputs, max_new_tokens=64, eos_token_id=tokenizer.eop_token_id,
                                    do_sample=True, pad_token_id=tokenizer.eop_token_id)
            predict.append(
                tokenizer.decode(output[0].tolist()).split("<|startofpiece|>")[1].replace("<|endofpiece|>", "").strip())
            print("curID:", len(predict))
    docode_preds = []
    decode_labels = []
    for p, q in zip(predict, ds["test"]["title"]):
        if len(p) > 0:
            docode_pred = " ".join(p)
            decode_label = " ".join(q)

            docode_preds.append(docode_pred)
            decode_labels.append(decode_label)

    scores = rouge.get_scores(docode_preds, decode_labels, avg=True)
    r = {
        "rouge-1": scores["rouge-1"]["f"],
        "rouge-2": scores["rouge-2"]["f"],
        "rouge-l": scores["rouge-l"]["f"],
    }
    print(r)
    return r

if __name__ == '__main__':
    # 1、加载tokenizer及模型
    tokenizer = AutoTokenizer.from_pretrained(model_dir, trust_remote_code=True)
    model = AutoModelForSeq2SeqLM.from_pretrained(model_dir, trust_remote_code=True).to(device)

    # 2、加载数据集
    ds = Dataset.load_from_disk("./nlpcc_2017/")
    ds = ds.train_test_split(100, seed=42)

    # 3、数据集预处理
    def process_func(exmaples):
        # Note:添加任务前缀 + [MASK]
        contents = ["摘要生成: \n" + e + tokenizer.mask_token for e in exmaples["content"]]
        # 这里pad_token为<|endoftext|>
        inputs = tokenizer(contents, max_length=384, truncation=True, padding="max_length", return_tensors="pt")
        # inputs = (['input_ids', 'position_ids', 'attention_mask', 'labels'])
        # 将inputs和targets合并为一个句子,其中:position_ids为二维位置编码,分别为:position_ids和block_position_ids
        # input_ids shape =      (bs,    inputs_max_length + max_gen_length)
        # position_ids shape =   (bs, 2, inputs_max_length + max_gen_length)
        # attention_mask shape = (bs, 1, inputs_max_length + max_gen_length, inputs_max_length + max_gen_length)
        # labels shape =         (bs, inputs_max_length + max_gen_length)
        inputs = tokenizer.build_inputs_for_generation(inputs, targets=exmaples['title'], padding=True, max_gen_length=64)
        return inputs

    tokenized_ds = ds.map(process_func, batched=True, remove_columns=ds["train"].column_names)

    # 4、配置训练参数以及创建训练器
    args = Seq2SeqTrainingArguments(
        output_dir="./summary_glm",
        per_device_train_batch_size=1,
        per_device_eval_batch_size=4,
        gradient_accumulation_steps=8,
        logging_steps=8,
        num_train_epochs=1
    )

    trainer = Seq2SeqTrainer(
        args=args,
        model=model,
        train_dataset=tokenized_ds["train"],
        tokenizer=tokenizer,
    )

    trainer.train()

    # 5、模型评估
    model_eval(model)

    # 6、模型预测
    input_text = """
          “足球从未高于生死”,这是3年前欧洲杯赛场上丹麦球员埃里克森心脏骤停时,各路媒体报道该事件用的最多的表达。
          而在经历了那段惊心动魄但又充满人情味的艰难时刻后,32岁的埃里克森时隔1100天再次为国征战欧洲杯,而且奉献了进球。
          17日凌晨的欧洲杯小组赛,埃里克森进球的那一刻,感动和欣慰扑面而来。最终丹麦和斯洛文尼亚队1比1战平,各取1分。

          丹麦队对垒斯洛文尼亚,这场热度并不算高的小组赛首轮争夺因为一个人的出现得到了外界的关注,他就是埃里克森。
          曼联中场在在第17分钟的进球帮助祖国球队取得了领先,他也在经历上届欧洲杯的心脏骤停的遭遇之后,实现了“王者归来”。
          尽管这次破门遗憾没能帮助丹麦队最终获得胜利,但绰号“爱神”的埃里克森依然得到了全场乃至全世界球迷的祝福。
        """
    print(predict(model, input_text))

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

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

相关文章

马丁短链02异常码 异常拦截器

全局统一返回实体&#xff1a; 目的是除了数据信息&#xff0c;还要带上一些错误状态码&#xff0c;成功与否&#xff0c;错误信息等等以帮助更好理解可能的错误。 规约&#xff1a;默认的约定 异常码设计原则&#xff1a; A客户端异常 B服务端异常 C远程调用异常 具体异常码…

CentOS7.9 利用 KubeKey 扩容 Kubernetes v1.26 Worker 节点实战

转载&#xff1a;CentOS7.9 利用 KubeKey 扩容 Kubernetes v1.26 Worker 节点实战 知识点 定级&#xff1a;入门级 KubeKey 安装部署 KubeSphere 和 Kubernetes KubeKey 定制化部署集群 KubeSphere v3.4.0 功能概览 Kubernetes 基本操作 CentOS 系统内核升级 演示服务器配…

Spring源码- context:component-scan base-package标签的作用源码解析

1.扫描包路径下所有的类加载解析成bean定义信息 ClassPathBeanDefinitionScanner .doScan方法调用路径 doScan:276, ClassPathBeanDefinitionScanner (org.springframework.context.annotation) parse:95, ComponentScanBeanDefinitionParser (org.springframework.context.a…

【用C语言编写】题目名称:数9的个数题目内容:编写程序数一下1到100的所有整数中出现多少个数字9

题目名称&#xff1a;数9的个数 题目内容&#xff1a;编写程序数一下1到100的所有整数中出现多少个数字9 代码如下&#xff1a; #include <stdio.h> int main() {int i 0;int count 0;for (i 1; i <100; i){if (i % 10 9) //个位为9的count;else if (i / 10 9) //…

手机应用的时间可以修改吗??

&#x1f3c6;本文收录于《CSDN问答解惑-专业版》专栏&#xff0c;主要记录项目实战过程中的Bug之前因后果及提供真实有效的解决方案&#xff0c;希望能够助你一臂之力&#xff0c;帮你早日登顶实现财富自由&#x1f680;&#xff1b;同时&#xff0c;欢迎大家关注&&收…

Java线程安全之同步方法

同步方法 使用synchronized修饰的方法&#xff0c;就叫做同步方法&#xff0c;其固定格式如下&#xff1a; public [static] synchronized 返回值类型 同步方法() {可能会产生线程安全问题的代码 }注意事项&#xff1a; 同步方法可以是普通成员方法&#xff0c;也可以是sta…

聊一聊 webpack5性能优化有哪些?

介绍 此文章基于webpack5来阐述 webpack性能优化较多&#xff0c;可以对其进行分类 优化打包速度&#xff0c;开发或者构建时优化打包速度&#xff08;比如exclude、catch等&#xff09;优化打包后的结果&#xff0c;上线时的优化&#xff08;比如分包处理、减小包体积、CDN…

什么是安全生产痕迹化管理?如何做到生产过程中全程痕迹化管理?

安全生产痕迹化管理&#xff0c;简单来说&#xff0c;就是通过记录一些“信息”来确保安全工作的进展。这些方法包括记会议内容、写安全日记、拍照片、签字盖章、指纹识别、面部识别还有手机定位等。记录下来的文字、图片、数据和视频&#xff0c;就像一个个“脚印”&#xff0…

“免费”制作中国式报表的工具横空出世,内置丰富图表组件!

一.报表制作的烦恼 报表是我们日常工作中的好伙伴&#xff0c;它在企业管理和决策过程中扮演着重要角色&#xff0c;能够清晰直观地展示数据&#xff0c;让关键信息一目了然。 然而&#xff0c;无论是使用传统的手工报表还是基于软件的普通报表操作&#xff0c;都存在不便之处…

clickhouse sql 语法参考

clickhouse sql 语法参考 1. select1.1 将结果中的某些列与 re2 正则表达式匹配&#xff0c;可以使用 COLUMNS 表1.2 ARRAY JOIN - 数组数据平铺1.3 LEFT ARRAY JOIN 2. create2.1 分布式创建数据库2.2 分布式创建复制表2.4 CREATE TABLE [IF NOT EXISTS] [db.]table_name ENGI…

layui table 重新设置表格的高度

在layui的table模块中&#xff0c;如果使用table.render({})渲染了一个表格实例时&#xff0c;确定了height配置&#xff0c;后续用table.resize(id)方法重置表格尺寸时&#xff0c;表格的高度是不会变化的&#xff08;如果我的理解没有错的话&#xff09;。 有时我们希望根据…

ChatGPT在办公与科研中有怎样的应用?又是如何做论文撰写、数据分析、机器学习、深度学习及AI绘图

2022年11月30日&#xff0c;可能将成为一个改变人类历史的日子——美国人工智能开发机构OpenAI推出了聊天机器人ChatGPT-3.5&#xff0c;将人工智能的发展推向了一个新的高度。2023年11月7日&#xff0c;OpenAI首届开发者大会被称为“科技界的春晚”&#xff0c;吸引了全球广大…

8.1 字符串中等 43 Multiply Strings 38 Count and Say

43 Multiply Strings【默写】 那个难点我就没想先解决&#xff0c;原本想法是先想其他思路&#xff0c;但也没想出。本来只想chat一下使用longlong数据类型直接stoi()得不得行&#xff0c;然后就看到了答案&#xff0c;直接一个默写的大动作。但这道题确实考察的是还原乘法&…

C++(入门2)

承接上C&#xff08;入门1&#xff09;&#xff1a;CSDN 引用&#xff1a;&#xff08;类似指针但不同于指针&#xff0c;后续会将两则不同&#xff09; 概念 引⽤不是新定义⼀个变量&#xff0c;⽽是给已存在变量取了⼀个别名&#xff0c;编译器不会为引⽤变量开辟内存空间…

c语言第六天笔记

分支结构 分支结构&#xff1a;又被称之为选择结构 概念 选择结构&#xff1a;根据条件成立与否&#xff0c;选择相应的操作。 条件构建 关系表达式&#xff1a;含有关系运算符的表达式&#xff08;>,,&#xff09; 逻辑表达式&#xff1a;含有逻辑运算符的表达式&…

网络安全相关工作必须要有证书吗?

在当今数字化时代&#xff0c;网络安全已成为至关重要的领域。然而&#xff0c;对于从事网络安全相关工作的人员来说&#xff0c;证书是否是必不可少的呢? 一、网络安全证书的重要性 网络安全证书在一定程度上能够证明从业者具备相关的知识和技能。例如&#xff0c;CISP 作为国…

基于LLM开发AI应用竟如此简单

一、什么是LLM 随着人工智能技术的不断发展&#xff0c;越来越多的企业和机构开始将其应用于各个领域。其中&#xff0c;基于语言模型的人工智能技术&#xff08;LLM&#xff09;在自然语言处理、文本生成等方面表现出色&#xff0c;被广泛应用于各种场景中。 LLM是一种基于大…

你们要的“轮子”来了!67 个仓颉语言三方库正式公开!

01 Cangjie-TPC社区简介 Cangjie-TPC&#xff08;Third Party Components&#xff09;用于汇集基于仓颉编程语言开发的开源三方库&#xff0c;帮助开发者方便、快捷、高质量构建仓颉程序。 Cangjie-TPC社区联合软通动力、宝兰德、普元、上汽以及社区开发者共同完成第一批常用…

快速设置 terminator 透明背景

看图&#xff0c;按步骤设置后⭐重启一个终端则为透明效果 效果展示&#xff1a;

vscode+platformio开发小技巧

使用vscodeplatformio开发&#xff0c;具体安装配置文章很多&#xff0c;这里分享一些方便使用的小技巧&#xff0c;让使用体验在不增加学习成本的情况下更加丝滑。 1、配置依赖库 在使用vscode开发前&#xff0c;arduino环境遗留了一些库文件&#xff0c;这些第三方库可以通…