# ---
# jupyter:
#   jupytext:
#     text_representation:
#       extension: .py
#       format_name: light
#       format_version: '1.5'
#       jupytext_version: 1.14.4
#   kernelspec:
#     display_name: Python 3
#     language: python
#     name: python3
# ---

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns

#lets generate some points for the sine wave
#make sure to add +1 to the num(500) to get equally spaced values
np.linspace(0,50,501)

x = np.linspace(0,50,501)
y= np.sin(x)
y

#visualizing the sine wave
plt.plot(x,y)

sns.scatterplot(x,y)

# +
#convert the x and y values to a dataframe
# -

df = pd.DataFrame(y,columns=['sine'],index=x)
df

# +
#the data is put in a dataframe so that train and test data split can be made easily and can be used in the time series generator

# +
#train test split
#lets split 10% of the data for test
# -

len(df)

len(df)*0.1

# +
#lets create a general formula for future splits
# -

test_percent = 0.1

test_point = np.round(len(df)*test_percent)
test_point

test_index = int(len(df) - test_point)
test_index

# +
#convert the index to int so that we can use it in indexing else therell be an error
# -

train = df.iloc[:test_index]
test = df.iloc[test_index:]

train

# +
#normalising the data for both train and test(ie for both x input and y labels this time as we need time stamped data)
#fit for only train data and not test
# -

from sklearn.preprocessing import MinMaxScaler

scaler = MinMaxScaler()

scaled_train = scaler.fit_transform(train)
scaled_test = scaler.transform(test)

scaled_train

# +
#using timeseries generator to generate timestamped data
# -

from tensorflow.keras.preprocessing.sequence import TimeseriesGenerator

help(TimeseriesGenerator)

#define the length of the input to the generator and batch size
#data will contain the points we are trying to predict and the target will contain the timesteps
#here both the data and the timesteps is the same scaled_train as it contains both
length = 2
batch_size = 1
generator = TimeseriesGenerator(scaled_train,scaled_train,length=length,batch_size=batch_size)

generator

generator[0]

# +
#we can see that the first two points are the inputs and the third point is the y label its going to predict
# -

scaled_train

xs,ys = generator[0]

xs

ys

# +
#for our case where we are predicting sine wave we need a bigger input size(length) so that the model can recognise the pattern
# -

length = 50
batch_size = 1
generator = TimeseriesGenerator(scaled_train,scaled_train,length = length,batch_size = batch_size)

generator[0]

# +
#creating the model
# -

from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import SimpleRNN,Dense,LSTM

model = Sequential()

# +
#the input layer will be rnn layer with units = length of generator
#let the input shape be (length of input,nos of features)
model.add(SimpleRNN(50,input_shape=(50,1)))

#o/p layer of 1 neuron
model.add(Dense(1))

#the loss is mse for continuous values(regression) 
model.compile(optimizer='adam',loss='mse',metrics=['accuracy'])

# -

model.summary()

model.fit_generator(generator,epochs=5)

#the model trains for 401 steps as it has to predict the next values based on the input shape of 50 by considering the predicted values in the sequence
model.history.history

pd.DataFrame(model.history.history)

pd.DataFrame(model.history.history).plot()

# +
#evaluating the data
# -

scaled_train[:51]

scaled_train[-50:]

scaled_train[401:451]

#evaluating on the last 50 points in scaled_train 
first_batch = scaled_train[-length:]

first_batch

#reshape the i/p to (nos of batches,length,features)
first_batch=first_batch.reshape(1,length,1)

model.predict(first_batch)

scaled_test[0]

# +
#the model has evalluated by taking the last 50 points in the scaled_train data and predicted the next point which is the 
#first point in the scaled_test data

# +
#to predict the values into the future now we need to put this in a loop
#where the previous prediction is taken in input sequence for next prediction so that it follows a pattern
# -

test_predictions = []
first_eval_batch = scaled_train[-length:]
current_batch = first_eval_batch.reshape((1, length, 1))

current_batch

#we can drop the first value of the first and append the new predicted value into the sequence
#current batch indexing means that [grab all the first dimension values(nos i/p),except the 0th index grab everything else(length),grab all features]
#the appending value has to match the current batch array
np.append(current_batch[:,1:,:],[[[99]]],axis=1)



for i in range(len(test)):
    current_pred  = model.predict(current_batch)[0]
    
    test_predictions.append(current_pred)
    
    current_batch = np.append(current_batch[:,1:,:],[[current_pred]],axis=1)

test_predictions

# +
#note-here the test data is just used for comparison and not for direct training as we are using continuously updating
#the trained sequence with predicted values until the length of the test set is reached
# -

pd.DataFrame([test_predictions,scaled_test]).transpose()

#lets compare with the true test
test

# +
#the predicted values are scaled so perform inverse transform
# -

test_pred = scaler.inverse_transform(test_predictions)

test_pred

#ignore the warning as its just trying to inform us that we are changing the dataframe
test['predictions'] = test_pred

test

test.plot(figsize=(12,8))

# +
#pretty good performance on predicting the sine wave

# +
#Using LSTM , callbacks and validation data
# -

from tensorflow.keras.callbacks import EarlyStopping

early_stop = EarlyStopping(monitor='val_loss',patience=1)

# +
#to have validation generator the size of the scaled_test must be greater than the length else it wont work
# -

len(scaled_test)

length = 49
generator = TimeseriesGenerator(scaled_train,scaled_train,length = length,batch_size = 1)
validation_generator = TimeseriesGenerator(scaled_test,scaled_test,length=length,batch_size=1)

# +
# define model
model = Sequential()

# LSTM layer which has 4 gates and 2 memory features(long and short term)
model.add(LSTM(50,input_shape=(length, 1)))

# Final Prediction
model.add(Dense(1))

model.compile(optimizer='adam', loss='mse')
# -

model.fit_generator(generator,validation_data=validation_generator,epochs=10,callbacks=[early_stop])

# +
test_predictions = []

first_eval_batch = scaled_train[-length:]
current_batch = first_eval_batch.reshape((1, length, 1))

for i in range(len(test)):
    
    # get prediction 1 time stamp ahead ([0] is for grabbing just the number instead of [array])
    current_pred = model.predict(current_batch)[0]
    
    # store prediction
    test_predictions.append(current_pred) 
    
    # update batch to now include prediction and drop first value
    current_batch = np.append(current_batch[:,1:,:],[[current_pred]],axis=1)
# -

lstm_pred = scaler.inverse_transform(test_predictions)

test['lstm_predictions'] = lstm_pred

test

test.plot(figsize=(12,8))

# +
# Forecasting into the future!!

# +
#for forecasting the future values we need to use the full data (train+test) and we will have no values to compare it to
# -

df

full_data = scaler.fit_transform(df)

full_data

#set the generator
length = 50
full_generator = TimeseriesGenerator(full_data,full_data,length=length,batch_size=1)

# +
# define model
model = Sequential()

# LSTM layer which has 4 gates and 2 memory features(long and short term)
model.add(LSTM(50,input_shape=(length, 1)))

# Final Prediction
model.add(Dense(1))

model.compile(optimizer='adam', loss='mse')
# -

model.fit_generator(full_generator,epochs=5)

# +
#now for the full data the first_eval_batch will have points from 450-500 and the forecasts will start from 501 to 550(test size)
forecast = []

first_eval_batch = full_data[-length:]
current_batch = first_eval_batch.reshape((1, length, 1))

for i in range(len(test)):
    
    # get prediction 1 time stamp ahead ([0] is for grabbing just the number instead of [array])
    current_pred = model.predict(current_batch)[0]
    
    # store prediction
    forecast.append(current_pred) 
    
    # update batch to now include prediction and drop first value
    current_batch = np.append(current_batch[:,1:,:],[[current_pred]],axis=1)
# -

df.plot()

forecast

forecast_predictions = scaler.inverse_transform(forecast)

forecast_predictions

# +
#we only have the y data we need to generate the x points similar to the df
# -

df

#we generated 50 new points
50*0.1

# +
#we need points upto 55
# -

new_x  = np.arange(50.1,55.1,0.1)

new_x

fore_df = pd.DataFrame(forecast_predictions,index=new_x)

fore_df

fore_df.plot()

# +
#lets see the forecasted values with the original data
# -

plt.plot(df)
plt.plot(fore_df)

# +
#we can now see the forecasted sine wave which is pretty awesome!!
# -

model.save('ps_sine.h5')



StarcoderdataJupyterScriptsDedupFiltered