CS224N WINTER 2022(五)Transformers详解(附Assignment5答案)

lecture 9 Transformers

Transformer是对自然语言处理研究领域的一场革新,几乎目前NLP中所有的先进模型都离不开Transformer。典中典的Attention Is All You Need,很多人都有写过Transformer的原理解析,这里不赘述。

lecture 10 更多关于Transformers的内容以及预训练

本次作业不确定性较大,因为缺少计算资源无法完全跑通所有代码。

  • ( a ) (a)(a ) 提示:参考[slides]中注意力机制的相关内容。
  • ( 1 ) (1)(1 ) 作业中式( 1 ) (1)(1 )已经写得很明白了,这是一个模糊查询,我们不能直接通过查询向量q q q精确匹配到某个键向量k k k,只能赋予每个键一定的概率分布权重(即α i j \alpha_{ij}αi j ​),得到最终的输出结果。
  • ( 2 ) (2)(2 ) 根据式( 2 ) (2)(2 )的计算方法,如果查询向量q q q与某个键k i k_i k i ​的相似度非常高(点积值很大),且q q q与其他的键基本垂直(点积值为零),那么就会使得α i \alpha_i αi ​极大。
  • ( 3 ) (3)(3 ) 此时c c c基本近似等于v i v_i v i ​
  • ( 4 ) (4)(4 ) 直觉上就是单词的表示越相近,注意力权重就会越高,得到的注意力输出就越接近那个单词。(感觉在把一句废话换着方式说了好几遍)
  • ( b ) (b)(b ) 只考虑两个值向量的特殊情况,探究注意力机制的深层含义。
  • ( 1 ) (1)(1 ) 有人可能会觉得如果只是将值向量根据注意力得分取加权和,很难从这个结果中挖掘原先值向量的信息,事实上不然,但是这里做了一个非常强的假定,即两个值向量v a , v b v_a,v_b v a ​,v b ​是来自相互垂直的向量空间的:
    v a ∈ span { a 1 , a 2 , . . . , a m } ⇒ v a = ∑ i = 1 m c i a i v b ∈ span { b 1 , b 2 , . . . , b p } ⇒ v b = ∑ j = 1 p d i b i where { a i ⊤ b j = 0 ∀ i = 1 , . . . , m ; ∀ j = 1 , . . . , p a i ⊤ a j = 0 ∀ i = 1 , . . . , m b i ⊤ b j = 0 ∀ j = 1 , . . . , p (a5.1.1) v_a\in\text{span}{a_1,a_2,…,a_m}\Rightarrow v_a=\sum_{i=1}^mc_ia_i\ v_b\in\text{span}{b_1,b_2,…,b_p}\Rightarrow v_b=\sum_{j=1}^pd_ib_i\ \text{where }\left{\begin{aligned} &a_i^\top b_j=0&&\forall i=1,…,m;\forall j=1,…,p\ &a_i^\top a_j=0&&\forall i=1,…,m\ &b_i^\top b_j=0&&\forall j=1,…,p \end{aligned}\right.\tag{a5.1.1}v a ​∈span {a 1 ​,a 2 ​,…,a m ​}⇒v a ​=i =1 ∑m ​c i ​a i ​v b ​∈span {b 1 ​,b 2 ​,…,b p ​}⇒v b ​=j =1 ∑p ​d i ​b i ​where ⎩⎪⎪⎨⎪⎪⎧​​a i ⊤​b j ​=0 a i ⊤​a j ​=0 b i ⊤​b j ​=0 ​​∀i =1 ,…,m ;∀j =1 ,…,p ∀i =1 ,…,m ∀j =1 ,…,p ​(a 5 .1 .1 )
    根据秩一矩阵的构造方法,假定M M M具有如下的形式:
    M = ∑ i = 1 m λ i a i a i ⊤ (a5.1.2) M=\sum_{i=1}^m\lambda_ia_ia^\top_i\tag{a5.1.2}M =i =1 ∑m ​λi ​a i ​a i ⊤​(a 5 .1 .2 )
    其中λ i , i = 1 , . . . , m \lambda_i,i=1,…,m λi ​,i =1 ,…,m是待定系数,则有如下推导:
    M s = v a ⟺ M ( v a + v b ) = v a ⟺ ( ∑ i = 1 m λ i a i a i ⊤ ) ( ∑ i = 1 m c i a i + ∑ j = 1 p d i b i ) = ∑ i = 1 m c i a i ⟺ ∑ i = 1 m λ i c i a i a i ⊤ a i = ∑ i = 1 m c i a i (orthogonal property) ⟺ ∑ i = 1 m ( λ i c i a i ⊤ a i ) a i = ∑ i = 1 m c i a i ⟹ λ i c i a i ⊤ a i = c i ⟹ λ i = 1 a i ⊤ a i i = 1 , . . . , m (a5.1.3) \begin{aligned} Ms=v_a&\Longleftrightarrow M(v_a+v_b)=v_a\ &\Longleftrightarrow\left(\sum_{i=1}^m\lambda_ia_ia^\top_i\right)\left(\sum_{i=1}^mc_ia_i+\sum_{j=1}^pd_ib_i\right)=\sum_{i=1}^mc_ia_i\ &\Longleftrightarrow\sum_{i=1}^m\lambda_ic_ia_ia_i^\top a_i=\sum_{i=1}^mc_ia_i\quad\text{(orthogonal property)}\ &\Longleftrightarrow\sum_{i=1}^m(\lambda_ic_ia_i^\top a_i)a_i=\sum_{i=1}^mc_ia_i\ &\Longrightarrow\lambda_ic_ia_i^\top a_i=c_i\ &\Longrightarrow\lambda_i=\frac{1}{a_i^\top a_i}\quad i=1,…,m \end{aligned}\tag{a5.1.3}M s =v a ​​⟺M (v a ​+v b ​)=v a ​⟺(i =1 ∑m ​λi ​a i ​a i ⊤​)(i =1 ∑m ​c i ​a i ​+j =1 ∑p ​d i ​b i ​)=i =1 ∑m ​c i ​a i ​⟺i =1 ∑m ​λi ​c i ​a i ​a i ⊤​a i ​=i =1 ∑m ​c i ​a i ​(orthogonal property)⟺i =1 ∑m ​(λi ​c i ​a i ⊤​a i ​)a i ​=i =1 ∑m ​c i ​a i ​⟹λi ​c i ​a i ⊤​a i ​=c i ​⟹λi ​=a i ⊤​a i ​1 ​i =1 ,…,m ​(a 5 .1 .3 )
    综上所述:
    M = ∑ i = 1 m a i a i ⊤ a i ⊤ a i (a5.1.4) M=\sum_{i=1}^m\frac{a_ia_i^\top}{a_i^\top a_i}\tag{a5.1.4}M =i =1 ∑m ​a i ⊤​a i ​a i ​a i ⊤​​(a 5 .1 .4 )
  • 本质上就是找一个q q q使得k a ⊤ q = k b ⊤ q k_a^\top q=k_b^\top q k a ⊤​q =k b ⊤​q,则可知q ⊤ ( k a − k b ) = 0 q^\top (k_a-k_b)=0 q ⊤(k a ​−k b ​)=0,找一个与k a − k b k_a-k_b k a ​−k b ​垂直的q q q就完事了(表达式应该怎么写呢?)。
  • ( c ) (c)(c ) 探究单头注意力机制的缺陷:
  • ( 1 ) (1)(1 ) 因为协方差矩阵很小,因此可以近似用μ i \mu_i μi ​来替换k i k_i k i ​,因此等价于找一个q q q与( μ a − μ b ) (\mu_a-\mu_b)(μa ​−μb ​)垂直即可。
  • ( 2 ) (2)(2 ) 容易想到,如果存在一个明显很大的键向量k a k_a k a ​,那么单头注意力机制得到的权重就没有什么意义了,因为加权和之后基本就还是指向k a k_a k a ​的方向。
  • ( d ) (d)(d ) 探究多头注意力机制的优势: 这里的意思是说,给两个查询向量q 1 q_1 q 1 ​和q 2 q_2 q 2 ​,分别计算单头注意力得到权重c 1 c_1 c 1 ​和c 2 c_2 c 2 ​,然后取c = ( c 1 + c 2 ) / 2 c=(c_1+c_2)/2 c =(c 1 ​+c 2 ​)/2作为最终结果即可。
  • ( 1 ) (1)(1 ) 这个就没那么显然了,要求有下式的条件成立:
    α 1 a + α 2 a = α 1 b + α 2 b ⟺ exp ⁡ ( k a ⊤ q 1 ) exp ⁡ ( k a ⊤ q 1 ) + exp ⁡ ( k b ⊤ q 1 ) + exp ⁡ ( k a ⊤ q 2 ) exp ⁡ ( k a ⊤ q 2 ) + exp ⁡ ( k b ⊤ q 2 ) = exp ⁡ ( k b ⊤ q 1 ) exp ⁡ ( k a ⊤ q 1 ) + exp ⁡ ( k b ⊤ q 1 ) + exp ⁡ ( k b ⊤ q 2 ) exp ⁡ ( k a ⊤ q 2 ) + exp ⁡ ( k b ⊤ q 2 ) ⟺ exp ⁡ ( k a ⊤ q 1 ) − exp ⁡ ( k b ⊤ q 1 ) exp ⁡ ( k a ⊤ q 1 ) + exp ⁡ ( k b ⊤ q 1 ) + exp ⁡ ( k a ⊤ q 2 ) − exp ⁡ ( k b ⊤ q 2 ) exp ⁡ ( k a ⊤ q 2 ) + exp ⁡ ( k b ⊤ q 2 ) = 0 ⟺ [ exp ⁡ ( k a ⊤ ( q 1 + q 2 ) ) + exp ⁡ ( k a ⊤ q 1 + k b ⊤ q 2 ) − exp ⁡ ( k b ⊤ q 1 + k a ⊤ q 2 ) − exp ⁡ ( k b ⊤ ( q 1 + q 2 ) ) ] + [ exp ⁡ ( k a ⊤ ( q 1 + q 2 ) ) + exp ⁡ ( k b ⊤ q 1 + k a ⊤ q 2 ) − exp ⁡ ( k a ⊤ q 1 + k b ⊤ q 2 ) − exp ⁡ ( k b ⊤ ( q 1 + q 2 ) ) ] = 0 ⟺ exp ⁡ ( k a ⊤ ( q 1 + q 2 ) ) = exp ⁡ ( k b ⊤ ( q 1 + q 2 ) ) ⟺ k a ⊤ ( q 1 + q 2 ) = k b ⊤ ( q 1 + q 2 ) ⟺ ( k a − k b ) ⊤ ( q 1 + q 2 ) = 0 (a5.1.5) \begin{aligned} &\alpha_{1}^a+\alpha_2^a=\alpha_1^b+\alpha_2^b\ \Longleftrightarrow&\frac{\exp(k_a^\top q_1)}{\exp(k_a^\top q_1)+\exp(k_b^\top q_1)}+\frac{\exp(k_a^\top q_2)}{\exp(k_a^\top q_2)+\exp(k_b^\top q_2)}=\frac{\exp(k_b^\top q_1)}{\exp(k_a^\top q_1)+\exp(k_b^\top q_1)}+\frac{\exp(k_b^\top q_2)}{\exp(k_a^\top q_2)+\exp(k_b^\top q_2)}\ \Longleftrightarrow&\frac{\exp(k_a^\top q_1)-\exp(k_b^\top q_1)}{\exp(k_a^\top q_1)+\exp(k_b^\top q_1)}+\frac{\exp(k_a^\top q_2)-\exp(k_b^\top q_2)}{\exp(k_a^\top q_2)+\exp(k_b^\top q_2)}=0\ \Longleftrightarrow&[\exp(k_a^\top(q_1+q_2))+\exp(k_a^\top q_1+k_b^\top q_2)-\exp(k_b^\top q_1+k_a^\top q_2)-\exp(k_b^\top(q_1+q_2))]\ &+[\exp(k_a^\top(q_1+q_2))+\exp(k_b^\top q_1+k_a^\top q_2)-\exp(k_a^\top q_1+k_b^\top q_2)-\exp(k_b^\top(q_1+q_2))]=0\ \Longleftrightarrow&\exp(k_a^\top(q_1+q_2))=\exp(k_b^\top(q_1+q_2))\ \Longleftrightarrow&k_a^\top(q_1+q_2)=k_b^\top(q_1+q_2)\ \Longleftrightarrow&(k_a-k_b)^\top(q_1+q_2)=0 \end{aligned}\tag{a5.1.5}⟺⟺⟺⟺⟺⟺​α1 a ​+α2 a ​=α1 b ​+α2 b ​exp (k a ⊤​q 1 ​)+exp (k b ⊤​q 1 ​)exp (k a ⊤​q 1 ​)​+exp (k a ⊤​q 2 ​)+exp (k b ⊤​q 2 ​)exp (k a ⊤​q 2 ​)​=exp (k a ⊤​q 1 ​)+exp (k b ⊤​q 1 ​)exp (k b ⊤​q 1 ​)​+exp (k a ⊤​q 2 ​)+exp (k b ⊤​q 2 ​)exp (k b ⊤​q 2 ​)​exp (k a ⊤​q 1 ​)+exp (k b ⊤​q 1 ​)exp (k a ⊤​q 1 ​)−exp (k b ⊤​q 1 ​)​+exp (k a ⊤​q 2 ​)+exp (k b ⊤​q 2 ​)exp (k a ⊤​q 2 ​)−exp (k b ⊤​q 2 ​)​=0 [exp (k a ⊤​(q 1 ​+q 2 ​))+exp (k a ⊤​q 1 ​+k b ⊤​q 2 ​)−exp (k b ⊤​q 1 ​+k a ⊤​q 2 ​)−exp (k b ⊤​(q 1 ​+q 2 ​))]+[exp (k a ⊤​(q 1 ​+q 2 ​))+exp (k b ⊤​q 1 ​+k a ⊤​q 2 ​)−exp (k a ⊤​q 1 ​+k b ⊤​q 2 ​)−exp (k b ⊤​(q 1 ​+q 2 ​))]=0 exp (k a ⊤​(q 1 ​+q 2 ​))=exp (k b ⊤​(q 1 ​+q 2 ​))k a ⊤​(q 1 ​+q 2 ​)=k b ⊤​(q 1 ​+q 2 ​)(k a ​−k b ​)⊤(q 1 ​+q 2 ​)=0 ​(a 5 .1 .5 )
    刚好消掉了交叉项,那么结论就是找到q 1 , q 2 q_1,q_2 q 1 ​,q 2 ​使得它们的和与k a − k b k_a-k_b k a ​−k b ​垂直,这里用μ a \mu_a μa ​和μ 2 \mu_2 μ2 ​近似,就是跟μ a − μ b \mu_a-\mu_b μa ​−μb ​垂直。
  • ( 2 ) (2)(2 ) 实话说没怎么搞明白是什么意思,虽然增加了协方差,但是μ a − μ b \mu_a-\mu_b μa ​−μb ​依然可以近似表示k a − k b k_a-k_b k a ​−k b ​,而且理论上偏差值比没有协方差的情况要小一些(因为协方差都是正数,所以相减相当于抵消了一些偏差)。 我觉得可能就是想说在多头注意力的情况下,可以缓解( c . 2 ) (c.2)(c .2 )的问题,因为对输出的注意力权重进行了均衡。

本次代码实验是GPT \text{GPT}GPT模型的预训练和微调,GPT \text{GPT}GPT模型定义的代码已经完全写好了,要完成的只是数据处理、注意力机制定义、运行与报告部分的代码。

注意代码里有不少读取文件的默认代码可能出错,需要设置文件编码类型。

实话说这个任务有点离谱,居然是根据人名预测出生地,虽说的确不同地区的人名是可以做一些区分,但未免也太牵强了。

本题的代码借鉴自GitHub@Mr-maoge的解法,需要至少8 G 8\text{G}8 G以上的显存才能跑通,因为缺少计算资源无法跑通代码(经测试,可以调小batch size \text{batch size}batch size使得在低显存耗用的情况下通过代码测试,但是无法获得正确的结果)。

虽然代码很难跑通得到结果,但是其中的GPT \text{GPT}GPT模型代码以及两种注意力机制的实现代码是值得学习的。

  • ( a ) (a)(a ) 阅读 play_char.ipynb,看代码说明里应该还有 play_math.ipynbplay_image.ipynbplay_word.ipynb,有谁知道几个在哪儿可以找到,到时候踢我一下。
  • ( b ) (b)(b ) 运行 python src/dataset.py namedata得到以下输出:
data has 418352 characters, 256 unique.

x: Where was Khatchig Mouradian born?⁇Lebanon⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
y: □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□⁇Lebanon⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
x: Where was Jacob Henry Studer born?⁇Columbus⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
y: □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□⁇Columbus⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
x: Where was John Stephen born?⁇Glasgow⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
y: □□□□□□□□□□□□□□□□□□□□□□□□□□□⁇Glasgow⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
x: Where was Georgina Willis born?⁇Australia⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□
y: □□□□□□□□□□□□□□□□□□□□□□□□□□□□□□⁇Australia⁇□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□□

双问号表示 MASK_CHAR,正方形表示 PAD_CHAR
* ( c ) (c)(c ) 编写 run.py中相关代码块,注意如果出现 trainer.py中有 pipeline的报错信息,将 num_workers取0 0 0来避免。(从这边往下PC \text{PC}PC机就跑不通了)
* ( d ) (d)(d ) 运行下面的脚本:


python src/run.py finetune vanilla wiki.txt --writing_params_path vanilla.model.params --finetune_corpus_path birth_places_train.tsv

python src/run.py evaluate vanilla wiki.txt --reading_params_path vanilla.model.params --eval_corpus_path birth_dev.tsv --outputs_path vanilla.nopretrain.dev.predictions

python src/run.py evaluate vanilla wiki.txt --reading_params_path vanilla.model.params --eval_corpus_path birth_test_inputs.tsv --outputs_path vanilla.nopretrain.test.predictions
  • ( e ) (e)(e ) 运行 python src/dataset.py charcorruption
  • ( f ) (f)(f ) 运行下面的脚本:

python src/run.py pretrain vanilla wiki.txt --writing_params_path vanilla.pretrain.params

python src/run.py finetune vanilla wiki.txt --reading_params_path vanilla.pretrain.params --writing_params_path vanilla.finetune.params --finetune_corpus_path birth_places_train.tsv

python src/run.py evaluate vanilla wiki.txt --reading_params_path vanilla.finetune.params --eval_corpus_path birth_dev.tsv --outputs_path vanilla.pretrain.dev.predictions

python src/run.py evaluate vanilla wiki.txt --reading_params_path vanilla.finetune.params --eval_corpus_path birth_test_inputs.tsv --outputs_path vanilla.pretrain.test.predictions
  • ( g ) (g)(g ) 运行下面的脚本:

python src/run.py pretrain synthesizer wiki.txt --writing_params_path synthesizer.pretrain.params

python src/run.py finetune synthesizer wiki.txt --reading_params_path synthesizer.pretrain.params --writing_params_path synthesizer.finetune.params --finetune_corpus_path birth_places_train.tsv

python src/run.py evaluate synthesizer wiki.txt --reading_params_path synthesizer.finetune.params --eval_corpus_path birth_dev.tsv --outputs_path synthesizer.pretrain.dev.predictions

python src/run.py evaluate synthesizer wiki.txt --reading_params_path synthesizer.finetune.params --eval_corpus_path birth_test_inputs.tsv --outputs_path synthesizer.pretrain.test.predictions

记录一下synthesizer \text{synthesizer}synthesizer注意力(提出论文)的原理:
– 设X ∈ R l × d X\in\R^{l\times d}X ∈R l ×d,其中l l l的块大小(序列长度),d d d是词向量温度,d / h d/h d /h是每个注意力头的维度,Q , K , V ∈ R d × d / h Q,K,V\in\R^{d\times d/h}Q ,K ,V ∈R d ×d /h跟自注意力中的三个矩阵一样,则自注意力头的输出为:
Y i = softmax ( ( X Q i ) ( X K i ) ⊤ d / h ) ( X V i ) ∈ R l × d / h (a5.2.1) Y_i=\text{softmax}\left(\frac{(XQ_i)(XK_i)^\top}{\sqrt{d/h}}\right)(XV_i)\in\R^{l\times d/h}\tag{a5.2.1}Y i ​=softmax (d /h ​(X Q i ​)(X K i ​)⊤​)(X V i ​)∈R l ×d /h (a 5 .2 .1 )
接着将各个自注意力头拼接起来:
Y = [ Y 1 ; . . . ; Y h ] A ∈ R l × d (a5.2.2) Y=[Y_1;…;Y_h]A\in\R^{l\times d}\tag{a5.2.2}Y =[Y 1 ​;…;Y h ​]A ∈R l ×d (a 5 .2 .2 )
– 本题实现的是上面的一个变体:
Y i = softmax ( ReLU ( X A i + b 1 ) B i + b 2 ) ( X V i ) (a5.2.3) Y_i=\text{softmax}(\text{ReLU}(XA_i+b_1)B_i+b_2)(XV_i)\tag{a5.2.3}Y i ​=softmax (ReLU (X A i ​+b 1 ​)B i ​+b 2 ​)(X V i ​)(a 5 .2 .3 )
其中A i ∈ R d × d / h , B ∈ R d / h × l , V i ∈ R d × d / h A_i\in\R^{d\times d/h},B\in\R^{d/h\times l},V_i\in\R^{d\times d/h}A i ​∈R d ×d /h ,B ∈R d /h ×l ,V i ​∈R d ×d /h 可以作这样的解释: ① ( X Q i ) ( X K i ) ⊤ ∈ R l × l (XQ_i)(XK_i)^\top\in\R^{l\times l}(X Q i ​)(X K i ​)⊤∈R l ×l是注意力得分; ② synthesizer \text{synthesizer}synthesizer变体则避免计算所有成对的这种点积,而是直接通过将每个自注意力头的d d d维向量映射到l × l l\times l l ×l的注意力得分矩阵。

  • ( a ) (a)(a ) 预训练模型结果比非预训练模型结果好不是理所当然的吗,硬要说就是首先找到了一个比较好的初始解开始迭代,因而可以收敛到更好地解。实际情况,不微调只有0.02 0.02 0 .0 2,微调了之后是0.22 0.22 0 .2 2
  • ( b ) (b)(b ) 人无法辨别出机器到底是检索还是在瞎猜,这可能会使得机器的可解释性下降,无法用于实际应用。测试集中几乎所有人名都没有在训练集中出现过,但是只看姓氏或者名字的话还是有迹可循的,所以机器也并非完全是在瞎猜。
  • ( c ) (c)(c ) 模型瞎猜肯定会导致应用的可信度下降呗,不是很能理解这种应用有啥用。

Original: https://blog.csdn.net/CY19980216/article/details/125072701
Author: 囚生CY
Title: CS224N WINTER 2022(五)Transformers详解(附Assignment5答案)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/527779/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球