Эта история - вторая часть серии, где я объясняю, как выполнить задание Собаки против кошек на kaggle с помощью PyTorch. Если вы не читали первую часть серии, рекомендую сначала прочитать ее. В нем рассказывается о предварительной обработке данных, обучении модели из предварительно обученной модели, сохранении / загрузке лучшей модели и т. Д. Во второй части я расскажу о том, как делать выводы на основе тестовых данных, записывая их в файл csv и отправляя в kaggle. Код, показанный ниже, может потребовать импорта, функций, переменных из первой части для запуска без ошибок.

Итак, мы обучили нашу модель в предыдущей части, теперь давайте сначала визуализируем, как наша модель работает на проверочном наборе.

def visualize_model(model, num_images=6):
    was_training = model.training
    model.eval()
    images_so_far = 0
    fig = plt.figure()
    with torch.no_grad():
        for i, (inputs, labels) in enumerate(dataloaders['val']):
            inputs = inputs.to(device)
            labels = labels.to(device)
    outputs = model(inputs)
            _, preds = torch.max(outputs, 1)
            for j in range(inputs.size()[0]):
                images_so_far += 1
                ax = plt.subplot(num_images//2, 2, images_so_far)
                ax.axis('off')
                ax.set_title('predicted: {}'.format(class_names[preds[j]]))
                imshow(inputs.cpu().data[j])
                if images_so_far == num_images:
                    model.train(mode=was_training)
                    return
        model.train(mode=was_training)
visualize_model(model_conv)
plt.ioff()
plt.show()

Приведенный выше код покажет вам 6 (по умолчанию) изображений из набора для проверки и покажет, что наша модель думает о них. На данный момент прогнозы должны выглядеть вполне правильными.

Чтобы загружать изображения с помощью PyTorch, нам нужны подпапки в каталоге набора данных. test папка не имеет вложенных папок. Один из способов обойти это - сделать фиктивную папку в качестве подпапки и поместить все данные в подпапку и начать ее загрузку, как наборы train и val image_datasets. Однако я поступил иначе. Я выбрал более сложный способ загрузки данных, который не зависит от PyTorch. Я подумал, что это может быть полезной практикой для понимания того, как машинное обучение действительно работает за API библиотек и фреймворков машинного обучения.

Во-первых, я определяю функцию, которая берет изображение из библиотеки изображений Python (PIL) и выводит torch.Tensor, примененное с тем же преобразованием, что и данные проверки.

def apply_test_transforms(inp):
    out = transforms.functional.resize(inp, [224,224])
    out = transforms.functional.to_tensor(out)
    out = transforms.functional.normalize(out, [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    return out

Чтобы передать этой функции изображение PIL, мы должны сначала получить изображение как изображение PIL.

from PIL import Image
test_data_dir = f'{data_dir}/test'
test_data_files = os.listdir(test_data_dir)
im = Image.open(f'{test_data_dir}/{test_data_files[0]}')
plt.imshow(im)

Мы видим, что im - это изображение PIL, которое мы можем передать в apply_test_transforms.

im_as_tensor = apply_test_transforms(im)
print(im_as_tensor.size()) # torch.Size([3, 224, 224])
minibatch = torch.stack([im_as_tensor])
print(minibatch.size()) # torch.Size([1, 3, 224, 224])

apply_test_transforms возвращает нам тензор, но чтобы передать его в PyTorch для вывода, нам нужно преобразовать его в пакет, потому что модели PyTorch всегда ожидают пакетов. Используя torch.stack, достаточно легко превратить одиночный тензор в мини-пакет размером 1.

model_conv(minibatch) # tensor([[ 2.0083, -1.8386]], grad_fn=<ThAddmmBackward>)

Сделать вывод легко! просто вызовите модель с партией в качестве аргумента. классом предсказания в этом случае был кот (индекс 0), потому что 2,0083 больше -1,8386. Но подождите, отправка kaggle ожидает CSV-файл с заголовком id и label, и мы должны предоставить вероятность того, что изображение является изображением собаки для заголовка label. Примерно так.

id,label
1,0.8
2,0.1
3,0.55
...

Применение тензора к функции Softmax преобразует тензор в вероятности, которые в сумме дают 1.

softMax = nn.Softmax(dim = 1)
preds = softMax(model_conv(minibatch))
preds # tensor([[0.9791, 0.0209]], grad_fn=<SoftmaxBackward>)

Мы видим, что наша модель на 97,9% уверена, что это изображение кошки. Давайте определим функцию, с помощью которой мы можем получить вероятность собаки, передав модель и тензор.

def predict_dog_prob_of_single_instance(model, tensor):
    batch = torch.stack([tensor])
    softMax = nn.Softmax(dim = 1)
    preds = softMax(model(batch))
    return preds[0,1].item()

Также нам понадобится функция для получения изображения PIL из имени файла.

def test_data_from_fname(fname):
    im = Image.open(f'{test_data_dir}/{fname}')
    return apply_test_transforms(im)

Еще одна функция, которая нам нужна, - это функция, которая извлекает часть числового идентификатора из имени файла.

import re
def extract_file_id(fname):
    print("Extracting id from " + fname)
    return int(re.search('\d+', fname).group())
extract_file_id("cat34432.jpg") # 34432

Обратите внимание, что мы возвращаем его как int, поскольку мы хотим отсортировать идентификаторы численно. Сортировка идентификаторов строк не даст нам желаемого результата (например, «12000» раньше, чем «39», при сортировке как строка).

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

model_conv.eval()
id_to_dog_prob = {extract_file_id(fname): 
                  predict_dog_prob_of_single_instance(model_conv,
                                                      test_data_from_fname(fname))
                  for fname in test_data_files}

Pandas DataFrame имеет простой API для вывода в файл csv, поэтому теперь я собираюсь превратить id_to_dog_prob словарь в pandas DataFrame.

import pandas as pd
ds = pd.Series({id : label for (id, label) in zip(id_to_dog_prob.keys(), id_to_dog_prob.values())})
ds.head()df = pd.DataFrame(ds, columns = ['label']).sort_index()
df['id'] = df.index
df = df[['id', 'label']]
df.head()

Теперь, когда мы видим, что в нашем фрейме данных есть столбцы id и label, и все выглядит так, как ожидалось, давайте запишем в файл csv.

df.to_csv(SUBMISSION_FILE, index = False)

Изучите файл csv и убедитесь, что все в порядке. И, наконец, вы можете принять участие в конкурсе «собаки против кошек».

!kaggle competitions submit -c dogs-vs-cats-redux-kernels-edition -f submission.csv -m "Using PyTorch"

Несмотря на то, что я удалил много изображений из наборов данных для обучения и проверки, потери все равно были довольно низкими (0,07951). И это поставило бы меня на 248-е место среди 1314 записей в публичной таблице лидеров, если бы мое решение было отправлено в то время, когда конкурс еще продолжался. К сожалению, плата, похоже, больше не обновляется, поэтому я не вижу ручки kaggle на доске.

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

Вы можете увидеть полный код из моего репозитория на Github. Все из этой части находится в catsanddogs.ipynb.