{"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 numpy as np\nimport pandas as pd\nfrom tqdm.notebook import tqdm\nimport torch\nimport torchvision\nimport torchvision.transforms as T\nfrom collections import defaultdict, deque\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.faster_rcnn import FastRCNNPredictor\nfrom torchvision.models.detection import FasterRCNN\nfrom torchvision.models.detection.rpn import AnchorGenerator\nimport ast\nfrom torch.utils.data import DataLoader, Dataset\nfrom torch.utils.data.sampler import SequentialSampler\nimport albumentations as A\nfrom albumentations.pytorch.transforms import ToTensorV2\n\nimport cv2\nimport os,sys,matplotlib,re\nfrom PIL import Image\nfrom skimage import exposure\nfrom pydicom.pixel_data_handlers.util import apply_voi_lut\nimport matplotlib.pyplot as plt\nimport matplotlib.image as immg\n\nimport warnings\nwarnings.filterwarnings(\"ignore\")","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_kg_hide-input":false,"execution":{"iopub.status.busy":"2021-08-15T05:59:01.630796Z","iopub.execute_input":"2021-08-15T05:59:01.631206Z","iopub.status.idle":"2021-08-15T05:59:05.370905Z","shell.execute_reply.started":"2021-08-15T05:59:01.631117Z","shell.execute_reply":"2021-08-15T05:59:05.370046Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu')","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:10.009906Z","iopub.execute_input":"2021-08-15T05:59:10.010269Z","iopub.status.idle":"2021-08-15T05:59:10.081903Z","shell.execute_reply.started":"2021-08-15T05:59:10.010239Z","shell.execute_reply":"2021-08-15T05:59:10.080679Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Acá debemos escribir la ruta de la carpeta que contiene a las imágenes.\npath = \"../input/wheat-train512x512/\" ","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:12.692836Z","iopub.execute_input":"2021-08-15T05:59:12.693174Z","iopub.status.idle":"2021-08-15T05:59:12.697685Z","shell.execute_reply.started":"2021-08-15T05:59:12.693144Z","shell.execute_reply":"2021-08-15T05:59:12.696643Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Acá debemos escribir la ruta del archivo csv.\ndf = pd.read_csv('../input/global-wheat-detection/train.csv')\ndf.head()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:16.977983Z","iopub.execute_input":"2021-08-15T05:59:16.978359Z","iopub.status.idle":"2021-08-15T05:59:17.273156Z","shell.execute_reply.started":"2021-08-15T05:59:16.978314Z","shell.execute_reply":"2021-08-15T05:59:17.272198Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"La columna bbox contiene strings, las cuales son listas coercionadas al tipo string.\n\nLa función *literal_eval* del paquete *ast* nos permite convertir estas strings de vuelta en listas.\n\n&nbsp;\n\nEjemplo:","metadata":{}},{"cell_type":"code","source":"print(df[\"bbox\"][0])\ntype(df[\"bbox\"][0])","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:35.771965Z","iopub.execute_input":"2021-08-15T04:52:35.772392Z","iopub.status.idle":"2021-08-15T04:52:35.782317Z","shell.execute_reply.started":"2021-08-15T04:52:35.772355Z","shell.execute_reply":"2021-08-15T04:52:35.780998Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(ast.literal_eval(df[\"bbox\"][0]))\ntype(ast.literal_eval(df[\"bbox\"][0]))","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:35.78693Z","iopub.execute_input":"2021-08-15T04:52:35.787619Z","iopub.status.idle":"2021-08-15T04:52:35.797824Z","shell.execute_reply.started":"2021-08-15T04:52:35.787582Z","shell.execute_reply":"2021-08-15T04:52:35.796564Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Estos números describen a la bounding box. La menor coordenada  x  de la bounding box es 834. La menor coordenada  y  de la bounding box es 222. Es decir, la esquina inferior izquierda de la bounding box tiene coordenadas  (834,222) . El ancho de la bounding box es 56, y su alto es 36.\n\nEn lugar de tener en una sola columna los datos de la bounding box, vamos a darle su propia columna a cada uno de los cuatro datos que describen a la bounding box.","metadata":{}},{"cell_type":"code","source":"df['x_min'] = np.nan\ndf['y_min'] = np.nan\ndf['w'] = np.nan\ndf['h'] = np.nan\n\ndf[['x_min', 'y_min', 'w', 'h']] = np.stack(df['bbox'].apply(lambda x: ast.literal_eval(x)))\n\n# Habiendo hecho esto podemos prescindir de la columna bbox.\ndf.drop(columns=['bbox'], inplace=True)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:36.351609Z","iopub.execute_input":"2021-08-15T05:59:36.351984Z","iopub.status.idle":"2021-08-15T05:59:39.407592Z","shell.execute_reply.started":"2021-08-15T05:59:36.351949Z","shell.execute_reply":"2021-08-15T05:59:39.406579Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Luego de esta operación, así es como queda nuestro data frame:","metadata":{}},{"cell_type":"code","source":"df.head()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:38.804344Z","iopub.execute_input":"2021-08-15T04:52:38.804736Z","iopub.status.idle":"2021-08-15T04:52:38.821126Z","shell.execute_reply.started":"2021-08-15T04:52:38.804695Z","shell.execute_reply":"2021-08-15T04:52:38.820003Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Visualización de imágenes de entrenamiento","metadata":{}},{"cell_type":"markdown","source":"Cada fila de nuestro data frame corresponde a una bounding box. Nótese que varias filas pueden compartir la misma image_id.\n\nSi eso ocurre es porque asociada a una misma imagen hay varias bounding boxes.\n\nHagamos el ejercicio de visualizar una imagen y dibujar todas las bounding boxes que aparecen en ella.\n\n&nbsp;\n\n**Un detalle: nosotros hemos hecho un resizing a las imágenes.** Originalmente las imágenes eran de dimensiones 1024 x 1024 x 3. **Ahora son de dimensiones 512 x 512 x 3. Hemos hecho esto para acelerar el proceso de entrenamiento.**\n\n**Para que las bounding boxes quepan en nuestras imágenes reescaladas vamos a tener que editar las columnas de nuestro data frame.**\n\n","metadata":{}},{"cell_type":"code","source":"# En primer lugar hacemos el reescalamiento de las bounding boxes.\ndf[\"x_min\"] = df[\"x_min\"].astype(np.float)*512/1024\ndf[\"y_min\"] = df[\"y_min\"].astype(np.float)*512/1024\ndf[\"h\"] = df[\"h\"].astype(np.float)*512/1024\ndf[\"w\"] = df[\"w\"].astype(np.float)*512/1024\n\n# En segundo lugar, utilizando el ancho y la altura de las bounding boxes, obtenemos\n# las coordenadas máximas de las bounding boxes tanto en el eje vertical como en el\n# eje horizontal.\ndf['x_max'] = df['x_min'] + df['w']\ndf['y_max'] = df['y_min'] + df['h']","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:47.803327Z","iopub.execute_input":"2021-08-15T05:59:47.803681Z","iopub.status.idle":"2021-08-15T05:59:47.836642Z","shell.execute_reply.started":"2021-08-15T05:59:47.803649Z","shell.execute_reply":"2021-08-15T05:59:47.83555Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A modo de explicar las nuevas columnas que hemos creado, las coordenadas de la esquina inferior derecha son $(\\text{x_max}, \\text{y_min})$.\n\nHabiendo hecho ya este reescalamiento, a continuación mostramos una imagen.  ","metadata":{}},{"cell_type":"code","source":"id = 'b6ab77fd7'\nVars = [\"x_min\", \"y_min\", \"w\", \"h\"]\nbboxes = df[df[\"image_id\"] == id].loc[:, Vars]","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:54.407892Z","iopub.execute_input":"2021-08-15T05:59:54.408258Z","iopub.status.idle":"2021-08-15T05:59:54.445815Z","shell.execute_reply.started":"2021-08-15T05:59:54.408224Z","shell.execute_reply":"2021-08-15T05:59:54.444797Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig,ax = plt.subplots(figsize=(18,10))\nfor i in range(bboxes.shape[0]):\n    img = immg.imread(path + id + '.jpg')\n    ax.imshow(img,cmap='binary')\n    row = bboxes.iloc[i].values\n    rectangle = matplotlib.patches.Rectangle((row[0], row[1]), row[2], row[3], linewidth = 2, edgecolor='orangered', facecolor='none')\n    ax.text(*row[:2], \"wheat\", verticalalignment='top', color='cyan', fontsize=13, weight='bold')\n    ax.add_patch(rectangle)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T05:59:58.339657Z","iopub.execute_input":"2021-08-15T05:59:58.340054Z","iopub.status.idle":"2021-08-15T06:00:02.475486Z","shell.execute_reply.started":"2021-08-15T05:59:58.340021Z","shell.execute_reply":"2021-08-15T06:00:02.474256Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# División entrenamiento-validación","metadata":{}},{"cell_type":"markdown","source":"En esta sección partimos los datos para conseguir un conjunto de entrenamiento y un conjunto de validación.\n\n","metadata":{}},{"cell_type":"code","source":"image_ids = df['image_id'].unique()\nval_ids = image_ids[-665:]\ntrain_ids = image_ids[:-665]\nval_df = df[df['image_id'].isin(val_ids)]\ntrain_df = df[df['image_id'].isin(train_ids)]","metadata":{"execution":{"iopub.status.busy":"2021-08-15T06:05:12.984012Z","iopub.execute_input":"2021-08-15T06:05:12.984411Z","iopub.status.idle":"2021-08-15T06:05:13.038503Z","shell.execute_reply.started":"2021-08-15T06:05:12.984376Z","shell.execute_reply":"2021-08-15T06:05:13.037549Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Dataset, DataLoader y Transforms","metadata":{}},{"cell_type":"markdown","source":"Pytorch tiene una infraestructura que permite preprocesar y dar un formato conveniente a los datos.","metadata":{}},{"cell_type":"code","source":"class WheatDetectionDataset(Dataset):\n    def __init__(self, df, IMG_DIR, transform = None):\n        self.df = df\n        self.img_dir = IMG_DIR\n        self.image_ids = self.df[\"image_id\"].unique().tolist()\n        self.transform = transform\n        \n    def __len__(self):\n      # Declaramos esta función dentro de la definición de la clase para que cuando se aplique la función len\n      # en un objeto de clase WheatDetectionDataset lo que arroje sea la cantidad de ids únicos dentro del data frame.\n        return len(self.image_ids)\n    \n    \n    def __getitem__(self, idx):\n      # Declaramos los nombres de las columnas que vamos a seleccionar.\n        coords = [\"x_min\", \"y_min\", \"x_max\", \"y_max\"]\n\n      # Declaramos un id específico, el cual es una entrada dentro de la lista de los ids únicos.  \n        image_id = self.image_ids[idx]\n\n      # Dado el id específico, filtramos las filas del data frame para recuperar solamente aquellas que coincidan con\n      # aquel id. Seleccionamos solamente las columnas que nos interesan, y retornamos este objeto como un\n      # numpy.ndarray.   \n        boxes = self.df[self.df[\"image_id\"] == image_id].loc[:, coords].values\n\n      # A partir de las coordenadas de las bounding boxes podemos calcular sus áreas.\n        area = (boxes[:, 2] - boxes[:, 0])*(boxes[:, 3] - boxes[:, 1]) \n\n      # Cargamos la imagen en su representación como un numpy.ndarray 1024 x 1024 x 3.\n        image = cv2.imread(self.img_dir+image_id+\".jpg\",cv2.IMREAD_COLOR)\n\n      # Cambiamos el formato de la imagen y le bajamos el brillo.  \n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)/255.0\n\n      # Creamos un tensor.    \n        labels = torch.ones((boxes.shape[0],), dtype=torch.int64)\n        \n      # Guardamos los objetos creados dentro de un diccionario llamado target.  \n        target = {\n                  'boxes': boxes,\n                  'labels': labels,\n                  'image_id': torch.tensor([idx]),\n                  #'image_id': image_id,\n                  'area': torch.as_tensor(area, dtype = torch.float32),\n                  'iscrowd': torch.zeros((boxes.shape[0],), dtype = torch.int64)\n                 }\n    \n        if self.transform:\n            sample = {\n                      'image': image,\n                      'bboxes': target['boxes'],\n                      'labels': labels\n                     }\n            sample = self.transform(**sample)\n            image = sample['image']\n            \n            target['boxes'] = torch.stack(tuple(map(torch.tensor, zip(*sample['bboxes'])))).permute(1, 0)\n\n        return torch.tensor(image), target, image_id","metadata":{"execution":{"iopub.status.busy":"2021-08-15T06:05:22.817534Z","iopub.execute_input":"2021-08-15T06:05:22.817926Z","iopub.status.idle":"2021-08-15T06:05:22.831092Z","shell.execute_reply.started":"2021-08-15T06:05:22.817891Z","shell.execute_reply":"2021-08-15T06:05:22.829829Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Tenemos el parámetro opcional transform, el cual nos permite aplicarle una transformación a las imágenes. **Una parte importante de la transformación consiste en convertir la representación de la imagen como un numpy.ndarray en una representación como tensor.**\n\nEn el caso de las imágenes de entrenamiento, además de representarlas como tensor también las vamos a rotar.","metadata":{}},{"cell_type":"code","source":"def get_train_transform():\n    return A.Compose([\n        A.Flip(0.5),\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})\n\ndef get_val_transform():\n    return A.Compose([\n        ToTensorV2(p=1.0)\n    ], bbox_params={'format': 'pascal_voc', 'label_fields': ['labels']})","metadata":{"execution":{"iopub.status.busy":"2021-08-15T06:05:27.667563Z","iopub.execute_input":"2021-08-15T06:05:27.667988Z","iopub.status.idle":"2021-08-15T06:05:27.67687Z","shell.execute_reply.started":"2021-08-15T06:05:27.667952Z","shell.execute_reply":"2021-08-15T06:05:27.674517Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Hagamos el ejercicio de ver una imagen de entrenamiento luego de que esta ha sido transformada.","metadata":{}},{"cell_type":"code","source":"wheat_train = WheatDetectionDataset(train_df, path, get_train_transform())","metadata":{"execution":{"iopub.status.busy":"2021-08-15T06:05:44.771342Z","iopub.execute_input":"2021-08-15T06:05:44.77172Z","iopub.status.idle":"2021-08-15T06:05:44.788722Z","shell.execute_reply.started":"2021-08-15T06:05:44.771687Z","shell.execute_reply":"2021-08-15T06:05:44.787657Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img, tar,_ = wheat_train[0]\nbbox = tar['boxes'].numpy()\nfig,ax = plt.subplots(figsize=(18,10))\nax.imshow(img.permute(1,2,0).cpu().numpy())\nfor i in range(len(bbox)):\n    box = bbox[i]\n    x,y,w,h = box[0], box[1], box[2]-box[0], box[3]-box[1]\n    rect = matplotlib.patches.Rectangle((x,y),w,h,linewidth=2,edgecolor='orangered',facecolor='none',)\n    ax.text(*box[:2], \"wheat\", verticalalignment='top', color='cyan', fontsize=13, weight='bold')\n    ax.add_patch(rect)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T06:06:23.406518Z","iopub.execute_input":"2021-08-15T06:06:23.406898Z","iopub.status.idle":"2021-08-15T06:06:24.205888Z","shell.execute_reply.started":"2021-08-15T06:06:23.406863Z","shell.execute_reply":"2021-08-15T06:06:24.20495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ahora creamos un objeto de clase WheatDetectionDataset para el conjunto de validación.","metadata":{}},{"cell_type":"code","source":"# Nótese que el tercer argumento es la transformación que le aplicamos al conjunto de validación.\n# No es la misma transformación que le habíamos aplicado al conjunto de entrenamiento.\nwheat_val = WheatDetectionDataset(val_df, path, get_val_transform())","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:43.963096Z","iopub.execute_input":"2021-08-15T04:52:43.96343Z","iopub.status.idle":"2021-08-15T04:52:43.971438Z","shell.execute_reply.started":"2021-08-15T04:52:43.963395Z","shell.execute_reply":"2021-08-15T04:52:43.97036Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Esta es una función que especifica cómo hacemos el batching.\ndef collate_fn(batch):\n    return tuple(zip(*batch))\n\nindices = torch.randperm(len(wheat_train)).tolist()\n\ntrain_data_loader = DataLoader(\n    wheat_train,\n    batch_size=8,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n)\n\nvalid_data_loader = DataLoader(\n    wheat_val,\n    batch_size=8,\n    shuffle=False,\n    num_workers=4,\n    collate_fn=collate_fn\n)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:43.9734Z","iopub.execute_input":"2021-08-15T04:52:43.973756Z","iopub.status.idle":"2021-08-15T04:52:43.983059Z","shell.execute_reply.started":"2021-08-15T04:52:43.973719Z","shell.execute_reply":"2021-08-15T04:52:43.981747Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Entrenamiento","metadata":{}},{"cell_type":"code","source":"num_classes = 2  # 1 class (wheat) + background\n\nmodel = torchvision.models.detection.fasterrcnn_resnet50_fpn(pretrained=True)\n\nin_features = model.roi_heads.box_predictor.cls_score.in_features\n\nmodel.roi_heads.box_predictor = FastRCNNPredictor(in_features, num_classes)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:43.985322Z","iopub.execute_input":"2021-08-15T04:52:43.985783Z","iopub.status.idle":"2021-08-15T04:52:44.781065Z","shell.execute_reply.started":"2021-08-15T04:52:43.985735Z","shell.execute_reply":"2021-08-15T04:52:44.780098Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.to(device)\nparams = [p for p in model.parameters() if p.requires_grad]\noptimizer = torch.optim.SGD(params, lr=0.005, momentum=0.9, weight_decay=0.0005)\nlr_scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)\n#lr_scheduler = None","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:44.782509Z","iopub.execute_input":"2021-08-15T04:52:44.782898Z","iopub.status.idle":"2021-08-15T04:52:46.772288Z","shell.execute_reply.started":"2021-08-15T04:52:44.782859Z","shell.execute_reply":"2021-08-15T04:52:46.771478Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Averager","metadata":{}},{"cell_type":"code","source":"class Averager:\n    def __init__(self):\n        self.current_total = 0.0\n        self.iterations = 0.0\n\n    def send(self, value):\n        self.current_total += value\n        self.iterations += 1\n\n    @property\n    def value(self):\n        if self.iterations == 0:\n            return 0\n        else:\n            return 1.0 * self.current_total / self.iterations\n\n    def reset(self):\n        self.current_total = 0.0\n        self.iterations = 0.0","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:46.773619Z","iopub.execute_input":"2021-08-15T04:52:46.773987Z","iopub.status.idle":"2021-08-15T04:52:46.781721Z","shell.execute_reply.started":"2021-08-15T04:52:46.773949Z","shell.execute_reply":"2021-08-15T04:52:46.779916Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"A continuación aplicamos non-maximum supression. Para mayor referencia visitar el siguiente enlace: https://pytorch.org/vision/stable/ops.html\n\n","metadata":{}},{"cell_type":"code","source":"def apply_nms(orig_prediction, iou_thresh=0.2):\n    \n\n    keep = torchvision.ops.nms(orig_prediction['boxes'], orig_prediction['scores'], iou_thresh)\n    \n    final_prediction = orig_prediction\n    final_prediction['boxes'] = final_prediction['boxes'][keep]\n    final_prediction['scores'] = final_prediction['scores'][keep]\n    final_prediction['labels'] = final_prediction['labels'][keep]\n    \n    return final_prediction","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:46.783451Z","iopub.execute_input":"2021-08-15T04:52:46.784037Z","iopub.status.idle":"2021-08-15T04:52:46.792173Z","shell.execute_reply.started":"2021-08-15T04:52:46.783978Z","shell.execute_reply":"2021-08-15T04:52:46.790936Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"num_epochs = 10","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:52:46.793752Z","iopub.execute_input":"2021-08-15T04:52:46.794419Z","iopub.status.idle":"2021-08-15T04:52:46.802515Z","shell.execute_reply.started":"2021-08-15T04:52:46.794374Z","shell.execute_reply":"2021-08-15T04:52:46.801463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"loss_hist = Averager()\nbest_epoch = 0\nmin_loss = sys.maxsize\nfor epoch in range(num_epochs):\n    loss_hist.reset()\n    tk = tqdm(train_data_loader)\n    model.train();\n    for images, targets, image_ids in tk:\n        \n        images = list(image.to(device) for image in images)\n        targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n\n        loss_dict = model(images, targets)\n\n        losses = sum(loss for loss in loss_dict.values())\n        loss_value = losses.item()\n\n        loss_hist.send(loss_value)\n\n        optimizer.zero_grad()\n        losses.backward()\n        optimizer.step()\n        \n        tk.set_postfix(train_loss=loss_value)\n    tk.close()\n    \n    # update the learning rate\n    if lr_scheduler is not None:\n        lr_scheduler.step()\n\n    print(f\"Epoch #{epoch} loss: {loss_hist.value}\") \n    \n    if loss_hist.value<min_loss:\n        print(\"Better model found at epoch {0} with {1:0.5f} loss value\".format(epoch,loss_hist.value))\n        torch.save(model.state_dict(), f\"model_state_epoch_{epoch}.pth\")\n        min_loss = loss_hist.value\n        best_epoch = epoch\n    #validation \n    model.eval();\n    with torch.no_grad():\n        tk = tqdm(valid_data_loader)\n        for images, targets, image_ids in tk:\n        \n            images = list(image.to(device) for image in images)\n            targets = [{k: v.to(device) for k, v in t.items()} for t in targets]\n            val_output = model(images)\n            val_output = [{k: v.to('cpu') for k, v in t.items()} for t in val_output]\n            IOU = []\n            for j in range(len(val_output)):\n                val_out = apply_nms(val_output[j])\n                a,b = val_out['boxes'].cpu().detach(), targets[j]['boxes'].cpu().detach()\n                chk = torchvision.ops.box_iou(a,b)\n                res = np.nanmean(chk.sum(axis=1)/(chk>0).sum(axis=1))\n                IOU.append(res)\n            tk.set_postfix(IoU=np.mean(IOU))\n        tk.close()\n        \nmodel.load_state_dict(torch.load(f\"./model_state_epoch_{best_epoch}.pth\"));","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:58:17.952211Z","iopub.execute_input":"2021-08-15T04:58:17.952573Z","iopub.status.idle":"2021-08-15T04:58:49.101927Z","shell.execute_reply.started":"2021-08-15T04:58:17.952539Z","shell.execute_reply":"2021-08-15T04:58:49.09953Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.load_state_dict(torch.load(f\"./model_state_epoch_{best_epoch}.pth\"));","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.194444Z","iopub.status.idle":"2021-08-15T04:56:29.195246Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Validación y predicción","metadata":{}},{"cell_type":"code","source":"img,target,_ = wheat_val[50]\n\nmodel.eval()\nwith torch.no_grad():\n    prediction = model([img.to(device)])[0]\n    \nprint('predicted #boxes: ', len(prediction['boxes']))\nprint('real #boxes: ', len(target['boxes']))","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.196992Z","iopub.status.idle":"2021-08-15T04:56:29.197801Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Ground truths","metadata":{}},{"cell_type":"code","source":"bbox = target['boxes'].numpy()\nfig,ax = plt.subplots(1,figsize=(18,10))\nax.imshow(img.permute(1,2,0).cpu().numpy())\nfor i in range(len(bbox)):\n    box = bbox[i]\n    x,y,w,h = box[0], box[1], box[2]-box[0], box[3]-box[1]\n    rect = matplotlib.patches.Rectangle((x,y),w,h,linewidth=2,edgecolor='r',facecolor='none',)\n    ax.text(*box[:2], \"wheat\", verticalalignment='top', color='red', fontsize=13, weight='bold')\n    ax.add_patch(rect)\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.199334Z","iopub.status.idle":"2021-08-15T04:56:29.200069Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def plot_valid(img,prediction,nms=True,detect_thresh=0.5):\n    fig,ax = plt.subplots(figsize=(18,10))\n    val_img = img.permute(1,2,0).cpu().numpy()\n    ax.imshow(val_img)\n    nms_prediction = apply_nms(prediction, iou_thresh=0.2) if nms else prediction\n    val_scores = nms_prediction['scores'].cpu().detach().numpy()\n    bbox = nms_prediction['boxes'].cpu().detach().numpy()\n    for i in range(len(bbox)):\n        if val_scores[i]>=detect_thresh:\n            box = bbox[i]\n            x,y,w,h = box[0], box[1], box[2]-box[0], box[3]-box[1]\n            rect = matplotlib.patches.Rectangle((x,y),w,h,linewidth=2 ,edgecolor='r',facecolor='none',)\n            ax.text(*box[:2], \"wheat {0:.3f}\".format(val_scores[i]), verticalalignment='top', color='white', fontsize=12, weight='bold')\n            ax.add_patch(rect)\n    plt.show()","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.20152Z","iopub.status.idle":"2021-08-15T04:56:29.202392Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predicciones en el conjunto de validación","metadata":{}},{"cell_type":"code","source":"plot_valid(img,prediction)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.203842Z","iopub.status.idle":"2021-08-15T04:56:29.204674Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predicciones en el conjunto de test","metadata":{}},{"cell_type":"code","source":"submission = pd.read_csv('../input/global-wheat-detection/sample_submission.csv')","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.206099Z","iopub.status.idle":"2021-08-15T04:56:29.206885Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class TestDataset(object):\n    def __init__(self, df, IMG_DIR, transforms=None):\n        # select only those classes that have boxes\n        \n        self.df = df\n        self.img_dir = IMG_DIR\n        self.transforms = transforms\n        self.image_ids = self.df['image_id'].tolist()\n        \n    def __len__(self):\n        return len(self.image_ids)\n    \n    def __getitem__(self, idx):\n        \n        image_id = self.image_ids[idx]\n        image = cv2.imread(self.img_dir+image_id+\".jpg\",cv2.IMREAD_COLOR)\n        image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB).astype(np.float32)\n        \n        \n        if self.transforms:\n            sample = {\n                'image': image,\n            }\n            sample = self.transforms(**sample)\n            image = sample['image']\n\n        return image, image_id","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.208424Z","iopub.status.idle":"2021-08-15T04:56:29.209181Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_test_transform(IMG_SIZE=(512,512)):\n    return A.Compose([\n         A.Normalize(mean=(0, 0, 0), std=(1, 1, 1), max_pixel_value=255.0, p=1.0),\n        A.Resize(*IMG_SIZE),\n        ToTensorV2(p=1.0)\n    ])","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.210499Z","iopub.status.idle":"2021-08-15T04:56:29.211213Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Acá ponemos la ruta del conjunto test.\ntest_img_dir = '../input/global-wheat-detection/test/'","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.212479Z","iopub.status.idle":"2021-08-15T04:56:29.213185Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"IMG_SIZE = (512,512)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.214535Z","iopub.status.idle":"2021-08-15T04:56:29.215261Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_dataset = TestDataset(submission, test_img_dir ,get_test_transform(IMG_SIZE))","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.216818Z","iopub.status.idle":"2021-08-15T04:56:29.217601Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for j in range(submission.shape[0]):\n    img,_ = test_dataset[j]\n    # put the model in evaluation mode\n    model.eval()\n    with torch.no_grad():\n        prediction = model([img.to(device)])[0]\n    plot_valid(img,prediction)","metadata":{"execution":{"iopub.status.busy":"2021-08-15T04:56:29.219239Z","iopub.status.idle":"2021-08-15T04:56:29.220031Z"},"trusted":true},"execution_count":null,"outputs":[]}]}