데이터 중심 어플리케이션 설계에 이어 컴퓨터 밑바닥의 비밀이라는 책을 스터디 합니다. 1장은 프로그래밍 언어가 작동하는 기본을 설명하는 단원으로 컴파일러와 링커 그리고 추상화에에 대한 내용입니다. 이번에는 조금 색다르게 비유법을 통해서 설명해보겠습니다.
목차
1. 용어 정리
신규개념
설명
컴파일러(compiler)
고수준의 언어를 저수준의 언어로 번역하는 프로그램
토큰(Token)
컴파일러가 쪼갠 각 항목에 정보를 결합한 것
해석(parsing)
소스코드에서 토큰을 추출하는 과정
GCC 컴파일러
유닉스/리눅스 계열의 표준 컴파일러
대상 파일(object file)
소스파일을 컴파일한 결과로 기계어로 쓰여진 프로그램
링커(Linker)
컴파일러가 만든 대상파일을 하나의 실행파일로 합쳐주는 프로그램
심벌(Symbol)
전역 변수(Global Variable)과 함수(function)을 포함하는 모든 변수 이름들
라이브러리
컴파일된 패키지와 모든 함수의 선언을 포함한 헤더 파일로 만든 것
헤더 파일(header file)
컴파일러가 외부 심벌 정보를 기록하는 표
2. 프로그램이 음식점이라면
여기 이세계 식당이 있습니다. 요리사와 요리법이 존재하는 것 같지만 재료는 무한이 복제되는 마법 창고에서 생성됩니다. 이 때 요리사는 재료와 요리법의 순서를 조합하여 하나의 완성된 요리를 하게 됩니다. 이를 우린 컴퓨터에서 컴파일러라고 부릅니다.
만약 제육볶음을 만든다면? 파, 고기 앞다리살과 같은 재료가 필요할 것이구요, 파는 썰고 고기는 적당히 볶고 맛나게 먹는 그런 레시피가 만들어질 것 입니다. 이렇듯 컴파일러는 데이터와 코드의 조합으로 실행 가능한 그리고 기계가 이해할 수 있는 대상 파일을 만들게 됩니다.
그런데 말입니다. 옆 집 중국 음식점의 두반장이 그렇게 인기가 많다는데요? 제육볶음에 한 번 넣어보면 어떨까요? 마침 중국집 위치가 적혀있는 정보도 가지고 있네요! 찾아가서 가져옵니다. 이때 두반장은 외부 변수이고 외부 재료의 위치는 심벌 테이블로 비유될 수 있습니다.
자 이제 제육볶음 레시피를 개발했으니 우리 수 쉐프에게 보고하죠. 쉪! 이게 새로운 제육볶음 레시피입니다. 쉐프가 흡족해 하는군요. 이걸 우린 컴퓨터에서 링커 라고 합니다. 이 링커는 흡족한 대상 파일을 모아 하나의 요리법으로 정리합니다. 이를 우리는 라이브러리라고 하구요. 마치 밀키트를 연상 시키는군요!
그런데 말입니다. 컴파일러를 열심히 설명해봤는데, 인터프리터라는 것도 있단 말이죠? 말로는 파이썬, 자바스크립트가 인터프리터라고 하고 C가 컴파일러라는데 잘 이해가 되지 않는 군요. 요리법으로 비교하면 말이죠, 컴파일러는 설계형 요리사입니다. 차근히 대상파일을 보고 변수가 잘 선언되어있는지 에러는 나지 않는지 검토합니다. 레시피를 검토하는 것처럼 말이죠! 반면 인터프리터는 즉석 요리사입니다. 일단 만들고 해석합니다. 그러다보니 아차! 제육이 없군요! 다시 시작 해야겠어요.
3. 동적/정적 라이브러리
그런데 말입니다. 남의 요리법을 가져다 쓸 수 있다는 사실. 우린 라이브러리를 가져다 쓰는 것에 익숙한데 이렇게 남의 노하우, 요리법을 복사해다가 내 음식점을 저장해놀 수 있겠습니다. 마라탕이 그렇게 인기가 많대요!! 우리도 써봐요. 문제는 요리법이 세상에 너~무 많아서 이걸 모두 복사해놓다간 음식 창고보다 문서창고가 더 많아지겠어요. 이거이거 문제군요.
그러지 말고 우리 돕고 삽시다! 저기 동문시장 청년몰은 요리법을 공유하는 시스템이 있대요!! 몰래 복사해 놓을 것이아니라 필요할 때 마다 가져다 쓰면 된답니다 오호!! 역시 사람은 돕고 살아야해요. 운영시간 내내 가져다 써도 되고 주문이 들어올 때 마다 가져다 써도 된다니 이거 참 인심이 좋은 곳입니다.
이렇게 동적 라이브러리는 장점이 많아요! 매번 요리법을 복제하지 않아도 되고요! 요리법 ver 2.0가 나와도 수정된 요리법만 가져오면 만사 Ok! 요리법을 복사해다 쓰던(정적 라이브러리)시절보다 훨씬 편하군요. 추가 메뉴 확장도 좋아요. 요즘 또 안성재 쉐프가 유명하니까 도토리 국수 플러그인을 써보겠습니다. 또 다른 요리법과 쓰까 쓸 수 있는 점도 아주 좋군요!
하지만 단점도 있는데요! 아무래도 요리법을 공유 주방에서 가져오다보니 살짝 시간이 소요가 되네요! 아무렴 어떠한가요. 그리고 잘 쓰던 중국집 위치가 이사 갔대요 흑흑 원래는 참조표를 보고 다녔는데 이사갔나 보군요. 이런 경우를 대비해서 멀-티버스를 만들어서 극복~ 했답니다. (급마무리)
4. 후기
보통 데이터를 음식으로 표현하니까 적용해봤는데 재밌으면서도 엄밀하지 않은 부분이 공존하는 그런 글이 되었네요. 사실 링커가 내부 변수는 관심을 가지지 않는다 와 같은 사실도 한 번 표현해보고자 했는데 가상화 부분이 살짝 이해가 안되어서 추후 메모리 단원에서 정리할 예정!
데이터 중심 어플리케이션 설계(대중애설) 스터디의 후기를 작성하고 좋았던 방법과 이 책을 스터디로 선택하려는 사람들을 위한 기록을 남겨봅니다.
1. 스터디 방법
사용 플랫폼: 슬랙 - 허들
기간: 25년 2월 ~ 4월 24일
참여 방법: 총 12장을 매주 읽고 블로그/노션에 NDA 템플릿을 이용해서 정리하기
새롭게 알게된 점(New)
어려웠거나 이해하지 못한 부분(Difficulty)
추가 내용(Amendment)
스터디 방법: 매주 일요일 오후 10시에 모여 작성한 내용 읽으며 질의응답
2. 후기
책 후기
"데이터"라는 문구만 보고 데이터분석/사이언티스트가 도전할 수도 있겠으나 명확히는 설계에 관점이 맞춰져 있기 때문에 백엔드에 관심 있는 사람들이 보는게 좋다. Part 1에서는 신뢰성, 데이터 모델, 저장소와 검색, 부호화 Part 2에서는 분산 데이터, 복제, 파티셔닝, 트랜젝션, 일관성 Part 3 배치 처리, 스트림 처리에 대해서 다루는 흐름으로 폭 넓게 데이터 시스템 설계에 대한 이해를 도울 수 있을 것으로 판단할 수 있다. 하지만 각 단원마다의 내용이 압축되어 소개하므로 각 시스템에 대해서 설계하고 구현한 사람들에는 복기의 느낌으로 읽어보는 것이 가장 큰 도움이 될 수 있다. 다른 이론서와 비슷하게 책 자체를 필사해서 적는 블로그가 많기 때문에(본인 포함...) 참고 할만한 블로그가 적다. 늘 느끼지만 이런 난이도 높은 책들은 완벽하게 이해하려하기 보다는 책/단원에서 얘기말하고자 하는 바를 인내심있게 쫓되 나의 무지를 너무 탓하지 않고 꾸준히 읽는 것이 스터디 완료의 비결인 듯! 단원이 말하고자 바를 요약하고 나의 언어로 재구성해서 작성하는 셀프 스터디가 필요하다. 본인이 데이터 관련 직무라면 3장 정도는 읽어보는거 매우 추천
자율적으로 글을 쓰고 발표하기로 하였어서 고정적인 2-3명의 발표자가 있고 청중이 5명정도 유지되었다. 특히 청중에는 N사 광고도메인 개발자 분이 계셔서, 추상적으로 읽을 수 밖에 없는 주니어 개발자들의 질문이나 고민을 실무적으로 대답해주는 부분이 이번 스터디의 가장 큰 특징이였다. 느슨한 스터디에도 꾸준히 진행할 수 있었던 동기부여가 된 것 같다. 서로 얻을 수 있는 부분을 자연스럽게 구축하는 것이 스터디에서 중요함을 느꼈다. 꽤나 잘 진행된 스터디어서 다음 책은 컴퓨터밑바닥의 비밀 이라는 책을 진행할 예정
집앞의 횡단보도를 자주 지나가는데, 횡단보도를 기다릴지 아닐지 늘 고민하면서 지나갑니다.'4명 기다리고 있는데 금방 바뀌지 않을까? 좀 더 가서 사거리에서 건너는게 나을까?' 저는 데이터를 수집해서 언제쯤 횡단보도가 바뀌는지, 그리고 이를 활용할 수 있는 분포는 뭐가 있는지 확인해보자는 문제의식을 정의하고 해결하는 방법을 작성했습니다. 월간 데이터노트 4기의 스터디로 진행된 분석입니다. 월간 데이터노트 참여에 관심이 있다면 다음 공지를 확인해주세요!
횡단보도를 이용하는 사람은 주민, 학생, 출퇴근 직장인들이 주류를 이루며, 10시/ 4시 정도에는 일부 유모차를 동반한 엄마가 등장
2. 데이터 수집
4차선도로이며 횡단보도 기준 빨간불 2분, 파란불 25초
제가 주로 이동하는 시간인 출퇴근/점심시간/퇴근 시간에 수시로 22개 수기 수집
평균 4.1명, 중위 3.5, 표준편차 3.6명, IQR 기준 이상치 2개(13, 14)
시간대 분포
3. 분포 모델 검토
처음에는 포아송 분포라고 생각하고 호기심 발제했음
포아송 분포
정의: 일정 시간 & 공간 내에 독립적으로 발생하는 사건의 횟수를 모델링
분포의 특징과 적합성 체크
사건은 서로 독립적으로 발생 → 맞음
사건은 동시에 발생하지 않음 → 동시에 사람들이 도착하기도 함
평균 발생률은 관측 구간 내 일정 → 맞음
분산/평균의 값이 1임 → 아님 3.01임 (과산포임)
다른 조건이 어느정도 맞으나 분산과 평균이 같지 않아서 Claude가 음이항 분포를 추천함
음이항 분포
정의: r번째 성공을 관측할 때 까지 필요한 시행 횟수나 성공 확률이 p인 베르누이 시행에서 r번의 성공을 관측할때까지 실패한 횟수의 분포
분포의 특징과 적합성 체크
분산이 평균보다 큰 과산포(overdispersion) 데이터에 적합
Ex) 특정 지역의 범죄 발생 건수(범죄가 특정 지역에 집중)
Ex2) 소비자 구매 행동(일부 고객이 훨씬 더 자주 구매)
데이터에 과산포가 존재(분산> 평균) → 맞음
사건 발생이 군집화 되는 경향이 있음 → 맞음 일부시간대 몰림
개체별로 발생률에 차이가 있음 → 있을 수 있음(주부/학생 등)
발생 확률이 시간에 따라 변할 수 있음 → 그러함
4. 분석
히스토그램 및 분포 시각화
포아송 분포와 음이항 분포 적합
적합도 검정 (카이제곱 검정)
AIC 및 BIC를 사용한 모델 비교
import matplotlib.pyplot as plt
import platform
def set_matplotlib_font():
system = platform.system()
if system == "Windows":
plt.rc('font', family='Malgun Gothic')
elif system == "Darwin": # macOS
plt.rc('font', family='AppleGothic')
elif system == "Linux":
plt.rc('font', family='NanumGothic')
else:
print("Unknown system. Please set font manually.")
plt.rcParams['axes.unicode_minus'] = False
# 폰트 설정 함수 호출
set_matplotlib_font()
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import scipy.stats as stats
from statsmodels.discrete.discrete_model import Poisson, NegativeBinomial
df = data.copy()
df.columns = ['timestamp','count']
print("데이터 샘플:")
print(df.head())
# 기본 통계량
print("\n기본 통계량:")
print(df['count'].describe())
mean_count = df['count'].mean()
var_count = df['count'].var()
print(f"평균: {mean_count:.4f}")
print(f"분산: {var_count:.4f}")
print(f"분산/평균 비율: {var_count/mean_count:.4f}")
# 히스토그램 및 분포 시각화
plt.figure(figsize=(12, 6))
# 히스토그램
plt.subplot(1, 2, 1)
# sns.histplot(df['count'], kde=True, stat='density', discrete=True)
df['count'].hist(density = True)
plt.title('횡단보도 건너는 사람 수 히스토그램')
plt.xlabel('사람 수')
plt.ylabel('빈도')
# 경험적 분포와 이론적 분포 비교
x = np.arange(0, df['count'].max() + 1)
# 포아송 분포 확률 질량 함수
poisson_pmf = stats.poisson.pmf(x, mean_count)
# 음이항 분포 매개변수 추정
# 방법 1: 모멘트 방법
r = mean_count**2 / (var_count - mean_count) if var_count > mean_count else 100
p = mean_count / var_count if var_count > mean_count else mean_count / (mean_count + 1)
# 음이항 분포 확률 질량 함수
nb_pmf = stats.nbinom.pmf(x, r, p)
plt.subplot(1, 2, 2)
plt.bar(x, [df['count'].value_counts().get(i, 0) / len(df) for i in x], alpha=0.5, label='실제 데이터')
plt.plot(x, poisson_pmf, 'ro-', label=f'포아송 분포 (λ={mean_count:.2f})')
plt.plot(x, nb_pmf, 'go-', label=f'음이항 분포 (r={r:.2f}, p={p:.2f})')
plt.title('경험적 분포와 이론적 분포 비교')
plt.xlabel('사람 수')
plt.ylabel('확률')
plt.legend()
plt.tight_layout()
# 포아송 분포 적합도 검정 (카이제곱 검정)
observed = np.zeros(df['count'].max() + 1)
for i in range(len(observed)):
observed[i] = df['count'].value_counts().get(i, 0)
expected = len(df) * stats.poisson.pmf(np.arange(len(observed)), mean_count)
# 카이제곱 값 계산
# 기대빈도가 5 미만인 셀은 통합
min_expected = 5
observed_adj = []
expected_adj = []
last_obs = 0
last_exp = 0
for obs, exp in zip(observed, expected):
if exp >= min_expected:
observed_adj.append(obs)
expected_adj.append(exp)
else:
last_obs += obs
last_exp += exp
if last_exp > 0:
observed_adj.append(last_obs)
expected_adj.append(last_exp)
observed_adj = np.array(observed_adj)
expected_adj = np.array(expected_adj)
chi2 = np.sum((observed_adj - expected_adj) ** 2 / expected_adj)
df_chi2 = len(observed_adj) - 1 - 1 # 매개변수 1개 추정 (자유도 = 셀 수 - 1 - 추정된 매개변수 수)
p_value = 1 - stats.chi2.cdf(chi2, df_chi2) if df_chi2 > 0 else None
print("\n포아송 분포 적합도 검정 (카이제곱 검정):")
print(f"카이제곱 값: {chi2:.4f}")
print(f"자유도: {df_chi2}")
print(f"p-value: {p_value if p_value is not None else '자유도가 0 이하'}")
print(f"결론: {'포아송 분포에 적합하지 않음 (p < 0.05)' if p_value is not None and p_value < 0.05 else '포아송 분포에 적합함 (p >= 0.05)' if p_value is not None else '검정 불가 (자유도 부족)'}")
# AIC 및 BIC를 사용한 모델 비교
# 인공 변수를 만들어 모델 적합 (상수 모델)
X = np.ones((len(df), 1))
y = df['count'].values
# 포아송 회귀 모델
poisson_model = Poisson(y, X)
poisson_results = poisson_model.fit(disp=0)
# 음이항 회귀 모델
try:
nb_model = NegativeBinomial(y, X)
nb_results = nb_model.fit(disp=0)
print("\n모델 비교 (AIC 및 BIC):")
print(f"포아송 모델 AIC: {poisson_results.aic:.4f}")
print(f"음이항 모델 AIC: {nb_results.aic:.4f}")
print(f"포아송 모델 BIC: {poisson_results.bic:.4f}")
print(f"음이항 모델 BIC: {nb_results.bic:.4f}")
print(f"더 적합한 모델: {'음이항 분포' if nb_results.aic < poisson_results.aic else '포아송 분포'} (AIC 기준)")
except:
print("\n음이항 회귀 모델을 적합할 수 없습니다. 데이터가 충분하지 않거나 모델이 수렴하지 않습니다.")
# 시간대별 분석
df['hour'] = df['timestamp'].dt.hour
df['time_of_day'] = pd.cut(df['hour'],
bins=[0, 12, 18, 24],
labels=['아침(0-12시)', '오후(12-18시)', '저녁(18-24시)'],
include_lowest=True)
time_stats = df.groupby('time_of_day')['count'].agg(['mean', 'var', 'count'])
time_stats['var/mean'] = time_stats['var'] / time_stats['mean']
print("\n시간대별 통계:")
print(time_stats)
# 시간대별 분포 시각화
plt.figure(figsize=(10, 6))
sns.boxplot(x='time_of_day', y='count', data=df)
plt.title('시간대별 횡단보도 건너는 사람 수 분포')
plt.xlabel('시간대')
plt.ylabel('사람 수')
plt.tight_layout()
plt.show()
# 결론
print("\n분포 분석 결론:")
if var_count > mean_count:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 크므로 과산포(overdispersion)가 존재합니다.")
print("따라서 포아송 분포보다 음이항 분포가 더 적합할 수 있습니다.")
elif var_count < mean_count:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})보다 작으므로 과소산포(underdispersion)가 존재합니다.")
print("이 경우 포아송 분포보다 이항 분포가 더 적합할 수 있습니다.")
else:
print(f"분산({var_count:.4f})이 평균({mean_count:.4f})과 거의 같으므로 포아송 분포에 적합합니다.")
모델 비교 (AIC 및 BIC) -> 더 적합한 모델: 음이항 분포 (AIC 기준)
포아송 모델 AIC: 126.3588
음이항 모델 AIC: 111.9646
포아송 모델 BIC: 127.4498
음이항 모델 BIC: 114.1467
모수 추정 방법: 모먼트 방법과 최대 우도 추정법 존재
모멘트 방법
# 데이터의 평균과 분산 계산
mean = np.mean(data)
var = np.var(data, ddof=1) # 표본 분산
# 모수 추정
p = mean / var
r = mean * p / (1 - p)
5. 결론
눈으로 보기엔 포아송 분포가 더 잘 맞는거같은데 AIC기준으로는 음이항 분포가 더 잘맞다고 한다.
카이제곱을 검정을 쓰는 이유는 관측된 빈도와 분포의 기대 빈도와 비교하는 방법이기 떄문
AIC/BIC가 사용된 이유는 다른 통계 모델의 상대적 품질을 비교하는 지표이기 때문(상대적 평가)
음이항 분포의 모수
r(성공 횟수): 1.94
p(성공 확률): 0.32
위 내용을 가지고 95%의 신뢰도로 보행자 수는 11명 -> 엥? 너무 많고 비현실적임, 수집된 데이터중 13,14명인 사례는 출퇴근,점심시간 이였으며 10명 이상 건너는 상황은 잘 발견되지 않음, 50%정도만 넘어도 기다릴 가치는 있다고 생각되어 수정
from scipy.stats import nbinom
# 95% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.95, r, p)
max_count_95
# 11명
# 50% 확률 범위의 최대값 계산
max_count_95 = nbinom.ppf(0.5, r, p)
max_count_95
# 3명
누적확률 50% 이상은 3명, 3명 있으면 기다릴 가치가 있다.
6. 회고
GPT는 GP"F"로 바꿔야함. 앞뒤 서론이 너무 길고 공감 투가 너무 많음. Claude는 코딩능력은 좋은데 필요 이상으로 스켈레톤 코드를 제공해주며, 단순 수식 계산도 틀린 점이 자주 발견됨
포아송,음이항 분포 좀 다시 고찰할 필요가 있음. 내 상황에 적절한 분포를 선정하고 데이터를 수집하는게 좋겠다.
균등한 데이터 수집을 위해서 하루종일 혹은 대표성을 띄는 시간에 주기적으로 측정하는게 좋겠다.(평소에 부지런하자!)
Ex) 평일 오전 출근시간 가정 8-9시 집중관찰
본 주제는 매달 한 번씩 호기심을 주제로 분석하는 모임 <월간 데이터 노트>의 결과입니다. 관심이 있으시면 다음 링크를 확인해 보세요!!
분산 설명률: 80~90%의 분산을 설명하는데 필요한 주성분이 원래 특성 수의 절반 이하인가?
모델 성능: PCA적용 후 모델 성능이 유지되거나 향상되었는가?
다중공선성: 원본 특성간에 높은 상관관계가 많이 존재하는가?
절차
결측치, 이상치 처리, 원-핫 인코딩 사전 진행
스케일링
PCA 적용
설명된 분산확인 및 분산비율 시각화
적절한 주성분의 갯수 선택
선택된 주성분의 갯수로 PCA다시 적용
각 주성분이 원래 특성에 얼마나 기여하는지 확인
상위 5개 특성확인
from sklearn.decomposition import PCA
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from sklearn.preprocessing import StandardScaler
# 데이터는 이미 전처리되었다고 가정 (결측치, 이상치 처리, 원-핫 인코딩 등)
# X는 전처리된 특성 데이터
# 1. 스케일링 (PCA 적용 전 반드시 필요)
scaler = StandardScaler()
X_scaled = scaler.fit_transform(X)
# 2. PCA 적용
pca = PCA()
X_pca = pca.fit_transform(X_scaled)
# 3. 설명된 분산 비율 확인
explained_variance_ratio = pca.explained_variance_ratio_
cumulative_variance_ratio = np.cumsum(explained_variance_ratio)
# 4. 설명된 분산 비율 시각화
plt.figure(figsize=(10, 6))
plt.plot(range(1, len(explained_variance_ratio) + 1), cumulative_variance_ratio, marker='o', linestyle='-')
plt.xlabel('Number of Principal Components')
plt.ylabel('Cumulative Explained Variance Ratio')
plt.title('Explained Variance by Components')
plt.axhline(y=0.8, color='r', linestyle='--', label='80% Variance Threshold')
plt.axhline(y=0.9, color='g', linestyle='--', label='90% Variance Threshold')
plt.legend()
plt.grid(True)
plt.show()
# 5. 적절한 주성분 개수 선택 (예: 80% 분산 유지)
n_components = np.argmax(cumulative_variance_ratio >= 0.8) + 1
print(f"80% 분산을 유지하기 위한 주성분 개수: {n_components}")
# 6. 선택된 주성분 개수로 PCA 다시 적용
pca_selected = PCA(n_components=n_components)
X_pca_selected = pca_selected.fit_transform(X_scaled)
# 7. 각 주성분이 원래 특성에 얼마나 기여하는지 확인
components_df = pd.DataFrame(
pca_selected.components_,
columns=X.columns
)
# 8. 각 주성분에 대한 기여도가 높은 상위 5개 특성 확인
for i, component in enumerate(components_df.values):
sorted_indices = np.argsort(np.abs(component))[::-1]
top_features = [X.columns[idx] for idx in sorted_indices[:5]]
top_values = [component[idx] for idx in sorted_indices[:5]]
print(f"PC{i+1} 주요 특성:")
for feature, value in zip(top_features, top_values):
print(f" {feature}: {value:.4f}")
print()
# 9. 2D 시각화 (처음 두 개 주성분으로)
plt.figure(figsize=(10, 8))
plt.scatter(X_pca[:, 0], X_pca[:, 1], c=y, cmap='viridis', alpha=0.8)
plt.title('First Two Principal Components')
plt.xlabel('PC1')
plt.ylabel('PC2')
plt.colorbar(label='Target Class')
plt.grid(True)
plt.show()
판단근거1: 설명된 분산비율
하기의 누적분산 비율이 총 컬럼 12개에서 7개를 담아야지 80%를 설명하므로 차원축소가 효과적이지 않다는 판단 가능
# 누적 설명된 분산 비율을 확인
print("각 주성분별 설명된 분산 비율:")
for i, ratio in enumerate(explained_variance_ratio):
print(f"PC{i+1}: {ratio:.4f}")
print("\n누적 설명된 분산 비율:")
for i, ratio in enumerate(cumulative_variance_ratio):
print(f"PC1-PC{i+1}: {ratio:.4f}")
각 주성분별 설명된 분산 비율:
PC1: 0.2566
PC2: 0.1327
PC3: 0.1071
PC4: 0.0823
PC5: 0.0815
PC6: 0.0719
PC7: 0.0608
PC8: 0.0567
PC9: 0.0472
PC10: 0.0388
PC11: 0.0335
PC12: 0.0308
누적 설명된 분산 비율:
PC1-PC1: 0.2566
PC1-PC2: 0.3893
PC1-PC3: 0.4964
PC1-PC4: 0.5787
PC1-PC5: 0.6602
PC1-PC6: 0.7321
PC1-PC7: 0.7930
PC1-PC8: 0.8497
PC1-PC9: 0.8969
PC1-PC10: 0.9357
PC1-PC11: 0.9692
PC1-PC12: 1.0000
from sklearn.model_selection import cross_val_score
from sklearn.ensemble import RandomForestClassifier
# 원본 스케일링된 데이터에 대한 교차 검증
clf_original = RandomForestClassifier(random_state=42)
scores_original = cross_val_score(clf_original, X_scaled, y, cv=5, scoring='roc_auc')
# PCA 적용 데이터에 대한 교차 검증
clf_pca = RandomForestClassifier(random_state=42)
scores_pca = cross_val_score(clf_pca, X_pca_selected, y, cv=5, scoring='roc_auc')
print(f"원본 데이터 평균 ROC-AUC: {scores_original.mean():.4f} (±{scores_original.std():.4f})")
print(f"PCA 데이터 평균 ROC-AUC: {scores_pca.mean():.4f} (±{scores_pca.std():.4f})")
다중공선성 검사
특성간 높은 상관관계 발견(0.7 이상) 되면 PCA가 유용할 수 있음
해당 높은 특성들은 독립적인 주성분으로 변환
from sklearn.preprocessing import StandardScaler
from scipy.stats import spearmanr
import seaborn as sns
# 상관관계 행렬 계산
correlation_matrix = X.corr()
# 상관관계 히트맵 표시
plt.figure(figsize=(12, 10))
sns.heatmap(correlation_matrix, annot=False, cmap='coolwarm', center=0)
plt.title('Feature Correlation Matrix')
plt.tight_layout()
plt.show()
# 높은 상관관계(0.7 이상)를 가진 특성 쌍 찾기
high_corr_pairs = []
for i in range(len(correlation_matrix.columns)):
for j in range(i+1, len(correlation_matrix.columns)):
if abs(correlation_matrix.iloc[i, j]) > 0.7:
high_corr_pairs.append((correlation_matrix.columns[i], correlation_matrix.columns[j], correlation_matrix.iloc[i, j]))
print("높은 상관관계를 가진 특성 쌍:")
for pair in high_corr_pairs:
print(f"{pair[0]} - {pair[1]}: {pair[2]:.4f}")
from imblearn.under_sampling import RandomUnderSampler
SMOTE 클래스 대신 사용하면 됨
오버샘플링
소수 클래수 복제
SMOTE
클래스 가중치
모델이 직접 적용, 데이터가 작아 오버샘플링이 부담스러울 때
class_weight = 'balanced' (Logistic, RF, SVC, GBC 등 지원)
SMOTE 코드
from imblearn.over_sampling import SMOTE
X = df.drop(columns=['Category', 'target'])
y = df['target']
smote = SMOTE(random_state = 42)
X_resampled, y_resampled = smote.fit_resample(X, y)
2.4. 평가지표 metrics
메트릭
설명
사용시점
Precision
Positive 예측 한 것 중 실제 Positive
FP가 중요한 문제(스팸 필터처럼 잘못 걸러내면 안됄 때)
Recall(Sensitivity)
실제 Positive중 Positive로 예측한 비율
FN가 중요힌 문제(암 진단처럼 놓지면 안될 때)
F1-score
Precision과 Recall의 조화 평균
둘 다 중요할 때
AUC-ROC
다양한 threshhold에서 TPR vs FPR 곡선의 아래 면적
전체적인 분류 성능이 중요할 때
PR-AUC
Precision vs Recall 곡선의 면적
불균형이 매우 심하여 Positive가 극소수 일때
메트릭 코드 모음
# 분류모델
from sklearn.metrics import accuracy_score, precision_score,recall_score
from sklearn.metrics import f1_score, confusion_matrix, roc_auc_score, classification_report
# 회귀모델
from sklearn.metrics import mean_squared_error, mean_absolute_error
from sklearn.metrics import r2_score, adjusted_rand_score
# 비지도학습 군집
from sklearn.metrics import silhouette_score
classification_report 읽기
support: 해당 클래스의 샘플 수
macro avg: 일반적인 지표
weight avg: 샘플수 가중치를 더한 지표
ROC Curve
import matplotlib.pyplot as plt
from sklearn.metrics import roc_curve, roc_auc_score
# 모델 학습 (예시)
model = RandomForestClassifier(random_state=42)
model.fit(X_train, y_train)
# 테스트 데이터에 대한 예측 확률
y_pred_proba = model.predict_proba(X_test)[:, 1] # 양성 클래스(1)에 대한 확률
# ROC 커브 계산
fpr, tpr, thresholds = roc_curve(y_test, y_pred_proba)
# AUC 계산
roc_auc = roc_auc_score(y_test, y_pred_proba)
# ROC 커브 그리기
plt.figure()
plt.plot(fpr, tpr, color='blue', label=f'ROC curve (AUC = {roc_auc:.2f})')
plt.plot([0, 1], [0, 1], color='gray', linestyle='--') # 대각선 기준선
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc="lower right")
plt.show()
2.5. 머신러닝 스켈레톤 코드 - 분류
from sklearn.linear_model import LogisticRegression
from sklearn.ensemble import RandomForestClassifier
from sklearn.svm import SVC
from sklearn.model_selection import train_test_split
X = df.drop(columns = ['Kwh8','date','datetime'])
y = df['Kwh8']
X_train, X_test, y_train, y_test = train_test_split(X,y, random_state= 42, stratify= y, test_size= 0.2)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
# 모델 리스트와 이름
models = [
('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
('RandomForest', RandomForestClassifier(random_state=42)),
('SVM', SVC(random_state=42))
]
# 모델 학습 및 평가
for model_name, model in models:
model.fit(X_train, y_train)
# 예측 수행
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
# 성능 평가 (train)
print(f'Train - {model_name} Model')
print(classification_report(y_train, y_pred_train))
# 성능 평가 (test)
print(f'Test - {model_name} Model')
print(classification_report(y_test, y_pred_test))
2.5. 머신러닝 스켈레톤 코드 - 분류(cv)
from sklearn.metrics import classification_report, accuracy_score
from sklearn.model_selection import cross_val_score, KFold, StratifiedKFold
# 데이터 분할
X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=42, stratify=y, test_size=0.3)
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
# 교차 검증 설정
cv = StratifiedKFold(n_splits=5, shuffle=True, random_state=42)
# 모델 리스트와 이름
models = [
('Logistic', LogisticRegression(solver='liblinear')), #sklearn 0.23. 에러 대응
('RandomForest', RandomForestClassifier(random_state=42)),
('SVM', SVC(random_state=42))
]
# 모델 학습 및 평가
for model_name, model in models:
# 교차 검증 수행
cv_scores = cross_val_score(model, X_train, y_train, cv=cv, scoring='accuracy')
print(f'\n{model_name} Cross-Validation:')
print(f'CV Scores: {cv_scores}')
print(f'Mean CV Score: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')
# 전체 훈련 데이터로 모델 학습
model.fit(X_train, y_train)
# 예측 수행
y_pred_train = model.predict(X_train)
y_pred_test = model.predict(X_test)
# 성능 평가 (train)
print(f'\nTrain - {model_name} Model')
print(classification_report(y_train, y_pred_train))
# 성능 평가 (test)
print(f'\nTest - {model_name} Model')
print(classification_report(y_test, y_pred_test))
print('-' * 70)
# 다양한 교차 검증 방법 비교 (선택적)
print("\n다양한 교차 검증 방법 비교 (RandomForest 모델):")
cv_methods = {
'KFold (k=5)': KFold(n_splits=5, shuffle=True, random_state=42),
'StratifiedKFold (k=5)': StratifiedKFold(n_splits=5, shuffle=True, random_state=42),
'StratifiedKFold (k=10)': StratifiedKFold(n_splits=10, shuffle=True, random_state=42)
}
rf_model = RandomForestClassifier(random_state=42)
for cv_name, cv_method in cv_methods.items():
cv_scores = cross_val_score(rf_model, X_train, y_train, cv=cv_method, scoring='accuracy')
print(f'{cv_name}: {cv_scores.mean():.4f} ± {cv_scores.std():.4f}')
2.6. 종속변수에 영향 해석하기
로지스틱 회귀 베타계수 추정
양수는 변수가 증가감에 따라 간염 화률의 증가, 음수는 감소
로즈오즈 해석: 계수가 0.5이면 1단위 증가할 때 간염 발생 로그오즈 0.05 증가
오즈비 해석: exp(계수)값으로 계산하며 1.5이면 변수 1단위 증가할 때 간염 발생 오즈 50% 증가 0.8이면 20% 감소, 1이면 영향 미치지 않음
# 로지스틱 회귀 모델 학습
log_model = LogisticRegression(solver='liblinear')
log_model.fit(X_train, y_train)
# 계수 및 변수명 가져오기
coefficients = log_model.coef_[0]
feature_names = X.columns
# 계수 크기순으로 정렬하여 출력
coef_df = pd.DataFrame({'Feature': feature_names, 'Coefficient': coefficients})
coef_df = coef_df.sort_values('Coefficient', ascending=False)
print("로지스틱 회귀 계수 (양수=간염 위험 증가, 음수=간염 위험 감소):")
print(coef_df)
# 오즈비(Odds Ratio) 계산 - 해석이 더 직관적임
coef_df['Odds_Ratio'] = np.exp(coef_df['Coefficient'])
print("\n오즈비 (1보다 크면 위험 증가, 1보다 작으면 위험 감소):")
print(coef_df[['Feature', 'Odds_Ratio']])
Feature Importance
# Random Forest 모델 학습
rf_model = RandomForestClassifier(random_state=42)
rf_model.fit(X_train, y_train)
# 특성 중요도 추출 및 시각화
importances = rf_model.feature_importances_
indices = np.argsort(importances)[::-1]
feature_importance_df = pd.DataFrame({
'Feature': feature_names[indices],
'Importance': importances[indices]
})
plt.figure(figsize=(10, 6))
sns.barplot(x='Importance', y='Feature', data=feature_importance_df[:10])
plt.title('Top 10 Important Features for Hepatitis Prediction (Random Forest)')
plt.tight_layout()
plt.show()
SHAP value
게임 이론이 기반, 각 특성이 모델 예측에 미치는 기여도를 배분
양수면 해당 특성이 예측 확률을 증가시키는 방향으로 기여, 음수는 감소시키는 방향으로 기여
import tensorflow as tf
import numpy as np
import os
# TensorFlow 1.x 환경에서 Eager Execution을 비활성화
tf.compat.v1.disable_eager_execution()
# 데이터 생성 (이진 분류용)
X_data = np.random.rand(100, 2) # 100개의 샘플, 2개의 특성
y_data = np.random.randint(2, size=(100, 1)) # 100개의 이진 타겟 값 (0 또는 1)
# 모델 정의 (Sequential API)
model = tf.keras.models.Sequential()
model.add(tf.keras.layers.Dense(10, input_dim=2, activation='relu')) # 은닉층
model.add(tf.keras.layers.Dense(1, activation='sigmoid')) # 이진 분류를 위한 sigmoid 출력층
# 모델 컴파일
model.compile(optimizer='adam', loss='binary_crossentropy', metrics=['accuracy'])
# 체크포인트 설정
checkpoint_path = "training_checkpoints/cp.ckpt"
checkpoint_dir = os.path.dirname(checkpoint_path)
# 체크포인트 콜백 설정
cp_callback = tf.keras.callbacks.ModelCheckpoint(filepath=checkpoint_path,
save_weights_only=True,
verbose=1)
# 모델 학습 (체크포인트 콜백 적용)
model.fit(X_data, y_data, epochs=10, batch_size=10, callbacks=[cp_callback])
# 모델 저장 (전체 모델 저장)
model.save('saved_model/my_model')
# 모델 불러오기
new_model = tf.keras.models.load_model('saved_model/my_model')
# 불러온 모델 평가
loss, accuracy = new_model.evaluate(X_data, y_data)
print(f"불러온 모델 정확도: {accuracy:.4f}")
# 체크포인트에서 가중치만 불러오기
new_model.load_weights(checkpoint_path)
# 불러온 체크포인트의 가중치로 다시 평가
loss, accuracy = new_model.evaluate(X_data, y_data)
print(f"체크포인트에서 불러온 모델 정확도: {accuracy:.4f}")
# 분류 예측
predictions = new_model.predict(X_data)
print(f"예측값 (0 또는 1로 변환): \n{(predictions > 0.5).astype(int)}")
3. 머신러닝 - 비지도학습
3.1. Kmeans
import pandas as pd
from sklearn.cluster import KMeans
# KMeans 설정 및 클러스터링 수행
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans.fit(data)
# 클러스터 결과를 데이터프레임에 추가
data['cluster'] = kmeans.labels_
# 결과 확인
print(data.head())
3.2. DBSCAN
import pandas as pd
from sklearn.cluster import DBSCAN
# DBSCAN 설정 및 클러스터링 수행
dbscan = DBSCAN(eps=0.5, min_samples=5)
dbscan.fit(data)
# 클러스터 결과를 데이터프레임에 추가
data['cluster'] = dbscan.labels_
# 결과 확인
print(data.head())
3.3. Elbow method
# 클러스터 수에 따른 inertia (군집 내 거리 합) 계산
inertia = []
K = range(1, 11) # 1~10개의 클러스터를 테스트
for k in K:
kmeans = KMeans(n_clusters=k, random_state=42)
kmeans.fit(data)
inertia.append(kmeans.inertia_)
# Elbow Method 그래프 시각화
plt.figure(figsize=(8, 6))
plt.plot(K, inertia, 'bx-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Inertia')
plt.title('Elbow Method For Optimal k')
plt.show()
3.4. 실루엣 계수
from sklearn.metrics import silhouette_score
# 실루엣 계수를 저장할 리스트
silhouette_avg = []
# 2~10개의 클러스터를 테스트
K = range(2, 11)
for k in K:
kmeans = KMeans(n_clusters=k, random_state=42)
labels = kmeans.fit_predict(data)
silhouette_avg.append(silhouette_score(data, labels))
# 실루엣 계수 시각화
plt.figure(figsize=(8, 6))
plt.plot(K, silhouette_avg, 'bx-')
plt.xlabel('Number of clusters (k)')
plt.ylabel('Silhouette Score')
plt.title('Silhouette Score For Optimal k')
plt.show()
지난장 배치에서는 유닉스의 배치처리 방식과 맵리듀스를 기반으로 한 분산파일 시스템에대해서 알아보았습니다. 많이 혼란스럽고 어려운 장이였는데요, 이번 장에서는 스트림 처리에 대해서 알아봅니다. 배치를 기반으로 하는 설명이 나온다고 해서 많이 어려울 줄 알았는데 일부 읽을만한 내용들이 있어서 재밌었습니다.
목차
신규개념(New)
개념
설명
스트림(stream)
시간 흐름에 따라 점진적으로 생산된 데이터
레코드(record)
이벤트, 특정 시점에 일어난 사건에 대한 세부사항을 포함하는 작고 독립된 불변 객체
생산자(producer)
publisher, sender
소비자(consumer)
subscrier, recipient
트리거(trigger)
테이블에 대한 이벤트에 반응해 자동으로 실행되는 작업
메시징 시스템(Messaging system)
로그 데이터, 이벤트 메시지 등 API로 호출할 때 보내는 데이터를 처리하는 시스템
윈도우(window)
집계하는 시간의 간격
1. 배치와 스트림 처리의 차이
배치
스트림
정의
입력으로 파일 집합을 읽어 출력으로 새로운 파일 집합을 생성하는 기술
연속적으로 발생하는 데이터를 실시간 혹은 준 실시간으로 처리하는 기술
활용예시
검색 색인, 추천시스템, 분석
실시간 로그 분석, 거래 모니터링, 실시간 알림 시스템 등
출력
파생 데이터(요약 데이터ㅡ 결과 리포트 등)
실시간으로 처리된 이벤트 결과(알림 등)
특징
끝이 있음(작업 단위가 명확)
영원히 끝나지 않음(데이터 흐름이 지속적으로 발생)
2. 메시징 시스템
2.1 메시징 시스템 기본
새로운 이벤트에 대해서 소비자에게 알려주고 쓰이는 일반적인 방법은 메시징 시스템(messaging system) 이다. 메시징 시스템을 이해하기 위해서 다음 블로그에서 글 발췌했다.
ADP 시험을 준비하기 위하여 정리했던 방법론과 코드를 저장합니다.빅분기는 범위가 더 좁지만 공통된 부분이 많습니다. 선형계획법이나 기타 내용은 저도 몰라서 못넣었습니다 🤣 잘못된 내용이나 모호한 내용, 추가되어야하는 내용이 있으면 지적해주세요! 다들 시험에 합격하시길 바라요~
# 일반모듈
import warnings
warnings.filterwarnings('ignore')
import pandas as pd
import numpy as np
import os
import datetime as dt
# 머신러닝 - 전처리
from sklearn.preprocessing import StandardScaler, MinMaxScaler,
LabelEncoder, OneHotEncoder, RobustScaler
from sklearn.impute import SimpleImputer, KNNImputer
from imblearn.over_sampling import SMOTE, RandomOverSampler
from imblearn.under_sampling import RandomUnderSampler
from sklearn.model_selection import train_test_split, GridSearchCV, cross_val_score,
KFold, StratifiedKFold
# 머신러닝 - 모델
from sklearn.linear_model import LogisticRegression, RidgeClassifier, LinearRegression,
Lasso, Ridge, ElasticNet
from sklearn.ensemble import RandomForestClassifier, GradientBoostingClassifier,
RandomForestRegressor, GradientBoostingRegressor
from sklearn.ensemble import VotingClassifier
from sklearn.svm import SVC, SVR
from sklearn.neighbors import KNeighborsClassifier, KNeighborsRegressor
from sklearn.tree import DecisionTreeClassifier, DecisionTreeRegressor
# 머신러닝 - 비지도
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.decomposition import PCA
# 머신러닝 - 평가
from sklearn.metrics import (classification_report, confusion_matrix,
roc_auc_score, precision_recall_curve, auc,
mean_squared_error, mean_absolute_error, r2_score, accuracy_score,
precision_score, recall_score, f1_score)
from sklearn.metrics.pairwise import cosine_similarity
# 시각화
import matplotlib.pyplot as plt
import seaborn as sns
# 통계 - scipy
import scipy.stats as stats
from scipy.stats import ttest_ind, ttest_rel, f_oneway, chi2_contingency,
mannwhitneyu, wilcoxon, pearsonr, spearmanr, normaltest
# 통계 - statsmodels
import statsmodels.api as sm
import statsmodels.formula.api as smf
from statsmodels.stats.multicomp import pairwise_tukeyhsd
from statsmodels.stats.outliers_influence import variance_inflation_factor
from statsmodels.stats.diagnostic import het_breuschpagan
# 시계열
from statsmodels.tsa.stattools import adfuller, acf, pacf, kpss
from statsmodels.tsa.seasonal import seasonal_decompose
from statsmodels.tsa.arima.model import ARIMA
from statsmodels.tsa.holtwinters import ExponentialSmoothing, SimpleExpSmoothing
from statsmodels.tsa.statespace.sarimax import SARIMAX
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# pmdarima (자동 ARIMA)
from pmdarima import auto_arima
한글폰트 설정(선택사항)
import matplotlib.pyplot as plt
import platform
def set_matplotlib_font():
system = platform.system()
if system == "Windows":
plt.rc('font', family='Malgun Gothic')
elif system == "Darwin": # macOS
plt.rc('font', family='AppleGothic')
elif system == "Linux":
plt.rc('font', family='NanumGothic')
else:
print("Unknown system. Please set font manually.")
plt.rcParams['axes.unicode_minus'] = False
# 폰트 설정 함수 호출
set_matplotlib_font()
1.1. datetime
datetime
│
├── datetime # 날짜와 시간을 모두 처리하는 클래스
│ ├── now() # 현재 날짜와 시간을 반환
│ ├── strptime() # 문자열을 datetime 객체로 변환
│ ├── strftime() # datetime 객체를 문자열로 변환
│ ├── year,month, day # 년도, 월, 일 추출
│ ├── hour,minute, second # 시간, 분, 초 추출
│ ├── date(), time() # 날짜 (년, 월, 일)만, 시간 (시, 분, 초)만 반환
│ ├── replace() # 특정 날짜/시간 값을 변경하여 새로운 datetime 객체 반환
│ ├── weekday() # 요일 반환 (월: 0, 일: 6)
│ └── combine() # 날짜와 시간을 결합하여 새로운 datetime 객체 생성
│
├── date # 날짜만 처리하는 클래스
│ ├── today() # 오늘 날짜 반환
│ ├── fromisoformat() # ISO 형식의 문자열에서 date 객체 생성
│ ├── year,month, day # 년도, 월, 일 추출
│
├── time # 시간만 처리하는 클래스
│ ├── hour,minute, second # 시간, 분, 초 추출
│ └── microsecond # 마이크로초 추출
│
├── timedelta # 두 날짜 또는 시간 간의 차이를 계산하는 클래스
│ ├── days # 차이 나는 일수
│ ├── seconds # 차이 나는 초
│ └── total_seconds() # 총 차이 시간을 초 단위로 반환
│
└── timezone # 시간대 정보를 다루는 클래스
├── utc # UTC 시간대 객체
└── tzinfo # 사용자 정의 시간대 설정을 위한 클래스
1.2. numpy==1.21.6
numpy
│
├── 기본 통계 함수
│ ├── mean() # 데이터의 평균값 계산
│ ├── median() # 데이터의 중앙값 계산
│ ├── std() # 데이터의 표준편차 계산
│ ├── var() # 데이터의 분산 계산
│ ├── sum() # 데이터의 합계 계산
│ ├── prod() # 데이터의 곱 계산
│
├── 퍼센타일 및 백분위 함수
│ ├── percentile() # 데이터의 특정 퍼센타일 값 계산
│ ├── quantile() # 데이터의 특정 분위 값 계산
│
├── 최소값/최대값 관련 함수
│ ├── min() # 데이터의 최소값 반환
│ ├── max() # 데이터의 최대값 반환
│ ├── argmin() # 최소값의 인덱스 반환
│ ├── argmax() # 최대값의 인덱스 반환
│
├── 데이터 생성 및 처리 함수
│ ├── histogram() # 데이터의 히스토그램 계산
│ ├── unique() # 데이터에서 고유 값 반환
│ ├── bincount() # 정수 배열의 값의 빈도 계산
│
├── 랜덤 데이터 생성 (통계적 실험 시 사용 가능)
│ ├── random.randn() # 표준 정규분포를 따르는 랜덤 값 생성
│ ├── random.normal() # 정규분포를 따르는 랜덤 값 생성
│ ├── random.randint() # 정수 범위에서 랜덤 값 생성
│ ├── random.choice() # 데이터에서 랜덤 샘플 추출
1.3. scipy==1.7.3
scipy
│
├── stats # 통계 분석과 확률 분포 관련 함수 제공
│ ├── norm # 정규분포 관련 함수 (PDF, CDF, 랜덤 샘플링 등)
| |── uniform # 균등분포
| |── bernoulli # 베르누이 분포
| |── binom # 이항분포
│ ├── ttest_ind # 독립 두 표본에 대한 t-검정
│ ├── ttest_rel # 대응표본 t-검정
│ ├── mannwhitneyu # Mann-Whitney U 비모수 검정
│ ├── chi2_contingency # 카이제곱 독립성 검정
│ ├── shapiro # Shapiro-Wilk 정규성 검정
│ ├── kstest # Kolmogorov-Smirnov 검정 (분포 적합성 검정)
│ ├── probplot # Q-Q plot 생성 (정규성 시각화)
│ ├── pearsonr # Pearson 상관계수 계산
│ ├── spearmanr # Spearman 순위 상관계수 계산
│ └── describe # 기술 통계량 제공 (평균, 표준편차 등)
│
├── optimize # 함수 최적화 및 곡선 피팅 관련 모듈
│ ├── minimize # 다변수 함수의 최소값을 찾는 최적화 도구
│ ├── curve_fit # 비선형 곡선 피팅 (최소제곱법 기반)
│ └── root # 방정식의 근을 찾는 도구
│
├── linalg # 선형대수 관련 함수
│ ├── inv # 행렬의 역행렬 계산
│ ├── det # 행렬식 계산
│ └── svd # 특이값 분해(SVD) 수행
│
├── interpolate # 데이터 보간 관련 함수
│ ├── interp1d # 1차원 선형 보간 함수
│ └── griddata # 다차원 보간 수행 (비정형 데이터에 사용)
│
└── special # 특수 함수 (감마 함수, 베타 함수 등)
├── gamma # 감마 함수
└── beta # 베타 함수
pandas
│
├── melt: pivot 형태를 다시바꾸기
│
├── DataFrame # 2차원 데이터 구조, 테이블 형태의 데이터 관리
│ ├── groupby # 데이터프레임 그룹핑
│ ├── merge # SQL 스타일로 데이터프레임 병합
│ ├── join # 데이터프레임 조인 (인덱스를 기준)
│ ├── pivot_table # 피벗 테이블 생성
│ ├── apply # 사용자 정의 함수를 데이터프레임에 적용
│ ├── isnull # 결측치 여부 확인
│ ├── fillna # 결측치 대체
│ └── drop # 행 또는 열 삭제
│
├── Series # 1차원 데이터 구조, 배열 형태의 데이터 관리
│ ├── value_counts # 고유 값의 빈도수 반환
│ └── unique # 고유 값 반환
│
├── time_series # 시계열 데이터 처리 도구
│ ├── to_datetime # 문자열을 날짜 형식으로 변환
│ ├── resample # 시계열 데이터 리샘플링
│ ├── shift # 데이터를 앞이나 뒤로 이동
│ └── rolling # 이동 평균 등 롤링 윈도우 계산
│
└── plotting # 데이터 시각화 도구
├── plot # 라인 플롯, 기본 시각화 함수
├── hist # 히스토그램 생성
├── boxplot # 박스 플롯 생성
└── scatter # 산점도 생성
1.5. statsmodel==0.13.2
statsmodels
│
├── api # 주요 모듈에 접근하기 위한 API 제공
│
├── stats # 통계적 테스트 및 유틸리티
│ ├── diagnostic # 모델 진단 도구 (자기상관, 이분산성 등)
│ ├── stattools # 통계 도구 (자기상관 함수, 부분자기상관 함수 등)
│ ├── multivariate # 다변량 통계 분석
│ ├── anova # 분산 분석(ANOVA) 관련 함수
│ │ ├── anova_lm # 선형 모델에 대한 ANOVA 테이블
│ │ └── multicomp # 다중 비교 방법
│ └── nonparametric # 비모수 통계 검정 (KDE, 커널 회귀 등)
│
├── duration # 생존 분석(Survival Analysis) 관련 모듈
│ ├── hazard_regression # 위험 회귀 모델
│ │ ├── cox # Cox 비례 위험 모델
│ │ └── phreg # 비례 위험 회귀 모델
│ ├── survfunc # 생존 함수 추정 (Kaplan-Meier 등)
│ │ ├── kaplan_meier # 카플란-마이어 추정량
│
├── tsa # 시계열 분석(Time Series Analysis) 관련 모듈
│ ├── arima # ARIMA 모델 구현
│ ├── statespace # 상태 공간 모델 (State Space Models)
│ ├── vector_ar # 벡터 자기회귀(VAR) 모델
│ ├── seasonal # 계절성 분해 (SARIMAX 등)
│ └── filters # 칼만 필터, HP 필터 등
from statsmodels.stats.proportion import proportion_confint
# 성공 횟수와 전체 시행 횟수
x = np.sum(data) # 성공 횟수
n = len(data) # 전체 시행 횟수
lower_bound, upper_bound = proportion_confint(success_count, n, alpha=alpha, method='normal')
print(f"{confidence_level*100}% 신뢰구간: ({lower_bound}, {upper_bound})")
이표본 t검정일 때 정규성 & 등분산 검정을 진행하게 되는데, 등분산일 경우 두 표본의 분산을 하나로 합쳐서 추정하는 "합동 분산"활용이 가능해짐. 이는 검정통계량의 분산을 하나로 합치게 되고 좀 더 정확한 추정이 가능하게함. 하지만 등분산이 만족되지 않은 경우 비모수 검정인 맨-휘트니를 진행함
# 독립표본 t 검정
from scipy.stats import ttest_ind,ttest_rel
# 등분산성을 만족하는 경우
statistic, p_value = ttest_ind(data1, data2, equal_var=True)
# 등분산성을 만족하지 않는 경우
# statistic, p_value = ttest_ind(data1, data2, equal_var=False)
#대응표본 t검정
# statistic, p_value = ttest_rel(data_before, data_after)
print(statistic, p_value)
분산 분석
목적: 그룹간 분산과그룹내 분산을 비교
$H_0:$ 모든 그룹의 평균값이 같다.
$H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
import pandas as pd
import numpy as np
# 그룹별 데이터 생성 (예시 데이터)
np.random.seed(123)
group_A = np.random.normal(loc=50, scale=5, size=30)
group_B = np.random.normal(loc=55, scale=5, size=30)
group_C = np.random.normal(loc=60, scale=5, size=30)
# 데이터 프레임 생성
df = pd.DataFrame({
'score': np.concatenate([group_A, group_B, group_C]),
'group': ['A'] * 30 + ['B'] * 30 + ['C'] * 30
})
import statsmodels.api as sm
from statsmodels.formula.api import ols
# ANOVA 모델 생성
model = ols('score ~ C(group)', data=df).fit()
# 분산분석표 생성
anova_table = sm.stats.anova_lm(model, typ=2)
# 결과 출력
print(anova_table)
import statsmodels.api as sm
from statsmodels.formula.api import ols
# 이원분산분석 모델 생성 (교호작용 포함)
model = ols('값 ~ C(요인1) * C(요인2)', data=df).fit()
# 분산분석표 생성
anova_table = sm.stats.anova_lm(model, typ=2)
# 결과 출력
print(anova_table)
Welch Anova
목적: 그룹 간의 등분산성이 가정이 필요없는 경우 사용
$H_0:$ 모든 그룹의 평균이 같다.
$H_1:$ 적어도 하나 그룹의 평균값이 다른 그룹과 다르다.
import numpy as np
import pandas as pd
import scipy.stats as stats
import statsmodels.api as sm
from statsmodels.formula.api import ols
# 기술 통계량 확인
print(data.groupby('group').agg(['mean', 'std', 'count']))
# 등분산성 검정 (Levene's test)
levene_stat, levene_p = stats.levene(group1, group2, group3)
print(f"\nLevene's test: statistic={levene_stat:.4f}, p-value={levene_p:.4f}")
if levene_p < 0.05:
print("The variances are significantly different. Welch ANOVA is appropriate.")
else:
print("The variances are not significantly different. Standard ANOVA can be used.")
# Welch ANOVA 수행
welch_stat, welch_p = stats.f_oneway(group1, group2, group3, welch=True)
print(f"\nWelch ANOVA: F-statistic={welch_stat:.4f}, p-value={welch_p:.4f}")
if welch_p < 0.05:
print("Reject null hypothesis: At least one group mean is different.")
else:
print("Fail to reject null hypothesis: No significant difference between group means.")
# 사후 검정 (Games-Howell post-hoc test)
from statsmodels.stats.multicomp import pairwise_tukeyhsd
# 참고: statsmodels에는 Games-Howell 검정이 직접 구현되어 있지 않아 Tukey HSD를 대신 사용
# 실제 연구에서는 Games-Howell 테스트 사용 권장 (등분산 가정 필요 없음)
tukey_results = pairwise_tukeyhsd(data['value'], data['group'], alpha=0.05)
print("\nPost-hoc test (Tukey HSD, note: Games-Howell would be better for unequal variances):")
print(tukey_results)
4. 통계검정 - 비모수적 방법
4.1. 비모수방법 표 정리
데이터가 특정 분포를 따르지 않거나 데이터 양이 적어 분포가정을 만족시키기 어려울때 사용. 데이터의 순위나 중앙값을 사용
from scipy.stats import kruskal
# 예시 데이터 (세 그룹의 독립 표본)
group1 = [10, 12, 14, 16, 18]
group2 = [15, 17, 19, 21, 23]
group3 = [24, 26, 28, 30, 32]
# Kruskal-Wallis H 검정
stat, p_value = kruskal(group1, group2, group3)
4.6. 맥니마 검정
목적: 이항 데이터에서 전후 또는 대응된 두 집단의 비율 변화
가정: 데이터는 binary, 대응 표본 존재, 표본크기 충분히 커야함
$H_0$: 두 대응된 이항 분포간 변화 없음
$H_1$: 두 대응된 이항 분포간 변화 있음
import numpy as np
from statsmodels.stats.contingency_tables import mcnemar
# 2x2 분할표 생성 (a, b, c, d)
# 예시 데이터: (성공/실패 전후)
# [[성공 전/성공 후, 성공 전/실패 후],
# [실패 전/성공 후, 실패 전/실패 후]]
table = np.array([[40, 10], # 성공 전/후
[5, 45]]) # 실패 전/후
# 맥니마 검정 수행
result = mcnemar(table, exact=True) # exact=True: 정확한 검정 수행 (표본 크기가 작을 경우)
# 결과 출력
print('맥니마 검정 통계량:', result.statistic)
print('p-value:', result.pvalue)
4.7. 피셔의 정확검정
목적: 작은 표본에서 두 범주형 변수간의 독립성의 검정
가정: 각셀 기대빈도(N≤5) 이항 분포 또는 범주형 자료료 표현됨, 행과 열의 합은 고정
$H_0$: 두 변수는 독립적이다.
$H_1$: 두 변수는 독립적이지 않다.
import numpy as np
from scipy import stats
# 2x2 분할표 데이터 (예시)
# [[그룹 A, 그룹 B],
# [그룹 A, 그룹 B]]
table = np.array([[8, 2], # 결과 X
[1, 5]]) # 결과 Y
# 피셔의 정확검정 수행
odds_ratio, p_value = stats.fisher_exact(table, alternative='two-sided')
# 결과 출력
print('오즈비:', odds_ratio)
print('p-value:', p_value)
4.8. 프리드먼 검정
목적: 반복 측정된 세개 그룹 간의 차이를 비교하는 검정
가정: 연속형 or 순위형 데이처 사용, 각 그룹은 동일한 주제/대상에 여러번 측정된 결과
$H_0$ : 세 개 이상의 관련된 그룹 간의 차이가 없다.
$H_1$: 적어도 하나의 그룹에서 다른 그룹과 유의미한 차이가 있다.
import numpy as np
from scipy.stats import friedmanchisquare
# 예시 데이터: 세 개의 처리에 대한 측정값
# 동일한 대상(피험자)에 대해 세 번의 처리를 반복적으로 측정한 값
# 행은 피험자, 열은 각각의 처리
data1 = [10, 20, 30, 40] # 처리 1
data2 = [12, 21, 29, 35] # 처리 2
data3 = [14, 22, 27, 37] # 처리 3
# 프리드먼 검정 수행
statistic, p_value = friedmanchisquare(data1, data2, data3)
# 결과 출력
print('프리드먼 검정 통계량:', statistic)
print('p-value:', p_value)
4.9. 부호검정
목적: 관련된 두 그룹 간의 차이를 평가하는 검정, 중앙 값의 차이를 평가
가정: 대응된 표본
$H_0:$ 두 그룹간 중앙값 차이가 없다.
$H_1$ : 두 그룹간 중앙값 차이가 있다.
import numpy as np
from scipy import stats
# 예시 데이터: 두 그룹에 대한 대응된 표본 (처치 전후 데이터)
before = [85, 70, 90, 80, 65, 75, 95, 70] # 처리 전
after = [80, 65, 88, 82, 63, 78, 92, 72] # 처리 후
# 부호검정 수행
result = stats.binom_test((before > after).sum(), n=len(before), p=0.5, alternative='two-sided')
# 결과 출력
print('p-value:', result)
4.10. 코크란 q검정
목적: 세개 이상의 이항 처리에 대한 대응 표본의 비율의 차이를 비교하기 위한 검정
가정: 데이터는 binary, 대응된 표본
$H_0$: 세 개 이상의 처리 간에 차이가 없다.
$H_1$ : 적어도 하나의 처리에서는 다른 처리와 유의미한 차이가 있다.
import numpy as np
import pandas as pd
from statsmodels.sandbox.stats.runs import cochrans_q
# 예시 데이터: 세 가지 처리에 대한 이항형 결과 (성공/실패)
# 각 행은 개별 피험자, 각 열은 다른 처리
data = pd.DataFrame({
'treatment1': [1, 0, 1, 1, 0], # 처리 1 결과 (성공: 1, 실패: 0)
'treatment2': [0, 1, 1, 1, 0], # 처리 2 결과
'treatment3': [1, 0, 1, 0, 1], # 처리 3 결과
})
# 코크란 Q 검정 수행
statistic, p_value = cochrans_q(data)
# 결과 출력
print('코크란 Q 검정 통계량:', statistic)
print('p-value:', p_value)
import statsmodels.stats.api as sms
import statsmodel.api as sm
model = sm.OLS(y,X).fit
static , pvalue, f_static , f_pv = sms.het_breuschpagan(model.resid,model.model.exog)
정규성 가정: 잔차항이 정규분포를 따름
QQ플랏
sharpiro-wilk 검정
$H_0:$ 정규성을 따른다.
$H_1:$ 정규성을 따르지 않는다.
from scipy.stats import shapiro
static, pvalue = shapiro(model.resid)
전체 코드
# 1. 잔차 vs 예측값 : 잔차의 등분산성 가정
# 2. 잔차의 히스토그램: 잔차의 정규성 가정
# 3. QQPLOT: 잔차의 정규성 가정
# 4. 다중공선성: 독립변수의 독립성 가정
# 5. 더빈왓슨 검정: 잔차의 독립성 가정
import statsmodels.api as sm
import matplotlib.pyplot as plt
import seaborn as sns
from statsmodels.stats.outliers_influence import variance_inflation_factor as VIF
def review_lr(X,y, add_const = True):
if add_const:
X = sm.add_constant(X)
model = sm.OLS(y, X).fit()
else:
model = sm.OLS(y, X).fit()
residuals = model.resid
fitted_values = model.fittedvalues
plt.figure(figsize = (10,6))
#1. 잔차 vs 예측값
plt.subplot(2,2,1)
sns.residplot(x = fitted_values, y = residuals, lowess = True)
plt.title('Residual vs Fitted')
# 2. 잔차의 히스토그램
plt.subplot(2,2,2)
sns.histplot(residuals, kde = True)
plt.title('Residuals Distribution')
# 3. 잔차의 QQplot
plt.subplot(2,2,3)
sm.qqplot(residuals, line = '45', fit = True, ax=plt.gca())
plt.title('QQ plot)')
# 4. 다중공선성
plt.subplot(2,2,4)
vif_data = pd.DataFrame()
vif_data['VIF'] = [VIF(X.values,i) for i in range(X.shape[1])]
vif_data['Feature'] = X.columns
sns.barplot(x = 'VIF', y = 'Feature', data = vif_data)
plt.title('VIF')
plt.show()
# 5. 잔차의 독립성 검정: 더빈왓슨검정
# -> 기본적으로 sm.OLS(Y,X).summary()에서 제공
dw_stat = sm.stats.durbin_watson(residuals)
print(f"Durbin-Watson statistic: {dw_stat:.3f}")
'''
0에 가까우면 잔차의 양의 자기 상관성
2에 가까우면 잔차의 독립성 가정 만족
4에 가까우면 잔차가 음의 자기 상관성을 가짐
'''
#6. 잔차의 정규성 검정: # Shapiro-Wilk 검정
from scipy import stats
residuals = model.resid
shapiro_test = stats.shapiro(residuals)
print(f"Shapiro-Wilk Test Statistic: {shapiro_test.statistic:3f}")
print(f"p-value: {shapiro_test.pvalue:3f}")
# 결과 해석
if shapiro_test.pvalue > 0.05:
print("p-value가 0.05보다 큼: 잔차가 정규분포를 따를 가능성이 높음 (귀무가설을 기각할 수 없음).")
else:
print("p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).")
return model.summary()
y = df['MEDV']
X = df.drop(columns=['MEDV'])
review_lr(X,y)
Durbin-Watson statistic: 1.246
Shapiro-Wilk Test Statistic: 0.917579
p-value: 0.000000
p-value가 0.05보다 작음: 잔차가 정규분포를 따르지 않을 가능성이 높음 (귀무가설 기각).
선형회귀 다중공선성 관리 방법
상관관계가 0.7 이상인 것 기준
도메인지식을 바탕으로 중요하다고 생각되는 변수를 유지
다중공선성이 높은 변수들이 많다면 차원 축소 진행
corr_df = df.drop(columns =['MEDV']).corr().unstack().reset_index()
corr_df.columns =['col1','col2','corr']
corr_df =corr_df[corr_df['col1'] != corr_df['col2']]
.sort_values('corr').reset_index(drop=True)[::2].reset_index(drop=True)
display(corr_df)
print('''
강한 양의 상관관계 및 강한 음의 상관관계를 가지는 컬럼들이 다수 존재한다. 다중공선성이 발생할 여지가 크기에 이를 해결하기 위해 두가지 접근을 진행한다.
1. vif값을 기준으로 변수를 제거하는 방식
2. pca를 이용해서 변수간 선형관계를 줄인 축소된 차원으로 분석을 진행
''')
VIF
$VIF_i = \frac{1}{1 - R_i^2}$
$R^{2}_{i}$: VIF를 측정하고 싶은 독립변수를 종속변수로 놓고, 나머지 독립변수를 이용해 회귀분석을 진행한 결정계수
즉 결정계수가 90%이 된다는 것은 VIF가 10이 된다는 것
from statsmodels.stats.outliers_influence import variance_inflation_factor
df_vif = df.drop(columns =['MEDV'])
vif = pd.DataFrame()
vif['VIF Factor'] = [variance_inflation_factor(df_vif.values,i) for i in range(df_vif.shape[1])]
vif['features'] = df_vif.columns
display(vif)
방법1) 변수 제거
방법2) 차원축소(PCA)
거리기반의 알고리즘이기 때문에 정규화 필요
전체 변수를 넣어도, 부분변수를 넣어도되나, 전자의 경우 해석력이 많이 손상됌
from sklearn.decomposition import PCA
from sklearn.preprocessing import StandardScaler
sc =StandardScaler()
scaled_df = sc.fit_transform(df.drop(columns =['MEDV']))
pca = PCA(n_components=7)
pca_df = pca.fit_transform(scaled_df)
X = pca_df
y = df['MEDV']
X = sm.add_constant(X)
model = sm.OLS(y,X).fit()
display(model.summary())
방법3) Lidge/Lasso 회귀
Lasso(L1, 절대값):
변수를 선택하고 싶은 경우
일부 변수만 중요하고, 나머지는 무시해도 되는 상황
희소한 데이터셋에서 불필요한 변수를 제거하여 모델의 해석력을 높이고 싶은 경우
Ridge(L2, 제곱):
모든 변수가 모델에 중요할 때
다중공선성 문제를 해결하고 과적합을 방지하고 싶은 경우
변수 선택보다는 모델 성능 향상이 더 중요한 경우
6. 시계열 검정
정상성 검정
import pandas as pd
import numpy as np
from statsmodels.tsa.stattools import adfuller
# ADF 검정 수행
result = adfuller(data)
print('ADF Statistic:', result[0])
print('p-value:', result[1])
from statsmodels.tsa.seasonal import seasonal_decompose
# 시계열 분해 (Additive 모델)
# period는 데이터의 계절성 주기 설정 (예: 월별 데이터의 경우 12)
decomposition = seasonal_decompose(data, model='additive', period=12)
# 분해 결과 시각화
decomposition.plot()
plt.show()
지수 평활 모형
from statsmodels.tsa.holtwinters import ExponentialSmoothing
# 단순 지수 평활 모델 적합
model = ExponentialSmoothing(data, trend=None, seasonal=None)
model_fit = model.fit(smoothing_level=0.8, optimized=False)
# 예측값 생성
fitted_values = model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(fitted_values, label='예측값', color='red')
plt.legend()
plt.show()
6.1. 자기회귀(AR) 모형
현재 값이 과거 값들의 선형 조합으로 표현될 수 있다고 가정
자기상관에 강한 시계열과 추세나 패턴이 지속되는 경향이 있을때 유용(Ex 주식, 기온 데이터)
시차가 2라면 $y_t$와 $y_{t-2}$의 두 시차를 고려하며 중간 시차 $y_{t-1}$이 영향을 제거
첫 번째 선은 차수 1(lag1)
다음 그래프를 보고 AR(2) 결정
파란선: 유의성 경계
6.3. MA 모형
현재 값이 과거의 오차항(예측 오류)의 선형 조합으로 표현된다고 가정
단기적인 충격이나 이벤트의 반응을 모델링 하는데 유용(주가 실적 발표)
PACF: 점진적으로 감소, ACF: q 시차 이후 급격히 0에 가까워짐
# MA(q) 모델 - ARMA로 구현
from statsmodels.tsa.arima.model import ARIMA
model_ma = ARIMA(data, order=(0,0,q)).fit()
forecast = model_ma.forecast(steps=10)
6.4. ACF(Auto Correlation Function)
자기 상관 함수는 각 시차(lag)에 대해 현재 값과 과거 값의 상관관계를 계산, 시차 q를 결정
첫 번째 세로 선은 차수 0으로 자기와의 상관관계(항상 1.0)
다음은 MA(3)이 결정되는 ACF 그림
ACF, PACF 그리기
import matplotlib.pyplot as plt
from statsmodels.graphics.tsaplots import plot_acf, plot_pacf
# 한 그림에 두 플롯 함께 표시하기
fig, axes = plt.subplots(2, 1, figsize=(12, 8))
plot_acf(data, lags=40, alpha=0.05, ax=axes[0])
axes[0].set_title('ACF')
plot_pacf(data, lags=40, alpha=0.05, ax=axes[1])
axes[1].set_title('PACF')
plt.tight_layout()
plt.show()
6.5. ARIMA
ARIMA
from statsmodels.tsa.arima.model import ARIMA
import matplotlib.pyplot as plt
# ARIMA 모델 적합 (p=1, d=1, q=1 파라미터로 예시)
model = ARIMA(data, order=(1, 1, 1))
model_fit = model.fit()
# 예측값 생성
fitted_values = model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(fitted_values, label='ARIMA 예측값', color='red')
plt.legend()
plt.title('ARIMA 모델 적합 결과')
plt.show()
# 모델 요약 출력 (선택사항)
print(model_fit.summary())
SARIMA
from statsmodels.tsa.statespace.sarimax import SARIMAX
# SARIMA 모델 적합 (p, d, q, P, D, Q, m 설정)
p = 1 # AR 차수
d = 1 # 차분 차수
q = 1 # MA 차수
P = 1 # 계절 AR 차수
D = 1 # 계절 차분 차수
Q = 1 # 계절 MA 차수
m = 12 # 계절 주기 (예: 월별 데이터의 경우 12)
model = SARIMAX(data, order=(p, d, q), seasonal_order=(P, D, Q, m))
model_fit = model.fit()
print(model_fit.summary())
# 예측
forecast = model_fit.forecast(steps=12)
print(forecast)
import pmdarima as pm
from statsmodels.tsa.arima.model import ARIMA
data = df.copy()
# 자동으로 최적의 ARIMA 모델 찾기
auto_model = pm.auto_arima(data,
start_p=0, start_q=0,
max_p=5, max_q=5,
d=None, max_d=2,
seasonal=False,
trace=True,
error_action='ignore',
suppress_warnings=True,
stepwise=True)
print(f"최적의 ARIMA 모델 파라미터: {auto_model.order}")
# 최적 모델 적합
best_model = ARIMA(data, order=auto_model.order)
best_model_fit = best_model.fit()
# 예측값 생성
best_fitted_values = best_model_fit.fittedvalues
# 원본 데이터와 예측값 비교
plt.figure(figsize=(12, 6))
plt.plot(data, label='원본 데이터')
plt.plot(best_fitted_values, label=f'최적 ARIMA{auto_model.order} 예측값', color='red')
plt.legend()
plt.title('optimization result')
#진단코드
model.plot_diagnostics(figsize=(12, 8))
plt.tight_layout()
plt.show()
VAR 모형
from statsmodels.tsa.vector_ar.var_model import VAR
# 데이터 분할 (학습 및 테스트 데이터)
train_data = df[:-10]
test_data = df[-10:]
# VAR 모델 적합
model = VAR(train_data)
model_fit = model.fit(maxlags=15, ic='aic')
print(model_fit.summary())
# 예측
lag_order = model_fit.k_ar
forecast_input = train_data.values[-lag_order:]
forecast = model_fit.forecast(y=forecast_input, steps=10)
# 예측 결과를 데이터프레임으로 변환
forecast_df = pd.DataFrame(forecast, index=test_data.index, columns=df.columns)
print(forecast_df)
이번 장에는 batch processing이라고 불리우는 일괄처리 방법에 대해서 기술한다. 분산 저장 시스템에서 함께 등장하는 맵리듀스 방식과 과거 유닉스 방식의 유사점을 비교하면서 설명하는 장이다.
신규개념
개념
설명
데몬 프로세스
멀티태스킹 운영 체재에서 사용자가 직접 제어하지 않고, 백그라운드에서 작업을 하는 프로그램
NAS
Network Attached Storage, 여러 사용자가 TCP/IP 네트워크로 파일을 저장하고 공유할 수 있는 중앙 집중식 파일 서버
SAN
Storage Area Network, 서버, 스토리지 시스템, 소프트웨어 및 서비스를 결합하여 특정 환경에 맞게 조정된 전용 네트워크
RAID
여러개의 디스크를 묶어 하나의 디스크처럼 사용하는 기술
맵리듀스 워크플로
Work Flow, 맵 리듀스 작업을 관리하는 워크 플로우 시스템
1. 요청응답 방식의 시스템 3가지
서비스(온라인 시스템)
서비스는 클라이언트로부터 요청이나 지시가 올 때까지 기다린다. 요청이 들어오면 가능한 빨리 요청을 처리하며 응답시간이 그 지표가된다.
일괄 처리 시스템(오프라인 시스템)
매우 큰 입력 데이터를 받아 처리하는 작업을 수행하고 결과 데이터를 생산한다. 하루에 한번 수행과 같이 반복적인 일정으로 수행하며, 성능지표는 처리량이다.
스트림 처리 시스템(준실시간 시스템)
스트림 처리는 입력 이벤트가 발생한 후 바로 작동한다. 일괄 처리 시스템보다는 지연시간이 낮지만 기반은 같다.
2. 일괄 처리 시스템의 예시
맵 리듀스는 검색 엔진에서 사용할 색인을 위하여 처음 사용됨. 색인은 용어 사전 파일로 효율적으로 특정 키워드를 조회해 키워드가 포함된 문서 ID의 모록을 찾아 검색 결과를 관련성 순으로 순위를 매기는 등의 일을 함. 머신러닝 시스템(스팸 필터, 이상 검출, 이미지 인식)을 구축하거나 추천시스템이 그 예
3. 유닉스의 철학과 단점
1. 각 프로그램이 한가지 일만 하도록 작성하라. 2. 모든 프로그램의 출력은 아직 알려지지 않은 다른 프로그램의 입력으로 쓰일 수 있다고 생각하라. 3. 소프트웨어를 빠르게 써볼 수 있게하고 구축하라. 4. 프로그래밍 작업을 줄이려면 미숙한 도움보다는 도구를 사용하라.
유닉스 도구는 입력 파일에 손상을 주지 않으며, 어느 시점이든 less로 보내 원하는 형태의 출력이 확인 가능하여 디버깅이 유용하고, 특정 파이프라인 단계의 출력에 파일을 쓰고 다음 단계의 입력으로 사용가능하여 전체 파이프라인 재시작이 필요없는 장점 등이 있다. 하지만 이런 장점에도 불구하고 단일장비에서만 실행된다는 점 때문에 하둡 같은 도구의 필요성이 생겼다.
4. 맵리듀스와 분산파일 시스템
하둡 맵리듀스 구현에서 파일 시스템은 HDFS(Hadoop Distributed File System)이라고 하는데 GFS(Google File System)를 재구현한 오픈소스이다. HDFS는 비공유 원칙을 기반으로 하기 때문에 NAS, SAN의 아키텍쳐와 다른 점이다. 이때 2가지 콜백 함수로 구현되어있다.
매퍼(Mapper): 모든 입력 레코드마다 한번씩 실행되며, 입력 레코드로 부터 키와 값을 추출하는 작업이다.
리듀서(Reducer): 맵리듀스 프레임워크는 매퍼가 생산한 키-값 쌍을 받아 같은 키를 가지느 레코드를 모으고 해당 값의 집합을 반복해 리듀서 함수를 호출한다. 리듀서는 이때 출력 레코드를 생산한다.
맵리듀스에서 맵퍼는 입력 파일 블록에 따라서 파티셔닝됨. 매퍼의 출력은 재파티셔닝해 정렬하고 리듀셔 파이션으로 병합한다. 이 과정의 목적은 모든 데이터(같은 키 값을 가지는 모든 레코드)를 같은 장소로 가져오는 것이다. 맵리듀스 이후 데이터플로 엔진은 필요한 경우가 아니라면 정렬하지 않는다. 그렇지 않다면 대체로 비슷한 방법으로 파티셔닝 한다.
내결함성
맵리듀스는 빈번히 디스크에 기록한다. 디스크에 기록하면 개별 태스크가 실패하더라도 전체 작업을 재수행하지 않고 쉽게 복구할 수 있다. 하지만 작업이 실패하지 않는 경우 수행 시간이 느려지는 것은 감수해야한다. 데이터플로 엔진은 중간 상태를 최대한 구체화하지 않고 대신 메모리 상태를 유지한다. 이것은 특정 노드가 실패한다면 재계산 양이 늘어난다는 것을 의미한다. 결정적 연산자를 사용하면 재계산이 필요한 데이터의 양을 줄일 수 있다.
글을 쓰는 것이 중요성을 신입 때는 몰랐습니다. 내 기억력은 충분히 좋을줄 알았고 대화로 모든 것을 해결할 수 있을 것 같았습니다. 하지만 기억은 휘발되고 일의 절차와 내용은 문서로 남겨야만 의미가 있다는 것을 알게 되었고 글쓰기 모임은 이런 저의 갈증을 해결할 수 있는 선택지였습니다. 2023년 봄, 글을 쓰지 않는 사람은 사기꾼 이라는 전 팀장님의 말이 떠올라 글또 신청서를 넣고 8, 9 10기까지 진행했습니다. 이번 글은 지난 3년간 개발자 글쓰기 모임을 하면서 했던 활동을 회고하여 좋았던 점과 나의 삶의 방향이 어떻게 바뀌었는지 한번 작성해보고자 합니다.
1. 참여자로서의 8기
왜 하필 글쓰기 모임인가?
왜 하필 개발자의 글쓰기 모임일까? 저는 본래 공학에 대한 관심이 매우 많습니다. 하드웨어와 소프트웨어의 원리, 물체의 이치 등에 관심이 많은 사람이였고 자연스럽게 데이터 분야를 접하면서 이를 기반으로하는 개발 지식도 흡수하길 좋아했습니다. 또한 글쓰기의 중요성은 서두에 기술한 바있지만, 꽤나 생산적인 활동이고 꾸준히 내 지식을 축적해 나간다는 점에서 마음에 들었습니다. 재밌게도 이 기술 블로그의 출발은 대학교 시절 실험 연구 보고서를 아카이빙 하기 위한 목적으로 시작되었습니다.(실제로 화학생명공학 실험 키워드로 여전히 유입이 존재한다..)
운영진을 만나다.
2주마다 글쓰는 것이 대단한 어떤 결과를 만들어준다고는 기대하기 어려울 수 있습니다. 하지만 인생은 적분이라고 작은 변화들이 모여 지금의 나를 크게 변화시켰던 것 같습니다. 특히 IT 와 데이터 과학분야는 변화의 발걸음이 커서 그때 마다 적절한 자료나 레퍼런스를 아카이빙하는 것이 꽤나 도움이 됩니다. 이렇게 조금씩 글을 작성해 나갈때 쯔음 자체 데이터 반상회 모임에서 운영 담당에 지원했습니다. 그렇게 만난 글또의 운영진 정희님께 왜 운영진을 하냐는 질문을 해보았습니다.
성윤님을 옆에서 본다는 것. 그 이상의 이유가 필요할까요?
단순하지만 명확했습니다. 사실 성윤님이라는 한 명의 가치 뿐 아니라, 커뮤니티의 가치에 대해서 생각해보았습니다. 데이터 직군은 신생된지 얼마 되지 않아서 어떤 북극성 지표가 필요했으며, 다른사람이 어떻게 하고 있나라는 메타인지를 통해서 스스로 발전하는 변화가 필요한직군입니다. 그런 점에서 옆에서 함께 많이 배울 수 있는 사람과 환경이라고 생각 되었고 곰곰히 생각한 끝에 9기에는 운영진으로 지원하기로 결심하게 되었습니다.
2. 운영진으로서의 9기
Activation 크루 지원
9기 운영진을 준비하기 위해 내가 무엇을 할 수 있는가 뾰족하게 고민했습니다. 뒤돌아보니 제가 했던 다양한 활동들과 장으로서 일했던 경험이 소규모의 동호회를 유지하고 발전시키는데 장점이 있음을 파악했습니다. 그래서 저는 한장짜리 제안서를 함께 제출했습니다.
사실 이런 아이디어의 근간은 ChatGPT 가 활황이던 23년의 시대적 배경과 함께합니다. 8기 쯤하여 AI 코딩 어시스트 기술이 발전하고 대부분의 IT직군들이 일자리를 잃지 않을까하는 두려움에 다들 만나서 ChatGPT과 일자리에 대한 논의가 활발했습니다. 또한, 이직이나 조직생활 나아가서 밥먹고 사는 문제까지 다들 모여서 고민을을 나누는 시간들이 빈번했습니다. 저는 이런 자유로운 토론과 대화가 글또의 가치를 사용자들에게 주는 경험이라고 생각했고, 이를 좀 더 Global하게 키우는 것이 장기적으로 사용자의 리텐션 향상에 도움이 될 것이라는 가설을 세웠습니다. 실제로 지난 8기에서 오프라인으로 만난 참여자 6명은 글제출을 9.1번으로 평균 이상의 활동량을 보임을 확인했습니다. 이러한 경험을 바탕으로 다음 지원서를 작성했습니다.
9기 운영진 지원 형식
글또 하우스 2회 개최
위 고민을 담아 9기에서 글또하우스를 2회 개최했습니다. 지난 서비스 "클럽하우스" 본따 소수의 스피커와 다수의 청중으로 이루어진 고민상담소로 기획했습니다. 실제로 좋은 반응을 보였고, 1회에는 53명이 지원할 들어올정도로 많은 호응을 받았습니다. 이때 몇가지 회고를 하자면
Zoom으로 진행되어 소수의 스피커로 집중되지만 채팅 참여가 활발해서 다수의 의견을 들을 수 있는 장점
1차에는 인원이 많아서 정해진 시간에 쫓기는 느낌이 들어서 아쉬웠고, 참여자가 적은 2차는 여유로웠음
진행자가 상담을 하는 상황이 자주 벌어지는데 모르는 분야에 대해서 코멘트 다는게 어려운 단점
정해진 시간 내 탐색 -> 수렴 -> 결과 등의 진행방식의 필요성
상담자들의 도전지향형/안정지향형이 해법이 섞여있어서 좋았음
정도로 기억이 납니다. 이런 상담을 하는 와중에 진정 내가 이렇게 사람들에게 조언을 할 수 있는 똑똑한 혹은 대단한 사람인가에 대한 회의가 커졌고, 다음에는 pivot하여 다른 방법으로 기여해보자는 방향을 설정하게 됩니다.
3. 10기의 시작
CRM 크루로 pivot과 글또 내 활동들
Activation 크루는 재밌었고 좋았지만 좀 더 데이터 기반하여서 보길 원했습니다. 그래서 CRM 크루로 일단 이전을 신청했습니다. CRM의 사전적 의미는 고객관계관리이지만, 글또에서는 데이터를 가장 가까이 보고 관심있어하는분들이 많았습니다. 9기에 보냈던, 슬랙기반의 데이터를 활용한 글또 내 활동요약이 핵심 프로젝트 중 하나였고 역시 10기도 추진하게 되었습니다.
특히 10기에는 운영진 역할에 국한하지 않고 적극적으로 스터디와 4채널 활동을 많이 했던 기억이 납니다. 작은 호기심을 가지고 짧은 분석을 해보는 월간 데이터 노트, 테니스모임, 다진마늘 모임, 마인크래프트 서버 운영기, LLM 사이드프로젝트까지 오프라인 모임이 제약되는 상황에서도 다양한 활동을 할 수 있어서 매우 좋았습니다. 각 활동들에 대한 후기는 나중에 한번 정리를... 특히 월간 데이터 노트는 본격적으로 시스템화 해서 월별 루틴으로 올릴 예정이라서 나름 기대하고 있습니다.
처음에는 책과 같은 레퍼런스에서 얻을 수 있는 프로젝트가 다수인데 갈수록 내 호기심과 아이디어로 구현하가는 과정이 흥미롭습니다. 특히 8번 슬랙 봇 커뮤니티 활성하기는 순수하게 호기심과 재미로 시작한 것인데, 글또 내 큐레이션 선정도 되고 공감도 가장 높은 글이라서 작성하던 시기에도 후에도 보람찼던 경험이 있습니다. 역시 사람은 재밌는거 해야해?
사실 회사다닐 때는 책이나 스터디를 등한시 해왔습니다만, 갈수록 AI툴들이 수단을 고도화하고 기존의 툴을 대체하는 시대에 결국 살아남는 가치는 그 원리를 파악하고 잘 이용한는 방법이라는 결론에 도달하게 되었습니다. 에이 무슨 6년차에 스터디야 라고 생각했던 저는 다시 활자를 읽기 시작했고, 기존에 DA,DS에만 국한하던 스터디에서 CS까지 확장하게 되었습니다. 특히 데이터중심어플리케이션 설계(DDIA)는 백엔드 개발자들에게는 필독서로 추앙받는데, 함께 스터디하면서 전공을 다시듣는 그런 재미난 경험이었습니다.
글또의 대부분이 그러하든 나의 역할과 방향성에 고민을 하는 글들이 다수 발견됩니다. ㅋㅋ 생각이 많은 1인... 그런데 활자로 적는것 만큼 또 정리가 잘되는게 없습니다. 초반에는 아 뭐해먹고 살지 데이터분석은 뭘까 라는 등등에 대한 방향성으로 글을 써왔는데 점차 나의 목표가 뚜렷해지니 주제가 더 명확해지는 흐름이 보이네요. 이런 회고도 참 재밌습니다!
4. 갈무리 - 정량적 성과보기
블로그 성과 보기
직무에 대한 고민 글과 프로젝트, 스터디를 하면서 올라간 내면의 질적인 향상도 있지만 명시적으로 오른 지표는 방문자 수와 조회 수입니다. 월 천명, 조회수 2천건의 블로그로 성장했습니다. 데이터 분야 입문할 때만해도 Ratsgo's Blog나 어쩐지 오늘은 같은 데이터 블로그 글들로 공부를 많이했는데 어쩌면 저도 그런 블로그가 될 수 있을까 싶습니다 ㅋ_ㅋ
두번째로는 노출과 CTR입니다. 점점 글을 작성하면서 알게모르게 블로그의 랭크가 높아졌고, 메타 태그 설정, 구글 서치콘솔까지 연결하니 가장 인기 있는 글은 13%의 CTR를 달성하기도 했습니다.
블로그 전체의 CTR
물론 일반 웹서비스와 다르게, 기술블로그 특성상 적절한 주제라고 생각하면 클릭으로 전환하는 비용이 크지 않기 때문에 큰 값은 아니지만 그래도 꽤나 전환율이 좋은 글들이 많습니다. 특히 통계책으로 검색하면 일반 서점 사이트와 교수님이 쓰신 글! 까지 제치고 상위노출하는 쾌거를 이루어서 기분이 좋았네요.
(좌) 상단노출되는 내 글 (우) 인기목록 Top10
글또에서 양질의 컨텐츠를 얻으면서 배웠던 지식과 커피챗을 하면서 얻었던 아이디어등이 결합된 결과라고 생각됩니다. 물론 글또 뿐 아니라 다양한 글을 완성도에 집착하지 않고 일단 하자! 라는 결심으로 차곡히 쌓아온 결실이기도 하구요. 이런 환경을 만들어주신 성윤님께 무한 감사!
그래서 앞으로는?
아마 글또가 마무리되니 압박에 시달려서 2주마다 글을 쓰진 않을 수도 있을 것 같습니다. 하지만 글을 쓰는 것에 대한 가치는 더 커졌고 오픈소스처럼 나의 지식을 나누어주는 것에 대한 기쁨도 동반되고 있습니다. 문득 글을 정리하다가 10기의 목표를 "히언님의 친절한 데이터사이언티스트 되기 강좌" 과 같이 동영상보다 좋은 글을 남기자 였는데 이 목표에 지속해서 다가가려 합니다. 앞으로는 방통대를 입학해서 공부하고 추후에 대학원 고려도 해볼 수 있을 것 같은데 그때마다 떠오르는 생각과 기술들을 한번씩 적어보겠습니다. 그럼 20000
단일 시스템에서 다중시스템으로 확장하는 것은 단순히 공수의 N배가 아니라는 것을 느끼는 단원. 초반에는 네트워크장애에 대한 기술을 서술하나가는데 그 이후는 그만 정신이 혼미해지는 단원입니다. 그야말로 골칫거리군요. 후반은 읽다가 잠시 정신을 잃어서 이만..
신규개념
개념
설명
타임아웃(Timeout)
서버로 요청을 보냈지만 일정 시간동안 답변을 받지 못할 때 발생시키는 오류
TCP
전송 제어 프로토콜(Transmission Control Protocol), 애플리케이션 사이에서 안전하게 데이터를 통신하는 규약. IP(Internet Protocol)와 엮어서 TCP/IP라고 불리기도 하며, 통신속도를 높이면서도 안전성, 신뢰성을 보장할 수 있는 규약
UDP
사용자 데이터그램 프로토콜 (Universial Datagram Protocol). TCP처럼 네트워크 프로토콜이지만 비연결형 프로토콜이라느 차이점.
이더넷
다수의 시스템이 랜선 및 통신포트에 연결되어 통신이 가능한 네트워크 구조(인터넷 ㄴㄴ)
패킷(Packet)
네트워크에서 주고받을 때 사용되는 데이터 조각들
동기식 통신
Synchronous 동시에 일어남. Request를 보내면 얼마나 시간이 걸리든 그 자리에서 Resonse를 받음
비동기식 통신
Asynchronous, Request 보내도 Response를 언제 받아도 상관 없다. 할일이 끝난 후 Callback을 통해 완료를 알림
1. 비공유 시스템의 오류 관리 방법: 타임 아웃
(New) 분산 시스템은 비공유시스템. 즉 네트워크로 연결된 다수의 장비이다. 네트워크는 장비들이 통신하는 유일한 수단이라는 특징을 가지고 있다. 인터넷과 이더넷은 비동기 패킷 네트워크(asynchronouse packet network)다. 이런 종류의 네트워크에서는 다른 노드로 메시지(패킷)을 보낼 수 있지만 네트워크는 메시지가 언제 도착할지 혹은 머세지가 도착하기는 할지 보장하지 않는다. 여기서 패킷이란 네트워크에서 주고받는 데이터의 조각이다.
약간 mRNA 닮은거는 기분탓인가?
패킷의 기본 구조. 익숙한 단어가 보인다.
요청을 보내고 응답을 기다릴 때 여러가지 오류의 상황이 발생할 수 있다.
요청이 손실되었을 수 있다.(누군가 네트워크 케이블을 뽑았을 수도)
요청이 큐에서 대기하다가 나중에 전송될 수 있다.(네트워크나 수신자에 과부화가 걸렸을 수 도 있다.)
원격 노드에 장애가 생겼을 수 있다.(죽었거나 전원이 나갔을 수 있다.)
원격 노드가 일시적으로 응답하기를 멈췄지만 나중에는 다시 응답하기 시작할 수 있다.
원격 노드가 요청을 처리했지만 응답이 네트워크에서 손실됐을 수 있다.
원격 노드가 요청을 처리했지만 응답이 지연되다가 나중에 전솔될 수 있다.(네트워크가 요청을 보낸 장비가 과부하가 걸렸을 수 있다.)
(a) 요청의 손실 1번 사례, (b) 원격 노드 다운 3번 사례, (c) 응답이 손실 5번 사례
이 문맥을 읽으면서 외계 탐사행성을 위해 발사된 보이저1호의 상황이 상상이 되었다. 응답할 것이라고 기대하고 보내는 전파가 돌아오지 않는 상황이 네트워크 오류와 비슷한 느낌이랄까? 출처: https://www.thedailypost.kr/news/articleView.html?idxno=104231
우리의 요청을 오기까지 그럼 무제한 대기해야할까? 그렇지 않다. 발송이 잘못되었는지, 처리는 되었는지 어느 부분에서 장애가 일어난지 모르니 타임아웃을 이용해서 관리한다고 책은 기술한다. 토스페이먼츠 API Read Timeout은 30-60초로 설정한다고 한다. 이는 경험적인 수치인듯?
이런 문제를 다루는 흔한 방법은 타임아웃이다. 얼마간의 시간이 지나면 응답 대기를 멈추고 응답이 도착하지 않는다고(장애라고) 가정한다. 그렇다면 타임아웃은 얼마나 길어야할까? 타임아웃이 길면 노드가 죽었다고 선언될 때까지 기다리는 시간이 길어진다. 그 시간동안 사용자들은 기다리거나 오류 메시지를 보아야한다. 반면 타임아웃이 짧으면 결함을 빨리 발견하지만 노드가 일시적으로 느려졌을 뿐 결함이 아닌데도 죽었다고 선언할 확률이 높아진다. (뭐예요 나 아직 살아있어요) 단순히 오해에 그치는게 아니라 노드가 죽었다고 판단되면 그 역할이 다른 노드로 전달되는 내결함성으로 인하여 연쇄장애의 원인이 될 수 있다.
이때 인터넷 전화(VoIP)는 지연시간이 민감하기 때문에 TCP보다는 UDP를 선택하는 사례에 대해서 기술한다. UDP는 흐름제어를 따로 하지않고 속도가 빠르며 잃어버린 패킷에 대한 부분을 사람 계층에서 재시도 한다는 표현이 재밌었다. 대표적인 VoIP 서비스인 디스코드가 UDP 통신을 선택하고 있다.
2. 패킷이 유실되지 않는 견고한 네트워크는 어때?
(New) 네트워크에서 패킷이 손실될 수 있다는 점이 단점으로 명확해지는데 그럼 견고한 네트워크는 어디 없을까? 이를 고정회선 네트워크의 예시를 가져온다. 전화 네트워크는 극단적인 신뢰성을 가져서 음성이 지연되거나 통화가 유실되는 매우 드물다. (고양이가 전화선을 뜯지 않는다면 말이지)
이러한 견고함을 유지할 수 있는 이유는 통화를 하는 두 명사이에 있는 전체 보장된 대역폭이 할당된다. 언제까지? 통화가 명시적으로 끝날때까지. 이런 네트워크는 동기식으로 큐 대기가 없으므로 종단 지연시간의 최대치가 고정되어 있다. (제한 있는 지연) 따라서 특정 시간의 간격이 지나도 응답이 오지 않는다면 명백한 통신실패임이 확실하다.
반면 TCP연결은 가용한 네트워크 폭을 변화하면서 사용한다. 그러니까 연결이 없다면 어떤 대역폭도 사용하지 않는다. 연결이 있다면 기약없는 지연이 발생할 수 없는 구조이다. 그저 빨리 완료되길 기도하는 메타이다.
사이드프로젝트 혹은 사내 작은 프로젝트를 하기 위한 클라우드 서비스별 크레딧을 정리해보았습니다. 하기 내용은 정확하지 않을 수 있으며, 해당 서비스의 공식홈페이지를 참고하시는 것을 추천드립니다.
문서버전: 2025-03-24 최초 등록
일반 사용자
NCP 10만원, 3개월
접근성이 쉬움 + Naver API 사용하기도 좋음
GCP 300 달러 크레딧, 3개월
GCP는 Computing resource 하나 조건무관 제공 (Disk 30GiB)
AWS 프리티어 12개월 무료(쿠폰적용시 100달러)
GCP와 달리 1년 지나면 무료 해지인듯!
Azure 200 달러
후발 주자여서 그런지 지원 정책이 올라오고 있는 중
사업자/스타트업
개인의견:
사업자 & 스타트업 신청은 굳이라고 생각하실 수도 있지만, 개인사업자의 경우 국내 홈택스에 쉽게 사업자를 낼 수 있습니다. 다만 청년창업소득세면제와 같은 쿠폰을 써버리는게 아깝다고 사업자를 내는걸 꺼려할 수도 있지만 사실 뭘 안하는것보다 일단 하는게 좋다는 생각입니다. 법인사업자의 경우 절차와 신고 등이 부가적인 것이기 때문에 굳이 추천 드리진 않습니다.