# 130th solution - 92.0 private LB

Congratulation to all winners and thanks for great competition. This would be my first medal, so I decided to write up our approach.
Main problems to solve:
- inbalanced classes
- not enough data
- different distribution of the train and public test data set,
Data augmentation:
I think that one of the best way to improve score on public set was strong augmentation. So, to augment we differentiated two types of the images - first where image contains whole eyeball, and second where image contains cropped eyeball. In first case to augment we cropped both – rows and columns and in the second case we cropped only rows. Cropping was done randomly from 0 to 25% of the size of the eyeball in each side. After that we rotated image from 0 to 360 degrees. However to avoid cropping during rotation we used this function from [
[1](https://stackoverflow.com/questions/11764575/python-2-7-3-opencv-2-4-after-rotation-window-doesnt-fit-image/33247373#33247373)
]. I believed that this also helped to train model in different scales. We also used some pixel-wise augmentation - like addition, multiplications and so on. Then we used the same as Ben’s processing. During validation we got rid of black area in image. Picture below shows: original data, training data and validation/test.
![](assets/b8ea12b337b1ebda16f471f3.png)
Model
Basically we used the same augmentation and settings to pretrain model on data from 2015. We trained model with weights computed from sklearn.utils.class_weight. As a loss function we used slightly modified categorical_crossentropy loss to penalize more distant predictions (from [
[2](https://github.com/JHart96/keras_ordinal_categorical_crossentropy/blob/master/ordinal_categorical_crossentropy.py)
]) and this was our checkpoint metric. I also tried to use as a checkpoint metric accuracy, f1, or kappa but it scored worse in public data set. We used Adam optimizer with start learning rate 8e-5 and then learning rate was scheduled as follow.
def scheduler(epoch,learning_rate):
    lr = learning_rate
    if epoch==3:
        lr = 3e-5
    if epoch==6:
        lr = 1e-5
    if epoch==9:
        lr = 8e-6
    if epoch==12:
        lr = 4e-6
    return lr
Validation was done on 10% data. For single model the validation kappa score was about 0.87 (without TTA)
Prediction Time
During prediction we used simple cropping image to contain only eyeball and 7 TTA, each of them was only rotation (without cropping) by additional 45 degrees (0, 45, 90, and so on). Doing that allowed to improve score for EfficientNet-B5 from 80.4 (without TTA) to 81.8 on public test. Some people wrote that it is useless, but I really think it really depends on how you trained your network and how it is done TTA itself.
Ensembling
As a final submission we used soft-voting on 7 TTA on EfficientNet-B5 (image size 456) and 7 TTA on EfficientNet-B4 (image size 380). It scored 81.6 on public set and 92.0 on private set.
We believed that our solution was stable (after previous competition I tried to avoid my fallacy in relying only on public score), because improvement on validation fold was scored better on public LB, slight changes in settings did not changed public LB score, and we used strong augmentation. Any comment what could we do in different manner are welcomed. Thank you, and I'm starting to dive into fascinated readings of top solutions.
[1]
[https://stackoverflow.com/questions/11764575/python-2-7-3-opencv-2-4-after-rotation-window-doesnt-fit-image/33247373#33247373](https://stackoverflow.com/questions/11764575/python-2-7-3-opencv-2-4-after-rotation-window-doesnt-fit-image/33247373#33247373)
[2]
[https://github.com/JHart96/keras_ordinal_categorical_crossentropy/blob/master/ordinal_categorical_crossentropy.py](https://github.com/JHart96/keras_ordinal_categorical_crossentropy/blob/master/ordinal_categorical_crossentropy.py)