Autoencodeurs conditionnels pour l’évaluation des actifs fondée sur leurs caractéristiques
Résumé
Cette implémentation décrit un autoencodeur conditionnel pour l’évaluation des actifs. Un réseau bêta transforme les caractéristiques de chaque action en expositions à des facteurs latents, tandis qu’un réseau linéaire de facteurs transforme les rendements de portefeuilles gérés en rendements factoriels. Le modèle prévoit le rendement d’une action en additionnant les produits des expositions et des valeurs factorielles correspondantes. Les expositions peuvent ainsi varier selon les caractéristiques de l’entreprise, tandis que les facteurs sont dérivés d’instruments de portefeuille.
Le code fournit aussi des accesseurs aux expositions et aux facteurs appris, qui peuvent aider à examiner les composants du modèle. Sa pénalité L1 s’applique uniquement aux poids des couches cachées du réseau bêta ; elle exclut les biais et la couche finale des expositions, et vaut zéro en l’absence de couches cachées. Il s’agit d’une référence architecturale, et non d’une étude empirique : elle ne fournit ni procédure d’entraînement, ni jeu de données, ni résultats prédictifs, ni comparaison avec d’autres modèles d’évaluation des actifs.
Idées clés
- Le réseau bêta transforme les caractéristiques des actions en expositions à des facteurs latents propres à chaque action.
- Un réseau linéaire transforme les données de portefeuille géré en rendements factoriels.
- Les rendements prévus sont obtenus en additionnant les produits des expositions par les facteurs.
- Le terme de régularisation favorise la parcimonie des poids des couches cachées du réseau bêta, sans pénaliser la couche de sortie.
- L’implémentation définit la structure du modèle, mais ne fournit aucune évaluation empirique.
Étiquettes
Texte intégral
# cae.py
```py
"""Conditional Autoencoder for Asset Pricing (GKX 2021).
Architecture:
- ConditionalAutoencoder: predicted return = dot(betas, factors)
Reference: Gu, Kelly, Xiu (2021) "Autoencoder Asset Pricing Models"
"""
from __future__ import annotations
import torch
import torch.nn as nn
class BetaNetwork(nn.Module):
"""Maps stock characteristics to per-stock factor loadings."""
def __init__(self, n_characteristics: int, n_factors: int, hidden_units: tuple = (32,)):
super().__init__()
layers: list[nn.Module] = []
in_features = n_characteristics
for units in hidden_units:
layers.append(nn.Linear(in_features, units))
layers.append(nn.ReLU())
in_features = units
layers.append(nn.Linear(in_features, n_factors))
self.network = nn.Sequential(*layers)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.network(x)
class FactorNetwork(nn.Module):
"""Maps managed portfolio returns to factor returns (linear, no activation)."""
def __init__(self, n_instruments: int, n_factors: int):
super().__init__()
self.linear = nn.Linear(n_instruments, n_factors, bias=False)
def forward(self, x: torch.Tensor) -> torch.Tensor:
return self.linear(x)
class ConditionalAutoencoder(nn.Module):
"""Conditional Autoencoder for Asset Pricing (GKX Architecture).
Architecture:
- Beta Network: characteristics -> ReLU -> factor loadings (with L1 regularization)
- Factor Network: managed portfolios -> factor returns (linear)
- Output: dot product of betas and factors
Args:
n_characteristics: Number of stock characteristics (L)
n_instruments: Number of managed portfolio instruments (L+1 typically)
n_factors: Number of latent factors (K)
hidden_units: Tuple of hidden layer sizes for BetaNetwork
"""
def __init__(
self,
n_characteristics: int,
n_instruments: int,
n_factors: int = 6,
hidden_units: tuple = (32,),
):
super().__init__()
self.n_factors = n_factors
self.n_characteristics = n_characteristics
self.n_instruments = n_instruments
self.beta_net = BetaNetwork(n_characteristics, n_factors, hidden_units)
self.factor_net = FactorNetwork(n_instruments, n_factors)
def forward(self, characteristics: torch.Tensor, portfolios: torch.Tensor) -> torch.Tensor:
"""Forward pass.
Args:
characteristics: (batch_size, n_characteristics) per-stock
portfolios: (batch_size, n_instruments) managed portfolio features
Returns:
predicted_returns: (batch_size,)
"""
betas = self.beta_net(characteristics) # (batch, n_factors)
factors = self.factor_net(portfolios) # (batch, n_factors)
pred = (betas * factors).sum(dim=1)
return pred
def get_betas(self, characteristics: torch.Tensor) -> torch.Tensor:
"""Extract factor loadings."""
return self.beta_net(characteristics)
def get_factors(self, portfolios: torch.Tensor) -> torch.Tensor:
"""Extract factor returns."""
return self.factor_net(portfolios)
def l1_regularization(model: ConditionalAutoencoder, lambda_l1: float) -> torch.Tensor:
"""L1 regularization on BetaNetwork hidden Dense weights only.
Matches the GKX (2021) reference, which applies ``kernel_regularizer='L1'``
only to the *hidden* Dense layers. We exclude biases (sparsity is not
meaningful) and the final output ``Linear(in, n_factors)`` layer (factor
loadings should not be pushed to zero by an external regularizer — the
autoencoder itself learns their magnitudes).
"""
layers = [m for m in model.beta_net.network if isinstance(m, nn.Linear)]
if len(layers) <= 1:
# No hidden layers (CA0 — direct linear projection); no L1 to apply.
return torch.zeros((), device=next(model.parameters()).device)
hidden_layers = layers[:-1] # exclude the final output Linear
l1 = sum(layer.weight.abs().sum() for layer in hidden_layers)
return lambda_l1 * l1
```Reproduit dans son intégralité avec attribution, conformément à la licence de la source. Licence: MIT
Ce résumé a été rédigé par l’agent de recherche de Stratmill à partir de la source originale ; il n’en est pas une copie.