Мотивация

Преобразователи стали популярным выбором для прогнозирования временных рядов из-за их способности фиксировать сложные временные отношения. Однако недавние исследования показали, что простые модели глубокой нейронной сети (DNN) превосходят модели Transformer, такие как FEDFormer и Informer, в определенных задачах прогнозирования временных рядов [1]. Я также написал статью, объясняющую результаты эксперимента.



Для решения этой проблемы в недавней статье ВРЕМЕННОЙ РЯД СТОИТ 64 СЛОВ: ДОЛГОСРОЧНОЕ ПРОГНОЗИРОВАНИЕ С ТРАНСФОРМАТОРАМИ [2] была представлена ​​новая архитектура Transformer под названием PatchTST. Модель PatchTST включает в себя две ключевые концепции прогнозирования временных рядов: независимость канала и исправление.

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

В исходной статье PatchTST авторы оценили эффективность модели в задачах прогнозирования многоканальных временных рядов, где как входные, так и выходные данные являются многоканальными. Чтобы продемонстрировать превосходство PatchTST над другими моделями, авторы сравнили ее производительность с производительностью простой модели DNN, DLinear, которая превзошла модели Transformer в предыдущем исследовании.

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

Ключевые понятия PatchTST

В основном, в модели PatchTST есть две важные концепции: «исправление» и «независимость канала».

Исправление

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

Независимость канала

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

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

Результаты в оригинальной статье

В исходной статье авторы провели обширный эксперимент для оценки производительности модели PatchTST на нескольких наборах данных. Они экспериментировали с четырьмя шаблонами длин выходных последовательностей и сравнивали результаты с DLinear.

Результаты показали, что модель PatchTST значительно превосходит модель DLinear с точки зрения точности. Кроме того, было обнаружено, что PatchTST превосходит другие модели Transformer. Авторы заметили, что способность модели PatchTST фиксировать долгосрочные зависимости и ее свойство независимости от канала сыграли решающую роль в достижении лучших результатов. Эти результаты показывают, что PatchTST является многообещающей моделью для задач прогнозирования временных рядов, и ее применение может привести к значительному повышению точности прогнозирования.

Мои эксперименты с одним выходным каналом

В этой части я расскажу о своих экспериментах с PatchTST. Для этого я выбрал набор данных ETT [3] и сосредоточился на одном выходном канале, а именно на столбце Температура масла. В отличие от оригинальной статьи, где выходные последовательности были многоканальными, я реконструировал PatchTST для вывода одноканальной последовательности. Реконструкция была простым процессом и включала изменение последнего полносвязного слоя.

# Implementation of the original final layer
class Flatten_Head(torch.nn.Module):
    def __init__(self, n_vars, nf, target_window, head_dropout=0):
        super().__init__()
        self.n_vars = n_vars
        self.flatten = torch.nn.Flatten(start_dim=-2)
        self.linear = torch.nn.Linear(nf, target_window)
        self.dropout = torch.nn.Dropout(head_dropout)
            
    def forward(self, x):                                 # x: [bs x nvars x d_model x patch_num]
        x = self.flatten(x)                               # x: [bs x nvars x d_model *patch_num]
        x = self.linear(x)                                # x: [bs x nvars x target_window]
        x = self.dropout(x)
        return x

# Implementation for single channel output
class Flatten_Head_For_Single_Output(torch.nn.Module):
    def __init__(self, n_vars, nf, target_window, head_dropout=0):
        super().__init__()
        self.n_vars = n_vars
        self.flatten = torch.nn.Flatten(start_dim=-3)
        self.linear = torch.nn.Linear(nf * n_vars, target_window) 
        self.dropout = torch.nn.Dropout(head_dropout)
            
    def forward(self, x):                                 # x: [bs x nvars x d_model x patch_num]
        x = self.flatten(x)                               # x: [bs x nvars * d_model * patch_num]
        x = self.linear(x)                                # x: [bs x target_window]
        x = self.dropout(x)
        return x

#Implementation of PatchTST
class PatchTST(torch.nn.Module):

    def __init__(self, c_in, context_window, target_window, patch_len, stride, max_seq_len=1024, 
                 n_layers=3, d_model=16, n_heads=4, d_k=None, d_v=None,
                 d_ff=128, attn_dropout=0.0, dropout=0.3, key_padding_mask="auto",
                 padding_var=None, attn_mask=None, res_attention=True, pre_norm=False, store_attn=False,
                 head_dropout = 0.0, padding_patch = "end",
                 revin = True, affine = False, subtract_last = False,
                 verbose=False, target_idx=-1, **kwargs):
        super().__init__()

        self.revin = revin
        if revin:
            self.revin_layer = RevIN(c_in, affine=affine, subtract_last=subtract_last, target_idx=target_idx)

        self.patch_len = patch_len
        self.stride = stride
        self.padding_patch = padding_patch
        patch_num = int((context_window - patch_len)/stride + 1)

        if padding_patch == "end":
            self.padding_patch_layer = torch.nn.ReplicationPad1d((0, stride))
            patch_num += 1

        self.backbone = TSTiEncoder(c_in, patch_num=patch_num, patch_len=patch_len, max_seq_len=max_seq_len,
                                n_layers=n_layers, d_model=d_model, n_heads=n_heads, d_k=d_k, d_v=d_v, d_ff=d_ff,
                                attn_dropout=attn_dropout, dropout=dropout, key_padding_mask=key_padding_mask, padding_var=padding_var,
                                attn_mask=attn_mask, res_attention=res_attention, pre_norm=pre_norm, store_attn=store_attn,
                                verbose=verbose, **kwargs)
        
        self.head_nf = d_model * patch_num
        self.n_vars = c_in

        self.head = Flatten_Head_For_Single_Output(self.n_vars, self.head_nf, target_window, head_dropout=head_dropout)
        
        
    def forward(self, z):                                                                   # z: [bs x seq_len × nvars]
        # instance norm
        if self.revin:                                                          
            z = self.revin_layer(z, "norm")
            z = z.permute(0,2,1)                                                            # z: [bs x nvars × seq_len]
            
        # patching
        if self.padding_patch == "end":
            z = self.padding_patch_layer(z)
        z = z.unfold(dimension=-1, size=self.patch_len, step=self.stride)                   # z: [bs x nvars x patch_num x patch_len]
        z = z.permute(0,1,3,2)                                                              # z: [bs x nvars x patch_len x patch_num]
        
        # model
        z = self.backbone(z)                                                                # z: [bs x nvars x d_model x patch_num]
        z = self.head(z)                                                                    # z: [bs x target_window] 
        
        # denorm
        if self.revin:                                                         
            z = self.revin_layer(z, "denorm")
        return z

Чтобы оценить производительность одноканальной модели PatchTST, я сравнил ее как с Linear, так и с DLinear, которые реализованы следующим образом.

#Linear model
class Linear(torch.nn.Module):
    def __init__(self, c_in, context_window, target_window):
        super().__init__()
        self.c_in = c_in
        self.context_winsoq = context_window
        self.target_window = target_window

        self.flatten = torch.nn.Flatten(start_dim=-2)

        self.linear = torch.nn.Linear(c_in * context_window, target_window)
    
    def forward(self, x):                   # x: [bs x seq_len × nvars]
        x = self.flatten(x)                 # x: [bs x seq_len * nvars]
        x = self.linear(x)                  # x: [bs x target_window]
        return x


class moving_avg(torch.nn.Module):
    def __init__(self, kernel_size, stride):
        super().__init__()
        self.kernel_size = kernel_size
        self.avg = torch.nn.AvgPool1d(kernel_size=kernel_size, stride=stride, padding=0)

    def forward(self, x):
        # padding on the both ends of time series
        front = x[:, 0:1, :].repeat(1, (self.kernel_size - 1) // 2, 1)
        end = x[:, -1:, :].repeat(1, (self.kernel_size - 1) // 2, 1)
        x = torch.cat([front, x, end], dim=1)
        x = self.avg(x.permute(0, 2, 1))
        x = x.permute(0, 2, 1)
        return x


class series_decomp(torch.nn.Module):
    def __init__(self, kernel_size):
        super().__init__()
        self.moving_avg = moving_avg(kernel_size, stride=1)

    def forward(self, x):
        moving_mean = self.moving_avg(x)
        res = x - moving_mean
        return res, moving_mean


#DLinear model
class DLinear(torch.nn.Module):
    def __init__(self, c_in, context_window, target_window):
        super().__init__()
        # Decompsition Kernel Size
        kernel_size = 25
        self.decompsition = series_decomp(kernel_size)
        self.flatten_Seasonal = torch.nn.Flatten(start_dim=-2)
        self.flatten_Trend = torch.nn.Flatten(start_dim=-2)
        
        self.Linear_Seasonal = torch.nn.Linear(c_in * context_window, target_window)
        self.Linear_Trend = torch.nn.Linear(c_in * context_window, target_window)

    def forward(self, x):
        # x: [Batch, Input length, Channel]
        seasonal_init, trend_init = self.decompsition(x)
        seasonal_init = self.flatten_Seasonal(x)
        trend_init = self.flatten_Trend(x)

        seasonal_output = self.Linear_Seasonal(seasonal_init)
        trend_output = self.Linear_Trend(trend_init)

        x = seasonal_output + trend_output
        return x

Хотя я не выполнял обширную настройку гиперпараметров, я искал оптимальную скорость обучения для каждой модели.

Результаты моих экспериментов показали, что даже в задаче временных рядов с одним выходом одноканальная модель PatchTST превосходила модели Linear и DLinear.

Помимо сравнения производительности одноканальных PatchTST, Linear и DLinear в наборе данных ETT, я также проанализировал процесс обучения каждой модели. Используя набор данных для проверки, я построил график точности каждой модели в течение эпох обучения.

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

Заключение

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

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

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

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



Рекомендации

[1] А. Цзэн, М. Чен, Л. Чжан и К. Сюй, «Эффективны ли преобразователи для прогнозирования временных рядов?», Материалы конференции AAAI по искусственному интеллекту, 2023 г.

[2] Ю. Ни, Н. Х. Нгуен, П. Синтонг и Дж. Каланьянам, «Временной ряд стоит 64 слов: долгосрочное прогнозирование с помощью преобразователей», в материалах Международной конференции по обучающим представлениям, 2023 г.

[3] Х. Чжоу, С. Чжан, Дж. Пэн, С. Чжан, Дж. Ли, Х. Сюн и В. Чжан, «Информатор: помимо эффективного преобразователя для прогнозирования временных рядов с длинной последовательностью», в материалах 35-я конференция AAAI по искусственному интеллекту, AAAI 2021, виртуальная конференция, том. 35, нет. 12, стр. 11106–11115, 2021.