Добро пожаловать в наш еженедельный блог с советами и рекомендациями FiftyOne, где мы резюмируем интересные вопросы и ответы, недавно появившиеся на Slack, GitHub, Stack Overflow и Reddit.

Подожди, а что такое FiftyOne?

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

Хорошо, давайте погрузимся в советы и рекомендации этой недели!

Пропуск классов с несколькими экземплярами обнаружения

Член сообщества Slack Сильвия Шмитт спросила:

«При группировке выборок по значениям в определенном поле я хотел бы исключить выборки со значениями, которые редко встречаются в наборе данных. Как это можно сделать?»

Один из способов добиться этого — использовать count_values() для подсчета количества вхождений каждого уникального значения в заданном поле по всему объекту Dataset или DatasetView, взять значения, которые встречаются чаще, чем желаемое отсечение, и использовать match() для получения образцов, содержащих их.

Например, если вы хотите получить образцы из тестового разделения набора данных Семьи в дикой природе со значениями name, которые встречаются в наборе данных более десяти раз, вы можете сделать это следующим образом:

import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

## load the dataset
dataset = foz.load_zoo_dataset("fiw", split="test")

counts = dataset.count_values("name")
keep_names = [name for name, count in counts.items() if count > 10]

## filter for samples with these names
view = dataset.match(F("name").is_in(keep_names))

session = fo.launch_app(view)

Затем вы можете передать полученное представление в group_by() для группировки по значениям в поле или любой другой агрегации, которую вы хотите.

Узнайте больше о count_values(), is_in() и использовании агрегаций в FiftyOne Docs.

Сохранение изменений в образцах полей

Член сообщества Slack Сильвия Шмитт спросила:

«При добавлении образцов полей и последующем изменении этих значений в представлении нужно ли сделать изменения постоянными, вызвав save() для объекта «Набор данных», или эти изменения будут сохранены, если набор данных уже является постоянным?»

Отличный вопрос, Сильвия! Как правило, при внесении изменений в отдельный образец в Dataset или DatasetView изменения необходимо сохранить, вызвав save() для образца, а не для набора данных. Это имеет место, даже если набор данных является постоянным, т.е. если

dataset.persistent = True

Например, вы можете изменить метку класса для первого обнаружения первой выборки в наборе данных Quickstart следующим образом:

import fiftyone as fo
import fiftyone.zoo as foz

## load dataset
dataset = foz.load_zoo_dataset("quickstart")

## get sample
sample = dataset.first()

## change label
sample.ground_truth.detections[0].label = "bear"

## save changes to dataset
sample.save()

Использование метода save() в наборе данных необходимо только при редактировании метаданных на уровне набора данных, таких как dataset.info.

Однако есть несколько случаев, когда нет необходимости явно запускать sample.save() для распространения изменений обратно в набор данных. К ним относятся метод view.set_values(field_name, field_vals), который принимает список значений field_vals и записывает их в поле field_name для образцов в представлении, а также метод view.tag_samples(tags), добавляющий теги tags ко всем образцам в представлении.

Если вы знаете, что вам нужно перебирать Dataset или DatasetView и вносить изменения в каждую выборку, а не вызывать save() для каждой выборки, более эффективно передать autosave=True в iter_samples(), который группирует операции. Например, чтобы установить поле random со случайным числом для каждой выборки в нашем наборе данных, мы можем запустить:

import random
import fiftyone as fo
import fiftyone.zoo as foz

## load dataset
dataset = foz.load_zoo_dataset("quickstart")

## Automatically saves sample edits in efficient batches
for sample in dataset.select_fields().iter_samples(autosave=True):
    sample["random"] = random.random()

Узнайте больше о set_values() и пометке образцов в FiftyOne Docs.

Предсказание меток классов в однородных изображениях

Член сообщества Slack Джордж Пирс спросил:

«Как лучше поступить с приложением, в котором метка объекта сильно переплетена с метками других объектов в образце? Например, у меня могут быть изображения, которые обычно представляют собой толпы всех кошек или толпы всех собак, но не толпы, содержащие и кошек, и собак».

Отличный вопрос, Джордж! Есть много способов работать с такими данными. Один из подходов — собрать множество подобных примеров и обучить модель на этих данных. Учитывая достаточное количество высококачественных примеров, модель должна (теоретически) быть в состоянии изучить эти отношения.

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

Для каждого образца проверьте, есть ли, скажем, три или более объектов с одной и той же меткой класса. Для простоты предположим, что dog и есть этот класс. Если они есть, то для всех объектов, которые не помечены как dogs в model_raw, если их оценка достоверности класса ниже некоторого порога, установите для их метки класса значение dog в model_processed.

Вот как это может выглядеть:

import numpy as np
import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

## create or load your dataset
dataset = fo.Dataset(..)

## clone predictions into new field
dataset.clone_sample_field(
    "model_raw", 
    "model_processed"
)

## set a class confidence threshold
conf_thresh = 0.3

## iterate through samples in dataset
for sample in dataset.iter_samples(autosave=True):

    dets = sample.model_processed.detections
    labels = [det.label for det in dets]
    unique_labels, label_counts = np.unique(labels, return_counts=True)

    ## find samples with at least 3 labels of same class
    if max(label_counts) > 2:
        crowd_label = unique_labels[np.argmax(label_counts)]
        for det in dets:
            if (det.label != crowd_label) and 
               (det.confidence < conf_thresh):
                 det.label = crowd_label
                 det.confidence = None

        ## tag samples to look at later
        sample.tags.append("possible homogeneous crowd")

Затем вы можете сравнить эти помеченные образцы, чьи обработанные прогнозы модели отличаются от необработанных прогнозов, и проверить их в FiftyOne App.

Узнайте больше о сохранении, хранении и клонировании образцов полей в FiftyOne Docs.

Совпадение результатов классификации

Член сообщества Slack Надав спросил:

«У меня есть набор данных с двумя видами классификации. Как лучше всего создать представление в коде или в приложении, содержащее только образцы, на которых согласуются две классификации?»

Один из способов сделать это в коде — использовать встроенные в FiftyOne возможности фильтрации и сопоставления. Метод dataset.match(my_condition) вернет представление, состоящее из всех выборок, для которых выполняется условие my_condition.

В вашем случае вы можете использовать ViewField для создания условия согласования между двумя классификациями. Вот как это может выглядеть:

import fiftyone as fo
import fiftyone.zoo as foz
from fiftyone import ViewField as F

# create or load your dataset with
# classifications in field1 and field2

dataset = fo.Dataset(...)
view = dataset.match(
    F("field1.label") == F("field2.label")
)

session = fo.launch_app(view)

Если вместо этого вам нужно представление, содержащее все выборки, в которых две классификации не совпадают, вы можете заменить оператор равенства == оператором неравенства !=.

Узнайте больше о фильтрации в FiftyOne Docs.

Завершение сеанса

Скотт, член сообщества Slack, спросил:

"Как отключить запущенный сеанс?"

В FiftyOne Сеанс — это экземпляр Приложения FiftyOne, подключенный к определенному Dataset или DatasetView. Вы можете запустить сеанс для определенного набора данных или просмотра с помощью метода launch_app():

import fiftyone as fo
import fiftyone.zoo as foz

## load dataset
dataset = foz.load_zoo_dataset("quickstart")

## launch one session
session1 = fo.launch_app(dataset)

## create a view
view = dataset.take(20)

## launch another session
session2 = fo.launch_app(view)

Вы также можете просмотреть все зарегистрированные сеансы с помощью fo.core.session.session._subscribed_sessions:

defaultdict(set,
            {5151: {Dataset:          quickstart
              Media type:       image
              Num samples:      20
              Selected samples: 0
              Selected labels:  0
              Session URL:      http://localhost:5151/
              View stages:
                  1. Take(size=20, seed=None),
              Dataset:          quickstart
              Media type:       image
              Num samples:      20
              Selected samples: 0
              Selected labels:  0
              Session URL:      http://localhost:5151/
              View stages:
                  1. Take(size=20, seed=None)}})

Когда вы завершаете процесс Python, в котором работает FiftyOne, все сеансы закрываются, поэтому обычно вам не нужно закрывать сеансы явно.

Однако, если вы хотите завершить сеанс в любой момент, вы можете сделать это с помощью закрытого метода _unregister_session():

from fiftyone.core.session.session import _unregister_session
_unregister_session(session1)

Узнайте больше о сеансах, в том числе о том, как запустить несколько экземпляров приложения на удаленном компьютере, в FiftyOne Docs.

Присоединяйтесь к сообществу FiftyOne!

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

Что дальше?

Первоначально опубликовано на https://voxel51.com 24 февраля 2023 г.