고갱

[인공지능] 졸업 작품 #2, LLM + RAG 본문

인공지능/졸업작품

[인공지능] 졸업 작품 #2, LLM + RAG

주인장 고갱 2024. 11. 20. 20:53

우리나라 법을 토대로 법률 상담 서비스를 졸업 작품으로 선정하고 이것저것 살펴보았다.

과제도 많고 병행하는 프로젝트가 한 두개가 아니다 보니 시간적으로도 여유가 별로 나진 않았지만,

틈틈히 해보는데 흥미로운 점이 많았다.

 

🤔 모델 선정

임베딩 모델로는 'BAAI/bge-m3',

LLM 으로는 'MLP-KTLim/llama-3-Korean-Bllossom-8B-gguf-Q4_K_M'

을 사용하였다.

 

임베딩 모델은 한국어 처리 성능이 뛰어나다고 알려진 모델 중 하나로 선정하였으며,

LLM은 Ollama에서 불러오기 쉽게하기 위해 일부러 해당 모델을 사용하였다.

 

조금 테스트 해보면서 모델을 바꾸던가 여기서 추가로 어느정도의 파인튜닝을 거치면 되지 않을까.. 하는 생각 중이다.

(그래서 그냥 일단 검색해서 흔히 사용되는 모델로 선정한 것도 있다.)

 

 

😦 연구 과정

우선 RAG 수행에 쓰인 자료는 참고 자료에 기재하겠지만, '감염병의 예방 및 관리에 관한 법률' 이다.

 

감염병의 예방 및 관리에 관한 법률

 

www.law.go.kr

 

처음에는 해당 데이터를 PDF로 저장하고 이를 'PyMufLoader' 로 불러온 후, 'RecursiveCharacterTextSplitter'로 청크 단위로 쪼개주는 작업을 진행하였다.

일단 청크 사이즈는 500으로 두었으며, 청크 오버랩을 50으로 설정하였다.

이는 추후에 테스트하면서 조정하기 위함 임시 수치였다.

 

  • 청크 사이즈: 한 문서의 토큰 크기
  • 청크 오버랩: 분할된 문서의 끝에서 맥락이 이어지게끔 일부를 겹쳐서 분할하기 위해 사용

 

이후엔 한글 성능이 뛰어나다고 알려진 임베딩 모델 중 하나인 'BAAI/bge-m3' 을 이용하여 임베딩을 처리하였다.

(GPU가 있었으면 좋았겠지만, 내 맥미니 서버는 GPU가 없어서 CPU로 돌렸다.)

그 이후 Chroma 데이터베이스에 저장해주었고, langchain을 이용하여 RetrieverLLM을 연결하여 처리하였다.

 

그리고 내가 만들 졸업 작품은 '법률 서비스' 이기 때문에 그 근거가 법령이어야 하기 때문에 프롬프트 템플릿에는

 

모든 답변은 그에 대한 근거 조항이 꼭 포함되어야 한다.

 

는 내용을 작성해두었다.

 

근거 조항이 없는 모습

 

프롬프트 템플릿에 저렇게 정의해두지 않으면 위 사진처럼 나타나는데, 근거 조항을 명시해주게끔 정의해두면 아래와 같이 깔끔하게 근거 조항을 정리해준다.

 

근거 조항을 명시해주는 모습

 

 

또한 Retriever 파라미터를 통해 결과 문서를 가장 유사한 1개만 가져오도록 지정해두었다.

이는 문서 개수가 많으면 Input Token 수가 너무 많아져 아래 사진처럼 답변이 제대로 생성되지 않는 모습이 나타났는데,

이를 해결하기 위해 설정해두었다.

 

답변이 제대로 생성되지 않음

 

위 과정을 모두 마치고 langchain을 실행하게 되면 문제가 없을 것 같지만, 문제가 발생하게 된다.

 

❓  어떤 문제점?

지금까지의 과정에서 우리는 'PyMufLoader' 클래스를 이용하여 PDF 파일을 읽고있었다.

하지만 PDF 특성상 텍스트를 추출했을 때 줄 바꿈이 엉망으로 되어있는 것은 어쩔 수가 없다.

그래서 한 번 'RecursiveCharacterTextSplitter'로 문서를 분리한 결과를 한 번 살펴보면 아래와 같다.

줄넘김이 엉망인 모습
원본 PDF 파일

 

 

보면 문장이 그대로 들어간 것이 아니라, PDF에서 보이는 그대로의 줄넘김이 그대로 들어가 있는 점을 볼 수 있다.

이렇게 되면 법률 서비스를 제공할 때 온전한 하나의 조항을 근거로 삼아야 하는데 문서가 중간에 잘리는 등 RAG를 수행할 때 좋지 않은 성능을 보일 수도 있기 때문에 문장 그대로를 유지하는 게 중요하다.

(절대적으로 안좋다는 것이 아니라, 법률 서비스의 관점에서 온전한 하나의 조항이 필요하기 때문에 중요하다는 것이다.)

 

그렇다면 이걸 어떻게 문장 그대로를 유지할 수 있을까?

처음에는 모든 줄 넘김 문자 (\n) 을 없앤 후에 아래와 같은 정규식을 이용하여 각 조항을 구분해주고자 하였다.

제\d+조

 

이 정규식을 사용하면 "제1조", "제2조" 와 같은 곳에서 줄넘김을 다시 삽입해줄 수 있으니 좋은 선택지인 것처럼 보였다.

하지만 이는 좋은 해결 방법이 아니였다.

 

아래 사진처럼 각 조항 안에서 위 정규식을 만족하는 내용이 삽입되어 있는 경우가 있었기 때문이다.

정규식을 만족하는 내용이 포함된 모습

 

이를 해결하기 위해서는 다시 선택을 해야했다.

제\d+조\([^)]+\)

 

위 정규식처럼 바꾸면 '제N조(내용)' 에 대한 부분만 추출할 수 있겠지만, 다시 '제N조의N(내용)' 과 같이 점차 생각할 것도, 고려해야할 것도 점차 늘어나는 점이 신경이 쓰였다.

 

 

🤔 그렇다면 어떤 방법을?

위와 같은 문제를 해결하기 위해서 법령을 저장할 때 한글 파일로 저장하고 텍스트만 선택하여 txt 파일로 변환하였다.

그렇게 한다면 PDF 파일을 사용할 때의 근본적인 원인인 줄넘김 문제는 해결이 될터이니 말이다.

txt 파일로 내보낸 모습

 

이렇게 온전하게 문장의 구조가 유지되는 문서를 얻었으니, 이를 'TextLoader'로 해당 파일을 불러오고 'RecursiveCharacterTextSplitter'를 통해 청크 단위의 문서로 쪼개주는 작업을 거쳤다.

 

이렇게 하면 1차적으로 줄넘김 문제는 해결되었으나 아직 우리는 해야할 단계가 남았다.

만약 '제1급감염병에 대해 모두 나열해줘' 라고 질문을 하는 경우 아래의 결과값이 나온다.

RAG 수행 결과, 문서 1개
langchain 실행 결과

 

언뜻 보기에는 정상적으로 잘 나타나는 것 같지만 이것은 큰 착각이다.

이대로 마무리하면 절대로 안된다.

 

문제를 짚어보자면, 위 RAG 수행 결과 문서에서는 몇 조의 2항인지 잘려있다.

그렇다는 말은 각 조항별로 잘리는 것이 아니라 청크 사이즈에 따라 나뉘어지고 있다는 의미이고,

이는 뒤에 질병이 더 있을 수도 없을 수도 있다는 점이다.

 

질문을 달리해서 출력해보면 문제가 더욱 심각하게 바뀌게 된다.

가령 원래 질문이었던 '제1급감염병에 대해 모두 나열해줘'을 '제4급감염병에 대해 모두 나열해줘' 으로 바꾸어보면,

RAG 수행 결과, 문서 1개
langchain 실행 결과
제4급감염병 원본 데이터

 

위 결과를 보면 보이다시피 가져온 문서에 4항의 제3급감염병에 대한 정보도 들어가있으며,

원본 데이터에 있는 22개의 질병 중 16개밖에 표시되지 않는 현상이 나타난다.

 

확실히 문제가 나타나는 것이다.

 

😕 또 어떻게 해야하나?

위와 같은 문제가 나타나는 원인은 바로 Chunk 크기를 맞추기 위해 Splitter가 청크를 나누기 때문이다.

이를 방지하기 위해서는 Chunk 크기가 일정하지 않더라도 진행시키는 수 밖에 없다.

비록 성능 측면에서는 다소 떨어지겠지만, 지금 필요한건 정확한 문서 결과이기 때문이다.

 

이를 수행하기 위해서 몇 가지 절차를 거쳤는데,

 

첫 번째로는 문서 형식의 변경, 즉 기존 텍스트 파일에서 각 조항의 시작 지점을 특수한 문자 (나의 경우에는 '\n\n') 로 지정해주었고, 두 번째로는 'RecursiveCharacterTextSplitter'의 separators를 지정해주는 절차를 거쳤다.

text_splitter = RecursiveCharacterTextSplitter(
    separators=["\n\n"],
    chunk_overlap=0,
)

 

이렇게 하면 각 조항의 시작 지점을 기준으로 청크가 분할되기 때문에 더는 조항이 잘리는 현상은 나타나지 않게 된다.

 

 

 

❗ 앞으로 무엇을 해야할까?

자 이로써 langchain을 이용하여 RAG + LLM 을 처리하였다.

모든 문제가 사라진 것 같지만 아쉽게도 그건 아니다.

왜냐하면 RAG에서 필요한 작업이 더 남아있기 때문이다.

 

가령 82조의 양벌규정을 살펴보면

제82조(양벌규정) 법인의 대표자나 법인 또는 개인의 대리인, 사용인, 그 밖의 종업원이 그 법인 또는 개인의 업무에 관하여 제77조부터 제81조까지의 어느 하나에 해당하는 위반행위를 하면 그 행위자를 벌하는 외에 그 법인 또는 개인에게도 해당 조문의 벌금형을 과(科)한다. 다만, 법인 또는 개인이 그 위반행위를 방지하기 위하여 해당 업무에 관하여 상당한 주의와 감독을 게을리하지 아니한 경우에는 그러하지 아니하다.

 

라고 되어있는데, 여기서 위반 조항인 77조부터 81조가 무엇인가?

 

또 예를 들어 아래와 같은 쿼리를 물어본다면

query = "질병관리청장의 허가를 받지 않고 고위험병원체를 국내로 반입하였다. 처벌이 어떻게 되나?"
#22조 1항 위반, 77조 1항에 의거 5년 이하 징역 또는 5천만원 이하의 벌금에 처한다.

 

본래대로 라면 22조 1항을 위반했으므로 77조 1항에 의거하여 5년 이하의 징역형이나 5천만원 이하의 벌금에 처하지만, 22조에는 처벌 조항이 없기 때문에 에매모호하고 논리적으로 이상한 문장을 생성하게 된다.

구체적 처벌 수위가 명시되지 않은 모습

 

 

위와 같은 현상이 나타날 수 있기 때문에, 한 조항의 내용에서 다른 조항을 필요로 하는 경우 어떻게 처리해야 좋은지를 더 생각보아야 한다는 것이다.

(물론 성능 개선을 위해 임베딩 모델을 바꾸거나 LLM을 바꾸는 등의 작업도 필요하겠지만 그건 모든게 끝나고 나서 하고자한다.)

 

 

앞으로 시간 내서 틈틈히 해봐야겠다.

 

👍 참고 자료

임베딩 모델

 

BAAI/bge-m3 · Hugging Face

For more details please refer to our github repo: https://github.com/FlagOpen/FlagEmbedding In this project, we introduce BGE-M3, which is distinguished for its versatility in Multi-Functionality, Multi-Linguality, and Multi-Granularity. Multi-Functionalit

huggingface.co

 

LLM

 

MLP-KTLim/llama-3-Korean-Bllossom-8B-gguf-Q4_K_M · Hugging Face

Update! [2024.06.18] 사전학습량을 250GB까지 늘린 Bllossom ELO모델로 업데이트 되었습니다. 다만 단어확장은 하지 않았습니다. 기존 단어확장된 long-context 모델을 활용하고 싶으신분은 개인연락주세요!

huggingface.co

 

RAG에 사용된 데이터

 

감염병의 예방 및 관리에 관한 법률

 

www.law.go.kr