2017.03.14 14:52

추천 시스템에 대한 좁고도 얕은 지식

빅 데이터의 적용 사례로 빼놓을 수 없는것이 추천 시스템입니다.


관련된 용어도 많고, 이렇다 저렇다 말은 많은데, 실체를 알려면 좀 노력이 필요 하더라구요.

그래서 조금 탐색해 본 내용들을 정리해서 간단히 입문 수준의 자료를 만들었습니다. 


참고하세요.


https://www.slideshare.net/SooKyungChoi/recommendatioin-system-basic-73087621


저작자 표시 비영리 동일 조건 변경 허락
신고
Trackback 0 Comment 0
2017.03.06 18:02

구인/구직의 기록 - 데이터 분석가@스타트업

2년 반 정도 스타트업에서 데이터 분석가로 일했다. 그동안 다른 사람을 채용하기 위한 인터뷰를 스무번쯤 했고, 최근 이직을 위해 내가 지원한 인터뷰도 열번 정도된다. 그 과정에서 알게된 점을 정리 해두고자 한다.

(한글을 사랑하는 마음으로! 이제부터 인터뷰는 모두 면접이라고 쓰려고 함)

가장 먼저 얘기하고 싶은것은 면접은 일방적인 ‘선발’이 아니라, ‘탐색’의 자리라는 점이다. 그러니까 쫄 필요 전혀 없다! ^^;

빗대어보자면 시험이 아니고, 소개팅이나 맞선에 가까운 자리라는거다. 
조금만 노력한다면 지원자도 면접과정에서 이 회사가 나랑 맞는지 아닌지를 꽤나 정확하게 알아챌 수 있다. 
일단 회사에 가보면 느끼게 되는 첫인상이 있고, 말을 섞어보지 않아도 오가는 직원들의 표정과 말투에서 우리는 많은것을 알게된다. 느낌적느낌 이랄까? 뭐 그런거겠지. 그 와중에 한시간 이상 얼굴을 보고 대화하는 면접관이 주는 영향은 더 말할것도 없이 무진장 크고...  

한번은 인터뷰에 들어가자마자 먼길 오셨다며 의례적인 질문을 하길래, 나름 성실히 대답하고 있는데 이미 내 답변을 듣지 않고 있는 걸 느끼고 마음 상한 적이 있었다. (물론 중요한 질문은 아니지만, 난 누구랑 얘기하니? 이런 기분은 정말 별로거등.) 
좋은 회사일수록 피면접자를 충분히 존중하더라. 답변하는 내용을 노트북에 정리해야 해서 얘기에 집중하지 못하는 것처럼 느끼실 수도 있다며 사전에 양해를 구하는 경우가 일반적일 정도로... 

당신이 면접관이라면 - 특히 뽑고싶은 인재를 마주했다면 - 정말 잘 처신해야 한다. 오글오글 진부한 표현이지만 ‘여러분이 회사의 얼굴입니다’ 니깐...
면접자로써는 일단 지금까지 말했듯이 쫄지말고, 그리고 지원한 회사에 대해 궁금한 점을 충분히 생각해 두어야 한다. 아무 질문도 안하는 건, 사실 입사 하고픈 의지가 그리 강하지 않다는 사실을 반증하는 것이기도 하니까. 침구 장사하시는 친척 분 말씀이 떠오른다. '이것도 좋고 저것도 이쁘네요' 하는 사람은 안 살 사람이라고, 살 사람은 이것저것 묻고 따지는게 많다고...


그럼 이제 일반적인 얘기 말고, 데이터 분석가, 특히 스타트업에서의 데이터 분석가에 대해서 느낀점을 적어 보련다.

일단 스타트업은 업의 특성 상 당신이 좀 더 넓은 영역을 커버 할 수 있어야 뽑을 수 있다. 

적은 인원으로 회사가 굴러가야 하니까. 

그래서 데이터 추출은 남이 해주고 분석만 했다면 조금 난처하다. 
분석은 모르겠고 전 통계(에만!) 전문가입니다라고 하면 많이 난처하고….

그러니 SQL정도는 우습게! 쓸 수 있도록 갈고 닦아두고, 프로그래밍 언어, 예를 들면 python이나 R, scala 중에서 하나 정도는 기본적인 수준은 되도록 공부해두는 것이 필요하다. (찔린다. 나도 많이 부족하다. 이것땜에 떨어진 곳도 있는것 같은ㄷ…OTL)
요즘은 면접에서 SQL을 직접 작성해 보라거나 사전에 아예 과제를 내서 걸러버리는 경우가 허다하다. 
언어는 혼자서 공부는 해봤다고 답하면, 언어에 대한 간단한 질문을 던지긴 하는데, 이건 장님 코끼리 만지기라서 양쪽 모두 답답하다. 그냥 평소에 내가 자주 짜 본 코드는 이정도다 이런걸 제시할 수 있으면 면접이 백만배는 수월해 진다. 개발자들은 보통 오픈소스에 기여했거나 본인의 개인 프로젝트를 git에 올려두고 링크만 이력서에 넣는데, 이게 가능하다면 제일 멋지다. (아.. 나도 언제쯤 이렇게…)

그리고 데이터 분석가로써 스스로 문제를 정의해서 진행한 경험이 있는지 많이 묻더라.

아무래도 빅데이터 분석이 새로운 영역이고 보니 관리자나 C레벨에서 분석할 목록을 쫘악 정리해서 들고있지는 않다. 따라서 분석가가 스스로 전문가로써의 역량을 발휘해 문제를 찾고, 정의하고, 풀어나가서 해법을 제시해서 설득 해주길 원한다. 그러니, 홈즈의 예리한 관찰력, 컨설턴트의 빈틈없는 논리력, 네임드 웹 소설가의 흡인력 있는 문장력을 가지고 있다면 어디나 합격할 수 있을듯 하다. 이건 농담이자 진담인데, 다 갖추긴 어렵지만 다 갖추면 정말 좋으니깐...

어쨋든 분석이란걸, 평소에 차근차근 시도 해 두어야 한다. 지금 있는 곳에서 나름대로 궁금한 주제를 정하고, 분석해서 결과를 주변에 공유해보는 연습... 이런게 쌓여야 면접 자리에 가서도 할말이 있다. 정말이다 내 면접의 팔할이 이런 얘기였다. 

일단 이럴 '꺼리'가 있다면 다시 다음 단계의 질문이 이어진다.
  • 그 중에서 회사의 매출에 기여했거나, 새로운 기능 개발로 이어진게 있나요? -.-a 이런걸 답하기 위해 평소에 분석할때 이게 어떤 가치를 가지는지 고민해야 한다는 교훈!
  • 그 중에서 통계 방법이나, 통계 모델, 머신러닝/딥러닝을 적용한 게 있나요? =.=a 이런걸 답하기 위해서는 평소에 의식적으로 저것들을 공부하고 실험적으로라도 분석에 적용해봐야 한다는 교훈!

마지막으로 DW(Data Warehouse)나 CRM(Customer Relationship Management)과 같은 전통적인? 데이터 분석을 해 왔는데, 이제부터 스타트업의 빅 데이터 분석가로써 일하고 싶다면, (제발) 다름을 인정하고, 얼마나 준비되었는지 증거를 제시해야 한다.

스타트업의 빅 데이터 환경은 Spark나 Hadoop 기반인 경우가 많은데, 데이터 분석가의 공급이 한정돼 있다보니, SI 등에서 DW/CRM 경험을 가지신 분들도 꽤 지원한다. 이런 경우 아쉬운점은 DW = Big Data 로 생각하는 분이 많다는 것이다. 물론 크게보면 같은 범주이지만 다른 점도 꽤 존재한다. 특히나 프레임웍 측면에서 다르니, 기술이나 개념을 새로 학습해야 한다. 그런데 그냥 거의 같은거라 줄기차게 주장하면서 Spark이나 Hadoop이 별거냐? 이렇게 나오면, 흠… 뽑고 싶지 않다. 
요즘같은 세상엔 새로운 것을 학습하는 능력이 참 중요한데, 새로운 것이 이미 알고있는 것과 다르지 않다는 출발점은 학습을 하겠다는 건지 아닌지 의심이 갈 수 밖에 없으니까 말이다.  
그리고 여기서도 Spark이나 Hadoop을 개인적으로 공부는 해봤다는 경우가 대부분인데, 공부의 증거(!)를 남기는게 필요하다. (나도 이제부터 블로깅을 더 열심히… 쿨럭…) 이것도 역시 면접이 백만배는 수월해 지니까....

진짜 마지막인데, 빅데이터가 유행이라고 다니던 회사를 그만두고, 교육과정을 듣거나 스터디를 하면서 취업을 준비하는 것은 말리고 싶다. 

주니어 급에서 이런 경우를 왕왕 만난다. 회사가 시시하다?거나 무시무시하다?고 느껴서, 그 시간에 공부에 집중해서 다른 회사로 옮겨보리라 하고 용감하게 그만두고 공부에 집중한다. 물론 스터디도 열심히 나가고, 스터디 결과를 블로그에도 깃헙에도 잘 남긴다. 하지만, 뽑는 입장에서는 스터디는 스터디인지라 한계가 많다. 대부분 깊이가 얕고 관심있는 핫한 요소만을 섞어서 써봤을뿐 어떤 문제상황이나 고려할 사항은 빠진 경우가 대부분이니깐. 게다가 이런 분을 몇 명 만나다보면 스터디로 해 본 내용들이 참 비슷하기도 하다. 
현재 있는 곳에서 어떡해서든 데이터 관련된 일을 조금이라도 해 볼 수있도록 노력해보는게 좋다. 거기가 현장이니까, 단순한 추출을 하더라도 현장의 고민을 배울 수 있다. 예를 들면 많은 양의 데이터는 어떤 형태의 파일로 어떻게 다운 받을 수 있을지, 내가 추출한 데이터가 맞는지는 어떻게 확인할지 같은 고민 말이다. 

스타트업에서 폼잡으며! 빅데이터 엔지니어/분석가로 일하는 것은 불가능하다. 그 업무 언저리의 자질구레한 일과, 그 업무 속에 본질적으로 포함된 생!노가다들을 품에 안고 작업을 완성 해낼 수 있어야 한다. 그런 사람을 뽑는 입장에서, 아름답고 새로운 기술을 향해 현재 직장을 포기한 사람이 그리 끌리진 않을 거라는 거 이해 해주길 바란다.



결론은, 
"쫄지말고 현재 있는 그곳에서! 차근차근 분석과 학습의 로그!를 남겨나간다면, 당신은 이미 그들이 애타게 찾고있는 빅데이터 분석가이다."
라는 명제가 유의수준 0.05에서 참이라는 얘기… :-)



저작자 표시 비영리 동일 조건 변경 허락
신고
Trackback 0 Comment 0
2017.02.12 00:42

몇 번째 이벤트에 반응했는지 확인하는 Scala 코드 예제

오늘은 Spark + Zeppelin 상에서 돌아가는 scala 코드 예제를 좀 보여드리려고 합니다. 

이걸 보시면 map reduce의 개념과 로그 데이터 처리를 어떤 식으로 하는지에 대한 대략의 감을 잡으실 수 있을것 같아서요.

코드가 어렵지 않습니다. 포기하지 말고 읽어보세요. :)



아래와 같이 유저별 이벤트 시간과 반응여부 데이터를 가지고 있을때, 각각의 유저별로 몇번째 이벤트에서 처음 반응(O)을 했는지 알아보려고 합니다. 

val test_rdd = sc.makeRDD(List(

    ("user1","2016-07-01 03:03:00","X")

    ,("user1","2016-07-01 15:15:00","O")

    ,("user1","2016-07-01 16:16:00","X")

    ,("user1","2016-07-01 17:00:00","X")

    ,("user1","2016-07-01 00:00:00","X")

    ,("user2","2016-07-01 00:00:00","X")

    ,("user2","2016-07-01 03:02:00","O")

    ,("user3","2016-07-01 05:02:00","X")

    ,("user3","2016-07-01 04:02:00","O")

    ,("user3","2016-07-01 03:02:00","O")

    ))

이렇게 하면 RDD가 만들어 집니다. sc는 SparkContext로 스팍 쉘이 뜨면 자동 제공되는 변수이고, RDD는...까지 설명하면 너무 길어지니 이건 여러 자료를 통해 확인하시길 바래요. 다만 한가지 꼭 부탁하고 싶은건 이렇게 테스트 데이터를 만들고 이걸 돌려보면서 코딩을 하는 것을 아예 습관으로 만드시라는 겁니다.  

test_rdd: org.apache.spark.rdd.RDD[(String, String, String)] = ParallelCollectionRDD[742] at makeRDD at <console>:30

이건 결과 메시지인데요, zeppelin이 spark에서 받아와서 뿌려주기만 하는 겁니다. 스트링 세개가 들어간 RDD가 생성되었다는 말이네요.


val test_map_rdd = test_rdd.map(r => (r._1, (r._2, r._3)))

이제 map 함수를 씁니다. map은 여러가지 변형을 가할 수 있는 가장 기본적인 함수인데, 기본적으로 (key, value) 형태 이어야 하니까 기준이 될 값인 user id 부분 즉 r._1만 키로 만들고 나머지는 다 괄호로 묶어서 value 로 넣어줍니다. 

이렇게 하고 take를 통해 데이터를 가져와서 확인 해봅니다.

test_map_rdd: org.apache.spark.rdd.RDD[(String, (String, String))] = MapPartitionsRDD[5] at map at <console>:31
res3: Array[(String, (String, String))] = Array((user1,(2016-07-01 03:03:00,X)), (user1,(2016-07-01 15:15:00,O)), (user1,(2016-07-01 16:16:00,X)))

이벤트 시간과 반응여부가 ()로 묶여서 들어간 것 보이시죠?

val test_group_rdd = test_map_rdd.groupByKey

그리고 나서 키를 기준으로 그룹핑을 했네요.

test_group_rdd: org.apache.spark.rdd.RDD[(String, Iterable[(String, String)])] = ShuffledRDD[8] at groupByKey at <console>:33

res6: Array[(String, Iterable[(String, String)])] = Array((user1,CompactBuffer((2016-07-01 00:00:00,X), (2016-07-01 03:03:00,X), (2016-07-01 17:00:00,X), (2016-07-01 15:15:00,O), (2016-07-01 16:16:00,X))), (user2,CompactBuffer((2016-07-01 03:02:00,O), (2016-07-01 00:00:00,X))), (user3,CompactBuffer((2016-07-01 03:02:00,O), (2016-07-01 05:02:00,X), (2016-07-01 04:02:00,O))))

위 결과에서 유저별로 compact buffer라는 타입으로 value 부분이 묶인것이 보이실거에요.

val test_group_sort_tdd = test_group_rdd.map{r=>

    val newValue = r._2.toList.sortBy(_._1)

    (r._1, newValue)

}

자 이제는 user 별로 그룹핑 된 내용에서 시간순으로 정렬해주어야 하는데요, 여기서 user 내에서! 정렬한다는 것이 중요한거죠. 전체에 대해서 정렬하라고 하면 다시한번 map을 써서 시간 필드를 키로 보낸뒤 sortByKey를 하면 간단하겠지만 user 내에서의 정렬이기 때문에 고민이 필요합니다. 그래서 두번째 줄을 보면 map 안에서 r._2를 List형태로 만든 뒤 그안에서 sortBy를 하게됩니다. 그리고 나서 sort가 된 List를 리턴하구요.


test_group_sort_tdd.take(3)


 잘 되었는지 3개만 가져와서 화면에 뿌려봐서 결과를 확인합니다. 


test_group_rdd: org.apache.spark.rdd.RDD[(String, Iterable[(String, String)])] = ShuffledRDD[754] at groupByKey at <console>:32
test_group_sort_tdd: org.apache.spark.rdd.RDD[(String, List[(String, String)])] = MapPartitionsRDD[755] at map at <console>:34
res32: Array[(String, List[(String, String)])] = Array((user1,List((2016-07-01 00:00:00,X), (2016-07-01 03:03:00,X), (2016-07-01 15:15:00,O), (2016-07-01 16:16:00,X), (2016-07-01 17:00:00,X))), (user2,List((2016-07-01 00:00:00,X), (2016-07-01 03:02:00,O))), (user3,List((2016-07-01 03:02:00,O), (2016-07-01 04:02:00,O), (2016-07-01 05:02:00,X)))) 

결과는 역시 RDD로 나왔고, 내용을 보면 user1,2,3 별로 그룹핑되어 그 안에서 시간순으로 잘 정렬된 것을 확인할 수 있네요. 

이게 바로 interactive coding interface의 장점입니다. 하나하나 진행하면서 원하는대로 되어가는지 눈으로 보면서 진행할 수 있다는 거. 이걸 테스트 데이터로 쭉 따라가며 코딩해놓고 완성되면 실 데이터를 걸어놓고 밥먹으러 가면 되는 겁니다. :)


val refine_map = test_group_sort_tdd.map{r=>

    var index = 0

    var firstO = 0

    var isFirst = true

    for(v <- r._2) {

        index = index + 1 

        if (v._2 == "O" && isFirst) {

            firstO = index

            isFirst = false

        }

    }

    (r._1, firstO)

}

refine_map.take(3)

이제 정렬은 되었으니 첫번째로 반응한 이벤트가 몇 번째에 있는지 확인하면 되는데요. 역시 map 함수 내에서 처리하는데 user 내에서 첫번째  O가 등장한게 몇번째 이벤트인지를 구하는 코드를 for 문으로 구현했습니다. 뭐 로직이 복잡한건 전혀 아니니 읽어보면 아실거에요.  

아까는 맵의 두번째 리스트를 정렬만 해서 그대로 리턴했으나 이번에는 한개 값만 필요하므로 리턴문이 (r._1, firstO)로 단순해졌다는것이 차이점이겠네요. 

refine_map: org.apache.spark.rdd.RDD[(String, Int)] = MapPartitionsRDD[761] at map at <console>:36

res42: Array[(String, Int)] = Array((user1,3), (user2,2), (user3,1))

자 이제 우리가 원했던 각 user가 몇번째의 이벤트에서  반응했는지 하는 숫자가 나왔습니다.


어렵지 않죠? :)

이 코드에 약간씩만 변형을 가하면 필요한 거의 모든 작업을 할 수 있습니다. 그러니까 잘 살펴보시고 이용해 보세요.  


 

저작자 표시 비영리 동일 조건 변경 허락
신고
Trackback 0 Comment 0


티스토리 툴바