1
2
3
4 """ Contains the class _Network_ which is used to represent neural nets
5
6 **Network Architecture:**
7
8 A tacit assumption in all of this stuff is that we're dealing with
9 feedforward networks.
10
11 The network itself is stored as a list of _NetNode_ objects. The list
12 is ordered in the sense that nodes in earlier/later layers than a
13 given node are guaranteed to come before/after that node in the list.
14 This way we can easily generate the values of each node by moving
15 sequentially through the list, we're guaranteed that every input for a
16 node has already been filled in.
17
18 Each node stores a list (_inputNodes_) of indices of its inputs in the
19 main node list.
20
21 """
22 from __future__ import print_function
23 import numpy
24 import random
25
26 from rdkit.ML.Neural import NetNode, ActFuncs
27
28
29
30
31
32
34 """ a neural network
35
36 """
37
39 """initialize all the weights in the network to random numbers
40
41 **Arguments**
42
43 - minWeight: the minimum value a weight can take
44
45 - maxWeight: the maximum value a weight can take
46
47 """
48 for node in self.nodeList:
49 inputs = node.GetInputs()
50 if inputs:
51 weights = [random.uniform(minWeight, maxWeight) for _ in range(len(inputs))]
52 node.SetWeights(weights)
53
55 """ Fully connects each layer in the network to the one above it
56
57
58 **Note**
59 this sets the connections, but does not assign weights
60
61 """
62 nodeList = list(range(self.numInputNodes))
63 nConnections = 0
64 for layer in range(self.numHiddenLayers):
65 for i in self.layerIndices[layer + 1]:
66 self.nodeList[i].SetInputs(nodeList)
67 nConnections = nConnections + len(nodeList)
68 nodeList = self.layerIndices[layer + 1]
69
70 for i in self.layerIndices[-1]:
71 self.nodeList[i].SetInputs(nodeList)
72 nConnections = nConnections + len(nodeList)
73 self.nConnections = nConnections
74
76 """ build an unconnected network and set node counts
77
78 **Arguments**
79
80 - nodeCounts: a list containing the number of nodes to be in each layer.
81 the ordering is:
82 (nInput,nHidden1,nHidden2, ... , nHiddenN, nOutput)
83
84 """
85 self.nodeCounts = nodeCounts
86 self.numInputNodes = nodeCounts[0]
87 self.numOutputNodes = nodeCounts[-1]
88 self.numHiddenLayers = len(nodeCounts) - 2
89 self.numInHidden = [None] * self.numHiddenLayers
90 for i in range(self.numHiddenLayers):
91 self.numInHidden[i] = nodeCounts[i + 1]
92
93 numNodes = sum(self.nodeCounts)
94 self.nodeList = [None] * (numNodes)
95 for i in range(numNodes):
96 self.nodeList[i] = NetNode.NetNode(i, self.nodeList, actFunc=actFunc,
97 actFuncParms=actFuncParms)
98
99 self.layerIndices = [None] * len(nodeCounts)
100 start = 0
101 for i in range(len(nodeCounts)):
102 end = start + nodeCounts[i]
103 self.layerIndices[i] = list(range(start, end))
104 start = end
105
110
112 """ returns a list of output node indices
113 """
114 return self.layerIndices[-1]
115
117 """ returns a list of hidden nodes in the specified layer
118 """
119 return self.layerIndices[which + 1]
120
122 """ returns the total number of nodes
123 """
124 return sum(self.nodeCounts)
125
127 """ returns the number of hidden layers
128 """
129 return self.numHiddenLayers
130
132 """ returns a particular node
133 """
134 return self.nodeList[which]
135
137 """ returns a list of all nodes
138 """
139 return self.nodeList
140
142 """ classifies a given example and returns the results of the output layer.
143
144 **Arguments**
145
146 - example: the example to be classified
147
148 **NOTE:**
149
150 if the output layer is only one element long,
151 a scalar (not a list) will be returned. This is why a lot of the other
152 network code claims to only support single valued outputs.
153
154 """
155 if len(example) > self.numInputNodes:
156 if len(example) - self.numInputNodes > self.numOutputNodes:
157 example = example[1:-self.numOutputNodes]
158 else:
159 example = example[:-self.numOutputNodes]
160 assert len(example) == self.numInputNodes
161 totNumNodes = sum(self.nodeCounts)
162 results = numpy.zeros(totNumNodes, numpy.float64)
163 for i in range(self.numInputNodes):
164 results[i] = example[i]
165 for i in range(self.numInputNodes, totNumNodes):
166 self.nodeList[i].Eval(results)
167 self.lastResults = results[:]
168 if self.numOutputNodes == 1:
169 return results[-1]
170 else:
171 return results
172
174 """ returns the complete list of output layer values from the last time this node
175 classified anything"""
176 return self.lastResults
177
179 """ provides a string representation of the network """
180 outStr = 'Network:\n'
181 for i in range(len(self.nodeList)):
182 outStr = outStr + '\tnode(% 3d):\n' % i
183 outStr = outStr + '\t\tinputs: %s\n' % (str(self.nodeList[i].GetInputs()))
184 outStr = outStr + '\t\tweights: %s\n' % (str(self.nodeList[i].GetWeights()))
185
186 outStr = outStr + 'Total Number of Connections: % 4d' % self.nConnections
187 return outStr
188
189 - def __init__(self, nodeCounts, nodeConnections=None, actFunc=ActFuncs.Sigmoid, actFuncParms=(),
190 weightBounds=1):
191 """ Constructor
192
193 This constructs and initializes the network based upon the specified
194 node counts.
195
196 A fully connected network with random weights is constructed.
197
198 **Arguments**
199
200 - nodeCounts: a list containing the number of nodes to be in each layer.
201 the ordering is:
202 (nInput,nHidden1,nHidden2, ... , nHiddenN, nOutput)
203
204 - nodeConnections: I don't know why this is here, but it's optional. ;-)
205
206 - actFunc: the activation function to be used here. Must support the API
207 of _ActFuncs.ActFunc_.
208
209 - actFuncParms: a tuple of extra arguments to be passed to the activation function
210 constructor.
211
212 - weightBounds: a float which provides the boundary on the random initial weights
213
214 """
215 self.ConstructNodes(nodeCounts, actFunc, actFuncParms)
216 self.FullyConnectNodes()
217 self.ConstructRandomWeights(minWeight=-weightBounds, maxWeight=weightBounds)
218 self.lastResults = []
219
220
221 if __name__ == '__main__':
222
223 print('[2,2,2]')
224 net = Network([2, 2, 2])
225 print(net)
226
227 print('[2,4,1]')
228 net = Network([2, 4, 1])
229 print(net)
230
231 print('[2,2]')
232 net = Network([2, 2])
233 print(net)
234 inp = [1, 0]
235 res = net.ClassifyExample(inp)
236 print(inp, '->', res)
237 inp = [0, 1]
238 res = net.ClassifyExample(inp)
239 print(inp, '->', res)
240 inp = [.5, .5]
241 res = net.ClassifyExample(inp)
242 print(inp, '->', res)
243