-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfeed.xml
1844 lines (1275 loc) · 214 KB
/
feed.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<channel>
<title>Storm Spirit</title>
<description>这是我的中文博客,记录自己在技术、生活等方面的一些感想。博客叫 Storm Spirit, 因为我特别喜欢 Dota 里蓝猫这个英雄,可惜玩得不好…… 希望自己能够通过博客,得到成长。
</description>
<link>http://wulfric.me/</link>
<atom:link href="http://wulfric.me/feed.xml" rel="self" type="application/rss+xml" />
<pubDate>Thu, 04 May 2023 21:32:18 +0800</pubDate>
<lastBuildDate>Thu, 04 May 2023 21:32:18 +0800</lastBuildDate>
<generator>Jekyll v3.6.3</generator>
<rights>wulfric © 2013~2023. All rights reserved.</rights>
<item>
<title>Transformer 论文翻译:Attention is All you Need</title>
<description><p>注意力就是一切。</p>
<h2 id="摘要">摘要</h2>
<p>显性序列转导模型基于包括编码器和解码器的复杂递归或卷积神经网络。性能最佳的模型还通过注意力机制连接编码器和解码器。我们提出了一种新的简单网络架构,即 Transformer,它完全基于注意力机制,完全摒弃了循环和卷积。对两项机器翻译任务的实验表明,这些模型在质量上更胜一筹,同时可并行化程度更高,并且需要的训练时间明显减少。我们的模型在 WMT 2014 英德翻译任务中达到了 28.4 BLEU,比现有的最佳结果(包括集成)提高了超过 2 BLEU。在 WMT 2014 英法翻译任务中,我们的模型在八个 GPU 上训练 3.5 天后建立了一个新的单模型最先进的 BLEU 分数 41.8,这是最好的训练成本的一小部分来自文献的模型。我们通过将 Transformer 成功应用于具有大量和有限训练数据的英语选区解析,证明 Transformer 可以很好地泛化到其他任务。</p>
<h2 id="1-intro">1. intro</h2>
<p>循环神经网络、长短时记忆[13]和门控循环神经网络[7]已经被确定为序列建模和转换问题的最先进方法,例如语言建模和机器翻译[35、2、5]。自那以后,许多努力继续推动循环语言模型和编码器-解码器架构的边界[38、24、15]。</p>
<p>循环模型通常将输入和输出序列的符号位置作为计算的因素进行分解。将位置与计算时间步骤对齐,它们会生成一系列隐藏状态 $h_t$,作为先前隐藏状态 $h_{t−1}$ 和位置t的输入的函数。这种内在的连续性特质导致了在训练示例内部的并行化不可行,而在处理更长的序列时,记忆约束限制了跨示例批处理,因此这一点尤为重要。近期的工作通过分解技巧[21]和条件计算[32]取得了显著的计算效率提升,同时在后者的情况下提高了模型的性能。然而,顺序计算的基本约束仍然存在。</p>
<p>注意机制已成为各种任务中引人注目的序列建模和转录模型的不可或缺部分,在不考虑输入或输出序列中它们的距离的情况下建模依赖性 [2,19]。然而,在除了少数情况[27]以外,这样的注意机制通常与循环网络一起使用。</p>
<p>在本文中,我们提出Transformer模型架构,它摈弃了循环结构,而是完全依赖于注意力机制,以绘制输入和输出之间的全局关系。Transformer允许更高程度的并行化,在使用8个P100 GPU进行训练仅仅12个小时后,就可以在翻译质量上达到新的最高水平。</p>
<h2 id="2-背景">2. 背景</h2>
<p>降低串行计算的目标也是Extended Neural GPU [16]、ByteNet [18]和ConvS2S [9]的基础,它们都使用卷积神经网络作为基本构建块,为所有输入和输出位置并行地计算其隐藏表示。这些模型中,从两个任意输入或输出位置关联信号所需的操作数量随着位置之间的距离增加而增加,对于ConvS2S是线性增长,对于ByteNet是对数增长。这使得学习远距离位置之间的依赖关系变得更加困难[12]。在Transformer中,这被减少为固定数量的操作,但代价是由于平均注意力加权位置而导致的有效分辨率降低效应,我们在3.2节中所述的多头注意力中对此进行了抵消。</p>
<p>自注意力(Self-attention),有时也被称为内部注意力,是一种关注单个序列中不同位置的注意力机制,用于计算序列的表示。自注意力已成功应用于多种任务,包括阅读理解、抽象概括、文本蕴涵和学习任务无关的句子表示[4, 27, 28, 22]。</p>
<p>端到端记忆网络基于循环注意力机制,而非与序列对齐的循环机制,已被证明在简单语言问答和语言建模任务方面表现良好[34]。</p>
<p>据我们所知,Transformer 是第一个完全依赖自注意力来计算其输入和输出表示的转换模型,而无需使用与序列对齐的 RNN 或卷积。在以下几节中,我们将介绍 Transformer,阐述自注意力的动机,并讨论其相较于诸如 [17, 18] 和 [9] 等模型的优势。</p>
<h2 id="3-模型架构">3. 模型架构</h2>
<p>大多数竞争性神经序列转换模型都具有编码器-解码器结构[5, 2, 35]。其中,编码器将符号表示的输入序列(x1,…,xn)映射到一系列连续表示z = (z1,…,zn)。给定z,解码器随后生成一个符号输出序列(y1, …, ym),每次生成一个元素。在每个步骤中,该模型是自回归的[10],在生成下一个符号时消耗先前生成的符号作为额外的输入。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/Transformer-encoder-decoder.png" alt="图1:Transformer 模型架构" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>图1:Transformer 模型架构</figcaption>
</figure></p>
<p>Transformer遵循这种总体架构,使用堆叠的自注意力和点式全连接层分别作为编码器和解码器,分别显示在图1的左半部分和右半部分。</p>
<h3 id="31-编码器栈解码器栈">3.1 编码器栈、解码器栈</h3>
<p>编码器:编码器由N = 6个相同的层堆叠而成。每层包含两个子层:第一个是多头自注意力机制,第二个是简单的位置全连接前馈网络。我们在每个子层周围采用残差连接[11],然后进行层归一化[1]。也就是说,每个子层的输出为LayerNorm(x + Sublayer(x)),其中Sublayer(x)是子层本身实现的函数。为了方便这些残差连接,模型中的所有子层以及嵌入层的输出维度均为dmodel = 512。</p>
<p>解码器:解码器也由N = 6个相同的层堆叠而成。除了每个编码器层中的两个子层外,解码器还插入第三个子层,该子层对编码器堆栈的输出执行多头注意力。与编码器类似,我们在每个子层周围采用残差连接,然后进行层归一化。我们还修改了解码器堆栈中的自注意子层,以防止位置注意到后续位置。这种遮蔽机制,以及输出嵌入在位置上的偏移量,确保位置i的预测仅取决于小于i的已知输出位置。</p>
<h3 id="32-注意力">3.2 注意力</h3>
<p>注意力函数可以描述为将一个查询和一组键值对映射到一个输出,其中查询、键、值和输出都是向量。输出是值的加权和,其中分配给每个值的权重是由查询与相应键的兼容性函数计算的。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/Transformer-attention2.png" alt="图2:(左) 缩放点积注意力 (右) 多头注意力由多个并行运行的注意层组成。" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>图2:(左) 缩放点积注意力 (右) 多头注意力由多个并行运行的注意层组成。</figcaption>
</figure></p>
<h4 id="321-缩放点积注意力">3.2.1 缩放点积注意力</h4>
<p>我们将我们特定的注意力称为“缩放点积注意力”(图2)。输入由维度为 $d_k$ 的查询和键以及维度为 $d_v$ 的值组成。我们计算查询与所有键的点积,将每个点积除以 $\sqrt{d_k}$,并应用softmax函数以获得值的权重。</p>
<p>在实践中,我们同时在一组查询上计算注意力函数,将它们打包成矩阵Q。键和值也打包成矩阵K和V。我们计算输出矩阵如下:</p>
<script type="math/tex; mode=display">Attention(Q,K,V) = softmax(\frac{QK^T}{\sqrt{d_k}})V</script>
<p>两种最常用的注意力函数是加性注意力[2]和点积(乘法)注意力。点积注意力与我们的算法相同,除了缩放因子 $\frac{1}{d_k}$。加性注意力使用具有单个隐藏层的前馈网络计算兼容性函数。虽然两者在理论复杂度上相似,但在实践中,点积注意力速度更快,空间效率更高,因为它可以使用高度优化的矩阵乘法代码实现。</p>
<p>虽然在dk的值较小的情况下,这两种机制的表现相似,但对于较大的dk值,加性注意力优于没有缩放的点积注意力[3]。我们怀疑对于较大的dk值,点积变得很大,将softmax函数推入具有极小梯度的区域<sup id="fnref:4"><a href="#fn:4" class="footnote">1</a></sup>。为了抵消这种效应,我们通过 $\frac{1}{d_k}$ 对点积进行缩放。</p>
<h4 id="322-多头注意力">3.2.2 多头注意力</h4>
<p>我们发现,通过使用不同的、经过学习的线性映射将查询、键和值投影h次,分别投影到dk、dk和dv维度,而不是使用dmodel维度的键、值和查询执行单个注意力函数,可以获得更好的效果。对于这些投影版本的查询、键和值,我们并行执行注意力函数,得到dv维的输出值。然后将这些值进行连接,并再次进行投影,得到最终的值,如图2所示。</p>
<p>多头注意力允许模型在不同位置同时关注来自不同表示子空间的信息。使用单个注意力头时,平均效应会抑制这种效果。</p>
<script type="math/tex; mode=display">MultiHead(Q,K,V)=Concat(head_1,...,head_h)W^O \\
where \ head_i=Attention(QW_i^Q,KW_i^K,VW_i^V)</script>
<p>这里的线性映射就是参数矩阵 $W_i^Q \in \R^{d_{model}\times d_k},W_i^K\in \R^{d_{model}\times d_k},W_i^V\in \R^{d_{model}\times d_v}, W^O\in \R^{hd_v \times d_{model}}$。</p>
<p>在本文中,我们使用了h = 8个平行的注意力层或头。对于每个头,我们使用dk = dv = dmodel/h = 64。由于每个头的维度降低,总的计算成本类似于全维度的单头注意力。</p>
<h4 id="323-我们的模型中注意力机制的应用">3.2.3 我们的模型中,注意力机制的应用</h4>
<p>Transformer使用多头注意力有三种不同的方式:</p>
<p>• 在“编码器-解码器注意力”层中,查询来自前一个解码器层,而记忆键和值来自编码器的输出。这使得解码器中的每个位置都可以关注输入序列中的所有位置。这类似于序列到序列模型(如[38, 2, 9])中的典型编码器-解码器注意力机制。</p>
<p>• 编码器包含自注意力层。在自注意力层中,所有的键、值和查询都来自同一个地方,即编码器中前一层的输出。编码器中的每个位置都可以关注编码器前一层中的所有位置。</p>
<p>• 同样,解码器中的自注意力层允许解码器中的每个位置关注解码器中直到该位置的所有位置。我们需要防止解码器中的左向信息流,以保留自回归属性。我们通过在缩放点积注意力内部屏蔽(将其设置为−∞)所有与非法连接相对应的softmax输入中的值来实现这一点。请参见图2。</p>
<h3 id="33-位置编码前馈网络">3.3 位置编码前馈网络</h3>
<p>除了注意力子层外,我们的编码器和解码器中的每个层都包含一个全连接的前馈神经网络<sup id="fnref:PFFN"><a href="#fn:PFFN" class="footnote">2</a></sup>,它是独立地应用于每个位置并且相同地处理。该前馈神经网络由两个线性变换层组成,中间使用ReLU激活函数。</p>
<script type="math/tex; mode=display">FFN(x)=max(0,xW_1+b_1)W_2+b_2</script>
<p>虽然线性变换在不同位置上是相同的,但它们在不同层之间使用不同的参数。另一种描述方式是将其视为两个内核大小为1的卷积。输入和输出的维度是dmodel = 512,而内部层的维度为dff = 2048。</p>
<h3 id="34-嵌入和-softmax">3.4 嵌入和 softmax</h3>
<p>与其他序列转换模型类似,我们使用学习得到的嵌入将输入和输出的标记转换为dmodel维度的向量。我们还使用通常的学习过的线性变换和softmax函数将解码器的输出转换为预测的下一个 token 的概率。在我们的模型中,我们在两个嵌入层和pre-softmax线性转换之间共享同一权重矩阵,类似于[30]。在嵌入层中,我们将这些权重乘以 $\sqrt{d_{model}}$。</p>
<h3 id="35-位置编码">3.5 位置编码</h3>
<p>由于我们的模型不包含循环和卷积,为了使模型能够利用序列的顺序,我们必须注入有关序列中标记的相对或绝对位置的一些信息。为此,在编码器和解码器堆栈底部的输入嵌入中添加“位置编码”。位置编码与嵌入具有相同的维度dmodel,因此可以将它们相加。有许多位置编码的选择,可以是可学习的或固定的[9]。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/Transformer-table1.png" alt="表1:不同层类型的最大路径长度、每层复杂度和最小顺序操作数。n是序列长度,d是表示维度,k是卷积的内核大小,r是受限自注意力中邻域的大小。" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>表1:不同层类型的最大路径长度、每层复杂度和最小顺序操作数。n是序列长度,d是表示维度,k是卷积的内核大小,r是受限自注意力中邻域的大小。</figcaption>
</figure></p>
<p>我们的工作中使用了不同频率的 sin 和 cos 函数:</p>
<script type="math/tex; mode=display">PE_{(pos,2i)}=sin(pos/10000^{2i/d_{model}}) \\
PE_{(pos,2i+1)}=cos(pos/10000^{2i/d_{model}})</script>
<p>其中,pos 是未知,i 是维度。也就是说,位置编码中的每个维度对应于一个正弦波。波长从2π到10000·2π形成一个几何级数。我们选择这个函数是因为我们假设它可以使模型轻松地学习相对位置的注意力,由于对于任何固定的偏移k,$PE_{pos+k}$ 可以表示为$PE_{pos}$ 的线性函数。</p>
<p>我们还尝试使用学习得到的位置嵌入[9],并发现两个版本产生的结果几乎相同(见表3行(E))。我们选择正弦版本是因为它可能允许模型外推到比训练中遇到的序列长度更长的序列长度。</p>
<h2 id="4-为什么选择-self-attention">4. 为什么选择 Self-attention</h2>
<p>在本节中,我们将自注意力层的各个方面与常用于将一个可变长度符号表示序列(x1, …, xn)映射到另一个等长序列(z1, …, zn)的循环和卷积层进行比较( xi, zi ∈ Rd),比如典型序列转换编码器或解码器中的隐藏层。我们考虑三个期望目标来激发我们使用自注意力。</p>
<p>一个是每层的总计算复杂度。另一个是可以并行化的计算量,用所需的最小顺序操作次数衡量。第三个是网络中长距离依赖关系之间的路径长度。在许多序列转换任务中,学习长距离依赖关系是一个关键挑战。影响学习这种依赖关系能力的一个关键因素是网络中前向和后向信号必须遍历的路径长度。输入和输出序列中任何位置组合之间的路径越短,学习长距离依赖关系就越容易[12]。因此,我们还比较了由不同层类型组成的网络中任意两个输入和输出位置之间的最大路径长度。</p>
<p>正如表1所示,自注意力层通过执行固定数量的串行运算将所有位置连接起来,而循环层则需要进行O(n)个。在计算复杂度方面,当序列长度n小于表示维度d时,自注意力层的速度比循环层更快。在机器翻译领域表现最佳的模型所使用的语句表示(如单词片段[38]和字节对[31]表示)通常都是这种情况。为了提高涉及非常长的序列的任务的计算性能,我们可以将自关注限制在大小为r的输入序列周围的相应输出位置的邻域中考虑。这将增加最大路径长度到O(n / r)。我们计划在未来的工作中进一步研究这种方法。</p>
<p>一个内核宽度为k &lt; n的卷积层,不能连接所有输入和输出位置对。这需要在连续内核情况下使用O(n/k)个卷积层或在扩张卷积情况下使用O(logk(n))个卷积层[18],从而增加网络中任意两个位置之间最长路径的长度。相比循环层,卷积层通常更昂贵,因为要乘以一个因子k。然而,可分离卷积[6]可以大大降低复杂性至O(k · n · d + n · d2)。即使是当k = n时,可分离卷积的复杂度也等于自注意力层和逐点前馈层的组合,即在我们模型中采用的这种方法。</p>
<p>作为副作用,自注意力可以产生更易解释的模型。我们检查了我们模型中的注意分布,并在附录中呈现和讨论了例子。不仅单个注意头清楚地学习执行不同的任务,而且许多似乎表现出与句子的语法和语义结构相关的行为。</p>
<h2 id="6-结果">6. 结果</h2>
<p>在WMT 2014英德翻译任务中,大型Transformer模型(表2中的Transformer(big))的表现超过了先前报告的最佳模型(包括集成模型)超过2.0 BLEU,建立了一个新的BLEU得分最高的记录,为28.4。该模型的配置列在表3的底部行。训练使用8个P100 GPU进行了3.5天。即使是我们的基础模型也超过了所有先前发布的模型和集成模型,在训练成本的一小部分内。</p>
<h2 id="7-结论">7. 结论</h2>
<p>在这项工作中,我们提出了Transformer,这是第一个完全基于注意力的序列转导模型,用多头自注意力替换了编码器-解码器架构中最常用的循环层。</p>
<p>对于翻译任务,Transformer 模型的训练速度比基于循环或卷积层的架构要快得多。在WMT 2014英德和英法翻译任务中,我们实现了新的最先进水平。在前一项任务中,我们的最佳模型甚至超过了以前报告的所有集合模型。</p>
<p>我们对基于注意力机制的模型未来感到兴奋,并计划将其应用于其他任务。我们计划将Transformer扩展到涉及文本以外的输入和输出模态问题,并研究局部、受限制的注意力机制,以有效处理大量输入和输出,如图像、音频和视频。使生成 less sequential 是我们的另一个研究目标。</p>
<div class="footnotes">
<ol>
<li id="fn:4">
<p>为了说明为什么点积变得很大,假设q和k的分量是具有均值0和方差1的独立随机变量。那么它们的点积,$q·k = \sum_{i=1}^{d_k} q_ik_i$ 的均值为0,方差为dk。 <a href="#fnref:4" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:PFFN">
<p>(位置编码前馈网络)是Transformer模型中的一种组件,用于在每个注意力子层之间增加一个前馈神经网络层,从而提高模型性能。该网络层是在每个位置上独立地应用的,因此称为“位置编码”。它由两个全连接层组成,每个层之间使用ReLU激活函数。在每个位置上,该网络将输入向量映射到具有相同维度的中间向量,再通过第二个全连接层将中间向量映射回原始维度的输出向量。通过使用位置编码前馈网络,Transformer模型能够更好地处理不同位置之间的依赖关系,从而提高了其在序列到序列任务中的性能。 <a href="#fnref:PFFN" class="reversefootnote">&#8617;</a></p>
</li>
</ol>
</div>
</description>
<pubDate>Fri, 28 Apr 2023 22:48:00 +0800</pubDate>
<link>http://wulfric.me/2023/04/Transformer-ch/</link>
<guid isPermaLink="true">http://wulfric.me/2023/04/Transformer-ch/</guid>
<category>ai</category>
<category>chatgpt</category>
<category>技术</category>
</item>
<item>
<title>GPT3 论文翻译:Language Models are Few-Shot Learners</title>
<description><p>语言模型是少样本学习者。</p>
<h2 id="摘要">摘要</h2>
<p>近期的研究表明,在大型文本语料库上进行预训练,然后针对特定任务进行微调,可以在许多NLP任务和基准测试中取得显著成果。尽管这种方法在架构上通常不针对特定任务,但它仍然需要由数千或数万个样本组成的任务特定微调数据集。相比之下,人类通常只需几个示例或简单的指令就能完成新的语言任务,然而当前的NLP系统在很大程度上仍难以实现这一目标。在这里,我们展示了通过扩大语言模型规模可以极大地提高任务通用的少样本性能,有时甚至能达到与先前最先进的微调方法相媲美的水平。具体来说,我们训练了GPT-3,一种具有1750亿参数的自回归语言模型,比以前任何非稀疏语言模型多10倍,并在少样本设置中测试其性能。对于所有任务,GPT-3均无需进行任何梯度更新或微调,任务和少样本演示仅通过与模型的文本交互进行指定。GPT-3在许多NLP数据集上表现优异,包括翻译、问答和填空任务,以及一些需要实时推理或领域自适应<sup id="fnref:dat"><a href="#fn:dat" class="footnote">1</a></sup>的任务,如还原打乱的单词、在句子中使用新词或进行3位数算术运算。同时,我们还发现了,GPT-3在一些数据集上的少样本学习仍然存在困难,同时在一些大型网络语料库数据集的训练中遇到了一些方法论问题。最后,我们发现GPT-3可以生成新闻文章样本,人类评估者很难将其与人类撰写的文章区分开来。我们讨论了这一发现以及GPT-3总体产生的更广泛的社会影响。</p>
<h2 id="1-导言">1. 导言</h2>
<p>近年来,NLP系统中出现了一个趋势,即使用越来越灵活、任务无关的方式应用预训练的「语言表示」来进行下游迁移。首先,利用词向量来学习单层表示[MCCD13, PSM14]并输入到任务特定的架构中,然后使用具有多层表示和上下文状态的RNN形成更强大的表示[DL15, MBXS17, PNZtY18](尽管仍应用于任务特定的架构),最近则直接微调预训练的循环或Transformer语言模型[VSP+17],完全消除了任务特定架构的需求[RNSS18, DCLT18, HR18]。</p>
<p>最后这种范式在许多具有挑战性的NLP任务上取得了实质性进展,如阅读理解、问答、文本蕴涵等,并在新的架构和算法的基础上不断发展[RSR+19, LOG+19, YDY+19, LCG+19]。然而,这种方法的一个主要局限性在于,尽管架构是任务通用的,但仍然需要任务特定的数据集和任务特定的微调:要在期望的任务上取得优异表现,通常需要在针对该任务的数千到数十万个示例的数据集上进行微调。消除这种局限性是可取的,原因有以下几点。</p>
<p>首先,从实践角度来看,每个新任务都需要大量已标注样本的数据集限制了语言模型的应用范围。有非常多的有用的语言任务,包括纠正语法、生成抽象概念示例、评论短篇小说等等。对于这些任务中的很多来说,收集大量监督训练数据集极具挑战,尤其是当这个过程需要为每个新任务重复进行的时候。</p>
<p>其次,随着模型表达能力愈发强大和训练数据分布愈发狭窄,挖掘训练数据中的非实质性关联的潜力也随之增加。这给预训练加微调范式带来了问题,在这个范式中,模型被设计成庞大的,以便在预训练阶段吸收信息,但随后会在非常狭窄的任务分布上进行微调。例如,有研究发现,大模型并不一定在分布之外具有更好的泛化能力。有证据表明,在这种范式下所实现的泛化性能可能较差,因为模型过于依赖训练分布,在其之外泛化能力不足。因此,在特定基准测试上微调模型的性能,即使名义上达到人类水平,也可能高估了其在实际任务中的表现。</p>
<p>第三,人类在学习大部分语言任务时,并不需要大量的有监督数据集。一条简短的自然语言指令(例如,“请告诉我这句话描述的是快乐还是悲伤”)或者仅有的几个示例(例如,“这里有两个勇敢行为的例子;请给出第三个勇敢的例子”)通常就足以让人类具备至少一定程度的能力来执行新任务。除了揭示我们目前的NLP技术在概念上的局限性之外,这种适应能力还具有实际优势——它使人类能够在许多任务和技能之间无缝地混合或切换,如在漫长的对话过程中进行加法运算。为了使NLP系统更具实用性,我们期望未来的NLP系统能够拥有与人类相同的灵活性和通用性。</p>
<h2 id="8结论">8结论</h2>
<p>我们展示了一个拥有1750亿参数的语言模型,在零样本、单样本和少样本设置下,该模型在许多NLP任务和基准测试中表现出强大的性能。在某些情况下,它几乎能匹敌顶尖的微调系统的性能,同时能够生成高质量的样本,以及在即时定义的任务中表现出优秀的定性性能。我们记录了在不使用微调的情况下,性能在规模方面的大致可预测趋势。我们还讨论了这类模型的社会影响。尽管存在许多局限性和弱点,但这些结果表明,非常大的语言模型可能是可适应、通用语言系统发展中的重要组成部分。</p>
<div class="footnotes">
<ol>
<li id="fn:dat">
<p>在自然语言处理(NLP)中,领域自适应任务(domain adaptation task)主要指的是将在一个领域(源领域)训练的模型调整为适应另一个领域(目标领域)的任务。由于不同领域的数据分布可能存在差异,直接将源领域的模型应用于目标领域可能会导致性能下降。因此,领域自适应的目标是通过利用目标领域的数据,使模型更好地适应和处理目标领域的任务。领域自适应的挑战在于,在许多情况下,目标领域的标注数据是有限的或者根本不存在。为了解决这个问题,研究人员采用了多种方法,包括:特征选择或变换,无监督或半监督领域自适应,自监督学习 <a href="#fnref:dat" class="reversefootnote">&#8617;</a></p>
</li>
</ol>
</div>
</description>
<pubDate>Thu, 27 Apr 2023 22:48:00 +0800</pubDate>
<link>http://wulfric.me/2023/04/gpt3-ch/</link>
<guid isPermaLink="true">http://wulfric.me/2023/04/gpt3-ch/</guid>
<category>ai</category>
<category>chatgpt</category>
<category>技术</category>
</item>
<item>
<title>GPT2 论文翻译:Language Models are Unsupervised Multitask Learners</title>
<description><p>语言模型是无监督多任务学习者。</p>
<h2 id="摘要">摘要</h2>
<p>自然语言处理任务,如问题回答、机器翻译、阅读理解和摘要,通常通过在特定任务的数据集上进行监督学习来解决。我们证明,当在一个名为WebText的包含数百万网页的新数据集上训练时,语言模型开始在没有任何明确监督的情况下学习这些任务。当基于文档和问题进行条件设置时,语言模型生成的答案在CoQA数据集上达到55 F1,在4个基线系统中,有3个实现了性能匹配或超越,而无需使用127,000+的训练样本。语言模型的容量对于零样本任务迁移的成功至关重要,增加容量可以在任务之间以对数线性方式提高性能。我们的最大模型,GPT-2,是一个拥有15亿参数的Transformer,在没有借助训练数据的零样本情况下,在8个测试的语言建模数据集中的7个上实现了顶尖水平的表现,但在适应WebText方面仍有不足。模型生成的样本展示了这些改进,包含连贯的文本段落。这些发现表明,我们有望构建能够通过自然场景中的示例来学习执行任务的语言处理系统。</p>
<h2 id="1-导言">1. 导言</h2>
<p>如今,机器学习系统在所训练任务上表现优异,这得益于大型数据集、高容量模型和监督学习的相互结合(Krizhevsky等人,2012)(Sutskever等人,2014)(Amodei等人,2016)。然而,当面临数据分布(Recht等人,2018)和任务规格的微小变化(Kirkpatrick等人,2017)时,这些系统表现出脆弱性和敏感性。当前的系统更像是狭义的专家,而非胜任多项任务的通才。我们希望发展更具通用性的系统,能够执行许多任务,最终无需为每个任务手动创建和标注训练数据集。</p>
<p>创建机器学习系统的主要方法是收集展示目标任务正确行为的训练示例数据集,训练一个系统以模仿这些行为,然后在独立且相同分布(IID)的保留样本上测试其性能。这种方法在培养狭义专家方面取得了良好成果。然而,字幕模型(Lake等人,2017)、阅读理解系统(Jia和Liang,2017)以及图像分类器(Alcorn等人,2018)在处理多样性、多种类的输入数据的不稳定行为,凸显了这种方法的局限性。</p>
<p>我们怀疑,现有系统泛化能力不足的主要原因在于,人们普遍在单一领域的数据集上进行单任务训练。要使用当前架构实现健壮的系统,很可能需要在各种领域和任务上进行训练和性能评估。最近,已经提出了一些基准测试,如GLUE(Wang等人,2018)和decaNLP(McCann等人,2018),以便开始研究这一问题。</p>
<p>多任务学习(Caruana,1997)是一个有前景的框架,可以用来提高整体性能。然而,在自然语言处理领域,多任务训练仍然处于初级阶段。最近的研究报告显示,性能改进有限(Yogatama等人,2019),迄今为止最具野心的两项研究分别在10个和17个「(数据集,目标)对」上进行了训练(McCann等人,2018)(Bowman等人,2018)。从元学习的角度看,每个「(数据集,目标)对」都是从数据集和目标的分布中抽样出来的单个训练样本。当前的机器学习系统需要数百到数千个样本才能形成具有良好泛化能力的函数。这表明,多任务训练可能需要与当前方法相同数量的有效训练对,以实现其潜在的优势。要继续扩大数据集的创建和目标设计的规模,可能需要使用当前技术付出巨大努力。这促使我们探索进行多任务学习的其他设置。</p>
<p>目前在语言任务上表现最佳的系统采用了预训练和有监督微调相结合的方法。这种方法有着悠久的历史,逐渐朝着更灵活的迁移形式发展。起初,研究者学习词向量并将其作为特定任务架构的输入(Mikolov等人,2013年)(Collobert等人,2011年),接着迁移到循环网络的上下文表示(Dai &amp; Le,2015年)(Peters等人,2018年)。近期的研究表明,不再需要特定任务的架构,迁移多个自注意力模块就足够了(Radford等人,2018年)(Devlin等人,2018年)。</p>
<p>尽管如此,这些方法仍然需要有监督训练来完成任务。当只有极少量或没有监督数据时,另一方面的研究表明语言模型在执行特定任务方面具有潜力,例如常识推理(Schwartz等人,2017年)和情感分析(Radford等人,2017年)。</p>
<p>在本文中,我们将这两个研究方向联系起来,继续朝更通用的迁移方法发展。我们证明语言模型在零样本设置下可以执行下游任务,无需任何参数或架构的修改。我们通过强调语言模型在零样本环境下执行各种任务的能力,展示了这种方法的潜力。依据不同的任务,我们取得了具有前景、竞争力和最先进水平的成果。</p>
<h2 id="6-讨论">6. 讨论</h2>
<p>许多研究致力于学习(Hill等人,2016)、理解(Levy &amp; Goldberg,2014)以及严格评估(Wieting &amp; Kiela,2019)有监督和无监督预训练方法的表征。我们的研究结果表明,无监督任务学习是一个值得进一步探讨的有前景的研究方向。这些发现或许有助于解释预训练技术在下游NLP任务中为何取得广泛成功,因为我们证明,在某种程度上,这些预训练技术中的其中一种已经开始直接学习执行任务,而不需要监督适应或修改。</p>
<p>在阅读理解方面,GPT-2在零样本环境中的表现与有监督的基准相当。然而,在其他任务如摘要方面,尽管从质量上看GPT-2已经执行了这个任务,但从定量指标来看,其表现仍然处于初级阶段。虽然这一研究结果具有启示性,但从实际应用角度来看,GPT-2的零样本性能仍然远远无法满足实际需求。</p>
<p>我们已经研究了WebText语言模型在许多典型NLP任务上的零样本性能,然而仍有许多其他任务值得评估。无疑,有很多实际任务中,GPT-2的性能仍然不比随机选择优秀。甚至在我们评估过的常见任务中,如问答和翻译,只有当语言模型具备足够的容量时,它们才能开始超过简单的基线。</p>
<p>尽管零样本性能在许多任务上为GPT-2的潜在表现建立了基准,但在微调之后的上限并不明确。在一些任务中,GPT-2的完全生成式输出与当前在许多问答和阅读理解数据集上表现卓越的基于抽取式指针网络(Vinyals等人,2015)的输出有显著差异。考虑到GPT在微调方面的过往成功,我们计划探讨在decaNLP和GLUE等基准上的微调,特别是,目前尚不明确GPT-2额外的训练数据和容量是否足以克服BERT(Devlin等人,2018年)中提到的单向表示的低效问题。</p>
<h2 id="总结">总结</h2>
<p>当一个大型语言模型在一个足够庞大且多样的数据集上进行训练时,它能在众多领域和数据集中表现出色。GPT-2在8个经过测试的语言建模数据集中的7个上实现了零样本的顶尖性能。模型在零样本设置下能够完成的任务类型之多样,暗示着通过训练高容量模型来最大化足够多样化的文本语料库的似然性,开始学会执行大量令人惊叹的任务,而无需显式监督。</p>
</description>
<pubDate>Thu, 06 Apr 2023 22:48:00 +0800</pubDate>
<link>http://wulfric.me/2023/04/gpt2-ch/</link>
<guid isPermaLink="true">http://wulfric.me/2023/04/gpt2-ch/</guid>
<category>ai</category>
<category>chatgpt</category>
<category>技术</category>
</item>
<item>
<title>GPT1 论文翻译:Improving Language Understanding by Generative Pre-Training</title>
<description><p>通过生成式预训练提高语言模型的理解能力。</p>
<h2 id="摘要">摘要</h2>
<p>「自然语言理解」包括了文本蕴涵、问答、语义相似性评估和文档分类等多种不同的任务。尽管大型无标注文本语料库是很丰富的,但用于学习这些特定任务的标注数据却比较稀缺,这使得以判别方式训练的模型难以表现良好。我们证明,在多样化的无标注文本语料库上对语言模型进行「生成式预训练」(即 GPT),然后对每个特定任务进行「判别式微调」,可以在这些任务上实现大幅能力提升。与以前的方法不同,我们在微调过程中使用任务感知输入变换,在不需要对模型架构进行大量更改的情况下实现有效的迁移。我们在一系列自然语言理解基准任务上展示了我们方法的有效性。我们的通用任务不可知模型优于使用专门为每个任务定制的架构的判别式训练模型,在 12 个研究任务中有 9 个任务上显著改善了最先进水平。例如,我们在常识推理(Stories Cloze Test)上实现了 8.9% 的绝对提升,在问答(RACE)上实现了 5.7% 的提升,在文本蕴涵(MultiNLI)上实现了 1.5% 的提升。</p>
<h2 id="1-导言">1. 导言</h2>
<p>学会有效地从原始文本中学习对于减轻自然语言处理(NLP)中对监督学习的依赖至关重要。大多数深度学习方法需要大量手动标注的数据,这限制了它们在许多缺乏注释资源的领域中的适用性[61]。在这些情况下,能够利用未标注数据中的语言信息的模型提供了一种收集更多注释的有价值的替代方案,而收集这些注释可能是耗时且昂贵的。此外,即使在监督较多(considerable supervision)的情况下,以无监督的方式学习良好的表示(good representations)也可以带来显著的性能提升。迄今为止最引人注目的证据是广泛使用预训练词嵌入[10, 39, 42]来提高一系列 NLP 任务性能[8, 11, 26, 45]。</p>
<p>然而,从未标注文本中提取比词级信息(word-level information)更多的信息具有挑战性。原因有两个:首先,不清楚哪种类型的优化目标(optimization objectives)在学习对于迁移有用的文本表示<sup id="fnref:tr"><a href="#fn:tr" class="footnote">1</a></sup>(text representations)方面最有效。近期研究已经考察了各种目标,如语言建模[44]、机器翻译[38]和话语连贯性[22],在不同任务上,每种方法的表现也不同(在 A 任务上方法 1 优于方法 2;在 B 任务上可能相反)。其次,关于如何最有效地将这些学到的表示迁移到目标任务上,尚无共识。现有技术涉及对模型架构进行针对特定任务的更改[43, 44]、使用复杂的学习方案[21]以及添加辅助学习目标[50]。这些不确定性使得开发用于语言处理的有效半监督学习方法变得困难。</p>
<p>在本文中,我们探讨了一种使用无监督预训练和监督微调相结合的半监督方法来进行语言理解任务。我们的目标是学习一种通用表示,可以在很少的调整下适应各种任务。我们假设有大量未标记文本的语料库和几个带有手动注释训练示例的数据集(目标任务)。我们的设置不要求这些目标任务与未标记语料库在同一领域。我们采用两阶段训练过程。首先,我们在未标记数据上使用语言建模目标来学习神经网络模型的初始参数。随后,我们使用相应的监督目标将这些参数调整以适应目标任务。</p>
<p>对于我们的模型架构,我们采用了 Transformer 模型[62],它已被证明在各种任务上表现出色,如机器翻译[62]、文档生成[34]和句法解析[29]。与循环网络等替代方案相比,选择的这种模型为我们提供了一个更加结构化的记忆,用于处理文本中的长距离依赖关系,从而在各种不同任务中实现了稳健的迁移性能。在迁移过程中,我们利用遍历式方法[52],针对特定任务对输入进行调整,该方法将结构化文本输入作为一个连续的 token 序列进行处理。正如我们在实验中所展示的,这些调整使我们能够在对预训练模型的架构进行最小改动的情况下进行有效的微调<sup id="fnref:tna"><a href="#fn:tna" class="footnote">2</a></sup>。</p>
<p>我们在四种类型的语言理解任务上评估我们的方法,包括自然语言推理、问题回答、语义相似性和文本分类。我们的通用任务无关模型胜过了采用针对每个任务专门设计的架构的判别式训练模型<sup id="fnref:dtm"><a href="#fn:dtm" class="footnote">3</a></sup>,在研究的 12 个任务中有 9 个任务显著提高了技术水平。例如,我们在常识推理(Stories Cloze Test)[40]上实现了 8.9% 的绝对改进,在问题回答(RACE)[30]上实现了 5.7% 的改进,在文本蕴含(MultiNLI)[66]上实现了 1.5% 的改进,以及在最近引入的 GLUE 多任务基准[64]上实现了 5.5% 的改进。我们还分析了预训练模型在四种不同设置下的零样本行为,并证明它获取了对下游任务有用的语言知识。</p>
<h2 id="2-相关工作">2. 相关工作</h2>
<p>NLP 的半监督学习:我们的工作大致属于自然语言半监督学习的范畴。这一范式引起了广泛关注,应用于诸如序列标记[24, 33, 57]或文本分类[41, 70]等任务。最早的方法使用未标注数据计算单词级或短语级统计信息,然后将其用作监督模型中的特征[33]。在过去的几年里,研究人员已经证明了使用词嵌入[11, 39, 42]的好处,这些词嵌入在未标注的语料库上进行训练,以提高各种任务的性能[8, 11, 26, 45]。然而,这些方法主要迁移单词级信息,而我们的目标是捕捉更高级别的语义。</p>
<p>最近的方法研究了从未标注数据中学习和利用超过单词级语义的方法。可以使用未标注语料库训练的短语级或句子级嵌入已被用于将文本编码为适合各种目标任务的向量表示[28, 32, 1, 36, 22, 12, 56, 31]。</p>
<p>无监督预训练:无监督预训练是半监督学习的一个特例,其目标是寻找一个好的初始化点,而不是修改监督学习目标。早期的工作探讨了这种技术在图像分类[20, 49, 63]和回归任务[3]中的应用。随后的研究[15]表明,预训练作为一种正则化方案,能够在深度神经网络中实现更好的泛化。在最近的工作中,该方法已经被用于帮助训练各种任务的深度神经网络,如图像分类[69]、语音识别[68]、实体消歧[17]和机器翻译[48]。</p>
<p>与我们的工作最接近的一条研究路线是使用语言建模目标预训练神经网络,然后在有监督的目标任务上对其进行微调。Dai 等人[13]和 Howard 和 Ruder[21]遵循这种方法来改进文本分类。然而,尽管预训练阶段有助于捕捉一些语言信息,但他们使用 LSTM 模型,这一模型将其预测能力限制在一个比较小的范围内。相反,我们选择的 transformer 网络允许我们捕捉更长的语言结构,如我们的实验中所示。此外,我们还展示了我们的模型在更广泛的任务范围内的有效性,包括自然语言推理、释义检测和故事补全。其他方法[43, 44, 38]在训练目标任务的监督模型时,使用预训练语言或机器翻译模型的隐藏表示(hidden representations)作为辅助特征。这涉及到为每个独立的目标任务引入大量新的参数,而在迁移过程中,我们对模型架构的改变最小。</p>
<p>辅助训练目标:添加辅助无监督训练目标是半监督学习的另一种形式。Collobert 和 Weston[10]的早期工作使用了各种辅助 NLP 任务,如词性标注、分块、命名实体识别和语言建模来改进语义角色标注。最近,Rei[50]在他们的目标任务目标中添加了辅助语言建模目标,并在序列标注任务上获得了性能提升。我们的实验也使用辅助目标,但如我们所示,无监督预训练已经学会了与目标任务相关的多种语言方面。</p>
<h2 id="3-框架">3. 框架</h2>
<p>我们的训练过程包含 2 个阶段。第一阶段是在大型文本语料上学习高容量的语言模型。接下来是微调阶段。在这个阶段,我们借助标注数据,调整模型以适应一个判别任务。</p>
<p>无监督预训练:给定一个无监督的 token 语料库$U = {u_1, . . . , u_n}$,我们使用标准的语言建模目标来最大化以下似然:</p>
<script type="math/tex; mode=display">L_1(U) = \Sigma_ilogP(u_i|u_{i−k},...,u_{i−1};\Theta)</script>
<p>其中 k 是上下文窗口的大小,条件概率 P 使用具有参数$\Theta$的神经网络进行建模。这些参数使用随机梯度下降[51]进行训练。</p>
<p>在我们的实验中,我们使用多层 Transformer decoder [34]作为语言模型,这是 Transformer[62]的一个变种。该模型在输入上下文 token 上应用多头自注意力操作,然后通过逐位置前馈层来产生目标 token 上的输出分布:</p>
<script type="math/tex; mode=display">% <![CDATA[
\begin{aligned} &h_0 = UW_e + W_p \\ &h_l = transformer\_block(h_{l−1})\forall_i \in [1, n] \\ &P(u) = softmax(h_n W_e^T ) \end{aligned} %]]></script>
<p>其中$U = (u_{−k}, . . . , u_{−1})$是 token 的上下文向量,n 是层数,$W_e$是 token 嵌入矩阵,$W_p$是位置嵌入矩阵。l 是神经网络的层数。</p>
<p>在使用等式 1 中的目标训练模型之后,我们将参数调整以适应有监督目标任务。我们假设一个带标签的数据集$C$,其中每个实例包括一系列输入 token $x^1, . . . , x^m$,以及一个标签 y。该输入数据传输通过我们的预训练模型,以获得最终的 Transformer block 的激活器$h^m_l$ ,然后将其输入到新添加的线性输出层(参数为$W_y$)中以预测 y:</p>
<script type="math/tex; mode=display">P(y|x^1,...,x^m) = softmax(h^m_l W_y)</script>
<p>这给我们提供了最大化的目标:</p>
<script type="math/tex; mode=display">L_2(C) = \Sigma_{(x,y)} logP(y|x^1,...,x^m)</script>
<p>我们还发现,在微调过程中将语言建模作为辅助目标有助于学习,原因是:(a)改善监督模型的泛化能力;(b)加速收敛。这与之前的工作[50, 43]一致,他们也观察到了使用这样一个辅助目标可以提高性能。具体来说,我们优化以下目标(权重λ):</p>
<script type="math/tex; mode=display">L_3(C) = L_2(C) + \lambda * L_1(C)</script>
<p>总的来说,在微调过程中,我们仅需要额外的参数$W_y$以及分隔符 token 的嵌入(在第 3.3 节中描述)。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/gpt1.jpg" alt="图 1: (左侧) 这项工作中用到的 transformer 结构和训练目标。(右侧)微调阶段,在不同任务上的输入变换。我们将所有结构化的输出转换成可以被我们的预训练模型处理的 token 序列,然后在最后添加一层线性+softmax 层" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>图 1: (左侧) 这项工作中用到的 transformer 结构和训练目标。(右侧)微调阶段,在不同任务上的输入变换。我们将所有结构化的输出转换成可以被我们的预训练模型处理的 token 序列,然后在最后添加一层线性+softmax 层</figcaption>
</figure></p>
<p>对于某些任务,如文本分类,我们可以直接按照上述方法微调我们的模型。某些其他任务,如问题回答或文本蕴含,具有结构化输入,如有序句子对或文档、问题和答案的三元组。由于我们的预训练模型是在连续文本序列上进行训练的,因此我们需要对其进行一些修改才能将其应用于这些任务。之前的工作提出了在转移表示之上学习特定任务的架构[44]。这种方法重新引入了大量任务特定的定制,并且没有为这些额外的架构组件使用迁移学习。相反,我们使用遍历式方法[52],将结构化输入转换为我们的预训练模型可以处理的有序序列。这些输入转换使我们能够避免在任务之间对架构进行大量更改。我们在下面简要描述了这些输入转换,图 1 提供了一个视觉示例。所有转换都包括添加随机初始化的开始和结束标记(⟨s⟩, ⟨e⟩)。</p>
<p>文本蕴含:对于蕴含任务,我们将前提 p 和假设 h 的令牌序列连接在一起,中间用分隔符标记 ($) 隔开。</p>
<p>相似性:对于相似性任务,比较的两个句子之间没有固有的顺序。为了反映这一点,我们修改输入序列以包含两种可能的句子顺序(中间有分隔符),并分别处理它们以生成两个序列表示 $h^m_l$,这些表示在输入到线性输出层之前按元素相加。</p>
<p>问题回答和常识推理:对于这些任务,我们给定一个上下文文档 z,一个问题 q 和一组可能的答案${a_k}$。我们将文档上下文和问题与每个可能的答案连接在一起,在中间添加一个分隔符标记,得到$[z;q;$;a_k]$。使用我们的模型独立处理这些序列,然后通过 softmax 层进行归一化,以生成可能答案的分布。</p>
<h2 id="4-试验">4. 试验</h2>
<h3 id="41-设置">4.1 设置</h3>
<p>无监督预训练:我们使用 BooksCorpus 数据集[71]来训练语言模型。该数据集包含超过 7,000 本不同的未发表书籍,涵盖了冒险、奇幻和浪漫等多种类型。关键在于,它包含了长篇连续的文本,这使得生成模型能够学会根据长距离的上下文信息进行条件化建模。另一个可替代的数据集是 1B Word Benchmark,它被类似的方法 ELMo[44]所使用,虽然数据集规模大致相同,但在句子层面进行了打乱处理,从而破坏了长距离的结构。在这个语料库上,我们的语言模型取得了非常低的 token 级别困惑度,达到了 18.4。</p>
<table>
<thead>
<tr>
<th>Task</th>
<th>Datasets</th>
</tr>
</thead>
<tbody>
<tr>
<td>Natural language inference</td>
<td>SNLI [5], MultiNLI [66], Question NLI [64], RTE [4], SciTail [25]</td>
</tr>
<tr>
<td>Question Answering</td>
<td>RACE [30], Story Cloze [40]</td>
</tr>
<tr>
<td>Sentence similarity</td>
<td>MSR Paraphrase Corpus [14], Quora Question Pairs [9], STS Benchmark [6]</td>
</tr>
<tr>
<td>Classification</td>
<td>Stanford Sentiment Treebank-2 [54], CoLA [65]</td>
</tr>
</tbody>
</table>
<p>表 1: 本次试验中用到的任务和数据集</p>
<p>模型规格:我们的模型主要依据原始 Transformer 架构[62]。我们训练了一个 12 层的 decoder-only Transformer,具有遮蔽式自注意力机制(768 维状态和 12 个注意力头)。对于逐位置前馈网络,我们使用了 3072 维的内部状态。我们使用了 Adam 优化方案[27],最大学习率为 2.5e-4。学习率在前 2000 次更新中从零线性增加,然后使用余弦调度退火至 0。我们在 64 个随机抽样的连续序列(每个序列包含 512 个标记)上进行 100 个周期的小批量训练。由于 LayerNorm[2]在整个模型中被广泛使用,简单的权重初始化 N(0,0.02) 就足够了。我们使用了包含 40,000 次合并[53]的字节对编码(BPE)词汇表,以及具有 0.1 正则化率的残差、嵌入和注意力 dropout。我们还采用了[37]中提出的一种修改版的 L2 正则化,对所有非偏置或增益权重 w=0.01。对于激活函数,我们使用高斯误差线性单元(GELU)[18]。我们使用学习位置嵌入替代了原始工作中提出的正弦版本。我们使用 ftfy 库来清理 BooksCorpus 中的原始文本,标准化一些标点符号和空白,并使用 spaCy 分词器。</p>
<p>微调细节:除非特别指定,我们会沿用无监督预训练的超参数设置。我们在分类器中添加 0.1 的 dropout 率。对于大多数任务,我们采用 6.25e-5 的学习率和 32 的批次大小。我们的模型能够快速进行微调,在大多数情况下,训练 3 个周期就足够了。我们采用线性学习率衰减调度策略,训练的前 0.2% 阶段进行预热。λ设置为 0.5。</p>
<h3 id="42-有监督微调">4.2 有监督微调</h3>
<p>我们在多种有监督任务上进行实验,包括自然语言推理、问答、语义相似度和文本分类。其中一些任务包含在近期发布的 GLUE 多任务基准测试[64]中,我们也对其进行了使用。图 1 对所有任务和数据集进行了概述。</p>
<p>自然语言推理(NLI)任务,也称为识别文本蕴涵,涉及阅读一对句子并从蕴涵、矛盾或中立关系中判断它们之间的关系。尽管最近已经引起了很多关注[58, 35, 44],但由于存在多种现象,如词汇蕴涵、共指、词汇和句法歧义等,这个任务仍然具有挑战性。我们在五个数据集上进行评估,这些数据集来源丰富,包括图像标题(SNLI)、转录语音、通俗小说和政府报告(MNLI)、维基百科文章(QNLI)、科学考试(SciTail)或新闻文章(RTE)。</p>
<p>表 2 详细展示了我们的模型在不同 NLI 任务中与之前最先进方法的各种对比结果。在五个数据集中的四个上,我们的方法明显优于基准线,相较于之前的最佳结果,在 MNLI 上绝对提升达到 1.5%,在 SciTail 上绝对提升达到 5%,在 QNLI 上绝对提升达到 5.8%,在 SNLI 上绝对提升达到 0.6%。这证明了我们的模型在处理多句子推理和应对语言歧义方面的优势。在 RTE(一个较小的数据集,包含 2490 个示例)上,我们取得了 56% 的准确率,低于多任务双向 LSTM 模型报告的 61.7%。鉴于我们的方法在更大的 NLI 数据集上的强大性能,很可能我们的模型也将从多任务训练中受益,但我们目前尚未探讨这一点。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/GPT1-table2.jpg" alt="表 2:自然语言推理任务的实验结果,将我们的模型与当前最先进的方法进行比较。5x 表示 5 个模型的集成。所有数据集都使用准确率作为评估指标" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>表 2:自然语言推理任务的实验结果,将我们的模型与当前最先进的方法进行比较。5x 表示 5 个模型的集成。所有数据集都使用准确率作为评估指标</figcaption>
</figure></p>
<p>问题回答和常识推理:另一个涉及单句和多句推理的任务是问答。我们采用了最近发布的 RACE 数据集[30],该数据集包含了来自初中和高中考试的英文段落及相关问题。与 CNN [19] 或 SQuaD [47]等其他数据集相比,这个语料库包含了更多的推理类型问题,为我们的模型提供了极佳的评估,因为我们的模型经过训练能够处理长距离的上下文。此外,我们还评估了 Story Cloze Test[40],这个测试要求从两个选项中为多句故事选择正确的结尾。在这些任务上,我们的模型再次明显优于先前的最佳结果——在 Story Cloze 上最高提高了 8.9%,在 RACE 上整体提高了 5.7%。这证明了我们的模型在有效处理长距离上下文方面具有很强的能力。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/GPT1-table3.jpg" alt="表 3:在问答和常识推理方面的结果,将我们的模型与当前最先进的方法进行比较。9x 表示 9 个模型的集成" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>表 3:在问答和常识推理方面的结果,将我们的模型与当前最先进的方法进行比较。9x 表示 9 个模型的集成</figcaption>
</figure></p>
<p>语义相似度:语义相似度(或释义检测)任务包括预测两个句子在语义上是否等价。挑战在于识别概念的重新表述,理解否定语句以及处理句法歧义。我们在这个任务中使用了三个数据集——Microsoft Paraphrase 语料库(MRPC)[14](来源于新闻报道),Quora 问题对(QQP)数据集[9],以及语义文本相似度基准(STS-B)[6]。我们在三个语义相似度任务的两个任务中取得了最佳成绩(表 4),在 STS-B 上的绝对增益达到 1 分。在 QQP 上的性能差距显著,与单任务 BiLSTM + ELMo + Attn 相比,绝对提高了 4.2%。</p>
<p>最后,我们在两个不同的文本分类任务上进行评估。语言可接受性语料库(CoLA)[65] 包含了关于句子是否符合语法规则的专家评判,用以测试训练模型的固有语言偏差。另一方面,斯坦福情感树库(SST-2)[54] 是一个标准的二分类任务。我们的模型在 CoLA 上获得了 45.4 分,这比先前最好的结果 35.0 有显著提高,展示了我们模型所学到的固有语言偏差。在 SST-2 上,模型也取得了 91.3% 的准确率,与最佳结果相当。我们在 GLUE 基准测试上也获得了 72.8 的总分,明显优于先前最好的 68.9。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/GPT1-table4.jpg" alt="表 4:语义相似性和分类结果,将我们的模型与当前最先进的方法进行比较。本表中所有任务评估均使用 GLUE 基准测试进行。(mc= Mathews 相关系数,acc=准确率,pc=Pearson 相关系数)" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>表 4:语义相似性和分类结果,将我们的模型与当前最先进的方法进行比较。本表中所有任务评估均使用 GLUE 基准测试进行。(mc= Mathews 相关系数,acc=准确率,pc=Pearson 相关系数)</figcaption>
</figure></p>
<p>总体来说,我们的方法在 12 个评估数据集中的 9 个上取得了新的最佳成绩,很多情况下超过了集成模型。我们的结果还表明,我们的方法在不同规模的数据集上都表现良好,从较小的数据集如 STS-B(约 5.7k 训练样本)到最大的数据集 SNLI(约 550k 训练样本)。</p>
<h2 id="5-分析">5. 分析</h2>
<p>迁移层数的影响:我们观察了从无监督预训练向有监督目标任务迁移不同数量层数的影响。图 2(左)描绘了我们在 MultiNLI 和 RACE 上的方法性能,作为迁移层数量的函数。我们观察到标准结果,即迁移嵌入可以提高性能,每个 Transformer 层都能进一步带来好处,对于 MultiNLI 的完全转移,可以提高 9%。这表明预训练模型中的每一层都包含解决目标任务的有用功能。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/chatgpt/gpt1-2.jpg" alt="图 2:(左)将从预训练语言模型迁移的层数逐渐增加对 RACE 和 MultiNLI 的影响。(右)图表显示了 LM 预训练更新作为函数的不同任务零样本性能的演变。任务性能在随机猜测基线和使用单一模型的当前最先进技术之间归一化" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>图 2:(左)将从预训练语言模型迁移的层数逐渐增加对 RACE 和 MultiNLI 的影响。(右)图表显示了 LM 预训练更新作为函数的不同任务零样本性能的演变。任务性能在随机猜测基线和使用单一模型的当前最先进技术之间归一化</figcaption>
</figure></p>
<p>零样本表现:我们希望更好地了解为什么对 Transformer 进行语言模型预训练是有效的。一个假设是底层生成模型为了提高其语言建模能力,学会了我们用于评估的许多任务,而 Transformer 相比 LSTM 的更结构化的注意力记忆有助于更好地迁移。我们设计了一系列启发式解决方案,使用底层生成模型在无监督微调的情况下执行任务。我们在图 2(右)中显示了在生成预训练过程中这些启发式解决方案的有效性。我们观察到这些启发式方法的性能在训练过程中稳定且稳步提高,表明生成预训练支持学习各种与任务相关的功能。我们还观察到 LSTM 在零样本性能方面波动较大,表明 Transformer 架构的归纳偏差有助于迁移。</p>
<p>对于 CoLA(语言可接受性),将示例按照生成模型分配的平均 token 对数概率进行评分,并通过阈值进行预测。对于 SST-2(情感分析),我们将 very 这个 token 添加到每个示例后面,并将语言模型的输出分布仅限于单词 positive 和 negative,认定被分配到的更高概率的 token 作为预测结果。对于 RACE(问答),我们选择在给定文档和问题的条件下,生成模型分配的具有最高平均 token 对数概率的答案。对于 DPRD [46](Winograd 模式),我们用两个可能的指代物替换定指代词(确定的指代词),并预测生成模型在替换后的序列其余部分分配较高平均 token 对数概率的解析。</p>
<table>
<thead>
<tr>
<th>Method</th>
<th>Avg. Score</th>
<th>CoLA (mc)</th>
<th>SST2(acc)</th>
<th>MRPC(F1)</th>
<th>STSB(pc)</th>
<th>QQP(F1)</th>
<th>MNLI(acc)</th>
<th>QNLI(acc)</th>
<th>RTE(acc)</th>
</tr>
</thead>
<tbody>
<tr>
<td>Transformer w/ aux LM (full)</td>
<td>74.7</td>
<td>45.4</td>
<td>91.3</td>
<td>82.3</td>
<td>82.0</td>
<td>70.3</td>
<td>81.8</td>
<td>88.1</td>
<td>56.0</td>
</tr>
<tr>
<td>Transformer w/o pre-training</td>
<td>59.9</td>
<td>18.9</td>
<td>84.0</td>
<td>79.4</td>
<td>30.9</td>
<td>65.5</td>
<td>75.7</td>
<td>71.2</td>
<td>53.8</td>
</tr>
<tr>
<td>Transformer w/o aux LM</td>
<td>75.0</td>
<td>47.9</td>
<td>92.0</td>
<td>84.9</td>
<td>83.2</td>
<td>69.8</td>
<td>81.1</td>
<td>86.9</td>
<td>54.4</td>
</tr>
<tr>
<td>LSTM w/ aux LM</td>
<td>69.1</td>
<td>30.3</td>
<td>90.5</td>
<td>83.2</td>
<td>71.8</td>
<td>68.1</td>
<td>73.7</td>
<td>81.1</td>
<td>54.6</td>
</tr>
</tbody>
</table>
<p>消融研究:我们进行了三种不同的消融研究(表 5)。首先,我们在微调过程中检查没有辅助 LM 目标的方法性能。我们观察到,辅助目标在 NLI 任务和 QQP 上有所帮助。总体趋势表明,较大的数据集从辅助目标中受益,而较小的数据集则没有。其次,我们通过将 Transformer 与使用相同框架的单层 2048 单元 LSTM 进行比较,分析了 Transformer 的效果。我们观察到,在使用 LSTM 代替 Transformer 时,平均分数下降了 5.6 分。LSTM 仅在一个数据集上胜过 Transformer —— MRPC。最后,我们还将直接在监督目标任务上训练的 Transformer 架构与没有预训练的情况进行比较。我们观察到,缺乏预训练会影响所有任务的性能,与我们的完整模型相比,下降了 14.8%。</p>
<h2 id="6-结论">6. 结论</h2>
<p>我们提出了一个框架,通过生成式预训练和判别式微调,让单一的任务无关模型具备强大的自然语言理解能力。通过在包含长篇连续文本的多样化语料库上进行预训练,我们的模型学会了丰富的世界知识和处理长距离依赖关系的能力。然后,将这些知识和能力成功地应用于解决判别性任务,例如问答、语义相似度评估、蕴含关系判断和文本分类,从而在我们研究的 12 个数据集中,提高了其中 9 个的最高技术水平。在机器学习研究中,利用无监督(预)训练来提高判别任务性能一直是一个重要的目标。我们的工作表明,确实可以实现显著的性能提升,并为哪些模型(如 Transformer)和数据集(具有长距离依赖关系的文本)最适合这种方法提供了参考。我们希望这将有助于推动新的无监督学习研究,无论是在自然语言理解还是其他领域,进一步提高我们对无监督学习如何以及何时发挥作用的理解。</p>
<div class="footnotes">
<ol>
<li id="fn:tr">
<p>在自然语言处理(NLP)中,text representations(文本表示)是将文本转换为计算机可以处理的形式的过程。文本表示是 NLP 中的一个重要问题,因为计算机无法直接处理自然语言。因此,研究人员需要将文本转换为计算机可以理解的形式,以便进行下一步的处理,如分类、聚类、信息检索等。文本表示的目标是将文本转换为向量或矩阵的形式,使得计算机可以对其进行数学运算和统计分析。常用的文本表示方法包括以下几种:1) One-hot representation(独热表示):将每个单词表示为一个向量,其中只有一个元素是 1,其余元素均为 0。该方法不考虑单词之间的语义和上下文信息,因此效果有限。2) Bag-of-words representation(词袋表示):将文本表示为一个单词的集合,并统计每个单词出现的次数。该方法考虑了单词的出现频率,但忽略了单词之间的顺序和语义信息。3) Word embedding(词嵌入):将每个单词表示为一个低维向量,使得单词之间的距离反映了它们之间的语义关系。Word embedding 通常使用神经网络模型进行训练,例如 Word2Vec 和 GloVe 等。4) Transformer-based representation(基于 Transformer 的表示):最近的文本表示方法中,基于 Transformer 的表示方法已经成为主流。Transformer 是一种使用自注意力机制的神经网络,可以将文本转换为一系列向量,每个向量代表一个单词或短语的语义信息。BERT、GPT 等是基于 Transformer 的表示方法的代表。 <a href="#fnref:tr" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:tna">
<p>fine-tune 翻译成微调;adaption 翻译成调整,有些翻译成适应性、适应,个人感觉在这里是调整的意思。后文也提到了,对于特定的任务,会把输入的词句做一些简单的调整(比如添加分隔符等)。 <a href="#fnref:tna" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:dtm">
<table>
<tbody>
<tr>
<td>在机器学习中,discriminatively trained model(判别式训练模型)是一种常见的模型训练方法。该方法的主要目标是学习一个函数,该函数可以将输入数据映射到其相应的输出。与生成式模型不同,判别式模型不是直接建模联合分布 P(X,Y),而是直接建模条件分布 $P(Y</td>
<td>X)$,也就是在给定输入 X 的情况下预测输出 Y。判别式模型通常使用梯度下降等优化方法进行训练,优化目标是最大化对数据集的条件对数似然估计,或最小化分类误差等评价指标。在训练过程中,模型根据输入数据和其正确的标签来调整模型的权重和偏差,以便更准确地预测输出。相对于生成式模型,判别式模型在许多实际应用中具有更好的性能,特别是在分类、标注、聚类等任务中。因为判别式模型直接建模了输入和输出之间的映射关系,可以更好地适应不同的输入分布和输出分布。判别式模型还具有更快的训练速度和更高的泛化能力,这使得它们成为机器学习领域的重要工具之一。需要注意的是,判别式模型的性能取决于训练数据的质量和多样性,以及模型的选择和参数调整。因此,在应用判别式模型时,需要仔细选择训练数据和模型参数,以获得最佳的性能。</td>
</tr>
</tbody>
</table>
<p><a href="#fnref:dtm" class="reversefootnote">&#8617;</a></p>
</li>
</ol>
</div>
</description>
<pubDate>Wed, 05 Apr 2023 23:32:00 +0800</pubDate>
<link>http://wulfric.me/2023/04/gpt1-ch/</link>
<guid isPermaLink="true">http://wulfric.me/2023/04/gpt1-ch/</guid>
<category>ai</category>
<category>chatgpt</category>
<category>技术</category>
</item>
<item>
<title>JAVA 11 MAC 源码安装与调试</title>
<description><p>JAVA 11 是 JAVA 8 之后的第一个 LTS 版本,为了了解下一代 JAVA 版本更新的内容,也为了能调试到 JAVA 的内部代码,所以笔者决定源码安装试试。</p>
<h2 id="安装">安装</h2>
<p>官方给出的安装教程<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup>是这样的:</p>
<ol>
<li>下载源码:<span class="codespan">hg clone http://hg.openjdk.java.net/jdk/jdk</span></li>
<li>执行configure:<span class="codespan">bash configure</span>。缺失依赖会导致 configure 失败,但错误日志都会给出操作建议,根据建议安装依赖即可</li>
<li>执行 make:<span class="codespan">make images</span></li>
<li>验证新构建的 java 可用:<span class="codespan">./build/*/images/jdk/bin/java -version</span></li>
<li>测试:<span class="codespan">make run-test-tier1</span></li>
</ol>
<p>这个安装步骤大体上没问题,但是有些差异需要指出,因此我也给出我的安装步骤。</p>
<h3 id="安装依赖">安装依赖</h3>
<p>安装 jre 11 运行时,这个可以通过 brew 来安装,或者直接下载 <a href="https://www.oracle.com/technetwork/java/javase/downloads/jdk11-downloads-5066655.html">oracle</a> 或 <a href="https://jdk.java.net/java-se-ri/11">openjdk</a>。</p>
<p>因为编译的是比较新的 jdk 11,直接用 xcode 最新版即可(官方推荐的是 9.4,我用的是最新版 10.3 没问题)。</p>
<p>通过 brew 安装 autoconf 和 freetype。</p>
<h3 id="获取源码">获取源码</h3>
<p>从 jdk 10 开始,源码不再分散在不同的仓库中,所以只需要 clone 单独的 repository 即可<sup id="fnref:1:1"><a href="#fn:1" class="footnote">1</a></sup>。我选择是 <a href="http://hg.openjdk.java.net/jdk-updates/jdk11u">jdk11u</a>,而且不是通过 hg clone 的方式(比较慢,经常出错需要重试),而是直接下载整个源码包,如下图示。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-download.png" alt="jdk11 download" title="jdk11 download" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 download</figcaption>
</figure></p>
<h3 id="编译">编译</h3>
<p>执行 configure,为了 debug,需要加上对应的参数。</p>
<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>sh configure <span class="nt">--with-target-bits</span><span class="o">=</span>64 <span class="nt">--enable-ccache</span> <span class="nt">--with-jvm-variants</span><span class="o">=</span>server <span class="nt">--with-boot-jdk-jvmargs</span><span class="o">=</span><span class="s2">"-Xlint:deprecation -Xlint:unchecked"</span> <span class="nt">--disable-warnings-as-errors</span> <span class="nt">--with-debug-level</span><span class="o">=</span>slowdebug 2&gt;&amp;1 | tee configure_mac_x64.log
</code></pre></div></div>
<p>一般第一次执行总是会遇到些小问题,但编译 jdk 11 的问题比 jdk8,9少多了,根据提示很容易就可以解决。当看到如下返回即表明配置成功。</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">====================================================</span>
A new configuration has been successfully created <span class="k">in</span>
/Users/haidao/Downloads/openjdk11/build/macosx-x86_64-normal-server-slowdebug
using configure arguments <span class="s1">'--with-target-bits=64 --enable-ccache --with-jvm-variants=server --with-boot-jdk-jvmargs='</span><span class="nt">-Xlint</span>:deprecation <span class="nt">-Xlint</span>:unchecked<span class="s1">' --disable-warnings-as-errors --with-debug-level=slowdebug'</span><span class="nb">.</span>
Configuration summary:
<span class="k">*</span> Debug level: slowdebug
<span class="k">*</span> HS debug level: debug
<span class="k">*</span> JVM variants: server
<span class="k">*</span> JVM features: server: <span class="s1">'aot cds cmsgc compiler1 compiler2 dtrace epsilongc g1gc graal jfr jni-check jvmci jvmti management nmt parallelgc serialgc services vm-structs'</span>
<span class="k">*</span> OpenJDK target: OS: macosx, CPU architecture: x86, address length: 64
<span class="k">*</span> Version string: 11-internal+0-adhoc.haidao.openjdk11 <span class="o">(</span>11-internal<span class="o">)</span>
Tools summary:
<span class="k">*</span> Boot JDK: openjdk version <span class="s2">"11.0.2"</span> 2019-01-15 OpenJDK Runtime Environment 18.9 <span class="o">(</span>build 11.0.2+9<span class="o">)</span> OpenJDK 64-Bit Server VM 18.9 <span class="o">(</span>build 11.0.2+9, mixed mode<span class="o">)</span> <span class="o">(</span>at /Library/Java/JavaVirtualMachines/openjdk-11.0.2.jdk/Contents/Home<span class="o">)</span>
<span class="k">*</span> Toolchain: clang <span class="o">(</span>clang/LLVM from Xcode 10.3<span class="o">)</span>
<span class="k">*</span> C Compiler: Version 10.0.1 <span class="o">(</span>at /usr/bin/clang<span class="o">)</span>
<span class="k">*</span> C++ Compiler: Version 10.0.1 <span class="o">(</span>at /usr/bin/clang++<span class="o">)</span>
Build performance summary:
<span class="k">*</span> Cores to use: 4
<span class="k">*</span> Memory limit: 8192 MB
<span class="k">*</span> ccache status: Active <span class="o">(</span>3.7.2<span class="o">)</span>
</code></pre></div></div>
<p>然后执行 make 即可。根据 build 文档<sup id="fnref:1:2"><a href="#fn:1" class="footnote">1</a></sup>,执行 make 不带任何参数等同于<span class="codespan">make default</span>和<span class="codespan">make jdk</span>,这会 build 出一个较小的编译结果,并提供一个 exploded image。不知道怎么翻译 exploded image,大概意思是,这是一个分解开的镜像,可以直接使用,各个模块都是解压好的,不包含源码。这种设计是为了方便 jdk 的开发者渐进式开发,每次 make 只会 recompile 变化的部分。</p>
<p>其他 make 的常用 target 如下:</p>
<ul>
<li><span class="codespan">hotspot</span> - Build all of hotspot (but only hotspot)</li>
<li><span class="codespan">hotspot-&lt;variant&gt;</span> - Build just the specified jvm variant</li>
<li><span class="codespan">images</span> or <span class="codespan">product-images</span> - Build the JDK image</li>
<li><span class="codespan">docs</span> or <span class="codespan">docs-image</span> - Build the documentation image</li>
<li><span class="codespan">test-image</span> - Build the test image</li>
<li><span class="codespan">all</span> or <span class="codespan">all-images</span> - Build all images (product, docs and test)</li>
<li><span class="codespan">bootcycle-images</span> - Build images twice, second time with newly built JDK (good for testing)</li>
<li><span class="codespan">clean</span> - Remove all files generated by make, but not those generated by configure</li>
<li><span class="codespan">dist-clean</span> - Remove all files, including configuration</li>
</ul>
<p>我们以 $BUILD 表示构建结果目录<sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup>,构建结果目录如下<sup id="fnref:1:3"><a href="#fn:1" class="footnote">1</a></sup>:</p>
<ul>
<li><span class="codespan">jdk</span>: 这就是之前所说的 exploded image 的目录。执行<span class="codespan">make jdk</span>之后,你可以通过运行<span class="codespan">$BUILD/jdk/bin/java</span>直接启动新构建的 JDK。</li>
<li><span class="codespan">images</span>: 这个目录是 make *-image 的输出位置。例如,<span class="codespan">make jdk-image</span> 会构建出 jdk image, 目录是<span class="codespan">images/jdk</span>。</li>
<li><span class="codespan">test-results</span>: 测试结果目录。</li>
<li><span class="codespan">support</span>: 这个目录保存的是 build 过程中的中间文件,比如源码,对象文件,类文件等。<span class="codespan">support</span>中比较重要的是<span class="codespan">gensrc</span>,它包含生成的源码;<span class="codespan">modules_*</span> 包含了按模块层级分布的文件,它会在之后合并到 jdk 目录下。</li>
</ul>
<p>由于我们执行的是最简构建,我们主要看下 $BUILD/jdk 和 $BUILD/images/jdk 的差异:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$BUILD</span>/images/jdk <span class="nv">$BUILD</span>/jdk
├── bin ├── _packages_attribute.done
├── conf ├── bin
├── demo ├── conf
├── include ├── include
├── jmods ├── lib
├── legal ├── modules
├── lib └── release
├── man
└── release
</code></pre></div></div>
<p>和 $BUILD/jdk 不一样,$BUILD/images/jdk 这个目录下没有解压好的 modules 目录,而是以压缩包的形式放在 jmods 下。这是自 JAVA 9 引入的模块化打包设计,旨在减小 JAVA 应用的包体积,使得部署更加轻量。</p>
<p>$BUILD/jdk 下的 lib 目录是动态库文件和调试信息文件,不包含源码。而 $BUILD/images/jdk 下的 lib 目录下的动态库文件没有可执行权限,但包含源码 src.zip。</p>
<p>$BUILD/jdk 作为 exploded image,不像 product-image 那样需要包含法律文件和 demo。</p>
<p>除了这些区别之外,二者在使用上没啥差别。</p>
<p>现在,可以直接通过 $BUILD/jdk/bin/java 来使用编译出来的 JAVA11。你也可以配置 jenv:<span class="codespan">jenv add $BUILD/jdk</span>。关于 jenv 的使用请参看 <a href="/2017/03/macos-jenv/">Mac OS 使用 jenv 管理 java 版本</a>。</p>
<h2 id="配置-idea">配置 IDEA</h2>
<p>增加 SDK,配置 classpath 和 sourcepath。配置好 sourcepath 便可以正常查看代码了。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-classpath.png" alt="jdk11 classpath" title="jdk11 classpath" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 classpath</figcaption>
</figure></p>
<p>如果使用的是 $BUILD/images/jdk,直接将该目录加入到 classpath 即可,IDE 会自动识别 src.zip 并放在 sourcepath 中;</p>
<p>如果使用的是 $BUILD/jdk,由于这是 exploded image jdk,不包含源码,所以需要分别加入 classpath 和 sourcepath,sourcepath 即下载的 openjdk 下的 src 目录。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-sourcepath.png" alt="jdk11 sourcepath" title="jdk11 sourcepath" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 sourcepath</figcaption>
</figure></p>
<p>其他需要修改的配置不在赘述,如下图示。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-ide-conf.png" alt="jdk11 ide conf" title="jdk11 ide conf" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 ide conf</figcaption>
</figure></p>
<h2 id="配置调试">配置调试</h2>
<p>在 IDE sdk 中配置好 jdk 11 之后便可以正常调试 JAVA 代码了。如果我们想调试 jdk/jvm 源码的 C 代码怎么办呢?我们已经 build 一个可以 debug 的 java11 运行环境,下面就是配置 IDE 来 debug。</p>
<h3 id="clion-调试-jvm-代码">clion 调试 jvm 代码</h3>
<p>首先导入源码。使用 New Cmake Project from Sources,这样可以自动创建 CMakeLists.txt 文件。然后按照引导即可导入源码。你可以导入 src/hotspot,也可以整个导入 src。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/clion-debug-jvm.png" alt="clion debug jvm" title="clion debug jvm" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>clion debug jvm</figcaption>
</figure></p>
<p>导入之后,具体的 cpp 文件会报错,但是不影响调试,可以暂时忽略。</p>
<p>导入成功,reload CMakeLists.txt 之后,会自动生成一个 debug configuration。下面配置 debug configuration。如图所示,将 executable 改为你的 java 二进制文件,然后在 program arguments 里设置程序参数。我们先设置为 -version。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-debug1.png" alt="jdk11 debug1" title="jdk11 debug1" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 debug1</figcaption>
</figure></p>
<p>此时我们在<span class="codespan">share/prims/jni.cpp</span>文件上打一个断点,然后执行 debug,就可以看到效果了。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/java/jdk11-debug2.png" alt="jdk11 debug2" title="jdk11 debug2" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>jdk11 debug2</figcaption>
</figure></p>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>每个下载的 jdk 中都有一个 build 文档,一般在 doc 目录下。这是编译安装该 jdk 的最权威的参考文档。jdk11 的在线 build 文档是:<a href="https://hg.openjdk.java.net/jdk/jdk11/raw-file/tip/doc/building.html">Building the JDK 11</a> <a href="#fnref:1" class="reversefootnote">&#8617;</a> <a href="#fnref:1:1" class="reversefootnote">&#8617;<sup>2</sup></a> <a href="#fnref:1:2" class="reversefootnote">&#8617;<sup>3</sup></a> <a href="#fnref:1:3" class="reversefootnote">&#8617;<sup>4</sup></a></p>
</li>
<li id="fn:2">
<p>$BUILD 表示你构建出来的结果的目录,一般是你下载的 openjdk11 下的 build/xxx,xxx 是一个动态的名称,和你的开发机类型、编译参数有关,在我的机器上是 ./openjdk11/build/macosx-x86_64-normal-server-slowdebug。 <a href="#fnref:2" class="reversefootnote">&#8617;</a></p>
</li>
</ol>
</div>
</description>
<pubDate>Mon, 02 Sep 2019 23:32:00 +0800</pubDate>
<link>http://wulfric.me/2019/09/java11-install-debug/</link>
<guid isPermaLink="true">http://wulfric.me/2019/09/java11-install-debug/</guid>
<category>java</category>
<category>技术</category>
</item>
<item>
<title>对敏捷开发实践的思考</title>
<description><h2 id="背景">背景</h2>
<p>在加入现在的公司之前,我曾经在一家创业公司工作过几年,经历了从4,5人发展到上百人的过程。一开始老板就按照敏捷开发的套路执行项目管理。说是敏捷开发,其实也不算全面,主要是每日站会、看板管理、快速迭代等。之后由于随着人数扩大,但没有相应的调整开发策略,导致敏捷开发渐渐事实上废弃掉了。后面又由于要支持业务快速增长,项目开发质量没有很好的控制住,导致产品体验不够好,系统质量不稳定。为了解决这些问题,敏捷开发又被重新推到台前。</p>
<p>本文便是我在这段经历中,对敏捷开发的见识和理解。出于安全考虑,我们将业务特定场景转换了通用场景,并给我们的虚拟公司起一个名字:让世界变得更加热闹的上市公司,简称 SOS 公司(好老的梗)。</p>
<h2 id="开发模式简介">开发模式简介</h2>
<p>我想先介绍一下几种常见的开发模式,如果您不关心,这部分可以跳过。</p>
<p>在项目开发中,我们常用到下面几种开发模式。</p>
<h3 id="瀑布式开发">瀑布式开发</h3>
<p><a href="https://www.wikiwand.com/en/Waterfall_model">瀑布式开发</a>是一种传统的计算机软件开发方法。它强调开发进度像瀑布一样,沿着固定的状态流动。即严格按照预先计划的需求分析、设计、编码、集成、测试、维护的步骤顺序进行。</p>
<p><figure>
<picture>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Waterfall_model.svg/800px-Waterfall_model.svg.png?1546152701225" alt="瀑布式开发--来自维基百科" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>瀑布式开发--来自维基百科</figcaption>
</figure></p>
<h3 id="迭代式开发">迭代式开发</h3>
<p><a href="https://www.wikiwand.com/en/Iterative_and_incremental_development">迭代式开发</a>也被称作迭代增量式开发或迭代进化式开发,是一种与传统的瀑布式开发相反的软件开发过程,它弥补了传统开发方式中的一些弱点,具有更高的成功率和生产率。</p>
<p>在迭代式开发方法中,整个开发工作被组织为一系列的短小的、固定长度(如3周)的小项目,被称为一系列的迭代。每一次迭代都包括了需求分析、设计、实现与测试。采用这种方法,开发工作可以在需求被完整地确定之前启动,并在一次迭代中完成系统的一部分功能或业务逻辑的开发工作。再通过客户的反馈来细化需求,并开始新一轮的迭代。</p>
<p><figure>
<picture>
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/39/Iterative_development_model.svg/640px-Iterative_development_model.svg.png?1546154989142" alt="迭代式开发--来自维基百科" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>迭代式开发--来自维基百科</figcaption>
</figure></p>
<h3 id="螺旋式开发">螺旋式开发</h3>
<p><a href="https://www.wikiwand.com/en/Spiral_model">螺旋模型</a>是一种演化软件开发过程模型,它兼顾了快速原型的迭代的特征以及瀑布模型的系统化与严格监控。螺旋模型最大的特点在于引入了其他模型不具备的风险分析,使软件在无法排除重大风险时有机会停止,以减小损失。同时,在每个迭代阶段构建原型是螺旋模型用以减小风险的途径。螺旋模型更适合大型的昂贵的系统级的软件应用。</p>
<p>可以看出,螺旋式开发强调风险评估,同样也是一种迭代,只不过是螺旋式的迭代<sup id="fnref:1"><a href="#fn:1" class="footnote">1</a></sup>。</p>
<p><figure>
<picture>
<img src="https://cdn-images-1.medium.com/max/1600/0*xUPmGSQEX-40Z7Ag.png" alt="螺旋式开发" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>螺旋式开发</figcaption>
</figure></p>
<h3 id="敏捷开发">敏捷开发</h3>
<p><a href="https://www.wikiwand.com/en/Agile_software_development">敏捷软件开发</a>(英语:Agile software development),又称敏捷开发,是一种从1990年代开始逐渐引起广泛关注的一些新型软件开发方法,是一种应对快速变化的需求的一种软件开发能力。它们的具体名称、理念、过程、术语都不尽相同,相对于“非敏捷”,更强调程序员团队与业务专家之间的紧密协作、面对面的沟通(认为比书面的文档更有效)、频繁交付新的软件版本、紧凑而自我组织型的团队、能够很好地适应需求变化的代码编写和团队组织方法,也更注重软件开发过程中人的作用。</p>
<p>敏捷开发最着重强调的是价值观而不是具体的形式或流程,这是它和其他开发模式很不一样的地方。也因为这样,在具体的敏捷开发的实践中,瀑布、迭代、螺旋等开发方式都可能会用到,而差别主要在于:敏捷开发往往有更小的迭代规模<sup id="fnref:2"><a href="#fn:2" class="footnote">2</a></sup>;敏捷开发不应过度设计未知的需求<sup id="fnref:3"><a href="#fn:3" class="footnote">3</a></sup>。</p>
<p>从上面的介绍我们可以看出来,其实实际的大多数开发方式都是混搭的。很多公司或团队会声称使用敏捷开发,实际使用中很有可能会退化到其他基本开发方式。这些都是正常的,合适的才是最好的。如果一个好的开发模型实施起来很有难度,要么该模型不适合当前场景,要么使用方法不恰当。这方面后面也会提到。</p>
<h2 id="早期敏捷实践">早期敏捷实践</h2>
<p>SOS 公司的团队早期人数较少,业务也不是很复杂,主要目的是快速出 MVP,接受市场的评估并及时跟进。所以在初创期就已经施行了一个比较粗糙的敏捷开发。主要内容是:</p>
<ul>
<li>每日站会,介绍前一天的工作内容和遇到的问题</li>
<li>快速迭代上线</li>
<li>通过看板管理项目进度,排期计划</li>
</ul>
<p>每日站会的目的就是互通有无,大家能够快速了解小组成员的工作内容,潜在风险等。这个时候每日站会,大家可以快速的了解同事目前在做哪些工作,遇到哪些问题,而这些问题很可能自己这几天刚刚遇到过。基于早期业务发展方向比较单一,内容也比较少,通过每日站会,所有开发者几乎都可以对业务有一个比较清晰的全局认识。</p>
<p>随着业务发展和壮大,员工数量也开始大量增长,业务需求日益复杂。此时大家渐渐感觉每日站会不是那么有效了。早期还是不过 5,6 个人一起站会,但这时经常是 10 好几个人甚至 20 多人,而且大家的业务内容的差异也越来越大。显而易见,同事们开始听不懂对方站会汇报的内容了,站会上提出的问题也不再关心。同时由于人数太多,站会也渐渐变得冗长。这种情况下,我相信每次开站会的时候很多人都在开小差:这个人说的是啥?那个人遇到的问题感觉和我没多大关系啊?昨天晚上那把飞龙骑脸怎么也输了?午饭吃啥好?</p>
<p>经过一段时间痛苦的折磨,这种每日站会渐渐就废弃了,仅保留每周周会和勉强及格的迭代速度。同时由于业务差异性,不再强制每个开发都使用看板管理项目,而是分到具体的小组由每个小组自己负责。对于某些不方便拆分任务的业务小组,看板这个工具也渐渐处于事实上的弃用状态。</p>
<p>SOS 公司的业务仍然在快速发展中,业务需求迫使产品设计和项目开发无法很好的保证质量和可扩展性。在这一过程中,渐渐出现如下几个问题:</p>
<ul>
<li>产品质量下降,产品逻辑有不自洽的地方;代码质量下降,bug 增多,用户体验下降</li>
<li>早期建立起来的核心竞争力优势开始下降,竞品已经渐渐追上</li>
<li>粗糙的单体应用架构陈旧,无法承载日益繁多和复杂的业务需求</li>
</ul>
<p>造成这些问题的原因是多种多样的,并非全是产品和技术开发模式的过错,激进的市场计划和营销策略等也会带来一定的影响。但这个问题归根结底是一个技术问题,可以通过技术的手段解决。</p>
<h2 id="解决方案">解决方案</h2>
<p>定义了存在的问题,并抽象归纳以批量解决问题,SOS 公司决定实施两大战略:增长和重构。</p>
<p>为了实施这两个战略,需要向运营和市场说明当前问题的紧迫性,赢得他们的支持。事实上,这件事还是挺难的,以至于运营一直以为产品研发团队的当前战略是:改 bug。战略实施者需要当前要做的事情的本质诉求,以及背后的价值和意义。只有运营充分理解这两个战略,才能更好的配合产品和研发的工作(暂缓业务拓展和承接需求)。</p>
<p>增长战略的工作内容有:重新梳理用户需求,整理出用户故事地图,并建立用户画像,分清客户的行业背景,使用习惯、痛点等的差异。使用敏捷开发,全面优化产品的每个阶段(导流,留存,沉淀等等)。</p>
<p><figure>
<picture>
<img src="http://static.wulfric.me/R-aarrr-model.png" alt="AARRR 模型" width= height= />
</picture>
<figcaption><i class='icon-pencil'></i>AARRR 模型</figcaption>
</figure></p>
<p>如上图所示,产品团队针对 AARRR 模型开了 5 个小组,分别研究:</p>
<ul>
<li>获取用户:增加下载量、激活量,增加新用户数,降低用户获客成本</li>
<li>激发活跃:增加七日回访率,增加登录和使用时长</li>
<li>提高留存:增加次日留存率、七日留存率,降低用户流失率</li>
<li>增加收入:增加复购率、付费率,增加人均付费额</li>
<li>推荐传播:增加转发率,增加二次推荐率,增加搜索引擎收录数</li>
</ul>
<p>在每个小组内部,都会根据用户画像(行业化背景等)梳理对应的方案。我对产品的工作不怎么了解,这里就不多介绍了。落实到技术上就是,产品给出项目计划大图和里程碑目标,并和研发一起拆分需求,设计上要尽量能够支持 2 周即可完成一个迭代。</p>
<p>另一部分就是重构单体应用。单体应用的重构,或者说服务化拆分,文章、书籍非常多,而针对具体的场景和业务差别也挺大的,就不班门弄斧了,只谈谈我们做了什么。</p>
<p>服务化拆分最重要的当然就是梳理领域模型,划分限界上下文。但在我们规划中给这部分留的时间太少了。在梳理领域模型的时候,我们要求整理该领域所有的对外提供的业务员接口和内部调用关系,这都需要对业务熟悉的老员工来整理,因此往往是 Blocked 的,而即使是老员工,也几乎没有对该领域所有业务内容都熟悉的,因此这部分工作是延期的;整理抽取出限界上下文之后,需要评估有多少重写的工作量,比如参数校验、权限校验、BO、DO、DTO 等都有可能,但这部分有些轻视,低估了工作量,最终也导致了延期。</p>
<p>但接口梳理工作做得好的直接好处就是:敏捷迭代的拆分变得极为简单。我们以对外提供服务的接口为粒度分配敏捷开发迭代任务,在业务梳理清楚,基础模块重写工作做完之后,每个接口从请求一直到数据库的工作量的评估就非常容易,而且每个接口背后的工作量都不大。按照我们的应用的规模和实际的经验,每 2 周能重构 15 个左右的接口。在早期磨合<sup id="fnref:4"><a href="#fn:4" class="footnote">4</a></sup>完成之后几乎不会因为自身原因延期。同时,我们对每个接口都设置了路由切换,每次迭代的任何接口都可以随时回滚,大大降低了上线风险。</p>
<div class="footnotes">
<ol>
<li id="fn:1">
<p>https://medium.com/existek/sdlc-models-explained-agile-waterfall-v-shaped-iterative-spiral-e3f012f390c5 <a href="#fnref:1" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:2">
<p>https://stackoverflow.com/questions/11842318/difference-between-agile-and-iterative-and-incremental-development <a href="#fnref:2" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:3">
<p>https://stackoverflow.com/questions/253789/agile-vs-spiral-model-for-sdlc <a href="#fnref:3" class="reversefootnote">&#8617;</a></p>
</li>
<li id="fn:4">
<p>为了保障敏捷开发的快速迭代得以实现,我们补上了之前比较欠缺的服务监控、数据库 sql 监控;代码健康分检查(用 sonarqube 检查测试覆盖率,code smell 和 bug 数);单元测试和服务压测。这部分在早期磨合的时候会造成一定程度的延期。 <a href="#fnref:4" class="reversefootnote">&#8617;</a></p>
</li>
</ol>
</div>
</description>
<pubDate>Sun, 30 Dec 2018 18:59:00 +0800</pubDate>
<link>http://wulfric.me/2018/12/agile-practice/</link>
<guid isPermaLink="true">http://wulfric.me/2018/12/agile-practice/</guid>
<category>agile</category>
<category>敏捷</category>
<category>技术</category>
</item>
<item>
<title>nginx 502 和 504 超时演示</title>
<description><p>最近线上 nginx 遇到了一些较难排查的 502 和 504 错误,顺便了解了一下 nginx 的相关配置。我发现网上很多介绍 nginx 超时配置只是列了这几个配置的含义和数值,并没有解释什么原因会触发哪个配置。因此趁这个机会演示一下,如何让 nginx 符合预期正确出现 502 和 504。</p>
<h2 id="502-和-504-的解释">502 和 504 的解释</h2>
<p>在 http status 的 <a href="https://www.wikiwand.com/en/List_of_HTTP_status_codes">定义</a> 中:</p>
<ul>
<li>502 Bad Gateway: The server was acting as a <a href="https://www.wikiwand.com/en/Gateway_(telecommunications)">gateway</a> or proxy and received an invalid response from the upstream server.</li>
<li>504: he server was acting as a gateway or proxy and did not receive a timely response from the upstream server.</li>
</ul>
<p>502 的错误原因是 Bad Gateway,一般是由于上游服务的故障引起的;而 504 则是 nginx 访问上游服务超时,二者完全是两个意思。但在某些情况下,上游服务的超时(触发 tcp reset)也可能引发 502,我们会在之后详述。</p>
<h2 id="演示环境">演示环境</h2>
<p>你需要 3 个逻辑组件:nginx 服务器,php-fpm,client 访问客户端。3 个组件可以在同一台机器中,我用的是 docker 来配置 PHP 和 nginx 环境,在宿主机上访问。如果你很熟悉这 3 个组件,这部分可以跳过。用 docker 来做各种测试和实验非常方便,这里就不展开了。docker-compose 的配置参考了这篇<a href="http://geekyplatypus.com/dockerise-your-php-application-with-nginx-and-php7-fpm/">文章</a>。我的 docker composer 文件如下:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">version</span><span class="pi">:</span> <span class="s1">'</span><span class="s">3'</span>
<span class="na">services</span><span class="pi">:</span>
<span class="na">web</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">nginx:alpine</span>
<span class="na">ports</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s2">"</span><span class="s">8080:80"</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./code:/code</span>
<span class="pi">-</span> <span class="s">./nginx/site.conf:/etc/nginx/conf.d/site.conf</span>
<span class="na">depends_on</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">php</span>
<span class="na">php</span><span class="pi">:</span>
<span class="na">image</span><span class="pi">:</span> <span class="s">php:7.1-fpm-alpine</span>
<span class="na">volumes</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./code:/code</span>
<span class="pi">-</span> <span class="s">./php/php-fpm.conf:/usr/local/etc/php-fpm.conf</span>
</code></pre></div></div>
<p>使用的镜像都是基于 <a href="https://hub.docker.com/_/alpine/">alpine</a> 制作的,非常小巧:</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>REPOSITORY TAG SIZE
php 7.1-fpm-alpin 69.5MB
nginx alpine 18.6MB
</code></pre></div></div>
<p>nginx 的配置:</p>
<div class="language-nginx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">server</span> <span class="p">{</span>
<span class="kn">index</span> <span class="s">index.php</span> <span class="s">index.html</span><span class="p">;</span>
<span class="kn">server_name</span> <span class="s">php-docker.local</span><span class="p">;</span>