{"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":"markdown","source":"A high level EDA --> dataloaders --> model --> loss function --> training loop","metadata":{}},{"cell_type":"code","source":"from fastai.vision.all import *\nfrom torch.utils.data import Dataset, DataLoader\nimport cv2\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:23.258997Z","iopub.execute_input":"2021-10-16T00:18:23.259298Z","iopub.status.idle":"2021-10-16T00:18:27.476449Z","shell.execute_reply.started":"2021-10-16T00:18:23.259220Z","shell.execute_reply":"2021-10-16T00:18:27.475734Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"path = Path('../input/sartorius-cell-instance-segmentation')\nfiles = get_image_files(path/'train')\nfiles","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:27.478595Z","iopub.execute_input":"2021-10-16T00:18:27.478905Z","iopub.status.idle":"2021-10-16T00:18:27.882252Z","shell.execute_reply.started":"2021-10-16T00:18:27.478867Z","shell.execute_reply":"2021-10-16T00:18:27.881596Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's quickly take a look at what's been provided for our training data\n\nSo we have 606 images to use for training and validation of our models\n\nWe can easily plot one of these image files as seen below","metadata":{}},{"cell_type":"code","source":"Image.open(files[0]).resize((256,256))","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:27.885489Z","iopub.execute_input":"2021-10-16T00:18:27.885696Z","iopub.status.idle":"2021-10-16T00:18:27.965792Z","shell.execute_reply.started":"2021-10-16T00:18:27.885662Z","shell.execute_reply":"2021-10-16T00:18:27.965163Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Since this is a segmentation problem, we'll need to find the reference annotations -- these are in our train.csv file","metadata":{}},{"cell_type":"code","source":"df = pd.read_csv(path/'train.csv')\ndf.shape","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:27.967814Z","iopub.execute_input":"2021-10-16T00:18:27.968176Z","iopub.status.idle":"2021-10-16T00:18:28.583144Z","shell.execute_reply.started":"2021-10-16T00:18:27.968143Z","shell.execute_reply":"2021-10-16T00:18:28.582465Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df.head(1)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.586428Z","iopub.execute_input":"2021-10-16T00:18:28.586624Z","iopub.status.idle":"2021-10-16T00:18:28.608724Z","shell.execute_reply.started":"2021-10-16T00:18:28.586598Z","shell.execute_reply":"2021-10-16T00:18:28.608156Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Looks like the annotation column has our segmentation masks -- \"run length encoded pixels\". What's that mean?","metadata":{}},{"cell_type":"code","source":"df.width.unique(), df.height.unique()","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.611889Z","iopub.execute_input":"2021-10-16T00:18:28.612075Z","iopub.status.idle":"2021-10-16T00:18:28.628418Z","shell.execute_reply.started":"2021-10-16T00:18:28.612053Z","shell.execute_reply":"2021-10-16T00:18:28.627804Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Looks like all of our images are standardized to one size -- 704 pixels wide and 520 pixels in height\n\nIn addition, we have information on the following:<br>\n1) cell_type: the cell line <br>\n2) plate_time: the time the plate was created <br>\n3) sample_date: the date the sample was created <br>\n4) Sample_id: sample identifier -- not sure what the utility of having this is <br>\n5) elapsed_timedelta: time since first image taken of smaple<br>\n\n***None of the metadata is provided for test set***","metadata":{}},{"cell_type":"code","source":"#df.cell_type.unique()\n#df.plate_time.unique()\n#train_df.sample_date.unique()\n#len(train_df.sample_id.unique())\n#train_df.elapsed_timedelta.unique()","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.629322Z","iopub.execute_input":"2021-10-16T00:18:28.629502Z","iopub.status.idle":"2021-10-16T00:18:28.638212Z","shell.execute_reply.started":"2021-10-16T00:18:28.629479Z","shell.execute_reply":"2021-10-16T00:18:28.637446Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_image(path):\n    image = np.array(Image.open(path))\n    image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB)\n    return image\n\ndef get_annot(img_id):\n    return df[df.id == img_id].annotation.values\n\ndef get_mask(img_annotations, colors):\n    mask = np.zeros((502, 704, 3))\n    for annot in img_annotations:\n        mask += rle_decode(annot, shape=(502, 704, 3), color=colors)\n    mask = mask.clip(0, 1)\n    return mask","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.639122Z","iopub.execute_input":"2021-10-16T00:18:28.639292Z","iopub.status.idle":"2021-10-16T00:18:28.646637Z","shell.execute_reply.started":"2021-10-16T00:18:28.639271Z","shell.execute_reply":"2021-10-16T00:18:28.645930Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#https://www.kaggle.com/inversion/run-length-decoding-quick-start\ndef rle_decode(mask_rle, shape, color=1):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (height,width) of array to return \n    Returns numpy array, 1 - mask, 0 - background\n\n    '''\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros((shape[0] * shape[1], shape[2]), dtype=np.float32)\n    for lo, hi in zip(starts, ends):\n        img[lo : hi] = color\n    return img.reshape(shape)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.648140Z","iopub.execute_input":"2021-10-16T00:18:28.648443Z","iopub.status.idle":"2021-10-16T00:18:28.657064Z","shell.execute_reply.started":"2021-10-16T00:18:28.648409Z","shell.execute_reply":"2021-10-16T00:18:28.656223Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"colors = np.random.rand(3)\ncolors","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.661608Z","iopub.execute_input":"2021-10-16T00:18:28.662094Z","iopub.status.idle":"2021-10-16T00:18:28.667602Z","shell.execute_reply.started":"2021-10-16T00:18:28.662060Z","shell.execute_reply":"2021-10-16T00:18:28.666907Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.imshow(get_mask(get_annot(files[0].name[:-4]), colors));","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:28.668744Z","iopub.execute_input":"2021-10-16T00:18:28.669508Z","iopub.status.idle":"2021-10-16T00:18:29.592615Z","shell.execute_reply.started":"2021-10-16T00:18:28.669473Z","shell.execute_reply":"2021-10-16T00:18:29.591831Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Lets build our dataset object with the functions shown above -- we're not going to want to do that mask calculation everytime we want a sample in our real training loop, but it will serve it's purpose for now. It will be better to extract the masks once and same them as their own files. This will reduce the training time per epoch... allowing more time for trying out different ideas","metadata":{}},{"cell_type":"code","source":"class masked_ds(Dataset):\n    def __init__(self, files, df, colors, \n                 img_transforms=None, mask_transforms=None):\n        self.files = files\n        self.df = df\n        self.colors = colors\n        self.img_transforms = img_transforms\n        self.mask_transforms = mask_transforms\n    def __len__(self):\n        return len(self.files)\n    def __getitem__(self, idx):\n        img_path = self.files[idx]\n        name = img_path.name[:-4]\n        annotations = get_annot(name)\n        mask = get_mask(annotations, self.colors)\n        img = get_image(img_path)\n        if(self.img_transforms):\n            img = self.img_transforms(image=img)['image'].float()\n        if(self.mask_transforms):\n            mask = self.mask_transforms(image=mask)['image'].float()\n        return img, mask","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:29.593629Z","iopub.execute_input":"2021-10-16T00:18:29.593967Z","iopub.status.idle":"2021-10-16T00:18:29.604517Z","shell.execute_reply.started":"2021-10-16T00:18:29.593926Z","shell.execute_reply":"2021-10-16T00:18:29.603669Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img_transforms = A.Compose([\n                A.RandomResizedCrop(448, 448),\n                A.Normalize(\n                    mean=[0.485, 0.456, 0.406],\n                    std=[0.229, 0.224, 0.225]),\n                ToTensorV2()\n              ])\n\nmask_transforms = A.Compose([\n                A.RandomResizedCrop(448, 448),\n                ToTensorV2()\n              ])","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:29.606108Z","iopub.execute_input":"2021-10-16T00:18:29.606503Z","iopub.status.idle":"2021-10-16T00:18:29.618887Z","shell.execute_reply.started":"2021-10-16T00:18:29.606338Z","shell.execute_reply":"2021-10-16T00:18:29.617830Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"split = int(len(files) * 0.8)\ntrain_files = files[:split]\nvalid_files = files[split:]\ntrain_ds = masked_ds(train_files, df, colors, img_transforms, mask_transforms)\nvalid_ds = masked_ds(valid_files, df, colors, img_transforms, mask_transforms)\ntrain_dl = DataLoader(train_ds, batch_size=8, shuffle=True, num_workers=2, pin_memory=True)\nvalid_dl = DataLoader(valid_ds, batch_size=8, shuffle=False, num_workers=2, pin_memory=True)\ndls = DataLoaders(train_dl, valid_dl)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:29.620483Z","iopub.execute_input":"2021-10-16T00:18:29.620946Z","iopub.status.idle":"2021-10-16T00:18:29.630987Z","shell.execute_reply.started":"2021-10-16T00:18:29.620906Z","shell.execute_reply":"2021-10-16T00:18:29.630210Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"batch = next(iter(dls.train))\nbatch[0].shape, batch[1].shape","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:29.632472Z","iopub.execute_input":"2021-10-16T00:18:29.632775Z","iopub.status.idle":"2021-10-16T00:18:39.064267Z","shell.execute_reply.started":"2021-10-16T00:18:29.632740Z","shell.execute_reply":"2021-10-16T00:18:39.063441Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Model, loss function etc brought to you by @lafoss: https://www.kaggle.com/iafoss/hubmap-pytorch-fast-ai-starter?scriptVersionId=56448549","metadata":{}},{"cell_type":"code","source":"class FPN(nn.Module):\n    def __init__(self, input_channels:list, output_channels:list):\n        super().__init__()\n        self.convs = nn.ModuleList(\n            [nn.Sequential(nn.Conv2d(in_ch, out_ch*2, kernel_size=3, padding=1),\n             nn.ReLU(inplace=True), nn.BatchNorm2d(out_ch*2),\n             nn.Conv2d(out_ch*2, out_ch, kernel_size=3, padding=1))\n            for in_ch, out_ch in zip(input_channels, output_channels)])\n        \n    def forward(self, xs:list, last_layer):\n        hcs = [F.interpolate(c(x),scale_factor=2**(len(self.convs)-i),mode='bilinear') \n               for i,(c,x) in enumerate(zip(self.convs, xs))]\n        hcs.append(last_layer)\n        return torch.cat(hcs, dim=1)\n\nclass UnetBlock(Module):\n    def __init__(self, up_in_c:int, x_in_c:int, nf:int=None, blur:bool=False,\n                 self_attention:bool=False, **kwargs):\n        super().__init__()\n        self.shuf = PixelShuffle_ICNR(up_in_c, up_in_c//2, blur=blur, **kwargs)\n        self.bn = nn.BatchNorm2d(x_in_c)\n        ni = up_in_c//2 + x_in_c\n        nf = nf if nf is not None else max(up_in_c//2,32)\n        self.conv1 = ConvLayer(ni, nf, norm_type=None, **kwargs)\n        self.conv2 = ConvLayer(nf, nf, norm_type=None,\n            xtra=SelfAttention(nf) if self_attention else None, **kwargs)\n        self.relu = nn.ReLU(inplace=True)\n\n    def forward(self, up_in:Tensor, left_in:Tensor) -> Tensor:\n        s = left_in\n        up_out = self.shuf(up_in)\n        cat_x = self.relu(torch.cat([up_out, self.bn(s)], dim=1))\n        return self.conv2(self.conv1(cat_x))\n        \nclass _ASPPModule(nn.Module):\n    def __init__(self, inplanes, planes, kernel_size, padding, dilation, groups=1):\n        super().__init__()\n        self.atrous_conv = nn.Conv2d(inplanes, planes, kernel_size=kernel_size,\n                stride=1, padding=padding, dilation=dilation, bias=False, groups=groups)\n        self.bn = nn.BatchNorm2d(planes)\n        self.relu = nn.ReLU()\n\n        self._init_weight()\n\n    def forward(self, x):\n        x = self.atrous_conv(x)\n        x = self.bn(x)\n\n        return self.relu(x)\n\n    def _init_weight(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.kaiming_normal_(m.weight)\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n\nclass ASPP(nn.Module):\n    def __init__(self, inplanes=512, mid_c=256, dilations=[6, 12, 18, 24], out_c=None):\n        super().__init__()\n        self.aspps = [_ASPPModule(inplanes, mid_c, 1, padding=0, dilation=1)] + \\\n            [_ASPPModule(inplanes, mid_c, 3, padding=d, dilation=d,groups=4) for d in dilations]\n        self.aspps = nn.ModuleList(self.aspps)\n        self.global_pool = nn.Sequential(nn.AdaptiveMaxPool2d((1, 1)),\n                        nn.Conv2d(inplanes, mid_c, 1, stride=1, bias=False),\n                        nn.BatchNorm2d(mid_c), nn.ReLU())\n        out_c = out_c if out_c is not None else mid_c\n        self.out_conv = nn.Sequential(nn.Conv2d(mid_c*(2+len(dilations)), out_c, 1, bias=False),\n                                    nn.BatchNorm2d(out_c), nn.ReLU(inplace=True))\n        self.conv1 = nn.Conv2d(mid_c*(2+len(dilations)), out_c, 1, bias=False)\n        self._init_weight()\n\n    def forward(self, x):\n        x0 = self.global_pool(x)\n        xs = [aspp(x) for aspp in self.aspps]\n        x0 = F.interpolate(x0, size=xs[0].size()[2:], mode='bilinear', align_corners=True)\n        x = torch.cat([x0] + xs, dim=1)\n        return self.out_conv(x)\n    \n    def _init_weight(self):\n        for m in self.modules():\n            if isinstance(m, nn.Conv2d):\n                torch.nn.init.kaiming_normal_(m.weight)\n            elif isinstance(m, nn.BatchNorm2d):\n                m.weight.data.fill_(1)\n                m.bias.data.zero_()\n\nclass UneXt50(nn.Module):\n    def __init__(self, stride=1, **kwargs):\n        super().__init__()\n        #encoder\n        m = torch.hub.load('facebookresearch/semi-supervised-ImageNet1K-models',\n                           'resnext50_32x4d_ssl')\n        self.enc0 = nn.Sequential(m.conv1, m.bn1, nn.ReLU(inplace=True))\n        self.enc1 = nn.Sequential(nn.MaxPool2d(kernel_size=3, stride=2, padding=1, dilation=1),\n                            m.layer1) #256\n        self.enc2 = m.layer2 #512\n        self.enc3 = m.layer3 #1024\n        self.enc4 = m.layer4 #2048\n        #aspp with customized dilatations\n        self.aspp = ASPP(2048,256,out_c=512,dilations=[stride*1,stride*2,stride*3,stride*4])\n        self.drop_aspp = nn.Dropout2d(0.5)\n        #decoder\n        self.dec4 = UnetBlock(512,1024,256)\n        self.dec3 = UnetBlock(256,512,128)\n        self.dec2 = UnetBlock(128,256,64)\n        self.dec1 = UnetBlock(64,64,32)\n        self.fpn = FPN([512,256,128,64],[16]*4)\n        self.drop = nn.Dropout2d(0.1)\n        self.final_conv = ConvLayer(32+16*4, 3, ks=1, norm_type=None, act_cls=None)\n        \n    def forward(self, x):\n        enc0 = self.enc0(x)\n        enc1 = self.enc1(enc0)\n        enc2 = self.enc2(enc1)\n        enc3 = self.enc3(enc2)\n        enc4 = self.enc4(enc3)\n        enc5 = self.aspp(enc4)\n        dec3 = self.dec4(self.drop_aspp(enc5),enc3)\n        dec2 = self.dec3(dec3,enc2)\n        dec1 = self.dec2(dec2,enc1)\n        dec0 = self.dec1(dec1,enc0)\n        x = self.fpn([enc5, dec3, dec2, dec1], dec0)\n        x = self.final_conv(self.drop(x))\n        x = F.interpolate(x,scale_factor=2,mode='bilinear')\n        return x\n\n#split the model to encoder and decoder for fast.ai\nsplit_layers = lambda m: [list(m.enc0.parameters())+list(m.enc1.parameters())+\n                list(m.enc2.parameters())+list(m.enc3.parameters())+\n                list(m.enc4.parameters()),\n                list(m.aspp.parameters())+list(m.dec4.parameters())+\n                list(m.dec3.parameters())+list(m.dec2.parameters())+\n                list(m.dec1.parameters())+list(m.fpn.parameters())+\n                list(m.final_conv.parameters())]","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:39.066124Z","iopub.execute_input":"2021-10-16T00:18:39.066632Z","iopub.status.idle":"2021-10-16T00:18:39.106006Z","shell.execute_reply.started":"2021-10-16T00:18:39.066589Z","shell.execute_reply":"2021-10-16T00:18:39.105281Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = UneXt50()","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:39.107377Z","iopub.execute_input":"2021-10-16T00:18:39.107663Z","iopub.status.idle":"2021-10-16T00:18:45.431385Z","shell.execute_reply.started":"2021-10-16T00:18:39.107624Z","shell.execute_reply":"2021-10-16T00:18:45.430670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"out = model(batch[0])\nout.shape","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:45.432552Z","iopub.execute_input":"2021-10-16T00:18:45.435149Z","iopub.status.idle":"2021-10-16T00:18:56.311404Z","shell.execute_reply.started":"2021-10-16T00:18:45.435115Z","shell.execute_reply":"2021-10-16T00:18:56.310504Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plt.imshow(out.detach().cpu().numpy()[0].transpose(1,2,0));","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.312940Z","iopub.execute_input":"2021-10-16T00:18:56.313212Z","iopub.status.idle":"2021-10-16T00:18:56.589839Z","shell.execute_reply.started":"2021-10-16T00:18:56.313177Z","shell.execute_reply":"2021-10-16T00:18:56.589141Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"At least it looks cool?","metadata":{}},{"cell_type":"code","source":"plt.imshow(batch[1][0].cpu().numpy().transpose(1,2,0));","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.591248Z","iopub.execute_input":"2021-10-16T00:18:56.591515Z","iopub.status.idle":"2021-10-16T00:18:56.827111Z","shell.execute_reply.started":"2021-10-16T00:18:56.591478Z","shell.execute_reply":"2021-10-16T00:18:56.826380Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Just a reminder of what our label mask should look like","metadata":{}},{"cell_type":"code","source":"def dice_loss(inp, target):\n    inp = torch.sigmoid(inp)\n    smooth = 1.0\n\n    iflat = inp.view(-1)\n    tflat = target.view(-1)\n    intersection = (iflat * tflat).sum()\n    \n    return ((2.0 * intersection + smooth) / (iflat.sum() + tflat.sum() + smooth))\n\ndef IoU(pred, targs):\n    pred = (pred>0).float()\n    intersection = (pred*targs).sum()\n    return intersection / ((pred+targs).sum() - intersection + 1.0)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.828456Z","iopub.execute_input":"2021-10-16T00:18:56.828876Z","iopub.status.idle":"2021-10-16T00:18:56.836574Z","shell.execute_reply.started":"2021-10-16T00:18:56.828837Z","shell.execute_reply":"2021-10-16T00:18:56.835918Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Let's just double check that the dice_loss works from lafoss, no idea if this is the appropriate one to use at the moment, but it can serve the purpose of confirming our pipelines feasibility","metadata":{}},{"cell_type":"code","source":"dice_loss(out,batch[1])","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.837840Z","iopub.execute_input":"2021-10-16T00:18:56.838215Z","iopub.status.idle":"2021-10-16T00:18:56.878638Z","shell.execute_reply.started":"2021-10-16T00:18:56.838176Z","shell.execute_reply":"2021-10-16T00:18:56.877854Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learner = Learner(dls, model, loss_func=dice_loss, metrics=IoU)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.880153Z","iopub.execute_input":"2021-10-16T00:18:56.880416Z","iopub.status.idle":"2021-10-16T00:18:56.886115Z","shell.execute_reply.started":"2021-10-16T00:18:56.880380Z","shell.execute_reply":"2021-10-16T00:18:56.885263Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"learner.fit_one_cycle(3)","metadata":{"execution":{"iopub.status.busy":"2021-10-16T00:18:56.890071Z","iopub.execute_input":"2021-10-16T00:18:56.890395Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Ok, so looks like that isn't working too great. But that's not a problem, we have the starting point to go from data to training and this should allow us to try out different ideas moving forward.\n\nThis whole thing needs a ton of more work.... stay tuned","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}