ufunc函數


  無燈可看。雨水從教正月半。探繭推盤。探得千秋字字看。

  銅駝故老。說著宣和似天寶。五百年前。曾向杭州看上元。

  ufunc是universal function的縮寫,他是一種對數組的每個元素進行運算的函數。NumPy的內置許多函數都是用C語言實現的因此,他們的計算速度十分的快。

>>> x = np.linspace(0,2*np.pi,10)
>>> x
array([ 0.        ,  0.6981317 ,  1.3962634 ,  2.0943951 ,  2.7925268 ,
        3.4906585 ,  4.1887902 ,  4.88692191,  5.58505361,  6.28318531])
>>> y = np.sin(x)
>>> y
array([  0.00000000e+00,   6.42787610e-01,   9.84807753e-01,
         8.66025404e-01,   3.42020143e-01,  -3.42020143e-01,
        -8.66025404e-01,  -9.84807753e-01,  -6.42787610e-01,
        -2.44929360e-16])

 

   可以查看函數的使用方法,得到使用方法。

  下面我們比較一下np.sin()和math.sin()的時間復雜度。

#coding:utf-8
import math
import datetime
import numpy as np

x = np.array([i* 0.001 for i in range(3000000)])


def sin_math(x):
    beg = datetime.datetime.now()
    for i,t in enumerate(x):  # 返回下標和元素。
        x[i] = math.sin(t)
    end = datetime.datetime.now()
    return end-beg

def sin_np(x):
    beg = datetime.datetime.now()
    np.sin(x,x)
    end = datetime.datetime.now()
    return end-beg
    
print(sin_math(x),sin_np(x))

最后得到的結構大概 np.sin()的速度比math.sin()快5倍。這得益於np.sin()的C語言級別的計算。

  

  實際上標准的Python中可以用列表推導式的方法得到比for循環更快的計算效果。x = [math.sin(t) for t in x] ,但是列表推導式將產生一個新的列表,而不是修改原來的列表,相當於用 空間換了時間。

四則運算

  NumPy提供了許多的ufunc函數,例如計算兩個數組之和的add()函數。

>>> a+b
array([ 5,  7,  9, 11])
>>> a = np.arange(0,4)
>>> b = np.arange(5,9)
>>> a+b
array([ 5,  7,  9, 11])
>>> np.add(a,b)
array([ 5,  7,  9, 11])

 

  add()將返回一個數組,這個同樣可以指定out參數,如果沒有指定則創建一個新的數組來保存計算結果。

>>> np.add(a,b,a)
array([ 5,  7,  9, 11])
>>> a
array([ 5,  7,  9, 11])

 

  NumPy位數組定義了各種數學運算操作符,因此計算兩個數組想家可以簡單的寫成a+b而,而np.add(a,b,a)則可以用a += b來表示。下標列出了數組的運算符,以及與之對應的ufunc

  數組對象支持操作符,極大地簡化了算式的編寫,但是需要注意,如果算式很復雜,要產生大量的中間結果這樣就會降低程序的運算速度,例如堆a,b,c,三個數組采用算法進行 x = a * b + c 它相當於

t = a * b
x = t + c
del t

 

   我們可以通過分解算式來將上述的一句話,變成兩句話,且減少一次內存分配。

x = a * b
x += c

 

   使用比較運算符堆兩個數組進行運算的時候,將返回一個Bool 數組, 他的每個元素值都是兩個數組對應元素的比較結果 eg:

>>> np.array([1,2,3,4]) < np.array([4,3,2,1])
array([ True,  True, False, False], dtype=bool)

 

 每個比較運算符都有一個ufunc 函數對應, 表2-2是比較運算符與ufunc 函數的對照表。

  因為Python中的布爾運算使用,and,or和not等關鍵字,他們無法被重載,因此數組的布爾運算只能通過相應的ufunc函數進行,這些函數都以logical_開頭,可以通過自動補齊來找到相應函數。

  

>>> a = np.arange(5)
>>> a
array([0, 1, 2, 3, 4])
>>> b = np.arange(4,-1,-1)
>>> b
array([4, 3, 2, 1, 0])
>>> print (a == b)
[False False  True False False]
>>> print(a>b)
[False False False  True  True]
>>> print(np.logical_and(a,b))
[False  True  True  True False]
>>> print(a == b,a>b)
[False False  True False False] [False False False  True  True]

 

自定義ufunc函數  

  通過NumPy提供的標准ufunc函數,可以組合出復雜的表達式,在C語言級別堆數組的每個元素進行計算。但是這種表達式不易編寫,而對元素進行計算的程序卻很容易用Python實現,這時候可以通過frompyfunc()將計算單個元素的函數轉換成ufunc函數,這樣就可以很方便的用所產生的ufunc函數對數組進行計算了。

  例如我們可以通過一個分段函數描述三角波,三角波的形狀如圖所示,它分為三段:上升段,下降段和平坦段。

  根據圖2-5,我們可以很容易的寫出計算三角波上某點的Y坐標函數,顯然triangle_wave()只能計算單個數值,不能直接堆數組進行處理。

  

import numpy as np
import matplotlib.pyplot as plt 

def triangle_wave(x,c,c0,hc):
    x = x - int(x) #三角波的周期位1,因此只取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,1000)
y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x])

plt.plot(x,y1)
plt.show()

  通過frompyfunc()可以將計算單個值的函數轉換為能對數組的每個元素進行計算的ufunc函數。frompyfunc的調用格式為:

frompyfunc(func,nin,nout)

  其中func是計算單個元素的函數,nin是func的輸入參數的個數,nout是func的返回值的個數。下面的程序使用frompyfunc()將triangle_wave()轉換為ufunc函數對象triangle_ufunc1:

# -*- coding: utf-8 -*-
"""
Created on Tue Mar 14 13:24:50 2017

@author: x-power
"""
import numpy as np
import matplotlib.pyplot as plt 

def triangle_wave(x,c,c0,hc):
    x = x - int(x) #三角波的周期位1,因此只取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,1000)
y1 = np.array([triangle_wave(t,0.6,0.4,1.0) for t in x])

triangle_ufunc = np.frompyfunc(triangle_wave,4,1)
y2 = triangle_ufunc(x,0.6,0.3,1)

print("y1的類型是:" + str(y1.dtype))

print("y2的類型是:" + str(y2.dtype))

y2 = y2.astype(np.float)

print("轉換之后的y2類型是:" + str(y2.dtype))

print("如果使用np.frompyfunc的話,返回值的類型是object很尷尬,如果需要方便的使用的話,需要轉換類型。")

#plt.plot(x,y1)
#plt.plot(x,y2)
#
#plt.show()

廣播

  當使用ufunc函數堆兩個數組進行計算的時候,ufunc函數會對這兩個數組的對應元素進行計算,因此其要求兩個數組的shape相同,不同的話會進行廣播處理( broadcasting )處理:

    1)讓所有的輸入數組都向其中維數最多的數組看齊,shape屬性中不足的部分都通過向前面加1 的當時補齊。

    2)輸出的數組的shape屬性是輸入數組的shape屬性的各個軸上的最大值。

    3)如果輸入數組的某個軸長度為 1 時,沿着此軸運算時都用此軸上的第一組值。

  talk is cheap , show me your code !

import numpy as np
import matplotlib.pyplot as plt 

a = np.arange(0,60,10).reshape(-1,1)
b = np.arange(0,5)

c = a + b

print("a的形狀是:" + str(a.shape))
print(a)

print("b的形狀是:" + str(b.shape))
print(a)

print("c的形狀是:" + str(c.shape))
print(c)
a的形狀是:(6, 1)
[[ 0]
 [10]
 [20]
 [30]
 [40]
 [50]]
b的形狀是:(5,)
[[ 0]
 [10]
 [20]
 [30]
 [40]
 [50]]
c的形狀是:(6, 5)
[[ 0  1  2  3  4]
 [10 11 12 13 14]
 [20 21 22 23 24]
 [30 31 32 33 34]
 [40 41 42 43 44]
 [50 51 52 53 54]]

  由於a和b的維數不同,根據規則1),需要讓b的shape屬性向a 對齊,於是在b的 shape屬性前 加 1,補齊位(1,5).相當於做了如下計算:

b.shape = 1,5

 

  這樣加法運算的兩個輸入數組的shape屬性分別為(6,1)和(1,5),根據規則2)輸出數組的各個軸的長度位輸入數組的各個軸長度的最大值,可知輸出數組的shape屬性為(6,5)。

  由於b的第0軸長度為1,而a的第0軸長度為6,為了讓他們在第0軸上能夠相加,需要將b的第0軸的長度擴展為6,這相當於:

 

  

b
Out[16]: array([[0, 1, 2, 3, 4]])

b = b.repeat(6,0)

b
Out[18]: 
array([[0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4],
       [0, 1, 2, 3, 4]])

 

  這里的repeat()方法沿着axis參數指定的軸復制數組中的各個元素的值。由於a的第 1 軸的長度為 1 而b的第1軸的長度為5,為了能夠讓他們相加,需要將a的第一軸的長度擴展為5 這相當於:

a
Out[19]: 
array([[ 0],
       [10],
       [20],
       [30],
       [40],
       [50]])

a = a.repeat(5,1)

a
Out[21]: 
array([[ 0,  0,  0,  0,  0],
       [10, 10, 10, 10, 10],
       [20, 20, 20, 20, 20],
       [30, 30, 30, 30, 30],
       [40, 40, 40, 40, 40],
       [50, 50, 50, 50, 50]])

 

  經過上述處理就可以讓 a 和 b進行相加計算了。可是這樣不是很浪費內存么?由於這種廣播計算使用頻率比較高,所以一定是要有解決辦法的。因此NumPy提供了ogrid對象,用於創建廣播運算用的數組。

x,y = np.ogrid[:5,:5]
print(x)
print(y)

 

[[0]
 [1]
 [2]
 [3]
 [4]]
[[0 1 2 3 4]]

 

  此外,NumPy還提供了mgrid對象,它的用法和ogrid對象類似,但是它所返回的是進行廣播之后的數組:

x,y = np.mgrid[:5,:5]
print(x)
print(y)

 

[[0 0 0 0 0]
 [1 1 1 1 1]
 [2 2 2 2 2]
 [3 3 3 3 3]
 [4 4 4 4 4]]
[[0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]
 [0 1 2 3 4]]

 

  ogrid是一個很有趣的對象,它像是多維數組一樣,用切片元祖作為下標,返回的是一組可以用來廣播計算的數組,其切片下標有兩種形式:

  •   開始值:結束值:步長,和np.arange(開始,結束,步長)類似
  •        開始值:結束值:長度j,當第三個參數為虛數的時候,它表示所返回數組的長度和np.linspace(開始值,結束值,長度)類似。
x = np.arange(0,1,0.1)
y = np.linspace(0,1,10)
print(x)
print(y)

 

x,y = np.ogrid[:1:4j,:1:3j]
print(x)
print(y)

 

x,y = np.ogrid[-2:2:20j,-2:2:20j]
z = x + np.exp(-x**2 - y**2)

 

下圖則位使用ogrid()函數,計算二元函數在等間隔網格上的值,下面是繪制三維曲面(x,y)= xex^2-y^2的圖形。

  為了充分利用ufunc的廣播功能,我們經常需要調整數組的形狀,因此數組支持特殊的下標對象None,它表示在None對應的位置創建一個長度為1的新軸。例如堆一維數組a,a[None,:]和a.reshape(1,-1)等效,而a[:,None]和a.shape(-1,1)等效:

a = np.arange(4)
print(a[:,None])
print("\n\n\n")
print(a[None,:])

 

 

runfile('/home/x-power/untitled0.py', wdir='/home/x-power')
[[0]
 [1]
 [2]
 [3]]




[[0 1 2 3]]

 

  下面的例子以None作為下標,實現廣播運算。

x = np.array([0,1,4,10])
y = np.array([2,3,8])
print(x[None,:])
print(y[:,None])
print(x[None,:]+y[:,None])

 

[[ 0  1  4 10]]
[[2]
 [3]
 [8]]
[[ 2  3  6 12]
 [ 3  4  7 13]
 [ 8  9 12 18]]

 

  真正的英雄都是在最后力挽狂瀾的,這句話說的真他么的對,狗日的。  

x = np.array([0,1,4,10])
y = np.array([2,3,8])
gy,gx = np.ix_(y,x)
print(gy + gx)

 

[[ 2  3  6 12]
 [ 3  4  7 13]
 [ 8  9 12 18]]

 

  在上面的例子中,通過ix_()將數組x和y轉換成能進行廣播運算的二維數組,注意數組y對應廣播運算結果中的0軸,而數組x與第1軸相對應。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM