from sklearn.model_selection import train_test_split # 2-way / 3-way는 train_test_split을 두 번 하면 된다
Modeling
모델이란 수많은 데이터를 통해 패턴을 발견하거나 예측을 수행하는 알고리즘의 표현식이다
따라서 모델링은 그러한 알고리즘을 만들어 내는 과정을 말한다
모델을 잘 만들기 위해서 모델링하기 전 데이터에 대한 이해가 필요하다 => EDA를 한다
모델링 하기 전에 알아둬야 할 키워드
1. Ad-hoc : 특별한 목적을 가진 것
- 데이터에 따라서 달라진다
- 일반적이지 않다
2. No free lunch <----> Master algorithm
- 공짜 점심은 없다
from sklearn.model_selection import cross_val_score # 데이터 양이 적을때 사용하는 방법
re # 데이터 양이 적을때 사용하는 방법
model = tf.keras.Sequential([
tf.keras.layers.Flatten(input_shape = (28,28)) # (28,28)인 2차원으로 만들어 준다
])
X_train[0].shape # 2차원 데이터
# (28, 28)
X_train.dtype
# dtype('uint8')
data = model(X_train) # 묵시적으로 데이터 타입이 uint8에서 float32로 바뀌었다 / 미분해야 하기 때문에 float타입이어야 한다
model.fit() # class_weight => 클래스에 가중치를 줌으로써 불균등한 데이터의 문제를 해결할 수 있다
from sklearn.linear_model import LogisticRegression
from sklearn.multiclass import OneVsOneClassifier, OneVsRestClassifier
from sklearn.svm import SVC
Ir = LogisticRegression() # 가정이 성립되면 전통적인 머신러닝 알고리즘 사용 가능하다
Ir.fit(data.numpy(), y_train)
Ir.predict(X_test[0].reshape(1,-1))
y_test[0]
# 7
svm = SVC()
svm.fit(data.numpy(), y_train)
본 수업 용어 규칙
전통적인 머신러닝 모델 = end to end (feature extraction + model)이 아닌 모델
여기서 end to end 모델은 input이 들어가서 output이 나오는 과정을 오로지 데이터에 의존해서 찾아내는 모델을 말한다
end to end 모델이 성능이 좋을때가 많지만 충분한 학습 데이터가 없다면 굉장히 성능이 떨어지는 모델이 될 수 있다
그리고 end to end 모델은 중간 과정을 설명할 수 없기 때문에 중간 과정을 설명해야 하는 중요한 문제가 발생하는 부분에서는 사용하기가 쉽지 않다
i.i.d (independent identically distributed)
독립 항등 분포 column을 바꿔도 의미가 유지가 되어야 한다
import seaborn as sns
iris = sns.load_dataset('iris')
이미지는 column이 바뀌면 의미가 유지되지 않는다
그래서 전통적인 방식의 머신러닝으로 이미지 데이터 사용하기 쉽지 않다
단, 가설(가정)을 통해서 가능하게 할 수 있다
Assumption
이미지 데이터를 사용하여 전통적인 머신러닝 방식 해결하려면 가정이 필요하다
가정이 잘못 되면 모든 것이 잘 못 될 경우가 있다
이미지 데이터를 사용할 경우 data leakage문제가 발생하지 않는다 라는 가정을 갖고 학습을 하게 되면 전통적인 머신러닝 방식으로도 사용할 수 있다
가정을 많이 할 수록 사용할 수 있는 것이 한정적이게 된다
data leakage 문제: 학습 데이터 밖에서 유입된 데이터가 모델을 만드는데 사용되어 overfitting이 되거나 underfitting이 되는 경우를 말한다
%timeit im*im
# 1000 loops, best of 5: 202 µs per loop
%timeit im**2
# 1000 loops, best of 5: 227 µs per loop
cv2.useOptimized() # opencv연산에 최적화 되어 있다
# True
%timeit cv2.medianBlur(im, 49) # 최적화 되어 있을 때 (최적화 되어 있을 때 일반적으로 20% 빠르다 / colab에서 medianBlur연산은 최적화 해도 그렇게 빠르지 않다)
# 10 loops, best of 5: 37.5 ms per loop
cv2.setUseOptimized(False)
cv2.useOptimized()
# False
%timeit cv2.medianBlur(im, 49) # 최적화 되지 않을 때
# 10 loops, best of 5: 37.8 ms per loop
cv2.setUseOptimized(True)
cv2.useOptimized()
# True
%timeit im*im*im
# The slowest run took 20.40 times longer than the fastest. This could mean that an intermediate result is being cached.
# 1000 loops, best of 5: 292 µs per loop
%timeit im**3 # 연산량이 많아지면서 im*im*im연산보다 im**3이 훨씬 느려졌다
# 100 loops, best of 5: 3.24 ms per loop
EqualizeHist
im = cv2.imread('people.jpg', 0)
im2 = cv2.equalizeHist(im)
plt.hist(im2.ravel(), 256,[0,256]); # 정규 분포 처럼 분포를 균일하게 만들어 준다
plt.imshow(cv2.equalizeHist(im), cmap='gray') # 평탄화 => contrast가 낮아진다 => 분포가 균일해진다 => 밝고 어두운 정도가 구분하기 힘들어진다
from skimage.exposure import cumulative_distribution
len(cumulative_distribution(im))
# 2
y, x = cumulative_distribution(im)
plt.hist(y);
# (array([ 26., 29., 48., 28., 17., 13., 12., 13., 16., 54.]),
# array([ 2.74348422e-05, 1.00024691e-01, 2.00021948e-01,
# 3.00019204e-01, 4.00016461e-01, 5.00013717e-01,
# 6.00010974e-01, 7.00008230e-01, 8.00005487e-01,
# 9.00002743e-01, 1.00000000e+00]),
# <a list of 10 Patch objects>)
# 각각 점의 컬러 분포
plt.hist(im[...,0].ravel(), 256, [0, 256]); # B
plt.hist(im[...,1].ravel(), 256, [0, 256]); # G
plt.hist(im[...,2].ravel(), 256, [0, 256]); # R
plt.xlim([0,256])
np.bincount(np.arange(5)) # 각각 몇개 있는지 세는 함수
# array([1, 1, 1, 1, 1])
np.bincount(np.array([0,1,1,3,2,1,7]))
# array([1, 3, 1, 1, 0, 0, 0, 1])
from itertools import count, groupby
t = groupby([1,1,1,2,2])
b = count()
next(b)
# 3
next(t) # 그룹으로 묶어서 몇개 있는지 확인
# (2, <itertools._grouper at 0x7fa251080ad0>)
얼굴부분 crop해서 색 분포 확인하기
face = im[50:300,50:200]
plt.imshow(face[...,::-1])
plt.imshow(cv2.bitwise_and(im,mask)) # 차원이 달라서 연산이 안된다
# error: OpenCV(4.1.2) /io/opencv/modules/core/src/arithm.cpp:229: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and type), nor 'array op scalar', nor 'scalar op array' in function 'binary_op'
cv2.calcHist([im], [0], mask, [256], [0,256]) # mask외 분포 구할 때
plt.plot(cv2.calcHist([im], [0], mask, [256], [0,256]))
전통적인 머신러닝에서는 contrast가 높으면 이미지 분류가 잘 안되기 때문에 contrast 분포를 평평하게 만들었었다 (contrast normalization)
from skimage.exposure import histogram, rescale_intensity, equalize_hist
from skimage.util import img_as_float, img_as_ubyte
im = cv2.imread('people.jpg')
plt.imshow(img_as_float(im)[...,::-1])
# 이미지는 float나 int 둘다 사람이 볼때는 상관 없다 / 하지만 컴퓨터는 float로 바꾸면 연속적이기 때문에 미분이 가능해진다
x = [1,2,3,4,5,6]
y = np.array(x)
y.cumsum() # 누적합을 빠르게 구하는 방법
# array([ 1, 3, 6, 10, 15, 21])
np.add.accumulate(y)
# array([ 1, 3, 6, 10, 15, 21])
np.add.reduce(y)
# 21
import tensorflow as tf
tf.reduce_sum(y)
# <tf.Tensor: shape=(), dtype=int64, numpy=21>
# import seaborn as sns
iris = sns.load_dataset('iris')
sns.pairplot(iris, hue='species', diag_kind='hist') # 데이터에 분포에 따라서 옳바른 데이터인지 아닌지 확인할 수 있기 때문에 그래프를 확인한다
이미지 데이터를 활용하여 어떠한 목적을 달성해야 할때 (머신러닝, 딥러닝을 활용하여 문제 해결을 해야 할때)
충분하지 못한 이미지 데이터를 갖고 있거나, 데이터 품질이 좋지 못할때, 데이터에 표시를 해야 할때등
여러가지 이유에서 목적을 수월하게 달성할 수 있다.
이미지 처리를 위한 python library
1.Scikit-image=>Numpystyle
2.OpenCV=>Cstyle
3.PIL=>Pythonstyle
Scikit-Image
scikit-learn과 비슷한 명명 규칙을 따른다
skimage의 중분류
1. color # 색 변환
2. draw # 이미지내 그림 표시, 문자 표시, 좌표 그리기
from skimage.draw import line, rectangle, circle
from skimage.io import imread
import matplotlib.pyplot as plt
import numpy as np
len(line(0,0,100,100))
# 2
x = np.arange(24).reshape(4,6)
x[[0,1,2],[1,2,3]] # 1, 8, 15 방향의 직선 => a, b = line(0,0,100,200) 와 유사한 인덱싱
# array([ 1, 8, 15])
x
array([[ 0, 1, 2, 3, 4, 5],
[ 6, 7, 8, 9, 10, 11],
[12, 13, 14, 15, 16, 17],
[18, 19, 20, 21, 22, 23]])
h, w = rectangle((20,20),(100,100))
im[h,w] = 0
# 직선과 직사각형이 이미지에 같이 표시되는 이유는 im 객체가 mutable이기 때문에 line함수와 rectangle함수가 적용된 결과가 누적된다
plt.figure(figsize=(6,6))
plt.imshow(im)
h, w = rectangle((300,300),(100,100))
im[h,w] = 0
plt.figure(figsize=(6,6))
plt.imshow(im)
import numpy as np
import cv2 # opencv
im = cv2.imread('people.jpg')
type(im) # opencv로 불러올 때도 im객체가 ndarray이기 때문에 mutable이다
# numpy.ndarray
# im객체가 rectangel이라는 함수를 사용하면 결과가 누적된다 (mutable)
im_r = cv2.rectangle(im, (100,100), (250,250), (255,0,0), 10) # 두번째 인자는 직사각형에서 왼쪽 위 좌표, 오른쪽 아래 좌표 / 세번째 인자는 RGB / 네번째 인자는 테두리 크기
plt.figure(figsize=(6,6))
plt.imshow(im_r);
텍스트 그리기
opencv에서는 font변경이 한정적이기 때문에 기본적으로 한글 사용 불가하다
opencv를 font를 바꾸고 나서 compile하면 한글 사용할 수 있다
beatmap형태를 지원한다
im = cv2.imread('people.jpg')
cv2.putText(im, 'moon', (200,100), 1, 6, (255,0,0), 2) # 이미지 / 텍스트 / 위치 / 폰트 / 폰트크기 / RGB / 두께
for i in dir(cv2):
if 'FONT' in i:
print(i, getattr(cv2, i))
im1 = cv2.imread('people.jpg')
im2 = cv2.imread('apple.jpg')
cv2.addWeighted(im1, 0.7, im2, 0.3, 0) # im1의 불투명도를 70% im2의 불투명도를 30% / 두 개 이미지 shape이 같아야 한다
# error: OpenCV(4.1.2) /io/opencv/modules/core/src/arithm.cpp:663: error: (-209:Sizes of input arguments do not match) The operation is neither 'array op array' (where arrays have the same size and the same number of channels), nor 'array op scalar', nor 'scalar op array' in function 'arithm_op'
im1.shape, im2.shape
((540, 540, 3), (1000, 816, 3))
im2[:540,:540].shape
im2 = im2[250:790,150:690]
blend = cv2.addWeighted(im1, 0.7, im2, 0.3, 0) # 합성 이미지 / blending => 크기와 채널이 같아야 한다 / 두 이미지를 합 할때 가중치를 두고 연산한다 (가중합)
plt.imshow(blend)
Bitwise 연산
a = 20
bin(a)
# '0b10100'
a.bit_length()
# 5
a = 20
b = 21
# 10100
# 10101
# ↓
# 10100
a&b # bitwise and 연산 / 둘 다 1일 때 1, 둘 중 하나라도 0이면 0
# 20
# 10100
# 10101
# ↓
# 10101
a|b # bitwise or 연산 / 둘 중 하나라도 1이면 1, 둘 다 0이면 0
# 21
bin(a),bin(b)
# ('0b10100', '0b10101')
a >> 2 # 두 칸 뒤로 / 10100 => 00101
# 5
a << 2 # 두 칸 앞으로 / 10100 => 1010000
# 80
from PIL import Image, ImageDraw, ImageDraw2, ImageOps
import PIL
im_pil = Image.open('people.jpg')
type(im_pil)
# PIL.JpegImagePlugin.JpegImageFile
dir(PIL.JpegImagePlugin.JpegImageFile)
PIL.JpegImagePlugin.JpegImageFile.__class__
# type
im_pil
type(ImageDraw.ImageDraw) # Class
# type
type(ImageDraw.Draw) # function
# function
draw = ImageDraw.ImageDraw(im_pil) # composition 방식
draw2 = ImageDraw.Draw(im_pil) # function 방식 / function 방식이지만 return이 instacne이기 때문에 헷갈려서 대문자로 만들었다
draw.rectangle(((0,0),(100,100))) # mutable 방식
draw2.rectangle(((100,100),(200,200)))
im_pil
type(ImageOps)
# module
ImageOps.expand(im_pil, 20) # composition 함수 / im_pil이라는 인스턴스를 인자로 넣고 사용하기 때문에 composition 방식이다 / 결과가
텍스트 그리기
im_pil = Image.open('people.jpg')
from PIL import ImageFont
draw = ImageDraw.ImageDraw(im_pil)
draw2 = ImageDraw.Draw(im_pil)
draw.text((120,100),'Chim') # mutable 방식
im_pil
draw.text((100,120),'한글') # 기본적인 방식에서는 한글 지원 안한다
UnicodeEncodeError: 'latin-1' codec can't encode characters in position 0-1: ordinal not in range(256)
!sudo apt-get install -y fonts-nanum # colab에서 사용시 폰트를 다운 받아야 한다
!sudo fc-cache -fv
!rm ~/.cache/matplotlib -rf
for i in dir(cv2):
if 'BORDER_' in i :
print(i, getattr(cv2, i))
BORDER_CONSTANT 0
BORDER_DEFAULT 4
BORDER_ISOLATED 16
BORDER_REFLECT 2
BORDER_REFLECT101 4
BORDER_REFLECT_101 4
BORDER_REPLICATE 1
BORDER_TRANSPARENT 5
BORDER_WRAP 3
im4 = Image.open('people.jpg') # mutable 방식 지원
plt.imshow(im4)
b = np.array(im4)
Image.fromarray(b)
type(b)
# numpy.ndarray
type(im4) # 데이터 구조 자체가 numpy와 다르다 하지만 Numpy와 호환이 된다
# PIL.JpegImagePlugin.JpegImageFile
im4.format_description
# 'JPEG (ISO 10918)'
im4.getexif().keys()
# KeysView(<PIL.Image.Exif object at 0x7f424eb895d0>)
im_pil = Image.open('people.jpg')
R, G, B = im_pil.split() # method 방식
Image.merge('RGB', (R,G,B))
Image.merge('RGB', (B,G,R))
'ImageMode' in dir(PIL) # composition 방식으로 만들어짐
# True
from PIL import ImageMode
ImageMode._modes # Image.merge(mode, bands) 모드 자리에 들어 갈 수 있는 키값들
{'1': <PIL.ImageMode.ModeDescriptor at 0x7f4249d1b350>,
'CMYK': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45bd0>,
'F': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45d50>,
'HSV': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45590>,
'I': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45d90>,
'I;16': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45a50>,
'I;16B': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45f50>,
'I;16BS': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45e90>,
'I;16L': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45410>,
'I;16LS': <PIL.ImageMode.ModeDescriptor at 0x7f4249d450d0>,
'I;16N': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45390>,
'I;16NS': <PIL.ImageMode.ModeDescriptor at 0x7f4249d31610>,
'I;16S': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45ad0>,
'L': <PIL.ImageMode.ModeDescriptor at 0x7f4249d1bd10>,
'LA': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45190>,
'LAB': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45110>,
'La': <PIL.ImageMode.ModeDescriptor at 0x7f4249d457d0>,
'P': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45e50>,
'PA': <PIL.ImageMode.ModeDescriptor at 0x7f4249d452d0>,
'RGB': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45850>,
'RGBA': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45210>,
'RGBX': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45e10>,
'RGBa': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45510>,
'YCbCr': <PIL.ImageMode.ModeDescriptor at 0x7f4249d45a90>}
im_pil.convert('L')
im = tf.io.read_file('people.jpg')
im = tf.image.decode_jpeg(im)
im = imageio.imread('people.jpg') # Array (array 상속) / 다양한 포맷 지원 / 여러 이미지 불러올 수 있다
im = cv2.imread('people.jpg') # c/c++ style / array가 BGR로 구성되어 있다 / opencv는 gpu도 지원하기 때문에 속도가 가장 빠르다
im = Image.open('people.jpg') # numpy와 호환이 되지만 numpy format은 아니다 / PIL이 가장 느리다
plt.imread('people.jpg') # png파일 불러오지 못함
array([[[201, 189, 177],
[203, 191, 179],
[204, 192, 180],
...,
[194, 158, 126],
from skimage import io
im = io.imread('people.jpg')
plt.imshow(im)
im = Image.open('people.jpg')
im2 = im.getdata()
type(im2)
# ImagingCore
list(im2)
'__iter__' in dir(im2)
# False
im2.__class__.mro() # ImagingCore에 __iter__가 있기때문에 반복, 순회 가능
# [ImagingCore, object]
im = Image.open('people.jpg').convert('1') # meta data를 갖고 있기때문에 가능한 방법
im.getcolors()
# [(142786, 0), (148814, 255)]
복습
Stride
a = np.arange(24).reshape(6,4) # 내부적으로는 일렬로 저장된다
a.flags # Information about the memory layout of the array.
# C_CONTIGUOUS : True
# F_CONTIGUOUS : False
# OWNDATA : False
# WRITEABLE : True
# ALIGNED : True
# WRITEBACKIFCOPY : False
# UPDATEIFCOPY : False
a.dtype
# dtype('int64')
a.strides # 1차원의 array를 조립하는 가이드 라인
# (32, 8)
a.itemsize # memory 크기가 8인 정육각형
# 8
a
array([[ 0, 1, 2, 3],
[ 4, 5, 6, 7],
[ 8, 9, 10, 11],
[12, 13, 14, 15],
[16, 17, 18, 19],
[20, 21, 22, 23]])
a.shape
# (6, 4)
a.sum(0) # axis = 0 기준으로 더한다
# array([60, 66, 72, 78])
a.sum(1) # axis = 1 기준으로 더한다
# array([ 6, 22, 38, 54, 70, 86])
b = np.arange(24).reshape(2,3,4)
b.shape
# (2, 3, 4)
b.sum(axis=1) # shape에서 index 1을 지우면 결과는 (2,4)
array([[12, 15, 18, 21],
[48, 51, 54, 57]])
b.sum(axis=2) # shape에서 index 2를 지우면 결과는 (2,3)
array([[ 6, 22, 38],
[54, 70, 86]])
with open('Elon.jpg', 'rb') as f: # 기본 옵션 r은 unicode형식으로 불러온다 / rb는 바이너리로 불러온다
print(f.read()) # 파이썬에서는 이미지는 불러올수는 있지만 각각의 값이 어떤 색을 표현하는지 해석할 수 없다
# b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00II*\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xec\x00\x11Ducky\x00\x01\x00\x04...
import tensorflow as tf
im = tf.io.read_file('Elon.jpg') # vectorization으로 불러온다 / array연산을 최적화 하도록 불러온다
im
# <tf.Tensor: shape=(), dtype=string, numpy=b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00II*\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0
# .numpy() 하면 기타정보 빼고 내용만 보여줌
im.numpy()
# b'\xff\xd8\xff\xe1\x00\x18Exif\x00\x00II*\x00\x08\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xec\x00\x11Ducky\x00\x01\x00\x04
이미지를 불러올 때 tensor가 좋은 점은 gpu연산이 가능하기 때문에 대용량 이미지를 더 빠르게 불러올 수 있다
import matplotlib.pyplot as plt
plt.figure(figsize=(10,10))
plt.imshow(t) # array를 image로 해석하는 방법을 제공한다 / version 때문에 tensor를 직접 표현 못할 수 있다. 혹시 안된다면 colabd으로 사용해 보는 것을 추천한다
plt.colorbar(); # 컬러바 표시하기
plt.axis('off'); # 좌표 축 없애기
# 같은 셀안에서 실행하면
plt.figure();
plt.axes([0.5,0.5,1,1]); # figure 안에서 좌표형태로 보여준다
plt.plot([4,5,4]);
plt.axes([0,0,0.5,0.5]);
plt.plot([4,5,4]);
plt.axes([0,0,0.5,0.5])
# plt.figure()
# plt.axes()
plt.plot([1,2,3]) # plot만 하면 figure, axes default값으로 자동 지정된다
s = t / 255
t.dtype
# tf.uint8
s.dtype
# tf.float32
plt.imshow(s) # 이미지를 구성하는 값들은 상대적인 개념이기 때문에 특정 값을 기준으로 나누어도 사람이 볼때는 똑같은 이미지로 볼 수 있다
im = tf.io.read_file('Elon.jpg')
t = tf.image.decode_image(im)
ss = tf.cast(t, 'float32')
ss.dtype
# tf.float32
sss = (ss/127.5) -1 # -1에서 1 사이로 변환해주는 방법 / 가장 큰 값이 255이기 때문에 절반으로 나누고 1을 빼면 -1과 1 사이의 값으로 구성된다
import numpy as np
np.min(sss)
# -1.0
plt.imshow(sss) # imshow는 -1값은 표현하지 못하기 때문에 달라 보인다
def visualize_bw(img):
w, h = img.shape
plt.figure(figsize=(10,10))
plt.imshow(img, cmap='binary') # binary는 작은 숫자가 밝게 / gray는 큰 숫자가 밝게
for i in range(w):
for j in range(h):
x = img[j,i]
plt.annotate(x, xy=(i,j), horizontalalignment='center', verticalalignment='center', color = 'black' if x <= 128 else 'white') # 점 하나당 대체 시킨다
visualize_bw(X_train[0])
Image processing
input image가 어떠한 함수에 의해 새로운 image가 output으로 나오는 것
array연산을 통해서 새로운 형태의 image를 만들어 내는 것
visualize_bw(X_train[5][::-1])
# 뒤집기
visualize_bw(X_train[5][4:24,4:24])
# 잘라내기
visualize_bw(np.flipud(X_train[5])) # flipud => 이미지 뒤집기
# 크기가 (20,20)인 흑백 이미지 생성
visualize_bw(np.ones((20,20)))
class A:
def __repr__(self):
return 'REPR'
def __str__(self):
return 'STR'
a = A()
a # 이름을 부르면 __repr__가 호출된다
# REPR
print(a) # 이름을 print하면 __str__이 호출된다 / escape를 처리해준다
# STR
※ self를 붙이는 이유
class A:
def __init__(this): # self 대신 다른 것 사용 가능 그러나 pep8에서 self 사용 권장
print('a')
def __call__(self): # 객체 지향에서 self는 인스턴스(object)를 뜻한다
print('call')
def aa(self):
print('aa')
# python 내부에서 c기반으로 작동하기 때문에 (c는 method 개념이 없다) 이와 같이 만들어 졌다
a = A()
A.aa(a) # 어떤 인스턴스에 대해서 사용할 것인가 인자가 필요 / version 1 / 주체가 class A
a.aa(a) # 인스턴스가 주체이고 싶을 때 / argument가 중복되기 때문에 a 생략하게 만들었다
a.aa()
class A:
x = 1
class B:
x = 2
class C(A,B):
pass
# method resolution order (python2.2 부터 지원)
C.mro()
# [__main__.C, __main__.A, __main__.B, object]
C.__mro__
# (__main__.C, __main__.A, __main__.B, object)
C.x # C에 없어서 그 다음 우선순위인 A에서 찾는다
# 1
다이아몬드 문제
class A:
pass
class B(A):
pass
class C(A):
pass
class D(A, B):
pass
# 이런 경우 메소드의 실행순서를 정할 수 없다
# TypeError: Cannot create a consistent method resolution order (MRO) for bases A, B
class A:
def __init__(self):
print('A')
class B(A):
def __init__(self): #overriding (parent와 가능한 parameter 일치)
A.__init__(self)
print('B')
class C(A):
def __init__(self):
A.__init__(self)
print('C')
class D(B, C):
def __init__(self):
B.__init__(self) # __init__ 함수
C.__init__(self)
print('D')
d = D() # A가 중복해서 실행되는 문제가 발생한다
# A
# B
# A
# C
# D
D.mro()
# [__main__.D, __main__.B, __main__.C, __main__.A, object]
import tensorflow as tf
model = tf.keras.models.Sequential()
model.__class__.mro()
[keras.engine.sequential.Sequential,
keras.engine.functional.Functional,
keras.engine.training.Model,
keras.engine.base_layer.Layer,
tensorflow.python.module.module.Module,
tensorflow.python.training.tracking.tracking.AutoTrackable,
tensorflow.python.training.tracking.base.Trackable,
keras.utils.version_utils.LayerVersionSelector,
keras.utils.version_utils.ModelVersionSelector,
object]
Super - 중복 실행을 방지하는 방법
super는 상속을 전부 실행하지 않는다
class A:
def __init__(self):
print('A')
class B(A):
def __init__(self):
super().__init__() #super()는 부모의 인스턴스다 / python3 방식이다
print('B')
class C(A):
def __init__(self):
super(C, self).__init__() # __init__은 메소드 이다 / python2, python3 방식이다
print('C')
class D(B, C):
def __init__(self):
super().__init__()
print('D')
d = D()
# A
# C
# B
# D
D.mro()
# [__main__.D, __main__.B, __main__.C, __main__.A, object]
실행 순서는 D -> B -> C -> A 인데 출력 결과는 왜 반대일까? 그 이유는 바로 super사용시 stack에 들어가기 때문이다
※ Stackoverflow
def fib(n):
return fib(n-1) + fib(n-2) if n>2 else 1 # 재귀적으로 계산되는 중간 과정은 stack영역에 저장된다 => 중간 과정에서 중복되는 연산이 많이 쌓인다 => stack에 많이 쌓여 넘치는 것을 stackoverflow
# 그러나 python에서는 overflow가 없다
fib(10)
# 55
import sys
sys.maxsize
# 9223372036854775807
sys.maxsize + 1 # 정수형은 stackoverflow가 없는 대신에 내부적으로 현재 남아있는 만큼의 가용 메모리를 모두 수 표현에 끌어다 쓴다. 따라서 정수형 연산은 속도(성능)가 매우 느리다
# 9223372036854775808
sys.float_info
# sys.float_info(max=1.7976931348623157e+308, max_exp=1024, max_10_exp=308, min=2.2250738585072014e-308, min_exp=-1021, min_10_exp=-307, dig=15, mant_dig=53, epsilon=2.220446049250313e-16, radix=2, rounds=1)
0.1 + 0.1 + 0.1
# 0.30000000000000004
1.7976931348623157e+308 == 1.7976931348623157e+308 + 1 # 근사적으로 계산하기 때문에 속도가 빠르다
# True
a == a + 1 # infinity
Duck Typing
미운오리새끼 이야기에서 유래가 되어 오리가 아닌데 오리처럼 행동을 하면 오리라고 간주한다는 개념이다
타입을 미리 정하지 않고 실행되었을 때 해당 Method들을 확인하여 타입을 정한다
'장점'
-타입에대해자유롭다
-상속을하지않아도상속을한것처럼클래스의메소드사용이가능하다
'단점'
-원치않는타입이들어갈경우오류가발생할수있다
-오류발생시원인을찾기어려울수있다
-호환성떨어짐=>추상클래스로대체
-코딩많이해야함
Composition
상속을 하지 않고 클래스내에 객체를 불러와 다른 클래스의 일부 기능을 사용하는 방법.
상속의 위험부담을 줄일 수 있다
class SecurityDoor:
locked = True
def __init__(self, number, status):
self.door = Door(number, status) # Door의 객체를 갖게 한다
def open(self):
if self.locked:
return
self.door.open()
def __getattr__(self, attr): # try except와 비슷
return getattr(self.door, attr) # Door의 attr를 가져와라 (상속을 비슷하게 사용)
class ComposedDoor:
def __init__(self, number, status):
self.door = Door(number, status)
def __getattr__(self, attr): # 없으면 가져와라
return getattr(self.door, attr) # 바꾸지 않고 불러와서 사용할 때
Meta class
type => 모든 클래스의 행동을 결정해준다 클래스의 행동을 바꾸고자 할때 사용하는 클래스
type('int2', (int,), {}) # __main__ 메소드가 있다면 현재 내 작업 공간에서 만든 것이라는 의미이다
# __main__.int2
a = type('int2', (int,), {})
type(a)
# type
class A: # attribute(클래스안에 정의된 모든 것) vs property (descriptor)
pass
%whos # 현재 작업 공간에 어떤 객체가 있는가 확인 할때 사용
#
Variable Type Data/Info
-------------------------------
A type <class '__main__.A'>
a type <class '__main__.int2'>
drive module <module 'google.colab.dri<...>s/google/colab/drive.py'>
itertools module <module 'itertools' (built-in)>
%who_ls
['A', 'a', 'drive', 'itertools']
%lsmagic
Available line magics:
%alias %alias_magic %autocall %automagic %autosave %bookmark %cat %cd %clear %colors %config %connect_info %cp %debug %dhist %dirs %doctest_mode %ed %edit %env %gui %hist %history %killbgscripts %ldir %less %lf %lk %ll %load %load_ext %loadpy %logoff %logon %logstart %logstate %logstop %ls %lsmagic %lx %macro %magic %man %matplotlib %mkdir %more %mv %notebook %page %pastebin %pdb %pdef %pdoc %pfile %pinfo %pinfo2 %pip %popd %pprint %precision %profile %prun %psearch %psource %pushd %pwd %pycat %pylab %qtconsole %quickref %recall %rehashx %reload_ext %rep %rerun %reset %reset_selective %rm %rmdir %run %save %sc %set_env %shell %store %sx %system %tb %tensorflow_version %time %timeit %unalias %unload_ext %who %who_ls %whos %xdel %xmode
Available cell magics:
%%! %%HTML %%SVG %%bash %%bigquery %%capture %%debug %%file %%html %%javascript %%js %%latex %%perl %%prun %%pypy %%python %%python2 %%python3 %%ruby %%script %%sh %%shell %%svg %%sx %%system %%time %%timeit %%writefile
Automagic is ON, % prefix IS NOT needed for line magics.
A # REPR(Representation / 이름을 부르면 표현해 주세요) => 메모리에 저장되어 있는 것 부른다
# __main__.A
※ Error
1.NameError : 이름이없을때
2.AttributeError : attribute가없을때, 기능자체가없을때
import tensorflow as tf
model = tf.keras.models.Sequential()
model.summary() # 실행하는 시점에 따라서 인스턴스 변수가 다르게 갖기 때문에 / 값이 없기 때문에 valueError
# ValueError: This model has not yet been built. Build the model first by calling `build()` or calling `fit()` with some data, or specify an `input_shape` argument in the first layer(s) for automatic build.
model(tf.constant([[1,2]]))
# <tf.Tensor: shape=(1, 2), dtype=int32, numpy=array([[1, 2]], dtype=int32)>
model.summary()
#
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
Total params: 0
Trainable params: 0
Non-trainable params: 0
_________________________________________________________________
layer = tf.keras.layers.Dense(1)
layer.summary()
# AttributeError: 'Dense' object has no attribute 'summary'
class A:
t = 1
def tt(self):
print('t')
getattr(A, 't') # A에서 t접근 해주세요
# 1
a = A()
getattr(A, 'tt')(a)
# 1
변수와 상수가 구분이 있는 언어라면 상수는 재할당 할 수 없다
python은 변수와 상수를 구분하지 않는다
따라서 tensorflow에서는 이를 구분하기 위해 constant와 variable을 추가적으로 만들었다
cc = tf.constant([1,2,3]) # 상수
dd = tf.Variable([1,2,3]) # 변수
dd.assign_add([2,3,4])
# <tf.Variable 'UnreadVariable' shape=(3,) dtype=int32, numpy=array([3, 5, 7], dtype=int32)>
import numpy as np
x = np.array([1,2,3])
x
# array([1, 2, 3])
print(x)
# [1 2 3]
Python의 *
1.number*, **
-3*3# 곱하기
-3**3# 제곱
2.sequence*
3.assignment (binding) : unpacking
-x,y,*z=1,2,3,4# unpacking에서 나머지
4.parameter*name
-defx(*b): # 인자의 갯수 제한을 두지 않는다 returnb
5.parameter*
-defname(*,a,b): # keyword only *다음엔 keyword를 사용해야 한다
returna+b# positional 방식 다음에 keyword 방식을 써야 한다
6.parameter**
-defy(**b): # Variable Keywordreturnb7.argument*
-defa(*b):
returnbx= [1,2,3,4] a(*x) # argument unpacking
8.argument**
-defaa(**b):
returnby= {'a': 1, 'b' : 2} aa(**)
9.import에서모두
-import*
※ def t(a, *b)이런 형태를 자주 쓰는 이유
deft(*a, **b):
print(a)
print(b)
# parameter, argument를 정확히 맞출 필요가 없음, 인터페이스의 유연성을 위해
class B:
def __init__(self, m):
self.m = m
def __call__(self, n):
return self.m + n
B(3)(4)
# 7
def __call__(self, n):
return self.m + n + 1
B.__call__ = x
※ function이라는 데이터 타입도 존재한다
def a(n):
return n
type(a)
# function
Callable Method 종류
1.Instancemethod2.classmethod3.staticmethod
위임 개념은 크게 3가지로 볼 수 있다
1.instance2.class3.inheritance
class T:
a = 1 # class variable/attribute 클래스 안에서는 식별자를 변수라고 칭한다
# attribute 클래스 안에 정의 되어 있는 모든 것을 attribute 라고 한다
T.a # 클래스가 어떻게 행동해요? 클래스는 메타 클래스의 인스턴스이기 때문이다
# 1
T.a # 클래스가 어떻게 행동해요? 클래스는 메타 클래스의 인스턴스이기 때문이다
# {'xx': 1}
b.yy # 존재하지 않는 변수에 접근하면 attributeError를 발생시킨다
# AttributeError: 'T' object has no attribute 'yy'
b.a # 인스턴스 변수에 없으면 클래스 변수에서 찾는다 (위임)
# 1
class A:
@classmethod
def xx(cls): # instance / class variable의 생성은 시점에 따라 결정된다
cls.t = 1
print('cls')
def yy(self):
print('instance')
A.xx # classmethod는 클래스 입장에서 함수가 메소드로
# <bound method A.xx of <class '__main__.A'>>
A.yy
<function __main__.A.yy>
a = A()
a.xx() # 인스턴스에 없으면 클래스 찾는다
# cls
a.t
# 1
import pandas as pd
type(pd.DataFrame)
# type
pd.DataFrame.from_dict # classmethod
# <bound method DataFrame.from_dict of <class 'pandas.core.frame.DataFrame'>>
class B:
def aa(self):
self.a = 1
def bb(self):
self.b = 2 # 인스턴스 변수는 인스턴스가 사용한다
b = B()
b.a
# AttributeError: 'B' object has no attribute 'a'
b.aa() # 함수 실행시점에서 인스턴스 변수에 접근 가능해진다
b.a
# 1
vars(b)
# {'a': 1}
class test:
a = 1
@classmethod
def one(cls):
cls.a = 1
print('cls')
def two(self):
self.b = 2
t = test()
t.a
# 1
t.b # 인스턴스 메소드가 실행된 시점 이후에 접근 가능하다
# AttributeError: 'test' object has no attribute 'b'
t.two()
t.b
# 2
import tensorflow as tf
from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model
mnist = tf.keras.datasets.mnist
(x_train, y_train), (x_test, y_test) = mnist.load_data() # 동적 생성
x_train, x_test = x_train / 255.0, x_test / 255.0
class MyModel(Model):
def __init__(self): # callable 연산자 overloading / 역할은 기능을 초기화 한다
super(MyModel, self).__init__()
self.conv1 = Conv2D(32, 3, activation='relu')
self.flatten = Flatten()
self.d1 = Dense(128, activation='relu')
self.d2 = Dense(10)
def call(self, x):
x = self.conv1(x)
x = self.flatten(x)
x = self.d1(x)
return self.d2(x)
# Create an instance of the model
model = MyModel()
※ 클래스안의 클래스 메소드와 인스턴스 메소드의 가장 큰 차이점은 클래스를 인스턴스화 했을 때 클래스 메소드 안에 있는 클래스 변수는 바로 접근 가능하지만, 인스턴스 메소드 안에 있는 인스턴스 변수는 인스턴스 변수가 실행된 시점 이후에 접근 가능해진다
class C:
def __new__(cls):
print('new')
def __init__(self):
print('init')
C()
# new
c = C()
# new
vars(c)
# TypeError: vars() argument must have __dict__ attribute
class C:
def __new__(cls): # 객체를 반환하는 것
print('new')
return super().__new__(cls)
def __init__(self): # 반환된 객체의 값을 지정해주는 것 / 초기화 용도로 사용 / return이 None이어야 한
print('init')
c = C() # 파이썬에서 인스턴스 생성은 두 단계로 나누어져 있다
# new
# init
staticmethod
대형 프로젝트에서 주로 사용한다
class T:
@staticmethod # 기본적으로 python의 method는 정의 첫번째 인자 강제, 사용은 첫번째 인자 생략하는 것이 원칙이다
def st(): # staticmethos는 클래스 인스턴스와 상관 없이 존재한다
print('st')
T.st() # 체계적으로 구조화 할 수 있다
# st
property, descriptor
descriptor 는 . 행동 결정 (get set del) => 정보 은닉에 사용
class D: # callable 관점에서
def __init__(self, t):
self._t = t
@property # callable을 callable 하지 않게 변경
def t(self):
return self._t
@t.setter
def t(self, t):
self._t = t
def __getattribute__(self, x):
if x == '_t':
print('접근 안됨')
d = D(6)
d.t
vars(d)
d._t
# 접근 안됨
class E:
__a = 1 # mangling
E.__a
# AttributeError: type object 'E' has no attribute '__a'
E._E__a
# 1
Dispatch
Dispatch는어떤메소드를호출할것인가를결정하여그것을실행하도록유도해주는과정을말한다
python은singledispatch만지원한다
단, multipledispatch는3rdparty를설치하면구현가능하다
파라미터가한개면single
파라미터가두개이상이면multiple
len# type builtin_function or method / dispatch를 지원해주는 generic function
# method overloading
# 다른 프로그래밍 언어에서는 parameter가 다르면 다른 함수로 간주한다
# python에서는 method overloading을 지원하지 않는다
def x():
print('x')
def x(a):
print('a')
Single dispatch
from functools import singledispatch
@singledispatch # 데코레이터 singledispatch함수에 x함수의 기능이 추가 되는 것이다
def x(a):
print(a)
@x.register(int)
def _(a):
print('int', a)
@x.register(float)
def _(a):
print('float', a)
# 데코레이터 관점에서 봤을 때 메소드 이름이 동일해도 서로 다르게 작동할 수 있다
# 데이터 타입에 따라서 다른 메소드를 실행시킬수 있다
x('a')
# a
x(1)
# int 1
x(1.)
# float 1.0
Tensorflow predict (dispatch 예시)
import tensorflow as tf
model = tf.keras.models.Sequential([
tf.keras.layers.Dense(2, input_shape=(2,))
])
model.predict(tf.constant([[1,2]])) # numpy array type
# array([[-0.9186919, -1.4591577]], dtype=float32)
model(tf.constant([[1,2]])) # Tensor type
# <tf.Tensor: shape=(1, 2), dtype=float32, numpy=array([[-0.9186919, -1.4591577]], dtype=float32)>
a = [1,2,3]
b = iter(a) # __next__ 가 생기고 next를 사용할 수 있게 된다 (iterators)
next(b)
# 1
for i in [1,2,3]: # for문에 들어간 iterable한 객체는 iterator로 변환후 사용된다
print(i)
# 1
# 2
# 3
Lazy Evaluation
Lazy Evaluation은 계산 결과 값이 필요할 때까지 계산을 늦추는 방식이다 (next로 호출하는 순간 메모리에 올라간다) Lazy Evaluation은 속도가 느리다는 단점이 있지만 파이썬에서는 내부적으로 최적화 되어 있어 속도가 빠르다 메모리의 효율성을 위해 사용한다
a = [1,2,3]
b = iter(a) # next 호출하기 전까지 메모리에 올라가지 않는다 / next호출 시에만 실행
b[0] # iterator로 만들면 sequence한 성질을 잃는다
# 'list_iterator' object is not subscriptable
a = range(10)
b = iter(a)
next(b)
# 0
list(b)
# [1, 2, 3, 4, 5, 6, 7, 8, 9]
next(b)
# list를 통해 나머지 요소가 전부 출력 되었으므로 더 이상 남아있는 데이터가 없어서 StopIterationError가 발생한다
# StopIteration:
%%writefile a.txt
abcdefg
123123
dfskjfdskjl
# Writing a.txt
b = open('a.txt')
next(b)
# 'abcdefg \n'
b.close()
next(b) # 파일 읽기를 종료했기 때문에 더 이상 불러올 수 없다
ValueError: I/O operation on closed file.
import tensorflow as tf
tf.keras.preprocessing.image.I
[x for x in range(10)]
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
[(x, y) for x in range(5) for y in range(6,10)] # 여러 개의 데이터를 동시에 생성할 때
[(0, 6),
(0, 7),
(0, 8),
(0, 9),
(1, 6),
(1, 7),
(1, 8),
(1, 9),
(2, 6),
(2, 7),
(2, 8),
(2, 9),
(3, 6),
(3, 7),
(3, 8),
(3, 9),
(4, 6),
(4, 7),
(4, 8),
(4, 9)]
[x for x in range(10) if x%2 ==0]
# [0, 2, 4, 6, 8]
[x+1 for x in range(5)] # 기존에 있는 데이터를 변경시킬 때
Accumlation pattern
# 초기 값에서 값을 누적하며 저장하는 방식
temp = 0
for i in range(1, 11):
temp += 1
temp
# 10
temp = []
for i in range(10):
temp.append(i)
temp
# [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
import time
from functools import wraps
def timeit(fn):
@wraps(fn)
def inner(*args, **kwargs):
start_time = time.time()
retval = fn(*args, **kwargs)
duration = time.time() - start_time
print("%s : %2.2f sec" % (fn.__name__, duration))
return retval
return inner
@timeit
def acc(n):
temp = 0
for i in range(n):
temp += i
return temp
acc(100000000)
# acc : 6.47 sec
# 4999999950000000
@timeit
def acc2(n):
temp = 0
temp = [temp + x for x in range(n)]
return temp
# SyntaxError: invalid syntax
acc2(100000000)
# acc2 : 8.64 sec
# 4999999950000000
Python를활용한직업군
1.Automation
2.Data(big, ai)
3.Web
Data를다루는사람들의관점에서는comprehension은잘쓰지않는다.
왜냐하면comprehension은메모리에한꺼번에올리기때문에Bigdata에서는적합하지않다.
함수의 특징
1.return이반드시있어야한다 (return은하나이다)
-python에서는return을생략하면None을반환하도록되어있다
2.함수안에또다른함수를선언할수있다
3.Global, Local
-함수안에없는값을return하게될경우가까운Global식별자를return
-Global식별자이름하게되면접근, 수정이가능하다
-함수밖에서함수안의식별자에접근, 수정이불가능하다
First class function
함수를 값으로 사용할 수 있다
a = print
a('function')
# function
b = lambda x: x + 1
b(4)
# 5
# 일반적인 재귀용법
def sumrange(m, n):
if m <= n:
return m + sumrange(m+1, n)
else:
return 0
sumrange(1,960)
# 461280
# 꼬리 재귀용법
def sumrange(m, n):
def loop(m, total):
if m <= n:
return loop(m+1, m+total)
else:
return total
return loop(m, 0)
sumrange(1,960)
# 461280
Map & Filter & Reduce (Higher order functioin)
Map
iterable에있는모든요소에function을적용하여그결과를반환한다
map을사용하면lazyevaluation로진행해서메모리를크게절약할수있다
연산결과는mapiterator객체로리턴한다
-Deeplearning/Bigdata에서주로사용한다
def a(x):
return x + 1
list(map(a,[1,2,3]))
# [2, 3, 4]
a = map(lambda x : x+1, [1,2,3,4]) # 로직에 집중할 수 있다는 장점이 있다
'__next__' in dir(a)
# True
list(a)
# [2, 3, 4, 5]
import seaborn as sns
tips = sns.load_dataset('tips')
tips.tip.apply(lambda x:x+1) # 많은 데이터를 한번에 전처리 할때 자주 쓰인다
0 2.01
1 2.66
2 4.50
3 4.31
4 4.61
...
239 6.92
240 3.00
241 3.00
242 2.75
243 4.00
Name: tip, Length: 244, dtype: float64
from functools import reduce
reduce(lambda x,y:x+y,[1,2,3,4,5])
# 15
import tensorflow as tf
tf.reduce_mean([1,2,3,]) # multiprocessin 기법을 사용할 수 있기 때문에 속도를 향상시킬수 있다
<tf.Tensor: shape=(), dtype=int32, numpy=2>
def x(a): # 여기서 함수는 x
return a