-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsearch.xml
More file actions
1680 lines (1680 loc) · 417 KB
/
search.xml
File metadata and controls
1680 lines (1680 loc) · 417 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>BIO NIO AIO详解</title>
<url>/BIO-NIO-AIO%E8%AF%A6%E8%A7%A3.html</url>
<content><![CDATA[<h1 id="Java-BIO-NIO-AIO详解"><a href="#Java-BIO-NIO-AIO详解" class="headerlink" title="Java BIO NIO AIO详解"></a>Java BIO NIO AIO详解</h1><h2 id="同步,异步,阻塞,非阻塞"><a href="#同步,异步,阻塞,非阻塞" class="headerlink" title="同步,异步,阻塞,非阻塞"></a>同步,异步,阻塞,非阻塞</h2><p>这几个概念理解起来确实比较困难,特别是同步和阻塞,异步和非阻塞。首先要明确的一个概念是同步和异步主要是关注的是消息通信机制,所以同步和异步主要是关注客户端和服务端两个方面的消息如何通信。而阻塞和非阻塞主要是等待调用结果时的状态,所以关注的主要是当前线程在等待结果时能够做什么,如果在等待结果时当前线程能够做其他的事,则线程是非阻塞的;如果只能等待返回结果,则当前线程是阻塞的。下面举个例子来具体说明一下:<br>比如你跟书店老板打电话,确认是否书店中有哈利波特这本书。书店老板电话没有挂断,说你等一下,我现在查一下,你一直在等待,此时就是一种同步通信。而如果老板说我晚点电话通知你,然后挂断电话,此时就是异步通信。<br>让我们在换一个视角,还是打电话确定是否有哈利波特这本书这件事情。在打电话的过程中,你什么都没有干,只是一直在等待,那么此时你就是处于阻塞状态。而如果此时你正在看电视,此时你就是非阻塞状态,但是需要不定时的检查一下电话那边有没有回复。</p>
<blockquote>
<p>所谓同步,就是在发出一个<strong>调用</strong>时,在没有得到结果之前,该<strong>调用</strong>就不返回。但是一旦调用返回,就得到返回值了。换句话说,就是由<strong>调用者</strong>主动等待这个<strong>调用</strong>的结果.<br>而异步则是相反,<strong>调用</strong>在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在<strong>调用</strong>发出后,<strong>被调用者</strong>通过状态、通知来通知调用者,或通过回调函数处理这个调用。<br>阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。<br>在IO操作中,有以下四种组合:</p>
</blockquote>
<ol>
<li>同步阻塞IO:调用者发起IO操作请求,等待IO操作完成(阻塞)。IO操作的过程需要等待,等待服务端返回结果,操作执行完成后返回结果(同步)</li>
<li>同步非阻塞:调用者发起IO操作请求,询问IO操作的的状态,如果未完成,则立即返回;如果完成,则返回结果(非阻塞)。IO操作的过程需要等待执行完成才返回结果(同步)</li>
<li>异步阻塞:调用者发起IO操作请求,等待IO操作完成在返回(阻塞)。IO操作的过程不需要等待,操作完成后通过通知或者回调获取结果(异步)</li>
<li>异步非阻塞:调用者发起IO操作请求,询问IO操作的状态,如果未完成,则立即返回;如果完成,则返回结果(非阻塞)。IO操作的过程不需要等待,操作完成后通过通知或回调获得结果(异步)<br>在下面的具体介绍中,我会进行具体的说明。</li>
</ol>
<h2 id="Java-IO-操作类"><a href="#Java-IO-操作类" class="headerlink" title="Java IO 操作类"></a>Java IO 操作类</h2><p>Java中进行IO操作的类一般分为以下四类:</p>
<ol>
<li>字节流的输入和输出:InputStream和OutputStream</li>
<li>字符流的输入和输出</li>
<li>网络编程Socket</li>
<li></li>
</ol>
<h2 id="BIO"><a href="#BIO" class="headerlink" title="BIO"></a>BIO</h2><p>首先BIO是同步阻塞调用。阻塞是因为服务端在调用accept方法时,服务端会一直阻塞在accept方法上,直到在对应的端口上接收到数据;同步是因为客户端会一直等待服务端执行完成才返回结果。可以想一下JavaScript中的Ajax请求,在异步Ajax请求发出后,浏览器会执行接下来的JS代码,直到服务端发回处理结果,然后执行对应的回调函数。这是典型的异步请求。</p>
<h2 id="NIO"><a href="#NIO" class="headerlink" title="NIO"></a>NIO</h2><p>NIO(New IO or Non-Block IO)是一种同步非阻塞的通信模式。NIO客户端和服务器之间通过Channel通信。NIO可以在Channel进行读写操作。这些Channel都会被注册在Selector多路复用器上。Selector通过一个线程不停的轮询这些Channel。找出已经准备就绪的Channel执行IO操作。说是非阻塞是因为NIO通过一个线程轮询,实现千万个客户端的请求。说是同步是因为客户端一直在等待服务端执行完成才返回结果。</p>
<h2 id="AIO"><a href="#AIO" class="headerlink" title="AIO"></a>AIO</h2><h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><h2 id="参考链接"><a href="#参考链接" class="headerlink" title="参考链接"></a>参考链接</h2>]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>IO</tag>
<tag>BIO</tag>
<tag>NIO</tag>
</tags>
</entry>
<entry>
<title>2019-plan-and-learn</title>
<url>/2019-plan-and-learn.html</url>
<content><![CDATA[<h1 id="2019计划"><a href="#2019计划" class="headerlink" title="2019计划"></a>2019计划</h1><p>激励<a href="http://www.ituring.com.cn/article/9174">左耳朵耗子</a></p>
<h2 id="目标"><a href="#目标" class="headerlink" title="目标"></a>目标</h2><ol>
<li>掌握tensorflow的使用</li>
<li>掌握Python以及常用库(numpy,matplotlib,pandas)的使用</li>
<li>掌握常用机器学习算法(SVM,AdaBoost,LightGBM)的使用以及理论</li>
<li>掌握深度学习常用算法应用和理论</li>
<li>掌握英语单词5000个,提高自己的英语发音</li>
<li>spark大数据分析技术</li>
<li>linux相关技术</li>
<li>Java相关技术</li>
</ol>
<h2 id="具体实现"><a href="#具体实现" class="headerlink" title="具体实现"></a>具体实现</h2><p>今天看了一篇虎扑上面<非CS专业转行机器学习/人工智能阶段性成功,分享一点个人经验吧>的帖子,感触比较多,摘一点帖子里面的内容,为之后的学习提供一定的建议。<br>楼主的情况是没有人工智能基础的,为了找到人工智能的工作准备了两年时间。</p>
<p>编程:掌握一门语言,要达到非常熟悉的阶段。<br>数据结构:掌握常用的数据结构,多做leetcode上面的编程题目,至少要刷一下easy和medium模式的题目,写的时候考虑test case<br>机器学习:</p>
<pre><code>1. andrew ng的machine learning,仔细看课件和习题
2. hands on machine learning with scikit-learn and tensorflow,并且在pythhon中实践还有课后习题
3. bishop的pattern recognition and machine learning
4. 李彦宏的机器学习和深度学习(自己加的)
5. 李航的统计机器学习(自己加的)
</code></pre><h2 id="Java相关技术"><a href="#Java相关技术" class="headerlink" title="Java相关技术"></a>Java相关技术</h2><p>参考了<a href="https://www.v2ex.com/t/546203">v2ex</a> 或者<a href="https://github.com/farmerjohngit/myblog">github</a><br>这里做了一个思维导图<br><img src="/2019-plan-and-learn/Java%E7%9B%B8%E5%85%B3%E7%9F%A5%E8%AF%86%E5%AD%A6%E4%B9%A0%E6%80%9D%E7%BB%B4%E5%AF%BC%E5%9B%BE.PNG" class="" title="Java相关知识学习思维导图"><br>下面进行思维导图的一些解释。<strong>同时对这些技能点进行查漏补缺,同时也会在之后的过程中添加更多的技能点</strong></p>
<h3 id="集合"><a href="#集合" class="headerlink" title="集合"></a>集合</h3><p>集合主要是java.util包下的非线程安全和线程安全集合</p>
<h4 id="非线程安全"><a href="#非线程安全" class="headerlink" title="非线程安全"></a>非线程安全</h4><ol>
<li>List: ArrayList与LinkedList的实现和区别</li>
<li>Map:<ul>
<li>HashMap:了解其数据结构,源码,hash冲突如何解决(链表和红黑树),扩容时机,扩容时避免rehash优化</li>
<li>LinkedHashMap:了解基本原理,哪两种有序,如何实现LRU</li>
<li>TreeMap:了解数据结构,了解其key对象为什么必须要实现Compare接口,如何用它实现一致性哈希</li>
</ul>
</li>
<li>Set:基本上是由map实现,简单看看就好</li>
</ol>
<p><strong>常见问题</strong></p>
<ul>
<li>hashmap 如何解决 hash 冲突,为什么 hashmap 中的链表需要转成红黑树?</li>
<li>hashmap 什么时候会触发扩容?</li>
<li>jdk1.8 之前并发操作 hashmap 时为什么会有死循环的问题?</li>
<li>hashmap 扩容时每个 entry 需要再计算一次 hash 吗?</li>
<li>hashmap 的数组长度为什么要保证是 2 的幂?</li>
<li>如何用 LinkedHashMap 实现 LRU ?</li>
<li>如何用 TreeMap 实现一致性 hash ?</li>
</ul>
<h4 id="线程安全的集合"><a href="#线程安全的集合" class="headerlink" title="线程安全的集合"></a>线程安全的集合</h4><ol>
<li>Collection.synchronized:了解其实现原理</li>
<li>CopyOnWriteArrayList:了解写时复制机制,了解其适用场景,思考为什么没有ConcurrentArrayList</li>
<li>ConcurrentHashMap:了解实现原理,扩容时做的优化,与HashTable的对比</li>
<li>BlockingQueue:了解 LinkedBlockingQueue、ArrayBlockingQueue、DelayQueue、SynchronousQueue</li>
</ol>
<p><strong>常见问题</strong></p>
<ul>
<li>ConcurrentHashMap 是如何在保证并发安全的同时提高性能?</li>
<li>ConcurrentHashMap 是如何让多线程同时参与扩容?</li>
<li>LinkedBlockingQueue、DelayQueue 是如何实现的?</li>
<li>CopyOnWriteArrayList 是如何保证线程安全的?</li>
</ul>
<h3 id="并发"><a href="#并发" class="headerlink" title="并发"></a>并发</h3><ol>
<li>synchronized:了解偏向锁、轻量级锁、重量级锁的概念以及升级机制、以及和 ReentrantLock 的区别</li>
<li>CAS:了解 AtomicInteger 实现原理、CAS 适用场景、如何实现乐观锁</li>
<li>AQS:了解 AQS 内部实现、及依靠 AQS 的同步类比如 ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier 等的实现</li>
<li>ThreadLocal:了解 ThreadLocal 使用场景和内部实现</li>
<li>ThreadPoolExecutor:了解线程池的工作原理以及几个重要参数的设置</li>
</ol>
<p><strong>常见问题</strong></p>
<ul>
<li>synchronized 与 ReentrantLock 的区别?</li>
<li>乐观锁和悲观锁的区别?</li>
<li>如何实现一个乐观锁?</li>
<li>AQS 是如何唤醒下一个线程的?</li>
<li>ReentrantLock 如何实现公平和非公平锁是如何实现?</li>
<li>CountDownLatch 和 CyclicBarrier 的区别?各自适用于什么场景?</li>
<li>适用 ThreadLocal 时要注意什么?比如说内存泄漏?</li>
<li>说一说往线程池里提交一个任务会发生什么?</li>
<li>线程池的几个参数如何设置?</li>
<li>线程池的非核心线程什么时候会被释放?</li>
<li>如何排查死锁?</li>
</ul>
<h3 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h3><p>了解 Java 中的软引用、弱引用、虚引用的适用场景以及释放机制</p>
<p><strong>常见问题</strong></p>
<ul>
<li>软引用什么时候会被释放</li>
<li>弱引用什么时候会被释放</li>
</ul>
<h3 id="类加载"><a href="#类加载" class="headerlink" title="类加载"></a>类加载</h3><p>了解双亲委派机制</p>
<p><strong>常见问题</strong></p>
<ul>
<li>双亲委派机制的作用?</li>
<li>Tomcat 的 classloader 结构</li>
<li>如何自己实现一个 classloader 打破双亲委派</li>
</ul>
<h3 id="IO"><a href="#IO" class="headerlink" title="IO"></a>IO</h3><p>了解 BIO 和 NIO 的区别、了解多路复用机制</p>
<p><strong>常见问题</strong></p>
<ul>
<li>同步阻塞、同步非阻塞、异步的区别?</li>
<li>select、poll、eopll 的区别?</li>
<li>java NIO 与 BIO 的区别?</li>
<li>refactor 线程模型是什么?</li>
</ul>
<h3 id="JVM"><a href="#JVM" class="headerlink" title="JVM"></a>JVM</h3><p>了解GC和内存区域</p>
<ol>
<li>垃圾回收基本原理、几种常见的垃圾回收器的特性、重点了解 CMS (或 G1 )以及一些重要的参数</li>
<li>能说清 jvm 的内存划分</li>
</ol>
<p><strong>常见问题</strong></p>
<ul>
<li>CMS GC 回收分为哪几个阶段?分别做了什么事情?</li>
<li>CMS 有哪些重要参数?</li>
<li>Concurrent Model Failure 和 ParNew promotion failed 什么情况下会发生?</li>
<li>CMS 的优缺点?</li>
<li>有做过哪些 GC 调优?</li>
<li>为什么要划分成年轻代和老年代?</li>
<li>年轻代为什么被划分成 eden、survivor 区域?</li>
<li>年轻代为什么采用的是复制算法?</li>
<li>老年代为什么采用的是标记清除、标记整理算法</li>
<li>什么情况下使用堆外内存?要注意些什么?</li>
<li>堆外内存如何被回收?</li>
<li>jvm 内存区域划分是怎样的?</li>
</ul>
<h3 id="Spring"><a href="#Spring" class="headerlink" title="Spring"></a>Spring</h3><p>bean 的生命周期、循环依赖问题、spring cloud (如项目中有用过)、AOP 的实现、spring 事务传播</p>
<p><strong>常见问题</strong></p>
<ul>
<li>java 动态代理和 cglib 动态代理的区别(经常结合 spring 一起问所以就放这里了)</li>
<li>spring 中 bean 的生命周期是怎样的?</li>
<li>属性注入和构造器注入哪种会有循环依赖的问题?</li>
</ul>
<h3 id="Mysql"><a href="#Mysql" class="headerlink" title="Mysql"></a>Mysql</h3><p>事务隔离级别、锁、索引的数据结构、聚簇索引和非聚簇索引、最左匹配原则、查询优化( explain 等命令)</p>
<p><strong>常见问题</strong></p>
<ul>
<li>Mysql(innondb 下同) 有哪几种事务隔离级别?</li>
<li>不同事务隔离级别分别会加哪些锁?</li>
<li>mysql 的行锁、表锁、间隙锁、意向锁分别是做什么的?</li>
<li>说说什么是最左匹配?</li>
<li>如何优化慢查询?</li>
<li>mysql 索引为什么用的是 b+ tree 而不是 b tree、红黑树</li>
<li>分库分表如何选择分表键</li>
<li>分库分表的情况下,查询时一般是如何做排序的?</li>
</ul>
<h3 id="算法"><a href="#算法" class="headerlink" title="算法"></a>算法</h3><p>准备一下leetcode上的算法(easy,medium)</p>
]]></content>
<categories>
<category>plan</category>
</categories>
<tags>
<tag>2019</tag>
</tags>
</entry>
<entry>
<title>Gaussian Discriminant Analysis and Logistic Regression</title>
<url>/Gaussian-Discriminant-Analysis-and-Logistic-Regression.html</url>
<content><![CDATA[<h1 id="高斯判别分析和逻辑回归算法的关系"><a href="#高斯判别分析和逻辑回归算法的关系" class="headerlink" title="高斯判别分析和逻辑回归算法的关系"></a>高斯判别分析和逻辑回归算法的关系</h1><p>最近一直在看Andrew Ng的cs229那门课的讲义,看到高斯判别分析模型和逻辑回归算法的关系那一部分,自己采用贝叶斯后验概率也证明了两者之间的关系,证明不难,本来打算记录一下的。在网上看到有个外国人写的更好,那我就把他写的直接翻译过来了。当然文章中也加入了在网上看到的其他资料和自己的一些思考。<br><a href="https://duphan.wordpress.com/2016/10/27/gaussian-discriminant-analysis-and-logistic-regression/">原文</a></p>
<h2 id="判别模型和生成模型"><a href="#判别模型和生成模型" class="headerlink" title="判别模型和生成模型"></a>判别模型和生成模型</h2><p>有很多方式可以对机器学习算法进行分类,比如:监督/非监督,回归/分类,等等。还有一种方式用判别模型(<strong>Discriminative model</strong>)和生成模型(<strong>Generative model</strong>)进行区分.本篇文章,我们将会讨论高斯判别分析(Gaussian Discriminant Analysis )模型和逻辑回归(Logistic Regression)之间的关系,这也体现了判别模型和生成模型之间的关系。<br>判别模型是求$p(y|x)$.在分类问题中,判别模型是直接寻找能够将不同类的数据分开的超平面或者称之为决策边界。有很多常用的机器学习算法属于判别模型,比如:逻辑回归,SVM,神经网络等等。另一方面,生成模型是求取$p(x|y)$和$p(y)$.这意味着,在分类问题中,生成模型给出了每个类的概率分布,给出了数据是如何生成的。也即生成(generative)这个词的意思.生成模型依赖贝叶斯公式计算后验概率$p(y|x)$.像朴素贝叶斯,高斯判别分析等都属于生成模型。<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/discriminative_vs_generative.png" class="" title="Discriminative vs Generative"><br>在实际使用中,判别模型要比生成模型用的更多,原因很多。比如:判别模型更加灵活,更加鲁棒,当对模型做出错误的假设时,模型表现的也不是很敏感。而生成模型需要我们定义数据的先验概率,即数据分布,在很多时候这是非常有困难和挑战的。然后,生成模型也有它的优势,比如:相比于判别模型,生成模型对于数据有更多的先验信息。这导致如果我们对于数据的假设是正确的,那么生成模型在少量数据下也能够表现的更好。</p>
<h2 id="GDA和逻辑回归"><a href="#GDA和逻辑回归" class="headerlink" title="GDA和逻辑回归"></a>GDA和逻辑回归</h2><p>接下来我将要证明,GDA(Gaussian Discriminant Analysis)如何最终推导出逻辑回归,从这个推导中也可以看出采用逻辑回归的道理。<br>对于二分类问题,GDA假设类别服从伯努利分布,数据服从多元高斯分布,如下:<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/%E5%85%88%E9%AA%8C%E5%88%86%E5%B8%83.PNG" class="" title="先验分布"><br>具体的数学公式如下:<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/%E5%88%86%E5%B8%83%E7%9A%84%E6%95%B0%E5%AD%A6%E5%85%AC%E5%BC%8F.PNG" class="" title="分布的数学公式"><br>而采用GDA就是求取后验概率,我们可以证明如下等式成立:</p>
<script type="math/tex; mode=display">p(y=1|x) = \frac{1}{1+exp(-\theta^Tx)}</script><p>在公式中可以看到,GDA关于类别1的后验概率就是逻辑回归的sigmoid函数,这里$\theta$就是关于参数$\phi,\mu_0,\mu_1,\sum$的函数<br>接下来进行证明:</p>
<script type="math/tex; mode=display">
p(y=1|x)
=\frac{p(x|y=1)\times{p(y=1)}}{p(x)}
=\frac{p(x|y=1)\times{p(y=1)}}{p(x|y=1)\times{p(y=1)} + p(x|y=1)\times{p(y=0)}}
=\frac{1}{1 + \frac{p(x|y=0)\times{p(y=0)}}{p(x|y=1)\times{p(y=1)}}}</script><p>我们接下来带入概率公式,计算$\frac{1}{1 + \frac{p(x|y=0)\times{p(y=0)}}{p(x|y=1)\times{p(y=1)}}}$,计算过程如下,主要是涉及一些log和exp的转换:<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/%E8%AF%81%E6%98%8E.PNG" class="" title="证明"><br>在公式的最后,我们令$x_0=1$,至此我们得到了$\theta^Tx$,即如下式:<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/%E8%AF%81%E6%98%8E%E7%BB%93%E6%9E%9C.PNG" class="" title="证明结果"><br>$\theta$向量的值如下:</p>
<script type="math/tex; mode=display">\theta=\left[
\begin{matrix}
log(\frac{1-\phi}{\phi}) - \frac{\mu_0^2 + \mu_1^2}{2\sum} \\
\frac{\mu_0 - \mu_1}{\sum}
\end{matrix}
\right]</script><h2 id="关系说明"><a href="#关系说明" class="headerlink" title="关系说明"></a>关系说明</h2><p>可以看出,从GDA可以推导出逻辑回归,但是反过来是不成立的。即如果$p(y|x)$是一个逻辑回归函数并不能推导出$p(x|y)$是一个多元的高斯分布。这说明GDA模型比逻辑回归模型具有更强的假设。事实上,如果$p(x|y)$是指数分布族(<strong>Exponential Family</strong>,比如高斯分布,泊松分布)中的一种,那么其后验概率就是逻辑回归。由此我们也得到一个为什么逻辑回归使用的更加广泛的原因,因为逻辑回归是一种非常通用,健壮的算法,适用于许多基本假设。另一方面,GDA以及一般的生成模型做出了更强的假设,因此对于非高斯或某些未定义分布数据来说并不理想。<br>特别的,当$p(x|y)$是高斯分布(类别之间具有相同的协方差)时,则GDA是渐进有效的(首先搞清楚什么叫有效性,有效性是指在所有的无偏估计方法里,某种方法的估计量的方差是最小的,则该种估计方法就是有效的,比如古典假定的OLS(最小二乘算法)估计。渐近有效是指该种估计方法在中小样本时可能不是最有效的,但随着样本数的增加,慢慢变得有效(没有其它无偏估计方法可以得到更小的方差),这就称为渐近有效,比如正态分布下线性模型的最大似然估计)。非正式地说,当训练集数目很大时,没有模型能显著地比GDA更好(即模型准确估计$p(y|x)$的程度).如果数据服从正态分布,则GDA要比逻辑回归表现的更好,更近一步,即使对于比较小的训练集,我们也总是认为GDA要表现的更好。<br>假设有一个一维训练集,包含一些正样本和负样本,如下图x轴的叉和圈,设叉为0,圈为1,用GDA对两类样本分别拟合高斯概率密度函数p(x|y=0)和p(x|y=1),如下图的两个钟形曲线。沿x轴遍历样本,在x轴上方画出相应的p(y=1|x)。如选x轴靠左的点,那么它属于1的概率几乎为0,p(y=1|x)=0,两条钟形曲线交点处,属于0或1的概率相同,p(y=1|x)=0.5,x轴靠右的点,输出1的概率几乎为1,p(y=1|x)=1。最终发现,得到的曲线和sigmoid函数曲线很相似。<br><img src="/Gaussian-Discriminant-Analysis-and-Logistic-Regression/%E9%AB%98%E6%96%AF%E5%88%86%E5%B8%83%E5%92%8C%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92.png" class="" title="高斯分布和逻辑回归"><br>也就是说,当使用GDA模型时,p(x|y)属于高斯分布,计算p(y|x)时,几乎能得到和logistic回归中使用的sigmiod函数一样的函数,但实际上还是存在本质区别。</p>
<p>在Andrew Ng的cs229采用两种方法可以推导出逻辑回归</p>
<ol>
<li>通过指数分布族来推导。</li>
<li>通过生成学习假设先验概率分布的方式进行推导。</li>
</ol>
<h2 id="如何选择两种模型"><a href="#如何选择两种模型" class="headerlink" title="如何选择两种模型"></a>如何选择两种模型</h2><p>由上面的分析可以知道,GDA比逻辑回归有更多的前置假设。当数据服从或大致服从正态分布时,GDA会具有更高的拟合度,因为GDA利用了更多的信息构建模型。但是当数据不服从正态分布时,那么逻辑回归更有效,因为它做出更少的假设,构建的模型更加强壮,更加具有鲁棒性。生成学习还有另外的一个好处,就是可以使用比判别学习模型使用更少的数据构建出强壮的模型。</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>通过推导,我们可以看到很多算法内在都是存在某种联系的,理解算法之间的关系对于我们理解算法以及选择什么算法都是有很多的帮助的。</p>
<h2 id="参考文章"><a href="#参考文章" class="headerlink" title="参考文章"></a>参考文章</h2><ol>
<li><a href="https://duphan.wordpress.com/2016/10/27/gaussian-discriminant-analysis-and-logistic-regression/">Gaussian Discriminant Analysis and Logistic Regression</a></li>
</ol>
]]></content>
<categories>
<category>机器学习</category>
</categories>
<tags>
<tag>Logistic Regression</tag>
<tag>Gaussian Discriminant Analysis</tag>
<tag>Discriminative model</tag>
<tag>Machine Learning</tag>
<tag>Generative model</tag>
<tag>Statistics</tag>
</tags>
</entry>
<entry>
<title>CS231n Convolutional Neural Networks for Visual Recognition</title>
<url>/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition.html</url>
<content><![CDATA[<blockquote>
<p>这是一篇翻译文章,但是也不全是,主要是读了文章之后,用自己的话将其复述出来<br>来源: <a href="https://cs231n.github.io/convolutional-networks/">CS231n Convolutional Neural Networks for Visual Recognition</a></p>
</blockquote>
<h1 id="CNN"><a href="#CNN" class="headerlink" title="CNN"></a>CNN</h1><p>卷积神经网络跟一般的神经网络是非常相似的.它们都由神经元组成,并且这些神经元上都有需要学习的权重和偏置项.每个神经元接收输入,执行点积,然后可选择的将输出进行非线性运算.整个网络表示了一个可微的得分函数:即从原始的图像像素到对应的类.两者都有一个损失函数(例如:SVM/Softmax),并且在一般神经网络中用到的技术也能应用到CNN中.<br>两者的区别在哪里呢?ConvNet网络假设输入是图像,所以我们能够将某些属性编码到网络结构中.这使得前馈函数更有效率并且能极大的减少网络中的参数.</p>
<h1 id="CNN结构概览"><a href="#CNN结构概览" class="headerlink" title="CNN结构概览"></a>CNN结构概览</h1><p><em>回顾</em>:在一般的神经网络中,神经网络接收一个输入(一个向量),然后经过一系列的隐藏层进行转换.每个隐藏层是由一组神经元组成,每个神经元都与前一层的所有神经元相连,属于同一层的神经元完全独立并且不共享任何连接.最后一层是输出层,在分类问题中,它代表了类的得分.<br>一般的神经网络不能很好的扩展到完整的图像.在CIFAR-10中,一副图像的大小是32$\times$32$\times$3,所以隐藏层中的一个神经元上将会有32$\times$32$\times$3=3072个权重.参数的数量仍然可以接受,但是全连接结构不能扩展到更大的图像上了.例如,如果图像的大小为200$\times$200$\times$3,那么一个神经元将需要200$\times$200$\times$3=120000个权重.可见,全连接神经网络需要的参数将会非常多.这会导致过拟合.<br>卷积神经网络充分利用了输入是图像的事实,并且用一个更加合理的方式约束了神经网络的结构.特别的,与一般的神经网络不同,卷积神经网络各层由三维排列的神经元组成:宽度,高度,深度(注意,这里的深度指的是激活的第三维,而不是整个神经网络的深度).例如,在CIFAR-10中的输入图像的维度是32$\times$32$\times$3.我们很快会看到,当前层的神经元仅仅连接前一层中的部分神经元.此外,对于CIFAR-10最终的输出层的维度是1$\times$1$\times$10,因为ConvNet最后会将整个图像转换为一个类得分向量,下面是可视化:<br><div class="group-picture"><div class="group-picture-container"><div class="group-picture-row"><div class="group-picture-column" style="width: 100%;"><img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/neural_net2.jpeg" alt="常规神经网络"></div></div><div class="group-picture-row"><div class="group-picture-column" style="width: 100%;"><img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/cnn.jpeg" alt="ConvNet"></div></div></div></div><br>上图表示一个三层的神经网络,下图表示卷积神经网络.</p>
<blockquote>
<p>A ConvNet is made up of Layers. Every Layer has a simple API: It transforms an input 3D volume to an output 3D volume with some differentiable function that may or may not have parameters.</p>
</blockquote>
<h1 id="ConvNet-层"><a href="#ConvNet-层" class="headerlink" title="ConvNet 层"></a>ConvNet 层</h1><p>就像我们上面描述的,一个简单的ConvNet是一系列层组成,ConvNet的每层通过一个可微的函数将输入转化到输出.ConvNet主要由卷积层,池化层和全连接层组成.下面是一个针对CIFAR-10的简单例子:</p>
<ul>
<li>INPUT(32$\times$32$\times$3):输入是原始图像的像素,一副图像的宽是32,长是32,并且有三个颜色通道R,G,B</li>
<li>卷积层:当前层的神经元只连接前一层的部分神经元.如果我们使用12个过滤器,则经过卷积层后的维度是[32$\times$32$\times$12]</li>
<li>RELU:对于输入采用ReLu激活函数,并不会改变输入维度,如果前一个维度是[32$\times$32$\times$12],则经过ReLu之后仍然是[32$\times$32$\times$12]</li>
<li>POOL:池化层会在空间维度上执行下采样,这会导致维度变化,[16$\times$16$\times$12],但是注意最后一维没有改变</li>
<li><p>FC(全连接层):这是一个全连接的结构,最后的输出是[1$\times$1$\times$10].<br>卷积神经网络就是将原始的像素图像经过一层一层的计算,最后得到最终的分类得分.注意有些层需要参数,而有些层不需要参数.CONV/FC层不仅仅对于输入进行激活,同时需要将权重和偏置作用在输入上.而RELU/POOL只是一个固定的函数,并没有参数.<br>总结:</p>
</li>
<li><p>卷积神经网络就是一系列层组成,将输入图像体积转换为输出体积(体积表面了维度)</p>
</li>
<li>卷积神经网路包括完全不同的层(e.g. CONV/FC/RELU/POOl)</li>
<li>每一层通过一个可微的函数将3D volume的输入转换为3D volume的输出</li>
<li>有些层需要参数,有些不需要(e.g. CONV/FC 需要, RELU/POOlL不需要)</li>
<li>有些层需要额外的超参数,有些不需要(e.g. CONV/FC/POOL需要,RELU不需要)</li>
</ul>
<img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/convnet.jpeg" class="" title="卷积神经网络结构图">
<p>因为无法很难画出3D的部分,所以这里每一层只展示了深度部分的一片.最后给出了得分最高的五个标签.这里展示的是一个很小的 VGG网络.<a href="http://cs231n.stanford.edu/">查看详细展示</a></p>
<h2 id="卷积层"><a href="#卷积层" class="headerlink" title="卷积层"></a>卷积层</h2><p>卷积层是卷积神经网络的核心部分,并且涉及了大量的计算.卷积层使用过滤器对于原图像进行卷积,过滤器每次只能针对整副图像的一部分进行计算,所以我们需要移动过滤器,遍历整个图像.这里过滤器的大小又叫做神经元的接收域.<br><em>Example 1</em>:假设输入为[32$\times$32$\times$3],过滤器大小为5$\times$5,那么卷积层中的某个神经元需要的参数为5$\times$5$\times$3 + 1=76.为什么需要这么多的参数呢?针对颜色通道R,当前过滤器对应的局部区域的点是5$\times$5=25个,有三个通道,所有总的参数为75,另外再加一个偏置项,所有一个神经元总共需要76个参数.注意这里的深度为3,这是因为输入的深度是3.</p>
<h2 id="Example-2-假设输入为-16-times-16-times-20-过滤器大小为3-times-3-那么卷积层中每个神经元都需要3-3-20-1-180个参数"><a href="#Example-2-假设输入为-16-times-16-times-20-过滤器大小为3-times-3-那么卷积层中每个神经元都需要3-3-20-1-180个参数" class="headerlink" title="Example 2:假设输入为[16$\times$16$\times$20],过滤器大小为3$\times$3,那么卷积层中每个神经元都需要3*3*20 + 1=180个参数."></a><em>Example 2</em>:假设输入为[16$\times$16$\times$20],过滤器大小为3$\times$3,那么卷积层中每个神经元都需要3*3*20 + 1=180个参数.</h2><div class="group-picture"><div class="group-picture-container"><div class="group-picture-row"><div class="group-picture-column" style="width: 100%;"><img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/depthcol.jpeg" class="" title="卷积层"></div></div><div class="group-picture-row"><div class="group-picture-column" style="width: 100%;"><img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/neuron_model.jpeg" class="" title="神经元"></div></div></div></div>
<p>第一张图展示了卷积层,可以看到一个神经元连接了原图像的局部区域,但是连接了所有的深度(这是是三个颜色通道).这里展示了五个神经元,这五个神经元都连接到了图像的同一个局域上.第二张图展示了神经元的计算.<br><strong>Spatial arrangement</strong> 前面我们仅仅讨论了卷积层中的每个神经元如何连接到前一层,我们还没有讨论在卷积层的输出中有多少个神经元.深度,步长,0值填充这些超参数控制着卷积层的输出的大小</p>
<ol>
<li>卷积层输出的深度等于过滤器的数量,而每个过滤器就是去寻找输入到卷积层数据的不同之处.如果输入是原始图像,那么过滤器就是去寻找不同方向的边,颜色等.</li>
<li>步长是过滤器移动的长度,一般常用的是1和2</li>
<li>0值填充就是在卷积层的输入的边界上填充0值,使用0值填充使得我们能够控制经过卷积层之后的空间大小.<br>下面的公式可以用来计算卷积层输出的空间大小<script type="math/tex; mode=display">(W - F + 2P)/S + 1</script>其中W(输入数据的大小),F(卷积层神经元的接收域大小),S(步长),P(零值填充的宽度).例如输入为7$\times$7,过滤器为3$\times$3,步长为1,不填充,则得出输出为5$\times$5.当步长变为2时,那么输出变为3$\times$3.</li>
</ol>
<hr>
<img src="/CS231n-Convolutional-Neural-Networks-for-Visual-Recognition/stride.jpeg" class="" title="空间排列">
<p>这里的输入只有一个x轴,输入为[1,2,-1,1,-3],过滤器大小为3,采用零值填充.所以W=5,F=3,P=1.图片最右侧是过滤器的权重,偏置为0.左图:步长为1,最后得到的输出的尺寸为5;右图:步长为2,最后得到的输出尺寸为3.所有黄色的神经元共享相同的参数</p>
<hr>
<p><em>Use of zero-padding</em>.当步长为1(即S=1),设置$P=(F-1)/2$,这样卷积层的输入跟输出将会有相同大小的空间.<br><em>Constraints on strides</em>.</p>
<h2 id="池化层"><a href="#池化层" class="headerlink" title="池化层"></a>池化层</h2><h2 id="Normalization-Layer"><a href="#Normalization-Layer" class="headerlink" title="Normalization Layer"></a>Normalization Layer</h2><h2 id="全连接层"><a href="#全连接层" class="headerlink" title="全连接层"></a>全连接层</h2><h2 id="全连接层转变为卷积层"><a href="#全连接层转变为卷积层" class="headerlink" title="全连接层转变为卷积层"></a>全连接层转变为卷积层</h2><h1 id="ConvNet-结构"><a href="#ConvNet-结构" class="headerlink" title="ConvNet 结构"></a>ConvNet 结构</h1><h2 id="层模式"><a href="#层模式" class="headerlink" title="层模式"></a>层模式</h2><h2 id="层大小模式"><a href="#层大小模式" class="headerlink" title="层大小模式"></a>层大小模式</h2><h2 id="常用CNN"><a href="#常用CNN" class="headerlink" title="常用CNN"></a>常用CNN</h2><h2 id="计算考虑"><a href="#计算考虑" class="headerlink" title="计算考虑"></a>计算考虑</h2><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1>]]></content>
<categories>
<category>deep learning</category>
</categories>
<tags>
<tag>CNN</tag>
</tags>
</entry>
<entry>
<title>Read/Write locks in java(java中的读写锁)</title>
<url>/Read-Write-locks-in-java-java%E4%B8%AD%E7%9A%84%E8%AF%BB%E5%86%99%E9%94%81.html</url>
<content><![CDATA[<p>本篇文章主要介绍读写锁的一些原理及实现。翻译原文<a href="http://tutorials.jenkov.com/java-concurrency/read-write-locks.html">地址</a></p>
<h1 id="Java中的读写锁"><a href="#Java中的读写锁" class="headerlink" title="Java中的读写锁"></a>Java中的读写锁</h1><p>假设一个java应用程序需要读以及写一些资源,但是写的频率要远远低于读。多个读线程读取资源不会有什么问题。但是如果一个线程想要写资源,那么同时就不能有其他线程读或写这个资源。为了能够允许多个读线程和一个写线程,我们需要读写锁。<br>虽然Java中提供了读写锁的实现,但是我们还是要知道读写锁背后的原理,这样才能在实际使用中处理具体的问题。</p>
<h2 id="Java中实现读写锁"><a href="#Java中实现读写锁" class="headerlink" title="Java中实现读写锁"></a>Java中实现读写锁</h2><p>总结一下对于获取资源的读权限和写权限可以如下:<br>读权限:当没有其他线程写当前资源或者没有其他线程请求当前资源的写权限,那么当前线程就能够获取资源的读权限<br>写权限:如果没有其他线程读或者写当前资源,当前线程就能获取资源的写权限<br>只要没有其他线程正在写资源或者没有其他线程请求写资源,那么当前线程就能够读取资源。如果读线程发生的很多,但是又没有提升写线程的优先级,那么就可能发生”饥饿”现象。</p>
<h2 id="可重入读写锁"><a href="#可重入读写锁" class="headerlink" title="可重入读写锁"></a>可重入读写锁</h2><h2 id="可重入读锁"><a href="#可重入读锁" class="headerlink" title="可重入读锁"></a>可重入读锁</h2><h2 id="可重入写锁"><a href="#可重入写锁" class="headerlink" title="可重入写锁"></a>可重入写锁</h2><h2 id="读锁升级到写锁"><a href="#读锁升级到写锁" class="headerlink" title="读锁升级到写锁"></a>读锁升级到写锁</h2><h2 id="写锁降级到读锁"><a href="#写锁降级到读锁" class="headerlink" title="写锁降级到读锁"></a>写锁降级到读锁</h2><h2 id="可重入读写锁的完整实现"><a href="#可重入读写锁的完整实现" class="headerlink" title="可重入读写锁的完整实现"></a>可重入读写锁的完整实现</h2><h2 id="在finally中调用unlock"><a href="#在finally中调用unlock" class="headerlink" title="在finally中调用unlock"></a>在finally中调用unlock</h2>]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>并发</tag>
</tags>
</entry>
<entry>
<title>Refactor-improving the Design of Existing Code</title>
<url>/Refactor-improving-the-Design-of-Existing-Code.html</url>
<content><![CDATA[<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>Refactor-improving the Design of Existing Code(重构-改善既有代码的设计),这是Martin Fowler的一本书.主要是针对Java语言.书已经出版多年了,买这本书也有一年多了,但是一直只看了第一章.最近因为工作不是很忙,就拾起来读了.这里主要是记录下读这本书的一些感想以及收获.<br>读这本书有个收获,重构不单单只是修改函数名称,重命名变量名称那么简单.比如进行类的分解,将合适的函数放置在合适的类中.而这些都需要我们在平常的编程活动中去实践.<br>什么时候进行重构呢?当遇到几个方面时,可以考虑进行重构.发现当为函数添加新功能时,并不能很好的添加进去,这时候我们可以进行重构;当写完功能时,我们也可以考虑重构.总之,重构并不是开发过程中必须要通过的一个过程,就像编译-链接等.你可以随时进行重构.</p>
<h2 id="重构"><a href="#重构" class="headerlink" title="重构"></a>重构</h2><p>我们在平时编程的时候,不可能一次性的就把问题或者程序写的完美无缺,除非我们已经非常熟知某个问题或者某个领域,即使非常熟悉,可能还有我们没有发现的提升之处.那么当我们的程序写的不完美的时候,我们怎么办呢?这时候就可以采用重构的方法来小步快跑的提升我们程序的易读性,优化程序的结构.而这本书给我们提供了如何进行重构的一系列方法.<br>整个开发流程:<br>开发—-> 测试 —-> 重构 —-> 测试</p>
<h3 id="测试"><a href="#测试" class="headerlink" title="测试"></a>测试</h3><p>在我们重构之前,一定要有合适的测试.在Java中,我们经常用的就是Junit和TestNG了.其中Junit主要是用来做单元测试,而TestNG主要是功能测试或者集成测试.如果我们是开发人员,可能使用Junit比较多,不过TestNG一般也会用到.在我们重构完成之后,一定要跑测试用例,跑通了所有的测试用例,这项重构才算是完成.</p>
<h3 id="代码的坏味道"><a href="#代码的坏味道" class="headerlink" title="代码的坏味道"></a>代码的坏味道</h3><ol>
<li>重复代码<br> 遇到相同的程序结构,应该想办法将它们合二为一,这样程序会变得更好</li>
<li>过长函数<br>函数的代码行数不宜过长,并且函数的命名应该体现出函数做了什么而不是表明怎么做</li>
<li>过大的类<br>当类过大时,我们可以采取Extract class 或者Extract Subclass来减小类的大小</li>
<li>过长的参数列<br>将函数的参数可以用对象来进行传递</li>
<li>发散式变化<br>如果某个类经常因为不同的原因在不同的方向上发生变化,那么这个类就产生了发散式变化.理想情况是针对外界的某一个变化产生的所有相应修改,都应该发生在单一类中.采用 <strong>Extract Class</strong>将因为特定原因造成的所有变化提炼到另一个类中</li>
<li>霰弹式修改<br>如果遇到某种变化,都必须在许多不同的类内做出许多小修改,那么就产生了霰弹式修改.遇到这种情况,应该使用<strong>Move Method</strong>和<strong>Move Field</strong>将所有的修改集中在一个类中.<br>发散式修改是指”一个类受多种变化的影响”,而霰弹式修改是指”一种变化引发多个类的相应修改”.理想情况是外界变化与需要修改的类趋于一一对应.</li>
<li>依恋情结<br>函数对某个类的兴趣高过对自己所处类的兴趣.这时候我们应该将此函数移动到此函数对应的类中.</li>
<li>数据泥团<br>两个类中相同的字段,许多函数签名中相同的参数.这些总是绑定在一起出现的数据应该拥有属于它们的对象</li>
<li>基本类型偏执<br>我们有时候可以用对象来替代基本类型,比如当表示由一个起始值和一个结束值组成的range类,一个币种的money类.我们可以创建对应的小对象.</li>
<li>switch惊悚现身<br>在面向对象程序中尽量少用switch语句,因为switch容易造成重复.而且修改switch条件时,需要找到所有的switch来进行修改</li>
<li>平行继承体系</li>
<li>冗余类<br>消除没有用的类</li>
<li>夸夸其谈未来性</li>
<li>令人迷惑的暂时字段</li>
<li>过度耦合的消息链</li>
<li>中间人<br>不要过度使用委托</li>
<li>狎昵关系</li>
<li>异曲同工的类</li>
<li>不完美的类库</li>
<li>过多的注释</li>
</ol>
<h3 id="重构方法"><a href="#重构方法" class="headerlink" title="重构方法"></a>重构方法</h3><blockquote>
<p>这里仅仅记录了部分的重构方法</p>
</blockquote>
<ol>
<li>提取方法<br>在重构中有一个很核心的动作就是将代码提取为一个单独的方法.这种方式其实也杜绝了重复代码的出现,能够在我们修改代码的时候只需要修改一处就可以.并且能够在多个地方使用</li>
<li>将注释提取为方法<br>理想的程序就是代码完全能够表达自己,当我们在程序中看到注释或者添加注释的时候,我们可以先思考下能否将注释下的代码提取为一个方法,并且方法的函数名称能够体现注释的内容,方法名称要体现程序做了什么而不是怎么做.</li>
<li>明确类的职责<br>类的职责不宜过于多,不宜过于复杂.如果看到一个类承担的职责很多,我们可以考虑是否可以将此类拆分,将不属于其应该承担的责任提取到一个新的类中.然后在源类中通过引用来使用新类中的字段或方法</li>
<li>用查询来代替变量<br>就是在我们使用变量时,我们应该采用计算变量的方法来替代此变量.其实对于这一做法,我是抱有怀疑态度的,因为这造成了函数运行多次.会造成性能下降,但是书中说性能优化属于优化阶段的工作,而且这样重构会为优化阶段带来很好的铺垫.这个我觉得还是主要看平时我们的工作中需要具体情况具体分析.不能尽信书,什么东西都需要我们自己的独立思考</li>
<li>以卫语句取代嵌套条件表达式<br>条件表达式的两种形式:1.所有分支都属于正常分支;2.只有一种是正常行为,其他都是不正常行为.针对情况1,建议使用if…else结构;针对情况2,使用if进行判断,然后直接返回结果.这样子处理能够提高程序清晰度</li>
<li>封装集合<br>当我们在类中有个字段是集合时,我们的返回函数不应该直接返回集合自身,而是应该返回集合的只读副本.另外,不应该为这整个集合提供一个设值函数,应该提供为集合添加,删除元素的函数.</li>
<li>分解条件表达式<br>将复杂的条件表达式分解为较简单的表达式,可是试着尝试将if段落以及else then等提炼为单独的函数</li>
<li>合并条件表达式<br>如果发现检查条件各不相同,但是最终的行为一致.应该使用逻辑与和逻辑或合并为一个条件表示式</li>
<li>引入参数对象<br>可以使用对象来替换函数中的众多参数.比如,遇到数据范围的,参数是一个开始时间,结束时间,那么可以新建一个日期范围类,其中的开始时间和结束时间不能修改.用此类来替换开始时间和结束时间.这样做的好处是可以缩短参数列表.</li>
<li>移除设置函数<br>如果类中的某个字段在对象创建之后不能修改,那么就不提供设置函数,并且将此字段设为<strong>final</strong></li>
</ol>
]]></content>
<categories>
<category>读书笔记</category>
</categories>
<tags>
<tag>技术</tag>
</tags>
</entry>
<entry>
<title>Top 5 things every apache kafka developer should know</title>
<url>/Top-5-things-every-apache-kafka-developer-should-know.html</url>
<content><![CDATA[<h1 id="top-5-things-every-apache-kafka-developer-should-know"><a href="#top-5-things-every-apache-kafka-developer-should-know" class="headerlink" title="top 5 things every apache kafka developer should know"></a>top 5 things every apache kafka developer should know</h1><blockquote>
<p>翻译自:<a href="https://www.confluent.io/blog/top-5-things-every-apache-kafka-developer-should-know">https://www.confluent.io/blog/top-5-things-every-apache-kafka-developer-should-know</a><br>介绍下面5个内容</p>
<ul>
<li>理解消息传递和持久化保证</li>
<li>学习producer api中的新的粘性分区(learn about the new sticky partitioner in the producer API)</li>
<li>利用cooperative rebalancing(协同重平衡)来避免消费者组执行rebalance时的stop the world</li>
<li>掌握常用命令行工具<ul>
<li>kafka console producer</li>
<li>kafka consule consumer</li>
<li>dump log</li>
<li>delete records</li>
</ul>
</li>
<li>使用record headers的能力<ul>
<li>为kafka记录增加headers</li>
<li>检索headers</li>
</ul>
</li>
</ul>
</blockquote>
<h2 id="Tip1:理解消息传递和持久化保证"><a href="#Tip1:理解消息传递和持久化保证" class="headerlink" title="Tip1:理解消息传递和持久化保证"></a>Tip1:理解消息传递和持久化保证</h2><p>针对数据持久化,KafkaProducer提供了不同的配置。acks 配置指定了当生产者接受到多少消息确认后,才认为记录已经成功发送到broker上。kafka提供了以下三种选择:</p>
<ul>
<li>none: 生产者不等待broker的确认,发送消息后就认为已经成功发送到broker上。</li>
<li>one: 生产者等待leader broker的确认(leader broker有一个),一定收到确认,就认为消息发送成功</li>
<li>all: 生产者需要等待所有的ISR(in-sync replicas) broker都确认消息后,才认为消息发送成功。<br>如果需要更到的发送吞吐量,可以损失一定的数据,那么可以使用none或one。而如果应用不能容忍数据丢失,那么可以设置all,但是这样吞吐量会降低。<br>这里需要说明下acks=all的情况。下面的场景描述中,producer都是使用acks=all来发送消息,并且topic副本数是3,一个leader,两个follower。<br>情况1:如果这些副本中的记录偏移量是一致的,那么他们被认为是in-sync的。如下面所示,producer采用acks=all的情况:<img src="/Top-5-things-every-apache-kafka-developer-should-know/producer-ack-1024x691.png" class="" title="producer ack">
情况2:假设由于某些情况(网络分区,负载过高等),导致两个follower没有跟上leader,那么follower就不是in sync的。此时生产者发送消息,那么实际的确认只会有一个。acks=all并不是指定有多少副本必须在in-sync。leader broker始终跟自己是同步的。<img src="/Top-5-things-every-apache-kafka-developer-should-know/in-sync-replicas-1024x710.png" class="" title="in-sync acks">
</li>
</ul>
<p>一般来说,设置acks=all, 我们的要求通常都是所有副本都应该确认,或者至少大量的in sync副本应该确认。如果不是这样,那么应该抛出异常知道所有副本都在in sync中。<br>为了满足这个要求,kafka提供了这样一个配置:min.insync.replicas. 这个配置强制指定多少个副本写成功才被认为真正写成功。需要注意的是,min.insync.replicas配置在是broker或者topic级别,而不是在producer上。min.insync.replicas默认值是1。所以为了避免上面说的情况,在三个副本的情况下,需要将min.insync.replicas设置为2。<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/not-enough-replicas-1.png" class="" title="not-enough-replicas"><br>上图中展示了in sync中的副本不满足min.insync.replicas要求的情况,此时producer发送的消息,leader broker不会将记录添加到log中,而是会抛出NotEnoughReplicasException 或者 NotEnoughReplicasAfterAppendException。副本与leader不一致被认为是一种可以重试的错误,所以producer会重试直到成功或者达到超时时间(默认值两分钟)<a href="https://kafka.apache.org/documentation/#delivery.timeout.ms">delivery.timeout.ms</a>。<br>如果需要非常高的数据持久化保证,那么应该同时设置min.insync.replicas和acks=all.</p>
<h2 id="Tip-2-Learn-about-the-new-sticky-partitioner-in-the-producer-API"><a href="#Tip-2-Learn-about-the-new-sticky-partitioner-in-the-producer-API" class="headerlink" title="Tip 2: Learn about the new sticky partitioner in the producer API"></a>Tip 2: Learn about the new sticky partitioner in the producer API</h2><p>kafka需要partition来提升吞吐量并且将消息均衡到不同的broker上。kafka的消息记录是key/value格式,其中key可以为null。kafka producer在发送消息时,不会立即发送,而是将消息放置到对应的partition batch中(类似缓存),待缓存满了,在一次发送。batch是一种增加网络利用的有效方式。在将消息发送到partition中,通常有三种方式来决定发送到哪个partition上。</p>
<ul>
<li>方式1:在发送消息时,直接指定消息对应的partition。这种情况,producer直接使用这个partition</li>
<li>方式2:如果没有提供partition,消息包含key,那么producer会使用key的hash值来决定partition。</li>
<li>方式3:如果既没有key也没有提供partition信息,那么kafka会使用round-robin的方式将消息发送到不同的partition中。producer会将第一个消息发送到partition 0,第二个消息发送到partition 1,以此类推。</li>
</ul>
<p>下图展示了方式3:<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/producer-partition-updated.png" class="" title="producer-partition-updated"><br>round robin方法对于将消息均衡到不同的partition上工作的很好。但是存在一个缺点,由于producer是依次将消息发送到不同的partition batch中,那么有可能会出现每个partition中的batch都填充不满。比如下面展示的,topic有三个partition。假设应用产生了9条消息,并且消息没有key,所有的消息几乎是同时发送,如下图:<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/sparse_batches_sent-updated.png" class="" title="sparse_batches_sent-updated"><br>9条记录分散到是三个batch中,每个batch有三条。但是如果我们将9条消息放到一个batch中会更好。更少的batch使用更少的网络带宽并且对于broker的负载更小。<br>kafka 2.4.0新增了sticky partitioner approach. 这种方法能够将消息发送到一个partition的batch中直到此batch满了。然后,发送这个batch,sticky partitioner使用下一个partition的batch。如下图展示了使用sticky partitioner的例子:<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/batch-partition-updated.png" class="" title="batch-partition-updated"><br>通过使用sticky partitioner方法,我们减少了请求次数,同时也减少了请求队列上的负载,也减少了系统延迟。需要注意的时,sticky partitioner仍然是将消息均衡放置到不同的partition batch中。可以将这种认为是per-batch round robin 或者 eventually even approach。<br>如果想要更多了解sticky 模式,可以参考<a href="https://www.confluent.io/blog/apache-kafka-producer-improvements-sticky-partitioner/"> Apache Kafka Producer Improvements with the Sticky Partitioner</a></p>
<h2 id="Tip-3-Avoid-“stop-the-world”-consumer-group-rebalances-by-using-cooperative-rebalancing"><a href="#Tip-3-Avoid-“stop-the-world”-consumer-group-rebalances-by-using-cooperative-rebalancing" class="headerlink" title="Tip 3: Avoid “stop-the world” consumer group rebalances by using cooperative rebalancing"></a>Tip 3: Avoid “stop-the world” consumer group rebalances by using cooperative rebalancing</h2><p>kafka是一个分布式系统,而分布式系统中一个重要的事情就是如何处理失败。kafka处理失败的方式之一是使用consumer group,consumer group管理多个consumer。如果其中一个consumer停止,kafka会进行rebalance从而确保另一个consumer能够接管这个工作。<br>从2.4版本开始,kafka引入了一个新的rebalance协议,cooperative rebalancing。在深入了解cooperative rebalancing之前,先来了解一下consumer group的基础。<br>假设一个分布式应用(比如一个微服务的多个副本)有个多个consumer,订阅同一个topic。这些consumer组成了一个consumer group,具有同样的.group.id。在consumer group中的每个consumer负责从一个或多个partition中消费消息。这些partition的分配是由consumer group中的leader进行的。如下图所示:<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/six-partitions.png" class="" title="six-partitions"><br>从图中可以看到,总共有6个partition,在理想的情况下,每个consumer负责消费两个partition。但是如果其中的某个应用失败了或者不能连接网络。那么对应的partition中的消息是不是就不能被消费直到应用恢复?幸运的是,由于consumer rebalancing协议的存在,不会发生这种情况。<br>下图展示了consumer group protocal过程:<br><img src="/Top-5-things-every-apache-kafka-developer-should-know/minus-consumer-2.png" class="" title="minus-consumer-2"><br>如上图,consumer2由于某些原因失败了。group coordinator将它从组中移除然后触发rebalance。rebalance尝试将工作负载在组内所有工作的consumer上进行均衡分布。在这个例子中,consumer2离开了组,rebalance会将consumer2拥有的partition分配给组内其他的consumer。所以对于一个consumer group,如果其中consumer失败了, 那么对于这些partition的处理不会产生影响。<br>但是,默认的rebalance协议有个缺点。在rebalance过程中,每个consumer都会放弃之前获得的partition(这会造成consumer停止消费),知道topic下所有的partition都被重新分配。这种情况被称为stop the world rebalance。为了解决这个问题,依靠ConsumerPartitionAssignor实例,consumer简单的重新获取之前分配的partition,所以在这些partition上仍然能够继续消费。<br>上述描述的实现被称为<a href="https://www.confluent.io/blog/cooperative-rebalancing-in-kafka-streams-consumer-ksqldb/">eager rebalancing</a>, 因为它优先考虑的是针对一个consumer group中,不会有两个consumer同时对于一个partition拥有主权。<br>虽然对于同一个topic下的某个partition不能具有相同的consumer非常重要,但是有一种更好的方法,既能够提供安全性同时还不会暂停处理,既<a href="https://www.confluent.io/blog/incremental-cooperative-rebalancing-in-kafka/">incremental cooperative rebalancing</a>。这个方法在kafka2.3版本的kafka connect中被首次引入,现在已经在consumer group 协议中实现了。利用cooperative 方法,消费者不会在rebalance开始时主动放弃partition的所有权。在cooperative方法中,consumer group中的所有成员会将当前的分配进行编码然后将信息发送到group leader中。group leader决定那个partition需要修改对应的consumer。而不是一开始就完全从新分配。之后第二次rebalance发起,但是这一次,仅仅涉及到那些需要改变所有权的分区。这有可能是撤销不在用的partition或者新增的partition。对于那些没有改变所有权的分区,这些分区中的数据会继续进行处理。<br>这种处理办法解决了stop-the-world,而仅仅是暂停了哪些需要修改所有权分区的消费。这带来了更少的rebalance代驾以及降低了完成rebalance的时间。即使rebalance时间很长也没有关系,因为现在数据仍然被处理。使用CooperativeStickyAssignor能够开启这个功能。<br>如果要开启这个功能,则需要将partition.assignment.strategy设置为使用CooperativeStickyAssignor。这种设置完全是在客户端测,所以仅仅更新客户端版本即可。而在Kafka Stream中,这个功能是默认开启的。</p>
<h2 id="Tip-4:掌握命令行工具"><a href="#Tip-4:掌握命令行工具" class="headerlink" title="Tip 4:掌握命令行工具"></a>Tip 4:掌握命令行工具</h2><p>下面介绍了4种在平时工作中使用最多的工具。</p>
<h3 id="kafka-console-producer"><a href="#kafka-console-producer" class="headerlink" title="kafka console producer"></a>kafka console producer</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 开启发送者程序, 发送的消息只有value,没有key</span></span><br><span class="line">kafka-console-producer --topic <topic> --broker-list <broker-host:port></span><br><span class="line"><span class="comment"># 发送消息,发送的消息包含key 和 value</span></span><br><span class="line">kafka-console-producer --topic <topic> --broker-list <broker-host:port> --property parse.key=<span class="literal">true</span> --property key.separator=<span class="string">":"</span></span><br></pre></td></tr></table></figure>
<h3 id="kafka-console-consumer"><a href="#kafka-console-consumer" class="headerlink" title="kafka console consumer"></a>kafka console consumer</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 消费指定topic中的消息</span></span><br><span class="line">kafka-console-consumer --topic <topic> --bootstrap-server <broker-host:port></span><br><span class="line"><span class="comment"># 指定从开始的地方消费</span></span><br><span class="line">kafka-console-consumer --topic <topic> --bootstrap-server <broker-host:port> --from-beginning</span><br><span class="line"><span class="comment"># 默认情况下consumer只会打印消息的value,如果想要打印消息的key,则输入下面命令</span></span><br><span class="line">kafka-console-consumer --topic <topic> --bootstrap-server <broker-host:port> --property print.key=<span class="literal">true</span> --property key.separator=<span class="string">":"</span></span><br></pre></td></tr></table></figure>
<h3 id="Dump-log"><a href="#Dump-log" class="headerlink" title="Dump log"></a>Dump log</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 指定打印topic为example-0中的日志,参数--print-data-log表示输出日志</span></span><br><span class="line"><span class="comment"># 不过一般在生产环境中不会使用这个命令</span></span><br><span class="line">kafka-dump-log --print-data-log --files ./var/lib/kafka/data/example-0/00000000000000000000.<span class="built_in">log</span></span><br></pre></td></tr></table></figure>
<h3 id="delete-records"><a href="#delete-records" class="headerlink" title="delete records"></a>delete records</h3><p>kafka提供了配置来控制数据保留,包括时间和数据大小</p>
<ul>
<li>数据保留的时间由 log.retention.hours 控制,默认值是168hour,也就是一周</li>
<li>configuration.log.retention.bytes 控制segment文件最大是多少。默认值是-1, 也就是不限制大小</li>
</ul>
<p>如果想要删除数据,可以使用下述命令:<br><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">kafka-delete-records --bootstrap-server <broker-host:port> \</span><br><span class="line"> --offset-json-file offsets.json</span><br></pre></td></tr></table></figure><br>offsets.json 文件内容如下:<br><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">{</span></span><br><span class="line"> <span class="attr">"partitions"</span><span class="punctuation">:</span> <span class="punctuation">[</span></span><br><span class="line"> <span class="punctuation">{</span><span class="attr">"topic"</span><span class="punctuation">:</span> <span class="string">"example"</span><span class="punctuation">,</span> <span class="attr">"partition"</span><span class="punctuation">:</span> <span class="number">0</span><span class="punctuation">,</span> <span class="attr">"offset"</span><span class="punctuation">:</span> <span class="number">-1</span><span class="punctuation">}</span></span><br><span class="line"> <span class="punctuation">]</span><span class="punctuation">,</span></span><br><span class="line"> <span class="attr">"version"</span><span class="punctuation">:</span><span class="number">1</span></span><br><span class="line"> <span class="punctuation">}</span></span><br></pre></td></tr></table></figure><br>参数介绍如下:</p>
<ul>
<li>topic:指定要删除数据对应的topic</li>
<li>partition:指定需要删除数据对应的partition</li>
<li>offset:指定从哪个offset开始删除,注:是删除offset之前的数据。-1表示删除当前HW之前的数据,HW(high watermark)表示能够开始消费的位置</li>
</ul>
<h2 id="Tip5:使用record-headers的能力"><a href="#Tip5:使用record-headers的能力" class="headerlink" title="Tip5:使用record headers的能力"></a>Tip5:使用record headers的能力</h2><p>Record headers可以给kafka消息添加一些元数据,并且不是给消息的key value添加额外的信息。比如如果你想要在消息中嵌入一些信息,如表示消息来源系统,也是是想要增加一些审计功能。<br>为什么不能将这些额外的数据添加到key中。因为给key中添加数据会带来两个潜在的问题</p>
<ol>
<li>首先,如果你使用的是压缩主题,那么给key添加信息会使得消息不正确。这样压缩不会像之前起作用</li>
<li>其次,给key添加额外的信息有可能会影响数据的partition分布</li>
</ol>
<h1 id="回顾"><a href="#回顾" class="headerlink" title="回顾"></a>回顾</h1><p>我们了解了kafka的五个tips,我们理解了下面的知识点</p>
<ol>
<li>消息持久性以及和消息传递之间的关系</li>
<li>producer API中的sticky partitioner</li>
<li>command line tools</li>
<li>record headers的能力</li>
</ol>
]]></content>
<categories>
<category>kafka</category>
</categories>
<tags>
<tag>kafka</tag>
</tags>
</entry>
<entry>
<title>Understanding LSTM Networks</title>
<url>/Understanding-LSTM-Networks.html</url>
<content><![CDATA[<p>这是一篇译文,<a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/">原文地址</a>.如果英文可以,建议直接看英文.</p>
<h1 id="循环神经网络-Recurrent-Neural-Networks"><a href="#循环神经网络-Recurrent-Neural-Networks" class="headerlink" title="循环神经网络(Recurrent Neural Networks)"></a>循环神经网络(Recurrent Neural Networks)</h1><p>在我们思考时,我们不会从头开始,肯定会在思考时加入之前的知识.就如同当你在阅读当前的博客时,你读的每个单词都是基于前面的单词.你不会扔掉所有的东西,然后在从头开始.你的想法有持久性.<br>传统的神经网络不能使用之前信息.假设你使用传统神经网络来将一部电影中的正在发生的事件分类,怎么使用当前事件之前的信息是很困难的.<br>循环神经网络解决了这个问题,在其中有循环,允许信息能够保持.<br><img src="/Understanding-LSTM-Networks/RNN-rolled.png" class="" title="Recurrent Neural Networks have loops."><br>这个图看起来有点奇怪,我们可以将其展开,如下图所示<br><img src="/Understanding-LSTM-Networks/RNN-unrolled.png" class="" title="An unrolled recurrent neural network"><br>从图中可以看出,RNN使用了前一时刻的状态来预测当前的状态<br>RNN在很多领域都取得了成功,比如:语音识别,自然语言处理,翻译,图像字幕等等.这里Andrej Karpathy的<a href="http://karpathy.github.io/2015/05/21/rnn-effectiveness/">blog</a>,里面列举了RNN一些应用.<br>本文主要介绍LSTM模型,LSTM模型是RNN的一种变种,在很多领域的表现都要比一般的RNN模型更好</p>
<h1 id="长期依赖存在的问题-The-Problem-of-Long-Term-Dependencies"><a href="#长期依赖存在的问题-The-Problem-of-Long-Term-Dependencies" class="headerlink" title="长期依赖存在的问题(The Problem of Long-Term Dependencies)"></a>长期依赖存在的问题(The Problem of Long-Term Dependencies)</h1><p>加入我们要基于之前的单词预测下一个单词,如”the clouds are in the <em>sky</em>“,我们要预测最后一个单词sky.在这个例子中,我们不需要更多的上下文,很明显最后一个单词是sky.RNN针对这种情况有很好的表现.<br><img src="/Understanding-LSTM-Networks/RNN-shorttermdepdencies.png" class=""><br>但是在有些情况下,我们需要更多的上下文.例如,我们要预测”I grew up in France… I speak fluent <em>French</em>.”中的最后一个单词French.从最近的信息,比如speak fluent等,可以推测最后一个单词是一种语言.但是如果要知道具体的语言,我们需要更多的上下文.预测点和其对应的相关信息之间的差距很大是由很大可能的.<br>但是,不幸的是随着差距增加,RNN不可能学会如何连接这种信息<br><img src="/Understanding-LSTM-Networks/RNN-longtermdependencies.png" class=""><br>从图中可以看出$h_{t+1}$不能很好地利用$X_0$和$X_1$的信息.理论上,RNN能够学习到长期依赖,但是在实践中,要想在RNN中使用这些长期依赖很困难.幸运的是LSTM模型解决了这个问题.</p>
<h1 id="LSTM网络-LSTM-Networks"><a href="#LSTM网络-LSTM-Networks" class="headerlink" title="LSTM网络(LSTM Networks)"></a>LSTM网络(LSTM Networks)</h1><p>LSTM全称Long Short Term Memory,长短期记忆网络.LSTM的设计就是为了避免长依赖问题,记忆长周期的信息是LSTM的默认行为.<br>所有的递归神经网络都具有重复模块的链式结构.在标准的RNN中,重复模块是一个非常简单的结构,例如单层tanh<br><img src="/Understanding-LSTM-Networks/LSTM3-SimpleRNN.png" class="" title="The repeating module in a standard RNN contains a single layer."><br>LSTM也有这种链式结构,但是重复的模块有一个不同的结构,在其中一个模块中,包含一个四层的神经网络,并且这四层以一种特别方式进行交互.<br><img src="/Understanding-LSTM-Networks/LSTM3-chain.png" class="" title="The repeating module in an LSTM contains four interacting layers."><br>图中的示例如下<br><img src="/Understanding-LSTM-Networks/LSTM2-notation.png" class=""><br>接下来我会详细介绍其中涉及的内容.</p>
<h1 id="LSTMs中的核心思想-The-Core-Idea-Behind-LSTMs"><a href="#LSTMs中的核心思想-The-Core-Idea-Behind-LSTMs" class="headerlink" title="LSTMs中的核心思想(The Core Idea Behind LSTMs)"></a>LSTMs中的核心思想(The Core Idea Behind LSTMs)</h1><p>LSTMs中的核心是单元状态,在图中就是最上面的那条水平线.单元状态就像是一条输送带.单元状态沿着整个链流动,对其只有一些线性作用,信息的流动很容易.<br><img src="/Understanding-LSTM-Networks/LSTM3-C-line.png" class=""><br>LSTM能够移除或者添加信息到单元状态,并且由成为门的结构来控制.门可以选择性的让信息通过或不通过,它是由sigmoid神经网络层和点积操作组成.<br><img src="/Understanding-LSTM-Networks/LSTM3-gate.png" class=""><br>sigmoid层的输出范围0-1,描述了可以通过多少的信息.输出为0意味着什么都不通过,输出为1意味着都能通过.从LSTM的结构中,我们可以看到LSTM的一个重复模块包括三个这样的门.</p>
<h1 id="一步一步认识LSTM"><a href="#一步一步认识LSTM" class="headerlink" title="一步一步认识LSTM"></a>一步一步认识LSTM</h1><p>LSTM的第一步就是决定从单元状态中丢弃哪些信息.这个决定是由一个称为”遗忘门(forget gate layer)”单层sigmoid层组成.它接收$h<em>{t-1}$和$x_t$作为输入,输出0到1之间的数字.对于前一个单元状态$C</em>{t-1}$,当遗忘门的输出为1时,表示完全保留$C<em>{t-1}$;当遗忘门的输出为0时,表示完全舍弃$C</em>{t-1}$.<br>我们在回到之前根据之前的内容来预测下一个单词这个问题.在这样的问题中,单元状态可能包括当前对象的性别,所以可以使用正确的代词.但是当遇到一个新的对象时,我们就想要忘记老对象的性别.<br><img src="/Understanding-LSTM-Networks/LSTM3-focus-f.png" class=""></p>
<blockquote>
<p>图中的$W<em>f \cdot [h</em>{t-1},x<em>t]$,大家看起来有些不清楚.个人认为比较合适的写法如下<br>$\sigma(W_fx_t + U_r h</em>{t-1} + b_f)$.大家可以参考下<a href="http://blog.echen.me/2017/05/30/exploring-lstms/">英文博客</a>,<a href="https://www.jiqizhixin.com/articles/2017-07-24-2">中文博客</a></p>
</blockquote>
<p>接下来这一步就是决定新信息中的哪些部分需要保存在单元状态中.这包括两个部分,第一部分是sigmoid层(input gate layer),决定更新的值.第二部分是一个tanh层,用来创建一个新的候选值.在下一步中,我们将会把这两个结合起来用来给单元状态做更新.在语言模型的例子中,这一步就代表我们将新对象的性别加入单元状态中,用来代替我们需要忘记的老对象性别.<br><img src="/Understanding-LSTM-Networks/LSTM3-focus-i.png" class=""><br>现在要把旧的单元状态$C_{t-1}$更新为$C_t$.上一步已经决定了更新的内容.我们把旧的单元状态乘以$f_t$,表示我们需要忘记的内容.然后将结果加上$i_t*\tilde{C_t}$,这表示将候选值进行缩放或者延伸.在语言模型的例子中,这表示我们从旧对象的性别中去除信息,然后加上新的信息.<br><img src="/Understanding-LSTM-Networks/LSTM3-focus-C.png" class=""><br>最终,我们需要决定输出的内容.输出内容基于单元状态,但是需要一些处理.首先,我们使用一个sigmoid层来决定单元状态的哪部分需要输出.然后,我们将单元状态经过tanh(将值限定在-1到1之间),在乘以sigmoid gate的输出.所以我们仅仅输出了我们决定的.<br>对于语言模型例子,因为网络之前看到了主语,所以接下来它可能想要输出的信息是关于动词的.例如,网络会根据主语的单数或复数来决定接下来动词的形式.<br><img src="/Understanding-LSTM-Networks/LSTM3-focus-o.png" class=""></p>
<h1 id="长短期记忆模型的其他形式-Variants-on-Long-Short-Term-Memory"><a href="#长短期记忆模型的其他形式-Variants-on-Long-Short-Term-Memory" class="headerlink" title="长短期记忆模型的其他形式(Variants on Long Short Term Memory)"></a>长短期记忆模型的其他形式(Variants on Long Short Term Memory)</h1><p>上面提到的是LSTM的一般形式.但是还有好多LSTM的一些变形,虽然变化不大.其中一个流行的LSTM变形是由<a href="ftp://ftp.idsia.ch/pub/juergen/TimeCount-IJCNN2000.pdf">Gers & Schmidhuber (2000)</a>提出.此模型添加了”peephole connections”,这意味着让门看到了单元状态.<br><img src="/Understanding-LSTM-Networks/LSTM3-var-peepholes.png" class=""><br>从图中可以看到模型对于所有的gate都添加了peepholes,但是有很多文章只添加了一些peepholes,而另一些则没有.</p>
<p>LSTM另一种变形是将input gate和forget gate进行耦合.这种网络不会单独的决定哪些内容需要忘记,哪些新的信息需要添加,而是一起做这些决定.当我们将要输入内容时,我们仅仅忘记;当我们忘记旧的内容时,我们仅仅输入一些新的值.模型的结构如下<br><img src="/Understanding-LSTM-Networks/LSTM3-var-tied.png" class=""></p>
<p>一种引入注意的LSTM变形是GRU(Gated Recurrent Unit),它将forget gate和input gate结合为一个单独的”update gate”.它同样融合了单元状态和隐藏状态以及一些其他的改变.GRU模型要比标准的LSTM模型简单,并且越来越流行.<br><img src="/Understanding-LSTM-Networks/LSTM3-var-GRU.png" class=""><br>这里仅仅列举了一些LSTM变形,还有很多其他的LSTM变形.比如Depth Gated RNNs by <a href="http://arxiv.org/pdf/1508.03790v2.pdf">Yao, et al. (2015)</a>.同时在解决长期依赖问题上,也有跟LSTM完全不同的方法,比如Clockwork RNNs by <a href="http://arxiv.org/pdf/1402.3511v1.pdf">Koutnik, et al. (2014)</a>.<br><a href="http://arxiv.org/pdf/1503.04069.pdf">Greff, et al. (2015)</a>对这些变形做了一个对比.<a href="http://jmlr.org/proceedings/papers/v37/jozefowicz15.pdf">Jozefowicz, et al. (2015)</a>测试很多的RNN结构,发现在一些特定的任务上比LSTM模型表现的更好的模型.</p>
<h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p><a href="https://colah.github.io/posts/2015-08-Understanding-LSTMs/">colah博客</a><br><a href="http://blog.echen.me/2017/05/30/exploring-lstms/">echen博客</a></p>
<blockquote>
<p>echen博客中公式介绍的较为详细</p>
</blockquote>
]]></content>
<categories>
<category>deep learning</category>
</categories>
<tags>
<tag>RNN</tag>
</tags>
</entry>
<entry>
<title>Java Memory Model</title>
<url>/Java-Memory-Model.html</url>
<content><![CDATA[<p>Java内存模型规定了Java虚拟机如何跟计算机的内存协同工作。因为Java虚拟机模拟了计算机,所以自然Java虚拟机包括内存模型。</p>
<p>正确理解Java内存模型对于编写正确的并发程序非常重要。Java内存模型规定了线程何时以及怎么读取其他线程写的值,还有就是在获取共享变量时如何进行同步操作。</p>
<p>最初的Java内存模型是有缺陷的,因此在Java5中进行了修改,并且这个版本的Java内存模型一直到Java8都在使用。</p>
<h1 id="Java内存模型"><a href="#Java内存模型" class="headerlink" title="Java内存模型"></a>Java内存模型</h1><p>JVM中的内存模型将内存分为线程栈内存和堆内存。下面的图从逻辑上展示了Java内存模型:<br><img src="/Java-Memory-Model/java-memory-model-1.png" class="" title="java-memory-model"></p>
<p>每个在JVM中运行的线程都有它自己栈空间。线程的栈空间中包含了线程调用方法执行到那一刻的数据。随着线程执行它的代码,调用栈也随之改变。</p>
<p>线程的栈空间同样也包含每个执行的方法的局部变量。线程只能获取它自己的栈空间,其中包含的局部变量对于其他线程是不可见的。即使两个线程执行相同的代码,这两个线程也是在各自的线程栈空间中创建各自的局部变量。</p>
<p>所有的原型类型(boolean,byte,short,char,int,long,float,double)的局部变量都是存储在线程的栈空间中。一个线程可能传递一个原型变量的副本给另一个线程,但是另一个线程并不能共享这个原型变量。</p>
<p>不管哪个线程创建了对象,这些对象都是存储在堆空间中。这也包括原型类型的包装器类型(e.g. Byte, Integer)。不管对象是被分配给局部变量还是作为另一个对象的成员变量,这个对象都是存储在堆空间中。</p>
<p>下面的图中说明了调用栈和局部变量存储在线程的栈空间中,而对象存储在堆空间中:</p>
<img src="/Java-Memory-Model/java-memory-model-2.png" class="" title="java-memory-model">
<p>如果局部变量是原型类型,那么这个变量在线程的栈空间中。</p>
<p>如果局部变量是一个指向对象的引用类型,那么这个引用是在线程的栈空间,但是对象本身是在堆空间中。</p>
<p>如果一个对象包含方法,同时这些方法包含局部变量。那么这些局部变量(原型类型或者引用)是保存在线程栈空间中,即使这些方法所在的对象是存储在堆空间中。</p>
<p>一个对象的成员变量是跟对象一起存储在堆内存中,不管这个成员变量是原型还是指向一个对象的引用。</p>
<p>类的静态变量也是跟类的定义一起存在堆内存中。</p>
<p>堆空间中对象能够被所有拥有指向此对象的引用的线程访问到。当一个线程能够访问一个对象时,那么这个线程也能够访问此对象的成员变量(这里要看这个对象的封装性)。如果两个线程同时调用了同一个对象的一个方法,那么这两个线程将能够访问这个对象的成员变量,但是每个线程都会获得局部变量的一份拷贝。</p>
<p>下面的图说明了上面描述的内容:</p>
<img src="/Java-Memory-Model/java-memory-model-3.png" class="" title="java-memory-model">
<p>在上图中,两个线程有一系列的局部变量。其中一个局部变量(Local Variable 2)指向了堆内存上的共享对象(Object3)。这两个线程分别拥有一个指向同一个对像的引用,这两个引用是不同的。这些引用是局部变量,所以存储在各自线程的栈空间中。而这两个不同的引用指向了堆上的同一个对象。</p>
<p>注意到共享对象(Object3)有两个引用指向了Object2和Object4,如图中箭头所示。通过Object3中的成员变量引用,这两个线程能够获取Object2和Object4。</p>
<blockquote>
<p>The diagram also shows a local variable which point to two different objects on the heap. In this case the references point to two different objects (Object 1 and Object 5), not the same object. In theory both threads could access both Object 1 and Object 5, if both threads had references to both objects. But in the diagram above each thread only has a reference to one of the two objects.</p>
</blockquote>
<p>上图中同样了展示了一个局部变量指向堆上两个不同的对象。指向不同对象(Object1, Object5)的引用不是同一个对象。理论上,如果这两个线程有指向这两个对象的引用,那么这两个线程都能够访问Object1和Object5。但是在上图中每个线程只有指向其中一个对象的引用。</p>
<p>那么,在Java代码中如何反应上面的内存图呢?代码很简单,如下所示:</p>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyRunnable</span> <span class="keyword">implements</span> <span class="title class_">Runnable</span>() {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">run</span><span class="params">()</span> {</span><br><span class="line"> methodOne();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodOne</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">int</span> <span class="variable">localVariable1</span> <span class="operator">=</span> <span class="number">45</span>;</span><br><span class="line"></span><br><span class="line"> <span class="type">MySharedObject</span> <span class="variable">localVariable2</span> <span class="operator">=</span></span><br><span class="line"> MySharedObject.sharedInstance;</span><br><span class="line"></span><br><span class="line"> <span class="comment">//... do more with local variables.</span></span><br><span class="line"></span><br><span class="line"> methodTwo();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">methodTwo</span><span class="params">()</span> {</span><br><span class="line"> <span class="type">Integer</span> <span class="variable">localVariable1</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">99</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//... do more with local variable.</span></span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySharedObject</span> {</span><br><span class="line"></span><br><span class="line"> <span class="comment">//static variable pointing to instance of MySharedObject</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">MySharedObject</span> <span class="variable">sharedInstance</span> <span class="operator">=</span></span><br><span class="line"> <span class="keyword">new</span> <span class="title class_">MySharedObject</span>();</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="comment">//member variables pointing to two objects on the heap</span></span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">Integer</span> <span class="variable">object2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">22</span>);</span><br><span class="line"> <span class="keyword">public</span> <span class="type">Integer</span> <span class="variable">object4</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Integer</span>(<span class="number">44</span>);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="variable">member1</span> <span class="operator">=</span> <span class="number">12345</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="type">long</span> <span class="variable">member1</span> <span class="operator">=</span> <span class="number">67890</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>如果两个线程执行run()方法,那么此代码就表明了上图所示的内存分布。run方法首先调用methodOne方法,然后methodOne方法调用methodTwo方法。</p>
<p>在methodOne方法中定义了一个原型的局部变量localVariable1,同时定义了一个指向对象的引用localVariable2.</p>
<p>每个执行methodOne方法的线程都会在各自的线程栈空间中创建localVariable1和localVariable2的副本。localVariable1在两个线程中是完全独立的,仅仅存在于对应线程的栈空间中。一个线程不能看到其他线程对于localVariable1变量的修改。</p>
<p>执行methodOne方法的线程同样会创建localVariable2的副本。但是,这两个localVariable2的副本是指向在堆上的同一个对象。而localVariable2引用指向的对象是一个类的静态成员变量。而类的静态变量在堆中只存在一份。所以两个localVariable2指向同一个MySharedObject对象的实例,MySharedObject实例存储在堆内存上,对应图中的Object3对象。</p>
<p>我们在来看看methodTwo方法是如何创建localVariable1局部变量的。这个局部变量是指向一个Integer对象的引用。methodTwo方法每次都重新创建一个Integer实例。局部变量localVariable2引用是存储在对应的栈空间中,而对应的Integer对象是在堆内存中,因为每次执行methodTwo方法都会创建Integer对象,所以在堆内存中会有两个Integer对象。即对应于图中的Object1和Object5对象。</p>
<p>在MySharedObject中有两个long类型的局部变量,因为这些变量是类的成员变量,所以它们跟具体的对象一起保存在堆内存中。仅仅局部变量是保存在栈内存中的。</p>
<h1 id="硬件内存架构"><a href="#硬件内存架构" class="headerlink" title="硬件内存架构"></a>硬件内存架构</h1><p>硬件内存跟Java内存模型是有些不一样的。为了更好地理解Java内存模型,我们需要了解硬件内存架构。下面的图简单描述了现代计算机的硬件架构:</p>
<img src="/Java-Memory-Model/java-memory-model-4.png" class="" title="java-memory-model">
<p>现代的计算机通常会有两个以上的CPU,同时这些CPU可能有多个核。这也意味着我们可以将多个线程同时运行在多个CPU上。在给定的时间点,每个CPU都能运行一个线程。即如果你的Java程序是多线程的,那么能够在多个CPU上同时运行(并行)。</p>
<p>在上图中我们可以看到每个CPU内部都有一系列的寄存器。CPU在寄存器上执行的操作要比在主存中的操作快。这是因为CPU能够更快的获取寄存器上的内容。</p>
<p>每个CPU都有一个对应的缓存内存。获取缓存中的内容要快于获取主存中的内容,但是没有获取寄存器中的内容快。CPU缓存的速度要介于寄存器和主存之间。有些CPU可能会有多级的缓存。</p>
<p>一个计算机包含一个主存(RAM),所有的CPU都能够获取主存中的内容。主存的容量要远远大于缓存的容量。</p>
<p>如果一个CPU要读取主存的内容,通常只会读取主存中部分区域的内容到CPU缓存中,然后在从缓存读取到寄存器中,之后进行计算。当CPU需要写结果到主存中,它会将寄存机中的值刷新到缓存中,然后在之后的某个时间点,在将缓存中的内容刷新到主存中。</p>
<p>当CPU需要存储缓存中的值时,会将缓存中的值刷新到主存中。同时CPU缓存也可以局部的刷新缓存值以及写出缓存值。</p>
<h1 id="Java内存模型和硬件内存架构之间的桥接"><a href="#Java内存模型和硬件内存架构之间的桥接" class="headerlink" title="Java内存模型和硬件内存架构之间的桥接"></a>Java内存模型和硬件内存架构之间的桥接</h1><p>像前面说明的,Java内存模型和硬件内存架构是不同的。硬件内存架构不会区分线程栈和堆空间。在硬件中,线程栈和堆空间都是在主存中的。同时,部分线程栈和堆内存会出现在CPU缓存或者CPU寄存器中。下面的图进行了说明:</p>
<img src="/Java-Memory-Model/java-memory-model-5.png" class="" title="java-memory-model">
<p>当对象和变量存储在不同的内存区域时,会出现一些问题。主要的两个问题如下:</p>
<ol>
<li>共享变量的可见性(可见性,即一个线程能够及时的看到另一个线程对于共享变量的修改)</li>
<li>竞态条件(即读取,检查,写入共享变量)</li>
</ol>
<p>下面会依次介绍这两个问题。</p>
<h2 id="共享变量的可见性"><a href="#共享变量的可见性" class="headerlink" title="共享变量的可见性"></a>共享变量的可见性</h2><p>如果两个线程共享一个对象,但是没有采用合适的volatile关键字或者同步操作,那么当一个线程更新共享对象时,可能另一个线程并不能看到更新后的值。</p>
<p>想象一下,一个共享对象最初是在主存中,一个CPU上的线程读取了此对象到CPU缓存中,之后对于这个共享对象进行了修改。只要CPU缓存没有刷新到主存中,那么运行在其他CPU上的线程是不能读取到修改后的共享变量的值的。这会造成其他CPU只能看到修改之间的值。</p>
<p>下面的图说明了这种情况。在左边的CPU上运行的线程将共享对象读取到CPU缓存中,然后将它的count变量修改为2.由于没有将count的修改刷新到主存中,所以在右侧CPU上运行的线程不能看到这个修改。</p>
<img src="/Java-Memory-Model/java-memory-model-6.png" class="" title="java-memory-model">
<p>为了解决这个问题,我们可以使用Java中的<a href="http://tutorials.jenkov.com/java-concurrency/volatile.html">volatile</a>关键字.volatile关键字的作用是保证每次都是从主存中读取变量的值,同时如果修改了此变量,那么此变量会立刻写回到主存中。</p>
<h2 id="竞态条件"><a href="#竞态条件" class="headerlink" title="竞态条件"></a>竞态条件</h2><p>如果两个线程或多个线程共享一个对象,那么当多余一个线程修改此变量时,竞态条件就可能会出现。</p>
<p>想象一下,线程A将一个共享对象的count变量读取进CPU缓存,线程B也将count读取到另一个CPU缓存。现在线程A在count加上1,同时线程B也在count上加上1.现在变量被增加了两次,在每个CPU缓存中各一次。</p>
<p>如果加1操作是顺序进行的,那么count的值会加上2,然后写回到主存中。</p>
<p>但是,这两个操作是在没有正确同步的情况下同时进行的。尽管线程A或者B会将count修改后的值写回到主存,但是更新后的值始终比原来的值大1.</p>
<p>下面的图说明了上面描述的竞态条件:</p>
<img src="/Java-Memory-Model/java-memory-model-7.png" class="" title="java-memory-model">
<p>为了解决这个问题,可以使用Java的同步块,即<a href="http://tutorials.jenkov.com/java-concurrency/synchronized.html">synchronized</a>关键字。同步块保证了在同步块中获取到的变量都是从主存中读取的,并且当线程离开同步块时,所有修改的变量会被刷新到主存中,而不管这个变量是否被volatile声明。</p>
]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>并发</tag>
<tag>内存模型</tag>
</tags>
</entry>
<entry>
<title>hexo搭建github博客多设备更新</title>
<url>/hexo%E6%90%AD%E5%BB%BAgithub%E5%8D%9A%E5%AE%A2%E5%A4%9A%E8%AE%BE%E5%A4%87%E6%9B%B4%E6%96%B0.html</url>
<content><![CDATA[<p><strong>hexo分支上保存了原始博客文件(相关md文件),master分支上是博客展示的内容(相关html)</strong></p>
<p>前段时间由于电脑重新安装了系统,导致自己的博客文件丢失,而github上面的只有发布后的文件,没有博客的源文件。想要恢复到源文件至今还没有找到解决方法,好在原来的博客内容不多,丢失了也无所谓了。但是以后要是换电脑了,这种问题怎么解决呢?今天介绍一个解决方案。<br>其实方法很简单,首先应该有两个分支,一个分支用来保存发布后的内容,一个分支用来保存源文件.这里用master分支保存发布后的内容,hexo分支保存源文件内容.</p>
<h2 id="第一步"><a href="#第一步" class="headerlink" title="第一步"></a>第一步</h2><p>设备A上搭建github博客</p>
<h2 id="第二步"><a href="#第二步" class="headerlink" title="第二步"></a>第二步</h2><p>在博客根目录下,打开_config.yml,添加如下内容</p>
<figure class="highlight yml"><table><tr><td class="code"><pre><span class="line"><span class="attr">deploy:</span></span><br><span class="line"> <span class="attr">type:</span> <span class="string">git</span></span><br><span class="line"> <span class="attr">repository:</span> <span class="string">git@github.com:youname/youname.github.io.git</span></span><br><span class="line"> <span class="attr">branch:</span> <span class="string">master</span></span><br></pre></td></tr></table></figure>
<blockquote>
<p>可以看到现在已经有一个master分支,但是这时候个人博客还不是一个git目录</p>
</blockquote>
<p>提交代码</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">// hexo编译源文件,生成静态文件,也可以分开执行</span><br><span class="line">hexo clean && hexo g && hexo d</span><br></pre></td></tr></table></figure>
<blockquote>
<p>hexo clean: 清空博客缓存<br>hexo g(hexo generator 的简写):生成静态文件<br>hexo d(hexo deploy的简写): 部署文件,这条命令会使用第一步中的配置信息进行部署</p>
</blockquote>
<p>现在打开yourname.github.io就能够看到你的博客了</p>
<h2 id="第三步"><a href="#第三步" class="headerlink" title="第三步"></a>第三步</h2><p>因为我用的是next主题(其他主题做相似处理).删除next文件下的.git文件夹,这是因为我们在要我们的博客下创建.git,如果子目录下也有.git,会有问题.然后执行以下命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">// git初始化</span><br><span class="line">git init</span><br><span class="line">// 新建分支并切换到新建的分支</span><br><span class="line">git checkout -b 分支名</span><br><span class="line">// 添加所有本地文件到git</span><br><span class="line">git add .</span><br><span class="line">// git提交</span><br><span class="line">git commit -m <span class="string">"提交说明"</span></span><br><span class="line">// 文件推送到hexo分支</span><br><span class="line">git push origin hexo</span><br></pre></td></tr></table></figure>
<blockquote>
<p>以后操作都是在hexo分支中,当我们修改了我们的博客内容时,先执行hexo clean && hexo g && hexo d ,这个命令用来将本地博客发布到github上<br>然后在将本地内容提交到hexo分支中</p>
</blockquote>
<h2 id="第四步"><a href="#第四步" class="headerlink" title="第四步"></a>第四步</h2><p>假设我们需要在电脑B上搭建我们之前的博客内容.我们需要拉取我们的hexo分支,因为hexo分支是源文件,master可以不用拉取</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">// 克隆分支到本地</span><br><span class="line">git <span class="built_in">clone</span> -b hexo https://github.com/用户名/仓库名.git</span><br><span class="line">// 进入博客文件夹</span><br><span class="line"><span class="built_in">cd</span> youname.github.io</span><br><span class="line">// 安装依赖</span><br><span class="line">npm install</span><br></pre></td></tr></table></figure>
<h2 id="第五步"><a href="#第五步" class="headerlink" title="第五步"></a>第五步</h2><p>电脑B上编辑博客内容,静态文件提交到master分支,源文件提交到hexo分支</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">//博文提交到master上面。</span><br><span class="line">hexo clean && hexo g && hexo d</span><br><span class="line">//源文件提交到hexo分支上面。</span><br><span class="line">// 添加源文件</span><br><span class="line">git add .</span><br><span class="line">// git提交</span><br><span class="line">git commit -m <span class="string">""</span></span><br><span class="line">// 先拉原来Github分支上的源文件到本地,进行合并</span><br><span class="line">git pull origin hexo</span><br><span class="line">// 比较解决前后版本冲突后,push源文件到Github的分支</span><br><span class="line">git push origin hexo</span><br></pre></td></tr></table></figure>
<h2 id="第六步"><a href="#第六步" class="headerlink" title="第六步"></a>第六步</h2><p>在电脑A上可以同步hexo分支,开始更新博客</p>
<p><strong>注意: 以后操作都是在hexo分支中,当我们修改了我们的博客内容时,先执行hexo clean && hexo g && hexo d ,这个命令用来将本地博客发布到github上<br>然后在将本地内容提交到hexo分支中</strong></p>
]]></content>
<categories>
<category>hexo</category>
</categories>
<tags>
<tag>hexo</tag>
</tags>
</entry>
<entry>
<title>effective java third edition notes</title>
<url>/effective-java-third-edition-notes.html</url>
<content><![CDATA[<h1 id="effective-java-third-edition-notes"><a href="#effective-java-third-edition-notes" class="headerlink" title="effective java third edition notes"></a>effective java third edition notes</h1><p>记录阅读《effective java》得一些收获</p>
<h2 id="第二章-创建和销毁对象"><a href="#第二章-创建和销毁对象" class="headerlink" title="第二章 创建和销毁对象"></a>第二章 创建和销毁对象</h2>]]></content>
</entry>
<entry>
<title>Quartz与Spring结合使用及集群配置</title>
<url>/Quartz%E4%B8%8ESpring%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8%E5%8F%8A%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE.html</url>
<content><![CDATA[<h1 id="Quartz与Spring结合使用及集群配置"><a href="#Quartz与Spring结合使用及集群配置" class="headerlink" title="Quartz与Spring结合使用及集群配置"></a>Quartz与Spring结合使用及集群配置</h1><p>本文的原文在我个人的<a href="https://blog.csdn.net/benjaminlee1/article/details/72993879">CSDN</a>上。是在2017年的时候由于工作上面需要用到在集群环境中使用调度,采用了Quartz实现,当时进行了记录。不过现在我写博客主要是在个人站点上,所以就把之前的博客部分搬移过来了。<br>本文代码<a href="https://github.com/lightnine/spring-quartz">github地址</a></p>
<h2 id="quartz介绍"><a href="#quartz介绍" class="headerlink" title="quartz介绍"></a>quartz介绍</h2><p>quartz是进行任务调度执行的框架,相对于Java中线程池调度以及Spring自带注解的调度方法,有以下几个有点:</p>
<ol>
<li>能够支持上千上万个调度任务的执行 </li>
<li>任务调度方式较为灵活 </li>
<li>支持集群</li>
</ol>
<h2 id="运行环境"><a href="#运行环境" class="headerlink" title="运行环境"></a>运行环境</h2><ol>
<li>quartz版本:2.2.1,这是quartz最新的版本,其他版本可能跟此版本有一定差异</li>
<li>操作系统centos7,我在vmware中搭建了三台独立的虚拟机</li>
<li>JDK8</li>
<li>数据库:mysql 5.7.18</li>
<li>tomcat8</li>
</ol>
<h2 id="工程目录结构"><a href="#工程目录结构" class="headerlink" title="工程目录结构"></a>工程目录结构</h2><p>工程目录结构如下,采用Idea IDE<br><img src="/Quartz%E4%B8%8ESpring%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8%E5%8F%8A%E9%9B%86%E7%BE%A4%E9%85%8D%E7%BD%AE/%E5%B7%A5%E7%A8%8B%E7%9B%AE%E5%BD%95%E7%BB%93%E6%9E%84.png" class="" title="工程目录结构"></p>
<p>文件说明:<br>resources下是各种配置资源,create-schema.sql用于创建任务表,tables_mysql_innodb.sql用于创建quartz集群运行需要的表,共有十一张表。quartz_properties是quartz的配置</p>
<h2 id="主要配置文件内容"><a href="#主要配置文件内容" class="headerlink" title="主要配置文件内容"></a>主要配置文件内容</h2><p>配置文件中都有每个配置项详细的说明,我这里就只给出具体的配置内容<br>spring配置文件有两个,分别为applicationContext.xml和spring-mvc.xml<br><strong>applicationContext.xml文件如下</strong></p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:aop</span>=<span class="string">"http://www.springframework.org/schema/aop"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/context</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/context/spring-context-3.0.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"com.ll.*"</span>/></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 启用@Aspect支持 --></span></span><br><span class="line"> <span class="tag"><<span class="name">aop:aspectj-autoproxy</span>/></span></span><br><span class="line"> <span class="comment"><!-- 加载数据库配置 --></span></span><br><span class="line"> <span class="tag"><<span class="name">context:property-placeholder</span> <span class="attr">location</span>=<span class="string">"classpath:jdbc.properties"</span>/></span></span><br><span class="line"> <span class="comment"><!-- 数据源 --></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"dataSource"</span> <span class="attr">class</span>=<span class="string">"org.apache.commons.dbcp.BasicDataSource"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">destroy-method</span>=<span class="string">"close"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"driverClassName"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.driver}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"url"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.url}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"username"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.username}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"password"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.password}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"initialSize"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.initialSize}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxActive"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.maxActive}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxIdle"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.minIdle}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"maxWait"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.maxWait}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"jdbcTemplate"</span> <span class="attr">class</span>=<span class="string">"org.springframework.jdbc.core.JdbcTemplate"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"dataSource"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">ref</span> <span class="attr">bean</span>=<span class="string">"dataSource"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"fetchSize"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">value</span>></span>${jdbc.jdbcTemplate.fetchSize}<span class="tag"></<span class="name">value</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"> <span class="comment"><!-- 访问数据库方式 --></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"jdbcDao"</span> <span class="attr">class</span>=<span class="string">"com.dexcoder.dal.spring.JdbcDaoImpl"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"jdbcTemplate"</span> <span class="attr">ref</span>=<span class="string">"jdbcTemplate"</span>/></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="comment"><!-- 配置quartz调度器 --></span></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"scheduler"</span> <span class="attr">class</span>=<span class="string">"org.springframework.scheduling.quartz.SchedulerFactoryBean"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"dataSource"</span> <span class="attr">ref</span>=<span class="string">"dataSource"</span> /></span></span><br><span class="line"> <span class="comment"><!--可选,QuartzScheduler 启动时更新己存在的Job,这样就不用每次修改targetObject后删除qrtz_job_details表对应记录了 --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"overwriteExistingJobs"</span> <span class="attr">value</span>=<span class="string">"true"</span> /></span></span><br><span class="line"> <span class="comment"><!--必须的,QuartzScheduler 延时启动,应用启动完后 QuartzScheduler 再启动 --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"startupDelay"</span> <span class="attr">value</span>=<span class="string">"3"</span> /></span></span><br><span class="line"> <span class="comment"><!-- 设置自动启动 --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"autoStartup"</span> <span class="attr">value</span>=<span class="string">"true"</span> /></span></span><br><span class="line"> <span class="comment"><!-- 把spring上下 文以key/value的方式存放在了quartz的上下文中了 --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"applicationContextSchedulerContextKey"</span> <span class="attr">value</span>=<span class="string">"applicationContext"</span> /></span></span><br><span class="line"> <span class="comment"><!-- scheduler的名称 --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"schedulerName"</span> <span class="attr">value</span>=<span class="string">"ClusterScheduler"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"configLocation"</span> <span class="attr">value</span>=<span class="string">"classpath:quartz.properties"</span> /></span></span><br><span class="line"><span class="comment"><!-- <property name="quartzProperties"> --></span></span><br><span class="line"><span class="comment"><!-- <props> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.scheduler.instanceName">ClusterScheduler</prop> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.scheduler.instanceId">AUTO</prop> --></span></span><br><span class="line"><span class="comment"><!-- 线程池配置 --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.threadPool.class">org.quartz.simpl.SimpleThreadPool</prop> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.threadPool.threadCount">20</prop> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.threadPool.threadPriority">5</prop> --></span></span><br><span class="line"><span class="comment"><!-- JobStore 配置 --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.class">org.quartz.impl.jdbcjobstore.JobStoreTX</prop> --></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- 集群配置 --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.isClustered">true</prop> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.clusterCheckinInterval">15000</prop> --></span></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.maxMisfiresToHandleAtATime">1</prop> --></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.misfireThreshold">120000</prop> --></span></span><br><span class="line"></span><br><span class="line"><span class="comment"><!-- <prop key="org.quartz.jobStore.tablePrefix">QRTZ_</prop> --></span></span><br><span class="line"><span class="comment"><!-- </props> --></span></span><br><span class="line"><span class="comment"><!-- </property> --></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure>
<p>spring-mvc.xml内容如下:</p>
<figure class="highlight xml"><table><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version=<span class="string">"1.0"</span> encoding=<span class="string">"UTF-8"</span>?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/beans/spring-beans-3.0.xsd</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/context</span></span></span><br><span class="line"><span class="string"><span class="tag"> http://www.springframework.org/schema/context/spring-context-3.0.xsd"</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"com.ll.*"</span> /></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">bean</span></span></span><br><span class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.ContentNegotiatingViewResolver"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"order"</span> <span class="attr">value</span>=<span class="string">"1"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"ignoreAcceptHeader"</span> <span class="attr">value</span>=<span class="string">"true"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"mediaTypes"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">map</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"json"</span> <span class="attr">value</span>=<span class="string">"application/json;charset=UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"xml"</span> <span class="attr">value</span>=<span class="string">"application/xml;charset=UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">entry</span> <span class="attr">key</span>=<span class="string">"rss"</span> <span class="attr">value</span>=<span class="string">"application/rss+xml;charset=UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">map</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"defaultViews"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">list</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">property</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"velocityConfig"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.velocity.VelocityConfigurer"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"resourceLoaderPath"</span> <span class="attr">value</span>=<span class="string">"/WEB-INF"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"configLocation"</span> <span class="attr">value</span>=<span class="string">"classpath:velocity.properties"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"viewResolver"</span></span></span><br><span class="line"><span class="tag"> <span class="attr">class</span>=<span class="string">"org.springframework.web.servlet.view.velocity.VelocityViewResolver"</span>></span></span><br><span class="line"> <span class="comment"><!-- <property name="order" value="2"/> --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"suffix"</span> <span class="attr">value</span>=<span class="string">".vm"</span> /></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"prefix"</span> <span class="attr">value</span>=<span class="string">"/"</span>/></span></span><br><span class="line"> <span class="comment"><!-- <property name="exposeSpringMacroHelpers" value="true"/> --></span></span><br><span class="line"> <span class="comment"><!-- <property name="requestContextAttribute" value="rc"/> --></span></span><br><span class="line"> <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"contentType"</span> <span class="attr">value</span>=<span class="string">"text/html;charset=UTF-8"</span> /></span></span><br><span class="line"> <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></table></figure>
<p>Quartz配置文件为quartz.properties,内容如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">#配置参数详解,查看http://haiziwoainixx.iteye.com/blog/1838055</span><br><span class="line">#============================================================================</span><br><span class="line"># Configure Main Scheduler Properties </span><br><span class="line">#============================================================================</span><br><span class="line"># 可为任何值,用在jdbc jobstrore中来唯一标识实例,但是在所有集群中必须相同</span><br><span class="line">org.quartz.scheduler.instanceName: MyClusteredScheduler</span><br><span class="line">#AUTO即可,基于主机名和时间戳来产生实例ID</span><br><span class="line">#集群中的每一个实例都必须有一个唯一的"instance id",应该有相同的"scheduler instance name"</span><br><span class="line">org.quartz.scheduler.instanceId: AUTO</span><br><span class="line">#禁用quartz软件更新</span><br><span class="line">org.quartz.scheduler.skipUpdateCheck: true</span><br><span class="line"></span><br><span class="line">#============================================================================</span><br><span class="line"># Configure ThreadPool 执行任务线程池配置</span><br><span class="line">#============================================================================</span><br><span class="line">#线程池类型,执行任务的线程</span><br><span class="line">org.quartz.threadPool.class: org.quartz.simpl.SimpleThreadPool</span><br><span class="line">#线程数量</span><br><span class="line">org.quartz.threadPool.threadCount: 10</span><br><span class="line">#线程优先级</span><br><span class="line">org.quartz.threadPool.threadPriority: 5</span><br><span class="line">org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread = true </span><br><span class="line"></span><br><span class="line">#============================================================================</span><br><span class="line"># Configure JobStore 任务存储方式</span><br><span class="line">#============================================================================</span><br><span class="line"></span><br><span class="line">org.quartz.jobStore.misfireThreshold: 60000</span><br><span class="line">#可以设置两种属性,存储在内存的RAMJobStore和存储在数据库的JobStoreSupport</span><br><span class="line">#(包括JobStoreTX和JobStoreCMT两种实现JobStoreCMT是依赖于容器来进行事务的管理,而JobStoreTX是自己管理事务)</span><br><span class="line">#这里的属性为 JobStoreTX,将任务持久化到数据中。</span><br><span class="line">#因为集群中节点依赖于数据库来传播 Scheduler 实例的状态,你只能在使用 JDBC JobStore 时应用Quartz 集群。 </span><br><span class="line">#这意味着你必须使用 JobStoreTX 或是 JobStoreCMT 作为 Job 存储;你不能在集群中使用 RAMJobStore。</span><br><span class="line">org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX</span><br><span class="line"></span><br><span class="line">#JobStoreSupport 使用一个驱动代理来操作 trigger 和 job 的数据存储</span><br><span class="line">org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate</span><br><span class="line">#若要设置为true,则将JobDataMaps中的值当作string</span><br><span class="line">org.quartz.jobStore.useProperties=false</span><br><span class="line">#对应下方的数据源配置,与spring结合不需要这个配置</span><br><span class="line">#org.quartz.jobStore.dataSource=myDS</span><br><span class="line">#org.quartz.jobStore.tablePrefix=QRTZ_</span><br><span class="line"></span><br><span class="line">#你就告诉了Scheduler实例要它参与到一个集群当中。这一属性会贯穿于调度框架的始终,用于修改集群环境中操作的默认行为。</span><br><span class="line">org.quartz.jobStore.isClustered=true</span><br><span class="line">#属性定义了Scheduler实例检入到数据库中的频率(单位:毫秒)。默认值是 15000 (即15 秒)。</span><br><span class="line">org.quartz.jobStore.clusterCheckinInterval = 20000</span><br><span class="line"></span><br><span class="line">#这是 JobStore 能处理的错过触发的 Trigger 的最大数量。</span><br><span class="line">#处理太多(超过两打) 很快会导致数据库表被锁定够长的时间,这样就妨碍了触发别的(还未错过触发) trigger 执行的性能。</span><br><span class="line">org.quartz.jobStore.maxMisfiresToHandleAtATime = 1</span><br><span class="line">org.quartz.jobStore.misfireThreshold = 120000</span><br><span class="line">org.quartz.jobStore.txIsolationLevelSerializable = false</span><br><span class="line"></span><br><span class="line">#============================================================================</span><br><span class="line"># Other Example Delegates 其他的数据库驱动管理委托类</span><br><span class="line">#============================================================================</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v6Delegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DB2v7Delegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.DriverDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.HSQLDBDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.MSSQLDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PointbaseDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.WebLogicDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.OracleDelegate</span><br><span class="line">#org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.oracle.WebLogicOracleDelegate</span><br><span class="line"></span><br><span class="line">#============================================================================</span><br><span class="line"># Configure Datasources 数据源配置,与spring结合不需要这个配置</span><br><span class="line">#============================================================================</span><br><span class="line"></span><br><span class="line">#org.quartz.dataSource.myDS.driver: com.mysql.jdbc.Driver</span><br><span class="line">#org.quartz.dataSource.myDS.URL: jdbc:mysql://localhost:3306/quartz</span><br><span class="line">#org.quartz.dataSource.myDS.user: root</span><br><span class="line">#org.quartz.dataSource.myDS.password: 123</span><br><span class="line">#org.quartz.dataSource.myDS.maxConnections: 5</span><br><span class="line">#org.quartz.dataSource.myDS.validationQuery: select 0</span><br><span class="line"></span><br><span class="line">#============================================================================</span><br><span class="line"># Configure Plugins</span><br><span class="line">#============================================================================</span><br><span class="line">#当事件的JVM终止后,在调度器上也将此事件终止</span><br><span class="line">#the shutdown-hook plugin catches the event of the JVM terminating, and calls shutdown on the scheduler.</span><br><span class="line">org.quartz.plugin.shutdownHook.class: org.quartz.plugins.management.ShutdownHookPlugin</span><br><span class="line">org.quartz.plugin.shutdownHook.cleanShutdown: true</span><br><span class="line"></span><br><span class="line">#记录trigger历史的日志插件</span><br><span class="line">#org.quartz.plugin.triggHistory.class: org.quartz.plugins.history.LoggingJobHistoryPlugin</span><br></pre></td></tr></table></figure>
<h2 id="运行"><a href="#运行" class="headerlink" title="运行"></a>运行</h2><p>当按照此项目配置完成之后,将项目打包成war包,然后在一台centos下启动项目,添加任务,之后再另一台centos下也运行此war包。为了简单起见,这里添加的任务仅仅是输出任务的详细信息,并没有其他动作。在两台机器的catalina.out中可以看到输出的结果,可以看到任务可以负载均衡的运行在不同的机器上。</p>
<blockquote>
<p>如何动态的查看catalina.out中的内容,可以用tail -f来查看,能够实时看到文件的变化</p>
</blockquote>
]]></content>
<categories>
<category>Spring</category>
</categories>
<tags>
<tag>Quartz</tag>
<tag>调度</tag>
<tag>集群</tag>
</tags>
</entry>
<entry>
<title>java synchronized keyword</title>
<url>/java-synchronized-keyword.html</url>
<content><![CDATA[<h1 id="java-synchronized关键字"><a href="#java-synchronized关键字" class="headerlink" title="java synchronized关键字"></a>java synchronized关键字</h1><p>在平常进行java并发程序的开发过程中,synchronized关键字的出现频率很高。但是synchronized底层是如何实现的,synchronized都有哪些具体的用法。本篇将会在下面进行详细讲解。<br>synchronized关键字主要是用来进行同步操作。synchronized关键字修饰的内容,每次都只能有一个线程进入,如果其他线程想要进入相同的代码块,那么必须等前一个线程释放代码块对应的锁,其他的线程才能进入此代码块。但是同一个线程能够多次进入一个相同的同步块,也就是synchronized具有可重入锁的特性。<br>总的来说,在java中,主要在三个地方使用synchronized关键字</p>
<ol>
<li>类的实例方法</li>
<li>类的静态方法</li>
<li>修饰部分代码块</li>
</ol>
<h2 id="synchronized用在实例方法上"><a href="#synchronized用在实例方法上" class="headerlink" title="synchronized用在实例方法上"></a>synchronized用在实例方法上</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClassInstance</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="type">int</span> count;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> value)</span>{</span><br><span class="line"> <span class="built_in">this</span>.count += value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>在上面的代码中展示了,synchronized应用在实例方法的方式。当synchronized用在实例方法上时,每当线程进入此方法时,会尝试获取对应的类实例上的锁(注意是类实例)。如果没有其他的线程持有此实例上的锁,那么线程会获取此实例锁,然后运行此方法。<br>下面的图显示了使用javap进行反编译后的内容(运行:<code>javap -v MyClassInstance.class</code>):<br><img src="/java-synchronized-keyword/MyClassInstance.png" class="" title="java-memory-model"><br>图中仅仅展示了add方法反编译后的内容,可以看到在方法的flags标记中出现了ACC_SYNCHRONIZED。这个就是表明前面方法是用synchronized关键字修饰的。这个跟同步代码块的反编译结果是不同的。但是在线程执行同步操作时,都是要获取对应对象上的锁。</p>
<h2 id="synchronized用在类的静态方法上"><a href="#synchronized用在类的静态方法上" class="headerlink" title="synchronized用在类的静态方法上"></a>synchronized用在类的静态方法上</h2><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClassStatic</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">add</span><span class="params">(<span class="type">int</span> value)</span> {</span><br><span class="line"> System.out.print(value);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure>
<p>上面代码展示了如何在静态方法上使用synchronized关键字。当线程尝试进入类的静态方法时,会尝试获取类上的锁(注意是类上的锁,跟类实例上的锁不同的东西)。如果没有其他线程持有此类上的锁,那么当前线程会获取此类上的锁,然后运行此方法。<br>下面展示了反编译的结果(运行命令:<code>javap -v MyClassStatic.class</code>)<br><img src="/java-synchronized-keyword/MyClassStatic.png" class="" title="java-memory-model"><br>可以从反编译的结果中看到在方法的flags中也是包含了ACC_SYNCHRONIZED.</p>
<h2 id="synchronized用在代码块上"><a href="#synchronized用在代码块上" class="headerlink" title="synchronized用在代码块上"></a>synchronized用在代码块上</h2><p>如果synchronized关键字用在代码块上,会在其之后括号中的对象获取锁。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass</span> {</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="keyword">synchronized</span>(<span class="built_in">this</span>) {</span><br><span class="line"> System.out.println(msg);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">func</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="keyword">synchronized</span>(MyClass.class) {</span><br><span class="line"> System.out.print(msg);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>在上面的代码中,在方法log中,当线程进入log方法,然后执行synchronized关键字修饰的代码块。线程会尝试获取当前类实例上的锁,因为括号中使用的是this关键字。而在func方法中,线程会尝试获取MyClass类的锁。注意这两个锁是不同的。所以一个线程可以执行log中的同步代码块,而同时另一个线程也可以执行func中的同步代码块。<br>反编译结果如下(运行命令:<code>javap -v MyClass.class</code>):<br><img src="/java-synchronized-keyword/MyClass1.png" class="" title="java-memory-model"><br>可以看到在方法的签名中是没有ACC_SYNCHRONIZED的。但是在代码中出现了monitorenter和monitorexit。<br><strong>monitorenter</strong>:</p>
<blockquote>
<p>Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:<br>• If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.<br>• If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.<br>• If another thread already owns the monitor associated with objectref, the thread blocks until the monitor’s entry count is zero, then tries again to gain ownership.</p>
</blockquote>
<p>引用中的内容来自JVM规范。这段话的大概意思为:<br>每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:</p>
<ol>
<li>如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者。</li>
<li>如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1.</li>
<li>如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权。</li>
</ol>
<p><strong>monitorexit</strong>:</p>
<blockquote>
<p>The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.<br>The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.</p>
</blockquote>
<p>这段话的大概意思为:<br>执行monitorexit的线程必须是objectref所对应的monitor的所有者。<br>指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。<br>通过这两段描述,我们应该能很清楚的看出Synchronized的实现原理,Synchronized的语义底层是通过一个monitor的对象来完成,其实wait/notify等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait/notify等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因。<br>一个monitorenter都会对应有一个monitorexit。但是我们从反编译的结果中,可以看到多出了一个monitorexit,即第18行。因为在synchronized中的代码遇到异常时,会释放锁。第一个 monitorexit 指令如果正确执行,会走到下面的 goto 指令,直接跳转到 21 行 return,而如果发生异常,下面的 astore_3 和 aload_2 指令会继续执行异常问题,下一步会继续执行 monitorexit 指令退出同步。</p>
<p>当然,有时候,我们也可以这样用synchronized关键字修饰代码块<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyClass2</span> {</span><br><span class="line"> <span class="keyword">private</span> <span class="type">Object</span> <span class="variable">obj</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="type">Object</span> <span class="variable">obj2</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Object</span>();</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">log</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="keyword">synchronized</span>(obj) {</span><br><span class="line"> System.out.print(msg);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">func</span><span class="params">(String msg)</span> {</span><br><span class="line"> <span class="keyword">synchronized</span>(obj2) {</span><br><span class="line"> System.out.print(msg);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>在上面的代码中,log方法中的同步块是对obj实例进行加锁,注意每个MyClass2实例中的obj都是不同的。而func中的同步块是对obj2静态成员进行加锁。</p>
]]></content>
<categories>
<category>java</category>
</categories>
<tags>
<tag>并发</tag>
</tags>
</entry>
<entry>
<title>jupyter notebook使用</title>
<url>/jupyter-notebook%E4%BD%BF%E7%94%A8.html</url>
<content><![CDATA[<h1 id="jupyter-notebook"><a href="#jupyter-notebook" class="headerlink" title="jupyter notebook"></a>jupyter notebook</h1><p>Jupyter notebook, 前身是 IPython notebook, 它是一个非常灵活的工具,有助于帮助你构建很多可读的分析,你可以在里面同时保留代码,图片,评论,公式和绘制的图像。<br>这篇文章主要是记录下jupyter notebook使用过程中一些常用的内容和技巧,包括一些magic使用</p>
<h2 id="快捷键"><a href="#快捷键" class="headerlink" title="快捷键"></a>快捷键</h2><p>windows上,<strong>Ctrl + Shift + P</strong>可以调出快捷键的帮助信息,所有的快捷键都能够看到。掌握快捷键能够帮助我们更好的使用jupyter notebook</p>
<h2 id="Jupyter-Magic-Commands"><a href="#Jupyter-Magic-Commands" class="headerlink" title="Jupyter Magic Commands"></a>Jupyter Magic Commands</h2><ol>
<li>%matplotlib inline</li>
</ol>
<p>如果想在jupyter中画图,可以添加如下代码,则图像就能够进行显示</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">%matplotlib inline</span><br></pre></td></tr></table></figure>
<p>2. Timing</p>
<p>对于计时有两个十分有用的魔法命令:%%time 和 %timeit. 如果你有些代码运行地十分缓慢,而你想确定是否问题出在这里,这两个命令将会非常方便。</p>
<p><strong>%%time</strong> 将会给出cell的代码运行一次所花费的时间。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line">%%time</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">for</span> _ <span class="keyword">in</span> <span class="built_in">range</span>(<span class="number">1000</span>):</span><br><span class="line"> time.sleep(<span class="number">0.01</span>)<span class="comment"># sleep for 0.01 seconds</span></span><br></pre></td></tr></table></figure>
<p><strong>%timeit</strong> 使用Python的timeit模块,它将会执行一个语句100,000次(默认情况下),然后给出运行最快3次的平均值。</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> numpy</span><br><span class="line">%timeit numpy.random.normal(size=<span class="number">100</span>)</span><br></pre></td></tr></table></figure>
<h2 id="运行文件"><a href="#运行文件" class="headerlink" title="运行文件"></a>运行文件</h2>]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>jupyter</tag>
<tag>工具</tag>
</tags>
</entry>
<entry>
<title>java-zero-copy</title>
<url>/java-zero-copy.html</url>
<content><![CDATA[<blockquote>
<p>本篇内容主要翻译自<a href="https://developer.ibm.com/articles/j-zerocopy/">Efficient data transfer through zero copy</a>,包括有些自己的思考</p>
</blockquote>
<h1 id="java-zero-copy"><a href="#java-zero-copy" class="headerlink" title="java zero copy"></a>java zero copy</h1><p>很多网页应用有大量的静态内容,针对这些静态内容主要的处理逻辑就是从磁盘读取数据,然后将数据写入到响应的socket中。这项工作应该需要较少的CPU资源。但是有时候并不是。内核从磁盘读取数据,然后将数据拷贝到应用中。之后应用将数据写入到内核,然后推送到socket中。实际上,应用程序在这里扮演了一个无效率的中间层,既将数据从磁盘写入到socket。<br>每一次当数据在用户空间和内核空间转换时,数据都会被拷贝,而这会消耗cpu cycles以及内存带宽。幸运的是,我们可以采用zero copy技术来避免内核和应用程序之间的数据拷贝。应用程序使用zero copy技术来请求内核直接将数据从磁盘文件拷贝到socket中,而不需要经过应用程序。zero copy技术能够极大的提升应用程序性能并且减少内核空间和用户空间之间的切换。<br>Java中使用<code>java.nio.channels.FileChannel</code>中的<code>transferTo()</code>方法在linux和Unix系统重实现zero copy。使用<code>transferTo()</code>方法能够直接将字节数据从一个channel写入到另一个channel中,而数据不需要经过应用程序。本篇文章首先展示使用传统的拷贝方法消耗的资源,然后展示使用zero copy获得的性能提升。</p>
<h2 id="数据传输-传统方法"><a href="#数据传输-传统方法" class="headerlink" title="数据传输:传统方法"></a>数据传输:传统方法</h2><p>想想一个简单的场景,从一个文件读取数据,然后将数据通过网络写入到另一个程序中。核心操作如下所示。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line">File.read(fileDesc, buf, len);</span><br><span class="line">Socket.send(socket, buf, len);</span><br></pre></td></tr></table></figure><br>虽然看起来很简单,但是这个操作需要再内核空间和用户空间4次上下文切换,以及4次数据拷贝。下图展示了具体过程<br><img src="/java-zero-copy/figure1.gif" class="" title="传统的数据拷贝过程"><br>下图展示了上下文切换过程<br><img src="/java-zero-copy/figure2.gif" class="" title="上下文切换"></p>
<p>主要的步骤如下:</p>
<ol>
<li><code>read()</code>方法从user mode 转换到 kernel mode。在read内部是发一起了一次系统调用<code>sys_read()</code>从文件读取数据。第一次数据拷贝是由DMA引擎来执行,DMA从磁盘读取文件,然后将数据保存到内核缓冲区中。</li>
<li>请求的数据被从内核缓冲区拷贝到用户缓冲区,<code>read()</code>方法返回。这引起了第二次的上下文切换。现在数据是在用户空间中的缓冲区中。</li>
<li><code>send()</code>方法调用引起用户空间到内核空间的切换。这次会将数据从用户空间拷贝到内核缓冲区。这一次数据是放置到另外一个内核缓冲区中,与目的socket相关联。</li>
<li><code>send()</code>方法返回,又引起了一次上下文切换。DMA将数据从内核缓冲区拷贝到网卡缓冲区中,这是第四次数据拷贝。</li>
</ol>
<p>我们为什么不直接将数据拷贝到用户空间,而要经过内核空间呢?首先因为用户进程没有办法直接读取硬盘,这是基于保护的目的,都要经过操作系统的处理,才能读取到硬盘内容;其次这是因为操作系统引入内核缓冲区是为了提升性能。操作系统读取数据都会预读取一些数据,这样在应用程序读取额外的数据时,可以不用发起系统调用从硬盘中读取,而是直接从内核缓冲区获取即可。而写入过程,可以完全实现为异步过程。既应用程序只需要将数据写入到内核缓冲区中,而不是写入到磁盘中。<br>但是,设想当前应用程序需要处理的数据要远远大于内核空间缓冲区的大小。而此时,数据需要在磁盘,内核缓冲区,用户空间中来回拷贝。这会严重影响性能。<br>Zero copy技术是解决这个问题的方法。</p>
<h2 id="数据传输:zero-copy方法"><a href="#数据传输:zero-copy方法" class="headerlink" title="数据传输:zero copy方法"></a>数据传输:zero copy方法</h2><p>如果你仔细检查上面的过程,你会发现第二次和第三次数据拷贝可以省略。应用程序针对这些数据什么也不做。因此数据可以被直接从内核缓冲区拷贝到socket buffer中。<code>transferTo()</code>方法可以完成这个操作。下面展示了此方法<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">transferTo</span><span class="params">(<span class="type">long</span> position, <span class="type">long</span> count, WritableByteChannel target)</span>;</span><br></pre></td></tr></table></figure><br><code>transferTo()</code>方法将数据从文件channel拷贝到target channel中。这个方法依赖底层操作系统对于zero copy的支持。在UNIX或linux中,使用的是<code>sendfile()</code>系统调用。如下所示:<br><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="meta">#<span class="keyword">include</span> <span class="string"><sys/socket.h></span></span></span><br><span class="line"><span class="type">ssize_t</span> <span class="title function_">sendfile</span><span class="params">(<span class="type">int</span> out_fd, <span class="type">int</span> in_fd, <span class="type">off_t</span> *offset, <span class="type">size_t</span> count)</span>;</span><br></pre></td></tr></table></figure><br>下图展示了<code>transferTo()</code>方法执行过程<br><img src="/java-zero-copy/figure3.gif" class="" title="transferTo过程"></p>
<p>下图展示了具体的上下文切换<br><img src="/java-zero-copy/figure4.gif" class="" title="上下文切换"></p>
<p><code>transferTo()</code>方法执行过程如下:</p>
<ol>
<li>DMA引擎将文件内容拷贝到内核缓冲区。然后数据从内核缓冲区拷贝到socket buffer中。(涉及到两次数据拷贝)</li>
<li>第三次数据拷贝发生在DMA引擎将数据从socket buffer拷贝到网卡中。</li>
</ol>
<p>我们将上下文切换从4次降低到2次,数据拷贝从4次降低到3次(其中仅有一个数据拷贝需要CPU参与,图中序号2)。但是这还没有达到zero copy的目的。如果底层网卡支持gather操作,那么我们可以减少内核空间中的数据重复。在linux kernel2.4及以后版本中,socket buffer已经支持了这个操作。这个方法不仅仅减少了上下文切换并且也消除了CPU参与的数据拷贝。具体如下:</p>
<ol>
<li><code>transferTo()</code>方法将文件内容拷贝到内核缓冲区,由DMA引擎执行</li>
<li>不需要将数据拷贝进socket buffer中,仅仅将数据的位置以及数据长度添加到socket buffer中。DMA引擎直接将kernel buffer中的数据拷贝到网卡中。<br>下图展示了包含gather操作的<code>transferTo()</code><img src="/java-zero-copy/figure5.gif" class="" title="gather操作的transferTo方法">
</li>
</ol>
<h2 id="性能比较"><a href="#性能比较" class="headerlink" title="性能比较"></a>性能比较</h2><p>使用java实现文件传输,采用传统的IO和nio来分别实现进行对比。完整代码<a href="https://github.com/lightnine/j-zerocopy">参考</a>.其中客户端是主要的视线,服务端仅仅读取数据。</p>
<h3 id="传统IO-client-代码"><a href="#传统IO-client-代码" class="headerlink" title="传统IO client 代码"></a>传统IO client 代码</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="comment">// 1. create socket and connect to server</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> socket = <span class="keyword">new</span> <span class="title class_">Socket</span>(Common.SERVER, port);</span><br><span class="line"> System.out.println(<span class="string">"Connected with server "</span> + socket.getInetAddress() + <span class="string">":"</span> + socket.getPort());</span><br><span class="line"> } <span class="keyword">catch</span> (UnknownHostException e) {</span><br><span class="line"> System.out.println(e);</span><br><span class="line"> System.exit(Common.ERROR);</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> System.out.println(e);</span><br><span class="line"> System.exit(Common.ERROR);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 2. send data to server</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> inputStream = Files.newInputStream(Paths.get(fileName));</span><br><span class="line"> output = <span class="keyword">new</span> <span class="title class_">DataOutputStream</span>(socket.getOutputStream());</span><br><span class="line"> <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> <span class="type">byte</span>[] b = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">4096</span>];</span><br><span class="line"> <span class="type">long</span> <span class="variable">read</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="type">long</span> <span class="variable">total</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"> <span class="comment">// read function cause user mode to kernel mode,</span></span><br><span class="line"> <span class="comment">// and DMA engine read file content from disk to kernel buffer</span></span><br><span class="line"> <span class="comment">// then copy kernel buffer to the b array. This cause another context switch</span></span><br><span class="line"> <span class="comment">// then when read return, cause kernel mode to user mode</span></span><br><span class="line"> <span class="comment">// Summary: two context switch, two copy(one cpu copy)</span></span><br><span class="line"> <span class="keyword">while</span> ((read = inputStream.read(b)) >= <span class="number">0</span>) {</span><br><span class="line"> total = total + read;</span><br><span class="line"> System.out.println(<span class="string">"total size:"</span> + total);</span><br><span class="line"> <span class="comment">// write function cause user mode to kernel mode,</span></span><br><span class="line"> <span class="comment">// and copy data from b array to socket buffer,</span></span><br><span class="line"> <span class="comment">// then DMA engine copy socket buffer to nic(network interface) buffer</span></span><br><span class="line"> <span class="comment">// then when write return, cause kernel mode to user mode,</span></span><br><span class="line"> <span class="comment">// Summary: two context switch, two copy(one cpu copy)</span></span><br><span class="line"> output.write(b);</span><br><span class="line"> }</span><br><span class="line"> System.out.println(<span class="string">"bytes send: "</span> + total + <span class="string">" and totalTime(ms):"</span> + (System.currentTimeMillis() - start));</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> System.out.println(e);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<h3 id="nio-client-代码"><a href="#nio-client-代码" class="headerlink" title="nio client 代码"></a>nio client 代码</h3><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">testSendfile</span><span class="params">()</span> <span class="keyword">throws</span> IOException {</span><br><span class="line"> <span class="comment">// 1. get file size(bytes)</span></span><br><span class="line"> <span class="type">Path</span> <span class="variable">path</span> <span class="operator">=</span> Paths.get(fileName);</span><br><span class="line"> <span class="type">long</span> <span class="variable">fsize</span> <span class="operator">=</span> Files.size(path);</span><br><span class="line"></span><br><span class="line"> <span class="type">SocketAddress</span> <span class="variable">sad</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">InetSocketAddress</span>(Common.SERVER, port);</span><br><span class="line"> <span class="type">SocketChannel</span> <span class="variable">sc</span> <span class="operator">=</span> SocketChannel.open();</span><br><span class="line"> sc.connect(sad);</span><br><span class="line"> sc.configureBlocking(<span class="literal">true</span>);</span><br><span class="line"> <span class="type">FileInputStream</span> <span class="variable">fis</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">FileInputStream</span>(fileName);</span><br><span class="line"> <span class="type">FileChannel</span> <span class="variable">fc</span> <span class="operator">=</span> fis.getChannel();</span><br><span class="line"> <span class="type">long</span> <span class="variable">start</span> <span class="operator">=</span> System.currentTimeMillis();</span><br><span class="line"> <span class="type">long</span> <span class="variable">curnset</span> <span class="operator">=</span> <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"> <span class="comment">// in linux kernel 2.4 and later</span></span><br><span class="line"> <span class="comment">// transferTo() function cause user mode to kernel mode</span></span><br><span class="line"> <span class="comment">// DMA engine copy data from disk to kernel buffer</span></span><br><span class="line"> <span class="comment">// then just copy data position and data length to kernel buffer</span></span><br><span class="line"> <span class="comment">// then DMA engine copy kernel buffer to NIC buffer</span></span><br><span class="line"> <span class="comment">// when transferTo return, cause another context switch</span></span><br><span class="line"> <span class="comment">// Summary: two context switch, two copy(zero CPU copy)</span></span><br><span class="line"> curnset = fc.transferTo(<span class="number">0</span>, fsize, sc);</span><br><span class="line"> System.out.println(<span class="string">"total bytes transferred: "</span> + curnset + <span class="string">" and time taken in MS: "</span> +</span><br><span class="line"> (System.currentTimeMillis() - start) );</span><br><span class="line"></span><br><span class="line"> fc.close();</span><br><span class="line"> fis.close();</span><br><span class="line"> }</span><br></pre></td></tr></table></figure>
<p>性能比较如下:</p>
<div class="table-container">
<table>
<thead>
<tr>
<th>file size</th>
<th>traditional(ms)</th>
<th>nio(ms)</th>
</tr>
</thead>
<tbody>
<tr>
<td>12MB</td>
<td>50</td>
<td>18</td>
</tr>
<tr>
<td>221MB</td>
<td>690</td>
<td>314</td>
</tr>
<tr>
<td>2.5G</td>
<td>15496</td>
<td>2610</td>
</tr>
</tbody>
</table>
</div>
<p>评测环境:</p>
<ol>
<li>java : openjdk version “1.8.0_382”, OpenJDK Runtime Environment (build 1.8.0_382-b05), OpenJDK 64-Bit Server VM (build 25.382-b05, mixed mode)</li>
<li>linux: CentOS Linux release 7.6 (Final)</li>
<li>kernel: 4.14.0_1-0-0-51</li>
</ol>
]]></content>
<categories>
<category>linux</category>
</categories>
<tags>
<tag>java</tag>
<tag>linux</tag>
<tag>zero-copy</tag>
</tags>
</entry>
<entry>
<title>linux有关网络操作的11个命令</title>
<url>/linux%E6%9C%89%E5%85%B3%E7%BD%91%E7%BB%9C%E6%93%8D%E4%BD%9C%E7%9A%8411%E4%B8%AA%E5%91%BD%E4%BB%A4.html</url>
<content><![CDATA[<p>下面的11个命令在进行linux网络操作很有用,特意记录下.</p>
<blockquote>
<p>原文链接:<a href="https://github.com/oldratlee/translations/blob/master/how-to-work-with-network-from-linux-terminal/README.md">https://github.com/oldratlee/translations/blob/master/how-to-work-with-network-from-linux-terminal/README.md</a></p>
</blockquote>
<h2 id="curl-amp-wget"><a href="#curl-amp-wget" class="headerlink" title="curl & wget"></a>curl & wget</h2><p>curl和wget都可以下载文件</p>
]]></content>
<categories>
<category>Linux</category>
</categories>
</entry>
<entry>
<title>python3学习</title>
<url>/python3%E5%AD%A6%E4%B9%A0.html</url>
<content><![CDATA[<h1 id="模块"><a href="#模块" class="headerlink" title="模块"></a>模块</h1><p>在python中,一个py文件就是一个模块.而为了避免模块名称冲突,Python又引入了按目录来组织模块的方法,成为Package.引入了包后,对应的模块名前面就添加了包名.类似于Java中的Package.</p>
<p>__init__.py:在包目录下,必须有__init__.py这个文件,如果没有这个文件,那么python会将此文件作为一个普通目录,而不在是一个package.__init__.py可以为空,也可以有代码(一般都是一些import语句).__init__.py本身是一个模块,其模块名为对应的package名称.</p>
<h1 id="Python示例程序"><a href="#Python示例程序" class="headerlink" title="Python示例程序"></a>Python示例程序</h1><figure class="highlight python"><figcaption><span>hello.py</span></figcaption><table><tr><td class="code"><pre><span class="line"><span class="comment">#!/usr/bin/env python3</span></span><br><span class="line"><span class="comment"># -*- coding: utf-8 -*-</span></span><br><span class="line"></span><br><span class="line"><span class="string">' a test module '</span></span><br><span class="line"></span><br><span class="line">__author__ = <span class="string">'Michael Liao'</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">test</span>():</span><br><span class="line"> args = sys.argv</span><br><span class="line"> <span class="keyword">if</span> <span class="built_in">len</span>(args)==<span class="number">1</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Hello, world!'</span>)</span><br><span class="line"> <span class="keyword">elif</span> <span class="built_in">len</span>(args)==<span class="number">2</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Hello, %s!'</span> % args[<span class="number">1</span>])</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Too many arguments!'</span>)</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__==<span class="string">'__main__'</span>:</span><br><span class="line"> test()</span><br></pre></td></tr></table></figure>
<p>程序说明:程序开始的一,二行是标准注释.第一行注释说明这个文件可以直接在Unix/Linux/Max上运行.第二行表示文件本身使用utf-8编码.第四行是模块的文档注释,第六行是作者信息.<br><code>if __name__ == '__main__':</code>:当我们在命令行运行模块时,python解释器会将一个特殊的变量<code>__name__</code>设置为<code>__main__</code>.但是如果在其他模块中导入此模块,python解释器并不是设置<code>__name__</code>变量.</p>
<h1 id="python中的作用域"><a href="#python中的作用域" class="headerlink" title="python中的作用域"></a>python中的作用域</h1><p>正常的变量或函数是公开的,可以直接引用如<code>abc</code>,<code>xyz</code>等.<br>类似<code>__xxx__</code>的变量时特殊变量,也可以直接引用,但是有特殊用途.如<code>__author__</code>,<code>__name__</code>.而模块的文档注释可以用<code>__doc__</code>来访问<br>类似于<code>_x</code>,<code>__x</code>这样定义的变量或函数是非公开的,类似Java的private.</p>
<h1 id="python模块搜索路径"><a href="#python模块搜索路径" class="headerlink" title="python模块搜索路径"></a>python模块搜索路径</h1><p>当我们引入模块时,即使用import导入模块时,python解释器从搜索路径中查找对应的模块.如果找不到,则会报错.<br>默认情况下,Python解释器会搜索当前目录、所有已安装的内置模块和第三方模块,搜索路径存放在sys模块的path变量中.下面代码可以查看搜索路径内容</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">>>> import sys</span><br><span class="line">>>> sys.path</span><br></pre></td></tr></table></figure>
<h2 id="修改搜索路径"><a href="#修改搜索路径" class="headerlink" title="修改搜索路径"></a>修改搜索路径</h2><p>可以使用两种方式修改搜索路径</p>
<ol>
<li>直接修改<code>sys.path</code> <figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">>>> import sys</span><br><span class="line">>>> sys.path.append(<span class="string">'待添加路径'</span>)</span><br></pre></td></tr></table></figure>
但是这种方式只在程序运行时有效,程序运行结束后失效</li>
<li>设置<code>PATHONPATH</code><br> 直接将待添加的路径加入<code>PATHONPATH</code>环境变量中即可</li>
</ol>
<h1 id="类与对象"><a href="#类与对象" class="headerlink" title="类与对象"></a>类与对象</h1><p>在python类中,<code>__init__</code>方法就相当于Java中类的构造函数,<strong>self</strong>表示实例自身.<br>如果类中的变量定义前加了两个下划线,那么此变量就成为一个私有变量</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">__init__</span>(<span class="params">self, name, score</span>):</span><br><span class="line"> self.__name = name</span><br><span class="line"> self.score = score</span><br><span class="line">a = Student(<span class="string">'1'</span>, <span class="number">2</span>)</span><br><span class="line"><span class="built_in">print</span>(a.__name)</span><br><span class="line"><span class="built_in">print</span>(a.score)</span><br></pre></td></tr></table></figure>
<p>打印<code>__name</code>时,将会报错,而可以打印score的内容.</p>
<h2 id="鸭子类型"><a href="#鸭子类型" class="headerlink" title="鸭子类型"></a>鸭子类型</h2><blockquote>
<p>注明:个人解决因为无法限制方法入参的类型,那么传递进去什么都可以</p>
</blockquote>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Animal</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Animal is running...'</span>)</span><br><span class="line"><span class="keyword">def</span> <span class="title function_">run_twice</span>(<span class="params">animal</span>):</span><br><span class="line"> animal.run()</span><br><span class="line"> animal.run()</span><br><span class="line"><span class="keyword">class</span> <span class="title class_">Timer</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">run</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Start...'</span>)</span><br><span class="line">run_twice(Timer())</span><br></pre></td></tr></table></figure>
<p>在Java中,如果定义<code>run_twice</code>方法,必须定义函数的形参类型,如果指定了只能接受Animal类型,则只能传递Animal类型或其子类.但是在python不是这样,只要传递进的类型中存在<code>run</code>方法即可.不过python中不存在限制入参是什么类型.</p>
<blockquote>
<p>这就是动态语言的“鸭子类型”,它并不要求严格的继承体系,一个对象只要“看起来像鸭子,走起路来像鸭子”,那它就可以被看做是鸭子。</p>
</blockquote>
<h2 id="slots"><a href="#slots" class="headerlink" title="__slots__"></a><code>__slots__</code></h2><p>python是动态语言,可以在运行过程中给类实例以及类本身添加属性和方法。如果想要限制动态添加的属性,则可以在类中定义<code>__slots__</code>,如下代码</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"> __slots__ = (<span class="string">'name'</span>, <span class="string">'age'</span>) <span class="comment"># 运行绑定的属性限制为name和age</span></span><br></pre></td></tr></table></figure>
<h2 id="property"><a href="#property" class="headerlink" title="@property"></a><code>@property</code></h2><p><code>@property</code>是python内置装饰器,可以在给属性添加getter以及setter方法,从而简化代码。如下使用</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">class</span> <span class="title class_">Student</span>(<span class="title class_ inherited__">object</span>):</span><br><span class="line"><span class="meta"> @property</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">score</span>(<span class="params">self</span>):</span><br><span class="line"> <span class="keyword">return</span> self._score</span><br><span class="line"></span><br><span class="line"><span class="meta"> @score.setter</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">score</span>(<span class="params">self, value</span>):</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> <span class="built_in">isinstance</span>(value, <span class="built_in">int</span>):</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">'score must be an integer!'</span>)</span><br><span class="line"> <span class="keyword">if</span> value < <span class="number">0</span> <span class="keyword">or</span> value > <span class="number">100</span>:</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">'score must between 0 ~ 100!'</span>)</span><br><span class="line"> self._score = value</span><br></pre></td></tr></table></figure>
<p>第一个score方法被<code>@property</code>修改,从而为score属性添加了getter方法,第二个score方法被<code>@score.setter</code>修饰,添加了setter方法.如果去掉第二个score,则score属性将没有setter方法.</p>
]]></content>
<tags>
<tag>python</tag>
</tags>
</entry>
<entry>
<title>linux鸟哥私房菜收获</title>
<url>/linux%E9%B8%9F%E5%93%A5%E7%A7%81%E6%88%BF%E8%8F%9C%E6%94%B6%E8%8E%B7.html</url>
<content><![CDATA[<blockquote>
<p>最近半个月读完了《linux鸟哥私房菜》这本书,其实在研究生的时候这本书已经读过一遍,这次拿出来读,主要是巩固熟悉一下linux操作系统的相关概念。</p>
</blockquote>
<p>这本书整体来说对于初学者还是比较合适的。但是里面也有错误,同时书排版的方式有些地方有些问题,但是这些问题都不是很大。通过阅读这本书,对于linux的了解还是比较深度一些的。如果想要更深入的了解linux,可以看看《Unix & Linux大学教程》这本书,这本书对于命令的介绍和使用会更加深入些。</p>
<h1 id="操作系统"><a href="#操作系统" class="headerlink" title="操作系统"></a>操作系统</h1><p>操作系统是硬件和在操作系统上面运行软件的一个中间层。当然这里面包括了很多的内容,包括如何管理硬盘,内存,CPU等等一系列内容。所以一个操作系统要包括以下内容:文件系统,内存管理,网络管理,进程线程管理等等。下面主要聊一聊读了《linux鸟哥私房菜》的一些获得内容。</p>
<h2 id="linux命令"><a href="#linux命令" class="headerlink" title="linux命令"></a>linux命令</h2><p>在linux中更常用的还是命令行命令,由于在我们平时的工作中,使用linux主要是作为服务器,而服务器基本上是不提供X Window的。所以掌握linux命令就变得很重要。在linux下面的命令,我们要学会使用帮助文档,即如下获取命令的详细使用说明,其实就是软件程序的使用说明。</p>
<h3 id="命令帮助"><a href="#命令帮助" class="headerlink" title="命令帮助"></a>命令帮助</h3><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">man [<span class="built_in">command</span>]</span><br><span class="line">[<span class="built_in">command</span>] --<span class="built_in">help</span>(-h)</span><br></pre></td></tr></table></figure>
<p>man的输出内容主要包括以下内容:<br>NAME:简短的命令,数据名称说明<br>SYNOPSIS:简短的命令执行语法简介<br>DESCRIPTION:较为完整的说明<br>OPTIONS:针对SYNOPSIS部分,所有可用的选项说明<br>COMMANDS:当这个程序在执行的时候,可以在此程序中执行的命令<br>FILES:这个程序所使用或参考或连接到的某些文件<br>SEE ALSO:这个命令有相关的其他说明<br>EXAMPLE:一些可以参考的使用例子<br>BUGS:是否有错误</p>
<h3 id="命令分类"><a href="#命令分类" class="headerlink" title="命令分类"></a>命令分类</h3><p>因为在linux中命令太多了,我们不可能全部都记住的,但是一些常用的还是需要记忆一下的,这样至少我们在使用linux的时候可以能够操作。还有就是我们在使用命令的时候,可以稍微联想一下这个命令对应的英文单词,这样子能够帮助我们进行记忆。比如cd(change directory),mv(move),rm(remove)等等。而且linux下的很多命令在其他的软件中都有相同的意思。比如在docker操作中,<code>docker rm</code>也是代表的删除等意思。其实外国人这些简写都是根据相对应的单词而来。当然我这里仅仅列举了较少的一些命令,更多的命令大家还是多多使用linux进行探索吧。</p>
<h4 id="文件与目录相关管理命令"><a href="#文件与目录相关管理命令" class="headerlink" title="文件与目录相关管理命令"></a>文件与目录相关管理命令</h4><p>这部分命令主要是如何新建文件,目录;如何复制,移动文件;如何查看文件,目录等等。常用的命令如下,具体的使用规则可以使用帮助进行查看。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">cd</span>:切换目录;<span class="built_in">ls</span>:列出目录和文件;<span class="built_in">cp</span>:复制;</span><br><span class="line"><span class="built_in">rm</span>:删除;<span class="built_in">mv</span>:移动;<span class="built_in">touch</span>:创建文件;</span><br><span class="line"><span class="built_in">mkdir</span>:创建文件夹;<span class="built_in">cat</span>:查看文件内容;more:查看文件内容(每次输出一页);</span><br><span class="line">less:查看文件内容(可以往前翻页)</span><br></pre></td></tr></table></figure>
<h4 id="磁盘和文件系统管理"><a href="#磁盘和文件系统管理" class="headerlink" title="磁盘和文件系统管理"></a>磁盘和文件系统管理</h4><p>对于文件系统,大家可以找本关于操作系统原理的书好好了解一下。了解常规的文件系统对于理解分布式文件系统会比较有帮助。文件系统在我们开发的过程中是很重要的,特别是现在容器话,集群化。在linux文件系统中mount(挂载点)的含义,日志系统如何记录文件的操作,inode等等。常用命令如下:</p>
<figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">df:查看磁盘整体的情况;</span><br><span class="line">du:查看某一个目录的容量情况;</span><br><span class="line">mount:挂载某一个文件系统;</span><br><span class="line">ln:创建连接文件(有点类似于windows的快捷方式,但是分为软连接和硬连接)</span><br></pre></td></tr></table></figure>
<h4 id="文件和文件系统的压缩和打包"><a href="#文件和文件系统的压缩和打包" class="headerlink" title="文件和文件系统的压缩和打包"></a>文件和文件系统的压缩和打包</h4><p>在平时我们linux的过程中,tar命令是一个经常出现的命令,掌握tar命令的使用很关键。因为tar命令不光能够打包文件,同时还能够解压文件。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">tar:打包文件;</span><br><span class="line">gzip:压缩文件;bzip2:压缩文件;</span><br><span class="line">dump:备份工具</span><br></pre></td></tr></table></figure>
<p>tar命令能够压缩文件是因为这个命令支持gzip和bzip2软件。对于tar命令我们要掌握以下常用的命令<br><strong>压缩</strong>:<code>tar -jcv -f filename.tar.bz2 被压缩的文件或目录名称</code><br><strong>查询</strong>:<code>tar -jtv -f filename.tar.bz2</code>,可以查看文件内的内容<br><strong>解压缩</strong>:<code>tar -jxv -f filename.tar.bz2 -C 被解压缩的目录</code></p>
<h4 id="其他常用命令"><a href="#其他常用命令" class="headerlink" title="其他常用命令"></a>其他常用命令</h4><p>其他常用的命令,比如管道和数据重定向等等,如果我们需要编写shell脚本,这些命令会非常有用。在linux中掌握shell脚本是很重要的,shell脚本能够帮助我们做一些自动化的事情,代替手工作业。</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">grep:匹配文件;<span class="built_in">sort</span>:排序;</span><br><span class="line"><span class="built_in">wc</span>:计数,比如统计文件内行数;<span class="built_in">history</span>:查看历史命令</span><br></pre></td></tr></table></figure>
<blockquote>
<p>在linux的学习中,更重要的还是要多用。关记忆这些命令是没有用的,在我们用linux的命令的过程中,仔细想想每个命令的用法,同时加深自己对于这些命令背后的原理以及linux系统本身的原理的理解,相信能够完全掌握linux的使用的</p>
</blockquote>
<h2 id="权限理解"><a href="#权限理解" class="headerlink" title="权限理解"></a>权限理解</h2><p>因为linux是支持多用户的,即多个人可以同时登陆一台linux系统上的。所以权限管理就变得非常重要。如果不考虑特殊权限的,文件和目录的权限主要分为三种,分别为可读(r)、可写(w)、可执行(x),对应的数字分别为4,2,1。<br>对于文件,这三个权限比较好理解。那么对于目录呢?<br><strong>目录可读</strong>:你可以查询改目录下的文件名数据,即可以利用ls这个命令将目录下的内容列表显示出来。<br><strong>目录可写</strong>:表示你具有更改该目录结构列表的权限,即在该目录下新建文件和目录;删除已经存在的文件与目录(不论文件的权限如何);将已经存在的文件或目录进行重命名;转移该目录内的文件,目录位置。<br><strong>目录可执行</strong>:目录的x代表用户能否进入该目录成为工作目录的用途。能不能进入某个目录只与该目录的x权限有关。同时如果没有某个目录的x权限,是无法执行该目录下的任何命令。</p>
<p>常用命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="built_in">chmod</span>:改变文件权限;<span class="built_in">chown</span>:改变文件所有者;<span class="built_in">chgrp</span>:改变文件所属用户组</span><br></pre></td></tr></table></figure>
<h2 id="linux常用目录介绍"><a href="#linux常用目录介绍" class="headerlink" title="linux常用目录介绍"></a>linux常用目录介绍</h2><p>在linux中,目录的用途是有具体的规范的,即FHS标准。这个规范的目的就是系统让用户可以了解已安装软件通常放置与哪个目录下。大家在平时的使用过程中最好是遵循这些规范。<br>FHS主要定义了三层目录,分别如下:<br><strong>/(root,根目录)</strong>:与开机系统有关<br><strong>/usr(UNIX software resource)</strong>:与软件安装/执行有关,可以看到usr是UNIX软件资源的缩写<br><strong>/var(variable)</strong>:与系统运作过程有关</p>
<h3 id="根目录分类"><a href="#根目录分类" class="headerlink" title="根目录分类"></a>根目录分类</h3><p>根目录一般情况下会有这些目录,目录的含义如下:<br><strong>/bin</strong>:主要放置在单用户维护模式下还能使用的命令,可以被root和一般用户使用,主要由cat,chmod,chown,date,mv,mkdir,cp,bash等<br><strong>/boot</strong>:开机会用到的文件,包括内核文件和开机的配置文件<br><strong>/dev</strong>:因为linux中所有的内容都是文件,而任何设备和接口设备都是以文件的形式存在于这个目录中。<br><strong>/etc</strong>:系统主要的配置文件。例如用户的账户密码,各种服务的起始文件等。<br><strong>/home</strong>:系统默认的用户主文件夹<br><strong>/lib</strong>:开机时会用到的函数库,以及在/bin,/sbin下面的命令会调用的函数库。<br><strong>/media</strong>:可删除的设备,包括软盘,光盘,DVD等<br><strong>/mnt</strong>:如果暂时挂载某些额外的设备,一般建议放置在这个目录中<br><strong>/opt</strong>:给第三方软件放置的目录<br><strong>/root</strong>:系统管理员的主文件夹<br><strong>/sbin</strong>:主要是开机过程中需要的,包括开机、修复、还原系统所需要的命令<br><strong>/srv(service的缩写)</strong>:一些网络服务启动之后,这些网络服务需要取用数据的目录。如WWW服务需要的网页数据就可以放置在/srv/www中<br><strong>/tmp</strong>:放置临时文件的目录,重要一般不建议放在这个目录里,因为这个目录会定时清理<br><strong>/proc</strong>:这个目录下的数据都在内存中,如系统的内核,进程,外部设备,网络状态等。</p>
<h3 id="usr下目录"><a href="#usr下目录" class="headerlink" title="/usr下目录"></a>/usr下目录</h3><p><strong>/usr/bin/</strong>:绝大部分的用户可使用命令<br><strong>/usr/include/</strong>:C/C++等程序语言的头文件与包含文件放置处。<br><strong>/usr/lib/</strong>:包含应用软件的函数库、目标文件以及不被一般用户惯用的执行文件或脚本<br><strong>/usr/local/</strong>:系统管理员在本机自行安装自己下载的软件,一般安装在这个目录下<br><strong>/usr/sbin/</strong>:非系统正常运行所需要的系统命令<br><strong>/usr/share/</strong>:放置共享文件的地方<br><strong>/usr/src/</strong>:一般源码放置在这个目录下,而内核源码一般放在/usr/src/linux下</p>
<h3 id="var-下目录"><a href="#var-下目录" class="headerlink" title="/var 下目录"></a>/var 下目录</h3><p><strong>/var/cache/</strong>:应用程序本身运行过程中会产生的一些暂存文件<br><strong>/var/lib/</strong>:程序本身执行的过程中,需要使用到的数据文件放置的目录,比如Mysql数据库放置在/var/lib/mysql,rpm数据库主要放到/var/lib/rpm下<br><strong>/var/log/</strong>:登录文件放置的目录,如/var/log/wtmp(记录登录者的信息)<br><strong>/var/mail/</strong>:放置个人邮箱的目录</p>
<blockquote>
<p>你有时候看到的linux系统目录可能不仅仅包括这些或者不存在其中的某些目录,这是因为linux系统版本,同时这也是个规范,有些linux开发厂商会依据这些规范做些修改。所以不一样也没有关系。</p>
</blockquote>
<p>总结:因为linux系统包括的内容太多了,这里我仅仅提到了linux系统下的冰山一角。比如如何关机,系统的等级,如何在线安装软件,如何离线安装软件,如何创建用户和用户组,SELinux等等内容。大家都可以通过这本书学到。</p>
]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>linux</tag>
<tag>操作系统</tag>
</tags>
</entry>
<entry>
<title>redis study</title>
<url>/redis-study.html</url>
<content><![CDATA[<h2 id="redis-数据结构"><a href="#redis-数据结构" class="headerlink" title="redis 数据结构"></a>redis 数据结构</h2><h3 id="简单动态字符串"><a href="#简单动态字符串" class="headerlink" title="简单动态字符串"></a>简单动态字符串</h3><p>redis中字符串是自己定义的结构,名为SDS。不是用的c语言的字符串。<br>SDS的定义如下:<br><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">struct</span> <span class="title">sdshdr</span> {</span></span><br><span class="line"> <span class="comment">// 记录buf数组中已使用字节的数量</span></span><br><span class="line"> <span class="type">int</span> len;</span><br><span class="line"> <span class="comment">// 记录buf数组中未使用字节的数量</span></span><br><span class="line"> <span class="type">int</span> <span class="built_in">free</span>;</span><br><span class="line"> <span class="comment">// 字节数组,用于存储字符串;其中最后一位保存'\0'字符</span></span><br><span class="line"> <span class="type">char</span> buf[];</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>为什么要自己实现,而不是复用c中对于字符串的定义?</p>
<ul>
<li>获取字符串长度,在O(1)复杂度获取</li>
<li>杜绝缓冲区溢出。SDS在进行修改时,会检查SDS空间是否满足要求。若不满足,则会进行扩容。</li>
<li>二进制安全,c中的字符串必须符合某种编码格式(比如ASCII),并且除了字符串的末尾之外,字符串中不能包含空字符。而sds使用len记录字符串长度,所以是二进制安全的。</li>
</ul>
<p>SDS的扩容机制:</p>
<ul>
<li>空间预分配(若对sds进行修改,先将len设置为需要的空间大小)<ul>
<li>若sds中len小于1MB,则将free设置为跟len一样的大小</li>
<li>若sds中len大于1MB,则将free设置为1MB</li>
</ul>
</li>
<li>惰性空间释放<ul>
<li>若释放sds中内容,则free中进行增加。实际占用的空间不释放;当然也提供了相应的api,在需要时,真正释放sds未使用空间。</li>
</ul>
</li>
</ul>
<h3 id="链表"><a href="#链表" class="headerlink" title="链表"></a>链表</h3><p>redis中链表结构<br>链表中节点定义<br><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> {</span></span><br><span class="line"> <span class="comment">// 前置节点</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">prev</span>;</span></span><br><span class="line"> <span class="comment">// 后置节点</span></span><br><span class="line"> <span class="class"><span class="keyword">struct</span> <span class="title">listNode</span> *<span class="title">next</span>;</span></span><br><span class="line"> <span class="comment">// 节点值</span></span><br><span class="line"> <span class="type">void</span> *value;</span><br><span class="line">} listNode;</span><br></pre></td></tr></table></figure><br>链表定义<br><figure class="highlight c"><table><tr><td class="code"><pre><span class="line"><span class="keyword">typedef</span> <span class="class"><span class="keyword">struct</span> <span class="title">list</span> {</span></span><br><span class="line"> <span class="comment">// 表头节点</span></span><br><span class="line"> listNode *head;</span><br><span class="line"> <span class="comment">// 表尾节点</span></span><br><span class="line"> listNode *tail;</span><br><span class="line"> <span class="comment">// 链表包含的节点数量</span></span><br><span class="line"> <span class="type">unsigned</span> <span class="type">long</span> len;</span><br><span class="line"> <span class="comment">// 节点值复制函数</span></span><br><span class="line"> <span class="type">void</span> *(*dup) (<span class="type">void</span> *ptr);</span><br><span class="line"> <span class="comment">// 节点值释放函数</span></span><br><span class="line"> <span class="type">void</span> (*<span class="built_in">free</span>) (<span class="type">void</span> *ptr);</span><br><span class="line"> <span class="comment">// 节点值对比函数</span></span><br><span class="line"> <span class="type">int</span> (*match) (<span class="type">void</span> *ptr, <span class="type">void</span> *key);</span><br><span class="line">} <span class="built_in">list</span>;</span><br></pre></td></tr></table></figure><br>注:链表节点使用 void*指针保存节点值,并且通过list中的dup、free、match三个属性为节点值设置类型特定函数,所以链表可以保存各种不同类型的值。</p>
<h3 id="字典"><a href="#字典" class="headerlink" title="字典"></a>字典</h3><h3 id="跳跃表"><a href="#跳跃表" class="headerlink" title="跳跃表"></a>跳跃表</h3><h3 id="整数集合"><a href="#整数集合" class="headerlink" title="整数集合"></a>整数集合</h3><h3 id="压缩列表"><a href="#压缩列表" class="headerlink" title="压缩列表"></a>压缩列表</h3><h3 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h3>]]></content>
<categories>
<category>redis</category>
</categories>
<tags>
<tag>redis</tag>
</tags>
</entry>
<entry>
<title>pip以及conda镜像源修改及命令使用</title>
<url>/pip%E4%BB%A5%E5%8F%8Aconda%E9%95%9C%E5%83%8F%E6%BA%90%E4%BF%AE%E6%94%B9%E5%8F%8A%E5%91%BD%E4%BB%A4%E4%BD%BF%E7%94%A8.html</url>
<content><![CDATA[<h1 id="Python包的安装"><a href="#Python包的安装" class="headerlink" title="Python包的安装"></a>Python包的安装</h1><p>在国内环境下,因为网络原因,所以Python下很多包安装不了或者安装的速度很慢。这里主要介绍下如何修改conda以及pip对应的镜像源。</p>
<h2 id="pip"><a href="#pip" class="headerlink" title="pip"></a>pip</h2><p>pip主要是用来管理python包的工具,类似于Maven工具。</p>
<h3 id="临时修改pip安装源"><a href="#临时修改pip安装源" class="headerlink" title="临时修改pip安装源"></a>临时修改pip安装源</h3><p>比如我们要安装gevent包,我们可以输入一下命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pip install -i https://pypi.tuna.tsinghua.edu.cn/simple gevent</span><br></pre></td></tr></table></figure>
<p>这样就会从清华这边的镜像去安装gevent库.其中<code>-i</code>参数指定了使用清华的pip源</p>
<p>有时候可能需要添加受信源,命令如下:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">pip install packagename -i http://pypi.douban.com/simple --trusted-host pypi.douban.com</span><br></pre></td></tr></table></figure>
<p>其中<code>--trusted-host</code> 参数是指设置为受信源,否则在安全性较高的连接下是连接不上的</p>
<h3 id="永久修改"><a href="#永久修改" class="headerlink" title="永久修改"></a>永久修改</h3><p>在用户根目录(~,而非系统根目录 / )下添加配置~/.pip/pip.conf目录添加可信源,如果目录文件不存在,可直接创建。写入如下内容:</p>
<figure class="highlight yaml"><table><tr><td class="code"><pre><span class="line">[<span class="string">global</span>]</span><br><span class="line"><span class="string">index-url=http://pypi.douban.com/simple</span></span><br><span class="line"><span class="string">trusted-host</span> <span class="string">=</span> <span class="string">pypi.douban.com</span> </span><br></pre></td></tr></table></figure>
<p>这里添加的是豆瓣源,也可以添加清华源</p>
<h2 id="conda使用及源修改"><a href="#conda使用及源修改" class="headerlink" title="conda使用及源修改"></a>conda使用及源修改</h2><p>conda是Anaconda中用来安装python包的工具。在Anaconda中将镜像分为两类,一类是官方的python包,放在anaconda中;另一类是第三方的python包,放在conda-forge中。</p>
<p>采用conda 安装python包时,可以使用以下命令:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">conda install -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/pkgs/free/linux-64 joblib</span><br></pre></td></tr></table></figure>
<p>其中参数<code>-c</code>指定了镜像源的通道,这里实在anaconda官方中安装joblib</p>
<p>或者</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">conda install -c https://mirrors.tuna.tsinghua.edu.cn/anaconda/cloud/conda-forge/linux-64 jieba</span><br></pre></td></tr></table></figure>
<p>这里是在conda-forge中安装jieba第三方的Python包</p>
]]></content>
<categories>
<category>python</category>
</categories>
<tags>
<tag>pip</tag>
<tag>conda</tag>
</tags>
</entry>
<entry>
<title>docker与tensorflow结合使用</title>
<url>/docker%E4%B8%8Etensorflow%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8.html</url>
<content><![CDATA[<p>最近这段时间一直在学习docker的使用,以及如何在docker中使用tensorflow.今天就把在docker中如何使用tensorflow记录一下.</p>
<h1 id="docker安装"><a href="#docker安装" class="headerlink" title="docker安装"></a>docker安装</h1><p>我是把docker安装在centos 7.4操作系统上面,在vmware中装的centos,vmware中安装centos很简单.具体的网络配置可以参考<a href="https://segmentfault.com/a/1190000008743806">vmware nat配置</a>.docker安装很简单,找到docker官网,直接按照上面的步骤安装即可.运行<code>docker version</code>查看版本如下</p>
<img src="/docker%E4%B8%8Etensorflow%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/docker-version.png" class="">
<p>因为docker 采用的是客户端/服务端的结构,所以这里可以看到client以及server,它们分别都有版本号.</p>
<h1 id="tensorflow"><a href="#tensorflow" class="headerlink" title="tensorflow"></a>tensorflow</h1><p>在docker中运行tensorflow的第一步就是要找到自己需要的镜像,我们可以去<a href="https://hub.docker.com">docker hub</a>找到自己需要的tensorflow镜像.tensorflow的镜像主要分两类,一种是在CPU上面跑的,还有一种是在GPU上面跑的,如果需要GPU的,那么还需要安装<strong>nvidia-docker</strong>.这里我使用的是CPU版本的.当然我们还需要选择具体的tensorflow版本.这里我拉取的命令如下:</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker pull tensorflow/tensorflow:1.9.0-devel-py3</span><br></pre></td></tr></table></figure>
<p>拉取成功之后,运行<code>docker images</code>可以看到有tensorflow镜像.</p>
<h1 id="tensorflow在docker中使用"><a href="#tensorflow在docker中使用" class="headerlink" title="tensorflow在docker中使用"></a>tensorflow在docker中使用</h1><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run -it -p 8888:8888 --name tf-1.9 tensorflow/tensorflow:1.9.0-devel-py3</span><br></pre></td></tr></table></figure>
<p>运行上面的命令,在容器中启动镜像.<code>-p</code>表示指定端口映射,即将本机的8888端口映射到容器的8888端口.<code>--name</code>用来指定容器的名字为<code>tf-1.9</code>.因为这里采用的镜像是devel模式的,所以默认不启动jupyter.如果想使用默认启动jupyter的镜像,那么直接拉取不带devel的镜像就可以.即拉取最近的镜像<code>docker pull tensorflow/tensorflow</code><br>启动之后,我们就进入了容器,<code>ls /</code> 查看容器根目录内容,可以看到有<code>run_jupyter.sh</code>文件.运行此文件,即在根目录下执行<code>./run_jupyter.sh --allow-root</code>,<code>--allow-root</code>参数是因为jupyter启动不推荐使用root,这里是主动允许使用root.然后在浏览器中就可以访问jupyter的内容了.<br><img src="/docker%E4%B8%8Etensorflow%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/jupyter.png" class=""></p>
<h1 id="创建自己的镜像"><a href="#创建自己的镜像" class="headerlink" title="创建自己的镜像"></a>创建自己的镜像</h1><p>上面仅仅是跑了一个什么都没有的镜像,如果我们需要在镜像里面跑我们的深度学习程序怎么办呢?这首先做的第一步就是要制作我们自己的镜像.这里我们跑一个简单的mnist数据集,程序可以直接去tensorflow上面找一个例子程序.这里我的程序如下:</p>
<figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># Copyright 2015 The TensorFlow Authors. All Rights Reserved.</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Licensed under the Apache License, Version 2.0 (the "License");</span></span><br><span class="line"><span class="comment"># you may not use this file except in compliance with the License.</span></span><br><span class="line"><span class="comment"># You may obtain a copy of the License at</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># http://www.apache.org/licenses/LICENSE-2.0</span></span><br><span class="line"><span class="comment">#</span></span><br><span class="line"><span class="comment"># Unless required by applicable law or agreed to in writing, software</span></span><br><span class="line"><span class="comment"># distributed under the License is distributed on an "AS IS" BASIS,</span></span><br><span class="line"><span class="comment"># WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.</span></span><br><span class="line"><span class="comment"># See the License for the specific language governing permissions and</span></span><br><span class="line"><span class="comment"># limitations under the License.</span></span><br><span class="line"><span class="comment"># ==============================================================================</span></span><br><span class="line"></span><br><span class="line"><span class="string">"""Simple, end-to-end, LeNet-5-like convolutional MNIST model example.</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string">This should achieve a test error of 0.7%. Please keep this model as simple and</span></span><br><span class="line"><span class="string">linear as possible, it is meant as a tutorial for simple convolutional models.</span></span><br><span class="line"><span class="string">Run with --self_test on the command line to execute a short self-test.</span></span><br><span class="line"><span class="string">"""</span></span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> absolute_import</span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> division</span><br><span class="line"><span class="keyword">from</span> __future__ <span class="keyword">import</span> print_function</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> argparse</span><br><span class="line"><span class="keyword">import</span> gzip</span><br><span class="line"><span class="keyword">import</span> os</span><br><span class="line"><span class="keyword">import</span> sys</span><br><span class="line"><span class="keyword">import</span> time</span><br><span class="line"><span class="keyword">import</span> logging</span><br><span class="line"></span><br><span class="line"><span class="keyword">import</span> numpy</span><br><span class="line"><span class="keyword">from</span> six.moves <span class="keyword">import</span> urllib</span><br><span class="line"><span class="keyword">from</span> six.moves <span class="keyword">import</span> xrange <span class="comment"># pylint: disable=redefined-builtin</span></span><br><span class="line"><span class="keyword">import</span> tensorflow <span class="keyword">as</span> tf</span><br><span class="line"></span><br><span class="line"><span class="comment"># CVDF mirror of http://yann.lecun.com/exdb/mnist/</span></span><br><span class="line"><span class="comment"># 如果WORK_DIRECTORY中没有需要的数据,则从此地址下载数据</span></span><br><span class="line">SOURCE_URL = <span class="string">'https://storage.googleapis.com/cvdf-datasets/mnist/'</span></span><br><span class="line"><span class="comment"># 训练数据位置</span></span><br><span class="line"><span class="comment"># WORK_DIRECTORY = 'data'</span></span><br><span class="line">WORK_DIRECTORY = <span class="string">'./MNIST-data'</span></span><br><span class="line">IMAGE_SIZE = <span class="number">28</span></span><br><span class="line">NUM_CHANNELS = <span class="number">1</span></span><br><span class="line">PIXEL_DEPTH = <span class="number">255</span></span><br><span class="line">NUM_LABELS = <span class="number">10</span></span><br><span class="line">VALIDATION_SIZE = <span class="number">5000</span> <span class="comment"># Size of the validation set.</span></span><br><span class="line">SEED = <span class="number">66478</span> <span class="comment"># Set to None for random seed.</span></span><br><span class="line">BATCH_SIZE = <span class="number">64</span></span><br><span class="line">NUM_EPOCHS = <span class="number">10</span></span><br><span class="line">EVAL_BATCH_SIZE = <span class="number">64</span></span><br><span class="line">EVAL_FREQUENCY = <span class="number">100</span> <span class="comment"># Number of steps between evaluations.</span></span><br><span class="line"></span><br><span class="line">FLAGS = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 打印信息设置</span></span><br><span class="line"><span class="comment"># logging.basicConfig(format='%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s',</span></span><br><span class="line"><span class="comment"># level=logging.DEBUG)</span></span><br><span class="line">logging.basicConfig(level=logging.DEBUG, <span class="comment"># 控制台打印的日志级别</span></span><br><span class="line"> filename=<span class="string">'cnn_mnist.log'</span>,</span><br><span class="line"> filemode=<span class="string">'a'</span>, <span class="comment"># 模式,有w和a,w就是写模式,每次都会重新写日志,覆盖之前的日志</span></span><br><span class="line"> <span class="comment"># a是追加模式,默认如果不写的话,就是追加模式</span></span><br><span class="line"> <span class="built_in">format</span>=</span><br><span class="line"> <span class="string">'%(asctime)s - %(pathname)s[line:%(lineno)d] - %(levelname)s: %(message)s'</span></span><br><span class="line"> <span class="comment"># 日志格式</span></span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">data_type</span>():</span><br><span class="line"> <span class="string">"""Return the type of the activations, weights, and placeholder variables."""</span></span><br><span class="line"> <span class="keyword">if</span> FLAGS.use_fp16:</span><br><span class="line"> <span class="keyword">return</span> tf.float16</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="keyword">return</span> tf.float32</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">maybe_download</span>(<span class="params">filename</span>):</span><br><span class="line"> <span class="string">"""Download the data from Yann's website, unless it's already here."""</span></span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> tf.gfile.Exists(WORK_DIRECTORY):</span><br><span class="line"> tf.gfile.MakeDirs(WORK_DIRECTORY)</span><br><span class="line"> filepath = os.path.join(WORK_DIRECTORY, filename)</span><br><span class="line"> <span class="keyword">if</span> <span class="keyword">not</span> tf.gfile.Exists(filepath):</span><br><span class="line"> filepath, _ = urllib.request.urlretrieve(SOURCE_URL + filename, filepath)</span><br><span class="line"> <span class="keyword">with</span> tf.gfile.GFile(filepath) <span class="keyword">as</span> f:</span><br><span class="line"> size = f.size()</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Successfully downloaded'</span>, filename, size, <span class="string">'bytes.'</span>)</span><br><span class="line"> <span class="keyword">return</span> filepath</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">extract_data</span>(<span class="params">filename, num_images</span>):</span><br><span class="line"> <span class="string">"""Extract the images into a 4D tensor [image index, y, x, channels].</span></span><br><span class="line"><span class="string"></span></span><br><span class="line"><span class="string"> Values are rescaled from [0, 255] down to [-0.5, 0.5].</span></span><br><span class="line"><span class="string"> """</span></span><br><span class="line"> logging.info(<span class="string">'Extracting'</span> + filename)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Extracting'</span>, filename)</span><br><span class="line"> <span class="keyword">with</span> gzip.<span class="built_in">open</span>(filename) <span class="keyword">as</span> bytestream:</span><br><span class="line"> bytestream.read(<span class="number">16</span>)</span><br><span class="line"> buf = bytestream.read(IMAGE_SIZE * IMAGE_SIZE * num_images * NUM_CHANNELS)</span><br><span class="line"> data = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.float32)</span><br><span class="line"> data = (data - (PIXEL_DEPTH / <span class="number">2.0</span>)) / PIXEL_DEPTH</span><br><span class="line"> data = data.reshape(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS)</span><br><span class="line"> <span class="keyword">return</span> data</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">extract_labels</span>(<span class="params">filename, num_images</span>):</span><br><span class="line"> <span class="string">"""Extract the labels into a vector of int64 label IDs."""</span></span><br><span class="line"> logging.info(<span class="string">'Extracting'</span> + filename)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Extracting'</span>, filename)</span><br><span class="line"> <span class="keyword">with</span> gzip.<span class="built_in">open</span>(filename) <span class="keyword">as</span> bytestream:</span><br><span class="line"> bytestream.read(<span class="number">8</span>)</span><br><span class="line"> buf = bytestream.read(<span class="number">1</span> * num_images)</span><br><span class="line"> labels = numpy.frombuffer(buf, dtype=numpy.uint8).astype(numpy.int64)</span><br><span class="line"> <span class="keyword">return</span> labels</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">fake_data</span>(<span class="params">num_images</span>):</span><br><span class="line"> <span class="string">"""Generate a fake dataset that matches the dimensions of MNIST."""</span></span><br><span class="line"> data = numpy.ndarray(</span><br><span class="line"> shape=(num_images, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS),</span><br><span class="line"> dtype=numpy.float32)</span><br><span class="line"> labels = numpy.zeros(shape=(num_images,), dtype=numpy.int64)</span><br><span class="line"> <span class="keyword">for</span> image <span class="keyword">in</span> xrange(num_images):</span><br><span class="line"> label = image % <span class="number">2</span></span><br><span class="line"> data[image, :, :, <span class="number">0</span>] = label - <span class="number">0.5</span></span><br><span class="line"> labels[image] = label</span><br><span class="line"> <span class="keyword">return</span> data, labels</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">error_rate</span>(<span class="params">predictions, labels</span>):</span><br><span class="line"> <span class="string">"""Return the error rate based on dense predictions and sparse labels."""</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">100.0</span> - (</span><br><span class="line"> <span class="number">100.0</span> *</span><br><span class="line"> numpy.<span class="built_in">sum</span>(numpy.argmax(predictions, <span class="number">1</span>) == labels) /</span><br><span class="line"> predictions.shape[<span class="number">0</span>])</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">main</span>(<span class="params">_</span>):</span><br><span class="line"> <span class="keyword">if</span> FLAGS.self_test:</span><br><span class="line"> logging.info(<span class="string">'Running self-test.'</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Running self-test.'</span>)</span><br><span class="line"> train_data, train_labels = fake_data(<span class="number">256</span>)</span><br><span class="line"> validation_data, validation_labels = fake_data(EVAL_BATCH_SIZE)</span><br><span class="line"> test_data, test_labels = fake_data(EVAL_BATCH_SIZE)</span><br><span class="line"> num_epochs = <span class="number">1</span></span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> <span class="comment"># Get the data.</span></span><br><span class="line"> train_data_filename = maybe_download(<span class="string">'train-images-idx3-ubyte.gz'</span>)</span><br><span class="line"> train_labels_filename = maybe_download(<span class="string">'train-labels-idx1-ubyte.gz'</span>)</span><br><span class="line"> test_data_filename = maybe_download(<span class="string">'t10k-images-idx3-ubyte.gz'</span>)</span><br><span class="line"> test_labels_filename = maybe_download(<span class="string">'t10k-labels-idx1-ubyte.gz'</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Extract it into numpy arrays.</span></span><br><span class="line"> train_data = extract_data(train_data_filename, <span class="number">60000</span>)</span><br><span class="line"> train_labels = extract_labels(train_labels_filename, <span class="number">60000</span>)</span><br><span class="line"> test_data = extract_data(test_data_filename, <span class="number">10000</span>)</span><br><span class="line"> test_labels = extract_labels(test_labels_filename, <span class="number">10000</span>)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Generate a validation set.</span></span><br><span class="line"> validation_data = train_data[:VALIDATION_SIZE, ...]</span><br><span class="line"> validation_labels = train_labels[:VALIDATION_SIZE]</span><br><span class="line"> train_data = train_data[VALIDATION_SIZE:, ...]</span><br><span class="line"> train_labels = train_labels[VALIDATION_SIZE:]</span><br><span class="line"> num_epochs = NUM_EPOCHS</span><br><span class="line"> train_size = train_labels.shape[<span class="number">0</span>]</span><br><span class="line"></span><br><span class="line"> <span class="comment"># This is where training samples and labels are fed to the graph.</span></span><br><span class="line"> <span class="comment"># These placeholder nodes will be fed a batch of training data at each</span></span><br><span class="line"> <span class="comment"># training step using the {feed_dict} argument to the Run() call below.</span></span><br><span class="line"> train_data_node = tf.placeholder(</span><br><span class="line"> data_type(),</span><br><span class="line"> shape=(BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))</span><br><span class="line"> train_labels_node = tf.placeholder(tf.int64, shape=(BATCH_SIZE,))</span><br><span class="line"> eval_data = tf.placeholder(</span><br><span class="line"> data_type(),</span><br><span class="line"> shape=(EVAL_BATCH_SIZE, IMAGE_SIZE, IMAGE_SIZE, NUM_CHANNELS))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># The variables below hold all the trainable weights. They are passed an</span></span><br><span class="line"> <span class="comment"># initial value which will be assigned when we call:</span></span><br><span class="line"> <span class="comment"># {tf.global_variables_initializer().run()}</span></span><br><span class="line"> conv1_weights = tf.Variable(</span><br><span class="line"> tf.truncated_normal([<span class="number">5</span>, <span class="number">5</span>, NUM_CHANNELS, <span class="number">32</span>], <span class="comment"># 5x5 filter, depth 32.</span></span><br><span class="line"> stddev=<span class="number">0.1</span>,</span><br><span class="line"> seed=SEED, dtype=data_type()))</span><br><span class="line"> conv1_biases = tf.Variable(tf.zeros([<span class="number">32</span>], dtype=data_type()))</span><br><span class="line"> conv2_weights = tf.Variable(tf.truncated_normal(</span><br><span class="line"> [<span class="number">5</span>, <span class="number">5</span>, <span class="number">32</span>, <span class="number">64</span>], stddev=<span class="number">0.1</span>,</span><br><span class="line"> seed=SEED, dtype=data_type()))</span><br><span class="line"> conv2_biases = tf.Variable(tf.constant(<span class="number">0.1</span>, shape=[<span class="number">64</span>], dtype=data_type()))</span><br><span class="line"> fc1_weights = tf.Variable( <span class="comment"># fully connected, depth 512.</span></span><br><span class="line"> tf.truncated_normal([IMAGE_SIZE // <span class="number">4</span> * IMAGE_SIZE // <span class="number">4</span> * <span class="number">64</span>, <span class="number">512</span>],</span><br><span class="line"> stddev=<span class="number">0.1</span>,</span><br><span class="line"> seed=SEED,</span><br><span class="line"> dtype=data_type()))</span><br><span class="line"> fc1_biases = tf.Variable(tf.constant(<span class="number">0.1</span>, shape=[<span class="number">512</span>], dtype=data_type()))</span><br><span class="line"> fc2_weights = tf.Variable(tf.truncated_normal([<span class="number">512</span>, NUM_LABELS],</span><br><span class="line"> stddev=<span class="number">0.1</span>,</span><br><span class="line"> seed=SEED,</span><br><span class="line"> dtype=data_type()))</span><br><span class="line"> fc2_biases = tf.Variable(tf.constant(</span><br><span class="line"> <span class="number">0.1</span>, shape=[NUM_LABELS], dtype=data_type()))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># We will replicate the model structure for the training subgraph, as well</span></span><br><span class="line"> <span class="comment"># as the evaluation subgraphs, while sharing the trainable parameters.</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">model</span>(<span class="params">data, train=<span class="literal">False</span></span>):</span><br><span class="line"> <span class="string">"""The Model definition."""</span></span><br><span class="line"> <span class="comment"># 2D convolution, with 'SAME' padding (i.e. the output feature map has</span></span><br><span class="line"> <span class="comment"># the same size as the input). Note that {strides} is a 4D array whose</span></span><br><span class="line"> <span class="comment"># shape matches the data layout: [image index, y, x, depth].</span></span><br><span class="line"> conv = tf.nn.conv2d(data,</span><br><span class="line"> conv1_weights,</span><br><span class="line"> strides=[<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>],</span><br><span class="line"> padding=<span class="string">'SAME'</span>)</span><br><span class="line"> <span class="comment"># Bias and rectified linear non-linearity.</span></span><br><span class="line"> relu = tf.nn.relu(tf.nn.bias_add(conv, conv1_biases))</span><br><span class="line"> <span class="comment"># Max pooling. The kernel size spec {ksize} also follows the layout of</span></span><br><span class="line"> <span class="comment"># the data. Here we have a pooling window of 2, and a stride of 2.</span></span><br><span class="line"> pool = tf.nn.max_pool(relu,</span><br><span class="line"> ksize=[<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">1</span>],</span><br><span class="line"> strides=[<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">1</span>],</span><br><span class="line"> padding=<span class="string">'SAME'</span>)</span><br><span class="line"> conv = tf.nn.conv2d(pool,</span><br><span class="line"> conv2_weights,</span><br><span class="line"> strides=[<span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>, <span class="number">1</span>],</span><br><span class="line"> padding=<span class="string">'SAME'</span>)</span><br><span class="line"> relu = tf.nn.relu(tf.nn.bias_add(conv, conv2_biases))</span><br><span class="line"> pool = tf.nn.max_pool(relu,</span><br><span class="line"> ksize=[<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">1</span>],</span><br><span class="line"> strides=[<span class="number">1</span>, <span class="number">2</span>, <span class="number">2</span>, <span class="number">1</span>],</span><br><span class="line"> padding=<span class="string">'SAME'</span>)</span><br><span class="line"> <span class="comment"># Reshape the feature map cuboid into a 2D matrix to feed it to the</span></span><br><span class="line"> <span class="comment"># fully connected layers.</span></span><br><span class="line"> pool_shape = pool.get_shape().as_list()</span><br><span class="line"> reshape = tf.reshape(</span><br><span class="line"> pool,</span><br><span class="line"> [pool_shape[<span class="number">0</span>], pool_shape[<span class="number">1</span>] * pool_shape[<span class="number">2</span>] * pool_shape[<span class="number">3</span>]])</span><br><span class="line"> <span class="comment"># Fully connected layer. Note that the '+' operation automatically</span></span><br><span class="line"> <span class="comment"># broadcasts the biases.</span></span><br><span class="line"> hidden = tf.nn.relu(tf.matmul(reshape, fc1_weights) + fc1_biases)</span><br><span class="line"> <span class="comment"># Add a 50% dropout during training only. Dropout also scales</span></span><br><span class="line"> <span class="comment"># activations such that no rescaling is needed at evaluation time.</span></span><br><span class="line"> <span class="keyword">if</span> train:</span><br><span class="line"> hidden = tf.nn.dropout(hidden, <span class="number">0.5</span>, seed=SEED)</span><br><span class="line"> <span class="keyword">return</span> tf.matmul(hidden, fc2_weights) + fc2_biases</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Training computation: logits + cross-entropy loss.</span></span><br><span class="line"> logits = model(train_data_node, <span class="literal">True</span>)</span><br><span class="line"> loss = tf.reduce_mean(tf.nn.sparse_softmax_cross_entropy_with_logits(</span><br><span class="line"> labels=train_labels_node, logits=logits))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># L2 regularization for the fully connected parameters.</span></span><br><span class="line"> regularizers = (tf.nn.l2_loss(fc1_weights) + tf.nn.l2_loss(fc1_biases) +</span><br><span class="line"> tf.nn.l2_loss(fc2_weights) + tf.nn.l2_loss(fc2_biases))</span><br><span class="line"> <span class="comment"># Add the regularization term to the loss.</span></span><br><span class="line"> loss += <span class="number">5e-4</span> * regularizers</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Optimizer: set up a variable that's incremented once per batch and</span></span><br><span class="line"> <span class="comment"># controls the learning rate decay.</span></span><br><span class="line"> batch = tf.Variable(<span class="number">0</span>, dtype=data_type())</span><br><span class="line"> <span class="comment"># Decay once per epoch, using an exponential schedule starting at 0.01.</span></span><br><span class="line"> learning_rate = tf.train.exponential_decay(</span><br><span class="line"> <span class="number">0.01</span>, <span class="comment"># Base learning rate.</span></span><br><span class="line"> batch * BATCH_SIZE, <span class="comment"># Current index into the dataset.</span></span><br><span class="line"> train_size, <span class="comment"># Decay step.</span></span><br><span class="line"> <span class="number">0.95</span>, <span class="comment"># Decay rate.</span></span><br><span class="line"> staircase=<span class="literal">True</span>)</span><br><span class="line"> <span class="comment"># Use simple momentum for the optimization.</span></span><br><span class="line"> optimizer = tf.train.MomentumOptimizer(learning_rate,</span><br><span class="line"> <span class="number">0.9</span>).minimize(loss,</span><br><span class="line"> global_step=batch)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Predictions for the current training minibatch.</span></span><br><span class="line"> train_prediction = tf.nn.softmax(logits)</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Predictions for the test and validation, which we'll compute less often.</span></span><br><span class="line"> eval_prediction = tf.nn.softmax(model(eval_data))</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Small utility function to evaluate a dataset by feeding batches of data to</span></span><br><span class="line"> <span class="comment"># {eval_data} and pulling the results from {eval_predictions}.</span></span><br><span class="line"> <span class="comment"># Saves memory and enables this to run on smaller GPUs.</span></span><br><span class="line"> <span class="keyword">def</span> <span class="title function_">eval_in_batches</span>(<span class="params">data, sess</span>):</span><br><span class="line"> <span class="string">"""Get all predictions for a dataset by running it in small batches."""</span></span><br><span class="line"> size = data.shape[<span class="number">0</span>]</span><br><span class="line"> <span class="keyword">if</span> size < EVAL_BATCH_SIZE:</span><br><span class="line"> logging.error(<span class="string">"batch size for evals larger than dataset: %d"</span> % size)</span><br><span class="line"> <span class="keyword">raise</span> ValueError(<span class="string">"batch size for evals larger than dataset: %d"</span> % size)</span><br><span class="line"> predictions = numpy.ndarray(shape=(size, NUM_LABELS), dtype=numpy.float32)</span><br><span class="line"> <span class="keyword">for</span> begin <span class="keyword">in</span> xrange(<span class="number">0</span>, size, EVAL_BATCH_SIZE):</span><br><span class="line"> end = begin + EVAL_BATCH_SIZE</span><br><span class="line"> <span class="keyword">if</span> end <= size:</span><br><span class="line"> predictions[begin:end, :] = sess.run(</span><br><span class="line"> eval_prediction,</span><br><span class="line"> feed_dict={eval_data: data[begin:end, ...]})</span><br><span class="line"> <span class="keyword">else</span>:</span><br><span class="line"> batch_predictions = sess.run(</span><br><span class="line"> eval_prediction,</span><br><span class="line"> feed_dict={eval_data: data[-EVAL_BATCH_SIZE:, ...]})</span><br><span class="line"> predictions[begin:, :] = batch_predictions[begin - size:, :]</span><br><span class="line"> <span class="keyword">return</span> predictions</span><br><span class="line"></span><br><span class="line"> <span class="comment"># Create a local session to run the training.</span></span><br><span class="line"> start_time = time.time()</span><br><span class="line"> <span class="keyword">with</span> tf.Session() <span class="keyword">as</span> sess:</span><br><span class="line"> <span class="comment"># Run all the initializers to prepare the trainable parameters.</span></span><br><span class="line"> tf.global_variables_initializer().run()</span><br><span class="line"> logging.info(<span class="string">'Initialized!'</span>)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Initialized!'</span>)</span><br><span class="line"> <span class="comment"># Loop through training steps.</span></span><br><span class="line"> <span class="keyword">for</span> step <span class="keyword">in</span> xrange(<span class="built_in">int</span>(num_epochs * train_size) // BATCH_SIZE):</span><br><span class="line"> <span class="comment"># Compute the offset of the current minibatch in the data.</span></span><br><span class="line"> <span class="comment"># Note that we could use better randomization across epochs.</span></span><br><span class="line"> offset = (step * BATCH_SIZE) % (train_size - BATCH_SIZE)</span><br><span class="line"> batch_data = train_data[offset:(offset + BATCH_SIZE), ...]</span><br><span class="line"> batch_labels = train_labels[offset:(offset + BATCH_SIZE)]</span><br><span class="line"> <span class="comment"># This dictionary maps the batch data (as a numpy array) to the</span></span><br><span class="line"> <span class="comment"># node in the graph it should be fed to.</span></span><br><span class="line"> feed_dict = {train_data_node: batch_data,</span><br><span class="line"> train_labels_node: batch_labels}</span><br><span class="line"> <span class="comment"># Run the optimizer to update weights.</span></span><br><span class="line"> sess.run(optimizer, feed_dict=feed_dict)</span><br><span class="line"> <span class="comment"># print some extra information once reach the evaluation frequency</span></span><br><span class="line"> <span class="keyword">if</span> step % EVAL_FREQUENCY == <span class="number">0</span>:</span><br><span class="line"> <span class="comment"># fetch some extra nodes' data</span></span><br><span class="line"> l, lr, predictions = sess.run([loss, learning_rate, train_prediction],</span><br><span class="line"> feed_dict=feed_dict)</span><br><span class="line"> elapsed_time = time.time() - start_time</span><br><span class="line"> start_time = time.time()</span><br><span class="line"> logging.info(<span class="string">'Step %d (epoch %.2f), %.1f ms'</span> %(step, <span class="built_in">float</span>(step) * BATCH_SIZE / train_size, <span class="number">1000</span> * elapsed_time / EVAL_FREQUENCY))</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Step %d (epoch %.2f), %.1f ms'</span> %</span><br><span class="line"> (step, <span class="built_in">float</span>(step) * BATCH_SIZE / train_size,</span><br><span class="line"> <span class="number">1000</span> * elapsed_time / EVAL_FREQUENCY))</span><br><span class="line"> logging.info(<span class="string">'Minibatch loss: %.3f, learning rate: %.6f'</span> % (l, lr))</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Minibatch loss: %.3f, learning rate: %.6f'</span> % (l, lr))</span><br><span class="line"> logging.info(<span class="string">'Minibatch error: %.1f%%'</span> % error_rate(predictions, batch_labels))</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Minibatch error: %.1f%%'</span> % error_rate(predictions, batch_labels))</span><br><span class="line"> logging.info(<span class="string">'Validation error: %.1f%%'</span> % error_rate(eval_in_batches(validation_data, sess), validation_labels))</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Validation error: %.1f%%'</span> % error_rate(</span><br><span class="line"> eval_in_batches(validation_data, sess), validation_labels))</span><br><span class="line"> sys.stdout.flush()</span><br><span class="line"> <span class="comment"># Finally print the result!</span></span><br><span class="line"> test_error = error_rate(eval_in_batches(test_data, sess), test_labels)</span><br><span class="line"> logging.info(<span class="string">'Test error: %.1f%%'</span> % test_error)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'Test error: %.1f%%'</span> % test_error)</span><br><span class="line"> <span class="keyword">if</span> FLAGS.self_test:</span><br><span class="line"> logging.info(<span class="string">'test_error'</span> + test_error)</span><br><span class="line"> <span class="built_in">print</span>(<span class="string">'test_error'</span>, test_error)</span><br><span class="line"> <span class="keyword">assert</span> test_error == <span class="number">0.0</span>, <span class="string">'expected 0.0 test_error, got %.2f'</span> % (</span><br><span class="line"> test_error,)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> __name__ == <span class="string">'__main__'</span>:</span><br><span class="line"> parser = argparse.ArgumentParser()</span><br><span class="line"> parser.add_argument(</span><br><span class="line"> <span class="string">'--use_fp16'</span>,</span><br><span class="line"> default=<span class="literal">False</span>,</span><br><span class="line"> <span class="built_in">help</span>=<span class="string">'Use half floats instead of full floats if True.'</span>,</span><br><span class="line"> action=<span class="string">'store_true'</span>)</span><br><span class="line"> parser.add_argument(</span><br><span class="line"> <span class="string">'--self_test'</span>,</span><br><span class="line"> default=<span class="literal">False</span>,</span><br><span class="line"> action=<span class="string">'store_true'</span>,</span><br><span class="line"> <span class="built_in">help</span>=<span class="string">'True if running a self test.'</span>)</span><br><span class="line"></span><br><span class="line"> FLAGS, unparsed = parser.parse_known_args()</span><br><span class="line"> tf.app.run(main=main, argv=[sys.argv[<span class="number">0</span>]] + unparsed)</span><br><span class="line"></span><br></pre></td></tr></table></figure>
<p>这里我在原来的程序基础上面稍微改了下,因为我已经提前将数据下载好了,所以我让程序直接读取本机指定目录下的训练数据,同时增加了日志文件输出.这是为了在公司的容器云平台上测试获取容器输出文件</p>
<h2 id="编写Dockerfile"><a href="#编写Dockerfile" class="headerlink" title="编写Dockerfile"></a>编写Dockerfile</h2><p>我们可以在我们的用户目录下,创建一个空的文件夹,将mnist数据集以及程序文件都拷贝进这个文件夹下.其实数据集应该是放在数据卷中,但是这里为了方便,我直接将训练数据打进了镜像中.然后创建Dockerfile,文件内容如下</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">FROM tensorflow/tensorflow:1.9.0-devel-py3</span><br><span class="line"></span><br><span class="line">COPY . /home/ll</span><br><span class="line">WORKDIR /home/ll</span><br><span class="line">CMD [<span class="string">'python'</span>, <span class="string">'convolutional.py'</span>]</span><br></pre></td></tr></table></figure>
<p>即Dockerfile文件中最后一行表示容器启动的运行的命令</p>
<h2 id="build镜像"><a href="#build镜像" class="headerlink" title="build镜像"></a>build镜像</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker build -t tf:1.9 .</span><br></pre></td></tr></table></figure>
<p><code>-t</code>参数指定镜像跟tag,最后的<code>.</code>指定了镜像中的上下文.构建完之后使用<code>docker images</code>可以查看多了<code>tf:1.9</code>镜像</p>
<h2 id="运行镜像"><a href="#运行镜像" class="headerlink" title="运行镜像"></a>运行镜像</h2><p>运行下面的命令,运行上一步构建好的镜像</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker run -it --name <span class="built_in">test</span> tf:1.9</span><br></pre></td></tr></table></figure>
<p>然后就能够看到训练的输出.<br><img src="/docker%E4%B8%8Etensorflow%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/tensorflow.png" class=""><br>同时可以在看一个连接,进入容器,即运行下面命令</p>
<figure class="highlight bash"><table><tr><td class="code"><pre><span class="line">docker <span class="built_in">exec</span> -it <span class="built_in">test</span> /bin/bash</span><br></pre></td></tr></table></figure>
<p>可以看到如下内容<br><img src="/docker%E4%B8%8Etensorflow%E7%BB%93%E5%90%88%E4%BD%BF%E7%94%A8/container.png" class=""><br>即看到了cnn_mnist.log的日志输出文件</p>
]]></content>
<categories>
<category>docker</category>
</categories>
<tags>
<tag>docker</tag>
<tag>tensorflow</tag>
</tags>
</entry>
<entry>
<title>Thread Signaling</title>
<url>/Thread-Signaling.html</url>
<content><![CDATA[<h1 id="线程信号量及wait,notify方法"><a href="#线程信号量及wait,notify方法" class="headerlink" title="线程信号量及wait,notify方法"></a>线程信号量及wait,notify方法</h1><p>本篇主要介绍线程之间如何进行信号的通知。同时介绍wait,notify底层的一些实现。</p>
<h2 id="通过共享对象进行信号通知"><a href="#通过共享对象进行信号通知" class="headerlink" title="通过共享对象进行信号通知"></a>通过共享对象进行信号通知</h2><p>最简单的进行线程之间通知的方式就是采用共享变量的方式。比如下面的代码<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MySignal</span>{</span><br><span class="line"> <span class="keyword">protected</span> <span class="type">boolean</span> <span class="variable">hasDataToProcess</span> <span class="operator">=</span> <span class="literal">false</span>;</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="type">boolean</span> <span class="title function_">hasDataToProcess</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="built_in">this</span>.hasDataToProcess;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title function_">setHasDataToProcess</span><span class="params">(<span class="type">boolean</span> hasData)</span>{</span><br><span class="line"> <span class="built_in">this</span>.hasDataToProcess = hasData; </span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>线程A和线程B共享同一个MySingal实例,当线程A处理好数据后,可以设置hasDataToProcess属性为true,然后线程B获取到此属性。从而完成线程之间的信号通知。当然,如果线程A和线程B不是在同一个MySingal实例上进行的,则不能进行信号的传递。</p>
<h2 id="忙等待"><a href="#忙等待" class="headerlink" title="忙等待"></a>忙等待</h2><p>在采用MySingal的例子中,一般会采用下面的代码来判断一个线程是否可以进行处理了。<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">protected</span> <span class="type">MySignal</span> <span class="variable">sharedSignal</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MySingal</span>()</span><br><span class="line"><span class="keyword">while</span>(!sharedSignal.hasDataToProcess()){</span><br><span class="line"> <span class="comment">//do nothing... busy waiting</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>从代码中可以看到,检测hasDataToProcess属性是在一个while循环中,如果hasDataToProcess为false,这就会造成线程一直在执行while语句,造成忙等待。这会造成CPU资源的浪费。</p>
<h2 id="wait,notify,notifyAll使用"><a href="#wait,notify,notifyAll使用" class="headerlink" title="wait,notify,notifyAll使用"></a>wait,notify,notifyAll使用</h2><p>一般在Java中,我们一般会采用wait,notify或notifyAll进行线程之间信号的传递。线程调用某一个对象上的wait方法前,必须首先获取该对象上的锁,才能执行此对象上的wait方法。在调用notify或者notifyAll之前,也是要获取对应对象上的锁。调用wait方法时,会释放此对象上的锁,线程进入阻塞状态,等待信号。而调用notify后,会随机唤醒一个同对象上的线程,但是必须是退出了notify对应的synchronized块后,被唤醒的线程才能继续执行,因为被唤醒的线程还要获取对象上的锁。如下面的代码所示:<br><figure class="highlight java"><table><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MonitorObject</span>{</span><br><span class="line">}</span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MyWaitNotify</span>{</span><br><span class="line"> <span class="type">MonitorObject</span> <span class="variable">myMonitorObject</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">MonitorObject</span>();</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doWait</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(myMonitorObject){</span><br><span class="line"> <span class="keyword">try</span>{</span><br><span class="line"> myMonitorObject.wait();</span><br><span class="line"> } <span class="keyword">catch</span>(InterruptedException e){...}</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">doNotify</span><span class="params">()</span>{</span><br><span class="line"> <span class="keyword">synchronized</span>(myMonitorObject){</span><br><span class="line"> myMonitorObject.notify();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><br>从代码中,可以看到在myMonitorObject上调用wait方法之前,会先获取myMonitorObject上的锁。在调用notify方法之前,也是要先获取myMonitorObject上的锁。在下面的内容中我会简单介绍wait和notify的底层原理。</p>
<h3 id="wait-notify-notifyAll在Object源码中的介绍"><a href="#wait-notify-notifyAll在Object源码中的介绍" class="headerlink" title="wait notify notifyAll在Object源码中的介绍"></a>wait notify notifyAll在Object源码中的介绍</h3><p>在JDK1.8中,Object对象中有三个wait(),wait(long timeout),wait(long timeout, int nanos)这三个方法,它们的主要区别是后两个方法增加了等待的时间。<br>下面是JDK1.8中关于wait(long timeout)方法的描述:</p>
<blockquote>
<p>/**</p>
<pre><code> * Causes the current thread to wait until either another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object, or a
* specified amount of time has elapsed.
* <p>
* The current thread must own this object's monitor.
* <p>
* This method causes the current thread (call it <var>T</var>) to
* place itself in the wait set for this object and then to relinquish
* any and all synchronization claims on this object. Thread <var>T</var>
* becomes disabled for thread scheduling purposes and lies dormant
* until one of four things happens:
* <ul>
* <li>Some other thread invokes the {@code notify} method for this
* object and thread <var>T</var> happens to be arbitrarily chosen as
* the thread to be awakened.
* <li>Some other thread invokes the {@code notifyAll} method for this
* object.
* <li>Some other thread {@linkplain Thread#interrupt() interrupts}
* thread <var>T</var>.
* <li>The specified amount of real time has elapsed, more or less. If
* {@code timeout} is zero, however, then real time is not taken into
* consideration and the thread simply waits until notified.
* </ul>
* The thread <var>T</var> is then removed from the wait set for this
* object and re-enabled for thread scheduling. It then competes in the
* usual manner with other threads for the right to synchronize on the
* object; once it has gained control of the object, all its
* synchronization claims on the object are restored to the status quo
* ante - that is, to the situation as of the time that the {@code wait}
* method was invoked. Thread <var>T</var> then returns from the
* invocation of the {@code wait} method. Thus, on return from the
* {@code wait} method, the synchronization state of the object and of
* thread {@code T} is exactly as it was when the {@code wait} method
* was invoked.
* <p>
* A thread can also wake up without being notified, interrupted, or
* timing out, a so-called <i>spurious wakeup</i>. While this will rarely
* occur in practice, applications must guard against it by testing for
* the condition that should have caused the thread to be awakened, and
* continuing to wait if the condition is not satisfied. In other words,
* waits should always occur in loops, like this one:
* <pre>
* synchronized (obj) {
* while (&lt;condition does not hold&gt;)
* obj.wait(timeout);
* ... // Perform action appropriate to condition
* }
* </pre>
* (For more information on this topic, see Section 3.2.3 in Doug Lea's
* "Concurrent Programming in Java (Second Edition)" (Addison-Wesley,
* 2000), or Item 50 in Joshua Bloch's "Effective Java Programming
* Language Guide" (Addison-Wesley, 2001).
*
* <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
* interrupted} by any thread before or while it is waiting, then an
* {@code InterruptedException} is thrown. This exception is not
* thrown until the lock status of this object has been restored as
* described above.
*
* <p>
* Note that the {@code wait} method, as it places the current thread
* into the wait set for this object, unlocks only this object; any
* other objects on which the current thread may be synchronized remain
* locked while the thread waits.
* <p>
* This method should only be called by a thread that is the owner
* of this object's monitor. See the {@code notify} method for a
* description of the ways in which a thread can become the owner of
* a monitor.
*