-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
509 lines (331 loc) · 340 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
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>SaberDa的幻想乡</title>
<icon>https://www.gravatar.com/avatar/0a06c41b158eb3c87b3d334e94cd39a4</icon>
<subtitle> C++/ JS | 呐呐呐 | [email protected] | 没有什么胜利可言 挺住就意味着一切</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="https://saberda.github.io/"/>
<updated>2022-03-04T21:54:42.920Z</updated>
<id>https://saberda.github.io/</id>
<author>
<name>SaberDa</name>
<email>[email protected]</email>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>Tess4J for Java</title>
<link href="https://saberda.github.io/2022/03/04/Tess4J-for-Java/"/>
<id>https://saberda.github.io/2022/03/04/Tess4J-for-Java/</id>
<published>2022-03-04T18:27:00.000Z</published>
<updated>2022-03-04T21:54:42.920Z</updated>
<content type="html"><![CDATA[<p>近期因工作安排,开始接触OCR。在macOS环境下使用Tess4J这个OCR库时,踩了很多坑。</p><p>需记录下来</p><hr><h3 id="Tess4J简介"><a href="#Tess4J简介" class="headerlink" title="Tess4J简介"></a>Tess4J简介</h3><p>Tesseract-OCR支持中文识别,并且开源和提供全套的训练工具,是快速低成本开发的首选。而Tess4J则是Tesseract在Java PC上的应用。在英文和数字识别中性能还是不错的。</p><p>因为 tess4j.jar 中没有包含涉及到macOS的库,所以在项目中导入前,需要提前做一些配置。笔者没有试过在Windows或者Linux环境下使用Tess4J,所以暂时不知道其他系统上是否需要做相同的配置。</p><h3 id="macOS的本地配置"><a href="#macOS的本地配置" class="headerlink" title="macOS的本地配置"></a>macOS的本地配置</h3><p>首先需要通过 homebrew 安装 Tesseract 引擎。</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew install tesseract</span><br></pre></td></tr></table></figure><p>默认下的语言包含”eng”,”osd”,和”snum”,并不包含中文。如果需要检测中文,需要额外下载<a href="https://github.com/tesseract-ocr/tessdata" target="_blank" rel="noopener">语言包</a>。</p><p>安装成功之后执行下面的语句来获得当前安装的版本号。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">brew info tesseract</span><br></pre></td></tr></table></figure><p><img src="/2022/03/04/Tess4J-for-Java/1.png" alt=""></p><p>当前笔者安转的版本为 5.1.0。记住这个版本号,在之后的操作中会涉及到。如果按照笔者的流程配置时,记得后续步骤将版本号替换成自己安装的版本号。</p><p>下一步依次执行下列命令对 maven cached jar 文件。记住将命令中的 {username} 换成你自己的,以及替换自己本地版本的tesseract版本号。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">cd /Users/username/.m2/repository/net/sourceforge/tess4j/tess4j/5.1.0</span><br><span class="line">mkdir darwin</span><br><span class="line">jar uf tess4j-5.1.0.jar darwin/</span><br><span class="line"># 下个命令中的libtesseract.5.dylib的数字5,记得替换成本地版本的大版本号;比如本地版本为4.5.4,那就替换成libtesseract.4.dylib</span><br><span class="line">cp /usr/local/Cellar/tesseract/5.1.0/lib/libtesseract.5.dylib darwin/libtesseract.dylib</span><br><span class="line">jar uf tess4j-5.1.0.jar darwin/libtesseract.dylib</span><br><span class="line">jar tf tess4j-5.1.0.jar</span><br></pre></td></tr></table></figure><p>到此为止,Mac的本地环境配置结束。接下来进入项目配置。</p><hr><h3 id="项目配置"><a href="#项目配置" class="headerlink" title="项目配置"></a>项目配置</h3><h4 id="下载源码包"><a href="#下载源码包" class="headerlink" title="下载源码包"></a>下载源码包</h4><p>首先去<a href="http://tess4j.sourceforge.net/" target="_blank" rel="noopener">官网提供的下载地址</a>下载源码,其中包含其源码,JAR 文件和 DLL 文件等。</p><p><img src="/2022/03/04/Tess4J-for-Java/2.png" alt=""></p><h4 id="创建项目并添加配置"><a href="#创建项目并添加配置" class="headerlink" title="创建项目并添加配置"></a>创建项目并添加配置</h4><p>在创建完Java项目后,将上图中的 dist 文件夹和 lib 文件夹中的所有jar文件加入项目。</p><p>并将 tessdata文件夹放到项目根目录下。任何有关其他语言的traineddata文件都放在该文件夹下。</p><p>文件移植后的大致目录结构为下图所示:</p><p><img src="/2022/03/04/Tess4J-for-Java/3.png" alt=""></p><h4 id="添加依赖"><a href="#添加依赖" class="headerlink" title="添加依赖"></a>添加依赖</h4><p>在 pom.xml 中添加 dependency</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><dependency></span><br><span class="line"><groupId>net.sourceforge.tess4j</groupId></span><br><span class="line"><artifactId>tess4j</artifactId></span><br><span class="line"><version>5.1.0</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure><h4 id="设置Eclipse"><a href="#设置Eclipse" class="headerlink" title="设置Eclipse"></a>设置Eclipse</h4><p>我们需要将dist文件夹下的tess4j.jar导入到Eclipse中。</p><p>首先我们右键我们的项目,选择Build Path->Configure Build Path。</p><p>然后在Classpath中选择Add External JARs,将tess4j.jar导入到项目中。</p><p>关于更详细的将外部jar导入Eclipse可以查看<a href="https://www.edureka.co/community/4028/how-to-import-a-jar-file-in-eclipse" target="_blank" rel="noopener">这篇文章</a>。</p><p><img src="/2022/03/04/Tess4J-for-Java/4.png" alt=""></p><p>关于其他IDE的设置可以查阅<a href="http://tess4j.sourceforge.net/tutorial/" target="_blank" rel="noopener">官方给出的文档</a>。</p><h4 id="代码展示"><a href="#代码展示" class="headerlink" title="代码展示"></a>代码展示</h4><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">import java.io.File;</span><br><span class="line">import net.sourceforge.tess4j.ITesseract;</span><br><span class="line">import net.sourceforge.tess4j.Tesseract;</span><br><span class="line">import net.sourceforge.tess4j.TesseractException;</span><br><span class="line"></span><br><span class="line">public class OCRHelper {</span><br><span class="line"> </span><br><span class="line">public static String ocrHelper(String filePath) throws TesseractException {</span><br><span class="line"></span><br><span class="line">System.out.println("OCR Starting ...");</span><br><span class="line"></span><br><span class="line">ITesseract instance = new Tesseract();</span><br><span class="line"></span><br><span class="line">// Set language</span><br><span class="line">instance.setDatapath("your path of tessdata folder");</span><br><span class="line">instance.setLanguage("eng");</span><br><span class="line"></span><br><span class="line">// Select your file path</span><br><span class="line">File img = new File(filePath);</span><br><span class="line"></span><br><span class="line">// Output result</span><br><span class="line">String result = instance.doOCR(img);</span><br><span class="line">System.out.println("result: " + result);</span><br><span class="line"></span><br><span class="line">return result;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>上段代码只是最基础的调用。<a href="https://www.jianshu.com/p/b5e5b3fbc17c" target="_blank" rel="noopener">这篇文章</a>记录了更多的使用情况。</p><p>这里给出一个比较复杂的图片验证其功能性。</p><p><img src="/2022/03/04/Tess4J-for-Java/5.png" alt=""></p><p>最终的输出为</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">f.‘"" ‘ ~rT1-r-—l, T.</span><br><span class="line">ElelL —</span><br><span class="line">,, 7 ‘1',</span><br><span class="line">vs 6: it ~ .</span><br><span class="line">a; -* ““‘ rm:-</span><br><span class="line">f.— ( ‘ —</span><br><span class="line">43; vIn</span><br><span class="line">SEMI FINALl! - NaVi vs 62 -</span><br><span class="line">HIGHLIGHTS - IEM Katowice I CSGO</span><br><span class="line">MaltCSGOChanne‘</span><br></pre></td></tr></table></figure><p>可见图片上的一些特殊字体和logo会比较影响Tess4J的运行,但是对于正常的文字是可以准确识别出来的(比如上图下方的视频名字)。</p><hr><h3 id="本人在设置环境中踩的一些坑"><a href="#本人在设置环境中踩的一些坑" class="headerlink" title="本人在设置环境中踩的一些坑"></a>本人在设置环境中踩的一些坑</h3><h3 id="最主要的一个"><a href="#最主要的一个" class="headerlink" title="最主要的一个"></a>最主要的一个</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Unable to load library 'tesseract': Native library (darwin/libtesseract.dylib) not found in resource path</span><br></pre></td></tr></table></figure><p>报这个错误信息是因为本地的 tesseract环境没有设置正确。按照我之前的步骤重新走一遍基本可以解决。</p><p>另,这个<a href="https://www.deathearth.com/1154.html" target="_blank" rel="noopener">文章</a>记录了更多的冲突问题。</p>]]></content>
<summary type="html">
<p>近期因工作安排,开始接触OCR。在macOS环境下使用Tess4J这个OCR库时,踩了很多坑。</p>
<p>需记录下来</p>
<hr>
<h3 id="Tess4J简介"><a href="#Tess4J简介" class="headerlink" title="Tess4J简介"></a>Tess4J简介</h3><p>Tesseract-OCR支持中文识别,并且开源和提供全套的训练工具,是快速低成本开发的首选。而Tess4J则是Tesseract在Java PC上的应用。在英文和数字识别中性能还是不错的。</p>
<p>因为 tess4j.jar 中没有包含涉及到macOS的库,所以在项目中导入前,需要提前做一些配置。笔者没有试过在Windows或者Linux环境下使用Tess4J,所以暂时不知道其他系统上是否需要做相同的配置。</p>
<h3 id="macOS的本地配置"><a href="#macOS的本地配置" class="headerlink" title="macOS的本地配置"></a>macOS的本地配置</h3><p>首先需要通过 homebrew 安装 Tesseract 引擎。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="Java" scheme="https://saberda.github.io/tags/Java/"/>
</entry>
<entry>
<title>转移博客教程</title>
<link href="https://saberda.github.io/2021/07/01/%E8%BD%AC%E7%A7%BB%E5%8D%9A%E5%AE%A2%E6%95%99%E7%A8%8B/"/>
<id>https://saberda.github.io/2021/07/01/转移博客教程/</id>
<published>2021-07-02T02:27:01.000Z</published>
<updated>2021-07-02T02:40:16.891Z</updated>
<content type="html"><![CDATA[<p>本人于前些天将hexo博客转移到了新的电脑之中,记录下步骤过程。<br>此过程只针对于Mac。</p><hr><h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>基础的环境配置要求:</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">git</span><br><span class="line">nodeJS</span><br><span class="line">npm</span><br></pre></td></tr></table></figure><h2 id="转移步骤"><a href="#转移步骤" class="headerlink" title="转移步骤"></a>转移步骤</h2><h2 id="一、将原博客文件夹所有内容转移到新电脑上"><a href="#一、将原博客文件夹所有内容转移到新电脑上" class="headerlink" title="一、将原博客文件夹所有内容转移到新电脑上"></a>一、将原博客文件夹所有内容转移到新电脑上</h2><p>主要的就这以下几个文件夹</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">scaffolds/</span><br><span class="line">source/</span><br><span class="line">themes/</span><br><span class="line">package.json</span><br><span class="line">_config.yml</span><br></pre></td></tr></table></figure><h2 id="二、安装环境"><a href="#二、安装环境" class="headerlink" title="二、安装环境"></a>二、安装环境</h2><p>首先需要进入博客目录下,然后依次执行以下命令。有可能因为权限问题报错,如果出现那就在前面加上sudo</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">npm install</span><br><span class="line">npm install hexo-deployer-git --save</span><br><span class="line"></span><br><span class="line">npm install hexo-generator-feed --save</span><br><span class="line">npm install hexo-generator-sitemap --save</span><br></pre></td></tr></table></figure><h2 id="三、本地测试"><a href="#三、本地测试" class="headerlink" title="三、本地测试"></a>三、本地测试</h2><p>执行完第二步中的命令后,博客转移就基本完成了。这时我们可以通过下面的命令进行测试</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">hexo s</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<p>本人于前些天将hexo博客转移到了新的电脑之中,记录下步骤过程。<br>此过程只针对于Mac。</p>
<hr>
<h2 id="环境准备"><a href="#环境准备" class="headerlink" title="环境准备"></a>环境准备</h2><p>基础的环境配置要求:</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="hexo" scheme="https://saberda.github.io/tags/hexo/"/>
</entry>
<entry>
<title>海外网易云</title>
<link href="https://saberda.github.io/2021/02/04/%E6%B5%B7%E5%A4%96%E7%BD%91%E6%98%93%E4%BA%91/"/>
<id>https://saberda.github.io/2021/02/04/海外网易云/</id>
<published>2021-02-04T16:39:51.000Z</published>
<updated>2021-02-04T16:46:40.000Z</updated>
<content type="html"><![CDATA[<pre><code>此教程仅针对海外windows用户,若用于商业用途,一切与本文无关。</code></pre><p><strong>第一步,安装代理</strong></p><p>以 <code>管理员身份</code> 打开 <code>Powershell</code>,Windows 10 快捷入口:<code>Win + X</code> - <code>Windows Powershell(管理员)(A)</code></p><a id="more"></a><p><img src="/2021/02/04/海外网易云/1.PNG" alt=""></p><p>复制以下代码,右键粘贴到命令行回车,打开安装菜单。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force</span><br><span class="line">Invoke-Expression -Command (Invoke-WebRequest -UseBasicParsing -Uri https://bit.ly/2RYvE3p).Content</span><br></pre></td></tr></table></figure><p><img src="https://s1.ax1x.com/2020/10/14/04GEUf.png" alt="04GEUf.png"></p><ul><li>随后选择 <code>1</code> 即安装。</li><li>安装完毕后选择 <code>3</code> 运行。</li><li>如需添加开机自启,则执行 <code>7</code>。</li><li>最后输入 <code>0</code> 退出。</li></ul><p><img src="https://s1.ax1x.com/2020/10/14/04GV58.png" alt="04GV58.png"></p><p><strong>第二步,设置代理</strong></p><p>打开网易云音乐客户端,进入设置页面,设置自定义代理</p><ul><li><p>自定义代理 :填写服务器地址和端口号</p></li><li><p>代理服务器地址:127.0.0.1 (推荐本机搭建,速度快)</p></li><li><p>代理服务器端口:6666</p><p><img src="https://s1.ax1x.com/2020/10/14/04GePS.png" alt="04GePS.png"></p><p>如使用一段时间后无法解锁,则需要重新执行命令,选择 <code>5</code> 更新。</p></li></ul>]]></content>
<summary type="html">
<pre><code>此教程仅针对海外windows用户,若用于商业用途,一切与本文无关。
</code></pre><p><strong>第一步,安装代理</strong></p>
<p>以 <code>管理员身份</code> 打开 <code>Powershell</code>,Windows 10 快捷入口:<code>Win + X</code> - <code>Windows Powershell(管理员)(A)</code></p>
</summary>
<category term="LIFE" scheme="https://saberda.github.io/categories/LIFE/"/>
</entry>
<entry>
<title>LeetCode刷题记录-Part3</title>
<link href="https://saberda.github.io/2020/07/05/LeetCode-part3/"/>
<id>https://saberda.github.io/2020/07/05/LeetCode-part3/</id>
<published>2020-07-05T19:26:32.000Z</published>
<updated>2020-07-05T19:47:28.000Z</updated>
<content type="html"><![CDATA[<p>本篇是该系列第三篇,<a href="http://www.saberismywife.com/2020/03/15/LeetCode刷题记录-Part2/" target="_blank" rel="noopener">第一篇</a>和<a href="http://www.saberismywife.com/2020/03/15/LeetCode刷题记录-Part2/" target="_blank" rel="noopener">第二篇</a>的链接在此。本篇将介绍Trie Tree 和 Union Find。两者都是很常见的树状数据结构。</p><p>这篇应该是LeetCode刷题系列的最后一篇,本来还打算包含 segment tree 和 topological sort 的介绍,但是后来发现这两个部分的题目数量是真的少,没必要写出来。至于之前还计划整理一篇位运算的相关操作,但是想了想这东西在工作中的实际运用,也被删除了。</p><p>接下来估计会开 system design 系列。这东西在北美找全职也是要准备的,之前还自以为刷题什么的就可以了。</p><a id="more"></a><h2 id="什么是Trie-?"><a href="#什么是Trie-?" class="headerlink" title="什么是Trie ?"></a>什么是Trie ?</h2><ul><li>Trie 是一种树状的数据结构,它的每个结点通常上存储单个的字母</li><li>Trie 是一种有序的树数据结构,每次遍历它的分支用户都会得到一个字符串,是一种哈希树的变种</li><li>典型应用<br> 既然 Trie 又被称为字典树,它的主要应用在于统计和排序大量的字符串,比如在搜索引擎系统中进行词频统计。</li></ul><h2 id="基本结构"><a href="#基本结构" class="headerlink" title="基本结构"></a>基本结构</h2><p>这里采取最常见的结构,在实际做题的过程中,要根据题意进行细微的变化,比如将 bool 类型的判断符换成 string 类型的标识符,或者改变存储数组的大小。下文中提到的所有 Trie 结构都是基于该最基础的结构。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">struct TrieNode {</span><br><span class="line">bool isWord;</span><br><span class="line">TrieNode *next[26];</span><br><span class="line">TrieNode() : isWord(false) {</span><br><span class="line">memset(next, NULL, sizeof(next));</span><br><span class="line">}</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>一个典型的 Trie 结构包含一个为空的根结点,和对其他所有结点的引用。</p><p>至于引用的具体数量,取决于当前要遍历的值的总数。举个例子,比如当前 Trie 表示仅包含小写字母的字符串。然后从 “a” 到 “z” 有26个不同的字母,那么它的引用个数就是26。如果是专门储存二进制变量的 Bitwise Trie,那么的引用只需要两个,分别为 0 和 1。</p><hr><h2 id="Trie-的每个结点都包含什么呢?"><a href="#Trie-的每个结点都包含什么呢?" class="headerlink" title="Trie 的每个结点都包含什么呢?"></a>Trie 的每个结点都包含什么呢?</h2><p>Trie 的每个结点包含两部分:</p><ul><li>一个存储对其他结点引用的数组</li><li>一个 bool 值,可以称其为 Leaf。(需要注意的是,这里的 Leaf 是用于检查当前字符串是否在该结点结束,而不是结点本身是否是叶子结点)</li></ul><hr><h2 id="实例"><a href="#实例" class="headerlink" title="实例"></a>实例</h2><p>下面通过一步步构建出一个 Trie 树来帮助大家更好的理解。</p><pre><code>List = {"apple", "app", "abide", "ball", "bat"};Note: All the string in the list contains lowercase letters from 'a' to 'z'.</code></pre><p><img src="/2020/07/05/LeetCode-part3/Trie1.png" alt=""></p><p><strong>第一步:apple 被添加到树中。</strong><br>上图中的第一个结点是根结点,其本身值为空。<br>现在根结点已经有了一个引用 ‘a’,’a’ 结点本身只有一个对 ‘p’ 的引用,其他的为空。依次类推余下的结点。</p><p><img src="/2020/07/05/LeetCode-part3/Trie2.png" alt=""></p><p><strong>第二步:将 app 添加到树中。</strong><br>从根结点开始,判断当前的引用是否存在。如果存在,那么移动到该引用指向的结点那里。否则,需要创建一个新的结点,并将引用指向新的结点。<br>当前结点的所有子结点都具有与该结点关联的字符串的公共前缀。</p><p><img src="/2020/07/05/LeetCode-part3/Trie3.png" alt=""></p><p><strong>第三步:将 abide 添加到树中。</strong><br>abide 所代表的分支与 apple 分支共享一个公共前缀 ‘a’</p><p><img src="/2020/07/05/LeetCode-part3/Trie4.png" alt=""></p><p><strong>第四步:将 ball 添加到树中。</strong><br>因为当前根结点没有对 ‘b’ 的引用,所以我们创建一个新的分支。</p><p><img src="/2020/07/05/LeetCode-part3/Trie5.png" alt=""></p><p><strong>第五步:将 bat 添加到树中。</strong><br>上图即为根据当前输入字符串列表所建立的最终的 Trie。</p><hr><h2 id="具体代码实现:"><a href="#具体代码实现:" class="headerlink" title="具体代码实现:"></a>具体代码实现:</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><span class="line">class TrieTree {</span><br><span class="line">private:</span><br><span class="line">struct TrieNode {</span><br><span class="line">bool isWord;</span><br><span class="line">TrieNode *next[26];</span><br><span class="line">TrieNode() : isWord(false) {</span><br><span class="line">memset(next, NULL, sizeof(next));</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line">TrieNode *root;</span><br><span class="line"></span><br><span class="line">// 插入过程</span><br><span class="line">// Insert</span><br><span class="line">void buildTree(string s) {</span><br><span class="line">TrieNode *node = root;</span><br><span class="line">for (auto c : s) {</span><br><span class="line">if (!node->next[c - 'a']) node->next[c - 'a'] = new TrieNode();</span><br><span class="line">node = node->next[c - 'a'];</span><br><span class="line">}</span><br><span class="line">node->isWord = true;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// 查找过程</span><br><span class="line">// Search</span><br><span class="line">bool findWord(string s) {</span><br><span class="line">TrieNode *node = root;</span><br><span class="line">for (auto c : s) {</span><br><span class="line">if (!node->next[c - 'a']) return false;</span><br><span class="line">node = node->next[c - 'a'];</span><br><span class="line">}</span><br><span class="line">return node->isWord;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">public:</span><br><span class="line"></span><br><span class="line">TrieTree() {</span><br><span class="line">root = new TrieNode();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">// Do other things</span><br><span class="line"></span><br><span class="line">};</span><br></pre></td></tr></table></figure><hr><h2 id="例题分析"><a href="#例题分析" class="headerlink" title="例题分析"></a>例题分析</h2><p><a href="https://leetcode.com/problems/implement-magic-dictionary/" target="_blank" rel="noopener">Leetcode 676</a></p><p>这道题是一个典型的使用 Trie Tree 解决的问题。</p><p><img src="/2020/07/05/LeetCode-part3/676.jpg" alt=""></p><p>这道题我们首先需要依据输入的字符串数组建树。然后对之后输入的字符串进行处理,当每次仅变换一个字母时,能否在树中查找到对应的单词,如果可以返回 true,否则返回 false。</p><p><img src="/2020/07/05/LeetCode-part3/676code.jpeg" alt=""></p><p><a href="https://github.com/SaberDa/LeetCode/blob/master/C%2B%2B/676-implenmentMagicDictionart.cpp" target="_blank" rel="noopener">源码</a>。</p><p>还有一些使用 Trie 的变种。<a href="https://leetcode.com/problems/maximum-xor-of-two-numbers-in-an-array/" target="_blank" rel="noopener">LeetCode 421</a> 是使用了 Bitwise Trie,<a href="https://leetcode.com/problems/camelcase-matching/" target="_blank" rel="noopener">LeetCode 1023</a> 则是在初始化树的结构时需要考虑大写和小写的情况。</p><hr><h2 id="Trie-的优点"><a href="#Trie-的优点" class="headerlink" title="Trie 的优点"></a>Trie 的优点</h2><ul><li>Trie Tree 类似于 HashMap,但是在时间和空间上有一些变化。<br>在 Trie 中搜索一个字符串的时间复杂度为 O(m),其中 m 表示带搜索字符串长度。</li><li>相比于哈希表,Trie 避免了碰撞冲突。</li><li>并且 Trie 本身不需要任何的哈希计算函数</li><li>Trie在自动补全功能上有着显著的作用。</li></ul><h2 id="时间复杂度分析"><a href="#时间复杂度分析" class="headerlink" title="时间复杂度分析"></a>时间复杂度分析</h2><ul><li>最坏的情况下,建树为 O(mn)。其中 m 是最长的字符串,n 是字符串的总数。时间复杂度取决于 Trie 所包含的字符串数量以及它们的长度</li><li>Trie 执行插入、搜索、删除需要的时间都是 O(m),其中 m 是待操作单词的长度</li></ul><h2 id="空间复杂度分析"><a href="#空间复杂度分析" class="headerlink" title="空间复杂度分析"></a>空间复杂度分析</h2><ul><li>O(kN),其中 k 是所有索引的数量(包含为空的索引),N 是树中的结点个数</li></ul><hr><h2 id="Union-Find-并查集"><a href="#Union-Find-并查集" class="headerlink" title="Union Find 并查集"></a>Union Find 并查集</h2><p>Union Find 简单来讲就是解决图的连通性问题。并广泛应用于解决集合问题,比如判断某个元素是否属于一个集合,或者两个元素是否属于同一个集合,或者求解集合的个数等等。</p><hr><h2 id="主要操作"><a href="#主要操作" class="headerlink" title="主要操作"></a>主要操作</h2><ul><li>Find<br>确定元素属于哪一个子集,或判断两个元素是否属于同一子集</li><li>Union<br>将两个子集合并成一个集合</li></ul><p>简单来讲,find 操作是查找当前两个元素的公共祖先;Union 操作是修改元素的root。</p><p>在LeetCode中具体应用挺八股文的,只要你分析出这道题可以使用 Union Find ,那么代码的大致结构都是差不多的。</p><h2 id="一个简单的模板"><a href="#一个简单的模板" class="headerlink" title="一个简单的模板"></a>一个简单的模板</h2><p>该模板为了方便大家理解,就不使用泛型了。采取题目中出现次数比较频繁的int型,并将 Union 操作合并到了主要操作中。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line">int find(vector<int>& parents, int c) {</span><br><span class="line">return (parents[c] == c) ? parents[c] ? find(parents, parents[c]);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">int doSomething(vector<vector<int>> nums, ...) {</span><br><span class="line">int m = nums.size();</span><br><span class="line">// The vector size must be the size + 1</span><br><span class="line">vector<int> parents(m + 1);</span><br><span class="line">// Assignment value to vector from 0 to m</span><br><span class="line">iota(parents.begin(), parents.end(), 0);</span><br><span class="line">for (auto it : nums) {</span><br><span class="line">// This is the Union() operation</span><br><span class="line">int root1 = find(parents, it[0]); // Find the root1</span><br><span class="line">int root2 = find(parents, it[1]); // Find the root2</span><br><span class="line">// If root1 == root2, do operations according to problem</span><br><span class="line">// Else if root1 != root2, put the root2 as the value of parents[root1]</span><br><span class="line">if (root1 != root2) parents[root1] = root2;</span><br><span class="line">}</span><br><span class="line">// Do other operations</span><br><span class="line">}</span><br></pre></td></tr></table></figure><hr><h2 id="例题分析-1"><a href="#例题分析-1" class="headerlink" title="例题分析"></a>例题分析</h2><p>以 <a href="https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/" target="_blank" rel="noopener">LeetCode 323</a> 为例,这道题以图形化的方式帮助大家更好的理解 Union Find 问题。</p><p><img src="/2020/07/05/LeetCode-part3/323.png" alt=""></p><p>这道题的意思是让我们根据输入的结点,找出一共有几个联通子图。</p><p>比如在 Example 1 中,先将 0 和 1 相连,然后讲 2 和 1 相连。这就是合并集合的操作。</p><p><img src="/2020/07/05/LeetCode-part3/323code.png" alt=""></p><p><a href="https://github.com/SaberDa/LeetCode/blob/master/C%2B%2B/323-numberOfConnectedComponentsInAnUndirectedGraph.cpp" target="_blank" rel="noopener">源码</a>。</p><hr><p>Union Find 另一个比较典型的操作就是判断森林中是否存在环,比如 <a href="https://leetcode.com/problems/graph-valid-tree/" target="_blank" rel="noopener">LeetCode 261</a> 。</p><p>这几道题都是比较典型的应用,还有一些变形,需要我们处理 parent 数组的大小,或者对 Union 操作进行一些变形。比如 <a href="https://leetcode.com/problems/satisfiability-of-equality-equations/" target="_blank" rel="noopener">LeetCode 990</a> 就是一道需要我们变换 parent 数组的题目,这道题也可以理解为寻找环形 DFS。</p><p>一些比较复杂的 DFS 问题,如果能将其转换换成 Union Find 来处理,则会大大降低难度,比如 <a href="https://leetcode.com/problems/surrounded-regions/" target="_blank" rel="noopener">LeetCode 130</a>,这道题是个典型的 DFS 问题,但是我们可以使用一个 dummy 结点当做所有 O 值的祖先结点,添加动态连通性,这样就可以把二维坐标映射到一维坐标,降低难度。</p><hr><p>Union Find 操作的具体应用还是挺套路化的,感觉和 topological sort 差不多,两者都是使用起来模板化。但是需要针对具体题目进行变换。</p><p>Union Find 还有很多针对它本身的优化,这里就不一一讲解了,毕竟只是针对刷题,大家有兴趣的可以自行去了解。</p><hr><h2 id="后记"><a href="#后记" class="headerlink" title="后记"></a>后记</h2><p>笔者现在刷题数量已经400多了,已经开始有条件的按类型反复刷了。刷题这个过程主要是让你思考相关题目的主要思路,比如看到求和(xSUM)问题,自然而然的就想到双指针;树的同层遍历,那就先上deque。诸如此类。</p><p>刷题的目的不是让大家在面试中碰到原题,当然如果碰到原题那自然最好,而是去分析这个题目的目的,去联想这道题的 follow up。当然这都是老生常谈的问题。这里并不是说大量刷题是没用的、或者是浪费时间的,肌肉记忆还是需要的。有些题可能第一次见时一脸懵逼不知从哪里下手,但是见得多了就自然掌握了。</p><p>这篇文章总结的两种数据结构应用的大致模板,都是相关类型的题刷够一定数量上自然而然掌握的。类似的还有链表的翻转、二维矩阵的搜索等,都是那个套路,唯一不同的就是需要分析题意来判断边界。</p><p>至于自身的遗忘问题,这是必然的。但是当我们见过一道之前做过的题目时,不能只想起来“这道题我做过”,而是要想起来“这道题我记得是用XXX方法来做的”,所以说如果大家每次都是第一种反应的话,建议每做完一道题后,都对类似的相关题目进行 BFS 一遍(LeetCode的每道题下面基本都有 “similar question”),这些题目可以不做,但是一定要分析为什么和之前的题目是相似的,如果我使用之前题目的解法,我能不能解答出这道题。</p>]]></content>
<summary type="html">
<p>本篇是该系列第三篇,<a href="http://www.saberismywife.com/2020/03/15/LeetCode刷题记录-Part2/" target="_blank" rel="noopener">第一篇</a>和<a href="http://www.saberismywife.com/2020/03/15/LeetCode刷题记录-Part2/" target="_blank" rel="noopener">第二篇</a>的链接在此。本篇将介绍Trie Tree 和 Union Find。两者都是很常见的树状数据结构。</p>
<p>这篇应该是LeetCode刷题系列的最后一篇,本来还打算包含 segment tree 和 topological sort 的介绍,但是后来发现这两个部分的题目数量是真的少,没必要写出来。至于之前还计划整理一篇位运算的相关操作,但是想了想这东西在工作中的实际运用,也被删除了。</p>
<p>接下来估计会开 system design 系列。这东西在北美找全职也是要准备的,之前还自以为刷题什么的就可以了。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="LeetCode" scheme="https://saberda.github.io/tags/LeetCode/"/>
</entry>
<entry>
<title>从代码角度分析动森大头菜价格走势</title>
<link href="https://saberda.github.io/2020/04/20/%E5%8A%A8%E6%A3%AE%E5%A4%A7%E5%A4%B4%E8%8F%9C%E4%BB%B7%E6%A0%BC%E5%88%86%E6%9E%90/"/>
<id>https://saberda.github.io/2020/04/20/动森大头菜价格分析/</id>
<published>2020-04-20T04:09:18.000Z</published>
<updated>2020-04-20T06:00:27.000Z</updated>
<content type="html"><![CDATA[<p>首先感谢进行解包的大佬<a href="https://twitter.com/_Ninji/status/1244818665851289602?s=20" target="_blank" rel="noopener">Ninji</a>。本文只是对解包代码进行粗略的解读并加以分析。</p><p>解包代码大体可分为三个阶段,分别是随机数生成阶段、价格预测阶段和价格计算阶段。</p><h2 id="随机数生成阶段–梅森旋转算法"><a href="#随机数生成阶段–梅森旋转算法" class="headerlink" title="随机数生成阶段–梅森旋转算法"></a>随机数生成阶段–梅森旋转算法</h2><p>梅森旋转算法,Mersenne twister,简写为MT。在C++11中的随机数库中,有一个随机数生成引擎函数 std::mt19937,这里的mt就是梅森旋转算法的简写。</p><a id="more"></a><p>明明是介绍大头菜走势的文章,怎么就突然间跑到一个算法上了呢?因为这个算法是任天堂主要使用的随机数生成算法,也是目前主流的生成随机数算法。</p><p>随机数生成算法虽然说是用来生成随机数的,但是其生成的也只不过是伪随机数。为什么说是“伪”随机数呢?相信有些计算机编程基础的同学都知道,计算机本身并不能产生随机数,只能通过一组循环节很长的数字串来伪造随机。同时,在统计学中,伪随机数的概念为“统计学伪随机性值的是在给定的随机比特流样本中,1的数量大致等于0的数量,同理,00, 01, 10, 11 四者数量大致相等。类似的标准被称为统计学随机性。满足这类要求的数字在人类”一眼看上去“是随机的。</p><p>拿笔者比较熟悉的C++举例,其库函数中的rand函数,只是根据当时提供的种子计算出来的一组循环节很长的数。只要两次提供的种子是一样的,那么rand函数生成的随机数也是一样的。至于这个有多长呢?可以看rand的实现,如下图所示。</p><p><img src="/2020/04/20/动森大头菜价格分析/rand.png" alt=""></p><p>然而理想中的随机数,理应是与之前生成的数字没有任何关联,那么在计算机中对应的就是产生随机数的函数应该不需要调用任何参数(因为一旦调用了参数,其生成的随机数就与参数有关)。这点在目前计算机技术上是无法实现的。因为计算机代表的特性是有逻辑的可推理的。</p><p>好了有些说远了,梅森旋转算法是目前最普遍的优质随机数生成算法。梅森旋转算法使用的循环节是 2<sup>19937</sup>-1。这个数字叫梅森素数。梅森旋转算法之所以是优质随机数生成算法,是因为它的循环节是梅森素数,在一定程度下能保证生成的数字平均分布。</p><p>介绍完梅森之后开始简单讲讲旋转。这里笔者思考了很久如何展开,最后还是决定简单的略过。如果详细的对其讲解,其文字量可以完全单开另一篇文章进行介绍,有失偏颇。这里只简简单单概述下旋转算法的大致流程:</p><ul><li>第一阶段:初始化,获得基础的梅森旋转链</li><li>第二阶段:对于旋转链进行旋转算法</li><li>第三阶段:对于旋转算法所得到的结果进行处理</li></ul><p>如果各位看官有兴趣对梅森旋转算法进行深入的了解,笔者这里推荐几个当时自己觉得介绍的比较详细的博文链接:</p><p><a href="https://en.wikipedia.org/wiki/Mersenne_Twister" target="_blank" rel="noopener">梅森旋转算法Wiki</a></p><p><a href="https://liam.page/2018/01/12/Mersenne-twister/" target="_blank" rel="noopener">谈谈梅森旋转:算法及其爆破</a></p><p>这里不建议读者去查阅相关的中文Wiki,因为相对于英文Wiki,中文版Wiki这部分内容少得太多。</p><p><img src="/2020/04/20/动森大头菜价格分析/cabbageInit.png" alt=""></p><p>上图即大头菜随机数算法的第一阶段,初始化梅森旋转链。</p><p><img src="/2020/04/20/动森大头菜价格分析/rotate.png" alt=""></p><p>上图为大头菜随机数算法的第二阶段,开始对旋转链进行旋转算法。这里任天堂旋转的位数和原梅森旋转算法有些出入,应该是任天堂的算法工程师进行了一些针对的优化。</p><p><img src="/2020/04/20/动森大头菜价格分析/rate.png" alt=""></p><p>上图为大头菜随机数算法的最后一步,使用之前随机数算法生成随机的价格比率。后面计算部分就使用这里生成的随机价格比率乘上初始的购入大头菜价格,得到最终的售价。</p><hr><h2 id="价格预测阶段"><a href="#价格预测阶段" class="headerlink" title="价格预测阶段"></a>价格预测阶段</h2><p>这作动森相比于上一作,大头菜价格增加了一个变数,就是你上周的走势会影响这周的走势。这一点我查阅了很多3DS论坛在17年前后发表的有关价格预测的帖子,那些帖子只提及了比例,而没有上周的走势因素。</p><p>在这作动森火了之后,有很多营销号和不良游戏攻略制作者直接用上一作的大头菜价格预测规则来预测本作的走势,这里笔者需要提醒下大家,如果你当前使用的价格预测器没有询问你上一周走势的话,最好还是换一个带有上一周走势因素的计算器吧。</p><p><img src="/2020/04/20/动森大头菜价格分析/pattern.png" alt=""></p><p>简单解释下代码。</p><p>第一行是随机生成周日上午的曹卖售价。第二行生成的是你这周大头菜的价格bias。根据你生成的这个bias,确定你这周的价格走势。</p><p>然后分析下每个case代表的含义。</p><p><strong>case0:</strong></p><p>这里对应的走势是随机波形,价格变化为:</p><p>高 -> 低 -> 高 -> 低 -> 高</p><p>价格频繁的上下浮动,即使是价格突然下降也有可能立马回升。</p><p><img src="/2020/04/20/动森大头菜价格分析/波动.png" alt=""></p><p><strong>case1:</strong></p><p>这里对应的走势是四期型,前几日价格会持续下跌,接着会在某一天突然价格上涨,并在一天后达到峰值,然后逐渐降低。</p><p><img src="/2020/04/20/动森大头菜价格分析/四期.png" alt=""></p><p><strong>case2:</strong></p><p>这里对应的走势是递减型,价格会持续下跌。</p><p><img src="/2020/04/20/动森大头菜价格分析/递减.png" alt=""></p><p><strong>case3:</strong></p><p>这里对应的走势是三期型,前几日价格会持续下跌,接着在某一天突然上涨。</p><p><img src="/2020/04/20/动森大头菜价格分析/三期.png" alt=""></p><p>从代码上可以看出来,无论你上周是怎样的走势,这周为持续下跌型的概率最高只为20%。</p><hr><h2 id="价格计算阶段"><a href="#价格计算阶段" class="headerlink" title="价格计算阶段"></a>价格计算阶段</h2><p><strong>针对波形进行计算</strong></p><p><img src="/2020/04/20/动森大头菜价格分析/code波形.png" alt=""></p><p>从代码可知,一旦你的走势确认为波形,那你需要注意当你的售价为买价的1.4倍左右时,就是出手的好时机了,因为再高也不会高于这个价了。</p><p><strong>针对四期型计算</strong></p><p><img src="/2020/04/20/动森大头菜价格分析/code四期.png" alt=""></p><p>从代码可知,如果你的走势是四期型,那么你就可以适当的赌一把,因为四期型的最高价可以高达买入价的6倍左右。并且从代码中可以看出来,最高价出现的日子是价格开始回升的第三次开盘,这里是重点,千万不要错过这个时间段。</p><p><strong>针对递减型计算</strong></p><p><img src="/2020/04/20/动森大头菜价格分析/code递减.png" alt=""></p><p>这部分代码没什么好讲的,很简单,比率持续下降。</p><p>这里要提醒大家,递减型和四期型的前期走势特别相似,但是出于四期型的5次价格抬高,如果在周四下午还未发现价格回升,这里建议立即去好友的岛上进行大头菜的售出。因为此时已经确定为递减型。</p><p>换句话说,如果在周四下午时价格走势还是逐次降低的话,抓紧去好友岛上抛售吧,你的大头菜熔断了。</p><p><strong>针对三期形计算</strong></p><p><img src="/2020/04/20/动森大头菜价格分析/code三期.png" alt=""></p><p>从代码中可以看出,三期型是最为复杂的一种变化,它首先会持续走低,然后在某一天开始回暖,然后打到峰值。达到峰值后重新开始持续走低。</p><hr><h2 id="结语"><a href="#结语" class="headerlink" title="结语"></a>结语</h2><p>从整体上看,大头菜价格预测还是相对简单的,网上那些预测工具也是使用你的上一周走势情况,加上你的买入价和一些售价进行反计算,计算出预测阶段时产生的随机bias。当然想完全准确的预测出bias基本上是不可能的,只能给你提供一个大致的范围。</p><p>所以建议玩家在心里设置一个大头菜价格阈值,如果高于这个阈值就卖了吧。毕竟股市有风险,投资需谨慎。</p><p><strong>感谢攻略组的解包人员为玩家提供出准确的游戏数据。世上哪有那么多好用的游戏攻略,只不过是在你看不见的地方有大佬在替你负重前行。任何游戏的攻略数据都是如此,希望广大玩家在网上查找到有用的攻略时在心里默默感谢一下攻略组解包玩家的默默付出。</strong></p><p>最后,本文相关代码<a href="https://github.com/SaberDa/My-Swiss-Army-Knife-Made-By-Code/blob/master/CPP/Cabbage.cpp" target="_blank" rel="noopener">见此</a>。</p>]]></content>
<summary type="html">
<p>首先感谢进行解包的大佬<a href="https://twitter.com/_Ninji/status/1244818665851289602?s=20" target="_blank" rel="noopener">Ninji</a>。本文只是对解包代码进行粗略的解读并加以分析。</p>
<p>解包代码大体可分为三个阶段,分别是随机数生成阶段、价格预测阶段和价格计算阶段。</p>
<h2 id="随机数生成阶段–梅森旋转算法"><a href="#随机数生成阶段–梅森旋转算法" class="headerlink" title="随机数生成阶段–梅森旋转算法"></a>随机数生成阶段–梅森旋转算法</h2><p>梅森旋转算法,Mersenne twister,简写为MT。在C++11中的随机数库中,有一个随机数生成引擎函数 std::mt19937,这里的mt就是梅森旋转算法的简写。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>LeetCode刷题记录-Part2</title>
<link href="https://saberda.github.io/2020/03/15/LeetCode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95-Part2/"/>
<id>https://saberda.github.io/2020/03/15/LeetCode刷题记录-Part2/</id>
<published>2020-03-15T05:18:18.000Z</published>
<updated>2020-03-15T17:23:20.000Z</updated>
<content type="html"><![CDATA[<p>本篇是该系列第二篇,第一篇<a href="http://www.saberismywife.com/2020/01/19/LeetCode刷题记录-Part1/" target="_blank" rel="noopener">地址见此</a>。本篇还是数组篇,主要介绍各种常见算法的灵活使用,具体为<strong>桶排序、环检测算法、单调栈和BFS</strong>,并主要针对排序和查找类题目。</p><p>这个系列的上一篇是近两个月前写的,这篇的构思在上个月就已经想好了,但是一直拖到现在才开始动笔,实在不该。但是在我重新梳理本篇的结构时,重新审视了之前刷题的代码,发现人终归是会遗忘的,有些题的思路已经忘记了。所以这里笔者提醒正在刷题的各位,一定要养成定期温习之前刷的题目,这里并不是指重新敲一遍,而是在脑海中过一遍题目的解题思路和实现逻辑。</p><p>至于怎么温习,我给出两个建议。</p><a id="more"></a><h2 id="第一是要养成记录的习惯"><a href="#第一是要养成记录的习惯" class="headerlink" title="第一是要养成记录的习惯"></a>第一是要养成记录的习惯</h2><p>将每天刷的题按照类型分类,比如所使用的数据结构,是数组、链表还是其他的什么,这里建议在数据结构的大类之下,再对题目细分,比如这道题主要使用最大堆,那就要明确标明出来;或者可以按照解题的类型分类,是使用了DP,还是使用了二分查找,或者采取了自底向上等思想,都可以在记录中明确标明出来。</p><p>除了将题目分类之外,还要保持良好的编码习惯。这里的编码习惯主要是强调使用注释。此注释并不是简单的标出每个循环每个函数的意义,而是要在解题函数的声明上面简要的写明思路,特别是当你使用了特殊的算法或者使用了特殊的内置函数。这里只是给出笔者的习惯,至于是否可以借鉴还是取决你们自己的习惯。虽然LeetCode是网页编程,但是笔者还是习惯使用IDE或者编辑器在本地实现代码,然后复制到网页上进行提交。一是IDE的自动补全比LeetCode自带的好用,二是可以使用一些插件来提高效率。笔者的本地环境是VSCode,这里就使用了一些Comment高亮插件,使上文提到的这些特殊注释高亮显示,方便日后查找温习。</p><h2 id="第二是要养成建立代码仓库的习惯"><a href="#第二是要养成建立代码仓库的习惯" class="headerlink" title="第二是要养成建立代码仓库的习惯"></a>第二是要养成建立代码仓库的习惯</h2><p>这个习惯在这个系列的上一篇文章中已经提到。众所周知,LeetCode是网页编程,而且将来面试中也会出现白板编程或者文档编程等无法使用自动补全等功能的情况,但是还是推荐使用本地的IDE或者编辑器进行编码实现。除了上一部分提到的可以使用插件提高效率外,IDE的文件管理是另一个提高效率的利器。比如当前的这道题之前做过类似的,可以直接查找查看那道题的解题方法。</p><p>当然本地的代码仓库要做,但是同时还要做到远程托管,比如使用GitHub等托管平台。使用远程平台托管这个习惯不止可用于你的代码,你的所有重要文件都建议托管。大家除了可以使用现在免费的GitHub私有仓库外,各种云盘也要习惯使用,特别是Google Drive。</p><p>因为笔者本科的电脑硬盘容量只有128G,这迷你的容量迫使笔者养成了这个习惯,而且现在换了1T的硬盘笔者还是保留着这个习惯。就在上周电脑注册表被笔者魔改后系统服务开始大量消耗内存,在一切方法试尽后迫于无奈重装了系统。这里提一下笔者电脑背景,1T的SSD我没有分区,所以重做后基本没有任何保留文件。而我在重做之后除了配置环境费些麻烦,之前的文件基本都是从托管下载回来的。可见使用远程托管十分便利。</p><h2 id="量变引起质变"><a href="#量变引起质变" class="headerlink" title="量变引起质变"></a>量变引起质变</h2><p>到目前为止笔者刷的题目已经上了三位数,将近150余道,相比于其他人和总题库可能这个数量微不足道,但是笔者已经体会到了量变引起质变的这个过程。</p><p>笔者在本科时是基本没有碰过算法相关的比赛的,大部分的编码都是偏向应用类的实现。相比于解算法题要求的巧妙性,偏向应用的代码没有那么多的要求,只要可以跑起来并且没有bug,就是可用的。但是刷题不是,刷题相比于简简单单的写写界面传传值,涉及到的实现逻辑更加复杂。</p><p>这就导致了笔者在刚刚起步的阶段,做一道easy题都要花费很长的时间,甚至到最后可能都要依靠解答。当时更不要提hard了,有些hard看完之后一丁点的想法都没有。</p><p>但是现在情况有了肉眼可见的改善。首先easy题只要不是考数学的,基本没什么压力;大部分medium题也能完全独立AC;少部分hard题能在不看解答的情况下实现。这个阶段刷题所带来的快感是前所未有的,当你分析完题目后就能联想到可以用来解题的数据结构和算法,甚至可以考虑到一些特殊的边界情况,然后就动手实现,说行云流水有些夸张,但是相比一个半月前的支支吾吾,进步显而易见。这种感觉我第一次感受到是在本科大二时使用FFmpeg实现一个直播应用,这久别重逢的喜悦难以言表。可谓书读百遍其义自见了吧。</p><hr><h2 id="数组类"><a href="#数组类" class="headerlink" title="数组类"></a>数组类</h2><p>拿C++来说,如果想不费力气的去做这方面的题,这里建议事先复习好关于vector, unordered_map, map, prioity_queue等STL提供的数据结构。用一个适合该题目的数据结构解题会节省你大量的时间。</p><hr><h2 id="线性排序算法–桶排序"><a href="#线性排序算法–桶排序" class="headerlink" title="线性排序算法–桶排序"></a>线性排序算法–桶排序</h2><p>在我们刷题时,经常会遇见题目对算法的时间或者空间复杂度有要求,比较常见的除了O(nlogn)外还有 <strong>Try to solve it in linear time/space</strong>。当碰到线性时间复杂度要求时,并且题目经过分析后需要进行排序,那么首选就是桶排序。</p><p>大家可能要问,线性排序不是有三个吗?分别是基数排序,计数排序和桶排序。<strong>其实基数排序和计数排序从某种意义上来说都可以是桶排序,或者是桶排序的变形。</strong>当然在实际生产环境中,根据所需要的排序的数据选择不同的线性排序可能会带来不同的效果,但是在刷题时,并不用明确区分这是使用了那种线性排序,我们需要的只是这种思想。</p><p>这里并不给出桶排序的具体实现方式,因为网上已经有很多很成熟的实现方法,只给出三种排序的时间空间复杂度并分析为何三种排序实际上为一种排序。</p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/线性排序时间复杂度.png" alt=""></p><p>上图中,d表示位数,k在基数排序中表示k进制,在桶排序中表示桶的个数,maxV和minV表示元素最大值和最小值。</p><ul><li>计数排序本质上是一种特殊的桶排序,当桶的个数取最大 (maxV - minV + 1) 的时候,就变成了计数排序。</li><li>基数排序也是一种桶排序。桶排序是按照排序值的区间划分桶,基数排序是按照位数来划分;基数排序可以看做是多轮桶排序,每个数位上都进行一轮桶排序。</li><li>当用最大值作为基数时,基数排序就变成了计数排序</li><li>当使用二进制时,k = 2 最小,位数d最大,时间复杂度 O(nd) 就会变大,空间复杂度 O(n + k) 就会变小。当使用最大值作为基数时,k = maxV 最大,d = 1 最小,此时时间复杂度 O(nd) 变小,但是空间复杂度 O(n + k)会急剧增大,此时基数排序退化成计数排序。</li></ul><p>下面开始进行一些桶排序及其思想的应用。</p><blockquote><ol start="164"><li><strong>Maximum Gap</strong><br>Given an unsorted array, find the maximum difference between the successive elements in its sorted form.<br>Return 0 if the array contains less than 2 elements.<br><strong>Example 1:</strong><br> Input: [3, 6, 9, 1]<br> Output: 3<br> Explanation: The sorted from of the array is [1, 3, 6, 9], either (3, 6) or (6, 9) has the maximum difference 3.<br><strong>Example 2:</strong><br> Input: [10]<br> Output: 0<br> Explanation: The array contains less than 2 elements, therefore return 0.<br><strong>Note</strong>:<pre><code>- You may assume all elements in the array are non-negative integers and fit in the 32-bit signed integer range- Try to solve it in linear time/space</code></pre></li></ol></blockquote><p>这道题是一道经典的桶排序应用题,同时也可以简答理解为<strong>鸽子洞理论</strong>。</p><p>这里简单解释下什么是鸽子洞理论:若有N个笼子和N+1只鸽子,所有的鸽子都被关在鸽笼里,那么至少有一个笼子有至少2只鸽子。</p><p>仔细想想是不是和桶排序的思想很接近,这道题的最佳解题思路就是使用两者思想的结合。</p><p>这道题的最简单的思路就是暴力排序,但是代价十分昂贵,而且还需要对每个元素与其他元素进行比较。但是从侧面想,我们不一定必须要对所有的元素进行比较,我们可以只对一些元素进行比较。比如如果事先以某种方式将元素分成组,或者说桶,然后我们只需要对这些桶的边界进行比较即可。</p><p>我们一步步来分析。首先假设我们把每个元素都放在单独的桶中。此时如果我们减少桶的数量,一些桶就会装载多个元素。其次来讨论下元素之间距离的实际意义。先从最优解入手,数组中的所有元素都拥有均匀的间隔,这意味着每一对相邻的元素间的距离是一个常数。因此,对一个存在n个元素的数组,存在 n-1 个间隔,设每个间隔为t。那么我们就会有 t = (max - min) / (n - 1) (其中max和min分别为数组中元素的最大值最小值)。在最简单的情况下,此时的t就是我们的结果。由此,我们可以判断出,间隔t对于任意的数组都有如下性质:对于元素数量相同、并且极差相同的数组,他们的t是相同的。</p><p>现在再来分析普遍情况。上诉分析可以得出,如果我们将桶作为单位进行比较,会比在比较单个元素的基础上减少比较所需要的次数。所以我们进一步分析如何以某种方式,使得我们只需要比较桶与桶的边界元素,而不需要对每个桶内的元素进行比较。答案显而易见:我们只需要将元素分配到它正确的桶中即可。</p><p>但是与此同时,我们还需要保证桶与桶之间的间隔可以代表输入数组中的最大间隔。我们可以通过设置桶的存储范围b小于t。( t = (max - min) / (n - 1) )。这样我们保证了桶中元素的距离肯定小于t,这样就可以推断出最大间隔必定只会出现在两个相邻的桶之间。</p><p>综上,我们只需要将桶的大小b设置为 1 < b <= (max-min) / (n-1) 即可。这样,我们可以确保至少存在一个间隔为所解答的最大间隔。</p><p><strong>现在再重新梳理一遍:</strong></p><ul><li>每个桶的大小都被设为b。</li><li>每个桶存储的元素不应该存在覆盖问题。比如桶的大小为3,那么相邻的两个桶之间只会分别存储3~6和7~9。</li><li>每个桶的大小实际上指的是该桶能存储的元素范围,并且这个范围取决于输入数组元素的个数和它的最大最小值。</li><li>如何计算相邻桶之间的距离:将当前桶储存的最大值于下一个桶的最小值进行计算,结果就是桶之间的间隔。</li><li>怎么减少的比较次数:这个算法实际的比较次数只是桶的数量的两倍。</li></ul><p><strong>算法逻辑:</strong></p><ul><li>计算桶的存储范围 b = ⌊ (max−min) / (n−1) ⌋。(注意此处为向下取整)</li><li>将元素分配到桶中,计算出桶的整体数量 k = ⌈ (max−min) / b ⌉。(注意此处向上取整)</li><li>这样,i<sup>th</sup> 的桶说存储的元素范围为:[ min + (i - 1) <em> b, min + i </em> b)。</li><li>计算每个元素所属的桶:(curNum - min ) / b。</li><li>当所有的元素都在桶中时,计算 k - 1 个桶间间距,从中寻找出最大值即为解。</li></ul><p><strong>代码实现:</strong></p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/164.jpeg" alt=""></p><p><strong>算法时间复杂度分析:</strong></p><p>时间复杂度:O(n + b) ≈ O(n)</p><p>空间复杂度:O(2 * b) ≈ O(b)</p><p>这道题是一道很经典的桶排序应用题,但是更多的题目需要的是桶排序这个思想。比如下一道题。</p><blockquote><ol start="75"><li><strong>Sort Colors</strong><br>Given an array with <em>n</em> objects colored red, white or blue, sort them <strong>in place</strong> so that objects of the same color are adjacent, with the colors in the order red, white and blue.<br>Here, we will use the integers 0, 1, and 2 to represent the color red, white, and blue respectively.<br><strong>Note:</strong> You are not suppose to use the library’s sort function for this problem.<br><strong>Example:</strong><br> Input: [2, 0, 2, 1, 1, 0]<br> Output: [0, 0, 1, 1, 2, 2]<br><strong>Follow up:</strong><ul><li>A rather straight forward solution is a two-pass algorithm using counting sort.<br>First, iterate the array counting number of 0’s, 1’s, and 2’s, then overwrite array with total number of 0’s, then 1’s and followed by 2’s.</li><li>Could you come up with a one-pass algorithm using only constant space?</li></ul></li></ol></blockquote><p>这道题要求我们把所有的0放在前面,所有的2放在后面,中间放1。其实很简单,我们要做的只是把0放到前面,2放到后面,中间的自然是1。所以这里采取桶排序的计数思想。</p><p><strong>代码实现:</strong></p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/75.png" alt=""></p><p>总之,桶排序重要的并不是提供一种线性时间的排序算法,而是提出了计数这个思想。我们在做题中要活用这个思想。</p><hr><h2 id="环检测算法"><a href="#环检测算法" class="headerlink" title="环检测算法"></a>环检测算法</h2><p>大名鼎鼎的<strong>Floyd’s Tortoise and Hare</strong>算法,<strong>通过一快一慢两个指针来判断链表中是否存在环</strong>。既然是链表的算法,那笔者为什么要放在数组类中进行介绍?同理于桶排序,重要的不是检测环,而是检测环的这个思路。下面笔者将例举两道题来分析该算法的实际应用。一道是传统的链表环检测问题,另一道就是该算法的灵活使用。</p><blockquote><ol start="142"><li>Linked List Cycle II<br>Given a linked list, return the node where the cycle begins. If there is no cycle, return NULL.<br>To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.<br><strong>Note:</strong> Do not modify the linked list.<br>Example:<br> Input: head = [3, 2, 0, -4], pos = 1<br> Output: tail connects to node index 1<br> Explanation: There is a cycle in the linked list, where tail connects to the second node.</li></ol></blockquote><p><img src="/2020/03/15/LeetCode刷题记录-Part2/环.png" alt=""></p><p>环检测算法的原理就是通过一个快指针,一个慢指针,同时对该链表遍历。如果存在环路,则快慢两个指针必定会相遇;若没有相遇,这表明该链表不存在环。</p><p>这道题在判断环的基础上更进一步,要求我们求出环路开始的节点。那么我们在快慢指针相遇后,将其中一个指针移动到头结点,然后使两者匀速遍历,当两者再次相遇时就是环路开始的节点。</p><p><strong>代码实现:</strong></p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/142.png" alt=""></p><p>上题就是经典的环检测算法的应用。下一题我们采用这个思想,来判断数组中是否存在相同的元素。</p><blockquote><ol start="287"><li><strong>Find the Duplicate Number</strong><br>Given an array <em>nums</em> containing <em>n</em> + 1 integers where each integer is between 1 and <em>n</em> (inclusive), prove that at least one duplicate number must exist. Assume that there is only one duplicate number, find the duplicate one.<br><strong>Example 1:</strong><br> Input: [1, 3, 4, 2, 2]<br> Output: 2<br><strong>Example 2:</strong><br> Input: [3, 1, 3, 4, 2]<br> Output: 3<br><strong>Note:</strong><ul><li>You <strong>must not</strong> modify the array (assume the array is read only).</li><li>You must use only constant, <em>O</em>(1) extra space.</li><li>Your runtime complexity should be less than O(n<sup>2</sup>).</li><li>There is only one duplicate number in the array, but it could be repeated more than once.</li></ul></li></ol></blockquote><p>这道题我们的解决思路与上面的第142题有着异曲同工之处,同样使用两个快慢指针,当两个指针相遇后,让其中的一个从头开始,两者按照一样的速度遍历,直到再次相遇,相遇的值就是重复的值。</p><p><strong>代码实现:</strong></p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/287.png" alt=""></p><p>仔细观察不难看出,这道题的实现就是把142的数据结构换成数组,其余的基本一模一样。这也是我所说的灵活运用环检测算法的意义。</p><hr><h2 id="单调栈"><a href="#单调栈" class="headerlink" title="单调栈"></a>单调栈</h2><p>单调栈这种数据结构看上去简单,然而在实际应用中却不是很简单。<strong>首先什么是单调栈?</strong>单调栈就是要求我们维护一个栈,使其里面的元素单调递增或者递减。下面通过第84题来具体分析单调栈的使用。</p><blockquote><ol start="84"><li>Largest Rectangle in Histogram<br>Given <em>n</em> non-negative integers representing the histogram’s bar height where the width of each bar is 1, find the area of largest rectangle in the histogram.<br>Example:<br> Input: [2, 1, 5, 6, 2, 3]<br> Output: 10</li></ol></blockquote><p><img src="/2020/03/15/LeetCode刷题记录-Part2/单调栈.png" alt=""></p><p>这道题的具体思路就是将元素入栈,如果当前元素大于栈顶元素,入栈;否则栈顶元素出栈。用数学表达式来直观表达即为:(curNum - stcak[top - 1] - 1) * nums[stack[top]]。</p><p><strong>实现代码:</strong></p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/84.png" alt=""></p><p>通过代码来讲解这道题,首先每个元素都要入栈一次,出栈一次。入栈的时候是遍历到该元素时,我们只需要判断出元素出栈的条件即可。当元素出栈时,意味着我们已经可以计算以它为边界的右边框的最大矩形。首先,我们可以使用反证法来证明该算法的正确性。最后的结果中的最大矩形的右边界,一定与某一个元素代表的矩形重合,否则我们一定可以通过增加右边界来增加这个矩形的面积。</p><p>之后,当元素出栈时,此时我们可以计算的矩形的左右边界已经是极限值。结合单调栈的性质,我们可以知道左边框是栈顶元素+1,栈顶元素所对应的矩形已经比出栈元素对应的矩形小,所以出栈元素对应的矩形的高无法继续向左延伸。结合代码,我们知道右边界对应的值是当前的元素的值,因为我们已经判断过这个元素对应的矩形一定比出栈元素的矩形小,所以矩形此时也无法向右延伸。</p><p>若这个元素和左右边界之间还有空隙,那么这些空隙里所存在的矩形一定是当我们维护单调栈时被弹出了。换言之,如果存在一个矩形的高可以扩展当前围成的矩形,那么当前左右边界围成的不是一个矩形,而是一个凹形。</p><p>因此我们证明了当前元素出栈时,我们可以计算以当前元素为高的最大矩形面积。</p><p><strong>最后关于单调栈的时间复杂度:</strong></p><p>因为每个元素都入栈、出栈一次,所以是线性时间复杂度。</p><p>84题是典型的单调栈应用题,若各位想提高的话,可以试试第407题,该题在三维上使用单调栈,因为此题比较复杂,除了单调栈外,还需要用到大顶堆等组合成复杂的数据结构,笔者怕无法用笔者的语言准确的描述以及证明算法,所以不在此做过多描述。</p><p>简单来讲,<strong>使用单调栈的难点在于如何判断元素出栈,以及元素出栈所代表的意义</strong>。经过笔者的实践,可以推断出,当你有高效率的获取某个位置左右两侧大于或小于的数的位置的需求时,可以试试使用单调栈。</p><hr><h2 id="BFS的变种"><a href="#BFS的变种" class="headerlink" title="BFS的变种"></a>BFS的变种</h2><p>BFS可谓是老生常谈的考题了,但是有些BFS的变种,比如two-end BFS在解题时更有效率性。</p><h2 id="Two-End-BFS-Bidirectional-Search"><a href="#Two-End-BFS-Bidirectional-Search" class="headerlink" title="Two-End BFS: Bidirectional Search"></a>Two-End BFS: Bidirectional Search</h2><p>Bidirectional Search是一种图搜索算法,可找到目标顶点对的最短路径。它<strong>同时运行两个BFS</strong>:</p><ul><li>从源节点向目标节点的向前搜索</li><li>从目标节点向源节点的向后搜索</li></ul><p>Bidirectional Search使用两个较小的子图替换了整个搜索图,一个子图从源节点开始,另一个从目标节点开始。当两个图相交时,搜索结束,源节点、目标节点与交点相连接的路径即为所求结果。</p><p>下图给出了一个简单的例子:</p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/BidirectionalSearch.png" alt=""></p><p>假设我们要查找是否存在从节点0到节点14的最短路径。这里,我们开始两个BFS,一个从0开始,一个从14开始。当向前和向后搜索在节点7相遇时,停止搜索。可见,我们并不需要对其他节点,比如2,3,11,12等,即避免了不必要的搜索。</p><h2 id="为什么要使用Bidirectional-Search而不是普通的BFS"><a href="#为什么要使用Bidirectional-Search而不是普通的BFS" class="headerlink" title="为什么要使用Bidirectional Search而不是普通的BFS"></a>为什么要使用Bidirectional Search而不是普通的BFS</h2><p>能用Bidirectional Search解决的问题自然能用BFS解决,但是在许多情况下,使用Bidirectional Search会更快,会大大减少需要的勘探量。这里假设一棵树的分支节点为b,目标节点到源节点的距离为d,若使用传统的BFS搜索的复杂度为O(b ^ d)。另一方面,如果我们执行Bidirectional Search,这每次搜索的复杂度将为 O(b ^ (d / 2)),总复杂度为 O(b ^ (d / 2) + b ^ (d / 2)),远远小于O(b ^ d)。</p><h2 id="何时适合使用Bidirectional-Search"><a href="#何时适合使用Bidirectional-Search" class="headerlink" title="何时适合使用Bidirectional Search"></a>何时适合使用Bidirectional Search</h2><ul><li>初始状态和目标状态都是唯一的,并且定义完全</li><li>两个方向的分支节点权重相同</li></ul><h2 id="Bidirectional-Search性能"><a href="#Bidirectional-Search性能" class="headerlink" title="Bidirectional Search性能"></a>Bidirectional Search性能</h2><ul><li>Completeness: 如果两个搜索都使用BFS,则Bidirectional Search是完全的。</li><li>Optimality: 如果使用BFS进行Bidirectional Search,并且图上节点权重相同,则Bidirectional Search是最优的</li><li>Time and Space Complexity: 时空复杂度为O(b ^ (d / 2))</li></ul><p>算法伪代码:</p><p><img src="/2020/03/15/LeetCode刷题记录-Part2/BidirectionalSearch2.png" alt=""></p><p>在具体应用中,我们需要维护两个BFS队列。每次更新较小的那个队列,直到两个队列中有元素重合,说明路径找到。</p><blockquote><ol start="126"><li><strong>Word Ladder II</strong><br>Given two words (beginWord and endWord), and a dictionary’s word list, find all shortest transformation sequence(s) from beginWord to endWord, such that:<pre><code>1. Only one letter can be changed at a time 2. Each transformed word must exist in the word list. Note that beginWord is not a transformed word.</code></pre><strong>Note:</strong><ul><li>Return an empty list if there is no such transformation sequence.</li><li>All words have the same length</li><li>All words contains only lowercase alphabetic characters</li><li>You may assume beginWord and endWord are non-empty and are not the same</li><li>You may assume no duplicates in the word list<br><strong>Example:</strong><br> Input:<br> beginWord = “hit”<br> endWord = “cog”<br> wordList = [“hot”, “dot”, “dog”, “lot”, “log”, “cog”]<br> Output:<br> [<br> [“hit”,”hot”,”dot”,”dog”,”cog”],<br> [“hit”,”hot”,”lot”,”log”,”cog”]<br> ]</li></ul></li></ol></blockquote><p>这道题表面上给的是一个字符串数组,但是实际上是隐式地给出了一个迷宫,其中两个词可以通过规则相互转换,从而将他们连接起来。因此,问题实际上是找到从开始到结束的所有最短路径。</p><p>使用Bidirectional Search可以根据方向较少的选项从任意一个方向搜索下一个转换后的单词,以转换为ladder中的下一个单词。这样避免了从一段到另一端有太多路径,最后一刻才能到达另一端的情况。</p><p>此问题可以细化为两个子问题:</p><ul><li>构建mapNextWord来查找每个单词的所有下一个转换后的词典单词。因为边上没有权重,可以简单的使用BFS执行</li><li>使用mapNextWord建立所有最短的ladders,这可以简单的通过回溯完成。</li></ul><p><strong>代码实现:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br></pre></td><td class="code"><pre><span class="line">#include <iostream></span><br><span class="line">#include <vector></span><br><span class="line">#include <string></span><br><span class="line">#include <unordered_set></span><br><span class="line">#include <unordered_map></span><br><span class="line"></span><br><span class="line">using namespace std;</span><br><span class="line"></span><br><span class="line">// Key Observations:</span><br><span class="line">// 1. The dictionary actually implicitly gives a maze where two words are connected if they can transform to each other by the rules. </span><br><span class="line">// So the problem is actually to find all shortest path from begin to end</span><br><span class="line">// 2. Use two-end BFS to search next transformed words from either direction based on whichever direction has fewer options to transform to next word in ladder.</span><br><span class="line">// This avoid the scenario where the arr way too mant path from one end, but only a few paths could reach the other end at the last moment.</span><br><span class="line">// 3. This problem can be decoupled into two sub-questions:</span><br><span class="line">// a) build mapNextWords to find all next transformed dictionary words for each word</span><br><span class="line">// (we only need to build the map enough to only cover all shortest paths. This can be guaranteed by BFS on graph with no weight on edges)</span><br><span class="line">// b) build all shortest ladders using the map mapNextWords. This can be done by standard back tracking</span><br><span class="line"></span><br><span class="line">// word dictionary</span><br><span class="line">unordered_set<string> dict;</span><br><span class="line"></span><br><span class="line">// mapNextWord[w]: set of all w's possible next words in dictionary</span><br><span class="line">// The "next" is always in start -> end direction</span><br><span class="line">unordered_map<string, unordered_set<string>> mapNextWords;</span><br><span class="line"></span><br><span class="line">// all shortest ladders</span><br><span class="line">vector<vector<string>> ladders;</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * Build mapNextWords recursively using two-end BFS</span><br><span class="line"> * Note: each recursion dose exactly 1 transform distance from either start or end direction</span><br><span class="line"> * @param {unordered_set<string>} layer1 : </span><br><span class="line"> * @param {unordered_set<string>} layer2 : </span><br><span class="line"> * @param {bool} isForward : </span><br><span class="line"> */</span><br><span class="line">void buildMapNextWords(unordered_set<string>& layer1, unordered_set<string>& layer2, bool isForward) {</span><br><span class="line"> if (layer1.empty()) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // always BFS from the smaller layer side</span><br><span class="line"> if (layer1.size() > layer2.size()) {</span><br><span class="line"> swap(layer1, layer2);</span><br><span class="line"> isForward = !isForward;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // remove used words from dict</span><br><span class="line"> for (auto& w : layer1) {</span><br><span class="line"> dict.erase(w);</span><br><span class="line"> }</span><br><span class="line"> for (auto& w : layer2) {</span><br><span class="line"> dict.erase(w);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // next layer of layer1 by word transform</span><br><span class="line"> unordered_set<string> nextLayer;</span><br><span class="line"> bool shortest_ladder_found = false;</span><br><span class="line"></span><br><span class="line"> // transform each word in layer1</span><br><span class="line"> for (auto w : layer1) {</span><br><span class="line"> string origW = w;</span><br><span class="line"> for (int i = 0; i < w.size(); i++) {</span><br><span class="line"> char origCh = w[i];</span><br><span class="line"> for (char c = 'a'; c <= 'z'; c++) {</span><br><span class="line"> // transform word w</span><br><span class="line"> w[i] = c; </span><br><span class="line"> </span><br><span class="line"> // keep current->next always on start->end direction</span><br><span class="line"> string curWord = origW;</span><br><span class="line"> string nxtWord = w;</span><br><span class="line"> if (!isForward) {</span><br><span class="line"> swap(curWord, nxtWord);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> // next layer of layer1 meets layer2: shortest path found</span><br><span class="line"> if (layer2.count(w)) {</span><br><span class="line"> shortest_ladder_found = true;</span><br><span class="line"> mapNextWords[curWord].insert(nxtWord);</span><br><span class="line"> }</span><br><span class="line"> // next layer of layer1 didn't meet layer2, but still in dict, so keep going</span><br><span class="line"> else if (dict.count(w)) {</span><br><span class="line"> nextLayer.insert(w);</span><br><span class="line"> mapNextWords[curWord].insert(nxtWord);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> w[i] = origCh;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //recursively build map if not done</span><br><span class="line"> if (!shortest_ladder_found) {</span><br><span class="line"> buildMapNextWords(nextLayer, layer2, isForward);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * Build add shortest ladders using back tracking</span><br><span class="line"> * </span><br><span class="line"> * @param {string} curWord : current word in ladder</span><br><span class="line"> * @param {string} endWord : end word in ladder</span><br><span class="line"> * @param {vector<string>} ladder : current ladder in building process</span><br><span class="line"> */</span><br><span class="line">void buildLadders(string curWord, string endWord, vector<string>& ladder) {</span><br><span class="line"> // if current word reaches end, current ladder is done</span><br><span class="line"> if (curWord == endWord) {</span><br><span class="line"> ladders.push_back(ladder);</span><br><span class="line"> return;</span><br><span class="line"> } </span><br><span class="line"></span><br><span class="line"> // try all next word options and append to current ladder</span><br><span class="line"> for (auto& w : mapNextWords[curWord]) {</span><br><span class="line"> ladder.push_back(w);</span><br><span class="line"> buildLadders(w, endWord, ladder);</span><br><span class="line"> ladder.pop_back();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">int main() {</span><br><span class="line"> vector<string> wordList = { "hot","dot","dog","lot","log","cog" };</span><br><span class="line"> string beginWord = "hit";</span><br><span class="line"> string endWord = "cog";</span><br><span class="line"></span><br><span class="line"> dict = unordered_set<string>(wordList.begin(), wordList.end());</span><br><span class="line"></span><br><span class="line"> if (!dict.count(endWord)) {</span><br><span class="line"> return 0;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // initialize two layers for two-end BFS</span><br><span class="line"> unordered_set<string> forwardLayer({beginWord});</span><br><span class="line"> unordered_set<string> backwardLayer({endWord});</span><br><span class="line"></span><br><span class="line"> // sub-problem 1: build next word map</span><br><span class="line"> buildMapNextWords(forwardLayer, backwardLayer, true);</span><br><span class="line"></span><br><span class="line"> // sub-problem 2: build ladders</span><br><span class="line"> vector<string> ladder({beginWord});</span><br><span class="line"> buildLadders(beginWord, endWord, ladder);</span><br><span class="line"></span><br><span class="line"> for (int i = 0; i < ladders.size(); i++) {</span><br><span class="line"> for (int j = 0; j < ladders[0].size(); j++) {</span><br><span class="line"> cout << ladders[i][j] << " ";</span><br><span class="line"> }</span><br><span class="line"> cout << " " << endl;</span><br><span class="line"> }</span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>当然,既然存在双源BFS,那肯定存在多源BFS。实现多源BFS的核心在于将多个源节点同时push到队列中,然后开始BFS。</p><p>大家可以尝试去做第296题来实现多源BFS。</p><blockquote><ol start="296"><li>Best Meeting Point<br>A group of two or more people wants to meet and minimize the total travel distance. You are given a 2D grid of values 0 or 1, where each 1 marks the home of someone in the group. The distance is calculated using <a href="http://en.wikipedia.org/wiki/Taxicab_geometry" target="_blank" rel="noopener">Manhattan Distance</a>, where distance(p1, p2) = |p2.x - p1.x| + |p2.y - p1.y|.<br>Example:<br> Input:<br>1 - 0 - 0 - 0 - 1<br>| | | | |<br>0 - 0 - 0 - 0 - 0<br>| | | | |<br>0 - 0 - 1 - 0 - 0<br>Output: 6<br>Explanation: Given three point living at (0,0), (0,4) and (2,2): The point (0,2) is an ideal meeting point, so the total travel distance of 2+2+2=6 is minimal. So return 6.</li></ol></blockquote><p>然而可惜的是笔者在使用多源BFS解决这道题时,LeetCode提示超时。然后去看了看Discuss,发现所有使用BFS的都是超时,官方解法是使用中位数,其中涉及到数学证明,笔者不做过多陈诉。这里笔者吐槽一下,这种难度的题竟然考你数学,属实不应该。</p><hr><h2 id="其他一些碎碎念"><a href="#其他一些碎碎念" class="headerlink" title="其他一些碎碎念"></a>其他一些碎碎念</h2><p>现在全球基本都在流行COVID-19,目前国内是控制住了,但是国内的小伙伴们也不能放松警惕,每日出行口罩一定要戴。和我一样在国外的小伙伴,口罩该戴还是要戴的,<strong>相比于歧视,小命更重要</strong>。</p><p>各个阶层之间没有沟通渠道,我看不到你在干什么,你也看不到我在干什么。其他人过着怎样的生活与自己关系确实不大,但是还是希望人们都更加善良些。</p><p>《人世间》总导演周全曾经说过一段很有温度的话,我感觉很适合当前的环境。“生命力很奇怪,它熊熊燃烧的时候,你不敢去直视它,你会绕着走。恰恰是那火烧成灰,灰里吐出火星的时候,你反而注意到它。也许生长在人心最底层的善良与怜悯,才是真正的生命力。”</p><p>尽管目前全球各个国家都有自己各自应对疫情的方法,有些甚至令人觉得可笑,但是我并不希望人们带着偏见的眼光去审视任何一个国家的政策,因为<strong>那些国家也有因此离世的人,也有因此家庭遭遇了事故,那些国家更多的,是那些依然在努力活着的人</strong>。</p><p>我希望在今后的某个岁月,大家回想起这段疫情的时候,更多的不是吹嘘在家隔离了多长时间、不是回想不能出门的日子,而是回忆起这个特殊时期,做出了巨大贡献的那批人。<strong>人,还是要善良的。</strong></p><p>写于2020年3月14日的深夜,阿灵顿,弗吉尼亚,窗外下着雨。</p>]]></content>
<summary type="html">
<p>本篇是该系列第二篇,第一篇<a href="http://www.saberismywife.com/2020/01/19/LeetCode刷题记录-Part1/" target="_blank" rel="noopener">地址见此</a>。本篇还是数组篇,主要介绍各种常见算法的灵活使用,具体为<strong>桶排序、环检测算法、单调栈和BFS</strong>,并主要针对排序和查找类题目。</p>
<p>这个系列的上一篇是近两个月前写的,这篇的构思在上个月就已经想好了,但是一直拖到现在才开始动笔,实在不该。但是在我重新梳理本篇的结构时,重新审视了之前刷题的代码,发现人终归是会遗忘的,有些题的思路已经忘记了。所以这里笔者提醒正在刷题的各位,一定要养成定期温习之前刷的题目,这里并不是指重新敲一遍,而是在脑海中过一遍题目的解题思路和实现逻辑。</p>
<p>至于怎么温习,我给出两个建议。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="LeetCode" scheme="https://saberda.github.io/tags/LeetCode/"/>
</entry>
<entry>
<title>HashCode 2020</title>
<link href="https://saberda.github.io/2020/02/20/HashCode-2020/"/>
<id>https://saberda.github.io/2020/02/20/HashCode-2020/</id>
<published>2020-02-21T00:09:29.000Z</published>
<updated>2020-02-21T01:49:38.000Z</updated>
<content type="html"><![CDATA[<p>Hash Code 是由Google举办,并且每一年举办一次的大型编程比赛。<a href="https://codingcompetitions.withgoogle.com/hashcode" target="_blank" rel="noopener">今年的比赛官网</a></p><p>关于最终比赛结果,在一共10724份提交代码的小组中,我们最终积分是两千四百多万分,具体排名为2100名。感谢小组内所有大佬的付出,让我们4个小时的努力有所回报。</p><p><img src="/2020/02/20/HashCode-2020/2.PNG" alt=""></p><p>今年的<a href="https://github.com/SaberDa/HashCode-Competition-2020" target="_blank" rel="noopener">比赛题目和数据集见此</a>。该仓库包含今年的练习题目与正式题目。此篇文章主要陈述正式比赛题目中的数据集b和d的解法。</p><p>至于为什么只写这两个数据集的解法,因为是我写的。但是关于d的算法在数据集d上的实现并不好,然而在f上的实现超出预期。</p><a id="more"></a><h2 id="题目"><a href="#题目" class="headerlink" title="题目"></a>题目</h2><p>先简单介绍一下题目的大概意思。当然,如果你想真正了解题目,还是建议去看上文仓库链接中的<a href="https://github.com/SaberDa/HashCode-Competition-2020/blob/master/hashcode_2020_online_qualification_round.pdf" target="_blank" rel="noopener">源文件</a>。</p><p>题目的主要目的是让我们给出一个算法,可以在规定的时间内扫描书目,保证书目的数量尽可能多时,同时保证被扫描的书目的权重和越大。该权重和就是最后比赛的成绩。</p><p>题目中主要有三个约束条件,一个是图书馆,一个是图书,一个是总时间。这些具体见后文的代码。首先先分析输入数据集的具体意义。</p><p><img src="/2020/02/20/HashCode-2020/1.png" alt=""></p><p>输入数据集总体分为两部分,第一部分为前两行,剩下的为第二部分。在第一部分中,第一行的三个数字分别代表了总的图书数量、总的图书馆数量和总的日期;第二行为每个图书的具体权重。在第二部分中,又以每两行为单位定义了一个图书馆的属性;每组的第一行的三个数字分别代表了该图书馆总的图书数量、注册需要的时间和每天能运输的图书能力,每组的第二行表示该图书馆所存放的图书序号。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><span class="line">// 图书馆数据结构</span><br><span class="line">struct Library {</span><br><span class="line"> // 注册时间</span><br><span class="line"> unsigned long long signup_time;</span><br><span class="line"> // 每天可运输的图书数量</span><br><span class="line"> unsigned long long ships_per_day;</span><br><span class="line"> // 存放的图书</span><br><span class="line"> unordered_set<unsigned long long> lib_books;</span><br><span class="line"> // 已经扫描的图书</span><br><span class="line"> vector<unsigned long long> to_scan;</span><br><span class="line"></span><br><span class="line"> // 判断是否还能继续扫描</span><br><span class="line"> bool canScan() const {</span><br><span class="line"> return (days - signup_time) * ships_per_day - to_scan.size() > 0;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 计算该图书馆权重</span><br><span class="line"> unsigned long long potentialScore() const {</span><br><span class="line"> unsigned long long s = 0;</span><br><span class="line"> for (auto b : to_scan) {</span><br><span class="line"> s += books[b];</span><br><span class="line"> }</span><br><span class="line"> return s;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这里需要注意的是,在图书馆注册期间,是不允许运输图书的,而且注册之后每天运输的图书不允许超过其最大承受容量。但是是允许多线程工作的,详细见下图。</p><p><img src="/2020/02/20/HashCode-2020/3.png" alt=""></p><p>题目规定最后的标准输出也具体分为两部分。第一部分单独成一行,并且只有一个数据,表示一共可以注册的图书馆总数。第二部分以每两行为一组,第一行包含两个数据,分别代表该被选中的图书馆的序号和一共运输的图书数量;第二行为被运输数量的序号。需要注意的是,结果中如果有重复的书目数据,成绩只会登记一次。详细见下图。</p><p><img src="/2020/02/20/HashCode-2020/4.png" alt=""></p><p>题目整体介绍到这里。还是那句话,如果想真正了解题目,建议去看上文仓库链接。</p><h2 id="数据集b的解法"><a href="#数据集b的解法" class="headerlink" title="数据集b的解法"></a>数据集b的解法</h2><p>首先先分析一下数据集的构成。b这个数据集很有取巧性,不难发现,该数据集的每本数量的权重是一样的,并且每个图书馆的总图书数量和每天可以运输的图书数目是一样的。下图为b数据集的部分截图。<a href="https://github.com/SaberDa/HashCode-Competition-2020/blob/master/problem/database/b_read_on.txt" target="_blank" rel="noopener">详细数据集</a></p><p><img src="/2020/02/20/HashCode-2020/5.png" alt=""></p><p>所以破解b数据集很明显的算法就是贪心算法,对每个图书馆的注册时间进行贪心,从小排列到大,依次入栈,直到超出所给的总时间。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><span class="line">void solutionB() {</span><br><span class="line"> vector<pair<unsigned long long, unsigned long long>> libs_todo_index(libs.size());</span><br><span class="line"> // 将所有图书馆入栈并按照注册时间排序</span><br><span class="line"> for (unsigned long long i = 0; i < libs.size(); i++) {</span><br><span class="line"> libs_todo_index[i].first = libs[i].signup_time;</span><br><span class="line"> libs_todo_index[i].second = i;</span><br><span class="line"> }</span><br><span class="line"> sort(libs_todo_index.begin(), libs_todo_index.end());</span><br><span class="line"> unordered_set<unsigned long long> add_books;</span><br><span class="line"> // 开始扫描图书</span><br><span class="line"> // 同时注意判断是否超出总时间以及是否重复</span><br><span class="line"> for (unsigned long long i = 0; i < libs_todo_index.size(); i++) {</span><br><span class="line"> Library& lib = libs[libs_todo_index[i].second];</span><br><span class="line"> for (auto book : lib.lib_books) {</span><br><span class="line"> if (!add_books.count(book) && lib.canScan()) {</span><br><span class="line"> lib.to_scan.push_back(book);</span><br><span class="line"> add_books.insert(book);</span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> // 将结果入栈 </span><br><span class="line"> libraries.push_back(libs_todo_index[i].second);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>因为数据集b的构成十分简单,所以使用贪心得出的应该是最优解。</p><h2 id="数据集d的解法"><a href="#数据集d的解法" class="headerlink" title="数据集d的解法"></a>数据集d的解法</h2><p>数据集d的构成和b很类似,每个图书馆的注册时间和每天所承受最大运输数一样,只有每个图书馆所含图书数量不一样。<a href="https://github.com/SaberDa/HashCode-Competition-2020/blob/master/problem/database/d_tough_choices.txt" target="_blank" rel="noopener">详细数据集</a></p><p><img src="/2020/02/20/HashCode-2020/6.png" alt=""></p><p>在处理d数据集的时候,由于前期思考使用的数据结构和算法取舍上花费了比较多的时间,这里就在对d处理的基础上,向其他数据集进行了扩展。首先先对所有图书的权重进行从大到小排序,然后按照图书馆的注册时间对每个图书馆进行从小到大排序。</p><p>然后以图书的权重顺序为外层循环条件,以图书馆注册时间为内层循环条件,将图书馆所存放图书按照权重入栈。最后计算所有图书馆的得分权重,按照从大到小排序入栈最终结果。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">void solution() {</span><br><span class="line"> // 首先把所有图书按照权重排序</span><br><span class="line"> vector<pair<unsigned long long, unsigned long long>> book_score_todo_index(books.size());</span><br><span class="line"> for (unsigned long long i = 0; i < books.size(); i++) {</span><br><span class="line"> book_score_todo_index[i].first = books[i];</span><br><span class="line"> book_score_todo_index[i].second = i;</span><br><span class="line"> }</span><br><span class="line"> sort(book_score_todo_index.begin(), book_score_todo_index.end());</span><br><span class="line"> </span><br><span class="line"> // 把所有图书馆按照注册时间排序</span><br><span class="line"> vector<pair<unsigned long long, unsigned long long>> libs_todo_index(libs.size());</span><br><span class="line"> for (unsigned long long i = 0; i < libs.size(); i++) {</span><br><span class="line"> libs_todo_index[i].first = libs[i].signup_time;</span><br><span class="line"> libs_todo_index[i].second = i;</span><br><span class="line"> }</span><br><span class="line"> sort(libs_todo_index.begin(), libs_todo_index.end());</span><br><span class="line"></span><br><span class="line"> // 开始计算每个图书馆的具体权重</span><br><span class="line"> // 注意要去除重复出现的图书</span><br><span class="line"> unordered_set<unsigned long long> libs_set;</span><br><span class="line"> for (unsigned long long i = book_score_todo_index.size() - 1; i >= 0; i--) {</span><br><span class="line"> for (unsigned long long j = libs_todo_index.size() - 1; j >= 0; j--) {</span><br><span class="line"> Library& lib = libs[libs_todo_index[j].second];</span><br><span class="line"> if (lib.canScan() && lib.lib_books.count(i)) {</span><br><span class="line"> lib.to_scan.push_back(i);</span><br><span class="line"> libs_set.insert(libs_todo_index[j].second);</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 按图书馆的权重和入栈到最终结果</span><br><span class="line"> vector<pair<unsigned long long, unsigned long long>> score_index;</span><br><span class="line"> score_index.reserve(libs_set.size());</span><br><span class="line"> for (auto id : libs_set) {</span><br><span class="line"> score_index.emplace_back(libs[id].potentialScore(), id);</span><br><span class="line"> }</span><br><span class="line"> sort(score_index.begin(), score_index.end());</span><br><span class="line"> reverse(score_index.begin(), score_index.end());</span><br><span class="line"> for (int i = 0; i < score_index.size(); i++) {</span><br><span class="line"> libraries.push_back(score_index[i].second);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这样的双重贪心不可避免的损失精度,所以必定不是最优解。所以数据集d最后只有四百多万的积分。但是在计算真实世界的数据集f时,却达到了五百多万的积分,超乎意料。</p><h2 id="最后"><a href="#最后" class="headerlink" title="最后"></a>最后</h2><p>附上我们小组的每个数据集的具体得分。</p><p><img src="/2020/02/20/HashCode-2020/7.PNG" alt=""></p><p>这里吐槽一下,Google在example上跟我们玩了个游戏,因为题目中给出的example并不是最优解。</p><p>使用C++进行数据处理,可能唯一的优势就是速度了,但是在选择保存和处理数据的数据结构的选择上真是仿佛吃屎,从开始的类到三维数组再到最后使用的结构体真的很折腾人,这部分花费了太多时间,主要原因还是自己接触这种数据的次数过少,没能在第一时间选择出合理的数据结构。其次,文件的读写真的没有python便捷。</p><p>如果时间再充裕些,就可以对数据集c和d算法进行再次优化,让每个的最后积分再增加一百万达到五百万级别,到时我们就有可能冲击决赛名额,啧,算是比较遗憾吧。</p><p>但是整体来说,这次比赛极大的加深了我对unordered_set和pair这两个经典的STL数据结构的理解和使用。</p><p>而且最后的总分远远超出我们所有人最开始的估计,而且最后的排名也不错,虽说肯定进不了决赛了,但是总体排名前20%还是可以写进简历的。</p><p>以及加♂深了和各位大佬的友谊。</p><h2 id="最最后"><a href="#最最后" class="headerlink" title="最最后"></a>最最后</h2><p>今天早上刚醒就收到了挚友韩静轩发来的消息,他和她女朋友分别以407和397的考研成绩成功考上哈工大建筑系,可喜可贺可喜可贺。</p>]]></content>
<summary type="html">
<p>Hash Code 是由Google举办,并且每一年举办一次的大型编程比赛。<a href="https://codingcompetitions.withgoogle.com/hashcode" target="_blank" rel="noopener">今年的比赛官网</a></p>
<p>关于最终比赛结果,在一共10724份提交代码的小组中,我们最终积分是两千四百多万分,具体排名为2100名。感谢小组内所有大佬的付出,让我们4个小时的努力有所回报。</p>
<p><img src="/2020/02/20/HashCode-2020/2.PNG" alt=""></p>
<p>今年的<a href="https://github.com/SaberDa/HashCode-Competition-2020" target="_blank" rel="noopener">比赛题目和数据集见此</a>。该仓库包含今年的练习题目与正式题目。此篇文章主要陈述正式比赛题目中的数据集b和d的解法。</p>
<p>至于为什么只写这两个数据集的解法,因为是我写的。但是关于d的算法在数据集d上的实现并不好,然而在f上的实现超出预期。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
</entry>
<entry>
<title>LeetCode刷题记录-Part1</title>
<link href="https://saberda.github.io/2020/01/19/LeetCode%E5%88%B7%E9%A2%98%E8%AE%B0%E5%BD%95-Part1/"/>
<id>https://saberda.github.io/2020/01/19/LeetCode刷题记录-Part1/</id>
<published>2020-01-19T17:12:16.000Z</published>
<updated>2020-01-19T18:00:07.000Z</updated>
<content type="html"><![CDATA[<p>最近在找实习刷LeetCode,所以开了这么一个系列。一是巩固自己的记忆;二是发出来我的解题思路或者我找到的认为比较好的解题思路,想让大家集思广益,分析是否有更好的解法。熟人直接微信或者qq小窗即可,不熟的见上方邮箱。这个系列估计会在我找到全职工作后才会停止更新。</p><p>我觉得刷LeetCode最大的乐趣并不在于你WA了多次后迎来AC,或者一遍AC,而是AC后去讨论区,看看其他人的代码是怎么写的?看看有没有和你不一样的解题思路?看看其他人是怎样优化他们的代码?看看他们的编码方式有哪些值得学习。这些才是我认为LeetCode上最有用的地方。</p><a id="more"></a><p>或许你可以说这些东西GitHub上都有,甚至会有更好的代码云云,但是举个通俗的例子来讲,这就好比你在高中时刷的题一样,只有自己付出时间努力思考,才能高效理解老师或者同学给出的解题思路。</p><p>我是按照题目类型去刷题的,而非题目编号顺序。好比我现在一直在刷数组类的题目,即解题使用的数据结构是基于数组而非链表的。在这个大范围下,又细分了很多小类,比如 K-Sum、双指针之类的问题。这些类型网上都有很棒的开源仓库记录,大家如果也想按照这个方法刷题的话,用搜索引擎搜索“LeetCode 分类 GitHub”即可。</p><p>同时,我刷题的主要语言是C++,所以下面给出一些代码示例都是C++的。</p><h2 id="数组类"><a href="#数组类" class="headerlink" title="数组类"></a>数组类</h2><p>拿C++来说,如果想不费力气的去做这方面的题,这里建议事先复习好关于vector, unordered_map, map, prioity_queue等STL提供的数据结构。用一个适合该题目的数据结构解题会节省你大量的时间。</p><hr><h2 id="K-Sum-问题"><a href="#K-Sum-问题" class="headerlink" title="K-Sum 问题"></a>K-Sum 问题</h2><p><strong>问题描述:</strong>这类题会给你一个数组和一个数字,让你计算出这个数组中的n个值的和或者积等于或者大于给出的数字。LeetCode的第一题就是该类题目。</p><p><strong>解题:</strong>解题前要注意下题目给出的要求,观察是否要按照升序或者降序输出,或者是否要求不能有重复的子数组,或者要求是否连续。</p><p>一般这种题目我首先会使用双指针来解题。如果使用双指针过于复杂,我会考虑是否使用unordered_map,即hash-map解决。</p><p>这里拿259题做个例子。</p><pre><code>Given an array of *n* integers *nums* and a *target*, find the number of index triplets `i, j, k` with `0 <= i < j < k < n` that satisfy the condition `nums[i] + nums[j] + nums[k] < target`</code></pre><p><strong>Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">Input: nums = [-2,0,1,3], and target = 2</span><br><span class="line">Output: 2 </span><br><span class="line">Explanation: Because there are two triplets which sums are less than 2:</span><br><span class="line"> [-2,0,1]</span><br><span class="line"> [-2,0,3]</span><br></pre></td></tr></table></figure><p><strong>Follow up:</strong> Could you solve it in <em>O</em>(<em>n</em>2) runtime?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> int threeSumSmaller(vector<int>& nums, int target) {</span><br><span class="line"> int res = 0;</span><br><span class="line"> if(nums.size() <= 2) return res;</span><br><span class="line"> sort(nums.begin(), nums.end());</span><br><span class="line"> for(int i = 0; i < nums.size()-2; ++i) {</span><br><span class="line"> int left = i+1, right = nums.size()-1;</span><br><span class="line"> while(left < right) {</span><br><span class="line"> if(nums[i] + nums[left] + nums[right] < target) {</span><br><span class="line"> res += right-left;</span><br><span class="line"> left++;</span><br><span class="line"> }</span><br><span class="line"> else {</span><br><span class="line"> right--;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这道题是让我们从给出的数组中选出3个值,使这3个值的和小于给出的target,最后输出一共有几组符合上诉条件。像这种题目中已经暗示你需要排序的题目是很简单的,先把给出的数组排序,然后设立两个指针left和right分别指向头和尾。因为STL中的sort排序默认结果是从小到大的,所以排序后的数组越靠后的值越大。所以先固定一个值,然后分别移动left和right指针,如果和小于target,数量就+1同时左指针右移;反之右指针左移。</p><p>这道题是一个非常典型的使用双指针法求解K-Sum问题的例题。我们使用双指针时,这两个指针的初始位置一定要灵活,根据题意初始化位置。</p><hr><h2 id="区间问题"><a href="#区间问题" class="headerlink" title="区间问题"></a>区间问题</h2><p><strong>问题描述:</strong>这类问题一般会给你一个包含多个子数组的数组,然后针对这些数组创造的空间是否重合来对问题进行判断。</p><p><strong>解题:</strong>这种问题我们一般都需要对给出的数组进行排序。简单的情况可以只对原始数组进行排序,一些复杂的题解需要分别对数组的元素进行排序然后单独保存,之后再进行比较。解决这种问题时,用笔在纸上画图是最容易找出解题思路的,一般我们都需要在前一个区间的end和后一个区间的start中找交集。</p><p>这里我给出两道这方面的例题进行解释。</p><pre><code>56 Merge IntervalsGiven a collection of intervals, merge all overlapping intervals.</code></pre><p><strong>Example 1:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Input: [[1,3],[2,6],[8,10],[15,18]]</span><br><span class="line">Output: [[1,6],[8,10],[15,18]]</span><br><span class="line">Explanation: Since intervals [1,3] and [2,6] overlaps, merge them into [1,6].</span><br></pre></td></tr></table></figure><p><strong>Example 2:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Input: [[1,4],[4,5]]</span><br><span class="line">Output: [[1,5]]</span><br><span class="line">Explanation: Intervals [1,4] and [4,5] are considered overlapping.</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> </span><br><span class="line"> static bool compare(vector<int>& a, vector<int>& b) {</span><br><span class="line"> if (a[0] == b[0]) {</span><br><span class="line"> return a[1] < b[1];</span><br><span class="line"> }</span><br><span class="line"> return a[0] < b[0];</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> vector<vector<int>> merge(vector<vector<int>>& intervals) {</span><br><span class="line"> vector<vector<int>> res;</span><br><span class="line"> if (intervals.empty()) {</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line"> sort(intervals.begin(), intervals.end(), compare);</span><br><span class="line"> </span><br><span class="line"> int count = 0;</span><br><span class="line"> res.push_back(intervals[count]);</span><br><span class="line"> </span><br><span class="line"> for (int i = 1; i < intervals.size(); i++) {</span><br><span class="line"> if (res[count][1] >= intervals[i][0]) {</span><br><span class="line"> res[count][1] = max(res[count][1], intervals[i][1]);</span><br><span class="line"> } else {</span><br><span class="line"> res.push_back(intervals[i]);</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这个就是很明显的区间合并问题,比如在第一个example中,[1,3] 和 [2,6] 中,后一个区间的start在第一个区间内,此时就把两者合并为一个区间[1, 6]。此题的核心思路在于我们只需要比较当前区间的end是否大于等于下一个区间的start,如果是的话,则比较当前区间的end和下一个区间的end,并且取两者中的最大值作为当前区间的end,然后进行下一次循环,直到当前区间end小于下一个区间的start。</p><pre><code>253 Meeting Rooms IIGiven an array of meeting time intervals consisting of start and end times `[[s1,e1],[s2,e2],...]` (si < ei), find the minimum number of conference rooms required.</code></pre><p><strong>Example 1:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Input: [[0, 30],[5, 10],[15, 20]]</span><br><span class="line">Output: 2</span><br></pre></td></tr></table></figure><p><strong>Example 2:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">Input: [[7,10],[2,4]]</span><br><span class="line">Output: 1</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> int minMeetingRooms(vector<vector<int>>& intervals) {</span><br><span class="line"> </span><br><span class="line"> vector<int> start;</span><br><span class="line"> vector<int> end;</span><br><span class="line"> for (int i = 0; i < intervals.size(); i++) {</span><br><span class="line"> start.push_back(intervals[i][0]);</span><br><span class="line"> end.push_back(intervals[i][1]);</span><br><span class="line"> }</span><br><span class="line"> sort(start.begin(), start.end());</span><br><span class="line"> sort(end.begin(), end.end());</span><br><span class="line"> int room = 0;</span><br><span class="line"> int count = 0;</span><br><span class="line"> for (int i = 0; i < intervals.size(); i++) {</span><br><span class="line"> if (start[i] < end[count]) {</span><br><span class="line"> room++;</span><br><span class="line"> } else {</span><br><span class="line"> count++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return room; </span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这道题也是明显的区间判断问题,但是比上一道给出的例题复杂。这道题就需要我们分别对各个区间的start和end进行排序,然后通过计算比end大的start的数量给出最终答案。</p><p><img src="/2020/01/19/LeetCode刷题记录-Part1/1.png" alt=""></p><p>正如图解所示,第一行为start的排序结果,第二行为end的排序结果。如果下个会以的start大于当前的end,则表示需要第二间会议室来承办下个会议。</p><hr><h2 id="组合类"><a href="#组合类" class="headerlink" title="组合类"></a>组合类</h2><p><strong>问题描述:</strong>给出一个数组,要求解出所有满足条件的子数组。最典型的例子就是求子集。</p><p><strong>解法:</strong>这类题目利用回溯法递归来生成符合条件的子集。根据题目要求判断递归条件即可。</p><pre><code>90 Subset IIGiven a collection of integers that might contain duplicates, ***nums\***, return all possible subsets (the power set).</code></pre><p><strong>Note:</strong> The solution set must not contain duplicate subsets.</p><p><strong>Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line">Input: [1,2,2]</span><br><span class="line">Output:</span><br><span class="line">[</span><br><span class="line"> [2],</span><br><span class="line"> [1],</span><br><span class="line"> [1,2,2],</span><br><span class="line"> [2,2],</span><br><span class="line"> [1,2],</span><br><span class="line"> []</span><br><span class="line">]</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> </span><br><span class="line"> void backtrack(vector<int>& nums, int k, vector<int> subset, vector<vector<int>>& res) {</span><br><span class="line"> if (k == nums.size()) {</span><br><span class="line"> res.push_back(subset);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> backtrack(nums, k+1, subset, res);</span><br><span class="line"> subset.push_back(nums[k]);</span><br><span class="line"> backtrack(nums, k+1, subset, res);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> vector<vector<int>> subsetsWithDup(vector<int>& nums) {</span><br><span class="line"> </span><br><span class="line"> vector<vector<int>> res;</span><br><span class="line"> backtrack(nums, 0, vector<int> (), res);</span><br><span class="line"> </span><br><span class="line"> sort(res.begin(), res.end());</span><br><span class="line"> for (int i = 0; i < res.size(); i++) {</span><br><span class="line"> sort(res[i].begin(), res[i].end());</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> sort(res.begin(), res.end());</span><br><span class="line"> vector<vector<int>>::iterator iter;</span><br><span class="line"> iter = unique(res.begin(), res.end());</span><br><span class="line"> res.erase(iter, res.end());</span><br><span class="line"> </span><br><span class="line"> return res; </span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>注意,这道题目要求结果中不能包含重复的子集,所以主函数在递归的后面加了筛选条件剔除重复的子集。</p><hr><h2 id="连续子数组问题"><a href="#连续子数组问题" class="headerlink" title="连续子数组问题"></a>连续子数组问题</h2><p><strong>问题描述:</strong>给出一个数组,求符合条件的最长或者最短连续子数组。</p><p><strong>解法:</strong>这类问题使用滑窗法即可。至于什么是滑窗法,见下面这道例题。</p><pre><code>239 Sliding Window MaximumGiven an array *nums*, there is a sliding window of size *k* which is moving from the very left of the array to the very right. You can only see the *k* numbers in the window. Each time the sliding window moves right by one position. Return the max sliding window.</code></pre><p><strong>Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line">Input: nums = [1,3,-1,-3,5,3,6,7], and k = 3</span><br><span class="line">Output: [3,3,5,5,6,7] </span><br><span class="line">Explanation: </span><br><span class="line"></span><br><span class="line">Window position Max</span><br><span class="line">--------------- -----</span><br><span class="line">[1 3 -1] -3 5 3 6 7 3</span><br><span class="line"> 1 [3 -1 -3] 5 3 6 7 3</span><br><span class="line"> 1 3 [-1 -3 5] 3 6 7 5</span><br><span class="line"> 1 3 -1 [-3 5 3] 6 7 5</span><br><span class="line"> 1 3 -1 -3 [5 3 6] 7 6</span><br><span class="line"> 1 3 -1 -3 5 [3 6 7] 7</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> </span><br><span class="line"> int compare(vector<int>& nums, int i, int j) {</span><br><span class="line"> int max = i;</span><br><span class="line"> while (i < j) {</span><br><span class="line"> if (nums[max] < nums[i+1]) {</span><br><span class="line"> max = i+1;</span><br><span class="line"> }</span><br><span class="line"> i++;</span><br><span class="line"> }</span><br><span class="line"> return max;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> vector<int> maxSlidingWindow(vector<int>& nums, int k) {</span><br><span class="line"> vector<int> res;</span><br><span class="line"> if (nums.empty()) {</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line"> int first = 0;</span><br><span class="line"> int last = k-1;</span><br><span class="line"> while (last < nums.size()) {</span><br><span class="line"> int max = compare(nums, first, last);</span><br><span class="line"> res.push_back(nums[max]);</span><br><span class="line"> first++;</span><br><span class="line"> last++;</span><br><span class="line"> }</span><br><span class="line"> return res;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>239中的example很好的解释了什么是滑窗,只不过这道题固定了滑窗的长度。具体解法还是双指针,左指针确定滑窗左值,右指针确定滑窗右值。注意,一般这种要求给出连续数组的题目最好不要使用排序来打乱原始顺序。</p><hr><h2 id="其他一些分享"><a href="#其他一些分享" class="headerlink" title="其他一些分享"></a>其他一些分享</h2><p>这个模块是分享一些个人觉得很巧妙的解题思路。当然来自LeetCode的Discuss模块。<font color="red"><strong>抄代码不可耻,可耻的是不会抄袭好的代码,更可耻的是不去分析这些代码的逻辑与实现。</strong></font></p><p>这道题是求解中位数的一道题,并不怎么难,但是这个人的思路非常巧妙,他使用两个堆,一个大顶堆一个小顶堆,来表示排序后数组。中位数要么是大顶堆的顶值,要么是两个堆顶值的和的一半。</p><pre><code>295 Find Median from Data streamMedian is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.</code></pre><p>For example,</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">[2,3,4]`, the median is `3</span><br><span class="line">[2,3]`, the median is `(2 + 3) / 2 = 2.5</span><br></pre></td></tr></table></figure><p>Design a data structure that supports the following two operations:</p><ul><li>void addNum(int num) - Add a integer number from the data stream to the data structure.</li><li>double findMedian() - Return the median of all elements so far.</li></ul><p><strong>Example:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">addNum(1)</span><br><span class="line">addNum(2)</span><br><span class="line">findMedian() -> 1.5</span><br><span class="line">addNum(3) </span><br><span class="line">findMedian() -> 2</span><br></pre></td></tr></table></figure><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br></pre></td><td class="code"><pre><span class="line">class MedianFinder {</span><br><span class="line">public:</span><br><span class="line"> /** initialize your data structure here. */</span><br><span class="line"> MedianFinder() {</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> void addNum(int num) {</span><br><span class="line"> if (left.empty() || num <= left.top()) {</span><br><span class="line"> left.push(num);</span><br><span class="line"> } else {</span><br><span class="line"> right.push(num);</span><br><span class="line"> }</span><br><span class="line"> if (left.size() > right.size()+1) {</span><br><span class="line"> int temp = left.top();</span><br><span class="line"> left.pop();</span><br><span class="line"> right.push(temp);</span><br><span class="line"> }</span><br><span class="line"> if (left.size() < right.size()) {</span><br><span class="line"> int temp = right.top();</span><br><span class="line"> right.pop();</span><br><span class="line"> left.push(temp);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> double findMedian() {</span><br><span class="line"> </span><br><span class="line"> double res;</span><br><span class="line"> if (left.size() == right.size()) {</span><br><span class="line"> res = (left.top() + right.top()) / 2.0;</span><br><span class="line"> } else {</span><br><span class="line"> res = left.top();</span><br><span class="line"> }</span><br><span class="line"> return res;</span><br><span class="line"> </span><br><span class="line"> }</span><br><span class="line">private:</span><br><span class="line"> priority_queue<int> left;</span><br><span class="line"> priority_queue<int, vector<int>, greater<int> > right;</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>代码由两部分组成,第一部分是将数组中的元素分别入堆,第二部分是计算中位数的值。</p><p>分享的第二道题是昨晚新发现的一种map处理方式。一般我们使用map时,都习惯将key作为序号,value作为保存的值。但是这道题反其道而行之,用key来保存值,value表示序号,即长度。</p><pre><code>325 Maximum Size Subarray Sum Equals kGiven an array *nums* and a target value *k*, find the maximum length of a subarray that sums to *k*. If there isn't one, return 0 instead.</code></pre><p><strong>Note:</strong><br>The sum of the entire <em>nums</em> array is guaranteed to fit within the 32-bit signed integer range.</p><p><strong>Example 1:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Input: nums = [1, -1, 5, -2, 3], k = 3</span><br><span class="line">Output: 4 </span><br><span class="line">Explanation: The subarray [1, -1, 5, -2] sums to 3 and is the longest.</span><br></pre></td></tr></table></figure><p><strong>Example 2:</strong></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">Input: nums = [-2, -1, 2, 1], k = 1</span><br><span class="line">Output: 2 </span><br><span class="line">Explanation: The subarray [-1, 2] sums to 1 and is the longest.</span><br></pre></td></tr></table></figure><p><strong>Follow Up:</strong><br>Can you do it in O(<em>n</em>) time?</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><span class="line">class Solution {</span><br><span class="line">public:</span><br><span class="line"> int maxSubArrayLen(vector<int>& nums, int k) {</span><br><span class="line"> if (nums.empty()) {</span><br><span class="line"> return 0;</span><br><span class="line"> }</span><br><span class="line"> unordered_map<int, int> m;</span><br><span class="line"> int sum = 0;</span><br><span class="line"> int ans = 0;</span><br><span class="line"> for (int i = 0; i < nums.size(); i++) {</span><br><span class="line"> sum += nums[i];</span><br><span class="line"> if (!m.count(sum)) {</span><br><span class="line"> m[sum] = i;</span><br><span class="line"> }</span><br><span class="line"> if (sum == k) {</span><br><span class="line"> ans = max(ans, i+1);</span><br><span class="line"> }</span><br><span class="line"> if (m.count(sum-k)) {</span><br><span class="line"> ans = max(ans, i-m[sum-k]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return ans;</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这道题是让我们求出最长的连续自序列,使该子序列的值等于k。这里我使用哈希表来满足最后的O(n)。key里存储的是当前子序列的和,value储存的是当前连续子序列的长度。该子序列是从数组初始元素开始,每次递加一个长度。然后我们只需要判断当前的和减去给出的k,然后判断这个差是否存在于表里即可。如果存在的话,用当前长度减去差所储存的value值就是当前符合条件的连续子序列长度。</p><hr><h2 id="建立个人的代码仓库"><a href="#建立个人的代码仓库" class="headerlink" title="建立个人的代码仓库"></a>建立个人的代码仓库</h2><p>这里的代码仓库指的并不是记录你解题的仓库,而是在你刷题过程中,经常使用的或者一些巧妙的算法实现。比如我就把用大顶堆和小顶堆来计算中位数的代码存进了这个仓库。还存储了一些经常使用的操作符重载和一些题目都会用到的模板。这里分享两个。</p><p>第一个是对二维vector进行排序的函数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"> static bool compare(vector<int>& a, vector<int>& b) {</span><br><span class="line"> if (a[0] == b[0]) {</span><br><span class="line"> return a[1] < b[1];</span><br><span class="line"> }</span><br><span class="line"> return a[0] < b[0];</span><br><span class="line"> }</span><br><span class="line">// 调用</span><br><span class="line">vector<vector<int>> res;</span><br><span class="line">sort(intervals.begin(), intervals.end(), compare);</span><br></pre></td></tr></table></figure><p>第二个是我在做组合问题时总结出来的一个递归模板。等我再遇到组合问题时,直接上代码库中找到这段代码粘贴过去在更改递归判断条件即可。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">vector<vector<int>> main(...){</span><br><span class="line"> vector<vector<int>>res; // Store the result, could be other container</span><br><span class="line"> backtrack(res, ...); // Recursion function to fill the res</span><br><span class="line"> return res;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">void backtrack(vector<vector<int>>& res, int cur, ..., vector<int>vec){</span><br><span class="line"> if(meet the end critria, i.e. cur reach the end of array){ </span><br><span class="line"> //vec could be a certain path/combination/subset</span><br><span class="line"> res.push_back(vec);</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> // Current element is not selected</span><br><span class="line"> backtrack(res, cur+1, ..., vec);</span><br><span class="line"> // Current element is selected</span><br><span class="line"> vec.push_back(cur); // or could be vec.push_back(nums[cur]), vec.push_back(graph[cur]);</span><br><span class="line"> backtrack(res,cur+1, ..., vec);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>灵活运用你的代码库,并且不断的为其添加复用性高、灵活性高的代码模板,会极大提高刷题效率。</p>]]></content>
<summary type="html">
<p>最近在找实习刷LeetCode,所以开了这么一个系列。一是巩固自己的记忆;二是发出来我的解题思路或者我找到的认为比较好的解题思路,想让大家集思广益,分析是否有更好的解法。熟人直接微信或者qq小窗即可,不熟的见上方邮箱。这个系列估计会在我找到全职工作后才会停止更新。</p>
<p>我觉得刷LeetCode最大的乐趣并不在于你WA了多次后迎来AC,或者一遍AC,而是AC后去讨论区,看看其他人的代码是怎么写的?看看有没有和你不一样的解题思路?看看其他人是怎样优化他们的代码?看看他们的编码方式有哪些值得学习。这些才是我认为LeetCode上最有用的地方。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="LeetCode" scheme="https://saberda.github.io/tags/LeetCode/"/>
</entry>
<entry>
<title>前方乃是未踏之旅</title>
<link href="https://saberda.github.io/2019/08/13/%E5%89%8D%E6%96%B9%E4%B9%83%E6%98%AF%E6%9C%AA%E8%B8%8F%E4%B9%8B%E6%97%85/"/>
<id>https://saberda.github.io/2019/08/13/前方乃是未踏之旅/</id>
<published>2019-08-14T01:45:27.000Z</published>
<updated>2019-08-17T23:03:44.000Z</updated>
<content type="html"><![CDATA[<pre><code>世界正以前所未有的巨大幅度从远处逼近。 --三岛由纪夫</code></pre><h2 id="前方乃是未踏之旅"><a href="#前方乃是未踏之旅" class="headerlink" title="前方乃是未踏之旅"></a>前方乃是未踏之旅</h2><p>这是Fate/go国服三周年的标题,作为忠实的“月球人”,这个游戏已然陪我度过了1000天整,凑巧的是,这第1000天,也是我远赴大洋彼岸的日子。</p><p>现在是二〇一九年八月十七日晚,再过十二个小时,我就登上了前往华盛顿的飞机,开始人生新的篇章。此时我正在北京首都机场附近的酒店中写下这篇文章,望多年之后的某个闲暇之余,我无意识的再次翻到它,看看这个曾经稚嫩的自己,看看这颗是否依旧执着的心。</p><a id="more"></a><h2 id="出行与决绝"><a href="#出行与决绝" class="headerlink" title="出行与决绝"></a>出行与决绝</h2><p>自大二暑假开始,我准备赴美读研已经过去了两年有余,并且成功实现了当初定下的目标,进入一个不好不坏的学校,开始紧张忙碌的研究生学习生涯。</p><p>虽说已经做了这么长时间的准备和努力,并在嘴边挂了无数次的“我要出去”,但是真的走到了今天,与家人父母朋友同学告别即将远行的日子,内心是激动的,是沉重的,是充满希冀的,也是忐忑不安的。我不知道自己有没有做好在一个非母语国家独立生活的准备,也不知道在未来自己是否还能坚持现在的目标,唯一知道的就是,现在的我只能咬钉嚼铁负重前行。</p><p>在我签证期间,正是中美贸易战最紧张的时候,我的计算机专业自然而然的被标记上了“敏感专业”的头衔,我自信满满的认为有过一次美国签证的我签证会很顺利,然而区区人力之行,充其量不过是秋风中飘零的一片树叶而已,我被check了。还好,我室友凯齐也被check了,至少有人陪我,我心里很平衡。</p><p>今天早上离家前,特意去看了看家人,爷爷的拥抱姥爷的送别,大姨老舅他们的那句“注意安全锻炼身体”,父母在安检区域外的笑着挥手告别,我都不知道该如何应对,只能鼻头酸酸的笑着说“没事,我走了”后转身迈进候机楼。虽然在互联网高度发展的今天,随时随地可以和他们视频聊天,但不知下次拥抱牵手是何时。</p><p>人生就是不断的离别和重逢,我别无选择。</p><h2 id="留恋与憧憬"><a href="#留恋与憧憬" class="headerlink" title="留恋与憧憬"></a>留恋与憧憬</h2><p>高考后那个夏天,在我走出成电自主招生物理考试的考场时,我已经知道了成电与我无缘。机缘巧合之下,认识了当时西电在长春招生的李希文李老师,在他的指导下,我报考了西电软件学院。在本科期间,李老师给予我的帮助和指引很多,感谢李老师对我的付出。</p><p>四年弹指一挥间,西电软院在历史的进程下与计算机院合并了,随之消亡的就是我在西电最美好的回忆–SSSTA软院科协。说是消亡并不准确,只是和计算机院科协合并了而已,但是那个陪我度过了无数欢乐时光的G302不在了,我对西电唯一的留恋也消逝了。</p><p>软院科协是一个神奇的地方,进了里面感觉像回家一样,各个都是人才,说话又好听,超喜欢在里面。拉我入坑的是入学时认识的一个长春研究生学长陈昇辉,真正引我入行的是孙铭和大左,感情最深的徐志宇老变态,能说会道的刘皇叔,最佩服的是徐普和尹大人,可爱的滔滔和经常打游戏的黄大人,还有当时成天做我对面的胖子王亚彬,后来的室友谢仑辰,混的最熟的姚翔宇陈嘉睿和张师睿,以及最后成为现充的游朝阳和宋仕博。除了最开始的那三个室友,这些人陪我度过了大学最欢乐的时光。</p><p><img src="/2019/08/13/前方乃是未踏之旅/1.JPG" alt=""></p><p>有些自大的说,曾当过一年的主席的我对其有着稍微的贡献,当时主张成立的游戏组目前已成发展成学校内第一家独立游戏工作室–Nova,并且有着很多优秀的学弟开发出了很多杰出的作品,内心还是有着些许的自豪的。</p><p>当然我的那三个室友也很可爱,虽然刚入学时和他们有些小矛盾,但是在时间的作用下相互了解,对我而言他们已然成了家人,考研上岸但有些脱发的严浩奇,喜欢说唱重感情的程伟,和不断被我们说媒的王光辉,与你们相处的时间很短,留下的记忆很长。</p><p>当然最舍不的是从小玩到大的那些伙伴,虽然在北京签证时见到了韩静轩和王岑,但是那也是近三个月之前的事情了,也不知道下次相见是什么时候,你们过得如何。</p><p>哥伦布曾言:“不问脚下,只看前方”。我不知道未来等待我的是什么,但是我知道我选择的这条路很难走;我不知道未来我能走到多远,但是我知道接下来的这条路,有人陪我一起走完。非常庆幸,高中同桌于凯齐跟我报了同一所学校,尽管专业不同,但是未来这段求学生涯不会那么孤独。</p><p>未来是缥缈无根的,同时未来是满怀憧憬的。我不能说未来的自己一定是自己希望成为的那个自己,但是我敢说未来的自己仍然对技术充满激情,对文学充满热爱。</p><p>经过长时间的挣扎,我在目前这个机器学习神经网络最火的时代,放弃了这个方向。我从大二就开始学习机器学习算法,那时国内的大环境还没有如此般高潮,相比于现在的神经网络如过江之鲫,那时正方兴未艾。</p><p>印象特别深刻的就是,大二时在学校举办的“星火杯”答辩中,老师问我是否会继续研发这个应用时,我说“不会,目前正打算深入学习机器学习”。回忆起当时老师被我说的一脸懵逼,然后到现在全校都在深度学习,想想都十分可笑。</p><p>大数据技术的成熟和硬件算力的发展,使得在上个世纪提出的机器学习算法在如今蓬勃发展,我还是那个观点,现在的我错过了最好的时代,如果真的想继续在这个领域发展,读博深造是必然趋势。不想读博的我自然不敢去尝试以一个硕士身份去应聘竞争逐年激烈的AI岗位。</p><p>说的如此冠冕堂皇,实际上就是怕找不到工作罢了。也许陈嘉睿的观点是对的,转身投入到基础甚至底层的开发研发中未免不是一个不好的选择。</p><p>当然,四年的本科学习教会了我不止书本上的那些东西。作为一名真正的程序员,除了不止要掌握多门程序语言和多种数据库、了解前后端相关技术、通晓网络七层架构、知道TCP/IP的三次握手和四次挥手、编写漂亮易读的代码、设计优美简洁的架构,还要解决研发、程序运行和产品上线中遇到的各种问题,而且要努力做到以最小的代价来解决问题外,更重要的是要<u><strong>系统的构建自己的知识</strong></u>,使自己掌握的东西不局限与某个点上;<u><strong>要去主动选择技术方向而不是被动等待</strong></u>;<u><strong>在保持对新技术热爱的同时不过与保守</strong></u>;以及<u><strong>去积累学习或者事业道路上的经验</strong></u>。</p><p>编程技巧和程序设计能力固然重要,但是比其<u><strong>更重要的是解决问题的稳狠准</strong></u>。现在国内有些公司过于追求年轻人的学习能力,而忽视了老手依靠时间、经验和惨痛的教训积累下的经验。这样做确实会缩短研发新产品的时间,但是会逐渐埋下安全隐患。</p><p>我出去的目的当然是顺利毕业后斩获大厂offer,从此走上人生巅峰。与我竞争的同龄人不乏天才,我自认没什么天赋,只能依靠勤能补拙这个笨道理。我无法对未来的结果做出保证,但是我可以保证无论未来会是怎样的结果,我都能问心无愧的对自己说“你尽力了”而已。</p><h2 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h2><p>再过一天不到的时间,我就已经到达的地球的另一边,开始新的冒险新的征程。站在这个特殊的时间点上,回首,感慨颇多。</p><p>这一路走来,说不上多辛苦,但是庆幸的是我心里很清楚,正是因为那一点点的在乎,才使我执着这段旅途;正是因为那一点点的执着,才使我忍得住寂寞一个人聊胜于无;正式因为那一点点的寂寞,才使我在这滚滚浊世绝不把梦交出,尽管过程多残酷。</p><p>心偶尔酸酸的,汗与泪是咸咸的,但是希望是暖暖的。未来必定曲折忐忑,但是总有一天这些都将被抚平。人的生命如果只有一次的话,那总是需要去看写不同的风景,遇到不同的人,这样才能让不能重来的游戏玩的尽兴些。</p><p>是夜,几人相思,几人眠,几人忧愁,几人怜。</p>]]></content>
<summary type="html">
<pre><code>世界正以前所未有的巨大幅度从远处逼近。 --三岛由纪夫
</code></pre><h2 id="前方乃是未踏之旅"><a href="#前方乃是未踏之旅" class="headerlink" title="前方乃是未踏之旅"></a>前方乃是未踏之旅</h2><p>这是Fate/go国服三周年的标题,作为忠实的“月球人”,这个游戏已然陪我度过了1000天整,凑巧的是,这第1000天,也是我远赴大洋彼岸的日子。</p>
<p>现在是二〇一九年八月十七日晚,再过十二个小时,我就登上了前往华盛顿的飞机,开始人生新的篇章。此时我正在北京首都机场附近的酒店中写下这篇文章,望多年之后的某个闲暇之余,我无意识的再次翻到它,看看这个曾经稚嫩的自己,看看这颗是否依旧执着的心。</p>
</summary>
<category term="LIFE" scheme="https://saberda.github.io/categories/LIFE/"/>
</entry>
<entry>
<title>毕设填坑笔记-Git lfs的使用</title>
<link href="https://saberda.github.io/2019/03/31/%E6%AF%95%E8%AE%BE%E6%8C%96%E5%9D%91%E7%AC%94%E8%AE%B0-Git-lfs%E7%9A%84%E4%BD%BF%E7%94%A8/"/>
<id>https://saberda.github.io/2019/03/31/毕设挖坑笔记-Git-lfs的使用/</id>
<published>2019-03-31T23:49:43.000Z</published>
<updated>2019-03-31T12:44:06.000Z</updated>
<content type="html"><![CDATA[<pre><code>自从自己拟了这个毕设后,感觉自己把自己坑的很惨,上次训练模型把电脑烧了后,总是能碰上奇奇怪怪的问题,所以打算开个新篇幅来记录我是如何填上毕设路上的自己给自己挖的坑。</code></pre><p>我的毕设选题是关于用GAN生成图像方面的,为了记录中间的过程,我打算把每次训练生成的epoch都存下来;同时还想用git来管理代码版本,这时摆在我眼前的首要问题就是如何处理图片这些大文件。</p><p>使用一些开源的框架和成熟的模型,可以从一定程度上减小自己所需要的训练集体积,但是招架不住每次 pull/push 时所面对的动辄几百兆的传输列表,于是便在网上找到了git基于大文件传输的扩展 – <a href="https://git-lfs.github.com" target="_blank" rel="noopener">git lfs(Large File Storage)</a>。</p><a id="more"></a><h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>lfs将标记的大文件保存至另外的仓库,而主仓库仅保留其轻量级指针。</p><p>当你检出版本时,根据指针的变化情况更新对应的大文件,而不是本地保存所有版本的大文件。</p><p>简单来讲,就是把指定需要lfs进行管理的文件替换成了一个指针文件交给git来进行版本管理。</p><p><img src="/2019/03/31/毕设挖坑笔记-Git-lfs的使用/1.png" alt=""></p><h2 id="实际过程"><a href="#实际过程" class="headerlink" title="实际过程"></a>实际过程</h2><p>在执行 pull/push 等操作中,lfs通过<strong>lfs服务器</strong>把这些文件的真身给下载或者上传。</p><p>通过这个手段,使得本地仓库体积减小,避免随着这些文件的版本增多而出现仓库体积剧烈膨胀的情况。</p><h2 id="安装过程"><a href="#安装过程" class="headerlink" title="安装过程"></a>安装过程</h2><h2 id="macOS"><a href="#macOS" class="headerlink" title="macOS"></a>macOS</h2><pre><code>//使用homebrew安装brew install git-lfs</code></pre><h2 id="Windows"><a href="#Windows" class="headerlink" title="Windows"></a>Windows</h2><p>前往<a href="https://github.com/git-lfs/git-lfs/releases" target="_blank" rel="noopener">此处</a>下载</p><p>更详细安装过程见<a href="https://help.github.com/en/articles/installing-git-large-file-storage" target="_blank" rel="noopener">官方文档</a></p><h2 id="使用方法"><a href="#使用方法" class="headerlink" title="使用方法"></a>使用方法</h2><p>把需要用lfs管理的文件添加到追踪列表里,语法支持简单正则,比如添加所有的png文件:<code>git lfs track '*.png'</code></p><p>此时,仓库的根目录下会自动创建 <code>.gitattribute</code>文件,里面记录了使用lfs追踪的文件,该文件需要添加到git远程仓库中进行管理。</p><h2 id="具体步骤"><a href="#具体步骤" class="headerlink" title="具体步骤"></a>具体步骤</h2><ol><li><p>安装后,执行</p><pre><code>git lfs install </code></pre><p> 开启lfs功能,上诉语句只需要执行一次</p></li><li><p>执行</p><pre><code>git lfs track 'fileName'</code></pre><p> 进行大文件追踪</p></li><li><p>提交,别忘记提交 .gitattribute 文件</p></li><li><p>提交后可执行</p><pre><code>git lfs ls-file</code></pre><p> 查看当前追踪列表</p></li><li><p>push到远程仓库后,使用lfs追踪的文件会以 “Git LFS”的形式显示</p></li><li>clone时,执行 <code>git clone</code> 或者 <code>git lfs clone</code> 都行</li></ol>]]></content>
<summary type="html">
<pre><code>自从自己拟了这个毕设后,感觉自己把自己坑的很惨,上次训练模型把电脑烧了后,总是能碰上奇奇怪怪的问题,所以打算开个新篇幅来记录我是如何填上毕设路上的自己给自己挖的坑。
</code></pre><p>我的毕设选题是关于用GAN生成图像方面的,为了记录中间的过程,我打算把每次训练生成的epoch都存下来;同时还想用git来管理代码版本,这时摆在我眼前的首要问题就是如何处理图片这些大文件。</p>
<p>使用一些开源的框架和成熟的模型,可以从一定程度上减小自己所需要的训练集体积,但是招架不住每次 pull/push 时所面对的动辄几百兆的传输列表,于是便在网上找到了git基于大文件传输的扩展 – <a href="https://git-lfs.github.com" target="_blank" rel="noopener">git lfs(Large File Storage)</a>。</p>
</summary>
<category term="编程" scheme="https://saberda.github.io/categories/%E7%BC%96%E7%A8%8B/"/>
<category term="填坑笔记" scheme="https://saberda.github.io/tags/%E5%A1%AB%E5%9D%91%E7%AC%94%E8%AE%B0/"/>
</entry>
<entry>
<title>入职半月,初窥门径</title>
<link href="https://saberda.github.io/2019/03/09/%E5%85%A5%E8%81%8C%E5%8D%8A%E6%9C%88%EF%BC%8C%E5%88%9D%E7%AA%A5%E9%97%A8%E5%BE%84/"/>
<id>https://saberda.github.io/2019/03/09/入职半月,初窥门径/</id>
<published>2019-03-09T21:56:47.000Z</published>
<updated>2019-03-09T13:45:57.000Z</updated>
<content type="html"><![CDATA[<pre><code>如题,这篇文章写于入职后两周零两天,地点深圳图书馆。本来是想昨天写的,昨天与我对接的后端下午过节去了,原本应该是个清闲的下午,然而刚刚整理完开发文档和填坑笔记后,就来了新需求,所以挪到今天写了。</code></pre><p>本人以实习生身份进入一家公司,<del>利益相关,匿了</del>,工作两周有余,感慨颇深,遂写下此文。</p><p>我准备从工作、生活、学习三个方面总结体会。</p><h2 id="实习工作,初窥门径"><a href="#实习工作,初窥门径" class="headerlink" title="实习工作,初窥门径"></a>实习工作,初窥门径</h2><a id="more"></a><p>如果原计划顺利的话,我应该是不会进入我目前这家公司的,但是生活就是充满了变化与不确定。年前头条挂在了算法上,平安科技通知我他们实习生不要211,<del>cao</del>,然后腾讯的内推还一直没有消息,等过完了十五终于受不了了,索性来到了这里。</p><h2 id="公司环境"><a href="#公司环境" class="headerlink" title="公司环境"></a>公司环境</h2><p>我司是一家国家持股的混合型公司,条件很nice,工位很大<del>跟我租的房间差不多</del>,单位提供早中晚饭,而且每月给工牌里打饭钱,基本属于免费。工作之后,我甚至做到了在学校时都无法保证的“每天都去吃早饭”。</p><p>因为是合租,而且通勤时间长,索性每天就起的很早(不用和其他人抢厕所),然后慢慢悠悠的坐地铁(有时是公交,具体看心情),溜达到公司。到公司的时候基本没几个人,慢悠悠的吃完早饭,冲杯美式,开始工作。</p><p>每天下午还有下午茶,就是一些糕点什么的,我运气很好,下午茶的摆放地点就在我身后不到两米的地方,脚一蹬地,椅子过去,伸手一拿,再蹬回来,署实方便。</p><p>公司坐落在深圳湾生态科技园,写字楼三楼是一个超级大的平台,把这片的所有写字楼都链接了起来,绿化什么的也做得很好。公司二楼还有一个露天庭院,有时晚上吃完饭就去那里坐着吹吹风。但是现在发现了离我工位不远的地方就有一个露天阳台,这几天都是去那里吹风。</p><p>公司二楼还有一个水吧,之前去了一次看了眼价格就再也没去过。</p><h2 id="公司文化"><a href="#公司文化" class="headerlink" title="公司文化"></a>公司文化</h2><p>整个公司采取的是全透明办公性质,组长和院长甚至董事长的办公地点都是能直接用肉眼看到的。公司文化也不错,第一天组长就反复跟我说:“这里很开放的,没必要那么拘谨。”</p><p>虽然才入职两周,说完全了解公司的文化是不可能的,但是这里的加班文化署实让人震惊。原则上是晚上六点就下班了,然而我几乎没有一天离开公司的时间早于8点,最晚的一天出公司门是十点半。还行,他们挺照顾我的,没让我周日来加班,哭了。</p><p>我司每年都会举办技术分享大会,上到董事长下到基层,上去做技术分享会,历时三天,我很幸运刚入职就赶上了这个会议。但是因为我司不是纯正的互联网公司,很多分享的知识领域对我来说是盲区,就第一天听了下董事长的分享和我所属部门的头头的分享后就再去没去了。</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><blockquote><p>实习生不是活少,是工资少</p></blockquote><p>上面那句话是昨天秦神说的,我当时犹如醍醐灌顶,把心里稍许的不平衡打消了。</p><p>入职前,家里人都告诉我,实习生没什么活的,也就是端端茶倒倒水,基本不会充当生产力的。然后入职当天上午,就直接参与了某个外包公司与我们单位的一个项目的对接,下午由于原本的头去开会了,前端这一部分就我独自参与的对接。</p><p>那时,我还天真的认为,我也就是给别人打打下手,应该不会让我直接接手的。显然,我没能理解组长见到我时说的那句话“诶,你会前端啊,正好我们缺人”。现在回想起来,甚至感觉当时组长的眼睛都在冒光。</p><p>是的,我作为组里唯一会些前端的人,承担起了项目对接后的前端负责人,说是负责人实际就我一个人。我前段就一个学校大作业的半吊子水平,没办法,只能硬着头皮上了。</p><p><img src="/2019/03/09/入职半月,初窥门径/1.JPG" alt=""></p><p>俗话说得好,<strong>一切能在互联网上找到答案的问题都不是问题</strong>,还有一句话是,<strong>除非你在网上找了很久都没有找到答案,再去问别人</strong>。望各位互联网的同僚深刻记住这句话。</p><p>说是前端,实际上是微信小程序,我之前只是知道微信小程序主要通过js开发,以为跟vue之类的框架类似,但当我打开官方文档的那一刻起,就发现我错了。</p><p>微信小程序说是用js开发,但是本质上与iOS和安卓开发没有多大区别,而且和传统的前端开发也有很大区别,虽然看着差不多。许多操作需要调用微信写的接口。简单说,就是用传统的前端方式来写iOS app。</p><p>这部分我之前是打算开个新坑更新博客的,但是后期发现,官方文档虽然说得不是很详细,但是网上这方面的博文已经很多甚至很成熟,自己重新写这些意义不大,索性就直接在本子上记录下一些奇奇怪怪的坑,没有整理成文章。</p><p>也不能说运气不好吧,刚入职就赶上了一个项目的落地,这个落地项目还是一个超级大的项目的先前部队,所以公司上层很重视这个落地的进展情况。而且这个部门里就我一个稍微有些前端基础,很自然的所有前端变动都落到我的头上。</p><p>不能说需求总是变动吧,单基本每天都有两三个新需求,有些需求简单,有些复杂的需要改动多个页面的逻辑。而且我也是刚开始学习小程序,公司里也没有做过小程序的前辈,有些不懂的我只能问同学,<strong>在这里再一次感谢提供帮助的游总和程总,等回学校请你两吃饭</strong>。</p><p>剩下的关于小程序这部分我放到后面去说。</p><p>职场新人,很多东西我都不了解,不会沟通不会拒绝,导致了入职第一周的周五身体出现了状况。当时是晚上八点左右,刚跟后端改完一个离奇的bug,产品(组长)过来跟我说今晚上线,上线前再把这个功能写出来。当时顿时感觉压力山大,再加上晚上没有吃饭,最后诊断是低血糖加上心理变化,心脏出现了应激性反应,反正当晚是直接去医院了,在医院待到11点多才离开。</p><p>这也是我需要学习的一个地方吧,调整心理并且敢于沟通善于沟通。</p><p>到了第二周,情况就好多了,虽然每天的新需求不断,但随着对小程序的不断理解,并加上和产品积极沟通,每天虽然很累但是感觉很充实。</p><p>而且我司不是纯正的互联网公司,就连我所属的部门都不是纯正的互联网部门,面对一个即将上线的产品,我站在外人的角度发现各个方面都有缺陷。</p><ul><li>首先是一个产品从开发到发布的环节链的缺失。</li></ul><p>我接手的小程序之前是由外包公司写的,我入职时正好是交付阶段,之后所有的新需求都交给我这个新人来写,而且前端部门就我一个,从某种角度来说我的官挺大的。然后是测试,缺少完整的测试环境与流程。我的工作在某一方面还包含部署,但是令我惊讶的是,竟然没有测试环境,我部署的话是直接部署到生产环境,当时我就想,这要是某个功能bug没有测试出来,出事了,那我就要被迫离职了啊。</p><ul><li>其次是需求的强制性下达。</li></ul><p>正常产品和开发中,至少是有一个人等级是对等的,这样讨论需求上能从开发的角度谈一谈实现该需求的可行性。但是我司是上层给下层下需求,有些需求难以实现,甚至需要将整个产品重构,这种事情放在互联网公司里可是要郑重决策的。</p><ul><li>缺少完整的开发团队与运营团队。</li></ul><p>按照工作性质来讲,我们部门算是算法研发部门,但是自从这个项目上线以来,他们除了承担算法,还包括后端,测试和运营。我现在接手的项目只是个大项目的前置落地项目,在其后面还有一个超级复杂的应用需要落地,虽说这个落地已经在我离职后了,但是到时若没有一个完整的开发团队,其中包含iOS和安卓的移动端、后端、测试、运营,落地的难度会及其的大,单单只靠外包公司是不行的。</p><ul><li>各个版本的需求说明不清楚。</li></ul><p>就拿我的任务来讲,缺少明确的版本说明,不知道下个版本要上线哪些功能,只知道这些需求的优先级,这让我写的时候就很迷茫。有些需求有很高的优先级,但是实现起来需要一周左右的时间,其次版本上线时间不明确,搞得我不知道是先写短时间内就能完成的,还是其他什么。</p><h2 id="为人处世,初窥门径"><a href="#为人处世,初窥门径" class="headerlink" title="为人处世,初窥门径"></a>为人处世,初窥门径</h2><p>以这个实习为界,在此之前所有的生活都是在象牙塔里,并没有那么多的规矩,随心所欲。但我发现,一旦到了职场上,你会不自觉的开始思考,我应该怎样称呼对方,用什么样子的方式去向对方表达自己的见解。</p><p>组里的同事都叫组长某博,我刚开始不知道啊,就知道个名字,索性就从组长变为了旭哥,正好我叫我高中班主任也这么叫,挺顺嘴的,就这么叫下去了。然后组里另一个产品,刚开始我直接张口“钱姐”,当我把这条消息发过去时就意识到了把对方叫老了,之后改口了,但是还是很尴尬的。</p><p>这类的事情到第二周还时有发生,之前的前端负责人组长叫他华强,但是对方比我大很多,我不能这么叫吧,索性叫“朱哥”,没办法,听上去怪怪的。</p><p>然后想说的就是之前说的沟通问题,特别是有关工作的沟通问题。</p><p>现在我摸索到了一些敲门,当上面让你完成一份文件或者ppt时,立马跟上去问截止时间和具体内容,以及其他一些需要注意的事情。着手工作后,遇到的或者间接遇到的问题,或者觉得之前没有问清楚的地方,或者你不明白的地方,一定要及早及时的询问,等一切都问明白了再动手。不要等到最后提交时才发现存在问题。提交之后马上问一下,或者估摸个时间对方看完了再问一下,所完成的工作是否存在什么问题。</p><blockquote><p>事前、事中、事后的及时沟通很重要。主动沟通是提高效率的关键</p></blockquote><p>还有关于开发方面上的沟通。我现在接手的前端,虽说按之前的那种方式开发没问题,但是在上周实现的某一个需求上,我深刻发现了与后端充分沟通的必要性。那个需求是更改支付逻辑,并且还要判断付款人的性质,根据不同性质执行不同的支付操作。</p><p>拿到需求后,我跟后端讨论了大约能有40分钟,把从传值到最后实现的各个细节都讨论清楚了。本来这是个大需求,而且涉及到支付后的一系列操作,但是充分沟通后感觉虽然复杂但是写起来特别简单,最后的实现也是基本做到了bug free。</p><p>其次就是和产品的沟通。虽然产品是我的组长,是上级,但是该沟通的还是要沟通,需求觉得不合理就要提出来,虽然每次跟组长讨论需求时都怕怕的,但还是要硬着头皮上。只有把需求讨论清楚了,才能写的舒服。而且一定要和产品亲自讨论需求,不要从后端或者其他人那里得到需求就去写。</p><p>举个例子,我接到的有个需求是让客户录入他的公司和部门名称。这个我刚开始是从后端那里得知的,我就很简答的用了input组件,没想到写完后给组长交付,他跟我说要做picker(下拉式菜单),所以大半个下午的工作需要重做。从那之后,所有的需求细节我都是直接去找产品询问。</p><p>生活是最好的老师,有些事情只有你踩过坑之后,才有所收获。技术亦如此。</p><h2 id="微信小程序,初窥门径"><a href="#微信小程序,初窥门径" class="headerlink" title="微信小程序,初窥门径"></a>微信小程序,初窥门径</h2><blockquote><p>任何技术想要速成,都是存在代价和隐患的。</p></blockquote><p>在入职第一天,组长问我,“能不能试试微信小程序”,当我回答“可以试试”的那一刻起,我就走上了一条不归路。</p><p>还好的是,微信小程序是腾讯弄的,官方文档是中文,而且国内相关的论坛已经趋向成熟,这种种便利条件给予了我很大帮助。</p><p>还是那句话</p><blockquote><p>一切能在互联网上找到答案的问题都不是问题</p></blockquote><p>入职后不久,我就开始了面向文档和谷歌编程,开始接手小程序的更改。</p><p><del>虽然这么做不地道,但是没办法</del></p><p>期间也是踩了很多坑。因为我们这个项目并不是原生小程序,是H5与小程序的结合,听说是最开始的技术选型是完全的H5,但是后面出于各种复杂的原因,变成了现在这个样子。</p><p>因为涉及公司秘密,下面有些关于项目的描述我只是略过。</p><p>项目里有很多关于H5和小程序的跳转,有些还是要先跳到小程序中拿到一些值回传给H5,这就导致有些H5页面因为要等小程序传值,渲染的过程有几秒的白屏,这个是硬伤,网上我也没找到什么好的解决办法,较好的办法就是重构。</p><p>说道重构,我其实是有这个意图的,但是没有跟组长说,我怕我说出来就不是个意图这么简单了。<u>同学,收起你那大胆的想法。</u></p><p>但是重构我还是想试试的,每个程序员心里都会说,“这写得啥玩意,我来重写一个”。我打算用连续几个周末的时间和离职后的时间,一步一步把H5的功能移植到小程序上,当然到目前为止这还只是个想法。未来几周的空闲时间,我打算先把整个架构构思好,然后再看看实现的复杂度。</p><p>因为我并不是个纯正的前端开发,很多问题都是第一次遇到,有些方法也不知道怎么调用。而且搜索引擎的功能也是有限的,有些问题只能找人来解决。<u><strong>这里没啥说的,再次感谢程总和游总吧。</strong></u></p><h2 id="总结与展望"><a href="#总结与展望" class="headerlink" title="总结与展望"></a>总结与展望</h2><p>这个标题起的像是给领导展示PPT,23333。</p><p>现在我在生活与工作中还是找不到平衡,有时候加班到家已经快12点了(没办法,远,路上包含走路估计有70分钟,至于为什么这么远,因为房租便宜)。然后还存在一些其他的私人问题。</p><p>虽然入职这两周很累,是之前从未体验过的船新版本<del>(滑稽)</del>,但是收获很多,我在小程序这方面,还有前端,学到了相当多的新知识。这方面的知识我已经整理到自己的小本子上了,至于整不整理成博客,看日后心情吧。</p><p>我司虽然工作很累,但是总体上工作还是很开心的,认识了很多大佬,接触了很多新的知识领域,身边也有很多人都是有海外留学背景的,说不定将来我在国外遇到什么问题,还要向他们需求帮助。</p><p>而且我本意是想找家公司写C++的,因为正如在上篇文章中说的那样,因为将来绝对上研,有充足的时间了解学习各个方向。</p><p>你说我一个主写iOS、搞过深度学习的,怎么就去写前端了呢?<del>当时我就吟了一首诗</del> 虽这么说,但是我觉得我干完这份实习,我的前端水平将来应该也能去中等公司应聘了。</p><p>说到这,我对于我将来干什么方向什么岗位还是迷茫的,组长在平时闲聊之余也问过这个问题,也建议再考虑考虑算法岗,甚至是读博的问题。关于读博,身边有好多大佬保研都是直博,去日本的舍友也说将来准备赴美读博,家里的话也是支持我上博的,关于这个问题看将来吧,现在没法定夺。</p><p>就这样吧,写到这里,外面正下着大雨,深圳这一周都是雨天,虽说我喜欢听雨声,但是不喜欢在大雨里回家。很久没有写这方面的文章了,自己都感觉构思和措辞上大不如前,看来以后也要把写作锻炼加到日常表里。</p>]]></content>
<summary type="html">
<pre><code>如题,这篇文章写于入职后两周零两天,地点深圳图书馆。
本来是想昨天写的,昨天与我对接的后端下午过节去了,原本应该是个清闲的下午,然而刚刚整理完开发文档和填坑笔记后,就来了新需求,所以挪到今天写了。
</code></pre><p>本人以实习生身份进入一家公司,<del>利益相关,匿了</del>,工作两周有余,感慨颇深,遂写下此文。</p>
<p>我准备从工作、生活、学习三个方面总结体会。</p>
<h2 id="实习工作,初窥门径"><a href="#实习工作,初窥门径" class="headerlink" title="实习工作,初窥门径"></a>实习工作,初窥门径</h2>
</summary>
<category term="LIFE" scheme="https://saberda.github.io/categories/LIFE/"/>
</entry>
<entry>
<title>我的2018,记忆名为伽勒底</title>
<link href="https://saberda.github.io/2018/12/31/%E6%88%91%E7%9A%842018%EF%BC%8C%E8%AE%B0%E5%BF%86%E5%90%8D%E4%B8%BA%E4%BC%BD%E5%8B%92%E5%BA%95/"/>
<id>https://saberda.github.io/2018/12/31/我的2018,记忆名为伽勒底/</id>
<published>2018-12-31T08:14:31.000Z</published>
<updated>2018-12-30T19:37:43.000Z</updated>
<content type="html"><![CDATA[<pre><code>今年发生了太多值得写下来的事情,今年的感想也特别多。今年的种种最终交汇成这篇文章,以文字和图片的形式记录下来。尽管文笔有限,但文字会如咒语般,唤醒背后存在的情感与记忆</code></pre><p>每年年末都要抽出一两天的时间来回忆今年我干了什么,还记得去年的总结写完后已经过完年了,就没有发。好长时间没有写这种文章了,文笔肯定略显平庸,反正是总结,凑活着写吧。</p><h2 id="时间线"><a href="#时间线" class="headerlink" title="时间线"></a>时间线</h2><p>先捋一遍时间线吧。</p><h2 id="一二月份"><a href="#一二月份" class="headerlink" title="一二月份"></a>一二月份</h2><p>今年的一、二月份,对于大多数 fate go 国服玩家来说都是一段不可明灭的记忆,从第七章的乌鲁克的救赎到终章的众志成城,蘑菇本人亲手执笔创造出来的剧本诚不欺我。</p><p>下面这部分是对这部分的一个回忆。</p><a id="more"></a><p>第七章重新对吉尔伽美什这个最古英雄王塑造了一次人物形象,摆脱了 fate 传统的二五仔形象,描写的是自恩奇都死后唤醒人生第二春的贤王。为了拯救即将毁灭的乌鲁克城区与群众并等待迦勒底主角等人的到来,付出巨大的代价召唤出从者现世。剧本还重现了吉尔伽美什过劳死这个传说结局,真是米索不达米亚平原的焦裕禄。当然,最感动的并不是作为第七章主角之一的吉尔伽美什,而是坚守到最后一刻的乌鲁克群众。明知道自己的结局逃不脱死亡,但还是以乐观的心态对待新的一天,主动放弃其他领土固守都城,直到最后一刻,即使面对神代强大的提亚马特,也坚信他们的王会带领乌鲁克走向最后的胜利。</p><p>“<strong>乌鲁克仍存于此</strong>”,当吉尔伽美什第二次复生,从冥界以 Archer 现世大喊出这句话时,我承认我当时泪目了。配合当时恢复记忆的金固(恩奇都),给提亚马特重创。以及付出了整个冠位魔力的王哈桑剥脱了提亚马特的不死神性,咕哒子才得以借助梅林之力将提亚马特重新打回封印之所。当然,我估计这也和蘑菇有意洗白提亚马特有关,即使最后重新被封印,她也念念不忘当初创造的那些子民。(总之很期待明年的动漫)</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/1.jpg" alt=""></p><p>当然,恢弘壮烈的第七章只是为终章做的铺垫,FGO 的终章剧情可以说将这部手游封上神坛。</p><p>咕哒子一行人从乌鲁克刚刚回到迦勒底,就接到了与所罗门王所在的冠位时间神殿所在空间接触,来不及休息就直接走入宿命的碰撞。咕哒子和玛修进入后发现他们面对的是所罗门王麾下七十二魔神所化的无穷无尽的魔神柱,正当绝望之时,金色的圣光在咕哒子身旁绽放,贞德出现在战场后,举起高呼:</p><blockquote><p>“听着 在这个时空聚集起来的<br>一骑当千 万夫莫敌的英灵们啊<br>哪怕本为无法相容的敌人<br>哪怕本为没有交集的不同时代之人<br>现在也请互相把后背托付给对方吧<br>我们哪怕来自无法交融的时代<br>不是为了阻止人理烧却<br>我们并不是为了防止人类毁灭<br>而是为吾等的契约者开辟前行的道路<br>我的真名是贞德<br>在主的名义下<br>将成为汝等坚实之盾<br>吾主即在此地<br>集结于升旗之下怒吼吧”</p></blockquote><p>东风夜放花千树,更吹落,星如雨。英灵们随着颗颗流星现世,前七章与咕哒子一行人接下羁绊的英灵们纷纷在贞德后涌现,其中有敌人有挚友,但此时他们对咕哒子所说的都是一句话“往前走吧,背后交给我们”。在这之中,掺杂着无数复杂情感的诸多从者们,放弃了自身的立场,放弃了之间的恩怨,因与主角结下的一丝羁绊,主动被召唤到这片战场,为咕哒子阻拦住前方的障碍,让其直达神殿中间。</p><p>在神殿中间,面对着盖提亚的宝具,玛修虽知这不是自己能够承受的,“哪怕我的生命会在刹那之后终结,即便如此,我也想要见证未来,哪怕多一秒也好”,扔毅然决然的举起护盾保护在咕哒子前,即使肉体将无法承受光带的热量而蒸发,但那份精神、心灵不会遭任何侵扰。雪花之盾毫发无损,会一直保护她的主人。她不是什么勇敢的战士,也不是故事的主题。只是一个,极为普通的女孩子。</p><p>“迄今为止的旅程,从今往后的旅程。自己存在的过去,和自己不在了的,未来的梦”,在玛修最后回首对咕哒子说“我总是在被人保护,至少最后,希望能够帮上前辈一次忙”,我想保护的身影此刻牺牲了自己守护着我,“前辈,能再握一次我的手吗?”,我泪目了。</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/2.jpg" alt=""></p><p>最后一刻,医生戴上了最后的那枚戒指,微微一笑,走进了灵子转移。面对这曾是自己的恶的盖提亚,释放了属于自己真正的宝具:</p><blockquote><p>诞生之时已至,以此修正万象 Ars Almadel Salomonis<br>加冕之时已至,以此启发万象 Ars Paunila<br>诀别之时已至,以此,舍弃世界 Ars Nova</p></blockquote><p>作为代价,医生燃烧了自己,剥夺了盖提亚的能力,咕哒子拿起玛修的盾上前手撕了盖提亚。医生作为所罗门成为人类的愿望体现,在最后一刻察觉到了人类的终焉,即人理的毁灭。这十年间用一个凡人的力量,去学习了各种知识,准备迎接未知的敌人。十年来以凡人之躯默默的承受着这一切,为的还是其所热爱的人类。正如最后所言:</p><blockquote><p>所罗门王虽拥有万能的戒指<br>却一次也没有使用过<br>以及 他最后凭借自己的意志 将这戒指还给了上天<br>就像是在宣告 从今往后命运将不再交给全能的神<br>而是人类凭自身意志活下去的时代到来了</p></blockquote><p>国服有着千里眼的性质,明知道最后的结局会是这样,但就像《海上钢琴师》说的,“明知道结局如何,但是只能放开手,做自己该做的事,不去干扰他的选择”。在今年上海举办的 FES 中,医生的王座前摆满了鲜花,以及他最爱吃的草莓蛋糕。</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/3.png" alt=""></p><p>在逃离之刻,作为第四兽的芙芙,灾厄之兽凯茜帕鲁格,舍弃了自己的知性,仅仅因为在迦勒底中,在与玛修咕哒子的接触中,它明白了什么是美丽的存在,什么是不用厮杀就能打倒的恶,什么是不用流血才能到达的答案,什么是善良。</p><p>最后,“未返回者,一人”,其实医生的心,就在这里,跟着咕哒子和玛修,一同返回到了他所拯救的新世界里。</p><p>“<strong>啊,天空,仍是如此湛蓝</strong>”</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/4.png" alt=""></p><p>终章这段我是重新回顾了一遍剧情才执笔的,很多复杂的感情我很难通过目前的文笔表达出来。我承认,我打终章那一晚,蜷缩在被窝中哭了多次。</p><p>FGO用一年的时间,谱写了一部关于爱与希望的诗歌。</p><h2 id="三四月"><a href="#三四月" class="headerlink" title="三四月"></a>三四月</h2><p>三、四月份时跟着同学“入赘”了物光院老师的一个军方项目,做的多无人机多无人车自主协同。提到这个项目就很气,我们辛辛苦苦把项目书写的完完整整的,我甚至把识别交通标识的算法都训练好了,最后老师跟我们说这个对于本科生太难了,把项目书丢给他的研究生让他们按着我们的项目书把项目做了,贼气。</p><h2 id="八九月"><a href="#八九月" class="headerlink" title="八九月"></a>八九月</h2><p>八、九月份时,合伙参加了中美联合创客大赛,队长是上面那个项目的队长,其余的有一个西交大的小伙伴负责建模,一个北邮的小伙伴与两个普度的小伙伴负责硬件以及信号采集。我在队内负责的是算法,将 EMG 采集到的肌电信号分析特征后分类,再对即将可能做出的手势做出预测。这段时间内我读了超多的论文,把一些论文中觉得我能用上的方法挪用到了自己的模型中来,最后使用的模型是渐进神经网络(Progressive Neural Network, PNN),是迁移学习中多任务学习的一种变形。因为我们能收集到的数据源过少(十几M,都是队员们自己的肌电信号),模型选择上从 RNN 最后过渡到了 PNN,使用的辅助数据集是一篇论文使用的数据集,我发现我们数据之间的差别不是很大,稍微洗了下就直接用了。总之,9月份的决赛还是很开眼界的,在北京中华世纪坛举办,伙食和住宿条件很棒(划重点)。</p><p>上大学以来,参加的各种比赛都是与互联网相关的,睁眼闭眼都是专业内涉及到的,唯独这次感受到了百家争鸣百花齐放。决赛作品涉及到各行各业,印象很深刻的是一个自动染布机,运行效果超酷炫。</p><h2 id="十月十一月"><a href="#十月十一月" class="headerlink" title="十月十一月"></a>十月十一月</h2><p>十月十一月主要是冲刺TG成绩,要在申请之前成功拿下。这段时间我感觉是目前人生中压力最大的时期,压力来源于各种方面,家里对我的期待,自己的预期甚至周围同学的成绩,无形中几乎使我崩溃。那段时间我感觉高考时都没这么难受,天天往返于家、图书馆和食堂之间,脑子里都是英语,吃饭时会考虑这个食物英语怎么说,走路时会不由自主的思考作文模板怎么写才好。一天陪伴我最长的就是TPO的听力,几乎所有在图书馆的时间除了背单词就是练听力,没办法,基础差的伤不起。</p><p>感觉周围每一个出国党,十月与十一月都是最难熬的一段时间,这段时间,周围的同学保研的保研,工作的工作,考研的也学的滋滋有味,唯独选择出国的,我当时根本感受不到我的未来,或者说不像其他人一样明确,很迷茫,对未来是否能够成功录取以及录取到什么样子的学校,仿佛一柄刀子样悬在头顶,惴惴不安。</p><p>写到这里就想起今年两个假期参加的托福补习班,之前叫 Nova,后来改名为 U.Know。并不是打广告,<strong>这里的老师是我见过最负责任最有热情的老师</strong>,上课环境也是超级棒(免费的饮料和咖啡,赞)。跟我上课的基本都是深圳的高中生,我在里面上课时深深感受到了年轻真好。同时感慨更多的是阶级与阶级之间的差距难以追赶,家境比你好的人比你还努力,同时人家还享受着相对来说更高级的资源。也许迈步中产可以去搞互联网或者金融,但是中产与中产以及往上的差距,是一代人难以弥补的。当我们还在挣扎于高考这泥潭中时,他们已经在考虑去哪个国家做志愿者。</p><p>我现在已经能理解,为什么在北京上海深圳这些超一线城市生活这么艰难,但是还是有很多人选择拼了命的往里钻。我从小学初中高中,没有一位老师系统的教过音标,我的音标都是自学的,甚至高考结束后都有些音标不认识。寒假初次去补课时,我的口语老师曾把我叫出去,一个音标一个音标的带我过,当时的我是大三,教室内的高中生在等我过完音标继续上课,当时心里真的很难受。但这也没办法,家乡是个四五线的小城市,我能有如今这种成就我已经很知足了。但相比于这些超一线城市,基础教育以及生活素养是难以望其项背的。我老舅家的小孩,今年才上小学一年级,但是已经上过一年多的英语外教课程,我是很羡慕的。社会资源是不平等的,如何争取到更多的资源只有凭借更高的工资更好的工作。</p><h2 id="十二月"><a href="#十二月" class="headerlink" title="十二月"></a>十二月</h2><p>说远了说远了,不管如何,十一月我结束了所有的TG考试,不管怎样就这么申请吧。最终到来了较为舒适的十二月。</p><p>十二月的我可谓是很小资了,在周围考研同学都在拼命冲刺的时段,我能在图书馆悠闲的看着自己喜欢的书籍,搞着自己喜欢的研究,唯一的担心就是醒来时看到学校发来的拒信。我的运气还不错,有些学校刚在十一月末提交完申请,十二月中旬就给我发了 offer,截止到本文,我已经收到了三个学校的 offer,一个比一个好,家里人也跟着我一起高兴。</p><p>但是到月末,感觉自己的十二月仿佛荒废了一样,就好比在之前在海底忍受着海压潜泳,然后突然间浮出水面,体内压力失衡。曾有连续的几天,几乎天天在家里宅着,刷着b站知乎。后期我是强迫自己起来去图书馆,才能正经看会书敲点代码刷刷题,准备明年的实习。虽是这么说,但是我个人认为那段时间成功的将我几近崩溃的精神状态调养了过来。</p><h2 id="二〇一八大事记"><a href="#二〇一八大事记" class="headerlink" title="二〇一八大事记"></a>二〇一八大事记</h2><p>时间线就这么捋完了,接下来我要记录下今年值得纪念的事件,或许其中有些事情不算什么,但是对于今年的我来说,这些就像担担面里的花生,虽不是主食但吃到时还是会惊艳一下。</p><p>清明前,终于决定动身前往武汉<strong>赏花</strong>。一直向往被武大捧起的樱花大道,而且武汉也是国内有头有脸的赏花圣地,作为樱花喜好者,一定要去的。清明时,跟爸妈一同前往了京都,去清水寺看那樱花遍野。然而运气不是很好,今年的气温升的有些快,清明并不是京都一带最好的赏花季,我们虽然错过了清水寺的樱花,但是来到了海拔较高的上野,如愿的看到了樱花满山。虽说错过了所谓最好的赏花季,但是我们去的时间节点正是上野樱花的末期。记得十分清楚,当天飘着小雨,风拂过树林,樱花瓣随着细密的雨丝落在身上,名副其实的樱花雨。</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/5.JPG" alt=""></p><p>樱花可谓是我的本命花了,除了多年日漫的影响,除了它自身的美丽,我觉得樱花与人的一生特别相似,<font color="red"><strong>虽短,但是够绚烂</strong></font>。</p><p>今年我们软院亡了,学校政策改变,软件学院和计算机学院合并成计算机科学与技术学院,也算是曲线救国,成功跻身ESI前千分之一的计算机学院,嘿嘿。但是很担心的是SSSTA之后的存留问题,很怕明年就没有了这个组织,很怕明年302这个地方会被学院收回,很怕失去这个学校唯一值得我留恋的地方,很怕贵协这个寄托着我诸多宝贵记忆的组织成为历史。</p><p>今年我成功说服爸妈,有了自己的“肥宅快乐柜”,将大学四年省吃俭用(生活费)、辛苦工作(外包)、努力学习(奖学金)变现的手办放到了一起,摆在了透明的玻璃橱窗中,可能有些羞耻,但是有这么一个柜子摆着自己的心血,看到的时候内心还是超级激动的,这毕竟是每一个死宅的梦想。</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/6.JPG" alt=""></p><p>今年参加了银临的线下,从九点多进场排队到下午三点,才和小伙伴买到银临的签售,而且能如此近距离接触到银临女神,是超级激动的。今年还参加了西安哈迷的线下活动,虽说加入了这个组织两年有余,但是还是第一次参加线下,和哈迷一同看的《神奇动物在哪里2》的点映,一堆人穿着长袍,带着本院的围巾,有些小伙伴带了魔杖甚至扫帚。感谢这次线下,给了我们一个环境,在这个环境中我们能肆意妄为的表达对魔法世界的向往与喜爱而不顾路人的眼光,漫展同理。</p><h2 id="工作"><a href="#工作" class="headerlink" title="工作"></a>工作</h2><p>是的,已经到了要面对之后独立生活的年纪了,尽管我还有一年半左右的研究生生涯,但是工作也必须提上日程了。说实话,硕士的作用,个人认为就是为本科和工作之间做铺垫,如果想研究,那就去读博吧。</p><p>我将来干什么?什么方向?起步的语言?这些问题我思考了一个暑假加大半个学期,毕竟学的是软件工程,细分方向的话能给你说出小一百种,开发?研发?还是运营?应聘时选择什么语言?Java?C++?还是Python?也许其他人没有这种苦恼,毕竟他们只会一种。说到这里,我要感谢SSSTA,贵协的学长教授了我们很多宝贵的学校学不到的经验,同时感谢自己上了一个比较水的大学,从而有很多时间去学自己感兴趣的东西而不被学业束缚。</p><p><strong>大学只是提供教学设备和资源的一个地方,不代表或者决定你是一个什么样子的人,关键在于你对自己的定位,还有怎么利用资源。</strong>如果将我的大学生活比作在海中航行的话,那SSSTA就是我航行路上的灯塔。在刚刚入学,对软件工程这个概念一知半解时,贵协的学长清楚的告诉我,我们这个专业是干什么,能干什么,各个方向是什么,哪些知识适合初学者掌握。可以说我的大学是很幸运的,其中一半来源于高中毕业时爸爸单位同事送给我的Mac,另一半来自于SSSTA的学长们的教导。</p><p>那台Mac使我专注于学习,SSSTA告诉我应该学什么。如果这两者缺失了一个,都不会有现在的我。感谢爸爸的那位同事,感谢SSSTA。在同届刚刚接触到C语言时,我已经将C的语法学完了;在同届学完C时,我已经会编写iOS程序了。并在大学的第一个寒假,接了个简单的iOS外包,用自己赚的钱买了垂涎已久的 PS4。在同届刚刚接触面向对象编程时,我已经在 Coursera 上听吴恩达的机器学习课程了;在同届刚刚掌握一门将来吃饭的手艺时,我已经接触过了自己感兴趣的所有方向,iOS(OC)、前端(VUE,JS)、后端(JS,Python)、机器学习(Python)、客户端(C#)、甚至接触了函数式编程(Scala)。正因为接触过的东西多,所以选择一个作为吃饭的手艺我思考了很久。</p><p>我是在2016,也就是大二的时候开始跟 Coursera 学的机器学习,当时行业上机器学习刚刚种下爆发的种子。一年不到,深度学习已经火到大江南北,势头可以跟当年的云计算拼一拼,几乎到了老少皆知的地步。一年的时间行业就基本完成了从0到1的建设,现在已经向从1到10前进。虽然现在机器学习岗还是很火,但是很显然已经处于退潮期(没能赶上黄金期)。很难想象硕士毕业,也就是两年后,这个行业的发展会如何,是仍蓬勃向外扩展,还是趋于饱和。</p><p>我当初刚刚上大一时,业界内正是iOS开发的黄金期,网上的iOS开发辅导班如过江之卿,但等到大二下学期国内的iOS待业人员已经过饱和,就连很多辅导班的老师都找不到工作。我现在就担心,对大数据的挖掘日渐加深,近年深度学习模型又没有太大的进展,就连比较新的GAN,也是2014年的算法了。顶会很少出现新算法,大都是对现有算法(CNN为首)的优化,不知道等我毕业那年机器学习就业情况。</p><p>现在正处于对将来方向的选择期,研究生不同于本科,所选的方向跟就业方向还是比较密切的,所以思考了很久,最终还是放弃了热门的机器学习。选机器学习领域的话,就必须选定一个方向钻研下去,而且估计要细到CV、NLP等具体领域(光说深度学习明显是不够的),因为不用说几年后,现在大厂的机器学习人才已接近饱和,待我毕业时,大厂的刚需是高端人才。什么是高端人才?是博士。我个人不是很喜欢读博,一是对研究的兴趣不是很大,而是怕掉头发。</p><p>曾经我有段时间是想继续搞机器学习的,觉得将传统的机器学习算法与神经网络结合,有着光明的发展前景。但是近几年的顶会论文几乎没有做这个方面的,而且学校内实验室的方向也是传统的神经网络实验室。这些使我感觉我这个想法很难实现,因为我知道自己的极限,相比于天才差了很大一截,那些发顶会的天才都没有将这个想法变现,我一个普通211的大学生又何德何能呢。</p><p>于是出于诸多原因,我最终选择了C++。一是C++这门语言是万金油,比Java还万金油,下到底层上到操作系统,C++都能干。二是C++这门语言比较难,它有着许多良好但复杂的语言特性,可以说从中诞生了Java和C#。如果我将C++吃透了,即使将来需要换门新的语言,起手也不会太难。三是距离我正式工作还有两年左右的时间,我有足够的时间将这门语言掌握以及积累项目经验。选择C++也算是一种豪赌吧,将放弃熟悉的python为赌注,去赌一个更广阔的未来。</p><h2 id="技术栈"><a href="#技术栈" class="headerlink" title="技术栈"></a>技术栈</h2><p>2018一年,我几乎没有学习什么新的知识,因为整一年的重心都是英语。</p><p>唯一能谈得上进步的地方就是上半年接触了前端,初步入门了VUE;然后复现了一些神经网络论文,到YouTube上看了一些较为新的公开课或者说是报告;跟中科院做了一个文本情感分析的项目。</p><p>今年在技术上的进步仅此而已,说来惭愧。</p><h2 id="新的习惯"><a href="#新的习惯" class="headerlink" title="新的习惯"></a>新的习惯</h2><ul><li>笔记习惯回归到纸张。</li></ul><p>之前学习新的内容,或者听新的课程,习惯于用电脑记录笔记。但是这有个弊端就是,当需要记录下一些公式时,你需要将手头的放下,花费相较长的时间用 Mathtype 整理公式。用笔记这个方式萌芽于上学期跟李磊一起上课,他当时在听斯坦福的CS224n,他当时就是边听边用笔记,我当时突然发现这样效率好高。</p><p>而且当我选择这种方式后,发现记录表格图表之类的内容时,效率也远比电脑输入快。纸张记录的弊端就是保存问题,我为了解决这个问题采取的是,每当完成一篇笔记时,就扫描成PDF存入电脑。</p><p><img src="/2018/12/31/我的2018,记忆名为伽勒底/7.png" alt=""></p><ul><li>习惯于用便签记录提醒生活的琐事</li></ul><p>岁月不饶人,老了老了,有些事情如果不记下来,会忘记的。</p><p>初始我选择纸质的便签,后来发现不能随身携带并且就算随身携带,也不能随时随地记录。于是就倾向于手机和电脑配合记录,感谢苹果,生态体系好,手机电脑同步超级方便。</p><ul><li>习惯于VSCode</li></ul><p>之前习惯于用专门的应用处理专门的事情。</p><p>比如写C/C++,我用CLion;写python,我用PyCharm;写前端,我用WebStorm;写java,我用IDEA;写Markdown,我用专门的编辑器。</p><p>现在我统统VSCode,甚至写txt</p><ul><li>锻炼</li></ul><p>这个习惯是后半年开始培养的,现在坚持的并不如前几者那么好。</p><p>起因主要是因为今年暑假,我去深圳除了补习英语外,还主要去检查心脏。那段时期,我如果前一天没有休息好,第二天起来时心脏跳的仿佛爆炸一样;日常也会出现一阵阵的针扎似的痛。严重到晚上睡觉我都不能向左侧着躺着睡觉,如果向左侧躺着,几乎就几分钟的时间,心脏就开始疼。甚至向右侧躺着时,左胳膊不能压在胸膛上,否则心脏也承受不了。</p><p>在深圳给心脏做了全套的检查,前前后后花了近三千,最后得出的结论就是心悸,原因是昼夜生活不规律、长时间熬夜以及长时间缺少运动。从那之后,我几乎天天都要保证每天的运动量,我胖,跑步难受,那我快走还不行吗?回学校后也找到了一起夜走的小伙伴。这几天因为天气过于寒冷,取消了夜走,但是白天我几乎还是坚持出去走的。原本是有打羽毛球的小伙伴的,但是他这学期去了日本,所以羽毛球这项运动就搁置了。</p><h2 id="至于今年的记忆,为什么叫迦勒底"><a href="#至于今年的记忆,为什么叫迦勒底" class="headerlink" title="至于今年的记忆,为什么叫迦勒底"></a>至于今年的记忆,为什么叫迦勒底</h2><p>因为我除了是个王厨外,FGO 这款游戏给了我很大的安慰。我几乎把它当做了避风港,在里面逃避一会现实的压力,在里面舒缓自己紧绷的神经。我还通过 FGO 这款游戏,认识了很多志同道合的小伙伴,和小伙伴在群里吹吹逼也是我很重要的解压方式。</p><p>正如 FGO 内所说的,“<strong>无数的邂逅在等待着你</strong>”,我选择出国,这意味着世界会以一种前所未有的速度扑面而来,我将面临更多的挑战,将接受更多的羁绊,将拥抱不确定的未来。</p><p>FGO 作为一款文字量达百万级的手游,对我而言它超脱了游戏这个范围,里面的故事使我感动,里面的故事使我成长。如今,FGO 已经陪伴我两年有余了,与迦勒底从者们的故事仍将继续。</p><p>2019年,借用 FGO 中的一句话,我要豪爽的说:“<strong>吾等乃是决定星辰未来,在星辰上刻下碑文的存在</strong>”。</p><h2 id="感谢"><a href="#感谢" class="headerlink" title="感谢"></a>感谢</h2><p>2018年对我来说,是非常重要的一年。</p><p>如果说2015年是因为高考,改变了我的人生轨迹;那今年就是出国,将彻底决定今后的命运。</p><p>我非常感谢这一年来帮助过、鼓励过我的人,特别是我的爸妈。我知道,他们是那种恨不得将我留在他们身边一辈子的父母,我选择来西安上学有部分原因就是这里离家远,他们不能常来。这种父母竟然能忍心将我送出国外,过着每年才能相见几月的日子,爸妈的付出我很感动,他们几乎放弃了今后的幸福生活,换得我一个广阔的未来,我很感谢,真的。将来我无论要面临即使登天般的困难,也要想办法把他们接到外面,在我的陪伴下,度过晚年。</p><p>其次我要感谢帮助过我的老师,鼓励过我的同学。</p><p>出国路是漫长的,漫长到几乎要从大一就要准备(我起步晚了,是从大二开始的,所以绩点并不是很好);出国路是艰辛的,艰辛到甚至比高考比考研难上数倍。当时年轻的我觉得,出国比考研简单多了,考研还要学那么难的数学,背难以理解的政治。但是在出国这条路上走远后才发现,考研再难,也是学习。出国是不一样的。你需要在努力学习维持一个较高GPA的基础上,不断去丰富自己的软背景。这里丰富软背景包括但不限于参加各种大型比赛,去参加海外游学访学,去参加志愿者,你需要在你的软背景上呈现出:我不仅是一个学习好的学生,我还热爱生活,热爱新事物,关心社会时事,全面发展。这些可不仅仅是笔头说说的,是需要实际行动来证实的。</p><p>我很庆幸,自己特别热爱自己选择的这个专业,从而有很丰富的软背景;我很庆幸家里人支持我,给我提供资金帮助我完善这些软背景;我很庆幸,遇到了很多负责任的老师,从标化成绩到软背景提升,都给予了我莫大的支持与帮助;我很庆幸,遇到了很多可爱的小伙伴,给我加油打气,给我前行的动力。</p><font color="red"><strong>感谢今年帮助过我的所有人</strong></font>]]></content>
<summary type="html">
<pre><code>今年发生了太多值得写下来的事情,今年的感想也特别多。今年的种种最终交汇成这篇文章,以文字和图片的形式记录下来。尽管文笔有限,但文字会如咒语般,唤醒背后存在的情感与记忆
</code></pre><p>每年年末都要抽出一两天的时间来回忆今年我干了什么,还记得去年的总结写完后已经过完年了,就没有发。好长时间没有写这种文章了,文笔肯定略显平庸,反正是总结,凑活着写吧。</p>
<h2 id="时间线"><a href="#时间线" class="headerlink" title="时间线"></a>时间线</h2><p>先捋一遍时间线吧。</p>
<h2 id="一二月份"><a href="#一二月份" class="headerlink" title="一二月份"></a>一二月份</h2><p>今年的一、二月份,对于大多数 fate go 国服玩家来说都是一段不可明灭的记忆,从第七章的乌鲁克的救赎到终章的众志成城,蘑菇本人亲手执笔创造出来的剧本诚不欺我。</p>
<p>下面这部分是对这部分的一个回忆。</p>
</summary>
<category term="LIFE" scheme="https://saberda.github.io/categories/LIFE/"/>
</entry>
<entry>
<title>简述迁移学习</title>
<link href="https://saberda.github.io/2018/11/21/%E7%99%BD%E8%AF%9D%E8%BF%81%E7%A7%BB%E5%AD%A6%E4%B9%A0/"/>
<id>https://saberda.github.io/2018/11/21/白话迁移学习/</id>
<published>2018-11-21T16:19:26.000Z</published>
<updated>2018-11-21T13:59:14.000Z</updated>
<content type="html"><![CDATA[<pre><code>本篇并不是对迁移学习的一个概述,只是简单说明什么情景应该使用迁移学习,以及迁移学习的一些基本算法思路</code></pre><p>首先介绍的是<strong>使用情景</strong></p><blockquote><p>Data not directly related to the task considered</p></blockquote><p>直译过来就是使用的数据与任务目标不是直接相关。举个例子来帮助大家明白这句话,我是在今年夏天时的一个比赛中了解到这个算法的,当时我的任务是通过分析 EMG (肌电信号)来识别以及预测手势。当时的问题是,我们小组内并没有足够的数据,这里的数据指的是使用我们小组研发的 EMG 采集器收集的数据,基本都是组内人员自己制作的。那么问题在于,我们花费了大量时间收集数据,但是数据量还是相对而言较少,如果直接将这些数据给神经网络训练的话,最后得到的结果可能无法避免的过拟合。</p><p>这种情况就可以采用迁移学习的思想,使用自己的少量数据与使用其他与当前任务相关不大的数据源一同训练。在上述例子中,我最后使用了国外的一个大学实验室收集的 EMG 信号当 Source Data。</p><a id="more"></a><p>这里插入一下迁移学习中的<strong>两个常用术语</strong></p><ul><li><strong>Target Data</strong>:与当前 Task 有关的数据</li><li><strong>Source Data</strong>:与当前 Task 无关的数据</li></ul><p>上诉两种数据都可以细分为 <strong>labeled</strong> 与 <strong>unlabeled</strong></p><p>在上面的例子中,我们小组内自己收集的数据就是 Target Data,国外实验室的数据就是 Source Data。</p><hr><p>下面就是针对数据的不同形式,从而对迁移学习常用算法进行一个简单介绍。我将从 Source Data 与 Target Data 的 labeled 与 unlabeled 两个状态分为四个较大的模块。具体可以见下图</p><p><img src="/2018/11/21/白话迁移学习/1.png" alt=""></p><h2 id="Model-Fine-Tuning"><a href="#Model-Fine-Tuning" class="headerlink" title="Model Fine-Tuning"></a>Model Fine-Tuning</h2><p><strong>使用数据情景</strong>:Target Data 与 Source Data 都是 labeled</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) -> (very little)</li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) -> (a large amount)</li></ul><blockquote><p>“One-shot learning: only a few examples in target domain”</p></blockquote><p><strong>具体思想</strong>:</p><p>Training a model by source data, then fine-tune the model by target data.</p><p>该方法主要面临<strong>问题</strong>:因为只有有限的 target data,所以要谨慎处理过拟合问题</p><h2 id="一些防止过拟合的训练方法"><a href="#一些防止过拟合的训练方法" class="headerlink" title="一些防止过拟合的训练方法"></a>一些防止过拟合的训练方法</h2><ol><li>Conservative training </li></ol><p><strong>方法主要思想</strong>:</p><p>加入限制,让 Source data 训练出的 model 与 Target data 训练出的 model 差距不要过大</p><p><img src="/2018/11/21/白话迁移学习/2.png" alt=""></p><ol start="2"><li>Layer Transfer</li></ol><p><strong>方法主要思想</strong>:</p><p>先用 Source Data 训练好一个模型,将该模型的大部分 Layer 复制到新模型,用 Target Data 训练该模型中非复制部分的 Layer。</p><p>该方法的<strong>优点</strong>:</p><p>训练 Target Data 时只用考虑非常少的参数</p><p>那么使用该方法时,我们要将哪些 layer 复制过去呢?</p><p>答案是需要分情况讨论。拿目前主要的两个应用方面,语音识别与图片识别,来举例。</p><ul><li>语音识别:通常复制最后几层的 layer,因为这最后几层的作用往往是分辨词汇,与语种的关系不是很大</li><li>图片识别:通常是复制前几层 layer,因为前几层往往是分辨简单的几何图形,与最后的图片识别结果关系不是很大</li></ul><h2 id="Multitask-Learning"><a href="#Multitask-Learning" class="headerlink" title="Multitask Learning"></a>Multitask Learning</h2><p><strong>使用数据情景</strong>:Target Data 与 Source Data 都是 labeled</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) -> (very little)</li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) -> (a large amount)</li></ul><blockquote><p>“Multitask Learning: The multi-layer structure makes NN suitable for multitask learning.”</p></blockquote><p>简单来说,通过训练一个神经网络,使得最后得到多种分类。一般该模型主要分为两类。</p><ul><li>第一类是输入的数据是相同的类型,最后得到多个不同的分类。比如输入都是图片,最后的结果是得到猫的图片与狗的图片。</li><li>第二类是输入的数据不是相同的类型,最后得到多个不同的分类。</li></ul><p><img src="/2018/11/21/白话迁移学习/3.png" alt=""></p><p>下面举一个使用多任务学习的例子,是一个语音识别模型。</p><p>输入数据为“声音(acoustic features)”,输出的类别分别为“法语,德语,西班牙语,意大利语和汉语”</p><p><img src="/2018/11/21/白话迁移学习/4.png" alt=""></p><h2 id="Domain-Adversarial-Training"><a href="#Domain-Adversarial-Training" class="headerlink" title="Domain-Adversarial Training "></a>Domain-Adversarial Training </h2><p><strong>使用数据情景</strong>:Target Data 是 unlabeled 的,Source Data 是 labeled 的</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) -> (testing data)</li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) -> (training data)</li></ul><blockquote><p>“Domain-Adversarial Training: Not only cheat the domain classifier, but satisfying label classifier at the same time”</p></blockquote><p><img src="/2018/11/21/白话迁移学习/6.PNG" alt=""></p><p>上图神经网络中,不同部分的具体分工见下:</p><ul><li>Feature extractor: Maximize the label classification accuracy + Minimize domain classification accuracy</li><li>Label predictor: Maximize the label classification accuracy</li><li>Domain predictor: Maximize the domain classification accuracy</li></ul><p>Ps:本来是想自己做的,发现ppt绘制3D图形太操蛋,就直接挪用了介绍该算法的论文里的图片。<a href="https://arxiv.org/abs/1505.07818" target="_blank" rel="noopener">论文地址</a></p><h2 id="Zero-shot-Learning"><a href="#Zero-shot-Learning" class="headerlink" title="Zero-shot Learning"></a>Zero-shot Learning</h2><p><strong>使用数据情景</strong>:Target Data 是 unlabeled 的,Source Data 是 labeled 的</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) -> (testing data)</li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) -> (training data)</li></ul><p>其中,Target data 与 Source data 是 in different tasks,即在 Source data 中从未出现过 Target data 中的数据。</p><blockquote><p>“Zero-shot Learning: Representing each class by its attributes”</p></blockquote><p>该模型适用于语言识别,因为语言 data 中不可能出现所有单词的发音,所以我们建立一个子表,让模型去识别发音,让识别的发音与子表中的数据去匹配。</p><p>下面为了方便理解,使用一个简单的图片识别做一个浅显易懂的例子,下图中分别对狗、鱼还有猩猩划分了一个简单的子表</p><p><img src="/2018/11/21/白话迁移学习/5.png" alt=""></p><p>其中,分别对其是否有毛发、腿的数量与是否有尾巴等进行判断,若有则标记为“1”,没有则为“0”。子表建立好之后,用输入的数据进行匹配,若该数据有皮毛有四条腿而且有尾巴,那么其大概率是一只狗。</p><p>在训练时,我们不直接辨识那张图片属于哪一类,而是去辨识每张图片它具备怎样的 attribute。</p><p>在测试时,尽管输入一张训练集中没有的事物,但只要找到图中的 attribute,然后查找表,看表中的那个分类最接近结果。</p><p>有时,你的 attribute 会很复杂,这时可以借鉴词向量的思想,对其进行 embedding;甚至当我们没有 dataSet 时,我们可以借用 word2vec,即同时使用 Attribute embedding + word embedding,此时需要计算 loss function。当然,还有一种更简单的 zero-shot learning 方法,即 Convex Combination of Semantic Embedding,读者有兴趣的话可以自行去网上搜索这些算法的具体实现方式,这里写的话会占用超级长的篇幅,并且与“简述”这个主题不符。</p><h2 id="Self-taught-Learning"><a href="#Self-taught-Learning" class="headerlink" title="Self-taught Learning"></a>Self-taught Learning</h2><p><strong>使用数据情景</strong>:Target Data 是 labeled 的,Source Data 是 unlabeled 的</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) </li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) </li></ul><p>该思想有两种主要解释方法,分别针对于非监督学习和监督学习</p><p>针对非监督学习:</p><blockquote><p>“Self-taught Learning: Learning to extract better representation from the source data”</p></blockquote><p>针对监督学习:</p><blockquote><p>“Self-taught Learning: Extracting better representation for target data”</p></blockquote><h2 id="Self-taught-Clustering"><a href="#Self-taught-Clustering" class="headerlink" title="Self-taught Clustering"></a>Self-taught Clustering</h2><p><strong>使用数据情景</strong>:Target Data 与 Source Data 都是 unlabeled 的</p><p><strong>Task description</strong>: </p><ul><li>Target Data: (x<sup>t</sup>, y<sup>t</sup>) </li><li>Source Data: (x<sup>s</sup>, y<sup>s</sup>) </li></ul><p>很少有人在这种情况下,即两个数据集都是 unlabeled 的,使用迁移学习,大家对这个思想了解下就好,<a href="https://www.cse.ust.hk/~qyang/Docs/2008/dwyakicml.pdf" target="_blank" rel="noopener">论文地址</a></p>]]></content>
<summary type="html">
<pre><code>本篇并不是对迁移学习的一个概述,只是简单说明什么情景应该使用迁移学习,以及迁移学习的一些基本算法思路
</code></pre><p>首先介绍的是<strong>使用情景</strong></p>
<blockquote>
<p>Data not directly related to the task considered</p>
</blockquote>
<p>直译过来就是使用的数据与任务目标不是直接相关。举个例子来帮助大家明白这句话,我是在今年夏天时的一个比赛中了解到这个算法的,当时我的任务是通过分析 EMG (肌电信号)来识别以及预测手势。当时的问题是,我们小组内并没有足够的数据,这里的数据指的是使用我们小组研发的 EMG 采集器收集的数据,基本都是组内人员自己制作的。那么问题在于,我们花费了大量时间收集数据,但是数据量还是相对而言较少,如果直接将这些数据给神经网络训练的话,最后得到的结果可能无法避免的过拟合。</p>
<p>这种情况就可以采用迁移学习的思想,使用自己的少量数据与使用其他与当前任务相关不大的数据源一同训练。在上述例子中,我最后使用了国外的一个大学实验室收集的 EMG 信号当 Source Data。</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>Reinforcement Learning & Self-Play</title>
<link href="https://saberda.github.io/2018/07/30/reinforcement-learning-and-self-play/"/>
<id>https://saberda.github.io/2018/07/30/reinforcement-learning-and-self-play/</id>
<published>2018-07-31T03:21:22.000Z</published>
<updated>2018-11-21T05:53:27.000Z</updated>
<content type="html"><![CDATA[<h2 id="Meta-Learning-amp-Self-Play"><a href="#Meta-Learning-amp-Self-Play" class="headerlink" title="Meta Learning & Self Play"></a>Meta Learning & Self Play</h2><pre><code>This passage is a learning note about a paper talking about the reinforcement learning and self play.First of all, tell a joke.Title: How to perform as machine learning?Q: Do you know the result of 11 * 12?A: Yes. My answer is 233.Q: No, the answer is 132.A: Ok, my answer is 132.lol</code></pre><h2 id="The-reinforcement-Learning-Problem"><a href="#The-reinforcement-Learning-Problem" class="headerlink" title="The reinforcement Learning Problem"></a>The reinforcement Learning Problem</h2><p>The Reinforcement Learning framework just tell you that you have an agent in some environment and you want to find a policy for this agent that will maximize its reward.</p><a id="more"></a><p>It’s a super general framework because almost any problem you can think of can be describe as there is an agent that takes some actions and you want to take those actions which lead to the good rewards, the high rewards.</p><p>Now, the reason that reinforcement learning is interesting is because this reasonably good reinforcement learning algorithms. I should say reasonably good, I should say interesting reinforcement learning algorithms that can sometimes solve problems. So in the formulation, the environment gives the agents the observations and the rewards, but in the real world, the agent need to figure out its own rewards from the observation.</p><p>Humans and animals they are not being told by the world but something is good or bad, it’s on us to figure it out of for ourselves.</p><pre><code>Agent = neural work</code></pre><p>And this is how it looks like</p><p><img src="/2018/07/30/reinforcement-learning-and-self-play/1.png" alt=""></p><p>This is how it looks like now at least where the observation come in and a little network or helpfully a big neural network does some processing and produces an action.</p><p>And I’ll explain to you in this part the way in which the vast majority of reinforcement learning algorithms work.</p><ul><li>Add randomness to your actions</li><li>If the result was better than expected, do more of the same in the future</li></ul><p>So, this two both points it tries something random and if you eat better than expected, do it again.</p><p>And there is some math around it but that’s basically the core of it and then that everything else is like slightly clever ways of making better use of this randomness.</p><p>The reinforcement learning algorithms that we have new can solve some problems, but there is also a lot of things they can not solve.</p><p>If you had a super good reinforcement learning algorithms then you can build the system it could achieve super complicated goals really quickly and basically the technical portion of the field of AI would be complete and a really good algorithms would combine all the spectrum of ideas from machine learning, and reasoning and inference the best time and the training at the best time, all of those ideas would be put together in the right way to create a system which would figure out how the world works and then achieve its goals in this world and do it vey quickly.</p><p>But the algorithm we have today are still nowhere near at the level of what they can be in the future and will be.</p><h2 id="Hindsight-Experience-Replay"><a href="#Hindsight-Experience-Replay" class="headerlink" title="Hindsight Experience Replay"></a>Hindsight Experience Replay</h2><p>So now let’s discuss ways in which we can improve reinforcement learning algorithms and I’ll describe to you one very simple improvement.</p><p>The improvement boils down to this really simple idea so as discussed earlier, the very reinforcement learning algorithms is work is that you try something random and if you succeed, if you do better than expected then you should do it again.</p><p>But what will happen if you try lots of random things and nothing works, this is the case when exploration is hard when you rewards are infrequent you get a lot of failures, don’t have a lot of success. So the question is can we somehow find a way to learn from failure.</p><p>Next, I’ll explain to you the idea very briefly, the idea is the following. You try to do one thing, you aim to achieve one thing but you’ll probably fall unless you’re really good. So you will achieve something else.</p><p>So, why not use the failure ti achieve the one thing as training data to achieve the other thing.</p><ul><li>Setup: build a system that can reach any state</li><li>Goal: reach state A</li><li>Any trajectory ends up in some other state B</li><li>Use this as training data to each state B</li></ul><p>It’s really intuitive and it works.</p><h2 id="Learning-a-Hierarchy-of-Actions-With-Meta-Learning"><a href="#Learning-a-Hierarchy-of-Actions-With-Meta-Learning" class="headerlink" title="Learning a Hierarchy of Actions With Meta Learning"></a>Learning a Hierarchy of Actions With Meta Learning</h2><p>It’s a simple approach for learning hierarchy of actions, so one of the things that would be nice to do in reinforcement learning is to learn this hierarchy with the hierarchy of some kind.</p><p>But it’s never really been successful, truly successful, and I don’t want to claim that this is a success as well this is more of a demonstration which of how you could approach the problem learning a hierarchy if you had distribution over takes, then basically what you want is to train how level controllers such that they make it possible to solve the tasks quickly.</p><p>So you optimize the low level actions such that they make it possible to solve the tasks from your distribution tasks quickly.</p><h2 id="Evolved-Policy-Gradients"><a href="#Evolved-Policy-Gradients" class="headerlink" title="Evolved Policy Gradients"></a>Evolved Policy Gradients</h2><p>It will be kind of cool if we could evolve a cost function which would make it possible to solve reinforcement learning problems quickly, and as easy as you usually do in there kind of situations you have a distribution over take and you literally evolve the cost function. And the fitness of the cost function is the speed in which this cost function lets you solve problems from a distribution of problems.</p><pre><code>Goal: learn a cost function that leads to rapid learning.</code></pre><ul><li>Train a cost function such that reinforcement learning on this function learns very quickly.</li><li>Ingredients: a distribution over look</li><li>Use evolution strategies to learn the cost function</li></ul><p>So the learned cost function allows for extremely rapid learning but the learned cost function also has a lot of information about the distribution of tasks.</p><p>In this case, this result is not magic because you need your training task distribution to be equal to a test at distribution and now it’s improved some more.</p><h2 id="Self-Play"><a href="#Self-Play" class="headerlink" title="Self Play"></a>Self Play</h2><p>Self play is something which is really interesting. It’s an old idea that’s existed for many years back from the 60s.</p><p>The first really cool result in self play is from 1992 by Tesauro where he used a cluster of 386 computers to train a neural network using Q-learning to play backgammon with self play. And the neural network learned to the feed the world champion and it discover strategies but bag of and experts weren’t aware of and they decided and agreed those strategies were superior.</p><p><strong>Appealing properties of Self Play</strong></p><ul><li>Simple environment</li></ul><p>Self play has the property that you can have very simple environments. If you run self play in one simple environment, then you can potentially get behaviors with unbounded complexity self.</p><ul><li>Convert computer into data</li></ul><p>Self play gives you a way of converting computer into data which is great because data is really hard to get but computer is easier to get.</p><ul><li>Perfect curriculum</li></ul><p>Another very nice thing about self play is that it has an natural or perfect curriculum because if you are good then your opponent is good, the table is difficult. You always vain on between 50% of the tome. So it does not matter how good you are or how bad you are. It’s always challenging at the right level of challenge and so it means that you have a very smooth path of going from agents that don’t do much to agents that potentially do a lot of things.</p><h2 id="AI-Alignment-Learning-from-human-feedback"><a href="#AI-Alignment-Learning-from-human-feedback" class="headerlink" title="AI Alignment: Learning from human feedback"></a>AI Alignment: Learning from human feedback</h2><p>Here the question is that we are trying to address is really simple. You know as we train progressively more powerfully AI system it will be important to communicate to them goals of greater subtlety and intricacy and how can we do that.</p><p>Well, in this work, we investigate one approach which is having humans judge the behavior of an algorithms and some be route be really efficient.</p><p>The way it really works is that human judges provide feedback to the system. All of those bits of feedback are being cashed into a model of a reward using a triplet loss. It tries to come up with a single reward function that respects all the human feedback that was given to it.</p><p><img src="/2018/07/30/reinforcement-learning-and-self-play/2.png" alt=""></p>]]></content>
<summary type="html">
<h2 id="Meta-Learning-amp-Self-Play"><a href="#Meta-Learning-amp-Self-Play" class="headerlink" title="Meta Learning &amp; Self Play"></a>Meta Learning &amp; Self Play</h2><pre><code>This passage is a learning note about a paper talking about the reinforcement learning and self play.
First of all, tell a joke.
Title: How to perform as machine learning?
Q: Do you know the result of 11 * 12?
A: Yes. My answer is 233.
Q: No, the answer is 132.
A: Ok, my answer is 132.
lol
</code></pre><h2 id="The-reinforcement-Learning-Problem"><a href="#The-reinforcement-Learning-Problem" class="headerlink" title="The reinforcement Learning Problem"></a>The reinforcement Learning Problem</h2><p>The Reinforcement Learning framework just tell you that you have an agent in some environment and you want to find a policy for this agent that will maximize its reward.</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>Extreme Multi-label Text Classification:Kim-CNN & XML-CNN</title>
<link href="https://saberda.github.io/2018/03/23/EMTC-md/"/>
<id>https://saberda.github.io/2018/03/23/EMTC-md/</id>
<published>2018-03-23T20:17:41.000Z</published>
<updated>2018-03-23T08:58:49.000Z</updated>
<content type="html"><![CDATA[<pre><code>This passage is a learning note about a paper talking about the extreme multi-label text classification.</code></pre><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>XMTC -> Extreme Multi-label Text Classification</p><p>Finding each document its most relevant subset of labels from an extremely large space of categories.</p><p>Training data: {(x<sub>i</sub>, y<sub>i</sub>)}<sup>n</sup><sub>1</sub>, x<sub>i</sub> ∈ X, y<sub>i</sub> ∈ {0, 1}<sup>L</sup><br>X is the data, y is the label.</p><p><strong>Goal</strong>:<br>Learning a mapping g: X -> {0, 1}<sup>L</sup><br>Our goal is finding a mapping from x to y.</p><a id="more"></a><p>Each document x<sub>i</sub> is associated with a set of relevant labels, denoted by label vector y<sub>i</sub>.</p><h2 id="Two-key-challenges-in-XMTC"><a href="#Two-key-challenges-in-XMTC" class="headerlink" title="Two key challenges in XMTC"></a>Two key challenges in XMTC</h2><p>When <em>n, L, D</em> are large:</p><ol><li>Scalability -> both in training and testing (n -> the number of document; D -> the number of feature)</li><li>Data sparsity</li></ol><p>So, how to extract richer features representation and how to exploit label correlations are the key challenges.</p><h2 id="Related-works"><a href="#Related-works" class="headerlink" title="Related works"></a>Related works</h2><ul><li>Target-embedding methods </li></ul><p>Compress label vectors in target space down to low-dimensional embeddings.</p><ul><li>Tree-based ensemble methods</li></ul><p>Recursively partitions instance space to induce a tree structure.</p><ul><li>Deep learning for text classification</li></ul><p>Automatically extract features from raw text.<br>Has been remarkable success in multi-class classification.</p><h2 id="Kim-CNN"><a href="#Kim-CNN" class="headerlink" title="Kim-CNN"></a>Kim-CNN</h2><p><img src="/2018/03/23/EMTC-md/1.png" alt=""></p><p>It instead of using just a bag-of-word features, the roll text is fed into the model.</p><p>The resolution may not be that good, so here is a sequence of words, and each word is replaced by the word embedding. So you have a vector here. It’s a word embedding with the associated word.</p><p>So the input will form a <em>n</em> by <em>k</em> matrix, where <em>n</em> is number of words in a document, and <em>k</em> is dimension of the word embedding. You can think of this like image, and we are doing convolutional flitters on this image.</p><p>Then, what they do is they place one deconvolution flitter sliding through the time dimension.</p><p>So the first part is extracting a generalized version of the n-gram features, compared to the traditional bag-of-word or bigram or trigram features.</p><p>The second part, the red box there, they are using the filter size of two, but you can also use three or four and so on. That’s way, you are extracting more generalized bigram or trigram features. </p><p>After that, you have a feature map, and what they do, they just do max pooling to extract the most strongest signal in that feature map and stack them all together. And finally is the fully connected layer.</p><p>So this is a brief introduction of the current strongest method in multi-class classification. </p><p>There are several different parts, roughly three.</p><h2 id="XML-CNN-Extreme-Multi-label-CNN"><a href="#XML-CNN-Extreme-Multi-label-CNN" class="headerlink" title="XML-CNN (Extreme Multi-label CNN)"></a>XML-CNN (Extreme Multi-label CNN)</h2><p><img src="/2018/03/23/EMTC-md/2.png" alt=""></p><p>The first is the convolution, The red box is the convolution filter. We slide through the convolution filter in another dimension. Here we do it opposite direction that we swipe through the convolution filter through the word embedding dimension. You can think in an image is a spatial dimension. So the motivation is like each filters is capturing Moore’s global information, given the whole sequence.</p><p>So flying through different dimension of the word embedding is like capturing the most salient features of word among the entire document. So we also have filter of size 248 et cetera that capturing different spatial relations among the embedding matrix. </p><p>After that, we also have a feature map. What we do is not the traditional max pooling. What we do is like adaptive max pooling that extract, two to three most secure known features in the feature map. So if we are doing that, the final feature representation here will be even two to three times larger than the previous method. In the extreme multi-label setting, we know that the final output number of label here is very huge.</p><p>At last, we need to make a low-rank matrix factorization here. So there is a safe 512th hidden representation in the middle that first project this long feature map to a lower dimension and then we do fully connect.</p><h2 id="Memory-Consumption-A-case-study"><a href="#Memory-Consumption-A-case-study" class="headerlink" title="Memory Consumption: A case study"></a>Memory Consumption: A case study</h2><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">Vocabulary size: V(30k)</span><br><span class="line">Word embedding dimension: D(300) </span><br><span class="line">Document length: S(500) </span><br><span class="line">Number of labels: L(670K) </span><br><span class="line">Hidden = 512 </span><br><span class="line">Pooling units = 128Conv1D filters: 32</span><br><span class="line">filter sizes = [2, 4, 8]Total number of parameters: Embedding layer: V*D = 9MConv1D layer: S*32*(2+4+8) = 32K + 64K + 128K = 224KHidden layer: (128*32*3)*512 = 6.29MOutput layer: 512*670K = 343MTotal: 358.51M</span><br></pre></td></tr></table></figure><p>If using floating precision, the memory for medal parameters is around 1.33GB.</p><p>The analysis here is just the minimum of memory you use because when you are doing back propagation and the mini batches, that also depends on the mini batch size you use.</p><h2 id="Choice-of-Loss"><a href="#Choice-of-Loss" class="headerlink" title="Choice of Loss"></a>Choice of Loss</h2><p>We know that if in the Multi-class classification, if you use Softmax, the model will favor only one label and pushing other label to zero.</p><p><img src="/2018/03/23/EMTC-md/3.png" alt=""></p><p>So in the XMTC setting, you mostly put zero probability output on the other labels. But actually, in this kind of dataset, because each document is only tag with the most relevant, say five or ten labels, it doesn°Øt mean that other label is not relevant.</p><p>Using Softmax may not be that good choice, so we consider the most naive Binary Cross-entropy using the Sigmoid.</p><h2 id="So-how-do-we-learn-the-model-parameter-θ"><a href="#So-how-do-we-learn-the-model-parameter-θ" class="headerlink" title="So how do we learn the model parameter θ?"></a>So how do we learn the model parameter θ?</h2><p>We simply use the mean-square out less, the loss function here.</p><pre><code>Denotes the loss function L(~, ~). The energy function is parameterized by a neural network E(y; x, θ)</code></pre><p><img src="/2018/03/23/EMTC-md/4.png" alt=""></p><p>First, we need to find out the prediction y<sub>i</sub> hat, by solving the inference problem and of the energy network, given the fixed model parameter θ. So, exact solving the inference may not be feasible. Then what they do?</p><pre><code>Exact solving the inference may be infeasible, solved approximately by gradient descent with fix iterations.</code></pre><p><img src="/2018/03/23/EMTC-md/5.png" alt=""></p><p>They just do gradient descent with a fixed maximum iteration number, for instance, say just 5. So the do five times obtaining the prediction yi hat and fix that to calculate the gradient based on feature.</p><h2 id="Test-time-Optimization"><a href="#Test-time-Optimization" class="headerlink" title="Test-time Optimization"></a>Test-time Optimization</h2><ul><li>Computation Graph</li></ul><p>So actually calculating the gradient with respect to model parameter θ could be somehow tricky. So, let’s look at the computational graph here. This is the sketch on my note.</p><p><img src="/2018/03/23/EMTC-md/6.png" alt=""></p><p>The input is <em>x</em>, and you path this through some feature network. Use cache the feature and you will calculate, you first make a forward path based on this to calculate the prediction <em>y</em>. So you need to calculate the gradient with respect to <em>y</em> here in the 3rd box is a model part. And in the backward pass, you also want to calculate the gradient with respect to the model parameter θ.</p><p>Now you need to visit multiply times through the model box, for example, the first path it’s like this, the upper part. And the second part is like that, the middle part, because the y<sub>i</sub> is sort of like the final prediction of <em>y</em> is a dependency among the previous state and previous stat is an input function.</p><p>The last, you need to make the derivative multiple times path. But this is the detail and another work in the year is ICML.</p><h2 id="ICML-Input-Convex-Neural-Networks"><a href="#ICML-Input-Convex-Neural-Networks" class="headerlink" title="ICML(Input Convex Neural Networks)"></a>ICML(Input Convex Neural Networks)</h2><p>ICML, which is very similar to the precious structure Prediction Energy Network, except one thing, The design architecture of energy network.</p><p>In this work, they design the energy network to be convex with respect to the y. The benefit of that is when you do the test time optimization, solving inference problems put, this will have a global optimal solution.</p><p>Of course, to design the network to be convex, you have some assumption or can say constraint need to be made on with parameter W. W<sup>(z)</sup><sub>1:k-1</sub> are non-negative, activation functions are convex and non-decreasing.</p><p><img src="/2018/03/23/EMTC-md/7.png" alt=""></p><h2 id="Something-to-say"><a href="#Something-to-say" class="headerlink" title="Something to say"></a>Something to say</h2><p>In recent months,I have so much to do, such as TOEFL, GRE and final exam, that I don’t have time to update my blog. At the same time, I find that that writing notes by hand is more convenient and comfortable than using Word,which is another reason for reducing updates.</p><p>The following photo is two pages of this motes, and please don’t care about ugly handwritings.</p><p><img src="/2018/03/23/EMTC-md/8.png" alt=""></p>]]></content>
<summary type="html">
<pre><code>This passage is a learning note about a paper talking about the extreme multi-label text classification.
</code></pre><h2 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h2><p>XMTC -&gt; Extreme Multi-label Text Classification</p>
<p>Finding each document its most relevant subset of labels from an extremely large space of categories.</p>
<p>Training data: {(x<sub>i</sub>, y<sub>i</sub>)}<sup>n</sup><sub>1</sub>, x<sub>i</sub> ∈ X, y<sub>i</sub> ∈ {0, 1}<sup>L</sup><br>X is the data, y is the label.</p>
<p><strong>Goal</strong>:<br>Learning a mapping g: X -&gt; {0, 1}<sup>L</sup><br>Our goal is finding a mapping from x to y.</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>谈一谈 Fast R-CNN 和 Faster R-CNN</title>
<link href="https://saberda.github.io/2017/12/17/FastRCNN-FasterRCNN/"/>
<id>https://saberda.github.io/2017/12/17/FastRCNN-FasterRCNN/</id>
<published>2017-12-17T21:10:42.000Z</published>
<updated>2017-12-17T08:33:02.000Z</updated>
<content type="html"><![CDATA[<p>本文讨论内容涉及到之前整理的一篇文章,链接见下<a href="http://www.saberismywife.com/2017/10/31/CNN之定位检测/" target="_blank" rel="noopener">CNN之定位检测</a></p><h2 id="R-CNN-的一些问题"><a href="#R-CNN-的一些问题" class="headerlink" title="R-CNN 的一些问题"></a>R-CNN 的一些问题</h2><p>R-CNN并不是完美的,他也有一些问题。</p><p>在测试是它运行的很慢。我们可能有2000个区域,每个区域都要运行一下R-CNN,这就是很慢的原因。<br>我们还会面对一个比较有趣的问题。当我们使用SVM或者regression时是离线训练使用线性回归等方法训练,所以我们的R-CNN没有机会按照相应部分的网络目的升级。<br>同时R-CNN训练管道比较复杂时,他会有一些混乱。</p><p>Fast R-CNN</p><p>为了解决这些问题,有人提出了Fast R-CNN模型。Fast R-CNN的算法很简单:我们只需要交换提取出的区域然后在运行CNN。</p><a id="more"></a><p><img src="/2017/12/17/FastRCNN-FasterRCNN/1.png" alt=""></p><p>这个思想和那个overfeat的浮窗有些相似,所以这里的pipeline看起来有些相像。底层是我们的输入,我们把这个高分辨的图片输入,然后在卷积层运行,这样我们得到了高分辨率的卷积特征映射。之后我们的region proposals使用一个叫ROI pooling的东西从卷积特征映射里分离出这些区域的特征,这些区域的卷积特征将会进入全连接层,最后会有得到在之前文章里介绍过的和classification head和regression head。</p><p>这个算法解决了R-CNN有的一些问题。Fast R-CNN通过共享不同目标框的卷积特征的计算解决了测试过程中R-CNN很慢的问题。在Fast R-CNN中,我们同时训练所有部分,而不使用R-CNN中那个复杂的pipeline。</p><p>在Fast R-CNN中最有意思的部分就是region of interest pooling。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/2.png" alt=""></p><p>现在我们有一个输入图片,很大可能是高分辨率的,并且我们有这个目标框。我们可以把这个高分辨率的图片输入到卷积层和池化层。但是现在有个问题是全连接层希望得到low-res conv的特征值,而整个图片的特征是high-res的。在Fast R-CNN中使用了很简单粗暴的方法解决了这个问题。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/3.png" alt=""></p><p>对于给出的目标框,我们把它投影到那个conv feature的空间,再把那个conv feature volume切成小块,即切分成下层需要的h*w网络,在对每一小格都进行最大池化。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/4.png" alt=""></p><p>所以我们现在使用这个简单的方法,使用目标框共享卷积特征值,最后提取出那个区域的输出。简单来讲,就是交换卷积的顺序,然后扭曲修改。同时这也是个比较简单的计算过程。因为基本上我们只使用了最大池化,而且我们知道如何对最大池化进行向后传播。你可以对这些region of interest pooling进行BP,使得我们可以把整个整体一起训练。</p><p>在实际训练中,Fast R-CNN相比于R-CNN会有更好的结果。这主要是因为其经过调整后良好的属性值。在Fast R-CNN中你可以看到在每个卷积的连接处,都有这样的调整,其保证了输出结果的准确性,使得结果有些提升。</p><p>但是当前模型也存在一个比较大的问题:之前的测试时间没有包含推荐区域的选取,故Fast R-CNN的瓶颈在于推荐区域的选择。如果你将推荐区域的选取所花费的时间也考虑进来,相比于不考虑的情况,速度明显慢了很多。但是解决方法是显而易见的。</p><p>Faster R-CNN</p><p>我们能使用卷积神经网络做回归和分类,那么我们也可以用其来做推荐区域的选取。这就是另一个模型采用的方法,这个模型的名字也是相当简单粗暴,叫 Faster R-CNN</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/5.png" alt=""></p><p>在Fast R-CNN中我们使用对整张输入图片进行卷积来取代对各个推荐区域进行卷积;在Faster R-CNN中使用区域推荐网络来替代额外的区域推荐算法来获得卷积层最末端的特征图谱,并从中获取推荐区域,完成这一步后剩下的操作和Fast R-CNN一样了。</p><p>Faster R-CNN的神奇之处就在于区域推荐网络,它使得我们可以在一个巨大的卷积神经网络中完成所有工作。</p><p>区域推荐网络的工作方式大致是:我们把卷积神经网络的最后一层的特征图谱当做输入,然后我们将区域推荐网络添加到卷机网络之上,然后对特征图谱进行滑窗操作,就是卷积。我们用3*3的卷积和对特征图谱进行卷积。在区域推荐网络中,我们有两种相似的顶层结构。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/6.png" alt=""></p><p>在这边我们进行分类,在另一边我们判断图片中是否包含检测目标,并对位置进行回归。滑窗和特征图谱之间的位置关联表示我们查看的是图片的哪一部分,回归得出的结果给出特征图谱中具体位置。</p><p>但是实际上,它们所做的工作要稍微复杂一些。他们并不是直接对特征图谱中的位置进行回归,其中有几个形状固定的框。你可以想象这些不同形状和尺寸的框根据特征图谱点到原始图片点的关联,覆盖到原始图片上。在Fast R-CNN中,我们将这些框从原始图片映射到特征图谱;现在在Faster R-CNN中,我们将这些框从特征图谱映射到原始图片。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/7.png" alt=""></p><p>现在这里有n个卷积框对特征图谱进行卷积,对于每个区域进行卷积它们会产出一个评分来判断这个框内是否有检测目标,它们还会输出4个回归坐标点从而得到正确的框。就像之前介绍的那样,训练区域推荐网络,获得预测未知类的检测器。</p><p>在最原始的Faster R-CNN论文中,他们训练网络的方式挺有意思的。他们首先训练区域推荐网络,然后训练Fast R-CNN,然后他们把两个网络融合起来,最终得到一个整体的网络。之后他们将训练方式整体化,好比他们有一个大网络,输入图片,网络中有一个内置的区域推荐网络,还有一个分类损失值用来表示每个推荐区域内是否包含目标物体。在区域推荐网络中,有一个回归边框,对卷积顶部的特征图谱进行回归,然后进行roi池化,之后做Fast R-CNN的操作,在网络的末端我们会得到分类的损失函数用来表示目标所属类以及回归损失,来获得正确的推荐区域。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/8.png" alt=""></p><p>这项巨大的工作在这样一个巨大的网络中完成,最终输出4个损失值,然后对其训练,这样我们可以同时完成目标检测的多项工作</p><p>所以目前世界上最好的目标检测器是101层的ResNet加上Faster R-CNN在加上一些其他的内容。</p><p>因为可以看到在Fast R-CNN中,它是对你的区域建议做了一个修正,然后会将它反馈给你的网路,最后重新分类或重新获取一个新的预测值,这就进行了一次次边框的改进,性能也因为这些内容而得到了提高。除了对这些区域进行分类之外,他们还得到了一个可以体现整张图片所有的特征向量,它可以体现出更好的内容和更好的性能。</p><p>同时,它们还进行了多个尺度的测试,就像我们在Overfeat中看到的那样,测试时将在不同的尺寸上运行。</p><h2 id="YOLO:You-Only-Look-Once-Detection"><a href="#YOLO:You-Only-Look-Once-Detection" class="headerlink" title="YOLO:You Only Look Once Detection "></a>YOLO:You Only Look Once Detection </h2><p>我们之前说过关于定为回归的一个想法,这件事叫YOLO,事实上,它将检测问题直接当做回归问题来处理。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/9.png" alt=""></p><p>它将我们输入的图像划分成许多个空间网格(S <em> S),通常是7</em>7的网格,对于每一个网格中的元素,我们得到一个关于边界检测的数(B),在大部分的实验中我们通常使用B=2。</p><p>在每个网络上,你要预测2个B边界框,也就是4个数,还要预测一个树来表示这一边界框的可信度,同时你还要对你数据集中的每一个类计算预测分类的评分,所以这个最终检测问题最终就变成了回归问题。</p><p>对于这个回归问题,往往需要用CNN来训练它。它和我们之前讲的区域建议之类的东西不同,当然他也存在一些问题,它的模型输出的数量是有上限的,所以如果你的测试数据和训练数据中有很多的ground-truth,就可能出现一些问题。</p><p><img src="/2017/12/17/FastRCNN-FasterRCNN/10.png" alt=""></p><p>实际上这个YOLO检测器的速度是很快的,它甚至比Faster R-CNN还要快。但是不幸的是它的工作效果不是很好,所以就有了Fast YOLO</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><p><strong>外部区域推荐的思想</strong>:你在做外部区域推荐的时候,你所做的是选取区域,然后进行卷积。如果能够同时做到这些,那会是很不错的。</p><p>卷积就像一个对图片进行处理的一般过程,你期待卷积能对分类和回归有利。其实卷积中所包含的信息对区域选取同样是十分有用的。这其实就是为了减少计算量。</p><p>在最后,对于所有划分,你使用的是同样的卷积特征图谱,不论是区域推荐,还是接下来的分类和回归。这就是为何能从这里获得运算速度的提升</p><p><strong>Rol池化</strong>:其工作过程是通过将原始图像分解成固定的网络,然后对其做最大池化,对它做旋转变换是比较困难的,但是有一个叫空间变换神经网络模型提出了很好解决该问题的方法。</p><p>它的中心思想是:不同于Rol池化,我们要做的是双线性插值,这有些像我们在处理纹理和图形中用到的。当你使用双线性插值时,也许你就能对这些区域进行操作。</p><p>我认为另一个关于旋转对象比较实际的问题是,我们的数据集中没有与之对应的ground-truth,对于大部分检测数据集,我们仅有的ground-truth信息都是沿着坐标轴的。</p><h2 id="本文中提到的模型链接"><a href="#本文中提到的模型链接" class="headerlink" title="本文中提到的模型链接"></a>本文中提到的模型链接</h2><p><a href="https://github.com/rbgirshick/rcnn" target="_blank" rel="noopener">R-CNN(Caffe + MATLAB)</a></p><p><a href="https://github.com/rbgirshick/fast-rcnn" target="_blank" rel="noopener">Fast R-CNN(Caffe + MATLAB)</a></p><p><a href="https://github.com/ShaoqingRen/faster_rcnn" target="_blank" rel="noopener">Faster R-CNN(Caffe + MATLAB)</a></p><p><a href="https://github.com/rbgirshick/py-faster-rcnn" target="_blank" rel="noopener">Faster R-CNN(Caffe + Python)</a></p><p><a href="http://pjreddie.com/darknet/yolo/" target="_blank" rel="noopener">YOLO</a></p>]]></content>
<summary type="html">
<p>本文讨论内容涉及到之前整理的一篇文章,链接见下<a href="http://www.saberismywife.com/2017/10/31/CNN之定位检测/" target="_blank" rel="noopener">CNN之定位检测</a></p>
<h2 id="R-CNN-的一些问题"><a href="#R-CNN-的一些问题" class="headerlink" title="R-CNN 的一些问题"></a>R-CNN 的一些问题</h2><p>R-CNN并不是完美的,他也有一些问题。</p>
<p>在测试是它运行的很慢。我们可能有2000个区域,每个区域都要运行一下R-CNN,这就是很慢的原因。<br>我们还会面对一个比较有趣的问题。当我们使用SVM或者regression时是离线训练使用线性回归等方法训练,所以我们的R-CNN没有机会按照相应部分的网络目的升级。<br>同时R-CNN训练管道比较复杂时,他会有一些混乱。</p>
<p>Fast R-CNN</p>
<p>为了解决这些问题,有人提出了Fast R-CNN模型。Fast R-CNN的算法很简单:我们只需要交换提取出的区域然后在运行CNN。</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>如何在Xcode中添加<bits/stdc++.h>头文件</title>
<link href="https://saberda.github.io/2017/11/17/%E5%A6%82%E4%BD%95%E5%9C%A8Xcode%E4%B8%AD%E6%B7%BB%E5%8A%A0-bits-stdc-h-%E5%A4%B4%E6%96%87%E4%BB%B6/"/>
<id>https://saberda.github.io/2017/11/17/如何在Xcode中添加-bits-stdc-h-头文件/</id>
<published>2017-11-17T21:44:23.000Z</published>
<updated>2017-11-17T09:15:39.000Z</updated>
<content type="html"><![CDATA[<p>在C++中的头文件 <bits/stdc++.h> 十分强大,能帮助你在实际中节省很多时间,它包含了很多头函数,这样你就可以只导入一次头文件就把所有基础头文件一次导入了。</p><p>但是 Xcode 它不自带这个函数,需要我们手动导入。</p><p>首先进入终端,直接进入 Xcode 目录</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1</span><br></pre></td></tr></table></figure><p>然后在其中创建目录 bits,接下来的所有步骤都需要管理员权限</p><a id="more"></a><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sudo mkdir bits</span><br></pre></td></tr></table></figure><p>然后在该目录下创建 stdc++.h 文件</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">sudo touch stdc++.h</span><br><span class="line">sudo vim stdc++.h</span><br></pre></td></tr></table></figure><p>然后进入编辑模式,stdc++.h 的具体代码在<a href="https://gist.github.com/frankchen0130/9ac562b55fa7e03689bca30d0e52b0e5" target="_blank" rel="noopener">这里</a></p><p>然后就可以在工程文件中导入该头文件了</p>]]></content>
<summary type="html">
<p>在C++中的头文件 &lt;bits/stdc++.h&gt; 十分强大,能帮助你在实际中节省很多时间,它包含了很多头函数,这样你就可以只导入一次头文件就把所有基础头文件一次导入了。</p>
<p>但是 Xcode 它不自带这个函数,需要我们手动导入。</p>
<p>首先进入终端,直接进入 Xcode 目录</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cd /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1</span><br></pre></td></tr></table></figure>
<p>然后在其中创建目录 bits,接下来的所有步骤都需要管理员权限</p>
</summary>
<category term="算法" scheme="https://saberda.github.io/categories/%E7%AE%97%E6%B3%95/"/>
</entry>
<entry>
<title>RNN 与 LSTM</title>
<link href="https://saberda.github.io/2017/11/02/LSTM/"/>
<id>https://saberda.github.io/2017/11/02/LSTM/</id>
<published>2017-11-02T05:32:49.000Z</published>
<updated>2017-11-02T07:18:40.000Z</updated>
<content type="html"><![CDATA[<h2 id="RNN-的简短复习"><a href="#RNN-的简短复习" class="headerlink" title="RNN 的简短复习"></a>RNN 的简短复习</h2><p>我们只在一开始输入了一次图片,使用这个模型,你的神经网络就可以回看输入的图片,然后根据图片的一步登生成描述的词汇。</p><p>每当你产生一个新词时,你使你的神经网络可以回看图片,然后找到它想要形容不同特征的下一个词,你可以通过一个可训练的方式实现这些,所以 RNN 不仅产生了形容词,还决定了下一步看向图片的哪里。所以这个模型它所做到的不仅仅是产生了 RNN 的输出结果,还要找到按顺序排列的下一个形容词的概率分布。</p><a id="more"></a><p><img src="/2017/11/02/LSTM/1.png" alt=""></p><p>但是在这个例子里,这个网络给出了一个值,我们运行神经网路,得到了一个 14 <em> 14 </em> 512 大小的激活容量,而且每一次我们得到的不仅仅是这个分布,还有一个 512 维的向量,它大概就像是一个用来查询的关键词,找出你下一步想看图片哪里,但是实际上这不是这片论文主要做的事情。这个 512维 的向量是由 RNN 生成的,用一些权值来预测,然后这个向量可以和这片区域里的每一个 14 * 14 做点乘,这样我们做完了全部点乘,得到了一个 14 * 14 的 compatibility map,然后我们放一个 softmax 在上面,基本上我们对他进行正则化,得到 attention over the image,所以这是一个 14 * 14 的映射。</p><p>基于像在图中的 RNN 最有意思的一点是,我们使用这个概率分布,奇招具有这些特征的权重和。这个 RNN 可以自己产生这些向量来得出最近最吸引自己的一点是什么,然后它回到那里,最后你做了一个关于不同特征加权和,找出这一个时间点上 RNN 最感兴趣的是什么。</p><p>举个例子,RNN 正在生成一些东西,现在我决定要查找一些东西,他生成一个有 512 个数字的向量关于对象之类的东西,然后和卷积神经网络的激活函数交互,然后可能对神经网络的一部分或是激活公式,最后你就集中你的注意力在那图片的那部分。所以基本上,通过这个交互,你可以查找图片,这就是一些和 soft attention 有关的东西了</p><pre><code>RNN 的输入可以有选择的 attention 作为对输入的处理。</code></pre><h2 id="RNN-的复杂化与-LSTM-的引出"><a href="#RNN-的复杂化与-LSTM-的引出" class="headerlink" title="RNN 的复杂化与 LSTM 的引出"></a>RNN 的复杂化与 LSTM 的引出</h2><p>如果你想把 RNN 变得更复杂,其中一种方法就是增加层数。一般情况下,层数越多工作效果越好。堆叠层数的方法有很多,其中一个就是堆叠 RNN 的层数,这部分也有很多方法。</p><p><img src="/2017/11/02/LSTM/2.png" alt=""></p><p>上图中是一种人们经常使用的方法,你可以看到,可以直接向RNN里加入新的层。下一个 RNN 的输入是前一个 RNN 的隐藏状态向量。在这副图里,横轴是时间,向上看使我们的不同的 RNN 。这在张图里,我们有三个 RNN,每个都有属于自己的权重集,而这些 RNN 就是从一个流向另一个,这些 RNN 都是一起训练的(即没有依次训练的说法),全部只有一个过程,只有一个向后传播。</p><p><img src="/2017/11/02/LSTM/3.png" alt=""></p><p>这个 RNN 公式,是对之前的公式一个重写,但是本质却没有变化,任然和之前做一样的处理。我们使用了一个向量形式的深度和一个向量形式的时间,我们把它们和W转移矩阵做处理,再把他们放到 tanh 的运算里。所以当你增加层数或者输入一个W转移矩阵时,这个公式可以写成一个新的公式,这就是我们可以怎样增加 RNN 的层数。这种方法使网络变得复杂,不仅仅局限于增加层数,就是使用一个更好的 RNN 公式。</p><h2 id="Long-Short-Time-Memory"><a href="#Long-Short-Time-Memory" class="headerlink" title="Long Short Time Memory"></a>Long Short Time Memory</h2><p>我们现在已经了解到了这个非常简单的 RNN 公式,而在实际试验中,你基本上用不了这么简单的公式,基本上之前展示的那个网络很少使用,取而代之的是 LSTM(long short time memory),基本现在所有的论文都用这个。所以如果你要使用 RNN,这里就是你要使用的公式。在这里我要强调的是,他的一切都和之前的 Vanilla 网络是一样的,唯一不同的是RNN公式变得复杂了。我们还是从下一层接受 hidden vector,就像你的输入一样,在较早的时间点对应的是前一个状态。</p><p><img src="/2017/11/02/LSTM/4.png" alt=""></p><p>一般情况下,RNN 每一步只有一个向量 h,而LSTM每次有两个向量,hidden cector h 和 cell state vector c。所以每一次我们有 h 和 c 两个并行,然后向量 c 用黄色表示(下面那一层),所以这个空间里每个点有两个向量。</p><p><img src="/2017/11/02/LSTM/5.png" alt=""></p><p>我们把它串联起来,然后放入一个W转移矩阵,之后通过一个更复杂的方式得到新的 hidden state。我们现在只是找到了一个更复杂的办法来处理来自下层和过去的输入来得到一个 hidden state 的更新,接下来我们讲讲这个公式的细节。</p><p>上面展示的 LSTM 的方程我们先讲上面这部分,我们从下面和之前一个状态接受这两个向量,h 是前一个状态,x 是输入,我们用 W 映射到 x 和 h,x 和 h的大小都是 n,他们都由 n 个数字组成,最后我们会产生 4n 个数字,通过 W 矩阵产生了 4n * 2n。<strong>这里有 4 个向量,i, f, o, g,分别是输入 input,遗忘 forget,输出 output,然后i, f, o经过 sigmoid gate,g经过 tanh gate。</strong></p><p><img src="/2017/11/02/LSTM/6.png" alt=""></p><p>所以<strong>这个网络的工作方式</strong>就是,对 c 进行操作,根据你之前的状态和你下层传上来的值,使用 i, f, g, o 实现了对于 c 的操作。这里来解释一下过程,要把 i, f 和 o 想象成二进制的,不是 0 就是 1,我们想用他们来表示门(gate),之后我们对他们进行 sigmoid,因为我们想要使他们可以微分,然后队可以对所有点进行 BP。基于环境就把所有的都当做是二进制的就行了。</p><p>所以这个公式的作用是:根据这些门和 g,我们最终将完成对 c 值得更新,f 被称作忘记门,通常用来把一些细胞(cells)状态置 0,这些细胞可以理解为计数器,他们可以通过 f 运算重新置 0。这里发生的是数组运算乘法,如果 f 为 0,你会发现 c 通过与 f 相乘输出为 0,这样我们就<strong>实现了计数器重置</strong>。</p><p>我们也可以将其与 i 和 g 的乘积相加,由于 i 取值在 0 到 1 之间,g 取值 -1 到 1 之间,因此我们对每一个细胞加上一个介于 -1 到 1 的数值,在每一个时间步内都进行这些运算,包括通过忘记门把它重置为 0 或者加上一个 -1 到 1 之间的数值,这就是我们<strong>细胞状态的更新</strong>。</p><p>隐藏层函数 h 更新是以挤压细胞的形式进行的,tanh(c),挤压程度由输出门进行调整,所以经过 o 参数调整后只有一部分细胞进入隐含状态,我们通过学习选择一部分细胞状态进入隐藏状态。</p><p>这有一些东西需要强调一下,<strong>我们用 -1 到 1 之间的数值 i 来乘 g</strong>,但是如果用 g 替换,g 取值已经是 -1 到 1 之间,那为什么还要乘上 i 呢?在我们只想要为 c 增加 -1 到 1 情况下,这一步有什么意思吗?这是 LSTM 中 cell 参数的一部分,我认为一个原因是如果你认为 g 是前文的线性函数 tanh 得到的,如果我们直接加上 g 而不是加上 i * g,那将会得到一个非常简单的函数,通过加上 i 乘法操作,我们使函数更加复杂,这样我们更能表达我们加到细胞状态(cell state)里的东西;另一种思考方式就是把这两个概念分开来看,g 表示我们要在细胞状态里增加多少,i 表示我们是否要进行增加这个状态,所以 i 就像表示是否要进行这个操作,g 表示我们要增加的东西,通过分离这两个参数 LSTM 的训练效果会更好。</p><p>好的接下来来看下面部分的公式,我们把它看做细胞流过,<strong>第一个操作是 f 乘以 c</strong>,f 是 sigmoid 函数的输出,若 f 为 0,你将会关闭细胞,然后对计数器重置。如下图所示</p><p><img src="/2017/11/02/LSTM/7.png" alt=""></p><p>这部分是 g,他基本是加到细胞状态(cell state)里的,然后细胞状态一部分进入隐藏状态(hidden state),但是他要先经过 tanh 函数,然后经过 o 参数调整,由此可见,o 向量可以决定细胞状态那一部分进入隐藏状态,你会发现隐藏状态不仅进入了 LSTM 的下一次迭代运算,还会上浮到更高层,因为这个隐藏状态向量要接通上面更远的 LSTM 网络或者进行一次预测。</p><p><img src="/2017/11/02/LSTM/8.png" alt=""></p><p>所以当你展开的时候他是这个样子,从下面得到输入,从前面得到隐藏状态,x 和 h 决定了你的运算门 f, i, g和o,他们都是 n 维的向量,最后对细胞状态进行操作,你可以对它进行一次重置操作,一次加上一个 -1 到 1 之间的值,一部分细胞状态会以学习的方式释放,他可以向上去进行预测,也可以向前进入 LSTM 的下一次迭代。</p><p><img src="/2017/11/02/LSTM/9.png" alt=""></p><p>下面展示的就是 LSTM 正常的工作流程</p><p><img src="/2017/11/02/LSTM/10.png" alt=""></p><h2 id="LSTM-为什么效果比-RNN-好?"><a href="#LSTM-为什么效果比-RNN-好?" class="headerlink" title="LSTM 为什么效果比 RNN 好?"></a>LSTM 为什么效果比 RNN 好?</h2><p>RNN 也有一些状态向量,你要对他进行操作,通过递归公式进行转换,最后随着时间不断更新你的隐藏状态向量,你会发现在 LSTM 中,这些细胞状态进入网络,并且部分细胞进入隐藏状态。</p><p>我们根据隐藏状态决定如何对细胞进行操作。如果<strong>忽略忘记门</strong>,那我们只是对细胞进行加法迭代,所以这有一些操作可以看做是细胞状态的函数,但不管他们是什么,我们最后都是我们都是间接的改变细胞状态,而不是直接对他进行变换,同时这意味着这是一种依赖于加法的不断的计算。</p><p><img src="/2017/11/02/LSTM/11.png" alt=""></p><p>事实上他和 ResNet 有些相似,一般来说用卷积神经网络进行转换词义,ResNet 加入了这些跳跃的链接,你会发现残差有加法操作,即 x,我们要基于 x 进行这些计算,然后再加上 x,这些是ResNet 中的基本模块,事实上这也是 LSTM 中的运算方式,我们有这些加法操作,其中的 x 取决于你的细胞,我们进行了一些函数运算,然后选择加到你的细胞状态中但是 LSTM 与 ResNet 不同,还有这些忘记门可以选择关闭一部分信号。</p><pre><code>看起来拥有加法操作会使网络变得更好,这能使反向传播更加高效。</code></pre><h2 id="为何-RNN-存在梯度消失的问题?"><a href="#为何-RNN-存在梯度消失的问题?" class="headerlink" title="为何 RNN 存在梯度消失的问题?"></a>为何 RNN 存在梯度消失的问题?</h2><p>来考虑一下 RNN 和 LSTM 中反向传播的动态特性,尤其是在 LSTM 中。如果在某些时间中诸如一些梯度信号将会变得非常清楚。如果我在上张图片的最后注入梯度函数,然后这些加法操作就像是梯度的高速公路,这些梯度会经过这些加法门。因为加法相等的分配梯度,所以如果我在这接入梯度,他会沿着网络传递。当然梯度也会通过这些 f,这些有助于梯度回到梯度流,这样使你永远不会遇到RNN中梯度消失问题,因为在 RNN 里这些梯度会在反向传播中死亡变成 0。在 LSTM 中,由于加法高速公路的存在,在任何时间内,我们从上面注入 LSTM 的梯度,都会流过细胞,不会消失</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">H = 5# dimensionality of hidden state</span><br><span class="line">T = 50# number of time steps</span><br><span class="line">Whh = np.random.randn(H, H)</span><br><span class="line"></span><br><span class="line"># forward pass of an RNN (ignoring inputs x)</span><br><span class="line">hs = {}</span><br><span class="line">ss = {}</span><br><span class="line">hs[-1] = np.random.randn(H)</span><br><span class="line">for t in xrange(T):</span><br><span class="line">ss[t] = np.dot(Whh, hs[t - 1])</span><br><span class="line">hs[t] = np.maxinum(0, ss[t])</span><br><span class="line"></span><br><span class="line"># backward pass of the RNN</span><br><span class="line">dhs = {}</span><br><span class="line">dss = {}</span><br><span class="line">dhs[T-1] = np.random.randn(H) # start off the chain with random gradient</span><br><span class="line">for t in reversed(xrange(T)):</span><br><span class="line">dss[T] = (hs[t] > 0) * dhs[t]# back through the nonlinearitydhs[t-1] = np.dot(Whh.T, dss[t])# backprop into previous hidden state</span><br></pre></td></tr></table></figure><p>这是一个关于循环神经网络的例子,在这个循环神经网络中我们不管这些输入,只看隐藏状态的更新部分。所以要最小化权值 Whh,这是一个隐藏状态。现在要对这个 vanilla RNN 进行向前传播,这里我们的时间长度 T 设为 50,我们要用 Whh 乘前一时间长度,然后再用 ReLU 函数来计算。这就是忽略所有输入向量的前向传播,即用 Whh 乘以 h 然后和 0 比大小,再乘以 h 再比大小,循环往复。</p><p>接着要进行反向传播,在最后一步时在这里插入了一个随机的梯度,也就是说在第 50 时间长度插入一个随机的梯度,然后再做反向传播。在进行反向传播时要用到 ReLU 函数,先用 Whh 进行乘法,然后通过 ReLU 函数,再进行乘法再通过 ReLU。有件事需要注意一下,这里我再做反向传播的时候用到了 ReLU,对所有输入值去阈值,丢掉所有小于 0 的数,这里我对 Whh 乘以 h 运算符进行反向传播,事实上我们就是在进行非线性变换之前乘上 Whh 矩阵。</p><p>我们在运行过程中,一遍又一遍的乘以 Whh 这个矩阵,因为在向前传播中我们每一次循环中都乘了 Whh,并且对所有的隐藏状态进行反向传播,最后一公式 Whh 乘以 hs 结束,得到的结果就是你的梯度和 Whh 矩阵相乘,然后对他们使用 ReLU,再乘以 Whh 再运行 RelU,这样我们就乘了这个矩阵 50 次。</p><p>那么他可能会产生两个问题,首先如果你使用的是标量,而不是矩阵,例如使用了一个随机数,然后我得到了第二个数,我不停的用第二个数去乘以第一个数,那么这个序列会变成什么样?有两种情况,我不停的用同一个数去乘他,不管他是 0 还是无限,当然如果第二个数是 1,这是唯一一种不会出现爆炸的情况,否则不管是消失还是爆炸,都将是很糟糕的情况。</p><p>虽然这里是矩阵而不是数,但是实际上他的泛化也会发生这样的事情。如果 Whh 矩阵的谱半径,也就是这个矩阵的最大特征值要比1大很多,那么就会爆炸;如果小于 1,那么就会消失。<strong>因为 RNN 的这个循环的计算,使得它出现了这些很糟糕的问题,它非常不稳定有时候甚至会导致消失或者爆炸。</strong></p><p>所以在实践中,我们可以采取一点小技巧来控制梯度爆炸,在实践中,他就像一个不完整的解决方案,比如你懂得梯度是大于 5 的,那就将元素全部裁剪为 5,这个方法叫做<strong>梯度裁剪</strong>,它可以用来解决梯度爆炸问题,这样你的循环就不会在发生爆炸了。</p><p>但是在循环神经网络中仍然可能会出现梯度消失的问题,而 LSTM 能很好的抑制梯度消失,因为这些高速公路的细胞状态只改变了加法运算,他的梯度会被一直传下去,不会消失。</p><p>这大致解释了为什么他的工作效果会更好。通常我们会使用 LSTM,也会使用梯度裁剪,因为 LSTM 也可能会出现梯度爆炸,但它一般不会出现梯度消失。</p><h2 id="Summary"><a href="#Summary" class="headerlink" title="Summary"></a>Summary</h2><p>首先 RNN 是很不错的,但是只用 RNN 事实上工作的不是很好,所以通常使用 LSTM 来代替,他们最好的地方是他们的加法计算,可以使梯度传播工作的更好,并且也不会出现梯度消失的问题.</p>]]></content>
<summary type="html">
<h2 id="RNN-的简短复习"><a href="#RNN-的简短复习" class="headerlink" title="RNN 的简短复习"></a>RNN 的简短复习</h2><p>我们只在一开始输入了一次图片,使用这个模型,你的神经网络就可以回看输入的图片,然后根据图片的一步登生成描述的词汇。</p>
<p>每当你产生一个新词时,你使你的神经网络可以回看图片,然后找到它想要形容不同特征的下一个词,你可以通过一个可训练的方式实现这些,所以 RNN 不仅产生了形容词,还决定了下一步看向图片的哪里。所以这个模型它所做到的不仅仅是产生了 RNN 的输出结果,还要找到按顺序排列的下一个形容词的概率分布。</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>循环神经网络_RNN</title>
<link href="https://saberda.github.io/2017/11/01/%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C-RNN/"/>
<id>https://saberda.github.io/2017/11/01/循环神经网络-RNN/</id>
<published>2017-11-01T05:47:43.000Z</published>
<updated>2017-10-31T18:41:07.000Z</updated>
<content type="html"><![CDATA[<p>循环神经网络,英文名是Recurrent Neural Network,随着对它的不断深入了解,你会发现这个神经网络模型是多么的有趣。你可以喂给它莎士比亚的作品,经过有效的训练后它会给你输出带有莎士比亚风格的句子;你喂给它 Linux 源码,它会装模作样的给你生成一段它自己写的代码,尽管会有语意错误,但是不会有语法错误。</p><p>那么废话不多说,直接进入正文吧。</p><h2 id="Recurrent-Neural-Network"><a href="#Recurrent-Neural-Network" class="headerlink" title="Recurrent Neural Network"></a>Recurrent Neural Network</h2><pre><code>循环神经网络的好处就是他们会在你建立神经网络架构时给予你很高的灵活性</code></pre><p>咱们先来看一下最左边这个例子,所以一般你在处理神经网络的时候,你会得到一个固定大小的向量(即图中的红色框),然后用隐藏层 – 这个绿色的框来处理它,你就会得到一个固定大小的向量,就是蓝色。所以会有一个固定大小的图像进入网络,然后要输出一个固定大小的向量,他是一个 class score,在循环神经网络中,我们可以采用不同的顺序实现,比如从输入开始或输出开始,或者两者同时开始。</p><a id="more"></a><p><img src="/2017/11/01/循环神经网络-RNN/1.png" alt=""></p><p>再举一个图像字幕的例子。比方说你得到了一个固定大小的推向,通过循环神经网络,我们会生成一些按顺序排列的描述图像内容的词,那么这些词会连成一句话,这就是这幅图的描述。</p><p><img src="/2017/11/01/循环神经网络-RNN/2.png" alt=""></p><p>循环神经网络也可以用在情感分类中,我们来举游说的例子。我们会处理一定数量按照顺序排列的词,然后试着去把这个句子里的词按正面情感和负面情感来分类。</p><p><img src="/2017/11/01/循环神经网络-RNN/3.png" alt=""></p><p>在用机器进行语言翻译时,我们也可以用到循环神经网络我们需要让这个网络把这些单词,比如说是英文单词翻译成法语单词,所以我们把这些词放在循环神经网络中,我们把这称之为从一个序列翻译至另一个序列(seq to seq)。所以我们通过这个网络把英文句子翻译成了法语句子。</p><p><img src="/2017/11/01/循环神经网络-RNN/4.png" alt=""></p><p>最后一个例子就是视频分类,提到这个,也许你会想到把视频里的每一帧图像都按照一定数量的类来分类,但关键是你实际上不希望这个预测仅仅是当前时间所对应的当前脱氨的函数,你更希望他是当前时间之前所有图片的函数。那么循环神经网络就可以让你构建一个架构,这个架构可以让你得到一个预测得到某个时间点前所有图片的函数。即使你没有输入或输出的序列,你也可以用到循环神经网络,甚至你在最开始的那个例子中用到他,因为你可以对你的固定尺寸的输入或者输出按顺序的进行处理。</p><p><img src="/2017/11/01/循环神经网络-RNN/5.png" alt=""></p><p>那么循环神经网络实质上就是这个绿色的框。他自己有一个状态,并定期的接受数据,所以每一个在每一个时间点中它有内在的状态。然后它可以通过每个时间点所接受内容的根据函数来修改自己的状态,当然,他会在 RNN 里等待着,RNN 会根据他在接受输入时状态的参与程度来改变它的行为。</p><p><img src="/2017/11/01/循环神经网络-RNN/6.png" alt=""></p><p>我们还要关注基于 RNN 状态所生成的输出,我们可以在 RNN 上方生成这些向量,你会看到这样的图,但我要说的是 RNN 就只是在中间的一块,他有一个状态,可以随时间变化接受向量。我们可以在一些应用中根据他上方的状态进行假设</p><p><img src="/2017/11/01/循环神经网络-RNN/7.png" alt=""></p><h2 id="循环的过程"><a href="#循环的过程" class="headerlink" title="循环的过程"></a>循环的过程</h2><p>那么整个过程看起来是这样的:RNN 有某种状态,这里我们记为向量 H 。因为这也可以是许多向量的集合,所以这是一个更加综合的状态。我们现在要根据之前的隐藏状态 h 前的时间 t-1 以及现在输入向量 X 列一个方程,其中还要有一个函数,我把它称之为递归函数(recurrence function),这个函数有一个参数 W ,那么当我们对 W 进行改变的时候,我们就会发现RNN有了不同的表现。当然,我们想要的是RNN的某个特定的表现,所以我们要训练这些数据中的权重。</p><p>现在我要着重说明的是,在每个时间不长中我们都要有同一个函数,同一个固定大小的f<sub>w</sub>,在每一个时间步长中我们都要用这个函数。这样就既可以使我们按顺序使用循环回归网络,又不用去管这个序列的大小。无论输入或输出的序列有多长,在每一个时间不长中我们用的都是同一个函数。</p><p><img src="/2017/11/01/循环神经网络-RNN/8.png" alt=""></p><p>所以在一个特定的循环神经网络的情况中,在可用的最简单的循环中建立这个函数最简便的方法就是 vanilla RNN。在这个例子中,循环神经网络的状态就是这个隐藏状态(hidden state)h,我们还会得到一个循环方程式。这个方程式可以告诉你怎么来更新你的隐藏状态。这需要用到之前的隐藏状态还有现在输入的 X<sub>t</sub> 。在这个最特殊也是最简单的例子中,我们要用到这些权矩阵 W<sub>hh</sub> 和 W<sub>xh</sub>。这两个矩阵分别对之前的隐藏状态和现在的输入做投影,然后把这两者相加,并求出这个和的双曲正切值。这就是我们更新时间 t 下隐藏状态的方法。这个循环所做的就是告诉我们h是怎样随时间和目前时间步长的输入的变化而变化的。那么现在可以对h进行检测。比如用另一个矩阵对隐藏状态进行投影。</p><p><img src="/2017/11/01/循环神经网络-RNN/9.png" alt=""></p><p>那么,这就是一个完整的简单的例子,你可以把它用于你自己的神经网络中。为了讲述这到底怎么用的,我现在要讲 X<sub>t</sub> 和 y 在向量中的抽象形式,我们可以用语义学来分析这些向量。我们可以用到循环神经网络的其中一个方面,也就是字符级语言模型(character-level language model)。这是我认为理解RNN最简单的方法之一,因为它又直观又有趣。</p><h2 id="Char-RNN"><a href="#Char-RNN" class="headerlink" title="Char-RNN"></a>Char-RNN</h2><p>那么现在我们有了一个使用RNN的字符级语言模型,他的工作原理是:把一系列的字符输入到循环神经网络中。在每一个时间步长里,我们都会要求循环神经网络来预测下一个字符是什么,所以他就会根据他所看到的字符来预测下一个字符是什么。</p><p>举一个简单的例子,我们有一个训练序列 hello,字符词汇,即 [h, e, l, o],我们试着在这组训练数据中使用循环神经网络来学习预测序列中的下一个字符方法开始运作,将每一个字符按照先后不同时间点转化为一个循环神经网路。第一步完成的是 h 字符,然后是 e,我们就这样完成了 H-E-L-L。我们来使用一个词向量 – one hot向量,代表了字符的顺序和词汇。</p><p><img src="/2017/11/01/循环神经网络-RNN/10.png" alt=""></p><p>我们来看一下递推公式。</p><p><img src="/2017/11/01/循环神经网络-RNN/11.png" alt=""></p><p>在每一个测试中我们从h开始,然后我们要求计算隐藏层,每一个时间步骤使用我们的地推公式。假设这一层中只有三个数字,那么我们用一个三维向量,基本在时间点上总结了所有的字符,直到最后一个。每一个时间步骤上都有隐藏层,现在我们可以预测每个时间步骤所连接的序列中的下一个字符是什么。</p><p><img src="/2017/11/01/循环神经网络-RNN/12.png" alt=""></p><p>例如,因为这个单词中有四个字符,我们预测在每个时间点有四个数字。例如在第一个位置,我们对应了字母 H ,同时 RNN 在这时候的权重已经计算到了他现在的字母以及下一个位置和字符。那么推断,H 对应的下一位字符的权重是 H 的话是 1.0,E 是 2.2,L 是 -3.0,O 是 4.1,除了其他可能性,当然,我们也知道,在这个训练序列中,E 是在 H 后面的那个字符,所以事实上,变为绿色的那个 2.2 是正确的答案,我们希望这个值是高的,所以我门要让其他值比较低,这就使我们基本上有一个目标,谁会是序列中的下一个字符。</p><p><img src="/2017/11/01/循环神经网络-RNN/13.png" alt=""></p><p>我们仅仅是想让这一类的值是高的,其他的值是低的,包括绿色的信号、损失函数,并且通过这些链接向后传播。这种用来思考的方法是每个时间步骤,我们基本上有一个大的 softmax 分类器,这些大的 softmax 中的每一个结束后会接着下一个字符,在每个点上我们知道下一个字符是什么。所以我们只是得到了从上到下的损失,并且通过这张图流通,向后至所有的箭头,我们要得到所有权重矩阵的梯度,这样我们将会知道如何转移矩阵。</p><p>那么当前的问题来自于 RNN,所以我们要会塑造这些权重,这才是正确的方式来形成字符,并且你可以想象的出来你是如何训练的。</p><h2 id="训练-RNN"><a href="#训练-RNN" class="headerlink" title="训练 RNN"></a>训练 RNN</h2><p>正如我提到的每个递归场景都有自己的功能,我们每一个时间步骤都有一个 W<sub>xh</sub> ,也有一个 W<sub>hy</sub> 。我们在这个图解中用了四次 W<sub>xh</sub> 和 W<sub>hy</sub>,向后传播时,我们通过这些给他们计数,因为我们会把所有的添加到相同的权重矩阵,也因为他已经被用在多个时间步骤,这使我们能够处理不同输入的大小,因为即使我们每次做相同的事情,功能的数量也不会相同。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">import numpy as np</span><br><span class="line"></span><br><span class="line"># data I/O</span><br><span class="line">data = open('input.txt', 'r').read() # should be simple plain text file</span><br><span class="line">chars = list(set(data))</span><br><span class="line">data_size, vocab_size = len(data), len(chars)</span><br><span class="line">print 'data has %d characters, %d unique.' % (data_size, vocab_size)</span><br><span class="line">char_to_ix = { ch:i for i,ch in enumerate(chars) }</span><br><span class="line">ix_to_char = { i:ch for i,ch in enumerate(chars) }</span><br></pre></td></tr></table></figure><p>在最开始,正如你所看到的,只有 numpy,一些文本数据正在加载,所以我们在这里只是收集了大量的字符序列,在这种情况下输入 txt 文件,然后我们会得到文件中的所有字符,我们还会找到所有独一无二的字符。然后我们创建映射字典,映射字符索引,从索引能找到字符,我们最基本的还是为了字符。从表面上看是一大堆的数据,我们有数百个的字符或者类似的东西并且在序列里排序,所以我们把索引关联到每个字符上,然后我们在进行初始化。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"># hyperparameters</span><br><span class="line">hidden_size = 100 # size of hidden layer of neurons</span><br><span class="line">seq_length = 25 # number of steps to unroll the RNN for</span><br><span class="line">learning_rate = 1e-1</span><br><span class="line"></span><br><span class="line"># model parameters</span><br><span class="line">Wxh = np.random.randn(hidden_size, vocab_size)*0.01 # input to hidden</span><br><span class="line">Whh = np.random.randn(hidden_size, hidden_size)*0.01 # hidden to hidden</span><br><span class="line">Why = np.random.randn(vocab_size, hidden_size)*0.01 # hidden to output</span><br><span class="line">bh = np.zeros((hidden_size, 1)) # hidden bias</span><br><span class="line">by = np.zeros((vocab_size, 1)) # output bias</span><br></pre></td></tr></table></figure><p>首先是隐藏大小的初始值,因为你会用到 RNN,所以你不能让他成为 100,我们有学习率,序列长度在这里达到了 25,这是一个你需要意识到的参数。此外,需要注意的是,如果我们的输入数据很大,比如说有数百万次,你就没有办法把它放在所有的上面,因为我们需要保持所有的数据和内存,这样我们就可以开始向后传播。但是事实上,我们没办法把他们所有都存在内存中,并且向后传播所有的输入数据块,在这种情况下,我们可以在一段时间内通过一个 25 字符的序列,我会在下文讲到。我们有整个数据集,但是现在要让他变成在某一时间只有 25 个字符的数据块,并且每次都是按时通过 25 个字符,因为我们负担不起太长时间的向后传播,因此我们必须记录所有的数据。所以我们让数据块包含 2 5个字符。然后我们例举了所有的 W 矩阵,分析了一些随机的方框所以 W<sub>xh</sub> 和 W<sub>hy</sub> 都是我们的参数,这样我们才能训练向后传播。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><span class="line">n, p = 0, 0</span><br><span class="line">mWxh, mWhh, mWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why)</span><br><span class="line">mbh, mby = np.zeros_like(bh), np.zeros_like(by) # memory variables for Adagrad</span><br><span class="line">smooth_loss = -np.log(1.0/vocab_size)*seq_length # loss at iteration 0</span><br><span class="line">while True:</span><br><span class="line"> # prepare inputs (we're sweeping from left to right in steps seq_length long)</span><br><span class="line"> if p+seq_length+1 >= len(data) or n == 0: </span><br><span class="line"> hprev = np.zeros((hidden_size,1)) # reset RNN memory</span><br><span class="line"> p = 0 # go from start of data</span><br><span class="line"> inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]]</span><br><span class="line"> targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]]</span><br><span class="line"></span><br><span class="line"> # sample from the model now and then</span><br><span class="line"> if n % 100 == 0:</span><br><span class="line"> sample_ix = sample(hprev, inputs[0], 200)</span><br><span class="line"> txt = ''.join(ix_to_char[ix] for ix in sample_ix)</span><br><span class="line"> print '----\n %s \n----' % (txt, )</span><br><span class="line"></span><br><span class="line"> # forward seq_length characters through the net and fetch gradient</span><br><span class="line"> loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev)</span><br><span class="line"> smooth_loss = smooth_loss * 0.999 + loss * 0.001</span><br><span class="line"> if n % 100 == 0: print 'iter %d, loss: %f' % (n, smooth_loss) # print progress</span><br><span class="line"> </span><br><span class="line"> # perform parameter update with Adagrad</span><br><span class="line"> for param, dparam, mem in zip([Wxh, Whh, Why, bh, by], </span><br><span class="line"> [dWxh, dWhh, dWhy, dbh, dby], </span><br><span class="line"> [mWxh, mWhh, mWhy, mbh, mby]):</span><br><span class="line"> mem += dparam * dparam</span><br><span class="line"> param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update</span><br><span class="line"></span><br><span class="line"> p += seq_length # move data pointer</span><br><span class="line"> n += 1 # iteration counter </span><br></pre></td></tr></table></figure><p>然后我们先看最后一部分。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># prepare inputs (we're sweeping from left to right in steps seq_length long)</span><br><span class="line"> if p+seq_length+1 >= len(data) or n == 0: </span><br><span class="line"> hprev = np.zeros((hidden_size,1)) # reset RNN memory</span><br><span class="line"> p = 0 # go from start of data</span><br><span class="line"> inputs = [char_to_ix[ch] for ch in data[p:p+seq_length]]</span><br><span class="line"> targets = [char_to_ix[ch] for ch in data[p+1:p+seq_length+1]]</span><br></pre></td></tr></table></figure><p>在这里,我们有主函数,我们把这里的一些初始值设置为20,然后我们继续对一批数据进行采样,所以这也是我们在这个数据集处批处理 25 个字符的地方。这些就是输入列表,输入列表基本上只有对应的 25 个字符。你所看到的目标是所有的相同字符,除去移除的那个,因为这些都是我们试图在每一层预测的检索。重要的目标是那 25 个字符的输入列表,还有将会被移除的目标,这就是我们基本的数据采样。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"># sample from the model now and then</span><br><span class="line"> if n % 100 == 0:</span><br><span class="line"> sample_ix = sample(hprev, inputs[0], 200)</span><br><span class="line"> txt = ''.join(ix_to_char[ix] for ix in sample_ix)</span><br><span class="line"> print '----\n %s \n----' % (txt, )</span><br></pre></td></tr></table></figure><p>我们使用低层次字符和测试时间的方式,就是我们可以看到一些字符,然而他们并不是那些在这个序列中下一个字符的分布,所以你可以想象从他的采样到他在分布中形成下一个字符。我们需要不断的采样,并且持续下去,就可以生成任意的文本数据,这就是我们要做的代码,并且生成了样本函数。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"># forward seq_length characters through the net and fetch gradient</span><br><span class="line"> loss, dWxh, dWhh, dWhy, dbh, dby, hprev = lossFun(inputs, targets, hprev)</span><br><span class="line"> smooth_loss = smooth_loss * 0.999 + loss * 0.001</span><br><span class="line"> if n % 100 == 0: print 'iter %d, loss: %f' % (n, smooth_loss) # print progress</span><br></pre></td></tr></table></figure><p>现在我们来说一下 loss function(损失函数),损失函数接受输入的目标,它也接受H prep,H prep 的缺点就是他的形状向量来自于前一个数据块,所以我们要分批进行25个字符的区块,并且我们要跟踪在 25 个字符结尾的是什么情景,以至于在向后传播相遇时,我们可以看到 H 最初的形态。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line"># perform parameter update with Adagrad</span><br><span class="line"> for param, dparam, mem in zip([Wxh, Whh, Why, bh, by], </span><br><span class="line"> [dWxh, dWhh, dWhy, dbh, dby], </span><br><span class="line"> [mWxh, mWhh, mWhy, mbh, mby]):</span><br><span class="line"> mem += dparam * dparam</span><br><span class="line"> param += -learning_rate * dparam / np.sqrt(mem + 1e-8) # adagrad update</span><br></pre></td></tr></table></figure><p>因此我们确保隐藏层通过这个方式在区块之间是基本上正确的传播,但是我们只向后传播这些 25 次,为此我们添加了损失函数和梯度,还有所有的权重矩阵和方框,可以输出损失,然后会有一个参数的更新,告诉我们要更新比较老的部分。注意:这里共有 25 个 softmax 分类器,我们对这 25 个端同时进行反向传播,最后将所求梯度加起来。</p><p>损失函数是这一块代码,它包含了向前传播和向后传播两部分方法。我们可以比较一下向前传播和向后传播。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">def lossFun(inputs, targets, hprev):</span><br><span class="line"> """</span><br><span class="line"> inputs,targets are both list of integers.</span><br><span class="line"> hprev is Hx1 array of initial hidden state</span><br><span class="line"> returns the loss, gradients on model parameters, and last hidden state</span><br><span class="line"> """</span><br><span class="line"> xs, hs, ys, ps = {}, {}, {}, {}</span><br><span class="line"> hs[-1] = np.copy(hprev)</span><br><span class="line"> loss = 0</span><br><span class="line"> # forward pass</span><br><span class="line"> for t in xrange(len(inputs)):</span><br><span class="line"> xs[t] = np.zeros((vocab_size,1)) # encode in 1-of-k representation</span><br><span class="line"> xs[t][inputs[t]] = 1</span><br><span class="line"> hs[t] = np.tanh(np.dot(Wxh, xs[t]) + np.dot(Whh, hs[t-1]) + bh) # hidden state</span><br><span class="line"> ys[t] = np.dot(Why, hs[t]) + by # unnormalized log probabilities for next chars</span><br><span class="line"> ps[t] = np.exp(ys[t]) / np.sum(np.exp(ys[t])) # probabilities for next chars</span><br><span class="line"> loss += -np.log(ps[t][targets[t],0]) # softmax (cross-entropy loss)</span><br></pre></td></tr></table></figure><p>在向前传播,你应该基本认识到,我们得到的亏损目标和我们被等待接受的这 25 个索引并不是我们通过他们的传递从 1 到 25,我们创建了文本的输入向量,虽然只是一些 0,并且我们设置了一个 one-hot 编码,无论他的指数是什么,我们把它集成一个编码。在计算中,循环公式用的就是这个方程。hs[t],在这里 h 就是跟踪不同步骤中结果的量,我们使用循环公式计算隐含层的向量以及输出向量。接着使用 softmax 公式,得到归一化的概率,损失值则等于 -log(正确类的概率)。</p><p><img src="/2017/11/01/循环神经网络-RNN/16.png" alt=""></p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"># backward pass: compute gradients going backwards</span><br><span class="line"> dWxh, dWhh, dWhy = np.zeros_like(Wxh), np.zeros_like(Whh), np.zeros_like(Why)</span><br><span class="line"> dbh, dby = np.zeros_like(bh), np.zeros_like(by)</span><br><span class="line"> dhnext = np.zeros_like(hs[0])</span><br><span class="line"> for t in reversed(xrange(len(inputs))):</span><br><span class="line"> dy = np.copy(ps[t])</span><br><span class="line"> dy[targets[t]] -= 1 # backprop into y. see http://cs231n.github.io/neural-networks-case-study/#grad if confused here</span><br><span class="line"> dWhy += np.dot(dy, hs[t].T)</span><br><span class="line"> dby += dy</span><br><span class="line"> dh = np.dot(Why.T, dy) + dhnext # backprop into h</span><br><span class="line"> dhraw = (1 - hs[t] * hs[t]) * dh # backprop through tanh nonlinearity</span><br><span class="line"> dbh += dhraw</span><br><span class="line"> dWxh += np.dot(dhraw, xs[t].T)</span><br><span class="line"> dWhh += np.dot(dhraw, hs[t-1].T)</span><br><span class="line"> dhnext = np.dot(Whh.T, dhraw)</span><br><span class="line"> for dparam in [dWxh, dWhh, dWhy, dbh, dby]:</span><br><span class="line"> np.clip(dparam, -5, 5, out=dparam) # clip to mitigate exploding gradients</span><br><span class="line"> return loss, dWxh, dWhh, dWhy, dbh, dby, hs[len(inputs)-1]</span><br></pre></td></tr></table></figure><p>在向后传播,我们从第 25 层穿过隐藏层,直到第一层,你或许已经注意到,这里我并不知道我需要处理多少细节,但是这里反向通过了一个 softmax 函数,反向通过了激活函数,我对所有的梯度和参数进行加和。值得一提的是,梯度是与权值同尺寸的的矩阵,在代码中使用了 “+=”,因为在反向传播过程中权值矩阵会求得多个梯度,我们需要将这些梯度叠加起来,因为我们向前的每一步都用到了权值矩阵,所以在反向求导是也要不断的叠加梯度。这样我们就求出了梯度,现在就可以利用损失函数对初值进行更新了。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><span class="line">def sample(h, seed_ix, n):</span><br><span class="line"> """ </span><br><span class="line"> sample a sequence of integers from the model </span><br><span class="line"> h is memory state, seed_ix is seed letter for first time step</span><br><span class="line"> """</span><br><span class="line"> x = np.zeros((vocab_size, 1))</span><br><span class="line"> x[seed_ix] = 1</span><br><span class="line"> ixes = []</span><br><span class="line"> for t in xrange(n):</span><br><span class="line"> h = np.tanh(np.dot(Wxh, x) + np.dot(Whh, h) + bh)</span><br><span class="line"> y = np.dot(Why, h) + by</span><br><span class="line"> p = np.exp(y) / np.sum(np.exp(y))</span><br><span class="line"> ix = np.random.choice(range(vocab_size), p=p.ravel())</span><br><span class="line"> x = np.zeros((vocab_size, 1))</span><br><span class="line"> x[ix] = 1</span><br><span class="line"> ixes.append(ix)</span><br><span class="line"> return ixes</span><br></pre></td></tr></table></figure><p>最后,在这里我们有个采样方法。我们使用这个方法,基于我们之前训练出来的模型(如字符串的衔接)来生成新的文本。我们随机获得一些字符,并用训练好的模型对这个字符进行扩展,我们使用循环公式,获得字符的概率分布,然后从中取样,取出最有可能出现的字符。然后我们开始取下一个字符,依次迭代,直到我们获得足够长的文本。</p><p>共有 25 个 softmax 分类器,我们对这 25 个端同时进行反向传播,然后将所求梯度加起来</p><p><a href="https://gist.github.com/karpathy/d4dee566867f8291f086" target="_blank" rel="noopener">源码地址</a></p><h2 id="训练成果"><a href="#训练成果" class="headerlink" title="训练成果"></a>训练成果</h2><p>下面是 Andrej Karpathy 和 Justin Johnson 做的一些训练。</p><p>他们用 Char-RNN 去学习一些文本,RNN 去读这些文本、小段代码,我们注意到某些特定的单元以及 RNN 隐藏层的状态,我们用颜色来标注这些单元,来表示这些单元是否“兴奋”。可以看出,有很多隐藏层的状态很难去理解,他们时而兴奋时而沉默,显得很奇怪。这是因为他们关注的是字符级的变化,例如在 ah 之后接 e 的情况有多少等。</p><p><img src="/2017/11/01/循环神经网络-RNN/14.png" alt=""></p><p>但是有些单元表达的信息是可以理解的。对于像引号检测这样的单元而言,当前一个引号出现,它们即处于“开启状态”,一直到后一个引号出现。跟踪这样的单元比较可靠,这是从反向传播中得到的信息。很明显可以看出,对于这样的一个字符级模型,引导内外信号强度的差别很大,这是值得学习的有用特征,于是 RNN 用一部分隐藏层来对引号进行跟踪,以分辨目前是在引号中还是引号外。</p><p><img src="/2017/11/01/循环神经网络-RNN/15.png" alt=""></p><p>注意,这里的 RNN 使用了包含 100 个字符的序列进行学习,即我们只在 100 层上进行反向传播,这 100 层才是这个单元的学习区间,因为他不知道长于 100 字符的情况,但是这里的引号之间的长度明显大于 100,这个情况表明你可以对小于 100 长度的数据进行训练,然后将情况合理的推广到更长的序列,所以对于长度长于 100 字符的序列,模型依然有效</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><ul><li>在 RNN 中为什么不使用正则化?</li></ul><p>因为在 RNN 中使用正则化并不常见,甚至有时候正则化反而会得出更差的结果,所以我有时候不考虑他,他属于一种超参数</p><p>===</p><ul><li>我们是否要去学习这些输入的单词本身含义?</li></ul><p>对于这 25 个连续的单词,我们并不关心一个词是否存在,我们关心的是字符所处的位置,这个模型中我们不需要了解字符,也不需要了解语言,模型所学习的是字符的序列</p><p>===</p><p>经过训练后的 RNN 很少会犯语法错误,比如括号的匹配一类的,但是所生成的文章的意思会随着训练的进行而逐渐明朗。</p>]]></content>
<summary type="html">
<p>循环神经网络,英文名是Recurrent Neural Network,随着对它的不断深入了解,你会发现这个神经网络模型是多么的有趣。你可以喂给它莎士比亚的作品,经过有效的训练后它会给你输出带有莎士比亚风格的句子;你喂给它 Linux 源码,它会装模作样的给你生成一段它自己写的代码,尽管会有语意错误,但是不会有语法错误。</p>
<p>那么废话不多说,直接进入正文吧。</p>
<h2 id="Recurrent-Neural-Network"><a href="#Recurrent-Neural-Network" class="headerlink" title="Recurrent Neural Network"></a>Recurrent Neural Network</h2><pre><code>循环神经网络的好处就是他们会在你建立神经网络架构时给予你很高的灵活性
</code></pre><p>咱们先来看一下最左边这个例子,所以一般你在处理神经网络的时候,你会得到一个固定大小的向量(即图中的红色框),然后用隐藏层 – 这个绿色的框来处理它,你就会得到一个固定大小的向量,就是蓝色。所以会有一个固定大小的图像进入网络,然后要输出一个固定大小的向量,他是一个 class score,在循环神经网络中,我们可以采用不同的顺序实现,比如从输入开始或输出开始,或者两者同时开始。</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
<entry>
<title>卷积神经网络之定位检测</title>
<link href="https://saberda.github.io/2017/10/31/CNN%E4%B9%8B%E5%AE%9A%E4%BD%8D%E6%A3%80%E6%B5%8B/"/>
<id>https://saberda.github.io/2017/10/31/CNN之定位检测/</id>
<published>2017-10-31T04:41:05.000Z</published>
<updated>2017-10-30T17:38:17.000Z</updated>
<content type="html"><![CDATA[<p>简单的来复习一下卷积神经网络,我们分解了那些层次并花费了大量的时间来理解卷积算子的工作原理,并且学习如何把一个特征图像转换到另一个,这是通过滑动特征图上的窗口来计算内积,然后把这些表现通过许多层的处理来转化。如果你还记得这些较低的学习边界和颜色的卷积层,而高层则学习更加复杂的物体部分。我们还讲到了池化,池化用于抽样,并且缩小网路内的特征表现,这是我们看到的一个很普通的层级结构。我们还对特定的网络架构进行分析,这样你就能看到这些事物是如何在实践中被连接到了一起。</p><p>再简单复习一下经典模型,LeNet,他是一个很小的5层网络(98年)用于数字识别。AlexNet,是他拉开了深度学习的火爆序幕,之后是ZFNet,是一个图像分类网络。我们现在明白在分类中,更深一般会更好,例如表现良好的VGG和GoogLeNet。接下来我们讲到的是ResNet的新的神奇的网络,它深达152层,没有强大GPU的还是不要去轻易尝试使用这个。正如你们所见,这些网络都在变得更深,也随之表现的更好。</p><a id="more"></a><p><img src="/2017/10/31/CNN之定位检测/1.png" alt=""></p><h2 id="定位"><a href="#定位" class="headerlink" title="定位"></a>定位</h2><p>但是这些都仅仅为了分类,所以我现在介绍下定位,这是在计算机视觉另一个很大很重要的问题。正如我刚才所说的,网络越深结果可能越好这个结论,在这两个任务中一样成立,所以到目前为止我们一直在讲分类。其实就是对于一张图片,我们要对不同数量上的物体类别进行分类。这是一个在计算机视觉中很好也很基础的问题,我们用它来理解CNN。</p><p><img src="/2017/10/31/CNN之定位检测/2.png" alt=""></p><p>但人们在研究过程中也遇到了许多其他的问题。这其中就有分类和定位。现在,除了仅仅把图像和类标签进行分类,我们还希望把框框放在图像中,表明这就是分类发生的区域。还有一个人们正在解决的问题就是检测。所以这里还是一些对象类别的数量,但实际上我们希望选中在图像内所有该类别的目标以及它们周围的选框。在一个最近人们才开始着手研究的项目中有一个令人着魔的东西,他叫做实时图像分割,其原理就是你有一定数量的类别,你想在图像中找到所有类别对应的目标,但是相比于用框框选,实际上更希望圈出围绕该目标的轮廓,并识别所有的属于每一个目标的像素。图像分割是有一些疯狂,所以我在这里不去讨论他,但是你们也要了解他。</p><p>我们今天则要着重关注定位和检测的内容。这两者的区别就是我们所找到的目标的数量。在定位中,一般是有一个或一种对象;但在检测中,会有很多个或多种对象。这看起来是个很小的区别,但是他对最终的架构起很重要的作用。所以我们会先讲分类和定位。因为这两个是最简单的。</p><p>概括一下我刚才所讲的,分类是一张图片中只有一种给定类别标签的对象,定位则是在图像中给对象圈上框框,而分类定位就是指我们要同时符合这两者的要求。</p><p><img src="/2017/10/31/CNN之定位检测/3.png" alt=""></p><p>介绍一下人们目前用此达成的任务会给你们一些启发。我们讲到了ImageNet分类的挑战,ImageNet还有分类+定位的挑战。相似与分类,这里有1000个类,在这些类中,每一个训练目标都有一个类和许多的在图像内部对应该分类的位置选框。那么现在有一个用于测试的更小的算法,这个算法会得出五个猜想,与其说这些猜想仅是类标签,不如说他们是与位置框框在一起的类标签。为了得到正确的猜想,你要得到正确的类标签和正确的位置框框。如果得到了正确位置,那就代表你的结果很接近分割的准确率(loU: intersection of union)了,不过你现在不必去考虑loU的事情。</p><p>所以重复一遍就是,只要你五个猜想中的一个是正确的,你就会得到正确的imageNet。所以这就是人们一直研究的分类+定位的最主要数据集。</p><h2 id="回归的使用"><a href="#回归的使用" class="headerlink" title="回归的使用"></a>回归的使用</h2><p>当研究定位时,有一个特别有用的很基础的范式:回归。我不知道在回想机器学习的课程当中,你能不能想起分类算法,比如SVM,还有回归算法;当我们讨论定位的时候,我们可以把它视作回归问题。</p><p>比如说我们有一张图片,这张图片要经过一系列的处理过程,并最终生成四个代表框框大小的实数。有许多不同的参数来描述框框,人们常用的是用XY坐标定位框框的左上角、宽度和高度,不过也有其他的变量,但对于框框来说这些变量都只能是四个数。还有一些真实准确(ground truth)的选框,也是用四个变量描述的。</p><p><img src="/2017/10/31/CNN之定位检测/4.png" alt=""></p><p>我们还可以计算损失,比如欧式损失,欧式损失是一个很标准的选择,是我们所生成的数字与正确的数字之间的差额。那么现在我们就可以同样训练他就像我们训练分类网络那样:我们用ground truth边框对许多批数据进行抽样,并向前传播,我们所做的预测与正确的预测只差所带来的计算机损失则往回传播,一次为方法来升级网络。</p><p>这个范式很简单,也使得定位的实行也变得简单。这里有一个简单的秘诀,这个秘诀会告诉你怎样来实时分类+定位。首先你要下载现有的前训练模型。如果你对自己的技术感到自行的话,你可以自己进行训练,训练模型可以使AlexNet,VGG,GoogLeNet等。那么我们就可以得到训练后生成class scores的全连接层.</p><p><img src="/2017/10/31/CNN之定位检测/5.png" alt=""></p><p>但我们先暂且不去管他,现在我们在这个网络里再接上一些新的FC。我们把这称之为回归网络(regression head)。但我认为这实际上跟FC是一个意思,只不过输出一些实数。</p><p><img src="/2017/10/31/CNN之定位检测/6.png" alt=""></p><p>现在我们训练它就像训练我们自己的分类网络,唯一区别就是我们把class cores和classes替换成L2 loss和ground truth框框。除了以上的区别,我们使用训练原来网络的方法来同样训练这个新的网络。当测试的时候,我们就用这斜来实现分类和定位。</p><p><img src="/2017/10/31/CNN之定位检测/7.png" alt=""></p><p>我们找了一些照片,并且训练了分类网络和定位网络,我们将图片进行处理就能得到class scores和框框,就这样任务完成了,按照步骤做就能在你们的项目中进行分类和定位。</p><p>在这个方法中还有个细节要注意,在进行回归时一共有两种主要方式,<strong>不定类回归</strong>(class-agnostic regression)和<strong>特定类回归</strong>(class-specific regression)。</p><p><img src="/2017/10/31/CNN之定位检测/8.png" alt=""></p><p>不定位回归是无论我使用什么类别,全连接层中都使用相同的结构和权值来得到边界昂狂框(bounding box),得到的结果总是4个数字,无论是什么类别他们表示的都是那个框。另外一种选择是特定类回归,输出的结果是C承上4个数字,相当于每种类别有一个边界框,很多人发现这种方法在很多情况下效果会更好。直观地说这种方法确实是有意义的,试着思考一下,对一只猫猫确定其边界和对火车确定边界总是有一些不同的,所以你需要在网络中有不同的处理来应对这个问题。他稍微改变了计算损失的方式,不仅仅是使用ground truth class来计算损失,除此之外在其他方面这两种基本属于同一思想。</p><p><strong>另一个需要选择的地方是在网络的那个位置进行回归</strong>。这也不是很重要,不同的人有不同的方式。这个比较常见的选择是将回归层(regression head)放在最后一次卷积层后,这就像是你对全连接层重新做了初始化,像Overfeat、VGG网络就是这种方式。另一种选择是将回归层放在FC之后,像deepPose和RCNN就是这种方式工作的。这两种方式效果都不错,你可以按照自己的喜好去设置。</p><p><img src="/2017/10/31/CNN之定位检测/9.png" alt=""></p><p>顺便提一下,我们可以用这个框架对不止一种物体来划定其边界框。通过这个分类和定位,我们输入一张图片,然后关注他所产生的这个图片的边界框。但是在一些情况下,你提前知道要对固定数量的物体进行划分边界框,在这里总结起来很简单,回归层输出了每个物体对应的边界框,然后你用同样的方式对网络再次进行训练。</p><p>同时对多个物体进行定位是非常通用而且功能强大的。例如,这项技术在人类姿势判定中得到应用。当你输入一个人的特写镜头时,人们想知道这个人的姿势是什么。人体都有着固定数量的身体关节,像手腕。我们需要去找到所有的关节,所以当我们输入这张图片时通过一个卷积网络,然后我们能够在XY轴上找到每个关节的位置,从而能够让我们对这个人的姿势进行预测。</p><p><img src="/2017/10/31/CNN之定位检测/10.png" alt=""></p><p>总的来说,定位的思想和通过回归定位固定数量的物体是非常简单的。我知道有时你想在项目中进行检测,而不是定位和分类,如果你们向更深入的理解图像,并在进行项目时能够考虑到这些方面的话,我希望你们能够考虑一下这个定位的框架:如果在每个图像中都有固定数量的物体需要定位的话,你可以将它看成是定位问题,这样会使问题更简单一点。</p><p>所以这个通过回归定位的方法确实很简单,他确实有用,但是如果你想在imageNet这样的比赛占有一席之地的话,你需要一些新的东西。所以人们定位是考虑到的方法还包括了滑动窗口(sliding window)。在这个方法中你依然要在网络中进行分类和定位的操作,但是你不只是运行了一次,而是在不同的位置多次进行,再讲不同位置进行聚合,事实上有一种高效的方法来做这个事情。</p><h2 id="Overfeat"><a href="#Overfeat" class="headerlink" title="Overfeat"></a>Overfeat</h2><p>为了更具体的了解滑动窗定位方法的工作过程,我们先了解一下Overfeat的结构。</p><p><img src="/2017/10/31/CNN之定位检测/11.png" alt=""></p><p>Overfeat是在imageNet定位挑战赛中的优胜者,这个结构看上去和我们之前讲过的很相似,开始是一个AlexNet,然后是分类层和回归层。分类层输出产生类的分数,因为这是一个ALexNet类型的结构,期望的输入图片大小是 221 * 221,但是实际上我们可以输入更大的图片,此时滑动窗的方法能够起到作用。</p><p>比如说 257 * 257 的图片,然后在图片左上方进行分类和定位,从而得到类的分数和相应的边界框。重复这个操作,使用相同的分类和定位网络,在这个图片四个角落都运行了一次,最终我们会得到四个边界框,对于每个位置都有一个边界框和类的分数。但是事实上我们只需要一个边界框,所以使用了一些方法对这些边界框和分数进行了合并,这部分很复杂,我目前也不是很懂。反正通过组合和聚集不同位置的边界框可以帮助这个模型进行错误修正,他的工作效果很好。</p><p>(当你进行回归时,输出的是(表示边界框的)四个数字,这个数字理论上可能出现在任何地方,他甚至不一定在图片内部,当你再用滑动窗方法进行训练的时候,你对不同位置进行操作时坐标轴也会进行一些改变,这也是需要注意的一个地方)</p><p>但是在实际操作中要使用远多于四个的边界框,使用的输入图片的大小也有多种,你可以看到这是从论文中摘取的图像,在左边的图像中,你可以看到在不同的位置对这个网络进行评估,在中间的图像中你可以看到的是对每个位置输出响应的边界框,在图像底部你可以看见是对于这些位置分数的映射。这看上去有点乱,但是采用这个聚合方法,他们最终能得到那个熊的最终边界框,然后确定这个图片是一只熊。</p><p><img src="/2017/10/31/CNN之定位检测/12.png" alt=""></p><h2 id="全连接层与卷积层的转换"><a href="#全连接层与卷积层的转换" class="headerlink" title="全连接层与卷积层的转换"></a>全连接层与卷积层的转换</h2><p>一个你能够预料的问题是对每个类别都运行这个网络的花费是非常昂贵的,事实上有个高效的方法。我们通常将网络看成由卷积网络和链接网络构成的,但是换一种角度看,一个全连接网络是有4096个数字构成是一个向量,如果不把他看成一个向量,而是将它看成另一个卷积的特征映射。(下图为原结构)</p><p><img src="/2017/10/31/CNN之定位检测/13.png" alt=""></p><p>我们将它进行转换成 1 <em> 1 维的,所以这个方法是将全连接层转换成卷积层,我们得到了这个卷积特征映射,通过这个特征映射我们产生4096维向量的每一个元素,我们考虑通过一个 5 </em> 5 的卷积层,而不是对特征映射进行直接的变换。这看上去有一些怪异,但是思考以后你会发现这很有意义。所以我们将这个全连接层进行一个 5 <em> 5 的卷积运算,然后我们通过另一个全连接层,从4096维到4096维,这是一个 1 </em> 1 的卷积运算。如果你认真思考用数学公式演算,你就能理解了。(下图为转换后结构)</p><p><img src="/2017/10/31/CNN之定位检测/14.png" alt=""></p><p>现在我们要做的是使用卷积层来组合原来的网络的全连接层,这样效果很好因为我们的网络就完全只由卷积层与池化层和元素操作了。我们实际上可使用不同尺寸的图片来运行网络,这种方法可以通过一个非常节约计算资源的方法来给我们一个等价的、与位置无关的结果。</p><p>下面看看他的原理。</p><p>在训练环节你用的是一个 14 <em> 14 的输入图片进入卷积层,然后是这个全连接层,我们现在把它视为卷积层,在这里我们有个 5 </em> 5 的卷积核,通过这个卷积核把之前的结果变成一个 1 <em> 1 的元素。在这里我们没有展现深度,这里的 1 </em> 1 其实是 1 <em> 1 </em> 4096,这样我们就把这个层转化成了额卷积层。</p><p>下面这一行,我们的卷积层运行在了一张更大尺寸的图片上,你们可以看到我们加了一些额外的像素,然后我们在运行一次同样的卷积池化过程,最后得到了一个2*2的输出。这样最大的好处就是我们现在可以在不同大小的图片上使用相同的计算过程,这样能够大大提高我们的效率。</p><p><img src="/2017/10/31/CNN之定位检测/15.png" alt=""></p><p>现在我的输出是原来的四倍大,但是我们计算消耗的时间远远小于四倍,因为如果你考虑两次计算中不同的地方,其实额外的计算量只发生在黄色的这一部分,所以现在我们十分高效的计算了神经网络在不同位置上的结果,在这同时并没有带来特别大的额外计算量。</p><h2 id="补充"><a href="#补充" class="headerlink" title="补充"></a>补充</h2><ul><li>你可以使用同一个网络进行分类和定位,也可以使用不同的网络分别进行分类和定位</li><li>尽量不要选择没有使用反向传播的网络</li></ul>]]></content>
<summary type="html">
<p>简单的来复习一下卷积神经网络,我们分解了那些层次并花费了大量的时间来理解卷积算子的工作原理,并且学习如何把一个特征图像转换到另一个,这是通过滑动特征图上的窗口来计算内积,然后把这些表现通过许多层的处理来转化。如果你还记得这些较低的学习边界和颜色的卷积层,而高层则学习更加复杂的物体部分。我们还讲到了池化,池化用于抽样,并且缩小网路内的特征表现,这是我们看到的一个很普通的层级结构。我们还对特定的网络架构进行分析,这样你就能看到这些事物是如何在实践中被连接到了一起。</p>
<p>再简单复习一下经典模型,LeNet,他是一个很小的5层网络(98年)用于数字识别。AlexNet,是他拉开了深度学习的火爆序幕,之后是ZFNet,是一个图像分类网络。我们现在明白在分类中,更深一般会更好,例如表现良好的VGG和GoogLeNet。接下来我们讲到的是ResNet的新的神奇的网络,它深达152层,没有强大GPU的还是不要去轻易尝试使用这个。正如你们所见,这些网络都在变得更深,也随之表现的更好。</p>
</summary>
<category term="ML" scheme="https://saberda.github.io/categories/ML/"/>
<category term="Neural Networks" scheme="https://saberda.github.io/tags/Neural-Networks/"/>
</entry>
</feed>