{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nimport tensorflow as tf\nfrom tensorflow.keras import layers, callbacks\nfrom tensorflow.keras.layers.experimental import preprocessing\nfrom matplotlib import pyplot as plt\nfrom tqdm import tqdm\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-07-10T15:32:13.331277Z","iopub.execute_input":"2022-07-10T15:32:13.331894Z","iopub.status.idle":"2022-07-10T15:32:13.341221Z","shell.execute_reply.started":"2022-07-10T15:32:13.331857Z","shell.execute_reply":"2022-07-10T15:32:13.339978Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Read data","metadata":{}},{"cell_type":"code","source":"train = pd.read_csv(\"../input/digit-recognizer/train.csv\")\ntest = pd.read_csv(\"../input/digit-recognizer/test.csv\")\nsubmission = pd.read_csv(\"../input/digit-recognizer/sample_submission.csv\")","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:14.242381Z","iopub.execute_input":"2022-07-10T15:32:14.242958Z","iopub.status.idle":"2022-07-10T15:32:17.711490Z","shell.execute_reply.started":"2022-07-10T15:32:14.242921Z","shell.execute_reply":"2022-07-10T15:32:17.710445Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.shape","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.714753Z","iopub.execute_input":"2022-07-10T15:32:17.715403Z","iopub.status.idle":"2022-07-10T15:32:17.722218Z","shell.execute_reply.started":"2022-07-10T15:32:17.715361Z","shell.execute_reply":"2022-07-10T15:32:17.721208Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train.head()","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.723571Z","iopub.execute_input":"2022-07-10T15:32:17.724599Z","iopub.status.idle":"2022-07-10T15:32:17.742613Z","shell.execute_reply.started":"2022-07-10T15:32:17.724542Z","shell.execute_reply":"2022-07-10T15:32:17.741589Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = (train.iloc[:,1:].values).astype('float32') # all pixel values\ny_train = train.iloc[:,0].values.astype('int32') # only labels i.e targets digits\nX_test = test.values.astype('float32')","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.745719Z","iopub.execute_input":"2022-07-10T15:32:17.746537Z","iopub.status.idle":"2022-07-10T15:32:17.846886Z","shell.execute_reply.started":"2022-07-10T15:32:17.746499Z","shell.execute_reply":"2022-07-10T15:32:17.845806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = X_train.reshape(X_train.shape[0], 28, 28)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.848604Z","iopub.execute_input":"2022-07-10T15:32:17.848993Z","iopub.status.idle":"2022-07-10T15:32:17.853839Z","shell.execute_reply.started":"2022-07-10T15:32:17.848956Z","shell.execute_reply":"2022-07-10T15:32:17.852856Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_test = X_test.reshape(X_test.shape[0], 28, 28)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.855575Z","iopub.execute_input":"2022-07-10T15:32:17.856329Z","iopub.status.idle":"2022-07-10T15:32:17.863539Z","shell.execute_reply.started":"2022-07-10T15:32:17.856291Z","shell.execute_reply":"2022-07-10T15:32:17.862443Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.imshow(X_train[1])\nplt.title(y_train[1]);","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:17.865261Z","iopub.execute_input":"2022-07-10T15:32:17.865899Z","iopub.status.idle":"2022-07-10T15:32:18.040874Z","shell.execute_reply.started":"2022-07-10T15:32:17.865863Z","shell.execute_reply":"2022-07-10T15:32:18.039988Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Tensorflow Keras baseline model","metadata":{}},{"cell_type":"code","source":"def train_model(model, train_features, train_label, epochs,early_stopping,\n                batch_size=None, validation_split=0.1):\n\n    history = model.fit(x=train_features, y=train_label, batch_size=batch_size,\n                      epochs=epochs, shuffle=True, \n                      callbacks=[early_stopping],\n                      validation_split=validation_split)\n    epochs = history.epoch\n    hist = pd.DataFrame(history.history)\n    return epochs, hist    ","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:19.099650Z","iopub.execute_input":"2022-07-10T15:32:19.100252Z","iopub.status.idle":"2022-07-10T15:32:19.110451Z","shell.execute_reply.started":"2022-07-10T15:32:19.100206Z","shell.execute_reply":"2022-07-10T15:32:19.109343Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def create_nn_model(my_learning_rate):\n    model = tf.keras.models.Sequential()\n    model.add(tf.keras.layers.Flatten(input_shape=(28, 28)))  \n    model.add(tf.keras.layers.Dense(units=256, activation='relu'))\n    model.add(tf.keras.layers.Dropout(rate=0.2))\n    model.add(layers.BatchNormalization())\n    model.add(tf.keras.layers.Dense(units=128, activation='relu'))\n    model.add(tf.keras.layers.Dropout(rate=0.2))\n    model.add(layers.BatchNormalization())\n    model.add(tf.keras.layers.Dense(units=10, activation='softmax'))                          \n    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=my_learning_rate),\n                loss=\"sparse_categorical_crossentropy\",\n                metrics=['accuracy'])\n    return model    ","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:19.267093Z","iopub.execute_input":"2022-07-10T15:32:19.267373Z","iopub.status.idle":"2022-07-10T15:32:19.275448Z","shell.execute_reply.started":"2022-07-10T15:32:19.267348Z","shell.execute_reply":"2022-07-10T15:32:19.274471Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_curve(epochs, hist, list_of_metrics):\n    plt.figure()\n    plt.xlabel(\"Epoch\")\n    plt.ylabel(\"Value\")\n    for m in list_of_metrics:\n        x = hist[m]\n        plt.plot(epochs[1:], x[1:], label=m)\n    plt.legend()\n    print(\"Loaded the plot_curve function.\")","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:19.422130Z","iopub.execute_input":"2022-07-10T15:32:19.422440Z","iopub.status.idle":"2022-07-10T15:32:19.428360Z","shell.execute_reply.started":"2022-07-10T15:32:19.422416Z","shell.execute_reply":"2022-07-10T15:32:19.427329Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learning_rate = 0.001\nepochs = 100\nbatch_size = 4000\nvalidation_split = 0.2\n\nreduce_lr = callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.2,\n                              patience=5, min_lr=0.001)\n\nearly_stopping = callbacks.EarlyStopping(\n        min_delta=0.001, # minimium amount of change to count as an improvement\n        patience=20, # how many epochs to wait before stopping\n        restore_best_weights=True,\n    )\n\n# Establish the model's topography.\ntf_model = create_nn_model(learning_rate)\n\n# Train the model on the normalized training set.\nepochs, tf_hist = train_model(tf_model, X_train, y_train, \n                           epochs, early_stopping, batch_size, validation_split)","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tf_model.summary()","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a graph of the metric vs. epochs.\nlist_of_metrics_to_plot = ['accuracy', 'val_accuracy']\nplot_curve(epochs, tf_hist, list_of_metrics_to_plot)\nprint(\" Best validation accuracy : \", max(tf_hist['val_accuracy']))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### From the below figure, we can see that 0 got miscategorized as 9","metadata":{}},{"cell_type":"code","source":"num_rows = 1\nnum_cols = 5\nnum_images = num_rows*num_cols\nfor i in range(num_images):\n    plt.subplot(num_rows, num_cols, i+1)\n    plt.title(np.argmax(tf_model.predict(X_test)[i], axis=0)) #Predicted\n    plt.imshow(X_test[i]) #Viz","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_results = tf_model.predict(X_test)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission['Label'] = [np.argmax(test_results[i], axis=0) for i in tqdm(range(len(X_test)))]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv('submission_tf_baseline.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## CNN model","metadata":{}},{"cell_type":"markdown","source":"As input, a CNN takes tensors of shape (image_height, image_width, color_channels), so added gray channel to the input","metadata":{}},{"cell_type":"code","source":"def create_cnn_tf_model(my_learning_rate):\n    \n    model = tf.keras.Sequential()\n    model.add(layers.Conv2D(32, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))\n    model.add(layers.MaxPooling2D((2, 2)))\n    model.add(layers.Dropout(0.25))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Conv2D(64, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))\n    model.add(layers.MaxPooling2D((2, 2)))\n    model.add(layers.Dropout(0.25))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Conv2D(128, (3, 3), activation='relu', padding='same', kernel_initializer='he_uniform', input_shape=(28, 28, 1)))\n    model.add(layers.MaxPooling2D((2, 2)))\n    model.add(layers.Dropout(0.25))\n    model.add(layers.BatchNormalization())\n    model.add(layers.Flatten())\n    model.add(layers.Dense(64, activation='relu', kernel_initializer='he_uniform'))\n    model.add(layers.Dense(10, activation='softmax'))\n    \n    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=my_learning_rate),\n                loss=\"sparse_categorical_crossentropy\",\n                metrics=['accuracy'])\n    return model    ","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = X_train.reshape(-1,28,28,1) #X_train = np.expand_dims(X_train, -1)\nX_test = X_test.reshape(-1,28,28,1)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:30.263404Z","iopub.execute_input":"2022-07-10T15:32:30.263774Z","iopub.status.idle":"2022-07-10T15:32:30.269579Z","shell.execute_reply.started":"2022-07-10T15:32:30.263744Z","shell.execute_reply":"2022-07-10T15:32:30.268547Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learning_rate = 0.01\nepochs = 100\nbatch_size = 4000\nvalidation_split = 0.2\n\nearly_stopping = callbacks.EarlyStopping(\n        min_delta=0.001, # minimium amount of change to count as an improvement\n        patience=20, # how many epochs to wait before stopping\n        restore_best_weights=True,\n    )\n\n# Establish the model's topography.\ncnn_tf_model = create_cnn_tf_model(learning_rate)\n\n# Train the model on the normalized training set.\ncnn_epochs, cnn_tf_hist = train_model(cnn_tf_model, X_train, y_train, \n                           epochs, early_stopping, batch_size, validation_split)","metadata":{"_kg_hide-output":true,"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a graph of the metric vs. epochs.\nlist_of_metrics_to_plot = ['accuracy', 'val_accuracy']\nplot_curve(cnn_epochs, cnn_tf_hist, list_of_metrics_to_plot)\nprint(\" Best validation accuracy : \", max(cnn_tf_hist['val_accuracy']))","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### From the below figure we can see that 0 now got correctly categorized as 0","metadata":{}},{"cell_type":"code","source":"num_rows = 1\nnum_cols = 5\nnum_images = num_rows*num_cols\nfor i in range(num_images):\n    plt.subplot(num_rows, num_cols, i+1)\n    plt.title(np.argmax(cnn_tf_model.predict(X_test)[i], axis=0)) #Predicted\n    plt.imshow(X_test[i]) #Viz","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_results = cnn_tf_model.predict(X_test)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission['Label'] = [np.argmax(test_results[i], axis=0) for i in tqdm(range(len(X_test)))]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv('submission_tf_cnn.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Why did CNN perform better than the MLP? \n\n### MLP best validation accuracy was 0.975 vs CNN with 0.993","metadata":{}},{"cell_type":"markdown","source":"MLP is now deemed insufficient for modern advanced computer vision tasks. MLP has the characteristic of fully connected layers, where each perceptron is connected with every other perceptron. The disadvantage is that the number of total parameters can grow to very high (number of perceptron in layer 1 multiplied by # of p in layer 2 multiplied by # of p in layer 3…). This is inefficient because there is redundancy in such high dimensions. Another disadvantage is that it disregards spatial information. It takes flattened vectors as inputs. This is why we saw 0 being miscategorized as 9.","metadata":{}},{"cell_type":"markdown","source":"## CNN with transfer learning from Resnet50\n\nAcknowledgement : https://www.kaggle.com/code/namansood/resnet50-training-on-mnist-transfer-learning","metadata":{}},{"cell_type":"markdown","source":"### What is Resnet50\n\nResNet-50 is a convolutional neural network that is 50 layers deep. You can load a pretrained version of the network trained on more than a million images from the ImageNet database [1]. The pretrained network can classify images into 1000 object categories, such as keyboard, mouse, pencil, and many animals. As a result, the network has learned rich feature representations for a wide range of images. The network has an image input size of 224-by-224","metadata":{}},{"cell_type":"markdown","source":"### Channel stacking\nWe will proceed to stack the data along the last dimension. This will result in an artificially created 3-channeled image (RGB). As most of the pre-trained models work on RGB images, we need to convert ours in the same format as well","metadata":{}},{"cell_type":"code","source":"X_train = (train.iloc[:,1:].values).astype('float32') # all pixel values\ny_train = train.iloc[:,0].values.astype('int32') # only labels i.e targets digits\nX_test = test.values.astype('float32')","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:35.380069Z","iopub.execute_input":"2022-07-10T15:32:35.381093Z","iopub.status.idle":"2022-07-10T15:32:35.483912Z","shell.execute_reply.started":"2022-07-10T15:32:35.381043Z","shell.execute_reply":"2022-07-10T15:32:35.482884Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = X_train.reshape(X_train.shape[0], 28, 28)\nX_test = X_test.reshape(X_test.shape[0], 28, 28)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:35.754171Z","iopub.execute_input":"2022-07-10T15:32:35.754767Z","iopub.status.idle":"2022-07-10T15:32:35.759931Z","shell.execute_reply.started":"2022-07-10T15:32:35.754732Z","shell.execute_reply":"2022-07-10T15:32:35.758976Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train.shape","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:37.152461Z","iopub.execute_input":"2022-07-10T15:32:37.153263Z","iopub.status.idle":"2022-07-10T15:32:37.160051Z","shell.execute_reply.started":"2022-07-10T15:32:37.153222Z","shell.execute_reply":"2022-07-10T15:32:37.158742Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train = np.stack((X_train,)*3, axis=-1)\nX_test = np.stack((X_test,)*3, axis=-1)","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:37.375698Z","iopub.execute_input":"2022-07-10T15:32:37.376332Z","iopub.status.idle":"2022-07-10T15:32:37.704151Z","shell.execute_reply.started":"2022-07-10T15:32:37.376293Z","shell.execute_reply":"2022-07-10T15:32:37.703061Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"X_train.shape","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.applications.resnet50 import ResNet50\n\ndef create_cnn_resnet50_tf_model(my_learning_rate):\n    model = tf.keras.Sequential()\n    model.add(ResNet50(include_top = False, pooling = 'max', weights = 'imagenet'))\n    model.add(layers.Dense(10, activation = 'softmax'))\n    model.layers[0].trainable = False\n    \n    model.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=my_learning_rate),\n                loss=\"sparse_categorical_crossentropy\",\n                metrics=['accuracy'])\n    return model ","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:32:41.217553Z","iopub.execute_input":"2022-07-10T15:32:41.218188Z","iopub.status.idle":"2022-07-10T15:32:41.234399Z","shell.execute_reply.started":"2022-07-10T15:32:41.218137Z","shell.execute_reply":"2022-07-10T15:32:41.233235Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learning_rate = 0.01\nepochs = 100\nbatch_size = 4000\nvalidation_split = 0.2\n\nearly_stopping = callbacks.EarlyStopping(\n        min_delta=0.001, # minimium amount of change to count as an improvement\n        patience=20, # how many epochs to wait before stopping\n        restore_best_weights=True,\n    )\n\n# Establish the model's topography.\ncnn_resnet50_tf_model = create_cnn_resnet50_tf_model(learning_rate)\n\n# Train the model on the normalized training set.\ncnn_resnet50_epochs, cnn_resnet50_tf_hist = train_model(cnn_resnet50_tf_model, X_train, y_train, \n                           epochs, early_stopping, batch_size, validation_split)","metadata":{"_kg_hide-output":true,"execution":{"iopub.status.busy":"2022-07-10T15:32:41.795598Z","iopub.execute_input":"2022-07-10T15:32:41.796595Z","iopub.status.idle":"2022-07-10T15:37:09.724437Z","shell.execute_reply.started":"2022-07-10T15:32:41.796559Z","shell.execute_reply":"2022-07-10T15:37:09.723302Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Plot a graph of the metric vs. epochs.\nlist_of_metrics_to_plot = ['accuracy', 'val_accuracy']\nplot_curve(cnn_resnet50_epochs, cnn_resnet50_tf_hist, list_of_metrics_to_plot)\nprint(\" Best validation accuracy : \", max(cnn_resnet50_tf_hist['val_accuracy']))","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:37:09.726437Z","iopub.execute_input":"2022-07-10T15:37:09.727118Z","iopub.status.idle":"2022-07-10T15:37:10.113050Z","shell.execute_reply.started":"2022-07-10T15:37:09.727076Z","shell.execute_reply":"2022-07-10T15:37:10.112075Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### From the below figure we can see that 0 now got miscategorized as 2 instead","metadata":{}},{"cell_type":"code","source":"num_rows = 1\nnum_cols = 5\nnum_images = num_rows*num_cols\nfor i in range(num_images):\n    plt.subplot(num_rows, num_cols, i+1)\n    plt.title(np.argmax(cnn_resnet50_tf_model.predict(X_test)[i], axis=0)) #Predicted\n    plt.imshow(X_test[i]) #Viz","metadata":{"execution":{"iopub.status.busy":"2022-07-10T15:37:10.114411Z","iopub.execute_input":"2022-07-10T15:37:10.114965Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_results = cnn_resnet50_tf_model.predict(X_test)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission['Label'] = [np.argmax(test_results[i], axis=0) for i in tqdm(range(len(X_test)))]","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"submission.to_csv('submission_tf_resnet50.csv', index=False)","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Best model so far seems to be the CNN model\n\nNext steps to try : data augmentation and other transfer learning architectures with the input shape changed\n\nhttps://www.kaggle.com/code/saumandas/intro-to-transfer-learning-with-mnist/notebook\n\nhttps://www.kaggle.com/code/donatastamosauskas/using-resnet-for-mnist/notebook\n\nhttps://www.kaggle.com/code/aymanlafaz/mnist-resnet50-transfer-learning/notebook","metadata":{}}]}