NumPy入門 之 數組初探: 圖解view和copy (中)

原文鏈接:http://mp.weixin.qq.com/s?__biz=MzIyNDM4ODI0OA==&mid=2247484038&idx=1&sn=0afbb4609ae35977613ce45b76b62b00&chksm=e80ef771df797e677f5b1be61ad9f309411200d9f0899cf718c50d150edb6fec22007caac04c&scene=21#wechat_redirect
導讀 :在上一篇《NumPy入門 之 數組初探: 圖解view和copy (上)》中,我們知道view可以化身不同的shape去看待base數組的數據。實際上還允許以不同數據類型來解讀數據,這就是本篇的主題。提示: 如果代碼框中的代碼沒有全部顯示,只需用你的手指頭輕輕一劃。

在這裏插入圖片描述

我們知道,程序處理的數據就是躺在內存裏的一串01表示的二進制數。人們以字節(8個bit)作爲基本單元來組織這些二進制數。比如拿連續的四個字節,把它們組裝成一個32bit的二進制碼,可以把它看成帶符號的32bit整數類型,也可以把它看成帶符號的單精度浮點數類型,分別對應C語言中的int和float類型。
在《Numpy入門 之 數組初探 1: shape和stride》這篇裏,我們已經知道NumPy支持16 bit和8 bit的整數類型。爲了簡化問題,接下來我們就使用它們來做實驗。
import numpy as np
a = np.arange(5, dtype ='int16') 
'數組a:', a 
('數組a:', array([0, 1, 2, 3, 4], dtype=int16))
我們來看看數組a在內存中的樣子。前方高能,二進制方隊正邁着整齊的步伐向我們走來,
np.vectorize(np.binary_repr)(a, width=16)
array(['0000000000000000', '0000000000000001', '0000000000000010',
       '0000000000000011', '0000000000000100'], dtype='<U16')
v = a.view('int8') 
('v眼中的數組a:', v) 
('v眼中的數組a:', array([0, 0, 1, 0, 2, 0, 3, 0, 4, 0], dtype=int8))
np.vectorize(np.binary_repr)(v, width=8)
array(['00000000', '00000000', '00000001', '00000000', '00000010',
       '00000000', '00000011', '00000000', '00000100', '00000000'],
      dtype='<U8')

在這裏插入圖片描述

爲了看得更清楚一點,我們把二進制方隊也放到圖裏來,
a = np.arange(5, dtype ='int16') 
bin_16 = np.vectorize(np.binary_repr)(a, width=16)

v = a.view('int8') 
bin_8 = np.vectorize(np.binary_repr)(v, width=8)

在這裏插入圖片描述

看上圖,我們知道數組a裏面的一個16bit的整數在v看來變成了兩個整數。但是細心的你一定發現了,16bit被拆成兩個8bit時,順序好像反過來了。看上圖中的紅色框框裏的1被拆成了下面藍色框框裏的1和0,爲什麼不是0和1呢。
爲了解釋上面的二進制方隊,我們需要一個概念,即cpu的大小端模式。
  • 大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中。
  • 小端模式:是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中。
從上面的圖來看,這裏應該是小端模式,下面我們來驗證一下。
import sys
sys.byteorder
'little'
到這裏,應該好理解了,int16數組中的元素1(二進制0000000000000001)的兩個字節被按地址從低到高存放,低字節00000001放在內存的低地址,高字節00000000放在內存的高地址。因此,數組a中的元素1,在v眼中變成了1和0。數組a其他的4個元素也是一樣的處理。
我們知道,v不僅能以不同的角度來看待數組a的數據,它甚至還有權限修改它。我們繼續測試驗證一下。
v += 1
('v眼中的數組a每個元素加1以後:', v) 
('v眼中的數組a每個元素加1以後:', array([1, 1, 2, 1, 3, 1, 4, 1, 5, 1], dtype=int8))
('此時的數組a:', a) 
('此時的數組a:', array([257, 258, 259, 260, 261], dtype=int16))

在這裏插入圖片描述

v把它眼中看到的每一個數都加上1,結果導致a中的數發生了大變。爲了看得更清晰一些,我們再次輸出每個數的二進制。
a = np.arange(5, dtype ='int16') 

v = a.view('int8') 
v += 1

bin_8 = np.vectorize(np.binary_repr)(v, width=8)
bin_16 = np.vectorize(np.binary_repr)(a, width=16)

在這裏插入圖片描述

觀察上圖,我們可以看到,變量bin_16對應數組a的二進制,藍色框框裏的int16數字正是來自紅色框框裏的兩個int8數字交換順序後組裝起來的。二進制0000000100000011翻譯成10進制正是259,看看是不是等於數組a此時的第3個數?
上篇裏,我們講到,三觀包括shape、strides和dtype。上篇裏改變的是shape和strides,而本篇裏上面是僅僅改變dtype。下面我們來看看三者全部被改變後的效果會如何?
a = np.arange(5, dtype ='int16') 
  
v = a.view('int8') 
v.shape = (5, 2)

v_base  = v.base
v_dtype = v.dtype
v_shape = v.shape
v_strides = v.strides

在這裏插入圖片描述

再來看看此時v的三觀,即dtype, shape, strides,

在這裏插入圖片描述

# 最後再看看v加1以後的結果
v += 1 

在這裏插入圖片描述

再次輸出二進制,
bin_8 = np.vectorize(np.binary_repr)(v, width=8)
bin_16 = np.vectorize(np.binary_repr)(a, width=16)

在這裏插入圖片描述

相信大家能看明白上面的圖。
copy: 這一集的戲份都給了view嗎,我沒有存在感,寶寶不開森啊。好,那我們也爲copy插入一段戲吧,我們來看看copy和view不同順序的組合會有什麼效果?
# 先定義個函數,用於獲取數組x的數據的內存地址,以16進制顯示
def data_pointer(x):
    return hex(x.__array_interface__['data'][0])
a = np.arange(5, dtype ='int16') 

b = a.copy().reshape(5,1)
c = a.reshape(5,1).copy()
# 順便看看 = 
d = a
然後來看看a,b,b的base、c以及d,它們的數據分別放在內存哪裏呢?
data_pointer(a), data_pointer(b), data_pointer(b.base), data_pointer(c), data_pointer(d)
('0x56318c4023c0',
 '0x56318c3bf180',
 '0x56318c3bf180',
 '0x56318c2ccac0',
 '0x56318c4023c0')
可以看到,a、b、c的數據地址各不相同,顯然b的數據不是自己的,上面顯示的是它base的數據地址,而d顯然跟a一樣,相當於它的別名。
# 確認一把
d is a
True
再來看看view的view,
e = a.reshape(1,5)
f = e.reshape(5,)
f.base is e, f.base is a
(False, True)

在這裏插入圖片描述

好啦,今天就到這裏了。至於copy,其實還有內容,那就是關於它的參數order,
  • numpy.copy(a, order=‘K’) or
  • ndarray.copy(order=‘C’)
但這個問題不屬於本篇議題,後面再講。

在這裏插入圖片描述

小結一下,view相當於是它base數組的化身(宗教用語,通常指神或精靈等超自然力量,以人類或動物的形態出現在世間),沒有數據的所有權,但能超視距感應和使用base的數據,而且對數據有自己的解讀方式。通過創建不同的view可以用不同方式來解讀和處理base數組的數據。而copy方法會對數組及其數據進行完全拷貝,結果和原數組是兩個獨立的對象,兩者的數據存放在內存中的不同地址。
本文獲得原文作者授權,
轉載自公衆號“機器學習與數學”。

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章