수행기록퀘스트6

숫자 맞추기(Guess the number) 게임기
2022. 10. 9 (일) 18:28 최종수정 2022. 10. 9 (일) 21:20 rudals 조회 648 좋아요 2 스크랩 0 댓글 10

STM32 퀘스트 챌린지 - STM32Cube.AI

 

# 소개

이번 STM32Cube.AI 챌린지에서는 머신 러닝의 기본이라 생각되는 MNIST를 사용하여 ‘숫자 맞추기(Guess the number)’ 게임기를 제작해 보았습니다. MNIST(Modified National Institute of Standards and Technology database)는 숫자 0부터 9까지의 이미지로 구성된 손 글씨 데이터 셋인데 이것을 사용하여 사용자로부터 입력 받은 데이터를 B-L475E-IOT01A 보드가 추론한 결과값과 비교하여 맞춰가는 방식으로 동작됩니다

 

# 구성

대략적인 구성은 아래와 같습니다.

1. B-L475E-IOT01A 보드에 전원을 넣으면 랜덤하게 0~9중 하나의 숫자를 생성합니다.

2. 윈도우 PC에서 C# 프로그램을 사용하여 제작하였는데 사용자가 마우스로 숫자 0~9까지의 숫자 중 하나를 마우스로 그리면 입력된 이미지를 MNIST용 이미지로 변환 후 UART로 전송합니다.

3. 전송된 테스트 이미지는 STM32Cube.AI를 거쳐 추론된 결과값을 기존 값과 비교하여 높거나 낮은지를 판단한 결과 값을 OLED에 보여 줍니다.

 

# 구현

구현은 하드웨어적인 부분과 소프트웨어적인 부분으로 나눠 구현해 보았습니다.

 

하드웨어 구현

B-L475E-IOT01A 보드에서 게임 관련 정보를 표시하기 위해 아래와 같은 모듈을 만들어 보았습니다.

보드와 OLED가 I2C 인터페이스로 연결되기 때문에 단순히 4개의 라인만 연결시키면 되는 간단한 모듈입니다.

 

아래와 같은 식으로 연결하여 게임 정보 관련 내용을 디스플레이합니다.

 

소프트웨어 구현

- 사용자 입력 프로그램 제작

사용자로부터 숫자를 입력받기 위해 아래와 같은 프로그램을 C#을 사용하여 제작하였습니다.

Uart Log 영역은 B-L475E-IOT01A의 로그가 출력됩니다.

WebCam 영역은 웹캠으로 입력된 B-L475E-IOT01A의 현재 상태를 모니터링 하는 용도입니다.

Handwriting Board 영역은 사용자로부터 마우스로 그린 숫자를 입력받는 용도입니다.

 

사용자로가 마우스로 숫자를 그린 후 아래의 Send 버튼을 누르면 입력된 숫자 이미지를 MNIST에 필요한 이미지로 변환 후 uart를 통해 전송됩니다. 전송된 데이트는 STM32Cube.AI에 입력되고 추론된 결과를 OLED에 보여줍니다.

 

- 모델 생성 코드

아래 소스코드를 사용하여 모델을 생성하였습니다.

keras의 mnist.load_data로 MNIST 데이터셋을 얻은 후 CNN을 사용하였습니다.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from tensorflow import keras
from tensorflow.keras import layers
from sklearn import metrics

batch_size = 128
epochs = 15
num_classes = 10
input_shape = (28, 28, 1)


def prepare_data():
    (x_train, y_train), (x_test, y_test) = keras.datasets.mnist.load_data()

    x_train = x_train.astype("float32") / 255
    x_test = x_test.astype("float32") / 255

    x_train = np.expand_dims(x_train, -1)
    x_test = np.expand_dims(x_test, -1)

    print("x_train shape:", x_train.shape)
    print(x_train.shape[0], "train samples")
    print(x_test.shape[0], "test samples")

    y_train = keras.utils.to_categorical(y_train, num_classes)
    y_test = keras.utils.to_categorical(y_test, num_classes)

    return x_train, x_test, y_train, y_test


def build_model():
    model = keras.Sequential(
        [
            layers.Conv2D(32, kernel_size=3, activation="relu", padding='same', input_shape=input_shape),
            layers.MaxPooling2D(pool_size=2),
            layers.Conv2D(64, kernel_size=3, activation="relu", padding='same'),
            layers.MaxPooling2D(pool_size=2),
            layers.Flatten(),
            layers.Dropout(0.5),
            layers.Dense(10, activation="softmax"),
        ]
    )
    model.summary()
    model.compile(loss="sparse_categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
    return model


def train_model(model, x_train, x_test, y_train, y_test):
    model.compile(loss="categorical_crossentropy", optimizer="adam", metrics=["accuracy"])
    history = model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.3)

    test_loss, test_acc = model.evaluate(x_test, y_test)
    print('Test accuracy:', test_acc, " Test loss:", test_loss, "\n")
    model.save('model.h5')

    plt.plot(history.history['acc'])
    plt.plot(history.history['val_acc'])
    plt.title('Accuracy Trend')
    plt.ylabel('accuracy')
    plt.xlabel('epoch')
    plt.legend(['train', 'validation'], loc='best')
    plt.grid()
    plt.savefig('_accuracy_function.png')
    plt.show()

    plt.plot(history.history['loss'])
    plt.plot(history.history['val_loss'])
    plt.title('Loss Trend')
    plt.ylabel('loss')
    plt.xlabel('epoch')
    plt.legend(['train', 'validation'], loc='best')
    plt.grid()
    plt.savefig('_loss_function.png')
    plt.show()


def make_confusion_matrix(model, dataX, dataY):
    predictions = model.predict(dataX)

    predictedClassCode = np.argmax(predictions, axis=1)
    groundTruthClassCode = np.argmax(dataY, axis=1)

    cM = metrics.confusion_matrix(groundTruthClassCode, predictedClassCode)


    cmNormalized = np.around((cM / cM.sum(axis=1)[:, None]) * 100, 2)
    title = 'MNIST confusion matrix'

    fig, ax = plt.subplots(figsize=(7, 7))

    im = ax.imshow(cmNormalized, interpolation='nearest', cmap=plt.cm.Blues)

    plt.xlabel('Predicted Values')
    plt.ylabel('Ground Truth')
    plt.title(title)

    classes = [str(x) for x in range(0, 10)]
    plt.xticks(range(len(classes)), classes)
    plt.yticks(range(len(classes)), classes)
    plt.ylim(bottom=len(classes) - .5)
    plt.ylim(top=-0.5)

    width, height = cM.shape

    print('Accuracy for each class is given below.')
    for predicted in range(width):
        for real in range(height):
            if cmNormalized[predicted, real] > 45:
                color = 'white'
            else:
                color = 'black'
            if (predicted == real):
                print(classes[predicted].ljust(12) + ':', cmNormalized[predicted, real], '%')
            plt.gca().annotate('{}\n{}%'.format(cM[predicted, real], cmNormalized[predicted, real]),
                               xy=(real, predicted),
                               horizontalalignment='center', verticalalignment='center', color=color)
    divider = make_axes_locatable(plt.gca())
    cax = divider.append_axes("right", "5%", pad="3%")
    plt.colorbar(im, cax=cax)

    plt.tight_layout()

    plt.savefig('_confusion_matrix.png')
    plt.show()

x_train, x_test, y_train, y_test = prepare_data()
model = build_model()
train_model(model, x_train, x_test, y_train, y_test)
make_confusion_matrix(model, x_test, y_test)

 

Accuracy, Loss 그래프와 Confusion Matrix 차트를 출력해 보았습니다.

  

 

 

# 테스트 환경

아래와 같은 테스트 환경을 구축하여 테스트 하였습니다.

 

 

# 테스트 영상

첨부파일
Quest6_MNIST_Demo_rudals.zip 다운로드
자작공작소
2022.10.17 14:16
수상을 축하드립니다.^^
rudals
2022.10.17 14:19
감사합니다. 자작공작소님도 축하드립니다.
IRON
2022.10.14 19:59
감사합니다. MNIST 자료를 찾아보니 비슷한 내용이 있네요. "[텐서플로2] MNIST 데이터를 훈련 데이터로 사용한 DNN 학습"
rudals
2022.10.14 21:42
MNIST가 머신러닝계의 Hello World 정도의 내용이라 조금 검색해 보시면 다양한 내용을 쉽게 찾아 보실수 있을겁니다.
IRON
2022.10.12 19:00
많이 배웠습니다. Model과 학습 Data를 어떻게 만드셨는지 궁금하네요.
rudals
2022.10.13 14:22
prepare_data 메서드를 보면 keras.datasets.mnist.load_data() 를 호출하면 70000만개의 데이터를 로딩해 주며 train_model 메서드를 보면 model.save('model.h5') 부분에서 모델을 저장합니다. 그 이후는 이번 퀘스트에서 했던 방법과 동일하게 CubeMX를 통해 프로젝트를 생성 후 OLED를 추가하는 작업을 해 주었습니다.
칩헤드
2022.10.12 18:54
Rudals님의 참신한 작품 잘 감상하였습니다. 멋지십니다. ^^ MNIST는 정말 딥러닝계의 정석 (like 수학의 정석) 느낌입니다. UART외에 Bluetooth 인터페이스 버젼도 추후 기대합니다. Thums up. :)
rudals
2022.10.13 14:26
원래는 좀 더 범용적으로 사용하기 위해 WiFi 인터페이스를 적용하여 웹 브라우저에서 동작 테스트를 하던중 데스크탑에서는 괜찮은데 모바일에서는 선을 그리면 터치로 동작되어 웹페이지가 위/아래로 스크롤 동작이 되어 방법을 강구해 보다가... 차선책으로 UART로 전환하여 동작시켰습니다. ^^;;;
Tiel
2022.10.10 17:34
저도 그랬는데.. rudals님도 그러시군요.. 그래도 좋은 결과를 얻으신것 같아서 부럽습니다.. 저는 실험에 실패한채로 제출했는데 업로드도 이상하네요. -_-;
rudals
2022.10.09 20:29
게시글의 첨부파일을 새로 업데이트해도 맨 처음 올린 파일만 등록되고 그 후의 다시 올린 파일들은 등록이 안되는 문제로 문서 및 코드가 게시글과 다를수 있습니다.

로그인 후
참가 상태를 확인할 수 있습니다.