Tian Junhttps://tianjun.me/rssAll about Tian Junen-USMon, 20 Aug 2018 22:10:21 GMTrfeed v1.1.1https://github.com/svpino/rfeed/blob/master/README.mdAn_Introduction_to_Parallel_Computing_in_Julia_From_Bottom_Uphttps://tianjun.me/essays/An_Introduction_to_Parallel_Computing_in_Julia_From_Bottom_Up<body> <h1 id="自底向上理解julia中的并行计算">自底向上理解Julia中的并行计算</h1> <p>最近看到一些有关Julia并行计算的提问,所以这里不妨开个头,介绍下Julia中并行计算的实现,希望能有更多人能参与进来一起讨论。在Julia文档中,有专门的一部分讲解<a href="https://docs.julialang.org/en/stable/manual/parallel-computing/">Parallel Computing</a>(中文翻译见<a href="http://docs.juliacn.com/latest/manual/parallel-computing/">并行计算</a>),采用的是一种Top-Down的方式分别介绍了协程、多线程及分布式处理。这里我打算采用一种Bottom-Up的方式来介绍下Julia中的并行计算,建议先读完官方文档后继续往下看。</p> <h2 id="几个基本概念">几个基本概念</h2> <h3 id="task"><code>Task</code></h3> <p>顾名思义,<code>Task</code>就是构造一段执行任务,<code>Task</code>的定义在<a href="https://github.com/JuliaLang/julia/blob/master/src/task.c">task.c</a>文件中,不过作为使用者,我们更关心的是调用接口:</p> <pre class="language-julia"><code>julia&gt; methods(Task) # 1 method for generic function "(::Type)": [1] Task(f) in Core at boot.jl:377 julia&gt; methodswith(Task) [1] bind(c::Channel, task::Task) in Base at channels.jl:191 [2] serialize(s::Serialization.AbstractSerializer, t::Task) in Serialization at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.0/Serialization/src/Serialization.jl:427 [3] fetch(t::Task) in Base at task.jl:202 [4] istaskdone(t::Task) in Base at task.jl:117 [5] istaskstarted(t::Task) in Base at task.jl:134 [6] schedule(t::Task) in Base at event.jl:95 [7] schedule(t::Task, arg) in Base at event.jl:129 [8] show(io::IO, ::MIME{Symbol("text/plain")}, t::Task) in Base at show.jl:150 [9] show(io::IO, t::Task) in Base at task.jl:58 [10] wait(t::Task) in Base at task.jl:182 [11] yield(t::Task) in Base at event.jl:166 [12] yield(t::Task, x) in Base at event.jl:166 [13] yieldto(t::Task) in Base at event.jl:181 [14] yieldto(t::Task, x) in Base at event.jl:181 julia&gt; fieldnames(Task) (:parent, :storage, :state, :donenotify, :result, :exception, :backtrace, :logstate, :code)</code></pre> <p>task的构造函数只有一个<code>Task(f)</code>,其唯一的一个参数<code>f</code>必须是不带参数的函数,如果传一个带参数的函数,会在真正执行时触发<code>MethodError</code>。</p> <pre class="language-julia"><code>julia&gt; t = Task((x) -&gt; x + 1) Task (runnable) @0x00007f600e180d30 julia&gt; schedule(t) Task (failed) @0x00007f600e180d30 MethodError: no method matching (::getfield(Main, Symbol("##11#12")))() Closest candidates are: #11(::Any) at REPL[29]:1</code></pre> <p>当然,每次都要记得构造一个闭包很傻,有一个<code>@task</code>宏可以用于简化这个过程:</p> <pre class="language-julia"><code>julia&gt; t = @task println("Hi") Task (runnable) @0x00007f600f25aa10 julia&gt; schedule(t) Hi Task (queued) @0x00007f600f25aa10 julia&gt; t Task (done) @0x00007f600f25aa10</code></pre> <p>上面为了看到一个task的执行结果,我们使用了<code>schedule</code>函数,其作用是将这个runnable的task加入到一个全局的task队列中,然后将task的状态置成<code>:queued</code>,系统在<strong>空闲</strong>时会执行该task(TODO:调度的逻辑),执行结果存在<code>:result</code>字段下,并根据执行结果修改其<code>:state</code>状态(<code>:failed</code>,<code>:done</code>)。不过上面的例子似乎给人一种错觉,在执行完<code>schedule(t)</code>之后,task <code>t</code>立即就执行了,并没有感受到所谓的<strong>等待系统空闲</strong>。下面这个例子用一个计算密集型的任务来验证下:</p> <pre class="language-julia"><code>t = @task begin println("begin task") inv(rand(2000, 2000)) println("end task") end begin schedule(t) println(length(Base.Workqueue)) println(t.state) println("begin computing") println(sum(inv(randn(1500, 1500)))) println("end computing") println(length(Base.Workqueue)) println(t.state) end # 1 # queued # begin computing # 97.12983082590253 # end computing # 1 # queued # begin task # end task</code></pre> <p>可以看到,在<code>schedule(t)</code>之后,<code>t</code>并没有立即被执行,而是被添加到了<code>Base.Workqueue</code>中一直处于<code>queued</code>状态,主流程继续执行,先进行了求逆计算,结束之后,系统再进行task切换,执行<code>t</code>。以上,就是所谓的<strong>并发(Concurrency)</strong>。对于单一进程来说,并发执行计算密集型任务并没有太大收益,不过,对IO密集型任务来说,则非常有用,在等待的过程中,可以切换到其它任务,一旦条件满足,再切回来就执行,这样看起来,似乎是在同时执行多个任务(并发)。Julia对这里所谓的条件提供了一个统一的概念,称为<code>Condition()</code>:</p> <pre class="language-julia"><code>julia&gt; fieldnames(Condition) (:waitq,)</code></pre> <p><code>Condition()</code>只有一个类型为<code>Vector</code>的字段<code>:waitq</code>用于记录在等待该条件的所有task,在一个task内部,可以通过执行<code>wait(c::Condition)</code>,声明其正在等待某个条件,然后将自己添加到<code>Base.Workqueue</code>尾部,同时从中取出第一个task并做切换。当条件满足时,通过执行<code>notify(c::Condition)</code>再将这些task重新加入到<code>Base.Workqueue</code>中等待执行。</p> <pre class="language-julia"><code>julia&gt; c = Condition() Condition(Any[]) julia&gt; t = @task begin println("waiting condition") wait(c) println("condition meet") end Task (runnable) @0x00007f2d954c07f0 julia&gt; schedule(t) waiting condition Task (queued) @0x00007f2d954c07f0 julia&gt; notify(c) condition meet 1</code></pre> <p>除了通过执行<code>wait</code>进行task切换之外,还可以通过执行<code>yield()</code>主动进行<code>task</code>的切换(其实也是调用了<code>wait()</code>函数)。</p> <pre class="language-julia"><code>yield() = (enq_work(current_task()); wait())</code></pre> <p>下面看一个<code>yield</code>的例子:</p> <pre class="language-julia"><code>julia&gt; t1 = @task begin println("task1 begin") yield() println("task1 resumed") end Task (runnable) @0x00007f2d954c2f50 julia&gt; t2 = @task begin println("task2 begin") yield() println("task2 resumed") end Task (runnable) @0x00007f2d954c31f0 julia&gt; begin schedule(t1) schedule(t2) yield() end task1 begin task2 begin task1 resumed task2 resumed</code></pre> <p>关于task,理解这些基本够用了。一个典型的应用是<code>Timer</code>,其中有个字段<code>:cond</code>就是一个<code>Condition()</code>,每当设定的时间周期到了的时候,就会<code>notify</code>挂在该<code>:cond</code>上的task。另外经常用到的<code>@async</code>宏其实就是先构造了一个task,然后执行了<code>schedule</code>(二合一了)</p> <p>下面我们再深入理解一个更有意思的例子。</p> <h3 id="channel"><code>Channel</code></h3> <p><code>Channel</code>就是一个通道,不同的task可以从一端往其中写入数据,而另外一些task则可以从另外一端读取数据。<code>Channel</code>的结构很简单:</p> <pre class="language-julia"><code>mutable struct Channel{T} &lt;: AbstractChannel{T} cond_take::Condition # waiting for data to become available cond_put::Condition # waiting for a writeable slot state::Symbol excp::Union{Exception, Nothing} # exception to be thrown when state != :open data::Vector{T} sz_max::Int # maximum size of channel # Used when sz_max == 0, i.e., an unbuffered channel. waiters::Int takers::Vector{Task} putters::Vector{Task} end</code></pre> <p>其中<code>state</code>字段表示当前channel的状态(<code>:open</code>, <code>:closed</code>),<code>sz_max</code>则表示channel的长度(该长度可以设为0,即无限大)。</p> <p>对于长度有限的channel来说,执行<code>put!(c, v)</code>写入数据时,如果当前<code>data</code>的长度已经达到了<code>sz_max</code>,则会调用<code>wait()</code>将当前task阻塞,然后每个事件周期都会检查<code>data</code>的长度是否已经小于<code>sz_max</code>,一旦该条件满足,就会往<code>data</code>中写入<code>v</code>,同时通知所有挂在<code>cond_take</code>字段上的task。而执行<code>take!(c)</code>读取数据时,如果当前<code>data</code>中有数据,则取出来,同时通知挂在<code>cond_put</code>上的task,否则,将当前task挂起到<code>cond_take</code>中,等待新的数据。</p> <p>对于无限长的channel而言,需要用到<code>takers</code>和<code>putters</code>字段。在写入数据时,如果<code>takers</code>为空,就将当前task写入到<code>putters</code>中(然后还会通知<code>cond_take</code>上的task,这类task是通过<code>wait(c)</code>挂在在无限长channel上的),否则,从<code>takers</code>中取一个出来<strong>重新</strong>执行(这里用的是<code>yield(t, v)</code>操作)。取数据时,先将自己加入到<code>waiters</code>中,然后判断<code>putters</code>是否为空,若空,则调用<code>wait()</code>将自己挂起,否则从<code>putters</code>中取出一个执行。</p> <p>此外,关于<code>Channel</code>有个挺好用的函数<code>Channel(func::Function; ctype=Any, csize=0, taskref=nothing)</code>。关于<code>Channel</code>的例子实在太多了,手册中的那个生产者消费者的例子就挺不错的,这里不列举了。</p> <h2 id="多线程">多线程</h2> <p>这里暂时先不深入介绍多线程,主要是这个Julia中老大难的问题了,目前的接口仍然是实验性的,此外也有一些PR正在做这方面的事情,建议subscribe一些<a href="https://github.com/JuliaLang/julia/labels/multithreading">multithreading</a>的PR,了解下最新的进展(比如<a href="https://github.com/JuliaLang/julia/pull/22631">这个</a>)</p> <h2 id="多进程">多进程</h2> <p>前面提到的都还是<strong>并发</strong>,要实现真正的<strong>并行</strong>,需要充分利用多核/多台机器。手册里有提到,Julia实现的并行机制有点类似MPI,不过是单向的(也就是说,有一个master进程负责给其它进程分配执行任务)。所有分布式相关的代码都在Julia源码的<code>stdlib/Distributed</code>package下,接下来我们一步步展开介绍(如果你想在REPL中测试下面的示例代码,记得先执行<code>using Distributed</code>)。</p> <p>首先讨论单机多进程的情况。在Julia中,一个工作进程称作一个worker,管理这些worker的进程是<code>LocalProcess</code>(也就是打开REPL后进入的进程)。每个进程都有自己的pid,<code>LocalProcess</code>的pid是<code>1</code>(为了表述方便,以下称其为master)。接下来先回答几个问题:</p> <h3 id="如何表示一个work中的对象">1. 如何表示一个work中的对象?</h3> <p>对于master而言,worker中的对象有两种表示,一个是<code>Future</code>,另一个是<code>RemoteChannel</code>。</p> <pre class="language-julia"><code>mutable struct Future &lt;: AbstractRemoteRef where::Int whence::Int id::Int v::Union{Some{Any}, Nothing} end</code></pre> <p>其中,<code>where</code>表示<code>v</code>所在的pid,<code>whence</code>和<code>id</code>一般通过<code>RRID</code>生成,分别表示生成该<code>Future</code>对象的进程的pid,而<code>id</code>则是从1开始自增的id。<code>RemoteChannel</code>也类似:</p> <pre class="language-julia"><code>mutable struct RemoteChannel{T&lt;:AbstractChannel} &lt;: AbstractRemoteRef where::Int whence::Int id::Int end</code></pre> <h3 id="怎么发起远程调用">2. 怎么发起远程调用?</h3> <p>Julia中,提供了一个底层函数<code>remotecall</code>来实现远程调用,执行后会立即返回一个<code>Future</code>对象,然后可以通过<code>fetch</code>将value写入到<code>Future</code>的<code>v</code>字段中(此时会发生数据转移,也就是导致并行计算性能瓶颈的地方)。例如:</p> <pre class="language-julia"><code>julia&gt; using Distributed julia&gt; addprocs() 4-element Array{Int64,1}: 2 3 4 5 julia&gt; m = remotecall(rand, 5, 2, 2) Future(5, 1, 6, nothing) julia&gt; fetch(m) 2×2 Array{Float64,2}: 0.109123 0.304667 0.454125 0.197551</code></pre> <p>此外,<code>Distributed</code>中还提供了一些工具函数和有用的宏,这里不深入介绍,我们更关心的是:</p> <h3 id="什么时候会发生gc">3. 什么时候会发生GC?</h3> <p><code>Distributed</code>中有一个<code>clear!</code>函数用于将worker中的变量置成nothing,不过,如果不引入全局变量的话,大多时候并不需要手动进行该操作。<code>fetch</code>会自动执行<code>send_del_client</code>函数,并通知gc.此外手册里也提到,由于对master来说,一个RemoteReference的内存占用很小,并不会马上被gc,因而可以调用<code>finalize</code>,从而会立即执行<code>send_del_client</code>向worker发送gc信号。</p> <p>TODO: 一个分布式并行计算的实例</p> </body>Jun TianSun, 19 Aug 2018 23:12:20 GMTAn_Introduction_to_Parallel_Computing_in_Julia_From_Bottom_UpFrom_Python_to_Juliahttps://tianjun.me/essays/From_Python_to_Julia<body> <h1 id="写给python用户的julia编程指南updating">写给Python用户的Julia编程指南[updating]</h1> <p>本文的目的是,为拥有Python编程经验的用户提供一些指引,方便其快速上手Julia。</p> <h1 id="准备">准备</h1> <h2 id="下载和安装">下载和安装</h2> <p><a href="https://julialang.org/downloads/">官网</a>提供了各个系统下的安装包,不过我建议新手先安装<a href="https://juliacomputing.com/products/juliapro.html">Julia PRO</a>,这个有点类似Python下的Anaconda,目前版本还没有1.0,这里以0.6.2为例,安装Julia的同时还会安装一些Julia下常见的包(省去了许多包管理麻烦,当然,也会带来一些新的小问题,这个碰到的时候再细说)。</p> <p>由于我日常开发在Windows下,所以以下内容中操作系统相关的部分都以Windows为主(Linux和Mac下要容易很多,应该问题不大)。</p> <p>安装后直接双击桌面的<code>JuliaPro - Command Prompt 0.6.2.1</code>快捷键即可打开Julia的REPL界面,就跟IPython有点像(后面会详细介绍其与IPython的联系和区别),建议将JuliaPRO安装路径下<code>Julia-0.6.2</code>中的bin目录添加到环境变量Path中,这样可以直接在git-bash或者cmd中直接运行julia(如下图)。</p> <div class="figure"> <img alt="Julia_REPL" src="/static/essay_resources/From_Python_to_Julia/Julia_REPL.png"/> <p class="caption">Julia_REPL</p> </div> <h3 id="windows下开发学习的一点个人建议">Windows下开发学习的一点个人建议</h3> <p>我个人对Windows的态度是不吹不黑,但老实说,Windows下开发的体验很差。强烈建议安装个Debian/Ubuntu的WSL,除了不能直接跟底层GPU打交道外,我目前的开发中没遇到什么大问题,日常一般是开了两个terminal,一个显示REPL,另一个在Debian中切到对应的目录下做文件读取和服务管理。</p> <h2 id="编辑器">编辑器</h2> <p>一般Python的开发会选择PyCharm之类的IDE,不过Julia下还没有与PyCharm对应的IDE。安装JuliaPRO之后,桌面会有一个<code>Juno for JuliaPro 0.6.2.1</code>,其实就是一个Atom上套了个插件,如果你原来就用Atom的话,也许你会喜欢这个编辑环境。我个人在Windows下倾向使用VSCode+Julia插件,不过社区里也有人用Vim和Emacs,选择一个适合你的就好。</p> <div class="alert alert-warning"> <p>使用VSCode的Julia插件的时候,需要配置(File -&gt; Preferences -&gt; Settings)julia的路径,如"julia.executablePath": "D:\workspace\software\JuliaPro-0.6.2.1\Julia-0.6.2\bin\julia.exe"。</p> </div> <p>当然,你一定注意到了桌面还有一个<code>Jupyter 0.6.2.1</code>,其实就是Jupyter+IJulia的内核,如果你习惯Jupyter的话几乎可以做到无缝迁移。</p> <h2 id="repl">REPL</h2> <p>在IPython中,最常用的一个功能是查看函数的帮助文档,通过在函数/方法名后面加一个<code>?</code>来实现,不过在Julia的REPL中,是先输入<code>?</code>再输入函数名:</p> <div class="figure"> <img alt="使用?的例子" src="/static/essay_resources/From_Python_to_Julia/help_ENV.png"/> <p class="caption">使用?的例子</p> </div> <p>另外,在IPython中,经常会执行一些类似<code>ls</code>,<code>pwd</code>等等系统命令,这类命令在Julia中是通过函数来实现的(如<code>readdir()</code>, <code>pwd()</code>)。此外,原来<code>%%</code>的魔法函数也有对应的函数实现。不过如果是在REPL中,可以直接输入<code>;</code>进入shell模式,执行各种命令(自行对比iPython中加了<code>!</code>执行命令)。</p> <h2 id="包管理">包管理</h2> <p>新版的Pkg管理模块比以前好用了很多,功能上有点像内置了一个<a href="https://github.com/pypa/pipenv">pipenv</a>。打开Julia的REPL后,按<code>]</code>进入Pkg管理模块,通过<code>generate Foo</code>即可新建一个Project,然后<code>add Bar</code>可以添加依赖,更多操作可以查看<a href="https://docs.julialang.org/en/latest/stdlib/Pkg/">Pkg</a>的Doc。</p> <h1 id="语法细节">语法细节</h1> <p>下面从一些日常的操作来介绍如何从Python迁移到Julia。</p> <h2 id="代码结构">代码结构</h2> <p>在Python中,代码是以目录结构组织起来的,然后用<code>__init__.py</code>来区分包和普通的目录,Julia的代码以模块(Modules)组织起来的,一个典型的Julia文件有如下结构:</p> <pre class="language-julia"><code>module MyModule using Lib using BigLib: thing1, thing2 import Base.show export MyType, foo struct MyType x end bar(x) = 2x foo(a::MyType) = bar(a.x) + 1 show(io::IO, a::MyType) = print(io, "MyType $(a.x)") end</code></pre> <p>首先,<code>module</code>定义了整个模块,其作用范围一直到最后一行<code>end</code>,module名称一般采用驼峰式,在<code>module</code>中也可以定义一个<code>__init__</code>函数,其功能有点类似Python下的<code>__init__.py</code>文件(不完全对应,<code>__init__.py</code>某种程度上提供了下面<code>export</code>的功能)。</p> <p><code>using Lib</code>有点类似Python中的<code>from xxx import *</code>,此刻你脑海中想到的第一个问题应该是命名冲突!对,我一开始也这么想的,后来习惯了发现,好像问题不大,因为Julia是带类型的,一定程度上通过重载缓解了这个问题。</p> <p><code>using BigLib: thing1, thing2</code>就是显式地导入,类似<code>from xxx import foo, bar</code>。</p> <p>个人感觉,Julia中<code>import</code>主要用于扩展某个函数的时候使用,这一点<code>using</code>做不到(<code>using</code>只用于提供查找变量的搜索空间)。<del><code>importall</code>就是<code>import</code>的扩展版</del>(新版本中<code>importall</code>已废弃)。</p> <p><code>export</code>则是,将该<code>module</code>内部的某些变量暴露出去。在Python中可能是通过<code>__foo</code>等来实现的。</p> <p>函数部分的区别和联系后面再详述。</p> <p>有时候,我们想直接从某个文件中导入,需要用<code>include("foo.jl")</code>来实现。</p> <p>有一个函数<code>whos()</code>可以用来查看当前的变量信息,有点像Python中的<code>globals()</code>。</p> <h2 id="数据结构">数据结构</h2> <h3 id="index">index</h3> <div class="alert alert-danger"> <p>在Julia中,按index访问访问元素的时候,是从1而不是0开始的。这大概是我自己从Python迁移到Julia最不习惯的一方面(一不小心就出错了)。访问最后一个元素要使用<code>end</code>关键字,有点像Python中的<code>-1</code>。另外,Julia中索引的时候,是左闭右闭<code>[]</code>的,不是Python中的左闭右开<code>[)</code></p> </div> <h3 id="string">string</h3> <ul> <li>在Julia中,Char和String分别是用单引号<code>'</code>和双引号<code>"</code>初始化的,不能像Python中一样混用,三个双引号的用法是一致的。</li> <li>字符串的连接采用<code>*</code>符号,而不是<code>+</code>。</li> <li>字符串中支持变量替换,这个在Python3.6中有支持。二者只是表示上有所差别,Julia中是<code>"foo is ${1 + 1}"</code>,Python3.6中是<code>f"foo is {1 + 1}"</code>。</li> <li>前面你可能注意到了,Python中可以在string前增加<code>f""</code>,<code>r""</code>等实现某种特殊功能,在Julia中,可以自定义许多非标准的字符串解释器,这个很强大,谁用谁知道。</li> <li>读文件有一点稍稍不同,需要调用<code>eachline</code>函数,此外,Python中常常会用到的<code>strip</code>,Julia中对应的是<code>chomp</code>。</li> </ul> <h3 id="list">list</h3> <p>Python中最常用的就是list了,Julia中并没有严格与之对应的数据结构,不过,就一般使用而言,可以把一维的<code>Array{Any,1}</code>拿来用。</p> <ul> <li><code>push!</code>对应<code>append</code>,<code>append!</code>对应<code>extend</code>方法,这里提一下,Julia还是函数式的风格,而Python中平常使用还是面向对象的风格为主。此外还有<code>prepend!</code>等等对用的函数。</li> <li>Python中的list comprehension 一样可以使用,<code>[x * 2 for x in range(1,10) if x &gt; 3]</code>。我个人为了保持代码风格的一致性,习惯用<code>map</code>,<code>filter</code>处理。</li> <li>Python中<code>list</code>可以通过<code>*</code>来扩展,Julia中的Array并不能这样做(用<code>repeat</code>函数实现)。</li> </ul> <h3 id="tuple">Tuple</h3> <p>和Python中基本一致,<del>未来也会</del>目前支持类似Python3中的<code>NamedTuple</code>。</p> <h3 id="numpy">numpy</h3> <p>这里单独说明下,Julia可以看作是自带了numpy的Python,Numpy的许多操作都能在<a href="https://docs.julialang.org/en/stable/stdlib/arrays/">标准库中Array</a>部分找到。</p> <p>记录几个需要注意的点:</p> <ul> <li><code>max</code>和<code>maximum</code>的区别</li> <li>初始化矩阵的时候,julia中用<code>;</code>来区分行</li> <li>numpy中的数据类型一般通过<code>dtype</code>参数来指定(通过<code>.astype</code>做类型转换),而Julia中,则是采用带参数类型函数来初始化(如<code>Array{T}(m,n)</code>)</li> <li>Julia中,array的存储顺序是按照列存储的,因此对矩阵赋值的时候需要注意区别。<code>A[[1,2], [2,3]] = [2, 3,4 ,5]</code>和<code>[[1,2], [2,3]] = [2 3;4 5]</code>的结果是有区别的。</li> </ul> <h3 id="iterator">iterator</h3> <ul> <li>在python中经常会用到<code>yield</code>来生成迭代器,Julia中似乎没有找到对应的,我看到过一个相关的<a href="https://discourse.julialang.org/t/pygen-python-style-generators/3451/53">讨论</a>,一定程度上可以用Channel来实现。但仍然有些繁琐,希望以后能支持。</li> <li>Python中有个itertools也经常用,这个在Julia中都是通过macro实现的,我个人感觉<a href="https://github.com/MikeInnes/Lazy.jl">Lazy.jl</a>基本够用,有些个性化需求的话,自己写一个也很方便。</li> </ul> <h3 id="struct">struct</h3> <p>对比Python中的class</p> <h3 id="macro">macro</h3> <p>这个先放这里,在Python中很少会接触,前期迁移到Julia中的时候,只需要掌握一些常用的宏就可以了。</p> <h2 id="grammar">Grammar</h2> <p>Julia中有些Python中没有的语法糖,个人感觉有些还算有意思,有些就比较累赘了。</p> <ul> <li><code>do</code>,这个没啥意思,就是把lambda搬了个位置</li> <li>control flow上,因为没有了Python中的intent,需要用<code>end</code>来控制,虽然麻烦了点,但也可以理解。另外没有<code>pass</code>关键字。</li> <li><code>let</code>用于局部的变量重绑定,特定时候可以解决命名冲突问题,其实有很多方式绕开。有点怀念Clojure中的<code>let</code></li> </ul> <h3 id="function">function</h3> <p>如果不考虑类型的话,Julia下的function和Python相比,主要的差别是:</p> <ul> <li><code>end</code>作为结尾,默认返回最后一个表达式,基本不需要<code>return</code>语句</li> <li>positional,optional参数写法保持一致,keyword参数的写法用<code>;</code>做了分隔,不过一般都这么写: <code>julia function f(x; y=0, kwargs...) ### end</code></li> <li>对于one-line definition,一般用<code>f(x) = x</code>的形式,如果一个表达式不够用,可以这样<code>f(x) = (step1;step2;step3)</code>。</li> <li>lambda函数的写法看起来更简洁点,<code>x -&gt; x^x</code></li> </ul> <h3 id="operator">operator</h3> <ul> <li><code>.</code>,向量化操作,起初觉得没啥意思,记得Clojure有个macro实现类似的语义,后来发现,这个点真心简洁</li> <li><code>+=</code>,注意Julia中默认是immutable的,所以类似<code>A += 3</code>的操作其实是做了个rebinding</li> <li><code>...</code>,Python中会用<code>*</code>和<code>**</code>来对函数的参数解绑,在Julia是<code>...</code></li> <li>注意区分<code>==</code>和<code>===</code>,类比Python中的<code>==</code>和<code>is</code></li> </ul> <h3 id="type">type</h3> <p>这个应该是从Python转Julia最大的区别,没有捷径,好好阅读文档。这里我记下点自己觉得比较常用的几个知识点:</p> <ul> <li><code>typemax</code>和<code>typemin</code>可以提供某些类型的取值范围(Int64,Float32等)</li> </ul> <h1 id="接下来">接下来?</h1> <ul> <li>花点时间通读<a href="https://docs.julialang.org/en/stable/">Julia Documentation</a>。</li> <li>看看<a href="https://en.wikibooks.org/wiki/Introducing_Julia">Introducing Julia</a>这个wikibook,日常使用中的问题都可以在这里找到答案。</li> </ul> </body>Jun TianSun, 12 Aug 2018 21:41:48 GMTFrom_Python_to_JuliaQuantum_Computinghttps://tianjun.me/essays/Quantum_Computing<body> <h1 id="量子计算入门">量子计算入门</h1> <p>本文用于记录我学习量子计算的过程。</p> <p>(Suspended due to priority change.)</p> <h2 id="some-key-concepts">Some Key Concepts</h2> <h3 id="bloch-sphere">Bloch Sphere</h3> <h2 id="resources">Resources</h2> <h3 id="web-pages">Web Pages</h3> <ul> <li><p><a href="https://medium.com/@johncoogan/the-best-resources-for-learning-about-quantum-computing-4fcc9f3cbe56">The best resources for learning about quantum computing</a></p> <p>目前找到的最好的入门资料汇总。</p></li> <li><p><a href="https://github.com/krishnakumarsekar/awesome-quantum-machine-learning">Awesome quantum machine learning</a></p> <p>量子计算与机器学习的结合。</p></li> </ul> <h3 id="books">Books</h3> <h4 id="introduction-to-the-theory-of-computation"><a href="https://book.douban.com/subject/12986396/">Introduction to the Theory of Computation</a></h4> <div class="figure"> <img alt="Introduction_to_the_Theory_of_Computation.jpg" src="/static/essay_resources/Quantum_Computing/Resources/img/Introduction_to_the_Theory_of_Computation.jpg"/> <p class="caption">Introduction_to_the_Theory_of_Computation.jpg</p> </div> <p>这本书用来补一下有关计算复杂度的知识。 很意外,这本书的Part 1 部分,填补了之前读<a href="https://book.douban.com/subject/3136252/">EOPL</a>的一些关于Parser的空白。关于自动机,正则表达式,CFG的讲解一气呵成。Part2部分对图灵机有了更多的了解,halting啥的不再停留在表面的认识。读完Part3之后对复杂性有了新的的认识,之前看到有一个书评说,如果你在别人高谈阔论P,NP,NP-complete等问题时感到一脸懵逼,请立即抱起这本书,这里有你想要的答案。</p> <h4 id="quantum-computing-since-democritusqcsd"><a href="https://book.douban.com/subject/12030716/">Quantum Computing Since Democritus</a></h4> <div class="figure"> <img alt="Quantum_Computing_Since_Democritus.jpg" src="/static/essay_resources/Quantum_Computing/Resources/img/Quantum_Computing_Since_Democritus.jpg"/> <p class="caption">Quantum_Computing_Since_Democritus.jpg</p> </div> <p>这本书没法评价。</p> <p>因为压根没读懂,水平有限,摊手......</p> <p>前几章跟Quantum Computing的关系不大,开篇的冷笑话,真的好冷......以至于我真的只记住了那句话(有兴趣的话,可以去看看作者的<a href="https://www.scottaaronson.com">博客</a>):</p> <blockquote> <p>But if quantum mechanics isn't physics in the usual sense - if it's not about matter, or energy, or waves, or particles -then what <em>is</em> it about? From my perspective, it's about information and probabilities and observables, and how they relate to each other.</p> </blockquote> <p>我能体会到作者独特的视角,无奈,自己相关的基础并不扎实,强行读到了第十章,后面的部分只是草草翻了下。后来偶然在网上看到了别人写的一篇<a href="http://slatestarcodex.com/2014/09/01/book-review-and-highlights-quantum-computing-since-democritus/">review</a>,深有同感。总的来说,如果你看到chapter1~8的标题之后,确认你对相关内容不那么陌生(不是熟悉或精通),那么可以断定这是一本非常值得一读的书,否则真的很难跟上作者的脚步(当然,你也可以像我一样,不妨先读读试试~)。Anyway,即使只读了前十章,也依然收获颇丰,许多亮点与前面提到的那篇<a href="http://slatestarcodex.com/2014/09/01/book-review-and-highlights-quantum-computing-since-democritus/">review</a>有许多共通之处。这里只说我感受最深的一点:</p> <blockquote> <p>There are two ways to teach quantum mechanics. The first way - which for most physicists today is still the only way - follows the historical order in which the ideas were discovered... The second way to teach quantum mechanics eschews a blow-by-blow account of its discovery, and instead <em>starts directly from the conceptual core</em> - namely, a certain generalization of the laws of probability to allow minus signs )and more generally, complex numbers).</p> </blockquote> <p>作者首先提到了目前学习量子原理的两种方式(作者在书中采取的是后者),然后说道:</p> <blockquote> <p><em>Quantum mechanics is what you would inevitably come up with if you started from probability theory, and then said, let's try to generalize it so that the numbers we used to call "probabilities" can be negative numbers. As such, the theory could have been invented by mathematicians in the nineteenth century without any input from experiment.</em> <strong>It wasn't, but it could have been</strong>.</p> </blockquote> <p>是的,<strong>实验先于理论</strong>。另外两个类似的例子是<strong>进化论</strong>和<strong>狭义相对论</strong>。读到这里时,我联想到的是目前深度学习的现状,何其相似。作者认为:</p> <blockquote> <p>More often than not, the <em>only</em> reason we need experiments is that we're not smart enough.</p> </blockquote> <h4 id="q-is-for-quantumqfq"><a href="https://book.douban.com/subject/27167701/">Q is for Quantum</a></h4> <div class="figure"> <img alt="Q_IS_FOR_QUANTUM" src="/static/essay_resources/Quantum_Computing/Resources/img/Q_IS_FOR_QUANTUM.jpg"/> <p class="caption">Q_IS_FOR_QUANTUM</p> </div> <p>吸取读上一本书的教训,还是先从简单点的入手。这本小册子很薄,总共150多页。作者构建了一个PETE BOX,用图形化的语言来阐述量子计算相关的一些概念。</p> <p>PART 1 部分,对比经典计算机中的与或非门,清晰地描述了<em>量子门</em>(PETE BOX)的特性。</p> <p>关键词:</p> <ol style="list-style-type: decimal"> <li>superposition / misty state</li> <li>interfere</li> <li>collision</li> </ol> <p>PART 2 部分,用一个很形象的例子(心灵感应?)讲清楚了一个很有意思的现象:</p> <p><a href="https://en.wikipedia.org/wiki/Quantum_entanglement"><strong>Entanglement</strong></a></p> <p>PART 3展开讨论了什么是<strong>REALITY</strong>,这部分相比前两部分理解得没那么透彻。照惯例,附上两篇书评:</p> <ul> <li><a href="https://tomate.wordpress.com/2017/10/03/q-is-for-quantum-reality/">Q is for Quantum &amp; “reality”</a></li> <li><a href="http://janjanjan.uk/2017/08/29/review-q-quantum-terry-rudolph/">Review: Q is for Quantum by Terry Rudolph</a></li> </ul> <h4 id="picturing-quantum-processespqp"><a href="https://book.douban.com/subject/26995979/">Picturing Quantum Processes</a></h4> <div class="figure"> <img alt="Picturing Quantum Processes" src="/static/essay_resources/Quantum_Computing/Resources/img/picturing_quantum_processes.jpg"/> <p class="caption">Picturing Quantum Processes</p> </div> <p>感觉这本书更适合在有一点点量子计算的基础之后再读,然后应该会有种耳目一新的感觉,居然还能采用这种方式来描述。目前读了大约1/5,基本能跟上作者的节奏,读这本书之前稍微回顾下线性计算会好点,有利于融会贯通。暂时需要先放下来,因为读这本书对我这种新手来说会比较累,需要同时理解两套理念(尽管二者并非独立的关系),留到后面有一定基础了再看下。</p> <h4 id="quantum_computation_and_quantum_informationqcqi"><a href="https://book.douban.com/subject/6937989/">Quantum_Computation_and_Quantum_Information</a></h4> <div class="figure"> <img alt="Quantum_Computation_and_Quantum_Information" src="/static/essay_resources/Quantum_Computing/Resources/img/Quantum_Computation_and_Quantum_Information.jpg"/> <p class="caption">Quantum_Computation_and_Quantum_Information</p> </div> <p>这本书同步进行中。</p> <h4 id="linear-algebra-done-right-3rd-edladr"><a href="https://book.douban.com/subject/26265880/">Linear Algebra Done Right (3rd ed)</a></h4> <div class="figure"> <img alt="Linear_Algebra_Done_Right" src="/static/essay_resources/Quantum_Computing/Resources/img/Linear_Algebra_Done_Right.jpeg"/> <p class="caption">Linear_Algebra_Done_Right</p> </div> <p>有些线性代数相关的部分需要回顾下。</p> <p>这本书对应的<a href="http://linearalgebras.com/">Solution</a></p> </body>Jun TianMon, 23 Jul 2018 18:14:12 GMTQuantum_ComputingAn_Overview_of_Existing_Reinforcement_Learning_Librarieshttps://tianjun.me/essays/An_Overview_of_Existing_Reinforcement_Learning_Libraries<body> <h1 id="深度强化学习相关库概览">深度强化学习相关库概览</h1> <blockquote> <p>知己知彼,方能百战不殆!</p> </blockquote> <p>在写一个新的DRL库之前,不妨先学习下已有工具包的组织架构,以及不同的工具包都有哪些优缺点。</p> <p>以下是本文重点考虑的几个库(也欢迎推荐其它优秀的库):</p> <ul> <li><a href="https://github.com/ShangtongZhang/DeepRL">DeepRL</a> 这个库基于PyTorch,作者之前写了<em>Reinforcement Learning: An Introduction</em>的Python实现。似乎是前不久刚public的,可以看出作者的Python编码能力在这一年里似乎进步了不少😋,总之,DeepRL的结构还是蛮清晰的。</li> <li><a href="https://github.com/NervanaSystems/coach">Coach</a> 据说是架构最清晰的一个库,支持TensorFlow。</li> <li><a href="https://github.com/reinforceio/tensorforce">TensorForce</a> 也是基于TensorFlow的一个库。</li> <li><a href="https://github.com/ray-project/ray/tree/master/python/ray/rllib">RLlib</a> 这个框架需要花点时间仔细研读下源码,里面封装了一个Actor模型用来处理分布式并发执行的逻辑依赖问题,做法跟Coach的那类Parameter sharing很不一样,从论文上来看,效率也要高很多,感觉是未来的一个趋势。得花时间想想channel模型是否适用(毕竟Julia目前并没有内置的actor模型)。</li> </ul> <h2 id="deeprl">DeepRL</h2> <p>整个库是围绕着Agent对象展开的,通过config初始化Agent对象,然后在外围执行<code>run_iterations</code>或<code>run_episodes</code>控制进度。OO的思想有点重,以至于初始化的时候需要指定的部分非常多。</p> <h3 id="pros">Pros</h3> <ul> <li>Agent的划分很清晰,基本都控制在100行代码以内</li> <li>在Gym的基础之上又套了一层Task,这个值得借鉴</li> <li>在对PyTorch封装的部分,抽象出了<code>network_body</code>和<code>network_head</code>,姑且理解为编码层和输出层,这个也值得学习借鉴</li> <li>有logger模块,后面自己实现的时候,可以结合类似TensorBoard的工具打log</li> </ul> <h3 id="cons">Cons</h3> <ul> <li>没有文档</li> <li>Agent中与环境交互部分耦合得比较紧,把一些step和rollout单独划分出来会更简洁些</li> <li>一些小的模块作者已经在注意抽象了,比如replay等,但是接口还需改进</li> <li>并行化,跑多个副本</li> </ul> <h2 id="coach">Coach</h2> <p>目前粗略看了下,真的是相当清晰。要是有一些Guide知道如何新写一个Agent,怎么做Compare等等,应该会有更多的人Envolve进来。后面写的时候应该会反复参考这个库。</p> <p>怎么并行化运行多个task,以及维护一个Parameter Server,暂时没有想好如何在Julia中实现,直觉告诉我这点应该是Julia的优势。</p> <h2 id="tensorforce">TensorForce</h2> <p>目前文档还不是很全,但目前看到的几点值得学习的地方是:</p> <ul> <li>contrib中,对不同环境做了统一抽象,这个跟我正在做的不谋而合</li> <li>agent虽然也按类做了划分,但是感觉不如Coach做得好</li> <li>跟TF绑定得有点太紧了</li> </ul> </body>Jun TianSun, 22 Jul 2018 15:46:09 GMTAn_Overview_of_Existing_Reinforcement_Learning_LibrariesWrite_a_Reinforcement_Learning_Package_in_Julia_from_Scratchhttps://tianjun.me/essays/Write_a_Reinforcement_Learning_Package_in_Julia_from_Scratch<body> <h1 id="从零开始用julia写一个reinforcement-learning的库">从零开始用Julia写一个Reinforcement Learning的库</h1> <p>今天看到Julia-0.7的alpha版本出来了,1.0应该也快了。我打算在这里完整记录下如何从零开始写一个Julia的库,估计有好多坑......</p> <p>从<a href="http://yuandong-tian.com/">田渊栋</a>的一个<a href="http://yuandong-tian.com/presentation2018h1.pdf">talk</a>里摘个图:</p> <div class="figure"> <img alt="how_to_do_well_in_reinforcement_learning.png" src="/static/essay_resources/Write_a_Reinforcement_Learning_Package_in_Julia_from_Scratch/img/how_to_do_well_in_reinforcement_learning.png"/> <p class="caption">how_to_do_well_in_reinforcement_learning.png</p> </div> <p>当然,右下角的配图(Python/C++)要打个❓</p> <h2 id="prepare">Prepare</h2> <h3 id="install">Install</h3> <p>在<a href="https://julialang.org/downloads/">Download</a>页面的Upcoming Release部分可以找到对应平台的二进制文件。</p> <h3 id="package-management">Package Management</h3> <p>新版的Pkg管理模块比以前好用了很多,功能上有点像内置了一个<a href="https://github.com/pypa/pipenv">pipenv</a>。打开Julia的REPL后,按<code>]</code>进入Pkg管理模块,通过<code>generate Foo</code>即可新建一个Project,然后<code>add Bar</code>可以添加依赖,更多操作可以查看<a href="https://docs.julialang.org/en/latest/stdlib/Pkg/">Pkg</a>的Doc。</p> <h3 id="dependent-packages">Dependent Packages</h3> <ul> <li><p>✅<a href="https://github.com/JuliaPy/PyCall.jl">PyCall</a></p> <p>PyCall用于跟OpenAI的<a href="https://gym.openai.com/">Gym</a>进行交互。封装了一个<a href="https://github.com/findmyway/Environment.jl">Environment.jl</a>,目标是提供一个统一的接口。<del>暂时还不支持Julia-0.7。 执行<code>develop MacroTools</code>更新MacroTools到master分支之后便正常了。</del></p></li> <li><p><a href="https://github.com/denizyuret/Knet.jl">Knet</a>/<a href="https://github.com/FluxML/Flux.jl">Flux</a></p> <p>用于提供DL基础的工具包。我大致读完过Flux的源码,结构比较简单,但是有一些工具函数有所缺失。Knet相对而言文档丰富些,我暂时还没确定先用哪个(<del>哪个先支持Julia0.7就先用哪个吧😋</del>)。</p> <p>仔细考虑了下,决定用Flux,主要是可以很方便地在上面做个性化定制,Slack上相关的交流也很多(这点很重要)。具体关于Flux.jl的介绍可以查看最近写的一篇详细介绍:<a href="https://tianjun.me/essays/An_Introduction_to_Flux.jl">An Introduction to Flux.jl</a></p></li> <li><p>(Optional)<a href="https://github.com/JuliaInterop/CxxWrap.jl">CxxWrap</a></p> <p>有条件的话用CxxWrap封装一个可视化的库。</p> <ul> <li><p><a href="https://github.com/tensorflow/tensorboard">TensorBoard</a></p> <p>有个小哥已经封装了<a href="https://github.com/oxinabox/UniversalTensorBoard.jl">TensorBoard.jl</a>,不知道效果怎样,看README只是写了个prototype。</p></li> <li><p>✅<a href="https://github.com/findmyway/VisualDL.jl">VisualDL.jl</a></p> <p>(尝试用CxxWrap封装了下,有个bug一直没调试成功,写入数据总是全是0.0,无奈)。后面如果确实有需要的话,用PyCall封装下Python的接口。</p> <p>完成了用PyCall的封装,顺便熟悉了下发布一个package的流程,包括Unit Test, Travis, Documenter, Release等等。</p></li> </ul> <p>总的来说,CxxWrap用来对一些已经支持Python的C++库做封装还是蛮方便的,PyCall也很好用(只是需要注意1-based index和矩阵是按列存储的这两点)。</p></li> </ul> <h2 id="design">Design</h2> </body>Jun TianSun, 22 Jul 2018 15:40:37 GMTWrite_a_Reinforcement_Learning_Package_in_Julia_from_Scratch