I get this error: Traceback (most recent call last):
  File "c:\Users\Dell-PC\Desktop\Projets\Experimental-Learning\main-.py", line 76, in <module>
    output_vector += time_vector[j] * weights_time_output[j]
ValueError: operands could not be broadcast together with shapes (38,) (10,) (38,)   When i run this code: import numpy as np
import matplotlib.pyplot as plt

# Define the neural network architecture
input_neurons = 100  
time_neurons = 40
output_neurons = 10

# Initialize the weights randomly as numpy arrays
weights_input_time = np.random.rand(input_neurons, time_neurons)
weights_time_output = np.random.rand(time_neurons, output_neurons)

# Initialize output_vector to match the number of output_neurons:
output_vector = np.zeros(output_neurons)

# Define the training data
sentences = ["I love Pizza !", "The earth is round.", "Winter is a brand old time.", "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z"]

# Define the training parameters
learning_rate = 0.1
epochs = 100

# Lists to store the loss and accuracy values for plotting
loss_values = []
accuracy_values = []

def one_hot_encode(word, word_to_index, vocabulary_size):
    word_vec = np.zeros(vocabulary_size)
    word_vec[word_to_index[word]] = 1
    return word_vec

def one_hot_decode(word_vec, index_to_word):
    max_index = np.argmax(word_vec)
    return index_to_word[max_index]

# Function to predict the next word
def predict_next_word(input_word, time_vector, weights_time_output, word_to_index, index_to_word):
    input_vector = one_hot_encode(input_word, word_to_index, vocabulary_size)
    
    # Calculate the output vector
    output_vector = np.zeros(vocabulary_size)
    for j in range(time_neurons):
        output_vector += time_vector[j] * weights_time_output[j]

    # Get the predicted next word
    predicted_word = one_hot_decode(output_vector, index_to_word)
    return predicted_word

# Create a list of all words
all_words = set(word for sentence in sentences for word in sentence.split())

# Create a word to index mapping and index to word mapping
word_to_index = {word: i for i, word in enumerate(all_words)}
index_to_word = {i: word for word, i in word_to_index.items()}
vocabulary_size = len(word_to_index)

# Training loop
for epoch in range(epochs):
    total_loss = 0
    correct_predictions = 0
    
    for sentence in sentences:
        words = sentence.split()
        time_vector = np.zeros(time_neurons)
        
        for i in range(len(words) - 1):
            input_vector = one_hot_encode(words[i], word_to_index, vocabulary_size)
            target_vector = one_hot_encode(words[i + 1], word_to_index, vocabulary_size)

            # Calculate the output vector
            output_vector = np.zeros(vocabulary_size)
            for j in range(time_neurons):
                # Update each element in time_vector
                time_vector[j] = time_vector[j] + 1
                # Perform dot product with weights_time_output[j]
                output_vector += time_vector[j] * weights_time_output[j]

            predicted_word = one_hot_decode(output_vector, index_to_word)
            if predicted_word == words[i+1]:
                correct_predictions += 1 
            
            loss = np.sum((output_vector - target_vector) ** 2)
            total_loss += loss

            # Weight update
            for j in range(time_neurons):
                # Update the j-th row of weights based on the gradient
                weights_time_output[j] -= learning_rate * (output_vector - target_vector) * time_vector[j]

        # Handle prediction for last word
        # Ensure time_vector and last_output are numpy arrays
        last_output = np.zeros(vocabulary_size)
        for j in range(time_neurons):
            # last_output should not use input_vector since it’s already one-hot encoded
            last_output += time_vector[j] * weights_time_output[j]

        # Final prediction and weight update
        final_target_vector = one_hot_encode(words[-1], word_to_index, vocabulary_size)
        loss = np.sum((last_output - final_target_vector) ** 2)
        total_loss += loss
        
        for j in range(time_neurons):
            weights_time_output[j] -= learning_rate * (last_output - final_target_vector) * time_vector[j]

    # Calculate overall metrics
    average_loss = total_loss / len(sentences)
    accuracy = correct_predictions / (len(sentences) * (len(words) - 1))
    
    # Store loss and accuracy
    loss_values.append(average_loss)
    accuracy_values.append(accuracy)
    
    print(f"Epoch {epoch+1}/{epochs} - Loss: {average_loss:.4f} - Accuracy: {accuracy:.4f}")

# Plot the results  
plt.figure(figsize=(10, 4))
plt.subplot(1, 2, 1)
plt.plot(loss_values)
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.subplot(1, 2, 2)
plt.plot(accuracy_values) 
plt.xlabel('Epoch')
plt.ylabel('Accuracy')
plt.tight_layout()
plt.show()

# Test the model with different input cases
test_cases = ["I", "love", "The", "Everything"]
predictions = []

# Iterate over each test case
for test_case in test_cases:
    # For simplicity, use a reset time vector for the beginning of each prediction
    time_vector = np.zeros(time_neurons)
    predicted_sequence = [test_case]
    
    # Simulate a sequence of predictions
    for _ in range(5):  # Adjust the range to generate longer or shorter sequences
        next_word = predict_next_word(predicted_sequence[-1], time_vector, weights_time_output, word_to_index, index_to_word)
        predicted_sequence.append(next_word)
        
    # Store the predictions
    predictions.append(predicted_sequence)

# Plot the predictions
plt.figure(figsize=(12, 4))

for i, predicted_sequence in enumerate(predictions):
    plt.subplot(1, len(predictions), i+1)
    plt.title(f"Starting with ‘{test_cases[i]}’")
    plt.plot(predicted_sequence, "o-")
    plt.ylim(-1, 1)  # Adjust y-limits to fit your data
    plt.xticks(rotation=45)
    plt.tight_layout()

plt.show()

def predict_next_word(input_word, time_vector, weights_time_output, word_to_index, index_to_word):
    # If the word is not in the dictionary, return random word from vocabulary
    if input_word not in word_to_index:
        return np.random.choice(list(index_to_word.values()))
    
    input_vector = one_hot_encode(input_word, word_to_index, vocabulary_size)
    
    # Calculate the output vector
    output_vector = np.zeros(vocabulary_size)
    for j in range(time_neurons):
        output_vector += time_vector[j] * weights_time_output[j]
    
    # Get the predicted next word
    predicted_word = one_hot_decode(output_vector, index_to_word)
    return predicted_word

# Start the chat loop
print("AI Chat Initialized. Start the conversation. Type 'quit' to exit.")
time_vector = np.zeros(time_neurons)

while True:
    user_input = input("You: ")
    if user_input.lower() == "quit":
        break
    user_words = user_input.split()
    
    # Predict the next word based on the last word of the user’s input
    last_word = user_words[-1]
    next_word = predict_next_word(last_word, time_vector, weights_time_output, word_to_index, index_to_word)
    
    print(f"AI: {next_word}")

# End of chat
print("AI Chat Ended.")