딥러닝 없이 검색어 오타 보정하기

7 분 소요

검색이 가능한 대부분의 서비스는 키워드 검색 시 훌륭하게 오타 보정을 해준다.(니이키를 검색하면 나이키의 검색 결과를 보여주는 것처럼) 이 글에서는 3년 전 간단하게 만든 오타 보정 기능에 대해 설명하고자 한다. 최근에는 딥러닝까지 활용하여 오타 보정을 해주기도 하지만 딥러닝을 사용하기 부담되고 간단하게 오타보정을 도입하고 싶을 때 참고하면 좋을 것 같다.

아이디어

오타 보정에 대해 아이디어를 간단하게 설명하면 다음과 같다.

  1. 오타 보정을 할 수 있는 오타 키워드와 보정 키워드 사전을 DB에 저장해 놓는다.
  2. 오타 보정 사전은 개발자가 직접 추출하기 어렵기 때문에 사용자 행동 이력을 통해서 주기적으로 추출한다.
  3. 사용자가 키워드 검색 시 검색결과가 기준 개수보다 적고 해당 키워드가 오타 보정 사전에 등록되어 있다면 보정 키워드로 검색하여 결과를 전달한다.

아이디어를 살펴보면 생각보다 간단하게 오타 보정 기능을 구현할 수 있음을 알 수 있다. 이 아이디어의 핵심은 지속적으로 오타 보정을 처리하기 위해 사용자의 행동 이력을 이용한다는 것이다.

구조

위 아이디어를 구현한 서비스의 대략적인 구조는 아래와 같다.

structure

1. 사용자 검색 이력 조회 및 전달

오타 보정은 보정 대상이 되는 오타 키워드와 보정 후 키워드인 보정 키워드의 쌍이 저장된 오타 보정 사전에 의해 이루어진다. 이러한 오타 보정 사전을 지속적으로 갱신하고 관리하기 위해 오타 키워드와 보정 키워드를 개발자가 직접 찾아내서 사전 DB에 저장하는 것은 비효율적이다. 그렇기 때문에 서비스를 이용하고 있는 사용자의 행동 이력을 통해 오타 보정 키워드를 추출해야 한다.

사용자의 검색 이력 중 오타 보정 키워드 추출을 위해 필요한 데이터는 질의어, 사용자 식별번호, 검색 요청 시각, 결과 결과 개수이다. 이렇게 조회된 사용자 검색 이력은 다음 단계에서 오타 보정 사전을 갱신하기 위한 재료로 사용된다.

2. 오타 키워드와 보정 키워드 저장

오타 보정 키워드를 추출하는 방법을 살펴보기 앞서 사용자가 오타를 입력하고 스스로 보정하는 경우를 상상해보자.

  1. 사용자는 니이키라는 키워드를 입력하고 검색 결과를 본다.
  2. 결과가 없는 것을 보고 키워드가 잘못 입력되었다는 것을 깨닫고 나이키를 입력하여 검색한다.
  3. 나이키 검색 결과를 확인한다.

위의 상황을 보면 사용자는 키워드(오타 키워드)를 입력하고 검색 결과를 살펴본 후 잘못되었다는 것을 인지하고 새로운 키워드(보정 키워드)로 다시 검색할 것이다. 일반적으로 오타 검색과 보정 검색 사이의 시간은 짧은 시간안에 이루어질 것이다. 또한, 오타 키워드와 보정 키워드는 입력하는 과정에서 매우 비슷한 글자(한 두개의 자음, 모음이 다른)일 가능성이 높을 것이다. 마지막으로 사용자가 오타인 것을 깨닫는 순간은 오타를 입력하는 순간이 아니라 검색 결과를 본 후 검색 결과가 터무니없이 적은 것을 본 이후일 것이다. 이 내용을 토대로 오차 보정 적용하기 위해 필요한 조건을 일반화하면 아래와 같다.

  1. 동일한 사용자가 n초 이내 검색한 이력이 있을 경우
  2. 이전 검색 키워드와 이후 검색 키워드의 유사도가 매우 높은 경우
  3. 이전 검색 결과 개수가 x개 미만이고 이후 검색 결과 개수가 y개 이상인 경우

우선 동일한 사용자가 짧은 시간 내에 검색한 이력을 찾아야 한다. 짧은 시간의 기준은 서비스마다 다르므로 각자 판단하여 적용해야 한다.

유사도가 높은 키워드를 찾기 위해서는 이전 검색 키워드와 이후 검색 키워드의 유사도를 구할 수 있어야 한다. 일반적으로 두 키워드의 유사도는 의미적인 유사도와 형태적인 유사도로 구분된다. 의미적인 유사도는 외형적인 유사도가 아닌 키워드가 가지는 의미의 유사성을 비교하는 것으로 cosine similarity를 이용한 word2vec와 같은 word embedding 방법으로 구할 수 있다. 반면, 형태적인 유사도는 각 키워드를 구성하는 음절을 비교하여 물리적인 차이를 구한다. 이 작업에서는 오타 보정하기 위해 유사도를 구하는 것이므로 형태적인 유사도를 비교해야 한다.

두 키워드 간의 형태적인 유사도를 구할 수 있는 방법은 다양하지만, 이 글에서는 levenshtein distance를 이용하여 유사도를 구한다. 이 방법은 오타 보정을 위해 자주 사용되는 비교방법으로 간략하게 설명하면 글자 삭제, 글자 추가, 글자 수정의 방법을 이용하여 두 키워드를 똑같이 만들기 위해 가장 적게 수행할 수 있는 횟수를 구하고 이를 이용하여 두 키워드 간의 유사도를 구한다. levenshtein distance를 구하는 방법을 자세히 알고 싶으면 levenshtein distance 링크를 살펴보자.

앞의 두 가지 조건을 만족하면 사용자가 스스로 오타 보정을 했을 가능성이 높다. 하지만, 조금 더 세밀하게 오타 보정 사전을 갱신하고 싶다면 각각의 검색 결과 개수를 살펴볼 필요가 있다. 앞서 살펴보았듯이 사용자가 오타임을 깨닫고 스스로 보정을 하는 이유는 검색 결과가 자신의 의도한 것과 다르게 보이기 때문이다. 이 점을 명확한 기준으로 사용하고 싶다면 검색 결과의 개수를 살펴보는 것이 필요하다.

배치 서비스에서는 위 세 가지 조건이 만족하는 검색 이력의 키워드를 오타 보정 사전에 저장한다.

3. 검색 요청

앞서 1, 2단계가 오타 보정 사전을 갱신하는 작업이었다면 이 단계부터는 사용자의 요청에 맞는 오타 보정을 하기 위해 저장되어있는 오타 보정 사전을 활용한다. 클라이언트에서는 사용자가 찾고 싶은 키워드와 연관된 검색 결과를 요청한다.

4. 오타 키워드 검색 결과 요청 및 전달

이 단계에서는 사용자가 입력한 키워드와 일치하는 조건의 검색 결과를 가져오기 위해 검색 엔진에 요청할 쿼리를 만들고 검색 결과를 요청하여 결과를 받는다. 만약, 조회한 검색 결과가 특정 개수보다 많다면 오타 보정의 대상이 아니기 때문에 바로 7단계로 넘어간다.

5. 오타 키워드에 맞는 보정 키워드 요청 및 전달

전달받은 검색 결과를 살펴보고 특정 개수 미만이면 오타 보정의 후보가 된다. 물론 검색 결과가 부족하다고 해서 모두 오타인 것은 아니므로 DB에 저장되어있는 오타 보정 사전 정보를 조회하여 비교한다. 만약, 조회한 오타 보정 사전에 해당 키워드가 존재하지 않는다면 오타 보정의 대상이 아니기 때문에 6단계를 처리하지 않고 바로 7단계로 넘어간다.

6. 보정 키워드 검색 결과 요청 및 전달

조회한 오타 보정 사전 정보에 일치하는 오타 보정 키워드가 있다면 보정된 키워드로 검색 엔진에 검색 결과를 요청하여 전달받는다.

7. 검색 결과 전달

이 단계에서는 4가지 경우로 나누어 처리한다.

  1. 4단계에서 검색 결과 개수가 특정 개수 이상인 경우는 오타 보정의 대상이 아니므로 오타 키워드의 검색 결과를 그대로 전달한다.
  2. 5단계에서 일치하는 오타 보정 키워드가 없는 경우 오타 보정의 대상이 아니므로 오타 키워드의 검색 결과를 그대로 전달한다.
  3. 6단계에서 검색 결과 개수가 특정 개수 이상인 경우는 오타 보정의 대상이 되므로 보정 키워드의 검색 결과를 전달한다.
  4. 6단계에서 검색 결과 개수가 특정 개수 미만인 경우는 상황에 따라 검색 결과를 전달한다.

위 4가지 경우 중 마지막 경우는 상황에 따라 다르게 처리할 수 있다. 오타 보정의 대상 여부를 판단하기 어렵기 때문에 각 검색 결과의 개수를 비교하여 많은 쪽을 제공할 수 있고, 오타 보정의 대상으로 보지 않고 기존 키워드의 검색 결과를 제공할 수도 있다.

그리고 얼핏 생각했을 때 보정된 키워드의 검색 결과 개수에 따라 오타 보정 처리 여부를 정하는 것은 기존에 체크했으므로 불필요한 과정으로 보이지만, 서비스 운영 중에 검색 결과 개수는 언제든지 변경될 수 있으므로 매번 체크해주는 것이 바람직하다.

8. 검색 이력 저장 요청

오타 보정 기능이 지속적으로 최적화되기 위해서는 오타 보정 사전이 끊임없이 갱신되어야 한다. 그러기 위해서는 사용자의 검색 이력이 지속적으로 저장되어 1, 2번 단계에서 새로운 데이터로 오타 보정 사전을 만들 수 있어야 한다.

더 나은 오타 보정을 위해

위 과정대로 개발을 진행하면 그럴싸한 오타 보정 기능을 구현할 수 있다. 만약 조금 더 정교한 오타 보정 기능을 만들고 싶다면 아래의 내용을 개선할 필요가 있다.

1. 플랫폼 구분 처리

만약 당신의 서비스가 앱, 웹, 모바일 웹 등 다양한 플랫폼을 지원한다면 플랫폼 별로 오타 보정을 진행할 필요가 있다. 이는 입력 방식의 차이 때문인데, 웹 서비스는 일반적으로 qwerty 자판을 이용하여 검색어를 입력하지만 모바일 환경에서는 다양한 자판(천지인, SKY-II 한글 등)을 사용할 수 있기 때문이다. 또한, 자판의 간격이나 위치가 각각 다르기 때문에 조금 더 정확한 오타 보정을 원한다면 플랫폼이나 기기 별로 오타 보정을 처리하는 것이 바람직하다.

2. 상황에 맞는 UI & UX

오타 보정을 지원하는 서비스를 사용하면 사용자가 편리함을 느낄 수 있는 UI와 UX를 지원해야한다. 아래의 구글과 네이버가 오타 보정한 결과를 살펴보자.

결과를 보면 두 서비스 모두 오타 보정을 해주었지만, 네이버는 보정된 키워드의 검색 결과를 노출하고 오타 키워드로 다시 검색할 것인지 묻는다. 반면에 구글은 사용자가 입력한 키워드(오타 키워드)의 검색 결과를 노출하고 보정 키워드로 다시 검색할지 묻는다.(개인적으로는 오타 보정의 정확도가 높다면 네이버의 방식이 조금 더 낫다고 생각한다.) 이렇게 오타 보정은 서비스가 원하는 방향에 따라 다르게 동작할 수 있다. 어떤 방식을 취하던 장단점이 있겠지만, 사용자가 입력한 키워드가 어떻게 보정되었는지에 대한 정보는 알려줄 필요가 있다. 사용자가 의도하지 않게 동작하는 것이 언제나 바람직하지는 않기 때문이다.

3. 오타 보정 사전의 최적화

이 글에서 소개한 오타 보정 방법은 사용자의 행동 이력을 통해 오타 보정을 지속적으로 추가하는데에 집중한다. 하지만 더 나아가서 사용자의 행동 이력을 이용하여 오타 보정 사전에 불필요한 사전을 제거하거나 수정할 수도 있다.

(A, B)라는 오타 보정 사전이 있다고 가정해보자. 사용자가 A라는 키워드로 검색을 요청하면 오타 보정이 되었다는 것을 사용자에게 알려주면서 B라는 키워드의 검색 결과를 노출할 수 있다. 이 때 사용자는 실제로 A의 검색 결과를 원할 수 있다. 그렇다면 사용자는 자신이 원했던 A와 대한 검색 결과를 다시 요청할 것이다. 이런 사용자의 이력들이 일정 횟수 이상 반복되면 해당 키워드를 오타 보정 사전에서 제거할 필요가 있다. 이렇게 사용자의 행동 이력은 오타 보정 사전 제거 및 수정에 활용할 수 있다.

4. 사용자 행동이력 선정

오타 보정 사전에 들어갈만한 후보를 선정하기 위해 사용자 행동이력을 이용할 때 정제된 사용자 행동이력을 이용하는 것이 좋다. 일례로 사용자가 키워드 검색을 요청할 때 직접 키워드를 입력하는 경우도 있지만 서비스에서 제공하는 자동완성이나 인기 검색어를 이용할 수도 있다. 오타 보정은 직접 키워드를 입력하여 검색한 내역을 대상으로 하기 때문에 사용자가 직접 입력하지 않고 서비스의 도움으로 입력한 키워드는 오타라고 보기 어렵다. 이렇게 사용자의 행동 이력은 정제된 데이터를 이용해야 정확도를 높일 수 있다.

5. 자판의 거리를 활용한 유사도 계산

이 글에서는 두 키워드 간의 유사도를 구하기 위해 levenshtein distance를 이용한다. levenshtein distance는 오타 보정를 위한 유사도를 계산할 때 좋은 성능을 보이지만 어떤 글자든지 동일한 횟수만큼 유사도를 갖는다. 더 정확한 유사도를 계산하기 위해서는 키보드 자판에 인접해있는 자모음의 거리를 반영하여 levenshtein distance를 계산할 필요가 있다. 예를 들어, (니이키, 나이키)과 (누이키, 나이키)가 levenshtein distance는 서로 같지만 qwerty 키보드에서 의 거리와 의 거리는 서로 다르기 때문에 다른 유사도를 가질 필요가 있다. 이는 더 정확한 오타 보정 사전을 구성하기 위해 필요한 작업이며 추후 별도의 글을 통해 설명할 예정이다.

마치며

지금까지 간단하게 오타 보정 기능을 구현할 수 있는 방법에 대하여 알아보았다. 오타 보정을 위해서는 아래의 과정이 필요하다.

  1. 사용자의 행동 이력을 통해 오타 보정 사전을 구성한다.
  2. 사용자가 검색을 요청할 때 오타 보정 사전을 활용하여 보정된 결과를 반환한다.