Разработка мультимедийных приложений с использованием библиотек OpenCV и IPP

Машинное обучение

Разбить на страницы
Показывать лекцию целиком

Введение

Презентацию к лабораторной работе Вы можете скачать здесь.

Дополнительные материалы к лабораторной работе Вы можете скачать здесь.

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

В настоящей работе рассматриваются некоторые алгоритмы обучения с учителем и кластеризации, реализованные в открытой библиотеке компьютерного зрения OpenCV [6].

1. Методические указания

1.1. Цели и задачи работы

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

Данная цель предполагает решение следующих задач:

  • Изучить основные идеи, лежащие в основе следующих алгоритмов классификации:
  • машина опорных векторов;
  • дерево решений;
  • случайный лес;
  • градиентный бустинг деревьев решений.
  • Изучить идеи метода центров тяжести (k-means) для кластеризации.
  • Рассмотреть прототипы функций и интерфейсы классов, реализующих перечисленные алгоритмы в библиотеке OpenCV.
  • Рассмотреть простые примеры использования указанного набора функций.
  • Разработать приложения для решения задач классификации и кластеризации рассмотренными методами.
  • Применить разработанное приложение для решения модельных задач и проанализировать полученные результаты.
  • 1.2. Структура работы

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

    1.3. Тестовая инфраструктура

    Вычислительные эксперименты проводились с использованием следующей инфраструктуры (табл. 10.1).

    Тестовая инфраструктура
    Операционная система Microsoft Windows 7
    Среда разработки Microsoft Visual Studio 2010
    Библиотека TBB Intel® Threading Building Blocks 3.0 for Windows, Update 3 (в составе Intel® Parallel Studio XE 2011 SP1)
    Библиотеки OpenCV Версия 2.4.4

    1.4. Требования к участнику лабораторной работы

    Для выполнения данной лабораторной работы требуется:

  • Изучение лекционного материала курса по теме "Обзор библиотек OpenCV и IPP"
  • Изучение практического материала курса по теме "Сборка и установка библиотеки OpenCV. Использование библиотеки в среде разработки Microsoft Visual Studio"
  • Изучение лекционного материала курса по теме "Введение в машинное обучение".
  • 1.5. Рекомендации по проведению занятий

    При выполнении данной лабораторной работы рекомендуется следующая последовательность действий:

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

    2.1. Задача обучения с учителем

    Одной из задач, изучаемой в машинном обучении, является задача обучения с учителем, которая заключается в как можно более точном восстановлении (определении) вида зависимости $$f^*$$ некоторой величины $$y \in \mathcal{Y}$$, от вектора $$x \in \mathcal{X}$$ по конечному набору пар (прецедентов) $$\lbrace (x^{(i)},y^{(i)}=f^*(x^{(i)})):x^{(i)} \in \mathcal{X},y \in \mathcal{Y} i=\overline{1,N}\rbrace$$ , называемому обучающей выборкой. Множество $$\mathcal{X}$$ называется пространством признаков, компоненты вектора $$x \in \mathcal{X}$$, в свою очередь, признаками, y – целевым признаком или выходом. В рамках данной лабораторной работы будет рассматриваться конечное и неупорядоченное множество $$\mathcal{Y}$$ (далее будем предполагать, что $$\mathcal{Y}$$ состоит из целых чисел, не используя никакие отношения порядка), т.е. решать задачу классификации. Также в качестве множества $$\mathcal{X}$$ будем рассматривать d-мерное вещественное пространство $$\mathbb{R}^d$$.

    Алгоритмы, решающие задачу обучения с учителем, выполняют поиск функции (модели)$$f: \mathcal{X} \rightarrow \mathcal{Y}$$ из некоторого фиксированного множества $$\mathcal{K}$$ , зависящего от конкретного алгоритма. Процесс выбора функции $$f$$ называется обучением.

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

    Модуль ML библиотеки OpenCV содержит реализации следующих алгоритмов обучения с учителем: нормальный байесов классификатор, метод k ближайших соседей, нейронная сеть, машина опорных векторов, дерево решений, различные алгоритмы бустинга деревьев решений, в том числе градиентный бустинг, случайный лес и крайне случайный лес [1]. Каждый алгоритм обладает своими достоинствами и недостатками. Различные алгоритмы имеют свои ограничения применимости, например, могут быть предназначены только для задач классификации/восстановления регрессии, могут допускать работу лишь с количественными признаками, могут не поддерживать наличие пропущенных значений, т.е. неизвестных значений некоторых признаков у какого-либо прецедента. Также, тип функции, получаемой на выходе определенного алгоритма обучения, может лучше/хуже подходить для аппроксимации искомой зависимости. Таким образом, конкретный алгоритм обучения с учителем должен выбираться, исходя из специфики конкретной рассматриваемой задачи. Данная лабораторная работа посвящена описанию использования реализаций следующих алгоритмов: машина опорных векторов, дерево решений, случайный лес и градиентный бустинг деревьев решений.

    2.2. Машина опорных векторов

    Пусть $$\mathcal{Y}=\lbrace 1,-1 \rbrace$$, т.е. рассматривается задача бинарной классификации. Идея алгоритма машины опорных векторов (Support Vector Machine, SVM) заключается в построении оптимальной поверхности $$\beta h(x)+\beta_0=0$$, представляющей собой гиперплоскость в спрямляющем пространстве $$\mathcal{H}=h(\mathcal{X})$$ (см. лекционную часть курса), разделяющей точки $$x^(i)$$ различных классов из обучающей выборки. Фактически, обучение алгоритмом опорных векторов заключается в решении оптимизационной задачи

    $$min_{\beta ,\beta_0,\xi } \frac 1 2 ||\beta||+C\sum_{i=1}^N {\xi_i},$$

    при ограничениях

    $$y^{(i)}\left(\beta h \left(x^{(i)}\right) + \beta_{0} \right) \geq 1-\zeta_{i}, \zeta_{i} \geq 0, \;\;\; i=\overline{1,N},$$

    где отображение $$h: \mathcal{X} \rightarrow \mathcal{H}$$ задает переход в спрямляющее пространство, $$C \geqslant 0$$ – параметр алгоритма обучения, регулирующий величину штрафа за то, что некоторые точки выходят за границу разделяющей полосы $$-1 \leqslant \beta h(x)+\beta_0 \leqslant 1$$. При этом отображение h может быть задано неявно с помощью, так называемого, ядра $$K(x,x')=h(x)h(x')$$ Решающая функция запишется в виде $$f(x)=sign( h(x)\beta+\beta_0)=sign(\sum_{i=1}^N \alpha_i y^{(i)}K(x^{(i)},x)+\beta_0)$$ . Данная функция зависит от некоторых точек $$x^{(i)}$$ из обучающей выборки, называемых опорными векторами.

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

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

    Для обучения машины опорных векторов служит метод train.

    bool CvSVM::train(const Mat< trainData, 
                      const Mat< responses, 
                      const Mat< varIdx=Mat(), 
                      const Mat< sampleIdx=Mat(), 
                      CvSVMParams params=CvSVMParams()); 
                      

    Параметры:

  • trainData – матрица, содержащая векторы $$x^{(i)}$$ из обучающей выборки. Матрица должна иметь тип CV_32F и размеры $$n \times d$$, таким образом, храня в i-й строке координаты вектора $$x^{(i)}$$ .
  • responses – матрица-вектор, содержащая значения целевой переменной $$y^{(i)}$$ для прецедентов обучающей выборки. Данная матрица должна иметь тип CV_32S или CV_32F и размеры $$1 \times n$$ или $$n \times 1$$.
  • varIdx – матрица-вектор, содержащая либо номера признаков (тип матрицы CV_32S), которые необходимо использовать при обучении, либо маску (тип матрицы CV_8U) размера $$1 \times d$$, где единицами отмечены используемые признаки, нулями – игнорируемые. По умолчанию (varIdx=Mat()) используются все признаки.
  • sampleIdx – матрица-вектор, имеющая такой же формат, как и varIdx, но отвечающая за прецеденты выборки, которые необходимо использовать для обучения. По умолчанию используются все имеющиеся прецеденты.
  • params – параметры алгоритма обучения.
  • Для представления параметров SVM в OpenCV используется структура CvSVMParams.

    struct CvSVMParams 
    {     CvSVMParams(); 
        CvSVMParams( int svm_type, 
                     int kernel_type, 
                     double degree, 
                     double gamma, 
                     double coef0, 
                     double Cvalue, 
                     double nu, 
                     double p, 
                     CvMat* class_weights, 
                     CvTermCriteria term_crit ); 
     
        int         svm_type; 
        int         kernel_type; 
        double      degree; 
        double      gamma; 
        double      coef0; 
     
        double      C; 
        double      nu; 
        double      p; 
        CvMat*      class_weights; 
        CvTermCriteria term_crit; 
    }; 
    

    Рассмотрим поля данной структуры.

    Поле svm_type отвечает за тип используемой машины опорных векторов. Возможные значения данной переменной описаны перечислением в классе CvSVM:

    enum  {C_SVC=100,  NU_SVC=101,  ONE_CLASS=102,  EPS_SVR=103, 
    NU_SVR=104}; 
    

    C_SVC и NU_SVC обозначают различные модификации SVM для классификации, в то время как EPS_SVR и NU_SVR представляют собой формализации для регрессии. Машина опорных векторов типа ONE_CLASS строит границу области, в которой расположены точки одного единственного класса. Описанная выше задача оптимизации соответствует типу C_SVC (формализации с использованием параметра C). SVM типа NU_SVC решает схожую, но другую задачу с параметром $$v \in [0,1]$$ - , являющимся нижней оценкой на долю опорных векторов в обучающей выборке и верхней границей доли неправильно классифицированных прецедентов обучающей выборки.

    Поле kernel_type структуры CvSVMParams служит для обозначения используемого ядра. Возможные значения данной переменной также описаны в перечислении класса CvSVM:

    enum { LINEAR=0, POLY=1, RBF=2, SIGMOID=3 }; 
    

    В библиотеке OpenCV реализована непосредственная поддержка следующих ядер:

  • линейное ядро (LINEAR): $$K(x,x')=x,x'$$ ;
  • многочлен степени d (POLY): $$K(x,x')=(\gamma_0+yxx')^d,\gamma>0$$ ;
  • радиальная функция (RBF): $$K(x,x')=e^{-\gamma||x-x'||^2},\gamma>0$$ ;
  • сигмоидальная функция (SIGMOID): $$K(x,x')=tanh(\gamma_0+yxx')$$.
  • Далее в структуре CvSVMParams идут параметры ядер: degree соответствует степени многочлена, определяющего полиномиальное ядро, gamma – параметру в полиномиальном, радиальном и сигмоидальном ядрах, coef0 – параметру в полиномиальном и сигмоидальном ядрах. При использовании ядра, не имеющего того или иного параметра, значение, хранящееся в соответствующем поле структуры, игнорируется и может быть любым.

    Поля C, nu и p соответствуют параметрам оптимизационных задач, решаемых алгоритмом обучения. Тип используемой машины опорных векторов указывает на то, какой из этих параметров "активен": C используется совместно с C_SVC, EPS_SVR и NU_SVR; nu – с NU_SVC, NU_SVR и ONE_CLASS; p – с EPS_SVR.

    Поле class_weights предназначено для хранения матрицы-вектора, содержащей веса различных классов и может использоваться с машиной опорных векторов типа C_SVC, позволяя определить различные значения параметра для точек разных классов. Как отмечалось выше, решение задачи классификации более чем на два класса сводится к серии задач бинарной классификации. В CvSVM реализован подход "каждый против каждого". Таким образом, при решении задачи разделения точек классов

    $$\min_{\beta,\beta_{0},\zeta}\frac{1}{2}\parallel \beta\parallel + class\_weights[k_{1}] \cdot C \sum_{i:y_{i}=k_{1}} \zeta_{i} + class\_weights[k_{2}] \cdot C \sum_{i:y_{i}=k_{2}} \zeta_{i}.$$

    Данный параметр может быть полезен при несбалансированной обучающей выборке, содержащей малое количество прецедентов одного класса и большое количество другого. Чем больше вес, тем больший штраф назначается за неправильную классификацию обучающего прецедента этого класса. По умолчанию (class_weights = NULL) все веса равны.

    Последним нерассмотренным полем структуры CvSVMParams является term_crit. Данная переменная содержит параметры итерационного метода, используемого для решения оптимизационной задачи на этапе обучения. Данные параметры указываются с помощью следующей структуры:

    struct CvTermCriteria 
    { 
        int    type; 
        int    max_iter; 
        double epsilon; 
    }; 
    

    Рассмотрим поля данной структуры:

  • type – вид критерия останова: по точности (CV_TERMCRIT_EPS) или по количеству итераций (CV_TERMCRIT_ITER). Значение type, равное CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, применяется в случае, когда используются оба критерия.
  • max_iter – максимальное количество итераций.
  • epsilon – пороговое значение точности.
  • Для использования обученной ранее модели в классе CvSVM используются методы

    float predict( const Mat< sample, 
                   bool returnDFVal=false ) const; 
    void predict( InputArray samples, 
                  OutputArray results ) const; 
                  

    Рассмотрим их параметры.

  • sample – матрица-вектор типа CV_32F и размера $$1 \times d$$, содержащая координаты одной точки в пространстве признаков.
  • returnDFVal – флаг, позволяющий получать расстояние со знаком от разделяющей поверхности до указанной точки. Если returnDFVal=true и решается задача бинарной классификации, то возвращаемым значением будет величина $$h(x)\beta+\beta_0$$, иначе будет возвращено предсказанное значение целевого признака.
  • samples – матрица типа CV_32F и размера $$n_1 \times d$$, построчно содержащая координаты точек в пространстве признаков, для которых необходимо сделать предсказания.
  • results – матрица-вектор, в которую будут сохранены предсказанные значения.
  • Для сохранения обученной модели используется метод

    void save( const char* filename, 
               const char* name=0 ) const;
                

    Параметры данного метода:

  • filename – путь и имя файла, в который будет сохранена модель. В OpenCV поддерживается запись в XML- и YAML-файлы.
  • name – имя, под которым будет сохранена модель. Имя модели может состоять из строчных и заглавных букв латинского алфавита, цифр, символов ‘-’ и ‘_’. В случае сохранения в YAML-формате также допустимо использование пробелов.
  • Загрузка модели из файла осуществляется методом

    void load( const char* filename, const char* name=0 ); 
    

    Рассмотрим его параметры:

  • filename – XML- или YAML-файл, из которого будет выполнена загрузка.
  • name – имя модели, которую требуется загрузить.
  • Также в классе CvSVM реализован метод, позволяющий получить количество опорных векторов:

    int get_support_vector_count() const; 
    

    и метод для получения опорного вектора с номером :

    const float* get_support_vector(int i) const; 
     

    Рассмотрим пример использования класса CvSVM для решения задачи классификации.

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // используем SVM типа C_SVC и радиальным ядром 
        CvSVMParams params; 
        params.svm_type = CvSVM::C_SVC; 
        params.kernel_type = CvSVM::RBF; 
        params.gamma = 1.0; 
        params.C = 1.0; 
     
        CvSVM svm; 
        svm.train(samples, labels, 
                  Mat(), trainSampleMask, params); 
        svm.save("model.yml", "simpleSVMModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        Mat predictions; 
        svm.predict(samples.rowRange(0, n1), predictions); 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            trainError += (labels.at<int>(i) !=  
                           (int)(predictions.at<float>(i))); 
        }     trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        predictions = Mat(); 
        svm.predict(samples.rowRange(n1, n), predictions); 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            testError += (labels.at<int>(n1 + i) !=  
                          (int)(predictions.at<float>(i))); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    } 
    

    Визуальное представление данных и построенной границы между точками разных классов приведено на рис. 10.1.

    (рис 10.1) Точки обучающей выборки и граница, построенная с помощью машины опорных векторов с радиальным ядром

    2.3. Дерево решений

    Идея, лежащая в основе алгоритма обучения дерева решений, состоит в рекурсивном разбиении пространства признаков с помощью простых правил на непересекающиеся области. Полученная в результате обучения модель представляет собой дерево, где каждому внутреннему узлу соответствует некоторая область пространства признаков и правило ее разбиения на подобласти ($$x_j \leqslant c$$, если -й признак количественный и $$x_j \in L$$ , если признак категориальный). Каждой листовой вершине также соответствует область пространства $$\mathcal{X}$$ и приписано константное значение, которому принимается равной функция $$f$$ в данной области.

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

    Для обучения дерева решений служит метод train.

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(), 
                const Mat< missingDataMask=Mat(), 
                CvDTreeParams params=CvDTreeParams() ); 
                

    Рассмотрим параметры метода.

  • trainData – матрица, содержащая векторы $$x^{(i)}$$ из обучающей выборки. Матрица должна иметь тип CV_32F и размеры $$n \times d $$ или $$d \times n $$. В отличие от метода CvSVM::train здесь возможен вариант как с построчным хранением векторов $$x^{(i)}$$ , так и с их хранением по столбцам.
  • tflag – флаг, определяющий по строкам (tflag=CV_ROW_SAMPLE) хранятся вектора выборки в матрице trainData или по столбцам (tflag=CV_COL_SAMPLE).
  • responses – матрица-вектор, содержащая значения целевой переменной $$y^{(i)}$$ для прецедентов обучающей выборки. Данная матрица должна иметь тип CV_32S или CV_32Fи размеры $$1 \times n$$ или $$n \times 1$$.
  • varIdx – матрица-вектор, содержащая либо номера признаков (тип матрицы CV_32S), которые необходимо использовать при обучении, либо маску (тип матрицы CV_8U) размера $$1 \times d$$, где единицами отмечены используемые признаки, нулями – игнорируемые. По умолчанию (varIdx=Mat()) используются все признаки.
  • sampleIdx – матрица-вектор, имеющая такой же формат, как и varIdx, но отвечающая за прецеденты выборки, которые необходимо использовать для обучения. По умолчанию используются все имеющиеся прецеденты.
  • varType – матрица-вектор, содержащая информацию о типах переменных. Деревья решений поддерживают использование как количественных (CV_VAR_ORDERED), так и категориальных (CV_VAR_CATEGORICAL) признаков. Данная матрица должна иметь тип CV_8U и размеры $$1 \times (d+1)$$ или $$(d+1) \times 1$$, где последний элемент определяет тип целевой переменной. По умолчанию (varType=Mat()) целевая переменная считается категориальной, т.е. решается задача классификации, а остальные количественными.
  • missingDataMask – матрица, содержащая маску пропущенных значений, где единицами отмечены неизвестные значения признаков, нулями – известные. В отличие от машины опорных векторов, деревья решений позволяют работать с пропущенными значениями в признаковых описаниях объектов $$x^{(i)}$$ . Матрица missingDataMask должна иметь тип CV_8U и по размеру совпадать с матрицей trainData. По умолчанию считается, что пропущенных значений нет.
  • params – параметры алгоритма обучения.
  • Для представления параметров алгоритма обучения дерева решений используется структура CvDTreeParams:

    struct CvDTreeParams 
    { 
        int   max_categories; 
        int   max_depth; 
        int   min_sample_count; 
        int   cv_folds; 
        bool  use_surrogates; 
        bool  use_1se_rule; 
        bool  truncate_pruned_tree; 
        float regression_accuracy; 
        const float* priors; 
     
        CvDTreeParams(); 
        CvDTreeParams( int max_depth, 
                       int min_sample_count, 
                       float regression_accuracy, 
                       bool use_surrogates, 
                       int max_categories, 
                       int cv_folds,                    bool use_1se_rule, 
                       bool truncate_pruned_tree, 
                       const float* priors ); 
    }; 
    

    Остановимся подробнее на описании данной структуры.

  • max_depth – ограничение на высоту обучаемого дерева.
  • min_sample_count – минимальное количество объектов обучающей выборки, которое должно быть в области пространства признаков, определяемой некоторым узлом дерева, чтобы разбиение этой области продолжилось.
  • regression_accuracy – разбиение узла регрессионного дерева прекращается, если модуль разности значения, приписанного этому узлу, и истинного значения целевого признака для всех прецедентов, попавших в данный узел, не превышает данного значения.
  • max_categories – максимальное количество возможных значений категориального признака, для которого точно вычисляется наилучшее правило разбиения. Данный параметр актуален лишь для задач классификации с числом классов больше двух, т.к. в данном случае выбор подмножества L множества возможных значений Q категориального признака происходит с помощью алгоритма с экспоненциальной от мощности Q трудоемкостью [2]. В данном случае для снижения времени работы алгоритма производится предварительная кластеризация множества Q так, чтобы количество кластеров не превышало max_categories. В случае если решается задача регрессии или бинарной классификации, то точное отыскания оптимального разбиения по данному признаку выполняется за линейное время от |Q| [2] и не требует предварительной кластеризации. Следует отметить, что максимальная поддерживаемая в OpenCV мощность равна 64.
  • use_surrogates – флаг, определяющий необходимость построения суррогатных разбиений. Каждому узлу дерева решений может быть приписано не только одно (основное) правило разбиения, а также несколько второстепенных (суррогатных) правил. В качестве суррогатных выбираются разбиения наиболее похожие на основное [1]. Построение суррогатных разбиений (use_surrogates=true) ведет к росту времени обучения модели, однако, может повысить качество оценки значимости переменных с помощью построенного дерева и улучшить качество предсказания для объектов с пропущенными значениями.
  • cv_folds – количество частей, на которые разделяется обучающая выборка, для выполнения перекрестного контроля при выполнении процедуры отсечений (pruning) [1]. Типичная стратегия построения дерева решений заключается в обучении большого дерева, к которому затем применяются отсечения для предотвращения переобучения. Если cv_folds=0, то отсечения не выполняются.
  • use_1se_rule – использование более строгого критерия отсечения. Если use_1se_rule=true, то после процедуры отсечения получается меньшее дерево.
  • truncate_pruned_tree – указывает, следует ли физически удалять из памяти отсеченные узлы дерева.
  • priors – для задачи классификации, задает априорные вероятности появления точек различных классов. Данный параметр можно использовать, например, в случае несбалансированной обучающей выборки, т.е. неравного количества прецедентов разных классов.
  • Предсказания с помощью предварительно обученной модели выполняются с помощью метода predict:

    CvDTreeNode* predict( const Mat< sample, 
                          const Mat< missingDataMask=Mat(), 
                          bool preprocessedInput=false ) const; 
                          

    Рассмотрим параметры данного метода:

  • sample – матрица-вектор типа CV_32F и размера $$1 \times d$$, содержащая координаты одной точки в пространстве признаков.
  • missingDataMask – матрица-вектор типа CV_8U и размера $$1 \times d$$ , представляющая собой маску пропущенных значений в sample.
  • preprocessedInput – определяет порядок работы с категориальными признаками в ходе предсказания. В матрице признаковых описаний объектов обучающей выборки, используемой методом CvDTree::train, значения категориальных признаков могут быть обозначены произвольными целыми числами. Например, множеством различных значений признака может быть {3,7,9}. Однако внутренним представлением обученной модели является множество, состоящее из чисел от 0 до $$|Q|-1$$. Таким образом, на этапе предсказания требуется выполнять пересчет значений категориальных признаков, что может негативно сказаться на скорости выполнения данной операции. Значение preprocessedInput=true говорит, что пересчет значений уже выполнен. Данный параметр актуален лишь при наличии категориальных признаков.
  • Следует отметить, что метод CvDTree::predict возвращает указатель на структуру, описывающую узел дерева. Само предсказанное моделью значение целевого признака хранится в поле value данной структуры.

    Сохранение модели дерева решений в файл и загрузка из него выполняется с помощью методов save и load, использование которых полностью совпадает с аналогичными методами класса CvSVM, описанными выше.

    Следует отметить, что класс CvDTree также содержит дополнительный функционал, например, для определения значимости различных переменных для восстановления зависимости от , что, однако, выходит за рамки данной лабораторной работы и может быть предоставлено для самостоятельного изучения [7].

    Приведем пример использования класса CvDTree для решения задачи бинарной классификации аналогичной, решаемой в примере для CvSVM.

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной)     Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать дерево решений высоты не больше 10, 
        // после построения которого выполним отсечения 
        // с помощью пятикратного перекресного контроля 
        CvDTreeParams params; 
        params.max_depth = 10; 
        params.min_sample_count = 1; 
        params.cv_folds = 5; 
     
        CvDTree dtree; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        dtree.train(samples, CV_ROW_SAMPLE, 
                    labels, varIdx, 
                    trainSampleMask, varTypes, 
                    Mat(), params); 
        dtree.save("model-dtree.yml", "simpleDTreeModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(dtree.predict(samples.row(i))->value); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f;     for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(dtree.predict(samples.row(n1 + i))->value); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    } 
    

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

    (рис 10.2) Точки обучающей выборки и разбиение пространства признаков деревом решений

    2.4. Случайный лес

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

    Алгоритм случайного леса реализован в библиотеке OpenCV в виде класса CvRTrees. Рассмотрим принципы работы с данным классом. Для обучения случайного леса предназначен метод train:

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(), 
                const Mat< missingDataMask=Mat(), 
                CvRTParams params=CvRTParams() );

    Назначение и формат параметров данного метода целиком совпадает с методом CvDTree::train, реализующим обучение одиночного дерева решений, за исключением параметров алгоритма обучения. Параметры случайного леса представляются в виде структуры CvRTParams:

    struct CvRTParams : public CvDTreeParams 
    { 
        bool calc_var_importance; 
        int nactive_vars; 
        CvTermCriteria term_crit; 
     
        CvRTParams(); 
        CvRTParams( int max_depth, 
                    int min_sample_count, 
                    float regression_accuracy, 
                    bool use_surrogates, 
                    int max_categories, 
                    const float* priors, 
                    bool calc_var_importance, 
                    int nactive_vars, 
                    int max_num_of_trees_in_the_forest, 
                    float forest_accuracy, 
                    int termcrit_type ); 
    }; 

    Так как алгоритм случайного леса строит деревья решений, большинство параметров определяют настройки алгоритма обучения одиночных деревьев (см. описание структуры CvDTreeParams). Следует отметить, что в рамках модели случайного леса обучаются большие (оригинальный подход [4] не подразумевал ограничение высоты дерева как критерий останова его построения) деревья решений без последующего применения процедуры отсечения. Теперь рассмотрим параметры, имеющие отношение непосредственно к модели случайного леса:

  • nactive_vars – количество признаков, выбираемых случайным образом для обучения каждого дерева решений. Если nactive_vars=0, то при обучении дерева будет использоваться целая часть снизу от $$\sqrt d$$ признаков, если же указано значение nactive_vars>0, то min(nactive_vars, d ) признаков.
  • term_crit – критерий прекращения добавления деревьев в ансамбль (лес). Так как при обучении каждого дерева используются не все прецеденты обучающей выборки, то неиспользуемая часть выборки (out-of-bag samples) может быть использована в качестве тестовой для оценки качества модели (out-of-bag error, oob error). В связи с этим появляется возможность прекратить построение новых деревьев, при достижении достаточно малой oob ошибки. Структура CvTermCriteria была рассмотрена выше (см. описание CvSVMParams), здесь отметим лишь, что значение term_crit.max_iter задает максимальное количество обучаемых деревьев, term_crit.epsilon – максимальную допустимую oob ошибку, term_crit.type – тип используемого критерия останова.
  • calc_var_importance – флаг, определяющий необходимость вычисления значимости переменных в ходе обучения модели случайного леса.
  • Для осуществления предсказаний в классе CvRTrees предназначен метод predict:

    float predict( const Mat< sample, 
                   const Mat< missing=Mat() ) const;
                   

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

    Методы save и load, осуществляющие сохранение и загрузку модели соответственно, применяются аналогично данным методам классов CvSVM и CvDTree.

    Приведем пример использования класса CvRTrees для решения задачи бинарной классификации и иллюстрацию порождаемого обученной моделью разбиения пространства признаков (см. рис. 10.3).

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать случайный лес из 250 деревьев 
        // высоты не больше 10 
        CvRTParams params; 
        params.max_depth = 10; 
        params.min_sample_count = 1; 
        params.calc_var_importance = false; 
        params.term_crit.type = CV_TERMCRIT_ITER;     params.term_crit.max_iter = 250; 
     
        CvRTrees rf; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        rf.train(samples, CV_ROW_SAMPLE, 
                 labels, varIdx, 
                 trainSampleMask, varTypes, 
                 Mat(), params); 
        rf.save("model-rf.yml", "simpleRTreesModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(rf.predict(samples.row(i))); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(rf.predict(samples.row(n1 + i))); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    }  
    
    (рис 10.3) Точки обучающей выборки и разбиение пространства признаков случайным лесом

    2.5. Градиентный бустинг деревьев решений

    Градиентный бустинг, как и любой бустинг алгоритм, последовательно строит базовые модели так, что каждая следующая улучшает качество всего ансамбля. Градиентный бустинг деревьев решений строит модель в виде суммы деревьев $$f(x)=h_0+v \cdot \sum^M_{j=1} h_j(x)$$ , где $$h_0$$ – некоторая константная модель (начальное приближение), $$v \in (0,1]$$ – параметр, регулирующий скорость обучения и влияние отдельных деревьев на всю модель, $$h_j(x)$$ – регрессионные деревья решений. Новые слагаемые-деревья добавляются в сумму путем жадной минимизации эмпирического риска, заданного некоторой функцией потерь $$L(y,y')=L(y,f(x))$$ . Данный метод без серьезной модификации может применяться для любой дифференцируемой функции потерь.

    Метод градиентного бустинга деревьев решений в библиотеке OpenCV реализован в виде класса CvGBTrees. Для запуска обучения модели реализован метод train:

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(),             const Mat< missingDataMask=Mat(), 
                CvGBTreesParams params=CvGBTreesParams(), 
                bool update=false );
                

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

    struct CvGBTreesParams : public CvDTreeParams 
    { 
        int weak_count; 
        int loss_function_type; 
        float subsample_portion; 
        float shrinkage; 
     
        CvGBTreesParams(); 
        CvGBTreesParams( int loss_function_type, 
                         int weak_count, 
                         float shrinkage, 
                         float subsample_portion, 
                         int max_depth, 
                         bool use_surrogates ); 
    }; 
    

    Рассмотрим значения ее полей:

  • loss_function_type – тип используемой функции потерь. Список реализованных штрафных функций представлен перечислением в классе CvGBTrees:
    enum  {SQUARED_LOSS=0,  ABSOLUTE_LOSS,  HUBER_LOSS=3, 
    DEVIANCE_LOSS}; 
    
    где для задачи восстановления регрессии предназначены функции: $$L(y,y')=(y-y')^2$$ (SQUARED_LOSS), $$L(y,y')=|y-y'|$$ (ABSOLUTE_LOSS), $$L(y,y')=\frac 1 2 (y-y')^21(|y-y'|\leqslant \delta)+\delta(|y-y'|-\frac {\delta} 2)1(|y-y'|> \delta)$$ (HUBER_LOSS). Функция $$L(y,y'_1,y'_2,...,y'_K)=-\sum_{k=1}^K1(y=k)ln(\frac {exp(y'_k)}{\sum_{k=1}^K exp(y'_i)})$$ (DEVIANCE_LOSS) предназначена для решения задач классификации на K классов. При использовании данного штрафа строится K моделей в виде суммы деревьев, для оценки вероятностей принадлежности объекта каждому из классов.
  • weak_count – количество обучаемых деревьев. В случае loss_function_type=DEVIANCE_LOSS фактическое количество деревьев, которое будет построено в K раз больше.
  • shrinkage – скорость обучения, уменьшение которой позволяет бороться с переобучением модели.
  • subsample_portion – доля выборки, используемая для обучения каждого дерева. Для построения дерева решений случайным образом (без возвращения) выбирается subsample_portion прецедентов обучающей выборки. Случайные подвыбороки могут улучшить качество модели, однако, неприменимы в случае небольшого числа имеющихся прецедентов.
  • Предсказания с помощью модели градиентного бустинга деревьев решений выполняет метод predict:

    float predict( const Mat sample, 
                   const Mat missing=Mat(), 
                   const Range slice=Range::all(), 
                   int k=-1 ) const; 
                   

    Рассмотрим параметры данного метода:

  • sample – матрица-вектор признакового описания объекта.
  • missing – матрица-вектор, содержащая маску пропущенных значений.
  • slice – используемые для предсказания деревья (по умолчанию используются все). Может применяться для подбора оптимального количества деревьев без необходимости производить обучение заново.
  • k – для loss_function_type=DEVIANCE_LOSS номер функции, значение которой следует вернуть. Если k=-1, возвращается предсказанное значение целевой переменной.
  • Сохранение и загрузка модели выполняется так же, как и в рассмотренных ранее алгоритмах.

    Ниже приведен пример использования класса CvGBTrees для решения задачи бинарной классификации и иллюстрация полученного классификатора (см. рис. 10.4)

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv;  
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать модель градиентного бустинга 
        // деревьев решений из 250 деревьев высоты 3 и 
        // скоростью обучения 0.5,без использования подвыборок. 
        // Т.к. решается задача классификации используем 
        // функцию потерь DEVIANCE_LOSS 
        CvGBTreesParams params;     params.max_depth = 3; 
        params.min_sample_count = 1; 
        params.weak_count = 250; 
        params.shrinkage = 0.5f; 
        params.subsample_portion = 1.0f; 
        params.loss_function_type = CvGBTrees::DEVIANCE_LOSS; 
     
        CvGBTrees gbt; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        gbt.train(samples, CV_ROW_SAMPLE, 
                  labels, varIdx, 
                  trainSampleMask, varTypes, 
                  Mat(), params); 
        gbt.save("model-gbt.yml", "simpleGBTreesModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(gbt.predict(samples.row(i))); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(gbt.predict(samples.row(n1 + i))); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    }  
    
    (рис 10.4) Точки обучающей выборки и разбиение пространства признаков с помощью модели градиентного бустинга

    3. Обзор возможностей библиотеки OpenCV для решения задач обучения без учителя

    3.1. Задача кластеризации

    Задача кластеризации заключается в разбиении выборки $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$ на непересекающиеся подмножества таким образом, чтобы схожие точки (обычно близкие в некоторой метрике) попали в одно подмножество (кластер), а точки из разных кластеров сильно друг от друга отличались (были далеки). Для решения данной задачи в библиотеки OpenCV реализован метод центров тяжести (k-means) и EM-алгоритм. В данной лабораторной работе рассматривается использование программной реализации метод центров тяжести, как одного из наиболее популярных алгоритмов кластеризации на настоящий момент.

    3.2. Метод центров тяжести (k-means)

    Метод центров тяжести разбивает выборку на заданное количество кластеров K путем выбора их центров. Поиск центров кластеров производится из соображений минимизации суммарного расстояния от каждой точки до ближайшего центра с помощью метода локальной оптимизации. Так как алгоритм не гарантирует достижения глобального минимума, ключевую роль играет начальная инициализация центров кластеров. Распространенным методом является случайный (с равной вероятностью) выбор K точек из имеющегося множества $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$. Однако алгоритм центров тяжести, запущенный на таких начальных данных может выдать в результате локальный минимум сколь угодно хуже глобального. Альтернативой является метод k-means++ [5], предложенный Д. Артуром и С. Вассильвицким, основанный на последовательном выборе K точек из выборки случайным образом, но с вероятностью пропорциональной квадрату расстояния от точки до ближайшего уже выбранного центра. В данном случае, математическое ожидание отношения найденного минимума к глобальному является величиной $$O(log K)$$.

    Метод центров тяжести с евклидовой метрикой реализован в библиотеке OpenCV в виде функции kmeans модуля core:

    double kmeans( InputArray data, 
                   int K, 
                   InputOutputArray bestLabels, 
                   TermCriteria criteria, 
                   int attempts, 
                   int flags, 
                   OutputArray centers=noArray() ); 
                   

    Рассмотрим параметры данной функции:

  • data – матрица типа CV_32F, в которой каждой строке соответствует точка выборки.
  • K – количество кластеров, получаемых на выходе алгоритма.
  • bestLabels – матрица размера $$1 \times N$$, в которую для каждой точки $$x^{(i)}$$ будет сохранен номер кластера, в который попала данная точка.
  • criteria – критерий останова итерационного метода оптимизации. Алгоритм k-means может закончить работу либо после совершения заданного количества итераций, либо если каждый центр кластера сдвинется на величину меньше criteria.epsilon.
  • attempts – количество запусков алгоритма k-means с различными начальными центрами кластеров. В качестве конечного разбиения будет возвращен наилучший из полученных результатов.
  • flags – метод генерации центров кластеров перед запуском алгоритма k-means. Допустимые значения: KMEANS_RANDOM_CENTERS – случайный равновероятный выбор центров, KMEANS_PP_CENTERS – случайный выбор методом k- means++, KMEANS_USE_INITIAL_LABELS – для первого запуска используется заданное с помощью параметра bestLabels разбиение.
  • centers – матрица с наилучшими найденными центрами кластеров. Каждая строка соответствует координатам центра одно кластера.
  • Данная функция возвращает сумму квадратов расстояний от каждой точки до ближайшего к ней центра.

    Рассмотрим пример использования функции kmeans для кластеризации точек на плоскости. Результат полученной кластеризации приведен на рис. 10.5.

    #include <opencv2/core/core.hpp> 
     
    using namespace cv; 
     
    Mat generateDataset() 
    { 
        int n = 300; 
        Mat data(3 * n, 2, CV_32F); 
         
        randn(data(Range(0, n), Range(0, 1)), 0.0, 0.05); 
        randn(data(Range(0, n), Range(1, 2)), 0.5, 0.25); 
     
        randn(data(Range(n, 2 * n), Range(0, 1)), 0.7, 0.25); 
        randn(data(Range(n, 2 * n), Range(1, 2)), 0.0, 0.05); 
     
        randn(data(Range(2 * n, 3 * n), Range(0, 1)), 
              0.7, 0.15); 
        randn(data(Range(2 * n, 3 * n), Range(1, 2)), 
              0.8, 0.15); 
         
        return data; 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        Mat samples = generateDataset(); 
     
        Mat labels; 
        Mat centers; 
        kmeans(samples, 
            3, 
            labels, 
            TermCriteria(TermCriteria::COUNT + 
                         TermCriteria::EPS, 10000, 0.001),         10, 
            KMEANS_PP_CENTERS, 
            centers); 
         
        return 0; 
    } 
    
    (рис 10.5) Кластеризация методом центров тяжести (k-means)

    4. Программная реализация

    4.1. Разработка приложения для решения задач классификации

    4.1.1. Требования к приложению

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

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

    Приложение будет состоять из набора модулей (cvsvm.cpp/h, cvdtree.cpp/h, cvrtrees.cpp/h, cvgbtrees.cpp/h), каждый из которых предназначен для работы (запрос у пользователя параметров, обучение и предсказание) рассматриваемых алгоритмов обучения, модуля вычисления ошибки классификации (errorMetrics.cpp/h), модуля визуализации данных (drawingFunctions.cpp/h) и основного модуля (main.cpp), содержащего общую логику работы программы. Основной модуль и модуль визуализации предоставляются в реализованном виде, следовательно, необходимо реализовать непосредственно работу с различными алгоритмами обучения с учителем. Далее рассматриваются интерфейсы готовых функций, а также функционал, который предлагается реализовать самостоятельно.

    Модуль визуализации данных содержит функцию рисования точек из двумерного пространства признаков drawPoints:

    void drawPoints(Mat  img, 
                    const Mat  data, 
                    const Mat  classes, 
                    const Mat  ranges, 
                    std::map<int, Scalar>  classColors, 
                    int drawingMode = 0); 
                    

    В качестве аргументов данная функция принимает:

  • img – изображение (матрица типа CV_8UC3).
  • data – матрица типа CV_32F, содержащая признаковые описания объектов выборки.
  • classes – матрица типа CV_32S с номерами классов объектов выборки.
  • ranges – матрица типа CV_64F, содержащая в первом столбце визуализируемый диапазон первого признака (отображаемого по оси X), а вот втором – второго.
  • classColors – соответствие номеров классов и цветов точек. Если какому-либо классу не будет поставлен в соответствие цвет, то он будет сгенерирован случайным образом.
  • drawingMode – тип отображаемых точек. Для рисования закрашенных точек используется drawingMode=0, для "выколотых" точек drawingMode=1, для рисования черных точек drawingMode=2. Использование данного параметра позволяет различным образом отображать точки обучающей и тестовой выборок, а также (при использовании SVM) опорных векторов.
  • Функция drawPartition позволяет отображать разбиение пространства признаков на области, соответствующие разным классам, путем вычисления предсказаний на равномерной сетке:

    void drawPartition(Mat  img, 
                       map<int, Scalar>  classColors, 
                       const Mat  dataRanges, 
                       const Size stepsNum, 
                       const CvStatModel  model, 
                       getPredictedClassLabel * predictLabel); 
                       

    Параметры функции:

  • img – изображение (матрица типа CV_8UC3).
  • classColors – соответствие номеров классов и цветов точек. Если какому-либо классу не будет поставлен в соответствие цвет, то он будет сгенерирован случайным образом.
  • dataRanges – матрица типа CV_64F, содержащая в первом столбце визуализируемый диапазон первого признака (отображаемого по оси X ), а вот втором – второго.
  • stepsNum – количество узлов сетки в каждом из направлений.
  • model – обученная модель для выполнения предсказаний в узлах сетки.
  • predictLabel – указатель на функцию вычисления пресказаний с помощью model.
  • Функция getRanges предназначена для поиска матрицы минимальных и максимальных значений каждого признака в выборке data:

     
    Mat getRanges(const Mat  data); 
    

    Функция readDatasetFromFile позволяет считать данные (обучающую и тестовую выборки) из XML- или YAML-файла.

     
    void readDatasetFromFile(Mat  featuresTrain, 
                             Mat  classesTrain, 
                             Mat  featuresTest, 
                             Mat  classesTest); 
                             

    Файл должен содержать матрицы признаков объектов обучающей ("featuresTrain") и тестовой ("featuresTest") выборок и соответствующих им классов ("classesTrain" и "classesTest") в формате OpenCV. Матрицы classesTrain и classesTest должны иметь тип CV_32S, featuresTrain и featuresTest – CV_32F.

    Основная логика программы сосредоточена в модуле main.cpp и выглядит следующим образом:

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

    void trainSVM(const Mat  trainSamples, 
                  const Mat  trainClasses, 
                  const CvSVMParams  params, 
                  CvSVM  svm); 
    void trainDTree(const Mat  trainSamples, 
                    const Mat  trainClasses, 
                    const CvDTreeParams  params, 
                    CvDTree  dtree); 
    void trainRTrees(const Mat  trainSamples, 
                     const Mat  trainClasses, 
                     const CvRTParams  params, 
                     CvRTrees  rtrees); 
    void trainGBTrees(const Mat  trainSamples, 
                      const Mat  trainClasses, 
                      const CvGBTreesParams  params, 
                      CvGBTrees  gbtrees); 
                      

    Данные функции находятся в файлах cvsvm.cpp, cvdtree.cpp, cvrtrees.cpp, cvgbtrees.cpp соответственно. В теле каждой функции требуется вызвать метод переданного в нее объекта для обучения модели на данных trainSamples (матрица признаковых описаний объектов) и trainClasses (матрица номеров классов) с параметрами params. Также требуется реализация функций для предсказания:

    int getSVMPrediction(const Mat  sample, 
                         const CvStatModel  model); 
    int getDTreePrediction(const Mat  sample, 
                           const CvStatModel  model); 
    int getRTreesPrediction(const Mat  sample, 
                            const CvStatModel  model); 
    int getGBTreesPrediction(const Mat  sample, 
                             const CvStatModel  model); 
                             

    Функции, принимают признаковое описание объекта sample и обученную модель model, возвращая номер предсказанного класса. Интерфейс данных функций унифицирован для более простого подсчета ошибок на обучающей и тестовой выборках. Функции принимают объекты базового типа CvStatModel, однако, фактичеси это должны быть объекты соответствующих классов: для getSVMPrediction объект класса CvSVM, для getDTreePrediction – CvDTree и т.д.

    Также предлагается реализовать функцию getSupportVectors (файл cvsvm.cpp), принимающую обученную машину опорных векторов svm и возвращающую матрицу, где каждая строка соответствует опорному вектору:

    Mat getSupportVectors(const CvSVM  svm); 
    

    Функции запроса параметров каждого из рассматриваемых алгоритмов обучения предоставляются в готовом виде и рекомендуются для самостоятельного разбора.

    Также в рамках лабораторной работы предлагается реализовать функцию вычисления ошибки классификации (долю неправильно классифицированных объектов выборки) getClassificationError:

    float getClassificationError(const Mat  samples, 
          const Mat  classes, 
          const CvStatModel  model, 
          int (*predict) (const Mat  sample, 
                          const CvStatModel  model)); 
                          

    Параметры функции:

  • samples – признаковые описания объектов выборки.
  • classes – номера классов (истинные значения целевого признака) для объектов выборки.
  • model – обученная модель.
  • predict – указатель на функцию, принимающую один объект выборки и модель и возвращающую номер предсказанного класса.
  • Данная функция должна вычислять доля неправильно классифицированных объектов выборки.

    После того, как описанные функции будут реализованы, предлагается применить рассмотренные алгоритмы классификации к наборам данных из файлов dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml, datasetMulticlass.yml и datasetHighDim.yml. А также проанализировать, на каких данных лучше/хуже работает тот или иной подход и какое влияние на конечную модель оказывают параметры алгоритма обучения.

    4.2. Разработка приложения для решения задач кластеризации

    4.2.1. Требования к приложению

    Также в рамках данной лабораторной работы предлагается разработать приложение для кластеризации точек методом центров тяжести. К приложению предъявляются следующие требования:

  • Загрузка данных из файла, имя которого указывается в качестве параметра командной строки.
  • Выполнение кластеризации на заданное (в виде аргумента командной строки) число кластеров.
  • Визуализация кластеризации в двумерном пространстве.
  • 4.2.2. Структура приложения

    Приложение будет состоять из двух модулей: основной (main.cpp) и модуль визуализации (drawingFunctions.cpp/h). Функции визуализации предоставляются в готовом виде и аналогичны описанным в разделе 4.1.2.

    В основном модуле должна выполняться следующая последовательность действий:

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

    После того, как код основного модуля будет дописан, предлагается запустить программу на предоставленных наборах данных (dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml) и проанализировать полученные результаты.

    Контрольные вопросы

  • Для чего в алгоритме опорных векторов применяются ядра?
  • Какой эффект можно наблюдать при использовании машины опорных векторов с радиальным ядром с большим значением параметра $$\lambda$$ ?
  • Каким образом дерево решений разбивает пространство признаков?
  • Для чего к деревьям решений применяется процедура отсечений?
  • Применяются ли отсечения к деревьям решений в составе случайного леса?
  • Происходит ли переобучение при увеличении количества деревьев в случайном лесе?
  • Происходит ли переобучение при увеличении количества деревьев в модели градиентного бустинга?
  • В чем заключается идея метода центров тяжести?
  • 6. Дополнительные задания

  • Реализуйте возможность сохранения и загрузки обученной модели в приложении для решения задач классификации.
  • Реализуйте функцию вычисления матрицы ошибок классификации $$\varepsilon$$, где элемент $$\varepsilon_{i,j}$$ равен количеству прецедентов выборки принадлежащих к классу j и отнесенных алгоритмом классификации к классу i.
  • Реализуйте метод перекрестного контроля для подбора параметров алгоритмов обучения
  • Страницы:

    Введение

    Презентацию к лабораторной работе Вы можете скачать здесь.

    Дополнительные материалы к лабораторной работе Вы можете скачать здесь.

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

    В настоящей работе рассматриваются некоторые алгоритмы обучения с учителем и кластеризации, реализованные в открытой библиотеке компьютерного зрения OpenCV [6].

    1. Методические указания

    1.1. Цели и задачи работы

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

    Данная цель предполагает решение следующих задач:

  • Изучить основные идеи, лежащие в основе следующих алгоритмов классификации:
  • машина опорных векторов;
  • дерево решений;
  • случайный лес;
  • градиентный бустинг деревьев решений.
  • Изучить идеи метода центров тяжести (k-means) для кластеризации.
  • Рассмотреть прототипы функций и интерфейсы классов, реализующих перечисленные алгоритмы в библиотеке OpenCV.
  • Рассмотреть простые примеры использования указанного набора функций.
  • Разработать приложения для решения задач классификации и кластеризации рассмотренными методами.
  • Применить разработанное приложение для решения модельных задач и проанализировать полученные результаты.
  • 1.2. Структура работы

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

    1.3. Тестовая инфраструктура

    Вычислительные эксперименты проводились с использованием следующей инфраструктуры (табл. 10.1).

    Тестовая инфраструктура
    Операционная система Microsoft Windows 7
    Среда разработки Microsoft Visual Studio 2010
    Библиотека TBB Intel® Threading Building Blocks 3.0 for Windows, Update 3 (в составе Intel® Parallel Studio XE 2011 SP1)
    Библиотеки OpenCV Версия 2.4.4

    1.4. Требования к участнику лабораторной работы

    Для выполнения данной лабораторной работы требуется:

  • Изучение лекционного материала курса по теме "Обзор библиотек OpenCV и IPP"
  • Изучение практического материала курса по теме "Сборка и установка библиотеки OpenCV. Использование библиотеки в среде разработки Microsoft Visual Studio"
  • Изучение лекционного материала курса по теме "Введение в машинное обучение".
  • 1.5. Рекомендации по проведению занятий

    При выполнении данной лабораторной работы рекомендуется следующая последовательность действий:

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

    2.1. Задача обучения с учителем

    Одной из задач, изучаемой в машинном обучении, является задача обучения с учителем, которая заключается в как можно более точном восстановлении (определении) вида зависимости $$f^*$$ некоторой величины $$y \in \mathcal{Y}$$, от вектора $$x \in \mathcal{X}$$ по конечному набору пар (прецедентов) $$\lbrace (x^{(i)},y^{(i)}=f^*(x^{(i)})):x^{(i)} \in \mathcal{X},y \in \mathcal{Y} i=\overline{1,N}\rbrace$$ , называемому обучающей выборкой. Множество $$\mathcal{X}$$ называется пространством признаков, компоненты вектора $$x \in \mathcal{X}$$, в свою очередь, признаками, y – целевым признаком или выходом. В рамках данной лабораторной работы будет рассматриваться конечное и неупорядоченное множество $$\mathcal{Y}$$ (далее будем предполагать, что $$\mathcal{Y}$$ состоит из целых чисел, не используя никакие отношения порядка), т.е. решать задачу классификации. Также в качестве множества $$\mathcal{X}$$ будем рассматривать d-мерное вещественное пространство $$\mathbb{R}^d$$.

    Алгоритмы, решающие задачу обучения с учителем, выполняют поиск функции (модели)$$f: \mathcal{X} \rightarrow \mathcal{Y}$$ из некоторого фиксированного множества $$\mathcal{K}$$ , зависящего от конкретного алгоритма. Процесс выбора функции $$f$$ называется обучением.

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

    Модуль ML библиотеки OpenCV содержит реализации следующих алгоритмов обучения с учителем: нормальный байесов классификатор, метод k ближайших соседей, нейронная сеть, машина опорных векторов, дерево решений, различные алгоритмы бустинга деревьев решений, в том числе градиентный бустинг, случайный лес и крайне случайный лес [1]. Каждый алгоритм обладает своими достоинствами и недостатками. Различные алгоритмы имеют свои ограничения применимости, например, могут быть предназначены только для задач классификации/восстановления регрессии, могут допускать работу лишь с количественными признаками, могут не поддерживать наличие пропущенных значений, т.е. неизвестных значений некоторых признаков у какого-либо прецедента. Также, тип функции, получаемой на выходе определенного алгоритма обучения, может лучше/хуже подходить для аппроксимации искомой зависимости. Таким образом, конкретный алгоритм обучения с учителем должен выбираться, исходя из специфики конкретной рассматриваемой задачи. Данная лабораторная работа посвящена описанию использования реализаций следующих алгоритмов: машина опорных векторов, дерево решений, случайный лес и градиентный бустинг деревьев решений.

    2.2. Машина опорных векторов

    Пусть $$\mathcal{Y}=\lbrace 1,-1 \rbrace$$, т.е. рассматривается задача бинарной классификации. Идея алгоритма машины опорных векторов (Support Vector Machine, SVM) заключается в построении оптимальной поверхности $$\beta h(x)+\beta_0=0$$, представляющей собой гиперплоскость в спрямляющем пространстве $$\mathcal{H}=h(\mathcal{X})$$ (см. лекционную часть курса), разделяющей точки $$x^(i)$$ различных классов из обучающей выборки. Фактически, обучение алгоритмом опорных векторов заключается в решении оптимизационной задачи

    $$min_{\beta ,\beta_0,\xi } \frac 1 2 ||\beta||+C\sum_{i=1}^N {\xi_i},$$

    при ограничениях

    $$y^{(i)}\left(\beta h \left(x^{(i)}\right) + \beta_{0} \right) \geq 1-\zeta_{i}, \zeta_{i} \geq 0, \;\;\; i=\overline{1,N},$$

    где отображение $$h: \mathcal{X} \rightarrow \mathcal{H}$$ задает переход в спрямляющее пространство, $$C \geqslant 0$$ – параметр алгоритма обучения, регулирующий величину штрафа за то, что некоторые точки выходят за границу разделяющей полосы $$-1 \leqslant \beta h(x)+\beta_0 \leqslant 1$$. При этом отображение h может быть задано неявно с помощью, так называемого, ядра $$K(x,x')=h(x)h(x')$$ Решающая функция запишется в виде $$f(x)=sign( h(x)\beta+\beta_0)=sign(\sum_{i=1}^N \alpha_i y^{(i)}K(x^{(i)},x)+\beta_0)$$ . Данная функция зависит от некоторых точек $$x^{(i)}$$ из обучающей выборки, называемых опорными векторами.

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

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

    Для обучения машины опорных векторов служит метод train.

    bool CvSVM::train(const Mat< trainData, 
                      const Mat< responses, 
                      const Mat< varIdx=Mat(), 
                      const Mat< sampleIdx=Mat(), 
                      CvSVMParams params=CvSVMParams()); 
                      

    Параметры:

  • trainData – матрица, содержащая векторы $$x^{(i)}$$ из обучающей выборки. Матрица должна иметь тип CV_32F и размеры $$n \times d$$, таким образом, храня в i-й строке координаты вектора $$x^{(i)}$$ .
  • responses – матрица-вектор, содержащая значения целевой переменной $$y^{(i)}$$ для прецедентов обучающей выборки. Данная матрица должна иметь тип CV_32S или CV_32F и размеры $$1 \times n$$ или $$n \times 1$$.
  • varIdx – матрица-вектор, содержащая либо номера признаков (тип матрицы CV_32S), которые необходимо использовать при обучении, либо маску (тип матрицы CV_8U) размера $$1 \times d$$, где единицами отмечены используемые признаки, нулями – игнорируемые. По умолчанию (varIdx=Mat()) используются все признаки.
  • sampleIdx – матрица-вектор, имеющая такой же формат, как и varIdx, но отвечающая за прецеденты выборки, которые необходимо использовать для обучения. По умолчанию используются все имеющиеся прецеденты.
  • params – параметры алгоритма обучения.
  • Для представления параметров SVM в OpenCV используется структура CvSVMParams.

    struct CvSVMParams 
    {     CvSVMParams(); 
        CvSVMParams( int svm_type, 
                     int kernel_type, 
                     double degree, 
                     double gamma, 
                     double coef0, 
                     double Cvalue, 
                     double nu, 
                     double p, 
                     CvMat* class_weights, 
                     CvTermCriteria term_crit ); 
     
        int         svm_type; 
        int         kernel_type; 
        double      degree; 
        double      gamma; 
        double      coef0; 
     
        double      C; 
        double      nu; 
        double      p; 
        CvMat*      class_weights; 
        CvTermCriteria term_crit; 
    }; 
    

    Рассмотрим поля данной структуры.

    Поле svm_type отвечает за тип используемой машины опорных векторов. Возможные значения данной переменной описаны перечислением в классе CvSVM:

    enum  {C_SVC=100,  NU_SVC=101,  ONE_CLASS=102,  EPS_SVR=103, 
    NU_SVR=104}; 
    

    C_SVC и NU_SVC обозначают различные модификации SVM для классификации, в то время как EPS_SVR и NU_SVR представляют собой формализации для регрессии. Машина опорных векторов типа ONE_CLASS строит границу области, в которой расположены точки одного единственного класса. Описанная выше задача оптимизации соответствует типу C_SVC (формализации с использованием параметра C). SVM типа NU_SVC решает схожую, но другую задачу с параметром $$v \in [0,1]$$ - , являющимся нижней оценкой на долю опорных векторов в обучающей выборке и верхней границей доли неправильно классифицированных прецедентов обучающей выборки.

    Поле kernel_type структуры CvSVMParams служит для обозначения используемого ядра. Возможные значения данной переменной также описаны в перечислении класса CvSVM:

    enum { LINEAR=0, POLY=1, RBF=2, SIGMOID=3 }; 
    

    В библиотеке OpenCV реализована непосредственная поддержка следующих ядер:

  • линейное ядро (LINEAR): $$K(x,x')=x,x'$$ ;
  • многочлен степени d (POLY): $$K(x,x')=(\gamma_0+yxx')^d,\gamma>0$$ ;
  • радиальная функция (RBF): $$K(x,x')=e^{-\gamma||x-x'||^2},\gamma>0$$ ;
  • сигмоидальная функция (SIGMOID): $$K(x,x')=tanh(\gamma_0+yxx')$$.
  • Далее в структуре CvSVMParams идут параметры ядер: degree соответствует степени многочлена, определяющего полиномиальное ядро, gamma – параметру в полиномиальном, радиальном и сигмоидальном ядрах, coef0 – параметру в полиномиальном и сигмоидальном ядрах. При использовании ядра, не имеющего того или иного параметра, значение, хранящееся в соответствующем поле структуры, игнорируется и может быть любым.

    Поля C, nu и p соответствуют параметрам оптимизационных задач, решаемых алгоритмом обучения. Тип используемой машины опорных векторов указывает на то, какой из этих параметров "активен": C используется совместно с C_SVC, EPS_SVR и NU_SVR; nu – с NU_SVC, NU_SVR и ONE_CLASS; p – с EPS_SVR.

    Поле class_weights предназначено для хранения матрицы-вектора, содержащей веса различных классов и может использоваться с машиной опорных векторов типа C_SVC, позволяя определить различные значения параметра для точек разных классов. Как отмечалось выше, решение задачи классификации более чем на два класса сводится к серии задач бинарной классификации. В CvSVM реализован подход "каждый против каждого". Таким образом, при решении задачи разделения точек классов

    $$\min_{\beta,\beta_{0},\zeta}\frac{1}{2}\parallel \beta\parallel + class\_weights[k_{1}] \cdot C \sum_{i:y_{i}=k_{1}} \zeta_{i} + class\_weights[k_{2}] \cdot C \sum_{i:y_{i}=k_{2}} \zeta_{i}.$$

    Данный параметр может быть полезен при несбалансированной обучающей выборке, содержащей малое количество прецедентов одного класса и большое количество другого. Чем больше вес, тем больший штраф назначается за неправильную классификацию обучающего прецедента этого класса. По умолчанию (class_weights = NULL) все веса равны.

    Последним нерассмотренным полем структуры CvSVMParams является term_crit. Данная переменная содержит параметры итерационного метода, используемого для решения оптимизационной задачи на этапе обучения. Данные параметры указываются с помощью следующей структуры:

    struct CvTermCriteria 
    { 
        int    type; 
        int    max_iter; 
        double epsilon; 
    }; 
    

    Рассмотрим поля данной структуры:

  • type – вид критерия останова: по точности (CV_TERMCRIT_EPS) или по количеству итераций (CV_TERMCRIT_ITER). Значение type, равное CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, применяется в случае, когда используются оба критерия.
  • max_iter – максимальное количество итераций.
  • epsilon – пороговое значение точности.
  • Для использования обученной ранее модели в классе CvSVM используются методы

    float predict( const Mat< sample, 
                   bool returnDFVal=false ) const; 
    void predict( InputArray samples, 
                  OutputArray results ) const; 
                  

    Рассмотрим их параметры.

  • sample – матрица-вектор типа CV_32F и размера $$1 \times d$$, содержащая координаты одной точки в пространстве признаков.
  • returnDFVal – флаг, позволяющий получать расстояние со знаком от разделяющей поверхности до указанной точки. Если returnDFVal=true и решается задача бинарной классификации, то возвращаемым значением будет величина $$h(x)\beta+\beta_0$$, иначе будет возвращено предсказанное значение целевого признака.
  • samples – матрица типа CV_32F и размера $$n_1 \times d$$, построчно содержащая координаты точек в пространстве признаков, для которых необходимо сделать предсказания.
  • results – матрица-вектор, в которую будут сохранены предсказанные значения.
  • Для сохранения обученной модели используется метод

    void save( const char* filename, 
               const char* name=0 ) const;
                

    Параметры данного метода:

  • filename – путь и имя файла, в который будет сохранена модель. В OpenCV поддерживается запись в XML- и YAML-файлы.
  • name – имя, под которым будет сохранена модель. Имя модели может состоять из строчных и заглавных букв латинского алфавита, цифр, символов ‘-’ и ‘_’. В случае сохранения в YAML-формате также допустимо использование пробелов.
  • Загрузка модели из файла осуществляется методом

    void load( const char* filename, const char* name=0 ); 
    

    Рассмотрим его параметры:

  • filename – XML- или YAML-файл, из которого будет выполнена загрузка.
  • name – имя модели, которую требуется загрузить.
  • Также в классе CvSVM реализован метод, позволяющий получить количество опорных векторов:

    int get_support_vector_count() const; 
    

    и метод для получения опорного вектора с номером :

    const float* get_support_vector(int i) const; 
     

    Рассмотрим пример использования класса CvSVM для решения задачи классификации.

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // используем SVM типа C_SVC и радиальным ядром 
        CvSVMParams params; 
        params.svm_type = CvSVM::C_SVC; 
        params.kernel_type = CvSVM::RBF; 
        params.gamma = 1.0; 
        params.C = 1.0; 
     
        CvSVM svm; 
        svm.train(samples, labels, 
                  Mat(), trainSampleMask, params); 
        svm.save("model.yml", "simpleSVMModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        Mat predictions; 
        svm.predict(samples.rowRange(0, n1), predictions); 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            trainError += (labels.at<int>(i) !=  
                           (int)(predictions.at<float>(i))); 
        }     trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        predictions = Mat(); 
        svm.predict(samples.rowRange(n1, n), predictions); 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            testError += (labels.at<int>(n1 + i) !=  
                          (int)(predictions.at<float>(i))); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    } 
    

    Визуальное представление данных и построенной границы между точками разных классов приведено на рис. 10.1.

    (рис 10.1) Точки обучающей выборки и граница, построенная с помощью машины опорных векторов с радиальным ядром

    2.3. Дерево решений

    Идея, лежащая в основе алгоритма обучения дерева решений, состоит в рекурсивном разбиении пространства признаков с помощью простых правил на непересекающиеся области. Полученная в результате обучения модель представляет собой дерево, где каждому внутреннему узлу соответствует некоторая область пространства признаков и правило ее разбиения на подобласти ($$x_j \leqslant c$$, если -й признак количественный и $$x_j \in L$$ , если признак категориальный). Каждой листовой вершине также соответствует область пространства $$\mathcal{X}$$ и приписано константное значение, которому принимается равной функция $$f$$ в данной области.

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

    Для обучения дерева решений служит метод train.

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(), 
                const Mat< missingDataMask=Mat(), 
                CvDTreeParams params=CvDTreeParams() ); 
                

    Рассмотрим параметры метода.

  • trainData – матрица, содержащая векторы $$x^{(i)}$$ из обучающей выборки. Матрица должна иметь тип CV_32F и размеры $$n \times d $$ или $$d \times n $$. В отличие от метода CvSVM::train здесь возможен вариант как с построчным хранением векторов $$x^{(i)}$$ , так и с их хранением по столбцам.
  • tflag – флаг, определяющий по строкам (tflag=CV_ROW_SAMPLE) хранятся вектора выборки в матрице trainData или по столбцам (tflag=CV_COL_SAMPLE).
  • responses – матрица-вектор, содержащая значения целевой переменной $$y^{(i)}$$ для прецедентов обучающей выборки. Данная матрица должна иметь тип CV_32S или CV_32Fи размеры $$1 \times n$$ или $$n \times 1$$.
  • varIdx – матрица-вектор, содержащая либо номера признаков (тип матрицы CV_32S), которые необходимо использовать при обучении, либо маску (тип матрицы CV_8U) размера $$1 \times d$$, где единицами отмечены используемые признаки, нулями – игнорируемые. По умолчанию (varIdx=Mat()) используются все признаки.
  • sampleIdx – матрица-вектор, имеющая такой же формат, как и varIdx, но отвечающая за прецеденты выборки, которые необходимо использовать для обучения. По умолчанию используются все имеющиеся прецеденты.
  • varType – матрица-вектор, содержащая информацию о типах переменных. Деревья решений поддерживают использование как количественных (CV_VAR_ORDERED), так и категориальных (CV_VAR_CATEGORICAL) признаков. Данная матрица должна иметь тип CV_8U и размеры $$1 \times (d+1)$$ или $$(d+1) \times 1$$, где последний элемент определяет тип целевой переменной. По умолчанию (varType=Mat()) целевая переменная считается категориальной, т.е. решается задача классификации, а остальные количественными.
  • missingDataMask – матрица, содержащая маску пропущенных значений, где единицами отмечены неизвестные значения признаков, нулями – известные. В отличие от машины опорных векторов, деревья решений позволяют работать с пропущенными значениями в признаковых описаниях объектов $$x^{(i)}$$ . Матрица missingDataMask должна иметь тип CV_8U и по размеру совпадать с матрицей trainData. По умолчанию считается, что пропущенных значений нет.
  • params – параметры алгоритма обучения.
  • Для представления параметров алгоритма обучения дерева решений используется структура CvDTreeParams:

    struct CvDTreeParams 
    { 
        int   max_categories; 
        int   max_depth; 
        int   min_sample_count; 
        int   cv_folds; 
        bool  use_surrogates; 
        bool  use_1se_rule; 
        bool  truncate_pruned_tree; 
        float regression_accuracy; 
        const float* priors; 
     
        CvDTreeParams(); 
        CvDTreeParams( int max_depth, 
                       int min_sample_count, 
                       float regression_accuracy, 
                       bool use_surrogates, 
                       int max_categories, 
                       int cv_folds,                    bool use_1se_rule, 
                       bool truncate_pruned_tree, 
                       const float* priors ); 
    }; 
    

    Остановимся подробнее на описании данной структуры.

  • max_depth – ограничение на высоту обучаемого дерева.
  • min_sample_count – минимальное количество объектов обучающей выборки, которое должно быть в области пространства признаков, определяемой некоторым узлом дерева, чтобы разбиение этой области продолжилось.
  • regression_accuracy – разбиение узла регрессионного дерева прекращается, если модуль разности значения, приписанного этому узлу, и истинного значения целевого признака для всех прецедентов, попавших в данный узел, не превышает данного значения.
  • max_categories – максимальное количество возможных значений категориального признака, для которого точно вычисляется наилучшее правило разбиения. Данный параметр актуален лишь для задач классификации с числом классов больше двух, т.к. в данном случае выбор подмножества L множества возможных значений Q категориального признака происходит с помощью алгоритма с экспоненциальной от мощности Q трудоемкостью [2]. В данном случае для снижения времени работы алгоритма производится предварительная кластеризация множества Q так, чтобы количество кластеров не превышало max_categories. В случае если решается задача регрессии или бинарной классификации, то точное отыскания оптимального разбиения по данному признаку выполняется за линейное время от |Q| [2] и не требует предварительной кластеризации. Следует отметить, что максимальная поддерживаемая в OpenCV мощность равна 64.
  • use_surrogates – флаг, определяющий необходимость построения суррогатных разбиений. Каждому узлу дерева решений может быть приписано не только одно (основное) правило разбиения, а также несколько второстепенных (суррогатных) правил. В качестве суррогатных выбираются разбиения наиболее похожие на основное [1]. Построение суррогатных разбиений (use_surrogates=true) ведет к росту времени обучения модели, однако, может повысить качество оценки значимости переменных с помощью построенного дерева и улучшить качество предсказания для объектов с пропущенными значениями.
  • cv_folds – количество частей, на которые разделяется обучающая выборка, для выполнения перекрестного контроля при выполнении процедуры отсечений (pruning) [1]. Типичная стратегия построения дерева решений заключается в обучении большого дерева, к которому затем применяются отсечения для предотвращения переобучения. Если cv_folds=0, то отсечения не выполняются.
  • use_1se_rule – использование более строгого критерия отсечения. Если use_1se_rule=true, то после процедуры отсечения получается меньшее дерево.
  • truncate_pruned_tree – указывает, следует ли физически удалять из памяти отсеченные узлы дерева.
  • priors – для задачи классификации, задает априорные вероятности появления точек различных классов. Данный параметр можно использовать, например, в случае несбалансированной обучающей выборки, т.е. неравного количества прецедентов разных классов.
  • Предсказания с помощью предварительно обученной модели выполняются с помощью метода predict:

    CvDTreeNode* predict( const Mat< sample, 
                          const Mat< missingDataMask=Mat(), 
                          bool preprocessedInput=false ) const; 
                          

    Рассмотрим параметры данного метода:

  • sample – матрица-вектор типа CV_32F и размера $$1 \times d$$, содержащая координаты одной точки в пространстве признаков.
  • missingDataMask – матрица-вектор типа CV_8U и размера $$1 \times d$$ , представляющая собой маску пропущенных значений в sample.
  • preprocessedInput – определяет порядок работы с категориальными признаками в ходе предсказания. В матрице признаковых описаний объектов обучающей выборки, используемой методом CvDTree::train, значения категориальных признаков могут быть обозначены произвольными целыми числами. Например, множеством различных значений признака может быть {3,7,9}. Однако внутренним представлением обученной модели является множество, состоящее из чисел от 0 до $$|Q|-1$$. Таким образом, на этапе предсказания требуется выполнять пересчет значений категориальных признаков, что может негативно сказаться на скорости выполнения данной операции. Значение preprocessedInput=true говорит, что пересчет значений уже выполнен. Данный параметр актуален лишь при наличии категориальных признаков.
  • Следует отметить, что метод CvDTree::predict возвращает указатель на структуру, описывающую узел дерева. Само предсказанное моделью значение целевого признака хранится в поле value данной структуры.

    Сохранение модели дерева решений в файл и загрузка из него выполняется с помощью методов save и load, использование которых полностью совпадает с аналогичными методами класса CvSVM, описанными выше.

    Следует отметить, что класс CvDTree также содержит дополнительный функционал, например, для определения значимости различных переменных для восстановления зависимости от , что, однако, выходит за рамки данной лабораторной работы и может быть предоставлено для самостоятельного изучения [7].

    Приведем пример использования класса CvDTree для решения задачи бинарной классификации аналогичной, решаемой в примере для CvSVM.

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной)     Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать дерево решений высоты не больше 10, 
        // после построения которого выполним отсечения 
        // с помощью пятикратного перекресного контроля 
        CvDTreeParams params; 
        params.max_depth = 10; 
        params.min_sample_count = 1; 
        params.cv_folds = 5; 
     
        CvDTree dtree; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        dtree.train(samples, CV_ROW_SAMPLE, 
                    labels, varIdx, 
                    trainSampleMask, varTypes, 
                    Mat(), params); 
        dtree.save("model-dtree.yml", "simpleDTreeModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(dtree.predict(samples.row(i))->value); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f;     for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(dtree.predict(samples.row(n1 + i))->value); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    } 
    

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

    (рис 10.2) Точки обучающей выборки и разбиение пространства признаков деревом решений

    2.4. Случайный лес

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

    Алгоритм случайного леса реализован в библиотеке OpenCV в виде класса CvRTrees. Рассмотрим принципы работы с данным классом. Для обучения случайного леса предназначен метод train:

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(), 
                const Mat< missingDataMask=Mat(), 
                CvRTParams params=CvRTParams() );

    Назначение и формат параметров данного метода целиком совпадает с методом CvDTree::train, реализующим обучение одиночного дерева решений, за исключением параметров алгоритма обучения. Параметры случайного леса представляются в виде структуры CvRTParams:

    struct CvRTParams : public CvDTreeParams 
    { 
        bool calc_var_importance; 
        int nactive_vars; 
        CvTermCriteria term_crit; 
     
        CvRTParams(); 
        CvRTParams( int max_depth, 
                    int min_sample_count, 
                    float regression_accuracy, 
                    bool use_surrogates, 
                    int max_categories, 
                    const float* priors, 
                    bool calc_var_importance, 
                    int nactive_vars, 
                    int max_num_of_trees_in_the_forest, 
                    float forest_accuracy, 
                    int termcrit_type ); 
    }; 

    Так как алгоритм случайного леса строит деревья решений, большинство параметров определяют настройки алгоритма обучения одиночных деревьев (см. описание структуры CvDTreeParams). Следует отметить, что в рамках модели случайного леса обучаются большие (оригинальный подход [4] не подразумевал ограничение высоты дерева как критерий останова его построения) деревья решений без последующего применения процедуры отсечения. Теперь рассмотрим параметры, имеющие отношение непосредственно к модели случайного леса:

  • nactive_vars – количество признаков, выбираемых случайным образом для обучения каждого дерева решений. Если nactive_vars=0, то при обучении дерева будет использоваться целая часть снизу от $$\sqrt d$$ признаков, если же указано значение nactive_vars>0, то min(nactive_vars, d ) признаков.
  • term_crit – критерий прекращения добавления деревьев в ансамбль (лес). Так как при обучении каждого дерева используются не все прецеденты обучающей выборки, то неиспользуемая часть выборки (out-of-bag samples) может быть использована в качестве тестовой для оценки качества модели (out-of-bag error, oob error). В связи с этим появляется возможность прекратить построение новых деревьев, при достижении достаточно малой oob ошибки. Структура CvTermCriteria была рассмотрена выше (см. описание CvSVMParams), здесь отметим лишь, что значение term_crit.max_iter задает максимальное количество обучаемых деревьев, term_crit.epsilon – максимальную допустимую oob ошибку, term_crit.type – тип используемого критерия останова.
  • calc_var_importance – флаг, определяющий необходимость вычисления значимости переменных в ходе обучения модели случайного леса.
  • Для осуществления предсказаний в классе CvRTrees предназначен метод predict:

    float predict( const Mat< sample, 
                   const Mat< missing=Mat() ) const;
                   

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

    Методы save и load, осуществляющие сохранение и загрузку модели соответственно, применяются аналогично данным методам классов CvSVM и CvDTree.

    Приведем пример использования класса CvRTrees для решения задачи бинарной классификации и иллюстрацию порождаемого обученной моделью разбиения пространства признаков (см. рис. 10.3).

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv; 
     // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать случайный лес из 250 деревьев 
        // высоты не больше 10 
        CvRTParams params; 
        params.max_depth = 10; 
        params.min_sample_count = 1; 
        params.calc_var_importance = false; 
        params.term_crit.type = CV_TERMCRIT_ITER;     params.term_crit.max_iter = 250; 
     
        CvRTrees rf; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        rf.train(samples, CV_ROW_SAMPLE, 
                 labels, varIdx, 
                 trainSampleMask, varTypes, 
                 Mat(), params); 
        rf.save("model-rf.yml", "simpleRTreesModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(rf.predict(samples.row(i))); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(rf.predict(samples.row(n1 + i))); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    }  
    
    (рис 10.3) Точки обучающей выборки и разбиение пространства признаков случайным лесом

    2.5. Градиентный бустинг деревьев решений

    Градиентный бустинг, как и любой бустинг алгоритм, последовательно строит базовые модели так, что каждая следующая улучшает качество всего ансамбля. Градиентный бустинг деревьев решений строит модель в виде суммы деревьев $$f(x)=h_0+v \cdot \sum^M_{j=1} h_j(x)$$ , где $$h_0$$ – некоторая константная модель (начальное приближение), $$v \in (0,1]$$ – параметр, регулирующий скорость обучения и влияние отдельных деревьев на всю модель, $$h_j(x)$$ – регрессионные деревья решений. Новые слагаемые-деревья добавляются в сумму путем жадной минимизации эмпирического риска, заданного некоторой функцией потерь $$L(y,y')=L(y,f(x))$$ . Данный метод без серьезной модификации может применяться для любой дифференцируемой функции потерь.

    Метод градиентного бустинга деревьев решений в библиотеке OpenCV реализован в виде класса CvGBTrees. Для запуска обучения модели реализован метод train:

    bool train( const Mat< trainData, 
                int tflag, 
                const Mat< responses, 
                const Mat< varIdx=Mat(), 
                const Mat< sampleIdx=Mat(), 
                const Mat< varType=Mat(),             const Mat< missingDataMask=Mat(), 
                CvGBTreesParams params=CvGBTreesParams(), 
                bool update=false );
                

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

    struct CvGBTreesParams : public CvDTreeParams 
    { 
        int weak_count; 
        int loss_function_type; 
        float subsample_portion; 
        float shrinkage; 
     
        CvGBTreesParams(); 
        CvGBTreesParams( int loss_function_type, 
                         int weak_count, 
                         float shrinkage, 
                         float subsample_portion, 
                         int max_depth, 
                         bool use_surrogates ); 
    }; 
    

    Рассмотрим значения ее полей:

  • loss_function_type – тип используемой функции потерь. Список реализованных штрафных функций представлен перечислением в классе CvGBTrees:
    enum  {SQUARED_LOSS=0,  ABSOLUTE_LOSS,  HUBER_LOSS=3, 
    DEVIANCE_LOSS}; 
    
    где для задачи восстановления регрессии предназначены функции: $$L(y,y')=(y-y')^2$$ (SQUARED_LOSS), $$L(y,y')=|y-y'|$$ (ABSOLUTE_LOSS), $$L(y,y')=\frac 1 2 (y-y')^21(|y-y'|\leqslant \delta)+\delta(|y-y'|-\frac {\delta} 2)1(|y-y'|> \delta)$$ (HUBER_LOSS). Функция $$L(y,y'_1,y'_2,...,y'_K)=-\sum_{k=1}^K1(y=k)ln(\frac {exp(y'_k)}{\sum_{k=1}^K exp(y'_i)})$$ (DEVIANCE_LOSS) предназначена для решения задач классификации на K классов. При использовании данного штрафа строится K моделей в виде суммы деревьев, для оценки вероятностей принадлежности объекта каждому из классов.
  • weak_count – количество обучаемых деревьев. В случае loss_function_type=DEVIANCE_LOSS фактическое количество деревьев, которое будет построено в K раз больше.
  • shrinkage – скорость обучения, уменьшение которой позволяет бороться с переобучением модели.
  • subsample_portion – доля выборки, используемая для обучения каждого дерева. Для построения дерева решений случайным образом (без возвращения) выбирается subsample_portion прецедентов обучающей выборки. Случайные подвыбороки могут улучшить качество модели, однако, неприменимы в случае небольшого числа имеющихся прецедентов.
  • Предсказания с помощью модели градиентного бустинга деревьев решений выполняет метод predict:

    float predict( const Mat sample, 
                   const Mat missing=Mat(), 
                   const Range slice=Range::all(), 
                   int k=-1 ) const; 
                   

    Рассмотрим параметры данного метода:

  • sample – матрица-вектор признакового описания объекта.
  • missing – матрица-вектор, содержащая маску пропущенных значений.
  • slice – используемые для предсказания деревья (по умолчанию используются все). Может применяться для подбора оптимального количества деревьев без необходимости производить обучение заново.
  • k – для loss_function_type=DEVIANCE_LOSS номер функции, значение которой следует вернуть. Если k=-1, возвращается предсказанное значение целевой переменной.
  • Сохранение и загрузка модели выполняется так же, как и в рассмотренных ранее алгоритмах.

    Ниже приведен пример использования класса CvGBTrees для решения задачи бинарной классификации и иллюстрация полученного классификатора (см. рис. 10.4)

    #include <stdlib.h> 
    #include <stdio.h> 
    #include <opencv2/core/core.hpp> 
    #include <opencv2/ml/ml.hpp> 
     
    using namespace cv;  
    // размерность пространства признаков 
    const int d = 2; 
     
    // функция истинной зависимости целевого признака 
    // от остальных 
    int f(Mat sample) 
    { 
        return (int)((sample.at<float>(0) < 0.5f  
                      sample.at<float>(1) < 0.5f) || 
                     (sample.at<float>(0) > 0.5f  
                      sample.at<float>(1) > 0.5f)); 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        // объем генерируемой выборки 
        int n = 2000; 
        // объем обучающей части выборки 
        int n1 = 1000; 
         
        // матрица признаковых описаний объектов 
        Mat samples(n, d, CV_32F); 
        // номера классов (матрица значений целевой переменной) 
        Mat labels(n, 1, CV_32S); 
        // генерируем случайным образом точки 
        // в пространстве признаков 
        randu(samples, 0.0f, 1.0f); 
     
        // вычисляем истинные значения целевой переменной 
        for (int i = 0; i < n; ++i) 
        { 
            labels.at<int>(i) = f(samples.row(i)); 
        } 
     
        // создаем маску прецедентов, которые будут 
        // использоваться для обучения: используем n1 
        // первых прецедентов 
        Mat trainSampleMask(1, n1, CV_32S); 
        for (int i = 0; i < n1; ++i) 
        { 
            trainSampleMask.at<int>(i) = i; 
        } 
     
        // будем обучать модель градиентного бустинга 
        // деревьев решений из 250 деревьев высоты 3 и 
        // скоростью обучения 0.5,без использования подвыборок. 
        // Т.к. решается задача классификации используем 
        // функцию потерь DEVIANCE_LOSS 
        CvGBTreesParams params;     params.max_depth = 3; 
        params.min_sample_count = 1; 
        params.weak_count = 250; 
        params.shrinkage = 0.5f; 
        params.subsample_portion = 1.0f; 
        params.loss_function_type = CvGBTrees::DEVIANCE_LOSS; 
     
        CvGBTrees gbt; 
        Mat varIdx(1, d, CV_8U, Scalar(1)); 
        Mat varTypes(1, d + 1, CV_8U, Scalar(CV_VAR_ORDERED)); 
        varTypes.at<uchar>(d) = CV_VAR_CATEGORICAL; 
        gbt.train(samples, CV_ROW_SAMPLE, 
                  labels, varIdx, 
                  trainSampleMask, varTypes, 
                  Mat(), params); 
        gbt.save("model-gbt.yml", "simpleGBTreesModel"); 
         
        // вычисляем ошибку на обучающей выборке 
        float trainError = 0.0f; 
        for (int i = 0; i < n1; ++i) 
        { 
            int prediction = 
                (int)(gbt.predict(samples.row(i))); 
            trainError += (labels.at<int>(i) != prediction); 
        } 
        trainError /= float(n1); 
     
        // вычисляем ошибку на тестовой выборке 
        float testError = 0.0f; 
        for (int i = 0; i < n - n1; ++i) 
        { 
            int prediction =  
              (int)(gbt.predict(samples.row(n1 + i))); 
            testError += 
                (labels.at<int>(n1 + i) != prediction); 
        } 
        testError /= float(n - n1); 
     
        printf("train error = %.4f\ntest error = %.4f\n", 
               trainError, testError); 
         
        return 0; 
    }  
    
    (рис 10.4) Точки обучающей выборки и разбиение пространства признаков с помощью модели градиентного бустинга

    3. Обзор возможностей библиотеки OpenCV для решения задач обучения без учителя

    3.1. Задача кластеризации

    Задача кластеризации заключается в разбиении выборки $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$ на непересекающиеся подмножества таким образом, чтобы схожие точки (обычно близкие в некоторой метрике) попали в одно подмножество (кластер), а точки из разных кластеров сильно друг от друга отличались (были далеки). Для решения данной задачи в библиотеки OpenCV реализован метод центров тяжести (k-means) и EM-алгоритм. В данной лабораторной работе рассматривается использование программной реализации метод центров тяжести, как одного из наиболее популярных алгоритмов кластеризации на настоящий момент.

    3.2. Метод центров тяжести (k-means)

    Метод центров тяжести разбивает выборку на заданное количество кластеров K путем выбора их центров. Поиск центров кластеров производится из соображений минимизации суммарного расстояния от каждой точки до ближайшего центра с помощью метода локальной оптимизации. Так как алгоритм не гарантирует достижения глобального минимума, ключевую роль играет начальная инициализация центров кластеров. Распространенным методом является случайный (с равной вероятностью) выбор K точек из имеющегося множества $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$. Однако алгоритм центров тяжести, запущенный на таких начальных данных может выдать в результате локальный минимум сколь угодно хуже глобального. Альтернативой является метод k-means++ [5], предложенный Д. Артуром и С. Вассильвицким, основанный на последовательном выборе K точек из выборки случайным образом, но с вероятностью пропорциональной квадрату расстояния от точки до ближайшего уже выбранного центра. В данном случае, математическое ожидание отношения найденного минимума к глобальному является величиной $$O(log K)$$.

    Метод центров тяжести с евклидовой метрикой реализован в библиотеке OpenCV в виде функции kmeans модуля core:

    double kmeans( InputArray data, 
                   int K, 
                   InputOutputArray bestLabels, 
                   TermCriteria criteria, 
                   int attempts, 
                   int flags, 
                   OutputArray centers=noArray() ); 
                   

    Рассмотрим параметры данной функции:

  • data – матрица типа CV_32F, в которой каждой строке соответствует точка выборки.
  • K – количество кластеров, получаемых на выходе алгоритма.
  • bestLabels – матрица размера $$1 \times N$$, в которую для каждой точки $$x^{(i)}$$ будет сохранен номер кластера, в который попала данная точка.
  • criteria – критерий останова итерационного метода оптимизации. Алгоритм k-means может закончить работу либо после совершения заданного количества итераций, либо если каждый центр кластера сдвинется на величину меньше criteria.epsilon.
  • attempts – количество запусков алгоритма k-means с различными начальными центрами кластеров. В качестве конечного разбиения будет возвращен наилучший из полученных результатов.
  • flags – метод генерации центров кластеров перед запуском алгоритма k-means. Допустимые значения: KMEANS_RANDOM_CENTERS – случайный равновероятный выбор центров, KMEANS_PP_CENTERS – случайный выбор методом k- means++, KMEANS_USE_INITIAL_LABELS – для первого запуска используется заданное с помощью параметра bestLabels разбиение.
  • centers – матрица с наилучшими найденными центрами кластеров. Каждая строка соответствует координатам центра одно кластера.
  • Данная функция возвращает сумму квадратов расстояний от каждой точки до ближайшего к ней центра.

    Рассмотрим пример использования функции kmeans для кластеризации точек на плоскости. Результат полученной кластеризации приведен на рис. 10.5.

    #include <opencv2/core/core.hpp> 
     
    using namespace cv; 
     
    Mat generateDataset() 
    { 
        int n = 300; 
        Mat data(3 * n, 2, CV_32F); 
         
        randn(data(Range(0, n), Range(0, 1)), 0.0, 0.05); 
        randn(data(Range(0, n), Range(1, 2)), 0.5, 0.25); 
     
        randn(data(Range(n, 2 * n), Range(0, 1)), 0.7, 0.25); 
        randn(data(Range(n, 2 * n), Range(1, 2)), 0.0, 0.05); 
     
        randn(data(Range(2 * n, 3 * n), Range(0, 1)), 
              0.7, 0.15); 
        randn(data(Range(2 * n, 3 * n), Range(1, 2)), 
              0.8, 0.15); 
         
        return data; 
    } 
     
    int main(int argc, char* argv[]) 
    { 
        Mat samples = generateDataset(); 
     
        Mat labels; 
        Mat centers; 
        kmeans(samples, 
            3, 
            labels, 
            TermCriteria(TermCriteria::COUNT + 
                         TermCriteria::EPS, 10000, 0.001),         10, 
            KMEANS_PP_CENTERS, 
            centers); 
         
        return 0; 
    } 
    
    (рис 10.5) Кластеризация методом центров тяжести (k-means)

    4. Программная реализация

    4.1. Разработка приложения для решения задач классификации

    4.1.1. Требования к приложению

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

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

    Приложение будет состоять из набора модулей (cvsvm.cpp/h, cvdtree.cpp/h, cvrtrees.cpp/h, cvgbtrees.cpp/h), каждый из которых предназначен для работы (запрос у пользователя параметров, обучение и предсказание) рассматриваемых алгоритмов обучения, модуля вычисления ошибки классификации (errorMetrics.cpp/h), модуля визуализации данных (drawingFunctions.cpp/h) и основного модуля (main.cpp), содержащего общую логику работы программы. Основной модуль и модуль визуализации предоставляются в реализованном виде, следовательно, необходимо реализовать непосредственно работу с различными алгоритмами обучения с учителем. Далее рассматриваются интерфейсы готовых функций, а также функционал, который предлагается реализовать самостоятельно.

    Модуль визуализации данных содержит функцию рисования точек из двумерного пространства признаков drawPoints:

    void drawPoints(Mat  img, 
                    const Mat  data, 
                    const Mat  classes, 
                    const Mat  ranges, 
                    std::map<int, Scalar>  classColors, 
                    int drawingMode = 0); 
                    

    В качестве аргументов данная функция принимает:

  • img – изображение (матрица типа CV_8UC3).
  • data – матрица типа CV_32F, содержащая признаковые описания объектов выборки.
  • classes – матрица типа CV_32S с номерами классов объектов выборки.
  • ranges – матрица типа CV_64F, содержащая в первом столбце визуализируемый диапазон первого признака (отображаемого по оси X), а вот втором – второго.
  • classColors – соответствие номеров классов и цветов точек. Если какому-либо классу не будет поставлен в соответствие цвет, то он будет сгенерирован случайным образом.
  • drawingMode – тип отображаемых точек. Для рисования закрашенных точек используется drawingMode=0, для "выколотых" точек drawingMode=1, для рисования черных точек drawingMode=2. Использование данного параметра позволяет различным образом отображать точки обучающей и тестовой выборок, а также (при использовании SVM) опорных векторов.
  • Функция drawPartition позволяет отображать разбиение пространства признаков на области, соответствующие разным классам, путем вычисления предсказаний на равномерной сетке:

    void drawPartition(Mat  img, 
                       map<int, Scalar>  classColors, 
                       const Mat  dataRanges, 
                       const Size stepsNum, 
                       const CvStatModel  model, 
                       getPredictedClassLabel * predictLabel); 
                       

    Параметры функции:

  • img – изображение (матрица типа CV_8UC3).
  • classColors – соответствие номеров классов и цветов точек. Если какому-либо классу не будет поставлен в соответствие цвет, то он будет сгенерирован случайным образом.
  • dataRanges – матрица типа CV_64F, содержащая в первом столбце визуализируемый диапазон первого признака (отображаемого по оси X ), а вот втором – второго.
  • stepsNum – количество узлов сетки в каждом из направлений.
  • model – обученная модель для выполнения предсказаний в узлах сетки.
  • predictLabel – указатель на функцию вычисления пресказаний с помощью model.
  • Функция getRanges предназначена для поиска матрицы минимальных и максимальных значений каждого признака в выборке data:

     
    Mat getRanges(const Mat  data); 
    

    Функция readDatasetFromFile позволяет считать данные (обучающую и тестовую выборки) из XML- или YAML-файла.

     
    void readDatasetFromFile(Mat  featuresTrain, 
                             Mat  classesTrain, 
                             Mat  featuresTest, 
                             Mat  classesTest); 
                             

    Файл должен содержать матрицы признаков объектов обучающей ("featuresTrain") и тестовой ("featuresTest") выборок и соответствующих им классов ("classesTrain" и "classesTest") в формате OpenCV. Матрицы classesTrain и classesTest должны иметь тип CV_32S, featuresTrain и featuresTest – CV_32F.

    Основная логика программы сосредоточена в модуле main.cpp и выглядит следующим образом:

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

    void trainSVM(const Mat  trainSamples, 
                  const Mat  trainClasses, 
                  const CvSVMParams  params, 
                  CvSVM  svm); 
    void trainDTree(const Mat  trainSamples, 
                    const Mat  trainClasses, 
                    const CvDTreeParams  params, 
                    CvDTree  dtree); 
    void trainRTrees(const Mat  trainSamples, 
                     const Mat  trainClasses, 
                     const CvRTParams  params, 
                     CvRTrees  rtrees); 
    void trainGBTrees(const Mat  trainSamples, 
                      const Mat  trainClasses, 
                      const CvGBTreesParams  params, 
                      CvGBTrees  gbtrees); 
                      

    Данные функции находятся в файлах cvsvm.cpp, cvdtree.cpp, cvrtrees.cpp, cvgbtrees.cpp соответственно. В теле каждой функции требуется вызвать метод переданного в нее объекта для обучения модели на данных trainSamples (матрица признаковых описаний объектов) и trainClasses (матрица номеров классов) с параметрами params. Также требуется реализация функций для предсказания:

    int getSVMPrediction(const Mat  sample, 
                         const CvStatModel  model); 
    int getDTreePrediction(const Mat  sample, 
                           const CvStatModel  model); 
    int getRTreesPrediction(const Mat  sample, 
                            const CvStatModel  model); 
    int getGBTreesPrediction(const Mat  sample, 
                             const CvStatModel  model); 
                             

    Функции, принимают признаковое описание объекта sample и обученную модель model, возвращая номер предсказанного класса. Интерфейс данных функций унифицирован для более простого подсчета ошибок на обучающей и тестовой выборках. Функции принимают объекты базового типа CvStatModel, однако, фактичеси это должны быть объекты соответствующих классов: для getSVMPrediction объект класса CvSVM, для getDTreePrediction – CvDTree и т.д.

    Также предлагается реализовать функцию getSupportVectors (файл cvsvm.cpp), принимающую обученную машину опорных векторов svm и возвращающую матрицу, где каждая строка соответствует опорному вектору:

    Mat getSupportVectors(const CvSVM  svm); 
    

    Функции запроса параметров каждого из рассматриваемых алгоритмов обучения предоставляются в готовом виде и рекомендуются для самостоятельного разбора.

    Также в рамках лабораторной работы предлагается реализовать функцию вычисления ошибки классификации (долю неправильно классифицированных объектов выборки) getClassificationError:

    float getClassificationError(const Mat  samples, 
          const Mat  classes, 
          const CvStatModel  model, 
          int (*predict) (const Mat  sample, 
                          const CvStatModel  model)); 
                          

    Параметры функции:

  • samples – признаковые описания объектов выборки.
  • classes – номера классов (истинные значения целевого признака) для объектов выборки.
  • model – обученная модель.
  • predict – указатель на функцию, принимающую один объект выборки и модель и возвращающую номер предсказанного класса.
  • Данная функция должна вычислять доля неправильно классифицированных объектов выборки.

    После того, как описанные функции будут реализованы, предлагается применить рассмотренные алгоритмы классификации к наборам данных из файлов dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml, datasetMulticlass.yml и datasetHighDim.yml. А также проанализировать, на каких данных лучше/хуже работает тот или иной подход и какое влияние на конечную модель оказывают параметры алгоритма обучения.

    4.2. Разработка приложения для решения задач кластеризации

    4.2.1. Требования к приложению

    Также в рамках данной лабораторной работы предлагается разработать приложение для кластеризации точек методом центров тяжести. К приложению предъявляются следующие требования:

  • Загрузка данных из файла, имя которого указывается в качестве параметра командной строки.
  • Выполнение кластеризации на заданное (в виде аргумента командной строки) число кластеров.
  • Визуализация кластеризации в двумерном пространстве.
  • 4.2.2. Структура приложения

    Приложение будет состоять из двух модулей: основной (main.cpp) и модуль визуализации (drawingFunctions.cpp/h). Функции визуализации предоставляются в готовом виде и аналогичны описанным в разделе 4.1.2.

    В основном модуле должна выполняться следующая последовательность действий:

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

    После того, как код основного модуля будет дописан, предлагается запустить программу на предоставленных наборах данных (dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml) и проанализировать полученные результаты.

    Контрольные вопросы

  • Для чего в алгоритме опорных векторов применяются ядра?
  • Какой эффект можно наблюдать при использовании машины опорных векторов с радиальным ядром с большим значением параметра $$\lambda$$ ?
  • Каким образом дерево решений разбивает пространство признаков?
  • Для чего к деревьям решений применяется процедура отсечений?
  • Применяются ли отсечения к деревьям решений в составе случайного леса?
  • Происходит ли переобучение при увеличении количества деревьев в случайном лесе?
  • Происходит ли переобучение при увеличении количества деревьев в модели градиентного бустинга?
  • В чем заключается идея метода центров тяжести?
  • 6. Дополнительные задания

  • Реализуйте возможность сохранения и загрузки обученной модели в приложении для решения задач классификации.
  • Реализуйте функцию вычисления матрицы ошибок классификации $$\varepsilon$$, где элемент $$\varepsilon_{i,j}$$ равен количеству прецедентов выборки принадлежащих к классу j и отнесенных алгоритмом классификации к классу i.
  • Реализуйте метод перекрестного контроля для подбора параметров алгоритмов обучения
  • Вернуться к учебному плану