Skip to content

Commit 20ef6e9

Browse files
committed
Notebooks for cnn
A very tiny one without pooling layers. A less tiny with pooling. Right now I need to cheat on the bounds of the input layer.
1 parent d4406d2 commit 20ef6e9

File tree

3 files changed

+352
-22
lines changed

3 files changed

+352
-22
lines changed
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"metadata": {},
6+
"source": [
7+
"# Adversarial example using Keras\n",
8+
"\n",
9+
"In this example, we redo the [adversarial example](https://gurobi-optimization-ml2gurobi.readthedocs-hosted.com/en/latest/examples/adversarial_mnist.html) of the documentation but use tensorflow Keras for training the neural network.\n",
10+
"\n",
11+
"We don't detail the optimization model here. Please refer to the example in the documentation.\n",
12+
"Note that many of the differences between this notebook and the one from the documentation come from\n",
13+
"using tensorflow instead of numpy for manipulating data.\n",
14+
"\n",
15+
"This example requires the additional packages:\n",
16+
" - [tensorflow](https://www.tensorflow.org/)\n",
17+
" - [matplotlib](https://matplotlib.org/)"
18+
]
19+
},
20+
{
21+
"cell_type": "markdown",
22+
"metadata": {},
23+
"source": [
24+
"## Import the necessary packages and load data\n",
25+
"\n",
26+
"We import all the package we need for this example.\n",
27+
"The MNIST dataset is available from Keras."
28+
]
29+
},
30+
{
31+
"cell_type": "code",
32+
"execution_count": null,
33+
"metadata": {},
34+
"outputs": [],
35+
"source": [
36+
"from matplotlib import pyplot as plt\n",
37+
"import tensorflow as tf\n",
38+
"from tensorflow import keras\n",
39+
"import numpy as np\n",
40+
"import gurobipy as gp"
41+
]
42+
},
43+
{
44+
"cell_type": "code",
45+
"execution_count": null,
46+
"metadata": {},
47+
"outputs": [],
48+
"source": [
49+
"%load_ext autoreload\n",
50+
"%autoreload 2"
51+
]
52+
},
53+
{
54+
"cell_type": "code",
55+
"execution_count": null,
56+
"metadata": {},
57+
"outputs": [],
58+
"source": [
59+
"from gurobi_ml import add_predictor_constr"
60+
]
61+
},
62+
{
63+
"cell_type": "code",
64+
"execution_count": null,
65+
"metadata": {},
66+
"outputs": [],
67+
"source": [
68+
"(x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()"
69+
]
70+
},
71+
{
72+
"cell_type": "markdown",
73+
"metadata": {},
74+
"source": [
75+
"We reshape and scale `x_train` and `x_test`."
76+
]
77+
},
78+
{
79+
"cell_type": "code",
80+
"execution_count": null,
81+
"metadata": {},
82+
"outputs": [],
83+
"source": [
84+
"x_train = tf.cast(x_train, tf.float32) / 255.0\n",
85+
"x_test = tf.cast(x_test, tf.float32) / 255.0\n",
86+
"\n",
87+
"x_train = np.expand_dims(x_train, -1)\n",
88+
"x_test = np.expand_dims(x_test, -1)"
89+
]
90+
},
91+
{
92+
"cell_type": "markdown",
93+
"metadata": {},
94+
"source": [
95+
"## Construct and train the neural network\n",
96+
"\n",
97+
"We construct a sequential neural network with 2 hidden layers of 50 neurons and ReLU activation.\n",
98+
"\n",
99+
"We use the usual Keras functions to compile and fit the network."
100+
]
101+
},
102+
{
103+
"cell_type": "code",
104+
"execution_count": null,
105+
"metadata": {},
106+
"outputs": [],
107+
"source": [
108+
"nn = keras.Sequential(\n",
109+
" [\n",
110+
" keras.Input(shape=(28, 28, 1)),\n",
111+
" keras.layers.Conv2D(2, kernel_size=(4, 4), strides=(2, 2), activation=\"relu\"),\n",
112+
" keras.layers.Conv2D(2, kernel_size=(4, 4), strides=(2, 2), activation=\"relu\"),\n",
113+
" # keras.layers.MaxPooling2D(pool_size=(2, 2)),\n",
114+
" keras.layers.Flatten(),\n",
115+
" keras.layers.Dense(10, activation=\"relu\"),\n",
116+
" keras.layers.Dense(10),\n",
117+
" ]\n",
118+
")"
119+
]
120+
},
121+
{
122+
"cell_type": "code",
123+
"execution_count": null,
124+
"metadata": {},
125+
"outputs": [],
126+
"source": [
127+
"nn.compile(\n",
128+
" optimizer=tf.keras.optimizers.Adam(0.001),\n",
129+
" loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),\n",
130+
" metrics=[tf.keras.metrics.SparseCategoricalAccuracy()],\n",
131+
")"
132+
]
133+
},
134+
{
135+
"cell_type": "code",
136+
"execution_count": null,
137+
"metadata": {},
138+
"outputs": [],
139+
"source": [
140+
"nn.fit(\n",
141+
" x_train,\n",
142+
" y_train,\n",
143+
" batch_size=128,\n",
144+
" epochs=10,\n",
145+
" validation_data=(x_test, y_test),\n",
146+
")"
147+
]
148+
},
149+
{
150+
"cell_type": "markdown",
151+
"metadata": {},
152+
"source": [
153+
"Print summary of the trained network"
154+
]
155+
},
156+
{
157+
"cell_type": "code",
158+
"execution_count": null,
159+
"metadata": {},
160+
"outputs": [],
161+
"source": [
162+
"nn.summary()"
163+
]
164+
},
165+
{
166+
"cell_type": "markdown",
167+
"metadata": {},
168+
"source": [
169+
"## Build optimization model\n",
170+
"\n",
171+
"Now we turn to building the optimization model.\n",
172+
"\n",
173+
"We choose a training example and the steps are similar to the one with scikit-learn in the documentation.\n",
174+
"The only differences come from the data being stored in tensors instead of arrays."
175+
]
176+
},
177+
{
178+
"cell_type": "code",
179+
"execution_count": null,
180+
"metadata": {},
181+
"outputs": [],
182+
"source": [
183+
"example = x_train[18, :]\n",
184+
"plt.imshow(example, cmap=\"gray\")"
185+
]
186+
},
187+
{
188+
"cell_type": "code",
189+
"execution_count": null,
190+
"metadata": {},
191+
"outputs": [],
192+
"source": [
193+
"label = tf.math.argmax(nn.predict(tf.reshape(example, (1, 28, 28, 1)))[0])\n",
194+
"print(f\"Example is classified as {label}\")"
195+
]
196+
},
197+
{
198+
"cell_type": "code",
199+
"execution_count": null,
200+
"metadata": {},
201+
"outputs": [],
202+
"source": [
203+
"ex_prob = nn.predict(tf.reshape(example, (1, 28, 28, 1)))\n",
204+
"sorted_labels = tf.argsort(ex_prob)[0]\n",
205+
"right_label = sorted_labels[-1]\n",
206+
"wrong_label = sorted_labels[-2]"
207+
]
208+
},
209+
{
210+
"cell_type": "code",
211+
"execution_count": null,
212+
"metadata": {},
213+
"outputs": [],
214+
"source": [
215+
"m = gp.Model()\n",
216+
"delta = 5\n",
217+
"\n",
218+
"x = m.addMVar((1, 28, 28, 1), lb=0.0, ub=1.0, name=\"x\")\n",
219+
"y = m.addMVar(ex_prob.shape, lb=-gp.GRB.INFINITY, name=\"y\")\n",
220+
"\n",
221+
"abs_diff = m.addMVar(example.shape, lb=0, ub=1, name=\"abs_diff\")\n",
222+
"\n",
223+
"m.setObjective(y[0, wrong_label] - y[0, right_label], gp.GRB.MAXIMIZE)\n",
224+
"\n",
225+
"# Bound on the distance to example in norm-1\n",
226+
"m.addConstr(abs_diff >= x - example)\n",
227+
"m.addConstr(abs_diff >= -x + example)\n",
228+
"m.addConstr(abs_diff.sum() <= delta)"
229+
]
230+
},
231+
{
232+
"cell_type": "code",
233+
"execution_count": null,
234+
"metadata": {},
235+
"outputs": [],
236+
"source": [
237+
"pred_constr = add_predictor_constr(m, nn, x, y)\n",
238+
"\n",
239+
"pred_constr.print_stats()"
240+
]
241+
},
242+
{
243+
"cell_type": "code",
244+
"execution_count": null,
245+
"metadata": {},
246+
"outputs": [],
247+
"source": [
248+
"m.Params.BestBdStop = 0.0\n",
249+
"m.Params.BestObjStop = 0.0\n",
250+
"m.Params.OBBT = 2\n",
251+
"m.Params.Presolve = 1\n",
252+
"m.optimize()"
253+
]
254+
},
255+
{
256+
"cell_type": "markdown",
257+
"metadata": {},
258+
"source": [
259+
"Finally, display the adversarial example if one was found."
260+
]
261+
},
262+
{
263+
"cell_type": "code",
264+
"execution_count": null,
265+
"metadata": {},
266+
"outputs": [],
267+
"source": [
268+
"m.write(\"toto.lp\")"
269+
]
270+
},
271+
{
272+
"cell_type": "code",
273+
"execution_count": null,
274+
"metadata": {},
275+
"outputs": [],
276+
"source": [
277+
"if m.ObjVal > 0.0:\n",
278+
" plt.imshow(x.X.reshape((28, 28)), cmap=\"gray\")\n",
279+
" label = tf.math.argmax(nn.predict(x.X), axis=1)\n",
280+
" print(f\"Solution is classified as {label}\")\n",
281+
"else:\n",
282+
" print(\"No counter example exists in neighborhood.\")"
283+
]
284+
},
285+
{
286+
"cell_type": "markdown",
287+
"metadata": {},
288+
"source": [
289+
"copyright © 2023 Gurobi Optimization, LLC"
290+
]
291+
}
292+
],
293+
"metadata": {
294+
"kernelspec": {
295+
"display_name": "gurobi-machinelearning",
296+
"language": "python",
297+
"name": "python3"
298+
},
299+
"language_info": {
300+
"codemirror_mode": {
301+
"name": "ipython",
302+
"version": 3
303+
},
304+
"file_extension": ".py",
305+
"mimetype": "text/x-python",
306+
"name": "python",
307+
"nbconvert_exporter": "python",
308+
"pygments_lexer": "ipython3",
309+
"version": "3.13.7"
310+
},
311+
"license": {
312+
"full_text": "# Copyright © 2023 Gurobi Optimization, LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# =============================================================================="
313+
}
314+
},
315+
"nbformat": 4,
316+
"nbformat_minor": 4
317+
}

notebooks/adversarial/adversarial_cnn.ipynb

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@
108108
"nn = keras.Sequential(\n",
109109
" [\n",
110110
" keras.Input(shape=(28, 28, 1)),\n",
111-
" keras.layers.Conv2D(16, kernel_size=(2, 2), activation=\"relu\"),\n",
111+
" keras.layers.Conv2D(2, kernel_size=(2, 2), activation=\"relu\"),\n",
112112
" keras.layers.MaxPooling2D(pool_size=(2, 2)),\n",
113-
" keras.layers.Conv2D(32, kernel_size=(2, 2), activation=\"relu\"),\n",
113+
" keras.layers.Conv2D(4, kernel_size=(2, 2), activation=\"relu\"),\n",
114114
" keras.layers.MaxPooling2D(pool_size=(2, 2)),\n",
115115
" keras.layers.Flatten(),\n",
116116
" keras.layers.Dense(50, activation=\"relu\"),\n",
@@ -142,7 +142,7 @@
142142
" x_train,\n",
143143
" y_train,\n",
144144
" batch_size=128,\n",
145-
" epochs=1,\n",
145+
" epochs=10,\n",
146146
" validation_data=(x_test, y_test),\n",
147147
")"
148148
]
@@ -240,6 +240,21 @@
240240
"pred_constr.print_stats()"
241241
]
242242
},
243+
{
244+
"cell_type": "code",
245+
"execution_count": null,
246+
"metadata": {},
247+
"outputs": [],
248+
"source": [
249+
"# We really don't like maxpooling layers (because fomrulatting the max...)\n",
250+
"# Put bound on inputs to maxpooling layers (0.0 is valid, 10.0 looks reasonable)\n",
251+
"\n",
252+
"pred_constr.layers[1].input.lb = 0.0\n",
253+
"pred_constr.layers[1].input.ub = 10.0\n",
254+
"pred_constr.layers[3].input.lb = 0.0\n",
255+
"pred_constr.layers[3].input.ub = 10.0"
256+
]
257+
},
243258
{
244259
"cell_type": "code",
245260
"execution_count": null,
@@ -269,15 +284,6 @@
269284
"m.write(\"toto.lp\")"
270285
]
271286
},
272-
{
273-
"cell_type": "code",
274-
"execution_count": null,
275-
"metadata": {},
276-
"outputs": [],
277-
"source": [
278-
"m.getRow(pred_constr.layers[0].constrs[1])"
279-
]
280-
},
281287
{
282288
"cell_type": "code",
283289
"execution_count": null,
@@ -286,7 +292,7 @@
286292
"source": [
287293
"if m.ObjVal > 0.0:\n",
288294
" plt.imshow(x.X.reshape((28, 28)), cmap=\"gray\")\n",
289-
" label = tf.math.argmax(nn.predict(tf.reshape(x.X, (1, -1))), axis=1)\n",
295+
" label = tf.math.argmax(nn.predict(x.X), axis=1)\n",
290296
" print(f\"Solution is classified as {label}\")\n",
291297
"else:\n",
292298
" print(\"No counter example exists in neighborhood.\")"
@@ -316,7 +322,7 @@
316322
"name": "python",
317323
"nbconvert_exporter": "python",
318324
"pygments_lexer": "ipython3",
319-
"version": "3.12.9"
325+
"version": "3.13.7"
320326
},
321327
"license": {
322328
"full_text": "# Copyright © 2023 Gurobi Optimization, LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n# http://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n# =============================================================================="

0 commit comments

Comments
 (0)