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는 무작위적인 난수) 의 함수관계로 선형회귀되는 데이터셋을 생성한것이다.

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차원 데이터를 다루는법을 더 많이 공부해서 포스팅해볼까 한다. 





댓글

  1. 나중에는 이런거를 할 수 있는 거군요...하도 여기저기서 파이선 파이선 하길래...입문 책 산지 몇달되는데... 제자리 걸음...
    이정도 하려면 몇년은 배워야되겠죠....

    답글삭제
  2. 저는 No handles with labels found to put in legend. 창이 뜨고 선형회귀 그래프는뜨는데
    수치테이블이 안생깁니다..무슨 문제일까요?

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

[Python mini projects] Python을 활용한 텍스트 속 유의미한 통계 산출하기 -1

Kaggle competition 간단후기

(2부) 플랫폼 비즈니스,그리고 카카오의 수익모델에 대한 idea