Новосибирский Государственный Университет
Опубликован: 20.08.2013 | Доступ: свободный | Студентов: 861 / 36 | Длительность: 14:11:00
Самостоятельная работа 3:

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

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

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

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

Для обучения случайного леса предназначен метод train:

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

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

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

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

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

Для осуществления предсказаний в классе CvRTrees предназначен метод predict:

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

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

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

Приведем пример использования класса CvRTrees для решения задачи бинарной классификации и иллюстрацию порождаемого обученной моделью разбиения пространства признаков (см. рис. 8.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; 
}   
    
Точки обучающей выборки и разбиение пространства признаков случайным лесом

Рис. 8.3. Точки обучающей выборки и разбиение пространства признаков случайным лесом
Александра Максимова
Александра Максимова

При прохождении теста 1 в нем оказались вопросы, который во-первых в 1 лекции не рассматривались, во-вторых, оказалось, что вопрос был рассмаотрен в самостоятельно работе №2. Это значит, что их нужно выполнить перед прохождением теста? или это ошибка?
 

Алена Борисова
Алена Борисова

В лекции по обработке полутоновых изображений (http://www.intuit.ru/studies/courses/10621/1105/lecture/17979?page=2) увидела следующий фильтр:


    \begin{array}{|c|c|c|}
    \hline \\
    0 & 0 & 0 \\
    \hline \\
    0 & 2 & 0 \\
    \hline \\
    0 & 0 & 0 \\
    \hline 
    \end{array} - \frac{1}{9} \begin{array}{|c|c|c|}
    \hline \\
    0 & 0 & 0 \\
    \hline \\
    0 & 1 & 0 \\
    \hline \\
    0 & 0 & 0 \\
    \hline 
    \end{array}

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

Что вижу я в конструкции фильтра (скорее всего ошибочно): F(x, y) = 2 * I(x, y) - 1/9 I(x, y) = 17/9 * I(x, y), где F(x, y) - яркость отфильтрованного пикселя, а I(x, y) - яркость исходного пикселя с координатами (x, y). Что означает обычное повышение яркости изображения, при этом без учета соседних пикселей (так как их множители равны 0).

Объясните, пожалуйста, как данный фильтр может повышать четкость изображения?