안녕하세요. 빅데이터 센터 AI Lab의 이경호입니다.
오늘은 저희 팀에서 weak supervision framework 인 Snorkel을 키워드 추출 연구에 활용한 경험을 공유드리려고 합니다.
이 글의 1장에서는 Snorkel을 사용하게 된 배경에 대해 설명드리고 2장에서는 Snorkel에 대한 간략한 설명을 드리겠습니다. 그리고 3장과 4장에서 실제 Snorkel의 활용한 경험과 그 결과에 대해 공유 드리도록 하겠습니다.
1. 연구 배경
1.1 연구 목적
- 드라마앤컴퍼니는 90만 인재가 등록되어 있는 리멤버 커리어 서비스를 운영하고 있습니다(https://news.mt.co.kr/mtview.php?no=2022040709102461945).
- 인재와 회사를 더 잘 연결하기 위해, 인재들이 가지고 있는 역량을 파악하는 것이 저희 AI Lab의 주요 연구 과제 중 하나 입니다. 인재의 역량을 파악하기 위해 명함 네트워크, 커리어 전환 등 다양한 요소를 연구하고 있는데요, 이번에는 인재의 프로필에서 인재가 가진 직무 능력을 나타내는 키워드(직무 키워드)를 추출하는 연구를 진행하였습니다.
그림 1. 리멤버 커리어에 유저가 입력한 프로필의 예
리멤버 커리어에서 자신의 업무 경력을 담은 프로필을 작성할 수 있습니다.
1.2 직무 키워드 추출
- 텍스트로부터 키워드를 추출하는 방법은 다양합니다. 저희는 키워드 활용 목적과 보유 데이터를 고려하여 1) 후보 키워드 인식 2) 직무 키워드 선택 단계를 통해 직무 키워드를 추출하였습니다. 이렇게 추출된 키워드는 유사 인재 추천, 채용 공고와 인재의 적합도 평가 등 다양하게 활용될 수 있습니다.
- 인재가 보유한 직무 능력을 풍부하게 파악하기 위해, 이번 연구에서는 인재가 등록한 스킬 목록과 함께 자기소개와 업무 상세, 부서명에서도 직무 키워드를 추출하였습니다. 그림 2는 프로필 텍스트로부터 직무 키워드를 추출하는 과정의 예입니다.
그림 2. 키워드 추출 단계 예
1.2.1 후보 키워드 인식
- 저희 빅데이터 센터의 데이터팀에서는 유저가 입력한 데이터를 기반으로 1만 5천여개의 직무 스킬 키워드를 관리하고 있습니다. 이러한 데이터를 이용해 스킬 태거(Skill Tagger) 를 만들었고, 태거를 이용해 텍스트에서 등장한 직무 키워드를 인식하였습니다. 예제 1은 태거를 통해 그림 1의 자기소개를 태깅한 결과 입니다.
예제 1. 스킬 태거 결과 예
1.2.2 직무 키워드 선택
- 그림2의 ‘채용’, ‘명함’과 같이, 인재가 보유한 직무 능력을 나타내기에 적합하지 않은 키워드들이 태깅과정에서 인식 될 수 있습니다. 그렇기 때문에, 태깅을 통해 인식된 키워드(후보 키워드)들 중 인재가 가진 직무 역량을 잘 나타낼 수 있는 단어들을 선별하는 과정이 필요합니다.
- TextRank, Yake!와 같이 다양한 keywords, key-phrase 추출 연구들이 존재합니다. 하지만 다음과 같은 문제로 저희 문제에는 적합하지 않다고 판단했습니다.
-
- 저희는 이미 인식된 후보 키워드 중 직무 키워드를 찾아야 합니다.
-
- 인재가 선택한 직무나 재직해온 회사의 업종, 부서명과 같은 컨텍스트 정보를 이용하면 직무 키워드 판단에 도움을 받을 수 있습니다. 하지만 기존 모델에서는 이러한 정보 반영이 제한적입니다.
-
- 기존 방식은 단어의 co-occurrence에 기반한 그래프 정보나 단어의 빈도수, 품사 정보 등의 자질 정보를 기반으로 키워드를 선택합니다. 하지만 프로필 텍스트의 구조와 문제의 특성상, 이러한 정보로 키워드를 온전히 판단하기 어렵다고 생각했습니다.
-
- 이러한 이유로, 인재의 컨텍스트 정보와 후보 키워드들을 입력받아 각 후보 키워드의 직무 키워드 여부를 판별하는 트랜스포머 인코더 기반의 이진 분류 모델을 구성하였습니다.
1.2.3 모델
- 이번 연구에서 사용한 모델은 그림 3과 같습니다. 모델에 대한 자세한 설명은 생략하겠습니다.
- 입력:
- 컨텍스트 정보:
- 유저가 선택한 직무 category IDs,
- 재직한 업종 category IDs,
- 재직한 부서명들의 tokenized 결과
- list of 후보 키워드:
- 태거를 통해 인식된 후보 키워드 ID
- 후보 키워드의 인식 위치(스킬, 부서명, 자기소개 또는 업무 상세)
- 컨텍스트 정보:
- 출력
- 각 후보 키워드의 직무 키워드 확률
- 입력:
그림 3. 직무 키워드 선택 모델 Architecture와 입력 예
목적에 맞는 모델을 만들었습니다!
그런데, 학습을 위한 레이블은 어디서 구해야 하죠?
- 일반적으로 레이블 데이터(label data)는 사람의 수작업을 통해 얻을 수 있습니다. 하지만 레이블링 수작업은 시간과 비용이 많이 소요됩니다. 특히 의학 데이터와 같이, 레이블링에 전문성이 필요한 경우 더욱 그러하지요. 또한 레이블링 정책 변경에 대해서도 유연하지 못합니다. 직무 키워드 선택 문제도 마찬가지 인데요, 직무와 업종이 다양하기 때문에 비전문가가 키워드 여부를 레이블링하기 쉽지 않은 일이라 판단했습니다.
- 이러한 이유로, Snorkel을 사용해 보았습니다.
2. Snorkel
- Snorkel은 개발자가 작성한 프로그램의 조합을 이용해 unlabeled 데이터에 레이블을 할당해주는 프레임워크입니다. 이렇게 만들어진 labeled data를 이용해 모델을 학습합니다.
2.1 Labeling Function
- 사용자 입장에서 Snorkel의 장점을 요약하자면 “프로그래밍을 이용해 레이블링을 할 수 있다” 일 것 같습니다.
- Snorkel을 사용하기 위해 Labelging Function(LF) 을 정의합니다. LF는 레이블을 달고자 하는 unlabeled 데이터 인스턴스를 입력받고 해당 인스턴스의 레이블 또는 판별 불가 레이블(ABSTAIN)을 반환하는 Python 함수입니다.
- 예제 2는 Snorkel 튜토리얼에서 설명하고 있는 LF 예 입니다. 이 LF의 입력 x는 텍스트이고 출력은 x의 스팸 메시지 여부 입니다. 여기서는 간단하게 string type을 입력 받지만 Snorkel 라이브러리는 입력에 대한 다양한 형식을 지원합니다. 만약 레이블을 판별하기 어려울때는 ABSTAIN을 리턴하여 이 인스턴스에 대해 해당 LF는 레이블을 판별하지 못했다는 정보를 전달합니다.
예제 2. Snorkel 공식 튜터리얼 LF 설명 예
- 도메인 전문가가 레이블링 규칙을 코드로 작성하거나 기술한 결과를 프로그래머가 코드로 작성함으로써, 전문가의 지식을 모델 학습에 녹여낼 수 있습니다. 하나의 LF에서 모든 규칙을 포괄해야할 필요는 없습니다. 여러개의 LF함수를 만듦으로써 도메인 전문가의 지식을 유연하게 표현할 수 있습니다.
- 또한 외부 지식 베이스 활용하거나 유사한 데이터 또는 다른 방식으로 학습된 머신러닝 모델, 크라우드 작업자의 작업 결과들도 LF로 표현할 수 있습니다. 이러한 유연한 확장이 Snorkel의 장점입니다.
2.2 Generative Model
- unlabeled 데이터 인스턴스들에 4개의 LF를 적용한 결과가 예제 3과 같다고 가정해 봅시다. Snorkel에서는 이러한 매트릭스를 Label Matrix라고 부릅니다.
예제 3. SPAM과 HAM이라는 두가지 레이블을 예측하는 문제의 label matrix 예
- 인스턴스의 최종 레이블은 어떻게 정하면 될까요? 가장 쉬운 방법은 여러 LF 출력의 투표 결과(voting)를 따르는 방법일 것입니다. 하지만 앞서 말한바와 같이, Snorkel의 LF는 유연하게 정의될 수 있기 때문에 그만큼 서로간의 의존관계나 충돌이 있을 수 있고 LF들의 신뢰도가 다 다를 수 있습니다.
- Snorkel에서는 생성 모델(generative model)을 이용해 LF 간의 관계와 가중치를 분석합니다. label matrix을 이용해 학습된 생성 모델을 기반으로 각 LF 간의 관계를 추론하고 추론된 결과를 반영한 레이블을 얻을 수 있습니다. 해당 내용은 이 글의 범위를 넘어서기 때문에, 추후에 다뤄보도록 하겠습니다.
- Snorkel을 고도화 하기 위한 연구는 계속 진행되고 있습니다. Snorkel의 자세한 이론적 배경은 아래 논문들에서 확인할 수 있습니다.
2.3 Snorkel을 이용한 학습
- generative model을 통해 생성된 레이블을 unLabeled 데이터의 label로 간주하여 모델을 학습함으로써 Snorkel을 이용한 모델 학습의 한 사이클이 마무리 됩니다. Snorkel의 목적은 ‘좋은 학습데이터’를 만드는 것이지 LF를 통한 예측 모델 자체를 만드는 것이 아닙니다. 여러 데이터 소스를 이용해 만들어진 labeled data를 기반으로 더 나은 예측을 수행하는 모델을 만드는 것이 목적입니다.
“Our goal for LF development is to create a high quality set of training labels for our unlabeled dataset, not to label everything or directly create a model for inference using the LFs.
https://www.snorkel.org/use-cases/01-spam-tutorial#recommended-practice-for-lf-development
The training labels are used to train a separate discriminative model (in this case, one which just uses the comment text) in order to generalize to new, unseen data points.
Using this model, we can make predictions for data points that our LFs don’t cover.”
- 그림 4는 관련 논문에서 소개하는 Snorkel 사용의 전체 과정 요약입니다.
그림 4. Snorkel architecture(”Snorkel: Rapid Training Data Creation with Weak Supervision”, 2017)
3. 직무 키워드 추출에 적용
3.1 키워드 추출 LF 정의
- Snorkel을 저희 문제에 활용하기 위해, 저희가 가지고 있는 데이터와 경험을 바탕으로 “이런 컨텍스트에서는 이런 키워드가 중요할 것이다" 라는 6개의 규칙을 정의하였고 이들 각각을 LF로 표현하였습니다. 이 중 2가지 LF에 대해 소개하겠습니다.
3.1.1 LF 1: 검색어 사용 빈도수 기반 판별
- 직무 키워드 판단을 위해 키워드 자체의 중요성도 고려되어야 한다고 생각합니다. 이를 위해 리멤버 커리어의 검색 데이터를 활용하였습니다.
- 리멤버 커리어의 검색 시스템을 이용해 기업의 리크루터들이 인재를 검색하고 제안을 보냅니다. 리크루터는 원하는 인재를 찾기 위해, 그 인재가 가지고 있기 바라는 핵심 키워드를 검색어로 입력할 것 입니다. 그렇기 때문에 검색어에 많이 등작한 키워드라면, 중요한 키워드일 가능성이 높을 것이라 가정했습니다. 그리고 사용 빈도수가 적은 경우엔 중요한 키워드가 아니라 가정했습니다.
- 검색어에 등장하지 않은 키워드라면 중요하지 않다고 볼 수도 있고, 또는 너무 구체적이라 사용되지 않았을 수도 있습니다. 이런 경우 판단을 보류 했습니다. 검색어로 사용된 빈도수가 애매한 경우(많고 적음의 기준 사이)에도 판단을 보류 했습니다.
- LF_1은 이러한 가정을 반영한 함수입니다.
예제 4. 검색어 빈도수 기반 Labeling Function
@labeling_function(resources=dict(query_counter=query_counter)) def LF_1(x, query_counter): """ 입력 x = { "candidate": "명함", "context": { "job_categories": [123], "industries": [34, 23, 54], "department_tokens": [135, 14014, 3198, ...] }, ... } """ if x["candidate"] not in query_counter: # 검색어로 사용된적 없다면, 판단 불가 return ABSTAIN query_count = query_counter[x["candidate"]] # 해당 키워드가 검색어로 사용된 빈도수 if query_count >= query_counter_over: # 검색어로 사용된 빈도가 일정 이상이라면, 직무 키워드 return KEYWORD if query_count <= query_counter_under: # 검색어로 사용된 빈도수가 일정 이하라면, 일반 키워드 return NORMAL return ABSTAIN # 기준에 부합하지 않는다면, 판단 보류
3.1.2 LF 2: 컨텍스트 – 후보 키워드 관계 기반 판별
- 직무 키워드 여부는 키워드가 등장한 컨텍스트에 따라 달라질 수 있습니다. 그림 2의 ‘채용’, ‘명함’과 같은 키워드도 인사 업무 관련 컨텍스트나 인쇄, 출판 업무 관련 컨텍스트에서는 직무 키워드일 수 있습니다. LF_2 는 컨텍스트와 후보 키워드의 관계를 기반으로 직무 키워드 여부를 판별하는 LF 입니다.
- 컨텍스트와 키워드의 관계 파악을 위해, word2vec 알고리즘을 이용하여 서로 관련이 있는 컨텍스트 구성 요소(업종, 직무, 부서명 token)와 스킬 키워드들이 가까이 위치할 수 있도록 임베딩 벡터를 학습하였습니다.
- 이 임베딩에 기반하여, 컨텍스트 벡터(컨텍스트 구성 요소의 임베딩 벡터 평균)와 후보 키워드 벡터의 코사인 유사도로 컨텍스트와 후보 키워드의 관계 정도를 표현하였습니다.
- 컨텍스트와 후보키워드의 관계가 어느정도이면 (얼마나 유사도가 높다면) 직무키워드로 판별할지(또는 그 반대일지) 결정하기 위해, 학습데이터의 전체 컨텍스트와 각 컨텍스트의 후보키워드들의 코사인 유사도를 계산하였습니다. 그리고 이 분포를 기반으로 일정 유사도 이상(예제 5의 cosims_keyword)이면 직무 키워드로, 일정 유사도 이하(예제 5의 cosims_normal)라면 일반 키워드로 판별하도록 LF를 구성하였습니다. 그리고 그 사이의 유사도를 가진 키워드의 경우 판단을 보류하였습니다.
예제 5. 컨텍스트 – 후보 키워드 유사도 기반 labeling function
@labeling_function(resources=dict(embeddings=embeddings)) def LF_2(x, embeddings): # 컨텍스트와 후보 키워드의 유사도 계산 vec_candidate = embeddings[x["candidate"]] # 후보 키워드의 임베딩 벡터 vec_context = average(embeddings, x["context"]) # 컨텍스트 벡터 생성 함수 candidate_comsim = cosine_similarity(vec_context, vec_candidate) if candidate_comsim >= cosims_keyword: # 유사도가 기준치 이상이라면, 직무 키워드 return KEYWORD if candidate_comsim <= cosims_normal: # 유사도가 기준치 이하라면, 일반 키워드 return NORMAL return ABSTAIN # 기준에 부합하지 않는다면, 판단 보류
- 이 외에도 컨텍스트에서 키워드가 사용된 비율, 각 직무-업종별 키워드의 분포 등, 키워드 추출에 필요하다고 생각된 규칙들을 LF로 표현하였습니다.
3.2 학습 데이터 생성
- 인재들이 등록한 실재 프로필을 1.2.3절에서 소개한 모델의 입력 데이터 형식으로 변환했습니다. 그리고 이 데이터를 다시 예제 4의 입력과 같은 형식의 데이터로 변환하여 LF들에 입력했습니다.
- Snorkel에서는 여러 LF를 많은 unlabeled 데이터에 쉽게 적용할 수 있도록 도와주는 다양한 Applier을 제공하고 있습니다. 이를 이용해 손쉽게 Label Matrix를 생성할 수 있습니다(예제 6의 2 ~ 3).
예제 6. Label Matrix 생성과 분석
lfs = [LF_1, LF_2, LF_3, LF_4, LF_5, LF_6] # ····· (1) applier = LFApplier(lfs=lfs) # ····· (2) L_train = applier.apply(train_inputs) # ····· (3) lf_analysis = LFAnalysis(L=L_train, lfs=lfs).lf_summary() # ····· (4) label_model = LabelModel(cardinality=2, verbose=True) # ····· (5) label_model.fit(L_train=L_train, n_epochs=10000, log_freq=100, seed=7) # ····· (6) predict_labels = label_model.predict(L=L_train, tie_break_policy="abstain") # ····· (7)
- applier를 통해 생성된 label matrix를 이용해 생성 모델을 학습합니다(예제 6의 5 ~ 6).
- 그리고 학습된 생성 모델에 label matrix를 입력하여 각 데이터 인스턴스의 레이블을 생성합니다(예제 6의 7).
- 이렇게 생성된 각 키워드 후보의 레이블을 다시 프로필별로 모아서 최종적인 모델 학습 데이터를 생성하였습니다(예제 7).
예제 7. 생성된 모델 학습 데이터 예
{ "context": { "job_categories": [123], "industries": [34, 23, 54], "department_tokens": [135, 14014, 3198, ...] } "skill_keywords": ["명함", "NLP", "인공지능", ...], "keyword_type": ["description", "skill", "department", ...] "labels": [0, 1, 1, ....] # Snorkel을 통해 생성된 labels }
3.3 LFAnalysis
- Snorkel에서는 LF의 결과 분석을 도와주는 유틸리티를 제공합니다. 예제 8은 앞서 생성한 직무 키워드 추출용 LF의 분석 결과 입니다(예제 6의 4).
예제 8. 키워드 추출 LF anaiysis
- 예제 8에서 LF 1은,
- Polarity: 0(NORMAL)과 1(KEYWORD) 레이블을 출력합니다.
- Coverage: 입력 데이터의 약 78%에 대한 레이블을 출력하고 나머지에 대해서는 판정을 보류했습니다.
- Overlaps: 출력 결과 중 약 78%는 1개 이상의 다른 다른 LF의 출력과 동일합니다.
- Conflicts: 출력 결과 중 약 77%는 1개 이상의 다른 LF의 출력과 다릅니다.
- 데이터의 coverage가 높다고 해서 꼭 좋은 것은 아니라고 합니다. Snorkel은 ‘좋은 학습 데이터 생성’이 목적이기 때문에, 부정확하게 많은 데이터를 cover한다고 해서 꼭 좋은 것은 아닐 것입니다. 마찬가지로 overlaps이나 conflicts가 높다고해서 나쁜 LF 결과라고 볼 수 없을 것 입니다. Snorkel에서는 이 값을 토대로 LF를 더 향상할 수 있는 방향을 제안합니다.
4. 모델 학습과 분석
- Snorkel을 이용해 생성된 데이터(예제 7)를 기반으로 직무 키워드 예측 모델(그림 3)을 학습했습니다. 그리고 모델이 실제로 중요한 직무 키워드를 선택할 수 있을지 평가해보았습니다. 실제 직무키워드 여부에 대한 레이블 데이터가 없기 때문에, 간접적인 평가 방법을 고안하였습니다.
4.1 Fake Keywords 찾기
- 모델이 직무 키워드 여부를 구분할 수 있도록 학습하였기 때문에, 직무 키워드가 아닌 키워드를 얼마나 잘 구분해내는지를 측정하면 모델의 성능을 간접적으로 가늠해볼 수 있을 것이라 판단했습니다.
- 이를 위해, 인재의 프로필 정보에 임의로 선택된 가짜 키워드(fake keywords) 3개를 추가하고 모델이 이를 다른 키워드들과 얼마나 잘 구분해내는지를 평가해보았습니다.
예제 9. 예제 7의 데이터에 fake keywords 입력한 Fake 데이터 예
- 모델은 직무 키워드 일 경우 1, 아닐 경우 0을 출력하기 때문에, 1 – 모델 출력 으로 가짜 키워드 확률 점수를 계산하였습니다. 추천 모델 평가에 활용되는 mAP(mean Average Precision)를 이용해 가짜 키워드들의 이 점수가 다른 키워드들의 점수보다 얼마나 더 높은지를 측정하였습니다.
- 그림 5의 파란색 실선은 학습이 진행되는 동안 평가데이터의 mAP 결과입니다. epoch 0은 학습되기 전 random 하게 초기화된 모델의 결과 입니다. 즉, 가짜 키워드일 확률을 임의로 부여한 결과입니다.
- Snokel을 통해 만들어진 레이블로 모델을 학습하면서 점점 더 나은 분류 성능을 보이는것을 알 수 있습니다. 이 결과를 통해 Snorkel로 만들어진 레이블이 효과가 있었다고 판단하였습니다.
- LF들의 voting을 통해 생성된 레이블로도 한번 학습을 진행해보았습니다(그림 5의 주황색 점선). 이를 통해 단순한 voting보다 생성모델을 통해 만들어진 레이블이 좀더 나은 분류기를 학습 시킬 수 있었음을 알 수 있었습니다.
그림 5. 모델 평가 mAP 그래프(x 축: epoch, y 축: mAP)
5. 사용 소감
- 실제 산업현장에서 머신러닝을 적용할때 가장 큰 난관은 학습 데이터 확보라고 생각합니다. 유저가 입력한 데이터나 행동 이력을 기록한 로그 데이터가 많이 있지만 이를 학습데이터로 사용하기 어려운 경우가 있습니다. Snorkel은 이러한 데이터들과 도메인 전문가의 지식을 결합하기 위한 효과적인 도구라고 생각합니다.
- 하지만, 좋은 결과를 얻기 쉽지 않은 것 같습니다. 가장 어려운 점은 labeling function 정의하기 였는데요. 처음에는 많은 LF 아이디어가 나올 줄 알았는데, 아무리 쥐어짜도 10개 이상 만들기 어려웠습니다. 그마저도 적용해보니 결과가 좋지 않았습니다. 그래서 다시 LF를 고민하고 만드는 과정을 반복했습니다. 지금 구성된 LF에도 많은 개선점이 있습니다.
- 가능하다면, 만들어진 모델을 실제로 활용하면서 꾸준히 LF를 업데이트하는 방법으로 활용해도 좋을 것 같습니다. 해결하고자 하는 문제의 규모와 활용 방식, 중요도에 따라 Snorkel을 사용할지 다른 방법을 사용할지 잘 고민해봐야 할 것입니다.
- 하지만 개인적으론 Snorkel의 전망이 좋다고 생각합니다. 앞으로 Snorkel을 활용한 다양한 결과를 공유드릴 수 있도록 노력하겠습니다!
Reference
- https://www.snorkel.org/ (Snorkel 공식 홈페이지)
- Understanding Snorkel(Snorkel 소개 블로그, 2021)
- 게임의 부정 사용자를 탐지하는 방법, Snorkel을 활용해 라벨 보정하기 (NC 소프트 기술 블로그, 2021)
- Keyword Extraction Methods — The Overview(키워드 추출 관련 연구 소개, 2019)