機器學習-感知器學習算法
Python語言實現
初學者整理,參考資料均來源於網上
一、 概述
感知器是由美國計算機科學家羅森布拉特(Rosenblatt)於1957年提出的。感知器可謂是最早的人工神經網絡。
感知器分單層感知器和多層感知器。
二、 單層感知器(Single Layer Perceptron)
l 簡介
單層感知器是一個具有一層神經元、採用閾值激活函數的前向網絡。通過對網絡權值的訓練,可以使感知器對一組輸人矢量的響應達到元素爲0或1的目標輸出,從而實現對輸人矢量分類的目的。
單層感知器是一個簡單的線性二分類器,它保存着輸入權重,根據輸入和內置的函數計算輸出.人工神經網絡中的單個神經元。
單層感知器可以計算邏輯或,邏輯與,邏輯非等運算,但是不能計算異或。因爲異或不是平面線性可分的,在多層感知器中解決。
l 模型
單層感知器只有輸入和輸出,輸入和輸出直接相連,多個輸入一個輸出。
模型爲每個輸入定義爲X,輸入到輸出的權重定義爲w,所有的輸入和權重的乘積和爲輸出的值,對於這個乘積和做如下處理,如果乘機和大於臨界值(一般是0),輸入端就取1;如果小於臨界值,就取-1。
l 工作原理
單層感知器可將外部輸入分爲兩類。當感知器的輸出爲+1時,輸入屬於L1類,當感知器輸出-1時,輸入屬於L2類,從而實現兩類目標的識別。在二維空間,單層感知器進行模式識別的判決超平面由下式決定:。
對於只有兩個輸入的判別邊界是直線(),選擇合適的學習算法可訓練出滿意的w1和w2。將樣本中兩類數據用直線分開。如下圖:
b的值決定了直線的偏移量,w決定了直線的旋轉度數。
l 學習算法
第一步、設置變量和參數
f(x)爲激活函數,y(n)爲實際輸出,d(n)爲期望輸出,λ爲學習速率,n爲迭代次數,e爲實際輸出與期望輸出的誤差。
第二步、初始化
給權值向量w的各個分量賦一個較小的隨機非零值,置n=1
第三步、輸入一組樣本X(n)=[1,x1(n),x2(n),…,xm(n)],並給出它的期望輸出d(n)。
第四步、計算出實際輸出:y(n)=f()
第五步、求出期望輸出和實際輸出的差 e=d(n)-y(n)。根據誤差判斷輸出是否滿足條件,一般爲對所有樣本誤差爲零或者均小於預設的值,則算法結束,否則將值增加1,並用下式調整權值:
w(n+1)=w(n)+λ[d(n)-y(n)]x(n)。
權值調整公式屬於隨機梯度下降算法。(待確認)
然後轉到第三步,進行下一輪計算過程。
l 代碼(Python)
感知器源碼(Perceptron.py):
#單層感知器機器學習算法 20170621 gyk
#多層感知器使用梯度下降算法進行訓練
#單層感知器只能解決線性可分問題
#importlogging
#importos
#預測函數,訓練使用使用公式“輸入×權重的和”爲結果。使用階躍函數sigmoid把結果轉換爲1或0
#由於默認第一個輸入爲1,第一個權重爲b,所以在公式轉換爲:w1*x1+w2*x2+b=0
#結果等於0爲線性分割線的線上。小於0爲線下,大於0爲線上。用該線把結果分成兩種。
#由於在預測的時候,輸入參數比訓練參數少一個結果列,所以不能用輸入參數數組計算特徵列,而是用權重數組減去第一個bias偏移量列來計算特徵數。
defpredict(inputs, weights):
activation = weights[0]
for i in range(len(weights)-1):
activation += weights[i+1]*inputs[i]
return sigmoid(activation)
#階躍函數,把值轉換成1/0或者1/-1
defsigmoid(value):
return 1.0 if value >= 0.0 else 0.0
#訓練
#初始化權重爲0.5,然後循環訓練集,讀取每一條訓練集數據,進行預測,然後把預測結果和實際結果進行對比,如果一樣就用下一條訓練。如果和實際結果不相等。就使用隨機梯度下降算法重新計算權重。
#然後重新訓練,直到所有訓練數據的預測結果和實際結果一直,則訓練結束。
#訓練的結果其實就是在平面上找一條直線,該直線能把兩類的數據點分割在直線的兩邊。如果有的點是錯誤的,就變化w1和w2來旋轉直線,變化w0來移動直線找到最佳位置。訓練速率太大會找不到最佳位置,
#訓練速率太小,訓練速度就太慢。期望值和實際值的差值的正負決定了旋轉和移動的方向。
#l_rate爲學習速率,一般在0~0.1之間取值。
def train(dataSet,l_rate):
count = 0
weights = [0.5 for i inrange(len(dataSet[0]))]
learningOK = False
while not learningOK:
if count >= 100:
print('訓練已經超過100次,無法完成訓練')
return None
else:
count += 1
learningOK = True
for input_row in dataSet:
prediction = predict(input_row,weights)
error = input_row[-1] - prediction#期望輸出與實際輸出誤差
if error != 0:
weights = sgd(weights,input_row, l_rate, error)
learningOK = False
return weights
#隨機梯度下降算法,計算新的權重,即改變分割線的位置
defsgd(weights, input_row, l_rate, error):
for i in range(len(input_row)-1):
weights[i+1] += l_rate * error*input_row[i]
weights[0] += l_rate * error
return weights
測試源碼(Perceptron_UnitTest.py):
#單層感知器單元測試 20170621 gyk
from impimport reload
fromPerceptron import *
#測試
deftest():
l_rate = 0.2 #學習速率
l_while = True
dict_function ={'or':test_or,'and':test_and,'not':test_not,'xor':test_xor}
while l_while:
print('輸入q退出,輸入or實現或功能,輸入not實現非功能,輸入and實現與功能。輸入xor調用異或功能。或直接用命令行調用test_others函數可以實現其他預測')
type_str = input()
if type_str == 'q':
l_while = False
else:
dict_function.get(type_str,'nothing')(l_rate)
#計算或使用或數據集訓練完模型,實現或的效果。
deftest_or(l_rate):
weights =train([[1,1,1],[1,0,1],[0,1,1],[0,0,0]] , l_rate)
test_common([int(input('第一個參數')),int(input('第二個參數'))], weights)
#計算與使用或數據集訓練完模型,實現或的效果。
deftest_and(l_rate):
weights =train([[1,1,1],[1,0,0],[0,1,0],[0,0,0]] , l_rate)
test_common([int(input('第一個參數')),int(input('第二個參數'))],weights)
#計算異或使用異或數據集訓練完模型,實現異或的效果。單層感知器無法完成異或功能。
deftest_xor(l_rate):
print('單層感知器無法完成異或功能')
weights =train([[1,1,0],[1,0,1],[0,1,1],[0,0,0]] , l_rate)
if weights != None:
test_common([int(input('第一個參數')),int(input('第二個參數'))],weights)
#計算非使用或數據集訓練完模型,實現或的效果。
deftest_not(l_rate):
weights = train([[0,1],[1,0]] , l_rate)
test_common([int(input('第一個參數'))], weights)
#計算通用
deftest_common(inputs, weights):
if weights != None:
print('結果爲:%d'%predict1(inputs, weights))
#訓練其他功能,只要輸入訓練集和待預測數據,就能預測出結果
deftest_others(trainDataSet, inputs):
weights = train(trainDataSet , 0.2)
test_common(inputs, weights)
if__name__=='__main__':
#logging.basicConfig(filename =os.path.join(os.getcwd(), 'log.txt'), level = logging.DEBUG)
test()
三、 多層感知器(Multi-Layer Perceptrons)
l 簡介
多層感知機(Multi Layer Perceptron, MLP)是由多個感知機層全連接組成的前饋神經網絡,這種模型在非線性問題中表現出色.
相對於單層感知器,輸出端從一個變到了多個;輸入端和輸出端之間也不光只有一層,增加了隱藏層。
所謂全連接是指層A上任一神經元與臨近層B上的任意神經元之間都存在連接.
反向傳播(Back Propagation,BP)是誤差反向傳播的簡稱,這是一種用來訓練人工神經網絡的常見算法,通常與最優化方法(如梯度下降法)結合使用.多層感知器就是使用BP算法進行訓練的。
l 模型
l 工作原理
由前面介紹看到,單個感知器能夠完成線性可分數據的分類問題,是一種最簡單的可以“學習”的機器。但他無法解決非線性問題。比如下圖中的XOR問題:即(1,1)(-1,-1)屬於同一類,而(1,-1)(-1,1)屬於第二類的問題,不能由單個感知器正確分類。
單個感知器雖然無法解決異或問題,但卻可以通過將多個感知器組合,實現複雜空間的分割。如下圖:
將兩層感知器按照一定的結構和係數進行組合,第一層感知器實現兩個線性分類器,把特徵空間分割,而在這兩個感知器的輸出之上再加一層感知器,就可以實現異或運算。
也就是,由多個感知器組合:
來實現非線性分類面,其中θ(·)表示階躍函數或符號函數。
l 學習算法
經典的BP神經網絡通常由三層組成:輸入層,隱含層與輸出層.通常輸入層神經元的個數與特徵數相關,輸出層的個數與類別數相同,隱含層的層數與神經元數均可以自定義。
每個隱含層和輸出層神經元輸出與輸入的函數關係爲:
I j =∑ i W ij O i Ij=∑iWijOi
O j =sigmod(I l )=11+e −I l Oj=sigmod(Il)=11+e−Il
其中W ij Wij表示神經元i與神經元j之間連接的權重,O j Oj代表神經元j的輸出, sigmod是一個特殊的函數用於將任意實數映射到(0,1)區間.
上文中的sigmod函數稱爲神經元的激勵函數(activation function),除了sigmod函數11+e −I l 11+e−Il外,常用還有tanh和ReLU函數.
我們用一個完成訓練的神經網絡處理迴歸問題,每個樣本擁有n個輸入.相應地,神經網絡擁有n個輸入神經元和1個輸出神經元.
實際應用中我們通常在輸入層額外增加一個偏置神經元,提供一個可控的輸入修正;或者爲每個隱含層神經元設置一個偏置參數.
我們將n個特徵依次送入輸入神經元,隱含層神經元獲得輸入層的輸出並計算自己輸出值,輸出層的神經元根據隱含層輸出計算出迴歸值.
上述過程一般稱爲前饋(Feed-Forward)過程,該過程中神經網絡的輸入輸出與多維函數無異.
現在我們的問題是如何訓練這個神經網絡.
作爲監督學習算法,BP神經網絡的訓練過程即是根據前饋得到的預測值和參考值比較,根據誤差調整連接權重W ij Wij的過程.
訓練過程稱爲反向傳播過程(BackPropagation),數據流正好與前饋過程相反.
首先我們隨機初始化連接權重W ij Wij,對某一訓練樣本進行一次前饋過程得到各神經元的輸出.
首先計算輸出層的誤差:
E j =sigmod ′ (O j )∗(T j −O j )=O j (1−O j )(T j −O j ) Ej=sigmod′(Oj)∗(Tj−Oj)=Oj(1−Oj)(Tj−Oj)
其中E j Ej代表神經元j的誤差,O j Oj表示神經元j的輸出,T j Tj表示當前訓練樣本的參考輸出,sigmod ′ (x) sigmod′(x)是上文sigmod函數的一階導數.
計算隱含層誤差:
E j =sigmod ′ (O j )∗∑ k E k W jk =O j (1−O j )∑ k E k W jk Ej=sigmod′(Oj)∗∑kEkWjk=Oj(1−Oj)∑kEkWjk
隱含層輸出不存在參考值,使用下一層誤差的加權和代替(T j −O j ) (Tj−Oj) .
計算完誤差後就可以更新W ij Wij和θ j θj :
W ij =W ij +λE j O i Wij=Wij+λEjOi
其中λ λ是一個稱爲學習率的參數,一般在(0,0.1)區間上取值.
實際上爲了加快學習的效率我們引入稱爲矯正矩陣的機制,矯正矩陣記錄上一次反向傳播過程中的E j O i EjOi值,這樣W j Wj更新公式變爲:
W ij =W ij +λE j O i +μC ij Wij=Wij+λEjOi+μCij
μ μ是一個稱爲矯正率的參數.隨後更新矯正矩陣:
C ij =E j O i Cij=EjOi
每一個訓練樣本都會更新一次整個網絡的參數.我們需要額外設置訓練終止的條件.
最簡單的訓練終止條件爲設置最大迭代次數,如將數據集迭代1000次後終止訓練.
單純的設置最大迭代次數不能保證訓練結果的精確度,更好的辦法是使用損失函數(loss function)作爲終止訓練的依據.
損失函數可以選用輸出層各節點的方差:
L=∑ j (T j −O j ) 2 L=∑j(Tj−Oj)2
爲了避免神經網絡進行無意義的迭代,我們通常在訓練數據集中抽出一部分用作校驗.當預測誤差高於閾值時提前終止訓練.
l 代碼
多層感知器源碼(MLP.py):
#多層感知器機器學習算法 20170621 gyk
#多層感知器使用BP算法進行訓練
#多層感知器,可以實現平面無法完成線性分割。把點看成是在立體空間中,用平面對他們進行分割
#隱藏節點數量的計算公式:s=[2*log2(m+n+1)]>=2 s爲隱藏節點數,m爲輸入特徵數,n爲識別出的類別數
import logging
import random
import math
class Mlp(object):
#構造器 learn學習速率 limit最大循環次數 correct矯正率
def __init__(self, learn=0.05, limit=10000,correct=0.1):
random.seed(0)
self.__learn = learn
self.__limit = limit
self.__correct = correct
self.__input_count = 0
self.__hidden_count = 0
self.__output_count = 0
self.__inputs = []
self.__hiddens = []
self.__outputs = []
self.__input_weights = []
self.__output_weights = []
self.__input_correction = []
self.__output_correction = []
#初始化 input_count輸入節點數量 hidden_count隱藏節點數量 output_count輸出節點數量
#輸入節點數量和特徵數量相關輸出節點數量和輸出類別數量相關
def init(self, input_count, hidden_count,output_count):
#初始化輸入,隱藏,輸出數量
self.__input_count = input_count + 1
self.__hidden_count = hidden_count
self.__output_count = output_count
#初始化輸入,隱藏,輸出節點的值
self.__inputs = [1.0] *self.__input_count
self.__hiddens = [1.0] *self.__hidden_count
self.__outputs = [1.0] *self.__output_count
#初始化存放一級,二級權重數組給權重賦值隨機數一級權重多加一列保存偏移量
self.__input_weights =[[self.rand(-0.2, 0.2) for i in range(self.__hidden_count)]for j in range(self.__input_count)]
self.__output_weights =[[self.rand(-2.0, 2.0) for i in range(self.__output_count)]for j inrange(self.__hidden_count)]
#初始化糾正數組一級權重多加一列保存偏移量
self.__input_correction =self.make_matrix(self.__input_count, self.__hidden_count)
self.__output_correction =self.make_matrix(self.__hidden_count, self.__output_count)
#構造二維數組
def make_matrix(self, row_count,column_count, value=0.0):
mat = []
for i in range(row_count):
mat.append([value] * column_count)
return mat
#生成隨機數
def rand(self, a, b):
return (b - a) * random.random() + a
#階躍函數
def sigmoid(self, x):
return 1.0 / (1.0 + math.exp(-x))
#階躍函數導數
def sigmoid_derivate(self, x):
return x * (1 - x)
#訓練
def train(self, trainDataSet, labels):
leaningOK = False
for i in range(self.__limit):
if leaningOK: #無損失,退出,訓練完成
return None
error = 0.0
for j in range(len(trainDataSet)):
self.predict(trainDataSet[j])
error +=self.back_propagate(labels[j])
logging.debug("當前循環次數爲:%d當前損失函數錯誤值爲:%f"%(i,error))
leaningOK = error <= 0.01
#預測
def predict(self, inputDataSet):
#計算輸入層
for i in range(self.__input_count-1):
self.__inputs[i] = inputDataSet[i]
#計算隱藏層
for h in range(self.__hidden_count):
total = 0.0
for i inrange(self.__input_count):
total += self.__inputs[i] *self.__input_weights[i][h]
self.__hiddens[h] =self.sigmoid(total)
#計算輸出層
for o in range(self.__output_count):
total = 0.0
for h inrange(self.__hidden_count):
total += self.__hiddens[h] *self.__output_weights[h][o]
self.__outputs[o] =self.sigmoid(total)
return self.__outputs[:]
#反向傳播更新權值
def back_propagate(self, labels):
#計算輸出層誤差
output_deltas = [0.0] *self.__output_count
for o in range(self.__output_count):
error = labels[o] -self.__outputs[o]
output_deltas[o] =self.sigmoid_derivate(self.__outputs[o]) * error
#計算隱藏層誤差
hidden_deltas = [0.0] *self.__hidden_count
for h in range(self.__hidden_count):
error = 0.0
for o inrange(self.__output_count):
error += output_deltas[o] *self.__output_weights[h][o]
hidden_deltas[h] =self.sigmoid_derivate(self.__hiddens[h]) * error
#更新輸出對應的權值
for h in range(self.__hidden_count):
for o inrange(self.__output_count):
change = output_deltas[o] *self.__hiddens[h]
self.__output_weights[h][o] +=self.__learn * change + self.__correct * self.__output_correction[h][o]
self.__output_correction[h][o]= change
#更新輸入對應的權值
for i inrange(self.__input_count):
for h inrange(self.__hidden_count):
change = hidden_deltas[h] *self.__inputs[i]
self.__input_weights[i][h] +=self.__learn * change + self.__correct * self.__input_correction[i][h]
self.__input_correction[i][h] =change
#返回錯誤總量
error = 0.0
for o in range(self.__output_count):
error += 0.5 * (labels[o] -self.__outputs[o]) ** 2
return error
測試源碼(MLP_UnitTest.py):
#多層感知器單元測試 20170621gyk
from imp import reload
from MLP import *
import logging
import os
#測試
def test():
l_while = True
dict_function = {'or':test_or,'xor':test_xor}
whilel_while:
print('輸入q退出,輸入or實現或功能,輸入xor調用異或功能。或直接用命令行調用test_others函數可以實現其他預測')
type_str = input()
if type_str == 'q':
l_while = False
else:
dict_function.get(type_str,'nothing')()
#計算或使用或數據集訓練完模型,實現或的效果。
def test_or():
test_common([[1,1],[1,0],[0,1],[0,0]], [[1],[1],[1],[0]], [int(input('第一個參數')),int(input('第二個參數'))])
#計算異或使用異或數據集訓練完模型,實現異或的效果。
def test_xor():
test_common([[1,1],[1,0],[0,1],[0,0]], [[0],[1],[1],[0]], [int(input('第一個參數')),int(input('第二個參數'))])
#計算通用
def test_common(trainDataSet, labels,inputs):
mlp = Mlp()
mlp.init(2,5,1) #隱藏節點數量的計算公式:s=[2*log2(m+n+1)]>=2 s爲隱藏節點數,m爲輸入特徵數,n爲識別出的類別數
mlp.train(trainDataSet, labels)
print('結果爲:')
for i in range(1):
#print(mlp.predict(inputs)[i])
print(1 if mlp.predict(inputs)[i]>=0.5 else 0)
#訓練其他功能,只要輸入訓練集和待預測數據,就能預測出結果
def test_others(trainDataSet, inputs):
test_common(trainDataSet, inputs)
if __name__=='__main__':
#logging.basicConfig(filename = os.path.join(os.getcwd(), 'log.txt'),level = logging.DEBUG)
test()