본문 바로가기
HRDI_AI/머신러닝_딥러닝 핵심 기술과 실무 중심 생성형 AI 프로젝트

Day1: 인공지능 프로그래밍 준비-1

by Toddler_AD 2025. 12. 4.

학습 내용

1. 인공지능과 Python 기초
2. 인공지능과 Python 함수
3. 인공지능과 Python 클래스 이해

 
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

# 기본 폰트 사이즈 변경
plt.rcParams['font.size'] = 14

# 기본 그래프 사이즈 변경
plt.rcParams['figure.figsize'] = (4,4)

# 기본 그리드 표시
# 필요에 따라 설정할 때는, plt.grid()
plt.rcParams['axes.grid'] = True
plt.rcParams["grid.linestyle"] = ":"

# 마이너스 기호 정상 출력
plt.rcParams['axes.unicode_minus'] = False

 

파이썬 연산자

# Print arithmetic result
print(3 + 4)
print(3*2)
print(3**2)
print(3/2)
print(3//2) # 정수 part
print(3%2) # 나머지
print("Welcome to the python world!!")
#  print("Welcome to the python world!!")
print(True, False)

 

7
6
9
1.5
1
1
Welcome to the python world!!
True False

 

파이썬 자료의 구조 (Container Objects)

  • 리스트(list), 튜플(tuple), 사전(dict)
a = 12
b = 12.55

print("type(a) = ", type(a)) # 32 bit/ 4 Byte
print("type(b) = ", type(b)) # 64 bit/ 8 Byte
type(a) =  <class 'int'>
type(b) =  <class 'float'>

 

 

## int 리스트
price = [10000, 3000, 12000]

#list indexing
print("price[0] = ", price[0])

#list slicing
print("price[0:2] = ", price[0:2])
price[0] =  10000
price[0:2] =  [10000, 3000]

 

 

## string 리스트
fruit = ['apple', 'banana', 'orange']

print("type(fruit) = ", type(fruit))

#list indexing
print("fruit[0] = ", fruit[1])

#list slicing
print("fruit[1:3] = ", fruit[1:3])
type(fruit) =  <class 'list'>
fruit[0] =  banana
fruit[1:3] =  ['banana', 'orange']

 

 

## 튜플 (Tuple)
tup = ('apple', 'banana', 'watermelon', 'cucumber')
print("type(tup) = ", type(tup))

#튜플 indexing
print("tup[1] = ", tup[1])

#튜플 slicing
print("tup[1:3] = ", tup[1:3])

# tup[0] = "mango" # TypeError: 'tuple' object does not support item assignment
type(tup) =  <class 'tuple'>
tup[1] =  banana
tup[1:3] =  ('banana', 'watermelon')

 

 

## 딕셔너리 (Dictionary)
# * 자료에 접근할 때 'key' 값을 이용해서 접근

flower_price ={
    'rose': 10000,
    'iris': 3000,
    'stargauge': 12000,
    'lily': 9000,
    'daffodil': 3000
}

print(flower_price, '\n')

## 딕셔너리 접근하기
print(flower_price.items(), '\n') 
print(flower_price.keys(), '\n')
print(flower_price.values(), '\n')
print(flower_price.get("rose"), '\n')


flower_price['daisy'] = flower_price.pop('iris')
flower_price
{'rose': 10000, 'iris': 3000, 'stargauge': 12000, 'lily': 9000, 'daffodil': 3000} 

dict_items([('rose', 10000), ('iris', 3000), ('stargauge', 12000), ('lily', 9000), ('daffodil', 3000)]) 

dict_keys(['rose', 'iris', 'stargauge', 'lily', 'daffodil']) 

dict_values([10000, 3000, 12000, 9000, 3000]) 

10000
{'rose': 10000,
 'stargauge': 12000,
 'lily': 9000,
 'daffodil': 3000,
 'daisy': 3000}

 

 

fruit_price={}
print(type(fruit_price))

fruit_price['apple'] = 1000

print(fruit_price)

## access dictionary data
print("fruit_price['apple'] = \n", fruit_price['apple'])
{'rose': 10000, 'iris': 3000, 'stargauge': 12000, 'lily': 9000, 'daffodil': 3000} 

dict_items([('rose', 10000), ('iris', 3000), ('stargauge', 12000), ('lily', 9000), ('daffodil', 3000)]) 

dict_keys(['rose', 'iris', 'stargauge', 'lily', 'daffodil']) 

dict_values([10000, 3000, 12000, 9000, 3000])
{'rose': 10000,
 'stargauge': 12000,
 'lily': 9000,
 'daffodil': 3000,
 'daisy': 3000}

 

 

if 조건문

# 만약 condition 이 True 이면 code 1 을 수행, False 이면 code 2를 수행
# if condition: 
#      code 1
#  else:
#      code 2
# 

rose = 1500

if rose < 1000: #Boolean index:
    print('장미꽃을 구매합니다')
else:    
    print('나리꽃을 구매합니다')
나리꽃을 구매합니다

 

 

rose = 1500

if rose < 1000: #Boolean index:
    print('장미꽃을 구매합니다')
elif rose < 2000: #
    print('꽃구매를 기다립니다')
else:   
    print('나리꽃을 구매합니다')
꽃구매를 기다립니다

 

for 구문

# for 변수 in 리스트(또는 튜플, 문자열):
#     수행할_문장1
#     수행할_문장2
#     ...

for i in range(10):
    print(i)
0
1
2
3
4
5
6
7
8
9

 

 

## Using string 
flowers = ['장미', '백합', '붓꽃', '해바라기']

for i in flowers:
    print(i)
장미
백합
붓꽃
해바라기

 

 

## Using index

flowers = ['장미', '백합', '붓꽃', '해바라기', '후리지아']

for i in [0,1,2,3]:
    print(flowers[i])
장미
백합
붓꽃
해바라기

 

While 반복문

# 초기식                  
# while 조건식:          
#      반복할 코드   
#      변화식                   

i = 0
while i <= 10:
    print(i)
    i += 1
0
1
2
3
4
5
6
7
8
9
10

 

 

rose = 1000
day = 1

while day <= 10:
    rose = rose + rose*0.1
    print(round(rose, 3), ", day = ", day)
    day += 1
1100.0 , day =  1
1210.0 , day =  2
1331.0 , day =  3
1464.1 , day =  4
1610.51 , day =  5
1771.561 , day =  6
1948.717 , day =  7
2143.589 , day =  8
2357.948 , day =  9
2593.742 , day =  10

 

파이썬 함수만들기

# 함수정의 기본 문법
# def 함수 이름: 
#     함수 내용 

# 함수 호출 
def myfunction(): # 함수 이름
    print("Hello World") # 함수 내용

myfunction() # 함수 호출
Hello World

 

# 합을 구하는 함수
def addNum(num1: int | float, num2: int | float) -> float:
    sum = num1 + num2
    return float(sum)

result = addNum(10, 2)
print("result = ", result)
print("result = ", type(result))
result =  12.0
result =  <class 'float'>

 

## 여러 개의 값 반환하기
def add_sub(num1: int | float, num2: int | float) -> int | float:
    add = num1 + num2
    sub = num1 - num2
    return add, sub

x = add_sub(3, 4.3)  # tuple로 반환
print('x = ', x)

add, sub = add_sub(3, 4.3)
print("add = {}, sub = {}".format(add, sub))
x =  (7.3, -1.2999999999999998)
add = 7.3, sub = -1.2999999999999998

 

## 함수내 함수호출, stack 구조
def mul(a, b):
    return a*b

def divide(a, b):
    return a/b

def mul_div(x, y):
    num1 = mul(x, y)
    num2 = divide(x, y)
    return num1, num2

result = mul_div(2, 1)
print('result = ', result)
result =  (2, 2.0)

 

합성 함수를 파이썬으로 구현하기

  • 수학에서 합성함수가 파이썬에서 어떻게 구현되는지 확인한다.

def f(x):
    return (2 * x**2 + 2)
# 넘파이 배열로 x를 정의

x = np.arange(-2, 2.1, 0.25)
print(x)
[-2.   -1.75 -1.5  -1.25 -1.   -0.75 -0.5  -0.25  0.    0.25  0.5   0.75
  1.    1.25  1.5   1.75  2.  ]

 

# f(x)의 결과를 y에 대입

y = f(x)
print(y)
[10.     8.125  6.5    5.125  4.     3.125  2.5    2.125  2.     2.125
  2.5    3.125  4.     5.125  6.5    8.125 10.   ]

 

# 함수를 그래프로 그리기

plt.plot(x, y)
plt.show()

 

# 세 가지 기본 함수의 정의

def f1(x):
    return(x**2)

def f2(x):
    return(x*2)

def f3(x):
    return(x+2)

# 합성 함수 만들기

x1 = f1(x)
x2 = f2(x1)
y = f3(x2)
# 합성 함수 값 확인

print(y)
[10.     8.125  6.5    5.125  4.     3.125  2.5    2.125  2.     2.125
  2.5    3.125  4.     5.125  6.5    8.125 10.   ]

 

# 합성 함수 그래프 그리기

plt.plot(x, y)
plt.show()

 

## 합, 차 함수
def add(a, b):
    return a + b

def sub(a, b):
    return a - b

a, b = 1, 2
print("a + b = ", add(a, b))
print("a - b = ", sub(a, b))
a + b =  3
a - b =  -1

 

커스텀 클래스 정의하기

## class 만들기 : 비슷한 기능들을 한 번에 관리
# 클래스는 관련된 데이터(속성)와 기능(메서드)을 하나의 단위로 묶어 관리합니다.

class Calculator:
    # 1. 생성자 메서드 (Initializer/Constructor)
    def __init__(self, a:int, b:int):
        self.result = 0
        self.a = a
        self.b = b

    # 2. 메서드 (Method) - 덧셈 기능
    # 객체가 가지고 있는 self.a와 self.b를 사용하여 덧셈을 수행합니다.
    def add(self) -> float:
        self.result = self.a + self.b
        return self.result
    
    # 3. 메서드 (Method) - 뺄셈 기능
    # 객체가 가지고 있는 self.a와 self.b를 사용하여 뺄셈을 수행합니다.
    def sub(self) -> float:
        self.result = self.a - self.b
        return self.result
calculator = Calculator(3, 2) # Calculator 클래스의 객체(인스턴스)를 생성하고 __init__을 호출합니다.
print("add = ", calculator.add()) # 15 출력
print("sub = ", calculator.sub()) # # 1 출력
add =  5
sub =  1

 

클래스 상속

## 상속 (Inheritance)
# CalculatorV2 클래스는 괄호 안에 명시된 Calculator 클래스를 상속받습니다.
# 즉, CalculatorV2는 Calculator의 모든 메서드(add, sub)와 속성(self.a, self.b, self.result)을 물려받습니다.
class CalculatorV2(Calculator):

    # 1. 생성자 메서드 오버라이딩 (Overriding __init__)
    # 부모 클래스(Calculator)와 달리 세 개의 인자(x, y, c)를 받도록 재정의합니다.
    def __init__(self, x, y, c):
        super().__init__(x, y)
        self.c = c

    # 2. 새로운 메서드 추가 (곱셈)
    # 부모 클래스에는 없던 새로운 기능(mul)을 추가합니다.
    def mul(self):
        self.result = self.a * self.b
        return self.result
    
    # 3. 새로운 메서드 추가 (나눗셈)
    # 부모 클래스에는 없던 새로운 기능(div)을 추가합니다.    
    def div(self):
        self.result = self.a / self.b
        return self.result
# 1. 인스턴스 생성 및 __init__ 호출
# CalculatorV2 클래스의 새로운 객체(인스턴스)를 생성합니다.
calculator2 = CalculatorV2(10, 2, 1)

# 2. 부모 클래스(Calculator)로부터 상속받은 add() 메서드를 호출
print(calculator2.add())
# 3. 부모 클래스(Calculator)로부터 상속받은 sub() 메서드를 호출
print(calculator2.sub())
# 4. 자식 클래스(CalculatorV2)에 새로 추가된 메서드 호출
print(calculator2.mul())
# 5. 자식 클래스(CalculatorV2)에 새로 추가된 메서드 호출
print(calculator2.div())
12
8
20
5.0

 

클래스 상속(attribute)

class Dog:
    """
    Dog 클래스는 클래스 속성과 인스턴스 속성을 모두 보여줍니다.
    """
    
    # 1. 클래스 속성 (Class Attribute)
    # 클래스 수준에서 정의되며, Dog 클래스의 모든 인스턴스가 이 값을 공유합니다.
    # 일반적으로 변하지 않는 공통 값이나 카운터에 사용됩니다.
    species = "Beagle"
    
    # 모든 강아지 인스턴스의 수를 추적하기 위한 클래스 속성
    instance_count = 0 

    def __init__(self, name, age):
        # 2. 인스턴스 속성 (Instance Attribute)
        # __init__ 내에서 self를 사용하여 정의되며, 각 인스턴스마다 고유한 값을 가집니다.
        self.name = name
        self.age = age
        
        # 인스턴스가 생성될 때마다 클래스 속성인 instance_count를 1 증가시킵니다.
        Dog.instance_count += 1
        
    def bark(self):
        """인스턴스 메서드입니다."""
        return f"{self.name}가 짖습니다: 멍멍!"
    
    # 3. 클래스 메서드 (Class Method)
    # @classmethod 데코레이터를 사용하며, 첫 번째 인자로 클래스 자체(cls)를 받습니다.
    # 클래스 속성에 접근하거나 클래스 자체와 관련된 작업을 수행하는 데 사용됩니다.
    @classmethod
    def get_count(cls):
        """현재까지 생성된 Dog 인스턴스의 총 개수를 반환합니다."""
        # cls.instance_count를 통해 클래스 속성에 접근합니다.
        return f"총 강아지 인스턴스: {cls.instance_count}마리"
# --------------------- 예제 사용 ---------------------

# 초기 인스턴스 수 확인 (클래스 속성에 직접 접근)
print(f"--- 🐶 초기 상태 ---")
print(f"초기 인스턴스 수: {Dog.instance_count}")
print(f"클래스 메서드로 확인: {Dog.get_count()}\n")


# 1. 첫 번째 인스턴스 생성
dog1 = Dog("바둑이", 3)
print(f"--- 🐕 dog1 생성 ---")
print(f"dog1의 종(클래스 속성): {dog1.species}") # 인스턴스를 통해 클래스 속성 접근 가능
print(f"dog1의 이름(인스턴스 속성): {dog1.name}") 
print(f"현재 인스턴스 수: {Dog.get_count()}\n")


# 2. 두 번째 인스턴스 생성
dog2 = Dog("흰둥이", 5)
print(f"--- 🐾 dog2 생성 ---")
print(f"dog2의 종(클래스 속성): {dog2.species}")
print(f"현재 인스턴스 수: {Dog.get_count()}\n")


# # 3. 클래스 속성 값 변경 (모든 인스턴스에 영향)
Dog.species = "진돗개"

print(f"--- 📋 클래스 속성 변경 ---")
print(f"dog1의 새로운 종: {dog1.species}") # dog1의 값도 변경됨
print(f"dog2의 새로운 종: {dog2.species}") # dog2의 값도 변경됨
--- 🐶 초기 상태 ---
초기 인스턴스 수: 0
클래스 메서드로 확인: 총 강아지 인스턴스: 0마리

--- 🐕 dog1 생성 ---
dog1의 종(클래스 속성): Beagle
dog1의 이름(인스턴스 속성): 바둑이
현재 인스턴스 수: 총 강아지 인스턴스: 1마리

--- 🐾 dog2 생성 ---
dog2의 종(클래스 속성): Beagle
현재 인스턴스 수: 총 강아지 인스턴스: 2마리

--- 📋 클래스 속성 변경 ---
dog1의 새로운 종: 진돗개
dog2의 새로운 종: 진돗개

 

클래스 메서드 오버라이드

# Point 클래스 정의
class Point:
    # 인스턴스 생성 시에 두 개의 인수 x와 y를 가짐
    def __init__(self, x, y):
        # 인스턴스 속성 x에 첫 번째 인수를 할당
        self.x = x
        # 인스턴스 속성 y에 두 번째 인수를 할당
        self.y = y

    # draw 함수 정의(인수 없음)
    def draw(self):
        # (x, y)에 점을 그림
        print("3")
        plt.plot(self.x, self.y, marker='o', markersize=10, c='k')
# Point 클래스로 인스턴스 변수 p1과 p2 생성

p1 = Point(2,3)
p2 = Point(-1, -2)
# p1과 p2의 속성x, y

print(p1.x, p1.y)
print(p2.x, p2.y)
2 3
-1 -2

 

# p1과 p2의 draw 함수를 호출하고, 두 개의 점을 출력함
p1.draw()
p2.draw()
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
3
3

 

# Point의 자식 클래스 Circle 정의 1

class Circle1(Point):
    # Circle은 인스턴스 생성 시에 인수 x, y, r을 가짐
    def __init__(self, x, y, r):
        # x와 y는 부모 클래스의 속성으로 설정
        super().__init__(x, y)
        # r은 Circle의 속성으로 설정
        self.r = r
     
    # 이 단계에서 draw 함수는 정의하지 않음
# Circle1 클래스에서 인스턴스 변수 c1_1을 생성
c1 = Circle1(1, 0, 2)
# c1_1의 속성 확인
print(c1.x, c1.y, c1.r)
3
3
3

 

  • 이 단계에서 draw 함수는 부모쪽에서 정의한 함수가 호출되고 있음을 알 수 있다.
# 원을 그리기 위해 필요한 라이브러리
import matplotlib.patches as patches

# Point의 자식 클래스 Circle의 정의 2
class Circle2(Point):
    # Circle은 인스턴스 생성 시에 인수x, y, r을 가짐
    def __init__(self, x, y, r):
        # x와 y는 부모 클래스의 속성으로 설정
        super().__init__(x, y)
        # r은 Circle의 속성으로 설정
        self.r = r
     
    # draw 함수는 자식 클래스만 따로 원을 그림
    def draw(self):
        # 원 그리기
        c = patches.Circle(xy=(self.x, self.y), radius=self.r, fc='b', ec='k')
        ax.add_patch(c)
# Circle2 클래스로부터 인스턴스 변수 c2_1을 생성
c2 = Circle2(1, 0, 2)
# p1, p2, c2_1의 각 draw 함수를 호출

ax = plt.subplot()
p1.draw()
p2.draw()
c2.draw() # 자식 클래스 메서드 호출
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
3
3

 

 

  • 부모의 draw 함수 대신 자식의 draw 함수가 호출되었음을 알 수 있다. 그럼, 이 함수와 부모 함수를 모두 호출하고 싶을 때는 어떻게 해야 하는가?
# Point의 자식 클래스 Circle의 정의 3

class Circle3(Point):
    # Circle은 인스턴스 생성 시에 인수 x, y, r을 가짐
    def __init__(self, x, y, r):
        # x와 y는 부모 클래스의 속성으로 설정
        super().__init__(x, y)
        # r은 Circle의 속성으로 설정
        self.r = r
     
    # Circle의 draw 함수는 부모의 함수를 호출한 다음, 원 그리기를 독자적으로 수행함
    def draw(self):
        # 부모 클래스의 draw 함수 호출
        print("1")
        super().draw()
        
        print("2")
        # 원 그리기
        c = patches.Circle(xy=(self.x, self.y), radius=self.r, fc='b', ec='k')
        ax.add_patch(c)
# Circle3 클래스로부터 인스턴스 변수 c3_1를 생성
c3 = Circle3(1, 0, 2)
# p1, p2, c3_1의 각 draw 함수를 호출

ax = plt.subplot()
p1.draw()
p2.draw()
c3.draw()
plt.xlim(-4, 4)
plt.ylim(-4, 4)
plt.show()
3
3
1
3
2

 

인스턴스를 함수로 사용하는 방법

# 함수 클래스 H의 정의

class H:
    def __call__(self, x):
        return 2*x**2 + 2
# h가 함수로 동작하는지 확인

# 넘파이 배열 x를 정의
x = np.arange(-2, 2.1, 0.25)
print(x)

# H 클래스의 인스턴스로 h를 생성
h = H() 

# 함수 h 호출
y = h(x)
print(y)
[-2.   -1.75 -1.5  -1.25 -1.   -0.75 -0.5  -0.25  0.    0.25  0.5   0.75
  1.    1.25  1.5   1.75  2.  ]
[10.     8.125  6.5    5.125  4.     3.125  2.5    2.125  2.     2.125
  2.5    3.125  4.     5.125  6.5    8.125 10.   ]

 

# 그래프 출력

plt.plot(x, y)
plt.show()

 

클래스 스페셜 메서드(Class Special Method)

class CustomVector:
    """
    Python의 다양한 스페셜 메서드를 사용하여 벡터 객체의 동작을 정의하는 클래스입니다.
    """
    
    # 1. 초기화 메서드 (Constructor)
    def __init__(self, x, y):
        """객체를 생성하고 초기화합니다."""
        self.x = x
        self.y = y
    
    # --- 문자열 표현 관련 ---
    
    # 2. 공식 문자열 표현 (Debugging/Logging)
    def __repr__(self):
        """개발자를 위한 모호하지 않은 객체의 문자열 표현을 반환합니다."""
        # 이 문자열로 객체를 다시 생성할 수 있게 만드는 것이 일반적입니다.
        return f"CustomVector(x={self.x}, y={self.y})"
    
    # 3. 비공식 문자열 표현 (End-User Output)
    def __str__(self):
        """사용자를 위한 읽기 쉬운 문자열 표현을 반환합니다. (print() 호출 시 사용)"""
        return f"({self.x}, {self.y})"

    # --- 컨테이너 및 길이 관련 ---
    
    # 4. 길이 메서드
    def __len__(self):
        """벡터의 크기(magnitude) 또는 길이를 반환합니다."""
        # 이 예제에서는 유클리드 거리를 길이로 간주합니다.
        return len([self.x]) # 리스트 길이
    
    # 5. 아이템 접근 메서드 (Indexing)
    def __getitem__(self, index):
        """객체를 리스트처럼 인덱싱하여 접근할 수 있게 합니다. 예: vector[0]"""
        if index == 0:
            return self.x
        elif index == 1:
            return self.y
        else:
            raise IndexError("Index must be 0 or 1 for this 2D vector.")

    # --- 연산자 오버로딩 (Operator Overloading) ---
    
    # 6. 덧셈 메서드
    def __add__(self, other):
        """두 CustomVector 객체를 더할 때의 동작을 정의합니다. (vector1 + vector2)"""
        if isinstance(other, CustomVector):
            new_x = self.x + other.x
            new_y = self.y + other.y
            return CustomVector(new_x, new_y)
        return NotImplemented # 다른 타입과의 덧셈을 지원하지 않음
    
    # 7. 곱셈 메서드 (숫자와의 곱셈)
    def __mul__(self, scalar):
        """벡터에 스칼라 값을 곱할 때의 동작을 정의합니다. (vector * 3)"""
        if isinstance(scalar, (int, float)):
            return CustomVector(self.x * scalar, self.y * scalar)
        return NotImplemented
    
    # --- 함수 호출처럼 사용하기 ---
    
    # 8. 호출 가능 메서드 (Callable)
    def __call__(self, offset_x=0, offset_y=0):
        """객체를 함수처럼 호출할 수 있게 합니다. 예: vector()"""
        print(f"벡터를 호출했습니다! 기존 위치 ({self.x}, {self.y})에 오프셋 ({offset_x}, {offset_y})을 적용합니다.")
        return CustomVector(self.x + offset_x, self.y + offset_y)


# --- 예제 사용 ---
# if __name__ == "__main__":
v1 = CustomVector(3, 4)
v2 = CustomVector(10, 5)

print("=" * 30)
print("1. __str__과 __repr__ 테스트:")
print(f"print(v1) 결과 (__str__ 사용): {v1}") # (3, 4)
print(f"repr(v1) 결과 (__repr__ 사용): {repr(v1)}") # CustomVector(x=3, y=4)

print("-" * 30)
print("2. __len__ 테스트:")
# math.hypot(3, 4) = 5.0
print(f"len(v1) 결과 (벡터 크기): {len(v1)}")

print("-" * 30)
print("3. __getitem__ 테스트 (인덱싱):")
print(f"v1[0] 결과: {v1[0]}") # 3
print(f"v1[1] 결과: {v1[1]}") # 4

print("-" * 30)
print("4. __add__ 및 __mul__ 테스트 (연산자 오버로딩):")
v_sum = v1 + v2
v_scaled = v1 * 2.5

print(f"v1 + v2 결과 (새 벡터): {v_sum}") # (3+10, 4+5) = (13, 9)
print(f"v1 * 2.5 결과 (새 벡터): {v_scaled}") # (3*2.5, 4*2.5) = (7.5, 10.0)

print("-" * 30)
print("5. __call__ 테스트:")
v_shifted = v1(offset_x=7) # 객체를 함수처럼 호출
print(f"v1(offset_x=7) 호출 후 새 벡터: {v_shifted}") # (3+7, 4+0) = (10, 4)
print("=" * 30)
==============================
1. __str__과 __repr__ 테스트:
print(v1) 결과 (__str__ 사용): (3, 4)
repr(v1) 결과 (__repr__ 사용): CustomVector(x=3, y=4)
------------------------------
2. __len__ 테스트:
len(v1) 결과 (벡터 크기): 1
------------------------------
3. __getitem__ 테스트 (인덱싱):
v1[0] 결과: 3
v1[1] 결과: 4
------------------------------
4. __add__ 및 __mul__ 테스트 (연산자 오버로딩):
v1 + v2 결과 (새 벡터): (13, 9)
v1 * 2.5 결과 (새 벡터): (7.5, 10.0)
------------------------------
5. __call__ 테스트:
벡터를 호출했습니다! 기존 위치 (3, 4)에 오프셋 (7, 0)을 적용합니다.
v1(offset_x=7) 호출 후 새 벡터: (10, 4)
==============================

 

Dataset 스페셜 메서드

from torch.utils.data import Dataset

# PyTorch의 모든 사용자 정의 데이터셋은 torch.utils.data.Dataset을 상속받아야 합니다.
class PyTorch_Custom_Dataset_Class(Dataset):
    
    # 1. 생성자 (Constructor)
    def __init__(self):
        super().__init__()
        self.input = [i for i in range(10)] # 딥러닝 input
        self.output = [i for i in range(10)] # 딥러닝 output

    # 2. 인덱싱 메서드 (Item Retrieval) - 필수
    def __getitem__(self, idx):
        print("__getitem__ 실행")
        return self.input[idx], self.output[idx]
    

    # 3. 길이 메서드 (Length) - 필수
    def __len__(self):
        print("__len__ 실행")
        return len(self.input)
# 1. Dataset 클래스의 인스턴스를 생성합니다.
pytorch_custom = PyTorch_Custom_Dataset_Class()

# 2. 인덱싱 호출 (Indexing Call)
# 이 호출은 클래스 내의 특별 메서드인 __getitem__(self, idx)을 실행시킵니다.
# 결과:
# "__getitem__ 실행"이 출력되고, (self.input[0], self.output[0]), 즉 (0, 0)이 반환됩니다.
print(pytorch_custom[0])

# 3. 길이 확인 호출 (Length Check Call)
# 이 호출은 클래스 내의 특별 메서드인 __len__(self)을 실행시킵니다.
# 결과:
# "__len__ 실행"이 출력되고, len(self.input), 즉 10이 반환됩니다.
print(len(pytorch_custom))
__getitem__ 실행
(0, 0)
__len__ 실행
10

 

예외 처리(Exception Handling)

  • 실제 AI 모델을 개발하고 운영할 때는 다양한 오류 상황이 발생할 수 있습니다. 프로그램의 안정성을 높이는 예외처리는 고급 개발자의 필수 역량입니다.
  • try, except, finally, else 구문을 사용하여 프로그램 실행 중 발생하는 오류를 예측하고 처리하는 방법
try:
    # 오류가 발생할 가능성이 있는 코드
    result = 10 / 0
    # result = 10 / 1

except ZeroDivisionError:
    # 특정 오류 발생 시 처리할 코드
    print("0으로 나눌 수 없습니다.")
except Exception as e:
    # 그 외 모든 오류 처리
    print(f"예상치 못한 오류 발생: {e}")
else:
    # 오류가 발생하지 않았을 때 실행
    print("연산 성공")
finally:
    # 오류 발생 여부와 상관없이 항상 실행
    print("예외 처리 완료")
0으로 나눌 수 없습니다.
예외 처리 완료

 

  • 모듈(Module) 및 패키지(Package) 이해 AI 프로젝트는 규모가 크기 때문에 코드를 효율적으로 관리하고 재사용하기 위해 모듈과 패키지를 이해하는 것이 중요합니다.
    • 모듈 생성 및 가져오기(import) : 파이썬 파일을 모듈로 만들어 다른 파일에서 사용하는 방법
    • 패키지 구조 : 모듈들을 묶어 관리하는 디렉토리 구조 및 init.py의 역할
  • 고급 함수 기능(Advanced Function Features) / 람다 함수(Lambda Functions) : 간단한 익명 함수를 빠르게 정의할 때 사용하며, 데이터 처리나 모델 정의에서 종종 활용됩니다.
    • 데코레이터(Decorators) : 함수를 수정하지 않고 추가 기능을 덧붙일 때 사용하는 고급 기법으로, 프레임워크나 로깅 등에서 유용합니다.
    • 제너레이터(Generators)와 yield : 대용량 데이터를 처리할 때 메모리를 효율적으로 사용하기 위해 데이터를 '생성'하는 방식.(딥러닝에서 데이터 로더 구성 시 중요)