Tutorial: Inspeção visual automatizada usando a transferência de aprendizado com a API de Classificação de Imagem do ML.NET
Saiba como treinar um modelo de aprendizado profundo personalizado usando a transferência de aprendizado, um modelo TensorFlow pré-treinado e a API de classificação de imagem do ML.NET para classificar imagens de superfícies concretas como rachadas ou não rachadas.
Neste tutorial, você aprenderá a:
- Compreender o problema
- Saber mais sobre a API de Classificação de Imagem do ML.NET
- Entender o modelo pré-treinado
- Usar a transferência de aprendizado para treinar um modelo de classificação de imagem do TensorFlow personalizado
- Classificar imagens com o modelo personalizado
Pré-requisitos
Visão geral do exemplo de transferência de aprendizado de classificação de imagem
Este exemplo é um aplicativo de console do .NET Core em C# que classifica imagens usando um modelo do TensorFlow de aprendizado profundo pré-treinado. O código para este exemplo pode ser encontrado no navegador de exemplos.
Compreender o problema
A classificação de imagem é um problema de pesquisa visual computacional. A classificação de imagem usa uma imagem como entrada e a categoriza em uma classe prescrita. Os modelos de classificação de imagem normalmente são treinados usando redes neurais e de aprendizado profundo. Confira Aprendizado profundo versus machine learning para mais informações.
Alguns cenários em que a classificação de imagem é útil incluem:
- Reconhecimento de rosto
- Detecção de Emoções
- Diagnóstico médico
- Detecção de monumentos
Este tutorial treina um modelo de classificação de imagem personalizado para executar a inspeção visual automatizada de decks de ponte para identificar estruturas danificadas por rachaduras.
API de Classificação de Imagem do ML.NET
O ML.NET fornece várias maneiras de executar a classificação de imagem. Este tutorial aplica a transferência de aprendizado usando a API de Classificação de Imagem. A API de Classificação de Imagem usa TensorFlow.NET, uma biblioteca de baixo nível que fornece associações C# para a API do TensorFlow C++.
O que é a transferência de aprendizado?
Transferência de aprendizado aplica o conhecimento obtido com a resolução de um problema a outro problema relacionado.
Treinar um modelo de aprendizado profundo do zero requer a configuração de vários parâmetros, uma grande quantidade de dados de treinamento rotulados e uma grande quantidade de recursos de computação (centenas de horas de GPU). O uso de um modelo pré-treinado com a transferência de aprendizado permite que você siga um atalho para o processo de treinamento.
Processo de treinamento
A API de Classificação de Imagem inicia o processo de treinamento carregando um modelo pré-treinado do TensorFlow. O processo de treinamento consiste em duas etapas:
- Fase de gargalo
- Fase de treinamento
Fase de gargalo
Durante a fase de gargalo, o conjunto de imagens de treinamento é carregado e os valores de pixel são usados como entrada ou recursos para as camadas congeladas do modelo pré-treinado. As camadas congeladas incluem todas as camadas na rede neural até a penúltima camada, informalmente conhecida como camada de gargalo. Essas camadas são chamadas de congeladas porque nenhum treinamento ocorrerá nelas camadas e as operações são de passagem. É nessas camadas congeladas em que os padrões de nível inferior que ajudam um modelo a distinguir as diferentes classes são calculados. Quanto maior o número de camadas, maior será a utilização computacional dessa etapa. Felizmente, como esse é um cálculo único, os resultados podem ser armazenados em cache e usados em execuções posteriores ao experimentar parâmetros diferentes.
Fase de treinamento
Depois que os valores de saída da fase de gargalo são calculados, eles são usados como entrada para treinar novamente a camada final do modelo. Esse processo é iterativo e é executado pelo número de vezes especificado por parâmetros de modelo. Durante cada execução, a perda e a precisão são avaliadas. Em seguida, os ajustes apropriados são feitos para melhorar o modelo com a meta de minimizar a perda e maximizar a precisão. Depois que o treinamento for concluído, dois formatos de modelo serão gerados. Um deles é a versão .pb
do modelo e o outro é a versão serializada .zip
do ML.NET do modelo. Ao trabalhar em ambientes compatíveis com ML.NET, é recomendável usar a versão .zip
do modelo. No entanto, em ambientes em que não há suporte para ML.NET, você tem a opção de usar a versão .pb
.
Entender o modelo pré-treinado
O modelo pré-treinado usado neste tutorial é a variante de 101 camadas do modelo ResNet (Rede Residual) v2. O modelo original é treinado para classificar imagens em mil categorias. O modelo usa como entrada uma imagem do tamanho 224 x 224 e gera as probabilidades de classe para cada uma das classes em que é treinado. Parte desse modelo é usada para treinar um novo modelo usando imagens personalizadas para fazer previsões entre duas classes.
Criar um aplicativo de console
Agora que você tem uma compreensão geral da transferência de aprendizado e da API de Classificação de Imagem, é hora de compilar o aplicativo.
Crie um Aplicativo de Console C# chamado "DeepLearning_ImageClassification_Binary". Clique no botão Avançar.
Escolha o .NET 6 como a estrutura a ser usada. Selecione o botão Criar.
Instale o pacote NuGet Microsoft.ML:
Observação
Este exemplo usa a versão estável mais recente dos pacotes NuGet mencionados, salvo indicação em contrário.
- No Gerenciador de Soluções, clique com o botão direito do mouse no seu projeto e selecione Gerenciar Pacotes NuGet.
- Escolha "nuget.org" como a fonte do pacote.
- Selecione a guia Procurar.
- Marque a caixa de seleção Incluir pré-lançamento.
- Pesquise Microsoft.ML.
- Selecione o botão Instalar.
- Selecione o botão OK na caixa de diálogo Visualizar Alterações e selecione o botão Aceito na caixa de diálogo Aceitação da Licença, se concordar com o termos de licença para os pacotes listados.
- Repita essas etapas para os pacotes NuGet Microsoft.ML.Vision, SciSharp.TensorFlow.Redist versão 2.3.1 e Microsoft.ML.ImageAnalytics.
Preparar e compreender os dados
Observação
Os conjuntos de dados deste tutorial são de Maguire, Marc; Dorafshan, Sattar; e Thomas, Robert J., "SDNET2018: A concrete crack image dataset for machine learning applications" (2018). Navegue por todos os conjuntos de dados. Papel 48. https://digitalcommons.usu.edu/all_datasets/48
SDNET2018 é um conjunto de dados de imagem que contém anotações para estruturas de concreto rachadas e não rachadas (decks de ponte, paredes e pavimento).
Os dados são organizados em três subdiretórios:
- D contém imagens do baralho de ponte
- P contém imagens de pavimento
- W contém imagens de parede
Cada um desses subdiretórios contém dois subdiretórios prefixados adicionais:
- C é o prefixo usado para superfícies rachadas
- U é o prefixo usado para superfícies não rachadas
Neste tutorial, somente imagens do deck de ponte são usadas.
- Baixe o conjunto de dados e descompacte-o.
- Crie um diretório chamado "ativos" em seu projeto para salvar seus arquivos de conjunto de dados.
- Copie os subdiretórios de CD e UD do diretório descompactado recentemente para o diretório de ativos.
Criar classes de entrada e saída
Abra o arquivo Program.cs e substitua as instruções
using
na parte superior do arquivo pelo seguinte:using System; using System.Collections.Generic; using System.Linq; using System.IO; using Microsoft.ML; using static Microsoft.ML.DataOperationsCatalog; using Microsoft.ML.Vision;
Abaixo da classe
Program
em Program.cs, crie uma classe chamadaImageData
. Essa classe é usada para representar os dados carregados inicialmente.class ImageData { public string ImagePath { get; set; } public string Label { get; set; } }
ImageData
contém as propriedades a seguir:ImagePath
é o caminho totalmente qualificado em que a imagem é armazenada.Label
é a categoria à qual a imagem pertence. Esse é o valor a ser previsto.
Crie classes para os dados de entrada e saída
Abaixo da classe
ImageData
, defina o esquema dos dados de entrada em uma nova classe chamadaModelInput
.class ModelInput { public byte[] Image { get; set; } public UInt32 LabelAsKey { get; set; } public string ImagePath { get; set; } public string Label { get; set; } }
ModelInput
contém as propriedades a seguir:Image
é a representaçãobyte[]
da imagem. O modelo espera que os dados de imagem sejam desse tipo para treinamento.LabelAsKey
é a representação numérica doLabel
.ImagePath
é o caminho totalmente qualificado em que a imagem é armazenada.Label
é a categoria à qual a imagem pertence. Esse é o valor a ser previsto.
Somente
Image
eLabelAsKey
são usados para treinar o modelo e fazer previsões. As propriedadesImagePath
eLabel
são mantidas por conveniência para acessar o nome e a categoria do arquivo de imagem original.Em seguida, abaixo da classe
ModelInput
, defina o esquema dos dados de saída em uma nova classe chamadaModelOutput
.class ModelOutput { public string ImagePath { get; set; } public string Label { get; set; } public string PredictedLabel { get; set; } }
ModelOutput
contém as propriedades a seguir:ImagePath
é o caminho totalmente qualificado em que a imagem é armazenada.Label
é a categoria original à qual a imagem pertence. Esse é o valor a ser previsto.PredictedLabel
é o valor previsto pelo modelo.
Semelhante a
ModelInput
, somentePredictedLabel
é necessário para fazer previsões, pois ele contém a previsão feita pelo modelo. As propriedadesImagePath
eLabel
são retidas por conveniência para acessar o nome e a categoria do arquivo de imagem original.
Criar diretório de workspace
Quando os dados de treinamento e validação não são alterados com frequência, é uma boa prática armazenar em cache os valores de gargalo computados para execuções adicionais.
- Em seu projeto, crie um diretório chamado workspace para armazenar os valores de gargalo computados e a versão
.pb
do modelo.
Definir caminhos e inicializar variáveis
Nas instruções de uso, defina a localização de seus ativos, valores de gargalo computados e a versão
.pb
do modelo.var projectDirectory = Path.GetFullPath(Path.Combine(AppContext.BaseDirectory, "../../../")); var workspaceRelativePath = Path.Combine(projectDirectory, "workspace"); var assetsRelativePath = Path.Combine(projectDirectory, "assets");
Inicialize a variável
mlContext
com uma nova instância de MLContext.MLContext mlContext = new MLContext();
A classe MLContext é um ponto de partida para todas as operações do ML.NET, e a inicialização de mlContext cria um novo ambiente do ML.NET que pode ser compartilhado entre os objetos do fluxo de trabalho de criação de modelo. Ele é semelhante, conceitualmente, a
DbContext
no Entity Framework.
Carregar os dados
Criar método utilitário de carregamento de dados
As imagens são armazenadas em dois subdiretórios. Antes de carregar os dados, ele precisa ser formatado em uma lista de objetos ImageData
. Para fazer isso, crie o método LoadImagesFromDirectory
.
IEnumerable<ImageData> LoadImagesFromDirectory(string folder, bool useFolderNameAsLabel = true)
{
}
Dentro do
LoadImagesFromDirectory
, adicione o seguinte código para obter todos os caminhos de arquivo dos subdiretórios:var files = Directory.GetFiles(folder, "*", searchOption: SearchOption.AllDirectories);
Em seguida, itere em cada um dos arquivos usando uma instrução
foreach
.foreach (var file in files) { }
Dentro da instrução
foreach
, verifique se há suporte para as extensões de arquivo. A API de Classificação de Imagem dá suporte a formatos JPEG e PNG.if ((Path.GetExtension(file) != ".jpg") && (Path.GetExtension(file) != ".png")) continue;
Em seguida, obtenha o rótulo do arquivo. Se o parâmetro
useFolderNameAsLabel
estiver definido comotrue
, o diretório pai em que o arquivo é salvo será usado como o rótulo. Caso contrário, ele esperará que o rótulo seja um prefixo do nome do arquivo ou do próprio nome do arquivo.var label = Path.GetFileName(file); if (useFolderNameAsLabel) label = Directory.GetParent(file).Name; else { for (int index = 0; index < label.Length; index++) { if (!char.IsLetter(label[index])) { label = label.Substring(0, index); break; } } }
Por fim, crie uma instância de
ModelInput
.yield return new ImageData() { ImagePath = file, Label = label };
Preparar os dados
Chame o método utilitário
LoadImagesFromDirectory
para obter a lista de imagens usadas para treinamento depois de inicializar a variávelmlContext
.IEnumerable<ImageData> images = LoadImagesFromDirectory(folder: assetsRelativePath, useFolderNameAsLabel: true);
Em seguida, carregue as imagens para um método
IDataView
usandoLoadFromEnumerable
.IDataView imageData = mlContext.Data.LoadFromEnumerable(images);
Os dados são carregados na ordem em que foram lidos dos diretórios. Para equilibrar os dados, embaralhe-os usando o método
ShuffleRows
.IDataView shuffledData = mlContext.Data.ShuffleRows(imageData);
Os modelos de machine learning esperam que a entrada esteja em formato numérico. Portanto, alguns pré-processamentos precisam ser feitos nos dados antes do treinamento. Crie uma
EstimatorChain
composta pelas transformaçõesMapValueToKey
eLoadRawImageBytes
. A transformaçãoMapValueToKey
usa o valor categórico na colunaLabel
, converte-o em um valor numéricoKeyType
e armazena-o em uma nova coluna chamadaLabelAsKey
. OLoadImages
usa os valores da colunaImagePath
com o parâmetroimageFolder
para carregar imagens para treinamento.var preprocessingPipeline = mlContext.Transforms.Conversion.MapValueToKey( inputColumnName: "Label", outputColumnName: "LabelAsKey") .Append(mlContext.Transforms.LoadRawImageBytes( outputColumnName: "Image", imageFolder: assetsRelativePath, inputColumnName: "ImagePath"));
Use o método
Fit
para aplicar os dados aopreprocessingPipeline
EstimatorChain
seguido pelo métodoTransform
, que retorna umIDataView
que contém os dados pré-processados.IDataView preProcessedData = preprocessingPipeline .Fit(shuffledData) .Transform(shuffledData);
Para treinar um modelo, é importante ter um conjunto de dados de treinamento, bem como um conjunto de dados de validação. O modelo é treinado no conjunto de treinamento. A qualidade das previsões em dados invisíveis é medida pelo desempenho em relação ao conjunto de validação. Com base nos resultados desse desempenho, o modelo faz ajustes no que aprendeu em um esforço para melhorar. O conjunto de validação pode vir da divisão do conjunto de dados original ou de outra fonte que já foi reservada para essa finalidade. Nesse caso, o conjunto de dados pré-processado é dividido em conjuntos de treinamento, validação e teste.
TrainTestData trainSplit = mlContext.Data.TrainTestSplit(data: preProcessedData, testFraction: 0.3); TrainTestData validationTestSplit = mlContext.Data.TrainTestSplit(trainSplit.TestSet);
O exemplo de código acima executa duas divisões. Primeiro, os dados pré-processados são divididos e 70% são usados para treinamento, enquanto os 30% restantes são usados para validação. Em seguida, o conjunto de validação de 30% é dividido em conjuntos de validação e teste em que 90% são usados para validação e 10% são usados para teste.
Um modo de pensar sobre a finalidade dessas partições de dados é fazer um exame. Ao estudar para um exame, você analisa suas anotações, livros ou outros recursos para entender os conceitos que estão no exame. É para isso que serve o conjunto de trens. Então, você pode fazer um exame simulado para validar seu conhecimento. É aí que o conjunto de validação é útil. Você deseja verificar se você tem uma boa compreensão dos conceitos antes de fazer o exame real. Com base nesses resultados, você anota o que errou ou não entendeu bem e incorporou suas alterações à medida que analisa para o exame real. Por fim, faça o exame. É para isso que o conjunto de testes é usado. Você nunca viu as perguntas que estão no exame e agora usa o que aprendeu com o treinamento e a validação para aplicar seu conhecimento à tarefa em questão.
Atribua às partições seus respectivos valores para os dados de treinamento, validação e teste.
IDataView trainSet = trainSplit.TrainSet; IDataView validationSet = validationTestSplit.TrainSet; IDataView testSet = validationTestSplit.TestSet;
Definir o pipeline de treinamento
O treinamento de modelo consiste em algumas etapas. Primeiro, a API de Classificação de Imagem é usada para treinar o modelo. Em seguida, os rótulos codificados na coluna PredictedLabel
são convertidos novamente em seu valor categórico original usando a transformação MapKeyToValue
.
Crie uma variável para armazenar um conjunto de parâmetros obrigatórios e opcionais para um ImageClassificationTrainer.
var classifierOptions = new ImageClassificationTrainer.Options() { FeatureColumnName = "Image", LabelColumnName = "LabelAsKey", ValidationSet = validationSet, Arch = ImageClassificationTrainer.Architecture.ResnetV2101, MetricsCallback = (metrics) => Console.WriteLine(metrics), TestOnTrainSet = false, ReuseTrainSetBottleneckCachedValues = true, ReuseValidationSetBottleneckCachedValues = true };
Um ImageClassificationTrainer usa vários parâmetros opcionais:
FeatureColumnName
é a coluna usada como entrada para o modelo.LabelColumnName
é a coluna para o valor a ser previsto.ValidationSet
é oIDataView
que contém os dados de validação.Arch
define qual das arquiteturas de modelo pré-treinadas usar. Este tutorial usa a variante de 101 camadas do modelo ResNetv2.MetricsCallback
associa uma função para acompanhar o progresso durante o treinamento.TestOnTrainSet
diz para o modelo medir o desempenho em relação ao conjunto de treinamento quando nenhum conjunto de validação está presente.ReuseTrainSetBottleneckCachedValues
diz para o modelo se os valores armazenados em cache devem ser usados na fase de gargalo em execuções subsequentes. A fase de gargalo é uma computação de passagem única que é computacionalmente intensiva na primeira vez que é executada. Se os dados de treinamento não forem alterados e você quiser experimentar usando um número diferente de épocas ou tamanho do lote, usar os valores armazenados em cache reduzirá significativamente o tempo necessário para treinar um modelo.ReuseValidationSetBottleneckCachedValues
é semelhante aReuseTrainSetBottleneckCachedValues
, porém, nesse caso, é para o conjunto de dados de validação.WorkspacePath
define o diretório em que armazenar os valores de gargalo computados e a versão.pb
do modelo.
Defina o pipeline
EstimatorChain
de treinamento que consiste emmapLabelEstimator
e em ImageClassificationTrainer.var trainingPipeline = mlContext.MulticlassClassification.Trainers.ImageClassification(classifierOptions) .Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabel"));
Use o método
Fit
para treinar seu modelo.ITransformer trainedModel = trainingPipeline.Fit(trainSet);
Usar o modelo
Agora que você treinou seu modelo, é hora de usá-lo para classificar imagens.
Crie um método utilitário chamado OutputPrediction
para exibir informações de previsão no console.
private static void OutputPrediction(ModelOutput prediction)
{
string imageName = Path.GetFileName(prediction.ImagePath);
Console.WriteLine($"Image: {imageName} | Actual Value: {prediction.Label} | Predicted Value: {prediction.PredictedLabel}");
}
Classifique uma só imagem
Crie um método chamado
ClassifySingleImage
para fazer e gerar uma só previsão de imagem.void ClassifySingleImage(MLContext mlContext, IDataView data, ITransformer trainedModel) { }
Crie um
PredictionEngine
dentro do métodoClassifySingleImage
. OPredictionEngine
é uma API de conveniência, que permite passar uma única instância de dados e, em seguida, executar uma previsão nessa única instância de dados.PredictionEngine<ModelInput, ModelOutput> predictionEngine = mlContext.Model.CreatePredictionEngine<ModelInput, ModelOutput>(trainedModel);
Para acessar uma só instância de
ModelInput
, convertadata
IDataView
em umIEnumerable
usando o métodoCreateEnumerable
e obtenha a primeira observação.ModelInput image = mlContext.Data.CreateEnumerable<ModelInput>(data,reuseRowObject:true).First();
Use o método
Predict
para classificar a imagem.ModelOutput prediction = predictionEngine.Predict(image);
Gere a previsão para o console com o método
OutputPrediction
.Console.WriteLine("Classifying single image"); OutputPrediction(prediction);
Chame
ClassifySingleImage
abaixo chamando o métodoFit
usando o conjunto de testes de imagens.ClassifySingleImage(mlContext, testSet, trainedModel);
Classificar várias imagens
Adicione um método chamado
ClassifyImages
abaixo do métodoClassifySingleImage
para fazer e gerar várias previsões de imagem.void ClassifyImages(MLContext mlContext, IDataView data, ITransformer trainedModel) { }
Crie uma
IDataView
contendo as previsões usando o métodoTransform
. Adicione o seguinte código dentro do métodoClassifyImages
.IDataView predictionData = trainedModel.Transform(data);
Para iterar as previsões, converta
predictionData
IDataView
em um métodoIEnumerable
usandoCreateEnumerable
e obtenha as primeiras 10 observações.IEnumerable<ModelOutput> predictions = mlContext.Data.CreateEnumerable<ModelOutput>(predictionData, reuseRowObject: true).Take(10);
Iterar e gerar os rótulos originais e previstos para as previsões.
Console.WriteLine("Classifying multiple images"); foreach (var prediction in predictions) { OutputPrediction(prediction); }
Por fim, chame
ClassifyImages
abaixo do métodoClassifySingleImage()
usando o conjunto de testes de imagens.ClassifyImages(mlContext, testSet, trainedModel);
Executar o aplicativo
Execute seu aplicativo de console. A saída deve ser semelhante àquela abaixo. Você poderá ver avisos ou mensagens de processamento, mas essas mensagens foram removidas dos resultados a seguir para maior clareza. Para fins de brevidade, a saída foi condensada.
Fase de gargalo
Nenhum valor é impresso para o nome da imagem porque as imagens são carregadas como um byte[]
, portanto, não há nenhum nome de imagem a ser exibido.
Phase: Bottleneck Computation, Dataset used: Train, Image Index: 279
Phase: Bottleneck Computation, Dataset used: Train, Image Index: 280
Phase: Bottleneck Computation, Dataset used: Validation, Image Index: 1
Phase: Bottleneck Computation, Dataset used: Validation, Image Index: 2
Fase de treinamento
Phase: Training, Dataset used: Validation, Batch Processed Count: 6, Epoch: 21, Accuracy: 0.6797619
Phase: Training, Dataset used: Validation, Batch Processed Count: 6, Epoch: 22, Accuracy: 0.7642857
Phase: Training, Dataset used: Validation, Batch Processed Count: 6, Epoch: 23, Accuracy: 0.7916667
Classificar a saída de imagens
Classifying single image
Image: 7001-220.jpg | Actual Value: UD | Predicted Value: UD
Classifying multiple images
Image: 7001-220.jpg | Actual Value: UD | Predicted Value: UD
Image: 7001-163.jpg | Actual Value: UD | Predicted Value: UD
Image: 7001-210.jpg | Actual Value: UD | Predicted Value: UD
Após a inspeção da imagem 7001-220.jpg, você pode ver que ela de fato não está rachada.
Parabéns! Agora você criou com sucesso um modelo de aprendizado profundo para classificar imagens.
Aprimorar o modelo
Se você não estiver satisfeito com os resultados do modelo, tente melhorar o desempenho tentando algumas das seguintes abordagens:
- Mais dados: quanto mais exemplos um modelo aprender, melhor será seu desempenho. Baixe o conjunto de dados SDNET2018 completo e use-o para treinar.
- Aumentar os dados: uma técnica comum para adicionar variedade aos dados é aumentar os dados usando uma imagem e aplicando diferentes transformações (girar, inverter, mudar, cortar). Isso adiciona exemplos mais variados para o modelo aprender.
- Treinar por mais tempo: quanto mais tempo você treinar, mais ajustado ficará o modelo. Aumentar o número de épocas pode melhorar o desempenho do modelo.
- Experimentar os hiperparâmetros: além dos parâmetros usados neste tutorial, outros parâmetros podem ser ajustados para melhorar potencialmente o desempenho. Alterar a taxa de aprendizado, que determina a magnitude das atualizações feitas no modelo após cada época, pode melhorar o desempenho.
- Usar uma arquitetura de modelo diferente: dependendo da aparência dos dados, o modelo que pode aprender melhor seus recursos pode ser diferente. Se você não estiver satisfeito com o desempenho do modelo, tente alterar a arquitetura.
Próximas etapas
Neste tutorial, você aprendeu a criar um modelo de aprendizado profundo personalizado usando a transferência de aprendizado, um modelo TensorFlow de classificação de imagem pré-treinado e a API de Classificação de Imagem ML.NET para classificar imagens de superfícies de concreto como rachadas ou não rachadas.
Avance para o próximo tutorial para saber mais.