【2025】 SSAFY14/[이론] Python

[AI] 11-1. AI 강의 Wrap UP 1 - AI 강의 챕터별 리뷰🔖⭐⭐⭐⭐⭐

핑크핑크핑크핑 2025. 11. 2. 17:28

*️⃣ 시험 출제 방식

과목평가
- 1시간30분
- 객관식, 주관식, 서술형
- 범위 : MLP, CNN, 데이터 생성 방법, 토큰화, 임베딩, PEFT
- 이론을 정확히 이해하고 있다면, 문제 출제 의도를 이해하여 답변을 적을 수 있을 것이다
월말평가
- 1시간30분
- 파일제출형 (3문제)
- 범위 : EDA, 선형회귀, RAG 기본
- 코드의 흐름을 이해하고 있으면, 스켈레톤 코드를 수정하여 완성할 수 있을 것이다
- Solution.ipynb 파일을 압축하여 제출한다

 

AI강의II_랩업_실습.ipynb
11.64MB


AI 를 위한 Python 과 Math

 

AI를 위한 준비과정

  1. 파이썬 기본 문법 ㅡ if, for, 변수, 리스트, 함수
  2. 클래스 ㅡ PyThorch 로 모델 제작할때 필요
  3. Numpy ㅡ 선형대수학, 빠른 소수점 연산
  4. Pandas ㅡ 데이터확인, 삭제, EDA, 전처리(필터링), csv 불러오기
  5. Matplot ㅡ 데이터 시각화, 기본 라이브러리
  6. Seabon ㅡ 데이터 시각화, Matplot 기반, Pandas 와 찰떡궁합
  7. 선형회귀 ㅡ (수치) y = ax + b
  8. 로지스틱 회귀 2️⃣ ㅡ (분류) y = sigmod(ax+b)
  9. Gradient Decent ㅡ 오차가 가장 적게 나오도록 a 와 b 를 조정해나가는 것
  • 코드를 직접 실행해보면서 배운 내용을 리뷰
  • 주요 포인트에 대해서 어떤 내용을 학습했고 왜 이걸 배웠는지 대해 리뷰
- 여기까지 AI 를 위한 Python과 Math 과목에서는 이러한 내용이 중요했습니다.

- Python 
- Numpy, Pandas, Matplot 3총사 
- 선형회귀 / 로지스틱 회귀 (Gradient Descent)

EDA 2️⃣

 

EDA (Exploratory Data Analysis) 를 통해 데이터를 잘 이해한다

  • EDA : 데이터를 살펴보는 행동 ㅡ Pandas, Matplot 으로 할 수 있고, Pandas 를 쓸때는 Seaborn 을 사용헀다
  • 데이터 샘블을 확인 ㅡ load_dataset()
  • 필드 확인 ㅡ head()
  • 기본 통계량 확인
  • 분포 확인 ㅡ regplot(), heatmap(), pairplot() 으로 추세선 확인
  • 상관계수 확인 ㅡ 0에 가까울수록 무상관, 0에서 멀수록 비례/반비레
import seaborn as sns

df = sns.load_dataset("mpg")
df.head()

import seaborn as sns

df = sns.load_dataset("mpg")
sns.regplot(data=df, x="weight", y="acceleration", scatter_kws={'alpha':0.6})
print()
import numpy as np
import seaborn as sns

df = sns.load_dataset("mpg")
corr = df.corr(numeric_only=True) # 상관계수 행렬 계산
mask = np.triu(np.ones_like(corr, dtype=bool)) # 삼각형 마스크 만들기 (상단 삼각형 가리기)
sns.heatmap(corr, mask=mask, annot=True, cmap="coolwarm", linewidths=1)
print()
import seaborn as sns
import matplotlib.pyplot as plt

df = sns.load_dataset("mpg")
sns.pairplot(df[["mpg", "weight", "acceleration"]], corner=True, kind="reg", plot_kws={'line_kws': {'color': 'red'}})
print()

 


MLP 1️⃣

 

MLP (Multi-Layer Perceptron)

  • 하나 이상의 은닉층을 가진 신경망
  • 활성화 함수와 함께 복잡합 패턴을 학습하고 분류
  • 딥러닝 학습의 시작점
- 신경망으로도 선형회귀선을 그려볼 수 있습니다.
- 어차피 추세선은 y = ax + b 로 표현될수 있고, Nueral Network도 y = Wx + b로 표현될수 있거든요
- ReLU 라는 활성화 함수를 추가하여,  y = Wx + b 의 한계를 뛰어 넘었습니다. 
- 함수의 중첩과 ReLU라는 활성화 함수를 이용하면, 어떤 선도 모두 모사할수 있었습니다. 
- 함수의 중첩을 AI에서는 Sequantial Connection이라고 합니다.

토큰화 / 임베딩 1️⃣

 

  • 토큰화 : 문장을 토큰 단위로 나누는 과정
  • 임베딩 : 각 토큰화된 단어들에게 의미를 부여하는 과정. 의미공간의 Vector 값을 갖게 된다
- 문장은 모델에 바로 대입할 수 없었습니다~!
- 그래서 각 토큰단위로 글자를 자르고, 이를 수로 바꾸었습니다.
- 그리고 각 수를 벡터공간의 벡터값으로 치환하였습니다.
- 그리고 같은 의미를 가진 단어는 유사한 벡터값을 가지도록 밉팽해주는 "임베딩 모델"을 사용합니다. 
- 임베딩 모델은 보통 트랜스포머 기반 모델로 만들곤합니다.
- 유사한 단어가 유사한 벡터값을 가지는지 확인할 수 있습니다.

합성데이터와 데이터 증강

 

  • 합성데이터 (Synthetic Data) : 실제 세계에 없는 새로운 데이터를 생성하는 것
  • 데이터증강 (Data Augmentation) : 실제 데이터를 변형하여 데이터 셋을 확보하는 것

CNN 1️⃣

 

CNN (Convolutional Neural Network)

  • 이미지, 영사에서 시각 데이터를 특징을 기반으로 사물을 인식할 수 있는 딥러닝 알고리즘
  • 2012 년 AI 겨울을 끝내고, CNN 기반의 AlexNet 을 통해 AI 시대를 다시 열게 되었음

- 컨볼루션 연산으로, 테두리 추출 등 이미지의 특징을 도도라지도록 만들 수 있습니다.
- CNN은 이 커널함수의 값을 Weight로 취급하여 알아내는 것이죠.
- 이후 이렇게 특징이 뽑아진 값을 ReLU로 비선형함수로 만들구요
- Max Pooling을 통해, 강한 특징이 나오는 값만 뽑아냅니다.

- CNN은 다음과 같은 구조를 가졌습니다.
- Convolution 연산을 하고, Maxpooling하고, 이를 반복하고
- 마지막에는 FC : MLP를 연결해서 0 ~ 9 까지 분류할 수 있었습니다.
- 여기서 파라미터값은 다음과 같습니다.  1. 커널함수  2. FC의 Weight, bias 값들
- Gradient Decent를 통해 이 파라미터값을 알아내는 것이죠.
- CNN 원리를 살펴보고, CNN 기반인 AlexNet까지 학습하게 됩니다.

- 이후 첫 파인튜닝을 합니다
- Partial 파인튜닝중 하나인 Linear Probing을 통해 분류모델을 만들어봤습니다. 
- 마지막 Layer만 새로운 Layer로 바꾸는 것이죠. 
- 그리고 추가학습을통해 이 새로운 Layer의 파라미터값을 결정해줍니다.   
- 다른 파라미터값은 건드리지않습니다.
- 이를 이용하면, 멋진 특징추출기(백본)을 그대로 사용하면서, 다양한 분류모델을 만들 수 있습니다.


RNN / LSTM

 

RNN (Recurrent Neural Network)

  • AI 에서 핵심 딥러닝 알고리즘 중 하나
  • 시퀀스 데이터를 학습시키고 추론시키는데 특화된 딥러닝
  • Attention 의 필요성을 이해하기 위해 학습했음

- RNN은 Hidden Layer의 마지막 출력값을 다음 Layer의 입력값으로 넣어주는 방식입니다.
- Next 토큰을 예측할 수 있는 모델이었습니다.

- RNN에는 대표적으로 두 가지 단점이 문제점이 있었는데요.
- 과거 Hidden Layer의 출력값의 정보가 점점 소실되는 문제점이 있습니다.

- 또 하나는 GD할때 필요한 Backpropagation시 미분값이 점점 0으로 수렴한다는 문제가 있습니다.
- 이로 인해 긴 문장일때 추론과, 학습이 잘안되어습니다

 

LSTM (Long Short-Term Memory)

  • RNN의 가장 큰 단점 : 과거 정보를 오래 기억하지 못한다
  • 과거 정보가 사라지는 이유 : 이전 정보를 계속 곱하면서 진행하기 때문 (기울기 소실 발생)
  • LSTM 은 입력/출력/망각 게이트를 통해 중요한 정보는 기억하고 필요없는 정보는 버림 (긴 문맥도 이해할 수 있게 됨)
  • LTM 의 한계점 : 문맥 내 모든 단어 간의 관계를 한번에 파악하기 어려움


Attention 과 Transformer 모델

Attention

  • 입력데이터에 중요한 부분에 집중할 수 있게 돕는 기술
  • 문맥을 이해하는 벡터를 만들어주는 역할을 함
- 이를 보안하기 위해 Attention 이 등장했죠.
- 임베딩 벡터 값 끼리 유사성을 비교하여, 문맥표현공간의 벡터값을 만들어내는 것이 Attention 입니다.

- Attention을 이용하면 토큰이 아닌 문장 단위로, 같은 의미의 문맥은 유사한 벡터값을 갖도록 맵핑할 수 있습니다.
- 또한 같은 의미를 가진 사진도, 유사한 벡터값을 갖도록 맵핑할 수 있습니다.

- 여러개의 attention을 더 정교하게 만든 모델이 트랜스포머모델입니다.
- 근래 모델들은 다 트랜스포머모델이죠.

 

Transformer

  • Attention 을 encoder, decoder 형태로 활용하여 Foundation Model 시대를 열게된 딥러닝 모델


Transformer 기반 이미지 모델
  • Transformer 는 텍스트, 이미지, 음성 등 여러 모달을 다루는 모델의 성능을 극대화 시킴
- CNN 대신 트랜스포머 구조를 택하면, 이미지인식 성능이 더 올라갔습니다.

- 이미지 생성도 성능이 올라갔습니다
- 이미지 생성방법인 Diffusion 알고리즘은 다음과 같이 동작했었습니다.
- 학습 : 원본 이미지에 노이즈를 조금 추가하고 어디에 얼마나 노이즈를 추가했는지 예측합니다. 이를 반복합니다. 
- 추론 : 완전한 노이즈로 시작해서, 노이즈를 점점 제거하면서 복원하는 방법으로 이미지를 생성합니다.

- 이 알고리즘 역시 기존에는 CNN 기반 모델을 사용했었는데, 트랜스포머 구조로 성능이 월등히 올라갔습니다.

RAG 2️⃣

 

RAG (Retrieval-Augmented Generation)

  • LLM 이 신뢰성 있는 최신 정보를 참고하여 답변하게 하는 기술
  • 환각 현상을 줄일 수 있음
  • Langchain 사용방법과 함께 RAG 구현 방법을 실습
- 이제 새로운 주제로 넘어갑니다.
- LLM을 활용한 AI App을 만드는 LangChain과 RAG 기술을 학습합니다.
- RAG는 AI Workflow를 만드는 툴로 시작되었습니다

- 다양한 응용 사례가 있지만, 기업에서도 자주 사용되는 RAG 기술에 대해 알아보았습니다.
- RAG는 간단합니다!
- LLM에게 바로 프롬프트를 넣는 것이 아니고,
- 구글링 한번하고 그 결과와 함께 LLM에 입력으로 넣는것이 RAG 기술이죠 
- 꼭 임베딩 벡터를 거친 벡터값만 검색해야하는 건아닙니다. 
- 진짜 검색엔진에서 검색결과를 넣어줘도 RAG 입니다.

- RAG를 써야하는 이유는, 정확도가 더 올라가죠. 
- LLM이 아직 학습하지 못한 최신정보를 알고 답변할 수 있습니다.
- 준비 단계   
- 1. 사내 문서 Text로 추출하여, "Chunk" 라는 단락 단위로 나눕니다.   
- 2. "Chunk" 를 임베딩모델의 입력값으로 넣어, 벡터로 만들고 만듭니다.   
- 3. 벡터를 DB에 저장합니다. 

- Workflow 동작   
- 4. 질문 프롬프트를 입력 받습니다.   
- 5. 임베딩 모델을 거쳐 벡터 값을 얻어낸 후, Retriever로 유사한 Chunk를 검색합니다.   
- 6. 기존 질문과, 검색결과를 합쳐 프롬프트를 완성합니다.   
- 7. 프롬프트를 LLM에 입력으로 넣습니다.   
- 8. 더 멋진 결과가 나옵니다.

- LangChain으로는 다음과 같은 원리고 개발됩니다.

# - 검색할 Text를 준비합니다.

%%writefile shipping_policy.txt

========================
AI 온라인 서점 배송 정책
========================
[일반 배송]
평일 오후 3시 이전 주문 시 당일 발송됩니다.
오후 3시 이후 주문 건은 익일 발송됩니다.
주말 및 공휴일은 배송이 어렵습니다.
도서 산간 지역 배송
제주 및 도서 산간 지역은 추가 배송비 3,000원이 발생할 수 있습니다.

[배송 조회]
배송 관련 문의는 고객센터(1588-0000)로 연락 주시기 바랍니다.

==============
배송 파손 정책
==============
상품이 배송 중 파손된 경우, 즉시 새 상품으로 교환해드립니다.
제품 이상이 확인되면 왕복 배송비는 전액 당사 부담입니다.
수령 후 7일 이내에 고객센터로 연락해 주셔야 합니다.
파손된 상품과 포장 상태 사진을 함께 제출해 주세요.
확인 후, 새 상품을 발송하거나 환불 절차를 진행합니다.
단순 변심이나 부주의로 인한 손상은 교환 대상이 아닙니다.
# - 이제 Text를 청킹합니다.

from langchain_community.document_loaders.text import TextLoader
from langchain_text_splitters import RecursiveCharacterTextSplitter

# 1. Load: 텍스트 파일 불러오기
loader = TextLoader("./shipping_policy.txt", encoding="utf-8")
documents = loader.load()

print("--- 원본 문서 ---")
print(documents[0].page_content)

# 2. Split: 문서를 200자 단위로 자르기 (엔터 단위, 40자씩 겹치게)
splitter = RecursiveCharacterTextSplitter(chunk_size=200, chunk_overlap=40)
chunks = splitter.split_documents(documents)

print("\n--- 200자 단위로 잘린 문서 조각(Chunk)들 ---")
for i, chunk in enumerate(chunks):
    print(f"[Chunk {i+1}]")
    print(chunk.page_content)
    print()
    print()
# - Text를 임베딩 모델을 거쳐 vector로 만들고, VectorDB에 저장합니다.

from langchain_openai import OpenAIEmbeddings
from langchain_chroma import Chroma

embeddings = OpenAIEmbeddings(model="text-embedding-3-small")

# 잘라낸 문서 조각(chunks)들을 임베딩하여 Vector DB에 저장합니다.
vector_store = Chroma(
    embedding_function=embeddings,
    persist_directory="./chroma_store",
)

vector_store.add_documents(chunks)
print("Chunk들을 Vector DB에 저장 완료")
# - 검색기 생성 후 테스트 해봅니다.

retriever = vector_store.as_retriever()

question = "주말에도 배송해주나요?"
retrieved_docs = retriever.invoke(question)

print(f"[질문]: {question}")
print(f"\n[검색 결과]:\n{retrieved_docs[0].page_content}")
# - RAG를 구현합니다.
# - Chain 코드를 보면, Workflow를 확인할 수 있습니다.

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.runnables import RunnablePassthrough
from langchain_core.output_parsers import StrOutputParser

# LLM 모델을 준비합니다.
model = init_chat_model("openai:gpt-4o-mini")

# LLM에게 어떤 역할을 할지 알려주는 프롬프트(지시서)를 만듭니다.
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 AI 온라인 서점의 친절한 고객 서비스 담당자입니다. 주어진 정보를 바탕으로 사용자의 질문에 답변해주세요."),
    ("user", "[질문]: {question}\n\n[참고 정보]: {context}"),
])

# RAG 체인(Chain)을 만듭니다.
rag_chain = (
    {
      "context": retriever,              # 컴포넌트 1 : 전달받은 입력값(question_input1, 2)을 vector db에 검색 후 검색결과를 반환
      "question": RunnablePassthrough()  # 컴포넌트 2 : 전달받은 입력값(question_input1, 2)을 그대로 반환
    }
    | prompt              # 컴포넌트 3 : {context, question} 딕셔너리가 입력되면, 완성된 프롬프트를 반환
    | model               # 컴포넌트 4 : 완성된 프롬프트를 LLM에 전달, 완성된 프롬프트를 반환
    | StrOutputParser()   # 컴포넌트 5. LLM이 반환한 정보 중, Text 답변만 문자열로 반환
)

print("RAG 체인 준비 완료!")
print("----------------------------")
print()
print()

# RAG 체인에 질문을 던져봅니다.
question_input1 = "주말 배송에 대해 알려주세요."
answer1 = rag_chain.invoke(question_input1)
print(f"[질문 1]: {question_input1}")
print(f"[답변 1]: {answer1}")

print()
print()
print("----------------------------")
print()
print()

question_input2 = "제주도로 보내면 배송비가 추가되나요?"
answer2 = rag_chain.invoke(question_input2)
print(f"[질문 2]: {question_input2}")
print(f"[답변 2]: {answer2}")
#- 이후 멀티턴과 ReAct 구조 개념과 구현 방법을 살펴보았습니다.
# - 다음은 멀티턴 코드입니다.
#  - 과거의 정보를 가지고 대화를 하는 것이죠
  
from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

# =============================================
# 2개의 체인 구성
# =============================================
prompt = ChatPromptTemplate.from_template(
    """
    넌 최고의 미식가야
    매우 사랑스럽고, 한 문장으로만 대답하지.

    [지금까지의 대화] :
    {history}

    [질문] :
    {input}
    """
)

model = init_chat_model("openai:gpt-4o-mini")
parser = StrOutputParser()
chain = prompt | model | parser

# =============================================
# 대화 히스토리 저장
# =============================================

history = []

def chat(user_input):
    """한 턴의 대화를 수행하고 history에 추가"""

    # History List를 하나의 문자열로 변경
    formatted_history = "\n".join([f"{m['role']}: {m['content']}" for m in history])

    # Chain 시작! (LLM 답변 출력됨)
    response = chain.invoke({"input": user_input, "history": formatted_history})

    # History List에 질문과 대화 추가하기
    history.append({"role": "user", "content": user_input})
    history.append({"role": "assistant", "content": response})

    return response


# =============================================
# 대화 쉘
# =============================================

print("🍔 미식가 챗봇에 오신 걸 환영합니다!")
print("대화를 종료하려면 'bye'를 입력하세요.\n")

while True:
    user_input = input("👤 인간 : ")

    ai_reply = chat(user_input)
    print(f"🤖 미식가 AI : {ai_reply}\n")

    if user_input == 'bye':
        break
- ReAct 구조로 목적을 이룰 때 까지 툴을 사용하는 기능을 구현할 수 있습니다.

from dataclasses import dataclass
from langchain.agents.middleware import SummarizationMiddleware    # 요약 기능추가
from langchain.agents import create_agent
from langchain.chat_models import init_chat_model
from langchain.tools import tool
from langgraph.checkpoint.memory import InMemorySaver

# =====================================================
# 시스템 프롬프트
# =====================================================
SYSTEM_PROMPT = """너는 최고의 미식가야, 항상 간결하게 말하지
사용자가 요청하면 필요한 도구를 정확히 한 번만 사용하고

너에게는 세 개의 도구가 있어:

1. add_sum(a, b)
   - 두 수의 합을 구합니다.
2. add_minus(a, b)
   - 두 수의 차를 구합니다.
3. add_multiply(a, b)
   - 두 수의 곱을 구합니다.

결과를 이용해 농담 섞인 대답으로 답해줘.
"""

# =====================================================
# 도구 정의
# =====================================================
@tool
def add_sum(a: int, b: int) -> int:
    """두 수의 합을 구합니다."""
    return a + b

@tool
def add_minus(a: int, b: int) -> int:
    """두 수의 차를 구합니다."""
    return a - b

@tool
def add_multiply(a: int, b: int) -> int:
    """두 수의 곱을 구합니다."""
    return a * b

@tool
def get_weather() -> str:
    """오늘의 날씨를 반환합니다."""
    # API 호출 코드 삽입
    return "허리케인"

tool = [add_sum, add_minus, add_multiply, get_weather]

# =====================================================
# 모델 설정
# =====================================================
model = init_chat_model("openai:gpt-4o-mini")

# =====================================================
# 최종 결과 포맷
# =====================================================
@dataclass
class ResponseFormat:
    answer: str   # AI의 대답 문장

# =====================================================
# 멀티턴을 위한 InMemorySaver
# =====================================================
checkpointer = InMemorySaver()

# =====================================================
# Agent 생성 (이곳에 SummarizationMiddleware를 설치합니다.)
# =====================================================
agent = create_agent(
    model=model,
    system_prompt=SYSTEM_PROMPT,
    tools=tool,
    response_format=ResponseFormat,
    middleware=[
    SummarizationMiddleware(
        model="openai:gpt-4o-mini", # 요약을 담당할 모델
        max_tokens_before_summary=4000,  # 최대 4000 토큰으로 요약합니다.
        messages_to_keep=20,  # 요약 이후 최대 20 메세지는 유지합니다.
    )],
    checkpointer=checkpointer
)

# =====================================================
# 실행 예시 (이쁘게 출력하려는 노력입니다.)
# =====================================================
def print_message(msgs):
    for msg in msgs:
        if hasattr(msg, "tool_calls") and msg.tool_calls:
            for tool_call in msg.tool_calls:
                name = tool_call.get("name")
                args = tool_call.get("args")
                if name == "ResponseFormat":
                    continue
                print(f"💭( 🛠️{name} 함수 호출 {args} )")

        if "AIMessage" not in str(type(msg)):
            print(f"💭( {type(msg).__name__} : {msg.content!r} )")

# =============================================
# 대화 루프 (멀티턴)
# =============================================
print("AI Agent 챗봇에 오신 걸 환영합니다!")
print("대화를 종료하려면 'bye'를 입력하세요.\n")
print(f"🤖 AI Agent : 무엇이든 질문하세요")

# thread_id를 통해 대화 세션 유지 (필수)
config = {"configurable": {"thread_id": "kfc_user"}}

while True:
    print()
    user_input = input("👤 인간 : ")

    response = agent.invoke({
        "messages": [
            {"role": "user", "content": user_input}
        ]},config = config) # thread_id 단위로 대화 상태 저장

    print_message(response["messages"][1:-1])
    print(f"🤖 AI Agent : {response["structured_response"].answer}")

    if user_input == 'bye':
        break

PEFT 1️⃣

PEFT (Parameter-Efficient Fine-Tuning)

  • 효율적인 파인튜닝
  • 모든 파라미터를 변경하지 않고, 일부 파라미터만 변경하여 파인튜닝의 효율을 극대화시킴
  • LoRA 를 중심적으로 다룸

 

- 효율적으로 파인튜닝을 하는 방법인 LoRA를 학습하고, 양자화의 개념을 배웁니다.
- LoRA는 Delta W 를 A, B 의 저랭크의 연산으로 근사한 Delta W 를 찾아내는 방법입니다. 
- 이 방법으로 적은량의 파라미터 학습으로 풀 파인튜닝 효과를 낼 수 있었습니다.

- 실습은 Unsloth Library를 사용했습니다.
- Unsloth Library는 저성능, 적은 개수의 GPU로 파인튜닝을 가능하도록 만든 Library 입니다. 
- 허깅페이스 Transformer Library 처럼 추론도 가능합니다. 
- 허깅페이스 Transformer Library 쓰는 것 보다 LoRA 학습 효율이 더 좋다고 합니다.   
- (공식홈페이지 설명에 따르면)   
- **2배 이상 학습속도가 빠름**   
- **70% 이상 GPU 메모리 사용량이 줄어듬** 
- 비교 대상은 허깅페이스의 Flash Attention 2 가속화 옵션 대비입니다.   
- 그런데 구글 코랩의 T4 GPU에서는 Flash Attention 2 가속화 옵션도 못쓰거든요. (유료 GPU만 사용 가능)   
- 그래서 코랩 사용자한테는 Unsloth의 의미가 더더욱 커요

데이터 생성 방법 1️⃣

선형 회귀 2️⃣
import numpy as np
import plotly.graph_objects as go

# =====================================================
# 1. 데이터 준비 (y = 2x + 1 + noise)
# =====================================================
np.random.seed(0)
x = np.linspace(0, 10, 50)
y = 2 * x + 1 + np.random.randn(50) * 2

def mse(a, b):
    y_pred = a * x + b
    return np.mean((y - y_pred) ** 2)

# =====================================================
# 2. 손실 곡면
# =====================================================
a_range = np.linspace(0, 4, 80)
b_range = np.linspace(-2, 4, 80)
A, B = np.meshgrid(a_range, b_range)
Z = np.array([[mse(a, b) for a in a_range] for b in b_range])

# =====================================================
# 3. 경사하강법
# =====================================================
a, b = 0.0, 0.0
lr = 0.025
steps = 100
path = [(a, b, mse(a, b))]

for _ in range(steps):
    y_pred = a * x + b
    da = -2 * np.mean(x * (y - y_pred))
    db = -2 * np.mean(y - y_pred)
    a -= lr * da
    b -= lr * db
    path.append((a, b, mse(a, b)))

path = np.array(path)

# =====================================================
# 4. 그래프 기본 세팅
# =====================================================
fig = go.Figure()

# 손실 곡면
fig.add_trace(go.Surface(
    x=A, y=B, z=Z,
    colorscale='Viridis',
    opacity=0.8,
    showscale=False
))

# 초기점
fig.add_trace(go.Scatter3d(
    x=[path[0,0]],
    y=[path[0,1]],
    z=[path[0,2] - 80],
    mode='text',
    text=["🏃‍♂️"],
    textfont=dict(size=30),
    name='Runner'
))

# =====================================================
# 5. 프레임 생성 (Surface + Path)
# =====================================================
frames = []
for i in range(1, len(path)):
    frames.append(go.Frame(
        data=[
            go.Surface(
                x=A, y=B, z=Z,
                colorscale='Viridis',
                opacity=0.8,
                showscale=False
            ),
            go.Scatter3d(
                x=path[:i,0],
                y=path[:i,1],
                z=path[:i,2],
                mode='lines+markers',
                line=dict(color='red', width=6),
                marker=dict(size=4, color='red')
            )
        ]
    ))

# =====================================================
# 6. 애니메이션 설정
# =====================================================
fig.update_layout(
    title="🎯 Gradient Descent Converging on 3D Loss Surface",
    width=500, height=500,
    template='plotly_white',
    scene=dict(
        xaxis_title='a (slope)',
        yaxis_title='b (intercept)',
        zaxis_title='Loss',

        xaxis=dict(range=[-1, 4]),
        yaxis=dict(range=[-1, 2]),
        zaxis=dict(range=[0, 250]),

        camera=dict(eye=dict(x=1.5, y=1.5, z=1.2))
    ),
    updatemenus=[{
        "type": "buttons",
        "buttons": [
            {"label": "▶ Play (Slow)",
             "method": "animate",
             "args": [None, {
                 "frame": {"duration": 500, "redraw": True},
                 "transition": {"duration": 0},
                 "fromcurrent": True
             }]},
            {"label": "⏸ Pause",
             "method": "animate",
             "args": [[None], {
                 "frame": {"duration": 0, "redraw": False},
                 "mode": "immediate"
             }]}
        ]
    }]
)

fig.frames = frames
fig.show()