<jupyter_start><jupyter_text>Utils<jupyter_code>def should_distribute():
    return dist.is_available() and WORLD_SIZE > 1


def is_distributed():
    return dist.is_available() and dist.is_initialized()<jupyter_output><empty_output><jupyter_text>DatasetOur time series is a sequence of numbers that we can represent in general mathematically as $$s_{0},s_{1},s_{2},...,s_{P}$$where $s_{p}$ is the numerical value of the time series at time period $p$ and where $P$ is the total length of the series.  In order to apply our RNN we treat the time series prediction problem as a regression problem, and so need to use a sliding window to construct a set of associated input/output pairs to regress on.  This process is animated in the gif below.<jupyter_code>def get_data(name, url=DATA_URL):
    df = pd.read_csv(url)
    df = df[df['Name'] == name]
    df['date'] = pd.to_datetime(df['date'])
    df = df.sort_values('date')
    targets = df[TARGET_COL].values
    features = df[FEATURE_COLS].values
    
    # First split the whole dataset into training set and test set
    train_size = int(0.8 * len(features))
    X_train, X_test, y_train, y_test = features[:train_size], \
                                       features[train_size:], \
                                       targets[:train_size], \
                                       targets[train_size:]
    
    # Fit the scaler ONLY to training set. Use it to transform both training set and test set
    feature_scaler = MinMaxScaler()
    target_scaler = MinMaxScaler()
    X_train = feature_scaler.fit_transform(X_train)
    X_test = feature_scaler.transform(X_test)
    y_train = target_scaler.fit_transform(y_train.reshape(-1, 1)).squeeze()
    y_test = target_scaler.transform(y_test.reshape(-1, 1)).squeeze()
    
    # Retern the scaler because after prediction, we want to inverse_transform
    # the prediction back to the original scale
    return X_train, X_test, y_train, y_test, target_scaler


def process_data(features, targets, lag):
    # Preprocess raw data to generate input features
    # For each target value to be predicted,
    # the input features are composed of features from X days before the data of the target value
    # where X (lag) is defined by the user
    X, Y = [], []
    lag = lag
    for i in range(len(features) - lag - 1):
        X.append(features[i: (i + lag)])
        Y.append(targets[(i + lag)])
    return np.array(X), np.array(Y)<jupyter_output><empty_output><jupyter_text>RNN modelThe skeleton of our model -> LSTM followed by a dense layer<jupyter_code>class RNN(nn.Module):
    def __init__(self, i_size, h_size, n_layers, o_size, dropout=0.1, bidirectional=True):
        super(RNN, self).__init__()
        self.num_directions = bidirectional + 1
        self.rnn = nn.LSTM(
            input_size=i_size,
            hidden_size=h_size,
            num_layers=n_layers,
            dropout=dropout,
            batch_first=True, # our input tensors will be [batch_size, time_steps(lagging_window_size), featuer_dim]
            bidirectional=bidirectional
        )
        self.out = nn.Linear(h_size, o_size)

    def forward(self, x, h_state):
        r_out, hidden_state = self.rnn(x, h_state)
        r_out = r_out[:, -1, :] # Get the last output
        outs = self.out(r_out)

        return outs, hidden_state<jupyter_output><empty_output><jupyter_text>Training function<jupyter_code>def train(X_train, y_train, model, device, optimizer, loss_fn, epoch):
    model.train()
    
    X_train = torch.from_numpy(X_train).float().to(device)
    y_train = torch.from_numpy(y_train).float().to(device)
    optimizer.zero_grad()
    output, _ = model(X_train, None)
    loss = loss_fn(output.squeeze(), y_train)
    loss.backward()
    optimizer.step()
    
    if epoch % 10 == 0:
        msg = "Train Epoch: {}\tloss={}".format(epoch, loss.item())
        logging.info(msg)
    
    return loss.item()<jupyter_output><empty_output><jupyter_text>Test function<jupyter_code>def test(X_test, y_test, model, device, loss_fn, epoch):
    model.eval()
    with torch.no_grad():
        X_test = torch.from_numpy(X_test).float().to(device)
        y_test = torch.from_numpy(y_test).float().to(device)
        output, _ = model(X_test, None)
        test_loss = loss_fn(output.squeeze(), y_test)
    
    y_test = y_test.detach().cpu().numpy().squeeze()
    outputs = output.detach().cpu().numpy().squeeze()
    MAPE = np.mean(np.abs(outputs - y_test) / np.abs(y_test)) # the lower the better
    corr_coef = np.corrcoef(y_test, outputs)[0, 1] # the closer to 1 the better

    if epoch % 10 == 0:
        logging.info("{{metricName: loss, metricValue: {}}}".format(test_loss))
        logging.info("{{metricName: MAPE, metricValue: {}}}".format(MAPE))
        logging.info("{{metricName: corr_coef, metricValue: {}}}\n".format(corr_coef))
    
    return test_loss.item()<jupyter_output><empty_output><jupyter_text>Main function<jupyter_code>def main():
    if dist.is_available():
        BACKEND = dist.Backend.GLOO

    # Use this format (%Y-%m-%dT%H:%M:%SZ) to record timestamp of the metrics.
    # If log_path is empty print log to StdOut, otherwise print log to the file.
    if LOG_PATH == "":
        logging.basicConfig(
            format="%(asctime)s %(levelname)-8s %(message)s",
            datefmt="%Y-%m-%dT%H:%M:%SZ",
            level=logging.INFO)
    else:
        logging.basicConfig(
            format="%(asctime)s %(levelname)-8s %(message)s",
            datefmt="%Y-%m-%dT%H:%M:%SZ",
            level=logging.INFO,
            filename=LOG_PATH)

    use_cuda = not NO_CUDA and torch.cuda.is_available()
    if use_cuda:
        print("Using CUDA")

    torch.manual_seed(SEED)

    device = torch.device("cuda" if use_cuda else "cpu")

    if should_distribute():
        print("Using distributed PyTorch with {} backend".format(BACKEND))
        dist.init_process_group(backend=BACKEND)

    kwargs = {"num_workers": 1, "pin_memory": True} if use_cuda else {}

    X_train, X_test, y_train, y_test, target_scaler = get_data(name=COMPANY_NAME)
    X_train, y_train = process_data(X_train, y_train, LAG)
    X_test, y_test = process_data(X_test, y_test, LAG)

    
    # The dimension of our input features is 5 ['open', 'high', 'low', 'close', 'volume']
    # We create a model with 3 hidden layers in the LSTM part
    # which is followed by a dense layer that produces 1d outputs
    model = RNN(i_size=5,
                h_size=64,
                n_layers=3,
                o_size=1,
                bidirectional=False,
                )
    model.to(device)

    if is_distributed():
        Distributor = nn.parallel.DistributedDataParallel
        model = Distributor(model)

    optimizer = optim.Adam(model.parameters(), lr=LR)
    loss_fn = nn.MSELoss()
    
    train_losses = []
    test_losses = []
    for epoch in range(1, EPOCHS + 1):
        train_losses.append(train(X_train, y_train, model, device, optimizer, loss_fn, epoch))
        test_losses.append(test(X_test, y_test, model, device, loss_fn, epoch))
    
    return train_losses, test_losses
train_losses, test_losses = main()
plt.plot(train_losses, label='train')
plt.plot(test_losses, label='test')
plt.legend()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.11920165175498967, 'fraction_numerical': 0.005092911218169305, 'mean_word_length': 3.0797304884896124, 'pattern_counts': {'":': 3, '<': 25, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '6', 'dir': 'jupyter-structured-clean-dedup', 'id': '70672', 'license': 'MIT', 'n_tokens_mistral': 2471, 'n_tokens_neox': 2397, 'n_words': 586, 'path': 'stock-rnn-prediction.ipynb', 'repo_name': 'yinanli617/Stock-price-prediction-with-RNN', 'size': '7265'}