In [1]:
import random

def random_vector(minmax):
    return [row[0] + ((row[1] - row[0]) * random.random()) for row in minmax]

def initialize_weights(problem_size):
    return random_vector([[-0.5,0.5] for i in range(problem_size)])

def create_neuron(num_inputs):
    return {'weights' : initialize_weights(num_inputs)}

def propagate_was_change(neurons):
    i = random.randrange(len(neurons))
    activation = 0

    for j, other in enumerate(neurons):
        activation += other['weights'][i] * other['output'] if i != j else 0

    output = 1 if activation >= 0 else -1
    change = output != neurons[i]['output']
    neurons[i]['output'] = output
    return change

def flatten(nested):
    try:
        return [item for sublist in nested for item in sublist]
    except TypeError:
        return nested

def get_output(neurons, pattern, evals=100):
    vector = flatten(pattern)

    for i, neuron in enumerate(neurons):
        neuron['output'] = vector[i]

    for j in range(evals):
        propagate_was_change(neurons)

    return [neuron['output'] for neuron in neurons]

def train_network(neurons, patters):
    for i, neuron in enumerate(neurons):
        for j in range((i+1), len(neurons)):
            if i == j:
                continue

            wij = 0.0

            for pattern in patters:
                vector = flatten(pattern)
                wij += vector[i] * vector[j]

            neurons[i]['weights'][j] = wij
            neurons[j]['weights'][i] = wij

def to_binary(vector):
    return [0 if i == -1 else 1 for i in vector]

def print_patterns(provided, expected, actual):
    p, e, a = to_binary(provided), to_binary(expected), to_binary(actual)
    p1, p2, p3 = ', '.join(map(str, p[0:2])), ', '.join(map(str, p[3:5])), ', '.join(map(str, p[6:8]))
    e1, e2, e3 = ', '.join(map(str, e[0:2])), ', '.join(map(str, e[3:5])), ', '.join(map(str, e[6:8]))
    a1, a2, a3 = ', '.join(map(str, a[0:2])), ', '.join(map(str, a[3:5])), ', '.join(map(str, a[6:8]))
    print( "Provided\tExpected\tGot")
    print( "%s\t\t%s\t\t%s" % (p1, e1, a1))
    print( "%s\t\t%s\t\t%s" % (p2, e2, a2))
    print( "%s\t\t%s\t\t%s" % (p3, e3, a3))


def calculate_error(expected, actual):
    return sum([1 for i in range(len(expected)) if expected[i] != actual[i]])

def perturb_pattern(vector, num_errors=1):
    perturbed = [v for v in vector]
    indicies = [0 for i in range(random.randrange(len(perturbed)))]

    while len(indicies) < num_errors:
        index = random.randrange(len(perturbed))
        if not index in indicies:
            indicies.append(index)

    for i in indicies:
        perturbed[i] = -1 if perturbed[i] == 1 else 1

    return perturbed


def test_network(neurons, patterns):
    error = 0.0

    for pattern in patterns:
        vector = flatten(pattern)
        perturbed = perturb_pattern(vector)
        output = get_output(neurons, perturbed)
        error += calculate_error(vector, output)
        print_patterns(perturbed, vector, output)

    error = error / float(len(patterns))
    print("Final Result: avg pattern error=%s" % (error))

    return error

def execute(patters, num_inputs):
    neurons = [create_neuron(num_inputs) for i in range(num_inputs)]
    train_network(neurons, patters)
    test_network(neurons, patters)
    return neurons

if __name__ == "__main__":
    # problem configuration
    num_inputs = 9
    p1 = [[1,1,1],[-1,1,-1],[-1,1,-1]] # T
    p2 = [[1,-1,1],[1,-1,1],[1,1,1]] # U
    patters = [p1, p2]
    # execute the algorithm
    execute(patters, num_inputs)
Provided	Expected	Got
0, 1		1, 1		1, 1
0, 1		0, 1		0, 1
0, 1		0, 1		0, 1
Provided	Expected	Got
0, 0		1, 0		1, 0
1, 0		1, 0		1, 0
1, 1		1, 1		1, 1
Final Result: avg pattern error=0.0

In []: