ONCE는 카드 결제 전 보유한 카드 중 결제처에서 최적의 혜택을 받을 수 있는 카드를 미리 추천해주는 AI 모바일 어플리케이션이다.
[ 매년 사라지는 카드 포인트 1000억원, 이대로 괜찮을까? ]
우리나라 1인당 신용카드 보유 개수는 3.6장이며, 개인의 신용카드 보유율은 80.2%에 달한다. 하지만 전체 발급 카드 중 1년 동안 사용 실적이 없는 휴면카드가 1300만장에 육박하며, 매년 사라지는 카드 포인트 1000억원이 소비자가 아닌 카드사의 부가 수입으로 들어가는 이유에 의문을 가지고 프로젝트를 시작하게 되었다.
필요 기술 1 : 스크래핑 기반 금융 API를 이용한 사용자 정보 조회 (*스크래핑 : 각 카드사와 통신하여 실제 데이터를 제공)
→ CODEF API를 이용하여 실제 카드 보유 목록, 혜택 정보 및 실적을 불러올 수 있다.
필요 기술 2 : 크롤링을 이용한 카드사 별 카드 혜택 정보 실시간 수집
→ 파이썬 크롤링 라이브러리 BeautifulSoup, Selenium, Requests 등을 이용하여 카드별 혜택의 상세 정보를 불러올 수 있다.
필요 기술 3 : 자연어 처리 사전 훈련 언어 모델을 활용한 카드 혜택 학습
→ 사용자가 이용 중인 카드가 어떤 혜택을 제공하는지 분석하기 위해 수집한 상세 카드 혜택 정보를 BERT 모델을 활용하여 이해 및 학습시킨다.
필요 기술 4 : 트랜스포머 모델을 활용한 사용처에 맞는 최적의 카드 추천
→ 자연어 처리 기술을 활용하여 아이템을 벡터로 표현한 후, 사용자가 입력한 결제처와의 유사도를 계산하여 사용처에서 최적의 혜택을 제공하는 카드를 추천한다.
필요 기술 5 : 챗봇을 통한 사용자와의 상호작용
→ ChatGPT API를 활용하여 메인화면에서 사용자와의 주요 상호작용을 처리할 수 있도록 한다.
API란?
응용 프로그램에서 사용할 수 있도록, 운영 체제나 프로그래밍 언어가 제공하는 기능을 제어할 수 있게 만든 인터페이스
어떠한 응용프로그램에서 데이터를 주고 받기 위한 방법
오픈 API:
오픈API란 누구나 사용할 수 있도록 공개된 API
데이터를 표준화하고 프로그래밍해 외부 소프트웨어 개발자나 사용자가 바로 개발(어프리케이션)에 활용할 수 있는 형태의 개방 형식
업데이트가 빈번하고 활용도가 높은 대용량의 데이터를 연계 개발할 때, 날씨나 교통 정보 등 실시간 업데이트되는 데이터를 연계가 필요할 때 유용
ONCE에서의 필요
ONCE는 사용자에게 맞춤화된 서비스를 제공하기 위해서, 사용자의 카드 관련 정보들이 필요하다.
기본적으로 사용자가 보유하고 있는 카드의 종류를 파악하고 있어야 하고, 전월 실적을 파악해 혜택의 조건을 고려할 때 활용해야 한다. 또한 이번 달의 받은 혜택 정보와 실적이 얼마나 채워졌는지 등의 정보를 실시간 업데이트 하여 저장할 수 있어야 한다.
ONCE는 금융, 카드 관련 서비스로 보안 또한 중요한 지점이기 때문에, 이 정보들을 믿을 수 있는 방식으로 활용 가능한 금융 API를 활용하기로 결정하였다.
1) accessToken, connectedID 발급
urls.py
path('tocken/',tockenView),
views.py
@api_view(['GET'])
def tockenView(request):
client_id="[키 관리>client_id]"
client_secret="[키 관리>client_secret]"
url='https://oauth.codef.io/oauth/token'
authHeader = stringToBase64(client_id + ':' + client_secret).decode("utf-8")
headers = {
'Acceppt': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
'Authorization': 'Basic ' + authHeader
}
response = requests.post(url, headers = headers, data = 'grant_type=client_credentials&scope=read')
print(response.status_code)
response_text=response.text
json_data = json.loads(response_text)
access_token = json_data.get('access_token')
필요한 정보(client_id,client_secret)를 담아 https://oauth.codef.io/oauth/token
에 요청을 보내 access_token
을 받는다.
공동인증서를 이용한 로그인도 가능하지만, 서비스 이용 비용이 있어 카드사별 계정으로 직접 로그인하는 방식을 사용한다.
bank_code='0306' # 신한 코드
user_id='[신한 카드 홈페이지 아이디]'
user_password='[신한 카드 홈페이지 패스워드]'
pubkey="[키 관리>public_key]"
codef_account_create_url = 'https://development.codef.io/v1/account/create'
codef_account_create_body = {
'accountList':[
{
'countryCode':'KR', # KOREA
'businessType':'CD', # CARD
'clientType':'P', # PERSONAL
'organization':bank_code,
'loginType':'1', # 아이디 패스워드로 로그인
'id':user_id,
'password':publicEncRSA(pubKey, user_password) # 퍼블릭 키로 패스워드 암호화
}
]
}
response_account_create = http_sender(codef_account_create_url, access_token, codef_account_create_body)
decoded_response_text = urllib.parse.unquote(response_account_create.text) # URL 디코딩
필요한 파라미터들을 담아 데이터를 만들고, 해당 url에 요청한다. 이때 CODEF에서 제공해주는 API 명세서를 잘 참고해야 한다.
try:
response_data = json.loads(decoded_response_text)
connected_id = response_data['data']['connectedId']
return render(request,'home.html',{'tocken':access_token,'connectedID':connected_id})
# 예외처리
완료되어 얻은 access_token과 connected_id를 담아 home.html를 호출한다.
home.html은 access_token과 connected_id를 화면에 보여주고 세가지 api를 호출하는 버튼으로 구성하였다.
2) 보유 카드 조회
보유카드 조회 버튼 클릭 시 실행되는 함수이다.
@api_view(['GET'])
def mycardView(request,tocken,connectedID):
codef_mycard_url = 'https://development.codef.io/v1/kr/card/p/account/card-list'
codef_account_create_body = {
'organization':'0306',
'connectedId':connectedID,
'loginType':'1',
'inquiryType':'1'
}
response_account_create = http_sender(codef_mycard_url, tocken, codef_account_create_body)
my_data=unquote(response_account_create.text)
my_data=json.loads(my_data)
my_data=json.dumps(my_data,ensure_ascii=False)
return render(request,'home.html',{'my_data': my_data,'tocken':tocken,'connectedID':connectedID})
토큰을 header에, connectedId와 카드회사, 로그인type등 정보를 담아 보유카드를 조회하는 EndPoint에 정보를 전달한다.
그 결과는 다음과 같다.
카드 이름과 카드 번호, 카드 종류 뿐만아니라 교통카드 기능이 있는지, 카드 이미지 등의 정보가 반환된다.
3) 승인 내역 조회
(2)번 보유 카드 조회처럼 api 명세서의 필수 파라미터들을 담고, 해당 엔드포인트에 요청을 보내면 된다.
카드 승인 내역 정보로, 날짜와 시간, 금액과 사용한 결제처 등의 정보를 확보 가능하다. 사용자가 자주 들른 매장인 경우 GPS를 이용해 근처일 경우 알림을 울리는 기능을 구현할 때, 사용자의 단골 매장을 찾는데 사용될 API이다.
4) 실적 현황 조회
각 카드가 가지고 있는 혜택들과 해당 혜택을 받기 위한 실적이 채워졌는지 여부를 알려준다.
이번 달 해당 카드로 사요한 금액도 확인이 가능하다.
1) 정적 크롤링, csv파일로 저장
import csv
csvFile = open('kb_test.csv', 'w+')
writer = csv.writer(csvFile)
writer.writerow(('card name', 'part', 'discount','notice'))
카드 혜택을 저장할 csv파일을 연다. 칼럼명에는 카드이름, 혜택 분야, 할인 내용, 유의사항이 있다.
from urllib.request import urlopen
from bs4 import BeautifulSoup
html=urlopen('https://card.kbcard.com/CRD/DVIEW/HCAMCXPRICAC0076?mainCC=a&cooperationcode=09123')
bs=BeautifulSoup(html,'html.parser')
urlopen함수를 이용해 원하는 카드 상품의 페이지를 연다.
name = bs.find('h1', {'class':'tit'}).get_text()
soups=bs.findAll('div',{'id':'tabCon011'})
for i in range(1, 3):
partname=soups[i].find('h2').text[7:]
parts=soups[i].findAll('div', {'class':'titArea'})
for part in parts:
discount=part.find('h2', {'class':'titDep2'}).text
notice=part.find('div', {'class':'benefitBox1'}).text
writer.writerow((name,partname,discount,notice))
html의 구조를 파악하고, 원하는 정보의 class, id등을 이용하여 경로를 찾고 값을 저장한다.
writerow
함수로 찾은 값들을 csv파일에 한줄씩 적는다.
생성된 결과는 다음과 같다. 카드의 이름과 혜택 분야별 할인 내용이 잘 저장되었음을 확인 가능하다.
저장이 완료되면 csvFile.close()
명령어로 csv파일을 닫는다.
2) 동적 크롤링
# selenium의 webdriver를 사용하기 위한 import
from selenium import webdriver
from selenium.common.exceptions import TimeoutException
# selenium으로 키를 조작하기 위한 import
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
# 페이지 로딩을 기다리는데에 사용할 time 모듈 import
import time
필요 라이브러리들을 import
# 크롬드라이버 실행
driver = webdriver.Chrome()
url = 'https://card.kbcard.com/CRD/DVIEW/HCAMCXPRICAC0076?mainCC=a&cooperationcode=09123'
#크롬 드라이버에 url 주소 넣고 실행
driver.get(url)
# 페이지가 완전히 로딩되도록 3초동안 기다림
time.sleep(2)
크롬드라이버를 이용해 해당 url의 페이지를 연다.
# 링크 클릭
link = driver.find_element(By.ID,'topTab1').find_element(By.TAG_NAME,'a')
link.click()
상세 혜택을 보기 위해 "상세 혜택" 버튼을 클릭한다. 크롬 드라이버에서 코드의 명령에 따라 click해준다.
# 페이지 소스 가져오기
page_source = driver.page_source
# BeautifulSoup을 사용하여 파싱
soup = BeautifulSoup(page_source, 'html.parser')
# 원하는 데이터 추출
title = soup.find('div', {'id':'tabCon010'}).get_text()
클릭을 통해 새롭게 나타난 페이지에서 원하는 데이터를 추출한다.
동적 크롤링만으로 정보를 얻을 수 없는 카드사 사이트인 경우 위와 같이 Selenium을 이용해 원하는 데이터를 추출할 예정이다. csv파일에 저장하는 과정은 (1)과 같으므로 생략하겠다
3) AWS RDS에 연결해 크롤링한 데이터를 바로 저장
host_name = "[RDS 엔드포인트]"
username = "[rds username]"
password = "[rds password]"
database_name = "[데이터베이스 이름]"
db = pymysql.connect(
host=host_name,
port=3306,
user=username,
passwd=password,
db=database_name,
charset='utf8'
)
이 코드를 실행하여 AWS의 RDS와 연결한다
cursor.execute("DROP DATABASE IF EXISTS once;")
db.commit()
cursor.execute("CREATE DATABASE itta DEFAULT CHARSET=utf8 COLLATE=utf8_bin;")
db.commit()
cursor.execute("USE once;")
db.commit()
cursor.execute("DROP TABLE IF EXISTS `CARD`;")
db.commit()
cursor.execute("""
CREATE TABLE `CARD` (
# 테이블 구성..
) DEFAULT CHARSET=utf8;
""")
db.commit()
SQL문을 직접 입력하여 동작을 수행한다. 이 코드는 CARD 테이블을 생성한다.
이후 (1), (2)처럼 원하는 데이터를 추출하는 크롤링을 진행한다.
원하는 데이터들을 변수에 저장해둔 뒤,
cursor.execute()``db.commit()
을 이용해 INSERT문을 실행하면 된다.
pip install openai
를 입력해 openai 관련 패키지를 다운받는다.import os
import openai
openai.api_key = "[API 키]"
response = openai.Completion.create(
model = "text-davinci-003",
prompt = "my name is chaerin.\n\nQ: who am I?\nA:",
temperature = 0,
max_tokens=100,
top_p = 1,
frequency_penalty = 0.0,
presence_penalty = 0.0,
stop = ["\n"]
)
print(response)
print(response.choices[0].text.strip())
model
: openai가 제공해주는 모델 중 사용할 모델의 이름
prompt
: 원하는 실행어 입력 - 좋은 결과를 위해 많은 테스트 필요
max_tokens
: 입력+출력 값으로 잡을 수 있는 최대 토큰 길이. 초과되면 에러가 난다 (한글은 토큰이 많이 쓰여 영어 위주로 사용할 예정)
stop
: stop 지점을 설정한다. 여기서는 "\n"이다
결과는 다음과 같다. 내 이름을 알려준 뒤 내가 누군지 물었는데, 의도대로 내 이름을 답해준다.
OpenAI의 Fine-tuning API 장점
1) 프롬프트 디자인보다 높은 품질의 결과
2) 프롬프트에 맞지 않는 예제를 학습할 수 있는 능력
3) 짧은 프롬프트로 인한 토큰 절약
4) 낮은 지연 요청 시간
파인튜닝은 프롬프트에 맞지 않는 예제를 포함한 훨씬 많은 예제로 학습함으로써 다양한 작업에서 더 나은 결과를 얻을 수 있다. 모델을 파인튜닝한 후에는 더 이상 프롬프트에서 예제를 제공할 필요가 없기에 비용을 절감하고 지연 시간이 낮은 요청이 가능하다.
모델 별 파인튜닝 비용
ONCE에서의 필요
GPT는 뛰어난 모델이지만, 카드 추천이라는 우리의 서비스에 적합하게 학습된 상태가 아니다.
따라서 우리가 질문할 예정인 문장과, 이에 원하는 대답의 형식을 정해주어 학습시킬 것이다.
또한 GPT는 모든 은행의 카드 혜택 정보를 가지고 있지 않으며, 더더욱이 2022 이후의 데이터는 알 수 없기 때문에 두번째 기술 크롤링을 통해 얻은 카드 및 혜택 정보들을 입력하여 우리의 서비스에 관련된 정확한 응답을 생성하도록 할 것이다.
GPT 파인튜닝 과정
① 데이터 준비(format 맞추기)
모델을 파인튜닝하기 위한 데이터를 먼저 준비해야 한다.
prompt(원하는 프롬프트)-completion(원하는 답 형태) 쌍으로 이루어진 JSON 형태의 데이터
단일 입력 예제와 해당하는 출력으로 구성
CLI 데이터 준비 도구로 Fine-tuning 할 수 있는 데이터 생성:
openai tools fine_tunes.prepare_data -f <로컬_파일>
② Fine-tuning 모델 생성
OpenAi에서 제공하는 base model을 활용 (ada, babbage, curie, davinci)
OpenAI CLI를 사용하여 파인튜닝 작업 시작
openai api fine_tunes.create -t <TRAIN_FILE_ID_OR_PATH> -m <BASE_MODEL>
ex. openai --api-key [YOUR_KEY] api fine_tunes.create -t example_prepared.jsonl -m davinci
이 코드의 작업:
- 파일을 파일 API를 사용하여 업로드 (또는 이미 업로드된 파일을 사용)
- 파인튜닝 작업을 생성
- 작업이 완료될 때까지 이벤트를 스트리밍 (보통 몇 분 소요, 대기 중인 작업이 많거나 데이터셋의 크기에 따라 몇 시간)
# 추가 명령어들
# 생성된 모든 파인튜닝 작업 나열
openai api fine_tunes.list
# 파인튜닝 상태 검색. 결과 객체에는 작업 상태 (대기 중, 실행 중, 완료 또는 실패 중 하나일 수 있음)와 기타 정보가 포함됩니다.
openai api fine_tunes.get -i <YOUR_FINE_TUNE_JOB_ID>
# 작업 취소
openai api fine_tunes.cancel -i <YOUR_FINE_TUNE_JOB_ID>
③ Fine-tuning 모델 사용
OpenAI 내 계정 정보 > Fine-tune training에 정보 추가됨, playground에서 확인 및 사용 가능
작업이 성공 시, fine_tuned_model 필드에 모델의 이름 저장됨
이 모델을 Completions API의 매개변수로 지정하여 Playground를 사용하여 요청 보내기 가능
+ 파인튜닝된 모델 삭제
오버피팅과 언더피팅 대응 전략
데이터 추가 수집: 더 많은 데이터는 모델이 다양한 패턴을 학습하고 오버피팅의 위험을 줄이는 데 도움이 된다
데이터 증강: 백트랜스레이션과 유의어 대체와 같은 데이터 증강 기법을 사용하여 추가적인 훈련 예제를 생성하고 오버피팅을 줄일 수 있다
모델 단순화: 모델이 오버피팅되었다면, 모델의 크기나 복잡성을 줄이는 것을 고려하라
모델 복잡성 증가: 언더피팅이 문제라면, 데이터의 패턴을 파악하기 위해 더 복잡한 모델이 필요할 수 있다
파인튜닝 모델 평가
모든 인공지능 모델은 학습 및 테스트 후 모델이 학습한 내용을 얼마나 잘 일반화했는짖 명확하고 객관적으로 측정할 수 있어야 한다.
평가를 통해 모델이 세밀 조정 과정에서 실제로 유용한 내용을 학습했는지 아니면 훈련 데이터에만 과적합되었는지를 정량적으로 판단할 수 있다.
자연어 처리(NLP)에서 사용되는 일반적인 지표:
prompt = "Feel: Smile \nMSG:my name is Chaerin.\n ->",
openai --api-key [API KEY] api fine_tunes.create -t example_prepared.jsonl -m ada
prompt
와 completion
의 형식에서, 카드 추천을 위해 파인튜닝을 적용할 방법을 구상해 보았다.{”prompt” : “<결제처, [카드 1, 카드 1 상세 혜택, 카드 1 실적 충족 여부], ..., [카드 N, 카드 N 상세 혜택, 카드 N 실적 충족 여부]>”, “completion” : “<결제처에서 사용할 카드>”}
{”prompt” : “<결제처, 카드명, 그 카드의 혜택, 실적 조건>”, “completion” : “<결제처에서 해당 카드로 누릴 수 있는 혜택(실적 채운 경우와 채우지 못한 경우 나눠서)>”}
{”prompt” : “<[결제처 A, 카드명1, 결제처에서 해당 카드로 누릴 수 있는 혜택1], ... , [결제처 A, 카드명N, 결제처에서 해당 카드로 누릴 수 있는 혜택N]>”, “completion” : “<결제처, 사용할 카드, 그 카드의 혜택>”}
"user": "A카드, B카드, D카드의 혜택이 뭐야?",
"assistant": "A카드 혜택: [A 카드 혜택 정보],B카드 혜택: [B 카드 혜택 정보],C카드 혜택: [C 카드 혜택 정보]"
"user": "GS25에서 10000원 결제하려면 이 중 어느 카드를 쓸까?"
파인 튜닝 관련 자문 및 조사를 통해 3번의 방식대로 파인 튜닝을 진행하기로 하였다.
크롤링한 정보를 어떤 형식으로 넘길지를 포함하여 input과 output의 형식에 따라 GPT의 답변과 정확도가 달라질 것이다. 이를 모두 고려해 test해보며, 최고의 서비스를 구현해보자