A tarefa de reconhecimento de dígitos escritos a mão foi um dos primeiro grandes sucessos dos métodos de aprendizado de máquina. Hoje em dia, a tarefa pode ser realizada por diversas bibliotecas especializadas com altíssima acurácia (> 97% de acertos), tal que muitas vezes, apesar de utilizarmos indiretamente esses recursos em tablets e smartphones, em geral não sabemos exatamente como o método funciona.
Pensando nisso, como já trabalhei com esse problema antes, vou demonstrar nesse post como o processo funciona, as técnicas utilizadas e como implementar tudo na linguagem R. Para começar, vamos trabalhar com o problema de reconhecer se o dígito é 0,1,2,3,4,5,6,7,8,ou 9, isto é, um problema de classificação com 10 categorias.
Vou tentar trabalhar aqui implementando toda a modelagem somente com as funções do pacote base e uns poucos pacotes extras com as funções e algoritmos necessários; em um próximo post, posso tentar utilizar outros pacotes para automatizar as diversas etapas da modelagem.
1. LEITURA
Os dados do problema são imagens do tipo PGM, com 64 x 64 pixels por imagem, onde cada pixel tem valor 1 ou 0, indicando se o pixel é preto ou branco. Cada imagem tem um nome no formato X_ yyy.BMP.inv.pgm, onde o X representa o dígito desenhado na imagem. Os dados estão divididos em um conjunto de treino e um conjunto de teste e podem ser baixados nos seguintes links: teste e treino
Assim a primeira parte do problema é efetuar a leitura dos dados. Para isso me utilizarei do pacote pixmap com o qual é possível ler e manipular imagens PGM. A seguir, o processo de leitura das imagens e a criação de um vetor com as respostas, isto é, com o número do dígito que está escrito na imagem.
## Carregando o pacote pixmap library(pixmap) ## Definindo o diretório com as imagens de treino path_treino <- '/sua/pasta/treino/' ## Define como diretório de trabalho setwd(path_treino) ## Lê os nomes dos arquivos files <- dir() ## Retira as classes dos nomes dos arquivos classes <- as.factor(substring(files,first=1,last=1)) ## Cria data.frame para armazenar dados de treino treino <- as.data.frame(matrix(rep(0,length(files)*64*64), nrow=length(files))) ## Efetua leitura dos dados de treino for (i in 1:length(files)) { ## Lendo as imagens x <- read.pnm(files[i]) ## No slot 'grey' está a matriz de pixels que é retirada e vetorizada treino[i,] <- as.vector(x@grey, mode='integer') } ## Define como diretório de trabalho o local das imagens para teste path_teste <- '/sua/pasta/teste/' ## Diretório de trabalho setwd(path_teste) ## Lê os nomes dos arquivos files <- dir() ## Classes predic <- as.factor(substring(files,first=1,last=1)) ## Cria data.frame para armazenar conjunto de teste teste <- as.data.frame(matrix(rep(0,length(files)*64*64), nrow=length(files))) ## Leitura do conjunto de teste for (i in 1:length(files)) { x <- read.pnm(files[i]) teste[i,] <- as.vector(x@grey, mode='integer') }
É importante observar que a matriz de pixels fica armazenada no slot @grey, e que após a leitura é transformada em um vetor, tal que o data.frame final fica com 64×64 colunas e 1949 linhas (o total de imagens). O conjunto de teste tem somente 50 imagens, logo o data.frame vai ficar com 64×64 colunas e somente 50 linhas. Em suma, cada coluna é um píxel e cada linha uma das diferentes imagens.
2. MODELAGEM COM k-nn
Nessa etapa será realizado o aprendizado com o algoritmo k-nn (vizinhos mais próximos) sem nenhum tratamento dos dados. O algoritmo funciona atribuindo as classes às imagens, utilizando os valores conhecidos dos vizinhos mais próximos. Assim, digamos que k=3, o algoritmo busca as três imagens mais próximas, verifica qual é a classe majoritária dessas imagens e atribui essa classe à imagem sem label. É importante escolher um k ímpar para não ocorrer empates, por exemplo 2 vizinhos de uma classe e 2 de outra no caso de k=4.
## Carrega pacote class com o k-nn library(class) ## Utilizando o k-nn para previsão do dígitos nas imagens de teste predito <- knn(train=treino, test=teste, cl=classes, k=3, prob=T) ## Resultado result <- data.frame(cbind(predic, predito, acerto = predic==predito)) ## Cálculo da taxa de acerto sum(result$acerto)/nrow(result) [1] 0.56
E com um k=3 obtivemos uma taxa de acerto de somente 56%, muito aquém do que pode ser obtido. Assim, vamos rodar o algoritmo com diversos valores de k e verificar se conseguimos obter um resultado um pouco melhor.
## Data.frame com todos os resultados resultado <- data.frame(k = rep(0,101), taxa=rep(0.00,101)) for (i in seq(from=1, to=101, by=2)) { ## Imprime o valor de k para controle print(i) ## Obtém os valores preditos para as imagens predito <- knn(train=treino, test=teste, cl=classes, k=i, prob=T) ## Salva em um data.frame result <- data.frame(cbind(predic, predito, acerto = predic==predito)) ## Calcula a taxa de acerto e armazena no data.frame resultado[i,] <- c(i,sum(result$acerto)/nrow(result)) } ## Elimina linhas com 0 resultado <- subset(resultado, subset=resultado$taxa!=0) ## Plota o resultado para todos os k's plot(resultado$taxa~resultado$k, main='Taxa de Acerto para o k-nn', xlab='Valores de K', ylab='Taxa de acerto')
Obtendo o seguinte resultado:
Vejam que obtivemos algo em torno de 78% com k=1, mas que ainda é um resultado muito ruim perto do que pode ser alcançado. Também vale notar que aumentar o k não ajuda muito no fim das contas, mas é importante ficar atento pois um k muito pequeno pode levar ao superajustamento ou overfitting.
CONCLUSÃO PARCIAL
Ao que tudo indica a classificação de imagens funciona bem utilizando um algoritmo simples, sem nenhum tipo de tratamento. ENTRETANTO, é possível fazer muito melhor. Na Parte 2 vamos automatizar algumas tarefas com pacote caret e também vamos explorar outros algoritmos melhores como o SVM e o RandomForest.
[…] OBS: There is a version in portuguese. […]
[…] Reconhecimento de dígitos escritos a mão – Parte 1 Nenhum Comentário | dez 22, 2014 […]
[…] Parte 1 desse post (que já publiquei faz um tempão!) eu fiz uma classificação de imagens de dígitos […]