Skip to content

Commit b40f239

Browse files
author
Maksymilian Graczyk
committed
Update lenet_forward_pass.ipynb given the recent changes
1 parent f5f024f commit b40f239

File tree

1 file changed

+188
-61
lines changed

1 file changed

+188
-61
lines changed

examples/lenet_forward_pass.ipynb

Lines changed: 188 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,26 @@
11
{
22
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Running a forward pass through LeNet using MNIST and Joey"
8+
]
9+
},
10+
{
11+
"cell_type": "markdown",
12+
"metadata": {},
13+
"source": [
14+
"In this example, we will construct LeNet using Joey, set it up with pretrained parameters and run a forward pass through it with test data from MNIST. The results will be compared to the PyTorch ones to confirm Joey's numerical correctness."
15+
]
16+
},
17+
{
18+
"cell_type": "markdown",
19+
"metadata": {},
20+
"source": [
21+
"Firstly, let's import all the prerequisites:"
22+
]
23+
},
324
{
425
"cell_type": "code",
526
"execution_count": 1,
@@ -8,7 +29,21 @@
829
"source": [
930
"import torch\n",
1031
"import torchvision\n",
11-
"import torchvision.transforms as transforms"
32+
"import torchvision.transforms as transforms\n",
33+
"import torch.nn as nn\n",
34+
"import torch.nn.functional as F\n",
35+
"import torch.optim as optim\n",
36+
"import matplotlib.pyplot as plt\n",
37+
"import numpy as np\n",
38+
"import joey as ml\n",
39+
"from joey.activation import ReLU"
40+
]
41+
},
42+
{
43+
"cell_type": "markdown",
44+
"metadata": {},
45+
"source": [
46+
"We'll define `imshow()` to quickly have a look at the MNIST data we'll use for the forward pass."
1247
]
1348
},
1449
{
@@ -17,16 +52,22 @@
1752
"metadata": {},
1853
"outputs": [],
1954
"source": [
20-
"import matplotlib.pyplot as plt\n",
21-
"import numpy as np\n",
22-
"\n",
2355
"def imshow(img):\n",
2456
" img = img / 2 + 0.5\n",
2557
" npimg = img.numpy()\n",
2658
" plt.imshow(np.transpose(npimg, (1, 2, 0)))\n",
2759
" plt.show()"
2860
]
2961
},
62+
{
63+
"cell_type": "markdown",
64+
"metadata": {},
65+
"source": [
66+
"Before we start working with Joey, we have to download the images and convert them to NumPy arrays with `dtype=np.float64`. This is because Joey supports only NumPy arrays (rather than PyTorch tensors) and it currently works with double floating-point numbers.\n",
67+
"\n",
68+
"In our case, a batch will consist of 4 elements."
69+
]
70+
},
3071
{
3172
"cell_type": "code",
3273
"execution_count": 3,
@@ -41,22 +82,22 @@
4182
"testloader = torch.utils.data.DataLoader(testset, batch_size=4, shuffle=False, num_workers=2)\n",
4283
"\n",
4384
"classes = ('0', '1', '2', '3', '4', '5', '6', '7', '8', '9')\n",
44-
"dataiter = iter(testloader)"
85+
"dataiter = iter(testloader)\n",
86+
"\n",
87+
"images, labels = dataiter.next()\n",
88+
"input_data = images.double().numpy()"
4589
]
4690
},
4791
{
48-
"cell_type": "code",
49-
"execution_count": 4,
92+
"cell_type": "markdown",
5093
"metadata": {},
51-
"outputs": [],
5294
"source": [
53-
"images, labels = dataiter.next()\n",
54-
"input_data = images.numpy()"
95+
"Let's have a look at what we've downloaded:"
5596
]
5697
},
5798
{
5899
"cell_type": "code",
59-
"execution_count": 5,
100+
"execution_count": 4,
60101
"metadata": {},
61102
"outputs": [
62103
{
@@ -76,16 +117,21 @@
76117
"imshow(torchvision.utils.make_grid(images))"
77118
]
78119
},
120+
{
121+
"cell_type": "markdown",
122+
"metadata": {},
123+
"source": [
124+
"Now, we'll define `forward_pass()`. It creates LeNet using the `Net` class in Joey along with appropriate layer classes (here: `Conv`, `MaxPooling`, `Flat` and `FullyConnected`). Afterwards, by accessing the `kernel` and `bias` properties of each relevant layer, it inserts the pretrained weights saved in `.npy` files inside `resources/`.\n",
125+
"\n",
126+
"Note that we have to disable a strict stride check in `layer4`. If we didn't do that, we would get an error saying the stride is incompatible with the provided kernel and input sizes."
127+
]
128+
},
79129
{
80130
"cell_type": "code",
81-
"execution_count": 6,
131+
"execution_count": 5,
82132
"metadata": {},
83133
"outputs": [],
84134
"source": [
85-
"import joey as ml\n",
86-
"import numpy as np\n",
87-
"from joey.activation import ReLU\n",
88-
"\n",
89135
"def forward_pass(input_data):\n",
90136
" parameters = get_parameters()\n",
91137
" \n",
@@ -122,22 +168,33 @@
122168
" # Flattening layer necessary between layer 4 and 5\n",
123169
" layer_flat = ml.Flat(input_size=(batch_size, 16, 6, 6))\n",
124170
" \n",
125-
" current_data = input_data\n",
126-
" current_data = layer1.execute(current_data, parameters[1], parameters[0])\n",
127-
" current_data = layer2.execute(current_data)\n",
128-
" current_data = layer3.execute(current_data, parameters[3], parameters[2])\n",
129-
" current_data = layer4.execute(current_data)\n",
171+
" layers = [layer1, layer2, layer3, layer4,\n",
172+
" layer_flat, layer5, layer6, layer7]\n",
173+
" \n",
174+
" net = ml.Net(layers)\n",
175+
" \n",
176+
" # Setting up the pretrained parameters\n",
177+
" layer1.kernel.data[:] = parameters[0]\n",
178+
" layer1.bias.data[:] = parameters[1]\n",
179+
" \n",
180+
" layer3.kernel.data[:] = parameters[2]\n",
181+
" layer3.bias.data[:] = parameters[3]\n",
130182
" \n",
131-
" current_data = layer_flat.execute(current_data)\n",
183+
" layer5.kernel.data[:] = parameters[4]\n",
184+
" layer5.bias.data[:] = parameters[5]\n",
132185
" \n",
133-
" current_data = layer5.execute(current_data, parameters[5], parameters[4])\n",
134-
" current_data = layer6.execute(current_data, parameters[7], parameters[6])\n",
135-
" current_data = layer7.execute(current_data, parameters[9], parameters[8])\n",
186+
" layer6.kernel.data[:] = parameters[6]\n",
187+
" layer6.bias.data[:] = parameters[7]\n",
136188
" \n",
137-
" return current_data\n",
189+
" layer7.kernel.data[:] = parameters[8]\n",
190+
" layer7.bias.data[:] = parameters[9]\n",
191+
" \n",
192+
" net.forward(input_data)\n",
193+
" \n",
194+
" return (layer1, layer2, layer3, layer4, layer5, layer6, layer7)\n",
138195
"\n",
139196
"def get_parameters():\n",
140-
" # The LeNet trained parameters are stored in the following files:\n",
197+
" # The LeNet pretrained parameters are stored in the following files:\n",
141198
" # 1.npy: layer 1 weights\n",
142199
" # 2.npy: layer 1 biases\n",
143200
" # 3.npy: layer 3 weights\n",
@@ -159,9 +216,16 @@
159216
" return parameters"
160217
]
161218
},
219+
{
220+
"cell_type": "markdown",
221+
"metadata": {},
222+
"source": [
223+
"At this point, we're ready to run the forward pass!"
224+
]
225+
},
162226
{
163227
"cell_type": "code",
164-
"execution_count": 7,
228+
"execution_count": 6,
165229
"metadata": {},
166230
"outputs": [
167231
{
@@ -170,24 +234,24 @@
170234
"text": [
171235
"/home/maksymilian/Desktop/UROP/devito/devito/types/grid.py:206: RuntimeWarning: divide by zero encountered in true_divide\n",
172236
" spacing = (np.array(self.extent) / (np.array(self.shape) - 1)).astype(self.dtype)\n",
173-
"Operator `Kernel` run in 0.01 s\n",
174-
"Operator `Kernel` run in 0.01 s\n",
175-
"Operator `Kernel` run in 0.01 s\n",
176-
"Operator `Kernel` run in 0.01 s\n",
177-
"Operator `Kernel` run in 0.01 s\n",
178-
"Operator `Kernel` run in 0.01 s\n",
179-
"Operator `Kernel` run in 0.01 s\n",
180237
"Operator `Kernel` run in 0.01 s\n"
181238
]
182239
}
183240
],
184241
"source": [
185-
"results = forward_pass(input_data)"
242+
"layer1, layer2, layer3, layer4, layer5, layer6, layer7 = forward_pass(input_data)"
243+
]
244+
},
245+
{
246+
"cell_type": "markdown",
247+
"metadata": {},
248+
"source": [
249+
"After the pass is finished, we can access its output by checking the `result` property of the last layer."
186250
]
187251
},
188252
{
189253
"cell_type": "code",
190-
"execution_count": 8,
254+
"execution_count": 7,
191255
"metadata": {},
192256
"outputs": [
193257
{
@@ -208,7 +272,49 @@
208272
}
209273
],
210274
"source": [
211-
"print(results)"
275+
"output = layer7.result.data\n",
276+
"print(output)"
277+
]
278+
},
279+
{
280+
"cell_type": "markdown",
281+
"metadata": {},
282+
"source": [
283+
"The results look promising: for each batch element (arranged in columns rather than rows), the highest number corresponds to the expected class, i.e. '7' has been recognised as 7, '2' has been recognised as 2, '1' has been recognised as 1 and '0' has been recognised as 0.\n",
284+
"\n",
285+
"For reference, we'll construct the same network with the same weights in PyTorch, run the pass there and compare the outputs."
286+
]
287+
},
288+
{
289+
"cell_type": "code",
290+
"execution_count": 8,
291+
"metadata": {},
292+
"outputs": [],
293+
"source": [
294+
"class Net(nn.Module):\n",
295+
" def __init__(self):\n",
296+
" super(Net, self).__init__()\n",
297+
" self.conv1 = nn.Conv2d(1, 6, 3)\n",
298+
" self.conv2 = nn.Conv2d(6, 16, 3)\n",
299+
" self.fc1 = nn.Linear(16 * 6 * 6, 120)\n",
300+
" self.fc2 = nn.Linear(120, 84)\n",
301+
" self.fc3 = nn.Linear(84, 10)\n",
302+
" \n",
303+
" def forward(self, x):\n",
304+
" x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))\n",
305+
" x = F.max_pool2d(F.relu(self.conv2(x)), 2)\n",
306+
" x = x.view(-1, self.num_flat_features(x))\n",
307+
" x = F.relu(self.fc1(x))\n",
308+
" x = F.relu(self.fc2(x))\n",
309+
" x = self.fc3(x)\n",
310+
" return x\n",
311+
" \n",
312+
" def num_flat_features(self, x):\n",
313+
" size = x.size()[1:]\n",
314+
" num_features = 1\n",
315+
" for s in size:\n",
316+
" num_features *= s\n",
317+
" return num_features"
212318
]
213319
},
214320
{
@@ -217,17 +323,29 @@
217323
"metadata": {},
218324
"outputs": [],
219325
"source": [
220-
"expected = np.array([[-1.2509323 , 2.4017086 , -2.9189475 , 11.402614 ],\n",
221-
" [-2.0739274 , 3.711194 , 10.299156 , -3.8691325 ],\n",
222-
" [ 1.7185768 , 11.983461 , 0.7847577 , -0.8381885 ],\n",
223-
" [ 2.7290256 , 1.5788821 , -2.2999125 , -2.1093633 ],\n",
224-
" [-3.447302 , -0.9786217 , 0.7426771 , -2.761261 ],\n",
225-
" [-2.2462513 , -6.905971 , -2.5677016 , 0.6907152 ],\n",
226-
" [-9.817931 , -1.3155346 , -2.7154455 , 1.1705266 ],\n",
227-
" [11.809888 , -2.7028327 , 0.54783845, 1.0049481 ],\n",
228-
" [-1.0047406 , -2.4807127 , -1.013465 , -1.2820265 ],\n",
229-
" [ 4.6835623 , -6.3834734 , -2.2608764 , -0.76408434]])\n",
230-
"error = abs(results - expected) / expected"
326+
"net = Net()\n",
327+
"net.double()\n",
328+
"\n",
329+
"with torch.no_grad():\n",
330+
" net.conv1.weight[:] = torch.from_numpy(layer1.kernel.data)\n",
331+
" net.conv1.bias[:] = torch.from_numpy(layer1.bias.data)\n",
332+
" net.conv2.weight[:] = torch.from_numpy(layer3.kernel.data)\n",
333+
" net.conv2.bias[:] = torch.from_numpy(layer3.bias.data)\n",
334+
" net.fc1.weight[:] = torch.from_numpy(layer5.kernel.data)\n",
335+
" net.fc1.bias[:] = torch.from_numpy(layer5.bias.data)\n",
336+
" net.fc2.weight[:] = torch.from_numpy(layer6.kernel.data)\n",
337+
" net.fc2.bias[:] = torch.from_numpy(layer6.bias.data)\n",
338+
" net.fc3.weight[:] = torch.from_numpy(layer7.kernel.data)\n",
339+
" net.fc3.bias[:] = torch.from_numpy(layer7.bias.data)\n",
340+
"\n",
341+
"pytorch_output = np.transpose(net(images.double()).detach().numpy())"
342+
]
343+
},
344+
{
345+
"cell_type": "markdown",
346+
"metadata": {},
347+
"source": [
348+
"After creating and running the network in PyTorch, we'll calculate a relative error matrix as shown below. The maximum value in that matrix will be obtained as well."
231349
]
232350
},
233351
{
@@ -239,23 +357,32 @@
239357
"name": "stdout",
240358
"output_type": "stream",
241359
"text": [
242-
"[[-3.63663429e-08 4.40914511e-07 -3.72550049e-08 3.25849137e-07]\n",
243-
" [-1.05593081e-07 4.90411682e-08 5.72853023e-08 -2.79713692e-07]\n",
244-
" [ 1.45904743e-07 2.85106639e-08 4.09210438e-08 -5.01416183e-07]\n",
245-
" [ 1.20755307e-07 4.73338244e-07 -2.46663913e-07 -1.33781978e-07]\n",
246-
" [-1.13912282e-07 -1.40684427e-07 1.58951535e-07 -3.16022361e-07]\n",
247-
" [-2.53501737e-08 -8.97095633e-08 -2.15097806e-07 6.65195837e-07]\n",
248-
" [-8.21189975e-08 -2.44784091e-07 -4.71435314e-07 3.77461859e-07]\n",
249-
" [ 3.91327608e-08 -4.14888517e-08 3.43611389e-07 5.78272754e-08]\n",
250-
" [-7.21504199e-07 -1.64031238e-07 -1.47184007e-07 -3.07741901e-07]\n",
251-
" [ 1.71215680e-07 -8.06319813e-08 -3.03396411e-07 -1.81879287e-06]]\n",
252-
"6.651958371252912e-07\n"
360+
"[[1.77503288e-16 3.69811230e-16 3.04280379e-16 0.00000000e+00]\n",
361+
" [8.56518243e-16 5.98310452e-16 1.72475952e-16 0.00000000e+00]\n",
362+
" [1.42122890e-15 1.48234044e-16 4.24420039e-16 2.11928192e-15]\n",
363+
" [4.88184424e-16 9.84437976e-16 5.79268976e-16 2.10532377e-16]\n",
364+
" [1.28822268e-16 4.53790543e-16 4.48468063e-16 3.21656917e-16]\n",
365+
" [3.95404734e-16 2.57220454e-16 5.18855985e-16 1.12514772e-15]\n",
366+
" [1.80929841e-16 1.85665208e-15 1.63541857e-16 1.89696406e-16]\n",
367+
" [1.50412669e-16 4.92915335e-16 3.03982673e-15 4.41902657e-16]\n",
368+
" [2.20996787e-16 1.07410088e-15 8.76378119e-16 1.73198086e-16]\n",
369+
" [3.79274668e-16 4.17411542e-16 3.92847079e-16 7.26506870e-16]]\n",
370+
"3.0398267312380578e-15\n"
253371
]
254372
}
255373
],
256374
"source": [
375+
"error = abs(output - pytorch_output) / abs(pytorch_output)\n",
376+
"\n",
257377
"print(error)\n",
258-
"print(np.amax(error))"
378+
"print(np.nanmax(error))"
379+
]
380+
},
381+
{
382+
"cell_type": "markdown",
383+
"metadata": {},
384+
"source": [
385+
"As we can see, the maximum error is low enough (given the floating-point calculation accuracy) for the Joey results to be considered numerically correct."
259386
]
260387
}
261388
],

0 commit comments

Comments
 (0)