运筹系列41:Julia入门

参考 https://zhuanlan.zhihu.com/p/41953244
还有这篇教程:https://zhuanlan.zhihu.com/p/60889456

1. 介绍

1.1 背景

Julia是麻省理工学院计算机科学和人工智能实验室(Csil)开发和孵化的一种免费开放源码语言,于2012年发布,目标是结合C的速度与Python的可用性、Ruby的动态性、MATLAB的数学能力和R的统计能力。麻省理工学院说Julia是“petaflop club“中唯一的高级动态语言,在世界第10大超级计算机Cori上被用来模拟1.88亿颗恒星、星系和其他天体,模拟运行只需14.6分钟,使用650000Knights Landing Xeon Phi 核,有1.5千兆秒的运算(每秒四万亿次浮点运算)。
总的来说,目前还是MIT的老师和学生在推Julia。Julia开发都通过GitHub进行(社区驱动),现在上面的代码贡献者有200名左右,稳定的、高度活跃的贡献者约有30名。目前Julia主要用在科学计算上,另外Julia足以胜任的领域之一是教育。下一代学生可以直接跨过现有的技术。IJulia图形化的Notebook集成(运行Julia的IPython Notebook)使之对于教学极具魅力。研究者、算法开发者和库作者也在采用Julia,因为Julia使工作更高效。

1.2 特性

Julia能将工作负载分散到数十万个处理器核,这导致它从机器学习到大规模超级计算机模拟等各个方面的应用。
Julia采用即时编译的策略,底层通过LLVM实现,可以为多个平台编译高效的本地代码。在某些情况下,Julia 可以接近甚至达到 C 语言的速度。我们在下一篇文章会介绍julia的性能提升秘诀。
从本质上来说,Julia更倾向于是一种编译型语言,而不是解释型语言。
Julia的多分派dispatch范式是指允许函数重名,并会在调用时自动根据传入的参数类型进行分派,非常像java/C++中的函数重载,区别在于,C++/Java是在编译时完成分派,而Julia是在运行时完成这一工作。使用它的多分派dispatch范式能表达许多面向对象和函数编程范式,它还有一个非常适合数学运算的语法,有许多数字数据类型和内置的并行支持。
Julia和numpy一样,支持广播机制,可以在GPU和其它向量化硬件上实现高效的优化计算。
Julia 的 Base 包和标准库都是由 Julia 语言编写的。这包括像整数运算那样的基本操作。也就是说,Julia 实现了一定程度的自举。

2. 安装

2.1 基本模式

mac上安装很简单,brew cask install julia即可。
当然,如果你已经安装了jupyter notebook的话,直接输入jupyter notebook启动即可。

linux上安装,到julia官网下载julia,然后解压,配置到环境变量中,sudo vim .bashrc,在环境变量中加上这句,注意这里要写你的julia的bin的路径:export PATH="/myapp/julia/bin:$PATH",然后执行终端输入 julia 顺利执行到julia交互界面,到这里就安装成功了,如果想要安装julia notebook环境,就往下看

在julia 交互界面执行

using Pkg
Pkg.add("IJulia")
using IJulia; notebook(detached=true)

然后就安装成功了

启动julia时有一系列可配置参数:

  • -e:用于直接对跟在后面的表达式进行求值。例如,我们可以输入julia -e 'a = 5 * 8; println(a)'并回车。这时,julia会对单引号内的表达式进行逐一求值。多个表达式之间需要以英文分号;分隔。第二个表达式println(a)在被求值时会在计算机的标准输出上打印40。当所有求值都完成后,julia命令会直接退出(返回命令行提示符)。
  • -E:与-e的功能很类似。但不同的是,追加该参数的julia命令在退出之前还会在标准输出上打印出最后一个表达式的求值结果。上面的第二个表达式println(a)的求值结果会是nothing,表示没有结果值。
  • -p:指定用于处理并行任务的工作进程的数量。跟在它后面的值必须是一个大于 0 的整数,或者为auto(指代当前计算机的 CPU 逻辑核心数)。例如,如果我们输入的命令是julia -p 5,那么工作进程的总数就会是6。这是因为 REPL 环境本身还会占用一个工作进程。如果不追加参数-p,那么 Julia 就不会产生额外的工作进程。
  • -i:用于以交互模式运行命令。这意味着,命令执行后将进入 REPL(Read–eval–print loop)环境。简单来说,这个 REPL 环境就是一个可以与 Julia 的运行时系统进行即时交互的界面。比如,你在这个环境中输入println(“abc”)并回车,它立马就会回显独占一行的abc和一个空行。从字面上我们也可以了解到,该环境会读取你输入的表达式、对读到的表达式进行求值、显示表达式的求值结果,然后再次等待读取。如此循环往复。如果我们在输入julia命令的时候没有追加任何源码文件,那么它就会以交互模式运行。

2.2 pkg和shell模式

输入julia进入julia PERL,然后输入Pkg.add(“PackageName”)安装包;也可以按 ] 键进入pkg模式。输入;可以进入shell模式。

$ julia
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.4.2 (2020-05-23)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |
julia> using Pkg
julia> Pkg.add("IJulia")
julia> using IJulia
[ Info: Precompiling IJulia [7073ff75-c697-5162-941a-fcdaad2a7d2a]
julia> notebook()

3. 基本操作

3.1 注释

类似python,用#
多行注释用#=和=#
也可以用markdown注释方式,前后都用"""围起来即可

3.2 传参

使用MyArgs接收参数。

name, _ = MyArgs.get_parameter("name", true)
if name == "" 
    name = "handsome" 
end
println("Hey, $(name)!")

使用方法:$julia test.jl --name=Robert

3.3 快捷键

除了Ctrl+c之外,Julia 的 REPL 环境还支持不少的快捷键。在 macOS 操作系统中,最常用的快捷键有:

Option+←:将光标移动到左边最近的单词开始处。
Option+→:将光标移动到右边最近的单词末尾处之后。
Ctrl+d:退出julia命令,回到原始的命令行。
Ctrl+e:将光标移动到当前行的末尾处之后。
Ctrl+a:将光标移动到当前行的开始处。
Ctrl+l:清除当前界面中的历史命令,或称清屏。
Ctrl+r:向后搜索历史命令。
Ctrl+s:向前搜索历史命令。

3.4 程序包

自定义:建立xx.jl,用module、end包住。首先要使用activate命令激活,然后就可以使用import xx或者using导入。
社区托管:当然也可以托管到github上,然后add到本地。
Julia目前所有包均可在https://juliapackages.com/上找到。常用包介绍如下:

基本数据结构:DataFrames
交互:CSV, JSON, XLSX
随机数:Random
微分方程:DifferentialEquations
线性代数:LinearAlgebra,IterativeSolvers
统计:Turing(贝叶斯),Distributions,StatsBase,Regression,MixedModels,CurveFit,Interpolations,MultivariateAnalysis,HypothesisTests,POMDPs(马尔科夫决策过程模拟),HMMBase,TimeSeries
凸优化:JuMP(建模)、Convex(凸优化)、GeneticAlgorithms(遗传算法)
绘图:Gadfly(科学绘图)、Plots(可视化),注意这个包的依赖比较大,国内要改一下下载代码:https://zhuanlan.zhihu.com/p/115118883
图论:LightGraphs
机器学习:Flux
深度学习:Knet(这个就算了,还是得看python社区)
写作:Latexify

rand()产生均匀随机数
randn()为N(0,1)
更多随机数使用StatsFuns包

4. 语法

4.1 变量和常量

Julia 是一种可选类型化的编程语言。Julia 代码中的任何值都是有类型的。而一个区别在于,一个值的类型是由我们显式指定的,还是由 Julia 在自行推断之后附加上的。例如:

julia> typeof(2020)
Int64

在 Julia 中,变量的类型也是可以改变的。更确切地说,我们可以为同一个变量先后赋予不同类型的值。Julia 的变量实际上是没有类型的,只有值才有类型。但为了描述方便,我们仍然会说“变量的类型”。你要记住,它真正的意思是“变量中的值的类型”。下面举一个例子:

julia> y = 2020
2050

julia> y = "2050"
"2050"

虽然 Julia 允许我们随意改变一个变量的类型,但是这样做往往会对程序的性能造成不小的负面影响。所以官方在大多数情况下还是不推荐这种做法的。我们可以利用语法规则来约束对变量类型的随意变更,或者说约束赋予变量的那些值的类型。更具体地讲,我们可以在编程的时候用附加类型标识符的方式让变量的类型固定下来,比如:y::Int64。

操作符::可以将类型标识符附加到程序中的变量和表达式之后。下面是它的两个重要用途:

  1. 它可以用于类型标注,为编译器提供额外的类型信息,从而在某些情况下提高程序的性能。当用于类型标注时,操作符::及其右侧的类型标识符就意味着这个变量将永远是某个类型的。
  2. 它可以用于类型断言,判断某个值或者某个表达式的结果是否是某个类型的.

在 Julia 中,常量是一种特殊的变量。我们可以使用关键字const来定义一个常量:

const A = 2020

4.2 数据结构

Julia 属于动态类型语言。在Julia的类型图中,Any是一个唯一的顶层类型,union是唯一一个底层类型。

4.2.1 数值类型

Julia 中具体的数值类型一共有 19 个。罗列如下:
布尔类型:Bool
有符号整数类型:BigInt、Int8、Int16、Int32、Int64和Int128
无符号整数类型:UInt8、UInt16、UInt32、UInt64和UInt128
浮点数类型:BigFloat、Float16、Float32和Float64
复数类型:Complex
有理数类型:Rational
无理数类型:Irrational
在这里插入图片描述

Julia 预定义了非常丰富的数学函数。一些常用的函数如下:

数值类型转换: 主要有T(x)和convert(T, x)。其中,T代表目的类型,x代表源值。
数值特殊性判断: 有isequal、isfinite、isinf和isnan。
舍入: 有四舍五入的round(T, x)、向正无穷舍入的ceil(T, x)、向负无穷舍入的floor(T, x),以及总是向0舍入的trunc(T, x)。
除法: 有cld(x, y)、fld(x, y)和div(x, y),它们分别会将商向正无穷、负无穷和0做舍入。其中的x代表被除数,y代表除数。另外,与之相关的还有取余函数rem(x, y)和取模函数mod(x, y),等等。
公约数与公倍数: 函数gcd(x, y…)用于求取最大正公约数,而函数lcm(x, y…)则用于求取最小正公倍数。圆括号中的…的意思是,除了x和y,函数还允许传入更多的数值。但要注意,这里的数值都应该是整数。
符号获取: 函数sign(x)和signbit(x)都用于获取一个数值的符号。但不同的是,前者对于正整数、0和负整数会分别返回1、0和-1,而后者会分别返回false、false和true。
绝对值获取: 用于获取绝对值的函数是abs(x)。一个相关的函数是,用于求平方的abs2(x)。
求根: 函数sqrt(x)用于求取x的平方根,而函数cbrt(x)则用于求取x的立方根。
求指数: 函数exp(x)会求取x的自然指数。另外还有expm1(x),为接近0的x计算exp(x)-1。
求对数: log(x)会求取x的自然对数,log(b, x)会求以b为底的x的对数,而log2(x)和log10(x)则会分别以2和10为底求对数。另外还有log1p(x),为接近0的x计算log(1+x)。

4.2.2 参数化类型(struct)

参数化(parametric)是 Julia 类型系统中的一个非常重要且强大的特性。它允许类型自身包含参数,并使得一个这样的类型就可以代表整个类型族群。像Ref{T}这样的参数化类型,可以代表的类型的数量是无限的,因为我们可以用任何一个类型的名称替换掉T,从而表示一种确定的(或者说具体的)类型,如Ref{String}。
Julia 已经预定义了不少的参数化类型。我们在前面已经见过几个,包括Ref、Union、Complex和SubString等。对它们的进一步说明如下:
Ref{T}:它是专门用来做引用的类型。要想让它成为某一个类型的引用类型,我们就需要在其花括号中填入那个类型的名称。例如,Ref{UInt32}就表示针对UInt32类型的引用类型。
Union{Types…}:这个类型的花括号中可以有多个类型名称。这使它可以表示为针对那些类型的联合类型,从而让那些类型的值都成为这个联合类型的实例。例如,Union{Integer, AbstractString}就联合了Integer类型和AbstractString类型,从而使整数值和字符串值都变成了它的实例。
Complex{T<:Real}:代表复数的的类型。因为复数的实部和虚部都必须是实数,所以Complex类型的参数一定要是Real类型的子类型。
SubString{T<:AbstractString}:代表子字符串的类型。由于子字符串值只能基于字符串值创建,因此SubString类型的参数必须继承自AbstractString。
在类型定义中,操作符<:用于表示当前类型直接继承自哪一个抽象类型。它也可以与两个类型字面量构成一个表达式,以判断这两个类型之间是否存在直接或间接的继承关系。而在类型的参数定义中,<:则用来表明参数值的有效范围,或者说参数值必须是哪一个类型的(直接或间接的)子类型。由于一个类型也是它自己的子类型,所以这里的有效范围也会包含处于<:右侧的那个类型。

julia> mutable struct Drawer{T}
           content::T
       end 
julia> drawer1 = Drawer{String}("a kind of goods")
Drawer{String}("a kind of goods")
julia> drawer1.content = 'G'
ERROR: MethodError: Cannot `convert` an object of type Char to an object of type String
julia> mutable struct Showcase{T}
           drawer1::Drawer{T}
           drawer2::Drawer{T}
       end
julia> showcase1 = Showcase(Drawer("goods1"), Drawer("goods2"))
Showcase{String}(Drawer{String}("goods1"), Drawer{String}("goods2"))
julia> abstract type Jewelry end
julia> struct Necklace <: Jewelry end
julia> struct Ring <: Jewelry end
julia> mutable struct JewelryShop{T<:Jewelry}
           showcase1::Showcase{Necklace}
           showcase2::Showcase{Ring}
           showcase3::Showcase{Jewelry}
           showcase4::Showcase{T}
       end

4.2.3 容器:元组

元组(tuple)是一种很简单的容器。它可以包含若干个任意类型的元素值。我们在前面其实已经见过这类值很多次了。看一个例子你就应该能明白了:

julia> Drawer{Necklace} <: Drawer{Jewelry}, Drawer{Ring} <: Drawer{Jewelry}
(false, false)

julia> typeof(ans)
Tuple{Bool,Bool}

普通元组的表示形式与我们调用函数时传入参数值的方式很相似。下面来看一个之前展示过的示例:

julia> function sum1(a::Real, b::Real)
           a + b
       end
sum1 (generic function with 1 method)

julia> sum1(1.2, 5)
6.2

4.2.4 字符串

ASCII:美国标准代码-信息交换。
Unicode 是一个针对书面字符和文本的通用字符编码标准。它定义了多语言文本数据在国际间交换的统一方式,并为全球化软件创建了基础。Unicode 编码标准以 ASCII 编码集作为出发点,并突破了 ASCII 只能对拉丁字母进行编码的限制。
在 Unicode 编码标准中,代码空间由从0到10FFFF的十六进制整数组成。这就意味着,有 1114112 个代码点可以用于表示抽象字符。Unicode 编码标准的惯用法是使用十六进制形式来表示代码点的数值,并使用U+作为前缀。比如,英文字母字符’a’的 Unicode 代码点就是U+0061。
在 Unicode 编码标准的模型中,编码格式用于确定怎样将代码空间中的每一个整数(或者说代码点)都表示成包含若干个代码单元的序列。Unicode 编码标准中存在多种编码格式。其中有一种编码格式叫做 UTF-8。UTF 是 Unicode Transformation Format 的缩写。
对于字符和字符串,Julia 通常都会采用 UTF-8 编码格式将它们转换成二进制数并进行存储。

julia> 'a'
'a': ASCII/Unicode U+0061 (category Ll: Letter, lowercase)
julia> '\u4e2d'
'中': Unicode U+4e2d (category Lo: Letter, other)

字符串搜索可以用findfirst、findprev、findnext

4.2.5 向量、矩阵等

向量:
列向量:[1;2;3]
行向量:[1 2 3]
或者显式声明:p = Array{Float64}(undef, 3, 1)
向量的元素可以是tuple:pairs = Array{Tuple{Int64,Int64}}(undef, 3)
或者这样声明:pairs = [(2,3),(1,2),(3,4)]

矩阵:[1 2 3;4 5 6]
下标访问:A[1,2]
转置:transpose(A)或A’
函数和numpy基本一致

最后一个元素用end,而不是-1进行标识
任何一个标量函数后面加上.之后可以作用于向量。
任何一个标量操作符前面加上.之后可以作用于向量
使用!表示可以修改,使用.表示向量计算

4.2.6 字典与集合

用collect可以生成数组;使用value可以获得包含所有值的迭代器。
Tuple:Links = Tuple((s[i],e[i]) for i in 1:N),其中s和e是两个列表
字典:Dict(links .=> c),其中links和c是key和value的列表。另几种声明方式:

dict4 = Dict("a"=>1, "b"=>2, "c"=>3);
a = Dict([(1, "a"), (2, "b"), (3, "c")])
Dict{Int64,String} with 3 entries:
  2 => "b"
  3 => "c"
  1 => "a"
1
1
a[1]
"a"

3.2 流程

  1. 下标从1开始
  2. range(N)可直接用1:N表示
  3. 两种双循环
constraint = Dict()
for j in 1:2
constraint[j] = @constraint(m,sum(A[j,i] * x[1] for i in 1:3) <= b[j])
end
@constraint(m,constraint[j in 1:2],sum(A[j,i] * x[1] for i in 1:3) <= b[j])
  1. 神奇的声明方式:
@variable(model, 0<=x[link in links] <= u[link])
  1. for循环
julia> tuple3 = (10, 20, 30, 40, 50);

julia> for e in tuple3
           println(e)
       end
10
20
30
40
50

3.4 文件读写

语法和python很像,读文件使用open-readlines-close,写文件使用open-println(file,data)-close
使用DelimitedFiles包中的readlm()读取csv文件。

3.5 与python/c/Fortran交互

通过PyCall包,Julia可以直接调用Python包。例如:

using PyCall
np= pyimport("numpy")
x = np.linspace(1, 10, 10)

数值、布尔、字符串、IO stream、函数、元组、数组或列表、以及包含这些类型的字典等,它们都会自动进行类型的转换(Python函数会被转换或传递为Julia的函数,反之亦然)。其它类型则是通过通用的PyObject提供的。
下面是调用c函数的例子。

ccall((:clock, "libc"), Int32, ()) # 调用C的clock函数

绘图

既然都装了python了,就用pyplot绘图吧。

数值分析等

最小二乘拟合:LsqFit包,实现了LM算法
数值微分:Calculus包
数值积分:QuadGK包
自动微分:FowardDiff包

图论

LightGraphs包

OR接口

利用JuMP求解原问题。注意下标从1开始,使用Int和Bin表示整数和0-1变量

function CarpenterPrimal(c,A,b)
    # 定义Model对象, OutputFlag = 0指不输出log
    Primal = Model(solver = GurobiSolver(OutputFlag = 0))
    # 定义变量,注意这里使用了宏(macro),宏的调用也是Julia&JuMP高效编译/元编程(metaprogramming)的重要技巧
    @variable(Primal, x[1:2]>=0)
    # 定义不等式约束
    constr = @constraint(Primal, A*x.<=b)
    # 定义目标函数
    @objective(Primal, Max, dot(c,x))
    # 求解
    solve(Primal)
    # 返回最优目标函数值,最优解(原问题),最优解(对偶问题)
    return getobjectivevalue(Primal), getvalue(x), getdual(constr)
end
using JuMP,GLPK
m = Model(GLPK.Optimizer)
@variable(m,0<=x<=2)
@variable(m,0<=y<=2)
@objective(m,Max,5x+3y)
@constraint(m,1x+5y<=3.0)
JuMP.optimize!(m)
println(JuMP.value(x),",",JuMP.value(y),",",JuMP.objective_value(m))

6. 测试

使用@timeit、@time 来做基本的测试
使用

m = randn(1<<16, 8)

function test_swaprow(m::VecOrMat)
    N = size(m, 1)
    for i = 1:N-1
        swaprows!(m, i, i+1)
    end
    m
end

using BenchmarkTools
@benchmark test_swaprow($m)

返回

BenchmarkTools.Trial: 
  memory estimate:  25.97 MiB
  allocs estimate:  653308
  --------------
  minimum time:     12.866 ms (7.65% GC)
  median time:      17.959 ms (8.86% GC)
  mean time:        18.326 ms (10.94% GC)
  maximum time:     27.736 ms (12.75% GC)
  --------------
  samples:          273
  evals/sample:     1
function swaprows!(v::VecOrMat{T}, i::Int, j::Int) where T
    for c = 1:size(v, 2)
        temp = v[i, c]
        v[i, c] = v[j, c]
        v[j, c] = temp
    end
    v
end
@benchmark test_swaprow($m)
BenchmarkTools.Trial: 
  memory estimate:  0 bytes
  allocs estimate:  0
  --------------
  minimum time:     1.459 ms (0.00% GC)
  median time:      1.524 ms (0.00% GC)
  mean time:        1.582 ms (0.00% GC)
  maximum time:     2.926 ms (0.00% GC)
  --------------
  samples:          3147
  evals/sample:     1

猜你喜欢

转载自blog.csdn.net/kittyzc/article/details/106586083