Pybrain的安装
$ git clone git://github.com/pybrain/pybrain.git
$ python setup.py install
详细信息参看http://wiki.github.com/pybrain/pybrain/installation.
快速入门
神经网络是由模块(module)组成并且由连接组成,你可以把它看成是一个无环图,模块就是节点而边就是连接。创建网络的方式是:
>>> from pybrain.tools.shortcuts import buildNetwork
>>> net = buildNetwork(2, 3, 1)
返回一个双输入,三个隐藏层节点和单输出的神经元。这些层是模块对象并已形成全连接。这个网络已经由随机值初始化,我们可以使用activate方法计算输出,输入可以是一个列表,元组或者array:
>>> net.activate([2, 1])
array([-0.98646726])
神经网络的每个部分都有一个名称,你可以直接访问,用buildNetWork建立的时候,每个部分会自动命名:
>>> net['in']
<LinearLayer 'in'>
>>> net['hidden0']
<SigmoidLayer 'hidden0'>
>>> net['out']
<LinearLayer 'out'>
隐藏层神经元的名称后缀是数字,用于区分。当然我们更自由建立网络,比如默认情况下用的激活函数是sigmoid,但是在很多情况下,我们使用其他的函数:
>>> from pybrain.structure import TanhLayer
>>> net = buildNetwork(2, 3, 1, hiddenclass=TanhLayer)
>>> net['hidden0']
<TanhLayer 'hidden0'>
也可以为输出层设置不同的激活函数:
>>> from pybrain.structure import SoftmaxLayer
>>> net = buildNetwork(2, 3, 2, hiddenclass=TanhLayer, outclass=SoftmaxLayer)
>>> net.activate((2, 3))
array([ 0.6656323, 0.3343677])
我们还可以使用偏置因子:
>>> net = buildNetwork(2, 3, 1, bias=True)
>>> net['bias']
<BiasUnit 'bias'>
这些方式存在局限性,比如只能建立前向的拓扑结构,但是我们可以用pybrain建立精细的结构。
定义数据集
SupervisedDataSet类用于标准的监督学习。它支持输入值和目标值(target),数据大小需要在创建类时指定:
>>> from pybrain.datasets import SupervisedDataSet
>>> ds = SupervisedDataSet(2, 1)
我们产生了具有双输入和单输出的数据集。
一个经典的例子就是XOR函数,我们为数据集加入数据:
>>> ds.addSample((0, 0), (0,))
>>> ds.addSample((0, 1), (1,))
>>> ds.addSample((1, 0), (1,))
>>> ds.addSample((1, 1), (0,))
查看ds中的样本数量:
>>> len(ds)
4
我们可以迭代输出
>>> for inpt, target in ds:
... print inpt, target
...
[ 0. 0.] [ 0.]
[ 0. 1.] [ 1.]
[ 1. 0.] [ 1.]
[ 1. 1.] [ 0.]
可以将输入和目标值分别输出为array
>>> ds['input']
array([[ 0., 0.],
[ 0., 1.],
[ 1., 0.],
[ 1., 1.]])
>>> ds['target']
array([[ 0.],
[ 1.],
[ 1.],
[ 0.]])
清除数据集dataset
>>> ds.clear()
>>> ds['input']
array([], shape=(0, 2), dtype=float64)
>>> ds['target']
array([], shape=(0, 1), dtype=float64)
训练神经网络
为了在有监督学习中调节参数,pybrain提出了trainer的概念,trainer训练模块使其拟合数据集。一个经典的训练就是反向传播:
>>> from pybrain.supervised.trainers import BackpropTrainer
我们使用建立的XOR数据集去训练网络:
>>> net = buildNetwork(2, 3, 1, bias=True, hiddenclass=TanhLayer)
>>> trainer = BackpropTrainer(net, ds)
现在trainer知道了网络和数据集,就可以在这个数据集上开始训练网络:
>>> trainer.train()
0.31516384514375834
这个命令调用train方法训练一个完整周期,并返回一个double类型的误差。要想训练至收敛,可以调用下面的方法:
>>> trainer.trainUntilConvergence()
返回每个训练周期的误差元组。
使用模块和连接建立网络
我们从基础开始介绍使用最基本的结构元素,FeedForward,RecurrentNetwork类,Module类以及Connetion类。
Feed Forward Networks
我们建立一个多层感知器:
首先建立一个对象
>>> from pybrain.structure import FeedForwardNetwork
>>> n = FeedForwardNetwork()
然后建立输入层,隐藏层和中间层。
>>> from pybrain.structure import LinearLayer, SigmoidLayer
>>> inLayer = LinearLayer(2)
>>> hiddenLayer = SigmoidLayer(3)
>>> outLayer = LinearLayer(1)
这里有很多类型的层,详细可以查看module的包。
我们把这个层加入到网络中。
>>> n.addInputModule(inLayer)
>>> n.addModule(hiddenLayer)
>>> n.addOutputModule(outLayer)
实际上我们可以加入多层的输入和输出的模块,网络已经知道哪些是输出哪些是输入,这样就可以使输入值前向传播,误差值反向传播。
我们还需要指定连接方式,我们使用最常见的连接方式,层间全连接,就是将一层的每个神经元和另一层的每个神经元相连接。这个可以由FullConnection类实现:
>>> from pybrain.structure import FullConnection
>>> in_to_hidden = FullConnection(inLayer, hiddenLayer)
>>> hidden_to_out = FullConnection(hiddenLayer, outLayer)
和模块一样,也把它们加入到网络中:
>>> n.addConnection(in_to_hidden)
>>> n.addConnection(hidden_to_out)
所有元素就位了,让网络可用,调用sortModules()方法:
>>> n.sortModules()
网络内部初始化,这个在使用前的一步很关键,比如让模块按照拓扑结构排列。
测试一个网络:
输出网络并检查它们的结构:
>>> print n
FeedForwardNetwork-6
Modules:
[<LinearLayer 'LinearLayer-3'>, <SigmoidLayer 'SigmoidLayer-7'>, <LinearLayer 'LinearLayer-8'>]
Connections:
[<FullConnection 'FullConnection-4': 'LinearLayer-3' -> 'SigmoidLayer-7'>, <FullConnection 'FullConnection-5': 'SigmoidLayer-7' -> 'LinearLayer-8'>]
使用这个网络的方法是调用activate方法预测结果:
>>> n.activate([1, 2])
array([-0.11302355])
由于权重初始化时随机赋值,所以在不同的机子上的结果不同。我们可以查看连接的参数:
>>> in_to_hidden.params
array([ 1.37751406, 1.39320901, -0.24052686, -0.67970042, -0.5999425 , -1.27774679])
>>> hidden_to_out.params
array([-0.32156782, 1.09338421, 0.48784924])
封装模块的网络参数:
>>> n.params
array([ 1.37751406, 1.39320901, -0.24052686, -0.67970042, -0.5999425,
-1.27774679, -0.32156782, 1.09338421, 0.48784924])
命名网络结构
网络的零件在产生的时候就被自动命名,你可以在建立对象时传递一个name参数:
>>> LinearLayer(2)
<LinearLayer 'LinearLayer-11'>
>>> LinearLayer(2, name="foo")
<LinearLayer 'foo'>
这样使输出更具可读性,并且名字可以在运行过程中保留。
循环神经网络
和前向传播类似的,
>>> from pybrain.structure import RecurrentNetwork
>>> n = RecurrentNetwork()
和上述的例子相似:
>>> n.addInputModule(LinearLayer(2, name='in'))
>>> n.addModule(SigmoidLayer(3, name='hidden'))
>>> n.addOutputModule(LinearLayer(1, name='out'))
>>> n.addConnection(FullConnection(n['in'], n['hidden'], name='c1'))
>>> n.addConnection(FullConnection(n['hidden'], n['out'], name='c2'))
RecurrentNetwork类具有一个额外的方法,.addRecurrentConnection()在每个timestep后及时回顾,我们可以在隐藏层间加入:
>>> n.addRecurrentConnection(FullConnection(n['hidden'], n['hidden'], name='c3'))
激活网络后每次得到不同的输出
>>> n.sortModules()
>>> n.activate((2, 2))
array([-0.1959887])
>>> n.activate((2, 2))
array([-0.19623716])
>>> n.activate((2, 2))
array([-0.19675801])
我们可以清空网络的历史记录,采用reset方法:
>>> n.reset()
>>> n.activate((2, 2))
array([-0.1959887])
>>> n.activate((2, 2))
array([-0.19623716])
>>> n.activate((2, 2))
array([-0.19675801])
回到和初始建立时的一样。
使用前向传播网络分类
该教程带你建立分类数据集并训练一个神经网络并实现可视化。
首先导入必要的组件:
from pybrain.datasets import ClassificationDataSet
from pybrain.utilities import percentError
from pybrain.tools.shortcuts import buildNetwork
from pybrain.supervised.trainers import BackpropTrainer
from pybrain.structure.modules import SoftmaxLayer
可视化输出需要有pylab:
from pylab import ion, ioff, figure, draw, contourf, clf, show, hold, plot
from scipy import diag, arange, meshgrid, where
from numpy.random import multivariate_normal
为了可视化,我们产生了平面上的三类点,你也可以从文件中读取,比如使用
pylab.load()
means = [(-1,0),(2,4),(3,1)]
cov = [diag([1,1]), diag([0.5,1.2]), diag([1.5,0.7])]
alldata = ClassificationDataSet(2, 1, nb_classes=3)
for n in xrange(400):
for klass in range(3):
input = multivariate_normal(means[klass],cov[klass])
alldata.addSample(input, [klass])
随机将数据集的75%划分给训练集,25%给测试:
tstdata, trndata = alldata.splitWithProportion( 0.25 )
针对神经网络分类,建议每个类别一个输出。
trndata._convertToOneOfMany( )
tstdata._convertToOneOfMany( )
打印出一些关于数据集的信息:
print "Number of training patterns: ", len(trndata)
print "Input and output dimensions: ", trndata.indim, trndata.outdim
print "First sample (input, target, class):"
print trndata['input'][0], trndata['target'][0], trndata['class'][0]
现在我们使用buildNetwork()建立含有5个隐藏单元的神经网络。输入层和输出层必须和数据集的输入和目标值维数匹配。你可以加入更多的隐藏节点。
fnn = buildNetwork( trndata.indim, 5, trndata.outdim, outclass=SoftmaxLayer )
建立一个训练者,训练者需要网络和训练数据作为输入。还有其他的训练者可以查看trainers,我们这里使用BackpropTrainer.
trainer = BackpropTrainer(fnn, dataset=trndata, momentum=0.1, verbose=True, weightdecay=0.01)
产生一个数据点的方形网络并形成一个数据集,然后分类得到等高线图。因此这里的目标值忽略。
ticks = arange(-3.,6.,0.2)
X, Y = meshgrid(ticks, ticks)
need column vectors in dataset, not arrays
griddata = ClassificationDataSet(2,1, nb_classes=3)
for i in xrange(X.size):
griddata.addSample([X.ravel()[i],Y.ravel()[i]], [0])
griddata._convertToOneOfMany() # this is still needed to make the fnn feel comfy
开始迭代训练神经网络。
for i in range(20):
trainer.trainEpochs( 1 )
评价神经网络在训练集和测试集上的表现,这里有几种方式,检查pybrain.tools.validation模块,我们可以让训练者进行测试。
trnresult = percentError( trainer.testOnClassData(),trndata['class'] )
tstresult = percentError( trainer.testOnClassData(dataset=tstdata ),tstdata['class'] )
print "epoch: %4d" % trainer.totalepochs, " train error: %5.2f%%" % trnresult, \
" test error: %5.2f%%" % tstresult
我们通过FNN运行我们的网格数据,得到最可能的类别,并整理成一个方阵。
out = fnn.activateOnDataset(griddata)
out = out.argmax(axis=1) # the highest output activation gives the class
out = out.reshape(X.shape)
绘制等高线图:
figure(1)
ioff() # interactive graphics off
clf() # clear the plot
hold(True) # overplot on
for c in [0,1,2]:
here, _ = where(tstdata['class']==c)
plot(tstdata['input'][here,0],tstdata['input'][here,1],'o')
if out.max()!=out.min(): # safety check against flat field
contourf(X, Y, out) # plot the contour
ion() # interactive graphics on
draw() # update the plot
ioff()
show()
使用数据集
在pybrain中提供了一种更高等的数据结构,便于数据的处理。对于机器学习中的不同任务,不同的数据集类型具有共同的特征。一个数据集可以看成是2维的数组,本文中称为“字段(field)”。如果DS实现DataSet:
inp = DS['input']
返回输入字段,该字段的最后一维与输入维数一致。比如:
inp[0,:]
获取到第一个输入向量,通常还有一个“target”字段。通常情况下你需要迭代一个数据集类似这样:
for inp, targ in DS:
...
注意不论你得到一个或者多个样本行,依赖于数据集中的连接字段的数量,这些字段含有相同数量的样本并假定是同时使用的,就像上述的“input”和“target”字段。类似的,数据集可以逐一加入样本(清楚但是缓慢的做法),或者从数组引入。
for inp, targ in samples:
DS.appendLinked(inp, targ)
...
or alternatively, with ia and ta being arrays:
assert(ia.shape[0] == ta.shape[0])
DS.setField(‘input’, ia)
DS.setField(‘target’, ta)
在后一个案例,DS不能检查连接数组的维度,否则无法从scratch中建立数据集。你可以把自己连接或者未连接的数据加入到数据集中,虽然许多训练算法在连接字段上迭代,但是如果它们的数量发生改变可能会发生错误:
DS.addField('myfield')
DS.setField('myfield', myarray)
DS.linkFields('input','target','myfield') # must provide complete list here
一个快速有效分割出训练集和测试集的方法是:
>>> len(DS)
100
>>> TrainDS, TestDS = DS.splitWithProportion(0.8)
>>> len(TrainDS), len(TestDS)
(80, 20)
监督回归训练用的数据集
“有监督回归训练数据集”最简单的形式是用于监督训练任务。这由字段”input”和“target”组成。它的大小是这样指定的:
>>> from pybrain.datasets import SupervisedDataSet
>>> DS = SupervisedDataSet( 3, 2 )
>>> DS.appendLinked( [1,2,3], [4,5] )
>>> len(DS)
1
>>> DS['input']
array([[ 1., 2., 3.]])
数据集中引入了序列的概念,在序列学习的任务中,它的形式被划分为各种长度的子序列。可以通过以下的方法获得:
getNumSequences()
getSequence(index)
getSequenceLength(index)
创建一个序列化的数据集,它依旧含有”input”和“target”字段。Sequentialdataset继承了SupervisedDataSet,这是一个带有序列长度的特例。为了填充数据集中的内容,我们可以在每个要存储的序列的开始使用newSequence()并用appendLinked()加入样式。我们可以在理论上由数组建立序列化的数据集,但是不要与索引字段向混淆。
一个典型的在序列化的数据集DS上的迭代方式是:
for i in range(DS.getNumSequences):
for input, target in DS.getSequenceIterator(i):
# do stuff
用于有监督分类的训练
该数据集用于处理分类问题,它的“target”定义为integer。它有一个额外的字段叫做“class”,这是对”target”自动打包,大部分情况下你用不到它。初始化数据集:
DS = ClassificationDataSet(inputdim, nb_classes=2, class_labels=[‘Fish’,’Chips’])
Labels是可选的,主要用于注释,目标数组应是一维的。目标值标签从0开始。如果你弄不明白标签有多少个,或者不明白setField()方法,可以使用assignClasses()或者calculateStatistics()得到类别的统计信息。
>>> DS = ClassificationDataSet(2, class_labels=['Urd', 'Verdandi', 'Skuld'])
>>> DS.appendLinked([ 0.1, 0.5 ] , [0])
>>> DS.appendLinked([ 1.2, 1.2 ] , [1])
>>> DS.appendLinked([ 1.4, 1.6 ] , [1])
>>> DS.appendLinked([ 1.6, 1.8 ] , [1])
>>> DS.appendLinked([ 0.10, 0.80 ] , [2])
>>> DS.appendLinked([ 0.20, 0.90 ] , [2])
>>> DS.calculateStatistics()
{0: 1, 1: 3, 2: 2}
>>> print DS.classHist
{0: 1, 1: 3, 2: 2}
>>> print DS.nClasses
3
>>> print DS.getClass(1)
Verdandi
>>> print DS.getField('target').transpose()
[[0 1 1 1 2 2]]
在分类时,如果类别是每类一个输出单元,很多算法会表现更好。ClassificationDataSet提供方法将输出的结果转化类标签数字
>>> DS._convertToOneOfMany(bounds=[0, 1])
>>> print DS.getField('target')
[[1 0 0]
[0 1 0]
[0 1 0]
[0 1 0]
[0 0 1]
[0 0 1]]
>>> print DS.getField('class').transpose()
[[0 1 1 1 2 2]]
>>> DS._convertToClassNb()
>>> print DS.getField('target').transpose()
[[0 1 1 1 2 2]]
对应在序列化的分类中也有SequenceClassificationDataSet,融合了类的特征和Sequentialdataset。
黑盒优化Black-box Optimization
许多现实的问题可以看做是一个优化问题,比如搜索控制器(controller)最优的设置,最小化投资组合的风险,寻找游戏中的最佳策略等等。它涉及变量的数量,也就是问题的维度,每个变量都从集合中选取并最大(最小)化目标函数。
连续域优化
我们定义一个连续域的简单目标函数,比如平方和:
>>> def objF(x): return sum(x**2)
并给出起始位点:
>>> x0 = array([2.1, -1])
我们初始化一个优化算法,比如CMAES:
>>> from pybrain.optimization import CMAES
>>> l = CMAES(objF, x0)
所有算法默认参数是最大化目标函数,你可以利用minimize属性修改设定:
>>> l.minimize = True
我们创建对象时可以写为:CMAES(objF, x0, minimize = True)
停止标准可以在算法中指定,也可以在下列三个中指定:
maximal number of evaluations
maximal number of learning steps
reaching a desired value
>>> l.minimize = True
>>> l.maxEvaluations = 200
现在我们的优化程序已经建立,我们使用learn()方法学习,该方法努力优化变量直到满足终止条件,并返回一个参数的元组以及拟合的结果。
>>> l.learn()
(array([ -1.59778097e-05, -1.14434779e-03]), 1.3097871509722648e-06)
广义优化:使用Evolvable*
我们优化的方式是在一般情况下让用户定义一个Evolvable并实现以下方法:
Copy()操作:
产生随机点的方法randomize()
Mutate()在搜索空间中移动一小步
Crossover()交叉操作产生同类。
优化算法由Evolvable的实例初始化,并用目标函数评估这个实例。
我们给出一个简单的示例:
>>> from random import random
>>> from pybrain.structure.evolvables.evolvable import Evolvable
>>> class SimpleEvo(Evolvable):
... def __init__(self, x): self.x = max(0, min(x, 10))
... def mutate(self): self.x = max(0, min(self.x + random() - 0.3, 10))
... def copy(self): return SimpleEvo(self.x)
... def randomize(self): self.x = 10*random()
... def __repr__(self): return '<-%.2f->'+str(self.x)
这可以用HillClimer优化:
>>> from pybrain.optimization import HillClimber
>>> x0 = SimpleEvo(1.2)
>>> l = HillClimber(lambda x: x.x, x0, maxEvaluations = 50)
>>> l.learn()
(<-10.00->, 10)