口语理解任务源码详解系列(三)利用BiRNN-attention模型实现

news2024/12/26 14:08:59

利用RNN-attention模型实现

写在前面
在前文介绍了项目的数据集构建:传送门,以及利用seq2seq-attention模型实现意图分类与槽位填充任务:传送门
本文利用BiRNN-attention实现:实现细节请参考论文:《Attention-Based Recurrent Neural Network Models for Joint Intent Detection and Slot Filling》
1.利用encoder中的最后一个time step中的双向隐层,再加上平均池化或者利用attention加权平均,最后接一个fc层进行意图分类;
2.槽位填充是序列标注,双向隐藏状态加attention权重,再经过一个单元GRUcell,最后接一个fc层分类。这里注意一点是,槽的每一步的预测输出会输入到GRUcell中的前向传播时间步中;
3.总的loss = 意图分类loss + 槽位填充loss

一、模型结构图

在这里插入图片描述

二、构建attention权重计算方式

class Attention(nn.Module):
    def __init__(self, hidden_dim):
        super(Attention, self).__init__()
        self.attn = nn.Linear((hidden_dim * 2), hidden_dim)
        self.v = nn.Linear(hidden_dim, 1, bias=False)

    def concat_score(self, hidden, encoder_output):
        seq_len = encoder_output.shape[1]
        hidden = hidden.unsqueeze(1).repeat(1, seq_len, 1) # [batch_size, seq_len, hidden_size]
        energy = torch.tanh(self.attn(torch.cat((hidden, encoder_output),dim=2))) # [batch_size, seq_len, hidden_dim]
        attention = self.v(energy).squeeze(2) #[batch_size, seq_len]
        return attention #[batch_size, seq_len]

    def forward(self, hidden, encoder_output):
        # hidden = [batch_size, hidden_size]
        # #encoder_output=[batch_size, seq_len, hidden_size]
        
        attn_energies = self.concat_score(hidden, encoder_output)

        return F.softmax(attn_energies, dim=1).unsqueeze(1)
  1. 初始化方法(init):接收一个隐藏维度 hidden_dim 作为输入参数。在初始化过程中,通过调用父类 nn.Module 的构造函数进行初始化操作,然后创建了两个线性层 (nn.Linear),即 self.attn 和 self.v;
  2. self.attn 是一个线性层,将输入的维度设为 (hidden_dim * 2),输出的维度设为 hidden_dim;
  3. self.v 是另一个线性层,将输入的维度设为 hidden_dim,输出的维度设为 1。此处设置了 bias=False,表示不使用偏置项;
  4. concat_score 方法:接收隐藏状态 hidden 和编码器输出 encoder_output 作为输入,计算注意力分数(attention scores);
  5. 首先,在 seq_len 维度上将 hidden 进行复制并展开,使其与 encoder_output 的形状相匹配(使用 unsqueeze 和 repeat 操作)得到的张量形状为 [batch_size, seq_len, hidden_dim];
  6. 将复制后的 hidden 和 encoder_output 进行拼接(使用 torch.cat),然后通过线性层 self.attn 将其映射到维度为 hidden_dim 的张量;
  7. 对映射后的张量应用 tanh 函数,得到energy张量,形状为 [batch_size, seq_len, hidden_dim];
  8. 将张量输入到线性层 self.v 中,将其映射为形状为 [batch_size, seq_len, 1] 的张量,并去掉最后一个维度,得到注意力分数张量,形状为 [batch_size, seq_len];
  9. forward 方法:前向传播函数,接收隐藏状态 hidden 和编码器输出 encoder_output 作为输入;
  10. 调用 concat_score 方法,计算注意力分数;
  11. 对注意力分数进行 softmax 归一化处理,以便获得注意力权重,即每个位置的重要程度。使用 PyTorch 中的 F.softmax 函数,在 dim=1 维度上进行归一化操作,保持维度不变。最后,在第一个维度上增加一个维度(使用 unsqueeze 操作),得到形状为 [batch_size, 1, seq_len] 的张量。

三、构建模型

class BirnnAttention(nn.Module):
    def __init__(self, source_input_dim, source_emb_dim, hidden_dim, n_layers, dropout, pad_index, slot_output_size, intent_output_size, slot_embed_dim, predict_flag):
        super(BirnnAttention, self).__init__()
        self.pad_index = pad_index
        self.hidden_dim = hidden_dim//2 # 双向lstm
        self.n_layers = n_layers
        self.slot_output_size = slot_output_size
        # 是否预测模式
        self.predict_flag = predict_flag
        
        self.source_embedding = nn.Embedding(source_input_dim, source_emb_dim, padding_idx=pad_index)
        # 双向gru,隐层维度是hidden_dim
        self.source_gru = nn.GRU(source_emb_dim, self.hidden_dim, n_layers, dropout=dropout, bidirectional=True, batch_first=True) #使用双向  
        # 单个cell的隐层维度与gru隐层维度一样,为hidden_dim
        self.gru_cell = nn.GRUCell(slot_embed_dim + (2 * hidden_dim), hidden_dim)
        self.attention = Attention(hidden_dim)
        # 意图intent预测
        self.intent_output = nn.Linear(hidden_dim * 2, intent_output_size)
        # 槽slot预测
        self.slot_output = nn.Linear(hidden_dim, slot_output_size)
        self.slot_embedding = nn.Embedding(slot_output_size, slot_embed_dim)
    def forward(self, source_input, source_len):
        '''
        source_input:[batch_size, seq_len]
        source_len:[batch_size]
        '''
        if self.predict_flag:
            assert len(source_input) == 1, '预测时一次输入一句话'
            seq_len = source_len[0]

            # 1.Encoder阶段,将输入的source进行编码
            # source_embedded:[batch_size, seq_len, source_emb_dim]
            source_embedded = self.source_embedding(source_input)
            packed = torch.nn.utils.rnn.pack_padded_sequence(source_embedded, source_len, batch_first=True,
                                                             enforce_sorted=True)  # 这里enfore_sotred=True要求数据根据词数排序
            source_output, hidden = self.source_gru(packed)
            # source_output=[batch_size, seq_len, 2 * self.hidden_size],这里的2*self.hidden_size = hidden_dim
            # hidden=[n_layers * 2, batch_size, self.hidden_size]
            source_output, _ = torch.nn.utils.rnn.pad_packed_sequence(source_output, batch_first=True,
                                                                      padding_value=self.pad_index, total_length=len(
                    source_input[0]))  # 这个会返回output以及压缩后的legnths
            '''
            source_hidden[-2,:,:]是gru最后一步的forward
            source_hidden[-1,:,:]是gru最后一步的backward
            '''
            # source_hidden=[batch_size, 2*self.hidden_size]
            source_hidden = torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1)
            # 保存注意力向量
            attention_context = torch.zeros(1, seq_len, self.hidden_dim * 2)
            output_tokens = []

            aligns = source_output.transpose(0, 1)  # 对齐向量

            input = torch.tensor(2).unsqueeze(0)  # 预测阶段解码器输入第一个token-> <sos>
            for s in range(seq_len):
                aligned = aligns[s].unsqueeze(1)  # [batch_size, 1, hidden_size*2]
                # embedded=[1, 1, slot_embed_dim]
                slot_embedded = self.slot_embedding(input)
                slot_embedded = slot_embedded.unsqueeze(0)
                # 利用利用上一步的hidden与encoder_output,计算attention权重
                # attention_weights=[batch_size, 1, seq_len]
                attention_weights = self.attention(source_hidden, source_output)

                '''
                以下是计算上下文:利用attention权重与encoder_output计算attention上下文向量
                注意力权重分布用于产生编码器隐藏状态的加权和,加权平均的过程。得到的向量称为上下文向量
                '''
                context = attention_weights.bmm(source_output)
                attention_context[:, s, :] = context

                combined_grucell_input = torch.cat([aligned, slot_embedded, context], dim=2)

                source_hidden = self.gru_cell(combined_grucell_input.squeeze(1), source_hidden)

                slot_prediction = self.slot_output(source_hidden)

                input = slot_prediction.argmax(1)
                output_token = input.squeeze().detach().item()

                output_tokens.append(output_token)

            # 意图识别
            # 拼接注意力向量和encoder的输出
            combined_attention_sourceoutput = torch.cat([attention_context, source_output], dim=2)
            intent_outputs = self.intent_output(torch.mean(combined_attention_sourceoutput, dim=1))
            intent_outputs = intent_outputs.squeeze()
            intent_outputs = intent_outputs.argmax()
            return output_tokens, intent_outputs

        else:
            # 1.Encoder阶段,将输入的source进行编码
            # source_embedded:[batch_size, seq_len, source_emb_dim]
            source_embedded = self.source_embedding(source_input)
            packed = torch.nn.utils.rnn.pack_padded_sequence(source_embedded, source_len, batch_first=True,
                                                             enforce_sorted=True)  # 这里enfore_sotred=True要求数据根据词数排序
            source_output, hidden = self.source_gru(packed)
            # source_output=[batch_size, seq_len, 2 * self.hidden_size],这里的2*self.hidden_size = hidden_dim
            # hidden=[n_layers * 2, batch_size, self.hidden_size]
            source_output, _ = torch.nn.utils.rnn.pad_packed_sequence(source_output, batch_first=True,
                                                                      padding_value=self.pad_index, total_length=len(
                    source_input[0]))  # 这个会返回output以及压缩后的legnths
            '''
            source_hidden[-2,:,:]是gru最后一步的forward
            source_hidden[-1,:,:]是gru最后一步的backward
            '''
            # source_hidden=[batch_size, 2*self.hidden_size]
            source_hidden = torch.cat((hidden[-2, :, :], hidden[-1, :, :]), dim=1)

            # 2.Decoder阶段,预测slot与intent
            batch_size = source_input.shape[0]
            seq_len = source_input.shape[1]
            # 保存slot的预测概率
            slot_outputs = torch.zeros(batch_size, seq_len, self.slot_output_size).to(device)

            # 保存注意力向量
            attention_context = torch.zeros(batch_size, seq_len, self.hidden_dim * 2).to(device)

            # 每个batch数据的第一个字符<sos>对应的是index是2
            input = torch.tensor(2).repeat(batch_size).to(device)
            aligns = source_output.transpose(0, 1)  # 利用encoder output最后一层的每一个时间步
            # 槽识别
            for t in range(1, seq_len):
                '''
                解码器输入的初始hidden为encoder的最后一步的hidden
                接收输出即predictions和新的hidden状态
                '''
                aligned = aligns[t].unsqueeze(1)  # [batch_size, 1, hidden_size] # hidden_size包含前向和后向隐状态向量
                input = input.unsqueeze(1)
                # input=[batch_size, 1]
                # hidden=[batch_size, hidden_size] 初始化为encoder的最后一层 [batch_size, hidden_size]
                # encoder_output=[batch_size, seq_len, hidden_dim*2]
                # aligned=[batch_size, 1, hidden_dim*2]

                # embedded=[batch_sze, 1, slot_embed_dim]
                slot_embedded = self.slot_embedding(input)

                # 利用利用上一步的hidden与encoder_output,计算attention权重
                # attention_weights=[batch_size, 1, seq_len]
                attention_weights = self.attention(source_hidden, source_output)

                '''
                以下是计算上下文:利用attention权重与encoder_output计算attention上下文向量
                注意力权重分布用于产生编码器隐藏状态的加权和,加权平均的过程。得到的向量称为上下文向量
                '''
                context = attention_weights.bmm(
                    source_output)  # [batch_size, 1, seq_len] * [batch_size, seq_len, hidden_dim]=[batch_size, 1, hidden_dim]
                attention_context[:, t, :] = context.squeeze(1)
                # combined_grucell_input=[batch_size, 1, (hidden_size + slot_embed_dim + hidden_dim)]
                combined_grucell_input = torch.cat([aligned, slot_embedded, context], dim=2)
                # [batch_size, hidden_dim]
                source_hidden = self.gru_cell(combined_grucell_input.squeeze(1), source_hidden)
                # 预测slot, [batch_size, slot_output_size]
                slot_prediction = self.slot_output(source_hidden)
                slot_outputs[:, t, :] = slot_prediction
                # 获取预测的最大概率的token
                input = slot_prediction.argmax(1)
            # 意图识别,不同于原论文。这里拼接了slot所有时间步attention_context与句子编码后的输出,通过均值后作为意图识别的输入
            # 拼接注意力向量和encoder的输出,[batch_size, seq_len, hidden_dim * 2]
            combined_attention_sourceoutput = torch.cat([attention_context, source_output], dim=2)
            intent_outputs = self.intent_output(torch.mean(combined_attention_sourceoutput, dim=1))

            return slot_outputs, intent_outputs

构建模型,优化函数,损失函数,学习率衰减函数

def build_model(source, target, label, source_emb_dim, hidden_dim, n_layers, dropout, slot_embed_dim, lr, gamma,
                weight_decay):
    '''
    训练seq2seq model
    input与output的维度是字典的大小。
    encoder与decoder的embedding与dropout可以不同
    网络的层数与hiden/cell状态的size必须相同
    '''
    input_dim = len(source.vocab)  # source 词典大小(即词数量)
    output_dim = len(target.vocab)  # target 词典大小(即实体类型数量)
    label_dim = len(label.vocab)  # label 词典大小(即意图类别数量)

    model = BirnnAttention(input_dim, source_emb_dim, hidden_dim, n_layers, dropout,
                           source.vocab.stoi[source.pad_token], output_dim, label_dim, slot_embed_dim, False).to(device)

    model.apply(init_weights)

    # 定义优化函数
    optimizer = optim.Adam(model.parameters(), lr=lr)  # , weight_decay=weight_decay)
    # optimizer = torch.optim.SGD(model.parameters(), lr=lr) #, momentum=0.9, nesterov=True)
    # 定义lr衰减
    # scheduler = lr_scheduler.ExponentialLR(optimizer, gamma=gamma)

    '''
        当网络的评价指标不在提升的时候,可以通过降低网络的学习率来提高网络性能:
        optimer指的是网络的优化器
        mode (str) ,可选择‘min’或者‘max’,min表示当监控量停止下降的时候,学习率将减小,max表示当监控量停止上升的时候,学习率将减小。默认值为‘min’
        factor 学习率每次降低多少,new_lr = old_lr * factor
        patience=10,容忍网路的性能不提升的次数,高于这个次数就降低学习率
        verbose(bool) - 如果为True,则为每次更新向stdout输出一条消息。 默认值:False
        threshold(float) - 测量新最佳值的阈值,仅关注重大变化。 默认值:1e-4
        cooldown(int): 冷却时间“,当调整学习率之后,让学习率调整策略冷静一下,让模型再训练一段时间,再重启监测模式。
        min_lr(float or list):学习率下限,可为 float,或者 list,当有多个参数组时,可用 list 进行设置。
        eps(float):学习率衰减的最小值,当学习率变化小于 eps 时,则不调整学习率。
    '''
    scheduler = lr_scheduler.ReduceLROnPlateau(optimizer=optimizer, mode='min', factor=0.1, patience=2, verbose=False)
    # 这里忽略<pad>的损失。
    target_pad_index = target.vocab.stoi[source.pad_token]
    # 定义损失函数(实体识别)
    loss_slot = nn.CrossEntropyLoss(ignore_index=target_pad_index)
    # 定义损失函数(意图识别)
    loss_intent = nn.CrossEntropyLoss()

    return model, optimizer, scheduler, loss_slot, loss_intent

训练

def train(model, iterator, optimizer, loss_slot, loss_intent, clip):
    '''
    开始训练:
        1.得到source与target句子
        2.上一批batch的计算梯度归0
        3.给模型喂source与target,并得到输出output
        4.由于损失函数只适用于带有1维target和2维的input,我们需要用view进行flatten(在计算损失时,从output与target中忽略了第一列<sos>)
        5.反向传播计算梯度loss.backward()
        6.梯度裁剪,防止梯度爆炸
        7.更新模型参数
        8.损失值求和(返回所有batch的损失的均值)
    '''
    model.train()
    epoch_loss = 0

    for i, batch in enumerate(iterator):
        src, src_lens = batch.source  # src=[batch_size, seq_len],这里batch.src返回src和src的长度,因为在使用torchtext.Field时设置include_lengths=True
        trg, _ = batch.target  # trg=[batch_size, seq_len]
        label = batch.intent  # [batch_size]
        src = src.to(device)
        trg = trg.to(device)
        label = label.to(device)

        # slot_outputs=[batch_size, trg_len, trg_vocab_size], intetn_outputs=[batch_size, intent_size]
        slot_outputs, intent_outputs = model(src, src_lens)

        # 以下在计算损失时,忽略了每个tensor的第一个元素及<sos>
        output_dim = slot_outputs.shape[-1]
        slot_outputs = slot_outputs[:, 1:, :].reshape(-1, output_dim)  # output=[batch_size * (seq_len - 1), output_dim]
        trg = trg[:, 1:].reshape(-1)  # trg=[batch_size * (seq_len - 1)]
        loss1 = loss_slot(slot_outputs, trg)
        loss2 = loss_intent(intent_outputs, label)
        loss = loss1 + loss2

        with amp.scale_loss(loss, optimizer) as scaled_loss:
            scaled_loss.backward()
        torch.nn.utils.clip_grad_norm_(model.parameters(), clip)
        optimizer.step()
        optimizer.zero_grad()
        epoch_loss += float(loss.item())
        # print('epoch_loss:{}'.format(float(loss.item())))
    return epoch_loss / len(iterator)
def train_model(model, train_iterator, val_iterator, optimizer, scheduler, loss_slot, loss_intent, n_epochs, clip,
                model_path, writer):
    '''
    开始训练我们的模型:
    1.每一次epoch,都会检查模型是否达到的最佳的validation loss,如果达到了,就更新
    最好的validation loss以及保存模型参数
    2.打印每个epoch的loss以及困惑度。
    '''
    best_valid_loss = float('inf')
    for epoch in range(n_epochs):
        start_time = time.time()
        train_loss = train(model, train_iterator, optimizer, loss_slot, loss_intent, clip)
        writer.add_scalar('loss', train_loss, global_step=epoch + 1)

        valid_loss = evaluate(model, val_iterator, loss_slot, loss_intent)
        end_time = time.time()
        epoch_mins, epoch_secs = epoch_time(start_time, end_time)

        if valid_loss < best_valid_loss:
            best_valid_loss = valid_loss
            torch.save(model.state_dict(), model_path)
        scheduler.step(valid_loss)
        print('epoch:{},time-mins:{},time-secs:{}'.format(epoch + 1, epoch_mins, epoch_secs))
        print('train loss:{},train perplexity:{}'.format(train_loss, math.exp(train_loss)))
        print('val loss:{}, val perplexity:{}'.format(valid_loss, math.exp(valid_loss)))
    writer.flush()
    writer.close()

    # 每个epoch所花时间


def epoch_time(start_time, end_time):
    run_tim = end_time - start_time
    run_mins = int(run_tim / 60)
    run_secs = int(run_tim - (run_mins * 60))
    return run_mins, run_secs


# 对所有模块和子模块进行权重初始化
def init_weights(model):
    for name, param in model.named_parameters():
        nn.init.uniform_(param.data, -0.08, 0.08)

评估

def evaluate(model, iterator, loss_slot, loss_intent):
    model.eval()  # 评估模型,切断dropout与batchnorm
    epoch_loss = 0
    with torch.no_grad():  # 不更新梯度
        for i, batch in enumerate(iterator):
            src, src_len = batch.source  # src=[batch_size, seq_len]
            trg, _ = batch.target  # trg=[batch_size, seq_len]
            label = batch.intent
            src = src.to(device)
            trg = trg.to(device)
            label = label.to(device)
            # output=[batch_size, seq_len, output_dim]
            slot_outputs, intent_outputs = model(src, src_len)

            output_dim = slot_outputs.shape[-1]
            slot_outputs = slot_outputs[:, 1:, :].reshape(-1,
                                                          output_dim)  # output=[batch_size * (seq_len - 1), output_dim]
            trg = trg[:, 1:].reshape(-1)  # trg=[batch_size * (seq_len - 1)]
            loss1 = loss_slot(slot_outputs, trg)
            loss2 = loss_intent(intent_outputs, label)
            loss = loss1 + loss2
            epoch_loss += float(loss.item())
    return epoch_loss / len(iterator)

定义模型参数

from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter(os.getcwd()+'/log', comment='intent_slot')

source_emb_dim = 64
slot_embed_dim = 64
hidden_dim = 128
n_layers = 1
dropout = 0.5
lr = 0.01
gamma = 0.1
weight_decay = 0.1
n_epochs = 50
clip = 5.0
model_path = os.path.join(os.getcwd(), "model.h5")
model, optimizer, scheduler, loss_slot, loss_intent = build_model(SOURCE,
                                                                  TARGET,
                                                                  LABEL,
                                                                  source_emb_dim,
                                                                  hidden_dim,
                                                                  n_layers,
                                                                  dropout,
                                                                  slot_embed_dim,
                                                                  lr,
                                                                  gamma,
                                                                  weight_decay)

model, optimizer = amp.initialize(model, optimizer, opt_level='O1') 混合精度训练技术,将模型和优化器与AMP(Automatic Mixed Precision)库相结合。传入模型model,优化器optimizer,并指定了混合精度的级别01。

混合精度训练是一种加速神经网络训练的技术,它通过同时使用低精度(如半精度浮点数)和高精度(如单精度浮点数)来进行计算,从而在保持模型准确性的同时提高训练速度。这种技术利用了低精度计算的优势,减少了存储和计算的开销。

在混合精度训练中,模型的参数更新步骤通常使用高精度计算,而前向传播、反向传播和梯度计算可以使用低精度计算。这样可以提高训练速度,并且在大多数情况下不会对模型的准确性产生显著影响。

使用AMP库的initialize()函数可以自动配置模型和优化器,使其适用于混合精度训练。

train_model(model,
            train_iter,
            val_iter,
            optimizer,
            scheduler,
            loss_slot,
            loss_intent,
            n_epochs,
            clip,
            model_path,
            writer)

训练结果

在这里插入图片描述

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

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

相关文章

一键搞定发布自己Jar到Maven中央仓库

做java 开发那当然离不开jar包管理, 不知何时一直想想封装一个自己的jar包 然后发布到maven中央仓库给别人使用。 hhh 我感觉自己写一个jar包工具然后&#xff0c;被很多人使用是一件很牛&#xff0c;很快乐事情。 终于有了这个机会&#xff0c;和时间。SpringBoot stater出来了…

Steam搬砖项目介绍

Steam搬砖项目&#xff1a;轻松赚取稳定收入的副业选择 对于许多数字游戏玩家来说&#xff0c;Steam平台并不陌生。今天&#xff0c;我将向您介绍一个稳定的副业选择——Steam搬砖项目。通通过简单的操作&#xff0c;您可以轻松获得几十上百元的利润。 介绍 Steam搬运砖项目…

css 伪元素和浮动

展示为行内元素 inline-block <style>div {/* 浏览器解析行内块或行内标签的时候, 如果标签换行书写会产生一个空格的距离, 展示为行内元素 */display: inline-block;width: 100px;height: 100px;}.one {background-color: pink;}.two {background-color: skyblue;}</…

基于OpenCV 实现车牌号码识别--附免费源码

在本教程中,您将学习如何使用 OpenCV 和 EasyOCR 包自动执行车牌/车牌识别 (LPR/NPR)。 EasyOCR是一个开源 Python 包,用于执行光学字符识别 - OCR(从图像中提取文本)。 该软件包非常易于使用,在撰写本文时,它支持 80 多种语言,包括中文、阿拉伯语、法语、英语、西里尔…

多元回归预测 | Matlab主成分分析PCA降维,BP神经网络回归预测。PCA-BP回归预测模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | Matlab主成分分析PCA降维,BP神经网络回归预测。PCA-BP回归预测模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %% 清空环境变量 warnin…

【脚本语言】Shell Script - 日期的获取、设置和延时操作

目录 基础概念 基础语法 打印当前日期 打印纪元时 将日期转换为纪元时 打印要求格式的日期 设置日期和时间 基础概念 Bash可以帮助我们以不同的格式打印日期、设置日期&#xff0c;又或根据日期或时间进行操作等。 在类Unix系统中&#xff0c;日期被存储为一个整数&#xff0…

阿里云服务器架构x86、GPU、ARM、裸金属和超级计算集群说明

阿里云服务器架构有什么区别&#xff1f;X86计算、ARM计算、GPU/FPGA/ASIC、弹性裸金属服务器、超级计算集群有什么区别&#xff1f;阿里云服务器网分享云服务器ECS架构详细说明&#xff1a; 目录 阿里云服务器ECS架构说明 X86计算 ARM计算 GPU/FPGA/ASIC 弹性裸金属服务…

在 CI/CD流水线中运行自动化单元测试的4个原因

目录 什么是单元测试&#xff1f; C#中的单元测试示例 我需要在CI/CD 流水线中运行自动化测试吗&#xff1f; 开发人员代码验证反馈循环 预验证 步步为营 减少“另一个开发人员写了这段代码”的问题 什么是单元测试&#xff1f; 单元测试 什么是单元测试&#xff1f; 单…

【MySQL】内置函数详解

【MySQL】内置函数详解 日期函数字符串函数数学函数加密函数与其它 日期函数 --获取当前日期 mysql> select current_date(); ---------------- | current_date() | ---------------- | 2023-07-09 | ---------------- --获取当前时间 mysql> select current_time();…

ChatGLM2-6b本地部署

chatglm2-6m模型 git 地址 https://github.com/THUDM/ChatGLM2-6B 模型百度网盘地址&#xff1a;链接&#xff1a;https://pan.baidu.com/s/1-LFcPB0H23RSpTKOECsjxw?pwd5e3d 提取码&#xff1a;5e3d 1 模型简介 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#…

【高级程序设计语言C++】类与对象

2.1类的定义2.1.1 类的两种定义方式2.1.2 类的访问限定符2.1.3 C中的struct和class的区别是什么&#xff1f;2.1.4 类的实例化2.1.5 计算类对象的大小2.1.6 this指针 2.2 类的6个默认成员函数2.2.1 构造函数2.2.2 析构函数2.2.3 拷贝构造函数2.2.4 赋值运算符重载2.2.5 取地址及…

Java新特性:Lambda表达式

Java新特性&#xff1a;Lambda表达式 Lambda 表达式&#xff08;Lambda expression&#xff09;&#xff0c;也可称为闭包&#xff08;Closure&#xff09;&#xff0c;是 Java&#xff08;SE&#xff09;8 中一个重要的新特性。Lambda 表达式允许我们通过表达式来代替功能接口…

第五章——循环和关系表达式

for循环 很多情况下都需要程序执行重复的任务 #include<iostream> using namespace std; int main() {int i;for (i 0; i < 5; i){cout << "C knows loop.\n";}cout << "C knows when to stop.\n";return 0; } for循环的组成部分 …

107、基于51单片机多路无线调频对讲机系统设计(程序+原理图+PCB源文件+参考论文+外文翻译+任务书+开题报告+硬件设计资料+元器件清单等)

摘 要 对讲机作为短距离通信和移动调度指挥的重要工具,在社会各个行业都有广泛的应用。尤其是随着数字电路技术的发展&#xff0c;新型的对讲机无论在外型还是性能上相对传统的模拟对讲机都有了长足的进步。对讲机主要包含需要基站支持的集群对讲机和常规无中心对讲机两种&…

深度理解 JAVA序列化

前言 相信大家日常开发中&#xff0c;经常看到Java对象“implements Serializable”。那么&#xff0c;它到底有什么用呢&#xff1f;本文从以下几个角度来解析序列这一块知识点~ 什么是Java序列化&#xff1f;为什么需要序列化&#xff1f;序列化用途Java序列化常用API序列化…

Android 热修复一

一、什么是热修复&#xff1f; 在我们应用上线后出现bug需要及时修复时&#xff0c;不用再发新的安装包&#xff0c;只需要发布补丁包&#xff0c;在客户无感知下修复掉bug。 实现效果&#xff1a; Demo源码&#xff1a; https://gitee.com/sziitjim/hotfix 二、怎么进行热修…

极速上手k8s,Kubernetes 从入门到摸鱼系列-理论篇

1. 引言&#x1f44b; 大家好&#xff0c;我是比特桃&#xff01;随着微服务架构越来越流行&#xff0c;大规模的微服务容器编排成了一件具有挑战的事情。在这次容器化云原生的发展中&#xff0c;Docker 成了容器化的赢家&#xff0c;而 Kubernetes 则成为了容器编排的赢家。k…

「已解决」 模块““umi“” ““@umijs/max“” 没有导出的成员“useRequest” “request” 问题的所有方法汇总

背景 使用 Umi 搭建项目时候有的时候会出现这种错误&#xff0c;模块““umi”” ““umijs/max”” 没有导出的成员“useRequest” “request”。 解决 tsconfig.json "paths": {"/*": ["src/*"],"/*": ["./src/.umi/*"…

用JShaman本地部署版,加密2.7MB的Webpack生成的JS文件

JShaman是知名的JS代码保护平台。在线使用&#xff0c;一键混淆加密&#xff0c;无需注册、无需登录。可免费用&#xff0c;也有商业服务&#xff1b;有在线使用的SAAS平台网站&#xff0c;也有本地部署版。很方便、很强大&#xff0c;很专业。 今天&#xff0c;测试使用JSham…

unordered_map模拟实现|STL源码剖析系列|开散列

博主很久没有更新过STL源码剖析这个系列的文章了&#xff0c;主要是因为大部分STL常用的容器&#xff0c;博主都已经发过文章了&#xff0c;今天博主带着大家把哈希表也模拟实现一下。 前言 那么这里博主先安利一下一些干货满满的专栏啦&#xff01; 手撕数据结构https://blo…