카테고리 없음

[플레이데이터 SK 네트웍스 Family AI 캠프 17기] 10주차 회고

오리파 2025. 9. 2. 21:12

 

너무 바빠 회고가 밀린 사이 벌써 어느덧 9월이 되었습니다.

그동안 회고를 꾸준히 작성하지 못한 것을 반성하며,,

9주차 회고를 늦게나마 써보려고 합니다. 

앞으로 꾸준히 써보겠습니다 ㅎㅎ

화이팅!

 

 

 

1. 이번 주에 배운 내용 중 인상적이었던 것

(1) 트랜스포머

 

더보기

1. 트랜스포머란?

  • 순차 데이터(=시퀀스 데이터, 그 중에서도 특히 자연어)를 처리하기 위한 혁신적인 딥러닝 모델.
  • NLP와 컴퓨터 비전 등 다양한 분야에서 사용
  • Self-Attention 메커니즘병렬 연산을 기반으로 하여 RNN 모델의 단점 보완

 

(2) 트랜스포머의 가장 큰 특징

더보기

2. 트랜스포머의 가장 특징적인 구조

  • 인코더 : 자연어 이해하는 역할
  • 디코더 : 인코더에서 이해한 정보를 활용해 출력 시퀀스를 형성하는 역할
  • 인코더 블럭과 디코더 블럭을 여러개 가지고 있음

 

(3) 트랜스포머의 인코더

 

더보기
  • 인코더 부분 : 입력 시퀀스를 처리하여 컨텍스트 정보 생성
  • Positional Encoding : 문장에서 어디에 위치했는지 알게 해주도록 위치 정보 추가해줌. 병렬적으로 단어 정보 처리하기 때문에 위치 정보를 꼭 추가해야함
  • Multi-Head Attention : 어느 것을 중요 포인트로 보고 읽을지 결정해서 다각도에서 자연어 해석해 가중치 계산. 서로 다른 시각에서 데이터 바라보게 함. 

ex. 문장을 읽을 때, 기관을 중심으로, 시간을 중심으로, 인물을 중심으로 읽어서 한꺼번에 고려

  • Self Attention을 병렬적으로 수행하여 다양한 관계 학습
  • 미래 단어를 참조하지 않게 마스크 적용
  • Add & Normalization : 기울기 소실 문제를 해결하기 위해 잔차연결 해준 것과 유사
  • Feed Forward : 신경망으로 연결 . 단어 표현 강화하고 비선형으로 만들어서 더 잘 이해하게 도와줌.

 

(4) 트랜스포머의 디코더

 

더보기
  • 디코더 부분: 인코더의 출력을 받아 최종 출력을 생성
  • Ouput Embedding : 앞에서 생성된 맥락이 들어와서 임베딩 처리
  • Positional Encoding : 위치 정보 추가
  • 확률이 높은 걸 뽑아내기 위해 Softmax 통과해서 추론된 결과로 단어 생성
  • Self-Attention : 각 단어가 다른 단어들과의 관계를 학습하여 문맥 이해

 

2. 일주일 동안 고민한 내용

(1) SentencePiece Tokenizer 문장 시작 & 끝 태그 변경

SentencePiece Tokenizer는 문장을 tokenize하면서

문장 앞뒤에 각각 <s> 와 <\s> 태그를 붙인다.

SentencePiece Tokenizer 객체를 생성하면서 tokenize를 하게 되면 vocab 에

 

이런 식으로 태그가 저장이 된 걸 볼 수 있다.

 

<s> 와 <\s> 태그를 각각 <bos> 와 <eos>로 바꾸어주고 싶어 cmd를 이렇게 수정해보았으나,

cmd = f'--input={input} --model_prefix={model_prefix} --vocab_size={vocab_size} --user_defined_symbols={"<bos>,<eos>"}'

(user_defined_symbols 에 두 개 이상을 넣어줄 때 하나의 문자열에 콤마로 구분해서 넣어줘야함)

 

결과가 예상과는 다르게 나왔다.

 

 

원래 있던 <s>와 <\s> 태그 밑에 <bos>와 <eos>가 또 생겼다.

bos_id 와 eos_id를 조정해봐도 위와 똑같이 나온다.

 

이를 통해 bos_id와 eos_id는 각각 <s> 와 <\s> 의 인덱스를 의미한다는 것을 알게 되었다.

즉, bos_id와 eos_id를 변경해줘도 <bos>와 <eos> 인덱스가 바뀌는 것이 아니라,

<s> 와 <\s> 의 인덱스가 변경된다.

 

이를 해결하기 위해 열심히 알아본 결과,

bos_piece와 eos_piece라는 것을 지정해줄 수 있다는 것을 알게 되었다.

 

cmd = f'--input={input} --model_prefix={model_prefix} --vocab_size={vocab_size} --bos_id={1} --bos_piece={"<bos>"} --eos_id={2} --eos_piece={"<eos>"} --user_defined_symbols={"<bos>,<eos>"}'

 

이렇게 cmd 를 입력해주면 다음과 같이 원하는 결과를 얻을 수 있다.

 

 

 

(2) 보이스 챗봇 만들기

이번주 수업에서 gpt api를 이용한 prompt engineering을 배웠다.

또, tts와 stt 도 살짝 맛 보았다.

이번주 배운 것들을 토대로 주제를 하나 정해 보이스 챗봇을 만들어 보았다.

 

주제: 밸런스 게임 토론을 할 수 있는 챗봇을 만들어보자!

요구사항 : 챗봇의 반응을 보이스로 출력하고, 나의 말도 텍스트가 아닌 보이스로 줄 것!

챗봇 작동 순서

더보기

1. 챗봇에게 밸런스 게임을 하자고 제안하면 챗봇이 주제를 3개 반환

2. 챗봇이 말한 주제 3가지 중 하나를 골라 얘기하면서 토론을 시작

3. 챗봇과 이야기를 하다가 그만하고 싶다는 의사를 전달하면 토론 종료

 

챗봇을 만들 때 가장 신경썼던 부분은 

챗봇의 prompt engineering대화 내용을 기억하게 하는 것이었습니다.

 

[1] prompt engineering 부분

 

하나의 client 가 주제도 만들고 토론도 진행하면 좋겠지만, 

그렇게 할 경우 client에 전달되는 token의 개수가 너무 많아지기 때문에

필요 이상으로 과금되는 것을 막기 위해

주제를 만드는 client토론하는 client를 분리해 생성했습니다.

# 주제 만드는 clinet에 필요한 system_instruction 반환
def get_theme_instruction():
    system_instruction = '''
    당신은 금요일에 퇴근하고 친구들을 만나 맛있는 저녁을 먹고 술을 적당히 마셔 신난 상태입니다. 
    술자리에서 다같이 재미있게 얘기할 만한 연애 밸런스 게임 주제 세 개를 선정해주세요.

    ### 지시사항 ###
    - 토론이 가능한 주제를 선정할 것
    - 성인이니까 선정적인 주제도 하나 정도는 출력할 것
    - 선택하기 어렵고 애매한 주제를 선택할 것
    - 여사친/남사친 문제를 한 개 이상 포함할 것

    ### 출력 형식 ###
    좋아! 재미있는 주제를 세 가지 생각해봤어.

    주제 1:  선택지 vs 선택지.
    주제 2: 선택지 vs 선택지.
    '''
    return system_instruction
    
# 초기 chat_log 설정하고 반환하는 함수
def get_fisrt_chatlog(system_instruction):
    chat_log = [{ 'role': 'system', 'content': [{ 'type': 'text', 'text': system_instruction }] }
            ]
    
    return chat_log
    
# 주제를 만들어주는 함수
def make_theme(chat_log, temperature=0.3):
    client = OpenAI()
    response = client.chat.completions.create(

        model='gpt-4o',
        messages= chat_log,
        response_format={
            'type':'text'
        },
        temperature=temperature,
        max_tokens=2048,
        top_p = 1,
        frequency_penalty=0,
        presence_penalty=0
    )

    return response.choices[0].message.content

 

개인적으로 밸런스 게임을 했을 때 어렵다고 느꼈던 주제들은

선정적이거나 남사친/여사친에 관련된 주제들이었습니다.

그래서 이를 직접적으로 넣어주었습니다.

 

# 토론하는 client의 system_instruction 가져오는 함수
def get_discussion_instruction(theme_output):
    system_instruction = f'''
    당신은 금요일에 퇴근하고 친구들을 만나 맛있는 저녁을 먹고 술을 적당히 마셔 신난 상태입니다.
    상대가 재미있는 밸런스 게임을 해보자면서 3가지 주제를 제안했고 이 중 한 가지를 골랐습니다.

    {theme_output.split(sep=".")[1:]}

    상대가 선정한 주제에 대해 토론해봅시다.
    상대가 생각을 바꿀 수 있도록 영악하고 악독하게 비꼬면서 토론을 해주세요.

    
    ### 토론시 지시사항 ###
    - 3문장 이내로 반말로 대답할 것
    - 이해하는 척 하면서 교묘하게 상대가 선택한 선택지에서 악화될 상황을 상상해 말할 것
    - 상대와 반대 의견을 주장할 것
    - 만약 상대가 '그만하자'라는 말을 하면 "ㅋㅋㅋ 알았어 재밌었다 그치?" 를 그대로 출력할 것
    - 예시를 적극 활용할 것

    
    ### 주제 묻기 출력 예시 ###
    이 중 어떤 주제에 대해 이야기 해볼까?

    '''
    return system_instruction
    
 # 고른 주제로 토론하게 하는 함수
def discuss(chat_log, temperature=0.3):
    client = OpenAI()
    response = client.chat.completions.create(

        model='gpt-4o',
        messages= chat_log,
        response_format={
            'type':'text'
        },
        temperature=temperature,
        max_tokens=2048,
        top_p = 1,
        frequency_penalty=1,
        presence_penalty=0
    )

    return response.choices[0].message.content

 

내가 챗봇이 출력한 세 가지 주제 중 하나 선정하면

토론하는 client가 주제 3개와 내가 고른 주제를 입력받아 이에 대해 토론할 수 있도록 했습니다.

 

이때, 챗봇이 나를 설득하게 할 수 있도록

비꼬거나 부정적인 상황을 가정해 이야기하도록 했고,

대화하는 것이기 때문에 3문장 이내로 답하도록 지시했습니다.

 

그리고 이 과정에서 내가 비슷한 말을 반복하더라도

똑같은 값을 출력하지 않도록 penalty를 주었습니다.

 

그만하자는 뉘앙스로 이야기하면 자연스레 종료되게끔 지시도 해주었습니다.

 

 

 

[2] 대화 내용 기억하기

 

gpt api를 이용해 client를 만들면 아무리 같은 client 여도 대화내용을 기억하지 못합니다.

그래서 이를 해결하기 위해서 chat_log에 지속적으로

client 응답과 나의 응답이 기록되고 이를 client에 전달할 수 있도록 함수를 만들었습니다.

 

# chat_log 를 수정하는 함수
def modify_chatlog(chat_log, new_message):
    if len(chat_log)%2 ==0:
        chat_log.append({ 'role': 'assistant', 'content': [{ 'type': 'text', 'text': new_message }] })
    else:
        chat_log.append({ 'role': 'user', 'content': [{ 'type': 'text', 'text': new_message }]  })
    return chat_log

 

초기 chat_log 에는 'system' 만 존재합니다.

chat_log 의 길이가 짝수일 때에는 'assistant'의 응답이,

길이가 홀수일 때에는 'user'의 응답이 저장될 수 있도록 함수를 만들어

챗봇이나 사용자가 말하는 것들을 계속해서 chat_log에 붙여넣을 수 있도록 했습니다.

 

그 결과, 이런 식으로 대화를 잘 기억하는 챗봇을 만들 수 있었습니다.

 

 

이를 스트림릿으로 구현해보면 어땠을까 하는 아쉬움이 있었습니다.

또, 마음에 들지 않는 주제가 나와도 어쩔 수 없이 출력한 것 중에 골라야한다는 단점이 있었습니다.

 

그래서 다음주에 이를 발전시켜 

주제가 마음에 안 들면 바꾸어달라고 요청할 수 있게 만들어보고,

streamlit 사이트로 이를 구현해보고자 합니다.