티스토리 뷰

개요

머신러닝 입문 할 때 붓꽃 예제를 많이 돌려봅니다. 이 예제를 하고 나면 잎의 가로 세로 길이 꽃받침 가로 세로 길이 총 4개의 값이 있으면 붓꽃의 종을 예측 할 수 있습니다.

 

이 모형은 분류(Classification)입니다. 왜냐하면 꽃잎 길이와 꽃받침 길이를 넣어주면 어떤 종인지를 예측하는 모델이기 때문입니다. 반대로 어떤 조건을 넣었을 때 꽃잎 길이라던지 꽃받침 길이를 예측해주는 모델이라고 하면 회귀(Regression)가 되겠습니다.

 

데이터셋 받아오기

데이터셋은 구글 스토리지에서 받아올 수 있습니다. 다음 코드를 이용하면 다운로드 받을 수 있습니다.

import tensorflow as tf
import os

train_dataset_url = "https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv"

train_dataset_fp = tf.keras.utils.get_file(fname=os.path.basename(train_dataset_url),
                                           origin=train_dataset_url)

print("데이터셋이 복사된 위치: {}".format(train_dataset_fp))

 

결과

C:\Users\Kyeongrok\.keras\datasets\iris_training.csv

 

iris.csv
0.00MB

위에 업로드 해놓은 파일을 이용 하셔도 됩니다.

 

학습시킬 받은 데이터는 순서대로 화분의 길이, 넓이 꽃잎의 길이, 넓이이고 마지막은 라벨인데 종입니다.

column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
iris = pd.read_csv(train_dataset_fp, names=column_names)

 

이 데이터를 그냥 불러오면 맨 위에 필드명이 120, 4, setosa... 등으로 되어 있기 때문에 pd.read_csv()할 때 names=를 주어서 보기좋게 필드명을 만들어 줄 수 있습니다.

하지만 문제가 하나 있지요 0번 row입니다. 저 내용은 메타데이터입니다. 현 상태에서는 불순물이라고 할 수 있습니다.

column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
iris = pd.read_csv(train_dataset_fp, names=column_names)
iris = iris[1:]

그래서 위와 같이 iris[1:]이렇게 잘라 줍니다.

그러면 120개 row 5개 칼럼의 데이터로 잘 만들어 집니다.

 

Model만들고 학습 시키고 예측까지

import tensorflow as tf
import pandas as pd
import numpy as np

train_dataset_url = "https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv"

iris = pd.read_csv(train_dataset_url, header=0)

column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']
iris = pd.read_csv(train_dataset_fp, names=column_names)
iris = iris[1:]

x = iris[column_names[:4]].values
y = iris[column_names[4]].values

# Model 만들기
model = Sequential([
    Dense(10, activation=tf.nn.relu),
    Dense(1)
])
model.compile(loss='categorical_crossentropy', metrics='accuracy')

# 학습 시키기
model.fit(np.array(x).astype(np.float32), np.array(y).astype(np.float32), epochs=30)

# 예측 하기
print(model.predict(x_data[:5]))

일단 위와 같이 데이터, 모델, 학습, 예측 4단계를 진행할 수 있습니다.

 

model.fit(np.array(x).astype(np.float32), np.array(y).astype(np.float32), epochs=10)

위와 같이 .astype()을 붙여준 이유는 제가 쓰는 넘파이 버전 1.20.1에서 ValueError가 나기 때문입니다. .astype을 써주면 에러가 나지 않아서 써주었습니다.

 

 

결과

array([[-0.08714902],

[-0.20299137],

[-0.17674887],

[-0.69071734],

[-0.74491215]], dtype=float32)

 

120개를 학습시키고 x의 앞에서 5개를 그대로 넣어서 예측 해본 결과 입니다. 결과가 이상합니다. 조금씩 고쳐보겠습니다.

 

먼저 여기까지는 무작정 따라오셨다면 정리를 한번 해보고 가겠습니다. 

 

x값의 형태

x = [
[6.4, 2.8, '5.6', '2.2'],
[5.0, 2.3, '3.3', '1.0'],
[4.9, 2.5, '4.5', '1.7'],
[4.9, 3.1, '1.5', '0.1']
]

x 값은 위와 같이 생겼습니다. 각각 'sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species' 입니다.

 

y값의 형태

y = ['2', '1', '2', '0', '0', '0', '0', '2', '1', '0', '1', '1', '0',
       '0', '2', '1', '2', '2', '2', '0', '2', '2', '0', '2', '2', '0',
       '1', '2', '1', '1', '1', '1', '1', '2', '2', '2', '2', '2', '0',
       '0', '2', '2', '2', '0', '0', '2', '0', '2', '0', '2', '0', '1',
       '1', '0', '1', '2', '2', '2', '2', '1', '1', '2', '2', '2', '1',
       '2', '0', '2', '2', '0', '0', '1', '0', '2', '2', '0', '1', '1',
       '1', '2', '0', '1', '1', '1', '2', '0', '1', '1', '1', '0', '2',
       '1', '0', '0', '2', '0', '0', '2', '1', '0', '0', '1', '0', '1',
       '0', '0', '0', '0', '1', '0', '2', '1', '0', '2', '0', '1', '1',
       '0', '0', '1']

y값은 위와 같이 0, 1, 2 세개 중 한개의 값 입니다. 여기에서 0, 1, 2는 연속된 숫자 처럼 보이지만 0번종, 1번종, 2번종을 가리키는 값일 뿐입니다.

 

그래서 우리가 원하는 것은 [6.3, 3.3, '4.7', '1.6'] 이렇게 데이터를 넣었을 때 0번종인지 1번종인지 2번종인지를 예측하는 것입니다.

 

하지만 위에 결과는 [-0.08714902],[-0.20299137],[-0.17674887],[-0.69071734],[-0.74491215] 이런식으로 나와서 별로 쓸모가 없습니다. 그러면 위에 -0.08714902, -0.20299137 이런 값들 대신 0, 1, 2가 나오게 하려면 어디를 바꾸어야 할까요?

 

loss function을 sparse_categorical_crossentropy로 바꾸기

가장 먼저 loss_function을 바꿔주어야 합니다. 기존에는 categorical_crossentropy로 되어 있습니다만 이것을 쓰는 경우는 [0, 1, 0]이런 식으로 one-hot으로 데이터가 들어올 때 이렇게 씁니다. 0, 1, 2를 one-hot으로 바꿀 수도 있습니다만 그러면 한단계를 더 거쳐야 하기 때문에 조금 더 복잡해 집니다. 그래서 케라스에는 sparse_categorical_crossentropy라는 loss function이 있어서 이 과정을 편하게 해줍니다. sparse_categorical_crossentropy는 0, 1, 2로 학습 시키면 각각일 확률을 [-1.714381 , -3.4065707 , -0.401332 ] 이런식으로 보여줍니다.

 

model = Sequential([
    Dense(10, activation=tf.nn.relu),
    Dense(1)
])
# model.compile(loss='categorical_crossentropy', metrics='accuracy')
model.compile(loss='sparse_categorical_crossentropy', optimizer='sgd', metrics='accuracy')
model.fit(np.array(x).astype(np.float32), np.array(y).astype(np.float32), epochs=30)

위와 같이 바꾸어 주면 됩니다. 그리고 실행을 해보시기 바랍니다. 그러면 아래와 같은 InvalidArgumentError 에러가 납니다.

InvalidArgumentError: Received a label value of 2 which is outside the valid range of [0, 1).
Label values: 2 2 2 2 0 2 0 1 1 2 2 1 2 2 0 1 0 2 2 2 0 2 1 0 2 0 0 2 1 0 1 1 
[[node sparse_categorical_crossentropy/SparseSoftmaxCrossEntropyWithLogits/SparseSoftmaxCrossEntropyWithLogits (defined at <ipython-input-56-f74995bd44bb>:7) ]] [Op:__inference_train_function_23483]

에러메세지의 뜻은 Label value는 222220201122 인데 대상 범위인 0에서 벗어났다는 뜻입니다. 그래서 문제는 Dense(1) 이렇게 출력이 값 1개로 되어 있는 것이 문제입니다. 이것을 0, 1, 2 각각의 확률로 나타내기 위해 Dense(3)으로 바꿔주어야 합니다.

model = Sequential([
    Dense(10, activation=tf.nn.relu),
    Dense(3)
])
model.compile(loss='sparse_categorical_crossentropy', optimizer='sgd', metrics='accuracy')

모델은 이런식으로 해주면 됩니다.

 

입력이 [6.3, 3.3, '4.7', '1.6'] 이렇게 4개가 입력되고 출력이  [-1.714381 , -3.4065707 , -0.401332 ] 이렇게 3개의 확률로 나오고 중간에 10개의 노드가 1층 있기 때문에 아래와 같은 모양의 네트웍입니다.

 

이 모델로 돌리면 아래와 같은 결과가 나옵니다.

[ -0.4878862 , -13.000133  ,   7.956099  ],
[  0.10096001, -10.31357   ,   5.6980186 ],
[ -0.30338907, -10.385643  ,   6.140797  ],
[  1.0316837 , -10.721741  ,   4.8608565 ],
[  1.2603164 , -12.517324  ,   5.6669436 ]

아직도 0, 1, 2로 나오지는 않고 있습니다.

 

그래서 activation function을 relu와 softmax를 넣어주겠습니다.

model = Sequential([
    Dense(10, activation=tf.nn.relu, input_dim=4),
    Dense(3, activation=tf.nn.softmax)
])

model.compile(loss='sparse_categorical_crossentropy', optimizer='sgd')

# 학습시키기
model.fit(x, y, epochs=100)

# 예측
model.predict(x[:5])

결과

array([[0.01272912, 0.32815033, 0.65912056],
       [0.11130091, 0.4298703 , 0.45882875],
       [0.03837062, 0.39744723, 0.5641821 ],
       [0.9107736 , 0.05340891, 0.03581754],
       [0.94999933, 0.02993372, 0.02006695]], dtype=float32)

결과 해석

첫번째 줄은 0, 1 2중 2번이 65%로 가장 높으므로 2번

두번째 줄은 0, 1 2중 1번이 42.9%로 가장 높으므로 1번

이런식입니다.

 

[2, 1, 2, 0, 0]

실제 답은 위와 같습니다. 3번째도 56%로 2번이 높다고 예측했고 4, 5번은 각각 0번이 높다고 예측하여 잘 예측한 것을 볼 수 있습니다.

 

end

 

 

 

 

 

 

 

 

 

728x90
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함