How to build a Siamese Network with fastai-style code

I am trying to build a Siamese Network for Humpback Whale Identification simulating Alex Fitts’s work.
I have rewritten his code for his fastai version is a little bit old ( v1.0.39 ) and not compatible with the latest version so far ( v1.0.51 ).
But when I have just defined my custom dataset, trying to load the data, an error occured:

class SiamImage(ItemBase):
    def __init__(self, img1, img2):
        self.img1, self.img2 = img1, img2
        self.obj, = (img1, img2), [,]
    def apply_tfms(self, tfms, *args, **kwargs):
        self.img1 = self.img1.apply_tfms(tfms, *args, **kwargs)
        self.img2 = self.img2.apply_tfms(tfms, *args, **kwargs) = [,]
        return self
    def __repr__(self):
        return f'{self.__class__.__name__} {self.img1.shape, self.img2.shape}'
    def to_one(self):
        return Image(, dim=2))

class SiamImageList(ImageList):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def __len__(self):
        return len(self.items)

    def get(self, i):
        # 0 for match and 1 for dismatch
        target = (i >= len(self.items)//2)
        img1 = super().get(i)

        whaleImgs = self.inner_df.Image.values
        whaleIds = self.inner_df.Id.values
        img1Id = whaleIds[i]
        img2Name = ''
        if target == 0:
            img2Name = np.random.choice(whaleImgs[whaleIds == img1Id])
            img2Name = np.random.choice(whaleImgs[whaleIds != img1Id])
        img2 = super().open(self.path/img2Name)
        return SiamImage(img1, img2)

    def reconstruct(self, t):
        return SiamImage(t[0], t[1])

    def show_xys(self, xs, ys, figsize:Tuple[int,int]=(9,5), **kwargs):
        rows = int(math.sqrt(len(xs)))
        fig, axs = plt.subplots(rows,rows,figsize=figsize)
        for i, ax in enumerate(axs.flatten() if rows > 1 else [axs]):
            xs[i].to_one().show(ax=ax, y=ys[i], **kwargs)
data = (SiamImageList.from_df(df=train, path='../input/train', cols='Image')
        .transform(get_transforms(), size=224)

AttributeError                            Traceback (most recent call last)
<ipython-input-14-53e0711f0b1c> in <module>()
     41         .normalize(imagenet_stats))
---> 43 data.show_batch()

/opt/conda/lib/python3.6/site-packages/fastai/ in show_batch(self, rows, ds_type, reverse, **kwargs)
    183     def show_batch(self, rows:int=5, ds_type:DatasetType=DatasetType.Train, reverse:bool=False, **kwargs)->None:
    184         "Show a batch of data in `ds_type` on a few `rows`."
--> 185         x,y = self.one_batch(ds_type, True, True)
    186         if reverse: x,y = x.flip(0),y.flip(0)
    187         n_items = rows **2 if self.train_ds.x._square_show else rows

/opt/conda/lib/python3.6/site-packages/fastai/ in one_batch(self, ds_type, detach, denorm, cpu)
    166         w = self.num_workers
    167         self.num_workers = 0
--> 168         try:     x,y = next(iter(dl))
    169         finally: self.num_workers = w
    170         if detach: x,y = to_detach(x,cpu=cpu),to_detach(y,cpu=cpu)

/opt/conda/lib/python3.6/site-packages/fastai/ in __iter__(self)
     73     def __iter__(self):
     74         "Process and returns items from `DataLoader`."
---> 75         for b in self.dl: yield self.proc_batch(b)
     77     @classmethod

/opt/conda/lib/python3.6/site-packages/fastai/ in proc_batch(self, b)
     68         "Process batch `b` of `TensorImage`."
     69         b = to_device(b, self.device)
---> 70         for f in listify(self.tfms): b = f(b)
     71         return b

/opt/conda/lib/python3.6/site-packages/fastai/vision/ in _normalize_batch(b, mean, std, do_x, do_y)
     64     "`b` = `x`,`y` - normalize `x` array of imgs and `do_y` optionally `y`."
     65     x,y = b
---> 66     mean,std =,
     67     if do_x: x = normalize(x,mean,std)
     68     if do_y and len(y.shape) == 4: y = normalize(y,mean,std)

AttributeError: 'list' object has no attribute 'device'

What’s wrong with my code? My full code can be found on gist.

The reason is that the data property of your result in apply_tfms is not a torch.Tensor.

    def apply_tfms(self, tfms, *args, **kwargs):
        self.img1 = self.img1.apply_tfms(tfms, *args, **kwargs)
        self.img2 = self.img2.apply_tfms(tfms, *args, **kwargs) = [,] # This should be a tensor...
        return self

Take a look at @baz great thread on that:

From his code:

    def apply_tfms(self, tfms):
        for tfm in tfms:
   = torch.stack([tfm(x) for x in])
        return self