{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.12.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"94d83944-6631-4121-b44c-cb64f7afa239","cell_type":"markdown","source":"# Лабораторна робота №3. Перенос навчання\n\nМета роботи реалізувати переніс навчання засобами фреймворку Keras та провести порівняльні обчислювальні експерименти на наборах даних Dogs vs Cats і Fruits & Vegetables.\n\nВиконав [Ніколаєнко Данило Денисович]\n\nГрупа: [6.1213-1]","metadata":{}},{"id":"a07a801e-f590-477b-9d73-ac0c474ddc86","cell_type":"markdown","source":"## 1. Теоретичні відомості\n\nПереніс навчання (transfer learning) — підхід, у якому використовують нейронну мережу, попередньо навчену на великому наборі даних (наприклад, ImageNet), як базовий екстрактор ознак для нової задачі класифікації. Типовий алгоритм:\n\n1. Завантажити попередньо навчену архітектуру без верхніх класифікаційних шарів (`include_top=False`).\n2. Зафіксувати ваги базових шарів (`trainable=False`).\n3. Додати нові шари (GlobalAveragePooling2D, Dense, вихідний шар).\n4. Навчити лише нові шари на цільовому наборі даних.\n\nУ Keras доступні архітектури Xception, VGG19, ResNet50, MobileNet та інші з вагами ImageNet.","metadata":{}},{"id":"af949009-c4e8-4ada-9cb0-705ed3e3a372","cell_type":"markdown","source":"## 2. Підготовка середовища","metadata":{}},{"id":"59509ee6-02f3-460d-b17e-e7ac872005d9","cell_type":"code","source":"import os\nimport shutil\nimport random\nimport time\n\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import models, layers, optimizers\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications import Xception, VGG19, ResNet50, MobileNet\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n%matplotlib inline\n\nSEED = 42\nrandom.seed(SEED)\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:43.150415Z","iopub.execute_input":"2026-06-07T18:34:43.151008Z","iopub.status.idle":"2026-06-07T18:34:43.165056Z","shell.execute_reply.started":"2026-06-07T18:34:43.150979Z","shell.execute_reply":"2026-06-07T18:34:43.163980Z"}},"outputs":[],"execution_count":null},{"id":"600e01d6-f23e-47b7-8c40-30b98eb3981c","cell_type":"code","source":"print('TensorFlow:', tf.__version__)\nprint('GPU:', tf.config.list_physical_devices('GPU'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:43.166808Z","iopub.execute_input":"2026-06-07T18:34:43.167124Z","iopub.status.idle":"2026-06-07T18:34:43.178632Z","shell.execute_reply.started":"2026-06-07T18:34:43.167094Z","shell.execute_reply":"2026-06-07T18:34:43.177952Z"}},"outputs":[],"execution_count":null},{"id":"de38a26d-52ea-4f67-ab4f-9554e4099ebb","cell_type":"markdown","source":"## 3. Допоміжні функції","metadata":{}},{"id":"89d0966f-e21c-4389-943a-8259f8fee3f3","cell_type":"code","source":"def create_subfolders(base_dir, class_names):\n    subdir_list = ['train', 'validation', 'test']\n    created_dirs = []\n    if not os.path.exists(base_dir):\n        os.mkdir(base_dir)\n    for name in subdir_list:\n        sub_dir = os.path.join(base_dir, name)\n        if not os.path.exists(sub_dir):\n            os.mkdir(sub_dir)\n        for class_name in class_names:\n            class_dir = os.path.join(sub_dir, class_name)\n            if not os.path.exists(class_dir):\n                os.mkdir(class_dir)\n            created_dirs.append(class_dir)\n    return created_dirs\n\n\ndef data_split(data, test_ratio=0.3):\n    data = list(data)\n    random.shuffle(data)\n    n = len(data)\n    test_n = int(test_ratio * n)\n    val_n = int(test_ratio * n)\n    train_n = n - test_n - val_n\n    train_data = data[:train_n]\n    val_data = data[train_n:train_n + val_n]\n    test_data = data[train_n + val_n:]\n    return train_data, val_data, test_data\n\n\ndef data_split_list(base_dir, class_list, test_ratio=0.3):\n    train_data, val_data, test_data = [], [], []\n    for class_name in class_list:\n        class_files = [\n            f for f in os.listdir(base_dir['train'])\n            if os.path.isfile(os.path.join(base_dir['train'], f)) and class_name in f\n        ]\n        train, val, test = data_split(class_files, test_ratio)\n        train_data.append(train)\n        val_data.append(val)\n        test_data.append(test)\n    return [train_data, val_data, test_data]\n\n\ndef copy_images_to_folders(source_dir, dataset_dirs, datasets):\n    class_amount = len(datasets[0])\n    dir_count = -1\n    for dataset in datasets:\n        for i in range(class_amount):\n            dir_count += 1\n            for filename in dataset[i]:\n                source = os.path.join(source_dir, filename)\n                destination = os.path.join(dataset_dirs[dir_count], filename)\n                shutil.copyfile(source, destination)\n\n\ndef plot_history(history, title_prefix=''):\n    acc = history.history['accuracy']\n    val_acc = history.history['val_accuracy']\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    epochs = range(1, len(acc) + 1)\n\n    plt.figure(figsize=(12, 4))\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs, acc, label='Навчання')\n    plt.plot(epochs, val_acc, label='Валідація')\n    plt.title(f'{title_prefix} Точність')\n    plt.xlabel('Епоха')\n    plt.ylabel('Accuracy')\n    plt.legend()\n\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs, loss, label='Навчання')\n    plt.plot(epochs, val_loss, label='Валідація')\n    plt.title(f'{title_prefix} Функція втрат')\n    plt.xlabel('Епоха')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.tight_layout()\n    plt.show()\n\n\ndef plot_confusion_matrix(cm, class_names, title='Confusion matrix'):\n    fig, ax = plt.subplots(figsize=(8, 6))\n    im = ax.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)\n    ax.figure.colorbar(im, ax=ax)\n    ax.set(\n        xticks=np.arange(len(class_names)),\n        yticks=np.arange(len(class_names)),\n        xticklabels=class_names,\n        yticklabels=class_names,\n        ylabel='Справжній клас',\n        xlabel='Передбачений клас',\n        title=title,\n    )\n    plt.setp(ax.get_xticklabels(), rotation=45, ha='right', rotation_mode='anchor')\n    thresh = cm.max() / 2.0 if cm.max() > 0 else 0.5\n    for i in range(cm.shape[0]):\n        for j in range(cm.shape[1]):\n            ax.text(j, i, format(cm[i, j], '.2f'), ha='center', va='center',\n                    color='white' if cm[i, j] > thresh else 'black')\n    plt.tight_layout()\n    plt.show()\n\n\ndef build_transfer_model(base_model, num_classes, trainable_base=False):\n    base_model.trainable = trainable_base\n    x = GlobalAveragePooling2D()(base_model.output)\n    x = Dense(1024, activation='relu')(x)\n    if num_classes == 2:\n        output = Dense(1, activation='sigmoid')(x)\n        loss = 'binary_crossentropy'\n        class_mode = 'binary'\n    else:\n        output = Dense(num_classes, activation='softmax')(x)\n        loss = 'sparse_categorical_crossentropy'\n        class_mode = 'sparse'\n    model = Model(inputs=base_model.input, outputs=output)\n    model.compile(\n        optimizer=optimizers.Adam(learning_rate=1e-4),\n        loss=loss,\n        metrics=['accuracy'],\n    )\n    return model, class_mode\n\n\ndef make_generators(train_dir, val_dir, test_dir, target_size, batch_size, class_mode):\n    common = dict(target_size=target_size, batch_size=batch_size, class_mode=class_mode)\n    train_gen = ImageDataGenerator(rescale=1.0 / 255.0).flow_from_directory(train_dir, **common)\n    val_gen = ImageDataGenerator(rescale=1.0 / 255.0).flow_from_directory(val_dir, **common)\n    test_gen = ImageDataGenerator(rescale=1.0 / 255.0).flow_from_directory(\n        test_dir, **common, shuffle=False\n    )\n    return train_gen, val_gen, test_gen\n\n\ndef train_and_evaluate(model, train_gen, val_gen, test_gen, epochs=5, model_name='model'):\n    es = EarlyStopping(monitor='val_loss', mode='min', patience=3, restore_best_weights=True, verbose=1)\n    start = time.time()\n    history = model.fit(\n        train_gen,\n        epochs=epochs,\n        validation_data=val_gen,\n        callbacks=[es],\n        verbose=1,\n    )\n    elapsed = time.time() - start\n    test_loss, test_acc = model.evaluate(test_gen, verbose=0)\n    y_true = test_gen.classes\n    y_prob = model.predict(test_gen, verbose=0)\n    if y_prob.shape[1] == 1:\n        y_pred = (y_prob >= 0.5).astype(int).reshape(-1)\n    else:\n        y_pred = np.argmax(y_prob, axis=1)\n    return {\n        'model_name': model_name,\n        'history': history,\n        'test_acc': test_acc,\n        'test_loss': test_loss,\n        'train_time_sec': elapsed,\n        'epochs_run': len(history.history['loss']),\n        'y_true': y_true,\n        'y_pred': y_pred,\n        'class_indices': test_gen.class_indices,\n    }","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:43.179512Z","iopub.execute_input":"2026-06-07T18:34:43.179779Z","iopub.status.idle":"2026-06-07T18:34:43.203338Z","shell.execute_reply.started":"2026-06-07T18:34:43.179749Z","shell.execute_reply":"2026-06-07T18:34:43.202791Z"}},"outputs":[],"execution_count":null},{"id":"e8a23e65-622f-4e5f-91ed-710c2bc89db3","cell_type":"markdown","source":"## 4. Завдання 1. Переніс навчання на наборі Dogs vs Cats\n\nВиконано за прикладом [cats-vs-dog-transfer-learning](https://www.kaggle.com/avk256/cats-vs-dog-transfer-learning). Набір даних містить зображення котів і собак; файли іменуються з префіксом класу (`cat`, `dog`). Дані розбиваються на навчальну, валідаційну та тестову вибірки у співвідношенні 40:30:30.","metadata":{}},{"id":"547aef55-0609-4f3f-b2b9-1e6bfc7a69d1","cell_type":"code","source":"import zipfile\n\ndef find_kaggle_input_dir(required_file='train.zip'):\n    for root, dirs, files in os.walk('/kaggle/input'):\n        if required_file in files:\n            return root\n    raise FileNotFoundError(\n        f\"Не знайдено {required_file}. Додайте Dogs vs. Cats Redux через Add Input.\"\n    )\n\nCATS_DOGS_INPUT = find_kaggle_input_dir('train.zip')\nCATS_DOGS_RAW = '/kaggle/working/cats_dogs_raw'\nCATS_DOGS_WORK = '/kaggle/working/cats_dogs_data'\n\ntrain_zip_path = os.path.join(CATS_DOGS_INPUT, 'train.zip')\nmarker_file = os.path.join(CATS_DOGS_RAW, '.extracted')\n\nif not os.path.exists(marker_file):\n    if os.path.exists(CATS_DOGS_RAW):\n        shutil.rmtree(CATS_DOGS_RAW)\n    os.makedirs(CATS_DOGS_RAW, exist_ok=True)\n    with zipfile.ZipFile(train_zip_path, 'r') as z:\n        z.extractall(CATS_DOGS_RAW)\n    open(marker_file, 'w').close()\n\nif os.path.isdir(os.path.join(CATS_DOGS_RAW, 'train')):\n    CATS_DOGS_TRAIN = os.path.join(CATS_DOGS_RAW, 'train')\nelse:\n    CATS_DOGS_TRAIN = CATS_DOGS_RAW\n\nclasses_list = ['cat', 'dog']\nbase_dir_dic = {'train': CATS_DOGS_TRAIN, 'test': CATS_DOGS_TRAIN}\n\nif os.path.exists(CATS_DOGS_WORK):\n    shutil.rmtree(CATS_DOGS_WORK)\n\nsub_dirs = create_subfolders(CATS_DOGS_WORK, classes_list)\ndatasets = data_split_list(base_dir_dic, classes_list, test_ratio=0.3)\ncopy_images_to_folders(CATS_DOGS_TRAIN, sub_dirs, datasets)\n\nprint('Dogs vs Cats input:', CATS_DOGS_INPUT)\nprint('Джерело зображень:', CATS_DOGS_TRAIN)\nprint('Структура каталогів:', sub_dirs[:2], '...')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:43.204663Z","iopub.execute_input":"2026-06-07T18:34:43.205246Z","iopub.status.idle":"2026-06-07T18:34:46.140788Z","shell.execute_reply.started":"2026-06-07T18:34:43.205225Z","shell.execute_reply":"2026-06-07T18:34:46.140062Z"}},"outputs":[],"execution_count":null},{"id":"4eca1188-b23f-4860-95d9-e4a1030645e3","cell_type":"code","source":"target_size_cats = (300, 300)\nbatch_size_cats = 64\n\ntrain_gen_cats, val_gen_cats, test_gen_cats = make_generators(\n    os.path.join(CATS_DOGS_WORK, 'train'),\n    os.path.join(CATS_DOGS_WORK, 'validation'),\n    os.path.join(CATS_DOGS_WORK, 'test'),\n    target_size_cats,\n    batch_size_cats,\n    'binary',\n)\n\ninput_shape_cats = target_size_cats + (3,)\nbase_vgg19_cats = VGG19(weights='imagenet', include_top=False, input_shape=input_shape_cats)\nmodel_cats, _ = build_transfer_model(base_vgg19_cats, num_classes=2)\nmodel_cats.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:46.142091Z","iopub.execute_input":"2026-06-07T18:34:46.142332Z","iopub.status.idle":"2026-06-07T18:34:46.790017Z","shell.execute_reply.started":"2026-06-07T18:34:46.142311Z","shell.execute_reply":"2026-06-07T18:34:46.789361Z"}},"outputs":[],"execution_count":null},{"id":"4799a096-0213-4ad9-80fd-6d0bd6706a7d","cell_type":"code","source":"result_cats = train_and_evaluate(\n    model_cats,\n    train_gen_cats,\n    val_gen_cats,\n    test_gen_cats,\n    epochs=5,\n    model_name='VGG19 (Dogs vs Cats)',\n)\n\nprint(f\"Тестова точність: {result_cats['test_acc']:.4f}\")\nprint(f\"Час навчання: {result_cats['train_time_sec']:.1f} с\")\nplot_history(result_cats['history'], 'Dogs vs Cats — ')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:34:46.790940Z","iopub.execute_input":"2026-06-07T18:34:46.791131Z","iopub.status.idle":"2026-06-07T18:57:06.296035Z","shell.execute_reply.started":"2026-06-07T18:34:46.791112Z","shell.execute_reply":"2026-06-07T18:57:06.295475Z"}},"outputs":[],"execution_count":null},{"id":"a9074914-608a-4cfe-a8a8-0d034d94e12b","cell_type":"code","source":"cm_cats = confusion_matrix(result_cats['y_true'], result_cats['y_pred'], normalize='true')\ninv_map = {v: k for k, v in result_cats['class_indices'].items()}\nlabels_cats = [inv_map[i] for i in range(len(inv_map))]\nplot_confusion_matrix(cm_cats, labels_cats, title='Dogs vs Cats, VGG19')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T18:57:06.297811Z","iopub.execute_input":"2026-06-07T18:57:06.298017Z","iopub.status.idle":"2026-06-07T18:57:06.462426Z","shell.execute_reply.started":"2026-06-07T18:57:06.297997Z","shell.execute_reply":"2026-06-07T18:57:06.461749Z"}},"outputs":[],"execution_count":null},{"id":"0b5c6190-60b3-47d8-aead-85b8b262eb50","cell_type":"markdown","source":"**Результат завдання 1.** Базова модель VGG19 з зафіксованими згортковими шарами навчена на бінарній класифікації котів і собак. Отримані графіки точності та матриця помилок демонструють здатність перенесених ознак адаптуватися до нової задачі.","metadata":{}},{"id":"77186879-4aa6-4597-a71b-ff70776cf234","cell_type":"markdown","source":"## 5. Завдання 2. Переніс навчання на наборі Fruits & Vegetables\n\nНабір даних [Fruit and Vegetable Image Recognition](https://www.kaggle.com/datasets/kritikseth/fruit-and-vegetable-image-recognition) містить 36 класів фруктів і овощів. Структура каталогів уже розділена на `train`, `validation` і `test`, тому додаткове копіювання не потрібне.\n\nПорівнюються архітектури Xception, VGG19, ResNet50 та MobileNet з вагами ImageNet.","metadata":{}},{"id":"541627ee-3c3f-49fd-870e-9455a0759133","cell_type":"code","source":"def find_fruit_dataset_dir():\n    required = {'train', 'validation', 'test'}\n    for root, dirs, files in os.walk('/kaggle/input'):\n        if required.issubset(set(dirs)):\n            return root\n    raise FileNotFoundError('Не знайдено датасет з train/validation/test')\n\nFRUIT_BASE = find_fruit_dataset_dir()\nFRUIT_TRAIN = os.path.join(FRUIT_BASE, 'train')\nFRUIT_VAL = os.path.join(FRUIT_BASE, 'validation')\nFRUIT_TEST = os.path.join(FRUIT_BASE, 'test')\nprint('Fruit dataset:', FRUIT_BASE)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:00:20.191827Z","iopub.execute_input":"2026-06-07T19:00:20.192446Z","iopub.status.idle":"2026-06-07T19:00:20.203978Z","shell.execute_reply.started":"2026-06-07T19:00:20.192350Z","shell.execute_reply":"2026-06-07T19:00:20.203056Z"}},"outputs":[],"execution_count":null},{"id":"ebfb0c64-e5a6-4db9-9292-911a0c034e2b","cell_type":"code","source":"class_names_fruit = sorted(os.listdir(FRUIT_TRAIN))\nnum_classes_fruit = len(class_names_fruit)\n\ncounts = []\nfor split_name, split_dir in [('train', FRUIT_TRAIN), ('validation', FRUIT_VAL), ('test', FRUIT_TEST)]:\n    total = sum(len(os.listdir(os.path.join(split_dir, c))) for c in class_names_fruit)\n    counts.append(total)\n    print(f'{split_name}: {total} зображень')\n\nplt.figure(figsize=(10, 4))\nplt.bar(['train', 'validation', 'test'], counts)\nplt.title('Розподіл зображень за вибірками')\nplt.ylabel('Кількість')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:02:44.133129Z","iopub.execute_input":"2026-06-07T19:02:44.133894Z","iopub.status.idle":"2026-06-07T19:02:44.916292Z","shell.execute_reply.started":"2026-06-07T19:02:44.133859Z","shell.execute_reply":"2026-06-07T19:02:44.915658Z"}},"outputs":[],"execution_count":null},{"id":"816a3231-afa9-43e2-8d75-d5432537dcf2","cell_type":"code","source":"target_size_fruit = (224, 224)\nbatch_size_fruit = 32\n\ntrain_gen_fruit, val_gen_fruit, test_gen_fruit = make_generators(\n    FRUIT_TRAIN,\n    FRUIT_VAL,\n    FRUIT_TEST,\n    target_size_fruit,\n    batch_size_fruit,\n    'sparse',\n)\n\ninput_shape_fruit = target_size_fruit + (3,)\n\nbackbones = {\n    'Xception': Xception,\n    'VGG19': VGG19,\n    'ResNet50': ResNet50,\n    'MobileNet': MobileNet,\n}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:02:50.090834Z","iopub.execute_input":"2026-06-07T19:02:50.091100Z","iopub.status.idle":"2026-06-07T19:02:51.681755Z","shell.execute_reply.started":"2026-06-07T19:02:50.091078Z","shell.execute_reply":"2026-06-07T19:02:51.681102Z"}},"outputs":[],"execution_count":null},{"id":"11eaa593-a55e-4fa0-855e-9f05e17a99e5","cell_type":"code","source":"fruit_results = []\nfruit_histories = {}\n\nfor name, backbone_class in backbones.items():\n    print('\\n' + '=' * 60)\n    print('Навчання моделі:', name)\n    print('=' * 60)\n\n    base = backbone_class(weights='imagenet', include_top=False, input_shape=input_shape_fruit)\n    model, _ = build_transfer_model(base, num_classes=num_classes_fruit)\n    result = train_and_evaluate(\n        model,\n        train_gen_fruit,\n        val_gen_fruit,\n        test_gen_fruit,\n        epochs=5,\n        model_name=name,\n    )\n    fruit_results.append(result)\n    fruit_histories[name] = result['history']\n\n    print(f\"{name}: test accuracy = {result['test_acc']:.4f}, time = {result['train_time_sec']:.1f} s\")\n    plot_history(result['history'], f'{name} — ')\n\n    del model, base\n    tf.keras.backend.clear_session()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:02:53.831869Z","iopub.execute_input":"2026-06-07T19:02:53.832533Z","iopub.status.idle":"2026-06-07T19:34:55.652769Z","shell.execute_reply.started":"2026-06-07T19:02:53.832504Z","shell.execute_reply":"2026-06-07T19:34:55.650961Z"}},"outputs":[],"execution_count":null},{"id":"90d16960-d026-4c5b-afc6-c252d75e222e","cell_type":"markdown","source":"## 6. Завдання 3. Порівняльні обчислювальні експерименти\n\nУ таблиці наведено результати для чотирьох архітектур на одному наборі даних при однакових гіперпараметрах (розмір зображення 224×224, batch size 32, Adam lr=1e-4, EarlyStopping patience=5).","metadata":{}},{"id":"8757c269-8a18-4bc5-9df7-a1ce66a896e8","cell_type":"code","source":"experiment_rows = []\nfor r in fruit_results:\n    experiment_rows.append({\n        'Архітектура': r['model_name'],\n        'Епох': r['epochs_run'],\n        'Test accuracy': round(r['test_acc'], 4),\n        'Test loss': round(r['test_loss'], 4),\n        'Час навчання, с': round(r['train_time_sec'], 1),\n    })\n\nexperiment_rows.append({\n    'Архітектура': 'VGG19 (Dogs vs Cats)',\n    'Епох': result_cats['epochs_run'],\n    'Test accuracy': round(result_cats['test_acc'], 4),\n    'Test loss': round(result_cats['test_loss'], 4),\n    'Час навчання, с': round(result_cats['train_time_sec'], 1),\n})\n\ndf_experiments = pd.DataFrame(experiment_rows)\ndf_experiments.sort_values('Test accuracy', ascending=False, inplace=True)\ndf_experiments.reset_index(drop=True, inplace=True)\ndf_experiments","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:34:55.691497Z","iopub.execute_input":"2026-06-07T19:34:55.691836Z","iopub.status.idle":"2026-06-07T19:34:55.704239Z","shell.execute_reply.started":"2026-06-07T19:34:55.691812Z","shell.execute_reply":"2026-06-07T19:34:55.703545Z"}},"outputs":[],"execution_count":null},{"id":"1a49ed1d-1ddc-42aa-92a3-01eaa71e1de4","cell_type":"code","source":"plt.figure(figsize=(10, 5))\nplt.bar(df_experiments['Архітектура'], df_experiments['Test accuracy'])\nplt.xticks(rotation=30, ha='right')\nplt.ylabel('Test accuracy')\nplt.title('Порівняння архітектур')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:34:55.705843Z","iopub.execute_input":"2026-06-07T19:34:55.706567Z","iopub.status.idle":"2026-06-07T19:34:55.833238Z","shell.execute_reply.started":"2026-06-07T19:34:55.706542Z","shell.execute_reply":"2026-06-07T19:34:55.832625Z"}},"outputs":[],"execution_count":null},{"id":"1c30befe-a157-442b-ad50-4068cddad564","cell_type":"code","source":"best_result = max(fruit_results, key=lambda x: x['test_acc'])\ncm_fruit = confusion_matrix(best_result['y_true'], best_result['y_pred'], normalize='true')\ninv_map = {v: k for k, v in best_result['class_indices'].items()}\nlabels_fruit = [inv_map[i] for i in range(len(inv_map))]\n\nprint('Найкраща модель на Fruits & Vegetables:', best_result['model_name'])\nprint(classification_report(best_result['y_true'], best_result['y_pred'], target_names=labels_fruit))\nplot_confusion_matrix(cm_fruit, labels_fruit, title=f\"Fruits & Vegetables, {best_result['model_name']}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:34:55.834024Z","iopub.execute_input":"2026-06-07T19:34:55.834312Z","iopub.status.idle":"2026-06-07T19:34:57.509476Z","shell.execute_reply.started":"2026-06-07T19:34:55.834270Z","shell.execute_reply":"2026-06-07T19:34:57.508766Z"}},"outputs":[],"execution_count":null},{"id":"912b9d1c-af62-4667-bdd0-e84e441722cb","cell_type":"markdown","source":"**Аналіз експериментів.**\n\n1. Усі моделі використовують попередньо навчені згорткові шари ImageNet із зафіксованими вагами; навчаються лише додані Dense-шари.\n2. MobileNet зазвичай потребує менше часу навчання завдяки полегшеній архітектурі.\n3. ResNet50 і Xception часто показують вищу точність на задачах з великою кількістю класів.\n4. Різниця між точностями на train/validation/test відображає ступінь узагальнення моделі.\n\nКонкретні числові значення формуються після виконання notebook і підставляються у висновки нижче.","metadata":{}},{"id":"b80baf2f-8165-49a3-bdfe-784ae59df887","cell_type":"markdown","source":"## 7. Додатковий експеримент: навчання всіх шарів (fine-tuning)\n\nДля відповіді на контрольне питання 2 нижче показано фрагмент коду, який розблоковує базові шари після попереднього навчання класифікатора.","metadata":{}},{"id":"20e915e8-1214-44f3-9acd-f3aff6a67567","cell_type":"code","source":"# Приклад fine-tuning для найкращої архітектури (опційно, збільшує час виконання)\nFINETUNE = False  # змініть на True, якщо потрібно продемонструвати навчання всіх шарів\n\nif FINETUNE:\n    base_ft = MobileNet(weights='imagenet', include_top=False, input_shape=input_shape_fruit)\n    model_ft, _ = build_transfer_model(base_ft, num_classes=num_classes_fruit, trainable_base=False)\n\n    model_ft.fit(train_gen_fruit, epochs=5, validation_data=val_gen_fruit, verbose=1)\n\n    base_ft.trainable = True\n    for layer in base_ft.layers[:-20]:\n        layer.trainable = False\n\n    model_ft.compile(\n        optimizer=optimizers.Adam(learning_rate=1e-5),\n        loss='sparse_categorical_crossentropy',\n        metrics=['accuracy'],\n    )\n    history_ft = model_ft.fit(\n        train_gen_fruit,\n        epochs=5,\n        validation_data=val_gen_fruit,\n        verbose=1,\n    )\n    test_loss_ft, test_acc_ft = model_ft.evaluate(test_gen_fruit, verbose=0)\n    print(f'Fine-tuning test accuracy: {test_acc_ft:.4f}')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-06-07T19:34:57.517760Z","iopub.execute_input":"2026-06-07T19:34:57.518087Z","iopub.status.idle":"2026-06-07T19:34:57.532269Z","shell.execute_reply.started":"2026-06-07T19:34:57.518053Z","shell.execute_reply":"2026-06-07T19:34:57.531530Z"}},"outputs":[],"execution_count":null},{"id":"32437625-c602-4b93-9dcc-e2877d7c7b20","cell_type":"markdown","source":"## 8. Контрольні питання\n\n**1. Основна ідея переносу навчання.**\n\nПеренос навчання полягає у використанні знань, отриманих моделлю на одній задачі (наприклад, класифікація ImageNet), для прискореного навчання на іншій, пов’язаній задачі з меншим обсягом даних. Замість навчання мережі «з нуля» беруть попередньо навчену архітектуру, видаляють верхні класифікаційні шари, зафіксовують або частково розблоковують нижні шари та додають новий класифікатор під потрібну кількість класів. Нижні шари зберігають універсальні ознаки (краї, текстури, форми), тому достатньо навчити верхні шари або провести коротке донавчання (fine-tuning).\n\n**2. Яким чином навчати всі шари мережі, а не тільки Dense?**\n\nЗа замовчуванням базова модель робиться ненавчальною через `base_model.trainable = False`, і оновлюються лише додані шари. Щоб навчати також згорткові шари, потрібно:\n\n1. Встановити `base_model.trainable = True` (або `layer.trainable = True` для обраних шарів).\n2. Повторно скомпілювати модель (`model.compile(...)`).\n3. Використати менший коефіцієнт навчання (наприклад, `1e-5`), щоб не «зруйнувати» попередньо навчені ваги.\n4. Зазвичай застосовують двоетапну схему: спочатку навчають лише класифікатор, потім розблоковують останні N шарів базової мережі і продовжують навчання (fine-tuning).\n\nПриклад коду наведено у розділі 7.","metadata":{}},{"id":"e141bae2-da29-4559-989a-cff902912e87","cell_type":"markdown","source":"## 9. Висновки\n\n1. Реалізовано перенос навчання засобами Keras на двох наборах даних: Dogs vs Cats (бінарна класифікація) та Fruits & Vegetables (36 класів).\n2. Виконано приклад з лабораторного методичного матеріалу та розширено експеримент порівнянням архітектур Xception, VGG19, ResNet50 і MobileNet.\n3. Зафіксовано метрики точності, функції втрат і час навчання для кожної моделі.\n4. Підтверджено, що попередньо навчені згорткові мережі ефективно адаптуються до нових задач класифікації зображень без повного навчання від початкових випадкових ваг.\n\nПісля виконання notebook замініть абзац нижче фактичними результатами з таблиці експериментів:\n\nНайвищу тестову точність на наборі Fruits & Vegetables показала модель [назва], test accuracy = [значення]. На задачі Dogs vs Cats модель VGG19 досягла test accuracy = [значення].","metadata":{}}]}