├── .gitignore ├── LICENSE ├── Makefile ├── Pipfile ├── Pipfile.lock ├── README.md ├── models └── .gitkeep ├── notebooks ├── 01_cnn_vectors_for_sample.ipynb ├── 02_ train_products_distribution.ipynb ├── 03_vgg16_finetuning_models.ipynb └── 04_resnet50_finetuning_models.ipynb ├── src ├── __init__.py ├── data │ ├── big_sample.py │ ├── category_idx.py │ ├── product_info.py │ ├── top_categories_sample.py │ └── train_split.py ├── heng_cherkeng │ ├── excited_inception_v3.py │ ├── inception_v3.py │ ├── resnet101.py │ └── xception.py └── model │ ├── __init__.py │ ├── bcolz_iterator.py │ ├── bcolz_to_memmap.py │ ├── bson_iterator.py │ ├── combine_iterator.py │ ├── ensemble_fixed_weights.py │ ├── form_submission.py │ ├── form_submission_mul.py │ ├── form_submission_sum.py │ ├── heng_models.py │ ├── memmap_iterator.py │ ├── multi_memmap_iterator.py │ ├── predict_ensemble_nn.py │ ├── pseudo_label_prod_info.py │ ├── resnet50_vecs.py │ ├── sngl_preds_to_avg.py │ ├── train_ensemble_nn.py │ ├── tune_avg_resnet50_vecs.py │ ├── tune_avg_vgg16_vecs.py │ ├── tune_pl_avg_resnet50_vecs.py │ ├── tune_resnet50_memmap_vecs.py │ ├── tune_resnet50_vecs.py │ ├── tune_vgg16_memmap_vecs.py │ ├── tune_vgg16_vecs.py │ └── vgg16_vecs.py ├── test_environment.py └── tox.ini /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | /data 3 | /models 4 | .env 5 | .ipynb_checkpoints 6 | nohup.out 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | Copyright (c) 2017, NighTurs 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | 11 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | 3 | url = "https://pypi.python.org/simple" 4 | verify_ssl = true 5 | name = "pypi" 6 | 7 | 8 | [packages] 9 | 10 | keras = "*" 11 | pandas = "*" 12 | numpy = "*" 13 | jupyter = "*" 14 | theano = "*" 15 | scikit-learn = "*" 16 | jupyter-contrib-nbextensions = "*" 17 | pillow = "*" 18 | pymongo = "*" 19 | matplotlib = "*" 20 | seaborn = "*" 21 | "h5py" = "*" 22 | bcolz = "*" 23 | cookiecutter = "*" 24 | tqdm = "*" 25 | opencv-python = "*" 26 | "bb297d5" = {file = "http://download.pytorch.org/whl/cu80/torch-0.2.0.post3-cp36-cp36m-manylinux1_x86_64.whl"} 27 | 28 | 29 | [dev-packages] 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | kaggle-cdiscount-image-classification 2 | ============================== 3 | 4 | Solution to kaggle competition "Cdiscount’s Image Classification Challenge" -------------------------------------------------------------------------------- /models/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NighTurs/kaggle-cdiscount-image-classification/3646ee4dc7a0e35dfe6fb4cdaadcf2fb7b30d3a5/models/.gitkeep -------------------------------------------------------------------------------- /notebooks/02_ train_products_distribution.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import pandas as pd\n", 10 | "import numpy as np\n", 11 | "import scipy.stats as ss" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "prod_info = pd.read_csv('../data/interim/train_product_info.csv')" 21 | ] 22 | }, 23 | { 24 | "cell_type": "code", 25 | "execution_count": 3, 26 | "metadata": {}, 27 | "outputs": [ 28 | { 29 | "data": { 30 | "text/html": [ 31 | "
\n", 32 | "\n", 45 | "\n", 46 | " \n", 47 | " \n", 48 | " \n", 49 | " \n", 50 | " \n", 51 | " \n", 52 | " \n", 53 | " \n", 54 | " \n", 55 | " \n", 56 | " \n", 57 | " \n", 58 | " \n", 59 | " \n", 60 | " \n", 61 | " \n", 62 | " \n", 63 | " \n", 64 | " \n", 65 | " \n", 66 | " \n", 67 | " \n", 68 | " \n", 69 | " \n", 70 | " \n", 71 | " \n", 72 | " \n", 73 | " \n", 74 | " \n", 75 | " \n", 76 | " \n", 77 | " \n", 78 | " \n", 79 | " \n", 80 | " \n", 81 | " \n", 82 | " \n", 83 | " \n", 84 | " \n", 85 | " \n", 86 | " \n", 87 | " \n", 88 | " \n", 89 | " \n", 90 | " \n", 91 | " \n", 92 | " \n", 93 | " \n", 94 | " \n", 95 | " \n", 96 | " \n", 97 | " \n", 98 | "
product_idnum_imgsoffsetlengthcategory_id
001069791000010653
111697973181000010653
2211429754551000004079
3311975245801000004141
4412433263461000015539
\n", 99 | "
" 100 | ], 101 | "text/plain": [ 102 | " product_id num_imgs offset length category_id\n", 103 | "0 0 1 0 6979 1000010653\n", 104 | "1 1 1 6979 7318 1000010653\n", 105 | "2 2 1 14297 5455 1000004079\n", 106 | "3 3 1 19752 4580 1000004141\n", 107 | "4 4 1 24332 6346 1000015539" 108 | ] 109 | }, 110 | "execution_count": 3, 111 | "metadata": {}, 112 | "output_type": "execute_result" 113 | } 114 | ], 115 | "source": [ 116 | "prod_info.head()" 117 | ] 118 | }, 119 | { 120 | "cell_type": "code", 121 | "execution_count": 4, 122 | "metadata": {}, 123 | "outputs": [], 124 | "source": [ 125 | "categories = prod_info.category_id.sample(10, random_state=123).as_matrix()" 126 | ] 127 | }, 128 | { 129 | "cell_type": "code", 130 | "execution_count": 5, 131 | "metadata": {}, 132 | "outputs": [], 133 | "source": [ 134 | "max_index = np.max(prod_info.index.values)" 135 | ] 136 | }, 137 | { 138 | "cell_type": "code", 139 | "execution_count": 6, 140 | "metadata": {}, 141 | "outputs": [], 142 | "source": [ 143 | "prod_info = prod_info[prod_info.category_id.isin(categories)]" 144 | ] 145 | }, 146 | { 147 | "cell_type": "code", 148 | "execution_count": 7, 149 | "metadata": {}, 150 | "outputs": [ 151 | { 152 | "data": { 153 | "text/plain": [ 154 | "(212367, 5)" 155 | ] 156 | }, 157 | "execution_count": 7, 158 | "metadata": {}, 159 | "output_type": "execute_result" 160 | } 161 | ], 162 | "source": [ 163 | "prod_info.shape" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 8, 169 | "metadata": {}, 170 | "outputs": [], 171 | "source": [ 172 | "def dist_check(x):\n", 173 | " return ss.kstest(x.index.values, 'uniform', args=(0,max_index))" 174 | ] 175 | }, 176 | { 177 | "cell_type": "code", 178 | "execution_count": 9, 179 | "metadata": {}, 180 | "outputs": [ 181 | { 182 | "data": { 183 | "text/plain": [ 184 | "category_id\n", 185 | "1000003796 (0.0585434648794, 6.62093945672e-133)\n", 186 | "1000005890 (0.0282060871844, 0.000225859584689)\n", 187 | "1000009368 (0.0556134773972, 0.690667941194)\n", 188 | "1000010722 (0.2076794447, 0.0)\n", 189 | "1000011345 (0.0518859397384, 4.02373456585e-05)\n", 190 | "1000012993 (0.212172712988, 0.0)\n", 191 | "1000013645 (0.122406384056, 0.116693063939)\n", 192 | "1000014217 (0.0255374263699, 2.0049038596e-11)\n", 193 | "1000018294 (0.0219118239728, 1.6525548444e-24)\n", 194 | "Name: offset, dtype: object" 195 | ] 196 | }, 197 | "execution_count": 9, 198 | "metadata": {}, 199 | "output_type": "execute_result" 200 | } 201 | ], 202 | "source": [ 203 | "prod_info.groupby('category_id').offset.agg(dist_check)" 204 | ] 205 | }, 206 | { 207 | "cell_type": "code", 208 | "execution_count": 85, 209 | "metadata": {}, 210 | "outputs": [], 211 | "source": [ 212 | "prod_info_shuffled = prod_info.reindex(np.random.permutation(prod_info.index))" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 88, 218 | "metadata": {}, 219 | "outputs": [ 220 | { 221 | "data": { 222 | "text/html": [ 223 | "
\n", 224 | "\n", 237 | "\n", 238 | " \n", 239 | " \n", 240 | " \n", 241 | " \n", 242 | " \n", 243 | " \n", 244 | " \n", 245 | " \n", 246 | " \n", 247 | " \n", 248 | " \n", 249 | " \n", 250 | " \n", 251 | " \n", 252 | " \n", 253 | " \n", 254 | " \n", 255 | " \n", 256 | " \n", 257 | " \n", 258 | " \n", 259 | " \n", 260 | " \n", 261 | " \n", 262 | " \n", 263 | " \n", 264 | " \n", 265 | " \n", 266 | " \n", 267 | " \n", 268 | " \n", 269 | " \n", 270 | " \n", 271 | " \n", 272 | " \n", 273 | " \n", 274 | " \n", 275 | " \n", 276 | " \n", 277 | " \n", 278 | " \n", 279 | " \n", 280 | " \n", 281 | " \n", 282 | " \n", 283 | " \n", 284 | " \n", 285 | " \n", 286 | " \n", 287 | " \n", 288 | " \n", 289 | " \n", 290 | "
product_idnum_imgsoffsetlengthcategory_id
01853459725014377305383131000014217
122343911722675838256421000010722
21785766514848065168354561000018294
31924806415190400475174651000014217
4578354211555449142569561000018294
\n", 291 | "
" 292 | ], 293 | "text/plain": [ 294 | " product_id num_imgs offset length category_id\n", 295 | "0 18534597 2 50143773053 8313 1000014217\n", 296 | "1 2234391 1 7226758382 5642 1000010722\n", 297 | "2 17857665 1 48480651683 5456 1000018294\n", 298 | "3 19248064 1 51904004751 7465 1000014217\n", 299 | "4 5783542 1 15554491425 6956 1000018294" 300 | ] 301 | }, 302 | "execution_count": 88, 303 | "metadata": {}, 304 | "output_type": "execute_result" 305 | } 306 | ], 307 | "source": [ 308 | "prod_info_shuffled.reset_index(inplace=True, drop=True)\n", 309 | "prod_info_shuffled.head()" 310 | ] 311 | }, 312 | { 313 | "cell_type": "code", 314 | "execution_count": 91, 315 | "metadata": {}, 316 | "outputs": [], 317 | "source": [ 318 | "max_index = np.max(prod_info_shuffled.index.values)" 319 | ] 320 | }, 321 | { 322 | "cell_type": "code", 323 | "execution_count": 92, 324 | "metadata": {}, 325 | "outputs": [], 326 | "source": [ 327 | "def dist_check(x):\n", 328 | " return ss.kstest(x.index.values, 'uniform', args=(0,max_index))" 329 | ] 330 | }, 331 | { 332 | "cell_type": "code", 333 | "execution_count": 93, 334 | "metadata": {}, 335 | "outputs": [ 336 | { 337 | "data": { 338 | "text/plain": [ 339 | "category_id\n", 340 | "1000003796 (0.0029641280778, 0.829121438545)\n", 341 | "1000005890 (0.0107517857166, 0.523791313126)\n", 342 | "1000009368 (0.0644712714092, 0.482267109646)\n", 343 | "1000010722 (0.00406073851587, 0.53500273456)\n", 344 | "1000011345 (0.0201039563847, 0.393696555104)\n", 345 | "1000012993 (0.00389849966882, 0.524969145571)\n", 346 | "1000013645 (0.070996176003, 0.755455088588)\n", 347 | "1000014217 (0.0101867510116, 0.0355555653105)\n", 348 | "1000018294 (0.00226298289825, 0.92889410781)\n", 349 | "Name: offset, dtype: object" 350 | ] 351 | }, 352 | "execution_count": 93, 353 | "metadata": {}, 354 | "output_type": "execute_result" 355 | } 356 | ], 357 | "source": [ 358 | "prod_info_shuffled.groupby('category_id').offset.agg(dist_check)" 359 | ] 360 | }, 361 | { 362 | "cell_type": "code", 363 | "execution_count": 61, 364 | "metadata": {}, 365 | "outputs": [ 366 | { 367 | "data": { 368 | "text/plain": [ 369 | "KstestResult(statistic=0.50000544788818035, pvalue=0.0)" 370 | ] 371 | }, 372 | "execution_count": 61, 373 | "metadata": {}, 374 | "output_type": "execute_result" 375 | } 376 | ], 377 | "source": [ 378 | "ss.kstest(np.random.uniform(0, 0.5, 10000), 'uniform', args=(0,1))" 379 | ] 380 | }, 381 | { 382 | "cell_type": "code", 383 | "execution_count": 28, 384 | "metadata": {}, 385 | "outputs": [ 386 | { 387 | "data": { 388 | "text/plain": [ 389 | "77.10547314768715" 390 | ] 391 | }, 392 | "execution_count": 28, 393 | "metadata": {}, 394 | "output_type": "execute_result" 395 | } 396 | ], 397 | "source": [ 398 | "np.random.uniform(100)" 399 | ] 400 | }, 401 | { 402 | "cell_type": "code", 403 | "execution_count": 60, 404 | "metadata": {}, 405 | "outputs": [ 406 | { 407 | "data": { 408 | "text/plain": [ 409 | "KstestResult(statistic=0.92043693500882817, pvalue=0.0)" 410 | ] 411 | }, 412 | "execution_count": 60, 413 | "metadata": {}, 414 | "output_type": "execute_result" 415 | } 416 | ], 417 | "source": [ 418 | "ss.kstest(np.random.normal(0.5, 0.1, 10000), 'uniform', args=(0,10))" 419 | ] 420 | } 421 | ], 422 | "metadata": { 423 | "kernelspec": { 424 | "display_name": "Python 3", 425 | "language": "python", 426 | "name": "python3" 427 | }, 428 | "language_info": { 429 | "codemirror_mode": { 430 | "name": "ipython", 431 | "version": 3 432 | }, 433 | "file_extension": ".py", 434 | "mimetype": "text/x-python", 435 | "name": "python", 436 | "nbconvert_exporter": "python", 437 | "pygments_lexer": "ipython3", 438 | "version": "3.6.2" 439 | } 440 | }, 441 | "nbformat": 4, 442 | "nbformat_minor": 2 443 | } 444 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NighTurs/kaggle-cdiscount-image-classification/3646ee4dc7a0e35dfe6fb4cdaadcf2fb7b30d3a5/src/__init__.py -------------------------------------------------------------------------------- /src/data/big_sample.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import argparse 4 | 5 | 6 | def create_big_sample(prod_info_csv): 7 | prod_info = pd.read_csv(prod_info_csv) 8 | category_stats = prod_info.groupby(by='category_id').size() 9 | category_stats.sort_values(ascending=False, inplace=True) 10 | categories = category_stats[:2000].index.values 11 | categories_set = set(categories) 12 | 13 | np.random.seed(123) 14 | chunks = [] 15 | for category, prods in prod_info.groupby(by='category_id'): 16 | if category not in categories_set: 17 | continue 18 | chunks.append(prods.sample(100 if prods.shape[0] >= 100 else prods.shape[0])) 19 | sample = pd.concat(chunks) 20 | sample = sample.reset_index(drop=True) 21 | return sample 22 | 23 | 24 | def save_big_sample(big_sample, output_file): 25 | big_sample.to_csv(output_file, index=False) 26 | 27 | 28 | if __name__ == '__main__': 29 | parser = argparse.ArgumentParser() 30 | parser.add_argument('--prod_info_csv', required=True, help='Path to prod info csv') 31 | parser.add_argument('--output_file', required=True, help='File to save sample into') 32 | 33 | args = parser.parse_args() 34 | big_sample = create_big_sample(args.prod_info_csv) 35 | save_big_sample(big_sample, args.output_file) 36 | -------------------------------------------------------------------------------- /src/data/category_idx.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | import numpy as np 4 | 5 | 6 | def create_category_idx(prod_info): 7 | category_stats = prod_info.groupby(by='category_id').size() 8 | category_stats.sort_values(ascending=False, inplace=True) 9 | category_idx = pd.DataFrame([(i, v) for i, v in enumerate(category_stats.index.values)], 10 | columns=['category_idx', 'category_id']) 11 | return category_idx 12 | 13 | 14 | def category_to_index_dict(category_idx): 15 | return {row.category_id: row.category_idx for row in category_idx.itertuples()} 16 | 17 | 18 | def index_to_category_dict(category_idx): 19 | return {row.category_idx: row.category_id for row in category_idx.itertuples()} 20 | 21 | 22 | def map_categories(category_idx, categories): 23 | cat2idx = category_to_index_dict(category_idx) 24 | return np.array([cat2idx[x] for x in categories]) 25 | 26 | 27 | if __name__ == '__main__': 28 | parser = argparse.ArgumentParser() 29 | parser.add_argument('--prod_info_csv', required=True, help='Path to prod info csv') 30 | parser.add_argument('--output_file', required=True, help='File to save indexes into') 31 | args = parser.parse_args() 32 | prod_info = pd.read_csv(args.prod_info_csv) 33 | category_idx = create_category_idx(prod_info) 34 | category_idx.to_csv(args.output_file, index=False) 35 | -------------------------------------------------------------------------------- /src/data/product_info.py: -------------------------------------------------------------------------------- 1 | import struct 2 | import bson 3 | import pandas as pd 4 | from tqdm import tqdm 5 | import argparse 6 | 7 | 8 | def product_info(bson_path, with_categories): 9 | rows = {} 10 | with open(bson_path, "rb") as f, tqdm() as pbar: 11 | offset = 0 12 | while True: 13 | item_length_bytes = f.read(4) 14 | if len(item_length_bytes) == 0: 15 | break 16 | 17 | length = struct.unpack("`_. 24 | # Args: 25 | # pretrained (bool): If True, returns a model pre-trained on ImageNet 26 | # """ 27 | # if pretrained: 28 | # if 'transform_input' not in kwargs: 29 | # kwargs['transform_input'] = True 30 | # model = Inception3(**kwargs) 31 | # model.load_state_dict(model_zoo.load_url(model_urls['inception_v3_google'])) 32 | # return model 33 | # 34 | # return Inception3(**kwargs) 35 | 36 | 37 | class Inception3(nn.Module): 38 | 39 | def load_pretrain_pytorch_file(self,pytorch_file, skip=[]): 40 | pytorch_state_dict = torch.load(pytorch_file) 41 | state_dict = self.state_dict() 42 | 43 | keys = list(state_dict.keys()) 44 | for key in keys: 45 | if key in skip: continue 46 | state_dict[key] = pytorch_state_dict[key] 47 | 48 | self.load_state_dict(state_dict) 49 | pass 50 | 51 | #----------------------------------------------------------------------- 52 | 53 | def __init__(self, in_shape=(3,128,128), num_classes=1000 ): 54 | super(Inception3, self).__init__() 55 | in_channels, height, width = in_shape 56 | self.num_classes=num_classes 57 | assert(in_channels==3) 58 | 59 | # self.aux_logits = aux_logits 60 | # self.transform_input = transform_input 61 | self.Conv2d_1a_3x3 = BasicConv2d(in_channels, 32, kernel_size=3, stride=2) 62 | self.Conv2d_2a_3x3 = BasicConv2d(32, 32, kernel_size=3) 63 | self.Conv2d_2b_3x3 = BasicConv2d(32, 64, kernel_size=3, padding=1) 64 | self.Conv2d_3b_1x1 = BasicConv2d(64, 80, kernel_size=1) 65 | self.Conv2d_4a_3x3 = BasicConv2d(80, 192, kernel_size=3) 66 | self.Mixed_5b = InceptionA(192, pool_features=32) 67 | self.Mixed_5c = InceptionA(256, pool_features=64) 68 | self.Mixed_5d = InceptionA(288, pool_features=64) 69 | self.Mixed_6a = InceptionB(288) 70 | self.Mixed_6b = InceptionC(768, channels_7x7=128) 71 | self.Mixed_6c = InceptionC(768, channels_7x7=160) 72 | self.Mixed_6d = InceptionC(768, channels_7x7=160) 73 | self.Mixed_6e = InceptionC(768, channels_7x7=192) 74 | # if aux_logits: 75 | # self.AuxLogits = InceptionAux(768, num_classes) 76 | self.Mixed_7a = InceptionD(768) 77 | self.Mixed_7b = InceptionE(1280) 78 | self.Mixed_7c = InceptionE(2048) 79 | self.fc = nn.Linear(2048, num_classes) 80 | 81 | for m in self.modules(): 82 | if isinstance(m, nn.Conv2d) or isinstance(m, nn.Linear): 83 | import scipy.stats as stats 84 | stddev = m.stddev if hasattr(m, 'stddev') else 0.1 85 | X = stats.truncnorm(-2, 2, scale=stddev) 86 | values = torch.Tensor(X.rvs(m.weight.data.numel())) 87 | values = values.view(m.weight.data.size()) 88 | m.weight.data.copy_(values) 89 | elif isinstance(m, nn.BatchNorm2d): 90 | m.weight.data.fill_(1) 91 | m.bias.data.zero_() 92 | 93 | def forward(self, x): 94 | 95 | # if self.transform_input: 96 | # x = x.clone() 97 | # x[:, 0] = x[:, 0] * (0.229 / 0.5) + (0.485 - 0.5) / 0.5 98 | # x[:, 1] = x[:, 1] * (0.224 / 0.5) + (0.456 - 0.5) / 0.5 99 | # x[:, 2] = x[:, 2] * (0.225 / 0.5) + (0.406 - 0.5) / 0.5 100 | 101 | 102 | # 299 x 299 x 3 103 | x = self.Conv2d_1a_3x3(x) 104 | # 149 x 149 x 32 105 | x = self.Conv2d_2a_3x3(x) 106 | # 147 x 147 x 32 107 | x = self.Conv2d_2b_3x3(x) 108 | # 147 x 147 x 64 109 | x = F.max_pool2d(x, kernel_size=3, stride=2) 110 | # 73 x 73 x 64 111 | x = self.Conv2d_3b_1x1(x) 112 | # 73 x 73 x 80 113 | x = self.Conv2d_4a_3x3(x) 114 | # 71 x 71 x 192 115 | x = F.max_pool2d(x, kernel_size=3, stride=2) 116 | # 35 x 35 x 192 117 | x = self.Mixed_5b(x) 118 | # 35 x 35 x 256 119 | x = self.Mixed_5c(x) 120 | # 35 x 35 x 288 121 | x = self.Mixed_5d(x) 122 | # 35 x 35 x 288 123 | x = self.Mixed_6a(x) 124 | # 17 x 17 x 768 125 | x = self.Mixed_6b(x) 126 | # 17 x 17 x 768 127 | x = self.Mixed_6c(x) 128 | # 17 x 17 x 768 129 | x = self.Mixed_6d(x) 130 | # 17 x 17 x 768 131 | x = self.Mixed_6e(x) 132 | # 17 x 17 x 768 133 | 134 | # if self.training and self.aux_logits: 135 | # aux = self.AuxLogits(x) 136 | 137 | # 17 x 17 x 768 138 | x = self.Mixed_7a(x) 139 | # 8 x 8 x 1280 140 | x = self.Mixed_7b(x) 141 | # 8 x 8 x 2048 142 | x = self.Mixed_7c(x) 143 | # 8 x 8 x 2048 144 | 145 | #x = F.avg_pool2d(x, kernel_size=8) 146 | x = F.adaptive_avg_pool2d(x, output_size=1) 147 | 148 | # 1 x 1 x 2048 149 | #x = F.dropout(x, training=self.training) 150 | # 1 x 1 x 2048 151 | x = x.view(x.size(0), -1) 152 | # 2048 153 | x = self.fc(x) 154 | # 1000 (num_classes) 155 | 156 | # if self.training and self.aux_logits: 157 | # return x, aux 158 | 159 | return x 160 | 161 | 162 | class InceptionA(nn.Module): 163 | 164 | def __init__(self, in_channels, pool_features): 165 | super(InceptionA, self).__init__() 166 | self.branch1x1 = BasicConv2d(in_channels, 64, kernel_size=1) 167 | 168 | self.branch5x5_1 = BasicConv2d(in_channels, 48, kernel_size=1) 169 | self.branch5x5_2 = BasicConv2d(48, 64, kernel_size=5, padding=2) 170 | 171 | self.branch3x3dbl_1 = BasicConv2d(in_channels, 64, kernel_size=1) 172 | self.branch3x3dbl_2 = BasicConv2d(64, 96, kernel_size=3, padding=1) 173 | self.branch3x3dbl_3 = BasicConv2d(96, 96, kernel_size=3, padding=1) 174 | 175 | self.branch_pool = BasicConv2d(in_channels, pool_features, kernel_size=1) 176 | 177 | def forward(self, x): 178 | branch1x1 = self.branch1x1(x) 179 | 180 | branch5x5 = self.branch5x5_1(x) 181 | branch5x5 = self.branch5x5_2(branch5x5) 182 | 183 | branch3x3dbl = self.branch3x3dbl_1(x) 184 | branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl) 185 | branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl) 186 | 187 | branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1) 188 | branch_pool = self.branch_pool(branch_pool) 189 | 190 | outputs = [branch1x1, branch5x5, branch3x3dbl, branch_pool] 191 | return torch.cat(outputs, 1) 192 | 193 | 194 | class InceptionB(nn.Module): 195 | 196 | def __init__(self, in_channels): 197 | super(InceptionB, self).__init__() 198 | self.branch3x3 = BasicConv2d(in_channels, 384, kernel_size=3, stride=2) 199 | 200 | self.branch3x3dbl_1 = BasicConv2d(in_channels, 64, kernel_size=1) 201 | self.branch3x3dbl_2 = BasicConv2d(64, 96, kernel_size=3, padding=1) 202 | self.branch3x3dbl_3 = BasicConv2d(96, 96, kernel_size=3, stride=2) 203 | 204 | def forward(self, x): 205 | branch3x3 = self.branch3x3(x) 206 | 207 | branch3x3dbl = self.branch3x3dbl_1(x) 208 | branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl) 209 | branch3x3dbl = self.branch3x3dbl_3(branch3x3dbl) 210 | 211 | branch_pool = F.max_pool2d(x, kernel_size=3, stride=2) 212 | 213 | outputs = [branch3x3, branch3x3dbl, branch_pool] 214 | return torch.cat(outputs, 1) 215 | 216 | 217 | class InceptionC(nn.Module): 218 | 219 | def __init__(self, in_channels, channels_7x7): 220 | super(InceptionC, self).__init__() 221 | self.branch1x1 = BasicConv2d(in_channels, 192, kernel_size=1) 222 | 223 | c7 = channels_7x7 224 | self.branch7x7_1 = BasicConv2d(in_channels, c7, kernel_size=1) 225 | self.branch7x7_2 = BasicConv2d(c7, c7, kernel_size=(1, 7), padding=(0, 3)) 226 | self.branch7x7_3 = BasicConv2d(c7, 192, kernel_size=(7, 1), padding=(3, 0)) 227 | 228 | self.branch7x7dbl_1 = BasicConv2d(in_channels, c7, kernel_size=1) 229 | self.branch7x7dbl_2 = BasicConv2d(c7, c7, kernel_size=(7, 1), padding=(3, 0)) 230 | self.branch7x7dbl_3 = BasicConv2d(c7, c7, kernel_size=(1, 7), padding=(0, 3)) 231 | self.branch7x7dbl_4 = BasicConv2d(c7, c7, kernel_size=(7, 1), padding=(3, 0)) 232 | self.branch7x7dbl_5 = BasicConv2d(c7, 192, kernel_size=(1, 7), padding=(0, 3)) 233 | 234 | self.branch_pool = BasicConv2d(in_channels, 192, kernel_size=1) 235 | 236 | def forward(self, x): 237 | branch1x1 = self.branch1x1(x) 238 | 239 | branch7x7 = self.branch7x7_1(x) 240 | branch7x7 = self.branch7x7_2(branch7x7) 241 | branch7x7 = self.branch7x7_3(branch7x7) 242 | 243 | branch7x7dbl = self.branch7x7dbl_1(x) 244 | branch7x7dbl = self.branch7x7dbl_2(branch7x7dbl) 245 | branch7x7dbl = self.branch7x7dbl_3(branch7x7dbl) 246 | branch7x7dbl = self.branch7x7dbl_4(branch7x7dbl) 247 | branch7x7dbl = self.branch7x7dbl_5(branch7x7dbl) 248 | 249 | branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1) 250 | branch_pool = self.branch_pool(branch_pool) 251 | 252 | outputs = [branch1x1, branch7x7, branch7x7dbl, branch_pool] 253 | return torch.cat(outputs, 1) 254 | 255 | 256 | class InceptionD(nn.Module): 257 | 258 | def __init__(self, in_channels): 259 | super(InceptionD, self).__init__() 260 | self.branch3x3_1 = BasicConv2d(in_channels, 192, kernel_size=1) 261 | self.branch3x3_2 = BasicConv2d(192, 320, kernel_size=3, stride=2) 262 | 263 | self.branch7x7x3_1 = BasicConv2d(in_channels, 192, kernel_size=1) 264 | self.branch7x7x3_2 = BasicConv2d(192, 192, kernel_size=(1, 7), padding=(0, 3)) 265 | self.branch7x7x3_3 = BasicConv2d(192, 192, kernel_size=(7, 1), padding=(3, 0)) 266 | self.branch7x7x3_4 = BasicConv2d(192, 192, kernel_size=3, stride=2) 267 | 268 | def forward(self, x): 269 | branch3x3 = self.branch3x3_1(x) 270 | branch3x3 = self.branch3x3_2(branch3x3) 271 | 272 | branch7x7x3 = self.branch7x7x3_1(x) 273 | branch7x7x3 = self.branch7x7x3_2(branch7x7x3) 274 | branch7x7x3 = self.branch7x7x3_3(branch7x7x3) 275 | branch7x7x3 = self.branch7x7x3_4(branch7x7x3) 276 | 277 | branch_pool = F.max_pool2d(x, kernel_size=3, stride=2) 278 | outputs = [branch3x3, branch7x7x3, branch_pool] 279 | return torch.cat(outputs, 1) 280 | 281 | 282 | class InceptionE(nn.Module): 283 | 284 | def __init__(self, in_channels): 285 | super(InceptionE, self).__init__() 286 | self.branch1x1 = BasicConv2d(in_channels, 320, kernel_size=1) 287 | 288 | self.branch3x3_1 = BasicConv2d(in_channels, 384, kernel_size=1) 289 | self.branch3x3_2a = BasicConv2d(384, 384, kernel_size=(1, 3), padding=(0, 1)) 290 | self.branch3x3_2b = BasicConv2d(384, 384, kernel_size=(3, 1), padding=(1, 0)) 291 | 292 | self.branch3x3dbl_1 = BasicConv2d(in_channels, 448, kernel_size=1) 293 | self.branch3x3dbl_2 = BasicConv2d(448, 384, kernel_size=3, padding=1) 294 | self.branch3x3dbl_3a = BasicConv2d(384, 384, kernel_size=(1, 3), padding=(0, 1)) 295 | self.branch3x3dbl_3b = BasicConv2d(384, 384, kernel_size=(3, 1), padding=(1, 0)) 296 | 297 | self.branch_pool = BasicConv2d(in_channels, 192, kernel_size=1) 298 | 299 | def forward(self, x): 300 | branch1x1 = self.branch1x1(x) 301 | 302 | branch3x3 = self.branch3x3_1(x) 303 | branch3x3 = [ 304 | self.branch3x3_2a(branch3x3), 305 | self.branch3x3_2b(branch3x3), 306 | ] 307 | branch3x3 = torch.cat(branch3x3, 1) 308 | 309 | branch3x3dbl = self.branch3x3dbl_1(x) 310 | branch3x3dbl = self.branch3x3dbl_2(branch3x3dbl) 311 | branch3x3dbl = [ 312 | self.branch3x3dbl_3a(branch3x3dbl), 313 | self.branch3x3dbl_3b(branch3x3dbl), 314 | ] 315 | branch3x3dbl = torch.cat(branch3x3dbl, 1) 316 | 317 | branch_pool = F.avg_pool2d(x, kernel_size=3, stride=1, padding=1) 318 | branch_pool = self.branch_pool(branch_pool) 319 | 320 | outputs = [branch1x1, branch3x3, branch3x3dbl, branch_pool] 321 | return torch.cat(outputs, 1) 322 | 323 | 324 | class InceptionAux(nn.Module): 325 | 326 | def __init__(self, in_channels, num_classes): 327 | super(InceptionAux, self).__init__() 328 | self.conv0 = BasicConv2d(in_channels, 128, kernel_size=1) 329 | self.conv1 = BasicConv2d(128, 768, kernel_size=5) 330 | self.conv1.stddev = 0.01 331 | self.fc = nn.Linear(768, num_classes) 332 | self.fc.stddev = 0.001 333 | 334 | def forward(self, x): 335 | # 17 x 17 x 768 336 | x = F.avg_pool2d(x, kernel_size=5, stride=3) 337 | # 5 x 5 x 768 338 | x = self.conv0(x) 339 | # 5 x 5 x 128 340 | x = self.conv1(x) 341 | # 1 x 1 x 768 342 | x = x.view(x.size(0), -1) 343 | # 768 344 | x = self.fc(x) 345 | # 1000 346 | return x 347 | 348 | 349 | class BasicConv2d(nn.Module): 350 | 351 | def __init__(self, in_channels, out_channels, **kwargs): 352 | super(BasicConv2d, self).__init__() 353 | self.conv = nn.Conv2d(in_channels, out_channels, bias=False, **kwargs) 354 | self.bn = nn.BatchNorm2d(out_channels, eps=0.001) 355 | 356 | def forward(self, x): 357 | x = self.conv(x) 358 | x = self.bn(x) 359 | return F.relu(x, inplace=True) 360 | 361 | 362 | #####################################################################################################3 363 | 364 | 365 | def run_check_net(): 366 | 367 | # https://discuss.pytorch.org/t/print-autograd-graph/692/8 368 | batch_size = 1 369 | num_classes = 5000 370 | C,H,W = 3,128,128 371 | 372 | inputs = torch.randn(batch_size,C,H,W) 373 | labels = torch.randn(batch_size,num_classes) 374 | in_shape = inputs.size()[1:] 375 | 376 | net = Inception3(in_shape=in_shape, num_classes=num_classes) 377 | net.load_pretrain_pytorch_file( 378 | '/root/share/data/models/pytorch/imagenet/inception/inception_v3_google-1a9a5a14.pth', 379 | skip=['fc.weight' ,'fc.bias'] 380 | ) 381 | net.cuda() 382 | net.train() 383 | 384 | x = Variable(inputs).cuda() 385 | y = Variable(labels).cuda() 386 | logits = net.forward(x) 387 | probs = F.softmax(logits) 388 | 389 | loss = F.binary_cross_entropy_with_logits(logits, y) 390 | loss.backward() 391 | 392 | print(type(net)) 393 | print(net) 394 | 395 | print('probs') 396 | print(probs) 397 | 398 | #merging 399 | # net.eval() 400 | # net.merge_bn() 401 | 402 | 403 | ######################################################################################## 404 | if __name__ == '__main__': 405 | print( '%s: calling main function ... ' % os.path.basename(__file__)) 406 | 407 | run_check_net() 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | -------------------------------------------------------------------------------- /src/heng_cherkeng/resnet101.py: -------------------------------------------------------------------------------- 1 | import os 2 | from torch.autograd import Variable 3 | import torch 4 | import torch.nn as nn 5 | import torch.nn.functional as F 6 | import cv2 7 | import numpy as np 8 | 9 | #https://github.com/pytorch/vision/blob/master/torchvision/models/resnet.py 10 | 11 | #----- helper functions ------------------------------ 12 | BN_EPS = 1e-5 #1e-4 #1e-5 13 | 14 | 15 | class ConvBn2d(nn.Module): 16 | 17 | def merge_bn(self): 18 | #raise NotImplementedError 19 | assert(self.conv.bias==None) 20 | conv_weight = self.conv.weight.data 21 | bn_weight = self.bn.weight.data 22 | bn_bias = self.bn.bias.data 23 | bn_running_mean = self.bn.running_mean 24 | bn_running_var = self.bn.running_var 25 | bn_eps = self.bn.eps 26 | 27 | #https://github.com/sanghoon/pva-faster-rcnn/issues/5 28 | #https://github.com/sanghoon/pva-faster-rcnn/commit/39570aab8c6513f0e76e5ab5dba8dfbf63e9c68c 29 | 30 | N,C,KH,KW = conv_weight.size() 31 | std = 1/(torch.sqrt(bn_running_var+bn_eps)) 32 | std_bn_weight =(std*bn_weight).repeat(C*KH*KW,1).t().contiguous().view(N,C,KH,KW ) 33 | conv_weight_hat = std_bn_weight*conv_weight 34 | conv_bias_hat = (bn_bias - bn_weight*std*bn_running_mean) 35 | 36 | self.is_bn = False 37 | self.bn = None 38 | self.conv = nn.Conv2d(in_channels=self.conv.in_channels, out_channels=self.conv.out_channels, kernel_size=self.conv.kernel_size, 39 | padding=self.conv.padding, stride=self.conv.stride, dilation=self.conv.dilation, groups=self.conv.groups, 40 | bias=True) 41 | self.conv.weight.data = conv_weight_hat #fill in 42 | self.conv.bias.data = conv_bias_hat 43 | 44 | 45 | def __init__(self, in_channels, out_channels, kernel_size=3, padding=1, dilation=1, stride=1, groups=1): 46 | super(ConvBn2d, self).__init__() 47 | self.is_bn = True 48 | 49 | self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, padding=padding, stride=stride, dilation=dilation, groups=groups, bias=False) 50 | self.bn = nn.BatchNorm2d(out_channels, eps=BN_EPS) 51 | 52 | def forward(self,x): 53 | x = self.conv(x) 54 | if self.is_bn : 55 | x = self.bn(x) 56 | 57 | return x 58 | 59 | 60 | 61 | class Bottleneck(nn.Module): 62 | def __init__(self, in_planes, planes, out_planes, is_downsample=False, stride=1): 63 | super(Bottleneck, self).__init__() 64 | self.is_downsample = is_downsample 65 | 66 | self.conv_bn1 = ConvBn2d(in_planes, planes, kernel_size=1, padding=0, stride=1) 67 | self.conv_bn2 = ConvBn2d( planes, planes, kernel_size=3, padding=1, stride=stride) 68 | self.conv_bn3 = ConvBn2d( planes, out_planes, kernel_size=1, padding=0, stride=1) 69 | 70 | if self.is_downsample: 71 | self.downsample = ConvBn2d(in_planes, out_planes, kernel_size=1, padding=0, stride=stride) 72 | 73 | 74 | def forward(self, x): 75 | 76 | z = self.conv_bn1(x) 77 | z = F.relu(z,inplace=True) 78 | z = self.conv_bn2(z) 79 | z = F.relu(z,inplace=True) 80 | z = self.conv_bn3(z) 81 | 82 | if self.is_downsample: 83 | z += self.downsample(x) 84 | else: 85 | z += x 86 | 87 | z = F.relu(z,inplace=True) 88 | return z 89 | 90 | 91 | #resnet 92 | def make_layer(in_planes, planes, out_planes, num_blocks, stride): 93 | layers = [] 94 | layers.append(Bottleneck(in_planes, planes, out_planes, is_downsample=True, stride=stride)) 95 | for i in range(1, num_blocks): 96 | layers.append(Bottleneck(out_planes, planes, out_planes)) 97 | 98 | return nn.Sequential(*layers) 99 | 100 | def make_layer0(in_channels, out_planes): 101 | layers = [ 102 | ConvBn2d(in_channels, out_planes, kernel_size=7, stride=2, padding=3), 103 | nn.ReLU(inplace=True), 104 | nn.MaxPool2d(kernel_size=3, stride=2, padding=1), 105 | ] 106 | return nn.Sequential(*layers) 107 | 108 | 109 | 110 | ## resenet ## 111 | class ResNet101(nn.Module): 112 | 113 | def load_pretrain_file(self,pretrain_file, skip=[]): 114 | 115 | pretrain_state_dict = torch.load(pretrain_file) 116 | state_dict = self.state_dict() 117 | 118 | keys = list(state_dict.keys()) 119 | for key in keys: 120 | if any(s in key for s in skip): 121 | continue 122 | 123 | pretrain_key = key 124 | if 'layer0.0.conv.' in key: pretrain_key=key.replace('layer0.0.conv.', 'conv1.' ) 125 | if 'layer0.0.bn.' in key: pretrain_key=key.replace('layer0.0.bn.', 'bn1.' ) 126 | if '.conv_bn1.conv.' in key: pretrain_key=key.replace('.conv_bn1.conv.', '.conv1.') 127 | if '.conv_bn1.bn.' in key: pretrain_key=key.replace('.conv_bn1.bn.', '.bn1.' ) 128 | if '.conv_bn2.conv.' in key: pretrain_key=key.replace('.conv_bn2.conv.', '.conv2.') 129 | if '.conv_bn2.bn.' in key: pretrain_key=key.replace('.conv_bn2.bn.', '.bn2.' ) 130 | if '.conv_bn3.conv.' in key: pretrain_key=key.replace('.conv_bn3.conv.', '.conv3.') 131 | if '.conv_bn3.bn.' in key: pretrain_key=key.replace('.conv_bn3.bn.', '.bn3.' ) 132 | if '.downsample.conv.'in key: pretrain_key=key.replace('.downsample.conv.', '.downsample.0.') 133 | if '.downsample.bn.' in key: pretrain_key=key.replace('.downsample.bn.', '.downsample.1.') 134 | 135 | #print('%36s'%key, ' ','%-36s'%pretrain_key) 136 | state_dict[key] = pretrain_state_dict[pretrain_key] 137 | 138 | self.load_state_dict(state_dict) 139 | #torch.save(state_dict,save_model_file) 140 | 141 | 142 | def merge_bn(self): 143 | print ('merging bn ....') 144 | 145 | for name, m in self.named_modules(): 146 | if isinstance(m, (ConvBn2d,)): 147 | print('\t%s'%name) 148 | m.merge_bn() 149 | print('') 150 | 151 | #----------------------------------------------------------------------- 152 | def __init__(self, in_shape=(3,180,180), num_classes=5270 ): 153 | 154 | super(ResNet101, self).__init__() 155 | in_channels, height, width = in_shape 156 | self.num_classes=num_classes 157 | 158 | self.layer0 = make_layer0(in_channels, 64) 159 | self.layer1 = make_layer( 64, 64, 256, num_blocks= 3, stride=1) #out = 64*4 = 256 160 | self.layer2 = make_layer( 256, 128, 512, num_blocks= 4, stride=2) #out = 128*4 = 512 161 | self.layer3 = make_layer( 512, 256, 1024, num_blocks=23, stride=2) #out = 256*4 = 1024 162 | self.layer4 = make_layer( 1024, 512, 2048, num_blocks= 3, stride=2) #out = 512*4 = 2048 163 | self.fc = nn.Linear(2048, num_classes) 164 | 165 | 166 | # for m in self.modules(): 167 | # if isinstance(m, nn.Conv2d): 168 | # n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels 169 | # m.weight.data.normal_(0, math.sqrt(2. / n)) 170 | # elif isinstance(m, nn.BatchNorm2d): 171 | # m.weight.data.fill_(1) 172 | # m.bias.data.zero_() 173 | 174 | def forward(self, x): 175 | #x #; print('input ' ,x.size()) 176 | x = self.layer0(x) #; print('layer0 ',x.size()) 177 | x = self.layer1(x) #; print('layer1 ',x.size()) 178 | x = self.layer2(x) #; print('layer2 ',x.size()) 179 | x = self.layer3(x) #; print('layer3 ',x.size()) 180 | x = self.layer4(x) #; print('layer4 ',x.size()) 181 | 182 | x = F.adaptive_avg_pool2d(x, output_size=1) 183 | x = x.view(x.size(0), -1) 184 | x = self.fc (x) 185 | return x #logits 186 | 187 | 188 | 189 | ######################################################################################################## 190 | 191 | # test some images 192 | # https://github.com/soeaver/caffe-model/blob/master/cls/synset.txt 193 | # https://github.com/ruotianluo/pytorch-resnet/blob/master/synset.py () 194 | # 195 | # (441) 810 n02823750 beer glass 196 | # ( 1) 449 n01443537 goldfish, Carassius auratus 197 | # ( 9) 384 n01518878 ostrich, Struthio camelus 198 | # ( 22) 397 n01614925 bald eagle, American eagle, Haliaeetus leucocephalus 199 | # (281) 173 n02123045 tabby, tabby cat 200 | 201 | 202 | def run_check_net_imagenet(): 203 | num_classes = 1000 204 | C,H,W = 3,224,224 205 | net = ResNet101(in_shape=(C,H,W), num_classes=num_classes) 206 | net.load_pretrain_file( 207 | '/root/share/data/models/reference/imagenet/resnet/resnet101-5d3b4d8f.pth', 208 | skip=[] 209 | ) 210 | #net.cuda() 211 | net.eval() 212 | 213 | 214 | image = cv2.imread('/root/share/data/imagenet/dummy/256x256/beer_glass.jpg') 215 | #image = cv2.imread('/root/share/data/imagenet/dummy/256x256/goldfish.jpg') 216 | #image = cv2.imread('/root/share/data/imagenet/dummy/256x256/blad_eagle.jpg') 217 | #image = cv2.imread('/root/share/data/imagenet/dummy/256x256/ostrich.jpg') 218 | #image = cv2.imread('/root/share/data/imagenet/dummy/256x256/tabby_cat.jpg') 219 | #image = cv2.imread('/root/share/data/imagenet/dummy/256x256/bullet_train.jpg') 220 | 221 | 222 | #pre process ---- 223 | image = cv2.cvtColor(image,cv2.COLOR_BGR2RGB) 224 | image = cv2.resize(image,(224,224)).astype(np.float32) 225 | image = image.transpose((2,0,1)) 226 | image = image/255 227 | 228 | mean = [0.485, 0.456, 0.406 ] 229 | std = [0.229, 0.224, 0.225 ] 230 | image[0] = (image[0] - mean[0]) / std[0] 231 | image[1] = (image[1] - mean[1]) / std[1] 232 | image[2] = (image[2] - mean[2]) / std[2] 233 | #pre process ---- 234 | 235 | 236 | #run net 237 | logits = net( Variable(torch.from_numpy(image).unsqueeze(0).float() ) ) 238 | probs = F.softmax(logits,dim=1).data.numpy().reshape(-1) 239 | #print('probs\n',probs) 240 | 241 | #check 242 | print('results ', np.argmax(probs), ' ', probs[np.argmax(probs)]) 243 | 244 | 245 | 246 | def run_check_net(): 247 | 248 | # https://discuss.pytorch.org/t/print-autograd-graph/692/8 249 | batch_size = 1 250 | num_classes = 5270 251 | C,H,W = 3,180,180 252 | 253 | inputs = torch.randn(batch_size,C,H,W) 254 | labels = torch.randn(batch_size,num_classes) 255 | in_shape = inputs.size()[1:] 256 | 257 | net = ResNet101(in_shape=in_shape, num_classes=num_classes) 258 | net.load_pretrain_file( 259 | '/root/share/data/models/reference/imagenet/resnet/resnet101-5d3b4d8f.pth', 260 | skip=['fc.'] 261 | ) 262 | net.cuda() 263 | net.train() 264 | 265 | x = Variable(inputs).cuda() 266 | y = Variable(labels).cuda() 267 | logits = net.forward(x) 268 | probs = F.softmax(logits,dim=1) 269 | 270 | loss = F.binary_cross_entropy_with_logits(logits, y) 271 | loss.backward() 272 | 273 | print(type(net)) 274 | #print(net) 275 | 276 | print('probs') 277 | print(probs) 278 | 279 | #merging ---- 280 | # net.eval() 281 | # net.merge_bn() 282 | 283 | 284 | 285 | ######################################################################################## 286 | if __name__ == '__main__': 287 | print( '%s: calling main function ... ' % os.path.basename(__file__)) 288 | 289 | 290 | #run_check_net() 291 | run_check_net_imagenet() 292 | 293 | -------------------------------------------------------------------------------- /src/heng_cherkeng/xception.py: -------------------------------------------------------------------------------- 1 | # https://arxiv.org/pdf/1610.02357.pdf 2 | 3 | 4 | # "Xception: Deep Learning with Depthwise Separable Convolutions" - Francois Chollet (Google, Inc), CVPR 2017 5 | 6 | # separable conv pytorch 7 | # https://github.com/szagoruyko/pyinn 8 | # https://github.com/pytorch/pytorch/issues/1708 9 | # https://discuss.pytorch.org/t/separable-convolutions-in-pytorch/3407/2 10 | # https://discuss.pytorch.org/t/depthwise-and-separable-convolutions-in-pytorch/7315/3 11 | 12 | import os 13 | from torch.autograd import Variable 14 | import torch 15 | import torch.nn as nn 16 | import torch.nn.functional as F 17 | import pyinn as P 18 | from pyinn.modules import Conv2dDepthwise 19 | 20 | #----- helper functions ------------------------------ 21 | BN_EPS = 1e-4 #1e-4 #1e-5 22 | 23 | class ConvBn2d(nn.Module): 24 | 25 | def merge_bn(self): 26 | #raise NotImplementedError 27 | assert(self.conv.bias==None) 28 | conv_weight = self.conv.weight.data 29 | bn_weight = self.bn.weight.data 30 | bn_bias = self.bn.bias.data 31 | bn_running_mean = self.bn.running_mean 32 | bn_running_var = self.bn.running_var 33 | bn_eps = self.bn.eps 34 | 35 | #https://github.com/sanghoon/pva-faster-rcnn/issues/5 36 | #https://github.com/sanghoon/pva-faster-rcnn/commit/39570aab8c6513f0e76e5ab5dba8dfbf63e9c68c 37 | 38 | N,C,KH,KW = conv_weight.size() 39 | std = 1/(torch.sqrt(bn_running_var+bn_eps)) 40 | std_bn_weight =(std*bn_weight).repeat(C*KH*KW,1).t().contiguous().view(N,C,KH,KW ) 41 | conv_weight_hat = std_bn_weight*conv_weight 42 | conv_bias_hat = (bn_bias - bn_weight*std*bn_running_mean) 43 | 44 | self.bn = None 45 | self.conv = nn.Conv2d(in_channels=self.conv.in_channels, out_channels=self.conv.out_channels, kernel_size=self.conv.kernel_size, 46 | padding=self.conv.padding, stride=self.conv.stride, dilation=self.conv.dilation, groups=self.conv.groups, 47 | bias=True) 48 | self.conv.weight.data = conv_weight_hat #fill in 49 | self.conv.bias.data = conv_bias_hat 50 | 51 | 52 | def __init__(self, in_channels, out_channels, kernel_size=3, padding=1, dilation=1, stride=1, groups=1, is_bn=True): 53 | super(ConvBn2d, self).__init__() 54 | self.conv = nn.Conv2d(in_channels, out_channels, kernel_size=kernel_size, padding=padding, stride=stride, dilation=dilation, groups=groups, bias=False) 55 | self.bn = nn.BatchNorm2d(out_channels, eps=BN_EPS) 56 | 57 | if is_bn is False: 58 | self.bn =None 59 | 60 | def forward(self,x): 61 | x = self.conv(x) 62 | if self.bn is not None: 63 | x = self.bn(x) 64 | return x 65 | 66 | 67 | # ---- 68 | class SeparableConvBn2d(nn.Module): 69 | 70 | def __init__(self, in_channels, out_channels, kernel_size=3, padding=1, stride=1, is_bn=True): 71 | super(SeparableConvBn2d, self).__init__() 72 | 73 | #self.conv1 = nn.Conv2d(in_channels, in_channels, kernel_size=kernel_size, padding=padding, stride=stride, groups=in_channels, bias=False) #depth_wise 74 | #self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0, stride=1, bias=False) #point_wise 75 | 76 | self.conv1 = Conv2dDepthwise(in_channels, kernel_size=kernel_size, padding=padding, stride=stride, bias=False) 77 | self.conv2 = nn.Conv2d(in_channels, out_channels, kernel_size=1, padding=0, stride=1, bias=False) 78 | self.bn = nn.BatchNorm2d(out_channels, eps=BN_EPS) 79 | 80 | 81 | def forward(self,x): 82 | x = self.conv1(x) 83 | x = self.conv2(x) 84 | x = self.bn(x) 85 | return x 86 | 87 | # 88 | class SBlock(nn.Module): 89 | 90 | def __init__(self, in_channels, channels, out_channels, is_first_relu=True): 91 | super(SBlock, self).__init__() 92 | self.is_first_relu=is_first_relu 93 | 94 | self.downsample = ConvBn2d(in_channels, out_channels, kernel_size=1, padding=0, stride=2) 95 | self.conv1 = SeparableConvBn2d(in_channels, channels, kernel_size=3, padding=1, stride=1) 96 | self.conv2 = SeparableConvBn2d( channels, out_channels, kernel_size=3, padding=1, stride=1) 97 | 98 | def forward(self,x): 99 | residual = self.downsample(x) 100 | if self.is_first_relu: 101 | x = F.relu(x,inplace=False) 102 | x = self.conv1(x) 103 | x = F.relu(x,inplace=True) 104 | x = self.conv2(x) 105 | x = F.max_pool2d(x, kernel_size=3, padding=1, stride=2) 106 | x = x + residual 107 | 108 | return x 109 | 110 | 111 | 112 | class XBlock(nn.Module): 113 | 114 | def __init__(self, in_channels): 115 | super(XBlock, self).__init__() 116 | 117 | self.conv1 = SeparableConvBn2d(in_channels, in_channels, kernel_size=3, padding=1, stride=1) 118 | self.conv2 = SeparableConvBn2d(in_channels, in_channels, kernel_size=3, padding=1, stride=1) 119 | self.conv3 = SeparableConvBn2d(in_channels, in_channels, kernel_size=3, padding=1, stride=1) 120 | 121 | def forward(self,x): 122 | 123 | residual = x 124 | x = F.relu(x,inplace=True) 125 | x = self.conv1(x) 126 | x = F.relu(x,inplace=True) 127 | x = self.conv2(x) 128 | x = F.relu(x,inplace=True) 129 | x = self.conv3(x) 130 | x = x + residual 131 | 132 | return x 133 | 134 | 135 | 136 | class EBlock(nn.Module): 137 | 138 | def __init__(self, in_channels, channels, out_channels): 139 | super(EBlock, self).__init__() 140 | 141 | self.conv1 = SeparableConvBn2d(in_channels, channels, kernel_size=3, padding=1, stride=1) 142 | self.conv2 = SeparableConvBn2d(channels,out_channels, kernel_size=3, padding=1, stride=1) 143 | 144 | 145 | def forward(self,x): 146 | 147 | x = self.conv1(x) 148 | x = F.relu(x,inplace=True) 149 | x = self.conv2(x) 150 | x = F.relu(x,inplace=True) 151 | 152 | return x 153 | 154 | 155 | class Xception(nn.Module): 156 | 157 | def load_pretrain_pytorch_file(self,pytorch_file, skip=[]): 158 | pytorch_state_dict = torch.load(pytorch_file,map_location=lambda storage, loc: storage) 159 | state_dict = self.state_dict() 160 | keys = list(state_dict.keys()) 161 | for key in keys: 162 | if any(s in key for s in skip): 163 | continue 164 | #print(key) 165 | state_dict[key] = pytorch_state_dict[key] 166 | self.load_state_dict(state_dict) 167 | 168 | #----------------------------------------------------------------------- 169 | 170 | def __init__(self, in_shape=(3,128,128), num_classes=5000 ): 171 | super(Xception, self).__init__() 172 | in_channels, height, width = in_shape 173 | self.num_classes = num_classes 174 | 175 | self.entry0 = nn.Sequential( 176 | ConvBn2d(in_channels, 32, kernel_size=3, stride=2, padding=1), 177 | nn.ReLU(inplace=True), 178 | ConvBn2d(32, 64, kernel_size=3, stride=1, padding=0), 179 | nn.ReLU(inplace=True), 180 | ) 181 | self.entry1 = SBlock( 64,128,128,is_first_relu=False) 182 | self.entry2 = SBlock(128,256,256) 183 | self.entry3 = SBlock(256,728,728) 184 | 185 | self.middle1 = XBlock(728) 186 | self.middle2 = XBlock(728) 187 | self.middle3 = XBlock(728) 188 | self.middle4 = XBlock(728) 189 | self.middle5 = XBlock(728) 190 | self.middle6 = XBlock(728) 191 | self.middle7 = XBlock(728) 192 | self.middle8 = XBlock(728) 193 | 194 | self.exit1 = SBlock( 728, 728,1024) 195 | self.exit2 = EBlock(1024,1536,2048) 196 | self.fc = nn.Linear(2048, num_classes) 197 | 198 | 199 | def forward(self,x): 200 | 201 | x = self.entry0(x) #; print('entry0 ', x.size()) 202 | x = self.entry1(x) #; print('entry1 ', x.size()) 203 | x = self.entry2(x) #; print('entry2 ', x.size()) 204 | x = self.entry3(x) #; print('entry3 ', x.size()) 205 | x = self.middle1(x) #; print('middle1 ',x.size()) 206 | x = self.middle2(x) #; print('middle2 ',x.size()) 207 | x = self.middle3(x) #; print('middle3 ',x.size()) 208 | x = self.middle4(x) #; print('middle4 ',x.size()) 209 | x = self.middle5(x) #; print('middle5 ',x.size()) 210 | x = self.middle6(x) #; print('middle6 ',x.size()) 211 | x = self.middle7(x) #; print('middle7 ',x.size()) 212 | x = self.middle8(x) #; print('middle8 ',x.size()) 213 | x = self.exit1(x) #; print('exit1 ',x.size()) 214 | x = self.exit2(x) #; print('exit2 ',x.size()) 215 | 216 | x = F.adaptive_avg_pool2d(x, output_size=1) 217 | x = x.view(x.size(0), -1) 218 | x = F.dropout(x, training=self.training, p=0.2) # 219 | x = self.fc (x) 220 | return x #logits 221 | 222 | 223 | ######################################################################################################## 224 | 225 | 226 | def run_check_net(): 227 | 228 | # https://discuss.pytorch.org/t/print-autograd-graph/692/8 229 | batch_size = 1 230 | num_classes = 5000 231 | C,H,W = 3,180,180 232 | 233 | inputs = torch.randn(batch_size,C,H,W) 234 | labels = torch.randn(batch_size,num_classes) 235 | in_shape = inputs.size()[1:] 236 | 237 | 238 | net = Xception(in_shape=in_shape, num_classes=num_classes) 239 | net.load_pretrain_pytorch_file( 240 | '/root/share/data/models/reference/imagenet/xception/caffe-model/inception/xception/xception.keras.convert.pth', 241 | skip=['fc.weight' ,'fc.bias'] 242 | ) 243 | net.cuda().train() 244 | 245 | x = Variable(inputs).cuda() 246 | y = Variable(labels).cuda() 247 | logits = net.forward(x) 248 | probs = F.softmax(logits) 249 | 250 | loss = F.binary_cross_entropy_with_logits(logits, y) 251 | loss.backward() 252 | 253 | print(type(net)) 254 | print(net) 255 | 256 | print('probs') 257 | print(probs) 258 | 259 | #merging 260 | # net.eval() 261 | # net.merge_bn() 262 | 263 | 264 | ######################################################################################## 265 | if __name__ == '__main__': 266 | print( '%s: calling main function ... ' % os.path.basename(__file__)) 267 | 268 | run_check_net() 269 | 270 | -------------------------------------------------------------------------------- /src/model/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NighTurs/kaggle-cdiscount-image-classification/3646ee4dc7a0e35dfe6fb4cdaadcf2fb7b30d3a5/src/model/__init__.py -------------------------------------------------------------------------------- /src/model/bcolz_iterator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import bcolz 3 | import threading 4 | from keras.preprocessing.image import Iterator 5 | 6 | CHUNK_SIZE = 100000 7 | 8 | 9 | class BcolzIterator(): 10 | def __init__(self, bcolz_root, x_idxs, side_input=None, y=None, num_classes=None, batch_size=32, shuffle=True, seed=None): 11 | self.x = bcolz.open(bcolz_root) 12 | self.side_x = side_input 13 | self.x_idxs = x_idxs 14 | self.y = y 15 | self.num_classes = num_classes 16 | self.samples = len(self.x_idxs) 17 | self.batch_size = batch_size 18 | assert CHUNK_SIZE % batch_size == 0 19 | self.shuffle = shuffle 20 | if seed: 21 | np.random.seed(seed) 22 | self.chunk_idx = -1 23 | self.next_idx = -1 24 | self.thread = threading.Thread(target=self.preload, args=(0,)) 25 | self.thread.start() 26 | self.get_chunk() 27 | 28 | def preload(self, idx): 29 | idxs = self.x_idxs[(CHUNK_SIZE * idx):(CHUNK_SIZE * idx + CHUNK_SIZE)] 30 | self.preload_x = self.x[idxs] 31 | if self.side_x is not None: 32 | self.preload_side_x = self.side_x[idxs] 33 | 34 | def get_chunk(self): 35 | self.chunk_idx += 1 36 | if CHUNK_SIZE * self.chunk_idx >= len(self.x_idxs): 37 | self.chunk_idx = 0 38 | self.next_idx = self.chunk_idx + 1 39 | if CHUNK_SIZE * self.next_idx >= len(self.x_idxs): 40 | self.next_idx = 0 41 | idxs = self.x_idxs[(CHUNK_SIZE * self.chunk_idx):(CHUNK_SIZE * self.chunk_idx + CHUNK_SIZE)] 42 | 43 | self.thread.join() 44 | self.chunk_x = self.preload_x 45 | if self.side_x is not None: 46 | self.chunk_side_x = self.preload_side_x 47 | self.thread = threading.Thread(target=self.preload, args=(self.next_idx,)) 48 | self.thread.start() 49 | 50 | if self.y is not None: 51 | self.chunk_y = self.y[(CHUNK_SIZE * self.chunk_idx):(CHUNK_SIZE * self.chunk_idx + CHUNK_SIZE)] 52 | self.chunk_seen = 0 53 | self.it = Iterator(len(idxs), self.batch_size, self.shuffle, None) 54 | 55 | def next(self): 56 | if self.chunk_x.shape[0] <= self.chunk_seen: 57 | self.get_chunk() 58 | index_array = next(self.it.index_generator) 59 | if self.side_x is not None: 60 | out_x = [self.chunk_x[index_array[0]], self.chunk_side_x[index_array[0]]] 61 | else: 62 | out_x = self.chunk_x[index_array[0]] 63 | if self.y is not None: 64 | out = out_x, self.chunk_y[index_array[0]] 65 | else: 66 | out = out_x 67 | self.chunk_seen += len(index_array[0]) 68 | return out 69 | 70 | def __iter__(self): 71 | return self 72 | 73 | def __next__(self, *args, **kwargs): 74 | return self.next(*args, **kwargs) 75 | -------------------------------------------------------------------------------- /src/model/bcolz_to_memmap.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import bcolz 3 | import numpy as np 4 | from tqdm import tqdm 5 | 6 | if __name__ == '__main__': 7 | parser = argparse.ArgumentParser() 8 | parser.add_argument('--bcolz_path', required=True, help='Path to bcolz with vectors') 9 | parser.add_argument('--memmap_path', required=True, help="Write memmap to path") 10 | 11 | args = parser.parse_args() 12 | 13 | bcolz_path = args.bcolz_path 14 | memmap_path = args.memmap_path 15 | 16 | a = bcolz.open(bcolz_path) 17 | b = np.memmap(memmap_path, dtype='float32', mode='w+', shape=a.shape) 18 | 19 | with tqdm(total=a.shape[0]) as pbar: 20 | batch_size = 100000 21 | for i in range(0, a.shape[0], batch_size): 22 | chunk = a[i:(i + batch_size)] 23 | b[i:(i + batch_size)] = chunk 24 | b.flush() 25 | pbar.update(chunk.shape[0]) -------------------------------------------------------------------------------- /src/model/bson_iterator.py: -------------------------------------------------------------------------------- 1 | import io 2 | import numpy as np 3 | import bson 4 | from keras.preprocessing.image import Iterator 5 | from keras.preprocessing.image import load_img, img_to_array 6 | from keras import backend as K 7 | 8 | 9 | class BSONIterator(Iterator): 10 | def __init__(self, bson_file, images_df, num_class, 11 | image_data_generator, lock, target_size=(180, 180), 12 | with_labels=True, batch_size=32, shuffle=False, seed=None): 13 | 14 | self.file = bson_file 15 | self.images_df = images_df 16 | self.with_labels = with_labels 17 | self.samples = len(images_df) 18 | self.num_class = num_class 19 | self.image_data_generator = image_data_generator 20 | self.target_size = tuple(target_size) 21 | self.image_shape = (3,) + self.target_size 22 | 23 | super(BSONIterator, self).__init__(self.samples, batch_size, shuffle, seed) 24 | self.lock = lock 25 | 26 | def _get_batches_of_transformed_samples(self, index_array): 27 | batch_x = np.zeros((len(index_array),) + self.image_shape, dtype=K.floatx()) 28 | if self.with_labels: 29 | batch_y = np.zeros((len(batch_x), self.num_class), dtype=K.floatx()) 30 | 31 | for i, j in enumerate(index_array): 32 | # Protect file and dataframe access with a lock. 33 | with self.lock: 34 | image_row = self.images_df.iloc[j] 35 | 36 | # Read this product's data from the BSON file. 37 | self.file.seek(image_row["offset"]) 38 | item_data = self.file.read(image_row["length"]) 39 | 40 | # Grab the image from the product. 41 | item = bson.BSON.decode(item_data) 42 | img_idx = image_row["img_idx"] 43 | bson_img = item["imgs"][img_idx]["picture"] 44 | 45 | # Load the image. 46 | img = load_img(io.BytesIO(bson_img), target_size=self.target_size) 47 | 48 | # Preprocess the image. 49 | x = img_to_array(img) 50 | x = self.image_data_generator.random_transform(x) 51 | x = self.image_data_generator.standardize(x) 52 | 53 | # Add the image and the label to the batch (one-hot encoded). 54 | batch_x[i] = x 55 | if self.with_labels: 56 | batch_y[i, image_row["category_idx"]] = 1 57 | 58 | if self.with_labels: 59 | return batch_x, batch_y 60 | else: 61 | return batch_x 62 | 63 | def next(self): 64 | with self.lock: 65 | index_array = next(self.index_generator) 66 | return self._get_batches_of_transformed_samples(index_array[0]) 67 | -------------------------------------------------------------------------------- /src/model/combine_iterator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | 4 | class CombineIterator(): 5 | def __init__(self, first_iterator, second_iterator): 6 | self.first_iterator = first_iterator 7 | self.second_iterator = second_iterator 8 | self.batch_size = first_iterator.batch_size + second_iterator.batch_size 9 | self.samples = first_iterator.samples + second_iterator.samples 10 | 11 | def next(self): 12 | first_out = self.first_iterator.next() 13 | second_out = self.second_iterator.next() 14 | if type(first_out[0]) is list: 15 | x = [np.concatenate((x1, x2)) for x1, x2 in zip(first_out[0], second_out[0])] 16 | else: 17 | x = np.concatenate((first_out[0], second_out[0])) 18 | y = np.concatenate((first_out[1], second_out[1])) 19 | return x, y 20 | 21 | def __iter__(self): 22 | return self 23 | 24 | def __next__(self, *args, **kwargs): 25 | return self.next(*args, **kwargs) 26 | -------------------------------------------------------------------------------- /src/model/ensemble_fixed_weights.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import pandas as pd 3 | import os 4 | import argparse 5 | import itertools 6 | from tqdm import tqdm 7 | from collections import namedtuple 8 | 9 | TOP_PREDS = 10 10 | PREDICTIONS_FILE = 'predictions.csv' 11 | 12 | if __name__ == '__main__': 13 | parser = argparse.ArgumentParser() 14 | parser.add_argument('--preds_csvs', nargs='+', required=True, help='Files with predictions of test dataset') 15 | parser.add_argument('--weights', nargs='+', type=float, required=True, help='Weight of each model') 16 | parser.add_argument('--model_dir', required=True, help='Model directory') 17 | args = parser.parse_args() 18 | 19 | if len(args.preds_csvs) != len(args.weights): 20 | raise ValueError('Count of weights should much count of csvs') 21 | 22 | preds_csvs = args.preds_csvs 23 | weights = args.weights 24 | model_dir = args.model_dir 25 | 26 | all_preds = [] 27 | for i, csv in enumerate(preds_csvs): 28 | preds = pd.read_csv(csv, dtype={'product_id': np.int32, 29 | 'img_idx': np.int8, 30 | 'category_idx': np.int16, 31 | 'prob': np.float32}) 32 | preds['prob'] = preds['prob'] * weights[i] 33 | preds.sort_values(['product_id', 'img_idx'], inplace=True) 34 | all_preds.append(preds) 35 | 36 | prev_img = (0, 0) 37 | prev_cat = 0 38 | sum_prob = 0 39 | sum_all_probs = 0 40 | cat_prob = [] 41 | 42 | if not os.path.isdir(model_dir): 43 | os.mkdir(model_dir) 44 | 45 | # Can't concatenate and sort all preds simultaneously because of memory problems 46 | def preds_gen(all_preds): 47 | iters = [pred.itertuples() for pred in all_preds] 48 | while True: 49 | rows = [] 50 | for iter in iters: 51 | for i in range(TOP_PREDS): 52 | rows.append(next(iter)) 53 | rows.sort(key=lambda x: x.category_idx) 54 | for row in rows: 55 | yield row 56 | 57 | with tqdm(total=sum([preds.shape[0] for preds in all_preds])) as pbar, \ 58 | open(os.path.join(model_dir, PREDICTIONS_FILE), 'w') as out: 59 | out.write('product_id,img_idx,category_idx,prob\n') 60 | for row in itertools.chain(preds_gen(all_preds), 61 | [namedtuple('Pandas', ['product_id', 'img_idx', 'category_idx', 'prob'])(0, 0, 0, 62 | 0)]): 63 | product_id = row.product_id 64 | img_idx = row.img_idx 65 | category_idx = row.category_idx 66 | prob = row.prob 67 | if prev_img == (product_id, img_idx): 68 | if prev_cat == category_idx: 69 | sum_prob += prob 70 | else: 71 | cat_prob.append((prev_cat, sum_prob)) 72 | sum_all_probs += sum_prob 73 | prev_cat = category_idx 74 | sum_prob = prob 75 | else: 76 | cat_prob.append((prev_cat, sum_prob)) 77 | sum_all_probs += sum_prob 78 | cat_prob = sorted(cat_prob, key=lambda x: x[1], reverse=True)[:TOP_PREDS] 79 | if prev_img != (0, 0): 80 | for cat in cat_prob: 81 | out.write('{},{},{},{}\n'.format(prev_img[0], prev_img[1], cat[0], cat[1] / sum_all_probs)) 82 | prev_img = (product_id, img_idx) 83 | prev_cat = category_idx 84 | sum_prob = prob 85 | sum_all_probs = 0 86 | cat_prob = [] 87 | pbar.update() -------------------------------------------------------------------------------- /src/model/form_submission.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | import numpy as np 4 | from ..data.category_idx import index_to_category_dict 5 | 6 | 7 | def max_prob_category(df): 8 | i = df.prob.argmax() 9 | return df.category_idx.loc[i] 10 | 11 | 12 | def form_submission(preds, category_idx): 13 | preds.sort_values('prob', ascending=False, inplace=True) 14 | taken_prods = set() 15 | d = index_to_category_dict(category_idx) 16 | products = [] 17 | category_id = [] 18 | for row in preds.itertuples(): 19 | if row.product_id in taken_prods: 20 | continue 21 | taken_prods.add(row.product_id) 22 | products.append(row.product_id) 23 | category_id.append(d[row.category_idx]) 24 | submission = pd.Series(category_id, index=products) 25 | submission.rename('category_id', inplace=True) 26 | submission.index.rename(name='_id', inplace=True) 27 | return submission 28 | 29 | 30 | if __name__ == '__main__': 31 | parser = argparse.ArgumentParser() 32 | parser.add_argument('--preds_csv', required=True, help='File with predictions') 33 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 34 | parser.add_argument('--output_file', required=True, help='File to save submission into') 35 | 36 | args = parser.parse_args() 37 | preds = pd.read_csv(args.preds_csv, dtype={'category_idx': np.int16, 'prob': np.float32, 'product_id': np.int32, 38 | 'img_idx': np.int8}) 39 | category_idx = pd.read_csv(args.category_idx_csv) 40 | submission = form_submission(preds, category_idx) 41 | submission.to_csv(args.output_file, header=True) 42 | -------------------------------------------------------------------------------- /src/model/form_submission_mul.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | import numpy as np 4 | import itertools 5 | from tqdm import tqdm 6 | from collections import namedtuple 7 | from ..data.category_idx import index_to_category_dict 8 | 9 | DEFAULT_PROB = 0.001 10 | 11 | def max_prob_category(df): 12 | i = df.prob.argmax() 13 | return df.category_idx.loc[i] 14 | 15 | 16 | def form_submission(preds, category_idx): 17 | preds.sort_values(['product_id', 'category_idx'], ascending=False, inplace=True) 18 | d = index_to_category_dict(category_idx) 19 | cur = (0, 0) 20 | acc = 1 21 | imgs = 0 22 | max_acc = 0 23 | max_imgs = 1 24 | max_cat = 0 25 | products = [] 26 | category_id = [] 27 | with tqdm(total=preds.shape[0]) as pbar: 28 | for row in itertools.chain(preds.itertuples(), 29 | [namedtuple('Pandas', ['product_id', 'img_idx', 'category_idx', 'prob'])(0, 0, 0, 0)]): 30 | if cur == (row.product_id, row.category_idx): 31 | acc *= row.prob 32 | imgs += 1 33 | else: 34 | while imgs > max_imgs: 35 | max_imgs += 1 36 | max_acc *= DEFAULT_PROB 37 | while max_imgs > imgs: 38 | imgs += 1 39 | acc *= DEFAULT_PROB 40 | max_imgs = imgs 41 | if max_acc < acc: 42 | max_acc = acc 43 | max_cat = cur[1] 44 | if row.product_id != cur[0]: 45 | if cur != (0, 0): 46 | products.append(cur[0]) 47 | category_id.append(d[max_cat]) 48 | max_acc = 0 49 | max_imgs = 1 50 | cur = (row.product_id, row.category_idx) 51 | acc = row.prob 52 | imgs = 1 53 | pbar.update(1) 54 | submission = pd.Series(category_id, index=products) 55 | submission.rename('category_id', inplace=True) 56 | submission.index.rename(name='_id', inplace=True) 57 | return submission 58 | 59 | 60 | if __name__ == '__main__': 61 | parser = argparse.ArgumentParser() 62 | parser.add_argument('--preds_csv', required=True, help='File with predictions') 63 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 64 | parser.add_argument('--output_file', required=True, help='File to save submission into') 65 | 66 | args = parser.parse_args() 67 | preds = pd.read_csv(args.preds_csv, dtype={'category_idx': np.int16, 'prob': np.float32, 'product_id': np.int32, 68 | 'img_idx': np.int8}) 69 | category_idx = pd.read_csv(args.category_idx_csv) 70 | submission = form_submission(preds, category_idx) 71 | submission.to_csv(args.output_file, header=True) 72 | -------------------------------------------------------------------------------- /src/model/form_submission_sum.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | import numpy as np 4 | import itertools 5 | from tqdm import tqdm 6 | from collections import namedtuple 7 | from ..data.category_idx import index_to_category_dict 8 | 9 | DEFAULT_PROB = 0.001 10 | 11 | 12 | def max_prob_category(df): 13 | i = df.prob.argmax() 14 | return df.category_idx.loc[i] 15 | 16 | 17 | def form_submission(preds, category_idx): 18 | preds.sort_values(['product_id', 'category_idx'], ascending=False, inplace=True) 19 | d = index_to_category_dict(category_idx) 20 | cur = (0, 0) 21 | acc = 0 22 | max_acc = 0 23 | max_cat = 0 24 | products = [] 25 | category_id = [] 26 | with tqdm(total=preds.shape[0]) as pbar: 27 | for row in itertools.chain(preds.itertuples(), 28 | [namedtuple('Pandas', ['product_id', 'img_idx', 'category_idx', 'prob'])(0, 0, 0, 29 | 0)]): 30 | if cur == (row.product_id, row.category_idx): 31 | acc += row.prob 32 | else: 33 | if max_acc < acc: 34 | max_acc = acc 35 | max_cat = cur[1] 36 | if row.product_id != cur[0]: 37 | if cur != (0, 0): 38 | products.append(cur[0]) 39 | category_id.append(d[max_cat]) 40 | max_acc = 0 41 | cur = (row.product_id, row.category_idx) 42 | acc = row.prob 43 | pbar.update(1) 44 | submission = pd.Series(category_id, index=products) 45 | submission.rename('category_id', inplace=True) 46 | submission.index.rename(name='_id', inplace=True) 47 | return submission 48 | 49 | 50 | if __name__ == '__main__': 51 | parser = argparse.ArgumentParser() 52 | parser.add_argument('--preds_csv', required=True, help='File with predictions') 53 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 54 | parser.add_argument('--output_file', required=True, help='File to save submission into') 55 | 56 | args = parser.parse_args() 57 | preds = pd.read_csv(args.preds_csv, dtype={'category_idx': np.int16, 'prob': np.float32, 'product_id': np.int32, 58 | 'img_idx': np.int8}) 59 | category_idx = pd.read_csv(args.category_idx_csv) 60 | submission = form_submission(preds, category_idx) 61 | submission.to_csv(args.output_file, header=True) 62 | -------------------------------------------------------------------------------- /src/model/heng_models.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import numpy as np 3 | import pandas as pd 4 | import torch 5 | import cv2 6 | import os 7 | import bson 8 | import itertools 9 | from tqdm import tqdm 10 | from torch.autograd import Variable 11 | import torch.nn.functional as F 12 | from src.heng_cherkeng.inception_v3 import Inception3 13 | from src.heng_cherkeng.excited_inception_v3 import SEInception3 14 | from src.heng_cherkeng.xception import Xception 15 | from src.heng_cherkeng.resnet101 import ResNet101 16 | from src.data.category_idx import category_to_index_dict 17 | 18 | CDISCOUNT_NUM_CLASSES = 5270 19 | CDISCOUNT_HEIGHT = 180 20 | CDISCOUNT_WIDTH = 180 21 | 22 | 23 | def read_label_to_category_id(file): 24 | with open(file, 'r') as file: 25 | d = eval(file.read()) 26 | return d 27 | 28 | 29 | def read_train_ids(file): 30 | with open(file, 'r') as file: 31 | lines = file.readlines() 32 | return {int(line) for line in lines} 33 | 34 | 35 | def pytorch_image_to_tensor_transform(image): 36 | mean = [0.485, 0.456, 0.406] 37 | std = [0.229, 0.224, 0.225] 38 | image = cv2.cvtColor(image, cv2.COLOR_BGR2RGB) 39 | image = image.transpose((2, 0, 1)) 40 | tensor = torch.from_numpy(image).float().div(255) 41 | tensor[0] = (tensor[0] - mean[0]) / std[0] 42 | tensor[1] = (tensor[1] - mean[1]) / std[1] 43 | tensor[2] = (tensor[2] - mean[2]) / std[2] 44 | return tensor 45 | 46 | 47 | def image_to_tensor_transform(image): 48 | tensor = pytorch_image_to_tensor_transform(image) 49 | tensor[0] = tensor[0] * (0.229 / 0.5) + (0.485 - 0.5) / 0.5 50 | tensor[1] = tensor[1] * (0.224 / 0.5) + (0.456 - 0.5) / 0.5 51 | tensor[2] = tensor[2] * (0.225 / 0.5) + (0.406 - 0.5) / 0.5 52 | return tensor 53 | 54 | 55 | def doit(net, vecs, ids, dfs, label_to_category_id, category_dict, top_k=10, single_prediction=False): 56 | x = Variable(vecs, volatile=True).cuda() 57 | logits = net(x) 58 | preds = F.softmax(logits) 59 | preds = preds.cpu().data.numpy() 60 | 61 | if single_prediction: 62 | product_start = 0 63 | prev_product_id = 0 64 | chunk = [] 65 | for i, tuple in enumerate(itertools.chain(ids, [(1, 0)])): 66 | if prev_product_id != 0 and prev_product_id != tuple[0]: 67 | prods = preds[product_start:i].prod(axis=-2) 68 | prods = prods / prods.sum() 69 | top_k_preds = np.argpartition(prods, -top_k)[-top_k:] 70 | for pred_idx in range(top_k): 71 | chunk.append((prev_product_id, 0, category_dict[label_to_category_id[top_k_preds[pred_idx]]], 72 | prods[top_k_preds[pred_idx]])) 73 | product_start = i 74 | prev_product_id = tuple[0] 75 | else: 76 | top_k_preds = np.argpartition(preds, -top_k)[:, -top_k:] 77 | chunk = [] 78 | for i in range(len(ids)): 79 | product_id = ids[i][0] 80 | img_idx = ids[i][1] 81 | for pred_idx in range(top_k): 82 | chunk.append( 83 | (product_id, img_idx, category_dict[label_to_category_id[top_k_preds[i, pred_idx]]], 84 | preds[i, top_k_preds[i, pred_idx]])) 85 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 86 | dfs.append(chunk_df) 87 | 88 | 89 | def model_predict(bson_file, model_name, model_dir, label_to_category_id_file, batch_size, category_idx, is_pred_valid, 90 | train_ids_file, single_prediction=False, test_time_augmentation=False, tta_seed=123, crop_range=20, 91 | rotation_max=0): 92 | category_dict = category_to_index_dict(category_idx) 93 | 94 | if model_name == 'inception': 95 | net = Inception3(in_shape=(3, CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), num_classes=CDISCOUNT_NUM_CLASSES) 96 | elif model_name == 'seinception': 97 | net = SEInception3(in_shape=(3, CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), num_classes=CDISCOUNT_NUM_CLASSES) 98 | elif model_name == 'xception': 99 | net = Xception(in_shape=(3, CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), num_classes=CDISCOUNT_NUM_CLASSES) 100 | elif model_name == 'resnet101': 101 | net = ResNet101(in_shape=(3, CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), num_classes=CDISCOUNT_NUM_CLASSES) 102 | else: 103 | raise ValueError('Unknown model name ' + model_name) 104 | 105 | net.load_state_dict(torch.load(os.path.join(model_dir, 'model.pth'))) 106 | net.cuda().eval() 107 | 108 | label_to_category_id = read_label_to_category_id(label_to_category_id_file) 109 | if is_pred_valid: 110 | train_ids = read_train_ids(train_ids_file) 111 | 112 | bson_iter = bson.decode_file_iter(open(bson_file, 'rb')) 113 | batch_size = batch_size 114 | 115 | rnd = np.random.RandomState(tta_seed) 116 | 117 | dfs = [] 118 | with tqdm() as pbar: 119 | v = torch.from_numpy(np.zeros((batch_size + 3, 3, CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), dtype=np.float32)) 120 | ids = [] 121 | for d in bson_iter: 122 | product_id = d['_id'] 123 | # noinspection PyUnboundLocalVariable 124 | if is_pred_valid and product_id in train_ids: 125 | continue 126 | for e, pic in enumerate(d['imgs']): 127 | image = cv2.imdecode(np.fromstring(pic['picture'], np.uint8), 1) 128 | if test_time_augmentation: 129 | if rotation_max != 0: 130 | angle = rnd.rand(1)[0] * rotation_max * 2 - rotation_max 131 | M = cv2.getRotationMatrix2D((CDISCOUNT_HEIGHT / 2, CDISCOUNT_WIDTH / 2), angle, 1) 132 | image = cv2.warpAffine(image, M, (CDISCOUNT_HEIGHT, CDISCOUNT_WIDTH), 133 | borderMode=cv2.BORDER_REPLICATE) 134 | if crop_range != 0: 135 | image = cv2.resize(image, (CDISCOUNT_HEIGHT + crop_range, CDISCOUNT_WIDTH + crop_range)) 136 | crop = rnd.randint(0, crop_range, 2) 137 | image = image[crop[0]:(crop[0] + CDISCOUNT_HEIGHT), crop[1]:(crop[1] + CDISCOUNT_WIDTH)] 138 | x = image_to_tensor_transform(image) 139 | v[len(ids)] = x 140 | ids.append((product_id, e)) 141 | if len(ids) >= batch_size: 142 | doit(net, v, ids, dfs, label_to_category_id, category_dict, single_prediction=single_prediction) 143 | pbar.update(len(ids)) 144 | ids = [] 145 | if len(ids) > 0: 146 | doit(net, v, ids, dfs, label_to_category_id, category_dict, single_prediction=single_prediction) 147 | pbar.update(len(ids)) 148 | 149 | return pd.concat(dfs) 150 | 151 | 152 | if __name__ == '__main__': 153 | parser = argparse.ArgumentParser() 154 | parser.add_argument('--bson', required=True, help='Path to bson with products') 155 | parser.add_argument('--model_name', required=True, help='Model name: inception or seinception') 156 | parser.add_argument('--model_dir', required=True, help='Output directory for vectors') 157 | parser.add_argument('--label_to_category_id_file', required=True, help='Hengs label to category mappings file') 158 | parser.add_argument('--batch_size', type=int, required=False, default=256, help='Batch size') 159 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 160 | parser.add_argument('--predict_valid', action='store_true', required=False, dest='is_predict_valid') 161 | parser.set_defaults(is_predict_valid=False) 162 | parser.add_argument('--train_ids_file', required=False, help='Path to Hengs with train ids') 163 | parser.add_argument('--single_prediction', action='store_true', required=False, dest='single_prediction') 164 | parser.set_defaults(single_prediction=False) 165 | parser.add_argument('--test_time_augmentation', action='store_true', required=False, dest='test_time_augmentation') 166 | parser.set_defaults(test_time_augmentation=False) 167 | parser.add_argument('--tta_seed', type=int, required=False, default=123) 168 | parser.add_argument('--csv_suffix', required=False, default='') 169 | parser.add_argument('--crop_range', type=int, required=False, default=20) 170 | parser.add_argument('--rotation_max', type=int, required=False, default=0) 171 | 172 | args = parser.parse_args() 173 | 174 | category_idx = pd.read_csv(args.category_idx_csv) 175 | 176 | preds = model_predict(args.bson, args.model_name, args.model_dir, args.label_to_category_id_file, args.batch_size, 177 | category_idx, args.is_predict_valid, args.train_ids_file, args.single_prediction, 178 | args.test_time_augmentation, 179 | args.tta_seed, args.crop_range, args.rotation_max) 180 | if args.is_predict_valid: 181 | if args.single_prediction: 182 | csv_name = 'valid_single_predictions{}.csv' 183 | else: 184 | csv_name = 'valid_predictions{}.csv' 185 | else: 186 | if args.single_prediction: 187 | csv_name = 'single_predictions{}.csv' 188 | else: 189 | csv_name = 'predictions{}.csv' 190 | csv_name = csv_name.format(args.csv_suffix) 191 | preds.to_csv(os.path.join(args.model_dir, csv_name), index=False) 192 | -------------------------------------------------------------------------------- /src/model/memmap_iterator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from keras.preprocessing.image import Iterator 3 | from queue import Queue 4 | from threading import Thread 5 | import time 6 | 7 | 8 | class MemmapIterator(): 9 | def __init__(self, memmap_path, memmap_shape, images_df, num_classes=None, batch_size=32, shuffle=True, seed=None, 10 | pool_wrokers=4, use_side_input=False): 11 | if seed: 12 | np.random.seed(seed) 13 | self.x = np.memmap(memmap_path, dtype=np.float32, mode='r', shape=memmap_shape) 14 | self.images_df = images_df 15 | self.images_df_index = np.copy(self.images_df.index.values) 16 | self.images_df_num_imgs = np.copy(self.images_df.num_imgs.as_matrix()) 17 | self.images_df_img_idx = np.copy(self.images_df.img_idx.as_matrix()) 18 | self.has_y = 'category_idx' in images_df.columns 19 | if self.has_y: 20 | self.images_df_category_idx = np.copy(self.images_df.category_idx.as_matrix()) 21 | del self.images_df 22 | self.num_classes = num_classes 23 | self.batch_size = batch_size 24 | self.shuffle = shuffle 25 | self.use_side_input = use_side_input 26 | self.samples = len(self.images_df_index) 27 | self.it = Iterator(self.samples, self.batch_size, self.shuffle, seed) 28 | self.queue = Queue(maxsize=40) 29 | self.stop_flag = False 30 | self.threads = [] 31 | for i in range(pool_wrokers): 32 | thread = Thread(target=self.read_batches) 33 | thread.start() 34 | self.threads.append(thread) 35 | 36 | def read_batches(self): 37 | while True: 38 | if self.stop_flag == True: 39 | return 40 | with self.it.lock: 41 | index_array = next(self.it.index_generator)[0] 42 | m1 = np.zeros((len(index_array), *self.x.shape[1:]), dtype=np.float32) 43 | if self.use_side_input: 44 | m2 = np.zeros((len(index_array), 8), dtype=np.float32) 45 | 46 | if self.has_y: 47 | p = np.zeros(len(index_array), dtype=np.float32) 48 | 49 | for bi, i in enumerate(index_array): 50 | m1[bi] = self.x[self.images_df_index[i]] 51 | if self.use_side_input: 52 | m2[bi, self.images_df_num_imgs[i] - 1] = 1 53 | m2[bi, 4 + self.images_df_img_idx[i]] = 1 54 | if self.has_y: 55 | # noinspection PyUnboundLocalVariable 56 | p[bi] = self.images_df_category_idx[i] 57 | if self.use_side_input: 58 | inputs = [m1, m2] 59 | else: 60 | inputs = m1 61 | 62 | if self.has_y: 63 | self.queue.put((inputs, p)) 64 | else: 65 | self.queue.put(inputs) 66 | 67 | def next(self): 68 | return self.queue.get() 69 | 70 | def terminate(self): 71 | self.stop_flag = True 72 | while True: 73 | try: 74 | while True: 75 | self.queue.get(block=False) 76 | except: 77 | pass 78 | live_threads = 0 79 | for thread in self.threads: 80 | live_threads += 1 if thread.is_alive() else 0 81 | if live_threads == 0: 82 | return 83 | print('Threads running ', live_threads) 84 | for thread in self.threads: 85 | thread.join(timeout=5) 86 | 87 | def __iter__(self): 88 | return self 89 | 90 | def __next__(self, *args, **kwargs): 91 | return self.next(*args, **kwargs) 92 | -------------------------------------------------------------------------------- /src/model/multi_memmap_iterator.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import itertools 3 | from collections import namedtuple 4 | from keras.preprocessing.image import Iterator 5 | from queue import Queue 6 | from threading import Thread 7 | 8 | class MultiMemmapIterator(): 9 | def __init__(self, memmap_path, memmap_shape, images_df, num_classes=None, batch_size=32, shuffle=True, seed=None, 10 | pool_wrokers=4, only_single=False, include_singles=True, max_images=2, use_side_input=True): 11 | if seed: 12 | np.random.seed(seed) 13 | self.x = np.memmap(memmap_path, dtype=np.float32, mode='r', shape=memmap_shape) 14 | self.images_df = images_df.sort_values('product_id') 15 | self.images_df_index = np.copy(self.images_df.index.values) 16 | self.images_df_num_imgs = np.copy(self.images_df.num_imgs.as_matrix()) 17 | self.images_df_img_idx = np.copy(self.images_df.img_idx.as_matrix()) 18 | self.has_y = 'category_idx' in images_df.columns 19 | if self.has_y: 20 | self.images_df_category_idx = np.copy(self.images_df.category_idx.as_matrix()) 21 | self.num_classes = num_classes 22 | self.batch_size = batch_size 23 | self.shuffle = shuffle 24 | self.max_images = max_images 25 | self.use_side_input = use_side_input 26 | 27 | self.smpls = [] 28 | cur_index = [] 29 | prev_product_id = -1 30 | for i, row in enumerate( 31 | itertools.chain(self.images_df.itertuples(), [namedtuple('Pandas', ['Index', 'product_id'])(0, 0)])): 32 | if prev_product_id != -1 and row.product_id != prev_product_id: 33 | if include_singles or len(cur_index) == 1: 34 | self.smpls.extend([[idx] for idx in cur_index]) 35 | if len(cur_index) > 1 and not only_single: 36 | self.smpls.append(cur_index) 37 | cur_index = [] 38 | prev_product_id = row.product_id 39 | cur_index.append(i) 40 | del self.images_df 41 | 42 | self.samples = len(self.smpls) 43 | self.rnd = np.random.RandomState(seed) 44 | self.it = Iterator(self.samples, self.batch_size, self.shuffle, seed) 45 | self.queue = Queue(maxsize=40) 46 | self.stop_flag = False 47 | self.threads = [] 48 | for i in range(pool_wrokers): 49 | thread = Thread(target=self.read_batches) 50 | thread.start() 51 | self.threads.append(thread) 52 | 53 | def read_batches(self): 54 | while True: 55 | if self.stop_flag == True: 56 | return 57 | with self.it.lock: 58 | index_array = next(self.it.index_generator)[0] 59 | m1 = np.zeros((len(index_array), self.max_images, *self.x.shape[1:]), dtype=np.float32) 60 | if self.use_side_input: 61 | m2 = np.zeros((len(index_array), self.max_images, 8), dtype=np.float32) 62 | 63 | if self.has_y: 64 | p = np.zeros(len(index_array), dtype=np.float32) 65 | 66 | bi = 0 67 | for smpl_idx in index_array: 68 | smpl = self.smpls[smpl_idx] 69 | 70 | for i in smpl: 71 | cur_idx = 3 - self.images_df_img_idx[i] 72 | m1[bi, cur_idx] = self.x[self.images_df_index[i]] 73 | if self.use_side_input: 74 | m2[bi, cur_idx, self.images_df_num_imgs[i] - 1] = 1 75 | m2[bi, cur_idx, 4 + self.images_df_img_idx[i]] = 1 76 | 77 | if self.has_y: 78 | # noinspection PyUnboundLocalVariable 79 | p[bi] = self.images_df_category_idx[smpl[0]] 80 | bi += 1 81 | if self.use_side_input: 82 | inputs = [m1, m2] 83 | else: 84 | inputs = m1 85 | 86 | if self.has_y: 87 | self.queue.put((inputs, p)) 88 | else: 89 | self.queue.put(inputs) 90 | 91 | def next(self): 92 | return self.queue.get() 93 | 94 | def terminate(self): 95 | self.stop_flag = True 96 | while True: 97 | try: 98 | while True: 99 | self.queue.get(block=False) 100 | except: 101 | pass 102 | live_threads = 0 103 | for thread in self.threads: 104 | live_threads += 1 if thread.is_alive() else 0 105 | if live_threads == 0: 106 | return 107 | print('Threads running ', live_threads) 108 | for thread in self.threads: 109 | thread.join(timeout=5) 110 | 111 | def __iter__(self): 112 | return self 113 | 114 | def __next__(self, *args, **kwargs): 115 | return self.next(*args, **kwargs) 116 | -------------------------------------------------------------------------------- /src/model/predict_ensemble_nn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | from tqdm import tqdm 5 | from keras.models import load_model 6 | 7 | MODEL_FILE = 'model.h5' 8 | TOP_PREDS = 10 9 | PRODS_BATCH = 100000 10 | CATEGORIES_SPLIT = 2000 11 | PREDICTIONS_FILE = 'predictions.csv' 12 | 13 | if __name__ == '__main__': 14 | parser = argparse.ArgumentParser() 15 | parser.add_argument('--preds_csvs', nargs='+', required=True, help='Files with predictions of test dataset') 16 | parser.add_argument('--model_dir', required=True, help='Model directory') 17 | parser.add_argument('--total_records', type=int, default=30950800, 18 | help='Total number of records in prediction files') 19 | args = parser.parse_args() 20 | model = load_model(os.path.join(args.model_dir, MODEL_FILE)) 21 | weights_left = model.get_layer('embedding_1').get_weights() 22 | weights_right = model.get_layer('embedding_2').get_weights() 23 | 24 | whole = [] 25 | skiprows = 1 26 | with tqdm(total=args.total_records) as pbar: 27 | while skiprows < args.total_records + 1: 28 | all_preds = [] 29 | for i, csv in enumerate(args.preds_csvs): 30 | weight_left = weights_left[0][i][0] 31 | weight_right = weights_right[0][i][0] 32 | preds = pd.read_csv(csv, skiprows=skiprows, nrows=TOP_PREDS * PRODS_BATCH, 33 | names=['product_id', 'img_idx', 'category_idx', 'prob']) 34 | preds.fillna(0, inplace=True) 35 | preds.loc[preds.category_idx < CATEGORIES_SPLIT, 'prob'] = \ 36 | preds.loc[preds.category_idx < CATEGORIES_SPLIT, 'prob'] * weight_left 37 | preds.loc[preds.category_idx >= CATEGORIES_SPLIT, 'prob'] = \ 38 | preds.loc[preds.category_idx >= CATEGORIES_SPLIT, 'prob'] * weight_right 39 | all_preds.append(preds) 40 | all_preds = pd.concat(all_preds) 41 | all_preds = all_preds.groupby(['product_id', 'img_idx', 'category_idx'], as_index=False).sum() 42 | sum_preds = all_preds[['product_id', 'img_idx', 'prob']].groupby(['product_id', 'img_idx'], 43 | as_index=False).sum() \ 44 | .rename(columns={'prob': 'prob_sum'}) 45 | all_preds = all_preds.merge(sum_preds, on=['product_id', 'img_idx'], how='left') 46 | all_preds['prob'] = all_preds['prob'] / all_preds['prob_sum'] 47 | all_preds = all_preds[['product_id', 'img_idx', 'category_idx', 'prob']] 48 | all_preds.sort_values('prob', inplace=True, ascending=False) 49 | all_preds = all_preds.groupby(['product_id', 'img_idx'], as_index=False).head(TOP_PREDS) 50 | 51 | whole.append(all_preds) 52 | 53 | skiprows += TOP_PREDS * PRODS_BATCH 54 | pbar.update(TOP_PREDS * PRODS_BATCH) 55 | 56 | pd.concat(whole).to_csv(os.path.join(args.model_dir, PREDICTIONS_FILE), index=False) 57 | -------------------------------------------------------------------------------- /src/model/pseudo_label_prod_info.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | from src.data.category_idx import index_to_category_dict 4 | 5 | 6 | def pick_top_category(preds, category_map): 7 | ordered_preds = preds.sort_values('prob', ascending=False) 8 | ordered_preds.fillna(0, inplace=True) 9 | taken_products = set() 10 | tuples = [] 11 | for row in ordered_preds.itertuples(): 12 | if row.product_id in taken_products: 13 | continue 14 | taken_products.add(row.product_id) 15 | tuples.append((row.product_id, category_map[row.category_idx])) 16 | return pd.DataFrame(tuples, columns=['product_id', 'category_id'], dtype='int64').sort_values('product_id') 17 | 18 | 19 | def create_pl_prod_infos(train_prod_info_csv, test_prod_info_csv, valid_preds_csv, test_preds_csv, pl_train_prod_info, 20 | pl_test_prod_info, category_idx_csv): 21 | train_prod_info = pd.read_csv(train_prod_info_csv) 22 | test_prod_info = pd.read_csv(test_prod_info_csv) 23 | valid_preds = pd.read_csv(valid_preds_csv) 24 | test_preds = pd.read_csv(test_preds_csv) 25 | category_idx = pd.read_csv(category_idx_csv) 26 | 27 | category_map = index_to_category_dict(category_idx) 28 | 29 | test_preds = pick_top_category(test_preds, category_map) 30 | test_prod_info = test_prod_info.merge(test_preds, on='product_id', how='left') 31 | test_prod_info.to_csv(pl_test_prod_info, index=False) 32 | 33 | valid_preds = pick_top_category(valid_preds, category_map) 34 | train_prod_info.loc[train_prod_info.product_id.isin(valid_preds.product_id), 'category_id'] = valid_preds[ 35 | 'category_id'].as_matrix() 36 | train_prod_info.to_csv(pl_train_prod_info, index=False) 37 | 38 | 39 | if __name__ == '__main__': 40 | parser = argparse.ArgumentParser() 41 | parser.add_argument('--train_prod_info', required=True, help='Train product info') 42 | parser.add_argument('--test_prod_info', required=True, help='Test product info') 43 | parser.add_argument('--valid_preds', required=True, help='Valid split predictions') 44 | parser.add_argument('--test_preds', required=True, help='Test predictions') 45 | parser.add_argument('--pl_train_prod_info', required=True, help='Pseudo labeling train product info output file') 46 | parser.add_argument('--pl_test_prod_info', required=True, help='Pseudo labeling test product info output file') 47 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 48 | 49 | args = parser.parse_args() 50 | create_pl_prod_infos(args.train_prod_info, args.test_prod_info, args.valid_preds, args.test_preds, 51 | args.pl_train_prod_info, args.pl_test_prod_info, args.category_idx_csv) 52 | -------------------------------------------------------------------------------- /src/model/resnet50_vecs.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import argparse 4 | import os 5 | import bcolz 6 | from tqdm import tqdm 7 | from .bson_iterator import BSONIterator 8 | from keras.applications.resnet50 import ResNet50 9 | from keras.applications.resnet50 import preprocess_input 10 | from keras.preprocessing.image import ImageDataGenerator 11 | from keras.layers import Flatten 12 | from keras.models import Model 13 | import threading 14 | 15 | 16 | def compute_vgg16_vecs(bson_path, images_df, vecs_output_dir, save_step=100000): 17 | resnet_model = ResNet50(include_top=False, input_shape=(3, 197, 197)) 18 | 19 | if os.path.isdir(vecs_output_dir): 20 | vecs = bcolz.open(rootdir=vecs_output_dir) 21 | offset = vecs.shape[0] 22 | else: 23 | vecs = None 24 | offset = 0 25 | 26 | lock = threading.Lock() 27 | 28 | with open(bson_path, "rb") as train_bson_file, \ 29 | tqdm(total=images_df.shape[0], initial=offset) as pbar: 30 | for i in range(offset, images_df.shape[0], save_step): 31 | gen = ImageDataGenerator(preprocessing_function=preprocess_input) 32 | batches = BSONIterator(bson_file=train_bson_file, 33 | images_df=images_df[i:(i + save_step)], 34 | num_class=0, # doesn't matter here 35 | image_data_generator=gen, 36 | lock=lock, 37 | target_size=(197, 197), 38 | batch_size=220, 39 | shuffle=False, 40 | with_labels=False) 41 | 42 | x = Flatten()(resnet_model.output) 43 | model = Model(resnet_model.input, x) 44 | out_vecs = model.predict_generator(batches, 45 | steps=batches.samples / batches.batch_size, 46 | verbose=1) 47 | if not vecs: 48 | vecs = bcolz.carray(out_vecs, rootdir=vecs_output_dir, mode='w') 49 | vecs.flush() 50 | else: 51 | vecs.append(out_vecs) 52 | vecs.flush() 53 | pbar.update(save_step) 54 | 55 | 56 | def create_images_df(product_info, only_first_image=False): 57 | rows = [] 58 | for row in product_info.itertuples(): 59 | for i in range(row.num_imgs): 60 | rows.append([row.product_id, i, row.offset, row.length]) 61 | 62 | images_df = pd.DataFrame(rows, columns=['product_id', 'img_idx', 'offset', 'length']) 63 | if only_first_image: 64 | images_df = images_df[images_df.img_idx == 0] 65 | images_df = images_df.reset_index(drop=True) 66 | return images_df 67 | 68 | 69 | if __name__ == '__main__': 70 | parser = argparse.ArgumentParser() 71 | parser.add_argument('--bson', required=True, help='Path to bson with products') 72 | parser.add_argument('--prod_info_csv', required=True, help='Path to prod info csv') 73 | parser.add_argument('--output_dir', required=True, help='Output directory for vectors') 74 | parser.add_argument('--save_step', type=int, required=True, help='Save computed vectors to disk each N steps') 75 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 76 | help="Include only first image from each product") 77 | parser.add_argument('--shuffle', type=int, default=None, required=False, 78 | help='If products should be shuffled, provide seed') 79 | parser.set_defaults(only_first_image=False) 80 | 81 | args = parser.parse_args() 82 | product_info = pd.read_csv(args.prod_info_csv) 83 | 84 | images_df = create_images_df(product_info, args.only_first_image) 85 | if args.shuffle: 86 | np.random.seed(args.shuffle) 87 | perm = np.random.permutation(images_df.shape[0]) 88 | images_df = images_df.reindex(perm) 89 | 90 | compute_vgg16_vecs(args.bson, images_df, args.output_dir, args.save_step) 91 | -------------------------------------------------------------------------------- /src/model/sngl_preds_to_avg.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import argparse 3 | import numpy as np 4 | import itertools 5 | from tqdm import tqdm 6 | from collections import namedtuple 7 | 8 | TOP_K = 10 9 | 10 | def sngl_preds_to_avg(preds): 11 | preds.sort_values(['product_id', 'category_idx'], inplace=True) 12 | cur = (0, 0) 13 | acc = 0 14 | prod_cats = [] 15 | chunks = [] 16 | 17 | with tqdm(total=preds.shape[0]) as pbar: 18 | for row in itertools.chain(preds.itertuples(), 19 | [namedtuple('Pandas', ['product_id', 'img_idx', 'category_idx', 'prob'])(0, 0, 0, 20 | 0)]): 21 | if cur == (row.product_id, row.category_idx): 22 | acc += row.prob 23 | else: 24 | prod_cats.append((cur[1], acc)) 25 | if row.product_id != cur[0]: 26 | if cur != (0, 0): 27 | prod_cats.sort(key=lambda x: x[1], reverse=True) 28 | s = sum([x[1] for x in prod_cats]) 29 | for t in prod_cats[:TOP_K]: 30 | chunks.append((cur[0], 0, t[0], t[1] / s)) 31 | prod_cats = [] 32 | cur = (row.product_id, row.category_idx) 33 | acc = row.prob 34 | pbar.update(1) 35 | 36 | return pd.DataFrame(chunks, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 37 | 38 | 39 | if __name__ == '__main__': 40 | parser = argparse.ArgumentParser() 41 | parser.add_argument('--preds_csv', required=True, help='File with predictions') 42 | parser.add_argument('--output_file', required=True, help='File to save submission into') 43 | 44 | args = parser.parse_args() 45 | preds = pd.read_csv(args.preds_csv, dtype={'category_idx': np.int16, 'prob': np.float32, 'product_id': np.int32, 46 | 'img_idx': np.int8}) 47 | out = sngl_preds_to_avg(preds) 48 | out.to_csv(args.output_file, header=True) -------------------------------------------------------------------------------- /src/model/train_ensemble_nn.py: -------------------------------------------------------------------------------- 1 | import os 2 | import gc 3 | import argparse 4 | import pandas as pd 5 | import numpy as np 6 | import keras.backend as K 7 | from keras.preprocessing.image import Iterator 8 | from src.data.category_idx import map_categories 9 | from keras.layers.embeddings import Embedding 10 | from keras.layers import Flatten 11 | from keras.layers import Input 12 | from keras.layers import merge 13 | from keras.models import Model 14 | from keras.initializers import Ones 15 | from keras.optimizers import Adam 16 | from keras.models import load_model 17 | from keras.constraints import non_neg 18 | 19 | N_CATEGORIES = 5270 20 | CATEGORIES_SPLIT = 2000 21 | MODEL_FILE = 'model.h5' 22 | VALID_PREDICTIONS_FILE = 'valid_predictions.csv' 23 | TOP_K = 10 24 | 25 | 26 | class SpecialIterator(Iterator): 27 | def __init__(self, images, categories, n_models, batch_size=32, shuffle=True, seed=None): 28 | self.x = images 29 | self.products = images[['product_id', 'img_idx']].drop_duplicates().sort_values(['product_id', 'img_idx']) 30 | self.categories = categories.sort_index() 31 | self.num_classes = N_CATEGORIES 32 | self.samples = self.products.shape[0] 33 | self.n_models = n_models 34 | super(SpecialIterator, self).__init__(self.samples, batch_size, shuffle, seed) 35 | 36 | def next(self): 37 | index_array = next(self.index_generator)[0] 38 | prods = self.products.iloc[index_array] 39 | pd = {(row.product_id, row.img_idx): i for i, row in enumerate(prods.itertuples())} 40 | cats = self.categories.loc[prods.product_id] 41 | images = prods.merge(self.x, on=['product_id', 'img_idx'], how='left') 42 | p = np.zeros((len(index_array), self.num_classes, self.n_models), dtype=np.float32) 43 | for row in images.itertuples(): 44 | p[pd[(row.product_id, row.img_idx)], row.category_idx, row.model] = 0 if np.isnan(row.prob) else row.prob 45 | 46 | return [np.repeat(np.arange(self.n_models).reshape(1, self.n_models), len(index_array), axis=0), 47 | p[:, :CATEGORIES_SPLIT, :], p[:, CATEGORIES_SPLIT:, :]], cats['category_idx'].as_matrix() 48 | 49 | 50 | def train_ensemble_nn(preds_csv_files, prod_info_csv, category_idx_csv, model_dir, lr, seed, batch_size, epochs): 51 | prod_info = pd.read_csv(prod_info_csv) 52 | category_idx = pd.read_csv(category_idx_csv) 53 | 54 | all_preds = [] 55 | model_inx = {} 56 | for i, csv in enumerate(preds_csv_files): 57 | preds = pd.read_csv(csv) 58 | preds['model'] = i 59 | model_inx[i] = csv 60 | all_preds.append(preds) 61 | print('Assigned indexes to models: ', model_inx) 62 | all_preds = pd.concat(all_preds) 63 | 64 | n_models = len(preds_csv_files) 65 | 66 | categories = prod_info[prod_info.product_id.isin(all_preds.product_id.unique())][['product_id', 'category_id']] 67 | categories['category_idx'] = map_categories(category_idx, categories.category_id) 68 | categories = categories[['product_id', 'category_idx']] 69 | categories = categories.set_index('product_id') 70 | 71 | it = SpecialIterator(all_preds, categories, n_models, batch_size=batch_size, seed=seed, shuffle=True) 72 | 73 | model_file = os.path.join(model_dir, MODEL_FILE) 74 | if os.path.exists(model_file): 75 | model = load_model(model_file) 76 | else: 77 | model_inp = Input(shape=(n_models,), dtype='int32') 78 | 79 | preds_cat1_inp = Input((CATEGORIES_SPLIT, n_models)) 80 | preds_cat2_inp = Input((N_CATEGORIES - CATEGORIES_SPLIT, n_models)) 81 | 82 | mul_cat1 = Embedding(n_models, 1, input_length=n_models, embeddings_initializer=Ones(), 83 | embeddings_constraint=non_neg())(model_inp) 84 | mul_cat1 = Flatten()(mul_cat1) 85 | 86 | mul_cat2 = Embedding(n_models, 1, input_length=n_models, embeddings_initializer=Ones(), 87 | embeddings_constraint=non_neg())(model_inp) 88 | mul_cat2 = Flatten()(mul_cat2) 89 | 90 | def op(x): 91 | z_left = x[0].dimshuffle(1, 0, 2) * x[1] 92 | z_right = x[2].dimshuffle(1, 0, 2) * x[3] 93 | z = K.concatenate([z_left, z_right], axis=0) 94 | v = K.sum(z, axis=-1) 95 | p = K.sum(v, axis=-2) 96 | return (v / p).dimshuffle(1, 0) 97 | 98 | x = merge([preds_cat1_inp, mul_cat1, preds_cat2_inp, mul_cat2], mode=op, output_shape=(N_CATEGORIES,)) 99 | 100 | model = Model([model_inp, preds_cat1_inp, preds_cat2_inp], x) 101 | np.random.seed(seed) 102 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 103 | metrics=['sparse_categorical_accuracy']) 104 | 105 | model.fit_generator(it, steps_per_epoch=it.samples / it.batch_size, epochs=epochs) 106 | 107 | print('First {} categories model weights:'.format(CATEGORIES_SPLIT)) 108 | print(model.get_layer('embedding_1').get_weights()) 109 | print('Left categories model weights:'.format(CATEGORIES_SPLIT)) 110 | print(model.get_layer('embedding_2').get_weights()) 111 | 112 | if not os.path.isdir(model_dir): 113 | os.mkdir(model_dir) 114 | model.save(os.path.join(model_dir, MODEL_FILE)) 115 | 116 | 117 | def predict_valid(preds_csv_files, prod_info_csv, category_idx_csv, model_dir, batch_size): 118 | model_file = os.path.join(model_dir, MODEL_FILE) 119 | if os.path.exists(model_file): 120 | model = load_model(model_file) 121 | else: 122 | raise ValueError("Model doesn't exist") 123 | 124 | prod_info = pd.read_csv(prod_info_csv) 125 | category_idx = pd.read_csv(category_idx_csv) 126 | 127 | all_preds = [] 128 | model_inx = {} 129 | for i, csv in enumerate(preds_csv_files): 130 | preds = pd.read_csv(csv) 131 | preds['model'] = i 132 | model_inx[i] = csv 133 | all_preds.append(preds) 134 | print('Assigned indexes to models: ', model_inx) 135 | all_preds = pd.concat(all_preds) 136 | all_preds.sort_values(['product_id', 'img_idx'], inplace=True) 137 | 138 | n_models = len(preds_csv_files) 139 | 140 | categories = prod_info[prod_info.product_id.isin(all_preds.product_id.unique())][['product_id', 'category_id']] 141 | del prod_info 142 | 143 | categories['category_idx'] = map_categories(category_idx, categories.category_id) 144 | categories = categories[['product_id', 'category_idx']] 145 | categories = categories.set_index('product_id') 146 | 147 | del category_idx 148 | chunk_size = 50000 * n_models * TOP_K 149 | with open(os.path.join(args.model_dir, VALID_PREDICTIONS_FILE), 'w') as f: 150 | f.write('product_id,img_idx,category_idx,prob\n') 151 | for start_i in range(0, all_preds.shape[0], chunk_size): 152 | end_i = min(all_preds.shape[0], start_i + chunk_size) 153 | it = SpecialIterator(all_preds[start_i:end_i], categories, n_models, batch_size=batch_size, shuffle=False) 154 | 155 | preds = model.predict_generator(it, it.samples / batch_size, 156 | verbose=1, max_queue_size=10) 157 | del it 158 | gc.collect() 159 | top_k_preds = np.argpartition(preds, -TOP_K)[:, -TOP_K:] 160 | products = all_preds[start_i:end_i][['product_id', 'img_idx']].drop_duplicates() 161 | for i, row in enumerate(products.itertuples()): 162 | for pred_idx in range(TOP_K): 163 | f.write('{},{},{},{}\n'.format(row.product_id, row.img_idx, top_k_preds[i, pred_idx], 164 | preds[i, top_k_preds[i, pred_idx]])) 165 | del top_k_preds 166 | del preds 167 | del products 168 | gc.collect() 169 | f.flush() 170 | 171 | 172 | if __name__ == '__main__': 173 | parser = argparse.ArgumentParser() 174 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 175 | parser.set_defaults(is_predict_valid=False) 176 | parser.add_argument('--preds_csvs', nargs='+', required=True, help='Files with predictions of valid split') 177 | parser.add_argument('--prod_info_csv', required=True, help='Path to prod info csv') 178 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 179 | parser.add_argument('--model_dir', required=True, help='Model directory') 180 | parser.add_argument('--lr', type=float, default=0.01, required=False, help='Learning rate') 181 | parser.add_argument('--seed', type=int, default=456, required=False, help='Learning seed') 182 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Epochs') 183 | parser.add_argument('--batch_size', type=int, default=2000, required=False, help='Batch size') 184 | 185 | args = parser.parse_args() 186 | if args.is_predict_valid: 187 | predict_valid(args.preds_csvs, args.prod_info_csv, args.category_idx_csv, args.model_dir, args.batch_size) 188 | else: 189 | train_ensemble_nn(args.preds_csvs, args.prod_info_csv, args.category_idx_csv, args.model_dir, args.lr, 190 | args.seed, 191 | args.batch_size, args.epochs) 192 | -------------------------------------------------------------------------------- /src/model/tune_avg_resnet50_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | import keras.backend as K 6 | from keras.models import Model 7 | from keras.models import load_model 8 | from keras.layers import Dense 9 | from keras.layers import Input 10 | from keras.layers import BatchNormalization 11 | from keras.layers import TimeDistributed 12 | from keras.layers import SimpleRNN 13 | from keras.layers import GRU 14 | from keras.layers import Lambda 15 | from keras.layers import concatenate 16 | from keras.optimizers import Adam 17 | from keras.callbacks import ModelCheckpoint 18 | from keras.callbacks import CSVLogger 19 | from src.data.category_idx import map_categories 20 | from src.model.multi_memmap_iterator import MultiMemmapIterator 21 | from src.model.resnet50_vecs import create_images_df 22 | 23 | LOAD_MODEL = 'model.h5' 24 | SNAPSHOT_MODEL = 'model.h5' 25 | LOG_FILE = 'training.log' 26 | PREDICTIONS_FILE = 'single_predictions.csv' 27 | VALID_PREDICTIONS_FILE = 'valid_single_predictions.csv' 28 | MAX_PREDICTIONS_AT_TIME = 50000 29 | 30 | 31 | def train_data(memmap_path, memmap_len, bcolz_prod_info, sample_prod_info, train_split, category_idx, batch_size, 32 | shuffle=None, batch_seed=123, max_images=2, only_single=False, use_img_idx=False, include_singles=True): 33 | images_df = create_images_df(bcolz_prod_info, False) 34 | bcolz_prod_info['category_idx'] = map_categories(category_idx, bcolz_prod_info['category_id']) 35 | bcolz_prod_info = bcolz_prod_info.merge(train_split, on='product_id', how='left') 36 | images_df = images_df.merge(bcolz_prod_info, on='product_id', how='left')[ 37 | ['product_id', 'category_idx', 'img_idx', 'num_imgs', 'train']] 38 | if shuffle: 39 | np.random.seed(shuffle) 40 | perm = np.random.permutation(images_df.shape[0]) 41 | images_df = images_df.reindex(perm) 42 | images_df.reset_index(drop=True, inplace=True) 43 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 44 | train_df = images_df[images_df['train']] 45 | valid_df = images_df[~images_df['train']] 46 | num_classes = np.unique(images_df['category_idx']).size 47 | 48 | train_it = MultiMemmapIterator(memmap_path=memmap_path, 49 | memmap_shape=(memmap_len, 2048), 50 | images_df=train_df, 51 | num_classes=num_classes, 52 | seed=batch_seed, 53 | batch_size=batch_size, 54 | only_single=only_single, 55 | include_singles=include_singles, 56 | max_images=max_images, 57 | pool_wrokers=4, 58 | shuffle=True, 59 | use_side_input=use_img_idx) 60 | valid_mul_it = MultiMemmapIterator(memmap_path=memmap_path, 61 | memmap_shape=(memmap_len, 2048), 62 | images_df=valid_df, 63 | num_classes=num_classes, 64 | seed=batch_seed, 65 | batch_size=batch_size, 66 | shuffle=False, 67 | only_single=False, 68 | include_singles=False, 69 | max_images=4, 70 | pool_wrokers=1, 71 | use_side_input=use_img_idx) 72 | valid_sngl_it = MultiMemmapIterator(memmap_path=memmap_path, 73 | memmap_shape=(memmap_len, 2048), 74 | images_df=valid_df, 75 | num_classes=num_classes, 76 | seed=batch_seed, 77 | batch_size=batch_size, 78 | shuffle=False, 79 | only_single=True, 80 | include_singles=True, 81 | max_images=4, 82 | pool_wrokers=1, 83 | use_side_input=use_img_idx) 84 | return train_it, valid_mul_it, valid_sngl_it, num_classes 85 | 86 | 87 | def fit_model(train_it, valid_mul_it, valid_sngl_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0, 88 | seed=125): 89 | model_file = os.path.join(models_dir, LOAD_MODEL) 90 | if os.path.exists(model_file): 91 | model = load_model(model_file) 92 | else: 93 | if mode == 0: 94 | inp1 = Input((None, 2048)) 95 | inp2 = Input((None, 8)) 96 | x = concatenate([inp1, inp2]) 97 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 98 | x = BatchNormalization(axis=-1)(x) 99 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 100 | x = BatchNormalization(axis=-1)(x) 101 | x = Lambda(lambda x: K.sum(x, axis=-2), output_shape=(4096,))(x) 102 | x = Dense(num_classes, activation='softmax')(x) 103 | model = Model([inp1, inp2], x) 104 | elif mode == 1: 105 | inp1 = Input((None, 2048)) 106 | inp2 = Input((None, 8)) 107 | x = concatenate([inp1, inp2]) 108 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 109 | x = BatchNormalization(axis=-1)(x) 110 | x = SimpleRNN(4096, activation='relu', recurrent_initializer='identity')(x) 111 | x = BatchNormalization(axis=-1)(x) 112 | x = Dense(num_classes, activation='softmax')(x) 113 | model = Model([inp1, inp2], x) 114 | elif mode == 2: 115 | inp1 = Input((None, 2048)) 116 | inp2 = Input((None, 8)) 117 | x = concatenate([inp1, inp2]) 118 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 119 | x = BatchNormalization(axis=-1)(x) 120 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 121 | x = BatchNormalization(axis=-1)(x) 122 | x = GRU(100, activation='relu')(x) 123 | x = BatchNormalization(axis=-1)(x) 124 | x = Dense(num_classes, activation='softmax')(x) 125 | model = Model([inp1, inp2], x) 126 | elif mode == 3: 127 | inp1 = Input((None, 2048)) 128 | inp1_com = Lambda(lambda x: K.max(x, axis=-2), output_shape=(2048,))(inp1) 129 | x = Dense(4096, activation='relu')(inp1_com) 130 | x = BatchNormalization(axis=-1)(x) 131 | x = Dense(4096, activation='relu')(x) 132 | x = BatchNormalization(axis=-1)(x) 133 | x = Dense(num_classes, activation='softmax')(x) 134 | model = Model(inp1, x) 135 | 136 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 137 | metrics=['sparse_categorical_accuracy']) 138 | 139 | np.random.seed(seed) 140 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 141 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 142 | model.fit_generator(train_it, 143 | steps_per_epoch=train_it.samples / batch_size, 144 | validation_data=valid_mul_it, 145 | validation_steps=valid_mul_it.samples / batch_size, 146 | epochs=epochs, 147 | callbacks=[checkpointer, csv_logger], 148 | max_queue_size=10, 149 | use_multiprocessing=False) 150 | 151 | with open(os.path.join(models_dir, LOG_FILE), "a") as file: 152 | file.write('Multi {}\n'.format(model.evaluate_generator(valid_mul_it, steps=valid_mul_it.samples / batch_size))) 153 | file.write( 154 | 'Single {}\n'.format(model.evaluate_generator(valid_sngl_it, steps=valid_sngl_it.samples / batch_size))) 155 | 156 | 157 | def predict(memmap_path, memmap_len, prod_info, sample_prod_info, models_dir, batch_size=200, 158 | shuffle=None, top_k=10, use_img_idx=True): 159 | model_file = os.path.join(models_dir, LOAD_MODEL) 160 | if os.path.exists(model_file): 161 | model = load_model(model_file) 162 | else: 163 | raise ValueError("Model doesn't exist") 164 | images_df = create_images_df(prod_info, False) 165 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 166 | ['product_id', 'img_idx', 'num_imgs']] 167 | if shuffle: 168 | np.random.seed(shuffle) 169 | perm = np.random.permutation(images_df.shape[0]) 170 | images_df = images_df.reindex(perm) 171 | images_df.reset_index(drop=True, inplace=True) 172 | if sample_prod_info is not None: 173 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 174 | images_df.sort_values('product_id', inplace=True) 175 | dfs = [] 176 | offset = 0 177 | while offset < images_df.shape[0]: 178 | end_idx = min(images_df.shape[0], offset + MAX_PREDICTIONS_AT_TIME - 5) 179 | while end_idx < images_df.shape[0]: 180 | if images_df.iloc[end_idx - 1].product_id == images_df.iloc[end_idx].product_id: 181 | end_idx += 1 182 | else: 183 | break 184 | it = MultiMemmapIterator(memmap_path=memmap_path, 185 | memmap_shape=(memmap_len, 2048), 186 | images_df=images_df[offset:end_idx], 187 | batch_size=batch_size, 188 | pool_wrokers=1, 189 | only_single=False, 190 | include_singles=False, 191 | max_images=4, 192 | shuffle=False, 193 | use_side_input=use_img_idx) 194 | 195 | preds = model.predict_generator(it, it.samples / batch_size, 196 | verbose=1, max_queue_size=10) 197 | it.terminate() 198 | del it 199 | chunk = [] 200 | for i, product_id in enumerate(images_df[offset:end_idx].product_id.unique()): 201 | top_k_preds = np.argpartition(preds[i], -top_k)[-top_k:] 202 | for pred_idx in range(top_k): 203 | chunk.append((product_id, 0, top_k_preds[pred_idx], preds[i, top_k_preds[pred_idx]])) 204 | 205 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 206 | dfs.append(chunk_df) 207 | offset = end_idx 208 | del preds 209 | del chunk 210 | return pd.concat(dfs) 211 | 212 | 213 | if __name__ == '__main__': 214 | parser = argparse.ArgumentParser() 215 | parser.add_argument('--fit', action='store_true', dest='is_fit') 216 | parser.add_argument('--predict', action='store_true', dest='is_predict') 217 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 218 | parser.add_argument('--bcolz_root', required=True, help='VGG16 vecs bcolz root path') 219 | parser.add_argument('--bcolz_prod_info_csv', required=True, 220 | help='Path to prod info csv with which VGG16 were generated') 221 | parser.add_argument('--sample_prod_info_csv', required=True, help='Path to sample prod info csv') 222 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 223 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 224 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 225 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 226 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 227 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 228 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 229 | help="Include only first image from each product") 230 | parser.add_argument('--shuffle', type=int, default=None, required=False, 231 | help='If products should be shuffled, provide seed') 232 | parser.set_defaults(only_first_image=False) 233 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 234 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 235 | parser.add_argument('--dont_use_img_idx', action='store_false', dest='use_img_idx') 236 | parser.set_defaults(use_img_idx=True) 237 | parser.add_argument('--memmap_len', type=int, required=True, help='Number of rows in memmap') 238 | parser.set_defaults(two_outs=False) 239 | parser.add_argument('--max_images', type=int, default=2, required=False, help='Max images in train record') 240 | parser.add_argument('--only_single', action='store_true', dest='only_single') 241 | parser.set_defaults(only_single=False) 242 | parser.add_argument('--dont_include_singles', action='store_false', dest='include_singles') 243 | parser.set_defaults(include_singles=True) 244 | 245 | args = parser.parse_args() 246 | if not os.path.isdir(args.models_dir): 247 | os.mkdir(args.models_dir) 248 | 249 | bcolz_prod_info = pd.read_csv(args.bcolz_prod_info_csv) 250 | sample_prod_info = pd.read_csv(args.sample_prod_info_csv) 251 | train_split = pd.read_csv(args.train_split_csv) 252 | category_idx = pd.read_csv(args.category_idx_csv) 253 | 254 | if args.is_fit: 255 | train_it, valid_mul_it, valid_sngl_it, num_classes = train_data(args.bcolz_root, 256 | args.memmap_len, 257 | bcolz_prod_info, 258 | sample_prod_info, 259 | train_split, 260 | category_idx, 261 | args.batch_size, 262 | args.shuffle, 263 | args.batch_seed, 264 | args.max_images, 265 | args.only_single, 266 | args.use_img_idx, 267 | args.include_singles) 268 | fit_model(train_it, valid_mul_it, valid_sngl_it, num_classes, args.models_dir, args.lr, args.batch_size, 269 | args.epochs, 270 | args.mode, 271 | args.batch_seed) 272 | train_it.terminate() 273 | valid_mul_it.terminate() 274 | valid_sngl_it.terminate() 275 | elif args.is_predict: 276 | out_df = predict(args.bcolz_root, args.memmap_len, bcolz_prod_info, sample_prod_info, args.models_dir, 277 | use_img_idx=args.use_img_idx) 278 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 279 | elif args.is_predict_valid: 280 | only_valids = bcolz_prod_info[ 281 | bcolz_prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 282 | out_df = predict(args.bcolz_root, args.memmap_len, bcolz_prod_info, only_valids, args.models_dir, 283 | shuffle=args.shuffle, use_img_idx=args.use_img_idx) 284 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 285 | -------------------------------------------------------------------------------- /src/model/tune_avg_vgg16_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | import keras.backend as K 6 | from keras.models import Model 7 | from keras.models import load_model 8 | from keras.layers import Dense 9 | from keras.layers import Input 10 | from keras.layers import BatchNormalization 11 | from keras.layers import TimeDistributed 12 | from keras.layers import Flatten 13 | from keras.layers import Lambda 14 | from keras.optimizers import Adam 15 | from keras.callbacks import ModelCheckpoint 16 | from keras.callbacks import CSVLogger 17 | from src.data.category_idx import map_categories 18 | from src.model.multi_memmap_iterator import MultiMemmapIterator 19 | from src.model.resnet50_vecs import create_images_df 20 | 21 | LOAD_MODEL = 'model.h5' 22 | SNAPSHOT_MODEL = 'model.h5' 23 | LOG_FILE = 'training.log' 24 | PREDICTIONS_FILE = 'single_predictions.csv' 25 | VALID_PREDICTIONS_FILE = 'valid_single_predictions.csv' 26 | MAX_PREDICTIONS_AT_TIME = 50000 27 | 28 | 29 | def train_data(memmap_path, memmap_len, bcolz_prod_info, sample_prod_info, train_split, category_idx, batch_size, 30 | shuffle=None, batch_seed=123, max_images=2, only_single=False, use_img_idx=False): 31 | images_df = create_images_df(bcolz_prod_info, False) 32 | bcolz_prod_info['category_idx'] = map_categories(category_idx, bcolz_prod_info['category_id']) 33 | bcolz_prod_info = bcolz_prod_info.merge(train_split, on='product_id', how='left') 34 | images_df = images_df.merge(bcolz_prod_info, on='product_id', how='left')[ 35 | ['product_id', 'category_idx', 'img_idx', 'num_imgs', 'train']] 36 | if shuffle: 37 | np.random.seed(shuffle) 38 | perm = np.random.permutation(images_df.shape[0]) 39 | images_df = images_df.reindex(perm) 40 | images_df.reset_index(drop=True, inplace=True) 41 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 42 | train_df = images_df[images_df['train']] 43 | valid_df = images_df[~images_df['train']] 44 | num_classes = np.unique(images_df['category_idx']).size 45 | 46 | train_it = MultiMemmapIterator(memmap_path=memmap_path, 47 | memmap_shape=(memmap_len, 512, 2, 2), 48 | images_df=train_df, 49 | num_classes=num_classes, 50 | seed=batch_seed, 51 | batch_size=batch_size, 52 | only_single=only_single, 53 | include_singles=False, 54 | max_images=max_images, 55 | pool_wrokers=4, 56 | shuffle=True, 57 | use_side_input=use_img_idx) 58 | valid_mul_it = MultiMemmapIterator(memmap_path=memmap_path, 59 | memmap_shape=(memmap_len, 512, 2, 2), 60 | images_df=valid_df, 61 | num_classes=num_classes, 62 | seed=batch_seed, 63 | batch_size=batch_size, 64 | shuffle=False, 65 | only_single=False, 66 | include_singles=False, 67 | max_images=4, 68 | pool_wrokers=1, 69 | use_side_input=use_img_idx) 70 | valid_sngl_it = MultiMemmapIterator(memmap_path=memmap_path, 71 | memmap_shape=(memmap_len, 512, 2, 2), 72 | images_df=valid_df, 73 | num_classes=num_classes, 74 | seed=batch_seed, 75 | batch_size=batch_size, 76 | shuffle=False, 77 | only_single=True, 78 | include_singles=True, 79 | max_images=4, 80 | pool_wrokers=1, 81 | use_side_input=use_img_idx) 82 | return train_it, valid_mul_it, valid_sngl_it, num_classes 83 | 84 | 85 | def fit_model(train_it, valid_mul_it, valid_sngl_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0, 86 | seed=125): 87 | model_file = os.path.join(models_dir, LOAD_MODEL) 88 | if os.path.exists(model_file): 89 | model = load_model(model_file) 90 | else: 91 | if mode == 0: 92 | inp1 = Input((None, 512, 2, 2)) 93 | inp1_flat = TimeDistributed(Flatten())(inp1) 94 | inp1_flat = Lambda(lambda x: K.max(x, axis=-2), output_shape=(2048,))(inp1_flat) 95 | x = Dense(4096, activation='relu')(inp1_flat) 96 | x = BatchNormalization(axis=-1)(x) 97 | x = Dense(4096, activation='relu')(x) 98 | x = BatchNormalization(axis=-1)(x) 99 | x = Dense(num_classes, activation='softmax')(x) 100 | model = Model(inp1, x) 101 | 102 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 103 | metrics=['sparse_categorical_accuracy']) 104 | 105 | np.random.seed(seed) 106 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 107 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 108 | model.fit_generator(train_it, 109 | steps_per_epoch=train_it.samples / batch_size, 110 | validation_data=valid_mul_it, 111 | validation_steps=valid_mul_it.samples / batch_size, 112 | epochs=epochs, 113 | callbacks=[checkpointer, csv_logger], 114 | max_queue_size=10, 115 | use_multiprocessing=False) 116 | 117 | with open(os.path.join(models_dir, LOG_FILE), "a") as file: 118 | file.write('Multi {}\n'.format(model.evaluate_generator(valid_mul_it, steps=valid_mul_it.samples / batch_size))) 119 | file.write( 120 | 'Single {}\n'.format(model.evaluate_generator(valid_sngl_it, steps=valid_sngl_it.samples / batch_size))) 121 | 122 | 123 | def predict(memmap_path, memmap_len, prod_info, sample_prod_info, models_dir, batch_size=200, 124 | shuffle=None, top_k=10, use_img_idx=False): 125 | model_file = os.path.join(models_dir, LOAD_MODEL) 126 | if os.path.exists(model_file): 127 | model = load_model(model_file) 128 | else: 129 | raise ValueError("Model doesn't exist") 130 | images_df = create_images_df(prod_info, False) 131 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 132 | ['product_id', 'img_idx', 'num_imgs']] 133 | if shuffle: 134 | np.random.seed(shuffle) 135 | perm = np.random.permutation(images_df.shape[0]) 136 | images_df = images_df.reindex(perm) 137 | images_df.reset_index(drop=True, inplace=True) 138 | if sample_prod_info is not None: 139 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 140 | images_df.sort_values('product_id', inplace=True) 141 | dfs = [] 142 | offset = 0 143 | while offset < images_df.shape[0]: 144 | end_idx = min(images_df.shape[0], offset + MAX_PREDICTIONS_AT_TIME - 5) 145 | while end_idx < images_df.shape[0]: 146 | if images_df.iloc[end_idx - 1].product_id == images_df.iloc[end_idx].product_id: 147 | end_idx += 1 148 | else: 149 | break 150 | it = MultiMemmapIterator(memmap_path=memmap_path, 151 | memmap_shape=(memmap_len, 512, 2, 2), 152 | images_df=images_df[offset:end_idx], 153 | batch_size=batch_size, 154 | pool_wrokers=1, 155 | only_single=False, 156 | include_singles=False, 157 | max_images=4, 158 | shuffle=False, 159 | use_side_input=use_img_idx) 160 | 161 | preds = model.predict_generator(it, it.samples / batch_size, 162 | verbose=1, max_queue_size=10) 163 | it.terminate() 164 | del it 165 | chunk = [] 166 | for i, product_id in enumerate(images_df[offset:end_idx].product_id.unique()): 167 | top_k_preds = np.argpartition(preds[i], -top_k)[-top_k:] 168 | for pred_idx in range(top_k): 169 | chunk.append((product_id, 0, top_k_preds[pred_idx], preds[i, top_k_preds[pred_idx]])) 170 | 171 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 172 | dfs.append(chunk_df) 173 | offset = end_idx 174 | del preds 175 | del chunk 176 | return pd.concat(dfs) 177 | 178 | if __name__ == '__main__': 179 | parser = argparse.ArgumentParser() 180 | parser.add_argument('--fit', action='store_true', dest='is_fit') 181 | parser.add_argument('--predict', action='store_true', dest='is_predict') 182 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 183 | parser.add_argument('--memmap_path', required=True, help='VGG16 vecs memmap path') 184 | parser.add_argument('--prod_info_csv', required=True, 185 | help='Path to prod info csv with which VGG16 were generated') 186 | parser.add_argument('--sample_prod_info_csv', required=True, help='Path to sample prod info csv') 187 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 188 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 189 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 190 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 191 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 192 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 193 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 194 | help="Include only first image from each product") 195 | parser.add_argument('--shuffle', type=int, default=None, required=False, 196 | help='If products should be shuffled, provide seed') 197 | parser.set_defaults(only_first_image=False) 198 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 199 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 200 | parser.add_argument('--use_img_idx', action='store_true', dest='use_img_idx') 201 | parser.set_defaults(use_img_idx=False) 202 | parser.add_argument('--memmap_len', type=int, required=True, help='Number of rows in memmap') 203 | parser.set_defaults(two_outs=False) 204 | parser.add_argument('--max_images', type=int, default=2, required=False, help='Max images in train record') 205 | parser.add_argument('--only_single', action='store_true', dest='only_single') 206 | parser.set_defaults(only_single=False) 207 | 208 | args = parser.parse_args() 209 | if not os.path.isdir(args.models_dir): 210 | os.mkdir(args.models_dir) 211 | 212 | prod_info = pd.read_csv(args.prod_info_csv) 213 | sample_prod_info = pd.read_csv(args.sample_prod_info_csv) 214 | train_split = pd.read_csv(args.train_split_csv) 215 | category_idx = pd.read_csv(args.category_idx_csv) 216 | 217 | if args.is_fit: 218 | train_it, valid_mul_it, valid_sngl_it, num_classes = train_data(args.memmap_path, 219 | args.memmap_len, 220 | prod_info, 221 | sample_prod_info, 222 | train_split, 223 | category_idx, 224 | args.batch_size, 225 | args.shuffle, 226 | args.batch_seed, 227 | args.max_images, 228 | args.only_single, 229 | args.use_img_idx) 230 | fit_model(train_it, valid_mul_it, valid_sngl_it, num_classes, args.models_dir, args.lr, args.batch_size, 231 | args.epochs, 232 | args.mode, 233 | args.batch_seed) 234 | train_it.terminate() 235 | valid_mul_it.terminate() 236 | valid_sngl_it.terminate() 237 | elif args.is_predict: 238 | out_df = predict(args.memmap_path, args.memmap_len, prod_info, sample_prod_info, args.models_dir, 239 | use_img_idx=args.use_img_idx) 240 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 241 | elif args.is_predict_valid: 242 | only_valids = prod_info[ 243 | prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 244 | out_df = predict(args.memmap_path, args.memmap_len, prod_info, only_valids, args.models_dir, 245 | shuffle=args.shuffle, use_img_idx=args.use_img_idx) 246 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 247 | -------------------------------------------------------------------------------- /src/model/tune_pl_avg_resnet50_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | import keras.backend as K 6 | from keras.models import Model 7 | from keras.models import load_model 8 | from keras.layers import Dense 9 | from keras.layers import Input 10 | from keras.layers import BatchNormalization 11 | from keras.layers import TimeDistributed 12 | from keras.layers import SimpleRNN 13 | from keras.layers import GRU 14 | from keras.layers import Lambda 15 | from keras.layers import concatenate 16 | from keras.optimizers import Adam 17 | from keras.callbacks import ModelCheckpoint 18 | from keras.callbacks import CSVLogger 19 | from src.data.category_idx import map_categories 20 | from src.model.multi_memmap_iterator import MultiMemmapIterator 21 | from src.model.combine_iterator import CombineIterator 22 | from src.model.resnet50_vecs import create_images_df 23 | 24 | LOAD_MODEL = 'model.h5' 25 | SNAPSHOT_MODEL = 'model.h5' 26 | LOG_FILE = 'training.log' 27 | PREDICTIONS_FILE = 'single_predictions.csv' 28 | VALID_PREDICTIONS_FILE = 'valid_single_predictions.csv' 29 | MAX_PREDICTIONS_AT_TIME = 50000 30 | 31 | 32 | def create_train_df(prod_info, category_idx, shuffle=None): 33 | images_df = create_images_df(prod_info, False) 34 | prod_info['category_idx'] = map_categories(category_idx, prod_info['category_id']) 35 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 36 | ['product_id', 'category_idx', 'img_idx', 'num_imgs']] 37 | if shuffle: 38 | np.random.seed(shuffle) 39 | perm = np.random.permutation(images_df.shape[0]) 40 | images_df = images_df.reindex(perm) 41 | images_df.reset_index(drop=True, inplace=True) 42 | return images_df 43 | 44 | 45 | def train_data(train_memmap_path, 46 | train_memmap_len, 47 | test_memmap_path, 48 | test_memmap_len, 49 | train_prod_info, 50 | train_pl_prod_info, 51 | test_pl_prod_info, 52 | train_split, 53 | category_idx, 54 | batch_size, 55 | shuffle=None, 56 | batch_seed=123, 57 | max_images=2, 58 | only_single=False, 59 | use_img_idx=False, 60 | include_singles=True): 61 | true_train_df = create_train_df(train_prod_info, category_idx, shuffle=shuffle) 62 | true_train_df = true_train_df.merge(train_split, on='product_id', how='left') 63 | num_classes = np.unique(true_train_df['category_idx']).size 64 | valid_df = true_train_df[~true_train_df['train']] 65 | del true_train_df 66 | 67 | pl_train_df = create_train_df(train_pl_prod_info, category_idx, shuffle=shuffle) 68 | pl_test_df = create_train_df(test_pl_prod_info, category_idx, shuffle=None) 69 | 70 | test_batch_size = int(batch_size * 0.25) 71 | train_batch_size = int(batch_size * 0.75) 72 | train_batch_size += 1 if test_batch_size + train_batch_size < batch_size else 0 73 | 74 | train_train_it = MultiMemmapIterator(memmap_path=train_memmap_path, 75 | memmap_shape=(train_memmap_len, 2048), 76 | images_df=pl_train_df, 77 | num_classes=num_classes, 78 | seed=batch_seed, 79 | batch_size=train_batch_size, 80 | only_single=only_single, 81 | include_singles=include_singles, 82 | max_images=max_images, 83 | pool_wrokers=4, 84 | shuffle=True, 85 | use_side_input=use_img_idx) 86 | 87 | train_test_it = MultiMemmapIterator(memmap_path=test_memmap_path, 88 | memmap_shape=(test_memmap_len, 2048), 89 | images_df=pl_test_df, 90 | num_classes=num_classes, 91 | seed=batch_seed, 92 | batch_size=test_batch_size, 93 | only_single=only_single, 94 | include_singles=include_singles, 95 | max_images=max_images, 96 | pool_wrokers=4, 97 | shuffle=True, 98 | use_side_input=use_img_idx) 99 | 100 | train_it = CombineIterator(train_train_it, train_test_it) 101 | 102 | valid_mul_it = MultiMemmapIterator(memmap_path=train_memmap_path, 103 | memmap_shape=(train_memmap_len, 2048), 104 | images_df=valid_df, 105 | num_classes=num_classes, 106 | seed=batch_seed, 107 | batch_size=batch_size, 108 | shuffle=False, 109 | only_single=False, 110 | include_singles=False, 111 | max_images=4, 112 | pool_wrokers=1, 113 | use_side_input=use_img_idx) 114 | return train_it, valid_mul_it, num_classes 115 | 116 | 117 | def fit_model(train_it, valid_mul_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0, 118 | seed=125): 119 | model_file = os.path.join(models_dir, LOAD_MODEL) 120 | if os.path.exists(model_file): 121 | model = load_model(model_file) 122 | else: 123 | if mode == 0: 124 | inp1 = Input((None, 2048)) 125 | inp2 = Input((None, 8)) 126 | x = concatenate([inp1, inp2]) 127 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 128 | x = BatchNormalization(axis=-1)(x) 129 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 130 | x = BatchNormalization(axis=-1)(x) 131 | x = Lambda(lambda x: K.sum(x, axis=-2), output_shape=(4096,))(x) 132 | x = Dense(num_classes, activation='softmax')(x) 133 | model = Model([inp1, inp2], x) 134 | elif mode == 1: 135 | inp1 = Input((None, 2048)) 136 | inp2 = Input((None, 8)) 137 | x = concatenate([inp1, inp2]) 138 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 139 | x = BatchNormalization(axis=-1)(x) 140 | x = SimpleRNN(4096, activation='relu', recurrent_initializer='identity')(x) 141 | x = BatchNormalization(axis=-1)(x) 142 | x = Dense(num_classes, activation='softmax')(x) 143 | model = Model([inp1, inp2], x) 144 | elif mode == 2: 145 | inp1 = Input((None, 2048)) 146 | inp2 = Input((None, 8)) 147 | x = concatenate([inp1, inp2]) 148 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 149 | x = BatchNormalization(axis=-1)(x) 150 | x = TimeDistributed(Dense(4096, activation='relu'))(x) 151 | x = BatchNormalization(axis=-1)(x) 152 | x = GRU(100, activation='relu')(x) 153 | x = BatchNormalization(axis=-1)(x) 154 | x = Dense(num_classes, activation='softmax')(x) 155 | model = Model([inp1, inp2], x) 156 | elif mode == 3: 157 | inp1 = Input((None, 2048)) 158 | inp1_com = Lambda(lambda x: K.max(x, axis=-2), output_shape=(2048,))(inp1) 159 | x = Dense(4096, activation='relu')(inp1_com) 160 | x = BatchNormalization(axis=-1)(x) 161 | x = Dense(4096, activation='relu')(x) 162 | x = BatchNormalization(axis=-1)(x) 163 | x = Dense(num_classes, activation='softmax')(x) 164 | model = Model(inp1, x) 165 | 166 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 167 | metrics=['sparse_categorical_accuracy']) 168 | 169 | np.random.seed(seed) 170 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 171 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 172 | model.fit_generator(train_it, 173 | steps_per_epoch=train_it.samples / train_it.batch_size, 174 | validation_data=valid_mul_it, 175 | validation_steps=valid_mul_it.samples / valid_mul_it.batch_size, 176 | epochs=epochs, 177 | callbacks=[checkpointer, csv_logger], 178 | max_queue_size=10, 179 | use_multiprocessing=False) 180 | 181 | def predict(memmap_path, memmap_len, prod_info, sample_prod_info, models_dir, batch_size=200, 182 | shuffle=None, top_k=10, use_img_idx=True): 183 | model_file = os.path.join(models_dir, LOAD_MODEL) 184 | if os.path.exists(model_file): 185 | model = load_model(model_file) 186 | else: 187 | raise ValueError("Model doesn't exist") 188 | images_df = create_images_df(prod_info, False) 189 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 190 | ['product_id', 'img_idx', 'num_imgs']] 191 | if shuffle: 192 | np.random.seed(shuffle) 193 | perm = np.random.permutation(images_df.shape[0]) 194 | images_df = images_df.reindex(perm) 195 | images_df.reset_index(drop=True, inplace=True) 196 | if sample_prod_info is not None: 197 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 198 | images_df.sort_values('product_id', inplace=True) 199 | dfs = [] 200 | offset = 0 201 | while offset < images_df.shape[0]: 202 | end_idx = min(images_df.shape[0], offset + MAX_PREDICTIONS_AT_TIME - 5) 203 | while end_idx < images_df.shape[0]: 204 | if images_df.iloc[end_idx - 1].product_id == images_df.iloc[end_idx].product_id: 205 | end_idx += 1 206 | else: 207 | break 208 | it = MultiMemmapIterator(memmap_path=memmap_path, 209 | memmap_shape=(memmap_len, 2048), 210 | images_df=images_df[offset:end_idx], 211 | batch_size=batch_size, 212 | pool_wrokers=1, 213 | only_single=False, 214 | include_singles=False, 215 | max_images=4, 216 | shuffle=False, 217 | use_side_input=use_img_idx) 218 | 219 | preds = model.predict_generator(it, it.samples / batch_size, 220 | verbose=1, max_queue_size=10) 221 | it.terminate() 222 | del it 223 | chunk = [] 224 | for i, product_id in enumerate(images_df[offset:end_idx].product_id.unique()): 225 | top_k_preds = np.argpartition(preds[i], -top_k)[-top_k:] 226 | for pred_idx in range(top_k): 227 | chunk.append((product_id, 0, top_k_preds[pred_idx], preds[i, top_k_preds[pred_idx]])) 228 | 229 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 230 | dfs.append(chunk_df) 231 | offset = end_idx 232 | del preds 233 | del chunk 234 | return pd.concat(dfs) 235 | 236 | 237 | if __name__ == '__main__': 238 | parser = argparse.ArgumentParser() 239 | parser.add_argument('--fit', action='store_true', dest='is_fit') 240 | parser.add_argument('--predict', action='store_true', dest='is_predict') 241 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 242 | parser.add_argument('--memmap_path_train', required=True) 243 | parser.add_argument('--memmap_train_len', type=int, required=True) 244 | parser.add_argument('--memmap_path_test', required=True) 245 | parser.add_argument('--memmap_test_len', type=int, required=True) 246 | parser.add_argument('--train_prod_info_csv', required=True) 247 | parser.add_argument('--train_pl_prod_info_csv', required=True) 248 | parser.add_argument('--test_pl_prod_info_csv', required=True) 249 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 250 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 251 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 252 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 253 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 254 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 255 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 256 | help="Include only first image from each product") 257 | parser.add_argument('--shuffle', type=int, default=None, required=False, 258 | help='If products should be shuffled, provide seed') 259 | parser.set_defaults(only_first_image=False) 260 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 261 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 262 | parser.add_argument('--dont_use_img_idx', action='store_false', dest='use_img_idx') 263 | parser.set_defaults(use_img_idx=True) 264 | parser.set_defaults(two_outs=False) 265 | parser.add_argument('--max_images', type=int, default=2, required=False, help='Max images in train record') 266 | parser.add_argument('--only_single', action='store_true', dest='only_single') 267 | parser.set_defaults(only_single=False) 268 | parser.add_argument('--dont_include_singles', action='store_false', dest='include_singles') 269 | parser.set_defaults(include_singles=True) 270 | 271 | args = parser.parse_args() 272 | if not os.path.isdir(args.models_dir): 273 | os.mkdir(args.models_dir) 274 | 275 | train_prod_info = pd.read_csv(args.train_prod_info_csv) 276 | train_pl_prod_info = pd.read_csv(args.train_pl_prod_info_csv) 277 | test_pl_prod_info = pd.read_csv(args.test_pl_prod_info_csv) 278 | train_split = pd.read_csv(args.train_split_csv) 279 | category_idx = pd.read_csv(args.category_idx_csv) 280 | 281 | if args.is_fit: 282 | train_it, valid_mul_it, num_classes = train_data(args.memmap_path_train, 283 | args.memmap_train_len, 284 | args.memmap_path_test, 285 | args.memmap_test_len, 286 | train_prod_info, 287 | train_pl_prod_info, 288 | test_pl_prod_info, 289 | train_split, 290 | category_idx, 291 | args.batch_size, 292 | args.shuffle, 293 | args.batch_seed, 294 | args.max_images, 295 | args.only_single, 296 | args.use_img_idx, 297 | args.include_singles) 298 | fit_model(train_it, valid_mul_it, num_classes, args.models_dir, args.lr, args.batch_size, 299 | args.epochs, 300 | args.mode, 301 | args.batch_seed) 302 | train_it.first_iterator.terminate() 303 | train_it.second_iterator.terminate() 304 | valid_mul_it.terminate() 305 | elif args.is_predict: 306 | test_prod_info = test_pl_prod_info.drop('category_id', 1) 307 | out_df = predict(args.memmap_path_test, args.memmap_test_len, test_prod_info, test_prod_info, args.models_dir, 308 | use_img_idx=args.use_img_idx) 309 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 310 | elif args.is_predict_valid: 311 | only_valids = train_prod_info[ 312 | train_prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 313 | out_df = predict(args.memmap_path_train, args.memmap_train_len, train_prod_info, only_valids, args.models_dir, 314 | shuffle=args.shuffle, use_img_idx=args.use_img_idx) 315 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 316 | -------------------------------------------------------------------------------- /src/model/tune_resnet50_memmap_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | import itertools 6 | from collections import namedtuple 7 | from keras.models import Model 8 | from keras.models import load_model 9 | from keras.layers import Dense 10 | from keras.layers import Input 11 | from keras.layers import BatchNormalization 12 | from keras.layers import concatenate 13 | from keras.optimizers import Adam 14 | from keras.callbacks import ModelCheckpoint 15 | from keras.callbacks import CSVLogger 16 | from src.data.category_idx import map_categories 17 | from src.model.resnet50_vecs import create_images_df 18 | from src.model.memmap_iterator import MemmapIterator 19 | 20 | LOAD_MODEL = 'model.h5' 21 | SNAPSHOT_MODEL = 'model.h5' 22 | LOG_FILE = 'training.log' 23 | PREDICTIONS_FILE = 'single_predictions.csv' 24 | VALID_PREDICTIONS_FILE = 'valid_single_predictions.csv' 25 | MAX_PREDICTIONS_AT_TIME = 50000 26 | 27 | 28 | def train_data(memmap_path, memmap_len, prod_info, sample_prod_info, train_split, category_idx, batch_size, 29 | shuffle=None, batch_seed=123, use_side_input=False): 30 | images_df = create_images_df(prod_info, False) 31 | prod_info['category_idx'] = map_categories(category_idx, prod_info['category_id']) 32 | prod_info = prod_info.merge(train_split, on='product_id', how='left') 33 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 34 | ['product_id', 'category_idx', 'img_idx', 'num_imgs', 'train']] 35 | if shuffle: 36 | np.random.seed(shuffle) 37 | perm = np.random.permutation(images_df.shape[0]) 38 | images_df = images_df.reindex(perm) 39 | images_df.reset_index(drop=True, inplace=True) 40 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 41 | train_df = images_df[images_df['train']] 42 | valid_df = images_df[~images_df['train']] 43 | num_classes = np.unique(images_df['category_idx']).size 44 | 45 | train_it = MemmapIterator(memmap_path=memmap_path, 46 | memmap_shape=(memmap_len, 2048), 47 | images_df=train_df, 48 | num_classes=num_classes, 49 | seed=batch_seed, 50 | batch_size=batch_size, 51 | pool_wrokers=4, 52 | use_side_input=use_side_input, 53 | shuffle=True) 54 | valid_it = MemmapIterator(memmap_path=memmap_path, 55 | memmap_shape=(memmap_len, 2048), 56 | images_df=valid_df, 57 | num_classes=num_classes, 58 | seed=batch_seed, 59 | batch_size=batch_size, 60 | pool_wrokers=4, 61 | use_side_input=use_side_input, 62 | shuffle=False) 63 | return train_it, valid_it, num_classes 64 | 65 | 66 | def fit_model(train_it, valid_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0, seed=125): 67 | model_file = os.path.join(models_dir, LOAD_MODEL) 68 | if os.path.exists(model_file): 69 | model = load_model(model_file) 70 | else: 71 | if mode == 0: 72 | inp = Input((2048,)) 73 | x = Dense(num_classes, activation='softmax')(inp) 74 | model = Model(inp, x) 75 | elif mode == 1: 76 | inp = Input((2048,)) 77 | x = Dense(4096, activation='relu')(inp) 78 | x = BatchNormalization(axis=-1)(x) 79 | x = Dense(4096, activation='relu')(x) 80 | x = BatchNormalization(axis=-1)(x) 81 | x = Dense(num_classes, activation='softmax')(x) 82 | model = Model(inp, x) 83 | elif mode == 2: 84 | inp = Input((2048,)) 85 | x = Dense(4096, activation='relu')(inp) 86 | x = BatchNormalization(axis=-1)(x) 87 | x = Dense(num_classes, activation='softmax')(x) 88 | model = Model(inp, x) 89 | elif mode == 3: 90 | inp = Input((2048,)) 91 | x = Dense(2048, activation='relu')(inp) 92 | x = BatchNormalization(axis=-1)(x) 93 | x = Dense(2048, activation='relu')(x) 94 | x = BatchNormalization(axis=-1)(x) 95 | x = Dense(num_classes, activation='softmax')(x) 96 | model = Model(inp, x) 97 | elif mode == 4: 98 | inp = Input((2048,)) 99 | x = Dense(1024, activation='relu')(inp) 100 | x = BatchNormalization(axis=-1)(x) 101 | x = Dense(1024, activation='relu')(x) 102 | x = BatchNormalization(axis=-1)(x) 103 | x = Dense(num_classes, activation='softmax')(x) 104 | model = Model(inp, x) 105 | elif mode == 5: 106 | inp = Input((2048,)) 107 | x = Dense(6144, activation='relu')(inp) 108 | x = BatchNormalization(axis=-1)(x) 109 | x = Dense(6144, activation='relu')(x) 110 | x = BatchNormalization(axis=-1)(x) 111 | x = Dense(num_classes, activation='softmax')(x) 112 | model = Model(inp, x) 113 | elif mode == 6: 114 | inp_vec = Input((2048,)) 115 | img_idx_inp = Input((8,)) 116 | x = concatenate([inp_vec, img_idx_inp]) 117 | x = Dense(4096, activation='relu')(x) 118 | x = BatchNormalization(axis=-1)(x) 119 | x = Dense(4096, activation='relu')(x) 120 | x = BatchNormalization(axis=-1)(x) 121 | x = Dense(num_classes, activation='softmax')(x) 122 | model = Model([inp_vec, img_idx_inp], x) 123 | elif mode == 7: 124 | inp_vec = Input((2048,)) 125 | img_idx_inp = Input((8,)) 126 | x = concatenate([inp_vec, img_idx_inp]) 127 | x = Dense(2048, activation='relu')(x) 128 | x = BatchNormalization(axis=-1)(x) 129 | x = Dense(2048, activation='relu')(x) 130 | x = BatchNormalization(axis=-1)(x) 131 | x = Dense(num_classes, activation='softmax')(x) 132 | model = Model([inp_vec, img_idx_inp], x) 133 | elif mode == 8: 134 | inp_vec = Input((2048,)) 135 | img_idx_inp = Input((8,)) 136 | x = concatenate([inp_vec, img_idx_inp]) 137 | x = Dense(6144, activation='relu')(x) 138 | x = BatchNormalization(axis=-1)(x) 139 | x = Dense(6144, activation='relu')(x) 140 | x = BatchNormalization(axis=-1)(x) 141 | x = Dense(num_classes, activation='softmax')(x) 142 | model = Model([inp_vec, img_idx_inp], x) 143 | 144 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 145 | metrics=['sparse_categorical_accuracy']) 146 | 147 | np.random.seed(seed) 148 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 149 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 150 | model.fit_generator(train_it, 151 | steps_per_epoch=train_it.samples / batch_size, 152 | validation_data=valid_it, 153 | validation_steps=valid_it.samples / batch_size, 154 | epochs=epochs, 155 | callbacks=[checkpointer, csv_logger]) 156 | 157 | 158 | def predict(memmap_path, memmap_len, prod_info, sample_prod_info, models_dir, use_side_input=False, batch_size=200, 159 | shuffle=None, top_k=10): 160 | model_file = os.path.join(models_dir, LOAD_MODEL) 161 | if os.path.exists(model_file): 162 | model = load_model(model_file) 163 | else: 164 | raise ValueError("Model doesn't exist") 165 | images_df = create_images_df(prod_info, False) 166 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 167 | ['product_id', 'img_idx', 'num_imgs']] 168 | if shuffle: 169 | np.random.seed(shuffle) 170 | perm = np.random.permutation(images_df.shape[0]) 171 | images_df = images_df.reindex(perm) 172 | images_df.reset_index(drop=True, inplace=True) 173 | if sample_prod_info is not None: 174 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 175 | images_df.sort_values('product_id', inplace=True) 176 | dfs = [] 177 | offset = 0 178 | while offset < images_df.shape[0]: 179 | end_idx = min(images_df.shape[0], offset + MAX_PREDICTIONS_AT_TIME - 5) 180 | while end_idx < images_df.shape[0]: 181 | if images_df.iloc[end_idx - 1].product_id == images_df.iloc[end_idx].product_id: 182 | end_idx += 1 183 | else: 184 | break 185 | it = MemmapIterator(memmap_path=memmap_path, 186 | memmap_shape=(memmap_len, 2048), 187 | images_df=images_df[offset:end_idx], 188 | batch_size=batch_size, 189 | pool_wrokers=1, 190 | use_side_input=use_side_input, 191 | shuffle=False) 192 | preds = model.predict_generator(it, it.samples / batch_size, 193 | verbose=1, max_queue_size=10) 194 | it.terminate() 195 | del it 196 | product_start = 0 197 | prev_product_id = 0 198 | chunk = [] 199 | for i, row in enumerate( 200 | itertools.chain(images_df[offset:(offset + preds.shape[0])].itertuples(), 201 | [namedtuple('Pandas', ['product_id', 'img_idx'])(1, 0)])): 202 | if prev_product_id != 0 and prev_product_id != row.product_id: 203 | prods = preds[product_start:i].prod(axis=-2) 204 | prods = prods / prods.sum() 205 | top_k_preds = np.argpartition(prods, -top_k)[-top_k:] 206 | for pred_idx in range(top_k): 207 | chunk.append((prev_product_id, 0, top_k_preds[pred_idx], prods[top_k_preds[pred_idx]])) 208 | product_start = i 209 | prev_product_id = row.product_id 210 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 211 | dfs.append(chunk_df) 212 | offset += preds.shape[0] 213 | del preds 214 | del chunk 215 | return pd.concat(dfs) 216 | 217 | 218 | if __name__ == '__main__': 219 | parser = argparse.ArgumentParser() 220 | parser.add_argument('--fit', action='store_true', dest='is_fit') 221 | parser.add_argument('--predict', action='store_true', dest='is_predict') 222 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 223 | parser.add_argument('--memmap_path', required=True, help='ResNet50 vecs memmap path') 224 | parser.add_argument('--memmap_len', type=int, required=True, help='Length of memmap') 225 | parser.add_argument('--prod_info_csv', required=True, 226 | help='Path to prod info csv with which VGG16 were generated') 227 | parser.add_argument('--sample_prod_info_csv', required=True, help='Path to sample prod info csv') 228 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 229 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 230 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 231 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 232 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 233 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 234 | parser.add_argument('--shuffle', type=int, default=None, required=False, 235 | help='If products should be shuffled, provide seed') 236 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 237 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 238 | parser.add_argument('--use_img_idx', action='store_true', dest='use_img_idx') 239 | parser.set_defaults(use_img_idx=False) 240 | 241 | args = parser.parse_args() 242 | if not os.path.isdir(args.models_dir): 243 | os.mkdir(args.models_dir) 244 | 245 | prod_info = pd.read_csv(args.prod_info_csv) 246 | sample_prod_info = pd.read_csv(args.sample_prod_info_csv) 247 | train_split = pd.read_csv(args.train_split_csv) 248 | category_idx = pd.read_csv(args.category_idx_csv) 249 | 250 | if args.is_fit: 251 | train_it, valid_it, num_classes = train_data(memmap_path=args.memmap_path, 252 | memmap_len=args.memmap_len, 253 | prod_info=prod_info, 254 | sample_prod_info=sample_prod_info, 255 | train_split=train_split, 256 | category_idx=category_idx, 257 | batch_size=args.batch_size, 258 | shuffle=args.shuffle, 259 | batch_seed=args.batch_seed, 260 | use_side_input=args.use_img_idx) 261 | fit_model(train_it, valid_it, num_classes, args.models_dir, args.lr, args.batch_size, args.epochs, args.mode, 262 | args.batch_seed) 263 | train_it.terminate() 264 | valid_it.terminate() 265 | elif args.is_predict: 266 | out_df = predict(args.memmap_path, args.memmap_len, prod_info, sample_prod_info, args.models_dir, 267 | args.use_img_idx) 268 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 269 | elif args.is_predict_valid: 270 | only_valids = prod_info[ 271 | prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 272 | out_df = predict(args.memmap_path, args.memmap_len, prod_info, only_valids, args.models_dir, 273 | shuffle=args.shuffle, use_side_input=args.use_img_idx) 274 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 275 | -------------------------------------------------------------------------------- /src/model/tune_resnet50_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | from keras.models import Model 6 | from keras.models import load_model 7 | from keras.layers import Dense 8 | from keras.layers import Input 9 | from keras.layers import BatchNormalization 10 | from keras.layers import concatenate 11 | from keras.optimizers import Adam 12 | from keras.callbacks import ModelCheckpoint 13 | from keras.callbacks import CSVLogger 14 | from keras.utils import to_categorical 15 | from ..data.category_idx import map_categories 16 | from .bcolz_iterator import BcolzIterator 17 | from .resnet50_vecs import create_images_df 18 | 19 | LOAD_MODEL = 'model.h5' 20 | SNAPSHOT_MODEL = 'model.h5' 21 | LOG_FILE = 'training.log' 22 | PREDICTIONS_FILE = 'predictions.csv' 23 | VALID_PREDICTIONS_FILE = 'valid_predictions.csv' 24 | MAX_PREDICTIONS_AT_TIME = 50000 25 | 26 | def form_side_input(df): 27 | return np.hstack([to_categorical(df.num_imgs - 1, num_classes=4), 28 | to_categorical(df.img_idx, num_classes=4)]) 29 | 30 | def train_data(bcolz_root, bcolz_prod_info, sample_prod_info, train_split, category_idx, only_first_image, batch_size, 31 | shuffle=None, batch_seed=123, use_img_idx=False): 32 | images_df = create_images_df(bcolz_prod_info, only_first_image) 33 | bcolz_prod_info['category_idx'] = map_categories(category_idx, bcolz_prod_info['category_id']) 34 | bcolz_prod_info = bcolz_prod_info.merge(train_split, on='product_id', how='left') 35 | cat_idxs = images_df.merge(bcolz_prod_info, on='product_id', how='left')[ 36 | ['product_id', 'category_idx', 'img_idx', 'num_imgs', 'train']] 37 | del images_df 38 | if shuffle: 39 | np.random.seed(shuffle) 40 | perm = np.random.permutation(cat_idxs.shape[0]) 41 | cat_idxs = cat_idxs.reindex(perm) 42 | cat_idxs.reset_index(drop=True, inplace=True) 43 | cat_idxs_smpl = cat_idxs[cat_idxs.product_id.isin(sample_prod_info.product_id)] 44 | idxs = cat_idxs_smpl.index.values 45 | train_idxs = idxs[cat_idxs_smpl['train']] 46 | valid_idxs = idxs[~cat_idxs_smpl['train']] 47 | num_classes = np.unique(cat_idxs_smpl['category_idx']).size 48 | 49 | if use_img_idx: 50 | side_input = form_side_input(cat_idxs) 51 | else: 52 | side_input = None 53 | 54 | train_it = BcolzIterator(bcolz_root=bcolz_root, x_idxs=train_idxs, 55 | side_input=side_input, 56 | y=cat_idxs_smpl['category_idx'].loc[train_idxs].as_matrix(), 57 | num_classes=num_classes, seed=batch_seed, batch_size=batch_size, shuffle=True) 58 | valid_it = BcolzIterator(bcolz_root=bcolz_root, x_idxs=valid_idxs, 59 | side_input=side_input, 60 | y=cat_idxs_smpl['category_idx'].loc[valid_idxs].as_matrix(), 61 | num_classes=num_classes, batch_size=batch_size, shuffle=False) 62 | return train_it, valid_it, num_classes 63 | 64 | 65 | def fit_model(train_it, valid_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0, seed=125): 66 | model_file = os.path.join(models_dir, LOAD_MODEL) 67 | if os.path.exists(model_file): 68 | model = load_model(model_file) 69 | else: 70 | if mode == 0: 71 | inp = Input((2048,)) 72 | x = Dense(num_classes, activation='softmax')(inp) 73 | model = Model(inp, x) 74 | elif mode == 1: 75 | inp = Input((2048,)) 76 | x = Dense(4096, activation='relu')(inp) 77 | x = BatchNormalization(axis=-1)(x) 78 | x = Dense(4096, activation='relu')(x) 79 | x = BatchNormalization(axis=-1)(x) 80 | x = Dense(num_classes, activation='softmax')(x) 81 | model = Model(inp, x) 82 | elif mode == 2: 83 | inp = Input((2048,)) 84 | x = Dense(4096, activation='relu')(inp) 85 | x = BatchNormalization(axis=-1)(x) 86 | x = Dense(num_classes, activation='softmax')(x) 87 | model = Model(inp, x) 88 | elif mode == 3: 89 | inp = Input((2048,)) 90 | x = Dense(2048, activation='relu')(inp) 91 | x = BatchNormalization(axis=-1)(x) 92 | x = Dense(2048, activation='relu')(x) 93 | x = BatchNormalization(axis=-1)(x) 94 | x = Dense(num_classes, activation='softmax')(x) 95 | model = Model(inp, x) 96 | elif mode == 4: 97 | inp = Input((2048,)) 98 | x = Dense(1024, activation='relu')(inp) 99 | x = BatchNormalization(axis=-1)(x) 100 | x = Dense(1024, activation='relu')(x) 101 | x = BatchNormalization(axis=-1)(x) 102 | x = Dense(num_classes, activation='softmax')(x) 103 | model = Model(inp, x) 104 | elif mode == 5: 105 | inp = Input((2048,)) 106 | x = Dense(6144, activation='relu')(inp) 107 | x = BatchNormalization(axis=-1)(x) 108 | x = Dense(6144, activation='relu')(x) 109 | x = BatchNormalization(axis=-1)(x) 110 | x = Dense(num_classes, activation='softmax')(x) 111 | model = Model(inp, x) 112 | elif mode == 6: 113 | inp_vec = Input((2048,)) 114 | img_idx_inp = Input((8,)) 115 | x = concatenate([inp_vec, img_idx_inp]) 116 | x = Dense(4096, activation='relu')(x) 117 | x = BatchNormalization(axis=-1)(x) 118 | x = Dense(4096, activation='relu')(x) 119 | x = BatchNormalization(axis=-1)(x) 120 | x = Dense(num_classes, activation='softmax')(x) 121 | model = Model([inp_vec, img_idx_inp], x) 122 | elif mode == 7: 123 | inp_vec = Input((2048,)) 124 | img_idx_inp = Input((8,)) 125 | x = concatenate([inp_vec, img_idx_inp]) 126 | x = Dense(2048, activation='relu')(x) 127 | x = BatchNormalization(axis=-1)(x) 128 | x = Dense(2048, activation='relu')(x) 129 | x = BatchNormalization(axis=-1)(x) 130 | x = Dense(num_classes, activation='softmax')(x) 131 | model = Model([inp_vec, img_idx_inp], x) 132 | elif mode == 8: 133 | inp_vec = Input((2048,)) 134 | img_idx_inp = Input((8,)) 135 | x = concatenate([inp_vec, img_idx_inp]) 136 | x = Dense(6144, activation='relu')(x) 137 | x = BatchNormalization(axis=-1)(x) 138 | x = Dense(6144, activation='relu')(x) 139 | x = BatchNormalization(axis=-1)(x) 140 | x = Dense(num_classes, activation='softmax')(x) 141 | model = Model([inp_vec, img_idx_inp], x) 142 | 143 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 144 | metrics=['sparse_categorical_accuracy']) 145 | 146 | np.random.seed(seed) 147 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 148 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 149 | model.fit_generator(train_it, 150 | steps_per_epoch=train_it.samples / batch_size, 151 | validation_data=valid_it, 152 | validation_steps=valid_it.samples / batch_size, 153 | epochs=epochs, 154 | callbacks=[checkpointer, csv_logger]) 155 | 156 | 157 | def predict(bcolz_root, prod_info, sample_prod_info, models_dir, only_first_image, batch_size=200, shuffle=None, 158 | top_k=10, use_img_idx=False): 159 | model_file = os.path.join(models_dir, LOAD_MODEL) 160 | if os.path.exists(model_file): 161 | model = load_model(model_file) 162 | else: 163 | raise ValueError("Model doesn't exist") 164 | images_df = create_images_df(prod_info, only_first_image) 165 | images_df = images_df.merge(prod_info, on='product_id', how='left')[ 166 | ['product_id', 'img_idx', 'num_imgs']] 167 | if shuffle: 168 | np.random.seed(shuffle) 169 | perm = np.random.permutation(images_df.shape[0]) 170 | images_df = images_df.reindex(perm) 171 | images_df.reset_index(drop=True, inplace=True) 172 | if sample_prod_info is not None: 173 | images_df_smpl = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 174 | else: 175 | images_df_smpl = images_df 176 | idxs = images_df_smpl.index.values 177 | dfs = [] 178 | steps = MAX_PREDICTIONS_AT_TIME // batch_size 179 | offset = 0 180 | while offset < images_df_smpl.shape[0]: 181 | it = BcolzIterator(bcolz_root=bcolz_root, 182 | x_idxs=idxs[offset:], 183 | side_input=form_side_input(images_df[offset:]) if use_img_idx else None, 184 | batch_size=batch_size, 185 | shuffle=False) 186 | preds = model.predict_generator(it, min(steps, (images_df_smpl.shape[0] - offset) / batch_size), 187 | verbose=1, max_queue_size=5) 188 | top_k_preds = np.argpartition(preds, -top_k)[:, -top_k:] 189 | chunk = [] 190 | for i in range(top_k_preds.shape[0]): 191 | product_id = images_df_smpl.iloc[offset + i]['product_id'] 192 | img_idx = images_df_smpl.iloc[offset + i]['img_idx'] 193 | for pred_idx in range(top_k): 194 | chunk.append((product_id, img_idx, top_k_preds[i, pred_idx], preds[i, top_k_preds[i, pred_idx]])) 195 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 196 | dfs.append(chunk_df) 197 | offset += top_k_preds.shape[0] 198 | del top_k_preds 199 | del preds 200 | del chunk 201 | return pd.concat(dfs) 202 | 203 | 204 | if __name__ == '__main__': 205 | parser = argparse.ArgumentParser() 206 | parser.add_argument('--fit', action='store_true', dest='is_fit') 207 | parser.add_argument('--predict', action='store_true', dest='is_predict') 208 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 209 | parser.add_argument('--bcolz_root', required=True, help='VGG16 vecs bcolz root path') 210 | parser.add_argument('--bcolz_prod_info_csv', required=True, 211 | help='Path to prod info csv with which VGG16 were generated') 212 | parser.add_argument('--sample_prod_info_csv', required=True, help='Path to sample prod info csv') 213 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 214 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 215 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 216 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 217 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 218 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 219 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 220 | help="Include only first image from each product") 221 | parser.add_argument('--shuffle', type=int, default=None, required=False, 222 | help='If products should be shuffled, provide seed') 223 | parser.set_defaults(only_first_image=False) 224 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 225 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 226 | parser.add_argument('--use_img_idx', action='store_true', dest='use_img_idx') 227 | parser.set_defaults(use_img_idx=False) 228 | 229 | args = parser.parse_args() 230 | if not os.path.isdir(args.models_dir): 231 | os.mkdir(args.models_dir) 232 | 233 | bcolz_prod_info = pd.read_csv(args.bcolz_prod_info_csv) 234 | sample_prod_info = pd.read_csv(args.sample_prod_info_csv) 235 | train_split = pd.read_csv(args.train_split_csv) 236 | category_idx = pd.read_csv(args.category_idx_csv) 237 | 238 | if args.is_fit: 239 | train_it, valid_it, num_classes = train_data(args.bcolz_root, bcolz_prod_info, sample_prod_info, 240 | train_split, 241 | category_idx, 242 | args.only_first_image, 243 | args.batch_size, args.shuffle, 244 | args.batch_seed, 245 | args.use_img_idx) 246 | fit_model(train_it, valid_it, num_classes, args.models_dir, args.lr, args.batch_size, args.epochs, args.mode, 247 | args.batch_seed) 248 | elif args.is_predict: 249 | out_df = predict(args.bcolz_root, bcolz_prod_info, sample_prod_info, args.models_dir, args.only_first_image, 250 | args.use_img_idx) 251 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 252 | elif args.is_predict_valid: 253 | only_valids = bcolz_prod_info[ 254 | bcolz_prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 255 | out_df = predict(args.bcolz_root, bcolz_prod_info, only_valids, args.models_dir, args.only_first_image, 256 | shuffle=args.shuffle, use_img_idx=args.use_img_idx) 257 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 258 | -------------------------------------------------------------------------------- /src/model/tune_vgg16_vecs.py: -------------------------------------------------------------------------------- 1 | import os 2 | import argparse 3 | import pandas as pd 4 | import numpy as np 5 | from keras.models import Model 6 | from keras.models import load_model 7 | from keras.layers import Dense 8 | from keras.layers import Flatten 9 | from keras.layers import Input 10 | from keras.layers import Dropout 11 | from keras.layers.normalization import BatchNormalization 12 | from keras.optimizers import Adam 13 | from keras.optimizers import SGD 14 | from keras.callbacks import ModelCheckpoint 15 | from keras.callbacks import CSVLogger 16 | from ..data.category_idx import map_categories 17 | from .bcolz_iterator import BcolzIterator 18 | from .vgg16_vecs import create_images_df 19 | 20 | LOAD_MODEL = 'model.h5' 21 | SNAPSHOT_MODEL = 'model.h5' 22 | LOG_FILE = 'training.log' 23 | PREDICTIONS_FILE = 'predictions.csv' 24 | VALID_PREDICTIONS_FILE = 'valid_predictions.csv' 25 | MAX_PREDICTIONS_AT_TIME = 50000 26 | 27 | 28 | def train_data(bcolz_root, bcolz_prod_info, sample_prod_info, train_split, category_idx, only_first_image, batch_size, 29 | shuffle=None, batch_seed=123): 30 | images_df = create_images_df(bcolz_prod_info, only_first_image) 31 | bcolz_prod_info['category_idx'] = map_categories(category_idx, bcolz_prod_info['category_id']) 32 | bcolz_prod_info = bcolz_prod_info.merge(train_split, on='product_id', how='left') 33 | cat_idxs = images_df.merge(bcolz_prod_info, on='product_id', how='left')[['product_id', 'category_idx', 'train']] 34 | if shuffle: 35 | np.random.seed(shuffle) 36 | perm = np.random.permutation(cat_idxs.shape[0]) 37 | cat_idxs = cat_idxs.reindex(perm) 38 | cat_idxs.reset_index(drop=True, inplace=True) 39 | cat_idxs = cat_idxs[cat_idxs.product_id.isin(sample_prod_info.product_id)] 40 | idxs = cat_idxs.index.values 41 | train_idxs = idxs[cat_idxs['train']] 42 | valid_idxs = idxs[~cat_idxs['train']] 43 | num_classes = np.unique(cat_idxs['category_idx']).size 44 | 45 | train_it = BcolzIterator(bcolz_root=bcolz_root, x_idxs=train_idxs, 46 | y=cat_idxs['category_idx'].loc[train_idxs].as_matrix(), 47 | num_classes=num_classes, seed=batch_seed, batch_size=batch_size, shuffle=True) 48 | valid_it = BcolzIterator(bcolz_root=bcolz_root, x_idxs=valid_idxs, 49 | y=cat_idxs['category_idx'].loc[valid_idxs].as_matrix(), 50 | num_classes=num_classes, batch_size=batch_size, shuffle=False) 51 | return train_it, valid_it, num_classes 52 | 53 | 54 | def fit_model(train_it, valid_it, num_classes, models_dir, lr=0.001, batch_size=64, epochs=1, mode=0): 55 | model_file = os.path.join(models_dir, LOAD_MODEL) 56 | if os.path.exists(model_file): 57 | model = load_model(model_file) 58 | else: 59 | if mode == 0: 60 | inp = Input((512, 2, 2)) 61 | x = Flatten()(inp) 62 | x = Dense(1024, activation='relu')(x) 63 | x = BatchNormalization(axis=-1)(x) 64 | x = Dense(num_classes, activation='softmax')(x) 65 | model = Model(inp, x) 66 | elif mode == 1: 67 | inp = Input((512, 2, 2)) 68 | x = Flatten()(inp) 69 | x = Dense(2048, activation='relu')(x) 70 | x = BatchNormalization(axis=-1)(x) 71 | x = Dense(num_classes, activation='softmax')(x) 72 | model = Model(inp, x) 73 | elif mode == 2: 74 | inp = Input((512, 2, 2)) 75 | x = Flatten()(inp) 76 | x = Dense(512, activation='relu')(x) 77 | x = BatchNormalization(axis=-1)(x) 78 | x = Dense(1024, activation='relu')(x) 79 | x = BatchNormalization(axis=-1)(x) 80 | x = Dense(num_classes, activation='softmax')(x) 81 | model = Model(inp, x) 82 | elif mode == 3: 83 | inp = Input((512, 2, 2)) 84 | x = Flatten()(inp) 85 | x = Dense(num_classes, activation='softmax')(x) 86 | model = Model(inp, x) 87 | elif mode == 4: 88 | inp = Input((512, 2, 2)) 89 | x = Flatten()(inp) 90 | x = BatchNormalization(axis=-1)(x) 91 | x = Dense(num_classes, activation='softmax')(x) 92 | model = Model(inp, x) 93 | elif mode == 5: 94 | inp = Input((512, 2, 2)) 95 | x = Flatten()(inp) 96 | x = Dense(512, activation='relu')(x) 97 | x = BatchNormalization(axis=-1)(x) 98 | x = Dense(num_classes, activation='softmax')(x) 99 | model = Model(inp, x) 100 | elif mode == 6: 101 | inp = Input((512, 2, 2)) 102 | x = Flatten()(inp) 103 | x = Dense(512, activation='relu')(x) 104 | x = BatchNormalization(axis=-1)(x) 105 | x = Dense(1024, activation='relu')(x) 106 | x = BatchNormalization(axis=-1)(x) 107 | x = Dense(num_classes, activation='softmax')(x) 108 | model = Model(inp, x) 109 | elif mode == 7: 110 | inp = Input((512, 2, 2)) 111 | x = Flatten()(inp) 112 | x = Dense(1024, activation='relu')(x) 113 | x = BatchNormalization(axis=-1)(x) 114 | x = Dense(2048, activation='relu')(x) 115 | x = BatchNormalization(axis=-1)(x) 116 | x = Dense(num_classes, activation='softmax')(x) 117 | model = Model(inp, x) 118 | elif mode == 8: 119 | inp = Input((512, 2, 2)) 120 | x = Flatten()(inp) 121 | x = Dense(512, activation='relu')(x) 122 | x = BatchNormalization(axis=-1)(x) 123 | x = Dense(512, activation='relu')(x) 124 | x = BatchNormalization(axis=-1)(x) 125 | x = Dense(1024, activation='relu')(x) 126 | x = BatchNormalization(axis=-1)(x) 127 | x = Dense(num_classes, activation='softmax')(x) 128 | model = Model(inp, x) 129 | elif mode == 9: 130 | inp = Input((512, 2, 2)) 131 | x = Flatten()(inp) 132 | x = BatchNormalization(axis=-1)(x) 133 | x = Dense(2048, activation='relu')(x) 134 | x = BatchNormalization(axis=-1)(x) 135 | x = Dense(num_classes, activation='softmax')(x) 136 | model = Model(inp, x) 137 | elif mode == 10: 138 | inp = Input((512, 2, 2)) 139 | x = Flatten()(inp) 140 | x = Dense(4096, activation='relu')(x) 141 | x = BatchNormalization(axis=-1)(x) 142 | x = Dense(num_classes, activation='softmax')(x) 143 | model = Model(inp, x) 144 | elif mode == 11: 145 | inp = Input((512, 2, 2)) 146 | x = Flatten()(inp) 147 | x = Dense(4096, activation='relu')(x) 148 | x = BatchNormalization(axis=-1)(x) 149 | x = Dropout(0.5)(x) 150 | x = Dense(num_classes, activation='softmax')(x) 151 | model = Model(inp, x) 152 | elif mode == 12: 153 | inp = Input((512, 2, 2)) 154 | x = Flatten()(inp) 155 | x = Dense(4096, activation='relu')(x) 156 | x = BatchNormalization(axis=-1)(x) 157 | x = Dense(4096, activation='relu')(x) 158 | x = BatchNormalization(axis=-1)(x) 159 | x = Dense(num_classes, activation='softmax')(x) 160 | model = Model(inp, x) 161 | elif mode == 13: 162 | inp = Input((512, 2, 2)) 163 | x = Flatten()(inp) 164 | x = Dense(4096, activation='relu')(x) 165 | x = BatchNormalization(axis=-1)(x) 166 | x = Dropout(0.5)(x) 167 | x = Dense(4096, activation='relu')(x) 168 | x = BatchNormalization(axis=-1)(x) 169 | x = Dropout(0.5)(x) 170 | x = Dense(num_classes, activation='softmax')(x) 171 | model = Model(inp, x) 172 | elif mode == 14: 173 | inp = Input((512, 2, 2)) 174 | x = Flatten()(inp) 175 | x = Dense(4096, activation='relu')(x) 176 | x = BatchNormalization(axis=-1)(x) 177 | x = Dropout(0.2)(x) 178 | x = Dense(4096, activation='relu')(x) 179 | x = BatchNormalization(axis=-1)(x) 180 | x = Dropout(0.4)(x) 181 | x = Dense(num_classes, activation='softmax')(x) 182 | model = Model(inp, x) 183 | 184 | if mode == 6: 185 | model.compile(optimizer=SGD(lr=lr), loss='sparse_categorical_crossentropy', 186 | metrics=['sparse_categorical_accuracy']) 187 | else: 188 | model.compile(optimizer=Adam(lr=lr), loss='sparse_categorical_crossentropy', 189 | metrics=['sparse_categorical_accuracy']) 190 | 191 | np.random.seed(125) 192 | checkpointer = ModelCheckpoint(filepath=os.path.join(models_dir, SNAPSHOT_MODEL)) 193 | csv_logger = CSVLogger(os.path.join(models_dir, LOG_FILE), append=True) 194 | model.fit_generator(train_it, 195 | steps_per_epoch=train_it.samples / batch_size, 196 | validation_data=valid_it, 197 | validation_steps=valid_it.samples / batch_size, 198 | epochs=epochs, 199 | callbacks=[checkpointer, csv_logger]) 200 | 201 | 202 | def predict(bcolz_root, prod_info, sample_prod_info, models_dir, only_first_image, batch_size=200, shuffle=None, 203 | top_k=10): 204 | model_file = os.path.join(models_dir, LOAD_MODEL) 205 | if os.path.exists(model_file): 206 | model = load_model(model_file) 207 | else: 208 | raise ValueError("Model doesn't exist") 209 | images_df = create_images_df(prod_info, only_first_image) 210 | if shuffle: 211 | np.random.seed(shuffle) 212 | perm = np.random.permutation(images_df.shape[0]) 213 | images_df = images_df.reindex(perm) 214 | images_df.reset_index(drop=True, inplace=True) 215 | if sample_prod_info is not None: 216 | images_df = images_df[images_df.product_id.isin(sample_prod_info.product_id)] 217 | idxs = images_df.index.values 218 | dfs = [] 219 | steps = MAX_PREDICTIONS_AT_TIME // batch_size 220 | offset = 0 221 | while offset < images_df.shape[0]: 222 | it = BcolzIterator(bcolz_root=bcolz_root, x_idxs=idxs[offset:], 223 | batch_size=batch_size, 224 | shuffle=False) 225 | preds = model.predict_generator(it, min(steps, (images_df.shape[0] - offset) / batch_size), 226 | verbose=1, max_queue_size=5) 227 | top_k_preds = np.argpartition(preds, -top_k)[:, -top_k:] 228 | chunk = [] 229 | for i in range(top_k_preds.shape[0]): 230 | product_id = images_df.iloc[offset + i]['product_id'] 231 | img_idx = images_df.iloc[offset + i]['img_idx'] 232 | for pred_idx in range(top_k): 233 | chunk.append((product_id, img_idx, top_k_preds[i, pred_idx], preds[i, top_k_preds[i, pred_idx]])) 234 | chunk_df = pd.DataFrame(chunk, columns=['product_id', 'img_idx', 'category_idx', 'prob']) 235 | dfs.append(chunk_df) 236 | offset += top_k_preds.shape[0] 237 | del top_k_preds 238 | del preds 239 | del chunk 240 | return pd.concat(dfs) 241 | 242 | 243 | if __name__ == '__main__': 244 | parser = argparse.ArgumentParser() 245 | parser.add_argument('--fit', action='store_true', dest='is_fit') 246 | parser.add_argument('--predict', action='store_true', dest='is_predict') 247 | parser.add_argument('--predict_valid', action='store_true', dest='is_predict_valid') 248 | parser.add_argument('--bcolz_root', required=True, help='VGG16 vecs bcolz root path') 249 | parser.add_argument('--bcolz_prod_info_csv', required=True, 250 | help='Path to prod info csv with which VGG16 were generated') 251 | parser.add_argument('--sample_prod_info_csv', required=True, help='Path to sample prod info csv') 252 | parser.add_argument('--category_idx_csv', required=True, help='Path to categories to index mapping csv') 253 | parser.add_argument('--train_split_csv', required=True, help='Train split csv') 254 | parser.add_argument('--models_dir', required=True, help='Output directory for models snapshots') 255 | parser.add_argument('--lr', type=float, default=0.001, required=False, help='Learning rate') 256 | parser.add_argument('--batch_size', type=int, default=64, required=False, help='Batch size') 257 | parser.add_argument('--epochs', type=int, default=1, required=False, help='Number of epochs') 258 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 259 | help="Include only first image from each product") 260 | parser.add_argument('--shuffle', type=int, default=None, required=False, 261 | help='If products should be shuffled, provide seed') 262 | parser.set_defaults(only_first_image=False) 263 | parser.add_argument('--mode', type=int, default=0, required=False, help='Mode') 264 | parser.add_argument('--batch_seed', type=int, default=123, required=False, help='Batch seed') 265 | 266 | args = parser.parse_args() 267 | if not os.path.isdir(args.models_dir): 268 | os.mkdir(args.models_dir) 269 | 270 | bcolz_prod_info = pd.read_csv(args.bcolz_prod_info_csv) 271 | sample_prod_info = pd.read_csv(args.sample_prod_info_csv) 272 | train_split = pd.read_csv(args.train_split_csv) 273 | category_idx = pd.read_csv(args.category_idx_csv) 274 | 275 | if args.is_fit: 276 | train_it, valid_it, num_classes = train_data(args.bcolz_root, bcolz_prod_info, sample_prod_info, 277 | train_split, 278 | category_idx, 279 | args.only_first_image, 280 | args.batch_size, args.shuffle, 281 | args.batch_seed) 282 | fit_model(train_it, valid_it, num_classes, args.models_dir, args.lr, args.batch_size, args.epochs, args.mode) 283 | elif args.is_predict: 284 | out_df = predict(args.bcolz_root, bcolz_prod_info, sample_prod_info, args.models_dir, args.only_first_image) 285 | out_df.to_csv(os.path.join(args.models_dir, PREDICTIONS_FILE), index=False) 286 | elif args.is_predict_valid: 287 | only_valids = bcolz_prod_info[bcolz_prod_info.product_id.isin(train_split[train_split.train == False].product_id)] 288 | out_df = predict(args.bcolz_root, bcolz_prod_info, only_valids, args.models_dir, args.only_first_image, 289 | shuffle=args.shuffle) 290 | out_df.to_csv(os.path.join(args.models_dir, VALID_PREDICTIONS_FILE), index=False) 291 | -------------------------------------------------------------------------------- /src/model/vgg16_vecs.py: -------------------------------------------------------------------------------- 1 | import pandas as pd 2 | import numpy as np 3 | import argparse 4 | import os 5 | import bcolz 6 | from tqdm import tqdm 7 | from .bson_iterator import BSONIterator 8 | from keras.applications.vgg16 import VGG16 9 | from keras.applications.vgg16 import preprocess_input 10 | from keras.models import Model 11 | from keras.layers import AveragePooling2D 12 | from keras.preprocessing.image import ImageDataGenerator 13 | import threading 14 | 15 | 16 | def compute_vgg16_vecs(bson_path, images_df, vecs_output_dir, save_step=100000): 17 | vgg_model = VGG16(include_top=False, input_shape=(3, 180, 180)) 18 | 19 | if os.path.isdir(vecs_output_dir): 20 | vecs = bcolz.open(rootdir=vecs_output_dir) 21 | offset = vecs.shape[0] 22 | else: 23 | vecs = None 24 | offset = 0 25 | 26 | lock = threading.Lock() 27 | 28 | with open(bson_path, "rb") as train_bson_file, \ 29 | tqdm(total=images_df.shape[0], initial=offset) as pbar: 30 | for i in range(offset, images_df.shape[0], save_step): 31 | gen = ImageDataGenerator(preprocessing_function=preprocess_input) 32 | batches = BSONIterator(bson_file=train_bson_file, 33 | images_df=images_df[i:(i + save_step)], 34 | num_class=0, # doesn't matter here 35 | image_data_generator=gen, 36 | lock=lock, 37 | target_size=(180, 180), 38 | batch_size=220, 39 | shuffle=False, 40 | with_labels=False) 41 | x = AveragePooling2D()(vgg_model.output) 42 | model = Model(vgg_model.input, x) 43 | out_vecs = model.predict_generator(batches, 44 | steps=batches.samples / batches.batch_size, 45 | verbose=1) 46 | if not vecs: 47 | vecs = bcolz.carray(out_vecs, rootdir=vecs_output_dir, mode='w') 48 | vecs.flush() 49 | else: 50 | vecs.append(out_vecs) 51 | vecs.flush() 52 | pbar.update(save_step) 53 | 54 | 55 | def create_images_df(product_info, only_first_image=False): 56 | rows = [] 57 | for row in product_info.itertuples(): 58 | for i in range(row.num_imgs): 59 | rows.append([row.product_id, i, row.offset, row.length]) 60 | 61 | images_df = pd.DataFrame(rows, columns=['product_id', 'img_idx', 'offset', 'length']) 62 | if only_first_image: 63 | images_df = images_df[images_df.img_idx == 0] 64 | images_df = images_df.reset_index(drop=True) 65 | return images_df 66 | 67 | 68 | if __name__ == '__main__': 69 | parser = argparse.ArgumentParser() 70 | parser.add_argument('--bson', required=True, help='Path to bson with products') 71 | parser.add_argument('--prod_info_csv', required=True, help='Path to prod info csv') 72 | parser.add_argument('--output_dir', required=True, help='Output directory for vectors') 73 | parser.add_argument('--save_step', type=int, required=True, help='Save computed vectors to disk each N steps') 74 | parser.add_argument('--only_first_image', dest='only_first_image', action='store_true', 75 | help="Include only first image from each product") 76 | parser.add_argument('--shuffle', type=int, default=None, required=False, 77 | help='If products should be shuffled, provide seed') 78 | parser.set_defaults(only_first_image=False) 79 | 80 | args = parser.parse_args() 81 | product_info = pd.read_csv(args.prod_info_csv) 82 | 83 | images_df = create_images_df(product_info, args.only_first_image) 84 | if args.shuffle: 85 | np.random.seed(args.shuffle) 86 | perm = np.random.permutation(images_df.shape[0]) 87 | images_df = images_df.reindex(perm) 88 | 89 | compute_vgg16_vecs(args.bson, images_df, args.output_dir, args.save_step) 90 | -------------------------------------------------------------------------------- /test_environment.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | REQUIRED_PYTHON = "python3" 4 | 5 | 6 | def main(): 7 | system_major = sys.version_info.major 8 | if REQUIRED_PYTHON == "python": 9 | required_major = 2 10 | elif REQUIRED_PYTHON == "python3": 11 | required_major = 3 12 | else: 13 | raise ValueError("Unrecognized python interpreter: {}".format( 14 | REQUIRED_PYTHON)) 15 | 16 | if system_major != required_major: 17 | raise TypeError( 18 | "This project requires Python {}. Found: Python {}".format( 19 | required_major, sys.version)) 20 | else: 21 | print(">>> Development environment passes all tests!") 22 | 23 | 24 | if __name__ == '__main__': 25 | main() 26 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 120 3 | max-complexity = 10 4 | --------------------------------------------------------------------------------