Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

새로운 코드구조 (RNN 계열) 주의해야할 지점들입니당. #50

Open
MignonDeveloper opened this issue May 29, 2021 · 0 comments
Labels
enhancement New feature or request

Comments

@MignonDeveloper
Copy link
Member

MignonDeveloper commented May 29, 2021

1. train dataset을 통해 구한 feature value들을 test dataset에 적용하고 싶은 경우

예를 들어 문항별 정답률은 train dataset에서 뽑은 정답률과 test dataset에서 뽑은 정답률이 다를 수 밖에 없습니다. 따라서 이런 경우에는 train dataset을 학습했을 때 나온 결과물을 미리 저장해둬야 합니다. 그래야지 inference.py를 돌릴 때 새로 value를 추출하지 않고 train과 test를 같은 값으로 모델에 입력시켜줄 수 있으니까요. 그래서 저는 다음과 같이 코드를 구성했습니다.

feature_engineering 함수부분은 개인별로 자유롭게 수정하는 부분이니까 너무 신경안쓰셔도 돼요 참고만 부탁드립니당

# dataloader.py
def __feature_engineering(self, df):
    df = df.sort_values(by=["userID", "Timestamp"], axis=0)

    # (1) 풀고 있는 assessmentItemID 의 정답률을 나타내는 feature

    # -> train dataset에서 구한 정답률을 미리 저장하는 코드입니다.
    # assessmentItemID_mean_sum = df.groupby(['assessmentItemID'])['answerCode'].agg(['mean', 'sum']).to_dict()
    # le_path = os.path.join(self.args.asset_dir, "assessmentItemID_mean_sum.pk")
    # with open(le_path, 'wb') as f:
    #     pickle.dump(assessmentItemID_mean_sum, f)

    # -> 저장된 정답률을 가져와서 mapping하는 코드입니다.
    le_path = os.path.join(self.args.asset_dir, "assessmentItemID_mean_sum.pk")
    with open(le_path, 'rb') as f:
        assessmentItemID_mean_sum = pickle.load(f)
    df["assessmentItemID_mean"] = df.assessmentItemID.map(assessmentItemID_mean_sum['mean'])

위의 경우 9,10번째 line은 train data를 바탕으로 문제별 정답률을 뽑는 것입니다. 이것을 미리 pickle file로 저장해둡니다. 그렇게 한뒤 inference를 할 때는 해당 부분을 위와 같이 주석처리 해서 train dataset을 통해 얻은 값들을 그대로 test dataset의 feature로 추가해줄 수 있습니다.

  • 하지만 예를들어 사용자가 각 문제를 푸는데 얼마나 걸렸는지에 해당하는 feature는 위와같은 작업을 하면 안되겠죠? 각 dataset에 고유한 것이니까요!

2. 내가 사용하고 싶은 feature name 지정하기

feature engineering을 하고나면 df에 다양한 feature들이 추가되어 있을 것입니다. 그 중 내가 학습과 예측에 사용할 category를 미리 지정을 해줘야 하는데요! 해당 부분은 아래와 같습니다.

# dataloader.py
    def load_data_from_file(self, file_name, is_train=True):
        csv_file_path = os.path.join(self.args.data_dir, file_name)
        df = pd.read_csv(csv_file_path)

        # stratified K-Fold를 위한 key value를 만듭니다.
        self.__make_stratified_key(df)

        # row에 추가할 feature column을 생성합니다.
        df = self.__feature_engineering(df)

        # =========== !! 이 부분을 만드신 feature의 column name으로 채워주세요 !! =========
        # feature engineering을 수행한 뒤, feature로 사용할 column을 적어줍니다.
        cate_cols = ["testId", "assessmentItemID", "KnowledgeTag", "nth_test"]
        cont_cols = ["assessmentItemID_mean"]
        # ============================================================================

        self.args.n_cates = len(cate_cols) # 사용할 categorical feature 개수
        self.args.n_conts = len(cont_cols) # 사용할 continuous feature 개수

        # categorical & continuous feature의 preprocessing을 진행합니다.
        df = self.__cate_preprocessing(df, cate_cols, is_train)
        df = self.__cont_preprocessing(df, cont_cols, is_train)
        ....
  • 내가 feature를 만들어 주면 dataframe에 특정 이름을 가지고 feature가 생성이 될 것입니다. 생성한 feature의 name을 category에 속하는지 continuous에 속하는지 잘 판단해서 list에 추가해주면 됩니다. 예를들어 저는 #1 에서 df에 assessmentItemID_mean라는 feature를 추가한 것을 확인해볼 수 있는데요, 이거는 문항별 정답률 이므로 cont_cols에 추가해놓은 것을 확인하실 수 있습니다.
  • 마지막 2줄을 보시면 cate, cont가 따로 나뉘어져 있음을 알 수 있는데, 어떤 변수냐에 따라서 전처리 하는 방법이 달라질 수 있기 때문에 따로 분리해두었습니다.

3. Model의 embedding dimension을 category feature 개수와 맞춰주기

nn.Embedding layer를 통해 categorical feature의 의미를 다양하게 파악할 수 있도록 차원을 확장시킬 수 있습니다. 이를 확장시킬 때 다양한 scale로 할 수 있겠죠? 각각의 categorical feature에 적용하고 싶은 embeddding dimension의 수를 category feature수와 맞춰서 제공해주면 됩니다. (단, #2에서 cate_cols는 4개이지만 추가적으로 interaction 이라는 feature를 process_batch과정에서 추가해주므로 총 category feature의 개수는 내가 만든 cate_cols + 1개가 됩니다.)

# model.py
class LSTM(nn.Module):

    def __init__(self, args):
        super(LSTM, self).__init__()
        self.args = args
        self.device = args.device

        self.hidden_dim = self.args.hidden_dim
        self.n_layers = self.args.n_layers

        # categorical features
        # ========================== nn.Embedding에 들어갈 self.embedding_dims의 개수를 수정해주세요 ===============================
        self.embedding_dims = [self.hidden_dim // 3] * self.args.n_cates # cate feature 수와 동일하게 해주세요
        assert len(self.embedding_dims) == self.args.n_cates
        self.total_embedding_dims = sum(self.embedding_dims)
        # =========================================================================================================================

4. KFold 학습이후 앙상블 prediction 생성

각각의 fold에 대해서 학습이 끝난 이후

inference_kfold.py
# 전체 fold ensemble한 prediction 만들기
for fold in range(args.kfold):
    fold_path = os.path.join(args.output_dir, f"{args.wandb_run_name}-output_{fold}.csv")
    fold_prediction = pd.read_csv(fold_path)

    if fold == 0:
        ensemble = fold_prediction
    else:
        ensemble["prediction"] += fold_prediction["prediction"]

ensemble["prediction"] /= args.kfold
ensemble.to_csv(os.path.join(args.output_dir, f"{args.wandb_run_name}-output_ensemble.csv"), index=False)
  • 질문있으시면 언제든지 말씀해주세요!
  • 구조적으로 더 간편하게 할 수 있는 방법이 있으시면 언제든지 피드백 부탁드립니다. 감사합니다
@MignonDeveloper MignonDeveloper pinned this issue May 29, 2021
@MignonDeveloper MignonDeveloper added the enhancement New feature or request label May 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant