딥러닝 모델로 하는 일?
최적의 가중치 구하기
ㄴ그 과정에서 손실이 줄어드는지 테스트
신경망 모델 훈련
keras API 사용
손실 곡선
from tensorflow import keras
from sklearn.model_selection import train_test_split
(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()
train_scaled = train_input / 255.0
train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
def model_fn(a_layer=None):
model = keras.Sequential()
model.add(keras.layers.Flatten(input_shape=(28, 28)))
model.add(keras.layers.Dense(100, activation='relu'))
if a_layer:
model.add(a_layer)
model.add(keras.layers.Dense(10, activation='softmax'))
return model
model = model_fn()
model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
dense (Dense) (None, 100) 78500
dense_1 (Dense) (None, 10) 1010
=================================================================
Total params: 79510 (310.59 KB)
Trainable params: 79510 (310.59 KB)
Non-trainable params: 0 (0.00 Byte)
_________________________________________________________________
model.compile(loss='sparse_categorical_crossentropy', metrics='accuracy')
history = model.fit(train_scaled, train_target, epochs=5, verbose=0)
print(history.history.keys())
# dict_keys(['accuracy', 'loss'])
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()
plt.plot(history.history['accuracy'])
plt.xlabel('epoch')
plt.ylabel('accuracy')
plt.show()
model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=20, verbose=0)


model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=20, verbose=0)
plt.plot(history.history['loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.show()

검증 손실
model = model_fn()
model.compile(loss='sparse_categorical_crossentropy', metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target))
print(history.history.keys())
# dict_keys(['accuracy', 'loss', 'val_accuracy', 'val_loss'])
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
model = model_fn()
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target))
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()


드롭아웃
model = model_fn(keras.layers.Dropout(0.3))
model.summary()
Model: "sequential_6"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ flatten_6 (Flatten) │ (None, 784) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_12 (Dense) │ (None, 100) │ 78,500 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout) │ (None, 100) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_13 (Dense) │ (None, 10) │ 1,010 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 79,510 (310.59 KB)
Trainable params: 79,510 (310.59 KB)
Non-trainable params: 0 (0.00 B)
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target))
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()

모델 저장과 복원
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
history = model.fit(train_scaled, train_target, epochs=10, verbose=0,
validation_data=(val_scaled, val_target))
model.save_weights('model-weights.weights.h5')
model.save('model-whole.h5')
!ls -al *.h5
# -rw-r--r-- 1 root root 976600 Mar 24 00:44 model-weights.weights.h5
# -rw-r--r-- 1 root root 978584 Mar 24 00:45 model-whole.h5
model = model_fn(keras.layers.Dropout(0.3))
model.load_weights('model-weights.weights.h5')
import numpy as np
val_labels = np.argmax(model.predict(val_scaled), axis=-1)
print(np.mean(val_labels == val_target))
# 375/375 ━━━━━━━━━━━━━━━━━━━━ 1s 1ms/step
# 0.8780833333333333
model = keras.models.load_model('model-whole.h5')
model.evaluate(val_scaled, val_target)
# WARNING:absl:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
# 375/375 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8781 - loss: 0.3317
# [0.3317151963710785, 0.878083348274231]
콜백
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
save_best_only=True)
model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb])
model = keras.models.load_model('best-model.h5')
model.evaluate(val_scaled, val_target)
# WARNING:absl:Compiled the loaded model, but the compiled metrics have yet to be built. `model.compile_metrics` will be empty until you train or evaluate the model.
# 375/375 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8882 - loss: 0.3155
# [0.31552979350090027, 0.8881666660308838]
model = model_fn(keras.layers.Dropout(0.3))
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-model.h5',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
restore_best_weights=True)
history = model.fit(train_scaled, train_target, epochs=20, verbose=0,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
print(early_stopping_cb.stopped_epoch)
# 14
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
model.evaluate(val_scaled, val_target)
# 375/375 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step - accuracy: 0.8827 - loss: 0.3213
# [0.32125428318977356, 0.8826666474342346]

코랩 런타임 유형
무료로 쓸 것인가?
├── Yes → CPU (가벼운 작업) 또는 T4 GPU (딥러닝)
└── No → 작업 규모에 따라
├── 중형 모델 → L4 GPU
├── 대형 모델 → A100 GPU
└── 초대형 모델 → H100 GPU
TPU는 AI 계산만을 위한 칩(상황에 따라 사용)
ㄴ전용 코드가 필요하고, 수백억 개의 파라미터를 가진 초대형 모델을 빠르게 학습시키는 것이 목적
드롭아웃
은닝층에 있는 뉴런의 출력을 랜덤하게 꺼서 과대적합을 막는 기법
훈련 중에 적용되며 평가나 예측에서는 적용되지 않음
keras는 이를 자동으로 처리
콜백
케라스 모델을 훈련하는 도중에 어떤 작업을 수행할 수 있도록 도와주는 도구
대표적으로 최상의 모델을 자동으로 저장해 주거나 검증 점수가 더 이상 향상되지 않으면 일찍 종료할 수 있음
조기 종료
검증 점수가 더 이상 감소하지 않고 상승하여 과대적합이 일어나면 훈련을 계속 진행하지 않고 멈추는 기법
계산 비용과 시간 절약 거능
실습
딥러닝 버섯 이진분류
# 데이터 확인 (데이터프레임)
# 특성과 타겟 나누기
# 데이터 세트 나누기
# 모델 정의하기 (자유)
# 콜백 정의 (조기종료 & 모델 저장)
# 컴파일 및 학습
# 평가, 예측 하면서 마무리
import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.metrics import classification_report, confusion_matrix
from tensorflow import keras
# 1. 데이터 확인
# CSV 파일 불러오기
df = pd.read_csv('/content/drive/MyDrive/R2/MLDL/mushroom_cleaned.csv')
# 기본 정보 출력
print("=== 데이터 기본 정보 ===")
print(f"행 수: {df.shape[0]}, 열 수: {df.shape[1]}")
print()
df.info()
# 상위 5행 확인
print("\n=== 상위 5행 ===")
print(df.head())
# 타겟 클래스 분포 확인 (0: 독버섯, 1: 식용버섯)
print("\n=== 타겟 클래스 분포 ===")
print(df['class'].value_counts())
# 결측치 확인
print("\n=== 결측치 확인 ===")
print(df.isnull().sum())
# 2. 특성과 타겟 나누기
# 타겟(정답): class 열
y = df['class']
# 특성(입력): class 열을 제외한 나머지
X = df.drop('class', axis=1)
# 범주형 열 원-핫 인코딩
categorical_cols = ['cap-shape', 'gill-attachment', 'gill-color', 'stem-color']
X = pd.get_dummies(X, columns=categorical_cols, drop_first=True)
print(f"\n원-핫 인코딩 후 특성 수: {X.shape[1]}")
# 3. 데이터 세트 나누기 (훈련 60% / 검증 20% / 테스트 20%)
# 1차 분할: 훈련+검증(80%) / 테스트(20%)
X_train, X_test, y_train, y_test = train_test_split(
X, y, test_size=0.2, random_state=42, stratify=y
)
# 2차 분할: 훈련(60%) / 검증(20%)
X_train, X_val, y_train, y_val = train_test_split(
X_train, y_train, test_size=0.25, random_state=42, stratify=y_train
)
# 수치 정규화 (StandardScaler: 평균 0, 표준편차 1로 변환)
# 훈련 데이터로만 fit, 검증/테스트는 transform만 적용 (데이터 누수 방지)
scaler = StandardScaler()
X_train = scaler.fit_transform(X_train)
X_val = scaler.transform(X_val)
X_test = scaler.transform(X_test)
print(f"\n훈련 세트 크기: {X_train.shape}")
print(f"검증 세트 크기: {X_val.shape}")
print(f"테스트 세트 크기: {X_test.shape}")
# 4. 모델 정의
def build_model(input_dim):
model = keras.Sequential([
keras.Input(shape=(input_dim,)),
# 첫 번째 은닉층
keras.layers.Dense(256, activation='relu'),
keras.layers.BatchNormalization(), # 학습 안정화 (각 층의 입력을 정규화)
keras.layers.Dropout(0.3), # 30% 뉴런 랜덤 비활성화 (과적합 방지)
# 두 번째 은닉층
keras.layers.Dense(128, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.3),
# 세 번째 은닉층
keras.layers.Dense(64, activation='relu'),
keras.layers.BatchNormalization(),
keras.layers.Dropout(0.2),
# 출력층: 이진 분류이므로 sigmoid, 유닛 1개
keras.layers.Dense(1, activation='sigmoid')
])
return model
model = build_model(X_train.shape[1])
model.summary()
# 5. 콜백 정의 (조기종료 & 모델 저장)
# 모델 저장: 검증 손실이 가장 낮을 때만 저장
checkpoint_cb = keras.callbacks.ModelCheckpoint(
'best-mushroom-model.keras', # .keras 형식
save_best_only=True,
monitor='val_loss',
verbose=1
)
# 조기 종료: 검증 손실이 10 에포크 동안 개선되지 않으면 학습 중단
early_stopping_cb = keras.callbacks.EarlyStopping(
patience=10,
restore_best_weights=True, # 종료 시 가장 좋은 가중치로 복원
monitor='val_loss',
verbose=1
)
# 학습률 동적 감소: 검증 손실이 5 에포크 개선 없으면 학습률을 절반으로
reduce_lr_cb = keras.callbacks.ReduceLROnPlateau(
monitor='val_loss',
factor=0.5,
patience=5,
min_lr=1e-6,
verbose=1
)
callbacks = [checkpoint_cb, early_stopping_cb, reduce_lr_cb]
# 6. 컴파일 및 학습
model.compile(
optimizer=keras.optimizers.Adam(learning_rate=0.001),
loss='binary_crossentropy',
metrics=['accuracy']
)
history = model.fit(
X_train, y_train,
epochs=100,
batch_size=256, # 배치 크기 (한 번에 학습할 샘플 수)
validation_data=(X_val, y_val),
callbacks=callbacks,
verbose=1
)
# 7. 평가 및 예측
# 저장된 최적 모델 불러오기
best_model = keras.models.load_model('best-mushroom-model.keras')
# 테스트 세트 평가
loss, accuracy = best_model.evaluate(X_test, y_test, verbose=0)
print(f"\n=== 테스트 세트 평가 결과 ===")
print(f"손실(Loss): {loss:.4f}")
print(f"정확도(Accuracy): {accuracy:.4f}")
# 예측 확률 → 이진 클래스 변환
y_pred_proba = best_model.predict(X_test)
y_pred = (y_pred_proba > 0.5).astype(int).flatten()
# 상세 분류 리포트 출력
print("\n=== 분류 리포트 ===")
print(classification_report(y_test, y_pred, target_names=['독버섯(0)', '식용버섯(1)']))
# 혼동 행렬 출력
print("=== 혼동 행렬 ===")
print(confusion_matrix(y_test, y_pred))
=== 데이터 기본 정보 ===
행 수: 54035, 열 수: 9
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 54035 entries, 0 to 54034
Data columns (total 9 columns):
# Column Non-Null Count Dtype
--- ------ -------------- -----
0 cap-diameter 54035 non-null int64
1 cap-shape 54035 non-null int64
2 gill-attachment 54035 non-null int64
3 gill-color 54035 non-null int64
4 stem-height 54035 non-null float64
5 stem-width 54035 non-null int64
6 stem-color 54035 non-null int64
7 season 54035 non-null float64
8 class 54035 non-null int64
dtypes: float64(2), int64(7)
memory usage: 3.7 MB
=== 상위 5행 ===
cap-diameter cap-shape gill-attachment gill-color stem-height \
0 1372 2 2 10 3.807467
1 1461 2 2 10 3.807467
2 1371 2 2 10 3.612496
3 1261 6 2 10 3.787572
4 1305 6 2 10 3.711971
stem-width stem-color season class
0 1545 11 1.804273 1
1 1557 11 1.804273 1
2 1566 11 1.804273 1
3 1566 11 1.804273 1
4 1464 11 0.943195 1
=== 타겟 클래스 분포 ===
class
1 29675
0 24360
Name: count, dtype: int64
=== 결측치 확인 ===
cap-diameter 0
cap-shape 0
gill-attachment 0
gill-color 0
stem-height 0
stem-width 0
stem-color 0
season 0
class 0
dtype: int64
원-핫 인코딩 후 특성 수: 39
훈련 세트 크기: (32421, 39)
검증 세트 크기: (10807, 39)
테스트 세트 크기: (10807, 39)
Model: "sequential_14"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ dense_28 (Dense) │ (None, 256) │ 10,240 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization │ (None, 256) │ 1,024 │
│ (BatchNormalization) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_6 (Dropout) │ (None, 256) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_29 (Dense) │ (None, 128) │ 32,896 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_1 │ (None, 128) │ 512 │
│ (BatchNormalization) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_7 (Dropout) │ (None, 128) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_30 (Dense) │ (None, 64) │ 8,256 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ batch_normalization_2 │ (None, 64) │ 256 │
│ (BatchNormalization) │ │ │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_8 (Dropout) │ (None, 64) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_31 (Dense) │ (None, 1) │ 65 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 53,249 (208.00 KB)
Trainable params: 52,353 (204.50 KB)
Non-trainable params: 896 (3.50 KB)
Epoch 87: val_loss did not improve from 0.02374
127/127 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - accuracy: 0.9889 - loss: 0.0267 - val_accuracy: 0.9907 - val_loss: 0.0242 - learning_rate: 1.2500e-04
Epoch 88/100
123/127 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9892 - loss: 0.0272
Epoch 88: val_loss did not improve from 0.02374
127/127 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - accuracy: 0.9889 - loss: 0.0277 - val_accuracy: 0.9905 - val_loss: 0.0239 - learning_rate: 1.2500e-04
Epoch 89/100
123/127 ━━━━━━━━━━━━━━━━━━━━ 0s 8ms/step - accuracy: 0.9884 - loss: 0.0274
Epoch 89: val_loss did not improve from 0.02374
Epoch 89: ReduceLROnPlateau reducing learning rate to 6.25000029685907e-05.
127/127 ━━━━━━━━━━━━━━━━━━━━ 1s 10ms/step - accuracy: 0.9882 - loss: 0.0276 - val_accuracy: 0.9903 - val_loss: 0.0243 - learning_rate: 1.2500e-04
Epoch 89: early stopping
Restoring model weights from the end of the best epoch: 79.
=== 테스트 세트 평가 결과 ===
손실(Loss): 0.0245
정확도(Accuracy): 0.9909
338/338 ━━━━━━━━━━━━━━━━━━━━ 1s 2ms/step
=== 분류 리포트 ===
precision recall f1-score support
독버섯(0) 0.99 0.99 0.99 4872
식용버섯(1) 0.99 0.99 0.99 5935
accuracy 0.99 10807
macro avg 0.99 0.99 0.99 10807
weighted avg 0.99 0.99 0.99 10807
=== 혼동 행렬 ===
[[4817 55]
[ 43 5892]]
| 훈련 정확도 (Epoch 89) | 98.82% |
| 검증 정확도 (Epoch 89) | 99.03% |
| 테스트 정확도 | 99.09% |
3층 Dense 신경망(256→128→64)을 사용
배치 정규화와 드롭아웃으로 과적합을 방지
조기종료로 89 에포크에서 학습이 중단
최적 가중치는 79 에포크 시점으로 복원
테스트 정확도 99.09%를 달성
정답 코드
import pandas as pd
mushroom = pd.read_csv("/content/drive/MyDrive/R2/MLDL/mushroom_cleaned.csv")
# 상관관계 확인
import matplotlib.pyplot as plt
import seaborn as sns
sns.heatmap(mushroom.corr(), annot=True)
# 특성과 타겟 나누기
feature = mushroom.iloc[:, 0:8].to_numpy()
target = mushroom.iloc[:, 8].to_numpy()
# 데이터 나누기
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = \
train_test_split(feature, target, test_size=0.2, random_state=10)
# 모델 정의하기
from tensorflow import keras
model = keras.models.Sequential()
model.add(keras.layers.Dense(32, input_shape=(8,), activation="relu"))
model.add(keras.layers.Dense(64, activation="relu"))
model.add(keras.layers.Dense(1, activation="sigmoid"))
# 콜백 정의
es = keras.callbacks.EarlyStopping(patience=20, monitor='val_loss', restore_best_weights=True)
# 컴파일 및 학습
model.compile(optimizer="adam", metrics=["accuracy"], loss="binary_crossentropy")
model.fit(train_input, train_target, epochs=100, batch_size=64, validation_split=0.2, callbacks=[es])
# 평가
score = model.evaluate(test_input, test_target)
print(score[1])
0.8864624500274658

kaggle 커뮤니티에서 다른 사람들이 작업한 mushroom_cleaned 보기
https://www.kaggle.com/datasets/shabbirchinioti/mushroom-cleaned/code
mushroom_cleaned
www.kaggle.com
합성곱 신경망의 구성요소
합성곱
밀집층과 비슷하게 입력과 가중치를 곱하고 절편을 더하는 선형 계산
밀즙층과 달리 각 합성곱은 입력 전체가 아니라 일부만 사용하여 선형 계산을 수행
필터: 합성곱 층의 필터는 밀집층의 뉴런에 해당
ㄴ필터의 가중치와 절편을 종종 커널이라 부름, 주로 사용되는 커널 크기는 (3,3), (5,5), 커널 깊이 ==입력 깊이
특성 맵
합성곱 층이나 풀링 층의 출력 배열 의미
필터 하나가 하나의 특성 맵 만듦
패딩
합성곱 층의 입력 주위에 추가한 0으로 채워진 픽셀
밸리드 패딩: 패딩을 사용하지 않는 것
세임 패딩: 합성곱 층의 출력 크기를 입력과 동일하게 만들기 위해 입력에 패딩을 추가하는 것
스트라이드
합성곱 층에서 필터가 입력 위를 이동하는 크기
ㄴ일반적으로 1픽셀 사용
풀링
가중치 없고 특성 맵의 가로세로 크기 줄이는 역할 수행
대표적으로 최대 풀링과 평균 풀링, (2,2)풀링으로 입력을 절반으로 줄임






커널 직접 계산 X -> PyTorch나 TensorFlow 같은 라이브러리
합성곱 신경망을 사용한 이미지 분류
Conv2D
커널을 이미지 위에 Z자로 슬라이딩하면서 특징(선, 패턴 등)을 추출하는 층
filters는 커널 개수(= 출력 채널 수), kernel_size는 커널 크기, activation은 출력값 변환 방식을 지정
padding='same'을 주면 출력 크기가 입력과 같아지고, 기본값인 'valid'는 커널이 삐져나가지 않는 범위만 계산해서 출력이 작아짐
MaxPooling2D
지정한 영역 안에서 가장 큰 값 하나만 남기고 나머지를 버려서 이미지를 축소하는 층
크기를 줄이면서 계산량을 낮추고, 특징의 위치가 조금 달라져도 같은 값이 나오게 해서 위치 변화에 강해짐
pool_size=(2,2)가 기본값이며, 이 경우 가로·세로 각각 절반으로 줄어듦
plot_model
모델의 층 구성을 이미지 파일로 저장해주는 시각화 함수
show_shapes=True를 주면 각 층의 입출력 텐서 크기가 함께 표시되어 크기 변화를 한눈에 확인할 수 있음
Colab에서는 graphviz와 pydot 설치가 필요하며, 저장 없이 바로 보려면 SVG(plot_model(...))로 인라인 출력할 수 있음
패션 MNIST 데이터 불러오기
from tensorflow import keras
from sklearn.model_selection import train_test_split
(train_input, train_target), (test_input, test_target) = \
keras.datasets.fashion_mnist.load_data()
train_scaled = train_input.reshape(-1, 28, 28, 1) / 255.0
train_scaled, val_scaled, train_target, val_target = train_test_split(
train_scaled, train_target, test_size=0.2, random_state=42)
합성곱 신경망 만들기
model = keras.Sequential()
model.add(keras.layers.Conv2D(32, kernel_size=3, activation='relu',
padding='same', input_shape=(28,28,1)))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Conv2D(64, kernel_size=(3,3), activation='relu',
padding='same'))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(100, activation='relu'))
model.add(keras.layers.Dropout(0.4))
model.add(keras.layers.Dense(10, activation='softmax'))
model.summary()
Model: "sequential_16"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 28, 28, 32) │ 320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 14, 14, 32) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D) │ (None, 14, 14, 64) │ 18,496 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 7, 7, 64) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten_12 (Flatten) │ (None, 3136) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_35 (Dense) │ (None, 100) │ 313,700 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout_9 (Dropout) │ (None, 100) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_36 (Dense) │ (None, 10) │ 1,010 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 333,526 (1.27 MB)
Trainable params: 333,526 (1.27 MB)
Non-trainable params: 0 (0.00 B)
keras.utils.plot_model(model)
keras.utils.plot_model(model, show_shapes=True)


모델 컴파일과 훈련
model.compile(optimizer='adam', loss='sparse_categorical_crossentropy',
metrics=['accuracy'])
checkpoint_cb = keras.callbacks.ModelCheckpoint('best-cnn-model.h5',
save_best_only=True)
early_stopping_cb = keras.callbacks.EarlyStopping(patience=2,
restore_best_weights=True)
history = model.fit(train_scaled, train_target, epochs=20,
validation_data=(val_scaled, val_target),
callbacks=[checkpoint_cb, early_stopping_cb])
import matplotlib.pyplot as plt
plt.plot(history.history['loss'])
plt.plot(history.history['val_loss'])
plt.xlabel('epoch')
plt.ylabel('loss')
plt.legend(['train', 'val'])
plt.show()
model.evaluate(val_scaled, val_target)
# 375/375 [==============================] - 6s 16ms/step - loss: 0.2217 - accuracy: 0.9195
# [0.2217467874288559, 0.9194999933242798]
plt.imshow(val_scaled[0].reshape(28, 28), cmap='gray_r')
plt.show()


preds = model.predict(val_scaled[0:1])
print(preds)
plt.bar(range(1, 11), preds[0])
plt.xlabel('class')
plt.ylabel('prob.')
plt.show()
classes = ['티셔츠', '바지', '스웨터', '드레스', '코트',
'샌달', '셔츠', '스니커즈', '가방', '앵클 부츠']
import numpy as np
print(classes[np.argmax(preds)])
# 가방
test_scaled = test_input.reshape(-1, 28, 28, 1) / 255.0
model.evaluate(test_scaled, test_target)
# 313/313 ━━━━━━━━━━━━━━━━━━━━ 5s 17ms/step - accuracy: 0.9146 - loss: 0.2508
# [0.2508467733860016, 0.9146000146865845]

딥러닝 합성곱 신경망 이미지 분류 연습
# 1. 파일로부터 데이터 읽어들이기
# 2. 시각화를 통한 데이터 파악
# 3. 학습에 필요한 전처리와 세트 분할
# 4. 합성곱 신경망 만들기 : 가장 좋은 성과가 나올 방법을 고민해서 만들기
# 5. 컴파일 및 학습
# 6. 예측 잘되나 테스트 (predict)
# 7. 시각화를 통해 결과 확인하기(막대그래프 prob., class)
데이터 손가락 이미지 2,062장 (64×64 흑백, 10클래스), 클래스당 약 206장으로 균등. 훈련 60% / 검증 20% / 테스트 20%로 분할
Conv2D(32) -> Conv2D(32) -> MaxPooling 블록을 시작으로, 필터를 64, 128로 늘려가며 총 3개 블록 구성
각 Conv 뒤에 BatchNormalization 적용
마지막은 GlobalAveragePooling -> Dense(256) -> Dropout(0.4) -> Dense(10, Softmax) 순서
총 파라미터 약 363,000개
| 옵티마이저 | Adam (초기 lr=0.001) |
| 손실함수 | sparse_categorical_crossentropy |
| 배치 크기 | 32 |
| 최대 에포크 | 50 (실제 종료 47, best는 37) |
| EarlyStopping | patience=10 |
| ReduceLROnPlateau | patience=5, factor=0.5 |
결과
| 테스트 정확도 | 99.03% |
| 오분류 | 413개 중 4개 |
| 클래스별 F1-score | 전 클래스 0.98~1.00 |
정답 코드
import numpy as np
# 1. 파일로부터 데이터 읽어들이기
finger_data = np.load("/content/drive/MyDrive/R2/MLDL/finger_data.npy")
finger_target = np.load("/content/drive/MyDrive/R2/MLDL/finger_target.npy")
finger_target = np.argmax(finger_target, axis=1)
# 2. 시각화를 통한 데이터 파악
# print(np.unique(finger_target, return_counts=True))
import matplotlib.pyplot as plt
plt.imshow(finger_data[999], cmap="gray")
# print(finger_target[999])
# 3. 학습에 필요한 전처리와 세트 분할
finger_data = finger_data.reshape(-1, 64, 64, 1)
from sklearn.model_selection import train_test_split
train_input, test_input, train_target, test_target = \
train_test_split(finger_data, finger_target, test_size=0.2, random_state=42)
# 4. 합성곱 신경망 만들기
from tensorflow import keras
model = keras.models.Sequential()
model.add(keras.layers.Input(shape=(64, 64, 1)))
model.add(keras.layers.Conv2D(64, kernel_size=3, activation="relu", padding="same"))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Conv2D(64, kernel_size=3, activation="relu", padding="same"))
model.add(keras.layers.MaxPooling2D(2))
model.add(keras.layers.Flatten())
model.add(keras.layers.Dense(32, activation="relu"))
model.add(keras.layers.Dropout(0.3))
model.add(keras.layers.Dense(10, activation="softmax"))
model.summary()
# 5. 컴파일 및 학습
model.compile(optimizer="adam", loss="sparse_categorical_crossentropy", metrics=["accuracy"])
es = keras.callbacks.EarlyStopping(patience=5, restore_best_weights=True)
history = model.fit(train_input, train_target, epochs=100, batch_size=64, validation_split=0.2, callbacks=[es])
# 6. 예측 잘되나 테스트해보기
pred = model.predict(test_input[:5])
print("pred:", end=" ")
for p in pred :
print(np.argmax(p), end=" ")
print("\nreal:", test_target[:5])
# 7. 시각화를 통해 결과 확인하기
plt.plot(history.history["loss"], label="train")
plt.plot(history.history["val_loss"], label="val")
plt.xlabel("epoch")
plt.ylabel("score")
plt.legend()
plt.show()
Model: "sequential"
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━┓
┃ Layer (type) ┃ Output Shape ┃ Param # ┃
┡━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━┩
│ conv2d (Conv2D) │ (None, 64, 64, 64) │ 640 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d (MaxPooling2D) │ (None, 32, 32, 64) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ conv2d_1 (Conv2D) │ (None, 32, 32, 64) │ 36,928 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ max_pooling2d_1 (MaxPooling2D) │ (None, 16, 16, 64) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ flatten (Flatten) │ (None, 16384) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense (Dense) │ (None, 32) │ 524,320 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dropout (Dropout) │ (None, 32) │ 0 │
├─────────────────────────────────┼────────────────────────┼───────────────┤
│ dense_1 (Dense) │ (None, 10) │ 330 │
└─────────────────────────────────┴────────────────────────┴───────────────┘
Total params: 562,218 (2.14 MB)
Trainable params: 562,218 (2.14 MB)
Non-trainable params: 0 (0.00 B)
Epoch 1/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 10s 225ms/step - accuracy: 0.1016 - loss: 2.3138 - val_accuracy: 0.1091 - val_loss: 2.2996
Epoch 2/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 2s 16ms/step - accuracy: 0.1008 - loss: 2.2959 - val_accuracy: 0.1697 - val_loss: 2.2831
Epoch 3/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 18ms/step - accuracy: 0.1569 - loss: 2.2307 - val_accuracy: 0.2424 - val_loss: 2.1675
Epoch 4/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.1835 - loss: 2.0831 - val_accuracy: 0.2394 - val_loss: 1.9578
Epoch 5/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.2267 - loss: 1.9033 - val_accuracy: 0.3091 - val_loss: 1.7948
Epoch 6/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.2813 - loss: 1.7579 - val_accuracy: 0.3545 - val_loss: 1.5918
Epoch 7/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.3503 - loss: 1.6460 - val_accuracy: 0.5091 - val_loss: 1.4608
Epoch 8/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4215 - loss: 1.5685 - val_accuracy: 0.5636 - val_loss: 1.4147
Epoch 9/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4564 - loss: 1.5050 - val_accuracy: 0.6091 - val_loss: 1.3024
Epoch 10/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4701 - loss: 1.4298 - val_accuracy: 0.6061 - val_loss: 1.2106
Epoch 11/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.4920 - loss: 1.4108 - val_accuracy: 0.6576 - val_loss: 1.1648
Epoch 12/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5019 - loss: 1.3550 - val_accuracy: 0.6394 - val_loss: 1.1027
Epoch 13/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5193 - loss: 1.3322 - val_accuracy: 0.6485 - val_loss: 1.0723
Epoch 14/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5390 - loss: 1.2837 - val_accuracy: 0.6818 - val_loss: 1.0749
Epoch 15/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5292 - loss: 1.2839 - val_accuracy: 0.7303 - val_loss: 1.0078
Epoch 16/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.5413 - loss: 1.2784 - val_accuracy: 0.7212 - val_loss: 0.9634
Epoch 17/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5610 - loss: 1.2297 - val_accuracy: 0.7182 - val_loss: 0.9638
Epoch 18/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.5322 - loss: 1.2740 - val_accuracy: 0.7121 - val_loss: 0.9790
Epoch 19/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.5618 - loss: 1.2214 - val_accuracy: 0.7061 - val_loss: 0.9877
Epoch 20/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.5739 - loss: 1.2016 - val_accuracy: 0.7515 - val_loss: 0.9146
Epoch 21/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.5853 - loss: 1.1651 - val_accuracy: 0.7576 - val_loss: 0.8646
Epoch 22/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 18ms/step - accuracy: 0.5815 - loss: 1.1282 - val_accuracy: 0.7545 - val_loss: 0.8898
Epoch 23/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 18ms/step - accuracy: 0.5898 - loss: 1.1320 - val_accuracy: 0.7727 - val_loss: 0.8276
Epoch 24/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 18ms/step - accuracy: 0.5853 - loss: 1.0977 - val_accuracy: 0.7788 - val_loss: 0.8271
Epoch 25/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 19ms/step - accuracy: 0.6027 - loss: 1.0602 - val_accuracy: 0.7727 - val_loss: 0.7837
Epoch 26/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.5936 - loss: 1.0448 - val_accuracy: 0.7636 - val_loss: 0.7608
Epoch 27/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.6300 - loss: 0.9857 - val_accuracy: 0.7727 - val_loss: 0.7508
Epoch 28/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6293 - loss: 0.9659 - val_accuracy: 0.8030 - val_loss: 0.7150
Epoch 29/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6353 - loss: 0.9444 - val_accuracy: 0.8061 - val_loss: 0.7017
Epoch 30/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6209 - loss: 0.9538 - val_accuracy: 0.8182 - val_loss: 0.7007
Epoch 31/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 16ms/step - accuracy: 0.6247 - loss: 0.9514 - val_accuracy: 0.7970 - val_loss: 0.7062
Epoch 32/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6027 - loss: 0.9681 - val_accuracy: 0.8091 - val_loss: 0.7277
Epoch 33/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6346 - loss: 0.9485 - val_accuracy: 0.8242 - val_loss: 0.6894
Epoch 34/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 17ms/step - accuracy: 0.6444 - loss: 0.8893 - val_accuracy: 0.8121 - val_loss: 0.6427
Epoch 35/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6444 - loss: 0.8875 - val_accuracy: 0.8030 - val_loss: 0.6475
Epoch 36/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 17ms/step - accuracy: 0.6422 - loss: 0.8890 - val_accuracy: 0.7939 - val_loss: 0.6811
Epoch 37/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6558 - loss: 0.8792 - val_accuracy: 0.8273 - val_loss: 0.6316
Epoch 38/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6702 - loss: 0.8277 - val_accuracy: 0.8212 - val_loss: 0.6388
Epoch 39/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6611 - loss: 0.8491 - val_accuracy: 0.8061 - val_loss: 0.6170
Epoch 40/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6801 - loss: 0.8086 - val_accuracy: 0.8182 - val_loss: 0.6209
Epoch 41/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6543 - loss: 0.8639 - val_accuracy: 0.8091 - val_loss: 0.6053
Epoch 42/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6839 - loss: 0.8141 - val_accuracy: 0.8091 - val_loss: 0.6633
Epoch 43/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6482 - loss: 0.8521 - val_accuracy: 0.8303 - val_loss: 0.6013
Epoch 44/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6702 - loss: 0.8372 - val_accuracy: 0.8030 - val_loss: 0.6497
Epoch 45/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 20ms/step - accuracy: 0.6808 - loss: 0.8030 - val_accuracy: 0.8303 - val_loss: 0.5994
Epoch 46/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 16ms/step - accuracy: 0.7005 - loss: 0.7495 - val_accuracy: 0.8333 - val_loss: 0.5997
Epoch 47/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6755 - loss: 0.7891 - val_accuracy: 0.8303 - val_loss: 0.6008
Epoch 48/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6793 - loss: 0.7782 - val_accuracy: 0.8303 - val_loss: 0.6235
Epoch 49/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6778 - loss: 0.7682 - val_accuracy: 0.8242 - val_loss: 0.6131
Epoch 50/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6960 - loss: 0.7409 - val_accuracy: 0.8394 - val_loss: 0.5880
Epoch 51/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 16ms/step - accuracy: 0.6975 - loss: 0.7446 - val_accuracy: 0.8333 - val_loss: 0.5750
Epoch 52/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 15ms/step - accuracy: 0.7149 - loss: 0.7426 - val_accuracy: 0.8303 - val_loss: 0.5947
Epoch 53/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 0s 18ms/step - accuracy: 0.6710 - loss: 0.7729 - val_accuracy: 0.8333 - val_loss: 0.5987
Epoch 54/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 35ms/step - accuracy: 0.7134 - loss: 0.7393 - val_accuracy: 0.8424 - val_loss: 0.5566
Epoch 55/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 38ms/step - accuracy: 0.7149 - loss: 0.7044 - val_accuracy: 0.8394 - val_loss: 0.5750
Epoch 56/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 37ms/step - accuracy: 0.7354 - loss: 0.6621 - val_accuracy: 0.8515 - val_loss: 0.5371
Epoch 57/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 34ms/step - accuracy: 0.7066 - loss: 0.7221 - val_accuracy: 0.8545 - val_loss: 0.5693
Epoch 58/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 47ms/step - accuracy: 0.7005 - loss: 0.7233 - val_accuracy: 0.8000 - val_loss: 0.6545
Epoch 59/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 29ms/step - accuracy: 0.6937 - loss: 0.7388 - val_accuracy: 0.8333 - val_loss: 0.5941
Epoch 60/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 25ms/step - accuracy: 0.7180 - loss: 0.7010 - val_accuracy: 0.8303 - val_loss: 0.6003
Epoch 61/100
21/21 ━━━━━━━━━━━━━━━━━━━━ 1s 32ms/step - accuracy: 0.6801 - loss: 0.7569 - val_accuracy: 0.8424 - val_loss: 0.5964
1/1 ━━━━━━━━━━━━━━━━━━━━ 1s 510ms/step
pred: 6 9 3 9 0
real: [6 9 3 9 0]

'로보테크AI' 카테고리의 다른 글
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/03/23 (0) | 2026.03.23 |
|---|---|
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/03/20 (0) | 2026.03.20 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/03/19 (0) | 2026.03.19 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/03/18[ML, DL] (0) | 2026.03.18 |
| 융합_로보테크 AI 자율주행 로봇 개발자 과정-26/03/17 (0) | 2026.03.17 |