/[pyrobot]/trunk/brain/conx.py
ViewVC logotype

Contents of /trunk/brain/conx.py

Parent Directory Parent Directory | Revision Log Revision Log


Revision 2577 - (show annotations) (download) (as text)
Fri Aug 6 00:04:51 2010 UTC (3 weeks, 6 days ago) by dblank
File MIME type: text/x-python
File size: 242192 byte(s)
Lisa's changes, and updates for 64-bit: lights in pyro simulator
1 """
2 ----------------------------------------------------
3 An Artificial Neural Network System Implementing
4 Backprop. Part of the Pyrobot Robotics Project.
5 Provided under the GNU General Public License.
6 ----------------------------------------------------
7 (c) 2001-2006, Developmental Robotics Research Group
8 ----------------------------------------------------
9
10 This file implements the major classes and functions for
11 making artificial neural networks in Python.
12 """
13
14 __author__ = "Douglas Blank <dblank@brynmawr.edu>"
15 __version__ = "$Revision$"
16
17 # Includes newConx version 4/24/2009
18 # Jim Marshall
19
20 import Numeric, math, random, time, sys, operator
21 try:
22 import pyrobot.system.share as share
23 except:
24 class share: debug = 0
25 if not share.debug:
26 try:
27 import psyco; psyco.full()
28 print "Conx, version %s (psyco enabled)" % __version__.split()[1]
29 except:
30 print "Conx, version %s (regular speed)" % __version__.split()[1]
31 else:
32 print "Conx, version %s (debug; regular speed)" % __version__.split()[1]
33
34 def reverse(lyst):
35 """ Returns a reversed list. """
36 return [lyst[p] for p in range(len(lyst) - 1, -1, -1)]
37
38 def pad(s, n, p = " ", sep = "|", align = "left"):
39 """
40 Returns a padded string.
41 s = string to pad
42 n = width of string to return
43 sep = separator (on end of string)
44 align = text alignment, "left", "center", or "right"
45 """
46 if align == "left":
47 return (s + (p * n))[:n] + sep
48 elif align == "center":
49 pos = n + len(s)/2 - n/2
50 return ((p * n) + s + (p * n))[pos:pos + n] + sep
51 elif align == "right":
52 return ((p * n) + s)[-n:] + sep
53
54 def sumMerge(dict1, dict2):
55 """
56 Adds two dictionaries together, and merges into the first, dict1.
57 Returns first dict.
58 """
59 for key in dict2:
60 dict1[key] = map(lambda a,b: a + b, dict1.get(key, [0,0,0,0]), dict2[key])
61 return dict1 # and also returns it, in case you want to do something to it
62
63 def loadNetwork(filename, mode = 'pickle'):
64 """
65 Loads network from a file using pickle. See Network.saveNetwork()
66 """
67 return loadNetworkFromFile(filename, mode)
68
69 def loadNetworkFromFile(filename, mode = 'pickle'):
70 """
71 Deprecated. Use loadNetwork instead.
72 """
73 if mode == 'pickle':
74 import pickle
75 fp = open(filename)
76 network = pickle.load(fp)
77 fp.close()
78 return network
79 elif mode in ['plain', 'conx']:
80 fp = open(filename, "r")
81 line = fp.readline()
82 network = None
83 while line:
84 if line.startswith("layer,"):
85 # layer, name, size
86 temp, name, sizeStr = line.split(",")
87 name = name.strip()
88 size = int(sizeStr)
89 network.addLayer(name, size)
90 line = fp.readline()
91 weights = [float(f) for f in line.split()]
92 for i in range(network[name].size):
93 network[name].weight[i] = weights[i]
94 elif line.startswith("connection,"):
95 # connection, fromLayer, toLayer
96 temp, nameFrom, nameTo = line.split(",")
97 nameFrom, nameTo = nameFrom.strip(), nameTo.strip()
98 network.connect(nameFrom, nameTo)
99 for i in range(network[nameFrom].size):
100 line = fp.readline()
101 weights = [float(f) for f in line.split()]
102 for j in range(network[nameTo].size):
103 network[nameFrom, nameTo].weight[i][j] = weights[j]
104 elif line.startswith("parameter,"):
105 temp, exp = line.split(",")
106 exec(exp) # network is the neural network object
107 elif line.startswith("network,"):
108 temp, netType = line.split(",")
109 netType = netType.strip().lower()
110 if netType == "cascornetwork":
111 from pyrobot.brain.cascor import CascorNetwork
112 network = CascorNetwork()
113 elif netType == "network":
114 network = Network()
115 elif netType == "srn":
116 network = SRN()
117 else:
118 raise AttributeError, "unknown network type: '%s'" % netType
119 line = fp.readline()
120 return network
121 def ndim(n, *args):
122 """
123 Makes a multi-dimensional array of random floats. (Replaces RandomArray).
124 """
125 if not args:
126 return [random.random() for i in xrange(n)]
127 A = []
128 for i in range(n):
129 A.append( ndim(*args) )
130 return A
131 def ndim2(n, *args):
132 """
133 Makes a multi-dimensional array of random floats. (Replaces RandomArray).
134 This version generates random values from a probability distribution more appropriate for a Tanh activation function.
135 """
136 if not args:
137 return [random.gauss(0, 1) for i in xrange(n)]
138 A = []
139 for i in range(n):
140 A.append( ndim(*args) )
141 return A
142 def randomArray2(size, bound):
143 """
144 Returns an array initialized to random values between -max and max.
145 """
146 if type(size) == type(1):
147 size = (size,)
148 temp = Numeric.array( ndim(*size) ) * (2.0 * bound)
149 return temp - bound
150 def randomArray(size, bound):
151 """
152 Returns an array initialized to random values between -max and max.
153 """
154 if type(size) == type(1):
155 size = (size,)
156 temp = Numeric.array( ndim(*size) ) * (2.0 * bound)
157 return temp - bound
158
159 def displayArray(name, a, width = 0):
160 """
161 Prints an array (any sequence of floats, really) to the screen.
162 """
163 print name + ": ",
164 cnt = 0
165 for i in a:
166 print "%4.2f" % i,
167 if width > 0 and (cnt + 1) % width == 0:
168 print ''
169 cnt += 1
170
171 def toStringArray(name, a, width = 0):
172 """
173 Returns an array (any sequence of floats, really) as a string.
174 """
175 string = name + ": "
176 cnt = 0
177 for i in a:
178 string += "%4.2f " % i
179 if width > 0 and (cnt + 1) % width == 0:
180 string += '\n'
181 cnt += 1
182 return string
183
184 def writeArray(fp, a, delim = " ", nl = 1):
185 """
186 Writes a sequence a of floats to file pointed to by file pointer.
187 """
188 for i in a:
189 fp.write("%f%s" % (i, delim))
190 if nl:
191 fp.write("\n")
192
193 class LayerError(AttributeError):
194 """
195 Used to indicate that a layer has some improper attribute (size,
196 type, etc.).
197 """
198
199 class NetworkError(AttributeError):
200 """
201 Used to indicate that a network has some improper attribute (no
202 layers, no connections, etc.).
203 """
204
205 class SRNError(NetworkError):
206 """
207 Used to indicate that SRN specific attributes are improper.
208 """
209
210 class Node:
211 """
212 A temp place to hold values for reference. If given a layer and position, then
213 the node can be used to set values, and can update itself.
214 """
215 def __init__(self, layer = None, position = None, **keywords):
216 self.__dict__["_layer"] = layer
217 self.__dict__["_position"] = position
218 self.__dict__["_attributes"] = keywords.keys()
219 for key in keywords:
220 self.__dict__[key] = keywords[key]
221 def __str__(self):
222 retval = "Node:"
223 if self._layer != None:
224 retval += "\n layer = '%s'" % self._layer.name
225 retval += "\n position = %d" % self._position
226 for key in self._attributes:
227 retval += "\n %s = %s" % (key, self.__dict__[key])
228 return retval
229 def update(self):
230 if self._layer != None:
231 for key in self._attributes:
232 self.__dict__[key] = self._layer.__dict__[key][self._position]
233 else:
234 raise AttributeError, "node is read-only"
235 def __setattr__(self, attribute, value):
236 if self._layer != None:
237 if attribute in self._layer.__dict__:
238 self._layer.__dict__[attribute][self._position] = value
239 else:
240 raise AttributeError, "no such property '%s'" % attribute
241 else:
242 raise AttributeError, ("node is read-only: use net['layerName'].%s[NUM] = %s" % (attribute, value))
243
244 class Layer:
245 """
246 Class which contains arrays of node elements (ie, activation,
247 error, bias, etc).
248 """
249 # constructor
250 def __init__(self, name, size, maxRandom = 0.1):
251 """
252 Constructor for Layer class. A name and the number of nodes
253 for the instance are passed as arguments.
254 """
255 if size <= 0:
256 raise LayerError, ('Layer was initialized with size zero.' , size)
257 self.patternReport = 0 # flag to determine if layer should be in report
258 # patternReport is set to 1 automatically for output layers (in .connect())
259 self.name = name
260 self.size = size
261 self.displayWidth = size
262 self.type = 'Undefined' # determined later by connectivity
263 self.kind = 'Undefined' # mirrors self.type but will include 'Context'
264 self._verbosity = 0
265 self.log = 0
266 self.logFile = ''
267 self._logPtr = 0
268 self.active = 1 # takes layer out of the processing
269 self.frozen = 0 # freezes weights (dbiases), if layer still active
270 self._maxRandom = maxRandom
271 self.initialize()
272 self.verify = 1
273 # layer report of stats:
274 self.pcorrect = 0
275 self.ptotal = 0
276 self.correct = 0
277 # misc:
278 self.minTarget = 0.0
279 self.maxTarget = 1.0
280 self.minActivation = 0.0
281 self.maxActivation = 1.0
282
283 def initialize(self):
284 """
285 Initializes important node values to zero for each node in the
286 layer (target, error, activation, dbias, delta, netinput, bed).
287 """
288 self.randomize()
289 self.dweight = Numeric.zeros(self.size, 'f')
290 self.delta = Numeric.zeros(self.size, 'f')
291 self.wed = Numeric.zeros(self.size, 'f')
292 self.wedLast = Numeric.zeros(self.size, 'f')
293 self.target = Numeric.zeros(self.size, 'f')
294 self.error = Numeric.zeros(self.size, 'f')
295 self.activation = Numeric.zeros(self.size, 'f')
296 self.netinput = Numeric.zeros(self.size, 'f')
297 self.targetSet = 0
298 self.activationSet = 0
299 def randomize(self, force = 0):
300 """
301 Initialize node biases to random values in the range [-max, max].
302 """
303 if force or not self.frozen:
304 self.weight = randomArray(self.size, self._maxRandom)
305
306 # general methods
307 def __len__(self):
308 """
309 Returns the number of nodes in the layer.
310 """
311 return self.size
312 def __str__(self):
313 return self.toString()
314 def __getitem__(self, i):
315 """ Return a temp object with some properties set """
316 if type(i) == int:
317 return Node(
318 layer = self,
319 position = i,
320 activation = self.activation[i],
321 error = self.error[i],
322 target = self.target[i],
323 netinput = self.netinput[i],
324 weight = self.weight[i]
325 )
326 else:
327 raise AttributeError, "expected integer instead of '%s'" % i
328 # layer methods
329 def setActive(self, value):
330 """
331 Sets layer to active or inactive. Layers must be active to propagate activations.
332 """
333 self.active = value
334 def getActive(self):
335 """
336 Used to determine if a layer is active or inactive.
337 """
338 return self.active
339 def changeSize(self, newsize):
340 """
341 Changes the size of the layer. Should only be called through
342 Network.changeLayerSize().
343 """
344 # overwrites current data
345 if newsize <= 0:
346 raise LayerError, ('Layer size changed to zero.', newsize)
347 minSize = min(self.size, newsize)
348 bias = randomArray(newsize, self._maxRandom)
349 Numeric.put(bias, Numeric.arange(minSize), self.weight)
350 self.weight = bias
351 self.size = newsize
352 self.displayWidth = newsize
353 self.targetSet = 0
354 self.activationSet = 0
355 self.target = Numeric.zeros(self.size, 'f')
356 self.error = Numeric.zeros(self.size, 'f')
357 self.activation = Numeric.zeros(self.size, 'f')
358 self.dweight = Numeric.zeros(self.size, 'f')
359 self.delta = Numeric.zeros(self.size, 'f')
360 self.netinput = Numeric.zeros(self.size, 'f')
361 self.wed = Numeric.zeros(self.size, 'f')
362 self.wedLast = Numeric.zeros(self.size, 'f')
363
364 # error and report methods
365 def TSSError(self):
366 """
367 Returns Total Sum Squared Error for this layer's pattern.
368 """
369 return Numeric.add.reduce((self.target - self.activation) ** 2)
370 def RMSError(self):
371 """
372 Returns Root Mean Squared Error for this layer's pattern.
373 """
374 tss = self.TSSError()
375 return math.sqrt(tss / self.size)
376 def getCorrect(self, tolerance):
377 """
378 Returns the number of nodes within tolerance of the target.
379 """
380 return Numeric.add.reduce(Numeric.fabs(self.target - self.activation) < tolerance)
381 def getWinner(self, type = 'activation'):
382 """
383 Returns the winner of the type specified {'activation' or
384 'target'}.
385 """
386 maxvalue = -10000
387 maxpos = -1
388 ttlvalue = 0
389 if type == 'activation':
390 ttlvalue = Numeric.add.reduce(self.activation)
391 maxpos = Numeric.argmax(self.activation)
392 maxvalue = self.activation[maxpos]
393 elif type == 'target':
394 # note that backprop() resets self.targetSet flag
395 if self.verify and self.targetSet == 0:
396 raise LayerError, \
397 ('getWinner() called with \'target\' but target has not been set.', \
398 self.targetSet)
399 ttlvalue = Numeric.add.reduce(self.target)
400 maxpos = Numeric.argmax(self.target)
401 maxvalue = self.target[maxpos]
402 else:
403 raise LayerError, ('getWinner() called with unknown layer attribute.', \
404 type)
405 if self.size > 0:
406 avgvalue = ttlvalue / float(self.size)
407 else:
408 raise LayerError, ('getWinner() called for layer of size zero.', \
409 self.size)
410 return maxpos, maxvalue, avgvalue
411
412 # used so pickle will work with log file pointer
413 def __getstate__(self):
414 odict = self.__dict__.copy()
415 del odict['_logPtr']
416 return odict
417 def __setstate__(self,dict):
418 if dict['log']:
419 self._logPtr = open(dict['logFile'], 'a')
420 else:
421 self._logPtr = 0
422 self.__dict__.update(dict)
423
424 # log methods
425 def setLog(self, fileName):
426 """
427 Opens a log file with name fileName.
428 """
429 self.log = 1
430 self.logFile = fileName
431 self._logPtr = open(fileName, "w")
432 def logMsg(self, msg):
433 """
434 Logs a message.
435 """
436 self._logPtr.write(msg)
437 def closeLog(self):
438 """
439 Closes the log file.
440 """
441 self._logPtr.close()
442 self.log = 0
443 def writeLog(self):
444 """
445 Writes to the log file.
446 """
447 if self.log:
448 writeArray(self._logPtr, self.activation)
449
450 # string and display methods
451 def setDisplayWidth(self, val):
452 """
453 Sets self.displayWidth the the argument value.
454 """
455 self.displayWidth = val
456 def toString(self):
457 """
458 Returns a string representation of Layer instance.
459 """
460 string = "Layer '%s': (Kind: %s, Size: %d, Active: %d, Frozen: %d)\n" % (
461 self.name, self.kind, self.size, self.active, self.frozen)
462 if (self.type == 'Output'):
463 string += toStringArray('Target ', self.target, self.displayWidth)
464 string += toStringArray('Activation', self.activation, self.displayWidth)
465 if (self.type != 'Input' and self._verbosity > 1):
466 string += toStringArray('Error ', self.error, self.displayWidth)
467 if (self._verbosity > 4 and self.type != 'Input'):
468 string += toStringArray('weight ', self.weight, self.displayWidth)
469 string += toStringArray('dweight ', self.dweight, self.displayWidth)
470 string += toStringArray('delta ', self.delta, self.displayWidth)
471 string += toStringArray('netinput ', self.netinput, self.displayWidth)
472 string += toStringArray('wed ', self.wed, self.displayWidth)
473 return string
474 def display(self):
475 """
476 Displays the Layer instance to the screen.
477 """
478 if self.displayWidth == 0: return
479 print "============================="
480 print "Layer '%s': (Kind: %s, Size: %d, Active: %d, Frozen: %d)" % (
481 self.name, self.kind, self.size, self.active, self.frozen)
482 if (self.type == 'Output'):
483 displayArray('Target ', self.target, self.displayWidth)
484 displayArray('Activation', self.activation, self.displayWidth)
485 if (self.type != 'Input' and self._verbosity > 1):
486 displayArray('Error ', self.error, self.displayWidth)
487 if (self._verbosity > 4 and self.type != 'Input'):
488 print " ", ; displayArray('weight', self.weight)
489 print " ", ; displayArray('dweight', self.dweight)
490 print " ", ; displayArray('delta', self.delta)
491 print " ", ; displayArray('netinput', self.netinput)
492 print " ", ; displayArray('wed', self.wed)
493
494
495 # activation methods
496 def getActivationsList(self):
497 """
498 Returns node activations in list (copy) form.
499 """
500 return self.activation.copy()
501 def getActivations(self):
502 """
503 Returns node activations in (Numeric) array (pointer) form.
504 """
505 return self.activation
506 def setActivations(self, value):
507 """
508 Sets all activations to the value of the argument. Value should be in the range [0,1].
509 """
510 #if self.verify and not self.activationSet == 0:
511 # raise LayerError, \
512 # ('Activation flag not reset. Activations may have been set multiple times without any intervening call to propagate().', self.activationSet)
513 Numeric.put(self.activation, Numeric.arange(len(self.activation)), value)
514 self.activationSet = 1
515 def copyActivations(self, arr, reckless = 0):
516 """
517 Copies activations from the argument array into
518 layer activations.
519 """
520 array = Numeric.array(arr)
521 if not len(array) == self.size:
522 raise LayerError, \
523 ('Mismatched activation size and layer size in call to copyActivations()', \
524 (len(array), self.size))
525 if self.verify and not self.activationSet == 0:
526 if not reckless:
527 raise LayerError, \
528 ('Activation flag not reset before call to copyActivations()', \
529 self.activationSet)
530 self.activation = array
531 self.activationSet = 1
532
533 # target methods
534 def getTargetsList(self):
535 """
536 Returns targets in list form.
537 """
538 return self.target.copy()
539 def getTargets(self):
540 """
541 Return targets in (Numeric) array form.
542 """
543 return self.target
544 def setTargets(self, value):
545 """
546 Sets all targets the the value of the argument. This value must be in the range [min,max].
547 """
548 # Removed this because both propagate and backprop (via compute_error) set targets
549 #if self.verify and not self.targetSet == 0:
550 # if not self.warningIssued:
551 # print 'Warning! Targets have already been set and no intervening backprop() was called.', \
552 # (self.name, self.targetSet)
553 # print "(Warning will not be issued again)"
554 # self.warningIssued = 1
555 if value > self.maxActivation or value < self.minActivation:
556 raise LayerError, ('Targets for this layer are out of the proper interval.', (self.name, value))
557 Numeric.put(self.target, Numeric.arange(len(self.target)), value)
558 self.targetSet = 1
559 def copyTargets(self, arr):
560 """
561 Copies the targets of the argument array into the self.target attribute.
562 """
563 array = Numeric.array(arr)
564 if not len(array) == self.size:
565 raise LayerError, \
566 ('Mismatched target size and layer size in call to copyTargets()', \
567 (len(array), self.size))
568 # Removed this because both propagate and backprop (via compute_error) set targets
569 #if self.verify and not self.targetSet == 0:
570 # if not self.warningIssued:
571 # print 'Warning! Targets have already been set and no intervening backprop() was called.', \
572 # (self.name, self.targetSet)
573 # print "(Warning will not be issued again)"
574 # self.warningIssued = 1
575 if Numeric.add.reduce(array < self.minTarget) or Numeric.add.reduce(array > self.maxTarget):
576 print self.name, self.minTarget, self.maxTarget
577 raise LayerError, ('Targets for this layer are out of range.', (self.name, array))
578 self.target = array
579 self.targetSet = 1
580
581 # flag methods
582 def resetFlags(self):
583 """
584 Resets self.targetSet and self.activationSet flags.
585 """
586 self.targetSet = 0
587 self.activationSet = 0
588 def resetTargetFlag(self):
589 """
590 Resets self.targetSet flag.
591 """
592 self.targetSet = 0
593 def resetActivationFlag(self):
594 """
595 Resets self.activationSet flag.
596 """
597 self.activationSet = 0
598
599 class Connection:
600 """
601 Class which contains references to two layers (from and to) and the
602 weights between them.
603 """
604 # constructor and initialization methods
605 def __init__(self, fromLayer, toLayer):
606 """
607 Constructor for Connection class. Takes instances of source and
608 destination layers as arguments.
609 """
610 self.active = 1 # propagates and backrops if active
611 self.frozen = 0 # freezes weights
612 self.fromLayer = fromLayer
613 self.toLayer = toLayer
614 self.initialize()
615 def __getitem__(self, i):
616 return self.weight[i]
617 def initialize(self):
618 """
619 Initializes self.dweight and self.wed to zero matrices.
620 """
621 self.randomize()
622 self.dweight = Numeric.zeros((self.fromLayer.size, \
623 self.toLayer.size), 'f')
624 self.wed = Numeric.zeros((self.fromLayer.size, \
625 self.toLayer.size), 'f')
626 self.wedLast = Numeric.zeros((self.fromLayer.size, \
627 self.toLayer.size), 'f')
628 def randomize(self, force = 0):
629 """
630 Sets weights to initial random values in the range [-max, max].
631 """
632 if force or not self.frozen:
633 self.weight = randomArray((self.fromLayer.size, \
634 self.toLayer.size),
635 self.toLayer._maxRandom)
636 def __str__(self):
637 return self.toString()
638
639 # connection modification methods
640 def changeSize(self, fromLayerSize, toLayerSize):
641 """
642 Changes the size of the connection depending on the size
643 change of either source or destination layer. Should only be
644 called through Network.changeLayerSize().
645 """
646 if toLayerSize <= 0 or fromLayerSize <= 0:
647 raise LayerError, ('changeSize() called with invalid layer size.', \
648 (fromLayerSize, toLayerSize))
649 dweight = Numeric.zeros((fromLayerSize, toLayerSize), 'f')
650 wed = Numeric.zeros((fromLayerSize, toLayerSize), 'f')
651 wedLast = Numeric.zeros((fromLayerSize, toLayerSize), 'f')
652 weight = randomArray((fromLayerSize, toLayerSize),
653 self.toLayer._maxRandom)
654 # copy from old to new, considering one is smaller
655 minFromLayerSize = min( fromLayerSize, self.fromLayer.size)
656 minToLayerSize = min( toLayerSize, self.toLayer.size)
657 for i in range(minFromLayerSize):
658 for j in range(minToLayerSize):
659 wed[i][j] = self.wed[i][j]
660 wedLast[i][j] = self.wedLast[i][j]
661 dweight[i][j] = self.dweight[i][j]
662 weight[i][j] = self.weight[i][j]
663 self.dweight = dweight
664 self.wed = wed
665 self.wedLast = wedLast
666 self.weight = weight
667
668 # display methods
669 def display(self):
670 """
671 Displays connection information to the screen.
672 """
673 if self.toLayer._verbosity > 4:
674 print "wed: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'"
675 for j in range(self.toLayer.size):
676 print self.toLayer.name, "[", j, "]",
677 print ''
678 for i in range(self.fromLayer.size):
679 print self.fromLayer.name, "[", i, "]", ": ",
680 for j in range(self.toLayer.size):
681 print self.wed[i][j],
682 print ''
683 print ''
684 print "dweight: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'"
685 for j in range(self.toLayer.size):
686 print self.toLayer.name, "[", j, "]",
687 print ''
688 for i in range(self.fromLayer.size):
689 print self.fromLayer.name, "[", i, "]", ": ",
690 for j in range(self.toLayer.size):
691 print self.dweight[i][j],
692 print ''
693 print ''
694 if self.toLayer._verbosity > 2:
695 print "Weights: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'"
696 print " ",
697 for j in range(self.toLayer.size):
698 print self.toLayer.name, "[", j, "]",
699 print ''
700 for i in range(self.fromLayer.size):
701 print self.fromLayer.name, "[", i, "]", ": ",
702 for j in range(self.toLayer.size):
703 print self.weight[i][j],
704 print ''
705 print ''
706 # string method
707 def toString(self):
708 """
709 Connection information as a string.
710 """
711 string = ""
712 if self.toLayer._verbosity > 4:
713 string += "wed: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'\n"
714 string += " "
715 for j in range(self.toLayer.size):
716 string += " " + self.toLayer.name + "[" + str(j) + "]"
717 string += '\n'
718 for i in range(self.fromLayer.size):
719 string += self.fromLayer.name+ "["+ str(i)+ "]"+ ": "
720 for j in range(self.toLayer.size):
721 string += " " + str(self.wed[i][j])
722 string += '\n'
723 string += '\n'
724 string += "dweight: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'\n"
725 string += " "
726 for j in range(self.toLayer.size):
727 string += " " + self.toLayer.name+ "["+ str(j)+ "]"
728 string += '\n'
729 for i in range(self.fromLayer.size):
730 string += self.fromLayer.name+ "["+ str(i)+ "]"+ ": "
731 for j in range(self.toLayer.size):
732 string += " " + str(self.dweight[i][j])
733 string += '\n'
734 string += '\n'
735 if self.toLayer._verbosity > 2:
736 string += "Weights: from '" + self.fromLayer.name + "' to '" + self.toLayer.name +"'\n"
737 string += " "
738 for j in range(self.toLayer.size):
739 string += " " + self.toLayer.name+ "["+ str(j)+ "]"
740 string += '\n'
741 for i in range(self.fromLayer.size):
742 string += self.fromLayer.name+ "["+ str(i)+ "]"+ ": "
743 for j in range(self.toLayer.size):
744 string += " " + str(self.weight[i][j])
745 string += '\n'
746 string += '\n'
747 return string
748
749 class Network(object):
750 """
751 Class which contains all of the parameters and methods needed to
752 run a neural network.
753 """
754 # constructor
755 def __init__(self, name = 'Backprop Network', verbosity = 0,
756 seed = None):
757 """
758 Constructor for the Network class. Takes optional name and
759 verbosity arguments.
760 """
761 if seed == None:
762 x = random.random() * 100000 + time.time()
763 else:
764 x = seed
765 self.layers = []
766 self.verbosity = verbosity
767 self.setSeed(x)
768 self.complete = 0
769 self.name = name
770 self.layersByName = {}
771 self.connections = []
772 self.inputMap = []
773 self.targetMap = []
774 self.association = []
775 self.inputs = []
776 self.targets = []
777 self.orderedInputs = 0
778 self.loadOrder = []
779 self.learning = 1
780 self.momentum = 0.9
781 self.resetEpoch = 5000
782 self.resetCount = 1
783 self.resetLimit = 1
784 self.batch = 0
785 self.epoch = 0
786 self.totalEpoch = 0
787 self.count = 0 # number of times propagate is called
788 self.stopPercent = 1.0
789 self.sigmoid_prime_offset = 0.1
790 self.tolerance = 0.4
791 self.interactive = 0
792 self.epsilon = 0.1
793 self.reportRate = 25
794 self.sweepReportRate = None #1000
795 self.crossValidationCorpus = ()
796 self.crossValidationReportLayers = []
797 self.crossValidationSampleRate = 0
798 self.crossValidationSampleFile = "sample.cv"
799 self.patterns = {}
800 self.patterned = 0 # used for file IO with inputs and targets
801 self.sharedWeights = 0
802 self.useCrossValidationToStop = 0
803 self.saveResults = 0 # will save error, correct, total in sweep()
804 self.results = []
805 self.autoCrossValidation = 0
806 self.autoSaveWeightsFile = None
807 self.autoSaveWeightsFileFormat = "conx"
808 self.lastAutoSaveWeightsFilename = None
809 self.autoSaveNetworkFile = None
810 self.autoSaveNetworkFileFormat = "conx"
811 self.lastAutoSaveNetworkFilename = None
812 self.lastLowestTSSError = sys.maxint # some maximum value (not all pythons have Infinity)
813 self._cv = False # set true when in cross validation
814 self._sweeping = 0 # flag set when sweeping through corpus (as apposed to just stepping)
815 self._maxRandom = 0.1
816 self.currentSweepCount = None
817 self.log = None # a pointer to a file-like object, like a Log object
818 self.echo = False # if going to a log file, echo it too, if true
819 self.hyperbolicError = 0 # exaggerate error?
820 # Quickprop settings:
821 self._quickprop = 0
822 self.mu = 1.75 # maximum growth factor
823 self.splitEpsilon = 0
824 self.decay = 0.0000
825 self.cacheConnections = []
826 self.cacheLayers = []
827 self.setup()
828 def setCache(self, val = 1):
829 """ Sets cache on (or updates), or turns off """
830 # first clear the old cached values
831 self.cacheConnections = []
832 self.cacheLayers = []
833 if val:
834 for layer in self.layers:
835 if layer.active and not layer.frozen:
836 self.cacheLayers.append( layer )
837 for connection in self.connections:
838 if connection.active and not connection.frozen:
839 self.cacheConnections.append( connection )
840 def setQuickprop(self, value):
841 if value:
842 self.batch = 1
843 self._quickprop = 1
844 self.mu = 1.75 # maximum growth factor
845 self.splitEpsilon = 1
846 self.decay = -0.0001
847 self.epsilon = 4.0
848 #set different activation function here?
849 self.name = "Quickprop Network"
850 else:
851 self._quickprop = 0
852 self.splitEpsilon = 0
853 self.decay = 0.0000
854 self.epsilon = 0.1
855 def getQuickprop(self): return self._quickprop
856 def setup(self):
857 pass
858 # general methods
859 def path(self, startLayer, endLayer):
860 """
861 Used in error checking with verifyArchitecture() and in prop_from().
862 """
863 next = {startLayer.name : startLayer}
864 visited = {}
865 while next != {}:
866 for item in next.items():
867 # item[0] : name, item[1] : layer reference
868 # add layer to visited dict and del from next
869 visited[item[0]] = item[1]
870 del next[item[0]]
871 for connection in self.connections:
872 if connection.fromLayer.name == item[0]:
873 if connection.toLayer.name == endLayer.name:
874 return 1 # a path!
875 elif next.has_key(connection.toLayer.name):
876 pass # already in the list to be traversed
877 elif visited.has_key(connection.toLayer.name):
878 pass # already been there
879 else:
880 # add to next
881 next[connection.toLayer.name] = connection.toLayer
882 return 0 # didn't find it and ran out of places to go
883 def __str__(self):
884 """
885 Returns string representation of network.
886 """
887 return self.toString()
888 def __iter__(self):
889 for layer in self.layers:
890 yield layer
891 def __getitem__(self, name):
892 """
893 Returns the layer specified by name.
894 """
895 if type(name) == str:
896 return self.layersByName[name]
897 else:
898 fromName, toName = name
899 return self.getConnection(fromName, toName)
900 def __len__(self):
901 """
902 Returns the number of layers in the network.
903 """
904 return len(self.layers)
905 def getLayerIndex(self, layer):
906 """
907 Given a reference to a layer, returns the index of that layer in
908 self.layers.
909 """
910 for i in range(len(self.layers)):
911 if layer == self.layers[i]: # shallow cmp
912 return i
913 return -1 # not in list
914 def addLayer(self, name, size, verbosity = 0, position = None):
915 assert type(name) == str, "first parameter (name) must be a string"
916 assert type(size) == int, "second parameter (size) must be an integer"
917 assert (name not in self.layersByName), ("duplicate layer name '%s' is not allowed" % name)
918 layer = Layer(name, size, maxRandom=self._maxRandom)
919 Network.add(self, layer, verbosity, position)
920 # methods for constructing and modifying a network
921 def add(self, layer, verbosity = 0, position = None):
922 """
923 Adds a layer. Layer verbosity is optional (default 0).
924 """
925 layer._verbosity = verbosity
926 layer._maxRandom = self._maxRandom
927 layer.minTarget = 0.0
928 layer.maxTarget = 1.0
929 layer.minActivation = 0.0
930 layer.maxActivation = 1.0
931 if position == None:
932 self.layers.append(layer)
933 else:
934 self.layers.insert(position, layer)
935 self.layersByName[layer.name] = layer
936 def isConnected(self, fromName, toName):
937 """ Are these two layers connected this way? """
938 for c in self.connections:
939 if (c.fromLayer.name == fromName and
940 c.toLayer.name == toName):
941 return 1
942 return 0
943 def connect(self, *names):
944 """
945 Connects a list of names, one to the next.
946 """
947 fromName, toName, rest = names[0], names[1], names[2:]
948 self.connectAt(fromName, toName)
949 if len(rest) != 0:
950 self.connect(toName, *rest)
951 def connectAt(self, fromName, toName, position = None):
952 """
953 Connects two layers by instantiating an instance of Connection
954 class. Allows a position number, indicating the ordering of
955 the connection.
956 """
957 fromLayer = self.getLayer(fromName)
958 toLayer = self.getLayer(toName)
959 if self.getLayerIndex(fromLayer) >= self.getLayerIndex(toLayer):
960 raise NetworkError, ('Layers out of order.', (fromLayer.name, toLayer.name))
961 if (fromLayer.type == 'Output'):
962 fromLayer.type = 'Hidden'
963 fromLayer.patternReport = 0 # automatically turned off for hidden layers
964 if fromLayer.kind == 'Output':
965 fromLayer.kind = 'Hidden'
966 elif (fromLayer.type == 'Undefined'):
967 fromLayer.type = 'Input'
968 fromLayer.patternReport = 0 # automatically turned off for input layers
969 if fromLayer.kind == 'Undefined':
970 fromLayer.kind = 'Input'
971 if (toLayer.type == 'Input'):
972 raise NetworkError, ('Connections out of order', (fromLayer.name, toLayer.name))
973 elif (toLayer.type == 'Undefined'):
974 toLayer.type = 'Output'
975 toLayer.patternReport = 1 # automatically turned on for output layers
976 if toLayer.kind == 'Undefined':
977 toLayer.kind = 'Output'
978 if position == None:
979 self.connections.append(Connection(fromLayer, toLayer))
980 else:
981 self.connections.insert(position, Connection(fromLayer, toLayer))
982 def addThreeLayers(self, inc, hidc, outc):
983 """
984 Creates a three layer network with 'input', 'hidden', and
985 'output' layers.
986 """
987 self.addLayer('input', inc)
988 self.addLayer('hidden', hidc)
989 self.addLayer('output', outc)
990 self.connect('input', 'hidden')
991 self.connect('hidden', 'output')
992 def addLayers(self, *arg, **kw):
993 """
994 Creates an N layer network with 'input', 'hidden1', 'hidden2',...
995 and 'output' layers. Keyword type indicates "parallel" or
996 "serial". If only one hidden layer, it is called "hidden".
997 """
998 netType = "serial"
999 if "type" in kw:
1000 netType = kw["type"]
1001 self.addLayer('input', arg[0])
1002 hiddens = []
1003 if len(arg) > 3:
1004 hcount = 0
1005 for hidc in arg[1:-1]:
1006 name = 'hidden%d' % hcount
1007 self.addLayer(name, hidc)
1008 hiddens.append(name)
1009 hcount += 1
1010 elif len(arg) == 3:
1011 name = 'hidden'
1012 self.addLayer(name, arg[1])
1013 hiddens.append(name)
1014 elif len(arg) == 2:
1015 pass
1016 else:
1017 raise AttributeError, "not enough layers! need >= 2"
1018 self.addLayer('output', arg[-1])
1019 lastName = "input"
1020 for name in hiddens:
1021 if netType == "parallel":
1022 self.connect('input', name)
1023 self.connect(name, 'output')
1024 else: # serial
1025 self.connect(lastName, name)
1026 lastName = name
1027 if netType == "serial" or lastName == "input":
1028 self.connect(lastName, "output")
1029 def setLayerVerification(self, value):
1030 for layer in self.layers:
1031 layer.verify = value
1032 def deleteLayerNode(self, layername, nodeNum):
1033 """
1034 Removes a particular unit/node from a layer.
1035 """
1036 # first, construct an array of all of the weights
1037 # that won't be deleted:
1038 gene = []
1039 for layer in self.layers:
1040 if layer.type != 'Input':
1041 for i in range(layer.size):
1042 if layer.name == layername and i == nodeNum:
1043 pass # skip it
1044 else:
1045 gene.append(layer.weight[i])
1046 for connection in self.connections:
1047 for i in range(connection.fromLayer.size):
1048 for j in range(connection.toLayer.size):
1049 if ((connection.fromLayer.name == layername and i == nodeNum) or
1050 (connection.toLayer.name == layername and j == nodeNum)):
1051 pass # skip weights from/to nodeNum
1052 else:
1053 gene.append(connection.weight[i][j])
1054 # now, change the size (removes rightmost node):
1055 self.changeLayerSize(layername, self[layername].size - 1)
1056 # and put the good weights where they go:
1057 self.unArrayify(gene)
1058 def addLayerNode(self, layerName, bias = None, weights = {}):
1059 """
1060 Adds a new node to a layer, and puts in new weights. Adds node on the end.
1061 Weights will be random, unless specified.
1062
1063 bias = the new node's bias weight
1064 weights = dict of {connectedLayerName: [weights], ...}
1065
1066 Example:
1067 >>> net.addLayers(2, 5, 1)
1068 >>> net.addLayerNode("hidden", bias = -0.12, weights = {"input": [1, 0], "output": [0]})
1069 """
1070 self.changeLayerSize(layerName, self[layerName].size + 1)
1071 if bias != None:
1072 self[layerName].weight[-1] = bias
1073 for name in weights.keys():
1074 for c in self.connections:
1075 if c.fromLayer.name == name and c.toLayer.name == layerName:
1076 for i in range(self[name].size):
1077 self[name, layerName].weight[i][-1] = weights[name][i]
1078 elif c.toLayer.name == name and c.fromLayer.name == layerName:
1079 for j in range(self[name].size):
1080 self[layerName, name].weight[-1][j] = weights[name][j]
1081 def changeLayerSize(self, layername, newsize):
1082 """
1083 Changes layer size. Newsize must be greater than zero.
1084 """
1085 # for all connection from to this layer, change matrix:
1086 if self.sharedWeights:
1087 raise AttributeError, "shared weights broken"
1088 for connection in self.connections:
1089 if connection.fromLayer.name == layername:
1090 connection.changeSize( newsize, connection.toLayer.size )
1091 if connection.toLayer.name == layername:
1092 connection.changeSize( connection.fromLayer.size, newsize )
1093 # then, change the actual layer size:
1094 self.getLayer(layername).changeSize(newsize)
1095 # reset and intialization
1096 def reset(self):
1097 """
1098 Resets seed values.
1099 """
1100 random.seed(self.seed)
1101 self.initialize()
1102 def initialize(self):
1103 """
1104 Initializes network by calling Connection.initialize() and
1105 Layer.initialize(). self.count is set to zero.
1106 """
1107 print >> sys.stderr, "Initializing '%s' weights..." % self.name
1108 if self.sharedWeights:
1109 raise AttributeError, "shared weights broken"
1110 self.count = 0
1111 for connection in self.connections:
1112 connection.initialize()
1113 for layer in self.layers:
1114 layer.initialize()
1115 def resetFlags(self):
1116 """
1117 Resets layer flags for activation and target.
1118 """
1119 for layer in self.layers:
1120 layer.resetFlags()
1121
1122 # set and get methods for attributes
1123 def putActivations(self, dict):
1124 """
1125 Puts a dict of name: activations into their respective layers.
1126 """
1127 for name in dict:
1128 self.layersByName[name].copyActivations( dict[name] )
1129 def getActivationsDict(self, nameList):
1130 """
1131 Returns a dictionary of layer names that map to a list of activations.
1132 """
1133 retval = {}
1134 for name in nameList:
1135 retval[name] = self.layersByName[name].getActivationsList()
1136 return retval
1137 def getLayer(self, name):
1138 """
1139 Returns the layer with the argument (string) name.
1140 """
1141 return self.layersByName[name]
1142 def setAutoCrossValidation(self, value):
1143 self.autoCrossValidation = value
1144 def setAutoSaveWeightsFile(self, filename, format = "conx"):
1145 self.autoSaveWeightsFile = filename
1146 self.autoSaveWeightsFileFormat = format
1147 def setAutoSaveNetworkFile(self, filename, format = "conx"):
1148 self.autoSaveNetworkFile = filename
1149 self.autoSaveNetworkFileFormat = format
1150 def setPatterned(self, value):
1151 """
1152 Sets the network to use patterns for inputs and targets.
1153 """
1154 self.patterned = value
1155 def setEpsilon(self, value): self.epsilon = value
1156 def setInteractive(self, value):
1157 """
1158 Sets interactive to value. Specifies if an interactive prompt
1159 should accompany sweep() or step().
1160 """
1161 self.interactive = value
1162 def setTolerance(self, value):
1163 """
1164 Sets tolerance to value. This specifies how close acceptable
1165 outputs must be to targets.
1166 """
1167 self.tolerance = value
1168 def setActive(self, layerName, value