Время прочтения: 8 мин.

Задача состоит в классификации гидроакустических сигналов. Сонары (гидролокаторы) посылают звук высокой частоты в определенном направлении и получают отраженную звуковую волну. По характеристике этой волны можно сделать вывод, от чего именно она отразилась – от морской мины или же от подводного камня, скалы. Используемый для решения задачи набор данных был разработан сотрудником аэрокосмического технологического центра Полом Горманом в разгар холодной войны. Для получения данных металлический цилиндр и цилиндрическая горная порода, оба длиной около 1,5 метров, размещались на песчаном дне океана.

База данных о гидроакустических сигналах содержит 208 экземпляров и 60 атрибутов. Файл «sonar.mines» содержит 111 экземпляров, полученных путем отскакивания сигналов от мины под разными углами и при различных условиях. Файл «sonar.rocks» содержит 97 экземпляров, полученных от камней или скал в аналогичных условиях. Переданный сигнал гидролокатора представляет собой частотно-модулированный ЛЧМ, повышающийся по частоте. Каждый экземпляр представляет собой набор из 60 чисел в диапазоне от 0,0 до 1,0. Каждое число представляет энергию в пределах определенной полосы частот, полученной за определенный период времени. Метка, связанная с каждой записью, содержит букву «R», если объект представляет собой камень/скалу («Rocks»), и «M», если это мина («Mines»). Оба файла с представлением данных были интегрированы в один csv-файл «sonar.all-data.csv».

Текущая работа посвящена сравнению различных классификаторов машинного обучения на описанном наборе данных. Среди таких классификаторов были выбраны многослойный персептрон, решающее дерево (деревья решений), случайный лес, логистическая регрессия и метод k ближайших соседей. Для создания классификаторов использовалась Python-библиотека Scikit-Learn. Набор данных уже нормализован. Следовательно, единственная предварительная обработка, которую здесь необходимо выполнить, состоит в том, чтобы разбить набор данных на атрибуты и метки классов (листинг 1), а затем разделить его на данные для обучения и тестирования. Так, использование разбиения 20:80 для тестирования и обучения оставляет 166 экземпляров для обучения (и проверки), а также 42 экземпляра для окончательной оценки. После этого можно применить различные алгоритмы классификации для получения окончательного результата.

Листинг 1. Исходный код функции разбиения данных для обучения модели

def PrepareData():
    dataset = dataframe.values # получить данные в виде numpy-массива
    X = dataset[:,0:60].astype(float) # вещественные данные 
    Y = dataset[:,60] # метки R и M 
    return X, Y # вернуть атрибуты и метки классов в виде двух массивов

По нажатию кнопки «Результат выбранной модели» программа выводит на экран результаты и точность прогноза модели выбранного в раскрывающемся списке классификатора. Для этого вызывается соответствующая ему функция (на примере многослойного персептрона – см. рисунок 1 и листинг 2), в которой выполняется построение модели, ее обучение и тестирование (листинг 3).

Листинг 2. Исходный код функции для обучения и тестирования модели многослойного персептрона

def MLP():
    T.delete(1.0, tk.END) # очистить текстовое поле
    X, Y = PrepareData() # разбить полученные данные 
    # Настройка модели многослойного перспетрона с тремя слоями по 100, 30 и 10 нейронов на каждом:
    mlp = MLPClassifier(hidden_layer_sizes=(100, 30, 10),
                        tol=0.001, learning_rate_init=0.001, random_state=99)
    # начальная скорость модели learning_rate_init и оптимизационный допуск tol подбираются экспериментально
    s = 'Многослойный персептрон: \n\n'
    s += GetScore(X, Y, mlp) # получить результаты обучения и тестирования и записать их в конец строки s
    T.insert(tk.END, s) # вставить в текстовое поле строку s с результатом
 
def DT(): # функция для обучения и тестирования модели решающего дерева
    T.delete(1.0, tk.END) # очистить текстовое поле
    X, Y = PrepareData() # разбить полученные данные
    # Настройка модели дерева принятия решений: энтропия в качестве критерия классификации и неограниченная глубина дерева
    dtc = DecisionTreeClassifier(criterion='entropy',
                                             max_features='auto',
                                             max_depth=None,
                                             random_state=99
                                        )
    s = 'Решающее дерево: \n\n'
    s += GetScore(X, Y, dtc)  # получить результаты обучения и тестирования и записать их в конец строки s
    T.insert(tk.END, s) # вставить в текстовое поле строку s с результатом

Листинг 3. Исходный код функции для обучения и тестирования модели model на данных X, Y

def GetScore(X, Y, model): 
    accuracy = 0 # точность распознавания
    correct = 0 # верно распознанных ответов
    total = 0 # всего ответов
    i = 0 # номер экземпляра
 
    # разбиение исходных данных X, Y на обучающую выборку (X_train, Y_train) и тестовую (X_validation, Y_validation)
    # в соотношении 80:20. random_state отличный от None нужен для случайных, но воспроизводимых наборов данных 
    X_train, X_validation, Y_train, Y_validation = train_test_split(X, Y, test_size=0.20, random_state=99)
 
    model.fit(X_train, Y_train) # обучение модели
 
    s = '' # строка с выводом результатов
 
    for row in X_validation: # для каждой строки данных в тестовом наборе
        guess = model.predict(row.reshape(1, -1)) # предсказание на строке, записанной в виде матрицы из одного столбца
        s += str(i + 1) + ') Предсказано: ' + guess[0] + '; Верно: ' + str(Y_validation[i]) + '\n' # вывод результата предсказания и верной метки
        if guess[0] == Y_validation[i]: # если эти значения совпали, то
            correct += 1 # увеличить количество верно распознанных ответов
        i += 1 
        total += 1
Рисунок 1 – Пример работы программы с многослойным персептроном в качестве выбранного классификатора

Также в программе предусмотрено построение графиков точности распознавания по каждой модели и построение диаграмм размаха (так называемых «ящиков с усами»), позволяющих оценить медианные значения точности всех используемых классификаторов (см. листинг 4 и рисунок 2). Таким образом, по результатам перекрестной проверки с разделением используемого набора данных по 10 блоков модель многослойного персептрона демонстрирует максимальную точность.

Листинг 4. Исходный код функции построения графиков всех моделей

def DrawPlot():
    models = [] # список моделей
 
    X, Y = PrepareData() # разбить полученные данные
 
    # разбиение исходных данных X, Y на обучающую выборку (X_train, Y_train) и тестовую (X_validation, Y_validation)
    # в соотношении 80:20. random_state отличный от None нужен для случайных, но воспроизводимых наборов данных 
    X_train, X_validation, Y_train, Y_validation = train_test_split(X, Y, test_size=0.20, random_state=99)
 
    # добавление всех моделей в список:
    models.append(('Многослойный персептрон', MLPClassifier(hidden_layer_sizes=(1000, 30, 10),
                    tol=0.001,
                    learning_rate_init=0.001,                    
                    random_state=99)))
    models.append(('Решающее дерево', DecisionTreeClassifier(criterion='entropy',
                                             max_features='auto',
                                             max_depth=None,
                                             random_state=99
                                        )))
    models.append(('Логистическая регрессия', LogisticRegression()))
    models.append(('Случайный лес', RandomForestClassifier(n_estimators=10, criterion='entropy', max_features='auto', max_depth=None, random_state=99)))
    models.append(('Метод k ближайших соседей', KNeighborsClassifier()))
 
    results = [] # список результатов
    names = [] # список названий моделей
    scoring = 'accuracy' # выбранная метрика - точность распознавания
    s = 'Сравнение моделей на валидации по 10 блокам: \n\n'
    for name, model in models: # цикл по всем моделям списка
        # кросс-валидация на 10 частях исходного набора данных
        kfold = KFold(n_splits=10)
        # обучение модели на на 10 частях с получением результата (оценки распознавания)
        cv_results = cross_val_score(model, X_train, Y_train, cv=kfold, scoring=scoring)
        results.append(cv_results) # добавить результаты в список результатов
        names.append(name) # добавить название модели в список названий моделей
        s += name + ': ' + str(round(cv_results.mean() * 100, 2)) + '%\n' # вывести в строку среднее по всем разбиениям в виде процентов с округлением до двух знаков после запятой
    T.insert(tk.END, s) # вставить в текстовое поле строку s с результатом
 
    plt.figure(figsize=(15, 10)) # создание фигуры с заданными размерами
    plt.title('Сравнение моделей на валидации по 10 блокам: ') # создание заголовка графика
    sns.set_style(style='whitegrid') 
    initial = sns.boxplot(data=results, palette='Set1', notch=False) # построение графиков по данным
    initial.set_xticklabels(['Многослойный персептрон', 'Решающее дерево', 'Логистическая регрессия', 'Случайный лес','Метод k ближайших соседей']) # вывод подписей
    initial.legend()
    plt.show() # показать график

Благодаря быстрому развитию науки и технологий, автоматическое распознавание объектов было реализовано за короткое время. Однако у традиционных методов есть множество недостатков, которые необходимо устранить. Из-за природы подводного мира процедура сбора и обработки данных более ограничена, чем на суше (или в воздухе), поэтому существует несколько проблем при использовании более агрессивных и открытых методов. По этой причине наиболее уместным видится сочетание традиционных методов исследования с методами машинного обучения, которые позволяют значительно снизить количество ложных срабатываний.

Рисунок 2 – Диаграммы размаха для оценки точности различных классификаторов

Таким образом, в рамках работы была решена задача бинарной классификации сигналов: отклику классификатора 1 соответствует метка «M» (mines – мина), 0 – «R» (rocks – камень, скала). Итогом стало сравнение точности различных классификаторов машинного обучения на наборе данных «Sonar, Mines and Rocks». Благодаря выполненной работе будущим исследователям будет легче проанализировать существующие методы, извлечь необходимые уроки и разработать эффективную модель для преодоления существующих недостатков.

Следует отметить, что задачи подобного класса часто решаются в аудите. Одним из примеров может служить расчет аудиторского риска по имеющимся данным финансовой отчетности фирм в различных секторах. В ходе планирования аудита изучаются существующие и исторические факторы риска. Предназначенный для решения задачи классификатор (например, модель глубокого обучения) позволяет получить соответствующий отклик: единице соответствует метка «мошенническая фирма», 0 – «не мошенническая фирма». Другой пример – промышленный аудит внутри конкретной компании. Например, в сфере медицины аудит качества доступности лекарственных средств (0 – «лекарство недоступно», 1 – «доступно») имеет дело с данными по их лабораторным исследованиям, сроку годности, доставке в больницы и т.п. Чтобы ускорить автоматизацию аудита в эпоху больших данных, для решения этой задачи могут быть использованы различные ансамблевые модели, сочетающие два и более классификаторов машинного обучения.