<jupyter_start><jupyter_text>Problem 4 - Batch Augmentation, Cutout Regularization 20 points<jupyter_code>import tensorflow as tf
# import keras
from tensorflow.keras.layers import Dense, Conv2D, BatchNormalization, Activation
from tensorflow.keras.layers import AveragePooling2D, Input, Flatten
from tensorflow.keras.optimizers import Adam
from tensorflow.keras.callbacks import ModelCheckpoint, LearningRateScheduler
from tensorflow.keras.callbacks import ReduceLROnPlateau
from tensorflow.keras.preprocessing.image import ImageDataGenerator
from tensorflow.keras.regularizers import l2
from tensorflow.keras import backend as K
from tensorflow.keras.models import Model
from tensorflow.keras.datasets import cifar10
import numpy as np
from albumentations import Cutout
import matplotlib.pyplot as plt
import cv2<jupyter_output>/home/ma-user/anaconda3/envs/TensorFlow-2.1.0/lib/python3.6/site-packages/h5py/__init__.py:36: FutureWarning: Conversion of the second argument of issubdtype from `float` to `np.floating` is deprecated. In future, it will be treated as `np.float64 == np.dtype(float).type`.
  from ._conv import register_converters as _register_converters<jupyter_text>1.Explain cutout regularization and its advantages compared to simple dropout (as argued in the paper by DeVries et al) in your own words. Select any 2 images from CIFAR10 and show how does these images look after applying cutout. Use a square-shaped fixed size zero-mask to a random location of each image and generate its cutout version. Refer to the paper by DeVries et al (Section 3) and associated github repository. (2+4)<jupyter_code>import numpy as np
from tarfile import open  # add by myself
from tensorflow.python.keras import backend as K
from tensorflow.python.keras.datasets.cifar import load_batch
from tensorflow.python.keras.utils.data_utils import get_file
from tensorflow.python.util.tf_export import keras_export
import os


@keras_export('keras.datasets.cifar10.load_data')
def load_data(filename):
    dirname = 'cifar-10-batches-py'
    origin = 'https://www.cs.toronto.edu/~kriz/cifar-10-python.tar.gz'
    if filename != None:
        path = filename
    else:
        path = get_file(
          dirname,
          origin=origin,
          untar=True,
          file_hash=
          '6d958be074577803d12ecdefd02955f39262c83c16fe9348329d7fe0b5c001ce')

    num_train_samples = 50000

    x_train = np.empty((num_train_samples, 3, 32, 32), dtype='uint8')
    y_train = np.empty((num_train_samples,), dtype='uint8')

    for i in range(1, 6):
        fpath = os.path.join(path, 'data_batch_' + str(i))
        (x_train[(i - 1) * 10000:i * 10000, :, :, :],
         y_train[(i - 1) * 10000:i * 10000]) = load_batch(fpath)

    fpath = os.path.join(path, 'test_batch')
    x_test, y_test = load_batch(fpath)

    y_train = np.reshape(y_train, (len(y_train), 1))
    y_test = np.reshape(y_test, (len(y_test), 1))

    if K.image_data_format() == 'channels_last':
        x_train = x_train.transpose(0, 2, 3, 1)
        x_test = x_test.transpose(0, 2, 3, 1)

    x_test = x_test.astype(x_train.dtype)
    y_test = y_test.astype(y_train.dtype)

    return (x_train, y_train), (x_test, y_test)
(x_train, y_train), (x_test, y_test) = load_data('cifar10')
cutout = Cutout(num_holes=1, max_h_size=7, max_w_size=7, fill_value=0, p=1)
plt.imshow(cutout(image=x_train[0])['image'])
plt.imshow(cutout(image=x_train[1])['image'])<jupyter_output><empty_output><jupyter_text>2.Using CIFAR10 datasest and Resnet-44 we will first apply simple data augmentation as in He et al. (look at Section 4.2 of He et al.) and train the model with batch size 64. Note that testing is always done with original images. Plot validation error vs number of training epochs. (4)<jupyter_code># Training parameters for ResNet-44
batch_size = 64
epochs = 100
# data_augmentation = True
num_classes = 10
n = 3
depth = n * 6 + 2
# Input image dimensions.
input_shape = x_train.shape[1:]

# Normalize data.
x_train = x_train.astype('float32') / 255
x_test = x_test.astype('float32') / 255

print('x_train shape:', x_train.shape)
print(x_train.shape[0], 'train samples')
print(x_test.shape[0], 'test samples')
print('y_train shape:', y_train.shape)

# Convert class vectors to binary class matrices.
y_train = tf.keras.utils.to_categorical(y_train, num_classes)
y_test = tf.keras.utils.to_categorical(y_test, num_classes)
def data_augmentation(img, pad_width, flip=True):
    '''
    padding some pixels on each side, and randomly sample a crop from the padded image or its horizontal flip.
    '''
    
    if flip == True:
        img = np.rot90(img ,int(np.random.rand()*4))
    pad_img = np.pad(img,((pad_width,pad_width),(pad_width,pad_width),(0,0)))
    rand_x = int(np.random.rand()*pad_width*2)
    rand_y = int(np.random.rand()*pad_width*2)
    return pad_img[rand_x:rand_x+img.shape[0], rand_y:rand_y+img.shape[1],...]
x_train_aug = np.zeros((x_train.shape[0]*4, x_train.shape[1], x_train.shape[2], x_train.shape[3]))
y_train_aug = np.zeros((y_train.shape[0]*4, y_train.shape[1]))
for i in range(x_train_aug.shape[0]):
    x_train_aug[i] = data_augmentation(x_train[i%x_train.shape[0]], 4)
    y_train_aug[i] = y_train[i%x_train.shape[0]]
def lr_schedule(epoch):
    """Learning Rate Schedule

    Learning rate is scheduled to be reduced after 80, 120, 160, 180 epochs.
    Called automatically every epoch as part of callbacks during training.

    # Arguments
        epoch (int): The number of epochs

    # Returns
        lr (float32): learning rate
    """
    lr = 1e-3
    if epoch > 180:
        lr *= 0.5e-3
    elif epoch > 160:
        lr *= 1e-3
    elif epoch > 120:
        lr *= 1e-2
    elif epoch > 80:
        lr *= 1e-1
    print('Learning rate: ', lr)
    return lr
def resnet_layer(inputs,
                 num_filters=16,
                 kernel_size=3,
                 strides=1,
                 activation='relu',
                 batch_normalization=True,
                 conv_first=True):
    """2D Convolution-Batch Normalization-Activation stack builder

    # Arguments
        inputs (tensor): input tensor from input image or previous layer
        num_filters (int): Conv2D number of filters
        kernel_size (int): Conv2D square kernel dimensions
        strides (int): Conv2D square stride dimensions
        activation (string): activation name
        batch_normalization (bool): whether to include batch normalization
        conv_first (bool): conv-bn-activation (True) or
            bn-activation-conv (False)

    # Returns
        x (tensor): tensor as input to the next layer
    """
    conv = Conv2D(num_filters,
                  kernel_size=kernel_size,
                  strides=strides,
                  padding='same',
                  kernel_initializer='he_normal',
                  kernel_regularizer=l2(1e-4))

    x = inputs
    if conv_first:
        x = conv(x)
        if batch_normalization:
            x = BatchNormalization()(x)
        if activation is not None:
            x = Activation(activation)(x)
    else:
        if batch_normalization:
            x = BatchNormalization()(x)
        if activation is not None:
            x = Activation(activation)(x)
        x = conv(x)
    return x

def resnet_v1(input_shape, depth, num_classes=10):
    """ResNet Version 1 Model builder [a]

    Stacks of 2 x (3 x 3) Conv2D-BN-ReLU
    Last ReLU is after the shortcut connection.
    At the beginning of each stage, the feature map size is halved (downsampled)
    by a convolutional layer with strides=2, while the number of filters is
    doubled. Within each stage, the layers have the same number filters and the
    same number of filters.
    Features maps sizes:
    stage 0: 32x32, 16
    stage 1: 16x16, 32
    stage 2:  8x8,  64
    The Number of parameters is approx the same as Table 6 of [a]:
    ResNet20 0.27M
    ResNet32 0.46M
    ResNet44 0.66M
    ResNet56 0.85M
    ResNet110 1.7M

    # Arguments
        input_shape (tensor): shape of input image tensor
        depth (int): number of core convolutional layers
        num_classes (int): number of classes (CIFAR10 has 10)

    # Returns
        model (Model): Keras model instance
    """
    if (depth - 2) % 6 != 0:
        raise ValueError('depth should be 6n+2 (eg 20, 32, 44 in [a])')
    # Start model definition.
    num_filters = 16
    num_res_blocks = int((depth - 2) / 6)

    inputs = Input(shape=input_shape)
    x = resnet_layer(inputs=inputs)
    # Instantiate the stack of residual units
    for stack in range(3):
        for res_block in range(num_res_blocks):
            strides = 1
            if stack > 0 and res_block == 0:  # first layer but not first stack
                strides = 2  # downsample
            y = resnet_layer(inputs=x,
                             num_filters=num_filters,
                             strides=strides)
            y = resnet_layer(inputs=y,
                             num_filters=num_filters,
                             activation=None)
            if stack > 0 and res_block == 0:  # first layer but not first stack
                # linear projection residual shortcut connection to match
                # changed dims
                x = resnet_layer(inputs=x,
                                 num_filters=num_filters,
                                 kernel_size=1,
                                 strides=strides,
                                 activation=None,
                                 batch_normalization=False)
            x = tf.keras.layers.add([x, y])
            x = Activation('relu')(x)
        num_filters *= 2

    # Add classifier on top.
    # v1 does not use BN after last shortcut connection-ReLU
    x = AveragePooling2D(pool_size=8)(x)
    y = Flatten()(x)
    outputs = Dense(num_classes,
                    activation='softmax',
                    kernel_initializer='he_normal')(y)

    # Instantiate model.
    model = Model(inputs=inputs, outputs=outputs)
    return model
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
lr_scheduler = LearningRateScheduler(lr_schedule)

lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)

callbacks = [lr_reducer, lr_scheduler]
import albumentations
from albumentations import (Blur,Flip,ShiftScaleRotate,GridDistortion,ElasticTransform,PadIfNeeded,
                            HueSaturationValue,Transpose,RandomBrightnessContrast,CLAHE,RandomCrop,
                            CoarseDropout,Normalize,ToFloat,OneOf,Compose,Cutout)
from tensorflow import keras
import tensorflow.keras.backend as K


class HeGenerator(keras.utils.Sequence):
    def __init__(self, image_filenames, labels,
                 batch_size=64, mix=False, augment=True):
        self.image_filenames = image_filenames
        self.labels = labels
        self.batch_size = batch_size
        self.is_mix = mix
        self.is_augment = augment

        if self.is_augment:
            self.generator = Compose([Flip(p=0.5),
                                      PadIfNeeded(40, 40, border_mode=0),
                                      RandomCrop(32, 32, always_apply=False, p=1.0)])
        else:
            self.generator = Compose([ToFloat(max_value=255.0,p=1.0)],p=1.0)
    def __len__(self):
        return int(np.ceil(len(self.image_filenames)/self.batch_size))

    def mix_up(self,x,y):
        original_index = np.arange(x.shape[0])
        new_index = np.arange(x.shape[0])
        np.random.shuffle(new_index)
        beta = np.random.beta(0.2, 0.4)
        mix_x = beta * x[original_index] + (1 - beta) * x[new_index]
        mix_y = beta * y[original_index] + (1 - beta) * y[new_index]
        return mix_x, mix_y
        
    def __getitem__(self,index):
        batch_x = self.image_filenames[index*self.batch_size:(index+1)*self.batch_size]
        batch_y = self.labels[index*self.batch_size:(index+1)*self.batch_size]
        new_images = []
        new_labels = []
        for image_name,label in zip(batch_x,batch_y):
            image = image_name
            img = self.generator(image=image)['image']
            new_images.append(img)
            new_labels.append(label)
        new_images = np.array(new_images)
        new_labels = np.array(new_labels)
        if self.is_mix:
            new_images, new_labels = self.mix_up(new_images, new_labels)
        return new_images, new_labels    
train_generator = HeGenerator(x_train, y_train, batch_size=batch_size)
train_generator.__getitem__(0)[0].shape
plt.imshow(train_generator.__getitem__(0)[0][0])
# Fit the model on the batches generated by datagen.flow().
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
valid_error = [1 - i for i in history.history['val_accuracy']]
epoch = [i for i in range(0, epochs)]
# Score trained model.
scores = model.evaluate(x_test, y_test, verbose=1)
plt.title('validation error VS epoch')
plt.xlabel("epoch")
plt.ylabel("validation error")
plt.plot(epoch, valid_error)<jupyter_output><empty_output><jupyter_text>3.Next use cutout for data augmentation in Resnet-44 as in Hoffer et al. and train the model and use the same set-up in your experiments. Plot validation error vs number of epochs for different values of M (2,4,8,16,32) where M is the number of instances generated from an input sample after applying cutout M times effectively increasing the batch size to M * B, where B is the original batch size (before applying cutout augmentation). You will obtain a figure similar to Figure 3(a) in the paper by Hoffer et al. Also compare the number of epochs and wallclock time to reach 94% accuracy for different values of M. Do not run any experiment for more than 100 epochs. If even after 100 epochs of training you did not achieve 94% then just report the accuracy you obtain and the corresponding wallclock time to train for 100 epochs. Before attempting this question it is advisable to read paper by Hoffer et al. and especially Section 4.1. (5+5)<jupyter_code>import albumentations
from albumentations import (Blur,Flip,ShiftScaleRotate,GridDistortion,ElasticTransform,PadIfNeeded,
                            HueSaturationValue,Transpose,RandomBrightnessContrast,CLAHE,RandomCrop,
                            CoarseDropout,Normalize,ToFloat,OneOf,Compose,Cutout)
from tensorflow import keras
import tensorflow.keras.backend as K


class MyGenerator(keras.utils.Sequence):
    def __init__(self, image_filenames, labels,
                 batch_size=64, M=16, mix=False, augment=True):
        self.image_filenames = image_filenames
        self.labels = labels
        self.batch_size = batch_size
        self.is_mix = mix
        self.is_augment = augment
        self.M = M

        if self.is_augment:
#             self.generator = Compose([Blur(),Flip(),Transpose(),ShiftScaleRotate(),
#                                   RandomBrightnessContrast(),HueSaturationValue(),
#                                  CLAHE(),GridDistortion(),ElasticTransform(),CoarseDropout(),
#                                  ToFloat(max_value=255.0,p=1.0)],p=1.0)
            self.generator = Compose([Flip(p=0.5),
                                      PadIfNeeded(40, 40, border_mode=0),
                                      RandomCrop(32, 32, always_apply=False, p=1.0),
                                      Cutout(num_holes=1, max_h_size=16, max_w_size=16, fill_value=0, p=0.5)])
        else:
            self.generator = Compose([ToFloat(max_value=255.0,p=1.0)],p=1.0)
    def __len__(self):
        return int(np.ceil(len(self.image_filenames)/self.batch_size))

    def mix_up(self,x,y):
        original_index = np.arange(x.shape[0])
        new_index = np.arange(x.shape[0])
        np.random.shuffle(new_index)
        beta = np.random.beta(0.2, 0.4)
        mix_x = beta * x[original_index] + (1 - beta) * x[new_index]
        mix_y = beta * y[original_index] + (1 - beta) * y[new_index]
        return mix_x, mix_y
        
    def __getitem__(self,index):
        batch_x = self.image_filenames[index*self.batch_size:(index+1)*self.batch_size]
        batch_y = self.labels[index*self.batch_size:(index+1)*self.batch_size]
        new_images = []
        new_labels = []
        for image_name,label in zip(batch_x,batch_y):
            image = image_name
            for j in range(0, self.M):   # Add M new generate batches
                img = self.generator(image=image)['image']
                new_images.append(img)
                new_labels.append(label)
        new_images = np.array(new_images)
        new_labels = np.array(new_labels)
        if self.is_mix:
            new_images, new_labels = self.mix_up(new_images, new_labels)
        return new_images, new_labels    
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=1)
train_generator.__getitem__(0)[0].shape
plt.imshow(train_generator.__getitem__(0)[0][0])
def lr_schedule(epoch):
    """Learning Rate Schedule

    Learning rate is scheduled to be reduced after 80, 120, 160, 180 epochs.
    Called automatically every epoch as part of callbacks during training.

    # Arguments
        epoch (int): The number of epochs

    # Returns
        lr (float32): learning rate
    """
    lr = 1e-3
    if epoch > 180:
        lr *= 0.5e-3
    elif epoch > 160:
        lr *= 1e-3
    elif epoch > 120:
        lr *= 1e-2
    elif epoch > 80:
        lr *= 1e-1
    print('Learning rate: ', lr)
    return lr
import time
class LossHistory(keras.callbacks.Callback):
    def on_train_begin(self, logs={}):
        self.totaltime = time.time()
        
    def on_train_end(self, logs={}):
        self.totaltime = time.time() - self.totaltime
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=1)   # Set M = 1
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M1 = time_history.totaltime
val_error_M1 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M1)
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=2)     # Set M = 2
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M2 = time_history.totaltime
val_error_M2 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M2)
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=4)     # Set M = 4
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M4 = time_history.totaltime
val_error_M4 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M4)
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=8)     # Set M = 8
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M8 = time_history.totaltime
val_error_M8 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M8)
epoch = [i for i in range(0, epochs)]
plt.title('validation error VS epoch')
plt.xlabel("epoch")
plt.ylabel("validation error")
plt.plot(epoch, val_error_M1)
plt.plot(epoch, val_error_M2)
plt.plot(epoch, val_error_M4)
plt.plot(epoch, val_error_M8)
plt.legend(["M = 1","M = 2","M = 4","M = 8"],loc='upper right')
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=16)     # Set M = 16
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M16 = time_history.totaltime
val_error_M16 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M16)
plt.title('validation error VS epoch')
plt.xlabel("epoch")
plt.ylabel("validation error")
plt.plot(epoch, val_error_M1)
plt.plot(epoch, val_error_M2)
plt.plot(epoch, val_error_M4)
plt.plot(epoch, val_error_M8)
plt.plot(epoch, val_error_M16)
plt.legend(["M = 1","M = 2","M = 4","M = 8","M = 16"],loc='upper right')
lr_scheduler = LearningRateScheduler(lr_schedule)
lr_reducer = ReduceLROnPlateau(factor=np.sqrt(0.1),
                               cooldown=0,
                               patience=5,
                               min_lr=0.5e-6)
time_history = LossHistory()
callbacks = [lr_reducer, lr_scheduler, time_history]
train_generator = MyGenerator(x_train, y_train, batch_size=batch_size, M=32)     # Set M = 32
model = resnet_v1(input_shape=input_shape, depth=depth)
model.compile(loss='categorical_crossentropy',
              optimizer=Adam(lr=lr_schedule(0)),
              metrics=['accuracy'])
history = model.fit_generator(train_generator,
                    validation_data=(x_test, y_test),
                    epochs=epochs, verbose=1,
                    callbacks=callbacks)
totaltime_M32 = time_history.totaltime
val_error_M32 = [1 - i for i in history.history['val_accuracy']]
print(totaltime_M32)
plt.title('validation error VS epoch')
plt.xlabel("epoch")
plt.ylabel("validation error")
plt.plot(epoch, val_error_M1)
plt.plot(epoch, val_error_M2)
plt.plot(epoch, val_error_M4)
plt.plot(epoch, val_error_M8)
plt.plot(epoch, val_error_M16)
plt.plot(epoch, val_error_M32)
plt.legend(["M = 1","M = 2","M = 4","M = 8","M = 16","M = 32"],loc='upper right')<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10215009810333552, 'fraction_numerical': 0.025874754741661215, 'mean_word_length': 2.7632671896631287, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 26, 'https://': 1, 'lorem ipsum': 0, 'www.': 1, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 13, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '4', 'dir': 'jupyter-structured-clean-dedup', 'id': '269304', 'license': 'MIT', 'n_tokens_mistral': 8269, 'n_tokens_neox': 7769, 'n_words': 1811, 'path': 'Assignment3/question4_p100server.ipynb', 'repo_name': 'IrohXu/NYU_Intro_to_Deep_Learning_System', 'size': '24464'}