Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Объем работы: 123 страницы, 9 иллюстраций, 4 приложения, 1 таблица
Ключевые слова: алгоритм классификации, анализ данных, data mining, машинное обучение
Дипломная работа посвящена методике разработки программного продукта для поиска причин в изменениях трендов в данных. Рассмотрено создание системы предобработки данных и разработка системы классификации на базе различных алгоритмов машинного обучения. В работе определяется область применения разработанной программы. Для разработки системы предобработки данных использован язык программирования C#, Фреймворк .Net, а также СУБД MS SQL Server 2008R2. Для разработки системы классификации использован язык программирования Python и библиотека Scikit-Learn. Для системы классификации была использована стратегия One-vs-Rest для алгоритмов классификации Машина Опорных Векторов (с различными ядрами) и Наивный Байесовский Классификатор. Разработка велась под операционной системой Windows 7. Осуществлены эксперименты по проверке точности объяснений, даваемых системой на тестовых данных.
[1] Реферат [2] Оглавление [3] 1. Введение [3.1] 1.1 Постановка задачи [4] 2. Методы машинного обучения и алгоритмы машинного обучения [4.1] 2.1 Задача классификации [4.2] 2.2 Multiclass классификация [4.2.1] 2.2.1 Стратегия One-vs.-rest [4.3] 2.3 Multi-label классификация [4.4] 2.4 Выводы [5] 3. Методы и алгоритмы, реализованные в программной системе [5.1] 3.1 TF-IDF [5.2] 3.2 Наивный Байесовский Классификатор [5.3] 3.3 SVM [5.3.1] 3.3.1 Стохастический Градиентный Спуск [5.4] 3.4 Выводы [6] 4. Реализация системы [6.1] 4.1 Предобработка информации [6.1.1] 4.1.1 Инструкция пользователя [6.2] 4.2 Система классификации [6.2.1] 4.2.1 Рабочий режим [6.2.2] 4.2.2 Тестовый режим [6.2.3] 4.2.3 Инструкция пользователя [6.2.3.1] 4.2.3.1 Тестовый режим [6.2.3.2] 4.2.3.2 Рабочий режим [6.3] 4.3 Выводы [7] 5. Машинный эксперимент [7.1] 5.1 Выводы [8] 6. Заключение [9] Список использованных ресурсов [10] Приложение 1. Система предобработки информации. [10.1] Program.cs [10.2] FactMiner.cs [10.3] Logger.cs [11] Приложение 2. Файл конфигурации системы предобработки информации [12] Приложение 3. Хранимые процедуры и табличные представления базы данных FactEventAnalysisDB [12.1] usp_populateCoraxFactsTable [12.2] usp_clearFactsTables [12.3] SpikeFactsHypothesis [12.4] PriceFactsHypothesis [13] Приложение 4. Система классификации |
В современном мире особенно сложной является задача по поиску причины в изменении поведения комплексной системы. Например:
- Что послужило причиной скачка энергопотребления города?
- Почему резко выросла заболеваемость гриппом в определенной стране?
- По какой причине цена некой ценной бумаги резко упала в конкретный день?
Одной из самых сложных проблем при выявлении и разработке законов развития комплексных систем и принятия решений является формирование понимания внутренних связей и механизмов тех или иных процессов в системах. Как правило, этих связей и механизмов много, разные элементы системы могут быть связаны между собой самыми разными прямыми и обратными связями, при этом каждый элемент так или иначе связан с функциями системы и очень часто с вредными или нежелательными эффектами. Учитывать и использовать все эти связи в работе очень сложно.
В самом деле, в любом из упомянутых выше случаев на динамику системы (уровень энергопотребления, заболеваемость, цена ценной бумаги) могут влиять сотни тысяч различных событий. Выявить какое из событий вызвало изменение в динамике чрезвычайно сложно. Согласно исследованиям психологов, нормальный человек способен удерживать в зоне своего внимания 7 ± 2 объекта. А в реальных системах объектов и связей во много раз больше. В результате при попытке все это «охватить в уме» внимание нарушается, случайным образом «прыгает» от объекта к объекту, преувеличивает одни и преуменьшает или просто пропускает другие элементы и связи и т.п. Цельную и объективную картину увидеть не удается.
Например, ниже представлен график цен некой нефтедобывающей компании по месяцам.
Рис.1. Пример слома тренда
На данном графике явно виден рост цены акций данной компании вплоть до 6.1.2008, который сменился ее стабильным спадом после этого момента времени. Опытный специалист в анализе ценных бумаг может проверить события, которые произошли 6.1.2008 и выяснить, что в этот день был опубликован отчет о перспективах китайской экономики, который был негативным. Цена акций нефтедобывающих компаний сильно зависит от экономики Китая, ибо данная страна является крупнейшим в мире потребителем нефти, и проблемы в ее экономике могут сильно снизить доходы нефтедобывающих компаний.
Однако для выполнения анализа, который позволит найти объяснение произошедшему изменению в динамике цены ценной бумаги требуются обширные знания в предметной области, хорошо разбираться в характеристиках компаний, а также быть в состоянии изучить колоссальные объемы данных ибо в тот же день, 6.1.2008, в мире произошли миллионы событий, каждое из которых могло повлиять на динамику цены.
Очевидно, одним из способов помочь эксперту в определении причин изменения динамики системы будет решение задачи по сокращению списка событий, связанных с системой, которые эксперту нужно рассмотреть. Вместо десятков тысяч событий эксперт сможет сосредоточиться на рассмотрении лишь их небольшого подмножества и применив свою компетенцию в предметной области сможет ответить на вопрос, что же вызвало изменение в динамике системы. Именно данная задача выделения наиболее вероятных объяснений причин некоего изменения и будет решена в данной работе.
В первой главе производится постановка задачи, ее более подробное описание а также описание исходных данных.
Во второй главе приводится описание алгоритмов и методов, примененных при разработке данной системы.
В третей главе описывается реализация системы с помощью языков программирования T-SQL, C# и Python, а также библиотеки scikit-learn.
В четвертой главе приводится описание методов оценки качества работы полученной системы и делаются выводы о качестве работы системы на тестовых данных.
Решить задачу по идентификации наиболее интересных для рассмотрения событий может помочь Интеллектуальный Анализ Данных.
Интеллектуальный анализ данных (также известный как Data Mining) - собирательное название, используемое для обозначения совокупности методов обнаружения в данных ранее неизвестных, нетривиальных, практически полезных и доступных интерпретации знаний, необходимых для принятия решений в различных сферах человеческой деятельности. Данный термин был введен в обиход Григорием Пятецким-Шапиро в 1989 году.
Основу методов Data Mining составляют всевозможные методы классификации, моделирования и прогнозирования, основанные на применении деревьев решений, искусственных нейронных сетей, генетических алгоритмов, эволюционного программирования, ассоциативной памяти, нечёткой логики. К методам Data Mining нередко относят статистические методы (дескриптивный анализ, корреляционный и регрессионный анализ, факторный анализ, дисперсионный анализ, компонентный анализ, дискриминантный анализ, анализ временных рядов, анализ выживаемости, анализ связей). Такие методы, однако, предполагают некоторые априорные представления об анализируемых данных, что несколько расходится с целями Data Mining (обнаружение ранее неизвестных нетривиальных и практически полезных знаний). Одними из важнейших инструментов, позволяющих решать задачи Data Mining являются методы Машинного Обучения.
Машинное обучение или Machine Learning обширный подраздел искусственного интеллекта, изучающий методы построения моделей, способных обучаться, и алгоритмов для их построения и обучения. Различают два типа обучения. Обучение по прецедентам, или индуктивное обучение, основано на выявлении закономерностей в эмпирических данных. Дедуктивное обучение предполагает формализацию знаний экспертов и их перенос в компьютер в виде базы знаний. Дедуктивное обучение принято относить к области экспертных систем, поэтому термины машинное обучение и обучение по прецедентам можно считать синонимами.
Машинное обучение находится на стыке математической статистики, методов оптимизации и дискретной математики, но имеет также и собственную специфику, связанную с проблемами вычислительной эффективности и переобучения. Многие методы индуктивного обучения разрабатывались как альтернатива классическим статистическим подходам.
Задачу машинного обучения можно описать следующим образом. Имеется множество объектов (ситуаций) и множество возможных ответов (откликов, реакций). Существует некоторая зависимость между ответами и объектами, но она неизвестна. Известна только конечная совокупность прецедентов пар «объект, ответ», называемая обучающей выборкой. На основе этих данных требуется восстановить зависимость, то есть построить алгоритм, способный для любого объекта выдать достаточно точный ответ. Для измерения точности ответов определённым образом вводится функционал качества.
Данная постановка является обобщением классических задач аппроксимации функций. В классических задачах аппроксимации объектами являются действительные числа или векторы. В реальных прикладных задачах входные данные об объектах могут быть неполными, неточными, нечисловыми, разнородными. Эти особенности приводят к большому разнообразию методов машинного обучения. Раздел машинного обучения, с одной стороны, образовался в результате разделения науки о нейросетях на методы обучения сетей и виды топологий архитектуры сетей, а с другой, вобрал в себя методы математической статистики. Подобное разнообразие позволяет нам подобрать инструмент для решения озвученной задачи по поиску событий, вызвавших изменения в динамике систем.
Итак, имеется набор данных о ценах на ценные бумаги и о корпоративных событиях (таких как выплата дивидендов, изменения рейтинга компаний, собраниях акционеров) которые хранятся в базе данных MS SQL Server.
Задача делится на три части:
С учетом этого описания и анализа имеющейся литературы в данной области определим направления работы:
Система должна быть способна к масштабируемости, а ее архитектура должна быть легко изменяемой. Легче всего этого добиться использую библиотеку scikit-learn, которая предоставляет большое количество различных модулей, которые можно легко использовать в других системах.
Задачу, стоящую перед нами, можно отнести к задаче классификации, где в ответ на набор значений параметров (страна, тип бумаг, тип изменения в динамике) система должна возвращать несколько меток классов, которые описывают событие, которое предположительно вызвало данное изменение в динамике. Ниже проводится информация о задаче классификации и ее подвидах.
Задача классификации формализованная задача, в которой имеется множество объектов (ситуаций), разделённых некоторым образом на классы. Задано конечное множество объектов, для которых известно, к каким классам они относятся. Это множество называется выборкой. Классовая принадлежность остальных объектов неизвестна. Требуется построить алгоритм, способный классифицировать (см. ниже) произвольный объект из исходного множества.
Классифицировать объект значит, указать номер (или наименование) класса, к которому относится данный объект.
Классификация объекта номер или наименование класса, выдаваемый алгоритмом классификации в результате его применения к данному конкретному объекту.
В машинном обучении задача классификации решается, как правило, с помощью методов искусственных нейронных сетей при постановке эксперимента в виде обучения с учителем.
Математически данную задачу можно описать следующим образом:
Пусть множество описаний объектов, множество номеров (или наименований) классов. Существует неизвестная целевая зависимость отображение , значения которой известны только на объектах конечной обучающей выборки . Требуется построить алгоритм , способный классифицировать произвольный объект .
Более общей считается вероятностная постановка задачи. Предполагается, что множество пар «объект, класс» является вероятностным пространством с неизвестной вероятностной мерой . Имеется конечная обучающая выборка наблюдений , сгенерированная согласно вероятностной мере . Требуется построить алгоритм , способный классифицировать произвольный объект .
Признаком называется отображение , где множество допустимых значений признака. Если заданы признаки ,то вектор называется признаковым описанием объекта . Признаковые описания допустимо отождествлять с самими объектами. При этом множество называют признаковым пространством.
В зависимости от множества признаки делятся на следующие типы:
Часто встречаются прикладные задачи с разнотипными признаками, для их решения походят далеко не все методы.
Классической задачей классификации считается двухклассовая классификация, то есть задача определения принадлежит ли объект лишь одному из двух классов, которая служит основой для решения более сложных задач.
Multiclass classification (многоклассовая классификация) частный случай задачи классификации при которой требуется классифицировать объекты в более чем один из двух классов. Когда число классов достигает многих тысяч задача существенно вырастает в сложности.
В то время как некоторые алгоритмы классификации по определению допускают использования нескольких классов другие могут не иметь такой возможности, в то же время они могут быть использованы в задачах многоклассовой классификации с помощью различных стратегий. Классической считается стратегия One-vs.-rest (один-против-всех, OvA или OvR).
Стратегия One-vs.-rest включает в себя тренировку одного классификатора для каждого класса, при котором мы считаем примеры с нужным классом позитивными примерами, а все остальные примеры негативными. Стратегия требует чтобы базовые классификаторы возвращали меру уверенности (confidence score) своего решения, а не просто метку класса. Дискретные метки класса могут привести к двусмысленности, так как несколько классов могут быть предсказаны для одного примера.
Примерное описание алгоритма для стратегии OvA, которая использует бинарный классификатор L представлено ниже:
Вход:
Выход:
Процесс:
Для принятия решения необходимо применить все классификаторы к новому примеру и назначить ему метку для которой соответствующий классификатор демонстрирует наибольшую меру уверенности:
Данная стратегия является чрезвычайно популярной но во многом является эвристикой, которая страдает от нескольких проблем. Во-первых, масштаб меры уверенности может значительно отличаться между различными бинарными классификаторами. Во-вторых, даже если распределение различных классов сбалансированно во всем тренировочном наборе данных, алгоритмы обучения бинарных классификаторов наблюдают несбалансированное распределение, так как, как правило, количество отрицательных примеров намного превышает количество позитивных примеров. В процессе изучения данного класса задач были созданы методы для решения задачи Multi-label классификации, речь о которой пойдет ниже.
Multi-label классификация (примерный перевод «многотемная классификация») один из видов задач классификации, где каждому примеру необходимо присвоить сразу несколько меток принадлежности к определенному классу. Формально задача может быть описана как нахождение модели, которая будет ставить в соответствие входные примеры бинарным векторам , а не скалярным значениям, как в классической задаче классификации.
Существует два основных подхода для решения данной задачи методы трансформации проблемы и методы адаптации алгоритмов. Методы трансформации проблемы преобразуют проблему к набору проблем бинарной классификации, которые могут быть решены с помощью классификаторов, способных решать бинарные задачи классификации. Методы адаптации алгоритмов, в свою очередь, модифицируют алгоритмы классификации так, чтобы они могли напрямую решать задачу multi-label классификации. Таким образом, вместо того, чтобы упрощать проблему они пытаются непосредственно решать проблему multi-label классификации.
В области machine learning классическими считаются методы трансформации проблемы, которые демонстрируют наилучшее качество предсказаний. Среди них самым распространенным и популярным считается метод двоичных отношений (binary relevance method). Данный метод предполагает создание и обучения одного бинарного классификатора для каждой возможной метки. Далее, когда модели подается новый пример, она присваивает данному примеру все метки для которых соответствующие классификаторы дали положительный ответ. Метод превращения задачи в набор бинарных задач классификации имеет много общего с методом one-vs.-all мультиклассовой классификации. Тем не менее, надо учитывать что это не идеально тот же метод он тренирует отельный классификатор для каждой метки, но не для каждого возможного значения этой метки.
Мерой «многотемности» данного набора данных можно оценить с помощью двух статистических формул:
Методы оценки производительности multi-label классификации в корне отличаются от используемых в многоклассовой или бинарной классификации, в связи с естественными отличиями данной задачи классификации. Если правильный набор меток для данного примера, а набор предсказанных меток, то можно определить следующие метрики для такого примера:
Суммируя все вышесказанное мы можем указать, что задача, которая стоит перед нами является гибридом multi-label и multiclass классификации. Для каждого примера нам требуется предсказывать одну или несколько меток (например, страна, в которой произошло событие, или индустрия, с которой связано произошедшее событие), каждой метке может быть присвоено несколько различных значений. Подобные задачи имеют название multi-task классификация или multiclass multi-output классификация. Задачи, подобные этой относительно успешно решаются в такой области как Document classification (классификация документов), что позволяет предположить, что ее методы могут быть успешно адаптированы для решения стоящей перед нами задачи. Рассмотрим некоторые методы применяемые в рамках классификации документов, а также применяемые в этой области классификаторы.
TF-IDF (TF - term frequency, IDF inverse document frequency) статистическая мера, используемая для оценки важности слова в контексте документа, являющегося частью коллекции документов или корпуса. Вес некоторого слова пропорционален количеству употребления этого слова в документе, и обратно пропорционален частоте употребления слова в других документах коллекции.
Мера TF-IDF часто используется в задачах анализа текстов и информационного поиска, например, как один из критериев релевантности документа поисковому запросу, при расчёте меры близости документов при кластеризации.
TF (term frequency частота слова) отношение числа вхождения некоторого слова к общему количеству слов документа. Таким образом, оценивается важность слова в пределах отдельного документа.
где есть число вхождений слова в документ, а в знаменателе общее число слов в данном документе.
IDF (inverse document frequency обратная частота документа) инверсия частоты, с которой некоторое слово встречается в документах коллекции. Основоположником данной концепции является Карен Спарк Джонс. Учёт IDF уменьшает вес широкоупотребительных слов. Для каждого уникального слова в пределах конкретной коллекции документов существует только одно значение IDF.
Где:
Выбор основания логарифма в формуле не имеет значения, поскольку изменение основания приводит к изменению веса каждого слова на постоянный множитель, что не влияет на соотношение весов.
Таким образом, мера TF-IDF является произведением двух сомножителей:
Большой вес в TF-IDF получат слова с высокой частотой в пределах конкретного документа и с низкой частотой употреблений в других документах. Мера TF-IDF часто используется для представления документов коллекции в виде числовых векторов, отражающих важность использования каждого слова из некоторого набора слов (количество слов набора определяет размерность вектора) в каждом документе. Подобная модель называется векторной моделью (Vector space model) и даёт возможность сравнивать тексты, сравнивая представляющие их вектора в какой либо метрике (евклидово расстояние, косинусная мера, манхэттенское расстояние, расстояние Чебышёва и др.), то есть производя кластерный анализ.
В контексте рассматриваемой в данной работе задачи, мера TF-IDF может быть применена для решения двух задач:
Таким образом, использовав подсчет меры TF-IDF мы сможем преобразовать наш список событий и изменений параметров в формат числовых векторов, то есть мы сможем преобразовать входные данные к векторной модели, в которой большей вес будет предоставлен значениям параметров, которые встречаются редко, а также уравняет важность событий с большим и малым числом параметров. После этого мы сможем применить методы multiclass - multi-label классификации с помощью какого-либо классификатора. В рамках Document classification отлично себя показали два классификатора SVM (с алгоритмом обучения SGD) и Наивный Байесовский классификатор. Оба этих классификатора рассматриваются ниже.
Наивный байесовский классификатор простой вероятностный классификатор, основанный на применении Теоремы Байеса со строгими (наивными) предположениями о независимости.
В зависимости от точной природы вероятностной модели, наивные байесовские классификаторы могут обучаться очень эффективно. Во многих практических приложениях, для оценки параметров для наивных байесовых моделей используют метод максимального правдоподобия; другими словами, можно работать с наивной байесовской моделью, не веря в байесовскую вероятность и не используя байесовские методы.
Несмотря на наивный вид и, несомненно, очень упрощенные условия, наивные байесовские классификаторы часто работают намного лучше во многих сложных жизненных ситуациях.
Достоинством наивного байесовского классификатора является малое количество данных для обучения, необходимых для оценки параметров, требуемых для классификации.
Вероятностная модель для классификатора это условная модель над зависимой переменной класса с малым количеством результатов или классов, зависимая от нескольких переменных . Проблема заключается в том, что когда количество свойств очень велико или когда свойство может принимать большое количество значений, тогда строить такую модель на вероятностных таблицах становится невозможно. Поэтому мы переформулируем модель, чтобы сделать её легко поддающейся обработке.
Используя теорему Байеса, запишем
На практике интересен лишь числитель этой дроби, так как знаменатель не зависит от и значения свойств даны, так что знаменатель константа.
Числитель эквивалентен совместной вероятности модели которая может быть переписана следующим образом, используя повторные приложения определений условной вероятности:
и т. д. Теперь можно использовать «наивные» предположения условной независимости: предположим, что каждое свойство условно независимо от любого другого свойства при . Это означает:
таким образом, совместная модель может быть выражена как:
Это означает, что из предположения о независимости, условное распределение по классовой переменной может быть выражено так:
где это масштабный множитель, зависящий только от , то есть константа, если значения переменных известны.
Все параметры модели могут быть аппроксимированы относительными частотами из набора данных обучения. Это оценки максимального правдоподобия вероятностей. Непрерывные свойства, как правило, оцениваются через нормальное распределение. В качестве математического ожидания и дисперсии вычисляются статистики среднее арифметическое и среднеквадратическое отклонение соответственно.
Если данный класс и значение свойства никогда не встречаются вместе в наборе обучения, тогда оценка, основанная на вероятностях, будет равна нулю. Это проблема, так как при перемножении нулевая оценка приведет к потере информации о других вероятностях. Поэтому предпочтительно проводить небольшие поправки во все оценки вероятностей так, чтобы никакая вероятность не была строго равна нулю.
Наивный байесовский классификатор объединяет модель с правилом решения. Одно общее правило должно выбрать наиболее вероятную гипотезу; оно известно как апостериорное правило принятия решения (MAP). Соответствующий классификатор это функция определённая следующим образом:
Метод опорных векторов (англ. SVM, support vector machine) набор схожих алгоритмов обучения с учителем, использующихся для задач классификации и регрессионного анализа. Принадлежит к семейству линейных классификаторов, может также рассматриваться как специальный случай регуляризации по Тихонову. Особым свойством метода опорных векторов является непрерывное уменьшение эмпирической ошибки классификации и увеличение зазора, поэтому метод также известен как метод классификатора с максимальным зазором.
Основная идея метода перевод исходных векторов в пространство более высокой размерности и поиск разделяющей гиперплоскости с максимальным зазором в этом пространстве. Две параллельных гиперплоскости строятся по обеим сторонам гиперплоскости, разделяющей наши классы. Разделяющей гиперплоскостью будет гиперплоскость, максимизирующая расстояние до двух параллельных гиперплоскостей. Алгоритм работает в предположении, что чем больше разница или расстояние между этими параллельными гиперплоскостями, тем меньше будет средняя ошибка классификатора.
Часто в алгоритмах машинного обучения возникает необходимость классифицировать данные. Каждый объект данных представлен как вектор (точка) в -мерном пространстве (последовательность p чисел). Каждая из этих точек принадлежит только одному из двух классов. Нас интересует, можем ли мы разделить точки гиперплоскостью размерностью Это типичный случай линейной разделимости. Таких гиперплоскостей может быть много. Поэтому вполне естественно полагать, что максимизация зазора между классами способствует более уверенной классификации. То есть можем ли мы найти такую гиперплоскость, чтобы расстояние от неё до ближайшей точки было максимальным. Это бы означало, что расстояние между двумя ближайшими точками, лежащими по разные стороны гиперплоскости, максимально. Если такая гиперплоскость существует, то она нас будет интересовать больше всего; она называется оптимальной разделяющей гиперплоскостью, а соответствующий ей линейный классификатор называется оптимально разделяющим классификатором.
Формально можно описать задачу следующим образом.
Полагаем, что точки имеют вид:
где принимает значение 1 или −1, в зависимости от того, какому классу принадлежит точка . Каждое это -мерный вещественный вектор, обычно нормализованный значениями или . Если точки не будут нормализованы, то точка с большими отклонениями от средних значений координат точек слишком сильно повлияет на классификатор. Мы можем рассматривать это как учебную коллекцию, в которой для каждого элемента уже задан класс, к которому он принадлежит. Мы хотим, чтобы алгоритм метода опорных векторов классифицировал их таким же образом. Для этого мы строим разделяющую гиперплоскость, которая имеет вид:
Вектор перпендикуляр к разделяющей гиперплоскости. Параметр равен по модулю расстоянию от гиперплоскости до начала координат. Если параметр равен нулю, гиперплоскость проходит через начало координат, что ограничивает решение.
Так как нас интересует оптимальное разделение, нас интересуют опорные вектора и гиперплоскости, параллельные оптимальной и ближайшие к опорным векторам двух классов. Можно показать, что эти параллельные гиперплоскости могут быть описаны следующими уравнениям (с точностью до нормировки).
Если обучающая выборка линейно разделима, то мы можем выбрать гиперплоскости таким образом, чтобы между ними не лежала ни одна точка обучающей выборки и затем максимизировать расстояние между гиперплоскостями. Ширину полосы между ними легко найти из соображений геометрии, она равна , таким образом наша задача минимизировать . Чтобы исключить все точки из полосы, мы должны убедиться для всех , что
Это может быть также записано в виде:
В случае линейной разделимости классов, проблема построения оптимальной разделяющей гиперплоскости сводится к минимизации при условии (1). Это задача квадратичной оптимизации, которая имеет вид:
По теореме Куна Таккера эта задача эквивалентна двойственной задаче поиска седловой точки функции Лагранжа.
Где вектор двойственных переменных
Сведем эту задачу к эквивалентной задаче квадратичного программирования, содержащую только двойственные переменные:
Допустим мы решили данную задачу, тогда и можно найти по формулам:
В итоге алгоритм классификации может быть записан в виде:
При этом суммирование идет не по всей выборке, а только по опорным векторам, для которых
В случае линейной неразделимости классов, для того, чтобы алгоритм мог работать, позволим ему допускать ошибки на обучающей выборке. Введем набор дополнительных переменных , характеризующих величину ошибки на объектах . Возьмем за отправную точку (2), смягчим ограничения неравенства, так же введём в минимизируемый функционал штраф за суммарную ошибку:
Коэффициент параметр настройки метода, который позволяет регулировать отношение между максимизацией ширины разделяющей полосы и минимизацией суммарной ошибки.
Аналогично, по теореме Куна-Таккера сводим задачу к поиску седловой точки функции Лагранжа:
По аналогии сведем эту задачу к эквивалентной:
На практике для построения машины опорных векторов решают именно эту задачу, а не (3), так как гарантировать линейную разделимость точек на два класса в общем случае не представляется возможным. Этот вариант алгоритма называют алгоритмом с мягким зазором (soft-margin SVM), тогда как в линейно разделимом случае говорят о жёстком зазоре (hard-margin SVM).
Для алгоритма классификации сохраняется формула (4), с той лишь разницей, что теперь ненулевыми обладают не только опорные объекты, но и объекты-нарушители. В определённом смысле это недостаток, поскольку нарушителями часто оказываются шумовые выбросы, и построенное на них решающее правило, по сути дела, опирается на шум.
Константу обычно выбирают по критерию скользящего контроля. Это трудоёмкий способ, так как задачу приходится решать заново при каждом значении .
Если есть основания полагать, что выборка почти линейно разделима, и лишь объекты-выбросы классифицируются неверно, то можно применить фильтрацию выбросов. Сначала задача решается при некотором C, и из выборки удаляется небольшая доля объектов, имеющих наибольшую величину ошибки . После этого задача решается заново по усечённой выборке. Возможно, придётся проделать несколько таких итераций, пока оставшиеся объекты не окажутся линейно разделимыми.
Алгоритм построения оптимальной разделяющей гиперплоскости, предложенный в 1963 году Владимиром Вапником и Алексеем Червоненкисом алгоритм линейной классификации. Однако в 1992 году Бернхард Босер, Изабелл Гийон и Вапник предложили способ создания нелинейного классификатора, в основе которого лежит переход от скалярных произведений к произвольным ядрам, так называемый kernel trick (предложенный впервые М. А. Айзерманом, Э. М. Браверманном и Л. В. Розоноэром для метода потенциальных функций), позволяющий строить нелинейные разделители. Результирующий алгоритм крайне похож на алгоритм линейной классификации, с той лишь разницей, что каждое скалярное произведение в приведённых выше формулах заменяется нелинейной функцией ядра (скалярным произведением в пространстве с большей размерностью). В этом пространстве уже может существовать оптимальная разделяющая гиперплоскость. Так как размерность получаемого пространства может быть больше размерности исходного, то преобразование, сопоставляющее скалярные произведения, будет нелинейным, а значит функция, соответствующая в исходном пространстве оптимальной разделяющей гиперплоскости, будет также нелинейной.
Стоит отметить, что если исходное пространство имеет достаточно высокую размерность, то можно надеяться, что в нём выборка окажется линейно разделимой.
Наиболее распространённые ядра:
В рамках поставленной перед нами задачи будем использовать линейное однородное ядро. Данное ядро показало отличные результаты в задачах Document Classification, хотя по сравнению с Наивным Байесовским Классификатором обучение данного классификатора занимается сравнительно большой промежуток времени. Также проверена работа всех остальных ядер из данного списка и выявлено, что их обучение занимает значительно больший промежуток времени, при этом не привнося особых улучшений в точности классификации.
Для ускорения обучения мы будем использовать метод под названием Стохастический Градиентный Спуск, который позволяет значительно ускорить обучение классификатора, не сильно жертвуя его точностью.
Градиентные методы - это широкий класс оптимизационных алгоритмов, используемых не только в машинном обучении. Здесь градиентный подход будет рассмотрен в качестве способа подбора вектора синаптических весов в линейном классификаторе. Пусть - целевая зависимость, известная только на объектах обучающей выборки:
Найдём алгоритм , аппроксимирующий зависимость . В случае линейного классификатора искомый алгоритм имеет вид:
где играет роль функции активации (в простейшем случае можно положить ).
Согласно принципу минимизации эмпирического риска для этого достаточно решить оптимизационную задачу:
Где - заданная функция потерь.
Для минимизации применим метод градиентного спуска (gradient descent). Это пошаговый алгоритм, на каждой итерации которого вектор изменяется в направлении наибольшего убывания функционала (то есть в направлении антиградиента):
Где - положительный параметр, называемый темпом обучения (learning rate).
Возможны 2 основных подхода к реализации градиентного спуска:
Можно представить алгоритм стохастического градиентного спуска в виде псевдокода следующим образом:
Вход:
Выход:
Тело:
Главным достоинством SGD можно назвать его скорость обучения на избыточно больших данных. Именно это интересно для нас в рамках поставленной перед нами задачи ибо объем входных данных будет весьма велик. В то же время, алгоритм SGD в отличие от классического пакетного градиентного спуска дает несколько меньшую точность классификации. Также алгоритм SGD неприменим при обучении машины опорных векторов с нелинейным ядром.
В рамках решаемой задачи нам потребуется воспользоваться алгоритмом преобразования исходных данных TF-IDF, который позволит нам повысить весомость редких событий и снизить вес частых событий. Полученные после преобразования данные мы будем передавать классификаторам, которые подходят для решения стоящей перед нами задачи, а именно: Наивный Байесовский Классификатор или Машина Опорных Векторов с Линейным ядром, обученная по методу стохастического градиентного спуска. Также мы осуществим проверку эффективности Машины Опорных Векторов с нелинейными ядрами, обученной по методу пакетного градиентного спуска. Однако, данный тип классификатора не кажется подходящим для поставленной задачи в силу слишком сложного ядра и склонности к переобучаемости, при которой классификатор плохо справляется с данными, которые не использовались для обучения классификатора.
Работа системы состоит из двух этапов. На первом этапе производится извлечение информации о трендах в ценах, устанавливаются периоды роста, снижения и постоянства цен на ценные бумаги. Разработка системы, способной выполнить данный этап не является основной темой данной работы, потому примененная для предобработки данных программа будет описана кратко.
На втором этапе добытая информация о трендах применяется для тренировки классификатора и выполнения предсказаний о причинах анализируемых изменений в трендах цен.
Для извлечения информации о трендах в ценах используется приложение FactEventAnalyzer.exe, разработанное на C#. Текст программы доступен в Приложении 1.
Программа работает с набором данных о ценах на ценные бумаги и о корпоративных событиях (таких как выплата дивидендов, изменения рейтинга компаний, собраниях акционеров) которые хранятся в базе данных MS SQL Server.
Примеры доступных данных продемонстрированы ниже.
Информация о ценных бумагах:
SecId |
Ticker |
Issuer |
SecType |
InvType |
PriceCurrency |
Region |
IssuerCountry |
Exchange |
IndustrySector |
1283450 |
IBM |
International Business Machines Corp |
EQTY |
EQTY |
USD |
American |
USA |
XNYS |
IT Consulting & Other Services |
1283451 |
ADVS |
Advent Software Inc |
EQTY |
EQTY |
USD |
American |
USA |
XNGS |
Application Software |
1283454 |
MSFT US Equity |
Microsoft Corp |
EQTY |
EQTY |
USD |
American |
USA |
XNGS |
Systems Software |
1283456 |
WIL SP |
Wilmar International Ltd |
EQTY |
EQTY |
SGD |
Asian |
SGP |
XSES |
Agricultural Products |
1283457 |
ADM |
Archer-Daniels-Midland Co |
EQTY |
EQTY |
USD |
American |
USA |
XNYS |
Agricultural Products |
1283459 |
AGU |
Agrium Inc |
EQTY |
EQTY |
USD |
American |
CAN |
XNYS |
Fertilizers & Agricultural Chemicals |
1283460 |
BG |
Bunge Ltd |
EQTY |
EQTY |
USD |
American |
BMU |
XNYS |
Agricultural Products |
1283462 |
GGR SP |
Golden Agri-Resources Ltd |
EQTY |
EQTY |
SGD |
Asian |
MUS |
XSES |
Agricultural Products |
1283463 |
INGR |
Ingredion Inc |
EQTY |
EQTY |
USD |
American |
USA |
XNYS |
Agricultural Products |
1283464 |
VT |
Viterra Inc |
EQTY |
EQTY |
CAD |
American |
CAN |
XTSE |
Agricultural Products |
Информация о ценах:
Date |
DateTick |
SecId |
Price |
AskPrice |
BidPrice |
1/24/12 0:00 |
40930 |
1287491 |
196 |
195 |
196 |
2/20/12 0:00 |
40957 |
1289220 |
26.5 |
20 |
20 |
3/5/12 0:00 |
40971 |
1289817 |
0.028375 |
0.022 |
0.0265 |
3/12/12 0:00 |
40978 |
1283601 |
37.14 |
37.12 |
37.23 |
3/20/12 0:00 |
40986 |
1289231 |
1.49 |
1.45 |
1.45 |
4/9/12 0:00 |
41006 |
1290160 |
1800 |
2200 |
1800 |
4/20/12 0:00 |
41017 |
1301676 |
0.195 |
0.013 |
0.12 |
5/2/12 0:00 |
41029 |
1290181 |
1500 |
1400 |
1500 |
5/14/12 0:00 |
41041 |
1289360 |
3.4 |
2.8 |
2.8 |
5/21/12 0:00 |
41048 |
1361228 |
0.0024 |
0.0005 |
0.00175 |
Информация о корпоративных событиях:
SecId |
UniqueCoraxId |
EffectiveDate |
EventType |
EventMajorType |
1283525 |
4625097 |
2/4/14 0:00 |
Merger |
Standard |
1283679 |
4626420 |
4/3/14 0:00 |
Stock Split |
Standard |
1284173 |
4627461 |
2/4/14 0:00 |
Merger |
Standard |
1284756 |
4629913 |
12/12/13 0:00 |
Cash Dividend |
Standard |
1284756 |
4629915 |
3/13/14 0:00 |
Cash Dividend |
Standard |
1284900 |
4630130 |
2/27/14 0:00 |
Merger |
Standard |
1285138 |
4630535 |
3/27/14 0:00 |
Cash Dividend |
Standard |
1285590 |
4631157 |
12/23/13 0:00 |
Divestiture |
Standard |
1285590 |
4631162 |
3/30/14 0:00 |
Merger |
Standard |
1285590 |
4631163 |
3/30/14 0:00 |
Divestiture |
Standard |
Приложение вызывается из командной строки, после чего выполняет следующий процесс:
Диаграмма классов системы приведена ниже:
Рис. 2. Диаграмма классов приложения FactEventAnalyzer
Краткое описание классов надо ниже:
В процессе работы система базу данных FactEventAnalysisDB, ее диаграмма приведена ниже:
Рис. 3. Диаграмма базы данных FactEventAnalysisDB.
Также используется две хранимых процедуры из базы FactEventAnalysisDB, код которых доступен в Приложении 3. Их описание приведено ниже:
Помимо этого используются два табличных представления, описание которых дано ниже:
Код данных представлений также доступен в Приложении 3.
Задача извлечения трендов не является главной темой работы, потому упомянута кратко.
По результатам работы система производит два файла SpikeFacts.csv и PriceFacts.csv, которые используются в дальнейшем. Данные файлы представляют собой список пар «Характеристика события» - «Характеристика изменения» на каждую дату.
Ниже представлена инструкция по использованию системы предобработки.
Для использования системы необходимо убедиться, что база с исходными данными FactEventAnalysisDB развернута на компьютере пользователя. После этого необходимо скопировать файл FactEventAnalyzer в любую папку на пользовательском компьютере. В ту же папку нужно положить файл Config.xml (доступен в Приложении 2). В данном файле необходимо указать актуальные параметры для следующих переменных:
Пример использования программы продемонстрирован ниже.
Программа скопирована в папку D:\DiplomaProject\PreProcessing
Рис. 4. Программа скопирована в папку
Двойной щелчок мыши по FactEventAnalyzer.exe запускает предобработчик. Так как это консольное приложение то появляется окно консоли, которое будет оставаться открытым на все время работы программы.
Рис. 5. Работа системы предобработки данных
После завершения работы программы в подпапке Log доступен журнал работы программы:
Рис. 6. Журнал работы системы предобработки данных
А в указанной ранее в файле Config.xml папке хранятся результаты работы программы предобработки:
Рис. 7. Результат работы системы предобработки данных
Полученные в предыдущем шаге файлы используются системой FactGeneralyzer, код которой доступен в Приложении 4.
Данная система разработана с помощью языка Python и пакета Scikit-learn, которые предоставляют реализацию упомянутых в предыдущих разделах методов и алгоритмов машинного обучения. Система позволяет выбрать классификатор, который будет использоваться при работе. Система способна работать в двух режимах тестовом и рабочем. Описание обоих режимов представлено ниже.
Данный режим предназначен для обучения классификаторов на основе информации о прошлых событиях и изменениях. Обученные классификаторы применяются для анализа предложенных изменений, после чего система делает предположение о причине данных изменений.
После запуска программы вызывается функция main(), которая записывает дату и время запуска программы, после чего определяет в каком режиме должна работать система тестовом или рабочем. Для рабочего режима вызывается функция workMode, которой передается информация о файлах с данными для обучения и для выполнения предсказаний, информация о выбранном классификаторе, а также информация о папке в которую надо записать результаты предсказаний.
Функция workMode загружает данные для обучения и данные ля выполнения предсказаний из файлов и трансформирует их в формат Pandas.DataFrame, который удобен для обработки данных, которые можно представить в виде таблицы. Затем система формирует 3 набора данных:
После формирования данных наборов, набор y_train (информация о произошедших изменениях в тренировочных данных) проходит через трансформацию с помощью функции preprocessing.MultiLabelBinarizer(). Данная трансформация преобразовывает матрицу текстовых меток (информацию о изменениях в трендах страну выпуска ценной бумаги, тип изменения в тренде, биржу на которой торгуется бумага) в формат бинарной матрицы, содержащей метки о присутствии или отсутствии определенного класса в примере. Данное преобразование необходимо, так как использованные в дальнейшем алгоритмы ожидают именно такой формат на вход для выполнения предсказаний.
После этого система тренирует классификатор на базе выбранного алгоритма классификации.
Для этого система сначала выполняет преобразование исходного набора пар текстовых меток о событиях и произошедших одновременно с ними изменениях (напомним, информация о произошедших изменениях уже была преобразована функцией MultiLabelBinarizer) в формат бинарных разряженных матриц, содержащих количество появления каждой из меток с помощью функции CountVectorizer().
Полученные матрицы передается функции TfidfTransformer(). Данная функция выполняет TF-IDF преобразование над переданными матрицами, которое было разобрано ранее. Преобразование выполняется для того, чтобы повысить вес редких параметров событий и понизить вес часто встречающихся характеристик. Например, если подавляющее большинство событий в наборе данных связано с российскими ценными бумагами то событие связанное с ценными бумагами другой страны будет иметь больший вес при обучении классификатора.
Полученные матрицы, содержащие информацию о изменениях и о произошедших одновременно с ними событиях, после преобразования TF-IDF передаются метаклассификатору OneVsRestClassifier(). Данный метаклассификатор использует переданные ему матрицы для выполнения задачи многоклассовой\многотемной классификации с помощью выбранного нами классификатора и стратегии One-vs-all.
Для облегчения задачи последовательного вызова функций CountVectorizer(), TfidfTransformer() и передачи результатов метаклассификатору OneVsRestClassifier() мы воспользуемся так называемым конвейером Pipeline(). Конвейер просто последовательно применяет переданные ему функции и передает их результат выбранному классификатору.
После обучения классификатора на тестовых данных (команда fit()) мы выполняем предсказание для данных в X_test, то есть пытаемся объяснить изменения в трендах, хранимые в данной переменной. Полученные предсказания проходят обратное преобразование из формата бинарной матрицы, содержащей метки о присутствии или отсутствии определенного класса в примере в формат обычной текстовой информации с помощью функции inverse_transform(). Полученные предсказания сохраняются в текстовый файл. Пример результата:
0,"NegSpike,AAB,Apple Inc,EQTY,REIT,USD,Asian,JPN,XTKS,Internet Software & Services","(American, Bought Back, EQTY, FOX, Movies & Entertainment, Standard, Twenty-First Century Fox Inc, USA, USD, XNGS)"
По результатам системы производится файл с предсказаниями выполненными с помощью выбранного алгоритма классификации.
В тестовом режиме оценивается производительность системы на переданном ей файле с данными с помощью выбранного классификатора. Для оценки производительности системы и качества ее предсказаний попробуем оценивать метрику Precision (доля правильно предсказанных меток от общего количества предсказанных меток). Ожидается, что система будет способна выделить наиболее вероятные причины изменения в трендах цены и сможет предсказать, какие события скорее всего происходили одновременно с происходящими изменениями в трендах, то есть сможет указать максимальное количество меток таких корпоративных событий.
В тестовом режиме системе передается файл с тестовыми парами событий и изменений в трендах, после чего данный файл разделяется на два случайных подмножества тренировочный набор данных (90% исходных пар событий и трендов) и тестовый набор данных (10% исходных пар). После чего выбранный классификатор тренируется на тренировочном наборе данных (процесс полностью аналогичен таковому в рабочем режиме). Затем обученный классификатор запускается для каждого описания изменения тренда в тестовом наборе данных и система делает предсказания о причинах изменения этого тренда. После этого подсчитывается, сколько из меток предсказанного описания события присутствуют в реально существовавшей паре событие изменение в тренде. Путем деления полученного количества на общее количество предсказанных меток получаем Precision системы.
По результатам работы системы получаем файл Results.csv, который содержит следующую информацию:
Пример одной такой записи:
0, PosSpike,ICPT,Intercept Pharmaceuticals Inc,EQTY,REIT,USD,American,USA,XNGS,Biotechnology, [Merger, Standard, ETE, Energy Transfer Equity LP, UNIT, EUNT, USD, American, USA, XNYS, Oil & Gas Storage & Transportation] , (American, EQTY, Standard, USA, USD)
4.2.3.1 Тестовый режим
Необходимо скопировать файл с программой FactGeneralizer.py в любую папку на пользовательском компьютере. Также необходимо скопировать нужные для работы файлы в любую папку на пользовательском компьютере. В случае работы программы в тестовом режиме необходим один файл с парами событие изменение в тренде. Далее необходимо открыть командную строку и с помощью команды cd перейти в папку, в которой хранится файл FactGeneralizer.py. Для запуска программы в тестовом режиме необходимо ввести в командной строке следующую команду и нажать Enter:
python FactGeneralizer.py “test” “PriceFacts.csv” “Results.csv” “linear”
Значение параметров приведено ниже:
После завершения программы в командной строке будет выведена информация о времени работы программы и информация о precision (точности) программы на тестовых данных. Также в файл результатов будут сохранены тестовые примеры и сделанные системой предсказания.
Ниже приведен пример запуска и получения результатов программы в тестовом режиме.
Рис. 8. Работа системы классификации в тестовом режиме
4.2.3.2 Рабочий режим
Необходимо скопировать файл с программой FactGeneralizer.py в любую папку на пользовательском компьютере. Также необходимо скопировать нужные для работы файлы в любую папку на пользовательском компьютере. В случае работы программы в рабочем режиме необходимы два файла. Первый из них, содержащий пары «событие изменение в тренде» будет использован для обучения классификатора. Второй должен содержать записи об изменениях в трендах, которые система попытается объяснить. Далее необходимо открыть командную строку и с помощью команды cd перейти в папку, в которой хранится файл FactGeneralizer.py. Для запуска программы в тестовом режиме необходимо ввести в командной строке следующую команду и нажать Enter:
python FactGeneralizer.py “work” “PriceFacts.csv” “ToPredict.csv” “Results.csv” “linear”
Значение параметров приведено ниже:
После завершения программы в командной строке будет выведена информация о времени работы программы. В файл результатов будет записана информация о каждом изменении в тренде, которое система пыталась объяснить, и описание вероятной причины в данном изменении в тренде. Ниже приведен пример запуска программы в рабочем режиме.
Рис. 9. Работа системы классификации в рабочем режиме
Реализована система, осуществляющая предобработку исходных данных о ценах на ценные бумаги и корпоративных событиях, связанных с ценными бумагами. Предобработка создает файл, содержащий информацию о том, какие корпоративные события происходили одновременно с какими изменениями в трендах цен. Далее данная информация передается системе классификации, которая производит преобразование по алгоритму TF-IDF и тренирует классификатор на базе одного из поддерживаемых алгоритмов. В ходе тренировки классификатор определяет, какие параметры корпоративных событий сопутствуют конкретным параметрам в изменениях трендов. Например, классификатор способен обнаружить, что в большинстве случаев одновременно со сломом тренда для ценных бумаг, торгующихся на московской бирже и выпущенных металлургическими компаниями, обычно происходит корпоративное событие связанное с китайскими автопроизводителями.
Обученный классификатор используется далее для поиска причин слома тренда цен переданных классификатору ценных бумаг.
В ходе машинного эксперимента этап предобработки информации был запущен на следующем наборе тестовых данных:
Были установлены следующие параметры работы для предобработчика данных:
Системе предобработки данных понадобилось около 19 часов работы для завершения предобработки исходных данных. По результатам было получено 26975 пар событие изменение в тренде. Полученные пары были использованы для работы системы классификации в тестовом режиме. Система была запущена три раза для каждого из поддерживаемых классификаторов. Тренировка классификаторов производилась на случайно выбранных 90% данных (24280) и далее тестировалась на 2695 оставшихся записях. Ниже представлена таблица с результатами тестирования:
Тип классификатора |
Всего предсказано меток |
Корректно предсказано меток |
Precision, % |
Время выполнения программы, сек. |
SVM с линейным ядром, SGD обучение |
15047 |
12119 |
81 |
21 |
15217 |
12424 |
82 |
23 |
|
14977 |
12121 |
81 |
19 |
|
SVM с линейным ядром, обучение пакетным градиентным спуском |
15157 |
12192 |
80 |
159 |
15286 |
12471 |
82 |
146 |
|
15218 |
12200 |
80 |
147 |
|
SVM с ядром RBF |
1765880 |
28073 |
1.59 |
9985 |
1765880 |
28083 |
1.59 |
9950 |
|
1765880 |
28050 |
1.59 |
9949 |
|
SVM с полиномиальным ядром |
1765880 |
28128 |
1.59 |
8620 |
1765880 |
28118 |
1.59 |
9639 |
|
1765880 |
28081 |
1.59 |
9960 |
|
SVM с ядром - сигмоид |
1765880 |
28093 |
1.59 |
9685 |
1765880 |
28112 |
1.59 |
8739 |
|
1765880 |
28084 |
1.59 |
8677 |
|
Наивный Байесовский классификатор |
17904 |
12627 |
70.53 |
12 |
18141 |
12672 |
69.85 |
12 |
|
16790 |
12812 |
76 |
12 |
Таблица 1. Результаты тестирования различных классификаторов
Как и предполагалось ранее, машина опорных векторов с нелинейным ядром (RBF, полиномиальное ядро или сигмоид) не подходит для решения данной задачи. Каждый из этих трех классификаторов сильно переучивается на тестовых данных и дает практически одинаковые прогнозы для всех тестовых примеров. Данные классификаторы требуют значительное время для обучения (порядка 3 часов каждый) и демонстрируют одинаковую точность, равную примерно 1,5%.
Как и предполагалось ранее, наилучшую точность демонстрирует машина опорных векторов с линейным ядром. При этом обучение по методу стохастического градиентного спуска дает резкий прирост в скорости обучения средняя время тренировки по методу стохастического градиентного спуска равно 20 секундам, против 150 секунд при обучении по методу пакетного градиентного спуска. При этом переход на стохастический градиентный спуск не снижает точности классификатора она все также близка к 80%.
Наивный Байесовский классификатор также хорошо показал себя в машинном эксперименте. Его отличительной чертой можно назвать большую агрессивность при выполнении предсказаний (если машина опорных векторов в среднем предсказывала около 15000 меток то Наивный Байесовский Классификатор предсказывал около 17500 меток). Также можно отметить больший разброс точности данного классификатора если результаты SVM отличаются друг от друга примерно на 1% то результаты NBC колеблются в более широких пределах и могут отличаться друг от друга почти на 7%. Тем не менее, данный алгоритм обучается гораздо быстрее, чем SVM, даже при ее обучении по методу стохастического градиентного спуска. NBC в среднем потребовалось 12 секунд на тренировку, в то время как SVM в среднем обучалась около 20 секунд. Также следует отметить, что NBC до лучше масштабируется до больших объемов данных, соответственно с ростом объема обучающих данных время, необходимое на его обучение, будет расти медленнее, чем для SVM.
Таким образом, применение классификаторов NBC и SVM действительно позволяет достичь поставленной нами задачи. Данные классификаторы способны с большой точностью (примерно 70-80%) предсказывать, какими характеристиками обладают события, которые должны одновременно происходить с переданными им записями о сломах в трендах цен.
В рамках данной работы нам удалось решить задачу по поиску наиболее вероятных причин в изменениях в трендах данных. Задача, которая стояла перед нами является гибридом multi-label и multiclass классификации. Для каждого примера нам требуется предсказывать одну или несколько меток (например, страна, в которой произошло событие, или индустрия, с которой связано произошедшее событие), каждой метке может быть присвоено несколько различных значений. Подобные задачи имеют название multi-task классификация или multiclass multi-output классификация. Задачи, подобные этой относительно успешно решаются в такой области как Document classification (классификация документов), что позволило нам использовать методы, традиционно применяемые в рамках классификации документов
Задача состояла из двух частей:
1) Извлечение из информации о ценах данные о том для каких ценных бумаг когда наблюдалось изменение в тренде в цене (если цена на бумагу росла некоторое время, то в какой момент она начала снижаться или стала неизменной).
2) Разработка системы, которая сможет обучиться на информации о предшествующих событиях и изменениях в трендах выявляя наиболее вероятные характеристики событий - причин, и далее сумеет предсказать, какими характеристиками наиболее вероятно обладает событие, которое вызвало переданный системе слом в тренде.
Первая часть задачи была решена с помощью языка программирования C#.
Вторая задача была решена с помощью языка python и открытой библиотеки программного обеспечения для машинного обучения scikit-learn. Данная библиотека имеет открытый исходный код, предлагает эффективную реализацию всех популярных алгоритмов машинного обучения и поддерживается большим сообществом разработчиков и исследователей из университетов и лабораторий всего мира. При решении второй части задачи мы использовали алгоритм TF-IDF который позволил нам повысить весомость редких событий и снизить вес частых событий. Далее были применены алгоритмы классификации Машина Опорных Векторов и Наивный Байесовский Классификатор. Проверена эффективность различных ядер для Машины Опорных Векторов, а также эффективность пакетного градиентного спуска и стохастического градиентного спуска. Установлено, что наилучшей точностью обладают SVM с линейным ядром и Наивный Байесовский Классификатор. Нам удалось получить точность классификации около 80%. Также установлено, что обучение по методу стохастического градиентного спуска позволяет значительно ускорить тренировку SVM классификатора без каких-либо жертв в точности.
Реализованная система обладает приемлемой производительностью. И достаточной точностью для ее практического применения. Архитектура разработанной программы позволяет включить её в состав более сложной системы.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace FactEventAnalyzer
{
public struct Parameters
{
public double ChangeThreshold;
public int SecIdFrom;
public int SecIdTo;
public int DateFrom;
public int DateTo;
public double SpikeThreshold;
public Logger fileLogger;
public int minerDateThreshold;
public string folderForMLFiles;
}
class Program
{
static void Main()
{
DateTime startDate = System.DateTime.Now;
Parameters curParam = readInitialParams(AppDomain.CurrentDomain.BaseDirectory);
curParam.fileLogger.writeToLog("Fact-Event analysis tool started");
FactMiner.runFactMiner(CurParam: curParam);
curParam.fileLogger.writeToLog("Fact-Event analysis tool completed, it took " + (System.DateTime.Now - startDate).TotalSeconds.ToString() + " seconds.");
}
static Parameters readInitialParams(string paramPass)
{
Parameters curParam = new Parameters();
XElement config = XElement.Load(paramPass + "\\Config.xml");
XElement LogConfig = config.Element("logging").Element("logFolder");
curParam.fileLogger = new Logger(LogConfig.Attribute("path").Value.ToString());
XElement factMinerSettings = config.Element("factMinerSettings");
curParam.ChangeThreshold = (double) factMinerSettings.Element("changeThreshold").Attribute("value");
curParam.SecIdFrom = (int)factMinerSettings.Element("securities").Attribute("from");
curParam.SecIdTo = (int)factMinerSettings.Element("securities").Attribute("to");
curParam.DateFrom = (int)factMinerSettings.Element("dates").Attribute("from");
curParam.DateTo = (int)factMinerSettings.Element("dates").Attribute("to");
curParam.SpikeThreshold = (double)factMinerSettings.Element("spikeThreshold").Attribute("value");
curParam.minerDateThreshold = (int)factMinerSettings.Element("dateThreshold").Attribute("value");
curParam.folderForMLFiles = (string)factMinerSettings.Element("filesForMl").Attribute("value");
string copyConfigFile = paramPass+"\\Log\\Config" + curParam.fileLogger.composeDateString(mode: 0) + ".xml";
System.IO.File.Copy(paramPass + "\\Config.xml", copyConfigFile);
curParam.fileLogger.writeToLog("Config loaded, copy of loaded config file:\n " + copyConfigFile);
return curParam;
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
namespace FactEventAnalyzer
{
static class FactMiner
{
enum ChangeType
{
Growing = 0,
Decreasing = 1,
Unchanged = 2,
PosSpike = 3,
NegSpike = 4
}
enum FieldType
{
Ask = 0,
Bid = 1,
Price = 2
}
public static void runFactMiner(Parameters CurParam)
{
DateTime startDate = DateTime.Now;
CurParam.fileLogger.writeToLog("Mining of Facts started");
FactEventAnalysisDBDataContext dbContext = new FactEventAnalysisDBDataContext();
dbContext.usp_clearFactsTables(dateFrom: CurParam.DateFrom, dateTo: CurParam.DateTo, secIdFrom: CurParam.SecIdFrom, secIdTo: CurParam.SecIdTo, isPrices: true, isCorax: false, isSpike: true);
CurParam.fileLogger.writeToLog("Price Facts and Spike Facts are cleared, it took " + (DateTime.Now - startDate).TotalSeconds);
var SecurityQuery = from security in dbContext.SecuritiesSources
orderby security.SecId
where security.SecId >= CurParam.SecIdFrom && security.SecId <= CurParam.SecIdTo && dbContext.PricesSources.Any(p => (p.SecId == security.SecId && p.DateTick >= CurParam.DateFrom && p.DateTick <= CurParam.DateTo))
select security;
int totalSecs = SecurityQuery.Count();
int curSecNum = 0;
foreach (FactEventAnalyzer.SecuritiesSource curSec in SecurityQuery)
{
curSecNum++;
CurParam.fileLogger.writeToLog(curSecNum.ToString() + "/" + totalSecs.ToString() + ", SecId is " + curSec.SecId);
analyzePriceField(DbContext: dbContext, CurSec: curSec, curParam: CurParam);
CurParam.fileLogger.writeToLog("Price completed");
analyzeAskField(DbContext: dbContext, CurSec: curSec, curParam: CurParam);
CurParam.fileLogger.writeToLog("Ask completed");
analyzeBidField(DbContext: dbContext, CurSec: curSec, curParam: CurParam);
CurParam.fileLogger.writeToLog("Bid completed");
if (CurParam.SpikeThreshold > 0)
{
produceSpikeFacts(DbContext: dbContext, CurSec: curSec, curParam: CurParam);
CurParam.fileLogger.writeToLog("Spikes analyzed");
}
}
CurParam.fileLogger.writeToLog("Price Facts are produced.\n Start time: " + startDate.ToString() +
"\n Finish time: " + System.DateTime.Now.ToString() +
"\n Securities processed: " + totalSecs.ToString() +
"\n Total runtime: " + (System.DateTime.Now - startDate).TotalSeconds.ToString());
CurParam.fileLogger.writeToLog("Starting Corax Facts production");
produceCoraxFacts(curParam: CurParam);
CurParam.fileLogger.writeToLog("Corax Facts are produced.Starting to save results to file.\n");
var hypothesisQuery = from hypothesis in dbContext.PriceFactsHypothesis
select hypothesis;
string resultsFile = CurParam.folderForMLFiles + "\\PriceFacts_" + CurParam.fileLogger.composeDateString(mode: 0) + ".csv";
string delimiter = ",";
Type type = typeof(PriceFactsHypothesi);
PropertyInfo[] properties = type.GetProperties();
string line = "";
foreach (PropertyInfo property in properties)
{
line += property.Name + delimiter;
}
System.IO.StreamWriter file = new System.IO.StreamWriter(resultsFile, true);
file.WriteLine(line);
file.Close();
foreach (var hypothesis in hypothesisQuery)
{
properties = type.GetProperties();
line = "";
foreach (PropertyInfo property in properties)
{
string value = property.GetValue(hypothesis) != null
? property.GetValue(hypothesis).ToString()
: string.Empty; // or whatever you want the default text to be
line += value + delimiter;
}
file = new System.IO.StreamWriter(resultsFile, true);
file.WriteLine(line);
file.Close();
}
var hypothesisQuery2 = from hypothesis in dbContext.SpikeFactsHypothesis
select hypothesis;
resultsFile = CurParam.folderForMLFiles + "\\SpikeFacts_" + CurParam.fileLogger.composeDateString(mode: 0) + ".csv";
type = typeof(SpikeFactsHypothesi);
properties = type.GetProperties();
line = "";
foreach (PropertyInfo property in properties)
{
line += property.Name + delimiter;
}
file = new System.IO.StreamWriter(resultsFile, true);
file.WriteLine(line);
file.Close();
foreach (var hypothesis in hypothesisQuery2)
{
properties = type.GetProperties();
line = "";
foreach (PropertyInfo property in properties)
{
string value = property.GetValue(hypothesis) != null
? property.GetValue(hypothesis).ToString()
: string.Empty; // or whatever you want the default text to be
line += value + delimiter;
}
file = new System.IO.StreamWriter(resultsFile, true);
file.WriteLine(line);
file.Close();
}
CurParam.fileLogger.writeToLog("Fact mining have completed.\n Start time: " + startDate.ToString() +
"\n Finish time: " + System.DateTime.Now.ToString() +
"\n Total runtime of Fact Miner: " + (System.DateTime.Now - startDate).TotalSeconds.ToString());
}
static void analyzePriceField(FactEventAnalysisDBDataContext DbContext, FactEventAnalyzer.SecuritiesSource CurSec, Parameters curParam)
{
var PriceQuery = from price in DbContext.PricesSources
where price.SecId.Equals(CurSec.SecId) && price.DateTick >= curParam.DateFrom && price.DateTick <= curParam.DateTo
orderby price.DateTick
select price;
bool isFirst = true;
var previousPrice = new FactEventAnalyzer.PricesSource();
var startPrice = new FactEventAnalyzer.PricesSource();
var lastPrice = new FactEventAnalyzer.PricesSource();
bool isSkip = false;
bool isSecond = false;
int skipNum = 0;
ChangeType curChange = ChangeType.Unchanged;
foreach (FactEventAnalyzer.PricesSource curPrice in PriceQuery)
{
lastPrice = curPrice;
if (isFirst == true)
{
startPrice = curPrice;
isFirst = false;
isSecond = true;
}
else
{
if (isSecond == true)
{
curChange = determineChangeType(prevPrice: startPrice.Price, curPrice: curPrice.Price, changeThreshold: curParam.ChangeThreshold);
previousPrice = curPrice;
isSecond = false;
}
else
{
if (isSkip == false)
{
if (determineChangeType(prevPrice: previousPrice.Price, curPrice: curPrice.Price, changeThreshold: curParam.ChangeThreshold) != curChange)
{
isSkip = true;
}
else
{
previousPrice = curPrice;
}
}
else
{
if (skipNum < (curParam.minerDateThreshold - 1))
{
skipNum++;
}
else
{
skipNum = 0;
isSkip = false;
if (determineChangeType(prevPrice: previousPrice.Price, curPrice: curPrice.Price, changeThreshold: curParam.ChangeThreshold) != curChange)
{
insertNewPriceFact(curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
curChange = determineChangeType(prevPrice: previousPrice.Price, curPrice: curPrice.Price, changeThreshold: curParam.ChangeThreshold);
startPrice = previousPrice;
previousPrice = curPrice;
}
else
{
previousPrice = curPrice;
}
}
}
}
}
}
if (isSkip == true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
insertNewPriceFact(changeType: determineChangeType(previousPrice.Price, curPrice: lastPrice.Price, changeThreshold: curParam.ChangeThreshold), dateStart: previousPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
}
else
{
if (isFirst != true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
}
}
}
static void analyzeAskField(FactEventAnalysisDBDataContext DbContext, FactEventAnalyzer.SecuritiesSource CurSec, Parameters curParam)
{
var PriceQuery = from price in DbContext.PricesSources
where price.SecId.Equals(CurSec.SecId) && price.DateTick >= curParam.DateFrom && price.DateTick <= curParam.DateTo
orderby price.DateTick
select price;
bool isFirst = true;
var previousPrice = new FactEventAnalyzer.PricesSource();
var startPrice = new FactEventAnalyzer.PricesSource();
var lastPrice = new FactEventAnalyzer.PricesSource();
bool isSkip = false;
bool isSecond = false;
int skipNum = 0;
ChangeType curChange = ChangeType.Unchanged;
foreach (FactEventAnalyzer.PricesSource curPrice in PriceQuery)
{
lastPrice = curPrice;
if (isFirst == true)
{
startPrice = curPrice;
isFirst = false;
isSecond = true;
}
else
{
if (isSecond == true)
{
curChange = determineChangeType(prevPrice: startPrice.AskPrice, curPrice: curPrice.AskPrice, changeThreshold: curParam.ChangeThreshold);
previousPrice = curPrice;
isSecond = false;
}
else
{
if (isSkip == false)
{
if (determineChangeType(prevPrice: previousPrice.AskPrice, curPrice: curPrice.AskPrice, changeThreshold: curParam.ChangeThreshold) != curChange)
{
isSkip = true;
}
else
{
previousPrice = curPrice;
}
}
else
{
if (skipNum < (curParam.minerDateThreshold - 1))
{
skipNum++;
}
else
{
skipNum = 0;
isSkip = false;
if (determineChangeType(prevPrice: previousPrice.AskPrice, curPrice: curPrice.AskPrice, changeThreshold: curParam.ChangeThreshold) != curChange)
{
insertNewPriceFact(curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
curChange = determineChangeType(prevPrice: previousPrice.AskPrice, curPrice: curPrice.AskPrice, changeThreshold: curParam.ChangeThreshold);
startPrice = previousPrice;
previousPrice = curPrice;
}
else
{
previousPrice = curPrice;
}
}
}
}
}
}
if (isSkip == true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
insertNewPriceFact(changeType: determineChangeType(previousPrice.AskPrice, curPrice: lastPrice.AskPrice, changeThreshold: curParam.ChangeThreshold), dateStart: previousPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
}
else
{
if (isFirst != true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
}
}
}
static void analyzeBidField(FactEventAnalysisDBDataContext DbContext, FactEventAnalyzer.SecuritiesSource CurSec, Parameters curParam)
{
var PriceQuery = from price in DbContext.PricesSources
where price.SecId.Equals(CurSec.SecId) && price.DateTick >= curParam.DateFrom && price.DateTick <= curParam.DateTo
orderby price.DateTick
select price;
bool isFirst = true;
var previousPrice = new FactEventAnalyzer.PricesSource();
var startPrice = new FactEventAnalyzer.PricesSource();
var lastPrice = new FactEventAnalyzer.PricesSource();
bool isSkip = false;
bool isSecond = false;
int skipNum = 1;
ChangeType curChange = ChangeType.Unchanged;
foreach (FactEventAnalyzer.PricesSource curPrice in PriceQuery)
{
lastPrice = curPrice;
if (isFirst == true)
{
startPrice = curPrice;
isFirst = false;
isSecond = true;
}
else
{
if (isSecond == true)
{
curChange = determineChangeType(prevPrice: startPrice.BidPrice, curPrice: curPrice.BidPrice, changeThreshold: curParam.ChangeThreshold);
previousPrice = curPrice;
isSecond = false;
}
else
{
if (isSkip == false)
{
if (determineChangeType(prevPrice: previousPrice.BidPrice, curPrice: curPrice.BidPrice, changeThreshold: curParam.ChangeThreshold) != curChange)
{
isSkip = true;
}
else
{
previousPrice = curPrice;
}
}
else
{
if (skipNum < (curParam.minerDateThreshold - 1))
{
skipNum++;
}
else
{
skipNum = 1;
isSkip = false;
if (determineChangeType(prevPrice: previousPrice.BidPrice, curPrice: curPrice.BidPrice, changeThreshold: curParam.ChangeThreshold) != curChange)
{
insertNewPriceFact(curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
startPrice = previousPrice;
previousPrice = curPrice;
curChange = determineChangeType(prevPrice: startPrice.BidPrice, curPrice: previousPrice.BidPrice, changeThreshold: curParam.ChangeThreshold);
}
else
{
previousPrice = curPrice;
}
}
}
}
}
}
if (isSkip == true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: previousPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
insertNewPriceFact(changeType: determineChangeType(previousPrice.BidPrice, curPrice: lastPrice.BidPrice, changeThreshold: curParam.ChangeThreshold), dateStart: previousPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
}
else
{
if (isFirst != true)
{
insertNewPriceFact(changeType: curChange, dateStart: startPrice.DateTick, dateEnd: lastPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
}
}
}
static void insertNewPriceFact(ChangeType changeType, int dateStart, int dateEnd, int secId, FieldType fieldType)
{
FactEventAnalysisDBDataContext dbContext = new FactEventAnalysisDBDataContext();
FactEventAnalyzer.PriceFactsTable newFact = new FactEventAnalyzer.PriceFactsTable();
newFact.ChangeType = changeType.ToString();
newFact.DateStart = dateStart;
newFact.DateEnd = dateEnd;
newFact.SecID = secId;
newFact.FieldType = fieldType.ToString();
dbContext.PriceFactsTables.InsertOnSubmit(newFact);
dbContext.SubmitChanges();
}
static void insertNewSpikeFact(ChangeType changeType, int date, int secId, FieldType fieldType)
{
FactEventAnalysisDBDataContext dbContext = new FactEventAnalysisDBDataContext();
FactEventAnalyzer.SpikeFactsTable newSpikeFact = new FactEventAnalyzer.SpikeFactsTable();
newSpikeFact.Date = date;
newSpikeFact.FieldType = fieldType.ToString();
newSpikeFact.SecId = secId;
newSpikeFact.SpikeType = changeType.ToString();
dbContext.SpikeFactsTables.InsertOnSubmit(newSpikeFact);
dbContext.SubmitChanges();
}
static void produceCoraxFacts(Parameters curParam)
{
int? insertedSecs = 0, insertedCoraxes = 0;
DateTime startDate = DateTime.Now;
FactEventAnalysisDBDataContext dbContext = new FactEventAnalysisDBDataContext();
dbContext.usp_clearFactsTables(dateFrom: curParam.DateFrom, dateTo: curParam.DateTo, secIdFrom: curParam.SecIdFrom, secIdTo: curParam.SecIdTo, isPrices: false, isCorax: true, isSpike: false);
curParam.fileLogger.writeToLog("Corax Facts are cleared, it took " + (DateTime.Now - startDate).TotalSeconds);
dbContext.usp_populateCoraxFactsTable(curParam.DateFrom, curParam.DateTo, curParam.SecIdFrom, curParam.SecIdTo, ref insertedSecs, ref insertedCoraxes);
curParam.fileLogger.writeToLog("Corax Facts are produced.\n Start time: " + startDate.ToString() +
"\n Finish time: " + System.DateTime.Now.ToString() +
"\n Securities processed: " + insertedSecs.ToString() +
"\n Facts inserted: " + insertedCoraxes.ToString() +
"\n Total runtime: " + (System.DateTime.Now - startDate).TotalSeconds.ToString());
}
static void produceSpikeFacts(FactEventAnalysisDBDataContext DbContext, FactEventAnalyzer.SecuritiesSource CurSec, Parameters curParam)
{
var PriceQuery = from price in DbContext.PricesSources
where price.SecId.Equals(CurSec.SecId) && price.DateTick >= curParam.DateFrom && price.DateTick <= curParam.DateTo
orderby price.DateTick
select price;
bool isFirst = true;
FactEventAnalyzer.PricesSource prevPrice = new FactEventAnalyzer.PricesSource();
foreach (FactEventAnalyzer.PricesSource curPrice in PriceQuery)
{
if (isFirst == true)
{
prevPrice = curPrice;
isFirst = false;
}
else
{
double changeAmount = (double)(100 * (curPrice.Price - prevPrice.Price) / prevPrice.Price);
if (Math.Abs(changeAmount) > curParam.SpikeThreshold)
{
if (changeAmount < 0)
{
insertNewSpikeFact(changeType: ChangeType.NegSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
}
else
{
insertNewSpikeFact(changeType: ChangeType.PosSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Price);
}
}
changeAmount = (double)(100 * (curPrice.AskPrice - prevPrice.AskPrice) / prevPrice.AskPrice);
if (Math.Abs(changeAmount) > curParam.SpikeThreshold)
{
if (changeAmount < 0)
{
insertNewSpikeFact(changeType: ChangeType.NegSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
}
else
{
insertNewSpikeFact(changeType: ChangeType.PosSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Ask);
}
}
changeAmount = (double)(100 * (curPrice.BidPrice - prevPrice.BidPrice) / prevPrice.BidPrice);
if (Math.Abs(changeAmount) > curParam.SpikeThreshold)
{
if (changeAmount < 0)
{
insertNewSpikeFact(changeType: ChangeType.NegSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
}
else
{
insertNewSpikeFact(changeType: ChangeType.PosSpike, date: curPrice.DateTick, secId: CurSec.SecId, fieldType: FieldType.Bid);
}
}
prevPrice = curPrice;
}
}
}
static ChangeType determineChangeType(decimal prevPrice, decimal curPrice, double changeThreshold)
{
double changeAmount = (double)(100 * (curPrice - prevPrice) / prevPrice);
if (Math.Abs(changeAmount) < changeThreshold)
{
return ChangeType.Unchanged;
}
else
{
if (changeAmount < 0)
{
return ChangeType.Decreasing;
}
else
{
return ChangeType.Growing;
}
}
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FactEventAnalyzer
{
public class Logger
{
private Logger() { }
string logFile;
public Logger(string logFolder)
{
this.logFile = logFolder + "\\FactMiner_" + composeDateString(mode: 0) + ".log";
}
public string composeDateString(int mode=1)
{
DateTime tempDate = System.DateTime.Now;
if (mode == 0)
{
return (tempDate.Year.ToString() + tempDate.Month.ToString("D2") + tempDate.Day.ToString("D2")
+ "T" + tempDate.Hour.ToString("D2") + tempDate.Minute.ToString("D2") + tempDate.Second.ToString("D2")
+ "_" + tempDate.Millisecond.ToString("D3"));
}
else
{
return (tempDate.Year.ToString() + "." + tempDate.Month.ToString("D2") + "." + tempDate.Day.ToString("D2")
+ "T" + tempDate.Hour.ToString("D2") + ":" + tempDate.Minute.ToString("D2") + ":" + tempDate.Second.ToString("D2")
+ "." + tempDate.Millisecond.ToString("D3"));
}
}
public void writeToLog(string logMessage)
{
System.IO.StreamWriter file = new System.IO.StreamWriter(this.logFile, true);
file.WriteLine(composeDateString() + " " + logMessage);
file.Close();
}
}
}
<factEventAnalyzer>
<logging>
<logFolder path="D:\DiplomaProject\AnalyzingWorkFolder\log"/>
</logging>
<factMinerSettings>
<changeThreshold value="0.3"/>
<securities from="0" to="99999999"/>
<dates from="0" to="99999"/>
<spikeThreshold value="6"/>
<dateThreshold value = "3"/>
<filesForMl value="D:\DiplomaProject\FilesForML"/>
</factMinerSettings>
</factEventAnalyzer>
USE [FactEventAnalysisDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_populateCoraxFactsTable]
@DateFrom int = 0,
@DateTo int = 999999999,
@SecIdFrom int = 0,
@SecIdTo int = 999999999,
@SecuritiesInserted int = 0 output,
@CoraxesInserted int = 0 output
AS
BEGIN
create table #Inserted
(
UniqueCoraxId int not null,
SecId int not null
)
insert into dbo.CoraxFactsTable
(
SecId,
UniqueCoraxId,
EffectiveDate,
ReportedDate,
EventType,
EventMajorType
)
output
inserted.UniqueCoraxId,
inserted.SecId
into #Inserted
SELECT
c.SecId as SecId,
c.UniqueCoraxId as UniqueCoraxid,
CAST(c.EffectiveDate as int) as EffectiveDate,
CAST(c.ReportedDate as int) as ReportedDate,
c.EventType as EventType,
c.EventMajorType as EventMajorType
from
dbo.CoraxesSource as c
where
c.SecId >= @SecIdFrom
and c.SecId <= @SecIdTo
and (
(c.ReportedDate>=@DateFrom
and c.ReportedDate<=@DateTo)
or
(c.EffectiveDate>=@DateFrom
and c.EffectiveDate<=@DateTo)
)
select @CoraxesInserted = COUNT(*)
from
#Inserted
select
@SecuritiesInserted = COUNT(t.SecId)
from
(select distinct SecId from #Inserted) as t
END
GO
USE [FactEventAnalysisDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[usp_clearFactsTables]
@DateFrom int = 0,
@DateTo int = 999999999,
@SecIdFrom int = 0,
@SecIdTo int = 999999999,
@IsPrices bit = 0,
@IsCorax bit = 0,
@IsSpike bit = 0
AS
BEGIN
IF (@IsPrices = 1)
BEGIN
delete from
dbo.PriceFactsTable
where
SecId >= @SecIdFrom
and SecId <= @SecIdTo
and (
(DateStart >= @DateFrom
and DateStart <=@DateTo)
or
(DateEnd <= @DateTo
and DateEnd >= @DateFrom))
END
IF (@IsSpike = 1)
BEGIN
delete from
dbo.SpikeFactsTable
where
SecId >= @SecIdFrom
and SecId <= @SecIdTo
and [Date] >= @DateFrom
and [Date] <= @DateTo
END
IF (@IsCorax = 1)
BEGIN
delete from
dbo.CoraxFactsTable
where
SecId >= @SecIdFrom
and SecId <= @SecIdTo
and (
(ReportedDate>=@DateFrom
and ReportedDate<=@DateTo)
or
(EffectiveDate>=@DateFrom
and EffectiveDate<=@DateTo))
END
END
GO
USE [FactEventAnalysisDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [dbo].[SpikeFactsHypothesis] as
SELECT
cf.EventType as [CoraxEventType]
,cf.EventMajorType as [CoraxEventMajorType]
,csec.Ticker as [CoraxTicker]
,csec.Issuer as [CoraxIssuer]
,csec.SecType as [CoraxSecType]
,csec.InvType as [CoraxInvType]
,csec.PriceCurrency as [CoraxPriceCurrency]
,csec.Region as[CoraxRegion]
,csec.IssuerCountry as [CoraxIssuerCountry]
,csec.Exchange as [CoraxExchange]
,csec.IndustrySector as [CoraxIndustrySector]
,sf.SpikeType as [ChangeType]
,ssec.Ticker as [ChangeTicker]
,ssec.Issuer as [ChangeIssuer]
,ssec.SecType as [ChangeSecType]
,ssec.InvType as [ChangeInvType]
,ssec.PriceCurrency as [ChangePriceCurrency]
,ssec.Region as [ChangeRegion]
,ssec.IssuerCountry as [ChangeIssuerCountry]
,ssec.Exchange as [ChangeExchange]
,ssec.IndustrySector as [ChangeIndustrySector]
FROM
dbo.CoraxFactsTable as cf
join dbo.SecuritiesSource as csec on cf.SecId = csec.SecId
join dbo.Spikefactstable as sf on cf.EffectiveDate = sf.[Date]
join dbo.SecuritiesSource as ssec on sf.SecId = ssec.SecId
where
sf.FieldType = 'Price'
GO
USE [FactEventAnalysisDB]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE view [dbo].[PriceFactsHypothesis] as
SELECT
cf.EventType as [CoraxEventType]
,cf.EventMajorType as [CoraxEventMajorType]
,csec.Ticker as [CoraxTicker]
,csec.Issuer as [CoraxIssuer]
,csec.SecType as [CoraxSecType]
,csec.InvType as [CoraxInvType]
,csec.PriceCurrency as [CoraxPriceCurrency]
,csec.Region as[CoraxRegion]
,csec.IssuerCountry as [CoraxIssuerCountry]
,csec.Exchange as [CoraxExchange]
,csec.IndustrySector as [CoraxIndustrySector]
,sf.ChangeType as [ChangeType]
,ssec.Ticker as [ChangeTicker]
,ssec.Issuer as [ChangeIssuer]
,ssec.SecType as [ChangeSecType]
,ssec.InvType as [ChangeInvType]
,ssec.PriceCurrency as [ChangePriceCurrency]
,ssec.Region as [ChangeRegion]
,ssec.IssuerCountry as [ChangeIssuerCountry]
,ssec.Exchange as [ChangeExchange]
,ssec.IndustrySector as [ChangeIndustrySector]
FROM
dbo.CoraxFactsTable as cf
join dbo.SecuritiesSource as csec on cf.SecId = csec.SecId
join dbo.PriceFactstable as sf on cf.EffectiveDate = sf.[DateStart]
join dbo.SecuritiesSource as ssec on sf.SecId = ssec.SecId
where
sf.FieldType = 'Price'
GO
from __future__ import division
import sys
import csv as csv
import numpy as np
import pandas as pd
from pandas import DataFrame
import sklearn
from sklearn.preprocessing import LabelEncoder
from sklearn.cross_validation import train_test_split
from sklearn.grid_search import GridSearchCV
from sklearn.metrics import classification_report
from sklearn.svm import SVC
from sklearn.svm import LinearSVC
from sklearn import cross_validation, svm, tree
from sklearn.naive_bayes import MultinomialNB
from sklearn.pipeline import Pipeline
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.neighbors import KNeighborsClassifier
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.multiclass import OneVsRestClassifier
from sklearn import preprocessing
from sklearn.linear_model import SGDClassifier
import random
import warnings
from datetime import datetime
from sklearn.grid_search import GridSearchCV
warnings.filterwarnings("ignore")
selClassifiers = {
'linear': LinearSVC(),
'linearWithSGD': SGDClassifier(),
'rbf': SVC(kernel='rbf', probability=True),
'poly': SVC(kernel='poly', probability=True),
'sigmoid': SVC(kernel='sigmoid', probability=True),
'bayes': MultinomialNB()
}
classifierDescriptions = {
'linearWithSGD': 'linear SVM with SGD training',
'linear': 'linear SVM without SGD training',
'rbf': 'SVM with RBF kernel',
'poly': 'SVM with polynomial kernel',
'sigmoid': 'SVM with sigmoid kernel',
'bayes': 'Naive Bayes classifier'
}
def replacer(text):
return str(str(text).replace("u'",'').replace("'", ''))
def workMode(fileIn, toPredict, fileOut, classif):
work = pd.read_csv(fileIn, header = 0, encoding='utf-8-sig')
work_test = pd.read_csv(toPredict, header = 0, encoding='utf-8-sig')
X_train = []
y_train = []
X_test = []
for i in work[[i for i in list(work.columns.values) if i.startswith('Change')]].values:
X_train.append(','.join(i.T.tolist()))
X_train = np.array(X_train)
for i in work[[i for i in list(work.columns.values) if i.startswith('Corax')]].values:
y_train.append(list(i))
for i in work_test[[i for i in list(work_test.columns.values) if i.startswith('Change')]].values:
X_test.append(','.join(i.T.tolist()))
X_test = np.array(X_test)
lb = preprocessing.MultiLabelBinarizer()
Y = lb.fit_transform(y_train)
print ("Getting results of classifier")
classifier = Pipeline([('vectorizer', CountVectorizer()),('tfidf', TfidfTransformer()),('clf', OneVsRestClassifier(selClassifiers[classif]))])
classifier.fit(X_train, Y)
predicted = classifier.predict(X_test)
all_labels = lb.inverse_transform(predicted)
df = DataFrame.from_items([('Change', X_test), ('Prediction',all_labels)])
df.Prediction = df.Prediction.map(replacer)
df.to_csv(fileOut)
def testMode(fileIn, fileOut, classif):
df = pd.read_csv(fileIn, header = 0, encoding='utf-8-sig')
rows = random.sample(list(df.index), int(len(df) * 0.9))
work = df.ix[rows]
work_test = df.drop(rows)
X_train = []
y_train = []
X_test = []
y_test = []
for i in work[[i for i in list(work.columns.values) if i.startswith('Change')]].values:
X_train.append(','.join(i.T.tolist()))
X_train = np.array(X_train)
for i in work[[i for i in list(work.columns.values) if i.startswith('Corax')]].values:
y_train.append(list(i))
for i in work_test[[i for i in list(work_test.columns.values) if i.startswith('Change')]].values:
X_test.append(','.join(i.T.tolist()))
X_test = np.array(X_test)
for i in work_test[[i for i in list(work_test.columns.values) if i.startswith('Corax')]].values:
y_test.append(list(i))
lb = preprocessing.MultiLabelBinarizer()
Y = lb.fit_transform(y_train)
print ("Getting results of %s" % classifierDescriptions[classif])
classifier = Pipeline([('vectorizer', CountVectorizer()),('tfidf', TfidfTransformer()),('clf', OneVsRestClassifier(selClassifiers[classif]))])
classifier.fit(X_train, Y)
predicted = classifier.predict(X_test)
all_labels = lb.inverse_transform(predicted)
df = DataFrame.from_items([('Test', X_test), ('RealAnswer', y_test), ('Prediction',all_labels)])
CorPred = 0
Total = 0
for classifying, item, labels in zip(X_test, y_test, all_labels):
for res in labels:
if res in item:
CorPred+=1
Total+=len(labels)
print('Predicted correctly %s labels out of %s labels' % (CorPred, Total))
print('Precision is %.2f %%' % (100*float(CorPred)/float(Total)))
df.Prediction = df.Prediction.map(replacer)
df.RealAnswer = df.RealAnswer.map(replacer)
df.to_csv(fileOut)
def main():
start = datetime.now()
print("Program started at %s" % start)
if sys.argv[1] == 'test':
testMode(sys.argv[2], sys.argv[3], sys.argv[4])
elif sys.argv[1] == 'work':
workMode(sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5])
else:
print('Unknown mode, only test or work modes are available')
end = datetime.now()
print("Program finished at %s" % end)
print("It took %s seconds for program to complete" % (end - start).total_seconds())
if __name__ == '__main__':
main()
PAGE \* MERGEFORMAT 1