توقع تعلم الآلة باستخدام لغة Python مع واجهة برمجة تطبيقات Flask REST

نشرت: 2022-03-11

تتناول هذه المقالة استخدام Python في سياق التعلم الآلي أو نظام الذكاء الاصطناعي (AI) لعمل تنبؤات في الوقت الفعلي ، باستخدام Flask REST API. يمكن اعتبار الهيكل المعروض هنا وسيلة للانتقال من إثبات المفهوم (PoC) إلى الحد الأدنى من المنتج القابل للتطبيق (MVP) لتطبيقات التعلم الآلي.

بايثون ليست الخيار الأول الذي يمكن للمرء أن يفكر فيه عند تصميم حل في الوقت الفعلي. ولكن نظرًا لأن Tensorflow و Scikit-Learn هما من أكثر مكتبات التعلم الآلي استخدامًا التي تدعمها Python ، فإنه يتم استخدامه بشكل ملائم في العديد من نقاط تثبيت دفتر الملاحظات من Jupyter.

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

التدريب هو نوع من العرض المضغوط لهذا "الفيلم" والتنبؤ هو استرداد المعلومات من العرض المضغوط. يجب أن يكون سريعًا حقًا ، سواء كان الفيلم معقدًا أو طويلًا.

دعنا ننفذ ذلك باستخدام مثال [Flask] سريع في Python!

هندسة تعلم الآلة العامة

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

نص بديل للصورة

أولاً ، يتم إنشاء خط تدريب للتعرف على البيانات السابقة وفقًا لوظيفة موضوعية.

يجب أن ينتج عن هذا عنصرين رئيسيين:

  1. وظائف هندسة الميزات : يجب إعادة استخدام التحولات المستخدمة في وقت التدريب في وقت التنبؤ.
  2. معلمات النموذج : يجب حفظ الخوارزمية والمعلمات الفائقة التي تم تحديدها أخيرًا ، بحيث يمكن إعادة استخدامها في وقت التنبؤ

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

إذا تم تغيير حجم الميزة X1 من القيمة 1 إلى 1000 وتم إعادة قياسها إلى النطاق [0،1] مع وظيفة f(x) = x/max(X1) ، ماذا سيحدث إذا كانت مجموعة التنبؤ لها قيمة 2000؟

يجب التفكير في بعض التعديلات الدقيقة مسبقًا بحيث تقوم وظيفة التعيين بإرجاع مخرجات متسقة سيتم حسابها بشكل صحيح في وقت التنبؤ.

تدريب التعلم الآلي مقابل التنبؤ

هناك سؤال رئيسي يجب تناوله هنا. لماذا نفصل بين التدريب والتنبؤ من البداية؟

من الصحيح تمامًا أنه في سياق أمثلة ودورات التعلم الآلي ، حيث تُعرف جميع البيانات مسبقًا (بما في ذلك البيانات التي سيتم توقعها) ، فإن الطريقة البسيطة جدًا لبناء المتنبئ هي تكديس بيانات التدريب والتنبؤ (تسمى عادةً مجموعة اختبار).

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

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

علاوة على ذلك ، ليس من الضروري عادةً إعادة تدريب النموذج بأكمله في كل مرة تأتي فيها بيانات جديدة لأن التدريب يستغرق وقتًا (قد يستغرق أسابيع لبعض مجموعات الصور) ويجب أن يكون مستقرًا بدرجة كافية بمرور الوقت.

هذا هو السبب في أن التدريب والتنبؤ يمكن ، أو حتى يجب أن يكونا منفصلين بوضوح في العديد من الأنظمة ، وهذا أيضًا يعكس بشكل أفضل كيف يتعلم النظام الذكي (مصطنعًا أم لا).

الاتصال مع overfitting

يعد الفصل بين التدريب والتنبؤ أيضًا طريقة جيدة لمعالجة مشكلة فرط التجهيز.

في الإحصاء ، يعتبر overfitting "إنتاج تحليل يتوافق بشكل وثيق جدًا أو تمامًا مع مجموعة معينة من البيانات ، وبالتالي قد يفشل في ملاءمة بيانات إضافية أو توقع الملاحظات المستقبلية بشكل موثوق".

نص بديل للصورة

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

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

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

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

ستكون الخطوة الأخيرة هي التحكم في أن الخطأ في البيانات الجديدة هو نفسه كما هو متوقع. هناك دائمًا تحول (الخطأ الفعلي دائمًا أقل من المتوقع) ، ويجب على المرء أن يحدد ما هو التحول المقبول - ولكن هذا ليس موضوع هذه المقالة.

واجهة برمجة تطبيقات REST للتنبؤ

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

نص بديل للصورة

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

يمكن تصميم واجهة برمجة التطبيقات (API) لقبول مثال فريد يمكن توقعه ، أو عدة أمثلة مختلفة (تنبؤات الدُفعة).

هنا هو الحد الأدنى من كود Python / Flask الذي يطبق هذا المبدأ ، مع JSON in و JSON خارج (السؤال في ، الإجابة):

 app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call No need to re-load the model """ # reads the received json jsonfile = request.get_json() res = dict() for key in jsonfile.keys(): # calculates and predicts res[key] = model.predict(doTheCalculation(key)) # returns a json file return jsonify(res) if __name__ == '__main__': # Model is loaded when the API is launched model = pickle.load(open('modelfile', 'rb')) app.run(debug=True)

لاحظ أنه يمكن استخدام API للتنبؤ من البيانات الجديدة ، لكنني لا أوصي باستخدامها لتدريب النموذج. يمكن استخدامه ، لكن هذا يعقد نموذج رمز التدريب وقد يكون أكثر تطلبًا من حيث موارد الذاكرة.

مثال على التنفيذ - مشاركة الدراجة

لنأخذ مجموعة بيانات Kaggle ، مشاركة الدراجة ، كمثال. لنفترض أننا شركة مشاركة للدراجات تريد توقع عدد إيجارات الدراجات كل يوم من أجل إدارة صيانة الدراجة واللوجستيات والجوانب الأخرى للعمل بشكل أفضل.

نص بديل للصورة

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

أولاً ، نقوم بتدريب نموذج وحفظه ككائن مخلل يمكن رؤيته في دفتر Jupyter.

لم يتم التعامل مع تدريب النموذج والأداء هنا ، هذا مجرد مثال لفهم العملية الكاملة.

ثم نكتب تحويل البيانات الذي سيتم إجراؤه في كل استدعاء لواجهة برمجة التطبيقات:

 import numpy as np import pandas as pd from datetime import date def doTheCalculation(data): data['dayofyear']=(data['dteday']- data['dteday'].apply(lambda x: date(x.year,1,1)) .astype('datetime64[ns]')).apply(lambda x: x.days) X = np.array(data[['instant','season','yr','holiday','weekday','workingday', 'weathersit','temp','atemp','hum','windspeed','dayofyear']]) return X

هذا مجرد حساب لمتغير (يوم من السنة) ليشمل كلاً من الشهر واليوم المحدد. هناك أيضًا مجموعة مختارة من الأعمدة وترتيبها المطلوب الاحتفاظ به.

نحتاج إذن إلى كتابة REST API مع Flask:

 from flask import Flask, request, redirect, url_for, flash, jsonify from features_calculation import doTheCalculation import json, pickle import pandas as pd import numpy as np app = Flask(__name__) @app.route('/api/makecalc/', methods=['POST']) def makecalc(): """ Function run at each API call """ jsonfile = request.get_json() data = pd.read_json(json.dumps(jsonfile),orient='index',convert_dates=['dteday']) print(data) res = dict() ypred = model.predict(doTheCalculation(data)) for i in range(len(ypred)): res[i] = ypred[i] return jsonify(res) if __name__ == '__main__': modelfile = 'modelfile.pickle' model = pickle.load(open(modelfile, 'rb')) print("loaded OK") app.run(debug=True)

قم بتشغيل هذا البرنامج ، وسوف يخدم API على المنفذ 5000 بشكل افتراضي.

إذا اختبرنا طلبًا محليًا ، فلا يزال مع Python:

 import requests, json url = '[http://127.0.0.1:5000/api/makecalc/](http://127.0.0.1:5000/api/makecalc/)' text = json.dumps({"0":{"instant":1,"dteday":"2011-01-01T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":6,"workingday":0,"weathersit":2,"temp":0.344167,"atemp":0.363625,"hum":0.805833,"windspeed":0.160446}, "1":{"instant":2,"dteday":"2011-01-02T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":3,"workingday":0,"weathersit":2,"temp":0.363478,"atemp":0.353739,"hum":0.696087,"windspeed":0.248539}, "2":{"instant":3,"dteday":"2011-01-03T00:00:00.000Z","season":1,"yr":0,"mnth":1,"holiday":0,"weekday":1,"workingday":1,"weathersit":1,"temp":0.196364,"atemp":0.189405,"hum":0.437273,"windspeed":0.248309}})

يحتوي الطلب على كافة المعلومات التي تم تغذيتها للنموذج. لذلك ، سيستجيب نموذجنا بتنبؤ بإيجارات الدراجات في التواريخ المحددة (لدينا هنا ثلاثة منها).

 headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'} r = requests.post(url, data=text, headers=headers) print(r,r.text) <Response [200]> { "0": 1063, "1": 1028, "2": 1399 }

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

ضع كل شيء معا

يتمثل العيب الرئيسي في العديد من أنظمة التعلم الآلي ، وخاصة PoCs ، في المزج بين التدريب والتنبؤ.

إذا تم فصلها بعناية ، يمكن إجراء التنبؤات في الوقت الفعلي بسهولة تامة بالنسبة إلى MVP ، بتكلفة تطوير منخفضة للغاية وجهد مع Python / Flask ، خاصةً إذا تم تطويرها في البداية مع Scikit-Learn و Tensorflow و أو أي مكتبة أخرى لتعلم الآلة بلغة Python.

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

على أي حال ، هل تحتاج إلى مشاهدة الأفلام مرارًا وتكرارًا للإجابة على الأسئلة المتعلقة بها؟ نفس القاعدة تنطبق على التعلم الآلي!