본문 바로가기
Artificial Intelligence/PyTorch

데이터의 분리

by YUNZEE 2023. 12. 26.
728x90

이번에는 지도 학습을 위한 데이터 분리 작업에 대해서 배우도록 하겠습니다.

 

(Jupyter Notebook 설치 방법)

https://yunz-story.tistory.com/60#comment15524835

import pandas as pd
import numpy as np
from sklearn.model_selection import train_test_split

 

1. 지도 학습(Supervised Learning)

https://yunz-story.tistory.com/entry/%EB%94%A5-%EB%9F%AC%EB%8B%9D-%EB%A8%B8%EC%8B%A0%EB%9F%AC%EB%8B%9D-%EC%9D%B8%EA%B3%B5%EC%A7%80%EB%8A%A5

(지도 학습과 그 외의 것들에 대해 궁금하다면 참고해 주세요.)

 

지도 학습의 훈련 데이터는 문제지를 연상케 한다. 정답이 무엇인지 맞춰야 하는 '문제'에 해당되는 데이터와 레이블이라고 부르는 '정답'이 적혀있는 데이터로 구성되어 있다. 쉽게 비유하면, 기계는 정답이 적혀 있는 문제지를 문제와 정답을 함께 보면서 열심히 공부하고, 향후에 정답이 없는 문제에 대해서도 정답을 예측해야 한다.

 

스팸 메일과 정상 메일을 구분하는 프로그램을 지도학습을 사용해서 해결해 보자

1. 데이터 20,000개의 메일을 준비한다.

2. 두 개의 열로 구성하여 1열은 메일의 내용 2열은 스팸 여부를 판단한 내용을 입력한다. (행 ㅡ, 열 ㅣ) 그럼 총 20,000개의 행을 가지게 된다.

3. 데이터를 총 4개로 나눈다. 

1) 메일의 내용이 담긴 첫 번째 열을 X에 저장

2) 메일이 스팸인지 정상인지 정답이 적혀있는 두 번째 열에 y에 저장

3) 그리고 X와 y에 대해서 일부 데이터를 또다시 분리한다. 시험용으로 2,000 개를 분리한다고 가정

 

이때 분리 시에는 여전히 X와 y의 맵핑 관계를 유지해야 한다.

- 훈련 데이터

X_train: 문제지 데이터

y_train: 문제지에 대한 정답 데이터

- 데스트 데이

X_test: 시험지 데이터

y_test: 시험지에 대한 정답 데이터

 

기계는 이제부터 X_train과 y_train에 대해서 학습을 시작합니다. 기계는 y_train을 볼 수 있기 때문에 18,000개의 문제지를 어떤 메일 내용이 정상이고 어떤 메일이 스팸인지 열심히 규칙을 도출해 나가면서 정리해 나가게 됩니다. 그리고 학습을 다 한 기계에게 y_test를 보여주지 않고, x_test에 대해서 정답을 예측하게 합니다. 그리고 기계가 예측한 답과 실제 정답인 y_test를 비교하면서 기계가 정답을 얼마나 맞혔는지를 평가합니다. 이 수치가 기계의 정확도(Accuracy)가 됩니다.

 

2. x와 y분리하기

1) zip함수를 이용하여 분리하기

X,y = zip(['a',1], ['b',2], ['c',3])
# 시퀸스 자료형에서 각 순서에 등장하는 원소들끼리 묶어주는 역할을 한다.
#zip함수는 X와 y를 분리하는데 유용하다.
print(X,y) #X,y 순서대로 첫 번째는 X꺼 두 번째는 Y꺼
print('X 데이터:', X)
print('y 데이터:', y) 

#('a', 'b', 'c') (1, 2, 3)
#X 데이터: ('a', 'b', 'c')
#y 데이터: (1, 2, 3)

왜 sequences로 묶어주는가?

이유는 zip() 함수를 묶어주는 이유는 함수의 입력으로 하나의 시퀀스 자료형만 허용하기 때문이다. sequences변수에 여러 개의 리스트를 저장하고 있다면, zip() 함수에 *sequences를 전달하여 sequences변수에 저장된 모든 리스트를 순서대로 묶어줄 수 있다.

#리스트의 리스트 또는 행렬 또는 뒤에서 배울 개념인 2D 텐서
sequences = [['a',1], ['b',2], ['c',3]]
X,y = zip(*sequences) 

# `zip()` 함수에 `sequences` 변수를 직접 전달하면 에러가 발생한다.(TypeError)
# 그래서 그런 오류를 해결하기 위해
#`*sequences`를 전달하여 `sequences` 변수에 저장된 모든 리스트를 묶어줍니다.

print('X 데이터:', X) 
print('y 데이터:', y)

#X 데이터: ('a', 'b', 'c')
#y 데이터: (1, 2, 3)

2) 데이터프레임을 이용하여 분리하기

values = [['당신에게 드리는 마지막 혜택!', 1],
['내일 뵐 수 있을지 확인 부탁드...', 0],
['도연씨. 잘 지내시죠? 오랜만입...', 0],
['(광고) AI로 주가를 예측할 수 있다!', 1]]
columns = ['메일 본문', '스팸 메일 유무']

df = pd.DataFrame(values, columns=columns)
print(df)
#메일 본문	스팸 메일 유무
#0	당신에게 드리는 마지막 혜택!	1
#1	내일 뵐 수 있을지 확인 부탁드...	0
#2	도연씨. 잘 지내시죠? 오랜만입...	0
#3	(광고) AI로 주가를 예측할 수 있다!	1

print(df['메일 본문'])
#0          당신에게 드리는 마지막 혜택!
#1      내일 뵐 수 있을지 확인 부탁드...
#2      도연씨. 잘 지내시죠? 오랜만입...
#3    (광고) AI로 주가를 예측할 수 있다!
#Name: 메일 본문, dtype: object

데이터프레임은 열의 이름으로 각 열에 접근이 가능하므로, 이를 이용하여 X 데이터와 y데이터를 분리할 수 있다.

X = df['메일 본문'] #df객체를 각각 X변수에 저장
y = df['스팸 메일 유무'] #y변수에 저장

print('X 데이터 :',X.to_list())#X변수에 저장된 데이터를 리스트로 변환하는 코드이다.
print('y 데이터 :',y.to_list())#y도 마찬가지

#X 데이터:  ['당신에게 드리는 마지막 혜택!', '내일 뵐 수 있을지 확인 부탁드...', '도연씨. 잘 지내시죠? 오랜만입...', '(광고) AI로 주가를 예측할 수 있다!']
#y 데이터 : [1, 0, 0, 1]

3) Numpy를 이용하여 분리하기

임의의 데이터를 만들어서 Numpy의 슬라이싱(slicing)을 사용하여 데이터를 분리해 봅시다.

np_array=np.arange(0,16).reshape((4,4))
#np.arange(0,16) 0부터 15까지의 숫자를 넣는데
#reshape((4,4)) 4열4행 으로 맞춘다
#16개의 칸막이에 16개의 숫자가 들어가는게 아니면 에러 발생
print('전체 데이터:')
print(np_array)

#전체 데이터:
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]
#  [12 13 14 15]]

# 여기서 잠시 알아두기

#x1과 x2그리고 x3의 차이는?
x1 = np_array[:,3] #(0부터 셈)3번째 열을 추출
x2 = np_array[:3] #(0~2)3개의 행을 가져옴
x3 = np_array[:,:3] #(0~2)3개의 열을 가져옴
print(x1,'\n')
# [ 3  7 11 15] 

print(x2, '\n')
# [[ 0  1  2  3]
#  [ 4  5  6  7]
#  [ 8  9 10 11]] 

print(x3)
# [[ 0  1  2]
#  [ 4  5  6]
#  [ 8  9 10]
#  [12 13 14]]

마지막 열을 제외하고 X데이터에 저장합니다. 마지막 열만 y데이터에 저장합니다.

X = np_array[:, :3]
y = np_array[:,3]

print('X 데이터 :')
print(X)
print('y 데이터 :',y)

# X 데이터 :
# [[ 0  1  2]
#  [ 4  5  6]
#  [ 8  9 10]
#  [12 13 14]]
# y 데이터 : [ 3  7 11 15]

 

3. 테스트 데이터 분리하기

1) 사이킷 런을 이용하여 분리하기

#train_test_split 함수는 데이터를 학습 데이터와 테스트 데이터로 분할하는 함수이다.

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size= 0.2, random_state=1234)

#두 개의 인자를 입력받아 
#첫 번째 인자는 학습 데이터와 테스트 데이터로 분할할 데이터 비율을 80%와 20% 지정
#두 번째 인자는 테스트 데이터의 비율이다. 난수 생성기 시드값을 지정

X : 독립 변수 데이터. (배열이나 데이터프레임)
y : 종속 변수 데이터. 레이블 데이터.
test_size : 테스트용 데이터 개수를 지정한다. 1보다 작은 실수를 기재할 경우, 비율을 나타낸다.(둘 중에 하나만 기재해도 되기 때문에 test_size로 해줬다.)
train_size : 학습용 데이터의 개수를 지정한다. 1보다 작은 실수를 기재할 경우, 비율을 나타낸다.
random_state : 난수(예측할 수 없는 숫자) 시드(seed) = 데이터를 무작위로 분할할 때 사용한다.

 

random_state가 저는 아직 어려우니 이해를 위해 예시를 추가하겠습니다.

# 임의로 X와 y 데이터를 생성
X,y = np.arange(10).reshape((5,2)),range(5)

print('X 전체 데이터 :')
print(X)
print('y 전체 데이터 :')
print(list(y))

# X 전체 데이터 :
# [[0 1]
#  [2 3]
#  [4 5]
#  [6 7]
#  [8 9]]
# y 전체 데이터 :
# [0, 1, 2, 3, 4]

train_test_split()은 기본적으로 데이터의 순서를 섞고 나서 훈련 데이터와 테스트 데이터를 분리한다. 만약, random_state의 값을 특정 숫자로 기재해 준 뒤에 다음에도 동일한 숫자를 기재해 주면 항상 동일한 훈련 데이터와 테스트 데이터를 얻을 수 있다. 하지만 값을 변경해 주면 다른 순서로 섞인 채 분리되는 걸 확인할 수 있다. random_state값을 임의로 1234로 지정

# 7:3의 비율로 훈련 데이터와 테스트 데이터 분리
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1234)

print('X 훈련 데이터 :')
print(X_train)
print('X 테스트 데이터 :')
print(X_test,"\n")

print('y 훈련 데이터 :')
print(y_train)
print('y 테스트 데이터 :')
print(y_test)

# X 훈련 데이터 :
# [[2 3]
#  [4 5]
#  [6 7]]
# X 테스트 데이터 :
# [[8 9]
#  [0 1]]

# y 훈련 데이터 :
# [1, 2, 3]
# y 테스트 데이터 :
# [4, 0]

 

random_state값이 1234일 때 앞에 있는 샘플이 뒤로 가면서 전반적으로 섞이면서 분리된 것을 확인할 수 있다.

# random_state의 값을 변경 => 1
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=1)

print('y 훈련 데이터 :')
print(y_train)
print('y 테스트 데이터 :')
print(y_test)

# y 훈련 데이터 :
# [4, 0, 3]
# y 테스트 데이터 :
# [2, 1]

random_state의 값을 1로 변경해 줬을 때는 1234와 다르게 데이터가 다른 순서로 섞인 것을 확인할 수 있다.

여기서 다시 random_state를 1234로 변경해 주면 이전과 동일한 y데이터가 출력된다. 항상 동일한 순서로 섞인다.

 

2) 수동으로 분리하기

데이터를 분리하는 방법  하나는 수동으로 분리하는 것이다. 우선 임의로 X데이터와 y데이터를 만들어보자.

#실습을 위해 임의로 X와 y사 이미 분리 된 데이터를 생성
X,y= np.arange(0,24).reshape((12,2)), range(12)

print('X 전체 데이터 :')
print(X)
print('y 전체 데이터 :')
print(list(y))

# X 전체 데이터 :
# [[ 0  1]
#  [ 2  3]
#  [ 4  5]
#  [ 6  7]
#  [ 8  9]
#  [10 11]
#  [12 13]
#  [14 15]
#  [16 17]
#  [18 19]
#  [20 21]
#  [22 23]]
# y 전체 데이터 :
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

훈련 데이터의 개수와 데이터의 개수를 정해보자. num_of_train은 훈련 데이터의 개수를 의미하며, num_of_test는 테스트 데이터의 개수를 의미한다.

num_of_train = int(len(X)*0.8)#데이터의 전체 길이의 80%에 해당하는 길이값을 구한다.
num_of_test = int(len(X)- num_of_train)#전체 길이에서 80%에 해당하는 길이를 뺀다.
print('훈련 데이터의 크기 :',num_of_train)
print('테스트 데이터의 크기 :',num_of_test)

# 훈련 데이터의 크기 : 4
# 테스트 데이터의 크기 : 2

아직 훈련 데이터와 테스트 데이터를 나눈 것이 아니라 이 두 개의 개수를 몇 개로 할지 정하기만 한 상태이다. 여기서 num_of_test를 len(X) * 0.2로 계산해서는 안 된다. 데이터에 누락이 발생할 수 있다. 예를 들어서 전체 데이터의 개수가 4,518이라고 가정했을 때 4,158의 80%의 값은 3,614.4로 소수점을 내리면 3,614가 된다. 또한 4,518의 20%의 값은 903.6으로 소수점을 내리면 903이 된다.  그리고 3,614 + 903 = 4517이므로 데이터 1개가 누락이 된다. 그러므로 어느 한쪽을 먼저 계산하고 그 값만큼 제외하는 방식으로 계산해야 한다.

X_test = X[num_of_train:] # 전체 데이터 중에서 20%만큼 뒤의 데이터 저장
y_test = y[num_of_train:] # 전체 데이터 중에서 20%만큼 뒤의 데이터 저장
X_train = X[:num_of_train] # 전체 데이터 중에서 80%만큼 앞의 데이터 저장
y_train = y[:num_of_train] # 전체 데이터 중에서 80%만큼 앞의 데이터 저장
print('X 테스트 데이터 :')
print(X_test)
print('y 테스트 데이터 :')
print(list(y_test))

# X 테스트 데이터 :
# [[18 19]
#  [20 21]
#  [22 23]]
# y 테스트 데이터 :
# [9, 10, 11]

데이터를 나눌 때는 num_of_train와 같이 하나의 변수만 사용하면 데이터의 누락을 방지할 수 있다. 앞에서 구한 데이터의 개수만큼 훈련 데이터와 테스트 데이터를 분할한다. 그리고 테스트 데이터를 출력하여 정상적으로 분리되었는지 확인한다.

 

각 길이가 3인 것을 확인한다. train_test_split()(앞에서 나옴)과 다른 점은 데이터가 섞이지 않은 채 어느 지점에서 데이터를 앞과 뒤로 분리했다는 점이다. 만약, 수동으로 분리하게 된다면 데이터를 분리하기 전에 수동으로 데이터를 섞는 과정이 필요할 수 있다. 실제로 뒤에서 이러한 실습들을 진행한다.

728x90

'Artificial Intelligence > PyTorch' 카테고리의 다른 글

텐서 조작하기(3)  (4) 2024.01.10
텐서 조작하기(2)  (10) 2024.01.03
텐서 조작하기  (2) 2023.12.29
파이썬, 아나콘다, jupyter notebook 설치방법  (4) 2023.12.23