/****************************************************************************** ==================================================== Network: Backpropagation Network with Bias Terms and Momentum ==================================================== Application: Time-Series Forecasting Prediction of the Annual Number of Sunspots Description: This program implements the now classic multi-layer backpropagation network with bias terms and momentum. It is used to detect structure in time-series, which is presented to the network using a simple tapped delay-line memory. The program learns to predict future sunspot activity from historical data collected over the past three centuries. To avoid overfitting, the termination of the learning procedure is controlled by the so-called stopped training method. Author: Karsten Kutza Date: 17.4.96 Reference: D.E. Rumelhart, G.E. Hinton, R.J. Williams Learning Internal Representations by Error Propagation in: D.E. Rumelhart, J.L. McClelland (Eds.) Parallel Distributed Processing, Volume 1 MIT Press, Cambridge, MA, pp. 318-362, 1986 ******************************************************************************/ /****************************************************************************** D E C L A R A T I O N S ******************************************************************************/ #include #include #include typedef int BOOL; typedef int INT; typedef double REAL; #define FALSE 0 #define TRUE 1 #define NOT ! #define AND && #define OR || #define MIN_REAL -HUGE_VAL #define MAX_REAL +HUGE_VAL #define MIN(x,y) ((x)<(y) ? (x) : (y)) #define MAX(x,y) ((x)>(y) ? (x) : (y)) #define LO 0.1 #define HI 0.9 #define BIAS 1 #define sqr(x) ((x)*(x)) typedef struct { /* A LAYER OF A NET: */ INT Units; /* - number of units in this layer */ REAL* Output; /* - output of ith unit */ REAL* Error; /* - error term of ith unit */ REAL** Weight; /* - connection weights to ith unit */ REAL** WeightSave; /* - saved weights for stopped training */ REAL** dWeight; /* - last weight deltas for momentum */ } LAYER; typedef struct { /* A NET: */ LAYER** Layer; /* - layers of this net */ LAYER* InputLayer; /* - input layer */ LAYER* OutputLayer; /* - output layer */ REAL Alpha; /* - momentum factor */ REAL Eta; /* - learning rate */ REAL Gain; /* - gain of sigmoid function */ REAL Error; /* - total net error */ } NET; /****************************************************************************** R A N D O M S D R A W N F R O M D I S T R I B U T I O N S ******************************************************************************/ void InitializeRandoms() { srand(4711); } INT RandomEqualINT(INT Low, INT High) { return rand() % (High-Low+1) + Low; } REAL RandomEqualREAL(REAL Low, REAL High) { return ((REAL) rand() / RAND_MAX) * (High-Low) + Low; } /****************************************************************************** A P P L I C A T I O N - S P E C I F I C C O D E ******************************************************************************/ #define NUM_LAYERS 3 #define N 30 #define M 1 INT Units[NUM_LAYERS] = {N, 10, M}; #define FIRST_YEAR 1700 #define NUM_YEARS 280 #define TRAIN_LWB (N) #define TRAIN_UPB (179) #define TRAIN_YEARS (TRAIN_UPB - TRAIN_LWB + 1) #define TEST_LWB (180) #define TEST_UPB (259) #define TEST_YEARS (TEST_UPB - TEST_LWB + 1) #define EVAL_LWB (260) #define EVAL_UPB (NUM_YEARS - 1) #define EVAL_YEARS (EVAL_UPB - EVAL_LWB + 1) REAL Sunspots_[NUM_YEARS]; REAL Sunspots [NUM_YEARS] = { 0.0262, 0.0575, 0.0837, 0.1203, 0.1883, 0.3033, 0.1517, 0.1046, 0.0523, 0.0418, 0.0157, 0.0000, 0.0000, 0.0105, 0.0575, 0.1412, 0.2458, 0.3295, 0.3138, 0.2040, 0.1464, 0.1360, 0.1151, 0.0575, 0.1098, 0.2092, 0.4079, 0.6381, 0.5387, 0.3818, 0.2458, 0.1831, 0.0575, 0.0262, 0.0837, 0.1778, 0.3661, 0.4236, 0.5805, 0.5282, 0.3818, 0.2092, 0.1046, 0.0837, 0.0262, 0.0575, 0.1151, 0.2092, 0.3138, 0.4231, 0.4362, 0.2495, 0.2500, 0.1606, 0.0638, 0.0502, 0.0534, 0.1700, 0.2489, 0.2824, 0.3290, 0.4493, 0.3201, 0.2359, 0.1904, 0.1093, 0.0596, 0.1977, 0.3651, 0.5549, 0.5272, 0.4268, 0.3478, 0.1820, 0.1600, 0.0366, 0.1036, 0.4838, 0.8075, 0.6585, 0.4435, 0.3562, 0.2014, 0.1192, 0.0534, 0.1260, 0.4336, 0.6904, 0.6846, 0.6177, 0.4702, 0.3483, 0.3138, 0.2453, 0.2144, 0.1114, 0.0837, 0.0335, 0.0214, 0.0356, 0.0758, 0.1778, 0.2354, 0.2254, 0.2484, 0.2207, 0.1470, 0.0528, 0.0424, 0.0131, 0.0000, 0.0073, 0.0262, 0.0638, 0.0727, 0.1851, 0.2395, 0.2150, 0.1574, 0.1250, 0.0816, 0.0345, 0.0209, 0.0094, 0.0445, 0.0868, 0.1898, 0.2594, 0.3358, 0.3504, 0.3708, 0.2500, 0.1438, 0.0445, 0.0690, 0.2976, 0.6354, 0.7233, 0.5397, 0.4482, 0.3379, 0.1919, 0.1266, 0.0560, 0.0785, 0.2097, 0.3216, 0.5152, 0.6522, 0.5036, 0.3483, 0.3373, 0.2829, 0.2040, 0.1077, 0.0350, 0.0225, 0.1187, 0.2866, 0.4906, 0.5010, 0.4038, 0.3091, 0.2301, 0.2458, 0.1595, 0.0853, 0.0382, 0.1966, 0.3870, 0.7270, 0.5816, 0.5314, 0.3462, 0.2338, 0.0889, 0.0591, 0.0649, 0.0178, 0.0314, 0.1689, 0.2840, 0.3122, 0.3332, 0.3321, 0.2730, 0.1328, 0.0685, 0.0356, 0.0330, 0.0371, 0.1862, 0.3818, 0.4451, 0.4079, 0.3347, 0.2186, 0.1370, 0.1396, 0.0633, 0.0497, 0.0141, 0.0262, 0.1276, 0.2197, 0.3321, 0.2814, 0.3243, 0.2537, 0.2296, 0.0973, 0.0298, 0.0188, 0.0073, 0.0502, 0.2479, 0.2986, 0.5434, 0.4215, 0.3326, 0.1966, 0.1365, 0.0743, 0.0303, 0.0873, 0.2317, 0.3342, 0.3609, 0.4069, 0.3394, 0.1867, 0.1109, 0.0581, 0.0298, 0.0455, 0.1888, 0.4168, 0.5983, 0.5732, 0.4644, 0.3546, 0.2484, 0.1600, 0.0853, 0.0502, 0.1736, 0.4843, 0.7929, 0.7128, 0.7045, 0.4388, 0.3630, 0.1647, 0.0727, 0.0230, 0.1987, 0.7411, 0.9947, 0.9665, 0.8316, 0.5873, 0.2819, 0.1961, 0.1459, 0.0534, 0.0790, 0.2458, 0.4906, 0.5539, 0.5518, 0.5465, 0.3483, 0.3603, 0.1987, 0.1804, 0.0811, 0.0659, 0.1428, 0.4838, 0.8127 }; REAL Mean; REAL TrainError; REAL TrainErrorPredictingMean; REAL TestError; REAL TestErrorPredictingMean; FILE* f; void NormalizeSunspots() { INT Year; REAL Min, Max; Min = MAX_REAL; Max = MIN_REAL; for (Year=0; YearAlpha = 0.5; Net->Eta = 0.05; Net->Gain = 1; NormalizeSunspots(); TrainErrorPredictingMean = 0; for (Year=TRAIN_LWB; Year<=TRAIN_UPB; Year++) { for (i=0; iLayer = (LAYER**) calloc(NUM_LAYERS, sizeof(LAYER*)); for (l=0; lLayer[l] = (LAYER*) malloc(sizeof(LAYER)); Net->Layer[l]->Units = Units[l]; Net->Layer[l]->Output = (REAL*) calloc(Units[l]+1, sizeof(REAL)); Net->Layer[l]->Error = (REAL*) calloc(Units[l]+1, sizeof(REAL)); Net->Layer[l]->Weight = (REAL**) calloc(Units[l]+1, sizeof(REAL*)); Net->Layer[l]->WeightSave = (REAL**) calloc(Units[l]+1, sizeof(REAL*)); Net->Layer[l]->dWeight = (REAL**) calloc(Units[l]+1, sizeof(REAL*)); Net->Layer[l]->Output[0] = BIAS; if (l != 0) { for (i=1; i<=Units[l]; i++) { Net->Layer[l]->Weight[i] = (REAL*) calloc(Units[l-1]+1, sizeof(REAL)); Net->Layer[l]->WeightSave[i] = (REAL*) calloc(Units[l-1]+1, sizeof(REAL)); Net->Layer[l]->dWeight[i] = (REAL*) calloc(Units[l-1]+1, sizeof(REAL)); } } } Net->InputLayer = Net->Layer[0]; Net->OutputLayer = Net->Layer[NUM_LAYERS - 1]; Net->Alpha = 0.9; Net->Eta = 0.25; Net->Gain = 1; } void RandomWeights(NET* Net) { INT l,i,j; for (l=1; lLayer[l]->Units; i++) { for (j=0; j<=Net->Layer[l-1]->Units; j++) { Net->Layer[l]->Weight[i][j] = RandomEqualREAL(-0.5, 0.5); } } } } void SetInput(NET* Net, REAL* Input) { INT i; for (i=1; i<=Net->InputLayer->Units; i++) { Net->InputLayer->Output[i] = Input[i-1]; } } void GetOutput(NET* Net, REAL* Output) { INT i; for (i=1; i<=Net->OutputLayer->Units; i++) { Output[i-1] = Net->OutputLayer->Output[i]; } } /****************************************************************************** S U P P O R T F O R S T O P P E D T R A I N I N G ******************************************************************************/ void SaveWeights(NET* Net) { INT l,i,j; for (l=1; lLayer[l]->Units; i++) { for (j=0; j<=Net->Layer[l-1]->Units; j++) { Net->Layer[l]->WeightSave[i][j] = Net->Layer[l]->Weight[i][j]; } } } } void RestoreWeights(NET* Net) { INT l,i,j; for (l=1; lLayer[l]->Units; i++) { for (j=0; j<=Net->Layer[l-1]->Units; j++) { Net->Layer[l]->Weight[i][j] = Net->Layer[l]->WeightSave[i][j]; } } } } /****************************************************************************** P R O P A G A T I N G S I G N A L S ******************************************************************************/ void PropagateLayer(NET* Net, LAYER* Lower, LAYER* Upper) { INT i,j; REAL Sum; for (i=1; i<=Upper->Units; i++) { Sum = 0; for (j=0; j<=Lower->Units; j++) { Sum += Upper->Weight[i][j] * Lower->Output[j]; } Upper->Output[i] = 1 / (1 + exp(-Net->Gain * Sum)); } } void PropagateNet(NET* Net) { INT l; for (l=0; lLayer[l], Net->Layer[l+1]); } } /****************************************************************************** B A C K P R O P A G A T I N G E R R O R S ******************************************************************************/ void ComputeOutputError(NET* Net, REAL* Target) { INT i; REAL Out, Err; Net->Error = 0; for (i=1; i<=Net->OutputLayer->Units; i++) { Out = Net->OutputLayer->Output[i]; Err = Target[i-1]-Out; Net->OutputLayer->Error[i] = Net->Gain * Out * (1-Out) * Err; Net->Error += 0.5 * sqr(Err); } } void BackpropagateLayer(NET* Net, LAYER* Upper, LAYER* Lower) { INT i,j; REAL Out, Err; for (i=1; i<=Lower->Units; i++) { Out = Lower->Output[i]; Err = 0; for (j=1; j<=Upper->Units; j++) { Err += Upper->Weight[j][i] * Upper->Error[j]; } Lower->Error[i] = Net->Gain * Out * (1-Out) * Err; } } void BackpropagateNet(NET* Net) { INT l; for (l=NUM_LAYERS-1; l>1; l--) { BackpropagateLayer(Net, Net->Layer[l], Net->Layer[l-1]); } } void AdjustWeights(NET* Net) { INT l,i,j; REAL Out, Err, dWeight; for (l=1; lLayer[l]->Units; i++) { for (j=0; j<=Net->Layer[l-1]->Units; j++) { Out = Net->Layer[l-1]->Output[j]; Err = Net->Layer[l]->Error[i]; dWeight = Net->Layer[l]->dWeight[i][j]; Net->Layer[l]->Weight[i][j] += Net->Eta * Err * Out + Net->Alpha * dWeight; Net->Layer[l]->dWeight[i][j] = Net->Eta * Err * Out; } } } } /****************************************************************************** S I M U L A T I N G T H E N E T ******************************************************************************/ void SimulateNet(NET* Net, REAL* Input, REAL* Output, REAL* Target, BOOL Training) { SetInput(Net, Input); PropagateNet(Net); GetOutput(Net, Output); ComputeOutputError(Net, Target); if (Training) { BackpropagateNet(Net); AdjustWeights(Net); } } void TrainNet(NET* Net, INT Epochs) { INT Year, n; REAL Output[M]; for (n=0; nError; } TestError = 0; for (Year=TEST_LWB; Year<=TEST_UPB; Year++) { SimulateNet(Net, &(Sunspots[Year-N]), Output, &(Sunspots[Year]), FALSE); TestError += Net->Error; } fprintf(f, "\nNMSE is %0.3f on Training Set and %0.3f on Test Set", TrainError / TrainErrorPredictingMean, TestError / TestErrorPredictingMean); } void EvaluateNet(NET* Net) { INT Year; REAL Output [M]; REAL Output_[M]; fprintf(f, "\n\n\n"); fprintf(f, "Year Sunspots Open-Loop Prediction Closed-Loop Prediction\n"); fprintf(f, "\n"); for (Year=EVAL_LWB; Year<=EVAL_UPB; Year++) { SimulateNet(Net, &(Sunspots [Year-N]), Output, &(Sunspots [Year]), FALSE); SimulateNet(Net, &(Sunspots_[Year-N]), Output_, &(Sunspots_[Year]), FALSE); Sunspots_[Year] = Output_[0]; fprintf(f, "%d %0.3f %0.3f %0.3f\n", FIRST_YEAR + Year, Sunspots[Year], Output [0], Output_[0]); } } /****************************************************************************** M A I N ******************************************************************************/ void main() { NET Net; BOOL Stop; REAL MinTestError; InitializeRandoms(); GenerateNetwork(&Net); RandomWeights(&Net); InitializeApplication(&Net); Stop = FALSE; MinTestError = MAX_REAL; do { TrainNet(&Net, 10); TestNet(&Net); if (TestError < MinTestError) { fprintf(f, " - saving Weights ..."); MinTestError = TestError; SaveWeights(&Net); } else if (TestError > 1.2 * MinTestError) { fprintf(f, " - stopping Training and restoring Weights ..."); Stop = TRUE; RestoreWeights(&Net); } } while (NOT Stop); TestNet(&Net); EvaluateNet(&Net); FinalizeApplication(&Net); }