목차
  1. 프로젝트 소개
  2. 개발 과정
    1. Data Crawling & Labeling
    2. Preprocessing
    3. Word Embedding
    4. Classifier 모델 개발
  3. 성능 테스트
  4. 마치며

Daily Devblog 서비스를 위한 개발 문서 분류기를 개발한 이야기를 공유합니다.

프로젝트 소개

필자는 IT 블로그의 글을 모아서 매일 오전 10시에 이메일로 발송해주는 Daily Devblog라는 좋은 서비스를 구독하고 있습니다. 발송되는 글들은 Awesome Devblog에서 제공받는데, 가끔씩 IT와 관련 없는 글이 등장하기도 합니다. 큰 문제는 아니지만 서비스를 애용하는 구독자로서, 약간의 도움이라도 되었으면 하는 바람으로 딥러닝을 이용하여 간단한 문서 분류기를 만들어보았습니다. 🎉

개발에 사용된 코드 및 데이터는 아래 링크에서 확인하실 수 있습니다.


개발 과정

전체 개발 순서는 데이터 수집 및 전처리, 단어 임베딩 모델 개발 그리고 문서 분류모델 개발 순으로 진행하였습니다.


Data Crawling & Labeling

데이터는 Awesome Devblog에서 제공해주는 데이터를 이용하였습니다.

data sample
1
2
3
4
5
6
7
8
9
10
11
12
13
{ 
_id : "5e566644e0ffb12d4cae83ba",
title : "[영화 소감] 1917. 때론 영웅은 옆으로 달린다.",
link : "http://blog.hwang.gg/20200226/",
date : "2020-02-26T11:45:20.000Z",
description: "한국 사회에서 조직 문화 개선을 위해서 우리는 목숨을 내던지고 옆으로 달려야 하는가?",
author : "황건구",
imgUrl : "http://blog.hwang.gg/favicon.png",
tags : [ ],
count : 0,
postImage : "",
_class : "com.sarojaba.awesomedevblog.feed.Feed"
}

2020년 2월 초 기준으로 전체 34,620개의 글이 존재하였고, 이 중 10,382개의 글을 직접 라벨링하였습니다. 라벨링 작업이 가장 힘들었습니다.. 전체 데이터의 1/3 밖에 라벨링을 못했지만 일단 결과를 보고 싶어 다음 과정을 진행하였습니다.

라벨링 기준
1
2
3
4
     분류           라벨      개수
아직 라벨링 안된 글 -1 24,238개
개발과 관련 있는 글 1 7,634개
개발과 관련 없는 글 0 2,748개

라벨링을 완료한 1만개 데이터 중 개발과 관련있는 글과 관련없는 글의 비율은 약 3:1로 개발과 관련있는 글이 더 많았습니다.

라벨링 된 데이터 비율라벨링 된 데이터 비율

Preprocessing

가장 긴 대표 문장은 358자이고, 가장 짧은 문장은 3글자이며 평균 145글자로 이루어져있습니다. 각 문서의 대표값으로 임베딩된 단어들의 평균값을 사용할 예정이라 특정 길이로 문장을 자르거나 패딩을 채워넣지는 않았습니다.

1
2
3
4
5
6
7
문장 길이 최대 값 : 358
문장 길이 최소 값 : 3
문장 길이 평균 값 : 145.02
문장 길이 표준편차 : 60.59
문장 길이 중간 값 : 132
문장 길이 제 1 사분위 : 108
문장 길이 제 3 사분위 : 203
대표 문장 길이에 대한 Log-Histogram대표 문장 길이에 대한 Log-Histogram

Awesome Devblog에서 제공하는 데이터 중 link을 이용하면 본문 html을 받아올 수 있지만, 오래되어 존재하지 link도 상당히 많아 이번에는 간단히 title, tags, description만을 다음과 같이 전처리하여 이용하였습니다.

전처리 과정
1
2
3
4
5
6
7
8
9
배열로 이루어진 tags를 띄어쓰기를 기준으로 join
영어, 한글, 공백만 남김
html tag 삭제
\n, \r 삭제
2회 이상의 공백은 하나로 변환
영어 대문자 소문자로 변환
앞뒤 공백 삭제(strip)
제목이 공백인 데이터 제외
제목에 'about'이 단어가 들어간 데이터 제외

제목에 ‘about’이 단어가 들어간 데이터는 하나같이 블로그 주인에 대한 소개글로 Daily Devblog에 노출될 필요가 없다고 판단하여 제외하였고, tags, title, description을 합쳐서 하나의 대표 문장인 text를 만들었습니다.

학습에 쓰이는 데이터 예시
1
2
3
4
{
text : "영화 소감 때론 영웅은 옆으로 달린다 한국 사회에서 조직 문화 개선을 위해서 우리는 목숨을 내던지고 옆으로 달려야 하는가"
label : 0
}
전처리가 완료된 대표 문장의 단어들로 WordCloud를 나타내보았습니다.전처리가 완료된 대표 문장의 단어들로 WordCloud를 나타내보았습니다.

Word Embedding

Word EmbeddingGensim FastText를 이용하였습니다. 임베딩 모델 학습을 위한 데이터로 처음에는 크롤링한 데이터를 이용해 보았는데, RSS feed로 긁어온 데이터라 문장들이 끊켜있거나 html tag만 들어있는 등 데이터 질도 나쁘고 양도 부족하여 결과가 시원찮았습니다. 그래서 4.49GB의 wiki 한국어 데이터를 이용하여 임베딩 모델을 만들었는데 임베딩 모델의 용량만 14.5GB로 상당히 무거워졌지만 임베딩 결과는 훨씬 좋아졌습니다.

임베딩 모델을 이용하여 "파이썬"이라는 단어와 유사한 Top5를 뽑았습니다.
1
2
3
4
5
6
7
8
$ python word_embedding.py --predict '파이썬'
[
('Python', 0.565061628818512),
('자이썬', 0.5624369382858276),
('레일스', 0.5598082542419434),
('파이썬을', 0.5595801472663879),
('언어용', 0.5288202166557312)
]

다음으로 임베딩 모델을 이용하여 대표 문장을 벡터로 임베딩할 차례입니다. 임베딩 모델은 한 단어를 (300,) 형태의 벡터로 임베딩해주는데, 앞서 설명한 것과 같이 대표 문장을 이루는 각 단어들을 임베딩한 뒤 평균값을 문장의 벡터로 사용하였습니다.

embedding function
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
def embedding(self, we_model, text, embedding_dim=300):
"""
주어진 문장을 단어별로 벡터화한 뒤 평균값을 문장의 벡터로 반환

- input
: we_model / FastText 단어 임베딩 모델
: text / str / 벡터화하려는 문장
: embedding_dim / int / we_model vector의 차원 수 (wiki 기반 fasttext는 300)

- return
: nparray / shape = (embedding_dim)
"""
words = text.split(' ')
words_num = len(words)

# model dimension (wiki festtext의 경우 300)
vector = np.zeros(embedding_dim)
for word in words:
vector += we_model[word]
return vector/words_num

Classifier 모델 개발

Tensorflow 2.0.1의 tf.keras를 이용하여 레이어를 간단히 Dense Layer 3개만을 쌓아 구현하였습니다. LSTM으로도 구현해봤지만, 성능이 더 안좋아 Dense Layer만 이용하였습니다.

layer
1
2
3
4
5
6
7
8
9
10
model = Sequential()
model.add(Dense(100, activation='relu', kernel_initializer='he_normal', input_shape=(X_train.shape[1],)))
model.add(Dense(80, activation='relu', kernel_initializer='he_normal'))
model.add(Dense(2, activation='softmax'))
model.compile(optimizer='adam',
loss='sparse_categorical_crossentropy',
metrics=['acc',
self.f1_m,
self.precision_m,
self.recall_m])

성능 테스트

전체 10,382개의 데이터 중 33%(3,426개)를 test에 이용하여 모델의 성능을 테스트한 결과 validation accuracy 기준 88퍼센트가 측정되었습니다.

Model Performance
1
2
3
4
5
loss : 0.278
accuracy : 0.883
f1_score : 0.847
precision : 0.739
recall : 1.0

이렇게 만들어진 모델을 실제 Daily Devblog에서 2020년 2월 3일에 발송된 41개의 글의 제목을 이용하여 필터링 해보았습니다.(제목이 숫자로만 이루어진 글은 제외하였습니다.) 다중 predict의 구분자를 쉼표로 사용하여 구현하였기 때문에 제목에 있는 쉼표(,)는 모두 제거하였습니다.

predict 실행(쉼표를 이용하여 한 번에 여러 문장을 예측)
1
python predict.py --predict '맥북 초보자들이 꼭 알아야 할 단축키 top 5! mac keyboard shortcut 5. feat.맥북을 산  이유, [Spring Boot] 내장 웹 서버 - 2 (스프링부트 HTTPS / HTTP2), 네이버클라우드플랫폼 Certificate Manager 에 LetsEncrypt 인증서 등록, Go로 블록체인 만들기 #1, Ansible 파일 마지 막 변경 일자 확인하기, 엔티티 매핑, [11775]Compactness criteria for clustering, 쿠버네티스 CI/DI 를 위한 오픈소스 프로젝트 알아보기, [Windows] USB 윈도우 10 설치 /  다운로드 방 법, [Spring Boot] JAR파일(독립적으로 실행가능), 케라스(Keras)의 get_file 함수, 웹 서비스 Maintenance Mode (점검 모드) 지원기, Nodejs AES 128 CCM 암호화(복호화) 예제 - crypto, 영어패턴#37, [운영체제(OS)] 7. 쓰레드(Thread), 비동기 처리와 콜백함수 그리고 Promise, docker기반 데이터 시각화툴 Superset 설치하기 (리눅스), Wayland과 Weston, 테스트 주도 개발(Test-Driven Development:By Example) - 1부 : 화폐 예제 (9 ~ 10장), 당신은 정말 의지가 있는가?, [B급 프로그래머] 1월 5주 소식(빅데이터/인공지능 읽을거리 부문), 손흥민 시즌 13호골! epl 25라운드 토트넘 vs 맨체스터시티, [운영체제(OS)] 9. 프로세스 동기화 2, 자바 String StringBuilder 그리고 StringBuffer 차이 비교, (업무)2020년 2월 3일 REACT로 임상시험자동화솔루 션 개발 Start, [운영체제(OS)] 11. 모니터, 필리핀 세부 시티의 맛집! 하우스 오브 레촌 cebu city House of Lechon, [운영체제(OS)] 8. 프로세스 동기화 1, 영어패턴#38, c언어 fopen 함수 : 파일을 연다., 스프링 데이터 JPA와 Querydsl 인프런 강의 정리, [Sprint #10] Server Side Techniques Sprint, 카카오메일 IMAP / POP3 / SMTP 설정방법, [Algorithm] 이진트 리의 구현 과 순회 알고리즘, [운영체제(OS)] 10. 프로세스 동기화 3, [맥북] 트랙패드 제스처, [스프링 부트 개념과 활용] 로깅, 기술 뉴스 #143 : 20-02-03, [스프링 부트 개념과 활용] Profile, 언 덕 너머'

아래의 결과를 보면, 비개발 문서로 판단한 Ansible 파일 마지막 변경 일자 확인하기라는 글을 제외하고 전체 40개의 글 중 39개를 정확하게 분류하여 97.5%의 성능을 보였습니다. 🎉🎉

비개발 문서로 판단
1
2
3
4
5
6
7
(False, 0.191) 영어패턴#37
(False, 0.013) 당신은 정말 의지가 있는가?
(False, 0.041) 손흥민 시즌 13호골! epl 25라운드 토트넘 vs 맨체스터시티
(False, 0.051) 필리핀 세부 시티의 맛집! 하우스 오브 레촌 cebu city House of Lechon
(False, 0.191) 영어패턴#38
(False, 0.053) 언덕 너머
(False, 0.185) Ansible 파일 마지막 변경 일자 확인하기 😓
개발 문서로 판단
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
(True, 0.983) 맥북 초보자들이 꼭 알아야 할 단축키 top 5! mac keyboard shortcut 5. feat.맥북을 산  이유
(True, 1.000) [Spring Boot] 내장 웹 서버 - 2 (스프링부트 HTTPS / HTTP2)
(True, 0.971) 네이버클라우드플랫폼 Certificate Manager 에 LetsEncrypt 인증서 등록
(True, 0.791) Go로 블록체인 만들기 #1
(True, 1.000) 엔티티 매핑
(True, 0.973) [11775]Compactness criteria for clustering
(True, 0.989) 쿠버네티스 CI/DI 를 위한 오픈소스 프로젝트 알아보기
(True, 0.992) [Windows] USB 윈도우 10 설치 / 다운로드 방법
(True, 0.981) [Spring Boot] JAR파일(독립적으로 실행가능)
(True, 1.000) 케라스(Keras)의 get_file 함수
(True, 0.987) 웹 서비스 Maintenance Mode (점검 모드) 지원기
(True, 0.996) Nodejs AES 128 CCM 암호화(복호화) 예제 - crypto
(True, 0.706) [운영체제(OS)] 7. 쓰레드(Thread)
(True, 1.000) 비동기 처리와 콜백함수 그리고 Promise
(True, 0.994) docker기반 데이터 시각화툴 Superset 설치하기 (리눅스)
(True, 0.980) Wayland과 Weston
(True, 0.937) 테스트 주도 개발(Test-Driven Development:By Example) - 1부 : 화폐 예제 (9 ~ 10장)
(True, 0.584) [B급 프로그래머] 1월 5주 소식(빅데이터/인공지능 읽을거리 부문)
(True, 0.999) [운영체제(OS)] 9. 프로세스 동기화 2
(True, 1.000) 자바 String StringBuilder 그리고 StringBuffer 차이 비교
(True, 0.892) (업무)2020년 2월 3일 REACT로 임상시험자동화솔루션 개발 Start
(True, 0.544) [운영체제(OS)] 11. 모니터
(True, 0.999) [운영체제(OS)] 8. 프로세스 동기화 1
(True, 1.000) c언어 fopen 함수 : 파일을 연다.
(True, 0.726) 스프링 데이터 JPA와 Querydsl 인프런 강의 정리
(True, 0.991) [Sprint #10] Server Side Techniques Sprint
(True, 0.463) 기술 뉴스 #143 : 20-02-03
(True, 0.632) 카카오메일 IMAP / POP3 / SMTP 설정방법
(True, 0.985) [Algorithm] 이진트 리의 구현과 순회 알고리즘
(True, 0.999) [운영체제(OS)] 10. 프로세스 동기화 3
(True, 0.989) [맥북] 트랙패드 제스처
(True, 1.000) [스프링 부트 개념과 활용] 로깅
(True, 1.000) [스프링 부트 개념과 활용] Profile

마치며

이번 버전에서는 가볍게 title / tags / description만 이용하여 분류기를 만들어보았습니다. 진행하면서 명확한 라벨링 규칙이 필요하다고 생각이 들었고, title / tags / description만 봐서는 사람이 봐도 헷갈리는 데이터가 많아 결과가 잘 나올까 불안했지만, 다행히 실제 데이터에 적용했을 때 생각보다 만족스러운 결과가 나왔습니다. 다음 버전에서는 2만개의 데이터를 추가 및 title / tags / description가 아닌 데이터의 link를 이용하여 html 문서를 받아와 학습을 진행해보도록 하겠습니다.

이상 딥러닝으로 문서 분류기 만들기 프로젝트를 마치겠습니다.