我为什么将机器学习主力语言从Python转到Rust

简介: Rust语言诞生于2010年,一种多范式、系统级、高级通用编程语言,旨在提高性能和安全性,特别是无畏并发。虽然与Python相比,Rust还年轻,很多库还在开发中,但Rust社区非常活跃并且增长迅猛。很多大厂都是Rust基金会的成员,都在积极地用Rust重构底层基础设施和关键系统应用。

我为什么将机器学习主力语言从Python转到Rust

rust-for-python-developers.png

写在前面

首先要声明一下:Python依然是我最喜欢的编程语言,也是我日常使用最多的编程语言。自从10年前我转向人工智能和机器学习领域以来,Python迅速战胜C++和Java,成为我的主力编程语音。用Python编程让我感受到前所未有的“自由”和高效。

另一方面,在机器学习领域,我需要用到大量与机器学习和数据分析相关的包,例如:pyTorch, numpy, pandas, scikit-learn, jupyter...等。这些包极大地简化了机器学习和数据挖掘的开发工作,非常方便好用。

然而,现在我却想从Python转向Rust,因为在大型项目中Python始终显得力不从心。
$$
\ast \ast \ast
$$

Python的痛点

猴子补丁(Monkey Patch)

猴子补丁的意思是说动态语言可以在不改动源代码的情况下扩展或修改运行时代码。

Python的这一特性给与我们很大的自由和灵活性,让我们可以通过一些奇技淫巧来让程序跑起来。举个例子:很多代码用到 import json,后来发现 ujson 性能更高,如果觉得把每个文件的 import json 改成 import ujson as json 成本较高,或者只是单纯地想测试一下用 ujson 替换json 是否符合预期,只需要在入口加上:

import json
import ujson
def monkey_patch_json():
  json.__name__ = 'ujson'
  json.dumps = ujson.dumps
  json.loads = ujson.loads
monkey_patch_json()

但是这个特性太灵活,一旦我们在编码过程中不仔细或协作过程中缺乏沟通就很容易带来未知错误,且这种错误往往还很难定位。例如:代码中同一变量赋2个不同类型的值。这是很糟糕的编程习惯,但是随着代码量的增长,项目中几乎不可避免地会出现,更糟糕地是没人记得住所有他们用过的变量是什么类型。当新人接手程序后,他们会疑惑“为什么这里要给变量赋一个不同类型的值”。

缺乏参数类型校验

这点跟上面的猴子补丁类似,都是由语言的动态性带来的。例如,我们有一个函数,将传入的两个整数相加:

def add(num1: int, num2: int) -> int:
    return num1 + num2

但当我们调用它时,Python解释器不会检查参数类型,typing hints形同虚设。我们可以这样调用上面的函数

add("2", 3)

很明显,执行上面的代码会报错。为了让程序更加健壮,我们需要在返回前加入额外的判断逻辑

def add(num1: int, num2: int) -> int:
    if type(num1) != int or type(num2) != int:
        # 参数类型不匹配就抛出错误!
        raise IntNumberError()
    return num1 + num2

现在代码看起来好很多。但是事情远没有结束--由于我们在代码里加入了if分支,这就意味着我们的测试用例也要随之更新,至少要加入2个测试用例,一个num1非整数,另一个num2非整数。这些后续工作经常被忘记,以至于测试覆盖不完整。

允许跨作用域访问

请看下面的代码

for i in range(3):
    pass
print(i)

运行后控制台会输出2。但变量i理应只在for循环作用域下有效。这种作用域错误让代码变得很难维护和debug。

Python的这个设计让我非常不理解,印象中没有任意一门其他语言会这样。在我10年的Python开发生涯中,经常见到有人在if子句中定义变量,然后在if-else子句外使用它。这种写法就让很多Python新手无法理解,增加了代码的阅读和维护成本。

运行缓慢

Python运行慢是公认的。尤其是当项目庞大且复杂时,Python明显比其他主流编程语言要慢。当然我们可以用PyPynumba等工具提升Python程序的执行速度,但是相比起来还是杯水车薪。

太多隐含规则

Python中有很多反直觉的设定。比如下面的代码:

def change(lst, st):
    lst.append(4)
    st = "new string"

x = [1, 2, 3]
s = "old string"
print(x, s)
change(x, s)
print(x, s)

我们将一个list和一个string传入函数,但两个参数却有不同的行为。输出结果是:

[1, 2, 3] old string
[1, 2, 3, 4] old string

我们发现list的值变了,而string的值却不变。这是因为Python在传递参数时隐性地传递了list的引用,而string传递的却是拷贝。这就是为什么两个参数在函数中的行为不同。

Python的动态性确实非常灵活且强大,但确实也带来了很多“坑”。这些隐形的“坑”对一个Python新手来说太过隐晦,无形中增加了开发者的心智负担和dubug的成本。为此我专门总结了Python中常见的“坑”,大家可以阅读我写的 《Python避坑指南》《Python避坑指南(续)》

Rust之剑

说完Python我们再来看看Rust。

Rust语言诞生于2010年,是一种多范式、系统级、高级通用编程语言,旨在提高性能和安全性,特别是无畏并发。

Rust从语法和编译器层面帮我们消除了C/C++语言编程中常见的空指针和悬垂指针问题。编译器还会自动帮助我们检查变量生命周期和所有者。在大多数情况下,如果我们的Rust代码能够编译通过,那么我们的代码80%~90%不会存在内存安全问题。

除了内存安全外,Rust还解决了上面提到的Python的5个痛点。

猴子补丁

Rust是一门静态编程语言。这就决定了Rust不能使用猴子补丁。

  • 首先,Rust中的变量都有严格的数据类型,我们不能将不同数据类型的数据赋给同一变量。
  • 其次,变量默认是不可变的,如果我们想定义可变的变量,必须显式的用关键字mut声明。

参数类型

函数参数也跟上面保持一致的原则。Rust是静态语言,传递函数参数时也需要指明参数类型。如果传递的参数类型不匹配,编译器在编译时就会检查出来。这就意味着编译器会帮我们检查潜在的类型错误,我们再也不必写额外的if子句来做类型检查,相应的后续单元测试也可以省了。这让我们的测试可以聚焦于算法和逻辑,而不必为类型检测等细枝末节浪费时间。

作用域

Rust没有GC。当变量离开其作用域时,其生命周期结束,Rust会自动释放它。(这里的解释并不严谨,因为Rust中生命周期和作用域是两个概念,但在大多数代码中两者可以划等号。)

因此,上面Python的示例代码改写成Rust代码的话,在编译时就会报错。

for i in 0..3 {
   
    println!("{}", i);
}
println!("{}", i); // 编译时这里会报错

运行速度

我在上一篇文章《Rust让科学计算速度提升200倍》中详细对比了Python、Rust和C在科学计算上的效率。测试结果Rust比Python快200倍!类似性能比较的博文网上还有很多,这里我就不再深入比较了。结论是Rust几乎跟C/C++一样快。这里温馨提示一下,编译Rust程序时千万别忘了加--release,release编译和debug编译出来的程序性能差距很大。

隐含规则

Rust也有很多隐含规则,但是跟Python不同,这些隐含规则都是在编译器层面。相反Rust在语言层面一致性相当高。Rust的编译器非常的强大且对开发者友好,他被设计成开发者的伙伴,专门帮开发者发现潜在错误。

上面提到的Python语言的错误在Rust中绝对不会出现:

  1. 在定义Rust函数时我们就要声明参数的类型。这里的类型不仅指数据类型,还包括传递方式。如果我们想传指针(Rust中叫引用),我们需要在参数前加&
  2. 如果我们想让传递的参数可修改,就必须在显式地加上mut
  3. 如果我们传递给函数的不是引用,那么变量的所有权也会一同传递进函数,这意味着当函数结束,变量就会被丢弃,无法再使用。

总之,Python中的此类问题在Rust中都不存在。我们清晰地知道数据在函数间是如何传递和使用的,程序的一切都在我们的掌控之中。
$$
\ast \ast \ast
$$

结论

与Python相比,Rust还年轻。很多库还在开发中,但Rust社区非常活跃并且增长迅猛。很多大厂都是Rust基金会的成员,都在积极地用Rust重构底层基础设施和关键系统应用。

我用Rust重写了马尔可夫链蒙特卡罗(MCMC)方法解结构方程的程序,整个过程让我非常享受。尤其是最后爆发出的性能提升让我深感惊喜。

今天,我依然会用Python或其他语言来完成某些工作,这主要是因为某些库Rust上还没有。不过机器学习常用的库比如numpypandasscikit-learn, pytorch在Rust上都有类似替代的库。可以说目前用Rust可以方便地完成80%Python的功能。后面几章我会专注介绍Python常用机器学习库的Rust替代方案,内容如下表:

Python库 Rust替代方案 教程
numpy ndarray Rust机器学习之ndarray
pandas Polars Rust机器学习之Polars
scikit-learn Linfa Rust机器学习之Linfa
matplotlib plotters Rust机器学习之plotters
pytorch tch-rs Rust机器学习之tch-rs
networks petgraph Rust机器学习之petgraph

敬请大家关注。

目录
相关文章
|
8天前
|
机器学习/深度学习 人工智能 算法
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
手写数字识别系统,使用Python作为主要开发语言,基于深度学习TensorFlow框架,搭建卷积神经网络算法。并通过对数据集进行训练,最后得到一个识别精度较高的模型。并基于Flask框架,开发网页端操作平台,实现用户上传一张图片识别其名称。
29 0
【手写数字识别】Python+深度学习+机器学习+人工智能+TensorFlow+算法模型
|
11天前
|
机器学习/深度学习 数据采集 人工智能
探索机器学习:从理论到Python代码实践
【10月更文挑战第36天】本文将深入浅出地介绍机器学习的基本概念、主要算法及其在Python中的实现。我们将通过实际案例,展示如何使用scikit-learn库进行数据预处理、模型选择和参数调优。无论你是初学者还是有一定基础的开发者,都能从中获得启发和实践指导。
27 2
|
12天前
|
Rust 安全 Java
探索Rust语言的并发编程模型
探索Rust语言的并发编程模型
|
13天前
|
机器学习/深度学习 数据采集 搜索推荐
利用Python和机器学习构建电影推荐系统
利用Python和机器学习构建电影推荐系统
31 1
|
13天前
|
机器学习/深度学习 算法 PyTorch
用Python实现简单机器学习模型:以鸢尾花数据集为例
用Python实现简单机器学习模型:以鸢尾花数据集为例
36 1
|
18天前
|
Rust 安全 区块链
探索Rust语言:系统编程的新选择
【10月更文挑战第27天】Rust语言以其安全性、性能和并发性在系统编程领域受到广泛关注。本文介绍了Rust的核心特性,如内存安全、高性能和强大的并发模型,以及开发技巧和实用工具,展示了Rust如何改变系统编程的面貌,并展望了其在WebAssembly、区块链和嵌入式系统等领域的未来应用。
|
6天前
|
Rust 安全
深入理解Rust语言的所有权系统
深入理解Rust语言的所有权系统
16 0
|
12天前
|
Rust 安全 前端开发
探索Rust语言的异步编程模型
探索Rust语言的异步编程模型
|
6月前
|
机器学习/深度学习 存储 搜索推荐
利用机器学习算法改善电商推荐系统的效率
电商行业日益竞争激烈,提升用户体验成为关键。本文将探讨如何利用机器学习算法优化电商推荐系统,通过分析用户行为数据和商品信息,实现个性化推荐,从而提高推荐效率和准确性。
239 14
|
6月前
|
机器学习/深度学习 算法 搜索推荐
Machine Learning机器学习之决策树算法 Decision Tree(附Python代码)
Machine Learning机器学习之决策树算法 Decision Tree(附Python代码)

热门文章

最新文章

下一篇
无影云桌面