寒假在看機(jī)器學(xué)習(xí)這本書,看神經(jīng)網(wǎng)絡(luò)這一章的時(shí)候開始手動(dòng)敲一些代碼來(lái)實(shí)現(xiàn)一些基本的神經(jīng)網(wǎng)絡(luò)程序。首先介紹一下基本概念。
神經(jīng)元
神經(jīng)元是神經(jīng)網(wǎng)絡(luò)的基本單元,接收多個(gè)神經(jīng)元傳遞過(guò)來(lái)的輸入信號(hào),然后通過(guò)激活函數(shù)計(jì)算輸出信號(hào)。
圖是機(jī)器學(xué)習(xí)這本書里的。從圖里可以看到每個(gè)輸入信號(hào)都有一個(gè)權(quán)重w,這個(gè)權(quán)重是動(dòng)態(tài)改變的,我們平時(shí)所說(shuō)的訓(xùn)練神經(jīng)網(wǎng)絡(luò)主要就是訓(xùn)練(修正)這個(gè)權(quán)重w。
同時(shí)每個(gè)神經(jīng)元有一個(gè)參數(shù)θ,這個(gè)θ是閾值,生物意義上,如果輸入信號(hào)的加權(quán)和比閾值高,意味著這個(gè)神經(jīng)元被激活(處于興奮狀態(tài)),信號(hào)向下一個(gè)神經(jīng)元傳遞。但是在這里的感知機(jī)模型里,θ不過(guò)是個(gè)公式里的參數(shù)罷了。
感知機(jī)(Perceptron)
感知機(jī)本質(zhì)上就是個(gè)兩層神經(jīng)元構(gòu)成的簡(jiǎn)單神經(jīng)網(wǎng)絡(luò)。一層是輸入層,全部是輸入神經(jīng)元,直接把輸入信號(hào)傳遞給下一層神經(jīng)元,第二層就是輸出層。
感知機(jī)使用Sigmoid函數(shù)作為激活函數(shù):
使用Python可以用一行代碼表示出來(lái),不過(guò)得借助math庫(kù)。
lambda s: 1.0 / (1 + math.exp(-s))
每次感知機(jī)有輸出時(shí),內(nèi)部參數(shù)(w)都需要被訓(xùn)練和調(diào)整。其訓(xùn)練算法如下:
學(xué)習(xí)率介于0和1之間,我們可以手動(dòng)設(shè)置。算法本身很簡(jiǎn)單。
接下來(lái)就是思考一下代碼實(shí)現(xiàn)的問題了。
- 首先我們可以定義一個(gè)結(jié)點(diǎn)類Node用來(lái)表示一個(gè)神經(jīng)元節(jié)點(diǎn),從這個(gè)Node類可以進(jìn)一步派生出輸入節(jié)點(diǎn)類和輸出節(jié)點(diǎn)類。
- 我們可以認(rèn)為每個(gè)節(jié)點(diǎn)Node都有一個(gè)輸入節(jié)點(diǎn)列表。對(duì)于輸入節(jié)點(diǎn),它的輸入節(jié)點(diǎn)列表就是空集 [] 。
- 節(jié)點(diǎn)Node的默認(rèn)激活函數(shù)為sigmoid函數(shù)。
- 本來(lái)輸入節(jié)點(diǎn)的職能就是把輸入信號(hào)傳遞給輸出層,不需要激活函數(shù)的。但是我們也可以把它視為一個(gè)M-P神經(jīng)元,它的輸入信號(hào)加權(quán)和為0(它本身就是輸入層,不存在額外的輸入),將它的閾值θ視為它要傳遞給輸出層的數(shù)值的相反數(shù)。將它的激活函數(shù)設(shè)置為f(x)=x。就可以等同于一個(gè)M-P神經(jīng)元了。
代碼實(shí)現(xiàn)
完整python代碼如下:
# coding:utf-8
import math
def sigmoid(x):
return 1.0/(1+math.exp(-x))
class Record:# 輸入的一條訓(xùn)練數(shù)據(jù)
def __init__(self):
feature_vector = []# 特征向量
label = None# 標(biāo)簽
return
class Node: # 神經(jīng)元節(jié)點(diǎn)
def __init__(self):
self.input_list = []# 輸入的神經(jīng)元列表
self.activated = False# 這個(gè)神經(jīng)元是否已經(jīng)計(jì)算出輸出信號(hào)
self.recent_output = None# 上一次的輸出信號(hào)
self.threshold = 0.0# 閾值θ
self.activation_func = lambda s: 1.0 / (1 + math.exp(-s)) # default func: sigmoid function
return
def add_input(self, node):# 添加輸入結(jié)點(diǎn)
self.input_list.append([node, 1.0])# [結(jié)點(diǎn)對(duì)象,權(quán)重w] 權(quán)重默認(rèn)為1
return
def set_threshold(self, th):# 設(shè)置閾值θ
self.threshold = th
return
def output(self):# 通過(guò)激活函數(shù)計(jì)算輸出信號(hào)
sum_ = 0.0
for p in self.input_list:
prev_node = p[0]
sum_ += prev_node.output() * p[1]
self.recent_output = self.activation_func(sum_ - self.threshold)
return self.recent_output
class InputNode(Node):# 神經(jīng)元輸入節(jié)點(diǎn)
def __init__(self):
Node.__init__(self)
self.activation_func = lambda s: s# 注意激活函數(shù)是 f(x)=x
return
def set_input_val(self, val):
Node.set_threshold(self, -val)# 輸入的結(jié)點(diǎn)列表為空,設(shè)置閾值為 -val
return
class OutputNode(Node):# 神經(jīng)元輸出節(jié)點(diǎn)
def __init__(self):
Node.__init__(self)
self.threshold = 4.0
return
class NeuralNetwork:# 抽象神經(jīng)網(wǎng)絡(luò)
def __init__(self):
self.eta = 0.5# 學(xué)習(xí)率η
self.data_set = []# 輸入的數(shù)據(jù)集,列表內(nèi)的元素為 Record對(duì)象
def set_data_set(self, data_set_):# 設(shè)置數(shù)據(jù)集
self.data_set = data_set_
return
class SingleLayerNeuralNetwork(NeuralNetwork):# 感知機(jī)模型
def __init__(self):
NeuralNetwork.__init__(self)
self.perceptron = OutputNode()# 一個(gè)輸出結(jié)點(diǎn)
self.input_node_list = []
self.perceptron.threshold = 4.0
return
def add_input_node(self):# 添加輸入結(jié)點(diǎn)
inode = InputNode()
self.input_node_list.append(inode)
self.perceptron.add_input(inode)
return
def set_input(self, value_list):# 給每個(gè)輸入結(jié)點(diǎn)設(shè)置輸入數(shù)據(jù)
assert len(value_list) == len(self.input_node_list)
for index in range(0, len(value_list), 1):
value = value_list[index]
node = self.input_node_list[index]
node.set_input_val(value)
return
def adjust(self, label):# 每條記錄訓(xùn)練后,自動(dòng)調(diào)整內(nèi)部參數(shù)w
for prev_node_pair in self.perceptron.input_list:
delta_weight = self.eta * (label - self.perceptron.recent_output) * prev_node_pair[0].recent_output
origin_weight = prev_node_pair[1]
prev_node_pair[1] = origin_weight + delta_weight
return
def run(self):# 開始跑訓(xùn)練集
for data in self.data_set:# 遍歷訓(xùn)練集
self.set_input(data.feature_vector)
record_ = self.perceptron.output()
print record_
self.adjust(data.label)# 每條記錄訓(xùn)練后,自動(dòng)調(diào)整內(nèi)部參數(shù)w
return
現(xiàn)在讓這個(gè)感知機(jī)來(lái)解決或問題(x∨??),假設(shè)1表示真,0表示假,那么1和1進(jìn)行或運(yùn)算結(jié)果就是1,1和0進(jìn)行或運(yùn)算結(jié)果就是1,0和0進(jìn)行或運(yùn)算結(jié)果就是0。
輸入數(shù)據(jù)如下(一條記錄保存為一行,第一個(gè)和第二個(gè)數(shù)字是輸入,第3個(gè)數(shù)字是標(biāo)簽。數(shù)據(jù)假設(shè)保存在E://data/ann/train_0.txt):
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
1 0 1
0 0 0
1 0 1
1 1 1
1 1 1
0 1 1
1 0 1
0 0 0
有了以上代碼和數(shù)據(jù)后,用以下Python代碼開始跑訓(xùn)練集:
file_handler = open('E://data/ann/train_0.txt')
data_set = []
line = file_handler.readline()
while line:# 遍歷文件里的數(shù)據(jù)
record = Record()
item_feature_vector = []
str_list = line.split()
item_feature_vector.append(float(str_list[0]))
item_feature_vector.append(float(str_list[1]))
record.feature_vector = item_feature_vector
record.label = float(str_list[2])
data_set.append(record)
line = file_handler.readline()
print len(data_set)
ann = SingleLayerNeuralNetwork()# 實(shí)例化感知機(jī)對(duì)象
ann.add_input_node()
ann.add_input_node()# 添加兩個(gè)輸入結(jié)點(diǎn)
ann.set_data_set(data_set)# 設(shè)置數(shù)據(jù)集
ann.run()# 等待結(jié)果
我跑的結(jié)果如下圖所示:
可以看到一開始的輸出有點(diǎn)離譜,但是隨著數(shù)據(jù)的增多輸出變得越來(lái)越像與函數(shù)的輸出了。
實(shí)際上感知機(jī)能解決的問題很少,為了解決復(fù)雜問題,需要引入多層神經(jīng)網(wǎng)絡(luò)和新的學(xué)習(xí)算法:BP誤差逆?zhèn)鞑ニ惴?/strong>。