当前位置: 首页 > news >正文

Python Numpy 的 View 与 Copy 使用详解与优化技巧

Python Numpy 的 View 与 Copy 使用详解与优化技巧

文章目录

  • Python Numpy 的 View 与 Copy 使用详解与优化技巧
      • 一 NumPy Array 和 Python List
        • NumPy 与 List 的综合对比
      • 二 NumPy 的 View 和 Copy
        • 划重点
        • 1 copy 和 view
        • 2 操作 view 比 copy 快
        • 3 “光速” 展平矩阵
      • 三 优化一 数据选择提速
        • 1 view 的方式
        • 2 copy 的方式
      • 四 优化二 替代 copy 操作
        • 1 使用 `np.take()` 替代索引方式进行数据选择
        • 2 使用 `np.compress()` 替代 `mask` 选择数据
      • 五 优化三 利用 `out` 参数提升性能
        • 划重点
      • 六 完整代码示例
      • 七 源码地址

本文深入探讨了 Python Numpy 中的 ViewCopy 概念,并详细对比了它们的特性及应用场景。通过多个代码示例,展示了在不同情况下如何选择视图或副本来操作数据,以及它们对内存与计算性能的影响。此外,文章还提供了诸如使用 np.take()np.compress() 以及 out 参数等优化技巧,帮助开发者提升数据选择与运算效率。这些技巧在处理大规模数据时尤其有效,能够显著提高 Numpy 的运算性能。

导入依赖库

import numpy as np
import timeit
from functools import partial

一 NumPy Array 和 Python List

图文来自 Why Python is Slow: Looking Under the Hood
在这里插入图片描述

NumPy 与 List 的综合对比
特性NumPy 数组Python 原生列表
内存管理数组在内存中连续存储,访问速度快存储分散,访问速度相对较慢
数据类型支持固定数据类型(如 int32float64),高效使用内存支持多种数据类型,可能导致额外开销
运算性能支持向量化运算,无需显式循环,计算速度快通常需循环,效率较低
广播机制支持不同形状数组之间的运算不支持广播,运算需形状一致
功能丰富性提供大量数学和统计函数,适合科学计算功能较为简单,主要用于基本数据存储
维度支持支持多维数组(ndarray),处理复杂数据结构支持嵌套列表,操作不够高效

NumPy 数组在性能、功能和内存管理上优于 Python 原生列表,特别适合数值计算和大规模数据处理。

详细对比见 :Python NumPy 与 List 的性能对决:为何 NumPy 更胜一筹

二 NumPy 的 View 和 Copy

图文来自 SettingwithCopyWarning: How to Fix This Warning in Pandas

在这里插入图片描述

在这里插入图片描述

视图(View)和副本(Copy)特性对比

特性视图 (View)副本 (Copy)
定义视图是原数组的一个窗口,与原数组共享数据,只有结构被修改。副本是原数组的一个完全独立的复制,拥有独立的内存。
创建方法通过基本切片(如 a[1:4])、np.reshapenp.transpose 等操作,这些操作不会复制数据而只修改视图。通过 .copy() 方法、np.copy() 函数以及通过整数索引和布尔索引(如 a[idx]a[mask])创建副本。
优点- 不需要额外内存,节省内存使用和计算资源
- 快速访问和修改数据
- 修改副本不会影响原数组
- 可以安全地修改数据,适合数据隔离和保密
缺点- 修改视图会影响原数组
- 在不清晰的情况下可能会导致数据被意外修改
- 需要额外的内存和计算资源来创建和维护副本
- 操作速度可能比视图慢
划重点
  • 基本切片创建视图(View)。
  • 整数数组索引和布尔索引创建副本(Copy)。
1 copy 和 view

在副本 (copy) 上修改数据 不会影响 源数据,而在视图 (view) 上修改数据 会影响 “窗口”内的源数据。

    a = np.arange(1, 7).reshape((3, 2))print(a)a_view = a[:2]a_copy = a[:2].copy()print(a_view)print(a_copy)a_copy[1, 1] = 0print("在 copy 上修改数据,不会影响源数据:\n", a)a_view[1, 1] = 0print("在 view 上修改数据,会影响'窗里'的源数据:\n", a)
2 操作 view 比 copy 快

在视图 (view) 的操作中,处理速度通常比副本 (copy) 更快。通过以下代码可以展示这个差异:

def get_run_time(func, *args):repeat = 3number = 200return min(timeit.Timer(partial(func, *args)).repeat(repeat=repeat, number=number)) / numbera = np.random.rand(1000, 1000)
b = np.random.rand(1000, 1000)def f1():global b# 这会产生新的 bb = 2 * bdef f2():global a# 这不会产生新的 aa *= 2  # 和 a[:] *= 2 一样print('%.6f - b = 2*b' % get_run_time(f1))
print('%.6f - a *= 2' % get_run_time(f2))

运行结果如下:

0.000771 - b = 2*b
0.000238 - a *= 2

可以看到,视图操作 (a *= 2) 的速度比副本操作 (b = 2*b) 快了三倍以上。这是因为视图修改数据时不需要创建新的数组,而副本操作则会生成新的对象,导致额外的内存开销和处理时间。

3 “光速” 展平矩阵

在将矩阵展平时,np.flatten()np.ravel() 都能完成相似的功能,但它们的底层机制有所不同:

  • flatten() 总是执行 复制操作,即无论原始数组的存储方式如何,都会返回一个新的数组副本。
  • ravel() 只在必要时进行 复制操作,如果可以,它会返回一个视图,因此速度通常更快。

我们可以通过以下代码来对比它们的运行速度:

def f11():print(a.flatten())def f22():print(b.ravel())# 总是 Copy
print('%.6f - flatten' % get_run_time(f11))
# 用 ravel(), 需要 copy 的时候才会被 copy
print('%.6f - ravel' % get_run_time(f22))

运行结果如下:

0.000656 - flatten
0.000000 - ravel

可以看出,ravel() 的速度远快于 flatten(),甚至在这次运行中 ravel() 的执行时间接近 0。这是因为 ravel() 只在必要时才会进行内存复制,而 flatten() 总是创建数组的副本,导致更多的时间消耗。

三 优化一 数据选择提速

操作上优化

1 view 的方式
    a = np.zeros([100, 100])print(a)# 切片的操作是  viewa_view1 = a[1:2, 3:6]  # 切片 slicea_view2 = a[:10]  # 同上a_view3 = a[::2]  # 跳行a_view4 = a.ravel()  # 上面提到了
2 copy 的方式
    # # 花式索引访问的方式是 copya = np.zeros([2, 2])# a = np.random.rand(2, 2)print("a = ", a)a_copy2 = a[[True, True], [False, True]]  # 用 maskprint("a_copy2 = ", a_copy2)a = np.zeros([100, 100])a_copy1 = a[[1, 4, 6], [2, 4, 6]]  # 用 index 选a_copy3 = a[[1, 2], :]  # 虽然 1,2 的确连在一起了, 但是他们确实是 copya_copy4 = a[a[1, :] != 0, :]  # fancy indexinga_copy5 = a[np.isnan(a[:, 0]), :]  # fancy indexing

四 优化二 替代 copy 操作

1 使用 np.take() 替代索引方式进行数据选择

当我们通过索引选择数据时,np.take() 是一种更高效的替代方法。与直接使用索引相比,它通常能加快处理速度。以下代码对比

a000 = np.random.rand(1000000, 10)
indices = np.random.randint(0, len(a000), size=10000)def f111():# 使用索引进行数据选择_ = a000[indices]def f222():# 使用 np.take() 进行数据选择_ = np.take(a000, indices, axis=0)print('%.6f - [indices]' % get_run_time(f111))
print('%.6f - take' % get_run_time(f222))

运行结果

0.000416 - [indices]
0.000186 - take

可以看到,np.take() 比索引方式快了约 50%,表现更为优越,尤其是在处理大规模数据时。

2 使用 np.compress() 替代 mask 选择数据

在使用掩码(mask)筛选数据时,np.compress() 可以更高效地完成此任务。以下代码两者的性能对比

a111 = np.random.rand(10000, 10)
mask = a111[:, 0] < 0.5def f1111():# 使用 mask 进行数据选择_ = a111[mask]def f2222():# 使用 np.compress() 进行数据选择_ = np.compress(mask, a111, axis=0)print('%.6f - [mask]' % get_run_time(f1111))
print('%.6f - compress' % get_run_time(f2222))

运行结果

0.000067 - [mask]
0.000023 - compress

结果表明,np.compress() 的速度明显快于直接使用 mask,几乎快了 3 倍。这是因为 np.compress() 在底层进行了优化,能够更高效地处理数据筛选。

五 优化三 利用 out 参数提升性能

在数组运算中,使用 out 参数可以显著提升性能。out 参数允许将运算结果直接存储在现有的数组中,避免了创建新的数组对象。以下代码对比了三种不同的操作方式的性能。

aa = np.zeros([1000, 1000])
bb = np.zeros_like(aa)
cc = np.zeros_like(aa)def f1a():# 不使用 out 参数,结果重新赋值给原数组aa[:] = np.add(aa, 1)def f2b():# 使用 out 参数,结果直接存储在原数组中np.add(bb, 1, out=bb)def f3c():# 结果赋值到一个新的数组_cc = np.add(cc, 1)print('%.6f - without out' % get_run_time(f1a))
print('%.6f - out' % get_run_time(f2b))
print('%.6f - new data' % get_run_time(f3c))

运行结果

0.000817 - without out
0.000229 - out
0.000668 - new data
划重点

使用 out 参数与不使用时的性能对比

操作方式时间(秒)说明
不使用 out 参数0.000817运算后重新赋值给原数组,涉及数组的重新分配和赋值
使用 out 参数0.000229运算结果直接存储在原数组中,性能最优
生成新数组0.000668结果赋值到新创建的数组,存在内存分配开销

使用 out 参数可以显著提升性能,特别是在需要频繁进行大规模数据运算时,通过避免不必要的内存分配和数据拷贝,能够使程序更高效。详情见官方文档

六 完整代码示例

# This is a sample Python script.# Press ⌃R to execute it or replace it with your code.
# Press Double ⇧ to search everywhere for classes, files, tool windows, actions, and settings.
import numpy as np
import timeit
from functools import partialdef get_run_time(func, *args):repeat = 3number = 200return min(timeit.Timer(partial(func, *args)).repeat(repeat=repeat, number=number)) / numbera = np.random.rand(1000, 1000)
b = np.random.rand(1000, 1000)def f1():global b# 这会产生新的 bb = 2 * bdef f2():global a# 这不会产生新的 aa *= 2  # 和 a[:] *= 2 一样def f11():a.flatten()def f22():b.ravel()a000 = np.random.rand(1000000, 10)
indices = np.random.randint(0, len(a000), size=10000)def f111():# fancy indexing_ = a000[indices]# print("a000[indices] = ", _)def f222():# take_ = np.take(a000, indices, axis=0)# print("np.take(a000, indices, axis=0) = ", _)a111 = np.random.rand(10000, 10)
mask = a111[:, 0] < 0.5def f1111():_ = a111[mask]def f2222():_ = np.compress(mask, a111, axis=0)a222 = np.zeros([10000, 10])def f11111(a222):a222 = a222 + 1def f22222(a222):a222 = np.add(a222, 1)aa = np.zeros([1000, 1000])
bb = np.zeros_like(aa)
cc = np.zeros_like(aa)def f1a():aa[:] = np.add(aa, 1)  # 把计算结果赋值回原数据def f2b():np.add(bb, 1, out=bb)  # 把计算结果赋值回原数据def f3c():_cc = np.add(cc, 1)  # 把计算结果赋值到新数据def print_hi(name):# Use a breakpoint in the code line below to debug your script.print(f'Hi, {name}')  # Press ⌘F8 to toggle the breakpoint.a = np.arange(1, 7).reshape((3, 2))a_view = a[:2]a_copy = a[:2].copy()# print(a_view, a_copy)a_copy[1, 1] = 0print("在 copy 上修改数据,不会影响源数据:\n", a)a_view[1, 1] = 0print("在 view 上修改数据,会影响'窗里'的源数据:\n", a)print('%.6f - b = 2*b' % get_run_time(f1))print('%.6f - a *= 2' % get_run_time(f2))print()# 总是 Copyprint('%.6f - flatten' % get_run_time(f11))# 用 ravel(), 需要 copy 的时候才会被 copyprint('%.6f - ravel' % get_run_time(f22))# 在选择数据上加速a = np.zeros([100, 100])# a = np.random.rand(10, 6)# print(a)# 切片的操作是  viewa_view1 = a[1:2, 3:6]  # 切片 slicea_view2 = a[:10]  # 同上a_view3 = a[::2]  # 跳行a_view4 = a.ravel()  # 上面提到了# print("a_view1 = ", a_view1)# print("a_view2 = ", a_view2)# print("a_view3 = ", a_view3)# print("a_view4 = ", a_view4)print()# 花式索引访问的方式是 copy# a = np.zeros([2, 2])a = np.random.rand(2, 2)# print("a = ", a)a_copy2 = a[[True, True], [False, True]]  # 用 mask# print("a_copy2 = ", a_copy2)a = np.zeros([100, 100])a_copy1 = a[[1, 4, 6], [2, 4, 6]]  # 用 index 选a_copy3 = a[[1, 2], :]  # 虽然 1,2 的确连在一起了, 但是他们确实是 copya_copy4 = a[a[1, :] != 0, :]  # fancy indexinga_copy5 = a[np.isnan(a[:, 0]), :]  # fancy indexing# print("a_copy1 = ", a_copy1)# print("a_copy3 = ", a_copy3)# print("a_copy4 = ", a_copy4)# print("a_copy5 = ", a_copy5)print()# 优化copy的方法# 1. 使用 np.take(), 替代用 index 选数据的方法。# print("len(a000) = ", len(a000))# print("indices = ", indices)print('%.6f - [indices]' % get_run_time(f111))print('%.6f - take' % get_run_time(f222))print()# 2. 使用 np.compress(), 替代用 mask 选数据的方法。print('%.6f - [mask]' % get_run_time(f1111))print('%.6f - compress' % get_run_time(f2222))print()# 非常有用的 out 参数print('%.6f - a + 1' % get_run_time(f11111, a222))print('%.6f - np.add(a, 1)' % get_run_time(f22222, a222))a = np.zeros([2, ])a_copy = np.add(a, 1)  # copy 发生在这里# print(a, a_copy)b = np.zeros([2, ])c = np.zeros_like(b)  # copy 发生在这里np.add(b, 1, out=c)# print(b, c)print()print('%.6f - without out' % get_run_time(f1a))print('%.6f - out' % get_run_time(f2b))print('%.6f - new data' % get_run_time(f3c))# Press the green button in the gutter to run the script.
if __name__ == '__main__':print_hi('Numpy 的 View 与 Copy')# See PyCharm help at https://www.jetbrains.com/help/pycharm/

复制粘贴并覆盖到你的 main.py 中运行,运行结果如下。

Hi, Numpy 的 View 与 Copy
在 copy 上修改数据,不会影响源数据:[[1 2][3 4][5 6]]
在 view 上修改数据,会影响'窗里'的源数据:[[1 2][3 0][5 6]]
0.000711 - b = 2*b
0.000232 - a *= 20.000638 - flatten
0.000000 - ravel0.000404 - [indices]
0.000177 - take0.000066 - [mask]
0.000023 - compress0.000017 - a + 1
0.000017 - np.add(a, 1)0.000801 - without out
0.000235 - out
0.000647 - new data

七 源码地址

代码地址:

国内看 Gitee 之 numpy/Numpy 的 View 与 Copy.py

国外看 GitHub 之 numpy/Numpy 的 View 与 Copy.py

引用 莫烦 Python


http://www.mrgr.cn/news/52227.html

相关文章:

  • Django学习- ORM基础操作_创建数据
  • 塞班和诺基亚(中古手机图,你见过哪个?)
  • 基于在线教育系统源码的企业培训平台开发解决方案详解
  • 【Python】AI Navigator对话流式输出
  • 网络抓包 - Fiddler 安装和汉化
  • 【网络安全】从NA到P1,我是如何扩大思路的?
  • 位置式PID测试代码
  • C++ Vector 容器的模拟实现及应用详解
  • Dockerfile 中 Expose 命令的作用
  • SpringBoot中集成海康威视SDK实现布防报警数据上传/交通违章图片上传并在linux上部署(附示例代码资源)
  • 12、论文阅读:利用生成对抗网络实现无监督深度图像增强
  • git 操作
  • 【java】深入解析Lambda表达式
  • 涉密网和非涉密网之间企业如何进行安全跨网文件交换?
  • Python可以实现列表排序的几种方法
  • 解决MybatisPlus updateById更新数据时将没传的数据也更新成了null
  • 深入理解计算机系统--计算机系统漫游
  • STM32L1x 片上温度传感器采用ADC及工厂校准数据提升测量温度精度
  • 惊!随身WiFi流量套餐竟有这些“坑爹”套路,你了解多少?随身WiFi哪个牌子好?
  • 【C语言】结构体的定义与使用