{"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"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":22422,"databundleVersionId":2153105,"sourceType":"competition"}],"dockerImageVersionId":30085,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# SUMMARY\n\nThis notebook demonstrates how to compute mean and standard deviation of training and test images using PyTorch. Knowing mean and STD may be helpful for normalizing images within the augmentation pipeline. While computing mean is easy (we can simply average it over batches), standard deviation is a bit more tricky: averaging STDs across batches is not the same as the overall STD. Let's see how to do it properly!\n\n\n### TL;DR\n\n- train images: `mean = 0.9871, std = 0.0888`\n- test images:  `mean = 0.9863, std = 0.0921`","metadata":{}},{"cell_type":"markdown","source":"# PREPARATIONS\n\nFirst, we import relevant libraries and specify some parameters. No need to use GPU because there is no modeling involved.","metadata":{}},{"cell_type":"code","source":"##### PACKAGES\n\nimport numpy as np\nimport pandas as pd\n\nimport torch\nimport torchvision\nfrom torch.utils.data import Dataset, DataLoader\n\nimport albumentations as A\nfrom albumentations.pytorch import ToTensorV2\n\nimport cv2\n\nfrom tqdm import tqdm\n\nimport matplotlib.pyplot as plt\n%matplotlib inline","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##### PARAMS\n\ndevice      = torch.device('cpu') \nnum_workers = 4\nbatch_size  = 128\nimage_size  = 224\ndata_path   = '/kaggle/input/bms-molecular-translation/'","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DATA PREP\n\nNow, let's set up a Dataset and a DataLoader.","metadata":{}},{"cell_type":"code","source":"##### DATA IMPORT\n\ndef get_train_file_path(image_id):\n    '''\n    Borrowed from https://www.kaggle.com/ihelon/molecular-translation-exploratory-data-analysis\n    '''\n    return data_path + 'train/{}/{}/{}/{}.png'.format(image_id[0], image_id[1], image_id[2], image_id)\n\n\ndf              = pd.read_csv(data_path + 'train_labels.csv')\ndf['file_path'] = df['image_id'].apply(get_train_file_path)\ndf.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##### DATASET\n\nclass ImageData(Dataset):\n    \n    def __init__(self, df, transform):\n        super().__init__()\n        self.df         = df\n        self.file_paths = df['file_path'].values\n        self.transform  = transform\n    \n    def __len__(self):\n        return len(self.df)\n    \n    def __getitem__(self, idx):\n        \n        # import\n        file_path = self.file_paths[idx]        \n        image     = cv2.imread(file_path, cv2.IMREAD_GRAYSCALE) \n        if image is None:\n            raise FileNotFoundError(file_path)\n            \n        # augmentations\n        if self.transform:\n            image = self.transform(image = image)['image']\n            \n        return image","metadata":{"papermill":{"duration":0.043635,"end_time":"2021-03-12T23:46:45.694358","exception":false,"start_time":"2021-03-12T23:46:45.650723","status":"completed"},"tags":[],"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Our augmentation pipeline uses `A.Normalize()` with mean = 0 and std = 1 to scale pixel values from `[0, 255]` to `[0, 1]`.","metadata":{}},{"cell_type":"code","source":"##### AUGMENTATIONS\n\naugs = A.Compose([A.Resize(height  = image_size, \n                           width   = image_size),\n                  A.Normalize(mean = (0), \n                              std  = (1)),\n                  ToTensorV2()])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##### EXAMINE SAMPLE BATCH\n\n# dataset\nimage_dataset = ImageData(df        = df, \n                          transform = augs)\n\n# data loader\nimage_loader = DataLoader(image_dataset, \n                          batch_size  = batch_size, \n                          shuffle     = False, \n                          num_workers = num_workers)\n\n# display images\nfor batch_idx, inputs in enumerate(image_loader):\n    fig = plt.figure(figsize = (14, 7))\n    for i in range(4):\n        ax = fig.add_subplot(2, 4, i + 1, xticks = [], yticks = [])     \n        plt.imshow(inputs[i].numpy()[0, :, :], cmap = 'gray')\n    break","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# CALCULATIONS\n\nThe calculations are done in three steps:\n\n1. Define placeholders to store two batch-level stats: sum and squared sum of pixel values. The first will be used to compute the mean, the second will be needed for standard deviation calculations.\n2. Loop through the batches and add up sum and squared sum values.\n3. Perform final calculations to obtain data-level mean and standard deviation.\n\n## Training images","metadata":{}},{"cell_type":"code","source":"##### COMPUTE PIXEL SUM AND SQUARED SUM\n\n# placeholders\npsum    = torch.tensor([0.0])\npsum_sq = torch.tensor([0.0])\n\n# loop through images\nfor inputs in tqdm(image_loader):\n    psum    += inputs.sum(axis        = [0, 2, 3])\n    psum_sq += (inputs ** 2).sum(axis = [0, 2, 3])","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"- to get the mean, we simply divide the sum of pixel values by `count` - the total number of pixels in the dataset computed as `len(df) * image_size * image_size`.\n- to get the standard deviation, we use the following equation: `total_std = sqrt(psum_sq / count - total_mean ** 2)`. Why? Well, because this is how the variance equation can be simplified to make use of the sum of squares. If you are confused about this, check out [this link](https://www.thoughtco.com/sum-of-squares-formula-shortcut-3126266) for some details.\n\n![variance equation](https://kozodoi.me/images/copied_from_nb/images/fig_variance.jpg)","metadata":{}},{"cell_type":"code","source":"##### FINAL CALCULATIONS\n\n# pixel count\ncount = len(df) * image_size * image_size\n\n# mean and STD\ntotal_mean = psum / count\ntotal_var  = (psum_sq / count) - (total_mean ** 2)\ntotal_std  = torch.sqrt(total_var)\n\n# output\nprint('Training data stats:')\nprint('- mean: {:.4f}'.format(total_mean.item()))\nprint('- std:  {:.4f}'.format(total_std.item()))","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Test images","metadata":{}},{"cell_type":"code","source":"###### DATA IMPORT\n\ndf = pd.read_csv(data_path + 'sample_submission.csv')\n\ndef get_test_file_path(image_id):\n    return data_path + 'test/{}/{}/{}/{}.png'.format(image_id[0], image_id[1], image_id[2], image_id)\n\ndf['file_path'] = df['image_id'].apply(get_test_file_path)\ndf.head()","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"###### DATASET & DATALOADER\n\n# dataset\nimage_dataset = ImageData(df        = df, \n                          transform = augs)\n\n# data loader\nimage_loader = DataLoader(image_dataset, \n                          batch_size  = batch_size, \n                          shuffle     = False, \n                          num_workers = num_workers)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"##### CALCULATIONS\n\n# placeholders\npsum    = torch.tensor([0.0])\npsum_sq = torch.tensor([0.0])\n\n# loop through images\nfor inputs in tqdm(image_loader):\n    psum    += inputs.sum(axis        = [0, 2, 3])\n    psum_sq += (inputs ** 2).sum(axis = [0, 2, 3])\n    \n# pixel count\ncount = len(df) * image_size * image_size\n\n# mean and STD\ntotal_mean = psum / count\ntotal_var  = (psum_sq / count) - (total_mean ** 2)\ntotal_std  = torch.sqrt(total_var)\n\n# output\nprint('Test data stats:')\nprint('- mean: {:.4f}'.format(total_mean.item()))\nprint('- std:  {:.4f}'.format(total_std.item()))","metadata":{"trusted":true,"jupyter":{"source_hidden":true}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"","metadata":{}},{"cell_type":"markdown","source":"If you use a different image size, you can simply change this parameter to make sure calculations are done appropriately. Good luck!","metadata":{}}]}