网易首页 > 网易号 > 正文 申请入驻

NumPy广播机制与C语言扩展

0
分享至

本篇重点介绍广播机制以及针对高维数组的轴操作,最后对 NumPy 的 C 语言扩展作了介绍。

  • 广播机制

  • 转置等轴操作

  • 通用函数ufunc

  • NumPyC语言扩展

1广播

NumPy 运算通常是在两个数组的元素级别上进行的。最简单情况就是,两个具有完全相同 shape 的数组运算,如下面例子所示,

a = np.array([1.0, 2.0, 3.0])
b = np.array([2.0, 2.0, 2.0])
a * b

numpy 的广播机制是指在执行算术运算时处理不同 shape 的数组的方式。在一定规则下,较小的数组在较大的数组上广播,从而使得数组具有兼容的 shape。

a = np.array([1.0, 2.0, 3.0])
b = 2.0
a * b
发现这两个计算的结果是一样的,但第二个是有广播机制在发挥作用。 广播规则

在两个数组上执行运算时,NumPy 比较它们的形状。它从 shape 的最右边开始往左一一比较。如果所有位子比较下来都是下面两种情况之一,

  • 相同位子上的两个数字相等

  • 或者其中之一是 1

那么这两个数组可以运算。如果不满足这些条件,则将引发 ValueError,表明数组的 shape 不兼容。

可见,数组的 shape 好比人的八字,两个人如果八字不合,那是不能在一起滴。

在下面这些示例中,A 和 B 数组中长度为 1 的那些轴(缺失的轴自动补 1),在广播期间会扩展为另一个数组相同位子上更大的长度,

A (3d array): 15 x 3 x 5
B (3d array): 15 x 1 x 5
Result (3d array): 15 x 3 x 5

A (3d array): 15 x 3 x 5
B (2d array): 3 x 5
Result (3d array): 15 x 3 x 5

A (3d array): 15 x 3 x 5
B (2d array): 3 x 1
Result (3d array): 15 x 3 x 5

A (4d array): 8 x 1 x 6 x 1
B (3d array): 7 x 1 x 5
Result (4d array): 8 x 7 x 6 x 5

下面例子中第一个数组的 shape 为 (3,3),第二个数组的 shape 为 (3,),此时相当于 (1,3),因此先将第二个数组的 shape 改为 (3,3),相当于原来数组沿着 0 轴再复制 2 份。

为了更好地理解这个机制,下面再给出几个例子。下图共三行,分别对应三种广播方式,请对照后面代码。

而下面例子不满足广播规则,因而不能执行运算。

A (1d array): 3
B (1d array): 4 # 倒数最后的轴长度不兼容

A (2d array): 4 x 3
B (1d array): 4 # 倒数最后的轴长度不兼容

A (2d array): 2 x 1
B (3d array): 8 x 4 x 3 # 倒数第二个轴长度不兼容
〄 不能广播的例子。 广播机制小结

  • 广播机制为数组运算提供了一种便捷方式。

  • 话虽如此,它并非在所有情况下都有效,并且实际上强加了执行广播必须满足的严格规则。

  • 仅当数组中每个维的形状相等或维的大小为 1 时,才能执行算术运算。

2维度增减 维度增加

在需要增加轴的位子使用np.newaxis或者None

x = np.arange(6).reshape(2,3)
x, x.shape
x1 = x[:,np.newaxis,:]
x1, x1.shape
# 或者
x2 = x[:,None,:]
x2, x2.shape
维度压缩 有时候需要将数组中多余的轴去掉,以降低数组维度的目的。numpy.squeeze( )

  • 从数组中删除单维度的轴,即把 shape 中为 1 的维度去掉。

x = np.arange(6).reshape(2,1,3)
y = x.squeeze()
xd = x.__array_interface__['data'][0]
yd = y.__array_interface__['data'][0]
〄 查看数据在内存中的地址,验证是否指向同一块内存。 3数组转置(换轴)x = np.arange(9).reshape(3, 3)
y = np.transpose(x) # 或者 y = x.transpose() 或者 x.T
y = np.transpose(x, [1, 0])
x = np.array([3,2,1,0,4,5,6,7,8,9,10,11,12,13,14,15]).reshape(2, 2, 4)
y1 = np.transpose(x, [1, 0, 2])

请对照下图理解这个三维数组在内存中的样子以及对它的不同视图(view)。关于这点,文末附上的进阶篇有详细解读。

看看变轴后各个数组的元素具体是怎样的,注意,它们都指向同一份数据。

这是怎么实现对内存中同一份数据使用不同的轴序呢?实际上,数据还是那些数据,更改的是各个轴上的步长 stride。

x.strides, y1.strides, y2.strides
# 数据还是同一份
id(x.data), id(y1.data), id(y2.data)

再看一个例子,三维数组有三个轴,注意换轴后每个轴的步长。

x = np.arange(16).reshape(2, 2, 4)
y = x.transpose((1, 0, 2))

两个数组三个轴对应的步长不同了。

轴更换后,下标也跟着换了,所以换轴前后相同下标指向的数据是不同的。

RGB 图像数据

  • 每张图像由红绿蓝三个通道组成,每个通道对应一个32×32的二维数组

〄 一张 32x32 像素的图像。

看下图,从左到右,分别对应图像数据在内存中的存放,将一维数组转化为三维数组,更换轴。

那么,为什么要换轴呢?因为不同程序包对数据的要求不同,我们为了使用它们,需要按照它们对参数的要求来对数据作相应调整。

而有时候,并不需要换轴,只需要更换某个轴上元素的次序即可,例如,

# 变换某个轴上元素的次序
z = x[..., (3, 2, 1, 0)]
4通用函数 ufunc 函数

  • ufunc 是 universal function 的缩写,是指能对数组的每个元素进行操作的函数,而不是针对 narray 对象操作。

  • NumPy 提供了大量的 ufunc 函数。这些函数在对 narray 进行运算的速度比使用循环或者列表推导式要快得多。

  • NumPy 内置的许多 ufunc 函数是 C 语言实现的,因此计算效率很高。

x = np.linspace(0, 2*np.pi, 5)
y, z = np.sin(x), np.cos(x)
# 将结果直接传给输入 x
np.sin(x, x)
性能比较import time
import math
import numpy as np
x = [i for i in range(1000000)]

# math.sin
start = time.process_time()
for i, t in enumerate(x):
x[i] = math.sin(t)
math_time = time.process_time()-start

# numpy.sin
x = np.array(x, dtype=np.float64)
start = time.process_time()
np.sin(x, x)
numpy_time = time.process_time()-start

# comparison
math_time, numpy_time, math_time/numpy_time
reduce 操作

  • 这是 NumPy 内置的通用函数,如果需要这样的计算,建议直接使用,不要自己实现。

  • 沿着轴对数组进行操作,相当于将运算符插入到沿轴的所有子数组或者元素当中。

  • 格式为:.reduce (array=, axis=0, dtype=None)

np.add.reduce([1,2,3])
np.add.reduce([[1,2,3],[4,5,6]], axis=1)
np.multiply.reduce([[1,2,3],[4,5,6]], axis=1)
accumulate 操作
  • 这也是 NumPy 内置的通用函数,如果需要这样的计算,建议直接使用,不要自己实现。

  • 与 reduce 类似,只是它返回的数组和输入的数组的 shape 相同,保存所有的中间计算结果。

np.add.accumulate([1,2,3])
np.add.accumulate([[1,2,3],[4,5,6]], axis=1)
自定义 ufunc 函数# 定义一个 python 函数
def ufunc_diy(x):
c, c0, hc = 0.618, 0.518, 1.0
x = x - int(x)
if x >= c:
r = 0.0
elif x < c0:
r = x / c0 * hc
else:
r = (c-x) / (c-c0) * hc
return r
x = np.linspace(0, 2, 1000000)
ufunc_diy(x)
start = time.process_time()
y1 = np.array([ufunc_diy(t) for t in x])
time_1 = time.process_time()-start
time_1
np.frompyfunc 函数
  • 将一个计算单个元素的函数转换成 ufunc 函数

ufunc = np.frompyfunc(ufunc_diy, 1, 1)
start = time.process_time()
y2 = ufunc(x)
time_2 = time.process_time()-start
time_2
NumPy 之 C 扩展

本文主要介绍两种扩展方式,

  • ctypes

  • Cython

ctypes
  • ctypes 是 Python 的一个外部库,提供和 C 语言兼容的数据类型,可以很方便地调用 dll/so 中输出的 C 接口函数。

#ufunc.c
void ufunc_diy(double x, double y, int size) {

double xx,r,c=0.618,c0=0.518,hc=1.0;
for(int i=0;i xx = x[i]-(int)(x[i]);
if (xx>=c) r=0.0;
else if (xx else r=(c-xx)/(c-c0)*hc;
y[i]=r;
}
}
'''
#ufunc.py
""" Example of wrapping a C library function that accepts a C double array as
input using the numpy.ctypeslib. """

import numpy as np
import numpy.ctypeslib as npct
from ctypes import c_int

array_1d_double = npct.ndpointer(dtype=np.double, ndim=1, flags='CONTIGUOUS')

# load the library, using numpy mechanisms
lib = npct.load_library("lib_ufunc", ".")

# setup the return types and argument types
lib.ufunc_diy.restype = None
lib.ufunc_diy.argtypes = [array_1d_double, array_1d_double, c_int]

def ufunc_diy_func(in_array, out_array):
return lib.ufunc_diy(in_array, out_array, len(in_array))
# 编译
# gcc -shared -fPIC -O2 ufunc.c -ldl -o lib_ufunc.so
import time
import numpy as np
import ufunc

start = time.process_time()
ufunc.ufunc_diy_func(x, x)
end = time.process_time()
print("ufunc_diy time: ", end-start)
# python test_ufunc.py
# ufunc_diy time: 0.003 - 0.008
Cython

  • Cython 是 Python 的一个超集,可以编译为 C,Cython 结合了 Python 的易用性和原生 C 代码的高效率。

# ufunc_diy.h
void ufunc_diy(double in_array, double out_array, int size);
# ufunc_diy.c
void ufunc_diy(double x, double y, int size) {

double xx,r,c=0.618,c0=0.518,hc=1.0;
for(int i=0;i xx = x[i]-(int)(x[i]);
if (xx>=c) r=0.0;
else if (xx else r=(c-xx)/(c-c0)*hc;
y[i]=r;
}
}
# Cython支持 NumPy
# 在代码中声明 a = np.array([0,10,20,30])
b = np.array([0,1,2])cimport numpy,使用函数。
#_ufunc_cython.pyx_
""" Example of wrapping a C function that takes C double arrays as input using
the Numpy declarations from Cython """

# cimport the Cython declarations for numpy
cimport numpy as np

# if you want to use the Numpy-C-API from Cython
# (not strictly necessary for this example, but good practice)
np.import_array()

# cdefine the signature of our c function
cdef extern from "ufunc_diy.h":
void ufunc_diy (double in_array, double out_array, int size)

# create the wrapper code, with numpy type annotations
def ufunc_diy_func(np.ndarray[double, ndim=1, mode="c"] in_array not Noa = np.array([0,10,20,30])
b = np.array([0,1,2])ne,
np.ndarray[double, ndim=1, mode="c"] out_array not None):
ufunc_diy( np.PyArray_DATA(in_array),
np.PyArray_DATA(out_array),
in_array.shape[0])
# setup.py
from distutils.core import setup, Extension
import numpy
from Cython.Distutils import build_ext

setup(
cmdclass={'build_ext': build_ext},
ext_modules=[Extension("ufunc_cython",
sources=["_ufunc_cython.pyx", "ufunc_diy.c"],
include_dirs=[numpy.get_include()])],
)
# 或者
from distutils.core import setup
import numpy
from Cython.Build import cythonize

setup(
ext_modules=cythonize("_ufunc_cython.pyx", annotate=True),
include_dirs=[numpy.get_include()]
)
# 编译
python setup.py build_ext --inplace

可以看到多了两个文件,一个是_ufunc_cython.c,一个是ufunc_cython.so(如果是 windows,则是 .pyd)。

c文件就是cythonpyx文件解析成一个c文件,它不依赖平台,而so或者pyd文件,则是将c文件进行编译后的动态链接库,依赖于平台。

import time
import numpy as np
import ufunc_cython

start = time.process_time()
ufunc_cython.ufunc_diy_func(x, x)
end = time.process_time()
print("ufunc_diy time: ", end-start)

实际跑一下就知道,C 扩展下来有时候能得到上百倍的性能提升,这样来说,部分函数用 C 语言来实现还是值得。

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
黑龙江一考生因电话停机错过考编递补 为其充话费的人社局局长:该考生已主动联系

黑龙江一考生因电话停机错过考编递补 为其充话费的人社局局长:该考生已主动联系

红星新闻
2024-06-19 21:10:27
特斯拉落户广州人沸腾,台积电南京扩产却成毒刺:赏打工人碗饭吧

特斯拉落户广州人沸腾,台积电南京扩产却成毒刺:赏打工人碗饭吧

大风文字
2024-06-20 19:06:38
澳洲网红为了博流量跟狗跨物种交配,在西方引起效仿?

澳洲网红为了博流量跟狗跨物种交配,在西方引起效仿?

我有一盐
2024-06-20 20:44:11
沪指翻红,此前一度跌超0.47%

沪指翻红,此前一度跌超0.47%

每日经济新闻
2024-06-21 10:05:09
俄罗斯5地举行线上“脱俄公投”,结果是......

俄罗斯5地举行线上“脱俄公投”,结果是......

小刀99
2024-06-20 20:04:29
大快人心!烧伤科阿宝为俞老师发声,南医大的洗白者这次没话说了

大快人心!烧伤科阿宝为俞老师发声,南医大的洗白者这次没话说了

小怪吃美食
2024-06-21 08:08:14
4年3200万美元!雷迪克正式出任湖人主教练 联手詹眉冲击总冠军

4年3200万美元!雷迪克正式出任湖人主教练 联手詹眉冲击总冠军

罗说NBA
2024-06-21 04:13:39
签了!108亿美元,航空巨头要买100架国产C919飞机!还获得了较大价格优惠

签了!108亿美元,航空巨头要买100架国产C919飞机!还获得了较大价格优惠

每日经济新闻
2024-06-21 00:32:09
欧洲杯最神奇小组!4队都未出局,前3排名或反转,堪比国足算分

欧洲杯最神奇小组!4队都未出局,前3排名或反转,堪比国足算分

足球慢镜头
2024-06-21 09:06:39
成绩不理想,又想去中国?一留学海报打出惊人宣传语,还送免费住宿+每月津贴

成绩不理想,又想去中国?一留学海报打出惊人宣传语,还送免费住宿+每月津贴

小萝卜丝
2024-06-21 08:31:46
姜萍事件新消息:她初三的数学老师发声,让我们又看到了一些真相

姜萍事件新消息:她初三的数学老师发声,让我们又看到了一些真相

法制社会报
2024-06-20 12:11:39
管姚:普京访越南,老美到底在骂什么?

管姚:普京访越南,老美到底在骂什么?

直新闻
2024-06-20 23:27:08
呼和浩特一家五口被杀害,仅剩下一个儿媳妇,村干部曝更多细节

呼和浩特一家五口被杀害,仅剩下一个儿媳妇,村干部曝更多细节

180°视角
2024-06-20 15:16:22
团结,阿根廷进球后全队围着麦卡利斯特庆祝

团结,阿根廷进球后全队围着麦卡利斯特庆祝

懂球帝
2024-06-21 09:44:20
高考不能改变命运,但是WTO可以

高考不能改变命运,但是WTO可以

不死好鸟
2024-06-20 14:06:20
复旦打老师的男生,到手的研究生没了,北大已回应,打人原因曝光

复旦打老师的男生,到手的研究生没了,北大已回应,打人原因曝光

飞鱼的说说
2024-06-20 23:47:21
球霸!36岁本泽马彻底放飞,7个月逼走2主帅!高层:他比教练重要

球霸!36岁本泽马彻底放飞,7个月逼走2主帅!高层:他比教练重要

风过乡
2024-06-21 08:00:32
中国香港海关查获596颗高端CPU:走私中国内地,价值1120万!Intel至强处理器,可支持AI加速计算、云服务

中国香港海关查获596颗高端CPU:走私中国内地,价值1120万!Intel至强处理器,可支持AI加速计算、云服务

和讯网
2024-06-20 16:09:32
罗马诺:卡拉菲奥里想留在意甲,但尤文2000-2500万报价不够

罗马诺:卡拉菲奥里想留在意甲,但尤文2000-2500万报价不够

懂球帝
2024-06-20 07:04:12
小学生路边偶遇小米SU7一顿爆夸:太帅了!哪里搞的小米啊?雷军转发微博,并配上三个“得意”的表情

小学生路边偶遇小米SU7一顿爆夸:太帅了!哪里搞的小米啊?雷军转发微博,并配上三个“得意”的表情

和讯网
2024-06-20 17:18:21
2024-06-21 12:12:49
开源中国
开源中国
每天为开发者推送最新技术资讯
6337文章数 34226关注度
往期回顾 全部

科技要闻

王仲远:GPT4不是国内大模型的尽头

头条要闻

普京一天见了四位越南领导人 河内市委书记没有出现

头条要闻

普京一天见了四位越南领导人 河内市委书记没有出现

体育要闻

1-0"吊打"意大利 西班牙这就叫冠军相?

娱乐要闻

陈晓惹争议!被曝婚变离家出走冷暴力

财经要闻

普华永道,引火烧身

汽车要闻

领克纯电,来得不晚

态度原创

艺术
旅游
家居
本地
公开课

艺术要闻

穿越时空的艺术:《马可·波罗》AI沉浸影片探索人类文明

旅游要闻

强降雨天气来袭:桂林部分景点关闭 酒店启动退改

家居要闻

木质家具 充溢古典之风

本地新闻

2024·合肥印象|用崭新视角对话城市发展

公开课

近视只是视力差?小心并发症

无障碍浏览 进入关怀版