Полигон алгоритмов/Пошаговая реализация собственного алгоритма

Материал из MachineLearning.

Перейти к: навигация, поиск

Содержание

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

Далее описывается написание программы алгоритма на C# в Microsoft Visual Studio 2008. Соответствующую реализованную программу можно скачать по адресу http://poligon.machinelearning.ru/files/ExampleAlg.rar

Скачать данную инструкцию в формате pdf можно здесь

Создайте новый проект

Создайте новый проект типа ConsoleApplication. Дайте ему название. В нашем примере используется название TestServiceAlgorithm.

Добавьте в проект Web Reference на обрабатывающий сервис системы

a. Правой кнопкой нажмите на название проекта в Solution Explorer, выберете пункт Add Service Reference

b. В появившемся окне в левом нижнем углу нажмите на кнопку “Advanced…”

Изображение:AddWebReferenceStep1.png

c. В появившемся окне в левом нижнем углу нажмите на кнопку “Add Web Reference…”

Изображение:AddWebReferenceStep2.png

d. Введите URL-адрес веб-сервиса: http://poligon.machinelearning.ru/ProcessingService.asmx. Нажмите на кнопку “Go”

Изображение:AddWebReferenceStep3.png

e. Назовите новый Web Reference подходящим образом

Изображение:AddWebReferenceStep4.png

f.Нажмите на кнопку "Add Reference"

Изображение:AddWebReferenceStep5.png

В нашем проекте появляется новый namespace <название проекта>.<объявленное название web reference> (в нашем случае TestServiceAlgorithm.ProcessService), содержащий все типы и функции веб-сервиса. Включите данный namespace во все используемые файлы кода (Program.cs):

using TestServiceAlgorithm.ProcessService;

Для обращения к функциям сервиса создайте экземпляр класса ProcessingService, описание которого находится в подключенном namespace:

var procService = new ProcessingService();

Добавьте Web Reference на тестовый сервис системы

Аналогичным образом добавьте в проект тестовый веб-сервис, который расположен по адресу http://poligon.machinelearning.ru/TestService.asmx. В данном примере этот web reference называется TestService. Тогда для использования функций тестового сервиса вместо обрабатывающего нужно подключать namespace, соответствующий тестовому сервису:

using TestServiceAlgorithm.TestService;

И для обращения к функциям тестового сервиса нужно создать экземпляр класса TestService, находящийся в данном namespace:

var procService = new TestService();

При настройке взаимодействия и отладке работы алгоритма следует обращаться именно к тестовому сервису.


Далее считаем, что переменная procService соответствует нужному нам сервису.

Напишите логику работы с сервисом

Данный код следует писать в функции Main файла Program.cs.

a. Поскольку при вызове любой функции сервиса требуются параметры авторизации, введите переменные, отвечающие за эти данные.

const string algSynonim = "algSynonim";
const string algPassword = "algPassword";

b. Рассмотрим схему получения и обработки одного задания алгоритмом со вставками кода:

  1. запрос задания для алгоритма
    ProcessingTask procTask = procService.GetTask(algSynonim, algPassword);
  2. если задание получено и не было ошибки, то запрос данных задачи
    ProblemData data = procService.GetProblem(algSynonim, algPassword, procTask.ProblemSynonim);
  3. тестирование алгоритма на полученном задании (подробнее в пункте Логика тестирования алгоритма на задании)
    В результате тестирования возвращаются наборы результатов теста на обучении и на контроле:
    //Определение числа тестов
    int learnTaskCount = procTask.LearnIndexes.Length;
    //создание наборов результатов тестов
    var learnResults = new List<TestResult>(learnTaskCount);
    var testResults = new List<TestResult>(learnTaskCount);
  4. регистрация результатов
    ProcessingState state = procService.RegisterResult
    (algSynonim, algPassword, procTask.PocketId, learnResults.ToArray(), testResults.ToArray());

Полный код простой логики запроса и обработки одного задания:

//Запрос задания для алгоритма
ProcessingTask procTask = procService.GetTask(algSynonim, algPassword);


if (procTask != null)
{
//Проверка на ошибку при получении задания
if (procTask.ProcessingState.Status == StatusType.Error)
throw new Exception("Задание возвращено с ошибкой: " + procTask.ProcessingState.Message);


//Если задание есть и не было ошибки, надо получить данные по задаче
ProblemData data = procService.GetProblem(algSynonim, algPassword,procTask.ProblemSynonim);


//Проверка на ошибку при получении данных задачи
if (data.ProcessingState.Status == StatusType.Error)
throw new Exception("Данные задачи возвращены с ошибкой: " + procTask.ProcessingState.Message);


//Определение числа тестов
int learnTaskCount = procTask.LearnIndexes.Length;


//создание наборов результатов тестов
var learnResults = new List<TestResult>(learnTaskCount);
var testResults = new List<TestResult>(learnTaskCount);


/*
Код обработки задания (заполнение списков learnResults и testResults)
*/


//Регистрация результатов
ProcessingState state = procService.RegisterResult(algSynonim, algPassword, procTask.PocketId, learnResults.ToArray(), testResults.ToArray());


//Проверка на ошибки при сохранении результатов:
if (state.Status == StatusType.Warning)
throw new Exception("При сохранении результата" + procTask.PocketId + " было выдано предупреждение: " + state.Message);
if (state.Status == StatusType.Error)
throw new Exception("Произошла ошибка при сохранении результата" + procTask.PocketId + ": " + state.Message);
}


c. После каждого вызова функций веб-сервиса стоит проверять возвращенный статус (структура ProcessingState) на произошедшие ошибки или выданные сервером предупреждения.

Например:

//вызов функции получения задачи с сервиса
ProblemData data = procService.GetProblem(algSynonim, algPassword, procTask.ProblemSynonim);


//Проверка на ошибку при получении данных задачи
if (data.ProcessingState.Status == StatusType.Error)
throw new Exception("Данные задачи возвращены с ошибкой: " + procTask.ProcessingState.Message);

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

Нужно определить действия программы в случае возникновения ошибки – сгенерировать исключение (throw new Exception), записать ошибку в файл или выдать сообщение пользователю.


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

for (int counter = 0; counter < 1000; counter++)
{
/*
Запрос и последующая обработка одного задания (см.выше)
*/
Thread.Sleep((procTask != null) ? 500 : 60000);
}


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


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

Для этого нужно:

Перед объявлением цикла ввести переменную, в которой будут храниться данные последней полученной задачи, и переменную с синонимом последней полученной задачи:

ProblemData lastData = null;
string lastProblemSyn = "";

Вместо прежнего кода запроса данных задачи вставить новый:

if (lastProblemSyn != procTask.ProblemSynonim)
{
lastData = procService.GetProblem(algSynonim, algPassword, procTask.ProblemSynonim);
/* проверка ошибок */
lastProblemSyn = procTask.ProblemSynonim;
}
ProblemData data = lastData;

Напишите логику тестирования алгоритма на задании

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


Количество тестов определено длиной массива LearnIndexes в задании, она должна совпадать с длиной массива TestIndexes. Определите количество тестов:

int learnTaskCount = procTask.LearnIndexes.Length;


Создайте списки для сохранения результатов тестов:

var learnResults = new List<TestResult>(learnTaskCount);
var testResults = new List<TestResult>(learnTaskCount);

Результатом теста является объект типа TestResult. Данный класс объявлен в namespace веб-сервиса.


Следующая часть кода пишется в область «Код обработки задания» перед сохранением результатов тестирования (см. пример полного кода в пункте Логика работы с сервисом).

В цикле for (var i = 0; i < learnTaskCount; i++) напишите поэтапное прохождение тестов:

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

var alg = new Algorithm(data, procTask.LearnIndexes[i], procTask.AlgParamNames, procTask.AlgParamValues, procTask.AlgParamUsages);

В данном случае обучение алгоритма происходит в конструкторе класса. Он требует данные задачи, индексы объектов обучения, информацию о параметрах алгоритма. Именно у созданного (обученного) экземпляра класса Algorithm будет вызываться функция расчета результатов классификации. Разбор параметров алгоритма, переданных в задании, можно обрабатывать при создании алгоритма, как в данном случае, либо сразу после получения задания и конструктору передавать отдельную структуру с параметрами.

Подробнее код класса Algorithm будет рассмотрен ниже (пункт Написание кода самого алгоритма).


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

TestResult learnTestRes = alg.GetTestResult(data, procTask.LearnIndexes[i]);
TestResult testTestRes = alg.GetTestResult(data, procTask.TestIndexes[i]);

Для отслеживания корректности записанных результатов, нужно сохранять порядковый индекс теста:

learnTestRes.Index = i;
testTestRes.Index = i;

Сохраните результаты тестов в созданных массивах результатов:

learnResults.Add(learnTestRes);
testResults.Add(testTestRes);

Полный пример кода обработки (проведения тестирования) одного задания:

//Определение числа тестов
int learnTaskCount = procTask.LearnIndexes.Length;


//создание наборов результатов тестов
var learnResults = new List<TestResult>(learnTaskCount);
var testResults = new List<TestResult>(learnTaskCount);
for (var i = 0; i < learnTaskCount; i++)
{
// обучение алгоритма
var alg = new Algorithm(data, procTask.LearnIndexes[i], procTask.AlgParamNames, procTask.AlgParamValues, procTask.AlgParamUsages);


//Получаем результат классификации на объектах обучения
var learnTestRes = alg.GetTestResult(data, procTask.LearnIndexes[i]);
learnTestRes.Index = i;
learnResults.Add(learnTestRes);
//Получаем результат классификации на объектах контроля
var testTestRes = alg.GetTestResult(data, procTask.TestIndexes[i]);
testTestRes.Index = i;
testResults.Add(testTestRes);
}

Напишите код самого алгоритма

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

a. Если у алгоритма есть параметры, то опишите их.

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

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

private int _seed;

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

public Algorithm(ProblemData data, int[] indexes, string[] paramNames, string[] paramValues, bool[] paramUsages)
{
/* Разбор переданных параметров */


// имитация обучения
_seed = 0;
foreach (int i in indexes)
_seed += i;
}

c. Добавьте возможность рассчитать классификацию для определенной выборки, т.е. по данным задачи и индексам объектов в выборке вернуть TestResult.

Определите данную функцию:

public TestResult GetTestResult(ProblemData data, int[] indexes)

В теле функции:

1. Определите число объектов на классификацию по длине массива индексов объектов:
int objectsCount = indexes.Length;
2. Определите число признаков задачи по описанию признаков PropertiesDescription в данных задачи
int propertyCount = data.PropertiesDescription.Length - 1;
3. Определите число классов задачи (описание классов, как целевого признака, записано в последней строке PropertiesDescription данных задачи)
int classCount = data.PropertiesDescription[propertyCount].Values.Length;
4. Создайте структуру для возвращения результатов теста. В ней обязательно должна быть определена матрицу оценок ProbabilityMatrix. В данную матрицу на этапе работы алгоритма следует проставить оценки отнесения i-го объекта классификации (первый индекс) к j-му классу (второй индекс). Создание вектора ответов – необязательно, но если он присутствует, то должен быть согласован с матрицей оценок (т.е. ответ алгоритма на объекте должен соответствовать максимальной оценке в матрице оценок на данном объекте). Веса объектов и признаков должны присутствовать только для тестов на обучении, поэтому их следует создавать и сохранять на этапе обучения алгоритма.

var testResult = new TestResult
{
Answers = new int[objectsCount],
ObjectsWeights = null,
ProbabilityMatrix = new double[objectsCount][],
PropertiesWeights = null
};

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

try
{
/*Работа алгоритма*/
}
catch (Exception exp)
{
testResult.Error = true;
testResult.ErrorException = exp.Message;
}
Возвращаем результат:
return testResult;


Пример полного кода функции расчета результатов классификации на выборке:

public TestResult GetTestResult(ProblemData data, int[] indexes)
{
//определяем число объектов на классификацию
int objectsCount = indexes.Length;


//определяем число признаков
int propertyCount = data.PropertiesDescription.Length - 1;


//определяем число классов
int classCount = data.PropertiesDescription[propertyCount].Values.Length;


var testResult = new TestResult
{
Answers = new int[objectsCount],
ObjectsWeights = null,
ProbabilityMatrix = new double[objectsCount][],
PropertiesWeights = null
};
try
{
/*Работа алгоритма*/
}
catch (Exception exp)
{
testResult.Error = true;
testResult.ErrorException = exp.Message;
}
return testResult;
}

Протестируйте взаимодействие алгоритма с системой, работу алгоритма

Запустите программу алгоритма в режиме связи с тестовым сервисом (т.е. подключен namespace TestServiceAlgorithm.TestService и procService - экземпляр TestService). Тогда авторизация алгоритма при запросах не проверяется.

При запросе задания алгоритму всегда возвращается стандартное тестовое задание: в качестве параметров алгоритма возвращается null, ProblemSynonim равно Iris, указаны индексы разбиений в массивах LearnIndexes и TestIndexes.

При запросе задачи всегда возвращаются данные по задаче Iris.

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

При проверке данных на корректность в первую очередь проверяется соответствие числа тестов в массивах с заявленным в задании. Далее для всех тестов по очереди проверяется, что:

a. лежит непустой (не null) указатель на тест
b. индекс теста (поле Index) совпадает с порядковым номером теста в массивах learnResults и testResults.
c. существует обязательная матрица оценок
d. числа объектов в матрице оценок и векторе ответов совпадает с заявленным в задании числом объектов на данном тесте
e. длины вектора весов объектов, если он есть, равна числу объектов на данном тесте. Данный вектор может быть только в тесте на обучающей выборке
f. длина вектора весов признаков, если он есть, равна числу признаков задачи. Данный вектор может быть только в тесте на обучающей выборке
g. в каждой строчке матрицы оценок лежит непустой указатель
h. в каждой строчке матрицы оценок записано элементов, равное числу классов
i. все значения в матрице оценок неотрицательны
j. если есть вектор классификаций, то ответ на всех объектах определяет некоторый класс (т.е. записанное число лежит в пределах от 0 до <число классов – 1>) и соответствует максимальной оценке в матрице оценок.

Если в каком-либо тесте была ошибка (Error = true), то данные этого теста на корректность не проверяются, но будет выдано предупреждение, что в расчетах алгоритма произошли ошибки (в случае, если нет других ошибок).

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

Зарегистрируйте алгоритм на сайте

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

Проверьте работоспособность алгоритма на сайте

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

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