skorch-dev / skorch

A scikit-learn compatible neural network library that wraps PyTorch
BSD 3-Clause "New" or "Revised" License
5.82k stars 388 forks source link

Error: too many values to unpack (expected 2) #389

Closed eeeeeeeeeee555 closed 5 years ago

eeeeeeeeeee555 commented 5 years ago

Hi, I created TabularDataset base on Dataset class of Skorch then try to train but I got this error. Do you know how to fix it ? Thank you :)

ValueError                                Traceback (most recent call last)
<ipython-input-14-f574d74096e9> in <module>
----> 1 net.fit(X,y)

/opt/conda/envs/fastai/lib/python3.6/site-packages/skorch/regressor.py in fit(self, X, y, **fit_params)
     88         # this is actually a pylint bug:
     89         # https://github.com/PyCQA/pylint/issues/1085
---> 90         return super(NeuralNetRegressor, self).fit(X, y, **fit_params)

/opt/conda/envs/fastai/lib/python3.6/site-packages/skorch/net.py in fit(self, X, y, **fit_params)
    773             self.initialize()
    774 
--> 775         self.partial_fit(X, y, **fit_params)
    776         return self
    777 

/opt/conda/envs/fastai/lib/python3.6/site-packages/skorch/net.py in partial_fit(self, X, y, classes, **fit_params)
    733         self.notify('on_train_begin', X=X, y=y)
    734         try:
--> 735             self.fit_loop(X, y, **fit_params)
    736         except KeyboardInterrupt:
    737             pass

/opt/conda/envs/fastai/lib/python3.6/site-packages/skorch/net.py in fit_loop(self, X, y, epochs, **fit_params)
    669             self.notify('on_epoch_begin', **on_epoch_kwargs)
    670 
--> 671             for Xi, yi in self.get_iterator(dataset_train, training=True):
    672                 yi_res = yi if not y_train_is_ph else None
    673                 self.notify('on_batch_begin', X=Xi, y=yi_res, training=True)

ValueError: too many values to unpack (expected 2)

And this is some part of my code. For full code, please access: Here I am using this data for training: house-prices-advanced-regression

from skorch.dataset import Dataset
class TabularDataset(Dataset):
    def __init__(self, data, cat_cols=None, output_col=None):
        self.n = data.shape[0]

        if output_col:
            self.y = data[output_col].astype(np.float32).values.reshape(-1,1)
        else:
            self.y = np.zeros((self.n, 1))

        self.cat_cols = cat_cols if cat_cols else []
        self.cont_cols = [col for col in data.columns
                         if col not in self.cat_cols + [output_col]]

        if self.cont_cols:
            self.cont_X = data[self.cont_cols].astype(np.float32).values
        else:
            self.cont_X = np.zeros((self.n, 1))

        if self.cat_cols:
            self.cat_X = data[self.cat_cols].astype(np.int64).values
        else:
            self.cat_X = np.zeros((self.n,1))

    def __len__(self):
        #Denotes the total number of sampoes
        return self.n

    def __getitem__(self, idx):
        #generates one sample of data
        return [self.y[idx], self.cont_X[idx], self.cat_X[idx]]

.....

ds = TabularDataset(data=data, cat_cols=categorical_features,
                             output_col=output_feature)
X = data.drop(['SalePrice'], axis=1)
y = data['SalePrice'].values.reshape(-1,1)
net = NeuralNetRegressor(
    net,
    max_epochs=20,
    lr=0.1,
    dataset = ds
)
net.fit(X,y)
ottonemo commented 5 years ago

skorch expects datasets to yield (X, y) pairs. In your example you are returning more than two values in __getitem__, namely [self.y[idx], self.cont_X[idx], self.cat_X[idx]] which then breaks the training loop since it tries to unpack three values (y, x, cat) into (X, y).

It looks to me as though you want to have multiple features in X and one target in y, so you might want to do something like this in your dataset:

    def __getitem__(self, idx):
        return {'X': self.cont_X[idx], 'cat': self.cat_X[idx]}, self.y[idx]

The forward method of your neural net module will then be called with X and cat parameters, e.g.:

class MyNetModule(nn.Module):
    def forward(self, X, cat):
        pass

Note that in your case it might not even be necessary to create your own dataset since skorch supports pandas DataFrames just fine. You might get away with just creating a dataframe with your relevant columns and passing it as X. The behavior is the same as above but the parameter names to forward will correspond to your dataframe's column names.

eeeeeeeeeee555 commented 5 years ago

Thank you for detail answering ! :+1: I want to create my own dataset because I want to differentiate categorical columns and continuous columns. These categorical columns will be passed through the embedding layers in the model. Yes, I will try your way and tell how is it going on


def __getitem__(self, idx):
        return {'X': self.cont_X[idx], 'cat': self.cat_X[idx]}, self.y[idx]
eeeeeeeeeee555 commented 5 years ago

Hi, I changed in TabularDataset:

    def __getitem__(self, idx):
        # generates one sample of data
        return [self.cont_X[idx], self.cat_X[idx]], self.y[idx]

and in model:

    def forward(self, X):
        cont_data = X[0]
        cat_data = X[1]

It worked but I got nan value like this. Do you know what cause problem ?

  epoch    train_loss    valid_loss     dur
-------  ------------  ------------  ------
      1           nan           nan  0.1560
      2           nan           nan  0.1670
      3           nan           nan  0.1610
      4           nan           nan  0.1590
      5           nan           nan  0.1490
.........
    result = self.forward(*input, **kwargs)
TypeError: forward() got an unexpected keyword argument 'cat'

This is full code:

from skorch import NeuralNetRegressor
from skorch.dataset import Dataset
import torch
import torch.nn as nn
import torch.nn.functional as F
import pandas as pd
import numpy as np
from sklearn.preprocessing import LabelEncoder

class TabularDataset(Dataset):
    def __init__(self, data, cat_cols=None, output_col=None):
        self.n = data.shape[0]

        if output_col:
            self.y = data[output_col].astype(np.float32).values.reshape(-1, 1)
        else:
            self.y = np.zeros((self.n, 1))

        self.cat_cols = cat_cols if cat_cols else []
        self.cont_cols = [col for col in data.columns
                          if col not in self.cat_cols + [output_col]]

        if self.cont_cols:
            self.cont_X = data[self.cont_cols].astype(np.float32).values
        else:
            self.cont_X = np.zeros((self.n, 1))

        if self.cat_cols:
            self.cat_X = data[self.cat_cols].astype(np.int64).values
        else:
            self.cat_X = np.zeros((self.n, 1))

    def __len__(self):
        # Denotes the total number of sampoes
        return self.n

    def __getitem__(self, idx):
        # generates one sample of data
        return [self.cont_X[idx], self.cat_X[idx]], self.y[idx]

class FeedForwardNN(nn.Module):

    def __init__(self, emb_dims, no_of_cont, lin_layer_sizes,
                 output_size, emb_dropout, lin_layer_dropouts):

        """
        Parameters
        ----------
        emb_dims: List of two element tuples
          This list will contain a two element tuple for each
          categorical feature. The first element of a tuple will
          denote the number of unique values of the categorical
          feature. The second element will denote the embedding
          dimension to be used for that feature.
        no_of_cont: Integer
          The number of continuous features in the data.
        lin_layer_sizes: List of integers.
          The size of each linear layer. The length will be equal
          to the total number
          of linear layers in the network.
        output_size: Integer
          The size of the final output.
        emb_dropout: Float
          The dropout to be used after the embedding layers.
        lin_layer_dropouts: List of floats
          The dropouts to be used after each linear layer.
        """

        super().__init__()

        # Embedding layers
        self.emb_layers = nn.ModuleList([nn.Embedding(x, y)
                                         for x, y in emb_dims])

        no_of_embs = sum([y for x, y in emb_dims])
        self.no_of_embs = no_of_embs
        self.no_of_cont = no_of_cont

        # Linear Layers
        first_lin_layer = nn.Linear(self.no_of_embs + self.no_of_cont,
                                    lin_layer_sizes[0])

        self.lin_layers = \
            nn.ModuleList([first_lin_layer] + \
                          [nn.Linear(lin_layer_sizes[i], lin_layer_sizes[i + 1])
                           for i in range(len(lin_layer_sizes) - 1)])

        for lin_layer in self.lin_layers:
            nn.init.kaiming_normal_(lin_layer.weight.data)

        # Output Layer
        self.output_layer = nn.Linear(lin_layer_sizes[-1],
                                      output_size)
        nn.init.kaiming_normal_(self.output_layer.weight.data)

        # Batch Norm Layers
        self.first_bn_layer = nn.BatchNorm1d(self.no_of_cont)
        self.bn_layers = nn.ModuleList([nn.BatchNorm1d(size)
                                        for size in lin_layer_sizes])

        # Dropout Layers
        self.emb_dropout_layer = nn.Dropout(emb_dropout)
        self.droput_layers = nn.ModuleList([nn.Dropout(size)
                                            for size in lin_layer_dropouts])

    def forward(self, X):
        cont_data = X[0]
        cat_data = X[1]
        if self.no_of_embs != 0:
            x = [emb_layer(cat_data[:, i])
                 for i, emb_layer in enumerate(self.emb_layers)]
            x = torch.cat(x, 1)
            x = self.emb_dropout_layer(x)

        if self.no_of_cont != 0:
            normalized_cont_data = self.first_bn_layer(cont_data)

            if self.no_of_embs != 0:
                x = torch.cat([x, normalized_cont_data], 1)
            else:
                x = normalized_cont_data

        for lin_layer, dropout_layer, bn_layer in \
                zip(self.lin_layers, self.droput_layers, self.bn_layers):
            x = F.relu(lin_layer(x))
            x = bn_layer(x)
            x = dropout_layer(x)

        x = self.output_layer(x)

        return x

# Read data
data = pd.read_csv("data/train.csv", usecols=["SalePrice", "MSSubClass", "MSZoning", "LotFrontage", "LotArea",
                                              "Street", "YearBuilt", "LotShape", "1stFlrSF", "2ndFlrSF"]).dropna()

categorical_features = ["MSSubClass", "MSZoning", "Street", "LotShape", "YearBuilt"]
output_feature = "SalePrice"

# Label Encode Categorial Features
label_encoders = {}
for cat_col in categorical_features:
    label_encoders[cat_col] = LabelEncoder()
    data[cat_col] = label_encoders[cat_col].fit_transform(data[cat_col])

# feed Forward NN
cat_dims = [int(data[col].nunique()) for col in categorical_features]

emb_dims = [(x, min(50, (x + 1) // 2)) for x in cat_dims]

net = FeedForwardNN(emb_dims, no_of_cont=4, lin_layer_sizes=[50, 100],
                    output_size=1, emb_dropout=0.04,
                    lin_layer_dropouts=[0.001, 0.01])

# Fit
ds = TabularDataset(data=data, cat_cols=categorical_features,
                    output_col=output_feature)
X = data.drop(['SalePrice'], axis=1)
y = data['SalePrice'].values.reshape(-1, 1)
net = NeuralNetRegressor(
    net,
    max_epochs=5,
    lr=0.1,
    dataset=ds
)
net.fit(X, y)
benjamin-work commented 5 years ago

It is hard to debug why you are getting nans. Did you get good results with other, similar models? When using regression, it is often a good practice to scale the target data, depending on how it is distributed (e.g. log). Also, did you try a lower learning rate? If all that doesn't help, you should add a debugger to your forward method and see if any of the intermediate values look suspicious (very large or small).

TypeError: forward() got an unexpected keyword argument 'cat'

This happens when you pass in data as a dictionary or DataFrame -- skorch will then treat the keys/columns as keywords. I don't see that in your code.

eeeeeeeeeee555 commented 5 years ago

Thank you ! I will try your advice :)

benjamin-work commented 5 years ago

@truongtamgio Was your issue solved?

ottonemo commented 5 years ago

Closing this since the solution can be found on stackoverflow: https://stackoverflow.com/questions/53386245/why-skorch-show-nan-in-the-every-epoch

qyum commented 4 years ago

def extract_video(param, root_dir, crops_dir):

#for key,value in enumerate(param):

for key,value in param:

#video, bboxes_path =key,value
  #print(video,bboxes_path)

video, bboxes_path = map(int,param)

with open(bboxes_path,'r') as bbox_f:

  bboxes_dict = json.load(bbox_f)

capture = cv2.VideoCapture(video)
frames_num = int(capture.get(cv2.CAP_PROP_FRAME_COUNT))
print(frames_num)
for i in range(frames_num):
    capture.grab()
    if i % 10 != 0:
        continue
    success, frame = capture.retrieve()
    if not success or str(i) not in bboxes_dict:
        continue
    id = os.path.splitext(os.path.basename(video))[0]
    crops = []
    bboxes = bboxes_dict[str(i)]
    if bboxes is None:
        continue
    for bbox in bboxes:
        xmin, ymin, xmax, ymax = [int(b * 2) for b in bbox]
        w = xmax - xmin
        h = ymax - ymin
        p_h = h // 3
        p_w = w // 3
        crop = frame[max(ymin - p_h, 0):ymax + p_h, max(xmin - p_w, 0):xmax + p_w]
        h, w = crop.shape[:2]
        crops.append(crop)
    img_dir = os.path.join(root_dir, crops_dir, id)
    os.makedirs(img_dir, exist_ok=True)
    for j, crop in enumerate(crops):
        cv2.imwrite(os.path.join(img_dir, "{}_{}.png".format(i, j)), crop)

def get_video_paths(root_dir): paths = [] for json_path in glob(root_dir):

    dir = Path(json_path)
    #print(dir)

    with open(json_path,'r') as f:
        metadata = json.load(f)
        #print(metadata)

    for k, v in metadata.items():
        #print(k);
        #print(v)
        original = v.get("original", None)
        if not original:
            original = k
            #print(original)
        bboxes_path = os.path.join(root_dir, "boxes", original[:-4])
        #print(bboxes_path)
        #if not os.path.exists(bboxes_path):
            #continue
        #print(dir)
        #print(k)
        #print(bboxes_path)
        paths.append((os.path.join(dir,k), bboxes_path))
        #print(paths)
return paths

root_dir=data_folder crops_dir=root_save_image params = get_video_paths(root_dir_json) print(len(params)) params

print(params)

if name == 'main': extract_video(params,root_dir,crops_dir)

BenjaminBossan commented 4 years ago

@Qyum I suppose that you get the error message indicated at the top, which is why you posted your code. For us to be able to help you, could you please:

  1. format your code correctly -- it's hard to read at the moment
  2. paste the complete stack trace of the error you get

Thanks