第十四课.Transformer
目录
- 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=i∣X,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)=dxiTq - 双线性模型
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=1∑Naixi
即:
在软注意力中,输入信息一方面要用于计算注意力,另一方面是被注意力提取的对象,这对输入信息来说,负担过重。因此,提出了键值对注意力;
键值对注意力 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 即为输入信息 XXX;KKK 对应的信息不固定,只要是和 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=1∑Naivi
当 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=WQX∈Rd3×N
K=WKX∈Rd3×NK=W_{K}X\in R^{d_{3}\times N}K=WKX∈Rd3×N
V=WVX∈Rd2×NV=W_{V}X\in R^{d_{2}\times N}V=WVX∈Rd2×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
当使用缩放点积打分时,输出向量序列为:
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(d3KTQ,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=-1
即dim=-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}iq∈RSourceVocabSize反映了该词在词汇表中的序号(one-hot编码)
inputs=[i1,i2,...,iN]inputs=[i_{1},i_{2},...,i_{N}]inputs=[i1,i2,...,iN]
目标序列,tq∈RTargetVocabSizet_{q}\in R^{TargetVocabSize}tq∈RTargetVocabSize反映了该词在词汇表中的序号(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}oq∈RTargetVocabSize反映了词汇表中词的概率;
词嵌入与位置信息融合
输入序列词嵌入为:
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}pos∈postioninputs,i∈(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(el−1),l∈[1,n]
其中,e0∈RN,dmodele_{0}\in R^{N,d_{model}}e0∈RN,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}}ein∈RN,dmodel为编码器层输入,eout∈RN,dmodele_{out}\in R^{N,d_{model}}eout∈RN,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
可学习的参数为:
[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,i∈Rdmodel,dK,WK,i∈Rdmodel,dK,WV,i∈Rdmodel,dV,WO∈Rh⋅dV,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(dl−1),l∈[1,n]
outputsprobabilities=softmax(dnW)outputs_{probabilities}=softmax(d_{n}W)outputsprobabilities=softmax(dnW)
其中,d0∈RM,dmodeld_{0}\in R^{M,d_{model}}d0∈RM,dmodel为解码器的输入,dl∈RM,dmodeld_{l}\in R^{M,d_{model}}dl∈RM,dmodel为解码器第lll层的输出,W∈Rdmodel,TargetVocabSizeW\in R^{d_{model},TargetVocabSize}W∈Rdmodel,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}}din∈RM,dmodel为解码器输入,dout∈RM,dmodeld_{out}\in R^{M,d_{model}}dout∈RM,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相关推荐
- 孙鑫mfc学习笔记第十四课
第十四课 网络的相关知识,网络程序的编写,Socket是连接应用程序与网络驱动程序的桥梁,Socket在应用程序中创建,通过bind与驱动程序建立关系.此后,应用程序送给Socket的数据,由Sock ...
- NeHe OpenGL第二十四课:扩展
NeHe OpenGL第二十四课:扩展 扩展,剪裁和TGA图像文件的加载: 在这一课里,你将学会如何读取你显卡支持的OpenGL的扩展,并在你指定的剪裁区域把它显示出来. 这个教程有一些难度,但它 ...
- 计算机病毒ppt教案免费,第十四课 计算机病毒 课件(共14张ppt)+教案
第十四课 计算机病毒 课件(共14张ppt)+教案 ==================资料简介====================== 第十四课 计算机病毒 课件:14张PPT 第十四课 计算机 ...
- window.addeventlistener 不能调用方法_Java入门第十四课:如何定义”方法“
第十四课,学习定义方法.一个对象包含三种最常见的成员:构造器.Field和方法.Field用于定义状态数据,而方法是行为特征的抽象. 那么什么是方法呢? 在Java中,方法就是用来完成解决某件事情或实 ...
- python dataframe 新列_Python第二十四课:Pandas库(四)
Python第二十四课:Pandas库(四)点击上方"蓝字",关注我们. 不知不觉,我们已经跨越了千难万险,从零开始,一步步揭开了Python神秘的面纱.学到至今,回过头,才晓得自 ...
- NeHe OpenGL教程 第四十四课:3D光晕
转自[翻译]NeHe OpenGL 教程 前言 声明,此 NeHe OpenGL教程系列文章由51博客yarin翻译(2010-08-19),本博客为转载并稍加整理与修改.对NeHe的OpenGL管线 ...
- NeHe OpenGL第四十四课:3D光晕
NeHe OpenGL第四十四课:3D光晕 3D 光晕 当镜头对准太阳的时候就会出现这种效果,模拟它非常的简单,一点数学和纹理贴图就够了.好好看看吧. 大家好,欢迎来到新的一课,在这一课中我们将扩 ...
- 实践数据湖iceberg 第三十四课 基于数据湖icerberg的流批一体架构-流架构测试
系列文章目录 实践数据湖iceberg 第一课 入门 实践数据湖iceberg 第二课 iceberg基于hadoop的底层数据格式 实践数据湖iceberg 第三课 在sqlclient中,以sql ...
- 实践数据湖iceberg 第十四课 元数据合并(解决元数据随时间增加而元数据膨胀的问题)
系列文章目录 实践数据湖iceberg 第一课 入门 实践数据湖iceberg 第二课 iceberg基于hadoop的底层数据格式 实践数据湖iceberg 第三课 在sqlclient中,以sql ...
最新文章
- missing required icon file.图标错误解决
- ActiveMQ传输文件的几种方式原理与优劣
- 何樱c语言,C语言程序设计-电子教案-连卫民(442页)-原创力文档
- 【hihocoder - offer编程练习赛60 B】最大顺子(双指针,思维)
- Docker 容器遇到的乱码问题
- 1121. Damn Single (25)-PAT甲级真题
- StoryBoard和代码结合 按比例快速兼容iPhone6/6 Plus教程
- the third assignment of software testing
- 怎么注册开通个人微信小程序
- linux系统怎么改输入法,linux系统输入法怎么切换
- S7-1500 SD卡格式化
- docker配置aria2
- android去掉锁屏界面,android怎么去掉锁屏界面
- 关于php的梗儿_php是世界上最好的语言是什么梗?
- npm方法创建一个vue项目,引入element插件
- 集合查询和查询结果处理
- html转换高清pdf,html转换pdf
- 在阿里云ACP认证考试中授权码有效期时限是多久?
- Logback日志名和日志内容配置增加ip等信息
- 公众号(服务号)申请与认证