1. 程式人生 > >深度學習框架Keras學習系列(一):線性代數基礎與numpy使用(Linear Algebra Basis and Numpy)

深度學習框架Keras學習系列(一):線性代數基礎與numpy使用(Linear Algebra Basis and Numpy)

又開一個新坑~~

因為確實很有必要好好地趁著這個熱潮來研究一下深度學習,畢竟現在深度學習因為其效果突出,熱潮保持高漲不退,上面的政策方面現在也在向人工智慧領域傾斜,但是也有無數一知半解的人跟風吹捧,於是希望藉此教程,讓自己和讀者一起藉助keras,從上到下逐漸摸清楚深度學習領域的相關知識,包括它的前世今生,它的使用方法,它的應用領域等等。

該系列教程各部分連結:

另外,請先安裝好python的jupyter notebook工具,後面的程式碼結果演示都將是基於jupyter notebook的,這樣方便大家可以跟著執行程式看效果~~

線性代數基礎

向量與矩陣

重點在於瞭解向量與矩陣的基本概念,以及向量和矩陣的加減乘除運算規則。並且理解向量就是一種特殊的矩陣。

張量(Tensor)

簡而言之,張量是線性代數中的一種概念,它可以被理解為一個n維陣列,它的描述單位是階。我們規定,0階的張量是標量(scalar,如1,4,23這樣的數字),1階張量是向量(vector,如[1,2]),2階張量是矩陣(matrix,如[[1,2,3],[2,3,4]]),三階張量[[[1,2],[3,4]],[[5,6],[7,8]]],以此類推。所以,張量實際是對數的表示的一種推廣。

之所以要理解張量,是因為張量是tensorflow這個人工智慧框架的基本元素,而keras實際上是在tensorflow與Theano(另一個相似框架,已經被維護者宣佈不再繼續支援

)之上做了一層封裝,使得我們可以很簡單地通過keras的介面去建模,因此,當後面要了解人工智慧演算法的原理時,是繞不開對於tensorflow的理解的,也就繞不開對其核心計算元素張量的理解。(目前,我個人對為什麼tensorflow要用張量作為運算元素的一個動機理解是,張量用於描述模型的輸入特徵值時,是非常合適的)。

Numpy

為什麼用Numpy

 Numpy是python中的一款高效能科學計算和資料分析的基礎包,是Python的一種開源的數值計算擴充套件。這種工具可用來儲存和處理大型矩陣,比Python自身的巢狀列表(nested list structure)結構要高效的多(該結構也可以用來表示矩陣(matrix))。據說NumPy將Python相當於變成一種免費的更強大的MatLab系統。

其內部工具包括:包括:1、一個強大的N維陣列物件Array;2、比較成熟的(廣播)函式庫;3、用於整合C/C++和Fortran程式碼的工具包;4、實用的線性代數、傅立葉變換和隨機數生成函式。numpy和稀疏矩陣運算包scipy配合使用更加方便。

NumPy(Numeric Python)提供了許多高階的數值程式設計工具,如:矩陣資料型別、向量處理,以及精密的運算庫。專為進行嚴格的數字處理而產生。多為很多大型金融公司使用,以及核心的科學計算組織如:Lawrence Livermore,NASA用其處理一些本來使用C++,Fortran或Matlab等所做的任務。

因為深度學習的運算中會涉及大量線性代數的運算,即向量與矩陣相關的一些運算,而Numpy中封裝好了許多高效的線性代數運算方法,我們只需呼叫即可,多麼方便。

Numpy的使用

一般來說,python中約定俗成的引入numpy的方式是:

  import numpy as np

之後呼叫的時候,我們用np.方法名()就ok。

下面,我們從numpy中最常用的一些類來展開描述。

多維陣列:ndarray

ndarray是numpy中陣列的類,它是numpy最主要的一個構成要素(也被稱為陣列(array)),其本質是元素同質的多維陣列,也可說是同類元素的一張表(一般元素是數字),索引任一元素的方式是通過一個元組來進行。而在numpy中,維度被稱為“axes(軸)”,軸的數量成為“rank(階)”。

比如說,一個3d空間中的點的座標為[1,2,1]是一個階為1的陣列,因為它只有一個軸,而這個軸的長度為3. 而[[1,0,0],[0,1,2]]是一個二階(2維的)的陣列,它第一個維度(軸)的長度為2,第二個維度(軸)的長度為3.

  • ndarray的常用屬性

    • ndim:陣列的軸(維度)的數量,即階。
    • shape:陣列的維度。是一個表示陣列在每個維度上的尺寸的元組。
    • size:陣列中的元素的總數量。
    • dtype:描述陣列元素型別的一個物件。
    • itemsize:陣列元素的大小(以位元組為單位)。
    • data:一個包含陣列元素的緩衝區。
  • 舉例

>>> import numpy as np
>>> a = np.arange(15).reshape(3, 5)
>>> a
array([[ 0,  1,  2,  3,  4],
       [ 5,  6,  7,  8,  9],
       [10, 11, 12, 13, 14]])
>>> a.shape
(3, 5)
>>> a.ndim
2
>>> a.dtype.name
'int64'
>>> a.itemsize
8
>>> a.size
15
>>> type(a)
<type 'numpy.ndarray'>
>>> b = np.array([6, 7, 8])
>>> b
array([6, 7, 8])
>>> type(b)
<type 'numpy.ndarray'>
  • Array的建立方法

    1. 用python的list作為引數傳入numpy的array的建構函式中:
 >>> import numpy as np
>>> a = np.array([2,3,4])
>>> a
array([2, 3, 4])

這種方法常見的一種錯誤是錯傳成了多個數字引數,而不是list:

>>> a = np.array(1,2,3,4)    # WRONG
>>> a = np.array([1,2,3,4])  # RIGHT

而如果要建立多維的陣列,則需要傳入多層巢狀的序列(suquence),因為array方法會將序列的序列轉換成二維陣列,而將序列的序列的序列轉換為三圍陣列,以此類推。

>>> b = np.array([(1.5,2,3), (4,5,6)])
>>> b
array([[ 1.5,  2. ,  3. ],
       [ 4. ,  5. ,  6. ]])

另外,陣列元素的型別是可以在建立時明確宣告的:

>>> c = np.array( [ [1,2], [3,4] ], dtype=complex )
>>> c
array([[ 1.+0.j,  2.+0.j],
       [ 3.+0.j,  4.+0.j]])
  1. numpy的zeros(),ones(),empty()方法

在很多時候建立陣列時,我們可能並不知道陣列的具體數值,於是我們可以先用一些隨機值代替,這時,numpy內建的一些函式就可以發揮作用了。

zero()函式的預設元素是0,ones()函式的預設值是1,empty()函式的預設值是隨機化的值。它們所需傳入的引數都是一個元組,元組的元素是陣列各個軸的長度。

>>> np.zeros( (3,4) )
array([[ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.],
       [ 0.,  0.,  0.,  0.]])
>>> np.ones( (2,3,4), dtype=np.int16 )                # dtype can also be specified
array([[[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]],
       [[ 1, 1, 1, 1],
        [ 1, 1, 1, 1],
        [ 1, 1, 1, 1]]], dtype=int16)
>>> np.empty( (2,3) )                                 # uninitialized, output may vary
array([[  3.73603959e-262,   6.02658058e-154,   6.55490914e-260],
       [  5.30498948e-313,   3.14673309e-307,   1.00000000e+000]])
  1. numpy的arange()與linspace()

arange()函式類似於python內建的range()函式。分別傳入起始值,終止值還有步長,就可以返回一個從起始值按照步長生成的陣列。(如果不寫第一個起始值引數,只寫一個終止值,則預設起始值為0)

>>> np.arange( 10, 30, 5 )
array([10, 15, 20, 25])
>>> np.arange( 0, 2, 0.3 )                 # it accepts float arguments
array([ 0. ,  0.3,  0.6,  0.9,  1.2,  1.5,  1.8])

但是arange()函式的一個缺點是,它無法準確確定生成的陣列中所包含的元素的個數,因為它是自己依靠步長生成的,而linspace()函式就可以明確制定元素數量。

>>> from numpy import pi
>>> np.linspace( 0, 2, 9 )                 # 9 numbers from 0 to 2
array([ 0.  ,  0.25,  0.5 ,  0.75,  1.  ,  1.25,  1.5 ,  1.75,  2.  ])
>>> x = np.linspace( 0, 2*pi, 100 )        # useful to evaluate function at lots of points
>>> f = np.sin(x)
  • 基本操作

numpy內建的一些運算操作符可以對陣列中的元素逐個進行處理,並返回一個新的陣列物件用以儲存處理後的陣列,包括矩陣的加減法,乘方,乘數還有判斷:

>>> a = np.array( [20,30,40,50] )
>>> b = np.arange( 4 )
>>> b
array([0, 1, 2, 3])
>>> c = a-b
>>> c
array([20, 29, 38, 47])
>>> b**2
array([0, 1, 4, 9])
>>> 10*np.sin(a)
array([ 9.12945251, -9.88031624,  7.4511316 , -2.62374854])
>>> a<35
array([ True, True, False, False], dtype=bool)

numpy中的運算子是對矩陣元素逐個進行運算的(elementwise),而如果要得到矩陣之間的乘積,是需要用dot()函式來獲得的,可以是矩陣A.dot(矩陣B),也可以用np.dot(矩陣A,矩陣B)的方式,看下面的例子理解運算子和dot()函式之間的區別:

>>> A = np.array( [[1,1],
...             [0,1]] )
>>> B = np.array( [[2,0],
...             [3,4]] )
>>> A*B                         # elementwise product
array([[2, 0],
       [0, 4]])
>>> A.dot(B)                    # matrix product
array([[5, 4],
       [3, 4]])
>>> np.dot(A, B)                # another matrix product
array([[5, 4],
       [3, 4]])

還有一些一元的操作方法,比如計算所有陣列元素的和,也被ndarray類實現了:

>>> a = np.random.random((2,3))
>>> a
array([[ 0.18626021,  0.34556073,  0.39676747],
       [ 0.53881673,  0.41919451,  0.6852195 ]])
>>> a.sum()
2.5718191614547998
>>> a.min()
0.1862602113776709
>>> a.max()
0.6852195003967595

這些一元方法預設將整個陣列當成一個list中的數,而無所謂其shape。不過,我們可以通過axis軸引數來決定我們對陣列的具體某個軸進行我們的統一操作:

>>> b = np.arange(12).reshape(3,4)
>>> b
array([[ 0,  1,  2,  3],
       [ 4,  5,  6,  7],
       [ 8,  9, 10, 11]])
>>>
>>> b.sum(axis=0)                            # sum of each column
array([12, 15, 18, 21])
>>>
>>> b.min(axis=1)                            # min of each row
array([0, 4, 8])
>>>
>>> b.cumsum(axis=1)                         # cumulative sum along each row
array([[ 0,  1,  3,  6],
       [ 4,  9, 15, 22],
       [ 8, 17, 27, 38]])
  • 普遍函式(Universal Functions)

Numpy提供了一些數學函式如sin,cos,exp等,這類函式被稱為“universal functions”(ufunc),這些函式也是對陣列進行逐元素的處理(elementwise),並返回處理後的陣列。

>>> B = np.arange(3)
>>> B
array([0, 1, 2])
>>> np.exp(B)
array([ 1.        ,  2.71828183,  7.3890561 ])
>>> np.sqrt(B)
array([ 0.        ,  1.        ,  1.41421356])
>>> C = np.array([2., -1., 4.])
>>> np.add(B, C)
array([ 2.,  0.,  6.])
  • 索引,切片和迭代

一維的陣列可以很簡單的索引,切片和迭代就像python中的list和其它序列。

>>> a = np.arange(10)**3
>>> a
array([  0,   1,   8,  27,  64, 125, 216, 343, 512, 729])
>>> a[2]
8
>>> a[2:5]
array([ 8, 27, 64])
>>> a[:6:2] = -1000    # equivalent to a[0:6:2] = -1000; from start to position 6, exclusive, set every 2nd element to -1000
>>> a
array([-1000,     1, -1000,    27, -1000,   125,   216,   343,   512,   729])
>>> a[ : :-1]                                 # reversed a
array([  729,   512,   343,   216,   125, -1000,    27, -1000,     1, -1000])
>>> for i in a:
...     print(i**(1/3.))
...
nan
1.0
nan
3.0
nan
5.0
6.0
7.0
8.0
9.0

而多維的陣列可以在每個軸上由一個索引,這些索引是以一個由逗號分隔的元組表示的:

>>> def f(x,y):
...     return 10*x+y
...
>>> b = np.fromfunction(f,(5,4),dtype=int)
>>> b
array([[ 0,  1,  2,  3],
       [10, 11, 12, 13],
       [20, 21, 22, 23],
       [30, 31, 32, 33],
       [40, 41, 42, 43]])
>>> b[2,3]
23
>>> b[0:5, 1]                       # each row in the second column of b
array([ 1, 11, 21, 31, 41])
>>> b[ : ,1]                        # equivalent to the previous example
array([ 1, 11, 21, 31, 41])
>>> b[1:3, : ]                      # each column in the second and third row of b
array([[10, 11, 12, 13],
       [20, 21, 22, 23]])

而若當某提供的索引數少於陣列軸的數量時,缺失的索引會被認為是對響應的軸進行完全切片:

>>> b[-1]                                  # the last row. Equivalent to b[-1,:]
array([40, 41, 42, 43])

對多維陣列進行其第一個軸上的迭代:

>>> for row in b:
...     print(row)
...
[0 1 2 3]
[10 11 12 13]
[20 21 22 23]
[30 31 32 33]
[40 41 42 43]

而如果想對多維陣列的元素逐個進行迭代,則需要用到陣列的flat屬性(將陣列”展開平鋪“):

>>> for element in b.flat:
...     print(element)
...
0
1
2
3
10
11
12
13
20
21
22
23
30
31
32
33
40
41
42
43
  • 形狀控制(shape manipulation)

形狀(shape)即為陣列在每個軸上的長度/元素個數。

>>> a = np.floor(10*np.random.random((3,4)))
>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.shape
(3, 4)

陣列的形狀可以用各種各樣的函式進行修改,注意以下的ravel()、reshape()、.T屬性都是返回一個修改後的陣列,而不是在原陣列的基礎上進行修改:

>>> a.ravel()  # returns the array, flattened
array([ 2.,  8.,  0.,  6.,  4.,  5.,  1.,  1.,  8.,  9.,  3.,  6.])
>>> a.reshape(6,2)  # returns the array with a modified shape
array([[ 2.,  8.],
       [ 0.,  6.],
       [ 4.,  5.],
       [ 1.,  1.],
       [ 8.,  9.],
       [ 3.,  6.]])
>>> a.T  # returns the array, transposed
array([[ 2.,  4.,  8.],
       [ 8.,  5.,  9.],
       [ 0.,  1.,  3.],
       [ 6.,  1.,  6.]])
>>> a.T.shape
(4, 3)
>>> a.shape
(3, 4)

與reshape()函式形成對比的是ndarray的resize()函式,兩者的區別是,resize()函式是改變了陣列本身:

>>> a
array([[ 2.,  8.,  0.,  6.],
       [ 4.,  5.,  1.,  1.],
       [ 8.,  9.,  3.,  6.]])
>>> a.resize((2,6))
>>> a
array([[ 2.,  8.,  0.,  6.,  4.,  5.],
       [ 1.,  1.,  8.,  9.,  3.,  6.]])