为什么选择Julia?
当提到数据科学,机器学习,深度学习,人工智能等,大家第一个想到的工具一定是Python。不得不说,Python确实拥有最健全的生态,Numpy、Pandas、matplotlib、sklearn、tensorflow等等。如果你是刚开始入门数据科学,机器学习,深度学习,人工智能,Python无疑是最好的选择。但是,Python最致命的弱点,缓慢的运行效率就自然的暴露出来了,当面对超高的计算量,或庞大的数据集时,显得力不从心。
这时候,为了提高效率,我们会想到用其他的语言,比如Golang,Rust,Java等。首先,Golang 和Rust都有着极快的编译型语言,但是这两种语言的定位都并不是用于数据科学或者人工智能。
Golang因其独特的goroutine和channel,被广泛用于微服务,云计算等领域,Golang对变量类型的要求过于的严苛,用于机器学习的gonum包,灵活性较低,当面对高维矩阵时显得力不从心,而矩阵包gonum, 算法包golearn,DataFrame包gota之间的衔接简直是令人头痛。至于Rust,这是一门刚刚有兴起趋势的语言,它现在的状态很像13,14年左右的Golang,生态不全,程序员也更倾向于用Rust做微服务或系统编程。比较流行的机器学习包rusty-machine的作者,也在GitHub上官宣,将不再积极的维护。
至于Java,它太繁琐,太沉重了。
Julia 有着超越C语言的极快的效率,比Python更加简练的代码,加入了JavaScript中的一些特点(箭头函数,三元表达式,async等),甚至对Golang的goroutine和channel也进行了实现。
下方是一张以C语言作为度量单位的运行效率对比图。
一次关于Julia旅行
变量
- 你可以像python一样声明全局变量,但你不需要再担心在文件一开始声明一个变量会影响到全局,也不需要担心不小心修改了一个变量导致出乎意料的结果,因为Julia中的变量作用范围是受我们程序员控制的
# 声明全局变量的方式
x = 10
# 我们可以通过关键字global local来控制我们的变量的作用范围
function funscope(n)
x = 0
for i = 1:n
local x
x = i + 1
if (x == 7)
println("这是for循环中的x: $x")
end
end
x
println("这是函数中的x: $x")
# 在这里我们修改了全局变量x
global x = 15
end
funscope(10)
println("这是全局变量x: $x")
# 上面的函数会输出
# 这是for循环中的x: 7
# 这是函数中的x: 0
# 这是全局变量x: 15
复制代码
- 你可以像Rust一样用代码块声明一个变量
# 下方运算后 x 为 70
x = begin
local a = 10
a * 7
end
# 下方运算后 y 为 70
y = (b = 10; b*7)
复制代码
- 你也可以规定变量的类型,注意,全局变量不可以指定其类型
# 这是错误的行为 # 在外部声明变量默认为global # x::Float64 = 1.2 local x::Float64 = 1.2 # 在函数、循环、条件中声明的变量默认为local function specify_variable_type() x::Int32 = 10 typeof(x) end 复制代码
Array
数据科学、人工智能中离不开矩阵,Julia拥有极其强大的Array,原生支持矩阵极其各种运算,这意味着我们不必像其他语言一样依赖第三方的numeric包(例如Python依赖Numpy, Golang 依赖gonum等等)
- 矩阵
# 创建一个矩阵数组 mat = [1 2; 3 4] # 输出 # 2×2 Array{Int64,2}: # 1 2 # 3 4 mat1 = [1 2] .* [3 4] # 输出 # 2×2 Array{Int64,2}: # 1 2 # 3 4 # 获取矩阵的dimension ndims(mat) # 2 # 获取矩阵的size size(mat) # (2, 2) # 获取矩阵的元素个数 length(mat) # 4 # 创建一个Identity矩阵 using LinearAlgebra idm = Matrix(1*I, 3, 3) # 输出 # 3×3 Array{Int64,2}: # 1 0 0 # 0 1 0 # 0 0 1 # 切片 idmc = idm[2:end, 2:end] # 输出 # 2×2 Array{Int64,2}: # 1 0 # 0 1 # 对矩阵的一部分进行重新赋值 idm[2:end, 2:end] = [5 7; 9 11] idm # 输出 # 3×3 Array{Int64,2}: # 1 0 0 # 0 5 7 # 0 9 11 # 将矩阵进行reshape ridm = idm[:] # 输出 # 9-element Array{Int64,1}: # 1 # 0 # 0 # 0 # 5 # 9 # 0 # 7 # 11 # 获取矩阵的transpose m2 = [1 2; 3 4] m2' # 注意此处的' # 输出 # 2×2 Adjoint{Int64,Array{Int64,2}}: # 1 3 # 2 4 # 矩阵点乘 m2 * m2' # 输出 # 2×2 Array{Int64,2}: # 5 11 # 11 25 # 矩阵相乘 m2 .* m2' # 输出 # 2×2 Array{Int64,2}: # 1 6 # 6 16 # 获取逆矩阵 inv(m2) # 输出 # 2×2 Array{Float64,2}: # -2.0 1.0 # 1.5 -0.5 复制代码
函数
Julia中的函数异常的强大,支持generic函数,柯里化,匿名函数,还可以像Go语言中的函数一样return 多个值。我们甚至可以像写javascript代码一样来编写Julia的函数
- 声明函数
# 使用关键字进行声明 function mult(x::Number, y::Number) return x * y end # 使用short-hand的形式声明函数 # 注意在进行数学计算时我们甚至可以省略一些操作符 f(x::Number,y::Number) = 3x - 6y + 12 # 像Rust一样省略return关键字 function div(x::Number, y::Number) x / y end # 像Golang一样返回多个函数值 using Printf function mult_add_divide_subtract(x,y) x * y, x + y, x- y, x / y end m, a, d, s = mult_add_divide_subtract(20, 10) # print 结果为200 30 10 2.0 @printf("%d, %d, %d, %.1f", m, a, d, s) 复制代码
- 范形函数
Julia支持函数的重载,类似于在Java的class中我们可以声明同名函数
function add(x, y) println("没有指定x,y的类型") x+y end function add(x::Integer, y::Float64) println("x是Integer型, y是Float64型") x + y end function add(x::Float64, y::Integer) println("x是Float64型, y是Integer型") x + y end function add(x::Float64, y::Float64) println("x、y是Float64型") x + y end add(1,2) add(1.0, 2) add(1, 2.0) add(1.0, 2.0) # 输出结果 # 没有指定x,y的类型 # x是Float64型, y是Integer型 # x是Integer型, y是Float64型 # x、y是Float64型 # 我们甚至可以重定义基本的操作符 using Base.+ +(x::Bool, y::Integer, z::Float64) = println("布尔值加整数加浮点数") true + 10 + 2.0 # 输出 # 布尔值加整数加浮点数 复制代码
- 柯里化及匿名函数
Julia支持JavaScript中的三元表达式,箭头函数,柯里化,匿名函数
# 三元表达式 fib(n) = n < 2 ? n : fib(n - 1) + fib(n - 2) # 双目运算 negative(n) = n println("我是箭头函数") arrow() arrow1 = x -> println("x是$x") arrow1(10) arrow2 = (x, y, z) -> println("$(x + y + z)") arrow2(2,3,4) # 匿名函数 c = function (x) x + 2 end c(9) 复制代码
- 可选参数及关键字参数
# 可选参数 f(a, b = 5) = a+ b f(1) # 6 f(2,7) # 9 # 关键字参数函数 k(;kw = "keyword") = println(kw) # 输出keyword k() # 输出hello k(kw="hello") # 可选参数,关键字参数结合, 轻松的创建key-value对 function varargs3(;args...) args end rst = varargs3(k1="name1", k2="name2", k3=7) rst # 输出 # pairs(::NamedTuple) with 3 entries: # :k1 => "name1" # :k2 => "name2" # :k3 => 7 复制代码
Channel 及 Coroutine
Julia 引入Golang的协程概念创造出来其特有的Channel来进行协程操作
# 下方函数将计算斐波那契数列,把计算好的结果放进channel中
function fib_producer(c::Channel)
a, b = (0, 1)
for i = 1: 5
put!(c, b)
a, b = (b, a+b)
end
end
chnl = Channel(fib_producer)
take!(chnl) # 1
take!(chnl) # 1
take!(chnl) # 2
take!(chnl) # 3
take!(chnl) # 5
# 下方进行阶乘运算,如果你熟悉go语言
# 你会惊讶这几乎就是go语言中goroutine和channel的交互方式!
fac(i::Integer) = (i > 1) ? i*fac(i - 1) : 1
c = Channel(0)
# 此处使用async 进行异步
task = @async foreach(i->put!(c,fac(i)), 1:5)
bind(c,task)
for i in c
@show i
end
# i = 1
# i = 2
# i = 6
# i = 24
# i = 120
复制代码
结语
此外,Julia还可以调用C和Python的代码。Jupyter Notebook和深度学习框架Tensorflow,也已经全面支持了最新版本的Julia。
无论你是否习惯Python,来自MIT实验室的Julia或许是你在未来最好的选择。