시계열 데이터를 다루기 위해 lstm을 사용중인데 훈련데이터가 너무 많아서 문제입니다.

질문을 하실 때에는 다음 내용들을 포함해주시면 함께 디버깅 하는 데 도움이 됩니다.

  • 어떻게 동작하도록 하고 싶은지에 대한 설명
  • 겪고 있는 문제에 대한 상세한 설명 (에러의 경우 에러 메시지 포함)
  • Python, PyTorch 및 기타 주요 라이브러리의 버전 등을 포함한 환경 설명

안녕하세요.
시계열 데이터로 미래의 값을 예측하기 위해서 lstm모델을 사용하고 있습니다.
(실제 로봇을 제어하는데 사용하고 싶어서 로봇의 데이터를 받아서 미래 데이터를 예측하는 학습을 진행중 입니다.)

질문은 제목처럼 훈련데이터가 너무 많아서 문제입니다.
데이터가 5.3GB 인데, lstm에 넣기 위해 전처리를 하면 데이터 셋이 38만개 정도 나옵니다.

이 데이터를 다 사용하고 싶은데 한번에 넣어서 학습을 시키면 Out of memory가 떠서 학습 시작도 못하고 죽어버립니다.

제가 알기로는 lstm처럼 시계열을 다루는 학습은
데이터를 분할해서, 분할한 데이터를 가지고 먼저 학습하고 학습된 모델을 불러와 분할한 나머지 데이터로 학습시키는 방식의 학습 방법은 적절하지 못한 것으로 알고 있습니다.

저와같은 경우에, 하이퍼 파라미터를 변경하면 데이터가 학습이 거의 안되다시피 되서, 어느정도의 하이파라미터는 유지한 상태에서 학습을 해야하는데… 그럼 데이터 양이 꽤 필요한 상황입니다.

저처럼 시계열 데이터에서 가지고 있는 데이터로 한번에 학습을 못시키는 경우에 해결할 수 있는 방법이 있을까요 ??

좋아요 3

안녕하세요, @euncheol 님.

먼저 아래와 같이 말씀해주신 내용을 읽어봤을 때는 학습 데이터를 여러 토막을 낸 뒤, 첫 번째 토막으로 모델을 학습을 다 하고 모델을 저장까지 한 뒤, 다음 토막으로 다시 모델을 추가 학습하는 상황을 말씀하신 것 같습니다. 제가 잘 이해한게 맞을까요?

일반적으로 시계열 데이터의 경우 순서가 중요하기에 데이터를 뽑을 때 섞지 말라(shuffle=False)고 설명하고 있는데요, 혹시 이 내용과 데이터를 batch(묶음, 배치)로 나누어 학습하는 것을 헷갈리신게 아닐까 싶은 생각이 듭니다. 즉,

1. 순서가 중요한 상황(모델)이라고 하더라도, 학습 데이터셋을 섞지만 않는다면 묶음(batch) 단위로 쪼개어 학습할 수 있습니다.

노파심에 추가 설명을 조금 더 드리면, 전체 입력 데이터가 38만개라고 하더라도, 5천개나 1만개와 같이 GPU의 메모리에 올라갈 정도의 크기로 잘라서 학습을 진행하시면 됩니다. :smiley:

예를 들어, 아래 튜토리얼의 경우 학습 데이터가 6만개인 Fashion-MNIST 데이터셋을 사용하면서, 한 번에 6만개를 모두 학습을 시키는 것이 아니라, 64개씩의 데이터를 갖는 묶음(batch)으로 나누어 처리하고 있습니다.

# training_data라는 Dataset으로부터 64개씩 하나의 묶음(batch)으로, 데이터를 섞어서 DataLaoder를 생성 
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)

이렇게 batch 단위로 나눠서 학습을 진행하면, 전체 데이터의 일부만 가지고 모델을 학습하고, 학습된 모델을 불러와 나머지 데이터로 학습시키는 형태가 아닌, 전체 데이터를 가지고 조금씩 나누어 학습하게 됩니다. (전체 데이터를 1번 순회하는 것을 1 에폭(epoch)이라고 합니다.)

물론 이러한 경우 각 batch에서 전체 데이터를 보지 못하였기 때문에 모델 내부 가중치의 최적화에 조금 더 시간이 걸릴 수 있지만, 어쨌든 데이터 셋 전체를 사용하여 하나의 모델을 학습하는 것이 됩니다.


2. 전처리 시에 입력들과 출력들의 순서를 맞춰 묶어두신 경우에는 데이터셋을 섞으셔도 됩니다.

위에서 말씀드린 데이터를 섞는 것을 하면 된다 / 안된다는 모델의 종류와 입/출력에 따라서 이러한 내용은 달라질 수 있는데요, 최근 & 대부분의 경우 아래와 같이 학습을 하고 계시므로 가능할 것으로 예상합니다.
(가끔 모델에 따라 전처리 과정에서 행렬 분해(MF; Matrix Factorization) 등을 하는 경우가 있는데, 이런 경우에는 기존 시계열의 순서를 유지하셔야 할 수도 있습니다.)

먼저 아래와 같은 총 n+1개의 데이터가 있는 단변량(univariate) 시계열 데이터를 가정해보겠습니다.
(다변량(multivariate) 데이터의 경우에는 차원이 하나 더 있는거로 생각해주세요 :sweat_smile: )

X_{raw} = [x_0, x_1, x_2, x_3, x_4, x_5, x_6, ..., x_{n-2}, x_{n-1}, x_n]

이 때, 입력을 5개의 timestep을 받아서 다음 2개의 값을 예측하는 경우, 데이터 셋은 다음과 같이 구성될 것입니다. (n_window= 5, n_hozion=2)

X_{input_k} = [x_{k-4}, x_{k-3}, x_{k-2}, x_{k-1}, x_{k}] \\ Y_{output_k} = [x_{k+1}, x_{k+2}]

이렇게 입/출력이 정해지는 경우, 전처리 과정에서 대략 다음과 같은 식으로 (정규화를 포함한 작업들 후) 입력으로 받아야 하는 5개의 timestep과 출력(예측)에 사용할 2개의 timestep의 쌍으로 묶는 작업까지 포함하신다면, 전체 데이터 셋의 모양은 아마도 다음과 같을 것입니다.

X_{input} = [ [x_0, x_1, x_2, x_3, x_4], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_1, x_2, x_3, x_4, x_5], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_2, x_3, x_4, x_5, x_6], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_3, x_4, x_5, x_6, x_7], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_4, x_5, x_6, x_7, x_8], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ... \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-8}, x_{n-7}, x_{n-6}, x_{n-5}, x_{n-4}], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-7}, x_{n-6}, x_{n-5}, x_{n-4}, x_{n-3}], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-6}, x_{n-5}, x_{n-4}, x_{n-3}, x_{n-2}]] \\ \ \\ Y_{output} = [ [x_5, x_6 ], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_6, x_7], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_7, x_8], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_8, x_9], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_9, x_{10}], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ... \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-3}, x_{n-2}], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-2}, x_{n-1}], \\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ [x_{n-1}, x_{n}]]

위와 같이 데이터셋이 구성된 경우에는 DataLoader로부터 가져오는 각각의 (X, Y) 묶음은 이미 입력과 출력에 대한 순서가 각 데이터셋 내에 정리가 되어 있기 때문에, 학습 시 순서를 섞으셔도 상관이 없습니다.


혹시 위에서 제가 추측한 내용이 아니시라면, 다시금 알려주시기를 부탁드립니다. :sweat_smile:

좋아요 4

먼저 친절한 답변 감사합니다 @9bow 님ㅎㅎ:)
말씀하신것처럼

이해하신 내용이 맞습니다!

셔플은 하면 안될것 같아서 시도조차 못했습니다 ㅎㅎㅎ;;;

위에서 말씀해주신 내용이 38만개의 데이터를 “batch_size = 1만” 의 크기로 잘라서 학습을 진행시키면 된다는 말씀이실까요 ?? 학습이 어떤구조로 이루어지는지가 좀 헷갈립니다 ㅠㅠ

이렇게 하게 되면 전체 데이터 셋의 크기에 상관없이 GPU에 올라가는게 batch_size(여기선 1만개)만큼 만 들어가기 때문에 Out of Memory없이 학습이 잘 될거라는 말씀이실까요 ?

# training_data라는 Dataset으로부터 64개씩 하나의 묶음(batch)으로, 데이터를 섞어서 DataLaoder를 생성 
train_dataloader = DataLoader(training_data, batch_size=64, shuffle=True)

이렇게 batch 단위로 나눠서 학습을 진행하면, 전체 데이터의 일부만 가지고 모델을 학습하고, 학습된 모델을 불러와 나머지 데이터로 학습시키는 형태가 아닌, 전체 데이터를 가지고 조금씩 나누어 학습하게 됩니다. (전체 데이터를 1번 순회하는 것을 1 에폭(epoch)이라고 합니다.)

위에 예시처럼
38만개 데이터 = 1,2, … , 37, 38 개로 쪼개고 (batch_size만큼 쪼갬)

NN에 1번 데이터가 들어가고 backprop 진행 후에 2번 데이터가 들어가서 다시 backprop진행 , 38번 데이터가 들어가서 backprop이 진행 된게 1 epoch이라고 하는 건가요 ?? (너무 기초적인 질문 죄송합니다… ㅠㅠ)

좋아요 1

제가 질문해주신 내용을 제대로 이해한 것 같아 다행이네요 :smiley:

네, 이해하신 것이 맞습니다! :+1:

잠시 찾아봤는데, 아래 링크를 참고하시면 epoch / batch size / iteration에 대해서 그림으로 이해하실 수 있을 것 같습니다.

이 때의 최적화에 대해서는 아래 링크를 참고하시면 이해하시기 편하실 것 같습니다. :smile:

좋아요 2

@9bow 답변 감사합니다!

혹시, 알려주신 내용 반영하고 코드를 실행했는데,
디버깅 해보니, 애초에 NN에 들어가는 데이터 수의 문제가 아니였더라구요!

문제의 부분은,
데이터 파일(txt)를 np.genfromtxt로 받아서 데이터 전처리를 하는데

def make_xy(self, dir_file):
        xy = np.genfromtxt(dir_file, delimiter=',', dtype = float)
        # print(xy.shape)
        return xy

이부분에서 코드가 실행되다가 터미널이 죽더라구요!
txt 파일 크기는 5.3GB 정도입니다!

혹시 이부분 해결방안이 있을까요 ??

np.genfromtxt → np.loadtxt 로 바꾸니까 어느정도 해결은 된거 같습니다!
데이터 용량이 크면 numpy로 읽어 들이는 와중에 터미널이 죽어버리는거 같은데, 이유와 개선방법을 물어봐도 될까요?

한번 읽어진다면,
읽은 후에 pickle로 출력 시켜 보시는 건 어떤가요?

저는 무거운 이미지 파일들 한번 읽은 후에 pickle로 저장해놓고 많이 씁니다.
그럼 속도도 빠르고 좋아요.

좋아요 3

오늘 정신이 없어서 확인이 늦었네요;; :sob:
@bub3690 님께서도 좋은 방법을 알려주신 것 같습니다. :+1:

저는 말씀해주신 np.genfromtxt()np.loadtxt()는 사용해본 적이 없는데요,
실행 도중에 메모리 부족 문제가 발생한다면 아마도 해당 파일을 한 번에 메모리에 모두 읽어온 다음에, NumPy 배열(ndarray)로 바꾸는 작업을 진행하느라 그런 것 아닐까 싶습니다.

만드신 파일에 NA 값 처리 등의 이슈가 없다면, 아래와 같이 해보시는 것은 어떠실까요?

def make_xy(self, dir_file):
    results = []
    with open(dir_file, 'r') as f:
        for line in f:
            results.append(np.array([item for item in line.strip().split(",")], dtype=np.float32))
    return np.array(results)

테스트를 안해봐서 잘 돌아가지 않을 수는 있겠지만;;
전체 파일을 모두 읽고 나서 파싱을 하는 대신, 한 줄씩 읽어서 ndarray로 변환하는 위와 같은 방식이 조금이라도 낫지 않을까 싶습니다.
(array에 쌓아두고 ndarray로 만드는 것과 ndarray를 concat/stack하는 것 중에 어떤게 더 빠를지는 모르겠네요;)

한 번 읽으신 뒤에는 @bub3690 님 말씀처럼 pickle로 저장해보신다거나, NumPy에서 제공하는 np.save() 또는 np.savez() 등을 사용하여 저장해두시는 것도 방법일 것 같습니다. :smiley:

https://numpy.org/doc/stable/reference/generated/numpy.save.html

https://numpy.org/doc/stable/reference/generated/numpy.savez.html

(해보시고 혹시 속도가 개선된다면 알려주세요~ ^^)

좋아요 2