English 中文 Español Deutsch 日本語 Português
preview
Нейросети — это просто (Часть 14): Кластеризация данных

Нейросети — это просто (Часть 14): Кластеризация данных

MetaTrader 5Примеры | 17 мая 2022, 13:48
4 621 0
Dmitriy Gizlyk
Dmitriy Gizlyk

Содержание

Введение

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

Но есть и другой подход в методах обучения искусственного интеллекта — обучение без учителя. Данный метод позволяет обучать модели только на основе исходных данных без наличия эталонных значений. Это позволяет снизить трудозатраты на стадии подготовки обучающей выборки. Что даёт нам возможность использовать больше исходных данных для обучения модели. Но при этом и ограничивается спектр решаемых задач.

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

1. Обучение без учителя

В области развития алгоритмов искусственного интеллекта принято выделять 3 отдельных направления:

  • обучение с учителем,
  • обучение без учителя,
  • обучение с подкреплением.

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

Но что делать, когда исходных данных много, а знаний о них мало. Когда мы не можем сопоставить каждому состоянию изучаемого процесса какое-то однозначное эталонное значение. Или даже мы не знаем какое оно должно быть это эталонное значение. Такая ситуация часто возникает при первичном знакомстве с большим объемом данных. И вместо того, чтобы затрачивать ресурсы на поиск эталонных значений для каждого состояния системы мы обращаем свой взор в сторону обучения без учителя. И тут надо сказать, что в зависимости от поставленной задачи, в результате обучения модели без учителя мы можем получить как решение задачи, так и предварительную обработку исходных данных.

Сразу надо сказать, задачи, решаемые методами обучения с учителем и без сильно отличаются. К примеру, в случае обучения без учителя нельзя решить задачи регрессии. В какой-то мере можно сравнить задачи классификации, решаемые методом обучения с учителем, и задачи кластеризации, решаемые алгоритмами обучения без учителя. Но за схожим значением почти слов-синонимом, кроется абсолютно разная логика. И часто, оба метода могут дать абсолютно разные результаты. Если в случае классификации с учителем мы предлагаем модели выучить какое из состояний системы соответствует какому классу. То в случае кластеризации без учителя мы предлагаем модели самостоятельно определить к какому кластеру отнести состояние системы за совокупностью признаков, описывающих данное состояние. При этом, в начале работы мы можем даже не знать количество таких кластеров. Их количество является гиперпараметром системы и может подбираться в процессе обучения модели.

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

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

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

Как видите, алгоритмы обучения без учителя позволяют решать различные задачи. Но каким образом мы сможем использовать их в своей торговле? Давайте подумаем. При использовании методов графического анализа мы практически всегда говорим о тех или иных графических паттернах: двойная вершина / двойное дно, голова-плечи, флаг, различные гармонические паттерны и т.д. Есть и более мелкие свечные паттерны, состоящие из 1-3 свечей. И, практически всегда, при попытке описания того или иного паттерна математическим языком мы сталкиваемся с большим количеством условностей и допусков. Что усложняет их использование в алготрейдинге. Надо сказать, что и при определении паттернов трейдером-человеком много субъективизма. Именно поэтому, анализируя один и тот же график различные трейдеры находят на нём различные паттерны, часто имеющие противоположную направленность прогнозного движения. Да, я согласен, на этом и построена вся система торговли. Кто-то получил прибыль, а кто-то убыток. В процессе торговли новые товарно-материальные ценности не создавались, и денежная масса осталась неизменной. Она лишь перекочевала из одного кошелька в другой. Но как нам не остаться в убытке?

Паттерн Голова-Плечи

Давайте посмотрим ещё раз на упомянутые выше графические паттерны. Да, все они имеют свои допуски. Но вместе с тем, каждый паттерн имеет свою определённую структуру, которая выделяет его из общего графика движения цены. А что, если мы воспользуемся алгоритмами кластеризации данных без учителя и поручим модели выделить все возможные вариации данных на некотором временном отрезке. Так как мы используем обучение без учителя, то нам не надо производить разметку данных и временной отрезок может быть довольно большим. Разумеется, мы должны помнить, что с увеличением временного отрезка истории растут и затраты на обучение модели.

2. Алгоритм k-средних (k-means)

Для решения задачи кластеризации, предложенной выше, мы воспользуемся одним из наиболее простых и понятных методов k-средних (k-means). Несмотря на простоту, метод является довольно эффективным в решении задач кластеризации данных и может использоваться как самостоятельно, так и для предварительной обработки данных.

Для использования метода каждое состояние изучаемой системы описывается неким набором данных, собранных в единый вектор. Каждый такой вектор представляет собой координаты некой точки в N-мерном пространстве. Где размерность пространства равна размерности вектора описания состояния системы.

Исходные данные на плоскости

Суть метода заключается в поиске таких центров (векторов), вокруг которых можно объединить все известные состояния системы в кластеры. При этом среднее расстояние всех состояний системы до центра соответствующего кластера должно быть минимальным. Как можно заметить, отсюда и название метода k-средних. Количество таких кластеров является гиперпараметром модели и определяется на стадии её проектирования или валидации.

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

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

На рисунке выше представлена визуализация случайных 100 точек на плоскости. Визуализация данных полезна для понимания их структуры, но не обязательна для рассматриваемого метода. Как Вы можете заметить, точки расположены довольно равномерно по всей плоскости и мы не можем визуально явно выделить какие-либо кластеры. А тем более их количество. Для первого эксперимента возьмем, к примеру, 5 кластеров.

Что ж, с количеством определились. А где же нам расположить их центры. Помните, при инициализации весовых коэффициентов мы заполняли матрицы случайными значениями. Здесь мы поступим приблизительно также. Только мы не будем генерировать случайные вектора, так как они могут оказаться в стороне от наших исходных данных. Мы просто возьмем 5 случайных точек из нашей обучающей выборки. На рисунке ниже они обозначены Х.

Добавляем центры кластеров

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

Формула определения расстояния между точками

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

Первая итерация 

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

Итерации повторяем в цикле до тех пор, пока центры кластеров не перестанут перемещаться. И как следствие, точки не будут менять принадлежность к тому или иному кластеру от итерации к итерации.

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

Финальное распределение  

Давайте обобщим рассмотренный алгоритм:

  1. Определяем k случайных точек из обучающей выборки в качестве центров кластеров.
  2. Организовываем цикл операций:
    • Определяем расстояние от каждой точки до каждого центра;
    • По ближайшему центру определяем принадлежность точки к кластеру;
    • Арифметическим средним определяем новый центр для каждого кластера.
  3. Операции в цикле повторяем до "остановки" центров кластера.

Надо сказать, что так как нас не интересует конкретное расстояние от точки до центра, а лишь какое из них меньше. То для экономии ресурсов при расчете расстояния мы не будем извлекать квадратный корень из полученной суммы, что абсолютно не повлияет на результат кластеризации данных.

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

Функция потерь

где:

  • m — количество элементов в обучающей выборке,
  • N — размерность вектора описания одного элемента из обучающей выборки,
  • Xi ji-тое значение вектора описания j-го элемента обучающей выборки,
  • Ci x ji-тое значение центрального вектора кластера к которому принадлежит j-тый элемент обучающей выборки.

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

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

Зависимость ошибки от количества кластеров

На приведённом графике четко видно, что при изменении количества кластеров от 2 до 4 значение функции ошибки резко снижается. При дальнейшем увеличении числа кластеров до 6 темпы снижения значения функции ошибки постепенно снижаются. А при изменении числа кластеров с 6 до 7 значение функции ошибки практически не меняется. Здесь мы видим сглаженное изменение функции потерь. Но иногда можно встретить ломанное изменение графика в конкретной точке. Такое явление чаще всего встречается, когда данные обучающей выборки четко разделимы.

Общее же правило трактовки графика следующее:

  • При наличии ломанной линии графика оптимальным количеством кластеров принимается в месте перелома;
  • При сглаженном графике мы выбираем из баланса качества и производительности в зоне изгиба.  

Для приведенного выше примера, учитывая небольшие размеры выборки и количество кластеров, я бы рекомендовал использовать 5 или 6 кластеров.

3. Реализация средствами Python

Мы познакомились с теоретическими аспектами работы метода k-средних на примере абстрактных данных. И стает резонный вопрос, а как метод будет работать на реальных данных. Чтобы ответить на этот вопрос мы воспользуемся возможностью интеграции MetaTrader 5 и Python. Как известно, именно Python обладает большим количеством библиотек, способных удовлетворить практически любые потребности.

О средствах интеграции уже не раз говорилось на этом сайте, в том числе процедура инсталляции библиотек описана в документации.

3.1. Подключаемые библиотеки

Для реализации поставленной задачи мы будем использовать несколько библиотек. И прежде всего, это библиотека MetaTrader5. Именно средствами этой библиотеки организовываются все точки интеграции терминала MetaTrader 5 и Python.

Вторая библиотека, которая нам потребуется это Scikit-Learn. Одна из тех, которые предлагают простые и эффективные средства для анализа данных. Среди прочего, в ней реализованы несколько алгоритмов кластеризации данных. В том числе и рассматриваемый нами метод k-средних.

Визуализацию данных мы реализуем с помощью библиотеки Matplotlib.

Средства интеграции MetaTrader 5 и Python позволяют передавать в скрипты различную информацию о состоянии счета, торговых операциях и рыночной ситуации. Но не позволяют использовать данные внутренних программ, таких как индикаторы. Поэтому, всю реализацию индикаторов нужно будет повторить на стороне Python. И здесь, для облегчения нашего с вами труда мы воспользуемся библиотекой TA-Lib, которая предлагает различные средства технического анализа.

Разумеется, перед созданием скрипта все используемые библиотеки и интерпретатор языка Python должны быть установлены на Вашем компьютере. Этот процесс выходит за рамки данной статьи. Но если у кого-то возникнут трудности, я по возможности отвечу в комментариях к статье. 

3.2. Создание скрипта

Теперь, когда мы определились со списком используемых библиотек, можно приступать к написанию скрипта. Код скрипта мы сохраним в файл "clustering.py".

В начале скрипта мы подключаем необходимые библиотеки.

# Импорт библиотек
import numpy as np
import matplotlib.pyplot as plt
import MetaTrader5 as mt5
from talib import abstract as tl
import sklearn.cluster as cluster
from datetime import datetime as dt

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

# Подключаемся к терминалу MetaTrader 5
if not mt5.initialize():
    print("initialize() failed, error code =",mt5.last_error())
    quit()

При успешном подключении к терминалу мы загрузим исторические данные за анализируемый период и отключимся от терминала.

# Загрузка котировок
rates=mt5.copy_rates_range('EURUSD',mt5.TIMEFRAME_H1,dt(2006,1,1),dt(2022,1,1))
mt5.shutdown()

Теперь, когда мы получили исторические данные, переходим к определению значений индикаторов. В этом блоке мы вычислим значения тех же индикаторов, которые мы использовали при тестировании различных моделей с использованием обучения с учителем. Это классические осцилляторы RSI, CCI и MACD.

# Рассчитаем значение индикаторов
rsi=tl.RSI(rates['close'])
cci=tl.CCI(rates['high'],rates['low'],rates['close'])
macd,macdsignal,macdhist=tl.MACD(rates['close'])

И вот у нас есть исходные данные, но они разделены на 6 тензоров. Для проведения анализа нам необходимо объединить их в единый тензор. При этом надо обратить внимание еще на один момент. Функция кластеризации построена таким образом, что на вход получает 2-мерный массив, строки которого рассматриваются как отдельные паттерны. Объединив все тензоры в один, мы также получаем 2-мерный массив, каждая строка которого содержит информацию об одной отдельной свече. Мы, конечно, можем его использовать в таком виде. Но тогда мы осуществим кластеризацию отдельных свечей. Но насколько эта информация будет нам полезна? Если же мы хотим искать паттерны из нескольких свечей, то нам надо изменить размерность тензора. В то же время простое изменение размерности не совсем удовлетворяет нашим требованиям. Это сравнимо с использованием скользящего окна с шагом перемещения равным его размеру. Но нам бы хотелось понимать паттерн на каждой свече. Поэтому нам потребуется переформатирование тензора с копированием данных. Ниже представлен пример кода объединения тензора с последующим копированием данных для составления паттерна из 20 свечей. Надо отметить обрезание исторических данных, где значения индикаторов не определены.

# Сгруппируем обучающую выборку
data=np.array([rates['close']-rates['open'],rates['high']-rates['close'],rates['close']-rates['low'],
                                                                   rsi,cci,macd,macdsignal,macdhist]).T
s=data.shape[0]
data=np.hstack([data[40+k:s-20+k] for k in range(0,20)])

На этом завершается процесс подготовки данных и можем осуществить кластеризацию данных. Но чтобы оценить необходимое количество кластеров нам необходимо провести несколько тестирований с различным количеством кластеров. В данном примере я провел кластеризацию для диапазона от 50 до 1000 кластеров с шагом в 50 кластеров.

# Проведем кластеризацию с различным количеством кластеров
R=range(50,1000,50)
KM = (cluster.KMeans(n_clusters=k).fit(data) for k in R)

В заключении определим ошибку для каждого случая и визуализируем полученные данные.

distance=(k.transform(data) for k in KM)                      
dist = (np.min(D, axis=1) for D in distance)
avgWithinSS = [sum(d) / data.shape[0] for d in dist]
# Отрисовка результатов обучения модели
plt.plot(R, avgWithinSS)
plt.xlabel('$Clasters$')
plt.title('Loss dynamic')
# Вывод созданных графиков
plt.show()

На этом мы завершаем работу с кодом скрипта и переходим к тестированию. С полным кодом скрипта можно ознакомиться во вложении.

4. Тестирование

Мы создали скрипт на языке Python и можем провести его тестирование. Все параметры тестирования указаны в коде скрипта:

  • Инструмент EURUSD;
  • Таймфрейм H1;
  • Исторический интервал в 16 лет с 01.01.2006 до 01.01.2022
  • Количество кластеров от 50 до 1000 с шагом 50

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

Влияние количества кластеров на ошибку модели

Как видно на графике излом оказался довольно растянут. Оптимальным представляется использование от 400 до 500 кластеров. При этом надо сказать, что было проанализировано 98641 состояний системы.

Заключение

В данной статье мы познакомились с методом кластеризации данных k-средних, который является одним из алгоритмов обучения без учителя. Мы создали скрипт с использование библиотек Python и провели обучение модели с различным количеством кластеров. По результатам тестирования можно сделать вывод, что модель смогла выделить около 500 паттернов. Конечно, мы понимаем, что не все они дадут четкие сигналы к торговым операциям. Но о том, как можно на практике использовать полученные результаты мы поговорим в следующих статьях.


Ссылки

  1. Нейросети  — это просто
  2. Нейросети  — это просто (Часть 2): обучение и тестирование сети
  3. Нейросети  — это просто (Часть 3): сверточные сети
  4. Нейросети  — это просто (Часть 4): рекуррентные сети
  5. Нейросети  — это просто (Часть 5): многопоточные вычисления в OpenCL
  6. Нейросети — это просто (Часть 6): эксперименты с коэффициентом обучения нейронной сети
  7. Нейросети — это просто (Часть 7): Адаптивные методы оптимизации
  8. Нейросети — это просто (Часть 8): Механизмы внимания
  9. Нейросети — это просто (Часть 9): Документируем проделанную работу
  10. Нейросети — это просто (Часть 10): Multi-Head Attention (многоголовое внимание)
  11. Нейросети — это просто (Часть 11): Вариации на тему GPT
  12. Нейросети — это просто (Часть 12): Dropout
  13. Нейросети — это просто (Часть 13): Пакетная нормализация (Batch Normalization)

Программы, используемые в статье

# Имя Тип Описание
1 clustering.py Скрипт Скрипт кластеризации данных на Python







Прикрепленные файлы |
clustering.py (1.74 KB)
Разработка торгового советника с нуля (Часть 7): Добавляем Volume At Price (I) Разработка торгового советника с нуля (Часть 7): Добавляем Volume At Price (I)
Это один из самых мощных индикаторов из существующих. Те, кто торгует и старается иметь определенную степень уверенности, не могут не иметь этот индикатор на своем графике. Хотя чаще всего его используют те, кто торгует, наблюдая за лентой сделок («tape reading»). Также этот индикатор могут использовать и те, кто использует только Price Action.
Разработка торговой системы на основе индикатора RSI Разработка торговой системы на основе индикатора RSI
В этой статье мы поговорим об еще одном популярном и часто используемом индикаторе — RSI. Узнаем, как разработать торговую систему на основе показателей от этого индикатора.
DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize DoEasy. Элементы управления (Часть 5): Базовый WinForms-объект, элемент управления "Панель", параметр AutoSize
В статье создадим базовый объект всех WinForms-объектов библиотеки и приступим к реализации свойства AutoSize WinForms-объекта "Панель" — автоизменение размера под его внутреннее содержимое.
Несколько индикаторов на графике (Часть 06): Превращаем MetaTrader 5 в систему RAD (II) Несколько индикаторов на графике (Часть 06): Превращаем MetaTrader 5 в систему RAD (II)
В предыдущей статье я показал, как создать Chart Trade с использованием объектов MetaTrader 5 и превратить платформу в систему RAD. Система работает очень хорошо, и наверняка многие задумывались о создании библиотеки — она позволит иметь всё больше и больше функциональности в предлагаемой системе, и можно будет разработать более интуитивно понятный советник с более приятный и простым в использовании интерфейсом.