7 из 9 для Введение в обработку изображений

Обработка изображений в машинном обучении

Машинное обучение в действии

Наконец, мы увидим, как обработка изображений дополняет машинное обучение!

В этой записи блога мы будем классифицировать фотографии листьев с помощью методов машинного обучения (ML), как традиционного ML, так и методов глубокого обучения (DL).

Существует 5 типов листьев, которые мы обозначим как Растения A, B , C, D и E.

Традиционное машинное обучение

Для традиционных методов машинного обучения потребуются следующие шаги:

  1. Прочитайте и очистите изображения
  2. Сегментируйте интересующие вас объекты
  3. Извлечение признаков из объектов
  4. Обучение модели машинного обучения

Чтение и уборка

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

Это будет сделано для всех листьев во всех пяти классах, но в демонстрационных целях я покажу, как это было сделано для одного набора изображений листьев, принадлежащих растению A.

image_raw = io.imread('leaves/plantA_1.jpg')
fig, ax = plt.subplots()
ax.imshow(image_raw,cmap='gray')
fig.show()

Бинаризация проста с использованием ванильного порога.

gray_leaves = rgb2gray(image_raw[:,:,:3])
binary_leaves = util.invert(gray_leaves > 0.5)
plt.figure()
plt.imshow(binary_leaves, cmap='gray')
plt.axis('off')
plt.show()

Сегментация

Сегментация выполняется с помощью функции regionprops, которая уже обсуждалась в предыдущих сообщениях блога.

label_leaves = label(binary_leaves)
plt.figure()
plt.imshow(label_leaves);

Эти свойства regionprops будут использоваться для дифференциации листьев для модели ML.

  1. Площадь
  2. Периметр
  3. Эксцентричность
  4. Солидность
  5. Объем

Площадь представляет собой общую площадь поверхности, занимаемую листом. Листья разных форм и размеров будут иметь разную площадь. Например, круглые листья, как правило, имеют большую площадь по сравнению с продолговатыми или зазубренными листьями, которые могут иметь меньшие или более неправильные участки.

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

Эксцентриситет измеряет степень удлинения или коэффициент удлинения объекта. В контексте классификации листьев это может помочь различить продолговатые и круглые листья. Листья с более высокими значениями эксцентриситета будут иметь тенденцию быть более продолговатыми, а листья с более низкими значениями эксцентриситета будут казаться более круглыми.

Прочность описывает компактность формы листа, представляя отношение площади листа к площади выпуклой оболочки. Выпуклая оболочка — это наименьший выпуклый многоугольник, полностью охватывающий форму листа. Листья различной формы, такие как зазубренные или круглые, будут иметь разные значения прочности. Зубчатые листья могут иметь меньшую прочность из-за их неровностей, в то время как круглые листья обычно имеют более высокую прочность.

Протяженность — это отношение площади листа к общей площади ограничивающей рамки. Он показывает, какая часть ограничивающей рамки занята листом. Различные формы листа будут иметь разные значения экстента. Например, более крупные листья могут иметь более высокие значения экстента по сравнению с меньшими листьями, что указывает на то, что они занимают большую часть ограничивающей рамки.

Извлечение признаков объекта

Функции для модели ML будут получены из пяти regionprops свойств, упомянутых ранее.

def get_class(fpath):
    '''
    Extracts the class of the leaves from the filepath.
    '''
    return fpath.split('/')[1].split('.')[0].split('_')[0]

leaves_data = []

folder_path = 'leaves'

for filename in tqdm(os.listdir(folder_path)):
    file_path = os.path.join(folder_path, filename)
    
    if os.path.isfile(file_path):
        
        image_raw = io.imread(file_path)
        gray_leaves = rgb2gray(image_raw[:,:,:3])
        binary_leaves = util.invert(gray_leaves > 0.5)
        
        label_leaves = label(binary_leaves)
        
        raw_props = regionprops(label_leaves)[1:] # remove the background class
        clean_props = [prop for prop in raw_props if prop.area > 1000] # just the leaves, remove specks
        
        for prop in clean_props:
            
            leaves_data.append({'area': prop.area,
                                'perim': prop.perimeter,
                                'ecc': prop.eccentricity,
                                'solid': prop.solidity,
                                'extent': prop.extent,
                                'label': get_class(file_path)
                               })
            
df_leaves = pd.DataFrame(data=leaves_data)
display(df_leaves)

Обучение модели машинного обучения

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

Мы рассмотрим 3 различных традиционных метода машинного обучения, представляющих обучение на основе сходства (kNN), обучение на основе ошибок (логистическая регрессия) и обучение на основе информации (метод повышения градиента).

Целевая точность должна быть около 1,25 x PCC. Критерий пропорционального шанса (PCC) — это вероятность того, что объекты будут правильно классифицированы случайно. В нашем случае PCC составляет около 0,20, а 1,25 x PCC составляет 0,25.

X = df_leaves.drop(['label'], axis=1)
y = df_leaves['label']

X_trainval, X_hold, y_trainval, y_hold = train_test_split(X, y, test_size=0.1, random_state=69)

Разделение trainval (обучение + проверка) и тестов было установлено на уровне 0,9–0,1.

Во-первых, мы можем попробовать использовать kNN для этих функций.

pipeline = Pipeline(steps=[('scl', StandardScaler()),
                                ('model', KNeighborsClassifier())])


param_grid = {'model__n_neighbors': list(range(5, 31, 5)),
             }

scoring = 'accuracy'
cv = 3

grid_search = GridSearchCV(estimator=pipeline,
                            param_grid=param_grid,
                            scoring=scoring,
                            cv=cv,
                            n_jobs=-1,
                            verbose=1,
                            return_train_score=True)

grid_search.fit(X_trainval, y_trainval)
# grid_search.fit(X_trainval_res, y_trainval_res)

val_acc = grid_search.best_score_
train_acc = grid_search.cv_results_[
    'mean_train_score'][grid_search.best_index_]
hold_acc = grid_search.score(X_hold, y_hold)

print(f'\nKNN Classifier\n\nTrain score: {train_acc:.3f}\nVal score: {val_acc:.3f}\n\nTest score: {hold_acc:.3f}')

Мы также можем попробовать использовать логистическую регрессию.

pipeline = Pipeline(steps=[('scl', StandardScaler()),
                                ('model', LogisticRegression())])


param_grid = {'model__C': [0.1, 1, 5, 10, 100, 1000],
              'model__penalty': ['l2'],
              'model__solver': ['liblinear'],
              'model__random_state': [69]
             }

scoring = 'accuracy'
cv = 3

grid_search = GridSearchCV(estimator=pipeline,
                            param_grid=param_grid,
                            scoring=scoring,
                            cv=cv,
                            n_jobs=-1,
                            verbose=1,
                            return_train_score=True)

grid_search.fit(X_trainval, y_trainval)
# grid_search.fit(X_trainval_res, y_trainval_res)

val_acc = grid_search.best_score_
train_acc = grid_search.cv_results_[
    'mean_train_score'][grid_search.best_index_]
hold_acc = grid_search.score(X_hold, y_hold)

print(f'\nLogistic Regression\n\nTrain score: {train_acc:.3f}\nVal score: {val_acc:.3f}\n\nTest score: {hold_acc:.3f}')

Наконец, мы можем попробовать ансамблевую древовидную модель (метод повышения градиента).

pipeline = Pipeline(steps=[('scl', StandardScaler()),
                                ('model', GradientBoostingClassifier())])


param_grid = {'model__learning_rate': [0.001],
              'model__max_features': [3, 4, 5],
              'model__max_depth': [10, 20],
              'model__random_state': [69]
             }

scoring = 'accuracy'
cv = 3

grid_search = GridSearchCV(estimator=pipeline,
                            param_grid=param_grid,
                            scoring=scoring,
                            cv=cv,
                            n_jobs=-1,
                            verbose=1,
                            return_train_score=True)

grid_search.fit(X_trainval, y_trainval)
# grid_search.fit(X_trainval_res, y_trainval_res)

val_acc = grid_search.best_score_
train_acc = grid_search.cv_results_[
    'mean_train_score'][grid_search.best_index_]
hold_acc = grid_search.score(X_hold, y_hold)

print(f'GBM\n\nTrain score: {train_acc:.3f}\nVal score: {val_acc:.3f}\n\nTest score: {hold_acc:.3f}')

Все три традиционных метода машинного обучения смогли превзойти 1,25-кратный PCC. GBM имел самую высокую точность, за ней следовали логистическая регрессия и kNN.

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

Глубокое обучение

В то время как традиционным методам машинного обучения требовалось 4 разных шага для классификации, для методов глубокого обучения требуется только один шаг:

  1. Обучение модели машинного обучения

Да, нет необходимости бинаризировать и сегментировать изображения листьев. Вы можете просто передать его в наш конвейер глубокого обучения.

В этом случае мы выполним извлечение признаков на предварительно обученной модели VGG-19. Мы пропустили создание наборов данных и загрузчиков данных, но это уже часть области глубокого обучения и может выходить за рамки перспективы обработки изображений для этой темы.

# Set device
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# Load train, validation, and test datasets
train_data = image_datasets['train']
val_data = image_datasets['validation']
test_data = image_datasets['test']

# Define data loaders
train_loader = dataloaders['train']
valid_loader = dataloaders['validation']
test_loader = dataloaders['test']

# Load the pretrained VGG19 model
model = models.vgg19(pretrained=True)

# Freeze the parameters of the pretrained layers
for param in model.parameters():
    param.requires_grad = False

# Modify the last fully connected layer to match the number of classes
num_classes = len(class_names)
model.classifier[6] = nn.Linear(4096, num_classes)

# Move the model to the appropriate device
model = model.to(device)

# Define loss function and optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# Train the model
num_epochs = 50
best_valid_loss = float('inf')

for epoch in range(num_epochs):
    train_loss = 0.0
    valid_loss = 0.0
    
    # Training
    model.train()
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        
        optimizer.zero_grad()
        
        outputs = model(images)
        loss = criterion(outputs, labels)
        
        loss.backward()
        optimizer.step()
        
        train_loss += loss.item() * images.size(0)
    
    # Validation
    model.eval()
    with torch.no_grad():
        for images, labels in valid_loader:
            images, labels = images.to(device), labels.to(device)
            
            outputs = model(images)
            loss = criterion(outputs, labels)
            
            valid_loss += loss.item() * images.size(0)
    
    train_loss = train_loss / len(train_loader.dataset)
    valid_loss = valid_loss / len(valid_loader.dataset)
    
    print(f"Epoch: {epoch+1}/{num_epochs}, Train Loss: {train_loss:.4f}, Valid Loss: {valid_loss:.4f}")
    
    # Save the best model based on validation loss
    if valid_loss < best_valid_loss:
        best_valid_loss = valid_loss
        torch.save(model.state_dict(), 'best_leaves_model_vgg19.pt')

# Test the model
model.load_state_dict(torch.load('best_leaves_model_vgg19.pt'))
model.eval()
correct = 0
total = 0

with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        
        outputs = model(images)
        _, predicted = torch.max(outputs.data, 1)
        
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

accuracy = 100 * correct / total
print(f"Test Accuracy: {accuracy:.2f}%")

После обучения VGG-19 в течение 50 эпох становится очевидным, что эта модель дала нам самую высокую точность по сравнению с традиционными методами машинного обучения, обсуждавшимися ранее.

Глядя на тестовый набор, ошибочно идентифицированные листья находились между растениями C и E. Два листа почти похожи по форме на основе их бинарных представлений. Возможно, это одна из ловушек моей текущей реализации. Я использовал сегментацию из regionprops ранее для своих обучающих и тестовых данных вместо необработанных обрезанных изображений листьев. Для обрезки изображений листьев можно использовать другие предварительно обученные модели. Если мы будем работать с необработанными изображениями (не бинарными), мы сможем даже правильно классифицировать все фотографии листьев, поскольку информация о текстуре и структуре жилок все еще будет присутствовать, а модель может узнать о них во время обучения.

Заключение

На нашем занятии мы узнали о применении обработки изображений в машинном обучении, в частности, в классификации фотографий листьев. Мы изучили как традиционные методы машинного обучения (ML), так и методы глубокого обучения, чтобы классифицировать пять различных типов листьев. Традиционный подход ML включал несколько шагов, включая чтение и очистку изображений, сегментацию интересующих объектов (листьев), извлечение таких характеристик, как площадь, периметр, эксцентриситет, плотность и протяженность, а также обучение моделей ML, таких как kNN, логистическая регрессия и повышение градиента. Эти модели достигли точности выше, чем критерий пропорционального шанса (PCC), который является базовой мерой для случайных прогнозов, пропорциональных распределению классов.

Однако мы поняли, что методы глубокого обучения, такие как использование предварительно обученной модели VGG-19, обеспечивают еще более высокую точность без необходимости явных этапов обработки изображений, таких как бинаризация и сегментация. Мы поняли, что работа с необработанными изображениями может получить больше информации о текстуре листа и структуре жилок, что потенциально приведет к повышению производительности классификации.

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