自然语言处理: 第七章GPT的搭建

自然语言处理: 第七章GPT的搭建

理论基础

在以transformer架构为框架的大模型遍地开花后,大模型的方向基本分成了三类分别是:

  • decoder-only架构 , 其中以GPT系列为代表
  • encoder-only架构,其中以BERT系列为代表
  • encoder-decoder架构,标准的transformer架构以BART和T5为代表

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0HeorgFi-1692024387908)(image/GPT/1691331765617.png)]

大模型的使用方法如下: 分解成pre-train 和fine-tuning ,其中pre-train是收集大量的高质量的文本(或者其他多模态的输入)去让模型拥有文本理解的泛化能力,而fine-tuing则是对应各自的下游任务将pre-train好的model在下游任务中作微调,从而适应不同的任务头。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i9UGSWHc-1692024387910)(image/GPT/1691337425884.png)]



那么为什么基于transformer的架构为什么需要可以分成上面的三个分支呢?除了最基本的encoder-decoder架构,这种能普遍处理好各种任务外,那么decoder-only 和 encoder-only的区别在哪?下面以BERT和GPT为代表来分别解释这两种架构的代表,而其中最主要的区别就是二者的预训练目标的区别: 我们由之前Seq2Seq的模型知道,

  • BERT全称是Bidirectional Encoder Representation from Transformers,可以看到它是一个双向的模型,而编码器的作用主要是将输入的全部文本信息压缩至一个定长的向量,然后再给下游任务作fine_tuning,所以BERT这种Encoder-only的架构的预训练任务更像是一个填空题,以下图的例子为例,BERT的任务就是给一个完整的文本, 一(二)三四五,上山(打)老虎,需要去预测括号里的内容,而且BERT本身是一个双向的网络,所以在预测括号里的内容时候,他是已经看过全文的,所以这种encoder-only的架构它更具有推理和理解上下文的能力,所以用来做文本分类,关系抽取与命名实体识别的任务有更好的效果,这种预训练的模式叫做MLM(masked language model)。
  • 而GPT作为decoder-only,它拥有更好的文本生成能力,它的预训练任务就更加贴合我们传统理解的NLP任务,同样如下图的例子,GPT的预训练过程是老虎没打到,(抓到小松鼠),通过上文去预测下文,所以它是一个单向的,也就是更像一个问答题,所以它具有更好的文本生成能力所以就更适合用来作聊天机器人。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-riPOD8AQ-1692024387910)(image/GPT/1691336373171.png)]




因此,GPT的生成式预训练如,内容如下: 输入是上文,输出是下文,并且是单向的decoder结构,所以相比于传统的transformer结构,GPT结构更加的轻量了。除此之外还需要注意的是,在训练阶段由于保证运行效率,直接就由文本在前端加一个 <sos>, 但是在inference阶段需要没生成一个字,连同之前的上文一起再输入给下一次作为输入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zpCejAxc-1692024387911)(image/GPT/1691337602259.png)]




因此这种decorder-only的结构,除了去除了encoder结构之外,自身的decoder基本跟transfor的decoder结构一致,但是去掉了encoder-decoder的self-attention这部分,transformer基本的结构可以参考上文第六章Transformer- 现代大模型的基石: 分解的结构如下图:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B45U7CTL-1692024387911)(image/GPT/1691339029807.png)]




还有一种解释是是从对模型的期望来解释BERT 和GPT的区别,根据前文我们可以知道BERT的预训练的模式是作填空题,所以它本身并不具备生成文字内容的能力,但是它具有更好的理解上下文的能力,所以对应不同的任务,只需要BERT + Head(任务头) 就可以针对不同的任务,所以这就导致了BERT更适合成为专才。而GPT由于预训练是做的问答题,而其实所有的NLP任务都可以看成是问答的任务,比如说机器翻译,你只要给GPT下一个prompt 请帮我将下列句子翻译成英文这样GPT就可以翻译成英文了。对于其他任务也是一样的,只需要下对应的prompt,所以GPT是更像一个通才,无需加单独的任务头,便可以完成不同的任务。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-InmH5a07-1692024387911)(image/07_GPT/1692005331398.png)]

代码实现

1. 多头注意力

class ScaledDotProductAttention(nn.Module):
    def __init__(self):
        super(ScaledDotProductAttention, self).__init__()  
    def forward(self, Q, K, V, attn_mask):  
        # Q K V [batch_size, n_heads, len_q/k/v, dim_q=k/v] (dim_q=dim_k)
        # 计算注意力分数(原始权重)[batch_size,n_heads,len_q,len_k]
        scores = torch.matmul(Q, K.transpose(-1, -2)) / np.sqrt(d_k) 
        # 使用注意力掩码,将attn_mask中值为1的位置的权重替换为极小值
        # attn_mask [batch_size,n_heads,len_q,len_k],形状和scores相同
        scores.masked_fill_(attn_mask.to(torch.bool), -1e9) 
        # 对注意力分数进行softmax
        weights = nn.Softmax(dim=-1)(scores)
        # 计算上下文向量(也就是注意力的输出), 是上下文信息的紧凑表示
        context = torch.matmul(weights, V)
        return context, weights # 返回上下文向量和注意力分数
  
# 定义多头注意力类
d_embedding = 512  # Embedding Size
n_heads = 8  # number of heads in Multi-Head Attention
batch_size = 3 # 每一批数据量
class MultiHeadAttention(nn.Module):
    def __init__(self):
        super(MultiHeadAttention, self).__init__()
        self.W_Q = nn.Linear(d_embedding, d_k * n_heads) # Q的线性变换层
        self.W_K = nn.Linear(d_embedding, d_k * n_heads) # K的线性变换层
        self.W_V = nn.Linear(d_embedding, d_v * n_heads) # V的线性变换层
        self.linear = nn.Linear(n_heads * d_v, d_embedding)
        self.layer_norm = nn.LayerNorm(d_embedding)

    def forward(self, Q, K, V, attn_mask): 
        # Q K V [batch_size,len_q/k/v,embedding_dim]  
        residual, batch_size = Q, Q.size(0) # 保留残差连接
        # 将输入进行线性变换和重塑,以便后续处理
        # q_s k_s v_s: [batch_size,n_heads.,len_q/k/v,d_q=k/v]
        q_s = self.W_Q(Q).view(batch_size, -1, n_heads, d_k).transpose(1,2)  
        k_s = self.W_K(K).view(batch_size, -1, n_heads, d_k).transpose(1,2)
        v_s = self.W_V(V).view(batch_size, -1, n_heads, d_v).transpose(1,2)
        # 将注意力掩码复制到多头 [batch_size,n_heads,len_q,len_k]
        attn_mask = attn_mask.unsqueeze(1).repeat(1, n_heads, 1, 1)
        # 使用缩放点积注意力计算上下文和注意力权重
        context, weights = ScaledDotProductAttention()(q_s, k_s, v_s, attn_mask)
        # 重塑上下文向量并进行线性变换,[batch_size,len_q,n_heads * dim_v]
        context = context.transpose(1, 2).contiguous().view(batch_size, -1, n_heads * d_v) 
        output = self.linear(context)
        # 与输入(Q)进行残差链接,并进行层归一化后输出[batch_size, len_q, embedding_dim]
        output = self.layer_norm(output + residual)
        return output, weights # 返回层归一化的输出和注意力权重

2. 逐位置前馈网络

# 定义逐位置前向传播网络类
class PoswiseFeedForwardNet(nn.Module):
    def __init__(self):
        super(PoswiseFeedForwardNet, self).__init__()
        # 定义一维卷积层1,用于将输入映射到更高维度
        self.conv1 = nn.Conv1d(in_channels=d_embedding, out_channels=2048, kernel_size=1)
        # 定义一维卷积层2,用于将输入映射回原始维度
        self.conv2 = nn.Conv1d(in_channels=2048, out_channels=d_embedding, kernel_size=1)
        # 定义层归一化
        self.layer_norm = nn.LayerNorm(d_embedding)

    def forward(self, inputs): 
        # inputs: [batch_size, len_q, embedding_dim]  
        residual = inputs  # 保留残差连接
        # 在卷积层1后使用ReLU激活函数
        output = nn.ReLU()(self.conv1(inputs.transpose(1, 2)))
        # 使用卷积层2进行降维
        output = self.conv2(output).transpose(1, 2)
        # 与输入进行残差链接,并进行层归一化,[batch_size, len_q, embedding_dim]
        output = self.layer_norm(output + residual)
        return output # 返回层归一化后的输出加上残差连接的结果

3. 正弦位置编码表

def get_sin_enc_table(n_position, embedding_dim):
    # 根据位置和维度信息,初始化正弦位置编码表
    sinusoid_table = np.zeros((n_position, embedding_dim))  
    # 遍历所有位置和维度,计算角度值
    for pos_i in range(n_position):
        for hid_j in range(embedding_dim):
            angle = pos_i / np.power(10000, 2 * (hid_j // 2) / embedding_dim)
            sinusoid_table[pos_i, hid_j] = angle  
    # 计算正弦和余弦值
    sinusoid_table[:, 0::2] = np.sin(sinusoid_table[:, 0::2])  # dim 2i 偶数维
    sinusoid_table[:, 1::2] = np.cos(sinusoid_table[:, 1::2])  # dim 2i+1 奇数维  
    return torch.FloatTensor(sinusoid_table)

4. 填充位置掩码

# 生成填充注意力掩码的函数,用于在多头自注意力计算中忽略填充部分
def get_attn_pad_mask(seq_q, seq_k):
    batch_size, len_q = seq_q.size()
    batch_size, len_k = seq_k.size()
    # 生成布尔类型张量[batch_size,1,len_k(=len_q)]
    pad_attn_mask = seq_k.data.eq(0).unsqueeze(1)  #<PAD> Token的编码值为0 
    # 变形为何注意力分数相同形状的张量 [batch_size,len_q,len_k]
    pad_attn_mask = pad_attn_mask.expand(batch_size, len_q, len_k) 
    return pad_attn_mask # 形状[batch_size,len_q,len_k]

5. 后续位置掩码

# 生成后续注意力掩码的函数,用于在多头自注意力计算中忽略未来信息
def get_attn_subsequent_mask(seq):
    # 获取输入序列的形状 [batch_size, seq_len(len_q), seq_len(len_k)]
    attn_shape = [seq.size(0), seq.size(1), seq.size(1)]
    # 使用numpy创建一个上三角矩阵(triu = triangle upper)
    subsequent_mask = np.triu(np.ones(attn_shape), k=1)
    # 将numpy数组转换为PyTorch张量,并将数据类型设置为byte(布尔值)
    subsequent_mask = torch.from_numpy(subsequent_mask).byte()
    return subsequent_mask # [batch_size, seq_len(len_q), seq_len(len_k)]

6. 解码器

# 构建解码器层
class DecoderLayer(nn.Module):
    def __init__(self):
        super(DecoderLayer, self).__init__()
        self.self_attn = MultiHeadAttention()  # 多头自注意力层
        self.feed_forward = PoswiseFeedForwardNet()  # 位置前馈神经网络层
        self.norm1 = nn.LayerNorm(d_embedding)  # 第一个层归一化
        self.norm2 = nn.LayerNorm(d_embedding)  # 第二个层归一化

    def forward(self, dec_inputs, attn_mask=None):
        # 使用多头自注意力处理输入
        attn_output, _ = self.self_attn(dec_inputs, dec_inputs, dec_inputs, attn_mask)
        # 将注意力输出与输入相加并进行第一个层归一化
        norm1_outputs = self.norm1(dec_inputs + attn_output)
        # 将归一化后的输出输入到位置前馈神经网络
        ff_outputs = self.feed_forward(norm1_outputs)
        # 将前馈神经网络输出与第一次归一化后的输出相加并进行第二个层归一化
        dec_outputs = self.norm2(norm1_outputs + ff_outputs)
        return dec_outputs
  
# 构建解码器
n_layers = 6  # 设置Encoder/Decoder的层数
class Decoder(nn.Module):
    def __init__(self, corpus):
        super(Decoder, self).__init__()
        self.src_emb = nn.Embedding(corpus.vocab_size, d_embedding)  # 词嵌入层(参数为词典维度)
        self.pos_emb = nn.Embedding(corpus.seq_len, d_embedding)  # 位置编码层(参数为序列长度)  
        self.layers = nn.ModuleList([DecoderLayer() for _ in range(n_layers)]) # 初始化N个解码器层

    def forward(self, dec_inputs):  
        positions = torch.arange(len(dec_inputs), device=dec_inputs.device).unsqueeze(-1) # 位置信息  
        inputs_embedding = self.src_emb(dec_inputs) + self.pos_emb(positions) # 词嵌入与位置编码相加  
        attn_mask = get_attn_subsequent_mask(inputs_embedding).to(dec_inputs.device) # 生成自注意力掩码
        dec_outputs =  inputs_embedding # 初始化解码器输入,这是第一层解码器层的输入  
        for layer in self.layers:
            # 每个解码器层接收前一层的输出作为输入,并生成新的输出
            # 对于第一层解码器层,其输入是dec_outputs,即词嵌入和位置编码的和
            # 对于后续的解码器层,其输入是前一层解码器层的输出  
            dec_outputs = layer(dec_outputs, attn_mask) # 将输入数据传递给解码器层
        return dec_outputs # 返回最后一个解码器层的输出,作为整个解码器的输出

7. GPT

class GPT(nn.Module):
    def __init__(self, corpus):
        super(GPT, self).__init__()
        self.corpus = corpus
        self.decoder = Decoder(corpus) # 解码器,用于学习文本生成能力
        self.projection = nn.Linear(d_embedding, corpus.vocab_size)  # 全连接层,输出预测结果

    def forward(self, dec_inputs):  
        dec_outputs = self.decoder(dec_inputs) # 将输入数据传递给解码器
        logits = self.projection(dec_outputs) # 传递给全连接层以生成预测
        return logits #返回预测结果
  
    def decode(self, input_str, strategy='greedy', **kwargs):
        if strategy == 'greedy': # 贪心解码函数
            return generate_text_greedy_search(self, input_str, **kwargs)
        elif strategy == 'beam_search': # 集束解码函数
            return generate_text_beam_search(self, input_str, **kwargs)
        else:
            raise ValueError(f"Unknown decoding strategy: {strategy}")

8. Greedy_search & Beam_search

def generate_text_beam_search(model, input_str, max_len=5, beam_width=5, repetition_penalty=1.2):
    # 将模型设置为评估(测试)模式,关闭dropout和batch normalization等训练相关的层
    model.eval()
    # 让NLTK工具帮忙分一下词
    input_str = word_tokenize(input_str)
    # 将输入字符串中的每个token转换为其在词汇表中的索引, 如果输入的词不再词表里面,就忽略这个词
    input_tokens = [model.corpus.vocab[token] for token in input_str if token in model.corpus.vocab]
    # 检查输入的有意义的词汇长度是否为0
    if len(input_tokens) == 0:
        return   
    # 创建一个列表,用于存储候选序列,初始候选序列只包含输入tokens
    candidates = [(input_tokens, 0.0)]  
    # 创建一个列表,用于存储所有生成的序列及其得分
    final_results = []
    # 禁用梯度计算,以节省内存并加速测试过程
    with torch.no_grad():
        # 生成最多max_len个tokens
        for _ in range(max_len):
            # 创建一个新的候选列表,用于存储当前时间步生成的候选序列
            new_candidates = []    
            # 遍历当前候选序列
            for candidate, candidate_score in candidates:
                # 将当前候选序列转换为torch张量并将其传递给模型
                device = "cuda" if torch.cuda.is_available() else "cpu"
                inputs = torch.LongTensor(candidate).unsqueeze(0).to(device)
                outputs = model(inputs)        
                # 只关心最后一个时间步(即最新生成的token)的logits
                logits = outputs[:, -1, :]
                # 应用重复惩罚:为已经生成的词汇应用惩罚,降低它们再次被选择的概率
                for token in set(candidate):
                    logits[0, token] /= repetition_penalty
                # 将<pad>标记的得分设置为一个很大的负数,以避免选择它
                logits[0, model.corpus.vocab["<pad>"]] = -1e9        
                # 找到具有最高分数的前beam_width个tokens
                scores, next_tokens = torch.topk(logits, beam_width, dim=-1)
                # 遍历生成的tokens及其得分
                for score, next_token in zip(scores.squeeze(), next_tokens.squeeze()):
                    # 将生成的token添加到当前候选序列
                    new_candidate = candidate + [next_token.item()]            
                    # 更新候选序列得分
                    new_score = candidate_score - score.item()            
                    # 如果生成的token是EOS(结束符),将其添加到最终结果中
                    if next_token.item() == model.corpus.vocab["<eos>"]:
                        final_results.append((new_candidate, new_score))
                    else:
                        # 将新生成的候选序列添加到新候选列表中
                        new_candidates.append((new_candidate, new_score))
            # 从新候选列表中选择得分最高的beam_width个序列
            candidates = sorted(new_candidates, key=lambda x: x[1], reverse=True)[:beam_width]
    # 选择得分最高的候选序列,如果final_results为空,选择当前得分最高的候选序列
    if final_results:
        best_candidate, _ = sorted(final_results, key=lambda x: x[1])[0]
    else:
        best_candidate, _ = sorted(candidates, key=lambda x: x[1])[0]
    # 将输出 token 转换回文本字符串
    output_str = " ".join([model.corpus.idx2word[token] for token in best_candidate])
    return output_str

def generate_text_greedy_search(model, input_str, max_len=5):
    # 将模型设置为评估(测试)模式,关闭dropout和batch normalization等训练相关的层
    model.eval()
    # 使用NLTK工具进行词汇切分
    input_str = word_tokenize(input_str)
    # 将输入字符串中的每个token转换为其在词汇表中的索引, 如果输入的词不在词表里面,就忽略这个词
    input_tokens = [model.corpus.vocab[token] for token in input_str if token in model.corpus.vocab]
    # 检查输入的有意义的词汇长度是否为0
    if len(input_tokens) == 0:
        return   
    # 创建一个列表,用于存储生成的词汇
    output_tokens = input_tokens
    # 禁用梯度计算,以节省内存并加速测试过程
    with torch.no_grad():
        # 生成最多max_len个tokens
        for _ in range(max_len):
            # 将当前生成的tokens转换为torch张量并将其传递给模型
            device = "cuda" if torch.cuda.is_available() else "cpu"
            inputs = torch.LongTensor(output_tokens).unsqueeze(0).to(device)
            outputs = model(inputs)        
            # 只关心最后一个时间步(即最新生成的token)的logits
            logits = outputs[:, -1, :]
            # 找到具有最高分数的token
            _, next_token = torch.topk(logits, 1, dim=-1)
            # 如果生成的token是EOS(结束符),则停止生成
            if next_token.item() == model.corpus.vocab["<eos>"]:
                break
            # 否则,将生成的token添加到生成的词汇列表中
            output_tokens.append(next_token.item())
    # 将输出 tokens 转换回文本字符串
    output_str = " ".join([model.corpus.idx2word[token] for token in output_tokens])
    return output_str



结果

本次实验设置了三个对照组,分别是baseline(N_head = 8 , n_layer = 6), N_head = 32 , n_layer = 18,可以看到训练10000个step之后得loss分别如下图:

从收敛程度上来看,18层layer得transformer 完全没有收敛,这个可能是因为深度神经网络的梯度消失,所以我们设置的网络如果没有残差链接的话,尽量不要太深。然后再看多头,可以看到头的数量好像也不是越多越好,但是其实二者都收敛了,具体结果我们可以结合一下inference的结果看看。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OrbLynmi-1692024387912)(image/GPT/1691457265515.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f13C96uI-1692024387912)(image/GPT/1691457362491.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sgAzQFd1-1692024387912)(image/GPT/1691457508644.png)]
在这里插入图片描述
在这里插入图片描述


可以看到两种解码得方式,greedy_search在大部分时候由于设置了惩罚项所以现在大部分时候是两个单词无限循环,相比之下beam_search得结果就好得多,更像一句话。

其次对比一下三个对照组得结果,正如loss的结果一样,深层次GPT架构无论是beam_search还是greedy_search翻译的结果都非常的差,出现了很多标点,这应该就是没有收敛的结果。然后对比下不同的head数量,这里看上去也是n_head越少的效果越好。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IGo8HqTZ-1692024387913)(image/GPT/1691456509885.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RlxRHHwJ-1692024387913)(image/GPT/1691456528323.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VzW91uqg-1692024387913)(image/GPT/1691456544126.png)]

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

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

相关文章

关于Java中synchronized的实现原理

并发编程的三个理念 原子性&#xff1a;一个操作要么全部完成&#xff0c;要么全部失败。可见性&#xff1a;当一个线程对共享变量进行修改后&#xff0c;其他线程也应立刻看到。有序性&#xff1a;程序按照顺序执行 synchronized基本使用 修饰静态方法&#xff0c;锁的是类…

时序预测 | Matlab实现基于RF随机森林的电力负荷预测模型

文章目录 效果一览基本介绍模型描述源码设计学习小结参考资料效果一览 基本介绍 时序预测 | Matlab实现基于RF随机森林的电力负荷预测模型 电力负荷预测是指通过对历史电力负荷数据分析,来预测未来某个时间段内的电力负荷需求。这项预测对于电力系统的运行和调度至关重要,可以…

【Echart地图】jQuery+html5基于echarts.js中国地图点击弹出下级城市地图(附完整源码下载)

文章目录 写在前面涉及知识点实现效果1、实现中国地图板块1.1创建dom元素1.2实现地图渲染1.3点击地图进入城市及返回 2、源码分享2.1 百度网盘2.2 123云盘2.3 邮箱留言 总结 写在前面 这篇文章其实我主要是之前留下的一个心结&#xff0c;依稀记得之前做了一个大屏项目的时候&…

【Sklearn】基于决策树算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于决策树算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理1.1 模型原理1.2 数学模型 2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 决策树是一种基于树状结构的分类和回归模型&#xff0c;它通过一系列…

C++ QT(一)

目录 初识QtQt 是什么Qt 能做什么Qt/C与QML 如何选择Qt 版本Windows 下安装QtLinux 下安装Qt安装Qt配置Qt Creator 输入中文配置Ubuntu 中文环境配置中文输入法 Qt Creator 简单使用Qt Creator 界面组成Qt Creator 设置 第一个Qt 程序新建一个项目项目文件介绍项目文件*.pro样式…

【网络】传输层——UDP | TCP(协议格式确认应答超时重传连接管理)

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 现在是传输层&#xff0c;在应用层中的报文(报头 有效载荷)就不能被叫做报文了&#xff0c;而是叫做数…

【Sklearn】基于最中心分类器算法的数据分类预测(Excel可直接替换数据)

【Sklearn】基于最中心分类器算法的数据分类预测&#xff08;Excel可直接替换数据&#xff09; 1.模型原理2.模型参数3.文件结构4.Excel数据5.下载地址6.完整代码7.运行结果 1.模型原理 最近中心分类器&#xff08;Nearest Centroid Classifier&#xff09;也被称为近似最近邻…

若依框架浅浅介绍

由若依官网所给介绍可知 1、文件结构介绍 在ruoyi-admin的pom.xml文件中引入了ruoyi-framework、ruoyi-quartz和ruoyi-generatior模块&#xff0c;在ruoyi-framework的pom.xml文件中引入了ruoyi-system模块。 2、技术栈介绍 前端&#xff1a;Vue、Element UI后端&#xff1a…

xxljob搭建(内网穿透)

调度中心搭建 先从码云或者github上将项目拷贝到本地&#xff0c;选择最新的release分支拷贝下来的xxl-job-admin模块就是调度中心&#xff0c;我们需要做的有两点&#xff0c;第一点将doc/db/tables_xxl_job.sql执行&#xff0c;第二点修改xxl-job-admin的application.proper…

SAP Fiori 将GUI中的自开发报表添加到Fiori 工作台

1. 首先我们在workbench 中开发一个GUI report 这里我们开发的是一个简单的物料清单报表 2. 分配一个事务代码。 注意这里的SAP GUI for HTML 要打上勾 3. 创建语义对象&#xff08; Create Semantic Object&#xff09; 事物代码&#xff1a; path: SAP NetWeaver ->…

2. 获取自己CSDN文章列表并按质量分由小到大排序(文章质量分、博客质量分、博文质量分)(阿里云API认证)

文章目录 写在前面步骤打开CSDN质量分页面粘贴查询文章url按F12打开调试工具&#xff0c;点击Network&#xff0c;点击清空按钮点击查询是调了这个接口https://bizapi.csdn.net/trends/api/v1/get-article-score用postman测试调用这个接口&#xff08;不行&#xff0c;认证不通…

React源码解析18(6)------ 实现useState

摘要 在上一篇文章中&#xff0c;我们已经实现了函数组件。同时可以正常通过render进行渲染。 而通过之前的文章&#xff0c;beginWork和completeWork也已经有了基本的架子。现在我们可以去实现useState了。 实现之前&#xff0c;我们要先修改一下我们的index.js文件&#x…

nodejs+vue+elementui美食网站的设计与实现演示录像2023_0fh04

本次的毕业设计主要就是设计并开发一个美食网站软件。运用当前Google提供的nodejs 框架来实现对美食信息查询功能。当然使用的数据库是mysql。系统主要包括个人信息修改&#xff0c;对餐厅管理、用户管理、餐厅信息管理、菜系分类管理、美食信息管理、美食文化管理、系统管理、…

java内存模型JMM

Java内存模型的主要目标&#xff1a;定义程序中各个变量的访问规则&#xff0c;即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。 主内存&#xff1a;所有的变量都存储在主内存&#xff0c;且线程共享。 工作内存&#xff1a;每条线程私有&#xff0c;保存了…

Linux目录结构(快速了解)

linux目录结构 核心 1.Linux一切皆文件 2.只有一个顶级目录&#xff0c;而windows分C盘、D盘等 目录结构 目录含义&#xff08;与windows进行比对&#xff09; Linux含义windows/bin所有用户可用的基本命令存放的位置windows无固定的命令存放目录/bootlinux系统启动的时候需要…

C进阶(1/7)——数据在内存中的存储

目录 前言&#xff1a; 一.数据类型介绍 类型基本归类&#xff1a; 整型家族&#xff1a; 浮点数家族&#xff1a; 构造类型&#xff1a; ​指针类型&#xff1a; 空类型&#xff1a; 二.整型在内存中的存储 1.原码&#xff0c;反码&#xff0c;补码 2.大小端介绍 3.练…

分布式 - 消息队列Kafka:Kafka 消费者消息消费与参数配置

文章目录 1. Kafka 消费者消费消息01. 创建消费者02. 订阅主题03. 轮询拉取数据 2. Kafka 消费者参数配置01. fetch.min.bytes02. fetch.max.wait.ms03. fetch.max.bytes04. max.poll.records05. max.partition.fetch.bytes06. session.timeout.ms 和 heartbeat.interval.ms07.…

2023国赛数学建模思路 - 复盘:光照强度计算的优化模型

文章目录 0 赛题思路1 问题要求2 假设约定3 符号约定4 建立模型5 模型求解6 实现代码 建模资料 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 问题要求 现在已知一个教室长为15米&#xff0c;宽为12米&…

(css)点击前隐藏icon图表 点击后显示

(css)点击前隐藏icon图表 点击后显示 效果 html <liv-for"(item,index) in sessionList":key"index"class"liClass":class"{ active: change2 index }"tabindex"2">...<el-tooltip class"item" effec…

opencv实战项目 手势识别-实现尺寸缩放效果

手势识别系列文章目录 手势识别是一种人机交互技术&#xff0c;通过识别人的手势动作&#xff0c;从而实现对计算机、智能手机、智能电视等设备的操作和控制。 1. opencv实现手部追踪&#xff08;定位手部关键点&#xff09; 2.opencv实战项目 实现手势跟踪并返回位置信息&…