728x90
반응형

 지난 포스트에서 퍼셉트론을 이용해 대표적인 논리 게이트인 AND 게이트, NAND 게이트, OR 게이트를 구현해보았다. 이번 포스트에선 또 다른 대표적인 논리 게이트인 XOR 게이트를 구현해보자.

 

 

1. XOR 게이트

  • XOR 게이트는 배타적(자기 자신을 제외하고 나머지는 거부한다.) 논리합이라는 회로로, 변수 중 단 하나만 True(참 = 1) 일 때, True(=1)을 반환한다.
  • XOR 게이트의 진리표는 다음과 같다.
0 0 0
0 1 1
1 0 1
1 1 0
  • 자, 위 진리표를 구현할 수 있는 가중치($w_1, w_2, b$)를 찾을 수 있는가?

$$ y = \begin{cases}
 0, \ \ (w_1*0 + w_2*0 + b \leq 0) \\ 
 1, \ \ (w_1*0 + w_2*1 + b \leq 0) \\ 
 1, \ \ (w_1*1 + w_2*0 + b \leq 0) \\ 
 0, \ \ (w_1*1 + w_2*1 + b > 0) 
\end{cases} $$

  • 위 공식에 들어 맞는 가중치를 찾는 것은 불가능하다.
  • 그 이유는 앞서 말한 퍼셉트론 역시 회귀분석과 마찬가지로 선형성을 이용해서 0보다 크고, 작은 지를 구분해내기 때문이다.
  • 위 말을 이해하기 쉽도록 위 진리표를 시각적으로 보자.
>>> import matplotlib.pyplot as plt

# 캔버스 설정
>>> fig = plt.figure(figsize=(8,8)) # 캔버스 생성
>>> fig.set_facecolor('white')      # 캔버스 색상 설정

>>> plt.title('XOR Gate, Visualization of True table', fontsize = 25)
>>> plt.ylim(-1, 4)
>>> plt.xlim(-1, 4)
>>> plt.axhline(color = 'k', alpha = 0.5)
>>> plt.axvline(color = 'k', alpha = 0.5)
>>> plt.xlabel("x1", fontsize = 20)
>>> plt.ylabel("x2", fontsize = 20, rotation = 0)

>>> plt.scatter([0, 1], [0, 1], s = 200, c = "green", marker="v")
>>> plt.scatter([0, 1], [1, 0], s = 200, c = "blue", marker="s")

>>> plt.show()

  • 위에서 퍼셉트론의 구분 방법은 선형성을 이용한다고 했다.
  • 선형성을 이용한다는 소리는 위 그래프에서 선 하나(퍼셉트론)로 파란 네모 점과 녹색 세모 점을 구분할 수 있어야 한다는 소리다.
  • 직선이 아닌 곡선을 사용하지 않는한, 그 어떠한 방법으로도 하나의 직선으로는 위 점을 서로 다른 2개의 집단으로 구분해낼 수 없다.
  • 즉, 퍼셉트론 하나로는 논리 게이트의 구현이 불가능하다는 것이고, 이는 퍼셉트론 하나로는 컴퓨터 같은 고등 연산을 수행할 수 없다는 것이다!

 

 

 

 

2. XOR 게이트의 해결 방법

 머리가 비상하게 좋은 친구라면(블로그 주인장은 그렇지 못하지만...), "앞서 만들었던 논리 게이트를 조합해서 이 문제를 해결할 수 있지 않을까?"라는 생각이 들지도 모른다.

 우리는 앞에서 AND, NAND, OR 게이트를 만들어보았고, 이들을 조합해서 문제를 해결할 수는 없을까?

  • 위 그림을 보면, NAND, OR, AND 논리 게이트를 조합하면 우리가 위에서 만들었던 XOR 진리표의 결과를 출력하는 것을 알 수 있다.
  • $x_1, x_2$는 일정하며, 어떤 논리 게이트를 결정하느냐에 따라 가중치($w_1, w_2, b$)만 바뀐다.
  • 반대로 말하면, 가중치만 다른 퍼셉트론을 조합해서 내가 원하는 기능을 얻을 수 있다는 것이다.
  • 자, 위 그림을 코드로 구현해보자. 이번에는 조금 더 코드 친화적으로 Numpy의 array를 이용해서 짜 보겠다.
>>> import numpy as np

>>> def step_function(x):
    
>>>     y = x > 0
    
>>>     return y.astype(np.int) 
        

>>> def Perceptron(x, dict_name, gate_name):

>>>     weight = dict_name[gate_name]
>>>     y = np.sum(x*weight['w']) + weight["b"]

>>>     return step_function(y)


>>> def XOR_gate(x):

>>>     array_x = np.array(x)
>>>     dict_W = {"AND":{"w":[0.5,0.5], "b":-0.7},
>>>               "NAND":{"w":[-0.5,-0.5], "b":0.7},
>>>               "OR":{"w":[0.5,0.5], "b":-0.2}}

>>>     y1 = Perceptron(array_x, dict_W, "NAND")
>>>     y2 = Perceptron(array_x, dict_W, "OR")
    
>>>     X = np.array([y1, y2])
>>>     Y = Perceptron(X, dict_W, "AND")
    
>>>     return step_function(Y)
>>> print("XOR Gate")
>>> print("----"*20)
>>> print("(0,0):", XOR_gate([0,0]))
>>> print("(0,1):", XOR_gate([0,1]))
>>> print("(1,0):", XOR_gate([1,0]))
>>> print("(1,1):", XOR_gate([1,1]))

XOR Gate
--------------------------------------------------------------------------------
(0,0): 0
(0,1): 1
(1,0): 1
(1,1): 0
  • 위 코드에서 새롭게 ster_function()이라는 함수가 생겼는데, 이는 계단 함수로, 앞서 우리가 if문을 이용해서 합산된 결과가 0보다 클 때, 1을 반환하고, 0보다 작거나 같으면, 0을 반환하던 부분이다.
  • 이를 활성화 함수(Activation Function)라 하는데, 활성화 함수를 통해 합산된 값을 출력할지, 출력한다면 어떻게 출력할지를 결정한다.
  • 계단 함수는 이후 활성화 함수를 자세히 다룰 때, 다시 이야기하도록 하겠다.
  • 위 코드를 보면, 오로지 가중치만 다른 퍼셉트론을 겹쳐서 사용했는데, XOR 게이트 문제를 해결한 것을 알 수 있다.
  • 이렇게 2개 이상의 퍼셉트론을 쌓는 것을 다층 퍼셉트론이라고 한다.

 

 

 

 

3. 논리를 비약시켜보자.

  • 위에서 우리는 2개 이상의 층(Layer)을 쌓아, 우리가 원하는 문제를 해결하였다.
  • 이 곳에 사용된 층들은 오로지 가중치만 다르며, 이 가중치가 갖는 의미도 꽤 다르다.
  • $w_1, w_2$ 같은 가중치는, 각 입력 신호에 부여되는 영향력(중요도)을 조절한다.
  • $\theta$는 임계점이라 하였는데, 좌변으로 이항 시켜, $b$로 만들어주었고, 이는 편향(bias)라고 한다. 편향은 뉴런이 얼마나 쉽게 활성화되느냐에 영향을 미치며, $b$의 크기에 따라 활성화 함수가 작동하는지, 마는지가 결정된다.
  • 지금까지 우리가 학습한 내용을 단순화시켜보면, 층을 많이 쌓고, 가중치를 맞게 설정해주면, 내가 원하는 결과를 얻을 수 있다는 것이다.
  • 층을 많이 쌓는다는 것이 바로 우리가 아는 딥러닝(Deep Learning)의 딥(Deep)을 의미하며, 층이 많이 쌓인 신경망은 각 노드에서 다음 노드로 이동하는 신호에 부여하는 가중치를 다르게만 한다면, 구체적으로 함수 식을 모른다 할지라도 우리가 원하는 결과를 알 수 있다는 것이다.
  • 그런데, 만약 그 가중치를 컴퓨터가 알아서 찾을 수 있다면 어떨까?
  • 여기서 조금 어려워질지도 모르겠는데, 기계가 어떠한 방법(손실 함수)을 이용해서 가장 적합한 가중치를 찾아낸다면, 우리가 스스로 공부를 해서 어떤 결과를 도출하는 것처럼 컴퓨터도 적합한 가중치를 찾기 위한 활동, 즉! 학습과 동일하게 보이는 행동을 통해 데이터만 가지고 원하는 결과를 도출할 수 있다는 것이다.
  • 여기서 "기계가 학습한다(Machine Learning)"이라는 말이 나오게 되는 것이며, 학습을 하되(가중치를 찾는 과정) 그것을 다층 레이어를 통해 찾아내는 것을 딥러닝(Deep Learning)이라 하게 되는 것이다.

 

 

 

 이번 포스트에선 퍼셉트론을 이용해 머신러닝이라는 단어와 딥러닝이라는 단어가 어떻게 만들어졌는지를 학습해보았다. 다음 포스트에서는 짧게 짚고 넘어갔던 활성화 함수에 대해 학습해보자.

728x90
반응형
728x90
반응형

 지난 포스트에서 기초적인 파이썬 코드를 사용하여, 퍼셉트론을 구현해보았다. 이번 포스트에서는 지난번에 생성한 퍼셉트론을 사용해서 논리 회로를 적용해보겠다.

 

 

논리회로(Logical circuit)

 1937년 클로드 섀넌(Claude Shannon)이 개발한 논리 회로(Logical Circuit)는 불 대수(Boolean algebra)를 물리적으로 구현한 것으로, 하나 이상의 논리적 입력값(True / False)이 들어가면, 그에 맞는 논리 연산(And / Or 등)을 수행하여 하나의 논리적 출력 값(True / False)을 얻는 전자 회로다.

 논리 회로는 다양한 불 대수의 조합을 통해 다양한 기능을 수행할 수 있는데, 이를 이용해서 컴퓨터 같은 고등 연산이 가능한 기계를 만들어 낼 수도 있다.

 여기서 AND, NOT, OR, XOR 등과 같은 기본이 되는 논리 연산을 수행하는 것을 논리 게이트(Logical gate)라 한다.

 만약, 앞에서 만들었던 퍼셉트론이 논리 게이트에 대해서도 적용 가능하다면, 퍼셉트론을 이용해서 컴퓨터도 만들 수 있지 않을까?

 

 

 

논리 게이트

1. AND 게이트

  • AND 게이트는 우리가 코드를 짤 때, 익숙한 논리 연산자 AND와 같으며, 이는 집합에서 교집합에 해당한다. 교집합은 두 집합이 모두 참(True)일 경우, 참(True)을 반환하는 것이다.
  • 두 집합에 대한 진리표를 만들어, 이를 보다 쉽게 이해해보자.
$x_1$ $x_2$ $y$
0 0 0
0 1 0
1 0 0
1 1 1
  • 위 표를 볼 때, 1은 True(참)이라 생각하고, 0은 False(거짓)이라고 생각해보자.
  • 위 표를 퍼셉트론의 공식에 맞게 고쳐보자.

$$ y = \begin{cases}
 0, \ \ \ w_1*0 + w_2*0 \leq \theta \\ 
 0, \ \ \ w_1*0 + w_2*1 \leq \theta \\ 
 0, \ \ \ w_1*1 + w_2*0 \leq \theta \\ 
 1, \ \ \ w_1*1 + w_2*1 > \theta  
\end{cases} $$

  • 위 공식을 보면, 변수인 가중치($w_1, w_2$)와 임계값($\theta$)을 어떻게 설정하느냐로 AND 게이트를 구현할 수 있다는 것을 알 수 있다.
  • 위 공식을 참으로 만드는 가중치와 임계값을 설정하여, AND 게이트를 구현해보자
# Perceptron
>>> def Perceptron(x1, x2, w1, w2, theta):
    
>>>     y = w1*x1 + w2*x2
    
>>>     if y <= theta:
>>>         return 0
>>>     elif y > theta:
>>>         return 1
    
    
# AND Gate를 구현해보자.
>>> w1, w2, theta = 0.5, 0.5, 0.8

>>> print("AND Gate")
>>> print("----"*20)
>>> print("(0, 0):", Perceptron(x1=0, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(0, 1):", Perceptron(x1=0, x2=1, w1=w1, w2=w2, theta=theta))
>>> print("(1, 0):", Perceptron(x1=1, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(1, 1):", Perceptron(x1=1, x2=1, w1=w1, w2=w2, theta=theta))

AND Gate
--------------------------------------------------------------------------------
(0, 0): 0
(0, 1): 0
(1, 0): 0
(1, 1): 1
  • 위 코드를 보면, 가중치와 임계값을 0.5로 정했을 뿐인데, AND Gate와 동일한 결과가 나온 것을 알 수 있다.

 

 

 

2. NAND 게이트

  • 이번에는 AND 게이트의 반대인 NOT AND 게이트인 NAND 게이트를 구현해보자.
  • NAND 게이트는 AND 게이트의 반대이므로, 진리표는 다음과 같다.
$x_1$ $x_2$ $y$
0 0 1
0 1 1
1 0 1
1 1 0
# NAND Gate를 구현해보자.
>>> w1, w2, theta = -0.5, -0.5, -0.8

>>> print("NAND Gate")
>>> print("----"*20)
>>> print("(0, 0):", Perceptron(x1=0, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(0, 1):", Perceptron(x1=0, x2=1, w1=w1, w2=w2, theta=theta))
>>> print("(1, 0):", Perceptron(x1=1, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(1, 1):", Perceptron(x1=1, x2=1, w1=w1, w2=w2, theta=theta))

NAND Gate
--------------------------------------------------------------------------------
(0, 0): 1
(0, 1): 1
(1, 0): 1
(1, 1): 0
  • NAND Gate는 AND Gate의 반대이므로, 가중치와 임계값을 모두 역수로 만들어주었다.

 

 

 

3. OR 게이트

  • 다음은 또 다른 기본 논리 게이트 중 하나인 OR 게이트를 구현해보자.
  • OR은 집합의 합집합에 해당하며, 둘 중 하나라도 True인 경우 True를 반환한다.
$x_1$ $x_2$ $y$
0 0 0
0 1 1
1 0 1
1 1 1
# OR Gate를 구현해보자.
>>> w1, w2, theta = 0.5, 0.5, 0.2

>>> print("OR Gate")
>>> print("----"*20)
>>> print("(0, 0):", Perceptron(x1=0, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(0, 1):", Perceptron(x1=0, x2=1, w1=w1, w2=w2, theta=theta))
>>> print("(1, 0):", Perceptron(x1=1, x2=0, w1=w1, w2=w2, theta=theta))
>>> print("(1, 1):", Perceptron(x1=1, x2=1, w1=w1, w2=w2, theta=theta))

OR Gate
--------------------------------------------------------------------------------
(0, 0): 0
(0, 1): 1
(1, 0): 1
(1, 1): 1
  • OR 게이트도 오로지 가중치만 바꾸었는데, 우리가 원하는 값을 반환한 것을 볼 수 있다!

 

 

 

4. 퍼셉트론 공식 정리 및 코드 체계화

  • 지금까지 퍼셉트론 코드를 짜보며, 이 코드를 보다 쉽게 바꿀 수 있다는 생각이 들지 않는가?
  • 먼저 퍼셉트론에 사용된 공식에서 임계값 $\theta$를 왼쪽으로 이동시켜, 절편으로 만들면 어떨까?

$$ y = \begin{cases}
 0, \ \ \  (w_1x_1 + w_2x_2 + b \leq 0)  \\ 
 1, \ \ \  (w_1x_1 + w_2x_2 + b > 0) 
\end{cases} $$

  • 좌변으로 이동하면서 $-\theta$로 부호가 음수로 바뀌었는데, 보기에 깔끔하지 않으니 부호가 양수인 $b$라는 절편으로 만들어보았다.
  • 그런데 위 공식을 보면, 어디서 많이 본 공식과 굉장히 유사하지 않은가? 그렇다. 회귀식과 퍼셉트론의 공식은 동일하며, 이로써 퍼셉트론도 선형성을 따지는 것임을 알 수 있다.
  • 위 내용을 코드에 반영하면서, 동시에 퍼셉트론으로 AND 게이트, NAND 게이트, OR 게이트를 구현할 때, 오로지 가중치만을 바꿨는데, 이를 보다 깔끔한 코드로 구현해보자.
>>> class Logical_gate:
    
>>>     def __init__(self, weight_dict):
        
>>>         self.weight = weight_dict


>>>     def Perceptron(self, x1, x2, key):

>>>         weight = self.weight[key]
>>>         w = weight["w"]
>>>         b = weight["b"]

>>>         y = w[0]*x1 + w[1]*x2 + b

>>>         if y <= 0:
>>>             return 0
>>>         elif y > 0:
>>>             return 1
        
>>>     def Run_Gate(self, key):

>>>         print(key + " Gate")
>>>         print("----"*20)
>>>         print("(0, 0):", self.Perceptron(0, 0, key))
>>>         print("(0, 1):", self.Perceptron(0, 1, key))
>>>         print("(1, 0):", self.Perceptron(1, 0, key))
>>>         print("(1, 1):", self.Perceptron(1, 1, key))
>>>         print("----"*20)
>>>         print("\n")
>>> weight_dict = {"AND":{"w":[0.5,0.5], "b":-0.5},
>>>                     "NAND":{"w":[-0.5,-0.5], "b":0.5},
>>>                     "OR":{"w":[0.5,0.5], "b":-0.2}}

>>> LG = Logical_gate()

>>> LG.Run_Gate("AND")
>>> LG.Run_Gate("NAND")
>>> LG.Run_Gate("OR")

AND Gate
--------------------------------------------------------------------------------
(0, 0): 0
(0, 1): 0
(1, 0): 0
(1, 1): 1
--------------------------------------------------------------------------------


NAND Gate
--------------------------------------------------------------------------------
(0, 0): 1
(0, 1): 0
(1, 0): 0
(1, 1): 0
--------------------------------------------------------------------------------


OR Gate
--------------------------------------------------------------------------------
(0, 0): 0
(0, 1): 1
(1, 0): 1
(1, 1): 1
--------------------------------------------------------------------------------
  • 위 코드는 기존의 퍼셉트론 코드에 비해 더 어려워 보이긴 하지만, 일단 만들어 놓으면, 사용하기는 훨씬 쉽다.
  • 새로운 논리 게이트를 구현하고자 하면, 딕셔너리인 weight_dict에 새로운 가중치($w_1, w_2, b$ 모두를 앞으로 단순하게 가중치로 부르겠다.)만 담으면 끝이다.

 

 

 

 지금까지 퍼셉트론을 이용해서 AND, NAND, OR 게이트를 가중치만 바꿔서 구현한 것을 살펴보았는데, 지금까지만 보면, 퍼셉트론이 논리 게이트에서도 잘 작동하는 것으로 보일 것이다.

 그러나, 앞서 말했듯 퍼셉트론도 선형성을 기반으로 결과를 도출하고, 그 결과가 활성화 함수인 계단 함수(이 부분은 뒤에서 자세히 다루겠다.)를 통해 출력되었는데, 이에 대한 반례가 존재한다.

 다음 포스트에서는 퍼셉트론의 논리 게이트 적용에서 반례인 XOR 게이트에 대해 학습해보고, 어째서 그런 문제가 발생하는지, 그리고 해결 방안은 무엇인지에 대해 학습해 보겠다.

728x90
반응형
728x90
반응형

 지난 포스트의 머신러닝, 딥러닝에 대한 설명이 잘 와 닿지 않았을 수 있다. 그러나 퍼셉트론(Perceptron)에 대해 학습해보면, 어떤 과정을 통해서 머신러닝이 이루어지고, 기계 학습이라는 단어의 학습이 정확히 무엇을 의미하는지 알 수 있을 것이다.

 

 

1. 신경 세포

 머신러닝 공부를 해보면 신경망(Neural Network)라는 단어를 종종 볼 수 있었을 것이다. 그리고 머신러닝에 사용되는 인공신경망이 사람의 신경을 흉내 내어 만들어졌다는 글도 나오는데, 대체 이게 무슨 소리일까?

ZUM 학습백과. 뉴런의 구조(http://study.zum.com/book/11779)

  • 위 그림은 인간의 신경 세포인 뉴런이다.
  • 뉴런은 가지돌기(수상돌기)에 여러 신호가 도착하면 신경 세포체에서 이를 합치고, 합쳐진 신호가 특정 임계 값을 넘으면, 출력 신호가 생성되어 축삭 돌기를 통해 다음 뉴런으로 신호가 전달된다.
  • 여기서 중요한 포인트는 다음과 같다.
    • A. 여러 신호가 신경 세포체로 전달된다.
    • B. 전달된 신호는 하나로 합쳐진다.
    • C. 합쳐진 신호가 특정 임계값을 넘으면, 전달된다.
  • 나중에 다룰 이야기지만, 위 이야기에서 정보가 하나로 합쳐져서 전달되는 과정이 활성화함수(Activation function)를 나타내는 부분이라고 생각하자(이 건 나중에 다시 이야기하니깐 대충 넘기자)

 

 

 

 

2. 퍼셉트론(Perceptron)

  • 퍼셉트론은 위에서 나온 신경 세포를 알고리즘화한 것으로, 딥러닝의 기본이 되는 개념이다.
  • 퍼셉트론은 N개의 입력 받아 1개의 신호를 출력한다.
  • 퍼셉트론은 받아들여 합친 신호가 임계값보다 크면 1을 출력하고, 임계값보다 작으면 0을 출력하여, 정보를 전달하거나 전달하지 않는다.

입력이 2개인 퍼셉트론

  • 위 퍼셉트론 그림에서 원은 노드(Node) 혹은 뉴런(Neuron)이라고 부르며, 선은 엣지(Edge)라고 부른다.
  • 노드에서 다음 노드로 정보가 전해질 때, 가중치($w_1$, $w_2$)가 곱해져 전달된다.
  • 전달되는 값과 가중치가 곱해진 값($w_1x_1$)의 합($w_1x_1 + w_2x_2$)이 임계값 $\theta$(theta)보다 크면 1이, 작으면 0이 출력되는데, 이는 위에서 언급한 신경세포에서 신호가 전달되는 과정을 그대로 따라한 것과 같다.
  • 이를 수식으로 나타내면 다음과 같다.

$$y = \begin{cases}
 0 \ \ \ (w_1x_1 + w_2x_2 \leq \theta) \\ 
 1 \ \ \ (w_1x_1 + w_2x_2 > \theta)
\end{cases}$$

  • 위 함수를 보면, 위 신경세포에서 설명한 신호가 움직이는 과정을 쉽게 표현한 것임을 알 수 있다.

 

 

 

3. 퍼셉트론의 구현

  • 앞서 입력 노드가 2개인 퍼셉트론의 함수를 적어보았다. 만약, 위 함수를 파이썬 코드로 구현한다면, 그것이 바로 퍼셉트론을 구현하는 것이 아니겠는가. 한 번 만들어보도록 하자.
# Perceptron 함수를 코드로 구현해보자
>>> def Perceptron(x1, x2, w1, w2, theta):

>>>     y = w1*x1 + w2*x2
            
>>>     if y <= theta:
>>>         return 0
                
>>>     elif y > theta:
>>>         return 1
  • $x_1$과 $x_2$는 퍼셉트론의 노드이므로, 0과 1의 값만 나올 수 있다.
  • $x_1=0,\ x_2=1,\ w_1=0.1,\ w_2=0.3,\ theta=0.6$으로 함수를 실행해보자 
>>> Perceptron(x1=0, x2=1, w1=0.1, w2=0.3, theta=0.6)
0
  • 위 결과를 보면, 0*0.1 + 1*0.3 <= 0.6으로 나와 0이 반환된 것을 알 수 있다.
  • 이를 보면, 가중치는 해당 노드에서 전달되는 정보의 중요도를 어느 정도나 강하게 줄 것인지를 따지는 것이고, 임계값은 받아들인 신호를 얼마나 타이트하게 평가할 것인가를 판단하는 수단이라는 것을 알 수 있다.

 

 

 지금까지 신경망의 기본이 되는 신경 세포, 퍼셉트론의 기본 개념에 대해 학습해보았다. 다음 학습에선 퍼셉트론을 이용하여 단순한 논리 회로를 적용하는 방법에 대해 학습해보도록 하겠다.

 

 

 해당 포스팅은 사이토 고키의 "밑바닥부터 시작하는 딥러닝"책을 참고하여 작성하였다. 해당 책은 꽤 얇고 내용도 이해하기 쉽게 써놨으므로, 머신러닝을 시작하는 초보가 읽기에 괜찮은 책이다. 시간 날 때, 한 번 읽어보기 바란다(참고로 PPL이 아니다! 내 돈 주고 내가 산 괜찮은 책 소개다!).

728x90
반응형
728x90
반응형

 현재 컴퓨터를 사용하는 모든 분야에서 가장 핫한 분야를 한 가지 고르라고 한다면, 장담컨대 "인공지능 > 머신러닝 > 딥러닝"을 꼽을 수 있을 것이다. 알파고 이후로 모든 매체에서는 인공지능, 머신러닝, 딥러닝이라는 단어를 외치고 있으며, 이 것이 중요하다는 것은 알겠는데 구체적으로 어떻게 중요한지, 그리고 이 것이 구체적으로 무엇인지를 제대로 설명해주는 곳은 많지가 않다.

 이번 포스트에서는 "인공지능 > 머신러닝 > 딥러닝"으로 이어지는 단어들에 대해 설명하고, 앞으로의 학습 방향에 대해 이야기해보도록 하겠다.

인공지능의 포함 관계

 

 

1. 인공지능(AI)이란?

 인공지능이라는 단어를 들으면 막연하게 "터미네이터 같은 인간과 유사한 생김새를 갖고 있으며, 인간보다 뛰어난 능력을 가진 존재"라는 생각이 들 것이다. 인공지능은 크게 General AI(일반적인 AI)와 Narrow AI(좁은 AI) 두 가지로 나뉘는데, 이를 설명해보면, 대충 감이 올 것이다.

General AI

  • 영화에 나오는 인공지능으로 인간처럼 사고를 하며, 상황에 맞는 다양한 활동을 하는 인공지능을 말한다.
  • 예를 들자면 자신의 다리로 계단이던 길이던 횡단보도던 알아서 돌아다니고, 필요에 따라 판단하여 택시나 버스를 타고, 에너지가 부족하다 싶으면 알아서 에너지도 충전하고, 회사에 출근해서 눈치를 살피는 그런 인간과도 같은 존재를 가리킨다.
  • 이런 복합적인 기능을 자유자재로 동시에 수행하는 것은 아직까진 우리 상상 속에만 존재하며, 영화나 드라마에서만 나오는 존재다.

Narrow AI

  • 말 그대로 좁은 영역에서의 인공지능을 말하며, 특정한 행동에 대해서만 특화 돼 있는 인공지능이다. 
  • 가장 유명한 인공지능인 알파고는 바둑에 특화된 인공지능으로, 학습을 위해 바둑 기보들을 수집하고, 학습된 내용을 바탕으로 스스로 경쟁하여 바둑을 학습해, 바둑이라는 게임에서 승리할 수 있도록 만든 것이다.
  • 학교, 뉴스, 4차 산업 혁명에 해당하는 인공지능이 바로 Narrow AI이다.
  • 우리가 앞으로 학습할 인공지능이 이 곳에 해당한다.

 

 

 

 

2. 머신러닝(Machine Learning)이란?

 머신러닝은 위에서 이야기한 Narrow AI에 속하는데, 말 그대로 기계를 학습시킨다는 말이다.

 갓 태어난 아이는 성장을 하며 말을 배우고, 친구를 사귀는 법을 배우며, 국어, 영어, 수학 같은 학문을 배운다. 나아가 음악을 들었을 때, 이 음악이 어떤 음악이었는지 그 제목을 떠올리기도 하고, 그림을 보고 그림의 제목과 화가의 이름을 맞추기도 한다.

 우리가 처음으로 말을 배웠을 땐, 부모님이 하는 말을 계속 들어왔고, 부모님들은 사물에 사물 이름을 적은 메모장을 붙여놓는 방법 등을 통해 우리를 가르쳤다. 친구를 사귀는 법은 다양한 사람을 만나가며, 어떻게 하면 그들과 유대감을 쌓을 수 있는지 경험으로 익혔다. 국어, 영어, 수학 같은 학문은 계속 책을 보며, 문제를 푸는 방법과 사고하는 방법을 익혔다. 음악이나 그림을 판단하는 방법도 다양한 음악이나 그림을 접하면서 그것들을 구분하는 능력을 쌓았다.

 위 예시에서 우리는 오로지 데이터만을 가지고 지금의 복잡한 작업을 수월하게 할 수 있게 된 것이다. 컴퓨터를 이용한 기계학습 역시 이와 유사하게 이루어진다. 기본적인 머신러닝 알고리즘이 존재하고, 그 알고리즘에 데이터를 부어넣으면, 그 데이터가 가지고 있는 패턴을 찾아내, 그 패턴대로 분류하게 되는 것이 바로 머신러닝이다.

 

 

 

 

3. 딥러닝(Deep Learning)이란?

 1943년 논리학자 윌터 피츠(Walter Pitts)와 신경외과의 워렌 맥컬럭(Warren Mc Cullonch)은 "A Logical Calculus of Ideas Immanent in Nervous Activity"라는 논문에서 딥러닝의 기반이 되는 인공신경망이라는 개념을 등장시켰고, 인간의 신경세포를 모방한 퍼셉트론을 등장시켰다.

 퍼셉트론을 이용한 인공지능 연구능 처음엔 굉장한 인기를 끌었으나, 굵직 굵직한 사건이 터져 몇 번이고 사장될 위기에 처했었다. 처음엔 XOR 게이트로 인해 퍼셉트론은 선형 분류밖에 할 수 없다는 한계점이 등장했고, 이를 다층 퍼셉트론(MLP)라는 개념을 등장시켜 선형 분류의 한계점을 해결하였으나, MLP를 학습시킬 방법이 없다는 한계점이 또 등장하였다. 이는 오류역전파(Backpropagation of errors)라는 기법으로 해결하였으나, 층이 늘어날수록 기울기가 소실되는 문제가 또 등장하고, 은닉층 활성화 함수로 Sigmoid 대신 ReLU를 사용하여 해결하는 등 수많은 과정을 거쳐 지금의 딥러닝이 탄생하게 되었다.

 딥러닝이라는 이름이 생긴 이유도 기존의 다층 퍼셉트론(MLP)에 대한 부정적인 시선을 피하기 위해 딥러닝이라는 새로운 이름을 붙인 것이며, 다층 퍼셉트론(MLP)의 은닉층을 아주 많이 쌓기 때문에 딥(Deep)해진다. 즉, Layer가 깊어진다라는 의미에서 딥러닝이라는 이름이 붙은 것이다.

 딥러닝 역시 머신러닝의 한 갈래에 속하지만, 머신러닝에는 퍼셉트론을 포함하여 수많은 이론들이 존재하므로, 머신러닝과 딥러닝을 분리해서 생각하는 것이 좋다.

 

 

 해당 카테고리에서는 머신러닝에 대해서 학습할 계획이며, 딥러닝에 대해서는 Deep Learning 카테고리에서 따로 학습할 예정이다.

728x90
반응형
728x90
반응형

R 기본 함수로 결측값 다루기

0. 결측값을 다뤄볼 test Dataset을 만들어보자.

# test Dataset
row_number = 20
ID_vt = seq(1, row_number)
ID_vt = paste0("A", ID_vt)

math_vt = sample(10:100, row_number)
english_vt = sample(10:100, row_number)
science_vt = sample(10:100, row_number)
korean_vt = sample(10:100, row_number)

df = data.frame(ID = ID_vt, math = math_vt, english = english_vt,
                science = science_vt, korean = korean_vt)

df[c(10,16), c("math")] = NA
df[c(4,6,10,19), c("english")] = NA
df[c(10,20), c("science")] = NA
df[c(4,15,16,20), c("korean")] = NA

df
##      ID math english science korean
##  1   A1   66      94      78     53
##  2   A2   89      87      74     82
##  3   A3   36      58      44     60
##  4   A4   96      NA      21     NA
##  5   A5   37      96      63     19
##  6   A6   63      NA      45     94
##  7   A7   80      14      79     20
##  8   A8   75      19      56     66
##  9   A9   35      44      59     14
##  10 A10   NA      NA      NA     35
##  11 A11   72      92      27     39
##  12 A12   74      79      96     76
##  13 A13   98      80      10     10
##  14 A14   11      28      87     62
##  15 A15   44      90      20     NA
##  16 A16   NA      24      35     NA
##  17 A17   88      12      55     22
##  18 A18   58      25      40     81
##  19 A19   67      NA      48     25
##  20 A20   50      75      NA     NA
  • 무작위 위치에 결측값을 넣어주었다.

 

 

 

1. 결측값 확인하기

함수 의미
is.na(x) data에 속한 NA를 True로 나타낸다.
na.fail(x) dasta에 NA가 포함되어 있으면 Error를 반환한다.
na.omit(x) data에 NA가 포함되어 있으면 이를 제외한다.
na.pass(x) data에 NA가 포함되어 있더라도 무시하고 진행한다.
complete.case(x) data에서 결측값이 없는 행만 반환한다.
  • is.na() 함수와 table() 함수를 혼합해서 사용해보면 보다 재밌는 결과가 나온다.

 

# 데이터 전체를 NA 기준으로 TRUE, FALSE를 반환
is.na(df)
##           ID  math english science korean
##  [1,] FALSE FALSE   FALSE   FALSE  FALSE
##  [2,] FALSE FALSE   FALSE   FALSE  FALSE
##  [3,] FALSE FALSE   FALSE   FALSE  FALSE
##  [4,] FALSE FALSE    TRUE   FALSE   TRUE
##  [5,] FALSE FALSE   FALSE   FALSE  FALSE
##  [6,] FALSE FALSE    TRUE   FALSE  FALSE
##  [7,] FALSE FALSE   FALSE   FALSE  FALSE
##  [8,] FALSE FALSE   FALSE   FALSE  FALSE
##  [9,] FALSE FALSE   FALSE   FALSE  FALSE
## [10,] FALSE  TRUE    TRUE    TRUE  FALSE
## [11,] FALSE FALSE   FALSE   FALSE  FALSE
## [12,] FALSE FALSE   FALSE   FALSE  FALSE
## [13,] FALSE FALSE   FALSE   FALSE  FALSE
## [14,] FALSE FALSE   FALSE   FALSE  FALSE
## [15,] FALSE FALSE   FALSE   FALSE   TRUE
## [16,] FALSE  TRUE   FALSE   FALSE   TRUE
## [17,] FALSE FALSE   FALSE   FALSE  FALSE
## [18,] FALSE FALSE   FALSE   FALSE  FALSE
## [19,] FALSE FALSE    TRUE   FALSE  FALSE
## [20,] FALSE FALSE   FALSE    TRUE   TRUE
  • 전체 Data 안에서 결측값 여부를 보여준다.
# 데이터 내, 다수 변수들의 결측값을 한 번에 확인
# 출력된 결과는 각 변수별 결측값의 수이다.
colSums(is.na(df))
## ID    math english science  korean 
##  0       2       4       2       4
  • 가장 쉬운 코드로 결측값의 상태를 보여준다.
# 데이터 내 총 결측값의 수
table(is.na(df))
## FALSE  TRUE 
##    88    12
sum(is.na(df))
## [1] 12
  • 둘 모두 총 결측값의 수이다.
# 특정 열의 결측치 확인
table(is.na(df$math))
## FALSE  TRUE 
##    18     2
  • 특정 열에서 결측값이 몇 개 있는지 반환한다.
# 결측값이 전체 데이터 셋에서 차지하는 비중
mean(is.na(df))
## [1] 0.12
  • 전체 데이터에서 결측값이 차지하는 비중으로 dataframe$column을 하여 특정 컬럼에 대해서만 확인할 수도 있다.

 

 

 

 

2. 결측값이 있는 행 제거하기

  • 데이터 내에서 결측값이 있는 모든 행을 제거하는 방법과 변수별 결측값이 있는 행만 취사 선택하여 제거하는 방법이 있다.
  • 전체 결측값을 제거하는 경우는 아래와 같다.
# 결측치가 없는 행만 반환1
na.omit(df)
##     ID math english science korean
## 1   A1   55      15      63     52
## 2   A2   79      73      95     21
## 3   A3   33      84      66     49
## 5   A5   15      35      78     91
## 7   A7   80      45      67    100
## 8   A8   91      34      97     84
## 9   A9   85      51      61     23
## 11 A11   63      65      43     88
## 12 A12   13      83      32     12
## 13 A13   99      39      69     64
## 14 A14   76      46      83     96
## 17 A17   11      80      31     33
## 18 A18   68      92      12     24
# 결측치가 없는 행만 반환2
df[complete.cases(df),]
##     ID math english science korean
## 1   A1   47      94      37     54
## 2   A2   61      62      25     94
## 3   A3   96      40      83     49
## 5   A5   63      81      11     43
## 7   A7   90      41      85     88
## 8   A8   70      30      49     19
## 9   A9   97      84      90     22
## 11 A11   80      76      52     14
## 12 A12   84      22      41     66
## 13 A13   16      58      93     61
## 14 A14   75      21      30     45
## 17 A17   51      56      55     71
## 18 A18   37      53      84     33
  • na.omit()과 complete.cases()는 유사한 기능을 한다.
  • 특정 변수의 결측값만 제거하는 방법은 다음과 같다.
# math 컬럼의 결측값을 제외하고 가져오자
df %>% filter(!is.na(math))
##     ID math english science korean
## 1   A1   15      36      69     12
## 2   A2   94     100      74     58
## 3   A3   71      94      32     62
## 4   A4   93      NA      34     NA
## 5   A5   48      58      46     52
## 6   A6   64      NA      92     77
## 7   A7   40      16      72     40
## 8   A8   86      86      77     19
## 9   A9   81      89      28     63
## 10 A11   47      91      94     93
## 11 A12   97      27      45     70
## 12 A13   14      63      62     59
## 13 A14   61      72      50     49
## 14 A15   92      30      65     NA
## 15 A17   78      78      13     44
## 16 A18   68      99      15     86
## 17 A19   30      NA      68     48
## 18 A20   72      66      NA     NA
  • dplyr은 R에서 기본으로 제공하는 함수는 아니지만, Data Handling에서 필수인 함수이므로 한번 짚고 넘어가겠다.
df[complete.cases(df[,"math"]),]
##     ID math english science korean
## 1   A1   15      36      69     12
## 2   A2   94     100      74     58
## 3   A3   71      94      32     62
## 4   A4   93      NA      34     NA
## 5   A5   48      58      46     52
## 6   A6   64      NA      92     77
## 7   A7   40      16      72     40
## 8   A8   86      86      77     19
## 9   A9   81      89      28     63
## 11 A11   47      91      94     93
## 12 A12   97      27      45     70
## 13 A13   14      63      62     59
## 14 A14   61      72      50     49
## 15 A15   92      30      65     NA
## 17 A17   78      78      13     44
## 18 A18   68      99      15     86
## 19 A19   30      NA      68     48
## 20 A20   72      66      NA     NA
  • na.omit()처럼 모든 결측값을 한번에 제거하는 방법은, 분석에서 중요하지 않은 변수의 결측값의 존재로 인해 실제 사라지지 않아도 되는 결측값까지 모두 제거해버릴 위험이 있다.
  • 그러므로, na.omit()과 같은 결측값 제거 방법을 사용하기 전에 분석 대상이 될 변수와 표본 축소가 필요한 경우(예를 들어 비흡연자만 대상으로 하여 흡연 관련 변수와 흡연자를 분석 대상에서 제외함) 이를 먼저 실시하여 Data를 1차 정리하고 결측값 제거를 하도록 하자.

 

 

3. 결측값이 있는 열의 연산

  • 결측값이 데이터에 포함돼 있는 경우, 이를 바로 연산하면 결과가 NA로 출력된다.
  • 결측값이 있는 행을 제외하고 연산하고자 한다면 na.rm 파라미터에 True로 전달하면 된다.
# math 열에 있는 결측값만 제외하고 가져와보자
mean(df$math, na.rm = TRUE)
## [1] 60.44444
# math 컬럼의 결측값을 제외하고 mean을 계산해보자
library(dplyr)
df %>% summarise(mean_N = mean(math, na.rm = TRUE))
## [1] 60.44444

 

  •  결측값을 다룰 때 가장 중요한 것은 분석 대상 집단을 정하는 것으로, 분석 목표에 필요한 변수를 모두 가져오며, 결측값이 존재하지 않는 대상 집단을 명확히 해야한다.
  •  이 부분은 초보 분석가들이 하기 가장 좋은 실수 중 하나로 변수 A의 결측값과 변수 B의 결측값이 서로 다르게 제거된 상태에서 평균을 비롯한 각종 연산 값을 도출하면, 서로 다른 집단에 대한 연산값이 도출되는 결과가 나올 수 있다.
  •  그러므로, na.rm 인자를 이용해 연산하기보다는 분석 대상 집단을 명확히 하여 결측값을 모두 제거한 후 연산하기를 바란다.

 

 

 지금까지 R의 기본 함수를 이용해서 결측값을 다루는 법에 대해 학습해보았다. 다음 포스트에서는 결측값을 다루는 데 있어서 가장 유용한 라이브러리 중 하나인 naniar 패키지를 활용해서 결측값을 다루는 법을 알아보자.

728x90
반응형
728x90
반응형

 단일 대체법(Single Imputation)을 학습해보면서, 대체된 값이 임의의 오차를 가지므로 실제 현상과 차이가 클 수 있다는 것을 알았다. 이번 포스트에서는 이 단일 대체법을 여러 번 실시하여 오차의 불확실성을 고려(앙상블)하는 다중 대체법(Multiple Imputation)에 대해 학습해보겠다.

 

 

다중대체법(Multiple Imputation)

1. 다중대체법의 절차

  • A. 데이터셋 생성: 특정 알고리즘에 따라 결측 값을 대체 값으로 바꾼 m개의 데이터 셋 생성
  • B. 분석과 추정: m개의 완전한 데이터셋을 각각 원하는 분석 기법에 대해 분석하고, 그 결과에서 모수 추정치와 표준오차 계산
  • C. 결합: 각 데이터 셋의 결과를 Rubin's rule에 의해 결합
  • 위 과정을 간략히 이야기해보면 대체값으로 결측 값을 채워 넣은 데이터셋을 m개 만들고, 내가 분석하고 싶은 모델 A를 m개 모델에서 각각 추출한 후, 추정치의 평균을 모델 A의 분석 결과로 뱉어내는 방식이다.
  • 이런 여러 모델을 동시에 사용해 보다 정확한 예측을 하는 기법을 앙상블(Ensemble)이라고 한다.

 

※ Rubin's rule:

  • Rubin, B. D. (1987). “The Calculation of Posterior Distributions by Data Augmentation: Comment: A Non-iterative Sampling/Importance Resampling Alternative to the Data Augmentation Algorithm for Creating a Few Imputations when Fractions of Missing Information are Modest: the SIR algorithm, ”Journal of the American Statistical Association, 82(398): 543-546.
  • 각 데이터 셋 별로 구한 추정치($\bar{Q}$)와 표준오차($\sqrt{T}$)를 결합하는 방법으로, 추정치의 결합은 각 데이터셋으로부터 구한 추정치의 평균으로 정의된다.
  • 추정치($\bar{Q}$)의 분산 $T$는 대체 내 분산(within-imputation variance) $W$와 대체 간 분산(between-imputation variance) $B$의 결합 값으로 정의된다.

$$\bar{Q}=\frac{1}{m}\sum\limits_{i=1}^{m}\hat{Q_i}$$

$$\bar{W}=\frac{1}{m}\sum\limits_{i=1}^{m}\hat{W_i},   B=\frac{1}{m-1}\sum\limits_{i=1}^{m}(\hat{Q_i}-\bar{Q})^{2},   T=\bar{W}+(1+\frac{1}{m})B$$

 

  • 다중 대체법(MI)은 대체될 변수에 대한 사전  분포 가정의 존재 여부에 따라 MVNI, MICE 방식이 있다.

 

 

 

 

2. 다변량 정규분포 대체(Multivariate Normal Imputation, MVNI) 방식

  • 모든 변수들이 정규분포를 따른다는 것을 가정하고 베이지안 접근(Bayesian's approach)에 따라 정규분포에서 대체 값을 획득하게 된다.
  • 이러한 정규성 가정은 이항 변수나 범주형 변수에는 적용 가능성이 떨어질 수 있다는 지적이 있으나, Schafer(1999)는 정규성이 만족하지 않아도 MVNI 적용이 가능하다고 보았다(Schafer, J. (1999). “Multiple Imputation: A Primer”. Statistical Methods in Medical Reseach, 8(1): 3-15).
  • MVMI의 대표적인 방식은 MCMC 방식이 있다.

 

마르코프 연쇄 몬테카를로 방법(Markov Chain Monte Carlo, MCMC)

  • MCMC는 최초에 정규분포를 가정하고 시작하나, 반복이 되면서 바로 전 단계의 정보에 의해서만 현재 단계의 정보가 업데이트되는 마르코프 체인의 특성이 적용된다.
  • 그 결과 최초에 설정한 분포와 상관없이 최종적으로 관측치와 대체된 결측치를 가장 잘 설명하는 분포에 수렴하게 된다.
  • MCMC는 I-step, P-step 두 단계로 구성된다.
  • I-step(Imputation step): 결측치를 대체하는 단계
  • P-step(Posterior step): I-step을 통해 분포의 모수를 추정하는 단계
  • I-step과 P-step이 번갈아 가며 진행되면서, 이 두 단계가 대략적으로 독립적인 추출이 될 때까지 충분히 반복한다.
  • 보다 상세한 내용은 위키피디아를 참고하기 바란다.

 

ko.wikipedia.org/wiki/%EB%A7%88%EB%A5%B4%EC%BD%94%ED%94%84_%EC%97%B0%EC%87%84_%EB%AA%AC%ED%85%8C%EC%B9%B4%EB%A5%BC%EB%A1%9C_%EB%B0%A9%EB%B2%95

 

마르코프 연쇄 몬테카를로 방법 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 마르코프 연쇄 몬테카를로 방법(무작위 행보 몬테 카를로 방법 포함)은 마르코프 연쇄의 구성에 기반한 확률 분포로부터 원하는 분포의 정적 분포를 갖는 표본

ko.wikipedia.org

 

 

 

 

3. 연쇄방정식에 의한 다중대체(Multiple Imputation with Chained Equations, MICE)

  • 모든 변수들이 정규분포에 따른다는 가정이 없는 방식으로, 분포를 가정하지 않기 때문에 MVNI 방식에 비해 유연하게 사용할 수 있다.
  • 결측치의 조건적인 분포가 다른 모든 변수들에 의해 결정되며, 분포 가정이 없기 때문에 서열 척도, 명목형 척도 등 다양한 변수에도 적용할 수 있다.
  • 대표적인 방법으로 FCS 방식이 있다.

 

완전조건부 대체법(Fully Conditional Specification, FCS)

  • 분포에 대한 가정 없이 연속된 회귀방정식을 통해 값을 대체해 나가는 방법이다.
  • FCS는 Fill-in, Imputation 두 단계로 이루어진다.
  • Fill-in(채워 넣기): 모든 변수의 결측치를 변수의 순서대로 채우며, 앞서 채워진 변수는 다음 채워지는 변수의 독립변수로 활용되는 방식이다.
    • 회귀 대체, 평균 대체 등이 활용되며, 범주형 변수나 이항 변수는 로지스틱 회귀모형을 활용한다.
  • Imputation(대체): 앞서 채워진 값들을 변수의 순서대로 대체하는 과정으로, 이 과정을 충분히 길게 하여 대체된 데이터셋에서 결측치가 독립적인 추출이 될 때까지 시행한다.

 

 

 

 

4. MCMC와 FCS 방법 비교

  • FCS는 MCMC에 비해 상대적으로 적은 수의 반복으로도 수렴하며, 5~10번 정도면 충분히 만족스러운 결과를 나타낸다고 한다(Brand, J. P. L. (1999). Development, Implementation and Evaluation of Multiple Imputation Strategies for the Statistical Analysis of Incomplete Data Sets. PhD. dessertation; Erasmus University, Rotterdam.).
  • Lee & Carlin(2010)은 MVNI와 FCS를 완전제거법과 비교하는 시뮬레이션 분석을 통해, 정규성이라는 가정을 동반한 MVNI 방식이 FCS보다 못하다는 증거가 없다고 주장했다.
  • 이와 더불어 FCS와 MVNI 모두 완전제거법에 비해 불편성과 검정력 등에서 우월하며, 특히 MVNI는 이항 변수(binary variable)와 순서형 변수(ordinary variable)에서도 여전히 좋은 방법이라 주장하였다.
  • Lee, K, J. & Carlin, J. B.(2010). “Multiple Imputation for Missing Data: Fully Conditional Specification Versus Multivariate Normal Imputation,” American Journal of Epidemiology, 171(5): 624-632.

 

 

 

 

5. 변수 특성과 결측 값 유형에 따른 대체방법

결측 패턴 대체될 변수유형 대체방법
단조(Monotonic)
패턴
연속형 변수 Monotone regression
Monotone predicted mean matching
Monotone propensity score
서열 변수 Monotone logical regression
명목 변수 Monotone discriminant function
일반적 패턴 연속형 변수 MCMC full-data imputation
MCMC monotone-data imputation
FCS regression
FCS predicted mean matching
서열 변수 FCS logistic regression
명목 변수 FCS discriminant function

 

  • 결측 패턴과 변수의 특성에 따라 회귀분석을 이용한 추정(Regression), 성향 점수법(Propensity score), 로지스트기 회귀분석을 이용한 추정 등을 사용
  • 분포에 대한 가정 여부에 따라 정규분포를 가정하는 경우(MCMC)와 그렇지 않은 경우(FCS)로 구분
  • 결측의 패턴이 단조적이고 연속형 변수의 경우, 다변량 정규분포를 가정하는 모수 방법(parameter)과 성향점수법과 같은 비모수적인 방법(non-parametric)을 모두 사용할 수 있음
    • 단조(Montonic) 패턴: 함수의 진행방향이 항상 일정한 함수 - 단조증가함수 또는 단조감소함수 등
  • 단조 패턴의 서열형 변수의 경우 로지스틱 회귀분석법을 명목형 변수에는 판별 함수법 사용
  • 일반적인 결측 패턴의 경우, 다변량 정규분포를 가정하는 MCMC 방법이나, 모든 변수의 결합 분포가 존재한다고 가정하는 FCS 방법을 활용할 수 있다.
  • 연속형 변수에 대한 대체는 분포에 대한 가정과 상관없이 MCMC, FCS를 모두 사용할 수 있다.
  • 일반적으로 MCMC가 다변량 정규분포 가정을 하고 있으나, 결측 된 정보가 크지 않은 경우, 다중 대체에 의한 추정이 강건한(robust) 결과를 보인다고 알려져 있다. 하지만 연속형 변수와 더불어 서열형이나 명목형 변수에 대한 대체를 하기 위해서는 MCMC보다 FCS가 권장된다.

 

 

 

 지금까지 결측값 대체 기법 중 하나이며, 단일 대체법의 단점을 보완할 수 있는 방법인 Multiple Imputation에 대해 알아보았다. 다음 포스트에서는 R 언어를 활용하여 실제 결측값을 탐색하는 방법과 결측값을 대체하는 방법에 대하여 실습해보도록 하겠다.

728x90
반응형
728x90
반응형

지난 포스트에선 결측 값의 종류에 대해 살펴보았다. 이번 포스트에서는 발생한 결측 값들이 무작위 결측(MAR)이란 가정하에 결측 값을 대체하는 방법에 대해 알아보도록 하겠다.

 

 

결측 값 대체하기 - Single imputation

 발생한 결측 값이 어떠한 사유에 의해 발생한 것으로 추정되는 상태인 비 무작위 결측(NMAR)이 아니라면, 다음과 같은 비교적 단순한 방법으로 결측 값을 대체할 수 있다.

 

 

1. 완전제거법(Listwise deletion)

  • 결측치가 포함된 케이스를 분석 대상에서 완전히 제거하는 방법
  • SPSS, SAS, STATA와 같은 각종 통계 툴에서 기본값(Default)으로 설정돼있는 방법이다.
  • 발생한 결측값이 완전 임의 결측(MCAR)인 경우, 해당 방법을 사용해도 전체 집단을 대표할 수 있으며, 분석의 편향(bias)이 발생하지도 않는다.
  • 한계점
    1. 그러나, 완전 임의 결측(MCAR)이 발생할 확률은 굉장히 낮으므로, 완전제거법으로 만들어진 데이터는 전체 표본을 대변하지 못하고, 편향을 일으킬 수 있다. (Stuart, E. A., Azur, M, Frangakis, C. & Leadf, P.(2009). “Multiple Imputation with Large Data Sets: A Case Study of the Children’s Mental Health Initiative, ”American Journal of Epidemiology, 169(9): 1133-1139.)
    2. 각 변수의 결측치가 완전임의결측이라 일지라도, 서로서로 결측이 있는 행이 달라, 완전 제거법 사용 후, 표본 수가 급격하게 감소할 수 있다.
    3. 정보의 손실과 검정력 약화 문제가 발생할 수 있다.

 

 

 

2. 평균대체법(Mean substitution)

  • 어떤 변수의 결측값을, 관측된 값의 평균값으로 대체
  • 평균 대체법은 특정 정보가 부재할 경우, 평균이 가장 좋은 대푯값이라는 논리에 기인하는 방법
  • 한계점
    1. 정보량 손실을 줄인다는 관점에서는 완전 제거법에 비해 향상된 방법이라고 할 수는 있으나, 여전히 결측치 대체에 불확실성을 고려하지 못한다는 단점이 있다.
    2. 결측치 대신 평균값을 넣기 때문에, 변수의 분산이 감소되고, 공분산과 상관계수의 혼란도 발생할 가능성이 있다. 이로 인해 평균 대체를 가장 좋지 않은 대체방법이라고도 한다.
    3. 예를 들어, 실제 수학 성적의 평균 값은 65점이나, 점수가 매우 낮은 사람들이 자신의 성적을 기재하지 않아 결측 값을 제외한 평균값인 80점을 결측 값 대신 넣는다면, 관측값의 평균에 결측 값 데이터들이 모두 모이게 되고, 그로 인해 분산이 크게 왜곡될 수 있다.

 

 

 

3. 회귀대체법(Regression-based single inputation)

  • Osbome, J. W. (2012). Best Practices in Dataa Cleaning: A Complete Guide to Everything You need to Do Before and After Collecting Your Data. CH 6. Thousand Oaks, CA: Sage
  • 응답자의 응답값과 결측치 간에 강한 상관관계가 존재한다는 가정 하에, 관측치 간의 회귀 방정식을 통해 결측치를 예상한다.
  • 결측치를 포함하는 변수를 종속변수로, 나머지 변수를 독립변수로 하는 회귀식을 구성하고, 이렇게 추정된 회귀식의 예측값으로 결측 값을 대체한다.
  • 변수의 특성(연속형 변수, 범주형 변수, 교호작용항 및 이차항 등)에 따라 회귀식을 구성해 예측력을 향상한다.
  • 한계점
    • 회귀식을 이용한 단일 대체는 결측 값을 실제로 관찰된 값으로 가정하고 분석하므로, 결측값 대체의 불확실성을 고려하지 않는다.
    • 표준오차가 과소추정되어 p-value가 실제보다 작아지고, 신뢰구간이 좁아질 수 있음

 

 

 

4. 핫덱대체법(Hot deck)

  • 비슷한 성향을 가진 케이스의 값으로 결측 값을 대체한다.
  • 자료 분포를 가정하지 않으며, 다른 변수(나이, 성별, 소득 등)가 유사한 응답자의 값을 임의로 추출해 결측치를 대체한다.
  • 한계점
    1. 어떤 변수를 기준으로 대체군을 형성하는지와 어떤 순서로 기준을 적용하는지에 따라 결측값 대체 값이 달라진다.
    2. 대체에 대한 명확한 분포 가정이나 모형을 정의하지 않으므로, 수리적으로 편의(bias)를 계산할 수 없어 대체 결과를 평가하기 어렵다.

 

 

 

5. 가중치 보정법(Weighting)

  • 결측치를 대체하지 않고 관측된 값에 가중치를 부여하여 보정함.
  • Data를 특정 변수(성별, 연령대 등)에 따라 집단을 나누고, 각 집단의 조사설계 시 예상 관측 치수에 대비한 실제 응답한 관측치의 수를 비율로 응답 확률로 정의함
  • 응답 확률의 역수를 가중값으로 관측된 값에 곱해 분석 결과를 보정한다.
  • 한계점
    1. 가중값을 변수의 관측된 값에 곱하므로 해당 변수의 분산이 증가하게 된다.
    2. 무응답과 관련 있는 변수는 편의가 줄어드는 대신 분산이 늘어나는 상충관계지만, 무응답과 관련 없는 변수는 분산만 증가한다.

 

 

 

6. 기댓값 최대화 알고리즘(Expectaion-maximization algorithm, EM 알고리즘)

  • Little, R. J. & Rubin, D. B. (2002). Statistical Analysis with Missing Data. Hoboken, NJ: J Wiley & Sons.
  • 앞서 설명한 5가지 방법을 하나로 묶어 전통적 대체방법(Conventional imputation method)라고 하는데, 이러한 단일 대체 방법들은 각자 편향(bias), 분산 감소 등의 문제를 일으킬 수 있어, 실제 분석결과를 왜곡시킬 위험이 있다.
  • 이러한 문제를 감안하고도 완전한 데이터셋을 만들고자 한다면 EM 알고리즘을 사용하는 것이 유리하다.
  • E는 기댓값(Expectation), M은 최대화(Maximization)를 의미한다.
  • EM 알고리즘은 관측되지 않는 잠재변수에 의존하는 확률 모델에서 최대가능도(Maximum likelihood)나 최대 사후 확률(Maximum a posteriori)을 갖는 모수 추정 값을 E-step과 M-step 반복 계산을 통해 찾아내는 알고리즘이다.
  • E-step: 모수에 관한 추정 값으로 우도(Likelihood)의 기댓값을 계산하는 기댓값 단계
  • M-step: E-step의 기댓값을 최대화하는 모수 추정 값들을 구하는 최대화 단계
  • E-step과 M-step 과정을 반복하여 계산 결과가 충분히 수렴하게 되면, 최종 최대가능도나 최대 사후 확률을 통해 결측치의 대체 값을 결정한다.
  • 한계점
    1. EM 알고리즘 방식을 통한 단일 대체는 대체된 값의 분산이 너무 작다는 문제가 존재함

 

 

 

 지금까지 결측 값을 대체하는 방법 중 단일 대체 방법에 대해 학습해보았다. 단일 대체법은 간단하게 말하자면, 결측 값이 지나치게 많지 않은 상태(5% 이하이며, 그 이상은 보다 정교한 방식을 써야한다. 그러나 결측값이 30% 이상 넘어간다면, 정교한 방식을 사용하여도 제대로 추론하지 못한다.)에서 결측 값들을 채워서 완벽한 데이터 셋을 만드는 방법이다.

  • 결측값이 10% 미만이면 무슨 방법을 쓰던 큰 상관이 없으며, 그 이상부터는 회귀 대체를 포함한 보다 정교한 모델을 써야한다고 하지만, 개인적으로는 5% 미만을 생각하고 보다 보수적으로 접근하는 것이 안전하지 않나 싶다.
  • 결측값이 30% 이상 넘어간다면, 가능한 결측값 대체 방식을 사용하지 않기를 바란다(이전에 들었던 내용인데, 정확히 어디서 나온 내용인지 기억 나지 않아 확답은 힘들다만 결측값이 차지하는 비중이 지나치게 크다면, 관측값만으로 결측값을 추정하는데 한계가 생길 수 있으므로, 결측값 대체 방법에 너무 의존하지 않는 것이 좋다.)

 

 이 중 가장 쉽게 쓸 수 있으면서 효과도 썩 나쁘지 않은 방법은 완전 제거법인데, 결측 값을 감안하여 연구 대상인 표본 집단 영역을 보다 좁혀나가며 사용하기에 논리적으로도 큰 문제가 없는 방법이다.

 그러나 결측 값 대체를 해야한다면 일반적으로 회귀대체, EM알고리즘을 사용하며, 이 밖에도 k-nn 군집분석, 의사결정나무 등을 통해서도 결측값 대체를 할 수 있다.

 그러나 이러한 단일 대체 방법은 설정 방법이나 확률에 의존하는 경우 전혀 다른 대체 값이 출력될 위험이 존재하며, 범주형 데이터는 대체 값을 생성하였을 때, 정확도가 매우 낮다는 단점이 있다.

 다음 포스트에서는 이런 단일 대체 방법의 단점을 보완할 수 있는 방법인 다중 대체법(Multiple imputation)에 대해 알아보자.

728x90
반응형
728x90
반응형

지난 포스트에선 결측값이 미치는 영향에 대해 살펴보았다. 이번 포스트에서는 결측값의 종류를 알아보도록 하자.

 

 

결측값의 종류

결측값의 종류는 크게 결측값의 발생에 어떠한 인과 관계가 있느냐(결측값과 측정값이 서로 독립)의 정도에 따라 나눠진다. 후술 할 완전 무작위 결측(MCAR)은 이상적인 수준으로 결측값이 완전 무작위로 발생한 상태이고, 무작위 결측(MAR)은 이 상태일 것을 가정할 수 있는 상태이다. 마지막인 비 무작위 결측(NMAR)은 결측값 발생에 인과 구조가 있을 것임이 확실한 상태라고 보면 된다.

1. 완전 무작위 결측(MCAR: Missing completely at random)

  • 결측값이 변수의 성격과 전혀 무관하게 발생한 경우이다.
  • 자료의 관측된 값과 결측된 값 모두 결측의 발생과 독립적이다.
  • 결측 데이터를 가진 모든 변수가 완전 무작위 결측에 해당하는 경우, 단순 무작위 표본추출을 통해 완벽한 사례를 만들 수 있다.
  • 말 그대로 결측값이 발생한 것에 그 어떠한 의도도 없는 상태로 모든 정보가 데이터에 담겨 있어, 결측값의 존재가 전혀 문제 되지 않는다.
  • 결측값 발생에 대한 이상적인 경우지만, 현실에서 그럴 가능성이 높지 않다.

 

 

 

2. 무작위 결측(MAR: Missing at random)

  • 결측의 발생은 오로지 관측된 값에 의해서만 설명되며, 결측된 값과는 독립일 것이라 가정한 상태이다.
  • 관측된 값으로부터 결측치를 추정하는 것이 가능하게 되므로, 다양한 결측값 대체 방법을 적용할 수 있다.
  • 결측 조건이 다른 변수에 따라 조건부로 발생하는 경우, 결측값이 관측된 데이터가 아니라 관측되지 않은 데이터에 따라 결정된다.
  • 결측값을 추정할 수 있는 상태로, 일반적으로 설문지가 문제없이 작성되었거나, 설문에 대한 응답으로 어떠한 피해도 없을 것임이 설문 대상에게 이해가 된 상태로, 후술 할 결측값 추정 방법은 이 무작위 결측 상태임을 가정하고 시행한다.
  • 즉, 결측된 값의 발생이 어떤 인과 구조에 의해 발생한 것이 아닌 측정된 값들로 추정할 수 있는 상태라는 것을 의미한다.

 

 

 

3. 비 무작위 결측(NMAR: Not missing at random)

  • 결측값이 전혀 임의적으로 발생한 것이 아니며, 관측된 값과 결측된 값 모두에 영향을 받는 상태이므로, 결코 무시할 수 없는 상태이다.
  • 이 경우엔 결측값의 발생에 어떠한 이유가 있는 상태이므로, 결측값에 대해 세세하게 추가 조사를 해야 하는 상태이다.

 

 

 

쉬운 구분 방법

위 내용들만 읽어보면, 전혀 와닿지 않을 수 있는데, 아주 단순하게 이해하려면 다음과 같이 생각해보면 된다.

  • 3. 비 무작위 결측(NMAR): 우울증이 너무 심한 응답자들이 우울증 문항에 허위 기술을 하거나, 정치적, 종교적인 이유로 실제와 전혀 다른 응답을 하는 경우
  • 위 예시처럼 설문지 응답에서 중요한 특정 집단이 의도적으로 자신을 감춰버린 상태!
  • 3번 상태가 아니라면, 이 결측값 발생이 어느 정도 독립적으로 발생하지 않았을까?라고 생각할 수 있는 상태가 되고, 이건 완벽하게 우연히 발생한 결측값이야! 상태가 1. 완전 무작위 결측(MCAR)이 되는 것이다.
  • 즉, 결측값의 발생이 뚜렷하게 어떤 인과 관계가 숨어 있는 상태다 아니다 이것으로 구분 지으면 된다.

 

 

 

 지금까지 결측값의 종류에 대해 알아봤다. 설명을 하다보니 꽤 지저분하다는 느낌이 많이 드는데, 아주 단순하게 1. 이상적인 상태, 2. 정상, 3. 비정상 이렇게 생각해도 된다.

 다음 포스트에서는 이 결측값들을 어떻게 대체할 것인지에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

결측 값(Missing value)

: 결측 값은 R에서는 NA, Python에서는 None, NaN으로 출력되며, 값이 기록되지 않았거나 관측되지 않은 경우, 데이터에 저장되는 값을 말한다.

 

 

 

결측 값이 분석 결과에 미치는 영향

1. 표본의 규모가 감소되어 검정력이 감소된다. 

SPSS, SAS, STATA 등과 같은 통계 분석 도구들은 결측 값을 자동으로 제외하는 방식을 기본값(Default)으로 적용하고 있으며, 하나의 변수를 기준으로는 결측 값의 비중이 크지 않더라도, 전체 데이터에서 하나라도 결측 값을 갖는 케이스를 모두 제외하면, 소실되는 표본 비율은 커지게 된다.

  • 표본이 10,000명인 Data가 있다고 할 때, 변수 A의 결측 값이 200명, 변수 B의 결측 값이 400명, 변수 C의 결측 값이 1,000명, 변수 D의 결측 값이 600명이라고 가정할 때, 최대 결측 값은 2,200명 최소 결측 값은 1,000명이다. 
  • Graham. J.W.(2012). Missing Data: Analysis and Edsign. New York : Springer. 참조

 

 

 

2. 표본의 대표성이 낮아져 분석 결과에 편향(Bias)을 가져온다.

무응답의 원인이 설문 문항에 대한 적대적 태도, 무관심, 이해 부족 등에서 비롯된 경우, 표본의 대표성에 치명적인 문제를 일으킬 수 있다.

  • 만약, 고등학교 당시 수학 성적을 물어보는 설문 문항이 존재한다면, 설문조사 대상자가 자신에게 악영향이 있을 것이라 생각하여 자신의 성적을 적지 않거나, 잘못된 성적을 적을 수 있다.
  • 위 경우, 평균이 85점으로 나왔으나, 실제로는 수학 성적이 낮은 사람들이 자신의 성적을 적지 않아 85점으로 나온 것이라, 실제 수학 점수 평균은 70점일 수 있다.
  • Stuart, E. A., Azur, M, Frangakis, C. & Leadf, P.(2009). “Multiple Imputation with Large Data Sets: A Case Study of the Children’s Mental Health Initiative, ”American Journal of Epidemiology, 169(9): 1133-1139. 참조

 

 

 

3. 결측으로 인한 실제 문제의 발생을 식별하기 어렵다.

결측 값은 항상 결과에 영향을 주는 것이 아니라, 영향을 줄 수도 있지만, 때로는 영향을 주지 않을 수도 있다.

  • 결측률이 5% 이하인 경우: 특별한 결측치 보정 없이 분석이 가능하다고 한다.
    • Graham, J. W. (2009). “Missing Data Analysis: Making it Work in the Real World,” Annual Review of Psychology, 60: 549-576
  • 무응답이 1% 이하인 경우 무시할 수 있으며, 1~5%인 경우 보정 처리가 가능하나, 15% 이상인 경우 정교한 보정이 필요하다고 한다.
    • Acuna & Rodriguez, C. (2004). “The Treatment of Missing Values and Its Effect in the Classifier Accuracy,” Classification, Clustering and Data Mining Applications. 639-647.
  • 설문의 종류, 길이, 구조 등에 의해 결측 값이 발생할 수 있다.
    1. 긴 설문조사는 응답자의 중도포기로 인해 상대적으로 뒤에 있는 문항에서 결측이 발생할 수 있다.
    2. 설문지 하단의 문항을 발견하지 못해, 무응답 처리될 수 있다.
    3. 흡연자를 대상으로 하는 설문 문항에서 비흡연자는 결측 값 처리가 되므로, 실제 결측 값이 존재하지 않는 문항이나, 표본 설정을 잘못하여 결측 값이 다량 발생한 것으로 판단될 수 있다.
    4. 위와 같은 문제는 결과에 유의미한 영향을 주지 않을 가능성이 높다.
  • 결측 값의 존재로 인해 발생하는 문제는 수집된 데이터를 통한, 사후 분석에 의해서만 알 수 있으므로, 숨겨진 문제(Hidden problem)이라고도 한다.

 

 

이번 포스트에서는 결측 값이 분석 결과에 미치는 영향에 대해 알아보았다. 다음 포스트에서는 결측 값의 종류에 대해 알아보도록 하겠다.

728x90
반응형
728x90
반응형

 지금까지 문자열(string) type을 생성하고 이스케이프 문자, 포맷팅에 대해 학습해보았다. 지금까지 학습한 내용은 문자열을 생성하는 것에 관련 있는 것이지, 문자열을 다루는 내용과는 거리가 멀다고 할 수 있다.

 이번 포스트에서는 문자열을 직접 가지고 노는 문자열 전처리 코드에 대해 학습을 해보겠다.

 

 

문자열 전처리

1. 문자열 연산

 문자열 연산은 우리가 일반적으로 아는 더하기, 빼기, 곱하기와 같은 기능으로, 앞서 학습했던 List에서의 더하기, 곱하기처럼 문자A + 문자B는 문자A문자B가 되며, 문자 A * 3은 문자A문자A문자A가 된다.

# 1. 문자열을 더해보자
>>> A = "Python"
>>> B = "is"
>>> C = "easy"
>>> D = "and"
>>> E = "useful"
>>> F = A + " " + B + " " + C + " " + D + " " + E
>>> print(F)
Python is easy and useful


# 2. 문자열을 곱해보자
>>> A * 2
'PythonPython'


# 3. 문자열을 더하고 곱해보자
>>> G = A + " "
>>> print(G * 3)
Python Python Python 
  • 문자열 더하기는 꽤 유용하게 사용할 수 있는 기능으로, 포맷팅과 동일하게 사용할 수도 있다.
# 문자열 더하기를 포맷팅처럼 사용해보자
>>> AB = A + " " + B + " "
>>> print(AB + C)
>>> print(AB + E)
Python is easy
Python is useful

 

위와 같은 산술 연산 기호를 사용하는 것은 아니지만 문자열 빼기도 가능하다

# 생성된 문자열 F에서 and를 제거해보자
>>> print(F)
Python is easy and useful

>>> F.replace("and ", "")
'Python is easy useful'


# 문자열 F에서 and를 or로 바꿔보자
>>> F.replace("and", "or")
'Python is easy or useful'
  • 문자열 빼기가 문자열 F에서 문자열 D를 제거하는 것이라면, replace 함수를 응용해서 사용할 수 있다.
  • replace(): 문자열.replace(대상 문자, 바꿀 문자) 함수를 사용하면, 대상 문자를 내가 원하는 다른 문자로 바꿀 수 있다.

 

 

 

2. 문자열 인덱싱(슬라이싱)

 문자열 인덱싱은 문자에서 내가 원하는 구간의 문자만 선택하는 방법이다.

# 위에서 생성했던 문자열 F를 가지고 indexing을 해보자
>>> print(F)
'Python is easy and useful'


# 1. 먼저 문자 전체의 길이를 확인해보자
>>> len(F)
25


# 2. 문자열 F에서 첫 문자부터 5번째 문자까지 가지고 와보자
>>> F[0:5]
'Pytho'

# 2.1. 문자열 F를 뒤에서 6번째부터 뒤 모든 문자를 가지고 와보자
>>> F[-6:len(F)]
'useful'

>>> F[-6:]
'useful'
  • len(x): len 함수는 들어간 문자의 길이를 반환한다. x에 list나 DataFrame 등이 들어가는 경우, list는 원소의 수, DataFrame은 Row의 수를 반환한다.
  • 인덱싱에서 [n1:n2] n1에 아무것도 넣지 않는 경우 처음부터인 0을 의미한다. n2에 아무것도 넣지 않는 경우 마지막 위치를 의미한다.
  • 인덱싱한 값을 다른 변수에 담으면, 슬라이싱(부분만 가져오기)이 된다.
# 슬라이싱 기능을 활용하면 원하는 위치만 제거 혹은 다른 문자 끼워넣기가 가능하다.
>>> print(F)
'Python is easy and useful'

# 1. "easy and "를 제거해보자
>>> F[:10] + F[-6:]
'Python is useful'

# 2. useful을 useless로 바꿔보자
>>> F[:10] + F[-6:-3] + "less"
'Python is useless'

 

 위 인덱싱에선 내가 찾고자 하는 문자의 위치를 직접 확인해야 했으나, 문자 위치를 반환하는 함수인 find(), index() 함수를 사용하면, 위치를 수월하게 찾을 수 있다.

# 문자열 F에서 and의 위치를 찾아보자
>>> print(F)
'Python is easy and useful'

>>> F.find("and")   
15
>>> F[15:]
'and useful'    # 단어의 시작 위치가 출력된다.

# index() 함수는 find() 함수와 동일한 기능을 한다.
>>> target_start = F.index("and")
>>> print(target_start)
15

# len() 함수를 섞어서 사용하면 이 문제를 해결할 수 있다.
>>> F[target_start:target_start+len("and")]     # 문자 끝 위치 = 문자 시작 위치 + 문자 길이
'and'
  • 문자열.find(탐색 문자), 문자열.index(탐색 문자) 함수를 사용하면, 해당 문자가 처음 등장한 문자의 위치를 알 수 있다.
  • len() 함수와 섞어서 사용한다면 문자의 끝 위치도 알 수 있다.
  • 그러나 만약, 해당하는 문자가 여러개라면 가장 왼쪽에서 등장한 문자의 위치만 반환한다는 단점이 있다(이는 추후 학습할 정규표현식 함수를 사용하면 수월하게 해결할 수 있다).
  • index() 함수와 find() 함수의 차이는 대상 단어가 없을 때로, find() 함수 사용 시 대상 단어가 없으면 -1이 반환되고, index() 함수는 에러가 발생한다.

 

 

 

3. list와 str

 str type과 list type을 오가게 하는 함수가 있으며, 이를 단순히 말하면 아래와 같다.

  • "구분자".join(대상 문자열): 합치기
  • "대상 문자열".split("구분자"): 쪼개기
>>> target = "Python is fun and useful"

# 1. 문자열을 list로 쪼개보자
>>> target_list = target.split(" ")
>>> print(target_list)
['Python', 'is', 'fun', 'and', 'useful']

# 2. list를 문자열로 합쳐보자
# _(Under bar)로 합쳐보자
>>> target_str = "_".join(target_list)
>>> print(target_str)
Python_is_fun_and_useful

# 3. 생성된 단어를 한 글자 단위로 잘라보자
# join() 함수를 문자열을 대상으로 사용하면, 문자열에서 단어 하나하나 사이에 특정 단어가 들어간다.
>>> put_separator = "|".join(target_str)
>>> print(put_separator.split("|"))
['P', 'y', 't', 'h', 'o', 'n', '_', 'i', 's', '_', 'f', 'u', 'n', '_', 'a', 'n', 'd', '_', 'u', 's', 'e', 'f', 'u', 'l']
  • join() 함수를 문자열을 대상으로 사용 시, 문자열의 단어 하나하나 사이에 join 단어가 들어간다.
  • |는 거의 사용하지 않는 특수 문자 중 하나로 일반적으로 Rawdata에 존재하지 않는다.
  • join() 함수와 split() 함수를 섞어서 사용하면 한 단어 단위로 자를 수 있다.

 

 

 

4. 공백 제거하기

# Target
>>> target = " Python is easy and useful "

# 1. 앞의 공백을 제거하자
>>> target.lstrip()
'Python is easy and useful '

# 2. 뒤의 공백을 제거하자
>>> target.rstrip()
' Python is easy and useful'

# 3. 양쪽 공백을 제거하자
>>> target.strip()
'Python is easy and useful'

# 4. 모든 공백을 제거하자
>>> target.replace(" ", "")
'Pythoniseasyanduseful'
  • 문자열.lstrip(): 왼쪽 공백 제거
  • 문자열.rstrip(): 오른쪽 공백 제거
  • 문자열.strip(): 양쪽 공백 제거
  • 문자열.replace(" ", ""): 모든 공백 제거

 

 

 

5. 대소문자 바꾸기

# Target
>>> target = "Python is easy and useful"

# 1. 소문자를 대문자로 바꿔보자
>>> upper_T = target.upper()
>>> print(upper_T)
PYTHON IS EASY AND USEFUL

# 2. 대문자를 소문자로 바꿔보자
>>> upper_T.lower()
'Python is easy and useful'
  • 문자열.upper(): 소문자를 대문자로 만든다
  • 문자열.lower(): 대문자를 소문자로 만든다

 

 

 

6. 문자열 개수 세기

# target
>>> target = "Python is fun and useful"

# 1. 문자열 전체 길이를 확인해보자
>>> len(target)
25

# 2. 문자열 안에 특정 문자가 몇 개 있는지 확인해보자
>>> target.count(" ")
4

>>> target.count("Python")
1

 

 

 

 지금까지 문자열을 다루는 기본적인 함수에 대해 알아보았다. 위 함수들을 보면, 내가 원하는 단어를 탐색하고 조작하는 부분이 실전에서 필요한 것에 비해 부족하다는 것이 느껴지는데, 이를 보다 체계적으로 할 수 있는 방법이 바로 정규표현식이다.

 다음 포스트에서는 파이썬 정규표현식에 대해 학습해보도록 하겠다.

728x90
반응형

+ Recent posts