경험상 seed는 내가 조정할 수 있는 영역이 아니다. 물론 최적의 결과가 나오면 그 seed로 설정해야 하겠지만 내가 seed 값을 처음에 어떤게 나올지 모르는 난수영역이니 일단 건들지 않기!
내가 원하는건 어떤 값인지는 알잖아?
일단 플랜을 어떻게 프론트한테 주는지 다시한번 보자.
entity는 대략 이렇다
public class Plan extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
@NotNull(message = "유저는 필수입니다.")
private User user;
@NotBlank(message = "제목은 공백일 수 없습니다.")
@Size(max = 100, message = "제목은 100자를 넘을 수 없습니다.")
@Column(length = 100, nullable = false)
private String title;
@Size(max = 1000, message = "설명은 1000자를 넘을 수 없습니다.")
@Column(columnDefinition = "TEXT")
private String description;
@Column(nullable = false)
private LocalDateTime startDate;
@Column(nullable = false)
private LocalDateTime endDate;
@Builder.Default
@Column(nullable = true)
private Boolean accessibility = true;
@Builder.Default
@Column(nullable = false)
private Boolean isCompleted = false;
}
그래서 이를 바탕으로 학습 데이터를 생각해보면,
{
"prompt": "이번 주 내로 토익 공부하고 싶어",
"completion": [
{
"title": "토익 공부 - 대안 1",
"description": "이번 주 금요일 저녁 7시부터 2시간 동안 집중적으로 토익 공부",
"startDate": "<발화 시점 이후 금요일 저녁 7시>",
"endDate": "<금요일 저녁 9시>",
"accessibility": true,
"isCompleted": false
},
{
"title": "토익 공부 - 대안 2",
"description": "토요일 오전 10시부터 3시간 동안 학습 계획",
"startDate": "<발화 시점 이후 토요일 오전 10시>",
"endDate": "<토요일 오후 1시>",
"accessibility": true,
"isCompleted": false
},
{
"title": "토익 공부 - 대안 3",
"description": "일요일 오후 2시부터 4시간 동안 집중 학습",
"startDate": "<발화 시점 이후 일요일 오후 2시>",
"endDate": "<일요일 오후 6시>",
"accessibility": true,
"isCompleted": false
}
]
}
이런 느낌일 것이다
물론 startDate와 endDate 의 < > 안은 LocalDateTime 형식이니 바꿀 것이다.
GPT API 에서는 jsonl 이라는 파일을 요구하고 있다.
jsonl을 달라고 요구하고 있다.
json 이랑 별 차이 없겠지 뭐 라고 생각한 순간.. 그래도 알아보자라는 생각으로 찾아보았더니
json + line이라고 한다.
그러니까, 각 라인이 Json객체로 이루어진 파일 형식이다.
쉽게 생각하면 한줄에 사전하나씩 여러개가 같이 있는 것.
이 파일에 조건이 또 있다. 차라리 그냥 한줄에 사전하나씩 여러개가 있는것으로 끝나면 그냥 한줄에 쓰면 되지뭐~ 했지만..
json line을 정의하기위해서 몇가지 규칙이있다.(http://jsonlines.org/)
UTF-8 인코딩일 것.
각 줄은 유효한 json형식이어야 함.
줄 바꾸기 문자 "\n"으로 각 객체를 구분.
너 진짜 뭐 돼?
좋은 파인 튜닝을 위해 꼭 써야하는거니 변환? 해봐야지
일단 json을 만들어봤다.
이제 우리가 학습이나 평가 파일을 만들때마다 어떠한 웹사이트에 의존할 수 없으니 팀원들이 쓸 파이썬 파일을 만든다.
import json
def convert_json_to_jsonl_with_alternatives(input_json_path, output_jsonl_path):
with open(input_json_path, 'r', encoding='utf-8') as json_file:
data = json.load(json_file)
with open(output_jsonl_path, 'w', encoding='utf-8') as jsonl_file:
jsonl_item = {
"prompt": data["prompt"],
"completion": data["completion"] # completion 리스트에 대안 3개 포함
}
jsonl_file.write(json.dumps(jsonl_item, ensure_ascii=False) + '\\n')
print(f"JSONL file saved to {output_jsonl_path}")
# 사용 예시
input_json_path = './test.json'
output_jsonl_path = './test_output.jsonl'
convert_json_to_jsonl_with_alternatives(input_json_path, output_jsonl_path)
이렇게 하면..
{"prompt": "이번 주 내로 토익 공부하고 싶어", "completion": [{"title": "토익 공부 - 대안 1", "description": "이번 주 금요일 저녁 7시부터 2시간 동안 집중적으로 토익 공부", "startDate": "2024-10-25T19:00:00", "endDate": "2024-10-25T21:00:00", "accessibility": true, "isCompleted": false}, {"title": "토익 공부 - 대안 2", "description": "토요일 오전 10시부터 3시간 동안 학습 계획", "startDate": "2024-10-26T10:00:00", "endDate": "2024-10-26T13:00:00", "accessibility": true, "isCompleted": false}, {"title": "토익 공부 - 대안 3", "description": "일요일 오후 2시부터 4시간 동안 집중 학습", "startDate": "2024-10-27T14:00:00", "endDate": "2024-10-27T18:00:00", "accessibility": true, "isCompleted": false}]}
이런식으로 한개의 json이 한줄로 작성이 된다.
이제 prompt 부분을 손봐야한다. 유저들에게는 보이지 않을 그 부분을 손봐야한다.
예시로는 “이번 주 내로 토익 공부하고 싶어” 라고 했을때, (다른 일정이 있으면 겹치지 않게, 내가 말한 시간 이후로) 이런 느낌으로 가야한다.
그래서 이걸 어떻게 해야할까…
그냥 이걸 하드코딩 해버려야할까? 저걸 그대로 구현할 수 있을까? 의문…
자 뭐 그까짓거 해봅시다.
일단 json 형식을
{
"deviceId": "abc123",
"planGroups": [
{
"groupId": 1,
"planCards": [
{
"title": "토익 공부",
"description": "금요일 저녁 7시부터 2시간 동안 집중적으로 토익 공부",
"startDate": "2024-10-25T19:00:00",
"endDate": "2024-10-25T21:00:00",
"accessibility": true,
"isCompleted": false
},
{
"title": "코딩 테스트 준비",
"description": "토요일 오후 4시부터 2시간 동안 문제 풀이",
"startDate": "2024-10-26T16:00:00",
"endDate": "2024-10-26T18:00:00",
"accessibility": true,
"isCompleted": false
}
]
},
{
"groupId": 2,
"planCards": [
{
"title": "토익 공부",
"description": "토요일 오전 10시부터 3시간 동안 학습 계획",
"startDate": "2024-10-26T10:00:00",
"endDate": "2024-10-26T13:00:00",
"accessibility": true,
"isCompleted": false
},
{
"title": "코딩 테스트 준비",
"description": "일요일 오전 9시부터 3시간 동안 문제 풀이",
"startDate": "2024-10-27T09:00:00",
"endDate": "2024-10-27T12:00:00",
"accessibility": true,
"isCompleted": false
}
]
},
{
"groupId": 3,
"planCards": [
{
"title": "토익 공부",
"description": "일요일 오후 2시부터 4시간 동안 집중 학습",
"startDate": "2024-10-27T14:00:00",
"endDate": "2024-10-27T18:00:00",
"accessibility": true,
"isCompleted": false
},
{
"title": "코딩 테스트 준비",
"description": "일요일 오후 1시부터 2시간 동안 코딩 문제 풀이",
"startDate": "2024-10-27T13:00:00",
"endDate": "2024-10-27T15:00:00",
"accessibility": true,
"isCompleted": false
}
]
}
]
}
이런식으로 하면 여러 플랜을 한번에 받을 수 있다.
아 코드 그럼 다시 짜야하잖아
또 gpt가 알아듣게끔 현재 한국 시간기준, 내가 입력한 시간 이후로 해줘 이런 식으로 해야한다.
복잡하네 정말^_^
그걸 토대로한 condition.json
[
{
"key": "avoid_schedule_conflicts",
"condition": "다른 일정이 있으면 겹치지 않게 해줘",
"required": true
},
{
"key": "start_after_user_input_time",
"condition": "내가 말한 시간 이후로 계획을 세워줘",
"required": true
},
{
"key": "use_korean_timezone",
"condition": "일정은 한국 시간(UTC+9)을 기준으로 설정해줘",
"required": true
}
]
이 condition.json이 유저 입력에 녹아들어서 4o mini가 알아서 일정을 짜줄거다.
그래 이걸 바탕으로 파이썬 파일을 다시 짜보자.
import json
from datetime import datetime
def load_conditions(condition_file_path):
with open(condition_file_path, 'r', encoding='utf-8') as condition_file:
conditions = json.load(condition_file)
current_time = datetime.now().strftime('%Y-%m-%dT%H:%M:%S')
for condition in conditions:
condition["condition"] = condition["condition"].replace("${current_time}", current_time)
return conditions
def load_prompt(prompt_file_path):
with open(prompt_file_path, 'r', encoding='utf-8') as prompt_file:
prompt = prompt_file.read().strip()
return prompt
def create_prompt_with_conditions(user_input, conditions):
prompt = user_input
condition_statements = [condition["condition"] for condition in conditions if condition.get("required", False)]
if condition_statements:
prompt += " (" + ", ".join(condition_statements) + ")"
return prompt
def convert_json_file_to_jsonl(input_json_path, condition_file_path, prompt_file_path, output_jsonl_path):
with open(input_json_path, 'r', encoding='utf-8') as json_file:
input_data = json.load(json_file)
if not isinstance(input_data, list):
raise TypeError("input_json 파일이 리스트 형식이 아닙니다.")
conditions = load_conditions(condition_file_path)
user_input = load_prompt(prompt_file_path)
prompt = create_prompt_with_conditions(user_input, conditions)
with open(output_jsonl_path, 'w', encoding='utf-8') as jsonl_file:
jsonl_file.write(json.dumps({"prompt": prompt}, ensure_ascii=False) + '\\n')
for item in input_data:
jsonl_file.write(json.dumps(item, ensure_ascii=False) + '\\n')
print(f"JSONL file saved to {output_jsonl_path}")
# 사용 예시
input_json_path = './input_data.json'
condition_file_path = './conditions.json'
prompt_file_path = './prompt.txt'
output_jsonl_path = './test_output.jsonl'
convert_json_file_to_jsonl(input_json_path, condition_file_path, prompt_file_path, output_jsonl_path)
일단 해봐야지
prompt.txt는 일단 테스트용이니 뭐..
진짜 다행히도 잘나온다
json에서 jsonl로 변환된 파일:
{"prompt": "\\"이번 주 내로 토익 공부하고 싶어, 그리고 코딩 테스트 준비하고 싶어\\" (다른 일정이 있으면 겹치지 않게 해줘, 현재 시간 이후로 계획을 세워줘 (2024-10-27T17:33:05 기준), 일정은 한국 시간(UTC+9)을 기준으로 설정해줘)"}
{"completion": {"deviceId": "abc123", "groupId": 1, "planCards": [{"title": "토익 공부", "description": "10월 27일 일요일 오후 6시부터 2시간 동안 집중 학습", "startDate": "2024-10-27T18:00:00", "endDate": "2024-10-27T20:00:00", "accessibility": true, "isCompleted": false}, {"title": "코딩 테스트 준비", "description": "10월 28일 월요일 오전 9시부터 2시간 동안 문제 풀이", "startDate": "2024-10-28T09:00:00", "endDate": "2024-10-28T11:00:00", "accessibility": true, "isCompleted": false}]}}
{"completion": {"deviceId": "abc123", "groupId": 2, "planCards": [{"title": "토익 공부", "description": "10월 28일 월요일 오전 11시부터 3시간 동안 학습 계획", "startDate": "2024-10-28T11:00:00", "endDate": "2024-10-28T14:00:00", "accessibility": true, "isCompleted": false}, {"title": "코딩 테스트 준비", "description": "10월 28일 월요일 오후 3시부터 2시간 동안 문제 풀이", "startDate": "2024-10-28T15:00:00", "endDate": "2024-10-28T17:00:00", "accessibility": true, "isCompleted": false}]}}
{"completion": {"deviceId": "abc123", "groupId": 3, "planCards": [{"title": "토익 공부", "description": "10월 29일 화요일 오전 10시부터 2시간 동안 집중 학습", "startDate": "2024-10-29T10:00:00", "endDate": "2024-10-29T12:00:00", "accessibility": true, "isCompleted": false}, {"title": "코딩 테스트 준비", "description": "10월 29일 화요일 오후 1시부터 3시간 동안 문제 풀이", "startDate": "2024-10-29T13:00:00", "endDate": "2024-10-29T16:00:00", "accessibility": true, "isCompleted": false}]}}
위 python파일을 실행하기 위해서는 일단 노가다가 필요하다.
저렇게 하면 된다며… 공식문서도 그렇다며,,,
{"prompt": "<prompt text>", "completion": "<ideal generated text>"}
공식문서가 이렇게 하라는데.. 그래 그렇게 해보자!
그냥 접을까? 파인튜닝 그게 뭐라고^^
그래.. 그래도 해야지. 다시 알아보자.
제대로 다시 찾아보자.
OpenAI의 chat 모델(gpt-4-turbo 등)로 파인튜닝을 할 때, 요구되는 데이터 포맷이 일반 prompt-completion 형식과 다르기 때문에 발생한 문제입니다. chat 모델은 ‘메시지’ 형식이 필요합니다. 각 레코드는 단순한 prompt와 completion으로 구분되는 대신, message 형식의 JSON 객체로 변환해야 합니다.
데이터는 다음처럼 {"messages": [{"role": "user", "content": "질문 내용"}, {"role": "assistant", "content": "응답 내용"}]} 형식을 유지해야 합니다.
그래서 진짜 해야하는건 어떤 형식일까요
{"messages": [{"role": "user", "content": "질문 내용"}, {"role": "assistant", "content": "응답 내용"}]}
네. 이런 형식입니다…
아이고 두야….
그럼 이 형식을 바탕으로 다시 jsonl을 작성해보자.
{"messages": [{"role": "user", "content": "\\"다음 주에 있을 프로젝트 발표 준비하고 싶어. 그리고 이번 주말에는 가족 모임이 있어\\" (다른 일정이 있으면 겹치지 않게 해줘, 기존일정:[{\\"title\\": \\"데이터베이스 시험 준비\\", \\"description\\": \\"10월 30일 수요일 오후 1시부터 3시간 동안 개념 정리\\", \\"startDate\\": \\"2024-10-30T13:00:00\\", \\"endDate\\": \\"2024-10-30T16:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"데이터베이스 시험 준비\\", \\"description\\": \\"10월 31일 목요일 오후 3시부터 4시간 동안 심화 문제 풀이\\", \\"startDate\\": \\"2024-10-31T15:00:00\\", \\"endDate\\": \\"2024-10-31T19:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"영화 관람\\", \\"description\\": \\"11월 2일 토요일 오후 2시부터 3시간 동안 친구들과 영화 관람\\", \\"startDate\\": \\"2024-11-02T14:00:00\\", \\"endDate\\": \\"2024-11-02T17:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"저녁 약속\\", \\"description\\": \\"11월 2일 토요일 저녁 6시 30분부터 2시간 동안 친구들과 저녁 식사\\", \\"startDate\\": \\"2024-11-02T18:30:00\\", \\"endDate\\": \\"2024-11-02T20:30:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"병원 방문\\", \\"description\\": \\"11월 4일 월요일 오전 9시부터 1시간 동안 병원 방문\\", \\"startDate\\": \\"2024-11-04T09:00:00\\", \\"endDate\\": \\"2024-11-04T10:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"데이터베이스 시험 준비\\", \\"description\\": \\"11월 4일 월요일 오후 1시부터 5시간 동안 종합 복습\\", \\"startDate\\": \\"2024-11-04T13:00:00\\", \\"endDate\\": \\"2024-11-04T18:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"데이터베이스 시험 준비\\", \\"description\\": \\"11월 5일 화요일 오전 10시부터 6시간 동안 최종 마무리\\", \\"startDate\\": \\"2024-11-05T10:00:00\\", \\"endDate\\": \\"2024-11-05T16:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}] 현재 시간 이후로 계획을 세워줘 (2024-10-29T20:41:00 기준), 일정은 한국 시간(UTC+9)을 기준으로 설정해줘)\\""}, {"role": "assistant", "content": "{\\"deviceId\\": \\"xyz987\\", \\"groupId\\": 3, \\"planCards\\": [{\\"title\\": \\"프로젝트 발표 준비\\", \\"description\\": \\"발표 준비 - 주제 탐색 및 기초 자료 수집\\", \\"startDate\\": \\"2024-10-30T10:00:00\\", \\"endDate\\": \\"2024-10-30T12:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"프로젝트 발표 준비\\", \\"description\\": \\"발표 준비 - 발표 연습\\", \\"startDate\\": \\"2024-11-01T13:00:00\\", \\"endDate\\": \\"2024-11-01T15:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}, {\\"title\\": \\"가족 모임\\", \\"description\\": \\"주말 점심 가족 모임\\", \\"startDate\\": \\"2024-11-03T12:00:00\\", \\"endDate\\": \\"2024-11-03T14:00:00\\", \\"accessibility\\": true, \\"isCompleted\\": false}]}"}]}
{"messages": [{"role": "user", "content": "Hello"}, {"role": "assistant", "content": "올바른 입력을 해주세요. 형식: '이번 주 영어 공부와 수학 시험을 준비하고 싶어'와 같이 입력할 수 있습니다."}]}
대충 이런식 x 100개 정도..? 솔직히 이것도 최소수치라 학습 데이터로 잘 써야한다.
또, 프롬프팅문도 진짜 잘 작성해야한다. user는 정말 대충 말할 수도 있어서, 뒤에서 알아서 프롬프팅을 해줘야한다. 그게 프롬프팅+파인튜닝의 매력이 아닐까?
ok 일단 테스트용으로 학습 돌려 봅시다!
하..진짜 이거 하나보자고 너무 매달렸다..진짜…
이것마저 잘되다니 진짜 너무 좋다…
또, 이상한 요청에 대한 학습도 했었는데, 그건?
거의 뭐 이제 MVP 된 느낌..
이제 gpt가 뱉어내는 값중 deviceId만 가변 값(예시:<USER_DEVICE_ID>)으로 바꾸면 될 것 같다.
이제 프롬프팅을 잘 바꿔보자!
일정이 없는 경우: