It is powerful and interpretable, but its specified regressors must adequately describe the conditional mean and the model must remain linear in its coefficients.
The raw variables need not have only a straight-line relationship: transformations and interactions can represent nonlinear shapes when the specification is appropriate.
The Beauty and Burden of the Linear Assumption
A linear relationship means that for every one-unit increase in an independent variable \(X\), the change in the dependent variable \(Y\) is constant (\(\beta\)).
But is the real world always this simple?
The Real World: A Web of Complex, Non-Linear Relationships
Many economic phenomena cannot be perfectly described by a straight line.
Diminishing Marginal Utility: The happiness gained from an increase in income diminishes at higher income levels.
The Laffer Curve: The relationship between tax rates and tax revenue is an ‘inverted U-shape’.
‘Fear’ and ‘Greed’ in Financial Markets: Asset prices react non-linearly to news, exhibiting thresholds and sharp fluctuations.
When faced with these complex non-linear relationships, traditional econometric models may fall short.
Example 1: Diminishing Marginal Utility
The higher the income, the smaller the increase in happiness from the same amount of money.
Example 2: The Laffer Curve
Higher tax rates are not always better. Excessively high rates can stifle economic activity, leading to a decrease in tax revenue.
This Chapter’s Goal: Introduce a Powerful Non-Linear Tool
In this chapter, we will learn a new modeling paradigm inspired by the workings of the human brain:
Artificial Neural Networks (ANNs)
Our measurable objectives are to:
Compute one neuron’s output from inputs, weights, and bias.
Choose Sigmoid, Tanh, or ReLU from their gradient-propagation risks.
Calculate one chain-rule and gradient-descent update by hand.
Train an MLP on chronologically split local Chinese index data and identify leakage from random splitting.
Evaluate next-period downside alerts using ROC-AUC, AP (average precision), recall, and a confusion matrix.
Metric convention
average_precision_score computes AP (average precision), whose no-skill baseline is prevalence. AP is not the trapezoidal area under the empirical PR curve.
Before You Begin
If (z=2x+1, y=z^2), what is (dy/dx) at (x=1)?
Can a forecast claim be valid if 2024 is randomly placed in training and 2018 in testing?
Is accuracy enough when positives are rare?
Write all three answers before revealing.
Reveal and remediation
\(dy/dx=2z\times2=12\); no; no—also inspect AP (average precision), recall, and the decision threshold.
If item 1 was wrong, return to the chain rule; for item 2 or 3, return to chronological splitting or imbalance metrics.
90-Minute Main lesson and Optional Topics
0–15 min: perceptron, weighted sum, and nonlinearity; complete the Sigmoid predict–reveal.
15–40 min: feedforward network, loss, gradient descent, and backpropagation; complete one chain-rule check.
40–72 min: predict next-month HS300 direction; split chronologically and compare with prior and Logit baselines.
72–85 min: confusion matrix, calibration, recall interval, and expanding-window stability.
85–90 min: lesson review—why this MLP is not a successful forecast.
Before diving into the math, let’s look at the source of inspiration. A biological neuron consists of three main parts:
Dendrites: Receive signals from other neurons.
Soma (Cell Body): Processes the received signals.
Axon: Transmits the processed signal outwards.
Signals are passed between neurons across a Synapse.
The Mathematical Abstraction: The McCulloch-Pitts Neuron
In 1943, Warren McCulloch and Walter Pitts proposed the first mathematical model of a neuron, known as the ‘M-P model’.
It simulates two key processes of a biological neuron:
Signal Aggregation: It receives input signals from multiple upstream neurons and calculates their weighted sum.
Activation Decision: It compares this weighted sum to a threshold. If the sum exceeds the threshold, the neuron ‘fires’ and outputs a signal; otherwise, it remains ‘inhibited’ and outputs nothing.
M-P Model Step 1: Signal Aggregation
Assume a neuron receives p input signals \(x_1, x_2, \dots, x_p\) from other neurons.
First, a linear transformation (weighted sum) is performed:
\[ \large{u = \sum_{i=1}^{p} w_i x_i} \]
Here, \(w_i\) represents the ‘weight’ of the \(i\)-th connection, simulating the strength of a synapse. A higher weight means the corresponding input signal is more important.
M-P Model Step 2: Activation Decision
Next, the weighted sum \(u\) is compared with a threshold \(\theta\):
The activation process then becomes checking if \(z\) is greater than or equal to 0.
The Modern Neuron: From Threshold to Smooth Activation
The M-P model’s all-or-nothing step function is discontinuous and has derivative zero almost everywhere away from its threshold, so ordinary backpropagation receives no useful gradient for updating weights.
The decisive problem is not merely one nondifferentiable point: ReLU also has a kink at zero, but retains nonzero gradients elsewhere and uses a conventional subgradient at the kink.
Therefore, modern artificial neural networks replace the simple threshold with an activation function\(f(\cdot)\) that gradient methods can handle.
Sigmoid and Tanh are differentiable everywhere; ReLU is differentiable almost everywhere and uses a conventional subgradient at its kink at zero.
Here, \(y\) is no longer just 0 or 1, but can be a continuous value.
The Soul of the Network: The Activation Function
The activation function is the soul of a neural network. It is responsible for introducing non-linearity into the model.
Key Insight
If there were no activation function (or if it were linear, \(f(x)=x\)), then no matter how many layers you stack, the entire network would be equivalent to a single, simple linear model.
Saturated
The function’s curve flattens out at both ends.
Sigmoid
Tanh
Non-Saturated (ReLU-based)
The derivative is constant in the positive region.
ReLU
Leaky ReLU
Saturated Activation 1: The Sigmoid Function
The Sigmoid function, also known as the Logistic function, was one of the most common activation functions in early neural networks.
\[ \large{\sigma(z) = \frac{1}{1 + e^{-z}}} \]
Role: Squeezes any real-valued input into the range \((0, 1)\).
Probability condition: With a binary output layer and an appropriate Bernoulli likelihood/cross-entropy objective, Sigmoid can parameterize a probability; empirical calibration must still be checked on validation data.
Pros and Cons of the Sigmoid Function
Advantages
Output is bounded; it parameterizes a Bernoulli probability only with an appropriate probabilistic objective, and calibration still requires validation.
Smooth and differentiable everywhere.
Disadvantages
Vanishing Gradients: The derivative is close to 0 in the saturated regions, making deep networks hard to train.
Not Zero-Centered:
Its range is not symmetric around zero.
The realized activation mean depends on the preactivation distribution, parameters, and data; optimization can be affected, but convergence speed is not guaranteed by the range alone.
Visualizing the Sigmoid Function and Its Derivative
Without looking ahead, compare \(\sigma'(0)\) and \(\sigma'(2)\). Which is larger? Does the function rise or fall from \(z=0\) to \(z=2\)? Distinguish “function value” from “slope.”
Reveal and remediation
\(\sigma'(0)=0.25\) and \(\sigma'(2)\approx0.105\), while \(\sigma(0)=0.5<\sigma(2)\approx0.881\).
A positive derivative means the function rises; the smaller derivative means its slope flattens.
If you predicted a falling function, revisit derivative sign; if you predicted a steeper slope, revisit saturation.
Saturated Activation 2: The Tanh Function
The hyperbolic tangent (Tanh) function is a variant of the Sigmoid.
Zero-centered means the range and graph are symmetric around zero; it does not guarantee zero-mean realized activations.
With a roughly symmetric preactivation distribution this can improve gradient directions, but actual convergence depends on the distribution and optimizer.
Pros and Cons of the Tanh Function
Advantages
A zero-centered range can help optimization under suitable input distributions, but does not guarantee faster convergence.
Output is bounded.
Smooth and differentiable.
Disadvantages
The vanishing gradient problem still exists, although it’s slightly less severe than with Sigmoid.
Visualizing the Tanh Function and Its Derivative
Derivative: \(\tanh'(z) = 1 - \tanh^2(z)\)
The Modern Default: ReLU (Rectified Linear Unit)
The Rectified Linear Unit (ReLU) is currently the most popular activation function, especially in deep learning.
where \(\alpha\) is a small positive constant, such as 0.01.
Core Idea
When the input is negative, it has a small, non-zero gradient of \(\alpha\). This ensures that the neuron’s gradient never becomes completely zero, preventing it from ‘dying’.
Visualizing the Leaky ReLU Function and Its Derivative
Mathematical derivative: \(\text{LeakyReLU}'(z) = \begin{cases} 1, & z > 0 \\ \alpha, & z < 0 \end{cases}\); for \(\alpha\ne1\) it does not exist at \(z=0\).
An implementation may separately choose a kink gradient, but that convention is not the mathematical derivative.
Activation Function Choice Strategy
Layer
Task Type
Recommended Activation
Rationale
Hidden Layers
(General)
ReLU
Fast computation, good performance, the default choice.
(If ReLU fails)
Leaky ReLU / ELU
Solves the ‘Dying ReLU’ problem.
Output Layer
Binary Classification
Sigmoid
Pair with Bernoulli likelihood/cross-entropy; validate calibration.
Multiclass Classification
Softmax
Pair with categorical likelihood/cross-entropy; validate calibration.
Regression
None (Linear)
Outputs a continuous value in any range.
Scoped default: for ordinary feedforward hidden layers, usually start with ReLU. Gated/recurrent units, legacy-compatible architectures, or other explicit constraints may legitimately use Sigmoid/Tanh; choose using validation evidence.
From a Single Neuron to a Network: The Perceptron
In 1957, Frank Rosenblatt introduced the Perceptron, which can be considered the first complete, learnable neural network model.
Structure: A single M-P model neuron.
Activation Function: The sign function, which outputs -1 or 1.
\(\mathbf{y}^{(l-1)}\) is the output of the \((l-1)\)-th layer (or the original input \(\mathbf{x}\) when \(l=1\)).
\(\mathbf{W}^{(l)}\) and \(\mathbf{b}^{(l)}\) are the weight matrix and bias vector for the \(l\)-th layer.
\(f^{(l)}\) is the activation function for the \(l\)-th layer.
Network Architecture: Depth vs. Width
Width
The number of neurons in a hidden layer.
Wider networks can learn more complex features at a given layer.
Risk: Prone to overfitting.
Depth
The number of hidden layers.
Deeper networks can learn a hierarchy of features (from simple to complex).
Universal Approximation Theorem: A single hidden layer network with enough width can approximate any continuous function. However, in practice, deep networks are often more efficient than shallow, wide ones.
How to Train an MLP: The Core Idea
We have the network structure, but how do we find the optimal parameter values for the thousands (or millions) of parameters (all the W’s and b’s)?
Define a Loss Function: First, we need a function to measure how ‘bad’ the model’s predictions are.
Regression: Mean Squared Error (MSE)
Classification: Cross-Entropy
Objective: Find the set of parameters \((\mathbf{W}, \mathbf{b})\) that minimizes the total loss over the entire training set.
Method: Use the Gradient Descent algorithm.
Gradient Descent: An Intuition
Imagine you are on a dark mountain and your goal is to walk to the lowest point in the valley.
You feel around with your foot to find the direction of the steepest slope (this is the gradient).
You take a small step in the direction of the steepest descent.
You repeat this process, step by step, making your way down to the valley floor.
Taking the derivative of this directly is nearly impossible. We need an efficient algorithm to compute this gradient.
The Solution: The Backpropagation Algorithm
The Backpropagation (BP) algorithm is the cornerstone of training neural networks. It is essentially an efficient application of the Chain Rule from calculus to a neural network.
It involves two phases:
Forward Pass: From input to output, compute the prediction and the loss.
Backward Pass: From output to input, compute the gradient of the loss with respect to the parameters of each layer.
The Core of Backpropagation: The Chain Rule
If we have \(y = f(u)\) and \(u = g(x)\), then the derivative of \(y\) with respect to \(x\) is:
Dependency chain:\(L\) depends on the final output \(\mathbf{y}^{(L)}\), which depends on the net input \(\mathbf{z}^{(L)}\).
Layerwise dependence:\(\mathbf{z}^{(L)}\) is determined by \(\mathbf{y}^{(L-1)}\) and parameters \(\mathbf{W}^{(L)},\mathbf{b}^{(L)}\).
Backpropagation: the chain rule passes the gradient signal efficiently from the last layer back to the first.
Chain-Rule Check
Compute first: if \(u=2x+1\) and \(y=u^2\), what is \(dy/dx\) at \(x=1\)? Write both local derivatives.
Reveal and remediation:\(du/dx=2\) and \(dy/du=2u=6\), so \(dy/dx=12\). If you wrote 6, revisit multiplying local gradients; if you wrote 4, substitute \(x=1\) into \(u\) first.
Core Computation Practice: Neuron, Activation, and Update
Complete all three steps before revealing:
Given \(\mathbf{x}=(2,-1)\), \(\mathbf{w}=(0.5,-0.25)\), \(b=0.1\), and ReLU, compute \(z=\mathbf{w}^T\mathbf{x}+b\) and output \(y\).
A deep hidden unit must retain gradient for large positive inputs.
Which comes first among Sigmoid, Tanh, and ReLU?
If the output must parameterize a Bernoulli probability, what training objective and validation check are also required?
If \(u=2\theta+1\), \(L=u^2\), \(\theta_{old}=1\), and \(\eta=0.1\), compute \(dL/d\theta\) and \(\theta_{new}\).
Core Computation Practice: Complete Solution
\(z=0.5(2)+(-0.25)(-1)+0.1=1.35\), so \(y=\max(0,z)=1.35\). (2) Use ReLU first for the hidden unit because its positive-region gradient does not saturate.
Sigmoid can parameterize a Bernoulli probability when paired with a Bernoulli likelihood/cross-entropy objective, and calibration must be checked on validation data.
Tanh has a range symmetric around zero, but its realized mean need not be zero and it still saturates at both tails. (3) \(dL/d\theta=(2u)(2)=12\), so \(\theta_{new}=1-0.1(12)=-0.2\).
If you obtained \(2.2\), you performed ascent along the gradient.
In Practice: Alerting Next-Month HS300 Downside with Local Data
Using local HS300 daily observations, we construct a monthly task: at month-end (t), use only information then available to predict whether month (t+1) has a negative return.
File/key:data/index/hs300_index_only.h5 / hs300
Raw fields:datetime, close, volume, total_turnover; prices are index points and turnover follows the local dictionary
Sample: 2005-01-01 to 2024-12-31; the target only denotes next-month HS300 return direction
Evaluation: first 70% train, next 15% validation, final 15% test, strictly chronological
Feature Selection
All inputs are computable at month-end (t): current monthly return, three-month momentum, 20-day realized volatility, and monthly turnover growth.
The label uses the next month’s return, keeping the contemporaneous outcome out of the feature set.
Read the local HDF5 directly, with no network dependency and no random fallback.
Code
from pathlib import Path # Locating Local Index Files Using Path Objectsimport numpy as np # Calculate Logarithmic Change vs. Finite Valueimport pandas as pd # Read daily rows and aggregate them by month# Public download: https://assets.qiufei.site/data/index/hs300_index_only.h5# After downloading, change the next line to the file's actual location on your device.# Course-relative option: Path("data/index/hs300_index_only.h5")# Windows: Path(r"C:\qiufei\data\index\hs300_index_only.h5")# macOS: Path("/Users/your_name/data/index/hs300_index_only.h5")# Linux: Path("/home/your_name/data/index/hs300_index_only.h5")index_path = Path("/home/ubuntu/r2_data_mount/data/index/hs300_index_only.h5")daily_index = pd.read_hdf(index_path, key='hs300') # Reading real daily quotes from fixed HDF keydaily_index['date'] = pd.to_datetime(daily_index['datetime'].astype(str), format='%Y%m%d%H%M%S') # Parse local timestampdaily_index = daily_index.query("'2005-01-01' <= date <= '2024-12-31'").set_index('date').sort_index() # Fixed Sample Period and Sequencemonthly_index = daily_index.resample('ME').agg(close=('close', 'last'), turnover=('total_turnover', 'sum')) # Summarize end-of-month point and monthly turnovermonthly_index['monthly_return'] = monthly_index['close'].pct_change() # Calculate the current monthly returnmonthly_index['momentum_3m'] = monthly_index['close'].pct_change(3) # Compute three-month momentum at month-endmonthly_index['realized_volatility'] = daily_index['close'].pct_change().rolling(20).std().resample('ME').last() * np.sqrt(20) # Calculate 20th month Volatilitymonthly_index['turnover_growth'] = monthly_index['turnover'].pct_change() # Calculate current-month turnover growthmonthly_index['next_month_return'] = monthly_index['monthly_return'].shift(-1) # Aligning next-month realized returnmonthly_index['target_date_t1'] = monthly_index.index.to_series().shift(-1) # Preserve the next-month label-realization datefeature_columns = ['monthly_return', 'momentum_3m', 'realized_volatility', 'turnover_growth'] # Fix four features available at month-endanalysis_frame = monthly_index.dropna(subset=feature_columns + ['next_month_return', 'target_date_t1']).copy() # Drop the final month whose next-month return is unknownanalysis_frame['next_month_down'] = analysis_frame['next_month_return'].lt(0).astype(int) # Create binary labels only for observed future returnslabel_realization_date = analysis_frame.index + pd.offsets.MonthEnd(1) # Record label realization at the following month-endassert (analysis_frame['target_date_t1'].dt.to_period('M') == analysis_frame.index.to_period('M') +1).all() # Verify exact next-calendar-month realizationdisplay(analysis_frame.head()) # Display real input fields and label construction
close
turnover
monthly_return
momentum_3m
realized_volatility
turnover_growth
next_month_return
target_date_t1
next_month_down
date
2005-04-30
932.395
1.611263e+11
-0.010406
-0.023547
0.059615
0.046432
-0.081992
2005-05-31
1
2005-05-31
855.946
7.756412e+10
-0.081992
-0.176967
0.049075
-0.518613
0.026567
2005-06-30
0
2005-06-30
878.686
1.681570e+11
0.026567
-0.067410
0.104620
1.167974
0.010787
2005-07-31
0
2005-07-31
888.164
1.172239e+11
0.010787
-0.047438
0.058266
-0.302890
0.044757
2005-08-31
0
2005-08-31
927.916
2.182591e+11
0.044757
0.084082
0.058778
0.861898
-0.011342
2005-09-30
1
Step 2: Define Features and Target, and Split the Dataset
Neural networks are very sensitive to the scale of input features.
If different features have vastly different numerical ranges, the training process can become unstable.
Standardization, which scales all features to have a mean of 0 and a standard deviation of 1, is a crucial preprocessing step.
Note: We fit_transform only on the training set.
The test set must be transformed using the same scaling rules learned from the training set to avoid data leakage.
Code
from sklearn.preprocessing import StandardScalerscaler = StandardScaler()scaled_training_features = scaler.fit_transform(training_features)scaled_validation_features = scaler.transform(validation_features) # Transforming Continuous Validation Periods Using Training Period Parametersscaled_testing_features = scaler.transform(testing_features) # Transform fixed test samples with the training-period scaler onlyprint('Pre-scaling train set means:', np.mean(training_features, axis=0).values.round(2)) # 展示当前步骤的结果。print('Post-scaling train set means:', np.mean(scaled_training_features, axis=0).round(2)) # 展示当前步骤的结果。print('Post-scaling train set std devs:', np.std(scaled_training_features, axis=0).round(2)) # 展示当前步骤的结果。
Pre-scaling train set means: [0.01 0.04 0.07 0.1 ]
Post-scaling train set means: [ 0. 0. 0. -0.]
Post-scaling train set std devs: [1. 1. 1. 1.]
The validation-cost threshold is now fixed. Only now do we open the test set for one common check using a classification report and ranking metrics:
Precision: Of all months alerted as “next-month down,” how many actually fell? (TP / (TP + FP))
Recall: Of all months that actually fell, how many were alerted? (TP / (TP + FN))
F1-score: The harmonic mean of precision and recall.
Code
from sklearn.metrics import average_precision_score, classification_report, roc_auc_score # Evaluating Categories and Sorting Performance at the Same Timey_prob = mlp.predict_proba(scaled_testing_features)[:, 1] # Generate held-out test decline probabilities for the first timey_pred = (y_prob >= selected_cost_threshold).astype(int) # Apply the threshold fixed before test accesstest_cost_confusion = confusion_matrix(y_test, y_pred) # Save the cost confusion matrix in the same checkprint('Classification Report (Test Set):') # Mark the classification indicators from the test window that never participated in parameter tuningprint(classification_report(y_test, y_pred, target_names=['Up/Flat', 'Down'])) # 展示当前步骤的结果。print(f'Validation ROC-AUC: {roc_auc_score(y_validation, y_validation_prob):.3f}') # Report Validation Window ranking performanceprint(f'Test ROC-AUC: {roc_auc_score(y_test, y_prob):.3f}')print(f'Test AP (average precision): {average_precision_score(y_test, y_prob):.3f}') # Report Metrics More Sensitive to Downstream Categoriesprint(f'fixed-threshold test cost: {4* test_cost_confusion[1, 0] + test_cost_confusion[0, 1]}') # Report the complete cost within the same check
Classification Report (Test Set):
precision recall f1-score support
Up/Flat 0.36 0.71 0.48 14
Down 0.50 0.18 0.27 22
accuracy 0.39 36
macro avg 0.43 0.45 0.37 36
weighted avg 0.44 0.39 0.35 36
Validation ROC-AUC: 0.343
Test ROC-AUC: 0.412
Test AP (average precision): 0.566
fixed-threshold test cost: 76
Baselines First: This MLP Underperforms Simple References
Code
from sklearn.dummy import DummyClassifier # Establish a priori probability baseline for the training periodfrom sklearn.linear_model import LogisticRegression # Establish linear probability baseline for same fieldfrom sklearn.metrics import brier_score_loss, recall_score # Evaluation Probability Error and decline recallcomparison_models = {'Prior baseline': DummyClassifier(strategy='prior'), 'Logit': LogisticRegression(max_iter=1000, random_state=42), 'MLP': mlp} # Fixed Three Comparable Modelscomparison_rows = []for model_name, comparison_model in comparison_models.items(): # Compare on Same Training Sampleif model_name !='MLP': # Avoid fitting the trained network repeatedly comparison_model.fit(scaled_training_features, y_train) # Fit Baseline by Training Period Only comparison_probability = comparison_model.predict_proba(scaled_testing_features)[:, 1] # Generate Down Probability Next Month comparison_prediction = (comparison_probability >=.5).astype(int) # Use common default thresholds comparison_rows.append([model_name, roc_auc_score(y_test, comparison_probability), average_precision_score(y_test, comparison_probability), recall_score(y_test, comparison_prediction), brier_score_loss(y_test, comparison_probability)]) # Summarize sort, recall, and probability errorbaseline_table = pd.DataFrame(comparison_rows, columns=['model', 'ROC-AUC', 'AP (average precision)', 'down recall', 'Brier']) # Generate Comparison Tabledisplay(baseline_table.round(3)) # Display executed evidence
model
ROC-AUC
AP (average precision)
down recall
Brier
0
Prior baseline
0.500
0.611
0.000
0.274
1
Logit
0.360
0.531
0.045
0.292
2
MLP
0.412
0.566
0.182
0.451
Executed result:
the test set has only 36 months and downside prevalence 0.611.
Neither fitted model beats the PR prevalence baseline.
The MLP reaches the 500-iteration cap without convergence; Brier=0.451003, worse than the freshly executed purge-aware prior baseline’s 0.273899.
Baseline Decision: Do Not Deploy
Evidence: this is an honest failure case, not successful prediction.
Limits: small samples, regime drift, reversed or unstable ranking, and non-convergence constrain inference.
Boundary: the test set is never reused for training, tuning, or threshold choice.
Calibration and Uncertainty: Do Not Take Probabilities at Face Value
Code
from sklearn.metrics import confusion_matrix # Read Four Frame Count of Same Test Forecastcalibration_data = pd.DataFrame({'predicted_probability': y_prob, 'observed_down': y_test.to_numpy()}) # Aligning Test Probability and Real Dropscalibration_data['probability_bin'] = pd.qcut(calibration_data['predicted_probability'], q=3, duplicates='drop') # Avoid empty boxes with three equal-frequency groupscalibration_table = calibration_data.groupby('probability_bin', observed=True).agg(mean_predicted=('predicted_probability', 'mean'), observed_rate=('observed_down', 'mean'), n=('observed_down', 'size')) # Compare predicted and actual frequenciestrue_negative, false_positive, false_negative, true_positive = confusion_matrix(y_test, y_pred).ravel() # Read Same Test Confusion Matrixpositive_count = true_positive + false_negative # Count the number of months of true fallrecall_estimate = true_positive / positive_count # Calculate Point Estimated Recallwilson_denominator =1+1.96**2/ positive_count # Constructing 95% Wilson Interval Denominatorwilson_center = (recall_estimate +1.96**2/ (2* positive_count)) / wilson_denominator # Calculate the center of the intervalwilson_half_width =1.96* np.sqrt(recall_estimate * (1- recall_estimate) / positive_count +1.96**2/ (4* positive_count **2)) / wilson_denominator # Calculate the half-width of the intervaldisplay(calibration_table.round(3)) # Demonstrate Reliability instead of AUCprint(f'Down recall={recall_estimate:.3f}; 95% Wilson interval=[{wilson_center-wilson_half_width:.3f}, {wilson_center+wilson_half_width:.3f}]') # Report Limited Sample Uncertainty
mean_predicted
observed_rate
n
probability_bin
(-0.000999871, 0.0968]
0.032
0.667
12
(0.0968, 0.353]
0.230
0.667
12
(0.353, 0.876]
0.580
0.500
12
Down recall=0.182; 95% Wilson interval=[0.073, 0.385]
The three mean predicted probabilities are about 0.032, 0.230, and 0.580, while observed downside rates are 0.667, 0.667, and 0.500, indicating weak reliability.
Downside recall is 0.182 with a 95% Wilson interval [0.073, 0.385].
The interval ignores temporal dependence, so it is a small-sample warning rather than a formal confidence interval.
Visualizing the Confusion Matrix
A confusion matrix provides a clear visual breakdown of the model’s performance across different classes.
Top-left (TN): actual up/flat and no downside alert.
Bottom-right (TP): actual down and a correct downside alert.
Top-right (FP): actual up/flat but an alert was issued; the cost is unnecessary risk reduction or missed upside.
Bottom-left (FN): actual down but no alert; the cost is unprotected downside exposure.
fixed-Test Confusion Matrix: Executed Counts
Code
import matplotlib.pyplot as pltfrom sklearn.metrics import ConfusionMatrixDisplay # Convert Same Forecast from Previous Page to Confusion Matrixfig, ax = plt.subplots(figsize=(8, 6)) # Create teaching canvas for test set confusion matrixconfusion_display = ConfusionMatrixDisplay.from_predictions( y_test, y_pred, ax=ax, cmap='Blues', display_labels=['Up/Flat', 'Down'])ax.set_title('Next-Month Downside Alert: fixed Test Period', fontsize=42)ax.tick_params(axis='both', labelsize=36)ax.xaxis.label.set_size(40); ax.yaxis.label.set_size(40)for label in confusion_display.text_.ravel(): label.set_fontsize(42)confusion_display.im_.colorbar.ax.tick_params(labelsize=36)plt.show() # 展示当前步骤的结果。
Figure 1: Test-period confusion matrix for the HS300 next-month downside alert
Expanding Windows: Weak Performance Is Not One Holdout Accident
Code
from sklearn.model_selection import TimeSeriesSplit # Construct Training Window Expanding Only the Fold Not Looked Backdevelopment_mask = (analysis_frame.index < test_start_date) & (analysis_frame['target_date_t1'] < test_start_date) # Keep only labels realized before final testdevelopment_features = input_feature_matrix.loc[development_mask] # Diagnose stability on the purged development perioddevelopment_target = target_values.loc[development_mask] # Keep target aligned with feature datestime_splitter = TimeSeriesSplit(n_splits=5, test_size=12, gap=1) # Purge one month for the next-month horizonfold_rows = [] # save break dates and metricsfor fold_number, (fold_train, fold_test) inenumerate(time_splitter.split(development_features), 1): # Extend training period in sequenceassert analysis_frame.loc[development_features.index[fold_train], 'target_date_t1'].max() < development_features.index[fold_test].min() # Verify each fold's label boundary fold_scaler = StandardScaler() # Estimate scaling individually per fold Avoid leaks fold_train_scaled = fold_scaler.fit_transform(development_features.iloc[fold_train]) # Only fitting the current training month fold_test_scaled = fold_scaler.transform(development_features.iloc[fold_test]) # Transform a continuous test block with a training parameter fold_model = MLPClassifier(hidden_layer_sizes=(50, 50), max_iter=500, random_state=42) # Reuse the classroom MLP specification fold_model.fit(fold_train_scaled, development_target.iloc[fold_train]) # Train from scratch per fold fold_probability = fold_model.predict_proba(fold_test_scaled)[:, 1] # Generate probability of declineout fold_rows.append([fold_number, development_features.index[fold_train[-1]], development_features.index[fold_test[0]], development_features.index[fold_test[-1]], roc_auc_score(development_target.iloc[fold_test], fold_probability), average_precision_score(development_target.iloc[fold_test], fold_probability)]) # Record Date and Metricfold_table = pd.DataFrame(fold_rows, columns=['fold', 'train end', 'test start', 'test end', 'ROC-AUC', 'AP (average precision)']) # Form a complete answerdisplay(fold_table.round(3)) # Demonstrate All Folds instead of Best Folds
fold
train end
test start
test end
ROC-AUC
AP (average precision)
0
1
2016-09-30
2016-11-30
2017-10-31
0.444
0.498
1
2
2017-09-30
2017-11-30
2018-10-31
0.694
0.622
2
3
2018-09-30
2018-11-30
2019-10-31
0.406
0.371
3
4
2019-09-30
2019-11-30
2020-10-31
0.314
0.395
4
5
2020-09-30
2020-11-30
2021-10-31
0.444
0.604
Executed output
fold ROC-AUC values are 0.444, 0.694, 0.406, 0.314, and 0.444; AP (average precision) values are 0.498, 0.622, 0.371, 0.395, and 0.604.
Results vary widely and several folds rank below 0.5; every network reaches the 500-iteration cap.
Never select only the best fold or deploy from this evidence.
Core review: An Executable Selective Decision
Given this evidence, the selective action is do not use MLP probabilities for automated trading or formal risk limits.
Keep the model as a classroom diagnostic until it beats prior/Logit baselines across multiple time folds, improves Brier and reliability, and locks a cost-based threshold on validation data.
The complete index file is large, so this example uses the 1.48MB hs300_index_only.h5 / hs300, containing only datetime, close, total_turnover for 2005–2024.
It runs directly on an ordinary computer.
To study another index, download a single-index file with the same fields and change the file name.
Core learners now jump to leakage check and transfer to close the learning path. Extension learners continue below and follow the return link at the end.
The MLPs we’ve discussed are fully-connected, meaning every neuron in a layer is connected to every neuron in the previous layer.
When processing data with spatial or temporal structure, like images or time series, the number of parameters in a fully-connected network explodes, and it fails to leverage the local structure of the data.
A Convolutional Neural Network (CNN) is a special type of feedforward network that addresses these issues through local connectivity and weight sharing.
The Core Idea of CNNs: Analyzing Data Like a Visual System
CNNs are inspired by the biological visual cortex.
Receptive Field: Each neuron focuses only on a small region of the input (local connectivity).
Feature Map: A ‘filter’ or ‘kernel’ slides across the entire input, searching for a specific pattern (like an edge or corner) and generating a feature map (weight sharing).
This is like how we look at a photo: we don’t process every pixel at once, but rather identify local lines and shapes first, then combine them into more complex objects.
The Key Layer of a CNN: The Convolutional Layer
At each position, a kernel computes an element-wise product sum (plus bias) to extract a local feature.
The Key Layer of a CNN: The Pooling Layer
A pooling layer (or downsampling layer) typically follows a convolutional layer.
Purpose:
Downsampling: Reduces feature-map size. Pooling itself has no learned parameters and can reduce computation and parameters in downstream layers.
Local translation tolerance: Makes responses less sensitive to small shifts within a pooling window. This is not rotation invariance and does not guarantee global translation invariance.
Common Methods:
Max Pooling: Takes the maximum value from a region.
Average Pooling: Calculates the average value of a region.
Visualizing Max Pooling
The diagram below shows a 2x2 max pooling operation on a 4x4 feature map.
CNN Applications in Economics?
Although CNNs were born from image recognition, their core idea of recognizing local patterns can be applied to economics:
Time Series Analysis: A financial time series (e.g., stock prices) can be treated as a 1D ‘image’. CNNs can be used to identify technical analysis patterns like ‘head and shoulders’ or ‘double bottoms’.
Textual Analysis: A matrix of word vectors from a sentence can be treated as a 2D image. CNNs can extract local semantic features for analyzing the sentiment or topics of financial reports and news articles.
Satellite Imagery Analysis: Using satellite data like nighttime lights or ships in ports to predict regional economic activity.
A Brief History of Neural Networks: A Tour of Famous Models
Since 2012, the field of deep learning has seen a surge of landmark CNN architectures. Understanding them helps us appreciate how networks have become progressively deeper and more powerful.
LeNet-5 (1998): The ancestor of modern CNNs.
AlexNet (2012): Popularized the combination of ReLU, dropout, and GPU training at ImageNet scale.
VGGNet (2014): Demonstrated the importance of network depth.
GoogLeNet (2014): Introduced the ‘Inception module’, improving network width and efficiency.
ResNet (2015): Introduced ‘residual connections’, solving the training problem for extremely deep networks.
LeNet-5 (1998): The Founder of a Classic Architecture
Proposed by Yann LeCun for recognizing handwritten digits on checks. Its classic architecture [CONV -> POOL -> CONV -> POOL -> FC -> OUTPUT] is still influential today.
AlexNet (2012): The ‘Big Bang’ of Deep Learning
AlexNet won the 2012 ImageNet competition by a massive margin, heralding the dawn of the deep learning era.
Key Contributions:
Successful training of a comparatively deep CNN at ImageNet scale.
Widespread use of ReLU, which accelerated training.
Dropout and data augmentation to reduce overfitting.
Multi-GPU computation that made large-scale training a reproducible major advance.
VGGNet (2014): Depth is Power
The VGG team explored a simple but profound question: does making the network deeper improve performance?
Core Idea:
Minimalism: Used only small 3x3 convolution kernels and 2x2 pooling layers.
Stacking: By repeatedly stacking these simple blocks, they built very deep networks (e.g., VGG16, VGG19).
VGG proved that, to a certain extent, increasing network depth can significantly boost performance.
GoogLeNet (2014): Wider and More Efficient Networks
Inception module: run 1×1, 3×3, 5×5 convolutions and pooling in parallel, then concatenate their multi-scale features.
Efficiency: 1×1 convolutions reduce channels before expensive branches, sharply lowering parameter and compute cost.
ResNet (2015): Bridging the Depth Gap
As networks get extremely deep, a ‘degradation’ problem emerges: the training error of a deeper network is higher than that of its shallower counterpart.
ResNet (Residual Network), proposed by Kaiming He et al. at Microsoft Research Asia, elegantly solved this problem.
Core Idea: The Shortcut/Skip Connection
It allows information to ‘skip’ one or more layers. The network no longer needs to learn an identity mapping from scratch; it only needs to learn the ‘residual’ between the input and the output.
\[ \large{H(x) = F(x) + x} \]
ResNet’s innovation made it possible to train ultra-deep networks of hundreds or even thousands of layers; extension learners then return to the common leakage check and transfer.
ResNet Residual Block Diagram
Formative Check: Detect Time Leakage
Question: A random stratified split of 2005–2024 monthly data produces a higher test score. Does that establish better generalization?
Answer yes/no first and name the time boundary crossed.
Reveal and remediation: No. Later regimes enter training and adjacent months cross sets. If you answered yes, return to the practical use timeline; if you mentioned only a random seed, return to expanding windows.
Before the final test period, use five expanding folds with consecutive 12-month test blocks.
The purge-aware 199-month development sample induces training windows of 138, 150, 162, 174, and 186 months.
Submit each training end, test start/end, ROC-AUC, and AP (average precision).
Step-by-Step Exercise: Complete Evidence
Complete answer:
on 199 eligible development months from 2005-04 through 2021-10, TimeSeriesSplit(n_splits=5, test_size=12, gap=1) produces training windows of 138/150/162/174/186 months.
The one-month gap purges the next-month label boundary; the core code refits scaler and MLP in every fold and prints each date boundary.
Fold ROC-AUC values are 0.444, 0.694, 0.406, 0.314, and 0.444; AP values are 0.498, 0.622, 0.371, 0.395, and 0.604.
A valid answer keeps every fold, non-convergence, and the “do not deploy” interpretation.
Independent check Reconstruction: One decision process
Task:
reconstruct the decision process from the analysis: state the 4:1 cost, the three validation thresholds, when the final threshold was chosen, and the single test evaluation.
Do not reread y_test to select a threshold or generate a second test prediction.
Reference implementation:
threshold_cost_table and selected_cost_threshold were saved before test access.
test_cost_confusion came from the subsequent sole test check.
This page displays saved objects without recomputing from test labels or probabilities.
Apply It to a New Case: Reference Implementation and Output
Code
display(threshold_cost_table) # Show the validation evidence saved before test accessprint(selected_cost_threshold, test_cost_confusion.ravel(), 4* test_cost_confusion[1, 0] + test_cost_confusion[0, 1]) # Report Thresholds, Quads, and Costs
threshold
validation cost
0
0.3
52
1
0.5
47
2
0.7
49
0.5 [10 4 18 4] 76
Complete reference output:
validation costs are 52, 47, and 49, so threshold 0.5 is fixed.
Test confusion is TN=10, FP=4, FN=18, TP=4, with total cost 76.
Even under this illustrative cost function, missed alerts remain high; the decision remains “no automated action.”
Apply It to a New Case: Answer Checklist
Complete-answer reminder:
identify the file, key and required fields;
align \(t\rightarrow t+1\) correctly;
choose the threshold with validation data and use the test period once;
report the confusion matrix and cost;
and conclude that weak evidence does not justify action.
Sources and Further Reading
Goodfellow, I., Bengio, Y., and Courville, A. (2016), Deep Learning, MIT Press.
Rumelhart, D. E., Hinton, G. E., and Williams, R. J. (1986), “Learning Representations by Back-propagating Errors,” Nature.
Data: local hs300_index_only.h5 / hs300; all evaluation and confusion-matrix results come from the same completed analysis.
Core Summary: Evidence for All Five Objectives
Neuron: compute the weighted sum and bias, then apply the activation to obtain its output.
Activation choice: select Sigmoid, Tanh, or ReLU from gradient risk and architecture constraints; a default is not a prohibition.
Learning mechanism: the chain rule backpropagates loss gradients, which gradient descent uses to update parameters.
Model choice: order train → validation → test in time; choose the threshold on validation only, then evaluate it once on the test period.
Evidence decision: interpret ROC-AUC, AP, recall, and the confusion matrix together; high misses and weak evidence here mean do not deploy.
Extension Summary: CNNs and Model History
CNNs use local connectivity, shared weights, and pooling for spatial structure; model history shows how these components evolved into deeper architectures.
Conclusion: A New Paradigm for Economic Modeling
Capturing Non-linearity: The core strength of neural networks is their powerful ability to fit non-linear relationships, helping us understand complex economic phenomena that linear models cannot explain.
Data-Driven: They are highly data-driven models capable of automatically learning features and patterns from large-scale datasets.
A Powerful Toolkit: Core covers MLP training and evaluation; CNNs extend the same ideas to structured inputs in the optional section.
Future Outlook and Caveats
Explainability (XAI):
Neural networks are often called ‘black box’ models because their decision-making processes are not transparent.
This is a major hurdle for their application in high-stakes areas like policy advice and credit scoring, and it is a hot research topic.
Causal Inference:
Neural networks excel at prediction (finding correlations) but cannot be directly used for causal inference.
Combining neural networks with causal inference frameworks (like Diff-in-Diff or Instrumental Variables) is a frontier research area.
More Models: We only introduced feedforward networks today. For time series data, Recurrent Neural Networks (RNNs) and their variants (like LSTM, GRU) are a more natural choice.