rdkit.ML.Neural.Network

Package rdkit :: Package ML :: Package Neural :: Module Network |
[hide private]
[frames] | no frames]

# Source Code for Module rdkit.ML.Neural.Network

      1  #
      2  #  Copyright (C) 2000-2008  greg Landrum
      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  # FIX: this class has not been updated to new-style classes
     30  # (RD Issue380) because that would break all of our legacy pickled
     31  # data. Until a solution is found for this breakage, an update is
     32  # impossible.
     33 -class Network:
     34    """ a neural network
     35
     36    """
     37
     38 -  def ConstructRandomWeights(self, minWeight=-1, maxWeight=1):
     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
     54 -  def FullyConnectNodes(self):
     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
     75 -  def ConstructNodes(self, nodeCounts, actFunc, actFuncParms):
     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
    106 -  def GetInputNodeList(self):
    107      """ returns a list of input node indices
    108      """
    109      return self.layerIndices[0]
    110
    111 -  def GetOutputNodeList(self):
    112      """ returns a list of output node indices
    113      """
    114      return self.layerIndices[-1]
    115
    116 -  def GetHiddenLayerNodeList(self, which):
    117      """ returns a list of hidden nodes in the specified layer
    118      """
    119      return self.layerIndices[which + 1]
    120
    121 -  def GetNumNodes(self):
    122      """ returns the total number of nodes
    123      """
    124      return sum(self.nodeCounts)
    125
    126 -  def GetNumHidden(self):
    127      """ returns the number of hidden layers
    128      """
    129      return self.numHiddenLayers
    130
    131 -  def GetNode(self, which):
    132      """ returns a particular node
    133      """
    134      return self.nodeList[which]
    135
    136 -  def GetAllNodes(self):
    137      """ returns a list of all nodes
    138      """
    139      return self.nodeList
    140
    141 -  def ClassifyExample(self, example, appendExamples=0):
    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
    173 -  def GetLastOutputs(self):
    174      """ returns the complete list of output layer values from the last time this node
    175      classified anything"""
    176      return self.lastResults
    177
    178 -  def __str__(self):
    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__':  # pragma: nocover
    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