print( “Hello,NumPy!” )
print( “Hello,NumPy!” )
學習痛苦啊,今天學,明天丟。這種天氣,還是睡覺最舒服了。
咱說歸說,鬧歸鬧,但還是得學才行啊。
之前在學習的過程中一直都有記錄筆記的習慣,但筆記品質可不敢恭維,大多都未曾整理,不過拿來複習倒是個不錯的選擇。
自打接觸Python以來,寫的最多的就是爬蟲了,什麼網路小說啊,虛擬遊戲幣啊,考試題庫啊之類的都有寫過,也幫別人爬過不少網站公開數據。之前也整理過一篇爬蟲相關的文章(太懶了,才一篇,之後有機會有時間,再整理出來吧):網路爬蟲之頁面花式解析
再之後的話,就對Social Engineering產生了一定的興趣,或許需要能言善辯,巧舌如簧,達到「江湖騙子」的等級,才能玩轉社會工程學吧。之前也寫過一篇Web滲透相關的文章:Taoye滲透到一家黑平台總部,背後的真相細思極恐。
這裡還是有必要提醒一下大家,對於一些不信任的人或資訊,如果你會一些網路安全相關技能,可以當做一次滲透經驗,否則的話,最好的處理方式是置之不理,別讓你的好奇心成為墜入深淵的開始,尤其是在這雲龍混雜的虛擬網路世界中。就在前幾天,警方還破獲了全國最大網路 luo liao 敲詐案呢,受害者達10餘萬人,涉案金額也有XXXXXXXXXXXXXXX,大家還是需要注意的。
反正,學的東西很多、很雜,學的也不精,記錄的筆記也很少回過頭複習。工欲善其事,必先利其器,這不開始系統性學習機器學習么,所以想把之前記錄的Numpy、Pandas、Matplotlib「三劍客」筆記重新整理一下,也算是做一個回顧。
後期的話,會學習一些機器學習演算法,主要參考《機器學習實戰 / Machine Learning in Action》和周志華老師的《機器學習》西瓜書,以及其他一些圈內大佬寫的一些技術文章。能手撕的話盡量手撕,不能手撕只能說明自己還有待提高吧。
Flag立的太多,感覺會被啪啪打臉。沒事,慢慢來吧,打臉也不怕,反正皮糙肉厚 ( ̄_, ̄ )
這篇文章先把NumPy整理出來吧,可能記錄的並不全面,只記錄了一些常用的,其他的話後期用到了再進行更新吧。以下內容主要參考菜鳥教程和NumPy官方文檔:
- NumPy菜鳥教程://www.runoob.com/numpy/numpy-tutorial.html
- NumPy官方文檔://numpy.org/doc/stable/user/quickstart.html
關於NumPy的安裝,前面在介紹深度學習環境搭建的時候已經介紹過了,推薦安裝Anaconda,其內部集成了大量第三方工具模組,而不需要手動 pip install ...
,這一點就有點像Java中的Maven。Anaconda可參考:基於Ubuntu+Python+Tensorflow+Jupyter notebook搭建深度學習環境
如果您沒有安裝Anaconda那也沒事,只需要在Python環境下執行以下命令安裝NumPy即可:
> pip3 install numpy -i //pypi.tuna.tsinghua.edu.cn/simple
以下內容採用的NumPy的版本為:1.18.1
In [1]: import numpy as np
In [2]: np.__version__
Out[2]: '1.18.1'
在NumPy中,操作的對象大多為ndarray類型,也可稱其別名為array,我們可以把它看做矩陣或向量。
創建np.ndarray對象有多種方式,NumPy中也有多個api可供調用,比如我們可以通過如下方式創建一個指定的ndarray對象:
In [7]: temp_array = np.array([[1, 2, 3], [4, 5, 6]], dtype = np.int32)
In [8]: temp_array
Out[8]:
array([[1, 2, 3],
[4, 5, 6]])
In [9]: type(temp_array)
Out[9]: numpy.ndarray # 輸出的類型為ndarray
當然了,也可以調用arange
,然後對其進行reshape
操作來改變其形狀,將向量轉換成2×3的矩陣形式,此時的對象類型依然是numpy.ndarray
:
In [14]: temp_array = np.arange(1, 7).reshape(2, 3) # arange產生向量,reshape改變形狀,轉化成矩陣
In [15]: temp_array
Out[15]:
array([[1, 2, 3],
[4, 5, 6]])
In [16]: type(temp_array) # 輸出的類型依然是ndarray
Out[16]: numpy.ndarray
由上,我們可以發現,無論通過什麼方式(其他方式後期會有介紹)來創建對象,NumPy操作的都是ndarray類型,且該類型對象中主要包含以下屬性:
- ndarray.ndim:表示ndarray的軸數,也可理解成維度,或者可以通俗的理解成外層中括弧的數量。比如[1, 2, 3]的ndim就是1,[[1], [2], [3]]的ndim等於2,[[[1]], [[2]], [[3]]]的ndim等於3(注意觀察外層中括弧的數量)
- ndarray.shape:表示ndarray的形狀,輸出的是一個元組。該ndarray有n行m列,那輸出的就是(n, m),比如[[1], [2], [3]]輸出的是(3, 1),[[[1]], [[2]], [[3]]]輸出的是(3, 1, 1),[[[1, 2]], [[3, 4]]]輸出的是(2, 1, 2)。通過上述3個例子,可以發現shape是按照從外至內的順序來表示的
- ndarray.size:這個比較容易理解,表示的就是ndarray內部元素的總個數,也就是shape的乘積
- ndarray.dtype:表示ndarray內部元素的數據類型,常見的有
numpy.int32、numpy.int64、numpy.float32、numpy.float64
等
以上就是ndarray中常見的一些屬性,注意:只是部分,並非全部,其他屬性可參考官方文檔
我們可以通過以下列子來觀察下ndarray的各個屬性,以及其內部的屬性應該如何修改:
以上例子中涉及到的np.expand_dims
和np.astype
會在後面進行介紹。
np.zeros可以創建一個元素全0的ndarray,np.ones則可以創建一個元素全1的ndarray。創建的時候可以指定ndarray的shape形狀,也可以通過dtype屬性指定內部元素的數據類型:
In [70]: np.zeros([2,3,2], dtype=np.float32)
Out[70]:
array([[[0., 0.],
[0., 0.],
[0., 0.]],
[[0., 0.],
[0., 0.],
[0., 0.]]], dtype=float32)
In [71]: np.ones([3,2,2], dtype=np.float32)
Out[71]:
array([[[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.]],
[[1., 1.],
[1., 1.]]], dtype=float32)
另外,在Tensorflow中可以通過tf.fill產生指定元素的指定shape張量,如下產生2×3的張量,且內部元素為100:
In [76]: import tensorflow as tf
In [77]: tf.fill([2,3], 100)
Out[77]:
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[100, 100, 100],
[100, 100, 100]])>
而在NumPy中,也有fill介面,只不過只能通過已有的ndarray才能調用fill,而無法直接np.fill進行調用:
In [79]: data = np.zeros([2, 3])
In [80]: data.fill(100)
In [81]: data
Out[81]:
array([[100., 100., 100.],
[100., 100., 100.]])
np.arange與常用的range作用類似,用於產生一個固定區間連續的ndarray,注意取左不取右,且數組之間成一個等差數列,公差可自行定義(可為小數),如下:
In [85]: np.arange(10)
Out[85]: array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])
In [86]: np.arange(3, 10, 2)
Out[86]: array([3, 5, 7, 9])
In [87]: np.arange(3, 10, 0.7)
Out[87]: array([3. , 3.7, 4.4, 5.1, 5.8, 6.5, 7.2, 7.9, 8.6, 9.3])
numpy.linspace 函數用於創建一個一維數組,數組是一個等差數列構成的,可以指定元素內部元素的個數以及是否包含stop值。如下,在區間1-5中創建一個元素數目為10的等差數列:
In [89]: np.linspace(1, 5, 10) # 默認包含stop
Out[89]:
array([1. , 1.44444444, 1.88888889, 2.33333333, 2.77777778,
3.22222222, 3.66666667, 4.11111111, 4.55555556, 5. ])
In [90]: np.linspace(1, 5, 10, endpoint = False) # endpoint屬性可以設置不包含stop
Out[90]: array([1. , 1.4, 1.8, 2.2, 2.6, 3. , 3.4, 3.8, 4.2, 4.6])
np.random.random和np.random.rand隨機從0-1中生成對應shape的ndarray對象:
In [4]: np.random.random([3, 2])
Out[4]:
array([[0.68755531, 0.56727707],
[0.86027161, 0.01362836],
[0.56557302, 0.94283249]])
In [5]: np.random.rand(2, 3)
Out[5]:
array([[0.19894754, 0.8568503 , 0.35165264],
[0.75464769, 0.29596171, 0.88393648]])
np.random.randint隨機生成指定範圍的ndarray,且內部元素為int類型:
In [6]: np.random.randint(0, 10, [2, 3])
Out[6]:
array([[0, 6, 9],
[5, 9, 1]])
np.random.randn返回滿足標準正態分布的ndarray(均值為0,方差為1):
In [7]: np.random.randn(2,3)
Out[7]:
array([[ 2.46765106, -1.50832149, 0.62060066],
[-1.04513254, -0.79800882, 1.98508459]])
另外,我們在NumPy中使用random的時候,都是隨機產生的一組數據,而要想每次產生的數據相同,則需要通過np.random.seed來進行設置:
In [33]: np.random.seed(100)
In [34]: np.random.randn(2, 3)
Out[34]:
array([[-1.74976547, 0.3426804 , 1.1530358 ],
[-0.25243604, 0.98132079, 0.51421884]])
In [35]: np.random.seed(100)
In [36]: np.random.randn(2, 3)
Out[36]:
array([[-1.74976547, 0.3426804 , 1.1530358 ],
[-0.25243604, 0.98132079, 0.51421884]])
在NumPy中的一維ndarray里,就如同列表一樣操作,可以對其進行切片和遍歷等操作:
In [5]: a
Out[5]: array([1., 2., 3., 4., 5., 6., 7., 8., 9.])
In [6]: a[2], a[2:5]
Out[6]: (3.0, array([3., 4., 5.]))
In [7]: a * 3, a ** 3 # 立方
Out[7]:
(array([ 3., 6., 9., 12., 15., 18., 21., 24., 27.]),
array([ 1., 8., 27., 64., 125., 216., 343., 512., 729.]))
In [13]: for i in a:
...: print(i, end=", ")
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0,
假如我們的ndarray並非是一維數組,而是二維矩陣,或是更高維度的ndarray,則我們需要對其進行多維度的切分。且當我們對高維度的ndarray進行遍歷的時候,則遍歷出單個結果的維度比元維度少一,比如2維矩陣遍歷得到的結果為1維向量,三維遍歷的結果為2維矩陣。
另外,還有一點需要說明的是,加入我的數據的維度相當高,為了方便我們對數據進行索引,NumPy為我們提供了...
的方式來進行切分,具體例子如下:
In [14]: a = np.random.randint(0, 2, 6).reshape(2, 3)
In [15]: a
Out[15]:
array([[1, 1, 1],
[1, 0, 1]])
In [16]: a[:, :2]
Out[16]:
array([[1, 1],
[1, 0]])
In [17]: for i in a: # 對矩陣進行遍歷,則輸出的是行向量,單個輸出的維度比原維度少1
...: print(i, end=", ")
[1 1 1], [1 0 1],
In [19]: data = np.random.randint(0, 2, [2, 2, 3])
In [20]: data
Out[20]:
array([[[0, 0, 1],
[0, 1, 1]],
[[1, 0, 0],
[0, 1, 0]]])
In [21]: data[..., :2] # ...則表示前兩個維度全要,相當於 data[:, :, :2]
Out[21]:
array([[[0, 0],
[0, 1]],
[[1, 0],
[0, 1]]])
shape操作:
- a.ravel()、ndarray.flatten(),將ndarray進行拉伸操作(拉直成向量形式)
- a.reshape(),重新改變a的shape外形
- a.T、a.transpose(),返回a的倒置矩陣
以上幾個操作返回的都是新的結果,而不改變原來的ndarray(a)。且上述操作默認的都是橫向操作,如果需要縱向,則需要控制order參數,具體操作可參考菜鳥教程。除了reshape之外,還有resize,只不過resize會改變a的結果,而並非產生一個新的結果:
還有一個小技巧需要掌握的是,在進行reshape的時候,假如傳入-1,則會自動計算出對應的結果。比如一個 2×3的矩陣a,我們進行a.reshape(3, -1),則這裡的-1代表的就是2,當我們數據量大的時候,這個用起來還是挺方便的:
In [44]: a
Out[44]:
array([[1, 1, 1],
[1, 0, 1]])
In [45]: a.reshape([3, -1])
Out[45]:
array([[1, 1],
[1, 1],
[0, 1]])
數組維度的修改:
維度 | 描述 |
---|---|
broadcast_to | 將數組廣播到新形狀 |
expand_dims | 擴展數組的形狀 |
squeeze | 從數組的形狀中刪除一維條目 |
具體操作如下:
數組的連接:
函數 | 描述 |
---|---|
concatenate | 連接沿現有軸的數組序列 |
hstack | 水平堆疊序列中的數組(列方向) |
vstack | 豎直堆疊序列中的數組(行方向) |
下面程式碼顯示了數組連接的操作,其中concatenate可以通過控制axis的值來確定連接的方向,作用等同於hstack和vstack。還有一點需要注意的是:以下示例僅僅是連接兩個數組,其實可以同時連接多個的,比如np.concatenate((x, y, z), axis=1)
數組的切分:
函數 | 描述 |
---|---|
split | 將一個數組分割為多個子數組 |
hsplit | 將一個數組水平分割為多個子數組(按列) |
vsplit | 將一個數組垂直分割為多個子數組(按行) |
同數組的連接一樣,split可以通過控制axis屬性來得到與hsplit、vsplit相同的作用,下面只給出split的示例,關於hsplit和vsplit可參考官方文檔:
數組元素的添加和刪除:
函數 | 描述 |
---|---|
append | 將值添加到數組末尾 |
insert | 沿指定軸將值插入到指定下標之前 |
delete | 刪掉某個軸的子數組,並返回刪除後的新數組 |
廣播(Broadcast)是 numpy 對不同形狀(shape)的數組進行數值計算的方式, 對數組的算術運算通常在相應的元素上進行。
如果兩個數組 a 和 b 形狀相同,即滿足 a.shape == b.shape,那麼 a*b 的結果就是 a 與 b 數組對應位相乘。這要求維數相同,且各維度的長度相同。
In [8]: import numpy as np
In [9]: a = np.array([1,2,3,4])
...: b = np.array([10,20,30,40])
In [10]: a * b
Out[10]: array([ 10, 40, 90, 160])
當運算中的 2 個數組的形狀不同時,numpy 將自動觸發廣播機制。如:
In [11]: a = np.array([[ 0, 0, 0],
...: [10,10,10],
...: [20,20,20],
...: [30,30,30]])
...: b = np.array([1,2,3])
In [12]: a + b
Out[12]:
array([[ 1, 2, 3],
[11, 12, 13],
[21, 22, 23],
[31, 32, 33]])
下面的圖片展示了數組 b 如何通過廣播來與數組 a 兼容。
np.tile可以對目標操作數組進行廣播擴展,比如 1×3 如下操作可以廣播為 4×6,注意與前面所說的broadcast_to進行區別,broadcast_to必須要擴維,而tile可擴維,也可不擴維,具體操作根據自己的實際需求進行。
有過Tensorflow經驗的讀者應該知道,其內部也有tile和broadcast操作,但是當我們的數據量比較大的時候,據說tile的效率比broadcast要低,暫時還沒了解原因,以後有用到在了解吧。
In [20]: a
Out[20]: array([[1, 1, 0]])
In [21]: np.tile(a, [4, 2]) # 第二個參數表示各個維度廣播的倍數,這裡表示的是行擴4倍,列擴2倍
Out[21]:
array([[1, 1, 0, 1, 1, 0],
[1, 1, 0, 1, 1, 0],
[1, 1, 0, 1, 1, 0],
[1, 1, 0, 1, 1, 0]])
關於NumPy中的複製和試圖:這個部分的知識也是我之前在學習NumPy時候所遺漏的點,趁著這個機會,把它在這記錄一下。
- 沒有複製
關於這一點,其實在之前記錄LeetCode 熱題 HOT 100(01,兩數相加)演算法的時候同樣提到過,這一點還是需要格外注意的。
- 視圖或者淺拷貝(view)
同樣採用上面一樣的程式碼,僅僅修改了第57行,將 y = x 和 y = x.view(),可以發現,此時的x和y的id值不一樣,也就是說他們的記憶體地址不一樣,我們修改x的shape之後,y的shape並沒有發生改變。
但是,當我們改變不是shape,而是改一個變數組內部的數據,則另一個數組同樣會發生改變
- 副本或者深拷貝(copy)
視圖或者淺拷貝採用的是view
,而副本或者深拷貝採用的是copy
。使用copy
的時候無論是修改一個數組的shape,還是內部元素,另一個數組都不會發生改變。
(關於copy,這裡就不再演示程式碼了,讀者可自行操作,然後進行比較)
最後總結一下:
- y = x,說明x和y的記憶體地址相同,修改其中一個,另一個也會隨著發生改變(無論是shape,還是內部元素)
- y = x.view(),兩者的記憶體地址不一樣,修改其中一個的shape,另一個不會發生改變;而修改其中一個元組的內部元素,則另外一個會跟著發生改變
- y = x.copy(),兩者的記憶體地址不一樣,無論是修改一個元組的shape,還是內部元素,另外一個都不會發生改變,兩者相互獨立
NumPy中的數學相關函數,這部分內容沒什麼好講的:
- np.pi,返回π值
- np.sin(),返回正弦值
- np.cos(),返回餘弦值
- np.tan(),返回正切值
- numpy.ceil(),返回大於或者等於指定表達式的最小整數,即向上取整。
- np.exp(2),返回指數值,也就是 $e^2$
其他相關數學函數,可參考官方文檔。
NumPy中的算術運算,這部分內容也沒什麼好講的:
- numpy.add(a,b):兩數組相加
- numpy.subtract(a,b):兩數組相減
- numpy.multiply(a,b):兩數組相乘
- numpy.divide(a,b):兩數組相除
- numpy.reciprocal(a),返回倒數
- numpy.power(a, 4),返回a的四次方
NumPy中的統計函數,這個稍微記錄一下:
以上示例了在數組中獲取最值以及最值之差的方式,傳入了axis參數,則按照對應方向獲取,假如沒有傳入axis參數,則表示獲取數組整體的最值。除了以上幾個介面之外,還有其他一些常見的統計函數,具體操作和上述並無一二,如下:
- np.amin():獲取最小值
- np.amax():獲取最大值
- np.ptp():獲取最值之差
- np.median():獲取中位數(中值)
- np.mean():獲取均值
- np.var():獲取方差,$\sigma^2 = \frac{1}{n}\sum_{i=1}n(x_i-\overline{x})2$
- np.std():獲取標準差,$\sigma$
NumPy中的線性代數:
- np.dot(a, b) 就是兩個矩陣的乘積
- np.vdot(a, b) 兩個矩陣對應位置數的乘積之和
- np.inner(a, b) 內積,就是a的每行與b的每行相乘求和
比如a=[[1, 0], [1, 1]],b=[[1, 2], [1, 3]]
np.inner(a, b)相當於[1, 0] * [1, 2] = 1 -> 為第一個數
[1, 0] * [1, 3] = 1 -> 為第二個數
[1, 1] * [1, 2] = 3 -> 為第三個數
[1, 1] * [1, 3] = 4 -> 為第四個數
矩陣乘積就是第一個矩陣的行與第二個矩陣的列乘積之和,而inner相當於第一個矩陣和第二個矩陣的行乘積之和
- np.matmul(a, b) 目前感覺和np.dot(a, b)作用一樣,都是矩陣乘積
- np.linalg.det(a) 計算矩陣的行列式的值
- np.linalg.solve(a, [[1], [1]]) 求線性方程組的解,第一個的參數相當於係數,第二個參數相當於參數項
- np.linalg.inv(a) 計算矩陣的逆矩陣
# 將數組保存到以 .npy 為擴展名的文件中。
numpy.save(file, arr, allow_pickle=True, fix_imports=True)
- file:要保存的文件,擴展名為 .npy,如果文件路徑末尾沒有擴展名 .npy,該擴展名會被自動加上。
- arr: 要保存的數組
- allow_pickle: 可選,布爾值,允許使用 Python pickles 保存對象數組,Python 中的 pickle 用於在保存到磁碟文件或從磁碟文件讀取之前,對對象進行序列化和反序列化。
- fix_imports: 可選,為了方便 Pyhton2 中讀取 Python3 保存的數據。
In [81]: a = np.random.randint(1, 10, [3, 4])
In [82]: np.save("a.npy", a)
In [83]: np.load("a.npy")
Out[83]:
array([[2, 7, 3, 1],
[4, 6, 4, 3],
[2, 2, 9, 5]])
參考資料:
參考資料:
[1] NumPy菜鳥教程://www.runoob.com/numpy/numpy-tutorial.html
[2] NumPy官方文檔://numpy.org/doc/stable/user/quickstart.html
時間不太夠,後面寫的有點倉促了,但應該並不影響正常閱讀和以後的複習,暫時就這樣吧。
注意:只記錄了一些常用的,其他的話後期用到了再進行更新吧,其他內容可自行參考文檔。
本來是打算按照《機器學習實戰 / Machine Learning in Action》這本書來手撕其中程式碼的,但由於實際原因,可能需要先手撕SVM了,這個演算法感覺還是挺讓人頭疼,其中內部太複雜了,也很少有資料將其完整的推導出來,也涉及到了許多陌聲的名詞,如:非線性約束條件下的最優化、KKT條件、拉格朗日對偶、最大間隔、最優下界、核函數等等,天書或許、可能、大概就是這樣的吧。好在之前有學習過SVM,但想必依然需要花費大力氣來手撕,也需要參考不少資料,包括但不局限於《機器學習實戰 / Machine Learning in Action》、《機器學習》、《統計學習方法》。
所以,下期的話,應該會開始手撕SVM,至於最後能不能手撕成功,還真不好說。時間可能需要不少,這期間的LeetCode HOT 100 還需要正常刷起來。
我是Taoye,愛專研,愛分享,熱衷於各種技術,學習之餘喜歡下象棋、聽音樂、聊動漫,希望藉此一畝三分地記錄自己的成長過程以及生活點滴,也希望能結實更多志同道合的圈內朋友,更多內容歡迎來訪微信公主號:玩世不恭的Coder。
推薦閱讀:
幹啥啥不行,吃飯第一名
Taoye滲透到一家黑平台總部,背後的真相細思極恐
《大話資料庫》-SQL語句執行時,底層究竟做了什麼小動作?
那些年,我們玩過的Git,真香
基於Ubuntu+Python+Tensorflow+Jupyter notebook搭建深度學習環境
網路爬蟲之頁面花式解析
手握手帶你了解Docker容器技術
一文詳解Hexo+Github小白建站
打開ElasticSearch、kibana、logstash的正確方式