[내배캠] 데이터분석 6기/본캠프 기록

[본캠프 41일차] SQL 공부, 머신러닝 공부

물맨두 2025. 4. 15. 22:49

 

오늘 한 일은,

  • SQL 공부
    • [코드카타] SQL 4문제 풀기(145~148번)
  • 머신러닝 공부
    • [머신러닝 특강] "분류" 수강하기
    • [실무에 쓰는 머신러닝 기초] 1-7 실습하기
    • [실무에 쓰는 머신러닝 기초] 1-10 수강하기 
  • 다섯 번째 아티클 스터디 하기

SQL 공부: [코드카타] SQL 문제 풀기(145~148번)

145. Higher Than 75 Marks

Query the Name of any student in STUDENTS who scored higher than  Marks. Order your output by the last three characters of each name. If two or more students both have names ending in the same last three characters (i.e.: Bobby, Robby, etc.), secondary sort them by ascending ID.

정렬 기준이 왜 저래...

SELECT s.name
FROM (
    SELECT *,
           SUBSTR(name, -3, 3) name2
    FROM students
    WHERE marks > 75
) s
ORDER BY s.name2, s.id

 

148. Type of Triangle

Write a query identifying the type of each record in the TRIANGLES table using its three side lengths. Output one of the following statements for each record in the table:
 - Equilateral: It's a triangle with  sides of equal length.
- Isosceles: It's a triangle with  sides of equal length.
- Scalene: It's a triangle with  sides of differing lengths.
- Not A Triangle: The given values of A, B, and C don't form a triangle.

-- 첫 번째(+ɑ) 작성한 쿼리 (오답 처리됨)
SELECT CASE WHEN (A=B) AND (B=C) THEN 'Equilateral' 
            WHEN ((A=B) AND (A+B>C OR A+C>B OR B+C>A)) OR 
                 ((A=C) AND (A+B>C OR A+C>B OR B+C>A)) OR 
                 ((B=C) AND (A+B>C OR A+C>B OR B+C>A)) THEN 'Isosceles'
            WHEN ((A!=B AND A!=C AND B!=C) AND A+B>C) OR 
                 ((A!=B AND A!=C AND B!=C) AND A+C>B) OR 
                 ((A!=B AND A!=C AND B!=C) AND B+C>A) THEN 'Scalene'
            ELSE 'Not A Triangle' END decision
FROM triangles

계속 쿼리를 쓰다보니까 이게 맞나 싶으면서 계속해서 반복되는 부분(가장 긴 변보다 나머지 두 변의 합이 크다)이 거슬리기도 해서 case when 구문에 대해서 찾아보다가 중첩으로도 쓸 수 있다는 것을 발견했다.

 

[참고] SQL CASE WHEN 효율적 사용법: 단일 vs 중첩 CASE WHEN 구문

-- 두 번째 작성한 쿼리 (정답 처리됨)
SELECT CASE WHEN A + B > C AND A + C > B AND B + C > A THEN -- 일차적으로 삼각형이 성립하는지 확인
               CASE WHEN A = B AND B = C THEN 'Equilateral' -- 삼각형이라면 어떤 것에 속하는지를 판단함
                    WHEN A = B OR A = C OR B = C THEN 'Isosceles'
                    ELSE 'Scalene' END
            ELSE 'Not A Triangle' END decision
FROM triangles

우선 삼각형인지 아닌지를 판단하고, 삼각형이라면 Equilateral인지, Isosceles인지 Scalene인지 구분해주는 식으로 작성할 수 있다.

 

그런데 쿼리를 이렇게 작성하다 보니 깨달은 건데 굳이 중첩 CASE WHEN 구문을 사용할 필요 없이 조건의 순서만 고쳐서 했어도 됐겠구나 싶었다. 그냥 뭐가 됐든 나머지를 Scalene으로 처리하게끔 쿼리를 작성하기만 하면 된다. 그렇게 하려면 삼각형이 아닌 것을 먼저 걸러야 하는 것이었다.

-- 단일 CASE WHEN 구문으로 작성한 쿼리 (이 역시 정답 통과됨)
SELECT CASE WHEN A + B <= C OR A + C <= B OR B + C <= A THEN 'Not A Triangle'
            WHEN A = B AND B = C THEN 'Equilateral'
            WHEN A = B OR A = C OR B = C THEN 'Isosceles'
       ELSE 'Scalene' END decision
FROM triangles

 


 

머신러닝 공부: [실무에서 쓰는 머신러닝 기초]

딥러닝

딥러닝(Deep Learning)

  • 인공신경망을 심층으로 연결한 모델을 학습하는 기법
    • 두뇌 세포를 모사한 것을 인공 뉴런(artificial neuron)이라 하고, 인공 뉴런들을 엮어 만들 것을 인공 신경망(artificial neural network)이라 함
  • 핵심 아이디어
    • 계층적으로 특징을 학습함
    • 딥러닝의 인공 신경망 중 수많은 은닉층(hidden layers)은 비선형 활성화 함수를 통해 데이터를 변환하며 복잡한 패턴을 학습함
    • 예측과 실제 값의 차이(=오차)를 신경망 거꾸로 전달하여 가중치를 보정함 오차역전파(Backpropagation)

딥러닝의 주요 아키텍처

  • 아키텍처 : 딥러닝에서 인공 신경망의 구조 및 디자인을 지칭함 (어떻게 레이어가 배열되는지, 뉴런들이 어떻게 연결되는지, 어떤 활성화 함수를 사용하는지 등)
  • 딥러닝 아키텍처: ① CNN(Convolutional Neural Network)
    • 이미지를 볼 때 일정 크기의 창으로 이미지를 스캔하면서 특징을 찾는 방식
    • 이미지, 영상에 특화된 구조
      • 필터(or 커널)를 이용해 이미지(특징 맵)을 스캔하며 특징을 추출
        합성곱(convolution) 레이어: 필터가 이미지를 슬라이싱해가며 스캔함
      • 특징 맵의 크기를 줄이고 주요 특징만 요약
        풀링(pooling) 레이어: 입력된 데이터의 공간적 차원을 줄여나
      • 추출된 특징을 기반으로 분류 혹은 회귀를 수행
        완전연결(FC, fully connected) 레이어
  • 딥러닝 아키텍처: ② RNN(Recurrent Neural Network) 
    • 우리 뇌가 문장을 읽을 때 앞선 맥락 속에서 현재 단어를 이해하듯, RNN은 이전 정보를 순환하며 학습
    • 문장(텍스트), 음성 신호, 주가 등 순차적 데이터 처리에 특화된 구조 시간 순서가 있는!
      • 이전 단계(시점)의 은닉 상태(정보)를 다음 단계로 넘겨주며 연쇄적으로 학습
      • 순환 구조로 인해 과거의 정보를 기반으로 현재 출력에 영향을 줌
      • ⇒ 장기 메모리 문제가 발생(기울기 소실, 폭주 문제) 장기 기억이 없는 것이 치명적임
        ➥ 이 점을 보완한 신경망이 LSTM(Long Short-Term Memory), GRU(Gated Recurrent Unit)
  • 딥러닝 아키텍처: ③ Transformers
    • 사람에게 한 문단 전체를 빠르게 보여주고, 어떤 단어가 서로 얼마나 관련 있는지 파악해 의미 연결고리를 만드는 과정
    • 2017년 구글 논문(“Attention Is All You Need”)에서 제시된 모델로, 어텐션(Attention) 메커니즘을 활용하여 입력 전체에서 중요한 부분에 집중함
      • 인코더(Encoder)와 디코더(Decoder) 구조로 분리
      • 어텐션을 통해 단어 간 연관성(의미, 문맥)을 효율적으로 학습
    • 현재 많은 대형 모델들이 Transformers 기반자
    • 연어 처리뿐 아니라 영상, 음성 등 멀티모달에도 응용 가능
      멀티 모달 : 음성, 이미지가 한 번에 들어가 있는, 다양한 유형의 데이터들을 함께 고려해 처리하는 방식

 

250415 머신러닝 온라인 강의 완!

 


 

어제 공부한 머신러닝(군집, 차원 축소, 이상 탐지) 정리한 TIL : 

 

[본캠프 40일차] SQL 공부, 머신러닝 공부

오늘 한 일은,SQL 공부[코드카타] SQL 7문제 풀기(138~144번)통계학 공부[통계 라이브세션] 6회차 수강하기머신러닝 공부[python standard] 9회차 수강하기[실무에 쓰는 머신러닝 기초] 1-6 실습하기[실무에

maandoo.tistory.com


 

어제 들은 강의들의 실습을 해보려고 한다.

 

군집 분석: [실습] K-Means/DBSCAN/계층적 클러스터링으로 와인 분류하기

우선 군집 분석 문제부터 풀어보자!

 

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 1. Wine 데이터셋 불러오기
from sklearn.datasets import load_wine
wine = load_wine()
X = wine.data
y = wine.target  # 실제 레이블(군집 평가엔 직접 사용 안 함)

데이터가 독립 변수가 총 13개다.

 

# 2. 학습용 데이터와 검증용 데이터 나누기
from sklearn.model_selection import train_test_split

# 그냥 학습용과 검증용 둘로만 나누기로
X_train, X_test, y_train, y_test = train_test_split(
    X, y, 
    test_size=0.2,
    random_state=42,
    stratify=y
)

 

# 3. 클러스터링 모델 생성; K-Means, DBSCAN, 계층적 클러스터링
from sklearn.cluster import KMeans, DBSCAN, AgglomerativeClustering
from sklearn.metrics import silhouette_score

# (1) K-means
# .fit_predict() : 학습(fit)과 예측(predict)를 한 번에 수행하는 메서드 (비지도 학습에선 각 데이터가 어디에 속하는지 알려주는 라벨을 반환하려면 .predict()까지 해야 함)
kmeans = KMeans(n_clusters=3, random_state=42)
kmeans_labels = kmeans.fit_predict(X_train)

# K-Means의 실루엣 계수 구하기 (높을수록 좋음)
kmeans_sil = silhouette_score(X_train, kmeans_labels)

배운 군집화 중에서 우선 K-Means 모델을 생성했다. 우선 특별히 파라미터를 설정하지 않고, 애초에 해당 데이터셋은 클래스가 3개짜리라서 n_clusters 매개변수를 3으로 설정하기만 했다. 그리고 해당 모델의 실루엣 계수를 구했다.

 

# (2) DBSCAN - i. eps 값을 찾아서...
# eps: 보통은 k-distance plot을 그려서 시각적으로 결정
# min_samples: 보통은 "(데이터 차원 수)+1"로 설정함(나중에 조정할 때 이상치에 민감하게 하고 싶다면 값을 줄이고, 더 안정적인 모델을 원하면 값을 키움)

# DBSCAN의 eps 값을 위해 k-distance plot을 그리기
from sklearn.neighbors import NearestNeighbors

# n_neighbors: min_samples와 동일하게 설정
k = 14
neigh = NearestNeighbors(n_neighbors=k)
nbrs = neigh.fit(X_train)
distances, indices = nbrs.kneighbors(X_train)

# k번째 이웃까지의 거리 중 가장 큰 거리만 추출
k_distances = np.sort(distances[:, k-1])

# 시각화하기
plt.plot(k_distances)
plt.xlabel("Data Points")
plt.ylabel(f'{k}th Nearest Neighbor Distance')
plt.title("k-distance Graph for DBSCAN")
plt.grid(True)
plt.show()
# 아마도 한 110 정도?!

[참고1] [파이썬][머신러닝][비지도학습][군집] DBSCAN

[참고2] DBSCAN Clustering in ML | Density based clustering

그 다음은 DBSCAN으로 모델을 생성할 차례인데, 이때 중요한 매개변수가 eps(ε, 일정 거리)와 min_samples(minPts)를 설정해줘야 한다. 이는 eps 거리 안에 데이터가 min_samples 개수 이상 있으면 이 데이터 포인트를 핵심 샘플로 분류한다. 그리고 eps 거리 안에 있는 포인트 수가 min_samples 개수보다 적으면 그 포인트는 어떤 클래스에도 속하지 않는 잡음(noise)으로 분류한다.

 

어쨌든 두 값을 설정해줘야 하니 어떻게 입력하면 좋을지 찾아봤는데,

  • eps : k-distance 그래프를 그려서 거리가 급격히 증가하는 지점의 y값으로 설정
  • min_samples : 데이터셋의 차원에 1을 더해서 설정 

한다고 해서 우선 그 방법에 따라 min_samples=14(∵X의 컬럼 수가 13이라 13+1=14), eps=110으로 정해봤다.

# (2) DBSCAN - ii. 진짜 모델 생성하기
# eps=110: 위의 시각화에서 값이 급격히 증가하는 지점의 y값
# min_samples=14: wine 데이터 셋의 컬럼 수(13)에 1 더한 값
dbscan = DBSCAN(eps=110, min_samples=14)
dbscan_labels = dbscan.fit_predict(X_train)

dbscan_sil = silhouette_score(X_train, dbscan_labels)

 

# (3) 계층적 클러스터링 모델 생성
agg = AgglomerativeClustering(n_clusters=3)
agg_labels = agg.fit_predict(X_train)

agg_sil = silhouette_score(X_train, agg_labels)

그리고 이렇게 마지막으로 계층적 클러스터링 모델까지 생성해서 

 

# 4. 각 모델의 결과를 실루엣 계수로 평가하기 (높을수록 좋다!)

print("========= 군집 결과 비교 =========")
print(f'K-Means: 실루엣 점수={kmeans_sil} | 클러스터 라벨={np.unique(kmeans_labels)}')
print(f'DBSCAN: 실루엣 점수={dbscan_sil} | 클러스터 라벨={np.unique(dbscan_labels)}')
print(f'Agglomerative: 실루엣 점수={agg_sil} | 클러스터 라벨={np.unique(agg_labels)}')

실루엣 계수를 비교해보면 K-Means(0.59) > Agg(0.58) > DBSCAN(0.47)로 K-Means 알고리즘이 제일 나은 성능을 보여주었다.

 

다시 한 번 실루엣 계수에 대해서 되짚어보자면,

  • 실루엣 계수: 각 데이터가 클러스터 안에서는 얼마나 가까이 모여 있고, 또 다른 클러스터와는 얼마나 멀리 떨어져 있는지를 평가
  • 실루엣 계수의 범위 : -1 ~ 1 값이수록 좋다!
    • 1에 가까울수록 해당 데이터의 잘 되었음
    • 0 근처면 군집의 경계에 위치함
    • 0보다 작으면 군집화가 잘못됐을 가능성 큼

우선 다들 0보다 크고 1에 가까우니 군집화가 잘 된 것 같긴 한데, 추가적으로 어느 정도의 점수대면 성능이 괜찮다고 판단할 수 있는지 궁금해졌다. 찾아보니 정확하게 정해진 기준은 없지만 일반적으로 0.5 이상으면 좋다고 판단하고, 0.3 이하면 애매하다고 판단할 수 있는 듯했다.

[참고] 실루엣 계수 평균값 - 인프런 | 커뮤니티 질문&답변

 

그리고 결과에서 궁금한 것 또 하나는 DBSCAN의 클러스터 라벨은 둘로 나뉜 것은 그렇다치더라도 '-1'이 의미하는 바가 무엇인지 궁금했다.

[참고] sklearn.cluster / DBSCAN

사이킷런 공식 메뉴얼을 참고하면 .fit_predict()사용할 경우에 반환값으로 예측한 대로 라벨을 출력하는데 노이즈는 -1로 붙여서 내보낸다고 한다. 그러면 이상치를 제외하면 클러스터가 하나라는 말은 그냥 안 됐다는 말... '노이즈'라 하니까 어제 배운 차원 축소가 생각이 나서 그러면 이 참에 데이터셋을 다시 전처리하고서 해봐야겠다.

 

# 위의 결과에서 DBSCAN이 군집화가 잘 되지 않아서 데이터셋을 차원 축소로 전처리하는 것부터 새로 시작하기로 함
# [군집화 2회차] - 데이터 전처리(차원 축소) 과정 추가

# [2회차] 2. 데이터 전처리 - PCA
from sklearn.decomposition import PCA
pca = PCA(n_components=3) #그냥.. 2은 너무 적어보였어.. 컬럼이 13개나 되는데
X_pca = pca.fit_transform(X)

차원 축소를 한 X를 보니 진짜 컬럼이 3개로 줄었음을 확인할 수 있다.

 

# [2회차] 3. 학습용 데이터와 검증용 데이터 나누기
X_train_2, X_test_2, y_train_2, y_test_2 = train_test_split(
    X_pca, y,
    test_size=0.2,
    random_state=42,
    stratify=y
)
# [2회차] 4. 클러스터링 모델 생성 및 학습시키기(1)
# kmeans와 agg는 모델을 새로 생성할 필요 없음
# dbscan은 eps와 min_samples를 변경하여 새로 생성해야 함

# eps 값을 구하기 위해 다시 k-distance plot 그리기
k2 = 4
neigh2 = NearestNeighbors(n_neighbors=k2)
nbrs2 = neigh2.fit(X_train_2)
distances2, indices2 = nbrs2.kneighbors(X_train_2)

# k번째 이웃까지의 거리 중 가장 큰 거리만 추출
k_distances_2 = np.sort(distances2[:, k2-1])

# 시각화하기
plt.plot(k_distances_2)
plt.xlabel('Data Points')
plt.ylabel(f'{k2}th Nearest Neighbor Distance')
plt.title('k-distance Graph for DBSCAN')
plt.grid(True)
plt.show()

차원 축소를 하고 나니 K-거리 그래프의 y축 스케일도 많이 줄었다. 이번에는 eps를 한 45~50 정도로 설정해주면 될 것 같다.

 

# [2회차] 4. 클러스터링 모델 생성 및 학습시키기(2)
# kmeans와 agg는 모델을 새로 생성할 필요 없음
# dbscan은 새로 생성해야 함 --> eps=45, min_samples=4
dbscan_2 = DBSCAN(eps=45, min_samples=4)

# 모델들 학습시켜서 라벨 예측값 얻기
kmeans_labels_2 = kmeans.fit_predict(X_train_2)
dbscan_labels_2 = dbscan_2.fit_predict(X_train_2)
agg_labels_2 = agg.fit_predict(X_train_2)

# [2회차] 5. 실루엣 계수 구하기
kmeans_sil_2 = silhouette_score(X_train_2, kmeans_labels_2)
dbscan_sil_2 = silhouette_score(X_train_2, dbscan_labels_2)
agg_sil_2 = silhouette_score(X_train_2, agg_labels_2)

# [2회차] 6. Davies-Bouldin 지수 구하기
from sklearn.metrics import davies_bouldin_score

kmeans_DB_score = davies_bouldin_score(X_train_2, kmeans_labels_2)
dbscan_DB_score = davies_bouldin_score(X_train_2, dbscan_labels_2)
agg_DB_score = davies_bouldin_score(X_train_2, agg_labels_2)

print("========= 군집 결과 비교(2)(+Davies-Bouldin index) =========")
print(f'K-Means: 실루엣 점수={kmeans_sil_2} | DB 점수={kmeans_DB_score} | 클러스터 라벨={np.unique(kmeans_labels_2)}')
print(f'DBSCAN: 실루엣 점수={dbscan_sil_2} | DB 점수={dbscan_DB_score} | 클러스터 라벨={np.unique(dbscan_labels_2)}')
print(f'Agglomerative: 실루엣 점수={agg_sil_2} | DB 점수={agg_DB_score} | 클러스터 라벨={np.unique(agg_labels_2)}')

이번에는 Davies-Bouldin Index까지 추가해서 결과를 확인해봤다.

 

우선 차원 축소를 통해서 노이즈가 제거된 탓인지 확실히 DBSCAN의 군집화 성능이 좋아졌다. 클러스터 라벨도 0, 1, 2, 3가지로 잘 나왔고 실루엣 점수도 0.52로 처음보다 많이 좋아졌다. 

그리고 차원 축소를 하니(=데이터 전처리를 하니) K-Means와 계층적 클러스터링의 경우는 첫 번째와 그대로 모델을 사용했음에도 실루엣 점수가 소폭 오른 것을 확인할 수 있다. 

 

Davies-Bouldin 점수의 경우는 값은 무조건 0 이상으로 나오는데 값이 0에 가까울수록 군집 간 구분이 잘 되어 있다고 확인할 수 있다. 실루엣 점수로 따졌어도 K-Means가 제일 괜찮았는데 Davies-Bouldin 점수로 따져도 K-Means가 제일 군집화가 잘 됐음을 확인할 수 있다.

 

# [2회차] 7. PCA로 시각화해보기
X_pca_2 = pca.fit_transform(X_train_2)

fig, axes = plt.subplots(1, 3, figsize=(15, 4))

# K-Means 시각화
axes[0].scatter(X_pca_2[:, 0], X_pca_2[:, 1], c=kmeans_labels_2)
axes[0].set_title('K-Means')

# DBSCAN 시각화
axes[1].scatter(X_pca_2[:, 0], X_pca_2[:, 1], c=dbscan_labels_2)
axes[1].set_title('DBSCAN')

# Agglomerative 시각화
axes[2].scatter(X_pca_2[:, 0], X_pca_2[:, 1], c=agg_labels_2)
axes[2].set_title('Agglomerative')

plt.tight_layout()
plt.show()

마지막으로 시각화도 해봤다. 확실히 산점도로 확인해봐도 K-Means가 뭔가 고르게 잘 나눠진 것 같긴 하다. 

 

# 추가 학습: 나눠놓은 검증용 데이터로 K-Means 성능이 어느 정도 나오는지 확인하기

# 모델 테스트 데이터 넣고 학습시키기 및 라벨 예측하기
kmeans_labels_test = kmeans.fit_predict(X_test)
# 실루엣 계수 구하기
kmeans_sil_test = silhouette_score(X_test, kmeans_labels_test)
# Davies-Bouldin Index 구하기
kmeans_DBs_test = davies_bouldin_score(X_test, kmeans_labels_test)

print("========== K-Means 모델 군집 결과 비교(train / test) ==========")
print(f'K-Means(학습): 실루엣 점수={kmeans_sil_2} | DB 점수={kmeans_DB_score} | 클러스터 라벨={np.unique(kmeans_labels_2)}')
print(f'K-Means(검증): 실루엣 점수={kmeans_sil_test} | DB 점수={kmeans_DBs_test} | 클러스터 라벨={np.unique(kmeans_labels_test)}')

# 시각화하기
pca_test = PCA(n_components=2)
X_pca_test = pca_test.fit_transform(X_test)

fig, axes = plt.subplots(1, 2, figsize=(10, 4))
# K-Means(훈련용)
axes[0].scatter(X_pca_2[:, 0], X_pca_2[:, 1], c=kmeans_labels_2)
axes[0].set_title('K-Means(train dataset)')
# K-Means(검증용)
axes[1].scatter(X_pca_test[:, 0], X_pca_test[:, 1], c=kmeans_labels_test)
axes[1].set_title('K-Means(test dataset)')

plt.tight_layout()
plt.show()

추가적으로 이왕 학습용 데이터와 검증용 데이터로 나눠놨으니, 성능이 제일 괜찮았던 K-Means 모델로만 새로운 데이터셋이 들어가도 어느 정도 성능이 유지되는지를 확인해봤다. 실루엣 점수, Davies-Bouldin 점수가 살짝 더 안 좋아지긴 했으나 저 정도 차이면 그냥저냥 괜찮은 것 같기도..?

 

그런데 사실 와인 데이터셋은 정답값이 있는 데이터니까 지금 비지도로 분류한 것과 실제 정답값이 얼마나 잘 들어맞는지도 보고 싶다.

250415 궁금해서 이건 어떻게 해야 하는지 chatgpt한테 물어봤다

# 정답값과 K-Means가 예측한 라벨 비교해보기!
from sklearn.metrics import confusion_matrix, accuracy_score
from scipy.stats import mode
y_kmeans = kmeans.fit_predict(X)

# 클러스터 라벨을 실제 라벨에 맞춰서 재정렬하기 (다수 투표 방식으로 맵핑)
def map_labels(y_pred, y_true):
    labels = np.zeros_like(y_pred)
    for i in range(3):
        mask = (y_pred == i)
        labels[mask] = mode(y_true[mask], keepdims=True)[0]
    return labels

y_kmeans_mapped = map_labels(y_kmeans, y)

print('========== 혼동 행렬 ==========')
print(confusion_matrix(y, y_kmeans_mapped))
print(f'\n 분류 정확도: {accuracy_score(y, y_kmeans_mapped)}')

...? 정답값이랑 맞춰보니까 결과가 왜 이래?? 첫 번째랑 두 번째는 그냥저냥 맞췄는데 세 번째는 뭐 하나도 맞추질 못했는데.....? 군집 분석 이렇게 끝내도 되나? 찝찝한데.....