1. @staticmethod

함수 객체를 선언하지 않고, 바로 method에 접근해서 사용할 수 있습니다.

class Test:
    def __init__(self):
        pass

    @staticmethod
    def print_x(x):
    	return print(x)
        
Test.print_x(10) # 객체 선언하지않고 바로 사용        

 

2. list의 extend

주로 list 뒤에 값을 붙인다고 하면, append 함수만 생각나는 경우가 많습니다.
extend 함수는 익숙하지 않으면 잘 생각이...

[1, 2, 3]과 [10, 20]에 각각 함수를 사용했을 때 반환 결과는 다음과 같습니다.

  • extend 함수: [1, 2, 3, 10, 20]
  • append 함수: [1, 2, 3, [10, 20]]
a = [1, 2, 3]
b = [10, 20]

a.append(b)
print(a) # [1, 2, 3, [10, 20]]

a.extend(b)
print(a) # [1, 2, 3, 10, 20]

 

3. Pandas -> Numpy

Pandas 라이브러리는 데이터 분석과 관련한 다양한 함수를 제공해줌과 동시에 NumPy 라이브러리와 같이 빠른 성능을 보여줍니다.

하지만 만약 하고 있는 작업을 NumPy Array를 활용한 연산으로 수행할 수 있다면, 되도록이면 NumPy 연산으로 바꿔보는 것을 추천합니다.

생각치 못한 곳에서 속도 향상이 일어날 수 있고, 이후 처리 작업으로 전환하기에도 매우 편리합니다.

 

4. 속도가 정말 느린 것 같다면 배치 연산을 활용

전체 데이터에 전처리(preprocessing) 작업을 수행한 후, 처리된 데이터를 stack, concat과 같은 함수를 사용하여
새로운 저장 공간(ex: list or DataFrame etc.)에 쌓는 경우 속도 저하 현상이 발생할 수 있습니다.

이는 전체 데이터를 한번에 쌓고자하는 작업 때문에 병목 현상이 발생한 경우인데
특히, 우리가 자주쓰는 loc, iloc 함수와 같이 인덱싱 기능이 포함된 함수를 사용할 경우 자주 만나볼 수 있는 문제입니다.

속도 향상을 위해 전체를 한번에 쌓는 방법보다 분할하여 쌓은 뒤, 합쳐(merge)주는 순서로 변경해보세요.
신경망 모델 학습 시에 배치 연산을 하는 것처럼 바꿔보면 큰 속도 향상을 기대해볼 수 있습니다.
(ex: 1,000개 데이터를 100개씩 나누어 10번 처리한 뒤, 한번에 합쳐주는 방법을 의미)

매우 쉬운 방법이지만 한번에 떠오르기 쉽지 않습니다.

 

5. Class Config 설정

클래스를 정의할 때 여러 가지 하이퍼파라미터 정의가 필요할 수 있습니다.

__init__함수내 변수로 선언하여 관리할 수 있지만, 다루는 함수가 많아지거나 복잡해질수록 무언가 실수로 하나씩 변경하지 못하는 실수가 발생하기 마련입니다.

이를 방지하고자 관리하는 스타일에 따라 (1) Config 클래스 (2) 외부 파일 저장 (3) Config dict를 인자로 넘기기 등 방법을 사용할 수 있는데 여기서 볼 것은 (3)번 방법입니다.

**kwargs(dict type)를 사용해서 정의된 하이퍼파라미터를 넘겨준 후, 클래스 변수로 등록하는 과정입니다.
(+ *args는 tuple type)

class Test:
    def __init__(self):
        pass
    
    def set_config(self, **kwargs):
        self.add_entries(**kwargs)
    
    def add_entries(self, **kwargs):
        for key, value in kwargs.items():
            self.__dict__[key] = value
            
config = {
    'num_epochs': 10,
    'batch_size': 1024,
    'hidden_nums': 16,
}

t = Test()
t.set_config(**config)

# 결과는 __dict__로 확인할 수 있다
# {'num_epochs': 10, 'batch_size': 1024, 'hidden_nums': 16}
print(t.__dict__)

 

6. Jupyter Notebook으로 수식 쓰기

수학 공식을 표현하고 싶을 때, LaTeX 방법으로 수식을 만들 수 있지만 복잡하고 어렵습니다.

# RMSE 예시
# $$RMSE=\sqrt {\sum_{i} (Y_{i} - \hat Y_{i})^2}$$

handcalcs 패키지를 활용하면 매우 쉽게 수식을 렌더링할 수 있습니다.

pip install handcalcs로 쉽게 설치할 수 있습니다. 설치 후, 모듈을 임포트합니다.

import handcalcs.render

%%render magic 명령어로 아래와 같이 쉽게 사용할 수 있습니다.

%%render

r = sqrt(a**2 +b**2)
x = (-b + sqrt(b**2 -4*a*c))/(2*a)

실제 Jupyter Notebook Output

언더바(_)를 통해 밑 이름도 지정할 수 있습니다.

%%render

# under -> '_'
a_x = 1
b_under_underunder = 2

7. 파이썬 크롤링하기

파이썬으로 크롤링을 해야할 때, 스크래피(Scrapy) 등 다양한 방법이 있지만 기본적으로 우리가 사용하는 방법은 BeautifulSoupSelenium 라이브러리입니다.

특히, Selenium은 ChromeDriver을 활용해서 뷰(View)를 확인할 수 있는 장점이 있어 명확하고, 몇몇 함수를 사용해 더욱 편리하게 크롤링을 진행할 수 있습니다. 뿐만 아니라 버튼 클릭부터 페이지 변경까지 매우 편리합니다.

그런데 만약 버튼 클릭, 페이지네이션(Pagination), 링크 들어가서 또 링크를 들어가야하는 등 여러 가지 복잡한 액션이 포함된 크롤링을 진행해야할 때는 어떨까요? 뿐만 아니라 파싱해야할 정보량도 많다면?
이때, Selenium만 활용하면 속도가 매우 느리다는 단점을 느낄 수 있습니다. 음... 굳이 별다른 이유를 설명하지 않아도 일단 뷰를 우리에게 제공한다는 점을 생각해보면 일반적으로 납득이 가는 상황입니다.

이에 대한 해결방법으로 BeautifulSoup만 활용해보는 것입니다. BeautifulSoup은 HTTP 통신으로 시각적인 정보를 제공하지 않고 바로 파싱을 진행할 수 있기 때문에 크롤링 속도가 매우 빠르다는 장점이 있습니다.

만약, 여기서 페이지네이션이나 링크를 타고 들어가는 크롤링 과정을 뷰로 확인하고 싶다면, Selenium을 활용하면 되겠죠. 그런데 단점은 속도잖아요. 속도가 걱정된다면?

두 방법을 섞어서 활용해보세요. 여러 가지 액션은 Selenium, 파싱은 BeautifulSoup를 활용하면 빠른 속도로 크롤링을 진행할 수 있습니다.