検索キーワードを python sklearn LinearSVC でクラス分類してカテゴリ分けする


Google Search Console の キーワードを python で クラス分類してカテゴリ分けしてみた結果を記載します。
学習データに依るのかとは思いますが、Google Search Console の キーワードを python でクラスタリングする。 | Monotalk に続き、 カテゴリ分けの足がかりの部分はできた感はあります。1
[1].やはり実務に役立つかは謎です。。


前提

Querylabel_name
sonarqubesonarqube
no module namedpython
(1_8.w001) the standalone template_* settings were deprecated in django 1.8django
//nosonarsonarqube
404 エラー404
404エラー404
404エラーページ404

クラス分類の手順

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

  1. Google スプレッドシートの学習データをTSVにして読み込み、キーワードを抽出。

  2. サーチコンソールからの取得結果のGoogle スプレッドシートをCSV にして読み込み、キーワードを抽出。

  3. 学習データと、サーチコンソールからの取得結果のキーワードをマージ、キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。
    2
    [2]キーワードをマージは、サーチコンソールからの取得結果のキーワードは時間が経つと変わっていく(増えていく)ため、
    素性ベクトル作成時に、次元数を揃える?ために実施しています。(実際いらないのかもしれませんがたぶん必要)

  4. LinearSVC で学習

  5. CSV をクラス分類して、スコアを取得、確からしさが低いデータについては、分類結果のラベルは付与せず、unknown ラベルに置換

  6. 結果をCSVに書き出し、Google スプレッドシートにインポートし、グラフ表示


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

必要なライブラリをインストールします。

pip install sklearn numpy scipy pandas

以下のversionが、インストールされました。

pip list | grep -e sklearn  -e numpy -e scipy -e pandas
---------------------------
numpy (1.12.1)
pandas (0.20.1)
scipy (0.19.0)
sklearn (0.0)
---------------------------

実装

以下、作ったpython プログラムになります。

search_console_svm_classsifier.py

以下、記事を参考に作成しました。
3.6. scikit-learn: Python での機械学習 — Scipy lecture notes pythonの機械学習ライブラリscikit-learnの紹介 - 唯物是真 @Scaled_Wurm

  • search_console_svm_classsifier.py

    # -*- coding: utf-8 -
    from __future__ import print_function
    
    import sys
    
    import numpy as np
    import pandas as pd
    from sets import Set
    from sklearn import svm
    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
    
    
    # learning_tsv parseする
    def read_from_learning_tsv(index):
        lines = []
        row_count = 1
        for line in open('LearningData.tsv', 'r'):
            if row_count != 1:
                arr = line.split("\t")
                # キーワードカラムを取り出す
                lines.append(arr[index])
            row_count += 1
        return lines
    
    
    def get_min_abs_dict(array, score):
        # 最小絶対値として、設定されない値であろう[sys.maxint] を設定
        min_abs = sys.maxint
        index = 0
        for i in range(len(array)):
            item = abs(array[i])
            if min_abs > item:
                min_abs = item
                index = i
        return {score[index]: min_abs}
    
    
    # メインメソッド
    def execute():
        # ----------------------------------------------------------------------------
        # 3. 学習データと、サーチコンソールからの取得結果のキーワードをマージ、キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。
        # ------------------------------------------------------
        keywords = []
        # 学習データからキーワードを取得
        for line in read_from_learning_tsv(0):
            keywords.append(split_keyword(line))
        # CSVからキーワードを取得
        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])))
    
        # 学習したデータのみ切り出し
        learning_vectors = feature_vectors[:len(read_from_learning_tsv(0))]
        # データに対応したラベルを取得
        learning_labels = np.array(read_from_learning_tsv(1))
    
        # -------------------------------------
        # 4. `LinearSVC` で学習
        # --------------------------------
        # SVCのロード
        clf = svm.LinearSVC()
        # 学習
        clf.fit(learning_vectors, learning_labels)
    
        # --------------------------------------
        # 5. `CSV` をクラス分類して、スコアを取得、確からしさが低いデータについては、分類結果のラベルは付与せず、`unknown` ラベルに置換
        # --------------------------------------
        scores = clf.decision_function(feature_vectors[len(read_from_learning_tsv(0)):])
        classes = clf.classes_
        labels = []
        for score in scores:
            min_abs_dict = get_min_abs_dict(score, classes)
            for k, v in min_abs_dict.items():
                if 0.90 >= v:
                    # 0.90 以下は、ラベルを設定
                    labels.append(k)
                else:
                    # 0.90 より大きい場合は "unknown"
                    labels.append("unknown")
    
        # CSVを再度読み込み、ラベル名を追加して、CSV書き出し
        output_df = pd.read_csv('Google_Search_Console.csv')
        output_df['label_name'] = labels
        output_df.to_csv('Google_Search_Console_Output.csv', encoding="utf-8", index=False)
    
    if __name__ == '__main__':
        execute()
    

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


1. Google スプレッドシートの学習データをTSVにして読み込み、キーワードを抽出。
TSVにしているのは、キーワード文字列内にカンマが含まれているためです。
Google_Search_Console.csvにももちろん含まれますが。。。一旦見ないことにしました。
現在(2017/05/21)だと、Google スプレッドシートのcsvは、値にカンマを含む場合に、


3. 学習データと、サーチコンソールからの取得結果のキーワードをマージ、キーワード内の単語の出現頻度を数えて、結果を素性ベクトル化する。
最初mecabで形態素解析し、名詞、動詞、形容詞を抜き出していたのですが、あまりいい感じにならず、単純なスペース区切りでキーワードを分割し、
短すぎるワード(2文字以下)を除外して、結果を素性ベクトル化しました。
split_keyword メソッド内で、実装しています。
素性ベクトル作成後、学習したデータのみ切り出すため、
learning_vectors = feature_vectors[:len(read_from_learning_tsv(0))]
で、TSV データの行数を頼りにデータを抜き出しています。


4. LinearSVC で学習 LinearSVC を使っているのは、SVC で分類したところ、あまりスコアがよろしくなかったからです。
パラメータ指定なしのデフォルトで実行しただけなので、この辺りの指定を頑張れば、
もっといい感じで分類できるかもしれません。


5. CSV をクラス分類して、スコアを取得、確からしさが低いデータについては、分類結果のラベルは付与せず、unknown ラベルに置換
decision_function で、どのラベルが確からしいのか、決定境界からの距離が取得できます。
こいつは、正負の数値が設定されていて、その中で「絶対値がもっとも小さいラベル?」が確からしいので、
get_min_abs_dict というメソッドを実装して、絶対値が最小のラベル名と、絶対値を辞書にして返すようにしました。
その後、絶対値が0.9以下だったら、ラベルを使用し、そうでなければ、unknown となるようにしていますが、
0.9 は、何度か実施してみて、「いい感じ」に間違いがなさそうになった値です。
ちなみに、LinearSVC で、predict_proba を実行したところ、そんなメソッドはないらしく、以下のエラーが出力されました。

AttributeError: 'LinearSVC' object has no attribute 'predict_proba'


6.結果を CSVに書き出し、Google スプレッドシートにインポートし、グラフ表示
一旦CSVで吐き出して、Googleスプレッドシートに転記しました。
以下のような、キーワードのクラス分類結果のラベル名とClick数での円グラフが作成できました。3
[3].unknown の割合がもっとも大きい..
キーワードラベル名とClick数


検索キーワードをLinearSVCでクラス分類してみた感想

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


  • 1単語のみのキーワードがうまく分類できていない。
    3word 以上の分類は、結構うまくいっているように見えましたが、
    例えば SonarQube 日本語 がうまく分類できず、unknown になります。
    これは、IntelliJ 日本語IntelliJ とする学習データがあったりするのが、
    影響にしているのだろうと思ったりします。
    「カテゴリを示すキーワード」の重みづけをする。or 「学習データ」を調整するなどが必要に思いました。

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

クラス分類の方法としては、他の方法もあるので、
その他試してみて、丁度いいのを使おうかと思います。4
[4].今のままだと、使いものにならない感が..

以上です。

コメント