【原创】实现GPT中Transformer模型之框架概念

 

作者:黑夜路人

时间:2023年7月

GPT是什么意思

GPT的全称是 Generative Pre-trained Transformer(生成型预训练变换模型),它是基于大量语料数据上训练,以生成类似于人类自然语言的文本。其名称中的“预训练”指的是在大型文本语料库上进行的初始训练过程,其中模型学习预测文章中下一个单词,它可以完成各种自然语言处理任务,例如文本生成、代码生成、视频生成、文本问答、图像生成、论文写作、影视创作、科学实验设计等等。

下面我们用大家容易理解的方式简单介绍一下GPT模型的整个工作原理。

上面讲了我们GPT的单词是:Generative Pre-trained Transformer ,简单这三个词语拆解下来就是:

Generative - 生成下一个词

Pre-trained - 文本预训练(互联网各种文字材料)

Transformer - 基于Transformer架构(无监督学习)

GPT用中概括描述就是:通过Transformer 架构进行文本预训练后能够按照给定的文本,生成合理文本延续的模型。(文本接龙)

其中无监督训练中,核心依赖的就是Transformer模型,这是一个优秀的模型,如果说到Transformer框架比较优秀,就需要大概了解一下神经网络在这个过得的迭代发展过程,了解为什么它是一个在AI问答(ChatGPT)中优秀的模型框架。

对于ChatGPT能够取得那么好的一个互动交流的效果,最核心的是GPT机制,GPT机制里面除了预训练( Pre-trained)和人工反馈强化学习(RLHF - Reinforcement Learning from Human Feedback),最基础的就是这个T(Transformer)机制了。

整个Transformer可以简单理解为一个“无监督学习”的“文字接龙”游戏,然后完成整个核心基础大模型的训练,然后通过 RLHF 强化训练让这个LLM(大模型)看起来越来越智能。

Transformer 介绍

Transformer是Google在2017年提出的一种革命性的神经网络结构,最早考虑用于机器翻译任务。它通过注意力机制来建模序列中的长期依赖关系,实现并行计算,解决了循环神经网络中的梯度消失问题,成为序列建模领域的一大突破。

Transformer通过注意力机制建模输入序列的依赖关系,实现高效的并行计算。与循环神经网络(RNN/LSTM)相比,它的训练过程更加稳定,参数量也更少。这使Transformer在各种序列建模任务上表现优异, 它引领了机器学习的很多前沿方向,在NLP和其他序列建模任务中都有广泛的应用,成为目前最为流行的神经网络结构之一。

因为Transformer的出现,也诞生了包括 GPT、LLaMa、Bert 等优秀的预训练

Transformer模型的主要优势包括:

1. 并行计算。Transformer可以并行计算所有时间步,计算速度很快,这是其相比RNN和LSTM的最大优势。

2. 学习长期依赖。Transformer通过Attention机制可以直接建模任意两个时间步之间的依赖关系,可以很好地学习长期依赖,不容易出现梯度消失问题。

3. 训练更稳定。Transformer的非循环结构使其训练过程更加稳定,不容易出现梯度爆炸问题,参数选择也更加灵活。

4. 参数更少。相比RNN和LSTM,Transformer需要的参数更少,尤其在更长序列的任务中参数量的差距更加明显。

5. 无需标定的输入输出。Transformer无需在序列两端添加特殊的开始和结束标记。

Transformer通过注意力机制和非循环网络结构,实现了并行计算和长期依赖学习,训练过程更加稳定,参数量也更少,这使其在很多序列建模任务中展现出强大的性能。

Transformer核心“自注意力机制”:

自注意力机制解决的主要问题是序列建模中的长期依赖学习问题,它是Transformer中间的核心之一。

在序列建模任务中,模型需要建模序列中任意两个元素之间的依赖关系,这需要获得整个序列的上下文信息。但是,当序列越长,两个元素之间的距离越远时,这种依赖关系会越难建模,这就是长期依赖学习问题。

循环神经网络中的梯度会随着序列长度的增加而消失或爆炸,难以建立较远元素之间的联系,无法有效学习长期依赖。

自注意力机制解决了这个问题。它允许模型直接建立序列中任意两个元素之间的联系,获得整个序列的上下文信息。通过加权求和的方式,它可以关注相关元素,忽略无关信息,灵活地建模输入序列中的依赖关系。

具体来说,自注意力机制为输入序列的每个元素赋予一个查询向量、键向量和值向量。然后它计算每个元素的键向量与其他所有元素的查询向量的内积,得到注意力权重。最后用注意力权重加权求和所有值向量,得到该元素的上下文向量。

这个过程为每个元素提供了整个序列的上下文信息,建立起输入序列中任意两个位置之间的联系,有效地建模长期依赖关系。

所以,自注意力机制的主要优点是:

1. 可以并行计算序列中的依赖关系,获得整个序列的上下文信息。

2. 灵活地建模序列中的长期依赖,不易出现梯度消失问题。

3. 实现稳定高效的序列学习。

自注意力机制通过并行结构和灵活的依赖建模,很好地解决了长期依赖学习这个序列学习中一直存在的难题,使得Transformer等注意力模型在各个序列建模任务上对比传统RNN/LSTM对比取得了很好的效果。

(具体对内部构成更详细内容可以参考之前文章《理解ChatGPT之Transformer工作原理》)

Transformer分层逻辑

需要理解整个代码,先大概了解一下Transformer的核心架构图(以论文图片范本进行中文翻译)

在这个架构体系下面,主要分为三层:

  • Inputs Process:输入处理
  • Transformer Block - Encoder:编码层
  • Transformer Block - Decoder:解码层

Transformer通过Encoder学习输入序列的表征,Decoder依据此生成输出,实现了端到端的序列学习。

Transformer模型的核心主要包含Input Embedding、Encoder、Decoder等三部分,然后构成Transformer包含以下几部分:

Inputs Process 层:

  • Inputs Tokenize:把输入内容进行Tokenize之后变成一个个token
  • Input Embedding:将词转为词向量,为后续的Transformer层提供词序矩阵表示。
  • Positional Encoding:添加位置编码来为序列中的每个词编码相对的和绝对的位置信息。

Transformer Block - Encoder(编码器):

可以学习输入序列的表征,这些表征包含输入序列的特征和依赖关系信息。用于处理输入序列,编码器可以学习输入序列的表示,包含多个相同的层,每个层包含:

  • Multi-headed Attention(多头注意力层):通过不同的注意力函数并拼接结果,提高模型的表达能力,主要计算词与词的相关性和长距离次的相关性。
  • Normalization layer(归一化层):对每个隐层神经元进行归一化,使其特征值的均值为0,方差为1,解决梯度爆炸和消失问题。通过归一化,可以将数据压缩在一个合适范围内,避免出现超大或超小值,有利于模型训练,也能改善模型的泛化能力和加速模型收敛以及减少参数量的依赖。
  • Feed forward network(前馈神经网络):对注意力输出结果进行变换。
  • Another normalization layer(另一个归一化层):Weight Normalization用于对模型中层与层之间的权重矩阵进行归一化,其主要目的是解决梯度消失问题

Transformer Block - Decoder(解码器):

可以根据Encoder的表征和上下文生成输出序列,包含多个解码层,每个解码层包含 Masked 多头注意力层、多头注意力层和前馈神经网络。解码器可以根据输入和上下文生成输出序列,Decoder其实跟Encoder很像,除了mask处理部分,每层包含:

  • Masked(掩码):遮盖未生成的输出词,防止模型提前看到输出。
  • Multi-head attention(多头注意力层):聚焦编码器输出,将其与解码器的中间表示相结合。
  • Feed forward network(前馈神经网络):对注意力输出结果进行变换。
  • Normalization layer(归一化层):对每个隐层神经元进行归一化,使其特征值的均值为0,方差为1。

我们下面按照这三大层来进行使用Python的大概描述原理和具体代码实现。

注意,一下代码只是为了方便理解整个技术机制,不意味着跟Open AI等GPT实现代码一致,更多只是给一个学习参考。

一些基本概念介绍

为了方便理解后续的技术理解,有一些基本的概念等先简单同步一下,方便对更多复杂内容的理解。

神经网络及层数(Neural Network Layer)

深度学习主要是采用神经网络的方式来进行机器学习实现。

神经网络是一种模拟人脑神经元之间相互连接的数学模型。它由多层神经元组成,每一层都包含多个神经元,每个神经元都与上一层和下一层的神经元相连。

神经网络的层数指的是神经元按照一定的顺序排列后的层数,通常包括输入层、隐藏层和输出层。其中输入层接收外部输入信息,输出层输出最终的结果,隐藏层则在输入层和输出层之间进行信息处理和传递。

不同的神经网络模型具有不同的层数和结构,例如深度神经网络(Deep Neural Network,DNN)通常包含多个隐藏层,而卷积神经网络(Convolutional Neural Network,CNN)则包含卷积层和池化层等特殊的层级结构。

再举例说,比如从LSTM发展过来的Transformer框架,其中LSTM(Long Short-Term Memory)是一种常用于序列建模的神经网络模型,通常会包含多层LSTM单元。多层LSTM模型的每一层都包含多个LSTM单元,每个LSTM单元都包含输入门、遗忘门和输出门等组件,可以对序列中的长期依赖关系进行建模。多层LSTM模型可以通过增加网络深度来提高模型的表示能力和泛化能力,但也会增加模型的训练难度和计算复杂度。在实际应用中,多层LSTM模型的层数通常在2~4层之间,可以根据具体任务和数据集的特点进行调整。

向量( Vector)

深度学习中,向量又称为 vector或Vector,一般是采用 int 或 float表示的一组数字。 深度学习中的embedding是指由 embedding 技术从离散变量(如图片、视频、音频、自然语言等各种非结构化数据、结构化文字)转变而来的连续向量。embedding一般会采用vector的格式来进行存储,在数学表示上,embedding是一个由浮点数或者二值型数据组成的 n 维vector(比如表示字符的768维)。

神经网络中常提到的向量主要包括:

1. 权重向量(Weight Vector):连接两层神经元的权重参数,它决定上一层的输出如何映射到下一层的输入。

2. 偏置向量(Bias Vector):神经元节点的偏置参数,它决定神经元的基准活跃值。

3. 输入向量(Input Vector):输入到神经网络的数据,它表示一个样本中的特征。

4. 输出向量(Output Vector):神经网络的预测或推理结果,用于分类或回归任务。

5. 隐状态向量(Hidden State Vector):神经网络隐含层的输出,在循环神经网络中会不断更新。

所有的这些向量都表示为一个数字列表或数组,如[1, 2, 3]。它们在神经网络的推理和学习过程中流通,并根据模型的参数进行变换和映射。

通过现代的向量转化技术,比如各种人工智能(AI)和机器学习(ML)模型,可以将非结构化数据抽象为 n 维特征向量空间的向量。这样就可以采用最近邻算法(ANN)计算非结构化数据之间的相似度。

向量相似度检索,是指将目标对象与数据库中数据进行比对,并召回最相似的结果。同理,向量相似度检索返回的是最相似的向量数据。近似最近邻搜索(ANN)算法能够计算向量之间的距离,从而提升向量相似度检索的速度。如果两条向量十分相似,这就意味着他们所代表的源数据也十分相似。

Embedding和Word Embedding(嵌入/词嵌入)

Embedding是一种将高维稀疏数据映射到低维密集数据的技术。它的目的是产生更加平滑和相关的数据表示,便于模型的处理。

Embedding可以作用于各种数据,如:

- 词向量(Word Embedding):针对文本数据,将单词映射到向量。

- 项向量(Item Embedding):针对推荐系统中的商品等进行映射。

- 用户向量(User Embedding):针对用户进行特征映射。

- 知识图谱向量(KG Embedding):将知识图谱中的实体和关系映射到向量。

Embedding是一种广义的高维到低维映射技术,可以产生更加平滑和相关的数据表示,从而提高模型的训练效果。

Word Embedding是Embedding技术的一种具体应用,它是指词向量的学习技巧。通过Word Embedding,我们可以将词汇表中的每个单词映射到一个低维空间中的语义向量中。

这些语义向量可以捕捉单词之间的语义关系,实现诸如“语义接近的单词 embedding 也接近”的特性。这使得模型更加轻易地处理和理解文本数据。所以,简而言之:

Embedding:广义的高维到低维映射技术,可以作用于各种数据。

Word Embedding:Embedding技术的一种应用,指的是学习词向量的技巧,可以为每个单词学习一个语义向量。

Word Embedding是Embedding在自然语言处理的一种应用,而Embedding是更加广义和基础的概念。Word Embedding需要依赖于大量文本进行训练,而Embedding可以作用于任意类型的数据。

为了更好的理解Word Embedding,举个例子,我们有两个单词:“猫”和“狗”。如果我们要表达它们之间的相似性,可以将它们映射到二维空间中:

猫 -> (0.8, 0.2)

狗 -> (0.7, 0.4)

可以看到,这两个单词的向量很接近,这表示它们在语义上相似。

而对于“猫”和“车”:

猫 -> (0.8, 0.2)

车 -> (0.2, 0.8)

两个向量遥遥相反,表示这两个单词在语义上不太相似。

所以,Word Embedding允许我们以低维的向量表示高维的one-hot向量。这可以将向量空间的维度从几万维降到几百维,并且保留了单词之间的语义关系。

Word Embedding的作用主要有:

1. 降维:one-hot编码数据稀疏,无法有效训练模型。Word Embedding可以将高维的one-hot向量压缩为低维的密集向量。

2. 语义表达:语义相近的单词对应的嵌入向量也更加接近,这反映了单词之间的语义关系。

3. 平滑数据:one-hot编码的单词之间完全无关,Word Embedding可以将数据变得更加平滑,单词之间存在一定的关联。

4. 提高模型效果:Word Embedding可以作为神经网络的输入,这通常可以带来更好的训练效果。

所以,Word Embedding 是一种用于 Natural Language Processing 的技巧,它可以从大量文本中学习到单词的语义表达,并以低维嵌入向量的形式表示。这使得神经网络更易于处理文本数据,并可以产生更好的效果。

Softmax(Softmax函数/原始数据归一化)

在深度学习中,Softmax 是一种激活函数,用于对网络的输出结果进行归一化处理。Softmax 的功能是将网络的输出转换为概率分布。它可以将一个列表的数值压缩到0到1之间,并使它们的总和为1。Softmax 的公式为: softmax(x) = exp(x) / Σexp(x)

这里x代表网络的原始输出,exp(x) 对每个值进行指数运算,之后除以所有值的和,得到归一化的概率。

举个例子,如果网络有3个输出,原始值为[1, 2, 3],则:

exp(1) = 2.718

exp(2) = 7.389

exp(3) = 20.085

sum = 2.718 + 7.389 + 20.085 = 30.192

softmax = [2.718/30.192, 7.389/30.192, 20.085/30.192]

= [0.09, 0.25, 0.66]

我们可以看到,Softmax将原始输出压缩到0-1范围内,并且使得它们的总和为1,代表概率。Softmax 主要用于神经网络的分类输出。因为它可以将原始预测值转换为各个分类的概率,这更加符合分类的要求。并且,Softmax 具有归一化的效果,可以抑制网络输出中的极值,使得最终的概率值更加均衡,这有助于模型的稳定性。所以,简单地说,Softmax 是一种激活函数,它通过对神经网络的输出进行指数运算和归一化,将其转换为各分类的概率分布。这在分类问题中起到很重要的作用。

Loss(损失函数)

在深度学习中,Loss(损失)通常指的是模型预测值与实际值之间的差距,用于度量模型的性能。Loss函数则是用来计算Loss的函数,通常也称为目标函数、代价函数或误差函数。损失函数是用于评估神经网络预测结果与真实标签的差异程度的函数。它衡量着模型的拟合能力和精度。

神经网络的训练目标就是最小化损失函数的值。因为损失函数值越小,表示模型预测与真实结果的差异越小,模型质量越高。

在训练深度学习模型时,我们通过将输入数据输入到模型中,得到模型的预测值,然后将预测值与实际值进行比较,计算出Loss。然后,我们使用反向传播算法来调整模型的参数,使得Loss最小化,从而提高模型的性能。

常用的Loss函数包括均方误差(Mean Squared Error,MSE)、交叉熵(Cross Entropy)、对数损失(Log Loss)等。不同的任务和模型通常需要选择不同的Loss函数,以最大程度地反映模型的性能。

梯度下降和梯度爆炸(Gradient Descent/Gradient Explosion)

梯度是神经网络中一个很重要的概念。它指的是网络中某个参数对损失函数的影响程度。

通俗地说,梯度表示网络中的参数微小变化,会导致损失函数值的变化量。它衡量参数变化对损失函数的影响力。

举个简单的例子。假设我们有一个神经网络,其中一个参数w = 0.5。此时,损失函数L的值是0.3。如果我们将w增加0.1,也就是变成w = 0.6,此时损失函数L变为0.32。那么这个参数w对损失函数的梯度就是(0.32 - 0.3) / 0.1 = 0.2。它表示,当w增加0.1时,损失函数L增加了0.2。这个0.2就是w对损失函数的梯度。

梯度下降的意思是在神经网络训练的目的是找到一组参数,使损失函数最小。为此,我们会不断调整参数,并计算损失函数的梯度(变化率),来判断应该如何调整参数。

梯度下降法就是按照梯度的方向,逐渐调整参数,使损失函数下降,系统最小。这就像一颗小球慢慢滑落到山谷,找到最低点。

但是,如果学习率设置过大,那么每次参数调整的幅度就会很大。这会导致损失函数的值出现极大的波动,甚至出现远离最优解的情况。这就像小球按一定方向猛然滚下山坡,很难找到山谷。当损失函数值因参数调整而出现极大变化,甚至开始不收敛时,就称为梯度爆炸。这会使训练失败,无法找到最优解。

为了避免梯度爆炸的问题,通常可以采用一些技巧,如减小学习率、权重衰减、梯度裁剪(Gradient Clipping)等。梯度裁剪是一种常用的技巧,它通过限制梯度的范数,使得梯度的大小不会超过一个预定的阈值,从而避免梯度爆炸的问题。

归一化(Norm/Normalization)

归一化是深度学习中一个非常重要的概念。它指的是对数据或参数进行处理,使其分布更加均匀地落在一个范围内。

归一化的目标主要有两个:

1. 避免数据过大或过小,影响模型训练。如果数据范围太大,模型的参数更新会很小,收敛慢。如果数据范围太小,参数更新幅度太大,导致训练不稳定。

2. 避免某些特征由于数据范围太大,过于主导模型训练。如果不同特征的数据范围差异过大,那么范围更大的特征将更加影响训练结果。这使得模型难以捕捉到其他特征的信息。

所以,归一化的目的是 rescale 数据,使其分布在一个合适范围内,通常是0-1或-1-1之间。这可以解决上述两个问题,产生更加理想的训练效果。

具体而言,归一化可以作用在:

1. 输入数据:对输入特征进行归一化,使其均值为0,标准差为1。这样可以解决特征范围差异过大的问题。

2. 权重参数:对模型权重参数进行归一化,使其绝对值不会过大。这可以产生更加平稳的训练,避免梯度爆炸。

3. 梯度:在反向传播过程中,对梯度进行归一化,避免其变化过大。这也有助于稳定训练过程。

4. Batch Normalization:对批量数据在进入下一层神经网之前进行归一化。这可以加速训练,使模型更加健壮。

归一化技巧在深度学习中广泛使用,它使得模型更加稳定高效。简单来说,归一化的目的就是控制数据在合适的范围内,避免极值出现,产生更加理想的训练效果。

Softmax 和归一化都是深度学习中常用的技巧,Softmax和归一化相同的地方:

1. 都可以将数据映射到0-1范围内。Softmax通过指数运算和除法实现,归一化通过减去均值和除以标准差实现。

2. 都具有归一化的效果。可以抑制数据中的极值,使结果更加均衡。

Softmax和归一化不同同的地方:

1. 目标不同,Softmax用于产生概率分布,常用于分类问题的输出层。归一化更加广义,可以作用于输入、参数、梯度等,目的是产生更加稳定的训练。

2. Softmax依赖于数据本身,并不改变数据的分布。它只在输出时根据数据计算概率。而归一化则在数据上进行运算,直接改变了数据的均值和方差。

3. Softmax只能用于分类任务,不能产生连续的值。归一化的结果可以是一个连续范围内的值。

4. Softmax通过rival sum使得输出总和为1。归一化没有这个约束,输出的总和不一定为1。

举例描述二者的不同:

Softmax 输入: [1, 2, 3]

输出: [0.09, 0.24, 0.67]

归一化 输入: [1, 2, 3]

输出: [-1, 0, 1] (假设归一化到-1到1区间)

我们可以看到,Softmax 产生的输出是概率,总和为1。而归一化的输出是一个连续范围的值,总和不为1。

取代你的不是AI,而是比你更了解AI和更会使用AI的人!

##End##

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux远程服务器和本地服务器相互之间传输文件方法SSH(乌班图系统)

提前知道几点: 1.使用的MobaXterm软件 2.访问本地、远程服务器,需要账号和密码(远程机器上的用户名和密码 远程机器的 IP 地址或主机名(在同一子网上))。每次访问需要输入密码,可以搜Ubuntu SS…

8、gateway使用和原理

一、什么是Spring Cloud Gateway 1、网关简介 网关作为流量的入口,常用的功能包括路由转发,权限校验,限流等。 2、Gateway简介 Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关框架,定位于取代 Netflix Zuul。相比 …

QT禁用窗口【关闭】按钮的实现方法

QT禁用窗口关闭按钮的实现方法,直接在窗体类构造函数的内部写入setWindowFlags(Qt::CustomizeWindowHint | Qt::WindowMinimizeButtonHint | Qt::WindowMaximizeButtonHint)即可实现,案例如下: #include "form.h" #include "…

Nginx Linux设置开机自启动

使用如下命令 vi /lib/systemd/system/nginx.service 创建并编辑文件将以下代码黏贴至此文件中 [Unit] Descriptionnginx Afternetwork.target[Service] Typeforking TimeoutSec0 #防止启动超时 Userroot Grouproot criptionnacos Afternetwork.target[Service] Typeforking T…

Visual Studio 自定义的颜色字体不生效

问题描述: 1、dll1中引用第三方库的类不识别,颜色黑白,自定义颜色不生效;定义的是结构体 2、在dll2引用另一个dll1中的结构体。结构体不识别,今天成员函数cpp中自定义颜色不生效。 问题解决方式: 全部清…

业务变革与架构双驱动的多项目管理︱海康威视流程变革咨询顾问张燕飞

海康威视数字技术股份有限公司流程与变革管理部流程变革咨询顾问张燕飞女士受邀为由PMO评论主办的2023第十二届中国PMO大会演讲嘉宾,演讲议题:业务变革与架构双驱动的多项目管理。大会将于8月12-13日在北京举办,敬请关注! 议题简要…

[Volo.Abp升级笔记]使用旧版Api规则替换RESTful Api以兼容老程序

Volo.Abp 配置应用层自动生成Controller,增删查改服务(CrudAppService)将会以RESTful Api的方式生成对应的接口 (官方文档),这与旧版本的Abp区别很大。RESTful固然好,虽然项目里新的Api会逐步使用RESTful Api代替旧的&…

STM32单片机OLED智能饮水机童锁自动出水补水加热水位检测

实践制作DIY- GC0159--OLED智能饮水机 基于STM32单片机设计---OLED智能饮水机 二、功能介绍: 电路组成:STM32F103CXT6最小系统 OLED显示器DS18B20检测水温1个继电器模拟加热1个继电器模拟补水1个继电器模拟出水水位传感器超声波测距多个按键&#xff08…

81. 正则表达式

一、概述二、匹配单个字符三、匹配一组字符四、使用元字符五、重复匹配六、位置匹配七、使用子表达式八、回溯引用九、前后查找十、嵌入条件参考资料 一、概述 正则表达式用于文本内容的查找和替换。 正则表达式内置于其它语言或者软件产品中,它本身不是一种语言或…

【docker】部署svn服务器,docker安装部署svn服务器

话不多说直接上步骤! 1.下载镜像,创建容器 # 下载镜像 docker pull elleflorio/svn-server # 创建svn仓库目录,进入svn仓库目录 mkdir -p /var/svn # 创建svn服务容器,把容器中的svn仓库映射到本机,并映射3690端口 d…

设计模式-单例模式

面向对象语言讲究的是万物皆对象。通常流程是先定义可实例化类,然后再通过各种不同的方式创建对象,因此类一般可以实例化出多个对象。但是实际项目开发时,我们还是希望保证项目运行时有且仅包含一个实例对象。这个需求场景的出发点包括但不限…

Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2

Unity游戏源码分享-射击游戏Low Poly FPS Pack 3.2 项目地址:https://download.csdn.net/download/Highning0007/88057717