프로젝트1 다른 조 코드에서 인사이트 찾기
비트코인 시계열 예측 모델 프로젝트가 끝나고 모든 조의 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한테 예제를 불러달라니까 이해가 되었다.
- 경로 조작의 일관성:
# 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")
- 파일 시스템 작업:
# Path 사용
if (data_path / "test.csv").exists():
print("파일이 존재합니다")
# os.path 사용
if os.path.exists(os.path.join(data_path, "test.csv")):
print("파일이 존재합니다")
- 경로 정보 접근:
# 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가 없어서 그런가. 딱히 재미는 없었다. 그래도 첫 대회인데도 확실히 경험이 있는 사람들이 많아서 그런가. 코드가 꽤나 다들 구조화 되어있다는게 느껴졌다.