Prediction of a scalar with a CNN

Edit: @sgugger pointed out that I needed to call MSELossFlat (I was missing the parens). You can skip to post #18 for the working Dataset.


I updated fastai (I updated the code every couple days, but not every single day) and now neither @wyquek’s code nor yours will run for me. With the same architecture as above, I now get an error about RuntimeError: bool value of Tensor with more than one value is ambiguous:

---------------------------------------------------------------------------
RuntimeError                              Traceback (most recent call last)
<ipython-input-60-ec98ea40e834> in <module>()
      5 #                      loss_func=data.loss_func,
      6                      callback_fns=ShowGraph)
----> 7 learn2.lr_find(start_lr=1e-7, end_lr=100)
      8 learn2.recorder.plot()

/app/fastai/fastai/train.py in lr_find(learn, start_lr, end_lr, num_it, **kwargs)
     25     cb = LRFinder(learn, start_lr, end_lr, num_it)
     26     a = int(np.ceil(num_it/len(learn.data.train_dl)))
---> 27     learn.fit(a, start_lr, callbacks=[cb], **kwargs)
     28 
     29 def to_fp16(learn:Learner, loss_scale:float=512., flat_master:bool=False)->Learner:

/app/fastai/fastai/basic_train.py in fit(self, epochs, lr, wd, callbacks)
    135         callbacks = [cb(self) for cb in self.callback_fns] + listify(callbacks)
    136         fit(epochs, self.model, self.loss_func, opt=self.opt, data=self.data, metrics=self.metrics,
--> 137             callbacks=self.callbacks+callbacks)
    138 
    139     def create_opt(self, lr:Floats, wd:Floats=0.)->None:

/app/fastai/fastai/basic_train.py in fit(epochs, model, loss_func, opt, data, callbacks, metrics)
     87     except Exception as e:
     88         exception = e
---> 89         raise e
     90     finally: cb_handler.on_train_end(exception)
     91 

/app/fastai/fastai/basic_train.py in fit(epochs, model, loss_func, opt, data, callbacks, metrics)
     77             for xb,yb in progress_bar(data.train_dl, parent=pbar):
     78                 xb, yb = cb_handler.on_batch_begin(xb, yb)
---> 79                 loss = loss_batch(model, xb, yb, loss_func, opt, cb_handler)[0]
     80                 if cb_handler.on_batch_end(loss): break
     81 

/app/fastai/fastai/basic_train.py in loss_batch(model, xb, yb, loss_func, opt, cb_handler)
     20 
     21     if not loss_func: return to_detach(out), yb[0].detach()
---> 22     loss = loss_func(out, *yb)
     23 
     24     if opt is not None:

/usr/local/lib/python3.6/dist-packages/torch/nn/modules/loss.py in __init__(self, size_average, reduce, reduction)
    419     """
    420     def __init__(self, size_average=None, reduce=None, reduction='elementwise_mean'):
--> 421         super(MSELoss, self).__init__(size_average, reduce, reduction)
    422 
    423     def forward(self, input, target):

/usr/local/lib/python3.6/dist-packages/torch/nn/modules/loss.py in __init__(self, size_average, reduce, reduction)
     13         super(_Loss, self).__init__()
     14         if size_average is not None or reduce is not None:
---> 15             self.reduction = _Reduction.legacy_get_string(size_average, reduce)
     16         else:
     17             self.reduction = reduction

/usr/local/lib/python3.6/dist-packages/torch/nn/functional.py in legacy_get_string(size_average, reduce, emit_warning)
     45             reduce = True
     46 
---> 47         if size_average and reduce:
     48             ret = 'elementwise_mean'
     49         elif reduce:

RuntimeError: bool value of Tensor with more than one value is ambiguous

I haven’t changed my Dataset (except to experiment with MSELossFlat) from the original, which is:

class ImageScalarDataset(ImageDataset):
    def __init__(self, df:DataFrame, path_column:str='file_path', dependent_variable:str=None):
        super().__init__(df[path_column], np.array(df[dependent_variable], dtype=np.float32))
        self.loss_func = layers.MSELossFlat
        self.classes = [0]

    def __len__(self)->int:
        return len(self.y)
    
    def __getitem__(self, i):
        # return x, y | where x is an image, and y is the scalar
        return open_image(self.x[i]), self.y[i]

On the plus side, I get the same error whether I use @jeremy’s or @wyquek’s loss function, so I suspect there was some other internal change that my Dataset is not handling correctly. Perhaps something about how I am manually setting self.classes = [0]?

Can you do a %debug and print the value of yb when you go up three times (in the line loss = loss_func(out, *yb). It seems from the rest of the error message that you may have a target that is a list.

> /app/fastai/fastai/basic_train.py(22)loss_batch()
     20 
     21     if not loss_func: return to_detach(out), yb[0].detach()
---> 22     loss = loss_func(out, *yb)
     23 
     24     if opt is not None:

ipdb> yb
[tensor([0., 0., 0., 0., 0., 0., 0., 1., 0., 0., 0., 1., 0., 1., 0., 0., 0., 1.,
        1., 1., 0., 1., 0., 1., 0., 0., 0., 1., 1., 1., 1., 0., 1., 0., 1., 1.,
        1., 1., 0., 1., 1., 1., 1., 0., 1., 0., 1., 1., 1., 1., 0., 1., 0., 0.,
        1., 1., 1., 1., 1., 1., 0., 1., 0., 1., 1., 0., 1., 1., 1., 0., 0., 0.,
        0., 1., 1., 0., 0., 1., 1., 1., 1., 1., 1., 1., 1., 1., 0., 1., 0., 0.,
        0., 1., 0., 1., 1., 1., 1., 1., 0., 0., 0., 0., 0., 1., 1., 0., 1., 1.,
        1., 0., 1., 0., 0., 1., 0., 0., 0., 0., 1., 1., 1., 0., 1., 1., 0., 0.,
        1., 0.], device='cuda:0')]
ipdb> type(yb)
<class 'list'>

Hmm, yes indeed. But my Dataset is feeding this to the super() class:

super().__init__(df[path_column], np.array(df[dependent_variable], dtype=np.float32))

Where the righthand argument is:

np.array(merged[trait], dtype=np.float32).shape

Which looks like:

(18906,)

No it’s normal (there’s only one element, I thought it would be more).
Ah, I think I understand your problem: did you put loss_func = MSELoss() (with parenthesis)?

2 Likes

Oh. No, I did not!

Welp, that fixed it. Thank you. Updated code, internal notes-to-self and all:

class ImageScalarDataset(ImageDataset):
    def __init__(self, df:DataFrame, path_column:str='file_path', dependent_variable:str=None):
        
        # The superclass does nice things for us like tensorizing the numpy
        # input
        super().__init__(df[path_column], np.array(df[dependent_variable], dtype=np.float32))

        # Old FastAI uses loss_fn, new FastAI uses loss_func
        self.loss_func = layers.MSELossFlat()
        self.loss_fn = self.loss_func

        # We have only one "class" (i.e., the single output scalar)
        self.classes = [0]

    def __len__(self)->int:
        return len(self.y)
    
    def __getitem__(self, i):
        # return x, y | where x is an image, and y is the scalar
        return open_image(self.x[i]), self.y[i]
1 Like

Hi - I have a similar use case to yours. I want to classify the age of a photo and have processed the data to have the age number as the filename. My approach was to build the image data bunch and just use a different loss function (MSELoss rather than Cross Entropy) and hoped that would do it but ran into issues:

data = (ImageFileList.from_folder(path_img)
        .label_from_func(get_float_labels)
        .random_split_by_pct(valid_pct=0.2)
        .datasets()
        .transform(get_transforms(), size=224)
        .databunch(bs=bs).normalize(imagenet_stats)
       )

My classes look like this

class MSELossFlat2(nn.MSELoss):
    "Same as `nn.MSELoss`, but flattens input and target."
    def forward(self, input:Tensor, target:Tensor) -> Rank0Tensor:
        return super().forward(input.view(-1).float(), target.view(-1).float() )

learn = create_cnn(data, models.resnet34)
learn.loss_func = MSELossFlat2()
learn.fit_one_cycle(4)

If I don’t modify my MSELoss then I get an error about expecting a Float and getting a long form target. If i do this I get a mismatch error:

RuntimeError: input and target shapes do not match: input [7040], target [64] at /opt/conda/conda-bld/pytorch-nightly_1540036376816/work/aten/src/THCUNN/generic/MSECriterion.cu:12

Any tips would be appreciated. Thanks!

Did anyone work on this recently? I want to predict a scalar with an image too.

I have a dataframe with the filenames in one column and the scalar I want to predict on another column.

I can’t get the custom dataset to work. Where does the ImageDataset class come from?

Is there any other/easier way to get this to work in fastai v1 ? In fastai 0.7 you just had to change one argument to the ImageClassifierData factory method

EDIT:
nvm, I was just to stupid to save my scalar as a float instead of an integer. Now the last layer has one output like expected. It seems to work with the standard ImageDataBunch.from__df()

Would you be willing to provide an example of predicting scalars from images using the standard ImageDataBunch.from_df() ?

I have a very similar problem. I have a data frame with two columns, the first “filename” and the second “age”, saved as a string object and int64 respectively.

When I run the following code:

data = ImageDataBunch.from_df(path=path, df=df, size=224, bs=64)
arch = models.resnet34
learn = create_cnn(data, arch, metrics=MSELossFlat)
lr = 5e-2
learn.fit_one_cycle(1, slice(lr))

I get the same error message as jamesp:
RuntimeError: bool value of Tensor with more than one value is ambiguous

EDIT: I think I figured it out. This blog post/code are very helpful:

The key is to use ‘label_cls=FloatList’ when constructing the labels. This tells fastai to expect a regression problem, e.g:

data = (ImageItemList.from_csv(path, ‘csv_file’, folder=’’, suffix=’’)
.random_split_by_pct(0.2)
.label_from_df(cols=1, label_cls=FloatList)
.transform(tfms, size=224)
.databunch())
data.normalize(imagenet_stats)

1 Like

Could anyone provide some insight on how this might be extended to predict multiple scalar outputs? I’m having trouble figuring out a way to do this.

Another post with the same question that is unresolved.

If you pass a list instead of 1 to cols, it should work.

Not working on my problem. I needed to change a lot of code and I think now it is not working for other problems. I will send code here if everything worked well.

Should be fixed in master now.

I’m acctualy using this for text data so I’m not sure is this something related to that but I got this error when I tried to train the model.

RuntimeError: Expected object of scalar type Long but got scalar type Float for argument #2 'other'

targs: tensor([[0., 0., 1.],
[0., 1., 0.],
[1., 0., 0.],
[1., 0., 0.],
[1., 0., 0.],
[0., 1., 0.]], device=‘cuda:0’)
input: tensor([[2],
[2],
[0],
[0],
[0],
[0]], device=‘cuda:0’)

Targets can be anything from 0 to 1 and I need three different values.

data = (TextList.from_csv('.', text_data_with_three_targets.csv', vocab=data.vocab)
             .random_split_by_pct()
             .label_from_df(cols=[1,2,3],label_cls=FloatList)
             .databunch(bs=32,no_check=True))

When I try that, I run into the following error when I try to create my DataBunch:

TypeError: can't convert np.ndarray of type numpy.object_. The only supported types are: double, float, float16, int64, int32, and uint8.

This occurs when the summary (repr) of the DataBunch is about to be outputted, specifically in tensor() method, where there still seems to be an issue as referred to in this line:
# XXX: Pytorch bug in dataloader using num_workers>0; TODO: create repro and report

How can I get around this?

It’s hard to help without the rest of your code and the full error message.

I will provide those tomorrow and I will also try to debug it by myself.

I’ve put my example here, with the error message shown in the 4th cell.

You don’t have the latest code, so it’s normal it doesn’t work.

path = Path('/path/to/texts')
data = (TextList.from_folder(path,extensions='.txt')
            .random_split_by_pct(0.1)
            .label_for_lm()
            .databunch(bs=32))
learn = language_model_learner(data, pretrained_model=URLs.WT103_1, drop_mult=0.3)
learn.load(Path('/path/to/fine_tuned/language_model'))

data2 = (TextList.from_csv('.', 'example.csv', vocab=data.vocab)
             .random_split_by_pct()
             .label_from_df(cols=[1,2,3],label_cls=FloatList)
             .databunch(bs=32))

learn = text_classifier_learner(data2,drop_mult=0.5)
learn.load_encoder('/path/to/fine_tuned_enc')
learn.freeze()
learn.fit_one_cycle(1, 2e-2, moms=(0.8,0.7))

Error message:
RuntimeError Traceback (most recent call last)
in ()
----> 1 learn.fit_one_cycle(1, 2e-2, moms=(0.8,0.7))

~/Downloads/fastai-master (1)/fastai-master/fastai/train.py in fit_one_cycle(learn, cyc_len, max_lr, moms, div_factor, pct_start, wd, callbacks, **kwargs)
     20     callbacks.append(OneCycleScheduler(learn, max_lr, moms=moms, div_factor=div_factor,
     21                                         pct_start=pct_start, **kwargs))
---> 22     learn.fit(cyc_len, max_lr, wd=wd, callbacks=callbacks)
     23 
     24 def lr_find(learn:Learner, start_lr:Floats=1e-7, end_lr:Floats=10, num_it:int=100, stop_div:bool=True, **kwargs:Any):

~/Downloads/fastai-master (1)/fastai-master/fastai/basic_train.py in fit(self, epochs, lr, wd, callbacks)
    171         callbacks = [cb(self) for cb in self.callback_fns] + listify(callbacks)
    172         fit(epochs, self.model, self.loss_func, opt=self.opt, data=self.data, metrics=self.metrics,
--> 173             callbacks=self.callbacks+callbacks)
    174 
    175     def create_opt(self, lr:Floats, wd:Floats=0.)->None:

~/Downloads/fastai-master (1)/fastai-master/fastai/basic_train.py in fit(epochs, model, loss_func, opt, data, callbacks, metrics)
     93     except Exception as e:
     94         exception = e
---> 95         raise e
     96     finally: cb_handler.on_train_end(exception)
     97 

~/Downloads/fastai-master (1)/fastai-master/fastai/basic_train.py in fit(epochs, model, loss_func, opt, data, callbacks, metrics)
     88             if not data.empty_val:
     89                 val_loss = validate(model, data.valid_dl, loss_func=loss_func,
---> 90                                        cb_handler=cb_handler, pbar=pbar)
     91             else: val_loss=None
     92             if cb_handler.on_epoch_end(val_loss): break

~/Downloads/fastai-master (1)/fastai-master/fastai/basic_train.py in validate(model, dl, loss_func, cb_handler, pbar, average, n_batch)
     53             if not is_listy(yb): yb = [yb]
     54             nums.append(yb[0].shape[0])
---> 55             if cb_handler and cb_handler.on_batch_end(val_losses[-1]): break
     56             if n_batch and (len(nums)>=n_batch): break
     57         nums = np.array(nums, dtype=np.float32)

~/Downloads/fastai-master (1)/fastai-master/fastai/callback.py in on_batch_end(self, loss)
    248         "Handle end of processing one batch with `loss`."
    249         self.state_dict['last_loss'] = loss
--> 250         stop = np.any(self('batch_end', not self.state_dict['train']))
    251         if self.state_dict['train']:
    252             self.state_dict['iteration'] += 1

~/Downloads/fastai-master (1)/fastai-master/fastai/callback.py in __call__(self, cb_name, call_mets, **kwargs)
    196     def __call__(self, cb_name, call_mets=True, **kwargs)->None:
    197         "Call through to all of the `CallbakHandler` functions."
--> 198         if call_mets: [getattr(met, f'on_{cb_name}')(**self.state_dict, **kwargs) for met in self.metrics]
    199         return [getattr(cb, f'on_{cb_name}')(**self.state_dict, **kwargs) for cb in self.callbacks]
    200 

~/Downloads/fastai-master (1)/fastai-master/fastai/callback.py in <listcomp>(.0)
    196     def __call__(self, cb_name, call_mets=True, **kwargs)->None:
    197         "Call through to all of the `CallbakHandler` functions."
--> 198         if call_mets: [getattr(met, f'on_{cb_name}')(**self.state_dict, **kwargs) for met in self.metrics]
    199         return [getattr(cb, f'on_{cb_name}')(**self.state_dict, **kwargs) for cb in self.callbacks]
    200 

~/Downloads/fastai-master (1)/fastai-master/fastai/callback.py in on_batch_end(self, last_output, last_target, **kwargs)
    283         if not is_listy(last_target): last_target=[last_target]
    284         self.count += last_target[0].size(0)
--> 285         self.val += last_target[0].size(0) * self.func(last_output, *last_target).detach().cpu()
    286 
    287     def on_epoch_end(self, **kwargs):

~/Downloads/fastai-master (1)/fastai-master/fastai/metrics.py in accuracy(input, targs)
     29     print('targs:',targs)
     30     print('input:',input)
---> 31     return (input==targs).float().mean()
     32 
     33 def accuracy_thresh(y_pred:Tensor, y_true:Tensor, thresh:float=0.5, sigmoid:bool=True)->Rank0Tensor:

RuntimeError: Expected object of scalar type Long but got scalar type Float for argument #2 'other'

example.csv
image