-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathlocal-search.xml
More file actions
444 lines (210 loc) · 221 KB
/
local-search.xml
File metadata and controls
444 lines (210 loc) · 221 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>强化学习笔记(九)策略函数近似</title>
<link href="/2026/03/17/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B9%9D%EF%BC%89%E7%AD%96%E7%95%A5%E5%87%BD%E6%95%B0%E8%BF%91%E4%BC%BC/"/>
<url>/2026/03/17/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B9%9D%EF%BC%89%E7%AD%96%E7%95%A5%E5%87%BD%E6%95%B0%E8%BF%91%E4%BC%BC/</url>
<content type="html"><![CDATA[<h1 id="强化学习笔记九策略函数近似">强化学习笔记(九)策略函数近似</h1><p>策略函数近似是强化学习中一种重要的方法,用于解决状态空间和动作空间过大或连续的问题。通过函数近似,我们可以用参数化模型来表示策略,从而避免直接存储所有状态和动作的概率分布。</p><p>策略函数的输入是状态,输出是动作的概率分布。常见的近似方法包括线性函数近似、神经网络近似和核方法。其中,神经网络近似最为常用,称为策略网络(PolicyNetwork),记为 <spanclass="math inline"><em>π</em>(<em>a</em>|<em>s</em>; <em>θ</em>)</span>,其中<span class="math inline"><em>θ</em></span> 是神经网络的参数。</p><p>【摘要】(来自元宝) 一、策略梯度方法的基本思想:</p><p>1.将策略参数化</p><p>在策略梯度方法中,我们不是去估计值函数(如Q值),而是直接用一个可微函数(如神经网络)来表示策略π(a|s; θ),其中θ是策略的参数。也就是说,策略本身是可以被优化的对象。</p><p>2.定义目标函数</p><p>策略优化的目标是最大化某个性能指标,通常是期望回报(expectedreturn):</p><p><spanclass="math inline"><em>J</em>(<em>θ</em>) = <em>𝔼</em><em>π</em><em>θ</em>[<em>R</em>] = ∑<em>τ</em><em>P</em>(<em>τ</em>; <em>θ</em>)<em>R</em>(<em>τ</em>)</span></p><p>其中τ表示一个轨迹(state-action序列),P(τ;θ)是该轨迹在策略πθ下的概率,R(τ)是该轨迹的总回报。</p><p>3.梯度上升更新策略参数</p><p>为了最大化J(θ),我们使用梯度上升方法更新θ:</p><p><spanclass="math inline"><em>θ</em> ← <em>θ</em> + <em>α</em>∇<em>θ</em><em>J</em>(<em>θ</em>)</span></p><p>这里的∇θ J(θ)就是所谓的策略梯度(policy gradient)。</p><p>二、存在的问题与挑战:</p><p>尽管策略梯度方法的想法很简单,但在实现中会遇到一些问题,主要包括:</p><p>问题一:如何选择合适的标量指标(scalar metric)</p><p>这个标量指标就是我们想要最大化的目标函数J(θ)。理论上我们可以直接用期望回报,但实际操作中,回报的方差可能非常大,导致梯度估计不稳定。</p><p>因此,实际中我们会引入一些技巧来改善这个问题,比如:</p><p>使用折扣因子γ来控制远期回报的重要性;</p><p>使用基线(baseline)来减少方差,如减去状态值V(s);</p><p>使用优势函数A(s,a)替代原始回报R(τ),如:<spanclass="math inline"><em>A</em>(<em>s</em>, <em>a</em>) = <em>Q</em>(<em>s</em>, <em>a</em>) − <em>V</em>(<em>s</em>)</span>;</p><p>使用REINFORCE with baseline 或 Actor-Critic 方法。</p><p>问题二:如何计算该标量的梯度</p><p>由于J(θ)通常是一个期望,直接计算梯度很困难,因此我们需要用采样的方法来估计梯度。</p><p>策略梯度定理(Policy GradientTheorem)告诉我们,可以直接通过采样轨迹来估计梯度,而不需要显式地求P(τ;θ)的导数。</p><p>一个常见的梯度估计形式是:</p><p><spanclass="math inline">∇<em>θ</em><em>J</em>(<em>θ</em>) ≈ (1/<em>N</em>)∑<em>i</em> = 1<em>N</em>∑<em>t</em> = 1<em>T</em>∇<em>θ</em><em>l</em><em>o</em><em>g</em><em>π</em><em>θ</em>(<em>a</em><em>i</em>, <em>t</em>|<em>s</em><em>i</em>, <em>t</em>) · <em>G</em><sub><em>t</em></sub></span></p><p>其中Gt是从时间t开始的总回报,N是采样的轨迹数,T是轨迹长度。</p><p>这个形式说明,策略梯度依赖于log概率的梯度乘以回报,这称为“得分函数估计器”(scorefunction estimator)。</p><h2 id="一策略梯度的思路">一、策略梯度的思路</h2><p>从 <code>value based</code> 到<code>policy based</code>,区别:方法是基于值还是基于策略。基于策略的方法会建立策略的目标函数,并用于优化策略。目录:</p><p><img src="image.png" /></p><p>基于策略梯度,是用的是梯度上升的方法。</p><p>一、策略梯度的基础在这节课之前,都是用的表格形式的方式去存储状态和动作,以表格的形式储存</p><p><img src="image-1.png" /></p><p>对于连续状态,表格形式很难泛化为此,考虑引入策略网络,这里的π,多了一个参数θ</p><p><img src="image-2.png" /></p><p>表格和函数形式的最优策略不同之处第一点:最优函数的定义不一样,在表格行事历,如果一个policy的状态值比其他状态都好,那他是最优的。在函数形式的优化里面,如果一个策略π能最优化(最大化)一个具体的标量指标,那他是最优的。</p><p><img src="image-3.png" /></p><p>第二点:就是获取最优动作的时候,用函数的话得先跑一遍这个函数,到函数里运算得到</p><p><img src="image-4.png" /></p><p>第三点:就是更新策略的时候,不能像表格一样直接更新表格里的数,而是更新参数θ去优化。</p><p><img src="image-5.png" /></p><p><strong>策略梯度方法的思想</strong>首先梯度的目标函数可以用于优化策略然后,基于梯度的更新方法可以用于搜索最优策略虽然思想是简单的,但是还存在几个问题:第一个是我们如何选取合适的标量(metric)第二个是我们如何去计算这个标量的梯度?——需要通过策略梯度方法</p><p><img src="image-6.png" /></p><h2 id="二定义优化策略的指标">二、定义优化策略的指标</h2><p>衡量策略的指标,先介绍一种:平均价值①平均价值的定义,其实就是对这个状态值进行加权平均。这里vπbar的定义是在一个给定的策略π下,不同状态值的平均值</p><p><img src="image-7.png" /></p><p>这里d(s)的求和是=1而且各个权重都是大于0</p><p><img src="image-8.png" /></p><p>这样定义 ,在后面他就可以对他的梯度起到作用。</p><p>d(s)是什么呢?d(s)是一个分布系数,d(s)可以和π有关系,也可以没有关系。如果和π没关系,那么求梯度的时候就可以不考虑d(把他当做常数),否则就得考虑求导带上d。这里为了简化运算把他当做梯度。</p><p><img src="image-9.png" /></p><p>如果d和π没有关系一种简单的方式是都取平均去选取d,另一种方式是按照具体的状态值去选取。如果d和π有关系,或者说d依赖于π,那么一种基础的选取方式是用状态转移概率p去选取。另一种是对不同的状态,给不同的权重,这样有一些经常访问到的状态,他的权重就更大,没访问到的状态他的权重就小。</p><p><img src="image-11.png" /></p><p>第二种方式是平均奖励,用平均单步奖励作为标量</p><p><img src="image-10.png" /></p><p>平均单步奖励的定义如图所示,由两步的加权平均求得。第一步加权是由于不同的动作有不同的概率 要对动作加权平均第二步是不同的状态动作采取了之后获得不同奖励的概率不同,又得加权平均一下。</p><p>dπ是稳态分布 状态的另一种表达方式:n趋于无穷的时候,跑无穷多步reward的平均,</p><p><img src="image-12.png" /></p><p>另一种表达方式是省略了s0,因为当奖励是无穷多步的时候,从哪里开始就不再重要了。这个无穷多步的</p><p><img src="image-13.png" /></p><h2 id="三metrics-的作用和性质">三、metrics 的作用和性质</h2><p>metrics可以用于优化策略梯度。metrics都是和策略π相关的,优化metrics就优化了策略。</p><p><img src="image-14.png" /></p><p>第二点是所有metrics都有一个折扣率,在这个书里面都考虑的是有折扣率的情形</p><p><img src="image-15.png" /></p><p>第三点是直觉上看,<spanclass="math inline"><em>r̄</em></span>是一个即时量,应该是短视的,<spanclass="math inline">$\bar{v_π}$</span>是考虑到所有的<code>reward</code>,应该比<spanclass="math inline"><em>r</em></span>更加长远,但是实际上优化这两,其实是等价的。等价不是说这两个相等,而是说其中一个达到极值的时候另一个也达到极值。</p><p><img src="image-16.png" /></p><p><strong>这就是为什么强化里面优化即时奖励就可以优化价值。</strong></p><p>在文献里面,经常会见到如下的表述,他是什么呢,其实他就是<spanclass="math inline">$\bar{v_π}$</span>这个metric的另一种表述。即优化未来的折扣期望回报和。</p><p><img src="image-17.png" /></p><p>文献中,要么是<spanclass="math inline"><em>v</em></span>,要么是<spanclass="math inline"><em>r</em></span>,一定会见到二者其一。</p><p>证明如下,其实大<spanclass="math inline"><em>R</em></span>就是从轨迹里采样的,是在不同的状态下连续获得的奖励,这个加起来就是return,也就是<spanclass="math inline"><em>G</em><sub><em>t</em></sub></span>,对这个求和就得到了<spanclass="math inline">$\bar{v_π}$</span></p><p><img src="image-18.png" /></p><h2 id="四-metric的梯度">四 metric的梯度</h2><p>metrics的梯度计算是策略梯度方法中最重要的一部分。也是最复杂的一部分。</p><p>原因在于可能有不同的metrics,而且要考虑折扣率的情况。</p><p><img src="image-19.png" /></p><p>先给出梯度的结果的求和。 其中<spanclass="math inline"><em>J</em>(<em>θ</em>)</span>可以是我们要优化的metric,这里等号不一定是严格相等,可以是≈或近似 <spanclass="math inline"><em>η</em></span>是状态的分布或权重</p><p><img src="image-20.png" /></p><p>所有的情况下,求出的式子都和这个类似。</p><p><img src="image-21.png" /></p><p>metric是<spanclass="math inline">$\bar{r_π}$</span>的时候,如果折扣率是=1,那么是严格等于,不然就是约等于如果是<span class="math inline">$\bar{v_π}$</span>,那么他是成比例的</p><p><img src="image-25.png" /></p><p>这个梯度的式子也就e有用期望和对数的形式给出,为什么用这个形式比较好?因为这种形式上,梯度的近似可以写成下边的式子</p><p><img src="image-26.png" /></p><p>为什么这俩是等价的,因为<spanclass="math inline"><em>l</em><em>n</em><em>π</em></span>求导等于<spanclass="math inline"><em>π</em><sup>′</sup>/<em>π</em></span>所以对于<span class="math inline"><em>π</em></span>求梯度就等于<spanclass="math inline"><em>π</em></span>乘以<spanclass="math inline">▽<em>l</em><em>n</em><em>π</em></span></p><p><img src="image-27.png" /></p><p>这样做了之后,我们就可以做出如下推导,其中<spanclass="math inline"><em>s</em></span>服从<spanclass="math inline"><em>d</em></span>,就可以写成期望的形式<spanclass="math inline"><em>a</em></span>服从<spanclass="math inline"><em>π</em></span>,对他求和也可以把这个期望的式子变成<spanclass="math inline"><em>E</em>[<em>S</em> <em>d</em>, <em>A</em> <em>π</em>]</span>,这样整个式子就相当于是在S,A上进行采样,并求和的方式,能够近似计算出梯度。注意这里边s,a和S,A的含义区别</p><p>需要指出的是,为了要从上面式子中得到<spanclass="math inline"><em>l</em><em>n</em><em>π</em></span>,我们必须有约束:<spanclass="math inline"><em>π</em>>0</span>。 而为了引入<spanclass="math inline"><em>π</em>>0</span>,我们可以用到<spanclass="math inline"><em>s</em><em>o</em><em>f</em><em>t</em><em>m</em><em>a</em><em>x</em></span>函数,这个函数可以进行归一化。我们就直接令这个<spanclass="math inline"><em>π</em></span>的函数长成<spanclass="math inline"><em>s</em><em>o</em><em>f</em><em>t</em><em>m</em><em>a</em><em>x</em></span>函数的形式(反正π是个函数,可以任意指定),从而保证他的范围。这里的<spanclass="math inline"><em>h</em></span>函数是<code>feature function</code>,现在都是神经网络里面提取</p><p><img src="image-28.png" /></p><p>在神经网络里面提取的方式是输入特征,经过隐藏层,再用softmax激活,就可以得到不同层的概率。</p><p><img src="image-29.png" /></p><p>由于每个动作的概率是大于0而且小于1,这里的π就具有随机性和探索性</p><p><img src="dfdd3b9b9b9996d36c0194a2fd66d483.jpeg" /></p><p>需要说的是,上面的方法只适合离散动作,像连续动作的话适合用dpg类的方法来处理。</p><h2id="五梯度上升方法和reinforce算法">五、梯度上升方法和REINFORCE算法</h2><p>上面的文章里介绍了策略梯度。可以用梯度上升的方式进行求解。</p><p><img src="image-30.png" /></p><p>把上面的梯度公式代进来,就得到了梯度上升的计算公式。但是公式1是没法用的因为里面有一个期望的形式。为了计算可以把梯度换成公式2的随机形式,但是公式2也是没法用的,因为里面依赖<spanclass="math inline"><em>q</em><sub><em>π</em></sub>(<em>s</em>,<em>a</em>)</span>,也就是真实的动作值(如果我们知道q_π的真实值那就不用迭代了)。</p><p><img src="image-31.png" /></p><p>我们想到既然<spanclass="math inline"><em>q</em><sub><em>π</em></sub></span>未知,那么就可以用一个近似量<spanclass="math inline"><em>q</em><sub><em>t</em></sub></span>去替代,,这个方法就是<code>reinforce</code>方法。他计算<spanclass="math inline"><em>q</em><sub><em>t</em></sub></span>的方法是用蒙特卡洛,从一个<spanclass="math inline"><em>s</em></span>,<spanclass="math inline"><em>a</em></span>出发,历经一个完整的回合,计算得到完整的轨迹,用轨迹的<spanclass="math inline"><em>g</em></span>去计算<spanclass="math inline"><em>q</em><sub><em>t</em></sub></span>。</p><p>同时还有一些td方法可以介绍,这些是属于a2c方法的范畴。</p><p>如何采样s和a? 对于s,我们可以从长期运行的稳定的策略里面得到对于a,我们可以从给定的π里面采集。这里面π依赖于s和θ。因此他的行为策略和目标策略是一致的,属于是on-policy。</p><p><img src="image-32.png" /></p><p>接下来再来审视一下这个式子,对这个式子进行变形,然后把梯度<spanclass="math inline">▽<em>π</em></span>放到右边,公式就变成了中间的公式的形状。,令中间的<spanclass="math inline"><em>q</em>/<em>π</em>=<em>β</em><sub><em>t</em></sub></span>,所以梯度上升的式子就变成了在优化<spanclass="math inline"><em>π</em>(<em>a</em><sub><em>t</em></sub>|<em>s</em><sub><em>t</em></sub>)</span></p><p><img src="image-33.png" /></p><p>上面的梯度迭代公式变形后,我们发现,如果<spanclass="math inline"><em>β</em><em>t</em>>0</span>,那么在<spanclass="math inline"><em>s</em><sub><em>t</em></sub></span>下选择<spanclass="math inline"><em>a</em><sub><em>t</em></sub></span>的概率就增加了。如果<spanclass="math inline"><em>β</em><sub><em>t</em></sub><0</span>,那么概率减小。</p><p><img src="image-34.png" /></p><p>从数学上说,当<spanclass="math inline"><em>θ</em><sub><em>t</em> + 1</sub> − <em>θ</em><sub><em>t</em></sub></span>足够小的时候,就有π和后面的梯度是约等的。这个是源于微分。从微分上把第一行的公式代入就得到了 <spanclass="math inline"><em>π</em><em>θ</em><sub><em>t</em> + 1</sub>=<em>π</em><sub><em>θ</em><sub><em>t</em></sub></sub> + <em>a</em><em>β</em><sub><em>t</em></sub> ▽ <em>π</em><em>t</em>²</span>,也就是说βt决定了优化的方向当<spanclass="math inline"><em>β</em><sub><em>t</em></sub>>0</span>,概率增大<spanclass="math inline"><em>β</em><sub><em>t</em></sub><0</span>,概率减小</p><p><img src="image-35.png" /></p><p><spanclass="math inline"><em>β</em><sub><em>t</em></sub></span>系数的特性是平衡探索与利用。<span class="math inline"><em>β</em><sub><em>t</em></sub></span>和<spanclass="math inline"><em>q</em><sub><em>t</em></sub></span>成正比,如果<spanclass="math inline"><em>q</em><sub><em>t</em></sub></span>比较大,那么<spanclass="math inline"><em>β</em><sub><em>t</em></sub></span>也比较大,从而倾向于选,让<spanclass="math inline"><em>q</em></span>比较大的动作。 <spanclass="math inline"><em>β</em></span>和<spanclass="math inline"><em>π</em></span>成反比,如果<spanclass="math inline"><em>π</em></span>是比较小的,那么<spanclass="math inline"><em>β</em><sub><em>t</em></sub></span>算出来会比较大,从而增加模型的探索性</p><p>六、reinforce算法</p><p><img src="image-36.png" /></p><p>如果里面的<spanclass="math inline"><em>q</em><sub><em>π</em></sub></span>是从蒙特卡洛得到的,那他就是reinforce算法。reinforce是最早的策略梯度算法 伪代码如下</p><p><img src="image-37.png" /></p><p>这里<spanclass="math inline"><em>θ</em></span>更新了之后,并不是立刻用于模型的动作选取。这是因为蒙特卡洛的方法是off-policy的,要等所有的回合都遍历结束了之后才能选取。</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(八)值函数近似</title>
<link href="/2026/02/25/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%85%AB%EF%BC%89%E5%80%BC%E5%87%BD%E6%95%B0%E8%BF%91%E4%BC%BC/"/>
<url>/2026/02/25/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%85%AB%EF%BC%89%E5%80%BC%E5%87%BD%E6%95%B0%E8%BF%91%E4%BC%BC/</url>
<content type="html"><![CDATA[<h1 id="强化学习笔记八值函数近似">强化学习笔记(八):值函数近似</h1><p>在实际场景中,我们的状态空间是非常大的,如果我们使用之前的建立一个个表格的方法,他的数据规模也会非常大,这显然不是一个很现实的解决方案。我们希望提出一种新的valuefunction,他可以近似等于真正的valuefunction,但是我们不再需要为每一个state建立一个映射,也就是一种和函数拟合思路类似的方法。在这一章,也是首次将神经网络引入强化学习。</p><p><img src="image.png" /></p><p>前面7章中我们所有的算法都是基于表格的</p><p>表格的优点是分析起来容易,但是缺点是没法离散,为此来引入神经网络来估计值</p><p>首先先看一个经典的问题:神经网络拟合</p><p><img src="image-1.png" /></p><p>我们可以用直线来进行拟合,这里w就是待拟合曲线的参数。</p><p><img src="image-2.png" /></p><p>这样我们不用存储很多的值,用简单的参数w来表达就可以。</p><p>当然,也可以用很多的其他的曲线来拟合。</p><p><img src="image-3.png" /></p><p>总结:用曲线拟合的优势有两个:存储和泛化,泛化这一点可以看课件的解释,更新一个值其他相近的值也会改变。</p><p><img src="image-4.png" /></p><h1 id="二值函数估计算法">二、值函数估计算法</h1><h2 id="目标函数选择">2.1 目标函数选择</h2><p><img src="image-5.png" /></p><p>目的:找到最优的目标函数,使得它能给每个状态估值</p><p><img src="image-6.png" /></p><p>这是我们的目标函数,目标是找到最优权重让<spanclass="math inline"><em>J</em>(<em>w</em>)</span>最小。这里面还涉及到一个随机变量:<spanclass="math inline"><em>S</em></span></p><p>这里我们采用的是随机变量<spanclass="math inline"><em>s</em></span>,怎么去估计它的概率分布呢?第一种方法是使用同一分布,认为每个状态出现概率是一样的,去估计状态的概率</p><p><img src="image-7.png" /></p><p>第二种方法是用稳态分布,其实就是把里面的状态的权重变成了可变的。可以认为这个时候状态是平稳的,跑了很多次之后才出现的状态</p><p><img src="image-8.png" /></p><p>举个例子,<span class="math inline"><em>d</em></span>认为是多个状态的加权,在跑算法很多步之后,最后算出来的状态值都是平稳的。</p><p><img src="image-9.png" /></p><h2 id="优化的算法">2.2 优化的算法</h2><p><img src="image-11.png" /></p><p>优化的式子也是用迭代的方式去计算。但是这种计算方式需要依赖真实梯度,我们不知道真实的梯度怎么求,很自然的想到用随机梯度代替真实梯度</p><p><img src="image-10.png" /></p><p>这里面需要依赖真实梯度,没法获取就得想办法替代</p><p>一种方式是用蒙特卡洛的方式,依赖整个的<code>return</code>另一种是用<code>td learning</code>的方式去获取,这里就是把<spanclass="math inline"><em>v</em></span>换成了<code>td target</code>,也就是$ r + γ $</p><p><img src="image-12.png" /></p><p>这个方法就可以用于更新算法。这里还有一步就是<spanclass="math inline"><em>v̂</em></span>的选择:它可以是不同的函数,可以是线性的或非线性的。</p><p><img src="image-13.png" /></p><p><span class="math inline"><em>v̂</em></span>的选择:过往会选择线性或者多项式或者傅里叶的组合,现在更常用的是基于神经网络,引入非线性</p><p><img src="image-14.png" /></p><p>线性<span class="math inline"><em>v̂</em></span>的例子</p><p><img src="image-15.png" /></p><p>使用线性<span class="math inline"><em>v̂</em></span>的优势和劣势:<img src="image-16.png" /></p><p>线性函数还是有比较强的表征能力。而且比较便于理解。需要指出的是q表的方式其实是线性的方式的一种特殊形式 <imgsrc="image-17.png" /></p><p>例子:</p><p><img src="image-18.png" /></p><p>用平面作为待预测的vhat的函数去拟合真实v,,并且进行三维可视化。发现平面趋势差不多,但细节上还是有区别</p><p><img src="image-19.png" /></p><p><span class="math inline"><em>v̂</em></span>换用二次、三次函数之后,效果立竿见影</p><p>通过值函数的td学习主要就是做这件事:用近似的 <spanclass="math inline"><em>v̂</em></span> 去替换真实的,不知道的这个<spanclass="math inline"><em>v</em></span>,尽管这俩可能并不相等,甚至由于<span class="math inline"><em>v̂</em></span>的选取,可能永远无法拟合<spanclass="math inline"><em>v</em></span>(例如 <spanclass="math inline"><em>v̂</em></span> 选平面,就永远无法拟合)</p><p><img src="image-20.png" /></p><h1 id="三sarsa和值函数近似的结合">三、sarsa和值函数近似的结合</h1><p>如图所示,算法这里的值函数都用<spanclass="math inline"><em>q̂</em></span>去进行替代了原来的时序差分学习。(之前介绍的部分是针对<spanclass="math inline"><em>v̂</em></span>,这里是<spanclass="math inline"><em>q̂</em></span>)</p><p><img src="image-21.png" /></p><p>伪代码如图所示,基本上,步骤就是将<spanclass="math inline"><em>w</em></span>的更新用<spanclass="math inline"><em>q</em></span>函数替换掉</p><p><img src="image-22.png" /></p><h1 id="四q-learning和值近似的结合">四、q-learning和值近似的结合</h1><p>直接给出迭代的公式</p><p><img src="image-24.png" /></p><p>其实就是sarsa把td target 替换一下伪代码如下:需要指出的是这里给出的是on-policy版本的ql,当然他也可以是off-policy的。</p><p><img src="image-23.png" /></p><h1 id="五dqn">五、dqn</h1><p>dqn引入了两个机制:<code>经验回放</code>和<code>目标网络</code>,目标是打破样本的相关性。</p><p><strong>dqn的目标</strong>:最小化<code>td target</code>,这里的<code>td target</code>就是贝尔曼最优误差。所以这两个值做差,它的期望应该是0。dqn通过调整网络参数θ,缩小预测值和目标值之间的差距。</p><p><img src="image-25.png" /></p><p>优化的方法:梯度下降 直接用梯度下降方法是比较困难的,因为参数<spanclass="math inline"><em>w</em></span>不仅仅位于<spanclass="math inline"><em>q̂</em></span>中,也位于<spanclass="math inline"><em>y</em></span>中。为了简化运算,我们在计算的过程中先固定住<spanclass="math inline"><em>y</em></span>中的<spanclass="math inline"><em>w</em></span>,来进行运算。</p><p><img src="image-26.png" /></p><p>为了处理不一致性,这里还应用了一个技巧:两个神经网络:<strong>mainnetwork</strong> 和<strong>target network</strong>,固定一个网络去更新另一个</p><p><img src="image-28.png" /></p><p><img src="image-27.png" /></p><h2 id="经验回放">经验回放</h2><p>为什么经验回放是必须的?</p><p><img src="image-29.png" /></p><p>答案在更新的式子中,s,a是从分布中采样的,s,a被认为是一个随机变量</p><p><img src="image-31.png" /></p><p><img src="image-30.png" /></p><p>为什么ql里面没有涉及到shared replay?因为QL都没有涉及到分布,纯回放。</p><p><img src="image-32.png" /></p><p>dqn涉及到分布,求的是s,a的平均,ql里面没有分布的概念,纯粹是求贝尔曼公式。当然ql也可以使用经验回放</p><h2 id="dqn的伪代码off-policy">dqn的伪代码(off-policy)</h2><p><img src="image-33.png" /></p><p>①没有策略更新的原因:这个是off-policy</p><p>②为什么不用policy updateevaluation?其实是可以可以直接用$w_{t+1}=w_t +a_t(r+γmax-),但是现在都有神经网络了,可以直接用神经网络里面的黑盒性质来优化算法。</p><p>③这个算法实现和dqn论文原文不一样,dqn原文是输入s输出5个动作的q,这个是输入sa输出q,内涵是一样的</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(七)时序差分方法</title>
<link href="/2026/02/09/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%83%EF%BC%89%E6%97%B6%E5%BA%8F%E5%B7%AE%E5%88%86%E6%96%B9%E6%B3%95/"/>
<url>/2026/02/09/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%83%EF%BC%89%E6%97%B6%E5%BA%8F%E5%B7%AE%E5%88%86%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<h3 id="时序差分算法概述">时序差分算法概述</h3><p>时序差分(Temporal-Difference,TD)方法是强化学习中最核心的无模型学习技术之一,它结合了蒙特卡洛方法的采样能力与动态规划的自举思想,能够在无需环境模型的情况下进行在线、增量式更新。</p><p>核心思想是利用当前状态的价值估计与下一状态的价值估计之间的差异(TD误差)来更新价值函数。包括状态值的td学习、动作值的td学习、最优动作值估计的TD学习</p><h3 id="时序差分算法的目的和公式推导">时序差分算法的目的和公式推导</h3><p>时序差分算法的目标是求解statevalue,更具体的目标是把一个状态下的最优策略找到。</p><p>需要指出的是tdlearning也是一种强化算法,但和这里特指的时序差分学习不一样。</p><p>td 算法的输入是在一个策略下生成的轨迹,又称experience。td算法的目的是估计一个具体的策略下的状态值</p><p><img src="image.png" /></p><p>对于访问到的状态,用第一个式子更新,对于所有没访问到的状态,用第二个式子(保持不变)</p><p><img src="image-1.png" /></p><p>新的估计是由旧的估计加上修正项得到的。这个修正项后面的那部分(<spanclass="math display"><em>r</em><sub><em>t</em> + 1</sub> + <em>γ</em><em>v</em><em>t</em>(<em>s</em><sub><em>t</em></sub> + 1)</span>)叫做<code>td target</code>,两个状态的差叫<code>td error</code></p><p>为什么后面那部分叫做<code>td target</code>?因为每次迭代的过程中,<spanclass="math inline"><em>v</em><sub><em>t</em></sub></span>都是会趋近于<spanclass="math inline"><em>v</em><sub><em>t</em><em>a</em><em>r</em><em>g</em><em>e</em><em>t</em></sub></span></p><p><img src="image-2.png" /></p><p>这是具体证明,式子变形了之后,可以看到左右两边是相等的,但是有一个系数<spanclass="math inline"><em>α</em></span>在,所以每次更新,<spanclass="math inline"><em>v</em><sub><em>t</em> + 1</sub></span>都要比<spanclass="math inline"><em>v</em><sub><em>t</em></sub></span>更加靠近<spanclass="math inline"><em>v</em><sub><em>t</em><em>a</em><em>r</em><em>g</em><em>e</em><em>t</em></sub></span></p><p><img src="image-4.png" /></p><p><code>td error</code> 的理解,<code>td error</code>的期望就是<spanclass="math inline"><em>v</em><sub><em>t</em></sub></span>和<spanclass="math inline"><em>v</em><sub><em>π</em></sub></span>的差距,<spanclass="math inline"><em>v</em><sub><em>π</em></sub></span>是完备状态下的状态值。<spanclass="math inline"><em>v</em><sub><em>t</em></sub></span>是当前迭代的状态值。,这俩作差,期望应该是0。如果俩相等说明状态值是完备的。如果不等那就是有差距的。td误差可以用innovation来解释,这里指的是“新的信息”,可以认为每次更新一个experience就是引入新的信息。</p><p><img src="image-5.png" /></p><p>td算法的特点:</p><ol type="1"><li><p>可以估计状态值,但是不能估计动作值,也不能搜索最优状态</p></li><li><p>td算法做的事也是求贝尔曼公式,但是是在没有模型的情况下求解</p></li><li><p>td算法引入了一个新的贝尔曼公式:bellman expection form:bellmanexpection form</p></li></ol><p><img src="image-6.png" /></p><p>td算法也可以用rm算法来求解</p><p><img src="image-7.png" /></p><p>和td公式有点不一样 一个是vπ那里和td的公式有点不一样,td用的是vk。但是这里,td算法可以直接点把vπ替换成vk,替换成他的估计值</p><p><img src="image-8.png" />td算法求解状态值的前提条件,和rm算法基本上是一样的。</p><p>比较一下td算法和mc算法,主要是有两点不同</p><p><img src="image-9.png" /></p><p>第一点:是td是online的,不需要等回合结束就可以立刻更新,而mc必须等回合结束。</p><p>第二点:是td可以用于无穷尽的任务,mc只能用于有回合的任务。</p><p><img src="image-10.png" /></p><p>第三点:是td是一种自举的算法,在计算过程中需要依赖以前的状态进行自举,而mc算法不需要自举(不依赖以前的估计值)</p><p>第四点:是td是一种低方差的算法,计算过程中变量较少,mc算法在计算过程中用到了整个完整回合的数据,方差较大。如果我有5个动作,100步,那么mc算法他就有5的100次方种轨迹</p><h3 id="td学习估计动作值sarsa">TD学习估计动作值——sarsa</h3><p>td算法是用来估计状态值的,不能估计动作值 sarsa是估计动作值的,属于是policy evaluation,可以跟policy improment</p><p><img src="image-11.png" /></p><p>这里,q(s.a)是qπ(s,a)的一个估计值。可以看到,这算法和td算法是一模一样,只不过v换成了q。就是知道了新的s,a,就可以去更新q,如果没有访问到某个状态、动作,那么q值就保持不变。</p><p><img src="image-12.png" /></p><p>sarsa和td都是在求解贝尔曼公式,只是公式形式有所不同</p><p>sarsa的收敛性和证明都和td类似。</p><p><img src="image-13.png" /></p><p>sarsa算法的伪代码如下(和策略改进过程结合)策略改进的过程:还是和之前一样,用的epislon</p><p><img src="image-14.png" /></p><p>sarsa进行策略评估的时候只用了一个sa对就对策略进行更新。这是相比之前的办法最大的不同<img src="image-15.png" /></p><p>案例:sarsa算法走格子,找一条路径对应能走到终点的好状态,这个算法跑的结果如图所示,有几点必须要说:第一点:不是所有状态都是最优的,探索比较少的地方,她的格子对应的策略不一定最优。第二点:策略是不断在迭代中改进的,最开始好几百步猜得到目标,到后来就是几十步就能得到目标。</p><p><img src="image-16.png" /></p><h3 id="expected-sarsa算法">expected sarsa算法</h3><p><img src="image-17.png" /></p><p>sarsa就是把tdtarget里面的采样一个动作变成采样所有的动作,并且求和。只不过替换了一下,替换成了期望的形式</p><p><img src="image-18.png" /></p><h3 id="n-step-sarsa">n-step sarsa</h3><p>n-step sarsa是sarsa和mc的结合</p><p><img src="image-19.png" /></p><p>在q的定义里面,q是当前状态、动作条件下,能够获得的期望回报和。但是这个期望回报和是可以有多种定义的。第一种定义就是sarsa的定义,只用一步的q迭代作为新的sarsa的值第二种是迭代n步,用n步迭代的g作为新的q的估计第三种就是迭代无穷步,对应mc算法</p><p><img src="image-20.png" /></p><p>1-step sarsa 需要1个数据 n-step sarsa 需要n个数据mc需要无穷多个数据</p><p><img src="image-21.png" /></p><p>sarsa需要n步数据,也就是他虽然是online但不是立即更新,而是需要收集<spanclass="math display"><em>t</em> + <em>n</em></span>个数据再更新。</p><p>n step sarsa 是sarsa和mc的两个极端情况的折中mc算法需要无穷步数据,对应的偏差比较小,方差大。 sarsa 算法需要1步数据对应方差比较大</p><p>n-step就是靠n来平衡。</p><h3 id="q-learning">Q-learning</h3><p>q-learning 直接做的是估计optimal action value,这个过程不需要policyimprovement 和policy evaluation</p><p><img src="image-22.png" /></p><p><code>q-learning</code>和<code>sarsa</code>的区别在于,<code>sarsa</code>在算<code>td target</code>的时候需要把<spanclass="math inline"><em>a</em><sub><em>t</em> + 1</sub></span>代进去算下一步的<spanclass="math inline"><em>q</em></span>,<code>q-learning</code>在计算<code>td target</code>的时候</p><p><code>q-learning</code>实际在做的也是求解贝尔曼方程,核心的公式表达不一样。第一个不一样的地方是,他是基于动作值<spanclass="math inline"><em>q</em></span>而不是状态值<spanclass="math inline"><em>v</em></span>第二个不一样的地方是,他写成了期望的形式</p><p><img src="image-23.png" /></p><p>他最终求解的是最大的<spanclass="math inline"><em>q</em></span>值,最大的<spanclass="math inline"><em>q</em></span>对应最优的策略。</p><p>借此引入两个概念 <code>on-policy</code> 和<code>off-policy</code></p><p><img src="image-24.png" /></p><p>具体来说就是俩策略:<code>behavior policy</code>和<code>target policy</code>做动作的策略和行为的策略是不是同一个,如果是同一个这就是<code>behavior policy</code>,否则就是<code>target policy</code></p><p><img src="image-25.png" /></p><p><code>off-policy</code>的好处就是可以拿别人的经验来学习</p><p>如何判断一个算法是<code>on-policy</code>还是<code>off-policy</code>主要有两点:看算法的数学公式 第二点:确认算法里需要实现的事</p><p>以sarsa举例,他为什么是<code>on-policy</code></p><p><img src="image-26.png" /></p><p>因为他在使用样本更新模型的时候,需要<spanclass="math display"><em>a</em><sub><em>t</em> + 1</sub></span>,这个<spanclass="math display"><em>a</em><sub><em>t</em> + 1</sub></span>又需要依赖策略模型。也就是说在算法运行的时候,<spanclass="math inline"><em>s</em><sub><em>t</em></sub></span>,<spanclass="math inline"><em>a</em><sub><em>t</em></sub></span>这俩都是给定,<spanclass="math inline"><em>r</em><sub><em>t</em> + 1</sub></span>和<spanclass="math inline"><em>s</em><sub><em>t</em> + 1</sub>’</span>又是根据环境模型给出的(和rl无关,和world有关)下一步的动作是用新策略生成的,<code>target policy</code>和<code>behavior policy</code>是同一个,所以他的策略是<code>on-policy</code></p><p><img src="image-27.png" /></p><p>蒙特卡洛算法也是<code>on-policy</code> 蒙特卡洛方法的目的是求解<code>action value</code>,目的是从很多的轨迹中得到一系列的<code>return</code>,从<code>return</code>中获得<code>action value</code>。接下来,用<code>return</code>来估计<spanclass="math inline"><em>q</em><sub><em>π</em></sub></span>得到qπ之后就拿来改进<span class="math inline"><em>π</em></span>了。所以这里的<spanclass="math inline"><em>π</em></span>既是<code>behavior policy</code>又是 <code>target policy</code>。</p><p><img src="image-28.png" /></p><p>再来看<code>q-learning</code>,这是一个<code>off-policy</code>,它的原因是因为选动作的时候他是用<spanclass="math inline"><em>m</em><em>a</em><em>x</em><em>q</em></span>的策略去选,在更新模型的时候行为策略实际上是不依赖于连续轨迹的,可以通过##各种方式##采样得到。而目标策略是最优策略。这个时候,它的目标是最优策略,但是行为策略不依赖于这个最优策略,可以从任何策略中得到。</p><p>简单来说就是获取数据用的策略是不是我要用的最终策略,是就是<code>on-policy</code>。</p><h3 id="q-learning-的伪代码">q-learning 的伪代码</h3><p><img src="image-29.png" /><code>q-learning</code>是<code>off-learning</code>也就是允许行为策略和目标策略不同,当然也允许这俩策略相同,这时候他就是<code>on-policy</code>。</p><p><img src="image-30.png" /><code>off-policy</code>环境下,假设已经有了一系列轨迹,来自于任意环境,这个环境的策略是<spanclass="math inline"><em>π</em><sub><em>b</em></sub></span>。现在用这个<spanclass="math inline"><em>π</em><sub><em>b</em></sub></span>的策略去更新我们自己的<spanclass="math inline"><em>π</em></span>。目标策略和图上一样,按照最大<code>q</code>来选动作,这时候他是贪心的,这个贪心的目标策略<spanclass="math inline"><em>π</em><sub><em>t</em></sub></span>是最终用的策略。</p><p><img src="image-31.png" /></p><p>ql的案例,通过随机的环境策略πb采样(一百万步)每步5个动作各个的概率是0.2。</p><p><img src="image-32.png" /></p><p>然后用off-policy去学习</p><p>如果数据的样本是比较差的(比如往右走的概率是很大,那么很多的步就没探索到)</p><p><img src="image-33.png" /></p><h3 id="总结td学习的统一形式">总结td学习的统一形式</h3><p><img src="image-34.png" /></p><p>这一章介绍的算法都有统一的形式:</p><p><spanclass="math display"><em>q</em>(<em>s</em>, <em>a</em>)<sub><em>t</em> + 1</sub>=<em>q</em>(<em>s</em>,<em>a</em>)<sub><em>t</em></sub> − <em>a</em><sub><em>t</em></sub>(<em>q</em> − <em>q̂</em>)</span></p><p>其中后面的叫<code>td target</code>,后边俩相减的叫<code>td error</code>根据前面所学的知识,我们知道用这个式子迭代,能让q接近 tdtarget对应的这个q。 只是不同的算法有不同的td target 表达式。他们在做的事都是求解统一的公式</p><p>这些方法本质上都是求解给定策略的贝尔曼公式</p><p><img src="image-35.png" /></p><p>不同算法的区别的本质就是<code>td-target</code>的不同。</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(六)随机近似理论与随机梯度下降方法</title>
<link href="/2026/01/28/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%85%AD%EF%BC%89%E9%9A%8F%E6%9C%BA%E8%BF%91%E4%BC%BC%E7%90%86%E8%AE%BA%E4%B8%8E%E9%9A%8F%E6%9C%BA%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E6%96%B9%E6%B3%95/"/>
<url>/2026/01/28/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%85%AD%EF%BC%89%E9%9A%8F%E6%9C%BA%E8%BF%91%E4%BC%BC%E7%90%86%E8%AE%BA%E4%B8%8E%E9%9A%8F%E6%9C%BA%E6%A2%AF%E5%BA%A6%E4%B8%8B%E9%99%8D%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<h1 id="动机">动机</h1><p>回顾一下期望的定义,为什么我们要计算期望?是为了取平均。</p><p>为什么要计算期望?因为强化学习的本质就是求期望,求状态下能够获得的回报的期望、动作获得的未来回报的期望</p><p>求期望有两种方式:</p><p>①全量求期望,收集所有的样本然后求平均</p><p>②增量的方式求期望。</p><p>增量的方式求期望,如图所示,是可以通过推导,得到<spanclass="math inline"><em>w</em><sub><em>k</em> + 1</sub></span>和<spanclass="math inline"><em>w</em><sub><em>k</em></sub></span>之间的关系的。通过迭代最下面的式子可以实现来一个新增数据得一个期望</p><p><img src="image.png" /></p><p><img src="image-1.png" /> 这也是tddifference的基础,迭代式的求平均数。</p><p>我们不一定取<spanclass="math inline">1/<em>k</em></span>,而是取一个自适应的参数<spanclass="math inline"><em>a</em><sub><em>k</em></sub></span>,在满足某些条件的情况下,可以证明,这个迭代的式子也是可以收敛在<spanclass="math inline"><em>E</em>[<em>X</em>]</span>的。</p><p><img src="181a925f1728e0c93c954628125b8b73.jpg" /></p><p>这个就是随机梯度下降的方法。 核心的迭代式子就是 <spanclass="math display"><em>w</em><sub><em>k</em> + 1</sub> = <em>w</em><sub><em>k</em></sub> − <em>a</em><sub><em>g</em></sub>(<em>w</em><sub><em>k</em></sub>, <em>η</em><sub><em>k</em></sub>)</span></p><p>这里n是噪音项。</p><h2 id="rm算法">RM算法</h2><p>首先引入Robbins-Monro算法(RM算法),RM算法是一种经典的随机近似算法,用于解决寻根问题或优化问题,尤其在目标函数未知或无法直接计算导数的情况下表现出色。它是随机梯度下降(SGD)等现代优化算法的理论基础。</p><p><img src="image-2.png" /></p><p>我们要求一个问题,是求一个函数的最小值,来优化损失函数<spanclass="math display"><em>J</em>(<em>w</em>)</span>。我们很容易想到对这个损失函数进行求导数,并且令这个导数为0,从而得到这个导数的解。但是很多时候,我们并不知道这个导数长什么样子,只知道大概的形状。那么我们的问题就转换成:给定一个随机的观测噪声的函数<code>g</code>,求<code>g(w)=0</code>,从而得到原问题的导数的零点。原函数的最优值就在这些零点中间。</p><p>RM算法是用迭代的方式去求<code>g(w)=0</code>的一个方法,也就是g这个函数的根,这里g的原始值不知道,只知道它是带噪声的观测。这里噪声就是<spanclass="math display"><em>η</em><sub><em>k</em></sub></span>。</p><p>这种迭代的方式蕴含着哲学思想:<strong>没有模型就得有数据,没有数据就得有模型</strong></p><p><img src="984dd7c3c49d49916cd4f81f09df6b13.jpg" /></p><p>RM算法的迭代的方法计算可以如图得到,每一次迭代,<spanclass="math inline"><em>w</em><sub><em>k</em> + 1</sub></span>都是比<spanclass="math inline"><em>w</em><sub><em>k</em></sub></span>更靠近<spanclass="math inline"><em>w</em>*</span>的,哪怕是<spanclass="math inline"><em>w</em><sub><em>k</em></sub><<em>w</em>*</span>的情况。而rm算法需要满足三个条件:</p><p>第一个条件是:<spanclass="math inline"><em>g</em>(<em>w</em>)</span>的梯度必须是正数,而且必须有上界。</p><p>这个条件看着很严格,但是是求解原问题必不可少的。</p><p>因为比如说我们要求 <spanclass="math inline"><em>m</em><em>i</em><em>n</em><em>J</em>(<em>w</em>)</span>,如果我们已知<spanclass="math inline"><em>g</em>(<em>w</em>)</span>是<spanclass="math inline"><em>j</em>(<em>w</em>)</span>的导数,上述问题就转化成求<spanclass="math inline"><em>g</em>(<em>w</em>)= ▽ <em>j</em>(<em>w</em>)=0</span>所以<spanclass="math inline"><em>g</em>(<em>w</em>)</span>的导数就是j的二阶导。二阶导要有零点,它的何塞矩阵必须全是正数。<img src="image-7.png" alt="第一个条件的解释" /></p><p>第二个条件是<spanclass="math inline"><em>a</em><sub><em>k</em></sub></span>必须趋于0,随着k趋于无穷。但是<spanclass="math inline"><em>a</em><sub><em>k</em></sub></span>趋于0又不能趋于的太快,因此又要求∑ak发散。</p><p>如果<spanclass="math inline">∑<em>a</em><sub><em>k</em></sub></span>不发散,而<spanclass="math inline"><em>w</em>*</span>距离<spanclass="math inline"><em>w</em><sub>1</sub></span>很远,在这种情况下,可能迭代到中间的某几步,就停止了。无法从<spanclass="math inline"><em>w</em><sub>1</sub></span>迭代到<spanclass="math inline"><em>w</em>*</span></p><p><img src="image-6.png" /></p><p><spanclass="math inline"><em>a</em><em>k</em>=1/<em>k</em></span>的时候是符合上述条件的一个解。证明就是那个p级数的证明。但是实际问题里,不会取<spanclass="math inline"><em>a</em><em>k</em>=1/<em>k</em></span>,而是取一个很小的常数。从而让后面进来的数据也能使用到</p><p><img src="image-5.png" /></p><p>RM算法可以用于平均值问题估计的证明,因为估计问题满足上面的性质。</p><p><img src="image-4.png" /></p><h2 id="随机梯度下降算法">随机梯度下降算法</h2><p>随机梯度下降(Stochastic Gradient Descent,SGD)是一种高效的优化算法,通过迭代更新模型参数来最小化损失函数。与批量梯度下降(BGD)相比,SGD每次只使用一个或少量样本计算梯度,从而显著降低计算成本,并在大规模数据集上表现优异。</p><p>sgd算法在机器学习领域经常用,在强化领域也经常用。sgd是一种特别的rm算法 平均值估计算法是一种特殊的sgd算法。</p><p>现在假设我们要求一个优化问题:</p><p><spanclass="math inline"><em>m</em><em>i</em><em>n</em><em>j</em>(<em>w</em>)=<em>E</em>[<em>f</em>(<em>w</em>, <em>x</em>)]</span></p><p>这里表示对参数w进行优化,目标是让<spanclass="math inline"><em>J</em>(<em>w</em>)</span>的损失函数最小化,最小化这个损失函数等价于最小化在给定随机变量X的环境下,通过参数<spanclass="math inline"><em>w</em></span>、<spanclass="math inline"><em>f</em>(<em>w</em>,<em>x</em>)</span>来估计期望,调整w来优化估计期望的准确度。</p><p><span class="math inline"><em>w</em></span>是待优化变量;<spanclass="math inline"><em>X</em></span>是随机变量,它的期望由多个数据<spanclass="math inline"><em>x</em></span>决定</p><p><img src="image-3.png" /></p><p>优化目标函数<span class="math inline"><em>J</em></span> 是<spanclass="math inline"><em>f</em></span>的期望 <spanclass="math inline"><em>x</em></span>是随机分布, 现在要找到最优<spanclass="math inline"><em>w</em></span>,使得目标函数最小</p><p>有三种方法去求解这个期望:</p><figure><img src="image-8.png" alt="梯度下降和batch梯度下降" /><figcaption aria-hidden="true">梯度下降和batch梯度下降</figcaption></figure><p>第一种是gd梯度下降,目标函数按照梯度的方式下降,这种方式要求明确知道梯度。而期望值是难以获得的。</p><p>第二种方法对应的是没有模型的情况下,采用类似蒙特卡洛的方式从一批数据中近似获取他的均值。</p><p>第三种就是要介绍的SGD方法,其中是将其中的真实梯度替换成了随机梯度。而真实的梯度不一定能够求得,所以这里替换成随机梯度,里面有个随机的采样变量<spanclass="math inline"><em>x</em><sub><em>k</em></sub></span>。</p><figure><img src="image-9.png" alt="随机梯度下降" /><figcaption aria-hidden="true">随机梯度下降</figcaption></figure><p>和bgd算法的区别:就是令n=1</p><h3 id="sgd算法的收敛性和案例">SGD算法的收敛性和案例:</h3><p>现在我们举一个例子,优化一个目标,这个目标是求随机变量x和w之间的距离最小。首先,有一个小问题,我们需要证明w的最优解是EX,证明的如红笔所示。</p><figure><img src="image-10.png" alt="证明" /><figcaption aria-hidden="true">证明</figcaption></figure><p>然后,我们比较<strong>GD算法</strong>和<strong>SGD算法</strong>的差别</p><figure><img src="image-11.png" alt="GD和SGD的区别" /><figcaption aria-hidden="true">GD和SGD的区别</figcaption></figure><p><strong>GD算法</strong>:首先要知道优化目标的梯度,然后把梯度写出来。</p><p><strong>SGD算法</strong>:直接把<spanclass="math inline"><em>E</em><em>x</em></span>替换成梯度,也就是<spanclass="math inline"><em>w</em><sub><em>k</em></sub> − <em>x</em><sub><em>k</em></sub></span>。</p><p>这个公式的形式,和之前给出的平均值估计问题类似,就是有一组数,用增量的方式求平均值,是一样的,这也就是说,平均值估计算法是一种特殊的<spanclass="math inline"><em>s</em><em>g</em><em>d</em></span>算法。在那个算法里<spanclass="math inline"><em>a</em><sub><em>k</em></sub>=1/<em>k</em></span>,或者其他的值。</p><p>也就是说,求平均值问题可以描述成优化问题,描述成最小化期望问题这俩是殊途同归的。</p><h3id="sgd算法的收敛性证明为什么sgd算法是有效的">SGD算法的收敛性证明(为什么SGD算法是有效的)</h3><p><img src="image-12.png" /></p><p>sgd就是用一个采样的值去替代了整个的梯度的期望E,这个梯度的期望可以认为是真实梯度。这个采样的值是梯度真值的近似。</p><p>为什么sgd可以收敛呢?可以用前面讲的rm算法来证明。sgd是优化问题,他最终的目标是求损失函数<spanclass="math inline"><em>J</em>(<em>w</em>)</span>最小,这个最小可以转化成求根问题。也就是梯度(<spanclass="math inline"><em>g</em>(<em>w</em>)</span>)为0的时候去求<spanclass="math inline"><em>g</em>(<em>w</em>)</span>的根。</p><p><img src="image-13.png" /></p><p>他的形式就相当于g在有扰动的情况下,去求解这个算法。这和sgd是一样的。</p><p><img src="image-14.png" /></p><p>由于SGDa是特别的RM算法,它的收敛性服从下面的结论(看图)。第一个条件是<spanclass="math inline"><em>f</em></span>是凸函数,且上下界是向量第二个是收敛性 第三个是采样是独立同分布。</p><p>最后一个问题:有时候在SGD算法里面没有看到随机变量,而是一组数,他写成了和SGD类似的形式,这里面没有随机变量X,也没有期望,就是取平均,这时候还是SGD吗</p><p><img src="image-15.png" /></p><p><img src="image-16.png" /></p><p>答案是是的,我们可以定义一个随机变量<spanclass="math inline"><em>X</em></span>,让其中取每个数的概率是<spanclass="math inline">1/<em>n</em></span>,这样,我们就有了随机变量,求平均的问题也转化成了求期望问题。只是在运行SGD的时候,我们要一批次随机采样数据,这些数据样本可以重复利用。</p><h3 id="bgd-mbgd-和sgd的区别">BGD MBGD 和SGD的区别</h3><p><img src="image-17.png" /></p><p><spanclass="math inline"><em>b</em><em>g</em><em>d</em></span>是用全部的样本来做梯度下降,</p><p><spanclass="math inline"><em>m</em><em>b</em><em>g</em><em>d</em></span>是从中间抽取一个小的子集,进行梯度下降</p><p><spanclass="math inline"><em>s</em><em>g</em><em>d</em></span>是每次只取一个样本,进行梯度下降。</p><p><img src="image-18.png" /></p><p>相对来说,mbgd和bgd它的方差更小,随机性更小。</p><p>如果m=1 mbgd就是sgd</p><p>如果m=n mbgd只采取其中的n个样本,但是bgd使用所有样本。这时候这两者还不一样。</p><p>举例:求w和x的距离最小值。也就是估计x的均值。</p><p><img src="image-19.png" /></p><p>bgd是用所有样本,mbgd是采样一批,sgd是就采一个</p><p><img src="image-20.png" /></p><p><img src="image-21.png" /></p><h1 id="小结">小结:</h1><p>这一章介绍了</p><p>①用增量的方式求平均的方法</p><p>②经典的rm算法:求g(w)=0的根,但是不知道g(w)的表达式,只知道每输入一个w能输出一个结果,可能是带误差的,这时候怎么求解</p><p>③从rm算法衍生的sgd算法:求随机变量的期望,用每次取一个数的方式。</p><p><img src="image-22.png" /></p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(五)蒙特卡洛算法</title>
<link href="/2026/01/21/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%E8%92%99%E7%89%B9%E5%8D%A1%E6%B4%9B%E6%96%B9%E6%B3%95/"/>
<url>/2026/01/21/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%94%EF%BC%89%E8%92%99%E7%89%B9%E5%8D%A1%E6%B4%9B%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<h1 id="强化学习笔记五蒙特卡洛算法">强化学习笔记(五)蒙特卡洛算法</h1><p>第四章描述的值迭代算法和策略迭代算法都是model-based的算法。</p><p>从这一章开始,我们引入蒙特卡洛学习,这是一种不需要模型的算法。也正是对应了强化学习的话:没有模型就得有数据,没有数据就得有模型。</p><p>蒙特卡洛方法来源于数学中的统计方法,比如说抛硬币问题,算抛硬币中正面的概率,用<code>model based</code>的方法就是直接算期望,直接知道<code>p正=0.5</code>,<code>p反=0.5</code>。<code>model free</code>的方法就是估计很多次,重复丢这个硬币很多次,然后从结果中算概率</p><p><img src="image.png" /></p><p>蒙特卡洛定理不仅可以用到强化中,其他需要大量采数据估计期望的方法都可以使用</p><p>强化学习能够用<code>mc</code>方法的原因:<code>v</code>值和<code>q</code>值本质都是期望,既然是期望,对应着就是求平均的策略。</p><p>mc算法是用在策略迭代算法中的,策略迭代只要是以下两步:</p><p><strong>第一步</strong>更新v值:<spanclass="math display"><em>v</em>=<em>r</em> + <em>γ</em><em>P</em><sub><em>π</em></sub><em>v</em></span></p><p><strong>第二步</strong>更新π值:<spanclass="math display"><em>a</em><em>r</em><em>g</em><em>m</em><em>a</em><em>x</em><sub><em>π</em></sub>(<em>r</em><sub><em>π</em></sub> + <em>γ</em><em>P</em><sub><em>π</em></sub><em>v</em><sub><em>π</em></sub>)</span></p><p>但是策略迭代的过程中需要知道两个东西,一个是<code>p(r|s,a)</code>,另一个是<code>p(s'|s,a)</code>,这两个是<code>world model</code>中的内容。</p><p>如果我们想要进行策略迭代,又不知道<code>model</code>,那我们还有另一种方式,就是去估计这个q值。</p><p><img src="image-1.png" /></p><p>其中,核心是<code>q(s,a)</code>要得到动作的价值<code>q</code>,有两种方式,一种是用q值的定义,在有模型的情况下,给出<code>p(r)</code>和<code>p(s)</code></p><p><img src="image-2.png" /></p><p>在没有<code>model</code>的情况下,我们可以根据强化学习对于<code>q</code>值的定义去估计<code>q</code>值:在当前的<code>s,a</code>下用采样的方式,估计平均期望回报和<code>G</code>。这就是对应的<code>model-free RL</code>的过程。</p><p>蒙特卡洛估计动作价值的方法如下:</p><p><strong>第一步</strong>,从<code>(s,a)</code>开始,根据策略<code>πk</code>,生成一个回合</p><p><strong>第二步</strong>,这个回合的回报是<code>g(s,a)</code>,他是<code>Gt</code>的一个采样。</p><p>假如我们有一个g的集合,里面有很多条轨迹,那么 <spanclass="math display"><em>q</em><sub><em>π</em><sub><em>k</em></sub></sub>(<em>s</em>, <em>a</em>) = <em>E</em>[<em>G</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub> = <em>s</em>, <em>A</em><sub><em>t</em></sub> = <em>a</em>]</span>≈ <span class="math display">$$\frac{1}{N}\sum_{i=1}^{N}g^{(i)}(s,a)$$</span></p><p><img src="image-3.png" /></p><p>每次采样一条完整的轨迹g,这个轨迹是<code>G</code>的一个子集,采集到足够多的g之后取均值,就估计了整体的回报的期望。</p><p>蒙特卡洛算法和<code>policy evaluation</code>的算法是非常相似的。</p><p>这是它的伪代码:在每个状态下,采样每个动作,并且采样后续的<code>(s,a)</code>对;然后基于这些轨迹,去估计所有回合下,从当前状态开始的平均回报,这个平均回报就作为q值。</p><p><img src="image-4.png" /></p><p>策略改进的部分和<code>policy iteration</code>是一样的。<code>greedy</code>的方式选取当前q最大的a。</p><p><strong>注意点</strong><code>mc Basic</code>算法是把基于<code>model</code>的模块拿掉,是一种<code>model free</code>的算法,只是实际上,这种方法实际上不会采用,因为它的效率非常低。<code>mc</code>算法直接估计动作的价值,而非状态的价值,这是因为动作价值能够直接用于改进策略,状态价值还得转成动作价值才能改进策略由于策略迭代是保证收敛的,mc算法也是保证收敛的,之后给够充分的回合进行采样</p><p><img src="image-5.png" /></p><p><strong>epislon-greedy算法</strong></p><p><img src="image-6.png" /></p><p><code>mc exploring star</code>算法需要探索的状态动作对太多,为了提高其他动作的概率,可以采用<strong>epislon-greedy</strong>方法进行探索,这种方法的策略是:大概率贪心的策略选动作,小概率随机探索</p><p><img src="image-7.png" /></p><p><code>epislon greedy</code>算法,就是在选动作的时候,把选动作的逻辑从选qmax变成随机探索。</p><p><img src="image-8.png" /></p><p><code>greedy</code>策略,<code>epislon</code>不能开太大,比如<code>epislon=0.1</code>的时候,还能和最优策略保持一致(一致:最优策略和随机最优策略对应的最大概率动作保持一致),取0.20.5的时候,epislon就和原先最优策略不能用了。这个故事告诉我们,如果要是想用<code>epislon greedy</code>策略,<code>epislon</code>不能开太大。</p><p><img src="image-9.png" /></p><hr /><p><strong>为什么MC算法在强化学习训练中难以应用?</strong></p><p>MC方法是一种基于完整回合(episode)的学习方式。也就是说,它必须等到一个episode 完全结束后,才能计算该 episode的回报(return),并用这个回报来更新价值函数或策略。</p><p>❗问题:如果任务具有很长的episode(比如围棋、某些控制任务),或者环境是连续型的(没有明确的终止状态),那么MC 方法就无法有效更新,学习效率极低。</p><p>MC 方法要求任务必须是分幕式(episodic)的,即每个 episode有明确的开始和结束。</p><p>❌ 对于持续性任务(continuingtasks),如机器人持续导航、股票交易等没有明确终止点的任务,MC方法无法直接应用,因为回报无法定义。</p><p>✅ TD 方法可以通过引入折扣因子 γ < 1 来处理持续性任务。</p><p>MC 方法使用实际的回报 <spanclass="math display"><em>G</em><sub><em>t</em></sub></span> </p><p>作为目标,而回报是多个随机动作和状态转移的累积结果,因此:</p><p>📈 方差很大:每次采样得到的回报可能差异很大,导致学习不稳定。</p><p>🐢 需要大量样本:为了获得可靠的估计,MC通常需要更多的训练数据,样本效率低。</p><p>蒙特卡洛方法并不是“无法使用”,而是由于其固有的局限性(如必须等待episode结束、高方差、不适用于持续性任务等),在许多实际强化学习任务中不如 TD方法高效和实用。</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(四)值迭代和策略迭代</title>
<link href="/2026/01/21/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%E5%80%BC%E8%BF%AD%E4%BB%A3%E5%92%8C%E7%AD%96%E7%95%A5%E8%BF%AD%E4%BB%A3/"/>
<url>/2026/01/21/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%E5%80%BC%E8%BF%AD%E4%BB%A3%E5%92%8C%E7%AD%96%E7%95%A5%E8%BF%AD%E4%BB%A3/</url>
<content type="html"><![CDATA[<h1 id="一值迭代算法">一、值迭代算法</h1><p>如何求解贝尔曼等式?在上一章中,已经知道了求解贝尔曼公式核心是求解一个<code>f(v)=v</code>的不动点问题,通过<code>contraction mapping</code>定理可知,可以使用迭代的方式求解。</p><p><img src="image.png" /></p><p>贝尔曼公式中蕴含着π,π和v是绑定的。因此,需要通过两个步骤去求解。</p><p><strong>第一步【优化策略(policyupdate)】</strong>:是在给定vk的情况下求解πk+1</p><p><strong>第二步【优化值(valueupdate)】</strong>:是把πk+1带入值的公式,得到rπk+1和pπk+1(这俩是世界模型决定,和π相关),从而去更新v</p><p><img src="image-1.png" /></p><p>因为现在是<code>vk</code>迭代<code>vk+1</code>的方式得到最后的v,所以还要补充收敛性证明:<code>||vk+1-vk||<delta</code>的时候视为收敛,迭代结束。</p><p>理论上分析常用矩阵形式,但是实际实现上会用元素形式去实现(迭代的方式)</p><p><strong>第一步</strong>是更新策略,目的是求解最优的π,最优的π可以通过贪婪算法得到:在每一步都选取使得<code>q</code>值最大的<code>a</code>,这个动作的概率赋值为1,其他动作的概率赋值为0。这个策略是确定性的策略</p><p><img src="image-2.png" /></p><p><strong>第二步</strong>是值迭代,对于每个s都可以求解出q,把π还可以代进来,对应的q最大的值概率是1,其他的概率是0。</p><p><img src="image-3.png" /></p><p>上面的过程可以写成伪代码:</p><p><img src="image-4.png" /></p><p>首先我们有一个<code>vk</code>,在<code>vk</code>还没收敛的时候,就通过不断的迭代,遍历所有的状态<code>s</code>,遍历所有状态的<code>a</code>,计算所有状态的<code>qk</code>,看哪个<code>action</code>对应的是最大的<code>qk</code>,选这个<code>action</code>。并且根据这种方式去更新<code>value</code>(已知π的情况下就可以知道r,可以迭代。)</p><p>举例说明:在这个案例中,一开始<code>vk</code>是一个随机的策略(图b),然后知道了每个状态的。然后从<code>v</code>得到每个<code>q</code></p><p><img src="image-5.png" /></p><p>假设初始状态,令<code>v0=0</code>,然后得到状态,就可以得到每个policy的权重</p><p>第一步迭代完,已经可以知道<code>s2</code>,<code>s3</code>,<code>s4</code>的策略是最优的,但是s1的策略还没更新。</p><p>然后进行下一步迭代,把当前的<code>v</code>作为最新的<code>v</code>,得到新的值。</p><p><img src="image-6.png" /></p><p>这一步之后,把<code>s1</code>的最优值也更新了。</p><p><img src="image-7.png" /></p><h1 id="二策略迭代算法">二、策略迭代算法</h1><p>算法描述:给定一个<code>π0</code></p><p><strong>第一步</strong>,进行策略估计获得更好的值。</p><p><strong>第二步</strong>,用这个值去更新更新策略。</p><p><img src="image-8.png" /></p><p><img src="image-9.png" /></p><p><img src="image-10.png" /></p><p>和策略迭代相关的问题<strong>问题1</strong>,如何求解:和之前一样的两种方法,求逆和迭代。用迭代的方式去求解这个贝尔曼公式</p><p><strong>问题2</strong>,为什么得到的<code>πk+1</code>一定比<code>πk</code>好?因为<code>πk+1</code>具有更高的值(可以证明<code>vπk+1≥vπk</code>)</p><p><strong>问题3</strong>为什么迭代可以得到最优策略?在迭代过程中,策略会改进,一步又一步收敛到最优的值,这时候模型就可以收敛到最优策略。</p><p><strong>问题4</strong> 策略迭代和值迭代算法有什么关系?</p><p>①证明策略是收敛的需要用到值迭代是收敛的的定理</p><p>②策略迭代和值迭代实际上是两个极端,值迭代是相同的v下找最优策略,策略迭代是在一个给定的策略下找最优的v</p><p><strong>算法的实施</strong></p><p>还是通过迭代的方法</p><p><img src="image-11.png" /></p><p>值迭代和策略迭代的关系:</p><p>策略迭代先从策略开始,先给策略估值,再算价值更新策略</p><p>值迭代先从值开始,更新策略,得到策略,再更新值,再循环</p><p>两个步骤其实就是翻一下</p><p><img src="image-12.png" /></p><p>这张图展示了两个算法的差异:</p><p><img src="image-13.png" /></p><p>最重要的区别就在于在迭代的第四步,值迭代算法用的是<code>v1</code>,策略迭代算法:用的是<code>vπ1</code>,而<code>vπ1</code>又需要在<code>π1</code>下基于v0进行无穷多次迭代才能得到,收敛时才退出。作为对比,值迭代算法就迭代一次,直接得到下一时刻的v。说明值迭代算法只迭代1次,策略迭代算法要迭代很多次,我们由此可以想到,有没有一种迭代方法能在这两者之间进行一个折中的,所以就提出了截断策略迭代算法。</p><p><img src="image-14.png" /></p><h1 id="三截断策略迭代算法">三、截断策略迭代算法:</h1><p>截断策略迭代算法是值迭代算法和策略迭代算法的一般化的推广,值迭代只进行1步迭代,值迭代进行有限步的迭代就是截断策略迭代,如果进行很多步,直到算法收敛(价值准确估计出来),这个就是策略迭代算法。</p><p><strong>算法的实施</strong>一样,还是通过迭代的方法,只是中间迭代一定的步数。</p><p><img src="image-15.png" /></p><p>收敛性上来看,迭代k步,策略迭代算法最先收敛到最优策略,因为他的价值函数每次都要迭代很多次,以达到当前的最优。值迭代最慢,因为他每次只迭代1次策略。截断策略迭代介于二者之间。</p><p><img src="image-16.png" /></p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(三)贝尔曼最优方程</title>
<link href="/2026/01/19/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%9C%80%E4%BC%98%E6%96%B9%E7%A8%8B/"/>
<url>/2026/01/19/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%9C%80%E4%BC%98%E6%96%B9%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="动机">1. 动机</h1><p>动作的价值对我们来说很重要,动作的价值是与当前自己要执行的动作的概率无关的,我们从gridmodel案例中可以得到:<spanclass="math display"><em>q</em><sub><em>π</em></sub>(<em>s</em>, <em>a</em>) = <em>r</em> + <em>γ</em><em>v</em>(<em>s</em><sup>′</sup>)</span>,当前的动作是好与坏可以通过当前的动作的价值Q得到。</p><p>那么很明显,当前最好的动作就是q值最大的动作,也就是<spanclass="math display"><em>a</em><sup>*</sup> = <em>a</em><em>r</em><em>g</em><em>m</em><em>a</em><em>x</em><sub><em>a</em></sub><em>q</em><sub><em>π</em></sub>(<em>s</em>, <em>a</em>)</span>。</p><p>如果强化学习模型没有训练,策略是随机的,那么价值Q就不一定能够合理评估动作的价值,因为<spanclass="math display"><em>q</em><sub><em>π</em></sub>(<em>s</em>, <em>a</em>) = <em>r</em> + <em>γ</em><em>v</em>(<em>s</em><sup>′</sup>)</span>公式中,<code>v(s')</code>不一定能够准确衡量状态的价值,其他状态下模型可能乱做动作,导致估值失准。但是通过迭代优化的方式(<code>iteration loop</code>),能够通过不断学习,准确评估每个状态的价值,进而给每个动作估值,最后能够实现根据贝尔曼最优方程选动作。</p><h1 id="贝尔曼最优公式---定义与问题">2. 贝尔曼最优公式 - 定义与问题</h1><p>如何评估两个价值函数的好或者不好?通过价值函数。</p><p>于是,我们可以定义价值函数的评价标准: <spanclass="math display"><em>v</em><sub><em>π</em>1</sub>(<em>s</em>) > = <em>v</em><sub><em>π</em>2</sub>(<em>s</em>), <em>对</em><em>于</em><em>任</em><em>意</em><em>s</em> ∈ <em>S</em></span>那么就说明策略 <spanclass="math display"><em>π</em>1</span>优于策略<spanclass="math display"><em>π</em>2</span></p><p>如果一个策略<spanclass="math display"><em>π</em><sup>*</sup></span>的所以状态值都比其他策略要好,那么就说这个策略是最优的。</p><hr /><p>存在的问题:</p><ol type="1"><li>最优策略是否存在?</li><li>最优策略是否唯一?</li><li>最优策略是随机的还是确定的?</li><li>最优策略如何得到</li></ol><p>这些是研究贝尔曼最优公式要解决的问题。</p><h2 id="贝尔曼最优公式boe">2.1 贝尔曼最优公式(BOE)</h2><p>根据上述张杰的定义,此处直接给出贝尔曼公式的形式:</p><p><spanclass="math display"><em>v</em>(<em>s</em>) = <em>m</em><em>a</em><em>x</em><sub><em>π</em></sub>∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)[∑<sub><em>r</em></sub>(<em>p</em>(<em>r</em>|<em>s</em>, <em>a</em>)<em>r</em>) + ∑<sub><em>s</em><sup>′</sup></sub>(<em>p</em>(<em>s</em><sup>′</sup>|<em>s</em>, <em>a</em>)<em>v</em>(<em>s</em><sup>′</sup>))]</span></p><p>对于任意<code>s∈S</code> 和贝尔曼公式的最大区别在于公式的最前面多了一个<code>max</code></p><p>需要指出的是,公式里面的<code>p(r|s,a)</code>,<code>p(s'|s,a)</code>是已知的而且是由外部系统决定的(<code>world</code>),<code>v(s)</code>和<code>v(s')</code>是未知的,<code>π(s)</code>实际上也是未知的,需要通过迭代的方式求解,求解的目标是最大化<code>π</code></p><p>贝尔曼最优公式中如何求解是一个问题,我们需要找出一个a,使得整个v值能够最大化。现在的情况是有很多个动作:<code>a1,a2,...a5</code>.,每个都能够算出一个<code>q</code>值,即<code>q1,q2,...q5</code>我们希望求解最优的π,可以知道,用贪心策略选q最大的动作,可以让这个值最大,也就是让最优的q的π为1的时候,整体的<code>v(s)</code>是最优的。</p><p>具体证明是这样说的:我们现在希望最大化<code>c1q1+c2q2+c3q3</code>,其中<code>c1+c2+c3=1</code>且<code>c</code>非负(对应着概率),可以知道,如果有一个<code>q3</code>是比其他<code>q</code>更大的,那么,直接令<code>c3=1,c1=c2=0</code>,就是<code>c1q1+c2q2+c3q3</code>的最优解。其他情况下的结果不会比这个更好。</p><p>这样的思路可以用在求解贝尔曼最优公式之中。</p><p>最后的结果表示,贝尔曼公式的最优结果也就是 <spanclass="math display"><em>m</em><em>a</em><em>x</em><sub><em>π</em></sub>∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)<em>q</em>(<em>s</em>, <em>a</em>) = <em>m</em><em>a</em><em>x</em><sub><em>a</em> ∈ <em>A</em>(<em>s</em>)</sub><em>q</em>(<em>s</em>, <em>a</em>)</span></p><p>在取最优的情况下,应该能够得出: <spanclass="math display"><em>π</em>(<em>a</em>|<em>s</em>) = 1, <em>i</em><em>f</em> : <em>a</em> = <em>a</em><sup>*</sup>, <em>e</em><em>l</em><em>s</em><em>e</em> : 0</span>这里的<spanclass="math display"><em>a</em><sup>*</sup> = <em>a</em><em>r</em><em>g</em><em>m</em><em>a</em><em>x</em><sub><em>a</em></sub><em>q</em>(<em>s</em>, <em>a</em>)</span></p><p>直观的解释,就是最优的动作,给确定的概率1,其他动作都给0</p><h2 id="贝尔曼最优公式的求解">2.2 贝尔曼最优公式的求解</h2><p>为了求解贝尔曼最优公式(本质是<code>v=f(v)</code>求不动点,为了求解,可引入<code>constraction property Theorem</code>,并且证明可以通过迭代的方式获得<code>v=f(v)</code>最优解。这里补充<code>constration mapping</code> 定理,可以用于求解不动点。 <imgsrc="image.png" alt="constraction property Theorem" /></p><p><img src="image-1.png" /></p><figure><img src="image-2.png" alt="分析最优策略" /><figcaption aria-hidden="true">分析最优策略</figcaption></figure><p>满足<code>constraction property</code>的式子(<code>‖f(x2)-f(x1)‖<γ‖x2-x1‖</code>具有以下三个特性:1. 解是唯一的、 2. 解是可以迭代出来的 3.通过迭代的时候速率是指数级的</p><p>满足贝尔曼最优公式的式子的时候,策略Pai就是让这个价值函数最大的策略,这时候把他称作π*从而就有了贝尔曼最优公式: <spanclass="math display"><em>v</em><sup>*</sup>=<em>r</em><sub><em>π</em></sub><sup>*</sup> + <em>γ</em><em>P</em><sub><em>π</em></sub><sup>*</sup><em>v</em><sup>*</sup></span></p><p>也就是贝尔曼最优公式是一个特殊的贝尔曼公式。</p><p>最优策略:是确定性的,而且是贪心的</p><h2 id="贝尔曼公式的参数γ">2.3 贝尔曼公式的参数:γ</h2><p><img src="image-3.png" /></p><p>gamma=0.9的时候,智能体会长远考虑,认为穿过禁止进入区域虽然暂时有害于智能体,但是长远来看是有价值的,因此还是穿过forbiddenarea gamma=0.5的时候,智能体会短视,因此会避开forbidden area绕一个大圈进入最后的终点。</p><p><img src="image-4.png" /></p><p><img src="image-5.png" />gamma降低到0的时候,最优策略变成非常短视,无视未来的奖励。</p><p>最优策略的不变性: <img src="image-6.png" />如果放大奖励的倍数,最后获得的最优策略是不变的</p><p>如果智能体绕路,为什么策略不是最好的?因为折扣率存在,决定了智能体获得的总的回报。所以两个不同情况下,获得的总return是不一样的。</p><h1 id="小结">3.小结:</h1><h2 id="贝尔曼方案最优解的性质">3.1 贝尔曼方案最优解的性质:</h2><p>解一定存在且唯一(根据<code>contraction mapping</code>定理),这个定理是用于求解f(v)=v的方法 算法是使用迭代的过程求解,而且迭代的速度很快为什么我们要学习这个等式? 因为求解它对应最优的状态价值,并且可以得到最优的策略。 <img src="image-7.png"alt="小结" /></p><h2 id="复杂例子">3.2 复杂例子</h2><p>假设进入forbidden区域奖励是-10,进入边界奖励是-1,到达目标奖励是+1。<img src="image-8.png" /></p><p>这个例子是比较复杂的值迭代和策略迭代的求解方式,最开始的时候所有策略都是原地不动,但是随着值迭代和策略迭代,可以看到首先是距离目标近的地方的值和策略被改成最优的,然后是距离目标远的他的策略变成最优。</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>轻松在Windows上配置并运行LLMLight项目</title>
<link href="/2026/01/05/%E5%9C%A8Windows%E4%B8%8A%E5%BF%AB%E9%80%9F%E8%BF%90%E8%A1%8C%E5%B9%B6%E8%B0%83%E8%AF%95LLMLight/"/>
<url>/2026/01/05/%E5%9C%A8Windows%E4%B8%8A%E5%BF%AB%E9%80%9F%E8%BF%90%E8%A1%8C%E5%B9%B6%E8%B0%83%E8%AF%95LLMLight/</url>
<content type="html"><![CDATA[<h1id="轻松在windows上配置并运行llmlight项目">轻松在Windows上配置并运行LLMLight项目</h1><h1 id="introduction">1. Introduction</h1><p>近年来,基于强化学习的信号控制逐步迭代,已经发展出了基于大语言模型的LLMLight。LLMLight是一种将大型语言模型(LLM)作为TSC决策代理的新框架。传统的TSC方法主要基于交通工程和强化学习(RL),在不同交通场景下的泛化能力通常有限,并且缺乏可解释性。该框架首先通过提供包含实时交通状况的知识性提示来指导LLM。利用LLM强大的泛化能力,LLMLight进行类似人类直觉的推理和决策过程,以实现有效的交通控制。此外,该文章构建了LightGPT,一种专门为TSC任务定制的核心LLM。通过学习细微的交通模式和控制策略,LightGPT在成本效益方面增强了LLMLight框架。</p><p>LLMLight项目基于大语言模型进行信号控制,依赖cityflow仿真环境进行训练和测试,而cityflow又必须安装在linux操作系统下,交通工程专业的工程师或从业人员较难在短时间内从0开始配置完成整个项目。为此撰写一篇文章,希望教给读者从0开始在Windows上配置并运行LLMLight项目。</p><h1 id="准备工作">2. 准备工作</h1><p>LLMLight项目依赖Linux环境,过往我们想要工作中使用Windows系统,并且还要临时跑Linux项目还是比较费劲的,需要使用虚拟机或安装双系统,两种方式都有痛点。但随着时代的进步,在Windows上配置并运行Linux项目成为可能。WSL(WindowsSubsystem for Linux,适用于 Linux 的 Windows 子系统)是微软为 Windows系统(如 Windows 10 和 Windows 11)引入的一项功能,允许用户在 Windows上运行 Linux操作系统及其相关命令和应用程序,而无需安装虚拟机或双系统。这就为我们的部署提供了便利。</p><p>系统要求:Windows 11 ,具备良好的网络环境</p><h1 id="系统篇">3. 系统篇</h1><h1 id="安装wsl-和-ubuntu-24.04">3.1 安装wsl 和 ubuntu 24.04</h1><p>安装wsl并在该环境下配置ubuntu 24.04,可以参考这篇文章:<ahref="https://zhuanlan.zhihu.com/p/1903903315080774656">Windows安装和配置Linux子系统(WSL2)</a></p><h1 id="环境篇">4 环境篇</h1><h2 id="安装python3.10环境">4.1 安装python3.10环境</h2><p>Ubuntu默认的python版本是3.12.3,经过实验,在这个版本下安装cityflow将会编译不成功,部分依赖(例如tensorflow-cpu)也会无法安装2.8.0版本。为了保持版本一致,我们首先应该安装python3.10环境,替换掉系统自带的python3.12环境。在这一步,我们需要用到pyenv。</p><p>以下命令在ubuntu命令行中运行,建议直接复制粘贴命令</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment"># 1. 安装 pyenv</span><br>curl https://pyenv.run | bash<br><br><span class="hljs-comment"># 2. 将以下内容添加到 ~/.bashrc</span><br><span class="hljs-built_in">echo</span> <span class="hljs-string">'export PYENV_ROOT="$HOME/.pyenv"'</span> >> ~/.bashrc<br><span class="hljs-built_in">echo</span> <span class="hljs-string">'command -v pyenv >/dev/null || export PATH="$PYENV_ROOT/bin:$PATH"'</span> >> ~/.bashrc<br><span class="hljs-built_in">echo</span> <span class="hljs-string">'eval "$(pyenv init -)"'</span> >> ~/.bashrc<br><span class="hljs-built_in">source</span> ~/.bashrc<br><br><span class="hljs-comment"># 3. 安装编译依赖</span><br><span class="hljs-built_in">sudo</span> apt update<br><span class="hljs-built_in">sudo</span> apt install -y make build-essential libssl-dev zlib1g-dev \<br>libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \<br>libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev<br><span class="hljs-built_in">sudo</span> apt install -y libffi-dev libssl-dev <br><span class="hljs-built_in">sudo</span> apt install -y libssl-dev openssl<br><br><span class="hljs-comment"># 4. 安装python 3.10.2</span><br>pyenv install 3.10.2<br><br></code></pre></td></tr></table></figure><p>到这一步,我们就已经安装好了python3.10.2。需要指出的是,其中第三步安装编译依赖必须在安装python之前完成,特别是<code>openssl</code>、<code>libssl-dev</code>这些依赖,在后期编译cityflow时会使用到。</p><p>安装环境有了,我们接下来给项目创建一个venv环境。这一步可以在pycharm中完成。我使用的是pycharm2023完整版,在windows环境下,打开LLMLight项目根目录,选择<code>左上角的选项→Setting→Python Interpreter → Add Interpreter → on WSL...</code>系统会自动检测WSL环境下安装的python,路径就按照默认给出的路径,这里我默认创建的路径是<code>/root/.virtualenvs/LLMTSCS-main/</code></p><p><img src="image-6.png" /></p><figure><img src="image-5.png" alt="虚拟环境创建完成" /><figcaption aria-hidden="true">虚拟环境创建完成</figcaption></figure><p>虚拟环境创建完成!</p><p>进入venv安装的目录下,默认情况下,创建的venv解释器位于<code>root/.virtualenvs/LLMTSCS-main/bin</code></p><p>输入命令<code>source activate</code>运行venv</p><figure><img src="image-2.png" alt="venv界面命令的最前方有虚拟环境的名称" /><figcaptionaria-hidden="true">venv界面命令的最前方有虚拟环境的名称</figcaption></figure><h2 id="安装项目所需的依赖">4.2 安装项目所需的依赖</h2><h3 id="安装cityflow">4.2.1 安装CityFlow</h3><p>由于cityflow比较难安装,我们首先安装cityflow。</p><p>cityflow有两种方式进行安装:通过Docker安装和通过源码安装,这里我选择通过git安装。</p><p>首先在ubuntu上,安装所需的依赖:</p><p><code>sudo apt update && sudo apt install -y build-essential cmake</code></p><p>随后,在windows本地新建一个文件夹,从github上克隆代码到本地:</p><p><code>git clone https://github.com/cityflow-project/CityFlow.git</code></p><p>在Ubuntu中,进入CityFlow目录,通过pip方式安装:</p><p><code>cd CityFlow && pip install .</code></p><p>需要注意的是,安装依赖的时候,请确定自己是正所处于创建的虚拟环境venv中,而不是系统自带的python环境中。</p><p>当出现这个画面的时候,就意味着cityflow安装成功了! <imgsrc="image-7.png" /></p><h3 id="安装其他所需的依赖">4.2.2 安装其他所需的依赖</h3><p>LLMLight文档中提到,项目的主要依赖有:<code>python>=3.9,tensorflow-cpu==2.8.0, cityflow, pandas==1.5.0, numpy==1.26.2, wandb, transformers==4.45.0, peft==0.7.1, accelerate==0.27.2, datasets==2.16.1, fire, vllm==0.6.2</code></p><p>除了上面所需要的依赖,经过实测,还需要安装依赖:<code>protobuf==3.20.3</code></p><p>如果安装包下载太慢,可以先设置全局镜像源:<code>pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple</code><img src="image-3.png" alt="设置全局镜像源" /></p><h1 id="运行代码">4.3 运行代码</h1><p>经过配置完成,我们就可以运行代码了~</p><p><img src="image-10.png" /></p><p>不仅可以在ubuntu中以命令行的方式运行代码,还可以在pycharm中配置运行的文件以运行代码。<img src="image-11.png" /></p><h1 id="相关问题">5 相关问题</h1><h2 id="安装依赖时报错">5.1 安装依赖时报错</h2><p>在安装<code>pandas==1.5.0</code>依赖的时候,会报错: 解决“Could notbuild wheels for pandas”错误,大模型提供的解决方案有两个: 1.安装必要的编译工具</p><p>在Linux上,你需要安装gcc和make。 sudo apt-get installbuild-essential</p><ol start="2" type="1"><li>或者尝试 pip install pyproject</li></ol><p>但是经过我尝试,两个方案都没有解决问题。问题根源还是在于python的版本,不能太高,3.12就不行,甚至3.11都高了。即使是python3.10,也装不上tensorflow==2.8.0,最后我安装的是TensorFlow==2.9.0</p><h2 id="安装cityflow时报错">5.2 安装CityFlow时报错</h2><p><img src="image-4.png" /></p><p>这类报错大概率是前面的编译依赖环境没装好,建议重装python,并把编译依赖环境装上</p><h2 id="wandb未配置">5.3 wandb未配置</h2><p>运行代码时报错: <img src="image-9.png" /></p><p>问题分析:这个错误表明 wandb(Weights & Biases)没有配置 APIkey。wandb 是一个机器学习实验跟踪工具,需要登录才能使用。解决方案:注册并登录wandb 1. 如果没有 wandb账户,先注册:https://wandb.ai 2. 然后登录 <code>wandb login</code> 3.运行后会提示你输入 API key,你可以在<code>wandb.ai/authorize</code>获取。</p>]]></content>
<categories>
<category>操作系统</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(一)基础概念</title>
<link href="/2026/01/04/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5/"/>
<url>/2026/01/04/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%E5%9F%BA%E7%A1%80%E6%A6%82%E5%BF%B5/</url>
<content type="html"><![CDATA[<h1 id="强化学习笔记一基础概念">强化学习笔记(一)基础概念</h1><p>本笔记是b站的 <ahref="https://www.bilibili.com/video/BV1sd4y167NS">西湖大学强化学习课程</a>的笔记。并附带一些个人的思考。 # 一、基础概念</p><h2 id="强化学习各章节">强化学习各章节</h2><ol type="1"><li>基础概念</li><li>贝尔曼方程</li><li>贝尔曼最优方程</li><li>值迭代&策略迭代</li><li>蒙特卡洛学习</li><li>随机估计</li><li>时序差分学习</li><li>值函数估计</li><li>策略梯度方法</li><li>Actor-Critic 方法</li></ol><h2 id="章节关系">章节关系</h2><p>章节1-3 强化学习的基础工具 章节4-10 强化学习的算法 章节4→章节5model-based 到 model-free 章节5→章节7 非增量学习到增量学习 章节7→章节8表格化的表征到基于函数的表征 章节8→章节9 value-based到policy-based章节9→章节10 policy-based 和value-based结合</p><h1 id="二核心笔记">二、核心笔记</h1><ol type="1"><li>强化学习要做的是:不断进行策略评估(policyevaluation),然后迭代优化策略</li><li>由于第一条,可以知道statevalue的计算是强化学习的核心,可以用以下几种方法计算:基于model、基于MC,以及基于时序差分</li><li>强化学习基于的是状态的概率转移过程,他的核心就是条件概率公式,包括p(r|s,a),p(a|s),p(s’|s,a)本质都是状态转移的概率</li><li>强化学习的一大假设是马尔科夫性,描述的是一个马尔科夫过程。马尔科夫决策过程,这个名词可以用马尔科夫、决策、过程来描述。马尔科夫性就是无后效性</li><li>强化学习要想学好,没数据就得有模型,没模型就得有数据,啥都没有学不好。这里的model指的是worldmodel。没有模型的情况下,对应的model-free方法。</li><li>Off-policy和on-policy对应的是强化学习的两种范式,后面会介绍,不过一句话讲就是off-policy允许模型的targetpolicy和behavior policy不一样</li><li>Return的定义。强化学习里面,return是相比reward更重要的,return的定义是智能体在一个回合过程中获得的奖励或折扣奖励。也就是说return评估的是一个序列的价值(能够获得多少总奖励)</li><li>周期性任务和连续性任务:在强化学习里面,目标是折扣总奖励最大,也就是<spanclass="math display"><em>G</em> = 1 + <em>γ</em><em>r</em><sub>1</sub> + <em>γ</em>²<em>r</em><sub>2</sub> + ...</span>最大。如果任务是有结局的,比如智能体到了终点,在评估折扣总奖励的时候,会把他转换成连续任务去考虑。转换的方式有两种。一种是智能体到达结局后,中断智能体的动作,让他不再移动,奖励一直给0.第二种是智能体可以离开target,然后再回到target,这对应了更一般化的情况。在课程中默认采取的是第二种情况的方式进行讲解</li></ol>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>强化学习笔记(二)贝尔曼方程</title>
<link href="/2026/01/04/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%96%B9%E7%A8%8B/"/>
<url>/2026/01/04/%E5%BC%BA%E5%8C%96%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89%E8%B4%9D%E5%B0%94%E6%9B%BC%E6%96%B9%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="策略评估的方式">策略评估的方式</h1><p>如何不断的改进策略?用什么来评估:答案是基于return进行评估。return是一条控制轨迹能够获得的奖励或折扣奖励的和,能够用来评估当前状态的价值。</p><h1 id="状态的价值">状态的价值</h1><p>举个例子,在Grid机器人里,现在有3条路径: <img src="image.png"alt="强化学习的路径" />路径1:智能体从S1到S3,奖励是0,从S3到S4,奖励是1,呆在S4,一直有奖励1.路径2:智能体从S1到S2,奖励是-1,从S2到S4,奖励是1,呆在S4,一直有奖励1.路径3:智能体有50%概率走S1到S3,从S3到S4;有50%概率走S1到S2到S4。最后呆在S4,一直有奖励1.</p><p>路径1的总回报: <spanclass="math display">0 + <em>γ</em> × 1 + <em>γ</em>² × 1 + <em>γ</em>³ × 1 + ..... = <em>γ</em>/(1 − <em>γ</em>)</span>路径2的总回报: <spanclass="math display"><em>γ</em>/(1 − <em>γ</em>) − 1</span>路径3的总回报: <spanclass="math display">0.5 × [<em>γ</em>/(1 − <em>γ</em>) − 1] + 0.5 × <em>γ</em>/(1 − <em>γ</em>)</span>这里是按照概率进行加权,等价于求期望 这里<spanclass="math inline"><em>γ</em></span>是折扣因子。 可见 <spanclass="math display"><em>r</em><em>e</em><em>t</em><em>u</em><em>r</em><em>n</em><sub>1</sub>><em>r</em><em>e</em><em>t</em><em>u</em><em>r</em><em>n</em><sub>3</sub>><em>r</em><em>e</em><em>t</em><em>u</em><em>r</em><em>n</em><sub>2</sub></span>说明路径1的策略是最优的,路径2的策略是最烂的,然后路径3的策略介于二者中间。这里案例说明了策略的评估的重要性。接下来问题来了,考虑一个循环的格子,如何计算格子里每个状态的return?<img src="image-1.png" alt="如何计算return" /> 根据定义来,定义<spanclass="math inline"><em>v</em><sub>1</sub></span>是从状态<spanclass="math inline"><em>s</em><sub>1</sub></span>出发能够得到的return,从而有:<span class="math display">$$v_1=r_1+γr_2+γ²r_3+...\\v_2=r_2+γr_3+γ²r_4+...\\v_3=r_3+γr_4+γ²r_1+...\\v_4=r_4+γr_1+γ²r_2+...\\$$</span>可以看到,要v1是无穷无尽可以计算下去的,因为在这个例子中状态会出现往复。我们同时会发现上面的式子可以写成下面的形式,这就是启发我们:一个状态和另一个状态之间,是存在互相影响的,可以通过联立不同的状态的价值,把不同状态下对应的价值求解出来,这种特性叫做“自举策略(bootstrap)” <span class="math display">$$v_1=r_1+γv_2\\v_2=r_2+γv_3\\v_3=r_3+γv_4\\v_4=r_4+γv_1\\$$</span></p><p>我们可以把上面的式子转化为矩阵形式表达,也就是下面这张图所要展示的,并且从中抽取出共性的公式:<spanclass="math display"><em>v</em> = <em>r</em> + <em>γ</em><em>P</em><em>v</em></span>这就是bellman公式。其中<spanclass="math inline"><em>P</em></span>是状态转移的概率矩阵。 <imgsrc="image-2.png" alt="贝尔曼状态转移矩阵" />贝尔曼公式的内涵在于告诉我们:一个状态的价值依赖于其他的状态。我们依据贝尔曼方程,可以定义出价值函数,即: <spanclass="math display"><em>V</em><sub><em>π</em></sub>(<em>s</em>) = <em>E</em>[<em>G</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub> = <em>s</em>] = <em>E</em>[<em>R</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub>] + <em>γ</em>∑<em>E</em>[<em>G</em><sub><em>t</em> + 1</sub>|<em>S</em><sub><em>t</em></sub>]</span>这里的<spanclass="math inline"><em>G</em><sub><em>t</em></sub></span>就是前面所说的折扣奖励的和,所以价值函数就表示在当前状态下能够获得的期望折扣回报和。状态函数如何计算呢,我们为了在一般情况下计算,需要将上面的式子展开,并且用全概率公式算期望。</p><p>其中第一项<spanclass="math display"><em>E</em>[<em>R</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub>]</span>是即时奖励的期望,反映了当前状态下,执行各个动作能够获得的即时奖励和,计算方式是:<spanclass="math display"><em>E</em>[<em>R</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub>] = ∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)[∑<sub><em>r</em></sub><em>p</em>(<em>r</em>|<em>s</em>, <em>a</em>)<em>r</em>]</span>第二项<spanclass="math display">∑<em>E</em>[<em>G</em><sub><em>t</em> + 1</sub>|<em>S</em><sub><em>t</em></sub>]</span>是评估当前状态下,后续的折扣回报和。由于马尔科夫性,后续的回报和又由当前的状态下执行一个动作,到下一个状态决定,所以上面的式子就变成了当前状态下,执行动作转移到下一个状态,下一个状态的折扣回报和<spanclass="math inline"><em>G</em><sub><em>t</em> + 1</sub></span>。 <spanclass="math display"><em>E</em>[<em>G</em><sub><em>t</em> + 1</sub>|<em>S</em><sub><em>t</em></sub>] = ∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)[∑<sub><em>s</em><sup>′</sup></sub><em>p</em>(<em>s</em><sup>′</sup>|<em>s</em>, <em>a</em>)<em>V</em><sub><em>π</em></sub>(<em>S</em><sub><em>t</em> + 1</sub>)]</span>上面的式子还可以合并同类项,提出<spanclass="math inline">∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)</span>,也就变成了<spanclass="math display"><em>V</em><sub><em>π</em></sub>(<em>s</em>) = <em>E</em>[<em>G</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub> = <em>s</em>] = ∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)[∑<sub><em>r</em></sub><em>p</em>(<em>r</em>|<em>s</em>, <em>a</em>)<em>r</em>] + ∑<sub><em>s</em><sup>′</sup></sub><em>p</em>(<em>s</em><sup>′</sup>|<em>s</em>, <em>a</em>)<em>V</em><sub><em>π</em></sub>(<em>s</em><sup>′</sup>)]</span>这里,<spanclass="math inline"><em>V</em><sub><em>π</em></sub>(<em>s</em>)</span>和<spanclass="math inline"><em>V</em><sub><em>π</em></sub>(<em>s</em><sup>′</sup>)</span>都是状态的价值,求解方式就和上面的方式一样,用“自举策略”得到。<spanclass="math inline"><em>π</em>(<em>a</em>|<em>s</em>)</span>指的是策略网络,通过策略的评估来求解<spanclass="math inline"><em>p</em>(<em>r</em>|<em>s</em>, <em>a</em>), <em>p</em>(<em>s</em><sup>′</sup>|<em>s</em>, <em>a</em>)</span>都是状态转移概率,这些应该由动态模型(model)给出,也就是model-based,如果没有这样的model,就通过model-free的方式探索得到。</p><p>为什么要求解statevalue:给定一个策略,求解这个策略对应的状态价值,这个过程叫做策略评估(policyevaluation)贝尔曼公式的向量形式,由上面的公式可知,贝尔曼公式的一般向量形式可以写成:<spanclass="math display"><em>v</em><sub><em>π</em></sub> = <em>r</em><sub><em>π</em></sub> + <em>γ</em><em>P</em><sub><em>π</em></sub><em>v</em><sub><em>π</em></sub></span>这里的核心是用r和P来分别代表奖励模型和状态转移概率模型。其中v和r是向量,即<spanclass="math inline"><em>v</em><sub><em>π</em></sub> = [<em>v</em><sub>1</sub>,<em>v</em><sub>2</sub>, <em>v</em><sub>3</sub>, ..., <em>v</em><sub><em>n</em></sub>]</span>都是向量化的表达,P是矩阵,用来描述每个状态转移的概率。求解上述式子,最直观的方式是求逆,即:</p><p><spanclass="math display"><em>v</em><sub><em>π</em></sub> = (<em>I</em> − <em>γ</em><em>P</em><sub><em>π</em></sub>)<sup>−1</sup><em>r</em><sub><em>π</em></sub></span></p><p>但是这种方式,要求矩阵的逆,效率非常低。。 <img src="image-3.png"alt="求逆的方式获得贝尔曼方程的解" />解决的方式是用迭代的方法避免矩阵求逆,也就是让vk先从V0开始,迭代得到V1,或者其他的状态,然后再举一反三得到其他状态,最终由策略迭代的方式,使得vk收敛到vπ</p><p>动作的价值动作的价值指的是智能体在某个状态下,执行某个动作的价值,这和智能体的策略密切相关。动作的价值定义如下</p><p><spanclass="math display"><em>q</em><sub><em>π</em></sub>(<em>s</em>, <em>a</em>) = <em>E</em>[<em>G</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub> = <em>s</em>, <em>A</em><sub><em>t</em></sub> = <em>a</em>]</span></p><p>由全概率公式,我们可以得知:</p><p><spanclass="math display"><em>v</em><sub><em>π</em></sub>(<em>s</em>) = ∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)<em>E</em>[<em>G</em><sub><em>t</em></sub>|<em>S</em><sub><em>t</em></sub> = <em>s</em>, <em>A</em><sub><em>t</em></sub> = <em>a</em>]</span></p><p>从而可以得到v和q值之间的关系,也就是全概率公式表示的一个公式。</p><p><spanclass="math display"><em>v</em><sub><em>π</em></sub>(<em>s</em>) = ∑<sub><em>a</em></sub><em>π</em>(<em>a</em>|<em>s</em>)<em>q</em><sub><em>π</em></sub>(<em>s</em>, <em>a</em>)</span></p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>Python导包失败原因及解决方法</title>
<link href="/2025/11/03/Python%E5%AF%BC%E5%8C%85%E5%A4%B1%E8%B4%A5%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/"/>
<url>/2025/11/03/Python%E5%AF%BC%E5%8C%85%E5%A4%B1%E8%B4%A5%E5%8E%9F%E5%9B%A0%E5%8F%8A%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/</url>
<content type="html"><![CDATA[<h1 id="问题描述">问题描述</h1><p>Python在调试的时候提示:<code>ImportError: attempted relative import with no known parent package</code></p><h1 id="分析与解决方案">分析与解决方案</h1><p>这种情况一般是使用了相对路径来引入包,例如</p><p><code>From .stdmae_arch import MaskedAutoencoderViT</code></p><p>修改方案:①使用完整路径来引入包。这里的完整路径指的是从项目的根目录开始的子文件夹目录。例如目录结构是:</p><p>Basicts → stdmae →stdmae_arch xxx.py (里面包含要导入的类)</p><p>那么绝对路径就是<code>from stdmae.stdmae_arch import MaskedAutoencoderViT</code>②在运行文件的编辑配置中,配置文件的工作目录为整个项目的根目录。然后再执行<code>run</code>或<code>debug</code>操作。上述两个步骤建议都完成,以避免导包失败。</p>]]></content>
<categories>
<category>杂谈</category>
</categories>
</entry>
<entry>
<title>Linux常用命令及操作备忘</title>
<link href="/2025/07/28/Linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%8F%8A%E6%93%8D%E4%BD%9C%E5%A4%87%E5%BF%98/"/>
<url>/2025/07/28/Linux%E5%B8%B8%E7%94%A8%E5%91%BD%E4%BB%A4%E5%8F%8A%E6%93%8D%E4%BD%9C%E5%A4%87%E5%BF%98/</url>
<content type="html"><![CDATA[<h1 id="linux常用命令及操作备忘">Linux常用命令及操作备忘</h1><h2 id="脚本书写方法">脚本书写方法</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-meta">#!/bin/bash</span><br><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"current working dir <span class="hljs-subst">$(pwd)</span>"</span><br><br><span class="hljs-built_in">cd</span> <span class="hljs-string">"/data1/project-main"</span><br><br><span class="hljs-built_in">echo</span> <span class="hljs-string">"current working dir <span class="hljs-subst">$(pwd)</span>"</span><br><br>python <span class="hljs-string">"script/run.py"</span> --cfg=<span class="hljs-string">'script/aaa.py'</span> --gpus=<span class="hljs-string">'0'</span><br><br></code></pre></td></tr></table></figure><p><code>#!/bin/bash</code> —— 用于指定默认情况下运行指定脚本的解释器<code>echo</code>——在屏幕上输出 <code>Pwd</code> ——当前工作目录<code>cd</code>- 进入路径,从/开始表示绝对路径,不带/开头表示相对路径<code>python</code>——具体执行代码,需配置好环境需要注意的是,<code>windows</code>环境下的文本编辑器会默认在换行时添加<code>\r\n</code>,导致上述代码在<code>linux</code>环境下执行时,会提示类似:<code>cd \r 没有那个文件或目录</code>这样的报错。解决方案是在linux环境下编辑脚本(用vscode连接到服务器,或者用vim等编辑器)脚本熟悉完成后,保存为<code>.sh</code>文件,在终端执行(如果没有权限可用chmod命令加权限)。 例如,在终端键入 <figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">chmod +x helloworld.sh<br>/helloworld.sh<br></code></pre></td></tr></table></figure>给文件加权限并运行<code>helloworld.sh</code>文件。</p><h2 id="其他常见操作">其他常见操作</h2><ol type="1"><li><p>给脚本加权限 <code>chmod -x abc.sh</code></p></li><li><p>查看文件列表 <code>ls</code> 、<code>ls -l</code></p></li><li><p>查看显存占用情况 <code>nvidia-smi</code></p></li><li><p>查看cuda版本: <code>nvcc -V</code></p></li><li><p>查看进程运行情况 <code>top</code>,按M可以给进程占用排序</p></li><li><p>压缩文件的命令:<code>tar -czvf archive.tar.gz directory</code></p><p>-c 或 –create 表示创建新的备份文件。</p><p>-z 或 –gzip 表示通过gzip指令处理备份文件。</p><p>-v 或 –verbose 表示显示指令执行过程。</p><p>-f 表示指定备份文件。</p></li><li><p>解压文件的命令<code>tar -xzvf archive.tar.gz -C /home/user/</code>这个命令会将名为archive.tar.gz的压缩文件解压到指定的/home/user/目录中。</p></li><li><p>也可以用<code>unzip archive.zip -d /path/to/directory</code>命令解压</p></li></ol>]]></content>
<categories>
<category>杂谈</category>
</categories>
</entry>
<entry>
<title>TransformerLight - 学习笔记</title>
<link href="/2025/05/28/TransformerLight%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2025/05/28/TransformerLight%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="transformerlight---学习笔记">TransformerLight - 学习笔记</h1><h2 id="文章摘要">1. 文章摘要</h2><p>交通信号控制 (TSC)仍然是交通领域最重要和最具挑战性的研究问题之一。在线强化学习 (RL) 在TSC中取得了巨大成功,但由于过多的试错学习过程,在实际应用中的学习成本非常高。离线RL是一种很有前途的降低学习成本的方法,然而,离线强化学习主要面对数据分布偏移问题,这些问题仍然悬而未决。为此,在本文中,我们将交通控制表述为一个序列建模问题,其中包含由交通环境中的<strong>状态、动作和奖励</strong>描述的马尔可夫决策过程序列。从而引入了一种新的框架,即TransformerLight,它的目的不是通过平均所有可能的回报来拟合值函数(过往的模型常用方法,神经网络用于拟合RL中的V值或Q值),而是使用门控Transformer 产生最佳动作。此外,TransformerLight的学习过程通过用动态的引起的门控Transformer(GatedTransformer)块替换残差连接而更加稳定。通过对离线数据集的数值实验,证明了TransformerLight模型(1)无需动态规划即可构建高性能自适应的信号控制模型(2)与BCQ、CQL等离线强化学习模型相比展现了更好的性能(3)相比传统的离线强化学习,性能更加稳定。</p><p>论文:https://dl.acm.org/doi/10.1145/3580305.3599530</p><p>代码:https://github.com/Smart-Trafficlab/TransformerLight</p><h2 id="模型结构">2. 模型结构</h2><figure><img src="无标题.png" alt="模型结构" /><figcaption aria-hidden="true">模型结构</figcaption></figure><ol type="1"><li>模型状态动作奖励的定义由于和DT一样都是离线强化学习模型,这里输入都是(s,a,r)一样的轨迹,区别在于输入的状态变成了由多个特征组成的交通状态。这些状态的特征包括:车辆数(NV), 排队长度 (QL), 有效流向压力 (一般就是进出口道的车辆数相减EP),行驶中的车辆数 (ERV), 交通流向压力 (TMP), 以及更多指标.同时,奖励𝑟𝑡包括了排队长度、交通流向压力和平均行程时间(ATT)三个指标。动作则是在给定的相位集中{ABCD}中选择一个相位。需要指出的是原始的DecisionTransformer模型中使用的是return-to-go,也就是未来剩余的回报,作为奖励的指引。但是在交通信号控制任务上,奖励是无穷无尽的,奖励的获取不会像玩游戏一样具有终止条件。基于此,文章将模型输入的奖励改为直接优化算法的指标,也就是最小化平均行程时间。</li></ol><figure><img src="无标题-1.png" alt="优化目标" /><figcaption aria-hidden="true">优化目标</figcaption></figure><ol start="2" type="1"><li>网络结构</li></ol><p>TransformerLight的模型网络结构如图所示,可以看到和DecisionTransformer的模型结构是非常相似的,其唯一的区别在于将DT中的Transformer模型替换成了门控Transformer模块(GatedTransformer Module)。</p><figure><img src="无标题-2.png" alt="网络结构" /><figcaption aria-hidden="true">网络结构</figcaption></figure><p>文章对于Transformer模块的改进如图所示</p><figure><img src="无标题-3.png" alt="改进部分" /><figcaption aria-hidden="true">改进部分</figcaption></figure><p>将原Transformer中的add操作 替换成了gated操作。其中,多头注意力层替换后公式是:</p><figure><img src="无标题-4.png" alt="门控多头注意力公式" /><figcaption aria-hidden="true">门控多头注意力公式</figcaption></figure><p>X是输入的embedding,根据状态、动作、奖励token序列进行嵌入和位置编码后的矩阵FFN层替换后的公式是:</p><figure><img src="无标题-5.png" alt="门控FFN层公式" /><figcaption aria-hidden="true">门控FFN层公式</figcaption></figure><p>这里对应的是模型中的FFN层,把多头注意力的输出隐向量Z经过FFN,再来和原来的张量进行门控运算。</p><p>门控运算可以用下面的公式表示,这个公式是一种门控融合函数,常见于神经网络中用于动态融合两种信息(如向量、特征等)。核心思想是通过门控机制自适应调节两个输入的权重,从而实现控制信息的保留与融合。</p><figure><img src="无标题-6.png" alt="门控运算" /><figcaption aria-hidden="true">门控运算</figcaption></figure><p>其中o和r表示两个待融合的输入向量,σ通常是sigmoid函数(输出范围是0,1),用于生成门信号。通过σ的值,可以决定两个信号o和r之间的信息保留程度。</p><ol start="3" type="1"><li>伪代码模型伪代码如下图所示,伪代码用红框标记的两行的位置就是代码改进的地方,其实也就是把原来应该X+attn(X)的地方替换成了门控的计算方式。</li></ol><figure><img src="无标题-7.png" alt="伪代码" /><figcaption aria-hidden="true">伪代码</figcaption></figure><p>文章指出,这么做的好处是: (1) 改进的梯度流向:我们的 GT中的门控机制可在反向传播期间实现更好的梯度流。 (2)增强的表现力:门控机制为 GT提供了额外的表现力,使其能够学习更丰富、更复杂的表示。 (3)更好的噪声处理:我们的 GT可以通过选择性地抑制不相关的信息来更好地处理嘈杂的输入数据</p><p>文章还指出,使用序列决策对信号控制算法进行建模的方式,好处在于:(1) 获得更好的决策结果:序列建模模型已经证明它们能够做出可能与当前离线RL 方法相匹配或优于的决策。我们的 TransformerLight 还可以在 TSC 中实现SOTA 结果。 (2) 为了稳定的训练过程:研究表明,在高方差 RL设置中,transformers 的训练更稳定。此外,大量成熟的研究对 transformer模型的稳定训练技术进行了研究。 (3) 避免 OOD 问题:离线 RL 的 OOD问题仍然悬而未决,而 TransformerLight避免了目标的正则化或保守性需求,因为没有必要使用复杂的目标函数进行优化。</p><h1 id="实验部分">3.实验部分</h1><p>对比模型: BC(Behavior Cloning (BC): We employ Behavior Cloning (BC)to provide the performance of a pure imitative method.)纯模仿学习的方法Offline RL Methods: We apply the latest offline RL method CQL [16],TD3+BC [8] , and BEAR [15] for comparison. Transformer-based Methods: Weuse Decision Transformer [4] , Trajectory Transformer [14], andAlgorithm Distillation [17] to train on our historical trajectorydataset for final results of ATT.</p><p>实验评测的数据集:济南3×4路网、杭州4×4路网,纽约路网,对比在线和离线等多个模型。</p><figure><img src="无标题-8.png" alt="对比实验" /><figcaption aria-hidden="true">对比实验</figcaption></figure>]]></content>
<categories>
<category>信号控制</category>
</categories>
</entry>
<entry>
<title>一种简单且实用的单点自适应信号控制模型:OPAC</title>
<link href="/2025/05/28/%E4%B8%80%E7%A7%8D%E7%AE%80%E5%8D%95%E4%B8%94%E5%AE%9E%E7%94%A8%E7%9A%84%E4%BF%A1%E5%8F%B7%E6%8E%A7%E5%88%B6%E6%A8%A1%E5%9E%8B%E2%80%94%E2%80%94OPAC/"/>
<url>/2025/05/28/%E4%B8%80%E7%A7%8D%E7%AE%80%E5%8D%95%E4%B8%94%E5%AE%9E%E7%94%A8%E7%9A%84%E4%BF%A1%E5%8F%B7%E6%8E%A7%E5%88%B6%E6%A8%A1%E5%9E%8B%E2%80%94%E2%80%94OPAC/</url>
<content type="html"><![CDATA[<h1id="一种简单且实用的单点自适应信号控制模型opac">一种简单且实用的单点自适应信号控制模型:OPAC</h1>]]></content>
<categories>
<category>信号控制</category>
</categories>
</entry>
<entry>
<title>Decision Transformer - 学习笔记</title>
<link href="/2025/05/28/Decision%20Transformer%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2025/05/28/Decision%20Transformer%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="decision-transformer-学习笔记">Decision Transformer学习笔记</h1><h2 id="文章摘要">1. 文章摘要</h2><p>Transformer模型是一种seq2seq的模型,它的独特之处在于,给定一个输入序列,由模型自己决定输出序列的内容与长度。最开始很自然地应用在NLP类似的序列问题的解决上,不过很多问题都可以建模为seq2seq问题,从而这个模型也可以解决许许多多不同的问题,如:语音识别,词性标注,图像物体识别等。该文章将Transformer尝试使用在强化学习领域,提出一种将强化学习建模为序列决策任务的框架,称为DecisionTranformer 。模型的思想很简单:基于期望回报(Return ToGo)、历史状态以及历史动作输出下一刻的最优动作。与传统的拟合值函数或者计算策略梯度的强化学习方法不同,DecisionTranformer是一种“离线强化学习(Offline ReinforcementLearning)”模型,更具体的说就是在模型训练的过程中<strong>完全不与真实环境进行交互</strong>,通过监督学习的方式,从历史样本中学习专家经验。</p><p>模型通过利用casually maskedTransformer来输出最优动作。通过自回归(autoregressive意思就是把自己当前和之前的所有输出作为下一次的输入,迭代产生一个输出序列)的方式运行,历史序列+当前的序列不断运行,让本文的Transformer模型可以生成相应的动作去达成期望回报。</p><p>论文:https://arxiv.org/abs/2106.01345</p><p>代码:https://github.com/kzl/decision-transformer</p><h2 id="模型结构">2. 模型结构</h2><h3 id="整体模型结构">2.1 整体模型结构</h3><figure><img src="image.png" alt="Decision Transformer 模型结构" /><figcaption aria-hidden="true">Decision Transformer模型结构</figcaption></figure><p>DT的模型结构如图所示,状态、动作、回报被投入各自对应的embedding中,并进行位置编码。这里位置编码采用的是时间戳编码。R,S,A组成的token被送入GPT结构中,以自回归的方式,结合因果掩码(causalmask)预测下一个时刻的动作。</p><h3 id="模型分类">2.2 模型分类</h3><p>由于dt中没有model(model的作用是用于预测未来的状态),故模型属于<strong>model-free</strong>模型。由于dt是采用离线数据进行训练,训练过程中不与真实环境进行交互,故模型属于<strong>offlineRL</strong>模型。</p><h3 id="回报设计">2.3 回报设计</h3><p>与传统的强化学习不同,文章希望transformer从历史序列中学习到动作的信息,并用于<strong>预测未来</strong>的动作。然而,对奖励函数进行建模又是不现实的,文章因此使用了reward-to-go(RTG)作为轨迹在训练过程中的reward,而非reward的原始值。</p><p>在测试的时候,只需要给定一个<strong>期望的奖励</strong>,以及初始状态即可。在实际环境中运行,得到实际奖励之后,就将期望奖励减去这个实际奖励,再迭代送入input。</p><h2 id="模型伪代码">3. 模型伪代码</h2><figure><img src="image-1.png" alt="伪代码" /><figcaption aria-hidden="true">伪代码</figcaption></figure><p>模型伪代码如图所示,基本上和上图结构一致。首先R SA送入各自的embedding,然后进行stack操作(类似concat,可以理解把三张表按照合并列的方式拼接)。随后送入transformer模型中,得到隐状态(hidden_state),并根据隐状态进行动作选择,最终得到预测动作并执行。</p><p>在评价回合执行动作后,获取剩余奖励,减去RTG,和SA拼接成token送入模型继续预测下一个动作。</p><h2 id="实验部分">4. 实验部分</h2><p>由于DT的思路就是学习(s,a,r)的轨迹,很自然的想到,这和模仿学习非常相似,区别就在于dt还多加了一个rtg。1. 模型和行为模仿之间的比较 DecisionTransformer跟最好的%BC表现相当,表明在训练了整个数据集之后,它可以在特定的子集上选择更优的行为。这里测评的都是各种游戏,目标是获得更高的奖励分数。对比的是使用多少百分比轨迹训练的BC。<img src="1280X1280.PNG"alt="文章指出DT取得了与使用更多轨迹BC相似的结果" /></p><ol start="2" type="1"><li>使用更长上下文的好处DT是以序列的方式进行输入,我们很容易想到,序列里只放一个元素也可以进行预测,所以文章还测试了序列中序列上下文长度对预测效果的影响。结论是采用更长的序列进行预测可以获得更好的奖励。</li></ol><figure><img src="1280X1280-1.PNG" alt="长上下文评测结果" /><figcaption aria-hidden="true">长上下文评测结果</figcaption></figure><p>其他实验目的是证明当中的回报有效,就不再仔细展开了。</p><h2 id="参考文献">5. 参考文献</h2><p>https://zhuanlan.zhihu.com/p/501117104</p>]]></content>
<categories>
<category>强化学习</category>
</categories>
</entry>
<entry>
<title>STD-MAE - 学习笔记</title>
<link href="/2025/05/22/STD-MAE%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2025/05/22/STD-MAE%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="std-mae---学习笔记">STD-MAE - 学习笔记</h1><h2 id="文章摘要">1. 文章摘要</h2><p>与典型的多变量时间序列相比,时空数据和时序数据的关键区别在于时空异质性。简单来说就是不同地点(市中心和郊区)和日期(比如工作日和周末)的时间序列规律可能有所不同,但他们在相似环境中表现出一致且可预测的模式。因此,准确预测时空数据的关键在于有效捕捉这种时空异质性。之前的研究者们提出了许多关于时空预测的尝试,比如把图卷积网络(GCN)嵌入到时序卷积网络(TCN)或递归神经网络(RNN)中,或者沿着时空轴应用Transformer架构模型。然而这些模型在区分时空异质性方面存在困难。如何学习清晰的时空异质性仍然是时空预测的主要挑战。基于此,Gao等人提出一种新的时空解耦掩码预训练框架(STD-MAE)。它为学习清晰且完整的时空异质性提供高效且有效解决方案,且预训练与下游任务解耦,使得学习到的时空异质性可以无缝集成到下游任务中。</p><figure><img src="1280X1280.PNG" alt="时空异质性" /><figcaption aria-hidden="true">时空异质性</figcaption></figure><p>文章举例说明:不同检测器在时空规律上相差很大,导致同一时间点,不同流量检测的时间序列相近的情况下,未来流量的变化趋势差距可能非常大,导致预测不准。文章把这种现象称为“时空幻觉”。</p><p>论文原文:https://arxiv.org/abs/2312.00516</p><p>代码地址:<ahref="https://github.com/Jimmy-7664/STD-MAE">https://github.com/Jimmy-7664/STD-MAE</a></p><h2 id="模型结构">2. 模型结构</h2><figure><img src="1280X1280-1.PNG" alt="模型结构" /><figcaption aria-hidden="true">模型结构</figcaption></figure><p>时空解耦掩码自编码器由时间自编码器(T-MAE)和空间自编码器(S-MAE)组成,他们具有相似的结构。S-MAE在空间维度上应用自注意力机制,而T-MAE则在时间维度上应用自注意力机制。从模型结构上看,时间Encoder和空间Encoder的作用是分别对时空状态进行表征。所以整个模型的训练分成两阶段:预训练阶段和下游任务的训练阶段。## 2.1 预训练阶段</p><p>在预训练阶段以以掩码重构的方式进行预训练,此时模型的结构是Encoder-Decoder,采用自监督的方式进行训练:掩码一定比例的序列并尝试还原。预训练结束后,只使用Encoder即可表征时空。Decoder不再使用。预训练阶段,首先对预测的输入进行分块,采用分块的方式进行输入。每条长序列分割成Tp个Patch。其中</p><p><img src="1280X1280-1-1.PNG" /></p><p>每个patch包含L条数据,每个数据里面又有C个channel,所以时空Encoder的输入尺寸是</p><p><img src="1280X1280-2.PNG" /></p><p>随后,输入Xp经过全连接层,进行维度嵌入,嵌入后维度变成</p><p><img src="1280X1280-1-2.PNG" /></p><p>随后对于嵌入后的矩阵,进行二维时空位置编码。传统的Transformer是位置编码,是一维的编码,本文面向有时空两个维度,因此要进行时空二维编码,掩码的生成方式依然是使用正弦和余弦函数进行修改。</p><figure><img src="1280X1280-3.PNG" alt="时空二维编码" /><figcaption aria-hidden="true">时空二维编码</figcaption></figure><p>获得位置编码矩阵Epos后,和嵌入矩阵Ep相加,得到最终输入嵌入E:</p><p><img src="1280X1280-1-3.PNG" /></p><p>输入嵌入E随后分别进行时空掩码,获得可见的掩码嵌入。其中,时间维度上是对时间进行掩码,假设掩码比例是r,那么时间掩码(T-MAE)后的张量尺寸就是Tp(1-r)×N×D,空间掩码(S-MAE)后的张量尺寸就是Tp×N(1-r)×D。文章对掩码的比例r进行了敏感性分析,虽然在图像上,更高的掩码比例可以获得更好的效果,但是在时空领域,25%的掩码率效果是最好的。</p><figure><img src="1280X1280-4.PNG" alt="掩码超参实验" /><figcaption aria-hidden="true">掩码超参实验</figcaption></figure><p>掩码后的矩阵随后输入到Transformer Layers,输出的隐向量即为时序和空间的状态表达。但是只得到状态表达空间是没法训练的,还需要通过下游decoder恢复原始的序列。那么整个的过程和Encoder是相反的:首先,对于掩码掉的部分进行恢复,填充Padding,填充后对填充的部分进行位置编码,随后输出被掩码掉部分的序列,尝试对掩码的序列进行还原。并使用还原后掩码部分的序列与原序列掩码的部分进行Loss计算,可表示为:</p><figure><img src="无标题.png" alt="损失函数" /><figcaption aria-hidden="true">损失函数</figcaption></figure><h2 id="下游任务训练阶段">2.2 下游任务训练阶段</h2><p>STD-MAE可以无缝与现有的预测框架整合。该操作是添加时空状态表达,把STD-MAE训练好的Representation直接添加到预测器的隐藏状态层中。</p><p>比如,预测期经过某层,可以得到包含隐信息的张量Hf,这里Hf相当于是经过下游预测器的表征层之后的输出。</p><p><img src="无标题-1.png" /></p><p>随后,对下游任务的表征层应用ST-Encoder,具体应用方法很简单:把得到的ST-Encoder在时间维度上截取最后的一个时间戳,形成N×T’×D的张量,T’=1,然后经过全连接层和原状态表征直接加起来,得到新的状态表征,即:</p><p><img src="无标题-2.png" /></p><p>对应模型结构图中彩色方块。最终,经过FC层,输出最终的预测结果Y。</p><p>文章指出,下游任务可以是接标准预测任务的PipeLine,可以等效替换任意的模型。文章害测试了DCRNN、MTGNN、STID、STAE、GWNet作为下游的预测期,没改下游的代码,发现GWNet是效果最好的。这里有一个细节,就是时空编码器和原始状态表征接入的位置,看起来是接入在模型的中间层,偏后的位置,相当于原模型也对数据进行了一部分表征。</p><h1 id="实验部分">3 实验部分</h1><p>模型在多个数据集上和多个历史模型进行了测试,评测指标MAE、RMSE、MAPE,用过去的12个时间步预测未来的12个时间步,取得了SOTA的效果。</p><p><img src="无标题-1-1.png" /></p><p>MASK的消融实验部分,对比了只使用时间维度Mask、空间维度Mask,时空融合MAE,以及不用MASK,最终结果显示是时空解耦的方式的MAE效果最好</p><figure><img src="无标题-3.png" alt="消融实验" /><figcaption aria-hidden="true">消融实验</figcaption></figure><p>对于不同的预测器,只要是用了STD-MAE进行增强,模型的指标性能都有所提升,GWNet增强效果最好。</p><figure><img src="无标题-1-2.png" alt="不同预测器的增强结果" /><figcaption aria-hidden="true">不同预测器的增强结果</figcaption></figure>]]></content>
<categories>
<category>时空图</category>
</categories>
</entry>
<entry>
<title>Graph WaveNet - 学习笔记</title>
<link href="/2025/05/06/Graph%20WaveNet%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2025/05/06/Graph%20WaveNet%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="graph-wavenet---学习笔记">Graph WaveNet - 学习笔记</h1><h2 id="文章摘要">1. 文章摘要</h2><p>时空图建模是分析时间关系和空间关系的重要任务,过去的方法大多数都是从固定的图结构上提取空间依赖性,假设实体之间的基本关系是预先确定的。但是,显式的图结构不一定反应真实的以来关系,而且由于数据中连接不完整,可能缺少真正的关系。RNN和CNN又有各自的缺陷,无法捕捉长时间的序列,因此,该文章提出一种新的图神经网络架构GraphWaveNet用于时空图建模,通过开发一种新的自适应依赖关系矩阵,并通过节点嵌入的方式来进行学习。该模型可以捕捉数据中隐藏的空间以来关系,借助堆叠的膨胀1-D卷积,能够随着层数增加感受到宽广的感受野,从而处理非常长的序列。该论文主要用于解决时空建模问题上图结构不确定性问题,通过自适应的可学习的邻接矩阵从数据中自动学习图结构,该论文是基于wavenet网络改进的。论文主要思路:时空图建模背后的一个基本假设是:一个节点的未来信息取决于它的历史信息以及它邻居节点的历史信息。但是这种模型存在两个主要的缺点:- 显式的图结构不能充分的反应真实的依赖关系(空间): -连接不需要两个节点之间的相互依赖关系 -两个节点之间的相互依赖关系存在但连接缺失</p><ul><li>时空图不能有效地学习时间依赖关系(时间):<ul><li>基于RNN的方法在捕获长序列时存在耗时的迭代传播和梯度爆炸/消失现象;</li><li>基于CNN的方法具有并行计算、稳定梯度和低内存需求等优点。然而,由于采用标准的一维卷积,其感受野大小随隐藏层数的增加而线性增长,因此需要使用许多层才能捕获很长的序列。</li></ul></li></ul><p>论文原文:https://arxiv.org/pdf/1906.00121.pdf</p><p>代码地址:github.com/nnzhan/Graph-WaveNet</p><h2 id="问题定义">2. 问题定义</h2><p>交通预测问题,可以认为是给定一张图</p><figure><img src="image.png" alt="图结构" /><figcaption aria-hidden="true">图结构</figcaption></figure><p>其中V是节点,E是边,交通预测问题可以描述为: <img src="image-1.png"alt="交通预测问题" /> 式中: <img src="image-2.png" />分别表示X个特征在过去T个时刻的值(流量变化情况),这里N是节点数,D是数据维数,T是时间步,简单来说就是用过去的S步预测未来的T步。## 3. 空间卷积 GCN时代: <img src="image-3.png" alt="GCN公式" />其中要求邻接矩阵A已知,实际上很多情况下,A可能是变化的,或者存在未能被挖掘到的节点,对当前节点存在影响。文章不用传统的GCN,而是用了扩散的卷积层,形式如下:<img src="image-4.png" alt="扩散图卷积公式" />式中:Pk代表是转移矩阵的k次乘方,K的次数是可以改变的,X是原来的特征,对于无向图,P=A/rowsum(A),对于有向图,区分正反向,正向是Pf=A/rowsum(A),反向是Pb=At/rowsum(A_T) 从而扩散图卷积层可以写成式4的形式。</p><h2 id="自适应邻接矩阵">4. 自适应邻接矩阵</h2><p>文章同时提出一种自适应邻接矩阵的概念,这种矩阵不需要任何先验知识,而且是可以从端到端的方式进行梯度下降训练。可以表示成:<img src="image-5.png" alt="自适应邻接矩阵定义" /> 这里面,E1E2是两个可学习的Embedding矩阵,案例来说应该就是原始输入X乘了一个Embedding矩阵之后得到的。这个公式的形式,和自注意力的计算公式不能说一模一样只能说完全一致<img src="image-6.png" alt="自注意力的计算公式" />(自注意力的计算公式,Q Kt分别对应这里的E1E2)区别在于文章这里加了个Relu(这是GAT的做法)只关注对当前节点正向的内容。所以,在图已知的情况下,GWNet可以用式6的方式计算图卷积,如果是图未知的情况下,就用公式7计算 <img src="image-7.png"alt="GVNet提出的图卷积计算公式" />文章提到:值得注意的是,我们的图卷积属于基于空间的方法。尽管为了保持一致性,我们将图信号与节点特征矩阵互换使用,但我们在方程7 中的图卷积确实被解释为聚合来自不同邻域顺序的转换特征信息。</p><h2 id="时间卷积层">5. 时间卷积层</h2><p>时间卷积文章采用了 空洞因果卷积作为时间卷积层(TCN),空洞卷积神经网络能够以非递归的方式处理长距离序列。公式可以描述成:</p><figure><img src="image-8.png" alt="时序卷积公式" /><figcaption aria-hidden="true">时序卷积公式</figcaption></figure><figure><img src="image-11.png" alt="时序卷积示意图" /><figcaption aria-hidden="true">时序卷积示意图</figcaption></figure><h2 id="门控tcn">6. 门控TCN:</h2><p>门控机制在RNN中特别重要,可以有效的控制信息的流动(在层和层之间流动),在TCN中也是这样。该文章采用的门控TCN采用如下方式:</p><p>g()是激活函数,σ(·)是sigmoid函数,决定了信息传递到下一层的比例</p><h2 id="参考文献">7. 参考文献:</h2><p>http://zhuanlan.zhihu.com/p/594429261</p>]]></content>
<categories>
<category>时空图</category>
</categories>
</entry>
<entry>
<title>STIDGCN - 学习笔记</title>
<link href="/2025/04/28/STIDGCN%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/"/>
<url>/2025/04/28/STIDGCN%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/</url>
<content type="html"><![CDATA[<h1 id="stidgcn---学习笔记">STIDGCN - 学习笔记</h1><h2 id="文章摘要">1. 文章摘要</h2><p>准确的交通预测对于城市交通管理、路线规划和流量检测至关重要。时空模型的最新进展显着改进了交通预测中复杂的时空相关性的建模。不幸的是,之前的大多数研究在跨不同感知视角有效建模时空相关性方面遇到了挑战,并且忽略了时空相关性之间的交互学习。此外,受空间异质性的限制,大多数研究未能考虑每个节点不同的时空模式。为了克服这些限制,我们提出了一种用于流量预测的时空交互式动态图卷积网络(STIDGCN)。具体来说,文章提出了一个由空间和时间模块组成的交互式学习框架,用于对流量数据进行下采样。该框架旨在通过采用从全局到局部的感知视角来捕捉空间和时间的相关性,并通过积极的反馈促进它们的相互利用。在空间模块中,我们基于图构造方法设计了动态图卷积网络。该网络旨在利用考虑时空异质性的流量模式库作为查询来重建数据驱动的动态图结构。重构的图结构可以揭示交通网络中节点之间的动态关联。对八个真实世界流量数据集的大量实验表明,STIDGCN在平衡计算成本的同时优于最先进的基线。</p><p>源代码: <ahref="https://github.com/LiuAoyu1998/STIDGCN/blob/main/model.py">源代码</a></p><p>论文: <ahref="https://ieeexplore.ieee.org/abstract/document/10440184">论文</a></p><h2 id="模型核心结构">2. 模型核心结构</h2><p><img src="image0.png" alt="核心结构" /> -STI结构把输入分成多个序列,并且向下游不断分裂,形成类似二叉树的结构。这样做的目的:类似时序卷积,例如原始序列id:12345,分裂后序列A就是135,B就是246,这样序列A就能侧重分析到索引1和3之间的关系,如果原始数据是五分钟级别的,做一次STI可以认为变成关注十分钟级别的,两次就是关注二十分钟级别的。- 创新点:创新了一个DGCN模块,把原始序列分成奇序列(<img src="image.png"alt="奇数序列" />)和偶序列<img src="image-1.png"alt="偶数序列" />,送入时序卷积块中提取卷积信息,然后送到DGCN块中提取空间信息。最重要的一点是是<strong>交互学习</strong>,也就是看图中的红线,奇序列经过图模块提取完空间信息后的隐向量将会与偶序列进行哈德马积(红线、绿线),反过来偶数序列时序卷积、DGCN后也和奇序列进行哈德玛积。这个过程会进行两次,因此形成一个时空交互的结构,这个过程是本文最大的创新点。</p><h2 id="各模块介绍">3. 各模块介绍</h2><h3 id="encoder">3.1 Encoder</h3><p><strong>STI模块</strong>STI模块里面包含时间卷积TSConv模块和图卷积DGCN模块,两个模块在STI模块中进行交互学习。</p><p><strong>TSConv模块</strong>文章提出使用TSConv模块作为时间模块,捕捉时间关联性。时序卷积模块使用二维的CNN对padding后的序列进行卷积,两层卷积分别使用(stride= 1,padding =s1,)、(1,s2)作为卷积核,s1、s2是预先定义好的核尺寸,文章后续对该参数进行了敏感性分析。TSConv可与定义为如下公式:<img src="image-2.png" alt="卷积公式" /></p><p>其中H_t 和 H_t’ 代表了TSConv的隐状态,这里省略了激活函数。通过两层的卷积,能够提取到单个序列上的时序的动态性</p><p><strong>DGCN模块</strong>文章设计了一个权重共享的DGCN模块,作为空间模块,捕捉空间动态相关性。因为文章处理的是动态图,预先不知道图的邻接矩阵,得通过DGCN结构提取空间状态表征分成两个步骤: ①动态图重构:模拟动态的邻接矩阵②对于构建的动态图,聚合周边节点的信息输入是TSConv模块学习后的嵌入表示,这里输入的维度是Hg∈R(C×N×t’),C表示隐藏层的维度,channel,N是节点的个数,t’是时序的长度。这里由于输入的 尺寸不一样,因此先经过一个全连接层,得到聚合输入Hf∈R(C×N),随后和模式库(φ,PatternBank)进行交叉注意力(Hf和φ,得到Ap)和自注意力(Hf和Hf,得到Ah)计算。这里PatternBank是一个可训练的矩阵,φ∈R(C×N),可以认为是一种节点嵌入。得到嵌入矩阵后,进行交叉注意力【公式(6)】和自注意力【公式(7)】计算。:<img src="image-3.png" alt="注意力计算公式" />如此操作后,会得到两个邻接矩阵,Ap和Ah,这是两个矩阵,大小都是N×N然后把得到的两个空间注意力邻接矩阵使用Concat操作拼接,得到一个2N×N的矩阵,为了和下游的尺度对齐,又经过一个全连接层,这一步的目的是把矩阵的尺度从N×2N变成N×N<img src="image-4.png" alt="全连接层公式" />经过全连接层输出Af矩阵,可以认为Af矩阵中包含了节点和节点之间的空间关联关系,这是一个N×N的动态邻接矩阵。用这种方式计算的邻接矩阵会计算所有节点的空间关联性,然而实际上在图中不是所有节点都连接在一起的。因此还需要屏蔽掉不相关的节点。文章是对Af与矩阵M进行哈德玛积,矩阵M可以认为是注意力里面的Mask,取得是一个节点与他最相邻的K个邻居,通过Top-K的方式选取。(更通俗来说,一个节点i分别和各个节点计算相关性,最相关的K个节点标记1,否则标记0.)<img src="image-5.png" alt="节点相关性的定义" />得到邻接矩阵就可以进行图卷积操作了,文章采用扩散GCN进行动态图卷积,扩散图卷积把节点的动态变化描述成一个“扩散”过程,扩散图卷积聚合了图中节点之间的信息。扩散信号院子目标节点和当前最近的节点。扩散GCN可以描述为:<img src="image-6.png" alt="扩散GCN" />这里的Hg就是最开始的TSConv表征后的隐向量,尺寸是C×N×t’,W是自学习权重的矩阵,尺寸是N×N,Af是刚刚得到的邻接矩阵,大小N×N。这个地方矩阵乘的维度没有写的很清楚,纳闷了很久维度不一样怎么乘,看代码,我们可以看到矩阵相乘是在N维度上相乘,最终输出还是B×C×N×T(TODO单独实验一下DGCN模块的输入、输出,验证)。 <img src="image-7.png"alt="截图来自:https://github.com/LiuAoyu1998/STIDGCN/blob/main/model.py" /></p><p><strong>时空交互学习</strong>最终整个交互学习的过程,用公式的话可以描述成,和首图是对得上的: <imgsrc="image-8.png" alt="交互学习过程" /> ### 3.2 Decoder</p><p>解码器用于输入编码器的编码后的特征He进行解码,得到最终的计算结果Y。首先隐特征He送入GLU门控线性单元,门控单元就是将Hg分别经过两层FC,其中一层加上sigmoid激活函数,并进行哈德玛积。经过激活和FC后得到最终的预测结果Y。文章指出:没有采用自回归的方式生成y设结果是为了提高计算效率、缩小误差积累。</p><figure><img src="image-9.png" alt="解码器结构" /><figcaption aria-hidden="true">解码器结构</figcaption></figure><h2 id="实验">4. 实验</h2><h3 id="对比实验">4.1 对比实验</h3><p>模型采用了PEMS多个数据集,在多个数据集上进行测试,取得了SOTA的效果。</p><figure><img src="image-10.png" alt="评测结果" /><figcaption aria-hidden="true">评测结果</figcaption></figure><p>评价指标选的是MAE,MAPE,RMSE</p><h3 id="消融实验">4.2 消融实验</h3><p>消融实验:进行了四组消融实验: w/oIL:STIDGCN替换掉时空交互学习模块,使用串行策略进行学习。 w/o TSConv:移除TSConv模块 w/o GG:移除图生成模块 w/o DGCN:使用普通GCN替换DGCN,并且输入的邻接矩阵采用预定义图</p>]]></content>
<categories>
<category>时空图</category>
</categories>
</entry>
<entry>
<title>多模态表征学习 - 学习笔记</title>
<link href="/2025/04/23/%E8%A1%A8%E5%BE%81%E5%AD%A6%E4%B9%A0/"/>
<url>/2025/04/23/%E8%A1%A8%E5%BE%81%E5%AD%A6%E4%B9%A0/</url>
<content type="html"><![CDATA[<h1id="表征学习的定义分类和发展趋势">1.表征学习的定义、分类和发展趋势</h1><h2 id="表征学习的定义">1.1 表征学习的定义</h2><p><strong>表征学习的定义</strong>:表征学习(RepresentationLearning)是一种通过算法从数据中自动学习到有用特征的技术,其目的是将复杂的、高维的原始数据转化为机器学习能够高效处理的低维特征表示。表征学习对应的是经典机器学习中的“特征提取”模块,过往常常通过人工去提取特征,表征学习则将此过程自动化,通过机器学习算法处理。<strong>表征学习的模型输入</strong>:原始数据,其中包含高维度特征,例如:图像、文字、音频、视频、图等结构化或非结构化的数据<strong>表征学习输出</strong>:经过表征学习之后,提取出能被下游任务使用的低维特征。这里的特征可以是显式的也可以是隐式的。目前主流的技术路线是将表征层作为上游任务预训练,学习完成之后向下游任务传递隐式信息。模型的下游任务可以是分类、预测、生成式任务。</p><figure><img src="多模态表征.png" alt="多模态表征学习实例之Gemini模型架构" /><figcaptionaria-hidden="true">多模态表征学习实例之Gemini模型架构</figcaption></figure><h2 id="表征学习的分类">1.2 表征学习的分类</h2><p>表征学习按照任务划分,可以划分为: - 文字表征 -核心:对文本进行表征,即自然语言符号信息表示成数字信息,方便下游任务处理- 经典方法 - Word2Vec(Word Embedding) - Bert(Deep Model)</p><ul><li>视觉表征<ul><li>核心:理解各种视觉图像数据(如照片、医学图像、文件扫描、视频流)等的语义</li><li>经典方法<ul><li>Vgg-16、 ResNet(CNN系)</li><li>MAE、VIT(Transformer系)</li></ul></li></ul></li><li>音频表征<ul><li>核心:从音频信号中提取对应的声音特征</li><li>经典方法<ul><li>Wav2Vec</li><li>SimCLR</li><li>MAE</li></ul></li></ul></li><li>图表征<ul><li>核心:将图数据映射到向量空间,以保留图的结构特征和语义特征</li><li>经典方法<ul><li>GCN</li><li>GAT</li></ul></li></ul></li><li>多模态表征<ul><li>核心:旨在融合多种数据模态(如:文本、图像、音频、视频等)来提高模型的感知与理解能力,实现跨模态信息的交互与融合</li><li>经典方法<ul><li>预训练:BEIT、MAE</li><li>语义对齐:BLIP、CLIP、BEIT等</li><li>大模型:GPT、Gemini等</li></ul></li></ul></li></ul><h2 id="表征学习的发展趋势">1.3 表征学习的发展趋势</h2><h3id="年以前以cnnrnndnn架构为主">2018年以前:以CNN/RNN/DNN架构为主</h3><ul><li>AlexNet 【图像】</li><li>Vgg16 【图像】</li><li>ResNet 【图像】</li><li>Word2Vec 【文字】</li><li>GCN 【图】</li></ul><h3 id="transformer架构逐渐成为主流框架">2018-2023:Transformer架构逐渐成为主流框架、</h3><ul><li>BERT 【文】</li><li>ViT 【图像】</li><li>GAT/Graph Transformer 【图】</li><li>MAE 【图像/视频】</li><li>CLIP 【图像】</li><li>VILT 【图像】</li></ul><h3 id="后-与大模型架构相结合所列出的模型全部属于多模态大模型">2023后:与大模型架构相结合(所列出的模型全部属于多模态大模型)</h3><ul><li>Grok3</li><li>Qwen-VL</li><li>GPT-4o</li><li>Gemini</li><li>Deepseek-VL</li><li>LLAVA-NeXT</li><li>Mini-GPT</li><li>Doubao - 1.5</li><li>Cosmos ### 技术发展趋势:</li><li>小模型专注于计算机视觉、文字处理等机器学习经典领域,聚焦人脸识别、目标检测等专业任务</li><li>大模型时代多模态表征是标配,聚焦多模态理解(语义对齐、协同学习)与多模态生成方向</li><li>图像+文字+视频的多模态融合是主要研究方向,主要需要解决语义对齐、语义融合与协同学习的问题。# 2. 表征学习的主流架构 ## 2.1 图像表征经典架构———CNN结构说到表征就不得不说到CNN,关于CNN的结构就不多介绍了。我们在此思考,为什么CNN模型在CV领域取得了巨大的成功?主要有以下几点原因:</li><li><strong>感知性</strong>:卷积层通过卷积操作和参数共享,能够提取图像的局部特征</li><li><strong>参数共享</strong>:相同的卷积核在不同的位置对图像进行卷积操作,共享参数减少了模型的复杂度,也增强了模型的泛化能力</li><li><strong>空间不变性</strong>:卷积操作具有平移不变性,即无论图像中的物体在图像中的位置如何变化,比如上下左右平移,最终都能被卷积核扫到,能够提取到相应的特征。</li></ul><p>例如 可以到<ahref="https://poloclub.github.io/cnn-explainer/">卷积可视化网站</a>查看卷积操作都对图片做了什么。可以看到,浅层卷积分辨率高,提取点、颜色等基础特征,随着卷积的深入,逐渐提取到线段、边缘、轮廓、角点等特征,进行多层卷积之后的图像具有分辨率低的特点,从而能够和最终分类的抽象特征强相关联。这就是CNN模型起效的原因。<img src="image1.gif" alt="卷积操作可视化" /> ## 2.2图像表征改进架构———Transformer结构</p><h2 id="transformer架构讨论分析">2.3 Transformer架构讨论分析</h2><h1 id="多模态表征与多模态大模型">3. 多模态表征与多模态大模型</h1><h2 id="多模态表征的定义动机和核心问题">3.1多模态表征的定义、动机和核心问题</h2><h2 id="多模态表征的经典结构">3.2 多模态表征的经典结构</h2><h3 id="单塔结构以vilt为例">3.2.1 单塔结构(以VILT为例)</h3><h3 id="双塔结构以clip为例">3.2.2 双塔结构(以CLIP为例)</h3><h2 id="多模态大模型">3.3 多模态大模型</h2><h3 id="多模态大模型的通用结构">3.3.1 多模态大模型的通用结构</h3><h1 id="参考文献">4. 参考文献</h1>]]></content>
<categories>
<category>深度学习</category>
</categories>
</entry>
<entry>
<title>Pytorch快速入门笔记</title>
<link href="/2025/04/02/Pytorch%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/"/>
<url>/2025/04/02/Pytorch%E5%BF%AB%E9%80%9F%E4%B8%8A%E6%89%8B/</url>
<content type="html"><![CDATA[<p>本笔记从<ahref="https://www.bilibili.com/video/BV1hE411t7RN/">小土堆Pytorch教程</a>中记录一些实用的Pytorch相关操作.</p><h1 id="加载数据">1. 加载数据</h1><h2 id="pil">1.1 PIL</h2><p>PIL类可以用于加载图像、保存图像等操作 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">from</span> PIL <span class="hljs-keyword">import</span> Image<br>img = Image.<span class="hljs-built_in">open</span>(<span class="hljs-string">'data/hymenoptera_data/train/ants/342438950_a3da61deab.jpg'</span>)<br></code></pre></td></tr></table></figure> ## 1.2 DataSetDataSet是一个抽象类,需要实现其中的<code>__getitem__</code>方法,以及最好是实现<code>__len__</code>方法,不然不能用迭代器,用for循环的方式取数据,以下是一个自定义数据集的设置方式,可以看到需要重写<code>__getitem__</code>方法取数据<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyDataSet</span>(<span class="hljs-title class_ inherited__">Dataset</span>):<br><br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, data_dir, transforms=<span class="hljs-literal">None</span></span>):<br> <span class="hljs-built_in">super</span>().__init__()<br> <span class="hljs-variable language_">self</span>.data_dir = data_dir<br> <span class="hljs-variable language_">self</span>.image_paths = os.listdir(data_dir)<br> <span class="hljs-variable language_">self</span>.transforms = transforms<br><br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__getitem__</span>(<span class="hljs-params">self, index</span>) -> T_co:<br> img_file_name = <span class="hljs-variable language_">self</span>.image_paths[index]<br> img = Image.<span class="hljs-built_in">open</span>(<span class="hljs-variable language_">self</span>.data_dir + img_file_name)<br> img_trans = <span class="hljs-variable language_">self</span>.transforms(img)<br> <span class="hljs-keyword">return</span> img_trans<br><br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__len__</span>(<span class="hljs-params">self</span>):<br> <span class="hljs-keyword">return</span> <span class="hljs-built_in">len</span>(<span class="hljs-variable language_">self</span>.image_paths)<br></code></pre></td></tr></table></figure></p><h2 id="dataloader">1.3 DataLoader</h2><p>如果把DataSet看做一副牌,那么DataLoader就是用于定义如何发牌,或者对牌进行一些操作(洗牌、转换格式等),如果已经有一个数据集,那么可以通过这种方式定义data_loader<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">my_ds = MyDataSet(data_dir=<span class="hljs-string">'data/hymenoptera_data/val/ants/'</span>, transforms=trans)<br><span class="hljs-comment"># drop_last: 总长度除bs 除不尽的时候是否去掉最后一个</span><br><span class="hljs-comment"># batch_size: 批的量</span><br>data_loader = DataLoader(dataset=my_ds, batch_size=<span class="hljs-number">2</span>, shuffle=<span class="hljs-literal">True</span>, drop_last=<span class="hljs-literal">True</span>)<br></code></pre></td></tr></table></figure>定义好的数据集,可以通过DataLoader加载,并通过for循环取数据,例如:<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python">step = <span class="hljs-number">0</span><br><span class="hljs-keyword">for</span> images <span class="hljs-keyword">in</span> data_loader:<br> <span class="hljs-built_in">print</span>(images.shape)<br> writer.add_images(<span class="hljs-string">'image_batch'</span>, images, step) <span class="hljs-comment"># (tag,Image,step(不添加这个参数 tensorboard里面的step始终为零))</span><br> step += <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure> ## 1.4 torchvision数据集的下载和使用如果是一些成熟的数据集,比如CIFAR10,可以用封装好的方式获取数据集,这些数据集也是重写了DataSet类,可以传入transform<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">train_data = torchvision.datasets.CIFAR10(root=<span class="hljs-string">'../data'</span>, train=<span class="hljs-literal">True</span>, transform=torchvision.transforms.ToTensor(),<br> download=<span class="hljs-literal">True</span>)<br>test_data = torchvision.datasets.CIFAR10(root=<span class="hljs-string">'../data'</span>, train=<span class="hljs-literal">False</span>, transform=torchvision.transforms.ToTensor(),<br> download=<span class="hljs-literal">True</span>)<br></code></pre></td></tr></table></figure> # 2. Tensorboard 的使用TensorBoard是一个可视化工具,它可以用来展示网络图、张量的指标变化、张量的分布情况等。特别是在训练网络的时候,我们可以设置不同的参数(比如:权重W、偏置B、卷积层数、全连接层数等),使用TensorBoader可以很直观的帮我们进行参数的选择。它通过运行一个本地服务器,来监听6006端口。在浏览器发出请求时,分析训练时记录的数据,绘制训练过程中的图像。</p><p>首先定义一个SummaryWriter(),然后就可以用writer里面的方法往tensorboard里面写数据,不仅可以添加过程量还可以添加单张图像。默认的路径保存到本地runs目录下,可以用SummaryWriter()<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python">writer = SummaryWriter()<br><span class="hljs-comment"># 添加过程量(标量)</span><br><span class="hljs-keyword">for</span> n_iter <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(<span class="hljs-number">100</span>):<br> writer.add_scalar(<span class="hljs-string">'Loss/train'</span>, np.random.random(), n_iter)<br> writer.add_scalar(<span class="hljs-string">'Loss/test'</span>, np.random.random(), n_iter)<br> writer.add_scalar(<span class="hljs-string">'Accuracy/train'</span>, np.random.random(), n_iter)<br> writer.add_scalar(<span class="hljs-string">'Accuracy/test'</span>, np.random.random(), n_iter)<br>writer.add_image(tag=<span class="hljs-string">'test'</span>, img_tensor=img_tensor)<br></code></pre></td></tr></table></figure>查看数据:cd到保存文件的文件夹下,输入<code>tensorboard --logdir runs</code>runs对应文件保存的目录,然后就可以通过访问<code>http://localhost:6006/#timeseries</code>查看记录的结果</p><h1 id="transforms-的使用">3. Transforms 的使用</h1><p>Transforms用来对一张图片进行一系列的转换,可以用Compose定义需要转换的内容<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python">trans = transforms.Compose([<br> transforms.ToTensor(),<br> <span class="hljs-comment"># transforms.RandomCrop(size=(50,50)) 随机裁剪</span><br> transforms.Resize((<span class="hljs-number">100</span>, <span class="hljs-number">100</span>))<br>])<br><br></code></pre></td></tr></table></figure>定义好转换之后,可以对单张图片进行转换,把图像传入就可以,例如:<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 添加图像</span><br>img = Image.<span class="hljs-built_in">open</span>(<span class="hljs-string">'data/hymenoptera_data/train/ants/342438950_a3da61deab.jpg'</span>)<br><span class="hljs-comment"># 图像转换</span><br>trans = transforms.Compose([<br> transforms.ToTensor(),<br> <span class="hljs-comment"># transforms.RandomCrop(size=(50,50)) # 随机裁剪</span><br>])<br>img_tensor = trans(img)<br></code></pre></td></tr></table></figure></p><h1 id="常用函数">4. 常用函数</h1><h2 id="卷积函数">4.1卷积函数</h2><p>卷积函数的定义网上有很多了就不再赘述了,定义一个卷积核,然后和现在的矩阵进行卷积操作,可得到一个结果。</p><p>借用知乎<ahref="https://zhuanlan.zhihu.com/p/161660908">2D卷积,nn.Conv2d和F.conv2d</a>一段话:卷积操作:卷积核和扫过的小区域对应位置相乘再求和的操作,卷积完成后一般要加个偏置bias。一种Kernel如果分成多个通道上的子Kernel做卷积运算,最后运算结果还要加在一起后,再加偏置。</p><p>使用卷积运算的时候需要注意输入输出的尺寸,需要对齐,比如Conv2D如果是函数就要求B ,C 两个维度要对齐。需要注意的点是输入输出维度会根据stride、padding的设置改变,比如64×64的图像进去,不设置padding出来的图像可能就变成62×62了,如果还要保持图像尺寸一致(特别是复现论文的场景),需要反算一下stride和padding的值,这里公式在<ahref="https://pytorch.org/docs/stable/generated/torch.nn.Conv2d.html#torch.nn.Conv2d">PytorchConv2d文档</a>,需要的时候直接查阅就好。</p><p>关于可视化展示卷积函数中的stride、padding、dilation参数的含义,可参考文档:<ahref="https://github.com/vdumoulin/conv_arithmetic/blob/master/README.md">Convolutionarithmetic</a></p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn.functional <span class="hljs-keyword">as</span> F<br><br>w = torch.rand(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">2</span>, <span class="hljs-number">2</span>) <span class="hljs-comment"># 对应 B C ×卷积核的 H W,前2个维度对应上(在H W两个维度上进行卷积)</span><br>res = F.conv2d(<span class="hljs-built_in">input</span>=a, weight=w, stride=<span class="hljs-number">1</span>)<br><span class="hljs-built_in">print</span>(res)<br><br>tensor([[[[-<span class="hljs-number">0.9163</span>, -<span class="hljs-number">1.4657</span>, -<span class="hljs-number">3.6013</span>, ..., <span class="hljs-number">0.1913</span>, -<span class="hljs-number">1.4308</span>, -<span class="hljs-number">1.1725</span>],<br> [-<span class="hljs-number">1.7863</span>, -<span class="hljs-number">1.1487</span>, -<span class="hljs-number">3.5197</span>, ..., -<span class="hljs-number">0.5010</span>, -<span class="hljs-number">2.3962</span>, -<span class="hljs-number">3.8177</span>],<br> [-<span class="hljs-number">0.0863</span>, -<span class="hljs-number">0.3723</span>, -<span class="hljs-number">1.7177</span>, ..., -<span class="hljs-number">1.9196</span>, -<span class="hljs-number">1.4938</span>, -<span class="hljs-number">2.6761</span>],<br> ...,<br> [ <span class="hljs-number">0.8136</span>, -<span class="hljs-number">4.5267</span>, -<span class="hljs-number">0.6807</span>, ..., -<span class="hljs-number">2.2519</span>, <span class="hljs-number">1.4239</span>, -<span class="hljs-number">0.9793</span>],<br> [ <span class="hljs-number">1.8353</span>, -<span class="hljs-number">1.8440</span>, -<span class="hljs-number">3.9382</span>, ..., -<span class="hljs-number">1.8193</span>, <span class="hljs-number">2.7279</span>, <span class="hljs-number">4.4726</span>],<br> [ <span class="hljs-number">0.5444</span>, <span class="hljs-number">1.2673</span>, -<span class="hljs-number">3.4205</span>, ..., -<span class="hljs-number">2.3179</span>, -<span class="hljs-number">2.5870</span>, -<span class="hljs-number">1.7544</span>]]]])<br></code></pre></td></tr></table></figure><h2 id="池化函数">4.2 池化函数</h2><p>池化函数是深度学习中常用的技术,主要用于降低数据的维度和减少计算量。常见的池化函数包括:</p><ol type="1"><li>最大池化(MaxPooling):在池化窗口内选取最大值作为输出,能够提取图像中的主要特征。</li><li>平均池化(AveragePooling):在池化窗口内取平均值作为输出,可以平滑输入数据,减少噪声的影响。</li><li>自适应池化(AdaptivePooling):根据输入的大小自动调整池化窗口的大小,以适应不同的输入尺寸。</li></ol><p>池化操作可以分为一维池化、二维池化和三维池化,具体取决于被池化的张量维数。池化不仅可以减小数据大小,还可以增加数据大小,具体取决于应用场景。<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python">a = torch.randn(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">64</span>)<br>res = F.max_pool2d(a,kernel_size=<span class="hljs-number">2</span>) <span class="hljs-comment"># kernel_size指定2的话是默认2×2的2d(正方形)。而且池化默认区域不重叠的,默认步长就是kernel_size=2,这一点和卷积运算不一样</span><br><span class="hljs-built_in">print</span>(res.shape) <span class="hljs-comment"># torch.Size([1, 3, 32, 32])</span><br></code></pre></td></tr></table></figure></p><h1 id="神经网络的搭建">5. 神经网络的搭建</h1><h2 id="卷积层池化层非线性激活层">5.1 卷积层、池化层、非线性激活层</h2><p>通过引入<code>torch.nn</code>引入常见神经网络的层,包括卷积层、池化层等.以及非线性激活层,RELUSOFTMAX之类的,具体就不再展开了。 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-built_in">super</span>().__init__(*args, **kwargs)<br> <span class="hljs-variable language_">self</span>.conv1 = nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">3</span>, <span class="hljs-number">1</span>, <span class="hljs-number">1</span>)<br> <span class="hljs-variable language_">self</span>.conv2 = nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">3</span>, <span class="hljs-number">1</span>, <span class="hljs-number">0</span>)<br> <span class="hljs-variable language_">self</span>.max_pool = nn.MaxPool2d(<span class="hljs-number">4</span>)<br> <span class="hljs-variable language_">self</span>.relu = nn.ReLU()<br> <span class="hljs-variable language_">self</span>.softmax = nn.Softmax()<br> <span class="hljs-comment"># in_channels: int,</span><br> <span class="hljs-comment"># out_channels: int,</span><br> <span class="hljs-comment"># kernel_size: _size_2_t,</span><br> <span class="hljs-comment"># stride: _size_2_t = 1,</span><br> <span class="hljs-comment"># padding: Union[str, _size_2_t] = 0,</span><br> <span class="hljs-comment"># dilation: _size_2_t = 1,</span><br><br>a = torch.randn(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">64</span>)<br><br>mnn = MyNeuralNetwork()<br><br>res_conv1 = mnn.conv1(a)<br><span class="hljs-built_in">print</span>(res_conv1.shape) <span class="hljs-comment"># torch.Size([1, 64, 64, 64])</span><br><br>res_conv2 = mnn.conv2(a)<br><span class="hljs-built_in">print</span>(res_conv2.shape) <span class="hljs-comment"># torch.Size([1, 64, 62, 62])</span><br><br>res_max_pool = mnn.max_pool(a)<br><span class="hljs-built_in">print</span>(res_max_pool.shape) <span class="hljs-comment"># torch.Size([1, 3, 16, 16])</span><br><br><br></code></pre></td></tr></table></figure> ##5.2线性层及其他层</p><ol type="1"><li>线性层:线性层又叫全连接层,其中每个神经元和上一层所有的神经元相连,使用<code>nn.Linear(in_features,out_features,bias)</code>定义,运算公式是<spanclass="math display"><em>y</em> = <em>x</em><em>A</em><sup><em>T</em></sup> + <em>b</em></span>,注意默认是加上bias的,即<code>bias=True</code> 代码例子:<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-variable language_">self</span>.linear = nn.Linear(<span class="hljs-number">64</span>,<span class="hljs-number">32</span>)<br>a = torch.randn(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">64</span>)<br>res_l = mnn.linear(a)<br><span class="hljs-built_in">print</span>(res_l.shape) <span class="hljs-comment"># torch.Size([1, 3, 64, 32])</span><br></code></pre></td></tr></table></figure></li><li>展平层:将多维度的张量展平。默认参数:<code>start_dim: int = 1, end_dim: int = -1</code>从开始的维度展开到结束的维度 代码例子: <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-variable language_">self</span>.flatten1 = nn.Flatten(start_dim=<span class="hljs-number">0</span>)<br> <span class="hljs-variable language_">self</span>.flatten2 = nn.Flatten()<br>a = torch.randn(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">64</span>)<br>res_f = mnn.flatten1(a)<br>res_f2 = mnn.flatten2(a)<br><span class="hljs-built_in">print</span>(res_f.shape) <span class="hljs-comment"># torch.Size([12288])</span><br><span class="hljs-built_in">print</span>(res_f2.shape) <span class="hljs-comment"># torch.Size([1, 12288])</span><br></code></pre></td></tr></table></figure>一般说来,Flatten层常用于把多维的输入一维化,常用在从卷积层到全连接层的过渡。Flatten默认不影响batch的大小(start_dim=1 )。</li></ol><h2 id="sequential的使用">5.3 Sequential的使用</h2><p>nn.Sequential()可以作为容器,里面放入模型的各种层,在forward的时候将会贯序列执行各层,通常有2种定义方式- 定义方式1 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn <span class="hljs-keyword">as</span> nn<br>model = nn.Sequential(<br> nn.Conv2d(<span class="hljs-number">1</span>,<span class="hljs-number">20</span>,<span class="hljs-number">5</span>),<br> nn.ReLU(),<br> nn.Conv2d(<span class="hljs-number">20</span>,<span class="hljs-number">64</span>,<span class="hljs-number">5</span>),<br> nn.ReLU()<br> )<br> <br><span class="hljs-built_in">print</span>(model)<br><span class="hljs-built_in">print</span>(model[<span class="hljs-number">2</span>]) <span class="hljs-comment"># 通过索引获取第几个层</span><br><span class="hljs-string">'''运行结果为:</span><br><span class="hljs-string">Sequential(</span><br><span class="hljs-string"> (0): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))</span><br><span class="hljs-string"> (1): ReLU()</span><br><span class="hljs-string"> (2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))</span><br><span class="hljs-string"> (3): ReLU()</span><br><span class="hljs-string">)</span><br><span class="hljs-string">Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))</span><br><span class="hljs-string">'''</span><br></code></pre></td></tr></table></figure> - 定义方式2:给每个层添加一个名称<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn <span class="hljs-keyword">as</span> nn<br><span class="hljs-keyword">from</span> collections <span class="hljs-keyword">import</span> OrderedDict<br>model = nn.Sequential(OrderedDict([<br> (<span class="hljs-string">'conv1'</span>, nn.Conv2d(<span class="hljs-number">1</span>,<span class="hljs-number">20</span>,<span class="hljs-number">5</span>)),<br> (<span class="hljs-string">'relu1'</span>, nn.ReLU()),<br> (<span class="hljs-string">'conv2'</span>, nn.Conv2d(<span class="hljs-number">20</span>,<span class="hljs-number">64</span>,<span class="hljs-number">5</span>)),<br> (<span class="hljs-string">'relu2'</span>, nn.ReLU())<br> ]))<br> <br><span class="hljs-built_in">print</span>(model)<br><span class="hljs-string">'''运行结果为:</span><br><span class="hljs-string">Sequential(</span><br><span class="hljs-string"> (conv1): Conv2d(1, 20, kernel_size=(5, 5), stride=(1, 1))</span><br><span class="hljs-string"> (relu1): ReLU()</span><br><span class="hljs-string"> (conv2): Conv2d(20, 64, kernel_size=(5, 5), stride=(1, 1))</span><br><span class="hljs-string"> (relu2): ReLU()</span><br><span class="hljs-string">)</span><br><span class="hljs-string">'''</span><br></code></pre></td></tr></table></figure>我们可以将前面所学的层组合起来,形成深层神经网络的架构,例如我们可以编写一个自己的网络如下:<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-built_in">super</span>().__init__(*args, **kwargs)<br> <span class="hljs-variable language_">self</span>.model = nn.Sequential(<br> nn.Conv2d(<span class="hljs-number">3</span>,<span class="hljs-number">64</span>,<span class="hljs-number">3</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>), <span class="hljs-comment"># 【1,64,64,64】 备注:ks=3,stride=1,padding = 1</span><br> <span class="hljs-comment"># Hout(64) = (Hin(64) + 2×padding - dilation×[ks - 1] × 1 )/stride + 1</span><br> nn.ReLU(),<br> nn.Conv2d(<span class="hljs-number">64</span>,<span class="hljs-number">32</span>,<span class="hljs-number">3</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>), <span class="hljs-comment"># 1,32,64,64</span><br> nn.ReLU(),<br> nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">16</span>, <span class="hljs-number">3</span>,<span class="hljs-number">1</span>,<span class="hljs-number">1</span>), <span class="hljs-comment"># 1,16,64,64</span><br> nn.ReLU(),<br> nn.MaxPool2d(<span class="hljs-number">2</span>), <span class="hljs-comment"># 1,16,32,32</span><br> nn.ReLU(),<br> nn.Linear(<span class="hljs-number">32</span>,<span class="hljs-number">1024</span>), <span class="hljs-comment"># 1,16,32,1024</span><br> nn.ReLU(),<br> nn.Linear(<span class="hljs-number">1024</span>, <span class="hljs-number">1024</span>), <span class="hljs-comment"># 1,16,32,1024</span><br> nn.ReLU(),<br> nn.Flatten(), <span class="hljs-comment"># 1,524288</span><br> nn.Linear(<span class="hljs-number">524288</span>,<span class="hljs-number">10</span>), <span class="hljs-comment"># 1,10</span><br> nn.Softmax(dim= -<span class="hljs-number">1</span>)<br> )<br><br>mnn = MyNeuralNetwork()<br><br>a = torch.randn(<span class="hljs-number">1</span>,<span class="hljs-number">3</span>,<span class="hljs-number">64</span>,<span class="hljs-number">64</span>)<br><br>res = mnn.model(a)<br><br><span class="hljs-built_in">print</span>(res.shape) <span class="hljs-comment"># torch.Size([1, 10])</span><br><span class="hljs-built_in">print</span>(res) <span class="hljs-comment">#tensor([[0.0991, 0.0982, 0.0997, 0.0996, 0.1009, 0.1030, 0.0988, 0.1027, 0.0991,0.0988]], grad_fn=<SoftmaxBackward0>)</span><br></code></pre></td></tr></table></figure></p><p>通过使用Sequential()的方式可以便捷的完成网络的定义,快速实现网络。 ##5.4 小网络搭建实战以vgg16这个网络(图待补充)为例,搭建模型如下(暂未添加Relu层),其实和我们之前写的模型很像<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-built_in">super</span>().__init__(*args, **kwargs)<br> <span class="hljs-variable language_">self</span>.model= nn.Sequential(<br> nn.Conv2d(<span class="hljs-number">3</span>,<span class="hljs-number">32</span>,<span class="hljs-number">5</span>,padding=<span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Conv2d(<span class="hljs-number">32</span>,<span class="hljs-number">32</span>,<span class="hljs-number">5</span>,padding=<span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">64</span>, <span class="hljs-number">5</span>, padding=<span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Flatten(),<br> nn.Linear(<span class="hljs-number">4096</span>,<span class="hljs-number">64</span>),<br> nn.Linear(<span class="hljs-number">64</span>, <span class="hljs-number">10</span>),<br> )<br><br>a = torch.randn(<span class="hljs-number">1</span>, <span class="hljs-number">3</span>, <span class="hljs-number">64</span>, <span class="hljs-number">64</span>)<br>mnn = MyNeuralNetwork()<br>res = mnn.model(a)<br><span class="hljs-built_in">print</span>(res.shape) <span class="hljs-comment">#torch.Size([1, 10])</span><br></code></pre></td></tr></table></figure></p><h1 id="损失函数与反向传播">6 损失函数与反向传播</h1><h2 id="损失函数">6.1 损失函数</h2><p>损失函数(Loss Function)是一个衡量预测结果与真实结果之间差异的函数,也称为误差函数。它通过计算模型的预测值与真实值之间的不一致程度,来评估模型的性能.根据任务不同,选择的损失函数也不同,对于回归任务,常见的损失函数有<code>MSELoss</code>,对于分类任务常见的损失函数有交叉熵损失<code>CrossEntropyLoss</code>交叉熵的损失函数可以描述为</p><figure><img src="image.png" alt="交叉熵的损失函数" /><figcaption aria-hidden="true">交叉熵的损失函数</figcaption></figure><p>举例说明: <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn.functional <span class="hljs-keyword">as</span> F<br>x = torch.tensor([[<span class="hljs-number">0.1</span>, <span class="hljs-number">0.2</span>, <span class="hljs-number">0.3</span>]]) <span class="hljs-comment"># 预测三个类别概率分别是0.1,0.2,0.3</span><br>y = torch.tensor([<span class="hljs-number">1</span>]) <span class="hljs-comment"># 答案是1</span><br>loss = F.cross_entropy(x, y) <span class="hljs-comment"># 计算交叉熵 loss = -0.2 + ln(e^0.1+e^0.2+e^0.3) = 1.10194284823</span><br><span class="hljs-built_in">print</span>(loss) <span class="hljs-comment"># tensor(1.1019)</span><br></code></pre></td></tr></table></figure> 其他的案例也差不多 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs python">x = torch.tensor([[<span class="hljs-number">0.55</span>, <span class="hljs-number">0.88</span>]]) <span class="hljs-comment"># 预测值</span><br>y = torch.tensor([[<span class="hljs-number">0.5</span>, <span class="hljs-number">0.8</span>]]) <span class="hljs-comment"># 真实值</span><br>loss_l1 = F.l1_loss(x, y) <span class="hljs-comment"># L1Loss 一阶距</span><br>loss_mse = F.mse_loss(x, y) <span class="hljs-comment"># MSE_LOSS</span><br><span class="hljs-built_in">print</span>(loss_l1) <span class="hljs-comment"># tensor(0.0650)</span><br><span class="hljs-built_in">print</span>(loss_mse) <span class="hljs-comment"># tensor(0.0044)</span><br><br>loss_layer = nn.L1Loss(reduction=<span class="hljs-string">'sum'</span>) <span class="hljs-comment"># 备注:reduction默认是mean,用mean的话结果是0.065</span><br>loss_l1_by_layer = loss_layer(x, y)<br><span class="hljs-built_in">print</span>(loss_l1_by_layer) <span class="hljs-comment"># tensor(0.1300)</span><br></code></pre></td></tr></table></figure> ## 6.2反向传播 首先说一下什么是反向传播算法。反向传播算法(Backpropagation,简称BP算法)是“误差反向传播”的简称,是适合于多层神经元网络的一种学习算法,它建立在梯度下降法的基础上。梯度下降法是训练神经网络的常用方法,许多的训练方法都是基于梯度下降法改良出来的,因此了解梯度下降法很重要。梯度下降法通过计算损失函数的梯度,并将这个梯度反馈给最优化函数来更新权重以最小化损失函数。</p><p>在PyTorch中,loss.backward()函数用于计算模型参数相对于损失函数的梯度。</p><p>前向传播</p><p>首先,模型通过前向传播计算输出值。在这个过程中,PyTorch会记录计算图(ComputationGraph),这个计算图记录了从输入到输出的每一步运算及其依赖关系。每个张量(Tensor)都有一个.grad_fn属性,指向一个函数,这个函数描述了如何计算这个张量关于其输入的梯度。</p><p>反向传播</p><p>当调用loss.backward()时,PyTorch开始反向遍历计算图。这个过程从损失函数开始,沿着图反向传播误差,计算每一个参与运算的张量关于损失的梯度。这是通过链式法则(ChainRule)完成的,即将损失对某个中间变量的导数分解为其后续操作导数的乘积。</p><p>梯度计算在反向传播过程中,每个运算都会计算其输出关于输入的梯度,并将这个梯度累积到输入张量的.grad属性中(如果是标量损失,它没有.grad属性)。这意味着如果一个张量被多个路径使用,它的.grad属性会累积从所有路径来的梯度.</p><p>在使用loss.backward()时,有几个重要的注意事项:</p><pre><code class="hljs">梯度归零:在每次反向传播之前,通常需要调用optimizer.zero_grad()来将梯度归零,以避免梯度累加</code></pre><h2 id="优化器">6.3 优化器</h2><p>优化器决定了模型以何种方式的梯度下降算法更新模型。常见的优化器有 SGD,adam等。</p><p>在PyTorch中,optimizer.step()是优化器对象的一个方法,用于执行模型参数的更新。在深度学习训练过程中,参数更新是通过反向传播算法计算损失函数的梯度后,使用优化器根据这些梯度进行的。optimizer.step()方法正是用于根据梯度和学习率等超参数来更新模型参数,从而使损失函数值最小化的步骤</p><h1 id="使用常用模型">7 使用常用模型</h1><h2 id="使用库的方式调用常用模型">7.1 使用库的方式调用常用模型</h2><p>一些常用的模型,比较经典的模型都是包装在库里面了,可以通过<code>torchvision.models.xxx</code>调用模型.例如: <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><code class="hljs python">vgg16 = torchvision.models.vgg16(pretrained = <span class="hljs-literal">False</span>)<br><br><span class="hljs-built_in">print</span>(vgg16)<br><br>输出:<br>VGG(<br> (features): Sequential(<br> (<span class="hljs-number">0</span>): Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">64</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">1</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">2</span>): Conv2d(<span class="hljs-number">64</span>, <span class="hljs-number">64</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">3</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">4</span>): MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>, padding=<span class="hljs-number">0</span>, dilation=<span class="hljs-number">1</span>, ceil_mode=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">5</span>): Conv2d(<span class="hljs-number">64</span>, <span class="hljs-number">128</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">6</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">7</span>): Conv2d(<span class="hljs-number">128</span>, <span class="hljs-number">128</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">8</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">9</span>): MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>, padding=<span class="hljs-number">0</span>, dilation=<span class="hljs-number">1</span>, ceil_mode=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">10</span>): Conv2d(<span class="hljs-number">128</span>, <span class="hljs-number">256</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">11</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">12</span>): Conv2d(<span class="hljs-number">256</span>, <span class="hljs-number">256</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">13</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">14</span>): Conv2d(<span class="hljs-number">256</span>, <span class="hljs-number">256</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">15</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">16</span>): MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>, padding=<span class="hljs-number">0</span>, dilation=<span class="hljs-number">1</span>, ceil_mode=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">17</span>): Conv2d(<span class="hljs-number">256</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">18</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">19</span>): Conv2d(<span class="hljs-number">512</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">20</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">21</span>): Conv2d(<span class="hljs-number">512</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">22</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">23</span>): MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>, padding=<span class="hljs-number">0</span>, dilation=<span class="hljs-number">1</span>, ceil_mode=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">24</span>): Conv2d(<span class="hljs-number">512</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">25</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">26</span>): Conv2d(<span class="hljs-number">512</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">27</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">28</span>): Conv2d(<span class="hljs-number">512</span>, <span class="hljs-number">512</span>, kernel_size=(<span class="hljs-number">3</span>, <span class="hljs-number">3</span>), stride=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>), padding=(<span class="hljs-number">1</span>, <span class="hljs-number">1</span>))<br> (<span class="hljs-number">29</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">30</span>): MaxPool2d(kernel_size=<span class="hljs-number">2</span>, stride=<span class="hljs-number">2</span>, padding=<span class="hljs-number">0</span>, dilation=<span class="hljs-number">1</span>, ceil_mode=<span class="hljs-literal">False</span>)<br> )<br> (avgpool): AdaptiveAvgPool2d(output_size=(<span class="hljs-number">7</span>, <span class="hljs-number">7</span>))<br> (classifier): Sequential(<br> (<span class="hljs-number">0</span>): Linear(in_features=<span class="hljs-number">25088</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">1</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">2</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">3</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">4</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">5</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">6</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">1000</span>, bias=<span class="hljs-literal">True</span>)<br> )<br>)<br><br></code></pre></td></tr></table></figure></p><h2 id="对常用模型进行增加或修改">7.2 对常用模型进行增加或修改</h2><ol type="1"><li><p>增加某层 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python">vgg16.features.add_module(<span class="hljs-string">'relu'</span>,torch.nn.ReLU())<br>vgg16.classifier.add_module(<span class="hljs-string">'linear'</span>,torch.nn.Linear(<span class="hljs-number">1000</span>,<span class="hljs-number">10</span>))<br>例如分类器加上之后,模型结构如下:<br> (classifier): Sequential(<br> (<span class="hljs-number">0</span>): Linear(in_features=<span class="hljs-number">25088</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">1</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">2</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">3</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">4</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">5</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">6</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">1000</span>, bias=<span class="hljs-literal">True</span>)<br> (linear): Linear(in_features=<span class="hljs-number">1000</span>, out_features=<span class="hljs-number">10</span>, bias=<span class="hljs-literal">True</span>)<br> )<br></code></pre></td></tr></table></figure></p></li><li><p>修改某层 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs python">vgg16.classifier[<span class="hljs-number">7</span>]=nn.Linear(<span class="hljs-number">1000</span>,<span class="hljs-number">20</span>) <span class="hljs-comment"># 7 是模型的第几层</span><br><br>上面的代码执行之后会对刚添加的线性层修改输出特征节点的个数,改成了<span class="hljs-number">55</span>个<br> (classifier): Sequential(<br> (<span class="hljs-number">0</span>): Linear(in_features=<span class="hljs-number">25088</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">1</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">2</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">3</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">4096</span>, bias=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">4</span>): ReLU(inplace=<span class="hljs-literal">True</span>)<br> (<span class="hljs-number">5</span>): Dropout(p=<span class="hljs-number">0.5</span>, inplace=<span class="hljs-literal">False</span>)<br> (<span class="hljs-number">6</span>): Linear(in_features=<span class="hljs-number">4096</span>, out_features=<span class="hljs-number">1000</span>, bias=<span class="hljs-literal">True</span>)<br> (linear): Linear(in_features=<span class="hljs-number">1000</span>, out_features=<span class="hljs-number">55</span>, bias=<span class="hljs-literal">True</span>)<br> )<br></code></pre></td></tr></table></figure></p></li><li><p>删除某层 如果想删除某一层,直接将其删除即可,命令为<figure class="highlight stylus"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs stylus"><span class="hljs-selector-tag">del</span> vgg16<span class="hljs-selector-class">.classifier</span><span class="hljs-selector-attr">[7]</span><br></code></pre></td></tr></table></figure></p></li><li><p>冻结部分层我们现在只想微调最后的fc1层,其他层的参数冻结不训练,可以用<code>requires_grad = True</code>或 <code>False</code> 来控制是否参与梯度回传 <figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-comment"># 冻结fc1层的参数</span><br><span class="hljs-keyword">for</span> name, param <span class="hljs-keyword">in</span> model.named_parameters():<br> <span class="hljs-keyword">if</span> <span class="hljs-string">"fc1"</span> <span class="hljs-keyword">in</span> name:<br> param.requires_grad = <span class="hljs-literal">True</span><br> <span class="hljs-keyword">else</span>:<br> param.requires_grad = <span class="hljs-literal">False</span><br><br><span class="hljs-comment"># 在优化的过程中,只传入需要更新的参数的层给优化器(采用过滤器选出参与梯度回传的层)</span><br>optimizer = optim.SGD(<span class="hljs-built_in">filter</span>(<span class="hljs-keyword">lambda</span> p: p.requires_grad, model.parameters()), lr=<span class="hljs-number">1e-2</span>)<br></code></pre></td></tr></table></figure></p></li></ol><h1 id="完整的训练流程">8 完整的训练流程</h1><p>包括数据集准备,dataLoader准备、网络构建、损失函数定义、循环、计算误差、tensorboard可视化等</p><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch.nn<br><span class="hljs-keyword">import</span> torchvision<br><span class="hljs-keyword">from</span> torch <span class="hljs-keyword">import</span> nn<br><span class="hljs-keyword">from</span> torch.utils.data <span class="hljs-keyword">import</span> DataLoader<br><span class="hljs-keyword">from</span> torch.utils.tensorboard <span class="hljs-keyword">import</span> SummaryWriter<br><br>train_data = torchvision.datasets.CIFAR10(root=<span class="hljs-string">'/data'</span>, train=<span class="hljs-literal">True</span>, transform=torchvision.transforms.ToTensor(),<br> download=<span class="hljs-literal">True</span>)<br>test_data = torchvision.datasets.CIFAR10(root=<span class="hljs-string">'/data'</span>, train=<span class="hljs-literal">False</span>, transform=torchvision.transforms.ToTensor(),<br> download=<span class="hljs-literal">True</span>)<br><br>train_data_size = <span class="hljs-built_in">len</span>(train_data)<br>test_data_size = <span class="hljs-built_in">len</span>(test_data)<br><br>train_data_loader = DataLoader(train_data, batch_size=<span class="hljs-number">64</span>)<br>test_data_loader = DataLoader(test_data, batch_size=<span class="hljs-number">64</span>)<br><br><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MyNeuralNetwork</span>(nn.Module):<br><br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">__init__</span>(<span class="hljs-params">self, *args, **kwargs</span>) -> <span class="hljs-literal">None</span>:<br> <span class="hljs-built_in">super</span>().__init__(*args, **kwargs)<br> <span class="hljs-variable language_">self</span>.model = nn.Sequential(<br> nn.Conv2d(<span class="hljs-number">3</span>, <span class="hljs-number">32</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">32</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Conv2d(<span class="hljs-number">32</span>, <span class="hljs-number">64</span>, <span class="hljs-number">5</span>, <span class="hljs-number">1</span>, <span class="hljs-number">2</span>),<br> nn.MaxPool2d(<span class="hljs-number">2</span>),<br> nn.Flatten(),<br> nn.Linear(<span class="hljs-number">64</span> * <span class="hljs-number">4</span> * <span class="hljs-number">4</span>, <span class="hljs-number">64</span>),<br> nn.Linear(<span class="hljs-number">64</span>, <span class="hljs-number">10</span>)<br> )<br><br> <span class="hljs-keyword">def</span> <span class="hljs-title function_">forward</span>(<span class="hljs-params">self, x</span>):<br> <span class="hljs-keyword">return</span> <span class="hljs-variable language_">self</span>.model(x)<br><br><br>mnn = MyNeuralNetwork()<br><span class="hljs-keyword">if</span> torch.cuda.is_available():<br> mnn = mnn.cuda()<br><br>loss_fn = nn.CrossEntropyLoss()<br>loss_fn = loss_fn.cuda()<br><br>lr = <span class="hljs-number">1e-2</span><br>optim = torch.optim.SGD(mnn.parameters(), lr=lr)<br><br>writer = SummaryWriter(<span class="hljs-string">'../logs_train'</span>)<br>epoch = <span class="hljs-number">10</span><br><span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> <span class="hljs-built_in">range</span>(epoch):<br> mnn.train()<br> train_step = <span class="hljs-number">0</span><br> <span class="hljs-keyword">for</span> data <span class="hljs-keyword">in</span> train_data_loader:<br> imgs, targets = data<br> imgs = imgs.cuda()<br> targets = targets.cuda()<br> outputs = mnn(imgs)<br> loss = loss_fn(outputs, targets)<br><br> optim.zero_grad()<br> loss.backward()<br> optim.step()<br><br> train_step = train_step + <span class="hljs-number">1</span><br> <span class="hljs-keyword">if</span> train_step % <span class="hljs-number">100</span> == <span class="hljs-number">0</span>:<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"训练次数:{},loss = {}"</span>.<span class="hljs-built_in">format</span>(train_step, loss.item()))<br> writer.add_scalar(<span class="hljs-string">"train_loss"</span>, loss.item(), train_step)<br><br> mnn.<span class="hljs-built_in">eval</span>()<br> total_test_loss = <span class="hljs-number">0</span><br> total_accuracy = <span class="hljs-number">0</span><br> total_test_step = <span class="hljs-number">0</span><br> <span class="hljs-keyword">with</span> torch.no_grad():<br><br> <span class="hljs-keyword">for</span> data <span class="hljs-keyword">in</span> test_data_loader:<br> imgs, targets = data<br> imgs = imgs.cuda()<br> targets = targets.cuda()<br> outputs = mnn(imgs)<br> loss = loss_fn(outputs, targets)<br> total_test_loss += loss.item()<br> <span class="hljs-comment"># 求正确率</span><br> accuracy = (outputs.argmax(<span class="hljs-number">1</span>) == targets).<span class="hljs-built_in">sum</span>()<br> total_accuracy += accuracy<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"整体测试集上的loss:{}"</span>.<span class="hljs-built_in">format</span>(total_test_loss))<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"整体测试集上的正确率:{}"</span>.<span class="hljs-built_in">format</span>(total_accuracy / test_data_size))<br> writer.add_scalar(<span class="hljs-string">"test_loss"</span>, total_test_loss, total_test_step)<br> writer.add_scalar(<span class="hljs-string">"test_accuracy"</span>, total_accuracy / test_data_size, total_test_step)<br> total_test_step = total_test_step + <span class="hljs-number">1</span><br><br> <span class="hljs-comment"># 保存模型</span><br> torch.save(mnn, <span class="hljs-string">"mnn{}.pth"</span>.<span class="hljs-built_in">format</span>(i))<br> <span class="hljs-built_in">print</span>(<span class="hljs-string">"模型已保存"</span>)<br><br>writer.close()<br><br></code></pre></td></tr></table></figure><p>运行代码的效果: <figure class="highlight text"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs text">训练次数:100,loss = 2.2861804962158203<br>训练次数:200,loss = 2.2651848793029785<br>训练次数:300,loss = 2.2193050384521484<br>训练次数:400,loss = 2.1087183952331543<br>训练次数:500,loss = 2.0523011684417725<br>训练次数:600,loss = 1.9955447912216187<br>训练次数:700,loss = 1.9990053176879883<br>整体测试集上的loss:319.1352970600128<br>整体测试集上的正确率:0.2669000029563904<br>模型已保存<br></code></pre></td></tr></table></figure> # 8.1 使用GPU训练除了8.1的方式在所有张量用<code>.cuda()</code>送入显存的方式使用GPU训练外,还可以用<code>tensor.to(device)</code>的方式送入显存<figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs python">device = <span class="hljs-string">'cuda'</span> <span class="hljs-keyword">if</span> torch.cuda.is_available() <span class="hljs-keyword">else</span> <span class="hljs-string">'cpu'</span><br><br>net = Net().to(device)<br>imgs = imgs.to(device)<br></code></pre></td></tr></table></figure></p>]]></content>
<categories>
<category>深度学习</category>
</categories>
</entry>
<entry>
<title>Run's Studio 重新出发</title>
<link href="/2025/03/31/hello-world/"/>
<url>/2025/03/31/hello-world/</url>
<content type="html"><![CDATA[<pre><code class="hljs">原先网站的source因为换电脑的缘故没有保存,只能重新开一份博客,记录工作中的心得体会~</code></pre>]]></content>
<categories>
<category>杂谈</category>
</categories>
</entry>
<entry>
<title>安装和配置Pytorch和cuda</title>
<link href="/2025/03/16/%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AEPytorch%E5%92%8Ccuda/"/>
<url>/2025/03/16/%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AEPytorch%E5%92%8Ccuda/</url>
<content type="html"><![CDATA[<h1 id="安装方法">安装方法</h1><p>请参考:https://www.cnblogs.com/tryhardwy/p/14659131.html</p><ol type="1"><li>卸载掉旧版本torch torchvision</li><li>先到(https://pytorch.org/get-started/locally/)查到稳定版本torch对应的cuda</li><li>下载并安装cuda</li><li>到(https://pytorch.org/get-started/locally/)按照对应版本安装torch</li><li>安装完成后,进入python验证。</li></ol><figure class="highlight python"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs python"><span class="hljs-keyword">import</span> torch<br>torch.__version__<br>torch.cuda.is_available()<br></code></pre></td></tr></table></figure><p>显示True则安装成功。</p><h1 id="注意事项">注意事项</h1><p>注意:直接粘贴</p><p><code>pip install torch torchvision torchaudio -f https://download.pytorch.org/whl/torch_stable.html</code>,安装的是cpu版!</p><p>带cuda版本正确的安装语句是:</p><p><code>pip3 install torch==2.6.0+cu118 torchvision==0.21.0+cu118 --index-url https://download.pytorch.org/whl/cu118</code></p><p>如果上述地址下载太慢,还可以换用国内源例如aliyun(备注:阿里云的torch版本不全,部分最新版本无法下载,经过实验2.1.0版本可以下载并安装)<code>pip install torch==2.1.0+cu118 --use-deprecated=legacy-resolver --no-cache-dir -f https://mirrors.aliyun.com/pytorch-wheels/cu118</code></p><p>其他的包 如果国内地址下载太慢,可以用以下常用镜像: <figure class="highlight awk"><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><code class="hljs awk">常见镜像源<br><br>清华:https:<span class="hljs-regexp">//</span>pypi.tuna.tsinghua.edu.cn<span class="hljs-regexp">/simple/</span><br>阿里云:http:<span class="hljs-regexp">//mi</span>rrors.aliyun.com<span class="hljs-regexp">/pypi/</span>simple/<br>中国科技大学:https:<span class="hljs-regexp">//</span>pypi.mirrors.ustc.edu.cn<span class="hljs-regexp">/simple/</span><br>华中科技大学:http:<span class="hljs-regexp">//</span>pypi.hustunique.com<span class="hljs-regexp">/simple/</span><br>上海交通大学:https:<span class="hljs-regexp">//mi</span>rror.sjtu.edu.cn<span class="hljs-regexp">/pypi/</span>web<span class="hljs-regexp">/simple/</span><br></code></pre></td></tr></table></figure>如果使用不带https的连接,还需要加上<code>--trusted-host mirrors.xxx.com</code>例如<code>pip install numpy<=2.0.0 -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com</code></p>]]></content>
<categories>
<category>深度学习</category>
</categories>
</entry>
<entry>
<title>Hexo新建博客并上传Github全流程</title>
<link href="/2025/03/04/hexo%E6%96%B0%E5%BB%BA%E6%93%8D%E4%BD%9C%E5%B9%B6%E4%B8%8A%E4%BC%A0%E6%B5%81%E7%A8%8B/"/>
<url>/2025/03/04/hexo%E6%96%B0%E5%BB%BA%E6%93%8D%E4%BD%9C%E5%B9%B6%E4%B8%8A%E4%BC%A0%E6%B5%81%E7%A8%8B/</url>
<content type="html"><![CDATA[<h1 id="step-1.-安装node.js和git环境">step 1. 安装node.js和git环境</h1><p>可参考教程<ahref="https://blog.csdn.net/suwyer/article/details/125562473%60">node和git的安装以及环境配置(Windows)</a></p><h1 id="step-2.-安装hexo">step 2. 安装hexo</h1><p>此时已经安装好了node.js和git,下面开始hexo的安装首先在某一磁盘目录下创建文件夹,例如F盘,创建文件夹名为 Blog进入Blog文件夹,右键鼠标->选择<code>Git Bash Here</code> 输入<code>npm install -g hexo-cli</code> ,并耐心等待一段时间 输入<code>npm install hexo -save</code>,也耐心等待一段时间此时我们可以发现,在Blog文件夹中多了许多内容在Blog文件夹中新建文件夹,命名为hexo 关闭当前GitBash,进入hexo文件夹,右键鼠标->选择Git BashHere,或者直接在当前的Git Bash中输入 cd hexo 输入hexoinit,初始化hexo环境,耐心等待一段时间 输入npminstall,安装npm依赖包,耐心等待一段时间 输入hexo generate或者是hexog,生成静态页面,耐心等待一段时间 输入hexo server或者是hexos,生成本地服务。我们每次写完博客后,可以先在本地预览一下看看有没有什么问题,然后再发布到网上。接着,我们在浏览器中访问http://localhost:4000/,这就是在本地生成的一个博客。在Git Bash中按下Ctrl+C,可以关闭当前端口服务。</p><p>到这里,我们的本地博客就已经搭建完成了。下面将介绍如何与Github连接,将博客上传Internet# step 3. 新建Github仓库</p><p>登录到自己的Github中,新建一个仓库,命名为username.github.io,其中的username是你的用户名,勾选Initialiazethis repository with a README,创建仓库我们可以访问自己的<code>username.github.io</code>返回<code>username.github.io</code>的仓库中,复制Git地址</p><h1 id="step-4.本地操作">step 4.本地操作</h1><p>我们在<code>/Blog/hexo/</code>文件夹中,找到<code>_config.yml</code>文件,用文本编辑器打开它将最下面的deploy改为下图所示的内容,其中repo的地址就是刚才我们复制的Git地址,修改好后保存退出【注】修改内容中的:和后面的字母之间要有一个空格,否则后续内容会报错接下来,我们暂且不考虑新建文章,在<code>Git Bash</code>中执行<code>npm install hexo-deployer-git</code>–save命令,耐心等待一段时间最后执行<code>hexo deploy</code>或者<code>hexo d</code>【注】这一步需要保证Github上拥有本机的公钥,可以自行查找解决办法最后,成功部署</p><h1 id="step-5.上传博客">step 5.上传博客</h1><h2 id="新建文章">新建文章</h2><p>在GitBash中输入<code>hexo new title</code>,其中,title就是我们这篇文章的名字。我们可以看到,在_posts 文件夹中新建了一个名称为<code>Test1.md</code>的文件我们去编辑一下这个文件,此处需要Linux的部分知识,可以自行上网查找编辑结束后,保存退出</p><h2 id="上传">上传</h2><p>使用hexo g,生成静态文件 使用hexo d来将文档部署到Github上最后我们访问username.github.io,发现刚才编辑的文档已经成功发布到了Internet上面————————————————</p><p>版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。</p><p>原文链接:https://blog.csdn.net/qq_43669381/article/details/107823432</p><p>部署流程(带图版):https://blog.csdn.net/clearloe/article/details/139879493</p><h2 id="乱码问题解决方法">乱码问题解决方法</h2><p>需要注意:如果本地部署没有出现乱码,但是主题上传到github上可能出现乱码,排版不正常的情况,此时要修改<code>__config.yaml</code> 文件,修改: url: https://yourpage.github.io(自己的主页的网址,最后不要有/) root: / (增加这一行)保存后重新生成并部署即可正常显示。</p><h3id="使用vscode更改粘贴图片默认位置">使用vscode更改粘贴图片默认位置</h3><pre><code class="hljs">点击小齿轮,打开设置输入 markdown.copy, 找到 Markdown> Copy Files:Destination新增项, Key为: **/*.md, value为目标路径:assets/${documentBaseName}/${fileName}</code></pre>]]></content>
<categories>
<category>常用知识</category>
</categories>
</entry>
</search>