├── .gitignore
├── LICENSE
├── README.md
├── START_HERE.ipynb
├── contlearn
├── __init__.py
├── getdata.py
├── getmodels.py
└── gettrainer.py
├── files
├── F1.large.jpg
├── baseline_mnist.png
├── basemodel.pth
├── catastrophic_forgetting.png
├── ewc_training.png
├── ewcmodel.pth
└── notebook1.png
└── requirements.txt
/.gitignore:
--------------------------------------------------------------------------------
1 | # Misc
2 | .DS_Store
3 | docker-compose.yml
4 | docker-compose.override.yml
5 |
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | pip-wheel-metadata/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | *.py,cover
56 | .hypothesis/
57 | .pytest_cache/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100 | __pypackages__/
101 |
102 | # Celery stuff
103 | celerybeat-schedule
104 | celerybeat.pid
105 |
106 | # SageMath parsed files
107 | *.sage.py
108 |
109 | # Environments
110 | .env
111 | .venv
112 | env/
113 | venv/
114 | ENV/
115 | env.bak/
116 | venv.bak/
117 |
118 | # Spyder project settings
119 | .spyderproject
120 | .spyproject
121 |
122 | # Rope project settings
123 | .ropeproject
124 |
125 | # mkdocs documentation
126 | /site
127 |
128 | # mypy
129 | .mypy_cache/
130 | .dmypy.json
131 | dmypy.json
132 |
133 | # Pyre type checker
134 | .pyre/
135 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 Carson Lam
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # intro_continual_learning
2 |
3 | This is a tutorial to connect the mathematics and machine learning theory to practical implementations addressing the continual learning problem of artificial intelligence. We will learn this in python by examining and deconstructing a method called [elastic weight consolidation](https://www.pnas.org/content/114/13/3521) (EWC).
4 |
5 | I wish there were more learning tools in this style that directly try to help the learner connect the math to the code, and do it using a simple but completely end to end project. While it is true that the average programmer can load a "out of the box" library in 5 minutes and be running the latest model solving a common task in 15 minutes, I often hear from engineers that although they are engineers, they feel under-developed in the math that underlies recent academic research in machine learning. I have received criticism from some that believe tutorials like this provide a shortcut for "average" engineers to "think" they understand the math behind a new flashy artificial intelligence concept, who think the joy of reading these papers should be reserved for the traditionally trained academics that have gone through the years of formal coursework. I think there is nothing wrong with motivating learners using a cool AI concept to learn more of the fundamental math on their own.
6 |
7 | "anyone can cook" - ratatouille
8 |
9 |
10 |
11 |
12 |
13 | ### What does elastic weight consolidation do?
14 |
15 | The ability to learn tasks in a sequential fashion is crucial to the development of artificial intelligence. When an artificial neural network is trained on a new training set, unless that new training set includes all the old tasks combined with the new task, it generally is subject to catastrophic forgetting, whereby learning to solve new task B accompanies degradation of performance at old task A. In contrast, human neural networks can maintain expertise on tasks that they have not experienced for a long time. EWC addresses this problem by selectively slowing down learning on the weights (ie parameters, synaptic strengths) important for those old tasks.
16 |
17 | ## Setup
18 |
19 | - Ubuntu 18.04.3 LTS (bionic)
20 | - Python 3.8
21 | - Cuda 10.1
22 | - cudnn7.6.4
23 | - PyTorch 1.10.0
24 |
25 | ### These same steps should work on MacOS to
26 |
27 | ```console
28 | you@you:/path/to/folder$ pip3 install virtualenv
29 |
30 | you@you:/path/to/folder$ virtualenv venv --python=python3.8
31 |
32 | you@you:/path/to/folder$ source venv/bin/activate
33 |
34 | (venv) you@you:/path/to/folder$ pip3 install -r requirements.txt
35 |
36 | (venv) you@you:/path/to/folder$ jupyter notebook
37 | ```
38 |
39 | ### Credit/References:
40 |
41 | 1. [James Kirkpatrick et al. Overcoming catastrophic forgetting in neural networks 2016(10.1073/pnas.1611835114)](https://www.pnas.org/content/114/13/3521)
42 |
43 | 2. [shivamsaboo17](https://github.com/shivamsaboo17/Overcoming-Catastrophic-forgetting-in-Neural-Networks)
44 |
45 | 3. [moskomule](https://github.com/moskomule/ewc.pytorch)
46 |
--------------------------------------------------------------------------------
/START_HERE.ipynb:
--------------------------------------------------------------------------------
1 | {
2 | "cells": [
3 | {
4 | "cell_type": "markdown",
5 | "metadata": {},
6 | "source": [
7 | "# Part 1 intuition behind elastic weight colsolidation\n",
8 | "\n",
9 | "In the figure below, $\\theta^{*}$ are the weights (ie parameters, synaptic strengths) learned by the neural network (NN) to solve old task A, shown as a vector in vector space. ie if this neural network as 100 total weights then this is a 2D representation of $\\mathbb{R}^{100}$ space. The blue horizontal arrow shows an example of catastrophic forgetting, whereby $\\theta^{*}$ moves out of the region that allows the NN to perform well at task A (grey), and into the center of a region that allows the NN to perform well at task B (cream). The downward green arrow is the update to $\\theta^{*}$ regularized by L2 penalty $\\alpha (\\theta_{i} - \\theta_{A , i}^{*})^{2}$ that causes it to move toward the cream region irrespective of the shape of the grey region. The desired update vector is the red arrow that moves the NN weights into a region capable of performing well at both tasks A and B. \n",
10 | "\n",
11 | "How Elastic Weight Cosolidation Changes Learning New Weights $\\theta^{*}$\n",
12 | "
\n",
13 | "\n",
14 | "
\n",
15 | "\n",
16 | "EWC encourages movement of weights along the red path by modifying the loss function when re-training a NN that has already been trained to convergence using the loss function for task A, $L_{A}$, which has settled on weights $\\theta_{A}$. When re-training the NN on task B using $L_{B}$, we add a term which penalizes changes to weights that are both far from $\\theta_{A}$, ie $(\\theta_{i} - \\theta_{A , i}^{*})^{2}$, and also high in $F_{i}$ which encodes the shape of the grey region.\n",
17 | "\n",
18 | "$$L \\left(\\right. \\theta \\left.\\right) = L_{B} \\left(\\right. \\theta \\left.\\right) + \\underset{i}{\\sum} \\frac{\\lambda}{2} F_{i} \\left(\\theta_{i} - \\theta_{A , i}^{*}\\right)^{2} $$\n",
19 | "\n",
20 | "### But what is F_i? \n",
21 | "\n",
22 | "F_i is the diagonal of the Fisher information matrix. We want to use the diagonal components in Fisher Information Matrix to identify which parameters are more important to task A and apply higher weights to them (the direction of the short axis of grey oval). To learn B we should instead change those weights where F_i is small (long axis of grey oval). \n",
23 | "\n",
24 | "In the EWC paper:\n",
25 | "\n",
26 | "\"we approximate the posterior as a Gaussian distribution with mean given by the parameters θ∗A and a diagonal precision given by the diagonal of the Fisher information matrix F. F has three key properties (20): (i) It is equivalent to the second derivative of the loss near a minimum, (ii) it can be computed from first-order derivatives alone and is thus easy to calculate even for large models, and (iii) it is guaranteed to be positive semidefinite. Note that this approach is similar to expectation propagation where each subtask is seen as a factor of the posterior (21). where LB(θ) is the loss for task B only, λ sets how important the old task is compared with the new one, and i labels each parameter.\n",
27 | "\n",
28 | "When moving to a third task, task C, EWC will try to keep the network parameters close to the learned parameters of both tasks A and B. This can be enforced either with two separate penalties or as one by noting that the sum of two quadratic penalties is itself a quadratic penalty.\"\n",
29 | "\n",
30 | "### Lets learn what F is in the example\n",
31 | "\n",
32 | "This article gives a very good explaination of F in the context of EWC: [Fisher Information Matrix by Yuan-Hong Liao](https://andrewliao11.github.io/blog/fisher-info-matrix/)\n",
33 | "\n",
34 | "To compute F_i, we sample the data from task A once and calculate the empirical Fisher Information Matrix. \n",
35 | "\n",
36 | "$$\n",
37 | "I_{\\theta_\\mathcal{A}^*} = \\frac{1}{N} \\sum_{i=1}^{N} \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*) \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)^T\n",
38 | "$$\n",
39 | "\n",
40 | "This is just to say that the above equation is how you calculate the below equation from the data. For each pair of parameters in $\\theta$ ($\\theta_i$ and $\\theta_j$), the Fisher Information matrix at position ij is\n",
41 | "\n",
42 | "$$\n",
43 | "I(\\theta)_{ij} = E\\left[ \\left( \\frac{\\partial}{\\partial\\theta_i}\\log f(X;\\theta) \\right)\\left( \\frac{\\partial}{\\partial\\theta_j}\\log f(X;\\theta) \\right) \\mid \\theta\\right]\n",
44 | "$$\n",
45 | "\n",
46 | "If this equation is hard to understand, then the code should make it clearer, dont worry, we will match parts of the code to the equation above so it becomes more tangible.\n",
47 | "\n",
48 | "# Part 2 A look at the data and the task\n",
49 | "\n",
50 | "### MNIST\n",
51 | "\n",
52 | "The MNIST data set contains 70,000 images of handwritten digits and their corresponding labels. The images are 28x28 with pixel values from 0 to 255. The labels are the digits from 0 to 9. By default 60,000 of these images belong to a training set and 10,000 of these images belong to a test set.\n",
53 | "\n",
54 | "### Fashion-MNIST\n",
55 | "\n",
56 | "Fashion-MNIST is a dataset of Zalando's article images—consisting of a training set of 60,000 examples and a test set of 10,000 examples. Each example is a 28x28 grayscale image, associated with a label from 10 classes. Zalando intends Fashion-MNIST to serve as a direct drop-in replacement for the original MNIST dataset for benchmarking machine learning algorithms. It shares the same image size and structure of training and testing splits.\n",
57 | "\n",
58 | "Each training and test example is assigned to one of the following labels:\n",
59 | "\n",
60 | "- 0 T-shirt/top\n",
61 | "- 1 Trouser\n",
62 | "- 2 Pullover\n",
63 | "- 3 Dress\n",
64 | "- 4 Coat\n",
65 | "- 5 Sandal\n",
66 | "- 6 Shirt\n",
67 | "- 7 Sneaker\n",
68 | "- 8 Bag\n",
69 | "- 9 Ankle boot\n",
70 | "\n",
71 | "## Task\n",
72 | "\n",
73 | "as you might guess, our goal is to train an NN that retains it's ability to perform well on MNIST after being retrained on only Fashion-MNIST"
74 | ]
75 | },
76 | {
77 | "cell_type": "code",
78 | "execution_count": 1,
79 | "metadata": {},
80 | "outputs": [],
81 | "source": [
82 | "import numpy as np\n",
83 | "from PIL import Image\n",
84 | "from matplotlib import pyplot as plt\n",
85 | "from matplotlib.pyplot import imshow\n",
86 | "\n",
87 | "from contlearn.getdata import getMNIST, getFashionMNIST\n",
88 | "\n",
89 | "%load_ext autoreload\n",
90 | "%autoreload 2\n",
91 | "%matplotlib inline"
92 | ]
93 | },
94 | {
95 | "cell_type": "code",
96 | "execution_count": 2,
97 | "metadata": {},
98 | "outputs": [],
99 | "source": [
100 | "# task A training and test set\n",
101 | "train_loader_mnist, test_loader_mnist = getMNIST(batch_size=32)\n",
102 | "\n",
103 | "# task B training and test set\n",
104 | "train_loader_fashion, test_loader_fashion = getFashionMNIST(batch_size=32)"
105 | ]
106 | },
107 | {
108 | "cell_type": "code",
109 | "execution_count": 3,
110 | "metadata": {},
111 | "outputs": [
112 | {
113 | "name": "stdout",
114 | "output_type": "stream",
115 | "text": [
116 | "tensor(1)\n",
117 | "torch.Size([28, 28])\n"
118 | ]
119 | },
120 | {
121 | "data": {
122 | "text/plain": [
123 | ""
124 | ]
125 | },
126 | "execution_count": 3,
127 | "metadata": {},
128 | "output_type": "execute_result"
129 | },
130 | {
131 | "data": {
132 | "image/png": "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\n",
133 | "text/plain": [
134 | ""
135 | ]
136 | },
137 | "metadata": {
138 | "needs_background": "light"
139 | },
140 | "output_type": "display_data"
141 | }
142 | ],
143 | "source": [
144 | "input_image, target_label = next(iter(train_loader_mnist))\n",
145 | "\n",
146 | "print(target_label[0])\n",
147 | "print(input_image[0][0].shape)\n",
148 | "\n",
149 | "img = Image.fromarray(input_image[0][0].detach().cpu().numpy()*255)\n",
150 | "\n",
151 | "plt.imshow(img)"
152 | ]
153 | },
154 | {
155 | "cell_type": "code",
156 | "execution_count": 4,
157 | "metadata": {},
158 | "outputs": [
159 | {
160 | "name": "stdout",
161 | "output_type": "stream",
162 | "text": [
163 | "Coat\n",
164 | "torch.Size([28, 28])\n"
165 | ]
166 | },
167 | {
168 | "data": {
169 | "text/plain": [
170 | ""
171 | ]
172 | },
173 | "execution_count": 4,
174 | "metadata": {},
175 | "output_type": "execute_result"
176 | },
177 | {
178 | "data": {
179 | "image/png": "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\n",
180 | "text/plain": [
181 | ""
182 | ]
183 | },
184 | "metadata": {
185 | "needs_background": "light"
186 | },
187 | "output_type": "display_data"
188 | }
189 | ],
190 | "source": [
191 | "input_image, target_label = next(iter(train_loader_fashion))\n",
192 | "\n",
193 | "fashion_key = {\n",
194 | " 0: \"T-shirt/top\",\n",
195 | " 1: \"Trouser\",\n",
196 | " 2: \"Pullover\",\n",
197 | " 3: \"Dress\",\n",
198 | " 4: \"Coat\",\n",
199 | " 5: \"Sandal\",\n",
200 | " 6: \"Shirt\",\n",
201 | " 7: \"Sneaker\",\n",
202 | " 8: \"Bag\",\n",
203 | " 9: \"Ankle boot\",\n",
204 | "}\n",
205 | "\n",
206 | "print(fashion_key[int(target_label[0].detach().cpu().numpy())])\n",
207 | "print(input_image[0][0].shape)\n",
208 | "\n",
209 | "img = Image.fromarray(input_image[0][0].detach().cpu().numpy()*255)\n",
210 | "\n",
211 | "plt.imshow(img)"
212 | ]
213 | },
214 | {
215 | "cell_type": "markdown",
216 | "metadata": {},
217 | "source": [
218 | "# Part 3 Baseline results\n",
219 | "\n",
220 | "first we train on MNIST and the we will observe the drop in performance once we retrain on Fashion-MNIST, WITHOUT Elastic Weight Consolidation"
221 | ]
222 | },
223 | {
224 | "cell_type": "code",
225 | "execution_count": 3,
226 | "metadata": {},
227 | "outputs": [
228 | {
229 | "name": "stdout",
230 | "output_type": "stream",
231 | "text": [
232 | "The autoreload extension is already loaded. To reload it, use:\n",
233 | " %reload_ext autoreload\n",
234 | "you are using PyTorch version 1.10.0+cu102\n",
235 | "you have 2 GPUs\n",
236 | "cuda:0\n"
237 | ]
238 | }
239 | ],
240 | "source": [
241 | "import math\n",
242 | "import random\n",
243 | "\n",
244 | "import numpy as np\n",
245 | "import torch\n",
246 | "from matplotlib import pyplot as plt\n",
247 | "from matplotlib.pyplot import imshow\n",
248 | "from tqdm.notebook import tqdm\n",
249 | "\n",
250 | "from contlearn.getmodels import MLP\n",
251 | "from contlearn.gettrainer import one_epoch_baseline, test, var2device\n",
252 | "\n",
253 | "%load_ext autoreload\n",
254 | "%autoreload 2\n",
255 | "%matplotlib inline\n",
256 | "\n",
257 | "print('you are using PyTorch version ',torch.__version__)\n",
258 | "\n",
259 | "if torch.cuda.is_available():\n",
260 | " use_cuda = True\n",
261 | " print(\"you have\", torch.cuda.device_count(), \"GPUs\")\n",
262 | " device = torch.device(\"cuda:0\")\n",
263 | " print(device)\n",
264 | "else:\n",
265 | " use_cuda = False\n",
266 | " print('no GPUs detected')\n",
267 | " device = torch.device(\"cpu\")"
268 | ]
269 | },
270 | {
271 | "cell_type": "code",
272 | "execution_count": 6,
273 | "metadata": {},
274 | "outputs": [
275 | {
276 | "name": "stdout",
277 | "output_type": "stream",
278 | "text": [
279 | "torch.Size([32, 28, 28])\n",
280 | "torch.Size([32, 10])\n"
281 | ]
282 | }
283 | ],
284 | "source": [
285 | "# initialize a new model\n",
286 | "\n",
287 | "model = MLP(hidden_size=256)\n",
288 | "\n",
289 | "if torch.cuda.is_available() and use_cuda:\n",
290 | " model.cuda()\n",
291 | " \n",
292 | "# push an image through it\n",
293 | "\n",
294 | "input_image, target_label = next(iter(train_loader_fashion))\n",
295 | "\n",
296 | "input_image = var2device(input_image).squeeze(1)\n",
297 | "\n",
298 | "print(input_image.shape)\n",
299 | "\n",
300 | "output = model(input_image)\n",
301 | "\n",
302 | "print(output.shape)"
303 | ]
304 | },
305 | {
306 | "cell_type": "code",
307 | "execution_count": 7,
308 | "metadata": {},
309 | "outputs": [],
310 | "source": [
311 | "def baseline_training(\n",
312 | " model, \n",
313 | " epochs, \n",
314 | " train_loader,\n",
315 | " test_loader,\n",
316 | " test2_loader = None,\n",
317 | " use_cuda=True, \n",
318 | "):\n",
319 | " \n",
320 | " \"\"\"\n",
321 | " This function saves the training curve data consisting\n",
322 | " training set loss and validation set accuracy over the\n",
323 | " course of the epochs of training.\n",
324 | " \n",
325 | " I set this up such that if you provide 2 test sets,you\n",
326 | " can watch the test accuracy change together during training\n",
327 | " on train_loder\n",
328 | " \"\"\"\n",
329 | " \n",
330 | " if torch.cuda.is_available() and use_cuda:\n",
331 | " model.cuda()\n",
332 | " \n",
333 | " train_loss, val_acc, val2_acc = [], [], []\n",
334 | " \n",
335 | " for epoch in tqdm(range(epochs)):\n",
336 | "\n",
337 | " epoch_loss = one_epoch_baseline(model,train_loader)\n",
338 | " train_loss.append(epoch_loss)\n",
339 | " \n",
340 | " acc = test(model,test_loader)\n",
341 | " val_acc.append(acc.detach().cpu().numpy())\n",
342 | " \n",
343 | " if test2_loader is not None:\n",
344 | " acc2 = test(model,test2_loader)\n",
345 | " val2_acc.append(acc2.detach().cpu().numpy())\n",
346 | " \n",
347 | " return train_loss, val_acc, val2_acc, model "
348 | ]
349 | },
350 | {
351 | "cell_type": "code",
352 | "execution_count": 10,
353 | "metadata": {},
354 | "outputs": [
355 | {
356 | "data": {
357 | "application/vnd.jupyter.widget-view+json": {
358 | "model_id": "0620996aa0b143caa339803ee51b4aec",
359 | "version_major": 2,
360 | "version_minor": 0
361 | },
362 | "text/plain": [
363 | " 0%| | 0/25 [00:00, ?it/s]"
364 | ]
365 | },
366 | "metadata": {},
367 | "output_type": "display_data"
368 | }
369 | ],
370 | "source": [
371 | "# set seeds for reproducibility and train the model using the training loop called\n",
372 | "# baseline_training\n",
373 | "\n",
374 | "torch.manual_seed(0)\n",
375 | "np.random.seed(0)\n",
376 | "random.seed(0)\n",
377 | "\n",
378 | "train_loss, val_acc, val2_acc, model = baseline_training(\n",
379 | " model,\n",
380 | " epochs = 25,\n",
381 | " train_loader = train_loader_mnist,\n",
382 | " test_loader = test_loader_mnist,\n",
383 | ")\n",
384 | "\n",
385 | "# save the trained model\n",
386 | "model = model.cpu()\n",
387 | "torch.save(model, \"files/basemodel.pth\")"
388 | ]
389 | },
390 | {
391 | "cell_type": "code",
392 | "execution_count": 11,
393 | "metadata": {},
394 | "outputs": [
395 | {
396 | "data": {
397 | "image/png": "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\n",
398 | "text/plain": [
399 | ""
400 | ]
401 | },
402 | "metadata": {
403 | "needs_background": "light"
404 | },
405 | "output_type": "display_data"
406 | }
407 | ],
408 | "source": [
409 | "plt.figure()\n",
410 | "plt.xlabel('epochs', fontsize=25)\n",
411 | "plt.ylabel('validation accuracy', fontsize=25)\n",
412 | "plt.plot(val_acc, label='mnist')\n",
413 | "plt.legend()\n",
414 | "plt.show()"
415 | ]
416 | },
417 | {
418 | "cell_type": "markdown",
419 | "metadata": {},
420 | "source": [
421 | "### learning curve\n",
422 | "you should get something like this where the accuracy starts to plateau at around 75% for this simple feed forward model\n",
423 | "\n",
424 | ""
425 | ]
426 | },
427 | {
428 | "cell_type": "code",
429 | "execution_count": 26,
430 | "metadata": {},
431 | "outputs": [
432 | {
433 | "name": "stdout",
434 | "output_type": "stream",
435 | "text": [
436 | "mnist accuracy tensor(0.8238)\n",
437 | "fashion accuracy tensor(0.0704)\n"
438 | ]
439 | }
440 | ],
441 | "source": [
442 | "# you can use this to load the model instead of training it from scratch like above \n",
443 | "\n",
444 | "model = torch.load(\"files/basemodel.pth\")\n",
445 | "\n",
446 | "if torch.cuda.is_available() and use_cuda:\n",
447 | " model.cuda()\n",
448 | " \n",
449 | "# tensor(0.8238, device='cuda:0') means that the test set accuracy was 82.4%\n",
450 | "# compared to a baseline accuracy of 10% if the model is choosing randomly\n",
451 | "print('mnist accuracy', test(model,test_loader_mnist))\n",
452 | "print('fashion accuracy', test(model,test_loader_fashion))"
453 | ]
454 | },
455 | {
456 | "cell_type": "markdown",
457 | "metadata": {},
458 | "source": [
459 | "### Catastrophic Forgetting\n",
460 | "\n",
461 | "This is one of the main problems we are trying to solve in the continual learning, aka lifelong learning, aka sequential learning, research field. As you can see, learning Fashion MNIST also degrades performance on original MNIST. In part this is because some of the useful parameters, aka weights, used for original MNIST, are overwritten or updated past what is useful for original MNIST in order to become useful for Fashion MNIST\n",
462 | "\n",
463 | "We expect something like this in which the fashion accuracy increases from it's random performance at around 10% to almost 40% and the mnist test accuracy drops to below 30% from a previous performance of around 80%\n",
464 | "\n",
465 | "\n"
466 | ]
467 | },
468 | {
469 | "cell_type": "code",
470 | "execution_count": 16,
471 | "metadata": {},
472 | "outputs": [
473 | {
474 | "data": {
475 | "application/vnd.jupyter.widget-view+json": {
476 | "model_id": "04ee7766e75c4b2ba21f154dac08758b",
477 | "version_major": 2,
478 | "version_minor": 0
479 | },
480 | "text/plain": [
481 | " 0%| | 0/25 [00:00, ?it/s]"
482 | ]
483 | },
484 | "metadata": {},
485 | "output_type": "display_data"
486 | }
487 | ],
488 | "source": [
489 | "torch.manual_seed(0)\n",
490 | "np.random.seed(0)\n",
491 | "random.seed(0)\n",
492 | "\n",
493 | "train_loss, val_acc, val2_acc, model = baseline_training(\n",
494 | " model,\n",
495 | " epochs = 25,\n",
496 | " train_loader = train_loader_fashion,\n",
497 | " test_loader = test_loader_fashion,\n",
498 | " test2_loader = test_loader_mnist,\n",
499 | ")"
500 | ]
501 | },
502 | {
503 | "cell_type": "code",
504 | "execution_count": 17,
505 | "metadata": {},
506 | "outputs": [
507 | {
508 | "data": {
509 | "image/png": "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\n",
510 | "text/plain": [
511 | ""
512 | ]
513 | },
514 | "metadata": {
515 | "needs_background": "light"
516 | },
517 | "output_type": "display_data"
518 | }
519 | ],
520 | "source": [
521 | "plt.figure()\n",
522 | "plt.xlabel('epochs', fontsize=25)\n",
523 | "plt.ylabel('validation accuracy', fontsize=25)\n",
524 | "plt.plot(val_acc, label='fashion')\n",
525 | "plt.plot(val2_acc, label='mnist')\n",
526 | "plt.legend()\n",
527 | "plt.show()"
528 | ]
529 | },
530 | {
531 | "cell_type": "markdown",
532 | "metadata": {},
533 | "source": [
534 | "# Part 4 Elastic Weight Consolidation\n",
535 | "\n",
536 | "now that we have implemented the control group, lets implement the experimental group\n",
537 | "\n",
538 | "instead of revisiting every old MNIST example to build our Fisher Information Matrix, we will visit num_samples of them to form a approximate of the matrix\n",
539 | "\n",
540 | "please read the comments in the EWC class, which explains each step of the math needed\n",
541 | "to calculate the EWC loss function, dont worry if it doesnt make sense, we will then go through it step by step\n",
542 | "\n",
543 | "After gathering your examples of task A in the cell below, mnist, run the below cells from the second cell all the way to \n",
544 | "\n",
545 | "#### sum the squares of the gradients\n",
546 | "\n",
547 | "comparing the norm of the precision _matrices when you use a pretrained model and using a randomly initialized model "
548 | ]
549 | },
550 | {
551 | "cell_type": "code",
552 | "execution_count": 4,
553 | "metadata": {},
554 | "outputs": [
555 | {
556 | "name": "stdout",
557 | "output_type": "stream",
558 | "text": [
559 | "num_samples 512\n"
560 | ]
561 | }
562 | ],
563 | "source": [
564 | "# instead of revisiting every old MNIST example to build our Fisher Information Matrix\n",
565 | "# use num_samples of them to calculate a approximate of the matrix\n",
566 | "\n",
567 | "torch.manual_seed(0)\n",
568 | "np.random.seed(0)\n",
569 | "random.seed(0)\n",
570 | "\n",
571 | "num_batches = 16\n",
572 | "\n",
573 | "old_tasks = []\n",
574 | "\n",
575 | "for sample in range(num_batches):\n",
576 | " input_batch, target_batch = next(iter(train_loader_mnist))\n",
577 | " for image in input_batch:\n",
578 | " old_tasks.append(image)\n",
579 | " \n",
580 | "print(\"num_samples\", len(old_tasks))"
581 | ]
582 | },
583 | {
584 | "cell_type": "code",
585 | "execution_count": 5,
586 | "metadata": {},
587 | "outputs": [
588 | {
589 | "name": "stdout",
590 | "output_type": "stream",
591 | "text": [
592 | "The autoreload extension is already loaded. To reload it, use:\n",
593 | " %reload_ext autoreload\n",
594 | "accuracy on mnist test set 0.8237999677658081\n"
595 | ]
596 | }
597 | ],
598 | "source": [
599 | "from copy import deepcopy\n",
600 | "\n",
601 | "import torch\n",
602 | "from torch import nn\n",
603 | "from torch.nn import functional as F\n",
604 | "from torch.autograd import Variable\n",
605 | "from torch import optim\n",
606 | "import torch.utils.data\n",
607 | "\n",
608 | "from contlearn.getmodels import MLP\n",
609 | "from contlearn.gettrainer import var2device\n",
610 | "\n",
611 | "%load_ext autoreload\n",
612 | "%autoreload 2\n",
613 | "%matplotlib inline\n",
614 | "\n",
615 | "\n",
616 | "# Uncomment one of the two below lines of code that instantiates a model\n",
617 | "### birth a new randomly initiated model ###\n",
618 | "\n",
619 | "# model = MLP(hidden_size=256)\n",
620 | "\n",
621 | "### load a model previously trained on one task, task A ###\n",
622 | "\n",
623 | "model = torch.load(\"files/basemodel.pth\")\n",
624 | "\n",
625 | "#####\n",
626 | "\n",
627 | "\n",
628 | "if torch.cuda.is_available() and use_cuda:\n",
629 | " model.cuda() \n",
630 | "# tensor(0.8238, device='cuda:0') means that the test set accuracy was 82.4%\n",
631 | "# compared to a baseline accuracy of 10% if the model is choosing randomly\n",
632 | "acc = test(model,test_loader_mnist)\n",
633 | "print(\"accuracy on mnist test set\", acc.item())"
634 | ]
635 | },
636 | {
637 | "cell_type": "code",
638 | "execution_count": 6,
639 | "metadata": {},
640 | "outputs": [],
641 | "source": [
642 | "class EWC(object):\n",
643 | " \n",
644 | " \"\"\"\n",
645 | " Class to calculate the Fisher Information Matrix\n",
646 | " used in the Elastic Weight Consolidation portion\n",
647 | " of the loss function\n",
648 | " \"\"\"\n",
649 | " \n",
650 | " def __init__(self, model: nn.Module, dataset: list):\n",
651 | "\n",
652 | " self.model = model #pretrained model\n",
653 | " self.dataset = dataset #samples from the old task or tasks\n",
654 | " \n",
655 | " # n is the string name of the parameter matrix p, aka theta, aka weights\n",
656 | " # in self.params we reference all of those weights that are open to\n",
657 | " # being updated by the gradient\n",
658 | " self.params = {n: p for n, p in self.model.named_parameters() if p.requires_grad}\n",
659 | " \n",
660 | " # make a copy of the old weights, ie theta_A,star, ie 𝜃∗A, in the loss equation\n",
661 | " # we need this to calculate (𝜃 - 𝜃∗A)^2 because self.params will be changing \n",
662 | " # upon every backward pass and parameter update by the optimizer\n",
663 | " self._means = {}\n",
664 | " for n, p in deepcopy(self.params).items():\n",
665 | " self._means[n] = var2device(p.data)\n",
666 | " \n",
667 | " # calculate the fisher information matrix \n",
668 | " self._precision_matrices = self._diag_fisher()\n",
669 | "\n",
670 | " def _diag_fisher(self):\n",
671 | " \n",
672 | " # save a copy of the zero'd out version of\n",
673 | " # each layer's parameters of the same shape\n",
674 | " # to precision_matrices[n]\n",
675 | " precision_matrices = {}\n",
676 | " for n, p in deepcopy(self.params).items():\n",
677 | " p.data.zero_()\n",
678 | " precision_matrices[n] = var2device(p.data)\n",
679 | "\n",
680 | " # we need the model to calculate the gradient but\n",
681 | " # we have no intention in this step to actually update the model\n",
682 | " # that will have to wait for the combining of this EWC loss term\n",
683 | " # with the new task's loss term\n",
684 | " self.model.eval()\n",
685 | " for input in self.dataset:\n",
686 | " self.model.zero_grad()\n",
687 | " # remove channel dim, these are greyscale, not color rgb images\n",
688 | " # bs,1,h,w -> bs,h,w\n",
689 | " input = input.squeeze(1)\n",
690 | " input = var2device(input)\n",
691 | " output = self.model(input).view(1, -1)\n",
692 | " label = output.max(1)[1].view(-1)\n",
693 | " # calculate loss and backprop\n",
694 | " loss = F.nll_loss(F.log_softmax(output, dim=1), label)\n",
695 | " loss.backward()\n",
696 | "\n",
697 | " for n, p in self.model.named_parameters():\n",
698 | " precision_matrices[n].data += p.grad.data ** 2 / len(self.dataset)\n",
699 | "\n",
700 | " precision_matrices = {n: p for n, p in precision_matrices.items()}\n",
701 | " return precision_matrices\n",
702 | "\n",
703 | " def penalty(self, model: nn.Module):\n",
704 | " loss = 0\n",
705 | " for n, p in model.named_parameters():\n",
706 | " _loss = self._precision_matrices[n] * (p - self._means[n]) ** 2\n",
707 | " loss += _loss.sum()\n",
708 | " return loss\n",
709 | " \n",
710 | "# place the model pretrained on mnist, but not fashion-mnist, along with some mnist examples\n",
711 | "# into the Elastic Weight Consolidation object to perform EWC related tasks like calculating the\n",
712 | "# Fisher Matrix\n",
713 | "ewc = EWC(model, old_tasks)"
714 | ]
715 | },
716 | {
717 | "cell_type": "markdown",
718 | "metadata": {},
719 | "source": [
720 | "### Empirical Estimate of the Fisher Information Matrix\n",
721 | "\n",
722 | "Lets work through the _diag_fisher() method that calculates the fisher matrix, \n",
723 | "\n",
724 | "\n",
725 | "together, step by step\n",
726 | "\n",
727 | "as a reminder here is the equation for the fisher matrix one more time\n",
728 | "\n",
729 | "$$\n",
730 | "I_{\\theta_\\mathcal{A}^*} = \\frac{1}{N} \\sum_{i=1}^{N} \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*) \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)^T\n",
731 | "$$\n",
732 | "\n",
733 | "To my understanding, the equation is saying, sum N of these matrices on top of each other such that the resultant matrix is the same shape of the individual matrices. The gradient term $\\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)$ is a vector and the expression $ \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*) \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)^T$ represents the [outer product](https://en.wikipedia.org/wiki/Outer_product) of these two vectors to produce a matrix where the elements of that matrix we are interested in is the diagonal $F_{ii}$. ie we are interested in $I_{ii}$, not $I_{ij}$. \n",
734 | "\n",
735 | "note: does this mean that in addition to penalizing moving $\\theta_i$ when $F_{ii}$ is high, we could also penalize moving both $\\theta_i$ and $\\theta_j$ together when $F_{ij}$ is high?\n",
736 | "\n",
737 | "### The Fisher Information Matrix is w.r.t the log probability of the NN prediction p(y|x,theta), rather than w.r.t the log likelihood of the data p(x|theta)\n",
738 | "\n",
739 | "notice that the term grad log p(D|theta)\n",
740 | "\n",
741 | "$$\\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)$$\n",
742 | "\n",
743 | "looks alot like the formula for the gradient of the loss function with respect to the parameters.\n",
744 | "\n",
745 | "Thats because for the purposes of EWC, it is!\n",
746 | "\n",
747 | "from the [Overcoming catastrophic forgetting in neural networks paper](https://www.pnas.org/content/114/13/3521):\n",
748 | "\n",
749 | "\"\"\"\n",
750 | "Note that the log probability of the data given the parameters logp(D|θ) is simply the negative of the loss function for the problem at hand −L(θ). \n",
751 | "\"\"\"\n",
752 | "\n",
753 | "The key mental leap we need to make is that in our machine learning model, p(D|θ) is the probability of the target label given the parameters rather than the p(X|θ) we are used to seeing that represents the likelihood of observing X given θ. \n",
754 | "\n",
755 | "Had the neural netword ended in a softmax layer the term $p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)$ would be the softmax output, but in the code this is combined with the log using the F.log_softmax function. \n",
756 | "\n",
757 | "$ log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)$ is the log likelihood. In the code below this is:\n",
758 | "\n",
759 | "```\n",
760 | "F.log_softmax(output, dim=1)\n",
761 | "```\n",
762 | "\n",
763 | "from [the pytorch docs](https://pytorch.org/docs/stable/generated/torch.nn.NLLLoss.html#torch.nn.NLLLoss)\n",
764 | "\n",
765 | "\"\"\"\n",
766 | "Obtaining log-probabilities in a neural network is easily achieved by adding a LogSoftmax layer in the last layer of your network. You may use CrossEntropyLoss instead, if you prefer not to add an extra layer.\n",
767 | "\"\"\"\n",
768 | "\n",
769 | "Meaning that the full line of code:\n",
770 | "\n",
771 | "```\n",
772 | "F.nll_loss(F.log_softmax(output, dim=1), label)\n",
773 | "```\n",
774 | "\n",
775 | "is essentially the cross entropy loss"
776 | ]
777 | },
778 | {
779 | "cell_type": "code",
780 | "execution_count": 8,
781 | "metadata": {},
782 | "outputs": [
783 | {
784 | "name": "stdout",
785 | "output_type": "stream",
786 | "text": [
787 | "name of this layer's weights: fc1.weight\n",
788 | "shape of this matrix W torch.Size([256, 784])\n",
789 | "norm of the matrix 0.0\n"
790 | ]
791 | }
792 | ],
793 | "source": [
794 | "precision_matrices = {}\n",
795 | "for n, p in deepcopy(ewc.params).items():\n",
796 | " \n",
797 | " print(\"name of this layer's weights:\", n)\n",
798 | " p.data.zero_()\n",
799 | " precision_matrices[n] = var2device(p.data)\n",
800 | " print(\"shape of this matrix W\", precision_matrices[n].shape)\n",
801 | " #print(precision_matrices[n])\n",
802 | " print(\"norm of the matrix\",torch.norm(precision_matrices[n]).item())\n",
803 | " break"
804 | ]
805 | },
806 | {
807 | "cell_type": "markdown",
808 | "metadata": {},
809 | "source": [
810 | "#### the code above\n",
811 | "As a reminder, your input shape x is (batch_size, 784) becasue 28*28 = 784. \n",
812 | "\n",
813 | "The matrix W of this first linear, aka affine, aka fully connect layer, is\n",
814 | "(256, 784) because xW^T = a of shape (batch_size, 256) which is the shape of the first layer's activations\n",
815 | "\n",
816 | "#### the code below\n",
817 | "we pass just one image through our model, and examine the tensors being created\n",
818 | "as they are applied to just the first layer of parameters ofcourse the\n",
819 | "real thing accumulates this update through all the samples and all the layers"
820 | ]
821 | },
822 | {
823 | "cell_type": "code",
824 | "execution_count": 9,
825 | "metadata": {},
826 | "outputs": [
827 | {
828 | "name": "stdout",
829 | "output_type": "stream",
830 | "text": [
831 | "input.shape torch.Size([1, 28, 28])\n",
832 | "[0.0, 0.0, 4.682543, 0.0, 4.328751, 0.0, 7.281813, 0.0, 0.0, 0.0]\n",
833 | "predicted number tensor([6], device='cuda:0')\n",
834 | "loss 0.12338782846927643\n"
835 | ]
836 | },
837 | {
838 | "data": {
839 | "image/png": "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\n",
840 | "text/plain": [
841 | ""
842 | ]
843 | },
844 | "metadata": {
845 | "needs_background": "light"
846 | },
847 | "output_type": "display_data"
848 | }
849 | ],
850 | "source": [
851 | "ewc.model.eval()\n",
852 | "for input in ewc.dataset:\n",
853 | " ewc.model.zero_grad()\n",
854 | " # no need for the channel dim, these are greyscale, not color rgb images\n",
855 | " # bs,1,h,w -> bs,h,w\n",
856 | " input = input.squeeze(1)\n",
857 | " input = var2device(input)\n",
858 | " output = ewc.model(input).view(1, -1)\n",
859 | " label = output.max(1)[1].view(-1)\n",
860 | " loss = F.nll_loss(F.log_softmax(output, dim=1), label)\n",
861 | " loss.backward() \n",
862 | " break\n",
863 | "\n",
864 | "print(\"input.shape\", input.shape)\n",
865 | "img = Image.fromarray(input[0].detach().cpu().numpy()*255)\n",
866 | "plt.imshow(img)\n",
867 | "\n",
868 | "print(list(output.detach().cpu().numpy()[0]))\n",
869 | "print(\"predicted number\",label)\n",
870 | "print(\"loss\",loss.item())"
871 | ]
872 | },
873 | {
874 | "cell_type": "markdown",
875 | "metadata": {},
876 | "source": [
877 | "### sum the squares of the gradients\n",
878 | "\n",
879 | "the cell below is the last one you should run when comparing the [norm](https://pytorch.org/docs/stable/generated/torch.norm.html) of the matrix between when the model is pretrained or not.\n",
880 | "\n",
881 | "noticed that when you ran the above steps with a pretrained model, the norm is larger than when run with a randomly initialized model, why is this? "
882 | ]
883 | },
884 | {
885 | "cell_type": "code",
886 | "execution_count": 11,
887 | "metadata": {},
888 | "outputs": [
889 | {
890 | "name": "stdout",
891 | "output_type": "stream",
892 | "text": [
893 | "name of this layer's weights: fc1.weight\n",
894 | "shape of p.grad.data torch.Size([256, 784])\n",
895 | "shape of p.grad.data ** 2 torch.Size([256, 784])\n",
896 | "norm of the matrix 0.0003205974353477359\n"
897 | ]
898 | }
899 | ],
900 | "source": [
901 | "for n, p in ewc.params.items():\n",
902 | " print(\"name of this layer's weights:\", n)\n",
903 | " print(\"shape of p.grad.data\", p.grad.data.shape)\n",
904 | " print(\"shape of p.grad.data ** 2\", (p.grad.data ** 2).shape)\n",
905 | " precision_matrices[n].data += p.grad.data ** 2 / len(ewc.dataset)\n",
906 | " #print(precision_matrices[n])\n",
907 | " print(\"norm of the matrix\",torch.norm(precision_matrices[n]).item())\n",
908 | " break"
909 | ]
910 | },
911 | {
912 | "cell_type": "markdown",
913 | "metadata": {},
914 | "source": [
915 | "### The Math in the Code\n",
916 | "\n",
917 | "One clarification that I think needs to be made that was brought to my attention on [Reddit](https://www.reddit.com/r/MachineLearning/comments/t2riby/p_i_made_the_kind_of_tutorial_i_wish_someone_had/?utm_source=share&utm_medium=web2x&context=3). The equation for the empirical fisher below suggests we are calculating the full Fisher Matrix:\n",
918 | "\n",
919 | "$$\n",
920 | "I_{\\theta_\\mathcal{A}^*} = \\frac{1}{N} \\sum_{i=1}^{N} \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*) \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)^T\n",
921 | "$$\n",
922 | "\n",
923 | "But in the code we are not, this line is where the diagonal terms are calculated\n",
924 | "\n",
925 | "```\n",
926 | "p.grad.data ** 2\n",
927 | "```\n",
928 | "\n",
929 | "the above line of code corresponds only to the diagonal component of the matrix $I_{\\theta_\\mathcal{A}^*}$ above.\n",
930 | "\n",
931 | "Notice `p.grad.data ** 2` is the same shape as the W for each layer, thats becasue `** 2` is an element-wise operation where we square every element. which is essentially the gradient of the loss function w.r.t. just one parameter, aka weight, multiplied by itself. If we took all the layer's `p.grad.data ** 2`'s and flattened them out into a very long vector, then that would be the just diagonal of the fisher matrix we see in the above equation. So we have only calculated the F_ii terms of the matrix and none of the F_ij terms. \n",
932 | "\n",
933 | "note: the diagonal therefore must all be positive valued\n",
934 | "\n",
935 | "The fisher diagonals are summed over all the N examples and divided by N, aka we average them to get the mean, aka the expectation, which is why in the actual EWS class, the code is written\n",
936 | "\n",
937 | "```\n",
938 | "+= p.grad.data ** 2 / len(self.dataset)\n",
939 | "```\n",
940 | "\n",
941 | "and placed in the inner loop of:\n",
942 | "\n",
943 | "```\n",
944 | "for input in self.dataset:\n",
945 | "```\n",
946 | "\n",
947 | "After the code accumulates over all N examples, it completes the rest of the equation:\n",
948 | "\n",
949 | "$$\n",
950 | "\\frac{1}{N} \\sum_{i=1}^{N} \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*) \\nabla_\\theta log \\ p(x_{\\mathcal{A}, i}|\\theta_\\mathcal{A}^*)^T\n",
951 | "$$\n",
952 | "\n",
953 | "\n",
954 | "### using the F_i in the overall loss function\n",
955 | "\n",
956 | "this line of code in the `penalty()` method of the EWS class\n",
957 | "\n",
958 | "```\n",
959 | "_loss = self._precision_matrices[n] * (p - self._means[n]) ** 2\n",
960 | "```\n",
961 | "\n",
962 | "corresponds to this formula\n",
963 | "\n",
964 | "$$ F_{i} \\left(\\theta_{i} - \\theta_{A , i}^{*}\\right)^{2} $$\n",
965 | "\n",
966 | "where `self._precision_matrices[n]` = $ F_{i}$ and \n",
967 | "`(p - self._means[n]) ** 2` = $\\left(\\theta_{i} - \\theta_{A , i}^{*}\\right)^{2} $\n",
968 | "\n",
969 | "this is then summed over all parameters `loss += _loss.sum()`\n",
970 | "and done for each layer, which is why the `penaly()` method places this line of code in the inner loop of `for n, p in model.named_parameters():`\n",
971 | "\n",
972 | "later in the code for EWC training, this has to be added to the loss for task B `F.cross_entropy(output, target)` and scaled by \n",
973 | "\n",
974 | "$\\lambda$ = importance, for the full line of code:\n",
975 | "\n",
976 | "`loss = F.cross_entropy(output, target) + importance * ewc.penalty(model)`\n",
977 | "\n",
978 | "which is the equivalent of our EWC loss function\n",
979 | "\n",
980 | "$$\n",
981 | "L \\left(\\right. \\theta \\left.\\right) = L_{B} \\left(\\right. \\theta \\left.\\right) + \\underset{i}{\\sum} \\frac{\\lambda}{2} F_{i} \\left(\\theta_{i} - \\theta_{A , i}^{*}\\right)^{2} $$\n",
982 | "\n",
983 | "### Explaination\n",
984 | "\n",
985 | "For the full proof read [Fisher Information Matrix by Agustinus Kristiadi](https://agustinus.kristia.de/techblog/2018/03/11/fisher-information/) or [Fisher Information Matrix by Yuan-Hong Liao](https://andrewliao11.github.io/blog/fisher-info-matrix/). \n",
986 | "\n",
987 | "The end result of the proof is that \"The Fisher is the negative expectation of the Hessian of the log-likelihood\"\n",
988 | "\n",
989 | "The Hessian is a square matrix of second-order partial derivatives of a scalar-valued function, or scalar field. It describes the local curvature of a function of many variables. So the diagonal of the Hessian is the 2nd derivative of the loss (negative log likelihood) with respect to the parameters. Imagine if you only had 2 parameters, the Fisher Information over a patch of values for $\\theta_i$ adn $\\theta_j$ might look like this:\n",
990 | "\n",
991 | "Hypothetical F_ij Surface, ignore the vertical axis label for now\n",
992 | "
\n",
993 | "\n",
994 | "
\n",
995 | "\n",
996 | "The warmer areas (more red) are the areas of higher curvature, the areas of higher curvature are the areas of local minima and maxima. If you turn the above figure upside down, this would look just like the topological manifold, aka surface, of the loss function, And what is gradient descent trying to do? it is trying to find a local minima on this loss surface, aka a local maxima on the fisher information surface. \n",
997 | "\n",
998 | "In intuitive terms, both the Hessian and Fisher Matrix describe how sharp, ie curved, each point in $\\theta$ space is, with respect to the loss.\n",
999 | "\n",
1000 | "But wait a minute, cant we have a valley with a very flat and wide floor? yes we can, and it very well might be that some of our parameters have settled into such a region while training on task A. However, if that is the case, then while $\\theta$ is in the center of such a region, it can move in various directions and still contribute to good performance on task A, which means we should not penalize changes in this region until $\\theta$ reaches the edge of such a region, in which case, the Fisher information should start to increase. \n",
1001 | "\n",
1002 | "Basically the Fisher Matrix is the negative of this matrix below, where you replace f with log[p(x|theta)] and replace x with theta\n",
1003 | "
\n",
1004 | "\n",
1005 | "
\n",
1006 | "\n",
1007 | "*The $F_i$ is higher for those $\\theta_i$ that are already optimized into a narrow region and thus if changed, would cause an increase in the loss for task A, thereby reducing the performance on task A described by that loss. More of these \"important and just right\" parameters will exist for trained models and thus the norm on the fisher diagonals will be higher. For models not yet trained on task A, there are fewer parameters that have been optimized to help solve task A, therefore fewer parameters that when changed will have a significant effect on the loss for task A*"
1008 | ]
1009 | },
1010 | {
1011 | "cell_type": "code",
1012 | "execution_count": 12,
1013 | "metadata": {},
1014 | "outputs": [
1015 | {
1016 | "data": {
1017 | "text/plain": [
1018 | "tensor(0., grad_fn=)"
1019 | ]
1020 | },
1021 | "execution_count": 12,
1022 | "metadata": {},
1023 | "output_type": "execute_result"
1024 | }
1025 | ],
1026 | "source": [
1027 | "# since the parameters have not changed yet, we expect (𝜃 - 𝜃∗)2 to be zero throughout\n",
1028 | "ewc.penalty(model)"
1029 | ]
1030 | },
1031 | {
1032 | "cell_type": "markdown",
1033 | "metadata": {},
1034 | "source": [
1035 | "### EWC Training\n",
1036 | "\n",
1037 | "Load a model pretrained on MNIST, then train it on Fashion-MNIST while observing the new training's effect on MNIST performance"
1038 | ]
1039 | },
1040 | {
1041 | "cell_type": "code",
1042 | "execution_count": 20,
1043 | "metadata": {},
1044 | "outputs": [
1045 | {
1046 | "name": "stdout",
1047 | "output_type": "stream",
1048 | "text": [
1049 | "accuracy on mnist test set 0.8238000273704529\n"
1050 | ]
1051 | }
1052 | ],
1053 | "source": [
1054 | "# verify that the model you are about to retrain is indeed pretrained\n",
1055 | "acc = test(model,test_loader_mnist)\n",
1056 | "print(\"accuracy on mnist test set\", acc.item())"
1057 | ]
1058 | },
1059 | {
1060 | "cell_type": "code",
1061 | "execution_count": 21,
1062 | "metadata": {},
1063 | "outputs": [],
1064 | "source": [
1065 | "def one_epoch_ewc(\n",
1066 | " ewc: EWC, \n",
1067 | " importance: float,\n",
1068 | " model: nn.Module, \n",
1069 | " data_loader: torch.utils.data.DataLoader, \n",
1070 | " lr = 1e-3,\n",
1071 | "):\n",
1072 | " model.train()\n",
1073 | " epoch_loss = 0\n",
1074 | " optimizer = optim.SGD(params=model.parameters(), lr=lr)\n",
1075 | " for input, target in data_loader:\n",
1076 | " # no need for the channel dim\n",
1077 | " # bs,1,h,w -> bs,h,w\n",
1078 | " input = input.squeeze(1) \n",
1079 | " input, target = var2device(input), var2device(target)\n",
1080 | " optimizer.zero_grad()\n",
1081 | " output = model(input)\n",
1082 | " loss = F.cross_entropy(output, target) + importance * ewc.penalty(model)\n",
1083 | " epoch_loss += loss.item()\n",
1084 | " loss.backward()\n",
1085 | " optimizer.step()\n",
1086 | " return epoch_loss / len(data_loader)\n",
1087 | "\n",
1088 | "def ewc_training(\n",
1089 | " ewc,\n",
1090 | " importance,\n",
1091 | " model, \n",
1092 | " epochs, \n",
1093 | " train_loader,\n",
1094 | " test_loader,\n",
1095 | " test2_loader = None,\n",
1096 | " use_cuda=True, \n",
1097 | "):\n",
1098 | " \n",
1099 | " \"\"\"\n",
1100 | " This function saves the training curve data consisting\n",
1101 | " training set loss and validation set accuracy over the\n",
1102 | " course of the epochs of training.\n",
1103 | " \n",
1104 | " I set this up such that if you provide 2 test sets,you\n",
1105 | " can watch the test accuracy change together during training\n",
1106 | " on train_loder\n",
1107 | " \"\"\"\n",
1108 | " \n",
1109 | " if torch.cuda.is_available() and use_cuda:\n",
1110 | " model.cuda()\n",
1111 | " \n",
1112 | " train_loss, val_acc, val2_acc = [], [], []\n",
1113 | " \n",
1114 | " for epoch in tqdm(range(epochs)):\n",
1115 | "\n",
1116 | " epoch_loss = one_epoch_ewc(ewc,importance, model,train_loader)\n",
1117 | " train_loss.append(epoch_loss)\n",
1118 | " \n",
1119 | " acc = test(model,test_loader)\n",
1120 | " val_acc.append(acc.detach().cpu().numpy())\n",
1121 | " \n",
1122 | " if test2_loader is not None:\n",
1123 | " acc2 = test(model,test2_loader)\n",
1124 | " val2_acc.append(acc2.detach().cpu().numpy())\n",
1125 | " \n",
1126 | " return train_loss, val_acc, val2_acc, model "
1127 | ]
1128 | },
1129 | {
1130 | "cell_type": "code",
1131 | "execution_count": 22,
1132 | "metadata": {},
1133 | "outputs": [
1134 | {
1135 | "data": {
1136 | "application/vnd.jupyter.widget-view+json": {
1137 | "model_id": "b8b24f5220c942fba0da4b1f6cc6e46c",
1138 | "version_major": 2,
1139 | "version_minor": 0
1140 | },
1141 | "text/plain": [
1142 | " 0%| | 0/25 [00:00, ?it/s]"
1143 | ]
1144 | },
1145 | "metadata": {},
1146 | "output_type": "display_data"
1147 | }
1148 | ],
1149 | "source": [
1150 | "torch.manual_seed(0)\n",
1151 | "np.random.seed(0)\n",
1152 | "random.seed(0)\n",
1153 | "\n",
1154 | "train_loss, val_acc, val2_acc, model = ewc_training(\n",
1155 | " ewc = ewc,\n",
1156 | " importance = 1000,\n",
1157 | " model = model,\n",
1158 | " epochs = 25,\n",
1159 | " train_loader = train_loader_fashion,\n",
1160 | " test_loader = test_loader_fashion,\n",
1161 | " test2_loader = test_loader_mnist,\n",
1162 | ")"
1163 | ]
1164 | },
1165 | {
1166 | "cell_type": "code",
1167 | "execution_count": 23,
1168 | "metadata": {},
1169 | "outputs": [
1170 | {
1171 | "data": {
1172 | "image/png": "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\n",
1173 | "text/plain": [
1174 | ""
1175 | ]
1176 | },
1177 | "metadata": {
1178 | "needs_background": "light"
1179 | },
1180 | "output_type": "display_data"
1181 | }
1182 | ],
1183 | "source": [
1184 | "plt.figure()\n",
1185 | "plt.xlabel('epochs', fontsize=25)\n",
1186 | "plt.ylabel('validation accuracy', fontsize=25)\n",
1187 | "plt.plot(val_acc, label='fashion')\n",
1188 | "plt.plot(val2_acc, label='mnist')\n",
1189 | "plt.legend()\n",
1190 | "plt.show()"
1191 | ]
1192 | },
1193 | {
1194 | "cell_type": "code",
1195 | "execution_count": 24,
1196 | "metadata": {},
1197 | "outputs": [],
1198 | "source": [
1199 | "# save the trained model\n",
1200 | "model = model.cpu()\n",
1201 | "torch.save(model, \"files/ewcmodel.pth\")"
1202 | ]
1203 | },
1204 | {
1205 | "cell_type": "markdown",
1206 | "metadata": {},
1207 | "source": [
1208 | "### Conclusion\n",
1209 | "\n",
1210 | "We expect a result like this, where instead of the mnist accuracy dropping to below 30%, it degrades by a far lesser amount, it is still above 70%, from around 80% at the end of mnist training. meanwhile, learning of the fashion-mnist dataset still proceed forward, rising above 40% from a baseline below 10%. The performances here are modest, but that is not the point, im sure with architectures specialized for images like convolutional layers or vision transformers you can get the final accuracy much higher, the point here is that we were able to preserve previously learned capabilities of our NN\n",
1211 | "\n",
1212 | "