1 """
2 ConnectionField and associated classes.
3
4 This module defines some basic classes of objects used to create
5 simulations of cortical sheets that take input through connection
6 fields that project from other cortical sheets (or laterally from
7 themselves).
8
9 ConnectionField: Holds a single connection field within a
10 CFProjection.
11
12 CFProjection: A set of ConnectionFields mapping from a Sheet into a
13 ProjectionSheet.
14
15 CFSheet: A subclass of ProjectionSheet that provides an interface to
16 the underlying ConnectionFields in any projection of type
17 CFProjection.
18
19 $Id: cf.py 11247 2010-07-18 20:39:43Z ceball $
20 """
21
22 __version__ = '$Revision: 11247 $'
23
24 from copy import copy
25
26 from numpy import abs,array,zeros,where
27 from numpy.oldnumeric import Float,Float32
28
29 import param
30
31 import patterngenerator
32 from patterngenerator import PatternGenerator
33 from functionfamily import TransferFn,IdentityTF
34 from functionfamily import LearningFn,Hebbian,IdentityLF
35 from functionfamily import ResponseFn,DotProduct
36 from functionfamily import CoordinateMapperFn,IdentityMF
37 from projection import Projection,ProjectionSheet
38 from sheetcoords import Slice
39 from sheetview import UnitView
40 from boundingregion import BoundingBox,BoundingRegionParameter
41
42
43
44
46 """
47 Simplify creation of numpy.vectorize(fn) objects where all outputs
48 have the same typecode.
49 """
50 from numpy import vectorize,sctype2char
51
52
53
54
55
56
57
58 vfn = vectorize(fn,doc=doc)
59
60
61 vfn.nout=num_outputs
62 output_typecode = sctype2char(output_type)
63 vfn.otypes=output_typecode*num_outputs
64 import inspect
65
66 try:
67 fn_code = fn.func_code if hasattr(fn,'func_code') else fn.__call__.func_code
68 except:
69 raise TypeError("Couldn't find code of %s"%fn)
70
71 fn_args = inspect.getargs(fn_code)[0]
72 extra = 1 if fn_args[0]=='self' else 0
73 vfn.lastcallargs=len(fn_args)-extra
74 return vfn
75
76
77
78
79
80 weight_type = Float32
81
82
84 """
85 Error thrown when trying to create an empty CF.
86 """
88 ValueError.__init__(self,"ConnectionField at (%s,%s) (input_sheet=%s) has a zero-sized weights matrix (%s,%s); you may need to supply a larger bounds_template or increase the density of the sheet."%(x,y,input,rows,cols))
89
90
92 """
93 A set of weights on one input Sheet.
94
95 Each ConnectionField contributes to the activity of one unit on
96 the output sheet, and is normally used as part of a Projection
97 including many other ConnectionFields.
98 """
99 __slots__ = ['weights','input_sheet_slice','mask',
100 '_has_norm_total','_norm_total']
101
103 """
104 Return the stored norm_value, if any, or else the current sum of the weights.
105 See the norm_total property for more details.
106 """
107
108 if self._has_norm_total:
109 return self._norm_total
110 else:
111 return abs(self.weights).sum()
112
114 """
115 Set an explicit value to be returned by norm_total.
116 See the norm_total property for more details.
117 """
118 self._has_norm_total = True
119 self._norm_total = new_norm_total
120
122 """
123 Delete any cached norm_total that may have been set.
124 See the norm_total property for more details.
125 """
126 self._has_norm_total = False
127
128
129
130
131
132 norm_total = property(__get_norm_total,__set_norm_total,__del_norm_total,
133 """
134 The norm_total property returns a value useful in computing
135 a sum-based weight normalization.
136
137 By default, the value returned is simply the current sum of
138 the connection weights. However, another value can be
139 substituted by setting norm_total explicitly, and this cached
140 value will then be returned instead.
141
142 This mechanism has two main purposes. First, it allows a
143 learning function to cache the sum value for an output
144 function to use later without computation, which can result in
145 significant time savings. Second, the extra level of
146 indirection allows the sum value to be manipulated before it
147 is used, to implement operations like joint normalization
148 across corresponding CFs in multiple Projections.
149
150 Apart from such cases, norm_total can be ignored.
151
152 Note that every person who uses a class that sets or gets
153 norm_total must be very careful to ensure that stale values
154 will never be accessed. A good way to do this is to make sure
155 that the value is only set just before it will be used, and
156 deleted as soon as it has been accessed.
157
158 WARNING: Any c-optimized code can bypass this property and
159 access directly _has_norm_total, _norm_total
160
161 """)
162
163
166
167
168
169
170
171
172
173
174
179 """
180 Create weights at the specified (x,y) location on the
181 specified input_sheet.
182
183 The supplied template (if a BoundingRegion) is converted to a
184 Slice, moved to the specified (x,y) location, and then the
185 weights pattern is drawn inside by the weights_generator.
186
187 Note that if the appropriate template Slice is already known,
188 then it can be passed in instead of a BoundingRegion template.
189 This slice will then be used directly, instead of converting
190 the template into a Slice.
191
192 The supplied template object itself will not be modified (it
193 is copied before use).
194
195 The mask allows the weights to be limited to being non-zero in
196 a subset of the rectangular weights area. The actual mask
197 used is a view of the given mask created by cropping to the
198 boundaries of the input_sheet, so that the weights all
199 correspond to actual locations in the input sheet. For
200 instance, if a circular pattern of weights is desired, the
201 mask should have a disk-shaped pattern of elements with value
202 1, surrounded by elements with the value 0. If the CF extends
203 over the edge of the input sheet then the weights will
204 actually be half-moon (or similar) rather than circular.
205 """
206
207
208 template = copy(template)
209
210 if not isinstance(template,Slice):
211 template = Slice(template,input_sheet,force_odd=True,
212 min_matrix_radius=min_matrix_radius)
213
214
215 if not hasattr(mask,'view'):
216 mask = _create_mask(mask,template.compute_bounds(input_sheet),
217
218
219
220 input_sheet,True,0.5)
221
222
223
224
225
226
227
228
229 self._has_norm_total=False
230
231 if output_fns is None:
232 output_fns = []
233
234
235
236 weights_slice = self._create_input_sheet_slice(input_sheet,x,y,template,min_matrix_radius)
237
238
239
240 self.mask = weights_slice.submatrix(mask)
241 self.mask = array(self.mask,copy=1)
242
243
244
245
246
247
248
249
250 w = weights_generator(x=x,y=y,bounds=self.get_bounds(input_sheet),
251 xdensity=input_sheet.xdensity,
252 ydensity=input_sheet.ydensity,
253 mask=self.mask)
254
255
256
257
258
259
260 self.weights = w.astype(weight_type)
261
262
263
264
265
266
267
268 for of in output_fns:
269 of(self.weights)
270
271
272
299
300
301
302
307
308
309
311 """
312 Map an input activity matrix into an output matrix using the CFs
313 in a CFProjection.
314
315 Objects in this hierarchy of callable function objects compute a
316 response matrix when given an input pattern and a set of
317 ConnectionField objects. Typically used as part of the activation
318 function for a neuron, computing activation for one Projection.
319
320 Objects in this class must support being called as a function with
321 the arguments specified below, and are assumed to modify the
322 activity matrix in place.
323 """
324 __abstract=True
325
326 - def __call__(self, iterator, input_activity, activity, strength, **params):
327 raise NotImplementedError
328
329
331 """
332 Generic large-scale response function based on a simple single-CF function.
333
334 Applies the single_cf_fn to each CF in turn. For the default
335 single_cf_fn of DotProduct(), does a basic dot product of each CF with the
336 corresponding slice of the input array. This function is likely
337 to be slow to run, but it is easy to extend with any arbitrary
338 single-CF response function.
339
340 The single_cf_fn must be a function f(X,W) that takes two
341 identically shaped matrices X (the input) and W (the
342 ConnectionField weights) and computes a scalar activation value
343 based on those weights.
344 """
345 single_cf_fn = param.ClassSelector(ResponseFn,default=DotProduct(),
346 doc="Accepts a ResponseFn that will be applied to each CF individually.")
347
348 - def __call__(self, iterator, input_activity, activity, strength):
354
355
357 """
358 Compute new CFs for a CFProjection based on input and output activity values.
359
360 Objects in this hierarchy of callable function objects compute a
361 new set of CFs when given input and output patterns and a set of
362 ConnectionField objects. Used for updating the weights of one
363 CFProjection.
364
365 Objects in this class must support being called as a function with
366 the arguments specified below.
367 """
368 __abstract = True
369
370
372 """
373 Return the learning rate for a single connection assuming that
374 the total rate is to be divided evenly among all the units in
375 the connection field.
376 """
377 return float(learning_rate)/n_units
378
379
380
381 - def __call__(self, iterator, input_activity, output_activity, learning_rate, **params):
382 """
383 Apply this learning function to the given set of ConnectionFields,
384 and input and output activities, using the given learning_rate.
385 """
386 raise NotImplementedError
387
388
395
396
412
413
415 """
416 Type for an object that applies some operation (typically something
417 like normalization) to all CFs in a CFProjection for which the specified
418 mask (typically the activity at the destination of this projection)
419 is nonzero.
420 """
421 __abstract = True
422
423 - def __call__(self, iterator, **params):
424 """Operate on each CF for which the mask is nonzero."""
425 raise NotImplementedError
426
427
443
444
456
457
458
459
461 """
462 A projection composed of ConnectionFields from a Sheet into a ProjectionSheet.
463
464 CFProjection computes its activity using a response_fn of type
465 CFPResponseFn (typically a CF-aware version of mdot) and output_fns
466 (typically none). The initial contents of the
467 ConnectionFields mapping from the input Sheet into the target
468 ProjectionSheet are controlled by the weights_generator, cf_shape,
469 and weights_output_fn parameters, while the location of the
470 ConnectionField is controlled by the coord_mapper parameter.
471
472 Any subclass has to implement the interface
473 activate(self,input_activity) that computes the response from the
474 input and stores it in the activity array.
475 """
476
477 response_fn = param.ClassSelector(CFPResponseFn,
478 default=CFPRF_Plugin(),
479 doc='Function for computing the Projection response to an input pattern.')
480
481 cf_type = param.Parameter(default=ConnectionField,constant=True,
482 doc="Type of ConnectionField to use when creating individual CFs.")
483
484
485
486
487 allow_null_cfs = param.Boolean(default=False,
488 doc="Whether or not the projection can have entirely empty CFs")
489
490 nominal_bounds_template = BoundingRegionParameter(
491 default=BoundingBox(radius=0.1),doc="""
492 Bounds defining the Sheet area covered by a prototypical ConnectionField.
493 The true bounds will differ depending on the density (see create_slice_template()).""")
494
495 weights_generator = param.ClassSelector(PatternGenerator,
496 default=patterngenerator.Constant(),constant=True,
497 doc="Generate initial weights values.")
498
499 cf_shape = param.ClassSelector(PatternGenerator,
500 default=patterngenerator.Constant(),constant=True,
501 doc="Mask pattern to define the shape of the connection fields.")
502
503 same_cf_shape_for_all_cfs = param.Boolean(default=True,doc="""
504 Whether or not to share a single cf_shape mask for all CFs.
505 If True, the cf_shape is evaluated only once and shared for
506 all CFs, which saves computation time and memory. If False,
507 the cf_shape is evaluated once for each CF, allowing each to
508 have its own shape.""")
509
510 learning_fn = param.ClassSelector(CFPLearningFn,
511 default=CFPLF_Plugin(),
512 doc='Function for computing changes to the weights based on one activation step.')
513
514
515 learning_rate = param.Number(default=0.0,softbounds=(0,100),doc="""
516 Amount of learning at each step for this projection, specified
517 in units that are independent of the density of each Sheet.""")
518
519 weights_output_fns = param.HookList(default=[CFPOF_Plugin()],
520 class_=CFPOutputFn,
521 doc='Functions applied to each CF after learning.')
522
523 strength = param.Number(default=1.0,doc="""
524 Global multiplicative scaling applied to the Activity of this Sheet.""")
525
526 coord_mapper = param.ClassSelector(CoordinateMapperFn,
527 default=IdentityMF(),
528 doc='Function to map a projected coordinate into the target sheet.')
529
530
531
532
533 autosize_mask = param.Boolean(
534 default=True,constant=True,precedence=-1,doc="""
535 Topographica sets the mask size so that it is the same as the connection field's
536 size, unless this parameter is False - in which case the user-specified size of
537 the cf_shape is used. In normal usage of Topographica, this parameter should
538 remain True.""")
539
540 mask_threshold = param.Number(default=0.5,constant=True,doc="""
541 If a unit is above this value in the cf_shape mask, it is
542 included; otherwise it is excluded from the mask.""")
543
544 apply_output_fns_init=param.Boolean(default=True,doc="""
545 Whether to apply the output function to connection fields (e.g. for
546 normalization) when the CFs are first created.""")
547
548 min_matrix_radius = param.Integer(default=1,bounds=(0,None),doc="""
549 Enforced minimum for radius of weights matrix.
550 The default of 1 gives a minimum matrix of 3x3. 0 would
551 allow a 1x1 matrix.""")
552
553
554 precedence = param.Number(default=0.8)
555
556
557 - def __init__(self,initialize_cfs=True,**params):
558 """
559 Initialize the Projection with a set of cf_type objects
560 (typically ConnectionFields), each located at the location
561 in the source sheet corresponding to the unit in the target
562 sheet. The cf_type objects are stored in the 'cfs' array.
563
564 The nominal_bounds_template specified may be altered: the
565 bounds must be fitted to the Sheet's matrix, and the weights
566 matrix must have odd dimensions. These altered bounds are
567 passed to the individual connection fields.
568
569 A mask for the weights matrix is constructed. The shape is
570 specified by cf_shape; the size defaults to the size
571 of the nominal_bounds_template.
572 """
573 super(CFProjection,self).__init__(**params)
574
575 self.weights_generator.set_dynamic_time_fn(None,sublistattr='generators')
576
577
578
579 self._slice_template = Slice(copy(self.nominal_bounds_template),
580 self.src,force_odd=True,
581 min_matrix_radius=self.min_matrix_radius)
582
583 self.bounds_template = self._slice_template.compute_bounds(self.src)
584
585 self.mask_template = _create_mask(self.cf_shape,self.bounds_template,
586 self.src,self.autosize_mask,
587 self.mask_threshold)
588
589 self.n_units = self._calc_n_units()
590
591 if initialize_cfs:
592 self._create_cfs()
593
594
595
596
597
598
599 self.input_buffer = None
600 self.activity = array(self.dest.activity)
601
602
610
611
612
617
618
620 """
621 Create a ConnectionField at x,y in the src sheet.
622 """
623
624
625
626 try:
627 if self.apply_output_fns_init:
628 ofs = [wof.single_cf_fn for wof in self.weights_output_fns]
629 else:
630 ofs = []
631
632 if self.same_cf_shape_for_all_cfs:
633 mask_template = self.mask_template
634 else:
635 mask_template = _create_mask(self.cf_shape,self.bounds_template,
636 self.src,self.autosize_mask,
637 self.mask_threshold)
638
639 CF = self.cf_type(self.src,x=x,y=y,
640 template=self._slice_template,
641 weights_generator=self.weights_generator,
642 mask=mask_template,
643 output_fns=ofs,
644 min_matrix_radius=self.min_matrix_radius)
645 except NullCFError:
646 if self.allow_null_cfs:
647 CF = None
648 else:
649 raise
650
651 return CF
652
653
654
656 """Return the number of unmasked units in a typical ConnectionField."""
657
658 return min(len(self.mask_template.ravel().nonzero()[0]),
659
660
661
662 self.src.shape[0]*self.src.shape[1])
663
664
666 """Return the specified ConnectionField"""
667
668 self.warning("CFProjection.cf(r,c) is deprecated: use cfs[r,c] instead")
669 return self.cfs[r,c]
670
671
673 """Return the bounds of the specified ConnectionField."""
674 return self.cfs[r,c].get_bounds(self.src)
675
676
677 - def get_view(self, sheet_x, sheet_y, timestamp):
678 """
679 Return a single connection field UnitView, for the unit
680 located nearest to sheet coordinate (sheet_x,sheet_y).
681 """
682 matrix_data = zeros(self.src.activity.shape,Float)
683 (r,c) = self.dest.sheet2matrixidx(sheet_x,sheet_y)
684 r1,r2,c1,c2 = self.cfs[r,c].input_sheet_slice
685 matrix_data[r1:r2,c1:c2] = self.cfs[r,c].weights
686
687
688
689
690
691
692
693 return UnitView((matrix_data,self.src.bounds),sheet_x,sheet_y,self,timestamp)
694
695
697 """Activate using the specified response_fn and output_fn."""
698 self.input_buffer = input_activity
699 self.activity *=0.0
700 self.response_fn(MaskedCFIter(self), input_activity, self.activity, self.strength)
701 for of in self.output_fns:
702 of(self.activity)
703
704
705
706
708 """
709 For a CFProjection, learn consists of calling the learning_fn.
710 """
711
712
713 if self.input_buffer != None:
714 self.learning_fn(MaskedCFIter(self),self.input_buffer,self.dest.activity,self.learning_rate)
715
716
717
718
720 """
721 Apply the weights_output_fns to each unit.
722
723 If active_units_mask is True, inactive units will be skipped.
724 """
725 for of in self.weights_output_fns:
726 of(MaskedCFIter(self,active_units_mask=active_units_mask))
727
728
729
739
740
748
749
756
757
758
759
760 -def _create_mask(shape,bounds_template,sheet,autosize=True,threshold=0.5):
761 """
762 Create the mask (see ConnectionField.__init__()).
763 """
764
765
766 if hasattr(shape, 'size') and autosize:
767 l,b,r,t = bounds_template.lbrt()
768 shape.size = t-b
769 shape.aspect_ratio = (r-l)/shape.size
770
771
772 center_r,center_c = sheet.sheet2matrixidx(0,0)
773 center_x,center_y = sheet.matrixidx2sheet(center_r,center_c)
774
775 mask = shape(x=center_x,y=center_y,
776 bounds=bounds_template,
777 xdensity=sheet.xdensity,
778 ydensity=sheet.ydensity)
779
780 mask = where(mask>=threshold,mask,0.0)
781
782
783 return mask.astype(weight_type)
784
785
786
787 import numpy
789 """
790 Iterator to walk through all ConnectionFields of all neurons in
791 the destination Sheet of the given CFProjection. Each iteration
792 yields the tuple (cf,i) where cf is the ConnectionField at
793 position i in the projection's flatcfs list.
794
795 If active_units_mask is True, inactive units will be skipped. If
796 ignore_sheet_mask is True, even units excluded by the sheet mask
797 will be included.
798 """
799
800
801
802 - def __init__(self,cfprojection,active_units_mask=False,ignore_sheet_mask=False):
813
815
816
817
818
819 return numpy.ones(self.activity.shape,dtype=self.activity.dtype)
820
821
823 if not self.ignore_sheet_mask:
824 return self.mask.data
825 else:
826 return self.__nomask()
827
828
834
835
837 """
838 Return an array indicating whether or not each unit should be
839 processed.
840 """
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856 sheet_mask = self.get_sheet_mask()
857 active_units_mask = self.get_active_units_mask()
858 return numpy.logical_and(sheet_mask,active_units_mask)
859
860
867
868
869 MaskedCFIter = CFIter
870
871
872
873
874
875
877 """
878 A ProjectionSheet providing access to the ConnectionFields in its CFProjections.
879
880 CFSheet is a Sheet built from units indexed by Sheet coordinates
881 (x,y). Each unit can have one or more ConnectionFields on another
882 Sheet (via this sheet's CFProjections). Thus CFSheet is a more
883 concrete version of a ProjectionSheet; a ProjectionSheet does not
884 require that there be units or weights of any kind. Unless you
885 need access to the underlying ConnectionFields for visualization
886 or analysis, CFSheet and ProjectionSheet are interchangeable.
887 """
888
889 measure_maps = param.Boolean(True,doc="""
890 Whether to include this Sheet when measuring various maps to create SheetViews.""")
891
892 precedence = param.Number(0.5)
893
894
912
913
914
917
918
919
920
922 """
923 A CFProjection with resizable weights.
924 """
925
926
927
928