機械学習の数値認識-ゼロからアプリケーションまで

公開: 2022-03-11

機械学習、コンピュータービジョン、強力なAPIの構築、美しいUIの作成は、多くの革新を目の当たりにする刺激的な分野です。

最初の2つは、広範な数学と科学を必要としますが、APIとUIの開発は、アルゴリズム的思考と柔軟なアーキテクチャの設計に重点を置いています。 それらは非常に異なっているので、次に学びたいものを決めるのは難しいかもしれません。 この記事の目的は、画像処理アプリケーションの作成に4つすべてを使用する方法を示すことです。

これから作成するアプリケーションは、単純な数字認識機能です。 あなたが描くと、機械が数字を予測します。 シンプルさは、細部に焦点を合わせるのではなく、全体像を見ることができるため、不可欠です。

簡単にするために、最も人気があり、習得しやすいテクノロジーを使用します。 機械学習の部分では、バックエンドアプリケーションにPythonを使用します。 アプリの相互作用の側面については、紹介を必要としないJavaScriptライブラリを介して操作します:React。

数字を推測する機械学習

私たちのアプリの中核部分は、描かれた数字を推測するアルゴリズムです。 機械学習は、優れた推測品質を実現するために使用されるツールになります。 この種の基本的な人工知能により、システムは特定の量のデータを使用して自動的に学習できます。 広義には、機械学習は、データ内の一致または一致のセットを見つけて、結果を推測するためにそれらに依存するプロセスです。

画像認識プロセスには、次の3つのステップが含まれます。

  • トレーニング用に描かれた数字の画像を取得する
  • トレーニングデータを介して数値を推測するようにシステムをトレーニングします
  • 新しい/未知のデータでシステムをテストする

環境

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()

データがフェッチされて準備ができたら、データをトレーニングテストの2つの部分に分割する次のステップに進むことができます。

データの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値でアルゴリズムの精度をテストするために使用されるプロット。

ご覧のとおり、 k値3は、モデルとデータセットの最高の精度を保証します。

Flaskを使用してAPIを構築する

これで、画像から数字を予測するアルゴリズムであるアプリケーションコアの準備が整いました。 次に、アルゴリズムをAPIレイヤーで装飾して、使用できるようにする必要があります。 人気のあるFlaskWebフレームワークを使用して、これをクリーンかつ簡潔に実行しましょう。

まず、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)

PredictDigitViewIndexViewが定義されていないというエラーが表示されます。 次のステップは、これらのビューを初期化するファイルを作成することです。

 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)

繰り返しになりますが、未解決のインポートに関するエラーが発生します。 ビューパッケージは、まだ持っていない3つのファイルに依存しています。

  • 設定
  • レポ
  • サービス

それらを1つずつ実装します。

設定は、構成と定数変数を備えたモジュールです。 シリアル化された分類子へのパスが保存されます。 それは論理的な質問をします:なぜ分類子を保存する必要があるのですか?

これは、アプリのパフォーマンスを向上させる簡単な方法だからです。 リクエストを受け取るたびに分類器をトレーニングする代わりに、分類器の準備されたバージョンを保存して、箱から出してすぐに機能できるようにします。

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

設定のメカニズム(分類子の取得)は、リストの次のパッケージであるリポジトリで初期化されます。 これは、Pythonの組み込みpickleモジュールを使用して、トレーニングされた分類子を取得および更新するための2つのメソッドを持つクラスです。

 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にはClassifierFactoryprocess_imageの2つの依存関係があることがわかります。

モデルを作成してトレーニングするためのクラスを作成することから始めます。

 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はすぐに使用できます。 これで、画像処理ステップに進むことができます。

画像処理

画像処理は、画像に対して特定の操作を実行して、画像を強調したり、画像からいくつかの有用な情報を抽出したりする方法です。 この場合、ユーザーが描いた画像を機械学習モデル形式にスムーズに移行する必要があります。

Image alt:描画された画像を機械学習形式に変換します。

その目標を達成するために、いくつかのヘルパーをインポートしてみましょう。

 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形式に変更します

画像:サンプル画像のサイズを8x8形式に変更します。

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

これで、アプリをテストできます。 アプリケーションを実行し、以下のコマンドを入力して、このiStockイメージを含むリクエストをAPIに送信します。

画像:手描きの8番のストック画像。

 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

アプリケーションにはコンポーネントが1つだけあります。 このコンポーネントは、ロジックとビューの2つの部分に分けることができます。

ビューパーツは、描画ペイン、[送信]および[リセット]ボタンを表す役割を果たします。 相互作用するときは、予測またはエラーも表す必要があります。 ロジックの観点からは、次の役割があります。画像を送信し、スケッチをクリアします。

ユーザーが[送信]をクリックするたびに、コンポーネントはスケッチコンポーネントから画像を抽出し、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分以内に最初から動作するアプリケーションを作成しました。

画像:手書きの数字を識別する完成したアプリを示すアニメーション。

その過程で、4つの分野でスキルを磨きました。

  • 機械学習
  • バックエンド開発
  • 画像処理
  • フロントエンド開発

教育および管理ソフトウェアから郵便および金融サービスに至るまで、手書き数字を認識できるソフトウェアの潜在的なユースケースに不足はありません。

したがって、この記事が、機械学習能力、画像処理、フロントエンドとバックエンドの開発を改善し、それらのスキルを使用してすばらしい便利なアプリケーションを設計する動機付けになることを願っています。

機械学習と画像処理の知識を広げたい場合は、敵対的機械学習チュートリアルをご覧ください。