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 이상의 버전에서 하면 오류가 발생하지 않습니다.
[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]