التعرف على رقم التعلم الآلي - من الصفر إلى التطبيق

نشرت: 2022-03-11

يعد التعلم الآلي ورؤية الكمبيوتر وبناء واجهات برمجة تطبيقات قوية وإنشاء واجهات مستخدم جميلة مجالات مثيرة تشهد الكثير من الابتكار.

يتطلب الأولان رياضيات وعلومًا مكثفة ، بينما يركز تطوير API و UI على التفكير الحسابي وتصميم البنى التحتية المرنة. إنهما مختلفان تمامًا ، لذا فإن تحديد الشخص الذي تريد أن تتعلمه بعد ذلك قد يكون أمرًا صعبًا. الغرض من هذه المقالة هو توضيح كيفية استخدام الأربعة في إنشاء تطبيق معالجة الصور.

التطبيق الذي سنقوم ببنائه هو أداة التعرف على الأرقام البسيطة. أنت ترسم ، الآلة تتوقع الرقم. البساطة ضرورية لأنها تتيح لنا رؤية الصورة الكبيرة بدلاً من التركيز على التفاصيل.

من أجل البساطة ، سنستخدم أكثر التقنيات شيوعًا وسهولة في التعلم. سيستخدم جزء التعلم الآلي Python لتطبيق النهاية. بالنسبة للجانب التفاعلي للتطبيق ، سنعمل عبر مكتبة JavaScript لا تحتاج إلى مقدمة: React.

تعلم الآلة لتخمين الأرقام

الجزء الأساسي من تطبيقنا هو خوارزمية تخمين الرقم المرسوم. سيكون التعلم الآلي هو الأداة المستخدمة لتحقيق جودة تخمين جيدة. يسمح هذا النوع من الذكاء الاصطناعي الأساسي للنظام بالتعلم تلقائيًا بكمية معينة من البيانات. بشكل أوسع ، التعلم الآلي هو عملية إيجاد مصادفة أو مجموعة من المصادفات في البيانات للاعتماد عليها لتخمين النتيجة.

تتضمن عملية التعرف على الصور لدينا ثلاث خطوات:

  • احصل على صور للأرقام المرسومة للتدريب
  • تدريب النظام على تخمين الأرقام من خلال بيانات التدريب
  • اختبر النظام ببيانات جديدة / غير معروفة

بيئة

سنحتاج إلى بيئة افتراضية للعمل مع التعلم الآلي في بايثون. هذا النهج عملي لأنه يدير جميع حزم Python المطلوبة ، لذلك لا داعي للقلق بشأنها.

دعنا نثبته بأوامر المحطة التالية:

 python3 -m venv virtualenv source virtualenv/bin/activate

نموذج التدريب

قبل أن نبدأ في كتابة الكود ، نحتاج إلى اختيار "مدرس" مناسب لآلاتنا. عادةً ما يجرب متخصصو علوم البيانات نماذج مختلفة قبل اختيار أفضلها. سنتخطى النماذج المتقدمة جدًا التي تتطلب الكثير من المهارة والمضي قدمًا في خوارزمية k- الأقرب.

إنها خوارزمية تحصل على بعض عينات البيانات وترتبها على مستوى مرتبة حسب مجموعة معينة من الخصائص. لفهمها بشكل أفضل ، دعنا نراجع الصورة التالية:

الصورة: عينات بيانات التعلم الآلي مرتبة على متن طائرة

لاكتشاف نوع النقطة الخضراء ، يجب أن نتحقق من أنواع k أقرب الجيران حيث k هي مجموعة الوسيطة. بالنظر إلى الصورة أعلاه ، إذا كانت k تساوي 1 أو 2 أو 3 أو 4 ، فسيكون التخمين مثلثًا أسود لأن معظم جيران k الأقرب للنقطة الخضراء هم مثلثات سوداء. إذا قمنا بزيادة k إلى 5 ، فإن غالبية الكائنات تكون مربعات زرقاء ، ومن ثم سيكون التخمين مربعًا أزرق .

هناك بعض التبعيات اللازمة لإنشاء نموذج التعلم الآلي الخاص بنا:

  • sklearn.neighbours.KNeighboursClassifier هو المصنف الذي سنستخدمه.
  • 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 مختلفة.

كما ترى ، تضمن قيمة 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')

سيتم تهيئة آلية الإعدادات - الحصول على المصنف - في الحزمة التالية في قائمتنا ، الريبو . إنها فئة ذات طريقتين لاسترداد وتحديث المصنف المدرب باستخدام وحدة 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_)

نحن على وشك الانتهاء من واجهة برمجة التطبيقات الخاصة بنا. الآن تفتقر فقط إلى وحدة الخدمة . ما هو الغرض؟

  • احصل على المصنف المدرب من التخزين
  • قم بتحويل الصورة التي تم تمريرها من واجهة المستخدم إلى تنسيق يفهمه المصنف
  • احسب التنبؤ بالصورة المنسقة عبر المصنف
  • إعادة التنبؤ

لنقم بترميز هذه الخوارزمية:

 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

الصورة: تغيير حجم الصورة النموذجية إلى تنسيق 8x8.

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

الآن يمكنك اختبار التطبيق. قم بتشغيل التطبيق وأدخل الأمر أدناه لإرسال طلب مع صورة iStock هذه إلى واجهة برمجة التطبيقات:

الصورة: ألبوم الصور رقم ثمانية مرسومة باليد.

 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 boilerplate:

 create-react-app frontend cd frontend

بعد إعداد مكان العمل ، نحتاج أيضًا إلى التبعية لرسم الأرقام. تتوافق حزمة رسم التفاعل مع احتياجاتنا تمامًا:

 npm i react-sketch

التطبيق يحتوي على مكون واحد فقط. يمكننا تقسيم هذا المكون إلى جزأين: المنطق والعرض .

جزء العرض مسؤول عن تمثيل جزء الرسم وأزرار الإرسال وإعادة التعيين . عند التفاعل ، يجب أن نمثل أيضًا تنبؤًا أو خطأً. من منظور المنطق ، لديها الواجبات التالية: إرسال الصور ومسح الرسم التخطيطي .

عندما ينقر المستخدم على إرسال ، سيستخرج المكوِّن الصورة من مكون الرسم ويستأنف وظيفة 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

التطبيق التجريبي متاح هنا. يمكنك أيضًا تصفح الكود المصدري على جيثب.

تغليف

جودة هذا المصنف ليست مثالية ، وأنا لا أدعي أنها كذلك. الفرق بين البيانات التي استخدمناها للتدريب والبيانات الواردة من واجهة المستخدم هائل. على الرغم من ذلك ، أنشأنا تطبيقًا عمليًا من البداية في أقل من 30 دقيقة.

صورة: رسم متحرك يوضح التطبيق النهائي يحدد الأرقام المكتوبة بخط اليد.

في هذه العملية ، صقلنا مهاراتنا في أربعة مجالات:

  • التعلم الالي
  • تطوير الخلفية
  • معالجة الصورة
  • تطوير الواجهة الأمامية

لا يوجد نقص في حالات الاستخدام المحتملة للبرامج القادرة على التعرف على الأرقام المكتوبة بخط اليد ، بدءًا من البرامج التعليمية والإدارية إلى الخدمات البريدية والمالية.

لذلك ، آمل أن تحفزك هذه المقالة على تحسين قدراتك على التعلم الآلي ، ومعالجة الصور ، وتطوير الواجهة الأمامية والخلفية ، واستخدام تلك المهارات لتصميم تطبيقات رائعة ومفيدة.

إذا كنت ترغب في توسيع نطاق معرفتك بالتعلم الآلي ومعالجة الصور ، فقد ترغب في مراجعة البرنامج التعليمي الخاص بنا للتعلم الآلي.