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 中对应的新值。

Multiavatar
Multiavatar

Multiavatar是一个免费开源的多元文化头像生成器,可以生成高达120亿个虚拟头像

下载
# 原始数组 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”的代码。

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

通义千问
通义千问

阿里巴巴推出的全能AI助手

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
数据类型有哪几种
数据类型有哪几种

数据类型有整型、浮点型、字符型、字符串型、布尔型、数组、结构体和枚举等。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

310

2023.10.31

php数据类型
php数据类型

本专题整合了php数据类型相关内容,阅读专题下面的文章了解更多详细内容。

222

2025.10.31

go语言 数组和切片
go语言 数组和切片

本专题整合了go语言数组和切片的区别与含义,阅读专题下面的文章了解更多详细内容。

46

2025.09.03

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

416

2023.08.14

2026赚钱平台入口大全
2026赚钱平台入口大全

2026年最新赚钱平台入口汇总,涵盖任务众包、内容创作、电商运营、技能变现等多类正规渠道,助你轻松开启副业增收之路。阅读专题下面的文章了解更多详细内容。

32

2026.01.31

高干文在线阅读网站大全
高干文在线阅读网站大全

汇集热门1v1高干文免费阅读资源,涵盖都市言情、京味大院、军旅高干等经典题材,情节紧凑、人物鲜明。阅读专题下面的文章了解更多详细内容。

23

2026.01.31

无需付费的漫画app大全
无需付费的漫画app大全

想找真正免费又无套路的漫画App?本合集精选多款永久免费、资源丰富、无广告干扰的优质漫画应用,涵盖国漫、日漫、韩漫及经典老番,满足各类阅读需求。阅读专题下面的文章了解更多详细内容。

28

2026.01.31

漫画免费在线观看地址大全
漫画免费在线观看地址大全

想找免费又资源丰富的漫画网站?本合集精选2025-2026年热门平台,涵盖国漫、日漫、韩漫等多类型作品,支持高清流畅阅读与离线缓存。阅读专题下面的文章了解更多详细内容。

6

2026.01.31

漫画防走失登陆入口大全
漫画防走失登陆入口大全

2026最新漫画防走失登录入口合集,汇总多个稳定可用网址,助你畅享高清无广告漫画阅读体验。阅读专题下面的文章了解更多详细内容。

9

2026.01.31

热门下载

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

精品课程

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

共4课时 | 22.4万人学习

Django 教程
Django 教程

共28课时 | 3.7万人学习

SciPy 教程
SciPy 教程

共10课时 | 1.3万人学习

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

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