1. RAVQ
A RAVQ is an alternative to SOMs. A RAVQ can dynamically create new model vectors to describe novel stable input sequences.
Check the Pyro CVS Repository for the most recent version of ravq.py. http://compscitest.brynmawr.edu/cgi-bin/viewcvs.cgi/pyrobot/brain/ravq.py.
The RAVQ has three parameters. The first is a window size. This determines how many previous inputs will be used to generate the moving average. The second is an epsilon value. If the moving average is not within epsilon (by the metric found in references 1 and 2) of the set of inputs currently in the buffer that moving average is disqualified from being a model vector. The moving average does not reflect a 'typical' input given the window size. This indicates that the inputs are volatile. The third is a delta value. If the moving average is not at least delta from all existing model vectors, it is disqualified from being a model vector. The moving average in this case does not represent something 'new'. In an ARAVQ, there is an additional parameter, alpha, which specifies the learning rate. The ARAVQ algorithm updates existing winning model vectors to better match current inputs.
The ARAVQ and RAVQ algorithms don't specify a neighborhood that can be used for updating purposes as in Kohonen's SOM algorithm. Distance relationships among the model vectors can be calculated. This implementation currently supports a distance map showing the distance between any two model vectors. As with any set of highly dimensional data points, certain techniques can be used to capture the most important correlative features of the data. I am currently working on finding principle component analysis and other statistical tools available in Python to add to the functionality of the current RAVQ implementation.
Some thoughts: The delta and epsilon values depend on the scale and size of the vectors being quantized. Epsilon is also related to buffer size. Is there a way to make these relationships explicit? I've added a feature which fixes the number of model vectors. Would is be possible to train up a set of model vectors and then somehow initialize a SOM of the given size?
I've implemented an additional experimental version of the RAVQ that determines the winner based on the current input to the RAVQ. This differs from the previous approach in that the winner was previously determined using the moving average.
The most current RAVQ designs used in the Governor architecture:
Figure a shows the RAVQ with input histories associated with each model vector. Every time a model vector wins, the current input vector is stored in that model vector's history buffer. Figure b shows the ravq with a critical input history. The critical input history stores the inputs at the time of a change in the model vector winner. We consider these critical events in the input space, and would work well as training vectors for a network. Figure c shows the critical segment history, an adaptation of the critical input history to capture entire segments of the input sequence surrounding a change in the model vector.
A sample RAVQ brain is available in the Pyro CVS Repository. http://compscitest.brynmawr.edu/cgi-bin/viewcvs.cgi/pyrobot/plugins/brains/RAVQ.py.
Here is the most current version of the experiment I am running. I am attempting to duplicate Nolfi and Tani's results using the techniques of the RAVQ as described in Linaker and Niklasson within the pyro framework. The current implementation uses a ravq to classify both the scaled input vectors and the trained hidden layer activations of a prediction (motor values only) network. The robot is controlled by a prebuilt wall following algorithm. It is hoped that the trained network will be able to control the robot as well.
The current brain (be sure to change the paths!):
| 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
|
# imported modules
from pyrobot.brain import Brain
from pyrobot.brain.conx import *
from pyrobot.gui.plot.scatter import *
from pyrobot.gui.plot.hinton import *
import pyrobot.brain.ravq
import os
import time
import sys
# log file directories
rootDirectory = "/home/stober/ravq/experiments/"
currentExperiment = "Nolfi_RAVQ_Long/"
currentBrain = "/home/stober/ravq/Nolfi_Ravq.py"
class RAVQBrain(Brain):
"""A brain that uses a RAVQ to classify hidden layer activations in a prediction network."""
def setup(self):
# robot parameters
self.robot.range.units = 'ROBOTS'
self.maxvalue = self.robot.range.getMaxvalue()
# status variables
self.verbosity = 1
self.direction = 1
self.blockedFront = 0
self.wasStalled = 0
self.counter = 0
self.previous = [0.0, 0.0]
# tweakable params
self.timer = 50000 # turns on hiddenRavq
self.stop = 100000
self.sleepTime = 0.1
self.backupTimer = 10000
# create network
self.net = SRN()
self.net.setSequenceType("ordered-continuous")
self.inSize = self.robot.range.count + 2
self.net.addLayers(self.inSize, self.inSize/2, 2)
# defaults - but here explicit
self.net.setBatch(0)
self.net.setInteractive(0)
self.net.setVerbosity(0)
self.net.setInitContext(0)
# initialize network
self.net.initialize()
# learning parameters
self.net.setEpsilon(0.2)
self.net.setMomentum(0.1)
self.net.setTolerance(0.05)
# set learning
self.net.setLearning(1)
# hidden ravq (tweakable parameters)
self.hiddenRavq = pyro.brain.ravq.ARAVQ(10, .2, .5, .03)
self.hiddenRavq.setHistory(0)
self.hiddenRavq.setAddModels(0)
self.hiddenRavq.setLearning(0)
# input ravq (tweakable parameters)
self.inputRavq = pyro.brain.ravq.ARAVQ(10, .2, .6, .03)
self.inputRavq.setHistory(0)
self.inputRavq.setAddModels(1)
self.inputRavq.setLearning(1)
# file IO
self.path = rootDirectory + currentExperiment
if(os.path.isfile(self.path + "exp.lock")):
raise "Lock error!"
else:
try:
os.mkdir(self.path)
except:
pass
lock = open(self.path + "exp.lock", "w")
lock.write("This file locks the experiment directory to prevent overwriting experimental data.")
lock.close()
# archive brain
os.system("cp " + currentBrain + " " + self.path + "archive.py")
self.netInfo = open(self.path + 'nn.dat', 'w')
self.hiddenRavq.openLog(self.path + 'hidden_history.log')
self.hiddenRavqInfo = open(self.path + 'hidden_ravq.dat', 'w')
self.inputRavq.openLog(self.path + 'input_history.log')
self.inputRavqInfo = open(self.path + 'input_ravq.dat', 'w')
self.repositionLog = open(self.path + 'reposition.dat', 'w')
# gui displays
self.plot1 = Scatter()
self.plot1.setTitle('Target: Translate x Rotate')
self.plot2 = Scatter()
self.plot2.setTitle('Activation: Translate x Rotate')
self.hidd = Hinton(self.inSize/2, title = 'Hidden Layer')
self.inputs = Hinton(self.inSize, title = 'Input Layer')
def destroy(self):
self.plot1.destroy()
self.plot2.destroy()
self.hidd.destroy()
self.inputs.destroy()
self.netInfo.close()
self.hiddenRavq.closeLog()
self.hiddenRavqInfo.close()
self.inputRavq.closeLog()
self.inputRavqInfo.close()
self.repositionLog.close()
del self.net
del self.hiddenRavq
del self.inputRavq
def scaleSensors(self, val):
"""From Robots (or anything) to [0, 1]"""
return (val / self.maxvalue)
def scaleMotors(self, val):
"""[-1, 1] to [0, 1]"""
return (val + 1) / 2.0
def kick(self):
"""How to get unstuck."""
self.wasStalled += 1
self.repositionLog.write("STALLED " + str(self.counter) + "\n")
self.move(0.1, 0.0)
time.sleep(.2)
if self.robot.stall:
self.move(-0.1, 0.0)
time.sleep(.2)
if self.robot.stall and self.wasStalled > 10:
self.repositionLog.write("Cannot fix stall at " + str(self.counter) + " " + \
str(self.robot.simulation[0].getPose("Pioneer")')) +
" so repositioning to (796, 2211, 92).\n")
# we'll call this the hand of god
self.set('robot/truth/pose', (796, 2211, 92))
else:
self.wasStalled = 0
def controller(self):
# tweakable parameters
minRange = 0.85
maxRange = 1.25
#rand = random.random() * 0.5
rand = 0.25
# important sensors
minFront = min([s.value for s in self.robot.range["front"]])
minLeft = min([s.value for s in self.robot.range["front-left"]])
minRight = min([s.value for s in self.robot.range["front-right"]])
left = min([s.value for s in self.robot.range["left"]])
right = min([s.value for s in self.robot.range["right"]])
# the decision algorithm
if minFront < minRange:
if not self.blockedFront:
if right < left:
self.direction = 1
else:
self.direction = -1
self.blockedFront = 1
return [0, self.direction * rand]
elif minLeft < minRange:
if self.blockedFront:
return [0, self.direction * rand]
else:
return [rand, -rand]
elif minLeft > maxRange:
if self.blockedFront:
return [0, self.direction * rand]
else:
return [rand, rand]
elif minRight < minRange:
if self.blockedFront:
return [0, self.direction * rand]
else:
return [rand, rand]
else:
self.blockedFront = 0
return [0.5, 0.0]
def backup(self, tag):
self.net.saveWeightsToFile(self.path + "predict" + str(tag) + ".wts")
self.inputRavq.saveRAVQToFile(self.path + "input_ravq" + str(tag) + ".pck")
self.hiddenRavq.saveRAVQToFile(self.path + "hidden_ravq" + str(tag) + ".pck")
def step(self):
"""
1. Determine new motor values.
2. Determine inputs (old sensors + old motors).
3. Step through with new motor values as targets.
4. Move robot.
5. New motor values become old motor values.
"""
# display count
if self.verbosity > 0: print self.counter
# switch between training network and training ravq
if self.counter == self.timer:
self.net.setLearning(0)
self.hiddenRavq.setAddModels(1)
self.hiddenRavq.setLearning(1)
self.net.saveNetworkToFile(self.path + "predict")
self.net.saveWeightsToFile(self.path + "predict.wts")
elif self.counter == self.stop:
self.hiddenRavq.logHistory(0, str(self.robot.simulation[0],getPose('Pioneer')))
self.hiddenRavqInfo.write(str(self.hiddenRavq))
self.hiddenRavq.saveRAVQToFile(self.path + "hidden_ravq.pck")
self.inputRavq.logHistory(0, str(self.robot.simulation[0].getPose('Pioneer')))
self.inputRavqInfo.write(str(self.inputRavq))
self.inputRavq.saveRAVQToFile(self.path + "input_ravq.pck")
self.pleaseStop()
self.destroy()
else:
# train network or ravq
motors = self.controller()
inputs = [self.scaleSensors(s.value) for s in self.robot.range["all"] + map(self.scaleMotors, self.previous)
(error, correct, total) = self.net.step( input = inputs, \
output = map(self.scaleMotors, motors))
self.inputRavq.input(inputs)
# hidden ravq
if self.counter > self.timer:
self.hiddenRavq.input(self.net['hidden'].activation)
self.hiddenRavq.logHistory(0, \
str(self.robot.simulation[0].getPose('Pioneer')))
# backup sometimes
if self.counter % self.backupTimer == 0:
self.backup(self.counter)
# other IO
self.inputRavq.logHistory(0, \
str(self.robot.simulation[0].getPose('Pioneer')))
self.netInfo.write(str(self.counter) + " " + str(error) + "\n")
self.plot1.addPoint(self.scaleMotors(motors[0]), self.scaleMotors(motors[1]))
self.plot2.addPoint(self.net['output'].activation[0], \
self.net['output'].activation[1])
self.hidd.update(self.net['hidden'].activation)
self.inputs.update(self.net['input'].activation)
if self.verbosity > 0: print (error, correct, total)
# move robot
self.move(motors[0], motors[1])
self.previous = motors
# kick if things get bad
if self.robot.stall:
self.kick()
# sleep and increment counter
time.sleep(self.sleepTime)
self.counter += 1
def INIT(engine):
return RAVQBrain('RAVQBrain', engine) |
|
The current world file:
# Desc: 1 robot with player, laser, sonar and gps
# CVS: $Id: nolfi2.world,v 1.1 2003/05/15 21:45:55 yeelin Exp $
# the resolution of Stage's raytrace model in meters
#
resolution 0.02
# GUI settings
#
gui
(
size [ 502.000 506.000 ]
origin [5.018 4.950 0]
scale 0.021 # the size of each bitmap pixel in meters
)
# load a bitmapped environment from a file
#
bitmap
(
file "nolfi2.pnm"
#resolution 0.1
#resolution .044
resolution 0.07
)
include "/usr/local/stage/worlds/usc_pioneer.inc"
# create a robot, setting its start position and Player port,
# and equipping it with a laser range scanner
#
#position
#(
# port 6665
# pose [1.0 1.0 20]
#laser()
#)
usc_pioneer
(
color "green"
name "robot"
port 6665
pose [.796 2.211 92]
#(1101, 1113, 48)
truth()
)
# coordinates are defined from the center of the box
#box ( size [0.75 0.75] color "blue" pose [2.5 3.5 0.000] sonar_return "visible" )
#box ( size [0.75 0.75] color "red" pose [2.5 1.5 0.000] sonar_return "visible" )
The nolfi2.pnm file can be found here: http://www.cs.swarthmore.edu/~stober/nolfi2.pnm
I ran a long experiment and the results are in. The first level is certainly a success in this limited context. The network learned the primitive behavior, and the hidden ravq classified the hidden layer activations into roughly three categories, corner, wall, and corridor. I'll continue tweaking the parameters to find out what other features can be abstracted from the hidden layer. I also need to figure out how to get level two to work. I'm thinking of labeling all model vectors from the ravq after training, so as to further compress the data.
Update: I ran an experiment to see how well the model vectors captured the basic behavior of the robot. The setup was as follows:
1. Propagate level one network using sensors + previous motors. 2. Classify hidden layer using RAVQ. 3. Copy RAVQ winner into hidden layer. 4. Propagate from the hidden layer. 5. Use the outputs of this second propagation as motor values.
I am also training an L2 network that would predict the sequence of RAVQ winners. Then the level 2 predictions can determine what model vectors are copied into the hidden layer.
L2 prediction for anything other than the asynchronous case has proven to be too difficult without some adjustments. Matt and I are currently working on the governer idea to try to make predicition of rare events possible.
See also:
-
Linaker, Niklasson. "Sensory Flow Segmentation using a Resource Allocating Vector Quantizer". http://citeseer.nj.nec.com/285911.html.
-
Linaker, Niklasson. "Extraction and inversion of abstract sensory flow representations". http://citeseer.nj.nec.com/353646.html.
-
Bergfeldt, Linaker. "Self-Organized Modulation of a Neural Robot Controller". http://citeseer.nj.nec.com/542716.html.
-
Nolfi, Tani. "Extracting Regularities in Space and Time Through a Cascade of Prediction Networks" http://citeseer.nj.nec.com/nolfi99extracting.html.
-
Kohonen. "Self Organizing Maps"
Related topics:
-
Adaptive Resonance Theory
-
Self Organizing Maps