mxxhcm's blog

  • 首页

  • 标签

  • 分类

  • 归档

ubuntu NVIDIA driver

发表于 2019-04-26 | 更新于 2019-12-17 | 分类于 linux

方法1.命令行安装

步骤

卸载原有驱动
~$:sudo apt purge nvidia*
禁用nouveau
~$:sudo vim /etc/modprobe.d/blacklist.conf
在文件最后添加
blacklist nouveau
更新内核
~$:sudo update-initramfs -u
使用如下命令,如果没有输出,即已经关闭了nouveau
~$:lsmod | grep nouveau
关闭X service
~$:sudo service lightdm stop
接下来执行如下语句即可:

1
2
3
4
5
sudo apt-get install build-essential pkg-config xserver-xorg-dev linux-headers-`uname -r` sudo apt-get install mesa-common-dev
sudo apt-get install freeglut3-dev
sudo chmod a+x NVIDIA-Linux-x86_64-375.66.run
sudo sh NVIDIA-Linux-x86_64-375.66.run -no-opengl-files
sudo reboot

方法2.图形界面

方法3.apt安装

添加apt源

~$:sudo add-apt-repository ppa:graphics-drivers/ppa
~$:sudo apt update

apt安装

~$:sudo ubuntu-drivers devices
~$:sudo ubuntu-drivers autoinstall

安装cuda 9.0

到NVIDIA官网下载cuda 9.0的runfile,然后执行
~$:sudo sh cuda*.run

测试报错

Error: unsupported compiler: 7.4.0. Use --override to override this check.

安装gcc低版本
~$:sudo apt install gcc-6

从CUDA 4.1版本开始,支持gcc 4.5。gcc 4.6和4.7不受支持。
从CUDA 5.0版本开始,支持gcc 4.6。gcc 4.7不受支持。
从CUDA 6.0版本开始,支持gcc 4.7。
从CUDA 7.0版本开始,支持gcc 4.8,在Ubuntu 14.04和Fedora 21上支持4.9。
从CUDA 7.5版开始,支持gcc 4.8,在Ubuntu 14.04和Fedora 21上支持4.9。
从CUDA 8版本开始,Ubuntu 16.06和Fedora 23支持gcc 5.3。
从CUDA 9版本开始,Ubuntu 16.04,Ubuntu 17.04和Fedora 25支持gcc 6。
使用update-alternatives修改默认gcc版本
~$:sudo update-alternatives --install /usr/bin/g++ g++ /usr/bin/g+±6 50
~$:sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-6 50

然后继续安装:
~$:sudo sh cuda*.run

cuda安装在/usr/local/cuda-9.0 目录下
卸载的话进入/usr/loca/cuda-9.0/bin 找到uninstall_cuda_9.0.pl运行卸载。

import tensorflow 报错

ImportError: libcublas.so.9.0: cannot open shared object file: No such file or directory
Failed to load the native TensorFlow runtime.

配置cuda环境变量
在bashrc文件中加入
export PATH=/usr/local/cuda/bin${PATH:+:${PATH}}
export LD_LIBRARY_PATH=/usr/local/cuda/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}
export CUDA_HOME=/usr/local/cuda
执行
~$:source ~/.bashrc

继续报错
然后我才发现我没有装cudnn,按照参考文献[1]安装cudnn即可。
解压cudnn
~$:tar -xvf cudnn-x.x-linuz-x64-vx.x.tar.gz
然后执行

1
2
3
4
sudo cp cuda/include/cudnn.h /usr/local/cuda/include/
sudo cp cuda/lib64/libcudnn* /usr/local/cuda/lib64/
sudo chmod a+r /usr/local/cuda/include/cudnn.h
sudo chmod a+r /usr/local/cuda/lib64/libcudnn*

即可

版本对应

显卡

RTX 20系列显卡,需要使用cuda 10

pytorch

而pytorch目前不支持cuda 10.1,所以只能使用cuda 10.0。

tensorflow

tensorflow 13.1 – cuda 10.0 – cudnn 7.3

参考文献

1.http://gwang-cv.github.io/2017/07/26/Faster-RCNN+Ubuntu16.04+Titan XP+CUDA8.0+cudnn5.0/
2.https://docs.nvidia.com/deeplearning/sdk/cudnn-install/index.html#axzz4qYJp45J2

hexo常见问题(常见问题)

发表于 2019-04-26 | 更新于 2019-12-17 | 分类于 工具

问题1

Error: pandoc exited with code 7: pandoc: Unknown extension: smart

INFO Start processing
FATAL Something’s wrong. Maybe you can find the solution here: http://hexo.io/docs/troubleshooting.html
Error: pandoc exited with code 7: pandoc: Unknown extension: smart
at ChildProcess. (/home/mxxmhh/github/blog/node_modules/hexo-renderer-pandoc/index.js:94:20)
at emitTwo (events.js:126:13)
at ChildProcess.emit (events.js:214:7)
at maybeClose (internal/child_process.js:925:16)
at Socket.stream.socket.on (internal/child_process.js:346:11)
at emitOne (events.js:116:13)
at Socket.emit (events.js:211:7)
at Pipe._handle.close [as _onclose] (net.js:567:12)

解决方法

卸载pandoc
~$:npm un hexo-renderer-pandoc --save

问题2

部分公式无法解析。
是因为markdown和mathjax的解析有一些冲突,按照参考文献$1$中进行修改即可,原因见[2]。
修改node_modules/kramed/lib/rules/inline.js文件,将第11行替换成"escape: /^\([`*[]()#$+-.!_>])/",将第19行替换成"em: /\b_((?:__|[\s\S])+?)_\b|*((?😗*|[\s\S])+?)*(?!*)/"。(不用加双引号)
第一次修改是去掉\的转义。
第二次修改是去掉下划线转义。

问题3

Ubuntu 16.04直接使用命令安装nodejs,版本太老,需要使用源代码安装
~$:sudo apt install nodejs npm
上述命令可以在Ubuntu 18.04直接使用。

问题4

昨天发现博客的一些公式不能渲染,刚开始的时候以为是没有修改kramed文件,就是和问题2一样,后来发现不是,是^符号后面没有设置好。比如

1
$a^{x+y}b$

应该写成

1
$a^{x+y} b$

或者

1
$a\^{x+y}b$

就是要多一个空格才行,否则就会解析错误。
还有就是\可能没转义成功,就用\\去代替\吧。

参考文献

1.https://hexo-guide.readthedocs.io/zh_CN/latest/theme/[NexT]配置MathJax.html
2.https://shomy.top/2016/10/22/hexo-markdown-mathjax/

hexo 安装

发表于 2019-04-26 | 更新于 2019-12-17 | 分类于 工具

安装

安装git

~\$:sudo apt install git

安装nodejs

ubuntu 16.04安装

注意在ubuntu 16.04安装的时候,一直报错,

ERROR Local hexo not found in ~/mxxhcm/mxxhcm.github.io
ERROR Try running: ‘npm install hexo --save’

其实就是安装的nodejs版本太老了。

在官网下载linux 64位nodejs安装包
解压之后放在/usr/local/nodejs目录下。
然后在PATH环境变量中添加/usr/local/nodejs/bin即可(在.bashrc文件中修改即可)。
使用以下命令查看nodejs版本
~\$:node -v

ubuntu 18.04安装

在ubuntu 18.04可以直接使用以下命令安装。
安装nodejs
~\$:sudo apt install nodejs
安装npm
~\$:sudo apt install npm

安装hexo

~\$:sudo npm install -g hexo-cli

配置

以下二选一
创建文件夹
~\$:git clone your repo
或者直接
~\$:hexo init your repo

安装依赖包
~\$:npm install
解决问题
参见参考文献

参考文献

1.https://www.cnblogs.com/SHOR/p/9581661.html
如果换了主题,需要在主题的_config.yml文件修改。

pytorch Module.children() vs Module.modules()

发表于 2019-04-25 | 更新于 2019-05-08 | 分类于 pytorch

Module.modules()

modules()会返回所有的模块,包括它自己。
如下代码所示:

1
2
3
4
5
6
7
import torch.nn as nn


model = nn.Sequential(nn.Linear(5, 3), nn.Sequential(nn.Linear(3, 2)))

for module in model.modules():
print(module)

输出如下:

Sequential(
(0): Linear(in_features=5, out_features=3, bias=True)
(1): Sequential(
(0): Linear(in_features=3, out_features=2, bias=True)
)
)
Linear(in_features=5, out_features=3, bias=True)
Sequential(
(0): Linear(in_features=3, out_features=2, bias=True)
)
Linear(in_features=3, out_features=2, bias=True)

可以看出来,上面总共含有四个modules。

Module.children()

而children()不会返回它自己。
如下代码所示:

1
2
3
4
5
6
7
import torch.nn as nn


model = nn.Sequential(nn.Linear(5, 3), nn.Sequential(nn.Linear(3, 2)))

for child in model.children():
print(child)

输出如下:

Linear(in_features=5, out_features=3, bias=True)
Sequential(
(0): Linear(in_features=3, out_features=2, bias=True)
)

可以看出来,上面只给出了Sequential里面的modules。

完整代码

https://github.com/mxxhcm/myown_code/blob/master/pytorch/tutorials/module_vs_children.py

参考文献

1.https://discuss.pytorch.org/t/module-children-vs-module-modules/4551/2

python defaultdict

发表于 2019-04-25 | 更新于 2019-05-07 | 分类于 python

使用defaultdict创建字典的值默认类型

使用defaultdict创建值类型为dict的字典

如下示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from collections import defaultdict

ddd = defaultdict(dict)
print(ddd)

m = ddd['a']
m['step'] = 1
m['exp'] = 3
print(type(m))
print(ddd)

m = ddd['b']
m['step'] = 1
m['exp'] = 3
print(ddd)

上述代码创建了一个dict,dict的value类型还是一个dict

defaultdict(class ‘dict’&gt , {})
&lt class ‘dict’&gt
defaultdict(&lt class ‘dict’&gt , {‘a’: {‘step’: 1, ‘exp’: 3}})
defaultdict(&lt class ‘dict’&gt , {‘a’: {‘step’: 1, ‘exp’: 3}, ‘b’: {‘step’: 1, ‘exp’: 3}})

使用defaultdict创建值类型为list的dict

如下示例

1
2
3
4
5
6
7
8
9
from collections import defaultdict

ddl = defaultdict(list)
print(ddl)
m = ddl['a']
print(type(m))
m.append(3)
m.append('hhhh')
print(ddl)

上述代码创建了一个dict,dict的value类型是一个list,输出如下

defaultdict(&lt class ‘list’&gt , {})
&lt class ‘list’&gt
defaultdict(&lt class ‘list’&gt , {‘a’: [3, ‘hhhh’]})

代码

点击获得完整代码

参考文献

1.http://www.cnblogs.com/dancesir/p/8142775.html

hexo 博客迁移教程

发表于 2019-04-23 | 更新于 2019-12-17 | 分类于 工具

hexo博客迁移

详细内容见参考文献

参考文献

1.https://www.jianshu.com/p/fceaf373d797

python multiprocessing

发表于 2019-04-23 | 更新于 2019-12-17 | 分类于 python

multiprocessing vs multithread

多个threads可以在一个process中。同一个process中的所有threads共享相同的memory。而不同的processes有不同的memory areas,每一个都有自己的variables,进程之间为了通信,需要使用其他的channels,比如files, pipes和sockets等。thread比process更容易创建和管理,thread之间的交流比processes之间的交流更快。
这一节首先介绍一些GIL,然后介绍两个python的package,一个是threading,一个是multiprocessing。threading主要提供了多线程的实现。multiprocessing 主要提供了多进程的实现,当然也有多线程实现。

GIL

thread有一个东西,叫做GIL(Global Interpreter Lock),阻止同一个process中不同threads的同时运行,所以python多线程并不是多线程。举个例子,如果你有8个cores,使用8个threads,CPU的利用率不会达到800%,也不会快8倍。它会使用100%CPU,速度和原来相同,甚至会更慢,因为需要对多个threads进行调度。当然,有一些例外,如果大量的计算不是使用python运行的,而是使用一些自定义的C code进行GIL handling,就会得到你想要的性能。对于网络服务器或者GUI应用来说,大部分的事件都在等待,而不是在计算,这个时候就可以使用多个thread,相当于把他们都放在后台运行,而不需要终止相应的主线程。
如果想用纯python代码进行大量的CPU计算,使用threads并不能起到什么作用。使用process就没有GIL的问题,每个process有自己的GIL。这个时候需要在多线程和多进程之间做个权衡,因为进程之间的通信比线程之间通信的代价大得多。

CPython的GIL实现

CPython 2.7中GIL是这样一行代码:

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
static PyThread_type_lock interpreter_lock = 0; /* This is the GIL */

from multiprocessing import Process, Queue
import os,random,time


def proc1(name):
print("Run child process %s (%s)" % (name,os.getpid()))
print(time.time())
time.sleep(random.random())
print("%s end" % (name))
return "return A"

def proc2(name):
print("Run child process %s (%s)" % (name,os.getpid()))
print(time.time())
time.sleep(random.random())
print("%s end" % (name))
return "return B"

def proc(length, output):
result = "Hello! " + str(length) + "!"
time.sleep(random.random())
output.put(result)

if __name__ == '__main__':
# 1. mp.Process
print("Parent process %s" % os.getpid())
p1 = Process(target=proc1, args=('p1',))
p2 = Process(target=proc2, args=('p2',))
print("child processes will start.")
p1.start()
p2.start()
# 上面两行代码意思是p1.start()有返回值时,开始执行p2.start()。p1.start()有返回值并不是说p1执行完了
p1.join()
p2.join()
# 上面两行代码中,p1.join()执行完之后才会执行p2.join()。所以只有p1执行完之后,p2才能尝试结束。。
# The interpreter will, however, wait until P1 finishes before attempting to wait for P2 to finish.
print("child processes end.")


# 2.获得mp.Process的返回值
print("# 2.获得mp.Process的返回值")
output = Queue()
processes = [Process(target=proc, args=(x, output)) for x in range(4)]
for p in processes:
p.start()

for p in processes:
p.join()

results = [output.get() for p in processes]
print(results)

# https://stackoverflow.com/questions/31711378/python-multiprocessing-how-to-know-to-use-pool-or-process

在Unix类系统中,PyThread_type_lock是标准的C lock mutex_t的别名。它的初始化方式如下:

1
2
3
4
5
6
void
PyEval_InitThreads(void)
{
interpreter_lock = PyThread_allocate_lock();
PyThread_acquire_lock(interpreter_lock);
}

解释器中执行python的C代码必须持有这个lock。GIL的作用就是让你的程序足够简单:一个thread执行python代码,其他N个thread sleep或者等待I/O。或者可以等待threading.Lock或者其他同步操作。
那么什么时候threads进程切换呢?当一个thread 准备sleep或者进入等待I/O的时候,它释放GIL,其他thread请求GIL,执行相应的代码。这种任务叫做cooperative multitasking。还有一种是preemptive multitasking:在python2中一个thread不间断的执行1000个bytecode,或者python3中不间断的执行15 ms,然后放弃GIL让另一个thread运行。接下来举两个例子。

cooperative multithread

在网络I/O中,具有很强的不确定性,当一个拥有GIL的thread请求网络I/O时,它释放GIL,这样子其他thread可以获得GIL继续执行,等到I/O完成时,该thread请求GIL继续执行。

1
2
3
4
5
6
7
def do_connect():
s = socket.socket()
s.connect(('python.org', 80)) # drop the GIL

for i in range(2):
t = threading.Thread(target=do_connect)
t.start()

在上面的例子中,同一时刻只能有一个拥有GIL的thread执行python代码,但是一旦拥有GIL的thread开始connect,它就drop GIL,另一个thread可以申请GIL。但是所有的threads都可以drop GIL,也就是多个thread可以一起并行的等待sockets连接。
具体python在connect socket的时候是怎么drop GIL的,我们可以看一下socketmodule的c代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/* s.connect((host, port)) method */
static PyObject *
sock_connect(PySocketSockObject *s, PyObject *addro)
{
sock_addr_t addrbuf;
int addrlen;
int res;

/* convert (host, port) tuple to C address */
getsockaddrarg(s, addro, SAS2SA(&addrbuf), &addrlen);

Py_BEGIN_ALLOW_THREADS
res = connect(s->sock_fd, addr, addrlen);
Py_END_ALLOW_THREADS

/* error handling and so on .... */
}

其中Py_BEGIN_ALLOW_THREADS宏就是drop GIL,它的定义如下:

1
PyThread_release_lock(interpreter_lock);

同样,Py_END_ALLOW_THREADS宏是请求GIL。thread可以在这里block,等待GIL被释放,申请GIL继续执行。

preemptive multithread

除了自动释放GIL外,还可以强制的释放GIL。python代码的执行有两步,第一步将python源代码编译成二进制的bytecode;第二步,python interpreter的main loop,一个叫做PyEval_EvalFrameEx()的函数,读取bytecode,并且一个一个的执行。
在多线程的模式下,interpreter强制周期性的drop GIL。如下所示,是thread判断是否释放GIl的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
for (;;) {
if (--ticker < 0) {
ticker = check_interval;

/* Give another thread a chance */
PyThread_release_lock(interpreter_lock);

/* Other threads may run now */

PyThread_acquire_lock(interpreter_lock, 1);
}

bytecode = *next_instr++;
switch (bytecode) {
/* execute the next instruction ... */
}
}

默认设置下是1000个bytecode。所有的threads周期性的获取GIL,然后释放。在python3下,所有thread获得15ms的GIL,而不是1000个bytecode。

python的thread safety

但是,如果买票等之类的,必须保证操作的atomic,否则就会出现问题。对于sort() operation来说,它是atomic,所以无序担心。看下面一个code snippet

1
2
3
4
5
n = 0

def foo():
global n
n += 1

我们查看foo对应的bytecode:

1
2
3
4
5
6
7
8
9
10
import dis

print(dis.dis(foo))

# 7 0 LOAD_GLOBAL 0 (n)
# 2 LOAD_CONST 1 (1)
# 4 INPLACE_ADD
# 6 STORE_GLOBAL 0 (n)
# 8 LOAD_CONST 0 (None)
# 10 RETURN_VALUE

可以看出,foo有6个bytecode,如果在第三个bytecode处,强制释放了GIL锁,其他thread改了n的值,等到切回这个thread的时候,就会出错。。所以,为了保证不出问题,需要手动加一个lock,保证不会在这个时候释放GIL。

1
2
3
4
5
6
7
8
9
import threading

n = 0
lock = threading.Lock()

def foo():
global n
with lock:
n += 1

当然,如果operation本身就是atomic的话,就不需要了。

1
2
3
4
l = [1, 3, 5, 2]

def foo():
l.sort()

threading

threading是python多线程的一个package。

threading.Thread

代码示例

代码地址

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
import threading
import socket
import os

def do_connect(website):
s = socket.socket()
info = s.connect((website, 80)) # drop the GIL
print(type(info))
print(info)
print(os.getpid())

websites = ['python.org', 'baidu.com']

job_list = []
for i in range(len(websites)):
job_list.append(threading.Thread(target=do_connect, args=(websites[i],)))

for t in job_list:
t.start()

# join表示阻塞,一直到当前任务完成为止,如果不加的话,就会立刻执行下面的print语句
for t in job_list:
t.join()

print("Done")

threading.Lock

代码示例

代码地址

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
import time
import threading
import random

hhhh = 100
lock = threading.Lock()

def add_number():
global hhhh
for i in range(100):
with lock:
hhhh += 1
print("add: ", hhhh)
time.sleep(0.015)

def subtract_number():
global hhhh
for i in range(100):
with lock:
hhhh -= 1
print("subtract:", hhhh)
time.sleep(0.015)

job_list = []
job_list.append(threading.Thread(target=subtract_number, args=()))
job_list.append(threading.Thread(target=add_number, args=()))

for t in job_list:
t.start()

for t in job_list:
t.join()

print("Done")

multiprocessing

概述

方法| 并行|是否直接阻塞|目标函数|函数返回值|适用场景
–|--|–|--|–
mp.Pool.apply|否|是|只能有一个函数|函数返回值
mp.Pool.apply_async|是|否,调用join()进行阻塞|可以相同可以不同|返回AysncResult对象
mp.Pool.map|是|是|目标函数相同,参数不同|所有processes完成后直接返回有序结果
mp.Pool.map_async|是|否,调用join()阻塞|不知道。。|返回AysncResult对象
mp.Process|是|否|可以相同可以不同|无直接返回值|适用于线程数量比较小

mp.Pool适用于线程数量远大于cpu数量,mp.Process适用于线程数量小于或者等于cpu数量的场景。
mp.Pool.apply 适用于非并行,调用apply()直接阻塞,process执行结束后直接返回结果。
mp.Pool.apply_async 适用于并行,异步执行,目标函数可以相同可以不同,返回AysncResult对象,因为AsyncResult对象是有序的,所以调用get得到的结果也是有序的。调用join()进行阻塞,调用get()方法获得返回结果,get()方法也是阻塞方法。
mp.Pool.map 适用于并行,异步,目标函数相同,参数不同。调用map()函数直接阻塞,等待所有processes完成后直接返回有序结果。
mp.Pool.map_async 也是调用join()和get()都能阻塞。
mp.Process 适用于并行,异步,目标函数可以相同可以不同,返回的结果需要借助mp.Queue()等工具,mp.Queue()存储的结果是无序的,mp.Manager()存储的结果是有序的。无序的结果可以使用特殊方法进行排序。

统计cpu数量

1
cpus = mp.cpu_count()

实现并行的几种常用方法

1
2
3
4
5
6
# 方式1
pool.apply_async
# 方式2
pool.map
# 方式3
mp.Process

retrieve并行结果

1
2
3
4
5
6
7
8
9
10
# 方式1
results_obj = [pool.apply_async(f, args=(x,)) for x in range(3)]
results = [result_obj.get() for result_obj in results_obj]

# 方式2
results = pool.map(f, range(7))

# 方式3
output = Queue()
pool.Process(target=f, args=(output))

mp.Pool

简介

指定占用的CPU核数,进程的个数可以多于CPU的核数,Pool会负责调用。如果CPU核数小于进程数,一般遵循FIFO的原则进行调用。

API

  • Pool.apply,
  • Pool.apply_async,
  • Pool.map,
  • Pool.map_async。

python apply

在老版本的python中,调用具有任意参数的function要使用apply函数,

1
apply(f, args, kwargs)

甚至在2.7版本中还存在apply函数,但是基本上不怎么用了,3版本中已经没有了这种形式,现在都是直接使用函数名:

1
f(*args, **kwargs)

mp.Pool.apply vs mp.Pool.apply_async

multiprocessing.Pool中也有类似的interface。Pool.apply和python内置的apply挺像的,只不过Pool.apply会在一个单独的process执行,并且该函数会阻塞直到进程调用结束,所以Pool.apply不能异步执行。可以使用apply_async使用多个workers并行处理。
Pool.apply_async和apply基本一样,只不过它会在调用后立即返回一个AsyncResult对象,不用等到进程结束再返回。然后使用get()方法获得函数调用的返回值,get()方法会阻塞直到process结束。也就是说Pool.apply(func, args, kwargs)和pool.apply_async(func, args, kwargs).get()等价。Pool.apply_async可以调用很多个不同的函数。
Pool.apply_async返回值是无序的。

mp.Pool.map vs mp.Pool.map_async

Pool.map应用于同一个函数的不同参数,它的返回值顺序和调用顺序是一致的。Pool.map(func, iterable)和Pool.map_async(func, iterable).get()是一样的。

mp.Pool.map vs mp.Pool.apply

Pool.apply(f, args): f函数仅仅被process pool中的一个worker执行。
Pool.map(f, iterable): 将iterable分割成多个单独的task,就是相当于同一个函数,给定不同的参数,每一组是一个task,然后使用pool中所有的processes执行这些taskes。所以map也能实现并行处理,而且是有序结果。

mp.Pool.map vs mp.Pool.apply_async

Pool.map返回的结果是有序的;
Pool.apply_async返回的结果是无序的。
Pool.map处理相同的函数,不同的参数;

pool.map() is a completely different kind of animal, because it distributes a bunch of arguments to the same function (asynchronously), across the pool processes, and then waits until all function calls have completed before returning the list of results.
Pool.apply_async处理不同的参数。

retrieve return value

Pool.apply()会直接返回结果。
Pool.apply_async()会返回一个AsyncResult,然后使用get()方法获得结果。

其他问题

pool.map传递多个参数,或者重复参数,使用他的另一个版本,pool.starmap()
如下示例,代码地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from multiprocessing import Pool
import time
import os
from itertools import repeat


def f(string, x):
print(string)
return x*x

if __name__ == "__main__":
with Pool(processes=4) as pool:
number = 10
s = "hello"
print(pool.starmap(f, zip(repeat(s), range(number))))

使用流程

  1. 创建Pool进程池,指定cpu核数
    pool = Pool(cpu_core)
  2. 使用apply_async添加进程
    processes = [p1, p2, p3]
    results = []
    for p in processes:
    results.append(pool.apply_async(p, args=()))
  3. 关闭进程池
    pool.close()
  4. 等待所有进程执行完毕
    pool.join()
  5. 访问结果
    for res in results:
    print(res.get())

代码示例

代码地址

mp.Process

简介

每个进程占用一个CPU核。

retrieve结果

使用mp.Queue()或者mp.Pipe()等对象记录结果。Queue()不保证结果的顺序和task的执行顺序一致。

使用流程

代码示例

代码地址

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
 
from multiprocessing import Process, Queue
import os,random,time


def proc1(name):
print("Run child process %s (%s)" % (name,os.getpid()))
print(time.time())
time.sleep(random.random())
print("%s end" % (name))
return "return A"

def proc2(name):
print("Run child process %s (%s)" % (name,os.getpid()))
print(time.time())
time.sleep(random.random())
print("%s end" % (name))
return "return B"

def proc(length, output):
result = "Hello! " + str(length) + "!"
time.sleep(random.random())
output.put(result)

if __name__ == '__main__':
# 1. mp.Process
print("Parent process %s" % os.getpid())
p1 = Process(target=proc1, args=('p1',))
p2 = Process(target=proc2, args=('p2',))
print("child processes will start.")
p1.start()
p2.start()
# 上面两行代码意思是p1.start()有返回值时,开始执行p2.start()。p1.start()有返回值并不是说p1执行完了
p1.join()
p2.join()
# 上面两行代码中,p1.join()执行完之后才会执行p2.join()。所以只有p1执行完之后,p2才能尝试结束。。
# The interpreter will, however, wait until P1 finishes before attempting to wait for P2 to finish.
print("child processes end.")


# 2.获得mp.Process的返回值
print("# 2.获得mp.Process的返回值")
output = Queue()
processes = [Process(target=proc, args=(x, output)) for x in range(4)]
for p in processes:
p.start()

for p in processes:
p.join()

results = [output.get() for p in processes]
print(results)

# https://stackoverflow.com/questions/31711378/python-multiprocessing-how-to-know-to-use-pool-or-process

mp.Pool vs mp.Process

  1. Pool会负责对cpu进行调度,即tasks数量可以远大于worker数量,一个worker占用一个cpu核。而Process的task必须小于worker,每个worker只能运行一个task。
  2. 如果执行多个task的时候,Process一定会使用多个seperate workes,但是对于Pool来说,可能会使用同一个worker去执行多个task。如下示例,p1和p2一定是两个wrokers运行两个process,而pool中,pool中有两个worker,foo可以是第一个worker也可以是第二个worker运行的process解决的,而bar也可以是这两个中任意一个worker解决的,这种情况发生在foo已经运行结束了,两个worker都是空闲的,给bar任意分配一个worker。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def foo():
pass

def bar():
pass

p1 = Process(target=foo, args=())
p2 = Process(target=bar, args=())

p1.start()
p2.start()
p1.join()
p2.join()

pool = Pool(processes=2)
r1 = pool.apply_async(foo)
r2 = pool.apply_async(bar)

代码示例

代码地址

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
from multiprocessing import Process,Pool
import os,random,time


def run_proc(name):
print("Run child process %s (%s)" % (name,os.getpid()))


def long_time_task(name):
print("Run task %s (%s)" % (name, os.getpid()))
start = time.time()
time.sleep(random.random()*3)
end = time.time()
print("task %s runs %0.2f seconds." % (name,end-start))


if __name__ == '__main__':
# 1.Process
print("Parent process %s" % os.getpid())
p = Process(target=run_proc, args=('test',))
print("child process will start.")
p.start()
p.join()
print("child process end.")

# 2.Pool
print("Paranet Process %s" % os.getpid())
pool = Pool(4)
for i in range(5):
pool.apply_async(long_time_task,args=(i,))
print("Waitting for done.")
pool.close() # 回收Pool
pool.join()
print("All subprocesses done")

multiprocessing join方法

简介

用来阻塞当前进程,直到该进程执行完毕,再继续执行后续代码。

代码示例

代码地址

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
from multiprocessing import Process,Pool
import os,random,time


def run_proc(name):
time.sleep(2)
print("Run child process %s (%s)" % (name,os.getpid()))


if __name__ == '__main__':
print("==========1. join==========")
print("Parent process %s" % os.getpid())
p = Process(target=run_proc, args=('test',))
print("child process will start.")
p.start()
p.join()
print("child process end.")


print("==========2. no join==========")
print("Parent process %s" % os.getpid())
p = Process(target=run_proc, args=('test',))
print("child process will start.")
p.start()
# p.join()
print("child process end.")

可以看出来,调用join()函数的时候,会等子进程执行完之后再继续执行;而不使用join()函数的话,在子进程开始执行的时候,就会继续向后执行了。

参考文献

1.https://www.cnblogs.com/lipijin/p/3709903.html
2.https://www.ellicium.com/python-multiprocessing-pool-process/
3.https://stackoverflow.com/questions/8533318/multiprocessing-pool-when-to-use-apply-apply-async-or-map<mp Pool apply, apply_async, map用法>
4.https://stackoverflow.com/questions/31711378/python-multiprocessing-how-to-know-to-use-pool-or-process<mp Process和Pool.map获得不同目标函数process的结果,对mp.Process无序结果进行排序>
5.https://stackoverflow.com/questions/18176178/python-multiprocessing-process-or-pool-for-what-i-am-doing<mp Pool.apply_async, Process不同函数的多process>
6.https://stackoverflow.com/questions/10415028/how-can-i-recover-the-return-value-of-a-function-passed-to-multiprocessing-proce<获得传递给mp Process函数返回值的方法>
7.https://docs.python.org/3/library/multiprocessing.html#sharing-state-between-processes
8.https://sebastianraschka.com/Articles/2014_multiprocessing.html
9.https://opensource.com/article/17/4/grok-gil<GIL解释>

Asynchronous Methods for Deep Reinforcement Learning

发表于 2019-04-19 | 更新于 2019-12-17 | 分类于 强化学习

摘要

DQN使用experience replay buffer来稳定学习过程。本文提出一个异步框架来代替buffer,稳定学习过程。这个框架同时适用于on-policy和off-policy环境,也能应用于离散动作空间和连续的动作空间,既能训练前馈智能体,也能训练循环智能体。

Introduction

强化学习算法一般都是online的,而online学习是不稳定的,并且online更新通常都是强相关的。DQN通过引入experience replay buffer解决了这个问题,但是DQN只能应用在off policy算法上。DQN通过引入replay buffer取得了很大成功,但是replay buffer还有以下的几个缺点:

  • 在每一步交互的时候使用了更多的内存和计算资源
  • 它只能应用在off policy的算法上,也就是说权重的更新可能会使用到很久之前的数据。

这篇文章提出不使用replay buffer,而是使用异步的框架,同时在多个相同的环境中操作多个智能体(每个环境中一个智能体)并行的采集数据。这种并行性也能将智能体的数据分解成更稳定的过程(即和experience replay buffer起到了相同的作用),因为在给定的一个时间步,智能体可能会experience很多个不同的states。
这个框架既可以应用在on policy算法,如Sarsa,n-step methods和actor-critc等方法上,也可以应用在off policy算法如Q-learning上。

异步框架

作者给出了一个框架,将够将on-policy search的actor-critic方法以及off-policy value-based的Q-learning方法都包括进去。
具体的,使用一台机器上的多CPU线程,这样子可以避免在不同机器上传递参数和梯度的消耗。然后,多个并行的actor-learner可能会探索环境的不同部分,每个actor-learner可以设置不同的exploration policy。不同的thread运行不同的exploration policy,多个actor-learner并行执行online update可能比单智能体更新在时间上更不相关。所以这里使用了不同的探索策略取代了DQN中buffer稳定学习过程的作用。
除了稳定学习过程之外,多个actor-learner还可以减少训练时间,此外,不使用buffer以后还可以使用on-policy的方法进行训练。

总的来说,下面要介绍的四个算法,前面三个算法都使用了target network,第四个A3C算法没有使用target network。最重要的是所有四个算法都使用了多个actor-learner进行训练,并且使用累计的梯度进行更新(相当于batch的作用)。总共出现了三类参数,一类是network参数,一类是target network参数,一类是thread-specific(每个线程的参数)的参数。thread-specific参数是每个线程自己持有的,通过更新每个线程的参数更新network的参数,然后使用network的参数更新target network的参数,target network参数比network参数更新的要慢很多。
A3C算法的实质就是在多个线程中同步训练。分为主网络和线程中的网络,主网络不需要训练,主要用来存储和传递参数,每个线程中的网络用来训练参数。总的来说,多个线程同时训练提高了效率,另一方面,减小了数据之间的相关性,比如,线程$1$和$2$中都用主网络复制来的参数计算梯度,但是同一时刻只能有一个线程更新主网络的参数,比如线程$1$更新主网络的参数,那么线程$2$利用原来主网络参数计算的梯度会更新在线程$1$更新完之后的主网络参数上。

异步的one-step Q-learning

  • 每个thread都和它自己的环境副本进行交互,在每一个时间步计算Q-learning loss的梯度。
  • 通过使用不同的exploration策略,可以改进性能,这里实现exploration policy不同的方式就是使用$\epsilon$的不同取值实现。
  • 使用一个共享的更新的比较缓慢的target network,就是和DQN中的target network一样。
  • 同时也使用多个时间步上的累计梯度,和batch挺像的,这就减少了multi actor learner重写其他更新的可能性,同时也在计算效率和数据效率方面做了一个权衡。

伪代码

Algorithm 1 异步的one-step Q-learning--每个actor-learn线程的伪代码
用$\theta,\theta^{-}$表示全局共享参数,计数器$T=0$,
初始化线程时间步计数器$t\leftarrow 0$,
初始化target network权重$\theta^{-} \leftarrow 0$,
初始化network梯度$d\theta\leftarrow 0$,
初始化,得到初始状态$s$,
repeat
$\qquad$使用$\epsilon-$greedy策略采取action $a$,
$\qquad$接收下一个状态$s’$和reward $r$,
$\qquad$设置target value,$y=\begin{cases}r,&for\ terminal\ s’ \\ r+\gamma max_{a’}Q(s’,a’;\theta^{-}), &for\ non-terminal\ s’\end{cases}$
$\qquad$累计和$\theta$相关的梯度:$d\theta \leftarrow d\theta+\frac{\partial (y-Q(s,a;\theta))^2}{\partial \theta}$
$\qquad s\leftarrow s’$
$\qquad T\leftarrow T+1, t\leftarrow t+1$
$\qquad$if $T\ \ mod\ \ I_{target} ==0 $,那么
$\qquad\qquad$更新target network $\theta^{-}\leftarrow 0$
$\qquad$end if
$\qquad$if $t\ \ mod\ \ I_{AsyncUpdate} ==0$或者$s$是terminal state,那么
$\qquad\qquad$使用$d\theta$异步更新$\theta$
$\qquad\qquad$将累计梯度$d\theta\leftarrow 0$
$\qquad$end if
until $T\ge T_{max}$

异步的one-step Sarsa

概述

  • 和算法$1$很像,$Q-learning$计算target value使用$r+\gamma max_{a’}Q(s’,a’;\theta^{-})$,而Sarsa计算target value使用$r+\gamma Q(s’,a’;\theta^{-})$,即Q-learning的bahaviour policy和评估的策略是不一样的,而Sarsa的behaviour policy和评估策略是一样的。
  • 使用target network,
  • 同时使用多个时间步的累计梯度更新用来稳定学习过程。

伪代码

和算法$1$很像。

异步的n-step Q-learning

概述

  • 计算$n-step$的return
  • 在计算一次更新的时候,使用exploration policy采样到$t_{max}$步或者到terminal state。然后累加从上次更新到$t_{max}$时间步的reward。
  • 然后计算$n-step$更新对于上次更新之后所有state-action的梯度。
  • 使用单个时间步中的累计梯度进行更新。
  • 使用了target network。

伪代码

Algorithm 2 异步的n-step Q-learning算法--每个actor-learner线程的伪代码
用$\theta,\theta^{-}$表示全局共享的network参数和target network参数,用$T=0$表示全局共享计数器。
初始化线程步计数器$t\leftarrow 1$,
初始化target network参数$\theta^{-}\leftarrow \theta$
初始化每个线程的参数参数$\theta^{-}\leftarrow \theta$
初始化网络梯度$d\theta\leftarrow 0$
repeat
$\qquad$重置累计梯度$d\theta\leftarrow0$
$\qquad$同步每个线程的参数$\theta’=\theta$
$\qquad t_{start}=t$
$\qquad$得到$s_t$
$\qquad$repeat
$\qquad\qquad$根据基于$Q(s_t,a;\theta’)$的$\epsilon-greedy$策略执行动作$a_t$,
$\qquad\qquad$接收下一个状态$s_{t+1}$和reward $r_t$,
$\qquad\qquad T\leftarrow T+1, t\leftarrow t+1$
$\qquad$ until terminal $s_t$或者$t-t_{start}==t_{max}$
$\qquad$设置奖励$R=\begin{cases}0,&for\ terminal\ s_t\max_aQ(s_t,a;\theta^{-}), &for\ non-terminal\ s_t\end{cases}$
$\qquad$for $i\in{t-1,\cdots,t_{start}}$ do
$\qquad\qquad R\leftarrow r_i+\gamma R$
$\qquad\qquad$累计和$\theta’$相关的梯度:$d\theta \leftarrow d\theta+\frac{\partial (R-Q(s_t,a;\theta’))^2}{\partial \theta’}$
$\qquad$end for
$\qquad$使用$d\theta$异步更新$\theta$.
$\qquad$if$\quad T\quad mod\quad I_{target}==0$那么
$\qquad\qquad\theta^{-}\leftarrow \theta$
$\qquad$end if
until $T\gt T_{max}$

异步的advantage actor-critic

概述

  • A3C算法,是一个on-policy的actor-critic方法,使用值函数$V(s_t;\theta_v)$辅助学习policy $\pi(a_t|s_t;\theta)$,同时这里使用$n-step$的returns更新policy和value function。
  • 每隔$t_{max}$个action更新一次或者到了terminal state更新一次。
  • Actor的更新方向为$\nabla_{\theta’}log\pi(a_t|s_t;\theta’)A(s_t,a_t;\theta,\theta_v)$,其中$A$是advantage function的一个估计,通过$\sum_{i=0}^{k-1} \gamma^ir_{t+i}+\gamma^kV(s_{t+k};\theta_v) - V(s_t;\theta_v)$计算。
  • 这里同样使用并行的actor-learner和累计的梯度用来稳定学习。$\theta$和$\theta_v$在实现上通常共享参数。
  • 添加entropy正则项鼓励exploration。包含了正则化项的的objective function的梯度为$\nabla_{\theta’}log\pi(a_t|s_t;\theta’)(R_t-V(s_t;\theta_v))+\beta\nabla_{\theta’}H(\pi(s_t;\theta’))$。这里的$R$就是上面的$\sum_{i=0}^{k-1}\gamma^ir_{t+i}+\gamma^kV(s_{t+k};\theta_v) - V(s_t;\theta_v)$。
  • Critic的更新方向通过最小化loss来实现,这里的loss指的是TD-error,即$\sum_{i=0}^{k-1}\gamma^ir_{t+i} + \gamma^kV(s_{t+k};\theta_v) - V(s_t;\theta_v)$。
  • 没有使用target network。

伪代码

Algorithm 3 A3C--每个actor-learn线程的伪代码
用$\theta,\theta_v$表示全局共享参数,用$T=0$表示全局共享计数器,
用$\theta’,\theta’_v$表示每个线程中的参数
初始化线程步计数器$t\leftarrow 1$,
repeat
$\qquad$重置梯度$d\theta\leftarrow 0,d\theta_v\leftarrow 0$,
$\qquad$同步线程参数$\theta’=\theta,\theta’_v=\theta_v$
$\qquad t_{start}=t$
$\qquad$得到状态$s_t$,
$\qquad$repeat
$\qquad\qquad$根据策略$\pi(a_t|s_t;\theta’)$执行动作$a_t$,
$\qquad\qquad$接收下一个状态$s_{t+1}$和reward $r_t$,
$\qquad\qquad T\leftarrow T+1, t\leftarrow t+1$
$\qquad$ until terminal $s_t$或者$t-t_{start}==t_{max}$
$\qquad$设置奖励$R=\begin{cases}0,&for\ terminal\ s_t\\ V(s_t,\theta’_v), &for\ non-terminal\ s_t\end{cases}$
$\qquad$for $i\in{t-1,\cdots,t_{start}}$ do
$\qquad\qquad R\leftarrow r_i+\gamma R$
$\qquad\qquad$累计和$\theta’$相关的梯度:$d\theta \leftarrow d\theta+\frac{\partial (y-Q(s,a;\theta))^2}{\partial \theta}$
$\qquad\qquad$累计和$\theta’_v$相关的梯度:$d\theta_v \leftarrow d\theta_v+\frac{\partial (R-V(s_i;\theta’_v))^2}{\partial \theta’_v}$
$\qquad$end for
$\qquad$使用$d\theta$异步更新$\theta$,使用$d\theta_v$异步更新$\theta_v$.
until $T\ge T_{max}$

优化方法

作者尝试了三种不同的优化方法,带有momentum的SGD,带有共享statistics的RMSProp以及不带shared statistics的RMSProp。

实验

优化细节

作者在异步框架中测试了两个优化算法SGD和RMSProp,并且因为效率原因没有使用线程锁。

设置

  • Atari环境中,每个实验使用$16$个actor-learner线程。
  • 所有方法都每隔$5$个actions更新一次,并且使用共享的RMSProp进行优化。
  • 三个异步的value-based算法使用每隔$40000$帧更新的共享target network,
  • 使用了DQN中action repeat of $4$.
  • 网络架构和DQN一样
  • 基于值的方法只有一个线性输出层,每个输出单元代表一个action的值。
  • actor-critic方法有两个输出层,一个softmax表示选择某一个action的概率,一个线性输出代表值函数。
  • 所有实验使用的$\gamma=0.99$,RMSProp的衰减因子$\alpha = 0.99$。
  • Value-based方法采用的exploration rate $\epsilon$有三个取值$\epsilon_1,\epsilon_2,\epsilon_3$,相应的概率为$0.4,0.3,0.3$,它们的值在前$4$百万帧中从$1$退火到$0.1,0.01,0.5$。
  • A3C使用了entropy进行正则化,entropy项的权重为$\beta=0.01$
  • 初始学习率从分布$LogUniform(10^{-4},10^{-2})$中进行采样,在训练过程中退火到$0$。

代码

代码地址

https://github.com/ikostrikov/pytorch-a3c

问题

如果直接git下来运行的话,会出问题,需要在main()下加上这样一句

1
mp.set_start_method("forkserver")

可能是因为Unix系统默认的多进程方式是fork,这里只要不设置为fork,设置为其他两种方式spawn, forkserver都行。

参考文献

1.https://arxiv.org/pdf/1602.01783.pdf

Distral Robust Multitask Reinforcement Learning

发表于 2019-04-18 | 更新于 2019-12-17 | 分类于 强化学习

这篇文章给出了多任务学习和迁移学习的一个框架。

摘要

multitask learning的目的是在不同taskes之间共享网络参数,在不同的task之间迁移可以提高效率。但是这种方法有几个缺点:

  • 不同taskes的gradient可能有负干扰,让学习变得不稳定,更低效。
  • 另一个问题是不同taskes的reward scheme不同,其中的某一个reward可能占主导地位。

本文提出Distral方法,在不同taskes的worker之间共享一个distilled policy而不是共享参数,学习不同taskes的common behaviours。每一个worker解决它自己的task,但是需要限制每一个单独的policy离distilled policy足够近,而distilled policy需要通过所有的single policies得到,它在所有policies的质心上。通过优化一个联合目标函数,实现这两个过程。

Introduction

由于DRL需要的训练时间和训练数据很多,现在的DRL问题开始转向单个智能体同时或者连续的解决多个相关问题。由于巨大的计算开销,这个方向的研究需要设计出非常鲁棒的,与具体任务无关的算法。直观上来说,相似的taskes之间有共同的结构,所以我们觉得一起训练它们应该能够促进学习,然而实践表明并不是这样。
所以,multitask和transfer learning需要解决一个问题:在多个taskes上训练会对单个任务的训练产生负面影响。这个问题可能的原因有:其他任务的梯度可能会被当做噪音干扰学习,极端情况下,其中一个任务可能会主导其他的任务。
这篇论文作者提出了一种multitask和transfer RL算法,它能够高效的在多个taskes之间共享behaviour structure。除了在grid world领域的一些指导性illustration(例证),作者还在DeepMind Lab 3D环境中详细分析了算法,并且和A3C baseline的比较。作者验证了Distral算法学习的很快,而且能够达到很好的收敛性能,而且对超参数很鲁棒,比multitask A3C baselines要稳定的多。

Distral: Distill and Transfer Learning

作者给出了一个multi-taskes的框架Distral,如下图所示,作者给出了从四个taskes中提取shared policy的一个例子。该方法用一个shared policy去提取task-specific的polices之间的common behaviour和representation,然后又用这个shared policy和task-specific polices之间的KL散度正则化task-specific polices。KL散度的作用相当于shaping reward,鼓励exploration。最后,这些taskes之间的common knowledge都被distilled到shared policy中了,然后可以迁移到其他任务中去。
figure1

数学框架

一个multitask RL setting中有$n$个任务,折扣因子为$\gamma$,它们的state space和action space是相同的,但是每个任务$i$的状态转换概率$p_i(s’|s,a)$和奖励函数$R_i(s,a)$是不同的,用$\pi_i$表示第$i$个任务的stochastic polices。给定从一些初始状态开始的state和action联合分布的轨迹,用$\pi_i$表示dynamics和polices。
作者通过优化一个expected return和policy regularization组成的目标函数将学习不同任务的policy联系起来。用$\pi_0$表示要提取的shared policy,然后通过使用$\pi_0$和$\pi_i$的KL散度$\mathbb{E}_{\pi_i}\left[\sum_{t\ge 0}\gamma^t\log\frac{\pi_i(a_t|s_t)}{\pi_0(s_t|a_t)}\right]$对$\pi_i$进行约束,使所有的策略 $\pi_i$ 接近 $\pi_0$ 。此外,作者还使用了一个带折扣因子的entropy正则化项鼓励exploration。系统越混乱,entropy越大,所以exploration越多,采取的动作越随机,entropy就越大。为什么,举个例子,系统采取两个动作的概率分别是:[0, 1], [0.5, 0.5], [0.25, 0.75],他们对应的熵分别是$0, \log 2, \log 4- 0.75 \log 3 < \log 2$,也就是系统最混乱的时候,熵最大,为了鼓励探索,也就是让其他action出现的概率变大,也就是让熵变大。
最后总的优化目标就变成了:
\begin{align*}
J(\pi_0, {\pi_i}_{i=1}^n) &=\sum_i\mathbb{E}_{\pi_i}\left[\sum_{t\ge 0}\gamma^tR_i(s_t,a_t) -c_{KL}\gamma^t \log\frac{\pi_i(a_t|s_t)}{\pi_0(a_t|s_t)}-c_{Ent}\gamma^t \log\pi_i(a_t|s_t)\right]\\
&=\sum_i\mathbb{E}_{\pi_i}\left[\sum_{t\ge 0}\gamma^tR_i(s_t,a_t) - c_{KL}\gamma^t\log{\pi_i(a_t|s_t)} + c_{KL}\gamma^t\log{\pi_0(a_t|s_t)} - c_{Ent}\gamma^t\log\pi_i(a_t|s_t)\right]\\
&=\sum_i\mathbb{E}_{\pi_i}\left[\sum_{t\ge 0}\gamma^tR_i(s_t,a_t) + c_{KL}\gamma^t\log{\pi_0(a_t|s_t)} - (c_{Ent}\gamma^t + c_{KL}\gamma^t)\log\pi_i(a_t|s_t)\right]\\
&=\sum_i\mathbb{E}_{\pi_i}\left[\sum_{t\ge 0}\gamma^tR_i(s_t,a_t) +\frac{\gamma^t \alpha}{\beta}\log{\pi_0(a_t|s_t)}-\frac{\gamma^t}{\beta}\log\pi_i(a_t|s_t)\right], \tag{1}
\end{align*}
其中$c_{KL},c_{Ent}\ge 0$是控制KL散度正则化项和entropy正则化项大小的超参数,$\alpha = \frac{c_{KL}}{c_{KL}+c_{Ent}},\beta = \frac{1}{c_{KL}+c_{Ent}}。变换后的公式$(1)中出现的\log\pi_0(a_t|s_t)$可以看成reward shaping,鼓励大概率的action;而entropy项$-\log\pi_i(a_t|s_t)$鼓励exploration。在这个公式中,设置所有任务的正则化系数$c_{KL}$和$c_{Ent}$都是相同的,如果不同任务的reward scale不同,可以根据具体情况给相应任务设定相应系数。
让$R’_i(s,a) = R_i(s,a) + \frac{\alpha}{\beta}\log\pi_0(a|s)$,目标函数可以看成new reward $R’$的正则化约束问题。

Soft Q-Learing

有很多方法可以最大化上面给出的目标函数,这一节介绍表格形式下,如何使用和EM算法类似的策略优化目标函数--固定$\pi_0$优化$\pi_i$,固定$\pi_i$然后优化$\pi_0$。
当$\pi_0$固定的时候,式子(1)可以分解成每个task的最大化问题,即优化每个任务的entropy正则化return,return使用的是正则化reward:
正则化后的return可以使用G-learning来优化(这里说的应该是原来的return和R什么都没,这里都加上了正则化)。给定$\pi_0$,根据Soft Q-Learning(G-learning)的证明,我们能得到以下的关系:
$$\pi_i(a_t|s_t) = \pi_0^{\alpha} (a_t|s_t)e^{\beta Q_i(a_t|s_t)-\beta V(s_t)} = \pi_0^{\alpha} (a_t|s_t)e^{\beta A_i(a_t|s_t)} \tag{2}$$
其中$A_i(s,a) = Q_i(s,a)-V_i(s)$是advantage function,$\pi_0$可以看成是一个policy prior,需要注意的是这里多了一个指数$\alpha \lt 1$,这是多出来的entropy项的影响,soften了$\pi_0$对$\pi_i$的影响。$V$和$Q$是新定义的一种state value和action value,使用推导的softened Bellman公式更新:
$$V_i(s_t) = \frac{1}{\beta} \log\sum_{a_t}\pi_0^{\alpha} (a_t|s_t)e^{\beta Q_i(s_t,a_t)} \tag{3}$$
$$Q_i(s_t,a_t) = R_i(s_t, a_t)+ \gamma \sum_{s_t}p_i(s_{t+1}|s_t,a_t)V_i(s_{t+1}) \tag{4}$$
这个Bellman update公式是softened的,因为state value $V_i$在actions上的max操作被温度$\beta$倒数上的soft-max操作代替了,当$\beta\rightarrow\infty$时,就变成了max 操作,这里有些不明白。为什么呢?这个我不理解有什么关系,这是这篇文章给出的解释。按照我的理解,这个和我们平常使用Bellman 期望公式或者最优等式没有什么关系,只是给了一种新的更新Q值和V值的方法。实际上,这两个公式都是根据推导给出的定义。
还有一点:$\pi_0$是学出来的,而不是手动选出来的。式子(1)中和$\pi_0$相关的只有:
$$\frac{\alpha}{\beta}\sum_i\mathbb{E_{\pi_i}}\left[\sum_{t\ge 0}\gamma^t\log\pi_0(a_t|s_t) \right]\tag{5}$$
可以看出来,这是使用$\pi_0$去拟合一个混合的带折扣因子$\gamma$的state-action分布,每个$i$代表一个任务,可以使用最大似然估计来求解,如果是非表格情况的话,可以使用stochastic gradient ascent进行优化,但是需要注意的是本文中作者使用的目标函数多了一个KL散度。另一个区别是本文的distilled policy可以作为下一步要优化的task policy的反馈。
多加一个entropy正则项的意义?如果不加entropy正则化,也就是式子$(2)$中的$\alpha = 1$,考虑$n=1$时的例子,式子$(5)$在$\pi_0=\pi_1$的时候最大,KL散度为$0$,目标函数退化成了一个没有正则化项的expected return,最终策略$\pi_1$会收敛到一个局部最优值。**和TRPO的一个比较???未完待续。。。。**如果$\alpha\lt 1$,式(1)中有一个额外的entropy项。这样即使$\pi_0=\pi_1$,$KL(\pi_1||\pi_0)=0$,因为有entropy项,也无法通过greedy策略最大化式子$(1)$。式子$1$的entropy正则化系数变成了$\beta’=\frac{\beta}{1-\alpha} = \frac{1}{c_{Ent}}$(第一个等号是为什么??是因为$\pi_0=\pi_1$,然后就可以将$\pi_0,\pi_i$的系数合并了),最优的策略就是$\beta’$处的Boltzmann policy。添加这个entropy项可以保证策略不是greedy的,通过调整$c_{Ent}$的大小可以调整exploration。
最开始的时候,exploration是在multitask任务上加的,如果有多个任务,一个很简单,而其他的很复杂,如果先遇到了简单任务,没有加entropy的话,最后就会收敛到最简单任务的greedy策略,这样子就无法充分探索其他任务的,导致陷入到次优解。对于single-task的RL来说,在A3C中提出用entropy取应对过早的收敛,作者在这里推广到了multitask任务上。

Policy Gradient and a Better Parameterization

上面一节讲的是表格形式的计算,给定$\pi_0$,首先求解出$\pi$对应的$V$和$Q$,然后写出$\pi_i$的解析。但是如果我们用神经网络等函数去拟合$V$和$Q$,$V$和$Q$的求解特别慢,这里使用梯度下降同时优化task polices和distilled policy。这种情况下,$\pi_i$的梯度更新通过求带有entropy正则化的return即可求出来,并且可以放在如actor-critic之类的框架中。
每一个$\pi_i$都用一个单独的网络表示,$\pi_0$也用一个单独的网络表示,用$\theta_0$表示$\pi_0$的参数,对应的policy表示为:
$$\hat{\pi_0}(a_t|s_t) = \frac{e^{(h_{\theta_0}(a_t|s_t))} }{\sum_{a’}e^{h_{\theta_0}(a’|s_t)}} \tag{6}$$
使用参数为$\theta_i$的神经网络表示$Q$值,用$f_{\theta_i}$表示第$i$个策略$\pi$的$Q$值,用$Q$表示$V$,再估计$A=Q-V$的值:
$$\hat{A}_i(a_t|s_t) = f_{\theta_i}(a_t|s_t) - \frac{1}{\beta}\log\sum_a\hat{\pi}_0^{\alpha} (a|s_t)e^{\beta f_{\theta_i}(a|s_t)} \tag{7}$$
将式子$(7)$代入式子$(2)$得第$i$个任务的policy可以参数化为:
\begin{align*}
\hat{\pi}_i(a_t|s_t)
& = \hat{\pi}_0^{\alpha} (a_t|s_t)e^{\left(\beta \hat{Q}_i(a_t|s_t)-\beta \hat{V}(s_t)\right)}\\
& = \hat{\pi}_0^{\alpha} (a_t|s_t)e^{\left(\beta \hat{A}_i(a_t|s_t)\right)}\\
& = \hat{\pi}_0^{\alpha} (a_t|s_t)e^{\left(\beta \left(f_{\theta_i}(a_t|s_t) - \frac{1}{\beta}\log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}\right)\right)}\\
& = \hat{\pi}_0^{\alpha} (a_t|s_t)e^{\left(\beta f_{\theta_i}(a_t|s_t) - \log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}\right)}\\
& = \left(\frac{e^{(h_{\theta_0}(a_t|s_t))} }{\sum_{a’}e^{h_{\theta_0}(a’|s_t)}}\right)^{\alpha}e^{\left(\beta f_{\theta_i}(a_t|s_t) - \log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}\right)}\\
& = \left(\frac{e^{(h_{\theta_0}(a_t|s_t))} }{\sum_{a’}e^{h_{\theta_0}(a’|s_t)}}\right)^{\alpha}
\cdot
\frac{e^{\beta f_{\theta_i}(a_t|s_t)} } {e^{\log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}}}\\
& = \frac{\left(e^{(h_{\theta_0}(a_t|s_t))}\right)^{\alpha}} {\left(\sum_{a’}e^{h_{\theta_0}(a’|s_t)}\right)^{\alpha}}
\cdot
\frac{e^{\beta f_{\theta_i}(a_t|s_t)}} {e^{\log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}}}\\
& = \frac{e^{\alpha \cdot(h_{\theta_0}(a_t|s_t))}} {\left(\sum_{a’}e^{h_{\theta_0}(a’|s_t)}\right)^{\alpha}}
\cdot
\frac{e^{\beta f_{\theta_i}(a_t|s_t)}}{e^{\log\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}}}\\
& = \frac{e^{(\alpha h_{\theta_0}(a_t|s_t))}} {\left(\sum_{a’}e^{h_{\theta_0}(a’|s_t)}\right)^{\alpha}}
\cdot
\frac{e^{\beta f_{\theta_i}(a_t|s_t)}}{\sum_a\hat{\pi}_0^{\alpha}(a|s_t)e^{\beta f_{\theta_i}(a|s_t)}}\\
& = \frac{e^{(\alpha h_{\theta_0}(a_t|s_t))} \cdot e^{\beta f_{\theta_i}(a_t|s_t) }}
{\left(\sum_{a’}e^{h_{\theta_0}(a’|s_t)}\right)^{\alpha} \cdot {\sum_a\hat{\pi}_0^{\alpha}(a|s_t) e^{\beta f_{\theta_i}(a|s_t)}}}\\
& = \frac{e^{(\alpha h_{\theta_0}(a_t|s_t) + \beta f_{\theta_i}(a_t|s_t)) }}
{\left(\sum_{a’}e^{h_{\theta_0}(a’|s_t)}\right)^{\alpha} \cdot {\sum_a\hat{\pi}_0^{\alpha}(a|s_t) e^{\beta f_{\theta_i}(a|s_t)}}}(Why to blow equation???)\\
& = \frac{e^{(\alpha h_{\theta_0}(a_t|s_t) + \beta f_{\theta_i}(a_t|s_t))}} {\sum_{a’}e^{(\alpha h_{\theta_0}(a’|s_t) + \beta f_{\theta_i}(a’|s_t))}}
\end{align*}
所以:
$$\hat{\pi}_i(a_t|s_t) = \hat{\pi}_0^{\alpha}(a_t|s_t)e^{(\beta\hat{A}_i(a_t|s_t))}=\frac{e^{(\alpha h_{\theta_0}(a_t|s_t) + \beta f_{\theta_i}(a_t|s_t))}}{\sum_{a’}e^{(\alpha h_{\theta_0}(a’|s_t) + \beta f_{\theta_i}(a’|s_t))}} \tag{8}$$
这可以看成policy的一个两列架构,一列是提取的shared policy,一列是将$\pi_0$应用到task $i$上需要做的一些修改。
使用参数化的$\pi_0, \pi_i$,首先推导策略相对于$\pi_i$的梯度(policy gradient的推导,这里是直接应用了):
\begin{align*}
\nabla_{\theta_i}J& = \mathbb{E}_{\hat{\pi}_i}\left[\left(\sum_{t\gt 1} \nabla_{\theta_i}\log{\hat{\pi}}_i(a_t|s_t)\right) \left(\sum_{u\ge 1}\gamma^u \left(R^{reg}_i(a_u,s_u)\right)\right) \right]\\
& = \mathbb{E}_{\hat{\pi}_i}\left[\sum_{t\gt 1} \nabla_{\theta_i}\log\hat{\pi}_i(a_t|s_t)\left(\sum_{u\ge t}\gamma^u \left(R^{reg}_i(a_u,s_u)\right)\right) \right] \tag{9}\\
\end{align*}
其中$R_i^{reg}(s,a) = R_i(s,a) + \frac{\alpha}{\beta}\log\hat{\pi}_0(a|s) - \frac{1}{\beta}\log\hat{\pi}_i(a|s)$是正则化后的reward。注意,这里$\mathbb{E}_{\hat{\pi}_i}\left[\nabla_{\theta_i}\log\hat{\pi}_i(a_t|s_t)\right] = 0$,因为\log-derivative trick。如果有一个value baseline,那么为了减少梯度的方差,可以从正则化后的returns中减去它。
关于$\theta_0$的梯度如下:
\begin{align*}
\nabla_{\theta_0}J
& = \mathbb{E}_{\hat{\pi}_i}
\left[
\sum_{t\gt 1} \nabla_{\theta_i}\log\hat{\pi}_i(a_t|s_t)
\left(\sum_{u\ge 1}\gamma^u
\left(
R^{reg}_i(a_u,s_u)
\right)
\right)
\right]\\
& \qquad +\frac{\alpha}{\beta}\sum_i\mathbb{E}_{\hat{\pi}_i}
\left[
\sum_{t\ge 1}\gamma^t\sum_{a’_t}
\left(
\hat{\pi}_i(a’_t|s_t)-\hat{\pi}_0(a’_t|s_t)
\right)
\nabla_{\theta_0}h_{\theta_0}(a’_t|s_t)
\right] \tag{10}
\end{align*}
第一项和$\pi_i$一样,第二项是让$\hat{\pi}_i,\hat{\pi}_0$的概率尽可能接近。如果不使用KL散度的话,这里就不会有第二项了。KL正则是为了让$\pi_0$在$\pi_i$的质心上,即$\hat{\pi}_0(a’_t|s_t) = \frac{1}{n}\sum_i\hat{\pi}_i(a’_t|s_t)$,最后第二项就为$0$了,可以快速的将公共信息迁移到新任务上。
和ADMM,EASGD等在参数空间上进行优化不同的是,Distral是在策略空间上进行优化,这样子在语义上更有意义,对于稳定学习过程很重要。
本文的方法通过添加了entropy正则化和KL正则化,使得算法可以分开控制每个任务迁移的信息大小和exploration程序。

算法

上面给出的框架可以对不同的目标函数,算法和架构进行组合,然后生成一系列算法实例。

  • KL散度和entropy:当$\alpha=0$时,只有entorpy,不同任务之间没有耦合,在不同任务中进行迁移。当$\alpha=1$时,只有KL散度,不同任务之间有耦合,在不同任务中进行迁移,但是如果$\pi_i,\pi_0$很像的话,会过早的停止探索。当$0\lt \alpha \lt 1$时,KL散度和entropy都有。
  • 迭代优化还是联合优化:可以选择同时优化$\pi_0,\pi_i$,也可以固定其中一个,优化另一个。迭代优化和actor-mimic以及policy-distilled有一些相似,但是Distral是迭代进行的,$\pi_0$会对$\pi_i$的优化提供反馈。尽管迭代优化可能会很慢,但是从actor-mimic等的结果来看,可能它会更稳定。
  • Separate还是two-column参数化:这里的意思是$\pi_i$是否使用式子(8)中的$\pi_0$,如果用的话,$\pi_0$中提取到的信息可以立刻用到$\pi_i$上,transfer可以更快。但是如果transfer的太快的话,可能会抑制在单个任务上exploration的有效性。。

这里作者给出了使用到的一些算法组合,如下表和下图所示。这里作者和三个A3C baseline(三种架构)做了比较,作者做实验的时候,试了两种A3C,第一个是原始的A3C,第二个是A3C的变种,最后发现这两种A3C没啥差别,在实验部分就选择了原始的A3C作比较。

figure2
table

Algorithm

  • A3C: 在每个任务上单独使用A3C训练的policy
  • A3C_multitask: 使用A3C同时在所有任务上训练得到的policy
  • A3C_2col: 使用了式子(8)中的two-column架构A3C在每个任务上训练的policy
  • KL_1col: $\pi_0,\pi_i$分别用一个网络来表示,令$\alpha=1$,即只有KL散度的式子(1)进行优化,
  • KL+ent_1col: 和KL_1col一样,只不过包括了KL散度和entropy项,并设置$\alpha = 0.5$。
  • KL_2col: 和KL_1col一样,但是使用了式子(8)中的two-column架构
  • KL+ent_2col: 和KL+ent_1col一样,只是使用了two-column架构。

实验

总共有两个实验,第一个是在grid world上使用soft Q-learning和policy distilltion的迭代优化,第二个是七个算法在三个3D部分可观测环境上的评估。

环境

Grid world

这个实验是在一些简单的grid world上进行的,每一个任务通过一个随机选择的goal location进行区分。
每一个MDP的state由map location, previous action和previous reward组成。一个Distral智能体通过KL正则化的目标函数进行训练,优化算法在Soft Q-learing和policy distilltion之间进行迭代。每次soft Q-learing 的展开长度是$10$。

3D环境

这个实验使用了三个第一人称的$3D$环境。所有的智能体都是用pytorch/tensorflow实现的,每个任务有$32$个workers,使用异步的RMSProp进行学习。每个网络由CNN和LSTM组成,在不同的算法和实验中都是相同的。作者尝试了三个$\beta$和三个学习率$\epsilon$,每一组超参数跑了四次,其他超参数和单任务的A3C都一样的,对于KL+ent 1col和KL+ent 2col算法,$\alpha$被固定为$0.5$。

Maze

八个任务,每个任务都是一个随机放置reward和goal的迷宫。作者给出了$7$个算法的学习曲线,每一个学习曲线是选出最好的$\beta,\epsilon$在$8$个任务跑$4$次的平均值。Distral学习的很快,并且超过了三个A3C baselies,而two-column算法比one-column学习的要快,不带entropy的Distral要比带entropy学得快,但是最终得分要低,这可能是没有充分explration的原因。
multitask A3C和two-column A3C学习的不稳定,有时候学的好,有时候学的不好,有时候刚开始就不好了。而Distral对于超参数也很鲁棒。

Navigation

四个任务,比Maze难度要大。

Laser-tag

DeepMind Lab中的八个任务,最好的baseline是单独在每个任务上训练的A3C。

Discussion

有两个idea这里需要强调一下。在优化过程中,使用KL散度正则化使$\pi_i$向$\pi_0$移动,使用$\pi_0$正则化$\pi_i$。
另一个就是在深度神经网络中,它们的参数没有意义,所以作者不是在参数空间进行的正则化,而是在策略空间进行正则化,这样子更有语义意义。

参考文献

1.https://papers.nips.cc/paper/7036-distral-robust-multitask-reinforcement-learning.pdf

linux 终端快速访问某个目录

发表于 2019-04-15 | 更新于 2019-05-12 | 分类于 linux

动机

在写博客的过程中,每次在终端中进入该目录,都要输好长的命令,在想着有没有什么简单的方法。后来就在网上找到了。

方法

利用alias命令进行重命名
这里给出一个具体的例子,我的博客文件存放在/home/mxxmhh/github/blog/source/_posts下,
在/home/mxxmhh/.bashrc文件中添加如下一行即可(当然也可以在其他配置文件中添加):
alias posts='cd /home/mxxmhh/github/blog/source/_posts’
然后执行
~\$:source /home/mxxmhh/.bashrc
即可。
接下来可在终端输入
~\$:posts
直接访问该目录。

参考文献

1.https://www.cnblogs.com/wlsphper/p/6782625.html

1…272829…34
马晓鑫爱马荟荟

马晓鑫爱马荟荟

记录硕士三年自己的积累

337 日志
26 分类
77 标签
RSS
GitHub E-Mail
© 2022 马晓鑫爱马荟荟
由 Hexo 强力驱动 v3.8.0
|
主题 – NexT.Pisces v6.6.0