반응형

안녕하세요. 

인프런에서 파이썬 머신러닝 완벽 가이드 를 들으면서 알게된 내용들에 대해 간단히 글을 쓰려고 합니다.

데이터 셋 파일(보통 .csv) 읽어서 Train, Test 용으로 데이터를 나누기 위해 train_test_split() 메소드를 사용하는데요,
이 때 메소드의 파라미터 중 stratify 가 보이는데 의미와 역할에 대해 정리합니다.

Scikit-Learn의 공식 홈페이지에 가보면 train_test_split() 에 대해 자세히 알 수 있는데요,

train_test_split 설명 (scikit learn 버전 : 1.1.1)

stratify 파라미터에 대해서는 User Guide 링크로 대체해서 아래와 같이 설명하고 있습니다.

 

ensure that relative class frequencies is appoximately preserved in each train and validation fold.

간단히 예를 들어서 설명해보겠습니다.

아래 그림은 하나의 통에 파란색공 4개, 빨간색공 4개가 담겨 있는 모습입니다. 

[그림1] 파란색 공 4개, 빨간색 공 4개가 담긴 통

여기서 공을 4개씩 꺼내서 2개의 통으로 나눈다고 가정해보겠습니다. 그러면 같은 색의 공들이 같은 통에 담길 수도 있고, 

[그림 2] 같은 색의 공으로만 나뉜 2개의 통

 

아래 그림처럼 각 통에 빨간색 공 2개, 파란색 공 2개가 담길 수도 있습니다. 

[그림 3] 하나의 통에 빨간색 공 2개, 파란색 공 2개가 담긴 모습

이 경우에는 처음 하나의 통에 담겨있을 때와 마찬가지로 파란색공과 빨간색 공 개수의 비율이 1:1입니다.
이러한 방법을 통계학에서는 Stratified sampling 이라고 합니다. 

다시 돌아가서 train_test_split() 함수에서 stratify 파라미터는 데이터를 나누기 전 데이터들의 비율을 나눈 후에도 유지할 것인지를 정하는 파라미터입니다. 

극단적으로 [그림 2]와 같이 하나의 그룹(통)에 한 종류의 데이터(공)만 담긴 상태에서 그 데이터만을 이용해 Machine Learning 모델을 학습시키게 되면, 처음보는 종류의 데이터에 대해서는 예측할 수 없는 문제가 발생하게 됩니다.

코드

위의 내용을 강의에서 사용한 피마 인디언 당뇨병 예측 데이터를 이용해 코드로 설명해보겠습니다.
(데이터에서 'Outcome' 이름의 컬럼이 label 컬럼입니다.)

dataset_path = 'datasets/diabetes.csv'
df = pd.read_csv(dataset_path)

X = df.drop('Outcome', axis=1)
y = df['Outcome']
print(f"Outcome 컬럼의 value_counts()결과: \n {y.value_counts()}")
# Outcome 컬럼의 value_counts()결과: 
# 0    500
# 1    268
# Name: Outcome, dtype: int64

Outcome 컬럼은 0은 500개, 1은 268개로 이루어져있습니다.
(비율: 500/268 =  1.86)
train_test_split 함수를 사용하면 Outcome 컬럼은 아래 코드에서 y_train, y_test 2개의 집단으로 나뉘게 됩니다. 

1) stratify 파라미터가 None인 경우 (즉, 위의 500:268 비율을 유지하지 않고 데이터를 나누는 경우)

from sklearn.model_selection import train_test_split

# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

print(f"y_train 의 value_counts()결과: \n {y_train.value_counts()}")
# y_train 의 value_counts()결과:
# 0    393
# 1    221

print(f"y_test 의 value_counts()결과: \n {y_test.value_counts()}")
# y_test 의 value_counts()결과:
# 0    107
# 1     47

위의 결과를 보시면 y_train과 y_test의 0과 1의 비율은 각각

393/221 = 1.77, 107/47 = 2.27

로 Outcome 컬럼의 0과 1이 비율인 1.86과 같지 않습니다.

2) stratify 파라미터에 Outcome 컬럼을 넣어주는 경우 (즉, 500:268 비율을 유지하고 데이터를 나누는 경우)

from sklearn.model_selection import train_test_split

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0, stratify=y)
# X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=0)

print(f"y_train 의 value_counts()결과: \n {y_train.value_counts()}")
# y_train 의 value_counts()결과:
# 0    400
# 1    214

print(f"y_test 의 value_counts()결과: \n {y_test.value_counts()}")
# y_test 의 value_counts()결과:
# 0    100
# 1     54

위의 결과를 보시면 y_train과 y_test의 0과 1의 비율은 각각

400/214 = 1.86, 100/54 = 약 1.86

로 Outcome 컬럼의 0과 1이 비율인 1.86을 유지하고 있습니다. 

감사합니다.

 
 
 
 
 
반응형
반응형

2022.05.25 - [자료구조] - [자료구조] Linked List - (1) 개념

2022.06.05 - [자료구조] - [자료구조] Linked List - (2) 간단 개념 구현

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end)

2022.06.08 - [자료구조] - [자료구조] Linked List - (4) 노드 삽입 (at the front)

2022.06.12 - [자료구조] - [자료구조] Linked List - (5) 데이터 조회

본 글에서는 Linked List에서 노드 삭제에 대해 다룸. cur이라는 변수를 이용하여 head가 가리키는 노드부터 삭제할 노드를 찾음.

노드 삭제에 대해서 크게 3가지 경우로 나눠서 생각

1) Linked list가 비어있는 경우 : 메소드 바로 종료

cur = self.head

if cur is None:
	return

Linked List가 비어있는 상태

2) head가 가리키는 노드의 값이 삭제하고자하는 값인 경우 (아래 그림에서 data 변수가 3인 경우)

cur = self.head

 

head가 가리키는 노드를 삭제하는 경우

head는 현재 가리키는 노드의 next가 가리키는 노드를 참조해야 함. 그리고 cur에는 None 할당

if cur.data == data: # 첫 노드 삭제
    self.head = cur.next
    cur = None
    return

head가 가리키는 노드 업데이트

3) head가 가리키는 노드가 아닌 다른 노드를 삭제하는 경우 (data에 2가 담긴 노드를 삭제한다고 가정)

cur = self.head

prev 변수를 도입하여 cur이 가리키는 노드를 참조하고, cur 변수는 현재 가리키는 노드의 next가 가리키는 노드 참조.

prev = cur
cur = cur.next

그리고 cur 변수가 가리키는 노드의 data가 삭제할 데이터인지 확인하는 과정을 반복하고 맞으면 반복과정을 종료

while True:
    prev = cur
    cur = cur.next
        
    if cur.data == data:
        break

 

Linked List가 끊기지 않기 위해서는 prev가 가리키는 노드의 next가 cur노드가 가리키는 노드의 next가 가리키는 노드를 참조해야 함 (아래 그림에서 빨간선)

prev.next = cur.next

data에 3이 담긴 노드의 next가 data에 1이 담긴 노드를 가리킴

 

위 3가지 경우가 포함된 delete 메소드는 아래와 같음

    def delete(self, data): # data가 포함된 노드를 linked list에서 삭제
        cur = self.head
        
        if cur == None: # Linked List가 비어있는 경우
            return 

        if cur.data == data: # 첫 노드 삭제
            self.head = cur.next
            cur = None
            return

        while True:
            prev = cur
            cur = cur.next
        
            if cur.data == data: # 중간 노드 삭제 
                break
        
        prev.next = cur.next
        cur = None

전체 코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

    # linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

    def delete_all(self):
        while self.head is not None:
            cur = self.head
            self.head = self.head.next
            cur = None

    # data가 포함된 노드를 linked list에서 삭제
    def delete(self, data):
        cur = self.head
        
        if cur == None: # Linked List가 비어있는 경우
            return 

        if cur.data == data: # 첫 노드 삭제
            self.head = cur.next
            cur = None
            return

        while True:
            prev = cur
            cur = cur.next
        
            if cur.data == data: # 중간 노드 삭제
                break
        
        prev.next = cur.next
        cur = None

    def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

if __name__ == "__main__":
    linked_list = LinkedList()
    linked_list.print() # 출력 없음
    linked_list.push(1)
    linked_list.print() # 1
    linked_list.push(2)
    linked_list.print() # 2 1
    linked_list.push(3)
    linked_list.print() # 3 2 1

    linked_list.delete(2)
    linked_list.print() # 3 1

 

 

 
 
 
반응형
반응형

2022.06.08 - [자료구조] - [자료구조] Linked List - (4) 노드 삽입 (at the front)

 

[자료구조] Linked List - (4) 노드 삽입 (at the front)

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end) 본 글에서는 위의 글에 이어서 새로운 노드를 Linked List 맨 앞에 추가하는 방법을 다룸. (1) 새로운 노드를 Linked List 맨 앞..

wschoi.tistory.com

본 글에서는 Linked List에 삽입된 노드들의 데이터를 조회하는 메소드를 다룸.

Linked list에 연결된 노드들의 data를 조회 

1) head가 가리키는 노드부터 순차적으로 출력하기 위해 cur 변수를 사용해 head가 참조하는 노드를 같이 참조

cur = self.head

2) cur 변수가 가리키는 노드의 data 출력

print(cur.data, end=" ") # 3

3) cur이 가리키는 노드를 연결된 노드로 이동

cur = cur->next

4) cur이 가리키는 노드의 data 출력 

print(cur.data, end=" ") # 2

위 과정을 print 메소드로 묶어서 표현하면 아래와 같음

def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

최종 코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

	# linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

	# linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

    def print(self):
        cur = self.head

        while cur:
            print(cur.data, end=" ")
            cur = cur.next
        print()

if __name__ == "__main__":
    linked_list = LinkedList()
    linked_list.print() # 출력 없음
    linked_list.push(1)
    linked_list.print() # 1
    linked_list.push(2)
    linked_list.print() # 2 1
    linked_list.push(3)
    linked_list.print() # 3 2 1
반응형
반응형

2022.06.07 - [자료구조] - [자료구조] Linked List - (3) 노드 삽입 (at the end)

본 글에서는 위의 글에 이어서 새로운 노드를 Linked List 맨 앞에 추가하는 방법을 다룸. 

(1) 새로운 노드를 Linked List 맨 앞에 추가
(2) 새로운 노드를 Linked List 맨 뒤에 추가 

1) LinkedList 클래스를 정의

  • 클래스의 instance 변수로 head와 tail이 있고 모두 None값으로 초기화
class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

LinkedList의 초기 상태

 

head가 None을 가리키면, LinkedList에 아직 Node가 하나도 없는 상태.

2) 빈 Linked List에 새로운 node 추가

node가 새롭게 추가되면 head가 새로 추가된 node를 가리킴.

총 1개의 node만 있으므로 tail로 새로 추가된 node를 가리킴.

new_node = Node(1) # 새로운 노드 추가

if self.head is None: # head가 None을 가리키면,
    self.head = new_node
    self.tail = new_node

Linked List에 노드가 하나도 없는 상황에서 노드가 추가되는 과정

3) Node가 있는 Linked List에 새로운 node 추가

여기에 다시 새로운 node를 추가.

new_node = Node(2)

새로운 노드 추가

 

Linked List로 연결되기 위해서는 새로운 노드의 next가 head가 가리키는 노드를 가리켜야 함

new_node.next = self.head

새로운 노드의 next가 기존 노드를 가리킴

그리고 head는 새로 추가된 노드를 가리켜야함. 

self.head = new_node

head가 참조하는 노드 업데이트

 

마지막으로 새로운 노드 하나 더 추가.

new_node = Node(3)

세번째 노드 추가

새로운 노드의 next가 head가 가리키는 노드를 가리켜야 함.

new_node.next = self.head

 

마지막으로 head가 새로 추가된 노드를 가리키도록 업데이트

self.head = new_node

 

위의 과정을 'push' 라는 이름의 method로 변환하면 아래와 같음.

# linked list 처음에 노드 추가
def push(self, new_data):
    new_node = Node(new_data)

    if self.head is None:
        self.tail = new_node
    else:
        new_node.next = self.head
        
    self.head = new_node
  • push 메소드는 항상 처음에 노드를 추가하기 때문에 Time complexity가 항상 O(1)이다.

최종코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

	# linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.head = new_node
        else:
            self.tail.next = new_node
            
        self.tail = new_node

	# linked list 처음에 노드 추가
    def push(self, new_data):
        new_node = Node(new_data)

        if self.head is None:
            self.tail = new_node
        else:
            new_node.next = self.head
        
        self.head = new_node

 

 
 
 
 
 
 
반응형
반응형

2022.06.05 - [자료구조] - [자료구조] Linked List - (2) 간단 개념 구현

 

[자료구조] Linked List - (2) 간단 개념 구현

본 글에서는 이전 Linked List 글에서 언급한 Node를 구현. 최대한 간단히 Node 클래스만을 이용해서 어떻게 node들이 연결될 수 있는지에 대해 설명. Node 클래스 정의 class Node: def __init__(self, data=None..

wschoi.tistory.com

 

본 글에서는 위의 글에 이어서 Linked List에 노드를 추가하는 방법을 다룸. 노드를 추가하는 방법이 크게 2가지가 있음.

(1) 새로운 노드를 Linked List 맨 앞에 추가
(2) 새로운 노드를 Linked List 맨 뒤에 추가 (본 글에서 다룸)

1) LinkedList 클래스를 정의

  • 클래스의 instance 변수로 head와 tail이 있고 모두 None값으로 초기화
class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

LinkedList의 초기 상태

 

head가 None을 가리키면, LinkedList에 아직 Node가 하나도 없는 상태.

2) 빈 Linked List에 새로운 node 추가

node가 새롭게 추가되면 head가 새로 추가된 node를 가리킴.

총 1개의 node만 있으므로 tail로 새로 추가된 node를 가리킴.

new_node = Node(1) # 새로운 노드 추가

if self.head is None: # head가 None을 가리키면,
    self.head = new_node
    self.tail = new_node

Linked List에 노드가 하나도 없는 상황에서 노드가 추가되는 과정

3) Node가 있는 Linked List에 새로운 node 추가

여기에 다시 새로운 node를 추가.

new_node = Node(2)

새로운 노드 추가

Linked List로 연결되기 위해서는 head와 tail이 가리키는 노드의 next가 새로운 노드를 가리켜야함. 

self.tail.next = new_node

기존의 노드의 next가 새로운 노드를 가리킴

그리고 tail은 새로 추가된 노드를 가리켜야함. 

self.tail = new_node

tail이 참조하는 노드 업데이트

마지막으로 새로운 노드 하나 더 추가.

new_node = Node(3)

세번째 노드 추가

 

tail이 가리키는 노드의 next가 새로운 노드를 가리켜야함.

self.tail.next = new_node

두번재 node의 next가 새로운 노드를 가리킴

 

마지막으로 tail이 새로 추가된 노드를 가리키도록 업데이트

self.tail = new_node

 

위의 과정을 'append' 라는 이름의 method로 변환하면 아래와 같음.

# linked list 끝에 노드 추가
def append(self, new_data):

    new_node = Node(new_data)

    if self.head is None:# 기존에 node가 없는 경우
        self.head = new_node
    else: # 기존에 node가 있는 경우
        self.tail.next = new_node
    
    self.tail = new_node

 

  • self.tail = new_node는 if-else 문에서 공통이므로 바깥으로 빼줌

최종코드

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

class LinkedList:
    def __init__(self):
        self.head = None
        self.tail = None

    # linked list 끝에 노드 추가
    def append(self, new_data):
        new_node = Node(new_data)

        if self.head is None:# 기존에 node가 없는 경우
            self.head = new_node
        else: # 기존에 node가 있는 경우
            self.tail.next = new_node
    
        self.tail = new_node

 

참고)

1. Linked List 클래스에서 tail 이라는 인스턴스 변수를 사용함으로써, append 메소드의 time complexity가 O(1)을 유지한다. 변수를 사용하지 않는 경우는 head 노드부터 끝 노드까지 loop를 이용해 찾은 후 node를 추가해야하기 때문에 time complexity가 O(n)이 된다. (n은 노드 수)

 
 
반응형

+ Recent posts