Полигон алгоритмов/Пошаговая реализация собственного алгоритма
Материал из MachineLearning.
м (→Написание кода самого алгоритма) |
м (→Написание кода самого алгоритма) |
||
Строка 368: | Строка 368: | ||
::://определяем число объектов на классификацию | ::://определяем число объектов на классификацию | ||
:::int objectsCount = indexes.Length; | :::int objectsCount = indexes.Length; | ||
- | |||
::://определяем число признаков | ::://определяем число признаков | ||
:::int propertyCount = data.PropertiesDescription.Length - 1; | :::int propertyCount = data.PropertiesDescription.Length - 1; | ||
- | |||
::://определяем число классов | ::://определяем число классов | ||
:::int classCount = data.PropertiesDescription[propertyCount].Values.Length; | :::int classCount = data.PropertiesDescription[propertyCount].Values.Length; | ||
- | |||
:::var testResult = new TestResult | :::var testResult = new TestResult | ||
Строка 396: | Строка 393: | ||
:::return testResult; | :::return testResult; | ||
::} | ::} | ||
- | |||
</code> | </code> | ||
Текущая версия
В данной инструкции описывается реализация программы, отвечающей за проведение тестирования в системе Полигон алгоритма, реализованного пользователем. Взаимодействие с системой происходит при помощи веб-сервиса, к которому обращается программа. Алгоритм должен периодически запрашивать новые задания на тестирование, каждое из которых содержит несколько тестов (т.е. несколько обучающих и контрольных подвыборок данных задачи). При получении задания алгоритм должен рассчитать его и сохранить полученные результаты через веб-сервис. Подробнее о взаимодействии системы Полигон с пользовательскими алгоритмами и описание функций веб-сервиса можно посмотреть здесь.
Далее описывается написание программы алгоритма на 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…”
c. В появившемся окне в левом нижнем углу следует нажать на кнопку “Add Web Reference…”
d. Необходимо ввести URL-адрес веб-сервиса: http://poligon.machinelearning.ru/ProcessingService.asmx. Затем нажать на кнопку “Go”
e. Нужно дать подходящее название новому Web Reference
f. В этом же окне нужно нажать на кнопку "Add Reference"
В проекте появляется новый 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. Далее рассматривается схема получения и обработки одного задания алгоритмом со вставками кода:
- запрос задания для алгоритма
ProcessingTask procTask = procService.GetTask(algSynonim, algPassword);
- если задание получено и не было ошибки, то запрос данных задачи
ProblemData data = procService.GetProblem(algSynonim, algPassword, procTask.ProblemSynonim);
- тестирование алгоритма на полученном задании (подробнее в пункте Логика тестирования алгоритма на задании)
- В результате тестирования возвращаются наборы результатов теста на обучении и на контроле:
//Определение числа тестов
- int learnTaskCount = procTask.LearnIndexes.Length;
- //создание наборов результатов тестов
- var learnResults = new List<TestResult>(learnTaskCount);
- var testResults = new List<TestResult>(learnTaskCount);
- регистрация результатов
- 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;
- }
- 6. Возвращается результат:
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.
Для расчета созданного отчета написанную программу необходимо запускать на обрабатывающем сервисе. После того, как алгоритм вернет результаты всех заданий, связанных с некоторой ячейкой отчета (тестирование алгоритма на одной задаче при заданных параметрах), произойдет расчет статистик, и в этой ячейке отчета отобразятся результаты тестирования.