이번 오사카-도쿄 여행에서 얻은 최고의 꿀팁은 바로 ⭐️애플페이로 스이카 교통카드를 사용할 수 있다⭐️는 것이었다!
스이카 앱 첫 화면
하지만 애플페이에 스이카 교통카드를 등록하려면스이카 앱 다운로드 > 설정 > 카드를 애플페이에 등록 > 교통카드 충전의 과정을 거쳐야 하는데, 스이카 앱은 일본어만 지원이 되어서... 약간의 어려움을 겪었다. 그래서 누구든 일본어를 몰라도 이대로 따라 하기만 하면 한 번에 설정을 마칠 수 있도록 설정 과정을 기록해 보았다.
(앱이 간간이 업데이트되는 것 같은데, 이 포스트는 2025년 10월의 앱 버전 기준.)
우선 어플이 처음 시작되면 아래와 같은 화면이 뜬다. 그럼 첫 번째 버튼을 탭!
스이카 카드 신규 발급을 시작하게 된다.
그리고 진입한 화면에서는 스와이프할 수 있는 세 개의 선택지가 나오게 되는데,
우리는 가장 마지막 선택지로 이동하여 아래의 초록색 버튼을 탭하자.
"무기명" 옵션을 선택한 것인데,
이 편이 회원가입을 하고 인적 정보를 등록하여 교통카드를 발급받는 것보다 훨씬 간편하다.
그럼 "무기명" 옵션은 핸드폰 분실 시에 복구가 어렵다는 주의사항이 뜬다.
어차피 폰을 잃어버렸다면 스이카에 충전해 둔 만원쯤은 문제 축에도 끼지 못할 테니
오른쪽 위에 있는 "다음" 버튼을 탭해서 넘어가도록 하자.
그럼 동의해야 할 약관이 두 개가 나온다.
우리나라의 은행 앱처럼 각 약관을 끝까지 스크롤해야 "동의" 버튼이 등장하므로
각 약관을 탭하고, 끝까지 스크롤해보자.
우선 첫 번째 약관을 탭하면,
이와 같은 약관이 나온다.
이 페이지를 쭈우우욱 스크롤한 뒤, 오른쪽 위의 "약관 동의"를 탭하자.
그리고 그다음 약관도 동일한 방법으로 "약관 동의"를 해준다.
이제 거의 다 왔다!
신규 교통카드를 발급하면 금액을 충전해야 한다는 창이 뜬다.
아래에 표시된 "금액 고르기" 버튼을 클릭하고, 충전 금액을 선택하자.
나는 먼저 1000엔(한화로 약 만 원)을 충전했다.
그럼, 이 금액을 애플페이에 기존 등록되어 있던 카드로 바로 충전할 수 있는 버튼이 뜬다!
이 버튼을 탭!
자동으로 애플페이로 창이 넘어가면서, 충전이 완료된다.
마지막으로,
발급된 스이카를 어떤 기기의 애플페이에 등록할지를 선택한다!
나는 워치를 사용하지 않았으므로 iPhone 옵션을 선택했다.
목록에 뜬 기기 중 하나에만 등록이 가능하니 잘 보고 선택하도록 하자.
이제 애플페이로 스이카를 사용할 수 있다! 🎉
애플페이를 활성화(잠금 버튼 두 번 누르기)하고 개찰구에 가져다 대면 사용 가능하다.
나중에 잔액이 부족해도 애플페이 상에서 바로 잔액을 충전할 수도 있다는 점이 유용했고,
현금 결제만 받는 식당 중 스이카 결제가 가능한 곳이 있기도 해서 여행 동안 정말 유용하게 사용했다.
KeyError: 'unexpected key "module.**" in state_dict'
모델 state_dict의 key 값들을 출력해보면, "module."으로 시작하는 key는 존재하지 않는 것을 확인할 수 있다.
그런데 왜 이런 key값으로 저장되었을까?
이유는 간단하다. 처음 모델의 state_dict를 저장할 때, 모델이 nn.DataParallel로 선언이 되어있을 경우 이런 문제가 발생한다. 따라서 문제를 해결하는 방법은 두 가지가 있다.
1. 로딩하는 모델을 nn.DataParallel 로 변환하기
state_dict를 저장할 때와 똑같이 모델을 nn.DataParallel로 변환한 뒤, state_dict 를 로딩한다.
GPU_IDs = [0,1,2,3] # DataParallel에 사용할 GPU ID들을 입력
model = torch.nn.DataParallel(model, device_ids = GPU_IDs) # DataParallel 선언
model.load_state_dict(state_dict) # state_dict 로딩
2. state_dict의 key값을 "module."을 없앤 값으로 재설정하기
Key가 일치하지 않기 때문에 발생하는 문제이므로 state_dict의 key를 직접 해당 모델의 key와 일치하도록 바꾸는 방법도 있다. 예를 들면, 기존의 key값이 "module.encoder.fc.weight"였다면, 이를 "encoder.fc.weight"로 바꿔주면 문제가 해결된다.
from collections import OrderedDict
new_state_dict = OrderedDict()
for k, v in state_dict.items():
name = k[7:] # "module."을 지우기
new_state_dict[name] = v
model.load_state_dict(new_state_dict) # "module."이 지워진 새로운 state_dict 로딩
요즘 Contrastive learning이 재미있어 보여서 언어 도메인에 적용할 수 있는 방법을 생각해보던 중, 문득 "문장 임베딩의 공정성 향상에 사용할 수 있지 않을까?"라는 생각이 들었다. "Contrastive", "Fairness", "Debiasing" 같은 키워드로 몇 번 검색해보니 이 논문이 나와서 반갑기도 했고 또 아쉽기도 했다... 여튼 올해 5월 ICLR에 발표된 논문이니 너무 늦은 건 아니라고 위로하며 한 번 논문을 읽어보자.
# TL;DR
문장 임베딩의 사회적 편향성을 뉴럴넷 기반으로 제거한 첫 논문
Pretrained 인코더에서 나온 문장 임베딩의 편향을 개선하는 Fair Filter (FairFil) network를 제안
FairFil은 Contrastive learning 프레임워크로 학습함
실제 데이터셋에서 downstream task들의 성능을 어느 정도 유지하면서 편향을 개선함
인코더를 다시 학습시킬 필요가 없는 사후검정(post hoc) 방법을 제안함
# Introduction
문제 제기: The Problem
기존 Pretrained 문장 인코더에서 나온 문장 임베딩에서의 사회적 편향이 지속적으로 관측됨
지금까지의 편향 완화 방법론들은 단어 단위(word-level)로만 연구되어왔음
단어 단위의 편향을 완화한다고 해서 문장의 사회적 편향이 완화되는 것을 보장할 수 없음
연구 의의: Contributions
사전학습된 인코더(pretrained encoder)에서 나온 문장 임베딩에 대한 첫 신경망(Neural net) 기반 편향성 제거 방법론 제시
FairFil은 전체 사전학습된 인코더(pretrained encoder)를 다시 학습시킬 필요가 없는 사후 검정(post hoc) 방법
논문 개요: Outline
기존 학습 데이터에서, 그 의미는 동일하나 포함된 사회적 편향의 방향성이 달라지도록 데이터를 변형(data augmentation) - 이 때 기존의 데이터는 positive sample, 변형된 데이터는 negative sample이라 칭함
편향 제거 정규화(Debiasing regularizer)를 이용하여 민감한 단어로부터 생긴 편향을 제거함
FairFil은 임베딩의 공정성(fairness)과 표현성(representativeness)에 있어서 Sent-Debias보다 좋은 성능을 보임
# Preliminaries
이 단락에서는 논문에서 사용되는 주요한 개념들을 간단하게 설명하고자 한다.
Mutual Information(MI): 상호의존정보
두 변수 사이의 정보량을 의미한다. 하나의 무작위 변수를 통해 다른 하나의 무작위 변수에 대해 얻을 수 있는 정보량을 정량화한 것으로, 아래의 수식으로 표현된다.
상호의존 정보(이하 MI)는 정확한 값을 계산해내기가 어려운 것으로 잘 알려져 있다. 보통 우리는 주어진 변수 x와 y에 대한 정확한 결합 확률분포 $p(x, y)$를 알지 못하는 상황에서 $p(x, y)$의 일부 샘플값만을 갖고 있기 때문이다. 따라서 MI를 추정하는 방법들이 사용된다. 이 논문에서는 InfoNCE와 CLUB, 두 가지의 추정자를 사용한다.
InfoNCE estimator (Oord et al., 2018)
이 추정자는 MI를 최대화할 때 사용된다. 우리에게 한 배치(batch)의 샘플 쌍 ${(x_i, y_i)}_{i=1}^N$ 주어졌을 때, InfoNCE 값은 아래의 수식과 같이 계산된다.
CLUB, Contrastive Log-ratio Upper Bound (Cheng et al., 2020)
이 추정자는 이름에서도 알 수 있듯이 MI의 상한값을 계산한다. 따라서 이 CLUB은 MI를 최소화하는 데 사용된다. MI의 상한값은 변수 $y$의 $x$에 대한 조건부 확률을 이용하여 계산한다. 하지만 우리에게는 한 배치(batch)의 샘플 쌍 ${(x_i, y_i)}_{i=1}^N$만이 주어지기 때문에 정확한 조건부 확률을 구할 수 없다. 따라서 우리는 변분추론(Variational approximation) 값인 $q_\theta(y|x)$를 이용한다.
여기서 \(\theta\)는 모델의 파라미터를 의미하며, 따라서 $q_\theta(y|x)$ 또한 모델을 통해 학습되는 함수다.
이 논문에서는 사전 학습된 인코더에서 나온 문장 임베딩 $z$의 편향을 제거하는 필터 $f(\cdot)$을 학습하고자 한다. 곧, 필터의 출력인 $d=f(z)$는 기존의 문장 임베딩 $z$의 의미는 그대로 갖고 있지만 편향이 제거된 문장 임베딩이다.
방법 개요: Method Overview
전체 방법을 Multi-view contrastive learning framework이라 부르며, 그 과정은 아래와 같다.
기존 문장 $x$의 의미는 보존하지만 다른 잠재적 편향 방향성을 갖는 새로운 문장 \(x'\)을 만든다.
InfoNCE를 손실 함수로 사용하여 기존 문장 임베딩\(z=f(x)\)과 새로운 문장 임베딩\(z'=f(x')\) 간의 MI를 최대화한다.
편향 제거 정규화(Debiasing regularizer)를 이용하여 필터링된 임베딩 \(d\)와 문장\(x\) 안의 민감한 속성을 지닌 단어 간의 MI를 최소화한다.
1. Data Augmentations with Sensitive Attributes
우선 자주 사용되는 단어를 정리하자.
사회적으로 민감한 주제(Social sensitive topic)란 편향이 발생할 수 있는 주제를 의미하며, "성별", "인종", 혹은 "종교" 등을 의미한다.
사회적으로 민감한 주제에 대한 잠재적 편향 방향(Potential bias direction)이란 해당 주제에 대해 발생할 수 있는 편향의 방향성을 의미한다. 예를 들어 주제 "성별"에 대한 잠재적 편향 방향에는 "여성"과 "남성"이 포함될 수 있다.
논문에서는 사회적으로 민감한 주제를 "성별"로 정하고, 그에 대한 잠재적 편향 방향인 "여성"과 "남성"에 대해서 편향성을 없애는 실험을 진행했다. 하지만 이는 "성별"에 대한 데이터셋을 사용하였기 때문이고, 데이터셋만 있다면 다른 어떤 주제로도 이 방법을 적용할 수 있다.
데이터셋의 문장을 다른 잠재적 편향 방향을 가진 문장으로 바꾸는 Augmentation 과정은 아래와 같이 직관적이면서 간단하다. 문장에서 잠재적 편향을 포함하고 있는 단어(He, his)를 다른 잠재적 편향 방향의 대체 단어(She, her)로 변경한다.
이렇게 각자 잠재적 편향 방향이 상반되는 문장의 쌍\((x,x')\)이 만들어지면, 이 쌍을 이용하여 문장 임베딩의 해당 민감 주제 편향을 없애는 필터를 학습시킨다.
2. Contrastive Learning Framework
Contrastive Learning Framework는 아래 그림의 빨간 박스 부분을 의미한다.
한 배치(batch)의 샘플 \(\{(d_i, d_i')\}_{i=1}^N\)이 주어졌을 때, \(\mathcal{I}_{NCE}\) 값을 최대화하도록 \(f(\cdot)\)이 학습된다. 또한 그 과정에서 \(\mathcal{I}_{NCE}\)를 계산하는 과정에서 필요한 \(g(d, d')\) 함수 또한 학습된다.
여기서 Positive pair인 \(d_i, d_i'\)은 같은 문장 \(x_i\)로부터 나온 편향 제거 임베딩들의 쌍을 의미한다. Negative pair \((d_i, d_j')\)은 문장 \(x_i\)에 대한 편향 제거 임베딩 \(d_i\)와 배치 내 다른 문장 \(x_j\)에서 augmentation 된 문장 \(x_j'\)의 편향 제거 임베딩\(d_j'\)의 쌍을 의미한다. 따라서 \(\mathcal{I}_{NCE}\)를 최대화하기 위해서는, 문장 \(x_i\)의 편향 제거 임베딩 \(d_i\)를 기준으로 해당 문장에서 augmentation된 편향 제거 임베딩 \(d_i'\)은 최대한 비슷해지도록, 배치 내 다른 문장 \(x_j\)에서 aumentation된 편향 제거 임베딩 \(d_j'\)은 최대한 달라지도록 학습이 된다. 직관적으로 이해하자면, 다른 편향 방향을 갖고 있는 문장 \(x_i\)와 \(x_i'\)이 최대한 비슷한 임베딩이 되게 학습함으로써 임베딩 \(d_i\)와 \(d_i'\)이 편향 방향과 상관없는 문장의 의미만을 보존하도록 필터 \(f(\cdot)\)을 학습시킨다.
3. Debiasing Regularizer
\(\mathcal{I}_{NCE}\)를 최대화함으로써 문장 임베딩이 편향 방향에 상관 없는 의미만을 보존하도록 학습시켰다면, Debiasing regularizer는 문장과 문장 안의 편향을 내포하는 단어 간의 MI를 최소화하도록 필터 \(f(\cdot)\)을 학습시킨다. 아래 그림의 빨간 박스 안 도식이 해당 과정을 나타낸다.
문장 \(x\)의 단어들 충 \(p\) 번째 단어인 \(w^p\)가 편향을 내포하는 단어라고 할 때, \(w^p\)와 \(d\) 간의 MI의 상한값인 \(\mathcal{I}_{CLUB}\)를 최소화하는 정규화 방법이다. 이로써 \(w^p\)와 \(d\) 간의 MI를 최소화하는 효과를 얻을 수 있으며, 곧 필터링된 문장 임베딩 \(d\)가 편향을 내포한 단어의 정보를 최대한 포함하지 않도록 더욱 필터링을 강화하는 효과를 얻을 수 있다.
여기서 \(q_\theta\)는 조건부 확률의 변분근사이며, 변수인 \(\theta\)가 모델을 통해 학습된다.
# Experiments
모델을 통해 학습되는 변수들은 아래와 같이 정리할 수 있다.
실험에 사용한 데이터셋과 설정들은 아래와 같다.
사전 학습된 인코더: BERT
실험에 사용한 과제(Downstream tasks): SST-2, CoLA, QNLI
FairFil 학습 데이터: WikiText-2, SST, Reddit, MELD, POM에서 가져온 183,060개의 문장을 "성별"에 대하여 미리 정의해둔 사회적으로 민감한 단어들을 기준으로 변형(Augmentation)
편향 측정 방법: Bias Evaluation Metric
기존의 단어 임베딩의 편향을 측정하는 방법인 WEAT(Word Embedding Association Test)를 확장한 SEAT(Sentence Encoder Association Test)를 사용했다. 이 측정 방법들에 대해서는 다른 포스트에서 좀 더 자세히 다루도록 하겠다.
실험 결과
실험 결과의 평가는 두 가지 수준에서 이루어졌다. 우선 필터링된 문장 임베딩에서도 기존 문장 임베딩의 정보를 모두 담고 있는지를 확인하기 위한 하위 과제(Downstream tasks)에서의 성능을 평가한다. 또 필터링된 문장 임베딩의 편향성이 얼마나 제거되었는지를 평가하기 위한 방법으로 SEAT를 사용한다.
Table 2는 하위 과제(Downsteam Tasks)에서의 성능을, Table 4는 문장 임베딩의 사회적 편향 정도를 측정한 값이다.
Sent-D는 딥러닝을 사용하지 않고 문장 임베딩에서 편향을 제거하는 방법을 제안한 논문인 'Towards debiasing sentence representations, Liang et al. 2020'에서 나온 Sent-Debias를 의미한다. FairF-는 Debiasing regularizer를 사용하지 않은 모델을, FairF는 Debiasing regularizer까지 사용한 모델을 의미한다.
결과를 보면 FairFil은 문장 임베딩의 편향을 제거하는 기존 방법인 Sent-Debias와 대체적으로 유사한 성능을 유지하면서도, 편향성을 훨씬 제거한 것을 확인할 수 있다. 다만 Debiasing regularizer는 편향성을 더 제거하는 반면에 하위 과제의 성능을 떨어뜨리는 Tradeoff가 있음을 확인할 수 있다.
# Conclusion
대용량 텍스트 인코더에 대한 새로운 편향 제거 방법론을 제시
Multi-view Contrastive learning framework를 고안
Debiasing regularizer를 고안
편향을 효과적으로 제거하면서도 문장의 의미를 보존
이미 생성된 문장 임베딩을 필터링하는 방식의 사후 검정(post hoc) 방식으로, 인코더를 학습하는 데 사용했던 데이터셋이나 인코더를 다시 학습할 필요가 없음
다시 짧게 내 소개와 지원한 인턴십에 대해서 말하자면, 나는 GIST 학부 전기전자컴퓨터공학과를 졸업 후, 동일 대학원 AI 대학원 석박통합과정에 진학하여 2학기를 마친 석사생이다. 네이버 검색 콜로키움에 참가한 후, 채용 제안을 받아 네이버 Search CIC 연구/개발 체험형 인턴십에 지원, 최종 합격했다. 채용 전형은 '코딩테스트 > 1차 면접 > 최종 합격' 순으로 굉장히 간단했다.
합격의 기쁨도 잠시, "가장 빠른 입사 가능일"이 언제인지를 교수님께 허락받아야 한다는 현실이 떠올랐다. '일정이 조율되지 않아 인턴십을 포기하게 될 수도 있나?' 하는 불안한 마음으로 교수님께 합격 소식을 알리고 그다음 날 줌으로 미팅을 했다.
우선 나는 면접 전부터 김칫국을 시원하게 들이키고 있었기 때문에, 코딩 테스트를 합격하자마자 인턴십 기간이 어느 정도까지 협의가 가능한지를 미리 채용담당자님께 문의드렸다. 진행 중이던 연구를 AAAI에 게재한다면 8월 말~9월 초에는 인턴을 시작하기에 무리가 없겠다 싶어서, 이때까지 인턴십 시작일을 미뤄도 되냐고 여쭤봤고, 그 정도는 협의가 가능하다는 답변을 받았다.
그래서 9월 초쯤 인턴십을 시작하면 좋겠다고 혼자 생각하고 있었는데... 연구가 그만큼 진척이 되지 않았다. 교수님께서도 줌 미팅으로 AAAI는 너무 일정이 빡빡하니 10월 초가 마감일인 ICLR 개제를 목표로 하자고 말씀하셨다. 맞는 말이지만... 인턴 합격 후기 글을 찾아봤을 때 보통 합격 소식을 받고 일주일 안에 인턴십을 시작하는 것 같았고, 이미 9월 초로 시작일을 미룰 수 있는지 문의했던 상황에서 또 10월 이후로 시작일을 미루기 난감했다. 하지만 뭐, 어쩌겠는가. 다 내 잘못이지...
구구절절 인턴하게 해주세요...
왜 일정을 미뤄야 하는지도 굳이 굳이 메일에 썼다.
그리고 3일 정도 마음을 졸이며 회신을 기다렸다. 결과는...
10월 말! 중간고사/생일 주에 인턴십을 시작하는 진기명기
부서와 협의가 되었다...ㅠㅠ 심지어 일정도 넉넉해서 마음 편하게 하던 연구를 마저 마무리할 수 있을 것 같다. 다른 합격 후기를 찾아봤더니 시작일쯤에 사수에게 따로 연락이 오는 것 같은데, 벌써부터 설렌다. 여러모로 예상치 못하게 가을 학기 중에 네이버 인턴십을 가게 되었다. 교수님께서 학기 중 수업을 들으며 인턴십 할 수 있다는 것도 알아봐 주셔서 수업도 하나쯤 같이 들을 생각이다. 복잡한 문제는 다 해결됐다! 인턴십 기간을 확정하고, 입사자 정보 입력 링크를 받아서 사내 메일 주소도 신청하고, 마음을 이미 네이버에 가 있는데 정신줄 제대로 잡고 하던 연구부터 잘 마무리해야겠다. 11월에는 인턴십 후기도 글로 남겨야지!
다시 짧게 내 소개와 지원한 인턴십에 대해서 말하자면, 나는 GIST 학부 전기전자컴퓨터공학과를 졸업 후, 동일 대학원 AI 대학원 석박통합과정에 진학하여 2학기를 마친 석사생이다. 네이버 검색 콜로키움에 참가한 후, 채용 제안을 받아 네이버 Search CIC 연구/개발 체험형 인턴십에 지원하게 되었다. 채용 전형은 '코딩테스트 > 1차 면접 > 최종 합격' 순으로 굉장히 간단했다. 이번 글에서 다룰 1차 면접은 일대일 기술 면접으로, 한 세션에 각 1시간씩, 총 두 세션(2시간)이었다.
이제 면접만 남았다!
1. 면접 준비
대체 뭘 물어볼지... 감을 못잡고 헤맨 기록. 이 섹션은 그냥 넘어가도 좋다.
솔직히 말하기 좀 부끄럽지만 코딩테스트 합격 메일을 받은 시점부터 마음만은 이미 네이버로 출근하고 있었다. 사실 코딩 테스트를 볼 때까지 교수님께도 인턴십에 지원했다는 말씀을 안 드렸는데... 마음만은 이미 합격이었기에, 교수님께도 '사실 이런 인턴십 채용 제안을 받아 지원을 했고, 이제 최종 면접을 앞두고 있습니다.'라고 말씀드렸다. 교수님께서는 인턴십에 대한 조언을 이것저것 해주셨고, '저 아직 면접 합격한 것도 아닌데요, 뭐...'라 마음에도 없는 말로 대답했다. 그런데 막상 면접일이 다가오니 조금씩 불안해지다 못해, 면접에서 떨어지겠구나 싶었다. 두 시간은 굉장히 긴 시간이고, 내가 과연 연구에 대해 두 시간이나 떠들 지식이 있는지 걱정이 됐다. 예상 면접 질문이라도 뽑아보자는 마음으로 네이버 NLP 연구개발 면접 후기들을 찾아봤지만, 딱 두 게시물밖에 없었고, 그마저도 하나는 비공개 글이었다. NLP 직무 면접에 대한 감을 잡는데 도움이 되었던 게시물 링크는 옆에 첨부한다.(siAhn님의 블로그 링크, 결론부터 말하자면, 나도 siAhn님의 글에서 언급된 질문들과 굉장히 유사한 성격의 질문을 많이 받았다!)
네이버 NLP 면접 후기가 없어서, 그냥 네이버 개발 면접 후기까지 모두 다 찾아봤다. 그런데 대부분의 개발 면접은 CS 전공 지식 질문과 프로젝트 경험 질문이 주를 이뤘고, 코딩 테스트에서도 개발직과 연구직 채용 평가 기준이 다름을 느꼈기 때문에 참고하지 않고 다르게 면접 준비를 했다. 크게 세 항목으로 나눠서 준비했는데, 지원서에 쓴 프로젝트 내용을 복기한 것 빼고는 아무짝에도 쓸모없었으니 굳이 읽지 않고 넘어가는 것도 좋다.
통상적인 면접이라면 물어볼만한 질문들을 생각해봤다. 1분 자기소개, 팀원과의 문제 발생 시 갈등 관리, 프로젝트 경험 자유 소개 등 여러 질문들을 생각해봤지만, 인성 면접이라면 딱히 준비하지 않고 자연스럽게 대답하는 게 가장 좋을 것 같아서 프로젝트 관련 질문만 준비하는 게 낫겠다 싶었다. 지원서에 썼던 프로젝트를 진행했던 과정을 머릿속으로 복기하는 정도로만 준비했다. 내가 지원서에 쓴 프로젝트는 모두 개인 프로젝트라 당연히 프로젝트의 디테일까지 잘 알고 있었기 때문에 딱히 준비에 많은 시간을 들이지는 않았다.
2. NAVER Search팀 연구 내용 조사
당연히 현재 검색팀에서 연구하고 있는 내용에 대해 잘 아는 사람을 선호할 것이라는 생각에 최신 연구 내용들을 조사해보려 했다. 그런데 따로 서치팀 웹페이지가 있는 것도 아니고 출판 목록도 어느 팀에서 나온 건지 명확히 구분이 되어있지 않아서 감이 잡히지 않았다. 당연히 언어를 메인으로 다루는 검색팀이니까 NLP 연구개발자가 필요하겠지 싶지만, 딱 그뿐. 사실상 아무것도 모른 채 면접에 들어갔다. (그래서 면접에서 이 부분을 여쭤봤고 대답해주신 내용을 토대로 내가 내린 결론은, 그건 지원자가 어떻게 어필한다고 되는 게 아니라 면접자가 판단하는 게 더 효율적이다. NLP 분야의 대부분에 걸친 연구개발이 팀 내에서 진행되고 있기 때문에...) 다시 면접을 준비한다면 굳이 찾아보지는 않겠지만, 궁금하실 분들을 위해 내가 참고했던 웹페이지들의 링크를 첨부한다.(NAVER Tech Careers, 2021 검색 콜로키움 발표 영상들) 개인적으로는 그냥 찾아보는 것도 재미있었다. 마음은 이미 검색팀의 일원이었기 때문에...
3. NLP 최신 관심 논문 뽀개기
내가 얼마나 NLP 동향을 잘 읽고 있는지, NLP 연구에 내 나름의 시각을 갖고 있는지를 어필하기 위해 최신 논문 중 내가 재미있게 읽었던 논문 몇 편을 다시 읽었다. 결론적으로는 이것도 쓸모없었지만, 그냥 재미있었다.
요약하자면, 면접 준비는 이렇게 안 해도 된다. 굳이 준비 안 했어도 NLP 연구를 해봤다면 (혹은 해보려고 시도라도 했다면) 쉽게 대답할 수 있을만한 질문들이 나왔다. 그럼 이제 본격적으로 면접 분위기와 내용에 대해서 최대한 두루뭉술하고 자세하게 써보도록 하겠다.
2. 1차(라고 쓰고 최종이라 읽는) 면접
NLP의 기본기를 탄탄히. 개발자 면접의 관례인 CS 전공 질문은 없었다.
두루뭉술하게 쓸 수밖에 없는 게, 어디까지 자세하게 써도 될지 모르겠다. 당연히 면접에서 질문받은 내용을 그대로 올릴 수는 없고, 그렇다고 뭐 대충 잘 본 것 같아요~라고 쓰면 재수 없기 때문에, 그리고 나중에 떨어지면 쪽팔려서 그렇게 쓰면 안 된다.
우선 면접의 형식부터 다시 설명하자면, 일대일 기술면접으로, 각 세션 당 1시간씩 두 세션으로 면접이 구성되어있다. 두 세션에는 다른 면접관님이 들어오셨고, 서로 다른 팀에 소속되어 계신 분이었던 것 같다. 그렇다 보니 면접 구성이 완전히 다르면서도, 기술적인 부분에서는 겹치는 질문이 몇 개 있었다. 각 세션 별로 어떤 분위기에서 어떤 부분을 중점적으로 질문하셨는지를 적어보려 한다.
첫 번째 세션을 시작했을 때는 정말 심장이 터질 것 같았다. 생각해보니 인생 첫 번째 기업 면접이었으니 당연... 그래도 면접관님께서 긴장을 풀어주시려 해 주셔서 이후에는 편한 분위기 속에 면접을 볼 수 있었다. (면접관도 면접을 볼 땐 긴장이 된다, 그런 말씀을 해주셔서 같이 웃으며 긴장을 어느 정도 풀 수 있었다.) 면접은 질문을 아는지 모르는지 시험해보자는 느낌보다, 내가 정확히 어떤 분야를 얼마나 알고 있는지를 대화를 통해 파악하려 한다는 느낌이었다. 그리고 면접관님께서도 모르는 질문을 받으면 편하게 모른다고 이야기하고 다른 토픽으로 넘어가도 된다고 이야기해주시기도 했고, 정말 그렇게 면접이 진행되었다. 그런데 질문받은 내용은 정말 NLP를 한다면 모를 수 없는 Attention, Transformer 등 기본 지식에 가까운 질문들이었고, 큰 문제없이 대답할 수 있었다. 그리고 내가 대답 중 빼놓고 설명한 부분이 있었는데, 이후 설명이 하나가 빠졌는데 뭔지 알겠냐고 추가 질문을 받았고, 답변을 보충할 수 있었다.
대부분의 면접 시간은 NLP 분야 질의로 이루어졌다. 기술 질문 외에는, 문학/심리학 부전공을 선택한 이유를 면접 시작 전 짧게 물어보셨고, Linux 개발 환경 친숙도나 awk 언어 사용법을 아는지 등 실무적인 스킬에 대해서도 면접 후반부에 짧게 물어보셨다. 검색 콜로키움 내용을 같이 종합해서 생각해보면 GPT 같은 대용량 언어 모델을 검색에 적용하려는 시도가 이뤄지고 있는 것 같고, 이에 적합한 능력을 가졌는지를 평가하려고 하신 것 같았다.
두 번째 세션은 10분 정도의 쉬는 시간 이후 진행되었다. 앞 세션과는 다르게 간단한 자기소개를 요청받아서, 정말 간단하게 소개했다. GIST AI 대학원을 다니고 있고, 넓게는 NLP 분야의 공정성에, 좁게는 문장의 Representation 개선에 관심이 있다고 말했던 것 같다. (아... 지원 동기를 말 안 했네!) 그리고는 지원서에 작성했던 세 개의 프로젝트에 대해서 간략하게 소개해달라고 하셨고, 지원서에 쓴 내용을 거의 그대로 대답했다. 그리고 프로젝트에서 사용한 모델을 정확히 어떻게 사용했는지, 해당 모델들을 잘 이해하고 있는지에 대해서 물어보셨다. 이 부분에서 첫 세션과 겹치는 질문들이 좀 있었다! 내가 프로젝트에 거의 BERT 모델을 사용했어서... 그리고 대답을 못했던 질문도 있었는데, 정말 처음 들어보는 단어였다. 왜지...? 뭐였는지 기억도 안 난다. 끝나기 전에 여쭤볼걸... 그리고 이어진 질문에서는 내가 지원서에 첨부했던 CV에 대해서도 질문을 받았고, 응시했던 코딩 테스트 이야기도 나왔다! 아마도 면접관님들이 내 지원서와 코딩 테스트 결과 및 코드까지 한 번에 확인하며 면접을 보고 계신 것 같았다. (코딩 테스트를 잘 봤다고 이야기해주셔서 조금 안심했다...)
첫 번째 세션과는 다르게 현재 NLP에서 사용되는 한 모델에 대한 내 견해를 물어보셨다. 학부 졸업 논문 내용이나, 면접 질문에 응답할 때 기존 모델들을 삐딱하게 보는 시선을 느끼신 것 같았다... 그래서 내가 생각하는 문제들과 시도해볼 만한 해결방안을 함께 대답했다. 또 첫 번째 세션과 마찬가지로 실무적인 스킬의 정도도 좀 더 자세히 물어보셨고, 내게 질문할 것이 있는지를 물어보시고 면접이 종료되었다.
3. 후기 및 소감
면접 분위기는 정말 좋았고, 너무 즐거웠다. 내가 아는 것을 설명하는 게 이렇게 재미있는 일이구나 다시금 느끼기도 했고, 또 이렇게 새로운 연구자분들과 소통하면서 연구할 수 있는 기회를 놓치고 싶지 않다는 욕심도 들었다. 하지만 이미 면접은 끝났고, 결과를 기다리는 수밖에. 내가 아는 부분을 최대한 끌어내 주는 면접관분들을 만나서 면접에 큰 후회는 남지 않는다. 결과는 1~2주 후에 나온다고 했다. 합격이면 지금은 임시 저장함에 있는 이 글을 바로 올리고, 불합격이면... 불합격이라도 올려야지, 뭐.
+결과는 합격!
메일 제목이 아예 '합격을 축하드립니다!'라고 와서 알림 뜨자마자 소리질렀다.
그런데 이미 랩에서 진행되고 있는 연구 때문에 10월은 되어야 시작할 수 있을 것 같은데... 그럼 휴학도 해야 하고... 잘 조율해봐야 할 것 같다. 잘 협의가 되어서 인턴십을 진행하게 된다면 인턴십 후기 글도 써야지!
현재 나는 GIST 학부 전기전자컴퓨터공학과를 졸업 후, 동일 대학원 AI 대학원 석박통합과정에 진학하여 2학기를 마친 석사생이다. 이번에 예상치 못한 기회로 네이버 Search CIC 연구/개발 체험형 인턴십에 지원하게 되었다. 지원 과정에서 네이버 NLP 직군의 서류, 코딩테스트, 면접 준비에 관련해 정말 열심히 찾아봤는데 정보가 거의 없었고, 나중에 나와 같은 고생을 하는 사람에게 조금이나마 도움이 되고자 이 시리즈를 적게 되었다. 우선 첫 번째 글에서는 어떤 계기로 네이버 인턴에 지원하게 되었는지, 또 지원 서류와 코딩 테스트는 어떻게 준비했고, 어떻게 진행되었는지에 대해서 다뤄보도록 하겠다.
1. 인턴십에 지원을 하게된 계기
콜로키움 참가 후, 관련 직무가 있으니 지원을 해보라는 채용 제안 메일이 왔다.
확인도 못하고 놓쳤을 뻔한 채용 제안
2021년 5월 초, 네이버 검색 콜로키움(2021 NAVER Search Colloquium, Global Re:Search)에 참가했다. 코로나때문에 행사는 온라인으로 진행이 되었고, 타임테이블을 참고하며 본인이 원하는 세션을 들을 수 있었던 것으로 기억이 난다. 이후에도 네이버에서 주최한 다양한 행사들을 참가하며 대학원을 다니고 있었는데, 6월 초, 모르는 번호로 전화가 왔다. 네이버 인재영입팁(?)이라고 소개를 하시고는 메일을 받았냐고 물으시기에, '네이버에서 어떻게 나를 알고 연락을?'이라고 생각하며 무슨 메일을 말씀하시는 건지 되물었다. 알고 봤더니 검색 콜로키움 등록에 썼던 네이버 메일 주소로 채용 제안이 왔었고, 나는 네이버 메일을 잘 사용하지 않아서 확인을 못했던 것이었다. '메일을 보시고 관심이 있으시면 당일 저녁까지 회신을 달라'라고 하시기에 메일함을 확인했더니, 메일에 써져있던 회신 기한은 이미 지나 있었다;; 한 번 더 전화해서 확인을 해주셨기에 망정이지 나중에 알았으면 땅을 치고 후회할 뻔했다.
제안은 내년 2월 이전 졸업예정자라면 채용 지원을, 그 이후 졸업 예정이라면 체험형 인턴 지원 의사를 물어보는 내용이었다. 여튼 앞에서도 소개했지만 나는 이제 막 2학기를 끝낸 석박통합생으로, (준비야 열심히 하는 중이지만...) 아직 출판한 논문이 없는 상태다. 때문에 합격을 크게 기대하지는 않았지만, GIST AI 대학원의 박사 졸업 요건에는 인턴십 2개월이 포함되어있어서, 이번 기회에 네이버의 인턴 채용 프로세스를 경험해보면 좋겠다는 취지로 지원해보게 되었다. 어렵지 않게 결정을 한 후, 체험형 인턴에 지원 의사가 있다고 회신을 했고 자세한 전형 내용과 일정은 이후에 안내받았다.
2. 채용 전형 안내 및 지원 서류 작성
채용 제안을 받았지만 지원은 동일하게 NAVER Career 사이트에서 이루어졌다. 채용 전형은 '온라인 코딩테스트 > 1차 면접 > 최종합격'순으로, 간단하다.
자세한 인턴십 전형에 관한 안내 메일을 받고, 지원서를 작성하고 코딩테스트에 응시했다.
채용 제안 메일에 회신을 한 후, 약 10일 후에 지원서 작성 및 전형 일정을 안내받았다. 전형은 총 '온라인 코딩테스트 > 1차 면접 > 최종합격' 순으로 굉장히 간단했다. 나는 메일로 해당 직무의 채용을 처음 소개받았기 때문에, 지원 서류도 메일로 제출하게 될 것이라고 막연히 생각하고 있었다. 그런데 채용 전형 안내와 함께 NAVER Career에 지원서를 등록하라는 메일을 회신받았고, 이때 해당 직무의 공개적인 채용 공고가 있었음을 처음 알았다. 사실 이 때 조금 절망했는데, '채용 제안을 받은 사람들 중에서만 경쟁하는 것이 아니라 모든 지원자들과 경쟁해야 하는 거구나'라는 생각이 들어서 혹시나 인턴이 되지 않을까 하는 기대가 더더욱 낮아졌다.
지원 서류의 작성 항목은 1) 지원 동기, 2) 보유한 Skill의 자기 평가 점수, 3) 활동사항/프로젝트 설명, 이렇게 세 항목이었다. 솔직히 말하자면 내용을 작성하는데 많은 시간을 투자하지는 않았다. 지원 동기에는 언어 도메인에서 AI 공정성에 관심이 많다고 썼고, 쓰다 보니 좀 거창해져서 대단한 사명감을 갖고 있는 것처럼 써졌다. (이 내용은 면접에서도 한 번 언급되었다. 굉장한 사명감을 갖고 있는 것 같다고 이야기하셨는데 조금 민망...) 보유한 스킬은 Python, Pytorch, Linux 개발환경을 우선 쓰고, 학부 시절 다뤄봤던 언어들도 추가적으로 좀 더 리스트업 했다. 마지막 항목에는 내가 했던 프로젝트에서 세 가지 정도를 추려서, 각 프로젝트의 동기와 사용 기술, 결과, 그리고 개선방안을 간략하게 썼다. 프로젝트 내용도 세 개 중 두 개가 공정성 AI와 관련된 개인 프로젝트였는데, 이게 관심분야의 일관성으로 보였을지 편협함으로 보였을지는 미지수. 다시 읽어보니 좀 더 균형 있게 서류를 썼으면 좋겠다는 아쉬움이 남는다. 예를 들면, "인공지능의 공정성"은 광범위한 관심사이고, "인공지능에서 문장의 표현(Representation) 개선"은 좀 더 세부적인 관심사라는 식으로. (이런 이야기는 면접 때 추가적으로 더 나눌 수 있긴 했다!)
또 마지막에 첨부자료를 붙일 수 있었는데, 꾸준히 업데이트하고 있었던 CV와 (하나도 정돈되어있지 않은;;) 내 Github 링크를 첨부했다. (CV는 형식상 첨부한다는 생각이었는데, CV 안의 내용도 면접 때 물어보셔서 놀랐음)
면접 준비할 때 한 번 더 읽어보려고 지원서에 작성한 내용을 복붙 해뒀는데 그럴 필요가 없었다. NAVER Career 사이트에서 '내 지원서 조회하기'를 누르면 작성 내용과 첨부파일까지 다시 확인할 수 있다.
3. 코딩 테스트 응시
Codility를 통해 온라인으로 진행되었고 3문제를 190분 동안 풀어야 했으며 난이도는 무난했다. 개발직군 코딩 테스트와는 문제의 결이 조금 다른 편!
전형 일정 안내 메일과 함께 코딜리티(Codility)에서 코딩 테스트를 응시할 수 있는 링크가 왔다. 원하는 시간에 링크로 접속하여 테스트에 응시할 수 있는 방식이었고, 응시 기한도 안내 메일을 받은 날짜로부터 4일 뒤라서 여유로웠다. 190분 동안 3문제를 푸는 형식이라고 함께 공지가 왔고, 나는 주어진 기간 중 마지막 날 코딩 테스트를 봤다. 따로 화면 감독이나 카메라 감독은 없었다. 나는 이번이 겨우 두 번째 코딩 테스트 응시였다. 첫 번째로 응시했던 코딩테스트도 '경험이나 해보자'는 마음으로 아무 준비 없이 신청한 카카오 인턴십 코딩테스트였고, 이마저도 코딜리티가 아닌 프로그래머스에서 진행되었더랬다. 그래서 코딜리티에서 제공하는 코딩테스트 데모를 몇 번 하고, 알고리즘 문제도 흘끗흘끗 보느라 응시를 미루다가 기한이 다 되어서야 코딩 테스트를 치렀다. 결과적으로 알고리즘 문제를 푼 것은 큰 도움이 되지는 않았다.
내가 알기로, 개발 인턴십의 코딩 테스트는 알고리즘을 얼마나 잘 이해하고 구현하여 정확하고 효율성이 좋은 코드를 짜는지를 확인하는 것이 핵심이다. 그래서 정확성과 효율성 테스트 케이스가 나눠져있기도 하고... 그런데 네이버 검색팀 연구개발 인턴십 코딩테스트는 (내가 느끼기엔...) 알고리즘 문제를 푸는 것과는 크게 상관이 없었다. 프로그래머스나 코딜리티에 올라와 있는 예시 문제들과는 결이 아예 달랐다. 알고리즘 문제풀이라기보다는, 벤치마크 데이터셋을 실험에 사용할 때 필요할만한 데이터 전처리에 관한 간단한 코드(?)를 쓸 수 있는지, 그리고 문자열을 얼마나 잘 다룰 수 있는지를 검증하는 것 같았다. 아마 개발직이 아닌, 연구개발직 인턴을 뽑는 테스트라서 그런 듯하다. (그래서 대학원생인 나에게 채용 제안이 온 것이 아닌지...) 혹시 네이버 연구 인턴을 준비하는 분이 이 글을 본다면, 굳이 코딩 테스트 준비에 많은 시간을 들이지 않아도 된다고 말하고 싶다. NLP 연구에 관심이 있었다면 별도의 준비 과정 없이 충분히 쉽게 이해하고 구현할 수 있을만한 문항들이었기 때문이다.
정말 별로 어렵지 않았던 게, 총시간이 190분이 주어졌는데 세 문제를 다 풀고 보니 한 시간 정도밖에 지나있지 않았다. 프로그래머스로 응시했던 코딩 테스트와는 다르게 코딜리티는 응시자가 직접 확인할 수 있는 테스트 케이스가 몇 없었고, 코드를 제출하고 나서도 정확한 점수는 알 수가 없다. 내가 너무 문제를 대충 보고 풀었나 싶어서, 문제도 다시 읽어보고 테스트 케이스도 직접 만들어서 실험해보다가 뭘 더 할 수 없겠다는 생각에 결국 총 응시 시간 한시간 반 정도만에 그냥 코드를 제출했다. (나중에 면접에서 들었지만, 코딩테스트를 굉장히 잘 봤다고 칭찬해주심! 기분 좋음...^^) 내가 따로 코딩테스트를 준비해본 경험이 없다는 것을 감안하면, 정말, 정말, 연구에 필요한 스킬만을 확인하는 것 같았다.
4. 코딩테스트 및 서류 전형 결과 발표
코딩테스트 합격! 1차 면접 준비 시작.
코딩테스트 결과는 약 10일 후 메일로 왔다. 결과는 합격!
합격 소식 및 면접 응시 안내도 메일로 받았다.
코딩 테스트는 사실 난이도 면에서 보면 형식적으로 보는 전형이구나 싶었고, 이제 남은 전형은 면접 하나라고 생각하니 괜히 기대가 되기 시작했다. 1차 면접(이라고는 하지만 최종 면접)은 일대일 기술 면접으로, 세션 당 1시간씩 총 두 세션으로 이뤄진다고 안내를 받았다. 면접을 두 시간 씩이나...? 하며 무서워하고 있었는데 다른 개발 직군 인턴 면접을 찾아봐도 두 시간 정도는 기본으로 보는 것 같았다. 역시나 코로나 때문에 화상 면접을 봤고, 여러모로 광주에서 학교를 다니고 있는 나에게 좋은 상황이다 싶었다. 또, 면접 응시 날짜들을 주며 일정이 안 되는 날짜나 시간을 회신해달라고 쓰여 있었다. 면접자를 배려해주는... 좋은 기업... 여튼 자세한 면접 준비 방법이나, 분위기, 질문 유형에 대해서는 다음 글에서 다뤄 보도록 하겠다! (참고로 아직 최종 결과는 안 나왔다...) (결과 나왔다! 합격 후기가 되겠네! 신나!)
두 데이터 샘플(개와 의자) 각각 data augmentation을 거쳤다. 개 사진의 경우, 왼쪽 augmentation은 사진에서 개의 머리를 크로핑한 후 흑백으로 변환한 결과이고 오른쪽은 다리를 크로핑 한 후 색 augmentation을 적용한 결과이다. 이 때, 같은 사진에서 나온 두 augmentation 이미지들은 representation들이 비슷해지도록 학습이 되고, 나머지 augmentation 이미지들의 쌍은 representation이 최대한 멀어지는 방향으로 학습이 진행된다.
이 논문의 Key idea는 Positives/Negatives from Augmentation으로, positives란 하나의 이미지에서 augmentation된 두 개의 이미지들의 쌍을 의미한다. 위의 예제에서 보면, 개의 흑백 머리 사진과 색이 바뀐 다리 사진이 하나의 positives 쌍을 이루게 된다. 그리고 서로 다른 이미지에서 augmentation된 이미지의 쌍을 negatives라고 지칭한다. 위의 예제에서는, 개의 흑백 머리 사진과 흑백 의자 사진이 negative 쌍을 이룰 수 있다.
본 논문에서는 아래와 같이 positives에 대한 loss term을 제시함으로써 self-supervised contrastive learning을 가능하게 한다.
\( sim(z_i, z_j) \) : 같은 이미지에서 얻은 augmentation 이미지 \( i, j \)의 similarity(유사도) 값
\( sim(z_i, z_k) \) : 다른 이미지에서 얻은 augmentation 이미지 쌍의 similarity(유사도) 값
하나의 Batch에 대한 loss로, 한 개의 배치는 총 N개의 이미지를 담고 있음
위 식의 구조를 보면 softmax와 유사함을 알 수 있다.
$$Softmax: \sigma(z)_i = \frac{exp(z_i)}{\sum_{j=1}^{K}exp(z_j)}, \ for \ i = 1, ..., K \ and \ \textbf{z}=(z_1, ..., z_K) \in \mathbb{R}^K$$
결국 이 loss term을 이용하면, 같은 이미지에서 augmentation된 이미지들의 representation이 유사하도록, 다른 이미지에서 이 된 이미지들은 representation이 더 멀어지도록(less similar) 학습이 된다.
출처: A Simple Framework for Contrastive Learning of Visual Representation 논문
위 그림은 랜덤하게 선택된 10개의 클래스에 해당하는 이미지들의 hidden vector, 즉 학습된 representation들을 t-SNE로 시각화하여 나타낸 것이다. 실제로 이미지의 representation들이 각 class 별로 클러스터링되도록 학습된 것을 확인할 수 있다.
이 SimCLR은, 마찬가지로 unsupervised learning에서 contrastive learning을 이용한 MoCo보다도 월등하게 좋은 성능을 냈다.
그렇다면, Contrastive learning을 Language 도메인에서도 사용할 수 있을까?