天天看点

看了这篇你还不懂BERT,那你就过来打死我吧

目录

1. Word Embedding. 1

1.1 基于共现矩阵的词向量... 1

1.2 基于语言模型的词向量... 2

2. RNN/LSTM/GRU.. 5

2.1 RNN.. 5

2.2 LSTM 通过门的机制来避免梯度消失... 6

2.3 GRU 把遗忘门和输入门合并成一个更新门... 6

3. seq2seq模型... 8

3.1 朴素的seq2seq模型... 8

3.2 attention机制... 9

4. Transformer. 10

4.1 自注意力... 11

4.2 普通attention. 15

4.3 位置编码... 16

4.4 残差模块... 16

4.5 Decoder Mask. 17

5. ELMO.. 18

5.1 模型结构与原理... 19

5.2 使用... 20

5.3 局限:... 20

6. OpenAI GPT. 21

7. BERT. 22

7.1 BERT 输入表示... 23

7.2 Mask LM:... 23

7.3 怎么学习两个句子的关系:... 23

7.4实战... 25

2018年是NLP领域巨变的一年,这个好像我们都知道,但是究竟是哪里剧变了,哪里突破了?经常听大佬们若无其事地抛出一些高级的概念,你却插不上嘴,隐隐约约知道有这么个东西,刚要开口:噢!你说bert啊,我知道,就是一个预训练模型,然后,然后。。。然后就没有然后了。

好的,闲话少说,我们来总结一些高级的概念,究竟从何说起呢?首先要推荐大家读一读,https://zhuanlan.zhihu.com/p/49271699,张俊林大佬的解读。本文的主要目标力求把以下几个概念讲清楚:

  1. Word Embedding
  2. RNN
  3. Seq2Seq
  4. Transformer
  5. BERT

1. Word Embedding

首先,说到以上这些概念,不得不提词向量。而词向量,顾名思义,就是把词向量化,这样有神马好处我们就不多说了,获得词在向量空间的表示,可以度量词之间的距离,相关度等等……我们一般有两种获取方式,一种是比较传统的,基于共现矩阵,SVD等等,另一种就是语言模型。

1.1 基于共现矩阵的词向量

基于共现矩阵的意思,就是统计指定窗口大小各个词的共现频次,这样的思想就是:可以用某个词的周边词,来表示这个词。

比如以下三句话:

I like deep learning.

I like NLP.

I enjoy flying.

表示成共现矩阵:

看了这篇你还不懂BERT,那你就过来打死我吧

那我们不禁要问一句,为什么不用one-hot的方法来做呢,其实你可以想象,用one-hot来做,每个词之间的相似度似乎都是0,而且超级稀疏,那还玩个毛线啊,词之间的关联一点都刻画不出来,这对文本分析是个致命的问题。现在出现这么个共现矩阵,你说这下该可以坐享其成了吧,其实不是,这个的问题也很明显,因为,还是很稀疏呀,什么意思呢,就是说,Corpus里面有多少词,你就要多少维??那显然会有维度灾难,并且也很稀疏。

好的,你说高维稀疏那么就好办呀,咱们的那些降维的技术拿出来呀,一个很经典的方法就是SVD,那么SVD做了什么工作呢,这里的SVD就不展开讲了。

我们只要知道其基本的做法

看了这篇你还不懂BERT,那你就过来打死我吧

现在X就是共现矩阵,分解之后,得到正交矩阵U,U归一化后作为现在每个词的词向量。

现在得到了词的稠密矩阵,语义相近的词在空间的距离很近,甚至一定程度达到了反映词之间的线性关系的效果。

以上是基于词的共现矩阵来获取词向量,这种方法在推荐系统里面也经常用,可以获取item和item之间的相似关系。

1.2 基于语言模型的词向量

大家公认基于语言模型的词嵌入技术是在2003年Bengio提出的,就是经典的NNLM模型,但是又在2013年因为word2vec技术重新被认知。那么这些个模型,究竟有什么神奇的地方呢?

这个推荐大家看看《word2wec背后的数学原理》这个文章,写得很细致,在这里我们也不去推导其细节。

等等,我们是不是要先说说什么是语言模型?

一句话说:语言模型就是用来衡量一句话多大程度是人话的模型。好吧,这句话就很不人话。

详细一点,一句话概率,我们可以用这句话每个词的联合概率表示,进一步,又可以表示成多个条件概率的乘积。这个乘积越大,它这句话越可能是一句话。

看了这篇你还不懂BERT,那你就过来打死我吧

我推荐看看宗成庆老师的统计自然语言处理,说得很清楚。传统语言模型也有缺点:如只考虑前几个词的依赖,长时依赖的问题无法解决;还有就是和训练的原始数据强相关,如:我要去北京出现过很多次,但是我要去上海出现的次数少,最后预测出的北京的概率是远远大于上海的,那这其实是不科学的,因为这两个都是城市名,其实要求预测出的概率应该较为一致。(不能共享上下文)。

1.2.1 NNLM

首先是NNLM, neural network of language model. 它解决了一部分上面说的上下文共享的问题。这个模型说来也是很有意思。现在咱不是可以根据语料对每个词进行one-hot编码吗,这个模型假设了一个C矩阵(就是一个lookup table),用one-hot 乘以C矩阵,得到的就是该词的嵌入表示,(当然,这里的C矩阵大小维度和one-hot维度有关,也和嵌入后的维度有关。)然后,再用某词的上文词(context)嵌入后的稠密表示,做一个拼接,之后按照神经网络的思路,通过激活函数,然后全连接到原来one-hot的维度上,做一个softmax,就构建了一个神经网络。

说起来很抽象,看下图。

看了这篇你还不懂BERT,那你就过来打死我吧

这里要特别说明一点的就是:这个C矩阵,是一个随机初始化的矩阵,然后可以通过不断的训练,得到最终的嵌入矩阵,神奇的就在这里,我们仅仅是想构建一个神经网络语言模型,根据前面几个词,预测后一个可能是什么词。C矩阵是我们要训练的参数,结果我们却发现,我们的这个C矩阵,就是每个词的嵌入向量表示。

NNLM为何可以解决上下文共享的问题?是这样一个道理:如果“我要去北京”出现了多次,但是“我要去上海”没怎么出现过,但是只要在其他的地方,北京和上海的上下文对类似的,那么“北京”和“上海”的词向量就是类似的,最后预测“我要去”后面是“北京”还是“上海”的概率就是类似的。

如果要做其他的任务,如句法分析等,都会得到词向量,但是那种监督任务的数据宝贵,稀少。语言模型这是个无监督的,数据是大量的,代价小的,得到的词向量比较好。

1.2.2 word2vec

然后也不知道沉睡了多久,也不知还要多久才能睁开双眼。我们的word2vec出现在2013年,满足分布式假设:如果上下文是相似的,语义也是相似的。word2vec有两种训练方式,一种是CBOW: continuous bag-of-words. 一种是skip-gram.

这两个也很有意思,CBOW是根据周围的几个词预测中间的词,skip-gram是根据中间的一个词预测周围的几个词。他们和NNLM有什么关系吗?

有! 那CBOW来说,它相比NNLM,少了隐藏层,而且上下文通过投影层之后,是直接相加的,而我们上面说了,NNLM是通过拼接的方式。我们的CBOW就是奔着词向量去的。当然,这个不仅仅是这一点改进,最牛的还是w2v后面的大量运算的简化思想,这里也不说了,在《word2vec背后的数学原理》讲得十分的清楚,主要是两种手段:1, 层次化的softmax。 2, 负例采样。 至于这里的层次化softmax是怎么样用的哈夫曼树,负例采样的一些采样细节,我们也不在这里说了。

看了这篇你还不懂BERT,那你就过来打死我吧

然后说得十分闹热,这个词向量我们已经获得了,那为什么不能把这个看成是一个预训练模型呢,其实是可以的,就和图像领域的预训练似乎差不多了,而且也有两种打开方式:一种是frozen, 另一种当然就是fine-tune. 说到这里,我们还是要指出word2vec的不足,就是,这个模型,不能够很好地做歧义消解,怎么说呢,摊牌了,就是说这是一个静态向量:那我们在谈论苹果的时候,我们究竟在谈论什么?对的,我们在谈论乔布斯的iPhone,还是谈论砸中牛阿顿的那个东西?很显然,无从得知,因为你的embedding是固定的,没法区分,w2v把所有的意思都给学到了一个向量里面。所以才会有动态词向量这个概念产生。顾名思义,动态词向量,就是能够动的词向量,动体现在,随着上下文的不一致,我们产生的词向量也是不一致的,这好理解吧,就好比说,我们看苹果出现的时候,它周围出现的是乔布斯,还是牛阿顿,是一款,还是一斤?

2. RNN/LSTM/GRU

由于语义是上下文相关的,如果要强行用一个向量编码,只能编码到一个向量里面。我们都知道RNN是带有记忆的网络,上下文的语义可以通过RNN等来建模。

2.1 RNN

看了这篇你还不懂BERT,那你就过来打死我吧

存在梯度消失的问题。

看了这篇你还不懂BERT,那你就过来打死我吧

2.2 LSTM 通过门的机制来避免梯度消失

看了这篇你还不懂BERT,那你就过来打死我吧

2.3 GRU 把遗忘门和输入门合并成一个更新门

看了这篇你还不懂BERT,那你就过来打死我吧

可以做的任务:

  1. 序列标注
看了这篇你还不懂BERT,那你就过来打死我吧
  1. 文本分类
看了这篇你还不懂BERT,那你就过来打死我吧

但是还有一些任务做不了,比如机器翻译:我们不能要求输出句子一对一吧,也不能要求长度要一致,所以才出现了seq2seq模型。

3. seq2seq模型

3.1 朴素的seq2seq模型

需要两个RNN,一个是encoder,一个decoder。

看了这篇你还不懂BERT,那你就过来打死我吧

可用于翻译、摘要、问答和对话系统。

如翻译系统,可以认为是一个句子经过encoder,然后得到一个context向量,这个向量编码了整个句子的信息,然后由这个context向量,和每个时刻decoder的隐状态一起,输入到decoder解码出翻译出来的句子。(attention和transformer是对seq2seq的改进)

看了这篇你还不懂BERT,那你就过来打死我吧

Seq2seq也有问题,比如我们仅仅用一个向量试图编码整个语义,这其实很容易看出来比较简单粗暴,像我们人一样,如果读了一篇文章,脑子里有大概的印象,但是如果问一个特定的问题,还是需要在原文中去寻找对应匹配的内容的。所以这就出现了attention机制。

这种具有比较好的监督数据,一般能够得到比较好的句子的编码,表现不错,但是更多的任务需要无监督的词向量编码方法。所以出现了ELMO,GPT,BERT等。

3.2 attention机制

看了这篇你还不懂BERT,那你就过来打死我吧

可以看成是decoder隐状态向量ht,要从encoder中提取出更详细的信息,于是可以把ht和前面encoder中的隐状态向量hs做内积,得到的就是各个hs的得分(权重),再乘以各个hs,得到的context vector和原来的ht拼接起来,就得到了最后的向量。

可以看成是翻译中平行语料各个词的对齐过程。在翻译任务中学习到了对齐的关系。

RNN的问题:

  1. 时序依赖,无法并行
看了这篇你还不懂BERT,那你就过来打死我吧
  1. 单向信息流
看了这篇你还不懂BERT,那你就过来打死我吧

在这种语境下,如果是从头开始读句子的话,是无法知道it的指代的。

那么编码一个词的时候,其实是需要参考整个上下文的。

如何解决?

* Attention?

前面提到的Attention,好像是需要解码层那边的信息,来完成编码,这就出现了自注意力,可以把普通的attention看成是需要外部驱动的,那么self attention就是个自驱动的。

看了这篇你还不懂BERT,那你就过来打死我吧

Self-attention是transformer的一个重要部分。

4. Transformer

这里祭上大名鼎鼎的:Attention is all you need. https://arxiv.org/abs/1706.03762.

要说到这个transformer,不得不提到Encoder-decoder模型,我们知道,Encoder-decoder经常被用来做无监督的特征提取工作。

看了这篇你还不懂BERT,那你就过来打死我吧

当我们把编码组件给剖开,我们看看里面是何方神圣,我们发现编码组件里面包含很多个编码器,官方取的6个,当然你也可以取其他数量。而再细看,每个编码器又分为:self-attention 层和feed-forward层。

看了这篇你还不懂BERT,那你就过来打死我吧

那可以这么说,要弄懂transformer,就得先搞懂这两层:

自注意力层:

说到自注意力,我们都知道,通俗来说:自注意力是为给整个句子所有的单词一定的权重,得到一定的关注。此处,引入self-attention层,当然是为了让每个单词在进行编码时,都能关注要句子的其他单词。

然后自注意力层的输出送到前馈神经网络,注意,这里每个单词对应的前馈神经网络都是一样的。

4.1 自注意力

4.1.1 计算过程

这里抠一抠自注意力层的细节,比如说,现在有一个句子,这个句子输入进自注意力层,详细步骤是怎么样的呢?

看了这篇你还不懂BERT,那你就过来打死我吧

在这里的自注意力层中,是需要每个x去计算当前的z的。但是算r1是可以只根据z1计算出来的。

首先声明一点概念,我们在自注意力层会训练三个权重矩阵,分别是WQ查询矩阵,WK键矩阵,WV值矩阵, 这里有没有想起老大哥NNLM那个午后的C矩阵?

我们训练这三个矩阵,是为了干嘛呢?就是分别乘以初始的词嵌入向量,得到三个向量,分别是q查询向量,k键向量,v值向量。

看图:

看了这篇你还不懂BERT,那你就过来打死我吧

好了,大神画的这个图已经足够一目了然了。

得到了这三个向量,然后干嘛呢,仔细听:当我们要编码X1向量时,肯定是要考虑周围的词的,所以要用q1向量乘以K1向量,用q1乘以k2,分别得到两个得分,而这两个得分softmax归一化后作为权重,乘以各自的值向量。而值向量再求和,得到的就是注意力层该位置的输出!如果参数被训练的足够好,那么这个编码是完全能够体现出自注意力的特性的。

看了这篇你还不懂BERT,那你就过来打死我吧

x1是初始的向量,v可以看成是词的真正的语义。

【理解:普通attention也可以看成是上述过程的一个特例,每个decoder的隐状态就是query向量,key就是encoder的输出,value也是encoder的输出。】

但是呢,这样一个词一个词来,很慢,我们肯定要想办法提高速度,我们发现这个和RNN还不一样,各个词之间输入输出之间没有先后顺序,可以做并行,那么我们就会直接用矩阵的乘法来解决这个事情了:

看了这篇你还不懂BERT,那你就过来打死我吧
看了这篇你还不懂BERT,那你就过来打死我吧

不用惊讶,这里的过程和上面我们说的是一模一样,除了根号dk,是在归一化之前的一个小处理,并不影响。

得到的Z,就是我们的带注意力的词编码,是自注意力层的输出。

4.1.2 多头

进一步我们要多讨论一下,transformer里面有个概念叫多头(当然不是炒股炒汇里面的多头空头啦),multi-headed attention, 这个说的是啥呢,很容易理解,可能各个QKV的任务可能各司其职,比如一个head可能是指代消解的,一个head是考虑上下文的。就是大佬们觉得你一个Q, K, V矩阵好像不够啊,这怎么弄呢,就用多组Q, K, V矩阵,重复以上的操作

看了这篇你还不懂BERT,那你就过来打死我吧

会输出多个Z矩阵,然后再进前馈层,但是前馈层是一样的,可以吧多个Z矩阵合并再进行操作,合并的细节:可以把Z矩阵们拼接,再乘以权重矩阵(主要是为了压缩),得到最终Z矩阵。

看下图,更清晰:

看了这篇你还不懂BERT,那你就过来打死我吧

4.2 普通attention

Transformer中还是包含有普通的attention,因为假如还是一个翻译任务,像seq2seq那样的结构,还是需要有attention,从encoder中去提取出相应的需要的信息的。

所以在解码器中,除了编码器有的这两层,还多出一层编码-解码注意力层:

Encoder最后一个时刻的信息,被引入到decoder中。

看了这篇你还不懂BERT,那你就过来打死我吧

4.3 位置编码

看了这篇你还不懂BERT,那你就过来打死我吧

我们在编码北京的过程,北京变成了向量,然后乘以QKV,矩阵都是确定的,那么两个句子得到的最后的向量也是一样的。

这个transformer模型,没有LSTM的序列关系,那么是不是就意味着,全程都没有表示词的位置的信息?

为了解决这个问题,transformer特地用了一种位置编码的方式,赋予每个位置的单词一个位置编码,然后把这个位置编码和词嵌入相加。

有绝对位置编码(bert/gpt),以及相对位置编码(原始transformer)。

4.4 残差模块

这里有小细节,需要注意,我们看原结构:

看了这篇你还不懂BERT,那你就过来打死我吧

其实这个是简化的,啥意思呢,就是说,有细节没有画出来。

每一个编码器,到前馈神经网络输入的时候,其实是原向量和训练后的向量相加的结果:

看了这篇你还不懂BERT,那你就过来打死我吧

整个过程捋一遍:编码层在训练之后,得到许多个矩阵(Q,K,V),这些矩阵会送到解码层里面,解码的过程,就是每输入一个词,预测出下一个词,直到到达终止符。另外,就像我们对编码器的输入所做的那样,我们会嵌入并添加位置编码给那些解码器,来表示每个单词的位置。

这里的线性变换和softmax,是一个全连接层,从实数向量到一个很长的logits向量,然后softmax。

https://www.jiqizhixin.com/articles/2019-01-09-18,这里有机器之心更详尽的阐述。

4.5 Decoder Mask

在decoder中,未知的词不能够作为输入

看了这篇你还不懂BERT,那你就过来打死我吧

用一个掩码矩阵,翻译第i个词的时候只能看前i-1个词。

总结:

  1. Transformer能够并行,所以能够做得很深。
  2. Transformer解决了双向信息流的问题,至少在encoder端。

没解决的问题:

机器翻译当然没问题,但是其他的任务,没有这么大的监督语料。

所以,症结在这里:

word Embedding无监督学习得到的向量虽然不错,但是缺少上下文;

rnn,LSTM,transformer可以有上下文信息,但是监督的数据太少。

如何解决?

Contextual word embedding:

既可以无监督学习,又要有上下文信息。大佬出现:ELMO!!!!!

5. ELMO

隆重地推出来ELMo, embedding from language models.

看了这篇你还不懂BERT,那你就过来打死我吧

上图的意思是,要用Elmo模型产出词向量,你要先给我上下文。

ELMo的官网:https://allennlp.org/elmo,这个神器Elmo有什么好处呢。首先,这是通过训练一个语言模型实现的,语言模型而已,要找语料岂不是很容易?

5.1 模型结构与原理

看了这篇你还不懂BERT,那你就过来打死我吧

模型如上图,本质还是一样的,通过大一堆语料,通过预测任务学习出这样一个模型,pretrain就完成了。

接下来如何做一个任务,如情感分类。是这样的:首先来了一个句子,那么利用这个多层的双向的LSTM把每个词都进行编码,每个词得到一个向量,双向多层的就是2N个向量,然后加权组合,得到新的向量,这个向量作为特征,下游的任务该怎么做就怎么做。

另外,据说可以处理单词用法的复杂特性,如句法和语义(为什么可以?是由于ELMo的输入是字母为单位,而不是单词,所以这对于oov未登录的单词也有很好的效果)。它和word2vec的最大不同就是,它提供了一个动态的词向量,根据上下文的不同,模型产出的词向量是不同的。

说一说Elmo模型的结构,

看了这篇你还不懂BERT,那你就过来打死我吧

它是基于深度双向语言模型(biLM)训练的,即,通过双向的LSTM来提取文本特征,根据它们的内部状态学习到的函数,作为词向量。

这里还是说一下双向的意思,通俗的说,就是把文本正向反向都提取一遍,这个叫双向。上面的biLSTM内部结构可以再细化一点,就是下面这个样子,经过一个biLSTM之后,输出的是正向的和逆向的拼接向量。

ELMo的贡献还在于,它发现,在深层的RNN模型中,不同层次的RNN提取到的特征是有差异的。所以ELMO提出,赋予不同的层次一个可训练的参数,这个参数作为权重,以方便在做下游任务时,用这个加权和的词向量能更好适应任务。

看了这篇你还不懂BERT,那你就过来打死我吧

5.2 使用

Elmo可以通过pip install allennlp来安装,也可以download GitHub上的源码来做[https://github.com/allenai/allennlp],当然这个项目是构建在pytorch上面的。

这个模型还可以自如的使用:

看了这篇你还不懂BERT,那你就过来打死我吧

5.3 局限:

这个模型看起来十分不错,但是就是速度太慢了。加上我们站在这里来往前看历史,我们发现,LSTM的特征抽取的能力偏弱,而Google提出的transformer抽取器,则是一个更牛逼的存在。

还有一个问题,就是学习到的向量,不一定适合特定的任务,因为每个任务(只要句子是相同的)学习到的向量都是一样的。所以OpenAI GPT做了改进。

看了这篇你还不懂BERT,那你就过来打死我吧

6. OpenAI GPT

OpenAI GPT 如何做一个语言模型???

由于transformer有encoder和decoder,这当时是在你做类似翻译的任务所必须的,但是现在我们做语言模型,只有一个句子,不是句对,怎么办?

OpenAI GPT 用的就是没有encoder的Transformer!当然会用多层stack。

看了这篇你还不懂BERT,那你就过来打死我吧

那么我们怎么fine-tuning呢?

强行把所有的任务改造成一个句子,喂入transformer。

看了这篇你还不懂BERT,那你就过来打死我吧

优点:可以训练更深,效果也很好。

缺点:单向信息流的问题。

看了这篇你还不懂BERT,那你就过来打死我吧

为什么会有这个问题?其实我们在给一个句子分类任务的时候,我们出的句子是完整的,对不对,但是仅仅是由于这个模型的局限,使得在训练的时候,只能看前面的信息,这是最大的问题。

另外,pretrain 是一个句子,任务时有可能有两个。

有没有办法,能够使得模型能关注到整个句子??

BERT!

7. BERT

Bert 的方案:

  1. 解决单向信息流:

Mask LM

  1. NSP Multi-task Learning 搞成两个句子的多任务,解决不匹配的问题。
  2. Encoder again

7.1 BERT 输入表示

看了这篇你还不懂BERT,那你就过来打死我吧

7.2 Mask LM:

这就换了个任务,就不是从前面的词预测后面的词,而是完形填空的任务,(这其实也是几十年前就有了)。

7.3 怎么学习两个句子的关系:

随机0.5概率抽取连续的句子,预测1,0.5的负例,预测0。那么这样就可以学习出两个句子之前的关系(对问答,或者句子包含关系的任务都很有用)。通过这样的多任务:Mask LM,next sentence prediction,可以学习到一个很好的模型。

如何使用呢?

1. 单个句子的任务,如分类。

看了这篇你还不懂BERT,那你就过来打死我吧

选第一个词的embedding,作为整个句子的表示。不能用其他的词,因为只有第一个词[CLS]是没有意义的,他的意义就来自于其他的词!真的是妙。

2.两个句子的任务

看了这篇你还不懂BERT,那你就过来打死我吧

两个句子的分类也是一样的。

3.问答

看了这篇你还不懂BERT,那你就过来打死我吧

预测这个词是否是答案的开始和结束。

4. 序列标注

看了这篇你还不懂BERT,那你就过来打死我吧

7.4实战

Google预训练好的模型:

看了这篇你还不懂BERT,那你就过来打死我吧

7.4.1 Fine-tuning:

看了这篇你还不懂BERT,那你就过来打死我吧

7.4.2 Pretraining:

  1. 数据预处理
看了这篇你还不懂BERT,那你就过来打死我吧

2. Pretraining:

看了这篇你还不懂BERT,那你就过来打死我吧

好了,到了最后总结的时候了,其实也无多要总结的。

就是这么奇妙,你信心满满地要进军自然语言处理,然后踌躇满志地啃最牛逼的模型,我要那种最新的,最强的,什么?

听说目前BERT是最屌的,然后你就要去啃BERT了,但是你要啃BERT,你不清楚transformer怎么行?

好!那就从transformer开刀,你要啃transformer,你得弄清楚encoder-decoder模型吧?这个模型是怎么来的呀,噢,查资料,你知道是为了从seq2seq来的,那怎么会有seq2seq模型呢?

哦哦我们知道了是为了解决如翻译等序列到序列的任务提出来的,那seq2seq是怎么工作的?然后知道他最先是两个RNN(LSTM/GRU)组成的。

那你不得去看看RNN,为啥要RNN,是因为在词向量编码时我们希望有上下文的信息呀。

那不要上下文行不行,也行,就是word2vec,那word2vec的历史也得扒一扒。

词向量的历史,往前扒,一直到2003年的NNLM,以及之前的one-hot,以及共现矩阵的词向量,太多太多了,把NLP的祖宗十八代都扒出来了,所以最后发现。

又回到最初的起点,所以学习知识,是在积累,从基础的一步一个脚印过来吧,要仰望星空,关注前沿发展,也要脚踏实地,夯好基础,理解透彻!!

继续阅读