検索キーワードを python で KMeansクラスタリングする。


Google Search Console の キーワードを python で クラスタリングしてカテゴリ分けしてみた結果を記載します。
正直、ものすごくいい感じにできたとは思えないですが、
カテゴリ分けの足がかりの部分はできた感はあります。1
[1].実務に役立つかは謎です。。


動機

以前、Google Spread Sheet の 複数のシートのデータをスクリプトで統合(マージ)する で、

  1. Search Analytics for Sheets - Google スプレッドシート アドオン で、データをExport

  2. 1. でバックアップした月別シートをマージ

  3. 2. でマージしたシートを、Google データスタジオ で、グラフ表示。

するところまで実施しました。
これでざっくり分析はできるようになったので、そのデータにキーワードをクラスタリングした結果を追加してみようと思いました。


クラスタリングの手順

以下の通り、処理を組みました。
クラスタリングには、sklearnKMeans を使います。

クラスタ数の決定

クラスタ数の決定のため、エルボー法を用います。
実装は、k-meansの最適なクラスター数を調べる方法 - Qiita を参考にしました。

  1. Google スプレッドシートをCSV にして読み込み、キーワードを抽出。

  2. キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。

  3. エルボー法で、最適なクラスタ数を決定する。

クラスタリング

  1. Google スプレッドシートをCSV にして読み込み、キーワードを抽出。

  2. キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。

  3. sklearnKMeans でクラスタリング

  4. 各クラスターでもっとも頻出する単語をクラスターのラベル名として設定、結果をCSVに書き出す。

  5. Google スプレッドシートにインポートし、グラフ表示

各種ライブラリのインストール

必要なライブラリをインストールします。2
[2].以下で全部インストールできます.

pip install sklearn numpy scipy pandas matplotlib


実装

以下、作ったpython プログラムになります。
クラスタ数の決定 と、 クラスタリング を行う2つを作成しました。

クラスタ数の決定 (plot_elbow.py)

以下、記事を参考に作成しました。
scikit-learnを使ってテキストから素性ベクトルを取得する - もふもふ技術部
k-meansの最適なクラスター数を調べる方法 - Qiita
2つをコピーアンドペーストしマージをした雰囲気になります。

  • plot_elbow.py
    # -*- coding: utf-8 -
    from __future__ import print_function
    
    import matplotlib.pyplot as plt
    import numpy as np
    import pandas as pd
    from __builtin__ import xrange
    from sets import Set
    from sklearn.cluster import KMeans
    from sklearn.feature_extraction.text import CountVectorizer
    
    stop_words = Set(['name', 'not', 'the', 'usr', 'you', 'version', 'this'])
    
    # stop word のチェック
    # 2文字以下の文字列、クラスタリングした結果、
    # ラベルとして、出力されたあまり意味のわからない単語を除外
    def check_stop_word(word):
        if word in stop_words:
            return False
        if len(word) <= 2:
            return False
        return True
    
    
    # キーワードを区切る
    def split_keyword(text):
        keywords = text.split(" ")
        after_text = " ".join([keyword for keyword in keywords if check_stop_word(keyword)])
        return after_text
    
    
    # report csv を parse する
    def parse_report_csv():
        lines = []
        row_count = 1
        for line in open('Google_Search_Console.csv', 'r'):
            if row_count != 1:
                arr = line.split(",")
                # キーワードカラムを取り出す
                lines.append(arr[1])
            row_count += 1
        return lines
    
    
    # メインメソッド
    def execute():
        keywords = []
        for line in parse_report_csv():
            keywords.append(split_keyword(line))
    
        # テキスト内の単語の出現頻度を数えて、結果を素性ベクトル化する(Bag of words)
        count_vectorizer = CountVectorizer()
        # csr_matrix(疎行列)が返る
        feature_vectors = count_vectorizer.fit_transform(keywords)
        # エルボー法
        distortions = []
        # クラスタ数は100
        n_clusters = 100
        for i in range(1, n_clusters):
            km = KMeans(n_clusters=i,
                        init='k-means++',  # k-means++法によりクラスタ中心を選択
                        n_init=10,
                        max_iter=300,
                        random_state=0)
            km.fit(feature_vectors)  # クラスタリングの計算を実行
            distortions.append(km.inertia_)  # km.fitするとkm.inertia_が得られる
        plt.plot(range(1, n_clusters), distortions, marker='o')
        plt.xlabel('Number of clusters')
        plt.ylabel('Distortion')
        plt.show()
    
    
    if __name__ == '__main__':
        execute()
    

説明

以下、説明を記載します。


1. Google スプレッドシートをCSV にして読み込み、キーワードを抽出。

csv を read して、キーワード項目のみを抽出しています。項目は、Search Analytics for Sheets - Google スプレッドシート アドオン で取り込んだ内容をCSV化して、取り込み実施しました。


2. キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。

最初mecabで形態素解析し、名詞、動詞、形容詞を抜き出していたのですが、あまりいい感じにならず、単純なスペース区切りでキーワードを分割し、
短すぎるワード(2文字以下)を除外して、結果を素性ベクトル化しました。


3. エルボー法で、最適なクラスタ数を決定する。

クラスタ数100までで設定して、グラフ描画するようにしました。これはあまり小さい値だとエルボーにならなかったためです。
データ量は、3000件ほどですが、それなりに時間はかかります。(5分程度)


エルボ〜図

以下、出力されたエルボー図になります。
25-26 あたりが肘のような気がしたので、それを後続でクラスタ数として設定します。
elbow

クラスタリング (search_console_classsifier.py)

KMeans でクラスタリングして、代表的な単語で、ラベリングする python プログラムです。

  • search_console_classsifier.py
    # -*- coding: utf-8 -
    from __future__ import print_function
    
    import pandas as pd
    from __builtin__ import xrange
    from sets import Set
    from sklearn.cluster import KMeans
    from sklearn.feature_extraction.text import CountVectorizer
    
    stop_words = Set(['name', 'not', 'the', 'usr', 'you', 'version', 'this'])
    
    
    # stop word のチェック
    # 2文字以下の文字列、クラスタリングした結果、
    # ラベルとして、出力されたあまり意味のわからない単語を除外
    def check_stop_word(word):
        if word in stop_words:
            return False
        if len(word) <= 2:
            return False
        return True
    
    
    # キーワードを区切る
    def split_keyword(text):
        keywords = text.split(" ")
        after_text = " ".join([keyword for keyword in keywords if check_stop_word(keyword)])
        return after_text
    
    
    # report csv を parse する
    def parse_report_csv():
        lines = []
        row_count = 1
        for line in open('Google_Search_Console.csv', 'r'):
            if row_count != 1:
                arr = line.split(",")
                # キーワードカラムを取り出す
                lines.append(arr[1])
            row_count += 1
        return lines
    
    
    # メインメソッド
    def execute():
        # CSVからキーワードを取得
        keywords = []
        for line in parse_report_csv():
            keywords.append(split_keyword(line))
    
        # テキスト内の単語の出現頻度を数えて、結果を素性ベクトル化する(Bag of words)
        count_vectorizer = CountVectorizer()
    
        # csr_matrix(疎行列)が返る
        feature_vectors = count_vectorizer.fit_transform(keywords)
        print("word数:" + str(len(feature_vectors.toarray()[0])))
    
        # テキスト内の単語の出現頻度を数えて、結果を素性ベクトル化する(Bag of words)
        count_vectorizer = CountVectorizer()
    
        # csr_matrix(疎行列)が返る
        feature_vectors = count_vectorizer.fit_transform(keywords)
    
        # 単語の一覧を取得
        vocabulary = count_vectorizer.get_feature_names()
    
        # クラスタ数は[25]
        n_clusters = 25
        km = KMeans(n_clusters=n_clusters,
                    init='k-means++',  # k-means++法によりクラスタ中心を選択
                    n_init=10,
                    max_iter=300,
                    random_state=0)
    
        # データフレーム作成
        df = pd.DataFrame(feature_vectors.toarray())
        pred = km.fit_predict(df)
        # クラスタリング結果をデータフレームに設定
        df['cluster_id'] = pred
        # ------------------------------------
        # ラベルとラベル名称の辞書の紐付け
        # ------------------
        label_name_dict = {}
        # クラスタ数分繰り返し
        for i in xrange(n_clusters):
            # クラスタIDでデータを抽出して平均値をとる
            series = df[df['cluster_id'] == i].mean()
            # cluster_id を削除しておく
            series = series.drop(['cluster_id'])
            label_name = ""
            # 値を降順ソート(大きい順)で並べ替え、[1]番大きいデータを取得
            for index, value in series.sort_values(ascending=False)[:1].iteritems():
                if value > 0:
                    # 一番大きい(1番登場した単語)をラベル名にする
                    label_name += vocabulary[index]
            label_name_dict.update({i: label_name})
    
        # CSV出力のため、ラベルIDをラベル名に変換
        label_names = []
        for elem in pred:
            label_names.append(label_name_dict.get(elem))
    
        # CSVを再度読み込み、ラベル名を追加して、CSV書き出し
        output_df = pd.read_csv('Google_Search_Console.csv')
        output_df['label_name'] = label_names
        output_df.to_csv('Google_Search_Console_Output.csv', encoding="utf-8")
    
    
    if __name__ == '__main__':
        execute()
    

説明

以下、説明を記載します。説明した項番は省きます。


3. sklearnKMeans でクラスタリング

k-meansの最適なクラスター数を調べる方法 - Qiita
を参考に作成しました。


4. 各クラスターでもっとも頻出する単語をクラスターのラベル名として設定、結果をCSVに書き出す。

pandasで実装しました。何かこのシュチュエーション多い気がしますが、何か一撃で実行する方法あったりするんですかね…?


5. Google スプレッドシートにインポートし、グラフ表示

一旦CSVで吐き出して、Googleスプレッドシートに転記しました。
以下のような、キーワードのクラスタリング結果のラベル名とClick数での円グラフが作成できました。
キーワードラベル名とClick数


検索キーワードをクラスタリングしてみた感想

以下、実施した結果、定性的わかったことを記載します。


  • 形態素解析は不要?
    形態素解析後のwordでクラスタリングしてもいい感じにはならなかったです。
    検索エンジンに対して、送りつけるワードは基本スペース区切りなので、
    「なんらかの意味をもつWORDがスペース区切りで入力されている」前提で、分析したほうがいい結果がでるのかもしれません。

  • 代表語がいい感じなのに、分類結果を見ると[その他]っぽいものがある。
    円グラフ上のbrewがそうなのですが、キーワードにbrewがなかったりします。
    このラベルだけ、ごった煮感が出ていて、詳細にカテゴリ分けしたほうがよさそうに思いました。

  • いい感じに代表的な言葉がついて、分類できていることろもある。
    感覚ベースだと、5-6割はいい感じになっているように思います。
    最もClick数が大きい集合brewがいい感じにならなかったのが問題ですが。。

検索キーワードの分類として、素人としてどうするのがよいか?


  • 案1. 基本「教師あり」で分類して、分類できなかったものを「教師なし」
    「その他分類」の出来加減が非常にストレスが貯まる結果でした。
    キーワードに対する分類データを作成して、「教師あり」であてたらよりいい感じになるのかと思いました。
    次回やるとしたらこれかなと、で、あまりあてにならなそうなところを、今回実施した方法で分類するとかしたら、
    いい感じになりそうに思いました。

  • 案2. 代表後だけ手動で分類ロジックを組んでおき、分類できなかったものを「教師なし」
    代表語のリストで分類して、分類できなかったものを、「教師なし」で分類。
    現状だと、そんなにキーワード数も多くないので、案1より、時間がかからなそうな気はします。
    実装していて楽しくなさそうなのがデメリット。

  • 案3 . マーケットバスケット分析による検索キーワードのグルーピングと視覚化 - 廿TT等を参考に、現在のやりかたとは、WORDの解析の仕方(思考)を変えてみる。
    キーワードだと、文章とは違うからこのようなやり方のほうがいい感じになるのかもしれません。
    brew でまとまったキーワードはプログラム言語違いの横断的関心事(例えば、セキュリティ)とか、
    が含まれていたため名詞ベースではしっくりこないキーワードがまとまった感はありました。
    案3で実装するとこのケースがいい感じに分類(または視覚化)されそうに思いました。

今回作ったやつを多少使いつつ、気が向いたら、改良しようかと思います。
以上です。

コメント

カテゴリー