Microsoft MVP성태의 닷넷 이야기
닷넷: 2247. C# - tensorflow 연동 (MNIST 예제) [링크 복사], [링크+제목 복사],
조회: 10914
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 1개 있습니다.)

C# - tensorflow 연동 (MNIST 예제)

요즘 접하기 쉬운 예제로 MNIST 손글씨 인식을 C#에서 tensorflow와 연동해 만들어 보겠습니다. 여기서 중요한 것은, Model을 구해야 하는 것인데요 ^^ 그 부분은 그냥 파이썬 환경에서 자유롭게 코딩해 구하기만 하면 됩니다.

예를 들어, 아래의 MNIST 예제는 my_mnist_model.keras 파일로 모델을 저장하고 있습니다.

// 케라스 창시자에게 배우는 딥러닝
// https://github.com/gilbutITbook/080315/blob/main/chapter02_mathematical-building-blocks.ipynb

import setuptools.dist
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(512, activation='relu'),
    layers.Dense(10, activation='softmax')
    ])

model.compile(optimizer='rmsprop', loss='sparse_categorical_crossentropy', metrics=['accuracy'])

train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype('float32') / 255

test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype('float32') / 255

model.fit(train_images, train_labels, epochs=5, batch_size=128)

test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f'{test_acc}')

# https://www.tensorflow.org/tutorials/keras/save_and_load?hl=ko
model.save('my_mnist_model.keras')

my_mnist_model.keras 파일의 크기는 3MB 정도 됩니다. 이렇게 구한 Model 파일은 C# 프로젝트에 추가/배포해, 실행 시 C#에서 Python.NET을 이용해 저 Model 파일을 로드해 활용할 것입니다.




자, 그럼 본격적으로 위에서 만든 MNIST 필기체 인식 Model을 C#에서 Python과 연동해 볼까요? ^^

이를 위해, 모델을 이용한 predict 코드를 호출하는 파이썬 코드를 다음과 같이 만들어 줍니다.

# mnist_predict.py

import setuptools.dist
import tensorflow as tf
import numpy as np

model = tf.keras.models.load_model('my_mnist_model.keras')

def predict(img):
    imgs = np.expand_dims(img, axis=0)
    predictions = model.predict(imgs, verbose=0)
    predict_number = np.argmax(predictions[0])
    return (predict_number.item(), predictions[0][predict_number].item())

위의 predict 함수는 model.predict 호출 시 해당 이미지로 판정되는 숫자와 그 확률을 반환합니다.

그럼, 이제 Python.NET을 이용한 C# 코드에서는 이를 호출하는 코드만 다음과 같이 작성해 주면 됩니다.

using Python.Runtime;

namespace ConsoleApp3;

internal class Program
{
    static void Main(string[] args)
    {
        Runtime.PythonDLL = @".\python\python312.dll";

        PythonEngine.Initialize();

        using (_ = Py.GIL())
        {
            DisableTensorflowLog();

            dynamic npModule = Py.Import("numpy");

            {
                dynamic sys = Py.Import("sys");
                string dirPath = Path.GetDirectoryName(typeof(Program).Assembly.Location) ?? Environment.CurrentDirectory;
                sys.path.append(dirPath);
            }

            float[]? testImgArray = // ... 28x28 크기의 이미지 데이터 ...;
            dynamic npArray = npModule.array(testImgArray);

            {
                var pyFile = Py.Import(Path.GetFileNameWithoutExtension("mnist_predict"));

                dynamic results = pyFile.InvokeMethod("predict", npArray);

                int expected = results[0];
                double percentage = results[1];

                Console.WriteLine($"{expected}: {percentage:P0}");
            }
        }

        PythonEngine.Shutdown();
    }
}

만약 testImgArray에 7과 비슷한 숫자의 이미지를 담고 있는 28x28 크기의 버퍼가 있다면 위의 프로그램을 실행 시 "7: 100%"와 유사한 출력이 나옵니다.

(첨부 파일은 이 글의 예제 코드를 포함합니다.)

만약 지난 글에 설명한 대로 CopyToOutputDirectory 설정을 했다면, 위의 예제를 실행했을 때 "C:\temp\ConsoleApp3\net8.0" 디렉터리에 출력이 모였을 것입니다. 해당 출력 파일만 다른 컴퓨터에 그대로 복사하면 (당연히 별도의 파이썬 설치 없이) 정상적으로 실행까지 됩니다.

한 가지 문제점이라면, 위의 경우 net8.0 출력에 있는 전체 바이너리의 크기가 (python + tensorflow까지 포함하므로) 1.6GB 정도, 압축하면 480MB 정도 됩니다. 만약 대상 컴퓨터에 파이썬 tensorflow 환경이 설치돼 있다면 이 용량을 없앨 수 있지만 그렇지 않은 경우라면... 뭔가 있어 보이는 ^^ 응용 프로그램의 크기를 자랑합니다.




참고로, 위의 코드를 Windows 10+ 환경에서 Python 3.12.0 버전으로 실행하면 load_model 시에 다음과 같은 오류가 발생합니다.

Traceback (most recent call last):
  File "C:\temp\ConsoleApp3\net8.0\python\test.py", line 36, in <module>
    model2 = tf.keras.models.load_model('my_mnist_model.keras')
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\temp\ConsoleApp3\net8.0\python\Lib\site-packages\keras\src\saving\saving_api.py", line 176, in load_model
    return saving_lib.load_model(
           ^^^^^^^^^^^^^^^^^^^^^^
  File "C:\temp\ConsoleApp3\net8.0\python\Lib\site-packages\keras\src\saving\saving_lib.py", line 152, in load_model
    return _load_model_from_fileobj(
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\temp\ConsoleApp3\net8.0\python\Lib\site-packages\keras\src\saving\saving_lib.py", line 207, in _load_model_from_fileobj
    _raise_loading_failure(error_msgs)
  File "C:\temp\ConsoleApp3\net8.0\python\Lib\site-packages\keras\src\saving\saving_lib.py", line 295, in _raise_loading_failure
    raise ValueError(msg)
ValueError: A total of 1 objects could not be loaded. Example error message for object <keras.src.optimizers.adam.Adam object at 0x000001CBB0BCFBF0>:

The shape of the target variable and the shape of the target value in `variable.assign(value)` must match. variable.shape=(10,), Received: value.shape=(512, 10). Target variable: <KerasVariable shape=(10,), dtype=float32, path=adam/dense_1_bias_momentum>

List of objects that could not be loaded:
[<keras.src.optimizers.adam.Adam object at 0x000001CBB0BCFBF0>]))

3.12.2 이상의 버전에서 하면 오류가 발생하지 않습니다.




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 4/24/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 




... 46  47  48  49  50  51  52  53  54  55  56  57  58  [59]  60  ...
NoWriterDateCnt.TitleFile(s)
12498정성태1/20/202117860.NET Framework: 1012. .NET Core Kestrel 호스팅 - 비주얼 스튜디오의 Kestrel/IIS Express 프로파일 설정
12497정성태1/20/202121762.NET Framework: 1011. C# - OWIN Web API 예제 프로젝트 [1]파일 다운로드2
12496정성태1/19/202118565.NET Framework: 1010. .NET Core 콘솔 프로젝트에서 Kestrel 호스팅 방법 [1]
12495정성태1/19/202120838웹: 40. IIS의 HTTP/2 지원 여부 - h2, h2c [1]
12494정성태1/19/202120181개발 환경 구성: 522. WSL 2 인스턴스와 호스트 측의 Hyper-V에 운영 중인 VM과 네트워크 연결을 하는 방법 [2]
12493정성태1/18/202118273.NET Framework: 1009. .NET 5에서의 네트워크 라이브러리 개선 (1) - HTTP 관련 [1]파일 다운로드1
12492정성태1/17/202117134오류 유형: 695. ASP.NET 0x80131620 Failed to bind to address
12491정성태1/16/202118685.NET Framework: 1008. 배열을 반환하는 C# COM 개체의 메서드를 C++에서 사용 시 메모리 누수 현상 [1]파일 다운로드1
12490정성태1/15/202117587.NET Framework: 1007. C# - foreach에서 열거 변수의 타입을 var로 쓰면 object로 추론하는 문제 [1]파일 다운로드1
12489정성태1/13/202120403.NET Framework: 1006. C# - DB에 저장한 텍스트의 (이모티콘을 비롯해) 유니코드 문자가 '?'로 보인다면? [1]
12488정성태1/13/202118901.NET Framework: 1005. C# - string 타입은 shallow copy일까요? deep copy일까요? [2]파일 다운로드1
12487정성태1/13/202117337.NET Framework: 1004. C# - GC Heap에 위치한 참조 개체의 주소를 알아내는 방법파일 다운로드1
12486정성태1/12/202119947.NET Framework: 1003. x64 환경에서 참조형의 기본 메모리 소비는 얼마나 될까요? [1]
12485정성태1/11/202119416Graphics: 38. C# - OpenCvSharp.VideoWriter에 BMP 파일을 1초씩 출력하는 예제 [2]파일 다운로드1
12484정성태1/9/202121694.NET Framework: 1002. C# - ReadOnlySequence<T> 소개파일 다운로드1
12483정성태1/8/202117723개발 환경 구성: 521. dotPeek - 훌륭한 역어셈블 소스 코드 생성 도구
12482정성태1/8/202119485.NET Framework: 1001. C# - 제네릭 타입/메서드에서 사용 시 경우에 따라 CS8377 컴파일 에러
12481정성태1/7/202119414.NET Framework: 1000. C# - CS8344 컴파일 에러: ref struct 타입의 사용 제한 메서드파일 다운로드1
12480정성태1/6/202122212.NET Framework: 999. C# - ArrayPool<T>와 MemoryPool<T> 소개파일 다운로드1
12479정성태1/6/202119486.NET Framework: 998. C# - OWIN 예제 프로젝트 만들기
12478정성태1/5/202122571.NET Framework: 997. C# - ArrayPool<T> 소개파일 다운로드1
12477정성태1/5/202123467기타: 79. github 코드 검색 방법 [1]
12476정성태1/5/202119143.NET Framework: 996. C# - 닷넷 코어에서 다른 스레드의 callstack을 구하는 방법파일 다운로드1
12475정성태1/5/202123633.NET Framework: 995. C# - Span<T>와 Memory<T> [1]파일 다운로드1
12474정성태1/4/202120054.NET Framework: 994. C# - (.NET Core 2.2부터 가능한) 프로세스 내부에서 CLR ETW 이벤트 수신 [1]파일 다운로드1
12473정성태1/4/202118941.NET Framework: 993. .NET 런타임에 따라 달라지는 정적 필드의 초기화 유무 [1]파일 다운로드1
... 46  47  48  49  50  51  52  53  54  55  56  57  58  [59]  60  ...