비트코인 시계열 예측 모델 프로젝트가 끝나고 모든 조의 private이 public으로 바뀌었다. 이에 다른 조들의 코드도 구경하고 배워보려고 한다.

순서는

  • 우선 README와 코드를 흝어보고 특이사항과 인사이트를 기록
  • 멘토님들의 피드백을 보고 다시 공부

2조

모듈화를 많이 한 편이지만 중복되어 있는 코드들이라던가 좀 정리를 더 깨끗이 할 수 있을것같다.

from sklearn.model_selection import train_test_split


def get_accuracy_and_auroc(valid_type, model, train_df):

Feedback

함수마다 I/O 명시되면 좋을 것 같습니다. 어떤 용도로 사용되는지 잘 보이게 하는 게 좋습니다.

I/O가 뭐지? 찾아봤다.

I/O: 입력(Input)과 출력(Output)을 명확히 문서화

from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, roc_auc_score
import pandas as pd
from typing import Tuple, Any

def get_accuracy_and_auroc(valid_type: str, model: Any, train_df: pd.DataFrame) -> Tuple[float, float]:
    """
    주어진 모델과 데이터를 사용하여 정확도와 AUROC 점수를 계산합니다.

    Args:
        valid_type (str): 검증 유형 ('train' 또는 'test')
        model (Any): 학습된 모델 객체
        train_df (pd.DataFrame): 학습 데이터

    Returns:
        Tuple[float, float]: (정확도, AUROC 점수)

    Raises:
        ValueError: 잘못된 valid_type이 제공된 경우
    """
    X = train_df.drop('target', axis=1)
    y = train_df['target']

    if valid_type == 'train':
        X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2, random_state=42)
    elif valid_type == 'test':
        X_train, X_valid, y_train, y_valid = X, X, y, y
    else:
        raise ValueError("valid_type must be either 'train' or 'test'")

    y_pred = model.predict(X_valid)
    y_pred_proba = model.predict_proba(X_valid)[:, 1]

    accuracy = accuracy_score(y_valid, y_pred)
    auroc = roc_auc_score(y_valid, y_pred_proba)

    return accuracy, auroc
  • 타입 힌팅
  • 함수 설명 추가(멀티라인 문자열)

위 두가지를 사용해 함수의 설명, 매개변수, 반환값 등을 명시해줌으로써 가독성을 높여준다. 과제할때만 봐서 마스터님들이 그냥 우리 과제 편하게 하라고 써놓으신 줄 알았는데 원래 쓰는 거구나. 몰랐네.

3조

여기는 Optuna를 사용한 흔적이 보였다. 또한 branch를 다양하게 사용하고 병합한듯.

Feedback1

elif strategy == "SVMSMOTE":
        sampler = SVMSMOTE(sampling_strategy=sampling_strategy)
    else:
        raise ValueError(f"Unknown strategy: {strategy}")

python에 match/case 문이 있습니다(얼마전에 생김). 코드 간결하게 작성하는거에 관심있으시면 찾아보면 좋을 것 같습니다!

사실 이 피드백보고 되게 깜짝 놀랐다. python에 case문이 있다고? 찾아보니 3.10버전, 즉 2021년에 나왔다. 아니 그래도 학교에서 python배운게 2020년도긴해도 왜 몰랐지? 다른 대학친구들한테도 얘기해보니 다들 몰랐단다. 근데 얘기해보니 코딩에는 쓸 수 있어도 너무 최신버전이라 기업에 사용되는 프레임워크 버전에는 맞지않아 당장은 못 쓰지 않을까? 이런 얘기가 오가긴했다. 쨌든 신기했다.

Feedback2

각 기능별로 순서가 정리되어있는 것 같습니다. 각 기능별로 class를 만들어 객체로 관리해도 좋을 것 같습니다! 예를들어, nomarlize 기능은 Normarlize 클래스로 관리하는 식으로요ㅎㅎ 만약 함수만 바로 불러와 사용하고 싶다면, @staticmethod 데코레이터를 이용하시면 될 것 같습니다.

@staticmethod는 클래스나 인스턴스의 상태와 무관한, 독립적인 기능을 구현할 때 유용하다.

class MathOperations:
    @staticmethod
    def add(a, b):
        return a + b

    @staticmethod
    def multiply(a, b):
        return a * b

# 사용
result = MathOperations.add(5, 3)  # 인스턴스 생성 없이 바로 사용 가능

위는 클래스나 인스턴스의 상태를 변경하지 않고, 단순히 입력값에 대한 연산만 수행해서 ㄱㅊ

class DataProcessor:
    def __init__(self, data):
        self.data = data

    def normalize(self):
        return (self.data - self.data.min()) / (self.data.max() - self.data.min())

    def process(self):
        normalized_data = self.normalize()
        # 추가 처리...
        return normalized_data

# 사용
processor = DataProcessor(some_data)
result = processor.process()

얘는 안된다.

4조

4조는 파일구조가 되게 인상깊었다.

모델링을 하면 csv파일이 results에 저장되고 다시 ensemble.py에서 YAML 파일을 읽어와 가중치와 함께 예측을 진행한다는 점이 편해보였다.

Feedback1

# ID, target 열만 가진 데이터 미리 호출
data_path = "../data"
submission_df: pd.DataFrame = pd.read_csv(os.path.join(data_path, "test.csv")) 

os.path.join도 유용하지만, pathlib의 Path 클래스를 사용하면 좀더 직관적으로 코드를 작성할 수 있습니다.

from pathlib import Path

data_path = Path("../data")
submission_df: pd.DataFrame = pd.read_csv(data_path / "test.csv")

이게 처음 보고 뭔소리지 싶었다. 둘이 다른게 뭐지? 어차피 csv다른 파일 불러오고 싶으면 test.csv만 다른걸로 바꾸면 똑같은 거 아닌가? gpt한테 예제를 불러달라니까 이해가 되었다.

  1. 경로 조작의 일관성:
# Path 사용
data_path = Path("../data")
file_path = data_path / "subfolder" / "test.csv"

# os.path 사용
data_path = "../data"
file_path = os.path.join(data_path, "subfolder", "test.csv")
  1. 파일 시스템 작업:
# Path 사용
if (data_path / "test.csv").exists():
    print("파일이 존재합니다")

# os.path 사용
if os.path.exists(os.path.join(data_path, "test.csv")):
    print("파일이 존재합니다")
  1. 경로 정보 접근:
# Path 사용
file_path = Path("../data/test.csv")
print(file_path.parent)  # '../data'
print(file_path.name)    # 'test.csv'
print(file_path.suffix)  # '.csv'

# os.path 사용
file_path = "../data/test.csv"
print(os.path.dirname(file_path))     # '../data'
print(os.path.basename(file_path))    # 'test.csv'
print(os.path.splitext(file_path)[1]) # '.csv'

좀 더 subfolder가 생긴다던가 그럴때 훨씬 직관적인 코드가 될 것 같다.

Feedback2

# 명령줄에서 첫 번째 인수로 받은 파일명을 사용하여 config 파일 열기
if len(sys.argv) < 2:
    print("Usage: python ensemble.py <config_file>")
    sys.exit(1)

이러한 하드코딩보다는 argparser 사용에 익숙해지시길 권장드립니다.

  • 참고 문서: https://greeksharifa.github.io/references/2019/02/12/argparse-usage/

얘도 하드코딩이라서 피드백받은건 이해했는데 argparser이라는걸 처음 들어봐서 이해하지 못했다.

import argparse

def parse_arguments():
    parser = argparse.ArgumentParser(description='Ensemble prediction script')
    parser.add_argument('config_file', help='Path to the configuration file')
    parser.add_argument('--data_path', default='../data', help='Path to the data directory')
    parser.add_argument('--output_dir', default='../results', help='Directory to save the output')
    return parser.parse_args()

args = parse_arguments()

# 이제 args.config_file, args.data_path, args.output_dir를 사용할 수 있습니다.
data_path = args.data_path
submission_df: pd.DataFrame = pd.read_csv(os.path.join(data_path, "test.csv"))

with open(f'../config/{args.config_file}', 'r') as file:
    config = yaml.safe_load(file)
    
output_path = os.path.join(args.output_dir, output_filename)

이런 식으로 사용하라는 건데 이제

python ensemble.py config.yaml --data_path /path/to/data --output_dir /path/to/output 

이렇게 CLI 에서 쓸 수 있겠다. 하드 코딩에서 벗어난 코드긴한데 조금 어려운것 같다. 구조화나 확장가능성 측면에서는 훨씬 나은것같다.

6조

WrapUpReport가 올려져있어서 재밌게 봤다. 다른 조들것도 다 보고싶은데.. 근데 왜 이조는 Feedback이 닫혀있지?

8조

Feedback

def march_split(df, val_size=0.2, random_state=42):
    # 월별로 데이터 분류
    df['month'] = df['datetime'].dt.month
    df['period'] = pd.cut(df['month'], bins=[0, 2, 3, 12], labels=['before_march', 'march', 'after_march'])

np.select를 사용하여 성능을 향상시킬 수 있습니다

conditions = [df['month'] <= 2, df['month'] == 3, df['month'] > 3]
choices = ['before_march', 'march', 'after_march']
df['period'] = np.select(conditions, choices)

구글링해보니까

import numpy as np
import pandas as pd

# pd.cut 예시
x = np.array([1, 7, 5, 4, 6, 3])
bins = [0, 3, 6, 10]
labels = ['Low', 'Medium', 'High']
result = pd.cut(x, bins, labels=labels)
print(result)

# np.select 예시
x = np.arange(6)
condlist = [x<3, x>3]
choicelist = [x, x**2]
result = np.select(condlist, choicelist, 42)
print(result)

  • pd.cut: 구간을 지정하여 데이터 분류
  • np.select: 조건을 지정하여 데이터 분류

np.select는 벡터화되어있어서 속도가 더 빠르단다.

9조

뭐야. 9조는 왜 깃허브 없어. public으로 안 열렸나?

10조

이 조가 프로젝트 코드 구조만 봤을때 제일 깔끔한듯. 추상 메소드까지 사용된게 신기하다.

총평

뭔가 뒤로 갈수록 점점 대충 흝은 것 같은데. WrapUpreport가 없어서 그런가. 딱히 재미는 없었다. 그래도 첫 대회인데도 확실히 경험이 있는 사람들이 많아서 그런가. 코드가 꽤나 다들 구조화 되어있다는게 느껴졌다.