Machine Learning Recunoașterea numerelor - de la zero la aplicație
Publicat: 2022-03-11Învățarea automată, viziunea computerizată, construirea de API-uri puternice și crearea de interfețe de utilizare frumoase sunt domenii interesante care sunt martorii multor inovații.
Primele două necesită matematică și știință extinse, în timp ce dezvoltarea API și UI se concentrează pe gândirea algoritmică și proiectarea de arhitecturi flexibile. Ele sunt foarte diferite, astfel încât să decizi pe care vrei să înveți în continuare poate fi o provocare. Scopul acestui articol este de a demonstra modul în care toate cele patru pot fi folosite în crearea unei aplicații de procesare a imaginilor.
Aplicația pe care urmează să o construim este un simplu detector de cifre. Tu desenezi, mașina prezice cifra. Simplitatea este esențială, deoarece ne permite să vedem imaginea de ansamblu, mai degrabă decât să ne concentrăm pe detalii.
De dragul simplității, vom folosi cele mai populare și ușor de învățat tehnologii. Partea de învățare automată va folosi Python pentru aplicația back-end. În ceea ce privește latura interacțională a aplicației, vom opera printr-o bibliotecă JavaScript care nu are nevoie de prezentare: React.
Învățare automată pentru a ghici cifre
Partea de bază a aplicației noastre este algoritmul care ghicește numărul extras. Învățarea automată va fi instrumentul folosit pentru a obține o calitate bună a ipotezei. Acest tip de inteligență artificială de bază permite unui sistem să învețe automat cu o anumită cantitate de date. În termeni mai largi, învățarea automată este un proces de găsire a unei coincidențe sau a unui set de coincidențe în date pentru a se baza pe acestea pentru a ghici rezultatul.
Procesul nostru de recunoaștere a imaginii conține trei pași:
- Obțineți imagini cu cifrele desenate pentru antrenament
- Antrenați sistemul să ghicească numerele prin intermediul datelor de antrenament
- Testați sistemul cu date noi/necunoscute
Mediu inconjurator
Vom avea nevoie de un mediu virtual pentru a lucra cu machine learning în Python. Această abordare este practică, deoarece gestionează toate pachetele Python necesare, astfel încât nu trebuie să vă faceți griji pentru ele.
Să-l instalăm cu următoarele comenzi de terminal:
python3 -m venv virtualenv source virtualenv/bin/activateModel de antrenament
Înainte de a începe să scriem codul, trebuie să alegem un „profesor” potrivit pentru mașinile noastre. De obicei, profesioniștii în știința datelor încearcă diferite modele înainte de a-l alege pe cel mai bun. Vom sări peste modele foarte avansate care necesită multă abilitate și vom continua cu algoritmul k-nearest neighbors.
Este un algoritm care obține niște mostre de date și le aranjează pe un plan ordonat după un set dat de caracteristici. Pentru a înțelege mai bine, haideți să revizuim următoarea imagine:
Pentru a detecta tipul punctului verde , ar trebui să verificăm tipurile de k vecini cei mai apropiați, unde k este setul de argumente. Având în vedere imaginea de mai sus, dacă k este egal cu 1, 2, 3 sau 4, presupunerea va fi un triunghi negru , deoarece majoritatea celor mai apropiați k vecini ai punctului verde sunt triunghiuri negre. Dacă creștem k la 5, atunci majoritatea obiectelor sunt pătrate albastre, prin urmare presupunerea va fi un pătrat albastru .
Există câteva dependențe necesare pentru a crea modelul nostru de învățare automată:
- sklearn.neighbors.KNeighborsClassifier este clasificatorul pe care îl vom folosi.
- sklearn.model_selection.train_test_split este funcția care ne va ajuta să împărțim datele în date de antrenament și date utilizate pentru a verifica corectitudinea modelului.
- sklearn.model_selection.cross_val_score este funcția de a obține o notă pentru corectitudinea modelului. Cu cât valoarea este mai mare, cu atât este mai bună corectitudinea.
- sklearn.metrics.classification_report este funcția de afișare a unui raport statistic al presupunerilor modelului.
- sklearn.datasets este pachetul folosit pentru a obține date pentru antrenament (imagini cu cifre).
- numpy este un pachet utilizat pe scară largă în știință, deoarece oferă o modalitate productivă și confortabilă de a manipula structurile de date multidimensionale în Python.
- matplotlib.pyplot este pachetul folosit pentru vizualizarea datelor.
Să începem prin a le instala și importa pe toate:
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 pltAcum, trebuie să încărcăm baza de date MNIST. MNIST este un set de date clasic de imagini scrise de mână folosit de mii de începători în domeniul învățării automate:
digits = load_digits()Odată ce datele sunt preluate și gata, putem trece la următorul pas de împărțire a datelor în două părți: antrenament și testare .
Vom folosi 75% din date pentru a antrena modelul nostru să ghicească cifre și vom folosi restul datelor pentru a testa corectitudinea modelului:
(X_train, X_test, y_train, y_test) = train_test_split( digits.data, digits.target, test_size=0.25, random_state=42 )Datele sunt acum aranjate și suntem gata să le folosim. Vom încerca să găsim cel mai bun parametru k pentru modelul nostru, astfel încât presupunerile să fie mai precise. Nu putem ține cont de valoarea k în această etapă, deoarece trebuie să evaluăm modelul cu diferite valori k .
Să vedem de ce este esențial să luăm în considerare o gamă de valori k și cum acest lucru îmbunătățește precizia modelului nostru:
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()Executarea acestui cod vă va arăta următorul grafic care descrie precizia algoritmului cu diferite k valori.
După cum puteți vedea, o valoare k de 3 asigură cea mai bună acuratețe pentru modelul și setul nostru de date.
Folosind Flask pentru a construi un API
Miezul aplicației, care este un algoritm care prezice cifrele din imagini, este acum gata. În continuare, trebuie să decoram algoritmul cu un strat API pentru a-l face disponibil pentru utilizare. Să folosim popularul cadru web Flask pentru a face acest lucru în mod curat și concis.
Vom începe prin a instala Flask și dependențele legate de procesarea imaginilor în mediul virtual:
pip install Flask Pillow scikit-imageCând instalarea se termină, trecem la crearea fișierului punct de intrare al aplicației:
touch app.pyConținutul fișierului va arăta astfel:
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) Veți primi o eroare care spune că PredictDigitView și IndexView nu sunt definite. Următorul pas este crearea unui fișier care va inițializa aceste vizualizări:
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)Încă o dată, vom întâlni o eroare despre un import nerezolvat. Pachetul Views se bazează pe trei fișiere pe care nu le avem încă:
- Setări
- Repo
- Serviciu
Le vom implementa unul câte unul.
Settings este un modul cu configurații și variabile constante. Acesta va stoca calea către clasificatorul serializat pentru noi. Se ridică o întrebare logică: de ce trebuie să salvez clasificatorul?
Pentru că este o modalitate simplă de a îmbunătăți performanța aplicației dvs. În loc să antrenăm clasificatorul de fiecare dată când primiți o solicitare, vom stoca versiunea pregătită a clasificatorului, permițându-i să funcționeze imediat:
import os BASE_DIR = os.getcwd() CLASSIFIER_STORAGE = os.path.join(BASE_DIR, 'storage/classifier.txt') Mecanismul pentru setări — obținerea clasificatorului — va fi inițializat în următorul pachet de pe lista noastră, Repo . Este o clasă cu două metode de a prelua și actualiza clasificatorul antrenat folosind modulul pickle încorporat din 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_)Suntem aproape de finalizarea API-ului nostru. Acum îi lipsește doar modulul Service . Care este scopul lui?
- Luați clasificatorul instruit din depozit
- Transformați imaginea transmisă din UI într-un format pe care clasificatorul îl înțelege
- Calculați predicția cu imaginea formatată prin intermediul clasificatorului
- Întoarceți predicția
Să codificăm acest algoritm:

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 Aici puteți vedea că PredictDigitService are două dependențe: ClassifierFactory și process_image .
Vom începe prin a crea o clasă pentru a crea și antrena modelul nostru:
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 modelAPI-ul este gata de acțiune. Acum putem trece la pasul de procesare a imaginii.
Procesarea imaginii
Procesarea imaginii este o metodă de a efectua anumite operații asupra unei imagini pentru a o îmbunătăți sau a extrage unele informații utile din ea. În cazul nostru, trebuie să facem tranziția fără probleme a imaginii desenate de un utilizator la formatul modelului de învățare automată.
Să importăm câțiva ajutoare pentru a atinge acest obiectiv:
import numpy as np from skimage import exposure import base64 from PIL import Image, ImageOps, ImageChops from io import BytesIOPutem împărți tranziția în șase părți distincte:
1. Înlocuiți un fundal transparent cu o culoare
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. Tăiați marginile deschise
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 image3. Adăugați chenaruri de dimensiuni egale
def pad_image(image): return ImageOps.expand(image, border=30, fill='#fff')4. Convertiți imaginea în modul în tonuri de gri
def to_grayscale(image): return image.convert('L')5. Inversa culorile
def invert_colors(image): return ImageOps.invert(image)6. Redimensionați imaginea în format 8x8
def resize_image(image): return image.resize((8, 8), Image.LINEAR)Acum puteți testa aplicația. Rulați aplicația și introduceți comanda de mai jos pentru a trimite o solicitare cu această imagine iStock către API:
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)\"}" -iAr trebui să vedeți următoarea ieșire:
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 8Imaginea eșantion a descris numărul 8, iar aplicația noastră l-a identificat corect ca atare.
Crearea unui panou de desen prin React
Pentru a porni rapid aplicația frontală, vom folosi CRA boilerplate:
create-react-app frontend cd frontendDupă configurarea locului de muncă, avem nevoie și de o dependență pentru a desena cifre. Pachetul react-sketch se potrivește perfect nevoilor noastre:
npm i react-sketchAplicația are o singură componentă. Putem împărți această componentă în două părți: logică și vizualizare .
Partea de vizualizare este responsabilă pentru reprezentarea panoului de desen, butoanele Trimitere și Resetare . Atunci când interacționăm, ar trebui să reprezentăm și o predicție sau o eroare. Din punct de vedere logic, are următoarele atribuții: trimite imagini și șterge schița .
Ori de câte ori un utilizator dă clic pe Trimitere , componenta va extrage imaginea din componenta schiță și va apela la funcția makePrediction a modulului API. Dacă cererea către back-end reușește, vom seta variabila de stare de predicție. În caz contrar, vom actualiza starea de eroare.
Când un utilizator dă clic pe Resetare , schița se va șterge:
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 }Logica este suficientă. Acum îi putem adăuga interfața vizuală:
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; Componenta este gata, testați-o executând și accesând localhost:3000 după:
npm run startAplicația demo este disponibilă aici. De asemenea, puteți căuta codul sursă pe GitHub.
Încheierea
Calitatea acestui clasificator nu este perfectă și nu pretind că este. Diferența dintre datele pe care le-am folosit pentru antrenament și datele care provin din UI este enormă. În ciuda acestui fapt, am creat o aplicație funcțională de la zero în mai puțin de 30 de minute.
În acest proces, ne-am perfecționat abilitățile în patru domenii:
- Învățare automată
- Dezvoltare back-end
- Procesarea imaginii
- Dezvoltare front-end
Nu lipsesc cazurile potențiale de utilizare pentru software capabil să recunoască cifrele scrise de mână, de la software educațional și administrativ până la servicii poștale și financiare.
Prin urmare, sper că acest articol vă va motiva să vă îmbunătățiți abilitățile de învățare automată, procesarea imaginilor și dezvoltarea front-end și back-end și să utilizați aceste abilități pentru a crea aplicații minunate și utile.
Dacă doriți să vă extindeți cunoștințele despre învățarea automată și procesarea imaginilor, vă recomandăm să consultați Tutorialul nostru de învățare automată Adversarial.
