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

هذا هو السبب في أنني أكتب هذا الدليل الآن. أريدك أن تواجه المشكلة التي واجهتها في وقتي ، لكن يمكنني حلها بسرعة. قرب نهاية هذه المقالة ، سأبين لك كيفية تنفيذ نموذج التعلم الآلي باستخدام إطار Flask في Python.
المحتويات- خيارات التنفيذ لنماذج التعلم الآلي.
- ما هو API؟
- تثبيت بيئة بيثون والمعلومات الأساسية حول قارورة.
- إنشاء نموذج تعلم الآلة.
- توفير نماذج التعلم الآلي: التسلسل وإلغاء التسلسل.
- إنشاء API باستخدام Flask.
خيارات التنفيذ لنماذج التعلم الآلي.
في معظم الحالات ، يعد الاستخدام الفعلي لنماذج التعلم الآلي جزءًا أساسيًا من التطوير ، حتى لو كان مجرد جزء صغير من نظام توزيع البريد الإلكتروني أو chatbot. في بعض الأحيان تكون هناك أوقات تبدو فيها الحواجز التي تعترض سبيل التنفيذ مستعصية على الحل.
على سبيل المثال ، يستخدم معظم المتخصصين في ML أو R أو Python للبحث العلمي. ومع ذلك ، فإن مهندسي البرمجيات الذين يستخدمون كومة تقنية مختلفة تمامًا سيكونون مستهلكين لهذه النماذج. هناك خياران يمكنهما حل هذه المشكلة:
الخيار الأول: أعد كتابة كل الشفرة باللغة التي يعمل بها مهندسو التطوير. هذا يبدو منطقيًا إلى حد ما ، لكنه يتطلب الكثير من الوقت والجهد لتكرار النماذج المطورة. في النهاية ، اتضح مجرد مضيعة للوقت. معظم اللغات ، مثل JavaScript ، لا تملك مكتبات ملائمة للعمل مع ML. لذلك ، سيكون حلًا عقلانيًا عدم استخدام هذا الخيار.
الخيار 2: استخدام API. واجهات برمجة التطبيقات (APIs) للشبكة حل مشكلة العمل مع التطبيقات بلغات مختلفة. إذا كان المطور الأمامي يحتاج إلى استخدام نموذج التعلم الآلي الخاص بك لإنشاء تطبيق ويب على أساسه ، فسيحتاج فقط إلى الحصول على عنوان URL للخادم الوجهة الذي يناقش API.
ما هو API؟وبكلمات بسيطة ، فإن API (واجهة برمجة التطبيقات) هي نوع من العقود المبرمة بين برنامجين ، والتي تنص على أنه إذا قدم برنامج مستخدم بيانات الإدخال بتنسيق معين ، فسيقوم برنامج المطور (API) بتمريرها عبر نفسه ويزود المستخدم ببيانات الإخراج.
ستكون قادرًا على قراءة بعض المقالات بنفسك ، والتي تصف جيدًا سبب كون واجهة برمجة التطبيقات خيارًا شائعًا إلى حد ما بين المطورين.
توفر معظم مزودي الخدمات السحابية الكبيرة والشركات الأصغر التي تركز على تعلم الآلة واجهات برمجة تطبيقات جاهزة للاستخدام. أنها تلبي احتياجات المطورين الذين لا يفهمون تعلم الآلة ، ولكنهم يريدون دمج هذه التكنولوجيا في حلولهم.
على سبيل المثال ، أحد موفري واجهة برمجة التطبيقات هذه هو Google مع
واجهة برمجة تطبيقات Google Vision .
كل ما يحتاج المطور إلى القيام به هو استدعاء واجهة برمجة تطبيقات REST (نقل الحالة التمثيلية) باستخدام SDK التي توفرها Google. تعرف على ما يمكنك فعله باستخدام
واجهة برمجة تطبيقات Google Vision .
تبدو رائعة ، أليس كذلك؟ في هذه المقالة ، سنعرف كيفية إنشاء واجهة برمجة التطبيقات الخاصة بك باستخدام Flask ، إطار Python.
ملاحظة : Flask ليس إطار عمل الشبكة الوحيد لهذا الغرض. هناك أيضًا Django و Falcon و Hug والعديد غيرها غير المذكورة في هذه المقالة. على سبيل المثال ، بالنسبة لـ R يوجد حزمة تسمى
سباكتثبيت بيئة بيثون والمعلومات الأساسية حول قارورة.1) إنشاء بيئة افتراضية باستخدام أناكوندا. إذا كنت بحاجة إلى إنشاء بيئة افتراضية خاصة بك لبيثون والحفاظ على حالة التبعيات اللازمة ، تقدم أناكوندا حلولاً جيدة لهذا الغرض. التالي سوف تعمل مع سطر الأوامر.
- هنا ستجد المثبت miniconda لبيثون.
wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh
bash Miniconda3-latest-Linux-x86_64.sh
- اتبع تسلسل الأسئلة.
source .bashrc
- إذا قمت بكتابة:
conda
، يمكنك رؤية قائمة بالأوامر والمساعدة المتاحة. - لإنشاء بيئة جديدة ، اكتب:
conda create --name <environment-name> python=3.6
- اتبع الخطوات التي سيُطلب منك القيام بها وفي النهاية ، أدخل:
source activate <environment-name>
- تثبيت حزم بيثون المطلوبة. وأهمها قارورة وجونكورن.
2) سنحاول إنشاء تطبيقنا البسيط "Hello World " Flask باستخدام gunicorn .- افتح محرر النصوص المفضل لديك وقم بإنشاء ملف
hello-world.py
في المجلد - اكتب الكود التالي:
"""Filename: hello-world.py """ from flask import Flask app = Flask(__name__) @app.route('/users/<string:username>') def hello_world(username=None): return("Hello {}!".format(username))
- حفظ الملف والعودة إلى المحطة.
- لإطلاق واجهة برمجة التطبيقات ، قم بتشغيل في المحطة الطرفية:
gunicorn --bind 0.0.0.0:8000 hello-world:app
- إذا حصلت على ما يلي ، فأنت على الطريق الصحيح:

- في المتصفح ، أدخل ما يلي:
https://localhost:8000/users/any-name

الصيحة! لقد كتبت أول برنامج قارورة! نظرًا لأن لديك بالفعل بعض الخبرة في هذه الخطوات البسيطة ، يمكننا إنشاء نقاط نهاية للشبكة يمكن الوصول إليها محليًا.
باستخدام Flask ، يمكننا التفاف نماذجنا واستخدامها كواجهة برمجة تطبيقات ويب. إذا كنا نريد إنشاء تطبيقات شبكة أكثر تعقيدًا (على سبيل المثال ، في JavaScript) ، فسنحتاج إلى إضافة بعض التغييرات.
إنشاء نموذج تعلم الآلة.- للبدء ، دعنا نلقي نظرة على مسابقة تعلم الآلة للتنافس على القروض . الهدف الرئيسي هو إعداد خط أنابيب للمعالجة المسبقة وإنشاء نماذج ML لتسهيل مهمة التنبؤ أثناء النشر.
import os import json import numpy as np import pandas as pd from sklearn.externals import joblib from sklearn.model_selection import train_test_split, GridSearchCV from sklearn.base import BaseEstimator, TransformerMixin from sklearn.ensemble import RandomForestClassifier from sklearn.pipeline import make_pipeline import warnings warnings.filterwarnings("ignore")
- احفظ مجموعة البيانات في المجلد:
!ls /home/pratos/Side-Project/av_articles/flask_api/data/
test.csv training.csv
data = pd.read_csv('../data/training.csv')
list(data.columns)
['Loan_ID', 'Gender', 'Married', 'Dependents', 'Education', 'Self_Employed', 'ApplicantIncome', 'CoapplicantIncome', 'LoanAmount', 'Loan_Amount_Term', 'Credit_History', 'Property_Area', 'Loan_Status']
data.shape
(614, 13)
المجاهدين>
ابحث عن القيم الخالية / النانوية في الأعمدة:
for _ in data.columns: print("The number of null values in:{} == {}".format(_, data[_].isnull().sum()))
The number of null values in:Loan_ID == 0 The number of null values in:Gender == 13 The number of null values in:Married == 3 The number of null values in:Dependents == 15 The number of null values in:Education == 0 The number of null values in:Self_Employed == 32 The number of null values in:ApplicantIncome == 0 The number of null values in:CoapplicantIncome == 0 The number of null values in:LoanAmount == 22 The number of null values in:Loan_Amount_Term == 14 The number of null values in:Credit_History == 50 The number of null values in:Property_Area == 0 The number of null values in:Loan_Status == 0
- الخطوة التالية هي إنشاء مجموعات بيانات للتدريب والاختبار:
red_var = ['Gender','Married','Dependents','Education','Self_Employed','ApplicantIncome','CoapplicantIncome',\ 'LoanAmount','Loan_Amount_Term','Credit_History','Property_Area'] X_train, X_test, y_train, y_test = train_test_split(data[pred_var], data['Loan_Status'], \ test_size=0.25, random_state=42)
- للتأكد من إتمام جميع خطوات المعالجة المسبقة بشكل صحيح حتى بعد إجراء التجربة ، ولم نفتقد أي شيء أثناء التنبؤ ، سنقوم بإنشاء مقيم Scikit-Learn الخاص بنا للمعالجة المسبقة (مقدِّر Scikit-Learn المسبق) .
لفهم كيف أنشأناها ، اقرأ ما
يلي .
from sklearn.base import BaseEstimator, TransformerMixin class PreProcessing(BaseEstimator, TransformerMixin): """Custom Pre-Processing estimator for our use-case """ def __init__(self): pass def transform(self, df): """Regular transform() that is a help for training, validation & testing datasets (NOTE: The operations performed here are the ones that we did prior to this cell) """ pred_var = ['Gender','Married','Dependents','Education','Self_Employed','ApplicantIncome',\ 'CoapplicantIncome','LoanAmount','Loan_Amount_Term','Credit_History','Property_Area'] df = df[pred_var] df['Dependents'] = df['Dependents'].fillna(0) df['Self_Employed'] = df['Self_Employed'].fillna('No') df['Loan_Amount_Term'] = df['Loan_Amount_Term'].fillna(self.term_mean_) df['Credit_History'] = df['Credit_History'].fillna(1) df['Married'] = df['Married'].fillna('No') df['Gender'] = df['Gender'].fillna('Male') df['LoanAmount'] = df['LoanAmount'].fillna(self.amt_mean_) gender_values = {'Female' : 0, 'Male' : 1} married_values = {'No' : 0, 'Yes' : 1} education_values = {'Graduate' : 0, 'Not Graduate' : 1} employed_values = {'No' : 0, 'Yes' : 1} property_values = {'Rural' : 0, 'Urban' : 1, 'Semiurban' : 2} dependent_values = {'3+': 3, '0': 0, '2': 2, '1': 1} df.replace({'Gender': gender_values, 'Married': married_values, 'Education': education_values, \ 'Self_Employed': employed_values, 'Property_Area': property_values, \ 'Dependents': dependent_values}, inplace=True) return df.as_matrix() def fit(self, df, y=None, **fit_params): """Fitting the Training dataset & calculating the required values from train eg: We will need the mean of X_train['Loan_Amount_Term'] that will be used in transformation of X_test """ self.term_mean_ = df['Loan_Amount_Term'].mean() self.amt_mean_ = df['LoanAmount'].mean() return self
- تحويل
y_train
و y_test
إلى np.array
:
y_train = y_train.replace({'Y':1, 'N':0}).as_matrix() y_test = y_test.replace({'Y':1, 'N':0}).as_matrix()
لنقم بإنشاء خط أنابيب للتأكد من أن جميع خطوات المعالجة المسبقة التي نقوم بها هي عمل مقيم scikit-Learn.
pipe = make_pipeline(PreProcessing(), RandomForestClassifier())
pipe
Pipeline(memory=None, steps=[('preprocessing', PreProcessing()), ('randomforestclassifier', RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impurity_split=None, min_samples_leaf=1, min_samples_split=2, min_weight_fraction_leaf=0.0, n_estimators=10, n_jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False))])
للبحث عن معلمات فرط مناسبة (درجة للكائنات متعددة الحدود وألفا للحافة) ، سنقوم بالبحث في الشبكة (بحث عن الشبكة):
param_grid = {"randomforestclassifier__n_estimators" : [10, 20, 30], "randomforestclassifier__max_depth" : [None, 6, 8, 10], "randomforestclassifier__max_leaf_nodes": [None, 5, 10, 20], "randomforestclassifier__min_impurity_split": [0.1, 0.2, 0.3]}
grid = GridSearchCV(pipe, param_grid=param_grid, cv=3)
- نقوم بضبط بيانات التدريب لمقدار خط الأنابيب:
grid.fit(X_train, y_train)
GridSearchCV(cv=3, error_score='raise', estimator=Pipeline(memory=None, steps=[('preprocessing', PreProcessing()), ('randomforestclassifier', RandomForestClassifier(bootstrap=True, class_weight=None, criterion='gini', max_depth=None, max_features='auto', max_leaf_nodes=None, min_impurity_decrease=0.0, min_impu..._jobs=1, oob_score=False, random_state=None, verbose=0, warm_start=False))]), fit_params=None, iid=True, n_jobs=1, param_grid={'randomforestclassifier__n_estimators': [10, 20, 30], 'randomforestclassifier__max_leaf_nodes': [None, 5, 10, 20], 'randomforestclassifier__min_impurity_split': [0.1, 0.2, 0.3], 'randomforestclassifier__max_depth': [None, 6, 8, 10]}, pre_dispatch='2*n_jobs', refit=True, return_train_score=True, scoring=None, verbose=0)
- لنرى المعلمة التي اختارها البحث على الشبكة:
print("Best parameters: {}".format(grid.best_params_))
Best parameters: {'randomforestclassifier__n_estimators': 30, 'randomforestclassifier__max_leaf_nodes': 20, 'randomforestclassifier__min_impurity_split': 0.2, 'randomforestclassifier__max_depth': 8}
print("Validation set score: {:.2f}".format(grid.score(X_test, y_test)))
Validation set score: 0.79
- قم بتنزيل مجموعة الاختبار:
test_df = pd.read_csv('../data/test.csv', encoding="utf-8-sig") test_df = test_df.head()
grid.predict(test_df)
array([1, 1, 1, 1, 1])
يبدو خط أنابيبنا جيدًا بما يكفي للانتقال إلى الخطوة المهمة التالية: إجراء تسلسل لنموذج التعلم الآلي.
إنقاذ نموذج التعلم الآلي: التسلسل وإلغاء التسلسل."في علوم الكمبيوتر ، في سياق تخزين البيانات ، التسلسل هو عملية ترجمة بنيات البيانات أو حالات الكائنات إلى تنسيق مخزن (على سبيل المثال ، ملف أو مخزن مؤقت للذاكرة) وإعادة بنائه لاحقًا في بيئة الكمبيوتر نفسها أو في بيئة أخرى."
في Python ، يعد التخليل هو الطريقة القياسية لتخزين الكائنات واستعادتها لاحقًا في حالتها الأصلية. لجعله أكثر وضوحًا ، سأقدم مثالًا بسيطًا:
list_to_pickle = [1, 'here', 123, 'walker']
list_pickle
b'\x80\x03]q\x00(K\x01X\x04\x00\x00\x00hereq\x01K{X\x06\x00\x00\x00walkerq\x02e.'
ثم نفرغ الكائن المعلب مرة أخرى:
loaded_pickle = pickle.loads(list_pickle)
loaded_pickle
[1, 'here', 123, 'walker']
يمكننا حفظ الكائنات المعلبة في ملف واستخدامها. تشبه هذه الطريقة إنشاء ملفات
.rda
، كما هو الحال في برمجة R ، على سبيل المثال.
ملاحظة: قد لا يحب البعض طريقة الحفظ هذه للتسلسل. بديل يمكن أن يكون
h5py
.
لدينا فئة مخصصة (فئة) نحتاج إلى استيرادها أثناء التدريب ، لذلك سوف نستخدم وحدة
dill
لتعبئة مقيم الفصل مع كائن الشبكة.
يُنصح بإنشاء ملف
training.py
منفصل. يحتوي على جميع التعليمات البرمجية لتدريب النموذج. (مثال يمكن رؤيته
هنا ).
!pip install dill
Requirement already satisfied: dill in /home/pratos/miniconda3/envs/ordermanagement/lib/python3.5/site-packages
import dill as pickle filename = 'model_v1.pk'
with open('../flask_api/models/'+filename, 'wb') as file: pickle.dump(grid, file)
سيتم حفظ النموذج في الدليل المحدد أعلاه. مرة واحدة يتم mothballed النموذج ، يمكن أن تكون ملفوفة في غلاف قارورة. ومع ذلك ، قبل هذا تحتاج إلى التأكد من أن الملف المعلبة يعمل. دعنا تحميله مرة أخرى وجعل التنبؤ:
with open('../flask_api/models/'+filename ,'rb') as f: loaded_model = pickle.load(f)
loaded_model.predict(test_df)
array([1, 1, 1, 1, 1])
نظرًا لأننا اتبعنا خطوات المعالجة المسبقة بحيث تكون البيانات التي تم الوصول إليها حديثًا جزءًا من خط الأنابيب ، نحتاج فقط إلى تشغيل توقع (). باستخدام مكتبة scikit-learn ، من السهل جدًا العمل مع خطوط الأنابيب. يهتم المثمنون وخطوط الأنابيب بوقتك وأعصابك ، حتى إذا بدا أن التنفيذ الأولي أصبح هزيلًا.
إنشاء API باستخدام Flaskدعنا نبقي بنية المجلد بسيطة قدر الإمكان:

هناك ثلاثة أجزاء مهمة لإنشاء مجمّع
apicall()
:
- تلقي بيانات
request
(التي سيتم التنبؤ بها) ؛ - تحميل المثمن المعلبة.
- ترجمة توقعاتنا بتنسيق JSON واستلام
status code: 200
الاستجابة status code: 200
؛
يتم إنشاء رسائل HTTP من الرأس والنص. بشكل عام ، يتم نقل محتوى الجسم الرئيسي بتنسيق JSON. سنرسل (
POST url-endpoint/
) البيانات الواردة كحزمة لتلقي التوقعات.
ملاحظة: يمكنك إرسال نص عادي أو XML أو سيرة ذاتية أو صورة مباشرة من أجل التبادلية للتنسيق ، ومع ذلك فمن الأفضل استخدام JSON في حالتنا.
"""Filename: server.py """ import os import pandas as pd from sklearn.externals import joblib from flask import Flask, jsonify, request app = Flask(__name__) @app.route('/predict', methods=['POST']) def apicall(): """API Call Pandas dataframe (sent as a payload) from API Call """ try: test_json = request.get_json() test = pd.read_json(test_json, orient='records')
بعد التنفيذ ، أدخل:
gunicorn --bind 0.0.0.0:8000 server:app
لنقم بإنشاء بيانات للتنبؤ وقائمة انتظار لتشغيل API محليًا على
https:0.0.0.0:8000/predict
import json import requests
"""Setting the headers to send and accept json responses """ header = {'Content-Type': 'application/json', \ 'Accept': 'application/json'} """Reading test batch """ df = pd.read_csv('../data/test.csv', encoding="utf-8-sig") df = df.head() """Converting Pandas Dataframe to json """ data = df.to_json(orient='records')
data
'[{"Loan_ID":"LP001015","Gender":"Male","Married":"Yes","Dependents":"0","Education":"Graduate","Self_Employed":"No","ApplicantIncome":5720,"CoapplicantIncome":0,"LoanAmount":110.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001022","Gender":"Male","Married":"Yes","Dependents":"1","Education":"Graduate","Self_Employed":"No","ApplicantIncome":3076,"CoapplicantIncome":1500,"LoanAmount":126.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001031","Gender":"Male","Married":"Yes","Dependents":"2","Education":"Graduate","Self_Employed":"No","ApplicantIncome":5000,"CoapplicantIncome":1800,"LoanAmount":208.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"},{"Loan_ID":"LP001035","Gender":"Male","Married":"Yes","Dependents":"2","Education":"Graduate","Self_Employed":"No","ApplicantIncome":2340,"CoapplicantIncome":2546,"LoanAmount":100.0,"Loan_Amount_Term":360.0,"Credit_History":null,"Property_Area":"Urban"},{"Loan_ID":"LP001051","Gender":"Male","Married":"No","Dependents":"0","Education":"Not Graduate","Self_Employed":"No","ApplicantIncome":3276,"CoapplicantIncome":0,"LoanAmount":78.0,"Loan_Amount_Term":360.0,"Credit_History":1.0,"Property_Area":"Urban"}]'
"""POST <url>/predict """ resp = requests.post("http://0.0.0.0:8000/predict", \ data = json.dumps(data),\ headers= header)
resp.status_code
200
"""The final response we get is as follows: """ resp.json()
{'predictions': '[{"0":"LP001015","1":1},{...
استنتاجفي هذه المقالة ، قطعنا نصف الطريق فقط ، حيث قمنا بإنشاء واجهة برمجة تطبيقات فعالة تعمل على التنبؤات ، وأصبحنا على بعد خطوة واحدة من دمج حلول ML مباشرة في التطبيقات المتقدمة. لقد أنشأنا واجهة برمجة تطبيقات بسيطة إلى حد ما من شأنها أن تساعد في وضع نماذج أولية للمنتج وجعله وظيفيًا حقًا ، ولكن لإرساله إلى الإنتاج ، تحتاج إلى إجراء بعض التعديلات التي لم تعد موجودة في مجال التعلم الآلي.
هناك بعض الأشياء التي يجب وضعها في الاعتبار عند إنشاء واجهة برمجة التطبيقات:
- من المستحيل تقريبًا إنشاء واجهة برمجة تطبيقات (API) عالية الجودة من شفرة السباغيتي ، لذا استخدم معرفتك في التعلم الآلي لإنشاء واجهة برمجة تطبيقات مفيدة ومريحة.
- حاول استخدام التحكم في الإصدار للنماذج ورمز API. ضع في اعتبارك أن Flask لا يوفر الدعم لأدوات التحكم في الإصدار. يعد حفظ نماذج ML وتتبعها مهمة صعبة ، وإيجاد طريقة ملائمة لك. هناك مقال هنا يتحدث عن كيفية القيام بذلك.
- نظرًا لتفاصيل نماذج scikit-learn ، تحتاج إلى التأكد من أن المقيِّم ورمز التدريب بجوار بعضهما البعض (إذا كنت تستخدم مقيِّم مخصص لإجراء المعالجة المسبقة أو مهام أخرى مماثلة). وبالتالي ، فإن النموذج المعلبة سيكون مقيم الفصول بجانبه.
تتمثل الخطوة المنطقية التالية في إنشاء آليات لنشر واجهة برمجة التطبيقات هذه على جهاز ظاهري صغير. هناك عدة طرق للقيام بذلك ، لكننا سنغطيها في المقالة التالية.
رمز وشرح لهذه المادةمصادر مفيدة:[1]
لا تجمع بياناتك.[2]
بناء Scikit تعلم المحولات المتوافقة .
[3]
باستخدام jsonify في قارورة .
[4]
قارورة ، بدء التشغيل السريع.هنا هذه المواد. اشترك معنا إذا كنت تحب المنشور ، والاشتراك في
ندوة عبر الإنترنت مفتوحة مجانًا حول الموضوع: "خوارزميات تصنيف القياس المتري" ، والتي ستعقد في 12 مارس من قبل المطور وعالم البيانات بخمس سنوات من الخبرة -
ألكسندر نيكيتين .