目录

  • Seq2Seq的编码器-解码器架构与Attention机制
    • 柔性注意力 Soft Attention
    • 键值对注意力 Key-Value Pair Attention
    • 自注意力 Self-Attention
    • 多头注意力 Multi-Head Attention
  • Transformer通用特征提取器
    • 输入,目标,输出序列
    • 词嵌入与位置信息融合
    • 编码器
    • 解码器
  • 训练与翻译

谷歌的Transformer模型最早是用于机器翻译任务,当时达到了SOTA效果。Transformer改进了RNN最被人诟病的训练慢的缺点,利用self-attention机制实现快速并行。并且Transformer可以增加到非常深的深度,充分发掘DNN模型的特性,提升模型准确率;Transformer由论文 《Attention is All You Need》提出,现在被广泛应用于NLP的各个领域。目前在NLP各业务全面发展的模型如GPT,BERT等,都是基于Transformer模型

Seq2Seq的编码器-解码器架构与Attention机制

首先,关于Seq2Seq的Encoder-Decoder模型与Luong Attention机制原理及实现可回顾 第十二课Seq2Seq与Attention

Encoder-Decoder模型本质是两个循环神经网络(一般使用GRU)进行连接;假设现在有一个Seq元组:一句英文,一句中文,句子已经分词处理过,令xxx表示英语的分词,yyy表示中文的分词,既有:
(x,y):[x1,x2,x3]∣[y1,y2,y3,y4](x,y):[x_{1},x_{2},x_{3}]|[y_{1},y_{2},y_{3},y_{4}](x,y):[x1,x2,x3][y1,y2,y3,y4]
按照Seq2Seq的一般处理格式,会构造(x1,x2,x3,y1)(x_{1},x_{2},x_{3},y_{1})(x1,x2,x3,y1)为输入数据,(y2,y3,y4)(y_{2},y_{3},y_{4})(y2,y3,y4)为标签;

Encoder-Decoder的网络结构如下:

上述结构中,Encoder的初始输入 hidden state:h0h_{0}h0 可使用零向量,Decoder输出的预测结果为(yp2,yp3,yp4)(yp_{2},yp_{3},yp_{4})(yp2,yp3,yp4),对比标签数据(y2,y3,y4)(y_{2},y_{3},y_{4})(y2,y3,y4),机器翻译问题即转为普通的分类任务;Decoder其实是一个语言模型,利用当前中文分词,顺序预测后面的中文分词;

早期Attention机制通常有Bahdanau Attention与Luong Attention,两种注意力的理论相似,Luong Attention使用更加广泛。通常Attention会结合原始Encoder和原始Decoder的输出,重新整合得到新的输出:

网络的Encoder输出为序列oso_{s}os(每个元素是一个词向量),原始Decoder输出序列为oco_{c}oc,经过Luong Attention整合信息得到输出ypypyp

以机器翻译(英文到中文)为例,在原始的Encoder-Decoder模型里,英文句子的信息被压缩在Encoder的输出 hidden state 里,这不可避免的造成大量信息损失,对翻译中文不利,引入注意力后,给原始Decoder的某个输出词向量融合了其对应的重要英文分词信息,能提升翻译出该中文分词的准确性;

注意力机制的本质是更针对性实现特征提取,即加权平均,发展至今,注意力也有了不同的分类;

柔性注意力 Soft Attention

输入信息 X=[x1,x2,...,xN]X=[x_{1},x_{2},...,x_{N}]X=[x1,x2,...,xN],注意力计算过程如下:

  • 1.在输入信息上计算注意力分布;
  • 2.根据注意力分布计算输入信息的加权平均;

注意力分布
给定一个和任务相关的查询向量 qqq,用注意力变量 z∈[1,N]z\in [1,N]z[1,N] 表示被选择信息的索引位置,即 z=iz=iz=i 表示选择了第 iii 个输入信息,其中查询向量 qqq 可以是动态生成的,也可以是可学习的参数;通常在Seq2Seq中,查询向量会使用当前模型的输出信息(比如Encoder-Decoder当前输出词对应的词向量);

在给定输入信息 XXX 和查询向量 qqq 后,选择第 iii 个输入信息的概率:
ai=P(z=i∣X,q)=softmax(score(xi,q))=exp(score(xi,q))∑j=1Nexp(score(xj,q))a_{i}=P(z=i|X,q)=softmax(score(x_{i},q))=\frac{exp(score(x_{i},q))}{\sum_{j=1}^{N}exp(score(x_{j},q))}ai=P(z=iX,q)=softmax(score(xi,q))=j=1Nexp(score(xj,q))exp(score(xi,q))
其中,aia_{i}ai 称为注意力分布,反映提取信息 xix_{i}xi 的程度,score(xi,q)score(x_{i},q)score(xi,q) 为注意力打分函数;

打分函数有不同的形式:

  • 加性模型
    score(xi,q)=vTtanh(Wxi+Uq)score(x_{i},q)=v^{T}tanh(Wx_{i}+Uq)score(xi,q)=vTtanh(Wxi+Uq)
  • 点积模型(常用)
    score(xi,q)=xiTqscore(x_{i},q)=x_{i}^{T}qscore(xi,q)=xiTq
  • 缩放点积模型(常用)
    score(xi,q)=xiTqdscore(x_{i},q)=\frac{x_{i}^{T}q}{\sqrt{d}}score(xi,q)=d

    xiTq
  • 双线性模型
    score(xi,q)=xiTWqscore(x_{i},q)=x_{i}^{T}Wqscore(xi,q)=xiTWq

其中,[W,U,v][W,U,v][W,U,v]为待学习参数,ddd 为输入信息的维度。点积模型的计算效率更高,当输入信息 xix_{i}xi 的维度维度 ddd 较大,可以通过缩放点积平衡数值。注意力分布可解释为在给定查询向量下,第 iii 个信息的受关注程度;

加权平均
基于注意力分布和输入信息,得到:
attn(X,q)=∑i=1Naixiattn(X,q)=\sum_{i=1}^{N}a_{i}x_{i}attn(X,q)=i=1Naixi
即:

在软注意力中,输入信息一方面要用于计算注意力,另一方面是被注意力提取的对象,这对输入信息来说,负担过重。因此,提出了键值对注意力;

键值对注意力 Key-Value Pair Attention

输入信息为:
(K,V)=[(k1,v1),(k2,v2),...,(kN,vN)](K,V)=[(k_{1},v_{1}),(k_{2},v_{2}),...,(k_{N},v_{N})](K,V)=[(k1,v1),(k2,v2),...,(kN,vN)]
其中,键用于计算注意力分布 aia_{i}ai,值用来计算聚合信息,通常值 VVV 即为输入信息 XXXKKK 对应的信息不固定,只要是和 VVV 有关系的对象均可以做为 KKK
给定查询向量 qqq ,注意力分布为:
ai=exp(score(ki,q))∑j=1Nexp(score(kj,q))a_{i}=\frac{exp(score(k_{i},q))}{\sum_{j=1}^{N}exp(score(k_{j},q))}ai=j=1Nexp(score(kj,q))exp(score(ki,q))
加权平均:
attn((K,V),q)=∑i=1Naiviattn((K,V),q)=\sum_{i=1}^{N}a_{i}v_{i}attn((K,V),q)=i=1Naivi
K=VK=VK=V 时,键值对注意力就是柔性注意力;

自注意力 Self-Attention

在键值对注意力中,有两个量(K,q)(K,q)(K,q)比较模糊,没有一个统一的标准,于是提出自注意力机制,输入序列为:
X=[x1,x2,...,xN]∈Rd1×NX=[x_{1},x_{2},...,x_{N}]\in R^{d_{1}\times N}X=[x1,x2,...,xN]Rd1×N
输出序列为:
H=[h1,h2,...,hN]∈Rd2×NH=[h_{1},h_{2},...,h_{N}]\in R^{d_{2}\times N}H=[h1,h2,...,hN]Rd2×N
通过线性变换得到向量序列:
Q=WQX∈Rd3×NQ=W_{Q}X\in R^{d_{3}\times N}Q=WQXRd3×N
K=WKX∈Rd3×NK=W_{K}X\in R^{d_{3}\times N}K=WKXRd3×N
V=WVX∈Rd2×NV=W_{V}X\in R^{d_{2}\times N}V=WVXRd2×N
其中,[Q,K,V][Q,K,V][Q,K,V] 分别为查询向量,键向量,值向量;[WQ,WK,WV][W_{Q},W_{K},W_{V}][WQ,WK,WV]为待学习参数;

预测输出向量:
h^i=attn((K,V),qi)=∑j=1Nai,jvj=∑j=1Nsoftmax(score(kj,qi))vj\widehat{h}_{i}=attn((K,V),q_{i})=\sum_{j=1}^{N}a_{i,j}v_{j}=\sum_{j=1}^{N}softmax(score(k_{j},q_{i}))v_{j}h

i=attn((K,V),qi)=j=1Nai,jvj=j=1Nsoftmax(score(kj,qi))vj
当使用缩放点积打分时,输出向量序列为:
Hd2×N=WVXsoftmax(KTQd3,axis=−1)H_{d_{2}\times N}=W_{V}Xsoftmax(\frac{K^{T}Q}{\sqrt{d_{3}}},axis=-1)Hd2×N=WVXsoftmax(d3

KTQ
,axis=
1)


pytorch 中softmax函数举例:

import torch
import torch.nn as nnmat=torch.randn(2,2)
print(mat,mat.size())
softmax=nn.Softmax(dim=1)
output=softmax(mat)
print(output,output.size())"""
tensor([[ 0.0081,  0.1971],[-0.2666, -1.0529]]) torch.Size([2, 2])
tensor([[0.4529, 0.5471],[0.6870, 0.3130]]) torch.Size([2, 2])
"""

axis=-1dim=-1,即在最后一维上操作,在(2,2)的张量上,体现为沿着列轴计算softmax:
0.4529=exp(0.0081)exp(0.0081)+exp(0.1971)0.4529=\frac{exp(0.0081)}{exp(0.0081)+exp(0.1971)}0.4529=exp(0.0081)+exp(0.1971)exp(0.0081)


多头注意力 Multi-Head Attention

多头注意力起源于自注意力,多头注意力为:
attn(X)=attn((K1,V1),Q1)⊕attn((K2,V2),Q2)⊕...⊕attn((Kh,Vh),Qh)attn(X)=attn((K_{1},V_{1}),Q_{1})\oplus attn((K_{2},V_{2}),Q_{2})\oplus ...\oplus attn((K_{h},V_{h}),Q_{h})attn(X)=attn((K1,V1),Q1)attn((K2,V2),Q2)...attn((Kh,Vh),Qh)
其中的 ⊕\oplus 表示张量拼接,多头注意力相当于给出了注意力层的多个"表示空间",即融合了不同角度的自注意力信息;

Transformer通用特征提取器

Transformer是一种架构,目的是用于实现一种通用的特征提取器。模型架构如下:

模型有两个输入,一个输出,左部被称为编码器,右部被称为解码器。左边的输入为源序列,右边输入为目标序列,目标序列是一个固定长度的输入序列;

图中的 N×N\timesN× 表示网络的堆叠,图中的灰色部分表示一层单元(比如左边的Encoder单元和右边的Decoder单元),加深网络可以通过重复堆叠单元实现。

输入,目标,输出序列

输入序列,iq∈RSourceVocabSizei_{q}\in R^{SourceVocabSize}iqRSourceVocabSize反映了该词在词汇表中的序号(one-hot编码)
inputs=[i1,i2,...,iN]inputs=[i_{1},i_{2},...,i_{N}]inputs=[i1,i2,...,iN]
目标序列,tq∈RTargetVocabSizet_{q}\in R^{TargetVocabSize}tqRTargetVocabSize反映了该词在词汇表中的序号(one-hot编码)
targets=[t1,t2,...,tM]targets=[t_{1},t_{2},...,t_{M}]targets=[t1,t2,...,tM]
其中还有
outputsprobabilities=Transformer(inputs,targets)=(o1,o2,...,oM)outputs_{probabilities}=Transformer(inputs,targets)=(o_{1},o_{2},...,o_{M})outputsprobabilities=Transformer(inputs,targets)=(o1,o2,...,oM)
outputsprobabilitiesoutputs_{probabilities}outputsprobabilities为预测结果,oq∈RTargetVocabSizeo_{q}\in R^{TargetVocabSize}oqRTargetVocabSize反映了词汇表中词的概率;

词嵌入与位置信息融合

输入序列词嵌入为:
Embedding(inputs)∈RN,dmodelEmbedding(inputs)\in R^{N,d_{model}}Embedding(inputs)RN,dmodel
其中,NNN为输入序列长度,dmodeld_{model}dmodel为词嵌入维度,输入序列位置编码为:
PosEnc(postioninputs)∈RN,dmodelPosEnc(postion_{inputs})\in R^{N,d_{model}}PosEnc(postioninputs)RN,dmodel
其中,postioninputs=(1,2,...,p,...,N)postion_{inputs}=(1,2,...,p,...,N)postioninputs=(1,2,...,p,...,N) 为各个字符在句子中对应的位置序号;

位置编码计算为:
PosEnc(pos,2i)=sin(pos100002i/dmodel),PosEnc(pos,2i+1)=cos(pos100002i/dmodel)PosEnc(pos,2i)=sin(\frac{pos}{10000^{2i/d_{model}}}),PosEnc(pos,2i+1)=cos(\frac{pos}{10000^{2i/d_{model}}})PosEnc(pos,2i)=sin(100002i/dmodelpos),PosEnc(pos,2i+1)=cos(100002i/dmodelpos)
其中,pos∈postioninputspos\in postion_{inputs}pospostioninputsi∈(0,1,...,dmodel/2)i\in (0,1,...,d_{model}/2)i(0,1,...,dmodel/2)


融合位置信息的目的:注意力机制没有考虑单词的位置信息,而是单纯的加权平均,所以在Transformer中添加了位置信息


将词嵌入与位置信息融合:
Embedding(inputs)+PosEnc(postioninputs)Embedding(inputs)+PosEnc(postion_{inputs})Embedding(inputs)+PosEnc(postioninputs)
同样的,目标序列也进行对应的融合:
Embedding(targets)+PosEnc(postiontargets)Embedding(targets)+PosEnc(postion_{targets})Embedding(targets)+PosEnc(postiontargets)

编码器

编码器的计算为:
e0=Embedding(inputs)+PosEnc(postioninputs)e_{0}=Embedding(inputs)+PosEnc(postion_{inputs})e0=Embedding(inputs)+PosEnc(postioninputs)
el=EncoderLayer(el−1),l∈[1,n]e_{l}=EncoderLayer(e_{l-1}),l\in [1,n]el=EncoderLayer(el1),l[1,n]
其中,e0∈RN,dmodele_{0}\in R^{N,d_{model}}e0RN,dmodel为编码器输入,nnn为编码器层数,ele_{l}el为第lll层编码器的输出;
编码器EncoderLayerEncoderLayerEncoderLayer
emid=LayerNorm(ein+MultiHeadAttention(ein))e_{mid}=LayerNorm(e_{in}+MultiHeadAttention(e_{in}))emid=LayerNorm(ein+MultiHeadAttention(ein))
eout=LayerNorm(emid+FFN(emid))e_{out}=LayerNorm(e_{mid}+FFN(e_{mid}))eout=LayerNorm(emid+FFN(emid))
其中,ein∈RN,dmodele_{in}\in R^{N,d_{model}}einRN,dmodel为编码器层输入,eout∈RN,dmodele_{out}\in R^{N,d_{model}}eoutRN,dmodel为编码器层输出,MultiHeadAttentionMultiHeadAttentionMultiHeadAttention为多头注意力机制,FFNFFNFFN为前馈神经网络,LayerNormLayerNormLayerNorm为层归一化;


图中的多头注意力机制输入有三条支路,代表了三个待学习参数[WQ,WK,WV][W_{Q},W_{K},W_{V}][WQ,WK,WV]


关于Transformer的缩放点积和多头注意力机制:

注意左图的[Q,K,V][Q,K,V][Q,K,V]是右图[Q,K,V][Q,K,V][Q,K,V]经过线性变换得到的;

输入向量序列ein=[ein1,ein2,...,einN]∈RN,dmodele_{in}=[e_{in1},e_{in2},...,e_{inN}]\in R^{N,d_{model}}ein=[ein1,ein2,...,einN]RN,dmodel,分别得到查询向量序列Q=einQ=e_{in}Q=ein,键向量序列K=einK=e_{in}K=ein,值向量序列V=einV=e_{in}V=ein

使用缩放点积打分的多头注意力机制:
MultiHeadAttention(ein)=Concat(head1,...,headh)WOMultiHeadAttention(e_{in})=Concat(head_{1},...,head_{h})W_{O}MultiHeadAttention(ein)=Concat(head1,...,headh)WO
其中,多头输出:
headi=Attention(QWQ,i,KWK,i,VWV,i)=softmax(QWQ,i(KWK,i)TdK)VWV,ihead_{i}=Attention(QW_{Q,i},KW_{K,i},VW_{V,i})=softmax(\frac{QW_{Q,i}(KW_{K,i})^{T}}{\sqrt{d_{K}}})VW_{V,i}headi=Attention(QWQ,i,KWK,i,VWV,i)=softmax(dK

QWQ,i(KWK,i)T)VWV,i
可学习的参数为:
[WQ,i∈Rdmodel,dK,WK,i∈Rdmodel,dK,WV,i∈Rdmodel,dV,WO∈Rh⋅dV,dmodel][W_{Q,i}\in R^{d_{model},d_{K}},W_{K,i}\in R^{d_{model},d_{K}},W_{V,i}\in R^{d_{model},d_{V}},W_{O}\in R^{h\cdot d_{V},d_{model}}][WQ,iRdmodel,dK,WK,iRdmodel,dK,WV,iRdmodel,dV,WORhdV,dmodel]


Mask操作可以省略,Mask操作是为了保留非填充(pad)的词


前馈神经网络为全连接网络,两层网络如下:
FFN(emid)=ReLU(emidW1+b1)W2+b2FFN(e_{mid})=ReLU(e_{mid}W_{1}+b_{1})W_{2}+b_{2}FFN(emid)=ReLU(emidW1+b1)W2+b2

解码器

d0=Embedding(targets)+PosEnc(postiontargets)d_{0}=Embedding(targets)+PosEnc(postion_{targets})d0=Embedding(targets)+PosEnc(postiontargets)
dl=DecoderLayer(dl−1),l∈[1,n]d_{l}=DecoderLayer(d_{l-1}),l\in [1,n]dl=DecoderLayer(dl1),l[1,n]
outputsprobabilities=softmax(dnW)outputs_{probabilities}=softmax(d_{n}W)outputsprobabilities=softmax(dnW)
其中,d0∈RM,dmodeld_{0}\in R^{M,d_{model}}d0RM,dmodel为解码器的输入,dl∈RM,dmodeld_{l}\in R^{M,d_{model}}dlRM,dmodel为解码器第lll层的输出,W∈Rdmodel,TargetVocabSizeW\in R^{d_{model},TargetVocabSize}WRdmodel,TargetVocabSize

解码器层DecoderLayerDecoderLayerDecoderLayer
dmid1=LayerNorm(din+MaskedMultiHeadAttention(din))d_{mid1}=LayerNorm(d_{in}+MaskedMultiHeadAttention(d_{in}))dmid1=LayerNorm(din+MaskedMultiHeadAttention(din))
dmid2=LayerNorm(dmid1+MultiHeadAttention(dmid1,eout))d_{mid2}=LayerNorm(d_{mid1}+MultiHeadAttention(d_{mid1},e_{out}))dmid2=LayerNorm(dmid1+MultiHeadAttention(dmid1,eout))
dout=LayerNorm(dmid2+FFN(dmid2))d_{out}=LayerNorm(d_{mid2}+FFN(d_{mid2}))dout=LayerNorm(dmid2+FFN(dmid2))
其中,din∈RM,dmodeld_{in}\in R^{M,d_{model}}dinRM,dmodel为解码器输入,dout∈RM,dmodeld_{out}\in R^{M,d_{model}}doutRM,dmodel为解码器输出;

由于解码器的目标序列是逐步向后移动的固定长度输入,在预测当前序列时,使用MaskedMultiHeadAttentionMaskedMultiHeadAttentionMaskedMultiHeadAttention可以遮挡住当前输入中文数据内,目标序列以外的信息,确保当前计算所输入的目标序列是我们确实需要的信息。


MaskedMultiHeadAttentionMaskedMultiHeadAttentionMaskedMultiHeadAttention用于训练,因为训练是已知中文序列的,而翻译只有英文序列,所以不需要mask操作,使用中文逐个作为输入序列依次翻译即可;


训练与翻译

假设有一个英文与中文元组:

"""
(['BOS', 'why', 'me', 'EOS'],['BOS', '为', '什', '么', '是', '我', 'EOS'])
"""

对于Encoder-Decoder,或结合Luong Attention的Encoder-Decoder,都有训练格式如下:

  • 英文部分的输入为:BOS why me EOS
  • 中文部分的输入为:BOS 为 什 么 是 我
  • 标签为:为 什 么 是 我 EOS

翻译时:

  • 英文序列作为输入;
  • 模型固定为逐次调用以输出 max seq len 个词向量;
  • 将标志符号BOS作为中文的第一个分词,结合英文输入,得到第一个输出词向量,再将该词向量作为新的中文输入词向量,因此,可以依次得到一组输出词向量(一共 max seq len 个),每个词向量经过全连接映射得到one-hot编码,即得到输出的中文分词列表;
  • 顺着列表检查分词,如果出现标志符号EOS就截取前面的分词组成中文结果。

对于Transformer,如果设置目标序列长度固定为3,则输入的中文序列与对应标签依次为:

  • 输入中文BOS 为 什,标签为 什 么
  • 输入中文为 什 么,标签什 么 是
  • 输入中文什 么 是,标签么 是 我
  • 输入中文么 是 我,标签是 我 EOS

通常,每次设置目标序列长度为中文序列长度减1,该情况下的训练格式与Encoder-Decoder相同,mask操作只需遮挡中文序列最后一个元素EOS即可;

Transformer的翻译过程与Encoder-Decoder一致

第十四课.Transformer相关推荐

  1. 孙鑫mfc学习笔记第十四课

    第十四课 网络的相关知识,网络程序的编写,Socket是连接应用程序与网络驱动程序的桥梁,Socket在应用程序中创建,通过bind与驱动程序建立关系.此后,应用程序送给Socket的数据,由Sock ...

  2. NeHe OpenGL第二十四课:扩展

    NeHe OpenGL第二十四课:扩展 扩展,剪裁和TGA图像文件的加载: 在这一课里,你将学会如何读取你显卡支持的OpenGL的扩展,并在你指定的剪裁区域把它显示出来.   这个教程有一些难度,但它 ...

  3. 计算机病毒ppt教案免费,第十四课 计算机病毒 课件(共14张ppt)+教案

    第十四课 计算机病毒 课件(共14张ppt)+教案 ==================资料简介====================== 第十四课 计算机病毒 课件:14张PPT 第十四课 计算机 ...

  4. window.addeventlistener 不能调用方法_Java入门第十四课:如何定义”方法“

    第十四课,学习定义方法.一个对象包含三种最常见的成员:构造器.Field和方法.Field用于定义状态数据,而方法是行为特征的抽象. 那么什么是方法呢? 在Java中,方法就是用来完成解决某件事情或实 ...

  5. python dataframe 新列_Python第二十四课:Pandas库(四)

    Python第二十四课:Pandas库(四)点击上方"蓝字",关注我们. 不知不觉,我们已经跨越了千难万险,从零开始,一步步揭开了Python神秘的面纱.学到至今,回过头,才晓得自 ...

  6. NeHe OpenGL教程 第四十四课:3D光晕

    转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...

  7. NeHe OpenGL第四十四课:3D光晕

    NeHe OpenGL第四十四课:3D光晕 3D 光晕 当镜头对准太阳的时候就会出现这种效果,模拟它非常的简单,一点数学和纹理贴图就够了.好好看看吧.   大家好,欢迎来到新的一课,在这一课中我们将扩 ...

  8. 实践数据湖iceberg 第三十四课 基于数据湖icerberg的流批一体架构-流架构测试

    系列文章目录 实践数据湖iceberg 第一课 入门 实践数据湖iceberg 第二课 iceberg基于hadoop的底层数据格式 实践数据湖iceberg 第三课 在sqlclient中,以sql ...

  9. 实践数据湖iceberg 第十四课 元数据合并(解决元数据随时间增加而元数据膨胀的问题)

    系列文章目录 实践数据湖iceberg 第一课 入门 实践数据湖iceberg 第二课 iceberg基于hadoop的底层数据格式 实践数据湖iceberg 第三课 在sqlclient中,以sql ...

最新文章

  1. missing required icon file.图标错误解决
  2. ActiveMQ传输文件的几种方式原理与优劣
  3. 何樱c语言,C语言程序设计-电子教案-连卫民(442页)-原创力文档
  4. 【hihocoder - offer编程练习赛60 B】最大顺子(双指针,思维)
  5. Docker 容器遇到的乱码问题
  6. 1121. Damn Single (25)-PAT甲级真题
  7. StoryBoard和代码结合 按比例快速兼容iPhone6/6 Plus教程
  8. the third assignment of software testing
  9. 怎么注册开通个人微信小程序
  10. linux系统怎么改输入法,linux系统输入法怎么切换
  11. S7-1500 SD卡格式化
  12. docker配置aria2
  13. android去掉锁屏界面,android怎么去掉锁屏界面
  14. 关于php的梗儿_php是世界上最好的语言是什么梗?
  15. npm方法创建一个vue项目,引入element插件
  16. 集合查询和查询结果处理
  17. html转换高清pdf,html转换pdf
  18. 在阿里云ACP认证考试中授权码有效期时限是多久?
  19. Logback日志名和日志内容配置增加ip等信息
  20. 公众号(服务号)申请与认证

热门文章

  1. Java基础篇:Java集合
  2. MySQL基础篇:SELECT几种子句
  3. 深入理解MySQL执行过程及执行顺序
  4. 再爆安全漏洞,这次轮到Jackson了,竟由阿里云上报
  5. 拜托,别问我什么各种Tree了,干就完事!
  6. 查询太慢?看看ES是如何把索引的性能压榨到极致的!
  7. 程序员自购老板椅被HR搬去老板办公室:不能享受这么好的椅子
  8. 大型互联网大型分布式架构演进之路
  9. PingCode 全年上线功能盘点
  10. 大家对协同管理软件是怎么理解的?协同管理软件最主要需要解决企业/组织什么问题?