Jak napisać perceptron aby poprawnie rozpoznawał cyfry od 0 do 9?

0

Moje zadanie polega na nauczeniu 10 perceptronów do rozpoznawania cyfr (0-9). Każdy perceptron powinien nauczyć się jednej cyfry. Jako dane treningowe stworzyłem 30 obrazków (5x7 bmp). Są 3 warianty dla każdej cyfry.

Mam klasę perceptronu:

import numpy as np


def unit_step_func(x):
    return np.where(x > 0, 1, 0)


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


class Perceptron:
    def __init__(self, learning_rate=0.01, n_iters=1000):
        self.lr = learning_rate
        self.n_iters = n_iters
        self.activation_func = unit_step_func
        self.weights = None
        self.bias = None
        #self.best_weights = None
        #self.best_bias = None
        #self.best_error = float('inf')

    def fit(self, X, y):
        n_samples, n_features = X.shape

        self.weights = np.zeros(n_features)
        self.bias = 0

        #self.best_weights = self.weights.copy()
        #self.best_bias = self.bias

        for _ in range(self.n_iters):
            for x_i, y_i in zip(X, y):
                linear_output = np.dot(x_i, self.weights) + self.bias
                y_predicted = self.activation_func(linear_output)

                update = self.lr * (y_i - y_predicted)
                self.weights += update * x_i
                self.bias += update

            #current_error = np.mean(np.abs(y - self.predict(X)))
            #if current_error < self.best_error:
            #    self.best_weights = self.weights.copy()
            #    self.best_bias = self.bias
            #    self.best_error = current_error

    def predict(self, X):
        linear_output = np.dot(X, self.weights) + self.bias
        y_predicted = self.activation_func(linear_output)
        return y_predicted

Próbowałem zarówno funkcji aktywacji unit_step_func, jak i sigmoid, oraz algorytmu kieszeniowego, aby sprawdzić, czy istnieje jakaś różnica. Jestem noobem, więc nie jestem pewien, czy to jest nawet poprawnie zaimplementowane.

Tak trenuję te perceptrony:

import numpy as np
from PIL import Image
from Perceptron import Perceptron
import os

def load_images_from_folder(folder, digit):
    images = []
    labels = []
    for filename in os.listdir(folder):
        img = Image.open(os.path.join(folder, filename))
        if img is not None:
            images.append(np.array(img).flatten())
            label = 1 if filename.startswith(f"{digit}_") else 0
            labels.append(label)
    return np.array(images), np.array(labels)


digits_to_recognize = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

perceptrons = []
for digit_to_recognize in digits_to_recognize:
    X, y = load_images_from_folder("data", digit_to_recognize)
    p = Perceptron()
    p.fit(X, y)
    perceptrons.append(p)

W skrócie: nazwa pliku danych treningowych ma format cyfra_wariant. Jak wspomniałem wcześniej, dla każdej cyfry są 3 warianty.

Funkcja load_images_from_folder wczytuje 30 obrazków i sprawdza nazwę. Jeśli część cyfra nazwy jest taka sama jak cyfra wejściowa, to dodaje 1 do etykiet, aby perceptron wiedział, że to pożądana cyfra.

Dla cyfry 0 tablica etykiet to [1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Dla cyfry 1 tablica etykiet to [0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

itd.

Następnie trenuję 10 perceptronów przy użyciu tych danych.

To ćwiczenie wymaga również stworzenia jakiegoś interfejsu graficznego, który pozwala mi rysować cyfrę. Wybrałem pygame, ale mogłem użyć pyQT, to faktycznie nie ma znaczenia.

Oto kod, możesz go pominąć, nie jest ważny (oprócz funkcji on_rec_button, ale do tego wrócimy):

import pygame
import sys

pygame.init()

cols, rows = 5, 7
square_size = 50
width, height = cols * square_size, (rows + 2) * square_size
screen = pygame.display.set_mode((width, height))
pygame.display.set_caption("Zad1")

rec_button_color = (0, 255, 0)
rec_button_rect = pygame.Rect(0, rows * square_size, width, square_size)

clear_button_color = (255, 255, 0)
clear_button_rect = pygame.Rect(0, (rows + 1) * square_size + 1, width, square_size)

mouse_pressed = False

drawing_matrix = np.zeros((rows, cols), dtype=int)


def color_square(x, y):
    col = x // square_size
    row = y // square_size

    if 0 <= row < rows and 0 <= col < cols:
        drawing_matrix[row, col] = 1


def draw_button(color, rect):
    pygame.draw.rect(screen, color, rect)


def on_rec_button():
    np_array_representation = drawing_matrix.flatten()

    for digit_to_recognize in digits_to_recognize:
        p = perceptrons[digit_to_recognize]
        predicted_number = p.predict(np_array_representation)
        if predicted_number == digit_to_recognize:
            print(f"Image has been recognized as number {digit_to_recognize}")


def on_clear_button():
    drawing_matrix.fill(0)


while True:
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            sys.exit()

        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 3:
            mouse_pressed = True

        elif event.type == pygame.MOUSEBUTTONUP and event.button == 3:
            mouse_pressed = False

        elif event.type == pygame.MOUSEMOTION:
            mouse_x, mouse_y = event.pos
            if mouse_pressed:
                color_square(mouse_x, mouse_y)

        elif event.type == pygame.MOUSEBUTTONDOWN and event.button == 1:
            if rec_button_rect.collidepoint(event.pos):
                on_rec_button()
            if clear_button_rect.collidepoint(event.pos):
                on_clear_button()

    for i in range(rows):
        for j in range(cols):
            if drawing_matrix[i, j] == 1:
                pygame.draw.rect(screen, (255, 0, 0), (j * square_size, i * square_size, square_size, square_size))
            else:
                pygame.draw.rect(screen, (0, 0, 0), (j * square_size, i * square_size, square_size, square_size))

    draw_button(rec_button_color, rec_button_rect)
    draw_button(clear_button_color, clear_button_rect)

    pygame.display.flip()

Teraz, gdy uruchomiłem aplikację, narysowałem cyfrę 3 i kliknąłem zielony przycisk uruchamiający funkcję on_rec_button, spodziewałem się zobaczyć "Obraz został rozpoznany jako cyfra 3", ale otrzymuję "Obraz został rozpoznany jako cyfra 0".

To jest to, co narysowałem:

screenshot-20231204113240.png

To są dane treningowe:
screenshot-20231204113415.png

screenshot-20231204113430.png

screenshot-20231204113442.png

Gdy rysuję cyfrę 1, otrzymuję 2 wyniki: "Obraz został rozpoznany jako cyfra 0" i "Obraz został rozpoznany jako cyfra 1".

screenshot-20231204113505.png

Co powinienem zrobić, aby działało tak, jak chcę? Nie oczekuję, że będzie to działać z dokładnością 100%, ale myślę, że może być lepiej.

1

@JanekProgramista69966996: 30 obrazków to według mnie zdecydowanie za mało jak na 35 wag do wytrenowania

1 użytkowników online, w tym zalogowanych: 0, gości: 1