ChatGPT流程深度分析:一篇文章带你掌握大模型整体流程(专家篇)

4 篇文章 0 订阅
4 篇文章 0 订阅

在这里插入图片描述

肖哥弹架构 跟大家“弹弹” AI, 关注公号回复 ‘mvcc’ 获得手写数据库事务代码

欢迎 点赞,关注,评论。

关注公号Solomon肖哥弹架构获取更多精彩内容

历史热点文章

1、ChatGPT介绍

Chat GPT,通常指的是基于GPT(Generative Pre-trained Transformer)模型的聊天应用或功能。这里的“Chat”表示这个模型被设计用来进行对话或聊天,而“GPT”指的是它背后的技术基础,即生成式预训练变换器模型。分别的意思就是“Chat”-聊天,G:“Generative”-生成式,P:“Pre-trained”-预训练,T:“Transformer”-大模型推理架构。
在这里插入图片描述

图解释:
  • 开始:启动GPT模型的开发和训练过程。
  • 预训练:在大量文本数据上训练GPT模型,使其学习语言的模式和结构。
  • Transformer架构:在预训练阶段,GPT模型使用Transformer架构来处理语言数据。Transformer通过自注意力机制(Self-Attention)和前馈神经网络来学习文本中的依赖关系和模式。
  • 语言模型训练:训练模型预测下一个单词或句子,以理解语言的统计规律。
  • Next Sentence Prediction:训练模型预测下一个句子,增强模型对文本连贯性的理解。
  • 微调:在预训练的基础上,针对特定任务进一步训练模型,使其更好地适应任务需求。
  • 生成:使用训练好的模型,根据输入的提示词生成相应的文本。
  • 文本输出:生成的文本作为最终输出,用于各种应用场景。
Chat GPT详细解释:
  1. GPT模型
    • GPT是由人工智能研究实验室OpenAI开发的一系列语言模型。
    • 这些模型使用深度学习技术,特别是变换器(Transformer)架构,来处理和生成自然语言文本。
  2. 预训练
    • GPT模型在大量的文本数据上进行预训练,学习语言的模式和结构,以便能够理解和生成语言。
  3. 生成式
    • 预训练完成后,GPT模型能够生成新的文本数据,这些数据在统计上与训练数据相似,但并非直接复制。
  4. 聊天应用
    • 当GPT模型被用于聊天应用时,它可以与用户进行交互,理解用户的问题或指令,并生成合适的回答或响应。
  5. 上下文理解
    • Chat GPT能够理解对话的上下文,这使得它能够在多轮对话中保持连贯性,提供更加相关和个性化的回答。
  6. 多样性和创造性
    • 由于其生成式的特性,Chat GPT不仅可以提供基于事实的信息,还可以创造性地参与对话,提供建议或讲故事。
  7. 应用场景
    • Chat GPT可以用于各种场景,包括客户服务、虚拟助手、教育、娱乐和任何需要自然语言交互的领域。
  8. 持续学习和优化
    • 通过与用户的互动,Chat GPT可以不断学习和优化,以提供更好的服务和体验。

2、Generative

生成式(Generative)指的是模型能够生成新的数据样本,这些样本在统计上与训练数据相似,但并非直接复制。生成式模型能够创造出新的文本、图像或其他类型的数据。

在这里插入图片描述

2.1 原始故事内容:

“在森林深处,有一座古老的城堡,传说中它被魔法保护。一个勇敢的探险家,踏上了寻找城堡的旅程。他穿过密林,跨过小溪,最终找到了城堡的大门。”

2.2 数据预处理:
  • 整理:将故事文本转换为纯文本格式,去除所有格式和特殊字符。
  • 清洗:修正明显的输入错误,如错别字或标点错误。
  • 分词:将文本分解成单词列表,以便模型可以单独处理每个单词。

数据变化:原始文本变成了一个单词序列。

["在", "森林", "深处", ",", "有", "一座", "古老", "的", "城堡", ",", "传说", "中", "它", "被", "魔法", "保护", "。", "一个", "勇敢", "的", "探险家", ",", "踏上", "了", "寻找", "城堡", "的", "旅程", "。", "他", "穿过", "密林", ",", "跨过", "小溪", ",", "最终", "找到", "了", "城堡", "的", "大门", "。"]
2.3 学习数据分布:
  • 建模:使用单词序列训练模型,如RNN或Transformer,学习单词之间的序列关系。
  • 特征提取:模型学习到每个单词的嵌入表示,这些嵌入捕捉了单词的语义信息。

数据变化:单词序列被转换成一系列高维向量,每个向量代表一个单词的语义特征。

[
 [0.25, -0.75, 0.1],  # “在”
 [0.15, -0.65, 0.2],  # “森林”
 [0.35, -0.85, 0.05], # “深处”
 [0.45, -0.95, 0.35], # “有”
 [0.55, -0.55, 0.45], # “一座”
 [0.65, -0.45, 0.55], # “古老”
 [0.75, -0.35, 0.65], # “的”
 [0.85, -0.25, 0.75], # “城堡”
 [0.95, -0.15, 0.85], # “传说”
 [1.05, -0.05, 0.95], # “中”
 [1.15, 0.05, 1.05],  # “它”
 [1.25, 0.15, 1.15],  # “被”
 [1.35, 0.25, 1.25],  # “魔法”
 [1.45, 0.35, 1.35],  # “保护”
 [1.55, 0.45, 1.45],  # “一个”
 [1.65, 0.55, 1.55],  # “勇敢”
 [1.75, 0.65, 1.65],  # “的”
 [1.85, 0.75, 1.75],  # “探险家”
 [1.95, 0.85, 1.85],  # “踏上”
 [2.05, 0.95, 1.95],  # “了”
 [2.15, 1.05, 2.05],  # “寻找”
 [2.25, 1.15, 2.15],  # “城堡”
 [2.35, 1.25, 2.25],  # “的”
 [2.45, 1.35, 2.35],  # “旅程”
 [2.55, 1.45, 2.45],  # “他”
 [2.65, 1.55, 2.55],  # “穿过”
 [2.75, 1.65, 2.65],  # “密林”
 [2.85, 1.75, 2.75],  # “跨过”
 [2.95, 1.85, 2.85],  # “小溪”
 [3.05, 1.95, 2.95],  # “最终”
 [3.15, 2.05, 3.05],  # “找到”
 [3.25, 2.15, 3.15],  # “了”
 [3.35, 2.25, 3.25],  # “城堡”
 [3.45, 2.35, 3.35],  # “的”
 [3.55, 2.45, 3.45]   # “大门”
]

2.4 潜在空间建模:

  • 降维:使用技术如主成分分析(PCA)或t-SNE将高维嵌入映射到低维空间。
  • 编码:模型将文本编码为潜在空间中的点。

数据变化:文本被编码为潜在空间中的一个点,这个点代表了整个故事的压缩表示。

[
  0.5,  -0.5,  0.2,  0.3, -0.3,  0.1, -0.1,  0.4, -0.4,  0.05,
 -0.05, 0.06, -0.06, 0.07, -0.07, 0.08, -0.08, 0.09, -0.09, 0.10,
 -0.10, 0.11, -0.11, 0.12, -0.12, 0.13, -0.13, 0.14, -0.14, 0.15,
 -0.15, 0.16, -0.16, 0.17, -0.17, 0.18, -0.18, 0.19, -0.19, 0.20
]

2.5 生成新样本:

  • 采样:从潜在空间中的已知点附近随机采样新的点。
  • 解码:将这些新点解码回文本空间,生成新的单词序列。

数据变化:新的潜在空间点被解码为新的单词序列,形成了一个新故事的草稿。

["在", "森林", "边缘", ",", "有", "一位", "神秘", "的", "巫师", ",", "他", "掌握", "着", "古老", "的", "魔法", "。"]

2.6 后处理:

  • 修正:对生成的文本进行语法和语义修正,确保故事连贯。
  • 细化:调整故事的细节,使其更加丰富和有趣。

数据变化:生成的草稿经过润色,变成了一个完整的、连贯的新故事。

“在森林边缘,有一位神秘的巫师,他掌握着古老的魔法。”

2.7 输出生成的数据:

  • 最终输出:新故事被输出,准备展示给读者。

数据变化:模型生成了一个新的故事, “在森林边缘,有一位神秘的巫师,他掌握着古老的魔法。”

3、Pre-trained

预训练是指在大量数据上训练模型的过程,以便模型能够学习到语言的通用特征和模式。预训练通常使用无监督学习,模型在这个阶段没有特定任务的目标。

在这里插入图片描述

解释:
  1. 开始:启动预训练过程。
  2. 数据收集:收集大量文本数据。
  3. 数据预处理:清洗和格式化数据。
  4. 构建词汇表:提取唯一单词并分配索引。
  5. 嵌入表示:将单词转换为嵌入向量。
  6. 模型架构设计:选择合适的模型架构。
  7. 训练目标:定义训练任务。
  8. 模型训练:训练模型并调整权重。
  9. 保存模型:保存训练好的模型以供后续使用。

3.1 预训练过程

3.1.1 数据收集

收集大量的文本数据,通常来自书籍、文章、网页等多种来源。

原始数据:

“在森林深处,有一座古老的城堡,传说中它被魔法保护。”
3.1.2 数据预处理

对收集到的文本进行清洗和格式化,包括去除特殊字符、标点符号、HTML标签等。将文本分割成句子或单词,准备进行训练。

数据预处理后的结果:

["在", "森林", "深处", "有", "一座", "古老", "的", "城堡", "传说", "中", "它", "被", "魔法", "保护"]
3.1.3 构建词汇表

从预处理后的文本中提取所有唯一的单词,构建词汇表。为每个单词分配一个唯一的索引,以便在训练过程中使用。

构建词汇表:

{
  "在": 0,
  "森林": 1,
  "深处": 2,
  "有": 3,
  "一座": 4,
  "古老": 5,
  "的": 6,
  "城堡": 7,
  "传说": 8,
  "中": 9,
  "它": 10,
  "被": 11,
  "魔法": 12,
  "保护": 13
}
3.1.4 嵌入表示

将词汇表中的每个单词转换为嵌入向量,通常使用随机初始化或通过其他预训练模型获得。

嵌入表示:

[
 [0.1, -0.2, 0.3],  # “在”
 [0.4, -0.5, 0.6],  # “森林”
 [0.5, -0.3, 0.2],  # “深处”
 [0.2, -0.1, 0.4],  # “有”
 [0.7, -0.6, 0.1],  # “一座”
 [0.8, -0.7, 0.5],  # “古老”
 [0.6, -0.4, 0.8],  # “的”
 [0.3, -0.2, 0.7],  # “城堡”
 [0.9, -0.8, 0.2],  # “传说”
 [0.5, -0.5, 0.3],  # “中”
 [0.4, -0.3, 0.6],  # “它”
 [0.1, -0.1, 0.9],  # “被”
 [0.2, -0.2, 0.8],  # “魔法”
 [0.3, -0.3, 0.5],  # “保护”
 [0.6, -0.6, 0.4],  # “一个”
 [0.7, -0.7, 0.3],  # “勇敢”
 [0.8, -0.8, 0.1],  # “的”
 [0.9, -0.9, 0.0],  # “探险家”
 [0.0, -0.1, 0.2],  # “踏上”
 [0.1, -0.0, 0.1],  # “了”
 [0.2, -0.3, 0.0],  # “寻找”
 [0.3, -0.4, 0.2],  # “城堡”
 [0.4, -0.5, 0.3],  # “的”
 [0.5, -0.6, 0.4],  # “旅程”
 [0.6, -0.7, 0.5],  # “他”
 [0.7, -0.8, 0.6],  # “穿过”
 [0.8, -0.9, 0.7],  # “密林”
 [0.9, -1.0, 0.8],  # “跨过”
 [1.0, -0.9, 0.9],  # “小溪”
 [0.8, -0.8, 0.7],  # “最终”
 [0.7, -0.7, 0.6],  # “找到”
 [0.6, -0.6, 0.5],  # “了”
 [0.5, -0.5, 0.4],  # “城堡”
 [0.4, -0.4, 0.3],  # “的”
 [0.3, -0.3, 0.2]   # “大门”
]
3.1.5 模型架构设计

选择合适的模型架构(如Transformer),并设置模型的层数、隐藏单元数等超参数。

  1. 选择合适的模型架构
    • Transformer架构:Transformer模型由Vaswani等人在2017年提出,它基于自注意力机制(Self-Attention),能够捕捉序列数据中的长距离依赖关系。Transformer模型通常用于语言模型、机器翻译、文本摘要等任务。
  2. 设置模型的层数
    • 编码器和解码器层:Transformer模型由多个编码器和解码器层组成。每个层都包含自注意力模块和前馈神经网络。层数的增加可以提高模型的学习能力,但同时也会增加计算复杂度和训练时间。
  3. 隐藏单元数
    • 维度大小:隐藏单元数指的是模型中自注意力层和前馈网络的维度大小。这个参数影响模型的容量和性能。通常,更多的隐藏单元可以提供更丰富的表示能力,但也可能导致过拟合和增加训练成本。
  4. 超参数设置
    • 学习率:学习率决定了模型权重在训练过程中调整的速度。选择合适的学习率对于模型的收敛至关重要。
    • 批大小:批大小是指每次模型更新时使用的样本数量。较小的批大小可以提高模型的泛化能力,但可能减慢训练速度;较大的批大小则相反。
    • 优化器:选择合适的优化器(如Adam、SGD等)也很重要,因为它们影响模型训练的效率和稳定性。
    • 正则化技术:为了防止过拟合,可能需要使用dropout、权重衰减等正则化技术。
  5. 其他考虑因素
    • 注意力头数:Transformer模型中的多头自注意力机制允许模型在不同的表示子空间中并行学习信息。头数的选择会影响模型的表示能力和计算复杂度。
  • 位置编码:由于Transformer本身不具备捕捉序列顺序的能力,需要通过位置编码来提供序列中单词的位置信息。

基于Transformer的文本生成模型,可能的配置如下:

  • 模型架构:Transformer
  • 层数:编码器4层,解码器4层
  • 隐藏单元数:512
  • 注意力头数:8
  • 学习率:0.001
  • 批大小:32
  • 优化器:Adam
  • 正则化:dropout率0.1
3.1.6 训练目标

定义训练目标,例如使用语言模型任务(如下一个单词预测或掩码语言模型)来训练模型。

以下是一些常见的训练目标,特别是针对语言模型:

  1. 语言模型任务
    • 下一个单词预测:这是最常见的语言模型任务之一,模型需要预测序列中下一个单词的概率分布。给定一个单词序列,模型学习预测下一个最可能出现的单词。
  2. 掩码语言模型(Masked Language Model, MLM)
    • BERT(Bidirectional Encoder Representations from Transformers) :BERT使用掩码语言模型作为其预训练任务之一。在这种方法中,输入序列中的一些单词会被随机替换为一个特殊的[MASK]标记。模型的目标是预测这些被掩盖单词的原始单词。
  3. 序列到序列学习
    • 机器翻译:模型学习将一个语言的文本序列转换为另一种语言的文本序列。
    • 文本摘要:模型学习从较长的文本中生成简短的摘要。
  4. 对话系统
    • 响应生成:模型学习如何根据用户的输入生成合适的响应。
  5. 问答系统
    • 问题回答:模型学习如何从给定的文本中提取信息来回答相关的问题。
  6. 文本分类
    • 情感分析:模型学习如何根据文本内容判断作者的情绪或态度。
    • 主题分类:模型学习将文本分配到预定义的类别或主题中。
  7. 命名实体识别(NER)
    • 实体识别:模型学习识别文本中的特定实体,如人名、地点、组织等。

训练目标的设定

在设计训练目标时,需要考虑以下因素:

  • 数据集:训练目标应与可用的数据集和任务需求相匹配。
  • 模型架构:不同的模型架构可能更适合某些任务。例如,Transformer适合处理长距离依赖关系,而CNN可能更适合局部特征提取。
  • 计算资源:训练目标的复杂性直接影响到训练所需的计算资源和时间。
  • 评估指标:选择合适的评估指标来衡量模型在训练目标上的性能,如准确率、F1分数、BLEU分数等。

用于文本生成的Transformer模型,我们的训练目标可以是:

  • 任务:下一个单词预测
  • 数据集:包含大量书籍、文章和网页的文本语料库
  • 目标:模型能够根据给定的上下文(前几个单词)预测下一个单词
  • 评估指标:困惑度(Perplexity),较低的困惑度表示模型预测的准确性更高
3.1.7 模型训练

使用预处理后的数据和定义的训练目标,进行模型训练。模型通过反向传播算法不断调整权重,以最小化预测错误。
,假设使用的损失函数为交叉熵损失,模型通过反向传播不断调整权重。

  1. 模型预测
    • 假设模型在给定上下文“在森林深处”的情况下预测下一个单词是“有”的概率分布:
      P(有|在森林深处) = [0.2, 0.1, 0.3, ..., 0.4]  # 假设有10个可能的单词
      
  2. 目标概率分布
    • 真实的下一个单词是“有”,因此目标概率分布是一个one-hot向量:
      T = [0, 0, 1, 0, 0, ..., 0]  # “有”对应的索引是2
      
  3. 计算交叉熵损失
    • 交叉熵损失计算如下:
      L = -Σ(T[i] * log(P[i]))
      L = -(0 * log(0.2) + 0 * log(0.1) + 1 * log(0.3) + 0 * log(0.4) + ...)
      L = -log(0.3)
      
  4. 反向传播
    • 使用损失函数的结果计算模型权重的梯度。
  5. 权重更新
    • 根据梯度和学习率更新模型权重:
      W = W - learning_rate * gradient
      

后处理和生成新样本:

  1. 采样新点
    • 从潜在空间中采样一个新的点,例如:
      [0.25, -0.25, 0.35]
      
  2. 解码新点
    • 将新点解码为新的单词序列,生成新的故事草稿。
  3. 输出生成的数据
    • 最终生成的新故事文本,例如:
      “在森林边缘,有一座神秘的塔楼...”
      
3.18 保存模型

训练完成后,保存模型的权重和配置,以便后续微调或使用。保存模型的权重和配置文件,以便后续使用。

4、Transformer

Transformer 模型是由 Vaswani 等人在 2017 年提出的,它彻底改变了自然语言处理(NLP)领域。Transformer 模型的核心是自注意力机制(Self-Attention),它允许模型在序列的每个位置计算注意力权重,从而捕捉序列内部的依赖关系。Transformer是一种深度学习模型架构,它在自然语言处理(NLP)任务中被广泛使用。它由自注意力(Self-Attention)机制和前馈神经网络组成,能够处理序列数据,如文本。

在这里插入图片描述

原始文本数据:
“在森林深处,有一座古老的城堡。”
数据预处理后的结果(分词):
["在", "森林", "深处", "有", "一座", "古老", "的", "城堡", "。"]

Transformer 训练过程的详细步骤:

4.1. 输入嵌入(Input Embeddings)
  • 将文本数据转换为数值形式,通常是通过词嵌入(Word Embeddings)。
  • 词嵌入将词汇表中的每个单词映射到一个高维空间中的向量。

数据变化:原始文本数据(如单词序列)被转换为数值形式的词嵌入向量。这些向量通常通过查找预训练的嵌入矩阵来获取。

输入嵌入(Input Embeddings,初始化向量):

[
 [0.1, -0.2, 0.3],  # “在”
 [0.4, -0.5, 0.6],  # “森林”
 [0.7, -0.8, 0.9],  # “深处”
 ...                 # 其他单词
 [1.2, -1.3, 1.4]   # “。”
]
4.2. 位置编码(Positional Encoding)
  • 由于 Transformer 不像循环神经网络(RNN)那样处理序列,它需要一种方式来理解单词在序列中的位置。
  • 位置编码是与词嵌入相加的一组向量,它们为模型提供了单词在序列中的位置信息。

数据变化:将位置编码向量加到词嵌入向量上,为模型提供单词在序列中的位置信息。位置编码是预先计算好的,与词嵌入向量具有相同的维度。

位置编码(Positional Encoding向量):

[
 [0.01, 0.02, 0.03],  # “在”的位置编码
 [0.04, 0.05, 0.06],  # “森林”的位置编码
 [0.07, 0.08, 0.09],  # “深处”的位置编码
 ...                  # 其他单词的位置编码
 [0.12, 0.13, 0.14]  # “。”的位置编码
]
4.3. 自注意力层(Self-Attention Layers)
  • Transformer 模型包含多个自注意力层,每个层都包含多头注意力机制。
  • 每个“头”学习序列的不同表示,然后将这些表示合并,以便模型可以捕捉不同子空间中的信息。

数据变化:输入的嵌入向量通过自注意力层,每个单词的表示被更新为包含整个序列上下文信息的表示。自注意力层输出的是更新后的单词表示。
自注意力层后的输出:

[
 [0.2, -0.1, 0.4],  # “在”的更新表示
 [0.5, -0.4, 0.7],  # “森林”的更新表示
 [0.8, -0.6, 0.9],  # “深处”的更新表示
 ...                 # 其他单词的更新表示
 [1.3, -1.2, 1.5]   # “。”的更新表示
]
4.4. 前馈网络(Feed-Forward Networks)
  • 在每个自注意力层之后,有一个前馈网络,它对自注意力层的输出进行进一步的处理。
  • 前馈网络通常包含两个线性层,中间有一个激活函数,如ReLU。

数据变化:自注意力层的输出通过一个前馈网络,该网络对每个位置的表示进行独立处理,通常包括两个线性变换和一个非线性激活函数。

前馈网络后的输出(进一步处理向量):

[
 [0.3, -0.2, 0.5],  # “在”的进一步处理表示
 [0.6, -0.5, 0.8],  # “森林”的进一步处理表示
 [0.9, -0.7, 0.95], # “深处”的进一步处理表示
 ...                 # 其他单词的进一步处理表示
 [1.4, -1.3, 1.6]   # “。”的进一步处理表示
]
4.5. 残差连接(Residual Connections)
  • 每个子层(自注意力层和前馈网络)的输出都会与输入相加,形成残差连接。
  • 这有助于避免在深层网络中出现的梯度消失问题。

数据变化:前馈网络的输出与自注意力层的输出相加,形成残差连接。这一步有助于避免深层网络中的梯度消失问题。

权重更新(梯度下降步骤):

# 假设梯度为g,学习率为lr
weights = weights - lr * g
4.6. 层归一化(Layer Normalization)
  • 在每个子层的输出上应用归一化,有助于稳定训练过程。

数据变化:对残差连接的结果进行归一化处理,使得数据分布更加稳定,有助于加速训练过程。

输入到层归一化的数据(前馈网络输出):

[
 [2.0, -1.0, 0.5],  # 特征1
 [1.0, -0.5, 0.2],  # 特征2
 [0.5, -0.2, 0.1]   # 特征3
]
4.7. 输出层(Output Layer)
  • 在模型的最后一层,将前馈网络的输出转换为最终的预测,例如,对于语言模型任务,这可能是下一个单词的概率分布

    数据变化:最后一层的输出通过一个线性层,将隐藏状态映射到最终的预测结果,如单词概率分布。

输出层的预测(最终概率分布):

[
 [0.05, 0.1, 0.05, ..., 0.1],  # 每个单词的概率
 ...
]
4.8. 损失函数和优化
  • 根据任务定义损失函数,例如,对于语言模型,可能是交叉熵损失。
  • 使用优化算法(如Adam或SGD)来更新模型的权重,以最小化损失函数。

数据变化:根据预测结果和真实标签计算损失函数(如交叉熵损失)。损失函数的梯度用于更新模型的权重。

损失函数计算(交叉熵损失):

loss = -Σ(T[i] * log(P[i]))
# 假设目标是预测下一个单词“传说”,其在词汇表中的概率分布为P,目标概率分布为T
# T = [0, ..., 1, ..., 0] (“传说”的one-hot向量)
# P = [0.05, ..., 0.1, ..., 0.05] (模型预测的概率分布)
# loss = -(0 * log(0.05) + ... + 1 * log(0.1) + ... + 0 * log(0.05))
4.9. 训练和评估
  • 在训练集上训练模型,并在验证集上评估其性能。
  • 根据验证集上的性能调整超参数,如学习率、层数、隐藏单元数等。

数据变化:模型在训练数据上进行多次迭代训练,每次迭代都会更新权重。在验证集上评估模型的性能,如准确率或困惑度。

训练和评估后的性能(准确率):

accuracy = (正确预测的数量 / 总预测的数量) * 100
4.10. 微调和部署
  • 在特定任务上进一步微调模型。
  • 将训练好的模型部署到实际应用中。

数据变化:在特定任务上对模型进行微调,进一步优化模型在该任务上的表现。最终,模型被部署到实际应用中,用于处理新的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Solomon_肖哥弹架构

你的欣赏就是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值