-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
523 lines (328 loc) · 534 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>khan's study and life blog - 学习记录站</title>
<link href="/atom.xml" rel="self"/>
<link href="https://jackyin.space/"/>
<updated>2024-10-17T07:11:10.363Z</updated>
<id>https://jackyin.space/</id>
<author>
<name>Jack Yin | INFJ</name>
</author>
<generator uri="https://hexo.io/">Hexo</generator>
<entry>
<title>ubuntu22.04 日常化</title>
<link href="https://jackyin.space/2024/10/09/ubuntu22-04-%E6%97%A5%E5%B8%B8%E5%8C%96/"/>
<id>https://jackyin.space/2024/10/09/ubuntu22-04-%E6%97%A5%E5%B8%B8%E5%8C%96/</id>
<published>2024-10-09T10:17:37.000Z</published>
<updated>2024-10-17T07:11:10.363Z</updated>
<content type="html"><![CDATA[<p>最近有在折腾一些Ubuntu日常化的东西,给电脑重新装了双系统,相比前几年,ubuntu22.04之后系统确实变得非常好用了,软件生态支持也变多了,为了更加日常化的使用折腾了一些相关软件的安装,这边做一个简单的记录。</p><a id="more"></a><h2 id="ubuntu安装卸载"><a href="#ubuntu安装卸载" class="headerlink" title="ubuntu安装卸载"></a>ubuntu安装卸载</h2><p>ubuntu的安装卸载以及分区教程还是有些麻烦的,我也是参考了b战上面一个不错的up主的教学,期间有差点把自己windows引导搞出问题,然后用驱动精灵修复了。注意计算机专业同学还是尽量用英语安装,且安装以后禁用掉软件和内核更新,在<a href="https://ysyx.oscc.cc/docs/ics-pa/0.1.html#installing-ubuntu" target="_blank" rel="noopener">一生一芯</a>中有提到,禁用方法很简单自行百度即可。<br>参考链接:<a href="https://www.bilibili.com/video/BV1554y1n7zv/?share_source=copy_web&vd_source=5a36bf224e0b99f26a95b15bd816244b" target="_blank" rel="noopener">Windows 和 Ubuntu 双系统的安装和卸载</a></p><h2 id="Easyconnect"><a href="#Easyconnect" class="headerlink" title="Easyconnect"></a>Easyconnect</h2><p>目前华科的VPN是支持ubuntu系统的,这确实提供了学生在连接校园网和服务器上的便利。当然安装可能会打不开的小问题。<br>参考链接:<a href="https://yunwei365.blog.csdn.net/article/details/114263954?spm=1001.2101.3001.6650.3&utm_medium=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3-114263954-blog-108071231.t0_edu_mix&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2~default~CTRLIST~Rate-3-114263954-blog-108071231.t0_edu_mix&utm_relevant_index=4" target="_blank" rel="noopener">解决方案,需下载的软件内有可用的百度云网址</a></p><h2 id="VPN-Clash"><a href="#VPN-Clash" class="headerlink" title="VPN-Clash"></a>VPN-Clash</h2><p>找了一个不错的ubuntu支持的带有客户端的clash,亲测可用,导入任意可用的订阅链接就行。<br>参考链接:<a href="https://devpn.github.io/" target="_blank" rel="noopener">Devpn</a></p><h2 id="QQ"><a href="#QQ" class="headerlink" title="QQ"></a>QQ</h2><p>QQ 9可以直接在ubuntu22.04上直接使用。<br>参考链接:<a href="https://im.qq.com/linuxqq/index.shtml" target="_blank" rel="noopener">QQ 9官网</a></p><h2 id="QQ-音乐"><a href="#QQ-音乐" class="headerlink" title="QQ 音乐"></a>QQ 音乐</h2><p>官方目前已经有可支持的版本,再也不用担心冲了会员用不了啦!偶尔会有闪退问题,但是不影响。<br>参考链接:<a href="https://y.qq.com/download/download.html" target="_blank" rel="noopener">QQ音乐官网</a></p><h2 id="WPS"><a href="#WPS" class="headerlink" title="WPS"></a>WPS</h2><p>WPS是在linux系统下可支持office三件套的工具,有一些中文汉化的问题,解决方案跟下面一样,<strong>不过实际下载之后发现,在<code>/opt/kingsoft/wps-office/office6/mui</code>下面本来就有语言包,只需要像下面的链接一样,移除下载后的其他语言安装包,只保留<code>default zh_CN</code>就能直接汉化</strong>,虽然在选择字体上好像还有点问题没解决,但是算是能用了至少。<br>参考链接:<a href="https://blog.csdn.net/qq_34358193/article/details/132079878" target="_blank" rel="noopener">参考链接</a></p><h2 id="中文输入法"><a href="#中文输入法" class="headerlink" title="中文输入法"></a>中文输入法</h2><p>在b战找到了一个简单不错的中文输入法——rime输入法,感觉非常好用,安装后如果没有效果记得重启下电脑。<br>参考链接:<a href="https://www.bilibili.com/video/BV1Ks421A7R8/?share_source=copy_web&vd_source=5a36bf224e0b99f26a95b15bd816244b" target="_blank" rel="noopener">rime输入法安装</a></p><h2 id="微信"><a href="#微信" class="headerlink" title="微信"></a>微信</h2><p>微信的安装好像有很多版本了,官方也有在做统一的OS支持,这里我用的可能不一定是最好的版本,但是可以支持文字和图片截图,还能打开小程序和公众号,只是看不了朋友圈,安装过程没有踩坑。<br>参考链接:<a href="https://zhuanlan.zhihu.com/p/690854988" target="_blank" rel="noopener">2024如何在Ubuntu上安装原生微信wechat weixin</a></p><h2 id="浏览器"><a href="#浏览器" class="headerlink" title="浏览器"></a>浏览器</h2><p>Edge & Chrome 目前都支持linux的相关系统了,不用担心使用问题,编程相关的软件就更方便了。</p><h2 id="End"><a href="#End" class="headerlink" title="End"></a>End</h2><p>一生一芯可以启动了!</p><blockquote class="twitter-tweet"><p lang="en" dir="ltr">It's time to use ubuntu now😍 <a href="https://t.co/BSuh7xTN0M" target="_blank" rel="noopener">pic.twitter.com/BSuh7xTN0M</a></p>— kehan yin (@jack_kehan) <a href="https://twitter.com/jack_kehan/status/1834059136491024780?ref_src=twsrc%5Etfw" target="_blank" rel="noopener">September 12, 2024</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>]]></content>
<summary type="html">
<p>最近有在折腾一些Ubuntu日常化的东西,给电脑重新装了双系统,相比前几年,ubuntu22.04之后系统确实变得非常好用了,软件生态支持也变多了,为了更加日常化的使用折腾了一些相关软件的安装,这边做一个简单的记录。</p>
</summary>
<category term="linux" scheme="https://jackyin.space/tags/linux/"/>
</entry>
<entry>
<title>Dynamicgo 支持 Protobuf 动态反射</title>
<link href="https://jackyin.space/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/"/>
<id>https://jackyin.space/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/</id>
<published>2023-12-27T08:57:00.000Z</published>
<updated>2024-10-09T08:43:45.180Z</updated>
<content type="html"><![CDATA[<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Dynamicgo 是字节跳动自研的高性能 Golang RPC 编解码基础库,能在动态处理 RPC 数据(不依赖代码生成)的同时保证高性能,主要用于实现高性能 RPC 动态代理场景(见 <a href="https://mp.weixin.qq.com/s/KPfD5qp70uup6_ZI9UdYww" target="_blank" rel="noopener">dynamicgo 介绍</a>)。<br>Protobuf 是一种跨平台、可扩展的序列化数据传输协议,该协议序列化压缩特性使其具有优秀的传输速率,在常规静态 RPC 微服务场景中已经得到了广泛的应用。但是对于上述特殊的动态代理场景,我们调研发现目前业界主流的 Protobuf 协议基础库并不能满足我们的需求:</p><ul><li><a href="https://github.com/protocolbuffers/protobuf-go" target="_blank" rel="noopener">google.golang.org/protobuf</a>:Protobuf 官方源码支持协议转换和字段动态反射。实现过程依赖于反射完整的中间结构体 Message 对象来进行管理,使用过程中带来了很多不必要字段的数据性能开销,并且在处理多层嵌套数据时操作较为复杂,不支持内存字符串 io 流 IDL 解析。</li><li><a href="https://github.com/jhump/protoreflect" target="_blank" rel="noopener">github.com/jhump/protoreflect</a>:Protobuf 动态反射第三方库可支持文件和内存字符串 io 流 IDL 解析,适合频繁泛化调用,协议转换过程与官方源码一致,均未实现 inplace 转换,且内部实现存在Go版本兼容性问题。</li><li><a href="https://github.com/cloudwego/fastpb" target="_blank" rel="noopener">github.com/cloudwego/fastpb</a>:Protobuf 快速序列化第三方库,通过静态代码方式读写消息结构体,不支持协议转换和动态 IDL 解析。<br>因此如何设计自研一个功能完备、高性能、可扩展的 Protobuf 协议动态代理基础库是十分有必要的。<br><a href="https://github.com/khan-yin" target="_blank" rel="noopener">@khan-yin</a>和<a href="https://github.com/iStitches" target="_blank" rel="noopener">@iStitches</a>两位同学经过对 Protobuf 协议源码机制的深入学习,设计了高性能 Protobuf 协议动态泛化调用链路,能满足绝大多数 Protobuf 动态代理场景,并且性能优于官方实现,目前 PR<a href="https://github.com/cloudwego/dynamicgo/pull/37" target="_blank" rel="noopener">#37</a> 已经合入代码仓库。<a id="more"></a><h2 id="Protobuf-设计思想"><a href="#Protobuf-设计思想" class="headerlink" title="Protobuf 设计思想"></a>Protobuf 设计思想</h2>由于 Protobuf 协议编码格式细节较为复杂,在介绍链路设计之前我们有必要先了解一下 Protobuf 协议的设计思想,后续的各项设计都将在严格遵守该协议规范的基础上进行改进。</li></ul><h3 id="官方源码链路思想"><a href="#官方源码链路思想" class="headerlink" title="官方源码链路思想"></a>官方源码链路思想</h3><p>Protobuf 源码链路过程主要涉及三类数据类型之间的转换,以 Message 对象进行管理,自定义 Value 类实现反射和底层存储,用 Message 对象能有效地进行管理和迁移,但也带来了许多反射和序列化开销。<br><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/1.jpg"></p><h3 id="Protobuf编码格式"><a href="#Protobuf编码格式" class="headerlink" title="Protobuf编码格式"></a>Protobuf编码格式</h3><p>Protobuf 编码字段格式大体上遵循 TLV(Tag,Length,Value) 的结构,但针对具体的类型存在一些编码差异,这里我们将较为全面的给出常见类型的编码模式。</p><h4 id="Message-Field"><a href="#Message-Field" class="headerlink" title="Message Field"></a>Message Field</h4><p>Protobuf 的接口必定包裹在一个 Message 类型下,因此无论是 request 还是 response 最终都会包裹成一个 Message 对象,那么 Field 则是 Message 的一个基本单元,任何类型的字段都遵循这样的 TLV 结构:<br><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/2.jpg"></p><ul><li>Tag 由字段编号 Field_number 和 wiretype 两部分决定,对位运算后的结果进行 varint 压缩,得到压缩后的字节数组作为字段的 Tag。</li><li>wiretype 的值则表示的是 value 部分的编码方式,可以帮助我们清楚如何对 value 的读取和写入。<strong>到目前为止,wiretype只有 VarintType,Fixed32Type,Fixed64Type,BytesType 四类编码方式。</strong>其中VarintType 类型会被压缩后再编码,属于 Fixed32Type 和 Fixed64Typ 固定长度类型则分别占用4字节和8字节,而属于不定长编码类型 BytesType 的则会编码计算 value 部分的 ByteLen 再拼接 value 。</li><li>ByteLen 用于编码表示 value 部分所占的字节长度,同样我们的 bytelen 的值也是经过 varint 压缩后得到的,但bytelen并不是每个字段都会带有,只有不定长编码类型 BytesType 才会编码 bytelen 。<strong>ByteLen 由于其不定长特性,计算过程在序列化过程中是需要先预先分配空间,记录位置,等写完内部字段以后再回过头来填充。</strong></li><li>Value 部分是根据 wiretype 进行对应的编码,字段的 value 可能存在嵌套的 T(L)V 结构,如字段是 Message,Map等情况。</li></ul><h4 id="Message"><a href="#Message" class="headerlink" title="Message"></a>Message</h4><p>关于 Message 本身的编码是与上面提到的字段编码一致的,也就是说遇到一个 Message 字段时,我们会先编码 Message 字段本身的 Tag 和 bytelen,然后再来逐个编码 Message 里面的每个字段。<strong>但需要提醒注意的是,在最外层不存在包裹整个 Message 的 Tag 和 bytelen 前缀,只有每个字段的 TLV 拼接。</strong></p><h4 id="List"><a href="#List" class="headerlink" title="List"></a>List</h4><p>list字段比较特殊,为了节省存储空间,根据list元素的类型分别采用不同的编码模式。</p><ul><li>Packed List Mode<br>如果list的元素本身属于 VarintType/Fixed32Type/Fixed64Type 编码格式,那么将采用 packed 模式编码整个 List ,在这种模式下的 list 是有 bytelen 的。Protobuf3 默认对这些类型启用 packed。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/3.jpg"></p><ul><li>UnPacked List Mode<br>当 list 元素属于 BytesType 编码格式时,list 将使用 unpacked 模式,直接编码每一个元素的 TLV,这里的 V 可能是嵌套的如List模式,<strong>那么 unpacked 模式下所有元素的 tag 都是相同的,list 字段的结束标志为与下一个 TLV 字段编码不同或者到达 buf 末尾。</strong></li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/4.jpg"></p><h4 id="Map"><a href="#Map" class="headerlink" title="Map"></a>Map</h4><p>Map 编码模式与 unpacked list 相同,根据官方设计思想,Map 的每个 KV 键值对其实本质上就是一个 Message,固定 key 的 Field_number 是1,value 的 Field_number 是2,那么 Map 的编码模式就和 <code>List<Message></code> 一致了。</p><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/5.jpg"></p><h4 id="源码-Descriptor-细节"><a href="#源码-Descriptor-细节" class="headerlink" title="源码 Descriptor 细节"></a>源码 Descriptor 细节</h4><p>这里主要介绍一下源码的 descriptor 设计上的一些需要注意的细节。</p><ul><li>Service 接口由 ServiceDescriptor 来描述,ServiceDescriptor 当中可以拿到每个 rpc 函数的 MethodDescriptor。</li><li>MethodDescriptor 中 Input() 和 output() 两个函数返回值均为 MessageDescriptor 分别表示 request 和 response 。</li><li>MessageDescriptor 专门用来描述一个 Message 对象(也可能是一个 MapEntry ),可以通过 Fields() 找到每个字段的 FieldDescriptor 。</li><li>FieldDescriptor 则兼容所有类型的描述。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/6.jpg"></p><h2 id="动态反射"><a href="#动态反射" class="headerlink" title="动态反射"></a>动态反射</h2><p>针对 Protobuf 的反射使用场景,我们归纳出以下需求:</p><ul><li>具有完整的结构自描述和具体类型反射功能,兼容 scalar 类型以及复杂嵌套的 MESSAGE/LIST/MAP 结构。</li><li>支持字节流模式下的对任意局部进行动态数据修改与遍历。</li><li>保证数据可并发读。<br>这里我们借助 Go reflect 的设计思想,把通过 IDL 解析得到的准静态类型描述(只需跟随 IDL 更新一次)TypeDescriptor 和 原始数据单元 Node 打包成一个完全自描述的结构—— Value,提供一套完整的反射 API。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/7.jpg"></p><p><strong>IDL 静态文件 parse 过程:</strong><br>为了提供文件流和内存字符串 io 流的 idl 文件解析,同时保证保证 go 版本兼容性,我们利用<a href="https://pkg.go.dev/github.com/jhump/protoreflect@v1.8.2" target="_blank" rel="noopener">protoreflect@v1.8.2</a>解析结果完成按需构造。从实现原理上来看,与高版本 <code>protoreflect</code> 利用<a href="https://pkg.go.dev/github.com/bufbuild/protocompile" target="_blank" rel="noopener">protocompile</a>对原始链路再 make 出源码的 warp 版本一致,更好的实现或许是处理利用 protoreflect 中的 ast 语法树构造。</p><h3 id="Descriptor设计"><a href="#Descriptor设计" class="headerlink" title="Descriptor设计"></a>Descriptor设计</h3><p>Descriptor 的设计原理基本尽可能与源码保持一致,但为了更好的自反射性,我们抽象了一个 TypeDescriptor 来表示更细粒度的类型。</p><h4 id="FieldDescriptor"><a href="#FieldDescriptor" class="headerlink" title="FieldDescriptor"></a>FieldDescriptor</h4><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> FieldDescriptor <span class="hljs-keyword">struct</span> {<br> kind ProtoKind <span class="hljs-comment">// the same value with protobuf descriptor</span><br> id FieldNumber<br> name <span class="hljs-keyword">string</span><br> jsonName <span class="hljs-keyword">string</span><br> typ *TypeDescriptor<br>}<br></code></pre></td></tr></table></figure><ul><li><code>FieldDescriptor</code>: 设计上希望变量和函数作用与源码 FieldDescriptor 基本一致,增加<code>*TypeDescriptor</code>可以更细粒度的反应类型以及对 FieldDescriptor 的 API 实现。</li><li><code>kind</code>:与源码 kind 功能一致,LIST 情况下 kind 是列表元素的 kind 类型,MAP 和 MESSAGE 情况下都为 messagekind。</li></ul><h4 id="TypeDescriptor"><a href="#TypeDescriptor" class="headerlink" title="TypeDescriptor"></a>TypeDescriptor</h4><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> TypeDescriptor <span class="hljs-keyword">struct</span> {<br> baseId FieldNumber <span class="hljs-comment">// for LIST/MAP to write field tag by baseId</span><br> typ Type<br> name <span class="hljs-keyword">string</span><br> key *TypeDescriptor<br> elem *TypeDescriptor<br> msg *MessageDescriptor <span class="hljs-comment">// for message, list+message element and map key-value entry</span><br>}<br></code></pre></td></tr></table></figure><ul><li><code>baseId</code>:因为对于 LIST/MAP 类型的编码特殊性,如在 unpack 模式下,每一个元素都需要编写 Tag,我们必须在构造时针对 LIST/MAP 提供 fieldnumber,来保证读取和写入的自反射性。</li><li><code>msg</code>:这里的 msg 不是仅 Message 类型独有,主要是方便 J2P 部分对于 List 和裁剪场景中 map 获取可能存在 value 内部字段缺失的 MapEntry 的 MassageDescriptor(在源码的设计理念当中 MAP 的元素被认为是一个含有 key 和 value 两个字段的 message )的时候能直接利用 TypeDescriptor 进入下一层嵌套。</li><li><code>typ</code>:这里的 Type 是对源码的 FieldDescriptor 更细粒度的表示,即对 LIST/MAP 做了单独定义</li></ul><h4 id="MessageDescriptor"><a href="#MessageDescriptor" class="headerlink" title="MessageDescriptor"></a>MessageDescriptor</h4><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> MessageDescriptor <span class="hljs-keyword">struct</span> {<br> baseId FieldNumber<br> name <span class="hljs-keyword">string</span><br> ids FieldNumberMap <span class="hljs-comment">// store by tire tree </span><br> names FieldNameMap <span class="hljs-comment">// store name and jsonName for FieldDescriptor</span><br>}<br></code></pre></td></tr></table></figure><ul><li><code>MessageDescriptor</code>: 利用 Tire 树结构实现更高性能的字段 id 和字段 name 的存储和查找。</li></ul><h3 id="数据存储设计"><a href="#数据存储设计" class="headerlink" title="数据存储设计"></a>数据存储设计</h3><p>从协议本身的 TLV 嵌套思想出发,我们利用字节流的编码格式,建立健壮的自反射性结构体处理任意类型的解析。</p><h4 id="Node结构"><a href="#Node结构" class="headerlink" title="Node结构"></a>Node结构</h4><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Node <span class="hljs-keyword">struct</span> {<br> t proto.Type <span class="hljs-comment">// node type</span><br> et proto.Type <span class="hljs-comment">// for map value or list element type</span><br> kt proto.Type <span class="hljs-comment">// for map key type</span><br> v unsafe.Pointer<br> l <span class="hljs-keyword">int</span> <span class="hljs-comment">// ptr len</span><br> size <span class="hljs-keyword">int</span> <span class="hljs-comment">// only for MAP/LIST element counts</span><br>}<br></code></pre></td></tr></table></figure><p>具体的存储规则如下:</p><ul><li>基本类型 Node 表示:指针 v 的起始位置不包含 tag,指向 (L)V,t = 具体类型。</li><li>MESSAGE 类型:指针 v 的起始位置不包含 tag,指向 (L)V,如果是 root 节点,那么 v 的起始位置本身没有前缀的 L,直接指向了 V 即第一个字段的 tag 上,而其余子结构体都包含前缀 L。</li><li>LIST类型:为了兼容 List 的两种模式和自反射的完整性,我们必须包含 list 的完整片段和 tag。因此 List 类型的节点,<strong>v 指向 list 的 tag 上,即如果是 packed 模式就是 list 的 tag 上,如果是 unpacked 则在第一个元素的 tag 上。</strong></li><li>MAP类型:Map 的指针 v 也指向了第一个 pair 的 tag 位置。</li><li>UNKNOWN类型Node表示:无法解析的<strong>合理字段</strong>,Node 会将 TLV 完整存储,多个相同字段 Id 的会存到同一节点,缺点是内部的子节点无法构建,同官方源码unknownFields原理一致。</li><li>ERROR类型Node表示:在 setnotfound 中,若片段设计与上述规则一致则可正确构造插入节点。 </li></ul><p>虽然 MAP/LIST 的父级 Node 存储有些变化,但是其子元素节点都是基本类型 / MESSAGE,所以叶子节点存储格式都是基本的 (L)V,这也便于序列化和数据基本单位的原子操作。</p><h4 id="Value结构"><a href="#Value结构" class="headerlink" title="Value结构"></a>Value结构</h4><p>value 的结构本身是对 Node 的封装,将 Node 与相应的 descriptor 封装起来,但不同于 thrift,在 Protobuf 当中由于片段无法完全自解析出具体类型,之后的涉及到具体编码的部分操作不能脱离 descriptor,部分 API 实现只能 Value 类作为调用单位。</p><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> Value <span class="hljs-keyword">struct</span> {<br> Node<br> Desc *proto.TypeDescriptor<br> IsRoot <span class="hljs-keyword">bool</span><br>}<br></code></pre></td></tr></table></figure><p>由于从 rpc 接口解析后我们直接得到了对应的 TypeDescriptor,再加上 root 节点本身没有前缀TL的独特编码结构,我们通过设置<code>IsRoot</code>标记来区分 root 节点和其余节点,实现 Value 结构的 Descriptor 统一。</p><h2 id="数据编排"><a href="#数据编排" class="headerlink" title="数据编排"></a>数据编排</h2><p>不同于源码 Message 对象数据动态管理的思想,我们设计了更高效的动态管理方式。我们借助 DOM (Document Object Model)思想,将原始字节流数据层层包裹的结构,抽象成多层嵌套的 BTree 结构,实现对数据的定位,切分,裁剪等操作的 inplace 处理。</p><h3 id="Path与PathNode"><a href="#Path与PathNode" class="headerlink" title="Path与PathNode"></a>Path与PathNode</h3><p>为了准确描述 DOM 中数据节点之间的嵌套关系,我们设计了 Path 结构,在 Path 的基础上,我们组合对应的数据单元 Node,然后再通过一个 Next 数组动态存储子节点,便可以组装成一个类似于 BTree 的泛型单元结构。</p><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-comment">// Path represents the relative position of a sub node in a complex parent node</span><br><span class="hljs-keyword">type</span> Path <span class="hljs-keyword">struct</span> {<br> t PathType <span class="hljs-comment">// 类似div标签的类型,用来区分field,map,list元素,帮助判定父级嵌套属于什么类型结构</span><br> v unsafe.Pointer <span class="hljs-comment">// PathStrKey, PathFieldName类型,存储的是Key/FieldName的字符串指针</span><br> l <span class="hljs-keyword">int</span> <br> <span class="hljs-comment">// PathIndex类型,表示LIST的下标</span><br> <span class="hljs-comment">// PathIntKey类型,表示MAPKey的数值</span><br> <span class="hljs-comment">// PathFieldId类型,表示字段的id</span><br>}<br><br>pathes []Path : 合理正确的Path数组,可以定位到嵌套复杂类型里具体的key/index的位置<br><br><br><span class="hljs-keyword">type</span> PathNode <span class="hljs-keyword">struct</span> { <br> Path <span class="hljs-comment">// DOM tree中用于定位当前Node的位置,并包含FieldId/FieldName/Key/index信息</span><br> Node <span class="hljs-comment">// 存储了复杂嵌套关系中该位置对应的具体bytes片段 </span><br> Next []PathNode <span class="hljs-comment">// 下层嵌套的Node节点,基本类型下层Next为空</span><br> }<br></code></pre></td></tr></table></figure><h3 id="构建DOM-Tree"><a href="#构建DOM-Tree" class="headerlink" title="构建DOM Tree"></a>构建DOM Tree</h3><p>构建 DOM 支持懒加载和全加载,在懒加载模式下 LIST/MAP 的 Node 当中 size 不会同步计算,而全加载在构造叶子节点的同时顺便更新了 size,构造后的节点都将遵循上述存储规则,具有自反射性和结构完整性。</p><h3 id="查找字段"><a href="#查找字段" class="headerlink" title="查找字段"></a>查找字段</h3><p>支持任意Node查找,查找函数设计了三个外部API:GetByPath,GetByPathWithAddress,GetMany。</p><ul><li><code>GetByPath</code>:返回查找出来的 Value ,查找失败返回 ERROR 类型的节点。</li><li><code>GetByPathWithAddress</code>:返回Value和当前调用节点到查找节点过程中每个 Path 嵌套层的 tag 位置的偏移量。 <code>[]address</code> 与 <code>[]Path</code> 个数对应,若调用节点为 root 节点,那么可记录到 buf 首地址的偏移量。</li><li><code>GetMany</code>:传入当前嵌套层下合理的 <code>[]PathNode</code> 的 Path 部分,直接返回构造出来多个 Node,得到完整的<code>[]PathNode</code>,可继续用于 <code>MarshalMany</code></li></ul><p><strong>设计思路:</strong><br>查找过程是根据传入的 <code>[]Path</code> 来循环遍历查找每一层 Path 嵌套里面对应的位置,根据嵌套的 Path 类型(fieldId,mapKey,listIndex),调用对应的 search 函数。<strong>不同于源码翻译思路,由于 Node 的自反射性设计,我们可以直接实现字节流定位,无需依赖 Descriptor 查找,并跳过不必要的字段翻译。</strong>构造最终返回的 Node 时,根据具体类型看是否需要去除 tag 即可,返回的 <code>[]address</code> 刚好在 search 过程中完成了每一层 Path 的 tag 偏移量记录。</p><h3 id="动态插入-删除"><a href="#动态插入-删除" class="headerlink" title="动态插入/删除"></a>动态插入/删除</h3><p>新增数据思想采用尾插法,保证 pack/unpack 数据的统一性,完成插入操作后需要更新嵌套层 bytelen。</p><ul><li>SetByPath:只支持 root 节点调用,保证 UpdateByLen 更新的完整和正确性。</li><li>SetMany:可支持局部节点插入。</li><li>UnsetByPath:只支持 root 节点调用,思想同插入字段,即找到对应的片段后直接将片段置空,然后更新updatebytelen。</li></ul><p><strong>Updatebytelen细节:</strong> </p><ul><li>计算插入完成后新的长度与原长度的差值,存下当前片段增加或者减少的diffLen。</li><li>从里向外逐步更新 <code>[]Path</code> 数组中存在 bytelen 的嵌套层(只有packed list和message)的 bytelen 字节数组。</li><li>更新规则:先 readTag,然后再 readLength,得到解 varint 压缩后的具体 bytelen 数值,计算 newlen = bytelen + diffLen,计算 newbytelen 压缩后的字节长度与原 bytelen 长度的差值 sublen,并累计diffLen += sublen。</li><li>指针向前移动到下一个 path 和 address。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/8.jpg"></p><h3 id="DOM序列化"><a href="#DOM序列化" class="headerlink" title="DOM序列化"></a>DOM序列化</h3><ul><li>Marshal:建好 PathNode 后,可遍历拼接 DOM 的所有叶子节点片段,Tag 部分会通过 Path 类型和 Node 类型进行补全,bytelen 根据实际遍历节点进行更新。</li><li>MarshalTo:针对数据裁剪场景,该设计方案具有很好的扩展性,可直接比对新旧 descriptor 中共有的字段 id,对字节流一次性拼接写入,无需依赖中间结构体,可支持多层嵌套字段缺失以及 LIST/MAP 内部元素字段缺失。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/9.jpg"></p><h2 id="协议转换"><a href="#协议转换" class="headerlink" title="协议转换"></a>协议转换</h2><h3 id="ProtoBuf——-gt-JSON"><a href="#ProtoBuf——-gt-JSON" class="headerlink" title="ProtoBuf——>JSON"></a>ProtoBuf——>JSON</h3><p>Protobuf->JSON 协议转换的过程可以理解为逐字节解析 ProtoBuf,并结合 Descriptor 类型编码为 JSON 到输出字节流,整个过程是 in-place 进行的,并且结合内存池技术,仅需为输出字节流分配一次内存即可。</p><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/10.jpg"></p><p>ProtoBuf——>JSON 的转换过程如下:</p><ol><li>根据输入的 Descriptor 指针类型区分,若为 Singular(string/number/bool/Enum) 类型,跳转到第5步开始编码;</li><li>按照 Message([Tag] [Length] [TLV][TLV][TLV]….)编码格式对输入字节流执行 varint解码,将Tag解析为 fieldId(字段ID)、wireType(字段wiretype类型);</li><li>根据第2步解析的 fieldId 确定字段 FieldDescriptor,并编码字段名 key 作为 jsonKey 到输出字节流;</li><li>根据 FieldDescriptor 确定字段类型(Singular/Message/List/Map),选择不同编码方法编码 jsonValue 到输出字节流;</li><li>如果是 Singular 类型,直接编码到输出字节流;</li><li>其它类型递归处理内部元素,确定子元素 Singular 类型进行编码,写入输出字节流中;</li><li>及时输入字节流读取位置和输出字节流写入位置,跳回2循环处理,直到读完输入字节流。</li></ol><h3 id="JSON——-gt-ProtoBuf"><a href="#JSON——-gt-ProtoBuf" class="headerlink" title="JSON——>ProtoBuf"></a>JSON——>ProtoBuf</h3><p>协议转换过程中借助 JSON 状态机原理和 <a href="https://github.com/bytedance/sonic/blob/main/ast/visitor.go#L36" target="_blank" rel="noopener">sonic</a> 思想,设计 UserNodeStack 实现了接口 Onxxx(OnBool、OnString、OnInt64….)方法达到编码 ProtoBuf 的目标,实现 in-place 遍历 JSON 转换。</p><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/11.jpg"></p><h4 id="VisitorUserNode-结构"><a href="#VisitorUserNode-结构" class="headerlink" title="VisitorUserNode 结构"></a>VisitorUserNode 结构</h4><p>因为在编码 Protobuf 格式的 Mesage/UnpackedList/Map 类型时需要对字段总长度回写,并且在解析复杂类型(Message/Map/List)的子元素时需要依赖复杂类型 Descriptor 来获取子元素 Descriptor,所以需要 VisitorUserNode 结构来保存解析 json 时的中间数据。</p><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> VisitorUserNode <span class="hljs-keyword">struct</span> {<br> stk []VisitorUserNodeStack<br> sp <span class="hljs-keyword">uint8</span><br> p *binary.BinaryProtocol<br> globalFieldDesc *proto.FieldDescriptor<br>}<br></code></pre></td></tr></table></figure><ul><li>stk:记录解析时中间变量的栈结构,在解析 Message 类型时记录 MessageDescriptor、PrefixLen;在解析 Map 类型时记录 FieldDescriptor、PairPrefixLen;在解析 List 类型时记录 FieldDescriptor、PrefixListLen;</li><li>sp:当前所处栈的层级;</li><li>p:输出字节流;</li><li>globalFieldDesc:每当解析完 MessageField 的 jsonKey 值,保存该字段 Descriptor 值;</li></ul><h4 id="VisitorUserNodeStack-结构"><a href="#VisitorUserNodeStack-结构" class="headerlink" title="VisitorUserNodeStack 结构"></a>VisitorUserNodeStack 结构</h4><p>记录解析时字段 Descriptor、回写长度的起始地址 PrefixLenPos 的栈结构。</p><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> VisitorUserNodeStack <span class="hljs-keyword">struct</span> {<br> typ <span class="hljs-keyword">uint8</span><br> state visitorUserNodeState<br>}<br></code></pre></td></tr></table></figure><ul><li>typ:当前字段的类型,取值有对象类型(objStkType)、数组类型(arrStkType)、哈希类型(mapStkType);</li><li>state:存储详细的数据值;</li></ul><h4 id="visitorUserNodeState-结构"><a href="#visitorUserNodeState-结构" class="headerlink" title="visitorUserNodeState 结构"></a>visitorUserNodeState 结构</h4><figure class="highlight go"><table><tr><td class="code"><pre><code class="hljs go"><span class="hljs-keyword">type</span> visitorUserNodeState <span class="hljs-keyword">struct</span> {<br> msgDesc *proto.MessageDescriptor<br> fieldDesc *proto.FieldDescriptor<br> lenPos <span class="hljs-keyword">int</span><br>}<br></code></pre></td></tr></table></figure><ul><li>msgDesc:记录 root 层的动态类型描述 MessageDescriptor;</li><li>fieldDesc:记录父级元素(Message/Map/List)的动态类型描述 FieldDescriptor;</li><li>lenPos:记录需要回写 PrefixLen 的位置;</li></ul><h4 id="协议转换过程"><a href="#协议转换过程" class="headerlink" title="协议转换过程"></a>协议转换过程</h4><p>JSON——>ProtoBuf 的转换过程如下:</p><ol><li>从输入字节流中读取一个 json 值,并判断其具体类型(<code>object/array/string/float/int/bool/null</code>);</li><li>如果是 object 类型,可能对应 ProtoBuf MapType/MessageType,sonic 会按照 <code>OnObjectBegin()->OnObjectKey()->decodeValue()...</code> 顺序处理输入字节流</li></ol><ul><li><code>OnObjectBegin()</code>阶段解析具体的动态类型描述 FieldDescriptor 并压栈;</li><li><code>OnObjectKey()</code> 阶段解析 jsonKey 并以 ProtoBuf 格式编码 Tag、Length 到输出字节流;</li><li><code>decodeValue()</code>阶段递归解析子元素并以 ProtoBuf 格式编码 Value 部分到输出字节流,若子类型为复杂类型(Message/Map),会递归执行第 2 步;若子类型为复杂类型(List),会递归执行第 3 步。</li></ul><ol start="3"><li>如果是 array 类型,对应 ProtoBuf PackedList/UnpackedList,sonic 会按照 <code>OnObjectBegin()->OnObjectKey()->OnArrayBegin()->decodeValue()->OnArrayEnd()...</code> 顺序处理输入字节流</li></ol><ul><li><code>OnObjectBegin()</code>阶段处理解析 List 字段对应动态类型描述 FieldDescriptor 并压栈;</li><li><code>OnObjectKey()</code>阶段解析 List 下子元素的动态类型描述 FieldDescriptor 并压栈;</li><li><code>OnArrayBegin()</code>阶段将 PackedList 类型的 Tag、Length 编码到输出字节流;</li><li><code>decodeValue()</code>阶段循环处理子元素,按照子元素类型编码到输出流,若子元素为复杂类型(Message),会跳转到第 2 步递归执行。</li></ul><ol start="4"><li>在结束处理某字段数据后执行 <code>onValueEnd()、OnArrayEnd()、OnObjectEnd()</code>,获取栈顶 <code>lenPos</code> 数据,对字段长度部分回写并退栈。</li><li>更新输入和输出字节流位置,跳回第 1 步循环处理,直到处理完输入流数据。</li></ol><h2 id="性能测试"><a href="#性能测试" class="headerlink" title="性能测试"></a>性能测试</h2><p>构造与 Thrift 性能测试基本相同的<a href="https://github.com/cloudwego/dynamicgo/blob/main/testdata/idl/baseline.proto" target="_blank" rel="noopener">baseline.proto</a> 文件,定义了对应的简单( Small )、复杂( Medium )、简单缺失( SmallPartial )、复杂缺失( MediumPartial ) 两个对应子集,并用 kitex 命令生成了对应的 baseline.pb.go 。 主要与 Protobuf-Go 官方源码进行比较,部分测试与 kitex-fast 也进行了比较,测试环境如下:</p><ul><li>OS:Windows 11 Pro Version 23H2</li><li>GOARCH: amd64</li><li>CPU: 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz</li><li>Go VERSION:1.20.5</li></ul><h3 id="反射"><a href="#反射" class="headerlink" title="反射"></a>反射</h3><ul><li>图中列举了 DOM 常用操作的性能,测试细节与 thrift 相同。</li><li>MarshalTo 方法:相比 ProtobufGo 提升随着数据规模的增大趋势越明显,ns/op 开销约为源码方法的0.29 ~ 0.32。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/12.png"></p><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/13.png"></p><h3 id="字段Get-Set定量测试"><a href="#字段Get-Set定量测试" class="headerlink" title="字段Get/Set定量测试"></a>字段Get/Set定量测试</h3><ul><li>factor 用于修改从上到下扫描 proto 文件字段获取比率。</li><li>定量测试比较方法是 ProtobufGo 的 dynamicpb 模块和 DynamicGo 的 Get/SetByPath,SetMany,测试对象是medium data 的情况。</li><li>Set/Get 字段定量测试结果均优于 ProtobufGo,且在获取字段越稀疏的情况下性能加速越明显。</li><li>Setmany 性能加速更明显,在 100% 字段下 ns/op 开销约为 0.11。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/14.png"></p><h3 id="序列化-反序列"><a href="#序列化-反序列" class="headerlink" title="序列化/反序列"></a>序列化/反序列</h3><ul><li>序列化在 small 规模略高于 ProtobufGo,medium 规模的数据上性能优势更明显,ns/op 开销约为源码的0.54 ~ 0.84。</li><li>反序列化在 reuse 模式下,small 规模略高于 ProtobufGo,在 medium 规模数据上性能优势更明显,ns/op 开销约为源码的0.44 ~ 0.47,随数据规模增大性能优势增加。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/15.png"></p><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/16.png"></p><h3 id="协议转换-1"><a href="#协议转换-1" class="headerlink" title="协议转换"></a>协议转换</h3><ul><li>Json2Protobuf 优于 ProtobufGo,ns/op 性能开销约为源码的0.21 ~ 0.89,随着数据量规模增大优势增加。</li><li>Protobuf2Json 性能明显优于 ProtobufGo,ns/op 开销约为源码的0.13 ~ 0.21,而相比 Kitex,ns/op 约为Sonic+Kitex 的0.40 ~ 0.92,随着数据量规模增大优势增加。</li></ul><p><img src="/2023/12/27/Dynamicgo-%E6%94%AF%E6%8C%81-Protobuf-%E5%8A%A8%E6%80%81%E5%8F%8D%E5%B0%84/17.png"></p><h2 id="应用与展望"><a href="#应用与展望" class="headerlink" title="应用与展望"></a>应用与展望</h2><p>目前 dynamicgo 对于 Protobuf 协议的可支持的功能包括:</p><ul><li>替代官方源码的 JSON 协议转换,实现更高性能的 HTTP<>PB 动态网关</li><li>支持 IDL 内存字符串动态解析和数据泛化调用,可辅助 Kitex 提升 Protobuf 泛化调用模块性能。</li><li>支持动态数据裁剪、聚合等 DSL 场景,实现高性能 PB BFF 网关。</li></ul><p>目前 dynamicgo 还在迭代中,接下来的工作包括:</p><ol><li>支持 Protobuf 特殊字段,如 Enum,Oneof 等;</li><li>对于 Protobuf 协议转换提供 Http-Mapping 的扩展和支持;</li><li>继续扩展优化多种协议之间的泛化调用过程,集成到 Kitex 泛化调用模块中;</li></ol><p>也欢迎感兴趣的个人或团队参与进来,共同开发!</p><blockquote><p>代码仓库:<a href="https://github.com/cloudwego/dynamicgo" target="_blank" rel="noopener">https://github.com/cloudwego/dynamicgo</a><br>本文作者:尹可汗,徐健猇、段仪 | 来自:<a href="https://mp.weixin.qq.com/s/OeQwlgZJtYOGTHnN50IdOA" target="_blank" rel="noopener">官方微信推文</a></p></blockquote>]]></content>
<summary type="html">
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>Dynamicgo 是字节跳动自研的高性能 Golang RPC 编解码基础库,能在动态处理 RPC 数据(不依赖代码生成)的同时保证高性能,主要用于实现高性能 RPC 动态代理场景(见 <a href="https://mp.weixin.qq.com/s/KPfD5qp70uup6_ZI9UdYww" target="_blank" rel="noopener">dynamicgo 介绍</a>)。<br>Protobuf 是一种跨平台、可扩展的序列化数据传输协议,该协议序列化压缩特性使其具有优秀的传输速率,在常规静态 RPC 微服务场景中已经得到了广泛的应用。但是对于上述特殊的动态代理场景,我们调研发现目前业界主流的 Protobuf 协议基础库并不能满足我们的需求:</p>
<ul>
<li><a href="https://github.com/protocolbuffers/protobuf-go" target="_blank" rel="noopener">google.golang.org/protobuf</a>:Protobuf 官方源码支持协议转换和字段动态反射。实现过程依赖于反射完整的中间结构体 Message 对象来进行管理,使用过程中带来了很多不必要字段的数据性能开销,并且在处理多层嵌套数据时操作较为复杂,不支持内存字符串 io 流 IDL 解析。</li>
<li><a href="https://github.com/jhump/protoreflect" target="_blank" rel="noopener">github.com/jhump/protoreflect</a>:Protobuf 动态反射第三方库可支持文件和内存字符串 io 流 IDL 解析,适合频繁泛化调用,协议转换过程与官方源码一致,均未实现 inplace 转换,且内部实现存在Go版本兼容性问题。</li>
<li><a href="https://github.com/cloudwego/fastpb" target="_blank" rel="noopener">github.com/cloudwego/fastpb</a>:Protobuf 快速序列化第三方库,通过静态代码方式读写消息结构体,不支持协议转换和动态 IDL 解析。<br>因此如何设计自研一个功能完备、高性能、可扩展的 Protobuf 协议动态代理基础库是十分有必要的。<br><a href="https://github.com/khan-yin" target="_blank" rel="noopener">@khan-yin</a>和<a href="https://github.com/iStitches" target="_blank" rel="noopener">@iStitches</a>两位同学经过对 Protobuf 协议源码机制的深入学习,设计了高性能 Protobuf 协议动态泛化调用链路,能满足绝大多数 Protobuf 动态代理场景,并且性能优于官方实现,目前 PR<a href="https://github.com/cloudwego/dynamicgo/pull/37" target="_blank" rel="noopener">#37</a> 已经合入代码仓库。</li></ul>
</summary>
<category term="Go" scheme="https://jackyin.space/tags/Go/"/>
</entry>
<entry>
<title>既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子</title>
<link href="https://jackyin.space/2022/03/31/%E6%97%A2%E5%BE%80%E4%B8%8D%E6%81%8B%EF%BC%8C%E7%BA%B5%E6%83%85%E5%90%91%E5%89%8D%EF%BC%8C%E6%B1%9F%E6%B9%96%E5%86%8D%E8%A7%81%E2%80%94%E2%80%9422%E5%B2%81%E9%82%A3%E5%B9%B4%EF%BC%8C%E5%9C%A8%E5%8C%97%E4%BA%AC%E7%9A%84%E6%97%A5%E5%AD%90/"/>
<id>https://jackyin.space/2022/03/31/%E6%97%A2%E5%BE%80%E4%B8%8D%E6%81%8B%EF%BC%8C%E7%BA%B5%E6%83%85%E5%90%91%E5%89%8D%EF%BC%8C%E6%B1%9F%E6%B9%96%E5%86%8D%E8%A7%81%E2%80%94%E2%80%9422%E5%B2%81%E9%82%A3%E5%B9%B4%EF%BC%8C%E5%9C%A8%E5%8C%97%E4%BA%AC%E7%9A%84%E6%97%A5%E5%AD%90/</id>
<published>2022-03-30T16:41:32.000Z</published>
<updated>2024-10-09T08:43:45.316Z</updated>
<content type="html"><![CDATA[<h2 id="既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子"><a href="#既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子" class="headerlink" title="既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子"></a>既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子</h2><p>2022年3月31日0:52,睡不着,日常拖延症+强迫症导致清东西到现在,等会还想清扫一下弄脏的地板。</p><p><strong>莲竹花园甲2号楼一门101,从今天起,这里将不再是我的家</strong>,本来是昨天的机票,天气不似预期,不打算买高铁,还想再飞上一次云霄,所以倔强的又改签了机票,不过正好也让我多看一眼这里吧,最近东航事件其实让我也有一点小慌,希望能平安到达吧。</p><p>不出所料,我对这个城市并没有什么留恋,每天晚上我都会思考很多,有天夜里我的脑海里突然冒出来了那句:<strong>劝君更进一杯酒,西出阳关无故人。</strong> 这一刻我才真正明白什么是独在异乡为异客的感觉,什么才是我心里最想要的。</p><a id="more"></a><h2 id="从南山南到北海北"><a href="#从南山南到北海北" class="headerlink" title="从南山南到北海北"></a>从南山南到北海北</h2><p>作为一个一直生活在南方的人,第一次在北京生活,拥挤的早晚高峰,寒风和冰雪的夜晚,疫情形势也不断变化,恰逢过年,回家的日子也迟迟不敢定下来,奇怪,你可能会好奇,在大厂工作我竟然并没有提到工作的压力,仔细想来,其实美团的作息是相对舒服的10-8-5,尽管工作和技术基础我有许多不足,但mentor比较照顾我,不会刻意去push进度,更多的是希望让我自己去探索和思考,所以更多的工作压力还是来自于我自己一贯所坚持的负责和标准。这是一件好事,说明我做事情有自己的原则和态度,但有时候也会让自己比较心累,毕竟作为一个新人,从0-1去启动项目,虽然我是个自信的人,即使遇到问题也会尽可能自己去想办法,但我也深知自己的代码习惯和知识结构存在很多不足,害怕的不是做不好做不成,而是做事的效率和维护性,其实这也是我应该要从这些前辈身上要学到的经验。</p><p>年前一周,我申请了远程实习提前坐飞机回家了,到家的一刻才明白,这种人间烟火气才真的是生活,说来有愧,从回家到过年收假的时间,工作上我确实有点摆烂了哈哈哈,我知道这应该最后一个这么长时间的寒假了,经过将近1个月的在外实习也格外体会到了这种归家的温暖,去学一学切菜炒菜,陪一陪老年人,约一场球,见想见的人,街头漫步,吃点臭干子,米粉,麻辣烫,和朋友分享一些见闻,<strong>借酒消愁,确实愁更愁。</strong></p><p>时间很快,大年初五的下午,我就启程返工了,毕竟现在的身份是打工人而不是大学生。和我一同去北京的还有广平,也在美团,我们都不打算实习很久,刚好我房子够住2个人,就一起住了一个月,不得不说多了一个人生活确实没有这么孤单了,尽管工作和拥挤依旧麻烦,但至少回家轻松快活一些,虽然有时候会有一些赌气和争吵,睡眠问题,但不管怎么说,互相都分担了一些对未来的焦虑和思考,一块出去玩,打游戏开黑,去什刹海,天安门,吃各种小吃,也算是度过一段不那么枯燥的时间。</p><p><strong>在实习的这段日子里,最大的一个体会就是要学会如何平衡生活与工作,这个问题也是以后一定会要去面对的</strong>。也许是第一次实习,虽然不算很难的任务,但因为自己很多基础,技术,学习方法都还不够到位,上手项目和方向也不了解,所以效率上比较低,也许是拖延症,情绪化,该完成的任务没能及时完成,答应朋友的事情,也常常会忘记,衣服也有时候会拖着没洗,又或许是自己的性格本来就有些内向,在偏严肃的职场氛围下比起在学校,自己显得有些不自然,自己整个人的状态和生活界限变得模糊起来,当一个人进入这样的状态的时候,会发觉整个人比较迷茫,做事情专注度不够,热情也会逐渐消失。</p><p>在3月底,我决定从公司离职,一方面是想好好享受一下最后的大学时光,另一方面是想留出一段空白期,让自己再去学一学自己想学的内容,不得不说回归自由的感觉真的让人十分快乐,从公司买了好些纪念品,在最后几天,去见一见同学,逛一逛北京的大学,中关村,终于要飞走了,这段旅程终于要结束了,但接下来的未来好像还是看不清。</p><h2 id="执念"><a href="#执念" class="headerlink" title="执念"></a>执念</h2><p><strong>看到执念这个词,这里我说一句大胆的话,在我未来的5-8年时间里,若我能成功,一定是因为心中的执念,若我输得一败涂地,也一定是因为那执念。</strong></p><p>说来嘲讽,回想起来从小到大,我好像一直被这种执念所影响,也因为自己的固执,失去了各种各样的机会,但是我却很难改变,除了我这辈子认定的人以外,我几乎听不进去其他人的话,而且越是被人否定,越是想要证明自己的能力,我也不知道这是一种多么要强的心理,就好像明知道是火坑,也觉得自己跟别人不一样,能跨过去,然而事实大多是自己被烧得满身伤痕,甚至还在那叫嚣着他们不敢跳。</p><p>我的性格大家也知道,带有棱角,有个性,孤傲却又内向自卑,坚忍,舍得努力,看似矛盾,其实说白了也就是不太谦虚,爱吹吹牛逼,但却不是耍嘴皮子,对自己还是很负责的,但是这样也经常惹来一些麻烦,不合群,自作主张,在乎得失,也是我如此固执的原因之一。</p><p>随着年龄增大,我突然发现每一次选择的人生代价越来越大,自己的眼界和思想局限性也越来越明显,也许是从小就不爱看书(现在想看书却没多少时间,也很难静下心来)以及身处的环境的原因,思想成熟得很慢,自大,考虑问题过于天真,没能及时跟上时代,时而觉得一切皆有可能,时而又觉得一切皆是虚妄。去年的时候,我还觉得自己年轻,有无限可能,直到保研结束以后,我才恍然明白,其实我从18岁那年的夏天开始,就已经不再年轻,尽管我是在努力的朝改善未来的方向走,往后的岁月每一次决定都是那么矛盾,迷茫,自卑,妥协,后悔。</p><p>后悔,我总是做事情让自己有多个选择留有退路,但似乎给自己留选择本质上就是一种后悔,当你没能选到最理想的选择时,自然就会后悔吧。</p><h2 id="2024-06-02-更新"><a href="#2024-06-02-更新" class="headerlink" title="2024.06.02 更新"></a>2024.06.02 更新</h2><p>哈哈哈哈最近难得休闲几天,看到以前写的这些,看问题角度加上当时的个人情绪驱使文案还是略显年轻了,回过头来看其实无所谓后悔不后悔的,已经把这里标题的后悔去掉了,我一贯以来的行为准则就是对自己的选择负责,不论是什么样的未来,勇敢且不要停止思考,相信自己的光就好,这不仅仅会给自己能量也一定会给他人的生命带来光亮。</p>]]></content>
<summary type="html">
<h2 id="既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子"><a href="#既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子" class="headerlink" title="既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子"></a>既往不恋,纵情向前,江湖再见——22岁那年,在北京的日子</h2><p>2022年3月31日0:52,睡不着,日常拖延症+强迫症导致清东西到现在,等会还想清扫一下弄脏的地板。</p>
<p><strong>莲竹花园甲2号楼一门101,从今天起,这里将不再是我的家</strong>,本来是昨天的机票,天气不似预期,不打算买高铁,还想再飞上一次云霄,所以倔强的又改签了机票,不过正好也让我多看一眼这里吧,最近东航事件其实让我也有一点小慌,希望能平安到达吧。</p>
<p>不出所料,我对这个城市并没有什么留恋,每天晚上我都会思考很多,有天夜里我的脑海里突然冒出来了那句:<strong>劝君更进一杯酒,西出阳关无故人。</strong> 这一刻我才真正明白什么是独在异乡为异客的感觉,什么才是我心里最想要的。</p>
</summary>
<category term="youth" scheme="https://jackyin.space/tags/youth/"/>
</entry>
<entry>
<title>菜鸡的算法岗日常实习面经总结</title>
<link href="https://jackyin.space/2022/01/08/%E8%8F%9C%E9%B8%A1%E7%9A%84%E7%AE%97%E6%B3%95%E5%B2%97%E6%97%A5%E5%B8%B8%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F%E6%80%BB%E7%BB%93/"/>
<id>https://jackyin.space/2022/01/08/%E8%8F%9C%E9%B8%A1%E7%9A%84%E7%AE%97%E6%B3%95%E5%B2%97%E6%97%A5%E5%B8%B8%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F%E6%80%BB%E7%BB%93/</id>
<published>2022-01-08T12:06:00.000Z</published>
<updated>2024-10-09T08:43:45.343Z</updated>
<content type="html"><![CDATA[<p><img src="/2022/01/08/%E8%8F%9C%E9%B8%A1%E7%9A%84%E7%AE%97%E6%B3%95%E5%B2%97%E6%97%A5%E5%B8%B8%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F%E6%80%BB%E7%BB%93/1.jpg" alt="66CC884772E6FB9A1E2BA12BC9E7C9C4.jpg"></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote><p>第一次在掘金写面经,其实今年过得不尽人意,好在能找找实习调整一下心态吧,终于有钱换手机了!<strong>要说有什么新年愿望,那就希望2022一切好运吧。</strong> 虽然是实习面经但是其实也没怎么准备,随缘,放弃了bat的部门,估计对学历和论文有门槛(尝试性投了深圳字节直接挂简历,<strong>我永远喜欢bytedance</strong>😭),10月投b站简历挂,旷视面试没过以后就没投了,后面去学车了,结果科目二没过,有点闲看到有朋友也在投,就不抱希望还是投了3家,比较幸运最后拿到了美团和商汤的实习offer,感谢面试官们手下留情🤣。</p></blockquote><a id="more"></a><p><strong>个人情况:</strong> </p><ul><li>学校:211 大四</li><li>成绩:前5%</li><li>项目:3段CV相关项目经历,无论文,两个算法实践工程落地项目,一段kaggle竞赛铜牌,总体上来说都比较水</li><li>算法能力:无ACM,比较菜只是比较喜欢刷,leetcode400题(以easy+medium为主,剑指offer+程序员面试经典+每日一题/周赛)</li></ul><p>因为感觉自己很菜找不到实习,所以都没找内推,也没有海投,不过好像大多都会给面试机会的,感觉如果能找内推可能会更好一点。没想到最后能去美团当外卖骑手了😁! </p><h2 id="旷视"><a href="#旷视" class="headerlink" title="旷视"></a>旷视</h2><p>旷视的实习面试是最早的,不过也面试通知也等了好久,hr说有两个面试官mark了我的简历,所以面了两个组,当时是第一次面试,比较紧张,虽然面试问的不难,但是有些地方答的不好,面试问题记不太清了。另一个面试官,我等了半小时结果被他放鸽子了,改天再面的。。。</p><ul><li>手写IoU</li><li>求三个部分的IoU,根据定义就是求:$\frac{A \cap B \cap C}{A \cup B \cup C} $,只用讲思路,其实就是容斥原理,当时有点紧张,公式最后一项系数写错了一直没看出来,场面一度尴尬。</li><li>讲解Focal loss的原理作用</li><li>为什么会用到<code>StratifiedKFold</code>,和<code>KFold</code>有什么区别</li><li>介绍一下比赛用到的<code>TTA</code>,<code>Cutmix</code>方法(<code>Cutout</code>和<code>mixup</code>的结合,最好三个都讲解一下)</li><li>讲讲<code>Resnet</code>(建议读一下论文,从目的,作用,残差结构的形式,反向传播梯度计算,机器学习GBDT思想等角度进行阐述)</li><li>目标检测的<code>mAP</code>达到了多少,有没有测过双目测距的精准度,为啥使用wifi和flask推流通信,这样会比较慢(树莓派算力不够,跑着跑着宕机了),小车目标检测的帧数能达到多少fps(emm我们只是做了一个无人清洁车落地的demo,只测了模型目标检测在数据集上的<code>mAP</code>,其他的更多的是我们自己提供一种idea,具体指标没有测试过😥)。</li><li><a href="https://leetcode-cn.com/problems/rotate-matrix-lcci/" target="_blank" rel="noopener">旋转矩阵</a>,<strong>不能开空间</strong>,其实做过但是面试很容易紧张卡住,有想到思路但是没写对有点bug,不过面试官说我思路是对的,面完就发现原来是下标对错了。。。</li><li>团队合作交流是如何分工和解决问题的?</li><li>其他人或者老师给你任务安排时,如果与你的想法不合时,你会如何做?</li></ul><h3 id="手写IoU"><a href="#手写IoU" class="headerlink" title="手写IoU"></a>手写IoU</h3><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">IoU</span><span class="hljs-params">(bounding_box,ground_truth)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"></span><br><span class="hljs-string"> :param bounding_box: [[x1,y1,x2,y2,score]]</span><br><span class="hljs-string"> :param ground_truth: [x1,y1,x2,y2]</span><br><span class="hljs-string"> :return:</span><br><span class="hljs-string"> """</span><br> x1 = bounding_box[:,<span class="hljs-number">0</span>]<br> y1 = bounding_box[:,<span class="hljs-number">1</span>]<br> x2 = bounding_box[:,<span class="hljs-number">2</span>]<br> y2 = bounding_box[:,<span class="hljs-number">3</span>]<br> score = bounding_box[:,<span class="hljs-number">4</span>]<br><br> areas = (x2-x1) * (y2-y1)<br> gt_area = (ground_truth[<span class="hljs-number">2</span>] - ground_truth[<span class="hljs-number">0</span>]) * (ground_truth[<span class="hljs-number">3</span>] - ground_truth[<span class="hljs-number">1</span>])<br><br> xx1 = np.maximum(x1,ground_truth[<span class="hljs-number">0</span>])<br> yy1 = np.maximum(y1,ground_truth[<span class="hljs-number">1</span>])<br> xx2 = np.minimum(x2,ground_truth[<span class="hljs-number">2</span>])<br> yy2 = np.minimum(y2,ground_truth[<span class="hljs-number">3</span>])<br><br> h = np.maximum(<span class="hljs-number">0</span>,yy2-yy1)<br> w = np.maximum(<span class="hljs-number">0</span>,xx2-xx1)<br><br> inter = w * h<br><br> ovr = inter / (gt_area + areas - inter) <span class="hljs-comment"># np.true_divide(inter, (gt_area + areas - inter))</span><br><br> <span class="hljs-keyword">return</span> ovr<br></code></pre></td></tr></table></figure><h3 id="旋转矩阵"><a href="#旋转矩阵" class="headerlink" title="旋转矩阵"></a>旋转矩阵</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">rotate</span><span class="hljs-params">(<span class="hljs-built_in">vector</span><<span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>>& matrix)</span> </span>{<br> <span class="hljs-keyword">int</span> n = matrix.size();<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<n/<span class="hljs-number">2</span>;i++)<br> {<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">0</span>;j<(n+<span class="hljs-number">1</span>)/<span class="hljs-number">2</span>;j++)<br> {<br> <span class="hljs-comment">// (i,j) (j,n-i-1) (n-j-1,i) (n-i-1,n-j-1)</span><br> swap(matrix[i][j],matrix[n-i<span class="hljs-number">-1</span>][n-j<span class="hljs-number">-1</span>]);<br> swap(matrix[i][j],matrix[n-j<span class="hljs-number">-1</span>][i]);<br> swap(matrix[j][n-i<span class="hljs-number">-1</span>],matrix[n-i<span class="hljs-number">-1</span>][n-j<span class="hljs-number">-1</span>]);<br> }<br> }<br> }<br>};<br></code></pre></td></tr></table></figure><h2 id="小米"><a href="#小米" class="headerlink" title="小米"></a>小米</h2><p>小米面试其实是当时最好的选择,因为就在武汉本地,也不用外出,但是当时面试确实不太好,面试官对我其中一个项目比较感兴趣,被疯狂怼着追问细节。</p><ul><li>描述项目中用到的SIFT匹配和直方图相似度比对基本原理。</li><li>one-stage和two-stage的区别。</li><li>双目摄像头视觉下应该可以得到整个三维空间下的信息,没考虑方向角度的问题,对测距的处理有些草率(没有很懂,之后再去了解了解,只用到了物理上小孔成像的原理😰</li><li>为什么不直接在树莓派上推理,采用推流和主机服务器去进行计算,速度和精度如何</li><li>继续追问项目细节。</li><li>手写SIFT匹配过程,直接人傻了😭(被面试官怼对传统图像处理了解不深,只会玩玩深度学习搭积木)</li></ul><h2 id="美团-智能视觉-offer"><a href="#美团-智能视觉-offer" class="headerlink" title="美团-智能视觉 offer"></a>美团-智能视觉 offer</h2><p>美团面试官面试体验比较好,没有过分针对,对待我这种本科生可能相对更看重对我的motivation和potential吧感觉,评价也比较好。</p><h3 id="一面"><a href="#一面" class="headerlink" title="一面"></a>一面</h3><ul><li>聊项目,做项目的目的和缘由,你觉得有什么亮点。</li><li>讲讲one-stage和two-stage的区别。</li><li>anchor-free的方式比如FCOS有了解吗</li><li>看你有用过<code>ViT</code>,能不能讲讲<code>transformer</code>的架构,再讲讲<code>vit</code>是怎么做的,<code>BERT</code>有了解吗,跟<code>vit</code>有什么区别</li><li>看你项目经常用到kaiming的东西,kaiming最近新出的<strong>MAE</strong>有了解吗(虽然面试官看着年龄有点大,不过还挺紧跟潮流的,<strong>kaiming yyds!</strong></li><li>讲讲SIFT匹配到双目测距的过程和原理</li><li>为什么要用树莓派采集图像借助flask推流,再由主机进行模型推理</li><li>对GNN和GCN有了解吗</li><li>讲讲人脸检测<code>MTCNN</code>是怎么做的</li><li>聊美团这边的业务,问我有没有这方面技术的了解</li><li>自己出的算法题,以条形码识别为背景,有点像是去除干扰字符的匹配,用双指针解决即可。</li></ul><h3 id="二面"><a href="#二面" class="headerlink" title="二面"></a>二面</h3><ul><li>聊项目缘由,团队协作。</li><li>讲讲<code>transformer</code>,讲讲<code>position encoding</code>的方法,作者用这种三角函数的方式有什么特点,在<code>vit</code>里面是如何做位置编码的。</li><li>介绍<code>self-attention</code>,<code>transfomer</code>的<code>encode</code>和<code>decode</code>有什么区别,你对这两部分有什么理解,以及相对于<code>CNN</code>的一些特点。</li><li>项目用的数据集有多大。</li><li>项目目标检测用的什么指标,达到了什么效果。</li><li>聊美团业务,问我有没有兴趣。</li><li>出了个hard题<a href="https://leetcode-cn.com/problems/scramble-string/" target="_blank" rel="noopener">扰乱字符串</a>,不会,简单讲了一下dfs暴力的思路但还是没理清楚写不出😭,后面换了个简单点的题不过要求让我用python写</li></ul><h2 id="商汤-基础视觉-offer"><a href="#商汤-基础视觉-offer" class="headerlink" title="商汤-基础视觉 offer"></a>商汤-基础视觉 offer</h2><h3 id="一面-1"><a href="#一面-1" class="headerlink" title="一面"></a>一面</h3><p>一面比较简单,面试官也比较和蔼。</p><ul><li>自己出的题,二维矩阵找最长的严格单调上升的一个连续序列(可以跨行转弯,对角线不算),节点可上下左右移动。先用dfs暴力,然后再用记忆化优化了一下。虽然不难但是写的有点慢。</li><li>聊项目。</li><li>讲讲Batch Normalization(建议从目的,由来,公式解析,理解操作过程,作用以及使用场景等方面来讲解<a href="https://www.cnblogs.com/itmorn/p/11241236.html" target="_blank" rel="noopener">附博客</a>,看完发现自己面试讲的好烂)</li><li>介绍小批量梯度下降</li><li>讲讲分布不均衡的数据如何处理(讲了讲数据增强,过采样比如SMOTE,欠采样,调整样本对分类的权重,如FocalLoss,这个我感觉回答的不是很好,希望评论区有更好的理解)</li><li>有没有搭建或者使用过单机多卡和多机多卡,是否了解多卡时神经网络的梯度反向传播是如何计算的(没钱没资源🤣,瞎猜了一波可能和计算图的节点资源分配有关)<h3 id="二面-1"><a href="#二面-1" class="headerlink" title="二面"></a>二面</h3>二面其实感觉面试有点糟糕,连问了十几个八股文,做题也出了一些问题,自闭,答也能答出来一些但是整体感觉不好。。。。不过最后比较幸运还是让我过了。</li><li>简单介绍自己的项目和自己觉得有亮点的地方</li><li>讲讲one-stage和two-stage的区别和特点</li><li>什么是RPN,作用是什么</li><li>什么是FPN,有什么特点,这种多尺度是怎么实现和进行预测的</li><li>讲述yolov3预测目标的过程(比较关键的几个要点我觉得是损失函数,模型输出,非极大抑制,多种变种的IoU,SPP空间金字塔,以及论文中有提到yolov3的anchor是kmeans聚类出来的,可能会让你手写kmeans)</li><li>为什么two-stage要比one-stage的精度要高,你觉得本质是什么</li><li>yolov3的三个特征图大尺寸的用来预测大的图还是小的图</li><li>anchor-free的方式有了解吗,和anchor-based的差异在哪,本质和原理是什么</li><li>介绍一下CenterNet和FCOS,中心度的公式和理论,预测过程</li><li>什么是梯度消失和梯度爆炸,如何解决(提到某些要点或者答错了会继续追问,没有答的很全)</li><li>进程和线程的区别,python的多线程如何实现</li><li>maxpool是如何进行反向传播的(建议看看cs231n,首先要明确的是pool层中是没有参数的,然后再来将maxpool,其实我当时也忘了,我记得好像跟maxout的反向传播差不多)</li><li>讲讲Batch Normalization,以及在训练和预测过程的计算方式</li><li>讲讲dropout,以及在训练和预测过程的计算方式(建议看看cs231n),你觉得和机器学习当中哪种集成方式比较像(这个我不是很清楚希望各位大佬回答一下,当时瞎说了一个boosting🤣)</li><li><a href="https://leetcode-cn.com/problems/zhan-de-ya-ru-dan-chu-xu-lie-lcof/" target="_blank" rel="noopener">剑指 Offer 31. 栈的压入、弹出序列</a> 思路大致是对的但是没想得特别清楚,比较紧张,写了很久</li><li><a href="https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/" target="_blank" rel="noopener">剑指 Offer 39. 数组中出现次数超过一半的数字</a> <strong>第二题直接限制时间复杂度</strong>$O(n)$,<strong>空间复杂度</strong>$O(1)$,我只会哈希方法,然后和面试官说了一下最优解方法我只记得叫<strong>摩尔投票</strong>,然后具体忘了。</li></ul><h3 id="栈的压入、弹出序列"><a href="#栈的压入、弹出序列" class="headerlink" title="栈的压入、弹出序列"></a>栈的压入、弹出序列</h3><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">validateStackSequences</span><span class="hljs-params">(<span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>& pushed, <span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>& popped)</span> </span>{<br> <span class="hljs-built_in">stack</span><<span class="hljs-keyword">int</span>> stk;<br> <span class="hljs-keyword">int</span> n = popped.size();<br> <span class="hljs-keyword">int</span> m = pushed.size();<br> <span class="hljs-keyword">int</span> j = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<m;i++)<br> {<br> stk.push(pushed[i]);<br> <span class="hljs-keyword">while</span>(stk.size()&&stk.top()==popped[j])<br> {<br> j++;<br> stk.pop();<br> }<br> }<br> <span class="hljs-keyword">return</span> stk.empty();<br> }<br>};<br></code></pre></td></tr></table></figure><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>还有一些感觉比较重要,但是没有被问道的东西,包括各种评价指标的含义等等,包括数学基础等等。<br>经典教程推荐:cs231n,吴恩达,李宏毅,动手学深度学习,《统计学习方法》<br>关于八股文方面这里推荐一个<a href="https://github.com/scutan90/DeepLearning-500-questions" target="_blank" rel="noopener">DeepLearning-500-questions</a></p><h3 id="手写非极大抑制"><a href="#手写非极大抑制" class="headerlink" title="手写非极大抑制"></a>手写非极大抑制</h3><p><strong>使用非极大抑制的前提是,我们已经得到了一组候选框和对应label的置信分数,以及groud truth的信息,通过设定阈值来删除重合度较高的候选框。</strong><br>算法流程如下:</p><ul><li> 根据置信度得分进行排序</li><li> 选择置信度最高的比边界框添加到最终输出列表中,将其从边界框列表中删除</li><li> 计算所有边界框的面积</li><li> 计算置信度最高的边界框与其它候选框的IoU。</li><li> 删除IoU大于阈值的边界框</li><li> 重复上述过程,直至边界框列表为空。</li></ul><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nms</span><span class="hljs-params">(dets, threshod, mode=<span class="hljs-string">"Union"</span>)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> greedily select boxes with high confidence</span><br><span class="hljs-string"> keep boxes overlap <= thresh</span><br><span class="hljs-string"> rule out overlap > thresh</span><br><span class="hljs-string"> :param dets: [[x1, y1, x2, y2 score]]</span><br><span class="hljs-string"> :param threshod: retain overlap <= thresh</span><br><span class="hljs-string"> :return: indexes to keep</span><br><span class="hljs-string"> """</span><br> x1 = dets[:, <span class="hljs-number">0</span>]<br> y1 = dets[:, <span class="hljs-number">1</span>]<br> x2 = dets[:, <span class="hljs-number">2</span>]<br> y2 = dets[:, <span class="hljs-number">3</span>]<br><br> scores = dets[:, <span class="hljs-number">4</span>]<br><br> areas = (x2 - x1 + <span class="hljs-number">1</span>) * (y2 - y1 + <span class="hljs-number">1</span>)<br> order = scores.argsort()[::<span class="hljs-number">-1</span>] <span class="hljs-comment"># reverse</span><br><br> keep = []<br><br> <span class="hljs-keyword">while</span> order.size() > <span class="hljs-number">0</span>:<br> i = order[<span class="hljs-number">0</span>]<br> keep.append(i)<br> <span class="hljs-comment"># A & B left top position </span><br> xx1 = np.maximun(x1[i], x1[order[<span class="hljs-number">1</span>, :]])<br> yy1 = np.maximun(y1[i], y1[order[<span class="hljs-number">1</span>, :]])<br> <span class="hljs-comment"># A & B right down position</span><br> xx2 = np.minimum(x2[i], x2[order[<span class="hljs-number">1</span>, :]])<br> yy2 = np.minimum(y2[i], y2[order[<span class="hljs-number">1</span>:]])<br><br> w = np.maximum(<span class="hljs-number">0.0</span>, xx2 - xx1 + <span class="hljs-number">1</span>)<br> h = np.maximum(<span class="hljs-number">0.0</span>, yy2 - yy1 + <span class="hljs-number">1</span>)<br><br> inter = w * h<br><br> <span class="hljs-comment"># cacaulate the IOU between box which have largest score with other boxes</span><br> <span class="hljs-keyword">if</span> mode == <span class="hljs-string">"Union"</span>:<br> <span class="hljs-comment"># area[i]: the area of largest score</span><br> ovr = inter / (areas[i] + areas[order[<span class="hljs-number">1</span>:]] - inter)<br> <span class="hljs-keyword">elif</span> mode == <span class="hljs-string">"Minimum"</span>:<br> ovr = inter / np.minimum(areas[i], areas[order[<span class="hljs-number">1</span>:]])<br> <span class="hljs-comment"># delete the IoU that higher than threshod </span><br> inds = np.where(ovr <= threshod)[<span class="hljs-number">0</span>]<br> order = order[inds + <span class="hljs-number">1</span>] <span class="hljs-comment"># +1: eliminates the first element in order</span><br><br> <span class="hljs-keyword">return</span> keep<br></code></pre></td></tr></table></figure><h3 id="手写Kmeans方法"><a href="#手写Kmeans方法" class="headerlink" title="手写Kmeans方法"></a>手写Kmeans方法</h3><blockquote><p>参考链接:<a href="https://www.cnblogs.com/lunge-blog/p/11657415.html" target="_blank" rel="noopener">https://www.cnblogs.com/lunge-blog/p/11657415.html</a></p></blockquote><p>这个版本不是最佳写法,某些处理有点暴力,可以用矩阵和numpy相关的操作会更简洁,但是退出迭代的条件写的很全的,有达到迭代次数,中心点集不变,中心点变化范围小于$\delta$</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br><br>n = <span class="hljs-number">100</span><br>a = np.random.randn(<span class="hljs-number">0</span>,<span class="hljs-number">50</span>,n)<br>b = np.random.rand(<span class="hljs-number">0</span>,<span class="hljs-number">50</span>,n)<br>x = np.random.randint(<span class="hljs-number">0</span>,<span class="hljs-number">50</span>,n)<br>y = np.random.randint(<span class="hljs-number">0</span>,<span class="hljs-number">50</span>,n)<br>points = np.array(list(zip(x,y)))<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">distance</span><span class="hljs-params">(x,y)</span>:</span><br> <span class="hljs-keyword">return</span> np.sqrt(np.sum((x-y)**<span class="hljs-number">2</span>))<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">k_means</span><span class="hljs-params">(points,k=<span class="hljs-number">5</span>,epochs=<span class="hljs-number">500</span>,delta=<span class="hljs-number">1e-3</span>)</span>:</span><br> <span class="hljs-comment"># 初始化聚类中心</span><br> center_ids = np.random.randint(<span class="hljs-number">0</span>,n,k)<br> centers = points[center_ids]<br><br> <span class="hljs-comment"># 聚类集合初始化</span><br> results = []<br> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(k):<br> results.append([])<br> step = <span class="hljs-number">1</span><br> flag = <span class="hljs-literal">True</span><br><br> <span class="hljs-comment"># 计算各点到中心的距离</span><br> <span class="hljs-keyword">while</span> flag <span class="hljs-keyword">and</span> step < epochs:<br> <span class="hljs-comment"># 重新迭代</span><br> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(k):<br> results[i] = []<br> <span class="hljs-comment"># 计算每个点到距离中心的距离</span><br> <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(points)):<br> point = points[i]<br> min_dis = np.inf<br> min_id = <span class="hljs-number">0</span><br><br> <span class="hljs-keyword">for</span> idx, center <span class="hljs-keyword">in</span> enumerate(centers):<br> dis = distance(center,point)<br> <span class="hljs-keyword">if</span> min_dis > dis:<br> min_dis = dis<br> min_id = idx<br> results[min_id].append(point)<br><br> <span class="hljs-comment"># 更新聚类中心</span><br> <span class="hljs-keyword">for</span> idx, old_center <span class="hljs-keyword">in</span> enumerate(centers):<br> new_center = np.array(results[idx]).mean(axis=<span class="hljs-number">0</span>)<br> <span class="hljs-keyword">if</span> distance(center, new_center) > delta:<br> centers[idx] = new_center<br> flag = <span class="hljs-literal">False</span><br><br> <span class="hljs-comment"># flag=True说明聚类中心已经不变了则可以退出了</span><br> <span class="hljs-keyword">if</span> flag:<br> <span class="hljs-keyword">break</span><br> <span class="hljs-keyword">else</span>:<br> flag = <span class="hljs-literal">True</span><br> step += <span class="hljs-number">1</span><br><br> <span class="hljs-keyword">return</span> results,centers<br><br><br><br>plt.plot(x,y,<span class="hljs-string">'ro'</span>)<br>results,centers=k_means(points,k=<span class="hljs-number">5</span>)<br>color=[<span class="hljs-string">'ko'</span>,<span class="hljs-string">'go'</span>,<span class="hljs-string">'bo'</span>,<span class="hljs-string">'yo'</span>,<span class="hljs-string">'co'</span>]<br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(results)):<br> result=results[i]<br> plt.plot([res[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> res <span class="hljs-keyword">in</span> result],[res[<span class="hljs-number">1</span>] <span class="hljs-keyword">for</span> res <span class="hljs-keyword">in</span> result],color[i])<br>plt.plot([res[<span class="hljs-number">0</span>] <span class="hljs-keyword">for</span> res <span class="hljs-keyword">in</span> centers],[res[<span class="hljs-number">1</span>] <span class="hljs-keyword">for</span> res <span class="hljs-keyword">in</span> centers],<span class="hljs-string">'ro'</span>)<br>plt.show()<br></code></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><blockquote><p>本人能力有限,如果上述回答有任何错误,还请各位大佬及时指出</p></blockquote><ul><li>不同面试官的面试风格不一样,项目相关的知识积累问的会比较多(项目水没关系但相关技术还是要搞懂),项有的会考察广度和思维潜力,有的会考察基础(八股),算法题感觉并没有那种重要,把剑指offer刷了应该差不多。</li><li>关于八股文的看法,其实更多的还是要多理解,一些相关原理和数学还是多看看相关论文和经典课程,到时候也不用刻意记也能按自己的想法说出一点(瞎吹),<strong>感觉面试官想要的答案并不一定是你能完整的说出来,而是有自己理解的正确描述</strong>(这个可能需要在代码实践和理论知识之间多反复几次体会会比较好)。</li><li>代码实践还是要多一些,其实很多东西我了解的并不深入,只是大致理解过原理和思路,这样还是不太好,开始害怕顶不住实习压力了。</li><li>不用害怕,尽量多交流,避免场面陷入尴尬。</li><li>反问环节我一般问的是来这边的工作内容,学习的建议和评价,培养和安排之类的。</li></ul><p>「掘金链接:<a href="https://juejin.cn/post/7050660953846710302%22" target="_blank" rel="noopener">菜鸡的算法岗日常实习面经总结</a>」</p>]]></content>
<summary type="html">
<p><img src="/2022/01/08/%E8%8F%9C%E9%B8%A1%E7%9A%84%E7%AE%97%E6%B3%95%E5%B2%97%E6%97%A5%E5%B8%B8%E5%AE%9E%E4%B9%A0%E9%9D%A2%E7%BB%8F%E6%80%BB%E7%BB%93/1.jpg" alt="66CC884772E6FB9A1E2BA12BC9E7C9C4.jpg"></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>第一次在掘金写面经,其实今年过得不尽人意,好在能找找实习调整一下心态吧,终于有钱换手机了!<strong>要说有什么新年愿望,那就希望2022一切好运吧。</strong> 虽然是实习面经但是其实也没怎么准备,随缘,放弃了bat的部门,估计对学历和论文有门槛(尝试性投了深圳字节直接挂简历,<strong>我永远喜欢bytedance</strong>😭),10月投b站简历挂,旷视面试没过以后就没投了,后面去学车了,结果科目二没过,有点闲看到有朋友也在投,就不抱希望还是投了3家,比较幸运最后拿到了美团和商汤的实习offer,感谢面试官们手下留情🤣。</p>
</blockquote>
</summary>
<category term="youth" scheme="https://jackyin.space/tags/youth/"/>
<category term="计算机视觉" scheme="https://jackyin.space/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/"/>
</entry>
<entry>
<title>回首向来萧瑟处,归去,也无风雨也无晴|2021年终总结</title>
<link href="https://jackyin.space/2022/01/08/%E5%9B%9E%E9%A6%96%E5%90%91%E6%9D%A5%E8%90%A7%E7%91%9F%E5%A4%84%EF%BC%8C%E5%BD%92%E5%8E%BB%EF%BC%8C%E4%B9%9F%E6%97%A0%E9%A3%8E%E9%9B%A8%E4%B9%9F%E6%97%A0%E6%99%B4%EF%BD%9C2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/"/>
<id>https://jackyin.space/2022/01/08/%E5%9B%9E%E9%A6%96%E5%90%91%E6%9D%A5%E8%90%A7%E7%91%9F%E5%A4%84%EF%BC%8C%E5%BD%92%E5%8E%BB%EF%BC%8C%E4%B9%9F%E6%97%A0%E9%A3%8E%E9%9B%A8%E4%B9%9F%E6%97%A0%E6%99%B4%EF%BD%9C2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/</id>
<published>2022-01-08T12:04:00.000Z</published>
<updated>2024-10-09T08:43:45.306Z</updated>
<content type="html"><![CDATA[<p><img src="/2022/01/08/%E5%9B%9E%E9%A6%96%E5%90%91%E6%9D%A5%E8%90%A7%E7%91%9F%E5%A4%84%EF%BC%8C%E5%BD%92%E5%8E%BB%EF%BC%8C%E4%B9%9F%E6%97%A0%E9%A3%8E%E9%9B%A8%E4%B9%9F%E6%97%A0%E6%99%B4%EF%BD%9C2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/1.jpg" alt="66CC884772E6FB9A1E2BA12BC9E7C9C4.jpg"></p><h2 id="⏱2021-2022"><a href="#⏱2021-2022" class="headerlink" title="⏱2021-2022"></a>⏱2021-2022</h2><blockquote><p>说来惭愧,没想到在掘金这样神圣的技术社区,我的第一篇文章竟然是与技术无关的年终总结。(主要是想白嫖到周边棒球帽🧢,本来是打算发布已经写好的<a href="https://juejin.cn/post/7050660953846710302" target="_blank" rel="noopener">算法岗实习面经</a>,有兴趣的朋友可以继续看看)</p></blockquote><p>时间总是过的很快,我也是偶然间打开掘金看到的这个活动才意识到今年已经快要过去了,想了很久2021年对我而言到底意味着什么,好像过得很混乱,<strong>想的太多,做的太少,以前总是能在不确定性中规划好一些时间,但今年很多事情都是被时间推着走入不确定的维度。</strong></p><a id="more"></a><h2 id="读研or就业的思考"><a href="#读研or就业的思考" class="headerlink" title="读研or就业的思考"></a>读研or就业的思考</h2><p>这个问题其实在2年前就有在思考这件事情,虽然学生思维存在着一些局限性,但是你不得不承认这件事情确实是你需要尽早想清楚的事情,话是这么说其实还是当局者迷,旁观者清,尽管有一些我认识的人给过我他们的思考,我自己也没在最需要想清楚的时候想清楚这件事情,<strong>可能很多感受只有触碰到结果了才真正明白吧。</strong><br>这个问题抛出来,我写起来还是不知从何说起,没有正确答案,未来都是未知的,害怕失去现有的一切,凭我的经验来看,<strong>还是要多遵从自己的内心,不要完全理性的去权衡利弊</strong>,失去如果每个选择都会后悔,就不要选让自己更后悔的那个。还是说一下自己当时思考的几件事情:</p><ul><li>读研是为了什么,去企业又是为了什么</li><li>对于未来到底想做什么</li><li>抛开家长和他人的观点,你对自己的性格和能力是否足够了解,是否足够自信</li><li>都是最坏的情况,更能接受哪个选择</li><li>如何承受选择带来的代价和自我和解之道<h2 id="去黄鹤楼,看樱花"><a href="#去黄鹤楼,看樱花" class="headerlink" title="去黄鹤楼,看樱花"></a>去黄鹤楼,看樱花</h2>3月樱花又开了,大一没来得及看樱花,大二由于疫情影响也没能去成,大三正好有朋友过来武汉找我玩,就带着去黄鹤楼,隔壁学校赏樱花,那几天每天晚上和朋友们聊天一整夜,太久没一块说话了吧,我和他约定好考完研再来武汉陪我过生日。<br><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f311cffe4df34022b4c00975e5e2325c~tplv-k3u1fbpfcp-watermark.image" alt="image.png"><h2 id="春招面网易"><a href="#春招面网易" class="headerlink" title="春招面网易"></a>春招面网易</h2>4月有好友正好在做网易的春招宣传,内推了我一手,抱着涨点面试经验的心态,面了一下网易的算法岗,匆忙改了一版简历,笔试比较简单过了2/3,面试还行但还是没过,后面准备夏令营啥的也没有再投了。<h2 id="茶颜悦色-amp-湖南师大-amp-湖南大学"><a href="#茶颜悦色-amp-湖南师大-amp-湖南大学" class="headerlink" title="茶颜悦色&湖南师大&湖南大学"></a>茶颜悦色&湖南师大&湖南大学</h2>在湖南呆了这么久,这是第一次喝茶颜悦色,leo点的幽兰拿铁,晚上吃了一顿火锅直接拉肚子到凌晨2点,去了当初高二数学竞赛培训的湖师大,看了看那些当时买礼物的精品店和饭店,后面去了湖南大学找同学吃饭,湖南大学的建筑风格很不错,嘿嘿,不愧是湖大!<h2 id="有幸与神三元吃饭"><a href="#有幸与神三元吃饭" class="headerlink" title="有幸与神三元吃饭"></a>有幸与神三元吃饭</h2>毕业季,神三元学长也要毕业了,在学校一直听着他的<strong>江湖神话</strong>,终于有朝一日能见上一面了,迫不及待和朋友一起跑到另一个校区追星。见面一看确实是一表人才,吃饭紧张到有点不知道说话。不过听大佬聊技术趋势和工作思考,还是有很多不一样的体会。</li></ul><p><img src="https://img-blog.csdnimg.cn/780d6ab0ddb246759979bac48f2d4649.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pif6L6wIEpBQ0s=,size_20,color_FFFFFF,t_70,g_se,x_16" alt="在这里插入图片描述"></p><h2 id="bytecamp再送人头"><a href="#bytecamp再送人头" class="headerlink" title="bytecamp再送人头"></a>bytecamp再送人头</h2><p>去年暑假参加了一次bytecamp的笔试,感觉挺有意思的,虽然当时也没过,今年再参加编程<strong>又爆0</strong>了,第一题是树形dp模版题,但是我当时忘了,后面两个就更难了不会。</p><h2 id="滚滚长江东逝水——再会江滩"><a href="#滚滚长江东逝水——再会江滩" class="headerlink" title="滚滚长江东逝水——再会江滩"></a>滚滚长江东逝水——再会江滩</h2><p>暑假放假回家前,去了一次江滩,之前带朋友去的是汉口江滩,这次去武昌江滩看的,也算是两岸都看过了,虽然武汉夏天很热,但是长江的风吹过来还是很舒服的。<br><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d2e7071f1ab24f0b86925cc8310a0257~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a4174454152a430dba5b422615814b68~tplv-k3u1fbpfcp-watermark.image" alt="C11027AD58708F01003156F1C1F34870.jpg"></p><h2 id="魔都体验卡"><a href="#魔都体验卡" class="headerlink" title="魔都体验卡"></a>魔都体验卡</h2><p>有幸参加SIST的夏令营,体验了3天公费旅游的感觉,还吃了西餐,和朋友去了上海外滩和金融中心,半夜两点给健哥写前端。<strong>听说上海的天空很低!</strong><br><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/8dba2740c2a34620ae39b528d05758a4~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/461700e93de242b394132490663b9ae5~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="梦碎了"><a href="#梦碎了" class="headerlink" title="梦碎了"></a>梦碎了</h2><p>9月差一点点,最后还是没能去到自己最想去的地方,感觉自己的学生时代已经结束了,梦碎了,不展开了,容易晚上emo😭</p><h2 id="4年后再来南山区"><a href="#4年后再来南山区" class="headerlink" title="4年后再来南山区"></a>4年后再来南山区</h2><p>18年国庆来深圳,只觉得繁华,人上人,4年后再来觉得这里还是缺少了一些文化感和年代感的东西,感谢朋友们的盛情招待,<strong>看海</strong>没赶上好时候,跟小w发我的照片完全不一样😔,第一次<strong>吃椰子鸡</strong>,去了SUST<strong>看夕阳</strong>,感受了朋友在<strong>字节跳动</strong>的“快乐”生活(我永远喜欢bytedance),遇到了突然其来的台风和大暴雨,沿着大沙河逛THU和PKU</p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c9fc7cadb5174162b46e55572e8ac9e1~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/96c3a64c80364c9ba3124e8c3626732b~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/20d89d8b3a4144adaa220a2a08a0b921~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="最爱东湖与武大"><a href="#最爱东湖与武大" class="headerlink" title="最爱东湖与武大"></a>最爱东湖与武大</h2><p>在武汉,我<strong>独爱东湖</strong>,这次有幸还坐船浏览了对岸,去了磨山,<strong>一路听着粤语歌在东湖漫步</strong>,会有很多很多想法。逛完东湖与武大同学小歇畅聊,有幸参观了<strong>周恩来</strong>和<strong>闻一多</strong>先生的居所,一起吃饭听说他搞家教日薪1k+我羡慕。</p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ccac8943146346e390ce01348f4026dc~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/5ac47f321c1b48c7b81c00ab2d6eda04~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1da6114a0b8b407ea363e4420495abaa~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/eb5f62a7e9ce407ea8a1dfdbab5c04c9~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/665275bd47254a17b8771e8a515f86ee~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="摆烂的大四软测"><a href="#摆烂的大四软测" class="headerlink" title="摆烂的大四软测"></a>摆烂的大四软测</h2><p>大四一学期没听课,软测临时抱佛脚,卷了3年才知道整张试卷瞎写的感觉原来这么爽,还好没挂。</p><h2 id="CLANNAD"><a href="#CLANNAD" class="headerlink" title="CLANNAD"></a>CLANNAD</h2><p>看了一直没看的CLANNAD,不得不说这个剧表达的东西很深刻,给了我很多生活的思考,我觉得是我心中看过的最好的动漫之一了,以后可以再看一遍,好像要一个团子啊🥺!<br><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/906615193fd64982a92003ae34afc710~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="科目二的败北"><a href="#科目二的败北" class="headerlink" title="科目二的败北"></a>科目二的败北</h2><p>和朋友天天早出晚归跑去练科目二,到了考场发现自己学的东西都白学了,侧方直接挂,然后倒库紧张又挂了。。。坡上两只狗<br><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/731270d8c0894c4bbd87a51cac855248~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="12月所思"><a href="#12月所思" class="headerlink" title="12月所思"></a>12月所思</h2><p>12月陷入了一段特别迷茫的时间,他们说<strong>我所担心的并不能约束你,只能促进你,不要老是患得患失</strong>,后面拿了实习offer后开心了一些,等考研的朋友考完一起舒服了几天,后面的实训课继续摆烂,可惜说好来武汉陪我过生日的同学最后还是没来,那天写实训代码写到1点才睡,不过随着一年又一年,我倒是觉得就当平常日挺好的,也不用刻意去记,能想起来就想起来吧。</p><h2 id="回家的所闻所见"><a href="#回家的所闻所见" class="headerlink" title="回家的所闻所见"></a>回家的所闻所见</h2><p>纠结了很长时间关于寒假外出实习的事情,最后还是觉得去体验一下比较好,害怕过年回家待不了太久,所以提前跑路回家了几天,比较难受的是遇到了一些不好的事情,<strong>还是要常回家看看</strong>🙏</p><h2 id="啃了一些原理-Leetcode400题"><a href="#啃了一些原理-Leetcode400题" class="headerlink" title="啃了一些原理+Leetcode400题"></a>啃了一些原理+Leetcode400题</h2><p>今年感觉自己花在学习上的时间没有特别多,更多的时间被虚无的思考给浪费掉了,除了一些kaggle和工程代码之外,今年做了一些不一样的事情主要就是手推了《统计学习方法》当中一些基本算法,西瓜书也看了一些,<strong>leetocde不知不觉突破400题了!</strong> 还是很菜,只不过把刷题当成习惯了。<br><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b6515b8e76c841b58581edc03c2008ab~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="关于十年后再来北京这件事"><a href="#关于十年后再来北京这件事" class="headerlink" title="关于十年后再来北京这件事"></a>关于十年后再来北京这件事</h2><p>今年跨年有些不一样,12.31飞北京跨年,下午处理好了租房的问题,人生地不熟,接下来元旦几天都是被朋友带着玩哈哈哈。</p><h3 id="Dr-Pepper-amp-Sky"><a href="#Dr-Pepper-amp-Sky" class="headerlink" title="Dr.Pepper & Sky"></a>Dr.Pepper & Sky</h3><p>在旅店晚上喝了一杯<strong>命运石之门的Dr.Pepper,有一种穿越时空的味道</strong> 上次坐飞机还是10多年前,再一次飞上蓝天还是很激动!<br><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/030e3965f92e4455a6d4b1cfd8dfe690~tplv-k3u1fbpfcp-watermark.image" alt="image.png"><br><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b1c3f13325d24d5298632d6c85a06094~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="清华五道口"><a href="#清华五道口" class="headerlink" title="清华五道口"></a>清华五道口</h3><p>十年前曾踏过清华的大门,十年后还是没能跨进你的大门,来生请等我。</p><h3 id="单车刷夜去天安门和祖国母亲一起跨年"><a href="#单车刷夜去天安门和祖国母亲一起跨年" class="headerlink" title="单车刷夜去天安门和祖国母亲一起跨年"></a>单车刷夜去天安门和祖国母亲一起跨年</h3><p>作为一名预备党员,元旦必然要在天安门通宵等升旗,被北方的寒风给冻傻了,和祖国母亲一起放飞和平鸽!<br><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/acebd5a612724242b077955ba15cae28~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p9-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/3008c358c6b447738a319247f901f5c6~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="海底捞被朋友抓去过生日"><a href="#海底捞被朋友抓去过生日" class="headerlink" title="海底捞被朋友抓去过生日"></a>海底捞被朋友抓去过生日</h3><p>虽然生日过了,但是他们想在北京陪我过一次,店里送了相框,正好一起拍照留作纪念,摆在新家当饰品,第一次在海底捞过生日社死。<br><img src="https://img-blog.csdnimg.cn/f036c8138043499cbb87e0a08dbaaebe.png?x-oss-process=image/watermark,type_d3F5LXplbmhlaQ,shadow_50,text_Q1NETiBA5pif6L6wIEpBQ0s=,size_20,color_FFFFFF,t_70,g_se,x_16" alt="在这里插入图片描述"></p><h3 id="四季民福故宫店北京烤鸭南锣鼓巷中关村中科院"><a href="#四季民福故宫店北京烤鸭南锣鼓巷中关村中科院" class="headerlink" title="四季民福故宫店北京烤鸭南锣鼓巷中关村中科院"></a>四季民福故宫店北京烤鸭南锣鼓巷中关村中科院</h3><p>吃了很离谱的<strong>豆汁</strong>和<strong>正宗北京烤鸭</strong><br><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/a11b441f926b40a5a099980b9bfd3bfd~tplv-k3u1fbpfcp-zoom-1.image" alt="image.png"><br><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/f2ba445123db46dab47b5171faef3260~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="环球影城,我要上魔法学院!"><a href="#环球影城,我要上魔法学院!" class="headerlink" title="环球影城,我要上魔法学院!"></a>环球影城,我要上魔法学院!</h3><p>在环球影城玩了一天,看过的电影不多,环球影城的这几个设施特别还原,喝了魔法黄油啤酒,买了小黄人的盲盒。</p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/48657fb270784604ba8c46026354f68f~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d9c6e71af21440299849e4a0405614cf~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><p><img src="https://p1-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/1ef7d47e452448cb9740409b5108ac8e~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h3 id="美团实习ing"><a href="#美团实习ing" class="headerlink" title="美团实习ing"></a>美团实习ing</h3><p>太菜了只会摸鱼,要开始新的生活了<br><img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/af8d96ae69b641289ed8dff61114b637~tplv-k3u1fbpfcp-watermark.image" alt="image.png"></p><h2 id="To-2022"><a href="#To-2022" class="headerlink" title="To 2022"></a>To 2022</h2><ul><li>请你继续保持对技术的热情,脚踏实地,努力学习,祝你好运</li><li>好好体验实习,工作,一个人独立生活的感觉</li><li>考完驾照,厨艺++,滑一次雪,想看看北京冬奥会</li><li>劳逸结合,自律坚持,锻炼身体,要顶天立地,才能闯出自己的路</li><li>读万卷书,行万里路,在更多地方留下你的足迹</li><li>请做到一件很多年你都没能做到的事情</li><li>以高标准要求自己,但是别当成包袱,好的坏的都是一场体验</li><li>还有不能忘记的音乐梦!</li><li>希望未来以后有机会当一个speaker</li></ul><p>「掘金链接:<a href="https://juejin.cn/post/7050494560102776869%22" target="_blank" rel="noopener">回首向来萧瑟处,归去,也无风雨也无晴|2021年终总结</a>」</p>]]></content>
<summary type="html">
<p><img src="/2022/01/08/%E5%9B%9E%E9%A6%96%E5%90%91%E6%9D%A5%E8%90%A7%E7%91%9F%E5%A4%84%EF%BC%8C%E5%BD%92%E5%8E%BB%EF%BC%8C%E4%B9%9F%E6%97%A0%E9%A3%8E%E9%9B%A8%E4%B9%9F%E6%97%A0%E6%99%B4%EF%BD%9C2021%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93/1.jpg" alt="66CC884772E6FB9A1E2BA12BC9E7C9C4.jpg"></p>
<h2 id="⏱2021-2022"><a href="#⏱2021-2022" class="headerlink" title="⏱2021-2022"></a>⏱2021-2022</h2><blockquote>
<p>说来惭愧,没想到在掘金这样神圣的技术社区,我的第一篇文章竟然是与技术无关的年终总结。(主要是想白嫖到周边棒球帽🧢,本来是打算发布已经写好的<a href="https://juejin.cn/post/7050660953846710302" target="_blank" rel="noopener">算法岗实习面经</a>,有兴趣的朋友可以继续看看)</p>
</blockquote>
<p>时间总是过的很快,我也是偶然间打开掘金看到的这个活动才意识到今年已经快要过去了,想了很久2021年对我而言到底意味着什么,好像过得很混乱,<strong>想的太多,做的太少,以前总是能在不确定性中规划好一些时间,但今年很多事情都是被时间推着走入不确定的维度。</strong></p>
</summary>
<category term="youth" scheme="https://jackyin.space/tags/youth/"/>
</entry>
<entry>
<title>PAT笔记</title>
<link href="https://jackyin.space/2021/10/16/PAT%E7%AC%94%E8%AE%B0/"/>
<id>https://jackyin.space/2021/10/16/PAT%E7%AC%94%E8%AE%B0/</id>
<published>2021-10-16T06:19:00.000Z</published>
<updated>2024-10-09T08:43:45.243Z</updated>
<content type="html"><![CDATA[<h1 id="PAT笔记"><a href="#PAT笔记" class="headerlink" title="PAT笔记"></a>PAT笔记</h1><p>这里记录了一下常用的保研机试,剑指offer和PAT题目分类解析,主要用来快速回顾和复习相关模板,以及数据结构相关的知识点。</p><h2 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h2><ul><li>在c++中处理字符串类型的题目时,我们一般使用<code>string</code>,有时候我们也使用<code>char[]</code>方式进行操作。</li><li><code>HH:MM:SS</code>可以直接通过字符串字典序排序</li><li>输入一个包含空格的字符串需要使用<code>getline(cin,s1)</code><h2 id="STL"><a href="#STL" class="headerlink" title="STL"></a>STL</h2></li><li><code>vector<int></code>本省具备有字典序比较的方法,重载了<code>< == ></code>的运算符号</li><li><code>vector<int>::iterator iter=find(vec.begin(),vec.end(),target); if(iter==vec.end()) cout << "Not found" << endl;</code></li></ul><a id="more"></a><h2 id="高精度"><a href="#高精度" class="headerlink" title="高精度"></a>高精度</h2><ul><li><code>int</code>的范围$-2 \times 10^9 - 2 \times 10^9$</li><li><code>long long</code> $-9 \times 10^{18} - 9 \times 10^{18}$</li><li>用<code>vector</code>按位存储<br><img src="https://img-blog.csdnimg.cn/5daf401d64574189a11ea27178441996.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><h2 id="进制转换"><a href="#进制转换" class="headerlink" title="进制转换"></a>进制转换</h2></li><li>其他进制化成10进制,采用秦九韶算法<br><img src="https://img-blog.csdnimg.cn/b70e15f5f88d44a681920d73cbe14f21.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></li></ul><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">typedef</span> <span class="hljs-keyword">long</span> <span class="hljs-keyword">long</span> LL;<br><span class="hljs-function">LL <span class="hljs-title">get</span><span class="hljs-params">(<span class="hljs-keyword">char</span> c)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(c<=<span class="hljs-string">'9'</span>) <span class="hljs-keyword">return</span> c-<span class="hljs-string">'0'</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> c-<span class="hljs-string">'a'</span> + <span class="hljs-number">10</span>;<br>}<br><br><span class="hljs-function">LL <span class="hljs-title">getnum</span><span class="hljs-params">(<span class="hljs-built_in">string</span> a,LL r)</span></span><br><span class="hljs-function"></span>{<br> LL res=<span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<a.size();i++)<br> {<br> res = res * r + get(a[i]);<br> }<br> <span class="hljs-keyword">return</span> res;<br>}<br></code></pre></td></tr></table></figure><ul><li>十进制转其他进制的方法,使用带余除法<br><img src="https://img-blog.csdnimg.cn/d9508f671e634bceb3732dfc41a08b60.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></li></ul><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">get</span><span class="hljs-params">(<span class="hljs-keyword">char</span> c)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(c<=<span class="hljs-string">'9'</span>) <span class="hljs-keyword">return</span> c-<span class="hljs-string">'0'</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> c-<span class="hljs-string">'a'</span> + <span class="hljs-number">10</span>;<br>}<br><br><br><span class="hljs-function"><span class="hljs-keyword">char</span> <span class="hljs-title">tochar</span><span class="hljs-params">(<span class="hljs-keyword">int</span> c)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(c<=<span class="hljs-number">9</span>) <span class="hljs-keyword">return</span> c+<span class="hljs-string">'0'</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-string">'a'</span> + c - <span class="hljs-number">10</span>;<br>}<br><br><span class="hljs-comment">//一个r进制数num转10进制</span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">numr_to10</span><span class="hljs-params">(<span class="hljs-built_in">string</span> num,<span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">int</span> res = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<num.size();i++)<br> {<br> res = res * r + get(num[i]);<br> }<br> <span class="hljs-keyword">return</span> res;<br>}<br><br><span class="hljs-comment">//一个10进制数num转r进制</span><br><span class="hljs-function"><span class="hljs-built_in">string</span> <span class="hljs-title">num10_tor</span><span class="hljs-params">(<span class="hljs-built_in">string</span> num,<span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">string</span> res;<br> <span class="hljs-keyword">int</span> n = numr_to10(num,<span class="hljs-number">10</span>); <span class="hljs-comment">//先转成10进制整型</span><br> <span class="hljs-keyword">while</span>(n)<br> {<br> <span class="hljs-comment">// cout<<tochar(n % r)<<endl;</span><br> res = tochar(n % r) + res;<br> n /= r;<br> }<br> <span class="hljs-keyword">return</span> res;<br>}<br><span class="hljs-comment">// cout<<numr_to10("6a",16)<<" "<<num10_tor("15",16)<<endl;</span><br></code></pre></td></tr></table></figure><h2 id="判断质数"><a href="#判断质数" class="headerlink" title="判断质数"></a>判断质数</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//判断一个数是否为质数</span><br><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">is_prime</span><span class="hljs-params">(<span class="hljs-keyword">int</span> n)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span> (n < <span class="hljs-number">2</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <span class="hljs-comment">// 1和0不是质数</span><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">2</span>;i*i<=n;i++)<br> {<br> <span class="hljs-keyword">if</span>(n % i == <span class="hljs-number">0</span>) <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>; <br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="手写堆排序"><a href="#手写堆排序" class="headerlink" title="手写堆排序"></a>手写堆排序</h2><p>堆是一个完全二叉树的结构,分为小根堆和大根堆两种结构。</p><ul><li>小根堆的递归定义:小根堆的每个节点都小于他的左右孩子节点的值,树的根节点为最小值。</li><li>大根堆的递归定义:大根堆的每个节点都大于他的左右孩子节点的值,树的根节点为最大值。<br>在STL当中可以使用<code>prioirty_queue</code>来轻松实现大根堆和小根堆,但是只能实现前3个功能,有时候我们不得不自己实现一个手写的堆,同时这样也能让我们更理解堆排序的过程。<br><img src="https://img-blog.csdnimg.cn/c16a9e7d8c5a433084cd31cb996086e2.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></li></ul><p>在AcWing基础课当中有两道经典例题,<a href="https://www.acwing.com/problem/content/841/" target="_blank" rel="noopener">AcWing 839. 模拟堆(这个复杂一点)</a>,<a href="https://www.acwing.com/problem/content/840/" target="_blank" rel="noopener">AcWing 838. 堆排序</a><br>这里给出堆排序的模板级代码</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><br><span class="hljs-keyword">int</span> heap[N],heapsize=<span class="hljs-number">0</span>;<br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">down</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span><span class="hljs-comment">// 参数下标</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">int</span> p =x;<br> <span class="hljs-keyword">if</span>(<span class="hljs-number">2</span>*x<=heapsize && heap[<span class="hljs-number">2</span>*x]<heap[p]) p = <span class="hljs-number">2</span>*x;<span class="hljs-comment">//左子树</span><br> <br> <span class="hljs-keyword">if</span>(<span class="hljs-number">2</span>*x+<span class="hljs-number">1</span><=heapsize && heap[<span class="hljs-number">2</span>*x+<span class="hljs-number">1</span>]<heap[p]) p=<span class="hljs-number">2</span>*x+<span class="hljs-number">1</span>;<span class="hljs-comment">//右子树</span><br> <br> <span class="hljs-keyword">if</span>(p!=x) <br> {<br> swap(heap[p],heap[x]); <span class="hljs-comment">//说明存在比父节点小的孩子节点</span><br> down(p); <span class="hljs-comment">//继续向下递归down</span><br> }<br>}<br><br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">up</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span><span class="hljs-comment">// 参数下标</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">while</span>(x / <span class="hljs-number">2</span> && heap[x] < heap[x/<span class="hljs-number">2</span>]) <span class="hljs-comment">//父节点比子节点大则交换</span><br> {<br> swap(heap[x],heap[x/<span class="hljs-number">2</span>]);<br> x >>= <span class="hljs-number">1</span>; <span class="hljs-comment">// x = x/2</span><br> }<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">int</span> n,m;<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d"</span>, &n, &m);<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">1</span>; i <= n; i ++ ) <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d"</span>, &heap[i]);<br> heapsize=n;<br> <br> <span class="hljs-comment">// O(n)建堆</span><br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = n / <span class="hljs-number">2</span>; i; i -- ) down(i);<br> <br> <span class="hljs-keyword">while</span> (m -- )<br> {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d "</span>,heap[<span class="hljs-number">1</span>]); <span class="hljs-comment">//最小值是小根堆的堆顶</span><br> <span class="hljs-comment">// 删除最小值,并重新建堆排序,从而获得倒数第二小的元素</span><br> heap[<span class="hljs-number">1</span>] = heap[heapsize];<br> heapsize--;<br> down(<span class="hljs-number">1</span>);<br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>STL写法:<code>priority_queue</code>默认是大根堆,<code>less<int></code>是对第一个参数的比较类,表示数字大的优先级越大,而<code>greater<int></code>表示数字小的优先级越大,可以实现结构体运算符重载。<br>首先要引入头文件:<code>#include<queue></code><br>大根堆:</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp">priority_queue<<span class="hljs-keyword">int</span>> q;<br>priority_queue<<span class="hljs-keyword">int</span>, <span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>, less<<span class="hljs-keyword">int</span>> >q;<br></code></pre></td></tr></table></figure><p>小根堆:</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp">priority_queue < <span class="hljs-keyword">int</span>, <span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>, greater<<span class="hljs-keyword">int</span>> > q;<br></code></pre></td></tr></table></figure><h2 id="树"><a href="#树" class="headerlink" title="树"></a>树</h2><p>树是一种特殊的数据结构形式,在做题的过程当中,根据我的经验当题目需要使用树结构的时候主要有以下几种模式。</p><ul><li><strong>二叉树形式</strong>,在二叉树模型下,我们可以根据题目建立出静态的树形结构,构建每个节点<strong>左右孩子索引表</strong>来建立树的结构同时实现对树的遍历。<strong>如果已知或可以求得节点之间的关系,可以通过节点的度数或者访问标记找到根节点。</strong>,当然也是可以通过邻接表的方式创建二叉树。</li><li>多叉树形式,多叉树形式其实又类似于<strong>无向连通图</strong>的概念,常通过创建<strong>邻接表</strong>或者<strong>临接矩阵</strong>的方式建立树,并实现进行树的遍历,也是可以根据节点关系求出根节点的。注意在临接表当中,边的数量一般大于节点数量的两倍即我们需要开票邻接表的边数空间为$M = 2 \times N + d$</li><li>森林,多连通块的方式,这种也是利用无向图的方式,以<strong>邻接表</strong>或者<strong>临接矩阵</strong>的方式构建树的结构,同时我们可以利用<strong>并查集</strong>的方式得到当前无向图中含有的连通块数量并找到根节点。</li></ul><p>二叉树左右孩子索引表模型</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">int</span> l[N],r[N]<span class="hljs-comment">// 第i个节点的左孩子和右孩子的索引</span><br><span class="hljs-keyword">bool</span> has_father[N]; <span class="hljs-comment">//建立树的时候判断一下当前节点有没有父节点,可用于寻找根节点</span><br><br><span class="hljs-comment">//初始化,-1表示子节点为空</span><br><span class="hljs-built_in">memset</span>(l,<span class="hljs-number">-1</span>,<span class="hljs-keyword">sizeof</span> l);<br><span class="hljs-built_in">memset</span>(r,<span class="hljs-number">-1</span>,<span class="hljs-keyword">sizeof</span> r);<br><br><span class="hljs-comment">// 查找根节点的过程</span><br><span class="hljs-keyword">if</span>(l[i]>=<span class="hljs-number">0</span>) has_father[l[i]]=<span class="hljs-literal">true</span>;<br><span class="hljs-keyword">if</span>(r[i]>=<span class="hljs-number">0</span>) has_father[r[i]]=<span class="hljs-literal">true</span>;<br><span class="hljs-comment">//查找根节点</span><br><span class="hljs-keyword">int</span> root = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">while</span>(has_father[root]) root++;<br></code></pre></td></tr></table></figure><p>二叉树的遍历过程(以先序遍历为例子)</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> root)</span></span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">if</span>(root==<span class="hljs-number">-1</span>) <span class="hljs-keyword">return</span>;<br><span class="hljs-built_in">cout</span><<root<<<span class="hljs-built_in">endl</span>;<br> <span class="hljs-keyword">if</span>(l[root]>=<span class="hljs-number">0</span>) dfs(l[root]);<br> <span class="hljs-keyword">if</span>(r[root]>=<span class="hljs-number">0</span>) dfs(r[root]);<br>}<br></code></pre></td></tr></table></figure><p>临接表模型</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> M = <span class="hljs-number">2</span> * N + <span class="hljs-number">10</span>;<br><span class="hljs-keyword">int</span> h[N];<span class="hljs-comment">//邻接表的N个节点头指针,h[i]表示以i为起点的,最新的一条边的编号</span><br><span class="hljs-keyword">int</span> e[M];<span class="hljs-comment">// e[i] 表示第i条边的所指向的终点</span><br><span class="hljs-keyword">int</span> ne[M];<span class="hljs-comment">// ne[i]表示与第i条边起点相同的下一条边的编号</span><br><span class="hljs-keyword">int</span> idx;<span class="hljs-comment">// idx表示边的编号,每增加一条边就++</span><br><br><span class="hljs-comment">// 添加一条从a到b的边,如果是无向图,每次添加时要add(a,b)和add(b,a)</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-keyword">int</span> a,<span class="hljs-keyword">int</span> b)</span></span><br><span class="hljs-function"></span>{<br>e[idx] = b; <span class="hljs-comment">// 第idx条边的终点为b</span><br>ne[idx] = h[a]; <span class="hljs-comment">// h[a] 和 第idx都是以a为起点的边,通过ne[idx]串联起来,找到上一条以a为起点的边h[a]</span><br>h[a] = idx ++; <span class="hljs-comment">// 更新当前以a为起点的边的最新编号</span><br>}<br><br><span class="hljs-comment">//初始化,-1表示节点为空</span><br><span class="hljs-built_in">memset</span>(h,<span class="hljs-number">-1</span>,<span class="hljs-keyword">sizeof</span> h);<br></code></pre></td></tr></table></figure><p>临接表遍历过程方法1</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">// x为起点,father为x的来源,防止节点遍历走回头路导致死循环</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x,<span class="hljs-keyword">int</span> father)</span></span><br><span class="hljs-function"></span>{<br><span class="hljs-built_in">cout</span><<x<<<span class="hljs-built_in">endl</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = h[x];~i;i=ne[i]) <span class="hljs-comment">// ~i就是i!=-1的意思</span><br> {<br> <span class="hljs-keyword">int</span> to = e[i];<br> <span class="hljs-keyword">if</span>(to==father) <span class="hljs-keyword">continue</span>;<br> dfs(to,x);<br> }<br>}<br>dfs(x,<span class="hljs-number">-1</span>);<br></code></pre></td></tr></table></figure><p>临接表遍历过程方法2</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">bool</span> isvisited[N];<br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span></span><br><span class="hljs-function"></span>{<br>isvisited[x]=<span class="hljs-literal">true</span>;<br><span class="hljs-built_in">cout</span><<x<<<span class="hljs-built_in">endl</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = h[x];~i;i=ne[i]) <br> {<br> <span class="hljs-keyword">int</span> to = e[i];<br> <span class="hljs-keyword">if</span>(isvisited[to]) <span class="hljs-keyword">continue</span>;<br> dfs(to);<br> }<br>}<br>dfs(x);<br></code></pre></td></tr></table></figure><h2 id="树的深度"><a href="#树的深度" class="headerlink" title="树的深度"></a>树的深度</h2><p>临接表模型:<a href="https://www.acwing.com/problem/content/1500/" target="_blank" rel="noopener">AcWing1498. 最深的根</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">getdepth</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x,<span class="hljs-keyword">int</span> father)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-comment">// cout<<"father"<<father<<" node"<<x<<endl;</span><br> <span class="hljs-keyword">int</span> depth = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = h[x];~i;i=ne[i]) <br> {<br> <span class="hljs-keyword">int</span> to = e[i];<br> <span class="hljs-keyword">if</span>(to==father) <span class="hljs-keyword">continue</span>;<br> depth = max(depth,getdepth(to,x)+<span class="hljs-number">1</span>);<br> }<br> <span class="hljs-keyword">return</span> depth;<br>}<br></code></pre></td></tr></table></figure><p>二叉树模型:<a href="https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/" target="_blank" rel="noopener">剑指 Offer 55 - I. 二叉树的深度</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Definition for a binary tree node.</span><br><span class="hljs-comment"> * struct TreeNode {</span><br><span class="hljs-comment"> * int val;</span><br><span class="hljs-comment"> * TreeNode *left;</span><br><span class="hljs-comment"> * TreeNode *right;</span><br><span class="hljs-comment"> * TreeNode(int x) : val(x), left(NULL), right(NULL) {}</span><br><span class="hljs-comment"> * };</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">maxDepth</span><span class="hljs-params">(TreeNode* root)</span> </span>{<br> <span class="hljs-keyword">if</span>(root==<span class="hljs-literal">NULL</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">return</span> max(maxDepth(root->left),maxDepth(root->right))+<span class="hljs-number">1</span>;<br> }<br>};<br></code></pre></td></tr></table></figure><p>多叉树模型(该题也是求叶子节点个数的经典写法):<a href="https://www.acwing.com/problem/content/1478/" target="_blank" rel="noopener">AcWing 1476. 数叶子结点</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">int</span> max_depth = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">int</span> cnt[N];<br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x,<span class="hljs-keyword">int</span> depth)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-comment">//说明是叶子节点</span><br> <span class="hljs-keyword">if</span>(h[x]==<span class="hljs-number">-1</span>)<br> {<br> cnt[depth]++;<br> max_depth = max(max_depth,depth);<br> <span class="hljs-keyword">return</span>;<br> }<br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=h[x];~i;i=ne[i])<br> {<br> dfs(e[i],depth+<span class="hljs-number">1</span>);<br> }<br>}<br>dfs(root,<span class="hljs-number">0</span>)<br><span class="hljs-comment">//输出每一层的叶子个数</span><br><span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<=max_depth;i++) <span class="hljs-built_in">cout</span><<<span class="hljs-string">" "</span><<cnt[i];<br></code></pre></td></tr></table></figure><h2 id="二叉搜索树"><a href="#二叉搜索树" class="headerlink" title="二叉搜索树"></a>二叉搜索树</h2><p>二叉搜索树 (BST) 递归定义为具有以下属性的二叉树:</p><ul><li>若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值</li><li>若它的右子树不空,则右子树上所有结点的值均大于或等于它的根结点的值</li><li>它的左、右子树也分别为二叉搜索树</li></ul><p><strong>二叉搜索树的中序遍历一定是有序的</strong></p><h2 id="完全二叉树"><a href="#完全二叉树" class="headerlink" title="完全二叉树"></a>完全二叉树</h2><p>完全二叉树 (CBT) 定义为除最深层外的其他层的结点数都达到最大个数,最深层的所有结点都连续集中在最左边的二叉树。<br>构造完全二叉树的方法,可以直接开辟一个一维数组利用左右孩子与根节点的下标映射关系。如果通过中序遍历的方式以单调递增的方式来赋值则构造出了一颗完全二叉搜索树。<br><img src="https://img-blog.csdnimg.cn/fd06bb725e7140f0b1349101b932a9fa.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>完全二叉树的赋值填充和构造过程(这里我们以中序遍历为例子):<br>例题:<a href="https://www.acwing.com/problem/content/1552/" target="_blank" rel="noopener">AcWing 1550. 完全二叉搜索树</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//中序遍历填充数据</span><br><span class="hljs-keyword">int</span> cnt; <span class="hljs-comment">//记录已经赋值的节点下标</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span> <span class="hljs-comment">// 根节点为1-n</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(<span class="hljs-number">2</span>*x <=n) dfs(<span class="hljs-number">2</span>*x);<br> h[x] = a[cnt++];<br> <span class="hljs-keyword">if</span>(<span class="hljs-number">2</span>*x+<span class="hljs-number">1</span><=n) dfs(<span class="hljs-number">2</span>*x+<span class="hljs-number">1</span>);<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dfs</span><span class="hljs-params">(<span class="hljs-keyword">int</span> u, <span class="hljs-keyword">int</span>& k)</span> <span class="hljs-comment">// 中序遍历,k引用实现下标迁移</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span> (u * <span class="hljs-number">2</span> <= n) dfs(u * <span class="hljs-number">2</span>, k);<br> tr[u] = w[k ++ ];<br> <span class="hljs-keyword">if</span> (u * <span class="hljs-number">2</span> + <span class="hljs-number">1</span> <= n) dfs(u * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>, k);<br>}<br></code></pre></td></tr></table></figure><p>完全二叉树的节点个数规律:</p><ul><li>具有n个结点的完全二叉树的深度为$\lfloor log_2{n} \rfloor+ 1$</li><li>完全二叉树如果为满二叉树,且深度为$k$则总节点个数为$2^{k}-1$</li><li>完全二叉树的第$i(i \geq 1)$层的节点数最大值为$2^{i-1}$</li><li>完全二叉树最后一层按从左到右的顺序进行编号,上面的层数皆为节点数的最大值,<strong>因此不会出现左子树为空,右子树存在的节点</strong></li><li>根据完全二叉树的结构可知:<strong>完全二叉树度为1的节点只能为1或者0</strong>,则有当节点总数为$n$时,如果$n$为奇数,则$n_0 = (n+1)/2$,如果$n$为偶数,则$n_0 = n / 2$<blockquote><p>关于最后一条性质的一些拓展<br><strong>二叉树的重要性质:在任意一棵二叉树中,若叶子结点的个数为$n_0$,度为2的结点数为$n_2$,则$n_0=n_2+1$</strong><br>证明:<br>假设该二叉树总共有$n$个结点$(n=n_0+n_1+n_2)$,则该二叉树总共会有$n-1$条边,度为2的结点会延伸出两条边,度为1的结点会延伸出1条边。<br>则有$n - 1 = n_0+n_1+n_2- 1= 2 \times n_2 + n_1$<br>联立两式得到:$n_0=n_2+1$<br>拓展到完全二叉树,因为完全二叉树度为1的节点只有0个或者1个。即$n_1 = 0 或 1$<br>则节点总数$n=n_0+n_1+n_2 = 2 *n_0 + n_1 - 1$<br>由于节点个数必须为整数,因此可以得到以下结论:<br>当$n$为奇数时,必须使得$n_1=0$,则$n_0=(n + 1) / 2,n_2=n_0-1=(n + 1) / 2-1$<br>当$n$为偶数时,必须使得$n_1=1$,则$n_0=n / 2,n_2=n_0-1=n /2 -1$</p></blockquote></li></ul><p>例题(递归解法):<a href="https://leetcode-cn.com/problems/count-complete-tree-nodes/" target="_blank" rel="noopener">leetcode 完全二叉树的节点个数</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">/**</span><br><span class="hljs-comment"> * Definition for a binary tree node.</span><br><span class="hljs-comment"> * public class TreeNode {</span><br><span class="hljs-comment"> * int val;</span><br><span class="hljs-comment"> * TreeNode left;</span><br><span class="hljs-comment"> * TreeNode right;</span><br><span class="hljs-comment"> * TreeNode(int x) { val = x; }</span><br><span class="hljs-comment"> * }</span><br><span class="hljs-comment"> */</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br> <span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">int</span> <span class="hljs-title">countNodes</span><span class="hljs-params">(TreeNode root)</span> </span>{<br> <span class="hljs-keyword">return</span> root==null ? <span class="hljs-number">0</span>:countNodes(root.left)+countNodes(root.right)+<span class="hljs-number">1</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><p>完全二叉树</p><h2 id="二叉平衡树"><a href="#二叉平衡树" class="headerlink" title="二叉平衡树"></a>二叉平衡树</h2><h4 id="AVL树"><a href="#AVL树" class="headerlink" title="AVL树"></a>AVL树</h4><ul><li>AVL树是一种自平衡二叉搜索树。</li><li>在AVL树中,任何节点的两个子树的高度最多相差 1 个。</li><li>如果某个时间,某节点的两个子树之间的高度差超过 1,则将通过树旋转进行重新平衡以恢复此属性。</li><li>AVL本质上还是维护一个二叉搜索树,所以不管如果旋转,其中序遍历依旧是不变的。<br>旋转法则:</li></ul><p><img src="https://img-blog.csdnimg.cn/d38acda60f184d0987848cee7407709e.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述">AVL插入分为一下几种情况:</p><ul><li>LL型:新节点的插入位置在A的左孩子的左子树上,则右旋A</li><li>RR型:新节点的插入位置在A的右孩子的右子树上,则左旋A</li><li>LR型:新节点的插入位置在A的左孩子的右子树上,则左旋B,右旋A</li><li>RL型:新节点的插入位置在A的右孩子的左子树上,则右旋B,左旋A<br><img src="https://img-blog.csdnimg.cn/9f339a4b86a84d42aafabfc3b35b5e8a.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></li></ul><h4 id="红黑树"><a href="#红黑树" class="headerlink" title="红黑树"></a>红黑树</h4><p>数据结构中有一类平衡的二叉搜索树,称为红黑树。<br>它具有以下 5 个属性:</p><ul><li>节点是红色或黑色。</li><li>根节点是黑色。</li><li>所有叶子都是黑色。(叶子是 NULL节点)</li><li>每个红色节点的两个子节点都是黑色。</li><li>从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。<br><img src="https://img-blog.csdnimg.cn/19db0e5df72e4c66bcee2a26990e9b6d.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><h2 id="图论相关"><a href="#图论相关" class="headerlink" title="图论相关"></a>图论相关</h2><h4 id="并查集"><a href="#并查集" class="headerlink" title="并查集"></a>并查集</h4>经典例题:<a href="https://www.acwing.com/problem/content/838/" target="_blank" rel="noopener">AcWing 836. 合并集合</a></li></ul><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><cstring></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><algorithm></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><br><span class="hljs-keyword">int</span> p[N];<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">find</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span> <span class="hljs-comment">// 查找x的祖先节点,并在回溯的过程当中进行路径压缩,将各节点直接指向根节点</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(x!=p[x]) p[x] = find(p[x]); <span class="hljs-comment">// x和p[x]不相等,则继续向上找父节点的父节点</span><br> <span class="hljs-keyword">return</span> p[x];<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">int</span> n;<br> <span class="hljs-keyword">int</span> m;<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d"</span>, &n, &m);<br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">1</span>;i<=n;i++) <br> p[i]=i;<br><br> <span class="hljs-keyword">while</span> (m -- )<br> {<br> <span class="hljs-keyword">char</span> op[<span class="hljs-number">2</span>];<br> <span class="hljs-keyword">int</span> a,b;<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%s%d%d"</span>, op,&a,&b);<br> <span class="hljs-keyword">int</span> roota = find(a);<br> <span class="hljs-keyword">int</span> rootb = find(b);<br> <span class="hljs-keyword">if</span>(op[<span class="hljs-number">0</span>]==<span class="hljs-string">'M'</span>)<br> {<br><br> <span class="hljs-keyword">if</span>(roota == rootb) <span class="hljs-keyword">continue</span>;<br> p[roota] = rootb; <span class="hljs-comment">// root merge</span><br> }<br> <span class="hljs-keyword">else</span><br> {<br> <span class="hljs-built_in">cout</span><< (roota==rootb ? <span class="hljs-string">"Yes"</span>:<span class="hljs-string">"No"</span>)<<<span class="hljs-built_in">endl</span>;<br> }<br><br> }<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="dijstra算法"><a href="#dijstra算法" class="headerlink" title="dijstra算法"></a>dijstra算法</h4><ul><li>临接矩阵形式,适用于点的数量$N < 1000$的情形,朴素算法即可解决</li><li>邻接表形式,当$N>10000$,需要添加堆优化<br>一般来说堆优化版本的考试用的不多,这里就只介绍了朴素版本。<br><a href="https://www.acwing.com/problem/content/851/" target="_blank" rel="noopener">Dijkstra求最短路 I</a></li></ul><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><cstring></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><algorithm></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">510</span>;<br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> inf = <span class="hljs-number">0x3f3f3f3f</span>;<br><span class="hljs-keyword">int</span> n,m;<br><span class="hljs-keyword">int</span> g[N][N]; <span class="hljs-comment">// 稠密图使用邻接矩阵</span><br><span class="hljs-keyword">int</span> dist[N]; <span class="hljs-comment">// 存储距离</span><br><span class="hljs-keyword">bool</span> vis[N]; <span class="hljs-comment">// 标志到该节点的距离是否已经被规整为最短距离</span><br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">dijkstra</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">memset</span>(dist, inf, <span class="hljs-keyword">sizeof</span> dist);<br> dist[x] = <span class="hljs-number">0</span>;<br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<n;i++)<span class="hljs-comment">//外层循环n次遍历每个节点</span><br> {<br> <span class="hljs-keyword">int</span> t= <span class="hljs-number">-1</span>;<br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">1</span>;j<=n;j++)<br> {<br> <span class="hljs-keyword">if</span>(!vis[j]&&(t==<span class="hljs-number">-1</span> || dist[t]>dist[j])) t =j;<br> }<br> <span class="hljs-keyword">if</span>(t==<span class="hljs-number">-1</span>) <span class="hljs-keyword">break</span>;<br> vis[t]=<span class="hljs-literal">true</span>;<br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">1</span>;j<=n;j++)<br> {<br> <span class="hljs-keyword">if</span>(!vis[j])<br> {<br> dist[j] = min(dist[j],dist[t]+g[t][j]);<br> }<br> }<br> }<br><br> <span class="hljs-keyword">if</span>(dist[n]==inf) <span class="hljs-built_in">puts</span>(<span class="hljs-string">"-1"</span>);<br> <span class="hljs-keyword">else</span> <span class="hljs-built_in">cout</span><<dist[n]<<<span class="hljs-built_in">endl</span>;<br><br>}<br><br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d"</span>, &n, &m);<br> <span class="hljs-built_in">memset</span>(g, inf, <span class="hljs-keyword">sizeof</span> g);<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<m;i++)<br> {<br> <span class="hljs-keyword">int</span> x,y,z;<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d%d"</span>, &x, &y,&z);<br> <span class="hljs-keyword">if</span>(x==y) g[x][y]=<span class="hljs-number">0</span>; <span class="hljs-comment">// 自环</span><br> g[x][y] = min(g[x][y],z); <span class="hljs-comment">// 重边仅记录最小的边</span><br> }<br><br> dijkstra(<span class="hljs-number">1</span>);<br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="最小生成树Prime"><a href="#最小生成树Prime" class="headerlink" title="最小生成树Prime"></a>最小生成树Prime</h4><p><a href="https://www.acwing.com/activity/content/code/content/1219581/" target="_blank" rel="noopener">AcWing 858.Prime算法求最小生成树</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">//这里填你的代码^^</span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><cstring></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><algorithm></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">510</span>, INF = <span class="hljs-number">0x3f3f3f3f</span>;<br><span class="hljs-keyword">int</span> n,m;<br><br><span class="hljs-keyword">int</span> g[N][N]; <span class="hljs-comment">//稠密图使用prim和邻接矩阵</span><br><span class="hljs-keyword">int</span> dist[N]; <br><span class="hljs-keyword">bool</span> isvisited[N];<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">prime</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">memset</span>(dist, <span class="hljs-number">0x3f</span>, <span class="hljs-keyword">sizeof</span> dist);<br> <span class="hljs-keyword">int</span> res = <span class="hljs-number">0</span>;<br> dist[x]=<span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<n;i++)<br> {<br> <span class="hljs-keyword">int</span> t=<span class="hljs-number">-1</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">1</span>;j<=n;j++)<br> <span class="hljs-keyword">if</span>(!isvisited[j] && (t==<span class="hljs-number">-1</span> || dist[t] > dist[j]))<br> t= j;<br><br> <span class="hljs-keyword">if</span>(dist[t] == INF) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> <span class="hljs-comment">//标记访问</span><br> res += dist[t];<br> isvisited[t]=<span class="hljs-literal">true</span>;<br><br> <span class="hljs-comment">//更新dist</span><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=<span class="hljs-number">1</span>;j<=n;j++)<br> {<br> dist[j] = min(dist[j],g[t][j]); <br> }<br> }<br> <span class="hljs-keyword">return</span> res;<br>}<br><br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d"</span>, &n, &m);<br> <span class="hljs-built_in">memset</span>(g, <span class="hljs-number">0x3f</span>, <span class="hljs-keyword">sizeof</span> g);<br> <span class="hljs-keyword">while</span> (m -- )<br> {<br> <span class="hljs-keyword">int</span> a,b,c;<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d%d"</span>, &a, &b,&c);<br> g[a][b] = g[b][a] = min(g[a][b],c); <span class="hljs-comment">//无向图</span><br> }<br><br><br> <span class="hljs-keyword">int</span> t = prime(<span class="hljs-number">1</span>);<br><br> <span class="hljs-keyword">if</span>(t==<span class="hljs-number">-1</span>)<br> <span class="hljs-built_in">cout</span><<<span class="hljs-string">"impossible"</span><<<span class="hljs-built_in">endl</span>;<br> <span class="hljs-keyword">else</span><br> <span class="hljs-built_in">cout</span><<t<<<span class="hljs-built_in">endl</span>;<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="最小生成树Kruskal"><a href="#最小生成树Kruskal" class="headerlink" title="最小生成树Kruskal"></a>最小生成树Kruskal</h4><p><a href="https://www.acwing.com/problem/content/861/" target="_blank" rel="noopener">AcWing859.Kruskal算法求最小生成树</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><cstring></span></span><br><span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string"><algorithm></span></span><br><br><span class="hljs-keyword">using</span> <span class="hljs-keyword">namespace</span> <span class="hljs-built_in">std</span>;<br><br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>, INF =<span class="hljs-number">0x3f3f3f3f</span>;<br><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> M = <span class="hljs-number">2</span>*N;<br><br><span class="hljs-keyword">int</span> n,m;<br><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Edge</span></span><br><span class="hljs-class">{</span><br> <span class="hljs-keyword">int</span> x;<br> <span class="hljs-keyword">int</span> y;<br> <span class="hljs-keyword">int</span> w;<br> <span class="hljs-keyword">bool</span> <span class="hljs-keyword">operator</span> < (<span class="hljs-keyword">const</span> Edge & E) <span class="hljs-keyword">const</span><br> {<br> <span class="hljs-keyword">return</span> w < E.w;<br> }<br>}edge[M];<br><br><span class="hljs-keyword">int</span> p[N]; <span class="hljs-comment">//并查集</span><br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">find</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span><span class="hljs-comment">//找祖宗节点</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(x!=p[x]) p[x] = find(p[x]);<br> <span class="hljs-keyword">return</span> p[x];<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">kruskal</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">int</span> res = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">int</span> cnt=<span class="hljs-number">0</span>;<br> sort(edge,edge+m);<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">1</span>;i<=n;i++) p[i]=i;<span class="hljs-comment">//初始化并查集</span><br><br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=<span class="hljs-number">0</span>;i<m;i++)<br> {<br> <span class="hljs-keyword">int</span> x = edge[i].x, y = edge[i].y, w = edge[i].w;<br><br> <span class="hljs-keyword">int</span> a = find(x);<br> <span class="hljs-keyword">int</span> b = find(y);<br> <span class="hljs-comment">//不是连通的</span><br> <span class="hljs-keyword">if</span>(a!=b)<br> {<br> p[b] = a;<br> res += w;<br> cnt++;<br> }<br> }<br> <span class="hljs-comment">//路径数量<n-1说明不连通</span><br> <span class="hljs-keyword">if</span> (cnt<n<span class="hljs-number">-1</span>) <span class="hljs-keyword">return</span> INF;<br> <span class="hljs-keyword">return</span> res;<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d"</span>, &n, &m);<br> <span class="hljs-keyword">for</span> (<span class="hljs-keyword">int</span> i = <span class="hljs-number">0</span>; i < m; i ++ )<br> {<br> <span class="hljs-built_in">scanf</span>(<span class="hljs-string">"%d%d%d"</span>, &edge[i].x, &edge[i].y, &edge[i].w);<br> }<br><br> <span class="hljs-keyword">int</span> t = kruskal();<br><br> <span class="hljs-keyword">if</span>(t == INF) <span class="hljs-built_in">cout</span><< <span class="hljs-string">"impossible"</span><<<span class="hljs-built_in">endl</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-built_in">cout</span><<t<<<span class="hljs-built_in">endl</span>;<br><br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><h4 id="哈密顿图"><a href="#哈密顿图" class="headerlink" title="哈密顿图"></a>哈密顿图</h4><ul><li>通过图中所有顶点一次且仅一次的通路称为哈密顿通路。</li><li>通过图中所有顶点一次且仅一次的回路称为哈密顿回路。</li><li>具有哈密顿回路的图称为哈密顿图。</li><li>具有哈密顿通路而不具有哈密顿回路的图称为半哈密顿图</li></ul><h4 id="欧拉图"><a href="#欧拉图" class="headerlink" title="欧拉图"></a>欧拉图</h4><ul><li>通过图中所有边恰好一次且行遍所有顶点的通路称为欧拉通路。</li><li>通过图中所有边恰好一次且行遍所有顶点的回路称为欧拉回路。</li><li>具有欧拉回路的无向图或有向图称为欧拉图。</li><li>具有欧拉通路但不具有欧拉回路的无向图或有向图称为半欧拉图。</li><li><strong>如果一个连通图的所有顶点的度数都为偶数,那么这个连通图具有欧拉回路,且这个图被称为欧拉图。</strong></li><li><strong>如果一个连通图中有两个顶点的度数为奇数,其他顶点的度数为偶数,那么所有欧拉路径都从其中一个度数为奇数的顶点开始,并在另一个度数为奇数的顶点结束。</strong></li></ul><p><img src="https://img-blog.csdnimg.cn/9ce67d5dee3e4719aa4e38ca85de9564.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="数学"><a href="#数学" class="headerlink" title="数学"></a>数学</h2><h2 id="gcd"><a href="#gcd" class="headerlink" title="gcd"></a>gcd</h2><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function">LL <span class="hljs-title">gcd</span><span class="hljs-params">(LL a, LL b)</span> <span class="hljs-comment">// 欧几里得算法</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">return</span> b ? gcd(b, a % b) : a;<br>}<br></code></pre></td></tr></table></figure><h2 id="1的个数-数位dp"><a href="#1的个数-数位dp" class="headerlink" title="1的个数(数位dp)"></a>1的个数(数位dp)</h2><p><a href="https://www.acwing.com/problem/content/1535/" target="_blank" rel="noopener">ACWing1533.1的个数</a><br><a href="https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/" target="_blank" rel="noopener">剑指 Offer 43. 1~n 整数中 1 出现的次数</a></p><blockquote><p>给定一个数字 N,请你计算 1∼N 中一共出现了多少个数字 1。<br>例如,N=12 时,一共出现了 5 个数字 1,分别出现在 1,10,11,12 中。</p></blockquote><p>解题思路:<a href="https://leetcode-cn.com/problems/1nzheng-shu-zhong-1chu-xian-de-ci-shu-lcof/solution/python3si-lu-dai-ma-10fen-zhong-jiang-qi-9btr/" target="_blank" rel="noopener">相关视频链接</a><br><img src="https://img-blog.csdnimg.cn/49c9da0165364f10bde9df685f02be67.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">countDigitOne</span><span class="hljs-params">(<span class="hljs-keyword">int</span> n)</span> </span>{<br> <span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>> num;<br> <span class="hljs-keyword">while</span>(n) num.push_back(n%<span class="hljs-number">10</span>), n/=<span class="hljs-number">10</span>;<br> <span class="hljs-keyword">int</span> res = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i=num.size()<span class="hljs-number">-1</span>;i>=<span class="hljs-number">0</span>;i--)<br> {<br> <span class="hljs-keyword">int</span> d = num[i];<br> <span class="hljs-keyword">int</span> left=<span class="hljs-number">0</span>,right=<span class="hljs-number">0</span>,power=<span class="hljs-number">1</span>;<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=num.size()<span class="hljs-number">-1</span>;j>i;j--) left = left * <span class="hljs-number">10</span> + num[j];<br> <span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> j=i<span class="hljs-number">-1</span>;j>=<span class="hljs-number">0</span>;j--) right = right * <span class="hljs-number">10</span> + num[j], power*=<span class="hljs-number">10</span>;<br><br> <span class="hljs-keyword">if</span>(d==<span class="hljs-number">0</span>) res += left*power;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span>(d==<span class="hljs-number">1</span>) res += left*power + right + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">else</span> res += (left+<span class="hljs-number">1</span>) * power;<br> }<br> <span class="hljs-keyword">return</span> res;<br> }<br>};<br></code></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="PAT笔记"><a href="#PAT笔记" class="headerlink" title="PAT笔记"></a>PAT笔记</h1><p>这里记录了一下常用的保研机试,剑指offer和PAT题目分类解析,主要用来快速回顾和复习相关模板,以及数据结构相关的知识点。</p>
<h2 id="字符串"><a href="#字符串" class="headerlink" title="字符串"></a>字符串</h2><ul>
<li>在c++中处理字符串类型的题目时,我们一般使用<code>string</code>,有时候我们也使用<code>char[]</code>方式进行操作。</li>
<li><code>HH:MM:SS</code>可以直接通过字符串字典序排序</li>
<li>输入一个包含空格的字符串需要使用<code>getline(cin,s1)</code><h2 id="STL"><a href="#STL" class="headerlink" title="STL"></a>STL</h2></li>
<li><code>vector&lt;int&gt;</code>本省具备有字典序比较的方法,重载了<code>&lt; == &gt;</code>的运算符号</li>
<li><code>vector&lt;int&gt;::iterator iter=find(vec.begin(),vec.end(),target); if(iter==vec.end()) cout &lt;&lt; &quot;Not found&quot; &lt;&lt; endl;</code></li>
</ul>
</summary>
<category term="数据结构" scheme="https://jackyin.space/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>集成学习专题——Xgboost&LightGBM</title>
<link href="https://jackyin.space/2021/06/18/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94Xgboost-LightGBM/"/>
<id>https://jackyin.space/2021/06/18/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94Xgboost-LightGBM/</id>
<published>2021-06-17T16:11:00.000Z</published>
<updated>2024-10-09T08:43:45.344Z</updated>
<content type="html"><![CDATA[<h1 id="XGBoost算法"><a href="#XGBoost算法" class="headerlink" title="XGBoost算法"></a>XGBoost算法</h1><p>XGBoost是陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。<strong>XGBoost本质上还是一个GBDT,但是力争把速度和效率发挥到极致,所以叫X (Extreme) GBoosted,</strong> 包括前面说过,两者都是boosting方法。XGBoost是一个优化的分布式梯度增强库,旨在实现高效,灵活和便携。 它在Gradient Boosting框架下实现机器学习算法。 XGBoost提供了<strong>并行树提升</strong>(也称为GBDT,GBM),可以快速准确地解决许多数据科学问题。 相同的代码在主要的分布式环境(Hadoop,SGE,MPI)上运行,并且可以解决超过数十亿个样例的问题。XGBoost利用了核外计算并且能够使数据科学家在一个主机上处理数亿的样本数据。最终,将这些技术进行结合来做一个端到端的系统以最少的集群系统来扩展到更大的数据集上。Xgboost<strong>以CART决策树为子模型</strong>,通过Gradient Tree Boosting实现多棵CART树的集成学习,得到最终模型。下面我们来看看XGBoost的最终模型构建:</p><a id="more"></a> <p>引用陈天奇的论文,我们的数据为:$\mathcal{D}=\left{\left(\mathbf{x}<em>{i}, y</em>{i}\right)\right}\left(|\mathcal{D}|=n, \mathbf{x}<em>{i} \in \mathbb{R}^{m}, y</em>{i} \in \mathbb{R}\right)$<br>(1) 构造目标函数:<br>假设有K棵树,则第i个样本的输出为$\hat{y}<em>{i}=\phi\left(\mathrm{x}</em>{i}\right)=\sum_{k=1}^{K} f_{k}\left(\mathrm{x}<em>{i}\right), \quad f</em>{k} \in \mathcal{F}$,其中,$\mathcal{F}=\left{f(\mathbf{x})=w_{q(\mathbf{x})}\right}\left(q: \mathbb{R}^{m} \rightarrow T, w \in \mathbb{R}^{T}\right)$<br>因此,目标函数的构建为:<br>$$<br>\mathcal{L}(\phi)=\sum_{i} l\left(\hat{y}<em>{i}, y</em>{i}\right)+\sum_{k} \Omega\left(f_{k}\right)<br>$$<br>其中,$\sum_{i} l\left(\hat{y}<em>{i}, y</em>{i}\right)$为loss function,$\sum_{k} \Omega\left(f_{k}\right)$为正则化项。<br>(2) 叠加式的训练(Additive Training):<br>给定样本$x_i$,$\hat{y}<em>i^{(0)} = 0$(初始预测),$\hat{y}_i^{(1)} = \hat{y}_i^{(0)} + f_1(x_i)$,$\hat{y}_i^{(2)} = \hat{y}_i^{(0)} + f_1(x_i) + f_2(x_i) = \hat{y}_i^{(1)} + f_2(x_i)$…….以此类推,可以得到:$ \hat{y}_i^{(K)} = \hat{y}_i^{(K-1)} + f_K(x_i)$ ,其中,$ \hat{y}_i^{(K-1)} $ 为前K-1棵树的预测结果,$ f_K(x_i)$ 为第K棵树的预测结果。<br>因此,目标函数可以分解为:<br>$$<br>\mathcal{L}^{(K)}=\sum</em>{i=1}^{n} l\left(y_{i}, \hat{y}<em>{i}^{(K-1)}+f</em>{K}\left(\mathrm{x}<em>{i}\right)\right)+\sum</em>{k} \Omega\left(f_{k}\right)<br>$$<br>由于正则化项也可以分解为前K-1棵树的复杂度加第K棵树的复杂度,因此:$\mathcal{L}^{(K)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}<em>{i}^{(K-1)}+f</em>{K}\left(\mathrm{x}<em>{i}\right)\right)+\sum</em>{k=1} ^{K-1}\Omega\left(f_{k}\right)+\Omega\left(f_{K}\right)$,由于$\sum_{k=1} ^{K-1}\Omega\left(f_{k}\right)$在模型构建到第K棵树的时候已经固定,无法改变,因此是一个已知的常数,可以在最优化的时候省去,故:<br>$$<br>\mathcal{L}^{(K)}=\sum_{i=1}^{n} l\left(y_{i}, \hat{y}<em>{i}^{(K-1)}+f</em>{K}\left(\mathrm{x}<em>{i}\right)\right)+\Omega\left(f</em>{K}\right)<br>$$<br>(3) 使用泰勒级数<strong>近似</strong>目标函数:<br>$$<br>\mathcal{L}^{(K)} \simeq \sum_{i=1}^{n}\left[l\left(y_{i}, \hat{y}^{(K-1)}\right)+g_{i} f_{K}\left(\mathrm{x}<em>{i}\right)+\frac{1}{2} h</em>{i} f_{K}^{2}\left(\mathrm{x}<em>{i}\right)\right]+\Omega\left(f</em>{K}\right)<br>$$<br>其中,$g_{i}=\partial_{\hat{y}(t-1)} l\left(y_{i}, \hat{y}^{(t-1)}\right)$和$h_{i}=\partial_{\hat{y}^{(t-1)}}^{2} l\left(y_{i}, \hat{y}^{(t-1)}\right)$<br>在这里,我们补充下泰勒级数的相关知识:<br>在数学中,泰勒级数(英语:Taylor series)用无限项连加式——级数来表示一个函数,这些相加的项由函数在某一点的导数求得。具体的形式如下:<br>$$<br>f(x)=\frac{f\left(x_{0}\right)}{0 !}+\frac{f^{\prime}\left(x_{0}\right)}{1 !}\left(x-x_{0}\right)+\frac{f^{\prime \prime}\left(x_{0}\right)}{2 !}\left(x-x_{0}\right)^{2}+\ldots+\frac{f^{(n)}\left(x_{0}\right)}{n !}\left(x-x_{0}\right)^{n}+……<br>$$<br>由于$\sum_{i=1}^{n}l\left(y_{i}, \hat{y}^{(K-1)}\right)$在模型构建到第K棵树的时候已经固定,无法改变,因此是一个已知的常数,可以在最优化的时候省去,故:<br>$$<br>\tilde{\mathcal{L}}^{(K)}=\sum_{i=1}^{n}\left[g_{i} f_{K}\left(\mathbf{x}<em>{i}\right)+\frac{1}{2} h</em>{i} f_{K}^{2}\left(\mathbf{x}<em>{i}\right)\right]+\Omega\left(f</em>{K}\right)<br>$$<br>(4) 如何定义一棵树:<br>为了说明如何定义一棵树的问题,我们需要定义几个概念:第一个概念是样本所在的节点位置$q(x)$,第二个概念是有哪些样本落在节点j上$I_{j}=\left{i \mid q\left(\mathbf{x}<em>{i}\right)=j\right}$,第三个概念是每个结点的预测值$w</em>{q(x)}$,第四个概念是模型复杂度$\Omega\left(f_{K}\right)$,它可以由叶子节点的个数以及节点函数值来构建,则:$\Omega\left(f_{K}\right) = \gamma T+\frac{1}{2} \lambda \sum_{j=1}^{T} w_{j}^{2}$。如下图的例子:<br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t1tei3pE-1623946035306)(./16.png)]<br>$q(x_1) = 1,q(x_2) = 3,q(x_3) = 1,q(x_4) = 2,q(x_5) = 3$,$I_1 = {1,3},I_2 = {4},I_3 = {2,5}$,$w = (15,12,20)$<br>因此,目标函数用以上符号替代后:<br>$$<br>\begin{aligned}<br>\tilde{\mathcal{L}}^{(K)} &=\sum_{i=1}^{n}\left[g_{i} f_{K}\left(\mathrm{x}<em>{i}\right)+\frac{1}{2} h</em>{i} f_{K}^{2}\left(\mathrm{x}<em>{i}\right)\right]+\gamma T+\frac{1}{2} \lambda \sum</em>{j=1}^{T} w_{j}^{2} \<br>&=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T<br>\end{aligned}<br>$$<br>由于我们的目标就是最小化目标函数,现在的目标函数化简为一个关于w的二次函数:$\tilde{\mathcal{L}}^{(K)}=\sum_{j=1}^{T}\left[\left(\sum_{i \in I_{j}} g_{i}\right) w_{j}+\frac{1}{2}\left(\sum_{i \in I_{j}} h_{i}+\lambda\right) w_{j}^{2}\right]+\gamma T$,根据二次函数求极值的公式:$y=ax^2 bx c$求极值,对称轴在$x=-\frac{b}{2 a}$,极值为$y=\frac{4 a c-b^{2}}{4 a}$,因此:<br>$$<br>w_{j}^{*}=-\frac{\sum_{i \in I_{j}} g_{i}}{\sum_{i \in I_{j}} h_{i}+\lambda}<br>$$<br>以及<br>$$<br>\tilde{\mathcal{L}}^{(K)}(q)=-\frac{1}{2} \sum_{j=1}^{T} \frac{\left(\sum_{i \in I_{j}} g_{i}\right)^{2}}{\sum_{i \in I_{j}} h_{i}+\lambda}+\gamma T<br>$$<br>(5) 如何寻找树的形状:<br>不难发现,刚刚的讨论都是基于树的形状已经确定了计算$w$和$L$,但是实际上我们需要像学习决策树一样找到树的形状。因此,我们借助决策树学习的方式,使用目标函数的变化来作为分裂节点的标准。我们使用一个例子来说明:<br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nCrhWA4S-1623946035310)(./17.png)]<br>例子中有8个样本,分裂方式如下,因此:<br>$$<br>\tilde{\mathcal{L}}^{(old)} = -\frac{1}{2}[\frac{(g_7 + g_8)^2}{H_7+H_8 + \lambda} + \frac{(g_1 +…+ g_6)^2}{H_1+…+H_6 + \lambda}] + 2\gamma \<br>\tilde{\mathcal{L}}^{(new)} = -\frac{1}{2}[\frac{(g_7 + g_8)^2}{H_7+H_8 + \lambda} + \frac{(g_1 +…+ g_3)^2}{H_1+…+H_3 + \lambda} + \frac{(g_4 +…+ g_6)^2}{H_4+…+H_6 + \lambda}] + 3\gamma\<br>\tilde{\mathcal{L}}^{(old)} - \tilde{\mathcal{L}}^{(new)} = \frac{1}{2}[ \frac{(g_1 +…+ g_3)^2}{H_1+…+H_3 + \lambda} + \frac{(g_4 +…+ g_6)^2}{H_4+…+H_6 + \lambda} - \frac{(g_1+…+g_6)^2}{h_1+…+h_6+\lambda}] - \gamma<br>$$<br>因此,从上面的例子看出:分割节点的标准为$max{\tilde{\mathcal{L}}^{(old)} - \tilde{\mathcal{L}}^{(new)} }$,即:<br>$$<br>\mathcal{L}<em>{\text {split }}=\frac{1}{2}\left[\frac{\left(\sum</em>{i \in I_{L}} g_{i}\right)^{2}}{\sum_{i \in I_{L}} h_{i}+\lambda}+\frac{\left(\sum_{i \in I_{R}} g_{i}\right)^{2}}{\sum_{i \in I_{R}} h_{i}+\lambda}-\frac{\left(\sum_{i \in I} g_{i}\right)^{2}}{\sum_{i \in I} h_{i}+\lambda}\right]-\gamma<br>$$<br>(6.1) 精确贪心分裂算法:<br>XGBoost在生成新树的过程中,最基本的操作是节点分裂。节点分裂中最重 要的环节是找到最优特征及最优切分点, 然后将叶子节点按照最优特征和最优切 分点进行分裂。选取最优特征和最优切分点的一种思路如下:首先找到所有的候 选特征及所有的候选切分点, 一一求得其 $\mathcal{L}<em>{\text {split }}$, 然后选择$\mathcal{L}</em>{\mathrm{split}}$ 最大的特征及 对应切分点作为最优特征和最优切分点。我们称此种方法为精确贪心算法。该算法是一种启发式算法, 因为在节点分裂时只选择当前最优的分裂策略, 而非全局最优的分裂策略。精确贪心算法的计算过程如下所示: </p><p>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HRrbM0fp-1623946035312)(./18.png)] </p><p>(6.2) 基于直方图的近似算法:<br>精确贪心算法在选择最优特征和最优切分点时是一种十分有效的方法。它计算了所有特征、所有切分点的收益, 并从中选择了最优的, 从而保证模型能比较好地拟合了训练数据。但是当数据不能完全加载到内存时,精确贪心算法会变得 非常低效,算法在计算过程中需要不断在内存与磁盘之间进行数据交换,这是个非常耗时的过程, 并且在分布式环境中面临同样的问题。为了能够更高效地选 择最优特征及切分点, XGBoost提出一种近似算法来解决该问题。 基于直方图的近似算法的主要思想是:对某一特征寻找最优切分点时,首先对该特征的所有切分点按分位数 (如百分位) 分桶, 得到一个候选切分点集。特征的每一个切分点都可以分到对应的分桶; 然后,对每个桶计算特征统计G和H得到直方图, G为该桶内所有样本一阶特征统计g之和, H为该桶内所有样本二阶特征统计h之和; 最后,选择所有候选特征及候选切分点中对应桶的特征统计收益最大的作为最优特征及最优切分点。基于直方图的近似算法的计算过程如下所示: </p><ol><li>对于每个特征 $k=1,2, \cdots, m,$ 按分位数对特征 $k$ 分桶 $\Theta,$ 可得候选切分点, $S_{k}=\left{S_{k 1}, S_{k 2}, \cdots, S_{k l}\right}^{1}$</li><li>对于每个特征 $k=1,2, \cdots, m,$ 有:<br>$$<br>\begin{array}{l}<br>G_{k v} \leftarrow=\sum_{j \in\left{j \mid s_{k, v} \geq \mathbf{x}<em>{j k}>s</em>{k, v-1;}\right}} g_{j} \<br>H_{k v} \leftarrow=\sum_{j \in\left{j \mid s_{k, v} \geq \mathbf{x}<em>{j k}>s</em>{k, v-1;}\right}} h_{j}<br>\end{array}<br>$$ </li><li>类似精确贪心算法,依据梯度统计找到最大增益的候选切分点。<br>下面用一个例子说明基于直方图的近似算法:<br>假设有一个年龄特征,其特征的取值为18、19、21、31、36、37、55、57,我们需要使用近似算法找到年龄这个特征的最佳分裂点:<br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MKLD2poX-1623946035314)(./19.png)] </li></ol><p>近似算法实现了两种候选切分点的构建策略:全局策略和本地策略。全局策略是在树构建的初始阶段对每一个特征确定一个候选切分点的集合, 并在该树每一层的节点分裂中均采用此集合计算收益, 整个过程候选切分点集合不改变。本地策略则是在每一次节点分裂时均重新确定候选切分点。全局策略需要更细的分桶才能达到本地策略的精确度, 但全局策略在选取候选切分点集合时比本地策略更简单。<strong>在XGBoost系统中, 用户可以根据需求自由选择使用精确贪心算法、近似算法全局策略、近似算法本地策略, 算法均可通过参数进行配置。</strong> </p><p>以上是XGBoost的理论部分,下面我们对XGBoost系统进行详细的讲解:<br>官方文档:<a href="https://xgboost.readthedocs.io/en/latest/python/python_intro.html" target="_blank" rel="noopener">https://xgboost.readthedocs.io/en/latest/python/python_intro.html</a><br>知乎总结:<a href="https://zhuanlan.zhihu.com/p/143009353" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/143009353</a> </p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># XGBoost原生工具库的上手:</span><br><span class="hljs-keyword">import</span> xgboost <span class="hljs-keyword">as</span> xgb <span class="hljs-comment"># 引入工具库</span><br><span class="hljs-comment"># read in data</span><br>dtrain = xgb.DMatrix(<span class="hljs-string">'demo/data/agaricus.txt.train'</span>) <span class="hljs-comment"># XGBoost的专属数据格式,但是也可以用dataframe或者ndarray</span><br>dtest = xgb.DMatrix(<span class="hljs-string">'demo/data/agaricus.txt.test'</span>) <span class="hljs-comment"># # XGBoost的专属数据格式,但是也可以用dataframe或者ndarray</span><br><span class="hljs-comment"># specify parameters via map</span><br>param = {<span class="hljs-string">'max_depth'</span>:<span class="hljs-number">2</span>, <span class="hljs-string">'eta'</span>:<span class="hljs-number">1</span>, <span class="hljs-string">'objective'</span>:<span class="hljs-string">'binary:logistic'</span> } <span class="hljs-comment"># 设置XGB的参数,使用字典形式传入</span><br>num_round = <span class="hljs-number">2</span> <span class="hljs-comment"># 使用线程数</span><br>bst = xgb.train(param, dtrain, num_round) <span class="hljs-comment"># 训练</span><br><span class="hljs-comment"># make prediction</span><br>preds = bst.predict(dtest) <span class="hljs-comment"># 预测</span><br></code></pre></td></tr></table></figure><p>XGBoost的参数设置(括号内的名称为sklearn接口对应的参数名字):<br>推荐博客:<a href="https://link.zhihu.com/?target=https://blog.csdn.net/luanpeng825485697/article/details/79907149">https://link.zhihu.com/?target=https%3A//blog.csdn.net/luanpeng825485697/article/details/79907149</a><br>推荐官方文档:<a href="https://link.zhihu.com/?target=https://xgboost.readthedocs.io/en/latest/parameter.html">https://link.zhihu.com/?target=https%3A//xgboost.readthedocs.io/en/latest/parameter.html</a> </p><p><strong>XGBoost的参数分为三种:</strong></p><ul><li>通用参数:(两种类型的booster,因为tree的性能比线性回归好得多,因此我们很少用线性回归。)<ul><li>booster:使用哪个弱学习器训练,默认gbtree,可选gbtree,gblinear 或dart</li><li>nthread:用于运行XGBoost的并行线程数,默认为最大可用线程数</li><li>verbosity:打印消息的详细程度。有效值为0(静默),1(警告),2(信息),3(调试)。</li><li><strong>Tree Booster的参数:</strong><ul><li>eta(learning_rate):learning_rate,在更新中使用步长收缩以防止过度拟合,默认= 0.3,范围:[0,1];典型值一般设置为:0.01-0.2</li><li>gamma(min_split_loss):默认= 0,分裂节点时,损失函数减小值只有大于等于gamma节点才分裂,gamma值越大,算法越保守,越不容易过拟合,但性能就不一定能保证,需要平衡。范围:[0,∞]</li><li>max_depth:默认= 6,一棵树的最大深度。增加此值将使模型更复杂,并且更可能过度拟合。范围:[0,∞]</li><li>min_child_weight:默认值= 1,如果新分裂的节点的样本权重和小于min_child_weight则停止分裂 。这个可以用来减少过拟合,但是也不能太高,会导致欠拟合。范围:[0,∞]</li><li>max_delta_step:默认= 0,允许每个叶子输出的最大增量步长。如果将该值设置为0,则表示没有约束。如果将其设置为正值,则可以帮助使更新步骤更加保守。通常不需要此参数,但是当类极度不平衡时,它可能有助于逻辑回归。将其设置为1-10的值可能有助于控制更新。范围:[0,∞]</li><li>subsample:默认值= 1,构建每棵树对样本的采样率,如果设置成0.5,XGBoost会随机选择一半的样本作为训练集。范围:(0,1]</li><li>sampling_method:默认= uniform,用于对训练实例进行采样的方法。<ul><li>uniform:每个训练实例的选择概率均等。通常将subsample> = 0.5 设置 为良好的效果。</li><li>gradient_based:每个训练实例的选择概率与规则化的梯度绝对值成正比,具体来说就是$\sqrt{g^2+\lambda h^2}$,subsample可以设置为低至0.1,而不会损失模型精度。</li></ul></li><li>colsample_bytree:默认= 1,列采样率,也就是特征采样率。范围为(0,1]</li><li>lambda(reg_lambda):默认=1,L2正则化权重项。增加此值将使模型更加保守。</li><li>alpha(reg_alpha):默认= 0,权重的L1正则化项。增加此值将使模型更加保守。</li><li>tree_method:默认=auto,XGBoost中使用的树构建算法。<ul><li>auto:使用启发式选择最快的方法。<ul><li>对于小型数据集,exact将使用精确贪婪()。</li><li>对于较大的数据集,approx将选择近似算法()。它建议尝试hist,gpu_hist,用大量的数据可能更高的性能。(gpu_hist)支持。external memory外部存储器。</li></ul></li><li>exact:精确的贪婪算法。枚举所有拆分的候选点。</li><li>approx:使用分位数和梯度直方图的近似贪婪算法。</li><li>hist:更快的直方图优化的近似贪婪算法。(LightGBM也是使用直方图算法)</li><li>gpu_hist:GPU hist算法的实现。</li></ul></li><li>scale_pos_weight:控制正负权重的平衡,这对于不平衡的类别很有用。Kaggle竞赛一般设置sum(negative instances) / sum(positive instances),在类别高度不平衡的情况下,将参数设置大于0,可以加快收敛。</li><li>num_parallel_tree:默认=1,每次迭代期间构造的并行树的数量。此选项用于支持增强型随机森林。</li><li>monotone_constraints:可变单调性的约束,在某些情况下,如果有非常强烈的先验信念认为真实的关系具有一定的质量,则可以使用约束条件来提高模型的预测性能。(例如params_constrained[‘monotone_constraints’] = “(1,-1)”,(1,-1)我们告诉XGBoost对第一个预测变量施加增加的约束,对第二个预测变量施加减小的约束。)</li></ul></li><li><strong>Linear Booster的参数:</strong><ul><li>lambda(reg_lambda):默认= 0,L2正则化权重项。增加此值将使模型更加保守。归一化为训练示例数。</li><li>alpha(reg_alpha):默认= 0,权重的L1正则化项。增加此值将使模型更加保守。归一化为训练示例数。</li><li>updater:默认= shotgun。<ul><li>shotgun:基于shotgun算法的平行坐标下降算法。使用“ hogwild”并行性,因此每次运行都产生不确定的解决方案。</li><li>coord_descent:普通坐标下降算法。同样是多线程的,但仍会产生确定性的解决方案。</li></ul></li><li>feature_selector:默认= cyclic。特征选择和排序方法<ul><li>cyclic:通过每次循环一个特征来实现的。</li><li>shuffle:类似于cyclic,但是在每次更新之前都有随机的特征变换。</li><li>random:一个随机(有放回)特征选择器。</li><li>greedy:选择梯度最大的特征。(贪婪选择)</li><li>thrifty:近似贪婪特征选择(近似于greedy)</li></ul></li><li>top_k:要选择的最重要特征数(在greedy和thrifty内)</li></ul></li></ul></li><li>任务参数(这个参数用来控制理想的优化目标和每一步结果的度量方法。)<ul><li>objective:默认=reg:squarederror,表示最小平方误差。<ul><li><strong>reg:squarederror,最小平方误差。</strong></li><li><strong>reg:squaredlogerror,对数平方损失。$\frac{1}{2}[log(pred+1)-log(label+1)]^2$</strong></li><li><strong>reg:logistic,逻辑回归</strong></li><li>reg:pseudohubererror,使用伪Huber损失进行回归,这是绝对损失的两倍可微选择。</li><li><strong>binary:logistic,二元分类的逻辑回归,输出概率。</strong></li><li>binary:logitraw:用于二进制分类的逻辑回归,逻辑转换之前的输出得分。</li><li><strong>binary:hinge:二进制分类的铰链损失。这使预测为0或1,而不是产生概率。(SVM就是铰链损失函数)</strong></li><li>count:poisson –计数数据的泊松回归,泊松分布的输出平均值。</li><li>survival:cox:针对正确的生存时间数据进行Cox回归(负值被视为正确的生存时间)。</li><li>survival:aft:用于检查生存时间数据的加速故障时间模型。</li><li>aft_loss_distribution:survival:aft和aft-nloglik度量标准使用的概率密度函数。</li><li><strong>multi:softmax:设置XGBoost以使用softmax目标进行多类分类,还需要设置num_class(类数)</strong></li><li><strong>multi:softprob:与softmax相同,但输出向量,可以进一步重整为矩阵。结果包含属于每个类别的每个数据点的预测概率。</strong></li><li>rank:pairwise:使用LambdaMART进行成对排名,从而使成对损失最小化。</li><li>rank:ndcg:使用LambdaMART进行列表式排名,使标准化折让累积收益(NDCG)最大化。</li><li>rank:map:使用LambdaMART进行列表平均排名,使平均平均精度(MAP)最大化。</li><li>reg:gamma:使用对数链接进行伽马回归。输出是伽马分布的平均值。</li><li>reg:tweedie:使用对数链接进行Tweedie回归。</li><li>自定义损失函数和评价指标:<a href="https://xgboost.readthedocs.io/en/latest/tutorials/custom_metric_obj.html" target="_blank" rel="noopener">https://xgboost.readthedocs.io/en/latest/tutorials/custom_metric_obj.html</a></li></ul></li><li>eval_metric:验证数据的评估指标,将根据目标分配默认指标(回归均方根,分类误差,排名的平均平均精度),用户可以添加多个评估指标<ul><li><strong>rmse,均方根误差;</strong> <strong>rmsle:均方根对数误差;</strong> mae:平均绝对误差;mphe:平均伪Huber错误;<strong>logloss:负对数似然;</strong> <strong>error:二进制分类错误率;</strong></li><li><strong>merror:多类分类错误率;</strong> <strong>mlogloss:多类logloss;</strong> <strong>auc:曲线下面积;</strong> aucpr:PR曲线下的面积;ndcg:归一化累计折扣;map:平均精度;</li></ul></li><li>seed :随机数种子,[默认= 0]。</li></ul></li><li>命令行参数(这里不说了,因为很少用命令行控制台版本)</li></ul><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> IPython.display <span class="hljs-keyword">import</span> IFrame<br>IFrame(<span class="hljs-string">'https://xgboost.readthedocs.io/en/latest/parameter.html'</span>, width=<span class="hljs-number">1400</span>, height=<span class="hljs-number">800</span>)<br></code></pre></td></tr></table></figure><p><strong>XGBoost的调参说明:</strong></p><p>参数调优的一般步骤</p><ul><li><ol><li>确定学习速率和提升参数调优的初始值</li></ol></li><li><ol start="2"><li>max_depth 和 min_child_weight 参数调优</li></ol></li><li><ol start="3"><li>gamma参数调优</li></ol></li><li><ol start="4"><li>subsample 和 colsample_bytree 参数优</li></ol></li><li><ol start="5"><li>正则化参数alpha调优</li></ol></li><li><ol start="6"><li>降低学习速率和使用更多的决策树</li></ol></li></ul><p><strong>XGBoost详细攻略:</strong><br>具体的api请查看:<a href="https://xgboost.readthedocs.io/en/latest/python/python_api.html" target="_blank" rel="noopener">https://xgboost.readthedocs.io/en/latest/python/python_api.html</a><br>推荐github:<a href="https://github.com/dmlc/xgboost/tree/master/demo/guide-python" target="_blank" rel="noopener">https://github.com/dmlc/xgboost/tree/master/demo/guide-python</a></p><p><strong>安装XGBoost:</strong><br>方式1:pip3 install xgboost<br>方式2:pip install xgboost </p><p><strong>数据接口(XGBoost可处理的数据格式DMatrix)</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1.LibSVM文本格式文件</span><br>dtrain = xgb.DMatrix(<span class="hljs-string">'train.svm.txt'</span>)<br>dtest = xgb.DMatrix(<span class="hljs-string">'test.svm.buffer'</span>)<br><span class="hljs-comment"># 2.CSV文件(不能含类别文本变量,如果存在文本变量请做特征处理如one-hot)</span><br>dtrain = xgb.DMatrix(<span class="hljs-string">'train.csv?format=csv&label_column=0'</span>)<br>dtest = xgb.DMatrix(<span class="hljs-string">'test.csv?format=csv&label_column=0'</span>)<br><span class="hljs-comment"># 3.NumPy数组</span><br>data = np.random.rand(<span class="hljs-number">5</span>, <span class="hljs-number">10</span>) <span class="hljs-comment"># 5 entities, each contains 10 features</span><br>label = np.random.randint(<span class="hljs-number">2</span>, size=<span class="hljs-number">5</span>) <span class="hljs-comment"># binary target</span><br>dtrain = xgb.DMatrix(data, label=label)<br><span class="hljs-comment"># 4.scipy.sparse数组</span><br>csr = scipy.sparse.csr_matrix((dat, (row, col)))<br>dtrain = xgb.DMatrix(csr)<br><span class="hljs-comment"># pandas数据框dataframe</span><br>data = pandas.DataFrame(np.arange(<span class="hljs-number">12</span>).reshape((<span class="hljs-number">4</span>,<span class="hljs-number">3</span>)), columns=[<span class="hljs-string">'a'</span>, <span class="hljs-string">'b'</span>, <span class="hljs-string">'c'</span>])<br>label = pandas.DataFrame(np.random.randint(<span class="hljs-number">2</span>, size=<span class="hljs-number">4</span>))<br>dtrain = xgb.DMatrix(data, label=label)<br></code></pre></td></tr></table></figure><p>笔者推荐:先保存到XGBoost二进制文件中将使加载速度更快,然后再加载进来</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1.保存DMatrix到XGBoost二进制文件中</span><br>dtrain = xgb.DMatrix(<span class="hljs-string">'train.svm.txt'</span>)<br>dtrain.save_binary(<span class="hljs-string">'train.buffer'</span>)<br><span class="hljs-comment"># 2. 缺少的值可以用DMatrix构造函数中的默认值替换:</span><br>dtrain = xgb.DMatrix(data, label=label, missing=<span class="hljs-number">-999.0</span>)<br><span class="hljs-comment"># 3.可以在需要时设置权重:</span><br>w = np.random.rand(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>)<br>dtrain = xgb.DMatrix(data, label=label, missing=<span class="hljs-number">-999.0</span>, weight=w)<br></code></pre></td></tr></table></figure><p><strong>参数的设置方式:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd<br><span class="hljs-comment"># 加载并处理数据</span><br>df_wine = pd.read_csv(<span class="hljs-string">'https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data'</span>,header=<span class="hljs-literal">None</span>)<br>df_wine.columns = [<span class="hljs-string">'Class label'</span>, <span class="hljs-string">'Alcohol'</span>,<span class="hljs-string">'Malic acid'</span>, <span class="hljs-string">'Ash'</span>,<span class="hljs-string">'Alcalinity of ash'</span>,<span class="hljs-string">'Magnesium'</span>, <span class="hljs-string">'Total phenols'</span>,<br> <span class="hljs-string">'Flavanoids'</span>, <span class="hljs-string">'Nonflavanoid phenols'</span>,<span class="hljs-string">'Proanthocyanins'</span>,<span class="hljs-string">'Color intensity'</span>, <span class="hljs-string">'Hue'</span>,<span class="hljs-string">'OD280/OD315 of diluted wines'</span>,<span class="hljs-string">'Proline'</span>] <br>df_wine = df_wine[df_wine[<span class="hljs-string">'Class label'</span>] != <span class="hljs-number">1</span>] <span class="hljs-comment"># drop 1 class </span><br>y = df_wine[<span class="hljs-string">'Class label'</span>].values<br>X = df_wine[[<span class="hljs-string">'Alcohol'</span>,<span class="hljs-string">'OD280/OD315 of diluted wines'</span>]].values<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split <span class="hljs-comment"># 切分训练集与测试集</span><br><span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> LabelEncoder <span class="hljs-comment"># 标签化分类变量</span><br>le = LabelEncoder()<br>y = le.fit_transform(y)<br>X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=<span class="hljs-number">0.2</span>,random_state=<span class="hljs-number">1</span>,stratify=y)<br>dtrain = xgb.DMatrix(X_train, label=y_train)<br>dtest = xgb.DMatrix(X_test)<br><span class="hljs-comment"># 1.Booster 参数</span><br>params = {<br> <span class="hljs-string">'booster'</span>: <span class="hljs-string">'gbtree'</span>,<br> <span class="hljs-string">'objective'</span>: <span class="hljs-string">'multi:softmax'</span>, <span class="hljs-comment"># 多分类的问题</span><br> <span class="hljs-string">'num_class'</span>: <span class="hljs-number">10</span>, <span class="hljs-comment"># 类别数,与 multisoftmax 并用</span><br> <span class="hljs-string">'gamma'</span>: <span class="hljs-number">0.1</span>, <span class="hljs-comment"># 用于控制是否后剪枝的参数,越大越保守,一般0.1、0.2这样子。</span><br> <span class="hljs-string">'max_depth'</span>: <span class="hljs-number">12</span>, <span class="hljs-comment"># 构建树的深度,越大越容易过拟合</span><br> <span class="hljs-string">'lambda'</span>: <span class="hljs-number">2</span>, <span class="hljs-comment"># 控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。</span><br> <span class="hljs-string">'subsample'</span>: <span class="hljs-number">0.7</span>, <span class="hljs-comment"># 随机采样训练样本</span><br> <span class="hljs-string">'colsample_bytree'</span>: <span class="hljs-number">0.7</span>, <span class="hljs-comment"># 生成树时进行的列采样</span><br> <span class="hljs-string">'min_child_weight'</span>: <span class="hljs-number">3</span>,<br> <span class="hljs-string">'silent'</span>: <span class="hljs-number">1</span>, <span class="hljs-comment"># 设置成1则没有运行信息输出,最好是设置为0.</span><br> <span class="hljs-string">'eta'</span>: <span class="hljs-number">0.007</span>, <span class="hljs-comment"># 如同学习率</span><br> <span class="hljs-string">'seed'</span>: <span class="hljs-number">1000</span>,<br> <span class="hljs-string">'nthread'</span>: <span class="hljs-number">4</span>, <span class="hljs-comment"># cpu 线程数</span><br> <span class="hljs-string">'eval_metric'</span>:<span class="hljs-string">'auc'</span><br>}<br>plst = list(params.items())<br><span class="hljs-comment"># evallist = [(dtest, 'eval'), (dtrain, 'train')] # 指定验证集</span><br></code></pre></td></tr></table></figure><p><strong>训练:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 2.训练</span><br>num_round = <span class="hljs-number">10</span><br>bst = xgb.train(plst, dtrain, num_round)<br><span class="hljs-comment">#bst = xgb.train( plst, dtrain, num_round, evallist )</span><br></code></pre></td></tr></table></figure><p><strong>保存模型:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 3.保存模型</span><br>bst.save_model(<span class="hljs-string">'0001.model'</span>)<br><span class="hljs-comment"># dump model</span><br>bst.dump_model(<span class="hljs-string">'dump.raw.txt'</span>)<br><span class="hljs-comment"># dump model with feature map</span><br><span class="hljs-comment">#bst.dump_model('dump.raw.txt', 'featmap.txt')</span><br></code></pre></td></tr></table></figure><p><strong>加载保存的模型:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 4.加载保存的模型:</span><br>bst = xgb.Booster({<span class="hljs-string">'nthread'</span>: <span class="hljs-number">4</span>}) <span class="hljs-comment"># init model</span><br>bst.load_model(<span class="hljs-string">'0001.model'</span>) <span class="hljs-comment"># load data</span><br></code></pre></td></tr></table></figure><p><strong>设置早停机制:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 5.也可以设置早停机制(需要设置验证集)</span><br>train(..., evals=evals, early_stopping_rounds=<span class="hljs-number">10</span>)<br></code></pre></td></tr></table></figure><p><strong>预测:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 6.预测</span><br>ypred = bst.predict(dtest)<br></code></pre></td></tr></table></figure><p><strong>绘制重要性特征图:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 1.绘制重要性</span><br>xgb.plot_importance(bst)<br><span class="hljs-comment"># 2.绘制输出树</span><br><span class="hljs-comment">#xgb.plot_tree(bst, num_trees=2)</span><br><span class="hljs-comment"># 3.使用xgboost.to_graphviz()将目标树转换为graphviz</span><br><span class="hljs-comment">#xgb.to_graphviz(bst, num_trees=2)</span><br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/2021061800081388.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ir9Nm1ZN-1623946035316)(output_27_1.png)]"></p><h1 id="7-Xgboost算法案例"><a href="#7-Xgboost算法案例" class="headerlink" title="7. Xgboost算法案例"></a>7. Xgboost算法案例</h1><h2 id="分类案例"><a href="#分类案例" class="headerlink" title="分类案例"></a>分类案例</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> load_iris<br><span class="hljs-keyword">import</span> xgboost <span class="hljs-keyword">as</span> xgb<br><span class="hljs-keyword">from</span> xgboost <span class="hljs-keyword">import</span> plot_importance<br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> accuracy_score <span class="hljs-comment"># 准确率</span><br><span class="hljs-comment"># 加载样本数据集</span><br>iris = load_iris()<br>X,y = iris.data,iris.target<br>X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=<span class="hljs-number">0.2</span>, random_state=<span class="hljs-number">1234565</span>) <span class="hljs-comment"># 数据集分割</span><br><br>params = {<br> <span class="hljs-string">'booster'</span> : <span class="hljs-string">'gbtree'</span>,<br> <span class="hljs-string">'objective'</span>: <span class="hljs-string">'multi:softmax'</span>, <span class="hljs-comment"># 多分类的问题</span><br> <span class="hljs-string">'num_class'</span>: <span class="hljs-number">3</span>,<br> <span class="hljs-string">'gamma'</span>: <span class="hljs-number">0.1</span>, <span class="hljs-comment"># 默认= 0,分裂节点时,损失函数减小值只有大于等于gamma节点才分裂,gamma值越大,算法越保守,越不容易过拟合,但性能就不一定能保证,需要平衡。</span><br> <span class="hljs-string">'max_depth'</span>: <span class="hljs-number">12</span>, <span class="hljs-comment"># 树的最大深度</span><br> <span class="hljs-string">'lambda'</span>: <span class="hljs-number">2</span>, <span class="hljs-comment"># 控制模型复杂度的权重值的L2正则化项参数,参数越大,模型越不容易过拟合。</span><br> <span class="hljs-string">'subsample'</span>: <span class="hljs-number">0.7</span>, <span class="hljs-comment"># 随机采样训练样本</span><br> <span class="hljs-string">'colsample_bytree'</span>: <span class="hljs-number">0.7</span>, <span class="hljs-comment"># 生成树时进行的列采样,列采样率,也就是特征采样率</span><br> <span class="hljs-string">'min_child_weight'</span>: <span class="hljs-number">3</span>,<br> <span class="hljs-string">'silent'</span>: <span class="hljs-number">0</span>,<br> <span class="hljs-string">'eta'</span>: <span class="hljs-number">0.1</span>,<br> <span class="hljs-string">'seed'</span>: <span class="hljs-number">1</span>,<br> <span class="hljs-string">'nthread'</span>: <span class="hljs-number">4</span>,<br>}<br><br>plst = list(params.items())<br>dtrain = xgb.DMatrix(X_train,y_train)<br>num_rounds = <span class="hljs-number">500</span><br>model = xgb.train(plst,dtrain,num_rounds) <span class="hljs-comment"># xgboost模型训练</span><br><br><span class="hljs-comment"># 对测试集进行预测</span><br>dtest = xgb.DMatrix(X_test)<br>y_pred = model.predict(dtest)<br><br><span class="hljs-comment"># 计算准确率</span><br>accuracy = accuracy_score(y_test,y_pred)<br>print(<span class="hljs-string">"accuarcy: %.2f%%"</span> % (accuracy*<span class="hljs-number">100.0</span>))<br><br><span class="hljs-comment"># 显示重要特征</span><br>plot_importance(model)<br>plt.show()<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210618000838223.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8vD1cJWm-1623946035317)(output_30_1.png)]"></p><h2 id="回归案例"><a href="#回归案例" class="headerlink" title="回归案例"></a>回归案例</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> xgboost <span class="hljs-keyword">as</span> xgb<br><span class="hljs-keyword">from</span> xgboost <span class="hljs-keyword">import</span> plot_importance<br><span class="hljs-keyword">from</span> matplotlib <span class="hljs-keyword">import</span> pyplot <span class="hljs-keyword">as</span> plt<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> load_boston<br><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> mean_squared_error<br><br><br><span class="hljs-comment"># 加载数据集</span><br>boston = load_boston()<br>X,y = boston.data,boston.target<br><br><span class="hljs-comment"># XGBoost训练过程</span><br>X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=<span class="hljs-number">0.2</span>, random_state=<span class="hljs-number">0</span>)<br><br>params = {<br> <span class="hljs-string">'booster'</span>: <span class="hljs-string">'gbtree'</span>,<br> <span class="hljs-string">'objective'</span>: <span class="hljs-string">'reg:squarederror'</span>,<br> <span class="hljs-string">'gamma'</span>: <span class="hljs-number">0.1</span>,<br> <span class="hljs-string">'max_depth'</span>: <span class="hljs-number">5</span>,<br> <span class="hljs-string">'lambda'</span>: <span class="hljs-number">3</span>,<br> <span class="hljs-string">'subsample'</span>: <span class="hljs-number">0.7</span>,<br> <span class="hljs-string">'colsample_bytree'</span>: <span class="hljs-number">0.7</span>,<br> <span class="hljs-string">'min_child_weight'</span>: <span class="hljs-number">3</span>,<br> <span class="hljs-string">'silent'</span>: <span class="hljs-number">1</span>,<br> <span class="hljs-string">'eta'</span>: <span class="hljs-number">0.1</span>,<br> <span class="hljs-string">'seed'</span>: <span class="hljs-number">1000</span>,<br> <span class="hljs-string">'nthread'</span>: <span class="hljs-number">4</span>,<br>}<br><br>dtrain = xgb.DMatrix(X_train, y_train)<br>num_rounds = <span class="hljs-number">300</span><br>plst = list(params.items())<br>model = xgb.train(plst, dtrain, num_rounds)<br><br><span class="hljs-comment"># 对测试集进行预测</span><br>dtest = xgb.DMatrix(X_test)<br>ans = model.predict(dtest)<br><br><span class="hljs-comment"># 显示重要特征</span><br>plot_importance(model)<br>plt.show()<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210618000856713.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Zu2NWnA-1623946035318)(output_32_1.png)]"></p><h2 id="XGBoost调参(结合sklearn网格搜索)"><a href="#XGBoost调参(结合sklearn网格搜索)" class="headerlink" title="XGBoost调参(结合sklearn网格搜索)"></a>XGBoost调参(结合sklearn网格搜索)</h2><p>代码参考:<a href="https://www.jianshu.com/p/1100e333fcab" target="_blank" rel="noopener">https://www.jianshu.com/p/1100e333fcab</a></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> xgboost <span class="hljs-keyword">as</span> xgb<br><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> GridSearchCV<br><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> roc_auc_score<br><br>iris = load_iris()<br>X,y = iris.data,iris.target<br>col = iris.target_names <br>train_x, valid_x, train_y, valid_y = train_test_split(X, y, test_size=<span class="hljs-number">0.3</span>, random_state=<span class="hljs-number">1</span>) <span class="hljs-comment"># 分训练集和验证集</span><br>parameters = {<br> <span class="hljs-string">'max_depth'</span>: [<span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">15</span>, <span class="hljs-number">20</span>, <span class="hljs-number">25</span>],<br> <span class="hljs-string">'learning_rate'</span>: [<span class="hljs-number">0.01</span>, <span class="hljs-number">0.02</span>, <span class="hljs-number">0.05</span>, <span class="hljs-number">0.1</span>, <span class="hljs-number">0.15</span>],<br> <span class="hljs-string">'n_estimators'</span>: [<span class="hljs-number">500</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">2000</span>, <span class="hljs-number">3000</span>, <span class="hljs-number">5000</span>],<br> <span class="hljs-string">'min_child_weight'</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">5</span>, <span class="hljs-number">10</span>, <span class="hljs-number">20</span>],<br> <span class="hljs-string">'max_delta_step'</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0.2</span>, <span class="hljs-number">0.6</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>],<br> <span class="hljs-string">'subsample'</span>: [<span class="hljs-number">0.6</span>, <span class="hljs-number">0.7</span>, <span class="hljs-number">0.8</span>, <span class="hljs-number">0.85</span>, <span class="hljs-number">0.95</span>],<br> <span class="hljs-string">'colsample_bytree'</span>: [<span class="hljs-number">0.5</span>, <span class="hljs-number">0.6</span>, <span class="hljs-number">0.7</span>, <span class="hljs-number">0.8</span>, <span class="hljs-number">0.9</span>],<br> <span class="hljs-string">'reg_alpha'</span>: [<span class="hljs-number">0</span>, <span class="hljs-number">0.25</span>, <span class="hljs-number">0.5</span>, <span class="hljs-number">0.75</span>, <span class="hljs-number">1</span>],<br> <span class="hljs-string">'reg_lambda'</span>: [<span class="hljs-number">0.2</span>, <span class="hljs-number">0.4</span>, <span class="hljs-number">0.6</span>, <span class="hljs-number">0.8</span>, <span class="hljs-number">1</span>],<br> <span class="hljs-string">'scale_pos_weight'</span>: [<span class="hljs-number">0.2</span>, <span class="hljs-number">0.4</span>, <span class="hljs-number">0.6</span>, <span class="hljs-number">0.8</span>, <span class="hljs-number">1</span>]<br><br>}<br><br>xlf = xgb.XGBClassifier(max_depth=<span class="hljs-number">10</span>,<br> learning_rate=<span class="hljs-number">0.01</span>,<br> n_estimators=<span class="hljs-number">2000</span>,<br> silent=<span class="hljs-literal">True</span>,<br> objective=<span class="hljs-string">'multi:softmax'</span>,<br> num_class=<span class="hljs-number">3</span> , <br> nthread=<span class="hljs-number">-1</span>,<br> gamma=<span class="hljs-number">0</span>,<br> min_child_weight=<span class="hljs-number">1</span>,<br> max_delta_step=<span class="hljs-number">0</span>,<br> subsample=<span class="hljs-number">0.85</span>,<br> colsample_bytree=<span class="hljs-number">0.7</span>,<br> colsample_bylevel=<span class="hljs-number">1</span>,<br> reg_alpha=<span class="hljs-number">0</span>,<br> reg_lambda=<span class="hljs-number">1</span>,<br> scale_pos_weight=<span class="hljs-number">1</span>,<br> seed=<span class="hljs-number">0</span>,<br> missing=<span class="hljs-literal">None</span>)<br><br>gs = GridSearchCV(xlf, param_grid=parameters, scoring=<span class="hljs-string">'accuracy'</span>, cv=<span class="hljs-number">3</span>)<br>gs.fit(train_x, train_y)<br><br>print(<span class="hljs-string">"Best score: %0.3f"</span> % gs.best_score_)<br>print(<span class="hljs-string">"Best parameters set: %s"</span> % gs.best_params_ )<br></code></pre></td></tr></table></figure><p>Best score: 0.933<br>Best parameters set: {‘max_depth’: 5}</p><h1 id="8-LightGBM算法"><a href="#8-LightGBM算法" class="headerlink" title="8. LightGBM算法"></a>8. LightGBM算法</h1><p>LightGBM也是像XGBoost一样,是一类集成算法,他跟XGBoost总体来说是一样的,算法本质上与Xgboost没有出入,只是在XGBoost的基础上进行了优化,因此就不对原理进行重复介绍,在这里我们来看看几种算法的差别:</p><ul><li>优化速度和内存使用<ul><li>降低了计算每个分割增益的成本。</li><li>使用直方图减法进一步提高速度。</li><li>减少内存使用。</li><li>减少并行学习的计算成本。</li></ul></li><li>稀疏优化<ul><li>用离散的bin替换连续的值。如果#bins较小,则可以使用较小的数据类型(例如uint8_t)来存储训练数据 。 </li><li>无需存储其他信息即可对特征数值进行预排序 。</li></ul></li><li>精度优化 <ul><li><p>使用叶子数为导向的决策树建立算法而不是树的深度导向。</p></li><li><p>分类特征的编码方式的优化</p></li><li><p>通信网络的优化</p></li><li><p>并行学习的优化</p></li><li><p>GPU支持</p><p>LightGBM的优点:</p></li></ul></li></ul><p> 1)更快的训练效率</p><p> 2)低内存使用</p><p> 3)更高的准确率</p><p> 4)支持并行化学习</p><p> 5)可以处理大规模数据</p><p><strong>1.速度对比:</strong><br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-W562LSSA-1623946035318)(./速度对比.png)]<br><strong>2.准确率对比:</strong><br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J4JVvKjX-1623946035319)(./准确率对比.png)]<br><strong>3.内存使用量对比:</strong><br>[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-glXaoZd6-1623946035320)(./内存使用量.png)] </p><p><strong>LightGBM参数说明:</strong><br>推荐文档1:<a href="https://lightgbm.apachecn.org/#/docs/6" target="_blank" rel="noopener">https://lightgbm.apachecn.org/#/docs/6</a><br>推荐文档2:<a href="https://lightgbm.readthedocs.io/en/latest/Parameters.html" target="_blank" rel="noopener">https://lightgbm.readthedocs.io/en/latest/Parameters.html</a></p><p><strong>1.核心参数:</strong>(括号内名称是别名) </p><ul><li>objective(objective,app ,application):默认regression,用于设置损失函数<ul><li>回归问题:<ul><li>L2损失:regression(regression_l2,l2,mean_squared_error,mse,l2_root,root_mean_squared_error,rmse)</li><li>L1损失:regression_l1(l1, mean_absolute_error, mae)</li><li>其他损失:huber,fair,poisson,quantile,mape,gamma,tweedie</li></ul></li><li>二分类问题:二进制对数损失分类(或逻辑回归):binary</li><li>多类别分类:<ul><li>softmax目标函数: multiclass(softmax)</li><li> One-vs-All 目标函数:multiclassova(multiclass_ova,ova,ovr)</li></ul></li><li>交叉熵:<ul><li>用于交叉熵的目标函数(具有可选的线性权重):cross_entropy(xentropy)</li><li>交叉熵的替代参数化:cross_entropy_lambda(xentlambda) </li></ul></li></ul></li><li>boosting :默认gbdt,设置提升类型,选项有gbdt,rf,dart,goss,别名:boosting_type,boost<ul><li>gbdt(gbrt):传统的梯度提升决策树</li><li>rf(random_forest):随机森林</li><li>dart:多个加性回归树的DROPOUT方法 Dropouts meet Multiple Additive Regression Trees,参见:<a href="https://arxiv.org/abs/1505.01866" target="_blank" rel="noopener">https://arxiv.org/abs/1505.01866</a></li><li>goss:基于梯度的单边采样 Gradient-based One-Side Sampling </li></ul></li><li>data(train,train_data,train_data_file,data_filename):用于训练的数据或数据file</li><li>valid (test,valid_data,valid_data_file,test_data,test_data_file,valid_filenames):验证/测试数据的路径,LightGBM将输出这些数据的指标</li><li>num_iterations:默认=100,类型= INT</li><li>n_estimators:提升迭代次数,*<em>LightGBM构造用于多类分类问题的树num_class * num_iterations*</em></li><li>learning_rate(shrinkage_rate,eta) :收缩率,默认=0.1</li><li>num_leaves(num_leaf,max_leaves,max_leaf) :默认=31,一棵树上的最大叶子数</li><li>tree_learner (tree,tree_type,tree_learner_type):默认=serial,可选:serial,feature,data,voting<ul><li>serial:单台机器的 tree learner</li><li>feature:特征并行的 tree learner</li><li>data:数据并行的 tree learner</li><li>voting:投票并行的 tree learner</li></ul></li><li>num_threads(num_thread, nthread):LightGBM 的线程数,为了更快的速度, 将此设置为真正的 CPU 内核数, 而不是线程的数量 (大多数 CPU 使用超线程来使每个 CPU 内核生成 2 个线程),当你的数据集小的时候不要将它设置的过大 (比如, 当数据集有 10,000 行时不要使用 64 线程),对于并行学习, 不应该使用全部的 CPU 内核, 因为这会导致网络性能不佳。 </li><li>device(device_type):默认cpu,为树学习选择设备, 你可以使用 GPU 来获得更快的学习速度,可选cpu, gpu。</li><li>seed (random_seed,random_state):与其他种子相比,该种子具有较低的优先级,这意味着如果您明确设置其他种子,它将被覆盖。</li></ul><p><strong>2.用于控制模型学习过程的参数:</strong></p><ul><li>max_depth:限制树模型的最大深度. 这可以在 #data 小的情况下防止过拟合. 树仍然可以通过 leaf-wise 生长。</li><li>min_data_in_leaf: 默认=20,一个叶子上数据的最小数量. 可以用来处理过拟合。</li><li>min_sum_hessian_in_leaf(min_sum_hessian_per_leaf, min_sum_hessian, min_hessian):默认=1e-3,一个叶子上的最小 hessian 和. 类似于 min_data_in_leaf, 可以用来处理过拟合.</li><li>feature_fraction:default=1.0,如果 feature_fraction 小于 1.0, LightGBM 将会在每次迭代中随机选择部分特征. 例如, 如果设置为 0.8, 将会在每棵树训练之前选择 80% 的特征,可以用来加速训练,可以用来处理过拟合。</li><li>feature_fraction_seed:默认=2,feature_fraction 的随机数种子。</li><li>bagging_fraction(sub_row, subsample):默认=1,不进行重采样的情况下随机选择部分数据</li><li>bagging_freq(subsample_freq):bagging 的频率, 0 意味着禁用 bagging. k 意味着每 k 次迭代执行bagging</li><li>bagging_seed(bagging_fraction_seed) :默认=3,bagging 随机数种子。</li><li>early_stopping_round(early_stopping_rounds, early_stopping):默认=0,如果一个验证集的度量在 early_stopping_round 循环中没有提升, 将停止训练</li><li>lambda_l1(reg_alpha):L1正则化系数</li><li>lambda_l2(reg_lambda):L2正则化系数</li><li>min_split_gain(min_gain_to_split):执行切分的最小增益,默认=0.</li><li>cat_smooth:默认=10,用于分类特征,可以降低噪声在分类特征中的影响, 尤其是对数据很少的类别</li></ul><p><strong>3.度量参数:</strong></p><ul><li>metric:default={l2 for regression}, {binary_logloss for binary classification}, {ndcg for lambdarank}, type=multi-enum, options=l1, l2, ndcg, auc, binary_logloss, binary_error …<ul><li>l1, absolute loss, alias=mean_absolute_error, mae</li><li>l2, square loss, alias=mean_squared_error, mse</li><li>l2_root, root square loss, alias=root_mean_squared_error, rmse</li><li>quantile, Quantile regression</li><li>huber, Huber loss</li><li>fair, Fair loss</li><li>poisson, Poisson regression</li><li> ndcg, NDCG</li><li> map, MAP</li><li> auc, AUC</li><li> binary_logloss, log loss</li><li> binary_error, 样本: 0 的正确分类, 1 错误分类</li><li> multi_logloss, mulit-class 损失日志分类</li><li> multi_error, error rate for mulit-class 出错率分类</li><li> xentropy, cross-entropy (与可选的线性权重), alias=cross_entropy</li><li> xentlambda, “intensity-weighted” 交叉熵, alias=cross_entropy_lambda</li><li> kldiv, Kullback-Leibler divergence, alias=kullback_leibler</li><li> 支持多指标, 使用 , 分隔</li></ul></li><li>train_metric(training_metric, is_training_metric):默认=False,如果你需要输出训练的度量结果则设置 true </li></ul><p><strong>4.GPU 参数:</strong></p><ul><li>gpu_device_id:default为-1, 这个default意味着选定平台上的设备。 </li></ul><p><strong>LightGBM与网格搜索结合调参:</strong><br>参考代码:<a href="https://blog.csdn.net/u012735708/article/details/83749703" target="_blank" rel="noopener">https://blog.csdn.net/u012735708/article/details/83749703</a></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> lightgbm <span class="hljs-keyword">as</span> lgb<br><span class="hljs-keyword">from</span> sklearn <span class="hljs-keyword">import</span> metrics<br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> load_breast_cancer<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br> <br>canceData=load_breast_cancer()<br>X=canceData.data<br>y=canceData.target<br>X_train,X_test,y_train,y_test=train_test_split(X,y,random_state=<span class="hljs-number">0</span>,test_size=<span class="hljs-number">0.2</span>)<br> <br><span class="hljs-comment">### 数据转换</span><br>print(<span class="hljs-string">'数据转换'</span>)<br>lgb_train = lgb.Dataset(X_train, y_train, free_raw_data=<span class="hljs-literal">False</span>)<br>lgb_eval = lgb.Dataset(X_test, y_test, reference=lgb_train,free_raw_data=<span class="hljs-literal">False</span>)<br> <br><span class="hljs-comment">### 设置初始参数--不含交叉验证参数</span><br>print(<span class="hljs-string">'设置参数'</span>)<br>params = {<br> <span class="hljs-string">'boosting_type'</span>: <span class="hljs-string">'gbdt'</span>,<br> <span class="hljs-string">'objective'</span>: <span class="hljs-string">'binary'</span>,<br> <span class="hljs-string">'metric'</span>: <span class="hljs-string">'auc'</span>,<br> <span class="hljs-string">'nthread'</span>:<span class="hljs-number">4</span>,<br> <span class="hljs-string">'learning_rate'</span>:<span class="hljs-number">0.1</span><br> }<br> <br><span class="hljs-comment">### 交叉验证(调参)</span><br>print(<span class="hljs-string">'交叉验证'</span>)<br>max_auc = float(<span class="hljs-string">'0'</span>)<br>best_params = {}<br> <br><span class="hljs-comment"># 准确率</span><br>print(<span class="hljs-string">"调参1:提高准确率"</span>)<br><span class="hljs-keyword">for</span> num_leaves <span class="hljs-keyword">in</span> range(<span class="hljs-number">5</span>,<span class="hljs-number">100</span>,<span class="hljs-number">5</span>):<br> <span class="hljs-keyword">for</span> max_depth <span class="hljs-keyword">in</span> range(<span class="hljs-number">3</span>,<span class="hljs-number">8</span>,<span class="hljs-number">1</span>):<br> params[<span class="hljs-string">'num_leaves'</span>] = num_leaves<br> params[<span class="hljs-string">'max_depth'</span>] = max_depth<br> <br> cv_results = lgb.cv(<br> params,<br> lgb_train,<br> seed=<span class="hljs-number">1</span>,<br> nfold=<span class="hljs-number">5</span>,<br> metrics=[<span class="hljs-string">'auc'</span>],<br> early_stopping_rounds=<span class="hljs-number">10</span>,<br> verbose_eval=<span class="hljs-literal">True</span><br> )<br> <br> mean_auc = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).max()<br> boost_rounds = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).idxmax()<br> <br> <span class="hljs-keyword">if</span> mean_auc >= max_auc:<br> max_auc = mean_auc<br> best_params[<span class="hljs-string">'num_leaves'</span>] = num_leaves<br> best_params[<span class="hljs-string">'max_depth'</span>] = max_depth<br><span class="hljs-keyword">if</span> <span class="hljs-string">'num_leaves'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'max_depth'</span> <span class="hljs-keyword">in</span> best_params.keys(): <br> params[<span class="hljs-string">'num_leaves'</span>] = best_params[<span class="hljs-string">'num_leaves'</span>]<br> params[<span class="hljs-string">'max_depth'</span>] = best_params[<span class="hljs-string">'max_depth'</span>]<br> <br><span class="hljs-comment"># 过拟合</span><br>print(<span class="hljs-string">"调参2:降低过拟合"</span>)<br><span class="hljs-keyword">for</span> max_bin <span class="hljs-keyword">in</span> range(<span class="hljs-number">5</span>,<span class="hljs-number">256</span>,<span class="hljs-number">10</span>):<br> <span class="hljs-keyword">for</span> min_data_in_leaf <span class="hljs-keyword">in</span> range(<span class="hljs-number">1</span>,<span class="hljs-number">102</span>,<span class="hljs-number">10</span>):<br> params[<span class="hljs-string">'max_bin'</span>] = max_bin<br> params[<span class="hljs-string">'min_data_in_leaf'</span>] = min_data_in_leaf<br> <br> cv_results = lgb.cv(<br> params,<br> lgb_train,<br> seed=<span class="hljs-number">1</span>,<br> nfold=<span class="hljs-number">5</span>,<br> metrics=[<span class="hljs-string">'auc'</span>],<br> early_stopping_rounds=<span class="hljs-number">10</span>,<br> verbose_eval=<span class="hljs-literal">True</span><br> )<br> <br> mean_auc = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).max()<br> boost_rounds = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).idxmax()<br> <br> <span class="hljs-keyword">if</span> mean_auc >= max_auc:<br> max_auc = mean_auc<br> best_params[<span class="hljs-string">'max_bin'</span>]= max_bin<br> best_params[<span class="hljs-string">'min_data_in_leaf'</span>] = min_data_in_leaf<br><span class="hljs-keyword">if</span> <span class="hljs-string">'max_bin'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'min_data_in_leaf'</span> <span class="hljs-keyword">in</span> best_params.keys():<br> params[<span class="hljs-string">'min_data_in_leaf'</span>] = best_params[<span class="hljs-string">'min_data_in_leaf'</span>]<br> params[<span class="hljs-string">'max_bin'</span>] = best_params[<span class="hljs-string">'max_bin'</span>]<br> <br>print(<span class="hljs-string">"调参3:降低过拟合"</span>)<br><span class="hljs-keyword">for</span> feature_fraction <span class="hljs-keyword">in</span> [<span class="hljs-number">0.6</span>,<span class="hljs-number">0.7</span>,<span class="hljs-number">0.8</span>,<span class="hljs-number">0.9</span>,<span class="hljs-number">1.0</span>]:<br> <span class="hljs-keyword">for</span> bagging_fraction <span class="hljs-keyword">in</span> [<span class="hljs-number">0.6</span>,<span class="hljs-number">0.7</span>,<span class="hljs-number">0.8</span>,<span class="hljs-number">0.9</span>,<span class="hljs-number">1.0</span>]:<br> <span class="hljs-keyword">for</span> bagging_freq <span class="hljs-keyword">in</span> range(<span class="hljs-number">0</span>,<span class="hljs-number">50</span>,<span class="hljs-number">5</span>):<br> params[<span class="hljs-string">'feature_fraction'</span>] = feature_fraction<br> params[<span class="hljs-string">'bagging_fraction'</span>] = bagging_fraction<br> params[<span class="hljs-string">'bagging_freq'</span>] = bagging_freq<br> <br> cv_results = lgb.cv(<br> params,<br> lgb_train,<br> seed=<span class="hljs-number">1</span>,<br> nfold=<span class="hljs-number">5</span>,<br> metrics=[<span class="hljs-string">'auc'</span>],<br> early_stopping_rounds=<span class="hljs-number">10</span>,<br> verbose_eval=<span class="hljs-literal">True</span><br> )<br> <br> mean_auc = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).max()<br> boost_rounds = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).idxmax()<br> <br> <span class="hljs-keyword">if</span> mean_auc >= max_auc:<br> max_auc=mean_auc<br> best_params[<span class="hljs-string">'feature_fraction'</span>] = feature_fraction<br> best_params[<span class="hljs-string">'bagging_fraction'</span>] = bagging_fraction<br> best_params[<span class="hljs-string">'bagging_freq'</span>] = bagging_freq<br> <br><span class="hljs-keyword">if</span> <span class="hljs-string">'feature_fraction'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'bagging_fraction'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'bagging_freq'</span> <span class="hljs-keyword">in</span> best_params.keys():<br> params[<span class="hljs-string">'feature_fraction'</span>] = best_params[<span class="hljs-string">'feature_fraction'</span>]<br> params[<span class="hljs-string">'bagging_fraction'</span>] = best_params[<span class="hljs-string">'bagging_fraction'</span>]<br> params[<span class="hljs-string">'bagging_freq'</span>] = best_params[<span class="hljs-string">'bagging_freq'</span>]<br> <br> <br>print(<span class="hljs-string">"调参4:降低过拟合"</span>)<br><span class="hljs-keyword">for</span> lambda_l1 <span class="hljs-keyword">in</span> [<span class="hljs-number">1e-5</span>,<span class="hljs-number">1e-3</span>,<span class="hljs-number">1e-1</span>,<span class="hljs-number">0.0</span>,<span class="hljs-number">0.1</span>,<span class="hljs-number">0.3</span>,<span class="hljs-number">0.5</span>,<span class="hljs-number">0.7</span>,<span class="hljs-number">0.9</span>,<span class="hljs-number">1.0</span>]:<br> <span class="hljs-keyword">for</span> lambda_l2 <span class="hljs-keyword">in</span> [<span class="hljs-number">1e-5</span>,<span class="hljs-number">1e-3</span>,<span class="hljs-number">1e-1</span>,<span class="hljs-number">0.0</span>,<span class="hljs-number">0.1</span>,<span class="hljs-number">0.4</span>,<span class="hljs-number">0.6</span>,<span class="hljs-number">0.7</span>,<span class="hljs-number">0.9</span>,<span class="hljs-number">1.0</span>]:<br> params[<span class="hljs-string">'lambda_l1'</span>] = lambda_l1<br> params[<span class="hljs-string">'lambda_l2'</span>] = lambda_l2<br> cv_results = lgb.cv(<br> params,<br> lgb_train,<br> seed=<span class="hljs-number">1</span>,<br> nfold=<span class="hljs-number">5</span>,<br> metrics=[<span class="hljs-string">'auc'</span>],<br> early_stopping_rounds=<span class="hljs-number">10</span>,<br> verbose_eval=<span class="hljs-literal">True</span><br> )<br> <br> mean_auc = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).max()<br> boost_rounds = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).idxmax()<br> <br> <span class="hljs-keyword">if</span> mean_auc >= max_auc:<br> max_auc=mean_auc<br> best_params[<span class="hljs-string">'lambda_l1'</span>] = lambda_l1<br> best_params[<span class="hljs-string">'lambda_l2'</span>] = lambda_l2<br><span class="hljs-keyword">if</span> <span class="hljs-string">'lambda_l1'</span> <span class="hljs-keyword">and</span> <span class="hljs-string">'lambda_l2'</span> <span class="hljs-keyword">in</span> best_params.keys():<br> params[<span class="hljs-string">'lambda_l1'</span>] = best_params[<span class="hljs-string">'lambda_l1'</span>]<br> params[<span class="hljs-string">'lambda_l2'</span>] = best_params[<span class="hljs-string">'lambda_l2'</span>]<br> <br>print(<span class="hljs-string">"调参5:降低过拟合2"</span>)<br><span class="hljs-keyword">for</span> min_split_gain <span class="hljs-keyword">in</span> [<span class="hljs-number">0.0</span>,<span class="hljs-number">0.1</span>,<span class="hljs-number">0.2</span>,<span class="hljs-number">0.3</span>,<span class="hljs-number">0.4</span>,<span class="hljs-number">0.5</span>,<span class="hljs-number">0.6</span>,<span class="hljs-number">0.7</span>,<span class="hljs-number">0.8</span>,<span class="hljs-number">0.9</span>,<span class="hljs-number">1.0</span>]:<br> params[<span class="hljs-string">'min_split_gain'</span>] = min_split_gain<br> <br> cv_results = lgb.cv(<br> params,<br> lgb_train,<br> seed=<span class="hljs-number">1</span>,<br> nfold=<span class="hljs-number">5</span>,<br> metrics=[<span class="hljs-string">'auc'</span>],<br> early_stopping_rounds=<span class="hljs-number">10</span>,<br> verbose_eval=<span class="hljs-literal">True</span><br> )<br> <br> mean_auc = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).max()<br> boost_rounds = pd.Series(cv_results[<span class="hljs-string">'auc-mean'</span>]).idxmax()<br> <br> <span class="hljs-keyword">if</span> mean_auc >= max_auc:<br> max_auc=mean_auc<br> <br> best_params[<span class="hljs-string">'min_split_gain'</span>] = min_split_gain<br><span class="hljs-keyword">if</span> <span class="hljs-string">'min_split_gain'</span> <span class="hljs-keyword">in</span> best_params.keys():<br> params[<span class="hljs-string">'min_split_gain'</span>] = best_params[<span class="hljs-string">'min_split_gain'</span>]<br> <br>print(best_params)<br></code></pre></td></tr></table></figure><p>{‘bagging_fraction’: 0.7,<br> ‘bagging_freq’: 30,<br> ‘feature_fraction’: 0.8,<br> ‘lambda_l1’: 0.1,<br> ‘lambda_l2’: 0.0,<br> ‘max_bin’: 255,<br> ‘max_depth’: 4,<br> ‘min_data_in_leaf’: 81,<br> ‘min_split_gain’: 0.1,<br> ‘num_leaves’: 10} </p><h1 id="9-结语"><a href="#9-结语" class="headerlink" title="9. 结语"></a>9. 结语</h1><p>本章中,我们主要探讨了基于Boosting方式的集成方法,其中主要讲解了基于错误率驱动的Adaboost,基于残差改进的提升树,基于梯度提升的GBDT,基于泰勒二阶近似的Xgboost以及LightGBM。在实际的比赛或者工程中,基于Boosting的集成学习方式是非常有效且应用非常广泛的。更多的学习有待读者更深入阅读文献,包括原作者论文以及论文复现等。下一章我们即将探讨另一种集成学习方式:Stacking集成学习方式,这种集成方式虽然没有Boosting应用广泛,但是在比赛中或许能让你的模型更加出众。</p><p><strong>本章作业:</strong><br>本章在最后介绍LightGBM的时候并没有详细介绍它的原理以及它与XGBoost的不一样的地方,希望读者好好看看别的文章分享,总结LigntGBM与XGBoost的不同,然后使用一个具体的案例体现两者的不同。<br>参考链接: <a href="https://blog.csdn.net/weixin_30279315/article/details/95504220" target="_blank" rel="noopener">https://blog.csdn.net/weixin_30279315/article/details/95504220</a></p>]]></content>
<summary type="html">
<h1 id="XGBoost算法"><a href="#XGBoost算法" class="headerlink" title="XGBoost算法"></a>XGBoost算法</h1><p>XGBoost是陈天奇等人开发的一个开源机器学习项目,高效地实现了GBDT算法并进行了算法和工程上的许多改进,被广泛应用在Kaggle竞赛及其他许多机器学习竞赛中并取得了不错的成绩。<strong>XGBoost本质上还是一个GBDT,但是力争把速度和效率发挥到极致,所以叫X (Extreme) GBoosted,</strong> 包括前面说过,两者都是boosting方法。XGBoost是一个优化的分布式梯度增强库,旨在实现高效,灵活和便携。 它在Gradient Boosting框架下实现机器学习算法。 XGBoost提供了<strong>并行树提升</strong>(也称为GBDT,GBM),可以快速准确地解决许多数据科学问题。 相同的代码在主要的分布式环境(Hadoop,SGE,MPI)上运行,并且可以解决超过数十亿个样例的问题。XGBoost利用了核外计算并且能够使数据科学家在一个主机上处理数亿的样本数据。最终,将这些技术进行结合来做一个端到端的系统以最少的集群系统来扩展到更大的数据集上。Xgboost<strong>以CART决策树为子模型</strong>,通过Gradient Tree Boosting实现多棵CART树的集成学习,得到最终模型。下面我们来看看XGBoost的最终模型构建:</p>
</summary>
<category term="datawhale" scheme="https://jackyin.space/tags/datawhale/"/>
<category term="机器学习" scheme="https://jackyin.space/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>集成学习专题——GBDT</title>
<link href="https://jackyin.space/2021/06/17/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94GBDT/"/>
<id>https://jackyin.space/2021/06/17/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94GBDT/</id>
<published>2021-06-17T15:59:00.000Z</published>
<updated>2024-10-09T08:43:45.344Z</updated>
<content type="html"><![CDATA[<h2 id="梯度提升决策树-GBDT"><a href="#梯度提升决策树-GBDT" class="headerlink" title="梯度提升决策树(GBDT)"></a>梯度提升决策树(GBDT)</h2><p>(1) 基于残差学习的提升树算法:<br>在前面的学习过程中,我们一直讨论的都是分类树,比如Adaboost算法,并没有涉及回归的例子。在上一小节我们提到了一个加法模型+前向分步算法的框架,那能否使用这个框架解决回归的例子呢?答案是肯定的。接下来我们来探讨下如何使用加法模型+前向分步算法的框架实现回归问题。<br>在使用加法模型+前向分步算法的框架解决问题之前,我们需要首先确定框架内使用的基函数是什么,在这里我们使用决策树分类器。前面第二章我们已经学过了回归树的基本原理,<strong>树算法最重要是寻找最佳的划分点,分类树用纯度来判断最佳划分点使用信息增益(ID3算法),信息增益比(C4.5算法),基尼系数(CART分类树)。但是在回归树中的样本标签是连续数值,可划分点包含了所有特征的所有可取的值。所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。</strong> 基函数确定了以后,我们需要确定每次提升的标准是什么。回想Adaboost算法,在Adaboost算法内使用了分类错误率修正样本权重以及计算每个基本分类器的权重,那回归问题没有分类错误率可言,也就没办法在这里的回归问题使用了,因此我们需要另辟蹊径。模仿分类错误率,我们用每个样本的残差表示每次使用基函数预测时没有解决的那部分问题。因此,我们可以得出如下算法: </p><a id="more"></a><p>输入数据集$T=\left{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right}, x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}, y_{i} \in \mathcal{Y} \subseteq \mathbf{R}$,输出最终的提升树$f_{M}(x)$ </p><ul><li><p>初始化$f_0(x) = 0$ </p></li><li><p>对m = 1,2,…,M: </p><ul><li>计算每个样本的残差:$r_{m i}=y_{i}-f_{m-1}\left(x_{i}\right), \quad i=1,2, \cdots, N$ </li><li>拟合残差$r_{mi}$学习一棵回归树,得到$T\left(x ; \Theta_{m}\right)$ </li><li>更新$f_{m}(x)=f_{m-1}(x)+T\left(x ; \Theta_{m}\right)$</li></ul></li><li><p>得到最终的回归问题的提升树:$f_{M}(x)=\sum_{m=1}^{M} T\left(x ; \Theta_{m}\right)$ </p><p>下面我们用一个实际的案例来使用这个算法:(案例来源:李航老师《统计学习方法》)<br>训练数据如下表,学习这个回归问题的提升树模型,考虑只用树桩作为基函数。<br><img src="https://img-blog.csdnimg.cn/20210617235042845.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6RIHgryn-1623945003185)(./1.png)]"><br><img src="https://img-blog.csdnimg.cn/20210617235127439.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210617235127656.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210617235127832.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/2021061723512825.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>至此,我们已经能够建立起依靠加法模型+前向分步算法的框架解决回归问题的算法,叫提升树算法。那么,这个算法还是否有提升的空间呢?<br>(2) 梯度提升决策树算法(GBDT):<br>提升树利用加法模型和前向分步算法实现学习的过程,当损失函数为平方损失和指数损失时,每一步优化是相当简单的,也就是我们前面探讨的提升树算法和Adaboost算法。但是对于一般的损失函数而言,往往每一步的优化不是那么容易,针对这一问题,我们得分析问题的本质,也就是是什么导致了在一般损失函数条件下的学习困难。对比以下损失函数:<br>$$<br>\begin{array}{l|l|l}<br>\hline \text { Setting } & \text { Loss Function } & -\partial L\left(y_{i}, f\left(x_{i}\right)\right) / \partial f\left(x_{i}\right) \<br>\hline \text { Regression } & \frac{1}{2}\left[y_{i}-f\left(x_{i}\right)\right]^{2} & y_{i}-f\left(x_{i}\right) \<br>\hline \text { Regression } & \left|y_{i}-f\left(x_{i}\right)\right| & \operatorname{sign}\left[y_{i}-f\left(x_{i}\right)\right] \<br>\hline \text { Regression } & \text { Huber } & y_{i}-f\left(x_{i}\right) \text { for }\left|y_{i}-f\left(x_{i}\right)\right| \leq \delta_{m} \<br>& & \delta_{m} \operatorname{sign}\left[y_{i}-f\left(x_{i}\right)\right] \text { for }\left|y_{i}-f\left(x_{i}\right)\right|>\delta_{m} \<br>& & \text { where } \delta_{m}=\alpha \text { th-quantile }\left{\left|y_{i}-f\left(x_{i}\right)\right|\right} \<br>\hline \text { Classification } & \text { Deviance } & k \text { th component: } I\left(y_{i}=\mathcal{G}<em>{k}\right)-p</em>{k}\left(x_{i}\right) \<br>\hline<br>\end{array}<br>$$<br>观察Huber损失函数:<br>$$<br>L_{\delta}(y, f(x))=\left{\begin{array}{ll}<br>\frac{1}{2}(y-f(x))^{2} & \text { for }|y-f(x)| \leq \delta \<br>\delta|y-f(x)|-\frac{1}{2} \delta^{2} & \text { otherwise }<br>\end{array}\right.<br>$$<br>针对上面的问题,Freidman提出了梯度提升算法(gradient boosting),这是利用最速下降法的近似方法,利用损失函数的负梯度在当前模型的值$-\left[\frac{\partial L\left(y, f\left(x_{i}\right)\right)}{\partial f\left(x_{i}\right)}\right]<em>{f(x)=f</em>{m-1}(x)}$作为回归问题提升树算法中的残差的近似值,拟合回归树。<strong>与其说负梯度作为残差的近似值,不如说残差是负梯度的一种特例。</strong><br>以下开始具体介绍梯度提升算法:<br>输入训练数据集$T=\left{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right}, x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}, y_{i} \in \mathcal{Y} \subseteq \mathbf{R}$和损失函数$L(y, f(x))$,输出回归树$\hat{f}(x)$ </p></li><li><p>初始化$f_{0}(x)=\arg \min <em>{c} \sum</em>{i=1}^{N} L\left(y_{i}, c\right)$ </p></li><li><p>对于m=1,2,…,M: </p><ul><li>对i = 1,2,…,N计算:$r_{m i}=-\left[\frac{\partial L\left(y_{i}, f\left(x_{i}\right)\right)}{\partial f\left(x_{i}\right)}\right]<em>{f(x)=f</em>{m-1}(x)}$ </li><li>对$r_{mi}$拟合一个回归树,得到第m棵树的叶结点区域$R_{m j}, j=1,2, \cdots, J$ </li><li>对j=1,2,…J,计算:$c_{m j}=\arg \min <em>{c} \sum</em>{x_{i} \in R_{m j}} L\left(y_{i}, f_{m-1}\left(x_{i}\right)+c\right)$ </li><li>更新$f_{m}(x)=f_{m-1}(x)+\sum_{j=1}^{J} c_{m j} I\left(x \in R_{m j}\right)$ </li></ul></li><li><p>得到回归树:$\hat{f}(x)=f_{M}(x)=\sum_{m=1}^{M} \sum_{j=1}^{J} c_{m j} I\left(x \in R_{m j}\right)$</p></li></ul><p>下面,我们来使用一个具体的案例来说明GBDT是如何运作的(案例来源:<a href="https://blog.csdn.net/zpalyq110/article/details/79527653" target="_blank" rel="noopener">https://blog.csdn.net/zpalyq110/article/details/79527653</a> ):<br>下面的表格是数据:<br><img src="https://img-blog.csdnimg.cn/20210617235407802.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>学习率:learning_rate=0.1,迭代次数:n_trees=5,树的深度:max_depth=3<br>平方损失的负梯度为:<br>$$<br>-\left[\frac{\left.\partial L\left(y, f\left(x_{i}\right)\right)\right)}{\partial f\left(x_{i}\right)}\right]<em>{f(x)=f</em>{t-1}(x)}=y-f\left(x_{i}\right)<br>$$<br>$c=(1.1+1.3+1.7+1.8)/4=1.475,f_{0}(x)=c=1.475$<br><img src="https://img-blog.csdnimg.cn/20210617235349765.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>学习决策树,分裂结点:<br><img src="https://img-blog.csdnimg.cn/20210617235452339.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p><img src="https://img-blog.csdnimg.cn/20210617235503202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>对于左节点,只有0,1两个样本,那么根据下表我们选择年龄7进行划分:<br><img src="https://img-blog.csdnimg.cn/20210617235513407.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>对于右节点,只有2,3两个样本,那么根据下表我们选择年龄30进行划分:<br><img src="https://img-blog.csdnimg.cn/20210617235523252.png#pic_center" alt="在这里插入图片描述"></p><p><img src="https://img-blog.csdnimg.cn/20210617235547327.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>因此根据$\Upsilon_{j 1}=\underbrace{\arg \min }<em>{\Upsilon} \sum</em>{x_{i} \in R_{j 1}} L\left(y_{i}, f_{0}\left(x_{i}\right)+\Upsilon\right)$:<br>$$<br>\begin{array}{l}<br>\left(x_{0} \in R_{11}\right), \quad \Upsilon_{11}=-0.375 \<br>\left(x_{1} \in R_{21}\right), \quad \Upsilon_{21}=-0.175 \<br>\left(x_{2} \in R_{31}\right), \quad \Upsilon_{31}=0.225 \<br>\left(x_{3} \in R_{41}\right), \quad \Upsilon_{41}=0.325<br>\end{array}<br>$$<br>这里其实和上面初始化学习器是一个道理,平方损失,求导,令导数等于零,化简之后得到每个叶子节点的参数$\Upsilon$,其实就是标签值的均值。<br>最后得到五轮迭代:<br><img src="https://img-blog.csdnimg.cn/20210617235559608.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>最后的强学习器为:$f(x)=f_{5}(x)=f_{0}(x)+\sum_{m=1}^{5} \sum_{j=1}^{4} \Upsilon_{j m} I\left(x \in R_{j m}\right)$。<br>其中:<br>$$<br>\begin{array}{ll}<br>f_{0}(x)=1.475 & f_{2}(x)=0.0205 \<br>f_{3}(x)=0.1823 & f_{4}(x)=0.1640 \<br>f_{5}(x)=0.1476<br>\end{array}<br>$$<br>预测结果为:<br>$$<br>f(x)=1.475+0.1 *(0.2250+0.2025+0.1823+0.164+0.1476)=1.56714<br>$$<br>为什么要用学习率呢?这是Shrinkage的思想,如果每次都全部加上(学习率为1)很容易一步学到位导致过拟合。</p><p>下面我们来使用sklearn来使用GBDT: </p><ul><li><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html#sklearn.ensemble.GradientBoostingRegressor" target="_blank" rel="noopener">https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html#sklearn.ensemble.GradientBoostingRegressor</a> </li><li><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html?highlight=gra#sklearn.ensemble.GradientBoostingClassifier" target="_blank" rel="noopener">https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html?highlight=gra#sklearn.ensemble.GradientBoostingClassifier</a></li></ul><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> mean_squared_error<br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_friedman1<br><span class="hljs-keyword">from</span> sklearn.ensemble <span class="hljs-keyword">import</span> GradientBoostingRegressor<br><span class="hljs-string">'''</span><br><span class="hljs-string">GradientBoostingRegressor参数解释:</span><br><span class="hljs-string">loss:{‘ls’, ‘lad’, ‘huber’, ‘quantile’}, default=’ls’:‘ls’ 指最小二乘回归. ‘lad’ (最小绝对偏差) 是仅基于输入变量的顺序信息的高度鲁棒的损失函数。. ‘huber’ 是两者的结合. ‘quantile’允许分位数回归(用于alpha指定分位数)</span><br><span class="hljs-string">learning_rate:学习率缩小了每棵树的贡献learning_rate。在learning_rate和n_estimators之间需要权衡。</span><br><span class="hljs-string">n_estimators:要执行的提升次数。</span><br><span class="hljs-string">subsample:用于拟合各个基础学习者的样本比例。如果小于1.0,则将导致随机梯度增强。subsample与参数n_estimators。选择会导致方差减少和偏差增加。subsample < 1.0</span><br><span class="hljs-string">criterion:{'friedman_mse','mse','mae'},默认='friedman_mse':“ mse”是均方误差,“ mae”是平均绝对误差。默认值“ friedman_mse”通常是最好的,因为在某些情况下它可以提供更好的近似值。</span><br><span class="hljs-string">min_samples_split:拆分内部节点所需的最少样本数</span><br><span class="hljs-string">min_samples_leaf:在叶节点处需要的最小样本数。</span><br><span class="hljs-string">min_weight_fraction_leaf:在所有叶节点处(所有输入样本)的权重总和中的最小加权分数。如果未提供sample_weight,则样本的权重相等。</span><br><span class="hljs-string">max_depth:各个回归模型的最大深度。最大深度限制了树中节点的数量。调整此参数以获得最佳性能;最佳值取决于输入变量的相互作用。</span><br><span class="hljs-string">min_impurity_decrease:如果节点分裂会导致杂质的减少大于或等于该值,则该节点将被分裂。</span><br><span class="hljs-string">min_impurity_split:提前停止树木生长的阈值。如果节点的杂质高于阈值,则该节点将分裂</span><br><span class="hljs-string">max_features{‘auto’, ‘sqrt’, ‘log2’},int或float:寻找最佳分割时要考虑的功能数量:</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果为int,则max_features在每个分割处考虑特征。</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果为float,max_features则为小数,并在每次拆分时考虑要素。int(max_features * n_features)</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果“auto”,则max_features=n_features。</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果是“ sqrt”,则max_features=sqrt(n_features)。</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果为“ log2”,则为max_features=log2(n_features)。</span><br><span class="hljs-string"></span><br><span class="hljs-string">如果没有,则max_features=n_features。</span><br><span class="hljs-string">'''</span><br><br>X, y = make_friedman1(n_samples=<span class="hljs-number">1200</span>, random_state=<span class="hljs-number">0</span>, noise=<span class="hljs-number">1.0</span>)<br>X_train, X_test = X[:<span class="hljs-number">200</span>], X[<span class="hljs-number">200</span>:]<br>y_train, y_test = y[:<span class="hljs-number">200</span>], y[<span class="hljs-number">200</span>:]<br>reg = GradientBoostingRegressor(n_estimators=<span class="hljs-number">100</span>, <br> learning_rate=<span class="hljs-number">0.1</span>,max_depth=<span class="hljs-number">1</span>, random_state=<span class="hljs-number">0</span>, loss=<span class="hljs-string">'ls'</span>)<br>est = reg.fit(X_train, y_train)<br>mean_squared_error(y_test, est.predict(X_test))<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_regression<br><span class="hljs-keyword">from</span> sklearn.ensemble <span class="hljs-keyword">import</span> GradientBoostingRegressor<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br>X, y = make_regression(random_state=<span class="hljs-number">0</span>)<br>X_train, X_test, y_train, y_test = train_test_split(<br> X, y, random_state=<span class="hljs-number">0</span>)<br>reg = GradientBoostingRegressor(random_state=<span class="hljs-number">0</span>)<br>reg.fit(X_train, y_train)<br>reg.score(X_test, y_test)<br></code></pre></td></tr></table></figure><p>GradientBoostingRegressor与GradientBoostingClassifier函数的各个参数的参考文档:</p><ul><li><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html#sklearn.ensemble.GradientBoostingRegressor" target="_blank" rel="noopener">https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingRegressor.html#sklearn.ensemble.GradientBoostingRegressor</a></li><li><a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html?highlight=gra#sklearn.ensemble.GradientBoostingClassifier" target="_blank" rel="noopener">https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.GradientBoostingClassifier.html?highlight=gra#sklearn.ensemble.GradientBoostingClassifier</a></li></ul>]]></content>
<summary type="html">
<h2 id="梯度提升决策树-GBDT"><a href="#梯度提升决策树-GBDT" class="headerlink" title="梯度提升决策树(GBDT)"></a>梯度提升决策树(GBDT)</h2><p>(1) 基于残差学习的提升树算法:<br>在前面的学习过程中,我们一直讨论的都是分类树,比如Adaboost算法,并没有涉及回归的例子。在上一小节我们提到了一个加法模型+前向分步算法的框架,那能否使用这个框架解决回归的例子呢?答案是肯定的。接下来我们来探讨下如何使用加法模型+前向分步算法的框架实现回归问题。<br>在使用加法模型+前向分步算法的框架解决问题之前,我们需要首先确定框架内使用的基函数是什么,在这里我们使用决策树分类器。前面第二章我们已经学过了回归树的基本原理,<strong>树算法最重要是寻找最佳的划分点,分类树用纯度来判断最佳划分点使用信息增益(ID3算法),信息增益比(C4.5算法),基尼系数(CART分类树)。但是在回归树中的样本标签是连续数值,可划分点包含了所有特征的所有可取的值。所以再使用熵之类的指标不再合适,取而代之的是平方误差,它能很好的评判拟合程度。</strong> 基函数确定了以后,我们需要确定每次提升的标准是什么。回想Adaboost算法,在Adaboost算法内使用了分类错误率修正样本权重以及计算每个基本分类器的权重,那回归问题没有分类错误率可言,也就没办法在这里的回归问题使用了,因此我们需要另辟蹊径。模仿分类错误率,我们用每个样本的残差表示每次使用基函数预测时没有解决的那部分问题。因此,我们可以得出如下算法: </p>
</summary>
<category term="datawhale" scheme="https://jackyin.space/tags/datawhale/"/>
<category term="机器学习" scheme="https://jackyin.space/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>集成学习专题——adaboost原理和sklearn实现</title>
<link href="https://jackyin.space/2021/06/17/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94adaboost%E5%8E%9F%E7%90%86%E5%92%8Csklearn%E5%AE%9E%E7%8E%B0/"/>
<id>https://jackyin.space/2021/06/17/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94adaboost%E5%8E%9F%E7%90%86%E5%92%8Csklearn%E5%AE%9E%E7%8E%B0/</id>
<published>2021-06-17T15:15:00.000Z</published>
<updated>2024-10-09T08:43:45.344Z</updated>
<content type="html"><![CDATA[<h1 id="1-导论"><a href="#1-导论" class="headerlink" title="1. 导论"></a>1. 导论</h1><p>在前面的学习中,我们探讨了一系列简单而实用的回归和分类模型,同时也探讨了如何使用集成学习家族中的Bagging思想去优化最终的模型。Bagging思想的实质是:通过Bootstrap 的方式对全样本数据集进行抽样得到抽样子集,对不同的子集使用同一种基本模型进行拟合,然后投票得出最终的预测。我们也从前面的探讨知道:Bagging主要通过降低方差的方式减少预测误差。<strong>那么,本章介绍的Boosting是与Bagging截然不同的思想,Boosting方法是使用同一组数据集进行反复学习,得到一系列简单模型,然后组合这些模型构成一个预测性能十分强大的机器学习模型。</strong> 显然,Boosting思想提高最终的预测效果是通过不断减少偏差的形式,与Bagging有着本质的不同。在Boosting这一大类方法中,笔者主要介绍两类常用的Boosting方式:Adaptive Boosting 和 Gradient Boosting 以及它们的变体Xgboost、LightGBM以及Catboost。</p><a id="more"></a><h1 id="2-Boosting方法的基本思路"><a href="#2-Boosting方法的基本思路" class="headerlink" title="2. Boosting方法的基本思路"></a>2. Boosting方法的基本思路</h1><p>在正式介绍Boosting思想之前,我想先介绍两个例子:<br>第一个例子:不知道大家有没有做过错题本,我们将每次测验的错的题目记录在错题本上,不停的翻阅,直到我们完全掌握(也就是能够在考试中能够举一反三)。<br>第二个例子:对于一个复杂任务来说,将多个专家的判断进行适当的综合所作出的判断,要比其中任何一个专家单独判断要好。实际上这是一种“三个臭皮匠顶个诸葛亮的道理”。<br>这两个例子都说明Boosting的道理,也就是不错地重复学习达到最终的要求。<br>Boosting的提出与发展离不开Valiant和 Kearns的努力,历史上正是Valiant和 Kearns提出了”强可学习”和”弱可学习”的概念。那什么是”强可学习”和”弱可学习”呢?在概率近似正确PAC学习的框架下: </p><ul><li>弱学习:识别错误率小于1/2(即准确率仅比随机猜测略高的学习算法) </li><li>强学习:识别准确率很高并能在多项式时间内完成的学习算法 </li></ul><p>非常有趣的是,在PAC 学习的框架下,强可学习和弱可学习是等价的,也就是说一个概念是强可学习的充分必要条件是这个概念是弱可学习的。这样一来,问题便是:在学习中,如果已经发现了弱可学习算法,能否将他提升至强可学习算法。因为,弱可学习算法比强可学习算法容易得多。提升方法就是从弱学习算法出发,反复学习,得到一系列弱分类器(又称为基本分类器),然后通过一定的形式去组合这些弱分类器构成一个强分类器。<strong>大多数的Boosting方法都是通过改变训练数据集的概率分布(训练数据不同样本的权值),针对不同概率分布的数据调用弱分类算法学习一系列的弱分类器。</strong><br><strong>对于Boosting方法来说,有两个问题需要给出答案:第一个是每一轮学习应该如何改变数据的概率分布,第二个是如何将各个弱分类器组合起来。</strong>关于这两个问题,不同的Boosting算法会有不同的答案,我们接下来介绍一种最经典的Boosting算法—-Adaboost,我们需要理解Adaboost是怎么处理这两个问题以及为什么这么处理的。</p><h1 id="3-Adaboost算法"><a href="#3-Adaboost算法" class="headerlink" title="3. Adaboost算法"></a>3. Adaboost算法</h1><p><strong>Adaboost的基本原理</strong> </p><p>对于Adaboost来说,解决上述的两个问题的方式是:</p><ul><li>提高那些被前一轮分类器错误分类的样本的权重,而降低那些被正确分类的样本的权重。这样一来,那些在上一轮分类器中没有得到正确分类的样本,由于其权重的增大而在后一轮的训练中“备受关注”。</li><li>各个弱分类器的组合是通过采取加权多数表决的方式,具体来说,加大分类错误率低的弱分类器的权重,因为这些分类器能更好地完成分类任务,而减小分类错误率较大的弱分类器的权重,使其在表决中起较小的作用。 </li></ul><p>现在,我们来具体介绍Adaboost算法:(参考李航老师的《统计学习方法》)<br>假设给定一个二分类的训练数据集:$T=\left{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right}$ ,其中每个样本点由特征与类别组成。特征$x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}$,类别$y_{i} \in \mathcal{Y}={-1,+1}$,$\mathcal{X}$是特征空间,$ \mathcal{Y}$是类别集合,输出最终分类器$G(x)$。Adaboost算法如下:<br>(1) 初始化训练数据的分布:$D_{1}=\left(w_{11}, \cdots, w_{1 i}, \cdots, w_{1 N}\right), \quad w_{1 i}=\frac{1}{N}, \quad i=1,2, \cdots, N$<br>(2) 对于m=1,2,…,M </p><ul><li>使用具有权值分布$D_m$的训练数据集进行学习,得到基本分类器:$G_{m}(x): \mathcal{X} \rightarrow{-1,+1}$ </li><li>计算$G_m(x)$在训练集上的分类误差率$e_{m}=\sum_{i=1}^{N} P\left(G_{m}\left(x_{i}\right) \neq y_{i}\right)=\sum_{i=1}^{N} w_{m i} I\left(G_{m}\left(x_{i}\right) \neq y_{i}\right)$ </li><li>计算$G_m(x)$的系数$\alpha_{m}=\frac{1}{2} \log \frac{1-e_{m}}{e_{m}}$,这里的log是自然对数ln </li><li>更新训练数据集的权重分布<br>$$<br>\begin{array}{c}<br>D_{m+1}=\left(w_{m+1,1}, \cdots, w_{m+1, i}, \cdots, w_{m+1, N}\right) \<br>w_{m+1, i}=\frac{w_{m i}}{Z_{m}} \exp \left(-\alpha_{m} y_{i} G_{m}\left(x_{i}\right)\right), \quad i=1,2, \cdots, N<br>\end{array}<br>$$ 这里的$Z_m$是规范化因子,使得$D_{m+1}$称为概率分布,$Z_{m}=\sum_{i=1}^{N} w_{m i} \exp \left(-\alpha_{m} y_{i} G_{m}\left(x_{i}\right)\right)$ </li></ul><p>(3) 构建基本分类器的线性组合$f(x)=\sum_{m=1}^{M} \alpha_{m} G_{m}(x)$,得到最终的分类器 </p><p>$$<br>\begin{aligned}<br>G(x) &=\operatorname{sign}(f(x)) \<br>&=\operatorname{sign}\left(\sum_{m=1}^{M} \alpha_{m} G_{m}(x)\right)<br>\end{aligned}<br>$$ </p><p>下面对Adaboost算法做如下说明:<br>对于步骤(1),假设训练数据的权值分布是均匀分布,是为了使得第一次没有先验信息的条件下每个样本在基本分类器的学习中作用一样。<br>对于步骤(2),每一次迭代产生的基本分类器$G_m(x)$在加权训练数据集上的分类错误率$\begin{aligned}e_{m} &=\sum_{i=1}^{N} P\left(G_{m}\left(x_{i}\right) \neq y_{i}\right) =\sum_{G_{m}\left(x_{i}\right) \neq y_{i}} w_{m i}\end{aligned}$代表了在$G_m(x)$中分类错误的样本权重和,这点直接说明了权重分布$D_m$与$G_m(x)$的分类错误率$e_m$有直接关系。同时,在步骤(2)中,计算基本分类器$G_m(x)$的系数$\alpha_m$,$\alpha_{m}=\frac{1}{2} \log \frac{1-e_{m}}{e_{m}}$,它表示了$G_m(x)$在最终分类器的重要性程度,$\alpha_m$的取值由基本分类器$G_m(x)$的分类错误率有直接关系,当$e_{m} \leqslant \frac{1}{2}$时,$\alpha_{m} \geqslant 0$,并且$\alpha_m$随着$e_m$的减少而增大,因此分类错误率越小的基本分类器在最终分类器的作用越大!<br>**最重要的,对于步骤(2)中的样本权重的更新: **<br>$$<br>w_{m+1, i}=\left{\begin{array}{ll}<br>\frac{w_{m i}}{Z_{m}} \mathrm{e}^{-\alpha_{m}}, & G_{m}\left(x_{i}\right)=y_{i} \<br>\frac{w_{m i}}{Z_{m}} \mathrm{e}^{\alpha_{m}}, & G_{m}\left(x_{i}\right) \neq y_{i}<br>\end{array}\right.<br>$$<br>因此,从上式可以看到:被基本分类器$G_m(x)$错误分类的样本的权重扩大,被正确分类的样本权重减少,二者相比相差$\mathrm{e}^{2 \alpha_{m}}=\frac{1-e_{m}}{e_{m}}$倍。<br>对于步骤(3),线性组合$f(x)$实现了将M个基本分类器的加权表决,系数$\alpha_m$标志了基本分类器$G_m(x)$的重要性,值得注意的是:所有的$\alpha_m$之和不为1。$f(x)$的符号决定了样本x属于哪一类。 </p><p>下面,我们使用一组简单的数据来手动计算Adaboost算法的过程:(例子来源:<a href="http://www.csie.edu.tw/" target="_blank" rel="noopener">http://www.csie.edu.tw</a>) </p><p>训练数据如下表,假设基本分类器的形式是一个分割$x<v$或$x>v$表示,阈值v由该基本分类器在训练数据集上分类错误率$e_m$最低确定。<br>$$<br>\begin{array}{ccccccccccc}<br>\hline \text { 序号 } & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 & 10 \<br>\hline x & 0 & 1 & 2 & 3 & 4 & 5 & 6 & 7 & 8 & 9 \<br>y & 1 & 1 & 1 & -1 & -1 & -1 & 1 & 1 & 1 & -1 \<br>\hline<br>\end{array}<br>$$<br>解:<br>初始化样本权值分布<br>$$<br>\begin{aligned}<br>D_{1} &=\left(w_{11}, w_{12}, \cdots, w_{110}\right) \<br>w_{1 i} &=0.1, \quad i=1,2, \cdots, 10<br>\end{aligned}<br>$$<br>对m=1: </p><ul><li>在权值分布$D_1$的训练数据集上,遍历每个结点并计算分类误差率$e_m$,阈值取v=2.5时分类误差率最低,那么基本分类器为:<br>$$<br>G_{1}(x)=\left{\begin{array}{ll}<br>1, & x<2.5 \</li><li>1, & x>2.5<br>\end{array}\right.<br>$$ </li><li>$G_1(x)$在训练数据集上的误差率为$e_{1}=P\left(G_{1}\left(x_{i}\right) \neq y_{i}\right)=0.3$。 </li><li>计算$G_1(x)$的系数:$\alpha_{1}=\frac{1}{2} \log \frac{1-e_{1}}{e_{1}}=0.4236$ </li><li>更新训练数据的权值分布:<br>$$<br>\begin{aligned}<br>D_{2}=&\left(w_{21}, \cdots, w_{2 i}, \cdots, w_{210}\right) \<br>w_{2 i}=& \frac{w_{1 i}}{Z_{1}} \exp \left(-\alpha_{1} y_{i} G_{1}\left(x_{i}\right)\right), \quad i=1,2, \cdots, 10 \<br>D_{2}=&(0.07143,0.07143,0.07143,0.07143,0.07143,0.07143,\<br>&0.16667,0.16667,0.16667,0.07143) \<br>f_{1}(x) &=0.4236 G_{1}(x)<br>\end{aligned}<br>$$</li></ul><p>对于m=2: </p><ul><li><p>在权值分布$D_2$的训练数据集上,遍历每个结点并计算分类误差率$e_m$,阈值取v=8.5时分类误差率最低,那么基本分类器为:<br>$$<br>G_{2}(x)=\left{\begin{array}{ll}<br>1, & x<8.5 \</p></li><li><p>1, & x>8.5<br>\end{array}\right.<br>$$ </p></li><li><p>$G_2(x)$在训练数据集上的误差率为$e_2 = 0.2143$ </p></li><li><p>计算$G_2(x)$的系数:$\alpha_2 = 0.6496$ </p></li><li><p>更新训练数据的权值分布:<br>$$<br>\begin{aligned}<br>D_{3}=&(0.0455,0.0455,0.0455,0.1667,0.1667,0.1667\<br>&0.1060,0.1060,0.1060,0.0455) \<br>f_{2}(x) &=0.4236 G_{1}(x)+0.6496 G_{2}(x)<br>\end{aligned}<br>$$ </p><p>对m=3: </p></li><li><p>在权值分布$D_3$的训练数据集上,遍历每个结点并计算分类误差率$e_m$,阈值取v=5.5时分类误差率最低,那么基本分类器为:<br>$$<br>G_{3}(x)=\left{\begin{array}{ll}<br>1, & x>5.5 \</p></li><li><p>1, & x<5.5<br>\end{array}\right.<br>$$ </p></li><li><p>$G_3(x)$在训练数据集上的误差率为$e_3 = 0.1820$ </p></li><li><p>计算$G_3(x)$的系数:$\alpha_3 = 0.7514$ </p></li><li><p>更新训练数据的权值分布:<br>$$<br>D_{4}=(0.125,0.125,0.125,0.102,0.102,0.102,0.065,0.065,0.065,0.125)<br>$$ </p><p>于是得到:$f_{3}(x)=0.4236 G_{1}(x)+0.6496 G_{2}(x)+0.7514 G_{3}(x)$,分类器$\operatorname{sign}\left[f_{3}(x)\right]$在训练数据集上的误分类点的个数为0。<br>于是得到最终分类器为:$G(x)=\operatorname{sign}\left[f_{3}(x)\right]=\operatorname{sign}\left[0.4236 G_{1}(x)+0.6496 G_{2}(x)+0.7514 G_{3}(x)\right]$</p></li></ul><p><strong>下面,我们使用sklearn对Adaboost算法进行建模:</strong></p><p>本次案例我们使用一份UCI的机器学习库里的开源数据集:葡萄酒数据集,该数据集可以在 ( <a href="https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data" target="_blank" rel="noopener">https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data</a> )上获得。该数据集包含了178个样本和13个特征,从不同的角度对不同的化学特性进行描述,我们的任务是根据这些数据预测红酒属于哪一个类别。(案例来源《python机器学习(第二版》)</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 引入数据科学相关工具包:</span><br><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><span class="hljs-keyword">import</span> pandas <span class="hljs-keyword">as</span> pd <br><span class="hljs-keyword">import</span> matplotlib.pyplot <span class="hljs-keyword">as</span> plt<br>plt.style.use(<span class="hljs-string">"ggplot"</span>)<br>%matplotlib inline<br><span class="hljs-keyword">import</span> seaborn <span class="hljs-keyword">as</span> sns<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 加载训练数据: </span><br>wine = pd.read_csv(<span class="hljs-string">"https://archive.ics.uci.edu/ml/machine-learning-databases/wine/wine.data"</span>,header=<span class="hljs-literal">None</span>)<br>wine.columns = [<span class="hljs-string">'Class label'</span>, <span class="hljs-string">'Alcohol'</span>, <span class="hljs-string">'Malic acid'</span>, <span class="hljs-string">'Ash'</span>, <span class="hljs-string">'Alcalinity of ash'</span>,<span class="hljs-string">'Magnesium'</span>, <span class="hljs-string">'Total phenols'</span>,<span class="hljs-string">'Flavanoids'</span>, <span class="hljs-string">'Nonflavanoid phenols'</span>, <br> <span class="hljs-string">'Proanthocyanins'</span>,<span class="hljs-string">'Color intensity'</span>, <span class="hljs-string">'Hue'</span>,<span class="hljs-string">'OD280/OD315 of diluted wines'</span>,<span class="hljs-string">'Proline'</span>]<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 数据查看:</span><br>print(<span class="hljs-string">"Class labels"</span>,np.unique(wine[<span class="hljs-string">"Class label"</span>]))<br>wine.head()<br></code></pre></td></tr></table></figure><p>下面对数据做简单解读: </p><ul><li>Class label:分类标签 </li><li>Alcohol:酒精 </li><li>Malic acid:苹果酸 </li><li>Ash:灰 </li><li>Alcalinity of ash:灰的碱度 </li><li>Magnesium:镁 </li><li>Total phenols:总酚 </li><li>Flavanoids:黄酮类化合物 </li><li>Nonflavanoid phenols:非黄烷类酚类 </li><li>Proanthocyanins:原花青素 </li><li>Color intensity:色彩强度 </li><li>Hue:色调 </li><li>OD280/OD315 of diluted wines:稀释酒OD280 OD350 </li><li>Proline:脯氨酸 </li></ul><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 数据预处理</span><br><span class="hljs-comment"># 仅仅考虑2,3类葡萄酒,去除1类</span><br>wine = wine[wine[<span class="hljs-string">'Class label'</span>]!=<span class="hljs-number">1</span>]<br>y = wine[<span class="hljs-string">'Class label'</span>].values<br>X = wine[[<span class="hljs-string">'Alcohol'</span>,<span class="hljs-string">'OD280/OD315 of diluted wines'</span>]].values<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 将分类标签变成二进制编码:</span><br><span class="hljs-keyword">from</span> sklearn.preprocessing <span class="hljs-keyword">import</span> LabelEncoder<br>le = LabelEncoder()<br>y = le.fit_transform(y)<br><br><span class="hljs-comment"># 按8:2分割训练集和测试集</span><br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> train_test_split<br>X_train,X_test,y_train,y_test = train_test_split(X,y,test_size=<span class="hljs-number">0.2</span>,random_state=<span class="hljs-number">1</span>,stratify=y) <span class="hljs-comment"># stratify参数代表了按照y的类别等比例抽样</span><br></code></pre></td></tr></table></figure><h2 id="使用单一决策树建模"><a href="#使用单一决策树建模" class="headerlink" title="使用单一决策树建模"></a>使用单一决策树建模</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> sklearn.tree <span class="hljs-keyword">import</span> DecisionTreeClassifier<br>tree = DecisionTreeClassifier(criterion=<span class="hljs-string">'entropy'</span>,random_state=<span class="hljs-number">1</span>,max_depth=<span class="hljs-number">1</span>)<br><span class="hljs-keyword">from</span> sklearn.metrics <span class="hljs-keyword">import</span> accuracy_score<br>tree = tree.fit(X_train,y_train)<br>y_train_pred = tree.predict(X_train)<br>y_test_pred = tree.predict(X_test)<br>tree_train = accuracy_score(y_train,y_train_pred)<br>tree_test = accuracy_score(y_test,y_test_pred)<br>print(<span class="hljs-string">'Decision tree train/test accuracies %.3f/%.3f'</span> % (tree_train,tree_test))<br></code></pre></td></tr></table></figure><h2 id="使用sklearn实现Adaboost-基分类器为决策树"><a href="#使用sklearn实现Adaboost-基分类器为决策树" class="headerlink" title="使用sklearn实现Adaboost(基分类器为决策树)"></a>使用sklearn实现Adaboost(基分类器为决策树)</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 使用sklearn实现Adaboost(基分类器为决策树)</span><br><span class="hljs-string">'''</span><br><span class="hljs-string">AdaBoostClassifier相关参数:</span><br><span class="hljs-string">base_estimator:基本分类器,默认为DecisionTreeClassifier(max_depth=1)</span><br><span class="hljs-string">n_estimators:终止迭代的次数</span><br><span class="hljs-string">learning_rate:学习率</span><br><span class="hljs-string">algorithm:训练的相关算法,{'SAMME','SAMME.R'},默认='SAMME.R'</span><br><span class="hljs-string">random_state:随机种子</span><br><span class="hljs-string">'''</span><br><span class="hljs-keyword">from</span> sklearn.ensemble <span class="hljs-keyword">import</span> AdaBoostClassifier<br>adaboost = AdaBoostClassifier(base_estimator=tree,n_estimators=<span class="hljs-number">500</span>,learning_rate=<span class="hljs-number">0.1</span>,random_state=<span class="hljs-number">1</span>)<br>adaboost = adaboost.fit(X_train,y_train)<br>y_train_pred = adaboost.predict(X_train)<br>y_test_pred = adaboost.predict(X_test)<br>ada_train = accuracy_score(y_train,y_train_pred)<br>ada_test = accuracy_score(y_test,y_test_pred)<br>print(<span class="hljs-string">'Adaboost train/test accuracies %.3f/%.3f'</span> % (ada_train,ada_test))<br></code></pre></td></tr></table></figure><p>结果分析:单层决策树似乎对训练数据欠拟合,而Adaboost模型正确地预测了训练数据的所有分类标签,而且与单层决策树相比,Adaboost的测试性能也略有提高。然而,为什么模型在训练集和测试集的性能相差这么大呢?我们使用图像来简单说明下这个道理!</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 画出单层决策树与Adaboost的决策边界:</span><br>x_min = X_train[:, <span class="hljs-number">0</span>].min() - <span class="hljs-number">1</span><br>x_max = X_train[:, <span class="hljs-number">0</span>].max() + <span class="hljs-number">1</span><br>y_min = X_train[:, <span class="hljs-number">1</span>].min() - <span class="hljs-number">1</span><br>y_max = X_train[:, <span class="hljs-number">1</span>].max() + <span class="hljs-number">1</span><br><span class="hljs-comment"># 生成网格矩阵</span><br>xx, yy = np.meshgrid(np.arange(x_min, x_max, <span class="hljs-number">0.1</span>),np.arange(y_min, y_max, <span class="hljs-number">0.1</span>))<br>f, axarr = plt.subplots(nrows=<span class="hljs-number">1</span>, ncols=<span class="hljs-number">2</span>,sharex=<span class="hljs-string">'col'</span>,sharey=<span class="hljs-string">'row'</span>,figsize=(<span class="hljs-number">12</span>, <span class="hljs-number">6</span>))<br><span class="hljs-keyword">for</span> idx, clf, tt <span class="hljs-keyword">in</span> zip([<span class="hljs-number">0</span>, <span class="hljs-number">1</span>],[tree, adaboost],[<span class="hljs-string">'Decision tree'</span>, <span class="hljs-string">'Adaboost'</span>]):<br> clf.fit(X_train, y_train)<br> Z = clf.predict(np.c_[xx.ravel(), yy.ravel()])<br> Z = Z.reshape(xx.shape)<br> axarr[idx].contourf(xx, yy, Z, alpha=<span class="hljs-number">0.3</span>)<br> axarr[idx].scatter(X_train[y_train==<span class="hljs-number">0</span>, <span class="hljs-number">0</span>],X_train[y_train==<span class="hljs-number">0</span>, <span class="hljs-number">1</span>],c=<span class="hljs-string">'blue'</span>, marker=<span class="hljs-string">'^'</span>)<br> axarr[idx].scatter(X_train[y_train==<span class="hljs-number">1</span>, <span class="hljs-number">0</span>],X_train[y_train==<span class="hljs-number">1</span>, <span class="hljs-number">1</span>],c=<span class="hljs-string">'red'</span>, marker=<span class="hljs-string">'o'</span>)<br> axarr[idx].set_title(tt)<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210617231136412.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>从上面的决策边界图可以看到:Adaboost模型的决策边界比单层决策树的决策边界要复杂的多。也就是说,Adaboost试图用增加模型复杂度而降低偏差的方式去减少总误差,但是过程中引入了方差,可能出现过拟合,因此在训练集和测试集之间的性能存在较大的差距,这就简单地回答的刚刚问题。<br>值的注意的是:与单个分类器相比,Adaboost等Boosting模型增加了计算的复杂度,在实践中需要仔细思考是否愿意为预测性能的相对改善而增加计算成本,而且Boosting方式无法做到现在流行的并行计算的方式进行训练,因为每一步迭代都要基于上一部的基本分类器。</p><h1 id="4-前向分步算法"><a href="#4-前向分步算法" class="headerlink" title="4. 前向分步算法"></a>4. 前向分步算法</h1><p>回看Adaboost的算法内容,我们需要通过计算M个基本分类器,每个分类器的错误率、样本权重以及模型权重。我们可以认为:Adaboost每次学习单一分类器以及单一分类器的参数(权重)。接下来,我们抽象出Adaboost算法的整体框架逻辑,构建集成学习的一个非常重要的框架—-前向分步算法,有了这个框架,我们不仅可以解决分类问题,也可以解决回归问题。<br><strong>(1) 加法模型:</strong><br>在Adaboost模型中,我们把每个基本分类器合成一个复杂分类器的方法是每个基本分类器的加权和,即:$f(x)=\sum_{m=1}^{M} \beta_{m} b\left(x ; \gamma_{m}\right)$,其中,$b\left(x ; \gamma_{m}\right)$为即基本分类器,$\gamma_{m}$为基本分类器的参数,$\beta_m$为基本分类器的权重,显然这与第二章所学的加法模型。为什么这么说呢?大家把$b(x ; \gamma_{m})$看成是即函数即可。<br>在给定训练数据以及损失函数$L(y, f(x))$的条件下,学习加法模型$f(x)$就是:<br>$$<br>\min <em>{\beta</em>{m}, \gamma_{m}} \sum_{i=1}^{N} L\left(y_{i}, \sum_{m=1}^{M} \beta_{m} b\left(x_{i} ; \gamma_{m}\right)\right)<br>$$<br>通常这是一个复杂的优化问题,很难通过简单的凸优化的相关知识进行解决。前向分步算法可以用来求解这种方式的问题,它的基本思路是:因为学习的是加法模型,如果从前向后,每一步只优化一个基函数及其系数,逐步逼近目标函数,那么就可以降低优化的复杂度。具体而言,每一步只需要优化:<br>$$<br>\min <em>{\beta, \gamma} \sum</em>{i=1}^{N} L\left(y_{i}, \beta b\left(x_{i} ; \gamma\right)\right)<br>$$<br><strong>(2) 前向分步算法:</strong><br>给定数据集$T=\left{\left(x_{1}, y_{1}\right),\left(x_{2}, y_{2}\right), \cdots,\left(x_{N}, y_{N}\right)\right}$,$x_{i} \in \mathcal{X} \subseteq \mathbf{R}^{n}$,$y_{i} \in \mathcal{Y}={+1,-1}$。损失函数$L(y, f(x))$,基函数集合${b(x ; \gamma)}$,我们需要输出加法模型$f(x)$。 </p><ul><li>初始化:$f_{0}(x)=0$ </li><li>对m = 1,2,…,M: <ul><li>(a) 极小化损失函数:<br>$$<br>\left(\beta_{m}, \gamma_{m}\right)=\arg \min <em>{\beta, \gamma} \sum</em>{i=1}^{N} L\left(y_{i}, f_{m-1}\left(x_{i}\right)+\beta b\left(x_{i} ; \gamma\right)\right)<br>$$<br>得到参数$\beta_{m}$与$\gamma_{m}$ </li><li>(b) 更新:<br>$$<br>f_{m}(x)=f_{m-1}(x)+\beta_{m} b\left(x ; \gamma_{m}\right)<br>$$ </li></ul></li><li>得到加法模型:<br>$$<br>f(x)=f_{M}(x)=\sum_{m=1}^{M} \beta_{m} b\left(x ; \gamma_{m}\right)<br>$$ </li></ul><p>这样,前向分步算法将同时求解从m=1到M的所有参数$\beta_{m}$,$\gamma_{m}$的优化问题简化为逐次求解各个$\beta_{m}$,$\gamma_{m}$的问题。<br><strong>(3) 前向分步算法与Adaboost的关系:</strong><br>由于这里不是我们的重点,我们主要阐述这里的结论,不做相关证明,具体的证明见李航老师的《统计学习方法》第八章的3.2节。Adaboost算法是前向分步算法的特例,Adaboost算法是由基本分类器组成的加法模型,损失函数为指数损失函数。</p>]]></content>
<summary type="html">
<h1 id="1-导论"><a href="#1-导论" class="headerlink" title="1. 导论"></a>1. 导论</h1><p>在前面的学习中,我们探讨了一系列简单而实用的回归和分类模型,同时也探讨了如何使用集成学习家族中的Bagging思想去优化最终的模型。Bagging思想的实质是:通过Bootstrap 的方式对全样本数据集进行抽样得到抽样子集,对不同的子集使用同一种基本模型进行拟合,然后投票得出最终的预测。我们也从前面的探讨知道:Bagging主要通过降低方差的方式减少预测误差。<strong>那么,本章介绍的Boosting是与Bagging截然不同的思想,Boosting方法是使用同一组数据集进行反复学习,得到一系列简单模型,然后组合这些模型构成一个预测性能十分强大的机器学习模型。</strong> 显然,Boosting思想提高最终的预测效果是通过不断减少偏差的形式,与Bagging有着本质的不同。在Boosting这一大类方法中,笔者主要介绍两类常用的Boosting方式:Adaptive Boosting 和 Gradient Boosting 以及它们的变体Xgboost、LightGBM以及Catboost。</p>
</summary>
<category term="datawhale" scheme="https://jackyin.space/tags/datawhale/"/>
<category term="机器学习" scheme="https://jackyin.space/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>集成学习专题——voting&bagging</title>
<link href="https://jackyin.space/2021/06/16/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94voting-bagging/"/>
<id>https://jackyin.space/2021/06/16/%E9%9B%86%E6%88%90%E5%AD%A6%E4%B9%A0%E4%B8%93%E9%A2%98%E2%80%94%E2%80%94voting-bagging/</id>
<published>2021-06-16T15:55:00.000Z</published>
<updated>2024-10-09T08:43:45.344Z</updated>
<content type="html"><![CDATA[<h2 id="投票法的思路"><a href="#投票法的思路" class="headerlink" title="投票法的思路"></a>投票法的思路</h2><p><strong>投票法是集成学习中常用的技巧,可以帮助我们提高模型的泛化能力,减少模型的错误率。</strong>举个例子,在航空航天领域,每个零件发出的电信号都对航空器的成功发射起到重要作用。如果我们有一个二进制形式的信号:</p><p>11101100100111001011011011011</p><p>在传输过程中第二位发生了翻转</p><p>10101100100111001011011011011</p><p>这导致的结果可能是致命的。一个常用的纠错方法是重复多次发送数据,并以少数服从多数的方法确定正确的传输数据。一般情况下,错误总是发生在局部,因此融合多个数据是降低误差的一个好方法,这就是投票法的基本思路。</p><p>对于回归模型来说,投票法最终的预测结果是多个其他回归模型预测结果的平均值。</p><p>对于分类模型,<strong>硬投票法</strong>的预测结果是多个模型预测结果中出现次数最多的类别,<strong>软投票</strong>对各类预测结果的概率进行求和,最终选取概率之和最大的类标签。</p><a id="more"></a><h2 id="投票法的原理分析"><a href="#投票法的原理分析" class="headerlink" title="投票法的原理分析"></a>投票法的原理分析</h2><p>投票法是一种遵循少数服从多数原则的集成学习模型,通过多个模型的集成降低方差,从而提高模型的鲁棒性。在理想情况下,投票法的预测效果应当优于任何一个基模型的预测效果。</p><p>投票法在回归模型与分类模型上均可使用:</p><ul><li>回归投票法:预测结果是所有模型预测结果的平均值。</li><li>分类投票法:预测结果是所有模型种出现最多的预测结果。</li></ul><p>分类投票法又可以被划分为硬投票与软投票:</p><ul><li>硬投票:预测结果是所有投票结果最多出现的类。</li><li>软投票:预测结果是所有投票结果中概率加和最大的类。</li></ul><p>下面我们使用一个例子说明硬投票:</p><blockquote><p>对于某个样本:</p><p>模型 1 的预测结果是 类别 A</p><p>模型 2 的预测结果是 类别 B</p><p>模型 3 的预测结果是 类别 B</p></blockquote><p>有2/3的模型预测结果是B,因此硬投票法的预测结果是B</p><p>同样的例子说明软投票:</p><blockquote><p>对于某个样本:</p><p>模型 1 的预测结果是 类别 A 的概率为 99%</p><p>模型 2 的预测结果是 类别 A 的概率为 49%</p><p>模型 3 的预测结果是 类别 A 的概率为 49%</p></blockquote><p>最终对于类别A的预测概率的平均是 (99 + 49 + 49) / 3 = 65.67%,因此软投票法的预测结果是A。</p><p>从这个例子我们可以看出,软投票法与硬投票法可以得出完全不同的结论。<strong>相对于硬投票,软投票法考虑到了预测概率这一额外的信息,因此可以得出比硬投票法更加准确的预测结果。</strong></p><p>在投票法中,我们还需要考虑到不同的基模型可能产生的影响。理论上,基模型可以是任何已被训练好的模型。但在实际应用上,想要投票法产生较好的结果,需要满足两个条件:</p><ul><li>基模型之间的效果不能差别过大。当某个基模型相对于其他基模型效果过差时,该模型很可能成为噪声。</li><li>基模型之间应该有较小的同质性。例如在基模型预测效果近似的情况下,基于树模型与线性模型的投票,往往优于两个树模型或两个线性模型。</li></ul><p>当投票合集中使用的模型能预测出清晰的类别标签时,适合使用硬投票。当投票集合中使用的模型能预测类别的概率时,适合使用软投票。软投票同样可以用于那些本身并不预测类成员概率的模型,只要他们可以输出类似于概率的预测分数值(例如支持向量机、k-最近邻和决策树)。</p><p><strong>投票法的局限性在于,它对所有模型的处理是一样的,这意味着所有模型对预测的贡献是一样的。</strong>如果一些模型在某些情况下很好,而在其他情况下很差,这是使用投票法时需要考虑到的一个问题。</p><h2 id="投票法的案例分析-基于sklearn,介绍pipe管道的使用以及voting的使用"><a href="#投票法的案例分析-基于sklearn,介绍pipe管道的使用以及voting的使用" class="headerlink" title="投票法的案例分析(基于sklearn,介绍pipe管道的使用以及voting的使用)"></a>投票法的案例分析(基于sklearn,介绍pipe管道的使用以及voting的使用)</h2><p>Sklearn中提供了 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingRegressor.html" target="_blank" rel="noopener">VotingRegressor</a> 与 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.VotingClassifier.html" target="_blank" rel="noopener">VotingClassifier</a> 两个投票方法。 这两种模型的操作方式相同,并采用相同的参数。使用模型需要提供一个模型列表,列表中每个模型采用Tuple的结构表示,第一个元素代表名称,第二个元素代表模型,需要保证每个模型必须拥有唯一的名称。</p><p>例如这里,我们定义两个模型:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python">models = [(<span class="hljs-string">'lr'</span>,LogisticRegression()),(<span class="hljs-string">'svm'</span>,SVC())]<br>ensemble = VotingClassifier(estimators=models)<br></code></pre></td></tr></table></figure><p>有时某些模型需要一些预处理操作,我们可以为他们定义Pipeline完成模型预处理工作:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python">models = [(<span class="hljs-string">'lr'</span>,LogisticRegression()),(<span class="hljs-string">'svm'</span>,make_pipeline(StandardScaler(),SVC()))]<br>ensemble = VotingClassifier(estimators=models)<br></code></pre></td></tr></table></figure><p>模型还提供了voting参数让我们选择软投票或者硬投票:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python">models = [(<span class="hljs-string">'lr'</span>,LogisticRegression()),(<span class="hljs-string">'svm'</span>,SVC())]<br>ensemble = VotingClassifier(estimators=models, voting=<span class="hljs-string">'soft'</span>)<br></code></pre></td></tr></table></figure><p>下面我们使用一个完整的例子演示投票法的使用:</p><p>首先我们创建一个1000个样本,20个特征的随机数据集:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test classification dataset</span><br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_classification<br><span class="hljs-comment"># define dataset</span><br>X, y = make_classification(n_samples=<span class="hljs-number">1000</span>, n_features=<span class="hljs-number">20</span>, n_informative=<span class="hljs-number">15</span>, n_redundant=<span class="hljs-number">5</span>, random_state=<span class="hljs-number">2</span>)<br><span class="hljs-comment"># summarize the dataset</span><br>print(X.shape, y.shape)<br></code></pre></td></tr></table></figure><p>我们使用多个KNN模型作为基模型演示投票法,其中每个模型采用不同的邻居值K参数:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># get a voting ensemble of models</span><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_voting</span><span class="hljs-params">()</span>:</span><br><span class="hljs-comment"># define the base models</span><br>models = list()<br>models.append((<span class="hljs-string">'knn1'</span>, KNeighborsClassifier(n_neighbors=<span class="hljs-number">1</span>)))<br>models.append((<span class="hljs-string">'knn3'</span>, KNeighborsClassifier(n_neighbors=<span class="hljs-number">3</span>)))<br>models.append((<span class="hljs-string">'knn5'</span>, KNeighborsClassifier(n_neighbors=<span class="hljs-number">5</span>)))<br>models.append((<span class="hljs-string">'knn7'</span>, KNeighborsClassifier(n_neighbors=<span class="hljs-number">7</span>)))<br>models.append((<span class="hljs-string">'knn9'</span>, KNeighborsClassifier(n_neighbors=<span class="hljs-number">9</span>)))<br><span class="hljs-comment"># define the voting ensemble</span><br>ensemble = VotingClassifier(estimators=models, voting=<span class="hljs-string">'hard'</span>)<br><span class="hljs-keyword">return</span> ensemble<br></code></pre></td></tr></table></figure><p>然后,我们可以创建一个模型列表来评估投票带来的提升,包括KNN模型配置的每个独立版本和硬投票模型。下面的get_models()函数可以为我们创建模型列表进行评估。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># get a list of models to evaluate</span><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">get_models</span><span class="hljs-params">()</span>:</span><br>models = dict()<br>models[<span class="hljs-string">'knn1'</span>] = KNeighborsClassifier(n_neighbors=<span class="hljs-number">1</span>)<br>models[<span class="hljs-string">'knn3'</span>] = KNeighborsClassifier(n_neighbors=<span class="hljs-number">3</span>)<br>models[<span class="hljs-string">'knn5'</span>] = KNeighborsClassifier(n_neighbors=<span class="hljs-number">5</span>)<br>models[<span class="hljs-string">'knn7'</span>] = KNeighborsClassifier(n_neighbors=<span class="hljs-number">7</span>)<br>models[<span class="hljs-string">'knn9'</span>] = KNeighborsClassifier(n_neighbors=<span class="hljs-number">9</span>)<br>models[<span class="hljs-string">'hard_voting'</span>] = get_voting()<br><span class="hljs-keyword">return</span> models<br></code></pre></td></tr></table></figure><p>下面的evaluate_model()函数接收一个模型实例,并以分层10倍交叉验证三次重复的分数列表的形式返回。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># evaluate a give model using cross-validation</span><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">evaluate_model</span><span class="hljs-params">(model, X, y)</span>:</span><br>cv = RepeatedStratifiedKFold(n_splits=<span class="hljs-number">10</span>, n_repeats=<span class="hljs-number">3</span>, random_state=<span class="hljs-number">1</span>)<br>scores = cross_val_score(model, X, y, scoring=<span class="hljs-string">'accuracy'</span>, cv=cv, n_jobs=<span class="hljs-number">-1</span>, error_score=<span class="hljs-string">'raise'</span>)<br><span class="hljs-keyword">return</span> scores<br></code></pre></td></tr></table></figure><p>然后,我们可以报告每个算法的平均性能,还可以创建一个箱形图和须状图来比较每个算法的精度分数分布。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># define dataset</span><br>X, y = get_dataset()<br><span class="hljs-comment"># get the models to evaluate</span><br>models = get_models()<br><span class="hljs-comment"># evaluate the models and store results</span><br>results, names = list(), list()<br><span class="hljs-keyword">for</span> name, model <span class="hljs-keyword">in</span> models.items():<br>scores = evaluate_model(model, X, y)<br>results.append(scores)<br>names.append(name)<br>print(<span class="hljs-string">'>%s %.3f (%.3f)'</span> % (name, mean(scores), std(scores)))<br><span class="hljs-comment"># plot model performance for comparison</span><br>pyplot.boxplot(results, labels=names, showmeans=<span class="hljs-literal">True</span>)<br>pyplot.show()<br></code></pre></td></tr></table></figure><p>我们得到的结果如下:</p><figure class="highlight plain"><table><tr><td class="code"><pre><code class="hljs plain">>knn1 0.873 (0.030)<br>>knn3 0.889 (0.038)<br>>knn5 0.895 (0.031)<br>>knn7 0.899 (0.035)<br>>knn9 0.900 (0.033)<br>>hard_voting 0.902 (0.034)<br></code></pre></td></tr></table></figure><p>显然投票的效果略大于任何一个基模型。</p><p><img src="https://img-blog.csdnimg.cn/img_convert/f801ea25bd203a492f6c6c749788f553.png" alt="Box Plot of Hard Voting Ensemble Compared to Standalone Models for Binary Classification"></p><p>通过箱形图我们可以看到硬投票方法对交叉验证整体预测结果分布带来的提升。</p><h2 id="bagging的思路"><a href="#bagging的思路" class="headerlink" title="bagging的思路"></a>bagging的思路</h2><p>与投票法不同的是,<strong>Bagging不仅仅集成模型最后的预测结果,同时采用一定策略来影响基模型训练,保证基模型可以服从一定的假设。</strong> 在上一章中我们提到,希望各个模型之间具有较大的差异性,而在实际操作中的模型却往往是同质的,因此一个简单的思路是<strong>通过不同的采样增加模型的差异性</strong>。</p><h2 id="bagging的原理分析"><a href="#bagging的原理分析" class="headerlink" title="bagging的原理分析"></a>bagging的原理分析</h2><p>Bagging的核心在于<strong>自助采样</strong>(bootstrap)这一概念,即<strong>有放回的从数据集中进行采样,也就是说,同样的一个样本可能被多次进行采样。</strong> 一个自助采样的小例子是我们希望估计全国所有人口年龄的平均值,那么我们可以在全国所有人口中随机抽取不同的集合(这些集合可能存在交集),计算每个集合的平均值,然后将所有平均值的均值作为估计值。</p><p>首先我们随机取出一个样本放入采样集合中,再把这个样本放回初始数据集,重复K次采样,最终我们可以获得一个大小为K的样本集合。同样的方法, 我们可以采样出T个含K个样本的采样集合,然后基于每个采样集合训练出一个基学习器,再将这些基学习器进行结合,这就是Bagging的基本流程。</p><p>对回归问题的预测是通过预测取平均值来进行的。对于分类问题的预测是通过对预测取多数票预测来进行的。<strong>Bagging方法之所以有效,是因为每个模型都是在略微不同的训练数据集上拟合完成的,这又使得每个基模型之间存在略微的差异,使每个基模型拥有略微不同的训练能力。</strong></p><p>Bagging同样是一种<strong>降低方差</strong>的技术,因此它在不剪枝决策树、神经网络等易受样本扰动的学习器上效果更加明显。在实际的使用中,加入列采样的Bagging技术对高维小样本往往有神奇的效果。</p><h2 id="bagging的案例分析-基于sklearn,介绍随机森林的相关理论以及实例"><a href="#bagging的案例分析-基于sklearn,介绍随机森林的相关理论以及实例" class="headerlink" title="bagging的案例分析(基于sklearn,介绍随机森林的相关理论以及实例)"></a>bagging的案例分析(基于sklearn,介绍随机森林的相关理论以及实例)</h2><p>Sklearn为我们提供了 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.BaggingRegressor.html" target="_blank" rel="noopener">BaggingRegressor</a> 与 <a href="https://scikit-learn.org/stable/modules/generated/sklearn.ensemble.BaggingClassifier.html" target="_blank" rel="noopener">BaggingClassifier</a> 两种Bagging方法的API,我们在这里通过一个完整的例子演示Bagging在分类问题上的具体应用。这里两种方法的默认基模型是树模型。</p><p>我们创建一个含有1000个样本20维特征的随机分类数据集:</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># test classification dataset</span><br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_classification<br><span class="hljs-comment"># define dataset</span><br>X, y = make_classification(n_samples=<span class="hljs-number">1000</span>, n_features=<span class="hljs-number">20</span>, n_informative=<span class="hljs-number">15</span>, n_redundant=<span class="hljs-number">5</span>, random_state=<span class="hljs-number">5</span>)<br><span class="hljs-comment"># summarize the dataset</span><br>print(X.shape, y.shape)<br></code></pre></td></tr></table></figure><p>我们将使用重复的分层k-fold交叉验证来评估该模型,一共重复3次,每次有10个fold。我们将评估该模型在所有重复交叉验证中性能的平均值和标准差。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># evaluate bagging algorithm for classification</span><br><span class="hljs-keyword">from</span> numpy <span class="hljs-keyword">import</span> mean<br><span class="hljs-keyword">from</span> numpy <span class="hljs-keyword">import</span> std<br><span class="hljs-keyword">from</span> sklearn.datasets <span class="hljs-keyword">import</span> make_classification<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> cross_val_score<br><span class="hljs-keyword">from</span> sklearn.model_selection <span class="hljs-keyword">import</span> RepeatedStratifiedKFold<br><span class="hljs-keyword">from</span> sklearn.ensemble <span class="hljs-keyword">import</span> BaggingClassifier<br><span class="hljs-comment"># define dataset</span><br>X, y = make_classification(n_samples=<span class="hljs-number">1000</span>, n_features=<span class="hljs-number">20</span>, n_informative=<span class="hljs-number">15</span>, n_redundant=<span class="hljs-number">5</span>, random_state=<span class="hljs-number">5</span>)<br><span class="hljs-comment"># define the model</span><br>model = BaggingClassifier()<br><span class="hljs-comment"># evaluate the model</span><br>cv = RepeatedStratifiedKFold(n_splits=<span class="hljs-number">10</span>, n_repeats=<span class="hljs-number">3</span>, random_state=<span class="hljs-number">1</span>)<br>n_scores = cross_val_score(model, X, y, scoring=<span class="hljs-string">'accuracy'</span>, cv=cv, n_jobs=<span class="hljs-number">-1</span>, error_score=<span class="hljs-string">'raise'</span>)<br><span class="hljs-comment"># report performance</span><br>print(<span class="hljs-string">'Accuracy: %.3f (%.3f)'</span> % (mean(n_scores), std(n_scores)))<br></code></pre></td></tr></table></figure><p>最终模型的效果是Accuracy: 0.856 标准差0.037</p>]]></content>
<summary type="html">
<h2 id="投票法的思路"><a href="#投票法的思路" class="headerlink" title="投票法的思路"></a>投票法的思路</h2><p><strong>投票法是集成学习中常用的技巧,可以帮助我们提高模型的泛化能力,减少模型的错误率。</strong>举个例子,在航空航天领域,每个零件发出的电信号都对航空器的成功发射起到重要作用。如果我们有一个二进制形式的信号:</p>
<p>11101100100111001011011011011</p>
<p>在传输过程中第二位发生了翻转</p>
<p>10101100100111001011011011011</p>
<p>这导致的结果可能是致命的。一个常用的纠错方法是重复多次发送数据,并以少数服从多数的方法确定正确的传输数据。一般情况下,错误总是发生在局部,因此融合多个数据是降低误差的一个好方法,这就是投票法的基本思路。</p>
<p>对于回归模型来说,投票法最终的预测结果是多个其他回归模型预测结果的平均值。</p>
<p>对于分类模型,<strong>硬投票法</strong>的预测结果是多个模型预测结果中出现次数最多的类别,<strong>软投票</strong>对各类预测结果的概率进行求和,最终选取概率之和最大的类标签。</p>
</summary>
<category term="datawhale" scheme="https://jackyin.space/tags/datawhale/"/>
<category term="机器学习" scheme="https://jackyin.space/tags/%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0/"/>
</entry>
<entry>
<title>数据结构与算法——二分</title>
<link href="https://jackyin.space/2021/06/15/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E2%80%94%E2%80%94%E4%BA%8C%E5%88%86/"/>
<id>https://jackyin.space/2021/06/15/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E2%80%94%E2%80%94%E4%BA%8C%E5%88%86/</id>
<published>2021-06-15T13:48:00.000Z</published>
<updated>2024-10-09T08:43:45.316Z</updated>
<content type="html"><![CDATA[<h2 id="数据结构与算法——二分"><a href="#数据结构与算法——二分" class="headerlink" title="数据结构与算法——二分"></a>数据结构与算法——二分</h2><p>最近leetcode每日一题经常出二分的题目,正好对前段时间学过的二分进行一些总结,首先这里要明确的一点是,<strong>二分的本质并不是单调性,而是通过某种条件将整个区间划分成满足条件和不满足条件的两端即可进行二分查找。</strong><br>在二分这个专题,主要有两种类型的划分方式,一种是整数划分,一种是浮点数划分,前一种一般是我们最熟悉的二分查找的题型,也是出题比较灵活考的比较多的一种,后一种主要是为控制实数精度而设置的浮点数二分法(建议用<code>double</code>,<code>float</code>有时候会出现精度丢失)。</p><a id="more"></a><h2 id="整数二分"><a href="#整数二分" class="headerlink" title="整数二分"></a>整数二分</h2><p>这里我们拿一道经典例题来给出我们二分的两个十分精妙的模板。<a href="https://www.acwing.com/problem/content/791/" target="_blank" rel="noopener">AcWing789. 数的范围</a></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">check</span><span class="hljs-params">(<span class="hljs-keyword">int</span> x)</span> </span>{<span class="hljs-comment">/* ... */</span>} <span class="hljs-comment">// 检查x是否满足某种性质</span><br><br><span class="hljs-comment">// 区间[l, r]被划分成[l, mid]和[mid + 1, r]时使用:</span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">bsearch_1</span><span class="hljs-params">(<span class="hljs-keyword">int</span> l, <span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">while</span>(l < r)<br> {<br> <span class="hljs-keyword">int</span> mid = l + r >> <span class="hljs-number">1</span>; <span class="hljs-comment">// 如果写r=mid则这里不需要+1</span><br> <span class="hljs-keyword">if</span>(check(mid)) r = mid; <span class="hljs-comment">// check()判断mid是否满足性质</span><br> <span class="hljs-keyword">else</span> l = mid + <span class="hljs-number">1</span>;<br> }<br> <span class="hljs-keyword">return</span> l; <span class="hljs-comment">//退出时 l与r相等</span><br>}<br><span class="hljs-comment">// 区间[l, r]被划分成[l, mid - 1]和[mid, r]时使用:</span><br><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">bsearch_2</span><span class="hljs-params">(<span class="hljs-keyword">int</span> l, <span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">while</span>(l < r)<br> {<br> <span class="hljs-keyword">int</span> mid = l + r + <span class="hljs-number">1</span> >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span>(check(mid)) l = mid;<br> <span class="hljs-keyword">else</span> r = mid - <span class="hljs-number">1</span>;<br> }<br> <span class="hljs-keyword">return</span> l;<br>}<br></code></pre></td></tr></table></figure><h2 id="浮点数二分"><a href="#浮点数二分" class="headerlink" title="浮点数二分"></a>浮点数二分</h2><p>浮点数二分考的比较少,主要是实现对于高精度答案的控制。<a href="https://www.acwing.com/problem/content/792/" target="_blank" rel="noopener">AcWing 790. 数的三次方根</a><br><strong>这里有个小bug需要提醒一下</strong>,<strong>我们以求二次方根为例</strong>,我们可以知道,$x=0.01$时,$\sqrt{x}=0.1>x$,所以当我们把二分的区间设置成$[0,x]$时,我们则无法找到答案,所以设置区间的时候我们最好可以设置大一点的区间,或者设置成$[0,\max(1,x)]$<br><img src="https://img-blog.csdnimg.cn/20210615210034299.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">bool</span> <span class="hljs-title">check</span><span class="hljs-params">(<span class="hljs-keyword">double</span> x)</span> </span>{<span class="hljs-comment">/* ... */</span>} <span class="hljs-comment">// 检查x是否满足某种性质</span><br><br><span class="hljs-function"><span class="hljs-keyword">double</span> <span class="hljs-title">bsearch_3</span><span class="hljs-params">(<span class="hljs-keyword">double</span> l, <span class="hljs-keyword">double</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">const</span> <span class="hljs-keyword">double</span> eps = <span class="hljs-number">1e-8</span>; <span class="hljs-comment">// eps 表示精度,取决于题目对精度的要求</span><br> <span class="hljs-keyword">while</span>(r - l > eps)<br> {<br> <span class="hljs-keyword">double</span> mid = (l + r) / <span class="hljs-number">2</span>; <span class="hljs-comment">// 对于浮点数二分则不需要考虑+1的问题</span><br> <span class="hljs-keyword">if</span>(check(mid)) r = mid;<br> <span class="hljs-keyword">else</span> l = mid;<br>}<br><span class="hljs-keyword">return</span> l;<br>}<br></code></pre></td></tr></table></figure><h2 id="STL"><a href="#STL" class="headerlink" title="STL"></a>STL</h2><p>做题当中手写二分的题其实比较少,基本上记忆上述模板就能解决所以的问题,同时我们在日常做题的时候,为了方便,我们通常是使用STL当中的函数来实现二分,且支持vector,map,set等操作,还有结构体大小比较,这里就有三个一定要记住的API。头文件引入:<code>#include<algorithm></code><br><code>binary_search</code><br>功能:二分查找某个元素是否出现。<br>返回值:在数组中以二分法检索的方式查找,若在数组(<strong>要求数组元素非递减</strong>)中查找到indx元素则真,若查找不到则返回值为假。<br>用法实例:<br>a.数组用法</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">int</span> a[<span class="hljs-number">100</span>]= {<span class="hljs-number">4</span>,<span class="hljs-number">10</span>,<span class="hljs-number">11</span>,<span class="hljs-number">30</span>,<span class="hljs-number">69</span>,<span class="hljs-number">70</span>,<span class="hljs-number">96</span>,<span class="hljs-number">100</span>};<br><span class="hljs-keyword">int</span> b=binary_search(a,a+<span class="hljs-number">9</span>,<span class="hljs-number">4</span>);<span class="hljs-comment">//查找成功,返回1</span><br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"在数组中查找元素4,结果为:"</span><<b<<<span class="hljs-built_in">endl</span>;<br></code></pre></td></tr></table></figure><p>b.vector用法</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>> res = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>};<br><span class="hljs-built_in">cout</span><<binary_search(res.begin(),res.end(),<span class="hljs-number">3</span>)<<<span class="hljs-built_in">endl</span>;<br></code></pre></td></tr></table></figure><p><code>lower_bound</code><br>功能:查找非递减序列[first,last) 内第一个<strong>大于或等于</strong>某个元素的位置。<br>返回值:如果找到返回找到元素的地址否则返回<strong>数组边界的下一个元素的地址</strong>。(这样不注意的话会越界,小心)<br>用法实例:</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">int</span> a[<span class="hljs-number">100</span>]= {<span class="hljs-number">4</span>,<span class="hljs-number">10</span>,<span class="hljs-number">11</span>,<span class="hljs-number">30</span>,<span class="hljs-number">69</span>,<span class="hljs-number">70</span>,<span class="hljs-number">96</span>,<span class="hljs-number">100</span>};<br><span class="hljs-keyword">int</span> d=lower_bound(a,a+<span class="hljs-number">9</span>,<span class="hljs-number">10</span>)-a;<br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"在数组中查找第一个大于等于10的元素位置,结果为:"</span><<d<<<span class="hljs-built_in">endl</span>;<br><span class="hljs-keyword">int</span> e=lower_bound(a,a+<span class="hljs-number">9</span>,<span class="hljs-number">101</span>)-a;<br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"在数组中查找第一个大于等于101的元素位置,结果为:"</span><<e<<<span class="hljs-built_in">endl</span>;<br></code></pre></td></tr></table></figure><p>b.vector用法</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>> res = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>};<br><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>::iterator it = lower_bound(res.begin(),res.end(),<span class="hljs-number">3</span>);<span class="hljs-comment">//返回迭代器的位置</span><br><span class="hljs-comment">//如果不存在,迭代器的位置会返回res.end()</span><br><span class="hljs-keyword">if</span>(it==res.end()) <span class="hljs-built_in">cout</span><<<span class="hljs-string">"不存在"</span><<<span class="hljs-built_in">endl</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-built_in">cout</span><<<span class="hljs-string">"求出下标:"</span><<(it - res.begin())<<<span class="hljs-built_in">endl</span>;<br><span class="hljs-comment">//也可以添加偏移量</span><br><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>::iterator it = lower_bound(res.begin()+<span class="hljs-number">1</span>,res.end(),<span class="hljs-number">3</span>);<br></code></pre></td></tr></table></figure><p><code>upper_bound</code></p><p>功能:查找非递减序列[first,last) 内第一个<strong>大于</strong>某个元素的位置。</p><p>返回值:如果找到返回找到元素的地址,否则返回<strong>数组边界的下一个元素的地址</strong>。(同样这样不注意的话会越界,小心)<br>用法实例:</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">int</span> a[<span class="hljs-number">100</span>]= {<span class="hljs-number">4</span>,<span class="hljs-number">10</span>,<span class="hljs-number">11</span>,<span class="hljs-number">30</span>,<span class="hljs-number">69</span>,<span class="hljs-number">70</span>,<span class="hljs-number">96</span>,<span class="hljs-number">100</span>};<br><span class="hljs-keyword">int</span> d=upper_bound(a,a+<span class="hljs-number">9</span>,<span class="hljs-number">10</span>)-a;<br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"在数组中查找第一个大于等于10的元素位置,结果为:"</span><<d<<<span class="hljs-built_in">endl</span>;<br><span class="hljs-keyword">int</span> e=upper_bound(a,a+<span class="hljs-number">9</span>,<span class="hljs-number">101</span>)-a;<br><span class="hljs-built_in">cout</span><<<span class="hljs-string">"在数组中查找第一个大于等于101的元素位置,结果为:"</span><<e<<<span class="hljs-built_in">endl</span>;<br></code></pre></td></tr></table></figure><p>b.vector用法</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>> res = {<span class="hljs-number">1</span>,<span class="hljs-number">2</span>,<span class="hljs-number">3</span>};<br><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>::iterator it = upper_bound(res.begin(),res.end(),<span class="hljs-number">3</span>);<span class="hljs-comment">//返回迭代器的位置</span><br><span class="hljs-comment">//如果不存在,迭代器的位置会返回res.end()</span><br><span class="hljs-keyword">if</span>(it==res.end()) <span class="hljs-built_in">cout</span><<<span class="hljs-string">"不存在"</span><<<span class="hljs-built_in">endl</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-built_in">cout</span><<<span class="hljs-string">"求出下标:"</span><<(it - res.begin())<<<span class="hljs-built_in">endl</span>;<br><span class="hljs-comment">//也可以添加偏移量</span><br><span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>::iterator it = upper_bound(res.begin()+<span class="hljs-number">1</span>,res.end(),<span class="hljs-number">3</span>);<br></code></pre></td></tr></table></figure><h2 id="经典例题"><a href="#经典例题" class="headerlink" title="经典例题"></a>经典例题</h2><p>这里我顺便给出这两天的每日一题的解题方案,里面还涉及到了一个<strong>防止溢出</strong>的二分处理trick。<br><a href="https://leetcode-cn.com/problems/guess-number-higher-or-lower/" target="_blank" rel="noopener">猜数字大小</a><br><img src="https://img-blog.csdnimg.cn/20210615214228227.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-comment">/** </span><br><span class="hljs-comment"> * Forward declaration of guess API.</span><br><span class="hljs-comment"> * @param num your guess</span><br><span class="hljs-comment"> * @return -1 if num is lower than the guess number</span><br><span class="hljs-comment"> * 1 if num is higher than the guess number</span><br><span class="hljs-comment"> * otherwise return 0</span><br><span class="hljs-comment"> * int guess(int num);</span><br><span class="hljs-comment"> */</span><br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">guessNumber</span><span class="hljs-params">(<span class="hljs-keyword">int</span> n)</span> </span>{<br> <span class="hljs-keyword">int</span> l = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">int</span> r = n;<br> <span class="hljs-keyword">while</span>(l<r)<br> {<br> <span class="hljs-keyword">int</span> mid = l + (r - l >> <span class="hljs-number">1</span>);<span class="hljs-comment">//防止溢出</span><br> <span class="hljs-keyword">if</span>(guess(mid)<=<span class="hljs-number">0</span>) <br> r = mid;<br> <span class="hljs-keyword">else</span> <br> l = mid + <span class="hljs-number">1</span>;<br> }<br> <span class="hljs-keyword">return</span> r;<br> }<br>};<br></code></pre></td></tr></table></figure><p><a href="https://leetcode-cn.com/problems/peak-index-in-a-mountain-array/" target="_blank" rel="noopener"> 山脉数组的峰顶索引</a><br><img src="https://img-blog.csdnimg.cn/20210615214427955.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Solution</span> {</span><br><span class="hljs-keyword">public</span>:<br> <span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">peakIndexInMountainArray</span><span class="hljs-params">(<span class="hljs-built_in">vector</span><<span class="hljs-keyword">int</span>>& arr)</span> </span>{<br> <span class="hljs-keyword">int</span> l = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">int</span> r = arr.size() - <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span>(l<r)<br> {<br> <span class="hljs-keyword">int</span> mid = l + r >> <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">if</span>(arr[mid]>arr[mid+<span class="hljs-number">1</span>]) r = mid;<br> <span class="hljs-keyword">else</span> l = mid+<span class="hljs-number">1</span>;<br> }<br> <span class="hljs-keyword">return</span> l;<br> }<br>};<br></code></pre></td></tr></table></figure><p>这是<a href="https://leetcode-cn.com/problems/peak-index-in-a-mountain-array/solution/gong-shui-san-xie-er-fen-san-fen-cha-zhi-5gfv/" target="_blank" rel="noopener">三叶姐姐</a>的二分经典题型汇总,大家也可以参考一下。<br><img src="https://img-blog.csdnimg.cn/20210615213845614.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>]]></content>
<summary type="html">
<h2 id="数据结构与算法——二分"><a href="#数据结构与算法——二分" class="headerlink" title="数据结构与算法——二分"></a>数据结构与算法——二分</h2><p>最近leetcode每日一题经常出二分的题目,正好对前段时间学过的二分进行一些总结,首先这里要明确的一点是,<strong>二分的本质并不是单调性,而是通过某种条件将整个区间划分成满足条件和不满足条件的两端即可进行二分查找。</strong><br>在二分这个专题,主要有两种类型的划分方式,一种是整数划分,一种是浮点数划分,前一种一般是我们最熟悉的二分查找的题型,也是出题比较灵活考的比较多的一种,后一种主要是为控制实数精度而设置的浮点数二分法(建议用<code>double</code>,<code>float</code>有时候会出现精度丢失)。</p>
</summary>
<category term="数据结构" scheme="https://jackyin.space/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>数据结构与刷题——链表</title>
<link href="https://jackyin.space/2021/06/13/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E5%88%B7%E9%A2%98%E2%80%94%E2%80%94%E9%93%BE%E8%A1%A8/"/>
<id>https://jackyin.space/2021/06/13/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E5%88%B7%E9%A2%98%E2%80%94%E2%80%94%E9%93%BE%E8%A1%A8/</id>
<published>2021-06-13T07:49:00.000Z</published>
<updated>2024-10-09T08:43:45.316Z</updated>
<content type="html"><![CDATA[<h2 id="数据结构与刷题——链表"><a href="#数据结构与刷题——链表" class="headerlink" title="数据结构与刷题——链表"></a>数据结构与刷题——链表</h2><p><img src="https://img-blog.csdnimg.cn/20210607085716429.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><a id="more"></a><p><img src="https://img-blog.csdnimg.cn/20210607090224384.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="单链表代码模板"><a href="#单链表代码模板" class="headerlink" title="单链表代码模板"></a>单链表代码模板</h2><p>代码实现单链表的方法有很多种,但是对于acm刷题来说,我们通常使用的是静态链表的方式,这样代码运行速度更快,防止被卡时间。</p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">int</span> head; <span class="hljs-comment">// 头指针</span><br><span class="hljs-keyword">int</span> e[N]; <span class="hljs-comment">// e[i]表示第i个节点的</span><br><span class="hljs-keyword">int</span> ne[N];<span class="hljs-comment">// 第i个节点的next指针,表示当前节点的直接后继的节点编号</span><br><span class="hljs-keyword">int</span> idx;<span class="hljs-comment">//记录已经存储了多少个节点</span><br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br>head = <span class="hljs-number">-1</span>;<span class="hljs-comment">//用-1表示空节点</span><br>idx = <span class="hljs-number">0</span>;<br>}<br><br><span class="hljs-comment">//在第k个节点后面插入一个新节点,节点的值为x</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">insert</span><span class="hljs-params">(<span class="hljs-keyword">int</span> k,<span class="hljs-keyword">int</span> x)</span></span><br><span class="hljs-function"></span>{<br>e[idx] = x;<br>ne[idx] = ne[k];<br>ne[k] = idx++; <br>}<br><br><span class="hljs-comment">// 删除第k+1个节点,即将将该节点的指针指向他的下一个元素</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">delete_node</span><span class="hljs-params">(<span class="hljs-keyword">int</span> k)</span></span><br><span class="hljs-function"></span>{<br>ne[k] = ne[ne[k]];<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">delete_head</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br>head = ne[head];<br>}<br><br><span class="hljs-comment">//遍历过程</span><br><span class="hljs-keyword">for</span>(<span class="hljs-keyword">int</span> i = head; i!=<span class="hljs-number">-1</span>;i = ne[i])<br>{<br><span class="hljs-built_in">cout</span><<e[i]<<<span class="hljs-built_in">endl</span>;<br>}<br></code></pre></td></tr></table></figure><h2 id="双链表"><a href="#双链表" class="headerlink" title="双链表"></a>双链表</h2><p>在实际的代码编写过程当中,我们也是直接使用静态链表来设置双链表,我们首先设置0号点和1号点为左右边界head,tail。之后我们就只需要在这两者之间插入节点构造双链表。<br><img src="https://img-blog.csdnimg.cn/20210613150500543.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210613151354570.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p><img src="https://img-blog.csdnimg.cn/20210613151720314.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N = <span class="hljs-number">100010</span>;<br><span class="hljs-keyword">int</span> e[N];<br><span class="hljs-keyword">int</span> l[N];<br><span class="hljs-keyword">int</span> r[N];<br><span class="hljs-keyword">int</span> idx;<br><br><span class="hljs-comment">//初始化直接初始化出左右端点的下标,从而避免边界问题</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">init</span><span class="hljs-params">()</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-comment">//初始化的左右别搞反了</span><br> r[<span class="hljs-number">0</span>] = <span class="hljs-number">1</span>;<br> l[<span class="hljs-number">1</span>] = <span class="hljs-number">0</span>;<br> idx =<span class="hljs-number">2</span>;<br>}<br><br><span class="hljs-comment">// 在k节点的右侧插入一个x,其实也相当于左侧插入节点</span><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">add</span><span class="hljs-params">(<span class="hljs-keyword">int</span> k,<span class="hljs-keyword">int</span> x)</span></span><br><span class="hljs-function"></span>{<br> e[idx] = x;<br> <span class="hljs-comment">//操作新节点的左右指针</span><br> r[idx] = r[k];<br> l[idx] = k;<br> <span class="hljs-comment">//再操作原数组的节点</span><br> l[r[k]] = idx;<br> r[k] = idx++;<br>}<br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">remove</span><span class="hljs-params">(<span class="hljs-keyword">int</span> k)</span></span><br><span class="hljs-function"></span>{<br> l[r[k]] = l[k]; <span class="hljs-comment">//右节点的左侧,指向左节点</span><br> r[l[k]] = r[k]; <span class="hljs-comment">//左节点的右侧,指向右节点</span><br>}<br></code></pre></td></tr></table></figure><h2 id="经典例题"><a href="#经典例题" class="headerlink" title="经典例题"></a>经典例题</h2><p>以上两种实现方式都是通过静态链表实现的,主要是来自y总的AcWing经典例题。<br><a href="https://www.acwing.com/problem/content/828/" target="_blank" rel="noopener">AcWing826. 单链表</a><br><a href="https://www.acwing.com/problem/content/829/" target="_blank" rel="noopener">AcWing827. 双链表</a><br>使用指针的实现方式,在leetcode当中用的比较多,这里给大家补充一些leetcode中出现的链表类型的题目,<strong>主要涉及到链表的遍历,双指针算法,和一些小技巧,高阶一点的题目会涉及到树和递归的问题。</strong><br>我做的主要是leetcode上程序员面试经典和剑指offer的例题,不过个人感觉这些题也大部分涵盖了链表题型的大部分考法,而且题解也十分详细。<br><img src="https://img-blog.csdnimg.cn/20210613154244776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210613154325296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>]]></content>
<summary type="html">
<h2 id="数据结构与刷题——链表"><a href="#数据结构与刷题——链表" class="headerlink" title="数据结构与刷题——链表"></a>数据结构与刷题——链表</h2><p><img src="https://img-blog.csdnimg.cn/20210607085716429.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
</summary>
<category term="数据结构" scheme="https://jackyin.space/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>数据结构与算法基础——快速排序与归并排序</title>
<link href="https://jackyin.space/2021/06/06/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80%E2%80%94%E2%80%94%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E4%B8%8E%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/"/>
<id>https://jackyin.space/2021/06/06/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84%E4%B8%8E%E7%AE%97%E6%B3%95%E5%9F%BA%E7%A1%80%E2%80%94%E2%80%94%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F%E4%B8%8E%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/</id>
<published>2021-06-06T12:33:00.000Z</published>
<updated>2024-10-09T08:43:45.316Z</updated>
<content type="html"><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在复习数据结构,顺便整理之前刷题的一些模板和技巧,希望对大家都有帮助,博客会侧重讲解的是OJ代码实现,理论部分偏少但也会写一些自己的理解。<br>在之前大二上数据结构的时候我也有写过一个关于排序的专题介绍<a href="https://blog.csdn.net/Jack___E/article/details/102875285?spm=1001.2014.3001.5502" target="_blank" rel="noopener">数据结构复习——内部排序</a></p><a id="more"></a><h2 id="快速排序"><a href="#快速排序" class="headerlink" title="快速排序"></a>快速排序</h2><p>快速排序主要就是通过选取一个基准点,将一个区间内的数分成大于和小于两个部分,然后对左右区间再进行上述操作,直到子区间的长度为空为止。<strong>快速排序是不稳定的排序,如果需要变成稳定排序通过双关键字排序即可,通过下标控制绝对大小就能得到稳定的排序结果。</strong><br>快速排序分三步走:</p><ul><li>确定分界点</li><li>调整左右区间</li><li>递归处理左右子区间</li></ul><p>在代码实现当中,我们一般选取中间分位点会比较好,这样划分的区间比较平均。在遍历的过程当中每次调整区间的时间是$O(n)$,而区间递归的深度类似二叉树是$O(logn)$<br>在最好情况下,对于递归型的算法,我们利用<strong>主定理公式</strong>来计算快速排序时间复杂度得到$O(nlogn)$<br>$$<br>T(n) = 2 T\left(\frac{n}{2}\right)+\Theta(n)<br>$$<br>当然在最坏情况下,也就是数组是有序或者逆序的情况下,我们如果选择左右端点作为基准点,那么整个算法就相当于冒泡排序,递归的层数也就变成了$n$,则最坏时间复杂度为$O(n^2)$</p><h4 id="代码实现"><a href="#代码实现" class="headerlink" title="代码实现"></a>代码实现</h4><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">quick_sort</span><span class="hljs-params">(<span class="hljs-keyword">int</span>[] q,<span class="hljs-keyword">int</span> l,<span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br><span class="hljs-keyword">if</span>(l>=r) <span class="hljs-keyword">return</span>;<br><span class="hljs-comment">// 1.确定分界点</span><br><span class="hljs-keyword">int</span> i = l - <span class="hljs-number">1</span>, j = r + <span class="hljs-number">1</span>, mid = q[l + r >> <span class="hljs-number">1</span>];<br><span class="hljs-comment">// 2.调整左右区间</span><br><span class="hljs-keyword">while</span>(i<j)<br>{<br><span class="hljs-keyword">do</span> i++; <span class="hljs-keyword">while</span>(q[i]<mid);<br><span class="hljs-keyword">do</span> j--; <span class="hljs-keyword">while</span>(q[j]>mid);<br><span class="hljs-keyword">if</span>(i<j) swap(q[i],q[j]);<br>}<br><span class="hljs-comment">//3.递归处理左右子区间</span><br>quick_sort(l,j);<br>quick_sort(j+<span class="hljs-number">1</span>,r);<br>}<br></code></pre></td></tr></table></figure><h4 id="经典例题"><a href="#经典例题" class="headerlink" title="经典例题"></a>经典例题</h4><p><a href="https://www.luogu.com.cn/problem/P1177" target="_blank" rel="noopener">洛谷P1177 【模板】快速排序</a></p><h2 id="归并排序"><a href="#归并排序" class="headerlink" title="归并排序"></a>归并排序</h2><p>归并排序也是基于分治的思想,每次将区间对半分,逐步递归合并有序化子区间,最终实现所有的左右区间的有序归并,但是跟快速排序不同的是,我们需要开一个辅助数组来存储有序的部分,所以时间复杂度为$O(nlogn)$。归并排序是<strong>稳定</strong>的排序,在元素相等情况下我们总是放入数组下标较小的元素。<br>归并排序分三步走:</p><ul><li>确定分界点</li><li>递归处理子序列</li><li>合并有序序列</li></ul><p><img src="https://img-blog.csdnimg.cn/20210606160649607.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="olor_FFFFFF,t_70)"></p><h4 id="代码实现-1"><a href="#代码实现-1" class="headerlink" title="代码实现"></a>代码实现</h4><figure class="highlight cpp"><table><tr><td class="code"><pre><code class="hljs cpp"><span class="hljs-keyword">const</span> <span class="hljs-keyword">int</span> N=<span class="hljs-number">100010</span>;<br><span class="hljs-keyword">int</span> a[N],tmp[N];<br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">merge_sort</span><span class="hljs-params">(<span class="hljs-keyword">int</span> q[],<span class="hljs-keyword">int</span> l,<span class="hljs-keyword">int</span> r)</span></span><br><span class="hljs-function"></span>{<br> <span class="hljs-keyword">if</span>(l>=r)<br> <span class="hljs-keyword">return</span>;<br> <span class="hljs-comment">//确定分界点</span><br> <span class="hljs-keyword">int</span> mid = l + r >> <span class="hljs-number">1</span>;<br><br> <span class="hljs-comment">//递归处理子序列</span><br> merge_sort(q,l,mid);<br> merge_sort(q,mid+<span class="hljs-number">1</span>,r);<br><br> <span class="hljs-comment">//合并有序序列</span><br> <span class="hljs-keyword">int</span> k = l,i = l,j = mid + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span>(i <= mid && j <= r)<br> {<br> <span class="hljs-keyword">if</span>(q[i]<=q[j])<span class="hljs-comment">// 取等号,保证归并排序的稳定性</span><br> tmp[k++]=q[i++];<br> <span class="hljs-keyword">else</span><br> tmp[k++]=q[j++];<br> }<br><br> <span class="hljs-keyword">while</span>(i<=mid) <br> tmp[k++]=q[i++];<br> <span class="hljs-keyword">while</span>(j<=r) <br> tmp[k++]=q[j++];<br><br> <span class="hljs-keyword">for</span>(i=l;i<=r;i++)<br> q[i]=tmp[i];<br>}<br></code></pre></td></tr></table></figure><h4 id="经典例题-1"><a href="#经典例题-1" class="headerlink" title="经典例题"></a>经典例题</h4><p><a href="https://www.acwing.com/activity/content/problem/content/821/1/" target="_blank" rel="noopener">AcWing 787. 归并排序</a><br><a href="https://www.acwing.com/activity/content/problem/content/822/1/" target="_blank" rel="noopener">AcWing 788. 逆序对的数量</a></p><h2 id="排序相关时间复杂度整理"><a href="#排序相关时间复杂度整理" class="headerlink" title="排序相关时间复杂度整理"></a>排序相关时间复杂度整理</h2><p><img src="https://img-blog.csdnimg.cn/20210606164025501.jpg?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>]]></content>
<summary type="html">
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>最近在复习数据结构,顺便整理之前刷题的一些模板和技巧,希望对大家都有帮助,博客会侧重讲解的是OJ代码实现,理论部分偏少但也会写一些自己的理解。<br>在之前大二上数据结构的时候我也有写过一个关于排序的专题介绍<a href="https://blog.csdn.net/Jack___E/article/details/102875285?spm=1001.2014.3001.5502" target="_blank" rel="noopener">数据结构复习——内部排序</a></p>
</summary>
<category term="数据结构" scheme="https://jackyin.space/tags/%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84/"/>
</entry>
<entry>
<title>EfficientNet论文解读和pytorch代码实现</title>
<link href="https://jackyin.space/2021/05/09/EfficientNet%E8%AE%BA%E6%96%87%E8%A7%A3%E8%AF%BB%E5%92%8Cpytorch%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/"/>
<id>https://jackyin.space/2021/05/09/EfficientNet%E8%AE%BA%E6%96%87%E8%A7%A3%E8%AF%BB%E5%92%8Cpytorch%E4%BB%A3%E7%A0%81%E5%AE%9E%E7%8E%B0/</id>
<published>2021-05-09T09:44:00.000Z</published>
<updated>2024-10-09T08:43:45.228Z</updated>
<content type="html"><![CDATA[<h2 id="EfficientNet论文解读和pytorch代码实现"><a href="#EfficientNet论文解读和pytorch代码实现" class="headerlink" title="EfficientNet论文解读和pytorch代码实现"></a>EfficientNet论文解读和pytorch代码实现</h2><h2 id="传送门"><a href="#传送门" class="headerlink" title="传送门"></a>传送门</h2><blockquote><p>论文地址:<a href="https://arxiv.org/pdf/1905.11946.pdf" target="_blank" rel="noopener">https://arxiv.org/pdf/1905.11946.pdf</a><br>官方github:<a href="https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet" target="_blank" rel="noopener">https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet</a><br>github参考:<a href="https://github.com/qubvel/efficientnet" target="_blank" rel="noopener">https://github.com/qubvel/efficientnet</a><br>github参考:<a href="https://github.com/lukemelas/EfficientNet-PyTorch" target="_blank" rel="noopener">https://github.com/lukemelas/EfficientNet-PyTorch</a></p></blockquote><h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!<br><code>Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet. </code><br>卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文<strong>系统研究(工作量很充足)</strong>了模型缩放,并且证明了<strong>细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果</strong>,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:<strong>使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度</strong>。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。</p><a id="more"></a><p><code>To go even further, we use neural architecture search to design a new baseline network and scale it up to obtain a family of models, called EfficientNets, which achieve much better accuracy and efficiency than previous ConvNets. In particular, our EfficientNet-B7 achieves state-of-the-art 84.3% top-1 accuracy on ImageNet, while being 8.4x smaller and 6.1x faster on inference than the best existing ConvNet. Our EfficientNets also transfer well and achieve state-of-the-art accuracy on CIFAR-100 (91.7%), Flowers (98.8%), and 3 other transfer learning datasets, with an order of magnitude fewer parameters. Source code is at</code><a href="https://github.com/tensorflow/tpu/tree/%20master/models/official/efficientnet." target="_blank" rel="noopener">https://github.com/tensorflow/tpu/tree/<br>master/models/official/efficientnet.</a></p><p>更进一步,我们使用<strong>神经网络结构搜索</strong>设计了一个新的baseline网络架构并且对其进行缩放得到了一组模型,我们把他叫做EfficientNets。EfficientNets相比之前的ConvNets,有着更好的准确率和更高的效率。在ImageNet上达到了最好的水平即top-1准确率84.4%/top-5准确率97.1%,然而却比已有的最好的ConvNet模型小了8.4倍并且推理时间快了6.1倍。我们的EfficientNet迁移学习的效果也好,达到了最好的准确率水平CIFAR-100(91.7%),Flowers(98.8%),和其他3个迁移学习数据集合,<strong>参数少了一个数量级(with an order of magnitude fewer parameters)</strong>。</p><h2 id="论文思想"><a href="#论文思想" class="headerlink" title="论文思想"></a>论文思想</h2><p>在之前的一些工作当中,我们可以看到,有的会通过增加网络的width即增加卷积核的个数(从而增大channels)来提升网络的性能,有的会通过增加网络的深度即使用更多的层结构来提升网络的性能,有的会通过增加输入网络的分辨率来提升网络的性能。而在Efficientnet当中则重新探究了这个问题,并提出了一种组合条件这三者的网络结构,同时量化了这三者的指标。<br><img src="https://img-blog.csdnimg.cn/2021050914370943.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p><code>The intuition is that deeper ConvNet can capture richer and more complex features, and generalize well on new tasks. However, deeper networks are also more difficult to train due to the vanishing gradient problem</code><br>增加网络的深度depth能够得到更加丰富、复杂的特征并且能够在其他新任务中很好的泛化性能。但是,由于逐渐消失的梯度问题,更深层的网络也更难以训练。</p><p><code>wider networks tend to be able to capture more fine-grained features and are easier to train. However, extremely wide but shallow networks tend to have difficulties in capturing higher level features</code><br>with更广的网络往往能够捕获更多细粒度的功能,并且更易于训练。但是,极其宽泛但较浅的网络在捕获更高级别的特征时往往会遇到困难</p><p><code>With higher resolution input images, ConvNets can potentially capture more fine-grained pattern,but the accuracy gain diminishes for very high resolutions</code><br>使用更高分辨率的输入图像,ConvNets可以捕获更细粒度的图案,但对于非常高分辨率的图片,准确度会降低。</p><p>作者在论文中对整个网络的运算过程和复合扩展方法进行了抽象:<br>首先定义了每一层卷积网络为$\mathcal{F}<em>{i}\left(X</em>{i}\right)$,而$X_i$是输入张量,$Y_i$是输出张量,而tensor的形状是$<H_i,W_i,C_i>$<br>整个卷积网络由 k 个卷积层组成,可以表示为$\mathcal{N}=\mathcal{F}<em>{k} \odot \ldots \odot \mathcal{F}</em>{2} \odot \mathcal{F}<em>{1}\left(X</em>{1}\right)=\bigodot_{j=1 \ldots k} \mathcal{F}<em>{j}\left(X</em>{1}\right)$<br>从而得到我们的整个卷积网络:<br>$$<br>\mathcal{N}=\bigodot_{i=1 \ldots s} \mathcal{F}<em>{i}^{L</em>{i}}\left(X_{\left\langle H_{i}, W_{i}, C_{i}\right\rangle}\right)<br>$$</p><p>下标 i(从 1 到 s) 表示的是 stage 的序号,$\mathcal{F}<em>{i}^{L</em>{i}}$表示第 i 个 stage ,它表示卷积层 $\mathcal{F}<em>{i}$重复了${L</em>{i}}$ 次。</p><p>为了探究$d , r , w$这三个因子对最终准确率的影响,则将$d , r , w$加入到公式中,我们可以得到抽象化后的优化问题(在指定资源限制下),其中$s.t.$代表限制条件:<br><img src="https://img-blog.csdnimg.cn/20210509151026858.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><ul><li>$d$用来缩放深度$\widehat{L}_i $。</li><li>$r$用来缩放分辨率即影响$\widehat{H}_i$以及$\widehat{W}_i$。</li><li>$w$就是用来缩放特征矩阵的channels即 $\widehat{C}_i$。</li><li>target_memory为memory限制</li><li>target_flops为FLOPs限制<br><img src="https://img-blog.csdnimg.cn/20210509151622471.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><code>Bigger networks with larger width, depth, or resolution tend to achieve higher accuracy, but the accuracy gain quickly saturate after reaching 80%, demonstrating the limitation of single dimension scaling</code><br>具有较大宽度,深度或分辨率的较大网络往往会实现较高的精度,但是精度增益在达到80%后会迅速饱和,<strong>这表明了单维缩放的局限性</strong>。<h2 id="compound-scaling-method"><a href="#compound-scaling-method" class="headerlink" title="compound scaling method"></a>compound scaling method</h2><code>In this paper, we propose a new compound scaling method, which use a compound coefficient φ to uniformly scales network width, depth, and resolution in a principled way:</code><br>在本文中,我们提出了一种新的复合缩放方法,该方法使用一个统一的复合系数$\phi$对网络的宽度,深度和分辨率进行均匀缩放。<br><img src="https://img-blog.csdnimg.cn/20210509152344159.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>其中$\alpha,\beta,\gamma$是通过一个小格子搜索的方法决定的常量。通常来说,$\phi$是一个用户指定的系数来控制有多少的额外资源能够用于模型的缩放,$\alpha,\beta,\gamma$指明了怎么支配这些额外的资源分别到网络的宽度,深度,和分辨率上。尤其是,一个标准卷积操作的运算量的比例是$d,w^2,r^2$双倍的网络深度将带来双倍的运算量,但是双倍的网络宽度或分辨率将会增加运算为4倍。因为卷积操作通常在ConvNets中占据绝大部分计算量,通过3式来缩放ConvNet大约将增加$(\alpha,\beta^2,\gamma^2)$运算量。</li></ul><p><strong>我们限制$\alpha \cdot \beta^{2} \cdot \gamma^{2} \approx 2$所以对于任意给定的$\phi$值,我们总共的运算量将大约增加$2^{\phi}$</strong></p><h2 id="网络结构"><a href="#网络结构" class="headerlink" title="网络结构"></a>网络结构</h2><p>文中给出了Efficientnet-B0的基准框架,在Efficientnet当中,我们所使用的激活函数是swish激活函数,整个网络分为了9个stage,在第2到第8stage是一直在堆叠MBConv结构,<strong>表格当中MBConv结构后面的数字(1或6)表示的就是MBConv中第一个1x1的卷积层会将输入特征矩阵的channels扩充为n倍。Channels表示通过该Stage后输出特征矩阵的Channels。</strong><br><img src="https://img-blog.csdnimg.cn/20210509153603684.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="MBConv结构"><a href="#MBConv结构" class="headerlink" title="MBConv结构"></a>MBConv结构</h2><p>MBConv的结构相对来说还是十分好理解的,这里是我自己画的一张结构图。<br><img src="https://img-blog.csdnimg.cn/2021050916551423.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"><br>MBConv结构主要由一个1x1的<code>expand conv</code>(升维作用,包含BN和Swish激活函数)提升维度的倍率正是之前网络结构中看到的1和6,一个KxK的<strong>卷积深度可分离</strong>卷积<code>Depthwise Conv</code>(包含BN和Swish)k的具体值可看EfficientNet-B0的网络框架主要有3x3和5x5两种情况,一个SE模块,一个1x1的<code>pointer conv</code>降维作用,包含BN),一个<code>Droupout</code>层构成,一般情况下都添加上了残差边模块<code>Shortcut</code>,从而得到输出特征矩阵。<br>首先关于深度可分离卷积的概念是是我们需要了解的,相当于将卷积的过程拆成运算和合并两个过程,分别对应depthwise和point两个过程,这里给一个参考连接给大家就不在详细描述了<a href="https://blog.csdn.net/tintinetmilou/article/details/81607721" target="_blank" rel="noopener">Depthwise卷积与Pointwise卷积</a><br><img src="https://img-blog.csdnimg.cn/20210509170104747.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>1.由于该模块最初是用tensorflow来实现的与pytorch有一些小差别,所以在使用pytorch实现的时候我们最好将tensorflow当中的samepadding操作重新实现一下</strong><br><strong>2.在batchnormal时候的动量设置也是有一些不一样的,论文当中设置的batch_norm_momentum=0.99,在pytorch当中需要先执行1-momentum再设为参数与tensorflow不同。</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># Batch norm parameters</span><br>momentum= <span class="hljs-number">1</span> - batch_norm_momentum <span class="hljs-comment"># pytorch's difference from tensorflow</span><br>eps= batch_norm_epsilon<br>nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)<br></code></pre></td></tr></table></figure><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Conv2dSamePadding</span><span class="hljs-params">(nn.Conv2d)</span>:</span><br> <span class="hljs-string">"""2D Convolutions like TensorFlow, for a dynamic image size.</span><br><span class="hljs-string"> The padding is operated in forward function by calculating dynamically.</span><br><span class="hljs-string"> """</span><br><br> <span class="hljs-comment"># Tips for 'SAME' mode padding.</span><br> <span class="hljs-comment"># Given the following:</span><br> <span class="hljs-comment"># i: width or height</span><br> <span class="hljs-comment"># s: stride</span><br> <span class="hljs-comment"># k: kernel size</span><br> <span class="hljs-comment"># d: dilation</span><br> <span class="hljs-comment"># p: padding</span><br> <span class="hljs-comment"># Output after Conv2d:</span><br> <span class="hljs-comment"># o = floor((i+p-((k-1)*d+1))/s+1)</span><br> <span class="hljs-comment"># If o equals i, i = floor((i+p-((k-1)*d+1))/s+1),</span><br> <span class="hljs-comment"># => p = (i-1)*s+((k-1)*d+1)-i</span><br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, in_channels, out_channels, kernel_size,padding=<span class="hljs-number">0</span>, stride=<span class="hljs-number">1</span>, dilation=<span class="hljs-number">1</span>,</span></span><br><span class="hljs-function"><span class="hljs-params"> groups=<span class="hljs-number">1</span>, bias=True, padding_mode = <span class="hljs-string">'zeros'</span>)</span>:</span><br> super().__init__(in_channels, out_channels, kernel_size, stride, padding, dilation, groups, bias)<br> self.stride = self.stride <span class="hljs-keyword">if</span> len(self.stride) == <span class="hljs-number">2</span> <span class="hljs-keyword">else</span> [self.stride[<span class="hljs-number">0</span>]] * <span class="hljs-number">2</span><br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, x)</span>:</span><br> ih, iw = x.size()[<span class="hljs-number">-2</span>:] <span class="hljs-comment"># input</span><br> kh, kw = self.weight.size()[<span class="hljs-number">-2</span>:] <span class="hljs-comment"># because kernel size equals to weight size</span><br> sh, sw = self.stride <span class="hljs-comment"># stride</span><br><br> <span class="hljs-comment"># change the output size according to stride</span><br> oh, ow = math.ceil(ih/sh), math.ceil(iw/sw) <span class="hljs-comment"># output</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> kernel effective receptive field size: (kernel_size-1) x dilation + 1</span><br><span class="hljs-string"> """</span><br> pad_h = max((oh - <span class="hljs-number">1</span>) * self.stride[<span class="hljs-number">0</span>] + (kh - <span class="hljs-number">1</span>) * self.dilation[<span class="hljs-number">0</span>] + <span class="hljs-number">1</span> - ih, <span class="hljs-number">0</span>)<br> pad_w = max((ow - <span class="hljs-number">1</span>) * self.stride[<span class="hljs-number">1</span>] + (kw - <span class="hljs-number">1</span>) * self.dilation[<span class="hljs-number">1</span>] + <span class="hljs-number">1</span> - iw, <span class="hljs-number">0</span>)<br><br> <span class="hljs-keyword">if</span> pad_h > <span class="hljs-number">0</span> <span class="hljs-keyword">or</span> pad_w > <span class="hljs-number">0</span>:<br> x = F.pad(x, [pad_w // <span class="hljs-number">2</span>, pad_w - pad_w // <span class="hljs-number">2</span>, pad_h // <span class="hljs-number">2</span>, pad_h - pad_h // <span class="hljs-number">2</span>])<br><br> <span class="hljs-keyword">return</span> F.conv2d(x, self.weight, self.bias, self.stride, self.padding, self.dilation, self.groups)<br></code></pre></td></tr></table></figure><h2 id="MBConvBlock模块结构"><a href="#MBConvBlock模块结构" class="headerlink" title="MBConvBlock模块结构"></a>MBConvBlock模块结构</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">ConvBNActivation</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-string">"""Convolution BN Activation"""</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self,in_channels,out_channels,kernel_size,stride=<span class="hljs-number">1</span>,groups=<span class="hljs-number">1</span>,</span></span><br><span class="hljs-function"><span class="hljs-params"> bias=False,momentum=<span class="hljs-number">0.01</span>,eps=<span class="hljs-number">1e-3</span>,active=False)</span>:</span><br> super().__init__()<br> self.layer=nn.Sequential(<br> Conv2dSamePadding(in_channels,out_channels,kernel_size=kernel_size,<br> stride=stride,groups=groups, bias=bias),<br> nn.BatchNorm2d(num_features=out_channels, momentum=momentum, eps=eps)<br> )<br> self.active=active<br> self.swish = Swish()<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, x)</span>:</span><br> x = self.layer(x)<br> <span class="hljs-keyword">if</span> self.active:<br> x=self.swish(x)<br> <span class="hljs-keyword">return</span> x<br><br><br><br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MBConvBlock</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self,in_channels,out_channels,kernel_size,stride,</span></span><br><span class="hljs-function"><span class="hljs-params"> expand_ratio=<span class="hljs-number">1</span>, momentum=<span class="hljs-number">0.01</span>,eps=<span class="hljs-number">1e-3</span>,</span></span><br><span class="hljs-function"><span class="hljs-params"> drop_connect_ratio=<span class="hljs-number">0.2</span>,training=True,</span></span><br><span class="hljs-function"><span class="hljs-params"> se_ratio=<span class="hljs-number">0.25</span>,skip=True, image_size=None)</span>:</span><br> super().__init__()<br> self.expand_ratio = expand_ratio<br> self.se_ratio = se_ratio<br> self.has_se = (se_ratio <span class="hljs-keyword">is</span> <span class="hljs-keyword">not</span> <span class="hljs-literal">None</span>) <span class="hljs-keyword">and</span> (<span class="hljs-number">0</span> < se_ratio <= <span class="hljs-number">1</span>)<br> self.skip = skip <span class="hljs-keyword">and</span> stride == <span class="hljs-number">1</span> <span class="hljs-keyword">and</span> in_channels == out_channels<br><br> <span class="hljs-comment"># 1x1 convolution channels expansion phase</span><br> expand_channels = in_channels * self.expand_ratio <span class="hljs-comment"># number of output channels</span><br><br> <span class="hljs-keyword">if</span> self.expand_ratio != <span class="hljs-number">1</span>:<br> self.expand_conv = ConvBNActivation(in_channels=in_channels,out_channels=expand_channels,<br> momentum=momentum,eps=eps,<br> kernel_size=<span class="hljs-number">1</span>,bias=<span class="hljs-literal">False</span>,active=<span class="hljs-literal">False</span>)<br><br> <span class="hljs-comment"># Depthwise convolution phase</span><br> self.depthwise_conv = ConvBNActivation(in_channels=expand_channels,out_channels=expand_channels,<br> momentum=momentum,eps=eps,<br> kernel_size=kernel_size,stride=stride,groups=expand_channels,bias=<span class="hljs-literal">False</span>,active=<span class="hljs-literal">False</span>)<br><br> <span class="hljs-comment"># Squeeze and Excitation module</span><br> <span class="hljs-keyword">if</span> self.has_se:<br> sequeeze_channels = max(<span class="hljs-number">1</span>,int(expand_channels*self.se_ratio))<br> self.se = SEModule(expand_channels,sequeeze_channels)<br><br> <span class="hljs-comment"># Pointwise convolution phase</span><br> self.project_conv = ConvBNActivation(expand_channels,out_channels,momentum=momentum,eps=eps,<br> kernel_size=<span class="hljs-number">1</span>,bias=<span class="hljs-literal">False</span>,active=<span class="hljs-literal">True</span>)<br><br> self.drop_connect = DropConnect(drop_connect_ratio,training)<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self,x)</span>:</span><br> input_x = x<br> <span class="hljs-keyword">if</span> self.expand_ratio != <span class="hljs-number">1</span>:<br> x = self.expand_conv(x)<br> x = self.depthwise_conv(x)<br> <span class="hljs-keyword">if</span> self.has_se:<br> x = self.se(x)<br> x = self.project_conv(x)<br> <span class="hljs-keyword">if</span> self.skip:<br> x = self.drop_connect(x)<br> x = x + input_x<br> <span class="hljs-keyword">return</span> x<br></code></pre></td></tr></table></figure><h2 id="SE模块"><a href="#SE模块" class="headerlink" title="SE模块"></a>SE模块</h2><p><strong>SE模块是通道注意力模块,该模块能够关注channel之间的关系,可以让模型自主学习到不同channel特征的重要程度</strong>。由一个全局平均池化,两个全连接层组成。第一个全连接层的节点个数是输入该MBConv特征矩阵channels的1/4 ,且使用Swish激活函数。第二个全连接层的节点个数等于Depthwise Conv层输出的特征矩阵channels,且使用Sigmoid激活函数,这里引用一下别人画好的示意图。<br><img src="https://img-blog.csdnimg.cn/20210509170601162.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/2021050917075028.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">SEModule</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-string">"""Squeeze and Excitation module for channel attention"""</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self,in_channels,squeezed_channels)</span>:</span><br> super().__init__()<br> self.global_avg_pool = nn.AdaptiveAvgPool2d(<span class="hljs-number">1</span>)<br> self.sequential = nn.Sequential(<br> nn.Conv2d(in_channels,squeezed_channels,<span class="hljs-number">1</span>), <span class="hljs-comment"># use 1x1 convolution instead of linear</span><br> Swish(),<br> nn.Conv2d(squeezed_channels,in_channels,<span class="hljs-number">1</span>),<br> nn.Sigmoid()<br> )<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self,x)</span>:</span><br> x= self.global_avg_pool(x)<br> y = self.sequential(x)<br> <span class="hljs-keyword">return</span> x * y<br></code></pre></td></tr></table></figure><h2 id="swish激活函数"><a href="#swish激活函数" class="headerlink" title="swish激活函数"></a>swish激活函数</h2><p>Swish是Google提出的一种新型激活函数,其原始公式为:f(x)=x * sigmod(x),变形Swish-B激活函数的公式则为f(x)=x * sigmod(b * x),其拥有不饱和,光滑,非单调性的特征,Google在论文中的多项测试表明Swish以及Swish-B激活函数的性能即佳,在不同的数据集上都表现出了要优于当前最佳激活函数的性能。<br>$$f(x)=x \cdot \operatorname{sigmoid}(\beta x)$$<br>其中$\beta$是个常数或可训练的参数,Swish 具备无上界有下界、平滑、非单调的特性。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Swish</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-string">""" swish activation function"""</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self,x)</span>:</span><br> <span class="hljs-keyword">return</span> x * torch.sigmoid(x)<br></code></pre></td></tr></table></figure><h2 id="droupout代码实现"><a href="#droupout代码实现" class="headerlink" title="droupout代码实现"></a>droupout代码实现</h2><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">DropConnect</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-string">"""Drop Connection"""</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self,ratio,training)</span>:</span><br> super().__init__()<br> <span class="hljs-keyword">assert</span> <span class="hljs-number">0</span> <= ratio <= <span class="hljs-number">1</span><br> self.keep_prob = <span class="hljs-number">1</span> - ratio<br> self.training=training<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self,x)</span>:</span><br> <span class="hljs-keyword">if</span> <span class="hljs-keyword">not</span> self.training:<br> <span class="hljs-keyword">return</span> x<br> batch_size = x.shape[<span class="hljs-number">0</span>]<br><br> random_tensor = self.keep_prob<br> random_tensor += torch.rand([batch_size,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>],dtype=x.dtype,device=x.device)<br> <span class="hljs-comment"># generate binary_tensor mask according to probability (p for 0, 1-p for 1)</span><br> binary_tensor = torch.floor(random_tensor)<br><br> output = x / self.keep_prob * binary_tensor<br> <span class="hljs-keyword">return</span> output<br></code></pre></td></tr></table></figure><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这里我们只给出了b0结构的代码实现,对于其他结构的实现过程就是在b0的基础上对wdith,depth,resolution都通过倍率因子统一缩放,这个部分在这个博客里面有详细的介绍<a href="https://blog.csdn.net/qq_37541097/article/details/114434046" target="_blank" rel="noopener">EfficientNet(B0-B7)参数设置</a>。<br>在本文中,我们系统地研究了ConvNet的缩放比例,分析了各因素对网络结构的影响,并确定仔细平衡网络的宽度,深度和分辨率,这是目前网络训练最重要但缺乏研究的部分,也是我们无法获得更好的准确性和效率忽略的一个部分。为解决此问题,本文作者提出了一种简单而高效的复合缩放方法,通过一个倍率因子统一缩放各部分比例并添加上通道注意力机制和残差模块,从而改善网络架构得到SOTA的效果,同时对模型各成分因子进行量化十分具有创新性。</p><blockquote><p>参考连接<br><a href="https://blog.csdn.net/qq_37541097/article/details/114434046" target="_blank" rel="noopener">EfficientNet网络详解</a><br><a href="https://blog.csdn.net/weixin_44791964/article/details/106733795" target="_blank" rel="noopener">神经网络学习小记录50——Pytorch EfficientNet模型的复现详解</a><br><a href="https://blog.csdn.net/weixin_42464187/article/details/100939130" target="_blank" rel="noopener">论文翻译</a><br><a href="https://zhuanlan.zhihu.com/p/96773680" target="_blank" rel="noopener">令人拍案叫绝的EfficientNet和EfficientDet</a><br><a href="https://www.cnblogs.com/makefile/p/activation-function.html" target="_blank" rel="noopener">swish激活函数</a><br><a href="https://blog.csdn.net/tintinetmilou/article/details/81607721" target="_blank" rel="noopener">Depthwise卷积与Pointwise卷积</a><br><a href="https://blog.csdn.net/weixin_42907473/article/details/106525668" target="_blank" rel="noopener">CV领域常用的注意力机制模块(SE、CBAM)</a><br><a href="https://blog.csdn.net/fjsd155/article/details/88953153" target="_blank" rel="noopener">Global Average Pooling</a></p></blockquote>]]></content>
<summary type="html">
<h2 id="EfficientNet论文解读和pytorch代码实现"><a href="#EfficientNet论文解读和pytorch代码实现" class="headerlink" title="EfficientNet论文解读和pytorch代码实现"></a>EfficientNet论文解读和pytorch代码实现</h2><h2 id="传送门"><a href="#传送门" class="headerlink" title="传送门"></a>传送门</h2><blockquote>
<p>论文地址:<a href="https://arxiv.org/pdf/1905.11946.pdf" target="_blank" rel="noopener">https://arxiv.org/pdf/1905.11946.pdf</a><br>官方github:<a href="https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet" target="_blank" rel="noopener">https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet</a><br>github参考:<a href="https://github.com/qubvel/efficientnet" target="_blank" rel="noopener">https://github.com/qubvel/efficientnet</a><br>github参考:<a href="https://github.com/lukemelas/EfficientNet-PyTorch" target="_blank" rel="noopener">https://github.com/lukemelas/EfficientNet-PyTorch</a></p>
</blockquote>
<h2 id="摘要"><a href="#摘要" class="headerlink" title="摘要"></a>摘要</h2><p>谷歌的文章总是能让我印象深刻,不管从实验上还是论文的书写上都让人十分的佩服,不得不说这确实是一个非常creative的公司!<br><code>Convolutional Neural Networks (ConvNets) are commonly developed at a fixed resource budget, and then scaled up for better accuracy if more resources are available. In this paper, we systematically study model scaling and identify that carefully balancing network depth, width, and resolution can lead to better performance. Based on this observation, we propose a new scaling method that uniformly scales all dimensions of depth/width/resolution using a simple yet highly effective compound coefficient. We demonstrate the effectiveness of this method on scaling up MobileNets and ResNet. </code><br>卷积神经网络通常是在固定的资源预算下进行计算和发展的,如果有更多可用的资源,模型能通过缩放和扩展获得更好的效果,本文<strong>系统研究(工作量很充足)</strong>了模型缩放,并且证明了<strong>细致地平衡网络的深度,宽度,和分辨率能够得到更好的效果</strong>,基于这个观点,我们提出了一个新的尺度缩放的方法,我们提出了一个新的尺度缩放的方法即:<strong>使用一个简单且高效的复合系数统一地缩放所有网络层的深度/宽度/分辨率的维度</strong>。我们证明了该方法在扩大MobileNets和ResNet方面的有效性。</p>
</summary>
<category term="计算机视觉" scheme="https://jackyin.space/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/"/>
</entry>
<entry>
<title>yolov1-3论文解析</title>
<link href="https://jackyin.space/2021/04/22/yolov1-3%E8%AE%BA%E6%96%87%E8%A7%A3%E6%9E%90/"/>
<id>https://jackyin.space/2021/04/22/yolov1-3%E8%AE%BA%E6%96%87%E8%A7%A3%E6%9E%90/</id>
<published>2021-04-22T02:20:00.000Z</published>
<updated>2024-10-09T08:43:45.288Z</updated>
<content type="html"><![CDATA[<h2 id="yolov1-3论文解析"><a href="#yolov1-3论文解析" class="headerlink" title="yolov1-3论文解析"></a>yolov1-3论文解析</h2><p>最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。</p><a id="more"></a><h2 id="yolov1论文思想"><a href="#yolov1论文思想" class="headerlink" title="yolov1论文思想"></a>yolov1论文思想</h2><p>物体检测主流的算法框架大致分为one-stage与two-stage。two-stage算法代表有R-CNN系列,one-stage算法代表有Yolo系列。按笔者理解,two-stage算法将步骤一与步骤二分开执行,输入图像先经过候选框生成网络(例如faster rcnn中的RPN网络),再经过分类网络;one-stage算法将步骤一与步骤二同时执行,输入图像只经过一个网络,生成的结果中同时包含位置与类别信息。two-stage与one-stage相比,精度高,但是计算量更大,所以运算较慢。</p><blockquote><p>We present YOLO, a new approach to object detection. Prior work on object detection repurposes classifiers to perform detection. <strong>Instead, we frame object detection as a regression problem to spatially separated bounding boxes and associated class probabilities.</strong></p></blockquote><p>yolo相比其他R-CNN等方法最大的一个不同就是他将这个识别和定位的过程转化成了一个空间定位并分类的回归问题,这也是他为什么能够利用网络进行端到端直接同时预测的重要原因。</p><h4 id="yolo的特点"><a href="#yolo的特点" class="headerlink" title="yolo的特点"></a>yolo的特点</h4><p><strong>1.YOLO速度非常快。由于我们的检测是当做一个回归问题,不需要很复杂的流程。在测试的时候我们只需将一个新的图片输入网络来检测物体。</strong><br><code>First, YOLO is extremely fast. Since we frame detection as a regression problem we don’t need a complex pipeline</code><br><strong>2.Yolo会基于整张图片信息进行预测,与基于滑动窗口和候选区域的方法不同,在训练和测试期间YOLO可以看到整个图像,所以它隐式地记录了分类类别的上下文信息及它的全貌</strong><br><code>Second, YOLO reasons globally about the image when making predictions Unlike sliding window and region proposal-based techniques, YOLO sees the entire image during training and test time so it implicitly encodes contextual information about classes as well as their appearance</code><br><strong>3.第三,YOLO能学习到目标的泛化表征。作者尝试了用自然图片数据集进行训练,用艺术画作品进行预测,Yolo的检测效果更佳。</strong><br><code>Third, YOLO learns generalizable representations of objects. Since YOLO is highly generalizable it is less likely to break down when applied to new domains or unexpected inputs</code></p><h4 id="backbone-darknet"><a href="#backbone-darknet" class="headerlink" title="backbone-darknet"></a>backbone-darknet</h4><p><img src="https://img-blog.csdnimg.cn/20210421180804103.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>yolov1这块的话,由于是比较早期的网络所以在设计时没有使用batchnormal,激活函数上采用leaky relu,输入图像大小为448x448,经过许多卷积层与池化层,变为7x7x1024张量,最后经过两层全连接层,输出张量维度为7x7x30。除了最后一层的输出使用了线性激活函数,其他层全部使用Leaky Relu激活函数。<br>$$\phi(x)= \begin{cases}<br>x,& \text{if x>0} \<br>0.1x,& \text{otherwise}<br>\end{cases}$$</p><h4 id="输出维度"><a href="#输出维度" class="headerlink" title="输出维度"></a>输出维度</h4><p>yolov1最精髓的地方应该就是他的输出维度,论文当中有放出这样一张图片,看完以后我们能对yolo算法的特点有更加深刻的理解。在上图的backbone当中我们可以看到的是<br><img src="https://img-blog.csdnimg.cn/20210421182245129.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述">yolo的输出相当于把原图片分割成了$S \times S$个区域,然后对预测目标标定他的boundingbox,boundingbox由中心点的坐标,长宽以及物体的置信分数组成$x,y,w,h,confidence$,图中的$B$表示的是boundingbox的个数。置信分数在yolo当中是这样定义的:$Pr(Object)<em>IOU_{pred}^{truth}$对于$Pr(Object)=1 \text{ if detect object else 0}$,其实<strong>置信分数就是boudingbox与groundtruth之间的IOU</strong>,并对每个小网格对应的$C$个类别都预测出他的<strong>条件概率:</strong>$Pr(Class_i|Object)$。<br>在推理时,我们可以得到当前网格对于每个类别的预测置信分数:$$Pr(Class_i)*IOU_{pred}^{truth} = Pr(Class_i|Object)*Pr(Object)*IOU_{pred}^{truth}$$<br>这样一种严谨的条件概率的方式得到我们的最终概率是十分可行和合适的。<br><code>YOLO imposes strong spatial constraints on bounding box predictions since each grid cell only predicts two boxes and can only have one class. This spatial constraint limits the number of nearby objects that our model can predict. Our model struggles with small objects that appear in groups, such as flocks of birds.</code><br>*</em>不过在原论文当中也有提到的一点是:YOLO给边界框预测强加空间约束,因为每个网格单元只预测两个框和只能有一个类别。这个空间约束限制了我们的模型可以预测的邻近目标的数量。我们的模型难以预测群组中出现的小物体(比如鸟群)。**</p><h4 id="损失函数"><a href="#损失函数" class="headerlink" title="损失函数"></a>损失函数</h4><p>在损失函数方面,由于公式比较长所以就直接截图过来了,损失函数的设计放方面作者也考虑的比较周全,<br><img src="https://img-blog.csdnimg.cn/20210421183909382.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>预测框的中心点$(x,y)$。预测框的宽高$w,h$,其中$\mathbb{1}_{i j}^{\text {obj }}$为控制函数,<strong>在标签中包含物体的那些格点处,该值为 1 ,若格点不含有物体,该值为 0</strong></p><p>在计算损失函数的时候作者最开始使用的是最简单的平方损失函数来计算检测,因为它是计算简单且容易优化的,但是它并不完全符合我们最大化平均精度的目标,原因如下:<br><code>It weights localization error equally with classification error which may not be ideal.</code><br><strong>1.它对定位错误的权重与分类错误的权重相等,这可能不是理想的选择。</strong><br><code>Also, in every image many grid cells do not contain any object. This pushes the “confidence” scores of those cells towards zero, often overpowering the gradient from cells that do contain objects. This can lead to model instability, causing training to diverge early on.</code><br>而且,在每个图像中,许多网格单元不包含任何对象。这就把这些网格的置信度分数推到零,往往超过了包含对象的网格的梯度。<strong>2.这可能导致模型不稳定,导致训练早期发散难以训练。</strong></p><p><strong>解决方案:增加边界框坐标预测的损失,并且减少了不包含对象的框的置信度预测的损失。我们使用两个参数来实现这一点。</strong>$\lambda_{coord}=5,\lambda_{noobj}=0.5$,其实这也是yolo面临的一个样本数目不均衡的典型例子。主要目的是让含有物体的格点,在损失函数中的权重更大,让模型更加注重含有物体的格点所造成的损失。<br><strong>我们的损失函数则只会对那些有真实物体所属的格点进行损失计算,若该格点不包含物体,那么预测数值不对损失函数造成影响。</strong></p><p><strong>这里对$w,h$在损失函数中的处理分别取了根号,原因在于,如果不取根号,损失函数往往更倾向于调整尺寸比较大的预测框</strong>。例如,20个像素点的偏差,对于800x600的预测框几乎没有影响,此时的IOU数值还是很大,但是对于30x40的预测框影响就很大。<strong>取根号是为了尽可能的消除大尺寸框与小尺寸框之间的差异。</strong></p><p>预测框的置信度$C_i$。当该格点不含有物体时,该置信度的标签为0;若含有物体时,该置信度的标签为预测框与真实物体框的IOU数值(。<br>物体类别概率$P_i$,对应的类别位置,该标签数值为1,其余位置为0,与分类网络相同。</p><h2 id="yolov2:Better-Faster-Stronger"><a href="#yolov2:Better-Faster-Stronger" class="headerlink" title="yolov2:Better, Faster, Stronger"></a>yolov2:Better, Faster, Stronger</h2><p>yolov2的论文里面其实更多的是对训练数据集的组合和扩充,然后再添加了很多计算机视觉方面新出的trick,然后达到了性能的提升。同时yolov2方面所使用的trick也是现在计算机视觉常用的方法,我认为这些都是我们需要好好掌握的,同时也是面试和kaggle上都经常使用的一些方法。</p><h4 id="Batch-Normalization"><a href="#Batch-Normalization" class="headerlink" title="Batch Normalization"></a>Batch Normalization</h4><p>BN是由Google于2015年提出,论文是《Batch Normalization_ Accelerating Deep Network Training by Reducing Internal Covariate Shift》,这是一个深度神经网络训练的技巧,主要是让数据的分布变得一致,从而使得训练深层网络模型更加容易和稳定。<br>这里有一些相关的链接可以参考一下</p><blockquote><p><a href="https://www.cnblogs.com/itmorn/p/11241236.html" target="_blank" rel="noopener">https://www.cnblogs.com/itmorn/p/11241236.html</a><br><a href="https://zhuanlan.zhihu.com/p/24810318" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/24810318</a><br><a href="https://zhuanlan.zhihu.com/p/93643523" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/93643523</a><br><a href="https://www.bilibili.com/video/BV1Lx411j7GT?from=search&seid=16898413208243461324" target="_blank" rel="noopener">莫烦python</a><br><a href="https://www.bilibili.com/video/BV1bx411V798?from=search&seid=16898413208243461324" target="_blank" rel="noopener">李宏毅yyds</a></p></blockquote><p>算法具体过程:<br><img src="https://img-blog.csdnimg.cn/20210421213340208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p>这里要提一下这个$\gamma和\beta$是可训练参数,维度等于张量的通道数,主要是为了在反向传播更新网络时使得标准化后的数据分布与原始数据尽可能保持一直,从而很好的抽象和保留整个batch的数据分布。</p><p>Batch Normalization的作用:<br>将这些输入值或卷积网络的张量在batch维度上进行类似标准化的操作,将其放缩到合适的范围,加快模型训练时的收敛速度,使得模型训练过程更加稳定,避免梯度爆炸或者梯度消失,并且起到一定的正则化作用。</p><h4 id="High-Resolution-Classifier"><a href="#High-Resolution-Classifier" class="headerlink" title="High Resolution Classifier"></a>High Resolution Classifier</h4><p>在Yolov1中,网络的backbone部分会在ImageNet数据集上进行预训练,训练时网络输入图像的分辨率为224x224。在v2中,将分类网络在输入图片分辨率为448x448的ImageNet数据集上训练10个epoch,再使用检测数据集(例如coco)进行微调。高分辨率预训练使mAP提高了大约4%。</p><h4 id="Convolutional-With-Anchor-Boxes"><a href="#Convolutional-With-Anchor-Boxes" class="headerlink" title="Convolutional With Anchor Boxes"></a>Convolutional With Anchor Boxes</h4><p><code>predicts these offsets at every location in a feature map</code><br><code>Predicting offsets instead of coordinates simplifies the problem and makes it easier for the network to learn.</code><br>第一篇解读v1时提到,每个格点预测两个矩形框,在计算loss时,只让与ground truth最接近的框产生loss数值,而另一个框不做修正。这样规定之后,作者发现两个框在物体的大小、长宽比、类别上逐渐有了分工。在v2中,神经网络不对预测矩形框的宽高的绝对值进行预测,而是预测与Anchor框的偏差(offset),每个格点指定n个Anchor框。在训练时,最接近ground truth的框产生loss,其余框不产生loss。在引入Anchor Box操作后,mAP由69.5下降至69.2,原因在于,每个格点预测的物体变多之后,召回率大幅上升,准确率有所下降,总体mAP略有下降。</p><h4 id="Dimension-Clusters"><a href="#Dimension-Clusters" class="headerlink" title="Dimension Clusters"></a>Dimension Clusters</h4><p><code>Instead of choosing priors by hand, we run k-means clustering on the training set bounding boxes to automatically find good priors.</code><br>这里算是作者数据处理上的一个创新点,这里作者提到之前的工作当中先Anchor Box都是认为设定的比例和大小,而这里作者时采用无监督的方式将训练数据集中的矩形框全部拿出来,用kmeans聚类得到先验框的宽和高。<strong>使用(1-IOU)数值作为两个矩形框的的距离函数</strong>,这个处理也十分的聪明。<br>$$<br>d(\text { box }, \text { centroid })=1-\operatorname{IOU}(\text { box }, \text { centroid })<br>$$<br><img src="https://img-blog.csdnimg.cn/20210421214909877.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="Direct-location-prediction"><a href="#Direct-location-prediction" class="headerlink" title="Direct location prediction"></a>Direct location prediction</h4><p>Yolo中的位置预测方法是预测的左上角的格点坐标预测偏移量。网络预测输出要素图中每个单元格的5个边界框。网络为每个边界框预测$t_x,t_y,t_h,t_w和t_o$这5个坐标。如果单元格从图像的左上角偏移了$(c_x,c_y)$并且先验边界框的宽度和高度为$p_w,p_h$,则预测对应于:<br>$$<br>\begin{array}{l}<br>x=\left(t_{x} * w_{a}\right)-x_{a} \<br>y=\left(t_{y} * h_{a}\right)-y_{a}<br>\end{array}<br>$$<br><img src="https://img-blog.csdnimg.cn/20210421222704145.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>$$<br>\begin{aligned}<br>b_{x} &=\sigma\left(t_{x}\right)+c_{x} \<br>b_{y} &=\sigma\left(t_{y}\right)+c_{y} \<br>b_{w} &=p_{w} e^{t_{w}} \<br>b_{h} &=p_{h} e^{t_{h}} \<br>\operatorname{Pr}(\text { object }) * I O U(b, \text { object }) &=\sigma\left(t_{o}\right)<br>\end{aligned}<br>$$<br><strong>由于我们约束位置预测,参数化更容易学习,使得网络更稳定</strong>使用维度集群以及直接预测边界框中心位置使YOLO比具有anchor box的版本提高了近5%的mAP。</p><h4 id="Fine-Grained-Features"><a href="#Fine-Grained-Features" class="headerlink" title="Fine-Grained Features"></a>Fine-Grained Features</h4><p>在26<em>26的特征图,经过卷积层等,变为13</em>13的特征图后,作者认为损失了很多细粒度的特征,导致小尺寸物体的识别效果不佳,所以在此加入了<strong>passthrough层</strong>。<br><strong>传递层通过将相邻特征堆叠到不同的通道而不是堆叠到空间位置,将较高分辨率特征与低分辨率特征相连</strong>,类似于ResNet中的标识映射。这将26×26×512特征映射转换为13×13×2048特征映射,其可以与原始特征连接。<br><img src="https://img-blog.csdnimg.cn/20210421223145797.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="Multi-Scale-Training"><a href="#Multi-Scale-Training" class="headerlink" title="Multi-Scale Training"></a>Multi-Scale Training</h4><p>我觉得这个也是yolo为什么性能提升的一个很重要的点,解决了yolo1当中对于多个小物体检测的问题。<br>原始的YOLO使用448×448的输入分辨率。添加anchor box后,我们将分辨率更改为416×416。然而,<strong>由于我们的模型只使用卷积层和池化层,相比yolo1删除了全连接层,它可以在运行中调整大小(应该是使用了1x1卷积不过我没看代码)</strong>。我们希望YOLOv2能够在不同大小的图像上运行,因此我们将其训练到模型中。<br><code>instead of fixing the input image size we change the network every few iterations. Every 10 batches our network randomly chooses a new image dimension size.</code><br>不固定输入图像的大小,我们每隔几次迭代就更改网络。在每10个batch之后,我们的网络就会随机resize成{320, 352, …, 608}中的一种。不同的输入,最后产生的格点数不同,比如输入图片是320<em>320,那么输出格点是10</em>10,如果每个格点的先验框个数设置为5,那么总共输出500个预测结果;如果输入图片大小是608<em>608,输出格点就是19</em>19,共1805个预测结果。<br><code>YOLO’s convolutional layers downsample the image by a factor of 32 so by using an input image of 416 we get an output feature map of 13 × 13.</code><br><code> We do this because we want an odd number of locations in our feature map so there is a single center cell.</code><br>关于416×416也是有说法的,主要是下采样是32的倍数,而且最后的输出是13x13是奇数然后会有一个中心点更加易于目标检测。<br><strong>这种训练方法迫使网络学习在各种输入维度上很好地预测。这意味着相同的网络可以预测不同分辨率的检测。</strong><br><strong>关于yolov2最大的一个提升还有一个原因就是WordTree组合数据集的训练方法,不过这里我就不再赘述,可以看参考资料有详细介绍,这里主要讲网络和思路</strong></p><h2 id="yolov3论文改进"><a href="#yolov3论文改进" class="headerlink" title="yolov3论文改进"></a>yolov3论文改进</h2><p>yolov3的论文改进就有很多方面了,而且yolov3-spp的网络效果很不错,和yolov4,yolov5的效果差别也不是特别大,这也是为什么yolov3网络被广泛应用的原因之一。</p><h2 id="backbone:Darknet-53"><a href="#backbone:Darknet-53" class="headerlink" title="backbone:Darknet-53"></a>backbone:Darknet-53</h2><p>相比yolov1的网络,在网络上有了很大的改进,借鉴了Resnet、Densenet、FPN的做法结合了当前网络中十分有效的<br><img src="https://img-blog.csdnimg.cn/2021042122500522.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>1.Yolov3中,全卷积,对于输入图片尺寸没有特别限制,可通过调节卷积步长控制输出特征图的尺寸。<br>2.Yolov3借鉴了<strong>金字塔特征图</strong>思想,<strong>小尺寸特征图用于检测大尺寸物体,而大尺寸特征图检测小尺寸物体</strong>。特征图的输出维度为$N \times N \times (3 \times (4+1+80))$, NxN为输出特征图格点数,一共3个Anchor框,每个框有4维预测框数值 $t_x,t_y,t_w,t_h$ ,1维预测框置信度,80维物体类别数。所以第一层特征图的输出维度为8x8x255<br>3.<strong>Yolov3总共输出3个特征图</strong>,第一个特征图下采样32倍,第二个特征图下采样16倍,第三个下采样8倍。每个特征图进行一次3X3、步长为2的卷积,然后保存该卷积layer,再进行一次1X1的卷积和一次3X3的卷积,并把这个结果加上layer作为最后的结果。<strong>三个特征层进行5次卷积处理,处理完后一部分用于输出该特征层对应的预测结果,一部分用于进行反卷积UmSampling2d后与其它特征层进行结合。</strong><br>4.concat和resiual加操作,借鉴DenseNet将特征图按照通道维度直接进行拼接,借鉴Resnet添加残差边,缓解了在深度神经网络中增加深度带来的梯度消失问题。<br>5.上采样层(upsample):作用是将小尺寸特征图通过插值等方法,生成大尺寸图像。例如使用最近邻插值算法,将8<em>8的图像变换为16</em>16。上采样层不改变特征图的通道数。</p><p>对于yolo3的模型来说,<strong>网络最后输出的内容就是三个特征层每个网格点对应的预测框及其种类,即三个特征层分别对应着图片被分为不同size的网格后,每个网格点上三个先验框对应的位置、置信度及其种类</strong>。然后对输出进行解码,解码过程也就是yolov2上面的Direct location prediction方法一样,每个网格点加上它对应的x_offset和y_offset,加完后的结果就是预测框的中心,然后再利用 先验框和h、w结合 计算出预测框的长和宽。这样就能得到整个预测框的位置了。</p><h4 id="CIoU-loss"><a href="#CIoU-loss" class="headerlink" title="CIoU loss"></a>CIoU loss</h4><p>在yolov3当中我们使用的不再是传统的IoU loss,而是一个非常优秀的loss设计,整个发展过程也可以多了解了解IoU->GIoU->DIoU->CIoU,完整的考虑了<strong>重合面积,中心点距离,长宽比</strong>。<br>$$CIoU=IoU-(\frac{\rho(b,b^{gt}}{c^2}+\alpha v))$$<br>$$v=\frac{4}{\pi^2}(\arctan\frac{w^{gt}}{h^{gt}}-\arctan\frac{w}{h})^2$$<br>$$\alpha = \frac{v}{(1-IoU)+v}$$<br>其中, $b$ , $b^{gt}$ 分别代表了预测框和真实框的中心点,且 $\rho$代表的是计算两个中心点间的欧式距离。 [公式] 代表的是能够同时包含预测框和真实框的最小闭包区域的对角线距离。<br><strong>在介绍CIoU之前,我们可以先介绍一下DIoU loss</strong><br><img src="https://img-blog.csdnimg.cn/20210422092054189.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>其实这两项就是我们的DIoU<br>$$DIoU = IoU-(\frac{\rho(b,b^{gt}}{c^2})$$<br>则我们可以提出DIoU loss函数<br>$$<br>L_{\text {DIoU }}=1-D I o U \<br>0 \leq L_{\text {DloU}} \leq 2<br>$$<br><strong>DloU损失能够直接最小化两个boxes之间的距离,因此收敛速度更快。</strong><br><strong>而我们的CIoU则比DIoU考虑更多的东西,也就是长宽比即最后一项。而$v$用来度量长宽比的相似性,$\alpha$是权重</strong><br>在损失函数这块,有知乎大佬写了一下,其实就是对yolov1上的损失函数进行了一下变形。<br><img src="https://img-blog.csdnimg.cn/20210421233832256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="Spatial-Pyramid-Pooling"><a href="#Spatial-Pyramid-Pooling" class="headerlink" title="Spatial Pyramid Pooling"></a>Spatial Pyramid Pooling</h4><p>在yolov3的spp版本中使用了spp模块,实现了不同尺度的特征融合,spp模块最初来源于何凯明大神的论文SPP-net,主要用来解决输入图像尺寸不统一的问题,而目前的图像预处理操作中,resize,crop等都会造成一定程度的图像失真,因此影响了最终的精度。SPP模块,使用固定分块的池化操作,可以对不同尺寸的输入实现相同大小的输出,因此能够避免这一问题。<br>同时SPP模块中不同大小特征的融合,有利于待检测图像中目标大小差异较大的情况,也相当于增大了多重感受野吧,尤其是对于yolov3一般针对的复杂多目标图像。<br>在yolov3的darknet53输出到第一个特征图计算之前我们插入了一个spp模块。<br><img src="https://img-blog.csdnimg.cn/20210422093246740.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>SPP的这几个模块stride=1,而且会对外补padding,所以最后的输出特征图大小是一致的,然后在深度方向上进行concatenate,使得深度增大了4倍。<br><img src="https://img-blog.csdnimg.cn/20210422093556765.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210422093809296.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>可以看出随着输入网络尺度的增大,spp的效果会更好。多个spp的效果增大也就是spp3和spp1的效果是差不多的,为了保证推理速度就只使用了一个spp</p><h4 id="Mosaic图像增强"><a href="#Mosaic图像增强" class="headerlink" title="Mosaic图像增强"></a>Mosaic图像增强</h4><p>在yolov3-spp包括yolov4当中我们都使用了mosaic数据增强,有点类似cutmix。cutmix是合并两张图片进行预测,mosaic数据增强利用了四张图片,对四张图片进行拼接,每一张图片都有其对应的框框,将四张图片拼接之后就获得一张新的图片,同时也获得这张图片对应的框框,然后我们将这样一张新的图片传入到神经网络当中去学习,相当于一下子传入四张图片进行学习了。论文中说这极大丰富了检测物体的背景,且在标准化BN计算的时候一下子会计算四张图片的数据。<br><img src="https://img-blog.csdnimg.cn/20210422084715366.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>作用:增加数据的多样性,增加目标个数,BN能一次性归一化多张图片组合的分布,会更加接近原数据的分布。</p><h4 id="focal-loss"><a href="#focal-loss" class="headerlink" title="focal loss"></a>focal loss</h4><p>最后我们提一下focal loss,虽然在原论文当中作者说focal loss效果反而下降了,但是我认为还是有必要了解一下何凯明的这篇bestpaper。作者提出focal loss的出发点也是希望one-stage detector可以达到two-stage detector的准确率,同时不影响原有的速度。作者认为造成这种情况的原因是<strong>样本的类别不均衡导致的</strong>。<br>对于二分类的CrossEntropy,我们有如下表示方法:<br>$$<br>\mathrm{CE}(p, y)=\left{\begin{array}{ll}<br>-\log (p) & \text { if } y=1 \<br>-\log (1-p) & \text { otherwise. }<br>\end{array}\right.<br>$$<br>我们定义$p_t$:<br>$$<br>p_{\mathrm{t}}=\left{\begin{array}{ll}<br>p & \text { if } y=1 \<br>1-p & \text { otherwise }<br>\end{array}\right.<br>$$<br>则重写交叉熵<br>$$<br>\operatorname{CE}(n, y)=C E(p_t)=-\log (p_t)<br>$$<br>接着我们提出改进思路就是在计算CE时添加了一个平衡因子,是一个可训练的参数<br>$$<br>\mathrm{CE}\left(p_{\mathrm{t}}\right)=-\alpha_{\mathrm{t}} \log \left(p_{\mathrm{t}}\right)<br>$$<br>其中<br>$$<br>\alpha_{\mathrm{t}}=\left{\begin{array}{ll}<br>\alpha_{\mathrm{t}} & \text { if } y=1 \<br>1-\alpha_{\mathrm{t}} & \text { otherwise }<br>\end{array}\right.<br>$$<br>相当于给正负样本加上权重,负样本出现的频次多,那么就降低负样本的权重,正样本数量少,就相对提高正样本的权重。因此可以通过设定a的值来控制正负样本对总的loss的共享权重。a取比较小的值来降低负样本(多的那类样本)的权重。<br><strong>但是何凯明认为这个还是不能完全解决样本不平衡的问题,虽然这个平衡因子可以控制正负样本的权重,但是没法控制容易分类和难分类样本的权重</strong></p><blockquote><p>As our experiments will show, the large class imbalance encountered during training of dense detectors overwhelms the cross entropy loss. Easily classified negatives comprise the majority of the loss and dominate the gradient. <strong>While<br>α balances the importance of positive/negative examples, it does not differentiate between easy/hard examples. Instead, we propose to reshape the loss function to down-weight easy examples and thus focus training on hard negatives</strong></p></blockquote><p>于是提出了一种新的损失函数Focal Loss<br>$$<br>\mathrm{FL}\left(p_{\mathrm{t}}\right)=-\left(1-p_{\mathrm{t}}\right)^{\gamma} \log \left(p_{\mathrm{t}}\right)<br>$$<br><img src="https://img-blog.csdnimg.cn/20210422095440770.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="focal-loss的两个重要性质"><a href="#focal-loss的两个重要性质" class="headerlink" title="focal loss的两个重要性质"></a>focal loss的两个重要性质</h4><p>1、当一个样本被分错的时候,pt是很小的,那么调制因子(1-Pt)接近1,损失不被影响;当Pt→1,因子(1-Pt)接近0,那么分的比较好的(well-classified)样本的权值就被调低了。因此调制系数就趋于1,也就是说相比原来的loss是没有什么大的改变的。当pt趋于1的时候(此时分类正确而且是易分类样本),调制系数趋于0,也就是对于总的loss的贡献很小。</p><p>2、当γ=0的时候,focal loss就是传统的交叉熵损失,<strong>当γ增加的时候,调制系数也会增加</strong>。 专注参数γ平滑地调节了易分样本调低权值的比例。γ增大能增强调制因子的影响,实验发现γ取2最好。直觉上来说,调制因子减少了易分样本的损失贡献,拓宽了样例接收到低损失的范围。当γ一定的时候,比如等于2,一样easy example(pt=0.9)的loss要比标准的交叉熵loss小100+倍,当pt=0.968时,要小1000+倍,但是对于hard example(pt < 0.5),loss最多小了4倍。这样的话hard example的权重相对就提升了很多。这样就增加了那些误分类的重要性</p><p><strong>focal loss的两个性质算是核心,其实就是用一个合适的函数去度量难分类和易分类样本对总的损失的贡献。</strong></p><p>然后作者又提出了最终的focal loss形式<br>$$<br>\mathrm{FL}\left(p_{\mathrm{t}}\right)=-\alpha_{\mathrm{t}}\left(1-p_{\mathrm{t}}\right)^{\gamma} \log \left(p_{\mathrm{t}}\right)<br>$$<br>$$<br>F L(p)\left{\begin{array}{ll}<br>-\alpha(1-p)^{\gamma} \log (p) & \text { if } y=1 \<br>-(1-\alpha) p^{\gamma} \log (1-p) & \text { otherwise }<br>\end{array}\right.<br>$$<br><strong>这样既能调整正负样本的权重,又能控制难易分类样本的权重</strong><br>在实验中a的选择范围也很广,一般而言当γ增加的时候,a需要减小一点(实验中γ=2,a=0.25的效果最好)</p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>yolo和yolov2的严谨数学推理和网络相结合的做法令人十分惊艳,而yolov3相比之前的两个版本不管是在数据处理还是网络性质上都有很大的改变,也使用了很多流行的tirck来实现目标检测,基本上结合了很多cv领域的精髓,博客中有很多部分由于时间关系没有太细讲,其实每个部分都可以去原论文中找到很多可以学习的地方,之后有时候会补上yolov4和yolov5的讲解,后面的网络添加了注意力机制模块效果应该是更加work的。</p><h2 id="参考文献"><a href="#参考文献" class="headerlink" title="参考文献"></a>参考文献</h2><blockquote><p><a href="https://www.bilibili.com/video/BV1yi4y1g7ro?p=4" target="_blank" rel="noopener">yolov3-spp</a><br><a href="https://zhuanlan.zhihu.com/p/49981234" target="_blank" rel="noopener">focal loss</a><br><a href="https://www.cnblogs.com/itmorn/p/11241236.html" target="_blank" rel="noopener">batch normal</a><br><a href="https://zhuanlan.zhihu.com/p/94799295" target="_blank" rel="noopener">IoU系列</a><br><a href="https://blog.csdn.net/shuiyixin/article/details/82533849" target="_blank" rel="noopener">yolov1论文翻译</a><br><a href="https://zhuanlan.zhihu.com/p/76802514" target="_blank" rel="noopener">yolo1-3三部曲</a><br><a href="https://www.bilibili.com/video/BV1Hp4y1y788?from=search&seid=2780145238002767073" target="_blank" rel="noopener">Bubbliiiing的教程里</a><br>还有很多但是没找到之后会补上的</p></blockquote>]]></content>
<summary type="html">
<h2 id="yolov1-3论文解析"><a href="#yolov1-3论文解析" class="headerlink" title="yolov1-3论文解析"></a>yolov1-3论文解析</h2><p>最近在看经典目标检测算法yolo的思想,为了更好的了解yolo系列的相关文章,我从最初版本的论文思想开始看的,之后有时间会把yolov4和yolov5再认真看看,目前来说yolov3的spp版本是使用得最为广泛的一种,整体上来说yolo的设计思想还是很有创造性的数学也比较严谨。</p>
</summary>
<category term="计算机视觉" scheme="https://jackyin.space/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/"/>
</entry>
<entry>
<title>MTCNN论文和pytorch代码解读</title>
<link href="https://jackyin.space/2021/04/12/MTCNN%E8%AE%BA%E6%96%87%E5%92%8Cpytorch%E4%BB%A3%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
<id>https://jackyin.space/2021/04/12/MTCNN%E8%AE%BA%E6%96%87%E5%92%8Cpytorch%E4%BB%A3%E7%A0%81%E8%A7%A3%E8%AF%BB/</id>
<published>2021-04-12T00:39:00.000Z</published>
<updated>2024-10-09T08:43:45.243Z</updated>
<content type="html"><![CDATA[<h1 id="MTCNN人脸检测和pytorch代码实现解读"><a href="#MTCNN人脸检测和pytorch代码实现解读" class="headerlink" title="MTCNN人脸检测和pytorch代码实现解读"></a>MTCNN人脸检测和pytorch代码实现解读</h1><h2 id="传送门"><a href="#传送门" class="headerlink" title="传送门"></a>传送门</h2><blockquote><p>论文地址:<a href="https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf" target="_blank" rel="noopener">https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf</a><br>我的论文笔记:<a href="https://khany.top/file/paper/mtcnn.pdf" target="_blank" rel="noopener">https://khany.top/file/paper/mtcnn.pdf</a><br>本文csdn链接:<a href="https://blog.csdn.net/Jack___E/article/details/115601474" target="_blank" rel="noopener">https://blog.csdn.net/Jack___E/article/details/115601474</a><br>github参考:<a href="https://github.com/Sierkinhane/mtcnn-pytorch" target="_blank" rel="noopener">https://github.com/Sierkinhane/mtcnn-pytorch</a><br>github参考:<a href="https://github.com/GitHberChen/MTCNN_Pytorch" target="_blank" rel="noopener">https://github.com/GitHberChen/MTCNN_Pytorch</a></p></blockquote><h2 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h2><a id="more"></a><blockquote><p>abstract—Face detection and alignment in unconstrained environment are challenging due to various poses, illuminations and occlusions. Recent studies show that deep learning approaches can achieve impressive performance on these two tasks. In this paper, we propose a deep cascaded multi-task framework which exploits the inherent correlation between detection and alignment<br>to boost up their performance. In particular, our framework leverages a cascaded architecture with three stages of carefully designed deep convolutional networks to predict face and landmark location in a coarse-to-fine manner. In addition, we propose a new online hard sample mining strategy that further improves the performance in practice. Our method achieves superior accuracy over the state-of-the-art techniques on the challenging FDDB and WIDER FACE benchmarks for face detection, and AFLW benchmark for face alignment, while keeps real time performance. </p></blockquote><p>在无约束条件的环境下进行人脸检测和校准人脸检测和校准是非常具挑战性的,因为你要考虑复杂的姿势,光照因素以及面部遮挡问题的影响,最近的研究表明,使用深度学习的方法能够在这两项任务上有不错的效果。本文作者探索和研究了人脸检测和校准之间的内在联系,提出了一个深层级的多任务框架有效提升了网络的性能。我们的深层级联合架构包含三个阶段的卷积神经网络从粗略到细致逐步实现人脸检测和面部特征点标记。此外,我们还提出了一种新的线上进行困难样本的预测的策略可以在实际使用过程中有效提升网络的性能。我们的方法目前在FDDB和WIDER FACE人脸检测任务和AFLW面部对准任务上超越了最佳模型方法的性能标准,同时该模型具有不错的实时检测效果。</p><blockquote><p>my English is not so well,if there are some mistakes in translations, please contact me in blog comments.</p></blockquote><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>人脸检测和脸部校准任务对于很多面部任务和应用来说都是十分重要的,比如说人脸识别,人脸表情分析。不过在现实生活当中,面部特征时常会因为一些遮挡物,强烈的光照对比,复杂的姿势发生很大的变化,这使得这些任务变得具有很大挑战性。Viola和Jones 提出的级联人脸检测器利用<a href="https://www.cnblogs.com/zyly/p/9410563.html" target="_blank" rel="noopener">Haar特征和AdaBoost训练级联分类器</a>,实现了具有实时效率的良好性能。 然而,也有相当一部分的工作表明,这种分类器可能在现实生活的应用中效果显着降低,即使具有更高级的特征和分类器。,之后人们又提出了<a href="https://blog.csdn.net/weixin_41798111/article/details/79989794" target="_blank" rel="noopener">DPM</a>的方式,这些年来基于CNN的一些解决方案也层出不穷,基于CNN的方案主要是为了识别出面部的一些特征来实现。<strong>作者认为这种方式其实是需要花费更多的时间来对面部进行校准来训练同时还忽略了面部的重要特征点位置和边框问题。</strong><br>对于人脸校准的话目前也有大致的两个方向:一个是基于回归的方法还有一个就是基于模板拟合的方式进行,主要是对特征识别起到一个辅助的工作。<br>作者认为关于人脸识别和面部对准这两个任务其实是有内在关联的,而目前大多数的方法则忽略了这一点,所以本文所提出的方案就是将这两者都结合起来,构建出一个三阶段的模型实现一个端到端的人脸检测并校准面部特征点的过程。<br><strong>第一阶段:通过浅层CNN快速生成候选窗口。</strong><br><strong>第二阶段:通过more complex的CNN拒绝大量非面部窗口来细化窗口。</strong><br><strong>第三阶段:使用more powerful的CNN再次细化结果并输出五个面部标志位置。</strong></p><h2 id="方法"><a href="#方法" class="headerlink" title="方法"></a>方法</h2><h4 id="Image-pyramid图像金字塔"><a href="#Image-pyramid图像金字塔" class="headerlink" title="Image pyramid图像金字塔"></a>Image pyramid图像金字塔</h4><p>在进入stage-1之前,作者先构建了一组多尺度的图像金字塔缩放,这一块要理解起来还是有一点费力的,这里我们需要了解的是在训练网络的过程当中,作者都是把WIDER FACE的图片随机剪切成12x12size的大小进行单尺度训练的,不能满足任意大小的人脸检测,所以为了检测到不同尺寸的人脸,所以在推理时需要先生成图像金字塔生成一组不同scale的图像输入P-net进行来实现人脸的检测,同时也这是为什么P-net要使用FCN的原因。<br><img src="https://img-blog.csdnimg.cn/2021041118210630.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">calculateScales</span><span class="hljs-params">(img)</span>:</span><br>min_face_size = <span class="hljs-number">20</span> <span class="hljs-comment"># 最小的人脸尺寸</span><br>width, height = img.size<br>min_length = min(height, width)<br>min_detection_size = <span class="hljs-number">12</span><br>factor = <span class="hljs-number">0.707</span> <span class="hljs-comment"># sqrt(0.5)</span><br>scales = []<br>m = min_detection_size / min_face_size<br>min_length *= m<br>factor_count = <span class="hljs-number">0</span><br><span class="hljs-comment"># 图像尺寸缩小到不大于min_detection_size</span><br><span class="hljs-keyword">while</span> min_length > min_detection_size:<br> scales.append(m * factor ** factor_count)<br> min_length *= factor<br> factor_count += <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>$$\text{length} =\text{length} \times (\frac{\text{min detection size}}{\text{min face size}} )\times factor^{count} $$<br>$\text{here we define in our code:} factor=\frac{1}{\sqrt{2}}, \text{min face size}=20,\text{min detection size}=12$</p><p><strong>min_face_size和factor对推理过程会产生什么样的影响?</strong><br><code>min_face_size</code>越大,<code>factor</code>越小,图像最短边就越快缩放到接近<code>min_detect_size</code>,从而生成图像金字塔的耗时就越短,同时各尺度的跨度也更大。因此,加大<code>min_face_size</code>、减小<code>factor</code>能加速图像金字塔的生成,但同时也更易造成漏检。</p><h4 id="Stage-1-P-Net-Proposal-Network"><a href="#Stage-1-P-Net-Proposal-Network" class="headerlink" title="Stage 1 P-Net(Proposal Network)"></a>Stage 1 P-Net(Proposal Network)</h4><p>这是一个全卷积网络,也就是说这个网络可以兼容任意大小的输入,他的整个网络其实十分简单,该层的作用主要是为了获得人脸检测的大量候选框,这一层我们最大的作用就是要尽可能的提升recall,然后在获得许多候选框后再使用非极大抑制的方法合并高度重叠的候选区域。<br><img src="https://img-blog.csdnimg.cn/20210411181845202.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>P-Net的示意图当中我们也可以看到的是作者输入12x12大小最终的output为一个1x1x2的face label classification概率和一个1x1x4的boudingbox(左上角和右下角的坐标)以及一个1x1x10的landmark(双眼,鼻子,左右嘴角的坐标)<br><strong>注意虽然这里作者举例是12x12的图片大小,但是他的意思并不是说这个P-Net的图片输入大小必须是12,我们可以知道这个网络是FCN,这就意味着不同输入的大小都是可以输入其中的,最后我们所得到的featuremap$[m \times n \times (2+4+10) ]$每一个小像素点映射到输入图像所在的12x12区域是否包含人脸的分类结果,候选框左上和右下基准点以及landmark,然后根据图像金字塔的我们再根据scale和resize的逆过程将其映射到原图像上</strong></p><p><strong>P-net structure</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">P_Net</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span><br> super(P_Net, self).__init__()<br> self.pre_layer = nn.Sequential(<br> <span class="hljs-comment"># 12x12x3</span><br> nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">10</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv1</span><br> nn.PReLU(), <span class="hljs-comment"># PReLU1</span><br> <span class="hljs-comment"># 10x10x10</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool1</span><br> <span class="hljs-comment"># 5x5x10</span><br> nn.Conv2d(<span class="hljs-number">10</span>, <span class="hljs-number">16</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv2</span><br> <span class="hljs-comment"># 3x3x16</span><br> nn.PReLU(), <span class="hljs-comment"># PReLU2</span><br> nn.Conv2d(<span class="hljs-number">16</span>, <span class="hljs-number">32</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv3</span><br> <span class="hljs-comment"># 1x1x32</span><br> nn.PReLU() <span class="hljs-comment"># PReLU3</span><br> )<br> <span class="hljs-comment"># detection</span><br> self.conv4_1 = nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">1</span>, kernel_size=<span class="hljs-number">1</span>, stride=<span class="hljs-number">1</span>)<br> <span class="hljs-comment"># bounding box regresion</span><br> self.conv4_2 = nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">4</span>, kernel_size=<span class="hljs-number">1</span>, stride=<span class="hljs-number">1</span>)<br> <span class="hljs-comment"># landmark localization</span><br> self.conv4_3 = nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">10</span>, kernel_size=<span class="hljs-number">1</span>, stride=<span class="hljs-number">1</span>)<br> <span class="hljs-comment"># weight initiation with xavier</span><br> self.apply(weights_init)<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, x)</span>:</span><br> x = self.pre_layer(x)<br> det = torch.sigmoid(self.conv4_1(x))<br> box = self.conv4_2(x)<br> landmark = self.conv4_3(x)<br> <span class="hljs-comment"># det:[,2,1,1], box:[,4,1,1], landmark:[,10,1,1]</span><br> <span class="hljs-keyword">return</span> det, box, landmark<br></code></pre></td></tr></table></figure><p>这里要提一下<code>nn.PReLU()</code>,有点类似Leaky ReLU,可以看下面的博客深入了解一下<br><img src="https://img-blog.csdnimg.cn/20210411201535463.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><blockquote><p><a href="https://blog.csdn.net/qq_23304241/article/details/80300149" target="_blank" rel="noopener">https://blog.csdn.net/qq_23304241/article/details/80300149</a><br><a href="https://blog.csdn.net/shuzfan/article/details/51345832" target="_blank" rel="noopener">https://blog.csdn.net/shuzfan/article/details/51345832</a></p></blockquote><p>然后在P-Net之后我们通过<strong>非极大抑制</strong>的方法和将<strong>所有的boudingbox都修正为框的最长边为边长的正方形框</strong>,<strong>主要是避免后面的Rnet在resize的时候因为尺寸原因出现信息的损失。</strong><br><img src="https://img-blog.csdnimg.cn/20210411201152243.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="Non-Maximum-Suppression非极大抑制"><a href="#Non-Maximum-Suppression非极大抑制" class="headerlink" title="Non-Maximum Suppression非极大抑制"></a>Non-Maximum Suppression非极大抑制</h4><p>其实关于非极大抑制这个trick最初是在目标检测任务当中提出的来的,其思想是搜素局部最大值,抑制极大值,主要用在目标检测当中,最传统的非极大抑制所采用的评价指标就是<strong>交并比IoU</strong>(intersection-over-union)即两个groud truth和bounding box的交集部分除以它们的并集.<br><img src="https://img-blog.csdnimg.cn/20210412002012403.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>$$IoU = \frac{area(C) \cap area(G)}{area(C) \cup area(G)}$$</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">IoU</span><span class="hljs-params">(box, boxes)</span>:</span><br> <span class="hljs-string">"""Compute IoU between detect box and gt boxes</span><br><span class="hljs-string"></span><br><span class="hljs-string"> Parameters:</span><br><span class="hljs-string"> ----------</span><br><span class="hljs-string"> box: numpy array , shape (5, ): x1, y1, x2, y2, score</span><br><span class="hljs-string"> input box</span><br><span class="hljs-string"> boxes: numpy array, shape (n, 4): x1, y1, x2, y2</span><br><span class="hljs-string"> input ground truth boxes</span><br><span class="hljs-string"></span><br><span class="hljs-string"> Returns:</span><br><span class="hljs-string"> -------</span><br><span class="hljs-string"> ovr: numpy.array, shape (n, )</span><br><span class="hljs-string"> IoU</span><br><span class="hljs-string"> """</span><br> box_area = (box[<span class="hljs-number">2</span>] - box[<span class="hljs-number">0</span>] + <span class="hljs-number">1</span>) * (box[<span class="hljs-number">3</span>] - box[<span class="hljs-number">1</span>] + <span class="hljs-number">1</span>)<br> area = (boxes[:, <span class="hljs-number">2</span>] - boxes[:, <span class="hljs-number">0</span>] + <span class="hljs-number">1</span>) * (boxes[:, <span class="hljs-number">3</span>] - boxes[:, <span class="hljs-number">1</span>] + <span class="hljs-number">1</span>)<br> xx1 = np.maximum(box[<span class="hljs-number">0</span>], boxes[:, <span class="hljs-number">0</span>])<br> xx2 = np.minimum(box[<span class="hljs-number">2</span>], boxes[:, <span class="hljs-number">2</span>])<br> yy1 = np.maximum(box[<span class="hljs-number">1</span>], boxes[:, <span class="hljs-number">1</span>])<br> yy2 = np.minimum(box[<span class="hljs-number">3</span>], boxes[:, <span class="hljs-number">3</span>])<br><br> <span class="hljs-comment"># compute the width and height of the bounding box</span><br> w = np.maximum(<span class="hljs-number">0</span>, xx2 - xx1 + <span class="hljs-number">1</span>)<br> h = np.maximum(<span class="hljs-number">0</span>, yy2 - yy1 + <span class="hljs-number">1</span>)<br><br> inter = w * h<br> ovr = np.true_divide(inter,(box_area + area - inter))<br> <span class="hljs-comment">#ovr = inter / (box_area + area - inter)</span><br> <span class="hljs-keyword">return</span> ovr<br></code></pre></td></tr></table></figure><p><strong>使用非极大抑制的前提是,我们已经得到了一组候选框和对应label的置信分数,以及groud truth的信息,通过设定阈值来删除重合度较高的候选框。</strong><br>算法流程如下:</p><ul><li>根据置信度得分进行排序</li><li>选择置信度最高的比边界框添加到最终输出列表中,将其从边界框列表中删除</li><li>计算所有边界框的面积</li><li>计算置信度最高的边界框与其它候选框的IoU。</li><li>删除IoU大于阈值的边界框</li><li>重复上述过程,直至边界框列表为空。</li></ul><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">nms</span><span class="hljs-params">(dets,threshod,mode=<span class="hljs-string">"Union"</span>)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> greedily select boxes with high confidence</span><br><span class="hljs-string"> keep boxes overlap <= thresh</span><br><span class="hljs-string"> rule out overlap > thresh</span><br><span class="hljs-string"> :param dets: [[x1, y1, x2, y2 score]]</span><br><span class="hljs-string"> :param threshod: retain overlap <= thresh</span><br><span class="hljs-string"> :return: indexes to keep</span><br><span class="hljs-string"> """</span><br> x1 = dets[:,<span class="hljs-number">0</span>]<br> y1 = dets[;,<span class="hljs-number">1</span>]<br> x2 = dets[:,<span class="hljs-number">2</span>]<br> y2 = dets[:,<span class="hljs-number">3</span>]<br><br> scores = dets[:,<span class="hljs-number">4</span>]<br><br> areas = (x2 - x1 + <span class="hljs-number">1</span>) * (y2 - y1 + <span class="hljs-number">1</span>)<br> order = areas.argsort()[::<span class="hljs-number">-1</span>] <span class="hljs-comment"># reverse</span><br><br> keep=[]<br><br> <span class="hljs-keyword">while</span> order.size()><span class="hljs-number">0</span>:<br> i = order[<span class="hljs-number">0</span>]<br> keep.append(i)<br> <span class="hljs-comment"># A & B left top position </span><br> xx1 = np.maximun(x1[i],x1[order[<span class="hljs-number">1</span>,:]])<br> yy1 = np.maximun(y1[i],y1[order[<span class="hljs-number">1</span>,:]])<br> <span class="hljs-comment"># A & B right down position</span><br> xx2 = np.minimum(x2[i],x2[order[<span class="hljs-number">1</span>,:]])<br> yy2 = np.minimum(y2[i], y2[order[<span class="hljs-number">1</span>:]])<br><br> w = np.maximum(<span class="hljs-number">0.0</span>, xx2 - xx1 + <span class="hljs-number">1</span>)<br> h = np.maximum(<span class="hljs-number">0.0</span>, yy2 - yy1 + <span class="hljs-number">1</span>)<br><br> inter = w * h<br><br> <span class="hljs-comment"># cacaulate the IOU between box which have largest score with other boxes</span><br> <span class="hljs-keyword">if</span> mode == <span class="hljs-string">"Union"</span>:<br> <span class="hljs-comment"># area[i]: the area of largest score</span><br> ovr = inter / (areas[i] + areas[order[<span class="hljs-number">1</span>:]] - inter)<br> <span class="hljs-keyword">elif</span> mode == <span class="hljs-string">"Minimum"</span>:<br> ovr = inter / np.minimum(areas[i], areas[order[<span class="hljs-number">1</span>:]])<br> <span class="hljs-comment"># delete the IoU that higher than threshod </span><br> inds = np.where(ovr <= threshod)[<span class="hljs-number">0</span>]<br> order = order[inds + <span class="hljs-number">1</span>] <span class="hljs-comment"># +1: eliminates the first element in order</span><br> <br> <span class="hljs-keyword">return</span> keep<br></code></pre></td></tr></table></figure><h4 id="边框修正"><a href="#边框修正" class="headerlink" title="边框修正"></a>边框修正</h4><p>以最大边作为边长将矩形修正为正方形,同时包含的信息也更多,以免在后面resize输入下一个网络时减少信息的损失。</p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">convert_to_square</span><span class="hljs-params">(bboxes)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> Convert bounding boxes to a square form.</span><br><span class="hljs-string"> """</span><br> <span class="hljs-comment"># 将矩形对称扩大为正方形</span><br> square_bboxes = np.zeros_like(bboxes)<br> x1, y1, x2, y2 = [bboxes[:, i] <span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(<span class="hljs-number">4</span>)]<br> h = y2 - y1 + <span class="hljs-number">1.0</span><br> w = x2 - x1 + <span class="hljs-number">1.0</span><br> max_side = np.maximum(h, w)<br> square_bboxes[:, <span class="hljs-number">0</span>] = x1 + w * <span class="hljs-number">0.5</span> - max_side * <span class="hljs-number">0.5</span><br> square_bboxes[:, <span class="hljs-number">1</span>] = y1 + h * <span class="hljs-number">0.5</span> - max_side * <span class="hljs-number">0.5</span><br> square_bboxes[:, <span class="hljs-number">2</span>] = square_bboxes[:, <span class="hljs-number">0</span>] + max_side - <span class="hljs-number">1.0</span><br> square_bboxes[:, <span class="hljs-number">3</span>] = square_bboxes[:, <span class="hljs-number">1</span>] + max_side - <span class="hljs-number">1.0</span><br> <span class="hljs-keyword">return</span> square_bboxes<br></code></pre></td></tr></table></figure><h4 id="Stage-2-R-Net-Refine-Network"><a href="#Stage-2-R-Net-Refine-Network" class="headerlink" title="Stage 2 R-Net(Refine Network)"></a>Stage 2 R-Net(Refine Network)</h4><p>R-net的输入是固定的,必须是24x24,所以对于P-net产生的大量boundingbox我们需要先进行resize然后再输入R-Net,<strong>在论文中我们了解到该网络层的作用主要是对大量的boundingbox进行有效过滤,获得更加精细的候选框。</strong><br><img src="https://img-blog.csdnimg.cn/20210411203248732.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p><strong>R-net structure</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">R_Net</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span><br> super(R_Net, self).__init__()<br> self.pre_layer = nn.Sequential(<br> <span class="hljs-comment"># 24x24x3</span><br> nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">28</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv1</span><br> nn.PReLU(), <span class="hljs-comment"># prelu1</span><br> <span class="hljs-comment"># 22x22x28</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool1</span><br> <span class="hljs-comment"># 10x10x28</span><br> nn.Conv2d(<span class="hljs-number">28</span>, <span class="hljs-number">48</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv2</span><br> nn.PReLU(), <span class="hljs-comment"># prelu2</span><br> <span class="hljs-comment"># 8x8x48</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool2</span><br> <span class="hljs-comment"># 3x3x48</span><br> nn.Conv2d(<span class="hljs-number">48</span>, <span class="hljs-number">64</span>, kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv3</span><br> <span class="hljs-comment"># 2x2x64</span><br> nn.PReLU() <span class="hljs-comment"># prelu3</span><br> )<br> <span class="hljs-comment"># 2x2x64</span><br> self.conv4 = nn.Linear(<span class="hljs-number">64</span> * <span class="hljs-number">2</span> * <span class="hljs-number">2</span>, <span class="hljs-number">128</span>) <span class="hljs-comment"># conv4</span><br> <span class="hljs-comment"># 128</span><br> self.prelu4 = nn.PReLU() <span class="hljs-comment"># prelu4</span><br> <span class="hljs-comment"># detection</span><br> self.conv5_1 = nn.Linear(<span class="hljs-number">128</span>, <span class="hljs-number">1</span>)<br> <span class="hljs-comment"># bounding box regression</span><br> self.conv5_2 = nn.Linear(<span class="hljs-number">128</span>, <span class="hljs-number">4</span>)<br> <span class="hljs-comment"># lanbmark localization</span><br> self.conv5_3 = nn.Linear(<span class="hljs-number">128</span>, <span class="hljs-number">10</span>)<br> <span class="hljs-comment"># weight initiation weih xavier</span><br> self.apply(weights_init)<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, x)</span>:</span><br> x = self.pre_layer(x)<br> x = x.view(x.size(<span class="hljs-number">0</span>), <span class="hljs-number">-1</span>)<br> x = self.conv4(x)<br> x = self.prelu4(x)<br> det = torch.sigmoid(self.conv5_1(x))<br> box = self.conv5_2(x)<br> landmark = self.conv5_3(x)<br> <span class="hljs-keyword">return</span> det, box, landmark<br></code></pre></td></tr></table></figure><p>然后在P-Net之后我们通过<strong>非极大抑制</strong>的方法和将<strong>所有的boudingbox都修正为框的最长边为边长的正方形框</strong>,<strong>也是避免后面的Onet在resize的时候出现因为尺寸原因出现信息的损失。</strong><br><img src="https://img-blog.csdnimg.cn/20210411202906999.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h4 id="Stage-3-O-Net-Output-作者未指出命名-Network"><a href="#Stage-3-O-Net-Output-作者未指出命名-Network" class="headerlink" title="Stage 3 O-Net(Output?[作者未指出命名] Network)"></a>Stage 3 O-Net(Output?[作者未指出命名] Network)</h4><p><img src="https://img-blog.csdnimg.cn/20210411203345893.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>Onet与Rnet工作流程类似。只不过输入的尺寸变成了48x48,对于R-net当中的框再次进行处理,得到的网络结构的输出则是最终的label classfication,boundingbox,landmark。<br><strong>O-net structure</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">O_Net</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self)</span>:</span><br> super(O_Net, self).__init__()<br> self.pre_layer = nn.Sequential(<br> nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">32</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv1</span><br> nn.PReLU(), <span class="hljs-comment"># prelu1</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool1</span><br> nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">64</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv2</span><br> nn.PReLU(), <span class="hljs-comment"># prelu2</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool2</span><br> nn.Conv2d(<span class="hljs-number">64</span>, <span class="hljs-number">64</span>, kernel_size=<span class="hljs-number">3</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv3</span><br> nn.PReLU(), <span class="hljs-comment"># prelu3</span><br> nn.MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>), <span class="hljs-comment"># pool3</span><br> nn.Conv2d(<span class="hljs-number">64</span>, <span class="hljs-number">128</span>, kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">1</span>), <span class="hljs-comment"># conv4</span><br> nn.PReLU() <span class="hljs-comment"># prelu4</span><br> )<br> self.conv5 = nn.Linear(<span class="hljs-number">128</span> * <span class="hljs-number">2</span> * <span class="hljs-number">2</span>, <span class="hljs-number">256</span>) <span class="hljs-comment"># conv5</span><br> self.prelu5 = nn.PReLU() <span class="hljs-comment"># prelu5</span><br> <span class="hljs-comment"># detection</span><br> self.conv6_1 = nn.Linear(<span class="hljs-number">256</span>, <span class="hljs-number">1</span>)<br> <span class="hljs-comment"># bounding box regression</span><br> self.conv6_2 = nn.Linear(<span class="hljs-number">256</span>, <span class="hljs-number">4</span>)<br> <span class="hljs-comment"># lanbmark localization</span><br> self.conv6_3 = nn.Linear(<span class="hljs-number">256</span>, <span class="hljs-number">10</span>)<br> <span class="hljs-comment"># weight initiation weih xavier</span><br> self.apply(weights_init)<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, x)</span>:</span><br> x = self.pre_layer(x)<br> x = x.view(x.size(<span class="hljs-number">0</span>), <span class="hljs-number">-1</span>)<br> x = self.conv5(x)<br> x = self.prelu5(x)<br> <span class="hljs-comment"># detection</span><br> det = torch.sigmoid(self.conv6_1(x))<br> box = self.conv6_2(x)<br> landmark = self.conv6_3(x)<br> <span class="hljs-keyword">return</span> det, box, landmark<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210411204039760.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>注意,其实在之前的两个网络当中我们也预测了landmark的位置,只是在论文的图示当中没有展示而已,从作者描述的网络结构图的输出当中是详细指出了landmarkposition的卷积块的</strong></p><h2 id="损失函数"><a href="#损失函数" class="headerlink" title="损失函数"></a>损失函数</h2><p>在研究完网络结构以后我们可以来看看这个mtcnn模型的一个损失函数,作者将损失函数分为了3个部分:Face classification、Bounding box regression、Facial landmark localization。</p><h4 id="Face-classification"><a href="#Face-classification" class="headerlink" title="Face classification"></a>Face classification</h4><p>对于第一部分的损失就是用的是最常用的交叉熵损失函数,就本质上来说人脸识别还是一个二分类问题,这里就使用Cross Entropy是最简单也是最合适的选择。<br>$$L_i^{det} = -(y_i^{det} \log(p_i) + (1-y_i^det)(1-\log(p_i)))$$<br>$p_i$是预测为face的可能性,$y_i^{det}$指的是真实标签也就是groud truth label </p><h4 id="Bounding-box-regression"><a href="#Bounding-box-regression" class="headerlink" title="Bounding box regression"></a>Bounding box regression</h4><p>对于目标边界框的损失来说,对于每一个候选框我们都需要对与他最接近的真实目标边界框进行比较,the bounding box$(left, top, height, and width)$<br>$$L_i^{box} = ||y_j^{box}-y_i^{box} ||_2^2$$</p><h4 id="Facial-landmark-localization"><a href="#Facial-landmark-localization" class="headerlink" title="Facial landmark localization"></a>Facial landmark localization</h4><p>而对于boundingbox和landmark来说整个框的调整过程其实可以看作是一个连续的变化过程,固使用的是欧氏距离回归损失计算方法。比较的是各个点的坐标与真实面部关键点坐标的差异。<br>$$L_i^{landmark} = ||y_j^{landmark}-y_i^{landmark} ||_2^2$$</p><h4 id="total-loss"><a href="#total-loss" class="headerlink" title="total loss"></a>total loss</h4><p>最终我们得到的损失函数是有上述这三部分加权而成<br>$$\min{\sum_{i=1}^{N}{\sum_{j \in {det,box,landmark}} \alpha_j \beta_i^j L_i^j}}$$<br>其中$\alpha_j$表示权重,$\beta_i^j$表示第i个样本的类型,也可以说是第i个样本在任务j中是否需要贡献loss,如果不存在人脸即label为0时则无需计算loss。<br>对于不同的网络我们所设置的权重是不一样的<br><strong>in P-net & R-net(在这两层我们更注重classification的识别)</strong><br>$$alpha_{det}=1, alpha_{box}=0.5,alpha_{landmark}=0.5$$<br><strong>in O-net(在这层我们提高了对于注意关键点的预测精度)</strong><br>$$alpha_{det}=1, alpha_{box}=0.5,alpha_{landmark}=1 $$<br>文中也有提到,采用的是随机梯度下降优化器进行的训练。</p><h2 id="OHEM(Online-Hard-Example-Mining)"><a href="#OHEM(Online-Hard-Example-Mining)" class="headerlink" title="OHEM(Online Hard Example Mining)"></a>OHEM(Online Hard Example Mining)</h2><p>作者对于困难样本的在线预测所做的trick也比较简单,就是挑选损失最大的前70%作为困难样本,在反向传播时仅使用这70%困难样本产生的损失,这样就剔除了很容易预测的easy sample对训练结果的影响,不过在我参考的这两个版本的代码里面似乎没有这么做。</p><h2 id="实验"><a href="#实验" class="headerlink" title="实验"></a>实验</h2><p>实验这块的话也比较充分,验证了每个修改部分对于实验结果的有效性验证,主要是讲述了实验过程对于训练数据集的处理和划分,验证了在线硬样本挖掘的有效性,联合检测和校准的有效性,分别评估了face detection和lanmark的贡献,面部检测评估,面部校准评估以及与SOTA的对比,这一块的话就没有细看了,分享的网站也有详细解释,论文原文也写了。<br><img src="https://img-blog.csdnimg.cn/20210411214338982.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210411214409844.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>这篇论文是中科院深圳先进技术研究院的乔宇老师团队所作,2016ECCV,看完这篇文章的话给我的感觉的话就是idea和实现过程包括实验都是很充分的,创新点这块的话主要是挖掘了人脸特征关键点和目标检测的一些联系,结合了人脸检测和对准两个任务来进行人脸检测,相比其他经典的目标检测网络如yolov3,R-CNN系列,在网络和损失函数上的创新确实略有不足,但是也让我收到了一些启发,看似几个简单的model和常见的loss联合,在对数据进行有效处理的基础上也是可以实现达到十分不错的效果的,不过这个方案的话在训练的时候其实是很花费时间的,毕竟需要对于不同scale的图片都进行输入训练,然后就是这种输入输出的结构其实还是存在一些局限性的,对于图像检测框和关键点的映射我个人觉得也比较繁杂或者说浪费了一些时间,毕竟是一篇2016年的论文之后应该会有更好的实现方式。</p><h2 id="参考资料"><a href="#参考资料" class="headerlink" title="参考资料"></a>参考资料</h2><blockquote><p>参考链接: <a href="https://zhuanlan.zhihu.com/p/337690783" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/337690783</a><br>参考链接:<a href="https://zhuanlan.zhihu.com/p/60199964?utm_source=qq&utm_medium=social&utm_oi=1221207556791963648" target="_blank" rel="noopener">https://zhuanlan.zhihu.com/p/60199964?utm_source=qq&utm_medium=social&utm_oi=1221207556791963648</a><br>参考链接: <a href="https://blog.csdn.net/weixin_44791964/article/details/103530206" target="_blank" rel="noopener">https://blog.csdn.net/weixin_44791964/article/details/103530206</a><br>参考链接:<a href="https://blog.csdn.net/qq_34714751/article/details/85536669" target="_blank" rel="noopener">https://blog.csdn.net/qq_34714751/article/details/85536669</a><br>参考链接:<a href="https://www.bilibili.com/video/BV1fJ411C7AJ?from=search&seid=2691296178499711503" target="_blank" rel="noopener">https://www.bilibili.com/video/BV1fJ411C7AJ?from=search&seid=2691296178499711503</a></p></blockquote>]]></content>
<summary type="html">
<h1 id="MTCNN人脸检测和pytorch代码实现解读"><a href="#MTCNN人脸检测和pytorch代码实现解读" class="headerlink" title="MTCNN人脸检测和pytorch代码实现解读"></a>MTCNN人脸检测和pytorch代码实现解读</h1><h2 id="传送门"><a href="#传送门" class="headerlink" title="传送门"></a>传送门</h2><blockquote>
<p>论文地址:<a href="https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf" target="_blank" rel="noopener">https://arxiv.org/ftp/arxiv/papers/1604/1604.02878.pdf</a><br>我的论文笔记:<a href="https://khany.top/file/paper/mtcnn.pdf" target="_blank" rel="noopener">https://khany.top/file/paper/mtcnn.pdf</a><br>本文csdn链接:<a href="https://blog.csdn.net/Jack___E/article/details/115601474" target="_blank" rel="noopener">https://blog.csdn.net/Jack___E/article/details/115601474</a><br>github参考:<a href="https://github.com/Sierkinhane/mtcnn-pytorch" target="_blank" rel="noopener">https://github.com/Sierkinhane/mtcnn-pytorch</a><br>github参考:<a href="https://github.com/GitHberChen/MTCNN_Pytorch" target="_blank" rel="noopener">https://github.com/GitHberChen/MTCNN_Pytorch</a></p>
</blockquote>
<h2 id="abstract"><a href="#abstract" class="headerlink" title="abstract"></a>abstract</h2>
</summary>
<category term="计算机视觉" scheme="https://jackyin.space/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/"/>
</entry>
<entry>
<title>docker镜像操作</title>
<link href="https://jackyin.space/2021/02/25/docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/"/>
<id>https://jackyin.space/2021/02/25/docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C/</id>
<published>2021-02-24T16:03:00.000Z</published>
<updated>2024-10-09T08:43:45.257Z</updated>
<content type="html"><![CDATA[<h2 id="commit镜像"><a href="#commit镜像" class="headerlink" title="commit镜像"></a>commit镜像</h2><p><img src="https://img-blog.csdnimg.cn/20210224162521454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="数据卷操作实战:mysql同步"><a href="#数据卷操作实战:mysql同步" class="headerlink" title="数据卷操作实战:mysql同步"></a>数据卷操作实战:mysql同步</h2><p><strong>mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置</strong></p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker pull mysql:<span class="hljs-number">5.7</span><br></code></pre></td></tr></table></figure><p>docker运行,docker run的常用参数这里我们再次回顾一下</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell"><span class="hljs-literal">-d</span> 后台运行<br><span class="hljs-literal">-p</span> 端口映射<br><span class="hljs-literal">-v</span> 卷挂载<br><span class="hljs-literal">-e</span> 环境配置<br>-<span class="hljs-literal">-name</span> 环境名字<br></code></pre></td></tr></table></figure><a id="more"></a><p>通过docker hub我们找到了官方的命令:<code>docker run --name some-mysql -e MYSQL_ROOT_PASSWORD=my-secret-pw -d mysql:tag</code>,在修改一下得到我们最终的输入命令</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker run <span class="hljs-literal">-d</span> <span class="hljs-literal">-p</span> <span class="hljs-number">3310</span>:<span class="hljs-number">3306</span> <span class="hljs-literal">-v</span> /home/mysql/conf:/etc/mysql/conf.d <span class="hljs-literal">-v</span> /home/mysql/<span class="hljs-keyword">data</span>:/var/lib/mysql <span class="hljs-literal">-e</span> MYSQL_ROOT_PASSWORD=<span class="hljs-number">12345678</span> -<span class="hljs-literal">-name</span> mysql01 mysql:<span class="hljs-number">5.7</span><br></code></pre></td></tr></table></figure><figure class="highlight sql"><table><tr><td class="code"><pre><code class="hljs sql"><span class="hljs-keyword">CREATE</span> <span class="hljs-keyword">DATABASE</span> test_db;<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224233439169.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p><img src="https://img-blog.csdnimg.cn/20210224171410273.png" alt="在这里插入图片描述"><br>删除这个镜像后数据则依旧保存下来了<br><img src="https://img-blog.csdnimg.cn/20210224171612792.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="具名-匿名挂载"><a href="#具名-匿名挂载" class="headerlink" title="具名/匿名挂载"></a>具名/匿名挂载</h2><p><img src="https://img-blog.csdnimg.cn/20210224172157943.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224172227379.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224172304119.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>大多数情况下,为了方便,我们会使用具名挂载<br><img src="https://img-blog.csdnimg.cn/20210224172628376.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h2><p>Dockerfile就是用来构建docker镜像的文件,命令脚本,通过这个脚本可以生成镜像。<br>构建步骤</p><ol><li><p>编写一个Dockerfile</p></li><li><p>docker build 构建成为一个镜像</p></li><li><p>docker run 运行镜像</p></li><li><p>docker push 发布镜像(DockerHub,阿里云镜像)</p><p>这里我们可以先看看Docker Hub官方是怎么做的<br><img src="https://img-blog.csdnimg.cn/20210224203344446.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224203405252.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>官方镜像是比较基础的,有很多命令和功能都省去了,所以我们通常需要在基础的镜像上来构建我们自己的镜像<br><img src="https://img-blog.csdnimg.cn/20210224203837702.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p></li></ol><h2 id="Dockerfile命令"><a href="#Dockerfile命令" class="headerlink" title="Dockerfile命令"></a>Dockerfile命令</h2><table><thead><tr><th>常用命令</th><th>用法</th></tr></thead><tbody><tr><td>FROM</td><td>基础镜像,一切从这开始构建</td></tr><tr><td>MAINTAINER</td><td>镜像是谁写的,姓名+邮箱</td></tr><tr><td>RUN</td><td>镜像构建的时候需要运行的命令</td></tr><tr><td>ADD</td><td>添加内容,如tomcat压缩包</td></tr><tr><td>WORKDIR</td><td>镜像的工作目录</td></tr><tr><td>VOLUME</td><td>挂载的目录</td></tr><tr><td>CMD</td><td>指定这个容器启动时要运行的命令,只有最后一个会生效,可被替代</td></tr><tr><td>ENTRYPOINT</td><td>指定这个容器启动时要运行的命令,可以追加命令</td></tr><tr><td>ONBUILD</td><td>当构建一个被继承DockerFile这个时候就会执行ONBUILD命令</td></tr><tr><td>COPY</td><td>类似ADD将文件拷贝到镜像当中</td></tr><tr><td>ENV</td><td>构建的时候设置环境变量</td></tr></tbody></table><p><img src="https://img-blog.csdnimg.cn/20210224204049431.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="创建一个自己的centos"><a href="#创建一个自己的centos" class="headerlink" title="创建一个自己的centos"></a>创建一个自己的centos</h2><p>Dockerfile中99%的镜像都来自于这个scratch镜像,然后配置需要的软件和配置来进行构建。</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">mkdir dockerfile<br>cd dockerfile<br>vim mydockerfile<span class="hljs-literal">-centos</span><br></code></pre></td></tr></table></figure><p>编写mydockerfile-centos</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">FROM centos<br>MAINTAINER khan<khany@foxmail.com><br>ENV MYPATH /usr/local<br>WORKDIR <span class="hljs-variable">$MYPATH</span><br>RUN yum <span class="hljs-literal">-y</span> install vim<br>RUN yum <span class="hljs-literal">-y</span> install net<span class="hljs-literal">-tools</span><br>EXPOSE <span class="hljs-number">80</span><br>CMD echo <span class="hljs-variable">$MYPATH</span><br>CMD echo <span class="hljs-string">"---end---"</span><br>CMD /bin/bash<br></code></pre></td></tr></table></figure><p>然后我们进入<code>docker build</code>,<strong>注意后面一定要有一个.号</strong></p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker build <span class="hljs-operator">-f</span> mydockerfile<span class="hljs-literal">-centos</span> <span class="hljs-literal">-t</span> mycentos:<span class="hljs-number">1.0</span> .<br></code></pre></td></tr></table></figure><p>然后我们通过<code>docker run -it mycentos:1.0 </code>命令进入我们自己创建的镜像测试运行我们新安装的包和命令是否能正常运行。<br><img src="https://img-blog.csdnimg.cn/20210224220131565.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>我们可以通过 <code>docker history +容器名称/容器id</code>看到这个容器的构建过程。<br><img src="https://img-blog.csdnimg.cn/20210224220552645.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="CMD和ENTRYPOINT区别"><a href="#CMD和ENTRYPOINT区别" class="headerlink" title="CMD和ENTRYPOINT区别"></a>CMD和ENTRYPOINT区别</h2><p><img src="https://img-blog.csdnimg.cn/20210224222435489.png" alt="在这里插入图片描述"></p><p>dockerfile-cmd-test:</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">FROM centos<br>CMD [<span class="hljs-string">"ls"</span>,<span class="hljs-string">"-a"</span>]<br></code></pre></td></tr></table></figure><p>dockerfile-entrypoint-test:</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">FROM centos<br>ENTRYPOINT [<span class="hljs-string">"ls"</span>,<span class="hljs-string">"-a"</span>]<br></code></pre></td></tr></table></figure><p>执行命令<code> docker run 容器名称 -l</code>在CMD下会报错,命令会被后面追加的<code>-l</code>替代,而<code>-l</code>并不是有效的linux命令,所以报错,而ENTRYPOINT则是可以追加的则该命令会变为<code>ls -al</code><br><img src="https://img-blog.csdnimg.cn/20210224222324355.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="发布镜像"><a href="#发布镜像" class="headerlink" title="发布镜像"></a>发布镜像</h2><blockquote><p>发布到Docker Hub</p></blockquote><ol><li>地址 <a href="https://hub.docker.com/" target="_blank" rel="noopener">https://hub.docker.com/</a> 注册自己的账号</li><li>确定这个账号可以登录</li><li>在我们服务器上提交自己的镜像</li><li>登录成功,通过<code>push</code>命令提交镜像,<strong>记得注意添加版本号</strong><br><img src="https://img-blog.csdnimg.cn/20210224223425131.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>这里出了一点小问题:</li></ol><p><strong>在build自己的镜像的时候添加tag时必须在前面加上自己的dockerhub的username,然后再push就可以了</strong><br><img src="https://img-blog.csdnimg.cn/20210224230121332.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker tag 镜像id YOUR_DOCKERHUB_NAME/firstimage<br>docker push YOUR_DOCKERHUB_NAME/firstimage<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224231803691.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>提交成功,可以在docker hub上找到你提交的镜像<br><img src="https://img-blog.csdnimg.cn/20210224232108402.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><blockquote><p>阿里云镜像提交</p></blockquote><p>在阿里云的容器镜像服务里面,创建一个新的镜像仓库,然后就会有详细的教学,做法与docker hub基本一致,提交成功能在镜像版本当中查看到,这里就不再重复讲解了。<br><img src="https://img-blog.csdnimg.cn/202102242325274.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="Docker镜像操作过程总结"><a href="#Docker镜像操作过程总结" class="headerlink" title="Docker镜像操作过程总结"></a>Docker镜像操作过程总结</h2><p><img src="https://img-blog.csdnimg.cn/20210224232749397.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224232831852.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><blockquote><p>参考链接:<a href="https://www.bilibili.com/video/BV1og4y1q7M4?t=201&p=33" target="_blank" rel="noopener">【狂神说Java】Docker最新超详细版教程通俗易懂</a></p></blockquote>]]></content>
<summary type="html">
<h2 id="commit镜像"><a href="#commit镜像" class="headerlink" title="commit镜像"></a>commit镜像</h2><p><img src="https://img-blog.csdnimg.cn/20210224162521454.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
<h2 id="数据卷操作实战:mysql同步"><a href="#数据卷操作实战:mysql同步" class="headerlink" title="数据卷操作实战:mysql同步"></a>数据卷操作实战:mysql同步</h2><p><strong>mysql运行容器,需要做数据挂载,安装启动mysql是需要配置密码的这一点要注意,所以要去docker hub官方文档上面去看官方配置</strong></p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker pull mysql:<span class="hljs-number">5.7</span><br></code></pre></td></tr></table></figure>
<p>docker运行,docker run的常用参数这里我们再次回顾一下</p>
<figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell"><span class="hljs-literal">-d</span> 后台运行<br><span class="hljs-literal">-p</span> 端口映射<br><span class="hljs-literal">-v</span> 卷挂载<br><span class="hljs-literal">-e</span> 环境配置<br>-<span class="hljs-literal">-name</span> 环境名字<br></code></pre></td></tr></table></figure>
</summary>
<category term="docker" scheme="https://jackyin.space/tags/docker/"/>
</entry>
<entry>
<title>docker常用命令</title>
<link href="https://jackyin.space/2021/02/24/docker%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/"/>
<id>https://jackyin.space/2021/02/24/docker%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4/</id>
<published>2021-02-24T07:30:00.000Z</published>
<updated>2024-10-09T08:43:45.257Z</updated>
<content type="html"><![CDATA[<h1 id="Docker-常用命令"><a href="#Docker-常用命令" class="headerlink" title="Docker 常用命令"></a>Docker 常用命令</h1><h2 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">docker version <span class="hljs-comment">#显示docker版本信息</span><br>docker info <span class="hljs-comment">#显示docker的系统信息,包括镜像和容器的数量</span><br>docker 命令 --<span class="hljs-built_in">help</span> <span class="hljs-comment"># 帮助命令</span><br></code></pre></td></tr></table></figure><h2 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h2><p><strong>1.<code>docker images</code>查看所有本地的主机镜像</strong></p><table><thead><tr><th align="left">docker images显示字段</th><th align="left">解释</th></tr></thead><tbody><tr><td align="left">REPOSITORY</td><td align="left">镜像的仓库源</td></tr><tr><td align="left">TAG</td><td align="left">镜像的标签</td></tr><tr><td align="left">IMAGE ID</td><td align="left">镜像的id</td></tr><tr><td align="left">CREATED</td><td align="left">镜像的创建时间</td></tr><tr><td align="left">SIZE</td><td align="left">镜像的大小</td></tr></tbody></table><a id="more"></a><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">(base) [root@iZuf69rye0flkbn4kbxrobZ ~]<span class="hljs-comment"># docker images</span><br>REPOSITORY TAG IMAGE ID CREATED SIZE<br>hello-world latest bf756fb1ae65 13 months ago 13.3kB<br>(base) [root@iZuf69rye0flkbn4kbxrobZ ~]<span class="hljs-comment"># docker images --help</span><br><br>Usage: docker images [OPTIONS] [REPOSITORY[:TAG]]<br><br>List images<br><br>Options:<br> -a, --all Show all images <br> -q, --quiet Only show image IDs<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224130929333.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>2.<code>docker search</code>命令搜索镜像</strong><br>搜索镜像可以去docker hub网站上直接搜索,也可以通过命令行来搜索,通过万能帮助命令能更快的看到他的一些用法,这两种方法结果是一样的<br><img src="https://img-blog.csdnimg.cn/20210224131759589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224131506578.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p>我们也可以通过<code>--filter</code>来进行条件筛选<br>比如<code>docker search mysql --filter=STARS=3000</code></p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">(base) [root@iZuf69rye0flkbn4kbxrobZ ~]<span class="hljs-comment"># docker search mysql --filter=STARS=3000</span><br>NAME DESCRIPTION STARS OFFICIAL AUTOMATED<br>mysql MySQL is a widely used, open-source relation… 10538 [OK] <br>mariadb MariaDB is a community-developed fork of MyS… 3935 [OK]<br></code></pre></td></tr></table></figure><p><strong>3.<code>docker pull</code>下载镜像</strong><br><strong>这个命令其实信息量很大,这也是docker高明的地方,关于指定版本下载一定要是docker hub官网上面支持和提供的版本</strong><br><img src="https://img-blog.csdnimg.cn/2021022413252511.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>我这里使用了</p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">docker pull mysql<br>docker pull mysql:5.7<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224132844566.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>4.<code>docker rmi</code>删除镜像</strong><br>删除可以通过<code>REPOSITORY</code>来删,也可以通过<code>IMAGE ID</code>来删除<br><img src="https://img-blog.csdnimg.cn/20210224133232500.png" alt="在这里插入图片描述"></p><h2 id="容器命令"><a href="#容器命令" class="headerlink" title="容器命令"></a>容器命令</h2><p><strong>说明:我们有了镜像才可以创建容器,linux,下载一个centos镜像来测试学习</strong></p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">docker pull centos<br></code></pre></td></tr></table></figure><p><strong>1.新建容器并启动</strong><br>通过<code>docker run</code>命令进入下载的centos容器里面后我们可以发现的是,我们的rootname不一样了<br><img src="https://img-blog.csdnimg.cn/20210224134041815.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><img src="https://img-blog.csdnimg.cn/20210224134219210.png" alt="在这里插入图片描述"></p><p><strong>2.列出所有运行的容器</strong><br><code>docker ps</code>命令<br><img src="https://img-blog.csdnimg.cn/2021022413511697.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>3.<code>exit</code>退出命令</strong></p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell"><span class="hljs-keyword">exit</span> <span class="hljs-comment">#直接容器停止并退出</span><br>Ctrl + P + Q <span class="hljs-comment">#容器不停止并退出</span><br></code></pre></td></tr></table></figure><p>在执行exit命令后,我们看到rootname又变回来了<br><img src="https://img-blog.csdnimg.cn/20210224134531160.png" alt="在这里插入图片描述"></p><p><strong>4.删除容器</strong></p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker rm 容器id <span class="hljs-comment">#删除指定的容器,不能删除正在运行的容器,如果要强制删除,需要使用 rm -f</span><br>docker rm <span class="hljs-variable">$</span>(docker ps <span class="hljs-literal">-aq</span>) <span class="hljs-comment">#删除全部的容器</span><br>docker ps <span class="hljs-literal">-a</span> <span class="hljs-literal">-q</span>|xargs docker rm <span class="hljs-comment">#删除全部容器</span><br></code></pre></td></tr></table></figure><p><strong>5.启动和停止容器</strong><br><img src="https://img-blog.csdnimg.cn/20210224135856699.png" alt="在这里插入图片描述"></p><h2 id="日志元数据进程查看"><a href="#日志元数据进程查看" class="headerlink" title="日志元数据进程查看"></a>日志元数据进程查看</h2><p><img src="https://img-blog.csdnimg.cn/20210224141011643.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70"><br><img src="https://img-blog.csdnimg.cn/20210224141603263.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>1.<code>docker top 容器id</code>查看容器中的进程</strong></p><p><img src="https://img-blog.csdnimg.cn/20210224141846922.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><strong>2.<code>docker inspect 容器id</code>查看元数据</strong></p><p><strong>3.进入当前正在运行的容器</strong></p><p>方式1: <code>docker exec -it 容器id bashshell</code>并可通过<code>ps -ef</code>查看容器当中的进程<br><img src="https://img-blog.csdnimg.cn/20210224143749283.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70"></p><p>方式2:<code>docker attach 容器id</code>进入容器,如果当前有正在执行的容器则会直接进入到当前正在执行的进程当中</p><p><img src="https://img-blog.csdnimg.cn/20210224142610940.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="从容器内拷贝到主机上"><a href="#从容器内拷贝到主机上" class="headerlink" title="从容器内拷贝到主机上"></a>从容器内拷贝到主机上</h2><p>即使容器已经停止也是可以进行拷贝的</p><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker cp 容器id:容器内路径 目的主机路径<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224144038919.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="docker部署nginx"><a href="#docker部署nginx" class="headerlink" title="docker部署nginx"></a>docker部署nginx</h2><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell"><span class="hljs-variable">$</span> docker search nginx<br><span class="hljs-variable">$</span> docker pull nginx<br><span class="hljs-variable">$</span> docker run <span class="hljs-literal">-d</span> -<span class="hljs-literal">-name</span> nginx01 <span class="hljs-literal">-p</span> <span class="hljs-number">8083</span>:<span class="hljs-number">80</span> nginx<br><span class="hljs-variable">$</span> docker ps<br><span class="hljs-variable">$</span> curl localhost:<span class="hljs-number">8083</span><br></code></pre></td></tr></table></figure><p><code>docker stop</code> 后则无法再访问<br><img src="https://img-blog.csdnimg.cn/20210224145948506.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224150019256.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224144959653.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="portainer可视化管理"><a href="#portainer可视化管理" class="headerlink" title="portainer可视化管理"></a>portainer可视化管理</h2><figure class="highlight powershell"><table><tr><td class="code"><pre><code class="hljs powershell">docker run <span class="hljs-literal">-d</span> <span class="hljs-literal">-p</span> <span class="hljs-number">8088</span>:<span class="hljs-number">9000</span> -<span class="hljs-literal">-restart</span>=always <span class="hljs-literal">-v</span> /var/run/docker.sock:/var/run/docker.sock -<span class="hljs-literal">-privileged</span>=true portainer/portainer<br></code></pre></td></tr></table></figure><p><img src="https://img-blog.csdnimg.cn/20210224152421776.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>进入后选择<strong>local模式</strong>,然后就能看到这个版面了<br><img src="https://img-blog.csdnimg.cn/20210224152631399.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>参考链接:<br><a href="https://www.bilibili.com/video/BV1og4y1q7M4?p=9" target="_blank" rel="noopener">【狂神说Java】Docker最新超详细版教程通俗易懂</a></p>]]></content>
<summary type="html">
<h1 id="Docker-常用命令"><a href="#Docker-常用命令" class="headerlink" title="Docker 常用命令"></a>Docker 常用命令</h1><h2 id="帮助命令"><a href="#帮助命令" class="headerlink" title="帮助命令"></a>帮助命令</h2><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">docker version <span class="hljs-comment">#显示docker版本信息</span><br>docker info <span class="hljs-comment">#显示docker的系统信息,包括镜像和容器的数量</span><br>docker 命令 --<span class="hljs-built_in">help</span> <span class="hljs-comment"># 帮助命令</span><br></code></pre></td></tr></table></figure>
<h2 id="镜像命令"><a href="#镜像命令" class="headerlink" title="镜像命令"></a>镜像命令</h2><p><strong>1.<code>docker images</code>查看所有本地的主机镜像</strong></p>
<table>
<thead>
<tr>
<th align="left">docker images显示字段</th>
<th align="left">解释</th>
</tr>
</thead>
<tbody><tr>
<td align="left">REPOSITORY</td>
<td align="left">镜像的仓库源</td>
</tr>
<tr>
<td align="left">TAG</td>
<td align="left">镜像的标签</td>
</tr>
<tr>
<td align="left">IMAGE ID</td>
<td align="left">镜像的id</td>
</tr>
<tr>
<td align="left">CREATED</td>
<td align="left">镜像的创建时间</td>
</tr>
<tr>
<td align="left">SIZE</td>
<td align="left">镜像的大小</td>
</tr>
</tbody></table>
</summary>
<category term="docker" scheme="https://jackyin.space/tags/docker/"/>
</entry>
<entry>
<title>docker安装和简易原理</title>
<link href="https://jackyin.space/2021/02/24/docker%E5%AE%89%E8%A3%85%E5%92%8C%E7%AE%80%E6%98%93%E5%8E%9F%E7%90%86/"/>
<id>https://jackyin.space/2021/02/24/docker%E5%AE%89%E8%A3%85%E5%92%8C%E7%AE%80%E6%98%93%E5%8E%9F%E7%90%86/</id>
<published>2021-02-24T04:53:00.000Z</published>
<updated>2024-10-09T08:43:45.257Z</updated>
<content type="html"><![CDATA[<h2 id="docker安装和简易原理"><a href="#docker安装和简易原理" class="headerlink" title="docker安装和简易原理"></a>docker安装和简易原理</h2><p>最近参加了阿里云datawhale天池的一个比赛里面需要用docker进行提交,所以借此机会学习了一下docker,b站上有个很好的视频<a href="https://www.bilibili.com/video/BV1og4y1q7M4?p=6" target="_blank" rel="noopener">【狂神说Java】Docker最新超详细版教程通俗易懂</a></p><h2 id="docker基本组成"><a href="#docker基本组成" class="headerlink" title="docker基本组成"></a>docker基本组成</h2><p><img src="https://img-blog.csdnimg.cn/20210224124633537.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><a id="more"></a><p><img src="https://img-blog.csdnimg.cn/20210224124854806.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="docker安装"><a href="#docker安装" class="headerlink" title="docker安装"></a>docker安装</h2><p>centos7安装<br>先查看centos版本,新版本的docker都只支持centos7以上</p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">(base) [root@iZuf69rye0flkbn4kbxrobZ ~]<span class="hljs-comment"># cat /etc/os-release</span><br>NAME=<span class="hljs-string">"CentOS Linux"</span><br>VERSION=<span class="hljs-string">"7 (Core)"</span><br>ID=<span class="hljs-string">"centos"</span><br>ID_LIKE=<span class="hljs-string">"rhel fedora"</span><br>VERSION_ID=<span class="hljs-string">"7"</span><br>PRETTY_NAME=<span class="hljs-string">"CentOS Linux 7 (Core)"</span><br>ANSI_COLOR=<span class="hljs-string">"0;31"</span><br>CPE_NAME=<span class="hljs-string">"cpe:/o:centos:centos:7"</span><br>HOME_URL=<span class="hljs-string">"https://www.centos.org/"</span><br>BUG_REPORT_URL=<span class="hljs-string">"https://bugs.centos.org/"</span><br><br>CENTOS_MANTISBT_PROJECT=<span class="hljs-string">"CentOS-7"</span><br>CENTOS_MANTISBT_PROJECT_VERSION=<span class="hljs-string">"7"</span><br>REDHAT_SUPPORT_PRODUCT=<span class="hljs-string">"centos"</span><br>REDHAT_SUPPORT_PRODUCT_VERSION=<span class="hljs-string">"7"</span><br></code></pre></td></tr></table></figure><p>然后我们的操作均按照帮助文档进行操作即可<br><a href="https://docs.docker.com/engine/install/centos/" target="_blank" rel="noopener">dockerCentos安装</a></p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 卸载旧版本</span><br>sudo yum remove docker \<br> docker-client \<br> docker-client-latest \<br> docker-common \<br> docker-latest \<br> docker-latest-logrotate \<br> docker-logrotate \<br> docker-engine<br><br><span class="hljs-comment"># 安装</span><br>sudo yum install -y yum-utils<br><br><span class="hljs-comment">#配置镜像,官方是国外的很慢,这里我们使用阿里云镜像</span><br>sudo yum-config-manager \<br> --add-repo \<br> https://download.docker.com/linux/centos/docker-ce.repo<br><br><span class="hljs-comment"># 推荐使用这个</span><br>sudo yum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo<br><br><span class="hljs-comment">#更新yum</span><br>yum makecache fast<br><br><span class="hljs-comment"># 安装相关的包</span><br> sudo yum install docker-ce docker-ce-cli containerd.io<br></code></pre></td></tr></table></figure><h2 id="Start-Docker"><a href="#Start-Docker" class="headerlink" title="Start Docker"></a>Start Docker</h2><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">sudo systemctl start docker<br></code></pre></td></tr></table></figure><p>使用<code>docker version</code>查看是否安装成功<br><img src="https://img-blog.csdnimg.cn/2021022412185923.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><code>sudo docker run hello-world</code><img src="https://img-blog.csdnimg.cn/20210224122343176.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><p>使用<code>docker images</code> 查看所安装的所有镜像<br><img src="https://img-blog.csdnimg.cn/20210224122509960.png" alt="在这里插入图片描述"></p><h2 id="Uninstall-Docker-Engine"><a href="#Uninstall-Docker-Engine" class="headerlink" title="Uninstall Docker Engine"></a>Uninstall Docker Engine</h2><p>1.Uninstall the Docker Engine, CLI, and Containerd packages:</p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">sudo yum remove docker-ce docker-ce-cli containerd.io<br></code></pre></td></tr></table></figure><p>2.删除默认工作目录和资源</p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">$ sudo rm -rf /var/lib/docker<br></code></pre></td></tr></table></figure><h2 id="阿里云镜像加速器"><a href="#阿里云镜像加速器" class="headerlink" title="阿里云镜像加速器"></a>阿里云镜像加速器</h2><p>这一块的话推荐天池的一个<a href="https://tianchi.aliyun.com/competition/entrance/231759/tab/226" target="_blank" rel="noopener">docker学习赛</a><br>登录阿里云找到容器服务,并创建新容器,然后找到里面的镜像加速器对centos进行配置。<br><img src="https://img-blog.csdnimg.cn/20210224123245412.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><figure class="highlight bash"><table><tr><td class="code"><pre><code class="hljs bash">sudo mkdir -p /etc/docker<br><br>sudo tee /etc/docker/daemon.json <<-<span class="hljs-string">'EOF'</span><br>{<br> <span class="hljs-string">"registry-mirrors"</span>: [<span class="hljs-string">"https://fdm7vcvf.mirror.aliyuncs.com"</span>]<br>}<br>EOF<br><br>sudo systemctl daemon-reload<br><br>sudo systemctl restart docker<br></code></pre></td></tr></table></figure><h2 id="回归Hello-World镜像的运行过程"><a href="#回归Hello-World镜像的运行过程" class="headerlink" title="回归Hello World镜像的运行过程"></a>回归Hello World镜像的运行过程</h2><p><img src="https://img-blog.csdnimg.cn/20210224123453853.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p><h2 id="底层原理"><a href="#底层原理" class="headerlink" title="底层原理"></a>底层原理</h2><p>Docker是什么工作的?<br>Docker是一个Client - Server结构的系统,Docker的守护进行运行在主机上。通过Socket从客户端访问!DockerServer接收到Docker-Client的指令,就会执行这个命令!<br><img src="https://img-blog.csdnimg.cn/20210224124010560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br>docker为什么比虚拟机快?<br><a href="https://www.cnblogs.com/fanqisoft/p/10440220.html" target="_blank" rel="noopener">参考链接</a></p><p><img src="https://img-blog.csdnimg.cn/20210224124320560.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"><br><img src="https://img-blog.csdnimg.cn/20210224124350819.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>]]></content>
<summary type="html">
<h2 id="docker安装和简易原理"><a href="#docker安装和简易原理" class="headerlink" title="docker安装和简易原理"></a>docker安装和简易原理</h2><p>最近参加了阿里云datawhale天池的一个比赛里面需要用docker进行提交,所以借此机会学习了一下docker,b站上有个很好的视频<a href="https://www.bilibili.com/video/BV1og4y1q7M4?p=6" target="_blank" rel="noopener">【狂神说Java】Docker最新超详细版教程通俗易懂</a></p>
<h2 id="docker基本组成"><a href="#docker基本组成" class="headerlink" title="docker基本组成"></a>docker基本组成</h2><p><img src="https://img-blog.csdnimg.cn/20210224124633537.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70" alt="在这里插入图片描述"></p>
</summary>
<category term="docker" scheme="https://jackyin.space/tags/docker/"/>
</entry>
<entry>
<title>datawhale语义分割-Task4 评价函数与损失函数</title>
<link href="https://jackyin.space/2021/02/23/datawhale%E8%AF%AD%E4%B9%89%E5%88%86%E5%89%B2-Task4-%E8%AF%84%E4%BB%B7%E5%87%BD%E6%95%B0%E4%B8%8E%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0/"/>
<id>https://jackyin.space/2021/02/23/datawhale%E8%AF%AD%E4%B9%89%E5%88%86%E5%89%B2-Task4-%E8%AF%84%E4%BB%B7%E5%87%BD%E6%95%B0%E4%B8%8E%E6%8D%9F%E5%A4%B1%E5%87%BD%E6%95%B0/</id>
<published>2021-02-23T06:55:00.000Z</published>
<updated>2024-10-09T08:43:45.257Z</updated>
<content type="html"><![CDATA[<h1 id="零基础入门语义分割-Task4-评价函数与损失函数"><a href="#零基础入门语义分割-Task4-评价函数与损失函数" class="headerlink" title="零基础入门语义分割-Task4 评价函数与损失函数"></a>零基础入门语义分割-Task4 评价函数与损失函数</h1><p>本章主要介绍语义分割的评价函数和各类损失函数。</p><h2 id="4-评价函数与损失函数"><a href="#4-评价函数与损失函数" class="headerlink" title="4 评价函数与损失函数"></a>4 评价函数与损失函数</h2><h3 id="4-1-学习目标"><a href="#4-1-学习目标" class="headerlink" title="4.1 学习目标"></a>4.1 学习目标</h3><ul><li>掌握常见的评价函数和损失函数Dice、IoU、BCE、Focal Loss、Lovász-Softmax;</li><li>掌握评价/损失函数的实践;</li></ul><a id="more"></a><h3 id="4-2-TP-TN-FP-FN"><a href="#4-2-TP-TN-FP-FN" class="headerlink" title="4.2 TP TN FP FN"></a>4.2 TP TN FP FN</h3><p>在讲解语义分割中常用的评价函数和损失函数之前,先补充一**TP(真正例 true positive) TN(真反例 true negative) FP(假正例 false positive) FN(假反例 false negative)**的知识。在分类问题中,我们经常看到上述的表述方式,以二分类为例,我们可以将所有的样本预测结果分成TP、TN、 FP、FN四类,并且每一类含有的样本数量之和为总样本数量,即TP+FP+FN+TN=总样本数量。其混淆矩阵如下:</p><p><img src="https://img-blog.csdnimg.cn/20210223144602309.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>上述的概念都是通过以预测结果的视角定义的,可以依据下面方式理解:</p><ul><li><p>预测结果中的正例 → 在实际中是正例 → 的所有样本被称为真正例(TP)<预测正确></p></li><li><p>预测结果中的正例 → 在实际中是反例 → 的所有样本被称为假正例(FP)<预测错误></p></li><li><p>预测结果中的反例 → 在实际中是正例 → 的所有样本被称为假反例(FN)<预测错误></p></li><li><p>预测结果中的反例 → 在实际中是反例 → 的所有样本被称为真反例(TN)<预测正确></p></li></ul><p>这里就不得不提及精确率(precision)和召回率(recall):<br>$$<br>Precision=\frac{TP}{TP+FP} \<br>Recall=\frac{TP}{TP+FN}<br>$$<br>$Precision$代表了预测的正例中真正的正例所占比例;$Recall$代表了真正的正例中被正确预测出来的比例。</p><p>转移到语义分割任务中来,我们可以将语义分割看作是对每一个图像像素的的分类问题。根据混淆矩阵中的定义,我们亦可以将特定像素所属的集合或区域划分成TP、TN、 FP、FN四类。</p><p><img src="https://img-blog.csdnimg.cn/20210223144629443.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>以上面的图片为例,图中左子图中的人物区域(黄色像素集合)是我们<strong>真实标注的前景信息(target)</strong>,其他区域(紫色像素集合)为背景信息。当经过预测之后,我们会得到的一张预测结果,图中右子图中的黄色像素为<strong>预测的前景(prediction)</strong>,紫色像素为预测的背景区域。此时,我们便能够将预测结果分成4个部分:</p><ul><li><p>预测结果中的黄色无线区域 → 真实的前景 → 的所有像素集合被称为真正例(TP)<预测正确></p></li><li><p>预测结果中的蓝色斜线区域 → 真实的背景 → 的所有像素集合被称为假正例(FP)<预测错误></p></li><li><p>预测结果中的红色斜线区域 → 真实的前景 → 的所有像素集合被称为假反例(FN)<预测错误></p></li><li><p>预测结果中的白色斜线区域 → 真实的背景 → 的所有像素集合被称为真反例(TN)<预测正确></p></li></ul><h3 id="4-3-Dice评价指标"><a href="#4-3-Dice评价指标" class="headerlink" title="4.3 Dice评价指标"></a>4.3 Dice评价指标</h3><p><strong>Dice系数</strong></p><p>Dice系数(Dice coefficient)是常见的评价分割效果的方法之一,同样也可以改写成损失函数用来度量prediction和target之间的距离。Dice系数定义如下:</p><p>$$<br>Dice (T, P) = \frac{2 |T \cap P|}{|T| \cup |P|} = \frac{2TP}{FP+2TP+FN}<br>$$<br>式中:$T$表示真实前景(target),$P$表示预测前景(prediction)。Dice系数取值范围为$[0,1]$,其中值为1时代表预测与真实完全一致。仔细观察,Dice系数与分类评价指标中的F1 score很相似:</p><p>$$<br>\frac{1}{F1} = \frac{1}{Precision} + \frac{1}{Recall}<br>$$</p><p>$$<br>F1 = \frac{2TP}{FP+2TP+FN}<br>$$</p><p>所以,Dice系数不仅在直观上体现了target与prediction的相似程度,同时其本质上还隐含了精确率和召回率两个重要指标。</p><p>计算Dice时,将$|T \cap P|$近似为prediction与target对应元素相乘再相加的结果。$|T|$ 和$|P|$的计算直接进行简单的元素求和(也有一些做法是取平方求和),如下示例:<br>$$<br>|T \cap P| =<br>\begin{bmatrix}<br>0.01 & 0.03 & 0.02 & 0.02 \<br>0.05 & 0.12 & 0.09 & 0.07 \<br>0.89 & 0.85 & 0.88 & 0.91 \<br>0.99 & 0.97 & 0.95 & 0.97 \<br>\end{bmatrix} *<br>\begin{bmatrix}<br>0 & 0 & 0 & 0 \<br>0 & 0 & 0 & 0 \<br>1 & 1 & 1 & 1 \<br>1 & 1 & 1 & 1 \<br>\end{bmatrix} \stackrel{}{\rightarrow}<br>\begin{bmatrix}<br>0 & 0 & 0 & 0 \<br>0 & 0 & 0 & 0 \<br>0.89 & 0.85 & 0.88 & 0.91 \<br>0.99 & 0.97 & 0.95 & 0.97 \<br>\end{bmatrix} \stackrel{sum}{\rightarrow} 7.41<br>$$</p><p>$$<br>|T| =<br>\begin{bmatrix}<br>0.01 & 0.03 & 0.02 & 0.02 \<br>0.05 & 0.12 & 0.09 & 0.07 \<br>0.89 & 0.85 & 0.88 & 0.91 \<br>0.99 & 0.97 & 0.95 & 0.97 \<br>\end{bmatrix} \stackrel{sum}{\rightarrow} 7.82<br>$$</p><p>$$<br>|P| =<br>\begin{bmatrix}<br>0 & 0 & 0 & 0 \<br>0 & 0 & 0 & 0 \<br>1 & 1 & 1 & 1 \<br>1 & 1 & 1 & 1 \<br>\end{bmatrix} \stackrel{sum}{\rightarrow} 8<br>$$</p><p><strong>Dice Loss</strong></p><p>Dice Loss是在<a href="https://arxiv.org/abs/1606.04797" target="_blank" rel="noopener">V-net</a>模型中被提出应用的,是通过Dice系数转变而来,其实为了能够实现最小化的损失函数,以方便模型训练,以$1 - Dice$的形式作为损失函数:<br>$$<br>L = 1-\frac{2 |T \cap P|}{|T| \cup |P|}<br>$$<br>在一些场合还可以添加上<strong>Laplace smoothing</strong>减少过拟合:<br>$$<br>L = 1-\frac{2 |T \cap P| + 1}{|T| \cup |P|+1}<br>$$</p><p><strong>代码实现</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">dice</span><span class="hljs-params">(output, target)</span>:</span><br> <span class="hljs-string">'''计算Dice系数'''</span><br> smooth = <span class="hljs-number">1e-6</span> <span class="hljs-comment"># 避免0为除数</span><br> intersection = (output * target).sum()<br> <span class="hljs-keyword">return</span> (<span class="hljs-number">2.</span> * intersection + smooth) / (output.sum() + target.sum() + smooth)<br><br><span class="hljs-comment"># 生成随机两个矩阵测试</span><br>target = np.random.randint(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>))<br>output = np.random.randint(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>))<br><br>d = dice(output, target)<br><span class="hljs-comment"># ----------------------------</span><br>target = array([[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>]])<br>output = array([[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>]])<br>d = <span class="hljs-number">0.5714286326530524</span><br></code></pre></td></tr></table></figure><h3 id="4-4-IoU评价指标"><a href="#4-4-IoU评价指标" class="headerlink" title="4.4 IoU评价指标"></a>4.4 IoU评价指标</h3><p>IoU(intersection over union)指标就是常说的交并比,不仅在语义分割评价中经常被使用,在目标检测中也是常用的评价指标。顾名思义,交并比就是指target与prediction两者之间交集与并集的比值:<br>$$<br>IoU=\frac{T \cap P}{T \cup P}=\frac{TP}{FP+TP+FN}<br>$$<br>仍然以人物前景分割为例,如下图,其IoU的计算就是使用$intersection / union$。</p><table><thead><tr><th align="center">target</th><th align="center">prediction</th></tr></thead><tbody><tr><td align="center"><img src="https://img-blog.csdnimg.cn/20210223144740581.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></td><td align="center"><img src="https://img-blog.csdnimg.cn/20210223144755503.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></td></tr><tr><td align="center">Intersection( $T \cap P$)</td><td align="center">union($T \cup P$)</td></tr><tr><td align="center"><img src="https://img-blog.csdnimg.cn/20210223144825627.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></td><td align="center"><img src="https://img-blog.csdnimg.cn/20210223144847490.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></td></tr></tbody></table><p><strong>代码实现</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">iou_score</span><span class="hljs-params">(output, target)</span>:</span><br> <span class="hljs-string">'''计算IoU指标'''</span><br>intersection = np.logical_and(target, output) <br> union = np.logical_or(target, output) <br> <span class="hljs-keyword">return</span> np.sum(intersection) / np.sum(union)<br><br><span class="hljs-comment"># 生成随机两个矩阵测试</span><br>target = np.random.randint(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>))<br>output = np.random.randint(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, (<span class="hljs-number">3</span>, <span class="hljs-number">3</span>))<br><br>d = iou_score(output, target)<br><span class="hljs-comment"># ----------------------------</span><br>target = array([[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>]])<br>output = array([[<span class="hljs-number">1</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>],<br> [<span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>]])<br>d = <span class="hljs-number">0.4</span><br></code></pre></td></tr></table></figure><h3 id="4-5-BCE损失函数"><a href="#4-5-BCE损失函数" class="headerlink" title="4.5 BCE损失函数"></a>4.5 BCE损失函数</h3><p>BCE损失函数(Binary Cross-Entropy Loss)是交叉熵损失函数(Cross-Entropy Loss)的一种特例,BCE Loss只应用在二分类任务中。针对分类问题,单样本的交叉熵损失为:<br>$$<br>l(\pmb y, \pmb{\hat y})=- \sum_{i=1}^{c}y_i \cdot log\hat y_i<br>$$<br>式中,$\pmb{y}={y_1,y_2,…,y_c,}$,其中$y_i$是非0即1的数字,代表了是否属于第$i$类,为真实值;$\hat y_i$代表属于第i类的概率,为预测值。可以看出,交叉熵损失考虑了多类别情况,针对每一种类别都求了损失。针对二分类问题,上述公式可以改写为:<br>$$<br>l(y,\hat y)=-[y \cdot log\hat y +(1-y)\cdot log (1-\hat y)]<br>$$<br>式中,$y$为真实值,非1即0;$\hat y$为所属此类的概率值,为预测值。这个公式也就是BCE损失函数,即二分类任务时的交叉熵损失。值得强调的是,公式中的$\hat y$为概率分布形式,因此在使用BCE损失前,<strong>都应该将预测出来的结果转变成概率值</strong>,一般为sigmoid激活之后的输出。</p><p><strong>代码实现</strong></p><p>在pytorch中,官方已经给出了BCE损失函数的API,免去了自己编写函数的痛苦:</p><blockquote><p><code>torch.nn.BCELoss(weight: Optional[torch.Tensor] = None, size_average=None, reduce=None, reduction: str = 'mean')</code><br>$$<br>ℓ(y,\hat y)=L={l_1,…,l_N }^⊤,\ \ \ l_n=-w_n[y_n \cdot log\hat y_n +(1-y_n)\cdot log (1-\hat y_n)]<br>$$<br>参数:<br>weight(Tensor)- 为每一批量下的loss添加一个权重,很少使用<br>size_average(bool)- 弃用中<br>reduce(bool)- 弃用中<br>reduction(str) - ‘none’ | ‘mean’ | ‘sum’:为代替上面的size_average和reduce而生。——为mean时返回的该批量样本loss的平均值;为sum时,返回的该批量样本loss之和</p></blockquote><p><strong>同时,pytorch还提供了已经结合了Sigmoid函数的BCE损失:<code>torch.nn.BCEWithLogitsLoss()</code>,相当于免去了实现进行Sigmoid激活的操作。</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch<br><span class="hljs-keyword">import</span> torch.nn <span class="hljs-keyword">as</span> nn<br><br>bce = nn.BCELoss()<br>bce_sig = nn.BCEWithLogitsLoss()<br><br>input = torch.randn(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>, requires_grad=<span class="hljs-literal">True</span>)<br>target = torch.empty(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>).random_(<span class="hljs-number">2</span>)<br>pre = nn.Sigmoid()(input)<br><br>loss_bce = bce(pre, target)<br>loss_bce_sig = bce_sig(input, target)<br><br><span class="hljs-comment"># ------------------------</span><br>input = tensor([[<span class="hljs-number">-0.2296</span>],<br> [<span class="hljs-number">-0.6389</span>],<br> [<span class="hljs-number">-0.2405</span>],<br> [ <span class="hljs-number">1.3451</span>],<br> [ <span class="hljs-number">0.7580</span>]], requires_grad=<span class="hljs-literal">True</span>)<br>output = tensor([[<span class="hljs-number">1.</span>],<br> [<span class="hljs-number">0.</span>],<br> [<span class="hljs-number">0.</span>],<br> [<span class="hljs-number">1.</span>],<br> [<span class="hljs-number">1.</span>]])<br>pre = tensor([[<span class="hljs-number">0.4428</span>],<br> [<span class="hljs-number">0.3455</span>],<br> [<span class="hljs-number">0.4402</span>],<br> [<span class="hljs-number">0.7933</span>],<br> [<span class="hljs-number">0.6809</span>]], grad_fn=<SigmoidBackward>)<br><br>print(loss_bce)<br>tensor(<span class="hljs-number">0.4869</span>, grad_fn=<BinaryCrossEntropyBackward>)<br><br>print(loss_bce_sig)<br>tensor(<span class="hljs-number">0.4869</span>, grad_fn=<BinaryCrossEntropyWithLogitsBackward>)<br></code></pre></td></tr></table></figure><h3 id="4-6-Focal-Loss"><a href="#4-6-Focal-Loss" class="headerlink" title="4.6 Focal Loss"></a>4.6 Focal Loss</h3><p>Focal loss最初是出现在目标检测领域,主要是为了解决正负样本比例失调的问题。那么对于分割任务来说,如果存在数据不均衡的情况,也可以借用focal loss来进行缓解。Focal loss函数公式如下所示:</p><p>$$<br>loss = -\frac{1}{N} \sum_{i=1}^{N}\left(\alpha y_{i}\left(1-p_{i}\right)^{\gamma} \log p_{i}+(1-\alpha)\left(1-y_{i}\right) p_{i}^{\gamma} \log \left(1-p_{i}\right)\right)<br>$$<br>仔细观察就不难发现,它其实是BCE扩展而来,对比BCE其实就多了个<br>$$<br>\alpha(1-p_{i})^{\gamma}和(1-\alpha)p_{i}^{\gamma}<br>$$<br>为什么多了这个就能缓解正负样本不均衡的问题呢?见下图:</p><p><img src="https://img-blog.csdnimg.cn/20210223144942139.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0phY2tfX19F,size_16,color_FFFFFF,t_70#pic_center" alt="在这里插入图片描述"></p><p>简单来说:$α$解决样本不平衡问题,$γ$解决样本难易问题。</p><p>也就是说,当数据不均衡时,可以根据比例设置合适的$α$,这个很好理解,为了能够使得正负样本得到的损失能够均衡,因此对loss前面加上一定的权重,其中负样本数量多,因此占用的权重可以设置的小一点;正样本数量少,就对正样本产生的损失的权重设的高一点。</p><p>那γ具体怎么起作用呢?以图中$γ=5$曲线为例,假设$gt$类别为1,当模型预测结果为1的概率$p_t$比较大时,我们认为模型预测的比较准确,也就是说这个样本比较简单。而对于比较简单的样本,我们希望提供的loss小一些而让模型主要学习难一些的样本,也就是$p_t→ 1$则loss接近于0,既不用再特别学习;当分类错误时,$p_t → 0$则loss正常产生,继续学习。对比图中蓝色和绿色曲线,可以看到,γ值越大,当模型预测结果比较准确的时候能提供更小的loss,符合我们为简单样本降低loss的预期。</p><p><strong>代码实现:</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn <span class="hljs-keyword">as</span> nn<br><span class="hljs-keyword">import</span> torch<br><span class="hljs-keyword">import</span> torch.nn.functional <span class="hljs-keyword">as</span> F<br><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">FocalLoss</span><span class="hljs-params">(nn.Module)</span>:</span><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">__init__</span><span class="hljs-params">(self, alpha=<span class="hljs-number">1</span>, gamma=<span class="hljs-number">2</span>, logits=False, reduce=True)</span>:</span><br> super(FocalLoss, self).__init__()<br> self.alpha = alpha<br> self.gamma = gamma<br> self.logits = logits<span class="hljs-comment"># 如果BEC带logits则损失函数在计算BECloss之前会自动计算softmax/sigmoid将其映射到[0,1]</span><br> self.reduce = reduce<br><br> <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">forward</span><span class="hljs-params">(self, inputs, targets)</span>:</span><br> <span class="hljs-keyword">if</span> self.logits:<br> BCE_loss = F.binary_cross_entropy_with_logits(inputs, targets, reduce=<span class="hljs-literal">False</span>)<br> <span class="hljs-keyword">else</span>:<br> BCE_loss = F.binary_cross_entropy(inputs, targets, reduce=<span class="hljs-literal">False</span>)<br> pt = torch.exp(-BCE_loss)<br> F_loss = self.alpha * (<span class="hljs-number">1</span>-pt)**self.gamma * BCE_loss<br><br> <span class="hljs-keyword">if</span> self.reduce:<br> <span class="hljs-keyword">return</span> torch.mean(F_loss)<br> <span class="hljs-keyword">else</span>:<br> <span class="hljs-keyword">return</span> F_loss<br><br><span class="hljs-comment"># ------------------------</span><br><br>FL1 = FocalLoss(logits=<span class="hljs-literal">False</span>)<br>FL2 = FocalLoss(logits=<span class="hljs-literal">True</span>)<br><br>inputs = torch.randn(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>, requires_grad=<span class="hljs-literal">True</span>)<br>targets = torch.empty(<span class="hljs-number">5</span>, <span class="hljs-number">1</span>).random_(<span class="hljs-number">2</span>)<br>pre = nn.Sigmoid()(inputs)<br><br>f_loss_1 = FL1(pre, targets)<br>f_loss_2 = FL2(inputs, targets)<br><br><span class="hljs-comment"># ------------------------</span><br><br>print(<span class="hljs-string">'inputs:'</span>, inputs)<br>inputs: tensor([[<span class="hljs-number">-1.3521</span>],<br> [ <span class="hljs-number">0.4975</span>],<br> [<span class="hljs-number">-1.0178</span>],<br> [<span class="hljs-number">-0.3859</span>],<br> [<span class="hljs-number">-0.2923</span>]], requires_grad=<span class="hljs-literal">True</span>)<br> <br>print(<span class="hljs-string">'targets:'</span>, targets)<br>targets: tensor([[<span class="hljs-number">1.</span>],<br> [<span class="hljs-number">1.</span>],<br> [<span class="hljs-number">0.</span>],<br> [<span class="hljs-number">1.</span>],<br> [<span class="hljs-number">1.</span>]])<br> <br>print(<span class="hljs-string">'pre:'</span>, pre)<br>pre: tensor([[<span class="hljs-number">0.2055</span>],<br> [<span class="hljs-number">0.6219</span>],<br> [<span class="hljs-number">0.2655</span>],<br> [<span class="hljs-number">0.4047</span>],<br> [<span class="hljs-number">0.4274</span>]], grad_fn=<SigmoidBackward>)<br> <br>print(<span class="hljs-string">'f_loss_1:'</span>, f_loss_1)<br>f_loss_1: tensor(<span class="hljs-number">0.3375</span>, grad_fn=<MeanBackward0>)<br> <br>print(<span class="hljs-string">'f_loss_2'</span>, f_loss_2)<br>f_loss_2 tensor(<span class="hljs-number">0.3375</span>, grad_fn=<MeanBackward0>)<br></code></pre></td></tr></table></figure><h3 id="4-7-Lovasz-Softmax"><a href="#4-7-Lovasz-Softmax" class="headerlink" title="4.7 Lovász-Softmax"></a>4.7 Lovász-Softmax</h3><p>IoU是评价分割模型分割结果质量的重要指标,因此很自然想到能否用$1-IoU$(即Jaccard loss)来做损失函数,但是它是一个离散的loss,不能直接求导,所以无法直接用来作为损失函数。为了克服这个离散的问题,可以采用lLovász extension将离散的Jaccard loss 变得连续,从而可以直接求导,使得其作为分割网络的loss function。Lovász-Softmax相比于交叉熵函数具有更好的效果。</p><p>论文地址:<br><a href="http://openaccess.thecvf.com/content_cvpr_2018/html/Berman_The_LovaSz-Softmax_Loss_CVPR_2018_paper.html" target="_blank" rel="noopener">paper on CVF open access</a><br><a href="https://arxiv.org/abs/1705.08790" target="_blank" rel="noopener">arxiv paper</a></p><p>首先明确定义,在语义分割任务中,给定真实像素标签向量$\pmb{y^*}$和预测像素标签$\pmb{\hat{y} }$,则所属类别$c$的IoU(也称为Jaccard index)如下,其取值范围为$[0,1]$,并规定$0/0=1$:</p><p>$$J_c(\pmb{y^*},\pmb{\hat{y} })=\frac{|{\pmb{y^*}=c} \cap {\pmb{\hat{y} }=c}|}{|{\pmb{y^*}=c} \cup {\pmb{\hat{y} }=c}|}<br>$$</p><p>则Jaccard loss为:<br>$$\Delta_{J_c}(\pmb{y^*},\pmb{\hat{y} }) =1-J_c(\pmb{y^*},\pmb{\hat{y} })<br>$$</p><p>针对类别$c$,所有未被正确预测的像素集合定义为:</p><p>$$<br>M_c(\pmb{y^*},\pmb{\hat{y} })={ \pmb{y^*}=c, \pmb{\hat{y} } \neq c} \cup { \pmb{y^*}\neq c, \pmb{\hat{y} } = c }<br>$$</p><p>则可将Jaccard loss改写为关于$M_c$的子模集合函数(submodular set functions):<br>$$\Delta_{J_c}:M_c \in {0,1}^{p} \mapsto \frac{|M_c|}{|{\pmb{y^*}=c}\cup M_c|}<br>$$</p><p>方便理解,此处可以把${0,1}^p$理解成如图像mask展开成离散一维向量的形式。</p><p>Lovász extension可以求解子模最小化问题,并且子模的Lovász extension是凸函数,可以高效实现最小化。在论文中作者对$\Delta$(集合函数)和$\overline{\Delta}$(集合函数的Lovász extension)进行了定义,为不涉及过多概念以方便理解,此处不再过多讨论。我们可以将$\overline{\Delta}$理解为一个线性插值函数,可以将${0,1}^p$这种离散向量连续化,主要是为了方便后续反向传播、求梯度等等。因此我们可以通过这个线性插值函数得到$\Delta_{J_c}$的Lovász extension$\overline{\Delta_{J_c} }$。</p><p>在具有$c(c>2)$个类别的语义分割任务中,我们使用Softmax函数将模型的输出映射到概率分布形式,类似传统交叉熵损失函数所进行的操作:<br>$$p_i(c)=\frac{e^{F_i(c)} }{\sum_{c^{‘}\in C}e^{F_i(c^{‘})} } \forall i \in [1,p],\forall c \in C<br>$$<br>式中,$p_i(c)$表示了像素$i$所属类别$c$的概率。通过上式可以构建每个像素产生的误差$m(c)$:</p><p>$$m_i(c)=\left {<br>\begin{array}{c}<br>1-p_i(c),\ \ if \ \ c=y^{*}_{i} \<br>p_i(c),\ \ \ \ \ \ \ otherwise<br>\end{array}<br>\right.<br>$$</p><p>可知,对于一张图像中所有像素则误差向量为$m(c)\in {0, 1}^p$,则可以建立关于$\Delta_{J_c}$的代理损失函数:<br>$$<br>loss(p(c))=\overline{\Delta_{J_c} }(m(c))<br>$$<br>当我们考虑整个数据集是,一般会使用mIoU进行度量,因此我们对上述损失也进行平均化处理,则定义的Lovász-Softmax损失函数为:<br>$$<br>loss(\pmb{p})=\frac{1}{|C|}\sum_{c\in C}\overline{\Delta_{J_c} }(m(c))<br>$$</p><p><strong>代码实现</strong></p><p>论文作者已经给出了Lovász-Softmax实现代码,并且有pytorch和tensorflow两种版本,并提供了使用demo。此处将针对多分类任务的Lovász-Softmax源码进行展示。</p><p><a href="https://github.com/bermanmaxim/LovaszSoftmax" target="_blank" rel="noopener">Lovász-Softmax实现链接</a></p><figure class="highlight python"><table><tr><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch<br><span class="hljs-keyword">from</span> torch.autograd <span class="hljs-keyword">import</span> Variable<br><span class="hljs-keyword">import</span> torch.nn.functional <span class="hljs-keyword">as</span> F<br><span class="hljs-keyword">import</span> numpy <span class="hljs-keyword">as</span> np<br><span class="hljs-keyword">try</span>:<br> <span class="hljs-keyword">from</span> itertools <span class="hljs-keyword">import</span> ifilterfalse<br><span class="hljs-keyword">except</span> ImportError: <span class="hljs-comment"># py3k</span><br> <span class="hljs-keyword">from</span> itertools <span class="hljs-keyword">import</span> filterfalse <span class="hljs-keyword">as</span> ifilterfalse<br> <br><span class="hljs-comment"># --------------------------- MULTICLASS LOSSES ---------------------------</span><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lovasz_softmax</span><span class="hljs-params">(probas, labels, classes=<span class="hljs-string">'present'</span>, per_image=False, ignore=None)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> Multi-class Lovasz-Softmax loss</span><br><span class="hljs-string"> probas: [B, C, H, W] Variable, class probabilities at each prediction (between 0 and 1).</span><br><span class="hljs-string"> Interpreted as binary (sigmoid) output with outputs of size [B, H, W].</span><br><span class="hljs-string"> labels: [B, H, W] Tensor, ground truth labels (between 0 and C - 1)</span><br><span class="hljs-string"> classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.</span><br><span class="hljs-string"> per_image: compute the loss per image instead of per batch</span><br><span class="hljs-string"> ignore: void class labels</span><br><span class="hljs-string"> """</span><br> <span class="hljs-keyword">if</span> per_image:<br> loss = mean(lovasz_softmax_flat(*flatten_probas(prob.unsqueeze(<span class="hljs-number">0</span>), lab.unsqueeze(<span class="hljs-number">0</span>), ignore), classes=classes)<br> <span class="hljs-keyword">for</span> prob, lab <span class="hljs-keyword">in</span> zip(probas, labels))<br> <span class="hljs-keyword">else</span>:<br> loss = lovasz_softmax_flat(*flatten_probas(probas, labels, ignore), classes=classes)<br> <span class="hljs-keyword">return</span> loss<br><br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">lovasz_softmax_flat</span><span class="hljs-params">(probas, labels, classes=<span class="hljs-string">'present'</span>)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> Multi-class Lovasz-Softmax loss</span><br><span class="hljs-string"> probas: [P, C] Variable, class probabilities at each prediction (between 0 and 1)</span><br><span class="hljs-string"> labels: [P] Tensor, ground truth labels (between 0 and C - 1)</span><br><span class="hljs-string"> classes: 'all' for all, 'present' for classes present in labels, or a list of classes to average.</span><br><span class="hljs-string"> """</span><br> <span class="hljs-keyword">if</span> probas.numel() == <span class="hljs-number">0</span>:<br> <span class="hljs-comment"># only void pixels, the gradients should be 0</span><br> <span class="hljs-keyword">return</span> probas * <span class="hljs-number">0.</span><br> C = probas.size(<span class="hljs-number">1</span>)<br> losses = []<br> class_to_sum = list(range(C)) <span class="hljs-keyword">if</span> classes <span class="hljs-keyword">in</span> [<span class="hljs-string">'all'</span>, <span class="hljs-string">'present'</span>] <span class="hljs-keyword">else</span> classes<br> <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> class_to_sum:<br> fg = (labels == c).float() <span class="hljs-comment"># foreground for class c</span><br> <span class="hljs-keyword">if</span> (classes <span class="hljs-keyword">is</span> <span class="hljs-string">'present'</span> <span class="hljs-keyword">and</span> fg.sum() == <span class="hljs-number">0</span>):<br> <span class="hljs-keyword">continue</span><br> <span class="hljs-keyword">if</span> C == <span class="hljs-number">1</span>:<br> <span class="hljs-keyword">if</span> len(classes) > <span class="hljs-number">1</span>:<br> <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">'Sigmoid output possible only with 1 class'</span>)<br> class_pred = probas[:, <span class="hljs-number">0</span>]<br> <span class="hljs-keyword">else</span>:<br> class_pred = probas[:, c]<br> errors = (Variable(fg) - class_pred).abs()<br> errors_sorted, perm = torch.sort(errors, <span class="hljs-number">0</span>, descending=<span class="hljs-literal">True</span>)<br> perm = perm.data<br> fg_sorted = fg[perm]<br> losses.append(torch.dot(errors_sorted, Variable(lovasz_grad(fg_sorted))))<br> <span class="hljs-keyword">return</span> mean(losses)<br><br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">flatten_probas</span><span class="hljs-params">(probas, labels, ignore=None)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> Flattens predictions in the batch</span><br><span class="hljs-string"> """</span><br> <span class="hljs-keyword">if</span> probas.dim() == <span class="hljs-number">3</span>:<br> <span class="hljs-comment"># assumes output of a sigmoid layer</span><br> B, H, W = probas.size()<br> probas = probas.view(B, <span class="hljs-number">1</span>, H, W)<br> B, C, H, W = probas.size()<br> probas = probas.permute(<span class="hljs-number">0</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>, <span class="hljs-number">1</span>).contiguous().view(<span class="hljs-number">-1</span>, C) <span class="hljs-comment"># B * H * W, C = P, C</span><br> labels = labels.view(<span class="hljs-number">-1</span>)<br> <span class="hljs-keyword">if</span> ignore <span class="hljs-keyword">is</span> <span class="hljs-literal">None</span>:<br> <span class="hljs-keyword">return</span> probas, labels<br> valid = (labels != ignore)<br> vprobas = probas[valid.nonzero().squeeze()]<br> vlabels = labels[valid]<br> <span class="hljs-keyword">return</span> vprobas, vlabels<br><br><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">xloss</span><span class="hljs-params">(logits, labels, ignore=None)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> Cross entropy loss</span><br><span class="hljs-string"> """</span><br> <span class="hljs-keyword">return</span> F.cross_entropy(logits, Variable(labels), ignore_index=<span class="hljs-number">255</span>)<br><br><span class="hljs-comment"># --------------------------- HELPER FUNCTIONS ---------------------------</span><br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">isnan</span><span class="hljs-params">(x)</span>:</span><br> <span class="hljs-keyword">return</span> x != x<br> <br><span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">mean</span><span class="hljs-params">(l, ignore_nan=False, empty=<span class="hljs-number">0</span>)</span>:</span><br> <span class="hljs-string">"""</span><br><span class="hljs-string"> nanmean compatible with generators.</span><br><span class="hljs-string"> """</span><br> l = iter(l)<br> <span class="hljs-keyword">if</span> ignore_nan:<br> l = ifilterfalse(isnan, l)<br> <span class="hljs-keyword">try</span>:<br> n = <span class="hljs-number">1</span><br> acc = next(l)<br> <span class="hljs-keyword">except</span> StopIteration:<br> <span class="hljs-keyword">if</span> empty == <span class="hljs-string">'raise'</span>:<br> <span class="hljs-keyword">raise</span> ValueError(<span class="hljs-string">'Empty mean'</span>)<br> <span class="hljs-keyword">return</span> empty<br> <span class="hljs-keyword">for</span> n, v <span class="hljs-keyword">in</span> enumerate(l, <span class="hljs-number">2</span>):<br> acc += v<br> <span class="hljs-keyword">if</span> n == <span class="hljs-number">1</span>:<br> <span class="hljs-keyword">return</span> acc<br> <span class="hljs-keyword">return</span> acc / n<br></code></pre></td></tr></table></figure><h3 id="4-8-参考链接"><a href="#4-8-参考链接" class="headerlink" title="4.8 参考链接"></a>4.8 参考链接</h3><p><a href="https://blog.csdn.net/lingzhou33/article/details/87901365" target="_blank" rel="noopener">语义分割的评价指标IoU</a></p><p><a href="https://blog.csdn.net/Biyoner/article/details/84728417" target="_blank" rel="noopener">医学图像分割常用的损失函数</a></p><p><a href="https://www.jianshu.com/p/0998e6560288" target="_blank" rel="noopener">What is “Dice loss” for image segmentation?</a></p><p><a href="https://pytorch.org/docs/stable/nn.html#loss-functions" target="_blank" rel="noopener">pytorch loss-functions</a></p><p><a href="https://sudeepraja.github.io/Submodular/" target="_blank" rel="noopener">Submodularity and the Lovász extension</a></p><h3 id="4-9-本章小结"><a href="#4-9-本章小结" class="headerlink" title="4.9 本章小结"></a>4.9 本章小结</h3><p>本章对各类评价指标进行介绍,并进行具体代码实践。</p>]]></content>
<summary type="html">
<h1 id="零基础入门语义分割-Task4-评价函数与损失函数"><a href="#零基础入门语义分割-Task4-评价函数与损失函数" class="headerlink" title="零基础入门语义分割-Task4 评价函数与损失函数"></a>零基础入门语义分割-Task4 评价函数与损失函数</h1><p>本章主要介绍语义分割的评价函数和各类损失函数。</p>
<h2 id="4-评价函数与损失函数"><a href="#4-评价函数与损失函数" class="headerlink" title="4 评价函数与损失函数"></a>4 评价函数与损失函数</h2><h3 id="4-1-学习目标"><a href="#4-1-学习目标" class="headerlink" title="4.1 学习目标"></a>4.1 学习目标</h3><ul>
<li>掌握常见的评价函数和损失函数Dice、IoU、BCE、Focal Loss、Lovász-Softmax;</li>
<li>掌握评价/损失函数的实践;</li>
</ul>
</summary>
<category term="计算机视觉" scheme="https://jackyin.space/tags/%E8%AE%A1%E7%AE%97%E6%9C%BA%E8%A7%86%E8%A7%89/"/>
<category term="datawhale" scheme="https://jackyin.space/tags/datawhale/"/>
</entry>
</feed>