Tensorflow입문기 및 선형회귀분석 (Linear regression)
Category: Technology
<Tensorflow 입문기와 선형회귀분석(Linear regression)>
지난번 포스트에서 머신러닝의 지도학습/비지도학습과 간단한 Classifying/Clustering 예제를 살펴봤었다. 사실 이번에는 KNN, KMeans와 같은 Classifying, Clustering 알고리즘에 대한 포스팅을 하려고 했는데 텐서플로우를 살짝 다뤄보면서 해보게 된 '선형회귀분석' 에 대해서 먼저 포스팅해볼까 한다. 선형 회귀분석이 무엇인지, Tensorflow를 이용해서 선형회귀분석을 어떻게 하는지, 그리고 직접 데이터셋을 구해서 실행해본 회귀분석에 대해 다뤄보겠다.
선형 회귀분석
지난번 포스트에서 간단히 설명했지만, 선형 회귀분석은 수많은 데이터로 이루어진 데이터셋에 대해, 변수 간의 상관관계를 가장 잘 설명해주는 함수식을 찾는 과정이다. 예를들어, 1000명으로 이루어진 어떤 마을 사람 전체의 키와 몸무게에 대한 자료가 있다고 하자. 키를 x축으로, 몸무게를 y축으로 한 좌표평면에 이 마을사람들 1000명에 해당하는 [키, 몸무게]데이터를 1000개의 점으로 찍는다고 가정하자. 이 마을 사람들의 키와 몸무게가 양의 상관관계를 띄고 (키와 몸무게가 어느정도 비례하고), 어찌어찌해서 계산을 했더니 대부분 사람들의 키가 몸무게의 3.5배정도 됨을 알아냈다. 이 경우 이 마을사람들의 [키, 몸무게] 데이터는 '키 = 몸무게 * 3.5' 라는 함수식으로 선형회귀되며, 1001번째 새로운 마을사람의 몸무게를 알 때, 이 사람의 키를 대략적으로 유추할 수 있을것이다.
위의 예시는 어디까지나 예시일 뿐이다. 회귀분석을 직접 Tensorflow와 파이썬을 이용해 해보자. 아래의 코드는 [x, y] 로 이루어진 인위적으로 1000개의 점(Data point)가 대략적으로 y = x*0.1 + 0.3 이라는 함수 관계를 따르도록 생성하는 과정이다. 그러나 모든 점이 정확히 y=x*0.1 + 0.3이라는 함수관계를 정확히 따르는 경우는 현실성이 없으므로, 무작위적인 수의 편자를 조금씩 주기 위해 난수를 더해주었다.
즉, y = x *0.1 + 0.3 + a (a는 무작위적인 난수) 의 함수관계로 선형회귀되는 데이터셋을 생성한것이다.
이제 (x1, y1)부터 (x1000, y1000)까지 1000개의 data point를 가지는 dataset이 완성되었다. 이는 기본적으로 yn = xn*0.1 + 0.3 라는 식을 중심으로 선형회귀할 것이다. 즉, xn*0.1 + 0.3 식에서 크게 벗어나지 않는 선에서 xn, yn가 함수가 관계를 가진다는 것이다. 만일 새로운 데이터 x값으로 10이 주어졌다면 우리는 이 데이터의 y값이 10을 x1*0.1 + 0.3에 대입한 값인 1.3에 근접할 것이라 예측할 수 있다.
자, 그럼 이제 위의 데이터가 yn = xn*0.1 + 0.3이라는 식을 바탕으로 만들어졌다는 사실을 모른다고 가정하다. 아무런 정보가 없는 상태에서 위와 같은 (x1, y1)부터 (x1000, y1000)까지 1000개의 데이터가 주어졌고, 우리는 1000개의 데이터가 대략 어떤 패턴으로 구성되는지, x와 y의 변수가 어떤 관계를 가지는지 알고싶어한다. 그래서 미래의 데이터에 대해 x값만 가지고 y값을 추측하고 싶어한다. 이떻게 하면 될까?
x_data의 변수에는 x의 값들이, y_data 변수에는 y의 값들이 순서대로 할당되어있다. 이제 우리는 x_data를 넣었을때 y_data가 출력되는 함수관계를 찾아내면 된다. 변수가 두개인 선형회귀분석은 '선형'(linear)함수로 회귀시키므로 다음과 같은 일반식을 쓸 수 있다.
우리는 y=W*x + b의 직선을 위의 그래프 위에 그린다고 생각해보자.
y = W1 * x + b1과 y=W2 * + b2의 두 직선중에, 어느 함수의 그래프가 위 데이터셋을 잘 설명해주는 모델이라고 볼 수 있겠는가? 당연히 첫 번째 그래프이다. 이렇게 W와 b 의 값에 따라 선형회귀의 결과달라진다. 따라서, 데이터셋의 두 변수 (x, y)의 관계를 가장 잘 설명해줄 수 있는 최적의 W와 b 값을 찾음으로써 선형회귀분석이 이루어진다.
최적의 W와 b 값을 찾기 위해 우리는 실제 데이터 [x, y]와 x를 함수식에 넣었을 때 출력되는 함숫값 사이의 거리를 식으로 정의한 후 이를 최소화하면 된다. 즉,
이것이 파라미터가 되며, 이 값을 최소화하면 된다는 이야기이다. 이 파라미터를 Mean Square Error라고 한다.
예를들어, 아래의 그림에서 1000개의 데이터 포인트 중 하나인 파란색 점 (a, y1)은, 두 번째 함수식의 그래프보다 첫 번째 식의 그래프에 더 가깝다. MSE의 식에서 (Yi-Y'i)^2에 해당하는 거리값이 첫 번째 식의 경우에 더 작게 나타나는것이다. 이런식으로 모든 점에 대해서 (Yi-Y'i)^2의 거리값을 구해 평균을 내고, 이 평균 거리값이 최소가 되도록 최적화시키는 W, b값을 찾으면 된다.
'그걸 어떻게 계산해?' 라고 생각할 수도 있겠지만 다행히도 파이썬과 텐서플로우 라이브러리에는 최적화 연산을 위한 패키지가 있다. 우리는 그저 알고리즘을 세팅하고 코드를 돌리기만 하면 된다. 우선 텐서플로우를 통해 W, b, y값을 변수로 정의하고, MSE역시 loss라는 변수로 만들어 정의한다.
이제 필요한 작업이 모두 완료되었다. Gradient Descent Optimizer는 알아서 loss 값을 최소화시키는 W와 b의 값을 찾아줄 것이다. 0.5는 최적화 학습의 속도를 나타내는 수치이다. 이 수치가 클수록 최적화를 하며 건너뛰는 간격이 늘어나기에 더 적은 Step을 소요해서 최적값을 찾을 수 있지만, 최적의 수치를 건너뛰는 일이 생길 수 있다. 반대로 수치가 낮으면 더 섬세한 최적화가 진행되지만 훨씬 많은 Step 이 소요된다. 이제 15번의 스텝에 걸쳐 최적화를 시행하고, 매 스텝마다 선형회귀된 그래프를 그려보자.
우선 W 와 b 의 값이 각각 점점 0.1과 0.3에 가까워짐을 알 수 있다
이렇게 점점 데이터에 '적합한' 선형함수를 찾아가는것을 볼 수 있다. 이렇게 우리는 W와 b의 값을 모르고 시작했음에도 불구하고 위의 데이터셋을 선형회귀함으로써 y= [0.10470821]x + [0.3000459] 즉 약 0.1x+0.3 의 함수식으로 회귀함을 알 수 있다. 그렇다면 우리는 이 데이터셋에 한 변수값(x)을 아는 새로운 데이터가 유일될 때 이 데이터의 또다른 변수값(y)를 예측할 수 있다.
마지막으로 이 데이터의 선형회귀분석이 얼마나 정확한지 살펴보자. 물론 이 데이터는 특정한 선형 함수식으로부터 생성되었기때문에 상당히 정확할 수 밖에 없다. 여기서 우리가 살펴볼 수치 두 가지는 상관계수와 결정계수이다. 먼저 상관계수는 두 변수간의 상관관계를 보여주며 -1과 1사이의 값을 가진다. 음수이면 음, 양수이면 양의 상관관계를 가지며 절댓값이 0.3보다 작으면 매우 적은 상관관계를, 0.7보다 높으면 상대적으로 높은 상관관계를 의미한다. 결정계수는 회귀분석이 진행된 후, 회귀분석이 얼마나 해당 데이터에 대해 얼마나 정확한지를 보여주는 계수이다 위의 과정을 보았듯이 회귀분석은 loss값 (MSE 값)을 최소화시키는 식을 구해준다. 그러나 loss값이 최소인 식이라고 해도, 모든 데이터값이 특정 함수식의 그래프를 따라서만 존재하는 경우가 아니라면 분명히 어느정도의 오차는 존재한다. 만일 회귀 모델 함수식을 기준으로 데이터가 많이 퍼져있다면 오차는 더욱 더 커질 것이다.
상관분석과 회귀분석은 분명 다르다. 통계학에서 상관분석은 '상관관계'를 밝히기 위한 분석이며 회귀분석은 한 변수와 다른 변수간의 '인과관계'를 밝히는 분석이다. 그렇지만 대부분의 경우 상관관계가 아주 강하면 자연스럽게 데이터는 특정 선형 회귀식을 기준으로 밀집해 분포할 것이고, 결국 회귀분석의 정확도가 높다고 봐도 무방하겠다. 반대로 상관관계가 없거나 아주 약하다면, MSE 값을 최소화하는 선형회귀모델을 찾았다 하더라도 데이터는 이 모델에서 아주 멀리 떨어진 곳에, 퍼져서 분포할 것이다. 이 경우 선형회귀모델이 데이터에 적합하다고 볼 수 없다.
먼저 x와 y값들의 데이터를 Dataframe형태로 만들어 한번에 상관계수와 결정계수를 구해보자. 결정계수(R^2)를 구하는 방법으로는 두 가지가 있는데, 먼저 일반적으로는:
위의 과정을 통해 구하고, 독립변수와 종속변수가 한 개일 때는 상관계수의 제곱으로 구할수도 있다. 두 가지 방식 통해 결정계수를 두 번 따로 구해보자. (COF, COF2 로 설정)
상관계수는 약 86프로로 강한 상관관계를 띄며, 결정계수 역시 74프로, 99프로로 매우 강한 확실성을 보여준다. 독립변수가 한 개인 경우이기 때문에 첫 번째 방법으로 구한 결정계수가 조금 더 유효하지 않을까 싶지만, 두 가지 방법으로 구한 결정계수 값의 차이에 대해서는 더 알아보면 좋을것같다. (혹시 정확히 아는 사람이있다면 메일이나 댓글을 달아주면 좋을 듯 하다)
응용해보자 - NBA 선수 Stat 데이터
방금전의 예시를 통해 선형회귀분석을 해 보았다. 그러나 방금 전의 분석은 특정한 함수식으로부터 데이터를 생성하고, 다시 회귀분석을 통해 그 함수식을 찾는 과정이었기에 실제 데이터분석 상황과는 거리가 있다.말하자면 답을 정해놓고 답에서 문제를 만든 후, 다시 그 문제를 풀어 답을 구하는 식이었다. 이번에는 완전히 새로운 데이터를 가지고와서 회귀분석을 해보자. 오늘의 데이터셋은 kaggle에서 구해온 NBA 선수들의 STAT 데이터셋이다. 데이터셋과 데이터게 대한 상세 정보는 여기에서 구할 수 있다. 자, 먼저 데이터를 불러오고 한번 어떻게 생겼는지 살펴보자. (스크롤 주의!!!)
이제 훨씬 깔끔해졌다. 그런데 이 데이터를 이리저리 다루고 회귀분석을 시도해보니 한 가지 문제가 있었다. 득점, 어시스트, 그리고 파울의 수 데이터는 모두 다른 기준(scale)에 따라 작성된 데이터이고, 그래서 그런지 이 데이터를 그대로 사용했더니 inf(무한)이나 Nan과 같은 이상한 값을 뱉어냈다. 변수 데이터들을 표준화(Standarzation)해줘야 하는 것인가 해서 찾아보았더니 역시 비슷한 예제에서 변수를 표준화하는 것을 찾아볼 수 있었다. 정확히 어떤 경우에 표준화를 해 주어야 하는건지는 모르겠으나 구글과 stack overflow등을 검색해보니 이 경우처럼 변수들의 척도(scale)이 완전 다를 경우에 표준화를 해야 한다는 이야기도 있고, 회귀하려는 함수식의 항이 여러개 (즉 변수가 여러개인 다중회귀분석의 경우)인 경우에
표준화를 해주어야 한다는 이야기도 있다. (이것 역시 정확한 정보를 아는 사람이 있다면 역시 댓글이나 이메일을 주면 좋을것같다)
변수들을 표준화하는 함수를 만들어 표준화해보자. 표준화 함수를 정의하는 김에 위에서 만들었던 COF2함수 (결정계수를 계싼하는 함수)도 미리 같이 정의해봤다.
이제 변수값들을 깔끔하게 표준화해주었다. 이제 한번 데이터들의 분포를 봐보자. Point를 종속변수 y값으로, 파울과 어시스트를 독립변수로 해서 파울과 어시스트가 선수의 득점에 어떤 영향(?)을 미치는지를 한번 봐 보고싶다. 왠지 NBA경기를 보다보면 사실당 파울은 특정한 팀/개인 파울의 수를 넘기지만 않으면 패널티가 없으며 득점이 많은 선수일수록 상대 수비와 격하게 부딪힐 일이 많기 때문에 파울과 득점간의 관계가 어느정도 있을 것 같았다. 하지만 역시 상관관계와 인과관계는 다른 이야기이다. 나는 파울을 독립변수와 득점이라는 종속변수 사이에 어느정도 인과관계가 있을거라고 생각했다. 파울이 직접적인 득점을 발생시키는 것은 아니지만 파울이 많을수록 상대 수비를 재끼고 득점을 하기 위한 적극적인 플레이를 했다는 점을 의미한다고 생각했다. 어시스트의 경우에는 독립변수로 뒀을 때 득점과 어떤 관계를 가질지 머릿속으로는 도저히 그림이 그려지지 않았다. 그렇지만 빅데이터에서는 인과성 (여기선 독립-종속변수에 대한 인과성보다는 '왜 일어난 것인지' 이유에 대한 물음을 말함)보다는 상관성과 결과가 더 중요하기 때문에 일단은 모델에 넣고 돌려보기로 했다. 또, 마지막으로 하는김에 파울과 어시스트도 독립-종속변수 관계에 넣어 모델을 실행해보기로 했다.
한번 세 변수를 그래프 위에 놓고 데이터의 분포를 봐보자
일단 득점과 파울/득점과 어시스트 사이에는 어느정도 선형 관계가 존재하는 듯 보인다. 그러나 변수 값이 커짐에 따라 점점 분포가 퍼지는 모습을 보여주고 있다. 아직 분석은 하지 않았지만 변수 값이 작을수록 상관성/인과성이 크게 나타날것으로 보이는데 이는 아마 어시스트, 파울, 득점 모두 경기를 뛴 시간과 경기에 참여한 적극성과 밀접한 관계가 있기 때문이지 않을까 싶다. 플레이시간/경기에 참여한 적극성이 낮을수록 경기에서 기록할 수 있는 득점/파울/어시스트의 수도 상대적으로 줄어들 것이다. 반면 경기에 참여한 시간이 길고 더 적극적으로 참여한 선수일수록 기록할 수 있는 득점/파울/어시스트의 폭이 넓어진다(수가 많아진다는것이 아니다 - 경기를 적극적으로 오래 뛰어도 득점이 적은 선수가 있는 반면, 긴 경기시간동안 많은 득점을 내는 선수가 있다.) 이번엔 경기 시간이나 경기 참여의 적극성을 반영할 수 있는 변수를 제외했지만, 다음번에 그러한 변수를 포함한 분석도 추가적으로 해보고싶다.
어쨌건 다시 분석을 해보자. 이제 앞에서 했던것과 같은 방법으로 tensorflow를 이용해 선형회귀분석을 하나씩 진행해보자. 다른 점이라면 Step을 20개로, 학습도는 0.3으로 설정해보겠다. 코드는 한꺼번에 첨부한다 (스크롤 주의):
출력값을 보니 loss 가 약 여전히 0.4로 남아있다. 아마 분포가 워낙 퍼져있기 때문에 (차원을 일그러뜨리지 않는이상...)그 이상 줄이는것이 거의 불가능할 것이다. 상관계수는 0.77정도로 생각보다 큰 값을 보여줬다. 확실히 어느정도 파울과 득점간 상관관계가 있음은 분명하다. 그러나 이 모델의 정확도는 0.5~0.6 사이 수준으로, 아주 나쁜 건 아니지만 그렇다고 정확하다고도 할 수 없는 수준을 보여줬다. 아마 독립변수의 값이 작을수록 0.5보다 높은 정확도를, 점차 높아질수록 0.5보다 낮은 정확도를 보이지 않을까 싶다.
<Tensorflow 입문기와 선형회귀분석(Linear regression)>
지난번 포스트에서 머신러닝의 지도학습/비지도학습과 간단한 Classifying/Clustering 예제를 살펴봤었다. 사실 이번에는 KNN, KMeans와 같은 Classifying, Clustering 알고리즘에 대한 포스팅을 하려고 했는데 텐서플로우를 살짝 다뤄보면서 해보게 된 '선형회귀분석' 에 대해서 먼저 포스팅해볼까 한다. 선형 회귀분석이 무엇인지, Tensorflow를 이용해서 선형회귀분석을 어떻게 하는지, 그리고 직접 데이터셋을 구해서 실행해본 회귀분석에 대해 다뤄보겠다.
선형 회귀분석
지난번 포스트에서 간단히 설명했지만, 선형 회귀분석은 수많은 데이터로 이루어진 데이터셋에 대해, 변수 간의 상관관계를 가장 잘 설명해주는 함수식을 찾는 과정이다. 예를들어, 1000명으로 이루어진 어떤 마을 사람 전체의 키와 몸무게에 대한 자료가 있다고 하자. 키를 x축으로, 몸무게를 y축으로 한 좌표평면에 이 마을사람들 1000명에 해당하는 [키, 몸무게]데이터를 1000개의 점으로 찍는다고 가정하자. 이 마을 사람들의 키와 몸무게가 양의 상관관계를 띄고 (키와 몸무게가 어느정도 비례하고), 어찌어찌해서 계산을 했더니 대부분 사람들의 키가 몸무게의 3.5배정도 됨을 알아냈다. 이 경우 이 마을사람들의 [키, 몸무게] 데이터는 '키 = 몸무게 * 3.5' 라는 함수식으로 선형회귀되며, 1001번째 새로운 마을사람의 몸무게를 알 때, 이 사람의 키를 대략적으로 유추할 수 있을것이다.
위의 예시는 어디까지나 예시일 뿐이다. 회귀분석을 직접 Tensorflow와 파이썬을 이용해 해보자. 아래의 코드는 [x, y] 로 이루어진 인위적으로 1000개의 점(Data point)가 대략적으로 y = x*0.1 + 0.3 이라는 함수 관계를 따르도록 생성하는 과정이다. 그러나 모든 점이 정확히 y=x*0.1 + 0.3이라는 함수관계를 정확히 따르는 경우는 현실성이 없으므로, 무작위적인 수의 편자를 조금씩 주기 위해 난수를 더해주었다.
즉, y = x *0.1 + 0.3 + a (a는 무작위적인 난수) 의 함수관계로 선형회귀되는 데이터셋을 생성한것이다.
import numpy as np
import tensorflow as tf
import matplotlib.pyplot as plt
num_points = 1000
vectors_set = []
for i in range(num_points):
x1 =np.random.normal(0.0, 0.5)
y1 = x1*0.1 + 0.3 + np.random.normal(0.0, 0.03) #y = x*0.1 + 0.3의 함수식으로 회귀되는 1000개의 [x, y]데이터를 생성
vectors_set.append([x1, y1])
#y = 0.1x + 0.3의 관계에있는 점들에 조금의 변수를 줌 (normal distribution의 편차를 둠))
x_data = [v[0] for v in vectors_set]
y_data = [v[1] for v in vectors_set]
plt.plot(x_data, y_data, "ro", label = "Original Data")
plt.legend()
plt.show()
이제 (x1, y1)부터 (x1000, y1000)까지 1000개의 data point를 가지는 dataset이 완성되었다. 이는 기본적으로 yn = xn*0.1 + 0.3 라는 식을 중심으로 선형회귀할 것이다. 즉, xn*0.1 + 0.3 식에서 크게 벗어나지 않는 선에서 xn, yn가 함수가 관계를 가진다는 것이다. 만일 새로운 데이터 x값으로 10이 주어졌다면 우리는 이 데이터의 y값이 10을 x1*0.1 + 0.3에 대입한 값인 1.3에 근접할 것이라 예측할 수 있다.
자, 그럼 이제 위의 데이터가 yn = xn*0.1 + 0.3이라는 식을 바탕으로 만들어졌다는 사실을 모른다고 가정하다. 아무런 정보가 없는 상태에서 위와 같은 (x1, y1)부터 (x1000, y1000)까지 1000개의 데이터가 주어졌고, 우리는 1000개의 데이터가 대략 어떤 패턴으로 구성되는지, x와 y의 변수가 어떤 관계를 가지는지 알고싶어한다. 그래서 미래의 데이터에 대해 x값만 가지고 y값을 추측하고 싶어한다. 이떻게 하면 될까?
x_data의 변수에는 x의 값들이, y_data 변수에는 y의 값들이 순서대로 할당되어있다. 이제 우리는 x_data를 넣었을때 y_data가 출력되는 함수관계를 찾아내면 된다. 변수가 두개인 선형회귀분석은 '선형'(linear)함수로 회귀시키므로 다음과 같은 일반식을 쓸 수 있다.
y_data = W*x_data + b
우리는 y=W*x + b의 직선을 위의 그래프 위에 그린다고 생각해보자.
y = W1 * x + b1과 y=W2 * + b2의 두 직선중에, 어느 함수의 그래프가 위 데이터셋을 잘 설명해주는 모델이라고 볼 수 있겠는가? 당연히 첫 번째 그래프이다. 이렇게 W와 b 의 값에 따라 선형회귀의 결과달라진다. 따라서, 데이터셋의 두 변수 (x, y)의 관계를 가장 잘 설명해줄 수 있는 최적의 W와 b 값을 찾음으로써 선형회귀분석이 이루어진다.
최적의 W와 b 값을 찾기 위해 우리는 실제 데이터 [x, y]와 x를 함수식에 넣었을 때 출력되는 함숫값 사이의 거리를 식으로 정의한 후 이를 최소화하면 된다. 즉,
이것이 파라미터가 되며, 이 값을 최소화하면 된다는 이야기이다. 이 파라미터를 Mean Square Error라고 한다.
예를들어, 아래의 그림에서 1000개의 데이터 포인트 중 하나인 파란색 점 (a, y1)은, 두 번째 함수식의 그래프보다 첫 번째 식의 그래프에 더 가깝다. MSE의 식에서 (Yi-Y'i)^2에 해당하는 거리값이 첫 번째 식의 경우에 더 작게 나타나는것이다. 이런식으로 모든 점에 대해서 (Yi-Y'i)^2의 거리값을 구해 평균을 내고, 이 평균 거리값이 최소가 되도록 최적화시키는 W, b값을 찾으면 된다.
'그걸 어떻게 계산해?' 라고 생각할 수도 있겠지만 다행히도 파이썬과 텐서플로우 라이브러리에는 최적화 연산을 위한 패키지가 있다. 우리는 그저 알고리즘을 세팅하고 코드를 돌리기만 하면 된다. 우선 텐서플로우를 통해 W, b, y값을 변수로 정의하고, MSE역시 loss라는 변수로 만들어 정의한다.
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = x_data * W + b
loss = tf.reduce_mean(tf.square(y-y_data))
이제 이 loss의 값을 최소화하기 위해 Gradient Descent (경사 하강법)이라는 알고리즘을 불러와 사용할것이다. (자세한 내용은 위키 참조). 간단히 설명하자면 함수의 기울기를 계산해 극값에 도달할 때 까지 경사를 따라 이동하면서 최적값을 찾는 알고리즘이다.
optimizer = tf.train.GradientDescentOptimizer(0.5) # Gradient Descent 알고리즘을 불러온다
train = optimizer.minimize(loss) #loss를 최소화하는 방식으로 optimizer를 설정해준다
init = tf.global_variables_initializer() #변수를 초기화해준다
sess = tf.Session()
sess.run(init) #Session을 통해 위의 작업들을 실행시킬 준비를 한다
이제 필요한 작업이 모두 완료되었다. Gradient Descent Optimizer는 알아서 loss 값을 최소화시키는 W와 b의 값을 찾아줄 것이다. 0.5는 최적화 학습의 속도를 나타내는 수치이다. 이 수치가 클수록 최적화를 하며 건너뛰는 간격이 늘어나기에 더 적은 Step을 소요해서 최적값을 찾을 수 있지만, 최적의 수치를 건너뛰는 일이 생길 수 있다. 반대로 수치가 낮으면 더 섬세한 최적화가 진행되지만 훨씬 많은 Step 이 소요된다. 이제 15번의 스텝에 걸쳐 최적화를 시행하고, 매 스텝마다 선형회귀된 그래프를 그려보자.
for step in range(15):
sess.run(train)
print (step, sess.run(W), sess.run(b))
plt.plot(x_data, y_data, 'ro')
plt.plot(x_data, sess.run(W) * x_data + sess.run(b))
plt.legend()
plt.show()
# 아래에는 순서대로 Step, W, b의 값이 출력된다
1 [0.56075096] [0.3017533]
2 [0.44250873] [0.3013753]
3 [0.3546617] [0.30109444]
4 [0.28939652] [0.3008858]
5 [0.24090834] [0.30073076]
6 [0.20488447] [0.3006156]
7 [0.17812085] [0.30053005]
8 [0.15823705] [0.30046648]
9 [0.14346457] [0.30041924]
10 [0.13248947] [0.30038416]
11 [0.12433563] [0.3003581]
12 [0.1182778] [0.30033875]
13 [0.11377719] [0.30032435]
14 [0.1104335] [0.30031365]
15 [0.10470821] [0.3000459]
우선 W 와 b 의 값이 각각 점점 0.1과 0.3에 가까워짐을 알 수 있다
<15번의 step을 거친 후 최종적인 선형회귀모델>
이렇게 점점 데이터에 '적합한' 선형함수를 찾아가는것을 볼 수 있다. 이렇게 우리는 W와 b의 값을 모르고 시작했음에도 불구하고 위의 데이터셋을 선형회귀함으로써 y= [0.10470821]x + [0.3000459] 즉 약 0.1x+0.3 의 함수식으로 회귀함을 알 수 있다. 그렇다면 우리는 이 데이터셋에 한 변수값(x)을 아는 새로운 데이터가 유일될 때 이 데이터의 또다른 변수값(y)를 예측할 수 있다.
마지막으로 이 데이터의 선형회귀분석이 얼마나 정확한지 살펴보자. 물론 이 데이터는 특정한 선형 함수식으로부터 생성되었기때문에 상당히 정확할 수 밖에 없다. 여기서 우리가 살펴볼 수치 두 가지는 상관계수와 결정계수이다. 먼저 상관계수는 두 변수간의 상관관계를 보여주며 -1과 1사이의 값을 가진다. 음수이면 음, 양수이면 양의 상관관계를 가지며 절댓값이 0.3보다 작으면 매우 적은 상관관계를, 0.7보다 높으면 상대적으로 높은 상관관계를 의미한다. 결정계수는 회귀분석이 진행된 후, 회귀분석이 얼마나 해당 데이터에 대해 얼마나 정확한지를 보여주는 계수이다 위의 과정을 보았듯이 회귀분석은 loss값 (MSE 값)을 최소화시키는 식을 구해준다. 그러나 loss값이 최소인 식이라고 해도, 모든 데이터값이 특정 함수식의 그래프를 따라서만 존재하는 경우가 아니라면 분명히 어느정도의 오차는 존재한다. 만일 회귀 모델 함수식을 기준으로 데이터가 많이 퍼져있다면 오차는 더욱 더 커질 것이다.
상관분석과 회귀분석은 분명 다르다. 통계학에서 상관분석은 '상관관계'를 밝히기 위한 분석이며 회귀분석은 한 변수와 다른 변수간의 '인과관계'를 밝히는 분석이다. 그렇지만 대부분의 경우 상관관계가 아주 강하면 자연스럽게 데이터는 특정 선형 회귀식을 기준으로 밀집해 분포할 것이고, 결국 회귀분석의 정확도가 높다고 봐도 무방하겠다. 반대로 상관관계가 없거나 아주 약하다면, MSE 값을 최소화하는 선형회귀모델을 찾았다 하더라도 데이터는 이 모델에서 아주 멀리 떨어진 곳에, 퍼져서 분포할 것이다. 이 경우 선형회귀모델이 데이터에 적합하다고 볼 수 없다.
먼저 x와 y값들의 데이터를 Dataframe형태로 만들어 한번에 상관계수와 결정계수를 구해보자. 결정계수(R^2)를 구하는 방법으로는 두 가지가 있는데, 먼저 일반적으로는:
위의 과정을 통해 구하고, 독립변수와 종속변수가 한 개일 때는 상관계수의 제곱으로 구할수도 있다. 두 가지 방식 통해 결정계수를 두 번 따로 구해보자. (COF, COF2 로 설정)
import pandas as pd
df = pd.DataFrame({"X": x_data, "Y":y_data})
correlation = df["X"].corr(df["Y"])
COF = correlation*correlation
def COF2 (x, y, W, b):
SSE_list = []
SST_list = []
SSR_list = []
for i in range(len(x)):
SSE_list.append(np.square((y[i]-(W*x[i] + b))))
SSE = sum(SSE_list)
for i in range(len(x)):
SSR_list.append(np.square((np.mean(y)-(y[i]-(W*x[i] + b)))))
SSR = sum(SSR_list)
SST = SSE + SSR
return(SSR/SST)
print ("Correlation is:", correlation)
print ("Coefficient of determination is:", COF)
COF2(x_data, y_data, sess.run(W), sess.run(b))
#아래는 출력된 값이다
Correlation is: 0.863371123661639
Coefficient of determination is: 0.7454096971727612
array([0.99077594], dtype=float32) #COF2 로 구한 값
상관계수는 약 86프로로 강한 상관관계를 띄며, 결정계수 역시 74프로, 99프로로 매우 강한 확실성을 보여준다. 독립변수가 한 개인 경우이기 때문에 첫 번째 방법으로 구한 결정계수가 조금 더 유효하지 않을까 싶지만, 두 가지 방법으로 구한 결정계수 값의 차이에 대해서는 더 알아보면 좋을것같다. (혹시 정확히 아는 사람이있다면 메일이나 댓글을 달아주면 좋을 듯 하다)
응용해보자 - NBA 선수 Stat 데이터
방금전의 예시를 통해 선형회귀분석을 해 보았다. 그러나 방금 전의 분석은 특정한 함수식으로부터 데이터를 생성하고, 다시 회귀분석을 통해 그 함수식을 찾는 과정이었기에 실제 데이터분석 상황과는 거리가 있다.말하자면 답을 정해놓고 답에서 문제를 만든 후, 다시 그 문제를 풀어 답을 구하는 식이었다. 이번에는 완전히 새로운 데이터를 가지고와서 회귀분석을 해보자. 오늘의 데이터셋은 kaggle에서 구해온 NBA 선수들의 STAT 데이터셋이다. 데이터셋과 데이터게 대한 상세 정보는 여기에서 구할 수 있다. 자, 먼저 데이터를 불러오고 한번 어떻게 생겼는지 살펴보자. (스크롤 주의!!!)
Unnamed: 0 Year Player Pos Age Tm G GS \
0 0 1950.0 Curly Armstrong G-F 31.0 FTW 63.0 NaN
1 1 1950.0 Cliff Barker SG 29.0 INO 49.0 NaN
2 2 1950.0 Leo Barnhorst SF 25.0 CHS 67.0 NaN
3 3 1950.0 Ed Bartels F 24.0 TOT 15.0 NaN
4 4 1950.0 Ed Bartels F 24.0 DNN 13.0 NaN
5 5 1950.0 Ed Bartels F 24.0 NYK 2.0 NaN
6 6 1950.0 Ralph Beard G 22.0 INO 60.0 NaN
7 7 1950.0 Gene Berce G-F 23.0 TRI 3.0 NaN
8 8 1950.0 Charlie Black F-C 28.0 TOT 65.0 NaN
9 9 1950.0 Charlie Black F-C 28.0 FTW 36.0 NaN
10 10 1950.0 Charlie Black F-C 28.0 AND 29.0 NaN
11 11 1950.0 Nelson Bobb PG 25.0 PHW 57.0 NaN
12 12 1950.0 Jake Bornheimer F-C 22.0 PHW 60.0 NaN
13 13 1950.0 Vince Boryla SF 22.0 NYK 59.0 NaN
14 14 1950.0 Don Boven F-G 24.0 WAT 62.0 NaN
15 15 1950.0 Harry Boykoff C 27.0 WAT 61.0 NaN
16 16 1950.0 Joe Bradley G 21.0 CHS 46.0 NaN
17 17 1950.0 Bob Brannum PF 24.0 SHE 59.0 NaN
18 18 1950.0 Carl Braun G-F 22.0 NYK 67.0 NaN
19 19 1950.0 Frankie Brian G 26.0 AND 64.0 NaN
20 20 1950.0 Price Brookfield F-G 29.0 ROC 7.0 NaN
21 21 1950.0 Bob Brown F 26.0 DNN 62.0 NaN
22 22 1950.0 Jim Browne C 20.0 DNN 31.0 NaN
23 23 1950.0 Walt Budko PF 24.0 BLB 66.0 NaN
24 24 1950.0 Jack Burmaster G 23.0 SHE 61.0 NaN
25 25 1950.0 Tommy Byrnes F-G 26.0 BLB 53.0 NaN
26 26 1950.0 Bill Calhoun SG 22.0 ROC 62.0 NaN
27 27 1950.0 Don Carlson G-F 30.0 MNL 57.0 NaN
28 28 1950.0 Bob Carpenter F-C 32.0 FTW 66.0 NaN
29 29 1950.0 Jake Carter F-C 25.0 TOT 24.0 NaN
... ... ... ... ... ... ... ...
24661 24661 2017.0 Deron Williams PG 32.0 TOT 64.0 44.0
24662 24662 2017.0 Deron Williams PG 32.0 DAL 40.0 40.0
24663 24663 2017.0 Deron Williams PG 32.0 CLE 24.0 4.0
24664 24664 2017.0 Derrick Williams PF 25.0 TOT 50.0 11.0
24665 24665 2017.0 Derrick Williams PF 25.0 MIA 25.0 11.0
24666 24666 2017.0 Derrick Williams PF 25.0 CLE 25.0 0.0
24667 24667 2017.0 Lou Williams SG 30.0 TOT 81.0 1.0
24668 24668 2017.0 Lou Williams SG 30.0 LAL 58.0 1.0
24669 24669 2017.0 Lou Williams SG 30.0 HOU 23.0 0.0
24670 24670 2017.0 Marvin Williams PF 30.0 CHO 76.0 76.0
24671 24671 2017.0 Reggie Williams SF 30.0 NOP 6.0 0.0
24672 24672 2017.0 Troy Williams SF 22.0 TOT 30.0 16.0
24673 24673 2017.0 Troy Williams SF 22.0 MEM 24.0 13.0
24674 24674 2017.0 Troy Williams SF 22.0 HOU 6.0 3.0
24675 24675 2017.0 Kyle Wiltjer PF 24.0 HOU 14.0 0.0
24676 24676 2017.0 Justise Winslow SF 20.0 MIA 18.0 15.0
24677 24677 2017.0 Jeff Withey C 26.0 UTA 51.0 1.0
24678 24678 2017.0 Christian Wood PF 21.0 CHO 13.0 0.0
24679 24679 2017.0 Metta World SF 37.0 LAL 25.0 2.0
24680 24680 2017.0 Brandan Wright PF 29.0 MEM 28.0 5.0
24681 24681 2017.0 Delon Wright PG 24.0 TOR 27.0 0.0
24682 24682 2017.0 James Young SG 21.0 BOS 29.0 0.0
24683 24683 2017.0 Joe Young PG 24.0 IND 33.0 0.0
24684 24684 2017.0 Nick Young SG 31.0 LAL 60.0 60.0
24685 24685 2017.0 Thaddeus Young PF 28.0 IND 74.0 74.0
24686 24686 2017.0 Cody Zeller PF 24.0 CHO 62.0 58.0
24687 24687 2017.0 Tyler Zeller C 27.0 BOS 51.0 5.0
24688 24688 2017.0 Stephen Zimmerman C 20.0 ORL 19.0 0.0
24689 24689 2017.0 Paul Zipser SF 22.0 CHI 44.0 18.0
24690 24690 2017.0 Ivica Zubac C 19.0 LAL 38.0 11.0
MP PER ... FT% ORB DRB TRB AST STL BLK \
0 NaN NaN ... 0.705 NaN NaN NaN 176.0 NaN NaN
1 NaN NaN ... 0.708 NaN NaN NaN 109.0 NaN NaN
2 NaN NaN ... 0.698 NaN NaN NaN 140.0 NaN NaN
3 NaN NaN ... 0.559 NaN NaN NaN 20.0 NaN NaN
4 NaN NaN ... 0.548 NaN NaN NaN 20.0 NaN NaN
5 NaN NaN ... 0.667 NaN NaN NaN 0.0 NaN NaN
6 NaN NaN ... 0.762 NaN NaN NaN 233.0 NaN NaN
7 NaN NaN ... 0.000 NaN NaN NaN 2.0 NaN NaN
8 NaN NaN ... 0.651 NaN NaN NaN 163.0 NaN NaN
9 NaN NaN ... 0.632 NaN NaN NaN 75.0 NaN NaN
10 NaN NaN ... 0.688 NaN NaN NaN 88.0 NaN NaN
11 NaN NaN ... 0.626 NaN NaN NaN 46.0 NaN NaN
12 NaN NaN ... 0.667 NaN NaN NaN 40.0 NaN NaN
13 NaN NaN ... 0.764 NaN NaN NaN 95.0 NaN NaN
14 NaN NaN ... 0.688 NaN NaN NaN 137.0 NaN NaN
15 NaN NaN ... 0.775 NaN NaN NaN 149.0 NaN NaN
16 NaN NaN ... 0.395 NaN NaN NaN 36.0 NaN NaN
17 NaN NaN ... 0.690 NaN NaN NaN 205.0 NaN NaN
18 NaN NaN ... 0.762 NaN NaN NaN 247.0 NaN NaN
19 NaN NaN ... 0.824 NaN NaN NaN 189.0 NaN NaN
20 NaN NaN ... 0.923 NaN NaN NaN 1.0 NaN NaN
21 NaN NaN ... 0.683 NaN NaN NaN 101.0 NaN NaN
22 NaN NaN ... 0.481 NaN NaN NaN 8.0 NaN NaN
23 NaN NaN ... 0.757 NaN NaN NaN 146.0 NaN NaN
24 NaN NaN ... 0.681 NaN NaN NaN 179.0 NaN NaN
25 NaN NaN ... 0.702 NaN NaN NaN 88.0 NaN NaN
26 NaN NaN ... 0.719 NaN NaN NaN 115.0 NaN NaN
27 NaN NaN ... 0.726 NaN NaN NaN 76.0 NaN NaN
28 NaN NaN ... 0.742 NaN NaN NaN 92.0 NaN NaN
29 NaN NaN ... 0.679 NaN NaN NaN 24.0 NaN NaN
... ... ... ... ... ... ... ... ... ...
24661 1657.0 14.0 ... 0.826 14.0 133.0 147.0 360.0 31.0 8.0
24662 1171.0 15.0 ... 0.821 13.0 89.0 102.0 274.0 25.0 2.0
24663 486.0 11.4 ... 0.840 1.0 44.0 45.0 86.0 6.0 6.0
24664 804.0 10.6 ... 0.652 19.0 111.0 130.0 28.0 14.0 7.0
24665 377.0 10.1 ... 0.620 16.0 57.0 73.0 14.0 9.0 5.0
24666 427.0 11.1 ... 0.692 3.0 54.0 57.0 14.0 5.0 2.0
24667 1994.0 21.4 ... 0.880 26.0 176.0 202.0 239.0 80.0 19.0
24668 1403.0 23.9 ... 0.884 14.0 118.0 132.0 183.0 65.0 10.0
24669 591.0 15.4 ... 0.867 12.0 58.0 70.0 56.0 15.0 9.0
24670 2295.0 13.7 ... 0.873 89.0 409.0 498.0 106.0 58.0 53.0
24671 79.0 11.7 ... 0.857 2.0 4.0 6.0 4.0 3.0 0.0
24672 557.0 8.9 ... 0.656 15.0 54.0 69.0 25.0 27.0 10.0
24673 418.0 7.6 ... 0.600 6.0 39.0 45.0 19.0 24.0 9.0
24674 139.0 12.8 ... 0.857 9.0 15.0 24.0 6.0 3.0 1.0
24675 44.0 6.7 ... 0.500 4.0 6.0 10.0 2.0 3.0 1.0
24676 625.0 8.2 ... 0.617 23.0 71.0 94.0 66.0 27.0 6.0
24677 432.0 18.8 ... 0.750 52.0 69.0 121.0 7.0 16.0 32.0
24678 107.0 15.1 ... 0.733 14.0 15.0 29.0 2.0 3.0 6.0
24679 160.0 6.2 ... 0.625 5.0 15.0 20.0 11.0 9.0 2.0
24680 447.0 18.5 ... 0.657 31.0 47.0 78.0 15.0 11.0 20.0
24681 446.0 15.0 ... 0.764 16.0 32.0 48.0 57.0 27.0 11.0
24682 220.0 10.0 ... 0.667 6.0 20.0 26.0 4.0 10.0 2.0
24683 135.0 11.4 ... 0.733 1.0 16.0 17.0 15.0 4.0 0.0
24684 1556.0 14.1 ... 0.856 25.0 112.0 137.0 58.0 37.0 14.0
24685 2237.0 14.9 ... 0.523 131.0 318.0 449.0 122.0 114.0 30.0
24686 1725.0 16.7 ... 0.679 135.0 270.0 405.0 99.0 62.0 58.0
24687 525.0 13.0 ... 0.564 43.0 81.0 124.0 42.0 7.0 21.0
24688 108.0 7.3 ... 0.600 11.0 24.0 35.0 4.0 2.0 5.0
24689 843.0 6.9 ... 0.775 15.0 110.0 125.0 36.0 15.0 16.0
24690 609.0 17.0 ... 0.653 41.0 118.0 159.0 30.0 14.0 33.0
TOV PF PTS
0 NaN 217.0 458.0
1 NaN 99.0 279.0
2 NaN 192.0 438.0
3 NaN 29.0 63.0
4 NaN 27.0 59.0
5 NaN 2.0 4.0
6 NaN 132.0 895.0
7 NaN 6.0 10.0
8 NaN 273.0 661.0
9 NaN 140.0 382.0
10 NaN 133.0 279.0
11 NaN 97.0 242.0
12 NaN 111.0 254.0
13 NaN 203.0 612.0
14 NaN 255.0 656.0
15 NaN 229.0 779.0
16 NaN 51.0 87.0
17 NaN 279.0 713.0
18 NaN 188.0 1031.0
19 NaN 192.0 1138.0
20 NaN 7.0 34.0
21 NaN 269.0 724.0
22 NaN 16.0 47.0
23 NaN 259.0 595.0
24 NaN 237.0 598.0
25 NaN 76.0 327.0
26 NaN 100.0 560.0
27 NaN 126.0 267.0
28 NaN 168.0 614.0
29 NaN 59.0 82.0
... ... ...
24661 138.0 138.0 701.0
24662 98.0 96.0 522.0
24663 40.0 42.0 179.0
24664 28.0 60.0 304.0
24665 14.0 33.0 148.0
24666 14.0 27.0 156.0
24667 160.0 92.0 1421.0
24668 120.0 67.0 1078.0
24669 40.0 25.0 343.0
24670 60.0 134.0 849.0
24671 0.0 2.0 27.0
24672 33.0 60.0 185.0
24673 27.0 42.0 127.0
24674 6.0 18.0 58.0
24675 5.0 4.0 13.0
24676 33.0 52.0 196.0
24677 14.0 52.0 146.0
24678 7.0 11.0 35.0
24679 7.0 18.0 57.0
24680 10.0 42.0 189.0
24681 25.0 31.0 150.0
24682 4.0 15.0 68.0
24683 5.0 5.0 68.0
24684 36.0 137.0 791.0
24685 96.0 135.0 814.0
24686 65.0 189.0 639.0
24687 20.0 61.0 178.0
24688 3.0 17.0 23.0
24689 40.0 78.0 240.0
24690 30.0 66.0 284.0
[24691 rows x 53 columns]
일단 상당히 많은 feature를 담고 있다. 무려 24691 * 53의 빅 데이터이다! 흥미로운 feature들이 많겠지만 내가 가장 먼저 관심이 간 것은 파울, 어시스트, 그리고 득점 수 이다. 그럼 일단 파이썬을 통해 데이터를 불러오고, 파울, 어시스트, 득점 수에 대한 column만을 추려낸 후 Nan(값이 없는)인 데이터를 모두 도려내보자.
import pandas as pd
import numpy as np
from collections import Counter
df = pd.read_csv("/Users/HongSukhyun/Downloads/nba-players-stats-since-1950/Seasons_Stats.csv")
df = df[["PF", "AST", "PTS"]].dropna()
#결과는 다음과 같다
PF AST PTS
0 217.0 176.0 458.0
1 99.0 109.0 279.0
2 192.0 140.0 438.0
3 29.0 20.0 63.0
4 27.0 20.0 59.0
5 2.0 0.0 4.0
6 132.0 233.0 895.0
7 6.0 2.0 10.0
8 273.0 163.0 661.0
9 140.0 75.0 382.0
10 133.0 88.0 279.0
11 97.0 46.0 242.0
12 111.0 40.0 254.0
13 203.0 95.0 612.0
14 255.0 137.0 656.0
15 229.0 149.0 779.0
16 51.0 36.0 87.0
17 279.0 205.0 713.0
18 188.0 247.0 1031.0
19 192.0 189.0 1138.0
20 7.0 1.0 34.0
21 269.0 101.0 724.0
22 16.0 8.0 47.0
23 259.0 146.0 595.0
24 237.0 179.0 598.0
25 76.0 88.0 327.0
26 100.0 115.0 560.0
27 126.0 76.0 267.0
28 168.0 92.0 614.0
29 59.0 24.0 82.0
... ... ...
24661 138.0 360.0 701.0
24662 96.0 274.0 522.0
24663 42.0 86.0 179.0
24664 60.0 28.0 304.0
24665 33.0 14.0 148.0
24666 27.0 14.0 156.0
24667 92.0 239.0 1421.0
24668 67.0 183.0 1078.0
24669 25.0 56.0 343.0
24670 134.0 106.0 849.0
24671 2.0 4.0 27.0
24672 60.0 25.0 185.0
24673 42.0 19.0 127.0
24674 18.0 6.0 58.0
24675 4.0 2.0 13.0
24676 52.0 66.0 196.0
24677 52.0 7.0 146.0
24678 11.0 2.0 35.0
24679 18.0 11.0 57.0
24680 42.0 15.0 189.0
24681 31.0 57.0 150.0
24682 15.0 4.0 68.0
24683 5.0 15.0 68.0
24684 137.0 58.0 791.0
24685 135.0 122.0 814.0
24686 189.0 99.0 639.0
24687 61.0 42.0 178.0
24688 17.0 4.0 23.0
24689 78.0 36.0 240.0
24690 66.0 30.0 284.0
[24624 rows x 3 columns]
이제 훨씬 깔끔해졌다. 그런데 이 데이터를 이리저리 다루고 회귀분석을 시도해보니 한 가지 문제가 있었다. 득점, 어시스트, 그리고 파울의 수 데이터는 모두 다른 기준(scale)에 따라 작성된 데이터이고, 그래서 그런지 이 데이터를 그대로 사용했더니 inf(무한)이나 Nan과 같은 이상한 값을 뱉어냈다. 변수 데이터들을 표준화(Standarzation)해줘야 하는 것인가 해서 찾아보았더니 역시 비슷한 예제에서 변수를 표준화하는 것을 찾아볼 수 있었다. 정확히 어떤 경우에 표준화를 해 주어야 하는건지는 모르겠으나 구글과 stack overflow등을 검색해보니 이 경우처럼 변수들의 척도(scale)이 완전 다를 경우에 표준화를 해야 한다는 이야기도 있고, 회귀하려는 함수식의 항이 여러개 (즉 변수가 여러개인 다중회귀분석의 경우)인 경우에
표준화를 해주어야 한다는 이야기도 있다. (이것 역시 정확한 정보를 아는 사람이 있다면 역시 댓글이나 이메일을 주면 좋을것같다)
변수들을 표준화하는 함수를 만들어 표준화해보자. 표준화 함수를 정의하는 김에 위에서 만들었던 COF2함수 (결정계수를 계싼하는 함수)도 미리 같이 정의해봤다.
def feature_normalize(dataset):
mu = np.mean(dataset)
sig = np.std(dataset)
return (dataset - mu)/sig
df = feature_normalize(df)
Assist = [a[0] for a in np.array(df[["AST"]])]
Points = [a[0] for a in np.array(df[["PTS"]])]
Fouls = [a[0] for a in np.array(df[["PF"]])]
#결정계수 함수 정의
def COF2 (x, y, W, b):
SSE_list = []
SST_list = []
SSR_list = []
for i in range(len(x)):
SSE_list.append(np.square((y[i]-(W*x[i] + b))))
SSE = sum(SSE_list)
for i in range(len(x)):
SSR_list.append(np.square((np.mean(y)-(y[i]-(W*x[i] + b)))))
SSR = sum(SSR_list)
SST = SSE + SSR
return(SSR/SST)
이제 변수값들을 깔끔하게 표준화해주었다. 이제 한번 데이터들의 분포를 봐보자. Point를 종속변수 y값으로, 파울과 어시스트를 독립변수로 해서 파울과 어시스트가 선수의 득점에 어떤 영향(?)을 미치는지를 한번 봐 보고싶다. 왠지 NBA경기를 보다보면 사실당 파울은 특정한 팀/개인 파울의 수를 넘기지만 않으면 패널티가 없으며 득점이 많은 선수일수록 상대 수비와 격하게 부딪힐 일이 많기 때문에 파울과 득점간의 관계가 어느정도 있을 것 같았다. 하지만 역시 상관관계와 인과관계는 다른 이야기이다. 나는 파울을 독립변수와 득점이라는 종속변수 사이에 어느정도 인과관계가 있을거라고 생각했다. 파울이 직접적인 득점을 발생시키는 것은 아니지만 파울이 많을수록 상대 수비를 재끼고 득점을 하기 위한 적극적인 플레이를 했다는 점을 의미한다고 생각했다. 어시스트의 경우에는 독립변수로 뒀을 때 득점과 어떤 관계를 가질지 머릿속으로는 도저히 그림이 그려지지 않았다. 그렇지만 빅데이터에서는 인과성 (여기선 독립-종속변수에 대한 인과성보다는 '왜 일어난 것인지' 이유에 대한 물음을 말함)보다는 상관성과 결과가 더 중요하기 때문에 일단은 모델에 넣고 돌려보기로 했다. 또, 마지막으로 하는김에 파울과 어시스트도 독립-종속변수 관계에 넣어 모델을 실행해보기로 했다.
한번 세 변수를 그래프 위에 놓고 데이터의 분포를 봐보자
import matplotlib.pyplot as plt
x_data1 = Fouls
x_data2 = Assist
y_data = Points
plt.plot(x_data1, y_data, "ro", label = "Points and Fouls")
plt.legend()
plt.show()
plt.plot(x_data2, y_data, "ro", label = "Points and Assists")
plt.legend()
plt.show()
plt.plot(x_data1, x_data2, "ro", label = "Fouls and Assists")
plt.legend()
plt.show()
일단 득점과 파울/득점과 어시스트 사이에는 어느정도 선형 관계가 존재하는 듯 보인다. 그러나 변수 값이 커짐에 따라 점점 분포가 퍼지는 모습을 보여주고 있다. 아직 분석은 하지 않았지만 변수 값이 작을수록 상관성/인과성이 크게 나타날것으로 보이는데 이는 아마 어시스트, 파울, 득점 모두 경기를 뛴 시간과 경기에 참여한 적극성과 밀접한 관계가 있기 때문이지 않을까 싶다. 플레이시간/경기에 참여한 적극성이 낮을수록 경기에서 기록할 수 있는 득점/파울/어시스트의 수도 상대적으로 줄어들 것이다. 반면 경기에 참여한 시간이 길고 더 적극적으로 참여한 선수일수록 기록할 수 있는 득점/파울/어시스트의 폭이 넓어진다(수가 많아진다는것이 아니다 - 경기를 적극적으로 오래 뛰어도 득점이 적은 선수가 있는 반면, 긴 경기시간동안 많은 득점을 내는 선수가 있다.) 이번엔 경기 시간이나 경기 참여의 적극성을 반영할 수 있는 변수를 제외했지만, 다음번에 그러한 변수를 포함한 분석도 추가적으로 해보고싶다.
어쨌건 다시 분석을 해보자. 이제 앞에서 했던것과 같은 방법으로 tensorflow를 이용해 선형회귀분석을 하나씩 진행해보자. 다른 점이라면 Step을 20개로, 학습도는 0.3으로 설정해보겠다. 코드는 한꺼번에 첨부한다 (스크롤 주의):
import tensorflow as tf
###Fouls and Poinst
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = x_data1 * W + b
loss = tf.reduce_mean(tf.square(y-y_data))
optimizer = tf.train.GradientDescentOptimizer(0.3)
train = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for step in range(20):
sess.run(train)
print (step, sess.run(W), sess.run(b))
plt.plot(x_data1, y_data, 'ro')
plt.plot(x_data1, sess.run(W) * x_data1 + sess.run(b))
plt.legend()
plt.show()
print( df["PF"].corr(df["PTS"]))
FP_cor = df["PF"].corr(df["PTS"])
FP_COF = FP_cor*FP_cor
FP_COF2 = COF2(list(df["PF"]), list(df["PTS"]), sess.run(W), sess.run(b))
print ("Correlation is:",FP_cor)
print ("Score follows:", sess.run(W), "* Fouls + ", sess.run(b))
print ("loss is:", sess.run(loss))
print ("COF is:", FP_COF, FP_COF2
)
###Assist and Points
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = x_data2 * W + b
loss = tf.reduce_mean(tf.square(y-y_data))
optimizer = tf.train.GradientDescentOptimizer(0.3)
train = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for step in range(20):
sess.run(train)
print (step, sess.run(W), sess.run(b))
plt.plot(x_data2, y_data, 'ro')
plt.plot(x_data2, sess.run(W) * x_data2 + sess.run(b))
plt.legend()
plt.show()
AP_cor = df["AST"].corr(df["PTS"])
AP_COF = AP_cor*AP_cor
AP_COF2 = COF2(list(df["AST"]), list(df["PTS"]), sess.run(W), sess.run(b))
print ("Correlation is:", AP_cor)
print ("Score follows:", sess.run(W), "* Assists + ", sess.run(b))
print ("loss is:", sess.run(loss))
print ("COF is:", AP_COF, AP_COF2)
###Foul and Assist
W = tf.Variable(tf.random_uniform([1], -1.0, 1.0))
b = tf.Variable(tf.zeros([1]))
y = x_data1 * W + b
loss = tf.reduce_mean(tf.square(y-x_data2))
optimizer = tf.train.GradientDescentOptimizer(0.3)
train = optimizer.minimize(loss)
init = tf.global_variables_initializer()
sess = tf.Session()
sess.run(init)
for step in range(20):
sess.run(train)
print (step, sess.run(W), sess.run(b))
plt.plot(x_data1, x_data2, 'ro')
plt.plot(x_data1, sess.run(W) * x_data1 + sess.run(b))
plt.legend()
plt.show()
FA_cor = df["AST"].corr(df["PF"])
FA_COF = FA_cor *FA_cor
FA_COF2 = COF2(list(df["PF"]), list(df["AST"]), sess.run(W), sess.run(b))
print ("Correlation is:",FA_cor)
print ("loss is:", sess.run(loss))
print ("COF is:", FA_COF, FA_COF2)
#출력값:
Correlation is: 0.7720640352757873
Score follows: [0.77206403] * Fouls + [9.778889e-09]
loss is: 0.40391737
COF is: 0.5960828745663321 [0.5]
출력값을 보니 loss 가 약 여전히 0.4로 남아있다. 아마 분포가 워낙 퍼져있기 때문에 (차원을 일그러뜨리지 않는이상...)그 이상 줄이는것이 거의 불가능할 것이다. 상관계수는 0.77정도로 생각보다 큰 값을 보여줬다. 확실히 어느정도 파울과 득점간 상관관계가 있음은 분명하다. 그러나 이 모델의 정확도는 0.5~0.6 사이 수준으로, 아주 나쁜 건 아니지만 그렇다고 정확하다고도 할 수 없는 수준을 보여줬다. 아마 독립변수의 값이 작을수록 0.5보다 높은 정확도를, 점차 높아질수록 0.5보다 낮은 정확도를 보이지 않을까 싶다.
그래프는 너무 많으니 생략하고 1, 10, 20번째것만 가져왔다.
다음은 두 번째, 어시스트와 득점이다.
Correlation is: 0.7089500711396771
Score follows: [0.70895004] * Assists + [1.9278378e-08]
loss is: 0.4973901
COF is: 0.5026102033689532 [0.5]
출력값을 보면 역시 상관계수는 0.7정도로 큰 수치를 보여주지만 결정계수가 0.5정도 수준으로, 위의 경우와 같은 이유로 모델의 정확도는 썩 높다고 보기 힘들다. 어시스트와 득점은 어시스트와 파울 관계보다 훨씬 더 (독립변수값이 커질수록) 퍼지는 분포를 보여준다.
마지막으로, 그래프가 절망적이다싶을 정도로 큰 퍼짐을 보여준 어시스트와 파울이다:
Correlation is: 0.5505805646466714
loss is: 0.6968604
COF is: 0.3031389581666475 [0.5]
일단 해봤으니 올리긴 하지만 선형회귀 모델이라고 하기도 민망할 정도의 데이터 분포이기에 크게 기대는 하지 않았다. 상관계수부터 약 0.55로 강하지는 않은 양의 상관관계를 가지는것으로 보인다. loss 값 자체도 0.69로 높은 편이고, 결정계수 값은 각각 0.3, 0.5로 모델이 정확하다고 보기는 많이 힘들다.
---
나름 흥미롭고 재밌는 과정이었던 것 같다. 한가지 아쉬운점은 앞에서 말했든 경기 시간이나 플레이의 적극도와 같은 변수를 고려하지 않았다는 점이다. 그렇기에 득점/어이스트/파울이 전체적으로 많은 선수일수록 다양한 분포가 나타나고, 적은 선수일수록 가질 수 있는 변수값이 제한된다는 점이다. 만일 경기 시간이나 적극적인 플레이를 반영한 새로운 변수를 추가하여 다중회귀분석을 하게 되면, 경기에 참여하는 시간을 구간별로 나누어 구간별 예측모델을 만드는 등 더 의미있는 모델을 만들수 있지않을까 싶다. 나중에 다중회귀분석과 3차원 데이터를 다루는법을 더 많이 공부해서 포스팅해볼까 한다.
나중에는 이런거를 할 수 있는 거군요...하도 여기저기서 파이선 파이선 하길래...입문 책 산지 몇달되는데... 제자리 걸음...
답글삭제이정도 하려면 몇년은 배워야되겠죠....
저는 No handles with labels found to put in legend. 창이 뜨고 선형회귀 그래프는뜨는데
답글삭제수치테이블이 안생깁니다..무슨 문제일까요?