【NLP】大语言模型基础之Transformer结构

大语言模型基础之Transformer结构

  • 1. Transformer结构总览
  • 2. 嵌入表示层
  • 2. 注意力层
  • 3. 前馈层
  • 4. 残差连接与层归一化
  • 5. 编码器和解码器结构
  • 参考文献

Transformer是一种深度学习模型架构,由Vaswani等人于2017年在论文《Attention is All You Need》中首次提出。它在自然语言处理(NLP)领域取得了巨大的成功,并被广泛应用于各种任务,如机器翻译、文本摘要、语言建模等。

在Transformer之前,循环神经网络(RNN)和长短时记忆网络(LSTM)等序列模型是处理自然语言数据的主流选择。然而,这些模型存在一些问题,例如难以并行化处理、难以捕捉长距离依赖关系等。Transformer的提出旨在解决这些问题,并通过引入自注意力机制(Self-Attention)实现更高效的序列建模。

从2020年OpenAI发布GPT-3开始,对大语言模型的研究逐渐深入,虽然大语言模型的参数量巨大,通过有监督微调和强化学习能够完成非常多的人物,但是其理论基础仍然是Transformer结构。因此本文将再次回顾Transformer的结构,涉及复杂度、参数设置、上下文学习、Attention方法等。

1. Transformer结构总览

Transformer结构是由谷歌在2017年提出并首先应用于机器翻译的神经网络模型架构。如下图所示,并且标上了序号,方便后面讲解:
Transformer架构
图中(序号3)左侧和右侧分别对应着编码器(Encoder)和解码器(Decoder)结构,它们均由若干个基本的Transformer块(Block)组成。这里的N×表示进行了N次堆叠。每个Transformer块都接收一个向量序列 x i i = 1 t {x_i}_{i=1}^t xii=1t作为输入,并输出一个等长的向量序列作为输出 y i i = 1 t {y_i}_{i=1}^t yii=1t。这里的 x i x_i xi y i y_i yi分别对应文本序列中的一个词元的表示, y i y_i yi是当前Transformer块对输入 x i x_i xi进一步整合其上下文语义后对应的输出。在从输入 x i i = 1 t {x_i}_{i=1}^t xii=1t到输出 y i i = 1 t {y_i}_{i=1}^t yii=1t的语义抽象过程中,主要涉及如下几个模块:

  • 注意力层:使用多头注意力(Multi-Head Attention)机制整合上下文语义,它使得序列中任意两个单词之间的依赖关系可以直接被建模而不基于传统的循环结构,从而更好地解决文本的长程依赖问题。
    • 允许模型在不同的表示子空间中学习输入序列的多个关系
    • 多个自注意力头组合在一起,可以捕获不同类型的信息
  • 位置感知前馈层(Position-wise FFN):通过全连接层对输入文本序列中的每个单词表示进行更复杂的编码。
    • 位置编码:由于Transformer不像RNN那样天然具有处理序列顺序的能力,因此它通过位置编码(Positional Encoding)来引入序列中元素的位置信息,对应图中序号2。
  • 残差链接(Residual Connections):对应图中Add部分。它是一条分别作用在上述两个子层中的直连通路,被用于连接两个子层的输入和输出,使信息流动更高效,有利于模型的优化。
    • 允许梯度直接流过每一层,有助于训练深层网络。
  • 层归一化(Layer Normalization):对应图中Norm部分,它作用于上述两个子层的输出表示序列,对表示序列进行层归一化操作,同样起到稳定优化的作用。
    • 用于稳定神经网络的学习过程

补充:什么是tokens
tokens是分词tokenization的结果分词就是将句子、段落、文章这类型的长文本,分解为以字词(token)为单位的数据结构。比方说,在句子 “我很开心” 中,利用中文分词得到的列表是{“我”,“很”,“开心”},列表中的每一个元素代表一个token。对于“ChatGPT is great!”这个字符串,就被编码为6个tokens:“Chat”、“G”、“PT”、“ is”、“ great”、“!”


2. 嵌入表示层

对于输入文本序列,先通过输入嵌入层(Input Embedding)将每个单词转换为其相对应的向量表示。通常,直接对每个token创建一个向量表示,在论文中这个向量的大小是d_model=512。由于Transfomer模型不再基于循环的方式建模文本输入,序列中不再有任何信息能够提示来模型输入单词之间的相对位置关系。在送入编码器端建模上下文语义之前,一个非常重要的操作是在词嵌入中加入位置编码(Positional Encoding)这一特征,具体来讲,序列中的每个单词所在的位置都对应一个向量位置编码向量 与其 对应的单词表示 相加 然后送入到后续模块中做进一步处理,input如下:
input = input_embedding + positional_encoding \text{input} = \text{input\_embedding}+\text{positional\_encoding} input=input_embedding+positional_encoding
在训练过程中,模型会自动学习到如何利用这部分的位置信息。Transformer 模型使用不同频率的正余弦函数计算位置编码
P E ( p o s , 2 i ) = s i n ( p o s 1000 0 2 i d ) PE(pos, 2i)=sin(\frac{pos}{10000^{\frac{2i}{d}}}) PE(pos2i)=sin(10000d2ipos)
P E ( p o s , 2 i + 1 ) = c o s ( p o s 1000 0 2 i d ) PE(pos, 2i+1)=cos(\frac{pos}{10000^{\frac{2i}{d}}}) PE(pos,2i+1)=cos(10000d2ipos)
其中pos表示单词所在的位置, 2 i 2i 2i 2 i + 1 2i+1 2i+1表示位置编码向量中的对应维度,d表示对应位置编码的总维度(d_model=512)。

使用正余弦做位置编码的好处

  1. 正余弦函数的范围在[-1, 1],导出的位置编码与原词嵌入(input_embedding)相加不会使得结果偏离过大而对词义产生破坏;
  2. 依据三角函数的性质,可以得知第pos+k个位置的编码是第pos个位置的编码的线性组合,这就意味着位置编码中蕴含着单词之间的距离信息
    关于以上第2点的理解,论文里面的解释如下,使用正余弦交替的位置编码,有以下2个特性:

Property 1. For an offset k and a position t, P E t + k T P E t PE_{t+k}^TPE_t PEt+kTPEt only depends on k, which means the dot product of two sinusoidal position embeddings can reflect the distance between two tokens.

特性1:对于一个偏移k和一个位置t来说, P E t + k T P E t PE_{t+k}^TPE_t PEt+kTPEt 的结果仅仅和偏移k有关系,这意味着两个正弦位置嵌入的点乘可以反应两个tokens的距离

Property 2. For an offset k and a position t, P E t T P E t − k = P E t T P E t + k PE_{t}^TPE_{t-k}=PE_t^TPE_{t+k} PEtTPEtk=PEtTPEt+k, which means the sinusoidal position embeddings is unware of directionality.

特性2: 对于一个偏移k和一个位置t, P E t T P E t − k = P E t T P E t + k PE_{t}^TPE_{t-k}=PE_t^TPE_{t+k} PEtTPEtk=PEtTPEt+k,这意味着tokens的正弦位置嵌入不具有方向性

使用PyTorch实现的位置编码如下:

import math
import torch
import torch.nn as nn
import torch.nn.functional as Ffrom torch.autograd import Variableclass PositionalEncoder(nn.Module):def __init__(self, d_model, max_seq_len=80):super().__init__()self.d_model = d_model# 根据pos和i创建一个常量PE矩阵pe = torch.zeros(max_seq_len, d_model)for pos in range(max_seq_len):for i in range(0, d_model, 2):pe[pos, i] = math.sin(pos / (10000 ** (i / d_model)))pe[pos, i + 1] = math.cos(pos / (10000 ** (i / d_model)))pe = pe.unsqueeze(0)self.register_buffer('pe', pe)def forward(self, x):# 使得单词嵌入表示相对大一些x = x * math.sqrt(self.d_model)# 增加位置常量到单词嵌入表示中seq_len = x.size(1)x = x + Variable(self.pe[:, :seq_len], requires_grad=False).cuda()return x

2. 注意力层

自注意力(Self-Attention)操作是基于Transformer的机器翻译模型的基本操作,在源语言的编码和目标语言的生成中频繁地被使用以建模源语言、目标语言任意两个单词之间的依赖关系。

给定输入,对于每个tokens,它的输入是: x = input_embedding ( 单词语义嵌入 ) + positional_encoding ( 位置编码 ) x=\text{input\_embedding}(单词语义嵌入)+\text{positional\_encoding}(位置编码) x=input_embedding(单词语义嵌入)+positional_encoding(位置编码)
每一项都是d维的,有d_model参数决定;输入表示为 { x i ∈ R d } i = 1 t \{x_i\in \mathcal{R}^d\}_{i=1}^t {xiRd}i=1t,为了实现对 上下文语义依赖的建模,引入自注意力机制,涉及到Self-Attention机制中的三个元素及其对应的矩阵:

  • query: q i ( Q u e r y ) ; W Q ∈ R d ∗ d q q_i(Query); W^Q\in R^{d*d_q} qi(Query);WQRddq
  • key: k i ( K e y ) ; W K ∈ R d ∗ d k k_i(Key); W^K\in R^{d*d_k} ki(Key);WKRddk
  • value: v i ( V a l u e ) ; W V ∈ R d ∗ d v v_i(Value); W^V\in R^{d*d_v} vi(Value);WVRddv

在编码输入序列中每一个单词的表示过程中,这三个元素用于计算上下文单词所对应的权重得分,这些权重反映了在编码当前单词的时候,对于上下文不同部分所需要的关注程度。具体来说,通过三个线性变换 W Q ∈ R d ∗ d q W^Q\in R^{d*d_q} WQRddq, W K ∈ R d ∗ d k W^K\in R^{d*d_k} WKRddk, W V ∈ R d ∗ d v W^V\in R^{d*d_v} WVRddv将输入序列中的每一个token对应的表示 x i x_i xi转换对应的 q i ∈ R d q q_i\in R^{d_q} qiRdq k i ∈ R d k k_i\in R^{d_k} kiRdk v i ∈ R d v v_i\in R^{d_v} viRdv向量。
自注意力机制中的查询、键、值向量
针对当前token获取上下文关注权重的计算:在编码token x i x_i xi的时候,为了获得所需要关注的上下文信息,首先通过 q i q_i qi和其他位置的键向量做点积得到匹配分数 q i ⋅ k 1 , q i ⋅ k 2 , . . . , q i ⋅ k t q_i\cdot k_1, q_i\cdot k_2,...,q_i\cdot k_t qik1,qik2,...,qikt。为了防止匹配的权重在后续Softmax计算过程中导致梯度爆炸以及收敛效率差的问题,这些得分会除以放缩因子 d \sqrt{d} d 以稳定优化。放缩后的得分经过Softmax归一化为概率,与其他位置的值向量相乘来聚合希望关注的上下文信息,并且最小化不相关的干扰信息
Z = Attention ( Q , K , V ) = Softmax ( Q K T d ) V Z=\text{Attention}(Q,K,V)=\text{Softmax}(\frac{QK^T}{\sqrt{d}})V Z=Attention(Q,K,V)=Softmax(d QKT)V
其中 Q ∈ R L ∗ d q , K ∈ R L ∗ d k , V ∈ R L ∗ d v Q\in R^{L*d_q},K\in R^{L*d_k},V\in R^{L*d_v} QRLdqKRLdkVRLdv, L表示序列长度 Z ∈ R L ∗ d v Z\in R^{L*d_v} ZRLdv表示自注意力操作的输出
多头自注意力(Multi-head Attention)机制:为了进一步增强自注意力机制 聚合上下文信息的能力,提出了Multi-head Attention机制,用来关注上下文的不同侧面。具体来说,上下文中的每一个单词的表示 x i x_i xi经过多组线性KaTeX parse error: Expected 'EOF', got '}' at position 22: …Q,W_j^K,W_j^V\\}̲_{j=1}^N映射到不同的表示子空间中,即每个token的输入 x i x_i xi经过多组线性变化 { W j Q , W j K , W j V } j = 1 N \{W_j^Q,W_j^K,W_j^V\}_{j=1}^N {WjQ,WjK,WjV}j=1N映射到不同的 { Z j } j = 1 N \{Z_j\}_{j=1}^N {Zj}j=1N。最终使用线性变化 W O ∈ { R ( N d v ) ∗ d } W^O\in \{R^{(N_{d_v})*d}\} WO{R(Ndv)d}用于综合不同子空间的上下文表示并形成自注意力层最终的输出 { x i ∈ R d } i = 1 t \{x_i\in R^d\}_{i=1}^t {xiRd}i=1t

使用PyTorch实现的自注意力层代码如下:

import copy
import numpy as np
from typing import Optional, Any, Union, Callable
from torch import Tensorclass MultiHeadAttention(nn.Module):def __init__(self, num_heads: int, d_model: int, dropout: float = 0.1):super(MultiHeadAttention, self).__init__()assert d_model % num_heads == 0, "d_model must be divisible by num_heads"self.d_model = d_modelself.k_dim = d_model // num_headsself.num_heads = num_heads# self.q_linear = nn.Linear(d_model, d_model)# self.v_linear = nn.Linear(d_model, d_model)# self.k_linear = nn.Linear(d_model, d_model)# self.out = nn.Linear(d_model, d_model)self.proj_weights = clones(nn.Linear(d_model, d_model), 4)  # W^Q, W^K, W^V, W^Oself.attention_score = Noneself.dropout = nn.Dropout(p=dropout)def forward(self, query: Tensor, key: Tensor, value: Tensor, mask: Optional[Tensor] = None):"""Args:query: shape (batch_size, seq_len, d_model)key: shape (batch_size, seq_len, d_model)value: shape (batch_size, seq_len, d_model)mask: shape (batch_size, seq_len, seq_len). Since we assume all data use a same mask, sohere the shape also equals to (1, seq_len, seq_len)Return:out: shape (batch_size, seq_len, d_model). The output of a multi_head attention layer"""if mask is not None:mask = mask.unsqueeze(1)batch_size = query.size(0)# # 利用线性计算划分出h个头# query = self.q_linear(query).view(batch_size, -1, self.num_heads, self.k_dim)# key = self.k_linear(key).view(batch_size, -1, self.num_heads, self.k_dim)# value = self.v_linear(value).view(batch_size, -1, self.num_heads, self.k_dim)# # 矩阵转置# query = query.transpose(1, 2)# key = key.transpose(1, 2)# value = value.transpose(1, 2)# 1) Apply W^Q, W^K, W^V to generate new query, key, valuequery, key, value = [proj_weight(x).view(batch_size, -1, self.num_heads, self.k_dim).transpose(1, 2)for proj_weight, x in zip(self.proj_weights, [query, key, value])]  # -1 equals to seq_len# # 计算attention# scores = attention(query, key, value, mask, self.dropout)# # 连接多个头并输入最后的线性层# concat = scores.transpose(1,2).contiguous().view(batch_size, -1, self.d_model)# 2) Calculate attention score and the outout, self.attention_score = attention(query, key, value, mask=mask, dropout=self.dropout)# 3) "Concat" outputout = out.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.k_dim)# output = self.out(concat)# 4) Apply W^O to get the final outputoutput = self.proj_weights[-1](out)return outputdef clones(module, N):"""Produce N identical layers."""return nn.ModuleList([copy.deepcopy(module) for _ in range(N)])def attention(query: Tensor, key: Tensor, value: Tensor, mask: Optional[Tensor] = None, dropout: float = 0.1):"""Define how to calculate attention scoreArgs:query: shape (batch_size, num_heads, seq_len, k_dim)key: shape(batch_size, num_heads, seq_len, k_dim)value: shape(batch_size, num_heads, seq_len, v_dim)mask: shape (batch_size, num_heads, seq_len, seq_len). Since our assumption, here the shape is(1, 1, seq_len, seq_len)Return:out: shape (batch_size, v_dim). Output of an attention head.attention_score: shape (seq_len, seq_len)."""k_dim = query.size(-1)# shape (seq_len ,seq_len),row: token,col: that token's attention scorescores = torch.matmul(query, key.transpose(-2, -1)) / math.sqrt(k_dim)# 掩盖那些为了填补长度而增加的单元,使其通过Softmax计算后为0if mask is not None:mask = mask.unsqueeze(1)scores = scores.masked_fill(mask == 0, -1e9)attention_score  = F.softmax(scores, dim=-1)if dropout is not None:attention_score  = dropout(attention_score )output = torch.matmul(scores, value)return output, attention_score    # shape: (seq_len, v_dim), (seq_len, seq_lem)

3. 前馈层

前馈层接收自注意力子层的输出作为输入,并通过一个带有ReLU激活函数的两层全连接网络对输入进行更复杂的非线性变换。这一非线性变换会对模型最终的性能产生重要的影响。 F F N ( x ) = R e L U ( x W 1 + b 1 ) W 2 + b 2 FFN(x)=ReLU(xW_1+b_1)W_2+b_2 FFN(x)=ReLU(xW1+b1)W2+b2
其中 W 1 , b 1 , W 2 , b 2 W_1,b_1,W_2,b_2 W1,b1,W2,b2表示前馈子层的参数。结果表明,增大前馈子层隐状态的维度有利于提高最终翻译结果的质量,因此,前馈子层隐状态的维度一般比自注意力子层要大
使用PyTorch实现的前馈层的代码:

class FeedForward(nn.Module):def __init__(self, d_model, d_ff=2048, dropout=0.1):super(FeedForward, self).__init__()# d_ff默认设置为2048self.linear_1 = nn.Linear(d_model, d_ff)self.dropout = nn.Dropout(dropout)self.linear_2 = nn.Linear(d_ff, d_model)def forward(self, x):x = self.dropout(F.relu(self.linear_1(x)))x = self.linear_2(x)return x

4. 残差连接与层归一化

由Transformer结构组成的网络结构通常都非常大。编码器和解码器均由很多层基本的Transformer块组成,每一层中都包含复杂的非线性映射,这就导致模型的训练比较困难。因此,需要在Transformer块中进一步引入了残差连接与层归一化技术,以进一步提升训练的稳定性。

具体来说,残差连接主要是使用一条直连通道直接将对应子层的输入连接到输出,避免在优化过程中因网络过深而产生潜在的梯度消失问题 x l + 1 = f ( x l ) + x l x^{l+1}=f(x^l)+x^l xl+1=f(xl)+xl
其中 x l x^l xl表示第 l l l层的输入, f ( ⋅ ) f(\cdot) f()表示一个映射函数。此外,为了使每一层的输入/输出稳定在一个合理的范围内,层归一化技术被进一步引入每个Transformer块中:
L N ( x ) = α ⋅ x − μ σ + b LN(x)=\alpha\cdot \frac{x-\mu}{\sigma}+b LN(x)=ασxμ+b
其中 μ \mu μ σ \sigma σ分别表示均值和方差,用于将数据平移缩放到均值为0,方差为1的标准分布 α \alpha α β \beta β是可学习的参数。层归一化技术可以有效地缓解优化过程中潜在的不稳定、收敛速度慢等问题

使用PyTorch实现的层归一化代码如下:

class Norm(nn.Module):def __init__(self, d_model, eps=1e-6):super(Norm, self).__init__()self.size = d_model# 层归一化包含两个可以学习的参数self.alpha = nn.Parameter(torch.ones(self.size))self.blas = nn.Parameter(torch.zeros(self.size))self.eps = eps  # 用于数值稳定性的分母小值def forward(self, x):norm = self.alpha * (x - x.mean(dim=-1, keepdim=True)) / (x.std(dim=-1, keepdim=True) + self.eps) + self.biasreturn norm

5. 编码器和解码器结构

基于上述模块,根据图中给出的网络结构,编码器端较容易实现。但是解码器端更为复杂。具体来说,解码器的每个Transformer块的第一个自注意力子层额外增加了注意力掩码,对应图中的掩码多头注意力(Masked Multi-Head Attention)部分,这主要是因为在翻译的过程中,编码器端主要用于编码源语言序列的信息,而这个序列是完全已知的,因而编码器仅需要考虑如何融合上下文语义信息解码器端则负责生成目标语言序列,这一生成过程是自回归的,即对于每一个单词的生成过程,仅有当前单词之前的目标语言序列是可以被观测的,因此这一额外增加的掩码是用来掩盖后续的文本信息的,以防模型在训练阶段直接看到后续的文本序列,进而无法得到有效的训练。

此外,解码器端额外增加了一个多头交叉注意力(Multi-Head Cross-Attention)模块,使用交叉注意力(Cross-Attention)方法,同时接收来自编码器端的输出和当前Transformer块的前一个掩码注意力层的输出。Query是通过解码器前一层的输出进行投影的,Key和Value是使用编码器的输出进行投影的。它的作用是在翻译的过程中,为了生成合理的目标语言序列,观测待翻译的源语言序列是什么。基于上述编码器和解码器结构,待翻译的源语言文本,经过编码器端的每个Transformer块对其上下文语义进行层层抽象,最终输出每一个源语言单词上下文相关的表示。

解码器端以自回归的方式生成目标语言文本,即在每个时间步 t t t,根据编码器输出的源语言文本表示,以及前 t − 1 t-1 t1个时刻生成的目标语言文本,生成当前时刻的目标语言单词。

使用PyTorch实现的编码器代码:

class EncoderLayer(nn.Module):def __init__(self, d_model, num_heads, dropout=0.1):super(EncoderLayer, self).__init__()self.norm1 = Norm(d_model)self.norm2 = Norm(d_model)self.attn = MultiHeadAttention(num_heads, d_model, dropout=dropout)self.ff = FeedForward(d_model, dropout=dropout)self.dropout1 = nn.Dropout(dropout)self.dropout2 = nn.Dropout(dropout)def forward(self, x, mask):attn_output = self.attn(x, x, x, mask)attn_output = self.dropout_1(attn_output)x = x + attn_outputx = self.norm1(x)ff_output = self.ff(x)ff_output = self.dropout2(ff_output)x = x + ff_outputx = self.norm2(x)return xclass Encoder(nn.Module):def __init__(self, src_vocab_size, d_model, N, num_heads, dropout):super(Encoder, self).__init__()self.N = Nself.src_emb = nn.Embedding(src_vocab_size, d_model)self.pe = PositionalEncoder(d_model)self.layers = nn.ModuleList([EncoderLayer(d_model, num_heads, dropout) for _ in range(N)])self.norm = Norm(d_model)def forward(self, src, mask):x = self.embed(src)x = self.pe(x)for i in range(self.N):x = self.layers[i](x, mask)return self.norm(x)class DecoderLayer(nn.Module):def __init__(self, d_model, num_heads, dropout=0.1):super(DecoderLayer, self).__init__()self.norm_1 = Norm(d_model)self.norm_2 = Norm(d_model)self.norm_3 = Norm(d_model)self.dropout_1 = nn.Dropout(dropout)self.dropout_2 = nn.Dropout(dropout)self.dropout_3 = nn.Dropout(dropout)self.attn_1 = MultiHeadAttention(num_heads, d_model, dropout=dropout)self.attn_2 = MultiHeadAttention(num_heads, d_model, dropout=dropout)self.ff = FeedForward(d_model, dropout=dropout)def forward(self, x, e_outputs, src_mask, trg_mask):attn_output_1 = self.attn_1(x, x, x, trg_mask)attn_output_2 = self.dropout_1(attn_output_1)x = x + attn_output_1x = self.norm_1(x)attn_output_2 = self.attn_2(x, e_outputs, e_outputs, src_mask)attn_output_2 = self.dropout_2(attn_output_2)x = x + attn_output_2x = self.norm_2(x)ff_output = self.ff(x)ff_output = self.dropout_3(ff_output)x = x + ff_outputx = self.norm_3(x)return xclass Decoder(nn.Module):def __init__(self, vocab_size, d_model, N, num_heads, dropout):super(Decoder, self).__init__()self.embed = nn.Embedding(vocab_size, d_model)self.pe = PositionalEncoder(d_model)self.layers = nn.ModuleList([DecoderLayer(d_model, num_heads, dropout) for _ in range(N)])self.norm = Norm(d_model)def forward(self, trg, e_outputs, src_mask, trg_mask):x = self.embed(trg)x = self.pe(x)for i in range(self.N):x = self.layers[i](x, e_outputs, src_mask, trg_mask)return self.norm(x)

完整代码可参考相关链接大规模语言模型:从理论到实践

参考文献

  1. 模型结构简介以及位置编码Positional Encoding
  2. 新手深入浅出理解PyTorch归一化层全解析
  3. encoder(编码器)和decoder(解码器)
  4. Transformer的模型和代码解析

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

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

相关文章

【练习】二分查找思想

🎥 个人主页:Dikz12🔥个人专栏:算法(Java)📕格言:吾愚多不敏,而愿加学欢迎大家👍点赞✍评论⭐收藏 目录 二分查找算法介绍 1.二分查找 题目描述 讲解 ​编辑 代码实现 2.…

STM32应用开发——BH1750光照传感器详解

STM32应用开发——BH1750光照传感器详解 目录 STM32应用开发——BH1750光照传感器详解前言1 硬件介绍1.1 BH1750简介1.2 硬件接线 2 软件编程2.1 软件原理2.1.1 IIC设备地址2.1.2 IIC读写2.1.3 BH1750指令集2.1.4 BH1750工作流程2.1.5 BH1750测量模式 2.2 测试代码2.3 运行测试…

【剪映专业版】10时间线工具:主轨磁吸、自动吸附、联动、预览轴、全局缩放预览

视频课程:B站有知公开课【剪映电脑版教程】 主轨:有封面标志的轨道才是主轨。 主轨磁吸:开启后,在主轨上移动素材,自动向前磁吸,在其他轨道上移动无此效果;关闭后,不自动向前磁吸&…

6个步骤轻松实现Postman接口压力测试(建议收藏)

🍅 视频学习:文末有免费的配套视频可观看 🍅 关注公众号:互联网杂货铺,回复1 ,免费获取软件测试全套资料,资料在手,涨薪更快 这里讲是postman做接口并发测试,基础用法不做…

这些年背过的面试题--MySQL篇

MySQL一直是面试中的热点问题,也难道了很多的面试者。其实MySQL没那么难,只是大家没有系统化、实战性的过去学习、总结。同时很多开发者在实际的开发过程中也很少去接触一些偏向底层的知识。 本手册,已总结成完整的PDF文档。想获取该文档&am…

opencv的cmake报错

opencv编译报错 CMakeDownloadLog.txt #use_cache "D:/opencv/.cache" #do_unpack "ippicv_2021.8_win_intel64_20230330_general.zip" "71e4f58de939f0348ec7fb58ffb17dbf" "https://raw.githubusercontent.com/opencv/opencv_3rdparty/1…

遇事不决 量子力学?

文章目录 引入量子力学产生的必然性量子力学名称的由来粒子?波?波粒二象性测不准原理 (不确定原理)叠加态原理 量子纠缠态叠加量子纠缠量子纠缠实验 逻辑判断,量子力学到底完善吗观测量子纠缠?那我们宏观世…

快慢指针的应用

快慢指针的原理 快慢指针是利用两个指针移动速度的不一样,实现一些有趣的追击行为。就像上学时求解的那些操场上的追击问题一样。 问题1:判断两个链表是否相交。(链表不存在环结构) 形如: 方法:循环第一…

谷歌开源专业代码模型:对硬件要求低,性能超强!

谷歌发布了面向企业、开发人员的全新代码模型Code Gemma,一共有基础预训练、指令微调和快速推理三个版本。 Code Gemma是基于谷歌在今年2月发布的Gemma模型之上开发而成。其参数很小只有20亿和70亿两种,但使用了超过5000亿tokens的代码、数学、文本等数…

vue2和vue3的v-if与v-for优先级对比

Vue.js 中使用最多的两个指令就是 v-if 和 v-for,因此我们可能会想要同时使用它们。虽然官方不建议这样做,但有时确实是必须的,我们来了解下他们的工作方式: 在 vue 2.x 中,在一个元素上同时使用 v-if 和 v-for 时&am…

【第七届openGauss技术文章征集】 openGauss新版本征文活动来啦!

活动背景 2024年3月30日,openGauss 6.0.0版本正式上线,该版本与之前版本特性功能保持兼容,在内核能力、DataPod三层资源池化架构、DataKit数据全生命周期管理平台、生态兼容性等方面全面增强。(下方【点击原文】即可查看更多【新…

【GPT-4最新研究】GPT-4与科学探索:揭秘语言模型在科学领域的无限可能

各位朋友们,你们知道吗?自然语言处理领域最近取得了巨大的突破!大型语言模型(LLM)的出现,简直就像打开了新世界的大门。它们不仅在语言理解、生成和翻译方面表现出色,还能涉足许多其他领域&…

【绘图案例-屏幕截图 Objective-C语言】

一、屏幕截图 1.接下来,我们来说这个屏幕截图, 1.看一下我们的ppt, 屏幕截图呢,核心代码,也就是一句话, 我们看ppt上说,有时候需要从屏幕上的某一个View啊,截取一个图片出来,然后呢,这个里边儿,它举的一个例子,就是这个东西,捕鱼达人的这个图片,实际上,屏幕截图…

突破界限:LangChain 引领 AI 应用构建的新时代

前言 在上一篇文章中我们对 ChatWithPDF 的方案设计进行了整体的概览,现在细化下整体流程,如下图所示: 针对上面的流程,我们思考🤔一下会面临的问题: 如何读取 PDF 中的内容?(肯定…

一些重新开始面试之后的八股文汇总

一、内存中各项名词说明 1、机器内存概念说明 linux中的free命令可以查看机器的内存使用情况,vmstat命令也可以 其中不容易被理解的是: 内存缓冲/存数(buffer/cached) 1.buffers和cache也是RAM划分出来的一部分地址空间 2.buff…

如何利用逻辑引擎的对象变量节点优化表单数据处理?

JVS逻辑引擎-对象变量节点 概述 对象变量是面向对象编程中的一个重要概念。在编程中,对象变量用于存储对象的引用。在Java中,使用“类名变量名”可以创建一个对象变量,但此时对象变量本身并没有任何值。要赋予对象变量具体的值,…

Type-C接口PD取电IC6500

一、引言 随着科技的不断进步,移动设备已经成为我们日常生活中不可或缺的一部分。其中,电源管理对于确保设备的稳定运行和延长电池寿命至关重要。Type-C接口作为现代移动设备的主要电源和数据接口,其高效、便捷的特性得到了广泛应用。而Powe…

基于springboot实现音乐网站管理系统项目【项目源码+论文说明】计算机毕业设计

基于SpringBoot实现音乐网站管理系统演示 摘要 随着信息技术在管理上越来越深入而广泛的应用,管理信息系统的实施在技术上已逐步成熟。本文介绍了音乐网站的开发全过程。通过分析音乐网站管理的不足,创建了一个计算机管理音乐网站的方案。文章介绍了音乐…

李彦宏:开源模型会越来越落后

李彦宏:开源模型会越来越落后 昨天听完的李总讲座 大家以前用开源觉得开源便宜,其实在大模型场景下,开源是最贵的。所以,开源模型会越来越落后。 ——李彦宏 至于开源还是闭源,这和企业的利益息息相关。 随着科技的迅猛…

新品上市|水深测量专家 HD-680双变频测深仪

自动跟踪 一键测量 测深参数自动调节,换能器内置温度传感器,声速自动解算,提升数据精度,解放双手,提高测量效率。 高低频结合可实时显示淤泥厚度 HD-680双变频测深仪高低频双通道同时工作,全新升级的双频测…