{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.10","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":21154,"databundleVersionId":1243559,"sourceType":"competition"}],"dockerImageVersionId":30132,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"!pip install -q efficientnet","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-02-11T16:08:59.147467Z","iopub.execute_input":"2024-02-11T16:08:59.147924Z","iopub.status.idle":"2024-02-11T16:09:08.729470Z","shell.execute_reply.started":"2024-02-11T16:08:59.147843Z","shell.execute_reply":"2024-02-11T16:09:08.728187Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tensorflow as tf\nimport matplotlib.pyplot as plt\nimport math, random, re, os, numpy as np\n\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.layers import Dense\nfrom tensorflow.keras.callbacks import ModelCheckpoint\nfrom efficientnet.tfkeras import EfficientNetB7\nfrom tensorflow.keras.applications.vgg16 import VGG16\nfrom tensorflow.keras.applications.xception import Xception\n\nfrom kaggle_datasets import KaggleDatasets\n\nfrom tensorflow import keras\n#from tensorflow.keras.layers.experimental import preprocessing","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:09:12.275677Z","iopub.execute_input":"2024-02-11T16:09:12.275977Z","iopub.status.idle":"2024-02-11T16:09:17.574567Z","shell.execute_reply.started":"2024-02-11T16:09:12.275941Z","shell.execute_reply":"2024-02-11T16:09:17.573848Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Detect TPU, return appropriate distribution strategy\ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver() \n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    tpu = None\n\nif tpu:\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nelse:\n    strategy = tf.distribute.get_strategy() \n\nprint(\"REPLICAS: \", strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:09:37.515061Z","iopub.execute_input":"2024-02-11T16:09:37.515357Z","iopub.status.idle":"2024-02-11T16:09:37.526715Z","shell.execute_reply.started":"2024-02-11T16:09:37.515323Z","shell.execute_reply":"2024-02-11T16:09:37.525964Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"GCS_DS_PATH = KaggleDatasets().get_gcs_path()#Google cloud storage bucket path","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:09:42.110144Z","iopub.execute_input":"2024-02-11T16:09:42.110457Z","iopub.status.idle":"2024-02-11T16:09:43.199283Z","shell.execute_reply.started":"2024-02-11T16:09:42.110422Z","shell.execute_reply":"2024-02-11T16:09:43.198670Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import efficientnet.tfkeras as efn\n\nIMAGE_SIZE = [192, 192] # 192, 224, 331, 512\nEPOCHS = 20\nBATCH_SIZE = 8 * strategy.num_replicas_in_sync\n\n\n\nNUM_TRAINING_IMAGES = 12753\nNUM_TEST_IMAGES = 7382\nSTEPS_PER_EPOCH = NUM_TRAINING_IMAGES // BATCH_SIZE","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:14.424343Z","iopub.execute_input":"2024-02-11T16:13:14.425084Z","iopub.status.idle":"2024-02-11T16:13:14.429650Z","shell.execute_reply.started":"2024-02-11T16:13:14.425046Z","shell.execute_reply":"2024-02-11T16:13:14.428882Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def decode_image(image_data):\n    \"\"\"Декодирует изображение в vyjujvthye. vfnhbwe (тензор)\n    Нормализует данные и преобразовывает изображения к указанному размеру\"\"\"\n    image = tf.image.decode_jpeg(image_data, channels=3) # Декодирование изображения в формате JPEG в тензор uint8.\n    image = tf.cast(image, tf.float32) / 255.0  # преобразовать изображение в плавающее в диапазоне [0, 1]\n    image = tf.reshape(image, [*IMAGE_SIZE, 3]) # явный размер, необходимый для TPU\n    return image\n\ndef read_labeled_tfrecord(example):\n    LABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string), # tf.string означает байтовую строку\n        \"class\": tf.io.FixedLenFeature([], tf.int64),  # [] означает отдельный элемент\n    }\n    example = tf.io.parse_single_example(example, LABELED_TFREC_FORMAT) # парсим отдельный пример в указанном формате\n    image = decode_image(example['image']) # преобразуем изображение к нужному нам формату\n    label = tf.cast(example['class'], tf.int32)\n    return image, label # возвращает набор данных пар (изображение, метка)\n\ndef read_unlabeled_tfrecord(example):\n    UNLABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string), # tf.string означает байтовую строку\n        \"id\": tf.io.FixedLenFeature([], tf.string), # [] означает отдельный элемент\n        # класс отсутствует, задача этого конкурса - предсказать классы цветов для тестового набора данных\n    }\n    example = tf.io.parse_single_example(example, UNLABELED_TFREC_FORMAT)\n    image = decode_image(example['image']) # преобразуем изображение к нужному нам формату\n    idnum = example['id']\n    return image, idnum # returns a dataset of image(s)\n\ndef load_dataset(filenames, labeled=True, ordered=False):\n    \"\"\"Читает из TFRecords. Для оптимальной производительности одновременное чтение из нескольких\n    файлов без учета порядка данных. Порядок не имеет значения, поскольку мы все равно будем перетасовывать данные\"\"\"\n\n    ignore_order = tf.data.Options() # Представляет параметры для tf.data.Dataset.\n    if not ordered:\n        ignore_order.experimental_deterministic = False # отключить порядок, увеличить скорость\n\n    dataset = tf.data.TFRecordDataset(filenames) # автоматически чередует чтение из нескольких файлов\n    dataset = dataset.with_options(ignore_order) # использует данные сразу после их поступления, а не в исходном порядке\n    dataset = dataset.map(read_labeled_tfrecord if labeled else read_unlabeled_tfrecord)\n    # возвращает набор данных пар (изображение, метка), если метка = Истина, или пар (изображение, идентификатор), если метка = Ложь\n    return dataset\n\ndef get_training_dataset():\n    dataset = load_dataset(tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/train/*.tfrec'), labeled=True)\n    dataset = dataset.repeat() # the training dataset must repeat for several epochs\n    dataset = dataset.shuffle(2048)\n    dataset = dataset.batch(BATCH_SIZE)\n    return dataset\n\ndef get_validation_dataset():\n    dataset = load_dataset(tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/val/*.tfrec'), labeled=True, ordered=False)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()\n    return dataset\n\ndef get_test_dataset(ordered=False):\n    dataset = load_dataset(tf.io.gfile.glob(GCS_DS_PATH + '/tfrecords-jpeg-192x192/test/*.tfrec'), labeled=False, ordered=ordered)\n    dataset = dataset.batch(BATCH_SIZE)\n    return dataset\n\ntraining_dataset = get_training_dataset()\nvalidation_dataset = get_validation_dataset()\n","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:17.940286Z","iopub.execute_input":"2024-02-11T16:13:17.940647Z","iopub.status.idle":"2024-02-11T16:13:20.092072Z","shell.execute_reply.started":"2024-02-11T16:13:17.940612Z","shell.execute_reply":"2024-02-11T16:13:20.091457Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"CLASSES = ['pink primrose',    'hard-leaved pocket orchid', 'canterbury bells', 'sweet pea',     'wild geranium',     'tiger lily',           'moon orchid',              'bird of paradise', 'monkshood',        'globe thistle',         # 00 - 09\n           'snapdragon',       \"colt's foot\",               'king protea',      'spear thistle', 'yellow iris',       'globe-flower',         'purple coneflower',        'peruvian lily',    'balloon flower',   'giant white arum lily', # 10 - 19\n           'fire lily',        'pincushion flower',         'fritillary',       'red ginger',    'grape hyacinth',    'corn poppy',           'prince of wales feathers', 'stemless gentian', 'artichoke',        'sweet william',         # 20 - 29\n           'carnation',        'garden phlox',              'love in the mist', 'cosmos',        'alpine sea holly',  'ruby-lipped cattleya', 'cape flower',              'great masterwort', 'siam tulip',       'lenten rose',           # 30 - 39\n           'barberton daisy',  'daffodil',                  'sword lily',       'poinsettia',    'bolero deep blue',  'wallflower',           'marigold',                 'buttercup',        'daisy',            'common dandelion',      # 40 - 49\n           'petunia',          'wild pansy',                'primula',          'sunflower',     'lilac hibiscus',    'bishop of llandaff',   'gaura',                    'geranium',         'orange dahlia',    'pink-yellow dahlia',    # 50 - 59\n           'cautleya spicata', 'japanese anemone',          'black-eyed susan', 'silverbush',    'californian poppy', 'osteospermum',         'spring crocus',            'iris',             'windflower',       'tree poppy',            # 60 - 69\n           'gazania',          'azalea',                    'water lily',       'rose',          'thorn apple',       'morning glory',        'passion flower',           'lotus',            'toad lily',        'anthurium',             # 70 - 79\n           'frangipani',       'clematis',                  'hibiscus',         'columbine',     'desert-rose',       'tree mallow',          'magnolia',                 'cyclamen ',        'watercress',       'canna lily',            # 80 - 89\n           'hippeastrum ',     'bee balm',                  'pink quill',       'foxglove',      'bougainvillea',     'camellia',             'mallow',                   'mexican petunia',  'bromelia',         'blanket flower',        # 90 - 99\n           'trumpet creeper',  'blackberry lily',           'common tulip',     'wild rose']                          # 100 - 102","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:24.483502Z","iopub.execute_input":"2024-02-11T16:13:24.483784Z","iopub.status.idle":"2024-02-11T16:13:24.493391Z","shell.execute_reply.started":"2024-02-11T16:13:24.483753Z","shell.execute_reply":"2024-02-11T16:13:24.492487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Функции для визуализации","metadata":{}},{"cell_type":"code","source":"# numpy and matplotlib defaults\nnp.set_printoptions(threshold=15, linewidth=80)\n\ndef batch_to_numpy_images_and_labels(data):\n    images, labels = data\n    numpy_images = images.numpy()\n    numpy_labels = labels.numpy()\n    if numpy_labels.dtype == object: # binary string in this case, these are image ID strings\n        numpy_labels = [None for _ in enumerate(numpy_images)]\n    # If no labels, only image IDs, return None for labels (this is the case for test data)\n    return numpy_images, numpy_labels\n\ndef title_from_label_and_target(label, correct_label):\n    if correct_label is None:\n        return CLASSES[label], True\n    correct = (label == correct_label)\n    return \"{} [{}{}{}]\".format(CLASSES[label], 'OK' if correct else 'NO', u\"\\u2192\" if not correct else '',\n                                CLASSES[correct_label] if not correct else ''), correct\n\ndef display_one_flower(image, title, subplot, red=False, titlesize=16):\n    plt.subplot(*subplot)\n    plt.axis('off')\n    plt.imshow(image)\n    if len(title) > 0:\n        plt.title(title, fontsize=int(titlesize) if not red else int(titlesize/1.2), color='red' if red else 'black', fontdict={'verticalalignment':'center'}, pad=int(titlesize/1.5))\n    return (subplot[0], subplot[1], subplot[2]+1)\n    \ndef display_batch_of_images(databatch, predictions=None):\n    \"\"\"This will work with:\n    display_batch_of_images(images)\n    display_batch_of_images(images, predictions)\n    display_batch_of_images((images, labels))\n    display_batch_of_images((images, labels), predictions)\n    \"\"\"\n    # data\n    images, labels = batch_to_numpy_images_and_labels(databatch)\n    if labels is None:\n        labels = [None for _ in enumerate(images)]\n        \n    # auto-squaring: this will drop data that does not fit into square or square-ish rectangle\n    rows = int(math.sqrt(len(images)))\n    cols = len(images)//rows\n        \n    # size and spacing\n    FIGSIZE = 13.0\n    SPACING = 0.1\n    subplot=(rows,cols,1)\n    if rows < cols:\n        plt.figure(figsize=(FIGSIZE,FIGSIZE/cols*rows))\n    else:\n        plt.figure(figsize=(FIGSIZE/rows*cols,FIGSIZE))\n    \n    # display\n    for i, (image, label) in enumerate(zip(images[:rows*cols], labels[:rows*cols])):\n        title = '' if label is None else CLASSES[label]\n        correct = True\n        if predictions is not None:\n            title, correct = title_from_label_and_target(predictions[i], label)\n        dynamic_titlesize = FIGSIZE*SPACING/max(rows,cols)*40+3 # magic formula tested to work from 1x1 to 10x10 images\n        subplot = display_one_flower(image, title, subplot, not correct, titlesize=dynamic_titlesize)\n    \n    #layout\n    plt.tight_layout()\n    if label is None and predictions is None:\n        plt.subplots_adjust(wspace=0, hspace=0)\n    else:\n        plt.subplots_adjust(wspace=SPACING, hspace=SPACING)\n    plt.show()\n\ndef display_confusion_matrix(cmat, score, precision, recall):\n    plt.figure(figsize=(15,15))\n    ax = plt.gca()\n    ax.matshow(cmat, cmap='Reds')\n    ax.set_xticks(range(len(CLASSES)))\n    ax.set_xticklabels(CLASSES, fontdict={'fontsize': 7})\n    plt.setp(ax.get_xticklabels(), rotation=45, ha=\"left\", rotation_mode=\"anchor\")\n    ax.set_yticks(range(len(CLASSES)))\n    ax.set_yticklabels(CLASSES, fontdict={'fontsize': 7})\n    plt.setp(ax.get_yticklabels(), rotation=45, ha=\"right\", rotation_mode=\"anchor\")\n    titlestring = \"\"\n    if score is not None:\n        titlestring += 'f1 = {:.3f} '.format(score)\n    if precision is not None:\n        titlestring += '\\nprecision = {:.3f} '.format(precision)\n    if recall is not None:\n        titlestring += '\\nrecall = {:.3f} '.format(recall)\n    if len(titlestring) > 0:\n        ax.text(101, 1, titlestring, fontdict={'fontsize': 18, 'horizontalalignment':'right', 'verticalalignment':'top', 'color':'#804040'})\n    plt.show()\n    \ndef display_training_curves(training, validation, title, subplot):\n    if subplot%10==1: # set up the subplots on the first call\n        plt.subplots(figsize=(10,10), facecolor='#F0F0F0')\n        plt.tight_layout()\n    ax = plt.subplot(subplot)\n    ax.set_facecolor('#F8F8F8')\n    ax.plot(training)\n    ax.plot(validation)\n    ax.set_title('model '+ title)\n    ax.set_ylabel(title)\n    #ax.set_ylim(0.28,1.05)\n    ax.set_xlabel('epoch')\n    ax.legend(['train', 'valid.'])","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:26.959313Z","iopub.execute_input":"2024-02-11T16:13:26.959613Z","iopub.status.idle":"2024-02-11T16:13:26.986019Z","shell.execute_reply.started":"2024-02-11T16:13:26.959579Z","shell.execute_reply":"2024-02-11T16:13:26.985326Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_batch = iter(training_dataset)\ndisplay_batch_of_images(next(train_batch))","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:31.019488Z","iopub.execute_input":"2024-02-11T16:13:31.019774Z","iopub.status.idle":"2024-02-11T16:13:42.950075Z","shell.execute_reply.started":"2024-02-11T16:13:31.019742Z","shell.execute_reply":"2024-02-11T16:13:42.949059Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"LR_START = 0.00001\nLR_MAX = 0.00005 * strategy.num_replicas_in_sync#0.0001\nLR_MIN = 0.00001\nLR_RAMPUP_EPOCHS = 5\nLR_SUSTAIN_EPOCHS = 0\nLR_EXP_DECAY = .75\n\ndef lrfn(epoch):\n    if epoch < LR_RAMPUP_EPOCHS:\n        lr = (LR_MAX - LR_START) / LR_RAMPUP_EPOCHS * epoch + LR_START\n    elif epoch < LR_RAMPUP_EPOCHS + LR_SUSTAIN_EPOCHS:\n        lr = LR_MAX\n    else:\n        lr = (LR_MAX - LR_MIN) * LR_EXP_DECAY**(epoch - LR_RAMPUP_EPOCHS - LR_SUSTAIN_EPOCHS) + LR_MIN\n    return lr\n\nlr_callback = tf.keras.callbacks.LearningRateScheduler(lrfn, verbose=True)\n\n# построим график изменения шага обучение в зависимости от эпох\nrng = [i for i in range(EPOCHS)]\ny = [lrfn(x) for x in rng]\nplt.plot(rng, y)\nprint(\"Learning rate schedule: {:.3g} to {:.3g} to {:.3g}\".format(y[0], max(y), y[-1]))","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:13:51.548700Z","iopub.execute_input":"2024-02-11T16:13:51.549000Z","iopub.status.idle":"2024-02-11T16:13:51.749417Z","shell.execute_reply.started":"2024-02-11T16:13:51.548968Z","shell.execute_reply":"2024-02-11T16:13:51.748642Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Используем предобученную модель VGG-16","metadata":{}},{"cell_type":"code","source":"from tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.layers.experimental import preprocessing\nwith strategy.scope() :\n    pretrained_model = tf.keras.applications.VGG16(weights = 'imagenet', include_top = False)\n    pretrained_model.trainable = False\n    model = keras.Sequential([\n        layers.InputLayer([*IMAGE_SIZE, 3]),\n        preprocessing.RandomFlip('horizontal'),\n        preprocessing.RandomFlip('vertical'),\n        preprocessing.RandomWidth(factor = 0.15),\n        preprocessing.RandomRotation(factor = 0.20),\n        preprocessing.RandomTranslation(height_factor = 0.1, width_factor = 0.1),\n        pretrained_model,\n        layers.GlobalAveragePooling2D(),\n        layers.Dense(104, activation = 'softmax')\n    ])\n\nmodel.compile(\n    optimizer = 'adam',\n    loss = 'sparse_categorical_crossentropy',\n    metrics =['sparse_categorical_accuracy']\n    \n)\n\nhistory = model.fit(training_dataset,\n                    steps_per_epoch = STEPS_PER_EPOCH,\n                    epochs = EPOCHS,\n                    validation_data = validation_dataset\n)\n\n\n    \n    ","metadata":{"execution":{"iopub.status.busy":"2024-02-11T10:30:16.281080Z","iopub.execute_input":"2024-02-11T10:30:16.281396Z","iopub.status.idle":"2024-02-11T10:59:45.808799Z","shell.execute_reply.started":"2024-02-11T10:30:16.281361Z","shell.execute_reply":"2024-02-11T10:59:45.807954Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\ndf = pd.DataFrame(history.history)\ndf.loc[:, ['loss', 'val_loss']].plot()\ndf.loc[:,['sparse_categorical_accuracy', 'val_sparse_categorical_accuracy']].plot()","metadata":{"execution":{"iopub.status.busy":"2024-02-11T11:15:34.121043Z","iopub.execute_input":"2024-02-11T11:15:34.121363Z","iopub.status.idle":"2024-02-11T11:15:34.864653Z","shell.execute_reply.started":"2024-02-11T11:15:34.121329Z","shell.execute_reply":"2024-02-11T11:15:34.863893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"with strategy.scope():\n    model_name = 'efficientnet'\n    base_model = getattr(efn, \n                         'EfficientNetB7')(weights='noisy-student', \n     include_top=False,\n     pooling='avg', \n     input_shape=(*IMAGE_SIZE, 3)) \n    model = tf.keras.Sequential([\n        base_model,\n        layers.Dense(len(CLASSES), activation='softmax')\n    ])\n\nmodel.compile(\n    optimizer='nadam',\n    loss = 'sparse_categorical_crossentropy',\n    metrics=['sparse_categorical_accuracy']\n)\n","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:14:17.431604Z","iopub.execute_input":"2024-02-11T16:14:17.432208Z","iopub.status.idle":"2024-02-11T16:14:28.928503Z","shell.execute_reply.started":"2024-02-11T16:14:17.432171Z","shell.execute_reply":"2024-02-11T16:14:28.927851Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = model.fit(get_training_dataset(), \n              steps_per_epoch=STEPS_PER_EPOCH, \n              epochs=EPOCHS, \n              validation_data=get_validation_dataset(),\n              callbacks=lr_callback,\n              verbose=2)","metadata":{"execution":{"iopub.status.busy":"2024-02-11T16:14:36.987084Z","iopub.execute_input":"2024-02-11T16:14:36.987703Z","iopub.status.idle":"2024-02-11T19:23:06.001063Z","shell.execute_reply.started":"2024-02-11T16:14:36.987667Z","shell.execute_reply":"2024-02-11T19:23:06.000282Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import pandas as pd\ndf = pd.DataFrame(history.history)\ndf.loc[:, ['loss', 'val_loss']].plot()\ndf.loc[:,['sparse_categorical_accuracy', 'val_sparse_categorical_accuracy']].plot()","metadata":{"execution":{"iopub.status.busy":"2024-02-11T19:23:44.922918Z","iopub.execute_input":"2024-02-11T19:23:44.923194Z","iopub.status.idle":"2024-02-11T19:23:45.432280Z","shell.execute_reply.started":"2024-02-11T19:23:44.923163Z","shell.execute_reply":"2024-02-11T19:23:45.431614Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"test_ds = get_test_dataset(ordered=True)\n\nprint('Вычисляем предсказания...')\ntest_images_ds = test_ds.map(lambda image, idnum: image)\nprobabilities = model.predict(test_images_ds)\npredictions = np.argmax(probabilities, axis=-1)\nprint(predictions)\n\nprint('Создание файла submission.csv...')\ntest_ids_ds = test_ds.map(lambda image, idnum: idnum).unbatch()\ntest_ids = next(iter(test_ids_ds.batch(NUM_TEST_IMAGES))).numpy().astype('U')\nnp.savetxt('submission.csv', np.rec.fromarrays([test_ids, predictions]), fmt=['%s', '%d'], delimiter=',', header='id,label', comments='')","metadata":{"execution":{"iopub.status.busy":"2024-02-11T19:27:57.425782Z","iopub.execute_input":"2024-02-11T19:27:57.426142Z","iopub.status.idle":"2024-02-11T19:30:07.169133Z","shell.execute_reply.started":"2024-02-11T19:27:57.426104Z","shell.execute_reply":"2024-02-11T19:30:07.168488Z"},"trusted":true},"execution_count":null,"outputs":[]}]}