기계 학습 번호 인식 - 0에서 응용까지
게시 됨: 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로 늘리면 대부분의 객체는 파란색 사각형이므로 추측은 파란색 사각형이 됩니다.
기계 학습 모델을 만드는 데 필요한 몇 가지 종속성이 있습니다.
- 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 패키지는 아직 가지고 있지 않은 세 개의 파일에 의존합니다:
- 설정
- 레포
- 서비스
우리는 그것들을 하나씩 구현할 것입니다.
설정 은 구성 및 상수 변수가 있는 모듈입니다. 직렬화된 분류기의 경로를 저장합니다. 논리적인 질문 이 필요합니다. 분류자를 저장해야 하는 이유는 무엇입니까?
앱의 성능을 향상시키는 간단한 방법이기 때문입니다. 요청을 받을 때마다 분류기를 훈련시키는 대신 분류기의 준비된 버전을 저장하여 즉시 사용할 수 있도록 합니다.
import os BASE_DIR = os.getcwd() CLASSIFIER_STORAGE = os.path.join(BASE_DIR, 'storage/classifier.txt')
분류기를 가져오는 설정 메커니즘은 목록의 다음 패키지인 Repo 에서 초기화됩니다. Python의 내장 pickle
모듈을 사용하여 훈련된 분류기를 검색하고 업데이트하는 두 가지 방법이 있는 클래스입니다.
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
전환을 6개의 개별 부분으로 나눌 수 있습니다.
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을 묘사했으며 우리 앱은 숫자 8을 정확하게 식별했습니다.
React를 통해 그리기 창 만들기
프론트엔드 애플리케이션을 빠르게 부트스트랩하기 위해 CRA 상용구를 사용합니다.
create-react-app frontend cd frontend
작업 공간을 설정한 후에는 숫자를 그리기 위한 종속성도 필요합니다. react-sketch 패키지는 우리의 요구 사항과 완벽하게 일치합니다.
npm i react-sketch
응용 프로그램에는 하나의 구성 요소만 있습니다. 이 구성 요소를 logic 과 view 의 두 부분으로 나눌 수 있습니다.
보기 부분은 그리기 창, 제출 및 재설정 버튼을 나타내는 역할을 합니다. 상호 작용할 때 예측 또는 오류도 나타내야 합니다. 논리 관점에서 볼 때 다음과 같은 임무가 있습니다. 이미지 제출 및 스케치 지우기 .
사용자가 제출 을 클릭할 때마다 구성 요소는 스케치 구성 요소에서 이미지를 추출하고 API 모듈의 makePrediction
기능에 호소합니다. 백엔드에 대한 요청이 성공하면 예측 상태 변수를 설정합니다. 그렇지 않으면 오류 상태를 업데이트합니다.
사용자가 재설정 을 클릭하면 스케치가 지워집니다.
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분 이내에 처음부터 작동하는 애플리케이션을 만들었습니다.
그 과정에서 우리는 네 가지 분야에서 기술을 연마했습니다.
- 머신 러닝
- 백엔드 개발
- 이미지 처리
- 프론트엔드 개발
교육 및 관리 소프트웨어에서 우편 및 금융 서비스에 이르기까지 손으로 쓴 숫자를 인식할 수 있는 소프트웨어의 잠재적 사용 사례는 부족하지 않습니다.
따라서 이 기사가 기계 학습 능력, 이미지 처리, 프론트 엔드 및 백엔드 개발을 향상시키고 이러한 기술을 사용하여 훌륭하고 유용한 응용 프로그램을 설계하는 데 동기를 부여하기를 바랍니다.
기계 학습 및 이미지 처리에 대한 지식을 넓히려면 적대적 기계 학습 자습서를 확인하는 것이 좋습니다.