为什么选择Julia?——为人工智能而生的语言

2020年6月2日10:43:34 发表评论 319 views

为什么选择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?——为人工智能而生的语言

一次关于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 < 0 && println(true) 
    # 打印 true
    negative(-1)
    # 返回 false
    negative(2)
    
    # 柯里化
    function add(x)
        return function f(y)
            return x + y
        end
    end
    
    add(1)(2)
    
    # 箭头函数
    arrow = () -> 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或许是你在未来最好的选择。

作者:给我点阳光就灿烂
链接:https://juejin.im/post/5e7966546fb9a07cc97dc432
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: