-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathatom.xml
More file actions
577 lines (292 loc) · 267 KB
/
Copy pathatom.xml
File metadata and controls
577 lines (292 loc) · 267 KB
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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>小蛋子</title>
<subtitle>小蛋子</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://xv44586.github.io/"/>
<updated>2026-01-02T13:54:04.303Z</updated>
<id>https://xv44586.github.io/</id>
<author>
<name>[object Object]</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>再见 2025,你好 2026</title>
<link href="https://xv44586.github.io/2025/12/31/goodbye-2025/"/>
<id>https://xv44586.github.io/2025/12/31/goodbye-2025/</id>
<published>2025-12-31T07:29:38.000Z</published>
<updated>2026-01-02T13:54:04.303Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#goodbye-2025">goodbye 2025</a></li><li><a href="#chu-you-pian">出游篇</a><ul><li><a href="#2025-nian-3-yue-luo-yang-pian">2025年3月洛阳篇</a></li><li><a href="#2025-nian-6-yue-xin-jiang-xing">2025年6月新疆行</a></li><li><a href="#chuan-xi-pian">川西篇</a></li><li><a href="#yun-nan-pian">云南篇</a></li></ul></li><li><a href="#jian-kang-pian">健康篇</a><ul><li><a href="#zheng-ti-zhuang-kuang">整体状况</a></li><li><a href="#shou-shang">受伤</a></li><li><a href="#zheng-ji">正畸</a></li></ul></li><li><a href="#sheng-huo-pian">生活篇</a></li><li><a href="#gan-qing-pian">感情篇</a></li><li><a href="#gong-zuo-pian">工作篇</a></li><li><a href="#li-cai-pian">理财篇</a></li><li><a href="#2026-zhan-wang">2026 展望</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="goodbye-2025">goodbye 2025</span><a href="#goodbye-2025" class="header-anchor"></a></h1><p>2025年的最后一天,把断更两年的博客又重新搭起来了,并且修复了一些环境问题,优化了渲染速度,用起来更顺畅了,感谢antigravity,感谢Gemini。<br>顺便整理了一下今年的照片,也对即将过去的2025年做了一个总结。</p><h1><span id="chu-you-pian">出游篇</span><a href="#chu-you-pian" class="header-anchor"></a></h1><p>由于去年买了车,所以今年的出游次数比去年多了很多。去了几次川西,也在成都周边的镇逛了几次,买了车确实扩大了出游的范围。</p><h2><span id="2025-nian-3-yue-luo-yang-pian">2025年3月洛阳篇</span><a href="#2025-nian-3-yue-luo-yang-pian" class="header-anchor"></a></h2><p>3月初老张在洛阳结婚,我们去参加了婚礼,顺便在洛阳玩了两天,印象深刻的是洛阳博物馆,跟着讲解员讲解,体会了一次历史的厚重;夜游了洛邑古镇,只记得里面的灯光很漂亮。第二天去了龙门石窟和白马寺,女朋友还买了两条开光的手串,给各自戴上,据说可以带来好运。不过牡丹花还未开放,没能赶上牡丹节,但也错过了高峰期,住了体验最棒的一次民宿。</p><div style="display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-泥塑佛面像.jpeg" alt="洛阳-泥塑佛面像" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-博物馆-2.jpeg" alt="洛阳-博物馆-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-博物馆-3.jpeg" alt="洛阳-博物馆-3" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-陶俑-1.jpeg" alt="洛阳-陶俑" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-陶俑-2.jpeg" alt="洛阳-陶俑" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-陶俑-3.jpeg" alt="洛阳-陶俑" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-龙门石窟-1.jpeg" alt="洛阳-龙门石窟" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-白马寺-1.jpeg" alt="洛阳-白马寺" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-03-洛阳-洛邑古镇-1.jpeg" alt="洛阳-洛邑古镇" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"></div><h2><span id="2025-nian-6-yue-xin-jiang-xing">2025年6月新疆行</span><a href="#2025-nian-6-yue-xin-jiang-xing" class="header-anchor"></a></h2><p>六月份趁着端午节,带女朋友回了趟新疆,在家呆了两天,就拉着全家去自驾独库公路了。独库公路每年只开放5-10月份,其他时间由由于冰雪,都是封闭的。也让我体验了一下自驾的乐趣(疲惫)。<br>一路从阿克苏到库车,然后进入和静县,最后到达巴音布鲁克草原。由于刚好是古尔邦节,路上游人非常多,后半程时间不够了,没有去到伊犁大草原,只能期待下次再去了。<br>独库公路上从南到北,从戈壁到山区,最后进入草原,一路的风景风格各异,从荒凉的戈壁到广阔的草原,一路的风景都让人印象深刻。<br>当然,回到家各种美食还是少不了,抓饭烤肉拌面,还有一些特色的由于放假都没开门,只能下次回去吃了。</p><div style="display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-神秘大峡谷-1.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-2.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-3.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-4.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-5.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-6.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-7.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-8.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-新疆-独库公路-9.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> </div><h2><span id="chuan-xi-pian">川西篇</span><a href="#chuan-xi-pian" class="header-anchor"></a></h2><p>今年自驾了三次川西,分别去了康定、姑弄村和卧龙镇。在康定,我们还遇到了一件趣事:我们去吃了一家当地的特色藏餐,由于就两个人,但是每个都想尝尝,正在激情讨论点哪些的时候,隔壁的一对大哥大姐给我们送来来他们的酥油茶、酸奶和饼子,说他们也是想尝尝,但是没想到这么大量,就分一半给我们吧。我们连忙表示感谢,并想点几份他们没有的给他们也分一半,两个人忙说目前的都吃饱了还吃不完,让我们点少点些自己想吃的就好,他们马上就走了。感谢这对大哥大姐,感谢。<br>在姑弄村,一路经过了318国道,塔公草原,墨石公园。在姑弄村的一条小溪边搭了小帐篷,坐在溪边晒了一下午太阳。前面是潺潺的溪水,后面是巍峨的贡嘎雪山,相当巴适。<br>PS:最近才知道,<a href="https://zh.wikipedia.org/wiki/%E8%B4%A1%E5%98%8E%E5%B1%B1">贡嘎雪山</a>到2024年为止,只有<span style="color: red;">28</span>个人成功登顶贡嘎雪山,而遇难的就有<span style="color: red;">21</span>个,这座看似和蔼可亲的雪山,没想到却这么的难以征服,神奇的大自然。既然无法征服,那我就放一张照片给大家,让大家远远的欣赏它的美吧。</p><div style="display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-川西-姑弄村-1.jpeg" alt="川西-姑弄村-1" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-川西-姑弄村-2.jpeg" alt="川西-姑弄村-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-川西-墨石公园.jpeg" alt="川西-墨石公园 " style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-川西-墨石公园-2.jpeg" alt="川西-墨石公园-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-甘孜-1.jpeg" alt="川西-甘孜-1" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-甘孜-2.jpeg" alt="川西-甘孜-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-甘孜-3.jpeg" alt="川西-甘孜-3" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-甘孜-4.jpeg" alt="川西-甘孜-4" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-甘孜-5.jpeg" alt="独库公路" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> </div><p>11月又去了卧龙镇,主要是去看彩林,除此之外,我也完成了在川西找菌子的计划。<br>放心,没吃,嘿嘿嘿。</p><div style="display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-11-川西-卧龙镇彩林-1.jpeg" alt="川西-卧龙镇彩林-1" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-11-川西-卧龙镇彩林-2.jpeg" alt="川西-卧龙镇彩林-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-11-川西-卧龙镇彩林-3.jpeg" alt="川西-卧龙镇彩林-3" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"></div><h2><span id="yun-nan-pian">云南篇</span><a href="#yun-nan-pian" class="header-anchor"></a></h2><p>十一又去云南玩了几天,首先落地丽江,冒着雨夹雪去了玉龙雪山,虽然成功登顶了,但是也是真的冻坏了累坏了。<br>第二天转战虎跳峡徒步,相当有意思,怒江在这里是真的湍急,生动演绎了惊涛拍岸的景象,走在峡谷的小道上,边上就是深邃的峡谷,时不时还要跟羊让路,非常好玩的徒步路线了。当天住在了香格里拉,这里真是个神奇的地方,明明海拔也不算高,我却高反严重,怎么都睡不着,躺下就心脏难受,坐起来就困🥱。最后只呆了两天就回丽江了。PS:香格里拉的炒米线真好吃。<br>在丽江转了下古城,睡了个好觉。丽江古城的景色真不错,沿街全是各种鲜花,我很喜欢。PS:鲜花饼也好吃,买了不少。</p><div style="display: flex; flex-wrap: wrap; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-玉龙雪山.jpeg" alt="云南-玉龙雪山" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-玉龙雪山-2.jpeg" alt="云南-玉龙雪山-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-1.jpeg" alt="云南-虎跳峡-1" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-2.jpeg" alt="云南-虎跳峡-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-3.jpeg" alt="云南-虎跳峡-3" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-4.jpeg" alt="云南-虎跳峡-4" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-5.jpeg" alt="云南-虎跳峡-5" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-虎跳峡-6.jpeg" alt="云南-虎跳峡-6" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-云南-香格里拉.jpeg" alt="云南-香格里拉" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"> </div><h1><span id="jian-kang-pian">健康篇</span><a href="#jian-kang-pian" class="header-anchor"></a></h1><h2><span id="zheng-ti-zhuang-kuang">整体状况</span><a href="#zheng-ti-zhuang-kuang" class="header-anchor"></a></h2><p>今年的身体状况相比去年还是要差一点,上半年经常一天有18个小时都是疲惫不堪的状态,优甲乐的量也是按医嘱在吃,所以我断定可能是自己免疫力下降了,所以今年的运动相比去年也增加了一些。</p><p>除了骑车外,最多的就是羽毛球,我们俩个都爱打,所以天气好的时候我们一周可能打四次球。</p><div style="display: flex; justify-content: center; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-09-羽毛球.jpeg" alt="羽毛球" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block; margin-bottom: 5px;"></div><h2><span id="shou-shang">受伤</span><a href="#shou-shang" class="header-anchor"></a></h2><p>今年也经历了运动受伤,上次运动受伤还是读书时打篮球崴到脚。这次是打羽毛球时跟腱撕裂,主要原因还是运动前没有做好热身,一上场又打的比较激烈,导致跟腱撕裂。现在想想,当时断裂时的那一声“啪”,仿佛还在耳边回响。感谢女朋友当时的照顾,让我一个月左右就基本康复了。</p><h2><span id="zheng-ji">正畸</span><a href="#zheng-ji" class="header-anchor"></a></h2><p>十二月二号,我也终于正式的戴上了牙套,从去年第一次挂号到最终戴上牙套,陆陆续续的经历了多次的华西不同科室的抢号,洗牙,拍片,等待。由于正畸的执行医生是来学习的,还经历了中间换医生但是双方未做好交接,导致浪费了两个月。anyway,在这一年结束前,成功的戴上了牙套,后面还需要2-3年的时间,才能做手术。路漫漫其修远兮。</p><h1><span id="sheng-huo-pian">生活篇</span><a href="#sheng-huo-pian" class="header-anchor"></a></h1><p>今年从年初开始看房,把成都高新的新房基本看完了,中间也陆陆续续看了成华区/天府新区的房子,都不是很满意,明年还需要继续看,希望能尽快找到一个适合的房子,真的安定下来。</p><p>因为我是张学友的忠实歌迷,六月份女朋友买了张学友的演唱会门票,也圆了我追张学友的梦,再次感谢女朋友。</p><div style="display: flex; justify-content: center; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-06-成都-张学友.jpeg" alt="张学友" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block;"></div><p>十二月中了一张话剧票,第一次看了话剧,感受到了话剧的魅力。那种真正的身临其境作为旁观者看待故事发展的感觉,真的很爽,非常的沉浸。以后有机会会多看几场,新的爱好get。</p><div style="display: flex; justify-content: center; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/2025-12-话剧-雷雨.jpeg" alt="话剧" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block;"></div><p>十一月初的一个晚上,我跟女朋友在小区附近遛弯时,遇到一只最多满月的走丢小奶猫,起初只是朝着我们叫,后来开始跟着我们走,我们把他放车底几次,都跟着我女朋友,感觉这个小奶猫很有缘份,于是我们带着它回家了。我们两个也开始了做铲屎官的日常:擦脸擦脚擦身体,喂饭铲屎溜猫。<br>由于我们给他喂食,很多次他都看不到食物在哪,所以我们给他起了个名字:虾米(瞎咪)。虾米是只玳瑁小母猫,非常黏人,喜欢咬人,喜欢坐在电脑上,希望坐在我肩膀上,现在大了,连我上厕所都要坐在对面守着我,有时候还会趁我拉屎的时候跳上我的肩膀,非常活泼爱动的猫咪了。平时我上班,他就在我附近跑酷,跑累了就跳上来坐我电脑上或者肩膀上,跟我结对编程。🤣</p><div style="display: flex; justify-content: space-between; gap: 5px;"> <img src="/2025/12/31/goodbye-2025/cat-1.jpeg" alt="cat-1" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block;"> <img src="/2025/12/31/goodbye-2025/cat-2.jpg" alt="cat-2" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block;"> <img src="/2025/12/31/goodbye-2025/cat-3.jpeg" alt="cat-3" style="width: 32%; aspect-ratio: 4/3; object-fit: cover; display: block;"></div><p>当然也有一些难过的时刻,最近几个月女朋友正经历着人生悲苦时刻,常常想为他做些什么,可又感叹自己的能力有限,只能默默的陪伴在她身边。这段时间也加深了一些人生感悟:愿每个人都健健康康,长命百岁。</p><h1><span id="gan-qing-pian">感情篇</span><a href="#gan-qing-pian" class="header-anchor"></a></h1><p>今年跟往年最大的不同是一年都有女朋友的陪伴。从前我更喜欢独立生活,把自己的生活都规划好,一步一步的按部就班的做就是了。即使临时起意,我也会先大致计算下时间节点,简单安排下顺序,然后按计划行动。但是跟女朋友一起生活后,两个人的很多事情都不能同步,我的固有生活模式完全被打破,起初真的各种不适应,也为此争吵了几次。后来我也慢慢理解了,也开始能接受事情不按自己的计划进行,虽然这些不确定性的东西还是会给我带来不安全感,让我无措甚至生气,但大体上比之前的接受度高了许多。慢慢的学会了“拥抱不确定性,拥抱变化”,跟女朋友相处的也越来越融洽。整个一年大多数时候觉得女朋友真可爱,极少数时刻也会觉得她不能沟通😠。当然,整个过程中,我自己也在成长,变得更加成熟,更加理性,更加有耐心。所以今年是我们两个都在慢慢成长的一年。<br>最后,感谢女朋友这一年的付出与陪伴,希望我们的感情越来越好,明年结婚后能更上一层楼。</p><h1><span id="gong-zuo-pian">工作篇</span><a href="#gong-zuo-pian" class="header-anchor"></a></h1><p>今年在工作上可以用四个字总结:乏善可陈。学术上也没有看什么paper,有种大模型的世界很精彩,但是我却找不到感兴趣的部分,热闹是他们的,我站在门外看着。<br>工作上也没有做出什么让自己有成就感的项目,加上不停的有同组的同事离职,所以工作上的体验感很差。希望明年能自己主动打破吧,找到让自己兴奋的东西。</p><h1><span id="li-cai-pian">理财篇</span><a href="#li-cai-pian" class="header-anchor"></a></h1><p>今年下半年隐隐约约的感觉可能有牛市,买过技术股也炒过情绪股,好在整体市场向上,加上之前买了些金银铜铝相关的,误打误撞的多少算是赚了点。中间也开始陆续尝试学习了些知识,整体来说在没亏钱的情况下积累了一些经验。明年希望能更系统的学习一些知识,把投资做的更从从容容一些。</p><h1><span id="2026-zhan-wang">2026 展望</span><a href="#2026-zhan-wang" class="header-anchor"></a></h1><p>新的一年,重点还是会放在生活上,第一呢就是跟女朋友结婚顺利,早点有个baby,嘿嘿。第二呢就是能早点买到心仪的房子,安定下来。虽然说房地产在下行,房价可能还要跌20年或者更久,但是没有自己的房子总归有诸多不便,即使明知道不划算还是需要买一套自住舒服的。第三呢,就是多多关注自己的身体健康,多运动注意饮食,保持良好的生活习惯。第四呢,就是多花点时间陪伴家人,多陪陪女朋友,多陪陪父母。第五呢,要开始想想自己以后的路了,早点找到让自己兴奋的东西,开始新的征程。</p><p>最后,衷心祝愿大家新年快乐,新的一年愿我们每个人都能身体健康,阖家欢乐,心想事成。</p><div style="text-align: center; font-size: 3em; font-family: 'Brush Script MT', cursive; background: linear-gradient(to right, #ff0000, #ff7f00, #ffff00, #00ff00, #00ffff, #0000ff, #8b00ff); -webkit-background-clip: text; color: transparent; margin: 20px 0; font-weight: bold;">Happy New Year!</div><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>今年去川西时女朋友拍的红海子,是我今年最喜欢的照片</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="Life" scheme="https://xv44586.github.io/categories/Life/"/>
<category term="Life" scheme="https://xv44586.github.io/tags/Life/"/>
<category term="Annual Summary" scheme="https://xv44586.github.io/tags/Annual-Summary/"/>
</entry>
<entry>
<title>GPT-4 yes!! but</title>
<link href="https://xv44586.github.io/2023/03/25/gpt4/"/>
<id>https://xv44586.github.io/2023/03/25/gpt4/</id>
<published>2023-03-25T13:50:00.000Z</published>
<updated>2026-01-01T21:57:47.315Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#gpt-4-yes">GPT-4 yes</a></li><li><a href="#gpt-4-cun-zai-de-wen-ti">GPT-4存在的问题</a><ul><li><a href="#bu-kai-yuan">不开源</a></li><li><a href="#shu-ju-an-quan">数据安全</a></li><li><a href="#zi-yuan-xiao-hao-da">资源消耗大</a></li></ul></li><li><a href="#nlp-ke-zuo-de-fang-xiang">NLP可做的方向</a><ul><li><a href="#hallucination">hallucination</a></li><li><a href="#fu-xian-gpt-4-chatgpt-gpt-3-5-instructgpt">复现GPT-4/ChatGPT/GPT-3.5/InstructGPT</a></li><li><a href="#ru-he-ping-gu-llm">如何评估LLM</a></li><li><a href="#zhi-chi-chang-wen-ben">支持长文本</a></li><li><a href="#bian-xiao-bian-kuai">变小变快</a></li><li><a href="#di-cheng-ben-inference">低成本inference</a></li><li><a href="#di-cheng-ben-you-hua">低成本优化</a></li><li><a href="#you-hua-qi">优化器</a></li><li><a href="#geng-ke-kong">更可控</a></li><li><a href="#shi-bie-aigc">识别AIGC</a></li><li><a href="#dan-yi-ren-wu-ling-yu-shua-bang">单一任务/领域刷榜</a></li></ul></li><li><a href="#he-qu-he-cong">何去何从</a><ul><li><a href="#pu-tong-gong-cheng-shi">普通工程师</a></li><li><a href="#pu-tong-yong-hu">普通用户</a></li></ul></li><li><a href="#fan-wai">番外</a><ul><li><a href="#tong-guo-prompt-gou-jian-ji-zhu-bi-lei-shen-qing-prompt-zhuan-li">通过prompt 构建技术壁垒/申请prompt 专利</a></li><li><a href="#hui-bu-hui-shi-ye">会不会失业</a></li></ul></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><p>这篇博客简单讨论下在GPT-4 如此强大的技术冲击下,我们NLPer该何去何从。<br>首先说下我的结论:GPT-4 非常强大,但是还没有到完全取代我们工作的地步,我们依然有很多能做的方向。</p><h1><span id="gpt-4-yes">GPT-4 yes</span><a href="#gpt-4-yes" class="header-anchor"></a></h1><ol><li>更可靠了(胡说八道进一步降低)</li><li>性能更好:比GPT-3.5 又提升了一大截</li><li>reverse inverse scaling prize:一些随着模型变大性能下降的任务在GPT-4上不再出现类似现象(曾经没法通过增大模型规模提升性能的任务现在也解决了)</li><li>能够用图像做prompt:增加图像信息能进一步提升性能(看图说话,类似BLIP2,这个对盲人太友好了)</li><li>进一步closeAI</li></ol><h1><span id="gpt-4-cun-zai-de-wen-ti">GPT-4存在的问题</span><a href="#gpt-4-cun-zai-de-wen-ti" class="header-anchor"></a></h1><h2><span id="bu-kai-yuan">不开源</span><a href="#bu-kai-yuan" class="header-anchor"></a></h2><p>由于GPT-4 完全不公布任何技术细节,所以他为什么有如此强大的能力,我们只能猜,想要研究它变得困难重重。</p><h2><span id="shu-ju-an-quan">数据安全</span><a href="#shu-ju-an-quan" class="header-anchor"></a></h2><p>ChatGPT 的火爆让大家突然忘了曾经非常看重的数据安全问题,preview 版是有可能会参与下次迭代的;而商用api 即使强调不会用于模型训练,敏感业务数据你敢用吗?</p><h2><span id="zi-yuan-xiao-hao-da">资源消耗大</span><a href="#zi-yuan-xiao-hao-da" class="header-anchor"></a></h2><p>即使是GPT-3 也有175B 参数,训练/推理都是极其消耗资源的,从GPT-4 的价格上涨了50% 来看,GPT-4 的推理消耗资源也上升了50% 左右。</p><h1><span id="nlp-ke-zuo-de-fang-xiang">NLP可做的方向</span><a href="#nlp-ke-zuo-de-fang-xiang" class="header-anchor"></a></h1><p>这也是最近讨论比较热烈的一个问题,回答这个问题前,不妨先思考一下理想的NLP 模型应该具有哪些特征。我认为比较理想的模型是:安全可靠/支持长文本/小/快/私有化部署。所以我仅从个人出发,给出一些我比较关注的方向。</p><h2><span id="hallucination">hallucination</span><a href="#hallucination" class="header-anchor"></a></h2><p>目前LLM 最大的问题就是hallucination(一本正经的胡说八道)。目前主流两种思路:alignment/多模态。</p><ul><li>alignment<br>至于如何做alignment ,学术界主要是instruction-tuning为主,OpenAI 的路线是RLHF,然而普通玩家我是完全不推荐做RL的,只要仔细阅读InstructGPT/GPT-4 paper中关于reward model 部分就能劝退了。所以对于我们普通玩家,是否有别的路径?</li><li>多模态<br>GPT4 的paper 上看效果是不错的,我没做过,不多说了。</li></ul><h2><span id="fu-xian-gpt-4-x2f-chatgpt-x2f-gpt-3-5-x2f-instructgpt">复现GPT-4/ChatGPT/GPT-3.5/InstructGPT</span><a href="#fu-xian-gpt-4-x2f-chatgpt-x2f-gpt-3-5-x2f-instructgpt" class="header-anchor"></a></h2><p>不开源只能复现,目前主要有<a href="https://github.com/facebookresearch/llama">facebookresearch/llama</a>/<a href="https://huggingface.co/bigscience/bloom">bigscience/bloom</a>,此外还有不开源但是可以使用API 访问的百度文心一言/ChatGLM 等。</p><h2><span id="ru-he-ping-gu-llm">如何评估LLM</span><a href="#ru-he-ping-gu-llm" class="header-anchor"></a></h2><p>我们说百度文心一言性能不行时,到底如何不行?这里就牵扯到如何量化的评估LLM 的性能。曾经自动化的方案及benchmark 的参考意义随着LLM 的能力提升显得越来越弱,现在急需新的数据集/评估方案。目前的工作有<a href="https://github.com/openai/evals">openai/evals</a>/<a href="https://github.com/stanford-crfm/helm">stanford-crfm/HELM</a></p><h2><span id="zhi-chi-chang-wen-ben">支持长文本</span><a href="#zhi-chi-chang-wen-ben" class="header-anchor"></a></h2><p>更长的输入对某些任务是有利的,如何让模型支持更长的输入呢?主要的思路有两个:</p><ul><li>训练时使用较短文本,推理时外推更长的位置信息,使模型获得处理长文本的能力,如bloom 中使用的<a href="https://arxiv.org/pdf/2108.12409.pdf">ALiBI</a></li><li>调整模型结构,如最近的工作:<a href="https://arxiv.org/pdf/2303.09752.pdf">CoLT5:Faster Long-Range Transformers with Conditional Computation</a><br>PS: GPT-4 的输入从GPT-3.5 的4K(or 8K?) 提升到了30K,如何做的呢?</li></ul><h2><span id="bian-xiao-bian-kuai">变小变快</span><a href="#bian-xiao-bian-kuai" class="header-anchor"></a></h2><p>相同架构的模型通常变小就会变快,让模型变小的方法主要是蒸馏/量化/train 小模型,这个方向目前工作有<a href="https://github.com/tatsu-lab/stanford_alpaca">stanford_alpaca</a>/<a href="https://github.com/TimDettmers/bitsandbytes">bitsandbytes</a>,中文上也有<a href="https://github.com/THUDM/ChatGLM-6B">ChatGLM-6B</a>/<a href="https://github.com/LianjiaTech/BELLE">BELLE</a>等。</p><h2><span id="di-cheng-ben-inference">低成本inference</span><a href="#di-cheng-ben-inference" class="header-anchor"></a></h2><p>如何在低成本设备上使用这些模型?如单张GPU 上跑大模型或普通CPU 上跑模型。这个方向的工作也有<a href="https://github.com/FMInference/FlexGen">FlexGen</a>/<a href="https://github.com/ggerganov/llama.cpp">llama.cpp</a> 等。</p><h2><span id="di-cheng-ben-you-hua">低成本优化</span><a href="#di-cheng-ben-you-hua" class="header-anchor"></a></h2><p>低成本fine-tuning 主要有两个方向:parameter-efficient / sample-efficient.</p><ul><li>parameter-efficient 的思路目前主要有prompt-tuning/prefix-tuning/LoRA/Adapter 等,参考<a href="https://github.com/huggingface/peft">huggingcae/peft</a></li><li>sample-efficient 可以帮助我们如何更有效的构造训练集,最近的工作有<a href="http://arxiv.org/abs/2303.08114">Simfluence: Modeling the Influence of Individual Training Examples by Simulating Training Runs</a></li></ul><h2><span id="you-hua-qi">优化器</span><a href="#you-hua-qi" class="header-anchor"></a></h2><p>优化器决定了我们训练时需要的资源。虽然我们通常使用Adam 优化器,但是其需要2倍额外显存,而google 好像用Adafactor 更多一点,最近他们又出了一个新工作<a href="https://arxiv.org/abs/2302.06675">Lion</a>.</p><h2><span id="geng-ke-kong">更可控</span><a href="#geng-ke-kong" class="header-anchor"></a></h2><p>如从可控生成角度看,目前可控主要通过control token(prompt)来实现,有没有更好的办法来实现更“精细”的控制,就如controlnet 之于stable diffusion。</p><h2><span id="shi-bie-aigc">识别AIGC</span><a href="#shi-bie-aigc" class="header-anchor"></a></h2><p>如何判别内容是人写的还是模型生成的呢?随着模型的性能越来越强,识别AIGC 也越来越困难。目前的工作也有watermark/<a href="https://gptzero.me/">GPTZero</a> 等。不过我感觉还没什么特别有效的方案目前。<br>对此我有个简单的思路:将AI 生成的与非AI 生成的看作是两种不同的语言,如code 与英语一样,虽然都是相同符合构成,但是对应不同语言。使用大量的AI 生成的内容(或人机交互数据)pretrain 一个”AI 语言模型“,再来进行识别。</p><h2><span id="dan-yi-ren-wu-x2f-ling-yu-shua-bang">单一任务/领域刷榜</span><a href="#dan-yi-ren-wu-x2f-ling-yu-shua-bang" class="header-anchor"></a></h2><p>我认为在某个任务/领域上通过小模型挑战大模型依然有意义,LLM 虽然强大,但是依然有太多我们不知道的能力,通过小模型刷榜也许能提供一些思路,就像PET 本意是调战GPT-3,却打开了LLM 的新思路。</p><h1><span id="he-qu-he-cong">何去何从</span><a href="#he-qu-he-cong" class="header-anchor"></a></h1><h2><span id="pu-tong-gong-cheng-shi">普通工程师</span><a href="#pu-tong-gong-cheng-shi" class="header-anchor"></a></h2><p>这种新的革命性的技术我们普通工程师通常都不是第一线的,我第一次真正使用bert 也是在其出来两年后了。即使今天,也有很多场景/公司不使用bert这个技术。<br>换个角度,即使我们想参与,我想能参与训练/fine-tuning 一个10B 规模模型的工程师都相当少,更别提更大的了。所以到底是“左右逢源”还是“举步维艰”,让子弹飞一会儿吧。</p><h2><span id="pu-tong-yong-hu">普通用户</span><a href="#pu-tong-yong-hu" class="header-anchor"></a></h2><p>普通用户我觉得应该就是多读书,提高自己的鉴别能力了。”生活中不缺少美,而是缺少发现美的眼睛。”</p><h1><span id="fan-wai">番外</span><a href="#fan-wai" class="header-anchor"></a></h1><h2><span id="tong-guo-prompt-gou-jian-ji-zhu-bi-lei-x2f-shen-qing-prompt-zhuan-li">通过prompt 构建技术壁垒/申请prompt 专利</span><a href="#tong-guo-prompt-gou-jian-ji-zhu-bi-lei-x2f-shen-qing-prompt-zhuan-li" class="header-anchor"></a></h2><p>随着alignment 的进一步优化,LLM 通常越来越理解自然语言,所以我认为prompt-trick 越来越不重要,而清晰准备的用prompt 描述你的需求越来越重要。所谓技术壁垒也许就是如何更清晰有效的描述需求了,但也很难形成技术壁垒。<br>至于专利,软件著作权保护的是制作软件这个技术本身,而非你使用软件时的姿势,所以我想单独的prompt 应该也不会形成专利,但是作为你某个技术的一部分,还是有可能的。</p><h2><span id="hui-bu-hui-shi-ye">会不会失业</span><a href="#hui-bu-hui-shi-ye" class="header-anchor"></a></h2><p>我认为不会失业,但是会转变一部分人的工作方式。在计算这件事上,人类早已被计算机远远的甩在后面,而计算机的出现也带来了大量的新工作。尤其是LLM 现阶段的表现是“懂开车的人才能开车”,所以需要更多更懂某个业务,更熟练使用LLM 工具的人。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>放张动漫图,据说能缓解焦虑</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="LLM" scheme="https://xv44586.github.io/tags/LLM/"/>
<category term="GPT-4" scheme="https://xv44586.github.io/tags/GPT-4/"/>
</entry>
<entry>
<title>LLM Inference串讲</title>
<link href="https://xv44586.github.io/2023/03/10/llm-inf/"/>
<id>https://xv44586.github.io/2023/03/10/llm-inf/</id>
<published>2023-03-10T12:58:13.000Z</published>
<updated>2026-01-01T21:57:47.310Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#generation">generation</a><ul><li><a href="#greedy-search">Greedy Search</a></li><li><a href="#beam-search">Beam Search</a></li><li><a href="#sampling">Sampling</a><ul><li><a href="#topk">topK</a></li><li><a href="#topp">topP</a></li><li><a href="#beam-search-sampling">beam-search sampling</a></li></ul></li><li><a href="#generate-parameters">generate parameters</a><ul><li><a href="#temperature">Temperature</a></li><li><a href="#penalty">* penalty</a></li><li><a href="#qiang-zhi-jin-zhi-te-ding-token-de-chu-xian">强制/禁止 特定token 的出现</a></li></ul></li></ul></li><li><a href="#gpu">GPU</a><ul><li><a href="#performance">performance</a></li></ul></li><li><a href="#gpt-generation">GPT generation</a><ul><li><a href="#ops-vs-bytes">ops vs bytes</a></li><li><a href="#attention">attention</a></li><li><a href="#cache">cache</a></li><li><a href="#ding-liang-ping-gu-you-hua-fang-an">定量评估优化方案</a></li></ul></li><li><a href="#jie-lun">结论</a></li><li><a href="#ref">ref</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div>本文主要概述一下当前LLM 是如何生成文本及为什么对应的资源(cost/latency)与prompt 和completion 都有关系。<h1><span id="generation">generation</span><a href="#generation" class="header-anchor"></a></h1><p>上一篇我们讲了为了解决自然语言问题,我们引入了统计语言模型:$S$ 表示一连串特定顺序排列的词$w_1$, $w_2$, …, $w_n$,其中$n$ 是序列的长度,则$S$ 出现的概率$P(S)=P(w_1,w_2,…w_n)$. 通过序列的概率,来判断对应句子是否合理。而这个概率$P(S)$ 很难估算,所以我们将其转化一下。首先,利用条件概率公式将其展开:<br>$$<br>P(S)=P(w_1,w_2,…,w_n)=P(w_1)∗P(w_2∣w_1)∗P(w_3∣w_1,w_2)∗…∗P(w_n∣w_1,w_2,…,w_{n−1})<br>$$<br>即:<br>$$<br>P(w_1^n)=\prodP(w_i∣w_1^{i−1})<br>$$<br>然后用深度神经网络,对$P(w_i|w_1^{i-1})$ 进行建模,即:<br>$$<br>P(w_i|w_1^{i-1}) = g(w_1^{i-1})<br>$$</p><p>其中$g$ 是深度神经网络,如MLP/RNN/Transformer 等。</p><p>文本生成的过程,即我们在已有的深度神经网络$g$ 下,生成(采样)合理句子(序列)的过程。其中$g$ 能生成给定context $C$ 时下一个时刻对应的词表分布, 其中$C$ 是由$w_1$, $w_2$, …, $w_m$ 组成的序列。<br>目前我们训练$g$ 通常采用Teacher-Forcing 的方式:训练阶段,每个时刻$t$ 之前的label $y_{<t}$都是已知的,就像是老师讲课一样,将每一步的正确解题思路都告诉你,你只需要跟着老师的思路一步一步推导就能得到正确答案;然而在inference 阶段,我们是没法知道当前时刻之前的真实label 是什么的,所以需要将所有可能的序列的概率都求解出来,最后在所有可能性中选择概率最大的。如同考试时没了参考步骤,只能尝试多个思路,最后保留我们觉得”正确“的。</p><p><img src="/2023/03/10/llm-inf/Pasted-image-20230307215330.png" alt="Pasted-image-20230307215330"></p><p>求解过程是一个求解最优路径问题,然而这个计算量太大了。假设我们的词表大小是$V$, 生成序列的长度为$k$, 每一个时刻都需要对所有状态(整个词表)进行计算,即使我们的词表只有五万个词,要生成五个token 也需要计算25万次,这个计算量实在是太大了。所以我们需要一些更高效的生成策略。</p><blockquote><p>概率值$p$ 是一个范围在$0\sim 1$ ,多次连乘后会很快接近0,所以通常我们会将$max\prod p_i$ 转换为求$max \sum log(p_i)$ </p></blockquote><h2><span id="greedy-search">Greedy Search</span><a href="#greedy-search" class="header-anchor"></a></h2><p>Greedy search 的思路是:每次都选择概率最高的词作为最终采样结果,即: $w_t = argmax_wP(w|w_1^{t-1})$<br><img src="/2023/03/10/llm-inf/Pasted-image-20230307223501.png" alt="Pasted-image-20230307223501"><br>该方法是缺点也很明显:局部最优的最终结果很可能不是全局最优,由于每次都是选局部最优,这也扼杀了模型找到全局最优的可能性。如上图中Greedy search 的结果是(The, nice, woman),而全局最优是(The, dog, has) .除此之外,模型的生成结果也不够”丰富“,甚至会出现不停重复之前的内容。</p><h2><span id="beam-search">Beam Search</span><a href="#beam-search" class="header-anchor"></a></h2><p>”多一个选择,多一次机会“。为了缓解Greedy search 的问题,我们在每次选择时,不再只保留最高概率的一个,而是所有候选中保留概率最高的N 个(num_beams/beam_width) path。<br><img src="/2023/03/10/llm-inf/Pasted-image-20230308095954.png" alt="Pasted-image-20230308095954"><br>上图是num_beams=2 的示例:</p><ol><li>第一步时,在(The, dog)/(The, nice)/(The car) 三个路径中选择概率最高的两条path:(The, nice), (The, dog)</li><li>第二步时,在(The, nice, wowan)/(The, nice, house)/(The, nice, guy)/(The, dog, and)/(The,dog,runs)/(The,dog,has) 中选择路径概率(单步概率连乘)最高的两条path:(The, dog, has), (The, nice, woman)</li><li>依此类推,直到满足终止采样.</li></ol><p>此前Greedy Search 未找到的最优路径,此次通过Beam Search 找到了,但是依然无法保证每次都能找到全局最优。PS: num_beams =1 时,与Greedy Search 过程相同。</p><h2><span id="sampling">Sampling</span><a href="#sampling" class="header-anchor"></a></h2><p>以上两种方法都是确定性解码(deterministic),缺点就是不够丰富,不够”surprise“;为了让生成的内容更加的丰富多样有惊喜,我们可以采用另一种策略:采样(Sampling).</p><p>采样的基本思路是在概率分布上进行随机采样,选择一个作为下一个词$w_t$:<br>$$<br>w_t \sim P(w|w_1^{t-1})<br>$$<br>然而由于模型$g$ 距离真实的$P(w|w_1{t-1})$ 还是会有差距,直接按照$g$ 生成的”概率分布“采样风险太大,很容易走偏;一个折中的办法是控制候选集,在“低风险”范围内采样。<br>候选集的构造方式也有多个,常用的有topK/topP/beam-search.</p><h3><span id="topk">topK</span><a href="#topk" class="header-anchor"></a></h3><p>每次选择概率最大的K 个作为候选集,然后重新归一化,获取新的分布后进行采样。</p><ol><li>对词表进行排序,选择概率最高的K 个;</li><li>对候选集中的K 个候选的概率值进行归一化,构造K 个候选对应的分布$\widetilde{P}$</li><li>从分布$\widetilde{P}$ 中采样一个词作为$w_t$</li></ol><h3><span id="topp">topP</span><a href="#topp" class="header-anchor"></a></h3><p>topp sampling 又叫nucleus sampling,其思路是不在从通过保留最大的K 个作为候选,而是保留概率累计和范围$p$ 内的所有词作为候选集,候选集大小随着分布变化而动态调整。</p><ol><li>对词表进行排序,从大到小进行排列</li><li>依照概率大小,依次将词加入候选集,直到新增的词进入候选集后,整个候选集内的概率累计和大于$p$ 停止;</li><li>对候选集内的词进行归一化,构造新的分布$\widetilde{P}$</li><li>从分布$\widetilde{P}$ 采样一个词作为$w_t$</li></ol><p>topk 与topp 也可以一起使用,通常实现时是先进行topk,然后在topk 归一化后的候选集上进行topp 采样。</p><h3><span id="beam-search-sampling">beam-search sampling</span><a href="#beam-search-sampling" class="header-anchor"></a></h3><p>该方法是beam-search 的samling 版,其主要思路是: 在每次选择时,不在直接选择所有候选中概率最高的num_beams 个,而是从中采样。</p><h2><span id="generate-parameters">generate parameters</span><a href="#generate-parameters" class="header-anchor"></a></h2><h3><span id="temperature">Temperature</span><a href="#temperature" class="header-anchor"></a></h3><p>通常模型的输出是一些值(logits)而不是分布(probability distribution),我们需要将其转换成分布,转换通常使用的是softmax 函数:<br>$$\frac{\exp(z_i)}{\sum_jexp(z_j)}$$</p><p>softmax 函数的特点是:</p><ol><li>保持其原有的相对顺序;</li><li>累计和为1。</li></ol><p>然而其缺点也明显:很容易扩大/缩小内部元素的差异(softmax 变 max/mean),如 [11, 12, 13 ] softmax 后为[0.0900, 0.2447, 0.6652], 这将导致最终我们采样后的结果不够丰富;而[0.01,0.02,0.03] softmax 后[0.3300, 0.3333, 0.3367],这将导致最终的采样方法是在随机采样,生成不合理的序列。为为了解决这个问题,我们需要有个办法调节,让softmax 后的分布进一步符合我们的预期,对应的办法就是增加参数$T$:<br>$$\frac{\exp(z_i/T)}{\sum_jexp(z_j/T)}$$<br><img src="/2023/03/10/llm-inf/1_p1iKxUJcXDlSEZCpMCwNgg.gif"></p><p>$T$ 越大,分布越趋近均匀分布(uniform distribution),采样结果随机性越大,生成的序列是不合理句子的概率就越高;<br>$T$ 越小,分布越趋近与单点分布(one-point distribution),采样结果越趋近保持一致。</p><h3><span id="penalty">* penalty</span><a href="#penalty" class="header-anchor"></a></h3><p>除了temperature 这种对整体分布进行修改外,还有些场景需要我们对特定的某些token 的分布进行修改,此时就诞生了各种penalty,如repetition_penalty/diversity_penalty等参数。注意:这里是对其分布乘以一个系数,其结果有可能并不改变其大小顺序。</p><h3><span id="qiang-zhi-x2f-jin-zhi-te-ding-token-de-chu-xian">强制/禁止 特定token 的出现</span><a href="#qiang-zhi-x2f-jin-zhi-te-ding-token-de-chu-xian" class="header-anchor"></a></h3><p>一些场景下,我们要求更加苛刻:希望某些token 一定/永不 出现,同理,此时将满足条件的token 的分布直接调整为100%/0%,如bad_words_ids/no_repeat_ngram_size/force_words_ids 等参数。</p><h1><span id="gpu">GPU</span><a href="#gpu" class="header-anchor"></a></h1><p>下面已A100-80G SXM 为例,了解一下GPU 的基本信息。</p><p>A100 specifications<br><img src="/2023/03/10/llm-inf/Pasted-image-20230307111416.png" alt="Pasted-image-20230307111416"></p><p>simplified view of the GPU architecture<br><img src="/2023/03/10/llm-inf/Pasted-image-20230309090721.png" alt="Pasted-image-20230309090721"></p><p>Multiply-add operations per clock per SM<br><img src="/2023/03/10/llm-inf/Pasted-image-20230308160737.png" alt="Pasted-image-20230308160737"></p><ul><li>FP16 Tensor Core peak dense throughputs:<br>$$1024 (Tensor cores) \ast 108 (SMs) \ast 1.41 GHz (clock rate) \ast 2 (multiply-add) = 312 TFLOPS $$</li></ul><h2><span id="performance">performance</span><a href="#performance" class="header-anchor"></a></h2><p>性能主要受限于三个因素:内存带宽(memory bandwidth),计算带宽(math bandwidth), 延时(latency).</p><p>假设我们访问内存花费的时间为$T_{mem}$ ,计算花费的时间为$T_{math}$, 计算和访问内存可以”你算上一个,我读/写 下一个“,这样两者的时间大部分都可以重叠,此时花费的所有时间就为$max (T_{mem}, T_{math})$.<br>$$T_{mem} = bytes/BW_{mem}$$<br>$$<br> T_{math} = ops/BW_{math}<br>$$<br>当性能受限于计算带宽时:<br>$$<br>T_{math} > T_{mem}<br>=><br>ops/BW_{math} > bytes/BW_{mem} => ops/bytes > BW_{math}/BW_{mem}<br>$$<br>假设我们使用float16 进行计算,而A100-80G SXM 对应的$BW_{math} / BW_{mem}=312e12 flops/2039e9 bytes=153 Flops/Byte$.<br>即在当前显卡下,如果一个函数的计算量与所需的存储量的比值> 153 Flops/Byte 时,此时的性能主要受限于计算带宽,反之则受限于内存带宽。</p><blockquote><p> 如何计算一个矩阵乘法的flops<br> 假设$A∈R^{1×n}$ ,$B∈R^{n×1}$,计算$AB$ 则需要 n 次乘法运算(operations)和n 次加法运算,对应的就是 $2n$ operations, 即$2n$ flops;同理:$A∈R^{m×n}$, $B∈R^{n×p}$ ,则需要 $2mnp$ flops.</p></blockquote><blockquote><p>如何计算存储大小<br>通常我们使用float16/bfloat16 存储,对应一个参数需要2 个bytes ,对于矩阵$A∈R^{m×n}$ 则需要 $2 \ast m \ast n$ bytes</p></blockquote><p>下面做个最简单的矩阵乘法:$A∈R^{1×n}$,$B∈R^{n×p}$<br>$$ops/bytes = 2np / (2n + 2np + 2p) < 1 < 153$$<br>除了需要将矩阵读进来还需要将结果写进内存,参数使用半精度(2 bytes),所以对应的总内存为: $2n + 2np + 2p$<br>此时的性能主要受限于内存带宽,如果此时将$A$ 扩大$153$ 倍至 $A∈R^{153×n}$ 消耗的计算时间是一样的,忽略内存带宽$(152 \ast 2 \ast n + 152 \ast 2 \ast p )$ bytes新增带来的影响,则扩大batch size 整体latency 基本不变。</p><p>具体深度学习Layer示例</p><table><thead><tr><th>operation</th><th>ops/bytes</th></tr></thead><tbody><tr><td>Linear layer (4096 outputs, 1024 inputs, batch size 512)</td><td>315 Flops/B</td></tr><tr><td>Linear layer (4096 outputs, 1024 inputs, batch size 1)</td><td>1 Flops/B</td></tr><tr><td>Max pooling with 3x3 window and unit stride</td><td>2.25 Flops/B</td></tr></tbody></table><h1><span id="gpt-generation">GPT generation</span><a href="#gpt-generation" class="header-anchor"></a></h1><p>GPT2 model architecture</p><p><img src="/2023/03/10/llm-inf/Pasted-image-20230310005359.png"></p><h2><span id="ops-vs-bytes">ops vs bytes</span><a href="#ops-vs-bytes" class="header-anchor"></a></h2><p>我们忽略所有的bias/layernorm/activation/add 操作,计算生成一个token 时的ops vs bytes, 切分head 与不切对应的ops/bytes 是一样的,为了简单我们按不切计算。</p><ol><li><p>Embedding<br> multiply:$E_{token}∈R^{V\times d_{model}}$, $E_p\in R^{s_{maxseq} \times d_{models}}$<br> mem: $2 \ast (V \ast d_{model} + s_{maxseq} \ast d_{model})$<br> flops: 0</p></li><li><p>attention qkv<br>multiply: $t_e \in R^{1 \times d_{model}}, W_q, W_K, W_V\in R^{d_{model}\times d_{model}}, t_e \times W_q/W_k/W_v => q/k/v$<br>mem: $ 2 \ast 3\ast d_{model}^2$<br>flops: $ 2 \ast 3 \ast d_{model}^2$</p></li><li><p>attention output<br> multiply: $q,k,v\in R^{1\times d_{model}}, softmax((q\cdot k)\div \sqrt{d_{head}}) \cdot v => o$<br> mem: 0<br> flops: $2 \ast 2 \ast d_{model}$</p></li><li><p>output projection<br>multiply: $o \in R^{1\times d_{model}}$, $W_o\in R^{d_{model}\times d_{model}}$, $o \times W_o => a$<br>mem: $2\ast d_{model}^2$<br>flops: $2 \ast d_{model}^2$</p></li><li><p>feed-forward<br> multiply:$a\in R^{1\times d_{model}}$, $W_1\in R^{d_{model}\times 4d_{model}}$, $W_2\in R^{4d_{model}\times d_{model}}$, $a\times W_1 \times W_2 =>z$<br> mem: $2 \ast 2 \ast d_{model} \ast 4d_{model} = 16 d_{model}^2$<br> flops: $2 \ast 2 \ast d_{model} \ast 4d_{model} => 16 d_{model}^2$</p></li></ol><p>剩下的计算就是重复$n\_layers$ 次2-5.现在我们来汇总一下计算总的ops / bytes:</p><p>$$<br>\frac{ops}{ bytes} = \frac{n_{layers} \ast (6d_{model}^2 + 4d_{model} + 2d_{model}^2 + 16d_{model}^2)} {2 \ast (V \ast d_{model} + s_{maxseq} \ast d_{model}) + n_{layers} \ast (6d_{model}^2 + 2d_{model}^2 + 16d_{model}^2)}<br>$$</p><p>越大的模型$d_{model}$ 越大,如10B 的模型 $d_{model}=4096$, 所以这里我们去掉$d_{model}$ 的常数项:</p><p>$$<br>\frac{ops} { bytes} \approx \frac{n_{layers} \ast (6d_{model}^2 + 2d_{model}^2 + 16d_{model}^2)} { n_{layers} \ast (6d_{model}^2 + 2d_{model}^2 + 16d_{model}^2)} = 1<br>$$</p><blockquote><p>速记法<br>对于transformer-base encoder/decoder :<br>ops $\approx 2 \ast Parameters$,<br>float16/bfloat16 下:bytes $\approx 2 \ast Parameters$.</p></blockquote><p>按照上面我们的结论,此时对于A100-80G 来说,性能主要受限于内存带宽,计算1 个token 与计算153个token 对应的latency 是一样的!为了提升整体性能,我们应该增加$batch\_size \ast input\_length$直到 inputs 扩大至153倍.</p><h2><span id="attention">attention</span><a href="#attention" class="header-anchor"></a></h2><p>现在回过头来再看一眼刚刚被我随意丢弃掉的一项ops:attention output<br>由于我们计算是由长度为1 的token 生成 1 个token, 假设输入的token 长度为$s$, 则:<br>$q,k,v \in R^{s\times d_{model}}$<br>flops: $ 2 \ast s \ast d_{model} \ast s + 2 \ast s \ast d_{model} \approx 2 \ast s^2 \ast d_{model}$ </p><p>随着inputs 的长度增加,达到$\sqrt{d_{model}}$ 时,这一项已经不能丢弃了,进一步增加,长度到$12 \ast \sqrt{d_{model}}$ 时,这一项已经与其他部分的ops 想当了,此时的$ops/bytes \approx 2$.<br>以10B 的模型为例,$d_{model} = 4096$ ,当输入的长度为64 时,attention output 这项ops 与其他部分想当;当长度增长为768 时,对应的$ops/bytes$ 增长到2,此时整体的计算量已经扩大了768 倍,$768 > (153 / 2 ) $,已经转变为性能受限于计算带宽,增大batch 不会带来性能提升。</p><h2><span id="cache">cache</span><a href="#cache" class="header-anchor"></a></h2><p>随着整个生成过程的迭代,输入的长度会逐步增加,对应的每步所需的计算量也指数倍的增加,整体性能都被他拖垮了。<br>对于$Y = A \cdot X$ ,当$X$ ,$A$ 不变时,对应的结果$Y$ 是不变的;而在迭代过程中,模型的权重是不变的,即$X$ 都是一样的,对于$step_t$ 与 $step_{t-1}$ 来说,都会计算$A[:, :t-1]$ 对应的结果,既然重叠了,就不需要重复计算了,我们每次只需要计算最后一个token 就好了。<br>等等,有一个层只有计算没有weights,即其对应的$A$, $X$ 都来动态的,需要我们单独处理。这一层就是attention output。(哈,又是你。attention is all I want to remove!)</p><p>对于$step_t$ 来说,对应的计算是:<br>$$softmax(q[:, t-1:t] \cdot k[:,0:t] \div \sqrt(d_head) ) \cdot v[:,0:t]$$<br>此时我们缺少的是$k[:,:t-1]/v[:,:t-1]$ ,好在我们在$step_{t-1}$ 时已经计算过这两个结果了,只要把上一个时刻的这个结果“拿到”拼接回去即可。<br>完整的流程是:对于迭代$step_t$ ,把对应attention qkv 的结果$q[:,t]/k[:,t]/v[:,t]$ 拼接到之前的之前的缓存$q[:,:t-1]/k[:,:t-1]/v[:,:t-1]$ 后面,为下一个时刻准备;计算attention output 时,先将之前的$k\_cache[:,t-1]/v\_cache[:,:t-1]$ 拼接到$k[:,t]/v[:,t]$ 前面,还原真实的$k/v$.</p><p>经过cache 后,attention output 的计算回到了$O(s\cdot d_{model})$ 的水平,至少在大部分时候又可以基本忽略了。此外,随着$s$ 的增长,对应的cache 也在线性增长,需要的内存带宽也在增长,所以有些情况下可能增加cache 反而降低性能。</p><p>PS:不管是生成过程中的k_cache/v_cache 还是prompt cache 还是context cache,只要是可能重复计算,就值得思考要不要将之前的结果做cache,已减少后续的计算量。</p><h2><span id="ding-liang-ping-gu-you-hua-fang-an">定量评估优化方案</span><a href="#ding-liang-ping-gu-you-hua-fang-an" class="header-anchor"></a></h2><p>上面是一个相对简化的思路计算,而真实情况会比较复杂。比如我们忽略了一部分操作所需的ops;中间结果的每次写入/读取所需的内存带宽;CUDA Core 与 Tensor Core 的性能差异等;此外不同的硬件参数也不相同,具体的operation 实现也不相同,所以要定量给出优化方案实在太难,通常(主要是我)只能通过测试给出大致范围。</p><h1><span id="jie-lun">结论</span><a href="#jie-lun" class="header-anchor"></a></h1><p>现在回到最初的问题:为什么当前的LLM 生成文本时对应的资源与prompt 与 completion 都有关系:</p><ol><li>需要迭代$n$ 次,其中$n = len(completion)$</li><li>每次迭代所需要的资源都接近$O(s)$ ,其中$s_i=len_(prompt) + step_i$</li></ol><h1><span id="ref">ref</span><a href="#ref" class="header-anchor"></a></h1><p><a href="https://www.nvidia.com/content/dam/en-zz/Solutions/Data-Center/a100/pdf/nvidia-a100-datasheet-us-nvidia-1758950-r4-web.pdf">NVIDIA A100 TENSOR CORE GPU</a></p><p><a href="https://docs.nvidia.com/deeplearning/performance/dl-performance-gpu-background/index.html#gpu-arch">dl-performance-gpu-background</a></p><p><a href="https://developer.nvidia.com/blog/nvidia-ampere-architecture-in-depth/">nvidia-ampere-architecture-in-depth</a></p><p><a href="https://huggingface.co/blog/how-to-generate">how to generate</a></p><p><a href="https://medium.com/mlearning-ai/softmax-temperature-5492e4007f71">softmax-temperature</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>stable diffusion生成</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="LLM" scheme="https://xv44586.github.io/tags/LLM/"/>
<category term="Inference" scheme="https://xv44586.github.io/tags/Inference/"/>
<category term="GPU" scheme="https://xv44586.github.io/tags/GPU/"/>
</entry>
<entry>
<title>few-shot视角下的fine-tuning</title>
<link href="https://xv44586.github.io/2023/02/01/fine-tuning-at-few-shot/"/>
<id>https://xv44586.github.io/2023/02/01/fine-tuning-at-few-shot/</id>
<published>2023-02-01T04:46:43.000Z</published>
<updated>2023-02-01T07:41:39.000Z</updated>
<content type="html"><# few-shot目前LLM 的使用方式主要是zero-shot/few-shot,而通常few-shot 的性能也比zero-shot 要好,而且随着example 的数量的增加,few-shot 的性能也可能进一步提升;zero-shot 时只给出task input 效果可能不佳,通常需要给出对应的task description,而更”精准“ 的task description 通常也能得到更好的zero-shot 效果。所以,提升LLM zero-shot /few-shot 的性能,主要的方式有两个:1. 给出更”好“的task description2. 给出更多的example<h1><span id="fine-tuning">fine-tuning</span><a href="#fine-tuning" class="header-anchor"></a></h1><h2><span id="supervised-fine-tuning">supervised fine-tuning</span><a href="#supervised-fine-tuning" class="header-anchor"></a></h2><p>对于小模型来说,其in-context learning 能力较弱,性能不理想。为了提升模型的性能,一个可以尝试的方向是增加context 中的example 数量,但是由于模型的context 窗口有限,不支持我们无限制的将更多的example 塞进context 中,所以我们变通一下,采用通过更新参数的方式将example 塞进“context”。此时可以看作是无限example(inf-shot);</p><h2><span id="instruction-tuning">instruction-tuning</span><a href="#instruction-tuning" class="header-anchor"></a></h2><p>通常在supervised fine-tuning 时,我们是将一个任务的example “塞”进context,而instruction-tuning 可以看作是同时将多个任务的example “塞”进context,为了在使用时区分应该当前context 里应该使用哪些example,我们在不同的task 前面增加对应的description,作为判断依据。即:同时将多个任务的多个example 塞进模型的context 中,使用时通过不同的task description 来区分当前context 内的example 应该是哪些。</p><h2><span id="prompt-tuning">prompt tuning</span><a href="#prompt-tuning" class="header-anchor"></a></h2><p>由于pretrain model 的任务是预测下一个token,而非处理用户的指令(instruction),为了提升模型zero-shot 的性能,一个可以尝试的方法就是找到模型视角下更好的task description(pattern),prompt-tuning 的思路即通过大量的监督样本,尝试寻找到更适应模型的task description,然后期望这个task description 能提高模型的zero-shot 的性能。</p><h2><span id="reinforcement-learning-with-human-feedback">Reinforcement learning with human feedback</span><a href="#reinforcement-learning-with-human-feedback" class="header-anchor"></a></h2><p>prompt-tuning 时,我们尝试找到模型视角下更好的task description,而这个方法显然是不利于交互的,更好的方式是让模型理解人类视角下的task description。rlhf 就是按照这个思路,让模型反过来更好的理解人类视角下的task description,使得交互更方便。</p><h1><span id="qi-fa">启发</span><a href="#qi-fa" class="header-anchor"></a></h1><p>从这个视角看,我们发现即使是小模型,也是有一定的in-context learning 的能力的,只是不够强,所以我们需要更多的example 他才能发挥出更好的效果;<br>pretrain+fine-tuning 的模式之所以能work,是因为pretrain 后的model 有in-context learning 的能力,in-context learning 并不要求task 的形式与pretrain 的一致,所以我们才能在pretrain 的基础上根据下游任务的不同,来构造不同的fine-tuning 过程,在few-shot 视角下,其对应的是在context 中”塞“进更多的example;<br>in-context learning 的增强,对应的是所需的样本逐渐减少,从supervised fine-tuning 的大量样本到few-shot 的少量样本最终到zero-shot 的不需要提供样本,只需提供任务描述。</p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>本文是笔者最近思考pretrain + fine-tuning 模式为什么能work 时,通过在few-shot 视角下的一个解释。通过该思路,笔者尝试将目前的fine-tuning 主流思路统一起来。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>太空视角下的喜马拉雅山脉</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="In-context learning" scheme="https://xv44586.github.io/tags/In-context-learning/"/>
<category term="LLM" scheme="https://xv44586.github.io/tags/LLM/"/>
<category term="fine-tuning" scheme="https://xv44586.github.io/tags/fine-tuning/"/>
</entry>
<entry>
<title>From zero to ChatGPT</title>
<link href="https://xv44586.github.io/2023/01/09/zero-to-chatgpt/"/>
<id>https://xv44586.github.io/2023/01/09/zero-to-chatgpt/</id>
<published>2023-01-09T12:26:20.000Z</published>
<updated>2023-02-07T08:51:20.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#language-model">Language Model</a><ul><li><a href="#tong-ji-yu-yan-mo-xing">统计语言模型</a></li><li><a href="#shen-jing-wang-luo-yu-yan-mo-xing">神经网络语言模型</a></li><li><a href="#sheng-ji">升级</a></li><li><a href="#gpt-3">GPT-3</a></li></ul></li><li><a href="#prompt-engineering">Prompt engineering</a><ul><li><a href="#pet">PET</a></li><li><a href="#automated-discrete-prompt">Automated Discrete Prompt</a></li><li><a href="#automated-continuous-prompt">Automated Continuous Prompt</a></li><li><a href="#multi-step-reasong-san-bu-zou">Multi-Step Reasong(三步走)</a></li><li><a href="#cot">CoT</a></li><li><a href="#least-to-most-prompting">Least-to-Most Prompting</a></li><li><a href="#self-ask">self-ask</a></li><li><a href="#kuo-zhan-ce-shi">扩展测试</a></li></ul></li><li><a href="#emergent-abilities">Emergent Abilities</a><ul><li><a href="#scaling-up">Scaling Up</a></li></ul></li><li><a href="#alignment">Alignment</a><ul><li><a href="#fine-tuning-with-human-feedback">Fine-tuning with human feedback</a><ul><li><a href="#sft">SFT</a></li><li><a href="#feedme">FeedME</a></li><li><a href="#reinforcement-learning-with-human-feedback">Reinforcement learning with human feedback</a></li></ul></li><li><a href="#instruction-tuning">Instruction-tuning</a></li></ul></li><li><a href="#chatgpt">ChatGPT</a></li><li><a href="#fan-wai-pian">番外篇:</a><ul><li><a href="#ru-he-ti-sheng-llm-zai-mou-ge-zu-te-ding-ren-wu-shang-de-xing-neng">如何提升LLM在某个(组)特定任务上的性能</a><ul><li><a href="#fine-tuning">Fine-tuning</a></li><li><a href="#fine-tuning-with-human-feedback-1">Fine-tuning with human feedback</a></li><li><a href="#rag">RAG</a></li><li><a href="#prompt-engineering">Prompt Engineering</a></li><li><a href="#instruction-tuning-1">Instruction-tuning</a></li></ul></li><li><a href="#ru-he-jiang-neng-li-cong-da-mo-xing-qian-yi-dao-xiao-mo-xing-shang">如何将能力从大模型迁移到小模型上</a></li></ul></li><li><a href="#ref">Ref</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div># Language Model长期一来,人类一直梦想着让机器替代人来完成各种工作,其中也包括语言相关工作,如翻译文字,识别语言,检索、生成文字等。为了完成这些目标,就需要机器理解语言。最早人们想到的办法是让机器模拟人类进行学习,如学习人类通过学习语法规则、词性、构词法、分析语句等学习语言。尤其是在乔姆斯基(Noam Chomsky 有史以来最伟大的语言学家)提出 “形式语言” 以后,人们更坚定了利用语法规则的办法进行文字处理的信念。遗憾的是,几十年过去了,在计算机处理语言领域,基于这个语法规则的方法几乎毫无突破。## 统计语言模型另一个对自然语言感兴趣的就是香农,他在很早就提出了用数学的方法来处理自然语言的想法。但是当时即使使用计算机技术,也无法进行大量的信息处理。不过随着计算机技术的发展,这个思路成了一种可能。首先成功利用数学方法解决自然语言问题的是贾里尼克 (Fred Jelinek) 及他领导的IBM Wason实验室。贾里尼克提出的方法也十分简单:判断一个词序列(短语,句子,文档等)是否合理,就看他的可能性有多大。举个例子:判断“I have a pen" 翻译为中文”我有个笔“是否合理,只需要判断”I have a pen.我有个笔" 这个序列的可能性有多大。而如何判断一个词序列的可能性,就需要对这个词序列的概率进行建模,也就是统计语言模型:$S$ 表示一连串特定顺序排列的词$w_1$,$w_2$, ..., $w_n$,n 是序列的长度,则$S$出现的概率$P(S)=P(w_1,w_2,…w_n)$.但是这个概率$P(S)$ 很难估算,所以这里我们转化一下。首先,利用条件概率公式将其展开:$$P(S)=P(w_1,w_2,..w_n)=P(w_1)∗P(w_2|w_1)∗P(w_3|w_1,w_2)∗…∗P(w_n|w_1,w_2,..w_{n−1})$$即:$$P(w_{1}^{n})=\prod_{i=1}^{n}P(w_i|w_{1}^{i-1})$$<p>接着,我们利用马尔可夫假设,即任意一个词$w_i$ 出现的概率只与其前一个词$w_{i-1})$(或有限的几个) 有关。于是,问题就变的简单了许多。对应的$S$ 的概率就变为:<br>$$<br>P(S)=P(w_{1}^{n})=\prod_{i=1}^{n}P(w_i|w_{1}^{i-1})\approx\prod_{i=1}^{n}P(w_i|w_{i-1})<br>$$<br>以上对应的便是一个二元模型,当然,如果词由其前面的$N-1$ 个词决定,则对应的是N元模型。</p><h2><span id="shen-jing-wang-luo-yu-yan-mo-xing">神经网络语言模型</span><a href="#shen-jing-wang-luo-yu-yan-mo-xing" class="header-anchor"></a></h2><p>统计语言模型有很多问题:1.训练语料中未出现过的词(句子)如何处理(OOV);2.长尾低频词如何平滑;3.one-hot 向量带来的维度灾难;4.未考虑词之间的相似性等。<br>为了解决上述问题,Yoshua Bengio(深度学习三巨头之一)在2003年提出用神经网络来建模语言模型,同时学习词的低维度的分布式表征(distributed representation),具体的:<br>1.不直接对$P(w_{1}^{n})$ 建模,而是对$P(w_i|w_{1}^{i-1})$进行建模;<br>2.简化求解时,不限制只能是左边的词,也可以含右边的词,即可以是一个上下文窗口(context) 内的所有词;<br>3.共享网络参数。</p><p>具体形式如下:<br>$$<br>P(w_t=i|w_{1}^{t-1})=f(i,w_{t-1},…,w_{t-n+1})=g(i, C(w_{t-1}),…,C(w_{t-n+1}))<br>$$<br><img src="/2023/01/09/zero-to-chatgpt/NNLM.png" alt="NNLM"><br>由于当时的计算机技术的限制,神经网络语言模型的概率结果往往都不好(一层MLP效果肯定好不了),所以当时主要还是用这个形式训练词向量。</p><h2><span id="sheng-ji">升级</span><a href="#sheng-ji" class="header-anchor"></a></h2><p>随着数据、算力、模型架构、范式等的升级,神经网络语言模型也得到了长足的发展。如模型架构从 mlp 到cnn/rnn 又到目前的transformer-base ,对应的能力也在不断发展,从之前只对$P(w_i|w_{i-1})$ 建模,通过”“并行”或“串行” 的方式,也可以对$P(w_i^n)$建模。求解NLP task 从原来的word2vector + ML 发展为pretrain + fine-tuning。目前最有代表性的就是BERT和GPT。<br><img src="/2023/01/09/zero-to-chatgpt/bert.jpeg" alt="BERT"><br><img src="/2023/01/09/zero-to-chatgpt/GPT.jpeg" alt="GPT"></p><p>BERT: 双向,autoencoding,MLM,encoder</p><p>GPT:left-to-right, autoregressive, LM, decoder</p><h2><span id="gpt-3">GPT-3</span><a href="#gpt-3" class="header-anchor"></a></h2><p>随着NLP进入BERT时代后,pretrain + fine tune 这种方式可以解决大量的NLP 任务,但是他依然有很多限制:<br>1.每个任务都需要大量的标注数据,这大大限制了模型的应用。此外,还有大量不好收集标注数据的任务存在;<br>2.虽然pretrain 阶段模型吸收了大量知识,但是fine-tuned 后模型又被“缩”到一个很窄的任务相关的分布上,这也导致了一些问题,比如在OOD(out-of-distribution) 上表现不好;<br>3.如果参考人类的话,人类通常不需要在大量的标注数据上学习后才能做任务,而只需要你明确告知你想让他干嘛(比如:将所给单词翻译为英语:红色->)或者给他几个例子(比如:蓝色->blue,绿色->green,红色->),之后便能处理新的任务了。<br>而我们的一个终极目标就是希望模型能像人这样,灵活的学习如何帮助我们完成工作。一个可能的方向就是元学习(meta-learning):学习如何学习。而在LM语境下,即我们希望LM 在训练的时候能获得大量的技能和模式识别的能力,而在预测的时候能快速将技能迁移到新任务或者识别出新任务的要求。为了解决这个问题,一个显现出一定有效性的方法就是”in-context learning”:用指令(instruction)或者少量示例(demonstrations)组成预训练语言模型的输入,期望模型生成的内容可以完成对应的任务。根据提供多少个示例,又可以分为zero-shot, one-shot, few-shot。<br><img src="/2023/01/09/zero-to-chatgpt/few-shot.png" alt="Learning via SGD vs In-context learning"></p><p><img src="/2023/01/09/zero-to-chatgpt/in-context.png" alt="In-context learning vs Fine-tuning"></p><p>虽然in-context learning 被证明具有一定的有效性,但是其结果相比fine-tuing 还有一定的距离。而随着预训练语言模型(PTM)规模的扩大(scaling up),对应的在下游task 上的表现也在逐步上升,所以OpenAI就猜想:PTM的进一步scaling up,对应的in-context learning 的能力是不是也会进一步提升?于是他们做了GPT-3 系列模型,最大的为GPT-3 175B。<br><img src="/2023/01/09/zero-to-chatgpt/abstract.jpeg" alt="gpt3 abstract"></p><p>最终的模型效果简单总结一下:一些任务上few-shot (zero-shot)能赶上甚至超过之前fine-tuned SOTA(如:PIQA),有些任务上还达不到之前的SOTA(如:OpenBookQA);能做一些新task,如3位数算数。<br>不过他们也发现了模型存在的一些问题,并提出了一些可能的解决方案。(所以OpenAI 在2020 年就定下了未来的方向,持续投入至今)<br><img src="/2023/01/09/zero-to-chatgpt/limits.jpeg" alt="limitation and future directions"></p><h1><span id="prompt-engineering">Prompt engineering</span><a href="#prompt-engineering" class="header-anchor"></a></h1><p>zero-shot/few-shot 这种设定确实给NLP 社区带来了新的思路,但是$175B$ 的模型实在是太大了,即不好训练又不好微调也不好部署上线,如何在小模型上应用呢?此外,不同的pattern(prompt)下同一个task 的效果差距也非常大,如何找到效果最好的prompt 呢?于是大家就开始花式探索prompt,NLPer 也变成了prompt-engineer (误).PS:prompt 的语义目前即可以指模型的输入,也可以指输入的一部分。<br><img src="/2023/01/09/zero-to-chatgpt/prompt.jpeg" alt="prompt methods"></p><h2><span id="pet">PET</span><a href="#pet" class="header-anchor"></a></h2><p>PET(Pattern-Exploiting Training)应该是第一个(至少是我知道的)在小模型上在few-shot 设定下成功应用的工作。<br>PET 的主要思路是:</p><ol><li>用通顺的语言为task 构造一个pattern(prompt),如: “下面是{label}新闻。{x}”;</li><li>将label 映射为文字。如: “0->体育 ,1-> 财经, 2->科技”;</li><li>将样本按照pattern 进行重构,冻结模型主体,只更新label 对应的token(embedding),继续LM (MLM) 训练;</li><li>预测时,将label 对应位置的token 再映射回label。</li></ol><p><img src="/2023/01/09/zero-to-chatgpt/bert-pet.jpeg" alt="BERT-PET"><br><img src="/2023/01/09/zero-to-chatgpt/gpt-pet.jpeg" alt="GPT-PET"></p><p>PET 在few-shot 的设定下,利用BERT-base 就能获得比GPT-3 175B 更好的结果。不过pattern 是需要人来构造的,pattern 的“好坏” 直接影响最终的效果。<br>思考:PET中的fine-tuning 是与其pretrain 的形式是一致的,而pretrain 与 fine-tuning 形式一致能够work 才是一种“自然”的事情,pretrain + fine-tuning 这种下游任务与预训练形式不一致能work 其实不是一个自然的事情,为什么pretrain + fine-tuning 能work 值得思考。</p><h2><span id="automated-discrete-prompt">Automated Discrete Prompt</span><a href="#automated-discrete-prompt" class="header-anchor"></a></h2><p>人来写prompt还是需要大量的时间和经验,而且,即使一个经验丰富的人,写出的prompt 也可能是次优的。为了解决这些问题,一种办法就是“自动”的帮助我们寻找最优的prompt。</p><ol><li>Prompt Mining: 该方法是在语料上统计输入X 与输出Y 之间的中间词或者依赖路径,选取最频繁的作为prompt,即:{X} {middle words} {Y}.</li><li>Prompt Paraphrasing: 该方法是基于语义的,首先构造种子prompts,然后将其转述成语义相近的表达作为候选prompts,通过在任务上进行验证,最终选择效果最优的。</li><li>Gradient-based Search: 通过梯度下降搜索的方式来寻找、组合词构成最优prompt。</li><li>Prompt Generation: 用NLG 的方式,直接生成模型的prompts。</li><li>Prompt Scoring: 构造模型对不同的prompt 进行打分,选择分数最高的prompt 作为最优prompt。</li></ol><h2><span id="automated-continuous-prompt">Automated Continuous Prompt</span><a href="#automated-continuous-prompt" class="header-anchor"></a></h2><p>虽然PET最初在构造prompt 时认为prompt需要是通顺流畅的自然语言。而随着各种自动化方法构造出了很多虽然句子不通顺但是效果更好的prompt,大家也发现:通顺流畅的自然语言或者是自然语言的要求只是为了更好的实现预训练与下游任务的“一致性”,但是这并不是必须的,我们其实并不关心这个pattern 具体长什么样,我们真正关心的是他有哪些token 组成,都插入在什么位置,输出空间是什么,以及最重要的在下游任务上表现有多好。<br><img src="/2023/01/09/zero-to-chatgpt/p-tuning.jpeg" alt="P-tuning"></p><p>如上图所示,其中[u*] 为unused token,我们在tuning 时依然冻结模型的参数,只微调[u1-u8] 这8个token。</p><ol><li>prompt tuning: 利用N 个unused token/new token 构造prompt, 然后微调这N 个token。其中N 是个超参数。</li><li>Tuning initialized with Discrete prompts:用手工构造的prompt 或者自动搜索的离散prompt 初始化需要微调的token,然后进行prompt tuning,有利于提高准去率。</li><li>Hard-Soft Prompt Hybrid Tuning: 这类方法将手动设计和自动学习相结合,增强prompt token 之间的相关性。如p-tuning 首先通过一个LSTM 训练手工设计的prompt中插入的可学习的token 来增强prompt 之间的相关性,让prompt token 更接近“自然语言”。</li><li>Prompt-parameter Tuning: 仅仅训练prompt token 效果不够好,将其与fine-tuning 结合。如prefix-tuning,在输入前增加可学习的prompt token 的同时,在模型每层都增加一部分可学习参数。</li></ol><h2><span id="multi-step-reasong-san-bu-zou">Multi-Step Reasong(三步走)</span><a href="#multi-step-reasong-san-bu-zou" class="header-anchor"></a></h2><p>虽然大模型在很多task 都证明了其有效性,但是这些task 都是System 1 thinking,而System 2 thinking 任务需要更多的数学、逻辑以及常识推理。大模型对这类任务还做不好目前,如数学推理、符号推理等。</p><blockquote><p>Our responses to these two scenarios demonstrate the differences between our slower thinking process and our instantaneous one. System 1 thinking is a near-instantaneous process; it happens automatically, intuitively, and with little effort. It’s driven by instinct and our experiences. System 2 thinking is slower and requires more effort. It is conscious and logical.</p></blockquote><p> – <a href="https://thedecisionlab.com/reference-guide/philosophy/system-1-and-system-2-thinking">system-1-and-system-2-think</a></p><p>如GPT-3 175B 在GSM8K 上直接fine-tuning 也只能得到33% 的准确率,通过在fine-tuned model 上进行采样,再标注答案是否正确,然后训练一个verifier 来判断生成的答案是否正确,最终也只能得到55%,而一个9-12 岁的孩子平均能得到60%。所以,OpenAI 的研究员认为,如果想达到80% 以上,可能需要把模型扩大到$10**16$(175T?妈妈咪啊)。<br>然而,后续的工作Gopher 却给这个思路泼了盆冷水:即使继续放大模型,模型在这种推理任务上的表现也不会显著提升。也许语言模型就不能做推理这种system 2 thinking task。</p><h2><span id="cot">CoT</span><a href="#cot" class="header-anchor"></a></h2><p>“不就是个张麻子嘛,办他!”(误)不就是推理嘛,LLM 也能做,只需要向人学习一下就行了。<br><img src="/2023/01/09/zero-to-chatgpt/cot.png" alt="CoT"></p><p>回想读书时做数学应用题目,老师总是要求你写清解题步骤。而之前的方法总是让模型一步到位,直接给出答案,所以模型考不好。现在我们让模型像人类推理一样,先给出思考步骤(chain of thought),然后再给出答案,模型的推理能力就能大大提高了。而这个思路,只需要few-shot(8 examples)就能达到58.1% 的准确率,超过之前GPT-3 175B fine-tuning + verifier (SOTA)。除了GSM8K 这种算术推理外,在符号推理、尝试推理任务上CoT 也是能将模型的性能提升一大截。<br>CoT 确实很酷,改变了我们之前对LLM 的认知,但是还不够酷:很多时候我们不一定能凑够8个样本(我就无法快速给出8个带有解题步骤的数学题),那能不能在zero-shot 下让模型自己给出解题思路跟答案呢?</p><blockquote><p>“Let’s think step by step.”</p></blockquote><p>没错,答案就是这句话。只要在输入后面加一句”Let’s think step by step.”哄哄模型,模型就会自己按照CoT 的方式先生成解题思路,然后再生成对应的答案。(我第一次读完paper 的感觉就是离谱他妈给离谱开门,离谱到家了)PS:这句话是试出来的,还有很多类似的表达但是效果不如这句好。<br><img src="/2023/01/09/zero-to-chatgpt/zero-shot-cot.jpeg" alt="Zero-shot-CoT"></p><table><thead><tr><th>method</th><th align="center">GSM8K</th></tr></thead><tbody><tr><td>9-12 year olds (Cobbe et al,.2021)</td><td align="center">60</td></tr><tr><td>Finetuned GPT-3 175B</td><td align="center">33</td></tr><tr><td>Finetuned GPT-3 + verifier</td><td align="center">55</td></tr><tr><td>PaLM 540B: standard prompting</td><td align="center">17.9</td></tr><tr><td>PaLM 540: chain of thought prompting</td><td align="center">58.1</td></tr><tr><td>GPT-3 175B + Complexity-based Consistency</td><td align="center">72.6</td></tr><tr><td>PaLM 540B: Cot + majority voting</td><td align="center">74.4</td></tr><tr><td>Codex 175B (GPT3.5) + complex chains of thought</td><td align="center">82.9</td></tr><tr><td>PaLM 540B: Zero-Shot</td><td align="center">12.5</td></tr><tr><td>PaLM 540B: Zero-Shot-Cot</td><td align="center">43</td></tr><tr><td>PaLM 540B: Zero-Shot-Cot + self consistency</td><td align="center">70.1</td></tr></tbody></table><p>Zero-Shot-Cot 就能获得43% 的准确率,而Zero-Shot-Cot + self consistency 甚至可以获得70.1的准确率。<br>Zero-Shot-CoT + self consistency: 按照Zero-Shot-Cot 的方式,通过采样(sample)让模型生成多个结果,然后对答案进行投票。<br>目前在GSM8K上的SOTA是82.9,看来不需要继续放大模型,只需要正确使用模型。</p><p>关于CoT 来源的问题,目前的主要推论是可能来自预训练时数据中包含了代码数据(code),主要论据为:1.GPT-3.5(Codex) 有CoT 能力,PaLM 也有,而其他LLM (包括原始GPT-3)却没有,这两个模型与其他模型的一个主要区别就是增加了代码数据;2.有工作认为CoT 与代码的自然语言翻译形式相同,所以CoT 可能来自这种能力的迁移。</p><h2><span id="least-to-most-prompting">Least-to-Most Prompting</span><a href="#least-to-most-prompting" class="header-anchor"></a></h2><p>如果仔细对比CoT 和之前的prompt 的话,其中最大的不同是CoT 模仿人类推理将过程分为多个阶段。而有些问题如组合泛化直接用CoT 也不好解决。于是就提出了另一种多步推理的方法,Least-to-Most Prompting:<br>首先将问题分解为子问题“To solve{Q}, we need to first solve: sub-q”,得到子问题的答案后再来给出最后的答案.<br><img src="/2023/01/09/zero-to-chatgpt/least-to-most.png" alt="Least-to-most"></p><h2><span id="self-ask">self-ask</span><a href="#self-ask" class="header-anchor"></a></h2><p>self-ask:先让LLM 自问自答生成多跳问题与答案,然后再生成最终的答案。<br><img src="/2023/01/09/zero-to-chatgpt/self-ask.jpeg" alt="self-ask"></p><h2><span id="kuo-zhan-ce-shi">扩展测试</span><a href="#kuo-zhan-ce-shi" class="header-anchor"></a></h2><p>以上的实验都是基于事实的推理,但是我想看看模型是不是有类似反事实推理的能力,所以我做了三个测试:<br>第一次我直接让他解释一个反事实的东西;第二次设定一个反事实(红色对应单词是”blue”), 基于此让他做一个翻译任务;第三次,在给出的例子里增加相应的事实(蓝色->blue),继续让他做这个翻译任务。<br><img src="/2023/01/09/zero-to-chatgpt/test1.png" alt="实验1"><br>实验1<br><img src="/2023/01/09/zero-to-chatgpt/test2.png" alt="实验2"><br>实验2<br><img src="/2023/01/09/zero-to-chatgpt/test3.png" alt="实验3"><br>实验3</p><p>三个测试结果显示模型确实有很强的推理能力,包括反事实的推理能力。此外,实验二、三显示模型有很强的基于prompt 推理的能力(in-context learning?),甚至要想更正prompt 里错误的信息需要花点心思才行。</p><p>PS:后面两次测试只是证明了模型”能“基于prompt 做推理,而无法证明模型”总是“基于prompt 做推理。</p><p>思考:</p><ol><li>目前流行的RAG(Retrieval-Augmented Generation)是不是基于模型具有的这种推理能力?</li><li>LLM 表现出的能胡说八道(Hallucinations) 是否也是由模型具有这种反事实推理带来的?以及如何让”胡说八道“变成”创造“。</li><li>这种能力也带来了一个问题:模型生成的答案并不是预训练数据优先(pretrain data first),如果我们的prompt 里出现了反事实的东西(retrieval / dialog query/ demonstration),那模型就很可能生成一个”错误“ 的答案。</li></ol><h1><span id="emergent-abilities">Emergent Abilities</span><a href="#emergent-abilities" class="header-anchor"></a></h1><p>既然LLM 有这么多神奇的能力,包括Zero-Shot-CoT 这种推理能力。那我们之前这么多人用手工的或者自动的方式构造prompt,为什么没找到”Let’s think step by step”这句话呢?</p><p>原因可能是你的模型不够大(大力真的能出奇迹)。随着LLM不断的放大,当他大到一定规模时,他会突然显现出新的能力,即”涌现能”力(Emergent Abilities)。而即使是今天,我们大部分人接触的模型还是1B以下的,LLM 中被称作”small model” 的T5-11B 大部分人也用不起来,这就限制了我们发现LLM 的各种能力。<br>Emergency 的原始含义是指量变引起质变,即:</p><blockquote><p>Emergence is when quantitative changes in a system result in qualitative changes in behavior.</p></blockquote><p>而在LLM 语境下,其含义为在小模型中没有而在大模型中出现的能力,即:</p><blockquote><p>An ability is emergent if it is not present in smaller models but is present in larger models.</p></blockquote><h2><span id="scaling-up">Scaling Up</span><a href="#scaling-up" class="header-anchor"></a></h2><p><img src="/2023/01/09/zero-to-chatgpt/em.jpeg" alt="Emergent abilities"></p><p>上表是目前已有工作中涌现各种能力的模型及其最小规模。基本可以认为你需要至少68B parameter model (前提训练的OK)才能涌现新能力。而这里涌现新能力指的是性能优于随机,而要达到可用,你可能需要继续放大模型。如CoT 至少需要GPT-3 175B 才能优于精调小模型(t5-11b).</p><p>此外,与模型性能有关的不光有参数量,还有数据大小,数据质量,训练计算量,模型架构等.合理的比较应该是性能最好的LLM 在参数量上进行比较,然而我们目前还不知道如何训练让LLM 达到最优,所以同一个能力在不同模型上需要的参数量也不相同,如要涌现出2位数乘法的能力,只需要GPT-3 13B,而在LaMDA 却需要68B。</p><p>所以除了规模外,还有别的因素影响着是否能出现新能力:</p><ol><li>模型如何训练的,很多模型即使参数足够大,有些能力也可能不会出现。如原始GPT-3 175B、bloom-176B 等虽然参数够大,但是却都没有CoT 的能力。</li><li>LLM 的使用方法,fine-tuning/标准的prompt 方法在推理任务上效果不好,即使在GPT-3 175B 上效果也达不到中学生平均水平,而CoT 却只要100B parameter model 即可超越之前最好结果。</li><li>如何提升模型能力,在follow instruction 上,之前的工作认为至少需要68B parameter model 才能有效instruction-finetuning,而后续的flan-t5 却在11B 上就得到了更好的性能;GPT-3 经过RLFH 后的InstructGPT,在follow instruction 上, 1.3B 就已经比之前的GPT-3 175B 性能更好。</li><li>模型的架构,上面的结果都是transformer-based 的,而有工作验证了其他模型架构(RNN/MLP),最后结论是其他架构即使放大,也无法像transformer-based model 一样涌现能力。<strong>again: attention is all you need!</strong></li></ol><h1><span id="alignment">Alignment</span><a href="#alignment" class="header-anchor"></a></h1><p>到目前为止,我们已经知道了LLM 有很多能力,而且随着模型规模的扩大,可能会出现更多的新能力。但是,有个问题却严重制约着他在实际中的应用:prompt engineering。仔细思考一下这个问题,其本质其实是模型认为处理一个task 的prompt 跟我们以为的不一样,如我们认为当我们说“问答:”时模型就应该知道后面的是一个QA task,而模型可能觉得,如果你想让我做QA task,你需要告诉我”妈咪妈咪哄”。</p><p>这就好比至尊宝已经得到了月光宝盒,但是却需要找到“般若波罗蜜”这句口诀然后喊出来才能穿越一样,而且环境稍微不同,具体穿越到哪还不一定。那更好的方式应该是我们拿到月光宝盒,然后说一句:我要穿越到白晶晶自杀前五分钟,然后我们就穿越到了对应的时空。</p><p>理想情况下,LLM 应该正确理解用户的指令,包括同一个任务的不同描述(LLM 应该对语义相同的instruction 表现一致,而非对prompt 的形式非常敏感)。而LLM 训练时的任务是预测下一个时刻的词(predict next token),而非处理用户的指令(follow instruction),所以存在gap 也是很自然的事。为了缓解这个问题,一个方法就是进行“对齐”(Alignment),缩小模型与人类对同一个instruction 之间理解的gap,从而让模型能更好的理解用户的指令。</p><h2><span id="fine-tuning-with-human-feedback">Fine-tuning with human feedback</span><a href="#fine-tuning-with-human-feedback" class="header-anchor"></a></h2><p>一种很直接的想法就是构造数据进行fine-tuning。所以为了让模型更好的理解人类的指令,我们需要通过人类反馈进行微调模型(human in the loop)。</p><h3><span id="sft">SFT</span><a href="#sft" class="header-anchor"></a></h3><p>构造人类真实场景中的指令即期望的输出,然后直接进行SFT(supervised fine-tuning)。</p><h3><span id="feedme">FeedME</span><a href="#feedme" class="header-anchor"></a></h3><p>进过SFT 后模型可能已经能很好的理解人类指令了,但是其答案可能有其他问题,如胡编乱造,包含色情敏感内容等,此外,靠人写数据成本高又耗时,所以我们可以对多个模型的结果进行打分(7分),然后在7/7 的数据上继续训练,对多个模型的最好结果进行蒸馏(distill)。这个方法叫FeedME(Feedback Made Easy).</p><h3><span id="reinforcement-learning-with-human-feedback">Reinforcement learning with human feedback</span><a href="#reinforcement-learning-with-human-feedback" class="header-anchor"></a></h3><p>即使我们从人写完整的样本转换为人给模型采样的结果进行打分,整个流程依然需要人参与,也限制了整个流程的加速。为了更高效的进行整个微调的流程,引入Reinforcement learning。该方法又叫RLHF(Reinforcement learning from human feedback)。<br><img src="/2023/01/09/zero-to-chatgpt/rlhf.jpeg" alt="RLHF"></p><p>具体流程:</p><ol><li>标注人员手写(prompt,completion),然后进行SFT。这里主要是得到一个比较好的初始化模型,即模型至少能有一定的follow instruction 的能力。</li><li>收集模型输出并进行打分,然后训练一个reward model。</li><li>利用强化学习优化模型。<br><img src="/2023/01/09/zero-to-chatgpt/rlhf-result.jpeg" alt="RLHF evalutions"></li></ol><p>结果上看,效果显著,1.3B 就超过了之前175B 的结果,而且随着模型增大,结果也在上升。</p><h2><span id="instruction-tuning">Instruction-tuning</span><a href="#instruction-tuning" class="header-anchor"></a></h2><p>虽然fine-tuning with human feedback 可以提升LLM 在真实场景中用户任务上(customer task)的性能,但是在学术任务(academic NLP tasks)上的性能却会有所下降,即使OpenAI 尝试在RL 中增加部分pretrain data同时增加LM loss来尝试缓解这个问题,但是依然没有解决。<br><img src="/2023/01/09/zero-to-chatgpt/ins.jpeg" alt="performance on benchmark"></p><p>如何解决这个问题呢?办法就是instruction-tuning:<br>利用academic NLP data,为其构造对应的zero-shot/few-shot/CoT pattern,然后进行fine-tuning。<br><img src="/2023/01/09/zero-to-chatgpt/ins-tuning.jpeg" alt="instruction finetuning"></p><p>instruction-tuning 效果显著:<br>1.不光能提升大模型在academic NLP benchmark 上的性能,也能提升小模型上的性能;<br>2.能提升instruction-tuning 时未见过的task 上的性能;<br>3.能解锁小模型上的CoT 能力;<br>4.随着任务数量的增加,对应的提升也会增加。<br>5.最重要的是也能提升LLM 理解人类真实指令(follow instruction)的能力。</p><p>ps: 虽然follow human instruction 的能力提升了,但是跟InstructGPT 谁的性能更好却没有对比,我猜应该是不如InstructGPT,实际应用/学术指标两者依然是天枰的两端。<br><img src="/2023/01/09/zero-to-chatgpt/flan.png" alt="Instruction-fintuning performance"></p><h1><span id="chatgpt">ChatGPT</span><a href="#chatgpt" class="header-anchor"></a></h1><p>那如何才能得到一个ChatGPT呢?</p><ol><li>首先我们需要一个具备各种能力(潜力)的LLM,所以它要足够大,训练的足够好。OpenAI 大概率也是为此重新训练了一个GPT-3 模型(GPT-3.5),主要论据为:1.原始GPT-3 175B和复现GPT-3 的OPT-175B 都没有CoT 能力,而GPT-3.5 有CoT;<br>2.原始的GPT-3 的窗口只有2048,而其对应的是绝对位置编码,现在的GPT-3.5最大窗口为8192。<br>3.原始的GPT-3 不能写代码,现在的可以。</li><li>标注人员手写符合人类的instruction data(最好再混合一些academic instruction data,如:Flan),然后进行SFT,让模型能更好的follow instruction。</li><li>在对话场景下构造对应的instruction data,进一步fine-tuning with human feedback(RLHF加速流程).<br><img src="/2023/01/09/zero-to-chatgpt/chatgpt.png" alt="GPT-3/GPT-3.5 series"></li></ol><h1><span id="fan-wai-pian">番外篇:</span><a href="#fan-wai-pian" class="header-anchor"></a></h1><h2><span id="ru-he-ti-sheng-llm-zai-mou-ge-zu-te-ding-ren-wu-shang-de-xing-neng">如何提升LLM在某个(组)特定任务上的性能</span><a href="#ru-he-ti-sheng-llm-zai-mou-ge-zu-te-ding-ren-wu-shang-de-xing-neng" class="header-anchor"></a></h2><p>虽然LLM 具有很多能力,但在实际场景中,我们可能只使用其中的一个或一组特定的能力,那如何提升LLM 在某个特定任务上的性能呢?答案是:不确定。</p><h3><span id="fine-tuning">Fine-tuning</span><a href="#fine-tuning" class="header-anchor"></a></h3><p>另一个思考就是构造大量的supervised data 直接fine-tuning。<br>Gopher 中针对对话任务做了对比实验。<br>Dialog-Tuned Gopher: fine-tuning Gopher on 5B tokens of curated dialog dataset from MassiveWeb<br>Dialog-Prompted Gopher: few-shot<br><img src="/2023/01/09/zero-to-chatgpt/dialog.png" alt="prompt vs fine-tuning"></p><p>可以看到,fine-tuning 后的模型性能与直接prompt 的基本持平(甚至有点下降的趋势),并没有带来任何提升。</p><p>而Codex(GPT-3) 针对代码(code) 做了fine-tuning,利用159G github code data 在GPT-3 上进行fine-tuning,模型从基本无法处理代码任务提升到当时的SOTA,甚至只需要12B 就能达到从0 到72% 。<br><img src="/2023/01/09/zero-to-chatgpt/codex.png" alt="Codex"></p><h3><span id="fine-tuning-with-human-feedback">Fine-tuning with human feedback</span><a href="#fine-tuning-with-human-feedback" class="header-anchor"></a></h3><p>之前我们提到通过RLHF 可以进行alignment,让模型更好的follow instruction。但是,这种对齐也会对模型的性能带来一定的损失,又叫“对齐税”(alignment tax)。<br><img src="/2023/01/09/zero-to-chatgpt/ins.jpeg" alt="benchmark performance"></p><p>在学术NLP的benchmark 上,code-davinci-2(base model of text-davinci-2/text-davinci-3)的性能都是优于fine-tuning 后的模型。</p><h3><span id="rag">RAG</span><a href="#rag" class="header-anchor"></a></h3><p>另外一种常用的方案就是RAG(Retrieval-Augmented Generation)<br><img src="/2023/01/09/zero-to-chatgpt/rag.jpeg" alt="open-domain QA performance"></p><p>从实验结果上看,RAG 能带来一定的提升,但是有限,不如prompt 方法带来的提升明显。</p><p>而另一个工作说,RAG 是带来提升还是下降跟别的因素有关,如在QA 上,他可能跟对应实体的知名度(popularity) 有关。LLM 已经存储了知名度高的实体信息,而RAG 并不能带来性能提升,反而由于retrieval 到错误信息而导致性能下降,对于知名度低的实体通过RAG 是能带来显著提升的。<br><img src="/2023/01/09/zero-to-chatgpt/popular.png" alt="Popularity vs retrieval"></p><h3><span id="prompt-engineering">Prompt Engineering</span><a href="#prompt-engineering" class="header-anchor"></a></h3><p>在CoT 出来之前,我们一度认为LLM 可能需要继续进行指数级的扩大才能线性提升其推理能力,而CoT 的出现解锁了模型的推理能力。所以,一个可能的方案可能是在特定任务上继续寻找他的“般若波罗蜜”。不过笔者认为,这只是一个过渡期而非常态,随着RLHF/Instruction-tuning 等方法的发展,未来模型的使用一定会越来越简单便捷。</p><h3><span id="instruction-tuning">Instruction-tuning</span><a href="#instruction-tuning" class="header-anchor"></a></h3><p>instruction-tuning 已经证明了他的有效性,如flan-t5,flan-PaLM 经过instruction-tuning 后,其性能都得到了提升。</p><h2><span id="ru-he-jiang-neng-li-cong-da-mo-xing-qian-yi-dao-xiao-mo-xing-shang">如何将能力从大模型迁移到小模型上</span><a href="#ru-he-jiang-neng-li-cong-da-mo-xing-qian-yi-dao-xiao-mo-xing-shang" class="header-anchor"></a></h2><ol><li>instruction-tuning,通过大量的instruction-data 进行fine-tuning,可以解锁小模型上对应的能力,但是相对大模型,通常还是有差距。</li><li>压缩模型,如有工作将OPT-175B 蒸馏至75B,性能基本无损。(但是75B依然很大啊大佬!)。</li><li>蒸馏,让性能更好的大模型,生成大量的task-data,然后在小模型上进行fine-tuning,但是这可能需要生成很多data,鉴于LLM 都比较贵,所以这个可能需要很多钱。</li></ol><h1><span id="ref">Ref</span><a href="#ref" class="header-anchor"></a></h1><p>数学之美</p><p><a href="http://www.iro.umontreal.ca/~vincentp/Publications/lm_jmlr.pdf">A Neural Probabilistic Language Model</a></p><p><a href="http://arxiv.org/abs/2005.14165">Language Models are Few-Shot Learners</a></p><p><a href="https://arxiv.org/pdf/2205.01068.pdf">OPT: Open Pre-trained Transformer Language Models</a></p><p><a href="http://arxiv.org/abs/2107.13586">Pre-train, Prompt, and Predict: A Systematic Survey of Prompting Methods in Natural Language Processing</a></p><p><a href="https://spaces.ac.cn/archives/8295/comment-page-1">P-tuning:自动构建模版,释放语言模型潜能</a> </p><p><a href="http://arxiv.org/abs/2201.11903">Chain of Thought Prompting Elicits Reasoning in Large Language Models</a></p><p><a href="http://arxiv.org/abs/2205.11916">Large Language Models are Zero-Shot Reasoners </a></p><p><a href="http://arxiv.org/abs/2203.11171">Self-Consistency Improves Chain of Thought Reasoning in Language Models</a></p><p><a href="https://openreview.net/pdf?id=yf1icZHC-l9">Complexity-based Prompting for Multi-Step Reasoning. </a> </p><p><a href="http://arxiv.org/abs/2205.10625">Least-to-Most Prompting Enables Complex Reasoning in Large Language Models </a></p><p><a href="http://arxiv.org/abs/2210.03350">Measuring and Narrowing the Compositionality Gap in Language Models</a> </p><p><a href="http://arxiv.org/abs/2206.07682">Emergent Abilities of Large Language Models</a> </p><p><a href="http://arxiv.org/abs/2203.02155">Training language models to follow instructions with human feedback</a></p><p><a href="http://arxiv.org/abs/2110.14168">Training Verifiers to Solve Math Word Problems</a></p><p><a href="http://arxiv.org/abs/2210.11416">Scaling Instruction-Finetuned Language Models</a></p><p><a href="https://arxiv.org/pdf/2112.11446.pdf">Scaling Language Models: Methods, Analysis & Insights from Training Gopher</a><br> <br><a href="https://beta.openai.com/docs/model-index-for-researchers">model-index-for-researchers</a> </p><p><a href="https://yaofu.notion.site/How-does-GPT-Obtain-its-Ability-Tracing-Emergent-Abilities-of-Language-Models-to-their-Sources-b9a57ac0fcf74f30a1ab9e3e36fa1dc1">How does GPT Obtain its Ability? Tracing Emergent Abilities of Language Models to their Sources </a></p><p><a href="https://openai.com/blog/instruction-following/">instruction-following</a> </p><p><a href="https://arxiv.org/pdf/2209.10063.pdf">GENERATE RATHER THAN RETRIEVE: LARGE LANGUAGE MODELS ARE STRONG CONTEXT GENERATORS</a></p><p><a href="https://akariasai.github.io/files/llm_memorization.pdf">When Not to Trust Language Models: Investigating Effectiveness and Limitations of Parametric and Non-Parametric Memories.</a> </p><p><a href="https://arxiv.org/pdf/2107.03374.pdf">Evaluating Large Language Models Trained on Code</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>ChatGPT BG from <a href="https://openai.com/blog/chatgpt/">https://openai.com/blog/chatgpt/</a></p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="GPT-3" scheme="https://xv44586.github.io/tags/GPT-3/"/>
<category term="In-context learning" scheme="https://xv44586.github.io/tags/In-context-learning/"/>
<category term="LLM" scheme="https://xv44586.github.io/tags/LLM/"/>
<category term="ChatGPT" scheme="https://xv44586.github.io/tags/ChatGPT/"/>
</entry>
<entry>
<title>tokenizers 总结</title>
<link href="https://xv44586.github.io/2022/09/08/tokenizers/"/>
<id>https://xv44586.github.io/2022/09/08/tokenizers/</id>
<published>2022-09-08T07:23:52.000Z</published>
<updated>2026-01-01T21:57:47.227Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#tokenizer">tokenizer</a><ul><li><a href="#word-level">word level</a></li><li><a href="#char-level">char level</a></li><li><a href="#subword-level">subword level</a></li><li><a href="#bpe">BPE</a></li><li><a href="#bytes-bpe">Bytes BPE</a></li><li><a href="#wordpiece">WordPiece</a></li><li><a href="#unigram">Unigram</a></li><li><a href="#sentencepiece">SentencePiece</a></li><li><a href="#train-from-scratch">train from scratch</a></li></ul></li><li><a href="#tui-jian">推荐</a></li><li><a href="#bu-chong-yue-du">补充阅读</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div># tokenizer目前的机器学习模型都是数学模型,其对应的输入要求必须是数字形式(number)的,而我们处理的真实场景往往会包含许多非数字形式的输入(有时候即使原始输入是数字形式,我们也需要转换),最典型的就是NLP 中的文字(string),为了让文字能够作为输入参与到模型的计算中去,我们就需要构建一个映射关系(mapping):将对应的文字映射到一个数字形式上去,而其对应的数字就是token。而对应的这个映射关系,就是我们的tokenizer:他可以将文字映射到其对应的数字上去(encode),也可以将数字映射回对应的文字上(decode).<h2><span id="word-level">word level</span><a href="#word-level" class="header-anchor"></a></h2><p>那如何构建这个映射关系呢?最简单的想法:一个词对应一个id 不就好了。也就是”word level”。然后,”词”缺不是那么好分的:对于中文,词到底分成什么粒度,”苹果手机“到底是一个词还是二个词呢?”武汉市/长江/大桥/欢迎/你“与”武汉/市长/江大桥/欢迎/你“应该选择哪个方案呢?此外,对于”虚坤“/”鸡你太美“等这些新词怎么识别呢?对这部分感兴趣的可以看我更早期关于分词总结的博客:<a href="https://xv44586.github.io/2019/10/22/cutwords/">分词算法综述</a>.</p><p>中文有分词的问题,那英语总没有了吧,空格+标点就是天然分隔符,然而事情也没那么简单:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Don<span class="string">'t cry!</span></span><br></pre></td></tr></table></figure><p>按照空格与标点切分,得到的结果是:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">["Don", "'", "t", "cry", "!"]</span><br></pre></td></tr></table></figure><p>哈,有点怪,我们需要把”Don’t” -> “Do”/“n’t”,这样才更合理一点。即:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">["Do", "n't", "cry", "!"]</span><br></pre></td></tr></table></figure><p>除了如何切分成词外,还有一个最大的问题是结果集太大。比如TransformerXL 中,使用标点和空格,切分后的词表大小有267K,如此大的词表,不管是对存储还是计算都有压力。</p><p>此外,随着文化发展,每天都有大批的新词出现,用词粒度做映射就越来越不理想了。</p><h2><span id="char-level">char level</span><a href="#char-level" class="header-anchor"></a></h2><p>既然分词这么麻烦,我不分不好了,我就按“字”(char) 来做最小单元做映射。这样词表就小多了:英文只需要26个字母即可,中文根据2013年<a href="http://www.moe.gov.cn/jyb_xwfb/s5147/201308/t20130828_156423.html">中华人民共和国教育部《通用规范汉字表》定义“规范汉字”</a>,国家规定的通用规范汉字一共为8105个,相比之下也不算大。</p><p>然而char level 的主要问题是切分的太细:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">pneumonoultramicroscopicsilicovolcanoconiosis</span><br><span class="line"></span><br><span class="line">(医)肺尘病、矽肺病</span><br></pre></td></tr></table></figure><p>上面这个单词是我找到的最长的英语单词,他有45个字母组成,而中文中也存在大量的成语/歇后语/专用名词等,如:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">只许州官放火不许百姓点灯</span><br></pre></td></tr></table></figure><p>目前我们NLP 的主要思路是对句子进行,即:<br>$$<br>P(S)=P(w_1,w_2,..w_n)=P(w_1)∗P(w_2|w_1)∗P(w_3|w_1,w_2)∗…∗P(w_n|w_1,w_2,..w_{n−1})<br>$$<br>而切分太细,则对应$S$ 的长度会变长,无疑大大增加建模难度(通过字来学习词的语义),也常常导致模型效果不理想。</p><h2><span id="subword-level">subword level</span><a href="#subword-level" class="header-anchor"></a></h2><p>为了缓解两种方式的问题,一个想法是取长补短,即目前的主流方案:subword level.</p><p>subword level 的主要出发点是:我们应该尽量保留高频词,对低频词进行逐级切分,以获得一个词表大小合适,又对后续建模友好的方案。</p><p>目前subword level 的tokenizer 方法主要有BPE, Bytes BPE, WordPiece, Unigram, SentencePiece,下面简单总结一下各个方法。 </p><h2><span id="bpe">BPE</span><a href="#bpe" class="header-anchor"></a></h2><p>bpe 的方案是通过统计词频来确定两个相邻的pair subwords 要不要合并,具体做法:<br>1.统计pre-tokenize 的word 的词频;</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">(<span class="string">"hug"</span>, 10), (<span class="string">"pug"</span>, 5), (<span class="string">"pun"</span>, 12), (<span class="string">"bun"</span>, 4), (<span class="string">"hugs"</span>, 5)</span><br></pre></td></tr></table></figure><p>2.使用词典对word 进行切分:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># base vocabulary: ["b", "g", "h", "n", "p", "s", "u"]</span></span><br><span class="line">(<span class="string">"h"</span> <span class="string">"u"</span> <span class="string">"g"</span>, 10), (<span class="string">"p"</span> <span class="string">"u"</span> <span class="string">"g"</span>, 5), (<span class="string">"p"</span> <span class="string">"u"</span> <span class="string">"n"</span>, 12), (<span class="string">"b"</span> <span class="string">"u"</span> <span class="string">"n"</span>, 4), (<span class="string">"h"</span> <span class="string">"u"</span> <span class="string">"g"</span> <span class="string">"s"</span>, 5)</span><br></pre></td></tr></table></figure><p>3.统计相邻两个subword pair 词频,将top-k 高的pair 合并生成新的subword,添加进vocabulary,同时,如果当前的subword 只会同pair 一起出现,则同时将vocabulary 中对应subword 删除。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># count pair</span></span><br><span class="line">h + u = 10 + 5 = 15</span><br><span class="line">u + g = 10 + 5 + = 20</span><br><span class="line">...</span><br><span class="line"><span class="comment"># merge top k</span></span><br><span class="line"><span class="built_in">set</span> k = 1</span><br><span class="line">ug -> vocabulary</span><br><span class="line">base vocabulary: [<span class="string">"b"</span>, <span class="string">"g"</span>, <span class="string">"h"</span>, <span class="string">"n"</span>, <span class="string">"p"</span>, <span class="string">"s"</span>, <span class="string">"u"</span>, <span class="string">"ug"</span>]</span><br><span class="line"></span><br><span class="line">loop <span class="keyword">until</span> vocabulary match vocab_size</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2><span id="bytes-bpe">Bytes BPE</span><a href="#bytes-bpe" class="header-anchor"></a></h2><p>Bytes BPE 与BPE基本相同,唯一不同的是:BPE 中会存在UNK 的情况,为了解决unk 的问题,一个非常天才的想法是将所有text 先转为<code>bytes</code> ,这样就不会存在unk 的问题,尤其是在多语言中,这种方式可以大大缩减词表大小;此外即使不是目标语言训练的模型也可以拿来使用。通常词表大小包括256 个基本bytes + <end|of|text> + vocab-size,如gpt2 的词表为50257: 256 base bytes tokens + <end|of|text> + 50,000 merges.<br>此外,训练bytes bpe 时,通常我们还会选择先将文本进行normalize,这部分后面会进一步说明。</p><h2><span id="wordpiece">WordPiece</span><a href="#wordpiece" class="header-anchor"></a></h2><p>WordPiece 与BPE 也非常相似,区别主要在于merge 的策略:BPE 中选择频率最高的pair 进行合并,WordPiece 则选择使用语言模型来进行选择:<br>$$<br>L = logP(S) = \sum^Nlog(P_i)<br>$$<br>对于两个subword: $t_x$, $t_y$ ,合并后为 $t_z$ ,则合并前后的增益:<br>$$<br>Loss = logP(t_z) - (logP(t_x) + logP(t_y))<br>$$<br>通过计算合并增益是否增大来决定是否合并subword pair.</p><h2><span id="unigram">Unigram</span><a href="#unigram" class="header-anchor"></a></h2><p>Unigram 与 上述的方法都略有不同:Unigram 不再是通过合并base vocabulary 中的subword 来新增,他选择在初始化时初始化一个非常大的subword set,通过计算是否需要将一个subword 切分为多个base subword (remove 这个subword)来减小vocabulary size 直到达到vocab size。<br>这里有一个假设:句子之间是独立的,subword 与 subword 之间是独立的。对应的句子的语言模型似然值就是其subword 的概率的乘积。目标是保存vocab size 的同时语言模型似然值最大。<br>$$<br>x^* = argmax_{x \in U}P(\overrightarrow{x})<br>$$<br>整个求解过程是一个简单的EM 或者说一个迭代过程:<br>0.建立一个足够大的种子subword vocabulary,可以用字典树构建可以是所有字符的组合,也可以用bpe 构建;<br>1.(期望E)统计vocabulary 中每个subword 的频率,计算其对应概率值;<br>2.(最大化M)根据其概率,使用维特比算法返回其语言模型似然值最大化下的最佳分割方案;<br>3.计算最佳分割方案下每个新子词的loss,这里的loss 是指将当前subword 从vocabulary 中移除时,对应的语言模型似然值,即<br>$$<br>L = − \sum^Nlog (\sum_{x∈S(x_i)}p(x))<br>$$<br>4.丢弃掉loss 前x% 对应的subword;<br>5.重复2-4阶段,直到vocabulary 达到指定的vocab size。</p><h2><span id="sentencepiece">SentencePiece</span><a href="#sentencepiece" class="header-anchor"></a></h2><p>SentencePiece 其实并不是一个新的tokenizer 方法,他其实是一个实现了BPE/Unigram tokenizer 的一个集合,不过他有一些创新的地方。<br>上述方法中有一些问题:<br>1.都有字,子词或词的概念,然而在很多语言中并没有这样的概念;<br>2.都默认需要自己进行pre-tokenize,如英语则利用“空格”作为词的分割符,中文则一般选择jieba 进行pre-tokenize,这个过程不同的语言有自己的一套做法,不统一;<br>3.token 格式不统一。以英文为例,表示token 时会有 ##xx, xx/s 这种,表示subword 是否出现在词的首尾,然而中文中是没有这种概念的;<br>4.解码困难,如BPE解码时需要进行一些标准化,最常见的是去除标点符号。而我们解码后是 [new] [york]两个token,我们并不知道原来的词是 newyork/new york/new-york 中的哪一个.<br>SentencePiece 的做法:<br><a href="https://github.com/google/sentencepiece">SentencePiece treats the input text just as a sequence of Unicode characters. Whitespace is also handled as a normal symbol. To handle the whitespace as a basic token explicitly, SentencePiece first escapes the whitespace with a meta symbol “▁” (U+2581) as follows.</a></p><p>即首先将空格转换为一个标准的字符”▁”,然后将text 转换为unicode,其实这里的unicode 是NFKC-based normalization后的unicode,至于unicode 标准化,可以参考<a href="https://blog.csdn.net/weixin_43866211/article/details/98384017">unicode文本标准化</a> ,虽然通常我们使用NFKC 标准化,但sentencepiece 内部四种方法都实现了。<br>通过上述的空格转换加normalize,所有的语言经过转换后就有统一的格式了,这样多语言的问题就彻底的与token 切分解偶了,tokenizer 就有了一个完全端到端的解决方案。</p><h2><span id="train-from-scratch">train from scratch</span><a href="#train-from-scratch" class="header-anchor"></a></h2><p>训练一个tokenizer model 主要有两个仓库可以参考: <a href="https://github.com/huggingface/tokenizers">huggingface/tokenizers</a> 和 <a href="https://github.com/google/sentencepiece">google/sentencepiece</a>.<br>其中tokenizers 支持bpe/bytes bpe/unigram/wordpiece, sentencepiece 支持bpe/unigram.两者都支持四种标注化方法。<br>此外,tokenizers 不支持自定义的pre_tokenizer的保存,如中文时我们常用的jieba.lcut ;bytes bpe不支持big dataset 的训练,1T 内存训练100G 文本也会因内存不足被killed,(一个办法是缩小语料训练,因为没有oov 的问题,基本上小语料下训练也能用)。<br>在实际使用时,通常会结合huggingface/transformers 一起使用,这里也看了一下transformers/tokenizers 的实现。原始的GPT2 中的tokenizer 是没有做normalize 的,所以 transformers中的GPT2Tokenizer 也是没有做normalize 的,而通常我们自己训练的bytes bpe 是会加一个normalize 的过程,所以如果是通过huggingface/tokenizers 训练的tokenier,迁移到transformers 时需要注意normalize 是否实现。</p><h1><span id="tui-jian">推荐</span><a href="#tui-jian" class="header-anchor"></a></h1><p>当目标语言为中文时,推荐使用WordPiece + jieba 的方案;而是多语言场景时,推荐使用SentencePieceBPE/SentencePieceUnigram.<br>无论哪种合并/切分 subword 的策略,我们的初衷是:<br><code>尽量不切分常用词,而是将不常用词切分为常用的子词.</code><br>而中文中,有明确的字/词概念,却没有子词的概念(如英文中有”app”, “##le”, 中文却没有”苹” “##果”),而转bytes 后对子词更友好,此外,中文通常需要3个bytes(GBK)或者4个bytes(Chinese-Japanese character set),对于一个中文的字,很有可能需要大于1个token 来表示,反而会增加tokenize 后序列的长度,对模型的训练与使用不利;此外,中文中空格也没有切分词/句子 的语义,保留空格反而会由于各种空格的错误使用带来问题,最终的推荐方案就是jieba + Word Piece/SentencePieceUnigram。<br>而多语言场景下,推荐使用SentencePieceBPE,他提供一个端到端的方案,而不需要再根据不同语言进行不同的pre-tokenize/subword 格式,此外,SentencePiece 都是bytes 粒度的,这样既能大大缩减词表又能避免unk 的情况。</p><h1><span id="bu-chong-yue-du">补充阅读</span><a href="#bu-chong-yue-du" class="header-anchor"></a></h1><p><a href="https://huggingface.co/docs/transformers/tokenizer_summary">tokenizer summary-huggingface</a><br><a href="https://towardsdatascience.com/difference-between-nfd-nfc-nfkd-and-nfkc-explained-with-python-code-e2631f96ae6c">Difference Between NFD, NFC, NFKD, and NFKC Explained with Python Code-medium</a><br><a href="https://zhuanlan.zhihu.com/p/86965595">深入理解NLP Subword算法:BPE、WordPiece、ULM-知乎</a><br><a href="https://everdark.github.io/k9/notebooks/ml/natural_language_understanding/subword_units/subword_units.nb.html#121_expectation-maximization">natural_language_understanding/subword_units/subword_units-github.io</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>本人真实写照🐶</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="BPE" scheme="https://xv44586.github.io/tags/BPE/"/>
<category term="WordPiece" scheme="https://xv44586.github.io/tags/WordPiece/"/>
<category term="Unigram" scheme="https://xv44586.github.io/tags/Unigram/"/>
</entry>
<entry>
<title>训练加速篇(3)horovod之多机多卡</title>
<link href="https://xv44586.github.io/2022/07/06/horovod-multi-nodes/"/>
<id>https://xv44586.github.io/2022/07/06/horovod-multi-nodes/</id>
<published>2022-07-06T03:20:31.000Z</published>
<updated>2022-09-22T12:32:14.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#horovod-duo-ji-duo-qia">horovod 多机多卡</a><ul><li><a href="#huan-jing-da-jian">环境搭建</a></li><li><a href="#rdma">RDMA</a></li><li><a href="#qi-dong-xun-lian">启动训练</a></li></ul></li><li><a href="#xing-neng-dui-bi">性能对比</a></li><li><a href="#ref">ref</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="horovod-duo-ji-duo-qia">horovod 多机多卡</span><a href="#horovod-duo-ji-duo-qia" class="header-anchor"></a></h1><p><a href="https://xv44586.github.io/2022/05/25/horovod/">上一篇</a> 中介绍了如何在单机多卡下使用horovod 进行训练,本篇介绍如何在多机多卡下使用horovod 进行训练。<br>这篇中的测试GPU 为V100, 上篇A100 中遇到的环境问题在V100 中全都没有了,所以整个环境的搭建就异常简单了。</p><h2><span id="huan-jing-da-jian">环境搭建</span><a href="#huan-jing-da-jian" class="header-anchor"></a></h2><p>拉取最新的ngc 中的image,加载镜像并在container 中配置互相免密登陆,注意docker 启动时需要加入<code>privileged</code>权限,以便docker能够访问RDMA网口</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker run -itd --<span class="built_in">rm</span> --gpus all --shm-size=32g --<span class="built_in">ulimit</span> memlock=-1 --<span class="built_in">ulimit</span> stack=67108864 --net=host --privileged -v /data:/data --name horovod tensorflow:22.06-tf1-py3</span><br></pre></td></tr></table></figure><p>容器内互相免密</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 允许root 使用ssh</span></span><br><span class="line">sed -i <span class="string">'s/#PermitRootLogin prohibit-password/PermitRootLogin yes/'</span> /etc/ssh/sshd_config</span><br><span class="line"></span><br><span class="line"><span class="comment"># 修改容器内 ssh 默认端口为2222,防止与 host 所使用的22端口冲突</span></span><br><span class="line">sed -i <span class="string">'s/#Port 22/Port 2222/'</span> /etc/ssh/sshd_config</span><br><span class="line"></span><br><span class="line"><span class="comment"># 重启ssh 服务</span></span><br><span class="line">service ssh restart && netstat -tulpn</span><br><span class="line"></span><br><span class="line"><span class="comment"># 设置 root 密码</span></span><br><span class="line">passwd root</span><br><span class="line"></span><br><span class="line"><span class="comment"># SSH Key</span></span><br><span class="line">ssh-keygen</span><br><span class="line"></span><br><span class="line"><span class="comment"># 创建 ~/.ssh/config,并添加以下内容后,保存并退出,完成 host alias 配置。</span></span><br><span class="line"><span class="comment"># !注意:</span></span><br><span class="line"><span class="comment"># 如果是CVM机型,则ip是两台机器`ifconfig eth0`显示的ip</span></span><br><span class="line"><span class="comment"># 如果是黑石RDMA机型,则ip是两台机器`ifconfig bond0`显示的ip</span></span><br><span class="line">Host gpu1</span><br><span class="line"> hostname 172.0.0.1</span><br><span class="line"> port 2222</span><br><span class="line">Host gpu2</span><br><span class="line"> hostname 172.0.0.2</span><br><span class="line"> port 2222</span><br></pre></td></tr></table></figure><h2><span id="rdma">RDMA</span><a href="#rdma" class="header-anchor"></a></h2><p>上面提到了RDMA,这里简单介绍一下。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">在数据中心领域,远程直接内存访问(英语:remote direct memory access,RDMA)是一种绕过远程主机操作系统内核访问其内存中数据的技术,由于不经过操作系统,不仅节省了大量CPU资源,同样也提高了系统吞吐量、降低了系统的网络通信延迟,尤其适合在大规模并行计算机集群中有广泛应用。</span><br></pre></td></tr></table></figure><p>这段话引自wiki,通过使用RDMA技术,可以进一步提高分布式系统的整体性能。而我们使用的NCCL 进行通信,NCCL 是支持RDMA的。此外,我们使用的ngc 中是包含了RDMA 驱动的,如果image 内未安装,参考<a href="https://cloud.tencent.com/document/product/1573/74101">容器安装用户态 RDMA 驱动</a></p><h2><span id="qi-dong-xun-lian">启动训练</span><a href="#qi-dong-xun-lian" class="header-anchor"></a></h2><p>启动训练时,需要根据节点信息和通信方案调整参数,如在支持RDMA 下的启动命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mpirun -np 16 -H gpu1:8,gpu2:8 --allow-run-as-root -bind-to none -map-by slot -x NCCL_DEBUG=INFO -x NCCL_IB_DISABLE=0 -x NCCL_SOCKET_IFNAME=bond0 -x NCCL_IB_GID_INDEX=3 -x NCCL_NET_GDR_LEVEL=0 -x LD_LIBRARY_PATH -x PATH -mca pml ob1 -mca btl_tcp_if_include bond0 -mca btl ^openib python3 train.py</span><br></pre></td></tr></table></figure><p>其中 :<br><code>-H</code> 后面指定节点及显卡数;<br><code>-np</code> 需要根据 <code>-H</code> 调整为其总计worker 数量;<br><code>btl_tcp_if_include</code> RDMA 下为bond0,普通网络则为eth0;<br><code>NCCL_SOCKET_IFNAME</code> 为网络接口,RDMA 下为bond0,普通网络则需切换为 eth0<br><code>NCCL_NET_GDR_LEVEL</code> 为GDR 相关,GDR的概念是运行GPU 与RDMA 直接通信,开启后进一步降低通信延迟。不过GDR需要配合RDMA 一起使用;</p><p>NCCL 相关变量含义可以参考<a href="https://docs.nvidia.com/deeplearning/nccl/user-guide/docs/env.html">Environment Variables</a></p><h1><span id="xing-neng-dui-bi">性能对比</span><a href="#xing-neng-dui-bi" class="header-anchor"></a></h1><p>在bert-base 规模的模型上进行测试,其结果如下:</p><table><thead><tr><th>GPU</th><th align="center">batch size per GPU</th><th align="center">net</th><th align="center">node</th><th align="center">speed</th></tr></thead><tbody><tr><td>A100-40g</td><td align="center">16</td><td align="center">vpc</td><td align="center">single</td><td align="center">430 me/step</td></tr><tr><td>V100</td><td align="center">8</td><td align="center">vpc</td><td align="center">single</td><td align="center">485 ms/step</td></tr><tr><td>V100</td><td align="center">8</td><td align="center">vpc</td><td align="center">multi</td><td align="center">617 ms/step</td></tr><tr><td>V100</td><td align="center">8</td><td align="center">rdma</td><td align="center">single</td><td align="center">485 ms/step</td></tr><tr><td>V100</td><td align="center">8</td><td align="center">rdma</td><td align="center">multi</td><td align="center">510 ms/step</td></tr></tbody></table><p>可以看到,通过RDMA 进一步降低网络延迟后,多机多卡的加速效果接近线性加速了。如果开启GDR 网络延迟能进一步降低,加速效果应该会更解决线性加速。</p><h1><span id="ref">ref</span><a href="#ref" class="header-anchor"></a></h1><p><a href="https://zhuanlan.zhihu.com/p/55142557">RDMA技术详解(一):RDMA概述</a></p><p><a href="https://zh.m.wikipedia.org/zh-hans/%E8%BF%9C%E7%A8%8B%E7%9B%B4%E6%8E%A5%E5%86%85%E5%AD%98%E8%AE%BF%E9%97%AE">远程直接内存访问</a></p><p><a href="https://cloud.tencent.com/document/product/1573/74100">配置容器 SSH 免密访问</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>可爱小猫</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="speed-up" scheme="https://xv44586.github.io/tags/speed-up/"/>
<category term="horovod" scheme="https://xv44586.github.io/tags/horovod/"/>
</entry>
<entry>
<title>nohup踩坑记</title>
<link href="https://xv44586.github.io/2022/07/05/nohup-debug/"/>
<id>https://xv44586.github.io/2022/07/05/nohup-debug/</id>
<published>2022-07-05T02:07:22.000Z</published>
<updated>2022-07-07T03:38:46.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#bei-jing">背景</a></li><li><a href="#nohup">nohup</a></li><li><a href="#ti-dai">替代</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="bei-jing">背景</span><a href="#bei-jing" class="header-anchor"></a></h1><p>最近在测试<a href="https://xv44586.github.io/2022/05/25/horovod/">horovod</a>下的单机多卡/多机多卡的性能,结果遇到一个神奇的现象:训练总是会在一段时间后意外停止,停止后从系统到程序,没有任何异常,没有任何错误日志。而训练停止这个现象总是能复现,但是复现时又不完全一样,即同样的训练脚本,其每次训练意外停止时完成的更新次数还不一样。<br>为了解决这个神奇的bug,我们尝试从物理机、系统资源、驱动、软件环境、训练脚本、训练数据等多个环节进行检查,均未发现任何问题,最终无意间发现使用xshell (之前使用的WSL2)后训练停止的现象不在发生。最终发现竟然是因为对nohup 的坑,特此记录一下。</p><h1><span id="nohup">nohup</span><a href="#nohup" class="header-anchor"></a></h1><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">nohup is a POSIX command which means "no hang up". Its purpose is to execute a command such that it ignores the HUP (hangup) signal and therefore does not stop when the user logs out.</span><br></pre></td></tr></table></figure><p>上面这段是引自<a href="https://en.wikipedia.org/wiki/Nohup#Overcoming_hanging">wikipedia</a>,即nohup 的作用是忽略HUP 信号,从而让用户log out 时,程序依然保持执行而不中断。</p><p>然而有些场景下,nohup 依然会失效,即使用了nohup 后程序依然可能被中断,如<a href="https://unix.stackexchange.com/questions/420594/why-process-killed-with-nohup">hy-process-killed-with-nohup</a>提到的两个场景,此外,通常我们使用nohup 的场景时ssh 下,而ssh 为了避免丢失数据,会拒绝log out。 参考wiki中提到的:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Note that nohupping backgrounded jobs is typically used to avoid terminating them when logging off from a remote SSH session. A different issue that often arises in this situation is that ssh is refusing to log off ("hangs"), since it refuses to lose any data from/to the background job(s).[6][7] This problem can also be overcome by redirecting all three I/O streams:</span><br><span class="line"></span><br><span class="line">$ nohup ./myprogram > foo.out 2> foo.err < /dev/null &</span><br></pre></td></tr></table></figure><p>我的训练脚本使用时,对stdout/stderr 进行了重定向,未对stdin 进行重定向,即:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">nohup</span> ./myprogram > foo.out 2>&1 &</span><br></pre></td></tr></table></figure><p>最终导致训练在进行一段时间后意外中断而没有任何异常信息。而这个像幽灵一样无声无息的“小问题”让我们花了接近三周时间debug,最终在猜测中定位到nohup 。</p><h1><span id="ti-dai">替代</span><a href="#ti-dai" class="header-anchor"></a></h1><p>由上面可以看出,nohup 并不是一个”好” 的后台执行解决方案,而对应的替代品,推荐使用<a href="https://github.com/tmux/tmux/wiki">tmux</a></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">tmux is a terminal multiplexer. It lets you switch easily between several programs in one terminal, detach them (they keep running in the background) and reattach them to a different terminal.</span><br></pre></td></tr></table></figure><p>tmux稳定可靠,利用tmux,将session 与 terminal 进行detach,从而让程序在后台执行。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>深度debug</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="Programming" scheme="https://xv44586.github.io/categories/Programming/"/>
<category term="horovod" scheme="https://xv44586.github.io/tags/horovod/"/>
<category term="nohup" scheme="https://xv44586.github.io/tags/nohup/"/>
<category term="debug" scheme="https://xv44586.github.io/tags/debug/"/>
</entry>
<entry>
<title>训练加速篇(2)-horovod</title>
<link href="https://xv44586.github.io/2022/05/25/horovod/"/>
<id>https://xv44586.github.io/2022/05/25/horovod/</id>
<published>2022-05-25T02:29:28.000Z</published>
<updated>2022-06-02T08:53:13.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#horovod">horovod</a></li><li><a href="#da-huan-jing">搭环境</a><ul><li><a href="#docker">docker</a></li></ul></li><li><a href="#local">local</a><ul><li><a href="#nccl">NCCL</a><ul><li><a href="#yan-zheng-yi-xia">验证一下</a></li></ul></li><li><a href="#mpirun">mpirun</a></li><li><a href="#nvidia-tensorflow">nvidia-tensorflow</a></li><li><a href="#horovod-1">horovod</a></li></ul></li><li><a href="#huan-jing-zhong-cai-guo-de-keng">环境中踩过的坑</a><ul><li><a href="#nccl">nccl</a></li><li><a href="#horovod-2">horovod</a></li></ul></li><li><a href="#dai-ma-ceng-mian-xiu-gai">代码层面修改</a></li><li><a href="#run-code">run code</a></li><li><a href="#jia-su-xiao-guo">加速效果</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="horovod">horovod</span><a href="#horovod" class="header-anchor"></a></h1><p><a href="https://github.com/horovod/horovod">horovod</a>是Uber 团队开发的分布式训练框架,他可以满足让你尽量少的修改代码即可将在单卡训练的脚本横行扩展为多卡并行训练,同时又兼顾训练的加速。目前支持tensorflow/keras/pytorch/mxnet.底层通信主要依赖<a href="https://github.com/NVIDIA/nccl">NCCL</a>/<a href="https://github.com/facebookincubator/gloo">Gloo</a>(测试后NCCL是最快的),支持MPI(CPU 训练更快)。由于其训练加速效果比tensorflow 原生的distributedStrategy 快很多,所以在分布式训练时,推荐使用。<br>下面主要针对tensorflow1.x 下做分布式训练进行说明。</p><h1><span id="da-huan-jing">搭环境</span><a href="#da-huan-jing" class="header-anchor"></a></h1><p>环境主要依赖tensorflow1.x/horovod/nccl/mpi ,这里有两种方式搭环境:local/docker,下面分别介绍这两种环境的搭建及踩过的坑。</p><h2><span id="docker">docker</span><a href="#docker" class="header-anchor"></a></h2><p>由于谷歌官方的tensorflow1.x 不提供对A100/3090 及更新版本的显卡的支持,这里我们推荐使用nvidia 官方维护的<a href="https://github.com/NVIDIA/tensorflow">nvidia-tensorflow</a>,其提供了对A100/3090 等显卡的支持。支持pip/conda安装,同时也提供了对应的docker 镜像。<br>在使用镜像时,首先在<a href="https://catalog.ngc.nvidia.com/orgs/nvidia/containers/tensorflow/tags">ngc</a> 中寻找最新镜像并pull 下来。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">docker pull nvcr.io/nvidia/tensorflow:22.04-tf1-py3</span><br></pre></td></tr></table></figure><p>在启动时,建议设置share memory 为单张显卡显存差不多大小,否则有可能由于默认share memory size 太小(64M)导致nccl 通信时资源不足被kill。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">docker run -itd --gpus all --net=host --privileged -v /data:/data --name horovod --shm-size=32g --<span class="built_in">ulimit</span> memlock=-1 --<span class="built_in">ulimit</span> stack=67108864 nvcr.io/nvidia/tensorflow:22.04-tf1-py3</span><br><span class="line"></span><br></pre></td></tr></table></figure><h1><span id="local">local</span><a href="#local" class="header-anchor"></a></h1><p>在本地搭环境,主要需要安装nvidia-tensorflow/horovod/nccl/mpi,此外,由于nvidia-tensorflow 只支持ubuntu20.04,对应的OS 也要调整。</p><h2><span id="nccl">NCCL</span><a href="#nccl" class="header-anchor"></a></h2><p>安装NCCL,注意版本,为了兼顾A100,推荐使用v2.8.3-1 这个版本。<br>ref:<a href="https://github.com/NVIDIA/nccl">nccl</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 编译nccl</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/NVIDIA/nccl.git</span><br><span class="line"><span class="built_in">cd</span> nccl && git checkout v2.8.3-1</span><br><span class="line">make -j src.build</span><br><span class="line"></span><br><span class="line"><span class="comment"># 如果第一次安装,需要安装一下依赖</span></span><br><span class="line"><span class="comment"># Install tools to create debian packages</span></span><br><span class="line"><span class="built_in">sudo</span> apt install build-essential devscripts debhelper fakeroot</span><br><span class="line"><span class="comment"># Build NCCL deb package</span></span><br><span class="line">make pkg.debian.build</span><br><span class="line"><span class="built_in">ls</span> build/pkg/deb/</span><br><span class="line"></span><br><span class="line"><span class="comment"># install</span></span><br><span class="line"><span class="built_in">sudo</span> make install</span><br></pre></td></tr></table></figure><h3><span id="yan-zheng-yi-xia">验证一下</span><a href="#yan-zheng-yi-xia" class="header-anchor"></a></h3><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 确认horovod链接的nccl版本路径正确</span></span><br><span class="line">ldd /usr/local/lib/python3.8/dist-packages/horovod/tensorflow/mpi_lib.cpython-38-x86_64-linux-gnu.so</span><br><span class="line"></span><br></pre></td></tr></table></figure><h2><span id="mpirun">mpirun</span><a href="#mpirun" class="header-anchor"></a></h2><p>这里我们安装<a href="https://www.open-mpi.org/">open-mpi</a></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 安装依赖</span></span><br><span class="line">apt-get install libnuma-dev</span><br><span class="line"></span><br><span class="line"><span class="comment"># mpirun</span></span><br><span class="line">wget https://download.open-mpi.org/release/open-mpi/v4.0/openmpi-4.0.5.tar.gz && tar xvf openmpi-4.0.5.tar.gz</span><br><span class="line"><span class="built_in">cd</span> openmpi-4.0.5/ && ./configure --prefix=/usr/local/openmpi</span><br><span class="line">make -j$(<span class="built_in">nproc</span>) && <span class="built_in">sudo</span> make install</span><br><span class="line"><span class="built_in">echo</span> <span class="string">'export PATH=/usr/local/openmpi/bin:$PATH'</span> >> ~/.bashrc</span><br><span class="line"><span class="built_in">echo</span> <span class="string">'export LD_LIBRARY_PATH=/usr/local/openmpi/lib:$LD_LIBRARY_PATH'</span> >> ~/.bashrc</span><br><span class="line"><span class="built_in">source</span> ~/.bashrc</span><br><span class="line">mpirun --version</span><br></pre></td></tr></table></figure><h2><span id="nvidia-tensorflow">nvidia-tensorflow</span><a href="#nvidia-tensorflow" class="header-anchor"></a></h2><p>这里直接用pip 安装或conda 安装都行:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">pip install nvidia-pyindex</span><br><span class="line">pip install nvidia-tensorflow</span><br></pre></td></tr></table></figure><h2><span id="horovod">horovod</span><a href="#horovod" class="header-anchor"></a></h2><p>horovod 在安装时,需要安装支持NCCL ,同时建议安装最新版本</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">// 安装horovod</span><br><span class="line">pip show horovod && pip uninstall horovod</span><br><span class="line">HOROVOD_WITH_MPI=1 HOROVOD_WITHOUT_GLOO=1 HOROVOD_GPU_OPERATIONS=NCCL HOROVOD_WITH_TENSORFLOW=1 HOROVOD_NCCL_LINK=SHARED pip3 install horovod==0.24.3 --no-cache-dir</span><br></pre></td></tr></table></figure><p>验证一下horovod 使用了NCCL</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">horovodrun --check-build</span><br></pre></td></tr></table></figure><p>对应的NCCL 前面勾选则支持NCCL<br><img src="/2022/05/25/horovod/horovod-nccl.png" alt="horovod-nccl"></p><h1><span id="huan-jing-zhong-cai-guo-de-keng">环境中踩过的坑</span><a href="#huan-jing-zhong-cai-guo-de-keng" class="header-anchor"></a></h1><p>当前在A100 的机器上进行安装测试,其他型号显卡不一定也有同样的问题。</p><h2><span id="nccl">nccl</span><a href="#nccl" class="header-anchor"></a></h2><p>针对A100,ngc 的镜像中依然存在一个坑,其安装的nccl 版本太高,导致使用中会报错,对应的修复方式时对nccl 进行降级。</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment"># 下载编译nccl</span></span><br><span class="line">git <span class="built_in">clone</span> https://github.com/NVIDIA/nccl.git</span><br><span class="line"><span class="built_in">cd</span> nccl && git checkout v2.8.3-1</span><br><span class="line">make -j src.build</span><br><span class="line"></span><br><span class="line"><span class="comment"># install</span></span><br><span class="line"><span class="built_in">sudo</span> make install</span><br></pre></td></tr></table></figure><h2><span id="horovod">horovod</span><a href="#horovod" class="header-anchor"></a></h2><p>nvcr.io/nvidia/tensorflow:22.04-tf1-py3 中的horovod 不是最新版本的,在使用梯度累计时,老版本里<a href="https://github.com/horovod/horovod/issues/2806">有问题</a>,对应的修复在新版本的horovd 中。所以,不管是否使用梯度累计,都建议升级一下horovod。PS: 升级时参考上面安装horovod 的办法 ,注意对NCCL 的支持。</p><h1><span id="dai-ma-ceng-mian-xiu-gai">代码层面修改</span><a href="#dai-ma-ceng-mian-xiu-gai" class="header-anchor"></a></h1><p>代码层面需要引入horvod,然后将optimizer 用horovod 进行wrap,此外,evaluate 相关的内容,尽量使用一个节点即可。<br>简单的demo:</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line"><span class="comment"># import horovod</span></span><br><span class="line"><span class="keyword">import</span> horovod.tensorflow.keras <span class="keyword">as</span> hvd</span><br><span class="line"></span><br><span class="line"><span class="comment"># Horovod: initialize Horovod.</span></span><br><span class="line">hvd.init()</span><br><span class="line"><span class="comment"># Horovod: pin GPU to be used to process local rank (one GPU per process)</span></span><br><span class="line">config = tf.ConfigProto()</span><br><span class="line">config.gpu_options.allow_growth = <span class="literal">True</span></span><br><span class="line">config.gpu_options.visible_device_list = <span class="built_in">str</span>(hvd.local_rank())</span><br><span class="line">K.set_session(tf.Session(config=config))</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"><span class="comment"># wrap optimizer</span></span><br><span class="line">optimizer = hvd.DistributedOptimizer(optimizer,</span><br><span class="line"> backward_passes_per_step=grad_accum_steps,</span><br><span class="line"> average_aggregated_gradients=<span class="literal">True</span>,</span><br><span class="line"> sparse_as_dense=<span class="literal">True</span>)</span><br><span class="line"> </span><br><span class="line">...</span><br><span class="line"><span class="comment"># callback</span></span><br><span class="line">callbacks = [</span><br><span class="line"> <span class="comment"># Horovod: broadcast initial variable states from rank 0 to all other processes.</span></span><br><span class="line"> <span class="comment"># This is necessary to ensure consistent initialization of all workers when</span></span><br><span class="line"> <span class="comment"># training is started with random weights or restored from a checkpoint.</span></span><br><span class="line"> hvd.callbacks.BroadcastGlobalVariablesCallback(<span class="number">0</span>),</span><br><span class="line"> </span><br><span class="line"> ]</span><br><span class="line"><span class="comment"># evaluate only on worker 0 to prevent other workers from corrupting them. </span></span><br><span class="line"><span class="keyword">if</span> hvd.rank() == <span class="number">0</span>:</span><br><span class="line"> callbacks.append(evaluator)</span><br><span class="line"></span><br><span class="line">...</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>部分限制:</p><ol><li>由于horovod 是让你在单卡的代码能够平滑的迁移到多卡上,所以这里的batch size 设置的是针对单卡的,但是整个优化过程是在多卡的结果上进行的,所以需要你手动调整自己的optimizer 以适应最终的batch size(batch_size_node * node_num)</li><li>需要使用梯度累计以达到更大batch size 的效果时,需要使用horovod 提供的方式,即:</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">optimizer = hvd.DistributedOptimizer(optimizer,</span><br><span class="line"> backward_passes_per_step=grad_accum_steps, <span class="comment"># 累计步数</span></span><br><span class="line"> average_aggregated_gradients=<span class="literal">True</span>,</span><br><span class="line"> sparse_as_dense=<span class="literal">True</span> <span class="comment"># 累计步数大于1必须设为True</span></span><br><span class="line"> )</span><br></pre></td></tr></table></figure><p>而直接使用bert4keras 中的实现方式会报错,具体原因未知。<br>3. callback 中只支持原生的keras 的操作,其他自定义的操作都会报错,如bert4keras 中的 <code>Transformer.save_weights_as_checkpoint()</code> 就不支持。</p><h1><span id="run-code">run code</span><a href="#run-code" class="header-anchor"></a></h1><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">mpirun -np 8 --allow-run-as-root -bind-to none -map-by slot -x NCCL_DEBUG=INFO -mca btl_tcp_if_include eth0 -x LD_LIBRARY_PATH -x PATH python train_hvd.py</span><br></pre></td></tr></table></figure><p>其中 $-np$ 后面的参数是显卡数量,根据使用情况自行调整。</p><h1><span id="jia-su-xiao-guo">加速效果</span><a href="#jia-su-xiao-guo" class="header-anchor"></a></h1><p>对不同的OS 不同的环境进行了训练速度对比,效果如下:</p><table><thead><tr><th>local system</th><th align="center">run where</th><th align="center">distributed</th><th align="center">speed</th></tr></thead><tbody><tr><td>centos</td><td align="center">docker</td><td align="center">tf-mirroredStrategy</td><td align="center">520 ms/step</td></tr><tr><td>centos</td><td align="center">docker</td><td align="center">horovod-without-nccl</td><td align="center">440 ms/step</td></tr><tr><td>centos</td><td align="center">docker</td><td align="center">horovod-nccl</td><td align="center">285 ms/step</td></tr><tr><td>ubuntu20.04</td><td align="center">local</td><td align="center">tf-mirroredStrategy</td><td align="center">500 ms/step</td></tr><tr><td>ubuntu20.04</td><td align="center">local</td><td align="center">horovod nccl</td><td align="center">265 ms/step</td></tr><tr><td>ubuntu20.04</td><td align="center">docker</td><td align="center">tf-mirroredStrategy</td><td align="center">500 ms/step</td></tr><tr><td><code>ubuntu20.04</code></td><td align="center"><code>docker</code></td><td align="center"><code>horovod-nccl</code></td><td align="center"><code>240 ms/step</code></td></tr></tbody></table><p>可以看到,相对于tf 原生的单机多卡的MirroredStrategy,能提速一倍之多,加速相当惊人了。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>可爱猫猫</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="speed-up" scheme="https://xv44586.github.io/tags/speed-up/"/>
<category term="horovod" scheme="https://xv44586.github.io/tags/horovod/"/>
</entry>
<entry>
<title>faster-decoder之 decoder解码加速</title>
<link href="https://xv44586.github.io/2022/05/23/faster-decoder/"/>
<id>https://xv44586.github.io/2022/05/23/faster-decoder/</id>
<published>2022-05-23T02:25:43.000Z</published>
<updated>2022-05-23T10:35:28.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#1-bei-jing">1 背景</a></li><li><a href="#2-attention-cache">2 attention cache</a><ul><li><a href="#2-1-yuan-li">2.1 原理</a></li><li><a href="#encoder-decoder-cross-attention">encoder-decoder cross-attention</a></li><li><a href="#self-attention">self-attention</a></li><li><a href="#2-2-shi-xian">2.2 实现</a><ul><li><a href="#1-attention-ceng-xiu-gai">1. attention 层修改</a></li><li><a href="#2-attention-mask-de-jiu-zheng">2. attention mask 的“纠正”</a></li><li><a href="#3-position-bias-de-jiu-zheng">3. position bias 的“纠正”</a></li><li><a href="#4-jie-ma-shi-xian">4. 解码实现</a></li></ul></li></ul></li><li><a href="#3-onnx">3 onnx</a></li><li><a href="#4-demo">4 demo</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="1-bei-jing">1 背景</span><a href="#1-bei-jing" class="header-anchor"></a></h1><p>Transformer 模型舍弃了 step by step 的时序循环,依靠 self-attention 的并行结构获得了远超同量级 LSTM 网络的训练速度。即使做auto-regresisve 任务时,通过attention-mask 机制依然可以像encoder 一样并行计算。然而在解码时,却任然需要step by step 的进行,即需要知道上一个time step 的结果后才能进行下一个time step 的解码。此外,通常我们的解码策略是在获得模型结果后在内存中计算的,需要不停的将结果从GPU load 进 CPU 然后计算,这就进一步的拖慢了解码速度。而通常我们在部署时,首选的tf-serving 需要将结果通过网络传输,这将进一步的拖慢解码速度。而针对解码慢的问题,主要的加速方案有:</p><ol><li>将解码策略放在GPU 上计算,这样将避免结果在GPU/CPU 之间转换与等待;</li><li>attention cache,根据attention 层的特点,对attention 中的 $K$ / $V$ 进行cache,避免对之前的time step 进行重复计算,将attention 层的计算由 $O(n^2)$ 降低到 $O(n)$。</li><li>transformer 计算最耗时的是attention 层中的softmax,尝试使用一些线性函数进行近似替换</li></ol><h1><span id="2-attention-cache">2 attention cache</span><a href="#2-attention-cache" class="header-anchor"></a></h1><p>三种方案中,GPU 上进行解码需要一些底层技术进行开发,暂时没能力,而替换softmax 方案则会或多或少的损失一些精度,本文都不做进一步讨论。本文聚焦在attention cache 方案上,加速的同时又“不会”损失精度。</p><h2><span id="2-1-yuan-li">2.1 原理</span><a href="#2-1-yuan-li" class="header-anchor"></a></h2><p>attention 的计算公式:</p><p>$$<br>A = Softmax(QK^{T})* V<br>$$</p><p>在解码时,我们是step by step 进行的,所以,我们将时刻 t 的attention 写出来:</p><p>$$<br>A = Softmax(Q_{t}K^{T})* V<br>$$</p><p>即:对于时刻t 来说,attention 只需要当前的 $Q_{t}$ 时刻信息,$K$ / $V$ 的所有时刻信息进行计算。而 $Q_{t}$ 的计算只需要 $Token_{t-t}$ 即可,如何加速计算的关键就剩下如何更加高效的计算 $K$ / $V$.</p><h2><span id="encoder-decoder-cross-attention">encoder-decoder cross-attention</span><a href="#encoder-decoder-cross-attention" class="header-anchor"></a></h2><p>对于encoder-decoder cross-attention 来说,对应的 $K$ / $V$ 都来自encoder 的outputs,所以直接将其整个进行cache 即可,而无需每步都重新计算。</p><h2><span id="self-attention">self-attention</span><a href="#self-attention" class="header-anchor"></a></h2><p>而当attention 是self-attention 时,对于时刻 $t$ 来说,此时的 $K$ / $V$ 为 $K$ / $V$ 的前 $t$ 时刻信息,即 $K_{\leq t}$ / $V_{\leq t}$ .此时的 attention 计算为:</p><p>$$<br>A = Softmax(Q_{t}K_{\leq t}^{T})* V_{\leq t}<br>$$</p><p>而 $K_{t}$/$V_{t}$ 的计算只与 $Token_t$ 有关,与其他时刻的 $Token$ 无关,且不论是时刻 $t$ 还是时刻 $t+1$,对应的 $K_{t-1}$ / $V_{t-1}$ 的计算结果都是一样的。因此,每个时刻都对 $K_{\leq t}$ / $V_{\leq t}$ 全部计算是低效且浪费的。</p><p>由于 $K_t$ / $V_t$ 有只需 $Token_t$ 计算且不同时刻结果“一致”的特点,我们将每个时刻的 $K_t$ / $V_t$ 进行cache,在进行attention 计算时使用cache 中的 $K_{\leq t}$ / $V_{\leq t}$<br>即可。<br>此外,由于使用了attention cache 后,每次解码输入只需要 $Token_t$ 而非 $Token_{\leq t}$ ,这样将其他层的计算量也会随之降低。<br>PS:由于decoder 中为了实现auto-regressive 而采用了下三角的attention mask,因此,不同时刻的attention mask 是不同的,这会导致不同时刻的 $K_t$ / $V_t$ 的结果略有不同(约e-10),但是这并不影响最终端到端的结果。 </p><h2><span id="2-2-shi-xian">2.2 实现</span><a href="#2-2-shi-xian" class="header-anchor"></a></h2><p>attention 层在实现时,除了进行attention 计算的同时,还会包含attention mask 和 position bias 两种信息,其中,attention mask 来实现auto regressive,即当前位置的attention 只能包含当前位置及之前的信息;position bias 则包括各种position 信息的实现,所以在使用attention cache 后,还需要对这两种信息进行“纠正”。</p><h3><span id="1-attention-ceng-xiu-gai">1. attention 层修改</span><a href="#1-attention-ceng-xiu-gai" class="header-anchor"></a></h3><p>具体实现时,对于encoder-decoder cross-attention, 我们之间将encoder outputs 计算一次后进行cache,每次进行解码时作为inputs 送人decoder;</p><p>对于self-attention ,我们在得到 $Q_{t}$/$K_{t}$/$V_{t}$ 后,将 $K_t$/$V_t$ 与之前的 $K_{\leq t-1}$/ $V_{\leq t-1}$ cache 进行拼接,构造出完整的$K_{\leq t}$ / $V_{\leq t}$, 然后将$Q_t$ / $K_{\leq t}$ / $V_{\leq t}$ 进入self-attention 层进行计算。</p><h3><span id="2-attention-mask-de-jiu-zheng">2. attention mask 的“纠正”</span><a href="#2-attention-mask-de-jiu-zheng" class="header-anchor"></a></h3><p>由于attention mask 的作用是防止当前位置看到其后位置的信息,而在使用cache 后,当前位置即最后时刻的位置,所以此时的attention mask 已没有存在的必要,直接取消即可;PS: 由于这里直接取消了attention mask,而attention mask 的实现通常是通过加上一个 负无穷(-e12) 来实现的,所以加了cache 后的outputs 与没加之前会有一定的差异,大概在e-10 量级。</p><h3><span id="3-position-bias-de-jiu-zheng">3. position bias 的“纠正”</span><a href="#3-position-bias-de-jiu-zheng" class="header-anchor"></a></h3><p>由于position bias 通常是通过inputs 的长度进行计算的,而加了attention cache 后,每次的inputs 的长度变为1 了(当前时刻的$Token_t$),所以此时的position bias 恒等于长度为1 的序列。为了还原他原始的position bias,我们使用拼接了cache 后的$K_{\leq t}$ 进行计算完整序列的position bias, 然后取出当前query 在完整序列中位置对应的position bias 即可。</p><h3><span id="4-jie-ma-shi-xian">4. 解码实现</span><a href="#4-jie-ma-shi-xian" class="header-anchor"></a></h3><p>此外,在解码函数上,也需要进行相应的修改,以获得当前时刻的$K_t$/$V_t$ , 然后与之前时刻的所有 $K_{\leq t-1}$ / $V_{\leq t-1}$ cache 进行拼接,为下一个时刻计算做准备。</p><h1><span id="3-onnx">3 onnx</span><a href="#3-onnx" class="header-anchor"></a></h1><p>由于tensorflow 会对当前显卡的显存全部占用,所以一个显卡只能启动一个tensorflow 进程,这样就导致当一个模型的显存不需要占用所有显存即可解码时,使用tensorflow 会浪费一部分显存,这里我们将其转为onnx ,这样只需要占用模型需要的显存即可,避免显存浪费。即一个显卡可以起多个解码进程。</p><h1><span id="4-demo">4 demo</span><a href="#4-demo" class="header-anchor"></a></h1><p>在bert4keras 的基础上,对 T5/Roformer 进行了实现,具体代码参考:<a href="https://github.com/xv44586/faster-decoder">faster-decoder</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>网上流传的某个可达鸭形象😄</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="faster decoder" scheme="https://xv44586.github.io/tags/faster-decoder/"/>
<category term="T5" scheme="https://xv44586.github.io/tags/T5/"/>
<category term="simbert" scheme="https://xv44586.github.io/tags/simbert/"/>
</entry>
<entry>
<title>speed-up</title>
<link href="https://xv44586.github.io/2021/08/14/speed-up/"/>
<id>https://xv44586.github.io/2021/08/14/speed-up/</id>
<published>2021-08-14T04:22:06.000Z</published>
<updated>2026-01-01T21:57:47.310Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#xun-lian-jia-su">训练加速</a></li><li><a href="#hun-he-jing-du">混合精度</a><ul><li><a href="#dui-ying-de-ruan-ying-jian-yao-qiu">对应的软硬件要求</a></li><li><a href="#ru-he-kai-qi">如何开启</a></li><li><a href="#yi-xie-ce-shi-jie-guo">一些测试结果</a></li></ul></li><li><a href="#tui-li-jia-su">推理加速</a><ul><li><a href="#ce-shi">测试</a></li></ul></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><p>之前文章中介绍了通过模型压缩来加速其推理速度的主要思路,并就知识蒸馏总结了三篇内容,分别是:<a href="https://xv44586.github.io/2020/08/09/bert-of-theseus/">模型替换之bert-of-theseus</a> 、<a href="https://xv44586.github.io/2020/08/31/bert-01/">知识迁移</a> 和 <a href="https://xv44586.github.io/2020/09/25/fastbert/">看样本下菜的FastBERT</a>。本文总结两种与模型无关的加速方案。</p><h1><span id="xun-lian-jia-su">训练加速</span><a href="#xun-lian-jia-su" class="header-anchor"></a></h1><p>训练加速的主要方法包括pipeline 和混合精度,其中pipeline是指通过构造一个input pipeline将数据IO与GPU计算分开,从而避免GPU因IO而空闲,这个问题不是本篇重点,想进一步了解的可以参考<a href="https://zhuanlan.zhihu.com/p/27238630?utm_source=wechat_session&utm_medium=social&utm_oi=43831500210176">tensorflow数据读取机制-何之源</a>和<a href="https://www.tensorflow.org/versions/r1.15/api_docs/python/tf/data/Dataset">Doc-tf-data-dataset</a></p><h1><span id="hun-he-jing-du">混合精度</span><a href="#hun-he-jing-du" class="header-anchor"></a></h1><p>混合精度是指训练时在模型中同时使用 16 位和32位浮点类型,从而加快运行速度,减少内存使用的一种训练方法。通过让模型的某些部分保持使用 32 位类型以保持数值稳定性,可以缩短模型的单步用时,而在评估指标(如准确率)方面仍可以获得同等的训练效果。(<a href="https://tensorflow.google.cn/guide/mixed_precision?hl=zh-cn">tf doc</a>)简单说就是开启混合精度,既能更省显存又能加速训练,保证性能的前提下,偶尔还能提高性能,真是又省又快又好,了解更多混合精度相关知识,可以参考<a href="https://zhuanlan.zhihu.com/p/103685761">浅谈混合精度训练</a>.<br>然而很早之前笔者就知道开启混合精度的好处了,在pytorch 下有<a href="https://github.com/NVIDIA/apex">apex</a> 可以很方便的开启,但是在keras(tensorflow 1.x) 下尝试了多次,也没能找到正确的方法,最近笔者又一次尝试,终于找到了正确的姿势,这里也分享一下。</p><h2><span id="dui-ying-de-ruan-ying-jian-yao-qiu">对应的软硬件要求</span><a href="#dui-ying-de-ruan-ying-jian-yao-qiu" class="header-anchor"></a></h2><p>tensorflow要求版本在1.14+ ,对应的显卡需要算力(compute capability)在7及以上, 可以在<a href="https://developer.nvidia.com/cuda-gpus#compute">cuda-gpus#compute</a>查看对应型号卡的算力</p><h2><span id="ru-he-kai-qi">如何开启</span><a href="#ru-he-kai-qi" class="header-anchor"></a></h2><p>对应代码中,只需要增加一行代码,修改一下optimizer即可。不过仍有两点非常需要注意:</p><ol><li>修改optimizer最好在build model前完成,否则某些情况下可能会报错。 </li><li>optimizer需要是tf.train.Optimizer or tf.keras.optimizers.Optimizer继承来的,不支持keras 原生的optimizer。</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line">...</span><br><span class="line">opt = ...</span><br><span class="line">opt = tf.train.experimental.enable_mixed_precision_graph_rewrite(opt) <span class="comment"># rewrite opt</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># build_model</span></span><br><span class="line">...</span><br><span class="line"></span><br></pre></td></tr></table></figure><p>当在打印信息中看到 <code>tensorflow/core/grappler/optimizers/auto_mixed_precision.cc </code>相关信息,则说明已成功开启混合精度。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">...</span><br><span class="line">2021-08-04 07:36:24.231900: I tensorflow/core/grappler/optimizers/auto_mixed_precision.cc:1816] Running auto_mixed_precision graph optimizer</span><br><span class="line">...</span><br></pre></td></tr></table></figure><h2><span id="yi-xie-ce-shi-jie-guo">一些测试结果</span><a href="#yi-xie-ce-shi-jie-guo" class="header-anchor"></a></h2><p>笔者在V100 下用bert-base 做了部分测试,测试结果如下:</p><p><strong>batch_size=32, maxlen=128</strong></p><table><thead><tr><th></th><th>epoch 1</th><th>epoch 2</th><th>epoch 3</th><th>epoch 4</th><th>epoch 5</th></tr></thead><tbody><tr><td>开启前:</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td>277s</td><td>246s</td><td>244s</td><td>246s</td><td>244s</td></tr><tr><td></td><td>164ms/step</td><td>147ms/step</td><td>146ms/step</td><td>147ms/step</td><td>146ms/step</td></tr><tr><td></td><td>best acc:</td><td><code>0.57</code></td><td></td><td></td><td></td></tr><tr><td>开启后:</td><td></td><td></td><td></td><td></td><td></td></tr><tr><td></td><td>235s</td><td>200s</td><td>201s</td><td>200s</td><td>201s</td></tr><tr><td></td><td>140ms/step</td><td>120ms/step</td><td>121ms/step</td><td>120ms/step</td><td>121ms/step</td></tr><tr><td></td><td>best acc:</td><td><code>0.576</code></td><td></td><td></td><td></td></tr></tbody></table><p><strong>batch_size=64, maxlen=128</strong></p><table><thead><tr><th align="center"></th><th align="center">epoch 1</th><th align="center">epoch 2</th><th align="center">epoch 3</th><th align="center">epoch 4</th><th align="center">epoch 5</th></tr></thead><tbody><tr><td align="center">开启前:</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td></tr><tr><td align="center"></td><td align="center">234s</td><td align="center">202s</td><td align="center">201s</td><td align="center">203s</td><td align="center">203s</td></tr><tr><td align="center"></td><td align="center">281ms/step</td><td align="center">242ms/step</td><td align="center">241ms/step</td><td align="center">244ms/step</td><td align="center">244ms/step</td></tr><tr><td align="center"></td><td align="center">best acc:</td><td align="center"><code>0.567</code></td><td align="center"></td><td align="center"></td><td align="center"></td></tr><tr><td align="center">开启后:</td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td><td align="center"></td></tr><tr><td align="center"></td><td align="center">180s</td><td align="center">141s</td><td align="center">140s</td><td align="center">140s</td><td align="center">141s</td></tr><tr><td align="center"></td><td align="center">216ms/step</td><td align="center">169ms/step</td><td align="center">168ms/step</td><td align="center">168ms/step</td><td align="center">169ms/step</td></tr><tr><td align="center"></td><td align="center">best acc:</td><td align="center"><code>0.571</code></td><td align="center"></td><td align="center"></td><td align="center"></td></tr></tbody></table><p>可以看到,batch size越大,加速比越可观,约能节省1/3的训练时间,同时,性能不会出现明显下降甚至可能也会高一点点。<br>另外,测试使用的bert 是keras 代码,其中有一条日志是<code> converted 1265/17548 nodes to float16 precision</code>,所以约有不到10%的节点使用了半精度?所以猜测使用半精度的节点越多加速比越可观。</p><h1><span id="tui-li-jia-su">推理加速</span><a href="#tui-li-jia-su" class="header-anchor"></a></h1><p>推理时通常需要我们提供一个SDK或一个API 服务,这里我们只讨论API 服务的情况。<br>而API 服务通常有两种做法:</p><ol><li>在server 端load 模型,然后直接预测给出结果;</li><li>backend 调用tf-serving ,模型的预测由tf-serving 来提供,其余的(数据的预处理,结果的后处理等)则在backend 端进行。tf-serving 具有热更新,支持多模型多版本,异步调用,高可用等特性,所以也推荐使用tf-serving。使用了tf-serving后,完整的路线变为:<br><strong>client –> backend –> rpc/rest –> tf-serving</strong><br>其中tf-serving 提供了两种形式的api:restful api 和 grpc<br>对应的demo 代码可以查看<a href="https://github.com/xv44586/toolkit4nlp/tree/master/serving">serving</a></li></ol><h2><span id="ce-shi">测试</span><a href="#ce-shi" class="header-anchor"></a></h2><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>可爱修狗</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="speed-up" scheme="https://xv44586.github.io/tags/speed-up/"/>
</entry>
<entry>
<title>对比学习心路历程</title>
<link href="https://xv44586.github.io/2021/07/06/cl2rdrop/"/>
<id>https://xv44586.github.io/2021/07/06/cl2rdrop/</id>
<published>2021-07-06T14:25:15.000Z</published>
<updated>2022-09-08T08:50:09.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#qian-yan">前言</a></li><li><a href="#dui-bi-xue-xi-fan-shi">对比学习范式</a></li><li><a href="#wu-jian-du-dui-bi-xue-xi">无监督对比学习</a></li><li><a href="#you-jian-du-dui-bi-xue-xi">有监督对比学习</a></li><li><a href="#zong-jie">总结</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><h1><span id="qian-yan">前言</span><a href="#qian-yan" class="header-anchor"></a></h1><p>在之前的<a href="https://xv44586.github.io/2021/01/20/ccf-qa-2/">博客</a>里,笔者介绍了在有监督任务(文本匹配)上,增加对比学习来提高模型性能的实验,而当时尝试后却发现,在新增了对比学习的任务后,模型的性能并没有得到对应的提升,经过简单分析后,笔者也尝试给出了一些可能的问题与解决方案。虽然笔者的实验都失败了,但是笔者依然认为对比学习是一个非常好的方向,所以也在持续关注,这篇就算是后续的填坑与总结吧。</p><h1><span id="dui-bi-xue-xi-fan-shi">对比学习范式</span><a href="#dui-bi-xue-xi-fan-shi" class="header-anchor"></a></h1><p>之前也提到过,这里在简单称述一下对比学习目前的主要范式。对比学习主要是通过对比,拉近相似样本之间的距离,推远不相似样本之间的距离。而相似样本的构造,又可以分为有监督与无监督两种:</p><ul><li>有监督对比学习:通过将监督样本中的相同label的样本作为正样本,不同label的样本作为负样本,来进行对比学习;</li><li>无监督对比学习:由于没有监督信号(label),此时,我们对同一个样本构造两个view,让同一样本构造的两个view互为正样本,而其他样本构造的view则全部为负样本,以此来进行对比学习。而由同一个样本构造两个view,又是数据扩增的过程,所以也可以称作是数据扩展对比学习。而不管那种范式,通常对比学习都是在batch内进行。</li></ul><h1><span id="wu-jian-du-dui-bi-xue-xi">无监督对比学习</span><a href="#wu-jian-du-dui-bi-xue-xi" class="header-anchor"></a></h1><p>笔者曾经在实验中,通过对样本进行eda(随机替换、随机删除、随机重复和随机互换),来构造不同view,由此进行对比学习。实验失败后,笔者也提到失败的可能原因主要有两点:batch size太小且样本上直接进行操作,获得的新样本与原来的语义可能由较大的差别甚至是完全的反义。如:“我不会再爱你” –> “我会再爱你”,而强行让这两个样本的语义距离相互靠近,效果自然不会好;而batch size太小的问题,一来是换大的GPU,二来可以尝试在算法层面进行优化,如将Adam 替换为AdaFactor,re-compute等,这不是本文的重点,所以就不再细说了。<br>而针对直接对样本进行修改会导致语义不一致的问题,其主要原因是NLP中,样本的输入是one-hot形式的,我们的相似样本应该是语义上相似,对应的修改后的语义距离应该尽可能“小”,而直接对one-hot形式进行修改,对应的距离恒定是$\sqrt{2}$ ,怎么看也不小。一种解决方法是借鉴对抗训练在NLP中的方式,将修改放在Embedding 层,由于one-hot与Embedding的对应关系,就能够获得“相似语义”的样本了。对应思路的论文有美团今年的<a href="http://arxiv.org/abs/2105.11741">ConSERT: A Contrastive Framework for Self-Supervised Sentence Representation Transfer</a>,总的来说,这篇paper就是将EDA的思路放在embedding 层,然后构建不同view后进行对比学习,最后获得的语义表示超过了当前的sota,在多个数据集上获得了提升。不过值得吐槽的是,美团也学坏了,竟然学会了谷歌那套在更多数据上进行训练然后跟只有原始数据的结果进行对比,可能是论文本身没什么亮点,只能通过“大的提升”来吸引眼球了吧~<br>而做数据扩增时,最常用的两个方式随机删除与随机互换,而位置编码在transformer 中起到的作用不是关键性的,且直接互换位置也会带来语义变化较大的风险,那只使用随机删除策略做数据扩增,进行对比学习效果如何呢?答案是非常好!女神陈丹琦最近的论文<a href="https://arxiv.org/abs/2104.08821">SimCSE: Simple Contrastive Learning of Sentence Embeddings</a>,只使用dropout ,对同一个样本构造两个不同的view,然后进行对比学习,最终的效果在非监督学习任务下8个点提升起步,在监督任务下4个点提升起步,此外,<a href="https://github.com/bojone/SimCSE">苏神</a>在中文任务上也进行了实验,再次验证了该方法的有效性。</p><p><img src="/2021/07/06/cl2rdrop/simcse.png" alt="效果对比"></p><p>而simcse具体是如何工作的呢?其实做法相当简单,即将一个样本进入模型两次,然后通过dropout 两次,获得两个不同的view,互为正样本。而dropout 由于是在feature 维度进行随机mask,所以就得到了与在embedding层随机mask相同的效果,此外,由于在transformer 中,dropout 多次使用,也进一步的增加了两个view 的差异。读完论文,看着效果,直叹“大道至简”,而对比在embedding 做扰动与直接使用dropout,从效果上看dropout 也是一种更加有效的数据扩增手段。那这个思路是不是可以扩展到有监督任务呢?<br>其实读完SimCSE后,笔者就觉得dropout 是一种更加有效的数据扩增手段,自然可以扩展到有监督任务学习中。这里的有监督任务学习有别与有监督对比学习,请读者注意区分。而在有监督任务学习中的方案也是比较直观:将样本进入模型两遍,然后在做监督任务的同时,增加一个对比学习,不过笔者实验时被之前无监督任务的思路所束缚了,一直使用encoder output 的logits来表征语义,所以调来调去效果时好时坏,好的时候也没有超过0.5的提升,所以就放弃了。<br>而最近的论文<a href="http://arxiv.org/abs/2106.14448">R-Drop: Regularized Dropout for Neural Networks</a>却成功的将这个思路延续了下来,在读完其<a href="https://github.com/dropreg/R-Drop">代码</a>后,才发现原来是自己姿势不对~<br>R-Drop中的完整思路是这样的:首先,我们通过将样本重复的输入到模型,然后通过dropout,获取不同的view,而dropout的目的是为了将集成模型的思路延续到深度学习中,本质目的是增加模型的鲁棒性。所以,不同的view获得的最终的输出,我们也希望其尽可能的一致。而论文中的任务都是分类任务,所以模型最终的输出是一个概率分布,衡量两个概率分别的差异通常使用kl散度,所以最终的loss增加了一个对应view之间的kl-divergence。<br>对比笔者之前的思路与R-Drop 中的思路,首先R-Drop 中是直接作用在最终的probs上,而笔者是希望encoder output logits 之间能“同性相吸,异性排斥”,而由于在做分类任务时,在logits 后都会接一个dense层来压缩维度,这个dense 层就会大大抑制前面的对比学习的效果,即前面的logits可以差异很大,但通过最后的dense层,却能得到一样的预测结果(label相同),而直接作用在最终的输出层,才会得到想要的效果;其次在R-Drop 中,不再需要与其他样本做对比,即只要相同view 的输出足够“接近”即可,而不需要其与其他的view 尽可能的“远”,考虑到同一个batch 内,存在同样label的样本的概率是极大的,而要求同时预测对label又要其结果之间尽可能不同是不合理的,所以取消掉与其他view之间的对比也是合理与必要的;而由于不需要去其他view做对比,对比学习中大batch size的要求也就不再需要,即在小的batch size 下该方法依然有效,真可谓“方法简单效果好”,在所有的NLP任务中都值得尝试。<br>前面提到笔者之前的思路的最大问题是对比的不是最后一层的输出,而同一batch内,又不可避免的会出现相同label,让相同label的logits之间相互“远离”似乎不太合理,但从对抗训练或者模型的鲁棒性上考虑,让同一样本的不同view之间相互接近而与其他view之间相互远离,可以达到让样本在一个范围内的输出尽可能的保持一致,从而能使模型更加的鲁棒,自然会增加一些泛化能力。这个思路笔者也在<a href="https://github.com/xv44586/toolkit4nlp/blob/master/examples/classification_tnews_contrastive_learning_dropout.py">之前的实验</a>上进行了修改尝试,结果显示确实能提高一些性能,但是与只拉近相同view之间的kl-divergence相比,提升就不够看了。</p><h1><span id="you-jian-du-dui-bi-xue-xi">有监督对比学习</span><a href="#you-jian-du-dui-bi-xue-xi" class="header-anchor"></a></h1><p>有监督对比学习是指相同label之间互为正样本,不同label间为负样本,与上文提到的有监督任务是不同的。而有监督任务中也可以使用无监督对比学习的思路,如上文提到的R-Drop,而有监督对比学习自从<a href="https://arxiv.org/abs/2011.01403">Supervised Contrastive Learning for Pre-trained Language Model Fine-tuning</a>后,笔者还未关注到有什么新的进展,所以还是推荐在有监督任务上使用R-Drop思路。</p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>本文主要总结了从笔者之前对比学习实验时留下的问题,到最近一些论文中提出的解决思路,虽然整个心路历程与原作者们的肯定不一样,但希望能给读者提供对比学习到目前为止的一些进展脉络。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>图片来自大佬刑无刀(陈开江)的朋友圈:据说人工智能中有80%的部分是人工😄</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="SimCSE" scheme="https://xv44586.github.io/tags/SimCSE/"/>
<category term="R-Drop" scheme="https://xv44586.github.io/tags/R-Drop/"/>
</entry>
<entry>
<title>多任务学习-以天池比赛为例的三种思路</title>
<link href="https://xv44586.github.io/2021/03/28/multi-task/"/>
<id>https://xv44586.github.io/2021/03/28/multi-task/</id>
<published>2021-03-28T01:37:01.000Z</published>
<updated>2021-03-28T03:34:57.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#ti-mu-jian-jie">题目简介</a></li><li><a href="#baseline">baseline</a></li><li><a href="#yang-ben-qu-fen">样本区分</a></li><li><a href="#ruan-qu-fen">软区分</a></li><li><a href="#shi-yan-jie-guo">实验结果</a></li><li><a href="#zong-jie">总结</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><p>几个月前曾关注过天池的多任务学习比赛:<a href="https://tianchi.aliyun.com/competition/entrance/531841/information">NLP中文预训练模型泛化能力挑战赛</a>,本来应该代码写完就应该写这篇总结,不过拖延症患者总是无法按时完成计划!</p><h1><span id="ti-mu-jian-jie">题目简介</span><a href="#ti-mu-jian-jie" class="header-anchor"></a></h1><p>题目是希望选手能够通过算法实现泛化能力更强的中文预训练语言模型,来提高下游业务的准确性,为了评测模型的泛化能力,题目给出了三个不同的任务及对应的数据。 </p><ul><li>OCNLI:是第一个非翻译的、使用原生汉语的大型中文自然语言推理数据集;</li><li>OCEMOTION:是包含7个分类的细粒度情感性分析数据集;</li><li>TNEWS:来源于今日头条的新闻版块,共包含15个类别的新闻;简单来说就是需要用一个模型,来实现多个</li></ul><p>此外,最重要的一点,题目要求”单模型”,即一个任务只能有一个预测函数,所有任务只能使用同一个bert,在计算图中只能有一个bert来完成任务,不能集成多个模型进行预测;</p><p>在验证泛化能力时强加这个要求是否合理这里不做讨论,不过可以看出,题目是一个多任务学习的题目,不过又与平时常见的多任务学习不太一样:通常的多任务学习是在同一个样本上,同时进行多个目标的优化,如Bert中的MLM和NSP,而此次题目却是在多个样本上进行多个任务的学习,不同的任务间并不共享数据。</p><h1><span id="baseline">baseline</span><a href="#baseline" class="header-anchor"></a></h1><p>实现多任务学习最简单的方式就是共享backbone,然后对不同的任务拼接不同的分类器。参考BERT的训练过程,即在transformer last layer后面接task 相关的分类层即可。不过由于不同样本对应不同的任务,此时我们需要针对每个样本指定对应的分类层。由于笔者只会写keras,而由于tf这种静态图,对每个样本进行switch 不同的层不是很方便,所以实现的时候对每个样本都拼接了三个分类层,然后对loss 进行mask,来屏蔽掉非任务部分。<br>具体的实现细节还是看代码,代码:<a href="https://github.com/xv44586/tianchi_multitask/blob/master/baseline.ipynb">multi-task-baseline</a></p><h1><span id="yang-ben-qu-fen">样本区分</span><a href="#yang-ben-qu-fen" class="header-anchor"></a></h1><p>上面的baseline 是通过拼接不同的分类层来完成多任务学习,本质上是希望模型能对不同对任务进行区分,鉴于预训练语言模型的强大,我们还可以在样本上进行区分,让模型”感知”到任务的不同。而样本上进行区分,最简单的方式就是增加一个”任务说明”了。对GPT这种单向语言模型,可以在样本最前面加一个任务相关的字段,如”文本多分类”/“情感分析”,然后将所有任务都转成生成任务。而对bert这种掩码语言模型,也是类似的,即借鉴PET的思路,将所有任务都转成分类任务,并通过不同的pattern 来区分不同的任务,pattern的结果来区分对应的任务的结果。而pet的思路已经写过很多次了,如之前<a href="https://xv44586.github.io/2020/10/25/pet/">PET-文本分类的又一个秒解</a>。<br>不管是GPT还是BERT下,本质都是一样的:借鉴语言模型的生成能力,转化任务类型,对样本增加固定pattern来区分任务类型,最终通过训练语言模型来完成任务。</p><h1><span id="ruan-qu-fen">软区分</span><a href="#ruan-qu-fen" class="header-anchor"></a></h1><p>上面样本区分提到将任务转化为语言模型,那更进一步的想,在语言模型(seq2seq)下,多任务学习是如何做的呢?笔者认为,在语言模型下可以将多任务学习类比为有条件的文本生成任务,而不同的任务类型对应着不同的条件,由此就可以借鉴条件本文生成的做法来做多任务了。<br>条件文本生成的做法,笔者了解不多,所以这里直接借鉴苏神<a href="https://kexue.fm/archives/7124">基于Conditional Layer Normalization的条件文本生成</a>里提到的做法,即把条件融合到Layer Normalization的$β$和$γ$中去。对不同的条件,来调节所有的LayerNormalization层的平移缩放参数,来进行”软区分”。<br>而条件,则可以通过任务类型进行embedding后来表达。对应的实验代码<a href="https://github.com/xv44586/tianchi_multitask/blob/master/tnews_baseline-adapt.ipynb">multi-task-adapt</a></p><h1><span id="shi-yan-jie-guo">实验结果</span><a href="#shi-yan-jie-guo" class="header-anchor"></a></h1><p>上面的三个思路中,笔者没有样本区分的思路进行实验,原因是实在想不到三个长度相同的pattern,所以就没写实验~而另外两个思路,从结果上看效果差别不大,甚至baseline 比软区分还有更优一点点,我想原因可能在于三个不同任务的数据量不同,或三个任务的数据量都偏少,导致conditional学习的不充分,理论上至少应该学到与未加conditional效果持平,即此时conditional无效。具体调优就留给感兴趣的读者了。</p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>以上就是笔者三个月前针对天池比赛对多任务学习进行的一些思考与总结,不过时间间隔有些久了,难免有些东西忘了或者记错了,如有发现还望提出。<br>PS:拖延症该管管了!!</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="multi-task" scheme="https://xv44586.github.io/tags/multi-task/"/>
</entry>
<entry>
<title>辞旧迎新</title>
<link href="https://xv44586.github.io/2021/02/19/happy-new-year/"/>
<id>https://xv44586.github.io/2021/02/19/happy-new-year/</id>
<published>2021-02-18T23:31:19.000Z</published>
<updated>2021-02-20T13:26:37.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#ci-jiu">辞旧</a></li><li><a href="#ying-xin">迎新</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div>在新年这个重要的时间节点上,总应该做些总结与计划,来辞旧迎新,然而我总是懒惰加拖延,到了今天初八才正式开写,所以新的一年,第一个flag就是:希望能找到良药医治一下我的拖延症 - -!<h1><span id="ci-jiu">辞旧</span><a href="#ci-jiu" class="header-anchor"></a></h1><p>2020年是神奇的一年,这一年里发生了很多意想不到的事,用网友们的话讲就是这一年总忙着见证历史了。而需要见证的历史太多,大多数我都没关注,所以只记录一下我自己”见证”的历史。<br>首先是过年回家,刚到家我就去县里的药店屯了口罩和感冒药,之后就是持续至今的新冠,而由于新冠初期,大家对其认识很少,所以限制出行,而我也就因此度过了高中以来在家最久的一次”寒假”–整整三个月,直到四月初,才买到了机票回来上班。<br>在家的三个月,对我最大的帮助可能就是心灵治愈了,<code>人间烟火气,最抚凡人心</code><br>虽然在家陪父母,但是后期还是需要正常打卡的,所以我也顺便研究了一下藏文语言模型和文本纠错,也就是这个时期,让我更坚定的想要一台自己的服务器,来实验想法。于是,回到北京后,我就开始调研硬件相关,然后自己动手搭了一台<a href="https://xv44586.github.io/2020/05/05/make-a-computer/">服务器</a>,这里还要感谢一下银翔老哥,帮我报销了2080Ti的钱,哈哈。<br>有了服务器,我就开始<code>放弃幻想,拥抱transformer </code>, 开始了看论文与写demo 的路程,期间参考<a href="https://github.com/bojone/bert4keras.git">bert4keras</a> 与 <a href="https://github.com/CyberZHG/keras-bert">keras-bert</a>, 实现了一下Bert,不过中间有次实验结果有些奇怪,为了查问题,API基本上改成bert4keras一致了。后面一些相transformer相关的实验代码基本也都在<a href="https://github.com/xv44586/toolkit4nlp">toolkit4nlp</a><br>这里还有一个小插曲,王萌老哥带我打了一个脑科学的比赛,我们俩花了一天最后拿了个铜牌,这也让我了解到在很多领域,对机器学习和深度学习还是运用的相当少的,一方面是因为任务形态的原因,一方面也是这些领域接受新事物比较缓慢可能,所以我认为与机器学习做交叉,还是有很多事可以做的。<br>接着就到了十一月,由于看到群里有人讨论小学生解数学题的比赛,就去看了<a href="https://www.datafountain.cn/">datafountain</a>,然后就写了<a href="https://www.datafountain.cn/competitions/474/datasets">房产行业问答匹配比赛</a>的baseline:<a href="https://github.com/xv44586/ccf_2020_qa_match">ccf_2020_qa_match</a>, 其实比赛到这就结束了,我就去看论文了,然而有时候事情就是这么奇妙,很多群里的同学看到我的baseline 邀请我去组队,后来与南大的”钞人小哥”刘猛组队,最终拿了第一还。不过答辩时发现,在看论文与做实验的纬度上,我们应该也是第一了- - !无论如何,也要感谢一下刘猛小哥,不是他邀请我,我也不会拿到这个金牌。毕竟baseline 我写过不少,但是真的认真打到最后还是第一次~~</p><p>总结一下,去年发生了新冠,直到现在大家还是备受困扰,我个人度过了最爽的一个年假,在NLP 上也有了实足的进步,不过也有很多年初的计划没有完成,比如每周一篇blog,看完花书等。</p><h1><span id="ying-xin">迎新</span><a href="#ying-xin" class="header-anchor"></a></h1><p><code>凡事预则立,不预则废。</code>所以,flag还是要立的,新的一年,希望自己更少一点浮躁,做事能更多一点踏实。</p><ul><li>经典论文还有很多没读,最新的论文又源源不断,所以希望新的一年能找到适合自己的读论文节奏。</li><li>一周一篇blog 可能太困难了,希望能一年写三十篇吧就</li><li>感情上也要更成熟一些,对未来要有更清晰的规划</li><li>有空就多运动</li></ul><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>成都海洋馆的可爱白鲸</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="Life" scheme="https://xv44586.github.io/categories/Life/"/>
</entry>
<entry>
<title>ccf问答匹配比赛(下):如何只用“bert”夺冠</title>
<link href="https://xv44586.github.io/2021/01/20/ccf-qa-2/"/>
<id>https://xv44586.github.io/2021/01/20/ccf-qa-2/</id>
<published>2021-01-20T12:39:29.000Z</published>
<updated>2026-01-01T21:57:47.309Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#qian-yan">前言</a><ul><li><a href="#sai-ti">赛题</a></li><li><a href="#qa-pair">QA pair</a></li><li><a href="#qa-point">QA Point</a></li><li><a href="#pattern-exploiting-training-pet">Pattern-Exploiting Training (PET)</a></li><li><a href="#concat">Concat</a></li><li><a href="#focal-loss">focal loss</a></li><li><a href="#dui-kang-xun-lian">对抗训练</a></li><li><a href="#post-training">post training</a></li></ul></li><li><a href="#post-training">Post Training</a><ul><li><a href="#mlm">MLM</a></li><li><a href="#nsp">nsp</a></li><li><a href="#model-adaptive">model-adaptive</a></li><li><a href="#geng-xin-ci-shu">更新次数</a></li><li><a href="#zui-zhong-jie-guo">最终结果</a></li></ul></li><li><a href="#rong-ru-zhi-shi">融入知识</a></li><li><a href="#dui-bi-xue-xi">对比学习</a><ul><li><a href="#fei-jian-du-dui-bi-xue-xi">非监督对比学习</a></li><li><a href="#jian-du-dui-bi-xue-xi">监督对比学习</a></li><li><a href="#shi-yan-jie-guo">实验结果</a></li></ul></li><li><a href="#shu-ju-zeng-qiang">数据增强</a><ul><li><a href="#eda">EDA</a></li><li><a href="#wei-biao-qian">伪标签</a></li><li><a href="#shi-yan-jie-guo-1">实验结果</a></li></ul></li><li><a href="#zi-zheng-liu">自蒸馏</a></li><li><a href="#shuffle-jie-ma">shuffle 解码</a></li><li><a href="#mo-xing-rong-he">模型融合</a></li><li><a href="#shi-yan-zong-jie">实验总结</a></li><li><a href="#bi-sai-jie-guo">比赛结果</a></li><li><a href="#dai-ma">代码</a></li><li><a href="#zui-hou">最后</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div>ccf问答匹配比赛也结束了一段时间了,这篇算是一个下篇吧,总结一下后期优(夺)化(冠)的心路历程。标题中的“bert”指的是bert-base系列模型,包括bert/RoBERTa/NEZHA/MacBERT/ERNIE等,而取这个有点“标题党”的标题的主要原因,也是对答辩看到有些团队使用的bert+xgb这种“大力出奇迹”做法吐个槽。<h1><span id="qian-yan">前言</span><a href="#qian-yan" class="header-anchor"></a></h1><p>在<a href="https://xv44586.github.io/2020/11/08/ccf-qa/">上一篇</a>中,笔者对比赛做了简单说明,提出了四种baseline(QA Pair/QA Point/PET/Concat),并做了部分尝试(focal loss/对抗训练/梯度惩罚/kfold/post training),没看过的同学可以先看上篇,这里只简单再介绍一下:</p><h2><span id="sai-ti">赛题</span><a href="#sai-ti" class="header-anchor"></a></h2><p>本次赛题的任务是:给定IM交流片段,片段包含一个客户问题以及随后的经纪人若干IM消息,从这些随后的经纪人消息中找出一个是对客户问题的回答。</p><ul><li>数据示例<table><thead><tr><th>对话id</th><th>客户问题</th><th>经纪人回复id</th><th>经纪人回复内容</th><th>回复标签</th></tr></thead><tbody><tr><td>1</td><td>您好,请问这个户型有什么优缺点</td><td>1</td><td>你是想看看这套房子是吗</td><td>0</td></tr><tr><td></td><td></td><td>2</td><td>在的</td><td>0</td></tr></tbody></table></li></ul><p>| | | 3 | 此房房型方正 得房率高 多层不带电梯4/6楼 | 1 |</p><ul><li>评测标准</li></ul><p>f1:$$2 * (精度 * 召回) / (精度 + 召回)$$</p><h2><span id="qa-pair">QA pair</span><a href="#qa-pair" class="header-anchor"></a></h2><p>由于回答列表是不连续的,所以不考虑问答之间的顺序关系,将其拆分为query-answer pair,然后进行判断。<br><img src="/2021/01/20/ccf-qa-2/pair.png" alt="pair"></p><h2><span id="qa-point">QA Point</span><a href="#qa-point" class="header-anchor"></a></h2><p>考虑对话连贯性、相关性,将所有回答顺序拼接后再与问题拼接,组成query-answer list,模型对一个问题的所有答案进行预测。此外,我们还给模型增加了“大局观”,即新增一个任务来预测全局所有回答中是否存在label为 1 的回答。<br><img src="/2021/01/20/ccf-qa-2/point.png" alt="point"></p><h2><span id="pattern-exploiting-training-pet">Pattern-Exploiting Training (PET)</span><a href="#pattern-exploiting-training-pet" class="header-anchor"></a></h2><p>此方案通过增加一个pattern,将任务转换为MLM任务,然后通过pattern的得分来判断对应的类别。<br>如本次比赛可以添加一个前缀pattern:“间接回答问题”/ “直接回答问题”,分别对应label 0 / 1,pattern的得分只需看第一个位置中“间”/“直”两个token的概率谁高即可。对于unlabel data,可以不增加pattern 进行mlm任务,这也在一定程度增加了模型的泛化能力。此外,通过多个不同pattern进行融合也能进一步提高其性能。<br><img src="/2021/01/20/ccf-qa-2/pet.png" alt="pet"></p><h2><span id="concat">Concat</span><a href="#concat" class="header-anchor"></a></h2><p>由于bert 中不同的transformer 层提取到的语义粒度不同,而不同粒度的信息对分类来说起到的作用也可能不同,所以可以将所有粒度的语义信息拼接后作为特征进行分类。<br><img src="/2021/01/20/ccf-qa-2/concat.png" alt="concat"></p><h2><span id="focal-loss">focal loss</span><a href="#focal-loss" class="header-anchor"></a></h2><p>由于针对性回答与非针对性回答在数量上有不小差距,大约3:1,所以也想到尝试在loss上进行调节。<br>最终结果是没有多少提升,猜测样本不均衡的问题影响是非常小的,所以将Binary-Crossentropy训练后的模型在train data上进行了predict,并借鉴之前<a href="https://xv44586.github.io/2020/10/14/focal-loss/">focal loss</a>中的方式分析了一下,画出对应的难易样本分布。根据图形上的分布结果,也证实了之前的猜测。<br><img src="/2021/01/20/ccf-qa-2/focalloss.png" alt="focalloss"></p><h2><span id="dui-kang-xun-lian">对抗训练</span><a href="#dui-kang-xun-lian" class="header-anchor"></a></h2><p>对抗训练主要尝试了<a href="https://kexue.fm/archives/7234">FGM 方法对Embedding进行扰动</a>,线下对比提升大约一个点上下。<br>线下测试结果:</p><table><thead><tr><th>without adt</th><th>with adt</th></tr></thead><tbody><tr><td>0.831</td><td>0.838</td></tr></tbody></table><h2><span id="post-training">post training</span><a href="#post-training" class="header-anchor"></a></h2><p>上一篇中,提到post training 做的效果不好,然而pet 的效果又很好,两者比较矛盾,所以我也重新阅读了几篇关于优化bert 与post training 相关的论文,重新思考了一下,这篇就从重新做post training开始。</p><h1><span id="post-training">Post Training</span><a href="#post-training" class="header-anchor"></a></h1><p>post training一般包括两部分:Domain-Adaptive training 和 Task-Adaptive training,通过在同领域与任务数据上继续预训练,可以让模型更适应任务,有利于提高模型在下游的性能。而bert 在训练时主要有两个任务:mlm 与nsp ,接下来针对每个任务进行讨论。</p><h2><span id="mlm">MLM</span><a href="#mlm" class="header-anchor"></a></h2><p>在post training 阶段尝试进一步优化的只找到刘知远老师的<a href="https://arxiv.org/abs/2004.09733">Train No Evil: Selective Masking for Task-Guided Pre-Training</a>,论文里的思路是通过建立一个二分类模型,来有针对性的选择token 来进行mask,不过由于这个方法比较麻烦,需要三个中间模型,所以没有尝试,不过这个论文给出了一个结论:在继续预训练的过程中,优化mask 策略,是可以进一步提高下游性能的。<br>让我们回归一下bert 的mask 策略即后续的改进:</p><table><thead><tr><th>model</th><th>mask strategy</th></tr></thead><tbody><tr><td>bert</td><td>random mask</td></tr><tr><td>RoBERTa</td><td>dynamic mask</td></tr><tr><td>RoBERTa-wwm-ext</td><td>whole word mask</td></tr><tr><td>ERNIE</td><td>entity/phrase mask</td></tr><tr><td>SpanBERT</td><td>n-gram mask</td></tr></tbody></table><p>这里笔者思考后认为,不同的mask 策略本质区别是对更多的“固定搭配”进行同时mask,从而降低模型对局部、浅层信息的过拟合,增加任务的难度,提高模型的泛化能力。<br>所谓“固定搭配”,不仅仅包含词,或者说是更广义的“词”。字的固定搭配可以构成词,进一步固定搭配又可以形成短语。比如考虑“好好学习,天天向上”,“08北京奥运会”,如果只mask 其中一部分,是比较“容易”通过剩余的部分来还原的。<br>既然“固定搭配”是更广义的词,这里我们就可以来挖掘这些“固定搭配”了。最简单的方式就是新词/短语挖掘,而新词/短语挖掘最常用的方法是计算左右熵和紧密度,不过这种方式计算量较大,这次比赛笔者舍弃了这种方式,采用借鉴苏神的博客<a href="https://kexue.fm/archives/5476">最小熵原理(二):“当机立断”之词库构建</a>中的思路,用PMI表征紧密度,用相邻两个字之间的紧密度判断两者是否存在“固定搭配”,最终未被切分的为一个整体。最后将挖掘出的新词通过jieba 过滤掉已在词库内的,并只保留长度2~5的新词,添加到jieba的词库内。这里选择用jieba 做分词工具的原因是因为笔者用的是NEZHA,而NEZHA在训练时使用的就是jieba 处理的数据,这里与他保持一致,而长度选择上,主要借鉴spanBert中的结论。<br>最后挖掘了2736个新词,而如果是实际工作中,则可以进一步将积累的词也加入。<br><img src="/2021/01/20/ccf-qa-2/new_words.png" alt="new_words"><br>以上的方式中全程没有人为参与,所以新词的质量是无法保证的,即存在词的边界不准确。而此时的全词mask 退化为n-gram mask,依然是一种有效的提升方案。</p><h2><span id="nsp">nsp</span><a href="#nsp" class="header-anchor"></a></h2><p>原始bert 在训练时,句子级别的任务为nsp,而RoBERTa 中给出的结论是句子级别的任务没什么用,所以取消了句子级别的任务;而albert 中则将句子级别的任务切换为sop,而SpanBERT中则切换为sbo。这里笔者认为下游任务是句子级别的分类任务,所以句子级别的任务是有用的,不过由于nsp 会引入大量噪音,所以这里选择sop/aop:在qa pair格式的样本下互换qa(sop),在q a-list格式的样本下,保持query 在最前面,只shuffle a-list(aop)。</p><table><thead><tr><th></th><th>without sop/aop</th><th>with sop/aop</th></tr></thead><tbody><tr><td>qa pair</td><td>0.784</td><td>0.79</td></tr><tr><td>q a-list</td><td>0.799</td><td>0.802</td></tr></tbody></table><h2><span id="model-adaptive">model-adaptive</span><a href="#model-adaptive" class="header-anchor"></a></h2><p>由于样本的组织方式有qa pair 和 q a-list两种方式,而task 相关的数据是相对较小的,所以这里笔者认为两个阶段的样本组织方式相同的情况下,性能会更好,即:用qa pair格式post training后的模型,来微调qa pair格式的baseline,q a-list格式post training后的模型微调q a-list格式的baseline。</p><h2><span id="geng-xin-ci-shu">更新次数</span><a href="#geng-xin-ci-shu" class="header-anchor"></a></h2><p>这里参考邱锡鹏老师的<a href="http://arxiv.org/abs/1905.05583">How to Fine-Tune BERT for Text Classification?</a>,实验时每10 个epochs保存一次模型,最后通过在下游任务上的表现,得出与论文中基本一致的结论:更新10K steps左右模型在下游的表现是最好的。<br><img src="/2021/01/20/ccf-qa-2/update.png" alt="update"></p><h2><span id="zui-zhong-jie-guo">最终结果</span><a href="#zui-zhong-jie-guo" class="header-anchor"></a></h2><table><thead><tr><th>post-train/fine-tuning</th><th>pair</th><th>point</th></tr></thead><tbody><tr><td>pair</td><td>0.79</td><td>0.794</td></tr><tr><td>point</td><td>0.786</td><td>0.802</td></tr></tbody></table><p>此时我们认为已经将bert的能力最大化了,于是这里也尝试了在bert 后面接一些复杂的分类层(cnn/rnn/dgcnn/..),发现都无法进一步提高,所以也证实了之前的判断。</p><h1><span id="rong-ru-zhi-shi">融入知识</span><a href="#rong-ru-zhi-shi" class="header-anchor"></a></h1><p>既然从“内部”已经无法进一步提高bert的能力,所以此时尝试融入外部知识来增强。而融合的方式主要尝试了两种: </p><ul><li><p>最底层注入<br>在Embedding 层融入外部的embedding。优点:更多的交互<br><img src="/2021/01/20/ccf-qa-2/inside.png"> </p></li><li><p>最顶层注入<br>在transformer output 层融入外部Embedding。优点:更灵活,不局限外部知识的形式(可以是Embedding,也可以说是其他特征,如手工特征)。<br><img src="/2021/01/20/ccf-qa-2/outside.png"></p></li></ul><p>在知识选择上,首先想到的是Graph EMbedding,参考<a href="http://arxiv.org/abs/2004.05707">VGCN-BERT: Augmenting BERT with Graph Embedding for Text Classification</a>,我们重跑了一下论文的代码,发现无法重现其中的结论,而我对Graph Embedding也不熟,所以放弃了这个方案。<br>然后尝试简单的embedding,即用gensim 在task data上训练了一版词向量(dims=100),作为外部知识来实验。<br>线下测试结果:</p><table><thead><tr><th>model</th><th>score</th></tr></thead><tbody><tr><td>bert</td><td>0.831</td></tr><tr><td>external-embedding bottom</td><td>0.82</td></tr><tr><td>external-embedding top</td><td>0.83</td></tr></tbody></table><p>可以看到,两种方式都是无法进一步提高的,主要原因可能是:1.词向量的质量较差;2.词向量也是bert的“内部”知识;3.融入的方式或者调参没做好。</p><h1><span id="dui-bi-xue-xi">对比学习</span><a href="#dui-bi-xue-xi" class="header-anchor"></a></h1><p>在模型上,还能通过增加新的任务来尝试提高性能。而今年比较热的一个思路就是对比学习,所以这里尝试通过增加一个对比学习任务来提高性能。<br>对比学习的主要思路是拉近到正样本之间的距离,拉远到负样本之间的距离。<br><img src="/2021/01/20/ccf-qa-2/cl.png"><br>对比学习主要又分为两种:监督对比学习和分监督对比学习。监督对比学习中,将相同label的样本看做是正例,其他的为负例;而非监督对比学习中,则通过对每个样本构造一对view,view之间互为正例,其他的为负例。</p><h2><span id="fei-jian-du-dui-bi-xue-xi">非监督对比学习</span><a href="#fei-jian-du-dui-bi-xue-xi" class="header-anchor"></a></h2><p>非监督对比学习中,通过互换QA位置,同时随机mask 15% 的token,来构造一对view。</p><ul><li>对应的loss:</li></ul><p><img src="/2021/01/20/ccf-qa-2/sscl.png"></p><ul><li>对应的模型:</li></ul><p><img src="/2021/01/20/ccf-qa-2/sscl-model.png"></p><h2><span id="jian-du-dui-bi-xue-xi">监督对比学习</span><a href="#jian-du-dui-bi-xue-xi" class="header-anchor"></a></h2><p>这里主要follow <a href="http://arxiv.org/abs/2011.01403">Supervised Contrastive Learning for Pre-trained Language Model Fine-tuning</a>,修改对应loss.</p><ul><li>loss</li></ul><p><img src="/2021/01/20/ccf-qa-2/scl.png"></p><ul><li>model</li></ul><p><img src="/2021/01/20/ccf-qa-2/sc-model.png"></p><h2><span id="shi-yan-jie-guo">实验结果</span><a href="#shi-yan-jie-guo" class="header-anchor"></a></h2><ul><li>线下结果:</li></ul><table><thead><tr><th>model</th><th>score</th></tr></thead><tbody><tr><td>bert</td><td>0.831</td></tr><tr><td>self-supervised contrastive learning</td><td>0.80</td></tr><tr><td>supervised contrastive learning</td><td>0.824</td></tr></tbody></table><ul><li><p>非监督对比学习结果可视化<br><img src="/2021/01/20/ccf-qa-2/ssc-vis.png"></p></li><li><p>监督对比学习结果可视化<br><img src="/2021/01/20/ccf-qa-2/sc-vis.png"></p></li></ul><p>可以看到,两种方式都没有带来提升,而可视化图中可以看到,非监督对比学习的效果并不好,存在大量重叠但颜色不同的点,说明对比学习任务的结果不好,这里的原因猜测主要有两点:1.模型的设计与调参时有问题,batch size(32)太小,没有BN 层等,都有可能是性能不好的原因;2.构造view 的方式过于简单粗暴,由于样本长度大多较短,随机mask 后即有可能引入错误的label 信息,又可能引起view 间语义的gap过大,无法互为正例。<br>监督学习效果图中,不同label的数据被分到了不同的簇中,说明对比学习的还是相当不错,不过由于此次比赛中的label 代表的是“是否是针对问题的回答”,label 相同但内涵不同,所以强行将相同label的样本聚合,并不能带来提升。</p><h1><span id="shu-ju-zeng-qiang">数据增强</span><a href="#shu-ju-zeng-qiang" class="header-anchor"></a></h1><p>数据增强主要尝试了两种方式:EDA 和伪标签。</p><h2><span id="eda">EDA</span><a href="#eda" class="header-anchor"></a></h2><p>EDA主要包括四种方式:随机替换、随机删除、随机重复和随机互换。<br>由于词向量质量较差,所以操作时选择从当前句子中随机选取一个词作为“同义词”进行操作。<br>操作比例为10%,每个样本构造四个样本。<br>用训练过的模型对数据进行过滤,保留置信度高(>0.7)的样本。</p><h2><span id="wei-biao-qian">伪标签</span><a href="#wei-biao-qian" class="header-anchor"></a></h2><p>用训练过的模型在test data 上进行预测,对预测结果按0.5 为阈值计算置信度并进行排序,保留前30%的样本加入训练集。 这里没有单纯按置信度过滤样本,是因为模型预测结果大多数大于0.95或小于0.05,而过多的测试数据进入训练集,会导致模型最终的结果是在拟合训练集中的label,而无法带来提高(充分学习后的模型在训练数据上的预测结果自然是训练时的label)。</p><h2><span id="shi-yan-jie-guo">实验结果</span><a href="#shi-yan-jie-guo" class="header-anchor"></a></h2><ul><li>线上结果</li></ul><table><thead><tr><th>without DA</th><th>with DA</th></tr></thead><tbody><tr><td>0.802</td><td>0.806</td></tr></tbody></table><h1><span id="zi-zheng-liu">自蒸馏</span><a href="#zi-zheng-liu" class="header-anchor"></a></h1><p>借助知识蒸馏,我们尝试了自蒸馏方案:即Teacher 与 Student 为同一个模型,Teacher模型先学习一遍后,对训练样本打上soft labels,Student 同时学习true labels 与 soft labels. </p><ul><li><p>soft labels:<br>$$<br>q_i = \frac{exp(\frac{z_i}{T})}{\sum_j exp(\frac{z_j}{T})}<br>$$</p></li><li><p>线下测试结果:</p><table><thead><tr><th>without KD</th><th>with KD</th></tr></thead><tbody><tr><td>0.831</td><td>0.84</td></tr></tbody></table></li></ul><h1><span id="shuffle-jie-ma">shuffle 解码</span><a href="#shuffle-jie-ma" class="header-anchor"></a></h1><p>对于q a-list 的模型,可以在预测时,对answer list 进行全排列,然后将结果投票,一来可以将answer label之间的影响降低,二来可以在非常小的成本下融合,也算是一种trick。不过此次比赛的数据对顺序比较敏感,shuffle后大多数情况下会降低模型的性能,所以最终融合后结果没提升反而降低了。</p><h1><span id="mo-xing-rong-he">模型融合</span><a href="#mo-xing-rong-he" class="header-anchor"></a></h1><p>为了提高模型的稳定性与泛化能力,我们进行了模型融合。融合时,我们期望模型间能“和而不同”:每个单模型的性能之间差异小(都要接近最优单模型),且模型之间差异尽量大(架构或者优化方案上差异尽量大)。根据以上策略,对QA Pair 与 QA Point两种模型进行融合。</p><h1><span id="shi-yan-zong-jie">实验总结</span><a href="#shi-yan-zong-jie" class="header-anchor"></a></h1><ul><li>能work的方案</li></ul><table><thead><tr><th>方案</th><th>提升</th></tr></thead><tbody><tr><td>task-adaptive training</td><td>+1.5%~3%</td></tr><tr><td>加入新词</td><td>+0.5%~1%</td></tr><tr><td>加入sop/aop</td><td>+0.1%~0.3%</td></tr><tr><td>model-adaptive</td><td>+0.5%~0.7%</td></tr><tr><td>对抗训练</td><td>+0.5%~0.9%</td></tr><tr><td>EDA</td><td>+0.3%~0.5%</td></tr><tr><td>模型融合</td><td>+0.5%~0.7%</td></tr></tbody></table><ul><li>不能work的方案</li></ul><table><thead><tr><th>方案</th><th>提升</th></tr></thead><tbody><tr><td>external-embedding bottom</td><td>-0.2%~0%</td></tr><tr><td>external-embedding top</td><td>-0.1%~0%</td></tr><tr><td>self-supervised contrastive learning</td><td>-0.4%~-0.2%</td></tr><tr><td>supervised contrastive learning</td><td>-0.1%~0%</td></tr><tr><td>focal loss</td><td>0%</td></tr><tr><td>shuffle trick</td><td>-0.15%~0</td></tr></tbody></table><ul><li>线下有效但未提交</li></ul><table><thead><tr><th>方案</th><th>提升</th></tr></thead><tbody><tr><td>自蒸馏</td><td>+0.5%~1%</td></tr><tr><td>伪标签</td><td>+0.1%~0.3%</td></tr></tbody></table><p>对于PET ,在post training后的效果并不是很好,不过由于没有时间了,所以没有继续优化。这里提一下可以优化的点:1.可以增加解码空间;2.增加多个pattern 进行融合的方式尝试优化。笔者本人是比较喜欢PET 这个思路的,统一了两个阶段,所以可做的事还有很多。</p><h1><span id="bi-sai-jie-guo">比赛结果</span><a href="#bi-sai-jie-guo" class="header-anchor"></a></h1><p>比赛最终的线上成绩在A/B 榜均是第一,答辩阶段也得到了第一。</p><ul><li>A榜得分:<br><img src="/2021/01/20/ccf-qa-2/a-result.png"></li><li>B榜得分:<br><img src="/2021/01/20/ccf-qa-2/b-result.png"></li><li>答辩得分:<br><img src="/2021/01/20/ccf-qa-2/last-result.jpg"></li></ul><h1><span id="dai-ma">代码</span><a href="#dai-ma" class="header-anchor"></a></h1><p>比赛相关思路的代码开源在github上:<br><a href="https://github.com/xv44586/ccf_2020_qa_match">ccf_2020_qa_match</a><br>欢迎大家尝试使用,有问题或者想法可以提issue,一起讨论。</p><h1><span id="zui-hou">最后</span><a href="#zui-hou" class="header-anchor"></a></h1><p>本文主要总结了此次ccf 问答匹配中的实验思路,而其中提出的四种baseline ,可以横向推广至所有的文本分类相关的任务中,而优化相关的方案,则可以应用在所有bert-base 模型上。 从最初打算“白嫖”一份数据,到最终拿到第一,算起来这应该是笔者第一次参加NLP的比赛,所以很幸运也很惊喜。<br>Enjoy!</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>答辩头图</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="QA" scheme="https://xv44586.github.io/tags/QA/"/>
<category term="CCF" scheme="https://xv44586.github.io/tags/CCF/"/>
<category term="Competition" scheme="https://xv44586.github.io/tags/Competition/"/>
</entry>
<entry>
<title>重新认识矩阵</title>
<link href="https://xv44586.github.io/2021/01/12/matrix/"/>
<id>https://xv44586.github.io/2021/01/12/matrix/</id>
<published>2021-01-12T13:10:17.000Z</published>
<updated>2021-01-12T13:46:41.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#shi-me-shi-1">什么是1</a></li><li><a href="#xiang-liang">向量</a></li><li><a href="#ju-zhen">矩阵</a></li><li><a href="#ju-zhen-cheng-fa">矩阵乘法</a></li><li><a href="#xiang-si-ju-zhen">相似矩阵</a></li><li><a href="#xing-lie-shi">行列式</a></li><li><a href="#zong-jie">总结</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><p>最近有篇很火的论文bert-flow,其中的flow是出了GAN和VAE之外的第三种生成模型,我竟然是第一次听说,所以引起了我的好奇心,然而看flow模型时,发现里面有一个很重要的概念就是行列式,数学渣太久没碰过他所以再看到有些陌生,于是就找了一些文章来重新学习一下,后来发现了孟岩的认识矩阵系列博客与苏神的新认识矩阵系列博客,看完感觉对矩阵的认识refresh了一下,所以做一些总结记录。</p><h1><span id="shi-me-shi-1">什么是1</span><a href="#shi-me-shi-1" class="header-anchor"></a></h1><p>首先我们来讨论一下,什么是<code>1</code>,所谓1,就是参照物,或者是基,而其他的数字都是在参考他得到的,比如2,2就是2 个1,也就是1*2.<br>而1又是一个抽象的东西,你没法直观感受什么是1,只有在参考系下,才能直观感受到什么是1,如1瓶水,1米长,而1 与一米的关系就像类与对象一样。所以在参考系下,2米的含义就是2个1米,1米 * 2,即在以1米为基的坐标系下,他在2的“位置”。而5瓶水,对应的含义是在以1瓶水为基的参考系下,<br>他处在5的“位置”。而不同的坐标系,在不同的情况下也有优劣之分,如同样的酒,当用来统计销售额时,我们用“瓶”来统计就比用“杯”来统计方便,而当用来劝酒时,用“杯”就明显比用“瓶”要合适一些。也就是,酒还是那么多酒,但是当你用不同的方式来度量时,得到的结果也是不同的,是有优劣之分的。</p><h1><span id="xiang-liang">向量</span><a href="#xiang-liang" class="header-anchor"></a></h1><p>对于向量$A(a, b)$,其对应的含义是在直角坐标系(以$\vec{i}=(1,0),\vec{j}=(0,1)$为基)中,A是在x轴上为a,y轴上为b的点。<br>比如,在以x轴为斤y轴为瓶的直角坐标系(以$\vec{i}=(一斤,0瓶), \vec{j}=(0斤,1瓶)$为基)中,三瓶500毫升的肥仔水对应的向量就是B(3,3).所以,<code>所谓向量即在线性空间内,选定一组基后,用来刻画一个对象</code>。</p><h1><span id="ju-zhen">矩阵</span><a href="#ju-zhen" class="header-anchor"></a></h1><p>那何为矩阵呢?比如对于矩阵<br>$$A=\begin{pmatrix}<br>a & c \\<br>b & d<br>\end{pmatrix}$$<br>我们能观察到什么呢?首先,矩阵也是由向量(列向量)组成的,对于当前矩阵A,也就对应着$\vec{i}=(a,b),\vec{j}=(c,d)$ 两个向量,然后将他们按一定的顺序排列就组成了矩阵;此外,这个矩阵又代表了由这两个向量为基组成的坐标系(线性空间),在这个空间内,所有的对象都能通过这两个基来进行刻画,即对应于一个“向量”。这里如何更直观的理解呢?还是考虑之前的例子,同样是酒,我们可以用“杯”来度量,也可以用“瓶”来度量,假如我们用“杯”作为最初的基,<br>那矩阵对应的就是“瓶”作为基构成的坐标系,而构成矩阵的向量又是在以“杯”为基下度量出来的,其对应的向量中的值的含义就是“瓶”的基在“杯”这个基下的表示,<br>同样的酒,假如在“杯”坐标系下为10(杯),通过这个矩阵,就变为了2(瓶),<code>即矩阵是线性空间里变换(运动)的描述。</code></p><h1><span id="ju-zhen-cheng-fa">矩阵乘法</span><a href="#ju-zhen-cheng-fa" class="header-anchor"></a></h1><p>那何为矩阵乘法呢?比如现在有矩阵<br>$$<br>A=\begin{pmatrix}<br>a & c \\<br>b & d<br>\end{pmatrix}<br>$$<br>向量$x=(e,f)$,对于$Ax$这个矩阵与向量的乘法,这个是什么含义呢?上面我们已经提到了,矩阵是由列向量组成的,而列向量又可以看做是对应坐标系下的一组基,那根据之前我们提到向量与基的关系,就是在基确定后,用来刻画其空间内对象的,对应的就是乘法,那这个矩阵与向量的乘法,我们也可以用这种方式来看待,即矩阵与向量的乘法代表的是在矩阵对应的列向量为基组成的空间内,由向量刻画的对象。这也是为什么矩阵乘法要求对应维度要相等,<br>对应的就是需要用所有的基,才能正确刻画在这组基对应坐标系下的位置(对象)。而矩阵与向量乘法就是用新的基来刻画对象,<code>也即矩阵与向量的乘法代表施加变换。</code></p><p>那为什么矩阵的乘法公式里为对应行与列相乘并求和呢?这是因为最终的结果,我们是需要变换到对应的直角坐标系下的,因为我们书写出来的所有向量,都默认是在直角坐标系下的结果,所以我们需要将结果向量在每个轴上进行分解,然后合并得到最终的结果,对应的就是行与列相乘后求和。</p><p>那何为矩阵与矩阵的乘法呢?如$A * B$,同理,矩阵与矩阵的乘法也可以看做是连续变换后,构造的新的坐标系。矩阵是由向量组成的,而矩阵与向量乘法的含义是在新的基下刻画的对象表示,所以矩阵与矩阵的乘法,对应B这个变换在施加了A这个变换后,形成的新的变换,而施加这个变换,对应B中的列向量都是在A坐标下刻画得到的。</p><p>那何为线性方程呢?比如$Ax = y$, 对应的线性方程是什么含义呢?这个方程左侧有向量有矩阵,而右侧缺只有向量,有点不和谐,我们变换一下,让他们形式一样,$Ax = Iy$,这样,等式成立,两边形式也一样。那写成这个变换,我们就能猜到这个方程中等式的意义了:在直角坐标系($I$)下的y向量,在A坐标系下该如何刻画(x)呢?那再来看看这个方程的解,即$x = A^{-1}y$,而这个式子可以理解为将施加的变换“逆”着再变回去,就还原了原始位置。</p><p>普通乘法有交换律、结合律、分配律,那矩阵乘法是否也满足呢?对于交换律,矩阵乘法是不满足的,想象一下,5“瓶”酒换成“杯”可能是25,而5“杯”酒换成“瓶”,可能也就是1了,所以两个变换交换位置,得到的是不同的变换;而结合律与分配律是满足的,结合律可以看做是将变换分成“几步”走,即先进行“子变换a”,在进行“子变换b”,几步走与一步到位的结果是一致的;而分配律也是类似的思路。</p><h1><span id="xiang-si-ju-zhen">相似矩阵</span><a href="#xiang-si-ju-zhen" class="header-anchor"></a></h1><p>那何为相似矩阵呢?假设有一个矩阵A,对应$y=Ax$, 而现在有一个新的坐标系P,对应有${y}’=B{x}’$,在P坐标系下,x与y对应着$P{x}’=x$, $P{y}’=y$,代入后得到:$P{x}’=AP{y}’ = P(P^{-1}AP){x}’$,即在P坐标系下,从${x}’$ 到${y}’$的变换用矩阵$B=P^{-1}AP$来表示,这就是相似矩阵,<a href="https://spaces.ac.cn/archives/1777">即同一个线性变换在不同坐标系下的一个测量结果而已。</a></p><h1><span id="xing-lie-shi">行列式</span><a href="#xing-lie-shi" class="header-anchor"></a></h1><p>行列式是在通过<a href="https://zhuanlan.zhihu.com/p/37111386">高斯消元法解线性方程组时引入的数学工具</a>, 对应的定义为:<br>$$<br>D = \sum(-1)^t a_{1p_n}a_{2p_n}…a_{np_n}<br>$$<br>其中$t$为排列$a_{1p_n}a_{2p_n}…a_{np_n}$的逆序数,$\sum$ 为对所有可能的排列求和。<br>这个计算方式看起来好奇怪,还有一个逆序数,无法直观的理解,那怎么才能有个直观印象,让我们知道为什么这里是这么计算的呢?<br>说实话我虽然几年前在网易云课堂重新学了一遍线性代数,但是今天又基本全忘记了,所以这次,我希望找到一些更“直观”的东西。<br>如果按照矩阵就是对应坐标系的变换,也就是对应基的变换的思路,那矩阵的行列式应该是对应着变换的某种度量(实际是对应坐标系的缩放,也就是基的体积变换)。顺着这个思路,苏神的一篇博文<a href="https://kexue.fm/archives/2208">新理解矩阵5:体积=行列式</a>中,通过两者的性质相同,证明了行列式的几何意义,就是其对应的n维平行n维体的体积。<br>知道了这个,我们就可以尝试通过计算平行n维体的体积来“直观”感受一下了。<br><img src="/2021/01/12/matrix/det.png"></p><p>这里我们讨论二维空间,如上图所示,其中$\overrightarrow{A}$与$\overrightarrow{B}$分别代表两个向量,而其围成的面积S 为(O,A,I,B)四个点所围成的图形。而这个图形又可以看成两个部分的组合:一部分为与四边形(O,D,P,G)重叠的部分,另一部分为剩下的部分。而剩下的部分又可以通过$PI$切分为两个三角形:$\bigtriangleup BPI$ $\bigtriangleup API$,<br>我们分别做两个三角形的高(蓝色线)$PJ=b, PK=c$,此时我们将$\bigtriangleup BPI$ 与 $\bigtriangleup ODA$ 进行对比,两者的高都是$b$,而底的差为$c$,也就是$S\bigtriangleup ODA - S\bigtriangleup BPI = S\bigtriangleup OCM$, 类似的,我们可以求出$S\bigtriangleup OBG - S\bigtriangleup API = S\bigtriangleup OFM$,于是,我们就可以得到一个结论:<br>$$<br>S_{\square OAIB} = S_{\square ODPG} - S_{\square OCMF} = ad - bc = \begin{Vmatrix}<br>a &b \\<br>c & d<br>\end{Vmatrix} = det(A,B)<br>$$</p><p>而两个四边形的位置都是由向量在坐标轴上投影后确定的,也就是对应着行列式中的“排列相乘”,而面积不足需要被“减掉”的小面积就对应逆序数,所以完整的过程就是“通过排列求长方形的面积,然后用大面积减去小面积”。<br>以上的思路可以推广到N维空间,思想是一样的。 而理解了矩阵的行列式就是平行N维体的体积后,对应行列式的计算公式也就显得相当“直观”了。如,对行列式的某一列乘上$\alpha$,则对应的行列式的值也乘上$\alpha$,这里就是对应其中的一个基向量放大了$\alpha$倍,所以体积放大了$\alpha$倍。</p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>本文主要是参考了许多文章后,发现将矩阵看做是变换的表述这个视角下,很多相关问题都变得非常直观又容易理解,最后经过思考做的部分总结,如果对矩阵感兴趣或者总觉得不容易理解,推荐孟岩的<a href="http://blog.csdn.net/myan/article/details/647511">理解矩阵系列</a>、苏神的<a href="%5Bhttps://kexue.fm/archives/1765">新理解矩阵系列</a>、<a href="https://www.matongxue.com/madocs/247/">马同学的矩阵与行列式系列</a></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="Math" scheme="https://xv44586.github.io/categories/Math/"/>
<category term="Math" scheme="https://xv44586.github.io/tags/Math/"/>
<category term="Matrix" scheme="https://xv44586.github.io/tags/Matrix/"/>
</entry>
<entry>
<title>如何提升bert在下游任务中的性能</title>
<link href="https://xv44586.github.io/2020/11/24/fine-tune/"/>
<id>https://xv44586.github.io/2020/11/24/fine-tune/</id>
<published>2020-11-24T13:44:12.000Z</published>
<updated>2020-11-25T13:45:14.000Z</updated>
<content type="html"><中就验证了,在任务数据$D_t$ 继续进行pretraining 任务,可以提高模型的性能。<br>那如果我们除了任务数据没有别的数据时,怎么办呢?简单,任务数据肯定是相同领域的,此时直接将任务数据看作相同领域数据即可。所以,在进行下游任务之前,不妨先在任务数据上继续进行pre-training 任务继续训练LM ,之后再此基础上进行fine-tune。</p><h3><span id="si-jie-duan">四阶段</span><a href="#si-jie-duan" class="header-anchor"></a></h3><p>我们在实际工作上,任务相关的label data 较难获得,而unlabeled data 却非常多,那如何合理利用这部分数据,是不是也能提高模型在下游的性能呢?答案是:<code>也能! </code> </p><ol><li><em>在大量通用语料上训练一个LM(Pretrain);</em></li><li><em>在相同领域$D_{in_domain}$上继续训练LM(Domain transfer);</em></li><li><em>在任务相关的小数据上继续训练LM(Task transfer);</em></li><li><em>在任务相关数据上做具体任务(Fine-tune)。</em></li></ol><p>而且上述两篇论文中也给出了结论:先Domain transfer 再进行Task transfer 最后Fine-tune 性能是最好的。</p><h1><span id="ru-he-further-pre-training">如何further pre-training</span><a href="#ru-he-further-pre-training" class="header-anchor"></a></h1><h2><span id="how-to-mask">how to mask</span><a href="#how-to-mask" class="header-anchor"></a></h2><p>首先,在further pre-training时,我们应该如何进行mask 呢?不同的mask 方案是不是能起到更好的效果呢?<br>在Roberta 中提出,动态mask 方案比固定mask 方案效果更好,此外,在做Task transfer 时,由于数据通常较小,固定的mask 方案通常也容易过拟合,所以further pre-training 时,动态随机mask 方案通常比固定mask 效果更好。<br>而ERNIE 和 SpanBert 中都给出了结论,更有针对性的mask 方案可以提升下游任务的性能,那future pre-training 时是否有什么方案能更有针对性的mask 呢?<br>刘知远老师的论文<a href="http://arxiv.org/abs/2004.09733">Train No Evil: Selective Masking for Task-Guided Pre-Training</a>就提出了一种更有针对性的mask 方案<code>Selective Mask</code>,进行further pre-training 方案,该方案的整体思路是:</p><ol><li><em>在$D_t$上训练一个下游任务模型 $Model_0$;</em></li><li><em>利用$Model_0$判断token 是否是下游任务中的重要token,具体计算公式为:$S(w_i) = P(y_t|s) - P(y_t|s^{‘}_{i-1}W_i)$, 其中$s$为完整句子(序列),$s^{‘}$为一个初始化为空的buffer,每次将句子中的token 往buffer中添加,如果加入的token 对当前任务的表现与完整句子在当前任务的表现差距小于阈值,则认为该token 为重要token,并从buffer 中剔除;</em></li><li><em>利用上一步中得到的token label,训练一个二分类模型$Model_b$,来判断句子中的token 是否为重要token;</em></li><li><em>利用$Model_b$,在domain 数据上进行预测,根据预测结果进行mask ;</em></li><li><em>进行Domain transfer pre-training;</em></li><li><em>在下游任务进行Fine-tuning。</em><br>上述方案验证了更有针对性的mask 重要的token,下游任务中能得到不错的提升。综合下来,<code>Selective Mask > Dynamic Mask > Static Mask</code></li></ol><p>虽然selective mask 有提升,但是论文给出的思路太过繁琐了,本质上是判断token 在下游任务上的影响,所以这里给出一个笔者自己脑洞的一个方案:通过$Model_0$在unlabeled 的Domain data 上直接预测,然后通过不同token 下结果的熵的波动来确定token 对下游任务的影响。这个方案我没有做过实验,有兴趣的可以试试。 </p><h2><span id="when-to-stop">when to stop</span><a href="#when-to-stop" class="header-anchor"></a></h2><p>在further pretraining 时,该何时停止呢?是否训练的越久下游任务就提升的越多呢?答案是否定的。在进行Task transfer 时,应该训练多少步,论文<a href="http://arxiv.org/abs/1905.05583">How to Fine-Tune BERT for Text Classification?</a>进行了实验,最后得出的结论是<code>100k</code>步左右,下游任务上提升是最高的,这也与我自己的实验基本吻合,训练过多就会过拟合,导致下游任务上提升小甚至降低。</p><p><img src="/2020/11/24/fine-tune/step.png"></p><p>此外,由于下游任务数据量的不同,进行多少步结果是最优的也许需要实验测试。这里给出一个更快捷稳妥的方案:借鉴PET本质上也是在训练MLM 任务,我们可以先利用利用PET做fine-tuning,然后将最优模型作为预训练后的模型来进行分类任务fine-tuning,这种方案我实验后的结论是与直接进行Task transfer性能提升上相差不大。不了解PET的可以查看我之前博文<a href="https://xv44586.github.io/2020/10/25/pet/">PET-文本分类的又一种妙解</a>.</p><h2><span id="how-to-fine-tuning">how to fine-tuning</span><a href="#how-to-fine-tuning" class="header-anchor"></a></h2><p>不同的fine-tuning 方法也是影响下游任务性能的关键因素。</p><h3><span id="optimizer">optimizer</span><a href="#optimizer" class="header-anchor"></a></h3><p>关于优化方案上,Bert 的论文中建议使用与bert 预训练时一致的方案进行fine-tuning,即使用weighted decay修正后的Adam,并使用warmup策略 搭配线性衰减的学习率。不熟悉的同学可以查看我之前的博文<a href="https://xv44586.github.io/2020/08/01/optimizer-in-bert/">optimizer of bert</a></p><h3><span id="learning-rate">learning rate</span><a href="#learning-rate" class="header-anchor"></a></h3><p>不合适的learning rate可能会导致<code>灾难性遗忘</code>,通常learning rate 在$[-e^{-5}, 1e^{-4}]$之间,更大的learning rate可能就会发生灾难性遗忘,不利于优化。</p><p><img src="/2020/11/24/fine-tune/lrt.png"></p><p>此外,对transformer 逐层降低学习率也能降低发生灾难性遗忘的同时提升一些性能。</p><h3><span id="multi-task">multi-task</span><a href="#multi-task" class="header-anchor"></a></h3><p>Bert在预训练时,使用了两个task:NSP 和 MLM,那在下游任务中,增加一个辅助的任务是否能带来提升呢?答案是否定的。如我之前尝试过在分类任务的同时,增加一个相似性任务:让样本与label desc的得分高于样本与其他样本的得分,但是最终性能并没有得到提升。具体的实验过程请看博文<a href="https://xv44586.github.io/2020/09/13/classification-label-augment/">模型增强之从label下手</a>。<br>此外,论文<a href="http://arxiv.org/abs/1905.05583">How to Fine-Tune BERT for Text Classification?</a>也任务multi-task不能带来下游任务的提升。</p><h3><span id="which-layer">which layer</span><a href="#which-layer" class="header-anchor"></a></h3><p>Bert的结构上是一个12层的transformer,在做文本分类时,通常我们是直接使用最后一层的<code>[CLS]</code>来做fine-tuning,这样是最优的吗?有没有更好的方案?<br>论文<a href="http://arxiv.org/abs/1905.05583">How to Fine-Tune BERT for Text Classification?</a>中针对这个问题也做了实验,对比了不同的layer不同的抽取策略,最终结论是所有层拼接效果最好,但是与直接使用最后一层差距不大。<br><img src="/2020/11/24/fine-tune/layer.png"></p><p>而论文<a href="http://arxiv.org/abs/2008.06460">Hate Speech Detection and Racial Bias Mitigation in Social Media based on BERT model</a>中,作者通过组合多种粒度的语义信息,即将12层的<code>[CLS]</code>拼接后,送人CNN,在Hate Speech Detection 中能带来<code>8个点</code>的提升!<br><img src="/2020/11/24/fine-tuning/cnn.png"></p><p>所以在fine-tuning时,也可以想一想到底是哪种粒度的语义信息对任务更重要。</p><h1><span id="self-knowledge-distillation">Self-Knowledge Distillation</span><a href="#self-knowledge-distillation" class="header-anchor"></a></h1><p>self-knowledge distillation(自蒸馏)也是一种常用的提升下游任务的手段。做法是先在Task data上fine-tuning 一个模型,然后通过模型得到Task data 的soft labels,然后使用soft labels 代替hard label 进行fine-tuning。更多细节可以查看之前的博文<a href="https://xv44586.github.io/2020/08/31/bert-01/">Knowledge Distillation之知识迁移</a> </p><h1><span id="zhi-shi-zhu-ru">知识注入</span><a href="#zhi-shi-zhu-ru" class="header-anchor"></a></h1><p>通过注入外部知识到bert中也能提升Bert的性能,常用的方式主要有两种:</p><ol><li><em>在bert embedding 层注入:通过将外部Embedding 与Bert token-embedding 拼接(相加)进行融合,然后进行transformer一起作用下游;</em></li><li><em>在transformer的最后一层,拼接外部embedding,然后一起作用下游。</em><br>如<a href="http://arxiv.org/abs/1909.08402">Enriching BERT with Knowledge Graph Embeddings for Document Classification</a>中,通过在<br>transformer的最后一层中拼接其他信息,提高模型的性能。<br><img src="/2020/11/24/fine-tune/kg.png"></li></ol><h1><span id="shu-ju-zeng-qiang">数据增强</span><a href="#shu-ju-zeng-qiang" class="header-anchor"></a></h1><p>NLP中数据增强主要有两种方式:一种是保持语义的数据增强,一种是可能破坏语义的局部扰动增强。保持语义通常采用回译法,局部扰动的通常使用EDA,更多细节可以查看之前博文<a href="https://xv44586.github.io/2020/11/10/eda/">NLP中的数据增强</a></p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>本文总结了使用bert 时,当前主要的提升Bert 在下游任务上的性能的方法,遇到相关问题时,可以尝试一下。</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="BERT" scheme="https://xv44586.github.io/tags/BERT/"/>
</entry>
<entry>
<title>Contrastive Learning</title>
<link href="https://xv44586.github.io/2020/11/23/scl/"/>
<id>https://xv44586.github.io/2020/11/23/scl/</id>
<published>2020-11-23T13:21:36.000Z</published>
<updated>2020-11-23T15:13:38.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#dui-bi-xue-xi">对比学习</a></li><li><a href="#dui-bi-xue-xi-de-zi-jian-du-xue-xi">对比学习的自监督学习</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div>最近看了两篇关于对比学习(Contrastive Learning)的论文,觉得思路挺有意思,所以总结一下。<h1><span id="dui-bi-xue-xi">对比学习</span><a href="#dui-bi-xue-xi" class="header-anchor"></a></h1><p>不知道大家有没有小时候抽读课文的回忆,当读到不认识读字时,我们就会猜他的发音。猜的时候呢,通常就是先看看他的组成部分里有没有认识的,如果有,则读认识的部分的发音,即读<code>半边</code>, 如果拆开的部分不是独立的字,那我们就会联想一下有哪些字跟他有共同的部分,然后那些字都是如何发音的。即便到了现在,遇到不认识的字,我依然会使用这种方法去<code>读</code>. 而这背后的原理其实就是我们认为,该字(样本x)与其相似的字(距离更近的样本)具有相同的发音(label)。这样一个没有经过多少学习的<code>模型</code>却在很多时候都是奏效的。<br>而对比学习与这有着相似的含义:我们在学习表示的时候,不需要完完全全的学习样本的细节,只需要让样本相对其正样本的得分远远大于其相对负样本的得分即可:<br>$$<br>s(f(x), f(x^+)) >> s(f(x),f(x^-))<br>$$<br>其中 $f$是映射函数,也是我们的主要学习任务,$s$是一个打分函数,用来衡量样本之间的距离,最常用的如点积。<br>以上就是一个对比学习的框架,而要使用对比学习,核心问题只有两个:</p><ol><li>如何定义目标函数,上面提到的点积就是一种比较简单通用的方式,此外,还可以用triple-loss等。</li><li>如何构造正负样本,正负样本的构造,合理的正负样本,才能让学到的表示更好的作用在下游任务上。</li></ol><p>此外,如果熟悉Word2Vec的同学也会发现,其实负采样也在对比学习的范畴中,即在使用负采样训练Word2Vec 时,我们的优化目标是让正样本对之间的概率尽可能的大,而其与负样本之间的概率尽可能小,也即拉近正样本对之间的距离。其中定义的目标函数使用的是点积,而正样本的构造是通过窗口大小来选取,负样本的构造即负采样。</p><h1><span id="dui-bi-xue-xi-de-zi-jian-du-xue-xi">对比学习的自监督学习</span><a href="#dui-bi-xue-xi-de-zi-jian-du-xue-xi" class="header-anchor"></a></h1><p>目前学习表示的主流是自监督学习,那为了引入对比学习,我们就需要构造样本的正负样本,其中可以将所有非自己的样本都认为是负样本,需要拉远,那如何构造正样本呢?答案是data augmentation(数据增强)。在CV 中通常用旋转、裁剪等</p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
</entry>
<entry>
<title>跨界之阿尔滋海默病的分类竞赛</title>
<link href="https://xv44586.github.io/2020/11/21/ad-dti/"/>
<id>https://xv44586.github.io/2020/11/21/ad-dti/</id>
<published>2020-11-21T09:25:24.000Z</published>
<updated>2020-11-24T16:23:06.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#bi-sai-jie-shao">比赛介绍</a></li><li><a href="#shu-ju-te-dian">数据特点</a></li><li><a href="#si-lu">思路</a></li><li><a href="#jie-lun">结论</a></li><li><a href="#zong-jie">总结</a></li><li><a href="#guan-yu-tou-tu">关于头图</a></li></ul><!-- tocstop --></div><p>几周前受小王萌的邀请,参加了天津大学医学部组织的一场关于<a href="https://mp.weixin.qq.com/s/KlJOOvwqXkD1ANoVG2AOuw">利用DTI影像进行阿尔滋海默病的分类竞赛</a>,结果虽然只得了第四名,但是这也是第一次跨界参加比赛,所以总结一下。</p><h1><span id="bi-sai-jie-shao">比赛介绍</span><a href="#bi-sai-jie-shao" class="header-anchor"></a></h1><p>弥散磁共振影像(DTI)在<code>阿尔茨海默病(Alzheimer's disease, AD)</code>中应用广泛,从DTI影像中提取扩散参数可以用来描述白质结构的完整性,进而显示AD中脑白质的退化模式。利用机器学习的方法可以比较有效的对AD进行诊断和分类。所以比赛的内容是希望选手通过给定的18条主要的脑白质纤维束的扩散指标作为特征,建立并评估出对AD和健康人群的最优分类模型,如果有可能,进一步探索对轻度认知损害患者的预测性能。<br>说成通俗一点就是给定由18个扩散指标组成的一系列特征,然后希望选手通过这些特征来对正常人与病人进行建模,而轻度患者由于比较难区分,所以,如果可能就继续做关于病人/轻度患者/正常人的分类模型。</p><h1><span id="shu-ju-te-dian">数据特点</span><a href="#shu-ju-te-dian" class="header-anchor"></a></h1><p>本次的数据虽然是18个扩散指标,实际上拿到的是沿18条脑内主要纤维束上100个点的8种不同的指标,即每个纤维束上有8种不同指标,每个指标是按顺序固定间隔采样的100个点,合起来每个样本有$18 * 8 * 100 $个 基础特征。而样本包含三个类别才700个左右。所以此次的数据是一个样本少而基础特征特别多。<br>按每一百个点为一个集合,通过简单的统计,大多数均值方差都在0~1之间,也有几个为负值,还有几个为几百的大数。同时有部分数据为nan。</p><h1><span id="si-lu">思路</span><a href="#si-lu" class="header-anchor"></a></h1><p>基于上述特点,首先要解决的是样本数量太少的问题,其次需要解决特征太多的问题。<br>针对特征太多的问题,可以看作是高维数据,这里我们采样SVM 与 XGBoost两种方案来做,SVM在高维稀疏数据上效果相对较好,而XGBoost我们将其看作是一个特征选择器(encoder),然后在其后面接一个LR进行分类。<br>此外,特征过多我们也尝试了两种平滑方案,将每一百个点的集合进行降纬。1. 对每一百个点的集合上,每十个点进行求均值方差来代替原始特征. 2. 对全量样本按label 求取其中心点,然后求取其对三个中心点的 <cdoe>Wasserstein Distance.<br>针对样本少的问题,也尝试了两种方案:1. 利用GAN 来生成,这个方案是借鉴之前看到的一篇利用GAN 来做CONV-19 的文章,其中由于样本过少,通过GAN 进行生成新样本后,提高了模型的性能;另一个方案是互换相同label 的样本之间的特征,这个方案是由GAN 联想到的,即GAN 实际上在生成分布类似的特征,然后组合成新样本,与其通过一个模型生成相同分布然后组合,不然直接互换,这样他们一定是同分布的。<br>此外,我们还尝试了利用CNN 来提取样本特征然后进行分类。</cdoe></p><h1><span id="jie-lun">结论</span><a href="#jie-lun" class="header-anchor"></a></h1><ol><li>SVM上效果实际比XGB 略差一些,所以最后没有采样SVM。</li><li>XGB的效果最后,尤其是将其看作特征提取器,在后面接了一个分类器的模型。</li><li>平均方差平滑与WD 效果类似,最后保留了WD </li><li>GAN 的效果较差,一来是特征过多,二来是对GAN 不怎么熟,不太会调</li><li>随机互换特征效果时好时坏,感觉这也正常,毕竟特征之间是有关系的</li><li>深度学习在这种数据上基本不收敛</li></ol><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>本篇主要是总结一下跨界参加的一个脑科学比赛,出发点是觉得如果能有什么好的效果,说不定能做点贡献,实际参加后发现很多领域的问题由于各种各样的限制,实际上应用机器学习时还是有很大难度的。最后,贴一下排名吧。</p><p><img src="/2020/11/21/ad-dti/result.png"></p><h1><span id="guan-yu-tou-tu">关于头图</span><a href="#guan-yu-tou-tu" class="header-anchor"></a></h1><p>西伯利亚森林猫</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="MachineLearning" scheme="https://xv44586.github.io/categories/MachineLearning/"/>
<category term="Competition" scheme="https://xv44586.github.io/tags/Competition/"/>
</entry>
<entry>
<title>NLP中的数据增强</title>
<link href="https://xv44586.github.io/2020/11/10/eda/"/>
<id>https://xv44586.github.io/2020/11/10/eda/</id>
<published>2020-11-10T14:33:41.000Z</published>
<updated>2020-11-10T15:55:45.000Z</updated>
<content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><div class="toc"><!-- toc --><ul><li><a href="#shu-ju-zeng-qiang">数据增强</a></li><li><a href="#bao-chi-yu-yi-shu-ju-zeng-qiang">保持语义数据增强</a><ul><li><a href="#hui-yi">回译</a></li><li><a href="#sheng-cheng">生成</a></li></ul></li><li><a href="#ju-bu-rao-dong">局部扰动</a><ul><li><a href="#tong-yi-ci-ti-huan">同义词替换</a></li><li><a href="#cha-ru">插入</a></li><li><a href="#shan-chu">删除</a></li><li><a href="#hu-huan">互换</a></li></ul></li><li><a href="#zong-jie">总结</a></li></ul><!-- tocstop --></div>最近有同学问nlp中如何做data augmentation,这篇总结下目前知道的方法。<h1><span id="shu-ju-zeng-qiang">数据增强</span><a href="#shu-ju-zeng-qiang" class="header-anchor"></a></h1><p>数据增强技术已经是图像领域的标配了,如旋转、镜像、翻转等。由于图像本身的特性,通过这些操作后生成的图像虽然与原始图像不同,但其图像的内容确实基本一致的。所以可以增强模型的鲁棒性和泛化能力。<br>而在NLP领域情况确是不同的,因为NLP中改变一个词有可能变为语义完全想反的句子,比如:<code>"这好吃吧"</code> -> <code>"这好吃吗"</code>.<br>所以,NLP中数据增强主要有两种方式:一种是保持语义的数据增强,一种是可能破坏语义的局部扰动增强。</p><h1><span id="bao-chi-yu-yi-shu-ju-zeng-qiang">保持语义数据增强</span><a href="#bao-chi-yu-yi-shu-ju-zeng-qiang" class="header-anchor"></a></h1><p>保持语义的数据增强主要是构造与原句子语义一样的新句子,如回译、生成等。 </p><h2><span id="hui-yi">回译</span><a href="#hui-yi" class="header-anchor"></a></h2><p>回译即将句子从当前语种翻译至新的语种,然后再翻译回来,得到语义相同表达不同的句子。如将句子从中文翻译为英文然后再翻译回中文。可以借助各大互联网平台的免费API来完成。除此之外,还可以多翻译几组中间语种,增加其丰富性。 </p><h2><span id="sheng-cheng">生成</span><a href="#sheng-cheng" class="header-anchor"></a></h2><p>生成的方式即通过样本构建一个生成模型,生成与样本语义相同的句子。如<a href="https://arxiv.org/abs/1906.06045">Learning to Ask Unanswerable Questions for Machine Reading Comprehension</a> 就是通过生成新的问题来做SQuAD2.0. 此外,之前的文章<a href="https://xv44586.github.io/2020/08/22/qa-augmentation/">利用NLG 增强QA 任务性能</a>里也总结了通过生成问题及问题答案对来增强qa模型性能,不熟悉的可以翻看一下。</p><p>由于两种方式构造的新句子都是与原句子语义相同的句子,所以,这种方式进行数据增强表达模型偏好是:模型应对于不同表达形式的同一语义的文本具有不变性。</p><h1><span id="ju-bu-rao-dong">局部扰动</span><a href="#ju-bu-rao-dong" class="header-anchor"></a></h1><p>局部扰动主要包括同义词替换、插入、删除、互换四种操作,出自论文<a href="http://arxiv.org/abs/1901.11196">EDA: Easy Data Augmentation Techniques for Boosting Performance on<br>Text Classification Tasks</a>,因为操作简单,所以也叫<code>EDA</code>(Easy Data Augmentation)。下面分别介绍一下这四种策略。</p><h2><span id="tong-yi-ci-ti-huan">同义词替换</span><a href="#tong-yi-ci-ti-huan" class="header-anchor"></a></h2><p>从句子中随机找出1个非停用词,并求出其同义词,然后用同义词替换该词,重复n次操作</p><h2><span id="cha-ru">插入</span><a href="#cha-ru" class="header-anchor"></a></h2><p>从句子中随机找出一个非停用词,并求出其同义词,然后将同义词插入句子中的一个随机位置,重复n次操作。</p><h2><span id="shan-chu">删除</span><a href="#shan-chu" class="header-anchor"></a></h2><p>以概率p,随机删除句子中的每一个单词</p><h2><span id="hu-huan">互换</span><a href="#hu-huan" class="header-anchor"></a></h2><p>随机选择句子中的两个词,然后互换其位置,重复n次。<br>此外,论文中给出了替换删除等操作的比例$\alpha$ 与新增句子数量$n$ 的建议值:<br><img src="/2020/11/10/eda/eda.png"> </p><p>而现在我们通常都使用bert等transformer模型做下游任务,所以删除操作可以使用padding,即删除token但保留其占位,即保留其位置编码;互换操作可以选择更大的span进行;插入和同义词替换操作也可以尝试从当前句子选择一个词代替同义词等。<br>文章开头我们也提到了,对句子中的词进行改变时,很可能得到语义完全相反的句子,那上面这四种方式为何有效呢?首先,句子中引入的新词都是同义词,所以语义不会发生很大的变换,其次,论文作者通过分析发现,虽然构造的新句子变得可能都不是一个通顺的句子了,但其特征空间分布下的label并没有发散,即经过EDA变换后,原始数据一方面引入了很多噪声,扩大了数据集,同时又保持了原有的标签,因而有效的扩大了样本集的信息容量。<br>此外,上面的方式相当于对模型增加了一个正则约束,其所表达的模型偏好是:模型应该对文本的局部噪声不敏感</p><h1><span id="zong-jie">总结</span><a href="#zong-jie" class="header-anchor"></a></h1><p>以上就是当前NLP中常用的几种数据增强方案,尤其在样本不均衡及小样本任务下,数据增强往往能带来非常不错的提升。所以值得尝试。</p>]]></content>
<summary type="html">
<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" cla
</summary>
<category term="NLP" scheme="https://xv44586.github.io/categories/NLP/"/>
<category term="EDA" scheme="https://xv44586.github.io/tags/EDA/"/>
</entry>
</feed>