Reconociendo dígitos en fastai2

Como reconocer los dígitos de la competición https://www.kaggle.com/c/digit-recognizer
Published

May 26, 2020

Instalar fastai2

http://dev.fast.ai/#Installing yo prefiero instalarlo como dice ahí en su propio entorno de conda.

git clone https://github.com/fastai/fastai2
cd fastai2
pip install -e ".[dev]"

para que CUDA este disponible python -c 'import torch; print(torch.cuda.is_available())' si no lo esta checa que este instalado con nvidia-smi, si esta instalado puede que no se haya instalado la versión correcta de pytorch y los drivers instalados ve como hacerlo en https://pytorch.org/get-started/locally/

Registrandose en kaggle

  1. Registrarse como usuario en kaggle
  2. hacer pip install kaggle
  3. Obtener tu clave privada de usuario y guardarla en la ruta default
  4. Entrar en https://www.kaggle.com/c/digit-recognizer y dar click en aceptar reglas para poder usar la aplicación de kaggle.
  5. Bajar los archivos con kaggle competitions download -c digit-recognizer y descomprimirlos en su propia carpeta

Importar fastai2

Importamos el modulo vision de fastai2

from fastai2.vision.all import *

Vamos a definir algunas variables para poder ponerle nombre a nuestros archivos a enviar y guardar el nombre del modelo que usamos.

VERSION = 4
MODELO=resnet34
NOMBRE_MODELO="resnet34"
BASE_FILE = f"base-{NOMBRE_MODELO}_v{VERSION}"
SUBMIT_FILE = f"submit-{NOMBRE_MODELO}_v{VERSION}"

FINE_FILE = f"base-{NOMBRE_MODELO}-fine_v{VERSION}"
SUBMIT_FINE_FILE = f"submit-{NOMBRE_MODELO}-fine_v{VERSION}"

Fastai2 trabaja muy bien con data loaders, estos necesitan que exista algun archivo en disco, lo cual puede ayudar para datasets grandes, pero tal vez no mucho a su lectura recurrente. Sólo necesitamos preprocesar esto la primer vez, por lo mismo usamos esos ifs para extraerlos si es necesario desde cada row del csv hacia la imagen en disco.

import string
# in the same folder I have downloaded with: kaggle competitions download -c digit-recognizer
path = untar_data("file://digit-recognizer.zip", dest=".")
#print(path)
#print(path.ls())

from PIL import Image
from matplotlib import cm

import pandas as pd

path = Path("digit-recognizer")
df = pd.read_csv(path/"train.csv", header='infer')
df.head()
label pixel0 pixel1 pixel2 pixel3 pixel4 pixel5 pixel6 pixel7 pixel8 ... pixel774 pixel775 pixel776 pixel777 pixel778 pixel779 pixel780 pixel781 pixel782 pixel783
0 1 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
1 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
2 1 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
3 4 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0
4 0 0 0 0 0 0 0 0 0 0 ... 0 0 0 0 0 0 0 0 0 0

5 rows × 785 columns

%%time
def get_image(csv_row):
    img = csv_row.reshape(28,28)
    x = cm.gist_earth(img)*255
    return Image.fromarray(np.uint8(x))

def explode_train():
    df = pd.read_csv(path/'train.csv', header='infer')
    imagenes = df.iloc[:,1:].apply(lambda x: x.values, axis=1).values
    labels = df.iloc[:,:1].apply(lambda x: x.values[0], axis=1)
    p = Path(path)/'train'
    p.mkdir(parents=True, exist_ok=True)
        
    for idx, l in enumerate(labels):
        im = get_image(imagenes[idx])
        image_name = (str(l)+"_"+'train'+'_'+''.join(random.choice(string.ascii_lowercase) for i in range(8))) + ".png"
        im.save(p/image_name)

def explode_test():
    df = pd.read_csv(path/'test.csv', header='infer')
    imagenes = df.iloc[:,:].apply(lambda x: x.values, axis=1).values
    p = Path(path)/'test'
    p.mkdir(parents=True, exist_ok=True)
    for idx, l in enumerate(imagenes):
        im = get_image(imagenes[idx])
        im.save(p / f"{idx}.png")
if not (path/'train').exists():
    explode_train()
if not (path/'test').exists():
    explode_test()
CPU times: user 106 µs, sys: 13 µs, total: 119 µs
Wall time: 81.1 µs

Para leer todas las imagenes generadas, lo haciemos por medio de un DataBlock el cual es un blueprint de donde, como y que vamos a hacer con esas imagenes para obtener finalmente las imagenes en batches o grupos de imagenes (de 64 en 64 por default) y la carga de las imagenes como tal se hace por medio de data_loader = data_block.dataloaders(path) el cual en este caso también aprende el “vocabulario” a aprender ya que se ha especificado que el segundo bloque es un bloque de categorias a diferencia del primero que es de Imagenes.

get_my_labels obtiene a partir de el archivo de imagen leído el nombre y regresa el dígito al que pertenece del vocabulario.

%%time
def get_my_labels(fname):
    return int(fname.name[0])

dblock = DataBlock(
    splitter  = RandomSplitter(),
    item_tfms = Resize(224),
    blocks    = (ImageBlock, CategoryBlock),
    get_items = get_image_files,
    get_y     = get_my_labels
    )

dls = dblock.dataloaders("digit-recognizer/train")
dls.vocab
CPU times: user 4.12 s, sys: 549 ms, total: 4.67 s
Wall time: 4.81 s
(#10) [0,1,2,3,4,5,6,7,8,9]

show batch

Podemos ver que realmente este encontrando las imagenes mostrando un batch de imagenes.

dls.show_batch()

Cargar un modelo y entrenarlo

En fastai2 podemos cargar un modelo ya entrenado y reusarlo para ajustarlo a la tarea que queremos llevar a cabo.

learn = cnn_learner(dls, MODELO, metrics=error_rate)

Nuestro modelo en este momento ya tiene una estructura la cual se puede sumarizar así

learn.summary()
Sequential (Input shape: ['64 x 3 x 224 x 224'])
================================================================
Layer (type)         Output Shape         Param #    Trainable 
================================================================
Conv2d               64 x 64 x 112 x 112  9,408      False     
________________________________________________________________
BatchNorm2d          64 x 64 x 112 x 112  128        True      
________________________________________________________________
ReLU                 64 x 64 x 112 x 112  0          False     
________________________________________________________________
MaxPool2d            64 x 64 x 56 x 56    0          False     
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
ReLU                 64 x 64 x 56 x 56    0          False     
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
ReLU                 64 x 64 x 56 x 56    0          False     
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
ReLU                 64 x 64 x 56 x 56    0          False     
________________________________________________________________
Conv2d               64 x 64 x 56 x 56    36,864     False     
________________________________________________________________
BatchNorm2d          64 x 64 x 56 x 56    128        True      
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   73,728     False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
ReLU                 64 x 128 x 28 x 28   0          False     
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   8,192      False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
ReLU                 64 x 128 x 28 x 28   0          False     
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
ReLU                 64 x 128 x 28 x 28   0          False     
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
ReLU                 64 x 128 x 28 x 28   0          False     
________________________________________________________________
Conv2d               64 x 128 x 28 x 28   147,456    False     
________________________________________________________________
BatchNorm2d          64 x 128 x 28 x 28   256        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   294,912    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   32,768     False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
ReLU                 64 x 256 x 14 x 14   0          False     
________________________________________________________________
Conv2d               64 x 256 x 14 x 14   589,824    False     
________________________________________________________________
BatchNorm2d          64 x 256 x 14 x 14   512        True      
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     1,179,648  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
ReLU                 64 x 512 x 7 x 7     0          False     
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     2,359,296  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     131,072    False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     2,359,296  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
ReLU                 64 x 512 x 7 x 7     0          False     
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     2,359,296  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     2,359,296  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
ReLU                 64 x 512 x 7 x 7     0          False     
________________________________________________________________
Conv2d               64 x 512 x 7 x 7     2,359,296  False     
________________________________________________________________
BatchNorm2d          64 x 512 x 7 x 7     1,024      True      
________________________________________________________________
AdaptiveAvgPool2d    64 x 512 x 1 x 1     0          False     
________________________________________________________________
AdaptiveMaxPool2d    64 x 512 x 1 x 1     0          False     
________________________________________________________________
Flatten              64 x 1024            0          False     
________________________________________________________________
BatchNorm1d          64 x 1024            2,048      True      
________________________________________________________________
Dropout              64 x 1024            0          False     
________________________________________________________________
Linear               64 x 512             524,288    True      
________________________________________________________________
ReLU                 64 x 512             0          False     
________________________________________________________________
BatchNorm1d          64 x 512             1,024      True      
________________________________________________________________
Dropout              64 x 512             0          False     
________________________________________________________________
Linear               64 x 10              5,120      True      
________________________________________________________________

Total params: 21,817,152
Total trainable params: 549,504
Total non-trainable params: 21,267,648

Optimizer used: <function Adam at 0x7fb52353ab00>
Loss function: FlattenedLoss of CrossEntropyLoss()

Model frozen up to parameter group number 2

Callbacks:
  - TrainEvalCallback
  - Recorder
  - ProgressCallback

Podemos ver las predicciones actuales antes de reajustar los parametros de la red. En rojo se muestran los errores en verde los que estan bien. Los aciertos más bien son aleatorios.

Los resultados como se espera no pueden reconocer los patrones ya que esta inicializada para otro tipo de tarea.

learn.show_results()

Ahora podemos entrenarlo con fine_tune

El método fit sirve para ejecutar una serie de pasadas sobre los datos de entrenamiento.

%%time
learn.fine_tune(1)
epoch train_loss valid_loss error_rate time
0 0.227946 0.135213 0.040000 01:16
epoch train_loss valid_loss error_rate time
0 0.035625 0.025032 0.007024 01:43
CPU times: user 2min 15s, sys: 26.2 s, total: 2min 42s
Wall time: 3min
learn.show_results()

Nuestro modelo en este momento ya puede asertar con más confianza el dígito que se le esta pasando

learn.predict(Path("digit-recognizer")/'test'/"1111.png")
('2',
 tensor(2),
 tensor([8.4126e-10, 8.9289e-08, 1.0000e+00, 7.2959e-07, 2.4069e-09, 2.0494e-08,
         2.9449e-09, 1.0405e-07, 2.0946e-07, 1.2205e-10]))

Si volvemos a checar los resultados podemos ver que ciertamente ya puede reconocer los patrones.

Veamos cuales son las imagenes que más costaron reconocer.

interp = Interpretation.from_learner(learn)
interp.plot_top_losses(9, figsize=(15,10))

Para guardar los resultados obtenidos y mandarlos a kaggle, basta con hacer lo siguiente

def predict_test(the_learner, file_name):
    p = path/'test'
    print(f"predicting {len(p.ls())} in {p}")
    l = []
    for idx, img in enumerate(p.ls()):
        fname = p/f"{idx}.png"
        pred = the_learner.predict(fname)
        # la predicción contiene todo el resultado, la predicción esta en el elemento 0
        l.append( [idx+1, int(pred[0])] )
        if idx % 2800 == 0:
            print(f"{[idx, pred[0]]}...")
    df = pd.DataFrame(l)
    h = ["ImageId","label"]
    df.to_csv(f"{file_name}.csv", header=h, index=False) if True else print("** skipped save **")
    print("done!")

Removemos el callback del loader

%%time
learn.remove_cb(learn.cbs[2])
predict_test(learn, BASE_FILE)
predicting 28000 in digit-recognizer/test
[0, '2']...
[2800, '4']...
[5600, '8']...
[8400, '5']...
[11200, '4']...
[14000, '3']...
[16800, '1']...
[19600, '5']...
[22400, '9']...
[25200, '3']...
done!
CPU times: user 53min 39s, sys: 1h 36min 21s, total: 2h 30min
Wall time: 2h 49min 33s

El Learner que actualmente se encuentra en learn es muy bueno para quedar cerca de los primeros mil competidores, lo que tenemos que hacer ahora es mejorarlo poco a poco.

Pero antes de esto vamos a salvarlo de 2 formas:

  • exportandolo el cual requiere que despues se vuelva a cargar y crear otro cnn
  • salvar y cargar de manera directa

Guardamos el modelo actual para poder cargarlo despues via load.

learn.save(BASE_FILE) if True else print("No se a guardado el archivo")

Fine tunning

Ahora se puede cargar desde donde se quedo el paso guardado por save anterior, pero debe de contener datos a los cuales referirse, por eso se carga a travez de un modelo instanciado igual que antes, sólo que este ya esta “entrenado” a la tarea actual.

l2 = cnn_learner(dls, MODELO, metrics=error_rate)

Una vez instanciado igual que el modelo que guardamos, se puede cargar el mismo desde el archivo que se guardo con learn.save

l2.load(BASE_FILE)
<fastai2.learner.Learner at 0x7fb5202f10d0>

Buscamos un buen learning rate

l2.lr_find()
SuggestedLRs(lr_min=0.00043651582673192023, lr_steep=6.309573450380412e-07)

Descongelamos el modelo entrenado para que se pueda entrenar nuevamente y usamos el learning rate que encontramos anteriormente

%%time
l2.unfreeze()
l2.fine_tune(8, 1e-2) 
epoch train_loss valid_loss error_rate time
0 0.080212 0.057889 0.010238 01:10
epoch train_loss valid_loss error_rate time
0 0.074369 0.067521 0.013690 01:36
1 0.082539 0.081573 0.018690 01:36
2 0.058147 0.052700 0.015238 01:35
3 0.042055 0.044290 0.009048 01:34
4 0.029418 0.040308 0.009048 01:33
5 0.008509 0.022320 0.004286 01:34
6 0.003744 0.021935 0.004286 01:36
7 0.000918 0.023328 0.004524 01:35
CPU times: user 10min 46s, sys: 2min 52s, total: 13min 38s
Wall time: 13min 53s

Guardamos el nuevo modelo ajustado

l2.save(FINE_FILE)

Y lo usamos para predecir eliminando también el callback del progress bar

l2.cbs, l2.cbs[2]
((#3) [TrainEvalCallback,Recorder,ProgressCallback], ProgressCallback)
%%time
l2.remove_cb(l2.cbs[2])
predict_test(l2, FINE_FILE)
predicting 28000 in digit-recognizer/test
[0, '2']...
[2800, '4']...
[5600, '8']...
[8400, '5']...
[11200, '4']...
[14000, '3']...
[16800, '1']...
[19600, '5']...
[22400, '9']...
[25200, '3']...
done!
CPU times: user 39min 42s, sys: 1h 35min 30s, total: 2h 15min 13s
Wall time: 2h 36min 45s
l2.validate()
(#2) [0.023327725008130074,0.0045238095335662365]

Exportar a producción

Si no queremos seguir reentrenando nuestro trabajo, podemos sólo exportar el modelo para que se use en predicciones al cargarlo.

learn.export(BASE_FILE) if False else print("no se ha exportado")
no se ha exportado
l2.export(FINE_FILE) if False else print("no se ha exportado nada")
no se ha exportado nada