การรู้จำหมายเลขการเรียนรู้ของเครื่อง - จากศูนย์ถึงแอปพลิเคชัน
เผยแพร่แล้ว: 2022-03-11แมชชีนเลิร์นนิง คอมพิวเตอร์วิทัศน์ การสร้าง API ที่ทรงพลัง และการสร้าง UI ที่สวยงามเป็นสาขาที่น่าตื่นเต้นที่ได้เห็นนวัตกรรมมากมาย
สองรายการแรกต้องการคณิตศาสตร์และวิทยาศาสตร์อย่างกว้างขวาง ในขณะที่ศูนย์พัฒนา API และ UI อยู่ที่การคิดแบบอัลกอริทึมและการออกแบบสถาปัตยกรรมที่ยืดหยุ่น พวกเขาแตกต่างกันมาก ดังนั้นการตัดสินใจว่าจะเรียนรู้เรื่องใดต่อไปอาจเป็นเรื่องที่ท้าทาย บทความนี้มีจุดประสงค์เพื่อแสดงให้เห็นว่าทั้งสี่สามารถนำมาใช้ในการสร้างแอปพลิเคชันการประมวลผลภาพได้อย่างไร
แอปพลิเคชั่นที่เราจะสร้างคือตัวจำแนกหลักอย่างง่าย คุณวาด เครื่องทำนายตัวเลข ความเรียบง่ายเป็นสิ่งสำคัญเพราะช่วยให้เรามองเห็นภาพรวมมากกว่าที่จะเน้นที่รายละเอียด
เพื่อความเรียบง่าย เราจะใช้เทคโนโลยีที่ได้รับความนิยมและเรียนรู้ง่ายที่สุด ส่วนการเรียนรู้ของเครื่องจะใช้ Python สำหรับแอปพลิเคชันส่วนหลัง ในส่วนของการโต้ตอบของแอป เราจะดำเนินการผ่านไลบรารี JavaScript ที่ไม่ต้องการการแนะนำ: React
แมชชีนเลิร์นนิงเพื่อเดาตัวเลข
ส่วนหลักของแอปของเราคืออัลกอริทึมที่คาดเดาหมายเลขที่ออก แมชชีนเลิร์นนิงจะเป็นเครื่องมือที่ใช้เพื่อให้ได้คุณภาพการเดาที่ดี ปัญญาประดิษฐ์พื้นฐานประเภทนี้ช่วยให้ระบบเรียนรู้โดยอัตโนมัติด้วยข้อมูลตามจำนวนที่กำหนด ในแง่กว้างๆ แมชชีนเลิร์นนิงเป็นกระบวนการในการค้นหาความบังเอิญหรือชุดของความบังเอิญในข้อมูลเพื่อพึ่งพาพวกเขาในการเดาผลลัพธ์
กระบวนการจดจำภาพของเราประกอบด้วยสามขั้นตอน:
- รับภาพหลักในการฝึกซ้อม
- ฝึกระบบเดาตัวเลขผ่านข้อมูลการฝึก
- ทดสอบระบบด้วยข้อมูลใหม่/ไม่รู้จัก
สิ่งแวดล้อม
เราจำเป็นต้องมีสภาพแวดล้อมเสมือนเพื่อทำงานกับการเรียนรู้ของเครื่องใน Python วิธีนี้ใช้ได้จริงเพราะจัดการแพ็คเกจ Python ที่จำเป็นทั้งหมด ดังนั้นคุณไม่ต้องกังวลกับมัน
มาติดตั้งด้วยคำสั่งเทอร์มินัลต่อไปนี้:
python3 -m venv virtualenv source virtualenv/bin/activate
แบบฝึก
ก่อนที่เราจะเริ่มเขียนโค้ด เราต้องเลือก "ครู" ที่เหมาะสมสำหรับเครื่องของเรา โดยปกติ ผู้เชี่ยวชาญด้านวิทยาศาสตร์ข้อมูลจะลองใช้แบบจำลองต่างๆ ก่อนเลือกแบบจำลองที่ดีที่สุด เราจะข้ามโมเดลขั้นสูงที่ต้องใช้ทักษะมาก และดำเนินการอัลกอริธึมเพื่อนบ้านที่ใกล้ที่สุด k
เป็นอัลกอริธึมที่รับตัวอย่างข้อมูลและจัดเรียงบนระนาบโดยเรียงตามชุดของคุณลักษณะที่กำหนด เพื่อให้เข้าใจมากขึ้น เรามาทบทวนภาพต่อไปนี้:
ในการตรวจสอบประเภทของ Green Dot เราควรตรวจสอบประเภทของเพื่อนบ้านที่ใกล้ที่สุด k โดยที่ k คือชุดอาร์กิวเมนต์ เมื่อพิจารณาจากภาพด้านบน หาก k เท่ากับ 1, 2, 3 หรือ 4 การเดาจะเป็น สามเหลี่ยมสีดำ เนื่องจาก k เพื่อนบ้านที่ใกล้ที่สุดของจุดสีเขียวส่วนใหญ่เป็นสามเหลี่ยมสีดำ หากเราเพิ่ม k เป็น 5 วัตถุส่วนใหญ่จะเป็นสี่เหลี่ยมสีน้ำเงิน ดังนั้นการเดาจะเป็น Blue Square
มีการพึ่งพาบางอย่างที่จำเป็นในการสร้างโมเดลการเรียนรู้ของเครื่องของเรา:
- sklearn.neighbors.KNeighborsClassifier เป็นตัวแยกประเภทที่เราจะใช้
- sklearn.model_selection.train_test_split เป็นฟังก์ชันที่จะช่วยเราแบ่งข้อมูลออกเป็นข้อมูลการฝึกและข้อมูลที่ใช้ตรวจสอบความถูกต้องของแบบจำลอง
- sklearn.model_selection.cross_val_score เป็นฟังก์ชันสำหรับให้คะแนนความถูกต้องของแบบจำลอง ยิ่งค่าสูง ความถูกต้องยิ่งดี
- sklearn.metrics.classification_report เป็นฟังก์ชันที่จะแสดงรายงานทางสถิติของการคาดเดาของแบบจำลอง
- sklearn.datasets เป็นแพ็คเกจที่ใช้ในการรับข้อมูลสำหรับการฝึกอบรม (รูปภาพของตัวเลข)
- numpy เป็นแพ็คเกจที่ใช้กันอย่างแพร่หลายในด้านวิทยาศาสตร์ เนื่องจากเป็นวิธีที่มีประสิทธิผลและสะดวกสบายในการจัดการโครงสร้างข้อมูลหลายมิติใน Python
- matplotlib.pyplot เป็นแพ็คเกจที่ใช้ในการแสดงข้อมูลเป็นภาพ
เริ่มต้นด้วยการติดตั้งและนำเข้าทั้งหมด:
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
ตอนนี้ เราต้องโหลดฐานข้อมูล MNIST MNIST เป็นชุดข้อมูลคลาสสิกของรูปภาพที่เขียนด้วยลายมือซึ่งใช้งานโดยสามเณรหลายพันคนในสาขาการเรียนรู้ของเครื่อง:
digits = load_digits()
เมื่อดึงข้อมูลและพร้อมแล้ว เราสามารถย้ายไปยังขั้นตอนถัดไปของการแยกข้อมูลออกเป็นสองส่วน: การฝึกอบรม และ การทดสอบ
เราจะใช้ข้อมูล 75% เพื่อฝึกแบบจำลองของเราให้เดาตัวเลข และเราจะใช้ข้อมูลที่เหลือเพื่อทดสอบความถูกต้องของแบบจำลอง:
(X_train, X_test, y_train, y_test) = train_test_split( digits.data, digits.target, test_size=0.25, random_state=42 )
ข้อมูลถูกจัดเรียงและเราพร้อมที่จะใช้งาน เราจะพยายามค้นหาพารามิเตอร์ k ที่ดีที่สุดสำหรับแบบจำลองของเรา เพื่อให้การเดาแม่นยำยิ่งขึ้น เราไม่สามารถเก็บค่า k ไว้ในใจได้ในขั้นตอนนี้ เนื่องจากเราต้องประเมินแบบจำลองด้วยค่า k ที่แตกต่างกัน
มาดูกันว่าทำไมการพิจารณาช่วงของค่า k จึงจำเป็น และสิ่งนี้จะช่วยปรับปรุงความแม่นยำของแบบจำลองของเราได้อย่างไร:
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()
การดำเนินการโค้ดนี้จะแสดงแผนภาพต่อไปนี้ซึ่งอธิบายความถูกต้องของอัลกอริทึมด้วยค่า k ที่แตกต่างกัน
อย่างที่คุณเห็น ค่า k เท่ากับ 3 ช่วยให้มั่นใจถึงความแม่นยำสูงสุดสำหรับโมเดลและชุดข้อมูลของเรา
การใช้ Flask เพื่อสร้าง API
แกนของแอปพลิเคชันซึ่งเป็นอัลกอริธึมทำนายตัวเลขจากรูปภาพพร้อมแล้ว ต่อไป เราต้องตกแต่งอัลกอริทึมด้วยเลเยอร์ API เพื่อให้พร้อมใช้งาน ลองใช้เฟรมเวิร์กเว็บ Flask ยอดนิยมเพื่อทำสิ่งนี้อย่างหมดจดและรัดกุม
เราจะเริ่มต้นด้วยการติดตั้ง Flask และการพึ่งพาที่เกี่ยวข้องกับการประมวลผลภาพในสภาพแวดล้อมเสมือน:
pip install Flask Pillow scikit-image
เมื่อการติดตั้งเสร็จสิ้น เราจะย้ายไปยังการสร้างไฟล์จุดเข้าใช้งานของแอป:
touch app.py
เนื้อหาของไฟล์จะมีลักษณะดังนี้:
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)
คุณจะได้รับข้อผิดพลาดแจ้ง PredictDigitView
และ IndexView
ขั้นตอนต่อไปคือการสร้างไฟล์ที่จะเริ่มต้นมุมมองเหล่านี้:
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)
อีกครั้ง เราจะพบข้อผิดพลาดเกี่ยวกับการนำเข้าที่ยังไม่ได้แก้ไข แพ็คเกจ Views อาศัยสามไฟล์ที่เรายังไม่มี:
- การตั้งค่า
- Repo
- บริการ
เราจะนำไปใช้ทีละรายการ
การตั้งค่า เป็นโมดูลที่มีการกำหนดค่าและตัวแปรคงที่ มันจะเก็บพาธไปยังตัวแยกประเภทอนุกรมสำหรับเรา ทำให้เกิดคำถามเชิงตรรกะ: ทำไมฉันต้องบันทึกตัวแยกประเภท
เนื่องจากเป็นวิธีง่ายๆ ในการปรับปรุงประสิทธิภาพของแอป แทนที่จะฝึกตัวแยกประเภททุกครั้งที่คุณได้รับคำขอ เราจะจัดเก็บเวอร์ชันที่เตรียมไว้ของตัวแยกประเภท ซึ่งช่วยให้ทำงานนอกกรอบได้:
import os BASE_DIR = os.getcwd() CLASSIFIER_STORAGE = os.path.join(BASE_DIR, 'storage/classifier.txt')
กลไกสำหรับการตั้งค่า — รับตัวแยกประเภท — จะเริ่มต้นในแพ็คเกจถัดไปในรายการของเรา Repo เป็นคลาสที่มีสองวิธีในการดึงและอัปเดตตัวแยกประเภทที่ผ่านการฝึกอบรมโดยใช้โมดูล pickle
ในตัวของ 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_)
เราใกล้จะเสร็จสิ้น API ของเราแล้ว ตอนนี้มันขาดเฉพาะโมดูล บริการ จุดประสงค์ของมันคืออะไร?
- รับลักษณนามที่ผ่านการฝึกอบรมจากการจัดเก็บ
- เปลี่ยนภาพที่ส่งผ่านจาก UI เป็นรูปแบบที่ตัวแยกประเภทเข้าใจ
- คำนวณการทำนายด้วยภาพที่จัดรูปแบบผ่านตัวแยกประเภท
- กลับคำทำนาย
ลองโค้ดอัลกอริทึมนี้:
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
ที่นี่คุณจะเห็นว่า PredictDigitService
มีการพึ่งพาสองรายการ: ClassifierFactory
และ process_image

เราจะเริ่มต้นด้วยการสร้างคลาสเพื่อสร้างและฝึกโมเดลของเรา:
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
API พร้อมสำหรับการดำเนินการ ตอนนี้เราสามารถไปยังขั้นตอนการประมวลผลภาพได้
การประมวลผลภาพ
การประมวลผลภาพเป็นวิธีการดำเนินการบางอย่างกับรูปภาพเพื่อปรับปรุงหรือดึงข้อมูลที่เป็นประโยชน์ออกจากรูปภาพ ในกรณีของเรา เราจำเป็นต้องเปลี่ยนภาพที่ผู้ใช้วาดไปเป็นรูปแบบโมเดลแมชชีนเลิร์นนิงอย่างราบรื่น
มานำเข้าตัวช่วยเพื่อบรรลุเป้าหมายนั้นกันเถอะ:
import numpy as np from skimage import exposure import base64 from PIL import Image, ImageOps, ImageChops from io import BytesIO
เราสามารถแบ่งการเปลี่ยนแปลงออกเป็นหกส่วนที่แตกต่างกัน:
1. แทนที่พื้นหลังโปร่งใสด้วยสี
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. ตัดขอบที่เปิดอยู่
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. เพิ่มเส้นขอบที่มีขนาดเท่ากัน
def pad_image(image): return ImageOps.expand(image, border=30, fill='#fff')
4. แปลงภาพเป็นโหมดสีเทา
def to_grayscale(image): return image.convert('L')
5. สลับสี
def invert_colors(image): return ImageOps.invert(image)
6. ปรับขนาดภาพเป็นรูปแบบ 8x8
def resize_image(image): return image.resize((8, 8), Image.LINEAR)
ตอนนี้คุณสามารถทดสอบแอพ เรียกใช้แอปพลิเคชันและป้อนคำสั่งด้านล่างเพื่อส่งคำขอด้วยอิมเมจ iStock นี้ไปยัง 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)\"}" -i
คุณควรเห็นผลลัพธ์ต่อไปนี้:
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
ภาพตัวอย่างแสดงหมายเลข 8 และแอปของเราระบุเป็นตัวเลขดังกล่าวอย่างถูกต้อง
การสร้างบานหน้าต่างการวาดผ่าน React
ในการบูตแอปพลิเคชันฟรอนท์เอนด์อย่างรวดเร็ว เราจะใช้ต้นแบบของ CRA:
create-react-app frontend cd frontend
หลังจากตั้งค่าสถานที่ทำงานแล้ว เรายังต้องพึ่งพาการวาดตัวเลขด้วย แพ็คเกจ react-sketch ตรงกับความต้องการของเราอย่างสมบูรณ์:
npm i react-sketch
แอปพลิเคชันมีองค์ประกอบเดียวเท่านั้น เราสามารถแบ่งองค์ประกอบนี้ออกเป็นสองส่วน: ตรรกะ และ มุมมอง
ส่วนมุมมองมีหน้าที่ในการแสดงบานหน้าต่างรูปวาด ปุ่ม ส่ง และ รีเซ็ต เมื่อโต้ตอบกัน เราควรแสดงการคาดคะเนหรือข้อผิดพลาดด้วย จากมุมมองของตรรกะ มีหน้าที่ดังต่อไปนี้: ส่งภาพ และ ล้างร่าง
เมื่อใดก็ตามที่ผู้ใช้คลิก ส่ง ส่วนประกอบจะดึงรูปภาพออกจากส่วนประกอบแบบร่างและดึงดูดฟังก์ชัน makePrediction
ของโมดูล API หากคำขอไปยังส่วนหลังสำเร็จ เราจะตั้งค่าตัวแปรสถานะการคาดคะเน มิฉะนั้น เราจะอัปเดตสถานะข้อผิดพลาด
เมื่อผู้ใช้คลิกที่ รีเซ็ต ภาพสเก็ตช์จะล้าง:
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 }
ตรรกะก็เพียงพอแล้ว ตอนนี้เราสามารถเพิ่มอินเทอร์เฟซแบบภาพได้:
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;
คอมโพเนนต์พร้อมแล้ว ทดสอบโดยดำเนินการและไปที่ localhost:3000
หลังจาก:
npm run start
แอปพลิเคชันสาธิตมีให้ที่นี่ คุณยังสามารถเรียกดูซอร์สโค้ดบน GitHub
ห่อ
คุณภาพของตัวแยกประเภทนี้ไม่สมบูรณ์แบบและฉันไม่ได้แสร้งทำเป็นว่าเป็นเช่นนั้น ความแตกต่างระหว่างข้อมูลที่เราใช้สำหรับการฝึกอบรมกับข้อมูลที่มาจาก UI นั้นแตกต่างกันมาก อย่างไรก็ตาม เราได้สร้างแอปพลิเคชันที่ใช้งานได้ตั้งแต่เริ่มต้นภายในเวลาไม่ถึง 30 นาที
ในกระบวนการนี้ เราได้ฝึกฝนทักษะของเราในสี่สาขา:
- การเรียนรู้ของเครื่อง
- การพัฒนาส่วนหลัง
- การประมวลผลภาพ
- การพัฒนาส่วนหน้า
ไม่มีปัญหากรณีการใช้งานซอฟต์แวร์ที่สามารถจดจำตัวเลขที่เขียนด้วยลายมือได้ตั้งแต่ซอฟต์แวร์การศึกษาและการบริหารไปจนถึงบริการไปรษณีย์และการเงิน
ดังนั้น ฉันหวังว่าบทความนี้จะกระตุ้นให้คุณพัฒนาความสามารถในการเรียนรู้ของเครื่อง การประมวลผลภาพ การพัฒนาส่วนหน้าและส่วนหลัง และใช้ทักษะเหล่านั้นในการออกแบบแอปพลิเคชันที่ยอดเยี่ยมและมีประโยชน์
หากคุณต้องการขยายความรู้เกี่ยวกับแมชชีนเลิร์นนิงและการประมวลผลภาพ คุณอาจต้องการดูบทแนะนำ Adversarial Machine Learning ของเรา