Tech

RAG로 욕설 탐지를 할 수 있다??

민윤홍 2024. 6. 26. 16:25
반응형

안녕하세요 에이콘입니다.

이직하고나서 정말 간만에 포스팅을 해보는데요,

오늘은 RAG를 사용하여 욕설을 탐지하는 재밌는 주제를 가지고 왔습니다.

 

 

https://github.com/2runo/Curse-detection-data

 

GitHub - 2runo/Curse-detection-data: 문장의 욕설 여부를 분류한 한글 데이터셋입니다.

문장의 욕설 여부를 분류한 한글 데이터셋입니다. Contribute to 2runo/Curse-detection-data development by creating an account on GitHub.

github.com

우선 오픈소스에 있는 욕설 데이터셋을 가져옵니다. 데이터는 일간베스트(일베), 오늘의 유머와 같은 각종 커뮤니티 사이트의 댓글에 대해 총 5,825문장이 있고, 수직선 기호( | )를 기준으로 좌측에는 댓글 내용, 우측에는 욕설 여부(0,1)가 기록되어 있습니다.

 

아래는 욕설 데이터의 예시입니다.

"저러다 뒤지면 후방주시 태만으로 버스기사한테 책임 묻겠지 ㅋㅋ" (욕설아님)
"시위 진압 ㅆㅅㅌㅊ였음" (욕설아님)
"돈 좀 빨아먹으려고" (욕설아님)

 

커뮤니티 특유의 공격적이고 날 선 언어지만, 문장 안에 '욕설'이 들어가 있지는 않습니다. 

 

GPT를 사용하여 욕설을 분류해보기

최초에는 GPT3.5를 통해 약간의 오류가 있어도, 어느정도 수준의 정답률만 나와도 gpt를 통해 분류하는 방식을 취하려고 했으나, 커뮤니티성 날 선 문장들은 전부 True로 분류하는 문제가 있었습니다.

욕설이면 True, 욕설이 아니면 False를 리턴한다

 

어떻게 할까 고민하던중 RAG를 simular_search를 응용해서 욕설과 욕설이 아닌 단어 문서를 만들고, similarity_search 를 사용하야 욕설을 탐지하는 방식을 취해보면 되지 않을까? 라는 생각을 하였습니다.

 

욕설문서

욕설이 아닌 문서

 

위와 같이 욕설과 욕설이 아닌 단어들을 분리하였고, similarity_search를 사용해서 단어별로 detection을 해봤습니다.

from langchain.text_splitter import CharacterTextSplitter
from langchain_community.document_loaders import DirectoryLoader
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings
from langchain.prompts import PromptTemplate
from langchain.chat_models import ChatOpenAI
from langchain.chains import RetrievalQA

loader = DirectoryLoader('DATA', glob="**/*.txt")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=6, chunk_overlap=1)
docs = text_splitter.split_documents(documents)
embeddings = OpenAIEmbeddings()
db = FAISS.from_documents(docs, embeddings)


# 데이터베이스 유사도 검색을 수행하고 특정 조건을 만족하는지 확인
def search_and_check(query):
    for word in query:
        docs_and_scores = db.similarity_search_with_score(word, k=2)
        print(f"Query: {word}")
        for doc, score in docs_and_scores:
            print((doc, score))
            if doc.metadata.get('source') == 'DATA/True.txt':
                return "욕설임"
    return "욕설아님"
    
    
# query 문자열을 띄어쓰기를 기준으로 분리
query = "돈 좀 빨아먹으려고".split() 
# 결과 확인
result = search_and_check(query)
print(f"Result: {result}")
# 예시문장 : 돈 좀 빨아먹으려고

Query: 돈
(Document(page_content='돈', metadata={'source': 'DATA/False.txt'}), 4.2787956e-06)
(Document(page_content='돈', metadata={'source': 'DATA/False.txt'}), 4.2787956e-06)
Query: 좀
(Document(page_content='좀', metadata={'source': 'DATA/False.txt'}), 3.5106605e-06)
(Document(page_content='좀', metadata={'source': 'DATA/False.txt'}), 3.5106605e-06)
Query: 빨아먹으려고
(Document(page_content='빨아먹으려고', metadata={'source': 'DATA/False.txt'}), 5.4894253e-06)
(Document(page_content='빨아쓸수', metadata={'source': 'DATA/False.txt'}), 0.15901968)
Result: 욕설아님
# 예시 문장 : 탑으로 뛰라니까 바텀가고 존213나 지@랄 났네 ㅋㅋ

Query: ㅋㅋㅋㅋㅋㅋ
(Document(page_content='ㅋㅋㅋㅋㅋㅋ', metadata={'source': 'DATA/False.txt'}), 3.7806683e-06)
(Document(page_content='ㅋㅋㅋㅋㅋㅋ', metadata={'source': 'DATA/False.txt'}), 3.7806683e-06)
Query: 탑으로
(Document(page_content='탑\n\n아,', metadata={'source': 'DATA/False.txt'}), 0.18086685)
(Document(page_content='탑재된', metadata={'source': 'DATA/False.txt'}), 0.20738958)
Query: 뛰라니까
(Document(page_content='뛰어봐라', metadata={'source': 'DATA/False.txt'}), 0.13721207)
(Document(page_content='내리니까', metadata={'source': 'DATA/False.txt'}), 0.16078945)
Query: 바텀가고
(Document(page_content='백퍼저거', metadata={'source': 'DATA/False.txt'}), 0.2223049)
(Document(page_content='버티고', metadata={'source': 'DATA/False.txt'}), 0.22352348)
Query: 존213나
(Document(page_content='존1나', metadata={'source': 'DATA/True.txt'}), 0.19029179)
Result: 욕설임

 

존213나 와 같은 우회적인 욕설도 탐지하는 모습입니다.

 

단어 별로 끊기 때문에 띄어쓰기가 안된 문장에는 적용할 수 없다는 점, 데이터셋을 모으기 어려운 점 등 여러 문제점은 많지만 이런 방법도 있다 알아두면 좋을 것 같습니다!