Skip to content

Rationale

Randall C. O'Reilly edited this page Feb 4, 2019 · 1 revision

Rationale for new version

(Note: this was the first README announcing the new project.)

Although everyone is using Python these days, and it was the obvious choice for a new start, we decided against using it as our primary language, although a major goal is to fully support it for those that want to use it. A major issue is that native Python code runs relatively slowly, and generally requires C / C++ back-end code to achieve any kind of decent performance for computationally intensive tasks like neural network modeling. Several folks have ported our main Leabra algorithm to Python, and we found it to be unusably slow. It is fine for gluing stuff together, but we really wanted to have something powerful and fast enough that we could use for developing large-scale interactive 3D GUI's, and for running smaller, experimental neural models at speeds approaching C++, without having to write yet more C++ code all the time.

Instead, we chose Go (golang) as our primary languag. Go is elegant, simple, powerful, and fast (both to compile and to run) -- it was developed originally by people at Google: (Go Web Page) in 2009. Go code compiles to native machine code, and does so in a few seconds, even for large codebases. It borrows all the best ideas from C, C++, and Python, improves on them in significant ways, and has made some very strong decisions to remain extremely simple and elegant. It has great tooling, and an amazing standard library that supports just about everything (and has great, consistent naming conventions like in Qt). Except, unfortunately, a GUI. Yeah, that's bad.

Undaunted, we took this as an opportunity to start fresh and write the world's best GUI framework for Go! It is called GoGi and it is built on top of a core tree structure called GoKi, where ki = tree in Japanese. Our objective was to make a GUI framework that has all the same great features as Go (lean, simple, elegant, efficient, fast), and also incorporates many of the best ideas we had developed in our emergent temt toolkit, where we took advantage of the type-access system to generate most of the GUI automatically. Go has reflection built-in, and GoGi takes full advantage of it to provide automatic View representations of any standard Go data structure. The 2D part of the GUI, which is by far the most complex, is now complete, and we will start on the 3D part soon. The 3D part is mostly about managing data structures that get fed to OpenGL to run on the GPU, so it is much simpler overall, and we have a number of great existing projects to draw from.

Gide

The GUI part of the original C++ version of emergent is basically a special-purpose IDE (integrated development environment), centered around writing emergent-specific Program code that then generates our own scripting language (css) (and on manipulating various complex C++ data structures like DataTables and Networks). The design of this system evolved from its C++ object-oriented roots, and is based on deep hierarchies of C++ types, organized into a large tree structure (the Project), and interconnected with a liberal use of pointers. Programs are composed of C++ objects, and originally programming was only a drag-n-drop process, but slowly we incorporated more of an efficient text-based editing mode, with code completion. Although this improved the overall programming process, it is still fundamentally constrained by the underlying C++ objects that generate the program code, and could not handle many types of more advanced expressions, and overall our framework had no way of incorporating anything beyond the specific C++ code that we wrote for it (e.g., PyTorch etc). Furthermore, the only way someone can access our simulations is via this special-purpose IDE, which thus created significant barriers.

Our new approach is much simpler and much more general-purpose. Instead of making an awkward object-oriented IDE, we have now developed a regular text-based IDE, called Gide. Gide is naturally built on the GoGi GUI toolkit within Go, and supports advanced text editing features (many stolen from emacs), and a nice file tree viewer on the left, instead of the project tree browser. It has code completion for Go and will include the expected debugging and code refactoring tools common to modern IDEs, along with more sophisticated syntactic and semantic code parsing abilities.

Consistent with our open and inclusive approach, Gide can be used for editing anything and everything (LaTeX documents, Markdown files, Python / C / Go etc), just like a great text editor, but also provides the infrastructure for running commands and any other kind of program, and for displaying and managing interactive GUI views, like the graphs and network views from emergent, and the graphs and plots generated by e.g., matplotlib etc in Python. Thus, it is really providing the same functionality as emergent, but just in a completely general-purpose manner, which can support using PyTorch or any other framework, along with our specific neural network code.

Instead of managing gobs of C++ objects, Gide is good at managing regular old files. Instead of writing Program's, you just write regular old programs using whatever language you want. This should be much more familiar to those accustomed to the various narrow IDE-like GUI environments for MATLAB, Jupyter Lab, R Studio, etc, as well as more language-focused IDE's such as Visual Studio, XCode, and IntelliJ. Our goal is to provide something that rivals those other tools in ease-of-use and overall functionality, while also trying to keep everything as simple, elegant, and general-purpose as possible -- some of those systems are really complex and strongly tailored to specific languages and platforms.

The completely open-source nature of Gide, and the fact that it builds in seconds, and runs fast, and is really easy to understand at the code level due to the natural transparency and simplicity of Go, has the potential to make this a widely-adopted tool for all kinds of applications. Whether that happens or not, we certainly plan to continue improving it as we did emergent, but now everything we do should make it more attractive for a much larger audience of potential users, instead of spending so much of our time making a really great but entirely specialized tool that relatively few people would ever use.

Emergent, specifically

We just love the name emergent too much to give it up, so despite the significant potential for confusion, we're going to re-use the same name for the new framework (where necessary, we can use old-emergent and new-emergent to clarify). The name emergent will now specifically apply to the neural-network simulation software, which will be usable from any IDE or editor you might want to use, and will not be exclusively tied to the Gide framework (though Gide will provide significant visualization and GUI added value). Furthermore, we plan to make it accessible via Python, although it will be written in Go, and probably will be somewhat more functional to use within Go. In addition, we will be developing interactive visualization tools like the emergent NetView that run within Gide (and possibly outside of it too), and can be used to visualize any kind of neural network (including PyTorch ones, etc). Likewise, we will also port over the virtual environment (VirtEnv) from old-emergent, which is home to our virtual robot emer and has been a key tool in enabling us to develop our more advanced predictive learning models. Emer will now be able to interact with any other tool and will be fully independent of emergent (though the visualization will use the 3D part of GoGi).

Thus, overall, our vision is for a much more open, interoperable, flexible framework where we can more easily leverage the considerable recent advances in neural network software and hardware, while also providing some valuable tools for the larger community that can hopefully enrich everyone's productivity. Likewise, our vision for the actual emergent simulation software itself is much more open and flexible.

Specifically, here are the key principles behind our overall design at this point:

  • Everything is just code. No more crazy C++ object soup.

  • As is idiomatic in Go, there will be no deep class hierarchies. We want the entire code for a given model to fit nicely within a single file, with only minimal inclusion of some standard interface types that provide the hooks into the GUI visualizations etc. In particular, the entire set of equations and parameters will all be specified directly in explicit, flat, self-contained code, so it should be much easier for people to understand and modify things.

  • We will focus mainly on supporting Leabra and other related biologically-based models that currently don't fit well within the existing Tensor-gradient-descent paradigm as implemented in PyTorch and TensorFlow, etc. Leabra uses synchronous bidirectional activation dynamics, instead of the cascaded feed-forward dynamics of standard backpropagation models, and incorporates ion-channel-based activation dynamics and various scopes of inhibitory interactions within layers. Furthermore, these models have sparse overall activity levels, and benefit greatly from sender-based computation, which is also highly beneficial for discrete spiking models. While we are actively working on seeing how far we can incorporate some of these biologically-based properties into the framework supported by tools such as PyTorch, we have extensive experience with the existing Leabra framework, which can be directly mapped onto known biology, and thus need to have good support for such models. Unlike in the current emergent, there should be no need to support other algorithms such as backpropagation, as these are much better done within e.g., PyTorch, and thus we can greatly simplify and focus our code.

  • Instead of trying to support all possible variations of Leabra in a single codebase, we will have many different implementations, each in their own separate file, and each focused on particular versions of the algorithm. This is long overdue and has been a significant problem for the emergent community. And of course, this will also greatly simplify and focus the code. We envision that individual users will simply copy our "reference" implementations, and start adding and customizing from there, and will thus remain fully in control of their own code going forward. This should relieve a major pain point with the old emergent, where new releases often broke users's projects, and people did not have control of their own software destiny!

  • We will rely extensively on patch sets to make bug fixes and feature additions available to users. It is then up to users whether and what to apply, etc. Furthermore, everyone can create and share their own patch sets, making everything much more decentralized and pluralistic. The obvious cost to all this is that the ideal of a single unified framework doing everything will no longer hold. But people who want to always stay up-to-date etc can do so, without forcing everyone to do so. It will require some effort to get people up to speed on appropriate patch-set management tools, which we will need to investigate.

  • As a general principle, abstraction will be opt-in, not built-in, and layered on top of a fully explicit, transparent base layer of code that actually does the computation. For example, instead of using specs, we will just bake the parameters and functions (methods) directly into the relevant Layer object. But each object can also be styled using the equivalent of a cascading style sheet (that other CSS), to set parameters in a maximally flexible and efficient manner. This is how it works on the web, and CSS has been highly effective as a way to provide flexible and sensible styling. Likewise, in most word processors, you have the ability to apply various specific parameters to any given bit of text, while also being able to apply general styles to the text as well.

    • CSS-like styling is already a core feature of GoGi, and we will use that directly
    • We already have a decent GUI for editing style sheets, and can augment that as needed
    • The full functionality of the existing ParamSets should be easily achievable, and indeed the new framework should be even more powerful.
  • Everything that happens in running a model will be under full code control at all times. i.e., you will explicitly construct your model in code, and control the full running of your model directly in code, etc. Again, we will provide various example "reference implementations" that people can start from, but otherwise we have realized that code is always the best and final answer to any question of how to control something in a maximally flexible, efficient, and open-ended way

  • GUIs will be able to generate code to reflect any interactive configuration etc. Thus, if you do some things to configure a network or a graph in the GUI, you can then generate the corresponding code to do that same thing, and the paste that back into the right place in your code.

  • A control panel level of interface will be available to make the model accessible to undergraduates, e.g., for the Computational Cognitive Neuroscience textbook simulations. Although in theory the old emergent Program GUI made it possible for students to customize models, in practice it was not very easy, and likely having everything in code will actually make it clearer what is going on.

  • We are working on coordinating our efforts with PsyNeuLink to make Leabra models accessible in that framework, and vice-versa.

  • GoGi is based on SVG, and we will definitely have increasingly capable SVG editing going forward, so that you will be able to fully customize graphs etc directly in the software, allowing direct publication-quality and fully customized visualizations to be created.

  • Many more details remain to be resolved, but overall we are excited about this reboot and are planning to get working on it very soon!

Code-level design

General Interface types

In Go, the interface replaces the virtual type hierarchy of C++. Interfaces are mix-and-match and implicitly satisfied. The goal is to create small componential elements of functionality that you compose together to achieve whatever you want. Only the most essential abstractions should be in the interface -- everything else goes into the explicit struct type. These interfaces would only be used for "generic" code that supports visualization, I/O, etc, and everything else would be directly coded in the specific structs.

See the GoDoc for full details -- this is very high-level here.

Specific Layer impl

The main Leabra implementation would mostly be about layers, which provides the natural organization for most of the computation.

The current emer and basic/leabra directories in this repository contain stub versions of potential code to get a specific sense of what things might look like. From a naming perspective, it is good to have the package-level directory always be leabra so you always write e.g., leabra.Layer etc, but which specific version of leabra you're using depends on the import path -- e.g.,basic/leabra, spiking/leabra, deep/leabra, etc. This is only true if we don't mix-and-match.