이번 포스트는 GPT와 Bard를 활용한 도서 난이도 책정과 연결되는 부분이 있습니다.

 

 

GPT와 Bard를 활용한 도서 난이도 책정

안녕하세요! 저는 요즘 GPT 없이는 아무것도 못하는 삶을 살고 있어요. 심지어 시험 볼 때도 오픈 GPT인 세상,, 과학 기술의 발전이 또 이렇게 성큼성큼,, 이런 상황에 프로젝트를 할 때 GPT를 안 쓰

hanadoolsae.tistory.com


 
안녕하세요 !!!!!!
 
저번 포스트에서 제작한 도서 데이터를 바탕으로
도서 추천 알고리즘을 본격적으로 만들어보고자 합니다 !
 
그 전에 저희 프로젝트에 생긴 가장 큰 변화를 짚고 넘어가자면
바로 타겟유저가 '청소년'이 된 것입니다 !
 
회원가입시 진행되는 어휘력/문해력 테스트의 난이도 및 동기부여 등에 대해
국어국문과 교수님들께 자문을 구하는 과정에서 충분한 논의를 거친 뒤 '청소년'으로 메인 타겟을 정하게 되었어요.
이로인해 회원가입시 관심 전공을 입력 받아, 전공과 도서간의 유사도를 추천 알고리즘에 포함하게 되었답니다.
 
그럼 바로 저희 가이드북의 추천 알고리즘을 설명하겠습니다 !
 
* 추천 알고리즘이 무엇인지 알고싶다면 아래 더보기를 클릭해주세요 !

더보기

간단히 알고 가는 추천 알고리즘 ~!

 

추천 알고리즘에는 크게 2가지 종류가 있습니다.

하나는 사용자 기반 협업 필터링이고 또 하나는 아이템 기반 협업 필터링입니다.

 

 

저희 서비스에 적용해서 설명해보자면,

사용자 기반 협업 필터링'나'와 비슷한 다른 학생들의 선호도서를 바탕으로 추천이 이루어지고

아이템 기반 협업 필터링내가 좋아하는'도서'와 비슷한 도서를 바탕으로 추천이 이루어집니다.

 

사람이 비슷해야하는지 도서가 비슷해야하는지의 차이라고 생각하면 됩니다 !


GUIDE:BOOK의 추천 알고리즘

 
0. 도서 데이터


저희의 타겟 유저가 청소년으로 변경됨에 따라
도서 데이터에 청소년 권장도서대학교 추천도서를 반영하려고 했습니다.
따라서 문화체육관광부 추천 도서교육청 추천 도서를 활용하였으며
최신도서 정보를 포함하기 위해 알라딘의 장르별 베스트셀러를 함께 수집하였습니다.

이때, 도서 난이도는 bard를 통해 얻었고,
도서 이미지, 개요, 페이지수는 웹 크롤링으로 획득하였는데
selenium을 이용해서 각 isbn에 해당하는 도서에 접근해 정보를 가져왔습니다.
 
 


 1. 도서 임베딩


처음에는 알고 있는 모델이 word2vec뿐이라 이것을 사용하였는데,
한국어 맞춤 모델이 아니라 그런지 학습에 한계가 있더라고요.
 
그래서 한국어 맞춤 모델을 여러가지 찾아 보았습니다.

KoBERT(KoreanBidirectional Encoder Representations from Transformers)
SKT에서 공개한 위키피디아, 뉴스 등에서 수집한 5천만개의 문장으로 학습된 모델입니다. 한국어의 불규칙한 언어 변화의 특성을 반영하기 위해 데이터 기반 토큰화(SentencePiece tokenizer) 기법을 적용하였으며 vocab 크기는 8002, 모델의 파라미터 크기는 92M입니다.

KLUE-BERT
KLUE-BERT는 벤치마크 데이터인 KLUE에서 베이스라인으로 사용되었던 모델로, 모두의 말뭉치, CC-100-Kor, 나무위키, 뉴스, 청원 등 문서에서 추출한 63GB의 데이터로 학습되었습니다. Morpheme-based Subword Tokenizer를 사용하였으며, vocab size는32,000이고 모델의 크기는 111M입니다.

* 출처: https://www.letr.ai/blog/tech-20221124

 
이 두 가지 모델이 제가 활용했던 모델이고,
확실히 KLUE-BERT가 사이즈가 더 커서 학습이 잘 진행되었습니다.
그래서 최종적으로 KLUE-BERT를 사용하게 되었습니다.



 
2. 독서 중단 사유 반영


저희의 도서 추천 서비스의 핵심 기능의
첫째는 어휘력/문해력 기반 추천이고
둘째는 중단 사유 반영입니다.

기존 레퍼런스들은 독서중과 완독만을 기록하지 독서를 중단한 사례는 기록하지 않았습니다.

저희는 추천을 받을 때 좋아하는 것을 추천 받는 것도 중요하지만
싫어하는 것을 배제하는 것도 중요하다 생각하여 이 부분을 고려하였습니다.
 

def get_interruption_reasons(member_id):
    # 사용자가 중단한 도서 목록을 가져오기
    interrupted_books = MemberMyBook.query.filter_by(
        member_entity_id=member_id, status=2  # status 2가 중단임
    ).all()

    interruption_reasons = {
        'high_difficulty': [],
        'low_difficulty': [],
        'many_pages': [],
        'few_pages': [],
        'disliked_genre': []
    }

    # 중단 사유별로 도서의 특성을 분류
    for book in interrupted_books:
        if book.status_detail == '1':
            interruption_reasons['high_difficulty'].append(book.book_entity_id)
        elif book.status_detail == '2':
            interruption_reasons['low_difficulty'].append(book.book_entity_id)
        elif book.status_detail == '3':
            interruption_reasons['many_pages'].append(book.book_entity_id)
        elif book.status_detail == '4':
            interruption_reasons['few_pages'].append(book.book_entity_id)
        elif book.status_detail == '5':
            interruption_reasons['disliked_genre'].append(book.book_entity_id)
            logger.info(f"Book with ID {book.book_entity_id} added to disliked_genre for member {member_id}")

    # 모든 책을 확인한 후에 사유별 분류를 반환
    return interruption_reasons

def adjust_recommendations(member_id, recommendations):
    # 중단한 도서의 사유 가져오기
    interruption_reasons = get_interruption_reasons(member_id)
    # 싫어하는 장르의 장르 번호 업데이트
    interruption_reasons = update_disliked_genre_with_genre_numbers(member_id, interruption_reasons)

    # 평균 난이도와 페이지 수 계산
    avg_difficulty, avg_page_count = get_average_difficulty_and_page_count(interruption_reasons)

    # 추천 도서 목록 조정
    adjusted_recommendations = []
    for rec in recommendations:
        book_id = rec['id']
        book_difficulty = rec['bookDifficulty']
        book_page_count = rec['bookPage']
        book_genre_id = rec['bookGenre']

        # 난이도가 0인 책은 난이도 관련 필터링에서 제외
        if book_difficulty != '0':
            # 사용자가 난이도 때문에 중단한 책 제외
            if avg_difficulty is not None and difficulty_levels.get(book_difficulty, 3) > avg_difficulty:
                logger.info(f'Book with ID {book_id} excluded due to higher difficulty than preferred.')
                continue

        # 페이지 수에 따른 조정을 추가
        if avg_page_count is not None:
            # 사용자가 페이지 수가 많은 책을 중단한 경우, 평균보다 많은 페이지 수를 가진 책 제외
            if book_page_count > avg_page_count and book_id in interruption_reasons['many_pages']:
                logger.info(f'Book with ID {book_id} excluded due to having more pages than preferred.')
                continue
            # 사용자가 페이지 수가 적은 책을 중단한 경우, 평균보다 적은 페이지 수를 가진 책 제외
            if book_page_count < avg_page_count and book_id in interruption_reasons['few_pages']:
                logger.info(f'Book with ID {book_id} excluded due to having fewer pages than preferred.')
                continue

            # 중단한 장르와 같은 장르의 책 제외
        if book_genre_id in interruption_reasons['disliked_genre']:
            logger.info(
                f'Book with ID {book_id} excluded due to being of a disliked genre with genre number {book_genre_id}.')
            continue

            # 조정된 추천 도서를 목록에 추가
        adjusted_recommendations.append(rec)

    logger.info(f'Returning {len(adjusted_recommendations)} adjusted recommendations for member ID {member_id}')
    return adjusted_recommendations

저희 데이터베이스 중 member_mybook_table에서 status 컬럼에 숫자 2가 들어간 도서가 독서 중단된 도서이고,
status_detail 컬럼에 적힌 숫자들이 각각의 이유를 나타냅니다.
 

이 추천 목록에서 독서 중단 사유로 난이도와 장르를 주입하겠습니다.
경제/경영 부문 책을 장르가 맘에 안든다고 선택하고, 난이도가 4인 책이 어렵다고 하였습니다.
 

그 후 바뀐 추천 목록입니다.
경제/경영 도서가 사라졌고 난이도가 4이상인 도서도 사라진 것을 확인할 수 있습니다.
 

현재는 단순하게 도서의 난이도, 두께, 장르에 대한 사유만 고려하여 반영하였는데
이 부분은 추후에 좀 더 자세하게 사유를 반영할 수 있도록 수정하면 좋을 것 같습니다.


 
3. 유저 정보 기반

유저의 레벨,학년,선호 장르 기반 도서 추천입니다.
 
예시로 보여드리는 류한아라는 이 학생은 level3이 나왔으며, '과학'과 '소설'을 선택한 컴공에 관심있는 고등학교 1학년입니다.
테스트 결과가 3이 나와 첫 줄에는 난이도가 3으로 분류된 도서가 추천되었으며,
고등학교 1학년이기에 둘째줄에는 난이도가 4인 도서, 그리고 마지막 줄은 난이도가 2인 도서가 추천되었습니다.
 
초등학생부터 성인까지 회원가입이 가능하기에
본인의 레벨을 기준으로 초등학생~중3까지는 레벨보다 한 단계 낮은 도서가 상위에 뜨도록
고1~성인까지는 본인의 레벨보다 한 단께 높은 도서가 상위에 뜨도록 정렬되었습니다.
 
추천도서가 6개 이상이 나온다면 새로고침을 할 때마다 새로운 도서가 추천됩니다.
 
 


4. 유저의 내서재 기반

아까 류한아란 학생의 마이페이지입니다.
과학, 소설, 컴퓨터 IT와 관련된 도서들을 완독하고 찜한 모습입니다.
 

내 서재에 있는 도서 중 '우주탐사의 물리학'이라는 도서가 랜덤으로 선택되어
유사도가 높은 도서들이 출력되었습니다.
 

random_book_title_encoded = request.args.get('randomLikedOrCompletedBookTitle')

# random_book_title_encoded가 None인 경우를 처리
if random_book_title_encoded is not None:
    random_book_title = unquote(random_book_title_encoded)
else:
    random_book_title = ""  # 또는 다른 적절한 기본값 설정

logging.info(f"Received request for member_id: {member_id} with book title: {random_book_title}")

recommendations = []

if random_book_title:
    try:
        # 데이터프레임에서 해당 책의 인덱스를 찾기
        filtered_books = books[books['book_title'] == random_book_title]
        if filtered_books.empty:
            logging.warning(f"Book not found: {random_book_title}")
            return jsonify({"error": "Book not found"}), 404

        book = filtered_books.iloc[0]
        book_id = book['id']
        logging.info(f"Found book: {book['book_title']} with ID: {book_id}")

        # id를 사용하여 해당 책의 임베딩 인덱스를 찾기
        book_index = np.where(book_embeddings['id'] == book_id)[0]
        if not book_index.size:
            logging.warning(f"Book embedding not found for ID: {book_id}")
            return jsonify({"error": "Book embedding not found"}), 404

        # 임베딩 추출 부분 수정
        book_embedding = book_embeddings['embedding'][book_index][0]

        # NumPy 배열로 변환
        all_embeddings = np.array([emb for emb in book_embeddings['embedding']])

        similarity_scores = cosine_similarity([book_embedding], all_embeddings)[0]

스프링에서 플라스크로 내 서재 속 랜덤 도서 한 권의 제목을 url에 인코딩하여 보냈기에
디코딩 한 후 도서 제목을 이용해 도서 id를 찾고, 이 id를 바탕으로 임베딩 유사도를 파악하였습니다.

 

랜덤 도서의 book_id를 보내면 숫자라 따로 인코딩/디코딩을 안 해도 되는데 제목(한글)로 보내서 굳이 인코딩을 한 이유는

스프링부트에서 랜덤 도서명을 그대로 클라이언트측 코드에서 출력해서 사용하느라

변수를 두 개 만들어 그 도서명으로 다시 sql에 접근해 book_id를 확인해서 flask로 전송하느니

도서명을 그냥 flask에서 인코딩된 상태로 수신하는게 빠를 것 같았습니다.

어차피 flask에선 book_id로 받든 도서명으로 받든 똑같은 book테이블에 접근하는 거라 똑같거든요.
 

로그를 보면 122번 멤버(류한아)의 랜덤 도서로 우주탐사의 물리학이 플라스크 서버로 넘어온 것을 확인하였습니다.
그리고 이 도서 번호를 book_table에서 확인한 후 위에서 언급한 도서간의 임베딩 numpy 파일을 통해
유사도가 높은 도서들을 상위 6권 출력합니다.
 


이런식으로 내 서재에 있는 여러 도서 중 랜덤으로 책이 잘 추천되는 것을 볼 수 있습니다.
 
이렇게 아이템 기반 협업 필터링을 진행하였습니다.
꽤나 괜찮게 책이 추천되어 뿌듯합니다. 하하.
 

 

5. 유저와 전공&학년이 같은 다른 유저 기반

이 도서들은 류한아란 학생과 전공/학년을 동일하게 선택한 유저들이
완독을 하였고 위시리스트에 담아놓은 책들을 기반으로 사용자 기반 협업 필터링을 진행한 결과입니다.

 

 
위의 사진들이 컴퓨터공학전공에 관심이 있는 1학년 학생들 내 서재 중 일부입니다.
이런 식으로 류한아 학생과 전공과 학년이 똑같은 학생들의 내서재를 탐색한 뒤 협업 필터링을 진행합니다.
 

# 모든 비슷한 사용자의 도서 목록과 중복 횟수 계산
all_user_books = {}
for user_id in similar_members_ids:
    user_books = MemberMyBook.query.filter(
        MemberMyBook.member_entity_id == user_id,
        MemberMyBook.status == 1
    ).all()
    user_liked_books = MemberMyLike.query.filter(
        MemberMyLike.member_entity_id == user_id
    ).all()

    for book in user_books + user_liked_books:
        book_id = book.book_entity_id
        all_user_books[book_id] = all_user_books.get(book_id, 0) + 1

# 협업 필터링을 통해 찾은 도서 중 현재 사용자의 도서 목록과 중복되지 않는 것들만 추천 목록에 추가
collaborative_book_ids = set(all_user_books.keys()) - current_user_books
collaborative_recommendations = Book.query.filter(Book.id.in_(collaborative_book_ids)).all()
recommended_books_dicts = [book.as_dict() for book in collaborative_recommendations]

# 책 정보에 중복 횟수 추가
for book in recommended_books_dicts:
    book_id = book['id']
    book['duplicate_count'] = all_user_books.get(book_id, 0)

# 도서 목록을 중복 횟수와 전공 유사도에 따라 정렬
final_recommendations = sorted(recommended_books_dicts,
                               key=lambda x: (-x['duplicate_count'], -x.get('major_similarity', 0)))

# 로깅 및 상위 6권 추천 도서 반환
top_six_recommendations = final_recommendations[:6]
current_app.logger.info("Top 6 recommendations (based on collaborative filtering and major similarity):")
for book in top_six_recommendations:
    current_app.logger.info(
        f"Book ID: {book['id']}, Duplicate Count: {book['duplicate_count']}, Major Similarity: {book.get('major_similarity', 0)}")

 
류한아 학생과 같은 전공/학년을 선택한 다른 유저들이 선택한 도서중
류한아 학생의 내 서재 목록과 비슷한 유저들의 도서를 확인하고,
그 중 다른 유저들과 비교했을 때 중복이 많은(대중적으로 좋아하는) 도서를 확인한 뒤,
마지막으로 도서와 전공 유사도를 반영하여 순서를 정렬하였습니다.
 
 


보완점 및 아쉬운 점

 
1. 전공과 도서간의 미미한 유사도
전공과 도서간의 유사도를 산출할 때, 전공 설명과 도서의 개요를 전처리 후 임베딩을 진행하였습니다.
이 과정에서 나온 임베딩 값이 추천 알고리즘에 유의미한 차이를 제공하진 않더라고요.
이 부분은 전공 설명을 좀 더 자세하고 길게 가져와 여러 키워드들을 생성한 후 도서와 돌려봐야할 것 같습니다.
 
2. 적은 유저 데이터
추천 알고리즘의 핵심이 사실 여러 유저가 사용을 해야하는 것인데
유저 풀이 좁다보니 추천 결과가 아름답지 않았습니다.
하지만 이건 1년 기한의 프로젝트에서는 어쩔 수 없는 현상이라 생각하기에
추후에 이 프로젝트를 좀 더 구체화 시키고 사업화(?)할 계획이 있다면 해결해야할 문제같습니다.
 
3. 시시한 추천 알고리즘
개인적으로는 추천 알고리즘이 너무 시시하다고 생각합니다.
추천 알고리즘 자체를 구현하는데 시간을 많이 허비해
좀 더 특이하고 다양한 알고리즘을 개발하기엔 힘들었던 것이 아쉽네요.
더 다양한 인자를 활용해서 추천 알고리즘에 반영하면 어떨까 싶은데
레퍼런스에서 감정을 토대로 추천하는 서비스가 있었어서
이 부분도 재밌을 것 같습니다.
 
4. 느린 속도
클라이언트 -> 스프링 -> 데베 -> 스프링 -> 플라스크 -> 데베 -> 스프링 -> 클라이언트
이런 흐름으로 진행되다 보니 속도가 느립니다.
현재는 스프링과 플라스크 양쪽 모두 데베에 접근하도록 코드를 짰지만,
가능하다면 스프링에서만 데베에 접근하도록 변경해서 속도를 높이고 싶습니다.
이 부분은 좀 더 알아봐야 할 것 같네요.


1년간의 졸업 프로젝트 동안 많이 힘들었지만,,
얻어가는 것들이 많아서 참 뿌듯하네요.
 
물론, 아직 부족한 점들이 있긴 하지만,
이것들은 차차 보완해 나갈 수 있지 않을까 싶습니다.
 
이번 프로젝트르 통해
"구글링과 gpt와 함께라면 불가능은 없다." 를 뼈저리게 느껴서 더욱 해낼 수 있을 것 같네요.
 
 

 
궁금하신 점이나 오류가 나는 부분은 댓글 달아주시면 확인해보겠습니다 !
끝까지 읽어주셔서 감사합니다 !!

'2023 캡스톤 디자인' 카테고리의 다른 글

GPT와 Bard를 활용한 도서 난이도 책정  (4) 2023.05.23

빵야 0_<

안녕하세요!

 

저는 요즘 GPT 없이는 아무것도 못하는 삶을 살고 있어요.

심지어 시험 볼 때도 오픈 GPT인 세상,,

과학 기술의 발전이 또 이렇게 성큼성큼,,

이런 상황에 프로젝트를 할 때 GPT를 안 쓰는 건 말도 안 되겠죠?

 

 

저희 팀의 프로젝트 주제는

'유저의 문해력 수준에 맞는 도서 추천 서비스'입니다.

 

이 주제의 맹점은

1. 유저의 문해력 수준을 어떻게 판별할 것인지

2. 도서의 난이도를 어떻게 분류할 것인지

3. 어떤 방식으로 서비스를 제공할 것인지 (웹 vs 앱)

이 정도인 것 같아요.

 

 

이 중에서 가장 저희를 골머리 썩게 한 2번 문제,,

이 문제를 해결하기 위해 저희가 GPT를 사용하기로 했습니다,,!

그러나 모종의 이유로(아래에서 설명할 예정) Bard로 갈아타게 되었습니다.

하지만 GPT를 활용하는 방법도 나와 있으니 실망 마세요!

 


GPT와 Bard를 사용하게 된 이유

 

저희가 도서 난이도를 판별하는 방법으로 원래는 대교의 KreaD를 사용하려고 했어요.

KreaD는 대교에서 만든 서비스로 '텍스트의 난이도'를 알려줍니다.

https://www.kread.ai/kread/experience

 

http://sso3.daekyo.com/WebSSO/AuthWeb/ssoService.do?returnURL=https%3A%2F%2Fwww.kread.ai&ssosite=www.kread.ai

 

sso3.daekyo.com

 

이 말은 즉, 도서의 텍스트 파일이 있어야 한단 뜻이고,

저희는 시간과 노동력이 가장 남아돌았기에 약 1만 권의 책의 샘플 데이터를 직접 타이핑해서 돌릴 예정이었습니다.

그러나 이게 되게 무식한 방법이고, 지속가능성이 낮고, 대교 측과의 협업이 불발되어버려서,,

 

GPT나 Bard와 같은 AI를 사용하기로 생각했어요.

 

AI들이 모든 도서의 텍스트를 가지고 있는 것은 아니기에

KreaD만큼의 세밀한 난이도 산출은 어렵지만

작가에 대한 정보를 바탕으로 문체나 전반적인 어휘의 난이도를 고려할  수 있고,

장르 및 도서의 주제를 고려하여 산출해 주기에 오히려 좋았습니다,,

 

그리고 사실 도서 난이도 산출에 대한 정확도를 논하는 것은 약간 모순적인 것이

책을 읽는 유저마다 성향과 배경지식 등이 다 다르기에 난이도 설정에 차이가 생길 수밖에 없어

모든 유저가 납득하고 만족할만한 난이도 분류는 있을 수 없다고 생각합니다.

 

그래서 저희는 저희가 제공하는 기준이 '옳다'라고 말할 순 없지만,

최대한 통일된 기준을 가지고 책을 분류하는 작업을 진행하려고 했고,

그래서 GPT와 Bard를 활용하게 되었어요.

 

 


활용 방향

저희가 생각한 방식은

1. 출판사 별로 제공하는 도서 데이터를 다운로드한다.

2. 데이터에서 제목/작가/장르/페이지수 의 정보를 제외하고는 삭제한다.

3. GPT 또는 Bard의 API key를 받아와서 import 한다.

4. 엑셀 파일을 행마다 돌리면서 책의 정보를 읽고 prompt로 질문 넘긴다.

5. 출력된 대답을 엑셀의 새로운 열에 삽입한다.

6. 출력 값을 정제하여 책의 난이도만 추출한다.

 

이러한 방식으로 책의 난이도를 판별하고자 하였습니다.

 


진행 과정

 

1. 출판사 별로 제공하는 도서 데이터를 다운로드한다.

이건 각 출판사 별로 홈페이지에 들어가면 데이터를 얻을 수 있습니다.

 

민음사의 경우에는 아래 링크로 들어가시면 도서 목록을 다운로드할 수 있어요.

http://minumsa.com/booklist-download/

 

도서목록 다운로드 | 민음사 출판그룹

 

minumsa.com

 

 

2. 데이터에서 제목/작가/장르/페이지수 의 정보를 제외하고는 삭제한다.

1. 민음사 도서 데이터

여기서 저희는 제목, 작가, 분류(장르), 페이지수의 정보만 남기고 삭제할 거예요.

그리고 여러 출판사에서 이런 식으로 데이터를 받아와서 합쳐 줍니다.

2. 도서 목록

그러면 이런 식으로 도서 목록을 만들 수 있습니다.

 

 

 

3. GPT 또는 Bard의 API key를 받아와서 import 한다.

GPT와 Bard의 API를 가져오는 방식이 달라서 각각 설명하겠습니다.

 

1) GPT

https://platform.openai.com/overview

 

OpenAI API

An API for accessing new AI models developed by OpenAI

platform.openai.com

위의 링크로 들어가서 회원가입을 해주고 API key를 발급받으면 됩니다.

 

3. OpenAI key 발급

저는 이미 key를 발급받은 상태인데, 이 화면에서 'Create new seceret key'를 클릭하고 발급받으면 됩니다.

 

4. OpenAI usage 확인

그렇게 발급받은 key는 본인의 유효기간 동안 사용하거나, $5 범위 내로 사용할 수 있습니다.

저는 너무 많이 사용해서 유효기간이 남았음에도 $5를 넘겨서 지금 사용 못하고 있어요.

그래서 Bard로 넘어가게 되었답니다.

 

암튼 이렇게 key값을 불러왔다면,

!pip install openai

import openai
import openpyxl
import nltk
import pandas as pd
import re
import torch


openai.api_key = "Your API Key"
openai.Model.list()

# GPT-3.5 모델 세팅
model_engine = "text-davinci-003"

이렇게 기본 세팅까지 해주면 된답니다.

 

 

2) Bard

Bard는 사실 현재 실험 버전입니다.

그래서인지 Bard는 GPT와는 방식이 조금 다릅니다.

 

!pip install bardapi>=0.1.8
!pip install transformers

먼저 bardapi를 설치해 줍니다.

 

import os
import bardapi
import openpyxl
import pandas as pd
from transformers import AutoModelForSequenceClassification, AutoTokenizer
from bardapi import Bard

token='YOUR_KEY'

그리고 token에 각자의 key값을 넣으면 됩니다.

 

5. Bard 개발자 도구 실행 화면

key값은 Bard 사이트에서 f12를 눌러서 개발자 도구를 실행시키고,

쿠키에 들어가서 Secure-1PSID Value값을 가져오면 됩니다.

Value는 마지막에 온점(.)으로 끝이 납니다.

 

 

 

4. 엑셀 파일을 행마다 돌리면서 책의 정보를 읽고 prompt로 질문 넘긴다.

저희는 원래 GPT를 사용했는데, GPT의 OpenAI key값의 무료버전이 끝나면서

결제를 할지, 새로운 방식을 고안해야 할지 고민했습니다.

 

결제를 하기엔 아까웠던 이유가

저희는 책의 난이도를 분류할 때 GPT를 활용하고 나면, 그 이후로는 사용하지 않을 것 같아

결제를 해도 그만큼 뽕 뽑지 못할 것 같았기 때문이죠,,

그래서 주변인들에게 구걸해 주변인들의 무료 API key를 적선받아 데이터만 빠르게 돌려야 할지 고민했습니다.

하지만 이것도 안정적이진 못하다고 생각이 들어 결국엔 Bard를 활용하게 됐으나

GPT를 활용하려고 짠 코드에서 크게 달라진 건 없어서 오히려 좋았습니다.

 

아래 코드는 GPT를 활용할 당시 작성한 코드입니다.

prompt = (f"This Excel file contains the following information: Title of the book, Publisher, Author, Category.\n"
          f"Read each row from this Excel file and use the information to determine what kind of book it is and assign a difficulty rating from 1 to 5. "
          f"The rating should be determined by considering various factors such as complexity of the content, language level, and subject matter. \n"
          f"For each Korean book, output the title, publisher, author, number of pages, category, and the difficulty rating.\n")


# 엑셀 파일 로딩
workbook = openpyxl.load_workbook("Book_Data")
sheet = workbook.active

# 필요한 열 추출
data = pd.DataFrame(sheet.values)
book_data = data.iloc[1:, [0, 1, 2, 3]]
book_data.columns = ["title", "author", "category", "publisher"]

def get_book_difficulty(title, author, category, publisher):
    if title is None or author is None or category is None or publisher is None:
        return "Not available" 
    # 각 책마다 정보 돌리기
    for i, row in book_data.iterrows():
      book_info = f"Book: {row['title']} by {row['author']}, Category: {row['category']}, Publisher: {row['publisher']}"
      prompt_with_book_info = prompt + book_info
        
      response = openai.Completion.create(
          engine=model_engine,
          prompt=prompt_with_book_info,
          max_tokens=1024,
          n=1,
          stop=None,
          temperature=0.7,
        )
        
      # 생성된 텍스트에서 난이도 등급 추출
      difficulty = response.choices[0].text.strip()

 

prompt는 쉽게 말하면 Chat-GPT에 넣는 질문입니다.

Chat-GPT를 써보신 분들은 아시겠지만, 질문이 구체적이고, 또 자세할수록 GPT의 대답이 달라지죠.

 

제가 듣는 수업에서 한 교수님께서 이런 말씀을 하셨습니다.

"GPT는 가스라이팅을 해야 한다 ,, "

아주 맞는 말입니다.

이 자식은 아주 맹랑하고 뻔뻔해서 틀린 답도 당당하게 뱉어냄으로써 저희를 혼란에 빠트리거든요.

 

그래서 prompt를 자세하게 써야 합니다.

그리고 기왕 작성하는 거 영어로 작성해야 좋습니다.

그래서 제가 작성한 prompt는

'엑셀 파일에는 제목/작가/출판사/장르의 정보가 있다.

작가의 문체, 책의 두께, 어휘의 복잡성 등을 고려하여 책의 난이도를 1부터 5까지로 표현해라.'입니다.

 

그리고 엑셀 파일을 불러온 이후에

prompt랑 행마다 읽은 책의 정보를 합쳐서 최종 prompt로 만듭니다.

(코드에서는 prompt_with_book_info로 작성됨.)

 

그리고 response는 GPT의 대답인데,

여기서 engine/max_tokens/n/temperature 등을 조절해 주면 대답이 달라집니다.

engine은 OpenAI의 어떤 모델을 사용하는지입니다. 이 코드에서는 text-davinchi-003을 사용하는 중이고요.

max_tokens는 이 언어모델이 응답으로 생성할 수 있는 최대 토큰입니다.

n은 응답의 개수로 하나의 응답만 필요하기에 1로 설정했습니다.

temperature은 언어 모델의 창의성 정도라고 생각하면 됩니다.

0과 2 사이의 숫자로 작성 가능한데 0.0일 땐 가장 가능성이 높은 대답을, 2일 땐 정말 다양한 응답을 생성하게 됩니다.

 

 

Bard도 비슷하게 사용하면 되는데요.

# 엑셀 파일 로딩
workbook = openpyxl.load_workbook("Book_data")
sheet = workbook.active# 필요한 열 추출
data = pd.DataFrame(sheet.values)

book_data = data.iloc[1:, [0, 1, 2, 3]]
book_data.columns = ["title", "author", "category", "publisher"]

def get_book_difficulty(title, author, category, publisher):
    if title is None or author is None or category is None or publisher is None:
        return "Not available"

    book_info = f"Book: {title} by {author}, Category: {category}, Publisher: {publisher}"
    prompt_with_book_info = (
        "This Excel file contains the following information: Title of the book, Publisher, Author, Category.\n"
        f"Read each row from this Excel file and use the information to determine what kind of book it is and assign a difficulty rating from 1 to 5. "
        f"The rating should be determined by considering various factors such as complexity of the content, language level, and subject matter. \n"
        f"For each book, output the title, author and the difficulty rating, and summarize the reason why you determine the difficulty like that.\n"
        + book_info
    )

    response = bardapi.core.Bard(token).get_answer(prompt_with_book_info)

    difficulty = response['content'].strip()

    return difficulty

Bard도 GPT에서 활용한 코드랑 비슷한데

차이점은 Bard가 아직 실험 버전이라 GPT처럼 max_tokens나 temperature을 조절할 수 없습니다.

그래서 response에 GPT처럼 길게 파라미터를 넣지 않았어요.

이는 Bard가 발전해 나가면서 차차 개선될 부분이겠죠?

 

 

 

5. 출력된 대답을 엑셀의 새로운 열에 삽입한다.

# 새로운 엑셀 파일 생성
new_workbook = openpyxl.Workbook()
new_sheet = new_workbook.active
new_sheet.append(["Title", "Author", "Category", "Publisher", "Difficulty"])

# 모든 책에 대해 난이도 분류
for i, row in book_data.iterrows():
    title, author, category, publisher= row["title"], row["author"], row["category"], row["publisher"]
    difficulty = get_book_difficulty(title, author, category, publisher)
    new_sheet.append([title, author, publisher , category, difficulty])


# 새로운 엑셀 파일 저장
new_workbook.save("Book_data_result")

기존 책 데이터에 새로운 열 Difficulty를 추가하여 출력받은 값을 넣습니다.

그리고 난이도값이 추가된 상태의 데이터를 새롭게 저장합니다.

이 부분 코드는 GPT랑 Bard 모두 동일합니다!

 

6. Bard를 통해 출력받은 값

그러면 이런 식으로 결과가 나오게 됩니다.

사실 조금 의아한 것이 왜 GPT보다 Bard의 결과가 더 좋은지 모르겠어요.

GPT가 모르는 책도 Bard는 아주 잘 알고 있더라고요.

암튼, 이 똑똑한 Bard는 제가 부탁한 형식에 맞춰서

나름의 기준에 따라 difficulty rating을 책정하고, 그 이유를 함께 출력해 줍니다.

 

7. 난이도 1부터 5까지

난이도 1이 나온 책부터 5가 나온 책까지 한 번 확인해 봤는데

큰 예외 없이 예상가능하고 이해가 되는 수준에서 책정해 주었습니다.

 

 

 

6. 출력 값을 정제하여 책의 난이도만 추출한다.

위에 있는 사진을 보면 알 수 있겠지만,

Bard 이 자식은 투머치토커라 말이 많아요.

그래서 여기서 1부터 5까지로 표현한 도서의 난이도만 추출해낼 겁니다.

 

'6. Bard를 통해 출력받은 값' 사진을 보시면 아시겠지만,

Bard에서 출력받은 결과 값은

Sure, I can do that. Here is the output for the book "인어공주 by 안데르센":

Title: 인어공주 (The Little Mermaid)
Publisher: 어린이 (Children's)
Author: 안데르센 (Hans Christian Andersen)
Pages: 120
Category: 바로이북 (Easy-to-read books)
Difficulty rating: 2

The difficulty rating of 2 is based on the following factors:

* The book is relatively short, with 120 pages.
* The content is not very complex, and the language is simple enough for most readers to understand.
* The subject matter is familiar to most people, and there are no technical terms or concepts that would require special knowledge.

Overall, this book is a good choice for readers who are looking for an easy-to-read story. It is a classic tale that has been enjoyed by people of all ages for generations.

이런 식으로 공통된 형식을 가지고 있었어요.

 

그래서 저는 여기서 Difficulty rating: 뒤에 적힌 숫자만 남기는 과정을 진행하였습니다.

# 엑셀 파일 경로 설정
excel_file_path = "Book_data_result"

# 엑셀 파일 읽기
df = pd.read_excel(excel_file_path)

# Difficulty 열의 값을 수정
df['Difficulty'] = df['Difficulty'].str.extract(r'Difficulty rating: (\d+)', expand=False)

# 수정된 데이터프레임을 새로운 엑셀 파일로 저장
output_file_path = "Book_data_real_result"
df.to_excel(output_file_path, index=False)

이런 식으로 진행하였고,

8. 출력값에서 난이도만 남기고 다 삭제하기

결과는 이렇게 Difficulty 열에 숫자만 남게 되었습니다!

이렇게 되면 난이도 1부터 5까지 그룹별로 확인하기도 좋겠죠?

 

이렇게 도서의 난이도가 포함된 데이터를 만들어 보았습니다!

 

 


보완해야 할 점

1. 서비스의 타겟 유저층을 확실히 정하고 도서 범위를 한정시켜 다시 난이도를 책정하기.

저희는 처음 기획단계에서

'책을 읽고자 하는 사람이라면 누구나 다 이 서비스를 이용할 수 있다.'

라고 생각하고 타겟 유저층을 제대로 설정하지 않았어요.

그로 인해 책의 난이도를 보시면 알겠지만

정말 3~5세 정도가 읽을 법한 유아책이 난이도 1을 차지하고 있어

성인 기준 쉽다고 생각되는 신데렐라가 난이도 2가 되다 보니

난이도 3에 많은 책이 몰리게 되어 살짝 신뢰도가 떨어지는 듯합니다.

따라서 유아책을 빼고 (뺄 거면 어디까지 뺄 것인지, 신데렐라 같은 동화도 뺄 것인지) 다시 난이도를 산출해야 할 것 같아요.

 

2. 출력 값에서 난이도 제외 다른 데이터를 활용할 방법 찾기.

현재 제가 짠 코드에서는 책의 난이도 (숫자 데이터)를 제외하고는 모두 삭제하고 있는데,

이렇게 되면 왜 이런 결과가 나온 것인지에 대한 유의미한 데이터가 함께 삭제되어 나중에 데이터를 확인할 때 어려워집니다.

그래서 이 문제는 데이터에서 어떤 단어들을 살릴 것인지, 어떤 것들을 남길지 등 회의를 한 뒤 생각해 봐야겠습니다.

 


저희는 유저가 완독 할 수 있는 책을 추천해주고자 합니다.

 

'완독을 꼭 해야 하나요?'라고 질문하신다면

'아니요.'라고 대답하겠지만,

'완독을 하고 싶은데 못하겠어요.'라고 말씀하신다면

하실 수 있게끔 도와주는 것이 저희 프로젝트의 궁극적인 목적입니다.

 

그래서 저희는 유저들이

읽고 싶은 책/읽는 중인 책/읽다 중단한 책/완독 한 책

이렇게 네 가지로 책을 분류할 수 있도록 했습니다.

특히, 저희는 '읽다 중단한 책'을 중요하게 생각했습니다.

책을 읽다 만 이유는 참 다양할텐데,

책이 길어서(읽을 시간이 부족해서), 장르가 취향이 아니라서, 작가가 마음에 안 들어서, 책이 너무 어려워서 등이 있겠죠 ?

 

저희는 이 의견을 수렴하여

유저별로 어느정도의 길이의 책을 선호할지, 취향저격인 장르와 작가는 누구일지, 책의 전반적인 난이도는 어떨지

이렇게 세 가지 기준을 고려하여 추천 알고리즘을 작성할 계획입니다.

 

 

오늘은 그 중 책의 난이도를 분류하는 방법으로 GPT와 Bard를 사용해보았습니다.

다음엔 위에서 말한 보완점을 수정해서 돌아오거나,,

혹은 추천 알고리즘으로 새롭게 돌아오도록 하겠습니다,,

 

 

그럼 읽어주셔서 감사합니다!

 

 

 

 

'2023 캡스톤 디자인' 카테고리의 다른 글

도서 추천 알고리즘  (1) 2023.11.13

+ Recent posts