Tian Junhttps://tianjun.me/rssAll about Tian Junen-USTue, 26 Mar 2019 03:10:43 GMTrfeed v1.1.1https://github.com/svpino/rfeed/blob/master/README.mdA_Guide_to_Wrap_a_C++_Library_with_CXXWrap.jl_and_BinaryBuilder.jl_in_Juliahttps://tianjun.me/essays/A_Guide_to_Wrap_a_C++_Library_with_CXXWrap.jl_and_BinaryBuilder.jl_in_Julia<body> <h1 id="a-guide-to-wrap-a-c-library-with-cxxwrap.jl-and-binarybuilder.jl-in-julia">A Guide to Wrap a C++ Library with CxxWrap.jl and BinaryBuilder.jl in Julia</h1> <p>The following parts are not covered here:</p> <ol style="list-style-type: decimal"> <li>How to write the wrapper code in C++. (It is detailed <a href="https://github.com/JuliaInterop/CxxWrap.jl">here</a>. And I find the <a href="https://github.com/JuliaInterop/libcxxwrap-julia/tree/master/examples">examples</a> are also very useful!)</li> </ol> <p>And you will learn the following parts after reading this post.</p> <ol style="list-style-type: decimal"> <li>How to quickly debug your code?</li> <li>The missing parts that are not documented in <a href="https://github.com/JuliaPackaging/BinaryBuilder.jl">BinaryBuilder.jl</a> and <a href="https://github.com/JuliaInterop/CxxWrap.jl">CxxWrap.jl</a></li> </ol> <h2 id="prepare">Prepare</h2> <p>First things first. Suppose you want to write a Julia wrapper for a C++ package (I'll take the one I wrote, <a href="https://github.com/PaddlePaddle/VisualDL">VisualDL</a>, for example). You need to fork that repo into your own account and clone that repo to your local computer. Then install necessary dependencies according to the document and make sure you can compile it successfully.</p> <p>The next step is to write the wrapper code. Usually you would like to add a flag in the <code>CMakeLists.txt</code> file to signafy whether to build the Julia wrapper or not. Like <a href="https://github.com/findmyway/VisualDL/blob/julia/CMakeLists.txt#L37">this</a></p> <pre><code>option(WITH_JULIA "Compile VisualDL with Julia" OFF)</code></pre> <p>Then make a seperate folder containing all your source codes and corresponding <code>CMakeLists.txt</code>. And include that folder in the root <code>CMakeLists.txt</code> file. Like <a href="https://github.com/findmyway/VisualDL/blob/julia/CMakeLists.txt#L76-L78">this</a></p> <pre><code>if(WITH_JULIA) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/visualdl/julia) endif()</code></pre> <p>For the simplest case, only a <code>CMakeLists.txt</code> file and <code>your_wrapper_code.cc</code> are needed. Like <a href="https://github.com/findmyway/VisualDL/tree/julia/visualdl/julia">this</a></p> <p>Following the instructions from <a href="https://github.com/JuliaInterop/CxxWrap.jl">CxxWrap.jl</a>, you should find it easy to write the wrapper codes. If the project you are working on already has a python wrapper, I strongly suggest you to take look at it first. And it will save you a lot of time.</p> <p>Then you need to specify how to build your target in the <code>CMakeLists.txt</code>, for <a href="https://github.com/findmyway/VisualDL/blob/julia/visualdl/julia/CMakeLists.txt">example</a>:</p> <pre><code># 1. find JlCxx find_package(JlCxx REQUIRED) # 2. add libraries and dependencies add_library(im ${PROJECT_SOURCE_DIR}/visualdl/logic/im.cc) add_library(sdk ${PROJECT_SOURCE_DIR}/visualdl/logic/sdk.cc ${PROJECT_SOURCE_DIR}/visualdl/utils/image.h) add_dependencies(im storage_proto) add_dependencies(sdk entry binary_record storage storage_proto eigen3) add_library(vdljl SHARED vdljl.cc) add_dependencies(vdljl im entry tablet sdk storage protobuf eigen3) # 3. specify targets target_link_libraries(vdljl PRIVATE JlCxx::cxxwrap_julia entry binary_record im tablet storage sdk protobuf ${OPTIONAL_LINK_FLAGS}) # 4. install install(TARGETS vdljl RUNTIME DESTINATION lib ARCHIVE DESTINATION lib LIBRARY DESTINATION lib)</code></pre> <h2 id="local-debug">Local Debug</h2> <p>Now you have finished all the necessary changes to the original package, you may want to get the compiled library.</p> <p>Let's install <code>CxxWrap</code> in Julia first. Enter the package mode and add <code>CxxWrap</code></p> <pre class="language-julia"><code>(v1.1) pkg&gt; add CxxWrap</code></pre> <p>Now you have <code>CxxWrap</code> and the underlying <a href="https://github.com/JuliaInterop/libcxxwrap-julia"><code>libcxxwrap-julia</code></a> installed. We can print the library path and the cmake file path:</p> <pre class="language-julia"><code>julia&gt; using CxxWrap julia&gt; CxxWrap.jlcxx_path "/home/tj/.julia/packages/CxxWrap/KcmSi/deps/usr/lib/libcxxwrap_julia.so" julia&gt; julia&gt; readdir(joinpath(dirname(CxxWrap.jlcxx_path), "cmake", "JlCxx")) 5-element Array{String,1}: "FindJulia.cmake" "JlCxxConfig.cmake" "JlCxxConfigExports-release.cmake" "JlCxxConfigExports.cmake" "JlCxxConfigVersion.cmake"</code></pre> <p>What we are interested in here is the the path of <code>joinpath(dirname(CxxWrap.jlcxx_path), "cmake", "JlCxx")</code> (Here is the <code>/home/tj/.julia/packages/CxxWrap/KcmSi/deps/usr/lib/cmake/JlCxx</code>). Now we can compile the package with <code>JlCxx_DIR</code> properly set:</p> <pre><code>$ mkdir build $ cd build $ cmake -DWITH_JULIA=ON -DJlCxx_DIR=/home/tj/.julia/packages/CxxWrap/KcmSi/deps/usr/lib/cmake/JlCxx .. $ make $ make install</code></pre> <p>Then you can load the compiled library in the Julia REPL for testing:</p> <pre class="language-julia"><code>module VisualDL using CxxWrap @wrapmodule("/absolute/path/to/your/lib") function __init__() @initcxx end end using .VisualDL</code></pre> <h2 id="binarybuilder">BinaryBuilder</h2> <p>In order to leverage the BinaryBuilder, we create an independent repo (for example, <a href="https://github.com/findmyway/VisualDLBuilder">VisualDLBuilder</a>) to build the tarballs. Be careful with the following lines:</p> <ol style="list-style-type: decimal"> <li>Don't forget to specify the <code>compiler_abi</code> field in the platforms like <a href="https://github.com/findmyway/VisualDLBuilder/blob/master/build_tarballs.jl#L7">this</a> here, if your code doesn't compile with old gcc.</li> <li>For the <a href="https://github.com/findmyway/VisualDLBuilder/blob/master/build_tarballs.jl#L10"><code>sources</code></a> in the <code>build_tarballs.jl</code>, you can specify an absolute local path pointing to the modified package above for debugging. After making sure that everything works fine, you make a PR then ask the repo owner to tag a new release. And then change this variable into the <code>url =&gt; hash</code> form.</li> <li>Do not forget to run <code>make install</code> at the end of <a href="https://github.com/findmyway/VisualDLBuilder/blob/master/build_tarballs.jl#L19">script</a>. Seriously!</li> <li>In the <code>dependencies</code> <a href="https://github.com/findmyway/VisualDLBuilder/blob/master/build_tarballs.jl#L26">part</a>, remember to add both CxxWrap and Julia.</li> </ol> <p>As for the <code>.travis.yml</code> <a href="https://github.com/findmyway/VisualDLBuilder/blob/master/.travis.yml#L21">file</a>, for now you need to specify the <code>MbedTLS</code> to version <code>0.6.6</code> due to the <a href="https://github.com/JuliaWeb/MbedTLS.jl/issues/193">error</a>.</p> <p>Althought there's a helper function in BinaryBuilder.jl named <code>setup_travis</code> to help you set up the deploy step, I never succeed. So I'd suggest you to install <a href="https://github.com/travis-ci/travis.rb#installation">travis cli</a> and run <strong>travis login --pro</strong> first!!! Then <code>travis setup releases</code>.</p> <h2 id="the-julia-wrapper-package">The Julia Wrapper Package</h2> <p>This is the final step. You create a new package. Add the <code>CxxWrap</code> as your dependency. Rename the build file you get by BinaryBuilder to <code>build.jl</code> and put it into the <code>deps</code> folder. And you expect to get the <code>deps.jl</code> after running <code>julia deps/build.jl</code>. Unfortunately, you'll see an error like this:</p> <pre><code>ERROR: LoadError: LibraryProduct(nothing, ["libvdljl"], :libvdljl, "Prefix(/home/tianjun/tmp/visualdl/deps/usr)") is not satisfied, cannot generate deps.jl!</code></pre> <p>The reason is that we need the shared package of <code>jlcxx</code>. So remember to put <code>using CxxWrap</code> in the first line of <code>build.jl</code>. Then everything should work as you wish.</p> </body>Jun TianMon, 04 Mar 2019 00:35:31 GMTA_Guide_to_Wrap_a_C++_Library_with_CXXWrap.jl_and_BinaryBuilder.jl_in_JuliaLearn_You_Some_Reinforcement_Learninghttps://tianjun.me/essays/Learn_You_Some_Reinforcement_Learning<body> <h1 id="learn-you-some-reinforcement-learning">Learn You Some Reinforcement Learning</h1> <p>Here I will list everything that is helpful with a short note while implementing the reinforcement learning package <a href="https://github.com/Ju-jl/Ju.jl">Ju.jl</a> in Julia.</p> <h2 id="slides">Slides</h2> <ul> <li><p><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./slides/MSRA_RL_Essentials.pdf">An Introduction to Reinforcement Learning</a></p> <p>A concise introduction to RL by <a href="http://research.microsoft.com/users/taoqin/">Tao Qin</a> from MSRA.</p></li> </ul> <h2 id="papers">Papers</h2> <ul> <li><p><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/An_Introduction_to_Deep_Reinforcement_Learning.pdf">An Introduction to Deep Reinforcement Learning.pdf</a></p> <p>A very comprehensive introduction. The most useful chapter to me is <strong>Benchmarking Deep RL</strong>.</p></li> <li><p><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/Revisiting_the_Arcade_Learning_Environment.pdf">Revisiting the Arcade Learning Environment.pdf</a></p> <p>Because we will do a lot of experiments in the Atari environment, it’s better to read this paper first to avoid some potential pitfalls.</p></li> <li><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/DQNNaturePaper.pdf">Human-level control through deep reinforcement learning</a></li> <li><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/Playing_Atari_with_Deep_Reinforcement_Learning.pdf">Playing Atari with Deep Reinforcement Learning</a></li> <li><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/Deep_Reinforcement_Learning_with_Double_Q-learning.pdf">Deep Reinforcement Learning with Double Q-learning</a></li> <li><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/Prioritized_Experience_Replay.pdf">Prioritized Experience Replay</a></li> <li><p><a href="/static/essay_resources/Learn_You_Some_Reinforcement_Learning/./papers/Distributed_Prioritized_Experience_Replay.pdf">Distributed Prioritized Experience Replay</a></p></li> </ul> <h2 id="codes">Codes</h2> <ul> <li><p><a href="https://github.com/higgsfield/RL-Adventure">RL-Adventure</a></p> <p>A good start point. Although some implementations are not that efficient, this repo provides many code snippets that are easy to understand.</p></li> </ul> <h2 id="books">Books</h2> <ul> <li><p><a href="http://incompleteideas.net/book/the-book-2nd.html">Reinforcement Learning: An Introduction</a></p> <p>It is said that everyone needs to take a look at this book before digging into reinforcement learning. And I have reproduced the figures with <a href="https://github.com/Ju-jl/Ju.jl">Ju.jl</a> at <a href="https://github.com/Ju-jl/ReinforcementLearningAnIntroduction.jl">ReinforcementLearningAnIntroduction.jl</a></p></li> <li><p><a href="http://www.athenasc.com/dpbook.html">Dynamic Programming and Optimal Control</a></p> <p>I only finished the first volume. The latest edition has covered a lot of recent research results. It’s a good supplementary of <a href="http://incompleteideas.net/book/the-book-2nd.html">Reinforcement Learning: An Introduction</a>.</p></li> </ul> </body>Jun TianSun, 27 Jan 2019 15:42:07 GMTLearn_You_Some_Reinforcement_LearningAn_Introduction_to_Flux.jlhttps://tianjun.me/essays/An_Introduction_to_Flux.jl<body> <h1 id="详解flux.jl">详解Flux.jl</h1> <p>本文将详细介绍Julia语言中的一个深度学习库——<a href="http://fluxml.ai/">Flux.jl</a>,目的是在理解其内部结构之后,能在其之上做个性化定制。</p> <h2 id="核心概念">核心概念</h2> <h3 id="trackedarray"><code>TrackedArray</code></h3> <p><code>TrackedArray</code>类型用来对最基本的数组做封装。我们知道,深度学习框架带来的最大好处之一就是不用手写梯度反传的函数,其实现是基于这样一个事实,对于一类基本的函数,其梯度的计算方式是已知的,于是通过链式法则可以实现对整个网络中的每个参数进行更新。因此,一个<code>TrackedArray</code>类型应该至少包含</p> <ol type="1"> <li>数据,即数组当前的值</li> <li>映射函数,描述当前数据是根据怎样的函数(以及对应的参数)得到的,从而方便进一步反传</li> <li>梯度,当前数据的梯度</li> </ol> <p>然后我们看看源码中的<a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/lib/array.jl#L9-L15">定义</a></p> <pre class="language-julia"><code>struct TrackedArray{T,N,A&lt;:AbstractArray{T,N}} &lt;: AbstractArray{T,N} tracker::Tracked{A} data::A grad::A TrackedArray{T,N,A}(t::Tracked{A}, data::A) where {T,N,A} = new(t, data) TrackedArray{T,N,A}(t::Tracked{A}, data::A, grad::A) where {T,N,A} = new(t, data, grad) end</code></pre> <p>可以看到,代码的定义与我们的直觉相符,这里的<code>tracker</code>字段就是用来<strong>记录</strong><code>data</code>字段是怎么得到的。再具体看下<code>Tracked{T}</code>的<a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L32-L40">定义</a>:</p> <pre class="language-julia"><code>mutable struct Tracked{T} ref::UInt32 f::Call isleaf::Bool grad::T Tracked{T}(f::Call) where T = new(0, f, false) Tracked{T}(f::Call, grad::T) where T = new(0, f, false, grad) Tracked{T}(f::Call{Nothing}, grad::T) where T = new(0, f, true, grad) end</code></pre> <p><code>ref</code>先不管,<code>isleaf</code>用来标志当前是否是叶子节点(叶子节点有特殊含义,需要作区分,原因是一旦遇到叶子节点,就不需要继续反传了),<code>grad</code>用来记录梯度(初看似乎跟<code>TrackedArray</code>中有重复?其实从数据结构上来看,需要有这么个地方做缓存,后面会解释。),最关键的<code>f</code>记录了作用的函数以及其参数,下面是<code>Call</code>的<a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L19-L22">定义</a>:</p> <pre class="language-julia"><code>struct Call{F,As&lt;:Tuple} func::F args::As end</code></pre> <p>其实就是为了弥补Julia中函数的类型没有携带参数类型。</p> <p>总结一下:</p> <ul> <li><code>TrackedArray</code> 包含 <code>data</code>, <code>grad</code> 和 <code>tracker</code> 三个字段,分别用于记录当前节点上的数据,梯度以及当前节点是如何根据其它节点计算得到的。</li> <li><code>tracker</code> 是一个 <code>Tracked{T}</code> 类型,其中最核心的一个字段是计算函数<code>f::Call</code>。</li> </ul> <h3 id="前向计算">前向计算</h3> <p>现在我们了解了<code>TrackedArray</code>的组成,但是具体怎么做前向计算的呢?</p> <h4 id="通过param构造trackedarray">通过<code>param</code>构造<code>TrackedArray</code></h4> <p>万丈高楼平地起!</p> <p>在julia中,数组一般以<code>AbstractArray</code>的形式存在,而在Flux中,为了存储前向计算函数和梯度信息,需要将这类<code>AbstractArray</code>数据构造成<code>TrackedArray</code>,然后才能对不同的<code>TrackedArray</code>做前向计算。<code>param</code><a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L106-L107">函数</a>就是用来构造<code>TrackedArray</code>的。</p> <pre class="language-julia"><code># https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L107 param(xs::AbstractArray) = TrackedArray(float.(xs)) # https://github.com/FluxML/Flux.jl/blob/master/src/tracker/lib/array.jl#L32 TrackedArray(x::AbstractArray) = TrackedArray(Call(), x, zeros(x)) # https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L25 Call() = Call(nothing, ()) # https://github.com/FluxML/Flux.jl/blob/master/src/tracker/lib/array.jl#L29 TrackedArray(c::Call, x::A, Δ::A) where A &lt;: AbstractArray = TrackedArray{eltype(A),ndims(A),A}(Tracked{A}(c, Δ), x, Δ) # https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L39 Tracked{T}(f::Call{Nothing}, grad::T) where T = new(0, f, true, grad)</code></pre> <p>可以看到,对于原始的数组类型,用<code>param</code>封装成<code>TrackedArray</code>之后,主要就是在外层套了一个<code>nothing</code>的<code>f</code>,并置为了叶子节点。</p> <figure> <img alt="the param function" src="/static/essay_resources/An_Introduction_to_Flux.jl/Resources/img/param.png"/><figcaption>the <code>param</code> function</figcaption> </figure> <p>这里用一个例子来确认下:</p> <pre class="language-julia"><code>julia&gt; w1 = [1 2; 3 4] 2×2 Array{Int64,2}: 1 2 3 4 julia&gt; w1_tracked = param(w1) Tracked 2×2 Array{Float64,2}: 1.0 2.0 3.0 4.0 julia&gt; w1_tracked.data 2×2 Array{Float64,2}: 1.0 2.0 3.0 4.0 julia&gt; w1_tracked.grad 2×2 Array{Float64,2}: 0.0 0.0 0.0 0.0 julia&gt; w1_tracked.tracker.f Flux.Tracker.Call{Void,Tuple{}}(nothing, ()) julia&gt; w1_tracked.tracker.isleaf true</code></pre> <p>理解<code>TrackedArray</code>这个基础概念之后,接下来看看如何对<code>TrackedArray</code>做运算,毕竟在Flux的世界里,深度学习的网络就是靠<code>TrackedArray</code>构造的(其实还有<code>TrackedReal</code>等等)。在Flux的<a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/lib/array.jl">array.jl</a>文件中,做了大量对<code>TrackedArray</code>的封装工作,目的主要有:</p> <ol type="1"> <li>将<code>TrackedArray</code>看作普通的<code>AbstractArray</code>,把系统对Array的一些操作绑定到<code>data</code>字段上</li> <li>重载一些基本的数组运算,通过<code>track</code>函数将对<code>TrackedArray</code>的运算结果封装成新的<code>TrackedArray</code></li> </ol> <p>粗略看一下<a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/lib/array.jl">array.jl</a>便可发现,几乎所有的运算都靠<code>track</code><a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L50-L53">函数</a>来实现转换:</p> <pre class="language-julia"><code>function track(f, xs...; kw...) # 前向计算,得到结果y和反向求导函数back y, back = _forward(f, xs...; kw...) # 生成新的Tracked结构 track(Call(back, tracker.(xs)), y) end</code></pre> <p>第一次看这个代码的时候一脸懵,因为全局搜索<code>_forward</code>你会发现,源代码中总共只出现了三处。但这不符合直觉啊,所有的<code>TrackArray</code>运算都会用到<code>_forward</code>函数,因此应该有很多重载函数才对。后来才发现,<code>_forward</code>函数是通过一个<code>@grad</code><a href="https://github.com/FluxML/Flux.jl/blob/master/src/tracker/Tracker.jl#L55">宏</a>定义的(这个宏稍稍有点复杂,核心是定义前向和反向求导的计算方式),在重载(或者定义)每个计算函数的时候,通过这个<code>@grad</code>宏同时把前向计算和梯度计算函数都定义了。</p> <pre class="language-julia"><code>macro grad(ex) @capture(shortdef(ex), (name_(args__) = body_) | (name_(args__) where {T__} = body_)) || error("Need a function definition") T == nothing &amp;&amp; (T = []) isexpr(name, :(::)) || (name = :(::typeof($name))) insert!(args, 1+isexpr(args[1], :parameters) , name) @q(Tracker._forward($(args...)) where $(T...) = $body) |&gt; esc end</code></pre> <p>这里很骚气地用了<a href="https://github.com/MikeInnes/MacroTools.jl">MacroTools</a>中的<code>@capture</code>宏,说白了,就是用来构造上面的<code>_forward</code>函数,看个例子就明白了</p> <pre class="language-julia"><code>@grad a * b = data(a)*data(b), Δ -&gt; (Δ*b, a*Δ) # 返回两个元素,第一个是前向计算的结果,第二个是反向计算梯度的函数</code></pre> <p>于是,前向计算的问题基本解决了,同时反向计算需要的偏导函数也准备好了。为了支持除了built-in计算方式之外的一些常见的函数(比如Softmax, Relu等),Flux单独开发了一个库<a href="https://github.com/FluxML/NNlib.jl">NNlib.jl</a>。</p> <h3 id="反向传播">反向传播</h3> <p>从代码逻辑上来讲,反向传播的实现很容易:</p> <ol type="1"> <li>从后往前计算偏导并更新<code>TrackedArray</code>的<code>grad</code>字段</li> <li>根据偏导更新weight</li> </ol> <p>不过有些小细节需要处理:</p> <pre class="language-julia"><code>function back!(x, Δ) istracked(x) || return scan(x) back(tracker(x), Δ) return end</code></pre> <p>这里,<code>scan</code>的目的是重置整个网络中的<code>grad</code>:</p> <pre class="language-julia"><code>function scan(x::Tracked) x.isleaf &amp;&amp; return ref = x.ref += 1 if ref == 1 scan(x.f) isdefined(x, :grad) &amp;&amp; (x.grad = zero_grad!(x.grad)) end return end</code></pre> <p>可以看到,<code>ref</code>的作用是引用计数(这里先<code>+=1</code>,后面<code>back</code>执行的时候会<code>-=1</code>),反向传播的时候,会将多次计数的<code>grad</code>进行累加,直至计算完成后再真正执行<code>back_</code>:</p> <pre class="language-julia"><code>function back(x::Tracked, Δ) x.isleaf &amp;&amp; (x.grad = accum!(x.grad, Δ); return) ref = x.ref -= 1 if ref &gt; 0 || isdefined(x, :grad) if isdefined(x, :grad) x.grad = accum!(x.grad, Δ) else x.grad = Δ end ref == 0 &amp;&amp; back_(x.f, x.grad) else ref == 0 &amp;&amp; back_(x.f, Δ) end return end</code></pre> <p>而<code>back_</code>的逻辑就很简单了:</p> <pre class="language-julia"><code>function back_(c::Call, Δ) Δs = c.func(Δ) (Δs isa Tuple &amp;&amp; length(Δs) &gt;= length(c.args)) || error("Gradient is not a tuple of length $(length(c.args))") foreach(back, c.args, data.(Δs)) end</code></pre> <p>计算偏导并迭代下去。</p> <p>接下来是<code>update!</code></p> <pre class="language-julia"><code>function update!(x, Δ) x.data .+= data(Δ) tracker(x).grad .= 0 return x end</code></pre> <p>可以看到,每次计算完会有一个置零的操作。</p> <p>对,如果手动更新的话,就这么简单了。</p> <p>不过大多时候,都有个Optimiser,如<code>SGD</code>,<code>Adam</code>等,来辅助更新梯度。Flux在这方面没有任何特殊之处,作者用一个<code>Param</code>结构来管理data和Δ。</p> <pre class="language-julia"><code>struct Param{T} x::T Δ::T en</code></pre> <p>然后,各个Optimizer管理自己的状态,<del>主要是通过闭包实现的</del>,最新版的不再使用闭包了,直接构造了<code>struct</code>。</p> <h3 id="layer">layer</h3> <p>layer对一些常见的模块做了封装,如RNN和CNN等。写起来确实简单,不过,感觉需要有benchmark测试下性能。</p> <h3 id="其它">其它</h3> <p>剩下的主要就是一些工具函数了,比如<code>treelike</code>,<code>onehot</code>等。</p> </body>Jun TianTue, 22 Jan 2019 21:18:20 GMTAn_Introduction_to_Flux.jlAn_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,<del>即无限大</del>)。</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>对于<del>无限长</del>长度为0的channel而言,需要用到<code>takers</code>和<code>putters</code>字段。在写入数据时,如果<code>takers</code>为空,就将当前task写入到<code>putters</code>中(然后还会通知<code>cond_take</code>上的task,这类task是通过<code>wait(c)</code>挂在在<del>无限长</del>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 TianFri, 21 Dec 2018 16:15:55 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_Julia