Naver Ai Boostcamp

[DAY 3] 파이썬 기초 문법 II

잡담연구소 2021. 1. 20. 19:34
파이썬 기본 데이터 구조 

- 스택과 큐 , 튜플과 집합 , 사전 , collection모듈 

c++로는 많이 써봤어도, 파이썬으로는 한 번도 써본 적이 없어 낯익은 녀석덜,,,

 

📌 스택 

  • 리스트를 이용해 구현 append() , pop()

📌 큐

  • 리스트를 이용해 구현 : append() , pop(0) 
    pop(0) : 맨 앞에 있는 게 출력이 되면서 리스트에서 빠져나온다.

📌 튜플 

  • 값의 변경 불가 
  • t(1) : 정수 1로 인식! 값이 하나인 tuple은 (1,) 무조건 뒤에 콤마를 하나 넣어주자
  • 프로그램을 작동하는 동안 변경되지 않은 데이터의 저장 ex) 학번 이름 등 
  • 함수의 반환 값 등 사용자의 실수에 의한 에러 방지

📌 집합 

  • update : 여러개 추가 가능 
  • union ( | ), intersection( & ), difference( - )

📌 사전 

  • items : (key, value)가 tuple 형태로 unpacking이 일어난다

📌 Lab dict

import csv #csv불러오는 모듈

# w정렬을 위한 함수 
def getkey(item):
    return item[1]

# 파일을 읽어오기 
command_data = []
with open("command_data.csv", "r",encoding = 'utf-8') as csvfile :
    spamreader = csv.reader(csvfile , delimiter=',' , quotechar = '"')
    for row in spamreader: 
        command_data.append(row)
## ['command', 'id'], ['_raw', 'source'], ['vi gowithflow.py ', 'tiana'], 와 같이 하나의 리스트 안에 담겨져 나온다. 

command_counter = {}
for data in command_data : 
    if data[1] in command_counter.keys() : 
        command_counter[data[1]]+=1
    else : 
        command_counter[data[1]]=1

## source': 1, 'tiana': 167, 'white_rabbit': 237, 와 같이 이름 : 횟수 가 출력됨 
## sorting 하기 위해 손봐주자 

dict_list = []
for k, v in command_counter.items():
    temp = [k,v]
    dict_list.append(temp)  # 원래 dic -> list 

sorted_dict = sorted(dict_list , key = getkey , reverse = True) # 정렬
# ["이름" , "횟수"] 인데 횟수 기준으로 정렬하고싶음 -> getkey는 [1] 즉, 횟수를 반환 
# key = getkey라고 쓰게 되면 횟수를 기준으로 정렬됨. 
print(sorted_dict[:10])

 

📌 deque

  • stack, queue 지원 , list에 비해 효율적이고 빠른 저장방식
  • from collections import deque 로 불러주고 deque()로 선언해주면 됨
  • rotate, reverse 등이 가능한 linked_list
  • 효율적 메모리 구조로 처리 속도 향상 
    ❓ import time 이후 %time it : 여러번 반복해서 시간의 평균, 표준편차를 구해줌 
        % : 매직명령어라고 함 (신기하다)

📌 orderdict

    -> python 3.6 부터 순서가 보장되어 의미❌

 

📌 defaultdict 

  • 기본값을 지정, 신규값 생성 시 사용하는 방법 
  • default (lambda : 0) 
from collections import defaultdict
d = defaultdict(lambda :0)
print(d['first'])
  • c++에서 사용했던 map과 비슷해 아주 흡족하다,,, 파이썬의 dic은 원소가 있는지 없는 지 확인해줘야해서 불편했는데 아주 흡족하다..

📌 counter 

  • sequence type (리스트나 문자열)의 data element 들의 갯수를 dict 형태로 반환 

✨👀 리스트들을 정렬할 때, list[1] 값으로 정렬하고 싶다면 ? 

  👉 key = lambda t : t[1]

 

📌 namedtuple

  • 튜플 형태로 데이터의 구조체를 저장 (c에서의 구조체와 유사)

 

Pythonic code 

📌 split & join 

📌 list comprehension 👀✨

  • 기존 list를 사용하여 간단히 다른 list를 만드는 기법
  • for + append보다 빠름
  • 꿀팁 ] pprint 사용하면 보기좋게 예쁘게 출력 됨  
# 조건문 
result = [i * 10 for i in range(10) if i % 2 == 0]
print(result)

# nested loop for문 
result = [i * j for i in range(10) for j in range(10)]
print(result)

# 필터 
result = [i * j for i in range(10) for j in range(10) if not (i == j)]
print(result)

# 필터 심화   참일 때 실행문 , 참일 조건 , 거짓일 때 실행문 for ~~
result = [i * j if i == j else i+j for i in range(10) for j in range(10) ]
print(result)

#이차원 리스트 
words = "I ATE BREAKEFAST BUT I AM HUNGRY".split()
result = [[w.upper() , w.lower() , len(w)] for w in words]
print(result)

📌 enumerate & zip

  • enumerate : index , value가 나옴 
    주로 dict에서 사용 {i : v for i,v in enumerate(list)}
  • zip : 두 개의 list 값을 병렬적으로 추출해서 튜플로 출력해줌 
    평균 점수를 구할 때도 사용가능 
  • enumerate + zip 콤보사용 가능 : 두 리스트에서 같은 위치에 있는 값 찾기 
a = ['a', 'b', 'c']
result = {i : v for i,v in enumerate(a)}
print(result)

b = ['d','e','f']
result = [value for value in zip(a ,b)]
print(result)

for i,v in enumerate(zip(a,b)) : 
    print(i, v)
    
# {0: 'a', 1: 'b', 2: 'c'}
# [('a', 'd'), ('b', 'e'), ('c', 'f')]
# 0 ('a', 'd')
# 1 ('b', 'e')
# 2 ('c', 'f')

📌 lamda + map + reduce 

  • lambda : 일회용 함수 이름 필요 없음

    와 이거 짱 신기함 그냥 lamda 뒤에 튜플로 값 줘도 계산됨
  • 문법,코드 해석이 어려움 ,,, 테스트의 어려움,,, but 그래도 많이 쓴다 나도 쓸거임 ^_^
lambda(x,y : x+y)(10,50)
# 60
  • map : list 내 elment들을 하나하나 mapping 해주는 역할 
ex = [1,2,3,4,5]
list(map(lambda x : x **2 if x%2 == 0 else x, ex))
  • reduce : list에 똑같은 함수를 적용해서 통합 
    from functools에서 가져와야함 

reduce(lambda x, y : x+y , [1,2,3,4,5])

👀✨ lambda + map 혹은 reduce + lambda 콤보를 쓰면 간단히 해결할 수 있는 문제들이 많다. 

 

📌 iter + next 

  • sequence 자료형에서 데이터를 순서대로 출력하는 자료형 
  • iter들은 다음의 위치를 저장하고있다. 그래서 next를 사용하면 그 다음의 위치를 반환해줌 
cities = ['seoul' , 'busan' , 'jeju']
memory_adress = iter(cities)
# cities가 저장되어있는 주소가 찍힘 
print(next(memory_adress))
print(next(memory_adress))
print(next(memory_adress))

📌 generator

  • iterable object를 특수한 형태로 사용해주는 함수 
  • element가 사용되는 시점에 값을 메모리에 반환
  • yeild를 사용하여 하나만 반환 
  • 메모리가 절약됨 효율적!
  • 큰 데이터 , 파일 데이터를 처리할 때 generator를 사용하자.
def generator_list(value):
    for i in range(value):
        yield i

print(generator_list(50))
## 결과 : <generator object generator_list at 0x000002243AFBDBC8>

result = [i for i in generator_list(50)]
print(result)
# 결과 : [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 
# 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 
# 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49] 

👉 아래 코드에서 실제 사용되는 시점 list comprehension으로 표현되는 그 시점! 에서 값을 반환한다. 
평소에는 값을 메모리에 안올려놓고, 메모리 주소만 가지고 있다가 print와 같이 필요한 시점에 yield가 메모리에 맞는 값을 던져준다! 

 

❓ iterator 과 generator의 차이 

iterator는 next()함수를 통해서 순차적으로 호출이 가능한 object

generator는 yield를 통해 iterator를 생성함 

 

📌 generator comprehension

  • list comprehension과 유사하게 generator 형태의 list 생성 
  • [ ] 대신 ( ) 를 사용하여 표현 
  • 값을 보고싶으면 ? list() 안에 넣어주면 볼 수 있다.

📌 function agruments 

  • keyword arguments : parameter의 변수명을 사용, 함수명을 넘김 
    - layer를 쌓을 때, 함수를 사용할 때 보면 함수(kernel_size = 3 ) 요런식으로 키워드 알규먼트를 사용하는 걸 볼 수 있다. 
  • default arguments : 기본 값을 사용 
    함수를 호출할 때, 파라미터를 넘겨주지 않아도 default 값을 사용하여 함수를 실행하게 됨

📌 varialble asterisk

  • 함수의 파라미터 수가 정해져있지 않을 때 사용 keyword arguments 와 함께 추가 가능 
  • args * 와 같은 기호를 사용해 파라미터 표시 
  • 입력된 값은 tuple 형태
  • 오직 한 개만 맨 마지막에 위치 (이거 생각보다 중요)
  • unpacking 가능
def asterisk(a,b,*args):
    print(list(args))
    print(type(args))
    return a + b + sum(args)

print(asterisk(1,2,3,4,5))
# [3, 4, 5]
# <class 'tuple'>

def asterisk(a,*args, b):
    print(list(args))
    print(type(args))
    return a + b + sum(args)

print(asterisk(1,2,3,4,5))

# TypeError: asterisk() missing 1 required keyword-only argument: 'b'

📌 keyword variavle-length

  • parameter 이름을 지정하지 않고 입력
  • ** 두개를 사용해 parameter 표시 
  • 입력된 값은 dict type 사용 
def kwargs_test(**kwargs):
    print(kwargs)

print(kwargs_test(first = 1, second = 2))

## dict 형태로 출력됨 
# {'first': 1, 'second': 2}

짬뽕해서 사용 가능 but 얘네끼리도 서열이 있다,,

기본 parameter , keyword argument , variable-len , keyword variable-len 순서로 와야함 

## parameter , kw argument , variable-len , kw variable-len
def kwargs_test(one, two= 3, *args ,**kwargs):
    print(one + two + sum(args))
    print(args)
    print(kwargs)

kwargs_test(10,30,3,5,6,7,8, first = 1, second = 2)

# 69
# (3, 5, 6, 7, 8)
# {'first': 1, 'second': 2}

kwargs_test(one = 10, two = 30,3,5,6,7,8, first = 1, second = 2)
# 이건 안됨

📌 asterisk - unpacking a container  (*가 쓰이는 새로운 곳)

  • 자료형에 들어가 있는 값을 unpacking
def asterisk_test(a, *args) : 
    print(a,*args)
    print(a,args)
    print(type(args))

asterisk_test(1, *(2,3,4,5,6))

#1 2 3 4 5 6
#1 (2, 3, 4, 5, 6)
#<class 'tuple'>
  • args = (2,3,4,5,6) 으로 tuple이기 때문에 하나의 데이터로 볼 수 있다. 
    하지만 *args 해주는 순간 얘네갸 unpacking 되면서 2,3,4,5,6 원소 5개가 된다. 
    꼭 저렇게 장황하지 않고 *[1,2,3,4] 만 해줘도 1,2,3,4 가 출력됨
    단순 문자열도 가능 
a = 'hyerin'
print(*a)

# h y e r i n

📌 zip + * unpacking 콤보

진짜 개쩐다... for 문 돌릴 게 아니라 zip과 * 하나면 된다.

ex = ([1,2],[3,4], [5,6],[5,6],[5,6])
for value in zip(ex):
    print(value)

for value in zip(*ex):
    print(value)
    
####################
([1, 2],)
([3, 4],)
([5, 6],)
([5, 6],)
([5, 6],)
(1, 3, 5, 5, 5)
(2, 4, 6, 6, 6)

위의 for문을 보면 ex는 어차피 이미  하나이기 떄문에 zip을 할 수가 없다. 

아래의 for문을 보면 *ex 라서 ex의 unpacking이 일어나 원소가 6개로 인식된다. 

zip을 쓰면 각 원소별로 같은 인덱스에 있는 녀석들끼리 묶이게 된다 

와아아ㅏㅏ우 너무 신기함 

오늘 배운 lambda , map , enumerate , zip , * 등 만 잘 써도 파이썬 고수되버릴 거 같다;

 

 

1. 학습활동

  • 질문 : 왜 아래와 같이 코드를 짜면 주소값이 나올까 ?
    lambda와 list comprehension이 신기해서 이것저것 시도해보다가 생긴 질문 

    👉 피어세션에서 해결❌, 에드위드 게시판에 질문
result = [lambda x : x**2 for x in range(5)]

.👉 답변 : lambda의 x와 for 문의 x는 다른 x다. 
lamda x : x**2 for _ in range(5) 라고 적어도 무방함. 
lambda x : x*2 라는 함수를 5번 반복해서 리스트에 넣어주기 때문에 리스트에는 함수만 5개가 들어가게된다. 

result[0](4) = 16 이렇게 나온다 🤣🤣

result = [(lambda x : x**2)(x) for x in range(5)]

내가 생각했던 방식대로 표현하고싶으면 위와 같이 해주면 된다. 

하지만 그냥 x**2 for x in range(5) 쓰면 되는 거 굳이 저렇게 할 이유는 없는 거 같다.

그냥 아 이런 게 할 수도 있구나. 재밌었네 하고 넘어가면 될 거 같다. 

덕분에 lambda 와 map에 대해서 좀 더 공부할 수 있었다. 

 

❓ map의 iterable

- map의 구성 : r = map(function, iterable, ...) 

- iterable 이란 ? 한 번에 하나의 멤버를 반환할 수 있는 객체 

map(lambda x : x **2 if x%2 == 0 else x, ex)

함수 : lambda x : x **2 if x%2 == 0 else x

iterable : ex

함수를 ex 내의 원소들 즉 iterable의 모든 요소에 대해 적용한 후 반환해준다.

 

❓ lambda : 왜 함수객체가 나올까?

이 상태로는 함수를 호출할 수없다. 람다 표현식은 이름이 없는 함수를 만들기때문이다. 

>>> lambda x: x + 10
<function <lambda> at 0x02C27270>

괄호로 묶은 후 다시 ( ) 를 붙인 후, 인수를 넣어주면 lambda 자체를 호출할 수있다. 

(lambda x : x+10)(1)
>> 1

❌ 람다 표현식안에는 새로운 변수를 만들 수 없다.  람다 밖에 있는 변수를 데려올 수 는 있다. 

>>> (lambda x: y = 10; x + y)(1)
SyntaxError: invalid syntax

>>> y = 10
>>> (lambda x: x + y)(1)
11

 

피어세션 

 

  • 몬테카를로 적분 
    몬테카를로 적분은 무작위 샘플링을 이용하여 임의의 함수의 적분 값에 근사하는 방법이다. 

  • 구글링을 하면 이런 식이 나오는데 무작위 샘플링이랑 저 공식이랑 무슨 연관이 있는지에 대해 조원들에게 설명하였다. 
    우리나라의 넓이를 구해보자. 그러기 위해서는 우리나라를 그리는 함수를 구하고 범위를 자~알 설정해 적분해야한다. 하지만 우리나라를 구하는 함수는 구하기도 어렵고 계산하기도 어렵다. 몬테카를로 적분법을 사용해보자! 지도 위에 점을 무작위로 찍는다고 생각해보자. 예를 들어 점을 무작위로 1000번 (s = 1000) 찍었다고 하면, 점은 한반도 내 혹은 외에 찍히게 된다. 한반도의 넓이를 한반도 내에 찍힌 점의 개수 / 점을 직은 총 횟수 라고 생각할 수 있게된다. 물론 점을 조금만 찍으면 넓이에 근사하지 않겠지만 ,점을 많이 찍으면 찍을수록 (큰 수의 법칙) 실제 한반도의 넓이에 근사해진다. 
    그러면 x라는 점을 찍었을 때, 한반도 안에 있으면 함숫값이 1이 되고, 밖에 있으면 함숫값이 0이 되는 함수가 존재한다고 생각해보자. 그러면 $Sigma^{s} f(x)$ 는 한반도 안에 찍힌 점의 횟수가 된다. 안에 있다면 1이 더해지고 밖에 있다면 아무것도 안 더해질테니 (사실은 0이 더해진다.) 다시 보면 
    한반도 내에 찍힌 점의 개수 / 점을 직은 총 횟수 = $Sigma^{s} f(x)$ / S 로 위와 같은 식이 된다 

 

  •  확률분포 
    통계학부를 나와서 확률분포는 무엇인지, 어떤 것이 있는지에 대해서는 빠삭하다. 하지만 딥러닝에서 확률분포의 개념이 어떻게 사용되는지를 잘 모른다. 이번 피어세션에서는 딥러닝 속의 확률론에 대해 공부했다. 
    classification을 생각해보자.
    만약 우리가 고양이/개를 분류하는 모델에 어떤 이미지를 넣으면 고양이일 확률 0.98 , 개일 확률 0.02 이런 식으로 나오게 된다. 이 모델을 확률론적 관점에서 바라보면 분류문제는  "입력값 X를 넣어줬을 때, 모델에서 Y가 나올 확률" (예를 들어 이미지(x)를 입력으로 넣어줬을 때, 모델에서 개(y)가 나올 확률)
    이를 조건부 확률에 적용해보면 P( Y = y | X = x ) 라고 표현할 수 있다. -> 입력값 x가 주어졌을 때, 라벨 y가 나올 확률
    확률 분포를 알고있다면 좋겠지만, 현실에서는 데이터가 매우 많고 차원이 높아 분포를 알아내는 게 쉽지않은 경우가 대부분이다. 그렇기 때문에 이 데이터들이 어떠한 특정 확률 분포를 따른다고 미리 가정하고 시작한다.  그리고 그 가정한 확률분포에다가 우리가 주어진 관측값을 넣었을 때 나오는 확률값likelihood (우도)라고 한다. 평소에 최대우도라는 말은 많이 들어봤지만, 우도가 뭔지 모르고 넘어갔는데 자세히 알아보자. 
    예를 들어 확률분포 y 가 y = ax +b라는 방정식을 이루고 있다고 가정해보면 x는 관측값이고, a,b에 따라서 y의 값이 변한다.  지금 y 에 대해 ax+b라는 방정식 즉 어떤 분포에 따른다고 "가정"한 셈이니 y에 대해서 우도라고 하 수 있다.  그리고 a와 b는 y라는 확률분포의 파라미터가 된다. 파라미터의 값 즉 a,b의 값에 따라 확률분포의 모양이 결정된다. 
    우도의 관심사는 '파라미터'에 있다. 이 파라미터를 잘 조정하면 실제 확률 분포와 비슷한 분포를 가질 수 있기때문이다. 여기서 나온 개념이 최대 우도 추정법 바로 MLE다. MLE에 대해서는 다음 시간에 이어서~!

 

 

2. 학습활동 외 

  • 조교님께서 들어오셔서 현실적이면서도 좋은 얘기들을 많이 해주셨다. 무엇보다 중요한 건 "잘하는 거"
  • 일주일에 1번 2명씩 논문 발표 권장
    -> 일주일에 하루는 논문 발표 및 나머지 3일은 학습내용 및 과제 질문 토론 
         각자 관심있는 분야 논문을 읽고 발표, 발표자 외 조원들은 편하게 들어도 되고 논문을 읽고와도 된다.
  • 어제 과제를 하고 나서 또 오늘 강의를 듣고나서 파이써닉 코드란 무엇인지 생각하게 됨.
    어떻게 해야 파이써닉하게 코드를 짤 수 있을까? 
    1. 내가 다른 사람의 코드 보기 : 해설강의를 듣고 내가 못한 부분, 참고하면 좋을 부분등을 배운다. 
    2. 다른 사람이 내 코드 보기 : 다른 사람들의 피드백이 필요하다고 생각 -> 피어세션에 제안, 원하는 사람만 자신의 코드를 보여주고 다 같이 리뷰해주기로함