import numpy as np

np.random.seed(1013)

inputs = np.array([
    [0,0,1],
    [1,1,1],
    [1,0,1],
    [0,1,1]
])

outputs = [[0], [1], [1], [0]]

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def sigmoid_derivative(x):
    sx = sigmoid(x)
    return sx * (1-sx)

weights = 2 * np.random.random((3,1)) - 1


print("\n\nLet's start with a simple array of observations.\n\
Each observation is generated by 3 characteristics.\n\
I'll call them 'OB1', 'OB2', & 'OB3'.")
print("\n")
print("Here's 4 observations to start:")
print("\n")
print("Observation 1 - OB1:", inputs[0][0])
print("Observation 1 - OB2:", inputs[0][1])
print("Observation 1 - OB3:", inputs[0][2])
print("\n")
print("Observation 2 - OB1:", inputs[1][0])
print("Observation 2 - OB2:", inputs[1][1])
print("Observation 2 - OB3:", inputs[1][2])
print("\n")
print("Observation 3 - OB1:", inputs[2][0])
print("Observation 3 - OB2:", inputs[2][1])
print("Observation 3 - OB3:", inputs[2][2])
print("\n")
print("Observation 4 - OB1:", inputs[3][0])
print("Observation 4 - OB2:", inputs[3][1])
print("Observation 4 - OB3:", inputs[3][2])
print("\n")
print("So a simple perceptron will resolve to 4 responses.\n\
Our outputs are as follows:")
print("\n")
print("Output 1:", outputs[0][0])
print("Output 2:", outputs[1][0])
print("Output 3:", outputs[2][0])
print("Output 4:", outputs[3][0])

for iteration in range(100000):
    
    # Weighted sum of inputs / weights
    weighted_sum = np.dot(inputs, weights)
    print("Weighted sum:", weighted_sum[0], 
          weighted_sum[1], weighted_sum[2], 
          weighted_sum[3])
    # Activate!
    activated_output = sigmoid(weighted_sum)
    print("Activated output:", activated_output[0], 
          activated_output[1], activated_output[2], 
          activated_output[3])    
    # Cac error
    error = outputs - activated_output
    print("Errors:", error[0], error[1], error[2], 
          error[3])
        
    adjustments = error * sigmoid_derivative(weighted_sum)
    print("Adjustments to weight based on error:", 
          adjustments[0], adjustments[1], adjustments[2], 
          adjustments[3])    
    # Update the Weights
    weights += np.dot(inputs.T, adjustments)
    print("New weight:", 
          weights)       
    
print("Weights after training")
print(weights)

print("Output after training")
print(activated_output)
