연락처에서 성 이름을 추출하는 방법 고민
소셜 앱을 하나 만들고 있는데요, 연락처와 관련된 작업이 많습니다.
핸드폰에 있는 연락처를 받아와서 성과 이름을 추출하고, 유저가 마지막단계에 직접 추출 결과를 확인하면서 잘못된 부분 정정하는 식으로 만들려고 합니다.
그런데 문제는 성과 이름을 추출하는 방법인데요
아시다시피 핸드폰 연락처 데이터는 성, 이름, 경칭, 중간이름, 호칭, 닉네임, 휴대폰번호, 이메일 ...... 이런식으로 필드가 다 구분되어 있습니다.
근데 문제는 사람들이 이렇게 구분된 필드에 딱딱 정확하게 그 데이터만 넣는게 아니라서 문제가 발생하는데요.
당장 저만해도, 제 연락처에는 성 이름이 ( 슬래쉬 / 가 필드 구분자입니다)
홍/길동
처럼 성 이름이 필드에 맞게 구분 되어있는것과
/홍길동
처럼 그냥 이름 필드에 성 이름을 같이 넣어놓은게 섞여있고요
또 드물지 않게, 이름 옆에 해당 사람의 그룹이나 태그를 적어놓았다던가
홍/길동 동호회1
아니면 동명이인을 구분하기 위해
/임 꺽정 1
/임 꺽정 2
이렇게 되어있는 놈들도 있고
아니면 태그를 앞에 달아서
/모임1 - 심청이
/모임1 - 심봉사
이런식으로 되어있는 연락처들도 있습니다. 이건 아마 이름 정렬시 태그가 앞에 있는게 정렬이 쉬워지니까 했던거 같네요
당장 저만 해도 이렇게 연락처 이름 저장 방식이 다양하다보니까, 다른 사람들은 얼마나 더 다양한 방식으로 저장하고 있나 싶기도 하고,
그러다보니까, 제가 적당한 파싱 알고리즘을 만들었다고 해도 그 수많은 상황에 다 대응할 수 있을까 걱정이 먼저 앞서네요
다행인건, 성 이름 추출 작업이 semantic이아니라 syntactic 쪽이라서, 자연어 처리같은 어려운 부분은 안 건드려도 된다는 점이네요. 이거까지 해야 했으면 어휴 진짜 골때렸을거 같거든요....
혹시 성, 이름 파싱 알고리즘에 대한 아이디어를 주실 수 있는 분 계실까요.
몇가지가 걸리네요.
1.
정렬이나. 검색은 DB 쿼리로 구현하시면. 조금 편하실것 같습니다.
2.
그렇지만. 개인정보를 어떻게 처리해야 하는지는 저도 의문이 가네요.
암호화된 정보로 처리해야 하는건지... DB 에 저장할때도 암호화 하셔야 할겁니다.
페이스 북. 구글. 다음. 네이버. 정부.등에 OpenAPI를 사용해야 하는건지...
개인정보 보호법관련 내용도 있으니. 자세한것은 전문 기업에 문의해보시는것이 좋을것 같습니다.
구축한 웹메일(다람쥐메일)에 접속이 되지 않습니다.
https://kldp.org/node/155820
----------------------------------------------------------------------------
젊음'은 모든것을 가능하게 만든다.
매일 1억명이 사용하는 프로그램을 함께 만들어보고 싶습니다.
정규 근로 시간을 지키는. 야근 없는 회사와 거래합니다.
각 분야별. 좋은 책'이나 사이트' 블로그' 링크 소개 받습니다. shintx@naver.com
아니 DB 쿼리를 때렸을 때 나오는 값을 어떻게
아니 DB 쿼리를 때렸을 때 나오는 값을 어떻게 분리하는 지가 질문의 핵심인 것 같은데, 이건 왜 전혀 파악하지 못하셨나요? 그리고 아래 개인정보 처리에 관한 건 이 시점에서는 완전한 사족입니다.
정말 이건 Heuristic밖에 답이 없습니다. 성 필드가 비어 있다면 이름의 맨 첫 글자를 성으로 간주하는 건 쉬운데, 이름 란에 별도의 태그를 집어넣는 것은 사람마다 다르기 때문에 다른 필드를 사용하는 것을 유도하거나, 아니면 [가-힣] 밖의 문자를 구분자로 간주하는 법이 있습니다. 한국 이름이라면 이게 통하겠지만, 외국 이름이 섞여 있으면 이것도 어렵겠네요.
예민하게 신경 쓰는 사람이 손해...
질문 맥락 파악 안하고 (혹은 못하고) 동문서답하거나 잘 알지 못한다고 시인하면서도 이것저것 검색만 해서 링크해놓는 답변이 저도 썩 보기 유쾌하진 않습니다만 여기는 열린 공간이죠. 저런 글도 올릴 권리가 있지 않느냐 하면 할 말은 없습니다.
그냥 네이버, 구글 검색할 때 맨 위에 뜨는 검색광고 보듯 보시는 게 좋을 것 같아요. 존재 자체가 거슬리지만 사실 크게 나쁠 건 없고 또 아주 가끔은 도움이 된다는 점에서 말이죠.
사실 아는 분들은 그냥 피하면 되는데 모르는 분이 보고 유효한 답변인 줄 착각하시는 경우가 좀 있어서 경고문이라도 따로 붙여드리는 게 어떨까 가끔 생각은 합니다. (그건 검색 광고에 대해서도 마찬가집니다.)
어쨌거나 쓰레드에 끼어들려면 그 주제와 관련된 내용을 조금이라도 다루는 게 예의겠죠. 솔직히 저도 질문자님이 하시고자 하는 일은 상당히 어려울 것 같다고 생각합니다.
사용자가 정확한 형식대로 맞추어 입력해 주지 않으면 거의 불가능하다고 봅니다. 그래도 나름 유효한 휴리스틱 알고리즘 몇 개를 생각해볼 수는 있겠네요.
* 이름은 조금 애매하지만, 우리나라에서 쓰이는 성씨는 거의 그게 그겁니다. 김/이/박/... 다 합쳐도 100개 안 되겠지 싶은데, 일단 이걸 우선적으로 찾아볼 수 있겠지요.
* 이름...도 100% 커버리지를 포기한다면, 일단 인명용 한자의 한자음부터 찾아보는 게 어떨까요. [가-힣]은 11172자이지만, 익히 아시다시피 이 모든 글자가 인명으로 사용되는 것은 아니니까요. 인명용 한자음이 두 개 연달아 있을 때 이름 후보로 올린다면 탐색 공간을 어디까지 줄일 수 있을지는 저도 잘 모르겠습니다.
* 문제는 순우리말 이름이나 세례명("다니엘" 같은 이름을 쓰시는 분 계시죠) 같은 게 예외가 되는데 이런 것들도 아마 찾아보면 경우의 수가 그리 많지 않을겁니다.
* False positive를 줄이기 위해서라면 "명백히 이름이 아닌데 연락처에 잘 들어가는 키워드"들을 따로 모아놓아서 탐색에서 배제하는 방법도 유효할 것 같습니다. 예컨대 직위라던가("부장님", "과장님") 등.
그나마도 한국 이름이란 걸 확신할 수 있을 때나
그나마도 한국 이름이란 걸 확신할 수 있을 때나 가능한 휴리스틱이지, 외국 이름이 포함된다면 답이 없습니다. 전세계의 작명 방식을 모두 숙지하고 있지 않다면 말이죠.
사실 작명 방식은 각 문화의 일부라고 볼 수도 있는 만큼 외국인의 이름이 어떤 형식을 가지고 있으리라고 함부로 단정짓는 건 심지어 타 문화에 대한 무례라고 볼 수도 있습니다.
저는 개인적으로 이런 글을 인상깊게 읽었습니다.
http://j.mearie.org/post/105656799938/personal-name-convention
고민 후기
shint 님은 뭐하시는 분인지 저도 잘 모르겠네요. kldp에 글올린건 처음이고 첫 댓글이 달려서 반가운 마음에 읽어봤는데, 뭔가 내용이 구름 잡듯 붕뜬, 저랑은 관계없는 내용이고, 혹시나해서 작성글 검색해보니 죄다 영양가 없는, 무쓸모 댓글만 널려있네요.
한 일주일동안 제 연락처 데이터를 계속 들여다봤습니다. 한 400개 남짓 있는데, 친한 친구한테도 달라고 해서 700개 정도 되는 연락처를 하루에 몇시간씩 멍하니 들여다보면서 아이디어를 고민했던거 같아요
원래 목표는 연락처에서 성 이름 파싱해서, 이걸 바탕으로 성별 추측을 하는게 목표였습니다. 성별 추측은 http://www.erumy.com/nameAnalyze/eDefault.aspx 쪽으로 api 요청을 하려고 했지요. 외국은 이미 머신 러닝 분야에서 gender prediction이 꽤 보편적인 이슈가 되었는데, 이런 연구를 기반으로 할 수 있는 이름,성별 데이터도 미국 정부측에서 제공하고 있었구요 https://www.ssa.gov/oact/babynames/limits.html 근데 국내는 아무리 찾아도 이름 성별 통계 데이터를 제공하는곳이 하나도 없어서, 저 이루미란 쪽에서 만든 통계 데이터를 이용한 성별 예측이 거의 유일한 방법이라고 판단했습니다.
근데 어쨌든 연락처에서 성 이름 추출해서 성별까지 예측하는게 제 목표긴 했는데, 성 이름 파싱이 생각보다 꽤 어려운 작업이다 보니 고민고민하다가, 지금 제 서비스에 과연 성별이 핵심 기능인가...를 더 고민해 보니, 그렇게까지 핵심은 아닌거 같더군요. 그래서 그냥 성 이름 추출은 일주일정도 고민한 선에서 접기로 했습니다. 그래도 한 일주일정도 데이터를 들여다보니 어느정도 추출에 대한 실마리를 얻을 수 있었는데, 혹시 나중에 저와 비슷한 고민을 하시는 분이 있을까봐 흔적을 남겨놓도록 하겠습니다.
1. 일단 성 이름을 파싱하기 전에, 유저의 연락처 저장 행동 방식과 성 이름에 대한 어느정도 가정을 해 놓는게 필요했습니다. 완전히 제로 베이스에서 파싱을 할려고 하면 변수가 너무 많아서 통제가 안되더군요. 근거는 저의 행동 패턴을 근거로....
1) 연락처에 성을 안저장하고 이름만 저장하는 경우는 없다고 가정합니다.
2) 성과 이름 사이에는 띄어쓰기 외에 다른 문자가 오지 않는다고 가정합니다. 띄어쓰기는 있을수도있고 없을수도 있고.
3) 일단 사람 이름이란게 문화권별로 워낙 다양하기 때문에, 여기서 언급하는 모든 방식은 우리나라 사람, 그리고 한글 기준입니다.
4) 우리나라 사람 이름은 대부분 성+이름 3자이기 때문에 3글자 이름을 기준으로 합니다. 그리고 여기서 예외적으로 2글자와 4글자일때 파싱을 하도록 해야 합니다.
5) 아무리 파싱을 잘했어도 100% 신뢰가 불가능하기 때문에, 최종 작업 이전에 파싱 확률이 떨어지는 연락처들에 대해선 유저가 성 이름을 확인하고 입력하는 작업을 추가시켜줘야합니다.
6) 우리나라 성씨는 다행히 100개 내외로 한정되어 있습니다. https://namu.wiki/w/%ED%95%9C%EA%B5%AD%EC%9D%98%20%EC%84%B1%EC%94%A8 여기에서 1000명 이상만 사용하는 성씨 중에서 한자가 달라서 중복되는 성을 다 합쳐보니 120개 내외가 나오더군요 이것들을 familyNameArr에 저장해놓습니다.
7) 각 연락처의 input과 output 은 [(연락처)] -> [(성,이름,태그,확률)] 이 되겠습니다. 유저가 구분하기 위해 추가적으로 달아놓은 태그 문자열은 남겨놓는게 좋을거같더군요.
2. 사전 작업 (pre-processing)
1 ) globalTokenArr 들을 준비해놓습니다.
ㄱ) globalTokenArr 에는 어머니, 아버지, 선생님, 교수님, 대리님 처럼 모든 사람들이 보편적으로 쓸법한 토큰 목록을 의미합니다. 가족 호칭, 직함, 직위 이런것들은 적당히 인터넷에서 사전식으로 구할 수 있기 때문에 미리 저장해 놓습니다.
ㄴ) 이 globalTokenArr는 너무 넓으면 searching space에서 손해가 생기기때문에 최적화 시켜놓는게 중요합니다. 그래서 globalTokenThresholdArr가 필요하게 됩니다. 후술하겠습니다.
ㄷ) 차후에 personalTokenArr도 필요한데 이건 후술하도록 하겠습니다.
2) 사람 이름을 파싱하는게 기본 전제이기 때문에, 사람 연락처가 아닐 경우 일단 배제시켜놓는게 좋습니다.
- 연락처에는 전화번호 필드가 여러개 있습니다. 핸드폰전화번호, 집 전화번호, 회사 전화번호, 추가 전화번호 등등, 뽑아보면 한 6,7개정도 필드가 있는데, 보통은 핸드폰 전화번호에 010~으로 저장하는게 보통이지만, 조작 실수로 집 혹은 회사에 핸드폰 번호를 저장하는 경우도 꽤 있습니다. 저도 그렇구요. 그래서 모든 전화번호 필드를 다 긁은다음에 010 시작하는 필드가 하나도 없으면 non-human 으로 가정하고 빼버려야 합니다.
- 가끔 보면 +82 10으로 시작하는 핸드폰번호도 있기 때문에 이것들 010으로 replacing 해줘야 합니다.
- 국제 번호는 처리 포기... 안습...
3) fullName 필드 만들기띄어쓰기.. 골치아픔
- 성과 이름을, 성/이름 필드에 이쁘게 따로 저장되어 있을수도 있고, 한곳에 몰아서 저장되어 있을 수 있습니다. 따라서 성과 이름을 concat 시킨 fullName 필드를 따로 만들어서 이걸 source string 으로 보는게 합당합니다.
- 이름 필드에 성과 이름을 몰아넣을때 띄어쓰는 사람도 있고 안띄어쓰는 사람도 있습니다. "홍 길동 날쌘돌이" 처럼 이쁘게 성, 이름, 태그 사이에 띄어쓰기를 했으면 참 고마운데, "홍길동날쌘돌이"처럼 하는 경우도 있습니다.
- fullName을 만들때 띄어쓰기 한칸을 넣어야 하나 말아야하나? 결론은 넣는게 좋을거같다 입니다. 후술 하겠습니다.
4) 사전 작업 의 결과는 iOS나 android에서 연락처에 쓰이는 모든 필드 중에서 필요한 부분만 파싱한 필드들+fullName 필드가 됩니다.
(raw 연락처) = (성,이름,경칭,중간이름,호칭,닉네임,휴대폰번호,이메일,그룹명,회사번호,집번호,회사Fax번호,집Fax번호, 기타번호, .......)
(pre-processed 연락처) = (성,이름,fullName, 휴대폰번호)
3. iterating : for contact in preProcessedContactsArr
- iterating 은 정말 단순하게 length와 globalTokenArr 매칭을 기준으로 한 분류입니다.
1) fullName length 를 기준으로한 필터링
ㄱ) fullName length == 4 이면서, (word) + (space) + (word)(word) 인경우
a) 첫번째 word가 familyNameArr 에 해당할 경우 && 첫번째 word가 "성"필드 값과 일치할경우 => highest 확률 부여 // 이건 원래 성/이름 필드에 있던 성/이름이 fullName에 "성 이름"으로 합쳐진 케이스 입니다.
b) 첫번째 word가 familyNameArr 에만 해당할 경우 => 2nd highest 확률 부여 // 이건 이름 필드에 "성 이름" 으로 저장되어 있던 케이스라고 봐야합니다.
ㄴ) fullName length == 3 이면서 (word)(word)(word) 인 경우
a) 첫번째 word가 familyNameArr 에만 해당할 경우 => 3rd highest 확률 부여 // 이건 이름 필드에 "성이름" 으로 저장되어 있던 케이스라고 봐야합니다.
// 1) 에서 필터링 통과하면 확률 부여 후 다음 연락처로 넘어갑니다.
// 참고. 이름이 2자인것과 4자인건 두번째 iterating 단계에서 분류합니다.
// 1) 단계만 해도 꽤 많은 이름이 걸러지게 됩니다 제 연락처 기준 7~80% 정도?
2) globalTokenArr 매칭 필터링
ㄱ) fullName 이 아빠, 엄마, 수학쌤 이런것들과 매칭되면 볼거 없이 lowest 확률을 부여시키면 됩니다.
- globalTokenArr에서 반복적으로 검색을 해야하기 때문에, globalTokenArr은 해시 구조로 만드는게 이득일겁니다
3) 1), 2) 단계에서 필터링 되지 않은 것들에 한해서 tokenMixedContactsArr 로 넘겨주고 5번 스텝으로 넘어갑니다.
4. tokenMixedContactsArr 에서 token 뽑아내기
- 3. 단계를 거치고나면, 성+이름의 길이가 2자인 이름, 4자인 이름, 그리고 성, 이름 앞뒤로 태그가 달린 이름들만 남게 됩니다.
- 모임1 - 홍길동, 모임1 - 김말자 아니면 홍길도 우리과 08, 김말자 우리과 09 이런식으로
- 여기서 문제는 띄어쓰기를 위 예시처럼 이쁘게 했으면 토큰 파싱이 쉬워지는데, 모임1홍길동 모임김말자 홍길동우리과08 김말자우리과09 이런식으로 띄어쓰기 없이 태그를 달았으면 파싱이 어려워집니다. 그래서 좀 트릭이 필요합니다.
1) 띄어쓰기가 잘 되어있으면, 토큰 비교가 쉬워집니다. 띄어쓰기가 잘 안되어있으면 2)로
2) tokenMixedContactsArr 에서 fullName 기준으로 정렬 시키면, 자연스레 같은 태그가 앞에 달린 연락처들은 이쁘게 정렬이 됩니다. 여기서 fullName의 첫번째 문자부터 시작해서 공통된 문자열을 가진 다른 fullName이 있으면 (어차피 연속적으로 존재할겁니다) 늘려나다가 최대길이가 되면 토큰으로 끊고 personalTokenArr에 저장시킵니다. 다만 이 토큰이 familyNameArr에 존재할 경우, 태그가 아니라 성에 해당하므로 토큰에 저장시키지 않습니다. 그리고 토큰을 뺀 뒷 첫문자가 성씨에 들어가는지 확인하고, 나머지 글자 길이가 2이면 성 이름으로 파싱하고 tokenMixedContactsArr 에서 뺍니다.
3) tokenMixedContactsArr에 있는 모든 element들을 reversing 시킵니다.
4) tokenMixedContactsArr를 fullName 기준으로 정렬시키면, 뒤쪽에 있는 토큰을 기준으로 정렬이 됩니다. 2)과 마찬가지 작업을 해주면 뒤쪽에 있는 토큰만 이쁘게 뽑아내면 성,이름만 남게 됩니다
5. personalTokenArr -> globalTokenThresholdArr
1) personalTokenArr에서 생긴 토큰을 globalTokenThresholdArr로 반영시킵니다
2) globalTokenThresholdArr은 매 새로운 유저의 연락처를 파싱할때마다, 내부 값들을 decaying 시킵니다
3) 여러 유저들에게서 자주 발생하는 토큰들은 당연히 threshold값을 증가시킵니다
4) globalTokenThresholdArr에서 일정한 값이 넘으면 globalTokenArr 으로 elevate 시킵니다. (사람별로 자주 발생하는 토큰은 비슷하다는 전제하에 할수있습니다)
어쨌든 여기까지가 제가 생각한 휴리스틱 알고리즘 ㅜㅜ 효율 개떡일거같은데..... 그래서 그냥 성 이름 파싱은 포기할랍니다
shint 님은 뭐하시는 분인지 저도 잘 모르겠네요.
shint 님은 뭐하시는 분인지 저도 잘 모르겠네요. 동감!
세벌 https://sebuls.blogspot.kr/
shint님
저런 행동으로 예전에 게시판에서도 한소리 들으셨던거 같은데 한동안 안보이시더니 또 저러시네요 .. 그냥 넘어가시고
어짜피 이건 논외고...
저만큼 고민 하시고, 많이 생각 해 보셨으면 더 진행을 해 보셔도 될거 같은데 말이죠..
읽어보면서.. 뭔가 고민 진짜 많이 하셨다.라는 생각이 들면서 재밌게 읽고 있었는데요 ㅠㅠ 포기라뇨 ㅠㅠ
그럼.. 진행하시는 앱은 어떻게되시는건가요?
오 읽으시는 분이 있네요
성 예측 부분만 빼버리고 앱은 계속 진행하고 있어요~ 유저 편의를 위해서 넣으려 했던건데, 저거 알고리즘 짜기 시작하면 다듬는데만 해도 한세월 걸릴거같아서... 관심 가져주셔서 감사합니다
재밌게 읽었어요 ~
성 이름 파싱이 얼핏 생각 하기엔 간단해 보였는데
사람들이 저장하는 방식이 워낙 제각각이다 보니 생각보다 어려운 점이 많네요.
머신러닝 쪽에서 전문가분이
뭔가 좋은 아이디어를 하나 던져 주시면 좋을거 같은데 말이죠.
그쪽은 저도 문외한이라.. 흠흠..
만드시는 앱 잘 진행되리라 믿습니다!
댓글 달기