728x90
반응형

합성, 결합

composite pattern은 남의 클래스를 내가 가지고, 일을 함 

여러 instance를 하나의 명령어로 할 수 있고, 유지보수에 좋다.

또 이게 상속을 대신할 수 있음

잘안쓰는 것은 어려워서지 아예 안쓴다는 것이 아님

그래서 composite을 이용해서 상속을 대신하는 기법

 

내 클래스에 남의 instance를 가져올수있음

inheritance를 대신 씀, 활용 중 하나다

class A :
	pass
    
class C :
	pass

class B :
	def __init__(self) :
    	self.a = A()
		self.c = B();
        
	def multi(self) :
    	return self.a * self.b;
        # 이렇게 하면 상속

composite은 매우 밀접하게 연결짓는 것이라 유지보수면에서 힘든 면이 있다.

예를 들어 상속한 어미 클래스가 바뀌면 상속된 자식 클래스가 다 바뀌어버리는 경우가 있다.

 

그래서 느슨한 연결을 하는데 어떤 instance의 변수를 타 instance의 return 값으로 줘버리면

상속을 하지 않았지만 똑같이 만들 수 있다.

overiding 한다 하면

 

다른 composition 방식으로 __getattr__을 쓰면 된다.

만약 참조가 안되는 instance나 변수를 불러내면 attribute error가 나옴 

그런데 __getattr__를 쓰면 try : attribute error 로 받을 수 없는 값을 처리해줄 수 있다.

거기에 return getattr(다른 class)로 attributor를 이름으로, 지정된 값을 대신 불러다 줌

 

한편 _ 이름 규칙

1. 외부로 import 안됨, 내부 사용 

2. 국제화(internalization) 사용

3. name_이면 numpy 등 python과 충돌 방지용 

4. for i, _ in enumerate([1,2,3]) : 처럼 쓰지 않을 때 관례처럼

5. __name mangling private내부적으로 사용

6. _ : interactive last result : _이전의 마지막 out을 가져옴

7. __x__ : double underbar (dundu / magic method)

* sklearn은 __all__ = [a] *할 때 지정할 수 있음 => 사용자 편하라고 

혹은 __slot__으로 접근방지할 수있음

 

 

import sys

sys.path로 경로 순서대로 지정됨 확인 필요

 

 

getattribute => 아래와 같이 값이 있으면 값을 조작하는 것

setter 값을 선언 g.a = 1

getter 값을 봔환 g.a

deleter 값을 봔환 del g.a

 

'''

descriptor는 값을 사용못하게 하거나 숨기거나 할 때 사용

else [if else, for/while else, try]

from [import, raise, yield]

as [import, except, with]

import antigravity

import this

42

'''

# descriptor 만드는 3가지 방법

1. composite 방법

만들어진 class의 내용을 외부에서 조작할 수 있음

2. name = property(instance, instance) 지정할수있음

3. @property

@name.setter  / deleter로 지정할수있음

단순한 데이터를 처리할때는 = 로 할당할 수 있지만, =로 해야할게 많음

내부적으로 처리해야하는데 descriptor로 처리할 수 있다.

 

# meta class ★ class의 class

기능이 변경되면 meta class임

프레임워크 개발, 데이터를 조작할때 씀

class를 무제한으로 만들수있는데 1개의 instance만 만들수있게 class 기능 변경

type(1) => int

type(int) => type

meta class를 상속해서 meta class를 만듬

 

없으면 super().__call__()로 만들고, 있으면 기존의 instance를 return 해라 => 클래스의 기능을 변경함

class 지정해놓고 class 객체로 변수 선언하면 같은 값임

id()으로 확인하면 메모리 지정이 동일함

 

# abstract

공통된 규칙을 따르고 차이점만 새로 구현한다.

tensorflow가 a기능 제공하고 b,c 규칙을 따라야 한다하면

abstract 규칙을 공개해서 이거 따라야만 한다.

예를들어 from sklearn.naive_bayes import something이고

naive_bayes의 파생형들이 있다면, NB의 기본 규칙이 있고, 파생형들을 만들고 규칙을 공개하면 다른 사람들이 어디서든지 보고 따라할 수 있음 => 파생형이지만 쓰고 싶으면 행동의 규칙을 따라야 한다.

class ss(tf.keras.utils.sequence) :

     pass

dir(ss) => 동일한 기능이 나옴

print(inspect.getsource(기능))

=> abstractmethod있으면 규칙 따라야 함.

 

class A(metaclass = NBmeta) :

     @abstractmethod   => 제약 만듬

     def a(self) :            => 규칙

          pass

class B(A) :

     def a(self) :            => 규칙을 똑같이 만듬

         print('a')

b = B()  # notimplement error X

 

python에서도 이게 너무 어려우니깐 사람들 쓰라고 ABC쓰면 metaclass = 와 기능이 같음

ABC는 추상클래스이다.

 

 

# 상속 체계 확인

dir() 에 안보여도 metaclass, type안에 해봐서 mro를 쓸 수 있다.

.mro()

.__mro__

부모객체 확인 .__bases__

=> functional

마찬가지로 dir에서 확인할 수 없지만 metaclass에 정의되어있어서 가능 two track

 

# class 기능변경가능 => metaclass

# . descriptor

# 모든 연산자 기능 변경가능 => operator overloading

모든 연산자는 그에 대응하는 dundu(magic / special)가 있음

모든 연산자는 그에 대응하는 function이 있음 <- 함수형 프로그래밍 지원

3+4,

함수로하면 from operator import add, sub, mul

add(3, 4)

함수를 사용하는 이유는 인간은 연산자가 명확하지만 기능을 바뀔수가 있어 컴퓨터는 함수형이 의미를 명확하게 전달함

=> layers 도 함수형 연산함, 목적에 맞게 변형된 함수를 만들면 최적화에 좋다

 

 

 

 

 

 

 

 

 

 

 

반응형
728x90
반응형

Python

Python에서는 모든 것이 객체다

함수도 객체 파일도 객체 가능한 모든 것들이 객체

Object (type)의 내부 정보를 알고 싶을 때

1. type 2. dir 3. id 4. vars

이름/식별자를 불렀을 때 내부적으로 작동하는 것들

1. REPR 2. STR

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()
 import itertools 
 dir(itertools) #__package__ / dir은 객체의 모든 attribute를 조회 함수이다 
 ['__doc__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 '_grouper',
 '_tee',
 '_tee_dataobject',
 'accumulate',
 'chain',
 'combinations',
 'combinations_with_replacement',
 'compress',
 'count',
 'cycle',
 'dropwhile',
 'filterfalse',
 'groupby',
 'islice',
 'permutations',
 'product',
 'repeat',
 'starmap',
 'takewhile',
 'tee',
 'zip_longest']
type(itertools) # module이라는 데이터 타입 
# module

id(itertools)
# 140663255600208

dir(int) # 클래스도 객체 이다 
['__abs__',
 '__add__',
 '__and__',
 '__bool__',
 '__ceil__',
 '__class__',
 '__delattr__',
 '__dir__',
 '__divmod__',
 '__doc__',
 '__eq__',
 '__float__',
 '__floor__',
 '__floordiv__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__getnewargs__',
 '__gt__',
 '__hash__',
 '__index__',
 '__init__',
 '__init_subclass__',
 '__int__',
 '__invert__',
 '__le__',
 '__lshift__',
 '__lt__',
 '__mod__',
 '__mul__',
 '__ne__',
 '__neg__',
 '__new__',
 '__or__',
 '__pos__',
 '__pow__',
 '__radd__',
 '__rand__',
 '__rdivmod__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__rfloordiv__',
 '__rlshift__',
 '__rmod__',
 '__rmul__',
 '__ror__',
 '__round__',
 '__rpow__',
 '__rrshift__',
 '__rshift__',
 '__rsub__',
 '__rtruediv__',
 '__rxor__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__sub__',
 '__subclasshook__',
 '__truediv__',
 '__trunc__',
 '__xor__',
 'bit_length',
 'conjugate',
 'denominator',
 'from_bytes',
 'imag',
 'numerator',
 'real',
 'to_bytes']

객체 지향

[객체지향의 4가지 특성]

1. 캡슐화

- 실제 구현내용을 감추는

- 재활용 가능

- 파이썬에서는 기본적으로 외부에서 클래스 접근 가능

- descriptor로 클래스 접근을 막을 수도 있다

2. 추상화

- 필요로 하는 속성이나 행동을 추출하는 작업

- 구체적인것과 추상적인 것을 분리시킨다

- 특징을 뽑아 객체로 구현

3. 상속

- 추상화의 반대

- 추상화한 클래스를 물려받아 구체적으로 원하는 대로 바꾸어 사용 가능

4. 다형성

- 다양한 결과를 있다

Subclass(Inheritance)

'장점'

- 부모 클래스의 기능을 재사용

-부모의 기능을 그대로 사용한다

- 부모의 기능을 일부만 사용한다

- 부모에게 없는 기능을 추가 가능하다

'단점'

- 부모가 바뀌면 같이 바뀐다

# composition 방식으로 inheritance 방식 대체 가능하다

duck typing, composition으로 사용해도 상속받은 것과 유사하게 사용 가능하다

 

class A:
  a = 1

class B(A): # Delegat(위임/대리자)
  pass 
  
id(A.a) == id(B.a)
# True

A.a is B.a # B에 없으면 부모의 것을 불러오기 때문에 같아진다 
# True

Python의 모든 객체는 object로 부터 상속 받는다

class A(object, metaclass=type):
  pass
  
dir(A)
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']
dir(A.__class__)
['__abstractmethods__',
 '__base__',
 '__bases__',
 '__basicsize__',
 '__call__',
 '__class__',
 '__delattr__',
 '__dict__',
 '__dictoffset__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__flags__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__instancecheck__',
 '__itemsize__',
 '__le__',
 '__lt__',
 '__module__',
 '__mro__',
 '__name__',
 '__ne__',
 '__new__',
 '__prepare__',
 '__qualname__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasscheck__',
 '__subclasses__',
 '__subclasshook__',
 '__text_signature__',
 '__weakrefoffset__',
 'mro']

다중 상속

다중 상속이란 2개 이상 클래스를 상속하는 것을 말한다

python은 다중 상속을 지원하는 언어이다

다중 상속은 편한 기능이지만 다이아몬드 문제가 발생할 수 있다

단일 상속인 프로그래밍 언어들이 많음(Java)

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:
    pass

class B(A):
    pass

class C(A):
    pass

class D(B, C):
    pass
    
D.mro() # linearization / tail recursion elimination => linearization
# [__main__.D, __main__.B, __main__.C, __main__.A, object]

dir(D) # metaclass 
['__class__',
 '__delattr__',
 '__dict__',
 '__dir__',
 '__doc__',
 '__eq__',
 '__format__',
 '__ge__',
 '__getattribute__',
 '__gt__',
 '__hash__',
 '__init__',
 '__init_subclass__',
 '__le__',
 '__lt__',
 '__module__',
 '__ne__',
 '__new__',
 '__reduce__',
 '__reduce_ex__',
 '__repr__',
 '__setattr__',
 '__sizeof__',
 '__str__',
 '__subclasshook__',
 '__weakref__']

MRO (Method Resolution Order)

메소드 실행 순서를 확인하는 클래스 메소드(인스턴스로 사용 불가)

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

- def x(*b): # 인자의 갯수 제한을 두지 않는다 return b

5. parameter *

- def name(*,a,b): # keyword only *다음엔 keyword를 사용해야 한다

       return a + b # positional 방식 다음에 keyword 방식을 써야 한다

6. parameter **

- def y(**b): # Variable Keyword return b 7. argument *

- def a(*b):

      return b x = [1,2,3,4] a(*x) # argument unpacking

8. argument **

- def aa(**b):

       return b y = {'a': 1, 'b' : 2} aa(**)

9. import에서 모두

- import *

※ def t(a, *b)이런 형태를 자주 쓰는 이유

def t(*a, **b):

     print(a)

     print(b)

# parameter, argument를 정확히 맞출 필요가 없음, 인터페이스의 유연성을 위해

 

def aa(**b):
  return b

y= {'a':1,'b':2}
z= {'c':2,'a':3}
{**y, **z}
# {'a': 3, 'b': 2, 'c': 2}

 

 

반응형
728x90
반응형
!pip install multipledispatch​
xxx = 2

def x():
  xxx = 1 
  print(xxx)

x()
# 1

Python

Callable

Call: 함수를 호출 한다

Python에서 괄호('()') 붙일 있는 것은 callable 이다

, 호출 가능한 함수 또는 객체

1. function(method)

2. class

3. __call__이 정의된 object

LEGB

G: 기본적으로 사용하는 공간은 Global 공간 (global)

B: Builtin이라는 공간에 저장되어 있는 것들

E: Enclosing Function Local, 함수를 내포하는 다른 함수 영역

L: Local, 함수 정의된 지역변수

 

python 공간규칙

1. B에 없으면 G에 있는지 찾는다

2. 상위단계(B쪽으로 갈수록 상위)에서 하위단계로 접근할 없다

3. 하위단계(L쪽으로 갈수록 하위)에서 상위단계로는 접근할 있다

 

변수 찾는 우선순위 => L > E > G > B

xxx = 2

def x():
  xxx = 1 
  print(xxx)
x()
# 1
xxx = 2

def x():
  print(xxx)
x()
# 2

Closure

'함수를 중첩시키고 함수를 리턴하는 함수'. 클로저는 보통 함수를 쉽게 변형할 있는 기법이다 # Higher order function 클래스 안에 __call__이 선언되어 있으면 인스턴스가 호출가능해진다(인스턴스( )/__call__메소드가 호출된다)

사실 함수도 클래스의 객체이며 함수를 호출할 '함수이름( )' 같이 사용할 있었던 이유가 바로 __call__ 메서드가 선언되어 있기 때문이다

def clo(m):
    def inner(n):
        return m + n 
    return inner 

class A:
    def __init__(self, m):
        self.m = m
    def __call__(self, n):
        return self.m + n 
clo(4)(3)
# 7

a = A(3)
a(3)
# 6
import tensorflow as tf 
# 4개의 퍼셉트론에 tf.constant([[1,2],[3,4],[5,6]])값을 계산하는 함수 
tf.keras.layers.Dense(4)(tf.constant([[1,2],[3,4],[5,6]])) 

<tf.Tensor: shape=(3, 4), dtype=float32, numpy=
array([[ 1.57166   , -0.1644063 ,  1.6268642 , -0.73175406],
       [ 2.9206128 , -0.0049057 ,  3.2637818 , -1.5273063 ],
       [ 4.2695656 ,  0.1545949 ,  4.9006996 , -2.3228586 ]],
      dtype=float32)>
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. Instance method 2. classmethod 3. staticmethod

위임 개념은 크게 3가지로 볼 수 있다

1. instance 2. class 3. 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만 지원한다

, multiple dispatch는 3rd party를 설치하면 구현 가능하다

 

파라미터가 한개면 single

파라미터가 두개 이상이면 multiple

 

len # type builtin_function or method / dispatch를 지원해주는 generic function

len([1,2,3]) -> list 관련 메소드로 dispatch

len((1,2,3)) -> tuple 관련 메소드로 dispatch # dispatch는 tensorflow predict에 숨어있는 개념이다

Single dispatch vs Multiple dispatch

# 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)>

Multiple dispatch

!pip install multipledispatch
from multipledispatch import dispatch

@dispatch(int, int)
def add(x, y):
    return x + y

@dispatch(object, object)
def add(x, y):
    return "%s + %s" % (x, y)

add(1,2)
# 3

add('1','2')
# '1 + 2'

※ 용어 정리

__함수이름__ 이러한 형태의 메소드: dundu, magic method, speicial method, 일반적으로 프로그래밍 언어에서는 protocol이라고 한다

 

from itertools import zip_longest
x = zip([1,2,3,4],[4,5,6])
list(x)
# [(1, 4), (2, 5), (3, 6)]

y = zip_longest([1,2,3,4],[4,5,6])
list(y)
# [(1, 4), (2, 5), (3, 6), (4, None)]

 

 

 

반응형
728x90
반응형

실제 object 된 재현율recall의 변화에 따른 정밀도presion의 값을 평균한 성능수치

- IOU

- Precision Recall Curve(binary)의 면적인 Average Precision

- Confidence threshold

=> bounding box로 예측을 하였을 때 내부의 임계치, 확률값 신뢰값

=> inference time 작으면서 높은 average precision(coco ap)이면 좋다

 

정밀도는 예측한 것 중에 얼마나 맞췄는가

재현율은 실제값 중에 얼마나 맞췄는가

둘다 좋아야한다.

 

PASCAL에서는

IOU >0.5 이면 예측 성공으로 인정

 

업무에 따른 재현율과 정밀도의 상대적 중요도

- 재현율이 상대적으로 더중요한 지표인경우 실제 POSITIVE 양성인 데이터 예측을 NEGATIVE로 판단하면 큰문제가 되는 경우 => 암 진단, 금융사기 판별,

: 크든 작든 일단 실제 낌새가 있으면 건드려 봐야한다.

 

- 정밀도가 상대적으로 더중요한 지표인경우 실제 NEGATIVE 음성인 데이터 예측을 NEGATIVE로 판단하면 큰문제가 되는 경우 : 스펨메일

: 너무 건드리면 심기가 불편해짐

 

Confidence 임계값에 따른 정밀도-재현율 변화

- cs 임계값이 0.2정도일 때  후보 bbox를 만들어 낼 확률

confidence 임계값이 낮을수록 더 많은 예측 bbox를 만들게 되어 정밀도는 낮아지고 재현율은 높아짐

=> 있을법한 자신감 컷이 낮으면 '음? 이정도면 얘도 object인데?'이라서 아무거나 일단 실제값이라 해서 재현율이 높아짐, 난사를 해버리는 것이기 때문에 정밀도는 낮아진다

 

- cs 임계값이 0.8정도일 때  후보 bbox를 만들어 낼 확률

confidence 임계값이 높을수록 신중하게 예측 bbox를 만들게 되어 정밀도는 높아지고 재현율은 낮아짐

구간의 최대값을 기준으로 면적을 구하면 average precision

AP = 1/11 * ( mP(r = 0) + mP(r = 0.1) + ... + mP(r = 1) )

    = 1/11 * ( 1.0 + 1.0 + ... + 0.5 )

 

=> AP는 1개 Object에 대한 성능 수치

mAP는 여러 Object의 ap를 평균화한 것

 

COCO Challenge의 mAP

IOU를 0.5이상 고정한 PASCAL VOC와 달리 IOU를 다양한 범위로 설정하여 예측 성공 기준을 정함

IOU를 0.5부터 0.05씩 값을 증가시켜 0.95까지 해당하는 IOU별 mAP를 계산 ( step별

 

 

 

 

 

반응형

'Computer_Science > Computer Vision Guide' 카테고리의 다른 글

2-2. pascal dataset 살펴보기  (0) 2021.09.21
2-1. 주요 pretrained dataset  (0) 2021.09.21
1-8. Non Max Suppression  (0) 2021.09.17
1-7. IoU 성능평가 [colab]  (0) 2021.09.11
1-6. selective search 시각화 [colab]  (0) 2021.09.11
728x90
반응형

여러개의 bounding box가 detection되는 것은 있을만한 위치를 계속 예측하는 것이다

 

NMS는 Detected object의

max가 아닌 object를 눌러준다. suppression

 

1) detected된 bounding box별로 특정 confidence threshold 이하 bounding box는 먼저 제거( confidence score < 0.5 )

 

2) 가장 높은 confidence score를 가진 box순으로 내림차순 정렬하고 아래 로직을 모든 box에 순차적으로 적용

- 높은 confidence score인 box와 겹치는 다른 box를 모두 조사하여 IOU가 특정 threshold이상인 box를 모두 제거(IOU Threshold > 0.4)

 

3) 남아있는 box만 선택

 

=> confidence score가 높을수록, IOU threshhold가 낮을수록 box가 제거됨

 

 

 

 

 

 

 

 

 

 

 

반응형
728x90
반응형

Python

#목표: 텐서플로우를 활용하여 직접 구현하는 것을 목표로 한다

명령형 프로그래밍 vs 선언형 프로그래밍

명령형 프로그래밍

프로그래밍의 상태와 상태를 변경시키는 구문의 관점에서 연산을 설명하는 프로그래밍 패러다임의 일종이다. 명령형 프로그램은 컴퓨터가 수행할 명령들을 순서대로 써 놓은 것이다.

ex) 절차지향, 객체지향 프로그래밍

 

선언형 프로그래밍

선언형 프로그래밍은 무엇인가를 작업하기 위한 방법을 정의한다.

Python에서는 선언 대신 정의라는 말을 쓰고 어떤 기능을 미리 만들어 놓고 조합하여 결과를 내는 방식을 따른다

ex) 함수형 프로그래밍

 

함수형 프로그래밍의 특징

1. 코드가 간결해진다

- 내부 구조를 몰라도 input, output만 알면 사용 가능

2. 수학적으로 증명이 가능하다

3. for, while문을 자제하고 iter 사용한다

4. 수학적 증명이 필요하기 때문에 구현이 어렵다

- , python에서는 multi paradiam이기 때문에 적절히 혼용 가능

5. 디버깅, 테스트가 용이하다

6. 모듈성, 결합성이 있다.

7. mutable은 처리하지 않는다

8. vectorization 연산을 지원한다

 

# tensorflow에서 함수형 프로그래밍은 형식적 증명가능성이라는 표현이 나온다

# 이때 형식적 증명가능성이란 이론적으로는 가능하지만 실제로는 안될 가능성도 있다는 의미 이다

# 함수형 프로그래밍은 함수 자체가 수학함수로 만들기 때문에 증명이 가능하고 이론과 구현의 간극을 줄여주기 때문에 데이터 처리에 관한 분야에 적합하다고 할 수 있다

 

┌─ Mutable # 추가, 삭제가 가능한 경우 / 특징 : 메모리 번지 안바뀜, 재할당할 필요없음

└─ Immutable # 추가, 삭제가 불가능한 경우 / 특징 : 재할당으로 메모리 번지 바뀜

Container

┌─ Homogeneous # 요소들이 서로 같은 타입인 경우

└─ Heterogeneous # 요소들이 서로 다른 타입이 가능한 경우

(요소가 1 이상인 데이터 구조)

Sequence

┌─ Indexing # 요소를 하나씩 출력

└─ Slicing # 요소를 한번에 여러개 출력

(순서가 있음)

Lookup

┌─ Mapping hash # key값과 value를 갖는 Dictinary가 갖는 특징

└─ set # 순서가 없는 고유한 원소들의 집합

(key값으로 이루어진 데이터 구조)

 

Container (container이면 전부다 iterable하다 , 순서대로 하나씩 뽑아 있는 데이터 타입)

- Homogeneous ex) ndarray

- Heterogeneous ex) list

- sequence ex) list

- non sequence ex) set

- mutable ex) list

- immutable ex) tuple

 

x = [1,2,3]
x = iter(x)
'__iter__' in dir(x) # __iter__가 있기 때문에 iterable하다는 의미이다 
# True

'__next__' in dir(x) #__next__가 있다면 iterator라는 의미이다
# True

from collections.abc import Iterable, Iterator
set(dir(Iterator)) - set(dir(Iterable)) 
# {'__next__'}

set(dir(Iterable)) - set(dir(Iterator))
# set()

Iterator

Iterator는 데이터 스트림을 표현하는 객체, next()메소드를 사용하여 다음 요소를 가져온다

Iterator를 활용하여 Lazy Evaluation 방법을 사용한다

 

Lazy Evaluation

- 계산 결과 값이 필요할 때까지 계산을 늦추는 방식이다

- next 실행하는 순간 연산이 시작되고 호출한 값만 메모리에 할당되므로 메모리를 효율적으로 사용할 있게된다

- 내부적으로 최적화 되어 있어 속도가 빠르다

- 방대한 데이터를 다룰때 효율적인 처리를 있다

 

주의 iterator는 scope를 초과하면 StopIteration 에러가 뜬다

 

모든 iterable은 iterator로 만들 수 있다

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:

 

Generator

Iterator를 생성해주는 Function, 그리고 일반 함수와 비슷해 보이지만 Generator는 yield 포함한다는 점에서 차이가 있다

Iterator와 generator의 차이점은 generator에서는 immutable 데이터 타입으로만 생성 가능한 점이다

특히 lazy evaluation 기법을 사용하기 때문에 메모리 사용이 효율적이고 속도가 빠르다는 장점이 있다

가지 방법으로 만들 있다

1. generator 표현식(tuple)

2, yield

주의 generator는 scope를 초과하면 StopIteration 에러가 뜬다

Tuple 방식

a = (x for x in range(10)) # 메모리 번지가 표시될 경우 iterator 또는 generator인 경우가 많다 
a
# <generator object <genexpr> at 0x7fad65698cd0>

Yield 방식

def x():
    yield 1
    yield 2
y = x()
next(y)
# 1

next(y)
# 2
%%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.

Comprehension

여러 개의 데이터를 동시에 만들거나 변화시킬 수 있는 구문이다

Iterable한 객체를 생성하기 위한 방법

[종류] 1. List 2. Set 3. Dictionary

 

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

Higher order function

함수를 리턴값으로 쓰고, 함수를 인자로 쓰는 함수

First class function VS Higher order function

프로그래밍 언어가 first class function을 가진다는 말은 해당 프로그래밍 언어에서 함수를 값으로 취급 한다는 의미이다 Higher order function은 하나 이상의 함수를 인자로 받을 있고 함수를 반환 있는 함수를 의미한다

 

Higher order(고차)라는 개념은 수학적 의미의 함수와 같이 일반적으로 함수에 적용할 있고,

First class(일급)라는 개념은 프로그래밍 언어의 기능에만 연관이 있다

수학적 의미가 아닌 프로그래밍 관점에서 first class function을 지원하거나 지원하지 않거나 라고 말하는 것이 자연스러운 표현이다

 

따라서 Higher order function을 지원하지 않지만 first class function을 가진 언어 또는 higher order function은 지원하지만 first class function을 갖지 않는 언어가 존재할 있는가 싶을 만큼 둘은 밀접한 관련이 있다

 

Recursion

함수가 자기 자신을 호출하는 방법을 재귀라고 한다

※ 함수형 패러다임에서는 tail recursion elimination 기법을 제공하지만, python에서는 지원하지 않는다

 

def rr(x):
    if x==1:
        return 1
    return rr(x-1)*2
rr(6)
# 32

def fibb(n):
  x = 0
  1 =

Tail recursion elimination

Recursion(재귀)용법을 사용하면 함수가 자기 자신을 호출해야하기 때문에 호출할때 마다 중간 값을 저장하기 위한 메모리가 필요하고 호출 횟수가 늘어날 수록 실행 시간이 비약적으로 늘어나는 단점이 있다

그래서 이렇한 단점을 보완한 것이 바로 tail recursion elimination이다

 

반적으로 재귀 호출시 마지막(꼬리부분)에서 호출하는 경우를 반복문으로 바꾸어 결과를 누적시킴으로써 메모리 공간상의 효율도 챙기고 실행 시간도 많이 줄일수 있게 된다

# 일반적인 재귀용법 
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 사용하면 lazy evaluation로 진행해서 메모리를 크게 절약할 있다

연산 결과는 map iterator 객체로 리턴한다

- Deep learning / Big data에서 주로 사용한다

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

Filter

predicate function 함수

iterable객체에 있는 요소중 조건에 맞게 True or False 되돌려 주는 함수

전체 데이터에서 필요한 데이터만 뽑아내는 함수

 

def b(x):
    return x > 3

list(filter(b,[1,2,3,4,5,6]))
# [4, 5, 6]

x = filter(lambda x:x>2, [1,2,3,4]) #필요한 데이터만 뽑아낸다 
list(x) 
# [3, 4]

Reduce

iterable객체의 여러개 값을 하나의 값으로 축약하여 표현할 사용한다

machine learning, deeplearning에서 자주쓰는 함수

통계에서 전체 값중에서 하나의 대표 값을 표현하는 경우가 많기 때문에 reduce를 자주 사용한다

 

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

 

파이썬 코드 스타일

PEP8 (Python Enhance Proposal)

파이썬은 PEP8은 파이썬 개선 제안서, 파이썬 코드를 어떻게 구상할 알려주는 스타일 가이드이다

PEP8 참고 문서: https://www.python.org/dev/peps/

# 귀도 반로섬(Guido van Rossum) #자애로운 종신 독재자

식별자 표기법 3가지

1. snake - 단어마다

_ (underscore) 붙여 이어나가는 표기법이다

- 모듈은 표기법을 권장한다

- 내장 함수도 보통 스네이크 표기법을 따른다

- ex) hello_world

2. camel

- 문자는 소문자로 표기하고, 다음 단어의 시작은 대문자로 표기한다

- 보통 파이썬에서는 카멜방식 채택

- 함수명은 표기법을 권장한다. ,소문자 + underscore를 쓰기도 한다

- ex) helloWorld

3. pascal(caps word)

- 문자를 대문자로 표기하고, 다음 단어의 시작도 대문자로 표기한다

- 클래스명은 표기법을 권장한다. , 이미 만들어져 있는 클래스는 소문자로 시작한다

- ex) HelloWorld

## 원활한 의사소통을 위해 pep8을 활용한다

Lambda

대입문과 정의문의 공동 목표는 재사용이었지만,

재사용할 필요가 없는 function이 필요할 사용하는 것이 lambda이다

- lambda 함수 이름이 존재하지 않는다 (익명함수)

- lambda 식처럼 사용 가능하다 (함수식)

- lambda 함수 호출시 사라지기 때문에 stack영역에 저장된다

반응형
728x90
반응형

# 성능 평가

Metrics - IOU (Intersection over Union)

=> 모델이 예측한 결과와 실측(Gound Truth) box가 얼마나 정확하게 겹치는가를 나타내는 지표

 

생성된 전체 면적 중에 예측이 맞은 면적을 구하는 것이기 때문에 1에 가까울수록 좋다

pascal voc 0.5, ms coco competition 0.5 ~ 0.95

 

# 입력인자로 후보 박스와 실제 박스를 받아서 IOU를 계산하는 함수 생성

import numpy as np 

def compute_iou(cand_box, gt_box):
    # cand_box ss에서 추천한 box, gt_box 실제 box
    # Calculate intersection areas : 쉽게 구하는 방법
    x1 = np.maximum(cand_box[0], gt_box[0])
    y1 = np.maximum(cand_box[1], gt_box[1])
    x2 = np.minimum(cand_box[2], gt_box[2])
    y2 = np.minimum(cand_box[3], gt_box[3])
    
    intersection = np.maximum(x2 - x1, 0) * np.maximum(y2 - y1, 0)
    
    cand_box_area = (cand_box[2] - cand_box[0]) * (cand_box[3] - cand_box[1])
    gt_box_area = (gt_box[2] - gt_box[0]) * (gt_box[3] - gt_box[1])
    union = cand_box_area + gt_box_area - intersection
    
    iou = intersection / union
    return iou

# gt_box 생성

import cv2
import matplotlib.pyplot as plt
%matplotlib inline

# 실제 box(Ground Truth)의 좌표를 아래와 같다고 가정. 
gt_box = [60, 15, 320, 420]


img = cv2.imread('./data/audrey01.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)

red = (255, 0 , 0)
img_rgb = cv2.rectangle(img_rgb, (gt_box[0], gt_box[1]), (gt_box[2], gt_box[3]), color=red, thickness=2)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb)
plt.show()

사람이 입력한 gt_box 정보

# 예측하기

import selectivesearch

#selectivesearch.selective_search()는 이미지의 Region Proposal정보를 반환 
img = cv2.imread('./data/audrey01.jpg')
img_rgb2 = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
_, regions = selectivesearch.selective_search(img_rgb2, scale=100, min_size=2000)

print(type(regions), len(regions))
#<class 'list'> 41

# 평가하기

cand_rects = [cand['rect'] for cand in regions]
for index, cand_box in enumerate(cand_rects):
    # 전처리
    cand_box = list(cand_box)
    cand_box[2] += cand_box[0]
    cand_box[3] += cand_box[1]
    
    # 평가하기
    iou = compute_iou(cand_box, gt_box)
    print('index:', index, "iou:", iou)
    
# index: 0 iou: 0.06157293686705451
# index: 1 iou: 0.07156308851224105
# index: 2 iou: 0.2033654637255666
# index: 3 iou: 0.04298195631528965
# index: 4 iou: 0.14541310541310543
# index: 5 iou: 0.10112060778727446
# index: 6 iou: 0.11806905615946989
# index: 7 iou: 0.1420163334272036
# index: 8 iou: 0.035204259342190375
# index: 9 iou: 0.004256894317971497
# index: 10 iou: 0.5184766640298338
# index: 11 iou: 0.04465579710144928
# index: 12 iou: 0.0853656220322887
# index: 13 iou: 0.015722240419259743
# index: 14 iou: 0.037833068643021
# index: 15 iou: 0.22523535071077264
# index: 16 iou: 0.0
# index: 17 iou: 0.053941120607787274
# index: 18 iou: 0.05154006626579948
# index: 19 iou: 0.05660327592118798
# index: 20 iou: 0.01165009904393209
# index: 21 iou: 0.18588082901554404
# index: 22 iou: 0.19555555555555557
# index: 23 iou: 0.5409250175192712
# index: 24 iou: 0.205679012345679
# index: 25 iou: 0.042245111210628454
# index: 26 iou: 0.34848824374009246
# index: 27 iou: 0.18588082901554404
# index: 28 iou: 0.10952135872362326
# index: 29 iou: 0.29560078245307364
# index: 30 iou: 0.045470015655843715
# index: 31 iou: 0.3126506582607083
# index: 32 iou: 0.4934902582553282
# index: 33 iou: 0.5490037131949166
# index: 34 iou: 0.1018867924528302
# index: 35 iou: 0.31513409961685823
# index: 36 iou: 0.3423913043478261
# index: 37 iou: 0.6341234282410753
# index: 38 iou: 0.6270619201314865
# index: 39 iou: 0.6270619201314865
# index: 40 iou: 0.6270619201314865

 

 

# 수정 반영하기

# cand['size'] > 5000인 box들만
cand_rects = [cand['rect'] for cand in regions if cand['size'] > 5000]
cand_rects.sort()

# 다시로드
img = cv2.imread('./data/audrey01.jpg')
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
print('img shape:', img.shape)

# 추천 box
green_rgb = (125, 255, 51)
cand_rects = [cand['rect'] for cand in regions if cand['size'] > 3000]
gt_box = [60, 15, 320, 420]
img_rgb = cv2.rectangle(img_rgb, (gt_box[0], gt_box[1]), (gt_box[2], gt_box[3]), color=red, thickness=2)

for index, cand_box in enumerate(cand_rects):
    # 전처리
    cand_box = list(cand_box)
    cand_box[2] += cand_box[0]
    cand_box[3] += cand_box[1]
    # 평가
    iou = compute_iou(cand_box, gt_box)
    
    # 성능이 좋은 box만
    if iou > 0.5:
        print('index:', index, "iou:", iou, 'rectangle:',(cand_box[0], cand_box[1], cand_box[2], cand_box[3]) )
        cv2.rectangle(img_rgb, (cand_box[0], cand_box[1]), (cand_box[2], cand_box[3]), color=green_rgb, thickness=1)
        text = "{}: {:.2f}".format(index, iou)
        cv2.putText(img_rgb, text, (cand_box[0]+ 100, cand_box[1]+10), cv2.FONT_HERSHEY_SIMPLEX, 0.4, color=green_rgb, thickness=1)
    
plt.figure(figsize=(12, 12))
plt.imshow(img_rgb)
plt.show()

# img shape: (450, 375, 3)
# index: 8 iou: 0.5184766640298338 rectangle: (72, 171, 324, 393)
# index: 18 iou: 0.5409250175192712 rectangle: (72, 171, 326, 449)
# index: 28 iou: 0.5490037131949166 rectangle: (0, 97, 374, 449)
# index: 32 iou: 0.6341234282410753 rectangle: (0, 0, 374, 444)
# index: 33 iou: 0.6270619201314865 rectangle: (0, 0, 374, 449)
# index: 34 iou: 0.6270619201314865 rectangle: (0, 0, 374, 449)
# index: 35 iou: 0.6270619201314865 rectangle: (0, 0, 374, 449)

반응형
728x90
반응형

# selective search 설치

!pip install selectivesearch0

 

# 오드리 햅번 이미지 가져오기

!mkdir /content/data
!wget -O /content/data/audrey01.jpg https://raw.githubusercontent.com/chulminkw/DLCV/master/data/image/audrey01.jpg

# 데이터 로드

import selectivesearch
import cv2
import matplotlib.pyplot as plt
import os
%matplotlib inline

### 오드리헵번 이미지를 cv2로 로드하고 matplotlib으로 시각화 
img = cv2.imread('./data/audrey01.jpg')
# cvtColor : RGB로 바꿈
img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
print('img shape:', img.shape)

plt.figure(figsize=(8, 8))
plt.imshow(img_rgb)
plt.show()

사람 눈엔 직관적으로 사진이지만, 컴퓨터에겐 그래프이다.

# selective_search에 사진을 넣고 region 탐색

import selectivesearch 

#selectivesearch.selective_search()는 이미지의 Region Proposal정보를 반환 
# selectivesearch.selective_search(img file, scale=오브젝트 추천 크기, min_size=오브젝트 최소 크기)
_, regions = selectivesearch.selective_search(img_rgb, scale=100, min_size=2000)
# 튜플 생략, regions


print(type(regions), len(regions))
# <class 'list'> 41
# 41개의 공간

#반환된 Region Proposal(후보 영역)에 대한 정보 보기.

반환된 regions 변수는 리스트 타입으로 세부 원소로 딕셔너리를 가지고 있음. 개별 딕셔너리내 KEY값별 의미

- rect 키값은 x,y 시작 좌표와 너비, 높이 값을 가지며 이 값이 Detected Object 후보를 나타내는 Bounding box 정보임.

- rect: 시작점, 시작점, widths, heights

- size는 segment로 select된 Object의 크기

- labels는 해당 rect로 지정된 Bounding Box내에 있는 오브젝트들의 고유 ID

- [7, 11] 7번과 11번을 합쳐야겠다

- 아래로 내려갈 수록 너비와 높이 값이 큰 Bounding box이며 하나의 Bounding box에 여러개의 오브젝트가 있을 확률이 커짐.

regions
[{'labels': [0.0], 'rect': (0, 0, 107, 167), 'size': 11166},
 {'labels': [1.0], 'rect': (15, 0, 129, 110), 'size': 8771},
 {'labels': [2.0], 'rect': (121, 0, 253, 133), 'size': 17442},
 {'labels': [3.0], 'rect': (134, 17, 73, 62), 'size': 2713},
 {'labels': [4.0], 'rect': (166, 23, 87, 176), 'size': 8639},
 {'labels': [5.0], 'rect': (136, 53, 88, 121), 'size': 4617},
 {'labels': [6.0], 'rect': (232, 79, 117, 147), 'size': 7701},
 {'labels': [7.0], 'rect': (50, 91, 133, 123), 'size': 7042},
 {'labels': [8.0], 'rect': (305, 97, 69, 283), 'size': 11373},
 {'labels': [9.0], 'rect': (0, 161, 70, 46), 'size': 2363},
 {'labels': [10.0], 'rect': (72, 171, 252, 222), 'size': 34467},
 {'labels': [11.0], 'rect': (0, 181, 118, 85), 'size': 5270},
 {'labels': [12.0], 'rect': (106, 210, 89, 101), 'size': 2868},
 {'labels': [13.0], 'rect': (302, 228, 66, 96), 'size': 2531},
 {'labels': [14.0], 'rect': (0, 253, 92, 134), 'size': 7207},
 {'labels': [15.0], 'rect': (153, 270, 173, 179), 'size': 10360},
 {'labels': [16.0], 'rect': (0, 305, 47, 139), 'size': 4994},
 {'labels': [17.0], 'rect': (104, 312, 80, 71), 'size': 3595},
 {'labels': [18.0], 'rect': (84, 360, 91, 67), 'size': 2762},
 {'labels': [19.0], 'rect': (0, 362, 171, 87), 'size': 7705},
 {'labels': [20.0], 'rect': (297, 364, 77, 85), 'size': 5164},
 {'labels': [7.0, 11.0], 'rect': (0, 91, 183, 175), 'size': 12312},
 {'labels': [4.0, 5.0], 'rect': (136, 23, 117, 176), 'size': 13256},
 {'labels': [10.0, 15.0], 'rect': (72, 171, 254, 278), 'size': 44827},
 {'labels': [4.0, 5.0, 3.0], 'rect': (134, 17, 119, 182), 'size': 15969},
 {'labels': [8.0, 13.0], 'rect': (302, 97, 72, 283), 'size': 13904},
 {'labels': [2.0, 6.0], 'rect': (121, 0, 253, 226), 'size': 25143},
 {'labels': [7.0, 11.0, 9.0], 'rect': (0, 91, 183, 175), 'size': 14675},
 {'labels': [0.0, 1.0], 'rect': (0, 0, 144, 167), 'size': 19937},
 {'labels': [0.0, 1.0, 4.0, 5.0, 3.0], 'rect': (0, 0, 253, 199), 'size': 35906},
 {'labels': [14.0, 16.0], 'rect': (0, 253, 92, 191), 'size': 12201},
 {'labels': [14.0, 16.0, 7.0, 11.0, 9.0], 'rect': (0, 91, 183, 353), 'size': 26876},
 {'labels': [10.0, 15.0, 19.0], 'rect': (0, 171, 326, 278), 'size': 52532},
 {'labels': [10.0, 15.0, 19.0, 8.0, 13.0], 'rect': (0, 97, 374, 352), 'size': 66436},
 {'labels': [17.0, 18.0], 'rect': (84, 312, 100, 115), 'size': 6357},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0], 'rect': (0, 91, 184, 353), 'size': 33233},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0, 12.0], 'rect': (0, 91, 195, 353), 'size': 36101},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0, 12.0, 2.0, 6.0], 'rect': (0, 0, 374, 444), 'size': 61244},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0, 12.0, 2.0, 6.0, 10.0, 15.0, 19.0, 8.0, 13.0],
  'rect': (0, 0, 374, 449), 'size': 127680},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0, 12.0, 2.0, 6.0, 10.0, 15.0, 19.0, 8.0, 13.0, 20.0],
  'rect': (0, 0, 374, 449), 'size': 132844},
 {'labels': [17.0, 18.0, 14.0, 16.0, 7.0, 11.0, 9.0, 12.0, 2.0, 6.0, 10.0, 15.0, 19.0, 8.0, 13.0, 20.0, 0.0, 1.0, 4.0, 5.0, 3.0],
  'rect': (0, 0, 374, 449), 'size': 168750}]

# bounding box를 시각화 하기

# opencv의 rectangle()을 이용하여 시각화
# rectangle()은 이미지와 좌상단 좌표, 우하단 좌표, box컬러색, 두께등을 인자로 입력하면 원본 이미지에 box를 그려줌. 

# bounding box 섹상 지정
green_rgb = (125, 255, 51)
# 노트북형태라 메모리 꼬일것 때문에 copy
img_rgb_copy = img_rgb.copy()
for rect in cand_rects:
    
    # tuple bounding box 좌표 찍기
    left = rect[0]
    top = rect[1]
    # rect[2], rect[3]은 절대적인 수치(너비와 높이)이므로 우하단 좌표를 구하기 위해 좌상단 좌표에 각각을 더함. 
    right = left + rect[2]
    bottom = top + rect[3]

    # bounding box 표시하기
    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=green_rgb, thickness=2)
    
plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy)
plt.show()

너무 많은 bounding box

# bounding box의 크기가 큰 후보만 추출

# 크기가 10000이상인 애만
cand_rects = [cand['rect'] for cand in regions if cand['size'] > 10000]

green_rgb = (125, 255, 51)
img_rgb_copy = img_rgb.copy()
for rect in cand_rects:
    
    left = rect[0]
    top = rect[1]
    # rect[2], rect[3]은 너비와 높이이므로 우하단 좌표를 구하기 위해 좌상단 좌표에 각각을 더함. 
    right = left + rect[2]
    bottom = top + rect[3]
    
    img_rgb_copy = cv2.rectangle(img_rgb_copy, (left, top), (right, bottom), color=green_rgb, thickness=2)
    
plt.figure(figsize=(8, 8))
plt.imshow(img_rgb_copy)
plt.show()

반응형
728x90
반응형

object detection은 localization과 같은 방법을 동일하게 적용하면 문제가 생길 수 있음

 

이미지안에 이미지 안에 비슷한 object가 여러개 있으면 Feature Extraction Layer를 통과한 feature map이 생성되고 그러면 사이즈도 줄어들고 추상화된다.

feature map에서 추출하고 regression을 통해서 발견한 이미지 특성을 다른 곳에서 또 발견하면 모델이 위치좌표를 찍는 것에 엉뚱한 좌표를 찍는 문제가 생긴다.

 

이미지 내에서 좌표를 찍는 것이 detection 영역의 문제의식이다.

특정영역을 한정하고 찾고, 한정하고 찾는 것이 정확도가 높았다. 위치를 옮기면서 찾는 것이 슬라이딩 방식이다.

 

1) sliding window

window를 이동시키면서 detection하는 방식

- 다양한 형태의 window를 슬라이딩

- window scale은 고정하고 scale을 변경한 여러 이미지를 사용하는 방식

: 초기 기법이고, 오브젝트 없는 영역도 무조건 슬라이딩 하여야 하며, 여러형태의 window와 여러 scale이미지를 스캔하여 검출해야해서, 수행시간이 오래 걸리고, 검출 성능이 상대적으로 낮음

- region proposal 기법 등장으로 활용도는 떨어졌지만 OD 발전의 기술토대 제공

 

 

2) Region Proposal

 

이미지는 밝기, 색상, 윤곽선으로 구분이 됨, 이 특성을 이용해서 object가 있을만한 후보영역을 찾는다

- selective search

차이점을 segmentation을 함

빠른 detection과 높은 recall 예측 성능을 동시에 만족하는 알고리즘

color, Texture, size, shape에 따라 유사한 region을 계층적 그룹핑 방법으로 계산함

-> 각 기준에 의해 조금이라도 건덕지가 있으면 영역을 나눠버리고 마스킹을 해서 후보군을 만듬

=> 그리고 비슷한 애들은 중복, 비슷한 특성 등 찾아서 계층적 그룹핑, 합쳐버림

경계선은 edge detector,

 

selective search는 최초에는 pixel intensity 기반한 graph-based segment기법에 따라 over segmentation을 수행

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형
728x90
반응형

원본이미지에 1개의 object가 있는 경우

 

image classification과 똑같은 순서이고 

예를 들어 (224, 224) 라고 하고

VGG16과 같은 back bone 즉, Feature Extrator에 이미지를 입력하게 된다.

backbone의 역할은 이미지 내에서 중요한  피처를 추출하는 것이다.

 

그것을 통해서 최종적으로 feature map이 만들어짐

가령 feature map은 (7, 7)이 되어 size는 작아지는데, 대신 채널 수는 늘어나게 됨 (7, 7, 512) 

원본이미지 대비 축약된, 추상화된 이미지가 생성된다.

 

그리고 보통 img와 label을 maping시키고, 학습을 해놓음

그리고 softmax 함수로 class score를 매겼을 때 {car : 0.8, cat : 0.1, dog : 0.1 } 이러면 car이 된다.

 

3000개 img들로 각각 1000장씩 학습을 시키고

layer [ Dense layer, softmax 함수, fully connected ]로 학습시킴

=> img classification 원리

 

 

annotation 파일에 object, bounding box 좌표(xmin, ymin), (xmin, ymin)가 입력이 되어있다.

혹은 yolo는 center 좌표로 지정하기도 함. 

object localization 은 여기서 bounding box regression이 또 있음.

각각 label을 각각 좌표에 예측을 해봄

 

feature map은 map당 object가 딱 하나니깐 map에 이런 특성이 나오면 해당 bounding box regression을 적용한다.

그러면 잘나옴

 

그리고 object를 학습시키고, 가중치를 부여하면서, 여러 각도, 여러 이미지를 통해서 학습시킨다.

이 feature가 있으면 maping을 한다가 됨.

yolo의 경우 예측결과가 class confidence score이 있음

 

object detection으로 2개 이상의 object를 검출하면 거기에 문제가 생김

문제가 유사하게 적용했는데 각각 좌표가 이상한 곳에 찍힘

inference가 이상한 곳에 찍힘.

1000장의 이미지가 있으면 각각 학습시키면서 feature 특징은 학습했는데 feature 좌표를 획득하는데 문제가 생긴다.

예를 들어 중간값으로 좌표를 찍는 식으로

 

object가 있을 만한 위치를 먼저 찾아주고, 그 위치의 object를 탐색하는 방식이 보통 방식임

=> Region proposal

대충 가이드라인은 나눠줘야 번잡하지 않다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

반응형

+ Recent posts