|
|
|
"""app.py |
|
|
|
Automatically generated by Colab. |
|
|
|
Original file is located at |
|
https://colab.research.google.com/drive/1QIEwA7FDPNIgdUKfLyRF4K3Im9CjkadN |
|
|
|
Logistic Map Equation: x |
|
n+1 |
|
β |
|
=rβ
x |
|
n |
|
β |
|
β
(1βx |
|
n |
|
β |
|
) |
|
|
|
- x_n is the current state (a number between 0 and 1). |
|
|
|
- x_{n+1} is the next value in the sequence. |
|
|
|
- r is the growth rate parameter. |
|
|
|
This block: |
|
|
|
- Introduces the logistic map function |
|
|
|
- Lets us generate sequences with different r values |
|
|
|
- Plots them to visually understand convergence, cycles, and chaos |
|
""" |
|
|
|
import numpy as np |
|
import matplotlib.pyplot as plt |
|
import random |
|
|
|
|
|
def logistic_map(x0: float, r: float, n: int = 100) -> np.ndarray: |
|
""" |
|
Generates a logistic map sequence. |
|
|
|
Args: |
|
x0 (float): Initial value (between 0 and 1). |
|
r (float): Growth rate parameter (between 0 and 4). |
|
n (int): Number of time steps. |
|
|
|
Returns: |
|
np.ndarray: Sequence of logistic map values. |
|
""" |
|
seq = np.zeros(n) |
|
seq[0] = x0 |
|
for i in range(1, n): |
|
seq[i] = r * seq[i - 1] * (1 - seq[i - 1]) |
|
return seq |
|
|
|
|
|
def plot_logistic_map_examples(x0: float = 0.51, n: int = 100): |
|
""" |
|
Plots logistic map sequences for several r values to visualize behavior. |
|
|
|
Args: |
|
x0 (float): Initial value. |
|
n (int): Number of iterations. |
|
""" |
|
r_values = [2.5, 3.2, 3.5, 3.9, 4.0] |
|
plt.figure(figsize=(12, 8)) |
|
|
|
for i, r in enumerate(r_values, 1): |
|
x0_safe = random.uniform(0.11, 0.89) |
|
seq = logistic_map(x0, r, n) |
|
plt.subplot(3, 2, i) |
|
plt.plot(seq, label=f"r = {r}") |
|
plt.title(f"Logistic Map (r = {r})") |
|
plt.xlabel("Time Step") |
|
plt.ylabel("x") |
|
plt.grid(True) |
|
plt.legend() |
|
|
|
plt.tight_layout() |
|
plt.show() |
|
|
|
|
|
plot_logistic_map_examples() |
|
|
|
"""- Low r (e.g., 2.5) = stable |
|
|
|
- Mid r (e.g., 3.3) = periodic |
|
|
|
- High r (e.g., 3.8 β 4.0) = chaotic |
|
|
|
Generate synthetic sequences using random r values |
|
|
|
Label each sequence as: |
|
|
|
- 0 = stable (low r) |
|
|
|
- 1 = periodic (mid r) |
|
|
|
- 2 = chaotic (high r) |
|
|
|
Create a full dataset we can later feed into a classifier |
|
""" |
|
|
|
import random |
|
from typing import Tuple, List |
|
|
|
|
|
def label_from_r(r: float) -> int: |
|
""" |
|
Assigns a regime label based on the value of r. |
|
|
|
Args: |
|
r (float): Growth rate. |
|
|
|
Returns: |
|
int: Label (0 = stable, 1 = periodic, 2 = chaotic) |
|
""" |
|
if r < 3.0: |
|
return 0 |
|
elif 3.0 <= r < 3.57: |
|
return 1 |
|
else: |
|
return 2 |
|
|
|
|
|
def generate_labeled_sequence(n: int = 100) -> Tuple[np.ndarray, int]: |
|
""" |
|
Generates a single logistic map sequence and its regime label. |
|
|
|
Args: |
|
n (int): Sequence length. |
|
|
|
Returns: |
|
Tuple: (sequence, label) |
|
""" |
|
r = round(random.uniform(2.5, 4.0), 4) |
|
x0 = random.uniform(0.1, 0.9) |
|
sequence = logistic_map(x0, r, n) |
|
label = label_from_r(r) |
|
return sequence, label |
|
|
|
|
|
def generate_dataset(num_samples: int = 1000, n: int = 100) -> Tuple[np.ndarray, np.ndarray]: |
|
""" |
|
Generates a dataset of logistic sequences with regime labels. |
|
|
|
Args: |
|
num_samples (int): Number of sequences to generate. |
|
n (int): Length of each sequence. |
|
|
|
Returns: |
|
Tuple[np.ndarray, np.ndarray]: X (sequences), y (labels) |
|
""" |
|
X, y = [], [] |
|
|
|
for _ in range(num_samples): |
|
sequence, label = generate_labeled_sequence(n) |
|
X.append(sequence) |
|
y.append(label) |
|
|
|
return np.array(X), np.array(y) |
|
|
|
|
|
X, y = generate_dataset(num_samples=500, n=100) |
|
|
|
|
|
import collections |
|
print("Label distribution:", collections.Counter(y)) |
|
|
|
"""Used controlled r ranges to simulate different market regimes |
|
|
|
Created 500 synthetic sequences (X) and regime labels (y) |
|
|
|
Now we can visualize, split, and train on this dataset |
|
|
|
Visualize: |
|
|
|
- Randomly samples from X, y |
|
|
|
- Plots sequences grouped by class (0 = stable, 1 = periodic, 2 = chaotic) |
|
|
|
Helps us verify that the labels match the visual behavior |
|
""" |
|
|
|
import matplotlib.pyplot as plt |
|
import numpy as np |
|
|
|
|
|
def plot_class_samples(X: np.ndarray, y: np.ndarray, target_label: int, n_samples: int = 5): |
|
""" |
|
Plots sample sequences from a specified class. |
|
|
|
Args: |
|
X (np.ndarray): Dataset of sequences. |
|
y (np.ndarray): Labels (0=stable, 1=periodic, 2=chaotic). |
|
target_label (int): Class to visualize. |
|
n_samples (int): Number of sequences to plot. |
|
""" |
|
indices = np.where(y == target_label)[0] |
|
chosen = np.random.choice(indices, n_samples, replace=False) |
|
|
|
plt.figure(figsize=(12, 6)) |
|
for i, idx in enumerate(chosen): |
|
plt.plot(X[idx], label=f"Sample {i+1}") |
|
|
|
regime_name = ["Stable", "Periodic", "Chaotic"][target_label] |
|
plt.title(f"{regime_name} Regime Samples (Label = {target_label})") |
|
plt.xlabel("Time Step") |
|
plt.ylabel("x") |
|
plt.grid(True) |
|
plt.legend() |
|
plt.show() |
|
|
|
|
|
plot_class_samples(X, y, target_label=0) |
|
|
|
|
|
plot_class_samples(X, y, target_label=1) |
|
|
|
|
|
plot_class_samples(X, y, target_label=2) |
|
|
|
"""Stable: Sequences that flatten out |
|
|
|
Periodic: Repeating waveforms (2, 4, 8 points) |
|
|
|
Chaotic: No repeating pattern, jittery |
|
|
|
Each of these sequences looks completely different β even though they're all generated by the same equation. |
|
|
|
No fixed pattern. No periodic rhythm. Just deterministic unpredictability. |
|
|
|
But it's not random β it's chaotic: sensitive to initial conditions, governed by internal structure (nonlinear dynamics). |
|
|
|
Split X, y into training and testing sets |
|
|
|
Normalize (optional, but improves convergence) |
|
|
|
Convert to PyTorch tensors |
|
|
|
Create DataLoaders for training |
|
""" |
|
|
|
import torch |
|
from torch.utils.data import TensorDataset, DataLoader |
|
from sklearn.model_selection import train_test_split |
|
from sklearn.preprocessing import StandardScaler |
|
|
|
|
|
X_train, X_test, y_train, y_test = train_test_split( |
|
X, y, test_size=0.2, stratify=y, random_state=42 |
|
) |
|
|
|
|
|
scaler = StandardScaler() |
|
X_train_scaled = scaler.fit_transform(X_train) |
|
X_test_scaled = scaler.transform(X_test) |
|
|
|
|
|
X_train_tensor = torch.tensor(X_train_scaled, dtype=torch.float32) |
|
y_train_tensor = torch.tensor(y_train, dtype=torch.long) |
|
|
|
X_test_tensor = torch.tensor(X_test_scaled, dtype=torch.float32) |
|
y_test_tensor = torch.tensor(y_test, dtype=torch.long) |
|
|
|
|
|
batch_size = 64 |
|
|
|
train_dataset = TensorDataset(X_train_tensor, y_train_tensor) |
|
test_dataset = TensorDataset(X_test_tensor, y_test_tensor) |
|
|
|
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True) |
|
test_loader = DataLoader(test_dataset, batch_size=batch_size) |
|
|
|
"""This CNN will: |
|
|
|
- Take a 1D time series (length 100) |
|
|
|
- Apply temporal convolutions to learn patterns |
|
|
|
- Use global pooling to summarize features |
|
|
|
- Output one of 3 regime classes |
|
""" |
|
|
|
import torch.nn as nn |
|
import torch.nn.functional as F |
|
|
|
|
|
class ChaosCNN(nn.Module): |
|
def __init__(self, input_length=100, num_classes=3): |
|
super(ChaosCNN, self).__init__() |
|
|
|
|
|
self.conv1 = nn.Conv1d(in_channels=1, out_channels=32, kernel_size=5, padding=2) |
|
self.bn1 = nn.BatchNorm1d(32) |
|
|
|
self.conv2 = nn.Conv1d(in_channels=32, out_channels=64, kernel_size=5, padding=2) |
|
self.bn2 = nn.BatchNorm1d(64) |
|
|
|
|
|
self.global_pool = nn.AdaptiveAvgPool1d(1) |
|
|
|
|
|
self.fc = nn.Linear(64, num_classes) |
|
|
|
def forward(self, x): |
|
|
|
x = x.unsqueeze(1) |
|
|
|
x = F.relu(self.bn1(self.conv1(x))) |
|
x = F.relu(self.bn2(self.conv2(x))) |
|
|
|
x = self.global_pool(x).squeeze(2) |
|
out = self.fc(x) |
|
return out |
|
|
|
"""Conv1d: Extracts local patterns across the time dimension |
|
|
|
BatchNorm1d: Stabilizes training and speeds up convergence |
|
|
|
AdaptiveAvgPool1d: Summarizes the sequence into global stats |
|
|
|
Linear: Final decision layer for 3-class classification |
|
""" |
|
|
|
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
model = ChaosCNN().to(device) |
|
|
|
|
|
criterion = nn.CrossEntropyLoss() |
|
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) |
|
|
|
from sklearn.metrics import accuracy_score, classification_report, confusion_matrix |
|
import seaborn as sns |
|
import matplotlib.pyplot as plt |
|
|
|
|
|
def train_model(model, train_loader, test_loader, criterion, optimizer, device, epochs=15): |
|
train_losses, test_accuracies = [], [] |
|
|
|
for epoch in range(epochs): |
|
model.train() |
|
running_loss = 0.0 |
|
|
|
for X_batch, y_batch in train_loader: |
|
X_batch, y_batch = X_batch.to(device), y_batch.to(device) |
|
|
|
optimizer.zero_grad() |
|
outputs = model(X_batch) |
|
loss = criterion(outputs, y_batch) |
|
loss.backward() |
|
optimizer.step() |
|
|
|
running_loss += loss.item() * X_batch.size(0) |
|
|
|
avg_loss = running_loss / len(train_loader.dataset) |
|
train_losses.append(avg_loss) |
|
|
|
|
|
model.eval() |
|
all_preds, all_labels = [], [] |
|
|
|
with torch.no_grad(): |
|
for X_batch, y_batch in test_loader: |
|
X_batch = X_batch.to(device) |
|
outputs = model(X_batch) |
|
preds = outputs.argmax(dim=1).cpu().numpy() |
|
all_preds.extend(preds) |
|
all_labels.extend(y_batch.numpy()) |
|
|
|
acc = accuracy_score(all_labels, all_preds) |
|
test_accuracies.append(acc) |
|
|
|
print(f"Epoch {epoch+1}/{epochs} - Loss: {avg_loss:.4f} - Test Accuracy: {acc:.4f}") |
|
|
|
return train_losses, test_accuracies |
|
|
|
|
|
train_losses, test_accuracies = train_model( |
|
model, train_loader, test_loader, criterion, optimizer, device, epochs=15 |
|
) |
|
|
|
plt.figure(figsize=(12, 4)) |
|
|
|
plt.subplot(1, 2, 1) |
|
plt.plot(train_losses, label="Train Loss") |
|
plt.xlabel("Epoch") |
|
plt.ylabel("Loss") |
|
plt.title("Training Loss Over Time") |
|
plt.grid(True) |
|
|
|
plt.subplot(1, 2, 2) |
|
plt.plot(test_accuracies, label="Test Accuracy", color='green') |
|
plt.xlabel("Epoch") |
|
plt.ylabel("Accuracy") |
|
plt.title("Test Accuracy Over Time") |
|
plt.grid(True) |
|
|
|
plt.tight_layout() |
|
plt.show() |
|
|
|
|
|
model.eval() |
|
y_true, y_pred = [], [] |
|
|
|
with torch.no_grad(): |
|
for X_batch, y_batch in test_loader: |
|
X_batch = X_batch.to(device) |
|
outputs = model(X_batch) |
|
preds = outputs.argmax(dim=1).cpu().numpy() |
|
y_pred.extend(preds) |
|
y_true.extend(y_batch.numpy()) |
|
|
|
|
|
cm = confusion_matrix(y_true, y_pred) |
|
labels = ["Stable", "Periodic", "Chaotic"] |
|
|
|
plt.figure(figsize=(6, 5)) |
|
sns.heatmap(cm, annot=True, fmt="d", cmap="Blues", xticklabels=labels, yticklabels=labels) |
|
plt.title("Confusion Matrix") |
|
plt.xlabel("Predicted") |
|
plt.ylabel("Actual") |
|
plt.show() |
|
|
|
|
|
print(classification_report(y_true, y_pred, target_names=labels)) |
|
|
|
"""Input an r value (between 2.5 and 4.0) |
|
|
|
Generate a logistic map sequence |
|
|
|
Feed it to your trained model |
|
|
|
Predict the regime |
|
|
|
Plot the sequence and overlay the prediction |
|
""" |
|
|
|
|
|
label_map = {0: "Stable", 1: "Periodic", 2: "Chaotic"} |
|
|
|
def predict_regime(r_value: float, model, scaler, device, sequence_length=100): |
|
""" |
|
Generates a logistic sequence for a given r, feeds to model, and predicts regime. |
|
""" |
|
assert 2.5 <= r_value <= 4.0, "r must be between 2.5 and 4.0" |
|
|
|
|
|
x0 = np.random.uniform(0.1, 0.9) |
|
sequence = logistic_map(x0, r_value, sequence_length).reshape(1, -1) |
|
|
|
|
|
sequence_scaled = scaler.transform(sequence) |
|
|
|
|
|
sequence_tensor = torch.tensor(sequence_scaled, dtype=torch.float32).to(device) |
|
|
|
|
|
model.eval() |
|
with torch.no_grad(): |
|
output = model(sequence_tensor) |
|
pred_class = torch.argmax(output, dim=1).item() |
|
|
|
|
|
plt.figure(figsize=(10, 4)) |
|
plt.plot(sequence.flatten(), label=f"r = {r_value}") |
|
plt.title(f"Predicted Regime: {label_map[pred_class]} (Class {pred_class})") |
|
plt.xlabel("Time Step") |
|
plt.ylabel("x") |
|
plt.grid(True) |
|
plt.legend() |
|
plt.show() |
|
|
|
return label_map[pred_class] |
|
|
|
predict_regime(2.6, model, scaler, device) |
|
predict_regime(3.3, model, scaler, device) |
|
predict_regime(3.95, model, scaler, device) |
|
|
|
import gradio as gr |
|
|
|
|
|
def classify_sequence(r_value): |
|
x0 = np.random.uniform(0.1, 0.9) |
|
sequence = logistic_map(x0, r_value, 100).reshape(1, -1) |
|
sequence_scaled = scaler.transform(sequence) |
|
sequence_tensor = torch.tensor(sequence_scaled, dtype=torch.float32).to(device) |
|
|
|
model.eval() |
|
with torch.no_grad(): |
|
output = model(sequence_tensor) |
|
pred_class = torch.argmax(output, dim=1).item() |
|
|
|
|
|
fig, ax = plt.subplots(figsize=(6, 3)) |
|
ax.plot(sequence.flatten()) |
|
ax.set_title(f"Logistic Map Sequence (r = {r_value})") |
|
ax.set_xlabel("Time Step") |
|
ax.set_ylabel("x") |
|
ax.grid(True) |
|
|
|
return fig, label_map[pred_class] |
|
|
|
|
|
interface = gr.Interface( |
|
fn=classify_sequence, |
|
inputs=gr.Slider(2.5, 4.0, step=0.01, label="r (growth parameter)"), |
|
outputs=[ |
|
gr.Plot(label="Sequence Plot"), |
|
gr.Label(label="Predicted Regime") |
|
], |
|
title="π Chaos Classifier: Logistic Map Regime Detector", |
|
description="Move the slider to choose an r-value and visualize the predicted regime: Stable, Periodic, or Chaotic." |
|
) |
|
|
|
|
|
interface.launch(share=True) |