| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| class SingleAssetProcessor(nn.Module): |
| def __init__(self, sequence_length, num_features_per_asset, cnn_filters1, cnn_filters2, lstm_units): |
| super().__init__() |
| self.conv1 = nn.Conv1d(num_features_per_asset, cnn_filters1, kernel_size=3, padding=1) |
| self.conv2 = nn.Conv1d(cnn_filters1, cnn_filters2, kernel_size=3, padding=1) |
| self.lstm = nn.LSTM(input_size=cnn_filters2, hidden_size=lstm_units, batch_first=True) |
|
|
| def forward(self, x): |
| |
| x = x.transpose(1, 2) |
| x = F.relu(self.conv1(x)) |
| x = F.relu(self.conv2(x)) |
| x = x.transpose(1, 2) |
| _, (h_n, _) = self.lstm(x) |
| return h_n.squeeze(0) |
|
|
| class DeepPortfolioAgentNetworkTorch(nn.Module): |
| def __init__(self, num_assets, sequence_length, num_features_per_asset, |
| asset_cnn_filters1=32, asset_cnn_filters2=64, |
| asset_lstm_units1=64, asset_lstm_units2=32, |
| final_dense_units1=128, final_dense_units2=32, |
| final_dropout=0.3, mha_num_heads=4, mha_key_dim_divisor=2, |
| output_latent_features=True, use_sentiment_analysis=False): |
| super().__init__() |
| self.num_assets = num_assets |
| self.sequence_length = sequence_length |
| self.num_features_per_asset = num_features_per_asset |
| self.output_latent_features = output_latent_features |
| self.use_sentiment_analysis = use_sentiment_analysis |
|
|
| |
| self.asset_processor = SingleAssetProcessor( |
| sequence_length, num_features_per_asset, |
| asset_cnn_filters1, asset_cnn_filters2, asset_lstm_units1 |
| ) |
| |
| self.attention = nn.MultiheadAttention( |
| embed_dim=asset_lstm_units1, |
| num_heads=mha_num_heads, |
| batch_first=True |
| ) |
| |
| self.global_avg_pool = nn.AdaptiveAvgPool1d(1) |
| |
| self.dense1 = nn.Linear(asset_lstm_units1, final_dense_units1) |
| self.dropout1 = nn.Dropout(final_dropout) |
| self.dense2 = nn.Linear(final_dense_units1, final_dense_units2) |
| self.dropout2 = nn.Dropout(final_dropout) |
| self.output_allocation = nn.Linear(final_dense_units2, num_assets) |
|
|
| def forward(self, x): |
| |
| batch_size = x.size(0) |
| |
| asset_representations = [] |
| for i in range(self.num_assets): |
| start = i * self.num_features_per_asset |
| end = (i + 1) * self.num_features_per_asset |
| asset_data = x[:, :, start:end] |
| asset_repr = self.asset_processor(asset_data) |
| asset_representations.append(asset_repr) |
| |
| stacked = torch.stack(asset_representations, dim=1) |
| |
| attn_output, _ = self.attention(stacked, stacked, stacked) |
| |
| pooled = self.global_avg_pool(attn_output.transpose(1,2)).squeeze(-1) |
| |
| x = F.relu(self.dense1(pooled)) |
| x = self.dropout1(x) |
| x = F.relu(self.dense2(x)) |
| x = self.dropout2(x) |
| if self.output_latent_features: |
| return x |
| else: |
| return F.softmax(self.output_allocation(x), dim=-1) |
|
|