UNLV

[UNLV] - 딥러닝과 합성곱 신경망(8일차)

빡성 2026. 2. 14. 02:34

1. 수업 개요: 딥러닝의 기초

이번 수업은 딥러닝(Deep Learning)의 기초 개념과 실습을 다뤘다. 인공 신경망(ANN)의 작동 원리부터 시작해, 심층 신경망(DNN)과 합성곱 신경망(CNN)까지 이론과 실습을 함께 진행했다.

특히 오디오 분류 문제를 통해 DNN과 CNN의 차이점을 직접 경험했다. DNN은 MFCC 특성의 평균값을 사용하고, CNN은 시간 정보를 보존한 MFCC 시퀀스를 사용한다는 점이 핵심이었다.

실습에서는 TensorFlow와 Keras를 사용하여 모델을 구축하고, TFLite로 변환하여 임베디드 시스템에서 실행 가능한 형태로 만들었다.

2. 인공 신경망(ANN)의 작동 원리

인공 신경망은 생물학적 뉴런의 작동 방식을 모방한 계산 모델이다. 인공 뉴런은 여러 입력에 가중치를 적용하고, 편향과 함께 합산한 후 활성화 함수를 거쳐 출력을 생성한다.

뉴런 구조:

Σ = X₁w₁ + X₂w₂ + ... + Xₘwₘ + b = y

여기서:

  • X₁, X₂, ..., Xₘ: 입력 값들
  • w₁, w₂, ..., wₘ: 각 입력에 대한 가중치
  • b: 편향(Bias)
  • Σ: 합산 유닛(Summation unit)
  • y: 전달 함수(Transfer function)를 거친 최종 출력

활성화 함수: 가장 흔히 사용되는 것은 시그모이드(Sigmoid) 함수다. 시그모이드는 출력을 0과 1 사이의 값으로 변환하여 비선형성을 추가한다. 최근에는 ReLU(Rectified Linear Unit) 함수도 널리 사용된다.

활성화 함수가 없으면 신경망은 선형 변환만 수행할 수 있어 복잡한 패턴을 학습할 수 없다. 비선형 활성화 함수가 있어야 신경망이 복잡한 관계를 모델링할 수 있다.

3. ANN 아키텍처 설계 프로세스

신경망을 설계하고 훈련하는 과정은 다음과 같은 단계로 이루어진다:

  1. 입력을 받음: 데이터를 신경망에 입력한다.
  2. 편향(Bias) 추가: 필요시 편향을 추가한다.
  3. 입력 특징에 무작위 가중치 할당: 초기 가중치는 보통 [-1, 1] 사이의 작은 무작위 값으로 설정한다.
  4. 훈련 코드 실행: 모델을 학습시킨다.
  5. 예측 오류 확인: 모델의 예측과 실제 값 사이의 오차를 계산한다.
  6. 경사 하강법으로 가중치 업데이트: 오차를 최소화하는 방향으로 가중치를 조정한다.
  7. 업데이트된 가중치로 훈련 반복: 위 과정을 반복하여 모델 성능을 향상시킨다.
  8. 예측 수행: 학습이 완료된 모델로 새로운 데이터에 대한 예측을 수행한다.

설계 이슈:

  • 초기 가중치 설정: 보통 [-1, 1] 사이의 작은 무작위 값으로 초기화한다. 잘못된 초기화는 학습을 방해할 수 있다.
  • 활성화 함수 선택: 문제 유형에 따라 적절한 활성화 함수를 선택해야 한다.
  • 오차 추정 및 가중치 조정: 경사 하강법 최적화 알고리즘을 사용한다.
  • 뉴런의 수: 너무 적으면 학습 능력이 부족하고, 너무 많으면 과적합이 발생할 수 있다.
  • 데이터 표현 방식: 입력 데이터를 어떻게 전처리하고 표현할지 결정한다.
  • 훈련 세트의 크기: 충분한 데이터가 있어야 모델이 일반화할 수 있다.

4. 가중치 조정과 역전파

신경망 학습의 핵심은 가중치 조정이다. 목표는 반복할 때마다 오차(Error)를 최소화하도록 가중치를 조정하는 것이다.

역전파(Back propagation): 가중치를 업데이트하는 방향을 결정하는 알고리즘이다. 출력층에서 계산된 오차를 입력층 방향으로 역전파하여 각 가중치의 기여도를 계산한다.

역전파는 연쇄 법칙(Chain Rule)을 사용하여 각 가중치의 기여도를 계산한다. 이 과정을 반복하면서 모델이 점진적으로 정확도를 향상시킨다.

학습률(Learning Rate): 업데이트 시 한 번에 이동할 단계의 크기를 결정하는 튜닝 파라미터다. 학습률이 너무 크면 최적점을 지나쳐버리고, 너무 작으면 학습이 느리게 진행된다.

손실 함수(Loss Function)는 모델의 예측과 실제 값 사이의 차이를 측정한다. 경사 하강법을 통해 오차(Cost)를 최소화하는 지점으로 가중치가 업데이트된다.

5. TensorFlow & Keras

TensorFlow: 머신러닝의 복잡한 수학 연산을 수행하는 엔진이다. 수치 계산을 그래프(Graph)로 표현하는 것이 핵심 아이디어다.

TensorFlow에서 노드(Nodes)는 연산(Ops)을 나타내고, 엣지(Edges)는 데이터인 텐서(Tensors)의 흐름을 나타낸다. 예를 들어, f(x, y, z) = (x + y) * z 같은 수식을 그래프로 구현하여 계산한다.

Keras: TensorFlow 기반의 고수준 API로, 모델 구축과 빠른 프로토타이핑에 용이하다. 복잡한 TensorFlow 코드를 간단하게 작성할 수 있게 해준다.

TensorFlow Lite (TFLite): 리소스가 제한된 장치(모바일, IoT, 임베디드 시스템)를 위한 경량 버전이다. 특징은 다음과 같다:

  • 모델 변환 및 최적화
  • 온디바이스 추론
  • 저지연
  • 작은 점유 공간

Keras 훈련 옵션:

  • 옵티마이저(Optimizer):
    • sgd: 확률적 경사 하강법
    • adam: 적응형 학습률 알고리즘
  • 손실 함수(Loss):
    • binary_crossentropy: 이진 분류용
    • categorical_crossentropy: 다중 클래스 분류용
    • mean_squared_error: 회귀 문제용
  • 콜백(Callbacks): EarlyStopping(조기 종료), ModelCheckpoint(최적 모델 저장) 등

6. 심층 신경망 (DNN)

DNN 정의: 입력층과 출력층 사이에 여러 개의 은닉층(Hidden layers)이 있는 신경망이다.

일반 NN vs DNN:

  • 일반 NN: 층이 적음 → 단순한 패턴 학습
  • DNN: 층이 많음 → 복잡하고 계층적인 패턴 학습

DNN은 층이 깊어질수록 더 추상적이고 복잡한 특징을 학습할 수 있다. 첫 번째 은닉층은 저수준 특징을, 마지막 은닉층은 고수준 특징을 학습한다.

하지만 층이 너무 많으면 과적합(Overfitting)이 발생할 수 있으므로, 드롭아웃(Dropout) 같은 규제 기법이 필요하다. DNN에서 드롭아웃 비율은 보통 0.3~0.5를 권장한다.

7. 합성곱 신경망 (CNN)

CNN은 이미지나 시계열 데이터에서 공간적 또는 시간적 패턴을 학습하는 데 특화된 신경망이다.

완전 연결층의 문제: 이미지를 픽셀 단위로 일렬로 펼쳐 입력하면 파라미터가 폭증하고 공간 정보(Spatial information)가 손실된다. 예를 들어, 28×28 이미지를 784개의 입력으로 펼치면 인접 픽셀 간의 관계가 무너진다.

합성곱(Convolution)의 해결책: 이미지의 작은 패치(Patch)를 은닉층의 단일 뉴런에 연결하여 공간 구조를 보존한다. 필터(Filter/Kernel)가 이미지 위를 슬라이딩하며 연산하여 특징 맵(Feature map)을 생성한다.

합성곱 파라미터:

  • 필터 크기(f): 필터의 크기 (예: 3×3, 5×5)
  • 스트라이드(s): 필터가 이동하는 간격
  • 패딩(p): 이미지 주변에 추가하는 영역

레이어 스택: 합성곱, ReLU, 풀링(Pooling) 레이어를 차례로 배치한다. 층이 깊어질수록 필터는 더 크고 복잡한 특징을 배우고, 특징 맵의 크기는 작아진다.

배치 정규화(BatchNorm): 레이어의 출력을 정규화하여 훈련을 더 빠르고 안정적으로 만든다.

완전 연결층: 합성곱 층에서 추출된 고수준 특징들을 비선형적으로 조합하여 객체를 인식한다.

드롭아웃: CNN에서 드롭아웃 비율은 보통 0.1~0.3을 권장한다. DNN보다 낮은 이유는 합성곱 레이어 자체가 이미 규제 효과가 있기 때문이다.

소프트맥스(Softmax) 레이어: 출력층 직전에 위치하며, 입력 벡터를 확률 분포로 변환하여 반환한다. 모든 출력의 합은 1이며, 출력층과 동일한 수의 노드를 가져야 한다.

CNN 아키텍처 요약:

  1. 특징 학습(Feature Learning): INPUT → CONVOLUTION + RELU → POOLING 반복
  2. 분류(Classification): FLATTEN → FULLY CONNECTED → SOFTMAX → 최종 분류

8. 실습: 간단한 신경망 예제

가장 간단한 신경망 예제로 X(-1, 0, 1, 2, 3, 4)와 Y(-3, -1, 1, 3, 5, 7) 사이의 관계를 찾는 문제를 풀었다. 이는 선형 관계 Y = 2X - 1을 학습하는 문제다.

간단한 신경망 예제 (simple_NN_example.py)
import numpy as np
from tensorflow import keras

# 1. Define the model
model = keras.Sequential([keras.layers.Dense(units=1, input_shape=[1])])

# 2. Compile the model
model.compile(optimizer='sgd', loss='mean_squared_error')

# 3. Define the data
xs = np.array([-1.0, 0.0, 1.0, 2.0, 3.0, 4.0], dtype=float)
ys = np.array([-3.0, -1.0, 1.0, 3.0, 5.0, 7.0], dtype=float)

# 4. Train the model
model.fit(xs, ys, epochs=50)

# 5. Make a prediction
print(model.predict(np.array([10.0])))

이 예제는 단일 뉴런으로 구성된 가장 간단한 신경망이다. Dense 레이어 하나만 사용하여 선형 관계를 학습한다. 에포크가 진행될수록 손실(loss) 값이 감소하는 것을 확인할 수 있었다.

9. 실습: 오디오 분류를 위한 DNN

오디오 분류 문제를 DNN으로 해결하는 실습을 진행했다. 5가지 클래스(Clap, Cough, Footsteps, Glassbreak, Knock)를 분류하는 모델을 구축했다.

DNN의 특징: MFCC 특성을 추출한 후 시간에 대한 평균을 계산하여 1D 벡터로 만든다. 이렇게 하면 시간 정보가 손실되지만, 파라미터 수가 줄어들고 계산이 빠르다.

DNN 오디오 분류 (DNN_audio_classification.py)
import librosa
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Dense, Dropout

# MFCC 추출 후 평균 계산 (DNN용)
def extract_features_dnn(file_path, label):
    features = []
    labels = []
    
    y, sr = librosa.load(file_path, sr=22050)
    n_samples = int(0.5 * sr)  # 0.5초 세그먼트
    step = int(0.25 * sr)  # 50% 오버랩
    
    for start in range(0, len(y) - n_samples + 1, step):
        segment = y[start:start + n_samples]
        
        # MFCC 추출 (Shape: n_mfcc, time_steps)
        mfcc = librosa.feature.mfcc(y=segment, sr=sr, n_mfcc=40)
        
        # DNN: 시간에 대한 평균 (1D 벡터)
        mfcc_mean = np.mean(mfcc.T, axis=0)  # Shape: (40,)
        
        features.append(mfcc_mean)
        labels.append(label)
    
    return features, labels

# DNN 모델 구축
model = Sequential([
    Dense(256, activation='relu', input_shape=(40,)),
    Dropout(0.3),
    Dense(128, activation='relu'),
    Dropout(0.3),
    Dense(64, activation='relu'),
    Dense(5, activation='softmax')  # 5개 클래스
])

model.compile(optimizer='adam', 
              loss='categorical_crossentropy', 
              metrics=['accuracy'])

# 모델 학습
history = model.fit(X_train, y_train, 
                    epochs=50, 
                    batch_size=32, 
                    validation_data=(X_test, y_test))

# TFLite로 변환
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open('audio_dnn_model.tflite', 'wb') as f:
    f.write(tflite_model)

DNN 모델은 다음과 같은 구조를 가진다:

  • 입력층: 40차원 MFCC 평균 벡터
  • 은닉층 1: 256 뉴런, ReLU 활성화, Dropout 0.3
  • 은닉층 2: 128 뉴런, ReLU 활성화, Dropout 0.3
  • 은닉층 3: 64 뉴런, ReLU 활성화
  • 출력층: 5 뉴런, Softmax 활성화

학습 결과를 평가하기 위해 정확도, 정밀도, 재현율, F1-Score를 계산하고, 혼동 행렬(Confusion Matrix)을 시각화했다.

10. 실습: 오디오 분류를 위한 CNN

같은 오디오 분류 문제를 CNN으로 해결하는 실습을 진행했다. CNN은 시간 정보를 보존한 MFCC 시퀀스를 사용한다는 점이 DNN과 다르다.

CNN의 특징: MFCC 특성을 추출한 후 전체 시간 시퀀스를 그대로 사용한다. 이렇게 하면 시간적 패턴을 학습할 수 있다. 합성곱 층은 parameter sharing으로 파라미터 수는 FC에 비해 적게 유지되지만, 2D 시퀀스 전체에 대해 연산하므로 계산은 더 복잡해진다.

CNN 오디오 분류 (CNN_audio_classification.py)
import librosa
import numpy as np
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import Conv1D, MaxPooling1D, Flatten, Dense, Dropout

# MFCC 추출 후 시퀀스 유지 (CNN용)
def extract_features_cnn(file_path, label):
    features = []
    labels = []
    
    y, sr = librosa.load(file_path, sr=22050)
    n_samples = int(0.5 * sr)
    step = int(0.25 * sr)
    
    for start in range(0, len(y) - n_samples + 1, step):
        segment = y[start:start + n_samples]
        
        # MFCC 추출
        mfcc = librosa.feature.mfcc(y=segment, sr=sr, n_mfcc=40)
        
        # CNN: 시간 시퀀스 유지 (2D 배열)
        mfcc = mfcc.T  # Shape: (time_steps, 40)
        
        features.append(mfcc)
        labels.append(label)
    
    return features, labels

# 1D CNN 모델 구축
input_shape = (22, 40)  # (time_steps, n_mfcc)

model = Sequential([
    # 합성곱 레이어 1
    Conv1D(filters=32, kernel_size=3, activation='relu', input_shape=input_shape),
    MaxPooling1D(pool_size=2),
    Dropout(0.2),
    
    # 합성곱 레이어 2
    Conv1D(filters=64, kernel_size=3, activation='relu'),
    MaxPooling1D(pool_size=2),
    Dropout(0.2),
    
    # 완전 연결층
    Flatten(),
    Dense(64, activation='relu'),
    Dropout(0.3),
    Dense(5, activation='softmax')
])

model.compile(optimizer='adam', 
              loss='categorical_crossentropy', 
              metrics=['accuracy'])

# 모델 학습
history = model.fit(X_train, y_train, 
                    epochs=30, 
                    batch_size=32, 
                    validation_data=(X_test, y_test))

# TFLite로 변환
converter = tf.lite.TFLiteConverter.from_keras_model(model)
tflite_model = converter.convert()
with open('audio_cnn_model.tflite', 'wb') as f:
    f.write(tflite_model)

CNN 모델은 다음과 같은 구조를 가진다:

  • 입력층: (22, 40) 형태의 MFCC 시퀀스
  • 합성곱 레이어 1: 32개 필터, 커널 크기 3, MaxPooling, Dropout 0.2
  • 합성곱 레이어 2: 64개 필터, 커널 크기 3, MaxPooling, Dropout 0.2
  • Flatten: 2D 특징 맵을 1D로 변환
  • 완전 연결층: 64 뉴런, ReLU 활성화, Dropout 0.3
  • 출력층: 5 뉴런, Softmax 활성화

데이터의 시간적·공간적 국소 패턴이 중요한 문제에서는 CNN이 일반화에 더 유리한 경향을 보인다. 모델 크기와 계산 비용은 네트워크 설계(층 수, 필터 수, 완전 연결층 규모 등)에 따라 달라지며, 많은 CNN에서도 파라미터의 상당 부분은 마지막 완전 연결층이 차지한다.

11. 실습: USB 마이크를 이용한 오디오 녹음

Raspberry Pi Zero W2에 USB 마이크를 연결하여 오디오를 녹음하는 실습을 진행했다. 이는 오디오 분류 모델 학습을 위한 데이터 수집 과정이다.

USB 마이크 녹음 (rpi_USB_mic.py)
import sounddevice as sd
from scipy.io.wavfile import write
import numpy as np

# 설정
DURATION = 2  # 녹음 시간 (초)
SAMPLE_RATE = 22050  # 샘플링 레이트
OUTPUT_FILENAME = "rpi_mic_recording.wav"

def list_input_devices():
    """사용 가능한 입력 장치 목록 출력"""
    print("\n--- Available Audio Devices ---")
    devices = sd.query_devices()
    input_devices = []
    
    for i, device in enumerate(devices):
        if device['max_input_channels'] > 0:
            print(f"ID {i}: {device['name']}")
            input_devices.append(i)
    return input_devices

def record_from_device(device_id):
    print(f"\n녹음 준비: {DURATION}초, Device ID {device_id}...")
    
    try:
        # 녹음 시작
        audio_data = sd.rec(int(DURATION * SAMPLE_RATE),
                            samplerate=SAMPLE_RATE,
                            channels=1,
                            device=device_id,
                            dtype='int16')
        
        print("녹음 중... (지금 말하세요!)")
        sd.wait()  # 녹음 완료까지 대기
        print("완료.")
        
        # 파일로 저장
        write(OUTPUT_FILENAME, SAMPLE_RATE, audio_data)
        print(f"저장 완료: '{OUTPUT_FILENAME}'")
        
    except Exception as e:
        print(f"녹음 오류: {e}")

if __name__ == "__main__":
    # 1. 장치 목록 출력
    valid_ids = list_input_devices()
    
    # 2. 사용자가 USB 마이크 ID 선택
    print("\n위 목록에서 'USB Audio Device' 또는 유사한 이름을 찾으세요.")
    try:
        selected_id = int(input("USB 마이크의 ID 번호를 입력하세요: "))
        
        if selected_id in valid_ids:
            record_from_device(selected_id)
        else:
            record_from_device(selected_id)  # 폴백 체크
            
    except ValueError:
        print("잘못된 입력입니다. 숫자를 입력하세요.")

이 스크립트는 다음과 같은 기능을 제공한다:

  • 사용 가능한 오디오 입력 장치 목록 출력
  • 사용자가 USB 마이크를 선택
  • 지정된 시간 동안 오디오 녹음
  • WAV 파일로 저장

녹음된 오디오는 이후 MFCC 특성 추출 및 모델 학습에 사용된다.

12. 실습: TFLite 모델 추론 시스템

학습된 모델을 TFLite로 변환하고, 실제 오디오 파일에 대해 추론을 수행하는 시스템을 구축했다.

TFLite 추론 시스템 (CNN_classfication_inference_sys.py)
import numpy as np
import librosa
import tensorflow.lite as tflite

# 설정
MODEL_TYPE = 'dnn'  # 또는 'cnn'
MODEL_PATH = f"audio_{MODEL_TYPE}_model.tflite"
TEST_AUDIO_FILE = "test_audio.wav"

SAMPLE_RATE = 22050
DURATION = 0.5
OVERLAP = 0.25
N_MFCC = 40
CLASSES = ['Clap', 'Cough', 'Footsteps', 'Glassbreak', 'Knock']

def preprocess_dnn(segment, sr):
    """DNN 전처리: MFCC 평균 계산"""
    mfccs = librosa.feature.mfcc(y=segment, sr=sr, n_mfcc=N_MFCC)
    features = np.mean(mfccs.T, axis=0)
    return features.reshape(1, -1).astype(np.float32)

def preprocess_cnn(segment, sr, target_time_steps):
    """CNN 전처리: MFCC 시퀀스 유지"""
    mfccs = librosa.feature.mfcc(y=segment, sr=sr, n_mfcc=N_MFCC)
    features = mfccs.T  # (time_steps, n_mfcc)
    
    # 고정 길이로 패딩 또는 자르기
    if features.shape[0] < target_time_steps:
        pad_width = target_time_steps - features.shape[0]
        features = np.pad(features, ((0, pad_width), (0, 0)), mode='constant')
    else:
        features = features[:target_time_steps, :]
    
    return np.expand_dims(features, axis=0).astype(np.float32)

def run_inference(audio_path, model_path, model_type):
    # 1. 모델 로드
    interpreter = tflite.Interpreter(model_path=model_path)
    interpreter.allocate_tensors()
    
    input_details = interpreter.get_input_details()
    output_details = interpreter.get_output_details()
    
    input_index = input_details[0]['index']
    output_index = output_details[0]['index']
    
    # 2. 오디오 로드
    y, sr = librosa.load(audio_path, sr=SAMPLE_RATE)
    
    # 3. 슬라이딩 윈도우 추론
    n_samples = int(DURATION * sr)
    step = int((DURATION - OVERLAP) * sr)
    
    predictions = []
    
    for i in range(0, len(y) - n_samples + 1, step):
        segment = y[i: i + n_samples]
        
        # 전처리
        if model_type == 'dnn':
            input_data = preprocess_dnn(segment, sr)
        elif model_type == 'cnn':
            target_time_steps = input_details[0]['shape'][1]
            input_data = preprocess_cnn(segment, sr, target_time_steps)
        
        # 추론
        interpreter.set_tensor(input_index, input_data)
        interpreter.invoke()
        output_data = interpreter.get_tensor(output_index)
        
        # 결과 디코딩
        pred_idx = np.argmax(output_data[0])
        confidence = output_data[0][pred_idx]
        label = CLASSES[pred_idx]
        
        start_time = i / sr
        end_time = (i + n_samples) / sr
        
        predictions.append(label)
        print(f"Time {start_time:.2f}s - {end_time:.2f}s: {label:<10} (Conf: {confidence:.2f})")
    
    # 4. 최종 다수결 투표
    from collections import Counter
    most_common = Counter(predictions).most_common(1)
    print(f"\nFINAL PREDICTION: {most_common[0][0]}")

이 추론 시스템은 다음과 같은 기능을 제공한다:

  • TFLite 모델 로드 및 초기화
  • 오디오 파일을 세그먼트로 분할
  • 각 세그먼트에 대해 추론 수행
  • 다수결 투표로 최종 예측 결정

슬라이딩 윈도우 방식을 사용하여 긴 오디오 파일도 처리할 수 있다. 각 세그먼트의 예측 결과를 수집한 후, 가장 많이 예측된 클래스를 최종 결과로 선택한다.

13. 정리

이번 수업을 통해 딥러닝의 기초 개념부터 실전 적용까지 전 과정을 경험했다. 인공 신경망의 작동 원리를 이해하고, DNN과 CNN의 차이점을 오디오 분류 문제를 통해 직접 확인했다.

특히 중요한 것은 "DNN은 시간 정보를 평균내어 손실하지만 빠르고, CNN은 시간 정보를 보존하여 더 정확하지만 계산 비용이 높다"는 점이었다. 문제의 특성에 따라 적절한 모델을 선택하는 것이 중요하다.

TensorFlow와 Keras를 사용하여 모델을 구축하고, TFLite로 변환하여 임베디드 시스템에서 실행 가능한 형태로 만드는 과정을 배웠다. 이는 실제 IoT 프로젝트에서 매우 유용한 기술이다.

이 수업에서 다룬 내용은 앞으로 이미지 분류, 음성 인식, 센서 데이터 분석 등 다양한 딥러닝 프로젝트에 적용할 수 있다. 특히 CNN의 합성곱 연산과 풀링의 개념은 컴퓨터 비전 분야에서 필수적이다.