import numpy as np
import tkinter as tk
import os

class NeuralNetwork:
    def __init__(self, input_size, learning_rate=0.7, weights_file='weights.npy'):
        self.weights_file = weights_file
        self.input_size = input_size + 1
        if os.path.exists(weights_file):
            self.weights = np.load(weights_file)
        else:
            self.weights = np.random.uniform(-0.3, 0.3, self.input_size)
        self.learning_rate = learning_rate

    def predict(self, inputs):
        inputs_bias = np.append(inputs, 1)
        return np.dot(inputs_bias, self.weights)

    def train(self, inputs, expected_output):
        inputs_bias = np.append(inputs, 1)
        prediction = self.predict(inputs)
        error = expected_output - prediction
        self.weights += self.learning_rate * error * inputs_bias
        np.save(self.weights_file, self.weights)

    def evaluate(self, inputs):
        return 1 if self.predict(inputs) > 0.5 else 0

class DrawingApp:
    def __init__(self, root):
        self.network = NeuralNetwork(100)
        self.expected_output = 0
        
        self.pixel_size = 20
        self.canvas_size = 10 * self.pixel_size
        
        self.canvas = tk.Canvas(root, width=self.canvas_size, height=self.canvas_size, bg="white")
        self.canvas.grid(row=0, column=0, columnspan=3)
        self.canvas.bind("<B1-Motion>", self.draw)

        self.btn_clear = tk.Button(root, text="Clear", command=self.clear_canvas)
        self.btn_clear.grid(row=1, column=0)

        self.btn_train = tk.Button(root, text="Train", command=self.train_network)
        self.btn_train.grid(row=1, column=1)

        self.btn_set_output_x = tk.Button(root, text="Set 'x'", command=lambda: self.set_expected_output(0))
        self.btn_set_output_x.grid(row=2, column=0)
        
        self.btn_set_output_y = tk.Button(root, text="Set 'y'", command=lambda: self.set_expected_output(1))
        self.btn_set_output_y.grid(row=2, column=1)

        self.btn_predict = tk.Button(root, text="Predict", command=self.predict)
        self.btn_predict.grid(row=1, column=2)

        self.lbl_result = tk.Label(root, text="Result: ")
        self.lbl_result.grid(row=2, column=2)

    def draw(self, event):
        x, y = event.x // self.pixel_size, event.y // self.pixel_size
        self.canvas.create_rectangle(x * self.pixel_size, y * self.pixel_size, 
                                     (x + 1) * self.pixel_size, (y + 1) * self.pixel_size, fill="black")

    def clear_canvas(self):
        self.canvas.delete("all")

    def get_image_data(self):
        image_data = np.zeros((10, 10))
        for x in range(10):
            for y in range(10):
                if self.canvas.find_overlapping(x * self.pixel_size, y * self.pixel_size, 
                                                (x + 1) * self.pixel_size, (y + 1) * self.pixel_size):
                    image_data[y, x] = 1
        return image_data.flatten()

    def train_network(self):
        image_data = self.get_image_data()
        self.network.train(image_data, self.expected_output)

    def set_expected_output(self, output):
        self.expected_output = output

    def predict(self):
        image_data = self.get_image_data()
        result = self.network.evaluate(image_data)
        self.lbl_result.config(text=f"Result: {'x' if result == 0 else 'y'}")

if __name__ == "__main__":
    root = tk.Tk()
    root.title("X and Y Recognition")
    app = DrawingApp(root)
    root.mainloop()

переписать чтобы была возможность сохранять нарисованные изображения с их точным значением выхода в тренировочную выборку, после чего обучить по алгоритму:

def train_single_neuron(images, labels):
    global W
    num_samples = len(images)
    while True:
        global_error = False
        for X, Y_true in zip(images, labels):
            # Шаг 1: Вычислить выход Y
            Y = np.dot(X, W) >= 0.5  # Простой порог для бинарного классификатора

            # Шаг 2: Проверить ошибку
            if Y != Y_true:
                # Вычислить ошибку
                delta = Y_true - Y
                # Шаг 3: Обновить веса
                W += learning_rate * delta * X
                global_error = True

        if not global_error:
            break