Riconoscimento numerico di Machine Learning: da zero all'applicazione

Pubblicato: 2022-03-11

L'apprendimento automatico, la visione artificiale, la creazione di potenti API e la creazione di bellissime interfacce utente sono campi entusiasmanti che testimoniano molta innovazione.

I primi due richiedono matematica e scienze approfondite, mentre lo sviluppo di API e UI si concentra sul pensiero algoritmico e sulla progettazione di architetture flessibili. Sono molto diversi, quindi decidere quale vuoi imparare dopo potrebbe essere difficile. Lo scopo di questo articolo è dimostrare come tutti e quattro possono essere impiegati nella creazione di un'applicazione di elaborazione delle immagini.

L'applicazione che creeremo è un semplice riconoscitore di cifre. Tu disegni, la macchina prevede la cifra. La semplicità è essenziale perché ci permette di vedere il quadro generale piuttosto che concentrarci sui dettagli.

Per semplicità, utilizzeremo le tecnologie più popolari e di facile apprendimento. La parte di apprendimento automatico utilizzerà Python per l'applicazione back-end. Per quanto riguarda il lato interattivo dell'app, opereremo tramite una libreria JavaScript che non ha bisogno di presentazioni: React.

Apprendimento automatico per indovinare le cifre

La parte centrale della nostra app è l'algoritmo che indovina il numero estratto. L'apprendimento automatico sarà lo strumento utilizzato per ottenere una buona qualità di ipotesi. Questo tipo di intelligenza artificiale di base consente a un sistema di apprendere automaticamente con una determinata quantità di dati. In termini più ampi, l'apprendimento automatico è un processo per trovare una coincidenza o un insieme di coincidenze nei dati su cui fare affidamento per indovinare il risultato.

Il nostro processo di riconoscimento delle immagini comprende tre fasi:

  • Ottieni immagini di cifre disegnate per l'allenamento
  • Addestra il sistema per indovinare i numeri tramite i dati di allenamento
  • Testare il sistema con dati nuovi/sconosciuti

Ambiente

Avremo bisogno di un ambiente virtuale per lavorare con l'apprendimento automatico in Python. Questo approccio è pratico perché gestisce tutti i pacchetti Python richiesti, quindi non devi preoccuparti di loro.

Installiamolo con i seguenti comandi da terminale:

 python3 -m venv virtualenv source virtualenv/bin/activate

Modello di formazione

Prima di iniziare a scrivere il codice, dobbiamo scegliere un "insegnante" adatto alle nostre macchine. Di solito, i professionisti della scienza dei dati provano diversi modelli prima di scegliere quello migliore. Salteremo modelli molto avanzati che richiedono molta abilità e procederemo con l'algoritmo k-neiest neighbors.

È un algoritmo che ottiene alcuni campioni di dati e li dispone su un piano ordinato da un dato insieme di caratteristiche. Per capirlo meglio, esaminiamo la seguente immagine:

Immagine: campioni di dati di apprendimento automatico disposti su un piano

Per rilevare il tipo di Green Dot , dovremmo controllare i tipi di k vicini più vicini dove k è l'argomento impostato. Considerando l'immagine sopra, se k è uguale a 1, 2, 3 o 4, l'ipotesi sarà un triangolo nero poiché la maggior parte dei k vicini più vicini del punto verde sono triangoli neri. Se aumentiamo k a 5, la maggior parte degli oggetti sono quadrati blu, quindi l'ipotesi sarà un quadrato blu .

Ci sono alcune dipendenze necessarie per creare il nostro modello di machine learning:

  • sklearn.neighbors.KNeighborsClassifier è il classificatore che useremo.
  • sklearn.model_selection.train_test_split è la funzione che ci aiuterà a suddividere i dati in dati di addestramento e dati utilizzati per verificare la correttezza del modello.
  • sklearn.model_selection.cross_val_score è la funzione per ottenere un voto per la correttezza del modello. Maggiore è il valore, migliore è la correttezza.
  • sklearn.metrics.classification_report è la funzione per mostrare un report statistico delle ipotesi del modello.
  • sklearn.datasets è il pacchetto utilizzato per ottenere i dati per l'allenamento (immagini di cifre).
  • numpy è un pacchetto ampiamente utilizzato nella scienza in quanto offre un modo produttivo e comodo per manipolare strutture di dati multidimensionali in Python.
  • matplotlib.pyplot è il pacchetto utilizzato per visualizzare i dati.

Iniziamo installando e importando tutti:

 pip install sklearn numpy matplotlib scipy from sklearn.datasets import load_digits from sklearn.neighbors import KNeighborsClassifier from sklearn.model_selection import train_test_split, cross_val_score import numpy as np import matplotlib.pyplot as plt

Ora dobbiamo caricare il database MNIST. MNIST è un classico set di dati di immagini scritte a mano utilizzato da migliaia di principianti nel campo dell'apprendimento automatico:

 digits = load_digits()

Una volta che i dati sono stati recuperati e pronti, possiamo passare al passaggio successivo di dividere i dati in due parti: training e testing .

Useremo il 75% dei dati per addestrare il nostro modello a indovinare le cifre e useremo il resto dei dati per testare la correttezza del modello:

 (X_train, X_test, y_train, y_test) = train_test_split( digits.data, digits.target, test_size=0.25, random_state=42 )

I dati sono ora organizzati e siamo pronti per usarli. Cercheremo di trovare il miglior parametro k per il nostro modello in modo che le ipotesi siano più precise. Non possiamo tenere il valore k lontano dalla nostra mente in questa fase, poiché dobbiamo valutare il modello con valori k diversi.

Vediamo perché è essenziale considerare un intervallo di k valori e come questo migliora l'accuratezza del nostro modello:

 ks = np.arange(2, 10) scores = [] for k in ks: model = KNeighborsClassifier(n_neighbors=k) score = cross_val_score(model, X_train, y_train, cv=5) score.mean() scores.append(score.mean()) plt.plot(scores, ks) plt.xlabel('accuracy') plt.ylabel('k') plt.show()

L'esecuzione di questo codice ti mostrerà il grafico seguente che descrive l'accuratezza dell'algoritmo con diversi k valori.

Immagine: grafico utilizzato per testare l'accuratezza dell'algoritmo con diversi k valori.

Come puoi vedere, un valore k di 3 garantisce la migliore precisione per il nostro modello e set di dati.

Utilizzo di Flask per creare un'API

Il core dell'applicazione, che è un algoritmo che prevede le cifre dalle immagini, è ora pronto. Successivamente, dobbiamo decorare l'algoritmo con un livello API per renderlo disponibile per l'uso. Usiamo il popolare framework web Flask per farlo in modo pulito e conciso.

Inizieremo installando Flask e le dipendenze relative all'elaborazione delle immagini nell'ambiente virtuale:

 pip install Flask Pillow scikit-image

Al termine dell'installazione, si passa alla creazione del file del punto di ingresso dell'app:

 touch app.py

Il contenuto del file sarà simile a questo:

 import os from flask import Flask from views import PredictDigitView, IndexView app = Flask(__name__) app.add_url_rule( '/api/predict', view_func=PredictDigitView.as_view('predict_digit'), methods=['POST'] ) app.add_url_rule( '/', view_func=IndexView.as_view('index'), methods=['GET'] ) if __name__ == 'main': port = int(os.environ.get("PORT", 5000)) app.run(host='0.0.0.0', port=port)

Verrà visualizzato un errore che informa che PredictDigitView e IndexView non sono definiti. Il passaggio successivo è la creazione di un file che inizializzerà queste viste:

 from flask import render_template, request, Response from flask.views import MethodView, View from flask.views import View from repo import ClassifierRepo from services import PredictDigitService from settings import CLASSIFIER_STORAGE class IndexView(View): def dispatch_request(self): return render_template('index.html') class PredictDigitView(MethodView): def post(self): repo = ClassifierRepo(CLASSIFIER_STORAGE) service = PredictDigitService(repo) image_data_uri = request.json['image'] prediction = service.handle(image_data_uri) return Response(str(prediction).encode(), status=200)

Ancora una volta, incontreremo un errore relativo a un'importazione non risolta. Il pacchetto Views si basa su tre file che non abbiamo ancora:

  • Impostazioni
  • Repo
  • Servizio

Li implementeremo uno per uno.

Settings è un modulo con configurazioni e variabili costanti. Memorizzerà per noi il percorso del classificatore serializzato. Si pone una domanda logica: perché devo salvare il classificatore?

Perché è un modo semplice per migliorare le prestazioni della tua app. Invece di addestrare il classificatore ogni volta che ricevi una richiesta, memorizzeremo la versione preparata del classificatore, consentendogli di funzionare immediatamente:

 import os BASE_DIR = os.getcwd() CLASSIFIER_STORAGE = os.path.join(BASE_DIR, 'storage/classifier.txt')

Il meccanismo per le impostazioni - ottenere il classificatore - verrà inizializzato nel prossimo pacchetto del nostro elenco, il Repo . È una classe con due metodi per recuperare e aggiornare il classificatore addestrato utilizzando il modulo pickle integrato di Python:

 import pickle class ClassifierRepo: def __init__(self, storage): self.storage = storage def get(self): with open(self.storage, 'wb') as out: try: classifier_str = out.read() if classifier_str != '': return pickle.loads(classifier_str) else: return None except Exception: return None def update(self, classifier): with open(self.storage, 'wb') as in_: pickle.dump(classifier, in_)

Siamo vicini alla finalizzazione della nostra API. Ora manca solo il modulo Service . Qual è il suo scopo?

  • Ottieni il classificatore addestrato dalla memoria
  • Trasforma l'immagine passata dall'interfaccia utente in un formato comprensibile per il classificatore
  • Calcola la previsione con l'immagine formattata tramite il classificatore
  • Restituisci la previsione

Codifichiamo questo algoritmo:

 from sklearn.datasets import load_digits from classifier import ClassifierFactory from image_processing import process_image class PredictDigitService: def __init__(self, repo): self.repo = repo def handle(self, image_data_uri): classifier = self.repo.get() if classifier is None: digits = load_digits() classifier = ClassifierFactory.create_with_fit( digits.data, digits.target ) self.repo.update(classifier) x = process_image(image_data_uri) if x is None: return 0 prediction = classifier.predict(x)[0] return prediction

Qui puoi vedere che PredictDigitService ha due dipendenze: ClassifierFactory e process_image .

Inizieremo creando una classe per creare e addestrare il nostro modello:

 from sklearn.model_selection import train_test_split from sklearn.neighbors import KNeighborsClassifier class ClassifierFactory: @staticmethod def create_with_fit(data, target): model = KNeighborsClassifier(n_neighbors=3) model.fit(data, target) return model

L'API è pronta per l'azione. Ora possiamo procedere alla fase di elaborazione dell'immagine.

Elaborazione delle immagini

L'elaborazione delle immagini è un metodo per eseguire determinate operazioni su un'immagine per migliorarla o estrarne alcune informazioni utili. Nel nostro caso, dobbiamo trasferire senza problemi l'immagine disegnata da un utente al formato del modello di apprendimento automatico.

Image alt: trasformare le immagini disegnate in un formato di machine learning.

Importiamo alcuni aiutanti per raggiungere questo obiettivo:

 import numpy as np from skimage import exposure import base64 from PIL import Image, ImageOps, ImageChops from io import BytesIO

Possiamo dividere la transizione in sei parti distinte:

1. Sostituisci uno sfondo trasparente con un colore

Image alt: sostituzione dello sfondo su un'immagine di esempio.

 def replace_transparent_background(image): image_arr = np.array(image) if len(image_arr.shape) == 2: return image alpha1 = 0 r2, g2, b2, alpha2 = 255, 255, 255, 255 red, green, blue, alpha = image_arr[:, :, 0], image_arr[:, :, 1], image_arr[:, :, 2], image_arr[:, :, 3] mask = (alpha == alpha1) image_arr[:, :, :4][mask] = [r2, g2, b2, alpha2] return Image.fromarray(image_arr)

2. Taglia i bordi aperti

Immagine: tagliare i bordi su un'immagine di esempio.

 def trim_borders(image): bg = Image.new(image.mode, image.size, image.getpixel((0,0))) diff = ImageChops.difference(image, bg) diff = ImageChops.add(diff, diff, 2.0, -100) bbox = diff.getbbox() if bbox: return image.crop(bbox) return image

3. Aggiungi bordi di uguale dimensione

Immagine: aggiunta di bordi di una preimpostazione e di dimensioni uguali a un'immagine campione.

 def pad_image(image): return ImageOps.expand(image, border=30, fill='#fff')

4. Convertire l'immagine in modalità scala di grigi

 def to_grayscale(image): return image.convert('L')

5. Inverti i colori

Immagine: inversione dei colori dell'immagine campione.

 def invert_colors(image): return ImageOps.invert(image)

6. Ridimensiona l'immagine in formato 8x8

Immagine: ridimensionamento dell'immagine di esempio in un formato 8x8.

 def resize_image(image): return image.resize((8, 8), Image.LINEAR)

Ora puoi testare l'app. Esegui l'applicazione e inserisci il comando seguente per inviare una richiesta con questa immagine iStock all'API:

Immagine: immagine di stock di un numero otto disegnato a mano.

 export FLASK_APP=app flask run
 curl "http://localhost:5000/api/predict" -X "POST" -H "Content-Type: application/json" -d "{\"image\": \"data:image/png;base64,$(curl "https://media.istockphoto.com/vectors/number-eight-8-hand-drawn-with-dry-brush-vector-id484207302?k=6&m=484207302&s=170667a&w=0&h=s3YANDyuLS8u2so-uJbMA2uW6fYyyRkabc1a6OTq7iI=" | base64)\"}" -i

Dovresti vedere il seguente output:

 HTTP/1.1 100 Continue HTTP/1.0 200 OK Content-Type: text/html; charset=utf-8 Content-Length: 1 Server: Werkzeug/0.14.1 Python/3.6.3 Date: Tue, 27 Mar 2018 07:02:08 GMT 8

L'immagine di esempio raffigurava il numero 8 e la nostra app lo ha identificato correttamente come tale.

Creazione di un riquadro di disegno tramite React

Per avviare rapidamente l'applicazione frontend, utilizzeremo CRA boilerplate:

 create-react-app frontend cd frontend

Dopo aver impostato il posto di lavoro, abbiamo anche bisogno di una dipendenza per disegnare le cifre. Il pacchetto react-sketch soddisfa perfettamente le nostre esigenze:

 npm i react-sketch

L'applicazione ha un solo componente. Possiamo dividere questo componente in due parti: logica e vista .

La parte della vista è responsabile della rappresentazione del riquadro di disegno, dei pulsanti Invia e Ripristina . Quando interagiamo, dovremmo anche rappresentare una previsione o un errore. Dal punto di vista logico, ha i seguenti compiti: inviare immagini e cancellare lo schizzo .

Ogni volta che un utente fa clic su Invia , il componente estrae l'immagine dal componente di schizzo e si rivolge alla funzione makePrediction del modulo API. Se la richiesta al back-end ha esito positivo, imposteremo la variabile dello stato di previsione. In caso contrario, aggiorneremo lo stato di errore.

Quando un utente fa clic su Reimposta , lo schizzo verrà cancellato:

 import React, { useRef, useState } from "react"; import { makePrediction } from "./api"; const App = () => { const sketchRef = useRef(null); const [error, setError] = useState(); const [prediction, setPrediction] = useState(); const handleSubmit = () => { const image = sketchRef.current.toDataURL(); setPrediction(undefined); setError(undefined); makePrediction(image).then(setPrediction).catch(setError); }; const handleClear = (e) => sketchRef.current.clear(); return null }

La logica è sufficiente. Ora possiamo aggiungere l'interfaccia visiva ad esso:

 import React, { useRef, useState } from "react"; import { SketchField, Tools } from "react-sketch"; import { makePrediction } from "./api"; import logo from "./logo.svg"; import "./App.css"; const pixels = (count) => `${count}px`; const percents = (count) => `${count}%`; const MAIN_CONTAINER_WIDTH_PX = 200; const MAIN_CONTAINER_HEIGHT = 100; const MAIN_CONTAINER_STYLE = { width: pixels(MAIN_CONTAINER_WIDTH_PX), height: percents(MAIN_CONTAINER_HEIGHT), margin: "0 auto", }; const SKETCH_CONTAINER_STYLE = { border: "1px solid black", width: pixels(MAIN_CONTAINER_WIDTH_PX - 2), height: pixels(MAIN_CONTAINER_WIDTH_PX - 2), backgroundColor: "white", }; const App = () => { const sketchRef = useRef(null); const [error, setError] = useState(); const [prediction, setPrediction] = useState(); const handleSubmit = () => { const image = sketchRef.current.toDataURL(); setPrediction(undefined); setError(undefined); makePrediction(image).then(setPrediction).catch(setError); }; const handleClear = (e) => sketchRef.current.clear(); return ( <div className="App" style={MAIN_CONTAINER_STYLE}> <div> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <h1 className="App-title">Draw a digit</h1> </header> <div style={SKETCH_CONTAINER_STYLE}> <SketchField ref={sketchRef} width="100%" height="100%" tool={Tools.Pencil} imageFormat="jpg" lineColor="#111" lineWidth={10} /> </div> {prediction && <h3>Predicted value is: {prediction}</h3>} <button onClick={handleClear}>Clear</button> <button onClick={handleSubmit}>Guess the number</button> {error && <p style={{ color: "red" }}>Something went wrong</p>} </div> </div> ); }; export default App;

Il componente è pronto, provalo eseguendo e andando su localhost:3000 dopo:

 npm run start

L'applicazione demo è disponibile qui. Puoi anche sfogliare il codice sorgente su GitHub.

Avvolgendo

La qualità di questo classificatore non è perfetta e non pretendo che lo sia. La differenza tra i dati che abbiamo utilizzato per l'allenamento e i dati provenienti dall'interfaccia utente è enorme. Nonostante ciò, abbiamo creato un'applicazione funzionante da zero in meno di 30 minuti.

Immagine: Animazione che mostra l'app finalizzata che identifica le cifre scritte a mano.

Nel processo, abbiamo affinato le nostre competenze in quattro campi:

  • Apprendimento automatico
  • Sviluppo back-end
  • Elaborazione delle immagini
  • Sviluppo front-end

Non mancano potenziali casi d'uso per software in grado di riconoscere cifre scritte a mano, che vanno dal software educativo e amministrativo ai servizi postali e finanziari.

Pertanto, spero che questo articolo ti motiverà a migliorare le tue capacità di apprendimento automatico, l'elaborazione delle immagini e lo sviluppo front-end e back-end e utilizzare queste abilità per progettare applicazioni meravigliose e utili.

Se desideri ampliare le tue conoscenze sull'apprendimento automatico e sull'elaborazione delle immagini, dai un'occhiata al nostro Tutorial di apprendimento automatico contraddittorio.