تبدیل مدل یادگیری ماشین به برنامه وب — راهنمای کاربردی
در این مطلب، روش تبدیل مدل یادگیری ماشین به برنامه وب همراه با ارائه یک مثال کامل و کاربردی و کدهای پیادهسازی آن، آموزش داده شده است. زیبایی «تنسورفلو دات جیاس» (TensorFlow.js) در آن است که کاربر میتواند یک مدل «یادگیری ماشین» (Machine Learning) را در «زبان برنامهنویسی پایتون» (Python Programming Language) با استفاده از «کرس» (Keras) یا «تنسورفلو» (Tensorflow) آموزش دهد و آن را با استفاده از TensorFlow.js در مرورگر وب مستقر کند. کاربر برای این منظور نیازی به اجرای یک سرویس خارجی برای اجرای کوئریها ندارد.
تبدیل مدل یادگیری ماشین به برنامه وب
برنامه کاربردی جالب و سادهای که در این مطلب آموزش داده میشود، به کاربر اجازه ترسیم یک رقم در مرورگر را - به کمک موس - میدهد و سپس، با استفاده از یک مدل یادگیری ماشین ساده، تشخیص میدهد که عدد نوشته شده چیست. کارهای لازم برای ساخت این دمو، شامل موارد زیر است.
شایان توجه است که سایر کدهای این پروژه از اینجا [+] در دسترس است.
- دادههای مورد نیاز برای آموزش مدل
- محیط آموزش
- پیشپردازش دادهها
- یادگیری ماشین
- تبدیل یک مدل کرس به Tensorflow.js
- کنواس اچتیامال۵
- یکپارچهسازی
تصویری از این برنامه در مرورگر وب، در زیر آورده شده است.
دادههای مورد نیاز برای آموزش مدل
هر مدل یادگیری ماشین، نیازمند دادههای با کیفیت (برای آموزش دیدن) است.
در این مطلب، از مجموعه داده MNIST استفاده شده که مجموعه دادهای از اعداد دست نویس است.
در این مجموعه داده، ۶۰,۰۰۰ تصویر وجود دارد که همه در ابعاد ، به صورت سیاه و سفید و با مقادیر پیکسل از ۰ تا ۲۵۵ هستند. قطعه کد زیر، برای بارگذاری و مشاهده مجموعه داده MNIST است.
1%tensorflow_version 2.x
2from tensorflow.keras.datasets import mnist
3import matplotlib.pyplot as plt
1(X_train, y_train), (X_test, y_test) = mnist.load_data()
2print ("X_train.shape: {}".format(X_train.shape))
3print ("y_train.shape: {}".format(y_train.shape))
4print ("X_test.shape: {}".format(X_test.shape))
5print ("y_test.shape: {}".format(y_test.shape))
خروجی قطعه کدهای بالا به صورت زیر است.
X_train.shape: (60000, 28, 28) y_train.shape: (60000,) X_test.shape: (10000, 28, 28) y_test.shape: (10000,)
1plt.subplot(161)
2plt.imshow(X_train[3], cmap=plt.get_cmap('gray'))
3plt.subplot(162)
4plt.imshow(X_train[5], cmap=plt.get_cmap('gray'))
5plt.subplot(163)
6plt.imshow(X_train[7], cmap=plt.get_cmap('gray'))
7plt.subplot(164)
8plt.imshow(X_train[2], cmap=plt.get_cmap('gray'))
9plt.subplot(165)
10plt.imshow(X_train[0], cmap=plt.get_cmap('gray'))
11plt.subplot(166)
12plt.imshow(X_train[13], cmap=plt.get_cmap('gray'))
13
14plt.show()
خروجی قطعه کد بالا، به صورت زیر است.
محیط آموزش
«گوگل کُلَب» (Google Colab) به کاربران این امکان را میدهد که کدهای پایتون را در مرورگر بنویسند و اجرا کنند. Colab یک پلتفرم پایتون «ژوپیتر نوتبوک» (Jupyter Notebook) است که از پیش، با بسیاری از کتابخانههای یادگیری ماشین مورد نیاز کاربر، بارگذاری شده است. این گزینه روشی بیدردسر برای اجرای سریع پروژههای یادگیری ماشین است. شایان توجه است که گوگل کلب در حال حاضر دسترسی رایگان به GPU/CPU را نیز برای کاربر فراهم میکند.
پیشپردازش دادهها
پیشپردازشهای اندکی باید روی مجموعه داده MNIST انجام شود که در ادامه بیان شدهاند.
- نرمالسازی ورودیها؛ دادهها با مقادیری از ۰ تا ۲۵۵ ارائه میشوند و باید آنها را به مقیاسی از ۰ تا ۱ نرمال کرد.
- «کدبندی وان هات» (One-Hot Encoding) خروجیها.
# Normalize Inputs from 0–255 to 0–1 x_train = x_train / 255 x_test = x_test / 255 # One-Hot Encode outputs y_train = np_utils.to_categorical(y_train) y_test = np_utils.to_categorical(y_test) num_classes = 10
یادگیری ماشین
در نهایت، کاربر میتواند فعالیتهای مربوط به ساخت و پیادهسازی یادگیری ماشین را انجام دهد. کار با یک مدل بسیار ساده آغاز میشود. در این راستا، از یک «شبکه عصبی» (Neural Network) ساده با تنها یک لایه پنهان استفاده شده است.
این مدل ساده برای گرفتن خروجی با صحت ٪۹۸ کافی است.
1x_train_simple = x_train.reshape(60000, 28 * 28).astype(‘float32’)
2x_test_simple = x_test.reshape(10000, 28 * 28).astype(‘float32’)
3model = Sequential()
4model.add(Dense(28 * 28, input_dim=28 * 28, activation=’relu’))
5model.add(Dense(num_classes, activation=’softmax’))
6model.compile(loss=’categorical_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])
7model.fit(x_train_simple, y_train, validation_data=(x_test_simple, y_test), epochs=30, batch_size=200, verbose=2)
میتوان با استفاده از یک مدل «یادگیری عمیق» (Deep Learning) صحت را تا ٪۹۹ افزایش داد.
1x_train_deep_model = x_train.reshape((60000, 28, 28, 1)).astype(‘float32’)
2x_test_deep_model = x_test.reshape((10000, 28, 28, 1)).astype(‘float32’)
3deep_model = Sequential()
4deep_model.add(Conv2D(30, (5, 5), input_shape=(28, 28, 1), activation=’relu’))
5deep_model.add(MaxPooling2D())
6deep_model.add(Conv2D(15, (3, 3), activation=’relu’))
7deep_model.add(MaxPooling2D())
8deep_model.add(Dropout(0.2))
9deep_model.add(Flatten())
10deep_model.add(Dense(128, activation=’relu’))
11deep_model.add(Dense(50, activation=’relu’))
12deep_model.add(Dense(num_classes, activation=’softmax’))
13deep_model.compile(loss=’categorical_crossentropy’, optimizer=’adam’, metrics=[‘accuracy’])
14deep_model.fit(x_train_deep_model, y_train, validation_data=(x_test_deep_model, y_test), epochs=30, batch_size=200, verbose=2)
البته به طور کلی میتوان گفت که مدل ارائه شده در اینجا بهترین خروجی ممکن را ندارد و میتوان از مدلهای جایگزین بهتری استفاده کرد.
تبدیل مدل Keras به Tensorflow.js
اکنون که مدل آموزش داده شد، باید به منظور استفاده از آن با Tensorflow.js، آن را تبدیل کرد. ابتدا باید مدل را در یک مدل HDF5 ذخیره کرد.
1model.save(“model.h5”)
پس از آن، کاربر میتواند به فایلهای ذخیره شده، با کلیک روی آیکون پوشه در قاب سمت چپ، دسترسی داشته باشد.
راههایی برای تبدیل کردن مدل وجود دارد. یک راهکار ساده در نوتبوک، استفاده از دستور زیر است.
1!pip install tensorflowjs
2!tensorflowjs_converter --input_format keras ‘/content/model.h5’ ‘/content/model’
content/model.h5/ ورودی و خروجی ذخیره شده در پوشه content/model/ است.
TensorFlow.js باید به فایل JSON (یعنی model.json) اشاره کند و نیاز به یک فایل همنیا به نام «group1-shard1of1.bin» نیاز دارد. به هر دو این فایلها نیاز است، بنابراین باید هر دو آنها را دانلود کرد.
کنواس اچتیامال۵
در ادامه، یک صفحه HTML ساده که از مولفه کنواس اچتیامال۵ استفاده میکند باید ساخته شود. کد این صفحه HTML در ادامه آمده است.
1<!doctype html>
2<html lang="en">
3 <head>
4 <!-- Global site tag (gtag.js) - Google Analytics -->
5 <script async src="https://www.googletagmanager.com/gtag/js?id=G-H0NW5Z2MYC"></script>
6 <script>
7 window.dataLayer = window.dataLayer || [];
8 function gtag(){dataLayer.push(arguments);}
9 gtag('js', new Date());
10
11 gtag('config', 'G-H0NW5Z2MYC');
12 </script>
13 <title>Digit Recognition WebApp</title>
14 <meta name="description" content="Simple Machine Learning Model into an WebApp using TensorFlow.js">
15 <meta name="keywords" content="Machine Learning, TensorFlow.js">
16 <meta name="author" content="Carlos Aguayo">
17 <style>
18 body {
19 touch-action: none; /*https://developer.mozilla.org/en-US/docs/Web/CSS/touch-action*/
20 font-family: "Roboto";
21 }
22 h1 {
23 margin: 50px;
24 font-size: 70px;
25 text-align: center;
26 }
27 #paint {
28 border:3px solid red;
29 margin: auto;
30 }
31 #predicted {
32 font-size: 60px;
33 margin-top: 60px;
34 text-align: center;
35 }
36 #number {
37 border: 3px solid black;
38 margin: auto;
39 margin-top: 30px;
40 text-align: center;
41 vertical-align: middle;
42 }
43 #clear {
44 margin: auto;
45 margin-top: 70px;
46 padding: 30px;
47 text-align: center;
48 }
49 </style>
50 </head>
51 <body>
52 <!--<script type="text/javascript" src="https://livejs.com/live.js"></script>-->
53 <script src="https://code.jquery.com/jquery-2.2.4.min.js"></script>
54 <script src="https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.5.2/dist/tf.min.js"></script>
55 <h1>Digit Recognition WebApp</h1>
56 <div id="paint">
57 <canvas id="myCanvas"></canvas>
58 </div>
59 <div id="predicted">
60 Recognized digit
61 <div id="number"></div>
62 <button id="clear">Clear</button>
63 </div>
64 <script>
65 var isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
66 if (isMobile) {
67 $('#paint').css({'width': '60%'});
68 $('#number').css({'width': '30%', 'font-size': '240px'});
69 $('#clear').css({'font-size': '50px'});
70 } else {
71 $('#paint').css({'width': '300px'});
72 $('#number').css({'width': '150px', 'font-size': '120px'});
73 $('#clear').css({'font-size': '35px'});
74 }
75
76 var cw = $('#paint').width();
77 $('#paint').css({'height': cw + 'px'});
78
79 cw = $('#number').width();
80 $('#number').css({'height': cw + 'px'});
81
82 // From https://www.html5canvastutorials.com/labs/html5-canvas-paint-application/
83 var canvas = document.getElementById('myCanvas');
84 var context = canvas.getContext('2d');
85
86 var compuetedStyle = getComputedStyle(document.getElementById('paint'));
87 canvas.width = parseInt(compuetedStyle.getPropertyValue('width'));
88 canvas.height = parseInt(compuetedStyle.getPropertyValue('height'));
89
90 var mouse = {x: 0, y: 0};
91
92 canvas.addEventListener('mousemove', function(e) {
93 mouse.x = e.pageX - this.offsetLeft;
94 mouse.y = e.pageY - this.offsetTop;
95 }, false);
96
97 context.lineWidth = isMobile ? 60 : 25;
98 context.lineJoin = 'round';
99 context.lineCap = 'round';
100 context.strokeStyle = '#0000FF';
101
102 canvas.addEventListener('mousedown', function(e) {
103 context.moveTo(mouse.x, mouse.y);
104 context.beginPath();
105 canvas.addEventListener('mousemove', onPaint, false);
106 }, false);
107
108 canvas.addEventListener('mouseup', function() {
109 $('#number').html('<img id="spinner" src="spinner.gif"/>');
110 canvas.removeEventListener('mousemove', onPaint, false);
111 var img = new Image();
112 img.onload = function() {
113 context.drawImage(img, 0, 0, 28, 28);
114 data = context.getImageData(0, 0, 28, 28).data;
115 var input = [];
116 for(var i = 0; i < data.length; i += 4) {
117 input.push(data[i + 2] / 255);
118 }
119 predict(input);
120 };
121 img.src = canvas.toDataURL('image/png');
122 }, false);
123
124 var onPaint = function() {
125 context.lineTo(mouse.x, mouse.y);
126 context.stroke();
127 };
128
129 tf.loadLayersModel('model/model.json').then(function(model) {
130 window.model = model;
131 });
132
133 // http://bencentra.com/code/2014/12/05/html5-canvas-touch-events.html
134 // Set up touch events for mobile, etc
135 canvas.addEventListener('touchstart', function (e) {
136 var touch = e.touches[0];
137 canvas.dispatchEvent(new MouseEvent('mousedown', {
138 clientX: touch.clientX,
139 clientY: touch.clientY
140 }));
141 }, false);
142 canvas.addEventListener('touchend', function (e) {
143 canvas.dispatchEvent(new MouseEvent('mouseup', {}));
144 }, false);
145 canvas.addEventListener('touchmove', function (e) {
146 var touch = e.touches[0];
147 canvas.dispatchEvent(new MouseEvent('mousemove', {
148 clientX: touch.clientX,
149 clientY: touch.clientY
150 }));
151 }, false);
152
153 var predict = function(input) {
154 if (window.model) {
155 window.model.predict([tf.tensor(input).reshape([1, 28, 28, 1])]).array().then(function(scores){
156 scores = scores[0];
157 predicted = scores.indexOf(Math.max(...scores));
158 $('#number').html(predicted);
159 });
160 } else {
161 // The model takes a bit to load, if we are too fast, wait
162 setTimeout(function(){predict(input)}, 50);
163 }
164 }
165
166 $('#clear').click(function(){
167 context.clearRect(0, 0, canvas.width, canvas.height);
168 $('#number').html('');
169 });
170 </script>
171 </body>
172</html>
این مورد، امکان نوشتن را برای کاربر فراهم میکند. در واقع، در صفحه مرورگر وب، قسمتی (بوم) وجود دارد که کاربر با استفاده از موس، میتواند در آن عددی را بنویسد.
با استفاده از مولفه HTML5 Canvas میتوان با استفاده از موس، در بوم نوشت.
1canvas.addEventListener('mousedown', function(e) {
2 context.moveTo(mouse.x, mouse.y);
3 context.beginPath();
4 canvas.addEventListener('mousemove', onPaint, false);
5}, false);
6var onPaint = function() {
7 context.lineTo(mouse.x, mouse.y);
8 context.stroke();
9};
همچنین، Touch Events نیز به آن اضافه میشود تا روی موبایل نیز کار کند. touch-action نیز برای غیر فعال کردن اسکرول صفحه، اضافه میشود. هنگامی که قابلیت ترسیم فراهم شد، باید تصویر ترسیم شده با موس، واکشی شود. سپس، ابعاد آن به کاهش پیدا میکند تا با مدل آموزش دیده، تطبیق پیدا کند.
1canvas.addEventListener('mouseup', function() {
2 $('#number').html('<img id="spinner" src="spinner.gif"/>');
3 canvas.removeEventListener('mousemove', onPaint, false);
4 var img = new Image();
5 img.onload = function() {
6 context.drawImage(img, 0, 0, 28, 28);
7 data = context.getImageData(0, 0, 28, 28).data;
8 var input = [];
9 for(var i = 0; i < data.length; i += 4) {
10 input.push(data[i + 2] / 255);
11 }
12 predict(input);
13 };
14 img.src = canvas.toDataURL('image/png');
15}, false)
سپس، دادهها دریافت، در آرایه «input» نگهداری و در نهایت، به تابع پیشبینی که در ادامه تعریف خواهد شد، پاس داده میشوند.
1canvas.addEventListener('mouseup', function() {
2 $('#number').html('<img id="spinner" src="spinner.gif"/>');
3 canvas.removeEventListener('mousemove', onPaint, false);
4 var img = new Image();
5 img.onload = function() {
6 context.drawImage(img, 0, 0, 28, 28);
7 data = context.getImageData(0, 0, 28, 28).data;
8 var input = [];
9 for(var i = 0; i < data.length; i += 4) {
10 input.push(data[i + 2] / 255);
11 }
12 predict(input);
13 };
14 img.src = canvas.toDataURL('image/png');
15}, false);
data یک آرایه تکبُعدی با مقادیر RGBA است. مدل تنها مقادیر ۰ تا ۱ (یا ۰ تا ۲۵۵ در حالت سیاه و سفید) را دریافت میکند. با توجه به اینکه در بوم به رنگ آبی نوشته میشود (با موس و توسط کاربر)، میتواند آرایه را به ۴ بخش تقسیم و دومین عنصر هر بخش را دریافت کرد.
یکپارچهسازی کلیه موارد
در نهایت، باید TensorFlow.js را بارگذاری و پیشبینی را اجرا کرد.
1<script src=”https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@1.5.2/dist/tf.min.js"></script>
کاربر باید فایلهای model.json و group1-shard1of1.bin را دانلود و در پوشهای به نام model در پوشه مشابهی که فایل HTML در آن وجود دارد، ذخیره کند. هنگامی که بارگذاری انجام شد، میتوان مدل آموزش دیده را تنها با استفاده از دستور زیر، بارگذاری کرد.
1tf.loadLayersModel(‘model/model.json’).then(function(model) {
2 window.model = model;
3});
بعد از ترسیم با موس، وقتی که دادهها فراهم شد، میتوان آن را به عنوان خوراک به مدل داد.
1window.model.predict([tf.tensor(input).reshape([1, 28, 28, 1])]).array().then(function(scores){
2 scores = scores[0];
3 predicted = scores.indexOf(Math.max(...scores));
4 $('#number').html(predicted);
5});
تست کردن برنامه به صورت محلی کار سادهای است و میتوان یک سرور HTTP را به سادگی برای تست با پایتون راهاندازی کرد.
1python3 -m http.server 8080
برای تست کردن این برنامه با استفاده از تلفن هوشمند، میتوان از یک ابزار عالی به نام ngrok استفاده کرد.
1$ ngrok http 8080
این موجب میشود که یک تونل به URL زده شود تا کاربر بتواند از طریق گوشی خود دسترسی داشته باشد.
هنگامی که کاربر به نتایج مورد نظر خود دست یافت، میتواند HTML را در یک سایت میزبانی وب مستقر کند. یک گزینه راحت برای این کار، «گیتهاب» (Github) است.
اگر نوشته بالا برای شما مفید بوده است، آموزشهای زیر نیز به شما پیشنهاد میشوند:
- مجموعه آموزشهای دادهکاوی و یادگیری ماشین
- آموزش مدیریت فایلها و اسناد با استفاده از ذخیره ابری
- مجموعه آموزشهای هوش مصنوعی
- ابزارهای یادگیری ماشین متن باز — راهنمای کاربردی
- هوش مصنوعی در کسب و کار — بررسی جامع
- کلان داده یا مِه داده (Big Data) — از صفر تا صد
^^