機器學習(十二):深度神經網絡淺析

先闡述一些概念性的東西(也是對之前的東西的回顧,記性不好,老忘):

迴歸問題與分類問題:

迴歸:計算圓形面積的例子就屬於迴歸問題,即我們的目的是對於一個輸入x,預測其輸出值y,且這個y值是根據x連續變化的值。

分類:分類問題則是事先給定若干個類別,對於一個輸入x,判斷其屬於哪個類別,即輸出一般是離散的

監督學習和無監督學習:

監督學習:通過訓練讓機器自己找到特徵和標籤之間的聯繫(注:也就是學習的訓練集包含輸入和輸出,得到了最優參數模型之後 ,新來的數據集在面對只有特徵沒有標籤的情況下時,可以判斷出標籤)

無監督學習:訓練數據中只有特徵沒有標籤,輸入數據沒有被標記,也沒有確定的結果。樣本數據類別未知,需要根據樣本間的相似性對樣本集進行分類。(注:不一定"分類",沒有訓練集,旨在尋找規律性,不予以某種預先分類標籤對上號爲目的)

損失函數,二元損失函數:

quadratic loss function通過計算hy之間差值的二次方,來表達一個神經網絡的效果。具體形式如下:

J(Θ)=(h(Θ,X)−Y)2J(Θ)=(h(Θ,X)−Y)2

這個公式幾乎就是我們在上面提到的對模型的誤差求平方,只是稍微再複雜一點。其中的希臘字母theta代表網絡中的所有參數,X代表向神經網絡輸入的數據,Y代表輸入數據對應的預期正確輸出值。

實際上,在這裏theta纔是自變量,因爲我們一開始不知道讓網絡能夠正確的工作的參數值是多少。我們的學習算法learn按照某種策略,通過不斷的更新參數值來使損失函數J(theta, X, Y)的值減小。在這裏,theta是不斷變化的量。那XY就不是自變量了嗎?對,它們確實不是自變量!因爲對於我們要解決的一個具體的問題(比如圖片字母識別),其訓練數據中包含的信息其實代表的是這個問題本身的性質,我們的學習算法learn其實最終就是要從訓練數據data中學習到這些性質,並通過網絡模型model的結構和其中的參數把這些性質表達出來。對於一個特定的問題來說,可以認爲它們是常量!

梯度下降算法:來幫助我們減小損失函數的值

參數和超參數:

我們將模型model中的參數theta稱爲參數,而學習算法(即梯度下降算法)裏的參數alhpa被稱爲超參數。

叫法不同,是因爲它們的作用及我們設置它們值的方式不一樣。theta被稱爲 “參數”,是因爲theta決定了我們的模型model的性質,並且theta的最終值是由我們的學習算法learn學習得到的,不需要我們手工設定。

alpha則不同,在這裏alpha並不是模型model中的參數,而是在學習算法learn中決定我們的梯度下降算法每一步走多遠的參數,它需要我們手工設定,且決定了得到最優theta的過程。即超參數決定如何得到最優參數

超參數alpha又被稱爲學習速率(learning rate)。因爲alpha越大時,我們的參數theta更新的幅度越大,我們可能會更快的到達最低點。但是alpha不能設置的太大,否則有可能一次變化的太大導致 “步子太長”,會直接越過最低點,甚至導致損失函數值不降反升。

反向傳播算法:

對於一個具體的參數值,我們只需要把每個節點的值代入求得的導函數公式就可以求得導數(偏導數),進而得到梯度。 這很簡單,我們先從計算圖的底部開始向上,逐個節點計算函數值並保存下來。這個步驟,叫做前向計算(forward)

然後,我們從計算圖的頂部開始向下,逐步計算損失函數對每個子節點的導函數,代入前向計算過程中得到的節點值,得到導數值。這個步驟,叫做反向傳播(backward)或者更明確一點叫做反向梯度傳播

全連接層:

我們將層與層之間的每個點都有連接的層叫做全連接(fully connect)層

以sigmoid爲例進行簡易的反向傳播代碼:

import numpy as np
class FullyConnect:
    def __init__(self, l_x, l_y):  # 兩個參數分別爲輸入層的長度和輸出層的長度
        self.weights = np.random.randn(l_y, l_x)  # 使用隨機數初始化參數
        print(self.weights)
        self.bias = np.random.randn(1)  # 使用隨機數初始化參數
        print(self.bias)
    def forward(self, x):
        self.x = x  # 把中間結果保存下來,以備反向傳播時使用
        self.y = np.dot(self.weights, x) + self.bias  # 計算w11*a1+w12*a2+bias1
        return self.y  # 將這一層計算的結果向前傳遞
    def backward(self, d):
        self.dw = d * self.x  # 根據鏈式法則,將反向傳遞回來的導數值乘以x,得到對參數的梯度
        self.db = d
        self.dx = d * self.weights
        return self.dw, self.db  # 返回求得的參數梯度,注意這裏如果要繼續反向傳遞梯度,應該返回self.db
class Sigmoid:
    def __init__(self):  # 無參數,不需初始化
        pass
    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))
    def forward(self, x):
        self.x = x
        self.y = self.sigmoid(x)
        return self.y
    def backward(self):  # 這裏sigmoid是最後一層,所以從這裏開始反向計算梯度
        sig = self.sigmoid(self.x)
        self.dx = sig * (1 - sig)
        return self.dx  # 反向傳遞梯度
def main():
    fc = FullyConnect(2, 1)
    sigmoid = Sigmoid()
    x = np.array([[1], [2]])
    print('weights:', fc.weights, ' bias:', fc.bias, ' input: ', x)
    # 執行前向計算
    y1 = fc.forward(x)
    y2 = sigmoid.forward(y1)
    print('forward result: ', y2)
    # 執行反向傳播
    d1 = sigmoid.backward()
    dx = fc.backward(d1)
    print('backward result: ', dx)
if __name__ == '__main__':
    main()

激活函數:

在多層神經網絡中,上層節點的輸出和下層節點的輸入之間具有一個函數關係,這個函數稱爲激活函數(又稱激勵函數)。

激活函數的意義也就是加入非線性因素,讓神經網絡具備非線性的表達能力(當然不是真正意義上的非線性,不過可以逼近任意的非線性函數)

epoch:我們稱所有訓練圖片都已參與一遍訓練的一個週期稱爲一個epoch。每個epoch結束時,我們會將訓練數據重新打亂,這樣可以獲得更好的訓練效果。我們通常會訓練多個epoch

以淺層神經網絡識別圖片中的英文字母案例爲例:

代碼:

# encoding=utf-8
from scipy import misc
import numpy as np
def chuli(src, dst):
    with open(src, 'r') as f:  # 讀取圖片列表
        list = f.readlines()
    data = []
    labels = []
    for i in list:
        name, label = i.strip('\n').split(' ')  # 將圖片列表中的每一行拆分成圖片名和圖片標籤
        img = misc.imread(name)  # 將圖片讀取出來,存入一個矩陣
        img = img / 255  # 將圖片轉換爲只有0、1值的矩陣
        img.resize((img.size, 1))  # 爲了之後的運算方便,我們將圖片存儲到一個img.size*1的列向量裏面
        data.append(img)
        labels.append(int(label))
    print('write to npy')
    np.save(dst, [data, labels])  # 將訓練數據以npy的形式保存到成本地文件
    print('completed')
#數據層,讀入數據的操作放到一個數據層
class Data:
    def __init__(self, name, batch_size):  # 數據所在的文件名name和batch中圖片的數量batch_size
        with open(name, 'rb') as f:
            data = np.load(f)
        self.x = data[0]  # 輸入x
        self.y = data[1]  # 預期正確輸出y
        self.l = len(self.x)
        self.batch_size = batch_size
        self.pos = 0  # pos用來記錄數據讀取的位置
    def forward(self):
        pos = self.pos
        bat = self.batch_size
        l = self.l
        if pos + bat >= l:  # 已經是最後一個batch時,返回剩餘的數據,並設置pos爲開始位置0
            ret = (self.x[pos:l], self.y[pos:l])
            self.pos = 0
            index = range(l)
            np.random.shuffle(list(index))  # 將訓練數據打亂
            self.x = self.x[index]
            self.y = self.y[index]
        else:  # 不是最後一個batch, pos直接加上batch_size
            ret = (self.x[pos:pos + bat], self.y[pos:pos + bat])
            self.pos += self.batch_size
        return ret, self.pos  # 返回的pos爲0時代表一個epoch已經結束
    def backward(self, d):  # 數據層無backward操作
        pass
class FullyConnect:
    def __init__(self, l_x, l_y):  # 兩個參數分別爲輸入層的長度和輸出層的長度
        #l_x爲輸入單個數據向量的長度,在這裏是17*17=289,l_y代表全連接層輸出的節點數量,由於大寫英文字母有26個,所以這裏的l_y=26
        self.weights = np.random.randn(l_y, l_x) / np.sqrt(l_x)  # 使用隨機數初始化參數,請暫時忽略這裏爲什麼多了np.sqrt(l_x)
        self.bias = np.random.randn(l_y, 1)  # 使用隨機數初始化參數
        self.lr = 0  # 先將學習速率初始化爲0,最後統一設置學習速率
    def forward(self, x):
        self.x = x  # 把中間結果保存下來,以備反向傳播時使用
        self.y = np.array([np.dot(self.weights, xx) + self.bias for xx in x])  # 計算全連接層的輸出
        return self.y  # 將這一層計算的結果向前傳遞
    def backward(self, d):
        ddw = [np.dot(dd, xx.T) for dd, xx in zip(d, self.x)]  # 根據鏈式法則,將反向傳遞回來的導數值乘以x,得到對參數的梯度
        self.dw = np.sum(ddw, axis=0) / self.x.shape[0]
        self.db = np.sum(d, axis=0) / self.x.shape[0]
        self.dx = np.array([np.dot(self.weights.T, dd) for dd in d])
        # 更新參數
        self.weights -= self.lr * self.dw
        self.bias -= self.lr * self.db
        return self.dx  # 反向傳播梯度
#激活函數層
class Sigmoid:
    def __init__(self):  # 無參數,不需初始化
        pass
    def sigmoid(self, x):
        return 1 / (1 + np.exp(-x))
    def forward(self, x):
        self.x = x
        self.y = self.sigmoid(x)
        return self.y
    def backward(self, d):
        sig = self.sigmoid(self.x)
        self.dx = d * sig * (1 - sig)
        return self.dx  # 反向傳遞梯度
#損失函數層
class QuadraticLoss:
    def __init__(self):
        pass
    def forward(self, x, label):
        self.x = x
        self.label = np.zeros_like(x)  # 由於我們的label本身只包含一個數字,我們需要將其轉換成和模型輸出值尺寸相匹配的向量形式
        for a, b in zip(self.label, label):
            a[b] = 1.0  # 只有正確標籤所代表的位置概率爲1,其他爲0
        self.loss = np.sum(np.square(x - self.label)) / self.x.shape[0] / 2  # 求平均後再除以2是爲了表示方便
        return self.loss
    def backward(self):
        self.dx = (self.x - self.label) / self.x.shape[0]  # 2被抵消掉了
        return self.dx
#準確率層
class Accuracy:
    def __init__(self):
        pass
    def forward(self, x, label):  # 只需forward
        self.accuracy = np.sum([np.argmax(xx) == ll for xx, ll in zip(x, label)])  # 對預測正確的實例數求和
        self.accuracy = 1.0 * self.accuracy / x.shape[0]
        return self.accuracy
#構建神經網絡
def main():
    datalayer1 = Data('train.npy', 1024)  # 用於訓練,batch_size設置爲1024
    datalayer2 = Data('validate.npy', 10000)  # 用於驗證,所以設置batch_size爲10000,一次性計算所有的樣例
    inner_layers = []
    inner_layers.append(FullyConnect(17 * 17, 26))
    inner_layers.append(Sigmoid())
    losslayer = QuadraticLoss()
    accuracy = Accuracy()
    for layer in inner_layers:
        layer.lr = 1000.0  # 爲所有中間層設置學習速率
    epochs = 20
    for i in range(epochs):
        print('epochs:', i)
        losssum = 0
        iters = 0
        while True:
            data, pos = datalayer1.forward()  # 從數據層取出數據
            x, label = data
            for layer in inner_layers:  # 前向計算
                x = layer.forward(x)
            loss = losslayer.forward(x, label)  # 調用損失層forward函數計算損失函數值
            losssum += loss
            iters += 1
            d = losslayer.backward()  # 調用損失層backward函數層計算將要反向傳播的梯度
            for layer in inner_layers[::-1]:  # 反向傳播
                d = layer.backward(d)
            if pos == 0:  # 一個epoch完成後進行準確率測試
                data, _ = datalayer2.forward()
                x, label = data
                for layer in inner_layers:
                    x = layer.forward(x)
                accu = accuracy.forward(x, label)  # 調用準確率層forward()函數求出準確率
                print('loss:', losssum / iters)
                print('accuracy:', accu)
                break
if __name__ == '__main__':
    #chuli('train.txt', 'train.npy')
    #chuli('test.txt', 'test.npy')
    #chuli('validate.txt', 'validate.npy')
    main()

數據集下載:

http://labfile.oss.aliyuncs.com/courses/814/data.tar.gz

說明上面代碼的大體處理步驟:

1:下載數據集,加載到項目裏,然後規範好訓練集驗證集測試集所用圖片都是哪些,從規範好的列表中讀取圖片,轉化爲只有0,1值的矩陣,存儲到一個img.size*1的列向量,將轉化結果輸出到文件中。

2:編寫數據層處理函數,生成一份用於訓練,一份用於驗證的數據。

3:將數據傳入全連接層,以及激活函數層進行前向計算。

4:將前向計算返回的值代入損失函數層forward函數,計算損失函數值,用損失層backward函數層計算將要反向傳播的梯度。

5:用訓練好的模型帶入驗證數據前向計算,完成後數據加入準確率函數中進行計算。

6:查看損失值以及正確率的變化情況。

可以將平方損失函數層替換成交叉熵損失函數層加上隱層等優化。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章