TR3:Pytorch复现Transformer

  • 🍨 本文为🔗365天深度学习训练营 中的学习记录博客
  • 🍖 原作者:K同学啊

一、实验目的

  • 从整体上把握Transformer模型,明白它是个什么东西,可以干嘛
  • 读懂Transformer的复现代码

二、实验环境

  • 语言环境:python 3.8
  • 编译器:Jupyter notebook
  • 深度学习环境:Pytorch
    • torch==2.4.0+cu124
    • torchvision==0.19.0+cu124

三、Transformer

Transformer 网络架构架构由 Ashish Vaswani 等人在 Attention Is All You Need一文中提出,并用于机器翻译任务,和以往网络架构有所区别的是,该网络架构中,编码器和解码器没有采用 RNN 或 CNN 等网络架构,而是采用完全依赖于注意力机制的架构。网络架构如下所示:
在这里插入图片描述
在这里插入图片描述
Transformer改进了RNN被人诟病的训练慢的特点,利用self-attention可以实现快速并行。

理解注意力机制

注意力机制是神经网络中一个迷人的概念,特别是在涉及到像 NLP 这样的任务时。它就像给模型一个聚光灯,让它能够集中注意力在输入序列的某些部分,同时忽略其他部分,就像我们人类在理解句子时关注特定的单词或短语一样。

现在,让我们深入了解一种特定类型的注意力机制,称为自注意力,也称为内部注意力。想象一下,当你阅读一句话时,你的大脑会自动突出显示重要的单词或短语来理解意思。这就是神经网络中自注意力的基本原理。它使序列中的每个单词都能“关注”其他单词,包括自己在内,以更好地理解上下文。

自注意力是如何工作的

嵌入:首先,模型将输入序列中的每个单词嵌入到一个高维向量表示中。这个嵌入过程允许模型捕捉单词之间的语义相似性。

查询、键和值向量:接下来,模型为序列中的每个单词计算三个向量:查询向量、键向量和值向量。在训练过程中,模型学习这些向量,每个向量都有不同的作用。查询向量表示单词的查询,即模型在序列中寻找的内容。键向量表示单词的键,即序列中其他单词应该注意的内容。值向量表示单词的值,即单词对输出所贡献的信息。

注意力分数:一旦模型计算了每个单词的查询、键和值向量,它就会为序列中的每一对单词计算注意力分数。这通常通过取查询向量和键向量的点积来实现,以评估单词之间的相似性。

SoftMax 归一化:然后,使用 softmax 函数对注意力分数进行归一化,以获得注意力权重。这些权重表示每个单词应该关注序列中其他单词的程度。注意力权重较高的单词被认为对正在执行的任务更为关键。

加权求和:最后,使用注意力权重计算值向量的加权和。这产生了每个序列中单词的自注意力机制输出,捕获了来自其他单词的上下文信息。

在这里插入图片描述

Transformer 模型的基础

在我们深入探讨Transformer模型的复杂工作原理之前,让我们花点时间欣赏其开创性的架构。正如我们之前讨论的,Transformer模型通过引入围绕自注意力机制的新颖方法,重塑了自然语言处理(NLP)的格局。

编码器-解码器架构:在Transformer的核心是其编码器-解码器架构——两个关键组件之间的共生关系,分别负责处理输入序列和生成输出序列。编码器和解码器中的每一层都包含相同的子层,包括自注意力机制和前馈网络。这种架构不仅有助于全面理解输入序列,而且能够生成上下文丰富的输出序列。

位置编码:尽管Transformer模型具有强大的功能,但它缺乏对元素顺序的内在理解——这是位置编码所解决的一个缺点。通过将输入嵌入与位置信息结合起来,位置编码使模型能够区分序列中元素的相对位置。这种细致的理解对于捕捉语言的时间动态和促进准确理解至关重要。

多头注意力:Transformer模型的一个显著特征是它能够同时关注输入序列的不同部分——这是多头注意力实现的。通过将查询、键和值向量分成多个头,并进行独立的自注意力计算,模型获得了对输入序列的细致透视,丰富了其表示,带有多样化的上下文信息。

前馈网络:与人类大脑能够并行处理信息的能力类似,Transformer模型中的每一层都包含一个前馈网络——一种能够捕捉序列中元素之间复杂关系的多功能组件。通过使用线性变换和非线性激活函数,前馈网络使模型能够在语言的复杂语义景观中航行,促进文本的稳健理解和生成。

更多 Transformer 知识可以参考文章:Transformer从零详细解读

四、复现Transformer

1. 多头注意力机制

import torch  
import torch.nn as nn  
import math
  
class MultiHeadAttention(nn.Module):  
    # n_heads:多头注意力的数量  
    # hid_dim:每个词输出的向量维度  
    def __init__(self, hid_dim, n_heads):  
        super(MultiHeadAttention, self).__init__()  
        self.hid_dim = hid_dim  
        self.n_heads = n_heads  
        # 强制 hid_dim 必须整除 h  
        assert hid_dim % n_heads == 0  
        # 定义 W_q 矩阵  
        self.w_q = nn.Linear(hid_dim, hid_dim)  
        # 定义 W_k 矩阵  
        self.w_k = nn.Linear(hid_dim, hid_dim)  
        # 定义 W_v 矩阵  
        self.w_v = nn.Linear(hid_dim, hid_dim)  
        self.fc  = nn.Linear(hid_dim, hid_dim)  
        # 缩放  
        self.scale = torch.sqrt(torch.FloatTensor([hid_dim // n_heads]))
        
    def forward(self, query, key, value, mask=None):  
        # 注意 Q,K,V的在句子长度这一个维度的数值可以一样,可以不一样。  
        # K: [64,10,300], 假设batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        # V: [64,10,300], 假设batch_size 为 64,有 10 个词,每个词的 Query 向量是 300 维
        # Q: [64,12,300], 假设batch_size 为 64,有 12 个词,每个词的 Query 向量是 300 维
        bsz = query.shape[0] 
        Q = self.w_q(query) 
        K = self.w_k(key) 
        V = self.w_v(value) 
        # 这里把 K Q V 矩阵拆分为多组注意力 
        # 最后一维就是是用 self.hid_dim // self.n_heads 来得到的,表示每组注意力的向量长度, 每个 head 的向量长度是:300/6=50
        # 64 表示 batch size,6 表示有 6组注意力,10 表示有 10 词,50 表示每组注意力的词的向量长度
        # K: [64,10,300] 拆分多组注意力 -> [64,10,6,50] 转置得到 -> [64,6,10,50]
        # V: [64,10,300] 拆分多组注意力 -> [64,10,6,50] 转置得到 -> [64,6,10,50]
        # Q: [64,12,300] 拆分多组注意力 -> [64,12,6,50] 转置得到 -> [64,6,12,50]
        # 转置是为了把注意力的数量 6 放到前面,把 10 和 50 放到后面,方便下面计算   
        Q = Q.view(bsz, -1, self.n_heads, self.hid_dim //   
                   self.n_heads).permute(0, 2, 1, 3)   
        K = K.view(bsz, -1, self.n_heads, self.hid_dim //   
                   self.n_heads).permute(0, 2, 1, 3)   
        V = V.view(bsz, -1, self.n_heads, self.hid_dim //   
                   self.n_heads).permute(0, 2, 1, 3)
                      
        # 第 1 步:Q 乘以 K的转置,除以scale   
        # [64,6,12,50] * [64,6,50,10] = [64,6,12,10]   
        # attention:[64,6,12,10]   
        attention = torch.matmul(Q, K.permute(0, 1, 3, 2)) / self.scale
        
        # 如果 mask 不为空,那么就把 mask 为 0 的位置的 attention 分数设置为 -1e10,这里用“0”来指示哪些位置的词向量不能被attention到,比如padding位置,当然也可以用“1”或者其他数字来指示,主要设计下面2行代码的改动。
        if mask is not None:   
            attention = attention.masked_fill(mask == 0, -1e10) 
              
            # 第 2 步:计算上一步结果的 softmax,再经过 dropout,得到 attention。
            # 注意,这里是对最后一维做 softmax,也就是在输入序列的维度做 softmax   
            # attention: [64,6,12,10]   
        attention = torch.softmax(attention, dim=-1) 
          
        # 第3步,attention结果与V相乘,得到多头注意力的结果   
        # [64,6,12,10] * [64,6,10,50] = [64,6,12,50]   
        # x: [64,6,12,50]   
        x = torch.matmul(attention, V) 
          
        # 因为 query 有 12 个词,所以把 12 放到前面,把 50 和 6 放到后面,方便下面拼接多组的结果
        # x: [64,6,12,50] 转置-> [64,12,6,50]   
        x = x.permute(0, 2, 1, 3).contiguous()   
        # 这里的矩阵转换就是:把多组注意力的结果拼接起来   
        # 最终结果就是 [64,12,300]   
        # x: [64,12,6,50] -> [64,12,300]   
        x = x.view(bsz, -1, self.n_heads * (self.hid_dim // self.n_heads))
        x = self.fc(x) 
        return x 

2. 前馈传播

class Feedforward(nn.Module):
    def __init__(self, d_model, d_ff, dropout=0.1):
        super(Feedforward, self).__init__()
        # 两层线性映射和激活函数ReLU
        self.linear1 = nn.Linear(d_model, d_ff)
        self.dropout = nn.Dropout(dropout)
        self.linear2 = nn.Linear(d_ff, d_model)
    def forward(self, x):
        x = torch.nn.functional.relu(self.linear1(x))
        x = self.dropout(x)
        x = self.linear2(x)
        return x

3. 位置编码

class PositionalEncoding(nn.Module):
    "实现位置编码"
    def __init__(self, d_model, dropout=0.1, max_len=5000):
        super(PositionalEncoding, self).__init__()
        self.dropout = nn.Dropout(p=dropout)
        # 初始化Shape为(max_len, d_model)的PE (positional encoding)
        pe = torch.zeros(max_len, d_model).to(device)
        
        # 初始化一个tensor [[0, 1, 2, 3, ...]]
        position = torch.arange(0, max_len).unsqueeze(1)
        # 这里就是sin和cos括号中的内容,通过e和ln进行了变换
        div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
        
        pe[:, 0::2] = torch.sin(position * div_term) # 计算PE(pos, 2i)
        pe[:, 1::2] = torch.cos(position * div_term) # 计算PE(pos, 2i+1)
        
        pe = pe.unsqueeze(0) # 为了方便计算,在最外面在unsqueeze出一个batch
        
        # 如果一个参数不参与梯度下降,但又希望保存model的时候将其保存下来
        # 这个时候就可以用register_buffer
        self.register_buffer("pe", pe)
    def forward(self, x):
        """
        x 为embedding后的inputs,例如(1,7, 128),batch size为1,7个单词,单词维度为128
        """
        # 将x和positional encoding相加。
        x = x + self.pe[:, :x.size(1)].requires_grad_(False)
        return self.dropout(x)

4. 解码层

class EncoderLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout):
        super(EncoderLayer, self).__init__()
        # 编码器层包含自注意力机制和前馈神经网络
        self.self_attn   = MultiHeadAttention(d_model, n_heads)
        self.feedforward = Feedforward(d_model, d_ff, dropout)
        self.norm1   = nn.LayerNorm(d_model)
        self.norm2   = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
    def forward(self, x, mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, mask)
        x = x + self.dropout(attn_output)
        x = self.norm1(x)
        # 前馈神经网络
        ff_output = self.feedforward(x)
        x = x + self.dropout(ff_output)
        x = self.norm2(x)
        return x

5. 编码层

class DecoderLayer(nn.Module):
    def __init__(self, d_model, n_heads, d_ff, dropout=0.1):
        super(DecoderLayer, self).__init__()
        # 解码器层包含自注意力机制、编码器-解码器注意力机制和前馈神经网络
        self.self_attn   = MultiHeadAttention(d_model, n_heads)
        self.enc_attn    = MultiHeadAttention(d_model, n_heads)
        self.feedforward = Feedforward(d_model, d_ff, dropout)
        self.norm1   = nn.LayerNorm(d_model)
        self.norm2   = nn.LayerNorm(d_model)
        self.norm3   = nn.LayerNorm(d_model)
        self.dropout = nn.Dropout(dropout)
    def forward(self, x, enc_output, self_mask, context_mask):
        # 自注意力机制
        attn_output = self.self_attn(x, x, x, self_mask)
        x           = x + self.dropout(attn_output)
        x           = self.norm1(x)
        # 编码器-解码器注意力机制
        attn_output = self.enc_attn(x, enc_output, enc_output, context_mask)
        x           = x + self.dropout(attn_output)
        x           = self.norm2(x)
        # 前馈神经网络
        ff_output = self.feedforward(x)
        x = x + self.dropout(ff_output)
        x = self.norm3(x)
        return x

6. Transformer模型构建

class Transformer(nn.Module):
    def __init__(self, vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout=0.1):
        super(Transformer, self).__init__()
        # Transformer 模型包含词嵌入、位置编码、编码器和解码器
        self.embedding           = nn.Embedding(vocab_size, d_model)
        self.positional_encoding = PositionalEncoding(d_model)
        self.encoder_layers      = nn.ModuleList([EncoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_encoder_layers)])
        self.decoder_layers      = nn.ModuleList([DecoderLayer(d_model, n_heads, d_ff, dropout) for _ in range(n_decoder_layers)])
        self.fc_out              = nn.Linear(d_model, vocab_size)
        self.dropout             = nn.Dropout(dropout)
    def forward(self, src, trg, src_mask, trg_mask):
        # 词嵌入和位置编码
        src = self.embedding(src)
        src = self.positional_encoding(src)
        trg = self.embedding(trg)
        trg = self.positional_encoding(trg)
        # 编码器
        for layer in self.encoder_layers:
            src = layer(src, src_mask)
        # 解码器
        for layer in self.decoder_layers:
            trg = layer(trg, src, trg_mask, src_mask)
        # 输出层
        output = self.fc_out(trg)
        return output

7. 使用示例

vocab_size = 10000  # 假设词汇表大小为10000
d_model    = 128
n_heads    = 8
n_encoder_layers = 6
n_decoder_layers = 6
d_ff             = 2048
dropout          = 0.1
device = torch.device('cpu')
 
transformer_model = Transformer(vocab_size, d_model, n_heads, n_encoder_layers, n_decoder_layers, d_ff, dropout)
# 定义输入,这里的输入是假设的,需要根据实际情况修改
src = torch.randint(0, vocab_size, (32, 10))  # 源语言句子
trg = torch.randint(0, vocab_size, (32, 20))  # 目标语言句子
src_mask = (src != 0).unsqueeze(1).unsqueeze(2)  # 掩码,用于屏蔽填充的位置
trg_mask = (trg != 0).unsqueeze(1).unsqueeze(2)  # 掩码,用于屏蔽填充的位置
# 模型前向传播
output = transformer_model(src, trg, src_mask, trg_mask)
print(output.shape)

在这里插入图片描述

五、总结

通过本次 Transformer 的学习与模型复现,我对其架构和原理有了初步的了解。

在理论层面,明白了 Transformer 基于注意力机制构建,编码器 - 解码器架构、位置编码、多头注意力和前馈网络是其核心要素。注意力机制让模型能聚焦关键信息,多头注意力从多维度捕捉上下文,位置编码赋予位置感知,前馈网络处理复杂关系。

在实践方面,实现了 PyTorch 的复现过程。从多头注意力机制、前馈传播、位置编码、编码层和解码层的实现,到最终构建Transformer模型并给出使用示例。这有助于理解模型各部分如何协同工作,以及如何将理论转化为实际代码,为进一步应用和优化Transformer模型奠定了基础,也为深入研究深度学习中的序列处理模型提供了重要参考。

Transformer模型作为一种强大的深度学习架构,在众多自然语言处理和其他序列数据相关的实际问题中都有广泛应用,我主要关注其在医学领域中结合自然语言处理和计算机视觉的常见应用:

自然语言处理在医学领域的应用

  • 医学文献检索与信息提取:利用Transformer的语言理解能力,可以更准确地对医学文献进行检索和信息提取。例如,研究人员可以输入特定的医学问题或关键词,Transformer模型能够理解问题的语义,从大量的医学文献中筛选出与之相关的高质量文献,并提取其中的关键信息,如疾病的病因、症状、治疗方法等,帮助医学工作者快速获取所需知识,节省时间和精力。
  • 电子病历分析与处理:Transformer模型可用于分析电子病历中的文本数据,包括患者的症状描述、检查结果、诊断结论、治疗过程等。通过对这些文本的理解和分析,模型可以帮助医生更好地了解患者的病情历史,发现潜在的疾病模式和风险因素,为诊断和治疗提供参考依据。此外,还可以实现电子病历的自动编码和分类,提高医疗数据管理的效率和准确性。
  • 医学问答系统:基于Transformer构建的医学问答系统能够理解用户提出的自然语言问题,并生成准确、有用的答案。无论是患者咨询常见的健康问题,还是医学专业人员探讨复杂的病例,问答系统都可以快速提供相关的医学知识和建议,提高医疗信息的可及性和交流效率.
  • 医学文本生成:可以生成医学报告、疾病诊断书、治疗方案等文本内容。例如,根据患者的检查数据和基本信息,Transformer模型能够生成初步的诊断报告,为医生提供参考,辅助其进行更准确的诊断和治疗决策。同时,在医学教育领域,也可以用于生成教学案例、知识点讲解等文本材料,帮助学生更好地学习和理解医学知识.

计算机视觉在医学领域的应用

  • 医学图像分割:医学图像分割是指将医学图像中的不同组织、器官或病变区域等进行精确划分,这对于疾病的诊断、治疗计划制定以及手术导航等都具有重要意义。Transformer的全局建模能力使其能够有效地捕捉图像中的长距离依赖关系,从而更准确地分割出医学图像中的目标区域,例如将CT图像中的肿瘤、MRI图像中的脑部组织等进行精确分割,为医生提供更详细、准确的图像信息.
  • 医学图像分类:可用于对医学图像进行分类,如判断X光片中是否存在骨折、CT图像中是否有肺部疾病等。通过学习大量的医学图像数据,Transformer模型能够自动提取图像的特征,并根据这些特征对图像进行准确分类,辅助医生进行快速的疾病筛查和诊断.
  • 医学图像重建与增强:在医学成像过程中,由于设备限制、患者运动等因素,可能会导致图像质量下降。Transformer模型可以学习图像的潜在特征表示,对低质量的医学图像进行重建和增强,提高图像的清晰度和分辨率,有助于医生更准确地观察和分析图像中的细节信息,如从欠采样的MRI图像中恢复高分辨率图像.
  • 医学图像配准:医学图像配准是将不同时间、不同模态或不同视角下获取的医学图像进行对齐的过程,对于多模态图像融合、疾病监测和治疗评估等具有重要价值。Transformer能够捕获图像中的全局上下文信息和长程依赖关系,更好地处理大的空间变形和多模态特征融合问题,从而实现更准确的医学图像配准.
  • 医学图像合成:可以根据给定的条件或先验知识生成逼真的医学图像,如生成特定疾病的模拟图像、合成缺失模态的医学图像等。这有助于医生更好地理解疾病的形态和特征,进行术前规划和模拟手术,同时也为医学研究提供了更多的数据资源.

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

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

相关文章

数据分布之指数分布(sample database classicmodels _No.10)

数据分布之指数分布(sample database classicmodels _No.10) 准备工作,可以去下载 classicmodels 数据库具体如下 点击:classicmodels 也可以去 下面我的博客资源下载 https://download.csdn.net/download/tomxjc/88685970 文章…

无人机动力系统测试-实测数据与CFD模拟仿真数据关联对比分析

我们经常被问到这样的问题:“我们计划运行 CFD 仿真,我们还需要对电机和螺旋桨进行实验测试吗?我们可能有偏见,但我们的答案始终是肯定的,而且有充分的理由。我们自己执行了大量的 CFD 仿真,但我们承认&…

MinIO 的 S3 over RDMA 计划: 为高速人工智能数据基础设施设定对象存储新标准

随着 AI 和机器学习的需求不断加速,数据中心网络正在迅速发展以跟上步伐。对于许多企业来说,400GbE 甚至 800GbE 正在成为标准选择,因为数据密集型和时间敏感型 AI 工作负载需要高速、低延迟的数据传输。用于大型语言处理、实时分析和计算机视…

游戏引擎学习第13天

视频参考:https://www.bilibili.com/video/BV1QQUaYMEEz/ 改代码的地方尽量一张图说清楚吧,懒得浪费时间 game.h #pragma once #include <cmath> #include <cstdint> #include <malloc.h>#define internal static // 用于定义内翻译单元内部函数 #…

十分钟学会html超文本标记语言

前言 本次学习的是在b站up主泷羽sec课程有感而发&#xff0c;如涉及侵权马上删除文章。 笔记的只是方便各位师傅学习知识&#xff0c;以下网站只涉及学习内容&#xff0c;其他的都与本人无关&#xff0c;切莫逾越法律红线&#xff0c;否则后果自负。 &#xff01;&#xff01;…

【Linux系统编程】第四十七弹---深入探索:POSIX信号量与基于环形队列的生产消费模型实现

✨个人主页&#xff1a; 熬夜学编程的小林 &#x1f497;系列专栏&#xff1a; 【C语言详解】 【数据结构详解】【C详解】【Linux系统编程】 目录 1、POSIX信号量 2、基于环形队列的生产消费模型 2.1、代码实现 2.1.1、RingQueue基本结构 2.1.2、PV操作 2.1.3、构造析构…

除了 TON, 哪些公链在争夺 Telegram 用户?数据表现如何?

作者&#xff1a;Stella L (stellafootprint.network) 在 2024 年&#xff0c;区块链游戏大规模采用迎来了一个意想不到的催化剂&#xff1a;Telegram。随着各大公链争相布局这个拥有海量用户基础的即时通讯平台&#xff0c;一个核心问题浮出水面&#xff1a;这种用户获取策略…

小白进!QMK 键盘新手入门指南

经常玩键盘的伙伴应该都知道&#xff0c;现在的键盘市场可谓是百花齐放&#xff0c;已经不是之前的单一功能产品化时代。我们可以看到很多诸如&#xff1a;机械轴键盘、磁轴键盘、光轴键盘、电感轴键盘&#xff0c;以及可能会上市的光磁轴键盘&#xff0c;更有支持屏幕的、带旋…

【HarmonyOS】鸿蒙系统在租房项目中的项目实战(二)

从今天开始&#xff0c;博主将开设一门新的专栏用来讲解市面上比较热门的技术 “鸿蒙开发”&#xff0c;对于刚接触这项技术的小伙伴在学习鸿蒙开发之前&#xff0c;有必要先了解一下鸿蒙&#xff0c;从你的角度来讲&#xff0c;你认为什么是鸿蒙呢&#xff1f;它出现的意义又是…

《Markdown语法入门》

文章目录 《Markdown语法入门》1.标题2.段落2.1 换行2.2分割线 3.文字显示3.1 字体3.2 上下标 4. 列表4.1无序列表4.2 有序列表4.3 任务列表 5. 区块显示6. 代码显示6.1 行内代码6.2 代码块 7.插入超链接8.插入图片9. 插入表格 《Markdown语法入门》 【Typora 教程】手把手教你…

北京大学c++程序设计听课笔记101

基本概念 程序运行期间&#xff0c;每个函数都会占用一段连续的内存空间。而函数名就是该函数所占内存区域的起始地址&#xff08;也称“入口地址”&#xff09;。我们可以将函数的入口地址赋给一个指针变量&#xff0c;使该指针变量指向该函数。然后通过指针变量就可以调用这个…

C++:boost库安装

官网&#xff1a;https://www.boost.org/ Boost 库在 C 社区中广受欢迎&#xff0c;主要因为它提供了丰富、强大且稳定的功能&#xff0c;可以显著提高开发效率和代码质量。下面是使用 Boost 库的主要优势和特点&#xff1a; 1. 丰富的功能集合 Boost 提供了数十个高质量的 …

VScode学习前端-01

小问题合集&#xff1a; vscode按&#xff01;有时候没反应&#xff0c;有时候出来&#xff0c;是因为------>必须在英文状态下输入&#xff01; 把鼠标放在函数、变量等上面&#xff0c;会自动弹出提示&#xff0c;但挡住视线&#xff0c;有点不习惯。 打开file->pre…

机房动环境监控用各种列表已经淘汰了,现在都是可视化图表展示了

在信息技术飞速发展的今天&#xff0c;机房作为数据存储、处理和传输的核心场所&#xff0c;其稳定运行至关重要。过去&#xff0c;机房动环境监控主要依赖各种列表形式来呈现数据&#xff0c;但如今&#xff0c;这种方式已经逐渐被淘汰&#xff0c;取而代之的是更加直观、高效…

Pytest-Bdd-Playwright 系列教程(10):配置功能文件路径 优化场景定义

Pytest-Bdd-Playwright 系列教程&#xff08;10&#xff09;&#xff1a;配置功能文件路径 & 优化场景定义 前言一、功能文件路径的配置1.1 全局设置功能文件路径1.2. 在场景中覆盖路径 二、避免重复输入功能文件名2.1 使用方法2.2 functools.partial 的背景 三、应用场景总…

【软件测试】自动化常用函数

文章目录 元素的定位cssSelectorxpath查找元素 操作测试对象点击/提交对象——click()模拟按键输入——sendKeys(“”)清除文本内容——clear()获取文本信息——getText()获取页面标题和 URL 窗口设置窗口大小切换窗口关闭窗口 等待强制等待隐式等待显式等待 浏览器导航 元素的…

CC4学习记录

&#x1f338; CC4 CC4要求的commons-collections的版本是4.0的大版本。 其实后半条链是和cc3一样的&#xff0c;但是前面由于commons-collections进行了大的升级&#xff0c;所以出现了新的前半段链子。 配置文件&#xff1a; <dependency><groupId>org.apach…

【linux】网络基础 ---- 数据链路层

用于两个设备(同一种数据链路节点)之间进行传递 数据链路层解决的问题是&#xff1a;直接相连的主机之间&#xff0c;进行数据交付 1. 认识以太网 "以太网" 不是一种具体的网络, 而是一种技术标准&#xff1a; 既包含了数据链路层的内容, 也包含了一些物理层的内容…

5. ARM_指令集

概述 分类 汇编中的符号&#xff1a; 指令&#xff1a;能够编译生成一条32位机器码&#xff0c;并且能被处理器识别和执行伪指令&#xff1a;本身不是指令&#xff0c;编译器可以将其替换成若干条指令伪操作&#xff1a;不会生成指令&#xff0c;只是在编译阶段告诉编译器怎…

小程序租赁系统开发为企业提供高效便捷的租赁服务解决方案

内容概要 在这个数字化飞速发展的时代&#xff0c;小程序租赁系统应运而生&#xff0c;成为企业管理租赁业务的一种新选择。随着移动互联网的普及&#xff0c;越来越多的企业开始关注如何利用小程序来提高租赁服务的效率和便捷性。小程序不仅可以为用户提供一个快速、易用的平…