Rust机器学习之ndarray

众所周知,Python之所以能成为机器学习的首选语言,与其丰富易用的库有很大关系。某种程度上可以说是诸如numpypandasscikit-learnmatplotlibpytorchnetworks…等一系列科学计算和机器学习库成就了Python今天编程语言霸主的地位。基本上今天的机器学习任务主要就建立在上面列举的这6个库上。这6个库在Rust上都有对应的替代方案。从今天开始,我将撰写系列教程,带大家一起学习如何使用Rust及其库替代Python来更好得完成机器学习任务。

本文是“Rust替代Python进行机器学习”系列文章的第一篇,其他教程请参考下面表格目录:

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

数据和算法工程师偏爱Jupyter,为了跟Python保持一致的工作环境,文章中的示例都运行在Jupyter上。因此需要各位搭建Rust交互式编程环境(让Rust作为Jupyter的内核运行在Jupyter上),相关教程请参考 Rust交互式编程环境搭建

文章目录

  • 什么是ndarray?
  • 为什么需要ndarray?
  • ndarray简明教程
    • 创建数组
    • 向量加法
    • 二维数组和高维数组
  • 用ndarray-rand生成随机数
  • 用ndarray-stats实现统计
  • 结论

什么是ndarray?

ndarray是Rust生态中用于处理数组的库。它包含了所有常用的数组操作。简单地说ndarray相当于Rust的numpy

除了数组操作,ndarray还通过派生包提供其他丰富功能,比如

  • ndarray-linalg 用于线性代数运算;
  • ndarray-rand 用于产生随机数;
  • ndarray-stats 用于统计计算;

可以说ndaary不但包含了numpy的功能,还包含了部分scipy的功能。

值得一提的是,ndarray还很好地支持很多外部特性。比如可以支持 rayon做并行计算,支持BLAS进行底层运算加速。因此ndarray的性能非常彪悍。

ndarray 的 BLAS是通过 blas-src 实现的,blas-src为ndarray提供可选的BLAS源,目前支持

  • accelerate - 苹果的Accelerate 框架(仅支持Mac系统)
  • blis - BLIS是一个类BLAS高性能线性代数库
  • intel-mkl - Intel-MKL是⼀套经过高度优化和广泛线程化的数学库
  • netlib - Netlib是一系列数学工具的集合,其中包含BLAS和LAPACK实现
  • openblas - OpenBLAS是一套高度优化的开源BLAS库

为什么需要ndarray?

Rust本身已经自带了数组(或列表)类型,也有vector容器。得益于Rust自身的强大,这些自带的数据类型或容器比很多其他编程语言都要快,甚至快一个数量级。那么我们为什么还需要ndarray呢?

首先,ndarray是专门为处理n维数组(矩阵)而设计的,里面包含了很多数学运算,比如矩阵相乘、矩阵求逆等。

其次,ndarray支持SIMD(Single Instruction Multiple Data),可以进一步提升计算性能。

SIMD 的全称是 Single Instruction Multiple Data,中文名“单指令多数据”。顾名思义,一条指令处理多个数据。

如上图所示

(a) 是普通的标量计算,加法指令一次只能对两个数执行一个加法操作。

(b)是SIMD向量计算,SIMD加法指令一次可以对两个数组(向量)执行加法操作。

ndarray简明教程

看了上面的介绍我猜你已经迫不及待想尝试一下ndarray的威力了。下面我们开始ndarray的学习。

首先让我们引入ndarray包。

传统的Rust程序有Cargo进行包管理,只需要在cargo.toml[dependencies]中加入

ndarray = "0.15.6"

然后在main.rs开头加入

use ndarray::prelude::*;

即可引入ndarrayprelude涵盖了几乎一切我们需要的模块。

但是在交互环境下没有Cargo,我们需要用evcxr:dep命令来引入包。在Jupyter中输入如下代码:

:dep ndarray = {version = "0.15.6"}use ndarray::prelude::*;

创建数组

我们先来看一下如何创建数组:

let arr = array![1., 2., 3., 4., 5., 6.];
println!("1D array: {}", arr);

ndarray提供了array!宏来创建数组。array!宏会自动判断创建那种类型的ArrayBase。上面的示例代码创建了一个1-D类型的ArrayBase,即一维数组。基类ArrayBase实现了std::fmt::Display方法,因此创建的对象可以直接利用println!宏输出。

我们比较一下ndarray与rust自带数组和vec的区别:

let arr1 = array![1., 2., 3., 4., 5., 6.];
println!("1D array: \t{}", arr1);let ls1 = [1., 2., 3., 4., 5., 6.];
println!("1D list: \t{:?}", ls1);let vec1 = vec![1., 2., 3., 4., 5., 6.];
println!("1D vector: \t{:?}", vec1);

输出结果如下:

1D array:    [1, 2, 3, 4, 5, 6]
1D list:    [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
1D vector:  [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]

注意,array!将浮点型转成了整形,因为给定的数小数位都为0。

向量加法

下面我们让两个数组中的元素两两相加:

let arr2 = array![1., 2.2, 3.3, 4., 5., 6.];
let arr3 = arr1 + arr2;
println!("1D array: \t{}", arr3);

对比Rust自带数组和vec的实现,你就能发现ndarray多么简单自然。

let arr2 = array![1., 2.2, 3.3, 4., 5., 6.];
let arr3 = arr1 + arr2;
println!("1D array: \t{}", arr3);let ls2 = [1., 2.2, 3.3, 4., 5., 6.];
let mut ls3 = ls1.clone();
for i in 1..ls2.len(){ls3[i] = ls1[i] + ls2[i];
}
println!("1D list: \t{:?}", ls3);let vec2 = vec![1., 2.2, 3.3, 4., 5., 6.];
let vec3: Vec<f64> = vec1.iter().zip(vec2.iter()).map(|(&e1, &e2)| e1 + e2).collect();
println!("1D vec: \t{:?}", vec3);

输出结果:

1D array:    [2, 4.2, 6.3, 8, 10, 12]
1D list:    [1.0, 4.2, 6.3, 8.0, 10.0, 12.0]
1D vec:     [2.0, 4.2, 6.3, 8.0, 10.0, 12.0]

上面的对比可以发现,Rust自带数组和vec都需要循环或迭代逐个元素相加求值,而ndarray只需要简单的+即可,即简单又直观。对数据和算法工程师来说,ndarray可以极大提高开发效率。

二维数组和高维数组

接下来的演示就不再给大家比较Rust自带数组和vec的实现了。上一节已经充分说明用Rust自带数据类型和数据结构来实现数组操作非常繁琐,而ndarray则十分简洁直观。所以下面我们将聚集ndarray

ndarray提供多种方法创建和实例化二维数组。看下面代码:

let arr4 = array![[1., 2., 3.], [ 4., 5., 6.]];
let arr5 = Array::from_elem((2, 1), 1.);
let arr6 = arr4 + arr5;
println!("2D array:\n{}", arr6);

输出如下:

2D array:
[[2, 3, 4],[5, 6, 7]]

上面代码展示了2种创建二维数组的方法。

  • array!宏需要指定数组中的所有元素
  • Array::from_elem需要我们给定Shape(在上面案例中是(2,1))和填充元素(在上面案例中是1.0),该方法会用给定元素填充指定大小的二维数组

ndarray还提供了快速创建常用二维数组(矩阵)的方法。看下面代码:

let arr7 =  Array::<f64, _>::zeros(arr6.raw_dim());
let arr8 = arr6 * arr7;
println!("{}", arr8);

输出:

[[0, 0, 0],[0, 0, 0]]

Array::zeros(Shape)用0填充指定Shape大小的矩阵。

这里的<f64, _>要解释一下,因为Rust有着严格的类型,编译器有时无法通过0推断出数据类型,所以需要我们显式地告诉编译器这里填充的数据是什么类型。后面的_表示让编译器推断这里的数据类型,此处的数据类型是Shape

.raw_dim()方法我想你已经猜到了它的作用,没错,就是返回矩阵的维度(dimension)。

在线性代数和机器学习中经常会用到单位矩阵(主对角线元素为1,其余元素为0的二维矩阵)。ndarray提供了专门的eye方法来生成单位矩阵。请看下面的代码:

let identity: Array2<f64> = Array::eye(3);
println!("\n{}", identity);

输出:

[[1, 0, 0],[0, 1, 0],[0, 0, 1]]

eye方法需要我们传入矩阵大小,因为单位矩阵一定是方阵,所以只需要传一个整数即可。这里要注意的是identity的类型我们明确定义为Array2类型,Array2ArrayBase子类,表示一个二位数组。

单位矩阵(数学上一般用III表示)有很多性质,比如任何矩阵乘以单位矩阵都等于它本身: A⋅I=A=I⋅AA · I = A = I · AA⋅I=A=I⋅A,就像我们经典代数中的1一样。我们可以验证一下单位矩阵的这个性质。

let arr9 = array![[1., 2., 3.], [ 4., 5., 6.], [7., 8., 9.]];
let arr10 = arr9 * identity;
println!("{}", arr10);

结果输出:

[[1, 0, 0],[0, 5, 0],[0, 0, 9]]

输出结果似乎不是arr9本身。这是因为矩阵运算中*·不是一回事。*也叫标量乘法,是将两个矩阵对应位置元素相乘。而·是将前一个矩阵的行向量与后一个矩阵的列向量相乘。因此·对矩阵的维度有要求,且不满足交换律。相比*,·乘在机器学习中用的更多,ndarray提供了dot()函数来计算·乘。

let arr11: Array2<f64> = arr9.dot(&identity);
println!("{}", arr11);

这次输出就跟我们的预期一致了:

[[1, 2, 3],[4, 5, 6],[7, 8, 9]]

同样的道理,我们可以声明3维度或更高维度的数组。例如:

let arr12 = Array::<i8, _>::ones((2, 3, 2, 2));
println!("MULTIDIMENSIONAL\n{}", arr12);

输出的是一个2×3×2×22 \times 3 \times 2 \times 22×3×2×2数组

MULTIDIMENSIONAL
[[[[1, 1],[1, 1]],[[1, 1],[1, 1]],[[1, 1],[1, 1]]],[[[1, 1],[1, 1]],[[1, 1],[1, 1]],[[1, 1],[1, 1]]]]

用ndarray-rand生成随机数

ndarray-rand提供了比rand更强大的随机功能,且能更好得跟ndarray配合。

要想在交互编程环境中引入ndarray-rand,需要加入如下代码:

:dep ndarray-rand={version = "0.14.0"}use ndarray_rand::{RandomExt, SamplingStrategy};
use ndarray_rand::rand_distr::Uniform;

下面我们生成一个大小为2×52 \times 52×5的在满足[1, 10]之间正态分布的矩阵。

let arr13 = Array::random((2, 5), Uniform::new(0., 10.));
println!("{:5.2}", arr13);

输出结果为:

[[ 1.64,  8.13,  5.92,  5.88,  0.45],[ 7.13,  5.79,  8.21,  4.64,  6.04]]

上面的代码每次运行输出结果都不同,因为每次都是随机生成的,但生成的随机数一定满足[1, 10]之间的正态分布。

我们还可以实现类似Python中random.choice()的功能,随机从数组中选取元素

let arr14 = array![1., 2., 3., 4., 5., 6.];
let arr15 = arr14.sample_axis(Axis(0), 2, SamplingStrategy::WithoutReplacement);
println!("Sampling from:\t{}\nTwo elements:\t{}", arr14, arr15);

输出结果为:

Sampling from:   [1, 2, 3, 4, 5, 6]
Two elements:   [1, 3]

接下来给大家演示另一种随机采样方法,这种方法需要用到rand包。

首先我们引入需要用到的模块

use ndarray_rand::rand as rand;
use rand::seq::IteratorRandom;

这里我们将ndarray-rand包中的rand重命名为rand

然后我们就可以仿照rand文档中的示例,用ndarray-rand实现相同的功能

let mut rng = rand::thread_rng();
let faces = "												

Rust机器学习之ndarray相关推荐

  1. Rust机器学习之tch-rs

    Rust机器学习之tch-rs tch-rs是PyTorch接口的Rust绑定,可以认为tch-rs是Rust版的PyTorch.本文将带领大家学习如何用tch-rs搭建深度神经网络识别MNIST数据 ...

  2. Rust机器学习之Linfa

    Rust机器学习之Linfa 本文将带领大家用Linfa实现一个完整的Logistics回归,过程中带大家学习Linfa的基本用法. 本文是"Rust替代Python进行机器学习" ...

  3. Numpy 中的 Ndarray

    numpy概述 Numerical Python,数值的Python,补充了Python语言所欠缺的数值计算能力. Numpy是其它数据分析及机器学习库的底层库. Numpy完全标准C语言实现,运行效 ...

  4. 三万字 | 2021 年 Rust 行业调研报告

    作者 | 张汉东       责编 | 欧阳姝黎 文前 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发 ...

  5. Rust 学习总结(2)—— 2021 年 Rust 行业调研报告

    前言 Rust 语言是一门通用系统级编程语言,无GC且能保证内存安全.并发安全和高性能而著称.自2008年开始由 Graydon Hoare 私人研发,2009年得到 Mozilla 赞助,2010年 ...

  6. rust加载不进去服务器eac_基于腾讯云的 Rust 和 WebAssembly 函数即服务

    腾讯云云函数 (SCF) 已经支持十多种编程语言和运行时框架.腾讯云最近发布的 SCF custom runtime(自定义运行时)更进一步 -- SCF 现在可以支持用任何编程语言编写的函数. 本文 ...

  7. 基于腾讯云的 Rust 和 WebAssembly 函数即服务

    腾讯云云函数 (SCF) 已经支持十多种编程语言和运行时框架.腾讯云最近发布的 SCF custom runtime(自定义运行时)更进一步 -- SCF 现在可以支持用任何编程语言编写的函数. 本文 ...

  8. 2w+字长文,一篇文章扫盲Python、NumPy 和 Pandas,建议收藏!

    作为简单易学的编程语言,想要入门还是比较容易的,今天我们来一篇超级长文,一次性扫盲Python.NumPy 和 Pandas,文末提供Python 技术交流群,欢迎加入,喜欢本文,点赞.收藏. 搭建语 ...

  9. 长文预警,一篇文章扫盲Python、NumPy 和 Pandas,建议收藏慢慢看

    大家好,我是辰哥~ 今天我们来一篇超级长文,一次性扫盲Python.NumPy 和 Pandas Python 作为简单易学的编程语言,想要入门还是比较容易的 搭建语言环境 我们首先来了解下如何安装和 ...

最新文章

  1. springboot 集成mybatis时日志输出
  2. Java泛型背后是什么?
  3. Mac OS使用技巧十九:Safari碉堡功能之二查看网页源代码
  4. linux中的dup和fcntl的用法
  5. input上传图片;input上传file;vue上传图片。
  6. 微型计算机主板上安装的主要部件有,微型计算机的主板上安装的主要部件有()....
  7. Spring Boot细节挖掘(Redis的集成)
  8. vue .prop修饰符
  9. Java的oauth2.0 服务端与客户端的实现
  10. 一文看懂微服务,阿里云原生资深专家李国强独家分享
  11. Delphi2010Excel导入数据库
  12. 运动模糊matlab图像处理,“数字图像处理-MATLAB”运动模糊图像复原.ppt
  13. mergesort java 源码_MergeSort(Java)
  14. MyBatis-Plus配置全局sql注入器后,BaseMapper中方法失效
  15. python爬取起点中文网小说_爬虫实战——起点中文网小说的爬取
  16. r5 3600和i7 8700k 选哪个
  17. 10019---mybatis的缓存机制(一级缓存二级缓存和刷新缓存)
  18. 2021-2027全球及中国红外探测器芯片行业研究及十四五规划分析报告
  19. ODM 对象文档映射
  20. 第三次学车-侧位停车

热门文章

  1. 使用NNI,从此告别手动调参
  2. 无APP无品牌并不是要宁滥勿缺
  3. 极光推送零基础极速上手开发指南,快速搭建后台推送服务
  4. 荐书《遗留系统:重建实战》:当你面对一坨代码时,你应该这么做
  5. itertools库的使用方法
  6. Hadoop YARN ResourceManager未授权访问漏洞
  7. 陀螺仪计算姿态待完善
  8. hmr webpack 不编译_webpack - hmr热更新
  9. hmr webpack 不编译_Webpack hmr:__webpack_hmr 404找不到
  10. 微信小程序把图片转换为Base64编码