diff --git a/Makefile b/Makefile index 7f359df..a87ed1c 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,8 @@ -all: pypi +all: sphinx pypi conda + +sphinx: + sphinx-apidoc -o doc/modules neurodynex -f + make -C doc html pypi: rm -rf dist/* diff --git a/conda_build/meta.yaml b/conda_build/meta.yaml index 8923d12..3636c00 100644 --- a/conda_build/meta.yaml +++ b/conda_build/meta.yaml @@ -34,6 +34,7 @@ test: - neurodynex.hopfield_network - neurodynex.neuron_type - neurodynex.phase_plane_analysis + - neurodynex.ojas_rule - neurodynex.test commands: @@ -42,6 +43,7 @@ test: - nosetests --verbosity=2 neurodynex.test.test_hopfield - nosetests --verbosity=2 neurodynex.test.test_nagumo - nosetests --verbosity=2 neurodynex.test.test_neuron_type + - nosetests --verbosity=2 neurodynex.test.test_oja requires: - nose diff --git a/doc/exercises/index.rst b/doc/exercises/index.rst index b4a46f5..6161d31 100644 --- a/doc/exercises/index.rst +++ b/doc/exercises/index.rst @@ -12,4 +12,5 @@ Exercises hodgkin-huxley phase-plane-analysis neuron-type - hopfield-network \ No newline at end of file + hopfield-network + ojas-rule \ No newline at end of file diff --git a/doc/exercises/ojas-rule.rst b/doc/exercises/ojas-rule.rst new file mode 100644 index 0000000..46d7687 --- /dev/null +++ b/doc/exercises/ojas-rule.rst @@ -0,0 +1,58 @@ +Oja's hebbian learning rule +=========================== + +**Book chapters** + +See `Chapter 19 Section 2 `_ on the learning rule of Oja. + +.. _Chapter: http://neuronaldynamics.epfl.ch/online/Ch19.S2.html#SS1.p6 + + +**Python classes** + +The :mod:`.ojas_rule.oja` module contains all code required for this exercise. +At the beginning of your exercise solution file, import the contained functions by + +.. code-block:: py + + from neurodynex.ojas_rule.oja import * + +You can then simply run the exercise functions by executing, e.g. + +.. code-block:: py + + cloud = make_cloud() # generate data points + wcourse = learn(cloud) # learn weights and return timecourse + +Exercise: Circular data +----------------------- + +Use the functions :func:`make_cloud <.ojas_rule.oja.make_cloud>` and :func:`learn <.ojas_rule.oja.learn>` to get the timecourse for weights that are learned on a **circular** data cloud (``ratio=1``). Plot the time course +of both components of the weight vector. Repeat this many times (:func:`learn <.ojas_rule.oja.learn>` will choose random initial conditions on each run), and plot this into the same plot. Can you explain what happens? + + +Exercise: Elliptic data +----------------------- + + +Repeat the previous question with an **elongated** elliptic data cloud (e.g. ``ratio=0.3``). Again, repeat this several times. + +Question +~~~~~~~~ + +What difference in terms of learning do you observe with respect to the circular data clouds? + +Question +~~~~~~~~ + +Try to change the orientation of the ellipsoid (try several different angles). Can you explain what Oja's rule does? + +.. note:: + To gain more insight, plot the learned weight vector in 2D space, and relate its orientation to that of the ellipsoid of data clouds. + +Exercise: Non-centered data +--------------------------- + +The above exercises assume that the input activities can be negative (indeed the inputs were always statistically centered). In actual neurons, if we think of their activity as their firing rate, this cannot be less than zero. + +Try again the previous exercise, but applying the learning rule on a noncentered data cloud. E.g., use ``5 + make_cloud(...)``, which centers the data around ``(5,5)``. What conclusions can you draw? Can you think of a modification to the learning rule? \ No newline at end of file diff --git a/doc/modules/neurodynex.ojas_rule.rst b/doc/modules/neurodynex.ojas_rule.rst new file mode 100644 index 0000000..3fc4db2 --- /dev/null +++ b/doc/modules/neurodynex.ojas_rule.rst @@ -0,0 +1,22 @@ +neurodynex.ojas_rule package +============================ + +Submodules +---------- + +neurodynex.ojas_rule.oja module +------------------------------- + +.. automodule:: neurodynex.ojas_rule.oja + :members: + :undoc-members: + :show-inheritance: + + +Module contents +--------------- + +.. automodule:: neurodynex.ojas_rule + :members: + :undoc-members: + :show-inheritance: diff --git a/doc/modules/neurodynex.rst b/doc/modules/neurodynex.rst index 319d2b0..0b70646 100644 --- a/doc/modules/neurodynex.rst +++ b/doc/modules/neurodynex.rst @@ -10,6 +10,7 @@ Subpackages neurodynex.hopfield_network neurodynex.leaky_integrate_and_fire neurodynex.neuron_type + neurodynex.ojas_rule neurodynex.phase_plane_analysis neurodynex.test diff --git a/doc/modules/neurodynex.test.rst b/doc/modules/neurodynex.test.rst index 59255ed..379609a 100644 --- a/doc/modules/neurodynex.test.rst +++ b/doc/modules/neurodynex.test.rst @@ -44,6 +44,14 @@ neurodynex.test.test_neuron_type module :undoc-members: :show-inheritance: +neurodynex.test.test_oja module +------------------------------- + +.. automodule:: neurodynex.test.test_oja + :members: + :undoc-members: + :show-inheritance: + Module contents --------------- diff --git a/neurodynex/ojas_rule/__init__.py b/neurodynex/ojas_rule/__init__.py new file mode 100644 index 0000000..888d16e --- /dev/null +++ b/neurodynex/ojas_rule/__init__.py @@ -0,0 +1 @@ +__all__ = ['oja'] diff --git a/neurodynex/ojas_rule/oja.py b/neurodynex/ojas_rule/oja.py new file mode 100644 index 0000000..7c6e7e9 --- /dev/null +++ b/neurodynex/ojas_rule/oja.py @@ -0,0 +1,140 @@ +""" +This file implements Oja's hebbian learning rule. + +Relevant book chapters: + - http://neuronaldynamics.epfl.ch/online/Ch19.S2.html#SS1.p6 +""" + +# This file is part of the exercise code repository accompanying +# the book: Neuronal Dynamics (see http://neuronaldynamics.epfl.ch) +# located at http://github.com/EPFL-LCN/neuronaldynamics-exercises. + +# This free software: you can redistribute it and/or modify it under +# the terms of the GNU General Public License 2.0 as published by the +# Free Software Foundation. You should have received a copy of the +# GNU General Public License along with the repository. If not, +# see http://www.gnu.org/licenses/. + +# Should you reuse and publish the code for your own purposes, +# please cite the book or point to the webpage http://neuronaldynamics.epfl.ch. + +# Wulfram Gerstner, Werner M. Kistler, Richard Naud, and Liam Paninski. +# Neuronal Dynamics: From Single Neurons to Networks and Models of Cognition. +# Cambridge University Press, 2014. + +import matplotlib.pyplot as plt +import numpy as np + + +def make_cloud(n=10000, ratio=1, angle=0): + """Returns an oriented elliptic + gaussian cloud of 2D points + + Args: + n (int, optional): number of points in the cloud + ratio (int, optional): (std along the short axis) / + (std along the long axis) + angle (int, optional): rotation angle [deg] + + Returns: + numpy.ndarray: array of datapoints + """ + + if ratio > 1.: + ratio = 1. / ratio + + x = np.random.randn(n, 1) + y = ratio * np.random.randn(n, 1) + z = np.concatenate((x, y), 1) + radangle = (180. - angle) * np.pi / 180. + transfo = [ + [np.cos(radangle), np.sin(radangle)], + [-np.sin(radangle), np.cos(radangle)] + ] + return np.dot(transfo, z.T).T + + +def learn(cloud, initial_angle=None, eta=0.001): + """Run one batch of Oja's learning over + a cloud of datapoints + + Args: + cloud (numpy.ndarray): array of datapoints + initial_angle (float, optional): angle of initial + set of weights [deg]. If None, this is random. + eta (float, optional): learning rate + + Returns: + numpy.ndarray: time course of the weight vector + """ + + # get angle if not set + if initial_angle is None: + initial_angle = np.random.rand() * 360. + radangle = initial_angle * np.pi / 180. + + w = np.array([np.cos(radangle), np.sin(radangle)]) + wcourse = np.zeros((len(cloud), 2), float) + for i in range(0, len(cloud)): + wcourse[i] = w + y = np.dot(w, cloud[i]) # output + w = w + eta * y * (cloud[i] - y * w) # ojas rule + return wcourse + + +def run_oja(n=10000, ratio=1., angle=0., do_plot=True): + """Generates a point cloud and runs Oja's learning + rule once. Optionally plots the result. + + Args: + n (int, optional): number of points in the cloud + ratio (float, optional): (std along the short axis) / + (std along the long axis) + angle (float, optional): rotation angle [deg] + do_plot (bool, optional): plot the result + """ + + cloud = make_cloud(n=n, ratio=ratio, angle=angle) + wcourse = learn(cloud) + + if do_plot: + + # plot data cloud + plt.scatter( + cloud[:, 0], + cloud[:, 1], + marker='.', + facecolor='none', + edgecolor='#222222', + alpha=.2 + ) + + # color time and plot with colorbar + time = np.arange(len(wcourse)) + colors = plt.cm.cool(time/float(len(time))) + sm = plt.cm.ScalarMappable( + cmap=plt.cm.cool, + norm=plt.Normalize(vmin=0, vmax=n) + ) + sm.set_array(time) + cb = plt.colorbar(sm) + cb.set_label("Datapoints") + plt.scatter( + wcourse[:, 0], + wcourse[:, 1], + facecolor=colors, + edgecolor='none', + lw=2 + ) + + # ensure rectangular plot + x_min = cloud[:, 0].min() + x_max = cloud[:, 0].max() + y_min = cloud[:, 1].min() + y_max = cloud[:, 1].max() + lims = [min(x_min, y_min), max(x_max, y_max)] + + plt.xlim(lims) + plt.ylim(lims) + + plt.show() diff --git a/neurodynex/test/test_oja.py b/neurodynex/test/test_oja.py new file mode 100644 index 0000000..9ae2136 --- /dev/null +++ b/neurodynex/test/test_oja.py @@ -0,0 +1,8 @@ +import matplotlib +matplotlib.use('Agg') # needed for plotting on travis + + +def test_oja(): + """Test if Oja learning rule is runnable.""" + from neurodynex.ojas_rule.oja import run_oja + run_oja() # this uses all functions in the module