반응형

 

 

cuijamm/CompetitionReview

Review of Competitions. My solutions, winner's code, or trials with new algorithms are uploaded. - cuijamm/CompetitionReview

github.com

 

 

운동 동작 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

Github Repository & Dacon Codshare Post. 

 


 

오랜만의 대회 관련 포스팅입니다. 

2020년은 개인적으로 최고의 상승장이었지만, 2021년에는 다시 하락장이 시작되고있네요. 하락장 와중에 있었던 반등 같은 대회였습니다. 

월간 데이콘 13으로 진행되었던 운동 동작 분류 AI 경진대회에서 최종 3위를 기록하게 되었습니다. 무야호~

 

그만큼 신나시다는 거지!

 

전체 파이프라인 코드는 깃헙과 데이콘 코드공유 (맨 위의 링크 두개)에 올려져 있으니, 코드 자체를 블로그에 다시 적는건 의미가 없을 것 같고, 대회 중에 들었던 생각들과 과정들만 정리해보도록 하겠습니다. 

 


 

1. 데이터

총 600개의 timestep 을 가진 시계열 센서 데이터가 주어졌습니다. 해당 센서는 오른쪽 팔에 자이로스코프, 가속도계가 달린 센서를 착용하고, 특정 운동 동작을 수행했을 때, 그 동작이 61개 동작 중에서 어떤 class 에 해당하는지를 맞추는 Classification 문제였습니다. 데이터는 csv 파일로 주어지지만, 시계열 데이터에 맞춰 numpy array 로 reshape 하면 총 3125개의 센서 값이 기록되어 있음을 알 수 있습니다. 데이터가 아주 많지는 않네요. (Original Shape : (3125, 600, 6))

때마침 애플워치를 구입한지 얼마 되지 않았던 시기였기 때문에, 워치를 생각하며 애플워치를 착용하고 운동을 하는구나 라고 생각하고 대회에 재밌게 참여할 수 있었습니다. 

 

1.1. 라벨 불균형

대회 초반에 모델을 무작정 만들고 있을때도 어느 정도의 점수는 나왔었지만, 특정 점수 이상으로 잘 올라가지 않는 느낌을 받았습니다. 그래서 혹시나 해서 타겟변수를 살펴보니

 

총 학습데이터 3125개 중 절반이 26번, 나머지 절반 데이터를 60개 동작이 나눠먹고 있는 모습

 

상당히 imbalance 가 심한 것을 확인했습니다. 3000여 개의 데이터중에서 한 클래스의 갯수가 12개라니... 이거 너무한거 아니냐고? 응아니야

점수를 더 올리려면 이걸 해결해야겠다고 생각했습니다. 

 

1.2. Feature Engineering

feature_names = ['acc_x','acc_y','acc_z','gy_x','gy_y','gy_z']

grad_cols=[]
for col in feature_names:
    grad_cols.append(f"grad_{col}")

integ_cols = []
for col in feature_names:
    integ_cols.append(f"integ_{col}")
    
#position_cols = ['pos_x','pos_y','pos_z']
    
total_feature_names = feature_names + grad_cols + integ_cols #+ position_cols

고등학교때 수학시간에 들었던 말이 생각났습니다. 미적분 문제에 접근하는 것을 유독 힘들어했었는데, 선생님께서 '일단 속도가 보인다? 미분 할 생각부터 해라. 가속도를 구해야 풀리는 문제들이다' 이런 뉘앙스의 말을 하셨습니다. 주어진 데이터는 가속도 x, y, z 와 각속도 x, y, z 이므로 이들을 미분해서 가가속도, 각가속도를 만들고, 적분도 해서 속도, 각도 변수도 만들었습니다. 이렇게 적분했던 속도를 한번 더 적분하여 변위를 만들어서 사용했었는데, 이렇게 연속으로 적분을 하니까 오차가 점점 누적되어서 그런가, 의미가 없는 결과값을 얻었습니다. 

예전에 캐글의 Ion Switching 대회에서도 이렇게 gradient 를 만들어서 접근을 했던게 생각났습니다. 그때는 lag feature, delta features, moving average features 역시 만들어서 추가했었는데, 대회 중에는 생각이 안나서 시도해보지 못했던 것이 아쉽습니다. 

이렇게 해서 사용한 변수는 총 6 * 3 = 18개의 변수를 사용하였습니다. 

 


 

2. 모델

 

2.1. Augmentation

이번 대회에서 가장 아쉬움이 남았던 부분입니다. 1위 솔루션을 보았는데 정말 여러가지 Augmentation 기법들을 시도하고 사용해 보셨더라고요. 심지어 라벨에서 'left arm', 'right arm' 이라고 쓰여진 부분도 있었는데, 전부 다 오른팔에 착용했다고 생각하고 다른 augmentation 을 생각조차 하지 않았다는 점이 좀 아쉬웠습니다. 

처음에는 도저히 감이 잡히지 않았지만, Dobby님 께서 올려주신 코드 공유를 보고, 이런 방식으로 접근하면 되겠다고 생각했습니다. 

 

운동 동작 분류 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

numpy의 roll 을 이용하여 augmentation을 하면, 머릿속으로 동영상을 만들어 봤을 때 해당 센서 데이터가 Loop 처럼 반복된다고 볼 수 있다고 생각이 들었습니다. 킹도비 아이디어 갓... 직접적으로 저 코드처럼 구현을 하지는 않았지만, tf.roll 을 사용하여 커스텀 레이어를 만들어서, 학습시에는 랜덤한 값으로 Augmentation 을 수행하고, test 시에는 적용되지 않도록 구현하였습니다. 

# 모델의 인풋 바로 다음에 랜덤한 값으로 Rolling 을 하는 커스텀 레이어. 
class Rolling(Layer):
    def __init__(self, roll_max=599, roll_min=0):
        super(Rolling, self).__init__()
        self.random_roll = random.randint(roll_min, roll_max)   
        
    #def build(self, input_shape):  # Create the state of the layer (weights)
    #    pass
    
    def call(self, inputs, training=None):# Defines the computation from inputs to outputs
        if training:
            return tf.roll(inputs, shift=self.random_roll, axis=1)
        else:
            return inputs
        
    def get_config(self):
        return {'random_roll': self.random_roll}

 

2.2. Minority Oversampling

# 데이터를 하나하나마다 다른 Rolling 과 다른 노이즈를 추가하여 오버샘플링 하는 용도의 함수
def aug_data(w, noise=True, roll_max=550, roll_min=50, noise_std=0.02):
    assert w.ndim == 3
    auged=[]

    for i in range(w.shape[0]):
        roll_amount = np.random.randint(roll_min, roll_max)
        data = np.roll(w[i:i+1], shift=roll_amount, axis=1)
        if noise:
            gaussian_noise = np.random.normal(0, noise_std, data.shape)
            data += gaussian_noise
        auged.append(data)
    
    auged = np.concatenate(auged)
    return auged

위에서 확인했듯, Imbalance 가 매우 심합니다. 3125개중에 12개를 정확히 맞추는 것은 아무리 생각해 보아도 선을 넘은 것 같습니다. 그래서 Oversampling을 해 주었습니다. 

학습을 Stratified 10 Fold CV 를 하였는데, 매 Fold 마다 train과 valid를 쪼갠 이후, train데이터의 26번(Non-Exercise)항목이 아닌 데이터들만 뽑아서 위 함수를 이용하여 적용시켜 주었습니다. 원본 데이터를 그대로 복사하는것은 아니고, 데이터 전체가 아니라 각각의 데이터마다 랜덤하게 roll을 해주고, 약간의 가우시안 노이즈를 추가하여 train 데이터에 concat 하였습니다. 1번 정도만 적용하니 성능이 향상되었고, 2번 이상부터는 overfit이 쉽게 일어나는 것 같았습니다. 

 

2.3. Modeling

모델 구조는 여러 가지를 생각해 보았는데, 

  • Conv1D 이후 Dense (VGG-like)
  • RNN (LSTM / GRU) 이후 Dense (Stacked LSTM)
  • RNN 과 Conv1D 를 섞어서 Skip Connection을 골고루 넣는 (떡칠하는) 모델
  • RNN Path 와 Conv1D Path 를 따로 두고 Concat하여 Timestep 과 Local feature들을 동시에 고려하는 모델

들이 생각이 났었는데, 최종 모델로 선택한 것은 1번 이었습니다. 레이어를 아무리 넣고 빼고 자시고를 반복해도 RNN계열 층이 섞여있을 때는 성능이 생각보다 잘 나오지 않았습니다. 개인적으로 시계열 문제를 굉장히 싫어하는데, (잘하고싶은데, 잘 안돼요..) 아직까지는 한번도 RNN 계열 층을 써서 CNN보다 잘 나오는 경우를 못겪어봤습니다...

 

# Convolution, Dense 레이어 여러번 적기 번거로워서 만든 함수
def ConvBlock3(w, kernel_size, filter_size, activation):
    x_res = Conv1D(filter_size, kernel_size, kernel_initializer='he_uniform', padding='same')(w)
    x = BatchNormalization()(x_res)
    x = Activation(activation)(x)
    x = Conv1D(filter_size, kernel_size, kernel_initializer='he_uniform', padding='same')(x)
    x = BatchNormalization()(x)
    x = Activation(activation)(x)
    x = Conv1D(filter_size, kernel_size, kernel_initializer='he_uniform', padding='same')(x)
    x = Add()([x, x_res])
    x = BatchNormalization()(x)
    x = Activation(activation)(x)
    return x
    
def DenseBNAct(w, dense_units, activation):
    x = Dense(dense_units, kernel_initializer='he_uniform')(w)
    x = BatchNormalization()(x)
    x = Activation(activation)(x)
    return x



def build_fn(lr = 0.001):
    activation='elu'
    kernel_size=9
    
    
    model_in = Input(shape=Xtrain_scaled.shape[1:])
    x = Rolling(roll_max=599, roll_min=0)(model_in)
    x = SpatialDropout1D(0.1)(x)
    
    x = ConvBlock3(x, kernel_size=kernel_size, filter_size=128, activation=activation)
    x = MaxPooling1D(3)(x)
    x = SpatialDropout1D(0.1)(x)
    
    x = ConvBlock3(x, kernel_size=kernel_size, filter_size=128, activation=activation)
    x = GlobalAveragePooling1D()(x)
    
    x = DenseBNAct(x, dense_units=64, activation=activation)
    x = Dropout(0.4)(x)
    
    
    model_out = Dense(units=61, activation='softmax')(x)
    model = Model(model_in, model_out)
    model.compile(loss='sparse_categorical_crossentropy', optimizer=Nadam(learning_rate=lr), metrics='accuracy')
    
    return model


build_fn().summary()

VGG 스타일의 심플한 Conv1D 모델입니다. Conv1D는 커널사이즈를 꽤나 크게 잡아도 파라미터 수가 엄청 뻥튀기 되지 않고, 오히려 충분한 커널사이즈가 있어야 Timeseries 의 컨텍스트를 잡아낼 수 있을거라 생각해서 커널 사이즈를 흔히 Conv2D에서 사용하는 3이 아니라 9로 정했습니다. 

이후 학습은 Stratified 10Fold CV를 사용하여 10개 모델의 평균을 내어 제출하였습니다. 

 


 

3. 기타 다른 아이디어

  • 캐글의 ion switching 대회에서 나왔던 Kalman Filter 를 이용한 noise smoothing - 데이터가 상당히 깔끔하게 잘 나와있었어서 굳이 할 필요가 없었다고 생각이 듭니다. 
  • 데이터들의 statistics 들을 통한 aggregation, 및 Tree 기반 모델 접근 - 대회 초반에 가만히 생각해 보았지만, '굳이 데이터를 요약?까지 해야 하나? Conv1D나 LSTM, GRU 쓰면 바로도 충분히 접근할 수 있을 것 같은데.' 라는 생각에 시도해보지는 않았습니다. 
  • Stacking(meta-modeling) - 스태킹을 할때 test 셋을 bagging 해서 만들면 oof로 만들어진 meta training set과 bagging으로 만들어진 meta test set이 차이가 나서 그런가, 점수가 잘 오르지 않는 모습을 예전부터 보고 있었습니다. 스태킹 잘하시는 분들 혹시 이 글을 보신다면... 꿀팁 알려주시면 감사하겠습니다.  개인적으로 앞으로도 평균 앙상블은 정말 많이 사용할 것 같은데, 스태킹은 거의 안하게 될 것 같습니다. 좀 많이 양보하면.. 단순평균 아니라 가중평균정도...?

 


 

4. 결론 및 아쉬운 점

다른 대회에서도 저는 Augmentation을 잘 안하는 편인데, 역시나 이번에도 마찬가지였습니다. 항상 적절한 augmentation 방법을 찾아 적용하는데 실패해서 매번 버리는 경우가 많았는데, 이 대회에서는 Augmentation 에 더 노력을 덜 기울였던 점이 끝나고 보니까 아쉬움으로 남는 것 같습니다. 충분한 Augmentation으로 성능이 잘 나오는 데이터였는데, 위에 생각했던 것들을 하나씩 하고 나니까 리더보드 수상권으로 들어오기도 했고, 너무 안일하게 슬슬 마무리 짓자 라는 생각을 했던 것 같습니다. 기회가 된다면 다른 유저분들이 사용했던 Augmentation 방법론들을 또 추가해보고, (특히 왼손 오른손 Augmentation이 제일 인상깊었습니다...) 한번 더 해보고 싶은 대회네요. 데이터도 작아서 데스크탑 정도로 부담 없이 재밌게 진행할 수 있었고, CV-LB 점수가 상당히 정직하게 나와서 접근하기 좋았던 대회였던 것 같습니다. 

반응형
Posted by Jamm_
반응형

기말고사가 드디어 끝났습니다. 얼마 전 시험 시작 전에 한국수력원자력에서 진행했던 데이콘 대회의 최종 순위가 발표되었고, 정말 운이 좋게도 우승이라는 좋은 결과를 얻을 수 있었습니다. 개인적으로는 친구들이 양주사라, 코로나만 잠잠해지면 회식 가자 하는 중이고.. 양가 친척 모두에게 소문이 나서 '아이고 장하다 고놈' 그런 이야기를 듣고 있었습니다. 신문 기사에서 너무 대문짝만하게 나오기도 해서 너무 부끄럽고, 학교 홈페이지에도 올라가서 수업듣는 교수님이 '너가 그 우승한 걔 맞냐' 물어보시기도 했습니다... 아싸는 부담스러워요...

지금까지 공부하면서 얻었던 가장 좋은 성적이었기에 얼른 공유를 하고 싶었는데, 코드의 저작권과 데이콘과 작성했던 양수양도 계약서, 상금 지급 여부 등의 문제 때문에 바로 올리지는 못하고 약간 뒷북이 되긴 하였지만 이제서야 올릴 수 있게 되었습니다. 개인적으로 너무 힘들었던 대회였고, 마지막까지 예측이 안되던 대회였어서 얼떨떨하고 합니다...

 

공공데이터 활용 수력 댐 강우예측 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

입상자 코드가 공개된다고 하니, 저는 코드를 직접 쓰지는 않고 생각만 적어보겠습니다. 

사실 코드를 안올리는 진짜 이유는...코드 내에 오타가 있었어서...아래에 썼던 '생각했던 모델' 과 연결구조가 약간 다릅니다... 층을 3개 쓰려고 했는데 두개만 들어갔다던가... 이걸 대회 끝날때까지 모르고 있다가, 코드 제출하면서 찾았다니 어이가 없을 뿐입니다...

 

1. 데이터 소개

데이터셋에 대해서 이야기를 하자면, 학습데이터로는 (120, 120) 의 레이더 사진이 4장, 타겟 데이터로는 (120, 120)의 한장이 주어졌습니다. 샘플의 수는 약 6만여개가 주어졌고, npy 파일로 데이터를 받을 수 있습니다. 4장의 사진은 (30분 전, 20분 전, 10분 전, 현재) 의 4장의 사진을 가지고 10분 후의 사진 1장을 예측하는것이 목표입니다. 이렇게 레이더 사진을 예측하여 제출하면 레이더 사진을 강수량으로 변환하여, 강수 여부(정확도, 정확히는 CSI)와 강수량(MAE) 를 계산하여 점수를 얻게 됩니다. 

이미지의 시계열 데이터라고 생각을 했습니다. '이미지의 시계열이면 동영상 아니야?' 하는 생각에, 대회 초반에는 케라스의 ConvLSTM2D 층을 잔뜩 쌓은 모델을 만들려고 했습니다. 또 어떻게 보면 구름이 있어야만 비가 올 수 있으니, 미래 이미지에 대한 Segmentation으로도 접근할 수 있겠다고 생각하고 있었습니다. (CSI만 생각하면 어느정도 비슷한 접근이라고 생각합니다.) 하지만 ConvLSTM2D의 결과는 점수상으로 엉망이었고, 아 이게 아닌가 어떻게 하지 하며 고민하는 와중에 데이콘에서 Baseline 코드가 올라왔고, UNet이라는 것을 알게 되었습니다. 

UNet 의 구조 (논문 내 사진)

 

U-Net: Convolutional Networks for Biomedical Image Segmentation

There is large consent that successful training of deep networks requires many thousand annotated training samples. In this paper, we present a network and training strategy that relies on the strong use of data augmentation to use the available annotated

arxiv.org

UNet 의 논문은 위 링크에서 확인할 수 있습니다. 

원래는 Biomedical 분야에서의 의료 영상 Segmentation 을 위해 만들어진 모델이라고 하는데, 데이콘의 베이스라인 코드가 간소화된 UNet 구조를 사용하고 있었습니다. 

놀랍게도 이 UNet구조를 본따서 모델을 만들기 시작했는데, ConvLSTM2D와는 차원이 다른(?) 점수 상승을 불러왔습니다. 아, 이거구나! 유레카를 외치며 UNet 구조의 제 모델을 더 개선시키기 위해 코드를 계속 사용했습니다. 

그리고 또 검색을 해보니 RainNet 이라는 것도 있더군요. 

 

hydrogo/rainnet

RainNet: a convolutional neural network for radar-based precipitation nowcasting - hydrogo/rainnet

github.com

RainNet의 구조 역시 UNet과 거의 같았습니다. 깃헙 안에 있는 모델 코드를 보면 두군데 Dropout이 추가되었습니다. 이 RainNet에 따르면 데이터는 레이더 영상이 아니라 강수량으로 변환한 데이터였고, Loss Function은 LogCosh라고 검색을 하다가 봤던 것 같습니다. 아무튼 이 UNet Style Model이 효과가 있다는 것은 확인했습니다. 

 

 

2. 모델링

최종적으로 작성한 모델링 아이디어

키노트로 모델 구조를 그려보았습니다. 기본적인 UNet 구조로 흘러가는 Path 1 과, ConvLSTM2D를 거치는 Path 2 가 있고, 마지막에는 둘을 합쳐 마지막 Convolution을 진행하여 출력하고, 출력 Convolution을 3 * 3 으로 두었습니다. 이렇게 하면 구름의 주변 값도 고려하지 않을까 라고 생각했는데, 1 * 1 Convolution 과의 비교를 할 시간이 없었어가지고, 약간 아쉽습니다. Upsampling 층 대신에 Conv2DTranspose를 사용했고, RainNet에 있던 Dropout 위치에다가 그림에는 지금 Dropout 이라고 표시되어 있지만 SpatialDropout2D를 사용했고, 활성화함수는 ELU를 사용했습니다.  개인적으로 mish 를 정말 좋아하는데 이번에는 mish를 못써서 속상했습니다(?) 왜 좋냐고요? 그냥 이름이 이뻐서...

그림에는 그냥 나와있지만, 필터수 256, 512에서는 제가 '월간데이콘 6, 음성 중첩 분류 대회' 에서 사용했던 선형 Bottleneck 구조를 다시 사용했고,(파라미터 수를 줄이고 오래 걸리는 모델을 얻었다...) 모든 3번의 Convolution 블럭? 의 첫번째와 세번째를 잇는 Additive Skip Connection이 들어있습니다. 개인적으로 스킵커넥션이 Add 인 경우는 뭔가 '야 이게 맞으니까 당장 반영해.' 느낌이 있고, Concat인 경우는, '자 이렇게 될 수도 있는데 같이 생각해봐~' 이런 느낌이었습니다.

그냥 UNet만 사용하다가 ConvLSTM2D를 추가하게 된 이유는 다음과 같습니다. 

  • Conv 층의 필터끼리는 Dense Layer 와 비슷하게 연산된다. -> 이렇게 되면 시계열 순서가 담고있던 의미가 섞이게 될 것입니다.
  • (예전에 어떤 블로그에서 읽었던 글인데, 기억이 안나요 죄송합니다.) 신경망은 Sparse 할 수록 성능이 좋다, 인셉션이 성능이 좋았던 이유. 라고 설명을 해 주셨었는데, 지금도 Sparse가 어떤 의미인지 완벽히 이해하지 못하고 있지만, 인셉션 구조의 컨셉? 그림? 이 생각이 나서 이런식의 디자인이 나름의 인셉션 역할을 하지 않을까 라고 생각했습니다. 
  • ConvLSTM2D층 한개짜리 모델의 결과를 시각화 해 보았더니, 생각보다 성능이 좋아 보였다. -> 아주 개인적으로는 구름이 대충 '어디에 있는지' 정도는 층 하나로도 잘 잡아내는 듯 했습니다. 하지만 구름 모양의 세부적인 모양 디테일이나 그 값을 잘 잡아내지 못했기 때문에 점수가 잘 나오지 않았다고 생각했습니다. 그래서 ConvLSTM 구조로 모양을 잡고, UNet 구조로 디테일을 잡아서 합치면 어떨까? 하는 생각에 이렇게 만들게 되었습니다. 

학습, 예측 과정은 특별한 과정 없이 10 Fold Cross Validation을 하였고, Early Stopping을 걸어서 Fold 별로 베스트 모델을 저장해두었다가, 10개 모델들의 결과물의 단순평균을 구해서 제출했습니다. 오랫동안 1등을 유지하고있었던 0.50033(?) 점의 모델도 이 구조의 단일 모델 결과였습니다. 그래서 평균 앙상블 모델의 Public 점수는 매우 별로였지만 그래도 선택 할 수 있었다고 생각합니다. 

 

 

3. 기타 시도했던 것들 / 대충 시도 또는 생각만 해본....

  1. Metric 을 AUC 로 둔 것. 픽셀별로 0~1 값이니까 뭔가 측정이 될 수 있지 않을까 하는데 의미 없었습니다.
  2. Grouped Conv2D. Timestep이 4니까 UNet의 Conv에서 group을 4로 주고 해보았습니다. 지금 봐도 나쁘지 않은 생각인 것 같은데, 할 수 있다면 다시 해보고 싶습니다. 
  3. TimeDistributed(Conv2D). 이거 할 바에는 그냥 ConvLSTM2D를 해야겠다 생각했습니다.
  4. UNet Style ConvLSTM2D. ConvLSTM을 64로 시작해서, UNet의 모든 Conv 층을 ConvLSTM으로 대신할 수 있을까 생각했지만 컴퓨터가 못버텨서 빠른 포기.
  5. Input -> ConvLSTM2D -> UNet 구조 / UNet에서 가운데 층만 ConvLSTM2D. 전자의 경우는 좀 더 생각해볼 수 있지 않을까 싶은데, 후자의 경우는 Conv2D 통과하는 순간 시계열이 섞이기 때문에 틀린 생각이었다고 생각되네요. 시간 압박때문에 ConvLSTM2D 층을 두개 이상 쓰기는 정말 힘들었습니다. 
  6. Input -> DownSample -> ConvLSTM2D -> Upsample. 모델 크기는 작아지긴했지만, 대책없이 DownSampling을 해서 그런가 정보 손실이 있을 것 같습니다. 
  7. DownSampling 시에 CBAM 어텐션, UpSampling 시에는 'Attention UNet' 의 Attention Gate 추가. 둘다 썼을 때나, 둘중에 하나만 썼을 때나 둘다 결과가 그닥 좋지 않았습니다. 논문에 있는 그림 보고 무작정 만들어 봤었는데, 잘못 만들었을 것 같은 느낌이 확 드네요. 이것도 다시 해보고 싶은 아이디어 입니다. 

 

4. 결론

정말 너무 힘든 대회였습니다. 제출할 때마다 Public LB 점수는 오락가락 하고 있고, 모델 하나 만들어서 제출하는 시간은 너무 오래 걸리고, 컴퓨터는 이러다가 불날 것 같았고... 하지만 운이 잘 따라주었다고 생각합니다. 저는 여전히 부족한게 너무 많네요... 아직은 실력에 비해서 Over Prized(?) 된 것 같습니다. 그래도 상은 감사히 받고, 상값은 앞으로 더 열심히 해서 갚도록 하겠습니다. 

 

데이콘 토론 탭에도 요약본을 올렸습니다 :

 

공공데이터 활용 수력 댐 강우예측 AI 경진대회

출처 : DACON - Data Science Competition

dacon.io

 

반응형
Posted by Jamm_