1. 程式人生 > >導數與梯度、矩陣運算性質、科學計算庫numpy

導數與梯度、矩陣運算性質、科學計算庫numpy

一、實驗介紹

1.1 實驗內容

雖然在實驗一中我想盡量少的引入(會讓人放棄繼續學習的)數學概念,但我似乎還是失敗了。不過這幾乎是沒有辦法的事,要想真正學會深度學習,沒有一定的數學基礎(高等數學、線性代數、概率論、資訊理論等),(幾乎)是不可能的。學深度學習不學其中的原理你可能能夠學會搭建模型,但當模型出了問題或者無法訓練出好的結果時,不懂原理是很難除錯的。

不過話說回來,要想理解深度學習中的基本概念(而不是想要在深度學習領域做研究),要學的數學知識也不是很難。你應該很快就能掌握這些知識。

所以本次實驗課,我們介紹本課程會涉及到的數學知識以及在之後“ 圖片英文字母識別”的專案中要用到的python numpy

模組。

警告:本次實驗介紹的數學知識只是為了讓你更好地理解本課程中的相關概念,有些地方不夠嚴謹,請勿等同於數學教科書參考

1.2 實驗知識點

  • 導數、偏導、梯度、鏈式法則
  • 矩陣運算基本法則
  • numpy基本運算介紹

1.3 實驗環境

  • python 2.7
  • numpy 1.12.1

二、實驗步驟

2.1 導數、偏導、梯度、複合函式求導鏈式法則

2.1.1 函式值隨自變數的變化速率--導數

高中數學裡面我們已經學過,函式值隨自變數的變化速率是導數。導數衡量的,其實是一個變數對函式值影響能力的大小。導數值越大,則該變數每改變一點對最終函式值的影響越大。且導數值為正時,代表自變數增大時函式值增大,反之若導數值為負,則自變數增大時函式值減小。
常見函式的導函式:

原函式f 導函式f'
任何常數 0
x 1
e^x e^x
x^2 2*x
1/x -1/x^2
ln(x) 1/x

2.1.2 從單變數到多變數--偏導

上面我們列舉的都是隻有一個自變數的函式,如果自變數有多個,如何求導數呢?比如對於函式f=x+y,怎樣衡量x和y分別對函式值f的影響快慢呢?
數學上引入了偏導的概念,對一個多變數函式f,求f對其中一個自變數x的偏導很簡單,就是將與x無關的其他自變數視為常亮,再使用單變數求導的方法去求導。得到的即為f對x的偏導。比如:

令f=x+2y, 則f對x求偏導的結果為1,對y求偏導的結果為2。
令f=x*y, 則f對x求偏導結果為y,對y求偏導結果為x。

2.1.3 多變數函式變化最快的方向--梯度

2.1.1 中我們提到了,對單變數函式來說,導數值的正負代表自變數對函式值影響的“方向”:變大或變小。那對於多變數函式來說,如何表達這個方向呢?這就引入了梯度的概念:

梯度是一個向量,向量長度與自變數的個數相等,且其中的每一個元素為函式對於對應變數求偏導的值。

比如對於函式f=x*y, 其梯度向量為(y,x), 對於具體的自變數的值,比如x=1,y=1的點,其梯度向量就為(1,1), 又比如x=10,y=-20點,其梯度向量就為(-20,10)

梯度作為一個向量,指向的是使函式值增大最快的方向(回想第一次實驗中的損失函式圖,梯度所指的方向是向上的)。

2.1.4 複合函式求導鏈式法則

上面我們講的求導數和求偏導,都是對於“簡單函式”,對於“複合函式”,比如下面這樣的函式:

f1(x)=1/x
f2(x)=e^x

f=f1(f2(x))

f函式是一個複合函式,它由f1f2函式“串聯”而來。其中f1的輸入是f2的輸出。

對於複合函式求導,一種方法是將複合函式展開,比如對於上面的函式, 得到f=1/(e^x),然後再根據簡單函式求導法則對自變數求導。過程如下:
f' = -1/((e^x)^2)*((e^x)') = -(e^x)/((e^x)^2) = -1/(e^x)
即 f' = -1/(e^x)

其實,在上面的求導過程中,我們已經使用了求導鏈式法則(chain rule),只是你沒有察覺而已。求導鏈式法則讓我們可以一部分一部分地,對複合函式求導,而不用放在一起求。這對於程式設計來說十分重要,它使得對複合函式求導變得十分簡單。
但是這裡描述起來可能稍顯複雜。以f為例,當我們需要對自變數x求導時,我們可以先將f2(x)看做一個自變數f2,先讓f1f2求導,得到第一部分導函式-1/(f2^2),再讓f2x求導,得到第二部分導函式e^x。求好之後,直接將兩部分導數乘起來,即得到最終複合函式整體的導數。不過要先使用實際的表示式替換掉第一部分導數中的f2, 即第一部分導數為-1/((e^x)^2), 第二部分導數為e^x。兩部分乘起來就得到了最終正確的-1/(e^x)

現在你可能覺得這個鏈式法則是複雜乏味的,但是下一次實驗你會發現鏈式法則真是太強大了。實際上,我們最後實現的深度神經網路,就是不斷在運用求導鏈式法則。

2.2 矩陣及其基本運算性質

如果你上過本科線性代數課程,你十有八九會對矩陣沒有什麼感覺,甚至對這麼一個運演算法則十分奇怪的東西感到厭惡。但我希望你今後能改變對矩陣、對線性代數的看法,不要讓糟糕的教材和老師糟糕的ppt毀掉線性代數可能帶給你的巨大的提升自己(是的,這並不誇張)的機會。矩陣其實非常非常非常有用,在現代科學的每一個角落,幾乎都能看到矩陣的身影,深度學習中更是如此。

限於篇幅,本節只會介紹必要的矩陣相關知識,線性代數中的更多東西,請你通過其他途徑學習(推薦使用英文教材學習)。

2.2.1 矩陣的表達形式

一個m*n的矩陣為一個m行n列的陣列,比如:

此處輸入圖片的描述

a是一個3*2的矩陣,b是一個2*3的矩陣,c是一個3*1的矩陣,d是一個1*2的矩陣。

其中,c只有一列,我們也可以稱c列向量d只有一行,我們也可以稱d行向量。本課程中,對於向量,預設都是指列向量

2.2.2 矩陣的運演算法則

  1. 矩陣的數乘運算
    一個標量(你可以直接理解為一個數字)乘以矩陣,得到的結果為矩陣中的每個元素和該標量相乘,如下圖:
    此處輸入圖片的描述
  1. 矩陣的轉置運算
    轉置運算通過在矩陣右上角新增一“撇”表示。
    此處輸入圖片的描述
    轉置就是矩陣翻轉一下,轉置會改變矩陣的形狀。注意觀察轉置是繞著哪個軸翻轉的。

  2. 矩陣之間的加減法
    矩陣之間的加減法要求參與運算的兩個矩陣尺寸相同,運算的結果等於兩個矩陣對應元素相加減。
    此處輸入圖片的描述

  3. 矩陣魔力的來源--矩陣之間的乘法
    矩陣的乘法有些複雜,但在第一講實驗中你已經見過它了。矩陣的乘法其實就是代表了一個線性方程組引數和自變數如何結合的過程(矩陣乘法還有更多豐富的含義,如有興趣,請你自己去探索)。
    此處輸入圖片的描述
    矩陣乘法的具體規則就是,第一個矩陣中的第i行的所有元素,與第二個矩陣中的第j列的所有元素,分別相乘之後再求和,得到結果矩陣中第i行第j列的元素。
    上面的描述只看一遍很難弄懂,請你結合圖片中的例子仔細揣摩。
    矩陣乘法首先要求參與乘法運算的兩個矩陣的尺寸能夠“相容”,具體的要求就是,第一個矩陣的列數與第二個矩陣的行數必須相同。你可以觀察圖片中的示例,第一個矩陣的列數都是2,第二個矩陣的行數也都是2,這樣才能保證“第一個矩陣中的第i行所有元素”與“第二個矩陣中第j列的所有元素”能夠一一對應
    矩陣乘法運算得到的結果矩陣,其行數等於第一個矩陣的行數,其列數等於第二個矩陣的列數。
    矩陣乘法不滿足交換律!!首先,交換兩個矩陣的位置之後它們的尺寸不一定能夠相容,然後即使相容,運算得到的結果也不一定與原來相同。你可以自己隨便舉幾個例子試一下。

2.3 科學計算庫 numpy

實現我們的深度神經網路,需要進行很多數學運算,尤其是矩陣運算。而你也看到了,矩陣的(乘法)運算很複雜,自己程式設計實現比較困難而且容易出錯。為了解決這些問題,我們將會使用python中的科學計算庫numpy。有了numpy, 我們的程式碼將大大簡化,同時速度也會有很大提升。

2.3.1 使用numpy

實驗樓環境已經安裝了numpy,使用import語句匯入即可,為了簡化程式碼,匯入後我們將numpy命名為np。

import numpy as np
print numpy.__version__  # 檢視numpy版本

當你使用numpy進行計算時,在terminal 裡輸入top命令,你會發現有多個"一樣"的python程序在執行,這是因為numpy會自動進行多程序運算,提高計算速度。

>> top

以下的例項請你自己在python shell 中一起實驗一遍。

2.3.2 numpy基本資料型別

numpy中的資料型別被稱為ndarray(即 N-dimensional array,多維陣列),建立一個ndarray很簡單:

import numpy as np

array=np.array([1,2,3],dtype=np.uint8)
print array

即向np.array()函式傳入一個python列表即可。注意dtype引數是可選的,它指定了生成的陣列的資料長度和型別,這裡是長度為8bit的無符號整數。

2.3.3 快速建立矩陣

mat1=np.zeros((2,3))

np.zeros()快速建立一個指定維度的全0矩陣,注意傳進去的引數是一個tuple

2.3.4 numpy中的高維矩陣

"矩陣"一般指有行和列的“二維”矩陣,但numpy還支援高維矩陣,比如下面:

nd=np.zeros((1,2,3,4))
print nd.shape
print nd.size

nd就可以看作是一個1x2x3x4尺寸的高維矩陣。ndarray.shape儲存的是陣列的“形狀”,也就是高維矩陣每一維的長度。ndarray.size儲存的是陣列每一維長度相乘的結果,即陣列元素的個數。

2.3.5 標準矩陣運算

首先你要注意的是,numpy中的運算和數學中的運算不是完全一樣的,實際上,numpy不僅為我們提供了標準運算,還提供了更多方便我們程式設計的運算型別和特性。

我們先來看標準的矩陣運算:

  1. 標量與矩陣相乘
    scalar=2
    mat=np.ones((2,3))
    mat1=scalar*mat
    
  2. 矩陣轉置
    mat=np.zeros((2,3))
    tmat=mat.T
    print mat.shape, tmat.shape
    mat3=np.array((1,2,3))
    tmat3=mat3.T
    print mat3.shape, tmat3.shape
    
    對於二維矩陣,ndarray.T即可得到其轉置。對於高維矩陣,ndarray.T會將維度的順序完全翻轉(順序逆過來)。
  3. 矩陣相加
    mat1=np.array([[1,2],[3,4]])
    mat2=np.array([[1,0],[0,1]])
    mat3=mat1+mat2
    
  4. 矩陣乘法
    mat1=np.array([[1,2],[3,4]])
    mat2=np.array([[5,6],[7,8]])
    mat3=mat1.dot(mat2)
    
    注意這裡有一些變化,矩陣相乘不能直接使用*號,而是通過.dot()函式。

2.3.6 擴充套件運算

numpy內建的擴充套件運算用起來非常方便。

  1. 兩個矩陣的對應元素相乘

    mat1=np.array([[1,2],[3,4]])
    mat2=np.array([[5,6],[7,8]])
    mat=mat1*mat2
    

    注意相乘的兩個矩陣尺寸必須相同。

  2. 標量與矩陣相加

    scalar=2
    mat=np.array([[1,2],[3,4]])
    mat1=scalar+mat
    

    標量與矩陣相加就相當於對矩陣的每個元素都加上該標量。

  3. 操縱高維矩陣的維度

    mat3=np.zeros((1,2,3))
    tmat3=mat3.transpose(0,2,1)
    print mat3.shape,tmat3.shape
    

    有時候,我們想要改變高維矩陣維度的順序,但ndarray.T只能完全翻轉,無法滿足我們的需求,這個時候就可以呼叫ndarray.transpose(),其引數代表原本矩陣的維度重新排列的順序。所以這裡的例子實際上相當於第0維不變,第1第2維交換。

  4. broad cast--拓寬操作
    國內有些人將numpy的broadcast按照字面意思翻譯為“廣播”,這樣顯然是容易誤導人的。根據broadcast在numpy中的實際作用,我個人更傾向於將braodcast 拆開並翻譯為“拓寬”(向更寬的矩陣拓展)。其具體作用為:
    當兩個矩陣進行加/減法運算時,比如我們需要將一個列向量加到一個矩陣的每一列上,由於尺寸不同,無法直接進行運算,一種直接粗暴的做法就是迴圈遍歷矩陣的每一列,再把列向量加到每一列上,這樣程式碼會顯得很複雜。而 numpy會自動執行的broadcast操作則會先將列向量“拓寬”成一個相同尺寸的矩陣,且其每一列都是對原列向量的複製,然後再進行運算。如下:
    mat1=np.zeros((3,2))
    vec=np.array([[1],[2],[3]])
    print mat1+vec
    
    對於行向量和高維矩陣也是如此。
    更詳細的描述,請參考numpy文件:broadcasting

2.3.7 雜項操作

本節介紹一些後面的專案會用到的其他雜項操作

  1. 生成隨機資料

    rannum=np.random.randn(5,10)
    

    這裡的np.random.randn()函式生成一個指定尺寸的矩陣,且矩陣中的所有數字符合正態分佈(normal distribution)

    l=[1,2,3]
    np.random.shuffle(l)
    print l
    

    np.random.shuffle()函式可以接收python list或者numpy ndarray,並將陣列中的元素隨機打亂。

  2. 對矩陣求和

    a=np.random.randn(3,2)
    print np.sum(a)
    

    np.sum()函式會對矩陣中的所有元素求和。

  3. numpy中的“軸(axis)”
    我們之前使用“維度”描述矩陣的形狀,這樣容易和之前提到的向量的維度(長度)混淆,numpy中有另一個概念叫做“軸(axis)”與這裡所說的“維度”很類似,指的是對一個矩陣進行操作時,所執行的“方向”。文字不太好描述,我們結合例項來理解:

    a=np.ones((3,2))
    print np.sum(a,axis=0)
    print np.sum(a,axis=1)
    

    np.sum(a,axis=0)就是對矩陣a,在第一個“軸”上求和,具體效果就是對矩陣的每一列求和。np.sum(a,axis=1)就是對矩陣a,在第二個“軸”上求和,具體效果就是對矩陣的每一行求和。
    這裡可能不太好理解,請自己多舉幾個例子實驗一下。

  4. e的指數

    a=np.random.randn(3,2)
    print np.exp(a)
    

    np.exp()返回輸入中的每個元素x都對e求指數的結果。

  5. 求一個數組中最大元素的下標

    a=[1,2,3,4,3,2,1]
    print np.argmax(a)
    

    np.argmax()返回一個python列表或numpy ndarray中的最大元素的下標。

三、實驗總結

本次實驗的內容已經被我儘量精簡了,只保留了後面的專案當中會用到的內容。我希望你能儘量理解上面的知識,雖然對於一些人來說這可能有些難,但數學最能體現人類的智慧不是嗎,數學是深度學習,乃至人工智慧得以發展的重要基礎。
如果你覺得本次實驗內容太簡單或者寫的不夠好,請自行查閱其他資料學習相關內容。

本次實驗,我們學習了:

  1. 導數衡量一個自變數對函式值影響的能力大小。
  2. 偏導用來衡量多變數函式中的一個自變數對函式值影響的能力大小。
  3. 梯度是一個向量,指向函式值增大最快的方向。
  4. 鏈式法則是指,對於複合函式,其求導過程可以一部分一部分地進行,再“連結”起來。
  5. 可以認為向量是矩陣的一種特殊形式。
  6. 矩陣乘法與線性方程組關係密切。
  7. numpy庫中的ndarray可以很方便的用來進行矩陣運算。

四、課後作業

  1. 請你回想本次實驗所講的每一個知識點,確保你對它們都理解的很清楚。
  2. numpy是一個非常著名的,經常被使用的庫,值得你進一步學習,請你自己繼續學習numpy中的其他東西:numpy官網