Разрабатываем и развёртываем собственную платформу ИИ с Python и Django

Автор оригинала: Diego Salinas
  • Перевод
Взлёт искусственного интеллекта привёл к популярности платформ машинного обучения MLaaS. Если ваша компания не собирается строить фреймворк и развёртывать свои собственные модели, есть шанс, что она использует некоторые платформы MLaaS, например H2O или KNIME. Многие исследователи данных, которые хотят сэкономить время, пользуются этими инструментами, чтобы быстро прототипировать и тестировать модели, а позже решают, будут ли их модели работать дальше. 

Но не бойтесь всей этой инфраструктуры; чтобы понять эту статью, достаточно минимума знаний языка Python и фреймворка Django.  Специально к старту нового потока курса по машинному обучению в этом посте покажем, как быстро создать собственную платформу ML, способную запускать самые популярные алгоритмы на лету.


Портрет Орнеллы Мути Джозефа Айерле (фрагмент), рассчитанный с помощью технологии искусственного интеллекта.


Прежде всего взгляните, пожалуйста, на сайт, который я разработал для этого проекта, и позвольте мне показать вам краткое введение в то, что можно сделать с этой платформой, в этом видео.


Как видите, вы выбираете самые популярные модели с контролируемым и неконтролируемым обучением и запускаете их всего одним щелчком мыши. Если хочется попробовать те же наборы данных, что и в видео, вы можете скачать их здесь. 

Круто, давайте посмотрим, как это сделать! Возможно, вы работаете на ноутбуке или на облачном сервере, я использовал сервер Digital Ocean, приложение работает на Nginx и Gunicorn, и я подробно расскажу о том, как развёртывать его: это может быть полезно, когда нужно развёртывать приложения на Linux-сервере каждый день.

Давайте начнём настраивать наше окружение с некоторых установок:

sudo apt-get update
sudo apt-get install python3-pip python3-dev libpq-dev postgresql postgresql-contrib nginx # install python postgresql and nginx
# now install a postgresql database
sudo -u postgres psql  #log in to psql
CREATE DATABASE myproject;   #give a name to your database project 
CREATE USER myprojectuser WITH PASSWORD 'password';   # create user and password
ALTER ROLE myprojectuser SET client_encoding TO 'utf8';
ALTER ROLE myprojectuser SET default_transaction_isolation TO 'read committed';
ALTER ROLE myprojectuser SET timezone TO 'UTC';
\q # log out

# cd to the folder where you want to keep your environment
sudo -H pip3 install env
virtualenv env # create a virtual environment for this project
source myprojectenv/bin/activate
pip install django gunicorn psycopg2 #install django and gunicorn

# now cd to the directory where you want to create your project, in my case cd /www/var
django-admin.py startproject trainyourmodel
#cd trainyourmodel
django-admin startapp portal # create an app for the views of this app
#cd trainyourmodel again and nano settings.py

# now we configure our settings.py file

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'myproject',
        'USER': 'myprojectuser',
        'PASSWORD': 'password',
        'HOST': 'localhost',
        'PORT': '',
    }
}


STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static/')

После этого всё, что Вам нужно сделать, — это настроить Вашу конфигурацию для сервиса Gunicorn: 

sudo nano /etc/systemd/system/gunicorn.service

[Unit]
Description=gunicorn daemon
After=network.target

[Service]
User=root
Group=www-data
WorkingDirectory=/var/www/trainyourmodel
ExecStart=/opt/env/bin/gunicorn --access-logfile - --workers 3 --bind unix:/var/www/trainyourmodel/trainyourmodel.sock trainyourmodel.wsgi:application

sudo systemctl start gunicorn
sudo systemctl enable gunicorn
sudo systemctl status gunicorn

gunicorn --bind 0.0.0.0:8000 trainyourmodel.wsgi

И для Nginx: 

sudo nano /etc/nginx/sites-available/trainyourmodel

server {
    listen 80;
    server_name server_domain_or_IP;

    location = /favicon.ico { access_log off; log_not_found off; }
    location /static/ {
        root /var/www/trainyourmodel;
    }

    location / {
        include proxy_params;
        proxy_pass http://unix:/var/www/trainyourmodel/trainyourmodel.sock;
    }
}
#copy the same file also in sudo nano /etc/nginx/sites-enabled/trainyourmodel

sudo systemctl restart nginx
sudo systemctl status nginx

Теперь всё готово, и вы можете просто клонировать код из моего GitHub-репозитория (текущая ветка — releasee-1.2) и установить все нужные пакеты, просто выполнив команду: 

pip -install -r requirements.txt

Проверьте технические характеристики вашей системы, чтобы убедиться, что она соответствует требованиям (стоят нужные версии Python, Django и т. д.). После этого вы увидите типичные папки структуры проекта Django, а именно:

  • Папка проекта trainyourmodel с файлом settings.py.
  • Папка приложения под названием Portral c файлом views.py.
  • Папка шаблонов для кода .html.

Если хочется сделать приложение красивым, нужно создать папку со статическими файлами: файлами оформления фронтенда вашего шаблона, но это выходит за рамки статьи. Если вы не знакомы с Django, вам необходимо знать, что этот фреймворк разделяет файл бэкенда с таблицами вашей базы данных (эти таблицы называются моделями), и файлы фронтенда, которые называются представлениями и шаблонами. 

Я установил базу данных на тот случай, если захочу использовать её в будущем, но пока мы не используем никакую базу, так как будем запускать модели на лету, поэтому я поместил представления в папку под названием portal, и туда мы разместим весь код, запущенный нашими моделями. 

Прежде всего мы импортируем все библиотеки, которые нам понадобятся для наших моделей машинного обучения и построения красивых графиков онлайн (Pandas, NumPy, sci-kit-learn, TensorFlow, Keras, Seaborn, Plotly и т. д.). 

# This Python file uses the following encoding in format utf-8
import plotly
import plotly.express as px
import plotly.graph_objects as go
from django.core.mail import send_mail, BadHeaderError, EmailMessage

import io
import csv
from io import BytesIO
import base64
import functools
import operator
import random
import unicodedata
import datetime
import pandas as pd
import seaborn as sns
import numpy as np
from efficient_apriori import apriori

from sklearn import metrics
from sklearn.cross_validation import train_test_split
from sklearn.metrics import classification_report, confusion_matrix

from sklearn.ensemble import RandomForestClassifier
from sklearn.cluster import KMeans
from sklearn.svm import SVC
from sklearn.grid_search import GridSearchCV
from sklearn.neighbors import KNeighborsClassifier
from sklearn.tree import DecisionTreeClassifier
from sklearn.linear_model import LinearRegression
from sklearn.naive_bayes import GaussianNB

import tensorflow as tf
from tensorflow import keras
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout
from tensorflow.keras.optimizers import RMSprop

from django.template.loader import get_template, render_to_string
from django.shortcuts import render
from django.views.generic import TemplateView
from django.views.generic.detail import DetailView
from django.views import View
from django.db.models import Count
from django.shortcuts import redirect

from portal.models import Subscribe

from el_pagination.views import AjaxListView
from django.http import Http404, JsonResponse, HttpResponse, HttpResponseRedirect, HttpResponseServerError
from django.db.models import Q
from django.contrib.staticfiles.templatetags.staticfiles import static

Теперь мы готовы создать функцию, которую я назвал upload_csv, она берёт CSV-файл, загруженный в наши шаблоны, и создаст фрейм данных Pandas. Мы будем делать самые разные вещи с данными этого файла, в зависимости от запросов пользователя. Когда пользователь кликает по опциям фронтенда, мы сохраняем опции в переменной, переменная, в свою очередь, может принимать эти значения:

  • _plot — строим график рассеяния или тепловую карту.
  • _cate — выполняем алгоритм Априори для категориальных переменных.
  • _unsuper — выбираем один алгоритм ML без учителя.
  • _super — выполняем алгоритм с учителем.

Когда мы кликаем по опции, тип контролируемого или неконтролируемого алгоритма хранится в переменной 'algo', затем в зависимости от того, какой тип выбран, мы запускаем другую модель из опций Scikit-Learn, всё это выглядит так: 

def upload_csv(request):	
	if request.method == "POST":
	    file = request.FILES['csv_file']
	    df = pd.read_csv(file)
	    t = []
	    names = list(df.head(0))
	    df_target = df
	    grafica = request.POST['graficas']
	    if request.POST['submit'] == '_plot': 
	        if grafica == "scatter":
		        fig = px.scatter_matrix(df_target)
		        graph_div = plotly.offline.plot(fig, auto_open = False, output_type="div")
		        context = {'graph_div': graph_div}
		        return render (request, "plottings.html", context)
	        if grafica == "heatmap":
		        fig = go.Figure(data=go.Heatmap(z = df.corr(),x=names,y=names))
		        graph_div = plotly.offline.plot(fig, auto_open = False, output_type="div")
		        context = {'graph_div': graph_div}
		        return render (request, "heatmap.html", context)
	    if request.POST['submit'] == '_cate':
		    h = list(df.head(0))
		    t.append(list(h))
		    for i in range(0, len(df)):
			    a = list(df.iloc[i])
			    t.append(a)
		    itemsets, rules = apriori(t, min_support=0.2,  min_confidence=1)
		    context = {'rules': rules}           	  
		    return render(request, "apriori.html", context)	
	    lon = len(list(df.head(0)))
	    header = list(df[0:lon])
	    target = header[lon-1]
	    y = np.array(df[target])
	    df.drop(target,axis=1,inplace=True)
	    X = df.values
	    Z = X
	    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=101) 
	    graph_div = ''
	    pred = ''
	    algo = request.POST['algoritmo']	
	    grafica = request.POST['graficas']
	    clasi = request.POST['clasi']
	
	    if request.POST['submit'] == '_unsuper':
			    Nc = range(1, 20)
			    kmeans = [KMeans(n_clusters=i) for i in Nc]
			    score = [kmeans[i].fit(Z).score(Z) for i in range(len(kmeans))]
			    fig = go.Figure(data=go.Scatter(y=score, x=list(Nc), mode='markers'))
			    fig.update_xaxes(title="Number of Clusters")
			    fig.update_yaxes(title="Error")
			    fig.update_layout(autosize=False, width=800,height=500)
			    graph = plotly.offline.plot(fig, auto_open = False, output_type="div")           
			    nclusters = int(request.POST['clusters'])
			    kmeans = KMeans(n_clusters=nclusters)
			    model = kmeans.fit(Z)
			    labels = kmeans.labels_
			    resultados = []
			    for i in labels:
		                    resultados.append(i)	
			    with open('/var/www/trainyourmodel/static/labels.csv', 'w', ) as myfile:
				    wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
				    for word in resultados:
					    wr.writerow([word])	
			    context = {'graph': graph, 'labels': labels}           
			    return render(request, "kmeans.html", context)					
	    if request.POST['submit'] == '_super': 	
		    if algo == 'Linear Regression':
			    lm = LinearRegression()
			    model = lm.fit(X_train,y_train)
			    pred = lm.predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse = np.sqrt(metrics.mean_squared_error(y_test,pred))
			    coef = pd.DataFrame(lm.coef_, df.columns, columns=['Coefficient'])	
			    fig = go.Figure(data=go.Scatter(x=y_test, y=pred, mode='markers'))
			    fig.update_xaxes(title="Test Sample")
			    fig.update_yaxes(title="Predictions")
			    fig.update_layout(autosize=False, width=800,height=500)
			    scatter = plotly.offline.plot(fig, auto_open = False, output_type="div")
			    resultados = []
			    for i in pred:
		                    resultados.append(i)	
			    with open('/var/www/trainyourmodel/static/pred.csv', 'w', ) as myfile:
				    wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
				    for word in resultados:
					    wr.writerow([word])	
			    context = {'pred':pred, 'scatter': scatter, 'mae': mae, 'mse': mse, 'rmse': rmse, 'coef': coef}           
			    return render(request, "scatter.html", context)	
		    if algo == 'Support Vector Machine':
			    param_grid = {'C':[0.1,1,10,100,1000],'gamma':[1,0.1,0.01,0.001,0.0001]}
			    grid = GridSearchCV(SVC(),param_grid,verbose=3)
			    model = grid.fit(X_train,y_train)
			    pred = grid.predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse = np.sqrt(metrics.mean_squared_error(y_test,pred))
			    matrix = print(confusion_matrix(y_test,pred))
			    report = print(classification_report(y_test,pred))
		    if algo == 'K-Nearest Neighbor':
			    knn = KNeighborsClassifier(n_neighbors=1)
			    model = knn.fit(X_train,y_train)
			    pred = knn.predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse= np.sqrt(metrics.mean_squared_error(y_test,pred))
			    matrix = confusion_matrix(y_test,pred)
			    report = classification_report(y_test,pred)	
		    if algo == 'Naive Bayes':
			    gnb = GaussianNB()
			    pred = gnb.fit(X_train, y_train).predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse = np.sqrt(metrics.mean_squared_error(y_test,pred))
			    matrix = confusion_matrix(y_test,pred)
			    report = classification_report(y_test,pred)	
		    if algo == 'Decision Trees':
			    dtree = DecisionTreeClassifier()
			    model = dtree.fit(X_train,y_train)
			    pred = dtree.predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse = np.sqrt(metrics.mean_squared_error(y_test,pred))
			    matrix = confusion_matrix(y_test,pred)
			    report = classification_report(y_test,pred)
		    if algo == 'Random Forest':
			    forest = RandomForestClassifier(n_estimators=200)
			    model = forest.fit(X_train,y_train)
			    pred = forest.predict(X_test)
			    mae = metrics.mean_absolute_error(y_test,pred)
			    mse = metrics.mean_squared_error(y_test,pred)
			    rmse = np.sqrt(metrics.mean_squared_error(y_test,pred))
			    matrix = confusion_matrix(y_test,pred)	
			    report = classification_report(y_test,pred)	
		    resultados = []
		    for i in pred:
			    resultados.append(i)	
		    with open('/var/www/trainyourmodel/static/pred.csv', 'w', ) as myfile:
			    wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
			    for word in resultados:
			            wr.writerow([word])	
		    context = {'matrix00': matrix[0][0], 'matrix01': matrix[0][1], 'matrix10': matrix[1][0], 'matrix11': matrix[1][1], 'mae': mae, 'mse': mse, 'rmse': rmse, 'f1': report[0:52], 'f2': report[54:106], 'f3': report[107:159], 'f4': report[160:213]}           
		    return render(request, "upload_csv.html", context)	

Наконец, если вы нажмёте на кнопку «deep learning» в верхней части сайта, то перейдёте на другой экран, входные данные на этом экране берутся функцией под названием neural, которая будет работать с нейронными сетями. Есть два типа сетей на выбор: «mean-squared-error» или «binary cross-entropy». Как только функция проверяет тип сети для запуска, она берёт эпохи, пакет, значения плотности и выводит на экран модель Keras, показывая точность прогнозов, получается что-то вроде этого:

def neural(request):	
	if request.method == "POST":
	    file = request.FILES['file']
	    df = pd.read_csv(file)
	    df_target = df
	    lon = len(list(df.head(0)))
	    dim = len(list(df.head(0))) -1	
	    header = list(df[0:lon])
	    target = header[dim]
	    y = np.array(df[target])
	    df.drop(target,axis=1,inplace=True)
	    X = df.values
	    X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3,random_state=101) 
	    graph_div = ''
	    pred = ''
	    red = request.POST['red']
	    dense = int(request.POST['dense'])	
	    epochs = int(request.POST['epochs'])
	    batch = int(request.POST['batch'])
	    #nodes = int(request.POST['categories'])
	    if red == 'Binary Cross-Entropy':
			    model= Sequential()
			    model.add(Dense(dense, input_dim=dim, activation='relu'))
			    model.add(Dense(dim, activation='relu'))
			    model.add(Dense(1, activation='sigmoid'))
			    model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
			    model.fit(x=X_train,y=y_train,batch_size=batch, epochs=epochs,shuffle=True)
			    pred = model.predict(X_test)	
			    _, accuracy = model.evaluate(X_test, y_test)			    
			    accu = (accuracy*100)
			    resultados = []
			    for i in pred:
		                    resultados.append(i[0])	
			    with open('/var/www/trainyourmodel/static/pred.csv', 'w', ) as myfile:
				    wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
				    for word in resultados:
					    wr.writerow([word])	
			    context = {'accu': accu, 'pred': pred}           
			    return render(request, "neural.html", context)						
	    if red == 'Mean Squared Error':
			    model = Sequential()
			    model.add(Dense(dense, input_dim=dim, activation='relu'))
			    model.add(Dense(1, activation='linear'))
			    model.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])
			    model.fit(x=X,y=y,batch_size=batch, epochs=epochs,shuffle=True)
			    pred = model.predict(X_test)
			    _, accuracy = model.evaluate(X_test, y_test)
			    resultados = []
			    for i in pred:
		                    resultados.append(i[0])	
			    with open('/var/www/trainyourmodel/static/pred.csv', 'w', ) as myfile:
				    wr = csv.writer(myfile, quoting=csv.QUOTE_ALL)
				    for word in resultados:
					    wr.writerow([word])
			    accu = (accuracy*100)	
			    context = {'accu': accu, 'pred': pred}           
			    return render(request, "neural.html", context)

Заключение


В этом проекте мы увидели, как сравнительно легко внедрить приложение для компьютерного обучения на сервере Linux с помощью Django, мы могли бы также использовать Flask (еще одно популярное приложение на Python) или многие другие технологии. Дело в том, что, если вы просто хотите, чтобы ваша модель работала на фронтенде сайта, чтобы пользователи могли ею пользоваться, вам нужно лишь встроить свои модели в функции, которые принимают входные данные, настроенные пользователями. 

Обратите внимание, что мы делали всё на лету, не нуждаясь в базе данных. Это очень маленький навык, если вы являетесь инженером-машиностроителем и вам необходимо быстро построить и развернуть модель. Проблема с реальными платформами машинного обучения заключается в том, что им необходимо принимать огромные объёмы данных от тысяч пользователей и хранить пользовательскую информацию, то есть проблема этих платформ заключается не в сложности выполняемых ими моделей, а в сложности облачных операций, которые они выполняют, чтобы иметь возможность справляться с их рабочей нагрузкой. Что ж, на сегодня всё, надеюсь, вам понравился этот проект и понравилось программировать его!


image



SkillFactory
Школа Computer Science. Скидка 10% по коду HABR

Комментарии 3

    +5
    Это прям пример, как не надо писать код. Бесконечные портянки if; код, сделанный копипастом; абсолютные пути к файлам.
      0
      Кстати, замечаю подобное у людей из ML.
      0
      Вообще-то на КДПВ, очень похоже, Ди Каприо после приложеньки, показывающей того же персонажа другого пола.

      Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

      Самое читаемое