Презентацию к лабораторной работе Вы можете скачать здесь.
Дополнительные материалы к лабораторной работе Вы можете скачать здесь.
В настоящее время алгоритмы машинного обучения находят широкое применение в системах различного назначения: поисковых системах, алгоритмах распознавания, анализа и синтеза речи, медицинской диагностике, биоинформатике, финансовом прогнозировании и т.д. Исключением не является и компьютерное зрение. Например, подавляющее большинство современных систем детектирования объектов на изображениях и видео основано на применении алгоритмов машинного обучения. Такой подход позволяет компьютерной системе самой "научиться" отличать изображения, содержащие искомый объект, от остальных, используя для этого лишь примеры таких изображений. Также кластеризация и обучение с учителем успешно применяется в алгоритмах классификации изображений, опять же позволяя автоматически установить неявные различия между изображениями различных типов.
В настоящей работе рассматриваются некоторые алгоритмы обучения с учителем и кластеризации, реализованные в открытой библиотеке компьютерного зрения OpenCV [6].
Цель данной работы – изучить некоторые алгоритмы обучения с учителем и без с использованием соответствующих функций библиотеки компьютерного зрения OpenCV.
Данная цель предполагает решение следующих задач:
В работе приводится краткое описание некоторых алгоритмов классификации и кластеризации. Приводятся и описываются интерфейсы структур и классов, прототипы функций библиотеки OpenCV, реализующих рассматриваемые алгоритмы. Предлагаются примеры программ, демонстрирующие использование данных классов и функций. Приводятся результаты работы алгоритмов на модельных задачах. Разрабатывается структура приложений, для решения задач классификации и кластеризации.
Вычислительные эксперименты проводились с использованием следующей инфраструктуры (табл. 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 |
Для выполнения данной лабораторной работы требуется:
При выполнении данной лабораторной работы рекомендуется следующая последовательность действий:
Одной из задач, изучаемой в машинном обучении, является задача обучения с учителем, которая заключается в как можно более точном восстановлении (определении) вида зависимости $$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]. Каждый алгоритм обладает своими достоинствами и недостатками. Различные алгоритмы имеют свои ограничения применимости, например, могут быть предназначены только для задач классификации/восстановления регрессии, могут допускать работу лишь с количественными признаками, могут не поддерживать наличие пропущенных значений, т.е. неизвестных значений некоторых признаков у какого-либо прецедента. Также, тип функции, получаемой на выходе определенного алгоритма обучения, может лучше/хуже подходить для аппроксимации искомой зависимости. Таким образом, конкретный алгоритм обучения с учителем должен выбираться, исходя из специфики конкретной рассматриваемой задачи. Данная лабораторная работа посвящена описанию использования реализаций следующих алгоритмов: машина опорных векторов, дерево решений, случайный лес и градиентный бустинг деревьев решений.
Пусть $$\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'$$ ; 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 реализован подход "каждый против
каждого". Таким образом, при решении задачи разделения точек классов
Данный параметр может быть полезен при несбалансированной
обучающей выборке, содержащей малое количество прецедентов одного
класса и большое количество другого. Чем больше вес, тем больший
штраф назначается за неправильную классификацию обучающего
прецедента этого класса. По умолчанию (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) Точки обучающей выборки и граница, построенная с помощью машины опорных векторов с радиальным ядром
Идея, лежащая в основе алгоритма обучения дерева решений, состоит в рекурсивном разбиении пространства признаков с помощью простых правил на непересекающиеся области. Полученная в результате обучения модель представляет собой дерево, где каждому внутреннему узлу соответствует некоторая область пространства признаков и правило ее разбиения на подобласти ($$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) Точки обучающей выборки и разбиение пространства признаков деревом решений
Случайный лес является бэггинг алгоритмом, основная идея которого заключается в построении как можно менее зависимых друг от друга деревьев решений, с последующим принятием решения путем усреднения в случае регрессии и голосованием в случае классификации. Зависимость между деревьями снижается за счет использования случайной выборки с возвращением из исходных прецедентов и случайного подмножества признаков.
Алгоритм случайного леса реализован в библиотеке 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) Точки обучающей выборки и разбиение пространства признаков случайным лесом
Градиентный бустинг, как и любой бустинг алгоритм, последовательно строит базовые модели так, что каждая следующая улучшает качество всего ансамбля. Градиентный бустинг деревьев решений строит модель в виде суммы деревьев $$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) Точки обучающей выборки и разбиение пространства признаков с помощью модели градиентного бустинга
Задача кластеризации заключается в разбиении выборки $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$ на непересекающиеся подмножества таким образом, чтобы схожие точки (обычно близкие в некоторой метрике) попали в одно подмножество (кластер), а точки из разных кластеров сильно друг от друга отличались (были далеки). Для решения данной задачи в библиотеки OpenCV реализован метод центров тяжести (k-means) и EM-алгоритм. В данной лабораторной работе рассматривается использование программной реализации метод центров тяжести, как одного из наиболее популярных алгоритмов кластеризации на настоящий момент.
Метод центров тяжести разбивает выборку на заданное количество
кластеров 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)
В рамках данной лабораторной работы предлагается разработать приложение для решения задач классификации, предусматривающее возможность применения рассмотренных выше алгоритмов машинного обучения. К приложению предъявляются следующие требования:
Приложение будет состоять из набора модулей (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. А также проанализировать, на каких данных лучше/хуже работает тот или иной подход и какое влияние на конечную модель оказывают параметры алгоритма обучения.
Также в рамках данной лабораторной работы предлагается разработать приложение для кластеризации точек методом центров тяжести. К приложению предъявляются следующие требования:
Приложение будет состоять из двух модулей: основной (main.cpp) и
модуль визуализации (drawingFunctions.cpp/h). Функции визуализации
предоставляются в готовом виде и аналогичны описанным в разделе 4.1.2.
В основном модуле должна выполняться следующая последовательность действий:
Код, необходимый для загрузки и визуализации предоставляется в готовом виде, следовательно, требуется лишь написать вызов функции кластеризации kmeans.
После того, как код основного модуля будет дописан, предлагается запустить программу на предоставленных наборах данных (dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml) и проанализировать полученные результаты.
Презентацию к лабораторной работе Вы можете скачать здесь.
Дополнительные материалы к лабораторной работе Вы можете скачать здесь.
В настоящее время алгоритмы машинного обучения находят широкое применение в системах различного назначения: поисковых системах, алгоритмах распознавания, анализа и синтеза речи, медицинской диагностике, биоинформатике, финансовом прогнозировании и т.д. Исключением не является и компьютерное зрение. Например, подавляющее большинство современных систем детектирования объектов на изображениях и видео основано на применении алгоритмов машинного обучения. Такой подход позволяет компьютерной системе самой "научиться" отличать изображения, содержащие искомый объект, от остальных, используя для этого лишь примеры таких изображений. Также кластеризация и обучение с учителем успешно применяется в алгоритмах классификации изображений, опять же позволяя автоматически установить неявные различия между изображениями различных типов.
В настоящей работе рассматриваются некоторые алгоритмы обучения с учителем и кластеризации, реализованные в открытой библиотеке компьютерного зрения OpenCV [6].
Цель данной работы – изучить некоторые алгоритмы обучения с учителем и без с использованием соответствующих функций библиотеки компьютерного зрения OpenCV.
Данная цель предполагает решение следующих задач:
В работе приводится краткое описание некоторых алгоритмов классификации и кластеризации. Приводятся и описываются интерфейсы структур и классов, прототипы функций библиотеки OpenCV, реализующих рассматриваемые алгоритмы. Предлагаются примеры программ, демонстрирующие использование данных классов и функций. Приводятся результаты работы алгоритмов на модельных задачах. Разрабатывается структура приложений, для решения задач классификации и кластеризации.
Вычислительные эксперименты проводились с использованием следующей инфраструктуры (табл. 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 |
Для выполнения данной лабораторной работы требуется:
При выполнении данной лабораторной работы рекомендуется следующая последовательность действий:
Одной из задач, изучаемой в машинном обучении, является задача обучения с учителем, которая заключается в как можно более точном восстановлении (определении) вида зависимости $$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]. Каждый алгоритм обладает своими достоинствами и недостатками. Различные алгоритмы имеют свои ограничения применимости, например, могут быть предназначены только для задач классификации/восстановления регрессии, могут допускать работу лишь с количественными признаками, могут не поддерживать наличие пропущенных значений, т.е. неизвестных значений некоторых признаков у какого-либо прецедента. Также, тип функции, получаемой на выходе определенного алгоритма обучения, может лучше/хуже подходить для аппроксимации искомой зависимости. Таким образом, конкретный алгоритм обучения с учителем должен выбираться, исходя из специфики конкретной рассматриваемой задачи. Данная лабораторная работа посвящена описанию использования реализаций следующих алгоритмов: машина опорных векторов, дерево решений, случайный лес и градиентный бустинг деревьев решений.
Пусть $$\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'$$ ; 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 реализован подход "каждый против
каждого". Таким образом, при решении задачи разделения точек классов
Данный параметр может быть полезен при несбалансированной
обучающей выборке, содержащей малое количество прецедентов одного
класса и большое количество другого. Чем больше вес, тем больший
штраф назначается за неправильную классификацию обучающего
прецедента этого класса. По умолчанию (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) Точки обучающей выборки и граница, построенная с помощью машины опорных векторов с радиальным ядром
Идея, лежащая в основе алгоритма обучения дерева решений, состоит в рекурсивном разбиении пространства признаков с помощью простых правил на непересекающиеся области. Полученная в результате обучения модель представляет собой дерево, где каждому внутреннему узлу соответствует некоторая область пространства признаков и правило ее разбиения на подобласти ($$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) Точки обучающей выборки и разбиение пространства признаков деревом решений
Случайный лес является бэггинг алгоритмом, основная идея которого заключается в построении как можно менее зависимых друг от друга деревьев решений, с последующим принятием решения путем усреднения в случае регрессии и голосованием в случае классификации. Зависимость между деревьями снижается за счет использования случайной выборки с возвращением из исходных прецедентов и случайного подмножества признаков.
Алгоритм случайного леса реализован в библиотеке 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) Точки обучающей выборки и разбиение пространства признаков случайным лесом
Градиентный бустинг, как и любой бустинг алгоритм, последовательно строит базовые модели так, что каждая следующая улучшает качество всего ансамбля. Градиентный бустинг деревьев решений строит модель в виде суммы деревьев $$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) Точки обучающей выборки и разбиение пространства признаков с помощью модели градиентного бустинга
Задача кластеризации заключается в разбиении выборки $$\lbrace x^{(i)},i=\overline{1,N} \rbrace$$ на непересекающиеся подмножества таким образом, чтобы схожие точки (обычно близкие в некоторой метрике) попали в одно подмножество (кластер), а точки из разных кластеров сильно друг от друга отличались (были далеки). Для решения данной задачи в библиотеки OpenCV реализован метод центров тяжести (k-means) и EM-алгоритм. В данной лабораторной работе рассматривается использование программной реализации метод центров тяжести, как одного из наиболее популярных алгоритмов кластеризации на настоящий момент.
Метод центров тяжести разбивает выборку на заданное количество
кластеров 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)
В рамках данной лабораторной работы предлагается разработать приложение для решения задач классификации, предусматривающее возможность применения рассмотренных выше алгоритмов машинного обучения. К приложению предъявляются следующие требования:
Приложение будет состоять из набора модулей (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. А также проанализировать, на каких данных лучше/хуже работает тот или иной подход и какое влияние на конечную модель оказывают параметры алгоритма обучения.
Также в рамках данной лабораторной работы предлагается разработать приложение для кластеризации точек методом центров тяжести. К приложению предъявляются следующие требования:
Приложение будет состоять из двух модулей: основной (main.cpp) и
модуль визуализации (drawingFunctions.cpp/h). Функции визуализации
предоставляются в готовом виде и аналогичны описанным в разделе 4.1.2.
В основном модуле должна выполняться следующая последовательность действий:
Код, необходимый для загрузки и визуализации предоставляется в готовом виде, следовательно, требуется лишь написать вызов функции кластеризации kmeans.
После того, как код основного модуля будет дописан, предлагается запустить программу на предоставленных наборах данных (dataset1.yml, dataset2.yml, dataset3.yml, dataset4.yml) и проанализировать полученные результаты.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.