
在数据处理和机器学习的场景中,我们经常会遇到需要在一个循环中收集并组合多个具有相同形状的NumPy数组。例如,我们可能有一系列形状为 (10, 3) 的子数组,希望将它们累积到一个最终的父数组中,使其形状变为 (X, 10, 3),其中 X 是子数组的数量。
直接使用 np.stack() 或 np.concatenate() 在循环中进行迭代操作时,可能会遇到一些不符合预期的行为:
一个常见的“工作区”解决方案是先将所有子数组收集到一个标准的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提供了更直接、更“原生”的方式来处理这类迭代构建任务。
解决上述问题的关键在于,在每次迭代中,将要添加的子数组的维度进行扩展,使其与父数组的维度兼容,然后再进行连接。具体来说,我们需要将每个 (10, 3) 形状的子数组转换为 (1, 10, 3) 形状,然后使用 np.vstack() 或 np.concatenate(axis=0) 沿着第一个轴进行堆叠。
NumPy提供了两种主要方式来在现有数组中添加新的维度:
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)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) 数组上。
一旦子数组被扩展为 (1, 10, 3) 形状,我们就可以使用以下函数将其与现有的父数组进行连接:
# 示例:初始化一个父数组
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)下面是一个完整的代码示例,展示了如何在循环中迭代地将多个 (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)
初始数组: 确保在循环开始前正确初始化父数组。如果循环次数 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友好的解决方案。
本教程介绍了在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中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号