# 베이스 이미지: 파이썬 3.11 슬림 버전
FROM python:3.11-slim
# 작업 디렉토리 설정
WORKDIR /app
# 의존성 설치
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# 소스 코드 복사
COPY . .
# 서버 실행 (main.py 파일의 app 객체를 8000 포트로 실행)
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
CMD 부분의 "main:app" 부분을 본인 프로젝트에 맞춰서 작성하면 된다.
[파이썬 파일명]:[FastAPI 앱 변수명] 형식으로 지정
2. Docker 이미지 빌드 및 Docker Hub에 푸시
먼저 로컬 PC의 프로젝트 폴더에서 터미널을 열고 아래 명령어를 입력한다.
마지막 '.'이 현재 디렉토리의 Dockerfile을 사용하라는 의미이므로 꼭 포함시켜줘야 한다.
docker build -t {DockerHubID}/{프로젝트명} .
빌드된 이미지는 현재 로컬 PC에만 존재하므로 중앙 이미지 저장소인 Docker Hub에 올려준다.
docker login
docker push {DockerHubID}/{프로젝트명}
+ Mac OS라면 아래 트러블 슈팅으로 넘어가주세여
3. AWS EC2 준비 및 배포
AWS에서 Ubuntu를 사용하는 EC2 인스턴스를 하나 생성하고,
보안 그룹 설정을 통해 SSH(22번)와 HTTP(80번) 포트 등등을 열어주었다.
로컬 터미널에서 pem 키 통해서 이 서버에 접속한다.
ssh -i "키페어이름.pem" ubuntu@<EC2-퍼블릭-DNS-주소>
Docker를 설치하고
Docker Hub에 올려두었던 프로젝트 이미지를 가져온다.
docker pull {DockerHubID}/{프로젝트명}
마지막으로, 내려받은 이미지를 컨테이너로 실행하면 끝 !!!
# -d: 컨테이너를 백그라운드에서 계속 실행 (detached mode)
# -p 80:8000: EC2 서버의 80번 포트로 들어온 요청을 컨테이너의 8000번 포트로 전달
docker run -d -p 80:8000 --name my-ai-app {DockerHubID}/{프로젝트명}
🚨트러블 슈팅
1. 의존성 충돌
처음에 우분투의 패키지 매니저인 apt를 이용하여 도커를 설치하였더니 의존성 충돌 에러가 발생했다.
# 시도했던 명령어
sudo apt-get install docker.io
에러 메시지:The following packages have unmet dependencies: containerd.io : Conflicts: containerd
우분투의containerd와 도커의containerd.io가 같은 시스템 자원을 두고 충돌을 일으킨 것이었다.
해결 방법 : 기존 패키지 완전 삭제 -> 공식 스크립트로 설치
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
2. no matching manifest아키텍처 불일치
에러 메시지:no matching manifest for linux/amd64 in the manifest list entries
원인은 CPU 아키텍처의 불일치였다.
개발 환경인 내 로컬 컴퓨터는 M3 Mac이므로 arm64 아키텍처 기반이지만,
실행 환경인 AWS EC2 인스턴스는 amd64 아키텍처 기반의 CPU를 사용한다.
해결 방법: Docker가 지원하는 buildx 라는 CLI 플러그인 사용
buildx로 타겟 플랫폼 지정
docker buildx build명령어에--platform플래그를 추가하여, 이미지가 실행될 환경인linux/amd64를 명시적으로 지정해준다.
import os
dataset_path = "/content/EMOV-DB"
# 파일, 라벨 추출
data = []
for folder in os.listdir(dataset_path):
folder_path = os.path.join(dataset_path, folder)
if os.path.isdir(folder_path):
for file in os.listdir(folder_path):
if file.endswith('.wav'):
label = file.split('_')[0] # Sleepy_0001.wav → Sleepy
data.append({'file_path': os.path.join(folder_path, file), 'label': label})
for i in data:
i['label'] = i['label'].lower()
data_df = pd.DataFrame(data)
data_df.label.value_counts()
코드 실행 결과
5가지 레이블 : amused, anger, disgust, neutral, sleepiness
데이터 분포
Sleepy : 1,721개
Neutral : 1,568개
Amused : 1,317개
Anger : 1,268개
Disgust : 1,019개
2️⃣ 오디오 전처리
피처 추출
오디오 파일에서 MFCC(Mel Frequency Cepstral Coefficient) 특징을 추출하였고,
각 오디오 파일에서 평균 MFCC 값을 계산하여 모델 입력값으로 활용하였다.
✔️ librosa: 음성 신호 처리를 위한 Python 라이브러리
import librosa
# MFCC 특징 추출 함수
def extract_features(file_path, n_mfcc=13):
audio, sr = librosa.load(file_path, sr=None) # 원본 샘플링 속도 유지
mfcc = librosa.feature.mfcc(y=audio, sr=sr, n_mfcc=n_mfcc)
return np.mean(mfcc, axis=1) # 평균값으로 축소
# 모든 파일에 대해 특징 추출
features = []
labels = []
for item in data:
features.append(extract_features(item['file_path']))
labels.append(item['label'])
# numpy 배열로 변환
X = np.array(features)
y = np.array(labels)
레이블 인코딩
LableEncoder 객체로 레이블의 고유 값을 학습하여 매핑을 생성한다.
각 레이블을 0부터 시작하는 정수로 변환한다.
✔️sklearn.preprocessing.LabelEncoder: 카테고리형 데이터(문자열 레이블)를 숫자형으로 변환하는 데 사용
from sklearn.preprocessing import LabelEncoder
le = LabelEncoder()
y_encoded = le.fit_transform(y)
le.classes_
실행 결과
🔸왜 이런 처리가 필요한가 ?
피처 추출
음성 데이터는 원본 형태로는 머신러닝 모델에 사용하기 어려움.
MFCC는 음성 신호의 시간 및 주파수 정보를 압출하여 모델에 필요한 핵심 정보를 제공하기 때문에 필요하다 !
레이블 인코딩
문자열 형태의 레이블은 모델이 처리할 수 없으므로, 숫자형으로 변환해주어야 함.
숫자로 변환된 레이블은 다중 클래스 분류에서 모델이 각 클래스를 쉽게 구분할 수 있도록 도와준다.
🔹개선 가능성
스케일링
추출된 MFCC 값을 StandardScaler로 정규화하면 모델 성능이 더 좋아질 수 있다.
추가 음성 특징 추출
3️⃣ 모델 학습 및 실행
Multiclass 분류
음성을 5개의 감정(amused, anger, disgust, neutral, sleepiness)으로 분류
Model : Random Forest
from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report
X_train, X_test, y_train, y_test = train_test_split(X, y_encoded, test_size=0.2, random_state=42)
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
y_pred = model.predict(X_test)
print("Accuracy:", accuracy_score(y_test, y_pred))
print("Classification Report:\n", classification_report(y_test, y_pred))