-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
513 lines (300 loc) · 165 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>Yifan Guo Personal Blog</title>
<subtitle>you got a dream, you gotta protect it</subtitle>
<link href="/atom.xml" rel="self"/>
<link href="http://www.yifanguo.top/"/>
<updated>2019-04-17T06:51:36.745Z</updated>
<id>http://www.yifanguo.top/</id>
<author>
<name>Yifan Guo</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>online_learning</title>
<link href="http://www.yifanguo.top/2019/04/17/online-learning/"/>
<id>http://www.yifanguo.top/2019/04/17/online-learning/</id>
<published>2019-04-17T06:51:36.000Z</published>
<updated>2019-04-17T06:51:36.745Z</updated>
<summary type="html">
</summary>
</entry>
<entry>
<title>recsystem01</title>
<link href="http://www.yifanguo.top/2019/03/20/recsystem01/"/>
<id>http://www.yifanguo.top/2019/03/20/recsystem01/</id>
<published>2019-03-20T03:30:23.000Z</published>
<updated>2019-03-20T03:31:47.112Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="recommender-system01">recommender system01</span></h1>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="recommender-system01">recommender system01</span></h1>
</summary>
<category term="recommender" scheme="http://www.yifanguo.top/tags/recommender/"/>
</entry>
<entry>
<title>JAVA_MAGIC_UNSAFE</title>
<link href="http://www.yifanguo.top/2019/02/21/JAVA-MAGIC-UNSAFE/"/>
<id>http://www.yifanguo.top/2019/02/21/JAVA-MAGIC-UNSAFE/</id>
<published>2019-02-21T11:59:22.000Z</published>
<updated>2019-02-21T12:12:55.031Z</updated>
<content type="html"><![CDATA[<p>ref https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html</p><p>[TOC]</p><h1><span id="java-unsafe">JAVA UNSAFE</span></h1><p><em>//分配内存, 相当于C++的malloc函数</em><strong>public</strong> <strong>native</strong> <strong>long</strong> <strong>allocateMemory</strong>(<strong>long</strong> bytes);<em>//扩充内存</em><strong>public</strong> <strong>native</strong> <strong>long</strong> <strong>reallocateMemory</strong>(<strong>long</strong> address, <strong>long</strong> bytes);<em>//释放内存</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>freeMemory</strong>(<strong>long</strong> address);<em>//在给定的内存块中设置值</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>setMemory</strong>(Object o, <strong>long</strong> offset, <strong>long</strong> bytes, <strong>byte</strong> value);<em>//内存拷贝</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>copyMemory</strong>(Object srcBase, <strong>long</strong> srcOffset, Object destBase, <strong>long</strong> destOffset, <strong>long</strong> bytes);<em>//获取给定地址值,忽略修饰限定符的访问限制。与此类似操作还有: getInt,getDouble,getLong,getChar等</em><strong>public</strong> <strong>native</strong> Object <strong>getObject</strong>(Object o, <strong>long</strong> offset);<em>//为给定地址设置值,忽略修饰限定符的访问限制,与此类似操作还有: putInt,putDouble,putLong,putChar等</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>putObject</strong>(Object o, <strong>long</strong> offset, Object x);<em>//获取给定地址的byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果为确定的)</em><strong>public</strong> <strong>native</strong> <strong>byte</strong> <strong>getByte</strong>(<strong>long</strong> address);<em>//为给定地址设置byte类型的值(当且仅当该内存地址为allocateMemory分配时,此方法结果才是确定的)</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>putByte</strong>(<strong>long</strong> address, <strong>byte</strong> x);</p><ol><li><p>通常,我们在Java中创建的对象都处于堆内内存(heap)中</p></li><li><p>堆内内存是由JVM所管控的Java进程内存,并且它们遵循JVM的内存管理机制,JVM会采用垃圾回收机制统一管理堆内存。</p></li><li><p>与之相对的是堆外内存,存在于JVM管控之外的内存区域</p></li><li><p>Java中对堆外内存的操作,依赖于Unsafe提供的操作堆外内存的native方法</p></li></ol><h1><span id="使用堆外内存的原因">使用堆外内存的原因</span></h1><ul><li>对垃圾回收停顿的改善。由于堆外内存是直接受操作系统管理而不是JVM,所以当我们使用堆外内存时,即可保持较小的堆内内存规模。从而在GC时减少回收停顿对于应用的影响。 (avoid GC)</li></ul><ul><li>提升程序I/O操作的性能。通常在I/O通信过程中,会存在堆内内存到堆外内存的数据拷贝操作,对于需要频繁进行内存间数据拷贝且生命周期较短的暂存数据,都建议存储到堆外内存。 (处理生命周期短的object)</li></ul><h1><span id="典型应用">典型应用</span></h1><p>DirectByteBuffer是Java用于实现堆外内存的一个重要类,通常用在通信过程中做缓冲池,如在Netty、MINA等NIO框架中应用广泛。DirectByteBuffer对于堆外内存的创建、使用、销毁等逻辑均由Unsafe提供的堆外内存API来实现。</p><h1><span id="cas">CAS</span></h1><p><strong>public</strong> <strong>final</strong> <strong>native</strong> <strong>boolean</strong> <strong>compareAndSwapObject</strong>(Object o, <strong>long</strong> offset, Object expected, Object update);<strong>public</strong> <strong>final</strong> <strong>native</strong> <strong>boolean</strong> <strong>compareAndSwapInt</strong>(Object o, <strong>long</strong> offset, <strong>int</strong> expected,<strong>int</strong> update);<strong>public</strong> <strong>final</strong> <strong>native</strong> <strong>boolean</strong> <strong>compareAndSwapLong</strong>(Object o, <strong>long</strong> offset, <strong>long</strong> expected, <strong>long</strong> update);</p><p>什么是CAS?</p><p>即比较并替换,实现并发算法时常用到的一种技术。</p><p>CAS操作包含三个操作数——内存位置、预期原值及新值。</p><p>执行CAS操作的时候,将内存位置的值与预期原值比较,如果相匹配,那么处理器会自动将该位置值更新为新值,否则,处理器不做任何操作。</p><p>我们都知道,CAS是一条CPU的原子指令(cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX)底层实现即为CPU指令cmpxchg。</p><p>CAS在java.util.concurrent.atomic相关类、Java AQS、CurrentHashMap等实现上有非常广泛的应用。</p><p>AtomicInteger的实现中,静态字段valueOffset即为字段value的内存偏移地址,valueOffset的值在AtomicInteger初始化时,在静态代码块中通过Unsafe的objectFieldOffset方法获取。在AtomicInteger中提供的线程安全方法中,通过字段valueOffset的值可以定位到AtomicInteger对象中value的内存地址,从而可以根据CAS实现对value字段的原子操作</p><h1><span id="线程调度">线程调度</span></h1><p><em>//取消阻塞线程</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>unpark</strong>(Object thread);<em>//阻塞线程</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>park</strong>(<strong>boolean</strong> isAbsolute, <strong>long</strong> time);<em>//获得对象锁(可重入锁)</em>@Deprecated<strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>monitorEnter</strong>(Object o);<em>//释放对象锁</em>@Deprecated<strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>monitorExit</strong>(Object o);<em>//尝试获取对象锁</em>@Deprecated<strong>public</strong> <strong>native</strong> <strong>boolean</strong> <strong>tryMonitorEnter</strong>(Object o);</p><p>Java锁和同步器框架的核心类AbstractQueuedSynchronizer,就是通过调用<code>LockSupport.park()</code>和<code>LockSupport.unpark()</code>实现线程的阻塞和唤醒的,而LockSupport的park、unpark方法实际是调用Unsafe的park、unpark方式来实现</p><h1><span id="class相关">Class相关</span></h1><p><em>//获取给定静态字段的内存地址偏移量,这个值对于给定的字段是唯一且固定不变的</em><strong>public</strong> <strong>native</strong> <strong>long</strong> <strong>staticFieldOffset</strong>(Field f);<em>//获取一个静态类中给定字段的对象指针</em><strong>public</strong> <strong>native</strong> Object <strong>staticFieldBase</strong>(Field f);<em>//判断是否需要初始化一个类,通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。 当且仅当ensureClassInitialized方法不生效时返回false。</em><strong>public</strong> <strong>native</strong> <strong>boolean</strong> <strong>shouldBeInitialized</strong>(Class<?> c);<em>//检测给定的类是否已经初始化。通常在获取一个类的静态属性的时候(因为一个类如果没初始化,它的静态属性也不会初始化)使用。</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>ensureClassInitialized</strong>(Class<?> c);<em>//定义一个类,此方法会跳过JVM的所有安全检查,默认情况下,ClassLoader(类加载器)和ProtectionDomain(保护域)实例来源于调用者</em><strong>public</strong> <strong>native</strong> Class<?> defineClass(String name, <strong>byte</strong>[] b, <strong>int</strong> off, <strong>int</strong> len, ClassLoader loader, ProtectionDomain protectionDomain);<em>//定义一个匿名类</em><strong>public</strong> <strong>native</strong> Class<?> defineAnonymousClass(Class<?> hostClass, <strong>byte</strong>[] data, Object[] cpPatches);</p><h1><span id="对象操作">对象操作</span></h1><p><em>//返回对象成员属性在内存地址相对于此对象的内存地址的偏移量</em><strong>public</strong> <strong>native</strong> <strong>long</strong> <strong>objectFieldOffset</strong>(Field f);<em>//获得给定对象的指定地址偏移量的值,与此类似操作还有:getInt,getDouble,getLong,getChar等</em><strong>public</strong> <strong>native</strong> Object <strong>getObject</strong>(Object o, <strong>long</strong> offset);<em>//给定对象的指定地址偏移量设值,与此类似操作还有:putInt,putDouble,putLong,putChar等</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>putObject</strong>(Object o, <strong>long</strong> offset, Object x);<em>//从对象的指定偏移量处获取变量的引用,使用volatile的加载语义</em><strong>public</strong> <strong>native</strong> Object <strong>getObjectVolatile</strong>(Object o, <strong>long</strong> offset);<em>//存储变量的引用到对象的指定的偏移量处,使用volatile的存储语义</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>putObjectVolatile</strong>(Object o, <strong>long</strong> offset, Object x);<em>//有序、延迟版本的putObjectVolatile方法,不保证值的改变被其他线程立即看到。只有在field被volatile修饰符修饰时有效</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>putOrderedObject</strong>(Object o, <strong>long</strong> offset, Object x);<em>//绕过构造方法、初始化代码来创建对象</em><strong>public</strong> <strong>native</strong> Object <strong>allocateInstance</strong>(Class<?> cls) <strong>throws</strong> InstantiationException;</p><ul><li><strong>常规对象实例化方式</strong>:我们通常所用到的创建对象的方式,从本质上来讲,都是通过new机制来实现对象的创建。但是,new机制有个特点就是当类只提供有参的构造函数且无显示声明无参构造函数时,则必须使用有参构造函数进行对象构造,而使用有参构造函数时,必须传递相应个数的参数才能完成对象实例化。</li><li><strong>非常规的实例化方式</strong>:而Unsafe中提供allocateInstance方法,仅通过Class对象就可以创建此类的实例对象,而且不需要调用其构造函数、初始化代码、JVM安全检查等。它抑制修饰符检测,也就是即使构造器是private修饰的也能通过此方法实例化,只需提类对象即可创建相应的对象。由于这种特性,allocateInstance在java.lang.invoke、Objenesis(提供绕过类构造器的对象生成方式)、Gson(反序列化时用到)中都有相应的应用。</li></ul><h1><span id="数组相关">数组相关</span></h1><p><em>//返回数组中第一个元素的偏移地址</em><strong>public</strong> <strong>native</strong> <strong>int</strong> <strong>arrayBaseOffset</strong>(Class<?> arrayClass);<em>//返回数组中一个元素占用的大小</em><strong>public</strong> <strong>native</strong> <strong>int</strong> <strong>arrayIndexScale</strong>(Class<?> arrayClass);</p><p>这两个与数据操作相关的方法,在java.util.concurrent.atomic 包下的AtomicIntegerArray(可以实现对Integer数组中每个元素的原子性操作)中有典型的应用,如下图AtomicIntegerArray源码所示,通过Unsafe的arrayBaseOffset、arrayIndexScale分别获取数组首元素的偏移地址base及单个元素大小因子scale。后续相关原子性操作,均依赖于这两个值进行数组中元素的定位,如下图二所示的getAndAdd方法即通过checkedByteOffset方法获取某数组元素的偏移地址,而后通过CAS实现原子性操作</p><h1><span id="内存屏障">内存屏障</span></h1><p>在Java 8中引入,用于定义内存屏障(也称内存栅栏,内存栅障,屏障指令等,是一类同步屏障指令,是CPU或编译器在对内存随机访问的操作中的一个同步点,使得此点之前的所有读写操作都执行后才可以开始执行此点之后的操作),避免代码重排序</p><p><em>//内存屏障,禁止load操作重排序。屏障前的load操作不能被重排序到屏障后,屏障后的load操作不能被重排序到屏障前</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>loadFence</strong>();<em>//内存屏障,禁止store操作重排序。屏障前的store操作不能被重排序到屏障后,屏障后的store操作不能被重排序到屏障前</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>storeFence</strong>();<em>//内存屏障,禁止load、store操作重排序</em><strong>public</strong> <strong>native</strong> <strong>void</strong> <strong>fullFence</strong>();</p><p>在Java 8中引入了一种锁的新机制——StampedLock,它可以看成是读写锁的一个改进版本。StampedLock提供了一种乐观读锁的实现,这种乐观读锁类似于无锁的操作,完全不会阻塞写线程获取写锁,从而缓解读多写少时写线程“饥饿”现象。由于StampedLock提供的乐观读锁不阻塞写线程获取读锁,当线程共享变量从主内存load到线程工作内存时,会存在数据不一致问题,所以当使用StampedLock的乐观读锁时,需要遵从如下图用例中使用的模式来确保数据的一致性</p>]]></content>
<summary type="html">
<p>ref https://tech.meituan.com/2019/02/14/talk-about-java-magic-class-unsafe.html</p>
<p>[TOC]</p>
<h1><span id="java-unsafe">JAVA UNSAFE</
</summary>
<category term="java" scheme="http://www.yifanguo.top/tags/java/"/>
</entry>
<entry>
<title>yarn</title>
<link href="http://www.yifanguo.top/2019/02/13/yarn/"/>
<id>http://www.yifanguo.top/2019/02/13/yarn/</id>
<published>2019-02-13T05:32:17.000Z</published>
<updated>2019-02-13T06:01:36.000Z</updated>
<content type="html"><![CDATA[<h1><span id="如何在mac上创建一个hadoop环境">如何在mac上创建一个hadoop环境</span></h1><p>https://www.jianshu.com/p/dc6829389298</p><h1><span id="如何创建一个directory">如何创建一个directory</span></h1><p>https://blog.csdn.net/macanv/article/details/52729625</p><p>hadoop fs -put /home/hadoop/input hdfs:<em>//node1:9000/tmp</em></p><h1><span id="如何写一个app-on-yarn">如何写一个app on yarn</span></h1><p>http://blog.madhukaraphatak.com/running-scala-programs-on-yarn/</p>]]></content>
<summary type="html">
<h1><span id="如何在mac上创建一个hadoop环境">如何在mac上创建一个hadoop环境</span></h1>
<p>https://www.jianshu.com/p/dc6829389298</p>
<h1><span id="如何创建一个directo
</summary>
<category term="Hadoop" scheme="http://www.yifanguo.top/tags/Hadoop/"/>
</entry>
<entry>
<title>knowledgeTree</title>
<link href="http://www.yifanguo.top/2019/01/22/knowledgeTree/"/>
<id>http://www.yifanguo.top/2019/01/22/knowledgeTree/</id>
<published>2019-01-22T02:03:19.000Z</published>
<updated>2019-01-22T02:13:21.000Z</updated>
<content type="html"><![CDATA[<p>这个是整个知识图谱的架构,分为三大块:基础知识,机器学习,大数据</p><p>[TOC]</p><h1><span id="基础知识">基础知识</span></h1><h2><span id="编程语言">编程语言</span></h2><h3><span id="java">java</span></h3><h3><span id="c">c++</span></h3><h3><span id="python">python</span></h3><h3><span id="scala">scala</span></h3><h2><span id="数据结构">数据结构</span></h2><h3><span id="基础数据结构">基础数据结构</span></h3><h3><span id="高级数据结构">高级数据结构</span></h3><h2><span id="基础算法">基础算法</span></h2><h3><span id="基础算法">基础算法</span></h3><h3><span id="高级算法">高级算法</span></h3><h2><span id="操作系统">操作系统</span></h2><h3><span id="操作系统基础知识">操作系统基础知识</span></h3><h3><span id="linux">linux</span></h3><h2><span id="网络通信">网络通信</span></h2><h2><span id="内存管理">内存管理</span></h2><h2><span id="分布式">分布式</span></h2><h1><span id="机器学习">机器学习</span></h1><h2><span id="基础机器学习">基础机器学习</span></h2><h2><span id="强化学习">强化学习</span></h2><h2><span id="深度学习">深度学习</span></h2><h2><span id="数学知识">数学知识</span></h2><h3><span id="线性代数">线性代数</span></h3><h3><span id="统计与概率论">统计与概率论</span></h3><h3><span id="凸优化">凸优化</span></h3><h1><span id="大数据">大数据</span></h1><h2><span id="大数据基础知识">大数据基础知识</span></h2><h2><span id="分布式存储">分布式存储</span></h2><h3><span id="hdfs">hdfs</span></h3><h2><span id="分布式计算">分布式计算</span></h2><h3><span id="spark">spark</span></h3><h2><span id="分布式结构化存储">分布式结构化存储</span></h2><h3><span id="hbase">hbase</span></h3>]]></content>
<summary type="html">
<p>这个是整个知识图谱的架构,分为三大块:基础知识,机器学习,大数据</p>
<p>[TOC]</p>
<h1><span id="基础知识">基础知识</span></h1>
<h2><span id="编程语言">编程语言</span></h2>
<h3><span id=
</summary>
<category term="overview" scheme="http://www.yifanguo.top/tags/overview/"/>
</entry>
<entry>
<title>一文读懂逻辑回归</title>
<link href="http://www.yifanguo.top/2019/01/21/%E4%B8%80%E6%96%87%E8%AF%BB%E6%87%82%E9%80%BB%E8%BE%91%E5%9B%9E%E5%BD%92/"/>
<id>http://www.yifanguo.top/2019/01/21/一文读懂逻辑回归/</id>
<published>2019-01-21T07:47:27.000Z</published>
<updated>2019-01-21T07:47:27.000Z</updated>
<summary type="html">
</summary>
</entry>
<entry>
<title>线性代数库</title>
<link href="http://www.yifanguo.top/2019/01/21/%E7%BA%BF%E6%80%A7%E4%BB%A3%E6%95%B0%E5%BA%93/"/>
<id>http://www.yifanguo.top/2019/01/21/线性代数库/</id>
<published>2019-01-21T01:40:47.000Z</published>
<updated>2019-01-21T01:41:00.000Z</updated>
<summary type="html">
</summary>
<category term="BLAS" scheme="http://www.yifanguo.top/tags/BLAS/"/>
</entry>
<entry>
<title>跟我一起学c加加</title>
<link href="http://www.yifanguo.top/2019/01/18/%E8%B7%9F%E6%88%91%E4%B8%80%E8%B5%B7%E5%AD%A6c%E5%8A%A0%E5%8A%A0/"/>
<id>http://www.yifanguo.top/2019/01/18/跟我一起学c加加/</id>
<published>2019-01-18T02:09:38.000Z</published>
<updated>2019-01-18T06:39:01.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="c基本语法">C++基本语法</span></h1><ul><li><p>对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。</p></li><li><p><strong>类 -</strong> 类可以定义为描述对象行为/状态的模板/蓝图。</p></li><li><p><strong>方法 -</strong> 从基本上说,一个方法表示一种行为。一个类可以包含多个方法。可以在方法中写入逻辑、操作数据以及执行所有的动作。</p></li><li><p><strong>即时变量 -</strong> 每个对象都有其独特的即时变量。对象的状态是由这些即时变量的值创建的。</p></li></ul><p>块注释符(/<em>...</em>/)是不可以嵌套使用的。</p><p>此外,我们还可以使用 #if 0 ... #endif 来实现注释,且可以实现嵌套,格式为:</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">#if 0</span><br><span class="line"> code</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure></p><p>你可以把 #if 0 改成 #if 1 来执行 <strong>code</strong> 的代码。</p><p>这种形式对程序调试也可以帮助,测试时使用 <strong>#if 1</strong> 来执行测试代码,发布后使用 <strong>#if 0</strong> 来屏蔽测试代码。</p><p><strong>#if</strong> 后可以是任意的条件语句。</p><p>下面的代码如果 condition 条件为 tre 执行 code1 ,否则执行 code2。</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">#if condition</span><br><span class="line"> code1</span><br><span class="line">#else</span><br><span class="line"> code2</span><br><span class="line">#endif</span><br></pre></td></tr></table></figure></p><table><thead><tr><th>类型</th><th>位</th><th>范围</th></tr></thead><tbody><tr><td>char</td><td>1 个字节</td><td>-128 到 127 或者 0 到 255</td></tr><tr><td>unsigned char</td><td>1 个字节</td><td>0 到 255</td></tr><tr><td>signed char</td><td>1 个字节</td><td>-128 到 127</td></tr><tr><td>int</td><td>4 个字节</td><td>-2147483648 到 2147483647</td></tr><tr><td>unsigned int</td><td>4 个字节</td><td>0 到 4294967295</td></tr><tr><td>signed int</td><td>4 个字节</td><td>-2147483648 到 2147483647</td></tr><tr><td>short int</td><td>2 个字节</td><td>-32768 到 32767</td></tr><tr><td>unsigned short int</td><td>2 个字节</td><td>0 到 65,535</td></tr><tr><td>signed short int</td><td>2 个字节</td><td>-32768 到 32767</td></tr><tr><td>long int</td><td>8 个字节</td><td>-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807</td></tr><tr><td>signed long int</td><td>8 个字节</td><td>-9,223,372,036,854,775,808 到 9,223,372,036,854,775,807</td></tr><tr><td>unsigned long int</td><td>8 个字节</td><td>0 to 18,446,744,073,709,551,615</td></tr><tr><td>float</td><td>4 个字节</td><td>+/- 3.4e +/- 38 (~7 个数字)</td></tr><tr><td>double</td><td>8 个字节</td><td>+/- 1.7e +/- 308 (~15 个数字)</td></tr><tr><td>long double</td><td>16 个字节</td><td>+/- 1.7e +/- 308 (~15 个数字)</td></tr><tr><td>wchar_t</td><td>2 或 4 个字节</td><td>1 个宽字符</td></tr></tbody></table><h2><span id="定义常量">定义常量</span></h2><p>在 C++ 中,有两种简单的定义常量的方式:</p><ul><li>使用 <strong>#define</strong> 预处理器。</li><li>使用 <strong>const</strong> 关键字。</li></ul><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">unsigned x;</span><br><span class="line">unsigned int y;</span><br><span class="line"></span><br><span class="line">#include <iostream></span><br><span class="line">using namespace std;</span><br><span class="line"> </span><br><span class="line">/* </span><br><span class="line"> * 这个程序演示了有符号整数和无符号整数之间的差别</span><br><span class="line">*/</span><br><span class="line">int main()</span><br><span class="line">{</span><br><span class="line"> short int i; // 有符号短整数</span><br><span class="line"> short unsigned int j; // 无符号短整数</span><br><span class="line"> </span><br><span class="line"> j = 50000;</span><br><span class="line"> </span><br><span class="line"> i = j;</span><br><span class="line"> cout << i << " " << j;</span><br><span class="line"> </span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2><span id="c-中的类型限定符">C++ 中的类型限定符</span></h2><p>类型限定符提供了变量的额外信息。</p><table><thead><tr><th>限定符</th><th>含义</th></tr></thead><tbody><tr><td>const</td><td><strong>const</strong> 类型的对象在程序执行期间不能被修改改变。</td></tr><tr><td>volatile</td><td>修饰符 <strong>volatile</strong> 告诉编译器不需要优化volatile声明的变量,让程序可以直接从内存中读取变量。对于一般的变量编译器会对变量进行优化,将内存中的变量值放在寄存器中以加快读写效率。</td></tr><tr><td>restrict</td><td>由 <strong>restrict</strong> 修饰的指针是唯一一种访问它所指向的对象的方式。只有 C99 增加了新的类型限定符 restrict。</td></tr></tbody></table><h2><span id="auto-存储类">auto 存储类</span></h2><p>自 C++ 11 以来,<strong>auto</strong> 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。</p><p>C++98标准中auto关键字用于自动变量的声明,但由于使用极少且多余,在C++11中已删除这一用法。</p><p>根据初始化表达式自动推断被声明的变量的类型,如:</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">auto f=3.14; //double</span><br><span class="line">auto s("hello"); //const </span><br><span class="line">char* auto z = new auto(9); // int* </span><br><span class="line">auto x1 = 5, x2 = 5.0, x3='r';//错误,必须是初始化为同一类型</span><br></pre></td></tr></table></figure></p><h2><span id="register-存储类">register 存储类</span></h2><p><strong>register</strong> 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 '&' 运算符(因为它没有内存位置)。</p><p>{ register int miles; }</p><p>寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 'register' 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。</p><table><thead><tr><th>概念</th><th>描述</th></tr></thead><tbody><tr><td><a href="http://www.runoob.com/cplusplus/passing-parameters-by-references.html">把引用作为参数</a></td><td>C++ 支持把引用作为参数传给函数,这比传一般的参数更安全。</td></tr><tr><td><a href="http://www.runoob.com/cplusplus/returning-values-by-reference.html">把引用作为返回值</a></td><td>可以从 C++ 函数中返回引用,就像返回其他数据类型一样。</td></tr></tbody></table><h1><span id="c-动态内存">C++ 动态内存</span></h1><p>了解动态内存在 C++ 中是如何工作的是成为一名合格的 C++ 程序员必不可少的。C++ 程序中的内存分为两个部分:</p><ul><li>**栈:**在函数内部声明的所有变量都将占用栈内存。</li><li>**堆:**这是程序中未使用的内存,在程序运行时可用于动态分配内存。</li></ul><p>很多时候,您无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。</p><p>在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 <strong>new</strong> 运算符。</p><p>如果您不再需要动态分配的内存空间,可以使用 <strong>delete</strong> 运算符,删除之前由 new 运算符分配的内存。</p><p><strong>malloc()</strong> 函数在 C 语言中就出现了,在 C++ 中仍然存在,但建议尽量不要使用 malloc() 函数。new 与 malloc() 函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象。</p><h1><span id="c-预处理器">C++ 预处理器</span></h1><p>预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。</p><p>所有的预处理器指令都是以井号(#)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。</p><p>我们已经看到,之前所有的实例中都有 <strong>#include</strong> 指令。这个宏用于把头文件包含到源文件中。</p><p>C++ 还支持很多预处理指令,比如 #include、#define、#if、#else、#line 等,让我们一起看看这些重要指令。</p><h2><span id="define-预处理">#define 预处理</span></h2><p>创建 <code>Length</code> 作为 <code>int</code> 的别名 :</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">typedef int Length;</span><br></pre></td></tr></table></figure></p><p>have been seeing code like this usually in the start of header files:</p><p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">ifndef</span> HEADERFILE_H</span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">define</span> HEADERFILE_H</span></span><br></pre></td></tr></table></figure></p><p>And at the end of the file is</p><p><figure class="highlight cpp"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">endif</span></span></span><br></pre></td></tr></table></figure></p><p>What is the purpose of this?</p><p>Those are called <a href="http://en.wikipedia.org/wiki/Include_guard">#include guards</a>.</p><p>Once the header is included, it checks if a unique value (in this case <code>HEADERFILE_H</code>) is defined. Then if it's not defined, it defines it and continues to the rest of the page.</p><p>When the code is included again, the first <code>ifndef</code> fails, resulting in a blank file.</p><p>That prevents double declaration of any identifiers such as types, enums and static variables.</p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="c基本语法">C++基本语法</span></h1>
<ul>
<li>
<p>对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 - 摇动、叫唤、吃。对象是类的实例。</p>
</li>
<li>
</summary>
<category term="c++" scheme="http://www.yifanguo.top/tags/c/"/>
</entry>
<entry>
<title>xdl源码解读</title>
<link href="http://www.yifanguo.top/2019/01/16/xdl%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/"/>
<id>http://www.yifanguo.top/2019/01/16/xdl源码解读/</id>
<published>2019-01-16T11:46:32.000Z</published>
<updated>2019-01-18T07:26:13.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="xdl-深度源码解读">XDL 深度源码解读</span></h1><h1><span id="只是一些杂乱的记录">只是一些杂乱的记录</span></h1><p>主要接口都在ps-plus/client/client.cc下面,其中DensePush和SparsePush负责更新参数,xdl里负责和ps-plus通信的op实现在xdl/core/ops/ps_ops下面,python接口在xdl/python/training/optimizer_impls.py里</p><h1><span id="overview">overview</span></h1><p>读源码千万不要一开始就去看类具体的实现细节,这样容易陷入误区。因为往往开源的东西并不一定是按照最佳标准编写的,一开始就去深入细节容易迷失。</p><p>首先应该高屋建瓴,先看package的分布</p><p><img src="./xdl%E6%BA%90%E7%A0%81%E8%A7%A3%E8%AF%BB/xdl.png" alt="xdl"></p><p>ps-plus首先是一个parameter server.所以整体的设计应该满足ps的要求</p><p>client 是调用方与ps node通信的端</p><p>common 通用类,主要是和hdfs交互,加载模型等</p><p>main 启动类</p><p>message 通信类</p><p>Model_server model_server类</p><p>Plugins 插件</p><p>Profiler 不太清楚,像是一些锁之类的</p><p>scheduler 调度类,请求都由scheduler 转发给ps server</p><p>server server类</p><p>service 服务</p><p>tool 工具类</p><h1><span id="maincc">Main.cc</span></h1><p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ps-plus/common/option_parser.h"</span> <span class="comment">// 一个option parser 启动时用</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ps-plus/server/server_service.h"</span> <span class="comment">// server类</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ps-plus/scheduler/scheduler_impl.h"</span> <span class="comment">// scheduler的实现</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string">"ps-plus/scheduler/placementer.h"</span> <span class="comment">// placementer 用来放置key</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><dlfcn.h> // linux动态库显式框架</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><thread> // 线程类</span></span></span><br><span class="line"><span class="meta">#<span class="meta-keyword">include</span> <span class="meta-string"><glog/logging.h> // logging类</span></span></span><br></pre></td></tr></table></figure></p><h1><span id="common">Common</span></h1><pre><code>## types.h</code></pre><p>定义了一些数据类型,从8位int 到 double</p><h1><span id="client">Client</span></h1><h2><span id="base_client">base_client</span></h2><p>定义了pull和push的操作,全部是异步</p><p>我们看一个spare pull 和 push的实现</p><p><figure class="highlight c++"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SparsePull</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& variable_name, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> Tensor& ids, </span></span></span><br><span class="line"><span class="function"><span class="params"> Tensor* result, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> Callback& cb)</span> </span>= <span class="number">0</span>;</span><br><span class="line"><span class="function"><span class="keyword">virtual</span> <span class="keyword">void</span> <span class="title">SparsePush</span><span class="params">(<span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& variable_name, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> Tensor& ids, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">string</span>& updater, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> <span class="built_in">std</span>::<span class="built_in">vector</span><Data*>& data, </span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">const</span> Callback& cb)</span> </span>= <span class="number">0</span>;</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="xdl-深度源码解读">XDL 深度源码解读</span></h1>
<h1><span id="只是一些杂乱的记录">只是一些杂乱的记录</span></h1>
<p>主要接口都在ps-plus/client/client.
</summary>
<category term="xdl" scheme="http://www.yifanguo.top/tags/xdl/"/>
</entry>
<entry>
<title>YarnIsSimple</title>
<link href="http://www.yifanguo.top/2019/01/16/YarnIsSimple/"/>
<id>http://www.yifanguo.top/2019/01/16/YarnIsSimple/</id>
<published>2019-01-16T02:45:57.000Z</published>
<updated>2019-01-16T08:41:50.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="1hadoop-yarn-is-simple">1.Hadoop Yarn is Simple</span></h1><p>这个教程将手把手教你如何把一个hello world搬到yarn上运行,然后我们会实现一个简易的parameter server并把它搬到yarn上运行。最后我们会基于spark实现一个和parameter server交互的逻辑回归算法</p><h1><span id="2what-is-hadoop-yarn">2.What is Hadoop Yarn</span></h1><p>http://hadoop.apache.org/ 不知道hadoop是什么的自己看吧</p><p>简单来说我们通常说的hadoop由三部分组成,即负责分布式存储的hdfs, 负责分布式计算的mapReduce,负责分布式资源调度的Yarn.</p><p>相比于单机系统,hdfs就相当于单机的disk,mapReduce是运行在系统中的app, 资源调度就是操作系统</p><h1><span id="3yarn-architecture">3.Yarn Architecture</span></h1><p><img src="./YarnIsSimple/ya.png" alt="ya"></p><p>我们先来看yarn的结构,yarn两个主要的任务是1. 资源管理 (resource management) 2. 调度监测任务</p><p>(Job scheduling&monitoring)</p><p>所以根据这个理念 我们主要有三个大的组件,即</p><ul><li>ResourceManager(RM) 负责管理集群所有的可用资源</li><li>NodeManager(NM) 负责一台机器上的资源管理,主要是启动荣旗,监测(cpu,memory, disk, network)并把情况汇报给RM</li><li>ApplicationMaster(AM)负责一个application的运行,AM会向RM请求资源,然后和NM一起把具体运行任务的容器拉起来</li></ul><p>所以yarn的framework的设计是非常清晰的,即</p><ul><li>针对整个集群的框架 RM per cluster</li><li>针对单台机器的框架 NM per machine</li><li>针对单个运行程序的框架 AM per application</li></ul><p>了解清楚了Yarn的设计目的,我们再看这个架构图就更清晰了整个流程如下:</p><ol><li>客户端机向RM提交任务(submit job)</li><li>RM接到任务后,会查看集群资源是否足够运行该任务</li><li>如果资源充足,就命令NM启动运行AM的container</li><li>AM向RM请求运行具体执行任务的资源</li><li>RM命令NM启动容器执行具体任务</li><li>AM负责监控所有容器的运行情况</li><li>NM向RM报告节点的运行状况</li></ol><p>这个操作看上去是不是很熟悉,我们想象下单机你启动游戏,操作系统(NM)要为游戏(AM)分配对应的内存和cpu资源,然后启动游戏(AM)。如果游戏内有多个操作在进行,由你(AM)负责管理这些操作(Container)</p><p>而分布式的区别就在于我们用RM+NM来管理所有的节点上的所有操作</p><h2><span id="resourcemanager">ResourceManager</span></h2><p>RM是集群最高领导人,统管集群所有的资源和监控运行情况。它主要有两部分组成即调取器scheduler和应用程序管理器application manager(注意不是AM,这个很容易搞混)</p><p>Scheduler的目的是合理分配集群资源</p><p>applicationManager的目的是为了监控所有app的运行状况</p><h3><span id="scheduler">Scheduler</span></h3><p>scheduler是纯粹的,高尚的,只负责调度资源,至于app挂了,节点挂了,统统不管。它只关心各个节点上的cpu,memory, network, disk, gpu的使用情况</p><p>scheduler的两个调度机制如下: capacityScheduler 和 FairScheduler</p><p>https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/CapacityScheduler.html</p><p>https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/FairScheduler.html</p><p>具体的机制不再细讲了,可以去参考上述两个链接,简单来说</p><p>capacityScheduler保证集群的资源要高可用(富人优先,穷人靠边的思路)</p><p>fairScheduler保证集群的资源要公平分配,大家不要打架(不患寡而患不均的思想)</p><h3><span id="application-manager">Application Manager</span></h3><p>ApplicationManager当然就负责其他的事情了,监测所有application的运行情况,命令NM启动AM,如果AM挂了就重新启动。然后由AM向scheduler请求资源,最后启动容器来运行任务</p><h1><span id="4-yarn-常见的命令">4. yarn 常见的命令</span></h1><p>https://hadoop.apache.org/docs/current/hadoop-yarn/hadoop-yarn-site/YarnCommands.html</p><p>参考上述链接,我们常用的有</p><p>yarn application -kill <application_id> kill掉一个yarn application</p><p>yarn logs -applicationId <application_id> 查看application运行的日志</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line">Usage: yarn [--config confdir] [COMMAND | CLASSNAME]</span><br><span class="line"> CLASSNAME run the class named CLASSNAME</span><br><span class="line"> or</span><br><span class="line"> where COMMAND is one of:</span><br><span class="line"> resourcemanager -format-state-store deletes the RMStateStore</span><br><span class="line"> resourcemanager run the ResourceManager</span><br><span class="line"> nodemanager run a nodemanager on each slave</span><br><span class="line"> timelineserver run the timeline server</span><br><span class="line"> rmadmin admin tools</span><br><span class="line"> sharedcachemanager run the SharedCacheManager daemon</span><br><span class="line"> scmadmin SharedCacheManager admin tools</span><br><span class="line"> version print the version</span><br><span class="line"> jar <jar> run a jar file</span><br><span class="line"> application prints application(s)</span><br><span class="line"> report/kill application</span><br><span class="line"> applicationattempt prints applicationattempt(s)</span><br><span class="line"> report</span><br><span class="line"> container prints container(s) report</span><br><span class="line"> node prints node report(s)</span><br><span class="line"> queue prints queue information</span><br><span class="line"> logs dump container logs</span><br><span class="line"> classpath prints the class path needed to</span><br><span class="line"> get the Hadoop jar and the</span><br><span class="line"> required libraries</span><br><span class="line"> cluster prints cluster information</span><br><span class="line"> daemonlog get/set the log level for each</span><br><span class="line"> daemon</span><br></pre></td></tr></table></figure></p><h1><span id="5-writing-a-hello-world-on-yarn">5. Writing a Hello world on yarn</span></h1><p>说了那么多,终于到正题了,现在我们来手把手写一个hello world on yarn。</p><p>我们将使用scala来完成这个程序,如果你不熟悉scala请看:https://www.tutorialspoint.com/scala/</p><p>再来回顾下一个app从提交到运行的整个流程:</p><ol><li>客户端机向RM提交任务(submit job)</li><li>RM接到任务后,会查看集群资源是否足够运行该任务(verify resources)</li><li>如果资源充足,就命令NM启动运行AM的container(start AM)</li><li>AM向RM请求运行具体执行任务的资源(AM -> RM)</li><li>RM命令NM启动容器执行具体任务(RM-> NM)</li><li>AM负责监控所有执行该任务的容器的运行情况(AM reports)</li><li>NM向RM报告节点的运行状况(NM reports)</li></ol><p>这个流程一定要非常清楚,否则整个on yarn的程序对你来说会非常痛苦</p><p>针对这个流程,我们看到,我们需要完成三个主要的通信</p><p>1) submit job aka Client --- RM</p><p>客户端机向RM提交任务 通过YarnClient这个类来完成</p><ol start="2"><li>AM向RM请求资源</li></ol><p>AMRMClientAsync使用这个类, events通过AMRMClientAsync.CallbackHandler来处理</p><p>3)AM和NM协作启动容器</p><p>NMClientAsync。 handling container events by <code>NMClientAsync.CallbackHandler</code></p><p>三个通信协议</p><p>ApplicationClientProtocol, ApplicationMasterProtocol 和 ContainerManagementProtocol 被上述三个client包裹。大多数情况,所有的通信都通过上述三个client来进行,如果需要特别的定制化操作,可以使用这三个protocol来自己操作</p><p>好了话不多说,开始写"Hello world"</p><h2><span id="hello-world-on-yarn">Hello world on Yarn</span></h2><h3><span id="step1-submit-job">Step1 submit job</span></h3><p>首先回忆第一步,我需要操作YarnClient这个类去提交一个application给RM。代码结构如下</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> yarnClient = <span class="type">YarnClient</span>.createYarnClient</span><br><span class="line"><span class="comment">// 初始化</span></span><br><span class="line">yarnClient.init(conf)</span><br><span class="line"><span class="comment">// 启动yarnClient</span></span><br><span class="line">yarnClient.start()</span><br></pre></td></tr></table></figure></p><p>当yarnClient启动后,我们需要创建一个新的application, 然后拿到它的response</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> newApp = yarnClient.createApplication()</span><br><span class="line"><span class="keyword">val</span> newAppResponse = newApp.getNewApplicationResponse</span><br><span class="line">appId = newAppResponse.getApplicationId</span><br></pre></td></tr></table></figure></p><p>完整代码</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Client</span>(<span class="params">conf: <span class="type">Configuration</span></span>) </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> log = <span class="type">LoggerFactory</span>.getLogger(<span class="keyword">this</span>.getClass)</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建一个yarnClient</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">val</span> yarnClient = <span class="type">YarnClient</span>.createYarnClient</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">def</span> <span class="title">submitApplication</span></span>() : <span class="type">ApplicationId</span> = {</span><br><span class="line"> <span class="keyword">var</span> appId: <span class="type">ApplicationId</span> = <span class="literal">null</span></span><br><span class="line"></span><br><span class="line"> <span class="comment">// 初始化</span></span><br><span class="line"> yarnClient.init(conf)</span><br><span class="line"> <span class="comment">// 启动yarnClient</span></span><br><span class="line"> yarnClient.start()</span><br><span class="line"></span><br><span class="line"> log.info(<span class="string">"Requesting a new application from cluster with %d NodeManagers"</span></span><br><span class="line"> .format(yarnClient.getYarnClusterMetrics.getNumNodeManagers))</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 创建一个新的app然后拿到appResponse和Id</span></span><br><span class="line"> <span class="keyword">val</span> newApp = yarnClient.createApplication()</span><br><span class="line"> <span class="keyword">val</span> newAppResponse = newApp.getNewApplicationResponse</span><br><span class="line"> appId = newAppResponse.getApplicationId</span><br><span class="line"> </span><br><span class="line"> appId</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>是不是非常简单</p><p>newAppResponse中包含了cluster中资源capacity信息,这是为下一步启动AM做准备(step2), 你马上就能看到</p><h3><span id="step2-verify-resource-capacity">step2 verify resource capacity</span></h3><p>为了验证集群资源能力,防止我们请求了不合理的资源,我们需要对cluster resource capacity做一次验证</p><p>为了验证我们需要引入四个新的常量,为了后面和spark交互,我们统一和spark命名一致</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> amMemory = <span class="number">512</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> amCores = <span class="number">1</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> executorMemory = <span class="number">1024</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">val</span> executorCores = <span class="number">1</span></span><br></pre></td></tr></table></figure></p><p>默认值也和spark一致,我们给AM分配512m内存, 1个core. 给executor分配1024m内存,1个core</p><p>验证代码</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="function"><span class="keyword">def</span> <span class="title">verifyClusterResources</span></span>(newAppResponse: <span class="type">GetNewApplicationResponse</span>): <span class="type">Unit</span> = {</span><br><span class="line"> <span class="keyword">val</span> maxMem = newAppResponse.getMaximumResourceCapability.getMemory</span><br><span class="line"> <span class="keyword">if</span> (executorMemory > maxMem) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="type">IllegalArgumentException</span>(<span class="string">s"Required executor memory (<span class="subst">$executorMemory</span>),"</span> +</span><br><span class="line"> <span class="string">s"the max threshold (<span class="subst">$maxMem</span> MB) of this cluster! Please check the values of "</span> +</span><br><span class="line"> <span class="string">s"'yarn.scheduler.maximum-allocation-mb' and/or 'yarn.nodemanager.resource.memory-mb'."</span>)</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (amMemory > maxMem) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> <span class="type">IllegalArgumentException</span>(<span class="string">s"Required AM memory <span class="subst">$amMemory</span> "</span> +</span><br><span class="line"> <span class="string">s"is above the max threshold (<span class="subst">$maxMem</span> MB) of this cluster! "</span> +</span><br><span class="line"> <span class="string">"Please check the values of 'yarn.scheduler.maximum-allocation-mb' and/or "</span> +</span><br><span class="line"> <span class="string">"'yarn.nodemanager.resource.memory-mb'."</span>)</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h3><span id="step3-启动am">Step3 启动AM</span></h3><p>如果你坚持看到了这里,那么你马上迎来最复杂的步骤,越过这座山,后面就是星辰大海。</p><p>我们知道ApplicationMaster是负责管理运行这个app的所有的容器的,所以我们首先要向RM请求一个容器用来启动AM.</p><p>我们要操作的类是 ApplicationSubmissionContext(后面简称ASC), 这个类定义了所有RM需要启动AM的信息。</p><p>作为客户端机我们主要需要set以下几个field</p><p>1 application info: 主要包括id 和name</p><ol start="2"><li><p>queue 提交的队列, priority 任务执行的优先级</p></li><li><p>user: 谁在向RM submmit app</p></li><li><p>ContainerLaunchContext:启动容器所需的上下文。我们后面简称是CLC</p></li></ol><p>CLC 主要包含:</p><p>a) local resources(binaries, jars, files) 本地的资源</p><p>b) Environment Settings(CLASSPATH etc) 容器所需的环境变量</p><p>c) the command to be executed 需要执行的命令</p><p>d) security tokens 处理安全tokens</p><p>好我们看如何操作这个类</p><p>首先解决clc</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 创建一个clc</span></span><br><span class="line"><span class="keyword">val</span> clc = <span class="type">Records</span>.newRecord(classOf[<span class="type">ContainerLaunchContext</span>])</span><br><span class="line"><span class="comment">// 然后set我们需要的fields</span></span><br><span class="line">clc.setCommands(<span class="literal">null</span>) <span class="comment">// am里要执行的命令</span></span><br><span class="line">clc.setLocalResources(<span class="literal">null</span>) <span class="comment">// 需要的本地资源</span></span><br><span class="line">clc.setEnvironment(<span class="literal">null</span>) <span class="comment">// am里运行所需的环境变量</span></span><br><span class="line">clc.setTokens(<span class="literal">null</span>) <span class="comment">// 安全验证tokens</span></span><br></pre></td></tr></table></figure></p><p>具体的implementations请看完整代码</p><p>然后ASC</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">val</span> clc = createContainerLaunchContext(newAppResponse) <span class="comment">// 创建一个clc</span></span><br><span class="line"><span class="keyword">val</span> asc = newApp.getApplicationSubmissionContext <span class="comment">// 创建一个asc</span></span><br><span class="line">asc.setResource(ascSetResource()) <span class="comment">// 设置am所需的资源</span></span><br><span class="line">asc.setAMContainerSpec(clc) <span class="comment">// 设置clc</span></span><br></pre></td></tr></table></figure></p><p>最后提交任务</p><p><figure class="highlight scala"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">yarnClient.submitApplication(asc)</span><br></pre></td></tr></table></figure></p><p>没了,不要怀疑,on yarn看上去非常复杂,但其实就是set, set ,set就搞定了。关键是你要了解整个架构运行的原理和机制这样才能set好。所有的xxx on yarn本质上都是如此(tensorflow on yarn, pytorch on yarn, mxnet on yarn, spark on yarn ,flink on yarn) 之后有空的话,我们再手把手把tensorflow dnn训练mnist搬到yarn上来做分布式</p><p>具体set的内容可以参考完整代码</p><h3><span id="writing-an-applicationmaster">Writing an ApplicationMaster</span></h3><p>下面该轮到AM了,AM我们再回顾下它的职责,</p><p>AM per application 每个应用一个</p><p>AM由RM来启动,通过client来提交</p><p>AM管理任务直到任务完成</p><p>我们看下官方解释</p><ul><li>The AM is the actual owner of the job. It will be launched by the RM and via the client will be provided all the necessary information and resources about the job that it has been tasked with to oversee and complete.</li><li>As the AM is launched within a container that may (likely will) be sharing a physical host with other containers, given the multi-tenancy nature, amongst other issues, it cannot make any assumptions of things like pre-configured ports that it can listen on.</li><li>When the AM starts up, several parameters are made available to it via the environment. These include the <code>ContainerId</code> for the AM container, the application submission time and details about the NM (NodeManager) host running the ApplicationMaster. Ref <code>ApplicationConstants</code> for parameter names.</li><li>All interactions with the RM require an <code>ApplicationAttemptId</code> (there can be multiple attempts per application in case of failures). The <code>ApplicationAttemptId</code> can be obtained from the AM’s container id. There are helper APIs to convert the value obtained from the environment into objects.</li></ul>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="1hadoop-yarn-is-simple">1.Hadoop Yarn is Simple</span></h1>
<p>这个教程将手把手教你如何把一个hello world搬到yarn上运行,然后我们会实现一个简易的pa
</summary>
<category term="Hadoop" scheme="http://www.yifanguo.top/tags/Hadoop/"/>
</entry>
<entry>
<title>SparkML源码分析</title>
<link href="http://www.yifanguo.top/2019/01/07/SparkML%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/"/>
<id>http://www.yifanguo.top/2019/01/07/SparkML源码分析/</id>
<published>2019-01-07T12:02:44.000Z</published>
<updated>2019-01-10T07:24:00.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="rdd-basic">RDD Basic</span></h1><h2><span id="transformation">Transformation</span></h2><p>rdd的操作分为transform和action</p><p>transform不会触发任务执行,action会</p><table><thead><tr><th>Transformation</th><th style="text-align:left">Meaning</th></tr></thead><tbody><tr><td><strong>map</strong>(<em>func</em>)</td><td style="text-align:left">Return a new distributed dataset formed by passing each element of the source through a function <em>func</em>.</td></tr><tr><td><strong>filter</strong>(<em>func</em>)</td><td style="text-align:left">Return a new dataset formed by selecting those elements of the source on which <em>func</em>returns true.</td></tr><tr><td><strong>flatMap</strong>(<em>func</em>)</td><td style="text-align:left">Similar to map, but each input item can be mapped to 0 or more output items (so <em>func</em>should return a Seq rather than a single item).</td></tr><tr><td><strong>mapPartitions</strong>(<em>func</em>)</td><td style="text-align:left">Similar to map, but runs separately on each partition (block) of the RDD, so <em>func</em> must be of type Iterator<T> => Iterator<U> when running on an RDD of type T.</td></tr><tr><td><strong>mapPartitionsWithIndex</strong>(<em>func</em>)</td><td style="text-align:left">Similar to mapPartitions, but also provides <em>func</em> with an integer value representing the index of the partition, so <em>func</em> must be of type (Int, Iterator<T>) => Iterator<U> when running on an RDD of type T.</td></tr><tr><td><strong>sample</strong>(<em>withReplacement</em>, <em>fraction</em>, <em>seed</em>)</td><td style="text-align:left">Sample a fraction <em>fraction</em> of the data, with or without replacement, using a given random number generator seed.</td></tr><tr><td><strong>union</strong>(<em>otherDataset</em>)</td><td style="text-align:left">Return a new dataset that contains the union of the elements in the source dataset and the argument.</td></tr><tr><td><strong>intersection</strong>(<em>otherDataset</em>)</td><td style="text-align:left">Return a new RDD that contains the intersection of elements in the source dataset and the argument.</td></tr><tr><td><strong>distinct</strong>([<em>numPartitions</em>]))</td><td style="text-align:left">Return a new dataset that contains the distinct elements of the source dataset.</td></tr><tr><td><strong>groupByKey</strong>([<em>numPartitions</em>])</td><td style="text-align:left">When called on a dataset of (K, V) pairs, returns a dataset of (K, Iterable<V>) pairs. <strong>Note:</strong> If you are grouping in order to perform an aggregation (such as a sum or average) over each key, using <code>reduceByKey</code> or <code>aggregateByKey</code> will yield much better performance. <strong>Note:</strong> By default, the level of parallelism in the output depends on the number of partitions of the parent RDD. You can pass an optional <code>numPartitions</code> argument to set a different number of tasks.</td></tr><tr><td><strong>reduceByKey</strong>(<em>func</em>, [<em>numPartitions</em>])</td><td style="text-align:left">When called on a dataset of (K, V) pairs, returns a dataset of (K, V) pairs where the values for each key are aggregated using the given reduce function <em>func</em>, which must be of type (V,V) => V. Like in <code>groupByKey</code>, the number of reduce tasks is configurable through an optional second argument.</td></tr><tr><td><strong>aggregateByKey</strong>(<em>zeroValue</em>)(<em>seqOp</em>, <em>combOp</em>, [<em>numPartitions</em>])</td><td style="text-align:left">When called on a dataset of (K, V) pairs, returns a dataset of (K, U) pairs where the values for each key are aggregated using the given combine functions and a neutral "zero" value. Allows an aggregated value type that is different than the input value type, while avoiding unnecessary allocations. Like in <code>groupByKey</code>, the number of reduce tasks is configurable through an optional second argument.</td></tr><tr><td><strong>sortByKey</strong>([<em>ascending</em>], [<em>numPartitions</em>])</td><td style="text-align:left">When called on a dataset of (K, V) pairs where K implements Ordered, returns a dataset of (K, V) pairs sorted by keys in ascending or descending order, as specified in the boolean <code>ascending</code> argument.</td></tr><tr><td><strong>join</strong>(<em>otherDataset</em>, [<em>numPartitions</em>])</td><td style="text-align:left">When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (V, W)) pairs with all pairs of elements for each key. Outer joins are supported through <code>leftOuterJoin</code>, <code>rightOuterJoin</code>, and <code>fullOuterJoin</code>.</td></tr><tr><td><strong>cogroup</strong>(<em>otherDataset</em>, [<em>numPartitions</em>])</td><td style="text-align:left">When called on datasets of type (K, V) and (K, W), returns a dataset of (K, (Iterable<V>, Iterable<W>)) tuples. This operation is also called <code>groupWith</code>.</td></tr><tr><td><strong>cartesian</strong>(<em>otherDataset</em>)</td><td style="text-align:left">When called on datasets of types T and U, returns a dataset of (T, U) pairs (all pairs of elements).</td></tr><tr><td><strong>pipe</strong>(<em>command</em>, <em>[envVars]</em>)</td><td style="text-align:left">Pipe each partition of the RDD through a shell command, e.g. a Perl or bash script. RDD elements are written to the process's stdin and lines output to its stdout are returned as an RDD of strings.</td></tr><tr><td><strong>coalesce</strong>(<em>numPartitions</em>)</td><td style="text-align:left">Decrease the number of partitions in the RDD to numPartitions. Useful for running operations more efficiently after filtering down a large dataset.</td></tr><tr><td><strong>repartition</strong>(<em>numPartitions</em>)</td><td style="text-align:left">Reshuffle the data in the RDD randomly to create either more or fewer partitions and balance it across them. This always shuffles all data over the network.</td></tr><tr><td><strong>repartitionAndSortWithinPartitions</strong>(<em>partitioner</em>)</td><td style="text-align:left">Repartition the RDD according to the given partitioner and, within each resulting partition, sort records by their keys. This is more efficient than calling <code>repartition</code> and then sorting within each partition because it can push the sorting down into the shuffle machinery.</td></tr></tbody></table><h2><span id="action">Action</span></h2><table><thead><tr><th>Action</th><th>Meaning</th></tr></thead><tbody><tr><td><strong>reduce</strong>(<em>func</em>)</td><td>Aggregate the elements of the dataset using a function <em>func</em> (which takes two arguments and returns one). The function should be commutative and associative so that it can be computed correctly in parallel.</td></tr><tr><td><strong>collect</strong>()</td><td>Return all the elements of the dataset as an array at the driver program. This is usually useful after a filter or other operation that returns a sufficiently small subset of the data.</td></tr><tr><td><strong>count</strong>()</td><td>Return the number of elements in the dataset.</td></tr><tr><td><strong>first</strong>()</td><td>Return the first element of the dataset (similar to take(1)).</td></tr><tr><td><strong>take</strong>(<em>n</em>)</td><td>Return an array with the first <em>n</em> elements of the dataset.</td></tr><tr><td><strong>takeSample</strong>(<em>withReplacement</em>, <em>num</em>, [<em>seed</em>])</td><td>Return an array with a random sample of <em>num</em> elements of the dataset, with or without replacement, optionally pre-specifying a random number generator seed.</td></tr><tr><td><strong>takeOrdered</strong>(<em>n</em>, <em>[ordering]</em>)</td><td>Return the first <em>n</em> elements of the RDD using either their natural order or a custom comparator.</td></tr><tr><td><strong>saveAsTextFile</strong>(<em>path</em>)</td><td>Write the elements of the dataset as a text file (or set of text files) in a given directory in the local filesystem, HDFS or any other Hadoop-supported file system. Spark will call toString on each element to convert it to a line of text in the file.</td></tr><tr><td><strong>saveAsSequenceFile</strong>(<em>path</em>) (Java and Scala)</td><td>Write the elements of the dataset as a Hadoop SequenceFile in a given path in the local filesystem, HDFS or any other Hadoop-supported file system. This is available on RDDs of key-value pairs that implement Hadoop's Writable interface. In Scala, it is also available on types that are implicitly convertible to Writable (Spark includes conversions for basic types like Int, Double, String, etc).</td></tr><tr><td><strong>saveAsObjectFile</strong>(<em>path</em>) (Java and Scala)</td><td>Write the elements of the dataset in a simple format using Java serialization, which can then be loaded using<code>SparkContext.objectFile()</code>.</td></tr><tr><td><strong>countByKey</strong>()</td><td>Only available on RDDs of type (K, V). Returns a hashmap of (K, Int) pairs with the count of each key.</td></tr><tr><td><strong>foreach</strong>(<em>func</em>)</td><td>Run a function <em>func</em> on each element of the dataset. This is usually done for side effects such as updating an <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#accumulators">Accumulator</a> or interacting with external storage systems. <strong>Note</strong>: modifying variables other than Accumulators outside of the <code>foreach()</code> may result in undefined behavior. See <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#understanding-closures-a-nameclosureslinka">Understanding closures </a>for more details.</td></tr></tbody></table><h2><span id="shuffle">shuffle</span></h2><p>shuffle就是洗牌的意思,意味着重新分配数据,我们看官方的解释:</p><p>Certain operations within Spark trigger an event known as the shuffle. The shuffle is Spark’s mechanism for re-distributing data so that it’s grouped differently across partitions. This typically involves copying data across executors and machines, making the shuffle a complex and costly operation.</p><p>从reduceByKey来理解shuffle</p><p>thus, to organize all the data for a single <code>reduceByKey</code> reduce task to execute, Spark needs to perform an all-to-all operation. It must read from all partitions to find all the values for all keys, and then bring together values across partitions to compute the final result for each key - this is called the <strong>shuffle</strong>.</p><p>什么操作会触发shuffle</p><p>Operations which can cause a shuffle include <strong>repartition</strong> operations like <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#RepartitionLink"><code>repartition</code></a> and <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#CoalesceLink"><code>coalesce</code></a>, <strong>‘ByKey</strong> operations (except for counting) like <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#GroupByLink"><code>groupByKey</code></a> and <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#ReduceByLink"><code>reduceByKey</code></a>, and <strong>join</strong> operations like <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#CogroupLink"><code>cogroup</code></a> and <a href="https://spark.apache.org/docs/latest/rdd-programming-guide.html#JoinLink"><code>join</code></a>.</p><p>shuffle操作非常昂贵:</p><p>The <strong>Shuffle</strong> is an expensive operation since it involves disk I/O, data serialization, and network I/O.</p><p>Internally, results from individual map tasks are kept in memory until they can’t fit. Then, these are sorted based on the target partition and written to a single file. On the reduce side, tasks read the relevant sorted blocks.</p><p>When data does not fit in memory Spark will spill these tables to disk, incurring the additional overhead of disk I/O and increased garbage collection.</p><p>shuffle当内存不够时会使用磁盘</p><p>Shuffle also generates a large number of intermediate files on disk. As of Spark 1.3, these files are preserved until the corresponding RDDs are no longer used and are garbage collected. This is done so the shuffle files don’t need to be re-created if the lineage is re-computed. Garbage collection may happen only after a long period of time, if the application retains references to these RDDs or if GC does not kick in frequently. This means that long-running Spark jobs may consume a large amount of disk space. The temporary storage directory is specified by the<code>spark.local.dir</code> configuration parameter when configuring the Spark context.</p><h2><span id="broadcast">broadcast</span></h2><p>Broadcast variables allow the programmer to keep a read-only variable cached on each machine rather than shipping a copy of it with tasks.</p><h2><span id="accumulators">Accumulators</span></h2><p>Accumulators are variables that are only “added” to through an associative and commutative operation and can therefore be efficiently supported in parallel.</p><h1><span id="spark-streaming">Spark Streaming</span></h1><p>先看官方简介</p><p>Spark Streaming is an extension of the core Spark API that enables scalable, high-throughput, fault-tolerant stream processing of live data streams.</p><p>Data can be ingested from many sources like Kafka, Flume, Kinesis, or TCP sockets, and can be processed using complex algorithms expressed with high-level functions like <code>map</code>, <code>reduce</code>, <code>join</code> and <code>window</code>. Finally, processed data can be pushed out to filesystems, databases, and live dashboards.</p><p>其实spark streaming可以看成是间隔很小的批处理</p><p>虽然spark streaming是近似实时,但是throughput更大</p><h1><span id="latex">Latex</span></h1><p>$$ Ent(D) = - \sum_{i=1}^{|Y|} p_{k}\log_{2}p_{k}$$</p><h1><span id="决策树">决策树</span></h1><h2><span id="如何划分属性">如何划分属性</span></h2><p>在决策树算法中,如何选择最优划分属性是最关键的一步。一般而言,随着划分过程的不断进行,我们希望决策树的分支节点所包含的样本尽可能属于同一类别,即节点的“纯度(purity)”越来越高。 有几种度量样本集合纯度的指标。在<code>MLlib</code>中,信息熵和基尼指数用于决策树分类,方差用于决策树回归。</p><h2><span id="信息熵">信息熵</span></h2><p>信息熵是度量样本集合纯度最常用的一种指标,假设当前样本集合<code>D</code>中第<code>k</code>类样本所占的比例为<code>p_k</code>,则<code>D</code>的信息熵定义为:</p><p>$$ Ent(D) = - \sum_{i=1}^{|Y|} p_{k}\log_{2}p_{k}$$</p><p><code>Ent(D)</code>的值越小,则<code>D</code>的纯度越高</p><h2><span id="gini基尼系数">Gini基尼系数</span></h2><p>$$ Gini(D) = 1- \sum_{i=1}^{|Y|} p_{k}^2$$</p><p>直观来说,<code>Gini(D)</code>反映了从数据集<code>D</code>中随机取样两个样本,其类别标记不一致的概率。因此,<code>Gini(D)</code>越小,则数据集<code>D</code>的纯度越高。</p><h2><span id="方差">方差</span></h2><p>$$ Var(D) = 1/N \sum_{i=1}^{N} (y_{i} - 1/N\sum_{i=1}^{N}y_{i})$$</p><p>这个也是sparkMLlib使用的方式</p><p>##信息增益</p><p>假设切分大小为<code>N</code>的数据集<code>D</code>为两个数据集<code>D_left</code>和<code>D_right</code>,那么信息增益可以表示为如下的形式</p><p>$$IG(D,s) = impurity(D) - \frac{N_{left}}{N} impurity(D) -\frac{N_{right}}{N} impurity(D) $$</p><p>imformation gain</p><p>一般情况下,信息增益越大,则意味着使用属性<code>a</code>来进行划分所获得的纯度提升越大。因此我们可以用信息增益来进行决策树的划分属性选择。</p><h2><span id="决策树的缺点"><strong>决策树的缺点:</strong></span></h2><p>1.对那些各类别数据量不一致的数据,在决策树种,</p><p>信息增益的结果偏向那些具有更多数值的特征</p><p>2.容易过拟合</p><p>3.忽略了数据集中属性之间的相关性</p><h1><span id="随机森林">随机森林</span></h1><h2><span id="1-bagging">1 bagging</span></h2><p><code>Bagging</code>采用自助采样法(<code>bootstrap sampling</code>)采样数据。给定包含<code>m</code>个样本的数据集,我们先随机取出一个样本放入采样集中,再把该样本放回初始数据集,使得下次采样时,样本仍可能被选中, 这样,经过<code>m</code>次随机采样操作,我们得到包含<code>m</code>个样本的采样集。</p><p>按照此方式,我们可以采样出<code>T</code>个含<code>m</code>个训练样本的采样集,然后基于每个采样集训练出一个基本学习器,再将这些基本学习器进行结合。这就是<code>Bagging</code>的一般流程。在对预测输出进行结合时,<code>Bagging</code>通常使用简单投票法, 对回归问题使用简单平均法。若分类预测时,出现两个类收到同样票数的情形,则最简单的做法是随机选择一个,也可以进一步考察学习器投票的置信度来确定最终胜者。</p><p><code>Bagging</code>的算法描述如下图所示。</p><p>输入: 训练集 D={(x1,y1),...}基础学习算法 $$ B$$训练次数T过程:</p><ol><li><p>for t=1,2,...., T do</p></li><li><p>$$h_{t} = B(D, D_{bs})$$</p></li><li><p>End for</p><p>输出:$$H_{x} = argmax {y}\in{Y} \sum_{t=1}^{T}I(h_{t}(x) = y)$$</p></li></ol><h2><span id="2-随机森林">2 随机森林</span></h2><p>随机森林是<code>Bagging</code>的一个扩展变体。随机森林在以决策树为基学习器构建<code>Bagging</code>集成的基础上,进一步在决策树的训练过程中引入了随机属性选择。具体来讲,传统决策树在选择划分属性时, 在当前节点的属性集合(假设有<code>d</code>个属性)中选择一个最优属性;而在随机森林中,对基决策树的每个节点,先从该节点的属性集合中随机选择一个包含<code>k</code>个属性的子集,然后再从这个子集中选择一个最优属性用于划分。 这里的参数<code>k</code>控制了随机性的引入程度。若令<code>k=d</code>,则基决策树的构建与传统决策树相同;若令<code>k=1</code>,则是随机选择一个属性用于划分。在<code>MLlib</code>中,有两种选择用于分类,即<code>k=log2(d)</code>、<code>k=sqrt(d)</code>; 一种选择用于回归,即<code>k=1/3d</code>。</p><p>可以看出,随机森林对<code>Bagging</code>只做了小改动,但是与<code>Bagging</code>中基学习器的“多样性”仅仅通过样本扰动(通过对初始训练集采样)而来不同,随机森林中基学习器的多样性不仅来自样本扰动,还来自属性扰动。 这使得最终集成的泛化性能可通过个体学习器之间差异度的增加而进一步提升。</p><h2><span id="3-随机森林在分布式环境下的优化策略">3 随机森林在分布式环境下的优化策略</span></h2><ul><li>切分点抽样统计</li><li>特征装箱(<code>Binning</code>)</li><li>逐层训练(<code>level-wise training</code>)</li></ul><h1><span id="梯度提升树">梯度提升树</span></h1><h2><span id="1-boosting">1 boosting</span></h2><p><code>Boosting</code>是一类将弱学习器提升为强学习器的算法。这类算法的工作机制类似:先从初始训练集中训练出一个基学习器,再根据基学习器的表现对训练样本分布进行调整,使得先前基学习器做错的训练样本在后续受到更多关注。 然后基于调整后的样本分布来训练下一个基学习器;如此重复进行,直至基学习器的数目达到事先指定的值<code>T</code>,最终将这<code>T</code>个基学习器进行加权结合。</p><p><code>Boost</code>算法是在算法开始时,为每一个样本赋上一个相等的权重值,也就是说,最开始的时候,大家都是一样重要的。 在每一次训练中得到的模型,会使得数据点的估计有所差异,所以在每一步结束后,我们需要对权重值进行处理,而处理的方式就是通过<strong>增加错分点的权重</strong>,这样使得某些点如果老是被分错,那么就会被“严重关注”,也就被赋上一个很高的权重。 然后等进行了<code>N</code>次迭代,将会得到<code>N</code>个简单的基分类器(<code>basic learner</code>),最后将它们组合起来,可以对它们进行加权(错误率越大的基分类器权重值越小,错误率越小的基分类器权重值越大)、或者让它们进行投票等得到一个最终的模型。</p><p>梯度提升(<code>gradient boosting</code>)属于<code>Boost</code>算法的一种,也可以说是<code>Boost</code>算法的一种改进,它与传统的<code>Boost</code>有着很大的区别,它的每一次计算都是为了减少上一次的残差(<code>residual</code>),而为了减少这些残差,可以在残差减少的梯度(<code>Gradient</code>)方向上建立一个新模型。所以说,在<code>Gradient Boost</code>中,每个新模型的建立是为了使得先前模型残差往梯度方向减少, 与传统的<code>Boost</code>算法对正确、错误的样本进行加权有着极大的区别。</p><p>梯度提升算法的核心在于,每棵树是从先前所有树的残差中来学习。<strong>利用的是当前模型中损失函数的负梯度值作为提升树算法中的残差的近似值</strong>,进而拟合一棵回归(分类)树。</p><p>##2 梯度提升</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/SparkML%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90/gbdt2.png" alt="gbdt2"></p><h2><span id="3-随机梯度提升">3 随机梯度提升</span></h2><p>有文献证明,注入随机性到上述的过程中可以提高函数估计的性能。受到<code>Breiman</code>的影响,将随机性作为一个考虑的因素。在每次迭代中,随机的在训练集中抽取一个子样本集,然后在后续的操作中用这个子样本集代替全体样本。</p><h1><span id="线性模型">线性模型</span></h1><h2><span id="1-数学描述">1 数学描述</span></h2><p>许多标准的机器学习算法可以归结为凸优化问题。例如,找到凸函数<code>f</code>的一个极小值的任务,这个凸函数依赖于可变向量<code>w</code>(在<code>spark</code>源码中,一般表示为<code>weights</code>)。 形式上,我们可以将其当作一个凸优化问题${min}_{w}f(w)$。它的目标函数可以表示为如下公式 <strong>(1)</strong>:</p><p>$$f(W) = \lambda R(W) + \frac{1}{n} \sum_{i=1}^{n}L(w; x_{i}, y{i})$$</p><p>在上式中,向量<code>x</code>表示训练数据集,<code>y</code>表示它相应的标签,也是我们想预测的值。如果<code>L(w;x,y)</code>可以表示为${w}^{T}x$和<code>y</code>的函数, 我们称这个方法为线性的。<code>spark.mllib</code>中的几种分类算法和回归算法可以归为这一类。</p><p>目标函数<code>f</code>包含两部分:正则化(<code>regularizer</code>),用于控制模型的复杂度;损失函数,用于度量模型的误差。损失函数<code>L(w;.)</code>是一个典型的基于<code>w</code>的凸函数。固定的正则化参数<code>gamma</code>定义了两种目标的权衡(<code>trade-off</code>), 这两个目标分别是最小化损失(训练误差)以及最小化模型复杂度(为了避免过拟合)。</p><h1><span id="spark-ml整体架构">Spark ML整体架构</span></h1><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line">MLlib standardizes APIs for machine learning algorithms to make it easier to combine multiple algorithms into a single pipeline, or workflow. This section covers the key concepts introduced by the Pipelines API, where the pipeline concept is mostly inspired by the scikit-learn project.</span><br><span class="line"></span><br><span class="line">DataFrame: This ML API uses DataFrame from Spark SQL as an ML dataset, which can hold a variety of data types. E.g., a DataFrame could have different columns storing text, feature vectors, true labels, and predictions.</span><br><span class="line"></span><br><span class="line">Transformer: A Transformer is an algorithm which can transform one DataFrame into another DataFrame. E.g., an ML model is a Transformer which transforms a DataFrame with features into a DataFrame with predictions.</span><br><span class="line"></span><br><span class="line">Estimator: An Estimator is an algorithm which can be fit on a DataFrame to produce a Transformer. E.g., a learning algorithm is an Estimator which trains on a DataFrame and produces a model.</span><br><span class="line"></span><br><span class="line">Pipeline: A Pipeline chains multiple Transformers and Estimators together to specify an ML workflow.</span><br><span class="line"></span><br><span class="line">Parameter: All Transformers and Estimators now share a common API for specifying parameters.</span><br></pre></td></tr></table></figure></p><p>sparkML基本上是按照上述介绍搭起来的,最基本的是PipelineStage 是Pipeline中的一步。</p><p>主要是estimator 和 transformer.</p><p>数据类型是dataFrame</p><h2><span id="estimator">Estimator</span></h2><p>estimator就一个method 就是fit</p><p><pre class="mermaid">graph LR;ProbabilisticClassifier --> Classifier;Classifier --> Predictor;Predictor --> Estimator;</pre></p><p>ProbabilisticClassifier</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">class DecisionTreeClassifier @Since("1.4.0") (</span><br><span class="line"> @Since("1.4.0") override val uid: String)</span><br><span class="line"> extends ProbabilisticClassifier[Vector, DecisionTreeClassifier, DecisionTreeClassificationModel]</span><br></pre></td></tr></table></figure></p><h2><span id="transformer">Transformer</span></h2><p>transformer就是把一个df转成另一个df</p><h1><span id="logistic-regression">Logistic Regression</span></h1><p>spark的LR支持二分类和多分类,默认是二分类</p><p>看看LR的train都干了什么</p><p>首先LR支持样本有权重,只要df里有weight就行</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><span class="line">val instances: RDD[Instance] =</span><br><span class="line"> dataset.select(col($(labelCol)), w, col($(featuresCol))).rdd.map {</span><br><span class="line"> case Row(label: Double, weight: Double, features: Vector) =></span><br><span class="line"> Instance(label, weight, features)</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (handlePersistence) instances.persist(StorageLevel.MEMORY_AND_DISK)</span><br></pre></td></tr></table></figure></p><p>这一步是把df里的label, weight, features都挑出来,如果没有weight 就默认是1</p><p>instances.persist 是确定数据的储存级别,默认就是MEMORY_AND_DISK</p><p>我们常用的rdd.cache() 本质上是 persist(StorageLevel.MEMORY_ONLY) 即把rdd的数据加载到内存</p><p>关于spark对内存的管理,可以看</p><p>https://www.ibm.com/developerworks/cn/analytics/library/ba-cn-apache-spark-memory-management/index.html</p><p>https://www.jianshu.com/u/5b15278387a0</p><p>中间用的是Breeze的LBFGS优化器</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">new BreezeOWLQN[Int, BDV[Double]]($(maxIter), 10, regParamL1Fun, $(tol))</span><br></pre></td></tr></table></figure></p><p>最后组装model</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">val model = copyValues(new LogisticRegressionModel(uid, coefficientMatrix, interceptVector,</span><br><span class="line"> numClasses, isMultinomial))</span><br></pre></td></tr></table></figure></p><h2><span id="treeaggregate">TreeAggregate</span></h2><p>treeAggregate也是spark rdd一个非常重要的操作,是数据聚合操作</p><p>可以看这篇</p><p>https://blog.csdn.net/u011724402/article/details/79057450</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">与aggregate不同的是treeAggregate多了depth的参数,其他参数含义相同。aggregate在执行完SeqOp后会将计算结果拿到driver端使用CombOp遍历一次SeqOp计算的结果,最终得到聚合结果。而treeAggregate不会一次就Comb得到最终结果,SeqOp得到的结果也许很大,直接拉到driver可能会OutOfMemory,因此它会先把分区的结果做局部聚合(reduceByKey),如果分区数过多时会做分区合并,之后再把结果拿到driver端做reduce</span><br></pre></td></tr></table></figure></p><h2><span id="gradientdescent">GradientDescent</span></h2><p>所有在spark上实现并行的ML的算法都可以参考梯度下降的做法</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line">while (!converged && i <= numIterations) {</span><br><span class="line">// 把权重分发给executor, 每轮迭代都要发一次!?这就是spark的瓶颈</span><br><span class="line"> val bcWeights = data.context.broadcast(weights)</span><br><span class="line"> // Sample a subset (fraction miniBatchFraction) of the total data</span><br><span class="line"> // compute and sum up the subgradients on this subset (this is one map-reduce)</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> val (gradientSum, lossSum, miniBatchSize) = data.sample(false, miniBatchFraction, 42 + i)</span><br><span class="line"> .treeAggregate((BDV.zeros[Double](n), 0.0, 0L))(</span><br><span class="line"> seqOp = (c, v) => {</span><br><span class="line"> // c: (grad, loss, count), v: (label, features)</span><br><span class="line"> val l = gradient.compute(v._2, v._1, bcWeights.value, Vectors.fromBreeze(c._1))</span><br><span class="line"> (c._1, c._2 + l, c._3 + 1)</span><br><span class="line"> },</span><br><span class="line"> combOp = (c1, c2) => {</span><br><span class="line"> // c: (grad, loss, count)</span><br><span class="line"> (c1._1 += c2._1, c1._2 + c2._2, c1._3 + c2._3)</span><br><span class="line"> })</span><br><span class="line"> bcWeights.destroy(blocking = false)</span><br><span class="line"></span><br><span class="line"> if (miniBatchSize > 0) {</span><br><span class="line"> /**</span><br><span class="line"> * lossSum is computed using the weights from the previous iteration</span><br><span class="line"> * and regVal is the regularization value computed in the previous iteration as well.</span><br><span class="line"> */</span><br><span class="line"> stochasticLossHistory += lossSum / miniBatchSize + regVal</span><br><span class="line"> val update = updater.compute(</span><br><span class="line"> weights, Vectors.fromBreeze(gradientSum / miniBatchSize.toDouble),</span><br><span class="line"> stepSize, i, regParam)</span><br><span class="line"> weights = update._1</span><br><span class="line"> regVal = update._2</span><br><span class="line"></span><br><span class="line"> previousWeights = currentWeights</span><br><span class="line"> currentWeights = Some(weights)</span><br><span class="line"> if (previousWeights != None && currentWeights != None) {</span><br><span class="line"> converged = isConverged(previousWeights.get,</span><br><span class="line"> currentWeights.get, convergenceTol)</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> logWarning(s"Iteration ($i/$numIterations). The size of sampled batch is zero")</span><br><span class="line"> }</span><br><span class="line"> i += 1</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2><span id="rdd-sampling">RDD sampling</span></h2><p>RDD提供了sampling的接口</p><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line">/**</span><br><span class="line"> * Return a sampled subset of this RDD.</span><br><span class="line"> *</span><br><span class="line"> * @param withReplacement can elements be sampled multiple times (replaced when sampled out)</span><br><span class="line"> * @param fraction expected size of the sample as a fraction of this RDD's size</span><br><span class="line"> * without replacement: probability that each element is chosen; fraction must be [0, 1]</span><br><span class="line"> * with replacement: expected number of times each element is chosen; fraction must be greater</span><br><span class="line"> * than or equal to 0</span><br><span class="line"> * @param seed seed for the random number generator</span><br><span class="line"> *</span><br><span class="line"> * @note This is NOT guaranteed to provide exactly the fraction of the count</span><br><span class="line"> * of the given [[RDD]].</span><br><span class="line"> */</span><br><span class="line"> def sample(</span><br><span class="line"> withReplacement: Boolean,</span><br><span class="line"> fraction: Double,</span><br><span class="line"> seed: Long = Utils.random.nextLong): RDD[T] = {</span><br><span class="line"> require(fraction >= 0,</span><br><span class="line"> s"Fraction must be nonnegative, but got ${fraction}")</span><br><span class="line"></span><br><span class="line"> withScope {</span><br><span class="line"> require(fraction >= 0.0, "Negative fraction value: " + fraction)</span><br><span class="line"> if (withReplacement) {</span><br><span class="line"> new PartitionwiseSampledRDD[T, T](this, new PoissonSampler[T](fraction), true, seed)</span><br><span class="line"> } else {</span><br><span class="line"> new PartitionwiseSampledRDD[T, T](this, new BernoulliSampler[T](fraction), true, seed)</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="rdd-basic">RDD Basic</span></h1>
<h2><span id="transformation">Transformation</span></h2>
<p>rdd的操作分为transform和ac
</summary>
<category term="spark" scheme="http://www.yifanguo.top/tags/spark/"/>
</entry>
<entry>
<title>LightGBM模型解读</title>
<link href="http://www.yifanguo.top/2019/01/02/LightGBM%E6%A8%A1%E5%9E%8B%E8%A7%A3%E8%AF%BB/"/>
<id>http://www.yifanguo.top/2019/01/02/LightGBM模型解读/</id>
<published>2019-01-02T10:32:11.000Z</published>
<updated>2019-01-02T10:59:49.000Z</updated>
<content type="html"><![CDATA[<p>https://zhuanlan.zhihu.com/p/28768447</p><h2><span id="训练配置"><strong>训练配置:</strong></span></h2><p>6307410个样本做训练集</p><p><strong>num_trees</strong> = 2 // 树的棵树</p><p><strong>categorical_feature</strong>=1,2,3,5,6,8,299 //类别特征编号</p><p><strong>num_leaves</strong> = 6 // 每棵树的叶子数</p><p><strong>max_depth</strong> = 3 // 树的深度</p><p><strong>learning_rate</strong> = 0.1 // 学习率</p><p><strong>bagging_fraction</strong> = 0.8 // 样本采样比例</p><h2><span id="训练出的lightgbm模型文件及其含义解析"><strong>训练出的LightGBM模型文件及其含义解析:</strong></span></h2><p><strong>tree</strong> // 模型中子模型的名字,gbdt的子模型是tree</p><p><strong>num_class</strong>=1 // 类别数量,二分类问题变成了概率问题</p><p><strong>label_index</strong>=0 // lable所在列</p><p><strong>max_feature_idx</strong>=1365 //最大的特征index, 0~1365,LightGBM认为特征从0开始编码</p><p>(这个+1就是特征纬度)</p><p><strong>objective</strong>=binary //学习目标</p><p><strong>sigmoid</strong>=1 //结果输出时sigmoid的参数 output[0] = 1.0f / (1.0f + std::exp(-sigmoid_ * input[0]));,越大sigmoid越陡峭</p><p><strong>feature_names</strong>=Column_0 Column_1 Column_2 ... Column_1363 Column_1364 Column_1365 // 特征名字,就是”Column_” + 数据中特征index</p><p><strong>feature_infos</strong>=none 0:1 [0:10742] 1487112:0 [0:3999191] ...</p><p>// 没有“[]”的是category 特征的bin中的特征取值</p><p>// 有“[]”是数值特征的bin中的最小、最大值</p><p>// none表示此特征没有使用</p><p>// 第1棵树</p><p><strong>Tree</strong>=0 // 树的编号,从0开始</p><p><strong>num_leaves</strong>=6 // 树中叶子的数量</p><p><strong>split_feature</strong>=150 197 381 63 197 //6个叶子,分裂5次,有5个内部节点和分裂特征,这个特征编号是在原样本中的特征编号</p><p><strong>split_gain</strong>=579239.62681873201 101591.49813184602 78186.521895228478 75276.734034747526 57589.418844881991 // 每次分裂的增益</p><p><strong>threshold</strong>=0.028499999999999998 0.016500000000000001 554.04549999999995 3.1340000000000003 0.043499999999999997 // 分裂特征的特征值分界点</p><p><strong>decision_type</strong>=0 0 0 0 0 //5个内部节点的判定类型值,判定类型值是int8_t,以bit形式,第一低位存储是否是category特征,第二低位存储是否使用左子节点作为默认去向,第三、第四低位存储是None(0)、Zero(1)、NaN(2)中的哪种</p><p><strong>left_child</strong>=1 3 -2 -1 -4</p><p><strong>right_child</strong>=2 -3 4 -5 -6</p><p><strong>leaf_parent</strong>=3 2 1 4 3 4</p><p>// 树的结构</p><p>// 有5个内部节点,默认编号是:0、1、2、3、4</p><p>// 有6个叶子节点,编号分别是:-1、-2、-3、-4、-5、-6</p><p>// left_child表示这5个内部节点的左子节点,正值表示内部节点的节点编号,负值表示叶子节点的节点编号</p><p>// right_child表示这5个内部节点的左子节点</p><p>// leaf_parent 表示-1、-2、-3、-4、-5、-6这6个叶子节点的父节点编号</p><p>// 于是这个树的结构就是这样</p><p>![屏幕快照 2019-01-02 18.37.00](/Users/yifanguo/Desktop/blog/source/_posts/LightGBM模型解读/屏幕快照 2019-01-02 18.37.00.png)</p><p><strong>leaf_value</strong>=0.013151525839652695 -0.0031140914212769983 -0.017382907119786403 0.038475160439658297 -0.10110187665371873 0.091299535945193661 //各个叶子节点的预测值</p><p><strong>leaf_count</strong>=171831 511580 1078379 893167 1432378 958588 // 各个叶子节点的样本数量,这里总共有5045923个</p><p><strong>internal_value</strong>=0 -0.55733646225250466 0.54728595683818304 -0.85735596237957235 0.67893796844992116 // 各个中间节点的预测值</p><p><strong>internal_count</strong>=5045923 2682588 2363335 1604209 1851755 // 各个中间节点的样本数,1604209[中间节点3的样本数] = 171831 [叶子节点-1的样本数] + 1432378[叶子节点-5的样本数]</p><p>//可以看出这棵树的训练只用了5045923个样本,而实际训练集样本有6307410个,这是因为在模型配置文件中设置了采样比例为0.8</p><p><strong>shrinkage</strong>=0.1 // 设定的学习率</p><p>// 第二棵树,含义参考第一棵树</p><p>Tree=1</p><p>num_leaves=6</p><p>split_feature=145 161 198 11 381</p><p>split_gain=474253.30131810816 93455.112333323515 62969.704987476958 55878.668231101008 32961.303899061735</p><p>threshold=0.026500000000000003 0.018500000000000003 0.043499999999999997 8.4154999999999998 663.125</p><p>decision_type=0 0 0 0 0</p><p>left_child=1 3 4 -1 -2</p><p>right_child=2 -3 -4 -5 -6</p><p>leaf_parent=3 4 1 2 3 4</p><p>leaf_value=0.010494795842311992 -0.024170274578830017 -0.010405728632592726 0.075110240965977765 -0.08865782202254327 0.038228215007066219</p><p>leaf_count=167445 301508 975432 1063548 1556038 981952</p><p>internal_value=0 -0.50125289035240339 0.49837677764421778 -0.76617891719378095 0.25393645325883307</p><p>internal_count=5045923 2698915 2347008 1723483 1283460</p><p>shrinkage=0.1</p><p>// 特征重要性</p><p><strong>feature importances</strong>:</p><p><strong>Column_197=2</strong> // 特征197,重要性排最高,重要性值为2,表示在所有树中有两次作为中间节点的分裂特征</p><p>Column_381=2 // 所有树中有两次作为中间节点的分裂特征</p><p>Column_11=1 // 所有树中有一次作为中间节点的分裂特征</p><p>Column_63=1</p><p>Column_145=1</p><p>Column_150=1</p><p>Column_161=1</p><p>Column_198=1</p><p>// 重要性值是统计特征在所有树中作为中间节点(分裂节点)的分裂特征且分裂增益为正的次数,可以理解成是对分裂作用越大的特征越重要</p>]]></content>
<summary type="html">
<p>https://zhuanlan.zhihu.com/p/28768447</p>
<h2><span id="训练配置"><strong>训练配置:</strong></span></h2>
<p>6307410个样本做训练集</p>
<p><strong>num_tre
</summary>
<category term="lgb" scheme="http://www.yifanguo.top/tags/lgb/"/>
</entry>
<entry>
<title>glint</title>
<link href="http://www.yifanguo.top/2018/12/28/glint/"/>
<id>http://www.yifanguo.top/2018/12/28/glint/</id>
<published>2018-12-28T02:51:54.000Z</published>
<updated>2019-01-08T03:35:44.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="目的">目的</span></h1><p>实在受不鸟这些复杂的又充满bug的参数服务器,我就想要一个用jvm语言写的参数分布式缓存而已</p><h1><span id="调研glint">调研Glint</span></h1><p>简单,能看明白</p><h1><span id="ps就两个操作">PS就两个操作</span></h1><p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">(1) Asynchronously ‘Pull’ data from the servers. </span><br><span class="line">this will query parts of the matrix or vector.</span><br><span class="line">(2) Asynchronously ‘Push’ data to the servers. </span><br><span class="line">this will update parts of the matrix or vector.</span><br></pre></td></tr></table></figure></p><p>简化到就两个操作,pull和push</p><h1><span id="ps的目的">PS的目的</span></h1><p>ps的目的就是存放一个大型的分布式matrix,想那么多没用。</p><p>并且可以让用户快速query和update这个matrix</p><p>把参数想成个表,这里用redis也可以替代</p><p>这需要去切分matrix, 每个节点只存放matrix中的几行</p><p>算法只需要通过pull和push 来和这个matrix进行交互,不需要知道data的物理位置</p><h1><span id="pull-action">Pull Action</span></h1><p>Whenever an algorithm wants to retrieve entries from the matrixit will call the pull method. is method triggers an asynchronouspull request with a specific set of row and column indices thatshould be retrieved.</p><p>The request is split up into smaller requestsbased on the partitioning of the matrix such that there will be atmost one request per parameter server.</p><p>这句话没看明白,怎么算是一个ps至多一个请求</p><h1><span id="源码解读">源码解读</span></h1><h2><span id="几个点">几个点</span></h2><ol><li>数据是用dense来表示的</li></ol><h2><span id="先看组成">先看组成</span></h2><p>exceptions</p><p>iterators</p><p>messages</p><p>models</p><p>partitioning</p><p>serialization</p><p>utils</p><p>yarn</p><p>client</p><p>main</p><p>master</p><p>server</p><h2><span id="exceptions">exceptions</span></h2><p>三个exceptions model创建,pull push exceptions</p><h2><span id="iterators迭代器">iterators迭代器</span></h2><p>先看基类</p><p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><span class="line">PipelineIterator</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">abstract class PipelineIterator[T](duration: Duration = Duration.Inf)(implicit ec: ExecutionContext) extends Iterator[T] {</span><br><span class="line"></span><br><span class="line"> <span class="comment">// 获取下一个,返回是一个Future</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> def <span class="title">fetchNextFuture</span><span class="params">()</span>: Future[T]</span></span><br><span class="line"><span class="function"></span></span><br><span class="line"><span class="function"> <span class="keyword">protected</span> var index: Int </span>= <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">protected</span> var total: Int = <span class="number">0</span></span><br><span class="line"> </span><br><span class="line"> <span class="keyword">private</span> var nextFuture: Future[T] = fetchNextFuture()</span><br><span class="line"></span><br><span class="line"><span class="comment">// 当前index没有到total 就继续</span></span><br><span class="line"> override def hasNext: Boolean = index < total</span><br><span class="line"></span><br><span class="line"><span class="comment">// 下一个</span></span><br><span class="line"> <span class="function">override def <span class="title">next</span><span class="params">()</span>: T </span>= {</span><br><span class="line"> </span><br><span class="line"> <span class="comment">//Await and return the result </span></span><br><span class="line"> <span class="comment">// duration 超时</span></span><br><span class="line"> val result = Await.result(nextFuture, duration)</span><br><span class="line"> index += <span class="number">1</span></span><br><span class="line"> <span class="keyword">if</span> (hasNext) {</span><br><span class="line"> nextFuture = fetchNextFuture()</span><br><span class="line"> }</span><br><span class="line"> result</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>三个集成</p><p>ColumnIterator</p><p>RowBlockIterator</p><p>RowIterator</p><h2><span id="消息messages">消息messages</span></h2><h3><span id="master">master</span></h3><p>ClientList</p><p>RegisterClient</p><p>RegisterServer</p><p>ServerList</p><p>这些消息也很简答, 就是注册client server以及拿到list</p><h3><span id="servers">servers</span></h3><p>包含了logic, request ,response 主要是和ps交互的信息</p><h2><span id="models模型在client和server端的操作">Models模型在client和server端的操作</span></h2><h3><span id="client">client</span></h3><p>首先客户端这边包含两个trait, BigMatrix, BigVector</p><p>BigMatrix</p><ul><li><p>rows: Long</p></li><li><p>Cols:Int</p></li><li><p>pull</p></li><li><p>push</p></li><li><p>destroy</p></li><li><p>save</p></li></ul><p>BigVector</p><ul><li>size</li><li>pull</li><li>push</li><li>destroy</li><li>save</li></ul><p>Client还有三个基于上述两个trait的实现</p><p>先看async</p><h3><span id="async">async</span></h3><p>重点是async的matrix</p><h2><span id="partitioning">partitioning</span></h2><p>这个模块是重点中的重点,详细来看,参数如何分区的</p><p>主要包含了两个cyclic 和 range</p><p>cyclic的是 轮询, number of partitions就是ps的node数量</p><p>range是自定义划分</p><p>##serialization</p><p>使用了unsafe和bytebuffer来解析</p><h2><span id="core-classes">core classes</span></h2><p>下面来说四个核心类,Client, Main, Master, Server</p><h3><span id="client">Client</span></h3>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="目的">目的</span></h1>
<p>实在受不鸟这些复杂的又充满bug的参数服务器,我就想要一个用jvm语言写的参数分布式缓存而已</p>
<h1><span id="调研glint">调研Glint</span></h
</summary>
<category term="ps" scheme="http://www.yifanguo.top/tags/ps/"/>
</entry>
<entry>
<title>ehCache</title>
<link href="http://www.yifanguo.top/2018/12/26/ehCache/"/>
<id>http://www.yifanguo.top/2018/12/26/ehCache/</id>
<published>2018-12-26T12:16:15.000Z</published>
<updated>2018-12-26T12:16:32.000Z</updated>
<content type="html"><![CDATA[<p>https://www.jianshu.com/p/5a0669d6305e</p>]]></content>
<summary type="html">
<p>https://www.jianshu.com/p/5a0669d6305e</p>
</summary>
<category term="cache" scheme="http://www.yifanguo.top/tags/cache/"/>
</entry>
<entry>
<title>美团机器学习实践阅读笔记</title>
<link href="http://www.yifanguo.top/2018/12/26/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/"/>
<id>http://www.yifanguo.top/2018/12/26/美团机器学习实践阅读笔记/</id>
<published>2018-12-26T08:56:04.000Z</published>
<updated>2018-12-27T02:55:38.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="第1章-问题建模">第1章 问题建模</span></h1><p>机器学习应用流程:</p><p><pre class="mermaid">graph TD;问题建模-->特征工程特征工程-->模型选择模型选择-->模型融合</pre></p><p>线下指标(AUC)要和线上指标(Page Views)等保持一致</p><p>精确率 = TP/(TP+FP) 及预测为T的结果中多少是对的</p><p>召回率R = TP/(TP+FN) 及预测中召回了多少个T, 预测地震时,精确率无所谓,但是我们希望召回率尽量高</p><h2><span id="auc-vs-roc">AUC VS ROC</span></h2><p>真正率 = TP / (TP + FN)</p><p>假正率 = FP / (FP + TN)</p><h2><span id="auc-area-under-roc-curve">AUC area under roc curve</span></h2><p>auc越大说明模型越可能将正样本排在负样本前面</p><p>auc还有一些统计特性,auc等于随机挑选一个正样本和负样本时,分类器将正样本排前面的概率</p><p>AUC只对正负样本排序能力强弱,对score大小和精度没有要求</p><p>auc越高模型的排序能力越强</p><p>AUC把所有正样本排在负样本前面,AUC=1.0</p><h2><span id="对数损失-logistic-loss">对数损失 logistic loss</span></h2><p>对预测概率的似然估计</p><p>对数损失最小化本质上是利用样本中的已知分布,求解导致这种分布的最佳模型参数,</p><p>使这种分布出现概率最大</p><p>logloss衡量的是预测概率分布和真实概率分布的差异性,取值越小越好。</p><p>与AUC不同,logloss对预测概率敏感</p><h2><span id="回归metric">回归metric</span></h2><h3><span id="平均绝对误差-mean-absolute-error-mae">平均绝对误差 mean absolute error MAE</span></h3><p>也叫L1-norm loss.</p><p>MAE是绝对误差的平均值</p><p>使用MAE做lossFuc 是对数据分布的median做拟合</p><h3><span id="均方根误差-rmse-root-mean-squared-error">均方根误差 RMSE--root mean squared error</span></h3><p>RMSE代表的是预测值和真实值差值的样本标准差</p><p>和MAE比,RMSE对大误差样本有更大的惩罚,对outlier敏感,健壮性不如MAE</p><p>用RMSE做lossFuc是对数据分布的mean做拟合</p><h2><span id="12样本选择">1.2样本选择</span></h2><h1><span id="第2章-特征工程">第2章 特征工程</span></h1><p>数据和特征决定了机器学习算法的上限,模型和算法只是不断逼近这个上限而已</p><h1><span id="第16章-分布式机器学习">第16章 分布式机器学习</span></h1><p>分布式机器学习需要解决如下三个问题:</p><ol><li>如何更好切分成多个任务</li><li>如何调度子任务</li><li>均衡各节点负载</li></ol><h2><span id="参数服务器">参数服务器</span></h2><ul><li>参数的获取与提交:计算节点从参数服务节点上获取当前的梯度,然后根据本地分配的训练样本进行梯度计算,通过几轮迭代后将更新后的梯度推送给参数服务器节点。参数在参数服务器需要高效地进行分布式存储,同时计算节点和参数服务节点之间的通信要足够高效</li><li>参数值的同步问题:根据参数服务器的设计和运行原理,我们可以得知在每一个时刻计算节点的梯度和当前参数服务节点上储存的梯度可能是不一致的。</li></ul><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/ps.png" alt="ps"></p><p>什么时候计算节点与参数服务器节点更新同步一次参数呢?</p><p>这个更新同步过程对每个计算节点来说都是相互独立的。一般来说,一个比较简单而且常用的方式是,我们可以采用固定的迭代轮数,每个计算节点迭代这个轮数后和参数服务节点做一次同步</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/%E9%9D%9E%E5%87%B8%E9%97%AE%E9%A2%98.png" alt="非凸问题"></p><h2><span id="梯度如何更新">梯度如何更新</span></h2><p>主要有参数平均法和基于更新方法两种</p><h3><span id="参数平均法">参数平均法</span></h3><p>参数平均法的核心思想是将每个计算节点获取的参数值求平均后作为全局参数值,可以证明参数平均法的结果在数学意义上等同于用单个机器进行训练</p><h3><span id="异步梯度下降更新">异步梯度下降更新</span></h3><p>相对于在工作节点与参数服务器之间传递参数,我们只传递梯度更新信息。</p><h3><span id="比较">比较</span></h3><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/%E6%AF%94%E8%BE%83.png" alt="比较"></p><h2><span id="xgboost">XGBoost</span></h2><p>使用rabit,rabit在MPI的allReduce和broadcast操作原语这个基础上提供了更好的容错处理功能,弥补了MPI的不足</p><p>xgboost优化了rabit的MR流程</p><ol><li>每一轮迭代结束后,计算结果不用放入储存系统。保留在内存里</li><li>每一轮迭代后没有数据重新分发的过程(no shuffle)</li></ol><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/rabit.png" alt="rabit"></p><h3><span id="xgboost容错">xgboost容错</span></h3><p>rabit在一轮AllReduce后,会把在各个节点内存中将模型结果缓存为checkpoint,并加版本号</p><p>同步结束后,各个节点会继续计算到下一次AllReduce通信同步。</p><p>如果有节点发生故障,该故障节点会从集群中找到最近的节点,拿到上一轮的模型文件,然后重新开始计算,其他无故障的节点等待故障节点计算完成后再AllReduce</p><h2><span id="pslite">PSlite</span></h2><p>啥都没说,duang。美团用zmq,应该是pslite改的</p><h1><span id="第17章-特征工程">第17章 特征工程</span></h1><h2><span id="特征生产">特征生产</span></h2><h3><span id="离线特征生产">离线特征生产</span></h3><p>所谓的离线特征主要是从历史数据中总结和归纳出来的特征表示。原始数据一般保存在分布式储存Hive, HBase,ES或者DB中。</p><p>离线特征的生产过程主要包括特征计算和定时调度(linkedin的azakaban)</p><h3><span id="实时特征">实时特征</span></h3><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E7%BE%8E%E5%9B%A2%E6%9C%BA%E5%99%A8%E5%AD%A6%E4%B9%A0%E5%AE%9E%E8%B7%B5%E9%98%85%E8%AF%BB%E7%AC%94%E8%AE%B0/%E5%AE%9E%E6%97%B6%E7%89%B9%E5%BE%81.png" alt="实时特征"></p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="第1章-问题建模">第1章 问题建模</span></h1>
<p>机器学习应用流程:</p>
<p>&lt;pre class=&quot;mermaid&quot;&gt;graph TD;
问题建模--&gt;特征工程
</summary>
<category term="ML" scheme="http://www.yifanguo.top/tags/ML/"/>
</entry>
<entry>
<title>ctr那些事儿</title>
<link href="http://www.yifanguo.top/2018/12/26/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/"/>
<id>http://www.yifanguo.top/2018/12/26/ctr那些事儿/</id>
<published>2018-12-26T08:13:39.000Z</published>
<updated>2018-12-26T08:40:55.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="ctr概述">CTR概述</span></h1><p>转自【https://zhuanlan.zhihu.com/p/31499375】</p><p>click through rate 是用来估计一个用户(缩写为u)是否会点击一个广告或其它item(缩写为a)的技术</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-e5998fc5f766e99bbe92be20711a7206_hd.jpg" alt="v2-e5998fc5f766e99bbe92be20711a7206_hd"></p><p>问题的核心在于建模,即:<strong>寻找一个函数</strong> <img src="https://www.zhihu.com/equation?tex=f" alt="f"> <strong>可以学习出数据中</strong> <img src="https://www.zhihu.com/equation?tex=f%3A+X+%5Cto+y" alt="f: X \to y"> <strong>的映射,即尽可能用数据</strong> <img src="https://www.zhihu.com/equation?tex=%28X%2Cy%29" alt="(X,y)"> <strong>拟合</strong> <img src="https://www.zhihu.com/equation?tex=f" alt="f"> 。这可以简化地理解为一个分类(Classification)问题</p><p>作为一种预估问题,CTR预估潜在地包含了假设:<strong>过去的规律在未来依然有效。</strong></p><p>我们会自然地想到,从历史估计未来,最简单的方式就是对历史数据做统计。那么是否用统计值来预估就可以了?</p><p>不然。</p><p>首先,统计量本身有<strong>置信度</strong>的概念,数据中很难保证每个特征都有充足的数据支持每个特征的置信度。比如,张三看了1kw的广告、点击了1k次,或许可以说张三的点击率是0.01%;但如果李四只看了1次广告、没有点击,却不能简单地认为李四的点击率一定为0%。其次,历史上一定有<strong>未被观察</strong>(unseen)的事件。比如“张三看宝马会点击”是一个事件,但张三不可能看过所有的广告;张三在历史上没有看过奔驰广告,统计也就无法得知他是否会点击奔驰广告。</p><p>从另一个角度讲,传统的Machine Learning问题为便于建模,一般会隐含<strong>数据服从Gaussian分布</strong>的假设;但在互联网主要的展示(impression)、点击(click)、关注(follow)、转发(re-twitter)、交易(order)等数据中,由于强烈的“头部效应”,整体数据是呈现<strong>幂指数分布</strong>的——由于长尾的存在,对于部分特征来讲数据一定是不充分的。</p><p>于是我们总结出CTR预估第一个问题:<strong>CTR预估是在不完善信息下做出的决策</strong>。换句话说就是:<strong>总有统计上不充分的情况出现,无论是unseen事件,还是seen但事件重复次数较少的情况。</strong></p><p>回到“过去的规律在未来依然有效”这个假设。在实际应用中,这个假设是双刃剑:</p><ul><li>好的方面:过去的规律意味着可以利用历史信息,也就意味着利用更大量的<strong>样本</strong>(instance)、构造大量的<strong>特征</strong>(feature)、使用复杂的<strong>模型</strong>(model)的可能性。这也是CTR预估算法从2007年使用LR建模之后十多年不断发展和分化的基础。</li><li>坏的方面:这个假设在现实中并不完全成立——数据的分布在并不被模型预知的情况下变动着。用户行为影响ranking,ranking反过来也会影响用户行为;再加上用户分布变化对数据分布的扰动、运营手段对数据分布的扰动等等复杂因素,经常会导致模型刚上线效果最好,然后可能渐渐地就不好用了。</li></ul><p>这就是CTR预估第二个问题:**CTR预估是在变化的分布上做出的估计。**换句话说就是:<strong>总有从历史上得不到的规律。</strong></p><p>对于这两个问题,常见的解决思路有:</p><ul><li><p>针对在不完善信息下做出决策的问题:</p></li><li><ul><li>试:引入E&E机制快速试错;</li><li>泛:引入更通用、更泛化的特征,在无法精细估计的情况下给出一个更通用的估计。</li></ul></li><li><p>针对在变化的分布上做出估计的问题:</p></li><li><ul><li>快:更快的模型迭代更新速度、在线学习;</li><li>猜:引入博弈论,强化学习,对用户-系统交互行为进行建模。</li></ul></li></ul><h2><span id="一个例子">一个例子</span></h2><p>考虑一个简单的CTR预估场景的例子。这里有两个特征(position_id、ad)。为了方便,我们把相同特征下的曝光和点击进行计数,聚合在一起。</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-e366cc1eea72ef722c49a8933e1e46e0_hd.jpg" alt="v2-e366cc1eea72ef722c49a8933e1e46e0_hd"></p><blockquote><p>以第一行数据为例:“ad=宝马”在“position_id=1”的位置曝光了10次,有6次点击。</p></blockquote><p>我们把每个特征进行one-hot encoding:</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-0d6a9486da21453f893c09ce72dfa7c5_hd.jpg" alt="v2-0d6a9486da21453f893c09ce72dfa7c5_hd"></p><p>下面我们入手开始CTR预估建模。对于CTR预估问题,工业界解决的起点其实和大部分机器学习的通用思路是一致的:**先给出并使用一个较粗糙的解,然后把求解问题转化成一个优化问题。**毕竟,“随机猜”(random)也是一种模型,只是效果比较差而已。我们要优化出比random更好的解,也就是从test AUC = 0.5开始进行优化。</p><p>CTR系统核心建模是针对展示(pv)和点击(click)的关系进行建模的:</p><p><img src="https://www.zhihu.com/equation?tex=%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7Ey_%7Bclick%7D+%3D+Bernoulli%28p_%7Bctr%7D%29" alt="~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~y_{click} = Bernoulli(p_{ctr})"></p><p>这一点是众多CTR预估算法的共同基础。而针对 <img src="https://www.zhihu.com/equation?tex=p_%7Bctr%7D" alt="p_{ctr}"> 建模主要有两大类途径:</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-97bfe6be9ef47f6544fb5aa78a225d59_hd.jpg" alt="v2-97bfe6be9ef47f6544fb5aa78a225d59_hd"></p><p>以线性模型LR为例,核心建模为:<img src="https://www.zhihu.com/equation?tex=%5Cbegin%7Bsplit%7D+%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7E%7Ep_%7Bctr%7D+%26%3D+f%28%5Cphi%28x%29%29+%5C%5C+%26%3D+%5Cfrac%7B1%7D%7B1%2Be%5E%7B-%5Cphi%28x%29%7D%7D%5C%5C+%5Cphi%28x%29+%26%3D+w%5ETx+%5Cend%7Bsplit%7D" alt="\begin{split} ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~p_{ctr} &amp;= f(\phi(x)) \ &amp;= \frac{1}{1+e^{-\phi(x)}}\ \phi(x) &amp;= w^Tx \end{split}">在上图的例子中, <img src="https://www.zhihu.com/equation?tex=x" alt="x"> 为四维one-hot特征的取值,如对于上例的第一行数据:<img src="https://www.zhihu.com/equation?tex=x_1%3D1%2C+x_2%3D0%2C+x_3%3D1%2C+x_4%3D0." alt="x_1=1, x_2=0, x_3=1, x_4=0.">对应地,我们估计出的 <img src="https://www.zhihu.com/equation?tex=w" alt="w"> 也是一个四维向量,分别代表特征“psid=1、psid=2、ad=宝马、ad=奔驰”的权重。由这些权重,我们就可以估计出各种 <img src="https://www.zhihu.com/equation?tex=x" alt="x"> 取值下的点击率。</p><p>可以看到,线性模型假设模型 <img src="https://www.zhihu.com/equation?tex=f%28%C2%B7%29" alt="f(·)"> 是<strong>线性可分</strong>的。当然,这并不意味着线性模型无法表示非线性:我们可以在feature <img src="https://www.zhihu.com/equation?tex=X" alt="X"> 中增加非线性特征来提供非线性。与此对应的是,<strong>非线性模型在模型假设中直接具有非线性</strong>。</p><p>线性模型中,一般特征的规模都会特别大。这是因为线性模型如果要达到与非线性模型相似的模型效果(或者说表达能力),需要在特征侧做大量的工作:比如把原本非线性表达的特征离散化成局部线性的特征、把单一实体变换为高维度的one-hot encoding特征、以及由于上述的操作大量分割了实体之间的关系而需要添加大量组合(crossing)特征等等。这些变换都会大幅增加稀疏特征的维度。</p><p>如果我们把线性模型和非线性模型的计算链路展开,会发现线性模型的特征维度高但计算深度少,模型相对很“宽(wide)”;而非线性模型则相对更“深(deep)”,模型计算更为复杂。相对应地,有些模型更倾向于在<strong>特征侧</strong>进行优化,而有的模型更针对于<strong>模型侧</strong>的调优。</p><h2><span id="模型侧和特征侧优化">模型侧和特征侧优化</span></h2><p>CTR预估中,模型侧的优化主要集中在各类<strong>非线性模型</strong>上,如基于Kernel的模型、NN、Boosting等。在实际的CTR预估系统中,NN和Boosting较为常用,而kernel的身影比较少见。这是因为基于kernel的模型中特征维度通常很高,且 <img src="https://www.zhihu.com/equation?tex=O%28N%5E2%29" alt="O(N^2)"> 的时间复杂度太高难以满足工业要求。</p><p>在实际工程中,对模型侧的优化主要体现在:<strong>观察数据找规律,建模调参线上试</strong>。大致的步骤包括:</p><ol><li>观察数据;</li><li>找到规律;</li><li>根据规律做模型的假设;</li><li>对模型假设中的参数用数据进行拟合;</li><li>把拟合的结果用到线上,看看效果怎么样。</li></ol><p>举个例子:在某业务中,我们通过观察数据,认为图像特征是主要的影响CTR预估效果的因素;则我们在建模中引入CNN假设进行joint training,然后调参、离线验证,验证ok进行线上实验,完成一轮优化。其中,掌握模型的理论本质、适用范围和复杂度,针对需求对模型进行选择和业务适配,是模型侧优化的主要工作。</p><p><strong>特征侧优化</strong></p><p>特征侧有相对有迹可循的优化方法。常用的是<strong>层次化特征:保证细粒度ID无法命中的时候,层次化“上位”更粗粒度特征可以生效。</strong></p><p>如某个场景中有如下的特征层次关系,其曝光量如色阶所示:</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-2215e475e08325a0139828d2d00d8623_hd.jpg" alt="2-2215e475e08325a0139828d2d00d8623_hd"></p><ul><li>1阶特征中,“小吃套餐”特征曝光太少,特征置信度低,则至少上位特征“某的基”可以命中生效。这是一个从最细粒度到更粗粒度的扩展过程;</li><li>2阶组合特征中,最细粒度的“小吃素材&顶部通栏2号位”曝光很少很难命中优化,可以取其中某一个的上位,形成“某的基&顶部通栏2号位”;或者如果还不够,则扩充更多的维度,如“某的基&通栏” 。越宏观的特征表达出的个性化信息越少,但是在数据不足时,能表达出特性的概率越大。</li></ul><p>一图以蔽之,模型侧和特征侧优化方法可以比较总结如下:</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/ctr%E9%82%A3%E4%BA%9B%E4%BA%8B%E5%84%BF/v2-ddeb06abfa253f77304644badb7bbb61_hd.jpg" alt="v2-ddeb06abfa253f77304644badb7bbb61_hd"></p><h2><span id="逻辑回归">逻辑回归</span></h2>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="ctr概述">CTR概述</span></h1>
<p>转自【https://zhuanlan.zhihu.com/p/31499375】</p>
<p>click through rate 是用来估计一个用户(缩写为u)是否
</summary>
<category term="ctr" scheme="http://www.yifanguo.top/tags/ctr/"/>
</entry>
<entry>
<title>推荐系统遇上深度学习</title>
<link href="http://www.yifanguo.top/2018/12/26/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E9%81%87%E4%B8%8A%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0/"/>
<id>http://www.yifanguo.top/2018/12/26/推荐系统遇上深度学习/</id>
<published>2018-12-26T08:02:31.000Z</published>
<updated>2018-12-26T08:11:07.000Z</updated>
<content type="html"><![CDATA[<h1><span id="mlr">MLR</span></h1><p>业界常用的CTR预估算法的不足如下表所示:</p><table><thead><tr><th>方法</th><th>简介</th><th>不足</th></tr></thead><tbody><tr><td>逻辑回归</td><td>使用了Sigmoid函数将函数值映射到0~1区间作为CTR的预估值。LR这种线性模型很容易并行化,处理上亿条训练样本不是问题。</td><td>线性模型的学习能力有限,需要引入大量的领域知识来人工设计特征以及特征之间的交叉组合来间接补充算法的非线性学习能力,非常消耗人力和机器资源,迁移性不够友好。</td></tr><tr><td>Kernel方法</td><td>将低维特征映射到高维特征空间</td><td>复杂度太高而不易实现</td></tr><tr><td>树模型</td><td>如Facebook的GBDT+LR算法,有效地解决了LR模型的特征组合问题</td><td>是对历史行为的记忆,缺乏推广性,树模型只能学习到历史数据中的特定规则,对于新规则缺乏推广性</td></tr><tr><td>FM模型</td><td>自动学习高阶属性的权值,不用通过人工的方式选取特征来做交叉</td><td>FM模型只能拟合特定的非线性模式,常用的就是二阶FM</td></tr><tr><td>深度神经网络</td><td>使用神经网络拟合数据之间的高阶非线性关系,非线性拟合能力足够强</td><td>适合数据规律的、具备推广性的网络结构业界依然在探索中,尤其是要做到端到端规模化上线,这里面的技术挑战依然很大</td></tr></tbody></table><p><strong>那么挑战来了,如何设计算法从大规模数据中挖掘出具有推广性的非线性模式?</strong></p>]]></content>
<summary type="html">
<h1><span id="mlr">MLR</span></h1>
<p>业界常用的CTR预估算法的不足如下表所示:</p>
<table>
<thead>
<tr>
<th>方法</th>
<th>简介</th>
<th>不足</th>
</tr>
</thead>
<tbo
</summary>
<category term="rs" scheme="http://www.yifanguo.top/tags/rs/"/>
</entry>
<entry>
<title>阿里妈妈ctr演讲</title>
<link href="http://www.yifanguo.top/2018/12/26/%E9%98%BF%E9%87%8C%E5%A6%88%E5%A6%88ctr%E6%BC%94%E8%AE%B2/"/>
<id>http://www.yifanguo.top/2018/12/26/阿里妈妈ctr演讲/</id>
<published>2018-12-26T07:03:08.000Z</published>
<updated>2018-12-26T07:53:40.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="互联网数据和经典模型">互联网数据和经典模型</span></h1><p>文章地址: https://mp.weixin.qq.com/s/UzukJHlYvRKtYBeuLoApqg</p><p>CTR经典三种做法</p><ol><li>简单线性模型LR</li><li>稀疏正则L1-Norm特征筛选</li><li>处理非线性:人工特征工程</li></ol><h2><span id="经典方法1id特征">经典方法1:ID特征</span></h2><p>ID特征,这里指的是稀疏鉴别式特征。举个例子,假如有1亿个用户,可以把1亿个用户表示为1亿维的01向量,01向量的第一个用户就命中第一维,第二个用户就命中为第二维,所以一种特征可以用这种ID类表示展现成一个非常长的01稀疏向量。如果有很多组特征,就可以把这些向量拼起来,形成一个更长的向量。</p><p>就原始特征而言,一般用户量大的公司可能是上亿级,而大的互联网公司,是上亿、上十亿甚至上百亿级的。所以原始ID特征在表示上,可以轻松将其表示成十几亿或者几十亿级。此外,我们还可以做特征的交叉组合,只要工程能力够,可以轻松上千亿,这个特征维度很大。</p><h2><span id="逻辑回归">逻辑回归</span></h2><p>逻辑回归是线性模型加上非线性的变换,变成一个概率形式。逻辑回归在工业界使用的方式很不一样。第一,它能处理非常大规模的数据,所以其模型和数据都必须是并行处理的,这对工程和算法上的要求都特别高。第二,对于特别大的特征来讲,通常我们会用稀疏正则L1-Norm特征筛选的方法。</p><p>##经典方法三:人工特征工程</p><p>如果想用这个经典方法将更多有用的信息尤其是非线性的压榨出来,还需要用到人工特征工程的方法。比如刚才说的两个特征,如果两个特征的交互对目标影响很大,那么拼起来的线性模型可能不够,我们就要做交叉等很多特征。</p><h2><span id="问题">问题</span></h2><ol><li>人工能力有限,很难对非线性模式完全挖掘充分</li><li>依赖人力和领域经验,方法推广到其他的问题代价太大</li></ol><p><strong>有多少人工就有多少智能</strong></p><h2><span id="其他方法">其他方法</span></h2><h3><span id="kernel">kernel</span></h3><p>不适合工业界</p><h3><span id="tree-based">Tree based</span></h3><p>在ID特征上表现不够好</p><p>Tree based方法在一些低维的强特征上效果特别好,但在ID特征上反而作用不太好。</p><p>这里举一个例子:在推荐场景中,需要预估一个用户和一个宝贝的点击率,先不取历史行为就用用户ID和宝贝ID两种特征。有这两个特征,对于协同过滤的方法就已经够了。但是,如果用Tree based方法,要建树就会带来很多麻烦,树根到树叶的路径等价于是否是某个用户和是否是某个宝贝的联合判断。在这种情况下,它已经变成了一个历史记忆。这就是为什么Tree based的方法在稀疏大规模ID数据上表现不行的原因。</p><p>Facebook也做了一个方法,就是在强特征上用Tree based方法做数据筛选,再用一些LR聚合类的方法利用弱特征。</p><p>( GBDT + LR)</p><h3><span id="矩阵分解和分解机器模型无法处理高阶关系">矩阵分解和分解机器模型:无法处理高阶关系</span></h3><p>矩阵分解和分解机器模型,这两类模型其实有点共通。以分解机器模型为例,它主要处理的是有限次关系,经典的方法是二次关系。对于一些高阶关系是没法处理的。</p><h1><span id="分片线性模型和学习算法mlr模型">分片线性模型和学习算法MLR模型</span></h1><p>分片线性模型MLR是2011年我在阿里提出的方法。该模型的优点在于,可将整个数据分成不同的区域,在每个不同区域都用一个简单的模型预测,再将全部信息聚合起来,得到可以比较复杂的分片线性模型。如此一来,就能平衡欠拟合和过拟合的问题,从而在大规模数据中挖掘出推广性好的非线性信息。而其一个基本原则,就在于要使每分片对应足够量的样本。</p><p>特点:</p><p>分而治之;</p><p>分片数足够多时,有非常强的非线性能力;</p><p>模型复杂度可控:有较好泛化能力;</p><p>具有自动特征选择作用;</p><p>可以适用于大规模高维度数据;</p><p>#大规模ID特征+MLR实践</p><p>#深层用户兴趣分布网络</p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="互联网数据和经典模型">互联网数据和经典模型</span></h1>
<p>文章地址: https://mp.weixin.qq.com/s/UzukJHlYvRKtYBeuLoApqg</p>
<p>CTR经典三种做法</p
</summary>
<category term="ctr" scheme="http://www.yifanguo.top/tags/ctr/"/>
</entry>
<entry>
<title>ctr</title>
<link href="http://www.yifanguo.top/2018/12/26/ctr/"/>
<id>http://www.yifanguo.top/2018/12/26/ctr/</id>
<published>2018-12-26T06:37:16.000Z</published>
<updated>2018-12-26T06:46:44.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="ctr-模型">ctr 模型</span></h1><h1><span id="有用的资料">有用的资料</span></h1><p>https://zhuanlan.zhihu.com/p/39439947 【阿里的DIN】</p><h1><span id="历史">历史</span></h1><p>CTR预估是一个比较窄的研究领域,但是模型性能一点点的提升,在实际应用中都非常关键,真金白银毫不含糊。随着深度学习在CV、NLP等领域取得突破性进展,一些研究也开始尝试将DNN应用于CTR预估,比如:Wide&Deep, DeepFM等。</p><blockquote><p>这些做法一般分为两部分:</p><ol><li><p>在输入上面加一层embeding层,把最原始高维度、稀疏的数据转换为低维度的实值表示上(dense vector)。</p></li><li><p>增加多个全连接层,学习特征之间的非线性关系。Sparse Features -> Embedding Vector -> MLPs -> Output</p></li></ol></blockquote><p>这些方法的<strong>优点</strong>在于:<strong>相比于原来的Logistic Regression方法,大大减少了人工特征工程的工作量。</strong></p><p>**缺点:<strong>在电子商务领域中,用户的历史行为数据(User Behavior Data)中包含大量的用户兴趣信息,之前的研究并没有针对Behavior data</strong>特殊的结构(Diversity + Local Activation)**进行建模</p><p>这就是DIN要改进的地方!** DIN同时对Diversity和Local Activation进行建模。</p><p><strong>针对Diversity:</strong>针对用户广泛的兴趣,DIN用<em>an interest distribution</em>去表示。</p><p><strong>针对Local Activation:</strong>DIN借鉴机器翻译中的Attention机制,设计了一种<em>attention-like network structure</em>, 针对当前候选Ad,去局部的激活(<em>Local Activate</em>)相关的历史兴趣信息。和当前候选Ad相关性越高的历史行为,会获得更高的<em>attention score</em>,从而会主导这一次预测。</p><h1><span id="阿里推荐系统工作流程">阿里推荐系统工作流程</span></h1><ol><li>检查用户历史行为数据</li><li>使用matching module产生候选ads</li><li>通过ranking module得到候选ads的点击概率,并根据概率排序得到推荐列表</li><li>记录下用户在当前展示广告下的反应(点击与否)</li></ol><h1><span id="特征工程">特征工程</span></h1><h2><span id="21-训练数据">2.1 训练数据</span></h2><p>前面提到,电子商务领域,充分利用User Behavior Data非常关键,而它又有着非常显著的特点:</p><ul><li>Diversity. 兴趣爱好非常广泛</li><li>Local Activation. 历史行为中部分数据主导是否会点击候选广告</li></ul><p>还有的特点,就是CTR中输入普遍存在的特点:</p><ul><li>高纬度</li><li>非常稀疏</li></ul><p>CTR中一旦涉及到用户行为数据,还有一个特点:</p><ul><li>特征往往都是<strong>multi-hot</strong>的稀疏ids。</li></ul><p>也就是:多值离散特征。比如:用户在YouTube上看的视频和搜索过的视频。无论是看过的还是搜索过的,都不止一个,但是相对于所有的视频来说,看过和搜索过的数量都太小了(非常稀疏)。在电子商务上的例子就是:用户购买过的good_id有多个,购买过的shop_id也有多个,而这也直接导致了每个用户的历史行为id长度是不同的。</p><p>为了得到一个固定长度的Embedding Vector表示,原来的做法是在<code>Embedding Layer</code>后面增加一个<code>Pooling Layer</code>。Pooling可以用sum或average。最终得到一个固定长度的<code>Embedding Vector</code>,是用户兴趣的一个抽象表示,常被称作<code>User Representation</code>。缺点是会损失一些信息。</p><p>DIN使用Attention机制来解决这个问题。<strong>Attention机制</strong>来源于<code>Neural Machine Translation(NMT)</code>。DIN使用Attention机制去更好的建模局部激活。在DIN场景中,针对不同的候选广告需要自适应地调整<code>User Representation</code>。也就是说:在<code>Embedding Layer -> Pooling Layer</code>得到用户兴趣表示的时候,赋予不同的历史行为不同的权重,实现局部激活。从最终反向训练的角度来看,就是根据当前的候选广告,来反向的激活用户历史的兴趣爱好,赋予不同历史行为不同的权重。</p><h2><span id="22-特征处理">2.2 特征处理</span></h2><h1><span id="总结">总结</span></h1><ol><li>用户有多个兴趣爱好,访问了多个good_id,shop_id。为了降低纬度并使得商品店铺间的算术运算有意义,我们先对其进行Embedding嵌入。那么我们如何对用户多种多样的兴趣建模那?使用<strong>Pooling对Embedding Vector求和或者求平均</strong>。同时这也解决了不同用户输入长度不同的问题,得到了一个固定长度的向量。这个向量就是用户表示,是用户兴趣的代表。</li><li>但是,直接求sum或average损失了很多信息。所以稍加改进,针对不同的behavior id赋予不同的权重,这个权重是由当前behavior id和候选广告共同决定的。这就是Attention机制,实现了Local Activation。</li><li>DIN使用<em>activation unit</em>来捕获local activation的特征,使用<em>weighted sum pooling</em>来捕获diversity结构。</li><li>在模型学习优化上,DIN提出了<em>Dice激活函数</em>、<em>自适应正则</em> ,显著的提升了模型性能与收敛速度。</li></ol>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="ctr-模型">ctr 模型</span></h1>
<h1><span id="有用的资料">有用的资料</span></h1>
<p>https://zhuanlan.zhihu.com/p/39439947 【阿里的DI
</summary>
<category term="click through prediction" scheme="http://www.yifanguo.top/tags/click-through-prediction/"/>
</entry>
<entry>
<title>决策树</title>
<link href="http://www.yifanguo.top/2018/12/25/%E5%86%B3%E7%AD%96%E6%A0%91/"/>
<id>http://www.yifanguo.top/2018/12/25/决策树/</id>
<published>2018-12-25T02:22:20.000Z</published>
<updated>2018-12-26T05:17:24.000Z</updated>
<content type="html"><![CDATA[<p>[TOC]</p><h1><span id="决策树概述">决策树概述</span></h1><p>用于分类时,它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布</p><h1><span id="决策树场景">决策树场景</span></h1><p>决策树是通过不停缩小猜测的范围,最后得到答案</p><p>如下图:</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/dt.png" alt="dt"></p><h1><span id="决策树的定义">决策树的定义</span></h1><p>决策树由节点node和有向边(directed edge)组成</p><p>node有两类,内部节点和叶节点,内部节点表示一个特征(feature), 叶节点表示一个label</p><h1><span id="决策树的预测">决策树的预测</span></h1><p>当需要使用决策树对一个目标进行分类时,从根节点开始,对目标的某一特征开始进行测试,</p><p>根据测试结果将该目标分配到某一子节点。每一个子节点对应着该特征的一个取值。dfs到达叶节点</p><p>选择该叶节点作为预测label</p><h1><span id="决策树的术语">决策树的术语</span></h1><p>信息熵又名信息增益</p><p>熵: entropy 指的是体系的混乱程度</p><p>信息论:information theory 是一种信息的度量方式,表示信息的混乱程度,也就是说:信息越有序,信息熵越低。例如:火柴有序放在火柴盒里,熵值很低,相反,熵值很高。</p><p>信息增益:information gain 在划分数据集前后信息发生的变化称为信息增益</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/entropy.png" alt="entropy"></p><h1><span id="决策树的种类">决策树的种类</span></h1><p>针对如何生成决策树一般有三类:</p><p>https://zhuanlan.zhihu.com/p/30059442</p><p>https://blog.csdn.net/xbinworld/article/details/44660339</p><h2><span id="id3">ID3</span></h2><p>会overfitting 因为id3会尝试将错误率通过分割非常细来达到0</p><h2><span id="c45">C4.5</span></h2><p>c4.5对ID3进行了改进,C4.5中,增加的熵要除以分割太细的代价,这个比值叫做信息增益率,显然分割太细分母增加,信息增益率会降低。除此之外,其他的原理和ID3相同</p><h2><span id="cart">CART</span></h2><p>分类回归树</p><p>CART只能将一个父节点分为2个子节点。CART用GINI指数来决定如何分裂</p><p>GINI指数:总体内包含的类别越杂乱,GINI指数就越大(跟熵的概念很相似)</p><p>选择GINI指数最小的开始分</p><p>CART还是一个回归树,回归解析用来决定分布是否终止。理想地说每一个叶节点里都只有一个类别时分类应该停止,但是很多数据并不容易完全划分,或者完全划分需要很多次分裂,必然造成很长的运行时间,所以CART可以对每个叶节点里的数据分析其均值方差,当方差小于一定值可以终止分裂,以换取计算成本的降低。</p><p>CART和ID3一样,存在偏向细小分割,即过度学习(过度拟合的问题),为了解决这一问题,对特别长的树进行剪枝处理,直接剪掉。</p><h1><span id="随机森林">随机森林</span></h1><h2><span id="概述">概述</span></h2><p>首先,从原始的数据集中采取有放回的抽样,构造子数据集,子数据集的数据量是和原始数据集相同的。不同子数据集的元素可以重复,同一个子数据集中的元素也可以重复。第二,利用子数据集来构建子决策树,将这个数据放到每个子决策树中,每个子决策树输出一个结果。最后,如果有了新的数据需要通过随机森林得到分类结果,就可以通过对子决策树的判断结果的投票,得到随机森林的输出结果了。如下图,假设随机森林中有3棵子决策树,2棵子树的分类结果是A类,1棵子树的分类结果是B类,那么随机森林的分类结果就是A类。</p><h2><span id="例子">例子</span></h2><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/rf.png" alt="rf"></p><p>待选特征的随机选取:与数据集的随机选取类似,随机森林中的子树的每一个分裂过程并未用到所有的待选特征,而是从所有的待选特征中随机选取一定的特征,之后再在随机选取的特征中选取最优的特征。这样能够使得随机森林中的决策树都能够彼此不同,提升系统的多样性,从而提升分类性能。</p><p>下图中,蓝色的方块代表所有可以被选择的特征,也就是目前的待选特征。黄色的方块是分裂特征。左边是一棵决策树的特征选取过程,通过在待选特征中选取最优的分裂特征(别忘了前文提到的ID3算法,C4.5算法,CART算法等等),完成分裂。右边是一个随机森林中的子树的特征选取过程。</p><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/rf_2.png" alt="rf_2"></p><p>http://www.cnblogs.com/pinard/p/6131423.html</p><p>#Ensemble learning集成学习</p><h2><span id="集成学习概念">集成学习概念</span></h2><p><pre class="mermaid">graph TD;个体学习器A1-->强学习器B;个体学习器A2-->强学习器B;个体学习器A3-->强学习器B;个体学习器A4-->强学习器B;</pre></p><p>集成学习是通过训练出若干个个体学习器,通过一定的结合策略,最终形成一个强学习器,达到<strong>博采众长</strong>的目的</p><h2><span id="集成学习需要解决的两个问题">集成学习需要解决的两个问题</span></h2><p>1.如何得到若干个个体学习器</p><p>2.如何选择一种结合策略,将这些个体学习器集成一个强学习器</p><h2><span id="个体学习器">个体学习器</span></h2><p>个体学习器强依赖关系,需要串行--boosting算法</p><p>个体学习器不存在强依赖关系,可以并行—bagging和rf</p><h2><span id="boosting">Boosting</span></h2><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/boosting.png" alt="boosting"></p><p>从图中可以看出,Boosting算法的工作机制是首先从训练集用初始权重训练出一个弱学习器1,根据弱学习的学习误差率表现来更新训练样本的权重,使得之前弱学习器1学习误差率高的训练样本点的权重变高,使得这些误差率高的点在后面的弱学习器2中得到更多的重视。然后基于调整权重后的训练集来训练弱学习器2.,如此重复进行,直到弱学习器数达到事先指定的数目T,最终将这T个弱学习器通过集合策略进行整合,得到最终的强学习器。</p><p>Boosting系列算法里最著名算法主要有AdaBoost算法和提升树(boosting tree)系列算法。提升树系列算法里面应用最广泛的是梯度提升树(Gradient Boosting Tree)</p><h2><span id="bagging">Bagging</span></h2><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/bagging.png" alt="bagging"></p><p>从上图可以看出,bagging的个体弱学习器的训练集是通过随机采样得到的。通过T次的随机采样,我们就可以得到T个采样集,对于这T个采样集,我们可以分别独立的训练出T个弱学习器,再对这T个弱学习器通过集合策略来得到最终的强学习器。</p><p>对于这里的随机采样有必要做进一步的介绍,这里一般采用的是自助采样法(Bootstap sampling),即对于m个样本的原始训练集,我们每次先随机采集一个样本放入采样集,接着把该样本放回,也就是说下次采样时该样本仍有可能被采集到,这样采集m次,最终可以得到m个样本的采样集,由于是随机采样,这样每次的采样集是和原始训练集不同的,和其他采样集也是不同的,这样得到多个不同的弱学习器。</p><p>随机森林是bagging的一个特化进阶版,所谓的特化是因为随机森林的弱学习器都是决策树。所谓的进阶是随机森林在bagging的样本随机采样基础上,又加上了特征的随机选择,其基本思想没有脱离bagging的范畴。</p><h2><span id="结合策略">结合策略</span></h2><h3><span id="平均法">平均法</span></h3><p>最终的预测是各个弱学习器的平均值</p><p>如果弱学习器有权重,则是一个加权平均</p><h3><span id="投票法">投票法</span></h3><p>投票法可以有少数服从多数法,绝对票数,以及加权投票</p><h3><span id="学习法">学习法</span></h3><p>上两节的方法都是对弱学习器的结果做平均或者投票,相对比较简单,但是可能学习误差较大,于是就有了学习法这种方法,对于学习法,代表方法是stacking,当使用stacking的结合策略时, 我们不是对弱学习器的结果做简单的逻辑处理,而是再加上一层学习器,也就是说,我们将训练集弱学习器的学习结果作为输入,将训练集的输出作为输出,重新训练一个学习器来得到最终结果。</p><p>在这种情况下,我们将弱学习器称为初级学习器,将用于结合的学习器称为次级学习器。对于测试集,我们首先用初级学习器预测一次,得到次级学习器的输入样本,再用次级学习器预测一次,得到最终的预测结果。</p><h2><span id="boosting算法需要解决的问题">boosting算法需要解决的问题</span></h2><ol><li>如何计算学习误差率e</li><li>如何得到弱学习器权重系数a</li><li>如何更新样本权重d</li><li>使用何种结合策略</li></ol><p>#Adaboosting</p><ul><li><p>样本权重</p><ul><li><p>没有先验知识的情况下,初始的分布为等概分布,即计算集如果有N个样本,每个样本的分布概率为1/N</p></li><li><p>m每次循环后提高误分样本的分布概率,误分样本在训练集中所占的权重增大,使得下一次循环的弱学习器能够集中力量对这些误分样本进行判断</p></li><li><p>准确率越高的弱学习机权重较高</p></li></ul></li></ul><h2><span id="m1算法">M1算法</span></h2><p><img src="/Users/yifanguo/Desktop/blog/source/_posts/%E5%86%B3%E7%AD%96%E6%A0%91/ada.png" alt="ada"></p><h2><span id="前向逐步递增">前向逐步递增</span></h2><h2><span id="优缺点">优缺点</span></h2><p>Adaboost的主要优点有:</p><p>1)Adaboost作为分类器时,分类精度很高</p><p>2)在Adaboost的框架下,可以使用各种回归分类模型来构建弱学习器,非常灵活。</p><p>3)作为简单的二元分类器时,构造简单,结果可理解。</p><p>4)不容易发生过拟合</p><p>Adaboost的主要缺点有:</p><p>1)对异常样本敏感,异常样本在迭代中可能会获得较高的权重,影响最终的强学习器的预测准确性。</p><h1><span id="gbdtgradient-boosting-decision-tree">GBDT(Gradient Boosting Decision Tree)</span></h1><p>GBDT也是集成学习Boosting家族的成员,但是却和传统的Adaboost有很大的不同。回顾下Adaboost,我们是利用前一轮迭代弱学习器的误差率来更新训练集的权重,这样一轮轮的迭代下去。GBDT也是迭代,使用了前向分布算法,但是弱学习器限定了只能使用CART回归树模型,同时迭代思路和Adaboost也有所不同。</p><p>在GBDT的迭代中,假设我们前一轮迭代得到的强学习器是ft−1(x)ft−1(x), 损失函数是L(y,ft−1(x))L(y,ft−1(x)), 我们本轮迭代的目标是找到一个CART回归树模型的弱学习器ht(x)ht(x),让本轮的损失函数L(y,ft(x)=L(y,ft−1(x)+ht(x))L(y,ft(x)=L(y,ft−1(x)+ht(x))最小。也就是说,本轮迭代找到决策树,要让样本的损失尽量变得更小。</p><p>GBDT的思想可以用一个通俗的例子解释,假如有个人30岁,我们首先用20岁去拟合,发现损失有10岁,这时我们用6岁去拟合剩下的损失,发现差距还有4岁,第三轮我们用3岁拟合剩下的差距,差距就只有一岁了。如果我们的迭代轮数还没有完,可以继续迭代下面,每一轮迭代,拟合的岁数误差都会减小。</p><p>从上面的例子看这个思想还是蛮简单的,但是有个问题是这个损失的拟合不好度量,损失函数各种各样,怎么找到一种通用的拟合方法呢?</p><h2><span id="gbdt的负梯度拟合">GBDT的负梯度拟合</span></h2><h2><span id="gbdt回归算法">GBDT回归算法</span></h2><p>GBDT主要的优点有:</p><ol><li><p>可以灵活处理各种类型的数据,包括连续值和离散值。</p></li><li><p>在相对少的调参时间情况下,预测的准确率也可以比较高。这个是相对SVM来说的。</p></li></ol><p>3)使用一些健壮的损失函数,对异常值的鲁棒性非常强。比如 Huber损失函数和Quantile损失函数。</p><p>GBDT的主要缺点有:</p><p>1)由于弱学习器之间存在依赖关系,难以并行训练数据。不过可以通过自采样的SGBT来达到部分并行</p><h2><span id="gradient-boosting">Gradient Boosting</span></h2><p>用一个弱学习器近似负梯度</p><h1><span id="xgboost">XGBoost</span></h1><h1><span id="有用的资料">有用的资料</span></h1><p>http://codewithzhangyi.com/2018/06/01/XGBOOST%E4%BD%BF%E7%94%A8%E8%AF%B4%E6%98%8E/</p><p>https://zxth93.github.io/2017/09/29/XGBoost%E7%AE%97%E6%B3%95%E5%8E%9F%E7%90%86/index.html</p><p>https://www.bilibili.com/video/av26088803?from=search&seid=4150235187907599632</p><p>https://www.bilibili.com/video/av24476653?from=search&seid=11865982546931463007 (非常好的视频,不过适合有基础的看)</p><p>https://www.jiqizhixin.com/articles/2018-03-18-4 (非常好的讲xgboost的直方图和lgb的GOSS算法对比)</p>]]></content>
<summary type="html">
<p>[TOC]</p>
<h1><span id="决策树概述">决策树概述</span></h1>
<p>用于分类时,它可以认为是if-then规则的集合,也可以认为是定义在特征空间与类空间上的条件概率分布</p>
<h1><span id="决策树场景">决策树场景</sp
</summary>
<category term="决策树" scheme="http://www.yifanguo.top/tags/%E5%86%B3%E7%AD%96%E6%A0%91/"/>
</entry>
</feed>