diff --git a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb index b82d29f12..627903e62 100644 --- a/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb +++ b/tutorials/end_to_end/tutorial01_mnist_digit_classification.ipynb @@ -19,55 +19,49 @@ "source": [ "_**Motivation**: In this tutorial, we will build a Lava Process for an MNIST\n", "classifier, using the Lava Processes for LIF neurons and Dense connectivity.\n", - "Between those leaning towards Neuroscience and those partial to Computer\n", - "Science, this tutorial aims to be appealing to the former. It is supposed to\n", - "get one started with Lava in a few minutes._" + "The tutorial is useful to get started with Lava in a few minutes._" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### This tutorial assumes that you:\n", + "#### This tutorial assumes that you:\n", "- have the [Lava framework installed](../in_depth/tutorial01_installing_lava.ipynb \"Tutorial on Installing Lava\")\n", "- are familiar with the [Process concept in Lava](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", "\n", - "### This tutorial gives a bird's-eye-view of\n", + "#### This tutorial gives a bird's-eye view of\n", "- how Lava Process(es) can perform the MNIST digit classification task using\n", "[Leaky Integrate-and-Fire (LIF)](https://github.com/lava-nc/lava/tree/main/src/lava/proc/lif \"Lava's LIF neuron\") neurons and [Dense\n", "(fully connected)](https://github.com/lava-nc/lava/tree/main/src/lava/proc/dense \"Lava's Dense Connectivity\") connectivity.\n", "- how to create a Process \n", "- how to create Python ProcessModels \n", "- how to connect Processes\n", - "- how to execute them\n", - "\n", - "### Follow the links below for deep-dive tutorials on\n", - "- [Processes](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", - "- [ProcessModel](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\")\n", - "- [Execution](../in_depth/tutorial04_execution.ipynb \"Tutorial on Executing Processes\")" + "- how to execute them" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Our MNIST Classifier\n", + "#### Our MNIST Classifier\n", "In this tutorial, we will build a multi-layer feed-forward classifier without\n", " any convolutional layers. The architecture is shown below.\n", "\n", "> **Important Note**:\n", ">\n", - "> Right now, this model uses arbitrary _untrained_ network paramters (weights and biases)! We will update this model and fix this shortcoming in the next few days after release.\n", - "> Thus the MNIST classifier is not expected to produce any meaningful output at this point in time. \n", - "> Nevertheless, this example illustrates how to build, compile and run an otherwise functional model in Lava." + "> The classifier is a simple feed-forward model using pre-trained network \n", + "> parameters (weights and biases). It illustrates how to build, compile and \n", + "> run a functional model in Lava. Please refer to \n", + "> [Lava-DL](https://github.com/lava-nc/lava-dl) to understand how to train \n", + "> deep networks with Lava." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "
\"Training\n",\"MNIST
" ] }, @@ -76,12 +70,12 @@ "metadata": {}, "source": [ "The 3 Processes shown above are:\n", - " 1. Spike Input Process - generates spikes via integrate and fire dynamics,\n", + " - *SpikeInput* Process - generates spikes via integrate and fire dynamics,\n", " using the image input\n", - " 2. MNIST Feed-forward process - encapsulates feed-forward architecture of\n", + " - *ImageClassifier* Process - encapsulates feed-forward architecture of\n", " Dense connectivity and LIF neurons\n", - " 3. Output Process - accumulates output spikes from the feed-forward process\n", - "and infers the class label; compares the predicted class label with the ground truth" + " - *Output* Process - accumulates output spikes from the feed-forward process\n", + "and infers the class label" ] }, { @@ -97,7 +91,6 @@ "metadata": {}, "outputs": [], "source": [ - "# Assumes: $PYTHONPATH contains lava repository root\n", "import os\n", "import numpy as np" ] @@ -106,13 +99,13 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "### Create the Process class\n", + "#### Lava Processes\n", "\n", "Below we create the Lava Process classes. We need to define only the structure of the process here. The details about how the Process will be executed are specified in the [ProcessModels](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\") below.\n", "\n", "As mentioned above, we define Processes for \n", "- converting input images to binary spikes from those biases (_SpikeInput_),\n", - "- the 4-layer fully connected feed-forward network (_MnistClassifier_)\n", + "- the 3-layer fully connected feed-forward network (_MnistClassifier_)\n", "- accumulating the output spikes and inferring the class for an input image\n", "(_OutputProcess_)" ] @@ -123,7 +116,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Import Process level premitives\n", + "# Import Process level primitives\n", "from lava.magma.core.process.process import AbstractProcess\n", "from lava.magma.core.process.variable import Var\n", "from lava.magma.core.process.ports.ports import InPort, OutPort" @@ -144,8 +137,8 @@ " n_img = kwargs.pop('num_images', 25)\n", " n_steps_img = kwargs.pop('num_steps_per_image', 128)\n", " shape = (784,)\n", - " self.spikes_out = OutPort(shape=shape)\n", - " self.label_out = OutPort(shape=(1,))\n", + " self.spikes_out = OutPort(shape=shape) # Input spikes to the classifier\n", + " self.label_out = OutPort(shape=(1,)) # Ground truth labels to OutputProc\n", " self.num_images = Var(shape=(1,), init=n_img)\n", " self.num_steps_per_image = Var(shape=(1,), init=n_steps_img)\n", " self.input_img = Var(shape=shape)\n", @@ -154,14 +147,13 @@ " self.vth = Var(shape=(1,), init=kwargs['vth'])\n", " \n", " \n", - "class MnistClassifier(AbstractProcess):\n", - " \"\"\"A 4 layer feed-forward network with LIF and Dense Processes.\"\"\"\n", + "class ImageClassifier(AbstractProcess):\n", + " \"\"\"A 3 layer feed-forward network with LIF and Dense Processes.\"\"\"\n", "\n", " def __init__(self, **kwargs):\n", " super().__init__(**kwargs)\n", - " # As mentioned before, the weights and biases saved on the disk are\n", - " # arbitrary numbers. These will not produce any meaningful output\n", - " # classification.\n", + " \n", + " # Using pre-trained weights and biases\n", " trained_weights_path = kwargs.pop('trained_weights_path', os.path\n", " .join('.','mnist_pretrained.npy'))\n", " real_path_trained_wgts = os.path.realpath(trained_weights_path)\n", @@ -183,6 +175,15 @@ " self.w_dense2 = Var(shape=w2.shape, init=w2)\n", " self.b_output_lif = Var(shape=(w2.shape[0],), init=b3)\n", " \n", + " # Up-level currents and voltages of LIF Processes\n", + " # for resetting (see at the end of the tutorial)\n", + " self.lif1_u = Var(shape=(w0.shape[0],), init=0)\n", + " self.lif1_v = Var(shape=(w0.shape[0],), init=0)\n", + " self.lif2_u = Var(shape=(w1.shape[0],), init=0)\n", + " self.lif2_v = Var(shape=(w1.shape[0],), init=0)\n", + " self.oplif_u = Var(shape=(w2.shape[0],), init=0)\n", + " self.oplif_v = Var(shape=(w2.shape[0],), init=0)\n", + " \n", " \n", "class OutputProcess(AbstractProcess):\n", " \"\"\"Process to gather spikes from 10 output LIF neurons and interpret the\n", @@ -195,7 +196,7 @@ " self.num_images = Var(shape=(1,), init=n_img)\n", " self.spikes_in = InPort(shape=shape)\n", " self.label_in = InPort(shape=(1,))\n", - " self.spikes_accum = Var(shape=shape)\n", + " self.spikes_accum = Var(shape=shape) # Accumulated spikes for classification\n", " self.num_steps_per_image = Var(shape=(1,), init=128)\n", " self.pred_labels = Var(shape=(n_img,))\n", " self.gt_labels = Var(shape=(n_img,))" @@ -207,9 +208,7 @@ "tags": [] }, "source": [ - "### Create ProcessModels for Python execution\n", - "The code in these ProcessModels is what will get executed. Processes above\n", - "were declarations, in a way." + "#### ProcessModels for Python execution\n" ] }, { @@ -246,13 +245,33 @@ } }, "source": [ - "#### ProcessModel for producing spiking input" + "**Decorators for ProcessModels**: \n", + "- `@implements`: associates a ProcessModel with a Process through \n", + "the argument `proc`. Using `protocol` argument, we will specify the \n", + "synchronization protocol used by the ProcessModel. In this tutorial, \n", + "all ProcessModels execute \n", + "according to the `LoihiProtocol`. Which means, similar to the Loihi \n", + "chip, each time-step is divided into _spiking_, _pre-management_, \n", + "_post-management_, and _learning_ phases. It is necessary to specify \n", + "behaviour of a ProcessModel during the spiking phase using `run_spk` \n", + "function. Other phases are optional.\n", + "- `@requires`: specifies the hardware resource on which a ProcessModel \n", + "will be executed. In this tutorial, we will execute all ProcessModels \n", + "on a CPU." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SpikingInput ProcessModel**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -274,23 +293,33 @@ " ground_truth_label: int = LavaPyType(int, int, precision=32)\n", " v: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", " vth: int = LavaPyType(int, int, precision=32)\n", - " mnist_dataset = MnistDataset()\n", - " curr_img_id = -1\n", + " \n", + " def __init__(self, proc_params):\n", + " super().__init__(proc_params=proc_params)\n", + " self.mnist_dataset = MnistDataset()\n", + " self.curr_img_id = 0\n", "\n", " def post_guard(self):\n", + " \"\"\"Guard function for PostManagement phase.\n", + " \"\"\"\n", " if self.current_ts % self.num_steps_per_image == 1:\n", - " self.curr_img_id += 1\n", " return True\n", " return False\n", "\n", " def run_post_mgmt(self):\n", + " \"\"\"Post-Management phase: executed only when guard function above \n", + " returns True.\n", + " \"\"\"\n", " img = self.mnist_dataset.images[self.curr_img_id]\n", " self.ground_truth_label = self.mnist_dataset.labels[self.curr_img_id]\n", " self.input_img = img.astype(np.int32) - 127\n", " self.v = np.zeros(self.v.shape)\n", " self.label_out.send(np.array([self.ground_truth_label]))\n", + " self.curr_img_id += 1\n", "\n", " def run_spk(self):\n", + " \"\"\"Spiking phase: executed unconditionally at every time-step\n", + " \"\"\"\n", " self.v[:] = self.v + self.input_img\n", " s_out = self.v > self.vth\n", " self.v[s_out] = 0 # reset voltage to 0 after a spike\n", @@ -305,16 +334,21 @@ } }, "source": [ - "#### ProcessModel for the feed-forward network\n", + "**ImageClassifier ProcessModel**\n", + "\n", "Notice that the following process model is further decomposed into\n", "sub-Processes, which implement LIF neural dynamics and Dense connectivity. We\n", - " will not go into the details of how these are implemented in this tutorial." + " will not go into the details of how these are implemented in this tutorial.\n", + " \n", + "Also notice that a *SubProcessModel* does not actually contain any concrete \n", + "execution. This is handled by the ProcessModels of the constituent Processes." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -327,18 +361,9 @@ "from lava.proc.lif.process import LIF\n", "from lava.proc.dense.process import Dense \n", "\n", - "@implements(MnistClassifier)\n", + "@implements(ImageClassifier)\n", "@requires(CPU)\n", - "class PyMnistClassifierModel(AbstractSubProcessModel):\n", - " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", - " spikes_out: PyOutPort = LavaPyType(PyOutPort.VEC_DENSE, bool, precision=1)\n", - " w_dense0: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_lif1: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - " w_dense1: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_lif2: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - " w_dense2: np.ndarray = LavaPyType(np.ndarray, int, precision=8)\n", - " b_output_lif: np.ndarray = LavaPyType(np.ndarray, int, precision=13)\n", - "\n", + "class PyImageClassifierModel(AbstractSubProcessModel):\n", " def __init__(self, proc):\n", " self.dense0 = Dense(shape=(64, 784), weights=proc.w_dense0.init)\n", " self.lif1 = LIF(shape=(64,), b=proc.b_lif1.init, vth=400,\n", @@ -348,15 +373,23 @@ " dv=0, du=4095)\n", " self.dense2 = Dense(shape=(10, 64), weights=proc.w_dense2.init)\n", " self.output_lif = LIF(shape=(10,), b=proc.b_output_lif.init,\n", - " vth=2**17-1, dv=0, du=4095)\n", - "\n", - " proc.in_ports.spikes_in.connect(self.dense0.in_ports.s_in)\n", - " self.dense0.out_ports.a_out.connect(self.lif1.in_ports.a_in)\n", - " self.lif1.out_ports.s_out.connect(self.dense1.in_ports.s_in)\n", - " self.dense1.out_ports.a_out.connect(self.lif2.in_ports.a_in)\n", - " self.lif2.out_ports.s_out.connect(self.dense2.in_ports.s_in)\n", - " self.dense2.out_ports.a_out.connect(self.output_lif.in_ports.a_in)\n", - " self.output_lif.out_ports.s_out.connect(proc.out_ports.spikes_out)" + " vth=1, dv=0, du=4095)\n", + "\n", + " proc.spikes_in.connect(self.dense0.s_in)\n", + " self.dense0.a_out.connect(self.lif1.a_in)\n", + " self.lif1.s_out.connect(self.dense1.s_in)\n", + " self.dense1.a_out.connect(self.lif2.a_in)\n", + " self.lif2.s_out.connect(self.dense2.s_in)\n", + " self.dense2.a_out.connect(self.output_lif.a_in)\n", + " self.output_lif.s_out.connect(proc.spikes_out)\n", + " \n", + " # Create aliases of SubProcess variables\n", + " proc.lif1_u.alias(self.lif1.u)\n", + " proc.lif1_v.alias(self.lif1.v)\n", + " proc.lif2_u.alias(self.lif2.u)\n", + " proc.lif2_v.alias(self.lif2.v)\n", + " proc.oplif_u.alias(self.output_lif.u)\n", + " proc.oplif_v.alias(self.output_lif.v)" ] }, { @@ -367,13 +400,14 @@ } }, "source": [ - "#### Finally, ProcessModel for inference output" + "**OutputProcess ProcessModel**" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "metadata": { + "collapsed": false, "jupyter": { "outputs_hidden": false }, @@ -386,91 +420,148 @@ "@implements(proc=OutputProcess, protocol=LoihiProtocol)\n", "@requires(CPU)\n", "class PyOutputProcessModel(PyLoihiProcessModel):\n", - " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", " label_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, int, precision=32)\n", + " spikes_in: PyInPort = LavaPyType(PyInPort.VEC_DENSE, bool, precision=1)\n", " num_images: int = LavaPyType(int, int, precision=32)\n", " spikes_accum: np.ndarray = LavaPyType(np.ndarray, np.int32, precision=32)\n", " num_steps_per_image: int = LavaPyType(int, int, precision=32)\n", " pred_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", " gt_labels: np.ndarray = LavaPyType(np.ndarray, int, precision=32)\n", - " current_img_id = -1\n", + " \n", + " def __init__(self, proc_params):\n", + " super().__init__(proc_params=proc_params)\n", + " self.current_img_id = 0\n", "\n", - " # This is needed for Loihi synchronization protocol\n", " def post_guard(self):\n", - " if self.current_ts % self.num_steps_per_image == 1 and self\\\n", - " .current_ts > 1:\n", - " self.current_img_id += 1\n", + " \"\"\"Guard function for PostManagement phase.\n", + " \"\"\"\n", + " if self.current_ts % self.num_steps_per_image == 0 and \\\n", + " self.current_ts > 1:\n", " return True\n", " return False\n", "\n", " def run_post_mgmt(self):\n", - " print(f'Curr Img: {self.current_img_id}')\n", - " pred_label = np.argmax(self.spikes_accum)\n", - " self.pred_labels[self.current_img_id] = pred_label\n", - " self.spikes_accum = np.zeros(self.spikes_accum.shape)\n", + " \"\"\"Post-Management phase: executed only when guard function above \n", + " returns True.\n", + " \"\"\"\n", " gt_label = self.label_in.recv()\n", + " pred_label = np.argmax(self.spikes_accum)\n", " self.gt_labels[self.current_img_id] = gt_label\n", - " print(f'Pred Label: {pred_label}', end='\\t')\n", - " print(f'Ground Truth: {gt_label}')\n", + " self.pred_labels[self.current_img_id] = pred_label\n", + " self.current_img_id += 1\n", + " self.spikes_accum = np.zeros_like(self.spikes_accum)\n", "\n", " def run_spk(self):\n", - " spikes_buffer = self.spikes_in.recv()\n", - " self.spikes_accum += spikes_buffer" + " \"\"\"Spiking phase: executed unconditionally at every time-step\n", + " \"\"\"\n", + " spk_in = self.spikes_in.recv()\n", + " self.spikes_accum = self.spikes_accum + spk_in" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "### Run the Process" + "#### Connecting Processes" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 8, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Curr Img: 0\n", - "Pred Label: 0\tGround Truth: [5]\n", - "Curr Img: 1\n", - "Pred Label: 0\tGround Truth: [0]\n", - "Curr Img: 2\n", - "Pred Label: 0\tGround Truth: [4]\n", - "Curr Img: 3\n", - "Pred Label: 0\tGround Truth: [1]\n", - "Curr Img: 4\n", - "Pred Label: 0\tGround Truth: [9]\n" - ] - } - ], + "outputs": [], "source": [ - "num_images = 5\n", + "num_images = 25\n", "num_steps_per_image = 128\n", "\n", - "# Create instances\n", + "# Create Process instances\n", "spike_input = SpikeInput(num_images=num_images,\n", " num_steps_per_image=num_steps_per_image,\n", " vth=1)\n", - "mnist_clf = MnistClassifier(\n", + "mnist_clf = ImageClassifier(\n", " trained_weights_path=os.path.join('.', 'mnist_pretrained.npy'))\n", "output_proc = OutputProcess(num_images=num_images)\n", "\n", - "# Connect instances\n", - "spike_input.out_ports.spikes_out.connect(mnist_clf.in_ports.spikes_in)\n", - "mnist_clf.out_ports.spikes_out.connect(output_proc.in_ports.spikes_in)\n", - "spike_input.out_ports.label_out.connect(output_proc.in_ports.label_in)\n", + "# Connect Processes\n", + "spike_input.spikes_out.connect(mnist_clf.spikes_in)\n", + "mnist_clf.spikes_out.connect(output_proc.spikes_in)\n", + "# Connect Input directly to Output for ground truth labels\n", + "spike_input.label_out.connect(output_proc.label_in)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "#### Execution and results\n", + "Below, we will run the classifier process in a loop of `num_images`\n", + "number of iterations. Each iteration will run the Process for \n", + "`num_steps_per_image` number of time-steps. \n", "\n", + "We take this approach to clear the neural states of all three LIF \n", + "layers inside the classifier after every image. We need to clear \n", + "the neural states, because the network parameters were trained \n", + "assuming clear neural states for each inference.\n", + "\n", + "> Note:\n", + "Below we have used `Var.set()` function to set the values\n", + "of internal state variables. The same behaviour can be \n", + "achieved by using `RefPorts`. See the \n", + "[RefPorts tutorial](../in_depth/tutorial07_remote_memory_access.ipynb)\n", + "to learn more about how to use `RefPorts` to access internal \n", + "state variables of Lava Processes." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Current image: 25\n", + "Ground truth: [5 0 4 1 9 2 1 3 1 4 3 5 3 6 1 7 2 8 6 9 4 0 9 1 1]\n", + "Predictions : [3 0 4 1 4 2 1 3 1 4 3 5 3 6 1 7 2 8 5 9 4 0 9 1 1]\n", + "Accuracy : 88.0\n" + ] + } + ], + "source": [ "from lava.magma.core.run_conditions import RunSteps\n", "from lava.magma.core.run_configs import Loihi1SimCfg\n", "\n", - "mnist_clf.run(\n", - " condition=RunSteps(num_steps=(num_images+1) * num_steps_per_image),\n", - " run_cfg=Loihi1SimCfg(select_sub_proc_model=True))\n", - "mnist_clf.stop()" + "# Loop over all images\n", + "for img_id in range(num_images):\n", + " print(f\"\\rCurrent image: {img_id+1}\", end=\"\")\n", + " \n", + " # Run each image-inference for fixed number of steps\n", + " mnist_clf.run(\n", + " condition=RunSteps(num_steps=num_steps_per_image),\n", + " run_cfg=Loihi1SimCfg(select_sub_proc_model=True,\n", + " select_tag='fixed_pt'))\n", + " \n", + " # Reset internal neural state of LIF neurons\n", + " mnist_clf.lif1_u.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif1_v.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif2_u.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.lif2_v.set(np.zeros((64,), dtype=np.int32))\n", + " mnist_clf.oplif_u.set(np.zeros((10,), dtype=np.int32))\n", + " mnist_clf.oplif_v.set(np.zeros((10,), dtype=np.int32))\n", + "\n", + "# Gather ground truth and predictions before stopping exec\n", + "ground_truth = output_proc.gt_labels.get().astype(np.int32)\n", + "predictions = output_proc.pred_labels.get().astype(np.int32)\n", + "\n", + "# Stop the execution\n", + "mnist_clf.stop()\n", + "\n", + "accuracy = np.sum(ground_truth==predictions)/ground_truth.size * 100\n", + "\n", + "print(f\"\\nGround truth: {ground_truth}\\n\"\n", + " f\"Predictions : {predictions}\\n\"\n", + " f\"Accuracy : {accuracy}\")" ] }, { @@ -479,8 +570,11 @@ "source": [ "> **Important Note**:\n", ">\n", - "> Right now, this model uses arbitrary _untrained_ network paramters (weights and biases)! We will update this model and fix this shortcoming in the next few days after release.\n", - "> Thus the MNIST classifier is not expected to produce any meaningful output at this point in time. " + "> The classifier is a simple feed-forward model using pre-trained network \n", + "> parameters (weights and biases). It illustrates how to build, compile and \n", + "> run a functional model in Lava. Please refer to \n", + "> [Lava-DL](https://github.com/lava-nc/lava-dl) to understand how to train \n", + "> deep networks with Lava." ] }, { @@ -489,6 +583,13 @@ "source": [ "## How to learn more?\n", "\n", + "#### Follow the links below for deep-dive tutorials on the concepts in this tutorial:\n", + "- [Processes](../in_depth/tutorial02_processes.ipynb \"Tutorial on Processes\")\n", + "- [ProcessModel](../in_depth/tutorial03_process_models.ipynb \"Tutorial on ProcessModels\")\n", + "- [Execution](../in_depth/tutorial04_execution.ipynb \"Tutorial on Executing Processes\")\n", + "- [SubProcessModels](../in_depth/tutorial06_hierarchical_processes.ipynb) or [Hierarchical Processes](../in_depth/tutorial06_hierarchical_processes.ipynb)\n", + "- [RefPorts](../in_depth/tutorial07_remote_memory_access.ipynb)\n", + "\n", "If you want to find out more about Lava, have a look at the [Lava documentation](https://lava-nc.org/ \"Lava Documentation\") or dive into the [source code](https://github.com/lava-nc/lava/ \"Lava Source Code\").\n", "\n", "To receive regular updates on the latest developments and releases of the Lava Software Framework please subscribe to the [INRC newsletter](http://eepurl.com/hJCyhb \"INRC Newsletter\")." @@ -497,7 +598,7 @@ ], "metadata": { "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -511,9 +612,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.11" + "version": "3.8.10" } }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file