NumPy教程:在循环中高效构建多维数组

DDD
发布: 2025-09-22 12:37:34
原创
492人浏览过

numpy教程:在循环中高效构建多维数组

本教程旨在解决在循环中迭代地将多个二维NumPy数组(例如(10, 3))组合成一个更高维度的父数组(例如(X, 10, 3))的常见问题。我们将探讨传统方法为何可能不适用,并提供一个NumPy原生的高效解决方案,通过巧妙地扩展子数组的维度,结合np.vstack或np.concatenate实现目标,避免了不必要的维度增加或性能开销。

1. 理解问题:迭代构建多维数组的挑战

在数据处理和机器学习的场景中,我们经常会遇到需要在一个循环中收集并组合多个具有相同形状的NumPy数组。例如,我们可能有一系列形状为 (10, 3) 的子数组,希望将它们累积到一个最终的父数组中,使其形状变为 (X, 10, 3),其中 X 是子数组的数量。

直接使用 np.stack() 或 np.concatenate() 在循环中进行迭代操作时,可能会遇到一些不符合预期的行为:

  • np.stack() 的问题: np.stack() 的作用是在一个新的轴上连接数组。如果在循环中重复使用 np.stack(),它会在每次迭代时都创建一个新的轴,导致最终数组的维度不断增加,而不是我们期望的在第一个轴上进行堆叠。例如,将 (10, 3) 堆叠到 (1, 10, 3) 上,再堆叠会得到 (1, 1, 10, 3),而非 (2, 10, 3)。
  • np.concatenate() 的问题: np.concatenate() 默认沿着现有轴连接数组。如果直接将 (10, 3) 数组连接到一个 (X, 10, 3) 数组上,NumPy会尝试匹配维度,这通常会导致错误,除非我们显式地调整子数组的维度。

一个常见的“工作区”解决方案是先将所有子数组收集到一个标准的Python列表中,然后在循环结束后,一次性地使用 np.array(list_of_arrays) 将列表转换为NumPy数组。这种方法虽然有效,但在处理大量数据时,可能会引入额外的内存开销和类型转换的性能损耗,因为它需要先构建Python列表,然后再转换为NumPy数组。

import numpy as np

# 示例子数组
arr1 = np.random.rand(10, 3)
arr2 = np.random.rand(10, 3)
arr3 = np.random.rand(10, 3)

# 常见的列表-转换方法
list_of_arr = []
list_of_arr.append(arr1)
list_of_arr.append(arr2)
list_of_arr.append(arr3)

parent_array_from_list = np.array(list_of_arr)
print("使用列表转换得到的数组形状:", parent_array_from_list.shape)
# 预期输出: (3, 10, 3)
登录后复制

虽然列表转换方法可行,但NumPy提供了更直接、更“原生”的方式来处理这类迭代构建任务。

2. NumPy解决方案:维度扩展与垂直堆叠

解决上述问题的关键在于,在每次迭代中,将要添加的子数组的维度进行扩展,使其与父数组的维度兼容,然后再进行连接。具体来说,我们需要将每个 (10, 3) 形状的子数组转换为 (1, 10, 3) 形状,然后使用 np.vstack() 或 np.concatenate(axis=0) 沿着第一个轴进行堆叠。

2.1 维度扩展 (np.newaxis 或 np.expand_dims)

NumPy提供了两种主要方式来在现有数组中添加新的维度:

  • np.newaxis: 这是一个特殊的索引对象,用于在指定位置插入新轴。当用作索引时,它会在该位置创建一个长度为1的新维度。
    arr = np.random.rand(10, 3)
    # 将 (10, 3) 变为 (1, 10, 3)
    arr_expanded = arr[np.newaxis, :]
    print("使用 np.newaxis 扩展后的形状:", arr_expanded.shape)
    # 预期输出: (1, 10, 3)
    登录后复制
  • np.expand_dims(): 这是一个函数,可以更明确地指定在哪里添加新维度。
    arr = np.random.rand(10, 3)
    # 将 (10, 3) 变为 (1, 10, 3),在 axis=0 处添加新维度
    arr_expanded = np.expand_dims(arr, axis=0)
    print("使用 np.expand_dims 扩展后的形状:", arr_expanded.shape)
    # 预期输出: (1, 10, 3)
    登录后复制

    这两种方法效果相同,都可以将 (10, 3) 数组转换为 (1, 10, 3) 数组,使其可以被“垂直”堆叠到另一个 (X, 10, 3) 数组上。

2.2 垂直堆叠 (np.vstack 或 np.concatenate(axis=0))

一旦子数组被扩展为 (1, 10, 3) 形状,我们就可以使用以下函数将其与现有的父数组进行连接:

即构数智人
即构数智人

即构数智人是由即构科技推出的AI虚拟数字人视频创作平台,支持数字人形象定制、短视频创作、数字人直播等。

即构数智人 36
查看详情 即构数智人
  • np.vstack(): 这个函数用于垂直堆叠(按行堆叠)数组。它要求所有输入数组的除了第一个轴以外的其他轴的长度都相同。对于形状为 (N, ...) 的数组,vstack 相当于 concatenate(axis=0)。
  • np.concatenate(axis=0): 这是一个更通用的连接函数,允许我们指定连接的轴。当 axis=0 时,它会沿着第一个轴(垂直方向)连接数组。
# 示例:初始化一个父数组
initial_arr = np.random.rand(10, 3)
# 首次使用时,也需要扩展维度
parent_array = initial_arr[np.newaxis, :]
print("初始父数组形状:", parent_array.shape) # (1, 10, 3)

# 模拟循环添加新的子数组
new_arr_1 = np.random.rand(10, 3)
new_arr_1_expanded = new_arr_1[np.newaxis, :]
parent_array = np.vstack((parent_array, new_arr_1_expanded))
print("添加一个子数组后父数组形状:", parent_array.shape) # (2, 10, 3)

new_arr_2 = np.random.rand(10, 3)
new_arr_2_expanded = np.expand_dims(new_arr_2, axis=0)
parent_array = np.concatenate((parent_array, new_arr_2_expanded), axis=0)
print("再添加一个子数组后父数组形状:", parent_array.shape) # (3, 10, 3)
登录后复制

3. 完整实现示例

下面是一个完整的代码示例,展示了如何在循环中迭代地将多个 (10, 3) 数组添加到 (X, 10, 3) 父数组中:

import numpy as np

# 为了结果的可复现性,设置随机种子
np.random.seed(0)

# 1. 初始化父数组
# 创建第一个 (10, 3) 形状的子数组
first_child_array = np.random.random((10, 3))

# 将其维度扩展为 (1, 10, 3),作为父数组的初始状态
# 可以使用 np.newaxis 或 np.expand_dims
parent_array = first_child_array[np.newaxis, :]
# 或者: parent_array = np.expand_dims(first_child_array, axis=0)

print("初始父数组形状:", parent_array.shape)
print("初始父数组内容:\n", parent_array)

# 2. 在循环中迭代添加更多子数组
num_iterations = 3 # 假设我们要添加3个额外的子数组

for i in range(num_iterations):
    # 生成一个新的 (10, 3) 形状的子数组
    # 在实际应用中,这可能是从文件读取、计算或其他来源获取的数据
    current_child_array = np.random.random((10, 3))

    # 将当前子数组的维度扩展为 (1, 10, 3)
    current_child_array_expanded = current_child_array[np.newaxis, :]
    # 或者: current_child_array_expanded = np.expand_dims(current_child_array, axis=0)

    # 使用 np.vstack 将扩展后的子数组垂直堆叠到父数组上
    parent_array = np.vstack((parent_array, current_child_array_expanded))
    # 或者使用 np.concatenate:
    # parent_array = np.concatenate((parent_array, current_child_array_expanded), axis=0)

    print(f"\n迭代 {i+1} 后父数组形状:", parent_array.shape)

print("\n最终父数组内容:\n", parent_array)
print("\n最终父数组形状:", parent_array.shape)
# 预期最终形状为 (1 + num_iterations, 10, 3) = (4, 10, 3)
登录后复制

输出示例(部分):

初始父数组形状: (1, 10, 3)
初始父数组内容:
 [[[0.5488135  0.71518937 0.60276338]
  [0.54488318 0.4236548  0.64589411]
  ...
  [0.94466892 0.52184832 0.41466194]]]

迭代 1 后父数组形状: (2, 10, 3)

迭代 2 后父数组形状: (3, 10, 3)

迭代 3 后父数组形状: (4, 10, 3)

最终父数组内容:
 [[[0.5488135  0.71518937 0.60276338]
  [0.54488318 0.4236548  0.64589411]
  ...
  [0.94466892 0.52184832 0.41466194]]

 [[0.26455561 0.77423369 0.45615033]
  [0.56843395 0.0187898  0.6176355 ]
  ...
  [0.2532916  0.46631077 0.24442559]]

 [[0.15896958 0.11037514 0.65632959]
  [0.13818295 0.19658236 0.36872517]
  ...
  [0.09394051 0.5759465  0.9292962 ]]

 [[0.07683907 0.0871293  0.0202184 ]
  [0.83261985 0.77815675 0.87001215]
  ...
  [0.97676109 0.60484552 0.73926358]]]

最终父数组形状: (4, 10, 3)
登录后复制

4. 注意事项与性能考量

  • 初始数组: 确保在循环开始前正确初始化父数组。如果循环次数 X 至少为1,则用第一个子数组扩展维度后作为初始父数组是常见的做法。如果 X 可能为0,则需要特殊处理,例如初始化为空数组并检查其形状。

  • 性能: 尽管 np.vstack 和 np.concatenate 比纯Python列表操作更高效,但在循环中重复地创建新数组并复制旧数据到新数组(这是 vstack 和 concatenate 在内部可能做的)仍然会带来一定的性能开销,尤其是在 X 非常大时。

  • 预分配: 如果你知道最终需要多少个子数组(即 X 的值是已知的),最高效的方法是预先分配一个足够大的NumPy数组,然后直接填充数据。

    # 假设已知总共有 num_total_arrays 个子数组
    num_total_arrays = 4
    # 预分配一个 (num_total_arrays, 10, 3) 的数组
    # 可以用 np.zeros, np.empty 或 np.full
    final_parent_array = np.zeros((num_total_arrays, 10, 3), dtype=np.float32)
    
    for i in range(num_total_arrays):
        # 生成或获取当前子数组
        current_child_array = np.random.random((10, 3)).astype(np.float32)
        # 直接填充到预分配数组的相应位置
        final_parent_array[i, :, :] = current_child_array
    
    print("\n预分配方法得到的最终数组形状:", final_parent_array.shape)
    登录后复制

    预分配方法避免了在每次迭代中重新创建和复制数组,显著提高了性能。然而,如果 X 是未知且动态变化的,那么迭代地使用维度扩展和 np.vstack 仍然是一个简洁且NumPy友好的解决方案。

5. 总结

本教程介绍了在Python循环中,如何高效且正确地将多个相同形状的NumPy子数组迭代地组合成一个更高维度的父数组。核心方法是利用 np.newaxis 或 np.expand_dims 在每次迭代中为子数组添加一个新的维度,使其形状从 (N, M) 变为 (1, N, M),然后使用 np.vstack() 或 np.concatenate(axis=0) 将其垂直堆叠到父数组上。这种方法避免了 np.stack() 在迭代中产生额外维度的困扰,也比先收集到Python列表再转换的方式更具NumPy特性。对于已知最终数组大小的场景,预分配数组并直接填充数据是更优的性能选择。理解这些NumPy数组操作的细微差别,对于编写高效且健壮的科学计算代码至关重要。

以上就是NumPy教程:在循环中高效构建多维数组的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号