Прогнозирование временных рядов нейронными сетями.
Постановка задачи: при известных скоростях показа рекламы с 8 до 15 часов, сделать прогноз скорости с 16 до 23 часов.
Архитектура нейронной сети для прогнозирования временных рядов выглядит следующим образом:
3 слоя — входной (8 нейронов — известные скорости масштабированные к 1-це), скрытый слой, выходной слой 7 нейронов — прогнозируемая скорость по часам масштабированные к 1-це.
Максимальная точность прогноза на 7 часов составила 69,1%, при следующих параметрах нейронной сети:
- трехслойная нейронная сеть, при одном скрытом слое;
- входной слой – 8 нейронов, скрытый – 16, выходной – 7 нейронов;
- функция активации скрытого слоя: ELLIOT;
- функция активации выходного слоя: SIN;
- алгоритм обучения нейронной сети:TRAIN_BATCH.
Библиотека для разработки программ с использованием нейронных сетей FANN.
FANN – готовая библиотека для разработки программ, использующих нейронные сети, с открытым исходным кодом, обладает целым рядом достоинств:
- многослойная нейронная сеть, реализованная на языке C;
- поддерживает методы обратного распространения ошибки обучения (RPROP, Quickprop, Batch, Incremental);
- поддерживает эволюционный метод обучения;
- простота в использовании (позволяет создать, обучить и использовать нейронную сеть всего за три вызова функции);
- скорость работы до 150 раз быстрее, чем другие свободно распространяемые библиотеки;
- универсальность (можно настроить множество параметров, как в момент запуска, так и в процессе работы);
- подробная документация;
- кроссплатформеность, мультиязычность (библиотека доступна на более чем 20 языках программирования);
- большой выбор реализованных активаторных функций;
- простота сохранения и загрузки обученных сетей;
- поддерживает кэширование данных;
- открытый исходный код;
- широко используется;
- подходит как для десктопных приложений, так и для серверных и скриптовых решений;
- имеет графический интерфейс;
- занимает всего 300 кб на жестком диске.
Архитектура нейронных сетей: основные характеристики
В общем случае, для первичной настройки нейронной сети необходимо задать ряд обязательных параметров:
- число входов (входных нейронов);
- число скрытых слоев;
- число выходов (выходных нейронов);
- число нейронов в скрытых слоях;
- алгоритм обучения нейронной сети;
- параметры алгоритма обучения (коэффициенты и пределы обучения);
- функции активации для выходного и скрытых слоев.
Выбор подходящей, для каждой конкретной нейронной сети, функции активации и алгоритма обучения является критически важной задачей, от которой, в конечном счете, зависит точность и адекватность прогноза временных рядов.
Библиотека FANN позволяет выбрать для работы один из нескольких классических функций активации:
- линейная функция активации;
- пороговая функция активации;
- сигмоидальная функция активации;
- симметричная сигмоидальная функция активации;
- Гауссова функция активации;
- симметричная Гауссова функция активации;
- функция активации Давида Еллиотта;
- периодическая синусоидальная функция активации.
Библиотека FANN позволяет выбрать в качестве рабочего один из четырех популярных алгоритмов обучения нейронных сетей:
- пошаговый алгоритм обратного распространения ошибки — FANN_TRAIN_INCREMENTAL;
- среднеквадратичный алгоритм обратного распространения ошибки — FANN_TRAIN_BATCH;
- пакетный алгоритм обучения – FANN_TRAIN_RPROP;
- ускоренный пакетный алгоритм обучения –FANN_TRAIN_QUICKPROP.
Пример PHP скрипта, прогнозирующий скорость показов рекламы ВКонтакте:
<? set_time_limit(0); $conn = mysql_connect('', '', ''); mysql_select_db('', $conn); // ------------------------------------------------------ // Не учитывать праздничные и выходные дни в исследовании $weekend_f = false; $stady_end = '09-10-2014'; // ------------------------------------------------------ // Праздничные и выходные дни static $weekend = array ( '07-03-2014', '08-03-2014', '09-03-2014', '10-03-2014', '15-03-2014', '16-03-2014', '22-03-2014', '23-03-2014', '29-03-2014', '30-03-2014', '05-04-2014', '06-04-2014', '12-04-2014', '13-04-2014' ); $weekend_sql = ($weekend_f) ? implode("','", $weekend) : 0; $sql = "SELECT DATE_FORMAT(`time`, '%d-%m-%Y') AS `date`, COUNT(DISTINCT(DATE_FORMAT(`time`, '%d-%m-%Y %H'))) AS `count_h` FROM `vk_stat` WHERE DATE_FORMAT(`time`, '%d-%m-%Y') NOT IN ('$weekend_sql') GROUP BY DATE_FORMAT(`time`, '%d-%m-%Y') HAVING `count_h` = 24 ORDER BY `time`"; $result = mysql_query($sql); while ($row = mysql_fetch_array($result)){ $date_list[] = $row['date']; } $date_list_sql = implode("','", $date_list); $sql = "SELECT DATE_FORMAT(`time`, '%d-%m-%Y') AS `date`, DATE_FORMAT(`time`, '%H') AS `h`, MAX(`sum_spent`) AS `spend`, MAX(`sum_clicks`) AS `click`, ROUND(AVG(`impressions_sum`)) AS `speed` FROM `vk_stat` WHERE DATE_FORMAT(`time`, '%d-%m-%Y') IN ('$date_list_sql') AND (DATE_FORMAT(`time`, '%H') >= 0 AND DATE_FORMAT(`time`, '%H') < 24) GROUP BY DATE_FORMAT(`time`, '%H %d-%m-%Y') ORDER BY `time`"; //echo $sql.'<hr />'; $result = mysql_query($sql); $h = 0; $in = array(); $out = array(); $i = 0; $s_t = 0; $alpha = 0.70; $num_input = 40; $num_output = 8; $click_pre = 0; $price_pre = 0; $price_pre_buf = 0; $clock_stop = 16; while ($row = mysql_fetch_array($result)){ if ($i == 0){ $s_t = $row['speed']; $price_pre = $row['spend']; $click_pre = $row['click']; if (!empty($click_pre)){ $row['click_price'] = round($price_pre/$click_pre, 2); $price_pre_buf = round($price_pre/$click_pre, 2); } $i++; } else { $row['speed'] = ceil($s_t + $alpha*($row['speed']-$s_t)); $price_diff = $row['spend'] - $price_pre; $click_diff = $row['click'] - $click_pre; if (!empty($click_diff)){ $row['click_price'] = round($price_diff/$click_diff, 2); $price_pre_buf = round($price_diff/$click_diff, 2); } else { $row['click_price'] = $price_pre_buf; } $s_t = $row['speed']; $price_pre = $row['spend']; $click_pre = $row['click']; if (((int)$row['h'] == 23)) { $i = 0; } else $i++; } $exchel_data[$row['date']][(int)$row['h']]['s'] = $row['speed']; $exchel_data[$row['date']][(int)$row['h']]['p'] = $row['click_price']; $data_all['d_'.$row['date']][(int)$row['h']] = $row; if (strtotime($row['date']) < strtotime($stady_end)){ // Обучающая выборка if ((int)$row['h'] < $clock_stop){ $stady_array['d_'.$row['date']]['in'][] = $row['speed']; $stady_array['d_'.$row['date']]['in'][] = $row['click_price']*1000; $all[] = $row['speed']; $all[] = $row['click_price']*1000; } else { $stady_array['d_'.$row['date']]['in'][] = $row['click_price']*1000; $stady_array['d_'.$row['date']]['out'][] = $row['speed']; $all[] = $row['speed']; $all[] = $row['click_price']*1000; } } else { // Тестовая выборка if ((int)$row['h'] < $clock_stop){ $test_array['d_'.$row['date']]['in'][] = $row['speed']; $test_array['d_'.$row['date']]['in'][] = $row['click_price']*1000; $all[] = $row['speed']; $all[] = $row['click_price']*1000; } else { $test_array['d_'.$row['date']]['in'][] = $row['click_price']*1000; $test_array['d_'.$row['date']]['out'][] = $row['speed']; $all[] = $row['speed']; $all[] = $row['click_price']*1000; } } } $max = max($all); $min = min($all); $k_size = 1/($max-$min); foreach ($stady_array as $key=>$data){ foreach ($data['in'] as $k=>$r){ $stady_array[$key]['in'][$k] = ($r-$min)*$k_size; } foreach ($data['out'] as $k=>$r){ $stady_array[$key]['out'][$k] = ($r-$min)*$k_size; } } foreach ($test_array as $key=>$data){ foreach ($data['in'] as $k=>$r){ $test_array[$key]['in'][$k] = ($r-$min)*$k_size; } foreach ($data['out'] as $k=>$r){ $test_array[$key]['out'][$k] = ($r-$min)*$k_size; } } $desired_error = 0.0001; $max_epochs = 300; $epochs_between_reports = 300; // Настройка архитектуры нейронной сети $num_neurons_array = array( $num_input, // Входной слой 8, 16, 8, $num_output // Выходной слой ); $ann = fann_create_standard_array(sizeof($num_neurons_array), $num_neurons_array); fann_set_training_algorithm($ann, FANN_TRAIN_RPROP); fann_set_activation_function_hidden($ann, FANN_ELLIOT); fann_set_activation_function_output($ann, FANN_GAUSSIAN); $size_data_count = sizeof($stady_array); $tmp_file = "$size_data_count $num_input $num_output\n"; foreach ($stady_array as $data){ $tmp_file .= implode(' ', $data['in'])."\n"; $tmp_file .= implode(' ', $data['out'])."\n"; } file_put_contents(dirname(__FILE__) . '/tmp_data.tmp', $tmp_file); fann_train_on_file($ann, dirname(__FILE__) . "/tmp_data.tmp", $max_epochs, $epochs_between_reports, $desired_error); $s = 0; foreach ($test_array as $key_arr=>$data_item){ //foreach ($stady_array as $key_arr=>$data_item){ echo '<b>'.$key_arr.'</b><br />'; echo '<table border="1">'; echo '<tr><td><b>Час</b></td><td><b>Реальность</b></td><td><b>Прогноз</b></td><td><b>Цена клика</b></td><td><b>MAPE</b></td></tr>'; $result_arr = fann_run($ann, $data_item['in']); $err_abs = 0; $l_mape_sum = 0; $price_pre = 0; for ($i=0; $i<sizeof($result_arr); $i++){ $err = round(abs($data_item['out'][$i]/$k_size - $result_arr[$i]/$k_size), 3); $err_abs = round($err/($data_item['out'][$i]/$k_size), 3); $real = ceil($data_item['out'][$i]/$k_size); $out = ceil($result_arr[$i]/$k_size); $l_mape = round(100-$err_abs*100); if ($l_mape < 0) $c = '#BB0000'; if ($l_mape >= 0 && $l_mape < 25) $c = '#FFB3B3'; if ($l_mape >= 25 && $l_mape < 50) $c = '#FFFFCE'; if ($l_mape >= 50 && $l_mape < 75) $c = '#FFFF33'; if ($l_mape >= 75 && $l_mape <= 100) $c = '#17FF17'; if ($l_mape < 0) $l_mape = 0; $price = $data_all[$key_arr][$i+$clock_stop]['click_price']; if ($i > 0){ $price_dif = round($price - $price_pre, 2); $price_pre = $price; } else { $price_dif = 0; $price_pre = $price; } echo '<tr><td>'.($i+$clock_stop).'</td><td>'.$real.'</td><td>'.$out.'</td><td>'.$price.' ('.($price_dif >= 0 ? '+'.$price_dif : $price_dif).')</td><td bgcolor="'.$c.'">'.$l_mape.'</td></tr>'; $l_mape_sum += $l_mape; } $mape_step = $l_mape_sum/$i; $mape += $mape_step; $s++; echo '</table>'; echo 'Точность MAPE = '.round($mape_step,3).'<br /><br />'; } $mape = round($mape/$s,3); echo 'Точность ср. MAPE = '.$mape.'<br /><br />'; fann_destroy($ann); $exchel_data_tmp = $exchel_data; echo '<table border="1">'; echo '<tr><td>—</td>'; for ($i=0; $i<24; $i++){ echo '<td>'.$i.'</td>'; } echo '</tr>'; $i=0; foreach ($exchel_data as $date_key => $date_arr){ echo '<tr><td>'.$date_key.'</td>'; for ($i=0; $i<24; $i++){ echo '<td>'.$date_arr[$i]['s'].' <br /><small>('.$date_arr[$i]['p'].')</small></td>'; } echo '</tr>'; $i++; } echo '</table>'; ?>