1. Dockerfile 작성

내가 구현한 모듈을 Docker 이미지로 만들기 위한 설명서인 Dockerfile을 작성한다.

Python과 FastAPI 기반이므로, uvicorn이라는 비동기 서버로 실행하도록 구성하였다.

# 베이스 이미지: 파이썬 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를 명시적으로 지정해준다.

# M3 맥(로컬) 터미널에서 실행
# --push 플래그 사용하여 빌드 끝나자마자 푸시하도록
docker buildx build --platform linux/amd64 -t {DockerHubID}/{프로젝트명} --push .

 

DriveMate는 운전 중 사용자의 음성을 분석하여 사용자가 졸린 지 감지하고,

졸음이 감지되면 간단한 스트레칭 추천이나 졸음 쉼터 안내 등

졸음운전을 방지할 수 있도록 도와주는 서비스이다.

 

핵심 기술 중, 사용자의 음성을 분석하여 사용자가 졸린 지 감지하는 기술에 대해서 설명해보려 한다.

EMOV-DB 데이터셋을 활용해서 음성에서 졸림과 관련된 특징을 추출하고,

이를 통해 정확한 분류를 수행하는 Random Forest 모델을 구현하였다.

 


1️⃣ 데이터 준비

EMOV-DB는 다양한 감정 상태를 담고 있는 음성 데이터셋이다.

우리는 이 중에서 Sleepy (졸림) 상태를 주요 타겟으로 설정하고 분석을 진행하였다.

import sys
sys.path.append('/content/drive/MyDrive/X10')

from emov_mfa_alignment import Emov
dataset = Emov()
dataset.download()
dataset.prepare_mfa()

 

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))

분류 결과

결과

- 정확도 : 86.87%

- 졸림(Sleepiness) : Precision 89%, Recall 93%, F1-score 91%

 

 

 


 

Binary 분류

음성의 감정 상태가 졸림인 지 아닌지를 판단

Model : Random Forest

from sklearn.model_selection import train_test_split
from sklearn.ensemble import RandomForestClassifier
from sklearn.metrics import accuracy_score, classification_report

y_encoded_bi = (y_encoded == 4)

X_train, X_test, y_train, y_test = train_test_split(X, y_encoded_bi, test_size=0.2, stratify=y_encoded_bi, 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))

분류 결과

성능

- 정확도 : 93.98%

- 졸림(Sleepiness) : Precision: 91%, Recall: 84%, F1-score: 87%

이진 분류에서는 다중 클래스보다 높은 성능을 보였다.

졸음이 아닌 상태(False)에서 Recall이 97%로 매우 높은 반면,

졸린 상태(True)에서는 Recall이 84%로 다소 낮게 나타났다.

이러한 결과는 데이터 불균형이 영향을 미친 결과일 수 있다.

 

 


왜 Random Forest ❓

음성 데이터는 고차원적이며, 다양한 감정 상태를 포함하고 있기 때문에 비선형성이 강하다.

Random Forest 모델은 이러한 데이터를 효과적으로 처리할 수 있는 강점이 있다.

 

또한 Random Forest 모델은 회귀와 분류 문제 모두에 적합하며,

다중 클래스 및 이진 분류 문제에서도 효과적이다.

 

대규모 데이터에서도 비교적 빠르게 학습 및 예측이 가능하고,

개별 Decision Tree의 예측을 평균화함으로써 과적합(Overfitting)을 방지할 수 있기 때문에 적합하다고 판단했다.

 

 

개선 방향성 ⭐️

1. 다양한 모델 실험

  • Gradient Boosting Models: XGBoost, LightGBM, CatBoost 등을 테스트하여 성능을 비교할 수 있다.
  • Deep Learning
    • Convolutional Neural Networks(CNNs): 음성 데이터를 Spectrogram으로 변환하여 CNN 모델로 학습.
    • Recurrent Neural Networks(RNNs): 시간 종속성을 고려하는 LSTM이나 GRU를 활용.

 

2. 데이터 증강

sleepiness와 다른 클래스 간 데이터 불균형 문제를 해결하기 위해,

졸림 데이터를 증강하거나 SMOTE(Synthetic Minority Oversampling Technique) 같은 기법을 사용할 수 있다.

 

 

3. 하이퍼파라미터 튜닝

Random Forest의 파라미터(예: n_estimators, max_depth, min_samples_split)를

GridSearchCV나 RandomizedSearchCV를 통해 최적화할 수 있다.

from sklearn.model_selection import GridSearchCV

param_grid = {
    'n_estimators': [100, 200, 300],
    'max_depth': [10, 20, None],
    'min_samples_split': [2, 5, 10]
}
grid_search = GridSearchCV(RandomForestClassifier(random_state=42), param_grid, cv=5)
grid_search.fit(X_train, y_train)
print(grid_search.best_params_)

 

 

24-1에 참여했던 동아리에서 여름방학 프로젝트로 만든 게임이다.

기획자분의 기획발표가 인상 깊기도 했고

게임 개발이 처음이다보니, 배워가면서 할 수 있는 난이도의 게임인 것 같아 참여하게 되었다.

 

기획+코더 1분, 그래픽 1분과 함께 3인 프로젝트로 진행하게 되었다.

유니티도 처음이고 C# 도 익숙하지 않았지만, 그래도 구글링하고 공부해가면서 하다보니,

프로젝트 후반부에는 재밌게 했던 것 같다.

 

6월 마지막주부터 8월 셋째주까지 약 두달 간 진행된 프로젝트였다.

매주 1회 오프라인으로 회의가 진행되었고, 온라인 회의는 거의 없었다.

오프라인 회의에서 거의 모든 구현과 안건회의가 진행되었다.

 

 

 


 

 

 

나는 3가지 레벨 중 중간 레벨 전체 구현을 맡았고, esc 패널과 도움말 버튼, 타레벨 대사 씬 구현 등을 맡아서 했다.

 

중간 레벨은 boss가 player의 position을 따라다니면서 원형으로 총알을 발사하고,

플레이어는 2개의 목숨을 가진 채 총알을 발사하여 boss를 처치해야 한다.

 

 

 

 

 

B급 감성의 스토리 진행과 슈팅 게임으로 이루어진 게임이고, 감동 마무리까지 완벽한 게임이다 !!

이화여자대학교 게임개발동아리 KING 카페에 빌드본이 업로드 되어있으니,

관심 있으신 분들은 꼭 플레이해보시길 !!

 

 

 

졸리고.. 얼른 침대에 뛰어들고 싶지만

최대한 많은 것들을 기록에 남기고 싶어서 바로 호다닥 씀..📝

 

6월 1일부터 무박 2일 동안 너디너리에서 해커톤을 진행했다.

사실 노드 배운 지 2달밖에 안 돼서 민폐가 될까봐 넘 걱정됐지만

UMC 워크북도 예습하고 유튜브로도 열심히 공부하고 떨리는 마음으로 참석했다.

 

사전에 참여자 명단이 떴는데 node.js로 서버개발 참여자가 3명인 거 시롸야...?

다시 한번 스프링의 위력을 느끼고..

3명이 제발 한팀이길.. 1명 2명으로 찢어지지 않길.. 그 1명이 내가 되지 않길 기도했다 🥹🙏

CMC랑 UMC가 공동 개최하는 건데 블로그들을 보니까 UMC는 선택참여, CMC는 필수참여이길래

내가 속한 UMC는 엄청 적으면 어떡하지.. CMC 분들 사이에서 감자가 되면 어떡하지..

별의별 걱정을 다 하면서 갔다. 밤도 새야 되는데 잠도 제대로 못 자구 감..

 

정말 많은 걱정을 했구, 이화 UMC 노드파트에서 나밖에 안 나가길래(전체에서도 나 포함 2명 나감) 나가지 말까도 싶었다.

하지만, 정말 좋은 기회가 될 거라는 걸 직감했고

아직 부족한 점이 많은 나에게 많은 걸 배워갈 수 있는 행사라고 생각했다.

겁이 나고 걱정이 된다는 이유로 포기하고 싶지 않았고

참가 지원서에 썼듯이 많이 배워가고 할 수 있는 최선을 다하자는 마음가짐 하나로 참석했다.

다녀온 지금 드는 생각은 참여하길 정말 잘했다는 생각이 들었고,

생각보다 더 많은 것들을 배워가고 경험할 수 있었던 것 같다.

 


 

도착하자마자 받은 명찰 !-!

우리 팀은 Web 팀이었구

( PM 1명, 디자인 1명, Web 4명, 서버(node.js) 3명이었다 )

걱정과 달리 CMC 2분 제외 모두 UMC셨다.

 

이번 해커톤 주제는 '도파밍' 이었다.

점심 먹기 전후로 아이디어 메이킹 시간이었는데,

기획동아리 회장단 짬바로 아이디어를 왕창 냈다.

 

처음에는 도파민을 억제하는 방향으로 아이디어를 냈었는데

다른 팀들도 비슷한 방향성일 것 같다는 PM님 말에 다시 생각해보다가

무분별한 도파민을 억제하고, 자기주도적으로 도파민을 적절히 분비하자는 쪽으로 아이디어를 냈다.

나는 숏츠 중독의 원인은 계속해서 무의식적으로 스와이프하면서

보다보면 어느새 시간이 훌쩍 지나가있는 점이라고 생각했고,

그런 점에서 매일매일 자기가 볼 숏츠의 수를 정하고

정해진 개수를 모두 보면 자기가 정한 시간만큼 도파민 디톡스를 진행하는 앱이 어떨지 의견을 냈다.

 

투표를 통해 여러 아이디어 중에 위의 아이디어로 진행하기로 결정됐다.

PM님과 디자이너님이 플로우차트랑 와이어프레임을 그려주시는 동안

서버 파트는 서버를 파고 개발환경 셋업을 했다.

 


 

 

node.js를 많이 해보신 분이 주도해서 개발을 진행해주셨다.

그 분이 express 말고 최신 프레임워크인 NestJS를 써보는 거 어떠냐 제안해주셔서

같이 배워보면서 해보기로 했다.

 

데이터베이스도 Prisma ORM 을 사용해서 진행하였다.

Prisma로 해보니 너무 편했어서 따로 포스팅으로 정리해볼 생각이다.

 

그렇게 우리가 정한 개발 환경은 아래와 같다.

 

  •  Stack 
    • Javascript (Node.js)
  •  Framework 
    •  NestJS
  •  Database 
    • MySQL
  •  ORM 
    • Prisma
  •  Server 
    •  Amazon Web Service

 

Nest.js 제안해주신 분이 초기 기본 설정이랑 코드를 다 작성해주셨다.

그동안 나랑 다른 한 분이 ERD를 같이 작성하였다.

 

 

 

요렇게 그렸고, 사실 데이터베이스에 저장할 값들이 많지 않아서 ERD는 꽤 간단했다.

워크북으로 실습할 때는 데이터베이스 강의 들은 지 1년이 넘어서 하면서도 긴가민가 했었는데,

저번주에 SQLD를 봤더니 ERD는 꽤나 쉽게 그릴 수 있었다.

근데 확실히 개발하면서 조금조금씩 수정하긴 했움

 

 

API listup 하구 3명이서 1/3씩 맡아서 구현했다.

열심히 물어봐가면서 완성함 헷

스웨거도 저번주 UMC에서 처음으로 실습했지만

내가 맡은 API Swagger도 잘 마무리했댜.

 

 

 

API 다 개발하구 살짝 엎드려서 잠깐 잠들엇는데

허리가 끊어질 거 같아서 깨어보니 서버가 터졌단다....하아ㅜ

AWS에서 EC2 인스턴스를 몇개를 만들어봐도 계속 불안정하고 연결하면 터졌다.

 

프론트엔드랑 연결까지 했는데 그 이후로 데이터베이스가 문제인건지

서버가 문제인건지 계속 에러가 떴다.

 

시연영상까지 찍고, 발표준비까지 마쳤는데도

서버가 해결이 안 됐지만ㅜ 서버팀 포기하지 않구 계속계속 해결하려고 애썼다.

결국 마감 시간 조금 지나서 해결하긴 했숨 ..!!

(다른 분이 AWS 말고 다른 해외 서버로 서버를 다시 파서)

 

 


동영상 서비스가 종료되어 해당 콘텐츠를 재생할 수 없습니다.

 

다른 팀보다 아이디어도 안 겹치고, 디자인도 넘 예쁘고,

유튜브 API도 잘 따와서 선택한 카테고리에 맞게 보여주는 것도 잘 구현했고,

PM분이 발표도 너무너무 잘해주셔서

사실 수상을 기대했었다.

근데 아무래도 서버 터진 걸 마감 이후 발표 때 해결해서 그런건지 수상은 못했지만

그래도 너무 많이 배워가고

자극도 많이 됐고, 참가한 것 자체만으로도 너무 뿌듯했던 나의 첫 해커톤 !!

다음에도 해커톤 열리면 그때 가서도 또 걱정하겠지만,

덜?한 걱정으로 또 참석할 것 같다.

그 전까지 더더더더 실력 쌓기 !!

 

+ Recent posts