Python3でtf-idfとかコサイン類似度とか求める関数

MeCab使います。

def analysis(sentence):
    """
    # 与えられたテキストに対し、形態素解析を行います
    #
    # @param    string     sentence    形態素解析を行いたいテキストを渡します
    # @return   list                   形態素解析の結果が、リスト型で返ります
    """

    # 辞書をneologdを指定して、MeCabを起動
    # tagger = MeCab.Tagger(' -d /usr/local/lib/mecab/dic/mecab-ipadic-neologd')
    tagger = MeCab.Tagger()

    # 形態素解析を実行
    node = tagger.parseToNode(sentence)

    # 形態素解析結果を保持しておく変数
    result = []

    # 形態素分繰り返す
    while node:
        # タグ情報を取得を取得
        tag_line = node.feature

        # カンマ区切りのタグ情報を、カンマでスピリット
        tags = tag_line.split(',')

        # 最初のノイズ除去として、名詞・動詞・形容詞のみを残す
        if (tags[0] == '名詞') or (tags[0] == '動詞') or (tags[0] == '形容詞'):
            # データを変数に追加(その語句の原型を追加)
            result.append(tags[6])

        # 次のワードへポインタを動かす
        node = node.next

    # 結果を返す
    return result


def loadFile(file_path):
    """
    # 引数で指定されたファイルを1行ずつ読み込み、配列にして返します。
    #
    # @param string      file_path      形態素解析を行いたいテキストをまとめたファイル
    """

    # 結果として返す値を入れる変数
    result = []

    # 読み込むファイルをオープンする
    fp = open(file_path, 'r')

    # 全行一気に読み込む
    result = fp.readlines()

    # ファイルのクローズ
    fp.close()

    # データを返す
    return result


def excludeStopWords(file_path, word_list):
    """
    # 指定したストップワードリストを除外します
    #
    # @param string         file_path       除外したいワードが書かれているファイル
    # @param list           word_list       ワード(語句)が格納されているリストオブジェクト
    # @param list                           ストップワードを除外した配列が返ります
    """

    # ストップワードリストを読み込むための変数
    stopwords = []

    # ファイルのオープン(文字コードはUtf8)
    fp = open(file_path, 'r', -1, 'utf-8')

    # 全行読み込み
    stopwords = fp.read()

    # お返し
    fp.close()

    # 改行コードで分割
    stopwords = stopwords.split("
")

    # ストップワードを除外する
    result = list(filter(lambda x: x not in stopwords, word_list))

    # 除外したデータを返す
    return result


def calcTf(sentence):
    """
    # 与えられたテキストのtf値を求めます
    #
    # @param string     sentence        tf値を計算したい文章を入力
    # @return dict                      単語とtf値が返ります
    """

    # 文章を形態素解析
    morphological = analysis(sentence)

    # ストップワードを除去
    morphological = excludeStopWords('stop_word_list.csv', morphological)

    # 全形態素数
    morphological_number = len(morphological)

    # 形態素の数をカウントしていく
    result = {}         # 結果は辞書配列にしておく(dict型)
    for value in morphological:
        # まとめている変数に、すでにその形態素がキーとして存在しているかのチェック
        if value in result:
            # 値を保持している場合
            result[value] = result[value] + 1
        else:
            # 存在していない場合
            result[value] = 1

    # tf値の計算
    for value in result:
        # tf = その単語の登場回数 / 全単語の登場回数
        result[value] = float(result[value]) / morphological_number

    # 返却
    return result


def calcIdf(sentence_list):
    """
    # 与えられた文章集合のIDFを計算します
    # キーにワード、値にtf-idfの値というペアのdict型の配列が返ります
    #
    # @param    list       sentence_list            IDF値を計算したい文章集合
    # @return   dict                               単語とIdfの配列が返ります
    """

    # 文書集合の数
    sentence_number = len(sentence_list)

    # 与えられた全ての文章を形態素解析する
    word_list = {}                          # 出てきた単語とその登場回数を保存しておく変数
    for value in sentence_list:
        # 形態素解析
        morphological = analysis(value)

        # ストップワードを除去
        morphological = excludeStopWords('stop_word_list.csv', morphological)

        # データを全て登録
        temp_word_list = []                     # 一時的に形態素を保存しておく変数
        for v in morphological:
            # 出てきた形態素の原型を溜め込んでいく
            temp_word_list.append(v)

        # 形態素の重複を削除
        temp_word_list = list(set(temp_word_list))

        # 抽出した単語リストを、全体を保存している変数に入れる
        for v in temp_word_list:
            # 単語がすでに変数に存在するか
            if v in word_list:
                # 存在している場合は、センテンス数を加算
                word_list[v] = word_list[v] + 1
            else:
                # 存在していなければセンテンス数を1にセット
                word_list[v] = 1

    # IDFをを計算していく
    for value in word_list:
        # idf = log( センテンス毎の単語の登場回数 / センテンス数 )
        idf = float(word_list[value]) / sentence_number + 1
        word_list[value] = math.log(10, idf)

    # 結果を返す
    return word_list


def calcTfIdf(sentence_list):
    """
    # tf-idfを計算し、その結果を単語とtf-idf値でまとめたdict型の配列で返します。
    #
    # @param    list                tf-idf値を求めたい文章集合
    # @return   list                単語とtf-idf値をまとめたlist型の配列
    """

    # そのセンテンス内のtf-idfのdictを一時的に入れておく変数
    sentence_tfidf_list = {}

    # 各ワードのidf値を求める
    idf_list = calcIdf(sentence_list)

    # センテンス毎にtf-idfを出す
    for sentence in sentence_list:
        # tf値の計算
        tf_list = calcTf(sentence)

        # tfリストの中にある単語のtf-idfを計算していく
        for value in tf_list:
            # その語句のtf-idfの計算
            tf_idf = float(tf_list[value]) * float(idf_list[value])

            # 変数に保存しておく
            sentence_tfidf_list[value] = tf_idf

    # 結果を返す
    return sentence_tfidf_list


def createVector(dimensions, sentence_list):
    """
    # 与えられた語句を次元とした特徴ベクトル表を作成します
    #
    # @param    list    dimensions          次元とする語句を配列で渡す
    # @param    list    sentence_list        ベクトル化したい文章リストを、リスト形式で渡す
    # @return   list                        ベクトル表を多次元リスト形式で返す
    """

    # ベクトルデータを入れるリスト
    vector = []

    # センテンスを形態素解析
    for value in sentence_list:

        # 形態素解析を行う
        morphological = analysis(value)

        # ストップワードの除去
        morphological = excludeStopWords('stop_word_list.csv', morphological)

        # このセンテンスのベクトルデータを一時的に入れていおく箱
        temp_vector = []

        # 次元と比較して、ベクトルデータを作成していく
        for dimension in dimensions:
            # ベクトルの次元に、データが有れば1、なければ0の二値ベクトルの作成
            if dimension in morphological:
                # 含まれていれば1
                temp_vector.append(1)
            else:
                # 含まれていなければ0
                temp_vector.append(0)

        # 出来上がったベクトルのデータを保存
        vector.append(temp_vector)

    # ベクトルデータを返却
    return vector


def calcAllCosineSimilarity(vector, row_num):
    """
    # 与えられたベクトルデータのうち、row_numで渡された行番号との類似度を計算します
    #
    # @param    list    vector          上記のcreateVectorメソッドで作ったベクトル表
    # @param    int     row_num         計算したいデータの行番号
    # @return   dict                    keyに行番号、valueに類似度の組み合わせを持つ辞書配列が返る
    """

    # ========================
    # この辺にバリデーションを書け
    # ========================

    # 引数で指定された行のベクトル情報を取得
    own_vec = vector[row_num]

    # 類似度を保存しておく変数
    similarity = {}

    # 類似度を計算していく
    count_row = 0                      # ベクトルテーブルの行数をカウントする変数
    for vec in vector:
        # ループの中で使う変数定義場
        count_col = 0                  # ベクトルの何列目かをカウントする変数
        numer = float(0)               # 分子
        denom = float(0)               # 分母
        own_denom = float(0)           # 左側ルート
        other_denom = float(0)         # 右側ルート
        cos = float(0)                 # コサイン類似度

        # ベクトル計算開始
        for v in vec:
            # コサイン類似度を計算するための分子計算
            numer = numer + (own_vec[count_col] * v)

            # コサイン類似度を計算するための分母計算
            own_denom = own_denom + own_vec[count_col] * own_vec[count_col]
            other_denom = other_denom + v * v

            # 次の列へ
            count_col = count_col + 1

        # コサイン類似度の計算
        denom = math.sqrt(own_denom) * math.sqrt(other_denom)

        if denom != 0:
            cos = numer / denom
        else:
            cos = 0

        # 類似度情報を保存
        similarity[count_row] = cos

        # 次の行へ
        count_row = count_row + 1

    # 類似度リストの返却
    return similarity


def calcCosineSimilarity(vector, row_num1, row_num2):
    """
    # 与えられたベクトルリストから、row_num1とrow_num2で与えられた行番号のコサイン類似度を計算して返す
    #
    # @param    list    vector              ベクトルの次元が格納されたリスト
    # @param    int     row_num1            行番号
    # @rapam    int     row_num2            行番号
    # @return   float                       コサイン類似度の計算結果
    """

    # 類似度を計算したいベクトルを抜き出す
    vec1 = vector[row_num1]                 # row_num1のベクトル
    vec2 = vector[row_num2]                 # row_num2のベクトル

    # 2つのコサイン類似度を計算するのに必要な変数たち
    cos = float(0)                 # コサイン類似度
    numer = float(0)               # 分子
    denom_left = float(0)          # 分母(左)
    denom_right = float(0)         # 分母(右)

    # ベクトルの次元数分繰り返す
    for v_num in range(0, len(vec1) - 1):
        # 分子の計算
        numer = numer + (vec1[v_num] * vec2[v_num])

        # 分母の計算
        denom_left = denom_left + (vec1[v_num] * vec1[v_num])
        denom_right = denom_right + (vec2[v_num] * vec2[v_num])

    # コサイン類似度を計算
    if denom_left * denom_right == 0:
        # 0割対策
        cos = 0
    else:
        # 分母が0以外なら普通にこいさん類似度を求める
        cos = numer / (math.sqrt(denom_left) * math.sqrt(denom_right))

    # コサイン類似度を返却
    return cos
[PR] おすすめの本
この記事を書いた人
Nな人(えぬなひと)。
Nは本名から取っています。
Laravelが大好きなPHPerで、WEBを作るときはLaravelを技術スタックに絶対推すマン。
PHP、Pythonと、昔はperlを書いていたP言語エンジニア。
最近はNimを書いたりしています。