Transformer丨基础Transformer模型和代码详解

笔者在深度学习入门期间自学过Transformer,但是那时碍于急于求成,并未对其进行深度归纳与分享。

近期,笔者观察到不论是自然语言处理模型还是视觉模型,已经几乎从传统的CNN、RNN的网络结构设计全面转向基于Transformer的结构设计了。

如下图1所示,在ImageNet数据集上,基于Transformer的网络的精度几乎已经全面领先基于纯CNN设计的网络了。

图片图1

另外,最近大火的chatGPT,其核心也是基于Transformer构建的。这不禁重新燃起了我来做一期对于基础Transformer解析类文章的兴趣,便于新手入门老手复习

关于对Transformer的解析,网上已经有很多不错的相关教程了,这里推荐几个笔者认为写的不错的。

  1. 知乎文章《详解Transformer (Attention Is All You Need)》,点赞7000+
  2. 知乎文章《Transformer模型详解(图解最完整版)》,点赞4000+
  3. 非常有名的英文教程《The Illustrated Transformer》,知乎文章(1)就是借鉴该教程的
  4. B站李宏毅视频《第5讲-Transformer-1_哔哩哔哩_bilibili》
  5. 本文

与现有文章不同的是,本文我们打算结合相关的代码,从理论结合实践的角度加深读者对Transformer的理解。本文采用的代码是:

https://github.com/hyunwoongko/transformer

2 提出动机

Transformer提出的具体动机在现有文章中已经被阐明的很清楚了,总结而言就是:

(1)递归神经网络(RNN)或卷积神经网络(CNN)来处理序列数据时,在长序列上的表现不佳,因为它们难以捕捉长期依赖关系

(2)递归神经网络(RNN),包括LSTM/GRU及其变体,只能从左向右依次计算或者从右向左依次计算,也就是当前时刻的计算依赖上一步计算的输入,严重限制了模型的并行能力

所以,Transformer另辟蹊径,仅用自注意力(self-Attenion)和前馈网络/全连接层(Feed Forward Neural Network)就作为其主体框架,便获得对输入数据(文本、图像)较好的并行处理能力。另外,自注意力的使用使得模型对较长序列输入的处理能力也大大提升。

3 方法与代码详解

3.1 整体模型预览

本文我们以自顶而下的步骤来逐步解析Transformer中各个模块。为了更好地说明,这里我们借鉴了《The Illustrated Transformer》的原理图,没办法,这位老哥写的太好了,这里强烈建议大家去看一下原版。

假设我们要做一个语言翻译任务,如下图2所示,那么需要构建一个翻译模型,这里不妨这个模型就是基于Transformer构建的。该模型输入一段原始语言(看图片中可能是印度文),输出英文翻译。

图片图2

具体地,该基于Transformer构建的翻译模型由两部分构成,分别是编码器(encoder)解码器(decoder),如下图3所示。

图片图3

编码器的输出作为解码器的输入。这里的编码器是将输入映射为特征表征(可视为待翻译句子的含义),而解码器根据特征表征实现**“含义—>目标语言”**的转换。具体代码实现如下:

class Transformer(nn.Module):

    def __init__(self, src_pad_idx, trg_pad_idx, trg_sos_idx, enc_voc_size, dec_voc_size, d_model, n_head, max_len,        
                 ffn_hidden, n_layers, drop_prob, device):        
        super().__init__()        
        self.src_pad_idx = src_pad_idx        
        self.trg_pad_idx = trg_pad_idx        
        self.trg_sos_idx = trg_sos_idx        
        self.device = device        
        self.encoder = Encoder(d_model=d_model,
                               n_head=n_head,                               
                               max_len=max_len,                               
                               ffn_hidden=ffn_hidden,                               
                               enc_voc_size=enc_voc_size,                               
                               drop_prob=drop_prob,                               
                               n_layers=n_layers,                               
                               device=device)        
        self.decoder = Decoder(d_model=d_model, 
                               n_head=n_head,                               
                               max_len=max_len,                               
                               ffn_hidden=ffn_hidden,                               
                               dec_voc_size=dec_voc_size,                               
                               drop_prob=drop_prob,                               
                               n_layers=n_layers,                               
                               device=device)    
    def forward(self, src, trg):    
        src_mask = self.make_pad_mask(src, src, self.src_pad_idx, self.src_pad_idx)     
           
        src_trg_mask = self.make_pad_mask(trg, src, self.trg_pad_idx, self.src_pad_idx)   
             
        trg_mask = self.make_pad_mask(trg, trg, self.trg_pad_idx, self.trg_pad_idx) * \  
                   self.make_no_peak_mask(trg, trg)        
        enc_src = self.encoder(src, src_mask) # 编码器        
        output = self.decoder(trg, enc_src, trg_mask, src_trg_mask) # 解码器        
        return output

从代码的forward函数可以看出,作者先对模型的输入(src,trg)进行了预处理,获取到一些诸如掩码等信息。接着,将这些先喂给编码器(self.encoder)来获取特征表征(enc_src),该特征表征和一些掩码被馈入解码器(self.decoder)中,输出张量翻译信息(output)。

图片图4

如上图4所示,这个编码器解码器的结构可以总结如下:

(1)编码器

编码器由一个嵌入层+六个编码层构成,每个编码层由一个自注意力(self-Attenion)和一层前馈网络(Feed Forward Neural Network)组成,如下图5所示。需要注意的是,自注意力前馈网络中均包含残差连接(目的是为了防止梯度消失,提高训练稳定性);

图片图5

编码器的代码实现如下所示:

class Encoder(nn.Module):
    def __init__(self, enc_voc_size, max_len, d_model, ffn_hidden, n_head, n_layers, drop_prob, device):    
        super().__init__()        
        self.emb = TransformerEmbedding(d_model=d_model,                                        
                                        max_len=max_len,                                        
                                        vocab_size=enc_voc_size,                                        
                                        drop_prob=drop_prob,                                        
                                        device=device)        
                                        
        self.layers = nn.ModuleList([EncoderLayer(d_model=d_model,                                                  
                                                  ffn_hidden=ffn_hidden,                                                  
                                                  n_head=n_head,                                                  
                                                  drop_prob=drop_prob)                                     
                                     for _ in range(n_layers)])    
                                     
    def forward(self, x, s_mask):     
        x = self.emb(x)        
        
        for layer in self.layers:        
            x = layer(x, s_mask)     
               
        return x

上述代码中的self.emb就是嵌入层,n_layers被设定为6,表示该编码器由6层解码层(EncoderLayer)构成。我们再进入EncoderLayer的定义,可见:

class EncoderLayer(nn.Module):
    def __init__(self, d_model, ffn_hidden, n_head, drop_prob):      
        super(EncoderLayer, self).__init__()        
        self.attention = MultiHeadAttention(d_model=d_model, n_head=n_head)        
        self.norm1 = LayerNorm(d_model=d_model)        
        self.dropout1 = nn.Dropout(p=drop_prob)        
        
        self.ffn = PositionwiseFeedForward(d_model=d_model, hidden=ffn_hidden, drop_prob=drop_prob)        
        self.norm2 = LayerNorm(d_model=d_model)        
        self.dropout2 = nn.Dropout(p=drop_prob)    
        
    def forward(self, x, s_mask):    
        # 1. compute self attention        
        _x = x        
        x = self.attention(q=x, k=x, v=x, mask=s_mask)    
                    
        # 2. add and norm        
        x = self.dropout1(x)        
        x = self.norm1(x + _x)        
                
        # 3. positionwise feed forward network        
        _x = x        
        x = self.ffn(x)             
         
        # 4. add and norm        
        x = self.dropout2(x)        
        x = self.norm2(x + _x)        
        return x

通过查看上述代码的forward函数,可见在每个编码层中执行了如下操作:

  • (1)自注意力操作( self.attention)
  • (2)相加和归一化(也就是残差连接)
  • (3)前馈网络运算(self.ffn,本质就是全连接层)
  • (4)相加和归一化(残差连接)
(2)解码器

解码器由一个嵌入层+六个解码层+一个输出层构成,每个解码层由一个自注意力(self-Attenion),一个编码-解码注意力(Encoder- decoder Attention)和一层前馈网络(Feed Forward Neural Network)组成,如下图6所示;

图片图6

解码器的代码实现如下所示:

class Decoder(nn.Module):
    def __init__(self, dec_voc_size, max_len, d_model, ffn_hidden, n_head, n_layers, drop_prob, device):    
        super().__init__()        
        self.emb = TransformerEmbedding(d_model=d_model,                                        
                                        drop_prob=drop_prob,                                        
                                        max_len=max_len,                                        
                                        vocab_size=dec_voc_size,                                        
                                        device=device)        
                                        
        self.layers = nn.ModuleList([DecoderLayer(d_model=d_model,
                                                  ffn_hidden=ffn_hidden,                                                  
                                                  n_head=n_head,                                                  
                                                  drop_prob=drop_prob)                                     
                                     for _ in range(n_layers)])        
                                     
        self.linear = nn.Linear(d_model, dec_voc_size)    
    def forward(self, trg, enc_src, trg_mask, src_mask):   
        trg = self.emb(trg)       
         
        for layer in self.layers:       
            trg = layer(trg, enc_src, trg_mask, src_mask)   
                 
        # pass to LM head        
        output = self.linear(trg)        
        return output

其中,self.emb为嵌入层;n_layers=6,这表明解码器由6个解码层(DecoderLayer)构成;最后,使用输出层(self.linear)将解码特征映射为字向量输出。这里,我们就解码层(DecoderLayer)展开,其具体实现代码如下:

class DecoderLayer(nn.Module):

    def __init__(self, d_model, ffn_hidden, n_head, drop_prob):        
        super(DecoderLayer, self).__init__()        
        self.self_attention = MultiHeadAttention(d_model=d_model, n_head=n_head)        self.norm1 = LayerNorm(d_model=d_model)        
        self.dropout1 = nn.Dropout(p=drop_prob)        
        self.enc_dec_attention = MultiHeadAttention(d_model=d_model, n_head=n_head) 
               
        self.norm2 = LayerNorm(d_model=d_model)        
        self.dropout2 = nn.Dropout(p=drop_prob)        
        self.ffn = PositionwiseFeedForward(d_model=d_model, hidden=ffn_hidden, drop_prob=drop_prob)        
        
        self.norm3 = LayerNorm(d_model=d_model)        
        self.dropout3 = nn.Dropout(p=drop_prob)    
        
    def forward(self, dec, enc, t_mask, s_mask):    
        # 1. compute self attention        
        _x = dec        
        x = self.self_attention(q=dec, k=dec, v=dec, mask=t_mask) 
                       
        # 2. add and norm        
        x = self.dropout1(x)        
        x = self.norm1(x + _x)      
          
        if enc is not None:          
            # 3. compute encoder - decoder attention            
            _x = x            
            x = self.enc_dec_attention(q=x, k=enc, v=enc, mask=s_mask)       
                             
            # 4. add and norm            
            x = self.dropout2(x)            
            x = self.norm2(x + _x)        
        # 5. positionwise feed forward network        
        _x = x        
        x = self.ffn(x)               
         
        # 6. add and norm        
        x = self.dropout3(x)        
        x = self.norm3(x + _x)        
        return x

从上述代码可以看出,每个解码层比编码层多了一个模块运算,即编码-解码注意力及其残差连接。

再仔细一看,这个编码-解码注意力的k和v值为enc,其实也就是编码器的输出。也就是说,每层解码层的输入不光有上一解码层的输出(dec),还有编码器的最终输出(enc),和图4中展示的一致。

写到这里,Transformer的大致结构大家应该就有了初步的印象了。这里需要提一嘴的是,编码-解码注意力(Encoder- decoder Attention)的本质也是个自注意力,其唯一区别仅在输入的来源上。而前馈网络(Feed Forward Neural Network)其实就是全连接层与激活函数的组合,并不需要进行重点解析。

那么这样一来,我们只需要理解自注意力的具体实现,也就掌握了Transformer的核心了。

3.2 自注意力

自注意力模块,笔者最先接触的时候是在做视觉任务遇到了一个叫做Non-local注意力模块,其结构如下图7所示(具体见我之前写的知乎文章)。事实上,该模块就是借鉴自注意力进行设计的。

图片图7

在Transformer中,每个单词向量(embeddings)被映射成三个不同的向量,分别叫做Query向量(Q)、Key向量(K)和Value向量(V),如下图8所示。具体是怎么个映射法呢,就是输入到三个不同的全连接层(内参分别为、和)中即可。

图片图8

在每个单词向量都获取了其对应的三个Q、K、V向量后。对于任意单词,用它的Query向量(Q)去探索自身和其他单词的Key向量(K),如下图9所示。具体探索方法就是两者进行点积,某种意义上就是求解余弦相似度,如果两者相似度较高,则赋予较大的值(下图中的Score)来反应两者的关系。

图片图9

上述计算可以大致把握两个单词之间的相似度(或者称为关联性,即图中的Score)。之后,需要利用该相似度来衡量各单词相对于所关注单词(上图9中受关注的单词为thinking)的重要性,进而对各个单词所占权重进行调整。

具体地,作者先是对每个计算的相似度进行了标准化,然后使用softmax函数将标准化的相似度转换成了注意力,如下图10所示。

图片图10

接着,如图11所示,作者利用获取的注意力来调整各单词Value向量(V)所占的权重。最后,对加权后的各单词Value向量(V)进行按位相加融合,获取自注意力模块的输出(就是图10中的)。

图片图11

为了便于大家理解,上面的讲解是拆分进行的。在实际计算过程中,采用了基于矩阵的计算方法来提升运算效率,具体如下图12所示。

假设有2个单词,每个单词维度为4(当然这只是假设),那么输入的维度就是。然后通过三个不同权重的全连接层(权重维度为)将输入映射为三个维度为的向量,即Query向量(Q)、Key向量(K)和Value向量(V)。

那么上述关于自注意力的运算步骤可以用下面公式表示,即

图片

更形象地,其图示为:

图片图12

其代码实现如下所示:

在这里插入图片描述
在这里插入图片描述

其中self.attention(自注意力)定义为:

在这里插入图片描述

需要注意的是,上面文字部分我们讲了那么多,其实是在讲解单头自注意力。然而,细心的小伙伴应该发现了上述代码定义的是多头自注意力。我们可以认为,当self.n_head=1时,就是上面我们讲解的单头自注意力了。当然,在Transformer中,作者默认采用的是多头自注意力,这是为什么呢?

因为,相比单头自注意力,多头的优势主要体现在:

  • 更好的建模能力:利用多个注意力头可以捕获序列中更多的信息和特征,从而提升模型的建模能力。
  • 更好的泛化能力:多头自注意力可以提高模型对新数据的泛化能力,因为它可以将不同方面的特征编码到不同的注意力头中,从而使模型更加全面地理解输入序列。那么多头是如何实现的呢?

如图13所示,假设self.n_head=8,即八头注意力,对于每个头(序号为)都执行一次单头注意力运算,获得相应的输出。最后,对所有头所处的进行堆叠融合后,利用全连接层将其映射成与单一注意力输出相同的尺寸即可。

图片图13

至此,关于Transformer的核心部分(即自注意力)就讲解完毕了。这里顺嘴提一下,从上述代码中可以看出,解码层中的编码-解码注意力(self.enc_dec_attention)的定义也是一个多头自注意力(MultiHeadAttention类),唯一区别在于其输入的Query向量(q)为前面解码器的输出(x),而Key向量(k)和Value向量(v)输入为编码器的输出(enc)。

下面我们就Transformer另外两个重要的创新进行解析,分别为:

  • 位置编码
  • 输入掩码
3.3 位置编码

从上面的解析可以看出,自注意力对输入序列中各单词的位置/顺序不敏感,因为通过Query向量Key向量的交互,每个单词的距离被缩小为一个常量。这样会导致一个很严重的问题,比如“我吃牛肉”这句话,在Transformer看来和“牛吃我肉”是没什么区别的。

为了缓解该问题,作者提出了位置编码。简而言之,就是在词向量输入到注意力模块之前,与该词向量等长的位置向量进行了按位相加,进而赋予了词向量相应的位置信息。

那么,如何获取该位置编码呢?作者给出了位置编码的定义公式,具体如下:

图片

乍一看,变量有三,公式还有两个,着实不太好懂。这里需要注意,为位置向量的维度,前面提到该维度与词向量维度一致。变量为单词在句子中的位置,比如“我吃牛肉”,那么这个牛的。那么这个又是什么东西呢?其实,这个和分别表示位置向量的偶数位置和奇数位置,自然而然地,。

作者这样定义位置编码的原因在于:

  • 使得Transformer可以更容易掌握单词间的相对位置,因为Sin(A+B)=Sin(A)Cos(B)+ Cos(A)Sin(B), Cos(A+B)= Cos(A)Cos(B)- Sin(A)Sin(B)。那么知道某单词的位置编码后(如),其相对距离为的单词的位置编码()可以根据函数和函数的性质快速计算出。
  • 即使在测试的时候遇到比训练集中所有句子都长的句子,上述公式也可以给出一个合适的位置编码。

位置代码获取的代码实现如下:

在这里插入图片描述
在这里插入图片描述

这里也提供一个图例加深读者理解,如图14所示。图中每一行对应于一个词向量的位置编码,可见该句子共有20个词(0-19),其中第一行是输入序列中第一个单词向量的位置编码。每一行包含512个值(即),每个值都是按照上述计算公式计算而来,介于1和 -1之间。左半部分的值由正弦函数计算获得,而右半部分的值由余弦函数计算获得。在实现中,将这两部分计算出来的值穿插拼接起来,就可以形成每个单词对应的位置编码了。

图片图14

至此,关于位置编码的解析就结束了,相信大家在仔细看完上述解释和代码后,应当能够理解位置编码的含义了。

3.4 输入掩码

输入掩码是笔者认为比较重要的一部分了。在深度学习中,掩码的作用一般是让模型自动忽略一些内容

正常的翻译逻辑应该是,模型在接收原始语言的语句输入后(利用编码器进行编码),会利用解码器一个词一个词地逐个翻译。也就是,时刻翻译的单词会考虑之前时刻(如,)已经翻译好的所有单词。在Transformer的测试阶段,这是这样执行的。

那么可能会有人会问,前面不是提到Transformer的一大亮点在于其并行能力吗?这不还是在等待前面翻译的内容作为后续的输入吗?

这里需要解释一下,这种情况只发生在测试阶段。在训练阶段,作者利用将监督信息(即目标翻译)右移一位作为解码器的输入(加上了一个起始符),并设置掩码使得解码器在时刻的翻译只能考虑输入目标翻译时刻之前(如,)的内容,这是为了防止翻译信息的提前泄露。如图15所示,我举了个例子,

图片图15

从上图15可以看到,翻译第个单词时,都会用到目标翻译第1个到第个单词作为输入。需要注意的是,这里的训练方式叫做Teacher Forcing,也就是不管你模型最后的输出是啥,你的输入都得是目标翻译的一部分,给你强行扭过来。当然,在测试/应用阶段,解码器前一时刻的输出将作为后面的输入,因为我们是无法在测试阶段提前获取目标翻译的。

为了实现这种“翻译第个单词时用到目标翻译第1个到第个单词作为输入”,作者引入了输入掩码,具体实现也很简单。这里借鉴《知乎文章:Transformer模型详解(图解最完整版)》的图来更好的说明,具体如下:

图片图16

假设输入到Transformer解码器的矩阵维度为(也就是包含在内有5个单词,每个单词视作6维度),那么在翻译第一个单词时,只会用到起始符,而翻译第二个单词时只会用到起始符和第一个单词,后续类推,那么设置的掩码矩阵应该如上图16右边的Mask矩阵所示。

该Mask矩阵用在哪儿呢?这里通过查看自注意力的代码实现,如下:

在这里插入图片描述

可以发现,Mask 操作是在自注意力模块计算Score后,以及使用 Softmax 之前使用的

4 总结

写到这里,关于Transformer比较重要的点基本都讲解完毕了。如果大家一路看过来,能够理解我的所言所语,我相信应该会有所收获的。本期内容还是侧重于对基础Transformer的讲解,未来我打算多去分享一些基于Transformer设计的网络模型以及实际应用。欢迎大家关注!

图片图17 Transformer总结构图

如何学习AI大模型?

作为一名热心肠的互联网老兵,我决定把宝贵的AI知识分享给大家。 至于能学习到多少就看你的学习毅力和能力了 。我已将重要的AI大模型资料包括AI大模型入门学习思维导图、精品AI大模型学习书籍手册、视频教程、实战学习等录播视频免费分享出来。

这份完整版的大模型 AI 学习资料已经上传CSDN,朋友们如果需要可以微信扫描下方CSDN官方认证二维码免费领取【保证100%免费

一、全套AGI大模型学习路线

AI大模型时代的学习之旅:从基础到前沿,掌握人工智能的核心技能!

img

二、640套AI大模型报告合集

这套包含640份报告的合集,涵盖了AI大模型的理论研究、技术实现、行业应用等多个方面。无论您是科研人员、工程师,还是对AI大模型感兴趣的爱好者,这套报告合集都将为您提供宝贵的信息和启示。

img

三、AI大模型经典PDF籍

随着人工智能技术的飞速发展,AI大模型已经成为了当今科技领域的一大热点。这些大型预训练模型,如GPT-3、BERT、XLNet等,以其强大的语言理解和生成能力,正在改变我们对人工智能的认识。 那以下这些PDF籍就是非常不错的学习资源。

img

四、AI大模型商业化落地方案

img

作为普通人,入局大模型时代需要持续学习和实践,不断提高自己的技能和认知水平,同时也需要有责任感和伦理意识,为人工智能的健康发展贡献力量。

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

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

相关文章

002-基于Sklearn的机器学习入门:回归分析(上)

本节及后续章节将介绍机器学习中的几种经典回归算法,所选方法都在Sklearn库中聚类模块有具体实现。本节为上篇,将介绍基础的线性回归方法,包括线性回归、逻辑回归、多项式回归和岭回归等。 2.1 回归分析概述 回归(Regression&…

Vue3学习(一)

创建组件实例:我们传入 createApp 的对象实际上是一个组件 import { createApp } from vue // 从一个单文件组件中导入根组件 import App from ./App.vueconst app createApp(App) 大多数真实的应用都是由一棵嵌套的、可重用的组件树组成的。 App (root compone…

AI大模型的崛起:第四次工业革命的前奏?

在当今这个信息爆炸的时代,人工智能(AI)大模型的崛起引起了广泛的关注和讨论。有人将其视为第四次工业革命的前奏,然而,这真的可能吗?本文将探讨这一问题,并对中国AI大模型的发展进行简要分析。…

Android:移动垃圾软件

讲解政策相关,最近升级AI扫荡系统和证书防高风险,回复按留言时间来排,请耐心等待 移动垃圾软件 官方政策公告行为透明、信息披露清晰保护用户数据不要损害移动体验软件准则反垃圾软件政策Google API 服务用户数据政策官方政策公告 ​ 在 Google,我们相信,如果我们关注用户…

DIY智能音箱:基于STM32的低成本解决方案 (附详细教程)

摘要: 本文详细介绍了基于STM32的智能音箱的设计与实现过程,包括硬件设计、软件架构、语音识别、音乐播放等关键技术。通过图文并茂的方式,结合Mermaid流程图和代码示例,帮助读者深入理解智能音箱的工作原理,并提供实际操作指导。…

[图解]分析模式高阶+课程讲解03物品模式

1 00:00:00,280 --> 00:00:03,440 下一个要探讨的模式是物品模式 2 00:00:04,310 --> 00:00:08,300 说是物品模式,实际上更多的说物品规格 3 00:00:09,210 --> 00:00:12,560 首先,我们要区分一下物品和物品规格的定义 4 00:00:14,440 -->…

【C++】C++ 网店销售库存管理系统(源码+论文)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…

抖音直播自动点赞脚本:让点赞变得简单

抖音直播自动点赞脚本:让点赞变得简单 简介 点赞是社交媒体上表达喜爱的一种方式,尤其在抖音这样的平台上,点赞不仅能够增加主播的人气,还能鼓励他们创作更多优质内容。然而,手动点赞往往既耗时又费力。为了解决这个…

算法与数据结构面试宝典——常见的数据结构都有哪些?详细示例(C#,C++)

文章目录 一、逻辑结构:线性与非线性线性数据结构非线性数据结构访问方式 二、数组(Array)三、链表(LinkedList)四、栈(Stack)五、队列(Queue)六、树(Tree&am…

Android高级面试_6_性能优化

Android 高级面试-7:网络相关的三方库和网络协议等 1、网络框架 问题:HttpUrlConnection, HttpClient, Volley 和 OkHttp 的区别? HttpUrlConnection 的基本使用方式如下: URL url new URL("http://www.baidu.com")…

pytest测试框架pytest-random-order插件随机执行用例顺序

Pytest提供了丰富的插件来扩展其功能,本章介绍下pytest-random-order插件,随机设置pytest测试用例的运行顺序,并对随机性进行一些控制。 官方文档: https://pytest-cov.readthedocs.io/en/latest/index.html 适配版本说明&#x…

AI智能客服项目拆解(1) 产品大纲

本文作为拆解AI智能客服项目的首篇,以介绍产品大纲为主。后续以某AI智能客服产品为例,拆解相关技术细节。 AI智能客服是一种基于人工智能技术的客户服务解决方案,旨在提高客户满意度和优化企业运营。利用人工智能和自然语言处理技术&#xff…

如何为数据库中的位图添加动态水印

许多数据库存储了以blob或文件形式保存的位图,其中包括照片、文档扫描、医学图像等。当这些位图被各种数据库客户端和应用程序检索时,为了日后的识别和追踪,有时需要在检索时为它们添加唯一的水印。在某些情况下,人们甚至希望这些…

数字图像处理之【高斯金字塔】与【拉普拉斯金字塔】

数字图像处理之【高斯金字塔】与【拉普拉斯金字塔】 1.1 什么是高斯金字塔? 高斯金字塔(Gaussian Pyramid)是一种多分辨率图像表示方法,用于图像处理和计算机视觉领域。它通过对原始图像进行一系列的高斯平滑和下采样操作&#x…

istitle()方法——判断首字母是否大写其他字母小写

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 语法参考 istitle()方法用于判断字符串中所有的单词首字母是否为大写而其他字母为小写。istitle()方法的语法格式如下: str.istitle() …

Java并发编程基础知识点

目录 Java并发编程基础知识点1、线程,进程概念及二者的关系进程相关概念线程相关概念进程与线程的关系补充小知识点: 2、线程的状态Java线程的状态:Java线程不同状态之间的切换图示 3、Java程序中如何创建线程?①、继承Thread类②…

【python】python知名品牌调查问卷数据分析可视化(源码+调查数据表)【独一无二】

👉博__主👈:米码收割机 👉技__能👈:C/Python语言 👉公众号👈:测试开发自动化【获取源码商业合作】 👉荣__誉👈:阿里云博客专家博主、5…

某度,网盘免费加速,复活!

哈喽,各位小伙伴们好,我是给大家带来各类黑科技与前沿资讯的小武。 有小伙伴反馈之前如下夸克网盘脚本的加速方法失效,小武今天测试,依旧正常使用! 百度/迅雷/夸克,网盘免费加速,已破&#xf…

Vite: 高阶特性 Pure ESM

概述 ESM 已经逐步得到各大浏览器厂商以及 Node.js 的原生支持,正在成为主流前端模块化方案。 而 Vite 本身就是借助浏览器原生的 ESM 解析能力( type“module” )实现了开发阶段的 no-bundle ,即不用打包也可以构建 Web 应用。不过我们对于原生 ESM 的…

线性表与顺序存储结构(下)

前言 接上文(线性表与顺序存储结构(上))。 这些顺序存储结构的方法在顺序表上下卷中已经提到过,但是有些许不同,可以为理解顺序表提供更丰富的视角。(不过最主要的区别在于顺序表上下卷中的顺…