File: SOMTrain.html

package info (click to toggle)
supercollider-sc3-plugins 3.7.1~repack-2
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 14,332 kB
  • ctags: 11,704
  • sloc: cpp: 140,180; lisp: 14,746; ansic: 2,133; xml: 86; makefile: 82; haskell: 21; sh: 8
file content (143 lines) | stat: -rw-r--r-- 14,475 bytes parent folder | download | duplicates (3)
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
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta http-equiv="Content-Style-Type" content="text/css">
<title></title>
<meta name="Generator" content="Cocoa HTML Writer">
<meta name="CocoaVersion" content="824.48">
<style type="text/css">
p.p1 {margin: 0.0px 0.0px 0.0px 0.0px; font: 18.0px Helvetica}
p.p2 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; min-height: 14.0px}
p.p3 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica}
p.p4 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #8a2617}
p.p5 {margin: 0.0px 0.0px 0.0px 0.0px; font: 12.0px Helvetica; color: #606060}
span.s1 {color: #4972b9}
span.s2 {color: #0047e8}
span.s3 {color: #4972ba}
span.s4 {color: #0000ff}
span.s5 {color: #0033ae}
span.s6 {color: #8a2617}
span.s7 {color: #000000}
span.s8 {color: #2862cf}
span.s9 {color: #1354dc}
span.s10 {color: #606060}
span.s11 {color: #4c6a27}
span.Apple-tab-span {white-space:pre}
</style>
</head>
<body>
<p class="p1"><b>SOMTrain<span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>Create (train) a Self-Organising Map</b></p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-tab-span">	</span><b>SOMTrain.kr(bufnum, inputdata, netsize, numdims, traindur, nhood, gate, initweight)</b></p>
<p class="p2"><br></p>
<p class="p3">This UGen trains a <i>self-organising map</i> (a well-known type of neural net), which is a system which learns to map a high-dimensional feed of input data onto a lower-dimensional array of "nodes". The neural net is stored in a <a href="SC://Buffer"><span class="s1">Buffer</span></a>. Once trained, the net can be analysed or can be used to transform other incoming data (using <a href="SOMRd.html"><span class="s2">SOMRd</span></a>).</p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>bufnum</b><span class="Apple-tab-span">	</span>- A reference to the buffer where the map will be created. Initialising the map is up to you (see below).</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>inputdata</b><span class="Apple-tab-span">	</span>- An <a href="SC://Array"><span class="s3">Array</span></a> of the input signals for the net to learn.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>netsize</b><span class="Apple-tab-span">	</span>- The size of the neural net along one dimension.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>numdims</b> - The dimensionality of the neural net. Choose from 1, 2, 3, or 4.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>traindur</b><span class="Apple-tab-span">	</span>- The length of the training period; the number of data frames that will come in before the net gradually "freezes" into its final state</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>nhood</b><span class="Apple-tab-span">	</span>- The <i>initial</i> size of the neighbourhood used in training. (The size always shrinks to zero as the training progresses.) The size is expressed as a fraction of <b>netsize</b>. Default is 0.5, and you probably don't need to change it.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>gate</b> <span class="Apple-tab-span">	</span>-<span class="Apple-converted-space">  </span>A simple on-or-off control. When off (gate&lt;=0) the incoming data is ignored.</p>
<p class="p3"><span class="Apple-converted-space"> </span>- <b>initweight</b><span class="Apple-tab-span">	</span>- How heavily the algorithm weights the data points at first (the weighting always tails off to zero as the training progresses, in a reciprocal curve). Default is 1, and you usually don't need to change this.</p>
<p class="p2"><br></p>
<p class="p3">The UGen outputs an array of three values: the number of data points still to come in before training finishes; the "reconstruction error" of the single data point that has most recently been input (the squared-distance between it and the node nearest it); and the frame index of the most recent matching node. The "reconstruction error" will vary a lot but should in general decrease as the net comes closer and closer to mapping the data well. The frame index gives a direct index into the Buffer, and can be converted to a multidimensional location in the SOM structure using <a href="SOMRd.html"><span class="s4">SOMRd</span></a>.bufIndexToCoords.</p>
<p class="p2"><br></p>
<p class="p3">Note: this UGen does <i>not</i> cope well if the buffer is freed or changed during running. For efficiency purposes, it doesn't keep checking the buffer while running; so you should avoid changing the buffer while the training is running.</p>
<p class="p2"><br></p>
<p class="p3"><b>BUFFER SIZE AND NUMBER OF NODES/DIMENSIONS:</b></p>
<p class="p2"><br></p>
<p class="p3">The number of nodes in the net is defined by netsize AND numdims.<span class="Apple-converted-space"> </span></p>
<p class="p3">If the netsize is 10 and the numdims is 2, the actual number of nodes is 10 x 10 = 100.<span class="Apple-converted-space"> </span></p>
<p class="p3">Or if numdims is 3, the number of nodes is 10 x 10 x 10 = 1000.</p>
<p class="p2"><br></p>
<p class="p3">The buffer must contain the same number of frames as the number of nodes; and it must have the same number of channels as the <b>inputdata</b> array. The SOMTrain class provides a convenience function for allocating a buffer of the right size:</p>
<p class="p2"><br></p>
<p class="p3"><span class="Apple-tab-span">	</span>~asuitablebuffer = <span class="s5">SOMTrain</span>.allocBuf(s, netsize, numdims, numinputdims); <span class="s6">// Calls Buffer.alloc on your behalf</span></p>
<p class="p2"><br></p>
<p class="p3"><b>INITIALISING THE MAP</b></p>
<p class="p2"><br></p>
<p class="p3">The values held by the SOM nodes must usually be initialised to some state before the training begins. In the literature on SOMs there are a couple of common options such as random intialisation, or initialisation using the principal components of the input data. It's up to you - since the map is just a regular Buffer object, you can use Buffer's loadCollection methods (etc) to fill the map in any way. However, the SOMTrain class provides a couple of conveniences:</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufRand(b); </span>// Each node gets an independent randomly-distributed co-ordinate (i.e. ignores where its neighbours might be)</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufGrid(b, netsize, numdims, spinmatrix); </span>// The nodes are initialised as a grid, oriented according to the supplied rotation matrix (nested array, size [numinputdims][numdims])</p>
<p class="p2"><br></p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span><span class="s5">SOMTrain</span><span class="s7">.initBufGridRand(b, netsize, numdims); </span>// The nodes are initialised as a grid, randomly oriented in the input space</p>
<p class="p2"><br></p>
<p class="p3">The first of these options is simple but not recommended: starting with a completely random arrangement of nodes is quite likely to lead to a resulting map with "twists" in it, which may be an unhelpful local-minimum solution.</p>
<p class="p2"><br></p>
<p class="p2"><br></p>
<p class="p3"><b>Examples</b></p>
<p class="p2"><br></p>
<p class="p3">An SOM will try to fit a smooth-ish surface to the given data, so as a test case let's create a single sine-wave undulation in a Buffer, and see if a one-dimensional SOM can fit nicely to that sinewave. (For higher dimension examples see <a href="SOMTrain_2D_example.html"><span class="s8">SOMTrain_2D_example</span></a>, <a href="SOMTrain_3D_example.html"><span class="s9">SOMTrain_3D_example</span></a>)</p>
<p class="p2"><br></p>
<p class="p3">s.boot;</p>
<p class="p3">~numnodes = 20;</p>
<p class="p3">~traindur = 10000;</p>
<p class="p4">// First create the data</p>
<p class="p3">~dataspace = <span class="s5">Buffer</span>.alloc(s, s.sampleRate); <span class="s6">// Let's have 1 second of data</span></p>
<p class="p3">~dataspace.sine1([1], <span class="s5">true</span>, <span class="s5">false</span>);</p>
<p class="p3">~dataspace.plot;</p>
<p class="p4">// Allocate space for the SOM, and initialise it</p>
<p class="p3">~som = <span class="s5">SOMTrain</span>.allocBuf(s, ~numnodes, 1, 2);</p>
<p class="p4">// Typically wewouldn't recommend initBufRand, so let's use initBufGridRand:</p>
<p class="p3"><span class="s5">SOMTrain</span>.initBufGridRand(~som, ~numnodes, 1, [1,0], 1.5);</p>
<p class="p4"><span class="s7">~som.plot(minval:</span><span class="s5">nil</span><span class="s7">.dup, maxval: </span><span class="s5">nil</span><span class="s7">.dup); </span>// Quick peek at how the SOM nodes are initially positioned</p>
<p class="p2"><br></p>
<p class="p4">// Also, just for demonstration purposes, we're going to write the error values to a buffer so we can see how they evolve</p>
<p class="p3">~errorvals = <span class="s5">Buffer</span>.alloc(s, ~traindur);</p>
<p class="p2"><br></p>
<p class="p4">// Now train the SOM. To train it with data "randomly sampled" from the data buffer, we just wiggle the phase around and feed [phase, val] to the SOM.</p>
<p class="p3">(</p>
<p class="p3">x = {</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">var</span> phase, val, remain, err, loc, trig;</p>
<p class="p3"><span class="Apple-tab-span">	</span>trig = 1;</p>
<p class="p3"><span class="Apple-tab-span">	</span>phase = <span class="s5">WhiteNoise</span>.kr.range(0, ~dataspace.numFrames-1);</p>
<p class="p3"><span class="Apple-tab-span">	</span>val = <span class="s5">BufRd</span>.kr(1, ~dataspace, phase, 1);</p>
<p class="p3"><span class="Apple-tab-span">	</span>phase.poll(trig, label: <span class="s10">"phase"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>val.poll(trig, label: <span class="s10">"val"</span>);</p>
<p class="p4"><span class="s7"><span class="Apple-tab-span">	</span></span>// Note: phase is rescaled to 0--2 range here, so it can be similar range to other dimension</p>
<p class="p3"><span class="Apple-tab-span">	</span># remain, err, loc = <span class="s5">SOMTrain</span>.kr(~som, [phase*2/s.sampleRate, val], ~numnodes, 1, ~traindur, nhood: 0.75, gate: trig);</p>
<p class="p3"><span class="Apple-tab-span">	</span>remain.poll(trig, label: <span class="s10">"remain"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>err.poll(trig, label: <span class="s10">"err"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span>loc.poll(trig, label: <span class="s10">"wrote at frame index"</span>);</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">BufWr</span>.kr(err.sqrt, ~errorvals, ~traindur-remain);</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="s5">Out</span>.ar(0, <span class="s5">Pan2</span>.ar(<span class="s5">K2A</span>.ar(val) * 0.01));</p>
<p class="p3">}.play</p>
<p class="p3">)</p>
<p class="p4">// Once the training is finished (i.e. once "remain" drops to zero):</p>
<p class="p3">x.free;</p>
<p class="p4">// We should be able to look at the 2D data points represented in the SOM and see if they line up with the sinewave.</p>
<p class="p4">// This plot is nice if you have OctaveSC installed:</p>
<p class="p3">o = <span class="s5">OctaveSC</span>.new;</p>
<p class="p3">o.init</p>
<p class="p3">(</p>
<p class="p3">~som.loadToFloatArray(action: {<span class="s5">|arr|</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>o[<span class="s11">\xtemp</span>] = arr[0,2..].as(<span class="s5">Array</span>).postln;</p>
<p class="p3"><span class="Apple-tab-span">	</span>o[<span class="s11">\ytemp</span>] = arr[1,3..].as(<span class="s5">Array</span>).postln;</p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"figure; hold off"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"plot(xtemp, ytemp, '-@')"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"hold on"<span class="s7">);</span></p>
<p class="p5"><span class="s7"><span class="Apple-tab-span">	</span>o.(</span>"spls = [0:0.01:2]; plot(spls, sin(spls*2*pi/2))"<span class="s7">);</span></p>
<p class="p3">});</p>
<p class="p3">);</p>
<p class="p4">// Otherwise here's a quick 2D plot in pure SC - does it look like the sinewave you started with?</p>
<p class="p3">(</p>
<p class="p3">~som.loadToFloatArray(action: {<span class="s5">|arr|</span> {</p>
<p class="p3"><span class="Apple-tab-span">	</span>w = <span class="s5">GUI</span>.window.new(<span class="s10">"SOM nodes"</span>, <span class="s5">Rect</span>(200, 800, 600, 600)); <span class="s6">// SCWindow</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>w.view.background = <span class="s5">Color</span>.white;</p>
<p class="p3"><span class="Apple-tab-span">	</span>(0,2..arr.size-1).do{<span class="s5">|index|</span></p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="s5">GUI</span>.staticText.new(w, <span class="s5">Rect</span>(</p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>arr[index] * 600 / 2,<span class="Apple-converted-space"> </span></p>
<p class="p3"><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span><span class="Apple-tab-span">	</span>600 - (arr[index+1] + 1 * 300), 10, 10)).string_(<span class="s10">"x"</span>) <span class="s6">// SCStaticText</span></p>
<p class="p3"><span class="Apple-tab-span">	</span>};</p>
<p class="p3"><span class="Apple-tab-span">	</span>w.front;</p>
<p class="p3">}.defer})</p>
<p class="p3">)</p>
<p class="p2"><br></p>
<p class="p4">// How do the errors look? Should be a very noisy curve gradually decreasing, but won't necessarily stabilise</p>
<p class="p3">~errorvals.plot(maxval: <span class="s5">nil</span>, minval: <span class="s5">nil</span>);</p>
</body>
</html>