1
2
3
4
5
6 """ Contains the class _Network_ which is used to represent neural nets
7
8 **Network Architecture:**
9
10 A tacit assumption in all of this stuff is that we're dealing with
11 feedforward networks.
12
13 The network itself is stored as a list of _NetNode_ objects. The list
14 is ordered in the sense that nodes in earlier/later layers than a
15 given node are guaranteed to come before/after that node in the list.
16 This way we can easily generate the values of each node by moving
17 sequentially through the list, we're guaranteed that every input for a
18 node has already been filled in.
19
20 Each node stores a list (_inputNodes_) of indices of its inputs in the
21 main node list.
22
23 """
24 from __future__ import print_function
25 import numpy
26 import random
27
28 from rdkit.six.moves import xrange
29 from rdkit.ML.Neural import NetNode, ActFuncs
30
31
32
33
34
36 """ a neural network
37
38 """
40 """initialize all the weights in the network to random numbers
41
42 **Arguments**
43
44 - minWeight: the minimum value a weight can take
45
46 - maxWeight: the maximum value a weight can take
47
48 """
49 for node in self.nodeList:
50 inputs = node.GetInputs()
51 if inputs:
52 weights = [random.uniform(minWeight,maxWeight) for x in range(len(inputs))]
53 node.SetWeights(weights)
54
55
57 """ Fully connects each layer in the network to the one above it
58
59
60 **Note**
61 this sets the connections, but does not assign weights
62
63 """
64 nodeList = range(self.numInputNodes)
65 nConnections = 0
66 for layer in xrange(self.numHiddenLayers):
67 for i in self.layerIndices[layer+1]:
68 self.nodeList[i].SetInputs(nodeList)
69 nConnections = nConnections + len(nodeList)
70 nodeList = self.layerIndices[layer+1]
71
72 for i in self.layerIndices[-1]:
73 self.nodeList[i].SetInputs(nodeList)
74 nConnections = nConnections + len(nodeList)
75 self.nConnections = nConnections
76
78 """ build an unconnected network and set node counts
79
80 **Arguments**
81
82 - nodeCounts: a list containing the number of nodes to be in each layer.
83 the ordering is:
84 (nInput,nHidden1,nHidden2, ... , nHiddenN, nOutput)
85
86 """
87 self.nodeCounts = nodeCounts
88 self.numInputNodes = nodeCounts[0]
89 self.numOutputNodes = nodeCounts[-1]
90 self.numHiddenLayers = len(nodeCounts)-2
91 self.numInHidden = [None]*self.numHiddenLayers
92 for i in xrange(self.numHiddenLayers):
93 self.numInHidden[i] = nodeCounts[i+1]
94
95 numNodes = sum(self.nodeCounts)
96 self.nodeList = [None]*(numNodes)
97 for i in xrange(numNodes):
98 self.nodeList[i] = NetNode.NetNode(i,self.nodeList,
99 actFunc=actFunc,
100 actFuncParms=actFuncParms)
101
102 self.layerIndices = [None]*len(nodeCounts)
103 start = 0
104 for i in xrange(len(nodeCounts)):
105 end = start + nodeCounts[i]
106 self.layerIndices[i] = range(start,end)
107 start = end
108
114 """ returns a list of output node indices
115 """
116 return self.layerIndices[-1]
118 """ returns a list of hidden nodes in the specified layer
119 """
120 return self.layerIndices[which+1]
121
123 """ returns the total number of nodes
124 """
125 return sum(self.nodeCounts)
126
128 """ returns the number of hidden layers
129 """
130 return self.numHiddenLayers
131
133 """ returns a particular node
134 """
135 return self.nodeList[which]
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 xrange(self.numInputNodes):
164 results[i] = example[i]
165 for i in xrange(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 classified anything"""
175 return self.lastResults
176
178 """ provides a string representation of the network """
179 outStr = 'Network:\n'
180 for i in xrange(len(self.nodeList)):
181 outStr = outStr + '\tnode(% 3d):\n'%i
182 outStr = outStr + '\t\tinputs: %s\n'%(str(self.nodeList[i].GetInputs()))
183 outStr = outStr + '\t\tweights: %s\n'%(str(self.nodeList[i].GetWeights()))
184
185 outStr = outStr + 'Total Number of Connections: % 4d'%self.nConnections
186 return outStr
187
188 - def __init__(self,nodeCounts,nodeConnections=None,
189 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
216 """
217 self.ConstructNodes(nodeCounts,actFunc,actFuncParms)
218 self.FullyConnectNodes()
219 self.ConstructRandomWeights(minWeight=-weightBounds,maxWeight=weightBounds)
220 self.lastResults = []
221
222 if __name__ == '__main__':
223
224 print('[2,2,2]')
225 net = Network([2,2,2])
226 print(net)
227
228 print('[2,4,1]')
229 net = Network([2,4,1])
230 print(net)
231
232 print('[2,2]')
233 net = Network([2,2])
234 print(net)
235 input = [1,0]
236 res = net.ClassifyExample(input)
237 print(input,'->',res)
238 input = [0,1]
239 res = net.ClassifyExample(input)
240 print(input,'->',res)
241 input = [.5,.5]
242 res = net.ClassifyExample(input)
243 print(input,'->',res)
244