當前位置:
首頁 > 新聞 > 如何用自動機器學習實現神經網路進化

如何用自動機器學習實現神經網路進化

雷鋒網按:本文由圖普科技編譯自《Design by Evolution: How to evolve your neural network with AutoML》,雷鋒網獨家首發。

對大多數從事機器學習工作的人來說,設計一個神經網路無異於製作一項藝術作品。神經網路通常始於一個常見的架構,然後我們需要對參數不斷地進行調整和優化,直到找到一個好的組合層、激活函數、正則化器和優化參數。在一些知名的神經網路架構,如VGG、Inception、ResNets、DenseNets等的指導下,我們需要對網路的變數進行重複的操作,直到網路達到我們期望的速度與準確度。隨著網路處理能力的不斷提高,將網路優化處理程序自動化變得越來越可行。

在像Random Forests和SVMs這樣的淺模型中,我們已經能夠使超參數優化的操作自動化進行了。一些常用的工具包,比如sk-learn,向我們提供了搜索超參數空間的方法。在其最簡單的、最基礎的格式中,「超參數」是我們在所有可能的參數中搜索得到的,或者是通過從參數分布中任意採樣得到的。(詳情請點擊此鏈接查看。)這兩種方法都面臨著兩個問題:第一,在錯誤參數區域進行搜索時會造成資源浪費;第二,處理大量的動態特徵參數集將導致效率過低。因此,改變處理器的架構變得相當困難。雖然現在有很多看似高效的方法,比如Bayesian優化方法。但Bayesian優化法雖然能夠解決了第一個問題,卻對第二個問題無能為力;另外,在Bayesian優化設置中也很難進行探索模型。

自動識別最佳模型的想法就現在來說已經不算新鮮了,再加上最近大幅度提升的處理能力,實現這一想法比以往任何時候都要容易。

問題設定

考慮超參數優化的方式之一,就是將它看做一個「元學習問題」。

我們究竟能否打造出一個可以用於判斷網路性能好壞的演算法呢?

注意:接下來我將繼續使用「元學習」這個術語,即使將這個問題描述為「元學習」有點混淆視聽,但我們千萬不能把它與「學習」相關的一些方法弄混了。

如何用自動機器學習實現神經網路進化

我們的目標是定義網路隱含層(綠色)的數量以及每個隱含層的參數。

具體而言,就是探究模型架構和模型的參數空間,從而在給定的數據集上優化其性能。這個問題複雜難解,而回報稀薄。之所以說它回報稀薄,是因為我們需要對網路進行足夠的訓練,還要對它進行評估;而在訓練、評估完成後,我們得到回報的僅僅是一些得分。這些得分反映了整個系統的性能表現,而這種類型的回報並不是可導函數!說到這,是不是讓你想起了什麼呢?沒錯,這就是一個典型的「強化學習」情境!

維基百科對「強化學習」的定義:

強化學習」(RL)是一種重要的機器學習方法,它的靈感來自於心理學的行為主義理論。具體來說,「強化學習」是關於有機體(agent)如何在環境(environment)的刺激下,將累計獎勵最大化的方法。

「強化學習」與標準的監督式學習之間的區別在於它不需要出現正確的輸入或輸出對,也不需要精準校正其次優化行為。另外,「在線性能」也是「強化學習」關注的焦點,即在未知領域的探索與現有知識的開發之間找到平衡。

如何用自動機器學習實現神經網路進化

上圖情境中的有機體(agent)是一個模型,環境(environment)就是我們用於訓練和評估的數據集。解釋器(interpreter)是對每一行為進行分析以及設置有機體狀態(在我們這個情境中,解釋器設置的是網路參數)的過程。

通常情況下,「強化學習」問題都被定義為一個Markov決策過程。其目的就是優化有機體的總回報。每一步,你需要對優化模型輸出作出決策,或者是探索出一個新的行為。在環境的刺激下,有機體將根據得到的反饋,形成一個調整政策,不斷改進其行為。

注意:這個話題超出了本文討論的範圍,R.Sutton和A. Barto的《強化學習介紹》可能是關於這個主題的最佳入門指導書。

進化演算法

如何用自動機器學習實現神經網路進化

解決「強化學習」問題的另一種方法是「進化演算法」。在生物進化的啟發下,進化演算法通過創建一個解決方案的集合,尋找解決方案的空間;然後,它會對每一解決方案進行評估,並根據評估得分不斷調整這個方案集合。生物進化論中所講的「進化」涉及到一個種群中最佳成員的選擇和變異。因此,我們的解決方案集合也會不斷進化發展,以提高其整體適應性,並為問題找到提供可行的解決方案。

如何用自動機器學習實現神經網路進化

進化演算法中的「進化」

上圖的左邊介紹了進化的過程,設計一個「進化演算法」涉及到兩個部分——「選擇」,以及需要遵循的「跨界」或「變異」策略。

「選擇」:對於「選擇」,我們通常的做法是挑選最佳的個體和一些任意的個體,以達到多樣性。更先進的選擇方法是在種群下設立不同的「次群」,即「物種」;然後在物種中選擇最佳的個體,以保護其多樣性。另一種比較受歡迎的做法是「競賽選擇」,即任意選擇一些個體參與競賽,挑選出勝者(基因優勝的個體)。

「跨界」:「跨界」也稱「交叉跨界」,指的是兩組或兩組以上親本交叉混合,產生後代。「交叉跨界」高度依賴於問題結構的方式。常見的方法是用一個項目列表(一般是數值)對親本進行描述,然後從親本中挑選任意部分來生成新的基因組合。

變異」:「變異」或「突變」指的是任意改變基因組的過程。這是主要的開發因素,有助於保持種群的多樣性。

實施啟用

「進化演算法」的實施啟用使用了PyTorch來建立代理,這個代理將會探索用於完成簡單分類任務的DNNs。這個實驗使用的是MNIST,因為它小且快,即使在CPU上也能完成訓練。我們將建立一組DNN模型,並將其發展進化為N個步驟。

我們所講的「進化」主題實際上就是「物競天擇」的實施,完整的高水平「進化演算法」如下所示:

new_population =

while size(new_population) choose k(tournament) individuals from the population at random

choose the best from pool/tournament with probability p1

choose the second best individual with probability p2

choose the third best individual with probability p3

mutate and append selected to the new_population

附註:當涉及到架構合併時,跨界問題就變得相當複雜了。究竟該如何將兩個親本的架構合併呢?缺陷圖樣及環境整合訓練將對此產生什麼影響呢?近期的一篇來自Miikkulainen等人的論文提出了一種被稱為CoDeepNEAT的解決方案。基於Evolino進化理論,一個架構由部分單元模塊組成,其中的每一單元模塊都是服從於進化理論的。這個架構是一個合併了所有組成成分的理想藍圖。在這樣的情境下,將親本的組成成分混合是十分合理的,因為其成分是一個完整的微型網路。為了使文章更簡潔易懂,我在這個演算法實施過程中避開了跨界交叉的問題,而是簡單介紹了類似NEAT(或CoDeepNEAT)這樣的解決方案。(我打算在下一篇文章中詳細介紹這些解決方案。)

基本的構件

我們需要定義的第一件事情就是每一模型的解決方案空間,每一個個體都代表著一個架構。簡潔起見,我們堆疊了n層,每一層都包含三個參數:a)隱藏單元的數量;b)激活類型;c)丟失率。對於通用參數,我們在不同的優化器、學習率、權重衰減和層數量中進行選擇。

# definition of a space

# lower bound - upper bound, type param, mutation rate

LAYER_SPACE = dict

LAYER_SPACE["nb_units"] = (128, 1024, "int", 0.15)

LAYER_SPACE["dropout_rate"] = (0.0, 0.7, "float", 0.2)

LAYER_SPACE["activation"] =

(0, ["linear", "tanh", "relu", "sigmoid", "elu"], "list", 0.2)

NET_SPACE = dict

NET_SPACE["nb_layers"] = (1, 3, "int", 0.15)

NET_SPACE["lr"] = (0.0001, 0.1, "float", 0.15)

NET_SPACE["weight_decay"] = (0.00001, 0.0004, "float", 0.2)

NET_SPACE["optimizer"] =

(0, ["sgd", "adam", "adadelta", "rmsprop"], "list", 0.2)

完成以上操作以後,我們已經定義了模型的空間。接著我們還需要建立三個基本功能:

隨機選擇一個網路

def random_value(space):

"""Sample random value from the given space."""

val = None

if space[2] == "int":

val = random.randint(space[0], space[1])

if space[2] == "list":

val = random.sample(space[1], 1)[0]

if space[2] == "float":

val = ((space[1] - space[0]) * random.random) + space[0]

return {"val": val, "id": random.randint(0, 2**10)}

def randomize_network(bounded=True):

"""Create a random network."""

global NET_SPACE, LAYER_SPACE

net = dict

for k in NET_SPACE.keys:

net[k] = random_value(NET_SPACE[k])

if bounded:

net["nb_layers"]["val"] = min(net["nb_layers"]["val"], 1)

layers =

for i in range(net["nb_layers"]["val"]):

layer = dict

for k in LAYER_SPACE.keys:

layer[k] = random_value(LAYER_SPACE[k])

layers.append(layer)

net["layers"] = layers

return net

首先,我們任意地對層數量和每一層的參數進行採樣,樣本值會在預先定義好的範圍邊緣內出現下降。在初始化一個參數的同時,我們還會產生一個任意的參數id。現在它還不能使用,但我們可以追蹤所有的層。當一個新的模型發生突變時,舊的層會進行微調,同時僅對發生突變的層進行初始化。這樣的做法應該能夠顯著地加快速度,並穩定解決方案。

注意:根據問題性質的不同,我們可能需要不同的限制條件,比如參數的總量或層的總數量。

使網路發生變異

def mutate_net(net):

"""Mutate a network."""

global NET_SPACE, LAYER_SPACE

# mutate optimizer

for k in ["lr", "weight_decay", "optimizer"]:

if random.random net[k] = random_value(NET_SPACE[k])

# mutate layers

for layer in net["layers"]:

for k in LAYER_SPACE.keys:

if random.random layer[k] = random_value(LAYER_SPACE[k])

# mutate number of layers -- RANDOMLY ADD

if random.random if net["nb_layers"]["val"] if random.random layer = dict

for k in LAYER_SPACE.keys:

layer[k] = random_value(LAYER_SPACE[k])

net["layers"].append(layer)

# value & id update

net["nb_layers"]["val"] = len(net["layers"])

net["nb_layers"]["id"] +=1

else:

if net["nb_layers"]["val"] > 1:

net["layers"].pop

net["nb_layers"]["val"] = len(net["layers"])

net["nb_layers"]["id"] -=1

return net

每一個網路元素都存在變異的可能性,每一次變異都將重新採樣參數空間,進而使參數發生變化。

建立網路

class CustomModel:

def __init__(self, build_info, CUDA=True):

previous_units = 28 * 28

self.model = nn.Sequential

self.model.add_module("flatten", Flatten)

for i, layer_info in enumerate(build_info["layers"]):

i = str(i)

self.model.add_module(

"fc_" + i,

nn.Linear(previous_units, layer_info["nb_units"]["val"])

)

self.model.add_module(

"dropout_" + i,

nn.Dropout(p=layer_info["dropout_rate"]["val"])

)

if layer_info["activation"]["val"] == "tanh":

self.model.add_module(

"tanh_"+i,

nn.Tanh

)

if layer_info["activation"]["val"] == "relu":

self.model.add_module(

"relu_"+i,

nn.ReLU

)

if layer_info["activation"]["val"] == "sigmoid":

self.model.add_module(

"sigm_"+i,

nn.Sigmoid

)

if layer_info["activation"]["val"] == "elu":

self.model.add_module(

"elu_"+i,

nn.ELU

)

previous_units = layer_info["nb_units"]["val"]

self.model.add_module(

"classification_layer",

nn.Linear(previous_units, 10)

)

self.model.add_module("sofmax", nn.LogSoftmax)

self.model.cpu

if build_info["optimizer"]["val"] == "adam":

optimizer = optim.Adam(self.model.parameters,

lr=build_info["weight_decay"]["val"],

weight_decay=build_info["weight_decay"]["val"])

elif build_info["optimizer"]["val"] == "adadelta":

optimizer = optim.Adadelta(self.model.parameters,

elif build_info["optimizer"]["val"] == "rmsprop":

optimizer = optim.RMSprop(self.model.parameters,

optimizer = optim.SGD(self.model.parameters,

lr=build_info["weight_decay"]["val"],

weight_decay=build_info["weight_decay"]["val"],

momentum=0.9)

self.optimizer = optimizer

self.cuda = False

if CUDA:

self.model.cuda

self.cuda = True

上面的類別將會實例化模型的「基因組」。

現在,我們已經具備了建立一個任意網路、變更其架構並對其進行訓練的基本構件,那麼接下來的步驟就是建立「遺傳演算法」,「遺傳演算法」將會對最佳個體進行選擇和變異。每個模型的訓練都是獨立進行的,不需要其他有機體的任何信息。這就使得優化過程可以隨著可用的處理節點進行線性擴展。

GP優化器的編碼

"""Genetic programming algorithms."""

from __future__ import absolute_import

import random

import numpy as np

from operator import itemgetter

import torch.multiprocessing as mp

from net_builder import randomize_network

import copy

from worker import CustomWorker, Scheduler

class TournamentOptimizer:

"""Define a tournament play selection process."""

def __init__(self, population_sz, init_fn, mutate_fn, nb_workers=2, use_cuda=True):

"""

Initialize optimizer.

params::

init_fn: initialize a model

mutate_fn: mutate function - mutates a model

nb_workers: number of workers

"""

self.init_fn = init_fn

self.mutate_fn = mutate_fn

self.nb_workers = nb_workers

self.use_cuda = use_cuda

# population

self.population_sz = population_sz

self.population = [init_fn() for i in range(population_sz)]

self.evaluations = np.zeros(population_sz)

# book keeping

self.elite =

self.stats =

self.history =

def step(self):

"""Tournament evolution step."""

print("
Population sample:")

for i in range(0,self.population_sz,2):

print(self.population[i]["nb_layers"],

self.population[i]["layers"][0]["nb_units"])

self.evaluate

children =

print("
Population mean:{} max:{}".format(

np.mean(self.evaluations), np.max(self.evaluations)))

n_elite = 2

sorted_pop = np.argsort(self.evaluations)[::-1]

elite = sorted_pop[:n_elite]

# print top@n_elite scores

# elites always included in the next population

self.elite =

print("
Top performers:")

for i,e in enumerate(elite):

self.elite.append((self.evaluations[e], self.population[e]))

print("{}-score:{}".format( str(i), self.evaluations[e]))

children.append(self.population[e])

# tournament probabilities:

# first p

# second p*(1-p)

# third p*((1-p)^2)

# etc...

p = 0.85 # winner probability

tournament_size = 3

probs = [p*((1-p)**i) for i in range(tournament_size-1)]

# a little trick to certify that probs is adding up to 1.0

probs.append(1-np.sum(probs))

while len(children) pop = range(len(self.population))

sel_k = random.sample(pop, k=tournament_size)

fitness_k = list(np.array(self.evaluations)[sel_k])

selected = zip(sel_k, fitness_k)

rank = sorted(selected, key=itemgetter(1), reverse=True)

pick = np.random.choice(tournament_size, size=1, p=probs)[0]

best = rank[pick][0]

model = self.mutate_fn(self.population[best])

children.append(model)

self.population = children

# if we want to do a completely completely random search per epoch

# self.population = [randomize_network(bounded=False) for i in range(self.population_sz) ]

def evaluate(self):

"""evaluate the models."""

workerids = range(self.nb_workers)

workerpool = Scheduler(workerids, self.use_cuda )

self.population, returns = workerpool.start(self.population)

self.evaluations = returns

self.stats.append(copy.deepcopy(returns))

self.history.append(copy.deepcopy(self.population))

「進化演算法」看起來非常簡單,對嗎?沒錯!這個演算法可以非常成功,尤其是當你為個體定義了好的變異或跨界功能時。

存儲庫中還包含了一些額外的使用類別,比如工作器類和調度器類,使GP優化器能夠獨立平行地完成模型訓練和評估。

運行代碼

按照上述步驟操作運行。

"""Tournament play experiment."""

from __future__ import absolute_import

import net_builder

import gp

import cPickle

# Use cuda ?

CUDA_ = True

if __name__=="__main__":

# setup a tournament!

nb_evolution_steps = 10

tournament =

gp.TournamentOptimizer(

population_sz=50,

init_fn=net_builder.randomize_network,

mutate_fn=net_builder.mutate_net,

nb_workers=3,

use_cuda=True)

for i in range(nb_evolution_steps):

print("
Evolution step:{}".format(i))

print("================")

tournament.step

# keep track of the experiment results & corresponding architectures

name = "tourney_{}".format(i)

cPickle.dump(tournament.stats, open(name + ".stats","wb"))

cPickle.dump(tournament.history, open(name +".pop","wb"))

接下來,讓我們一起來看看運行的結果!

這是50個解決方案的得分結果,比賽規模為3。這些模型僅接受了10000個樣本的訓練,然後就被評估了。乍一看,進化演算法似乎並沒有起到太大的作用,因為解決方案在第一次進化中就已經接近最佳狀態了;而在第七階段,解決方案達到了它的最佳表現。在下圖中,我們用了一個盒式圖來依次描述這些解決方案的四分之一。我們發現,大多數方案都表現的很好,但在方案進化的同時,這個盒式圖也隨之緊縮了。

如何用自動機器學習實現神經網路進化

如何用自動機器學習實現神經網路進化

左邊:方案的分布;右邊:每一階段方案的盒式圖。

圖中的這個盒子展示了方案的四分之一,而其盒須則延伸展示了剩餘四分之三的方案分布。其中的黑點代表著方案的平均值,從圖中我們會發現平均值的上升趨勢。

如何用自動機器學習實現神經網路進化

如何用自動機器學習實現神經網路進化

不同的進化運行方式

為了進一步理解這一方法的性能和表現,我們最好將其與一個完全隨機的種群搜做相比較。每個階段之間都不需要進化,每個解決方案都要被重新設置為一個隨機的狀態。

如何用自動機器學習實現神經網路進化

如何用自動機器學習實現神經網路進化

上邊:方案的分布;下邊:每一步隨機生成的的方案盒式圖

在一個相對較小的(93.66% vs 93.22%)里進化演算法的性能較好。而隨機種群搜索似乎生成了一些好的解決方案,但模型的方差卻大大增加了。這就意味著在搜索次優架構的時候出現了資源浪費。將這個與進化圖相比較,我們會發現進化確實生成了更多有用的解決方案,它成功地使那些結構進化了,進而使之達到了更好的性能表現。

  • MNIST是一個相當簡單的數據集,即使是單層網路也能達到很高的準確度。

  • 像ADAM這樣的優化器對學習率的敏感度比較低,只有在它們的網路具備足夠的參數時,它們才能找到比較好的解決方案。

  • 在訓練過程中,模型只會查看10000個(訓練總數據的1/5)樣本示例。如果我們訓練得時間再長一些,好的架構可能會達到更高的準確度。

  • 限制樣本數量對於我們學習的層的數量同樣非常重要,越深層的模型需要越多樣本。為了解決這個問題,我們還增加了一個移除突變層,使種群調節層的數量。

這個實驗的規模還不足以突出這種方法的優勢,這些文章中使用的實驗規模更大,數據集也更複雜。

我們剛剛完成了一個簡單的進化演算法,這個演算法很好地詮釋了「物競天擇」的主題。我們的演算法只會選擇最終勝利的解決方案,然後將其變異來產生更多的後代。接下來,我們需要做的就是使用更先進的方法,生成和發展方案群。以下是一些改進的建議:

  • 為通用層重新使用親本的權重

  • 將來自兩個潛在親本的層合併

  • 架構不一定要連續的,你可以探索層與層之間更多不一樣的聯繫(分散或合併等)

  • 在頂部增加額外的層,然後進行微調整。

以上內容都是人工智慧研究領域的一個課題。其中一個比較受歡迎的方法就是NEAT及其擴展。EAT變數使用進化演算法在開發網路的同時,還對網路的權重進行了設置。在一個典型的強化學習場景下,代理權重的進化是非常有可能實現的。但是,當(x,y)輸入對可用時,梯度下降的方法則表現得更好。

相關文章

Evolino: Hybrid Neuroevolution / Optimal Linear Search for Sequence Learning

Evolving Deep Neural Networks— This is a very interesting approach of co-evolving whole networks and blocks within the network, it』s very similar to the Evolino method but for CNNs.

Large-Scale Evolution of Image Classifiers

Convolution by Evolution

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 雷鋒網 的精彩文章:

蘇寧:快遞開放是國策 有對手違背了;飛機安檢需拿出 iPad 和 MacBook | 雷鋒早報
CVPR 2017大會主席張正友博士:做研究要靜得下心,沉得住氣|CVPR 2017
HTC VIVE宣布推出中國版VIVE一體機
Google新成立AI工作室,尤其要解決數據與人才問題
吳恩達:比起爭論AI會否超越人類,倒不如先擔心你的飯碗能否保住

TAG:雷鋒網 |

您可能感興趣

神經網路進化能否改變機器學習?
機器學習與神經網路
機器學習—神經網路
加強神經網路 動物和人類實現零障礙交流
如何提高自組織機器人的「協同能力」?專家提出「進化」神經網路控制器
從腦波到機器人運動,使用深度神經網路對大腦活動進行解碼
以線蟲為模型模擬的神經網路,讓機器人無需訓練即可自動避開障礙物
神經網路重正化群研究取得進展
使用機器學習神經網路預測電影利潤
用於分層強化學習的隨機神經網路
用Keras 實現的神經網路機器翻譯
科研人員基於憶耦器實現神經突觸可塑性和神經網路模擬
機器學習中的人工神經網路
超酷的神經網路合成動物運動動畫,解救動畫師!代碼開源+視頻
在機器學習中經常使用的6種人工神經網路
邊緣運算盼來專用晶片神經網路加速器進駐行動終端
輕鬆機器學習-神經網路的歷史
自己動手寫深度學習模型之全連接神經網路
跆拳道運動能促進神經系統的發育!
神經衝動