{"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":"import os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))","metadata":{"execution":{"iopub.status.busy":"2023-05-19T23:06:51.589588Z","iopub.execute_input":"2023-05-19T23:06:51.589908Z","iopub.status.idle":"2023-05-19T23:06:51.621542Z","shell.execute_reply.started":"2023-05-19T23:06:51.58987Z","shell.execute_reply":"2023-05-19T23:06:51.620496Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Imports","metadata":{"id":"MOIkLWmzKbMD"}},{"cell_type":"code","source":"import torch\nfrom torch.utils.tensorboard import SummaryWriter\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.autograd import Variable\nfrom torchvision import datasets, transforms, models\nimport csv\n\nimport pandas as pd\nimport seaborn as sns\nimport numpy as np","metadata":{"id":"uw-kIsCPKahH","execution":{"iopub.status.busy":"2023-05-19T23:06:51.623579Z","iopub.execute_input":"2023-05-19T23:06:51.62399Z","iopub.status.idle":"2023-05-19T23:07:05.21331Z","shell.execute_reply.started":"2023-05-19T23:06:51.623959Z","shell.execute_reply":"2023-05-19T23:07:05.212246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Part 1 : First script using Numpy\n\n","metadata":{"id":"E5TD2Uof5qej"}},{"cell_type":"markdown","source":"This first script describes a Convolutional Neural Network taking randomly generated data $x$ as inputs and updating weights thanks to module *torch.randn()*. We also generated random outputs, the labels $y$. This network has three layers : the input layer, the hidden layer and the output layer. It has two weights, $ω_1$ between input and hidden layer and $ω_2$ between hidden and output layer.\n\nEach iteration involves a forward pass and a backward pass. The aim of the forward pass is to computes the current prediction value $y_{pred}$ thanks to an activation function. Here, the chosen function is a [ReLU (Rectified Linear Unit)](https://en.wikipedia.org/wiki/Rectifier_(neural_networks)).\nThe value of $y_pred$ is used for computing the loss function $ℒ = ||y_{pred} - y||_2$.\nThe last step is to compute the backward pass, which means to calculate the gradients of $w_1$ and $w_2$. This requires the computation of gradients of output prediction $y_{pred}$ and gradients of each activation functions and weights in the hidden layers. Here, we consider only one hidden layer so their is no intermediary weights to take into consideration (chain rule).","metadata":{"id":"eItK-RI-wpCw"}},{"cell_type":"code","source":"# -*- coding: utf-8 -*-\n\ndtype = torch.float\ndevice = torch.device(\"cpu\")\n# device = torch.device(\"cuda:0\") # Uncomment this to run on GPU\n\n# N is batch size; D_in is input dimension;\n# H is hidden dimension; D_out is output dimension.\nN, D_in, H, D_out = 64, 1000, 100, 10\n\n# Create random input and output data\nx = torch.randn(N, D_in, device=device, dtype=dtype)\ny = torch.randn(N, D_out, device=device, dtype=dtype)\n\n# Randomly initialize weights\nw1 = torch.randn(D_in, H, device=device, dtype=dtype)\nw2 = torch.randn(H, D_out, device=device, dtype=dtype)\n\nlearning_rate = 1e-6\nfor t in range(500):\n    # Forward pass: compute predicted y\n    h = x.mm(w1)\n    h_relu = h.clamp(min=0)\n    y_pred = h_relu.mm(w2)\n\n    # Compute and print loss\n    loss = (y_pred - y).pow(2).sum().item()\n    if t % 100 == 99:\n        print(t, loss)\n\n    # Backprop to compute gradients of w1 and w2 with respect to loss\n    grad_y_pred = 2.0 * (y_pred - y)\n    grad_w2 = h_relu.t().mm(grad_y_pred)\n    grad_h_relu = grad_y_pred.mm(w2.t())\n    grad_h = grad_h_relu.clone()\n    grad_h[h < 0] = 0\n    grad_w1 = x.t().mm(grad_h)\n\n    # Update weights using gradient descent\n    w1 -= learning_rate * grad_w1\n    w2 -= learning_rate * grad_w2","metadata":{"id":"b76CqsXl6NSj","outputId":"7509d5da-c10b-47c2-8aeb-fee84b0dd563"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Part 2 : script using nn.Module\n\n\n\n\n\n","metadata":{"id":"M4OkVCrImV4D"}},{"cell_type":"markdown","source":"We compute the same network from precedent part using Pytorch module. Instead of generating random data for inputs $x$ and labels $y$, we import [MNIST dataset](http://yann.lecun.com/exdb/mnist/) from torchvision, which is the module from Pytorch to import datasets.","metadata":{"id":"SCgpccFkQR2P"}},{"cell_type":"code","source":"# We import MNIST dataset from torchvision\npath: str = \"/kaggle/input/mnist-training-set-torchvision/\"\nmnist = datasets.MNIST(root=path)\nx = torch.FloatTensor(mnist.data.reshape(mnist.data.shape[0], 28 * 28).tolist())\ny = torch.LongTensor(mnist.targets.tolist())","metadata":{"id":"zwcZ75diTgsX","execution":{"iopub.status.busy":"2023-05-19T23:16:08.899214Z","iopub.execute_input":"2023-05-19T23:16:08.899577Z","iopub.status.idle":"2023-05-19T23:16:17.935486Z","shell.execute_reply.started":"2023-05-19T23:16:08.899549Z","shell.execute_reply":"2023-05-19T23:16:17.934437Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"mnist","metadata":{"execution":{"iopub.status.busy":"2023-05-19T23:16:17.937628Z","iopub.execute_input":"2023-05-19T23:16:17.937998Z","iopub.status.idle":"2023-05-19T23:16:17.94651Z","shell.execute_reply.started":"2023-05-19T23:16:17.937965Z","shell.execute_reply":"2023-05-19T23:16:17.945397Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We define the neural network in Pytorch, which requires to define a class with nn.Module inheritance.\nclass Net(nn.Module):\n  def __init__(self, D_in, H, D_out):\n    super(Net, self).__init__()\n    self.FC1 = nn.Linear(D_in, H)\n    self.relu = nn.ReLU()\n    self.FC2 = nn.Linear(H, D_out)\n  def forward(self, x):\n    A0 = x\n    A1 = self.FC1(A0)\n    A2 = self.relu(A1)\n    A3 = self.FC2(A1)\n    A4 = torch.sigmoid(A3)\n    return A4","metadata":{"id":"G6ZJFJ9KHEk-","execution":{"iopub.status.busy":"2023-05-19T23:16:47.591886Z","iopub.execute_input":"2023-05-19T23:16:47.592544Z","iopub.status.idle":"2023-05-19T23:16:47.598775Z","shell.execute_reply.started":"2023-05-19T23:16:47.59251Z","shell.execute_reply":"2023-05-19T23:16:47.597861Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We consider an other dataset than the one generated in part 1 so there are specifications to consider. The dimensions of the layers are different : the MNIST dataset is composed of images of size $28*28$ pixels. Therefore, the input dimension of hidden layer is $D_{in} = 28*28=784$","metadata":{"id":"trjHGU3OSuB7"}},{"cell_type":"code","source":"# We compute the model\n\"\"\"\nN : input dimension of input layer\nD_in : output dimension of input layer, which is also the input dimension of hidden layer\nH : output dimension of hidden layer, which is also the input dimension of output layer\nD_out : output dimension of output layer\n\"\"\"\nN, D_in, H, D_out = 10000, 784, 200, 10\nmodel = Net(D_in, H, D_out)","metadata":{"id":"uKQlCKvzmtiY","execution":{"iopub.status.busy":"2023-05-19T23:16:49.617662Z","iopub.execute_input":"2023-05-19T23:16:49.618511Z","iopub.status.idle":"2023-05-19T23:16:49.63434Z","shell.execute_reply.started":"2023-05-19T23:16:49.618477Z","shell.execute_reply":"2023-05-19T23:16:49.633325Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss() # The loss function is cross entropy loss\noptimizer = optim.SGD(model.parameters(),lr=0.00005, weight_decay= 1e-3, momentum = 0.9) # The optimizer is a stochastic gradient descent","metadata":{"id":"CXq61aYTmzzH","execution":{"iopub.status.busy":"2023-05-19T23:16:51.706195Z","iopub.execute_input":"2023-05-19T23:16:51.706546Z","iopub.status.idle":"2023-05-19T23:16:51.713956Z","shell.execute_reply.started":"2023-05-19T23:16:51.706519Z","shell.execute_reply":"2023-05-19T23:16:51.712894Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Write the training\n# We save summaries in subfolder runs/\nnb_epochs = 20\nwriter = SummaryWriter('runs')\ntotal = 0\ncorrect = 0\n\nfor num_epoch in range(1, nb_epochs+1) :\n  for i in range(0, x.shape[0], N):\n    inputs = Variable(x[i:i+N])\n    labels = Variable(y[i:i+N])\n    # zeroes the gradient buffers of all parameters\n    optimizer.zero_grad() # re-init the gradients (otherwise they are cumulated)\n    outputs = model(inputs) # Forward pass: Compute predicted y by passing  x to the model\n\n    loss = criterion(outputs, labels) # Compute loss\n    loss.backward() # perform back-propagation\n    # Perform the training parameters update\n    optimizer.step() # update the weights\n\n    # accuracy\n    _, predicted = torch.max(outputs.data, 1)\n    total += labels.size(0)\n    correct += (predicted == labels).sum().item()\n    accuracy = correct / total\n    writer.add_scalar('Loss/train', loss, num_epoch)\n    writer.add_scalar('Accuracy/train', accuracy, num_epoch)\n\n  print('epoch {}, loss {:.6f}, accuracy {}'.format(num_epoch, loss.item(), accuracy))\nwriter.close()","metadata":{"id":"iLRy60lOm4z-","outputId":"f2f7e6cc-b412-4d07-eb07-32fc140ddd2a","execution":{"iopub.status.busy":"2023-05-19T23:16:53.049251Z","iopub.execute_input":"2023-05-19T23:16:53.049604Z","iopub.status.idle":"2023-05-19T23:17:03.248233Z","shell.execute_reply.started":"2023-05-19T23:16:53.049578Z","shell.execute_reply":"2023-05-19T23:17:03.246023Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We obtain better results with a higher batch size, which is the input dimension of input layer. We consider here a value of $10$ $000$. It induces a value of $200$ for the output dimension of hidden layer. The learning rate has a value of $5*10^{-5}$.","metadata":{"id":"j1Y_xuIGT55i"}},{"cell_type":"markdown","source":"For a better understanding of the trainings, we save the values of accuracy and loss function through the epochs and visualise the performances thanks to Tensorboard summaries.","metadata":{"id":"FooPp1D6gAN9"}},{"cell_type":"code","source":"# Show summaries\n%load_ext tensorboard\n%tensorboard --logdir=runs","metadata":{"id":"tCg4iuYcJpk_","execution":{"iopub.status.busy":"2023-05-19T23:17:03.25037Z","iopub.execute_input":"2023-05-19T23:17:03.251063Z","iopub.status.idle":"2023-05-19T23:17:09.789754Z","shell.execute_reply.started":"2023-05-19T23:17:03.251028Z","shell.execute_reply":"2023-05-19T23:17:09.788762Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Part 3 : ResNet 18","metadata":{"id":"-rcoUlHr_ols"}},{"cell_type":"markdown","source":"This code has been inspired from https://www.kaggle.com/c/digit-recognizer to implement ResNet 18 algorithm to work on MNIST dataset","metadata":{"id":"VAs5my9goDw4"}},{"cell_type":"code","source":"# We import training data from the following github repository : https://github.com/wehrley/Kaggle-Digit-Recognizer/blob/master/train.csv\n!npx degit --force wehrley/Kaggle-Digit-Recognizer","metadata":{"id":"Zuo_YEAIcVdl","execution":{"iopub.status.busy":"2023-05-19T23:17:21.461598Z","iopub.execute_input":"2023-05-19T23:17:21.461975Z","iopub.status.idle":"2023-05-19T23:17:32.700775Z","shell.execute_reply.started":"2023-05-19T23:17:21.461944Z","shell.execute_reply":"2023-05-19T23:17:32.699376Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\nimport matplotlib.pyplot as plt\n\n# We compute training dataset from MNIST dataset we downloaded in .csv\npath: str = \"/kaggle/input/digit-recognizer/\"\ntrain = pd.read_csv(path + \"train.csv\", dtype = np.float32)\n\n# We compute training dataset and test dataset by splitting\n# We define labels and attributes\ntargets_numpy = train.label.values\nfeatures_numpy = train.loc[:,train.columns != \"label\"].values/255\n\n# We split with 80% for training dataset et 20% for testing dataset. \nfeatures_train, features_test, targets_train, targets_test = train_test_split(features_numpy,\n                                                                             targets_numpy,\n                                                                             test_size = 0.2,\n                                                                             random_state = 42) \n\n# We use Pytorch Variables for accumulating gradients. We must use tensors.\nfeaturesTrain = torch.from_numpy(features_train)\ntargetsTrain = torch.from_numpy(targets_train).type(torch.LongTensor)\nfeaturesTest = torch.from_numpy(features_test)\ntargetsTest = torch.from_numpy(targets_test).type(torch.LongTensor)\n\n# Pytorch train and test sets\ntrain = torch.utils.data.TensorDataset(featuresTrain,targetsTrain)\ntest = torch.utils.data.TensorDataset(featuresTest,targetsTest)\n\n# We use batch of size 100\nbatch_size = 100\n\n# We load data using DataLoader of Pytorch\ntrain_loader = torch.utils.data.DataLoader(train, batch_size = batch_size, shuffle = False)\ntest_loader = torch.utils.data.DataLoader(test, batch_size = batch_size, shuffle = False)\n\n# For first training, we define 2500 iterations, 7 epochs for our data and 0.05 learning_rate.\nn_iters = 2500\nnum_epochs = n_iters / (len(features_train) / batch_size)\nnum_epochs = int(num_epochs)\nlearning_rate = 0.05","metadata":{"id":"3kNlAA_SZIeb"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We define 3*3 convolutional neural network 3*3\ndef conv3x3(in_channels, out_channels, stride=1):\n    return nn.Conv2d(in_channels, out_channels, kernel_size=3,\n                    stride=stride, padding=1, bias=False)\n\n\n# We define the residual block class useful for defining ResNet 18\nclass ResidualBlock(nn.Module):\n    def __init__(self, in_channels, out_channels, stride=1, downsample=None):\n        super(ResidualBlock, self).__init__()\n        self.conv1 = conv3x3(in_channels, out_channels, stride)\n        self.bn1 = nn.BatchNorm2d(out_channels)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = conv3x3(out_channels, out_channels)\n        self.bn2 = nn.BatchNorm2d(out_channels)\n        self.downsample = downsample\n\n    def forward(self, x):\n        residual = x\n        out = self.conv1(x)\n        out = self.bn1(out)\n        out = self.relu(out)\n        out = self.conv2(out)\n        out = self.bn2(out)\n        if self.downsample:\n            residual = self.downsample(x)\n        out += residual\n        out = self.relu(out)\n        return out\n\n\n# We define the class to instantiate models based on ResNet 18\nclass ResNet(nn.Module):\n    def __init__(self, block, layers, num_classes=10):\n        super(ResNet, self).__init__()\n        self.in_channels = 16\n        self.conv = conv3x3(1, 16)\n        self.bn = nn.BatchNorm2d(16)\n        self.relu = nn.ReLU(inplace=True)\n        self.layer1 = self.make_layer(block, 16, layers[0])\n        self.layer2 = self.make_layer(block, 32, layers[0], 2)\n        self.layer3 = self.make_layer(block, 64, layers[1], 2)\n        self.avg_pool = nn.AvgPool2d(8)\n        self.fc = nn.Linear(64, num_classes)\n\n    def make_layer(self, block, out_channels, blocks, stride=1):\n        downsample = None\n        if (stride != 1) or (self.in_channels != out_channels):\n            downsample = nn.Sequential(\n                conv3x3(self.in_channels, out_channels, stride=stride),\n                nn.BatchNorm2d(out_channels))\n        layers = []\n        layers.append(block(self.in_channels, out_channels, stride, downsample))\n        self.in_channels = out_channels\n        for i in range(1, blocks):\n            layers.append(block(out_channels, out_channels))\n        return nn.Sequential(*layers)\n\n    def forward(self, x):\n        out = self.conv(x)\n        out = self.bn(out)\n        out = self.layer1(out)\n        out = self.layer2(out)\n        out = self.layer3(out)\n        out = self.avg_pool(out)\n        out = out.view(out.size(0), -1)\n        out = self.fc(out)\n        return out","metadata":{"id":"BUpfk73pZUzN"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We instatiate a model based on ResNet class we implemented\nnet_args = {\n    \"block\": ResidualBlock,\n    \"layers\": [2, 2, 2, 2]\n}\nmodel = ResNet(**net_args)","metadata":{"id":"ToPUJ7s8ZZ6P"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We define criterion and optimizer\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(),lr=learning_rate)","metadata":{"id":"sYBRA3VhZhud"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Training of the model. Variables loss_list, iteration_list and accuracy_list are useful to save values of prediction and loss on ranges of iteration\nloss_list = []\niteration_list = []\naccuracy_list = []\ncount = 0\nfor epoch in range(num_epochs):\n    for i, (images, labels) in enumerate(train_loader):\n        train  = Variable(images.resize_(batch_size, 1, 32, 32))\n        labels = Variable(labels)           \n        # Clear gradients\n        optimizer.zero_grad()    \n        # Forward propagation\n        outputs = model(train)        \n        # Calculate softmax and ross entropy loss\n        loss = criterion(outputs, labels)        \n        # Calculating gradients\n        loss.backward()        \n        # Update parameters\n        optimizer.step()        \n        count += 1     \n        if count % 250 == 0:\n            # Calculate Accuracy         \n            correct = 0\n            total = 0\n            # Iterate through test dataset\n            for images, labels in test_loader:\n                images = Variable(images.resize_(batch_size, 1, 32, 32))                \n                # Forward propagation\n                outputs = model(images)                \n                # Get predictions from the maximum value\n                predicted = torch.max(outputs.data, 1)[1]                \n                # Total number of labels\n                total += labels.size(0)              \n                correct += (predicted == labels).sum()         \n            accuracy = 100 * correct / float(total)           \n            # store loss and iteration\n            loss_list.append(loss.data)\n            iteration_list.append(count)\n            accuracy_list.append(accuracy)\n            if count % 500 == 0:\n                # Print Loss\n                print('Iteration: {}  Loss: {}  Accuracy: {} %'.format(count, loss.item(), accuracy))","metadata":{"id":"9G5SogeSZ1H3","outputId":"4609bcc3-afed-4f8b-e235-4442d00c5835"},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"We obtain better results than the ones of the previous part, but the computing time is higher. The network is more robust and show better results on the first epochs.","metadata":{"id":"MZhT7Bm-h9Pr"}},{"cell_type":"markdown","source":"ResNet 18 computes regularisations to prevent the risk of overfitting on the data. This regularisation is computed by a normalisation layer which follows every convolutional layer in the network. It corresponds to the addition of some noise when updating the weights. This noise depends of the batch size but also of the mean and the standard deviation on batch values.\nOther methods exist to diminish the error du to generalisation in deep neural network, such as the [DropOut](https://pytorch.org/docs/stable/generated/torch.nn.Dropout.html).","metadata":{"id":"yG4MgK1fi7uv"}},{"cell_type":"markdown","source":"## Grid Search for hyper parameters optimization","metadata":{"id":"veFn5L6lphkg"}},{"cell_type":"code","source":"# We define a data_load to reload the data with the batch_size\ndef data_load(batch_size) :\n  return torch.utils.data.DataLoader(train, batch_size = batch_size, shuffle = False), torch.utils.data.DataLoader(test, batch_size = batch_size, shuffle = False)\n\n# We use this function to create a new network, to train it using parameters and to recover precision after 250 iterations or end of DataLoader\ndef resnet18_simplified_for_grid_search(optimizer, batch_size=64) :\n  net_args = {\n      \"block\": ResidualBlock,\n      \"layers\": [2, 2, 2, 2]\n  }\n  model = ResNet(**net_args)\n  train_loader, test_loader = data_load(batch_size)\n  count = 0\n  for i, (images, labels) in enumerate(train_loader):\n      train  = Variable(images.resize_(batch_size, 1, 32, 32))\n      labels = Variable(labels)           \n      # Clear gradients\n      optimizer.zero_grad()    \n      # Forward propagation\n      outputs = model(train)        \n      # Calculate softmax and ross entropy loss\n      loss = criterion(outputs, labels)        \n      # Calculating gradients\n      loss.backward()        \n      # Update parameters\n      optimizer.step()        \n      count += 1\n      if count % 250 == 0 or count == len(list(enumerate(train_loader))):\n            # Calculate Accuracy         \n            correct = 0\n            total = 0\n            # Iterate through test dataset\n            for images, labels in test_loader:\n                images = Variable(images.resize_(batch_size, 1, 32, 32))                \n                # Forward propagation\n                outputs = model(images)                \n                # Get predictions from the maximum value\n                predicted = torch.max(outputs.data, 1)[1]            \n                # Total number of labels\n                try:\n                  correct += (predicted == labels).sum() \n                  total += labels.size(0)\n                except:\n                  None               \n            accuracy = 100 * correct / float(total)\n            break\n  return accuracy","metadata":{"id":"g9bza9eX-zAz"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We define the values of parameters for performing grid search.\nlearning_rate_list_1 = [1.0,0.1,0.01,0.001]\nweight_decay_list = [0.1,0.01,0.001]\nbatch_size_1 = [5,200]\n\nlearning_rate_list_2 = [0.11, 0.9910, 0.9905]\nbatch_size_2 = [5,64,200]","metadata":{"id":"HNfPErRH5m13"},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%cd /kaggle/working/","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# We write the result of grid search in grid_search.csv file\nf = open(\"grid_search.csv\", \"a\")\nfor i in range(len(learning_rate_list_1)) :\n  for j in range(len(weight_decay_list)) :\n    optimizer = optim.Adam(model.parameters(),lr=learning_rate_list_1[i],weight_decay=weight_decay_list[j])\n    current_accuracy = resnet18_simplified_for_grid_search(optimizer)\n    f.write(\" Learning rate = {}, Weight Decay = {}, Batch_size = {}, Accuracy = {} \".format(learning_rate_list_1[i],weight_decay_list[j],64,current_accuracy) + '\\n')\n  for k in range(len(batch_size_1)) :\n    optimizer = optim.Adam(model.parameters(),lr=learning_rate_list_1[i],weight_decay=0.001)\n    current_accuracy = resnet18_simplified_for_grid_search(optimizer,batch_size=batch_size_1[k])\n    f.write(\" Learning rate = {}, Weight Decay = {}, Batch_size = {}, Accuracy = {} \".format(learning_rate_list_1[i],0.001,batch_size_1[k],current_accuracy) + '\\n')\nfor i in range(len(learning_rate_list_2)) :\n  for k in range(len(batch_size_2)) :\n    optimizer = optim.Adam(model.parameters(),lr=learning_rate_list_2[i],weight_decay=0.001)\n    current_accuracy = resnet18_simplified_for_grid_search(optimizer,batch_size=batch_size_2[k])\n    f.write(\" Learning rate = {}, Weight Decay = {}, Batch_size = {}, Accuracy = {} \".format(learning_rate_list_2[i],0.001,batch_size_2[k],current_accuracy) + '\\n')\nf.close()","metadata":{"id":"w6rHYiHR8z57"},"execution_count":null,"outputs":[]}]}