Olá, daqui é o Alex da DEV STORIES DOT EU. scikit-image: O AI Image Pipeline, episódio 1 de 5. Antes que um modelo de deep learning consiga reconhecer uma cara, tem de digerir uma grelha de números. Se alimentares essa grelha na ordem errada, o teu modelo não aprende nada e o teu processamento fica extremamente lento. O AI Image Pipeline:
NumPy at the Core é a solução para estruturar essa grelha corretamente.
Quando carregas uma imagem usando o scikit-image, não recebes um objeto de imagem proprietário. Recebes um array N-dimensional standard, conhecido como ndarray. Uma imagem é simplesmente uma matriz de pixels. Como é um simples array NumPy, podes usar qualquer operação standard para fazer slice, mask, ou manipular os dados da imagem diretamente.
Aqui está o ponto chave. A forma como o scikit-image indexa estes arrays confunde muitos developers. Na geometria standard, usas coordenadas x e y, onde o x é horizontal e o y é vertical. O scikit-image abandona isto em favor da notação matricial. O primeiro index é a row, que corresponde à posição vertical. O segundo index é a column, que corresponde à posição horizontal. Se tiveres uma imagem a cores, o terceiro index é o channel de cor. Uma imagem a cores bidimensional standard é, na verdade, um array tridimensional ordenado como row, column, channel.
Supõe que estás a preparar um batch de imagens a cores para alimentar uma Convolutional Neural Network. A tua rede espera um shape específico. Se a tua imagem tiver 256 pixels de altura e 256 pixels de largura, com channels vermelho, verde e azul, o shape de uma única imagem é 256, 256, 3. Mas um batch destas imagens adiciona uma quarta dimensão no início, que representa o número de imagens.
Quando aplicas funções do scikit-image a estes dados, a função precisa de saber qual é a dimensão que contém os valores de cor, para não os processar como dados espaciais. Isto é tratado pelo argumento channel axis. Ao definires o argumento channel axis para menos um, dizes à função que os channels de cor estão sempre na última dimensão do array. Isto garante que a função utiliza os dados de cor corretos, independentemente de lhe passares uma única imagem ou um grande batch com dimensões espaciais extra.
Isto leva-nos à memória. Esta é a parte que importa para a velocidade de processamento. Os arrays NumPy são armazenados em blocos contíguos de memória, usando uma ordem tipo C por default. Isto significa que a última dimensão no shape do array é a que muda mais rapidamente na memória física. Para o nosso array de imagem standard de row, column, channel, os channels de cor individuais de um único pixel ficam lado a lado na tua RAM de hardware.
Se escreveres código custom para fazer um loop sobre estes pixels, tens de respeitar este memory layout. A regra é absoluta: a dimensão mais à direita do teu array deve ser processada no loop mais interno. Iteras sobre as rows no loop exterior, depois as columns, e depois os channels no interior. Se inverteres isto e fizeres o loop sobre as rows no interior, forças o CPU a saltar de um lado para o outro por endereços de memória fragmentados. Isto destrói a cache locality e faz com que o teu código corra significativamente mais devagar.
A coisa mais crítica a lembrar é que o scikit-image não usa coordenadas horizontais e verticais; usa uma indexação matricial estrita de row, column, e channel, e alinhar os teus loops com essa ordem exata de memória é o que mantém o teu data pipeline rápido.
Se quiseres ajudar a manter este podcast no ar, podes apoiar o programa procurando por DevStoriesEU no Patreon. É tudo por este episódio. Obrigado por ouvires, e continua a programar!