深度學習技術的核心優勢在於其對向量序列的強大處理能力。迴圈神經網路(Recurrent Neural Network, RNN)、長短期記憶網路(Long Short-Term Memory, LSTM)以及Transformer架構,雖然最初主要應用於自然語言處理領域,但其本質上處理的是向量化資料序列。這意味著只要能夠建立適當的向量化機制,這些模型便能夠延伸應用至音訊分析、金融時序預測、醫療訊號處理等多元領域。

台灣的技術團隊在導入深度學習解決方案時,經常面臨如何將企業特有的非標準化資料轉換為模型可處理格式的挑戰。本文將從實務角度出發,深入探討非文字資料的嵌入技術,並透過完整的程式碼範例與架構分析,協助開發團隊掌握這項關鍵技術。

向量化的本質與資料轉換策略

深度學習模型的輸入需求相當明確,即接受固定維度的向量序列。在自然語言處理領域,詞嵌入(Word Embedding)技術已經相當成熟,諸如Word2Vec、GloVe等方法能夠將文字轉換為高維向量空間中的點。然而當我們面對音訊波形、金融報表數據或醫療儀器訊號時,如何建構有效的向量表示便成為首要挑戰。

向量化策略的設計必須考量資料的本質特性。音訊訊號具有時序相關性與頻譜特徵,MIDI音樂資料則包含離散的音符事件與時間資訊,影像資料涉及空間位置與色彩通道。不同的資料型態需要相應的特徵工程方法,才能有效保留原始資訊的語義內容。

以台灣金融業常見的股票技術分析為例,原始的價格與成交量資料通常以時間序列形式呈現。若要將這些資料輸入深度學習模型,可以將每個時間點的開盤價、收盤價、最高價、最低價、成交量等指標組合成一個多維向量。額外加入技術指標如移動平均線(Moving Average)、相對強弱指標(RSI)或布林通道(Bollinger Bands)等衍生特徵,能夠豐富向量的資訊內容,提升模型的預測能力。

MIDI音樂資料的向量化實作

MIDI格式是數位音樂領域的重要標準,它並非儲存原始音訊波形,而是記錄音符的演奏指令。每個MIDI事件包含事件類型、音符編號、力度值與時間戳記等資訊。將MIDI資料轉換為向量序列的過程,實質上是將這些離散事件映射到連續向量空間的過程。

import mido
import numpy as np
from typing import List, Tuple

class MIDIVectorizer:
    """
    MIDI音樂資料向量化處理類別
    負責將MIDI檔案轉換為可供深度學習模型使用的向量序列
    """
    
    def __init__(self, max_note: int = 128, max_velocity: int = 127):
        """
        初始化向量化器
        
        參數說明:
            max_note: MIDI音符的最大值(標準MIDI為0-127)
            max_velocity: 力度的最大值(標準MIDI為0-127)
        """
        self.max_note = max_note
        self.max_velocity = max_velocity
        # 建立事件類型到索引的映射字典
        self.event_types = {
            'note_on': 0,
            'note_off': 1,
            'control_change': 2,
            'program_change': 3
        }
    
    def load_and_vectorize(self, midi_file_path: str) -> np.ndarray:
        """
        載入MIDI檔案並轉換為向量序列
        
        參數說明:
            midi_file_path: MIDI檔案的完整路徑
            
        回傳值:
            numpy陣列,形狀為(序列長度, 向量維度)
        """
        # 使用mido函式庫載入MIDI檔案
        midi_data = mido.MidiFile(midi_file_path)
        vectors = []
        cumulative_time = 0  # 累積時間追蹤
        
        # 遍歷所有音軌中的訊息
        for track in midi_data.tracks:
            for message in track:
                # 更新累積時間(以tick為單位)
                cumulative_time += message.time
                
                # 處理音符開啟事件
                if message.type == 'note_on':
                    vector = self._create_note_vector(
                        event_type='note_on',
                        note=message.note,
                        velocity=message.velocity,
                        time=cumulative_time
                    )
                    vectors.append(vector)
                
                # 處理音符關閉事件
                elif message.type == 'note_off':
                    vector = self._create_note_vector(
                        event_type='note_off',
                        note=message.note,
                        velocity=0,  # 音符關閉時力度設為0
                        time=cumulative_time
                    )
                    vectors.append(vector)
        
        # 將向量列表轉換為numpy陣列以便後續處理
        return np.array(vectors, dtype=np.float32)
    
    def _create_note_vector(self, event_type: str, note: int, 
                           velocity: int, time: int) -> List[float]:
        """
        建立單一音符事件的向量表示
        
        參數說明:
            event_type: 事件類型('note_on'或'note_off')
            note: 音符編號(0-127)
            velocity: 力度值(0-127)
            time: 累積時間(tick單位)
            
        回傳值:
            正規化後的向量列表
        """
        # 取得事件類型的編碼索引
        event_idx = self.event_types.get(event_type, 0)
        
        # 正規化數值到[0, 1]區間以穩定訓練過程
        normalized_note = note / self.max_note
        normalized_velocity = velocity / self.max_velocity
        normalized_time = time / 1000.0  # 時間正規化(可依需求調整)
        
        # 組合成完整向量:[事件類型, 音符, 力度, 時間]
        return [
            float(event_idx),
            normalized_note,
            normalized_velocity,
            normalized_time
        ]
    
    def add_temporal_features(self, vectors: np.ndarray) -> np.ndarray:
        """
        新增時序特徵以增強模型的時間感知能力
        
        參數說明:
            vectors: 原始向量序列
            
        回傳值:
            增強後的向量序列
        """
        enhanced_vectors = []
        
        for i, vector in enumerate(vectors):
            # 計算與前一個事件的時間差
            if i > 0:
                time_delta = vector[3] - vectors[i-1][3]
            else:
                time_delta = 0.0
            
            # 將時間差資訊附加到原始向量
            enhanced_vector = np.append(vector, time_delta)
            enhanced_vectors.append(enhanced_vector)
        
        return np.array(enhanced_vectors, dtype=np.float32)

# 實際應用範例
if __name__ == "__main__":
    # 初始化向量化器
    vectorizer = MIDIVectorizer()
    
    # 載入並處理MIDI檔案
    midi_vectors = vectorizer.load_and_vectorize('example_music.mid')
    
    # 新增時序特徵
    enhanced_vectors = vectorizer.add_temporal_features(midi_vectors)
    
    # 輸出向量資訊供後續使用
    print(f"向量序列形狀: {enhanced_vectors.shape}")
    print(f"向量維度: {enhanced_vectors.shape[1]}")

上述程式碼展示了完整的MIDI資料向量化流程。我們建立了一個專門的類別來處理轉換邏輯,包含事件類型編碼、數值正規化與時序特徵提取等步驟。正規化處理確保不同尺度的特徵值能夠平衡地影響模型訓練,而時序特徵的加入則協助模型更好地理解音樂的節奏結構。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

start
:讀取MIDI檔案;
:初始化向量序列容器;
:遍歷所有音軌;
while (是否還有訊息?) is (是)
  :讀取下一個MIDI訊息;
  :更新累積時間戳記;
  if (訊息類型判斷) then (note_on)
    :提取音符編號與力度;
    :建立note_on向量;
  elseif (訊息類型判斷) then (note_off)
    :提取音符編號;
    :建立note_off向量;
  else (其他類型)
    :跳過此訊息;
  endif
  :執行數值正規化;
  :加入向量序列;
endwhile (否)
:計算時序特徵;
:組合成最終向量矩陣;
:回傳numpy陣列;
stop

@enduml

這個流程圖清楚呈現了MIDI資料處理的完整步驟。從檔案讀取開始,經過事件分類、特徵提取、數值正規化,最終產生結構化的向量序列。每個步驟都經過精心設計,確保輸出的向量能夠有效保留音樂的語義資訊。

序列模型架構的選擇與應用

在確定資料的向量表示方法後,接下來需要選擇適當的模型架構來處理這些序列資料。RNN、LSTM與Transformer各有其特色與適用場景,理解它們的架構差異對於建構有效的深度學習系統至關重要。

長短期記憶網路的實作與優化

LSTM透過精巧的門控機制解決了傳統RNN在處理長序列時的梯度消失問題。其核心設計包含輸入門(Input Gate)、遺忘門(Forget Gate)與輸出門(Output Gate)三個關鍵組件,以及一個記憶單元(Cell State)來維護長期資訊。

import torch
import torch.nn as nn
from torch.nn.utils.rnn import pack_padded_sequence, pad_packed_sequence

class MusicLSTM(nn.Module):
    """
    專為音樂序列資料設計的LSTM模型
    適用於音樂生成、風格分類等任務
    """
    
    def __init__(self, input_dim: int, hidden_dim: int, 
                 num_layers: int = 2, dropout: float = 0.3):
        """
        初始化LSTM音樂模型
        
        參數說明:
            input_dim: 輸入向量的維度(例如MIDI向量的特徵數)
            hidden_dim: LSTM隱藏層的維度
            num_layers: LSTM堆疊的層數
            dropout: Dropout比率,用於防止過度擬合
        """
        super(MusicLSTM, self).__init__()
        
        self.input_dim = input_dim
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        
        # 建構多層LSTM架構
        # batch_first=True表示輸入張量的形狀為(batch_size, seq_len, input_dim)
        self.lstm = nn.LSTM(
            input_size=input_dim,
            hidden_size=hidden_dim,
            num_layers=num_layers,
            batch_first=True,
            dropout=dropout if num_layers > 1 else 0
        )
        
        # 輸出層用於將LSTM的隱藏狀態映射到目標空間
        # 例如預測下一個音符的機率分佈
        self.output_layer = nn.Linear(hidden_dim, input_dim)
        
        # Dropout層用於正則化
        self.dropout = nn.Dropout(dropout)
        
    def forward(self, x, seq_lengths=None):
        """
        前向傳播函式
        
        參數說明:
            x: 輸入序列張量,形狀為(batch_size, seq_len, input_dim)
            seq_lengths: 每個序列的實際長度(用於處理變長序列)
            
        回傳值:
            輸出張量與最終的隱藏狀態
        """
        batch_size = x.size(0)
        
        # 初始化隱藏狀態與細胞狀態
        # 形狀為(num_layers, batch_size, hidden_dim)
        h0 = torch.zeros(self.num_layers, batch_size, 
                        self.hidden_dim).to(x.device)
        c0 = torch.zeros(self.num_layers, batch_size, 
                        self.hidden_dim).to(x.device)
        
        # 如果提供了序列長度,使用pack_padded_sequence加速運算
        if seq_lengths is not None:
            # 將填充序列打包以提高運算效率
            x_packed = pack_padded_sequence(
                x, seq_lengths, batch_first=True, 
                enforce_sorted=False
            )
            lstm_out, (hn, cn) = self.lstm(x_packed, (h0, c0))
            # 解包回原始形狀
            lstm_out, _ = pad_packed_sequence(
                lstm_out, batch_first=True
            )
        else:
            # 一般的LSTM前向傳播
            lstm_out, (hn, cn) = self.lstm(x, (h0, c0))
        
        # 套用Dropout進行正則化
        lstm_out = self.dropout(lstm_out)
        
        # 通過輸出層得到最終預測
        output = self.output_layer(lstm_out)
        
        return output, (hn, cn)
    
    def generate_sequence(self, start_vector, length: int, 
                         temperature: float = 1.0):
        """
        自回歸生成音樂序列
        
        參數說明:
            start_vector: 起始向量,形狀為(1, input_dim)
            length: 要生成的序列長度
            temperature: 控制生成的隨機性,越低越確定
            
        回傳值:
            生成的向量序列
        """
        self.eval()  # 切換到評估模式
        generated = [start_vector]
        
        # 初始化隱藏狀態
        h = torch.zeros(self.num_layers, 1, self.hidden_dim)
        c = torch.zeros(self.num_layers, 1, self.hidden_dim)
        
        with torch.no_grad():  # 生成時不需要計算梯度
            for _ in range(length - 1):
                # 準備輸入(增加時間維度)
                current_input = generated[-1].unsqueeze(0)
                
                # 前向傳播
                lstm_out, (h, c) = self.lstm(current_input, (h, c))
                output = self.output_layer(lstm_out)
                
                # 套用溫度縮放
                output = output / temperature
                
                # 加入適度的隨機性
                noise = torch.randn_like(output) * 0.1
                next_vector = output.squeeze(0) + noise
                
                generated.append(next_vector)
        
        return torch.stack(generated, dim=0)

# 訓練範例
def train_music_lstm():
    """
    LSTM音樂模型的訓練流程範例
    """
    # 假設已經準備好訓練資料
    input_dim = 5  # MIDI向量維度
    hidden_dim = 256
    batch_size = 32
    seq_length = 100
    
    # 建立模型實例
    model = MusicLSTM(
        input_dim=input_dim,
        hidden_dim=hidden_dim,
        num_layers=3,
        dropout=0.3
    )
    
    # 定義損失函式與最佳化器
    criterion = nn.MSELoss()  # 回歸任務使用均方誤差
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    
    # 模擬訓練資料(實際應用中應載入真實資料)
    train_data = torch.randn(batch_size, seq_length, input_dim)
    target_data = torch.randn(batch_size, seq_length, input_dim)
    
    # 訓練迴圈
    num_epochs = 100
    for epoch in range(num_epochs):
        model.train()  # 切換到訓練模式
        
        # 清空梯度
        optimizer.zero_grad()
        
        # 前向傳播
        output, _ = model(train_data)
        
        # 計算損失
        loss = criterion(output, target_data)
        
        # 反向傳播
        loss.backward()
        
        # 梯度裁剪防止梯度爆炸
        torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm=1.0)
        
        # 更新權重
        optimizer.step()
        
        # 定期輸出訓練進度
        if (epoch + 1) % 10 == 0:
            print(f'Epoch [{epoch+1}/{num_epochs}], Loss: {loss.item():.4f}')
    
    return model

# 執行訓練
if __name__ == "__main__":
    trained_model = train_music_lstm()

這段程式碼實作了一個完整的LSTM音樂處理系統,包含模型定義、訓練流程與序列生成功能。程式碼中特別注意了梯度裁剪、Dropout正則化等實務技巧,這些細節對於訓練穩定性與模型效能都有重要影響。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "LSTM架構組件" {
  [輸入向量序列] as input
  [嵌入層] as embed
  [LSTM層1] as lstm1
  [LSTM層2] as lstm2
  [LSTM層3] as lstm3
  [Dropout] as dropout
  [全連接輸出層] as output
  [預測結果] as result
}

input --> embed
embed --> lstm1
lstm1 --> lstm2
lstm2 --> lstm3
lstm3 --> dropout
dropout --> output
output --> result

note right of lstm1
  包含輸入門、遺忘門
  與輸出門的門控機制
end note

note right of dropout
  防止過度擬合
  訓練時隨機丟棄神經元
end note

@enduml

這個架構圖展示了多層LSTM的內部結構。輸入序列首先經過可選的嵌入層,然後依序通過多個堆疊的LSTM層。每層LSTM都包含完整的門控機制,能夠選擇性地記憶或遺忘資訊。最後透過Dropout層與全連接層產生預測結果。

模型選擇的實務考量

在實際專案中選擇RNN、LSTM或Transformer時,需要綜合考量多個面向。序列長度是首要因素,若處理的序列長度在數百個時間步以內,LSTM通常能夠提供良好的效能與訓練效率。當序列長度超過千個時間步,Transformer的自注意力機制優勢開始顯現,但相對需要更多的運算資源。

資料量也是關鍵考量。Transformer模型通常需要大量訓練資料才能充分發揮其潛力,台灣企業若面臨資料量受限的情況,LSTM可能是更實際的選擇。此外,推論效率在生產環境中同樣重要,LSTM的序列處理特性使其在邊緣運算裝置上部署更為容易。

影像資料的向量化與位置編碼

影像資料的處理策略與序列資料有所不同。傳統上,卷積神經網路(Convolutional Neural Network, CNN)是影像任務的首選,但近年來Vision Transformer等架構證明了將影像視為序列資料處理的可行性。

import torch
import torch.nn as nn
import numpy as np
from typing import Tuple

class ImageToSequence:
    """
    將影像轉換為序列格式的處理器
    適用於Vision Transformer等序列模型
    """
    
    def __init__(self, patch_size: int = 16, channels: int = 3):
        """
        初始化影像序列轉換器
        
        參數說明:
            patch_size: 切割影像的patch大小(像素)
            channels: 影像通道數(RGB為3)
        """
        self.patch_size = patch_size
        self.channels = channels
    
    def image_to_patches(self, image: np.ndarray) -> Tuple[np.ndarray, int, int]:
        """
        將影像切割成固定大小的patch序列
        
        參數說明:
            image: 輸入影像,形狀為(height, width, channels)
            
        回傳值:
            patches陣列、patch的列數、patch的行數
        """
        height, width, channels = image.shape
        
        # 確保影像尺寸能被patch_size整除
        assert height % self.patch_size == 0, "影像高度必須能被patch大小整除"
        assert width % self.patch_size == 0, "影像寬度必須能被patch大小整除"
        
        # 計算會產生多少個patch
        num_patches_h = height // self.patch_size
        num_patches_w = width // self.patch_size
        
        # 重新排列影像資料以形成patch序列
        patches = image.reshape(
            num_patches_h, self.patch_size,
            num_patches_w, self.patch_size, channels
        )
        # 調整維度順序:(num_patches_h, num_patches_w, patch_size, patch_size, channels)
        patches = patches.transpose(0, 2, 1, 3, 4)
        # 展平成序列:(num_patches, patch_dim)
        patches = patches.reshape(-1, self.patch_size * self.patch_size * channels)
        
        return patches, num_patches_h, num_patches_w
    
    def add_positional_encoding(self, patches: np.ndarray, 
                               num_patches_h: int, 
                               num_patches_w: int) -> np.ndarray:
        """
        為patch序列加入位置編碼資訊
        
        參數說明:
            patches: patch序列陣列
            num_patches_h: patch的列數
            num_patches_w: patch的行數
            
        回傳值:
            加入位置編碼後的patch序列
        """
        num_patches = patches.shape[0]
        enhanced_patches = []
        
        for i in range(num_patches):
            # 計算patch在影像中的二維位置
            row = i // num_patches_w
            col = i % num_patches_w
            
            # 正規化位置資訊到[0, 1]區間
            norm_row = row / num_patches_h
            norm_col = col / num_patches_w
            
            # 建立位置編碼向量
            # 包含絕對位置與相對位置資訊
            positional_info = np.array([
                norm_row,  # 垂直位置
                norm_col,  # 水平位置
                norm_row * norm_col,  # 位置交互項
                np.sin(norm_row * np.pi),  # 週期性位置編碼
                np.cos(norm_col * np.pi)   # 週期性位置編碼
            ])
            
            # 將位置資訊附加到patch向量
            enhanced_patch = np.concatenate([patches[i], positional_info])
            enhanced_patches.append(enhanced_patch)
        
        return np.array(enhanced_patches, dtype=np.float32)
    
    def create_2d_sinusoidal_encoding(self, num_patches_h: int, 
                                     num_patches_w: int, 
                                     d_model: int) -> np.ndarray:
        """
        建立二維正弦位置編碼
        靈感來自Transformer的位置編碼機制
        
        參數說明:
            num_patches_h: patch的列數
            num_patches_w: patch的行數  
            d_model: 編碼向量的維度
            
        回傳值:
            位置編碼矩陣
        """
        # 初始化位置編碼矩陣
        pe = np.zeros((num_patches_h * num_patches_w, d_model))
        
        # 為每個位置計算編碼
        position = 0
        for row in range(num_patches_h):
            for col in range(num_patches_w):
                for i in range(0, d_model, 4):
                    # 計算不同頻率的正弦與餘弦值
                    div_term = np.exp(i * -np.log(10000.0) / d_model)
                    
                    # 行方向的編碼
                    pe[position, i] = np.sin(row * div_term)
                    pe[position, i + 1] = np.cos(row * div_term)
                    
                    # 列方向的編碼
                    if i + 2 < d_model:
                        pe[position, i + 2] = np.sin(col * div_term)
                    if i + 3 < d_model:
                        pe[position, i + 3] = np.cos(col * div_term)
                
                position += 1
        
        return pe

# 實際使用範例
def process_image_for_transformer():
    """
    完整的影像處理流程範例
    """
    # 建立影像處理器
    processor = ImageToSequence(patch_size=16, channels=3)
    
    # 模擬一張224x224的RGB影像
    image = np.random.rand(224, 224, 3).astype(np.float32)
    
    # 切割成patch序列
    patches, num_h, num_w = processor.image_to_patches(image)
    print(f"Patch序列形狀: {patches.shape}")
    print(f"Patch網格尺寸: {num_h} x {num_w}")
    
    # 加入位置編碼
    enhanced_patches = processor.add_positional_encoding(patches, num_h, num_w)
    print(f"增強後patch形狀: {enhanced_patches.shape}")
    
    # 建立正弦位置編碼
    sinusoidal_pe = processor.create_2d_sinusoidal_encoding(
        num_h, num_w, d_model=256
    )
    print(f"正弦位置編碼形狀: {sinusoidal_pe.shape}")
    
    return enhanced_patches, sinusoidal_pe

if __name__ == "__main__":
    patches, pe = process_image_for_transformer()

影像的向量化處理需要特別考慮空間位置資訊的保留。不同於一維序列,二維影像具有行列兩個維度的位置關係。上述程式碼展示了如何將影像切割成patch並加入位置編碼,這種處理方式已被Vision Transformer等先進架構驗證有效。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

start
:輸入原始影像\n(H×W×C);
:切割成固定大小patch;
note right
  例如16×16像素
  形成N個patch
end note
:展平每個patch為向量;
:計算每個patch的空間位置;
fork
  :建立絕對位置編碼;
fork again
  :建立相對位置編碼;
fork again
  :建立正弦位置編碼;
end fork
:合併patch向量與位置編碼;
:形成最終序列表示;
:輸入Transformer模型;
stop

@enduml

這個流程圖說明了影像轉換為序列的完整過程。關鍵步驟包括patch切割、向量展平與位置編碼的整合。多種位置編碼方式可以並行計算並組合使用,提供更豐富的空間資訊。

實務應用場景與最佳化策略

在台灣的技術環境中,深度學習模型的實際部署常遇到運算資源受限、訓練資料不足等挑戰。以下探討幾個實務場景與對應的最佳化策略。

金融時序預測系統

台灣金融業對於股價預測、風險評估等應用有強烈需求。利用LSTM處理技術指標序列,可以建構有效的預測系統。然而實務上需要處理資料不平衡、市場結構變化等問題。

import torch
import torch.nn as nn
from torch.utils.data import Dataset, DataLoader
import pandas as pd
import numpy as np

class StockDataset(Dataset):
    """
    股票技術指標資料集
    處理時間序列格式的金融資料
    """
    
    def __init__(self, data: pd.DataFrame, seq_length: int = 60, 
                 target_col: str = 'Close'):
        """
        初始化資料集
        
        參數說明:
            data: 包含技術指標的DataFrame
            seq_length: 輸入序列長度(例如60個交易日)
            target_col: 預測目標欄位
        """
        self.seq_length = seq_length
        self.target_col = target_col
        
        # 特徵欄位(排除目標欄位)
        self.feature_cols = [col for col in data.columns 
                            if col != target_col]
        
        # 正規化處理
        self.data_normalized = self._normalize_data(data)
        
        # 建立序列資料
        self.sequences, self.targets = self._create_sequences()
    
    def _normalize_data(self, data: pd.DataFrame) -> pd.DataFrame:
        """
        對資料進行正規化處理
        使用滾動視窗標準化以避免前視偏差
        """
        normalized = data.copy()
        
        for col in self.feature_cols + [self.target_col]:
            # 計算滾動均值與標準差
            rolling_mean = data[col].rolling(window=self.seq_length).mean()
            rolling_std = data[col].rolling(window=self.seq_length).std()
            
            # Z-score標準化
            normalized[col] = (data[col] - rolling_mean) / (rolling_std + 1e-8)
        
        # 填補前面無法計算的NaN值
        normalized = normalized.fillna(method='bfill')
        
        return normalized
    
    def _create_sequences(self):
        """
        建立輸入序列與對應目標值
        """
        sequences = []
        targets = []
        
        data_array = self.data_normalized[self.feature_cols].values
        target_array = self.data_normalized[self.target_col].values
        
        # 滑動視窗建立序列
        for i in range(len(data_array) - self.seq_length):
            # 輸入序列
            seq = data_array[i:i + self.seq_length]
            # 預測目標(下一個時間點)
            target = target_array[i + self.seq_length]
            
            sequences.append(seq)
            targets.append(target)
        
        return np.array(sequences), np.array(targets)
    
    def __len__(self):
        return len(self.sequences)
    
    def __getitem__(self, idx):
        return (
            torch.FloatTensor(self.sequences[idx]),
            torch.FloatTensor([self.targets[idx]])
        )

class FinancialLSTM(nn.Module):
    """
    金融時序預測LSTM模型
    包含注意力機制以提升預測準確度
    """
    
    def __init__(self, input_dim: int, hidden_dim: int = 128, 
                 num_layers: int = 2):
        super(FinancialLSTM, self).__init__()
        
        self.hidden_dim = hidden_dim
        self.num_layers = num_layers
        
        # 主要LSTM層
        self.lstm = nn.LSTM(
            input_dim, hidden_dim, num_layers,
            batch_first=True, dropout=0.2
        )
        
        # 注意力機制層
        self.attention = nn.Sequential(
            nn.Linear(hidden_dim, hidden_dim),
            nn.Tanh(),
            nn.Linear(hidden_dim, 1)
        )
        
        # 輸出層
        self.fc = nn.Sequential(
            nn.Linear(hidden_dim, 64),
            nn.ReLU(),
            nn.Dropout(0.2),
            nn.Linear(64, 1)
        )
    
    def forward(self, x):
        # LSTM處理序列
        lstm_out, _ = self.lstm(x)
        
        # 計算注意力權重
        attention_weights = torch.softmax(
            self.attention(lstm_out), dim=1
        )
        
        # 加權平均LSTM輸出
        context = torch.sum(attention_weights * lstm_out, dim=1)
        
        # 預測輸出
        output = self.fc(context)
        
        return output, attention_weights

# 訓練函式
def train_financial_model(train_loader, model, epochs=100):
    """
    訓練金融預測模型
    """
    criterion = nn.MSELoss()
    optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
    
    # 學習率排程器
    scheduler = torch.optim.lr_scheduler.ReduceLROnPlateau(
        optimizer, mode='min', patience=5, factor=0.5
    )
    
    for epoch in range(epochs):
        model.train()
        total_loss = 0
        
        for batch_x, batch_y in train_loader:
            optimizer.zero_grad()
            
            # 前向傳播
            predictions, _ = model(batch_x)
            loss = criterion(predictions, batch_y)
            
            # 反向傳播
            loss.backward()
            torch.nn.utils.clip_grad_norm_(model.parameters(), 1.0)
            optimizer.step()
            
            total_loss += loss.item()
        
        avg_loss = total_loss / len(train_loader)
        scheduler.step(avg_loss)
        
        if (epoch + 1) % 10 == 0:
            print(f'Epoch {epoch+1}: Loss = {avg_loss:.6f}')

這個金融預測系統的實作展示了如何處理真實世界的時序資料。特別注意資料正規化使用滾動視窗方式,避免了前視偏差問題。注意力機制的加入讓模型能夠自動學習哪些時間點的資訊最為重要。

RNN與Transformer的技術權衡

在架構選擇上,RNN系列模型與Transformer各有其適用場景。RNN的序列處理特性使其在處理變長序列時更具彈性,且記憶體需求相對較低。Transformer雖然在大規模資料上表現優異,但其二次方的運算複雜度在處理長序列時會成為瓶頸。

台灣許多中小型企業在導入深度學習時,常面臨資料量與運算資源的限制。此時LSTM往往是更實際的選擇,它能在有限資料上達到可接受的效能,且訓練時間與推論速度都相對友善。當資料規模擴大到數十萬筆以上,且有充足的GPU資源時,才建議考慮Transformer架構。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

package "RNN架構特性" {
  component "序列處理" as rnn_seq
  component "低記憶體需求" as rnn_mem  
  component "適合變長序列" as rnn_var
  component "梯度問題需處理" as rnn_grad
}

package "Transformer架構特性" {
  component "平行處理" as tf_par
  component "自注意力機制" as tf_att
  component "需大量資料" as tf_data
  component "運算資源需求高" as tf_res
}

rnn_seq -[hidden]- tf_par
rnn_mem -[hidden]- tf_att
rnn_var -[hidden]- tf_data
rnn_grad -[hidden]- tf_res

note bottom of "RNN架構特性"
  適用場景:
  - 中小規模資料集
  - 資源受限環境
  - 即時串流處理
end note

note bottom of "Transformer架構特性"
  適用場景:
  - 大規模資料集
  - 充足運算資源
  - 需要全域依賴關係
end note

@enduml

未來發展趨勢與技術展望

深度學習在非文字資料處理領域持續演進。混合架構的出現,例如將CNN的局部特徵提取能力與Transformer的全域建模能力結合,已在影像分類等任務上展現優勢。同樣的思路也可應用於序列資料,將LSTM的記憶機制與注意力機制整合,發展出更強大的模型。

台灣產業界在應用這些技術時,應重視模型的可解釋性與部署效率。金融、醫療等受監管產業對模型的可解釋性有嚴格要求,單純追求預測準確度而忽視可解釋性,可能導致監管合規問題。輕量化技術如模型剪枝、量化等方法,能夠在維持效能的同時大幅降低模型大小,使其能夠部署在邊緣裝置上。

從工程實踐角度來看,建構完整的資料管線與監控系統同樣重要。模型訓練只是整個系統的一部分,資料收集、清洗、特徵工程、模型部署與監控都需要妥善設計。採用MLOps的最佳實踐,建立自動化的訓練與部署流程,才能確保系統的長期穩定運作。

總結而言,將深度學習模型應用於非文字資料的關鍵在於設計有效的向量化策略,並根據實際需求選擇適當的模型架構。MIDI音樂、影像與金融時序資料的處理案例展示了這項技術的廣泛應用潛力。隨著硬體效能提升與演算法創新,我們可以期待看到更多創新應用在各領域落地實現。