├── README.md ├── LICENSE.md ├── backward_euler.py └── system_identification_machine_learning.py /README.md: -------------------------------------------------------------------------------- 1 | # Machine Learning of Dynamical Systems Using Recurrent Neural Networks 2 | 3 | **IMPORTANT NOTE: First, thoroughly read the license in the file called LICENSE.md!** 4 | 5 | This project deals with learning to reproduce the input-output behavior of state-space models using recurrent neural networks and the Keras machine learning toolbox. 6 | 7 | - The file "system_identification_machine_learning.py" is the main file. You should start from here. 8 | - The file "backward euler.py" defines a function for discretizing the continuous-time system using the backward Euler method. It is called from the file "system_identification_machine_learning.py". The complete description of this project is given on my webpage: https://aleksandarhaber.github.io/ 9 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | LICENSE AND COPYRIGHT NOTICE 2 | 3 | This code repository and all listed files are the ownership of Dr. Aleksandar Haber. Dr. Aleksandar Haber is referred to as the Author in the sequel. All the files on this repository, including all the supporting files on this repository, are referred to as the material in the sequel. 4 | The Author's contact is ml.mecheng@gmail.com 5 | 6 | (1) You have the right to download, share, fork, and use the material for personal educational use without modifying, upgrading, or integrating the material into other projects. You cannot remix, transform, or build upon the material, meaning you can only share the original work without any adaptations. That is, you have the right to use the material solely in its original and unaltered form. 7 | 8 | (2) The material should not be used for commercial purposes without explicit approval of the Author and/or paying an appropriate fee. 9 | 10 | (3) The material should not be used by engineers working in companies as a tool for their work without the explicit approval of the Author and/or paying an appropriate fee. 11 | 12 | (4) The material should not be used by academic staff (paid researchers, paid grad students, paid post-docs, paid professors, etc.) working in universities or research institutions as a tool in their research or for teaching other people without explicit approval of the Author and/or paying an appropriate fee. 13 | 14 | (5) The material should not be used for producing results in academic papers, engineering reports, books, and similar material without explicit approval of the author and possibly without paying an appropriate fee. 15 | 16 | (6) The material or parts of the material should not be copied and published in other commercial or open-source projects. The material should not be used to build other programs without the explicit approval of the Author and without paying an appropriate fee. 17 | 18 | (7) You must give appropriate credit to the Author and reference the Author and material. The citation and the reference must contain the Author’s name, the link, the name of the repository, and the date of accessing the code. 19 | 20 | 21 | -------------------------------------------------------------------------------- /backward_euler.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Wed Dec 12 22:30:56 2018 4 | 5 | 6 | @author: Aleksandar Haber 7 | 8 | Simulation of the continuous-time state-space model using the backward Euler method. 9 | 10 | """ 11 | 12 | import numpy as np 13 | import matplotlib.pyplot as plt 14 | 15 | # define the continuous-time system matrices 16 | 17 | A=np.matrix([[0, 1],[- 0.1, -0.05]]) 18 | B=np.matrix([[0],[1]]) 19 | C=np.matrix([[1, 0]]) 20 | #define an initial state for simulation 21 | x0=np.random.rand(2,1) 22 | 23 | #define the number of time-samples used for the simulation and the sampling time for the discretization 24 | time=300 25 | sampling=0.5 26 | 27 | #define an input sequence for the simulation 28 | #input_seq=np.random.rand(time,1) 29 | input_seq=np.ones(time) 30 | #plt.plot(input_sequence) 31 | 32 | 33 | # the following function simulates the state-space model using the backward Euler method 34 | # the input parameters are: 35 | # -- A,B,C - continuous time system matrices 36 | # -- initial_state - the initial state of the system 37 | # -- time_steps - the total number of simulation time steps 38 | # -- sampling_perios - the sampling period for the backward Euler discretization 39 | # this function returns the state sequence and the output sequence 40 | # they are stored in the vectors Xd and Yd respectively 41 | def simulate(A,B,C,initial_state,input_sequence, time_steps,sampling_period): 42 | from numpy.linalg import inv 43 | I=np.identity(A.shape[0]) # this is an identity matrix 44 | Ad=inv(I-sampling_period*A) 45 | Bd=Ad*sampling_period*B 46 | Xd=np.zeros(shape=(A.shape[0],time_steps+1)) 47 | Yd=np.zeros(shape=(C.shape[0],time_steps+1)) 48 | 49 | for i in range(0,time_steps): 50 | if i==0: 51 | Xd[:,[i]]=initial_state 52 | Yd[:,[i]]=C*initial_state 53 | x=Ad*initial_state+Bd*input_sequence[i] 54 | else: 55 | Xd[:,[i]]=x 56 | Yd[:,[i]]=C*x 57 | x=Ad*x+Bd*input_sequence[i] 58 | Xd[:,[-1]]=x 59 | Yd[:,[-1]]=C*x 60 | return Xd, Yd 61 | 62 | state,output=simulate(A,B,C,x0,input_seq, time ,sampling) 63 | 64 | 65 | plt.plot(output[0,:]) 66 | plt.xlabel('Discrete time instant-k') 67 | plt.ylabel('Position- d') 68 | plt.title('System step response') 69 | plt.savefig('step_response.png') 70 | ############################################################################### 71 | # end of model definition and simulation 72 | ############################################################################### -------------------------------------------------------------------------------- /system_identification_machine_learning.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Tue Dec 4 23:43:32 2018 4 | 5 | @author: Aleksandar Haber 6 | 7 | Using Neural Networks to reproduce the input-output behavior of a state space model from the input-output data 8 | 9 | \dot{x}(t)=A*x(t)+Bu(t) 10 | y(t)=C*x(t) 11 | 12 | where A,B,C are the system matrices 13 | x(t) is the state at the time instant t 14 | y(t) is the output (our observations) 15 | u(t) is the external input 16 | 17 | The estimation is performed using the sequence of the input-output data {y(t),x(t)} 18 | 19 | """ 20 | # uncomment these two lines if you installed this package: 21 | # https://github.com/plaidml/plaidml 22 | # this package is used to perform GPU computations on almost any GPU... 23 | #import plaidml.keras 24 | #plaidml.keras.install_backend() 25 | 26 | import numpy as np 27 | import matplotlib.pyplot as plt 28 | 29 | ############################################################################### 30 | # Model definition 31 | ############################################################################### 32 | 33 | # First, we need to define the system matrices of the state-space model: 34 | # this is a continuous-time model, we will simulate it using the backward Euler method 35 | A=np.matrix([[0, 1],[- 0.1, -0.000001]]) 36 | B=np.matrix([[0],[1]]) 37 | C=np.matrix([[1, 0]]) 38 | 39 | #define the number of time samples used for simulation and the discretization step (sampling) 40 | time=200 41 | sampling=0.5 42 | 43 | ############################################################################### 44 | # Create the training data 45 | ############################################################################### 46 | #define an input sequence for the simulation 47 | input_seq_train=np.random.rand(time,1) 48 | #define an initial state for simulation 49 | x0_train=np.random.rand(2,1) 50 | 51 | 52 | # here we simulate the dynamics 53 | from backward_euler import simulate 54 | state,output_train=simulate(A,B,C,x0_train,input_seq_train, time ,sampling) 55 | 56 | output_train=output_train.T 57 | # this is the output data used for training 58 | output_train=np.reshape(output_train,(1,output_train.shape[0],1)) 59 | 60 | input_seq_train=np.reshape(input_seq_train,(input_seq_train.shape[0],1)) 61 | tmp_train=np.concatenate((input_seq_train, np.zeros(shape=(input_seq_train.shape[0],1))), axis=1) 62 | tmp_train=np.concatenate((x0_train.T,tmp_train), axis=0) 63 | # this is the input data used for training 64 | trainX=np.reshape(tmp_train, (1,tmp_train.shape[0],tmp_train.shape[1])) 65 | 66 | ############################################################################### 67 | # Create the validation data 68 | ############################################################################### 69 | # new random input sequence 70 | input_seq_validate=np.random.rand(time,1) 71 | # new random initial condition 72 | x0_validate=np.random.rand(2,1) 73 | 74 | # create a new ouput sequence by simulating the system 75 | state_validate,output_validate=simulate(A,B,C,x0_validate,input_seq_validate, time ,sampling) 76 | output_validate=output_validate.T 77 | # this is the output data used for validation 78 | output_validate=np.reshape(output_validate,(1,output_validate.shape[0],1)) 79 | 80 | input_seq_validate=np.reshape(input_seq_validate,(input_seq_validate.shape[0],1)) 81 | tmp_validate=np.concatenate((input_seq_validate, np.zeros(shape=(input_seq_validate.shape[0],1))), axis=1) 82 | tmp_validate=np.concatenate((x0_validate.T,tmp_validate), axis=0) 83 | # this is the input data used for validation 84 | validateX=np.reshape(tmp_validate, (1,tmp_validate.shape[0],tmp_validate.shape[1])) 85 | ############################################################################### 86 | # Create the test data 87 | ############################################################################### 88 | # new random input sequence 89 | input_seq_test=np.random.rand(time,1) 90 | # new random initial condition 91 | x0_test=np.random.rand(2,1) 92 | 93 | # create a new ouput sequence by simulating the system 94 | state_test,output_test=simulate(A,B,C,x0_test,input_seq_test, time ,sampling) 95 | output_test=output_test.T 96 | # this is the output data used for test 97 | output_test=np.reshape(output_test,(1,output_test.shape[0],1)) 98 | 99 | input_seq_test=np.reshape(input_seq_test,(input_seq_test.shape[0],1)) 100 | tmp_test=np.concatenate((input_seq_test, np.zeros(shape=(input_seq_test.shape[0],1))), axis=1) 101 | tmp_test=np.concatenate((x0_test.T,tmp_test), axis=0) 102 | # this is the input data used for test 103 | testX=np.reshape(tmp_test, (1,tmp_test.shape[0],tmp_test.shape[1])) 104 | ############################################################################### 105 | # Here we define the network 106 | ############################################################################### 107 | 108 | from keras.models import Sequential 109 | from keras.layers import Dense 110 | from keras.layers import GRU 111 | from keras.layers import LSTM 112 | from keras.layers import SimpleRNN 113 | from keras.optimizers import RMSprop 114 | from keras.layers import TimeDistributed 115 | from keras.callbacks import ModelCheckpoint 116 | 117 | model=Sequential() 118 | #model.add(SimpleRNN(32, input_shape=(trainX.shape[1],trainX.shape[2]),return_sequences=True)) 119 | #model.add(GRU(32, input_shape=(trainX.shape[1],trainX.shape[2]),return_sequences=True)) 120 | model.add(LSTM(32, input_shape=(trainX.shape[1],trainX.shape[2]),return_sequences=True)) 121 | #model.add(Dense(1)) 122 | model.add(TimeDistributed(Dense(1))) #there is no difference between this and model.add(Dense(1))... 123 | # does not make sense to use metrics=['acc'], see https://stackoverflow.com/questions/41819457/zero-accuracy-training-a-neural-network-in-keras 124 | model.compile(optimizer=RMSprop(), loss='mean_squared_error', metrics=['mse']) 125 | 126 | 127 | # after every epoch, we save the model, this is the absolute path on my C: drive, so the path is 128 | # C:\python_files\system_identification\models\ 129 | filepath="\\python_files\\system_identification\\models\\weights-{epoch:02d}-{val_loss:.6f}.hdf5" 130 | checkpoint = ModelCheckpoint(filepath, monitor='val_loss', verbose=0, save_best_only=False, save_weights_only=False, mode='auto', period=1) 131 | callbacks_list = [checkpoint] 132 | history=model.fit(trainX, output_train , epochs=2000, batch_size=1, callbacks=callbacks_list, validation_data=(validateX,output_validate), verbose=2) 133 | 134 | # load the model with the smallest validation loss 135 | #model.load_weights("weights-1997-1.878475.hdf5") 136 | 137 | # use the test data to predict the model response 138 | testPredict = model.predict(testX) 139 | 140 | ############################################################################### 141 | # Plot the predicted and "true" output and plot training and validation losses 142 | ############################################################################### 143 | 144 | # plot the predicted and the "true" (test) outputs 145 | time_plot=range(1,time+2) 146 | plt.figure() 147 | plt.plot(time_plot,testPredict[0,:,0], label='Real output') 148 | plt.plot(time_plot,output_test[0,:],'r', label='Predicted output') 149 | plt.xlabel('Discrete time steps') 150 | plt.ylabel('Output') 151 | plt.legend() 152 | plt.savefig('responseLSTM32.png') 153 | plt.show() 154 | 155 | loss=history.history['loss'] 156 | val_loss=history.history['val_loss'] 157 | epochs=range(1,len(loss)+1) 158 | plt.figure() 159 | plt.plot(epochs, loss,'b', label='Training loss') 160 | plt.plot(epochs, val_loss,'r', label='Validation loss') 161 | plt.title('Training and validation losses') 162 | plt.xlabel('Epochs') 163 | plt.ylabel('Loss') 164 | plt.xscale('log') 165 | #plt.yscale('log') 166 | plt.legend() 167 | plt.savefig('lossLSTM32.png') 168 | plt.show() 169 | 170 | 171 | ############################################################################### 172 | 173 | 174 | 175 | --------------------------------------------------------------------------------