스터디 첫날, 왜 이 주제를 잡았는가

2026년 1월 7일, 스팸 분류 스터디를 시작했다. 이 스터디는 개발 경험이 있는 최규철님이 직접 개설해 주셨고, 목표도 처음부터 꽤 또렷했다.

최대한 LLM에 의존하지 않고, 수업 시간에 배운 자연어 처리와 머신러닝 흐름으로 스팸 분류를 끝까지 구현해 보기

같은 시기에 AWS 스터디도 함께 진행하고 있었지만, 요일이 달라서 병행 자체는 가능했다. 다만 이번 스터디는 바로 답을 얻는 방식이 아니라, 자료를 찾아가며 직접 연결하는 방식이었기 때문에 속도보다는 이해를 우선하는 흐름으로 출발했다.

스터디 모집 화면을 보면 주제와 기간이 한눈에 정리돼 있었다.

SMS 스팸 분류 2주 완성 프로젝트 스터디 모집 화면

첫날 내가 중요하게 본 것은 두 가지였다.

  • 이 스터디가 단순 실습이 아니라 작은 프로젝트처럼 운영된다는 점
  • 결과보다 과정을 제대로 이해하는 쪽에 무게가 실려 있다는 점

그래서 Day 1은 모델 성능을 바로 올리는 날이라기보다, 앞으로 2주 동안 어떤 문제를 다루게 될지 감을 잡는 날에 가까웠다.

먼저 환경부터 맞추려고 했다

스터디 자료 폴더에는 Dockerfile, docker-compose.yml, requirements.txt까지 같이 들어 있었다. 각자 로컬 환경에 맞춰 대충 돌리기보다, 가능하면 비슷한 환경에서 주피터 랩을 열고 같은 결과를 보자는 쪽에 가까웠다.

실제로 공유된 실행 흐름도 아래처럼 정리돼 있었다.

  1. 저장소 클론
  2. 프로젝트 폴더 진입
  3. docker-compose up --build
  4. 주피터 랩 접속
Docker 기반 스팸 분류 스터디 환경 설정 안내와 실행 시도 화면

환경 통일 시도는 생각보다 중요했다. 데이터 분석이나 머신러닝 실습은 코드만 같다고 결과가 항상 같지 않다. 라이브러리 버전, 토크나이저, 텐서플로 버전, 노트북 실행 환경이 조금만 달라도 경고가 달라지고, 심하면 실행 자체가 안 되기도 한다.

스터디 폴더의 의존성도 꽤 직관적이었다.

pandas==2.2.0
numpy==1.26.0
matplotlib==3.8.0
seaborn==0.13.0
scikit-learn==1.4.0
nltk==3.8.1
tensorflow==2.15.0
jupyterlab

이 구성을 보면서 첫날부터 알 수 있었던 것은, 이번 스터디가 단순히 “전통적인 머신러닝만” 보는 것도 아니고, “딥러닝만” 보는 것도 아니라는 점이었다. 전처리부터 Naive Bayes, SVM, LSTM 계열까지 한 번에 다루는 흐름이 이미 준비되어 있었다.

일단 데이터부터 읽어봤다

실습 노트북에서는 가장 먼저 spam.csv를 읽어 왔다. 경로는 data/spam.csv, 인코딩은 latin-1이었다.

import pandas as pd

df = pd.read_csv('data/spam.csv', encoding='latin-1')

이 코드 자체는 짧지만, 첫날에는 여기서부터 생각보다 많은 것을 확인할 수 있었다. 데이터를 불러온 직후 head()describe(), isnull().sum()을 확인하면서 파일 구조를 읽는 작업이 바로 이어졌다.

df.head(30)
df.describe()
df.isnull().sum()

여기서 먼저 확인한 사실은 다음과 같았다.

  • 원본 데이터 크기: 5572 x 5
  • 실제 의미 있는 주요 컬럼: v1, v2
  • 나머지 컬럼: Unnamed: 2, Unnamed: 3, Unnamed: 4

즉 처음 보기에는 5개 컬럼이 있는 표처럼 보이지만, 실제 스팸 분류에 쓰는 정보는 라벨과 문자 본문 두 개뿐이었다.

쓸모없는 컬럼이 생각보다 많았다

첫날 데이터 점검에서 가장 먼저 걸린 것은 불필요한 컬럼이었다. isnull().sum() 결과를 보니 아래처럼 거의 비어 있는 열이 세 개나 있었다.

df.isnull().sum()

출력 결과는 다음과 같은 구조였다.

  • v1: 0
  • v2: 0
  • Unnamed: 2: 5522
  • Unnamed: 3: 5560
  • Unnamed: 4: 5566

즉 전체 5,572행 중에서 대부분이 비어 있는 열이었다. 이런 컬럼을 그대로 두면 데이터 구조를 헷갈리게 만들고, 이후 전처리 단계에서도 방해가 된다. 그래서 Day 1에서 바로 이 열들을 제거하는 방향으로 정리했다.

df.drop(['Unnamed: 2', 'Unnamed: 3', 'Unnamed: 4'], axis=1, inplace=True)

이 작업을 하고 나면 데이터는 훨씬 이해하기 쉬워진다. 남는 것은 사실상 두 열이다.

  • v1: 정상/스팸 라벨
  • v2: 실제 문자 메시지 본문

그리고 이어서 컬럼명도 의미 있게 바꿔 두는 편이 낫다고 판단했다.

df.rename(columns={'v1': 'label', 'v2': 'text'}, inplace=True)

이름을 label, text로 바꾸고 나니 이후 노트북을 읽을 때도 훨씬 덜 헷갈렸다. 이런 사소해 보이는 정리가 실제로는 나중 코드의 가독성을 많이 바꾼다.

정상 메시지와 스팸 메시지 비율부터 확인했다

데이터를 읽은 첫날에는 모델보다 먼저 클래스 분포를 확인하는 것이 중요했다. 분류 문제에서 라벨 비율이 심하게 기울어져 있으면, 정확도 숫자만 보고 모델을 오해하기 쉽기 때문이다.

노트북에서는 value_counts()로 라벨 개수를 바로 확인했다.

print(df['v1'].value_counts())

원본 기준 결과는 다음과 같았다.

  • ham: 4825개
  • spam: 747개

즉 처음부터 스팸보다 정상 메시지가 훨씬 많았다. 비율로 환산하면 대략 다음 수준이었다.

  • 정상 메시지: 약 86.6%
  • 스팸 메시지: 약 13.4%

이 수치를 보는 순간, 첫날부터 한 가지 기준이 생겼다.

이 문제는 단순 정확도만 보면 안 된다.

예를 들어 모델이 모든 메시지를 정상이라고만 예측해도 정확도가 꽤 높게 나올 수 있다. 하지만 그런 모델은 실제로 스팸을 잡지 못하므로 쓸모가 없다. 그래서 첫날 데이터 분포를 확인하는 작업은 이후 평가 지표를 어떻게 볼지 기준을 세우는 일이기도 했다.

중복 데이터가 꽤 많다는 것도 뒤늦게 보였다

이 데이터에는 중복 메시지도 꽤 있었다. 이후 단계에서 실제로 확인한 수치는 403개였다.

print(df.duplicated().sum())

이 부분은 Day 1에 바로 제거까지 완료했다기보다, “이 데이터는 깨끗한 것처럼 보여도 그대로 쓰면 안 된다”는 경고처럼 받아들였다. 문자 데이터는 짧은 문장이 반복되는 경우가 많아서, 같은 문장이 여러 번 들어 있으면 모델이 특정 표현만 지나치게 외워 버릴 수 있다.

그래서 첫날 데이터 점검을 마치고 나니 정리할 포인트가 뚜렷했다.

  • 쓸모없는 컬럼을 버려야 한다
  • 클래스 비율이 불균형하다
  • 중복도 존재한다

즉 스팸 분류 모델을 바로 만들기 전에, 먼저 데이터부터 차분하게 읽어야 하는 상태였다.

첫날 기준으로 정리한 핵심 코드

Day 1에서 실제로 중요했던 코드는 길지 않았다. 하지만 이후 모든 실험의 출발점이 되는 코드였다.

import pandas as pd

df = pd.read_csv('data/spam.csv', encoding='latin-1')

print(df.shape)
print(df.columns)
print(df.isnull().sum())
print(df['v1'].value_counts())

df.drop(['Unnamed: 2', 'Unnamed: 3', 'Unnamed: 4'], axis=1, inplace=True)
df.rename(columns={'v1': 'label', 'v2': 'text'}, inplace=True)

이 정도만 해도 첫날 얻는 정보는 꽤 많다.

  • 데이터가 몇 개인지
  • 어떤 컬럼이 실제로 의미가 있는지
  • 결측치가 어디에 몰려 있는지
  • 라벨 비율이 얼마나 기울어져 있는지
  • 이후 전처리를 어떤 방향으로 해야 할지

첫날은 화려한 모델링보다 이런 정리 작업이 훨씬 더 중요했다.

Day 1에서 내가 느낀 점

첫날 스팸 분류 스터디를 하면서 가장 크게 느낀 것은, 머신러닝 실습은 결국 데이터를 읽는 일에서 시작된다는 점이었다. 아직 전처리도 본격적으로 하지 않았고, 시각화도 많이 보지 않았고, 모델 성능 비교도 시작하지 않았는데 이미 할 일이 많았다.

특히 이번 스터디는 LLM을 거의 쓰지 않고 진행하려고 했기 때문에, 작은 코드 한 줄도 왜 필요한지 직접 생각하게 되는 시간이 길었다. 평소라면 그냥 지나쳤을 수도 있는 부분들, 예를 들면 인코딩, 결측치, 불필요한 컬럼, 클래스 비율 같은 요소를 첫날부터 더 신경 쓰게 됐다.

그리고 이 첫날 작업 덕분에 이후 흐름도 자연스럽게 보였다.

  • Day 2에서는 문장 길이 분포와 빈출 단어 같은 시각화를 볼 수 있겠구나
  • 그다음에는 전처리를 직접 함수로 묶겠구나
  • 이후에는 벡터화와 Naive Bayes, SVM, LSTM으로 이어지겠구나

즉 Day 1은 단순한 준비 단계가 아니라, 스터디 전체의 방향을 잡아 주는 날이었다.

마무리

2026년 1월 7일의 스팸 분류 스터디 첫날은 모델 성능을 내는 날이 아니라, 데이터를 읽는 날이었다. 스터디가 어떤 방향으로 흘러갈지, 데이터가 얼마나 정리되어 있는지, 어떤 문제를 먼저 해결해야 하는지를 파악하는 시간이었다.

첫날 정리만으로도 이미 중요한 사실이 드러났다.

  • 데이터는 생각보다 깔끔하지 않았다
  • 스팸보다 정상 메시지가 훨씬 많았다
  • 불필요한 컬럼과 중복을 먼저 정리해야 했다

그리고 이런 기본 점검이 있어야만 이후 전처리, 시각화, 모델 비교 결과도 제대로 해석할 수 있다. 다음 글에서는 Day 2 기준으로, 문장 길이 분포와 워드클라우드처럼 데이터의 성격을 눈으로 확인한 과정을 더 자세히 정리해 보려고 한다.

Community

Comments

0 comments

Comments appear immediately. Use report if something needs review.

No comments yet.