Scikit-Learn과 Lime을 이용한 비속어 필터링

이번 글에서는 Scikit-Learn을 이용해서 악성(Toxic)문장을 분류하는 분류기를 만들고, Lime을 이용해 분류기의 결과를 분석해서 문장의 어떤 단어가 분류결과에 영향을 미쳤는지 알아보려고 합니다. 이를 통해 악성으로 분류하는데 큰 영향을 미치는 단어를 비속어라고 가정하고 문장에서 이를 제거하는 비속어 필터를 만들어보겠습니다.

주의: 영어 욕설이 많이 나옵니다.

아래는 제가 작성한 전체 코드입니다. 구글 코랩에서 작성했고, 데이터는 Google Drive에 올린 후 마운트해서 가져왔습니다.

코드는 안재현님이 위키북스에서 출간한 “XAI 설명 가능한 인공지능, 인공지능을 해부하다”와 GitHub의 Lime: Explaining the predictions of any machine learning classifier 의 예제 코드를 참고했습니다.

사용한 데이터셋

2018년에 캐글에서 진행된 Toxic Comment Classification Challenge의 데이터셋을 사용합니다. 인터넷 상의 영어 댓글들을 모아서 toxic, severe_toxic, obscene, threat, insult, identity_hate 6가지의 카테고리를 지정했습니다. 여러 카테고리에 속할 수 있습니다. 이 중 하나에라도 속한 문장을 악성문장이라고 하겠습니다. 어떤 카테고리에도 속하지 않는 문장도 있습니다. 이런 문장은 일반(Normal) 문장이라고 하겠습니다.

Image for post
Image for post

캐글에서 다운로드받고 드라이브에 저장한 csv 파일을 pandas로 불러옵니다.

Image for post
Image for post

이후 텍스트 데이터를 뽑아내고, 악성 문장을 1, 일반 문장을 0으로 변환합니다.

Image for post
Image for post

분포를 보면 왼쪽 일반 문장이 약 14만개, 오른쪽 악성 문장이 약 16000개로 쏠림이 심한 편입니다. 분류기가 모든 문장을 일반 문장으로 분류하기만 해도 정확도는 90% 가까이 됩니다.

TF-IDF 벡터화하기

학습을 위해선 텍스트를 벡터로 바꿔야합니다. Word Embedding 등 다양한 방법이 있지만 간단하게 TF-IDF를 사용해보겠습니다.

Image for post
Image for post

텍스트가 총 189775개의 term에 대한 TF-IDF 벡터로 바뀌었네요. 이 개수를 줄이거나 조절할 수 있지만 그건 나중에 해보도록 하겠습니다.

분류기 만들기

이제 이걸 분류해보겠습니다. 분류기로는 다중분류 NB를 사용했습니다.

Image for post
Image for post

정확도는 92%가 나왔습니다. 분류결과표는 아래와 같습니다.

[[일반 문장인데 일반 문장으로 분류(TP), 일반 문장인데 악성 문장으로 분류(FN)]
[악성 문장인데 일반 문장으로 분류(FP), 악성 문장인데 악성 문장으로 분류(TN)]]

악성 문장 분류의 정확도는 98.77%로 일반 문장 정확도인 92%보다 높습니다. 일반 문장이 훨씬 많은것에 비하면 악성문장이 더 정확도가 높은게 놀랍네요. 하지만 일반문장이라고 분류된것 중 8%가 악성 문장일 정도로 일반중만 정확도는 상대적으로 떨어집니다. 일반 문장을 더 정확하게 분류해낼 수 있다면, 정확도를 더 끌어올릴 수 있겠네요.

Lime은 로컬대리분석 기법 중 하나입니다. 대리분석은 분류기와 비슷한 예측결과를 만드는 설명 가능한 분류기를 만든 후, 이 설명 가능한 분류기를 통해서 기존 분류기의 결과를 설명합니다. 이중 로컬대리분석은 분류기가 예측하는 데이터 하나하나에 대해서 예측 결과를 분석하는 기법입니다.

Lime은 데이터 중 일부를 생략해서 분류기에 넣고 그것과 유사한 결과를 갖는 선형 모델을 학습합니다. 문장에서는 일부 단어를 생략하고, 이미지라면 이미지의 일부만을 떼어내서 분류기에 넣습니다. 원래의 분류기는 떼어낸 부분을 주더라도 왜 이런 결과를 내는지 알 수 없습니다. 하지만 같은 결과를 내는 설명 가능한 분류기를 통해 그 과정을 알 수 있습니다. 그렇기에 이미지의 어떤 부분, 문장의 어떤 단어가 결과에 영향을 미치는지 알게됩니다.

파이썬의 lime 패키지를 이용하면 쉽게 결과를 분석하고 바로 노트북에서 분석결과를 확인할 수 있습니다.

Image for post
Image for post
lime github에 있는 예시

먼저 일반 문장으로 잘못 분류된 악성문장을 보겠습니다.

Image for post
Image for post

scum이라는 명백한 비속어가 있었음에도 비속어로 판별되기에는 부족했군요. 하지만 영향을 많이 준 단어를 가져오면 비속어 필터로 사용할 수 있을 것 같습니다. is 같은 불용어가 제거되지 않아 영향을 주는 모습도 보입니다.

Image for post
Image for post

Keftiu, Caphtor는 성서에 나오는 지역 이름인 것 같군요… TF-IDF 에 min_df(단어의 문서당 최소등장횟수)가 없어서 이런 단어들도 영향을 주나봅니다.

Image for post
Image for post

욕설은 없지만 비난하는 내용인 것 같습니다. 어차피 목표는 비속어 필터를 만드는 거니까 이런 건 틀려도 괜찮을 것 같습니다. 아예 데이터셋에서 빼는게 좋겠네요.

이번엔 일반 문장을 악성 문장으로 잘못 분류한 결과를 보겠습니다.

Image for post
Image for post
Image for post
Image for post

단위나 잘 DAT, SERT, 3H같은 단어와 일본인 이름이 잡히네요. 마찬가지로 이런 건 TF-IDF에서 제거되어야겠습니다. 악성 문장으로 잘못 분류된 데이터에는 이런 경우가 굉장히 많았습니다.

  1. 자주 쓰이는 단어만 사용
  2. 불용어(Stop-words)제거(is, as 등…)
Image for post
Image for post

최소 10개 이상의 데이터에서 나온 단어만 사용하고, 영어 불용어를 제거헀습니다. TF-IDF 벡터의 크기가 1/6으로 줄었습니다.

Image for post
Image for post

전체적인 정확도는 3%p 올랐지만, 악성문장 분류의 정확도는 95%로 떨어졌습니다. 이전에 본 데이터들을 다시 확인해볼까요?

Image for post
Image for post

이제는 is가 영향을 주지 않는군요!

Image for post
Image for post
Image for post
Image for post

DAT는 남아있는걸 보니 10개 이상의 문장에서 쓰이는 단어인가봅니다. 다른 단어들은 사라졌습니다.

Image for post
Image for post

이 둘은 여전히 제대로 예측하지 못하고 있습니다. 문장의 길이가 길면 일반 문장에서 사용되는 여러 단어들이 영향을 주는 것 같습니다. 실제로 다른 긴 문장들을 보면 욕설들을 찾아내지만, 일반 단어들이 영향을 주는 경우를 많이볼 수 있었습니다.

Image for post
Image for post

이런 문제는 긴 문장은 짧게 쪼개는 방식으로 해결할 수 있습니다. 실제 위 문장을 쪼갤경우, 비속어가 들어간 문장은 제대로 평가됩니다.

Image for post
Image for post
쪼개서 다시 분류한 경우 잘 됨

비속어 필터 만들기

어찌됬건 우리 목표는 이게 비속어냐 아니냐를 맞추기보단, Lime을 이용해서 비속어를 찾아서 필터링하는게 주 목적입니다. 따라서 분류 결과와는 상관없이 lime에서 단어가 얼마나 영향을 미쳤는지만을 가지고 비속어를 찾아내서 *****로 바꿔버리는 함수를 만듭니다. as_list() 함수로 결과를 가져올 수 있습니다.

Image for post
Image for post
=====================
Original Text:
Please turn off your fucking cell phone please

Filtered:
Please turn off your ******* cell phone please
=====================
=====================
Original Text:

You are wasting your time. The Fascists of Wakopedia will never allow anything bad to be written about a Liberal.
Jesus you can trash all you want Wakopedia loves when people do that but don't touch a liberal or you will be banned.


Filtered:

You are wasting your time. The ******** of ********* will never allow anything bad to be written about a Liberal.
Jesus you can ***** all you want ********* loves when people do that but don't touch a liberal or you will be banned.

=====================
=====================
Original Text:

uan pablo montoya

Bold texthe drives fast cars and likes hot girls he is a pimp


Filtered:

uan pablo montoya

Bold texthe drives fast cars and ***** hot girls he is a ****

=====================

개선할 점

이건 TF-IDF의 한계점이기도 한데, 문맥을 반영하지 않습니다. hell, suck 같이 악성 문장과 일반 문장에서도 쓰이는 단어를 문맥에 따라 다르게 처리할 수 없습니다. 또한 비속어의 변형(DEEEEEEK) 등을 제대로 대처하지 못합니다. 이에 대응하기 위해선 문장의 순서나 유사한 단어를 판단할 수 있는 모델이 필요합니다. 또 위에서 보듯 긴 문장에 들어간 소수의 비속어가 점수가 낮아지는 현상이 있습니다. 문장을 짧게 쪼개며 판단해야합니다.

Lime이 조금 오래걸립니다. 분류기를 더 빠른 모델로 바꾸면 빨라질 수 있습니다. 아마 지금 코랩 런타임에서 GPU를 사용하지 않기 때문에 그럴 것 같습니다. 하지만 Lime 기법 자체가 비결정적이기때문에, 데이터에 따라 소요되는 시간이 달라지게됩니다.

끝으로

XAI 기법은 단순히 모델이 이래서 이럴 것이다~가 아니고 좀 더 구체적으로 해석하게 하는데 확실히 큰 도움을 줍니다. 또 비속어 필터처럼 아예 XAI기법을 사용해서 기능을 개발할 수도 있습니다. 이런 점이 XAI의 큰 매력이라고 생각됩니다.

주말안에 마무리하고싶었는데, 글을 쓰고 여러 실험을 해볼수록 부족한 점이나 개선할만한 점이 계속 보여서 끝이 안나다보니 급하게 마무리한 느낌이 있네요 ㅠ… 원래 이 프로젝트를 시작한 목표는 Lime을 이용해서 간단하게 필터링을 시도해보는 것이었습니다. TF-IDF를 이용한 간단한 모델에서도 괜찮은 결과를 보였습니다. 추후 데이터를 모아서 한글 비속어 처리도 시도해봐야겠습니다.

참고자료

Written by

2020.12.8 ~ 2022.6.9 군복무중 Serving in the South Korean Military Service

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store