זיהוי ספרות שכתב אדם באמצעות בינה מלאכותית

מחבר:
בתאריך:

הפרויקט הראשון שנהוג להתנסות עליו כשלומדים למידת מכונה (Machine learning) הוא של זיהוי ספרות שנכתבות בידי אדם. משימה לא פשוטה בכלל למחשב, כפי שמיד נראה (אחרי כל ההשקעה, הגעתי ל-10 זיהויים נכונים מתוך 12. עבור המודל הפשוט מאוד שאני מציג במאמר. מודלים יותר מורכבים נתנו לי איכות זיהוי קרובה ל-100% פיתוח אפליקציה לזיהוי ספרות שנכתבו על ידי אנשים מבוססת מודל CNN.)

האפליקציה שנפתח במדריך מאפשרת לאדם לכתוב ספרות בכתב ידו על אלמנט canvas, כשמודל מבוסס בינה מלאכותית מפענח את הכתוב ומציג את ההערכה שלו לגבי מה שכתב המשתמש.

זיהוי ספרות כתובות ביד באמצעות בינה מלאכותית

בחלק הראשון של המדריך נסביר כיצד לפתח את המודל, ובחלק השני כיצד לפתח את האפליקציה האינטרנטית שמשתמשת במודל כדי לנחש אילו ספרות כתב המשתמש.

 

פיתוח המודול

את המודל אימצתי מתוך מאמר מצוין Handwritten Digit Recognition using Convolutional Neural Networks in Python with Keras. זהו המודל הפשוט ביותר מתוך שלושה שמוסברים במאמר.

המודל נכתב באמצעות ספריית Keras שה-API שהיא מספקת הוא המובן ביותר בתחום של פיתוח רשתות נאורניות.

הדוגמאות שמשמשות לפיתוח המודל מגיעות מסט נתונים MNIST, שכולל 70,000 תמונות של ספרות שאנשים כתבו. גודל כל תמונה 28 על 28 פיקסלים.

את הקוד הרצתי על סביבת colab שמאפשרת לפתח קוד בסביבת Jupyter notebook על השרתים של Google. אתם יכולים לראות את העתק המחברת שבאמצעותה פיתחתי את הקוד ב-GitHub. באותו מאגר ריכזתי גם יתר הקבצים של הפרוייקט.

שימו לב! מזינים למודל תמונות פרוסות לאורך של 784 פיקסלים במקום התמונות המקוריות של 28 פיקסלים על 28 פיקסלים. זה מוריד מדיוק המודל, ומקצר את משך הריצה של האימון. בהמשך, כשנרצה להעריך תמונות שמשתמשים מזינים לאפליקציה נצטרך לפרוס גם אותם באותו אופן לפני שאנחנו שולחים אותם להערכת המודל.

בסוף תהליך הפיתוח הורדתי שני קבצים:

  • model.json
  • group1-shard1of1

שבהם אשתמש בשביל ה-api של האפליקציה, וגם אותם תוכלו למצוא במאגר ב-Github.

את הקבצים המרתי ממודל של Keras לפורמט שיכול לשמש api של אפליקציות באמצעות ספריית tensorflow.js.

 

פיתוח האפליקציה

האפליקציה כוללת קובץ index.html ואת תיקיית ה-api.

ה-html כולל את הסקריפט של ספריית tensorflow.js שהממשק שלה מאפשר לנו להריץ את המודל בדפדפן.

<!DOCTYPE html>
<html lang="en">
<head>
<title>CSS Template</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" rel="stylesheet">
<style>
.row.main > .col-md-6{border:1px solid #efefef;min-height:400px;padding:20px;}
#canvas{border:3px solid #efefef;width:280px;height:280px;background-color:#000;}
#prediction{width:280px;height:280px;text-align:center;font-size:230px;}
</style>
</head>
<body>

<div class="container">
  <p> </p>
  <div class="row main">
    <div class="col-md-6">
      <h2>Drawing</h2>
	  <canvas id="canvas" width="280" height="280"></canvas>
      <button class="btn btn-default" id="predictBtn">Predict</button>
      <button class="btn btn-default" id="clearBtn">Clear
    </div>
    
    <div class="col-md-6">
      <h2>Prediction</h2>
      <div id="prediction"></div>
    </div>
  </div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/tensorflow/0.13.0/tf.min.js"> </script>

ה-html כולל אלמנט canvas שעליו המשתמש כותב את הספרות, ואלמנט prediction שבו מופיעה הניחוש של המחשב.

הכפתור predict מפעיל את הקוד ששולח את הספרות להערכת המודל.

הכפתור clear מנקה את ה-canvas.

מאוד חשוב שה-canvas יהיה שחור והכיתוב בלבן כי אילו הצבעים של התמונות שעליהם אמנו את המודל.

 

ה-javascript

// API url
var model;
var mnistModelUrl = '/api/model.json';

var canvasSize = 280;

// Find and define the canvas element.
var canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
context.lineWidth   = 20;
context.strokeStyle = '#fff';

// train images size
var pixSize = 28;

// action buttons
var predictBtn = document.querySelector('#predictBtn');
var clearBtn   = document.querySelector('#clearBtn');
 
// Pencil tool instance.
tool = new tool_pencil();

// Attach the mouse events to functions.
canvas.addEventListener('mousedown', ev_canvas, false);
canvas.addEventListener('mousemove', ev_canvas, false);
canvas.addEventListener('mouseup',   ev_canvas, false);


// The painting tool works like a drawing pencil which tracks the mouse movements.
function tool_pencil () {
    var tool = this;
    this.started = false;

    this.mousedown = function (ev) {
        context.beginPath();
        context.moveTo(ev._x, ev._y);
        tool.started = true;
    };

    this.mousemove = function (ev) {
      if (tool.started) {
        context.lineTo(ev._x, ev._y);
        context.stroke();
      }
    };

    this.mouseup = function (ev) {
      if (tool.started) {
        tool.mousemove(ev);
        tool.started = false;
      }
    };
}

// The general-purpose event handler determines the mouse 
// position relative to the canvas element.
function ev_canvas (ev) {
    if (ev.layerX || ev.layerX == 0) { // Firefox
      ev._x = ev.layerX;
      ev._y = ev.layerY;
    } else if (ev.offsetX || ev.offsetX == 0) { // Opera
      ev._x = ev.offsetX;
      ev._y = ev.offsetY;
    }

    // Call the event handler of the tool.
    var func = tool[ev.type];
    if (func) {
      func(ev);
    }
}
  
clearBtn.addEventListener('click',function(){
	context.clearRect(0, 0, canvas.width, canvas.height);
	document.querySelector('#prediction').innerHTML = '';
},false);

function getImageDataAndScale(canvas, outputSize) {
	var scaled = document.createElement("canvas");
	var scaledCtx = scaled.getContext('2d');
	scaled.width = outputSize.width;
	scaled.height = outputSize.height;
	scaledCtx.drawImage(canvas, 0, 0, outputSize.width, outputSize.height);
	return scaledCtx.getImageData(0, 0, outputSize.width, outputSize.height);
}

// get the model from the api.
(async function getModel() {
    return model = await tf.loadModel(mnistModelUrl);
}());

function predict(imageData) {
    //convert to tensor
    var tfImg = tf.fromPixels(imageData, 1);
      
    //Resize the tensor to 28*28 like the train image set
    var smallImg = tf.image.resizeBilinear(tfImg, [pixSize, pixSize]);

	// Shape the image according to the model's batch_input_shape
    img = smallImg.reshape([1, pixSize*pixSize]);
    img = tf.cast(img, 'float32');
	
	// gray scale image so we use only one channel
    tensor = img.div(tf.scalar(255));
    var prediction = model.predict(img);
    var predictedVals = prediction.dataSync();

	// of the 10 classes the one that has more than .5 probability is the prediction
    for(var x=0;x<10;x++){
        if(predictedVals[x] > 0.5) return x;
    }
	
    return 'unknown';               
}

function makePrediction() {
    var image = getImageDataAndScale(canvas, { width: canvasSize, height: canvasSize });
    var predicted = predict(image);
    console.log(predicted);

    document.querySelector('#prediction').innerHTML = predicted;
}

predictBtn.addEventListener('click',makePrediction,false);

מספר פונקציות בקוד מטפלות בציור על ה-canvas באמצעות העכבר.

הפונקציה getModel טוענת את המודל מה-api.

בתגובה ללחיצה על כפתור prediction מופעלת הפונקציה makePrediction שמעבירה את אלמנט ה-canvas להערכה של הפונקציה predict.

הפונקציה predict:

  • ממירה את המידע שמתקבל מה-canvas ל-tensor (מערך רב-ממדי) באמצעות המתודה fromPixels של tensorflow
  • משנה את קנה המידה של התמונה ל-28 על 28 פיקסלים כדי שתתאים לסט התמונות ששימשו לאימון באמצעות המתודה resizeBilinear
  • מתאימה את המודל ל-input הצפוי של הרשת הנוירונית שהוא 1 כי אנחנו מזינים דוגמה אחת בכל פעם ו-784 כי אנחנו פורסים את התמונות ל-784 פיקסלים (28*28)
  • התאמה לסקלת צבעים אפורה נעשית באמצעות scalar שהערך שלו 255 (הסקלה היא באפור ולכך מתאים סקלר בעל ממד אחד במקום הסקלה המלאה של 3 ערוצים rgb)
  • המתודה dataSync ממצה את המידע למערך, ולולאה שרצה בהמשך מחזירה את הספרה שסיכוייה הגבוהים ביותר. הלולאה רצה עד 10 פעמים כי יש 10 תוצאות אפשריות (10 ספרות), ומחזירה את הפריט שערכו גבוה מ-0.5 (הערך המצטבר של ההסתברויות הוא 1 עבור סט התוצאות האפשריות, ואם אחת האפשרויות מקבלת יותר מ-0.5 היא חייבת להיות בעלת הסיכויים הגבוהים ביותר).

לכל המדריכים בנושא של למידת מכונה

 

אהבתם? לא אהבתם? דרגו!

0 הצבעות, ממוצע 0 מתוך 5 כוכבים

 

הוסף תגובה חדשה

 

= 6 + 3