Language modeling is a key task in natural language processing that involves predicting the next word in a sequence of words by capturing statistical patterns and dependencies of a given language and predicting the probability distribution of the next word in a sequence given previous inputted words. Sentence prediction is a particular application of language modeling that specifically focuses on the predicting aspect of generating predictable, yet coherent and meaningful sentences, sorting out mistaken guesses through error and training loss over time. This task has important practical applications for the future of artificial intelligence, neuroscience, and natural language processing such as in text completion and suggestion systems in chatbots, speech recognition, and sentiment analysis. 
Neural networks are a class of machine learning algorithms that are derived from the structure and function of the human brain and its behavior. They are capable of learning complex relationships between input and output data, and have been used for a variety of tasks that involve human-computer interaction. A recurrent neural network (RNN) is a type of neural network that is designed to process sequential data, containing loops that allow information to be passed from one part of the sequence to the next. A main feature of RNNs is their ability to maintain their own version of a memory, consisting of the previous inputs that have been processed and readily accessible for the prediction of future words in a given sentence. As a result, they have proven to be highly effective in language modeling tasks, due to their ability to capture word dependencies in a sequence to later predict what comes next.  In this research paper, we explore the use of RNNs for sentence prediction through the training of an RNN model on customized text data, which will be evaluated on its performance in predicting the next word in a sentence along with training loss, iterative execution, and general error. Our goal is to gauge the performance of how well our neural network is able to classify logical sentences and ultimately predict the given sentence in our provided text file. 
This paper is organized as follows. In the Methods section, we provide a detailed description of the dataset and the RNN used for setting up the prediction trials. In the Results section, we present the performance of the RNN and determine if it has done a sufficient job of accurately being accurately trained and able to predict certain text phrases. Finally, in the Discussion section, we discuss the implications of our results in the grand scheme of the field of natural language processing, the limitations of our study, and future directions for this research. 

Methods
Our code first defines a class called ‘DataReader’ in the ‘DataLoader.py’ file that reads text data from a file and provides a method for retrieving the data in batches. The class initializes the object with the path to the file and the desired length of the input sequences (‘seq_length’). In the ‘init’ function, we create a list of all unique characters in the data for sentence creation. Then, we create two dictionaries: one that maps a character to an index and an index to a character. Lastly, we store the size of the data and the number of unique characters in separate variables. 
The ‘next_batch’ method returns a tuple of input and target sequences, where each sequence is a list of integers representing the indices of the characters in the vocabulary. Specifically, the method determines the start and end indices of the input and target sequences based on the current value of the ‘pointer’ attribute. It then converts the characters in the input and target sequences to integer indices using the ‘char_to_ix’ dictionary, which maps each unique character in the data to an integer index. The ‘pointer’ attribute is then updated to point to the start of the next input sequence, and if it has reached the end of the data, it is reset to the beginning of the data. Finally, the method returns the input and target sequences as lists of integers.
The last function in DataReader is ‘just_started”, which returns a boolean indicating whether the ‘pointer’ attribute of the object is currently at the beginning of the data. 
	After we’ve parsed our data through the DataReader class, we set up our recurrent neural network in RecurrentNeuralNetwork.py. This code defines a RNN class and implements its forward and backward passes for training. The class constructor takes in the hyperparameters for the RNN: the size of the hidden layer (‘hidden_size’), the size of the vocabulary (‘vocab_size’), the sequence length (‘seq_length’), and the learning rate (‘learning_rate’). It also initializes the model parameters, including the weights and biases, using random values. The ‘softmax’ function takes in a vector ‘x’ and applies the softmax function to it to produce a probability distribution over its elements.
The ‘forward’ function takes in an input sequence ‘inputs’ and the previous hidden state ‘hprev’ and computes the forward pass of the RNN. It returns the one-hot encoded input sequence, the hidden states at each time step, the unnormalized log probabilities, and the normalized probabilities for the next character in the sequence.
The ‘backward’ function computes the gradients for all the parameters in the model, which are used to update the parameters during the optimization step. The function initializes the gradients to zero using ‘np.zeros_like’ for each of the parameters. Then, it iterates over the sequence in reverse order, and at each time step ‘t’, it computes the gradient of the loss function with respect to the output probabilities using the softmax function, and the target by subtracting one from the corresponding index of ‘ps[t]’. Next, it computes the gradients of the weights and biases by backpropagating the gradient. Finally, the gradients are clipped using ‘np.clip’ to prevent them from becoming too large and potentially causing numerical instability during the optimization process. The ‘loss’ function takes in the probabilities and the target output sequence and computes the cross-entropy loss for the sequence.
After initializing the network, we define methods to test and run the algorithm. The ‘update_model’ function updates the model parameters using the Adagrad optimization algorithm (Adaptive Gradient), which is an optimization algorithm that extends gradient descent. Next, the ‘sample’ function takes in the previous hidden state ‘h’, a seed index ‘seed_ix’, and the number of characters to generate ‘n’ to generate a new sequence of characters by repeatedly applying the RNN to the previous hidden state and the previously generated character.
The ‘train’ function takes in a training data set and trains the RNN using backpropagation through time. It returns the trained model and a list of losses at each iteration of the training process. The method keeps training the network until the loss falls below a specified threshold. In each iteration, it retrieves the next batch of input and target sequences from the ‘data_reader’ object, and feeds them through the RNN using the `forward` method to obtain the predicted outputs. Then, it computes the gradients of the loss function with respect to the network parameters using the ‘backward’ method, and updates the parameters using the ‘update_model’ method. The method also keeps track of the loss over time, using an exponentially weighted moving average of the loss values, and prints a sample of generated text and the current loss every 500 iterations.
Lastly, the ‘predict’ method generates text using the trained RNN. It uses the RNN to predict the next character in the sequence, by passing the input vector and the previous hidden state through the network using the ‘forward’ method, and sampling from the predicted output using the ‘np.random.choice’ function. This process is repeated for ‘n’ steps, and the generated text is returned as a string along with the corresponding iteration and loss values. The ‘plot_loss’ function plots the loss values from the prediction over time.

Results			
	To implement our RNN, we trained it on the first paragraph from “Notes from the Underground” by Fyodor Dostoyevsky. As opposed to a simple two-sentence input, this excerpt was a lot more challenging for the RNN to understand and predict. For example, when training the simple input text, only 5500 iterations of the RNN were needed to achieve a satisfactory output with a certain amount of accuracy. In addition, the two-sentence input resulted in a much smoother training loss over time. On the other hand, the excerpt from “Notes from the Underground” required 31500 iterations to finish training and running. Although the running complexity of an RNN is not long, the run time for the excerpt was significantly longer than the simple input. However, while the sentences created for the former input were more complex, both inputs resulted in accurate word pairings and grammar.

this is my research paper. given all the information that is provided above, please write a discussion/conclusion section that includes what we could do in the future to make the project better, implications of this field of study in terms of this research, and limitations of our research