赋能 AI 与具身智能:Rust ndarray 构建安全高效的数据底座

赋能 AI 与具身智能:Rust ndarray 构建安全高效的数据底座

一、引言:ndarray 在 AI 与具身智能中的核心价值

在AI和具身智能快速发展的今天,高效的数据处理能力成为了项目成功的基础。

作为Rust生态中最强大的多维数组库,ndarray 正在成为构建高性能AI系统的秘密武器。

ndarray 作为 Rust 生态中对标 NumPy 的高性能数值计算库,既提供了类似于 NumPy 的功能,又拥有 Rust 的内存安全保证和卓越性能,是搭建 AI 数据管道和具身智能传感器数据处理的核心工具,特别适合:

  • 实时传感器数据处理(机器人感知)
  • 神经网络权重管理
  • 图像和点云处理
  • 数值计算和线性代数运算

本文将从基础用法切入,解析其核心功能如何为复杂 AI 任务奠定基础。

二、快速入门:ndarray 简介与环境搭建

1. ndarray 特性与应用场景

ndarray 是 Rust 的高性能 N 维数组库,支持动态形状的数组操作,具备以下核心优势:

  • 内存安全:通过 Rust 所有权系统避免缓冲区溢出、悬垂指针等底层错误。

  • 高效计算 :支持 SIMD 向量化运算 和 并行处理(配合 rayon 库)。

  • 维度灵活 :轻松处理 1DnD 数组,兼容多种数据布局(C/Fortran 序)。

广泛应用于机器学习数据预处理、机器人感知数据融合、强化学习状态空间建模等场景。

例如,在图像识别中,可将图像数据表示为三维 ndarray(高度、宽度、通道数),利用 ndarray 进行卷积运算;在机器人导航中,利用 ndarray 处理激光雷达的点云数据。

2. 安装与基本引用

依赖配置

Cargo.toml中添加:

toml 复制代码
[dependencies]
ndarray = "0.17.1"
代码引入

在 Rust 代码中,使用use语句引入 ndarray 库:

rust 复制代码
use ndarray::prelude::*;

这将导入常用的 ndarray 类型和操作,如ArrayArrayView和各种数组创建函数。

三、数组创建:从基础初始化到灵活构造

1. 基础数组初始化

全零数组(zeros)

zeros方法用于创建一个全零的数组,在 AI 开发中常用于初始化缓冲区,或者作为模型初始化时的默认状态矩阵。

该方法接受一个表示数组形状的元组作为参数,并且可以通过类型标注或上下文推断元素类型。

rust 复制代码
// 创建一个形状为 (2, 3) 的二维全零数组,元素类型为 f64
let zeros_array: Array<f64, _> = Array::zeros((2, 3));
println!("{:?}", zeros_array);
// 输出:
// [[0.0, 0.0, 0.0],
//  [0.0, 0.0, 0.0]], shape=[2, 3], strides=[3, 1], layout=Cc (0x5), const ndim=2

上述代码创建了一个 2 行 3 列的二维数组,每个元素都是0.0 。这种方式在神经网络的权重初始化中很常见,例如初始化卷积层的权重矩阵。

从向量构造(from_shape_vec)

from_shape_vec方法可以将一维向量转换为指定形状的多维数组,在处理从外部数据源(如 CSV 文件、传感器数据流)加载的数据时非常有用。

需要注意的是,向量的长度必须与指定形状的元素总数匹配,否则会返回 Result类型,包含 Err 信息。

rust 复制代码
use std::result::Result;

// 尝试将向量转换为形状为 (2, 2) 的二维数组
let data = vec![1, 2, 3, 4];
let result: Result<Array<i32, _>, _> = Array::from_shape_vec((2, 2), data);
match result {
    Ok(array) => println!("{:?}", array),
    Err(_) => println!("形状不匹配,无法转换"),
}

// 输出:
// [[1, 2],
//  [3, 4]], shape=[2, 2], strides=[2, 1], layout=Cc (0x5), const ndim=2

如果向量元素数量与形状不匹配,from_shape_vec会返回 Err ,开发者可以通过 Resultmatch 语句进行错误处理,确保数据转换的可靠性。

2. 高级创建方法

除了基本的创建方法,ndarray还提供了一系列便捷函数,用于生成特殊模式的数组。

  • 全一数组Array::ones(shape) 使用ones方法可以创建一个全为 1 的数组,形状由传入的参数指定。常用于初始化权重矩阵为全 1 的情况,方便后续的归一化操作。
rust 复制代码
// 创建一个形状为 (3, 1) 的二维全一数组,元素类型为 f32
let ones_array: Array<f32, _> = Array::ones((3, 1));
println!("{:?}", ones_array);
// 输出:
// [[1.0],
//  [1.0],
//  [1.0]], shape=[3, 1], strides=[1, 1], layout=CFcf (0xf), const ndim=2
  • 线性空间Array::linspace(start, end, count) linspace方法用于生成一个包含count个元素的数组,元素在startend之间线性分布。在 AI 算法中,常用于生成等间距的样本点,比如在训练线性回归模型时,生成等间距的特征值。
rust 复制代码
// 生成一个包含5个元素,在0.0到1.0之间线性分布的一维数组
let linspace_array = Array::linspace(0.0, 1.0, 5);
println!("{:?}", linspace_array);
// 输出:
// [0.0, 0.25, 0.5, 0.75, 1.0], shape=[5], strides=[1], layout=CFcf (0xf), const ndim=1
  • 自定义填充Array::from_elem(shape, value) from_elem方法用指定的值value填充数组,形状由shape参数决定。这在初始化神经网络的偏置向量时非常有用,可以将所有偏置初始化为同一个值。
rust 复制代码
// 创建一个形状为 (2, 2) 的二维数组,并用值7填充
let custom_array = Array::from_elem((2, 2), 7);
println!("{:?}", custom_array);
// 输出:
// [[7, 7],
//  [7, 7]], shape=[2, 2], strides=[2, 1], layout=Cc (0x5), const ndim=2

这些高级创建方法极大地提高了数组初始化的灵活性,满足了 AI 和具身智能开发中多样化的数据准备需求 。

四、维度与形状:掌握数据结构的核心特征

1. 形状信息获取

ndarray 中,形状(shape)是描述数组维度大小的关键属性。通过shape方法可以获取一个表示数组各维度大小的元组,而ndim方法则返回数组的维度数。这两个属性在数据处理中非常重要,比如在矩阵乘法中,需要确保两个矩阵的维度匹配才能进行运算。

rust 复制代码
// 创建一个形状为 (2, 3) 的二维数组
let array: Array<f64, _> = Array::zeros((2, 3));
// 获取数组形状
let shape = array.shape();
println!("数组形状: {:?}", shape);
// 数组形状: [2, 3]
// 获取数组维度数
let ndim = array.ndim();
println!("数组维度数: {}", ndim);
// 数组维度数: 2

上述代码中,shape方法返回的数组切片[2, 3]分别表示数组的第一维(行数)大小为 2,第二维(列数)大小为 3;ndim方法返回 2,表示这是一个二维数组。这种形状信息的获取在复杂的数据处理流程中,有助于确保数据的正确性和兼容性。

2. 形状变换与操作

重塑形状(reshape)

to_shape方法是改变数组形状的重要工具,它可以在不改变数组元素总数的前提下,重新调整数组的维度。在 AI 开发中,经常需要将输入数据的形状进行变换以适应模型的输入要求,比如将一维数据重塑为二维矩阵用于线性代数运算。

rust 复制代码
// 创建一个包含6个元素的一维数组
let array: Array<i32, _> = Array::from_vec(vec![1, 2, 3, 4, 5, 6]);
println!("重塑前的数组: \n{:?}", array);
// 重塑前的数组: 
// [1, 2, 3, 4, 5, 6], shape=[6], strides=[1], layout=CFcf (0xf), const ndim=1

// 将一维数组重塑为形状为 (2, 3) 的二维数组
let array_views = array.view();
let reshaped_array = array_views.to_shape((2, 3)).unwrap();
println!("重塑后的数组: \n{:?}", reshaped_array);
// 重塑后的数组: 
// [[1, 2, 3],
// [4, 5, 6]], shape=[2, 3], strides=[3, 1], layout=Cc (0x5), const ndim=2

在这个例子中,to_shape方法将原本的一维数组[1, 2, 3, 4, 5, 6]成功转换为形状为[2, 3]的二维数组,其中元素按照行优先的顺序排列。需要注意的是,to_shape方法的参数必须保证新形状的元素总数与原数组相同,否则会返回Err

转置与轴操作
  1. 矩阵转置 :对于二维数组(矩阵),转置是一种常见的操作,用于交换矩阵的行和列。在 ndarray 中,可以使用t方法实现矩阵转置。
rust 复制代码
// 创建一个形状为 (2, 3) 的二维数组
let matrix: Array2<f64> = arr2(&[[1., 2., 3.], [4., 5., 6.]]);
// 转置矩阵
let transposed_matrix = matrix.t();
println!("原矩阵: \n{:?}", matrix);
// 原矩阵: 
// [[1.0, 2.0, 3.0],
//  [4.0, 5.0, 6.0]], shape=[2, 3], strides=[3, 1], layout=Cc (0x5), const ndim=2
println!("转置后的矩阵: \n{:?}", transposed_matrix);
// 转置后的矩阵: 
// [[1.0, 4.0],
//  [2.0, 5.0],
//  [3.0, 6.0]], shape=[3, 2], strides=[1, 3], layout=Ff (0xa), const ndim=2

上述代码中,matrix.t()将原矩阵的行和列进行了交换,得到一个形状为[3, 2]的转置矩阵。转置操作在图像数据处理中经常用于调整图像的通道顺序,或者在矩阵运算中满足特定的维度要求。

  1. 维度交换 :对于 n 维数组,permute_axes方法提供了更灵活的维度交换功能。它接受一个表示新轴顺序的元组,用于重新排列数组的维度。
rust 复制代码
// 创建一个形状为 (2, 3, 4) 的三维数组
let data: Vec<f64> = vec![
    1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
    17.0, 18.0, 19.0, 20.0, 21.0, 22.0, 23.0, 24.0,
];
let mut tensor: Array3<f64> = Array::from_shape_vec((2, 3, 4), data).unwrap();
println!("原张量:\n {:?}", tensor);
// 原张量: 
// [[[1.0, 2.0, 3.0, 4.0],
//  [5.0, 6.0, 7.0, 8.0],
//  [9.0, 10.0, 11.0, 12.0]],

// [[13.0, 14.0, 15.0, 16.0],
//  [17.0, 18.0, 19.0, 20.0],
//  [21.0, 22.0, 23.0, 24.0]]], shape=[2, 3, 4], strides=[12, 4, 1], layout=Cc (0x5), const ndim=3
println!("原张量形状: {:?}", tensor.shape());
// 张量形状: [2, 3, 4]

// 将维度从 (2, 3, 4) 交换为 (3, 4, 2)
tensor.permute_axes((1, 2, 0));
println!("交换后的张量:\n {:?}", tensor);
// 交换后的张量:
// [[[1.0, 13.0],
//  [2.0, 14.0],
//  [3.0, 15.0],
//  [4.0, 16.0]],

// [[5.0, 17.0],
//  [6.0, 18.0],
//  [7.0, 19.0],
//  [8.0, 20.0]],

// [[9.0, 21.0],
//  [10.0, 22.0],
//  [11.0, 23.0],
//  [12.0, 24.0]]], shape=[3, 4, 2], strides=[4, 1, 12], layout=Custom (0x0), const ndim=3
println!("交换后的张量形状: {:?}", tensor.shape());
// 交换后的张量形状: [3, 4, 2]

在这个例子中,permute_axes((1, 2, 0))将原三维数组的第一维与第二维交换,第二维与第三维交换,从而得到一个新的三维数组,形状变为[3, 4, 2]。这种操作在深度学习中处理多通道图像数据时非常有用,比如将(Channel, Height, Width)的图像数据格式转换为(Height, Width, Channel)

3. 切片与索引:高效访问子数据

切片和索引是从数组中提取特定元素或子数组的重要手段。在 ndarray 中,通过slice方法配合s!宏可以实现多维切片操作,支持指定起始索引、结束索引和步长,还可以使用负索引表示从数组末尾开始计数。

rust 复制代码
// 创建一个形状为 (3, 4) 的二维数组
let array: Array2<i32> = arr2(&[[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12]]);
// 切片操作,获取第一行所有元素
let row_slice = array.slice(s![0, ..]);
println!("第一行切片: {:?}", row_slice);
// 第一行切片: [1, 2, 3, 4], shape=[4], strides=[1], layout=CFcf (0xf), const ndim=1
// 切片操作,获取第二列所有元素
let col_slice = array.slice(s![.., 1]);
println!("第二列切片: {:?}", col_slice);
// 第二列切片: [2, 6, 10], shape=[3], strides=[4], layout=Custom (0x0), const ndim=1

// 切片操作,获取一个子矩阵 (形状为 (2, 2))
let sub_matrix = array.slice(s![1..3, 1..3]);
println!("子矩阵切片: \n{:?}", sub_matrix);
// 子矩阵切片: 
// [[6, 7],
//  [10, 11]], shape=[2, 2], strides=[4, 1], layout=c (0x4), const ndim=2

上述代码展示了不同类型的切片操作。s![0, ..]表示选取第一行(索引为 0)的所有元素;s![.., 1]表示选取第二列(索引为 1)的所有元素;s![1..3, 1..3]表示选取从第二行到第三行(不包括第三行)、第二列到第三列(不包括第三列)的子矩阵。

这些切片操作返回的是数组视图(ArrayView),不会复制数据,因此在处理大规模数据时非常高效,适合对数据进行局部处理和分析。

五、所有权与视图:平衡性能与安全性的关键

1. 所有权语义:Array 的独占访问

ndarray 中,Array类型代表拥有数据所有权的数组,这种所有权语义确保了内存安全,同时支持可变操作,使得开发者可以自由修改数组内容。

当一个Array变量被创建时,它独占其所包含的数据,在其生命周期内,只有该变量可以对数据进行读写操作。这在需要对数据进行独占修改的场景中非常有用,比如在实时传感器数据的预处理管道中,每个数据点都可能需要根据不同的规则进行变换,使用拥有所有权的Array能够灵活地进行这些修改操作。

rust 复制代码
// 创建一个形状为 (2, 2) 的二维数组,拥有数据所有权
let mut owned_array: Array<i32, _> = Array::from_elem((2, 2), 1);
// 对数组进行可变操作,修改元素值
owned_array[[0, 0]] = 2;
println!("拥有所有权的数组: \n{:?}", owned_array);
// 拥有所有权的数组:
// [[2, 1],
//  [1, 1]], shape=[2, 2], strides=[2, 1], layout=Cc (0x5), const ndim=2

上述代码中,owned_array拥有数组数据的所有权,因此可以通过索引操作对其元素进行修改。这种独占访问机制在多线程环境下尤为重要,它可以避免数据竞争问题,因为同一时间只有一个线程可以拥有并修改数组数据。

2. 轻量视图:ArrayView 的共享访问

ArrayView 是一种不拥有数据所有权的只读视图类型,它通过引用的方式访问原始数组,因此非常适合在不希望复制数据的情况下进行数据读取操作。

ArrayView 可以高效地访问原始数组的子集,而不会增加额外的内存开销,这在处理大规模数据时能够显著提升性能。在 AI 模型推理过程中,输入数据(如图像张量)通常是大规模的,使用ArrayView来处理这些数据可以避免不必要的内存拷贝,从而加快推理速度。

rust 复制代码
// 创建一个形状为 (3, 3) 的二维数组
let mut original_array: Array<i32, _> = Array::from_elem((3, 3), 5);
original_array[[1, 1]] = 6;
// 获取数组的视图
let view: ArrayView<i32, _> = original_array.view();
// 通过视图访问数组元素
println!("视图访问元素: {}", view[[1, 1]]);
// 视图访问元素: 6

在这个例子中,vieworiginal_array 的视图,它不拥有数据所有权,但可以安全地访问原始数组的元素。由于视图只是对原始数据的引用,所以创建和使用视图的开销非常小,这对于需要频繁访问大型数组的场景非常友好。

3. 所有权转移与视图限制

  1. 不可变性保证ArrayView 只能读取数据,无法修改,这一特性确保了在并发场景下的线程安全。多个线程可以同时持有同一个数组的ArrayView,而不会出现数据竞争的问题,因为没有任何一个视图可以修改数据。

  2. 生命周期绑定:视图的生命周期依赖于原始数组,这意味着只要原始数组存在,视图就可以安全地访问其数据,避免了悬空引用的风险。当原始数组超出作用域被销毁时,所有相关的视图也会自动失效。

  3. 性能优势ArrayView 配合 mapv 等方法对视图进行操作时,Rust 的零成本抽象机制能够确保这些操作在编译期进行优化,从而实现高效的计算。mapv 方法可以对视图中的每个元素应用一个函数,并且不会产生额外的运行时开销。

rust 复制代码
// 创建一个形状为 (2, 2) 的二维数组
let array: Array<i32, _> = arr2(&[[1, 2], [3, 4]]);
// 获取数组视图并应用函数
let result = array.view().mapv(|x| x * 2);
println!("视图操作结果: \n{:?}", result);
// 视图操作结果:
// [[2, 4],
//  [6, 8]], shape=[2, 2], strides=[2, 1], layout=Cc (0x5), const ndim=2

上述代码中,mapv 方法对 array 的视图中的每个元素乘以 2,得到一个新的数组。这个过程中,由于视图的高效性和 Rust 的零成本抽象,操作的执行非常高效,既避免了数据复制,又保证了计算的速度。

六、实践场景:AI 与具身智能中的典型应用

1. 机器学习数据预处理

在机器学习中,数据预处理是关键的第一步,ndarray 提供了强大的工具来高效地处理和转换数据。

例如,在处理图像数据时,通常需要将图像文件加载为多维数组,并进行归一化、裁剪等操作。假设我们有一批图像数据,每个图像的尺寸为 224x224,有 3 个颜色通道(RGB),我们可以使用 ndarray 来加载和预处理这些数据。

rust 复制代码
use image::{self, GenericImageView, Pixel, Rgb};
use ndarray::prelude::*;

fn main() {
    let image_data = load_image_data("./src/assets/test.jpeg");
    let normal_data = normalize_image_data(&image_data);
    println!("图像归一化数据:\n{:?}", normal_data);
}

// 加载图像数据
fn load_image_data(path: &str) -> Array3<u8> {
    let img = image::open(path).unwrap();
    let (width, height) = img.dimensions();
    let mut data = Array3::<u8>::zeros((height as usize, width as usize, 3));
    for y in 0..height {
        for x in 0..width {
            let Rgb([r, g, b]) = img.get_pixel(x, y).to_rgb();
            data[[y as usize, x as usize, 0]] = r;
            data[[y as usize, x as usize, 1]] = g;
            data[[y as usize, x as usize, 2]] = b;
        }
    }
    data
}

// 归一化图像数据到0.0 - 1.0
fn normalize_image_data(data: &Array3<u8>) -> Array3<f32> {
    data.mapv(|x| x as f32 / 255.0)
}

在上述代码中,load_image_data 函数将图像文件加载为一个三维的Array3<u8> 数组,其中每个元素表示一个像素点的 RGB 值。

normalize_image_data 函数则对图像数据进行归一化处理,将每个像素值从 0 ~ 255 的范围映射到 0.0 ~ 1.0 的范围,以满足机器学习模型的输入要求。这种数据预处理操作在图像分类、目标检测等任务中非常常见,ndarray 的高效数组操作使得这些计算能够快速完成。

2. 机器人传感器数据融合

在具身智能的机器人应用中,通常需要融合多种传感器的数据,如激光雷达、摄像头、惯性测量单元(IMU)等,以获得对环境的全面感知。

ndarray 可以方便地处理这些不同类型传感器的数据,并进行融合计算。

以激光雷达和摄像头数据融合为例,激光雷达可以提供环境的深度信息,而摄像头可以提供视觉图像信息。我们可以将激光雷达的点云数据表示为一个二维数组,其中每一行表示一个激光点的坐标(x, y, z),将摄像头图像数据表示为一个三维数组,如上述图像数据处理部分所述。

rust 复制代码
use ndarray::{Array2, Array3};

// 简单的数据融合示例:将激光雷达的z值叠加到图像的亮度通道
fn fuse_lidar_and_camera(lidar: &Array2<f32>, camera: &mut Array3<u8>) {
    for i in 0..lidar.nrows() {
        let (x, y, z) = (
            lidar[[i, 0]] as usize,
            lidar[[i, 1]] as usize,
            lidar[[i, 2]],
        );
        if x < camera.shape()[1] && y < camera.shape()[0] {
            let brightness = (camera[[y, x, 0]] as f32 + z * 10.0) as u8;
            camera[[y, x, 0]] = brightness;
            camera[[y, x, 1]] = brightness;
            camera[[y, x, 2]] = brightness;
        }
    }
}

fn main() {
    // 假设激光雷达数据,每一行是一个点的(x, y, z)坐标
    let lidar_data: Array2<f32> = Array2::from_shape_vec((1000, 3), vec![2.0; 3000]).unwrap();
    // 假设摄像头图像数据,形状为(height, width, channels)
    let mut camera_data: Array3<u8> = Array3::ones((224, 224, 3));
    fuse_lidar_and_camera(&lidar_data, &mut camera_data);
    println!("camera data:\n {:?}", camera_data);
}

在这个简单的示例中,fuse_lidar_and_camera 函数将激光雷达数据中的高度信息(z 值)叠加到摄像头图像的亮度通道中,实现了两种传感器数据的初步融合。

通过 ndarray 对不同形状和类型数组的灵活操作,机器人可以更有效地整合多源信息,做出更准确的决策,比如在导航、避障等任务中。

3. 强化学习状态处理

在强化学习中,智能体需要根据当前的环境状态做出决策,环境状态通常以多维数组的形式表示。

ndarray 可以用来高效地存储和更新这些状态信息,并且支持在不同状态表示之间进行转换。

例如,在一个简单的二维网格世界的强化学习任务中,智能体的状态可以表示为一个二维数组,其中每个元素表示网格中一个位置的特征(如是否为障碍物、是否为目标位置等)。

rust 复制代码
use ndarray::{Array2, Ix2};

// 定义网格世界的大小
const WIDTH: usize = 10;
const HEIGHT: usize = 10;

// 智能体移动函数,根据动作更新状态
fn move_agent(state: &mut Array2<i32>, action: (i32, i32), agent_pos: &mut Ix2) {
    let (dx, dy) = action;
    let new_x = (agent_pos[0] as i32 + dx) as usize;
    let new_y = (agent_pos[1] as i32 + dy) as usize;
    if new_x < WIDTH && new_y < HEIGHT && state[[new_y, new_x]] == 0 {
        state[[agent_pos[0], agent_pos[1]]] = 0;
        *agent_pos = Ix2(new_y, new_x);
        state[[agent_pos[0], agent_pos[1]]] = 2; // 标记智能体位置
    }
}

fn main() {
    // 初始化状态数组,0表示可通行,1表示障碍物
    let mut state: Array2<i32> = Array2::zeros((HEIGHT, WIDTH));
    state[[2, 3]] = 1; // 设置一个障碍物
    let mut pos = Ix2(1, 1);
    move_agent(&mut state, (1, 2), &mut pos);
    println!("状态:{:?}", state);
    println!("位置:{:?}", pos);
}

在上述代码中,state 数组表示网格世界的状态,move_agent 函数根据智能体的动作(如向上、向下、向左、向右移动)更新状态数组和智能体的位置。

通过 ndarray 的高效数组操作,强化学习算法可以快速地处理大量的状态信息,计算最优策略,从而实现智能体在复杂环境中的自主学习和决策 。

七、总结:选择 ndarray 的三大理由

在 AI 与具身智能的开发中,ndarray 凭借其独特优势,成为处理多维数据的首选工具。

  1. 性能保障:零运行时开销,支持底层硬件优化(SIMD、多线程),确保复杂计算高效执行,满足 AI 实时性需求。

  2. 安全可靠:编译期所有权检查,避免 AI 系统中常见的内存错误,提升系统稳定性和可靠性。

  3. 生态兼容 :无缝对接 tch-rs(PyTorch 绑定)、rustlearn 等 AI 库,构建混合编程架构,助力开发者充分利用 Rust 生态资源。

通过掌握 ndarray 的核心功能,开发者可以在 AI 算法实现和具身智能系统开发中更高效地处理多维数据,为复杂模型训练和实时控制打下坚实基础。

相关推荐
Fuly10242 小时前
LangGraph基础教程(4)---LangGraph的核心能力
人工智能·langchain
一如年少模样丶2 小时前
AI 智能体的终极记忆方案?来认识一下 Graphiti
人工智能
机器学习之心3 小时前
NRBO-XGBoost+SHAP分析+新数据预测!机器学习可解释分析不在发愁!提供9种混沌映射方法(tent、chebyshev、singer等)
人工智能·机器学习·nrbo-xgboost
天天讯通3 小时前
医院慢病电话随访:AI 问血压→异常转医生,0 人工
人工智能
Pomelo_刘金3 小时前
Rust : 新版本 1.90.0
rust
张较瘦_3 小时前
[论文阅读] 生成式人工智能嵌入对公众职业安全感冲击的影响机理及防范对策
论文阅读·人工智能
这张生成的图像能检测吗3 小时前
(论文速读)Regor - 渐进式对应点再生实现鲁棒3D配准
人工智能·算法·计算机视觉·配准·3d点云
shayudiandian3 小时前
AI学习路线图2025:从入门到进阶的完整指南
人工智能
聚梦小课堂3 小时前
2025年11月10日 AI快讯
人工智能·新闻资讯·ai大事件