0

0

使用NumPy高效处理二维数组的2x2块操作

DDD

DDD

发布时间:2025-11-06 13:04:38

|

305人浏览过

|

来源于php中文网

原创

使用NumPy高效处理二维数组的2x2块操作

本教程详细介绍了如何利用numpy的`np.lib.stride_tricks.as_strided`功能,结合查找表(lut)高效地对二维数组进行2x2块的修改。通过将原始数组转换为一个由2x2子块组成的视图,并利用numpy的矢量化索引能力,我们可以避免低效的python循环,从而显著提升处理大规模数组时的性能。文章还探讨了通过单索引查找表进一步优化操作的方法,并提供了详细的代码示例和使用注意事项。

在处理大型二维数组时,如果需要对固定大小的子块(例如2x2)进行遍历和修改,传统的Python循环结合切片操作往往效率低下。NumPy作为Python科学计算的核心库,提供了强大的矢量化能力,可以显著优化这类操作。本文将深入探讨如何利用np.lib.stride_tricks.as_strided和查找表(Lookup Table, LUT)来实现对2D数组2x2块的高效修改。

传统Python循环方法的局限性

考虑以下场景:您有一个二维网格,需要遍历其中所有不重叠的2x2子块,并根据每个子块的当前值从一个预定义的映射(transitions)中获取新的值来更新该子块。原始问题中给出的Python循环示例如下:

import numpy as np
import itertools

# 假设 transitions 是一个映射,将2x2块的字节表示映射到新的2x2布尔数组
# transitions = {b'\x00\x00\x00\x00': np.array([True, True, True, True]), …}

# 假设 grid 是一个二维NumPy数组
# ny, nx = grid.shape
# yshift, xshift = 0, 0 # 示例,实际可能根据需求调整

# for y, x in itertools.product(range(yshift, ny, 2), range(xshift, nx, 2)):
#     block = grid[y:y+2, x:x+2]
#     grid[y:y+2, x:x+2].flat = transitions[block.tobytes()]

这种方法虽然直观,但由于涉及到大量的Python级别循环和切片操作,对于大型数组而言,其性能瓶颈非常明显。NumPy的优势在于其底层C/Fortran实现,能够对整个数组或其视图进行批量操作,从而避免了Python解释器的开销。

利用NumPy的步幅技巧优化2x2块操作

NumPy的np.lib.stride_tricks.as_strided函数是一个强大但需要谨慎使用的工具。它允许我们创建一个现有数组的“视图”,而无需复制数据。通过巧妙地设置视图的shape和strides,我们可以将一个二维数组看作是由多个重叠或不重叠的子块组成的更高维数组。

1. 核心概念:np.lib.stride_tricks.as_strided

as_strided函数的核心在于shape和strides参数:

  • shape: 新视图的维度大小。
  • strides: 访问新视图中每个维度元素时需要跳过的字节数。

对于一个形状为 (N, M) 的二维数组 A,其步幅通常是 (A.itemsize * M, A.itemsize)。这意味着在行方向上移动一个元素需要跳过 M 个元素的大小,在列方向上移动一个元素需要跳过 1 个元素的大小。

要将一个 (10, 10) 的数组 A 视为一个 (5, 5) 的2x2块数组 Av,我们需要:

  • Av 的第一个维度(块的行索引)每移动一步,在 A 中需要跳过 2 * A.strides[0] 字节(即两行)。
  • Av 的第二个维度(块的列索引)每移动一步,在 A 中需要跳过 2 * A.strides[1] 字节(即两列)。
  • Av 的第三、第四个维度(2x2块内部的行、列索引)则对应 A 的原始行、列步幅。

因此,Av 的步幅将是 (A.strides[0]*2, A.strides[1]*2, A.strides[0], A.strides[1])。

import numpy as np

# 示例:创建一个10x10的0/1随机数组
A = np.random.randint(0, 2, (10, 10))
print("原始数组 A:\n", A)

# 使用 as_strided 创建一个视图 Av
# shape=(5,5,2,2) 表示 Av 是一个 5x5 的数组,每个元素都是一个 2x2 的子数组
# strides=(A.strides[0]*2, A.strides[1]*2, A.strides[0], A.strides[1])
# 第一个2*A.strides[0]:在 Av 的第一个维度(块行)移动1,A 的行索引移动2
# 第二个2*A.strides[1]:在 Av 的第二个维度(块列)移动1,A 的列索引移动2
# 第三个A.strides[0]:在 Av 的第三个维度(块内行)移动1,A 的行索引移动1
# 第四个A.strides[1]:在 Av 的第四个维度(块内列)移动1,A 的列索引移动1
Av = np.lib.stride_tricks.as_strided(A, shape=(5, 5, 2, 2),
                                     strides=(A.strides[0]*2, A.strides[1]*2,
                                              A.strides[0], A.strides[1]))

print("\nAv 的形状:", Av.shape) # (5, 5, 2, 2)
print("Av 的步幅:", Av.strides)
print("A 的步幅:", A.strides)

# 验证 Av 是 A 的视图,修改 Av 会影响 A
# Av[0, 0, 0, 0] = 99
# print("\n修改 Av[0,0,0,0] 后 A 的左上角:\n", A[:2,:2])

2. 构建查找表(Lookup Table, LUT)

由于我们需要根据2x2块的当前值来决定其新的值,一个查找表是理想的选择。对于一个由0和1组成的2x2块,它有 $2^4 = 16$ 种可能的组合。我们可以创建一个多维数组作为查找表,其索引对应2x2块的四个元素。

# lut 的形状 (2,2,2,2,2,2)
# 前四个 '2' 代表 2x2 块的四个布尔值 (0或1)
# 后两个 '2,2' 代表替换后的 2x2 结果
lut = np.zeros((2, 2, 2, 2, 2, 2), dtype=A.dtype)

# 填充查找表(根据您的 transitions 字典进行翻译)
# 示例:如果原始块是 [[0,0],[0,0]],替换为 [[1,1],[1,1]]
lut[0, 0, 0, 0] = [[1, 1], [1, 1]]
# 示例:如果原始块是 [[0,0],[0,1]],替换为 [[1,1],[1,0]]
lut[0, 0, 0, 1] = [[1, 1], [1, 0]]
# 示例:如果原始块是 [[0,0],[1,0]],替换为 [[1,1],[0,1]]
lut[0, 0, 1, 0] = [[1, 1], [0, 1]]
# 示例:如果原始块是 [[1,1],[0,0]],替换为 [[1,1],[1,1]]
lut[1, 1, 0, 0] = [[1, 1], [1, 1]]
# 其他未定义的组合将保持为0(根据 lut 初始化)

3. 应用查找表进行块修改

有了 Av 视图和 lut,我们可以使用NumPy的“花式索引”(fancy indexing)来一次性更新所有2x2块。我们将 Av 中的每个2x2块的四个元素作为索引,去查找 lut 中对应的新值。

Opus
Opus

AI生成视频工具

下载
# 原始数组 A (再次生成一个,避免之前的修改影响)
A = np.random.randint(0, 2, (10, 10))
print("\n修改前的数组 A:\n", A)

# 重新创建 Av 视图
Av = np.lib.stride_tricks.as_strided(A, shape=(5, 5, 2, 2),
                                     strides=(A.strides[0]*2, A.strides[1]*2,
                                              A.strides[0], A.strides[1]))

# 使用花式索引和查找表更新所有 2x2 块
# Av[...,0,0] 提取所有 2x2 块的左上角元素
# Av[...,0,1] 提取所有 2x2 块的右上角元素
# Av[...,1,0] 提取所有 2x2 块的左下角元素
# Av[...,1,1] 提取所有 2x2 块的右下角元素
Av[:] = lut[Av[..., 0, 0], Av[..., 0, 1], Av[..., 1, 0], Av[..., 1, 1]]

print("\n修改后的数组 A:\n", A)

通过这种方式,我们避免了显式的Python循环,将操作完全推送到NumPy的底层实现,从而实现了显著的性能提升。

4. 示例与结果

假设我们有一个初始数组 A:

# 原始数组 A
[[0 1 0 1 1 1 1 1 1 0]
 [0 1 1 1 1 1 1 1 0 0]
 [0 0 1 1 0 1 0 1 1 0]
 [0 0 0 1 0 1 1 0 1 1]
 [0 1 1 1 0 1 1 1 0 0]
 [0 0 0 0 0 1 1 0 0 0]
 [1 0 1 1 1 0 1 0 0 0]
 [0 1 1 0 1 0 0 0 1 0]
 [1 1 0 1 0 1 0 1 1 0]
 [0 1 1 1 1 1 1 1 1 1]]

经过上述 as_strided 和 lut 操作后,根据 lut 的定义,数组 A 将被修改为(部分示例):

# 修改后的数组 A
[[0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]
 [1 1 0 0 0 0 0 0 0 0]
 [1 1 0 0 0 0 0 0 0 0]
 [0 0 1 1 0 0 0 0 1 1]
 [0 0 1 1 0 0 0 0 1 1]
 [0 0 0 0 0 0 0 0 1 1]
 [0 0 0 0 0 0 0 0 0 1]
 [0 0 0 0 0 0 0 0 0 0]
 [0 0 0 0 0 0 0 0 0 0]]

可以看到,原始数组的2x2子块根据 lut 中的规则被整体替换。

进阶优化:单索引查找表

上述方法中,花式索引 lut[Av[..., 0, 0], Av[..., 0, 1], Av[..., 1, 0], Av[..., 1, 1]] 需要重复四次 Av 的索引,这在某些情况下可能显得冗长。我们可以通过将2x2块的四个布尔值(0或1)编码成一个0到15的单一整数索引来简化查找表的使用。

假设2x2块的元素为:

[[a, b],
 [c, d]]

我们可以将其转换为一个整数索引:idx = a*8 + b*4 + c*2 + d*1。

# 创建一个单索引的查找表
lut2 = np.zeros((16, 2, 2), dtype=A.dtype)

# 填充 lut2
# 示例:索引 0 (对应 [[0,0],[0,0]]) 替换为 [[1,1],[1,1]]
lut2[0] = [[1, 1], [1, 1]]
# 示例:索引 1 (对应 [[0,0],[0,1]]) 替换为 [[1,1],[1,0]]
lut2[1] = [[1, 1], [1, 0]]
# 示例:索引 2 (对应 [[0,0],[1,0]]) 替换为 [[1,1],[0,1]]
lut2[2] = [[1, 1], [0, 1]]
# 示例:索引 12 (对应 [[1,1],[0,0]]) 替换为 [[1,1],[1,1]]
lut2[12] = [[1, 1], [1, 1]]
# 其他未定义的组合将保持为0

# 原始数组 A (再次生成一个)
A = np.random.randint(0, 2, (10, 10))
print("\n使用单索引查找表前 A:\n", A)

# 重新创建 Av 视图
Av = np.lib.stride_tricks.as_strided(A, shape=(5, 5, 2, 2),
                                     strides=(A.strides[0]*2, A.strides[1]*2,
                                              A.strides[0], A.strides[1]))

# 将每个 2x2 块转换为一个 0-15 的整数索引
# Av*[[8,4],[2,1]] 会将每个 2x2 块的元素乘以对应的权重
# .sum(axis=(2,3)) 会对每个加权后的 2x2 块求和,得到一个 (5,5) 的索引数组
idx = (Av * [[8, 4], [2, 1]]).sum(axis=(2, 3))

# 使用单索引查找表更新所有 2x2 块
Av[:] = lut2[idx]

print("\n使用单索引查找表后 A:\n", A)

这种方法通过一次计算得到所有块的索引,再进行一次查找,使得代码更加简洁。

注意事项

  1. as_strided的安全性:as_strided是一个低级函数,如果shape和strides设置不当,可能导致访问到数组边界之外的内存,从而引发难以调试的错误甚至程序崩溃。请务必确保计算出的shape和strides与原始数组的内存布局兼容,且不会越界。
  2. 内存效率:as_strided创建的是一个视图,不涉及数据复制,因此在内存使用上非常高效。所有对视图的修改都会直接反映到原始数组上。
  3. 适用场景:这种方法特别适用于固定大小的、不重叠的子块操作,且子块的转换规则可以通过查找表表示的情况。如果块之间存在复杂重叠或转换逻辑无法用简单查找表表示,可能需要考虑其他NumPy函数或更复杂的算法。
  4. 数据类型:将布尔值或0/1整数编码为索引时,请确保数据类型一致,以避免意外结果。上述示例假设数组元素是0或1。
  5. 部分区域更新:如果只需要更新数组的某个特定区域,可以在创建 Av 视图后,对 Av 进行切片操作,例如 Av[2:4, 2:4] = lut2[idx[2:4, 2:4]]。

总结

通过巧妙地运用NumPy的np.lib.stride_tricks.as_strided函数创建数组的视图,并结合预先构建的查找表,我们可以实现对二维数组中2x2块的高效矢量化修改。这种方法避免了Python层面的循环开销,显著提升了处理性能,是NumPy在处理特定模式的数组操作时的强大体现。理解并掌握这种技巧,将有助于您编写更高效、更“NumPyic”的代码。

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

706

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

624

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

734

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

616

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1234

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

573

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

694

2023.08.11

苹果官网入口直接访问
苹果官网入口直接访问

苹果官网直接访问入口是https://www.apple.com/cn/,该页面具备0.8秒首屏渲染、HTTP/3与Brotli加速、WebP+AVIF双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

7

2025.12.24

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.4万人学习

SciPy 教程
SciPy 教程

共10课时 | 0.9万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

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