0

0

使用NumPy高效修改二维数组:2x2块操作的Stride Tricks技巧

DDD

DDD

发布时间:2025-11-06 12:51:27

|

273人浏览过

|

来源于php中文网

原创

使用NumPy高效修改二维数组:2x2块操作的Stride Tricks技巧

本教程详细介绍了如何利用numpy的`np.lib.stride_tricks.as_strided`函数高效地对二维数组进行2x2块的修改。文章通过创建数组的“块视图”并结合查找表(lut)机制,避免了传统python循环的性能瓶颈。内容涵盖了多维索引和扁平化索引两种lut构建方法,并提供了详细的代码示例与注意事项,旨在帮助读者掌握numpy高级技巧,优化大规模数组的块级操作性能。

引言:高效处理NumPy二维数组的块操作

在数据处理和科学计算中,我们经常需要对二维数组的局部区域,特别是固定大小的块(例如2x2)进行遍历和修改。传统的Python循环虽然直观,但在处理大型NumPy数组时效率低下,因为它无法充分利用NumPy底层C语言实现的优化。为了克服这一性能瓶颈,NumPy提供了一系列高级工具,其中np.lib.stride_tricks.as_strided是一个强大且灵活的函数,能够让我们以非传统的方式“查看”数组,从而实现高效的块级操作。本教程将深入探讨如何结合as_strided和查找表(Lookup Table, LUT)来高效地修改NumPy二维数组的2x2块。

核心技术:利用np.lib.stride_tricks.as_strided创建块视图

np.lib.stride_tricks.as_strided是一个用于创建数组新视图的函数。它的强大之处在于,你可以手动指定新视图的形状(shape)和步长(strides),而无需复制原始数据。这意味着对视图的修改会直接反映在原始数组上,极大地提高了内存效率和操作速度。

要将一个二维数组A(例如ny行nx列)转换为一个由2x2块组成的视图,我们需要理解shape和strides的含义:

  • shape: 新视图的形状。如果原始数组是ny行nx列,我们想将其看作(ny/2)行(nx/2)列的2x2块,那么新视图的形状将是(ny/2, nx/2, 2, 2)。前两个维度代表块的行和列索引,后两个维度代表每个块内部的行和列索引。
  • strides: 新视图中每个维度移动一个单位所需的字节数。
    • 对于块的行移动:原始数组每向下移动2行,新视图的块行索引才移动1。所以,块行的步长是原始数组行步长的两倍:A.strides[0] * 2。
    • 对于块的列移动:原始数组每向右移动2列,新视图的块列索引才移动1。所以,块列的步长是原始数组列步长的两倍:A.strides[1] * 2。
    • 对于块内部的行移动:新视图的第三个维度代表块内部的行。从块内第一行到第二行,实际上是在原始数组中向下移动了一行。所以,块内部行的步长是原始数组行步长:A.strides[0]。
    • 对于块内部的列移动:新视图的第四个维度代表块内部的列。从块内第一列到第二列,实际上是在原始数组中向右移动了一列。所以,块内部列的步长是原始数组列步长:A.strides[1]。

综合起来,strides参数将是(A.strides[0]*2, A.strides[1]*2, A.strides[0], A.strides[1])。

代码示例1:创建块视图

import numpy as np

# 假设原始数组A是一个10x10的0/1值数组
A = np.random.randint(0, 2, (10, 10))
print("原始数组 A:\n", A)

# 计算新视图的形状
# 如果A是(ny, nx),那么块视图的形状是(ny//2, nx//2, 2, 2)
block_rows = A.shape[0] // 2
block_cols = A.shape[1] // 2

# 创建块视图
# A.strides[0] 是行步长,A.strides[1] 是列步长
Av = np.lib.stride_tricks.as_strided(A,
                                     shape=(block_rows, block_cols, 2, 2),
                                     strides=(A.strides[0] * 2, A.strides[1] * 2, A.strides[0], A.strides[1]))

print("\n块视图 Av 的形状:", Av.shape)
# 验证 Av[0,0] 是否是 A 的左上角2x2块
print("\nAv[0,0] (第一个2x2块):\n", Av[0, 0])
print("\nA[0:2, 0:2] (A的左上角2x2块):\n", A[0:2, 0:2])

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

使用查找表(Lookup Table, LUT)进行块转换

一旦我们有了块视图Av,就可以使用查找表来根据每个2x2块的当前值来决定其新的值。查找表的构建方式可以有多种,这里介绍两种常见且高效的方法。

假设我们的2x2块中的元素都是0或1(布尔值)。一个2x2的块共有 $2^4 = 16$ 种可能的组合。

方法一:多维索引查找表

这种方法为查找表lut创建多个维度,每个维度对应2x2块中的一个元素的值。例如,一个lut的形状可以是(2, 2, 2, 2, 2, 2),其中前四个2代表输入块的四个元素([0,0], [0,1], [1,0], [1,1])的可能值(0或1),后两个2代表输出的2x2块。

构建查找表:

# lut 的形状:(输入块[0,0], 输入块[0,1], 输入块[1,0], 输入块[1,1], 输出块行, 输出块列)
lut = np.zeros((2, 2, 2, 2, 2, 2), dtype=A.dtype)

# 填充一些转换规则 (示例,根据实际需求定义)
# 假设输入块 [[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]]
# 假设输入块 [[1,1],[0,0]] 转换为 [[1,1],[1,1]]
lut[1, 1, 0, 0] = [[1, 1], [1, 1]]
# 其他未定义的组合将保持为0(根据lut的初始化)

应用查找表:

Opus
Opus

AI生成视频工具

下载

通过高级索引,我们可以直接将Av中每个2x2块的四个元素作为lut的索引,从而一次性完成所有块的转换。

# 重新初始化A以进行演示
A = np.random.randint(0, 2, (10, 10))
print("应用多维LUT前的 A:\n", A)

block_rows = A.shape[0] // 2
block_cols = A.shape[1] // 2
Av = np.lib.stride_tricks.as_strided(A,
                                     shape=(block_rows, block_cols, 2, 2),
                                     strides=(A.strides[0] * 2, A.strides[1] * 2, A.strides[0], A.strides[1]))

# 使用高级索引应用LUT
# Av[...,0,0] 获取所有块的[0,0]元素组成的数组
# Av[...,0,1] 获取所有块的[0,1]元素组成的数组,以此类推
Av[:] = lut[Av[..., 0, 0], Av[..., 0, 1], Av[..., 1, 0], Av[..., 1, 1]]

print("\n应用多维LUT后的 A:\n", A)

方法二:扁平化索引查找表

这种方法首先将每个2x2的0/1块转换成一个单一的整数索引(0-15),然后使用这个整数索引来查找一个一维的查找表。这种方式可以使查找表的定义更紧凑。

将2x2块转换为单一索引:

一个2x2的0/1块可以看作一个4位的二进制数。例如,块[[a,b],[c,d]]可以转换为索引 a*8 + b*4 + c*2 + d*1。

# 定义权重矩阵
weights = np.array([[8, 4], [2, 1]])

# 计算每个2x2块的扁平化索引
# (Av * weights) 会对每个2x2块内部进行元素级乘法
# .sum(axis=(2,3)) 会将每个2x2块内部的元素求和,得到一个 (block_rows, block_cols) 形状的索引数组
idx = (Av * weights).sum(axis=(2, 3))

构建扁平化查找表:

lut2的形状将是(16, 2, 2),其中16代表所有可能的输入块索引。

lut2 = np.zeros((16, 2, 2), dtype=A.dtype)

# 填充一些转换规则 (示例)
# 索引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]]
# 索引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("应用扁平化LUT前的 A:\n", A)

block_rows = A.shape[0] // 2
block_cols = A.shape[1] // 2
Av = np.lib.stride_tricks.as_strided(A,
                                     shape=(block_rows, block_cols, 2, 2),
                                     strides=(A.strides[0] * 2, A.strides[1] * 2, A.strides[0], A.strides[1]))

# 计算扁平化索引
idx = (Av * weights).sum(axis=(2, 3))

# 使用扁平化索引应用LUT
Av[:] = lut2[idx]

print("\n应用扁平化LUT后的 A:\n", A)

局部块修改

as_strided创建的视图支持常规的NumPy切片操作。这意味着你可以只对原始数组的某个特定区域的块进行修改,而不是整个数组。

# 重新初始化A
A = np.random.randint(0, 2, (10, 10))
print("进行局部修改前的 A:\n", A)

block_rows = A.shape[0] // 2
block_cols = A.shape[1] // 2
Av = np.lib.stride_tricks.as_strided(A,
                                     shape=(block_rows, block_cols, 2, 2),
                                     strides=(A.strides[0] * 2, A.strides[1] * 2, A.strides[0], A.strides[1]))

# 假设我们只想修改Av中索引为 (2,2) 到 (3,3) 的块区域
# 使用扁平化LUT进行修改
weights = np.array([[8, 4], [2, 1]])
lut2 = np.zeros((16, 2, 2), dtype=A.dtype)
lut2[0] = [[1, 1], [1, 1]] # 示例规则

# 计算指定区域块的索引
idx_partial = (Av[2:4, 2:4] * weights).sum(axis=(2, 3))

# 对指定区域的块进行修改
Av[2:4, 2:4] = lut2[idx_partial]

print("\n进行局部修改后的 A:\n", A)

注意事项与最佳实践

  1. 视图特性与内存效率: as_strided创建的是一个视图,不涉及数据复制。这意味着它非常内存高效,并且对视图的任何修改都会直接作用于原始数组。然而,这也要求使用者对视图的结构有清晰的理解,避免意外修改。
  2. 数据类型兼容性: 上述查找表方法假设块中的元素是0或1(布尔值或整数)。如果你的数组包含其他类型或更大范围的值,你需要相应地调整查找表的维度和索引转换逻辑。
  3. 性能优势: 这种基于NumPy向量化操作和as_strided的方法,相比于Python的for循环和itertools.product,能够带来显著的性能提升,尤其是在处理大型数组时。
  4. as_strided的谨慎使用: as_strided是一个低级函数,使用不当可能导致访问越界或创建无效视图,从而引发难以调试的问题。务必确保shape和strides参数的计算是准确的。
  5. 块的内存非连续性: 虽然as_strided创建的视图本身是连续的(逻辑上),但视图中的每个2x2小块在原始数组的内存中可能不是完全连续的。例如,Av[i,j]是一个2x2的视图,它内部的元素在原始数组中是连续的,但Av[i,j]作为一个整体,其与Av[i,j+1]之间存在跳跃。因此,直接对Av[i,j]调用如tobytes()这类依赖内存连续性的方法,可能无法得到预期的结果。本教程中的方法通过提取块的作为索引,有效规避了这一问题。
  6. 通用性: 这种方法不仅限于2x2块,可以推广到任意大小的块(例如3x3),只需相应调整shape和strides的计算以及查找表的维度或索引转换逻辑。

总结

通过巧妙地运用np.lib.stride_tricks.as_strided创建数组的块视图,并结合查找表机制,我们可以高效、内存友好地对NumPy二维数组的固定大小块进行批量修改。这种方法将复杂的循环逻辑转化为NumPy底层的向量化操作,显著提升了处理大规模数据的性能。掌握这一高级技巧,将使你在NumPy数据处理中如虎添翼。

相关专题

更多
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双格式图片、免登录浏览全参数等特性。本专题为大家提供相关的文章、下载、课程内容,供大家免费下载体验。

10

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号