블로그 이미지
평범하게 살고 싶은 월급쟁이 기술적인 토론 환영합니다.같이 이야기 하고 싶으시면 부담 말고 연락주세요:이메일-bwcho75골뱅이지메일 닷컴. 조대협


Archive»


 
 

노트7의 소셜 반응을 분석해 보았다. 


#3 제플린 노트북을 이용한 상세 분석



조대협 (http://bcho.tistory.com)



데이타 스튜디오는 편리하게 사용할 수 있지만, 쿼리 사용등이 불가능하기 때문에, 원본 데이타를 이용한 상세 분석에는 어려움이 있다. 원본 데이타를 이용해서 상세 분석을 하려면 노트북 계열의 애플리케이션이 효과적인데, 빅쿼리를 연동할 수 있는 노트북으로는 이전에 소개한 주피터 노트북 기반의 데이타랩 (datalab)과, 스파크나 다른 빅데이타 솔루션과 함께 많이 사용되는 제플린 노트북(zeppelin.apache.org) 이 있다.


지난 글에서 데이타랩에 대한 연동 방법은 이미 소개하였으니, 이번에는 제플린을 통하여, 빅쿼리의 데이타를 분석해보도록 한다.


제플린 설치

제플린을 설치 하는 방법은 간단하다. Zeppelin.apache.org 에서, 설치 파일을 다운로드 받는다.

빅쿼리 연동 인터프리터는 제플린 버전 0.61 버전 이상에 포함되어 있기 때문에, 0.61 버전 이상을 다운로드 받는다.  이 때 모든 인터프리터가 포함된 버전을 다운 받아야 한다. (아니면 별도로 인터프리터를 설치해야 하는 번거로움이 따른다.)


다운 로드 받은 파일의 압축을 푼다. 다음으로 제플린 설치 디렉토리로 들어가서 다음 명령어를 수행한다.

% ./bin/zeppelin.sh

윈도우의 경우에는 %./bin/zeppelin.cmd 를 실행하면 된다.

자바 애플리케이션이기 때문에 별도의 설치 과정이 필요없고, 제플린 애플리케이션을 실행하기만 하면 된다.

제플린이 기동되었으면 브라우져에서 http://localhost:8080 으로 접속하면 다음과 같이 제플린 콘솔을 볼 수 있다.

노트북 생성

제플린 콘솔에 들어왔으면 초기화면에서 Create new note 라는 메뉴를 이용하여 새로운 노트북을 생성하자. 여기서는 편의상 “BQ 노트북" 이라는 이름으로 노트북을 생성하였다.


분석 쿼리 작성

이제 분석할 내용은 수집된 트윗의 명사들에 대해서, 시간 단위로 그룹핑을 한 다음에, 각 단어에 대해서 발생한 횟수를 카운트해서 보여주는 내용을 구현하려고 한다.

예를 들어서 9월20일에는 “유행" 이라는 단어가 200회 발생하였고, “패션" 이라는 단어가 100회 발생하였다. 라는 식으로 조회를 하려고 한다.


현재 테이블 구조는 다음과 같다.

Date (발생 시간)

Noun (명사)

count (발생 횟수)


SQL 문장을 작성해보자

select date,noun,sum(count) from 테이블명

group by date,noun


이렇게 쿼리를 하면, 시간대 별로, 명사와 그 명사의 발생 횟수를 리턴을 해주는데, 우리는 앞의 데이타 플로우 프로그램에서 30초 단위로 통계를 집계하도록 하였기 때문에, 30초 단위로 결과가 리턴된다. 우리가 원하는 결과는 30초 단위가 아니라 1시간 단위의 결과 이기 때문에, 다음과 같이 쿼리를 수정한다.


select  DATE(date) as ddate,HOUR(date) as dhour,noun,sum(count) from 테이블명

group by ddate,dhour,noun


DATE와 HOUR라는 함수를 사용하였는데, DATE는 타임 스탬프 형태의 컬럼에서 날짜만을 추출해주는 함수 이고, HOUR는 타임 스탬프 형태의 컬럼에서 시간만을 추출해주는 함수 이다.

이렇게 날짜와 시간만을 추출한 다음에, group by 절을 이용하여, 날짜와,시간 그리고 명사로 그룹핑을 하게 되면 우리가 원하는 것과 같이 각 날짜의 시간대별로 명사별 발생횟수 ( sum(count)) 값의 통계를 얻을 수 있다.


제플린에서 빅쿼리 명령을 수행하려면 다음과 같이 %bigquery.sql 이라고 첫줄에 선언을 한 다음에 SQL 문장을 수행하면 된다.




결과는 디폴트로 테이블 형태로 나오는데, 아래 아이콘 중에서 그래프 아이콘을 누르면 그래프 형태로 볼 수 가 있는데, 이 때 X,Y축의 변수를 지정할 수 있다.

아래 그림과 같이 Keys (X축을) ddate,dhour를 선택하고 Values(Y축)을 dhour SUM을 선택하면, 시간별 나타난 단어수를 볼 수 있다.



그런데 이 쿼리를 수행하면, 각 시간별로 발생한 명사 단어의 수가 매우 많기 때문에, 보기가 매우 어렵다.

그렇다면 시간대별로 발생한 명사중에서 각 시간대별로 많이 발생한 명사 5개씩만을 볼 수 없을까? 즉 group by를 전체 데이타 구간이 아니라, 각각 시간대 별로 계산을 해줄 수 는 없을까 하는 필요가 발생한다.

빅쿼리 파티셔닝

데이타를 구간 별로 나눠서 연산할 수 있는 기능으로 빅쿼리에는 파티션이라는 기능이 있다.

예를 들어서 group by를 전체 결과에 대해 그룹핑을 하는 것이 아니라, 앞에서 언급한 요건 처럼 일 단위로 짤라서 그룹핑을 하는 것이 가능하다.




파티션을 이용해서 할 수 있는 것은 파티션별로 합계나, 통계를 내거나, 파티션의 각 로우의 값의 백분율(%)나 또는 소팅한 순서등을 볼 수 있다. 여기서는, 시간으로 파티션을 나누고  파티션내에서 명사의 수가 많은 수 순서대로 소팅을 한후에, RANK라는 함수를 이용하여 그 파티션에서 그 명사가 몇번째로 많이 나타났는지를 출력하도록 해보겠다.


파티션의 사용법은 다음과 같다.

“파티션 함수 OVER (PARTITION BY 파티션을할 키 목록)”

여기서는 일/시간 별로 파티션을 나눈 후에, 그 순위별로 소팅을 할 것이기 때문에, 다음과 같은 식을 쓴다.

RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank


이를 쿼리에 적용하면 다음과 같다.

   SELECT

       DATE(date) as ddate,HOUR(date) as dhour

       ,noun

       ,sum(count) as ncount

       , RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank

   FROM [useful-hour-138023:twitter.noun]

   group by noun,ddate,dhour

   order by ddate,dhour,ncount desc


그러면 다음과 같이 일/날짜 파티션별로 많이 발생한 명사 순으로 발생횟수와 순위(rank)를 출력해준다.



그런데 쿼리를 돌려보면 알겠지만, 시간대별로 수집한 명사의 종류가 많기 때문에, 일자별 데이타가 매우 많다. 그래서 파티션별로 많이 등장하는 단어 5개만을 보려고 하면 rank <5 인것만 걸러내면 된다. 이는 중첩 쿼리를 이용해서 수행이 가능하다

다음은 이를 적용한 예이다.


SELECT ddate,dhour

   ,noun

   , rank

from (

   SELECT

       DATE(date) as ddate,HOUR(date) as dhour

       ,noun

       ,sum(count) as ncount

       , RANK() OVER (PARTITION BY ddate,dhour ORDER BY ncount DESC  ) as rank

   FROM [useful-hour-138023:twitter.noun]

   where noun != "note7" and noun != "samsung" and noun !="galaxy"

   group by noun,ddate,dhour

   order by ddate,dhour,ncount desc

   )

where rank < 6


이렇게 하면, 각 시간대별로 자주 등장하는 단어 6개만을 보여준다.


이 쿼리를 이용하여 데이타를 어떻게 분석하는지는 예전글 http://bcho.tistory.com/1136 을 참고하세요.


간단하게나마 트위터 피드에서 특정 키워드를 기반으로 하여, 명사와 형용사를 추출하여 소셜 반응을 분석하는 애플리케이션 개발과 데이타 분석 방법에 대해서 설명하였다.

아이폰7을 분석해보니, 명사 분석도 의미가 있었지만, 아이폰7에 대한 기대를 형용사 분석을 통해서도 많은 인사이트를 얻을 수 있었다. Awesome, excellent와 같은 기대치가 높은 형용사가 많이 검출되었고 bad, fuck 과 같은 부정적인 의미의 형용사는 다소 낮게 검출되었다. (아마 이즈음이 노트7 폭발로 인하여 반사 이익을 얻은게 아닐까 추정되는데.)


이외에도, 이모콘티만 추출하여 분석을 한다거나, 부사등을 통해서 분석을 하는 것도 가능하고, 구글 자연어 처리 API는 글을 통해서 사람의 감정을 분석해주는 기능도 있기 때문에 응용 분야는 훨씬 더 넓다고 볼 수 있다.


노트7의 소셜 반응을 분석해 보았다. 


#2 구현하기


조대협 (http://bcho.tistory.com)

지난번 글 http://bcho.tistory.com/1136에 이어서, 트위터를 통한 소셜 반응을 분석하는 시 스템을 구축하는 방법에 대해서 알아본다. 

시나리오 및 아키텍쳐

스트리밍 처리와 데이타 플로우에 대한 개념 이해가 끝났으면 이제 실제로 실시간 분석 애플리케이션을 만들어보자.

SNS를 이용한 마케팅 분석에서 대표적인 시나리오중 하나는 트위터 피드를 분석하여, 사람들의 반응을 분석하는 시나리오이다. 자주 언급 되는 단어나 형용사를 분석함으로써, 특정 제품이나 서비스에 대한 소셜 네트워크상의 바이럴 반응을 분석할 수 있는데, 여기서  구현하고자 하는 시나리오는 다음과 같다. 트위터 피드에서 특정 키워드로 트윗 문자열들을 수집한 후에, 구글의 자연어 분석 API를 통하여 트윗 문자열에서 명사와 형용사를 추출한다

추출한 명사와 형용사의 발생 횟수를 통계내어서 대쉬보드에 출력하는 시나리오이다.


이를 구현하기 위한 솔루션 아키텍쳐는 다음과 같다.


fluentd를 이용하여 트위터의 특정 키워드를 기반으로 트위터 피드를 수집하고, 수집된 피드들은 구글 클라우드의 큐 서비스인 Pub/Sub으로 전달된다. 전달된 데이타는 데이타 플로우에서 읽어서 필요한 데이타만 필터링한 후, 구글의 자연어 분석 API를 통해서 명사와 형용사를 분리한다.

분리된 명사와 형용사는 데이타플로우에서 30초 주기의 고정윈도우(Fixed Window) 단위로, 명사에서 발생한 단어의 수와, 형용사에서 발생한 단어의 수를 카운트 한 다음에, 빅쿼리에 명사 테이블과 형용사 테이블에 저장한다.

저장된 데이타는 구글의 리포팅 도구인 데이타 스튜디오를 통해서 그래프로 출력한다.


구현

그러면 위에서 설명한 아키텍쳐대로 시스템을 하나씩 구현해보자.

전체 예제 코드와 설정 파일은 https://github.com/bwcho75/googledataflow/tree/master/twitter 에서 받아볼 수 있다.

트위터 피드 수집 서버 설정

먼저 트위터에서 피드를 수집하기 위해서 fluentd 에이전트를 설정한다. 구글 컴퓨트 엔진에서 VM을 생성한 후에, 앞의 빅쿼리 예제에서 한것과 마찬가지로 fluentd 에이전트를 설치한다.

VM을 설치할때, 반드시 Cloud API access scopes를 full API access로 설정해야 하는데, 이 VM에서 fluentd를 통해서 수집한 피드를 Pub/Sub으로 전달할때, Pub/Sub API를 사용하기 때문이다.


Fluentd 가 설치되었으면 Pub/Sub으로 데이타를 전달하기 때문에,Fluentd pub/sub 에이전트를 추가설치 한다.

에이전트명은 “fluent-plugin-gcloud-pubsub”로

% sudo td-agent-gem install fluent-plugin-gcloud-pubsub

명령을 이용해서 설치한다.


에이전트 설치가 끝났으면 fluentd 에이전트 설정을 해야 한다.

다음은 트위터에서 “note7”에 관련된 피드를 읽어서 pub/sub 큐로 피드를 전송하는 fluentd 설정 예제이다.


<source>

 type twitter

 consumer_key        트위터 Consumer Key

 consumer_secret     트위터 Consumer Secrect

 oauth_token         트위터 Access Token

 oauth_token_secret  트위터 Access Token Secrect

 tag                 input.twitter.sampling  # Required

 timeline            sampling                # Required (tracking or sampling or location or userstream)

 keyword             note7

 output_format       nest                    # Optional (nest or flat or simple[default])

</source>

<match input.twitter.sampling>

 type gcloud_pubsub

 project 본인의 프로젝트명

 topic projects/본인의 프로젝트명/topics/twitter

 key 다운로드받은 구글 클라우드 억세스 토큰 JSON 파일

 flush_interval 10

 autocreate_topic false

</match>


Fluentd 설정이 끝났다.

Pub/Sub 큐 설정

다음으로는 fluentd 읽어드린 트위터 피드를 받아드를 Pub/Sub 큐를 생성한다.

큐 생성 방법에 대해서는 앞의 Pub/Sub 챕터를 참고하기 바란다. (http://bcho.tistory.com/1120)

큐 이름은 twitter라고 한다. 전체 큐 이름은 “projects/본인 프로젝트명/twitter” 가 된다.

데이타 플로우 프로젝트 생성

큐까지 데이타를 읽어드렸으면, 이 데이타를 처리할 데이타 플로우 파이프라인을 구현한다.

이클립스에서 데이타 플로우 파이프라인 프로젝트를 생성하자. 프로젝트 생성은 앞장의 “데이타 플로우 개발환경 설정" 부분을 참고하기 바란다. (http://bcho.tistory.com/1128)


프로젝트가 생성되었으면, 이 프로젝트에서 사용할 의존성 라이브러리들을 메이븐 (maven) 빌드 스크립트인 pom.xml에 추가해준다.

추가해야 하는 API는 JSON 파싱을 위한 javax.json-api와, javax.json 그리고 구글의 자연서 분석 API를 호출하기 위한 google-api-client와 google-api-service-language 모듈이다.


다음 코드 블럭을 <dependencies> 엘리먼트 아래 하부 엘리먼트로 추가해준다


   <dependency>

   <groupId>javax.json</groupId>

   <artifactId>javax.json-api</artifactId>

   <scope>provided</scope>

   <version>1.0</version>

</dependency>

<dependency>

   <groupId>org.glassfish</groupId>

   <artifactId>javax.json</artifactId>

   <version>1.0.4</version>

</dependency>

<!-- NL API dependency -->

<dependency>

     <groupId>com.google.apis</groupId>

     <artifactId>google-api-services-language</artifactId>

     <version>v1beta1-rev7-1.22.0</version>

   </dependency>

   <dependency>

     <groupId>com.google.api-client</groupId>

     <artifactId>google-api-client</artifactId>

     <version>1.22.0</version>

   </dependency>


데이타 플로우 코드 작성

전체 파이프라인 흐름

파이프라인 코드 작성에 앞서서 전체 파이프라인 흐름을 살펴보자

전체 흐름은 다음과 같다.


  1. Read From PubSub
    PubSub의 “twitter” 큐에서 JSON 형태의 트위터 메세지를 읽는다.

  2. Parse Twitter
    트위터 JSON 메세지를 파싱한 후, 전체 메세지에서 트윗 메세지를 저장하고 있는 “text” 필드와 언어셋을 정의하고 있는 “lang” 필드만 추출한다.
    자연어 분석 API가 아직 영어, 스페인어, 일본어만 지원하기 때문에, 이 예제에서는 영어로 트윗만 추출하도록 한다.

  3. NL Processing
    앞에서 추출한 트윗 메세지를 구글의 자연어 분석 API에 분석을 요청하여 명사와 형용사만 추출해낸다.

  4. 명사 처리 파이프라인
    다양한 처리 방식을 보여주기 위해서, 이 예제에서는 하나의 데이타 스트림을 분기 처리하여 두개의 데이타 파이프라인에서 처리하는 방식으로 구현하였다. 명사 처리 파이프라인은 다음과 같은 단계를 거친다.

    1. Noun Filter
      명사와 형용사 리스트로 들어온 데이타 중에서 명사만 필터링 한다.

    2. Window 적용
      고정 크기 윈도우 (Fixed Window) 30초를 적용하여, 30초 단위로 데이타를 분석하도록 한다.

    3. Count.PerElement
      명사 단어와, 각 단어별 발생횟 수를 30초 단위로 모아서 카운트 한다.

    4. Noun Formating
      카운트된 결과를 빅쿼리에 쓰도록, [윈도우 시작 시간,명사 단어, 발생횟수] 형태의 빅쿼리 ROW(행) 데이타 타입으로 포매팅 한다.

    5. Write Noun Count to BQ
      포매팅 된 데이타를 빅쿼리에 쓴다.

  5. 형용사 처리 파이프라인
    형용사를 처리하는 파이프라인도 내용은 명사를 처리한 파이프라인과 다르지 않고 동일하게 다음과 같은 순서를 따른다.

    1. Adj Filter

    2. Window 적용

    3. Count.PerElement

    4. Adj Formating

    5. Write Adj Count to BQ

빅쿼리 데이타 구조

빅쿼리에는 두개의 테이블에 데이타를 나눠서 저장하였다.

명사와 형용사 테이블로 각각의 테이블 명과 구조는 다음과 같다.


명사 테이블 : noun

필드명

데이타 타입

date

TIMESTAMP

noun

STRING

count

INTEGER


형용사 테이블 : adj

필드명

데이타 타입

date

TIMESTAMP

adj

STRING

count

INTEGER

자연어 분석 클래스 작성

전체 데이타 흐름과 저장 구조가 이해되었으면, 파이프라인 코드 작성에 앞서서 자연어 처리 API를 호출하는 로직을 만들어보자


우리가 사용할 API는 String으로 문자열을 주면 다음과 같이 NLAnalyzeVO 객체로 분석 결과를 리턴해주는 코드이다.


package com.terry.nl;


import java.util.ArrayList;

import java.util.List;


public class NLAnalyzeVO {

List<String> nouns = new ArrayList<String>();

List<String> adjs = new ArrayList<String>();

List<String> emoticons = new ArrayList<String>();

float sentimental;


public List<String> getNouns() {

return nouns;

}


public List<String> getAdjs() {

return adjs;

}


public List<String> getEmoticons() {

return emoticons;

}


public float getSentimental() {

return sentimental;

}


public void setSentimental(float sentimental) {

this.sentimental = sentimental;

}

public void addNouns(String n){

nouns.add(n);

}

public void addAdj(String a){

adjs.add(a);

}

public void addEmoticons(String e){

emoticons.add(e);

}

}

<NLAnalyzeVO.java>


분석 결과로는 List<String> 타입으로 명사들의 목록을 nouns 로, 형용사들의 목록을 adj로 리턴해준다. float형으로 sentimental 이라는 필드에는 입력된 문장의 감정도를 리턴하도록 되어 있다. 음수값일 때는 부정적, 양수값일 경우에는 긍정을 의미한다.

VO안에는 List<String> emoticons 라는 필드가 있는데, 이는 트위터 메세지 내의 이모티콘을 추출하여 저장하기 위한 필드인데, 이 예제에서는 사용하지 않으니 신경 쓰지 않아도 된다.


package com.terry.nl;


import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;

import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport;

import com.google.api.client.http.HttpRequest;

import com.google.api.client.http.HttpRequestInitializer;

import com.google.api.client.json.JsonFactory;

import com.google.api.client.json.jackson2.JacksonFactory;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPI;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPI.Documents.AnnotateText;

import com.google.api.services.language.v1beta1.CloudNaturalLanguageAPIScopes;

import com.google.api.services.language.v1beta1.model.AnalyzeEntitiesRequest;

import com.google.api.services.language.v1beta1.model.AnalyzeEntitiesResponse;

import com.google.api.services.language.v1beta1.model.AnalyzeSentimentRequest;

import com.google.api.services.language.v1beta1.model.AnalyzeSentimentResponse;

import com.google.api.services.language.v1beta1.model.AnnotateTextRequest;

import com.google.api.services.language.v1beta1.model.AnnotateTextResponse;

import com.google.api.services.language.v1beta1.model.Document;

import com.google.api.services.language.v1beta1.model.Entity;

import com.google.api.services.language.v1beta1.model.Features;

import com.google.api.services.language.v1beta1.model.Sentiment;

import com.google.api.services.language.v1beta1.model.Token;


import java.io.IOException;

import java.io.PrintStream;

import java.security.GeneralSecurityException;

import java.util.List;

import java.util.Map;


/**

*

* Google Cloud NL API wrapper

*/



@SuppressWarnings("serial")

public class NLAnalyze {


public static NLAnalyze getInstance() throws IOException,GeneralSecurityException {


return new NLAnalyze(getLanguageService());

}


public NLAnalyzeVO analyze(String text) throws IOException, GeneralSecurityException{

Sentiment  s = analyzeSentiment(text);

List <Token> tokens = analyzeSyntax(text);

NLAnalyzeVO vo = new NLAnalyzeVO();


for(Token token:tokens){

String tag = token.getPartOfSpeech().getTag();

String word = token.getText().getContent();


if(tag.equals("NOUN")) vo.addNouns(word);

else if(tag.equals("ADJ")) vo.addAdj(word);

}


vo.setSentimental(s.getPolarity());


return vo;

}



/**

* Be sure to specify the name of your application. If the application name is {@code null} or

* blank, the application will log a warning. Suggested format is "MyCompany-ProductName/1.0".

*/

private static final String APPLICATION_NAME = "Google-LanguagAPISample/1.0";


/**

* Connects to the Natural Language API using Application Default Credentials.

*/

public static CloudNaturalLanguageAPI getLanguageService()

throws IOException, GeneralSecurityException {

GoogleCredential credential =

GoogleCredential.getApplicationDefault().createScoped(CloudNaturalLanguageAPIScopes.all());

JsonFactory jsonFactory = JacksonFactory.getDefaultInstance();

return new CloudNaturalLanguageAPI.Builder(

GoogleNetHttpTransport.newTrustedTransport(),

jsonFactory, new HttpRequestInitializer() {

@Override

public void initialize(HttpRequest request) throws IOException {

credential.initialize(request);

}

})

.setApplicationName(APPLICATION_NAME)

.build();

}


private final CloudNaturalLanguageAPI languageApi;


/**

* Constructs a {@link Analyze} which connects to the Cloud Natural Language API.

*/

public NLAnalyze(CloudNaturalLanguageAPI languageApi) {

this.languageApi = languageApi;

}


public List<Token> analyzeSyntax(String text) throws IOException{

AnnotateTextRequest request =

new AnnotateTextRequest()

.setDocument(new Document().setContent(text).setType("PLAIN_TEXT"))

.setFeatures(new Features().setExtractSyntax(true))

.setEncodingType("UTF16");

AnnotateText analyze =

languageApi.documents().annotateText(request);


AnnotateTextResponse response = analyze.execute();


return response.getTokens();


}

/**

* Gets {@link Sentiment} from the string {@code text}.

*/

public Sentiment analyzeSentiment(String text) throws IOException {

AnalyzeSentimentRequest request =

new AnalyzeSentimentRequest()

.setDocument(new Document().setContent(text).setType("PLAIN_TEXT"));

CloudNaturalLanguageAPI.Documents.AnalyzeSentiment analyze =

languageApi.documents().analyzeSentiment(request);


AnalyzeSentimentResponse response = analyze.execute();

return response.getDocumentSentiment();

}


}


<NLAnalyze.java>


코드 상의 주요 부분을 살펴보자

public NLAnalyzeVO analyze(String text)

메서느가 주요 메서드로, 트윗 문자열을 text 인자로 넘겨주면 분석 결과를 NLAnalyzeVO로 리턴한다.

이 메서드 안에서는 두개의 메서드를 호출하는데, analyzeSentiment(text) 와, analyzeSyntax(text)

를 두개 호출한다.

analyzeSentiment(text) 메서드는 text 를 넣으면 float 타입으로 감정도인 Sentinetal 지수를 리턴한다.

analyzeSyntax(text)는 구문을 분석하여, 명사,형용사,접속사,조사 등과 단어간의 의존 관계등을 분석해서 리턴해주는데, Token 이라는 데이타 타입의 리스트 형태로 다음과 같이 리턴한다.

List <Token> tokens = analyzeSyntax(text);


여기서 단어의 형(명사,형용사)는 token에서 tag 라는 필드를 통해서 리턴되는데, 우리가 필요한것은 명사와 형용사만 필요하기 때문에, tag가 NOUN (명사)와 ADJ (형용사)로 된 단어만 추출해서 NLAnalyzeVO 객체에 넣어서 리턴한다. (태그의 종류는 https://cloud.google.com/natural-language/reference/rest/v1beta1/documents/annotateText#Tag ) 를 참고하기 바란다.


중요

이 코드를 이용해서 구글 클라우드의 자연어 분석 API를 호출할때 그러면 API 인증은 어떻게 할까? 보통 구글 클라우드 콘솔에서 다운 받는 서비스 어카운트 키 (Service Account Key) JSON 파일을 사용하는데, 구글 자연어 분석 API를 호출하기 위해서도 서비스 어카운트 키가 필요하다.

이 키를 콘솔에서 다운로드 받은 후에, GOOGLE_APPLICATION_CREDENTIALS 라는 환경 변수에 서비스 어카운트 키의 경로를 지정해주면 된다.


예) export GOOGLE_APPLICATION_CREDENTIALS=/path/to/your-project-credentials.json


자연어 분석 클래스를 다 만들었으면 테스트 코드를 만들어서 테스트를 해보자.

다음은 JUnit 4.X를 이용한 간단한 테스트 코드 이다.


package com.terry.nl.test;


import static org.junit.Assert.*;


import java.io.IOException;

import java.security.GeneralSecurityException;

import java.util.List;


import org.junit.Test;


import com.terry.nl.NLAnalyze;

import com.terry.nl.NLAnalyzeVO;


public class NLAnalyzeTest {


@Test

public void test() {

try {

NLAnalyze instance = NLAnalyze.getInstance();

String text="Larry Page, Google's co-founder, once described the 'perfect search engine' as something that 'understands exactly what you mean and gives you back exactly what you want.'";

NLAnalyzeVO vo = instance.analyze(text);

List<String> nouns = vo.getNouns();

List<String> adjs = vo.getAdjs();

System.out.println("### NOUNS");

for(String noun:nouns){

System.out.println(noun);

}

System.out.println("### ADJS");

for(String adj:adjs){

System.out.println(adj);

}

} catch (IOException e) {

// TODO Auto-generated catch block

e.printStackTrace();

fail("API call error");

} catch (GeneralSecurityException e) {

// TODO Auto-generated catch block

e.printStackTrace();

fail("Security exception");

}

}


}


"Larry Page, Google's co-founder, once described the 'perfect search engine' as something that 'understands exactly what you mean and gives you back exactly what you want.'" 문자열을 분석하여,  명사와 형용사를 추출하여 다음과 같이 결과를 출력해준다.

### NOUNS

Larry

Page

Google

co-founder

search

engine

something

### ADJS

perfect

파이프라인 코드 작성

이제 메인 파이프라인 개발을 위한 준비가 다 되었다. 이제 TwitterPipeline 이라는 이름으로 파이프라인을 구현해보자. 전체 코드는 다음과 같다.

package com.terry.dataflow;


import java.io.IOException;

import java.io.StringReader;

import java.security.GeneralSecurityException;

import java.util.ArrayList;

import java.util.List;


import javax.json.Json;

import javax.json.JsonObject;

import javax.json.JsonReader;


import org.joda.time.DateTime;

import org.joda.time.Duration;

import org.joda.time.Instant;

import org.joda.time.format.DateTimeFormat;

import org.joda.time.format.DateTimeFormatter;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;


import com.google.api.services.bigquery.model.TableFieldSchema;

import com.google.api.services.bigquery.model.TableRow;

import com.google.api.services.bigquery.model.TableSchema;

import com.google.cloud.dataflow.sdk.Pipeline;

import com.google.cloud.dataflow.sdk.io.BigQueryIO;

import com.google.cloud.dataflow.sdk.io.PubsubIO;

import com.google.cloud.dataflow.sdk.options.PipelineOptionsFactory;

import com.google.cloud.dataflow.sdk.transforms.Count;

import com.google.cloud.dataflow.sdk.transforms.Create;

import com.google.cloud.dataflow.sdk.transforms.DoFn;

import com.google.cloud.dataflow.sdk.transforms.ParDo;

import com.google.cloud.dataflow.sdk.transforms.ParDo.Bound;

import com.google.cloud.dataflow.sdk.transforms.windowing.FixedWindows;

import com.google.cloud.dataflow.sdk.transforms.windowing.IntervalWindow;

import com.google.cloud.dataflow.sdk.transforms.windowing.Window;

import com.google.cloud.dataflow.sdk.values.KV;

import com.terry.nl.NLAnalyze;

import com.terry.nl.NLAnalyzeVO;


import com.google.cloud.dataflow.sdk.values.PCollection;


/**

* A starter example for writing Google Cloud Dataflow programs.

*

* <p>The example takes two strings, converts them to their upper-case

* representation and logs them.

*

* <p>To run this starter example locally using DirectPipelineRunner, just

* execute it without any additional parameters from your favorite development

* environment.

*

* <p>To run this starter example using managed resource in Google Cloud

* Platform, you should specify the following command-line options:

*   --project=<YOUR_PROJECT_ID>

*   --stagingLocation=<STAGING_LOCATION_IN_CLOUD_STORAGE>

*   --runner=BlockingDataflowPipelineRunner

*/

public class TwitterPipeline {

private static final Logger LOG = LoggerFactory.getLogger(TwitterPipeline.class);

private static final String NOWN_TABLE=

"useful-hour-138023:twitter.noun";

private static final String ADJ_TABLE=

"useful-hour-138023:twitter.adj";


// Read Twitter feed as a JSON format

// extract twitt feed string and pass into next pipeline

static class ParseTwitterFeedDoFn extends DoFn<String,String>{


private static final long serialVersionUID = 3644510088969272245L;


@Override

public void processElement(ProcessContext c){

String text = null;

String lang = null;

try {

JsonReader reader = Json.createReader(new StringReader(c.element()));

JsonObject json = reader.readObject();

text = (String) json.getString("text");

lang = (String) json.getString("lang");


if(lang.equals("en")){

c.output(text.toLowerCase());

}


} catch (Exception e) {

LOG.debug("No text element");

LOG.debug("original message is :" + c.element());

}  

}

}


// Parse Twitter string into

// - list of nouns

// - list of adj

// - list of emoticon


static class NLAnalyticsDoFn extends DoFn<String,KV<String,Iterable<String>>>{ /**

*

*/

private static final long serialVersionUID = 3013780586389810713L;


// return list of NOUN,ADJ,Emoticon

@Override

public void processElement(ProcessContext c) throws IOException, GeneralSecurityException{

String text = (String)c.element();


NLAnalyze nl = NLAnalyze.getInstance();

NLAnalyzeVO vo = nl.analyze(text);


List<String> nouns = vo.getNouns();

List<String> adjs = vo.getAdjs();


KV<String,Iterable<String>> kv_noun=  KV.of("NOUN", (Iterable<String>)nouns);

KV<String,Iterable<String>> kv_adj =  KV.of("ADJ", (Iterable<String>)adjs);


c.output(kv_noun);

c.output(kv_adj);

}


}



static class NounFilter extends DoFn<KV<String,Iterable<String>>,String>{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey();

if(!key.equals("NOUN")) return;

List<String> values = (List<String>) c.element().getValue();

for(String value:values){

// Filtering #

if(value.equals("#")) continue;

else if(value.startsWith("http")) continue;

c.output(value);

}

}

}


static class AddTimeStampNoun extends DoFn<KV<String,Long>,TableRow>

implements com.google.cloud.dataflow.sdk.transforms.DoFn.RequiresWindowAccess

{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey(); // get Word

Long value = c.element().getValue();// get count of the word

IntervalWindow w = (IntervalWindow) c.window();

Instant s = w.start();

DateTime sTime = s.toDateTime(org.joda.time.DateTimeZone.forID("Asia/Seoul"));

DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

String str_stime = sTime.toString(dtf);


TableRow row =  new TableRow()

.set("date", str_stime)

.set("noun", key)

.set("count", value);


c.output(row);

}


}


static class AddTimeStampAdj extends DoFn<KV<String,Long>,TableRow>

implements com.google.cloud.dataflow.sdk.transforms.DoFn.RequiresWindowAccess

{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey(); // get Word

Long value = c.element().getValue();// get count of the word

IntervalWindow w = (IntervalWindow) c.window();

Instant s = w.start();

DateTime sTime = s.toDateTime(org.joda.time.DateTimeZone.forID("Asia/Seoul"));

DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

String str_stime = sTime.toString(dtf);


TableRow row =  new TableRow()

.set("date", str_stime)

.set("adj", key)

.set("count", value);


c.output(row);

}


}

static class AdjFilter extends DoFn<KV<String,Iterable<String>>,String>{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey();

if(!key.equals("ADJ")) return;

List<String> values = (List<String>) c.element().getValue();

for(String value:values){

c.output(value);

}

}

}


static class Echo extends DoFn<KV<String,Iterable<String>>,Void>{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey();

List<String> values = (List<String>) c.element().getValue();

for(String value:values){

}

}


}

public static void main(String[] args) {

Pipeline p = Pipeline.create(

PipelineOptionsFactory.fromArgs(args).withValidation().create());


@SuppressWarnings("unchecked")

PCollection <KV<String,Iterable<String>>> nlprocessed

=  (PCollection<KV<String,Iterable<String>>>) p.apply(PubsubIO.Read.named("ReadFromPubSub").topic("projects/useful-hour-138023/topics/twitter"))

.apply(ParDo.named("Parse Twitter").of(new ParseTwitterFeedDoFn()))

.apply(ParDo.named("NL Processing").of(new NLAnalyticsDoFn()));



// Noun handling sub-pipeline

List<TableFieldSchema> fields = new ArrayList<>();

fields.add(new TableFieldSchema().setName("date").setType("TIMESTAMP"));

fields.add(new TableFieldSchema().setName("noun").setType("STRING"));

fields.add(new TableFieldSchema().setName("count").setType("INTEGER"));

TableSchema schema = new TableSchema().setFields(fields);


nlprocessed.apply(ParDo.named("NounFilter").of(new NounFilter()))

.apply(Window.<String>into(FixedWindows.of(Duration.standardSeconds(30))))

.apply(Count.<String>perElement())

.apply(ParDo.named("Noun Formating").of(new AddTimeStampNoun()) )

.apply(BigQueryIO.Write

.named("Write Noun Count to BQ")

.to( NOWN_TABLE)

.withSchema(schema)

.withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND)

.withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED));


// Adj handling sub-pipeline

fields = new ArrayList<>();

fields.add(new TableFieldSchema().setName("date").setType("TIMESTAMP"));

fields.add(new TableFieldSchema().setName("adj").setType("STRING"));

fields.add(new TableFieldSchema().setName("count").setType("INTEGER"));

schema = new TableSchema().setFields(fields);


nlprocessed.apply(ParDo.named("AdjFilter").of(new AdjFilter()))

.apply(Window.<String>into(FixedWindows.of(Duration.standardSeconds(30))))

.apply(Count.<String>perElement())

.apply(ParDo.named("Adj Formating").of(new AddTimeStampAdj()) )

.apply(BigQueryIO.Write

.named("Write Adj Count to BQ")

.to( ADJ_TABLE)

.withSchema(schema)

.withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND)

.withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED));





p.run();

}


}

<TwitterPipeline.java>

코드를 하나씩 분석해보자.

먼저 main함수 부분을 보자

PCollection <KV<String,Iterable<String>>> nlprocessed

=  (PCollection<KV<String,Iterable<String>>>) p.apply(PubsubIO.Read.named("ReadFromPubSub").topic("projects/useful-hour-138023/topics/twitter"))

.apply(ParDo.named("Parse Twitter").of(new ParseTwitterFeedDoFn()))

.apply(ParDo.named("NL Processing").of(new NLAnalyticsDoFn()));

<TwitterPipeline.java에서 main() 함수 일부 >


파이프 라인이 시작되면, PubSubIO를 이용하여 “projects/useful-hour-138023/topics/twitter” 이름의 큐에서 데이타를 읽는다. 읽은 데이타는 ParseTwitterFeedDoFn() 라는 함수에서 파싱이 된다.

ParseTwitterFeedDoFn() 은 다음과 같다.


static class ParseTwitterFeedDoFn extends DoFn<String,String>{

private static final long serialVersionUID = 3644510088969272245L;


@Override

public void processElement(ProcessContext c){

String text = null;

String lang = null;

try {

JsonReader reader = Json.createReader(new StringReader(c.element()));

JsonObject json = reader.readObject();

text = (String) json.getString("text");

lang = (String) json.getString("lang");


if(lang.equals("en")){

c.output(text.toLowerCase());

}


} catch (Exception e) {

LOG.debug("No text element");

LOG.debug("original message is :" + c.element());

}  

}

}

<TwitterPipeline.java 에서 ParseTwitterFeedDoFn 클래스 구현부>


PubSub에서 읽어드린 데이타는 문자열로 안에 JSON 데이타를 가지고 있다. 이 JSON 문자열을 파싱해서 “text”와 “lang” 엘리먼트만 추출한 후에, “lang”이 “en”(영어) 인 경우에만 다음 파이프라인으로 “text”에서 추출한 문자열을 보내고, 영어가 아닌 경우에는 데이타를 무시한다.


다음은 NLAnalyticsDoFn에서 트윗 문자열을 받아서 자연어 분석을 한다.


static class NLAnalyticsDoFn extends DoFn<String,KV<String,Iterable<String>>>{

// return list of NOUN,ADJ,Emoticon

@Override

public void processElement(ProcessContext c) throws IOException, GeneralSecurityException{

String text = (String)c.element();


NLAnalyze nl = NLAnalyze.getInstance();

NLAnalyzeVO vo = nl.analyze(text);


List<String> nouns = vo.getNouns();

List<String> adjs = vo.getAdjs();


KV<String,Iterable<String>> kv_noun=  KV.of("NOUN", (Iterable<String>)nouns);

KV<String,Iterable<String>> kv_adj =  KV.of("ADJ", (Iterable<String>)adjs);


c.output(kv_noun);

c.output(kv_adj);

}


}

<TwitterPipeline.java 에서 NLAnalyticsDoFn 클래스 구현부>


앞에서 작성한 자연어 분석 클래스인 NLAnalyze 클래스를 이용하여 text를 넘기고, 리턴 값으로 NLAnalyzeVO를 리턴 값으로 받은 후, 명사는 KV<String,Iterable<String>> 타입으로 다음과 같을 저장해서 c.output을 이용해서 다음 파이프라인으로 넘기고

“NOUN”

명사1,명사2,명사3,...


마찬가지 방법으로 형용사도 같은 데이타 형인 KV<String,Iterable<String>> 타입으로 저장하여 다음 파이프라인으로 넘긴다.


이 데이타를 각각 명사와 형용사 두개의 처리 파이프라인으로 전달하는데, 두개의 파이프라인으로 단일 데이타를 보내는 방법은 다음과 같다.


nlprocessed.apply(ParDo.named("NounFilter").of(new NounFilter()))

: (중략)


nlprocessed.apply(ParDo.named("AdjFilter").of(new AdjFilter()))

: (중략)

nlprocessed는 PCollection 타입으로, NLAnalyticsDoFn에 의해서 처리된 결과이다.

이 결과 값에 두 개의 각각 다른 트랜스폼 (NounFilter와 AdjFilter)를 적용하였다.

이렇게 하나의 PCollection 값에 두 개의 트랜스폼을 각각 적용하면 적용된 각각의 파이프라인은 다른 파이프라인으로 아래 그림 처럼 분기 처리가 된다.


자아 그러면, 명사 처리 파이프라인 흐름을 따라가 보자

nlprocessed.apply(ParDo.named("NounFilter").of(new NounFilter()))

.apply(Window.<String>into(FixedWindows.of(Duration.standardSeconds(30))))

.apply(Count.<String>perElement())

.apply(ParDo.named("Noun Formating").of(new AddTimeStampNoun()) )

.apply(BigQueryIO.Write

.named("Write Noun Count to BQ")

.to( NOWN_TABLE)

.withSchema(schema)

.withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND)  .withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED));

< TwitterPipeline.java의 main() 함수 일부>


첫번째 NounFilter에서는 앞에 파이프라인에서 들어온 명사와 형용사중에서 명사만 필터링 해서 다음 파이프라인으로 전달한다.


static class NounFilter extends DoFn<KV<String,Iterable<String>>,String>{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey();

if(!key.equals("NOUN")) return;

List<String> values = (List<String>) c.element().getValue();

for(String value:values){

// Filtering #

if(value.equals("#")) continue;

else if(value.startsWith("http")) continue;

c.output(value);

}


}

}

<TwitterPipeline.java 에서 NounFilter 클래스 구현부>

명사 인지 형용사 인지는 앞에서 넘어오는 데이타 형이 KV<String, .. > 인데, 키 부분의 값이 “NOUN” 일 경우에 명사이기 때문에, 이 값이 아니면 무시한다. 명사인경우에도 종종 쓰레기 값이 들어오는데, 예를 들어 트위터 특성상 해쉬 태그등을 위해서 “#”이 사용되고, 링크를 위해서 “http…” 링크가 들어가기도 하는데 이는 명사가 아니기 때문에 이 내용은 모두 필터링해서 무시한다.


이렇게 정재된 데이타는 파이프라인의 다음 단계인 .apply(Window.<String>into(FixedWindows.of(Duration.standardSeconds(30)))) 를 통해서 30초 단위의 고정 윈도우가 적용되고, 다음  .apply(Count.<String>perElement()) 을 통해서 단어별로 그룹핑되서 카운트 되고 그 결과는 앞서 적용한 30초 윈도우 시간 단위로 다음 파이프 라인으로 전달된다.  전달되는 데이타의 모양은 대략 다음과 같다.

Key (String)

Value (Long)

airplane

100

boy

29

india

92


이렇게 전달된 데이타는 빅쿼리에 저장하기 위해서 빅쿼리의 ROW 데이타 타입은 TableRow로 변환한다.

.apply(ParDo.named("Noun Formating").of(new AddTimeStampNoun()) )

.apply(BigQueryIO.Write

.named("Write Noun Count to BQ")

.to( NOWN_TABLE)

.withSchema(schema)

.withWriteDisposition(BigQueryIO.Write.WriteDisposition.WRITE_APPEND)

.withCreateDisposition(BigQueryIO.Write.CreateDisposition.CREATE_IF_NEEDED));


<TwitterPipeline.java 에서 main() 함수중 >

AddTimeStampNoun()에서 이 작업을 수행하는데, 이 함수는 윈도우의 시간을 추출하여 data라는 필드에 추가해준다.  아래는 AddTimeStampNoun()  함수의 코드이다.


static class AddTimeStampNoun extends DoFn<KV<String,Long>,TableRow>

implements com.google.cloud.dataflow.sdk.transforms.DoFn.RequiresWindowAccess

{

@Override

public void processElement(ProcessContext c) {

String key = c.element().getKey(); // get Word

Long value = c.element().getValue();// get count of the word

IntervalWindow w = (IntervalWindow) c.window();

Instant s = w.start();

DateTime sTime = s.toDateTime(org.joda.time.DateTimeZone.forID("Asia/Seoul"));

DateTimeFormatter dtf = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");

String str_stime = sTime.toString(dtf);


TableRow row =  new TableRow()

.set("date", str_stime)

.set("noun", key)

.set("count", value);


c.output(row);

}

}

<TwitterPipeline.java 에서 AddTimeStampNoun 클래스 구현부>


여기서 주의할점은 윈도우에 대한 데이타를 접근하기 위해서는 com.google.cloud.dataflow.sdk.transforms.DoFn.RequiresWindowAccess 인터페이스를 implementation 해야 한다.  그후에, 현재 윈도우에 대한 정보는 ProcessContext c 변수에서 c.window() 함수를 이용하면 윈도우의 정보를 읽어올 수 있다. 이 코드에서는 윈도우 시작 시간을 IntervalWindow w의 w.start() 를 통해서 읽어왔고, 이를 빅쿼리의 TIMESTAMP  데이타 타입으로 넣기 위해서 “yyyy-MM-dd HH:mm:ss” 형태로 포매팅을 한후 TableRow라는 빅쿼리의 row형 데이타 타입으로 생성한 후, 다음 파이프라인으로 넘겼다.


다음 파이프라인은 BigQueryIO로 Write 명령을 이용해서 NOWN_TABLE 에 (String 값은 “noun”) 데이타를 쓰도록 하였고, 쓰기 모드는 붙여쓰기 WRITE_APPEND로 하고, 테이블은 없으면 생성하도록 CREATE_IF_NEEDED로 지정하였다. 이때 테이블의 스키마를 정의해줘야 하는데, 테이블 스키마는 withSchema(schema) 함수로 지정을 했는데, 스키마를 정의한  schema 변수는 다음과 같이 정의 되어 있다.


List<TableFieldSchema> fields = new ArrayList<>();

fields.add(new TableFieldSchema().setName("date").setType("TIMESTAMP"));

fields.add(new TableFieldSchema().setName("noun").setType("STRING"));

fields.add(new TableFieldSchema().setName("count").setType("INTEGER"));

TableSchema schema = new TableSchema().setFields(fields);

<TwitterPipeline.java 에서 main() 중의 “noun” 테이블 스키마 정의 부분>


같은 방식으로 형용사를 처리하는 파이프라인도 정의를 한다음 정의가 끝났으면

p.run();

을 이용하여 파이프라인이 실행되도록 한다.

실행하기

모든 코드 구현이 끝났다. 이제, 파이프라인을 기동해보자

이클립스에서 파이프라인을 구동하는데, Run Configuration 부분을 아래와 같이 설정한다.


Runner를  DataflowPipelineRunner를 선택한다. BlockingPipeRunner의 경우에는 파이프라인이 기동되는 동안 이클립스에 프로그램이 실행중인것으로 되서, 이클립스에서 파이프라인을 멈춰버리면 전체 파이프라인이 멈추기 때문에 적절하지 않다.

다음 Argument 탭에서 아래와 같이 Program Argument에  --streaming 옵션을 추가한다.


데이타 플로우는 배치 및 스트리밍 모드 두개가 있는데, 이 예제는 스트리밍 예제이기 때문에, --streaming을 명시적으로 지정한다.

구글 자연어 분석 API에 대한 인증을 위해서 서비스 어카운트 키 (JSON 파일의 경로)를 GOOGLE_APPLICATION_CREDENTIALS 환경 변수에 설정해야 하는데, Environment 탭에서 New를 누른 후, GOOGLE_APPLICATION_CREDENTIALS를 Name으로 하고 Value 부분에 서비스 어카운트 키 파일의 경로를 적어준다.



환경 설정이 끝났으면 아래 Run 버튼을 눌러서 파이프라인을 기동시킨다.

파이프라인을 기동 시키면 구글 클라우드로 소스를 배포하고 인스턴스를 구동하는데 까지 수분이 걸리기 때문에 잠시 기다린다.

기다리는 동안 배포 상태를 보기 위해서, 구글 클라우드 콘솔로 들어가면 아래와 같이 Status가 Running으로 바뀔때 까지 기다린다.



Running으로 바뀌고 나서도 1~2분 정도 준비가 필요하기 때문에 기다렸다가 해당 JOB을 확인해보면 다음과 같이 잡이 정상적으로 기동 되고 있음을 확인할 수 있다.



작업이 실행되었으면 이 파이프라인에 데이타를 넣어주기 위한 Fluentd 에이전트를 실행해보자. Fluentd를 설치한 VM에 들어가서,

%sudo /etc/init.d/td-agent restart

명령을 이용해서 fluentd 에이전트를 가동한다.


데이타가 들어오기 시작하면 다시 구글 클라우드 콘솔의 데이타 플로우 화면을 보면 (위의 그림)

상단에 LOGS 라는 버튼을 볼 수 있는데



이 버튼을 누르면 죄측 하단에 다음과 같이 Job Logs라는 윈도우가 나타난다.



여기서 오른쪽의 “WORKER LOGS” 라는 버튼을 누르면 이 파이프라인의 전체 로그를 볼 수 있는데, 에러가 없는지를 잘 확인 한다.



별도의 에러가 없다면 정상적으로 데이타가 수집된다고 할 수 있다.

그러면 데이타가 제대로 수집되는지를 확인해보자

빅쿼리 콘솔로 들어가서 select count(*) from [noun 테이블명] LIMIT 1000

을 수행해서 데이타가 제대로 들어오는지 확인해보자


위의 그림과 같이 f0_가 0 이상이면 데이타가 쌓이고 있다고 생각해도 된다.

데이타 시각화와 분석

데이타 스튜디오(Google datastudio) 를 이용한 데이타 분석

쌓여 있는 데이타를 실제로 분석해보자. 리포트를 이용해서 시각화를 할 예정인데, 여기서 사용한 리포팅 도구는 구글 데이타스튜디오 라는 리포트 도구이다. (http://datastudio.google.com) 으로 9월 현재는 미국 지역만을 대상으로 서비스가 되고 있고, 곧 한국에 서비스가 오픈될 예정이다.


우리가 만들려고 하는 리포트는 다음과 같은 모양을 갖는다

전체 기간동안 가장 많이 발생한 명사 10개와 그 발생 회수를 표로 출력해주고, 우측에는 전체기간이 아닌 일자별로 많이 발생한 명사 10개에 대한 발생 회수 및 그 변화 추이를 출력해준다.

다음 행에는 형용사에 대한 분석 결과를 출력해준다.



새로운 리포트 생성

데이타 스튜디오 메인 화면에 들어오면 작성한 리포트 목록들이 아래와 같이 출력된다.


여기서 + 버튼을 누르면 아래와 같이 새로운 리포트를 생성할 수 있다.


새로운 리포트 화면에 들어오면 우측 하단에 “CREATE NEW DATA SOURCE”라는 버튼이 나타나는데, 이를 통해서 빅쿼리 테이블을 불러올 수 있다. “CREATE NEW DATA SOURCE” 버튼을 눌러보자


데이타 소스 생성해서 빅쿼리 테이블을 불러와야하는데, 데이타 스튜디오는 아래 그림에서와 같이 빅쿼리 뿐만 아니라 구글의 MySQL 서비스인 CloudSQL에서 부터 일반 MySQL 까지 연결이 가능하기 때문에, 빅쿼리 뿐 아니라 일반 데이타 분석에서 분석된 데이타르르 MySQL을 통해서 리포트로 시각화할 수 있고,

Google Sheet에 있는 데이타를 불러와서 같이 표현할 수 있는 기능을 제공하는데, 이는 특히 비지니스나 영업쪽에서 작성한 Sheet의 데이타를 실시간으로 읽어다가 하나의 리포트에 표현할 수 있기 때문에 매우 유용하게 사용할 수 있다.

아울러 YouTube나 Google Analytics 그리고, Adwords 광고 플랫폼등 다양한 구글 플랫폼의 데이타를 읽어서 시각화할 수 있다.


연동 소스 중에서 빅쿼리를 선택한 다음 프로젝트와 데이타셋 그리고 연동하고자 하는 테이블을 선택한다. 여기서는 noun 테이블을 선택하였다. 그러면 다음과 같이 테이블 스키마가 나오고 ADD TO REPORT 버튼이 나온다.


ADD TO REPORT를 눌러서 리포트에 추가하자

다음 리포트 화면에서 다음과 같이 Table 버튼을 눌러서 테이블을 추가하자


테이블을 추가하면 우측에 테이블에 출력하고자 하는 데이타를 선택할 수 있다.


우측에 Data source는 아까 불러들인 “noun”테이블을 선택하고, Dimension은 noun을 선택하고, Metric은 count를 추가하면 명사(noun)별, 발생횟수 (count)를 출력해준다.

위의 표를 보면 note7 등의 단어가 나오는데, 당연히 note7에 대해 검색했기 때문에  note7 단어가 많이 나오겠지만 이는 분석에서 얻고자 하는 데이타가 아니기 때문에 이 note7 등의 불필요한 문자열은 필터링해서 없애버리도록 한다.

필터는 우측 하단에 “Fiter”라는 메뉴에서 추가가 가능한데


메뉴에서 “+Add a filter”를 선택한후


Exclude (제외한다) 라는 버튼을 선택한 후에, Dimension을 noun으로 Match type을 Equal to 로 Expression을 note7 으로 선택하면 noun 필드에서 값이 note7인 내용은 제외 하도록 하는 필터이다. 필터가 적용되면 note7 단어는 필터링되서 출력되지 않는다.


다음으로 꺽은선 그래프를 추가하기 위해서 화면에서 Time series 버튼으로 꺽은선 그래프를 추가한다.



꺽은선 그래프가 추가되면 그래프에 출력될 데이타를 Time series Properties에서 다음과 같이 설정한다.


Data source는 noun 테이블로 하고, Dimention을 Time Dimention은 date로 하고, 여러 필드를 같이 분석하기 위해서 Breakdown Dimension에 noun을 추가하면 하나의 명사가 아니라 주요 명사들을 출력해준다.

그리고 Metric은 count로 선택하면 각 명사별 카운트수를 일자별로 볼 수 있다.


같은 방법으로 형용사에 대한 그래프도 추가한다.


그래프가 완성된 후에, 데이타를 수집 및 분석해보니 꽤나 의미가 있는 분석 결과를 얻을 수 있었다.


다음글에서는 오픈소스 데이타 분석 도구인 제플린을 이용하여 상세 데이타 분석을 하는 방법에 대해서 알아보기로 한다.

이글에서 소개한 데이타 스튜디오는 아직 한국에서는 서비스가 제공되지 않기 때문에, 한국에서 사용하고자 하는 사람들에게는 다음글의 제플린 기반의 데이타 분석이 훨씬 더 유용하리라 생각된다.





구글 빅쿼리와 데이타 플로우를 이용한 노트7 소셜 반응 분석


조대협 (http://bcho.tistory.com)




이 글은 개인이 개인적인 취미와 빅데이타 분석 플랫폼 기술 공유를 위해서 데이타 분석용 시나리오로 소셜 트랜드 분석을 선택하였고, 그중 노트7을 하나의 예로 선택한 내용이기 때문에, 이러한 분석 내용이 악의적으로 활용되거나 해석되기를 바라지 않으며 이 글의 라이센스는 본인이 저작권을 소유하고 있으며 출처를 밝히는 인용을 포함하여 모든 인용 및 내용에 대한 활용을 금합니다.


구글의 빅데이타 플랫폼인 빅쿼리와(https://cloud.google.com/bigquery), 데이타플로우((https://cloud.google.com/dataflow) 에 대해서 테스트를 하던중, 아무래도 데이타 분석 애플리케이션을 만들어보려면 실제 시나리오를 가지고 분석을 해보는게 가장 적절할것 같아서, 트위터에서 노트7에 대한 데이타를 수집해서 분석해봤다.


전체적인 시나리오는 note7 으로 태깅된 트위터 피드를 읽어서 30초 단위로 실시간 분석을 하는데, 수집된 트윗에서 구글의 자연어 분석 API를 이용하여(https://cloud.google.com/natural-language/) 명사와 형용사만 필터링을 해서 수집하는 방식으로 진행하였다.


다음은 9/12~9/19일까지 수집한 데이타에 대한 통계이다.



가장 위의 파란선이 recall이라는 단어로 12일 부터 꾸준히 등장해서 16일에 피크를 치고 계속해서 내용이 나타나고 있다. 17일에 피크를 친 빨간선은 S7이라는 단어인데, 왜 노트7 트윗에 S7이 등장했는지는 약간 미지수이다. 일단위 보다는 시간단위 분석이 필요하고 각 일자별로 주요 지표를 상세하게 볼필요가 있다고 생각해서 제플린 노트북을 이용하여 빅쿼리에 수집된 데이타를 분석을 해봤다.


이 그래프는 시간대별 명사들의 카운트인데, 시간당 1500을 넘는 시점이 9/12 16:00, 9/12 23:00, 9/14 01:00 등으로 보인다. 일자별이나 모든 시간대별로 트윗을 분석하는 것보다, 몬가 이슈가 있으면 시간당 트윗이 급격하게 올라가기 때문에, 트윗에서 명사 카운트가 1500을 넘는 시간만 분석해봤다.




이것이 분석 결과인데, 그래프로 보기가 어려우니 표로 표현해서 보자. 주요 날짜별로 주요 키워드를 3개씩만 검출해보면 다음과 같은 결과가 나온다.


먼저 9월12일16시에는  flight와 india라는 단어가 많이 잡혔는데, 이날은 인도에서도 항공기에 노트7을 가지고 탑승을 못하도록 공지한 시간때로 보인다.


http://mashable.com/2016/09/12/samsung-galaxy-note7-flight-ban-india/#nTLbsAiVWqqr


다음 23시에 boy라는 단어가 주로 잡혔는데, 이날은 노트7으로 인하여 6살 어린이 어린이(boy)가 상처를 입은 사건이 발생한 때이다.


https://www.cnet.com/news/exploding-samsung-galaxy-phone-burns-6-year-old/


다음으로 14일과 16일 로그를 분석해보자



14일 1시에는 software, update, explosions라는 단어가 많이 검출되었는데,

http://money.cnn.com/2016/09/14/technology/samsung-galaxy-note-7-software-update-battery-fires/

소프트웨어 업데이트를 통해서 폭발을 막겠다고 발표한 시점이다.


16일은 미국 정부가 노트7의 리콜을 명령한 날로 5시와6시에 goverment와 recall 등의 단어가 집중적으로 올라왔다.


http://www.forbes.com/sites/shelbycarpenter/2016/09/16/government-official-recall-samsung-galaxy-note-7/#351a35c46e53



18일에는 report와 lawsuit (소송) 이라는 단어가 많이 검출되었는데, report는 찾아보니


http://bgr.com/2016/09/18/galaxy-note-7-explosion-burns-samsung-lawsuit/

로이터 통신에서 16일날 언급한 플로리다 남성이 노트7으로 화상을 입고 소송을 한 내용이 2일의 시차를 두고18일에 급격하게 퍼졌다.

그러다가 아래 표와 같이 19일에는 florida, man, pocket,lawsuit 와 같이 플로리다, 남성,주머니,소송 등의 단어가 검출되면서 18일의 내용이 점점 더 구체화 되어가는 과정을 보여주었다.

사실 노트7을 분석 아이템으로 삼은것은 노트7이 출시되었을때, 꽤나 완성도가 높았고 그래서 재미있는 반응도 꽤나 많을것으로 기대했다. 그래서 굳이 다른 키워드 보다 노트7을 고른거였고, 지금은 떠났지만 한때 몸 담았던 회사였기도 했기 때문에 잘 되기를 바라는 마음이었는데, 분석 결과를 지켜보면서 씁쓸한 마음이 이내 떠나지를 않았다.


이 분석을 통해서 얻고자 한것은, 이 시스템을 구축하는데 혼자서 5~6시간 정도의 시간밖에 걸리지 않았다. 예전이라면 이런 분석 시스템을 구축하려면 몇명은 몇달은 투자해야 할텐데, 이제는 혼자서도 이러한 빅데이타 분석이 가능하다는 메세지와 함께, 실시간 분석 시스템 구현 기술을 습득하기 위한 개인 작업이다.

의외로 데이타에서 많은 인사이트를 얻어낼 수 있었고 추후에 이 분석에 대한 모든 코드를 공개할 예정인데, 다른 사람들도 유용하게 사용할 수 있는 정보 공유의 목적이다.

.

 

트위터 모바일 SDK 서비스 패브릭에 대한 소개


조대협 (http://bcho.tistory.com)




트위터에서는 2014년 부터, 모바일 생태계 지원을 위해서 다양한 기능을 제공하는 Fabric 서비스를 제공하고 있다. 데이타 분석 및 리포팅, 트위터 연동등 다양한 기능을 제공하고 있는데, 대략적인 프로덕트들에 대한 기능과 특징을 살펴보고자 한다.



Crashlytics - Crash Reporting (https://fabric.io/kits/android/crashlytics)


모바일앱에 대한 크래쉬 내용에 대한 수집 및 분석 기능을 제공한다.  

특이한 사항으로는 크래쉬 분석 뿐만 아니라, 베타 사용자나 테스터들에게 앱을 배포할 수 있는 기능을 제공하고 베타 테스트 사항을 추적할 수 있는 기능을 제공한다.

근래에는 게임 개발 SDK인 Unity를 지원하는 기능이 추가 되었다.

 


Answers - Mobile Analytics Kit (https://fabric.io/kits/android/answers/summary) 


Google Analytics나 Yahoo의 Flurry와 비슷한 앱 모니터링/리포팅 서비스이다. Google Analytics와는 다르게 완전 무료이다. (데이타 Limit가 없다.)

단 타 서비스와 차이점은 복잡한 형태의 분석이 불가능하다 Cohort, Funnel 분석이나 User Path등 복잡한 분석은 불가하고 DAU,MAU,Session등 단순한 분석만 가능하다.


단순하기 때문에 지표 이해가 쉬운것이 장점으로 볼 수 있고, 또 다른 장점은 타 서비스에 비해서 리얼타임이라는 것이다. 대쉬보드의 수치는 20~30초 정도의 지연이 있는 수치로, 실시간 이벤트를 하거나 PUSH에 대한 반응을 바로바로 봐야할때나 TV CF후에 반응등 실시간 반응 분석이 필요할때 유용하게 사용할 수 있다.


정확한 분석을 위해서는 Fabric 하나로만은 불가능하겠지만 실시간성을 지원하는 점을 보면, Fabric + Flurry와 같이 두개의 솔루션을 조합해서 사용하는 것을 고려하는 것이 좋다.


Answers에서 특이한 기능중에 하나는, 트위터의 사용자 정보를 기반으로, Fabric Answer 를 통해서 모니터링 되는 사용자에 대한 특성 파악이 가능하다는 것이다. 트위터는 컨텐츠 및 여러가지 종류의 계정 (스포츠, 코메디 등등)을 운영하고 있기 때문에, 트위터는 트위터 사용자의 특성이 어떤지를 알 수 있고, 이 정보를 바탕으로 Fabric이 연동된 서비스의 각 사용자들의 특성을 파악해줄 수 있기 때문에, 서비스 운영 입장에서 사용자에 대한 인사이트를 제공할 수 있다. 




Digit Kit


Digit Kit는 SMS를 이용한 인증 서비스 이다. SMS를 통해서 인증 번호를 전송해서 본인 여부를 확인하는 서비스인데, 200여개의 국가를 지원하고 있고, 가장 중요한건 무료다!!. 글로벌 서비스를 제공 하는 경우 글로벌 SMS 서비스를 고려해야 하고, 또 그에 대한 금액도 만만하지 않은데, 하나의 서비스로 글로벌 커버를 비용 부담없이 제공하는 것은 활용을 고려해볼만하다고 보다. 향후  Email Verification 서비스도 함께 제공할 예정이다. 




Twitter Kit


Twitter Kit은 트위터 기능을 사용하기 위한 모바일 SDK이다. 특이한 점은 트위터로의 공유하는 GUI등을 SDK로 제공해서 어렵지 않게 트위터로의 공유 기능을 구현할 수 가 있다. 




Curator - Twitter contents curation service


트위터 컨텐츠를 모아서 큐레이션 (기존의 컨텐츠들을 2차 가공하여 새로운 컨텐츠를 만드는 것) 해주는 서비스로, 주로 미디어 서비스나 컨텐츠 공급자, 큐레이터에게 유리한 서비스로 Curator라는 저작툴을 이용하여, 큐레이션할 컨텐츠를 골라서 특정 주제에 해당하는 피드를 만들 수 있다. 아래는 서울의 첫눈이라는 주제로 트윗을 검색한 후에, 이를 골라서 콜랙션을 만드는 저작도구 화면이다. 



다음은 큐레이트된 컨텐츠를 임베딩하기 위해서 퍼블리슁 화면이다. 



트위터에 개발자당 오픈했습니다.
검색하셔서 "#개발자당_" 찾으시면 되구요.아니면 간편하게 http://bit.ly/a7xxk7 링크 클릭하셔도 됩니다.  IT 관련 소식들 같이 공유했으면 합니다.
많이 Follow 부탁드립니다. :)
IT관련 뉴스들 제가 매일 올리고 있으니.. 무한 RT부탁합니다.

트위터를 사용하면서.

아키텍쳐 /WEB 2.0 | 2009.11.11 22:54 | Posted by 조대협
트위터를 사용한지가 대략 한달이 좀 넘어가는것 같습니다.
사실 고객이 엔터프라이즈 마이크로 블로그 (기업용 트위터)를 사용하겠다는 요구가 있어서 리서치 하다보니까는 어찌어찌하다가 여기까지 왔네요.
오늘 600번째 트윗을 올리고, 현재 Follower가 95명입니다. 곧 100명 채우겠네요.

요즘 대부분의 정보는 트윗터를 통해서 얻습니다. 140자밖에 안되기 때문에 읽기도 부담없고 왠만해서는 클릭도 필요없습니다. 그리고 소위 말하는 입소문이기 때문에 정보 전파력도 빠릅니다. 많이 펌질 (RT되는) 트윗은 또 그만큼 정보 가치가 높은것을 의미하기 때문에, 정보에 대한 필터링 능력도 좋구요. 

오히려 메타블로그나 포탈 또는 전문 사이트 보다 최신 정보나 트렌드 파악하기에는 더욱더 좋은것 같습니다.

아.. 제 트윗아이디는 TerryCho입니다. 요즘 안드로이드와 구글의 세계정복 음모 소식에 대해서 가끔 트윗하고 있습니다. 서로 많은 정보 공유했으면 좋겠습니다. :)

TAG 트위터

기업에서 마이크로 블로그의 도입

지금까지 마이크로 블로그에 대해서 알아보았다. 그러면 이 마이크로 블로그 시스템을 기업에 어떻게 적용할 수 있을까

기업 내부 협업 플랫폼으로써의 마이크로 블로그

먼저 기업 내부의 협업 플랫폼으로써 마이크로 블로그를 도입한다면 어떤 기대 효과를 얻을 수 있을지 살펴본다

개인 브랜드 개발

트윗 메시지의 포스팅의 질은 개인의 브랜드와 직결된다. 전문성이 많은 포스트나 현재의 일 진행 상황을 자세하게 기록하면서 개인의 브랜드 가치를 향상 시킬 수 있으며, 특정화된 브랜드는 조직입장에서 업무의 효율성이 높은 직원을 선별해내고, 조직내에서 전문성을 가지고 있는 사람을 쉽게 찾을 수 있게 한다

리스크 조기 감지

마이크로 블로그 내의 RT Hash Tag를 분석함으로써 현재 회사내의 트렌드를 감지할 수 있으며, 이를 통해서 특정 Risk 요인이 있을 경우에 조기에 발견할 수 있는 일종의 사내 조기 경보기와 같은 역할이 가능하다

전문가와 네트워크 구축

실제로 기업의 규모가 커지면 커질수록 많은 비즈니스 부서가 존재하고, 협업이 필요할 때 누가 전문성을 가지고 있는지를 알아내기가 매우 어렵다. 이는 조직의 결재 구조를 따라서 인재를 요청하거나 또는 인맥을 통하여 추천을 받는 방식을 사용하는데,검증이 쉽지 않고 시간이 많이 걸리는 문제가 있다. 마이크로 블로그의 검색을 통해서 특정 분야에 관심이 있는 사람을 검색하고 그 사람의 트윗 메시지를 살펴봄으로써 필요한 전문성이 있는 사람을 쉽게 찾을 수 있고 네트워크를 구축하여 업무적으로 필요한 전문지식에 대해서 도움을 받을 수 있다

지식 공유

마이크로 블로그내에 링크로 저장되어 있는 많은 문서와 전문성을 가진 사람들이 추천하는 링크 그리고 RT된 링크들은 링크된 문서의 신뢰도를 나타내어 주며 기존의 검색엔진(구글과 같은)과 다른 형태의 지식 검색 및 공유 방법을 제공한다. 단순하게 텍스트나 내용의 일치가 아니라 Following하고 있는 사람의 신뢰도와, 많은 사람에 의해서 퍼 날라지거나 인용된 문서의 경우 신뢰도가 높다는 사람의 신뢰 중심의 지식 검색이 가능하게 된다

업무에 대한 컨텍스트 공유

프로젝트나 업무 그룹에 대해서 그룹 필터를 사용하여 진척 상황이나 이슈를 포스팅하여 구성원과 이해 당사자들이 해당 업무에 대한 진행현황(Context)에 대해서 이해할 수 있도록 한다. 현황과 상황은 과거에서부터 현재 어떻게 되고 있다는 Context를 나타내는 만큼, 당사자가 한두번의 브리핑이나 이메일로 현재 업무의 진행 상태를 파악하기가 어렵다. 마이크로 블로그를 통해서 업무에 대해서 계속해서 포스팅을 하면 포스팅 내용이 시간의 순서대로 연결이 되어 업무에 대한 Context의 의미를 가지게 된다

신뢰감 증대 및 관계 개선

마이크로 블로그를 통해서 임원과 같이 조직에서 일반적으로 접하기 쉽지 않은 사람을 Following 하는 행위 자체만으로도 심리적인 유대감을 형성할 수 있다. 거기에 더해서 해당 임원에게 보낸 답변에 대해서 응답이라도 받는 경우에는 구체적인 교감이 있는 것으로 인식이 되서 심리적으로 느끼는 거리가 줄어든다.

또한 마이크로 블로그의 트윗 포스트는 메일이나 게시판과 같이 정형화된 커뮤니케이션이 아니라 좀더 캐주얼하고 비업무적인 부분 (그날의 기분, 일상)도 다루기 때문에 업무적이 아니라 인간적인면 즉 감성적인 커뮤니케이션을 통해서 직원간의 거리를 줄일 수 있게 한다.

결과적으로 좀더 활발한 커뮤니케이션을 유도하여 조직의 구조에서 오는 커뮤니케이션의 장벽을 허물 수 있고 구성원간의 신뢰도를 높일 수 있다

수평적이고 오픈된 커뮤니케이션

기업의 커뮤니케이션 문화는 수직 계층적인 문화를 가지고 있다. 여러 계층을 통하다 보니 커뮤니케이션의 효율성은 떨어지고, 좋은 아이디어나 의견이 하층에서 상층까지 제대로 전달되지 않거나 경영조직의 메시지가 스팸으로 취급되어 버리는 경우가 많다. 마이크로 블로그에서는 조직간의 상하 관계가 없으며 이슈와 주제만으로 커뮤니케이션을 하기 때문에 수직 계층에서 오는 이러한 차이를 극복할 수 있게 하고, 조직의 구조화된 커뮤니케이션 구조를 수평/네트워크화된 형태의 커뮤니케이션 구조로 개선할 수 있다

Near Real Time형태의 커뮤니케이션 스타일

이메일과 게시판, 메모 시스템들을 대체하여 간단한 비동기적인 커뮤니케이션을 효율적으로 수행할 수 있으며 특히 모바일 디바이스 연동을 통해서 장소와 시간에 관계 없는 커뮤니케이션 플랫폼을 구축할 수 있다. 

기업 외부로의 마이크로 블로그

기업이 비즈니스를 하는데 외부 관점으로는 어떻게 마이크로 블로그를 사용할 수 있을까? 주로 대고객 서비스에 마이크로 블로그를 적용할 수 있고, 실제로 S전자,L전자와 같은 글로벌 국내 기업이나 아마존 같은 기업들은 이미 트위터를 통해서 마케팅 활동을 진행하고 있다.

마케팅

새로운 제품에 대한 정보 제공이나 이벤트 행사등에 사용할 수 있다.

고객 서비스

고객의 불만 접수, 고객 모니터링등에 이용 가능하다

본 글을 기업내의 마이크로 블로그 구축 전략을 중점으로 다루고 있기 때문에 기업 외부에 있는 마이크로 블로그를 이용하여 기업 활동을 증진 시키는 방안에 대해서는 자세하게 설명하지는 않는다.

 기업에서 마이크로 블로그 아키텍쳐


Figure 2 Micro blog architecture for enterprise

 사용자 인터페이스

마이크로 블로그는 직원에게 다양한 사용자 인터페이스를 제공하며 기업의 기존 시스템들과 통한된다.

(1)    웹 인터페이스

가장 일반적인 형태의 인터페이스로, 마이크로 블로그 시스템의 고유 웹인터페이스이다.

(2)    모바일 디바이스

이동이 가능한 핸드폰이나 PDA같은 모바일 디바이스로 마이크로 블로그를 서비스 한다. 단말의 종류 서비스 국가에 따라서 서비스 형태가 다르게 제공된다. 스마트 폰의 경우 애플리케이션 형태로 2G이하의 폰의 경우 SMS형태로 서비스가 제공된다.

(3)    포탈

기업의 엔터프라이즈 포탈이 있을 경우, 포탈을 통해서 마이크로 블로그 서비스를 포틀릿 형태로 서비스 한다.

(4)    IM (Instant Messenger)

기업내 메신져와 통합하여 메신져를 통해서 트윗 메시지를 포스팅하거나 반대로 메신져를 통해서 Following하는 대상의 메시지를 받을 수 있게 한다.

(5)    기타 엔터프라이즈 애플리케이션

엔터프라이즈 시스템의 Notification. 예를 들어 Work flow Approval Request등이 기존 이메일 시스템들을 마이크로 블로그로 대체할 수 있다

IDM (Identity Management System)

기업내부에 이미 사용중인 직원 정보 (LDAP 등에 적용된 조직도나 직원 프로필 서비스)시스템인IDM Single Sign On등을 통해서 계정이 통합되어야 한다.

모바일게이트웨이

모바일 디바이스를 지원하기 위해서 통신사의 통신망을 이용하기 위한 게이트웨이이다.

모바일 디바이스의 타입과 연동 방식에 따라 텍스트 기반인 경우 SMS를 멀티미디어 기반의 경우 MMS를 지원하고 스마트폰 기반의 애플리케이션의 경우 TCP/IP 망등을 지원해야 한다.

즉 모바일 디바이스에 따라 모바일 게이트웨이의 연동 방식이 변경될 수 있다.

또한 글로벌 기업의 경우 각 나라마다 텔레콤 회사가 다르기 때문에 사용자의 근무 위치에 따라서 다른 모바일 게이트웨이를 사용하도록 한다.

검색엔진 및 소셜분석 도구

앞서 기업 내부 플랫폼의로써의 마이크로 블로그에서도 언급했듯이 마이크로 블로그의 포스팅 내용은 정보성을 갖는다. 특히 기존의 정확도 기준의 검색 방식이 아니라 마이크로 블로그에 의해서 언급된 비율이나 내가 Following하고 있는 사람이 언급한 정보(같은 이슈를 공유할 가능성이 높기 때문에)는 검색의 정확성이 더 높기 때문에 검색의 방법역시 마이크로 블로그의 가치를 높일 수 있는 검색 시스템이 필요하다

또 마이크로 블로그의 포스팅 내용을 분석하면 리스크 상황이나 트렌드를 읽을 수 있기 때문에 소셜분석도구들을 이용하여 포스팅 내용을 분석하여 의미 있는 데이터로 가공할 수 있다

Policy & Compliance Rule

마이크로 블로그의 기업 도입은 정보 보안의 문제를 해결해야한다. 기업 내부로는 특정 그룹 구성원들간에만 커뮤니케이션이 필요한 경우가 있고 기업 밖으로 배포 되는 정보역시 기업의 보안 정책에 따라서 선별적으로 배포 되어야 한다. Policy & Compliance 컴포넌트는 보안 정책에 따라 이 두가지 부분을 커버한다.

(1)    Selective publishing

직원의 아이디를 연동하여 외부 마이크로 블로그(트위터)로 배포 하고자 할 때, 직원이 선별적으로 외부에 배포하는 글을 선택하거나 또는 시스템에서 보안 정책에 위배되는 키키워드 있을 때 이를 필터링하게 해주는 선별적 배포 기능이다.

(2)    Multilevel access & grouping

임원과 일반 직원 또는 부서(예를 들어 HR부서) 내부에서만 다루어야 하는 정보가 있기 떼문에 특정 그룹이나 조직 단위로 커뮤니케이션을 할 수 있게 한다

Micro blog (Public)

외부 마이크로 블로그 시스템으로, 기업 홍보와 같은 마케팅을 위해서 기업 내부의 포스팅 메세지가 기업 외부의 일반 고객이 사용하는 마이크로 블로그 시스템으로 메시지가 배포 되는 대상이다

기업에서 마이크로 블로그 도입 Challenge

 문화의 변경

마이크로 블로그의 도입은 단순한 IT 시스템의 도입이 아니다. IT 시스템은 정해진 프로세스에 직원들이 프로세스의 구성원으로써 주어진 역할을 수행하면 됬지만, 마이크로 블로그의 도입은 정형화된 프로세스에서 벗어나 직원에게 새로운 커뮤니케이션 및 정보 저장 도구를 주어줌으로써 창의력 발휘를 통해서 업무 생산성의 혁신을 불러오고자 하는데 있다.

 이를 위해서는 마이크로 블로그를 이용한 수평적 그리고 비정형적인 커뮤니케이션 스타일의 도입이라는 문화적인 변경이 필요하다. 이는 관리 지향적인 조직 입장에서 이해당사자들의 설득이 필요한 하나의 도전 과제이다.

 정보 보안

마이크로 블로그에 저장되는 내용들은 업무의 진행상태, 개인의 상태등에 대한 정보로 기업의 비밀에 해당하는 내용이다. 이러한 내용이 유출되지 않도록 보안을 유지해야 하며, 반대로 마이크로 블로그의 장점은 오픈된 환경에서 오는 참여에 있기 때문에 보안의 폐쇄성이 원래 마이크로 블로그의 특징을 해치지 않도록 해야 한다.

 또한 구축하는 국가에 따라서 법률에 접촉되는 여부를 검토해야 한다. 예를 들어 미국의 Sox 법에 의하면 사내 커뮤니케이션 내용은 최소 7년간 보장/유지 해야한다는 내용이 있기 때문에 포스팅 데이터에 대한 유지 역시 정보 보안 부분의 도전 과제에 해당한다.

 구축

마이크로 블로그 시스템이 기업에 도입되기 시작한 것은 근 1년 이내이다. 그렇기 때문에 이렇다할 기업용 마이크로 블로그 시스템이 많지도 않을뿐더러, 각 기업의 구축 요건을 충족시키지도 않고 기존 기업 시스템과의 통합성도 떨어지기 때문에 구축에 있어서 적절한 솔루션을 찾고 커스터마이징하는 것이 큰 과제중의 하나이다

구축 전략

앞서 마이크로 블로그의 활용 방법과 얻을 수 있는 장점 그리고 이를 위한 아키텍쳐에 대해서 설명하였다. 물론 아키텍쳐에 제안된 모든 부분을 처음부터 구축할 수 있다면 가장 좋겠지만, 모든 것이 구축되었다고 해서 모든 장점을 얻을 수 있는 것은 아니다.

 기업의 문화를 바꿔야 하는 일인 만큼, 기대했던 효과가 한꺼번에 나타나지 않을 수 있고, 기업의 업무 환경이나 시스템 환경이 변화할 수 있기 때문에 단계적으로 시스템을 구축하는 것을 권장한다

1단계 마이크로 블로그 시스템의 구축

 첫번째 단계에서는 가장 기본적인 마이크로 블로그 시스템을 구축한다. 필수적인 기능으로는 IDM 시스템 연동,모바일 인터페이스 제공이다. 이 두가지 기능만 가지고도 마이크로 블로그를 서비스할 수 있고 대부분의 장점을 시험할 수 있다

2단계 활성화

구축이 완료된 후에는 활성화 단계로 기업의 문화를 마이크로 블로그를 사용하는 형태로 바꿔 나가면서 마이크로 블로그를 이용한 커뮤니케이션을 활성화 한다. 이 단계가 지나면, 시스템에 추가적으로 필요한 기능이 선별된다

3단계 확장

시스템이 활성화 되면 기능을 확장한다. 이미지 포스팅, 동영상 포스팅과 같은 기능을 추가하여 마이크로 블로그 시스템의 활용성을 높이고, 검색엔진, 소셜분석기능등을 구축하여 활성화를 통해서 축적된 정보를 재가공하여 그 가치를 높인다

4단계 새로운 모델 구축 및 확장

4단계에서는 전통적인 마이크로 블로그 시스템의 한계를 벗어나서 기업 업무 형태에 특화된 자체적인 확장 모델을 개발한다. 현장 근무가 많은 영업 조직의 경우 LBS (Location Based Service:GPS등을 이용한 위치 정보 시스템)과 연계하여 Status 정보에 개인의 위치 정보를 표시하거나 위험한 현장 업무가 많은 건설/건축 업무의 경우 개인의 건강 상태나 재해 여부를 Status로 표시하여 근로자가 위험에 쳐했을 때, 구난 요청을 자동으로 보낼 수 있는 시나리오등이 이에 속한다.

Micro blogging strategy for Enterprise

Terry.Cho (Principal Consultant/Oracle Korea)

마이크로 블로깅의 대명사로 지칭되는 트위터는 2006년 서비스를 시작한 이후로 월간 순방문자수 1820만명(20095 QuickTake 발표 자료 기준)를 기록하며, 대표적인 SNS 서비스로 자리 잡았으며,  미국 대선, 이란 대선등의 굴직한 사회적 이슈에 커다란 영향력을 행사하고 있다. 본 글에서는 마이크로 블로깅이 웹 생태계에서 폭발적인 인기를 얻는 요인에 대해서 분석하여 기업내부에서 마이크로 블로깅을 도입하여 그 장점을 활용하는 접근 전략에 대해서 소개한다

마이크로 블로깅

정의

마이크로 블로깅은, 140자 내외로 자신의 상태나 감정(이하 트윗이라고 칭한다.) 을 표현하고 이러한 다른 사람의 트윗을 필터링을 통해서 구독하여 보는 형태의 서비스 이다.

필터링 메커니즘에메 여러가지 방식이 있지만 트위터와 같은 주요 마이크로 블로깅 서비스가 사용하는 방식은 “Follow”라는 방식으로 특정인의 트윗을 구독하여 보는 형태이다. 그외에도 블로그 태깅과 같은 원리의 “hashtag”나 특정 주제에 관련된 그룹을 형성함으로써 관련 트윗을 구독할 수 있는 “group”과 같은 필터링이 있다

마이크로 블로깅으로 무엇을 하는가?

그렇다면 사람들은 마이크로 블로그를 왜 사용할까? 성공 요인 분석에 앞서 사용자의 마이크로 블로그에 대한 가치를 분석해볼 필요가 있다. TNS The Conference Board가 내놓은 2009 2분기 “Consumer Internet Barometer” 리포트에 따르면 사용자의 41.6%가 친구들과 관계를 유지하기 위해서, 29.1%가 자신의 상태를 업데이트 하기 위해서 그리고 25.8%가 뉴스와 정보를 찾기 위해서 이다.


Figure 1.트위터 서비스를 사용하는 이유 (출처:Consumer Internet Barometer” from TNS and The Conference Board")

1,2위 항목을 정리해보면 다른 사람과의 관계 구축과 자신의 상태를 알림으로써 자신을 네트워크에 프로모션하려고 하는 동기가 약 70%에 도달한다. 즉 인간 관계의 구축과 관리 그리고 인간 관계간의 커뮤니케이션이 주된 사용 이유가 된다. 이성적인 관점보다는 감성적인 관점에서 마이크로 블로깅이 사용되고 있음을 알 수 있다.

그렇다면 마이크로 블로깅으로 사람들은 무엇을 하고 있을까? 자신의 상태와 인간 관계간의 커뮤니케이션이 주요 내용이지만 이런 목적으로 작성된 트윗 메시지들은 다른 가치를 가지게 된다.

먼저 내가 무엇을 하고 있고 어떤 생각을 하고 있다.’라는 내용은 다른 사람입장에서 또 다른 정보가 된다.

예를 들어 웹2.0에 관심이 있는 사람이 보고 있는 웹사이트 URL이나 문서 정보 책정보는 같은 정보가 필요한 다른 사용자에게 유용한 자료가 될 수 있다. 이런 자료는 HashTag를 통해서 검색될 수 있고, 그 자료를 다른 사람이 재사용할 수 있다.

또한 특정 공통 주제에 관심이 있는 사람을 같은 방식의 필터링을 이용해서 찾은 후에, 그 사람의 상태 히스토리 정보를 보면 그사람이 같은 주제에 어느정도 관심이 있는지를 알 수 있고, 그 사람과 트윗 대화를 통해서 관게(Relationship)를 구축하고 주제에 대해서 논의를 할 수 있다.

 마이크로 블로깅의 기간별 HashTag나 키워드를 분석해보면 현재 사람들의 관심사가 무엇이고 트랜드가 어디로 가는지도 알 수 있다.

 개개인의 현재 상태는 하나의 흐름이며 집단 정보이다.마이크로 블로깅의 원래 목적이 개인 상태 정보의 로깅과 커뮤니케이션이지만 이는 앞에서 언급한바와 같이 그 자체가 다른 형태의 정보로 활용될 수 있다.

 성공 비결

IM이나 이메일,전화와 같은 기존의 커뮤니케이션 도구들이 있음에도 불구하고 마이크로 블로깅이 급격하게 성장할 수 있는 이유는 무엇일까?

 사용이 쉽다.

먼저 사용 방법이 매우 쉽다. 이메일 처럼 이메일 클라이언트 오픈 à 주소 입력 à 제목 입력 à 내용 작성 à SEND 하는 일련의 절차 없이 로그인 à작성후 엔터면 바로 자신의 상태를 포스팅할 수 있다.

 언제 어디서나 사용이 가능

마이크로 블로깅이 확산된 주요 원인중의 하나는 모바일 디바이스를 이용한 장소와 시간으로부터의 자유이다. 사용자는 언제 어디서나 모바일 디바이스를 통해서 트윗을 포스팅할 수 있고 반대로 읽을 수 도 있다. 여기에 탄력을 더한 것이 스마트폰의 등장인데, 스마트폰을 통해서 웹애플리케이션 수준으로 마이크로 블로깅을 사용할 수 있게 되었고, 이메일이나 다른 IM에 비해서 비동기적인 커뮤니케이션을 지원하고 간단한 단문형 메시지만을 지원함으로 인해서 모바일 디바이스에 좀더 적절한 애플리케이션 형태를 띄고 있었던 것도 성공 요인이다.

 실시간성

마이크로 블로깅의 트윗 메시지는 140자 내외의 단문 메시지이다. 업데이트 하기도 쉽고 모바일 디바이스의 도움으로 언제어디서나 포스팅이 가능하다. 덕분에 자주 포스팅이 가능하고, 포스팅은 그때 그때 실시간으로 이루어진다. 그래서 포스팅된 정보는 거의 실시간성을 갖는다. 실시간적으로 이슈를 올릴 수 도 있고, 모바일 다바이스를 통해서 그 정보를 실시간으로 접할 수 도 있다.

 실제로 국내의 T 제품 발표회장에서 제품 발표에 대한 정보를 마이크로 블로그를 통해서 실시간으로 중계를 하는 사람들이 있었고, 현장의 내용은 기존의 언론이나 포탈보다 훨씬 더 빠르게 접할 수 있었다.

 Open API

마이크로 블로깅의 특징 중의 하나가, 기능을 Open API라는 형태로 외부로 서비스를 제공한다. 이를 통해서 마이크로 블로깅을 응용한 애플리케이션들이 개발되고 융합을 통해서 마이크로 블로깅의 가치를 증대 시키게 된다.

 실예로 마이크로 블로깅은 단문 텍스트 메시지 기반의 시스템이다. 여기에 링크 기능이 추가가 되는데, 이 링크를 통해서 이미지 업로드, 동영상 업로드등이 가능하게 된다. 스마트 폰에서 작성한 사진과 단문 메시지가 하나의 완성된 형태의 정보가 되고 여기에 마이크로 블로그의 신속성이 더해져서 정보의 가치가 높아진다.

 이란 대선 결과에 불복하는 시위에서 네다 솔탄양의 사망 소식과 동영상이 트위터를 통해서 급속하게 전세계에 퍼져나간 사례가 있는데 이 역시 Open API를 이용한 동영상 연동 기능이 있었기 때문에 가능하였다.

  Open API의 적용 사례로 TweetStats.com Hashtags.com은 트위터내의 RT Hash tag를 분석하여 현재 트위터내에서 어떤 내용들이 가장 이슈이고 어떤 사람들이 주목 받는지를 통계로 나타내는 분석 애플리케이션이다.

 이 처럼 마이크로 블로깅은 커뮤니케이션과 네트워크 관리를 통해서 생성된 개인의 상태 정보를 Open API라는 형태로 외부에 오픈함으로써, 다양한 연동을 통해서 그 가치를 증대시키고 있다.

 요약

지금까지 마이크로 블로깅의 성공 요인에 대해서 간략하게 살펴보았다. 기존의 커뮤니케이션 수단들과의 차이를 정리해보면 다음과 같다.

마이크로 블로깅은 기본적으로 불특정 다수를 대상으로하는 1:N 커뮤니케이션이다. Twit 이라는 말 뜻에서도 알 수 있듯이 마이크로블로깅은 네트워크로 연결된 개인의 재잘거림이다. 이러한 재잘 거림이 뭉쳐져서 하나의 흐름을 만들게 되고 네트워크를 걸쳐서 급격하게 전파 되면서 집단적인 지성으로써 움직이게 된다. 메일처럼 형식적이거나 복잡하지 않고 쉽기 때문에 자주 쉽게 업데이트 할 수 있으며  일상적인 회의나 대화처럼 공간이나 시간의 제약을 받지 않기 때문에 실시간성을 갖는다

 

Face2Face

Email

Messenger

Micro Blog

 

1:N

1:N

1:1

1:N(Anoymous)

Connectivity

Need to arrange

Easy to connect

Easy to connect

Easy to Connect

Sync

Sync

Async

Sync

Async (Near-Real Time)

Place

Limited

Limited place depends on device

Limited place depends on device

No Limit

Communication Range

Closed Group

Closed Group

Closed Group

Open to Anonymous

 

마이크로 블로깅 아키텍쳐

이번에는 기술적인 측면에서 마이크로 블로깅 시스템의 아키텍쳐에 대해서 설명한다. 마이크로 블로깅 시스템은 크게 5개의 컴포넌트로 구성된다.


(1)   인증 (Authentication)

마이크로 블로그 시스템에 로그인하기 위한 사용자 인증 기능을 수행한다.

마이크로 블로그는 Open API를 통해서 다른 시스템과 연동을 하는 경우가 있기 때문에 인증 시스템에서 다른 시스템과 인증을 연동하는 기능이나 Single Sign On 같은 기능을 지원한다.

예를 들어 트위터에 글을 올렸을 때, Face Book으로 자동 포스팅이 되게 하게 위해서는 트위터와 Face book간의 ID를 연결 또는 공유해야 하는데 이 역할은 인증 모듈에서 수행한다.

(2)   검색 (Search Engine)

사용자 찾기, Hash Tag 겁색, RT 검색을 수행한다.

(3)   트윗 메시지 관리 (Posting System)

포스팅된 메시지를 관리하고 보여준다.

(4)   링크 관리

마이크로 블로그는 140자 내외의 텍스트 한계를 가지고 있기 때문에 다른 웹사이트로의 링크를 긴 문자열로 그대로 표현하기 어렵다. 이 긴 링크 문자열을 짧게 해서 사용하는 것을 Shorten이라고 하는데, 단문 텍스트의 한계에 맞게 링크를 Shorten 해줄 수 있는 기능이 필요하다.

(5)   모바일 게이트웨이(Mobile Gateway)

모바일 디바이스와 연동할 수 있는 인터페이스가 필요하다. 폰의 종류에 따라서 SMS기반의 연동, 이미지를 첨부할 수 있는 MMS 기반의 전송 그리고 스마트 폰의 경우 마이크로 블로깅 애플리케이션을 탑재하는 방식등을 이용한 연동등 단말기에 따른 여러가지 연동 방식과 로컬 통신사에 맞춘 모바일 게이트웨이가 필요하다.

(6)   Open API

마이크로 블로깅에 트윗을 포스팅하는 기능, 현재 Following하고 있는 사용자의 트윗들을 읽는 기능, 사용자 인증 연동등의 주요 기능을 Open API형태로 Expose (밖으로 제공)하여 다른 애플리케이션과 연동할 수 있도록 한다.

마이크로 블로깅 시스템의 주요 컴포넌트는 많지 않다. 복잡도도 높지 않다. 디자인에 있어서 고려사항은 마이크로 블로깅은 작은 트윗 메시지가 수시로 발생하고 검색 빈도도 매우 높기 때문에  아키텍쳐 Principal(기본)이 성능과 확장성에 맞춰져야 한다.

다음글에서는 기업에서 마이크로 블로깅을 도입하기 위한 전략에 대해서 소개한다.

경축 허본과 트위터 입성!!

사는 이야기 | 2009.07.31 10:02 | Posted by 조대협
지난 대선 후보였던 허경영 선생이 출소후에 트위터에 입성하였습니다.


역쉬 쵝오입니다. 덕분에 아침에 웃을 수 있는 시간이 생겼습니다.
허본좌 어록..

"출소 전에는 유체이탈을 통해서 많은 유명 인사들과 만났지만 이렇게 국민들과 만나보지 못해서 죄송하게 생각합니다. 정치인들이 이용은 하고 있지만 소통은 부족한 정치인이 많은 것 같습니다. 제가 이곳에서라도 이야기 할 수 있어서 감사하게 생각합니다."

"지금 보좌관이 없어서 그러는데 제 아이디 밑에 unhky 누르면 저한테 메세지 보낸 분들이 보이는데 제가 보내려면 어떻게 해야 합니까?"




===
가짜라네요. :(