Scikit-learn: полное руководство по машинному обучению на Python

Пройдите тест, узнайте какой профессии подходите
Сколько вам лет
0%
До 18
От 18 до 24
От 25 до 34
От 35 до 44
От 45 до 49
От 50 до 54
Больше 55

Для кого эта статья:

  • Начинающие аналитики и специалисты по машинному обучению
  • Студенты и обучающиеся на курсах по Data Science
  • Разработчики, желающие интегрировать машинное обучение в свои проекты

    Scikit-learn стал золотым стандартом машинного обучения на Python, позволяя превратить разрозненные данные в работающие предсказательные модели буквально за несколько строк кода. Для многих начинающих специалистов путь в мир ML начинается именно с этой библиотеки, которая удивительным образом сочетает мощь алгоритмов с простотой интерфейса. В этом руководстве я проведу вас от первой установки до создания полноценных моделей, которые можно интегрировать в рабочие проекты, раскрывая все ключевые особенности Scikit-learn на практических примерах. Готовы наконец-то понять, как работает машинное обучение не в теории, а в коде? 🚀

Начало работы со Scikit-learn: установка и настройка среды

Scikit-learn – мощная библиотека для машинного обучения на Python, предоставляющая широкий спектр алгоритмов для решения задач классификации, регрессии, кластеризации и многого другого. Давайте разберёмся, как начать работу с этим инструментом.

Пошаговый план для смены профессии

Установка Scikit-learn

Установить библиотеку можно несколькими способами:

  • Через pip: pip install scikit-learn
  • Через conda: conda install scikit-learn
  • Через дистрибутив Anaconda, где Scikit-learn уже предустановлен

Scikit-learn имеет несколько зависимостей, которые обычно устанавливаются автоматически:

  • NumPy – для эффективной работы с массивами
  • SciPy – для научных вычислений
  • Matplotlib – для визуализации (опционально)
  • Pandas – для работы с данными (опционально, но рекомендуется)

Проверить успешность установки можно следующим образом:

Python
Скопировать код
import sklearn
print(sklearn.__version__)

Рекомендую использовать виртуальные окружения для изоляции проектов. Создать окружение можно командой:

Bash
Скопировать код
python -m venv sklearn_env
source sklearn_env/bin/activate # для Linux/Mac
sklearn_env\Scripts\activate # для Windows

Настройка среды разработки также важна для комфортной работы. Вы можете использовать Jupyter Notebook для интерактивной разработки или любую IDE с поддержкой Python (PyCharm, VS Code).

Среда разработки Преимущества Недостатки
Jupyter Notebook Интерактивность, визуализация, пошаговое выполнение Сложности с версионированием, организацией больших проектов
PyCharm Полноценная IDE, отладка, профилирование Высокие требования к ресурсам, платная полная версия
VS Code Легковесность, расширения, интеграция с Git Требует настройки для комфортной работы с ML
Google Colab Бесплатный GPU/TPU, предустановленные библиотеки Ограничения бесплатной версии, зависимость от интернета

Александр Петров, Lead Data Scientist

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

С тех пор я придерживаюсь железного правила: всегда использовать виртуальные окружения с фиксированными версиями библиотек и файл requirements.txt для каждого проекта. Для Scikit-learn это особенно важно, поскольку API может меняться между версиями. Этот урок стоил мне бессонной ночи, но сэкономил месяцы проблем в будущем.

Основные концепции машинного обучения с Scikit-learn

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

Единый интерфейс моделей

Одно из главных преимуществ Scikit-learn — унифицированный API для всех алгоритмов. Практически каждая модель реализует следующие методы:

  • fit(X, y) — обучение модели на данных X и целевых значениях y
  • predict(X) — предсказание для новых данных X
  • score(X, y) — оценка качества модели на тестовых данных
  • transform(X) — преобразование данных (для препроцессоров)

Такой подход позволяет легко заменять один алгоритм другим без существенных изменений в коде.

Представление данных

Scikit-learn ожидает данные в виде двумерных массивов формы (nsamples, nfeatures):

  • n_samples — количество образцов (объектов, наблюдений)
  • n_features — количество признаков (параметров)

Целевые переменные обычно представлены в виде одномерного массива формы (n_samples,).

Пример базовой структуры данных:

Python
Скопировать код
import numpy as np
from sklearn import datasets

# Загрузка датасета ирисов
iris = datasets.load_iris()
X = iris.data # Признаки: (150, 4)
y = iris.target # Метки классов: (150,)

print(f"Форма данных X: {X.shape}")
print(f"Форма целевой переменной y: {y.shape}")

Основные типы задач машинного обучения

Scikit-learn предлагает инструменты для решения различных задач:

Тип задачи Описание Примеры алгоритмов Метрики оценки
Классификация Определение класса объекта LogisticRegression, RandomForestClassifier, SVM accuracy, precision, recall, f1-score
Регрессия Предсказание числовых значений LinearRegression, Ridge, Lasso, SVR MSE, MAE, R²
Кластеризация Группировка схожих объектов KMeans, DBSCAN, AgglomerativeClustering silhouette score, inertia
Уменьшение размерности Снижение числа признаков PCA, t-SNE, UMAP explained variance, reconstruction error

Конвейеры обработки данных

Pipeline (конвейер) — мощная концепция, позволяющая объединить препроцессинг, выделение признаков и модель в единую последовательность:

Python
Скопировать код
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.svm import SVC

# Создание конвейера: стандартизация -> SVM
pipe = Pipeline([
('scaler', StandardScaler()),
('classifier', SVC())
])

# Обучение всего конвейера одной командой
pipe.fit(X_train, y_train)

# Предсказание с использованием обученного конвейера
predictions = pipe.predict(X_test)

Конвейеры решают несколько проблем:

  • Предотвращают утечку данных между тренировочной и тестовой выборками
  • Упрощают код и делают его более читаемым
  • Обеспечивают последовательное применение всех шагов
  • Упрощают тюнинг гиперпараметров для всего процесса

Практические кейсы: от классификации до кластеризации

Теория важна, но настоящее понимание приходит через практику. Рассмотрим несколько практических примеров использования Scikit-learn для разных задач машинного обучения. 💡

Классификация: предсказание категорий

Решим задачу классификации на примере датасета ирисов — классической задачи определения вида цветка по измерениям его частей:

Python
Скопировать код
from sklearn.datasets import load_iris
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import classification_report

# Загрузка данных
iris = load_iris()
X, y = iris.data, iris.target

# Разделение на обучающую и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.25, random_state=42
)

# Создание и обучение модели
clf = RandomForestClassifier(n_estimators=100, random_state=42)
clf.fit(X_train, y_train)

# Предсказание и оценка
y_pred = clf.predict(X_test)
print(classification_report(y_test, y_pred, target_names=iris.target_names))

# Важность признаков
feature_importance = dict(zip(iris.feature_names, clf.feature_importances_))
for feature, importance in sorted(feature_importance.items(), key=lambda x: x[1], reverse=True):
print(f"{feature}: {importance:.4f}")

Регрессия: предсказание числовых значений

Рассмотрим задачу предсказания цен на жильё с помощью линейной регрессии:

Python
Скопировать код
from sklearn.datasets import fetch_california_housing
from sklearn.linear_model import LinearRegression
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error, r2_score
import numpy as np

# Загрузка данных
housing = fetch_california_housing()
X, y = housing.data, housing.target

# Разделение выборки
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42
)

# Обучение модели
reg = LinearRegression()
reg.fit(X_train, y_train)

# Предсказание
y_pred = reg.predict(X_test)

# Оценка качества
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
r2 = r2_score(y_test, y_pred)

print(f"RMSE: {rmse:.4f}")
print(f"R²: {r2:.4f}")

# Коэффициенты модели
for i, feature in enumerate(housing.feature_names):
print(f"{feature}: {reg.coef_[i]:.6f}")
print(f"Intercept: {reg.intercept_:.6f}")

Кластеризация: выявление групп

Теперь рассмотрим задачу кластеризации — группировки схожих объектов без предварительных меток:

Python
Скопировать код
from sklearn.cluster import KMeans, DBSCAN
from sklearn.datasets import make_blobs
from sklearn.metrics import silhouette_score
import matplotlib.pyplot as plt
import numpy as np

# Генерация данных
X, _ = make_blobs(n_samples=300, centers=4, cluster_std=0.60, random_state=0)

# Метод K-средних
kmeans = KMeans(n_clusters=4, random_state=0)
kmeans_labels = kmeans.fit_predict(X)

# Метод DBSCAN
dbscan = DBSCAN(eps=0.3, min_samples=10)
dbscan_labels = dbscan.fit_predict(X)

# Оценка качества кластеризации
print(f"K-means silhouette score: {silhouette_score(X, kmeans_labels):.4f}")
if len(set(dbscan_labels)) > 1: # DBSCAN может определить шум (-1)
dbscan_score = silhouette_score(X[dbscan_labels >= 0], dbscan_labels[dbscan_labels >= 0])
print(f"DBSCAN silhouette score: {dbscan_score:.4f}")

# Визуализация результатов (требуется matplotlib)
plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(X[:, 0], X[:, 1], c=kmeans_labels, cmap='viridis')
plt.title('K-means Clustering')

plt.subplot(1, 2, 2)
plt.scatter(X[:, 0], X[:, 1], c=dbscan_labels, cmap='viridis')
plt.title('DBSCAN Clustering')

plt.tight_layout()
plt.show()

Уменьшение размерности

Разберём пример снижения размерности данных для визуализации и ускорения обучения моделей:

Python
Скопировать код
from sklearn.decomposition import PCA
from sklearn.manifold import TSNE
from sklearn.datasets import load_digits

# Загрузка данных
digits = load_digits()
X, y = digits.data, digits.target

# Применение PCA
pca = PCA(n_components=2)
X_pca = pca.fit_transform(X)
print(f"Explained variance ratio: {sum(pca.explained_variance_ratio_):.4f}")

# Применение t-SNE
tsne = TSNE(n_components=2, random_state=42)
X_tsne = tsne.fit_transform(X)

# Визуализация результатов
plt.figure(figsize=(12, 5))

plt.subplot(1, 2, 1)
scatter = plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis')
plt.title('PCA visualization')
plt.colorbar(scatter)

plt.subplot(1, 2, 2)
scatter = plt.scatter(X_tsne[:, 0], X_tsne[:, 1], c=y, cmap='viridis')
plt.title('t-SNE visualization')
plt.colorbar(scatter)

plt.tight_layout()
plt.show()

Михаил Соколов, ML Engineer

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

Первый подход с использованием сложной нейросети не дал ожидаемых результатов. Модель оказалась переобученной и неинтерпретируемой. Я решил вернуться к основам и применить инструменты Scikit-learn.

С помощью RandomForestClassifier и встроенной функциональности featureimportances мы выявили всего 12 ключевых признаков из сотен. Среди них оказались: резкие изменения в объёме звонков, частые обращения в техподдержку и снижение расходов перед уходом.

Что действительно изменило ситуацию — это использование SMOTE для баланса классов (было всего 4% оттока) и правильная метрика (precision-recall AUC вместо обычной accuracy). В результате мы достигли 83% точности определения клиентов на грани ухода, что позволило компании запустить программу удержания и сэкономить миллионы.

Scikit-learn доказал, что иногда простые, но правильно настроенные инструменты могут превосходить сложные черные ящики.

Оптимизация и оценка моделей в Scikit-learn

Создание базовой модели — лишь начало пути. Для достижения высокой производительности необходимо правильно настраивать параметры и оценивать качество моделей. Scikit-learn предоставляет богатый арсенал инструментов для этих задач. 🔍

Разделение данных и перекрестная проверка

Надежная оценка моделей начинается с правильного разделения данных:

Python
Скопировать код
from sklearn.model_selection import train_test_split, cross_val_score, KFold

# Простое разделение на тренировочную и тестовую выборки
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)

# Перекрестная проверка для более надежной оценки
cv = KFold(n_splits=5, shuffle=True, random_state=42)
scores = cross_val_score(model, X, y, cv=cv, scoring='accuracy')
print(f"Cross-validation scores: {scores}")
print(f"Average accuracy: {scores.mean():.4f} ± {scores.std():.4f}")

Подбор гиперпараметров

Scikit-learn предлагает несколько способов оптимизации параметров моделей:

Python
Скопировать код
from sklearn.model_selection import GridSearchCV, RandomizedSearchCV
from sklearn.ensemble import RandomForestClassifier
from scipy.stats import randint

# Определение пространства параметров
param_grid = {
'n_estimators': [50, 100, 200],
'max_depth': [None, 10, 20, 30],
'min_samples_split': [2, 5, 10],
'min_samples_leaf': [1, 2, 4]
}

# Полный перебор параметров (Grid Search)
grid_search = GridSearchCV(
RandomForestClassifier(random_state=42),
param_grid,
cv=5,
scoring='accuracy',
n_jobs=-1
)
grid_search.fit(X_train, y_train)

print(f"Best parameters: {grid_search.best_params_}")
print(f"Best cross-validation score: {grid_search.best_score_:.4f}")

# Случайный поиск (Randomized Search) – эффективнее для большого пространства параметров
param_dist = {
'n_estimators': randint(50, 500),
'max_depth': randint(10, 50),
'min_samples_split': randint(2, 20),
'min_samples_leaf': randint(1, 10)
}

random_search = RandomizedSearchCV(
RandomForestClassifier(random_state=42),
param_distributions=param_dist,
n_iter=100,
cv=5,
scoring='accuracy',
n_jobs=-1,
random_state=42
)
random_search.fit(X_train, y_train)

print(f"Best parameters from random search: {random_search.best_params_}")
print(f"Best cross-validation score: {random_search.best_score_:.4f}")

Метрики оценки моделей

Разные задачи требуют разных метрик оценки. Scikit-learn предлагает широкий набор метрик:

Python
Скопировать код
from sklearn.metrics import (accuracy_score, precision_score, recall_score, f1_score,
roc_auc_score, confusion_matrix, classification_report,
mean_squared_error, mean_absolute_error, r2_score)

# Для классификации
y_pred = model.predict(X_test)
y_prob = model.predict_proba(X_test)[:, 1] # Вероятности для положительного класса

print(f"Accuracy: {accuracy_score(y_test, y_pred):.4f}")
print(f"Precision: {precision_score(y_test, y_pred):.4f}")
print(f"Recall: {recall_score(y_test, y_pred):.4f}")
print(f"F1 score: {f1_score(y_test, y_pred):.4f}")
print(f"ROC AUC: {roc_auc_score(y_test, y_prob):.4f}")

print("\nConfusion Matrix:")
print(confusion_matrix(y_test, y_pred))

print("\nClassification Report:")
print(classification_report(y_test, y_pred))

# Для регрессии
y_pred_reg = reg_model.predict(X_test)

print(f"Mean Squared Error: {mean_squared_error(y_test, y_pred_reg):.4f}")
print(f"Root Mean Squared Error: {mean_squared_error(y_test, y_pred_reg, squared=False):.4f}")
print(f"Mean Absolute Error: {mean_absolute_error(y_test, y_pred_reg):.4f}")
print(f"R² Score: {r2_score(y_test, y_pred_reg):.4f}")

Кривые обучения и проверки

Для диагностики проблем переобучения и недообучения используются кривые обучения:

Python
Скопировать код
from sklearn.model_selection import learning_curve
import matplotlib.pyplot as plt
import numpy as np

# Вычисление кривых обучения
train_sizes, train_scores, test_scores = learning_curve(
model, X, y, cv=5, scoring='accuracy',
train_sizes=np.linspace(0.1, 1.0, 10)
)

# Средние значения и стандартные отклонения
train_mean = np.mean(train_scores, axis=1)
train_std = np.std(train_scores, axis=1)
test_mean = np.mean(test_scores, axis=1)
test_std = np.std(test_scores, axis=1)

# Визуализация кривых
plt.figure(figsize=(10, 6))
plt.plot(train_sizes, train_mean, label='Training score', color='blue', marker='o')
plt.fill_between(train_sizes, train_mean – train_std, train_mean + train_std, color='blue', alpha=0.15)
plt.plot(train_sizes, test_mean, label='Cross-validation score', color='green', marker='s')
plt.fill_between(train_sizes, test_mean – test_std, test_mean + test_std, color='green', alpha=0.15)
plt.title('Learning Curve')
plt.xlabel('Training Examples')
plt.ylabel('Accuracy Score')
plt.legend(loc='best')
plt.grid(True)
plt.show()

Проблема Признаки Решения
Недообучение (высокое смещение) Низкая точность и на тренировочных, и на тестовых данных – Использовать более сложную модель<br> – Добавить признаки<br> – Уменьшить регуляризацию
Переобучение (высокая дисперсия) Высокая точность на тренировочных данных, низкая на тестовых – Собрать больше данных<br> – Применить регуляризацию<br> – Упростить модель<br> – Использовать ансамблевые методы
Проблемы с признаками Нестабильные результаты, низкая точность – Feature engineering<br> – Нормализация/стандартизация<br> – Отбор признаков<br> – Обработка выбросов
Дисбаланс классов Хорошая точность, но низкие recall/precision – Повторная выборка (upsampling/downsampling)<br> – SMOTE<br> – Настройка class_weight<br> – Использование правильных метрик

Интеграция Scikit-learn в реальные Python проекты

Создание моделей машинного обучения — это только часть процесса. В реальных проектах необходимо интегрировать модели в рабочие приложения, обеспечивать их обслуживание и мониторинг. 🚀

Сохранение и загрузка моделей

После обучения модели её нужно сохранить для последующего использования. Scikit-learn предлагает несколько способов сериализации:

Python
Скопировать код
import pickle
import joblib
from sklearn.ensemble import RandomForestClassifier

# Обучение модели
model = RandomForestClassifier(n_estimators=100, random_state=42)
model.fit(X_train, y_train)

# Сохранение модели с помощью pickle
with open('model.pkl', 'wb') as f:
pickle.dump(model, f)

# Загрузка модели
with open('model.pkl', 'rb') as f:
loaded_model = pickle.load(f)

# Альтернативный способ с использованием joblib (эффективнее для больших моделей)
joblib.dump(model, 'model.joblib')
loaded_model = joblib.load('model.joblib')

# Проверка загруженной модели
print(f"Accuracy on test data: {loaded_model.score(X_test, y_test):.4f}")

Создание конвейеров обработки данных

В реальных проектах важно автоматизировать все этапы обработки данных:

Python
Скопировать код
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.feature_selection import SelectKBest, f_classif
from sklearn.ensemble import RandomForestClassifier

# Создание полного конвейера обработки данных
full_pipeline = Pipeline([
('scaler', StandardScaler()), # Нормализация данных
('feature_selection', SelectKBest(f_classif, k=10)), # Отбор признаков
('classifier', RandomForestClassifier(n_estimators=100, random_state=42)) # Классификатор
])

# Обучение всего конвейера
full_pipeline.fit(X_train, y_train)

# Сохранение конвейера
joblib.dump(full_pipeline, 'full_pipeline.joblib')

# Использование конвейера для предсказаний
y_pred = full_pipeline.predict(X_test)
print(f"Pipeline accuracy: {accuracy_score(y_test, y_pred):.4f}")

Интеграция с веб-приложениями

Часто модели машинного обучения необходимо внедрить в веб-приложения. Вот пример интеграции с Flask:

Python
Скопировать код
# app.py
from flask import Flask, request, jsonify
import joblib
import numpy as np

app = Flask(__name__)

# Загрузка модели
model = joblib.load('model.joblib')

@app.route('/predict', methods=['POST'])
def predict():
# Получение данных из запроса
data = request.json
features = np.array(data['features']).reshape(1, -1)

# Генерация предсказания
prediction = model.predict(features)[0]
probability = model.predict_proba(features)[0].tolist()

# Возврат результата
return jsonify({
'prediction': int(prediction),
'probability': probability
})

if __name__ == '__main__':
app.run(debug=True)

Клиентский запрос к этому API может выглядеть так:

Python
Скопировать код
import requests

# Отправка запроса к API
url = "http://localhost:5000/predict"
data = {
"features": [5\.1, 3.5, 1.4, 0.2] # Пример признаков для ириса
}
response = requests.post(url, json=data)

# Обработка ответа
result = response.json()
print(f"Prediction: Class {result['prediction']}")
print(f"Probabilities: {result['probability']}")

Мониторинг производительности моделей

Со временем производительность моделей может ухудшаться из-за изменения данных. Мониторинг помогает отслеживать такие изменения:

  • Отслеживайте распределение входных данных для выявления data drift
  • Периодически переоценивайте модель на свежих данных
  • Внедрите автоматическую систему уведомлений о падении качества
  • Настройте процесс автоматического переобучения моделей

Пример простого скрипта для мониторинга производительности:

Python
Скопировать код
import pandas as pd
from sklearn.metrics import accuracy_score
import joblib
import datetime
import json

def monitor_model_performance(model_path, new_data_path, metrics_log_path):
# Загрузка модели
model = joblib.load(model_path)

# Загрузка новых данных
new_data = pd.read_csv(new_data_path)
X_new = new_data.drop('target', axis=1)
y_new = new_data['target']

# Вычисление производительности
y_pred = model.predict(X_new)
accuracy = accuracy_score(y_new, y_pred)

# Запись метрики
timestamp = datetime.datetime.now().isoformat()
metrics_entry = {
'timestamp': timestamp,
'accuracy': accuracy,
'data_size': len(X_new)
}

# Добавление в журнал метрик
try:
with open(metrics_log_path, 'r') as f:
metrics_log = json.load(f)
except (FileNotFoundError, json.JSONDecodeError):
metrics_log = []

metrics_log.append(metrics_entry)

with open(metrics_log_path, 'w') as f:
json.dump(metrics_log, f, indent=2)

# Проверка на снижение производительности
if len(metrics_log) > 1 and accuracy < metrics_log[-2]['accuracy'] * 0.95:
print(f"WARNING: Performance dropped by more than 5%! Current: {accuracy:.4f}, Previous: {metrics_log[-2]['accuracy']:.4f}")

return accuracy

# Пример использования
performance = monitor_model_performance('model.joblib', 'new_data.csv', 'metrics_log.json')
print(f"Current model performance: {performance:.4f}")

Масштабирование решений

При росте объема данных или сложности моделей, может потребоваться масштабирование решений:

  • Параллельные вычисления: Используйте параметр n_jobs=-1 во многих алгоритмах Scikit-learn для задействования всех доступных ядер
  • Инкрементное обучение: Некоторые алгоритмы (например, SGDClassifier) поддерживают инкрементное обучение с помощью метода partial_fit()
  • Распределенные вычисления: Для очень больших данных рассмотрите Dask-ML или Spark MLlib, которые имеют API, похожий на Scikit-learn
  • Оптимизация памяти: Используйте генераторы данных и итераторы для экономии памяти

Scikit-learn предлагает множество инструментов для интеграции моделей машинного обучения в реальные рабочие процессы. Выбирайте подходящие решения с учетом специфики задачи и требований проекта.

Scikit-learn предлагает не просто набор алгоритмов, но целую экосистему инструментов для всего жизненного цикла моделей машинного обучения. От предварительной обработки данных до промышленного внедрения — библиотека обеспечивает единообразный и продуманный интерфейс, который делает машинное обучение доступным для разработчиков с разным уровнем опыта. Освоив принципы построения моделей в Scikit-learn, вы заложите прочный фундамент для более глубокого погружения в специализированные области машинного обучения, сохраняя при этом практический подход к решению реальных задач.

Загрузка...