{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "lesson_12-R.ipynb", "provenance": [], "collapsed_sections": [] }, "kernelspec": { "name": "ir", "display_name": "R" }, "language_info": { "name": "R" }, "coopTranslator": { "original_hash": "fab50046ca413a38939d579f8432274f", "translation_date": "2025-09-04T08:44:05+00:00", "source_file": "4-Classification/3-Classifiers-2/solution/R/lesson_12-R.ipynb", "language_code": "uk" } }, "cells": [ { "cell_type": "markdown", "metadata": { "id": "jsFutf_ygqSx" }, "source": [] }, { "cell_type": "markdown", "metadata": { "id": "HD54bEefgtNO" }, "source": [ "## Класифікатори кухонь 2\n", "\n", "У цьому другому уроці класифікації ми дослідимо `більше способів` класифікувати категоричні дані. Ми також розглянемо наслідки вибору одного класифікатора над іншим.\n", "\n", "### [**Тест перед лекцією**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/23/)\n", "\n", "### **Передумови**\n", "\n", "Ми припускаємо, що ви завершили попередні уроки, оскільки будемо використовувати деякі концепції, які ми вивчали раніше.\n", "\n", "Для цього уроку нам знадобляться наступні пакети:\n", "\n", "- `tidyverse`: [tidyverse](https://www.tidyverse.org/) — це [колекція пакетів для R](https://www.tidyverse.org/packages), створена для того, щоб зробити роботу з даними швидшою, простішою та цікавішою!\n", "\n", "- `tidymodels`: [tidymodels](https://www.tidymodels.org/) — це [фреймворк](https://www.tidymodels.org/packages/) для моделювання та машинного навчання, який складається з колекції пакетів.\n", "\n", "- `themis`: [пакет themis](https://themis.tidymodels.org/) надає додаткові кроки для роботи з незбалансованими даними.\n", "\n", "Ви можете встановити їх за допомогою:\n", "\n", "`install.packages(c(\"tidyverse\", \"tidymodels\", \"kernlab\", \"themis\", \"ranger\", \"xgboost\", \"kknn\"))`\n", "\n", "Або ж скрипт нижче перевірить, чи є у вас необхідні пакети для завершення цього модуля, і встановить їх, якщо вони відсутні.\n" ] }, { "cell_type": "code", "metadata": { "id": "vZ57IuUxgyQt" }, "source": [ "suppressWarnings(if (!require(\"pacman\"))install.packages(\"pacman\"))\n", "\n", "pacman::p_load(tidyverse, tidymodels, themis, kernlab, ranger, xgboost, kknn)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "z22M-pj4g07x" }, "source": [ "## **1. Карта класифікації**\n", "\n", "У нашому [попередньому уроці](https://github.com/microsoft/ML-For-Beginners/tree/main/4-Classification/2-Classifiers-1) ми намагалися відповісти на запитання: як обрати між кількома моделями? Значною мірою це залежить від характеристик даних і типу задачі, яку ми хочемо вирішити (наприклад, класифікація чи регресія?).\n", "\n", "Раніше ми дізналися про різні варіанти класифікації даних за допомогою шпаргалки від Microsoft. Фреймворк машинного навчання Python, Scikit-learn, пропонує подібну, але більш деталізовану шпаргалку, яка може ще більше допомогти звузити вибір оцінювачів (інша назва для класифікаторів):\n", "\n", "

\n", " \n", "

\n" ] }, { "cell_type": "markdown", "metadata": { "id": "u1i3xRIVg7vG" }, "source": [ "> Порада: [відвідайте цю карту онлайн](https://scikit-learn.org/stable/tutorial/machine_learning_map/) і натискайте на шляхи, щоб прочитати документацію.\n", ">\n", "> [Сайт довідки Tidymodels](https://www.tidymodels.org/find/parsnip/#models) також надає чудову документацію про різні типи моделей.\n", "\n", "### **План** 🗺️\n", "\n", "Ця карта дуже корисна, якщо ви добре розумієте свої дані, адже ви можете «пройтися» її шляхами до рішення:\n", "\n", "- У нас є \\>50 зразків\n", "\n", "- Ми хочемо передбачити категорію\n", "\n", "- У нас є мічені дані\n", "\n", "- У нас менше ніж 100K зразків\n", "\n", "- ✨ Ми можемо обрати Linear SVC\n", "\n", "- Якщо це не спрацює, оскільки у нас числові дані\n", "\n", " - Ми можемо спробувати ✨ KNeighbors Classifier\n", "\n", " - Якщо це не спрацює, спробуйте ✨ SVC та ✨ Ensemble Classifiers\n", "\n", "Це дуже корисний шлях для слідування. Тепер давайте перейдемо до справи, використовуючи [tidymodels](https://www.tidymodels.org/) — фреймворк для моделювання: узгоджена та гнучка колекція пакетів R, розроблена для заохочення хороших статистичних практик 😊.\n", "\n", "## 2. Розділіть дані та вирішіть проблему незбалансованого набору даних.\n", "\n", "З наших попередніх уроків ми дізналися, що існує набір спільних інгредієнтів у наших кухнях. Також було помітно нерівномірний розподіл кількості кухонь.\n", "\n", "Ми вирішимо ці проблеми, виконавши наступне:\n", "\n", "- Видалимо найпоширеніші інгредієнти, які створюють плутанину між різними кухнями, використовуючи `dplyr::select()`.\n", "\n", "- Використаємо `recipe`, який попередньо обробляє дані, щоб підготувати їх до моделювання, застосовуючи алгоритм `over-sampling`.\n", "\n", "Ми вже розглядали це в попередньому уроці, тому це має бути легко 🥳!\n" ] }, { "cell_type": "code", "metadata": { "id": "6tj_rN00hClA" }, "source": [ "# Load the core Tidyverse and Tidymodels packages\n", "library(tidyverse)\n", "library(tidymodels)\n", "\n", "# Load the original cuisines data\n", "df <- read_csv(file = \"https://raw.githubusercontent.com/microsoft/ML-For-Beginners/main/4-Classification/data/cuisines.csv\")\n", "\n", "# Drop id column, rice, garlic and ginger from our original data set\n", "df_select <- df %>% \n", " select(-c(1, rice, garlic, ginger)) %>%\n", " # Encode cuisine column as categorical\n", " mutate(cuisine = factor(cuisine))\n", "\n", "\n", "# Create data split specification\n", "set.seed(2056)\n", "cuisines_split <- initial_split(data = df_select,\n", " strata = cuisine,\n", " prop = 0.7)\n", "\n", "# Extract the data in each split\n", "cuisines_train <- training(cuisines_split)\n", "cuisines_test <- testing(cuisines_split)\n", "\n", "# Display distribution of cuisines in the training set\n", "cuisines_train %>% \n", " count(cuisine) %>% \n", " arrange(desc(n))" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "zFin5yw3hHb1" }, "source": [ "### Робота з незбалансованими даними\n", "\n", "Незбалансовані дані часто негативно впливають на продуктивність моделі. Багато моделей працюють найкраще, коли кількість спостережень є рівною, і тому їм важко справлятися з незбалансованими даними.\n", "\n", "Існує два основних способи вирішення проблеми незбалансованих наборів даних:\n", "\n", "- додавання спостережень до меншої класу: `Over-sampling`, наприклад, використання алгоритму SMOTE, який синтетично генерує нові приклади меншої класу, використовуючи найближчих сусідів цих випадків.\n", "\n", "- видалення спостережень із більшої класу: `Under-sampling`\n", "\n", "У нашому попередньому уроці ми продемонстрували, як працювати з незбалансованими наборами даних, використовуючи `recipe`. Recipe можна уявити як план, який описує, які кроки слід застосувати до набору даних, щоб підготувати його до аналізу. У нашому випадку ми прагнемо до рівномірного розподілу кількості наших кухонь у `training set`. Давайте перейдемо до справи.\n" ] }, { "cell_type": "code", "metadata": { "id": "cRzTnHolhLWd" }, "source": [ "# Load themis package for dealing with imbalanced data\n", "library(themis)\n", "\n", "# Create a recipe for preprocessing training data\n", "cuisines_recipe <- recipe(cuisine ~ ., data = cuisines_train) %>%\n", " step_smote(cuisine) \n", "\n", "# Print recipe\n", "cuisines_recipe" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "KxOQ2ORhhO81" }, "source": [ "Тепер ми готові тренувати моделі 👩‍💻👨‍💻!\n", "\n", "## 3. Поза межами моделей багатономінальної регресії\n", "\n", "У нашому попередньому уроці ми розглянули моделі багатономінальної регресії. Давайте дослідимо більш гнучкі моделі для класифікації.\n", "\n", "### Машини опорних векторів\n", "\n", "У контексті класифікації `Машини опорних векторів` — це метод машинного навчання, який намагається знайти *гіперплощину*, що \"найкраще\" розділяє класи. Розглянемо простий приклад:\n", "\n", "

\n", " \n", "

https://commons.wikimedia.org/w/index.php?curid=22877598
\n" ] }, { "cell_type": "markdown", "metadata": { "id": "C4Wsd0vZhXYu" }, "source": [ "H1~ не розділяє класи. H2~ розділяє, але лише з невеликим відступом. H3~ розділяє їх з максимальним відступом.\n", "\n", "#### Лінійний класифікатор на основі опорних векторів\n", "\n", "Кластеризація на основі опорних векторів (SVC) є частиною сімейства методів машинного навчання, які використовують опорні вектори. У SVC гіперплощина обирається таким чином, щоб правильно розділити `більшість` навчальних спостережень, але `може неправильно класифікувати` деякі з них. Дозволяючи деяким точкам бути на неправильній стороні, SVM стає більш стійким до викидів, що забезпечує кращу узагальненість для нових даних. Параметр, який регулює це порушення, називається `cost` і має значення за замовчуванням 1 (див. `help(\"svm_poly\")`).\n", "\n", "Давайте створимо лінійний SVC, встановивши `degree = 1` у поліноміальній моделі SVM.\n" ] }, { "cell_type": "code", "metadata": { "id": "vJpp6nuChlBz" }, "source": [ "# Make a linear SVC specification\n", "svc_linear_spec <- svm_poly(degree = 1) %>% \n", " set_engine(\"kernlab\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle specification and recipe into a worklow\n", "svc_linear_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(svc_linear_spec)\n", "\n", "# Print out workflow\n", "svc_linear_wf" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "rDs8cWNkhoqu" }, "source": [ "Тепер, коли ми зафіксували етапи попередньої обробки та специфікацію моделі у *workflow*, можемо перейти до навчання лінійного SVC і одночасно оцінити результати. Для оцінки продуктивності давайте створимо набір метрик, який буде враховувати: `accuracy`, `sensitivity`, `Positive Predicted Value` та `F Measure`.\n", "\n", "> `augment()` додасть стовпець(и) з прогнозами до наданих даних.\n" ] }, { "cell_type": "code", "metadata": { "id": "81wiqcwuhrnq" }, "source": [ "# Train a linear SVC model\n", "svc_linear_fit <- svc_linear_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "# Create a metric set\n", "eval_metrics <- metric_set(ppv, sens, accuracy, f_meas)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "svc_linear_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "0UFQvHf-huo3" }, "source": [ "#### Машина опорних векторів\n", "\n", "Машина опорних векторів (SVM) є розширенням класифікатора опорних векторів, яке дозволяє враховувати нелінійну межу між класами. По суті, SVM використовують *трюк з ядром*, щоб розширити простір ознак і адаптуватися до нелінійних взаємозв'язків між класами. Однією з популярних і надзвичайно гнучких функцій ядра, яку використовують SVM, є *радіальна базисна функція*. Давайте подивимося, як вона працюватиме на наших даних.\n" ] }, { "cell_type": "code", "metadata": { "id": "-KX4S8mzhzmp" }, "source": [ "set.seed(2056)\n", "\n", "# Make an RBF SVM specification\n", "svm_rbf_spec <- svm_rbf() %>% \n", " set_engine(\"kernlab\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle specification and recipe into a worklow\n", "svm_rbf_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(svm_rbf_spec)\n", "\n", "\n", "# Train an RBF model\n", "svm_rbf_fit <- svm_rbf_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "svm_rbf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "QBFSa7WSh4HQ" }, "source": [ "Набагато краще 🤩!\n", "\n", "> ✅ Будь ласка, перегляньте:\n", ">\n", "> - [*Support Vector Machines*](https://bradleyboehmke.github.io/HOML/svm.html), Практичне машинне навчання з R\n", ">\n", "> - [*Support Vector Machines*](https://www.statlearning.com/), Вступ до статистичного навчання з застосуванням R\n", ">\n", "> для додаткового читання.\n", "\n", "### Класифікатори найближчих сусідів\n", "\n", "*K*-найближчий сусід (KNN) — це алгоритм, у якому кожне спостереження прогнозується на основі його *схожості* з іншими спостереженнями.\n", "\n", "Давайте застосуємо його до наших даних.\n" ] }, { "cell_type": "code", "metadata": { "id": "k4BxxBcdh9Ka" }, "source": [ "# Make a KNN specification\n", "knn_spec <- nearest_neighbor() %>% \n", " set_engine(\"kknn\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "knn_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(knn_spec)\n", "\n", "# Train a boosted tree model\n", "knn_wf_fit <- knn_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "knn_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "HaegQseriAcj" }, "source": [ "Здається, що ця модель працює не дуже добре. Можливо, зміна аргументів моделі (див. `help(\"nearest_neighbor\")`) покращить її продуктивність. Обов’язково спробуйте це зробити.\n", "\n", "> ✅ Будь ласка, ознайомтеся з:\n", ">\n", "> - [Hands-on Machine Learning with R](https://bradleyboehmke.github.io/HOML/)\n", ">\n", "> - [An Introduction to Statistical Learning with Applications in R](https://www.statlearning.com/)\n", ">\n", "> щоб дізнатися більше про класифікатори *K*-найближчих сусідів.\n", "\n", "### Ансамблеві класифікатори\n", "\n", "Ансамблеві алгоритми працюють, комбінуючи кілька базових оцінювачів для створення оптимальної моделі шляхом:\n", "\n", "`bagging`: застосування *усереднювальної функції* до набору базових моделей\n", "\n", "`boosting`: побудови послідовності моделей, які покращують одна одну для підвищення точності прогнозування.\n", "\n", "Давайте почнемо з моделі Random Forest, яка створює велику кількість дерев рішень, а потім застосовує усереднювальну функцію для отримання кращої загальної моделі.\n" ] }, { "cell_type": "code", "metadata": { "id": "49DPoVs6iK1M" }, "source": [ "# Make a random forest specification\n", "rf_spec <- rand_forest() %>% \n", " set_engine(\"ranger\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "rf_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(rf_spec)\n", "\n", "# Train a random forest model\n", "rf_wf_fit <- rf_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "rf_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "RGVYwC_aiUWc" }, "source": [ "Молодець 👏!\n", "\n", "Давайте також спробуємо модель Boosted Tree.\n", "\n", "Boosted Tree визначає ансамблевий метод, який створює серію послідовних дерев рішень, де кожне дерево залежить від результатів попередніх дерев, намагаючись поступово зменшити помилку. Він зосереджується на вагах неправильно класифікованих елементів і коригує підгонку для наступного класифікатора, щоб виправити помилки.\n", "\n", "Існують різні способи підгонки цієї моделі (див. `help(\"boost_tree\")`). У цьому прикладі ми підженемо Boosted trees за допомогою двигуна `xgboost`.\n" ] }, { "cell_type": "code", "metadata": { "id": "Py1YWo-micWs" }, "source": [ "# Make a boosted tree specification\n", "boost_spec <- boost_tree(trees = 200) %>% \n", " set_engine(\"xgboost\") %>% \n", " set_mode(\"classification\")\n", "\n", "# Bundle recipe and model specification into a workflow\n", "boost_wf <- workflow() %>% \n", " add_recipe(cuisines_recipe) %>% \n", " add_model(boost_spec)\n", "\n", "# Train a boosted tree model\n", "boost_wf_fit <- boost_wf %>% \n", " fit(data = cuisines_train)\n", "\n", "\n", "# Make predictions and Evaluate model performance\n", "boost_wf_fit %>% \n", " augment(new_data = cuisines_test) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class)" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "zNQnbuejigZM" }, "source": [ "> ✅ Будь ласка, ознайомтеся:\n", ">\n", "> - [Machine Learning for Social Scientists](https://cimentadaj.github.io/ml_socsci/tree-based-methods.html#random-forests)\n", ">\n", "> - [Hands-on Machine Learning with R](https://bradleyboehmke.github.io/HOML/)\n", ">\n", "> - [An Introduction to Statistical Learning with Applications in R](https://www.statlearning.com/)\n", ">\n", "> - - Досліджує модель AdaBoost, яка є гарною альтернативою xgboost.\n", ">\n", "> щоб дізнатися більше про ансамблеві класифікатори.\n", "\n", "## 4. Додатково - порівняння кількох моделей\n", "\n", "Ми побудували досить багато моделей у цій лабораторній роботі 🙌. Може стати виснажливим або обтяжливим створювати багато робочих процесів із різних наборів препроцесорів та/або специфікацій моделей, а потім по одній обчислювати метрики продуктивності.\n", "\n", "Давайте подивимося, чи зможемо ми вирішити це, створивши функцію, яка навчає список робочих процесів на навчальному наборі, а потім повертає метрики продуктивності на основі тестового набору. Ми скористаємося `map()` та `map_dfr()` із пакету [purrr](https://purrr.tidyverse.org/), щоб застосувати функції до кожного елемента списку.\n", "\n", "> [`map()`](https://purrr.tidyverse.org/reference/map.html) дозволяє замінити багато циклів for кодом, який є більш лаконічним і легшим для читання. Найкраще місце для вивчення функцій [`map()`](https://purrr.tidyverse.org/reference/map.html) — це [розділ про ітерацію](http://r4ds.had.co.nz/iteration.html) у книзі \"R for Data Science\".\n" ] }, { "cell_type": "code", "metadata": { "id": "Qzb7LyZnimd2" }, "source": [ "set.seed(2056)\n", "\n", "# Create a metric set\n", "eval_metrics <- metric_set(ppv, sens, accuracy, f_meas)\n", "\n", "# Define a function that returns performance metrics\n", "compare_models <- function(workflow_list, train_set, test_set){\n", " \n", " suppressWarnings(\n", " # Fit each model to the train_set\n", " map(workflow_list, fit, data = train_set) %>% \n", " # Make predictions on the test set\n", " map_dfr(augment, new_data = test_set, .id = \"model\") %>%\n", " # Select desired columns\n", " select(model, cuisine, .pred_class) %>% \n", " # Evaluate model performance\n", " group_by(model) %>% \n", " eval_metrics(truth = cuisine, estimate = .pred_class) %>% \n", " ungroup()\n", " )\n", " \n", "} # End of function" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "Fwa712sNisDA" }, "source": [] }, { "cell_type": "code", "metadata": { "id": "3i4VJOi2iu-a" }, "source": [ "# Make a list of workflows\n", "workflow_list <- list(\n", " \"svc\" = svc_linear_wf,\n", " \"svm\" = svm_rbf_wf,\n", " \"knn\" = knn_wf,\n", " \"random_forest\" = rf_wf,\n", " \"xgboost\" = boost_wf)\n", "\n", "# Call the function\n", "set.seed(2056)\n", "perf_metrics <- compare_models(workflow_list = workflow_list, train_set = cuisines_train, test_set = cuisines_test)\n", "\n", "# Print out performance metrics\n", "perf_metrics %>% \n", " group_by(.metric) %>% \n", " arrange(desc(.estimate)) %>% \n", " slice_head(n=7)\n", "\n", "# Compare accuracy\n", "perf_metrics %>% \n", " filter(.metric == \"accuracy\") %>% \n", " arrange(desc(.estimate))\n" ], "execution_count": null, "outputs": [] }, { "cell_type": "markdown", "metadata": { "id": "KuWK_lEli4nW" }, "source": [ "[**workflowset**](https://workflowsets.tidymodels.org/) пакет дозволяє користувачам створювати та легко налаштовувати велику кількість моделей, але в основному призначений для роботи з методами повторного вибіркового моделювання, такими як `перехресна перевірка`, підхід, який ми ще не розглядали.\n", "\n", "## **🚀Виклик**\n", "\n", "Кожна з цих технік має велику кількість параметрів, які ви можете налаштовувати, наприклад, `cost` у SVM, `neighbors` у KNN, `mtry` (випадково вибрані предиктори) у Random Forest.\n", "\n", "Дослідіть стандартні параметри кожної з них і подумайте, що зміна цих параметрів може означати для якості моделі.\n", "\n", "Щоб дізнатися більше про конкретну модель та її параметри, скористайтеся: `help(\"model\")`, наприклад, `help(\"rand_forest\")`.\n", "\n", "> На практиці ми зазвичай *оцінюємо* *найкращі значення* цих параметрів, тренуючи багато моделей на `змодельованому наборі даних` і вимірюючи, наскільки добре ці моделі працюють. Цей процес називається **налаштуванням**.\n", "\n", "### [**Тест після лекції**](https://gray-sand-07a10f403.1.azurestaticapps.net/quiz/24/)\n", "\n", "### **Огляд і самостійне навчання**\n", "\n", "У цих уроках багато спеціальних термінів, тому приділіть хвилину, щоб переглянути [цей список](https://docs.microsoft.com/dotnet/machine-learning/resources/glossary?WT.mc_id=academic-77952-leestott) корисної термінології!\n", "\n", "#### ДЯКУЄМО:\n", "\n", "[`Елісон Хорст`](https://twitter.com/allison_horst/) за створення дивовижних ілюстрацій, які роблять R більш доступним і цікавим. Знайдіть більше ілюстрацій у її [галереї](https://www.google.com/url?q=https://github.com/allisonhorst/stats-illustrations&sa=D&source=editors&ust=1626380772530000&usg=AOvVaw3zcfyCizFQZpkSLzxiiQEM).\n", "\n", "[Кессі Брев'ю](https://www.twitter.com/cassieview) та [Джен Лупер](https://www.twitter.com/jenlooper) за створення оригінальної версії цього модуля на Python ♥️\n", "\n", "Щасливого навчання,\n", "\n", "[Ерік](https://twitter.com/ericntay), Золотий студентський амбасадор Microsoft Learn.\n", "\n", "

\n", " \n", "

Ілюстрація від @allison_horst
\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "\n---\n\n**Відмова від відповідальності**: \nЦей документ було перекладено за допомогою сервісу автоматичного перекладу [Co-op Translator](https://github.com/Azure/co-op-translator). Хоча ми прагнемо до точності, зверніть увагу, що автоматичні переклади можуть містити помилки або неточності. Оригінальний документ мовою оригіналу слід вважати авторитетним джерелом. Для критично важливої інформації рекомендується професійний людський переклад. Ми не несемо відповідальності за будь-які непорозуміння або неправильні тлумачення, що виникли внаслідок використання цього перекладу.\n" ] } ] }