2020년 5월 31일 일요일

Brand Positioning Map 개발: Word2vec 활용 방법(코드)와 예제


               
목적:
Word Embedding을 이용하여 브랜드 포지셔닝 맵을 작성할 수 있습니다. 본 자료를 통해 word2vec을 마케팅에서 어떻게 활용할 수 있는 지를 이해시키기 위해 작성하였습니다.   

제대로 된 브랜드 포지셔닝 맵의 개발을 현재 진행중이며, 개발되면 이 사이트를 통해 다시 공개하도록 하겠습니다.

하지만, word2vec을 여러 상황에서 사용해 본 결과, word2vec 단순 적용만으로는 만족할 결과를 만들 수 없다는 판단을 하게 되었습니다.(아래 결과 보시면 알겠지만.) 

사용 함수: word2vecPlot()

사용자가 입력한 조건에 따라 word2vec 모델을 생성하고, 단어 간의 관련도(similarity)를 시각화 하는 함수. 여기서는 2가지 용도로 사용.

  1. 특정 키워드 제공시, 해당 키워드와 관련도가 높은 상위 n개 단어를 표시 (최대 10개)
  2. 브랜드 간의 관련도 표시.
    (최대 7개 브랜드: 스타벅스, 투썸플레이스, 빽다방, 폴바셋, 블루보틀, 공차, 이디야)

 

함수 인자: 최대 4가지 인자

dim = 단어를 몇 차원의 벡터로 임베딩 할 것인지 입력

fit_into = dim>3인 경우, 시각화를 위해 몇 차원으로 줄일 것인지 입력 (2 또는 3차원 PCA)

keyword = 해당 키워드와 가장 관련도 높은 상위 10개 단어 벡터를 시각화

count = 그래프에 표시할 단어 개수 입력

 

이 중 dim은 필수로 할당해야 하고, fit_into는 dim을 3보다 크게 주었을 때 2혹은 3으로 할당해야 하며, keyword는 원하는 경우 할당합니다. (키워드를 주지 않으면 자동으로 카페 브랜드 7곳 간의 관련도를 시각화 합니다.) count도 원하는 경우 할당합니다. (카운트를 설정하지 않으면, A그래프의 경우 10 단어, B그래프의 경우 7 단어(카페)로 자동 설정합니다.)

 

예시 코드 (작성: JLab 연구원 김지우)


def word2vecPlot(**info):

    dim = info['dim']

    try:

        fit_into = info['fit_into']

    except KeyError:

        fit_into = 0

    try:

        keyword = info['keyword']

    except KeyError:

        keyword = ''

    try:

        count = info['count']

    except KeyError:

        if keyword: count = 10

        else: count = 7

       

    print('Embedding.....')

    from gensim.models import Word2Vec

    model = Word2Vec(token, size=dim, window=6, min_count=30, workers=4, sg=1)

    # token은 [ [문장1], [문장2], … ], 즉 리스트를 원소로 갖는 리스트입니다.

    # 이때 각 문장은 불용어 처리/형태소 분석을 거친 상태입니다. 코드는 하단에 첨부합니다.

    model.init_sims(replace=True)

    word_vectors = model.wv

    vocabs = [key for key in word_vectors.vocab.keys()]

    word_vectors_list = [word_vectors[v] for v in vocabs]
 

    if dim > 3:

        # 차원이 3을 초과하면, 후에 시각화를 위해 fit_into에 입력한 저차원으로 축소합니다.

        print('Running PCA.....')

        from sklearn.decomposition import PCA

        pca = PCA(n_components=fit_into)

        if fit_into:

            # 그리고 차원을 축소해서 나온 x축값, y축값, z축값을 각각 x, y, z 변수에 저장합니다.

            if fit_into == 2:

                xy = pca.fit_transform(word_vectors_list)

                x = xy[:, 0]

                y = xy[:, 1]

            elif fit_into == 3:

                xyz = pca.fit_transform(word_vectors_list)

                x = xyz[:, 0]

                y = xyz[:, 1]

                z = xyz[:, 2]

            else:

                print('2차원 혹은 3차원으로만 축소할 수 있습니다.')

        else:

            print('축소할 차원을 입력해주세요.')       

    else:

        # dim이 2 또는 3이라면 word2vec 단계에서 생성한 벡터를 그대로 x, y, z에 저장합니다.

        x = [word_vectors[v][0] for v in vocabs]

        y = [word_vectors[v][1] for v in vocabs]

        if dim == 3:

            z = [word_vectors[v][2] for v in vocabs]

 

    print('Drawing plot.....')

    # 시각화는 두 가지가 있습니다.

    plt.figure(figsize=(10,9))

    if keyword:

        # keyword를 준 경우라면, A그래프를 그립니다(특정 키워드 연관어).

        if keyword == '스타벅스': find = '벅스'

        elif keyword == '투썸플레이스': find = '투썸'

        elif keyword == '빽다방': find = '다방'

        elif keyword == '블루보틀': find = '루보'

        else: find = keyword

        # 형태소 분석기에 따라 브랜드명을 하나의 고유명사로 인식하지 못하고 위와 같이 쪼개는 문제가 있어서, name 변수를 지정해 그래프 상에는 올바른 이름이 나타나도록 했습니다.

        try:

            i = vocabs.index(find)

        except:

            print('%s가 단어 목록에 없습니다.' %keyword)

        if (dim == 2) or (fit_into == 2):

            plt.scatter(x[i], y[i], marker='o')

            plt.annotate(keyword, xy=(x[i], y[i]), fontproperties=fontprop)

            for similar_word in word_vectors.most_similar(keyword)[:count]:

                iw = vocabs.index(similar_word[0])

                plt.scatter(x[iw], y[iw], marker='o')

                plt.annotate(similar_word[0], xy=(x[iw], y[iw]), fontproperties=fontprop)

        elif (dim == 3) or (fit_into == 3):

            ax = plt.subplot(111, projection='3d')

            ax.scatter(x[i], y[i], z[i], marker='o')

            ax.text(x[i], y[i], z[i], keyword, fontproperties=fontprop)

            for similar_word in word_vectors.most_similar(keyword)[:count]:

                iw = vocabs.index(similar_word[0])

                ax.scatter(x[iw], y[iw], z[iw], marker='o')

                ax.text(x[iw], y[iw], z[iw], similar_word[0], fontproperties=fontprop)

    else:

        # 키워드를 주지 않았다면, B그래프를 그립니다(브랜드 간 연관도).

        if (dim == 3) or (fit_into == 3):

            ax = plt.subplot(111, projection='3d')

        if count != 7:

            print('스타벅스, 투썸플레이스, 빽다방, 폴바셋, 블루보틀, 공차, 이디야 중 관련도를 알고 싶은 카페를 쉼표(,)로 구분해 입력하세요.')

            cafes = input('카페 이름 입력: ')

            try:

                cafes = cafes.split(',')

            except:

                print('공백 없이 쉼표로만 구분해 입력하세요.')

        else:

            cafes = ['스타벅스','투썸플레이스','빽다방','폴바셋','블루보틀','공차','이디야']

        for cafe in cafes:

            if cafe == '스타벅스': find = '벅스'

            elif cafe == '투썸플레이스': find = '투썸'

            elif cafe == '빽다방': find = '다방'

            elif cafe == '블루보틀': find = '루보'

            else: find = cafe

            try:

                i = vocabs.index(find)

            except:

                print('%s이/가 단어 목록에 없습니다.' %cafe)

            if (dim == 2) or (fit_into == 2):

                plt.scatter(x[i], y[i], marker='o')

                plt.annotate(cafe, xy=(x[i], y[i]), fontproperties=fontprop)

            elif (dim == 3) or (fit_into == 3):

                ax.scatter(x[i], y[i], z[i], marker='o')

                ax.text(x[i], y[i], z[i], cafe, fontproperties=fontprop)

  

Output: 브랜드 3D 포지셔닝 맵 (10차원 vector화 후, 그 결과 3차원에 시각화 처리한 경우)


word2vecPlot(dim=10, fit_into=3)



Output: 특정 브랜드 관련 주요 이미지 시각화. 

특정 브랜드(예 폴바셋)와 관련해 관련이 높은 단어 시각화 

 

word2vecPlot(dim=10, fit_into=2, keyword='폴바셋')


 

 

 

(별첨) token화 작업 예

 

# 사용한 파일은 카페통합(complete)_revise.csv 입니다.

import pandas as pd

df_cafe = pd.read_csv('카페통합 정제 텍스트들/카페통합(complete)_revise.csv', encoding='utf-8')

df_cafe = df_cafe[['date', 'contents']]

df_cafe = pd.concat([df_cafe[:10000], df_cafe[700000:710000], df_cafe[800000:810000], df_cafe[840000:850000], df_cafe[900000:910000], df_cafe[1000000:1010000], df_cafe[1200000:1210000]])

print(df_cafe.shape)

df_cafe.head()

 

# 불용어 처리에 사용한 파일은 현재 Korean_stopwords.txt 입니다.

stopwords = []

with open('Korean_stopwords.txt', 'r', encoding='utf-8') as f:

    while(1):

        line = f.readline()

        try:

            escape = line.index('\n')

        except:

            escape = len(line)

        if line:

            stopwords.append(line[0:escape])

        else:

            break

 

# 토큰화엔 형태소 분석기 중 속도와 성능이 괜찮은 Mecab을 이용했습니다.

# 이 외에 okt, komoran, kkma, 한나눔, khaiii 등이 있습니다.

%%time

from konlpy.tag import Mecab

mecab = Mecab()

token = []

for sentence in df_cafe['contents']:

    temp = mecab.pos(str(sentence))

    # Mecab 결과: 스타/벅스, 투썸/플레이스, 빽/다방, 폴바셋, 블/루보/틀, 공차, 이디야

    # 튜플이어서 이 단계에서는 단어 변경이 불가합니다.

    clean = []

    for word in temp:

        if word[0] not in stopwords:

            if word[0] not in ['스타', '플레이스', '블', '틀', ]:

                if word[1] in ['NNG', 'NNP', 'VV', 'VA', 'MM']:

                    # 일반명사, 고유명사, 동사, 형용사, 관형사

                    clean.append(word[0])

    token.append(clean)


Share this post: