peiprog’s blog

情報科学を学ぶ大学4年生。主にプログラミング、情報系の話題を書いていきます。

Pythonで相互情報量に基づく特徴ベクトル抽出

 お久しぶりです。peiprogです。
 今回は、ドキュメント群から生成したベクトルから、機械学習で重要な特徴ベクトルを抽出する内容です。テキストをベクトル化したものは何万次元にもなりますが、中には10000個あるドキュメントの中で1回しか出てこない単語など学習に必要のないデータが大量にあります。また、次元数が多いと学習時間が長くなり、メモリの消費量も増えます。
 そのため、今回は相互情報量に基づいて、学習に本当に必要な特徴ベクトルの抽出の仕方を考えます。

目次





相互情報量とは


 相互情報量とは、ざっくりいうと2つの事象(の集合)がどれだけ互いに関連性があるかを表す数値です。定義は以下のとおりです。
こちらにより詳しいことが載ってます。相互情報量とは

\displaystyle
I(X;Y) = \sum_{y\in{Y}}\sum_{x\in{X}}p(x,y)\log_2\frac{p(x,y)}{p(x)p(y)}




実装するアルゴリズム

 ドキュメントから生成したベクトルとラベルにおいての相互情報量は以下のようになります。Classesはドキュメントに紐付いたラベルの集合で、Wordsはベクトル化されている単語の出現頻度を表す集合です。

\displaystyle
I(Words;Classes) = \sum_{word\in{Words}}\sum_{class\in{Classes}}p(word, class)\log_2\frac{p(word,class)}{p(word)p(class)}



 今回は単語ごとのすべてのラベルの相互情報量の中で最大の数値を比較して、機械学習に使うデータを選択します。
   相互情報量を用いた以下の式を単語ごとの関連度の指標として、すべての単語でこの数値を計算します。相互情報量の特性上、値が大きいとラベルと単語の出現頻度の関連性が高いことがわかるため、値が大きければ大きいほど学習に重要な単語であると判断できます。


\displaystyle
Output(word) = MAX\Bigl(p(word, Classes)\log_2\frac{p(word,Classes)}{p(word)p(Classes)}\Bigl)







実装

 先ほど説明したアルゴリズムを実装していきます。式を展開してまとめると以下のようになります。c(〜)は該当する事象の出現数を意味します。
 単純に値を比較するだけのため、単語に依存しない数値は定数とみなして省略してあります。

\displaystyle
Output(word) = MAX\Bigl(c(word, Classes)\log_2\frac{c(doc)c(word,Classes)}{c(word)c(Classes)}\Bigl)

必要なパラメータは以下のとおりです。

c(word, class) ラベルがclassの全ドキュメント中のwordの出現数
c(class) ラベルがclassのドキュメントの数
c(word) 全ドキュメント上のwordの出現数
c(doc) ドキュメントの総数


 以下が単語ごとの相互情報量を保管したリストを返す関数です。extract_wordsメソッドは前回の記事に乗っています。
   vectorsはドキュメントをベクトル化したもののリスト、doc_listはドキュメントのリスト、label_listはドキュメントに対応するラベルのリスト、label_numはラベルの種類の数です。

def get_mi_list(vectors, doc_list, label_list, label_num):
    mi = []  # 単語ごとの相互情報量を保管するリスト
    word_sum = np.sum(vectors, axis=0)  # 全ドキュメント上の単語の出現数

    # すべてのドキュメントを単語リストに変換してラベルごとに保存
    labels_doc_words = [[] for i in range(label_num)]
    for doc_index in range(len(doc_ls)):
        labels_doc_words[labels[doc_index]].extend(extract_words(doc_ls[doc_index]))  # ドキュメントを単語のリストに変換
    # 各ラベルに属するデータ数を保管
    label_count = []
    for label in range(label_num):
        label_count.append(labels.count(label))

    # 全ワードの相互情報量を計算
    for word_index in range(len(words)):
        max_num = -100.0
        # 全ラベルの相互情報量の最大値をリストに追加
        for label in range(label_num):
            counter = labels_doc_words[label].count(words[word_index])  # ラベルがlabelの全文章中で出現するwordの数
            mi_num = counter * \
                         (np.log2(len(label_list) * counter / (label_count[label] * word_sum[word_index])))
            if not math.isnan(mi_num):
                max_num = max(mi_num, max_num)
            mi.append(max_num)

    return np.array(mi)

 実装は難しいことは無いですが、注意点はlogの中身が0以下だとNaNが返るため if not math.isnan(~)をする必要があるという点です。
 返ってきた相互情報量のリストに基づいて学習対象のデータを選定することで学習に重要な特徴ベクトルを抽出できます。


導入結果

 私が研究で作っている2値分類のニューラルネットワークのモデルを用いて今回紹介した手法を実験してみました。以下が実験の結果です。識別率、その他すべての数値が改善されているのがわかります。 
 ただしベクトル化の実行時間が大幅に伸びてしまいました。しかし、ニューラルネットワークの学習時間の比重のほうが大きいため、学習全体の時間としては大した差にはなりません。

※・導入前はTF-IDF重みでベクトル化し、値の大きい単語を選定。ベクトルの要素の値はTF-IDF
・導入後は出現数でベクトル化し、相互情報量に基づき単語を選定。ベクトルの要素の値は単語の出現数

指標(%) 導入前 導入後
識別率 92.9 95.2
精度 96.4 96.4
再現度 87.6 92.9
偽陽性 1.5 1.4
F値 91.8 94.6



Pythonでsklearn+janomeを使って特徴ベクトル抽出

 お久しぶりです。peiprogです。
 機械学習において入力データの形式はとても大事ですよね。分類手法と同じかそれ以上に大事かもしれないですね。今回はその重要な入力データの作り方(加工の仕方)についてです。テキストから機械学習に適した形の入力データに加工します。ライブラリを使うことで短く簡潔に書けます。

目次



仕組み


仕組みはこんな感じです↓
  1. 単語ごとにわける(形態素解析
  2. TF-IDFを用いてベクトル化
用語の説明は省略します。



使用するライブラリ

sklearn 機械学習ライブラリ
janome 日本語の形態素解析ライブラリ
インストール方法は以下を参照してください。pip installなどで必要なライブラリは用意してください。
pipのインストール
sklearnのインストール
janomeのインストール



下準備

 sklearnにはベクトライザーという文章からベクトルを生成できるクラスがあります。それを使うためには単語の分割方法を定義する必要があります。
 そのためまず、ベクトルを生成するために単語ごとに分ける処理(トークン化)のメソッドを定義します。ここで先ほどインストールしたライブラリのjanomeを使用します。
 以下がドキュメントを単語ごとに分割するプログラムです。与えられたドキュメントを単語に分割し、数値文字列と記号を取り除いた単語のリストを返すというメソッドです。

from janome.tokenizer import Tokenizer

def extract_words(text):
    words = []
    t = Tokenizer()  # トークン化してくれるクラス
    tokens = t.tokenize(text)  # トークン化
    for token in tokens:
        if not token.surface.isdigit() and token.part_of_speech.split(',')[0] != "記号":  # 記号と数字を取り除く
            words.append(token.surface)
    return words

 Tokenizerはトークン化してくれるクラスで、このクラスのtokenizeメソッドを使うことで単語の分割をしてくれます。
 次に取得したトークンに着目して必要な単語だけをリストに保存していきます。isdigit()は数値の文字列かを判定するメソッドです。
 また、token.part_of_speechはその単語の品詞や活用形をカンマ区切りで保持する文字列です。これをカンマで分割して0番目の文字列を比較します。0番目の文字列は品詞を表す文字列です。



Vectorizer生成+ベクトル化

次にTF-IDF重みでベクトル化をします。まず、ドキュメントをベクトル化してくれるオブジェクトvectorizerを生成します。先ほど定義したextract_wordsを引数にします。これはvectorizer側の、ドキュメントから単語に分割する際に呼び出されるメソッドを指定することを表します。
最後にvectorizer.fit_transformでテキストからベクトルに変換します。toarrayをするとnumpyの配列になります。ベクトルが見やすくなるし、データを扱いやすくなります。

from sklearn.feature_extraction.text import TfidfVectorizer

text = "お好きなドキュメントを代入してください。"
vectorizer = TfidfVectorizer(analyzer=extract_words)
vec = vectorizer.fit_transform([text]).toarray()
#vec = vectorizer.transform([text])

 また、fit_transformはベクトル化する一定のルールを設定してベクトル化を行い、transformはすでに設定されているルールに従ってベクトル化を行います。場面によって使い分けが必要ですが、詳しくはこちらを参照してください。



まとめ

 いかがでしたか?sklearnを使うと非常に簡潔に書けますね。ただ、ドキュメント数が多くなるとベクトルの次元数が大きくなり学習時間やメモリ消費量が大きくなるため、より対象のデータを絞り込む必要があります。それについても今後書きたいと考えています。最後にまとめたコードを載せておきます。

from janome.tokenizer import Tokenizer
from sklearn.feature_extraction.text import TfidfVectorizer


def extract_words(text):
    words = []
    t = Tokenizer()
    tokens = t.tokenize(text)
    for token in tokens:
        if not token.surface.isdigit() and token.part_of_speech.split(',')[0] != "記号":
            words.append(token.surface)
    return words

text = "お好きなドキュメントを代入してください。"
vectorizer = TfidfVectorizer(analyzer=extract_words)
vec = vectorizer.fit_transform([text]).toarray()





補足

 見ていただいた方でストップワードを取り除く処理をしていないじゃん?と思う人がいるかもしれません。TfidfVectorizerの中身を見たところ、analyzerに関数を代入、もしくはVectorizerのbuild_analyzerをオーバーライドするとストップワードを設定しても取り除いてくれないみたいです。
 analyzerを定義してなおかつ、ストップワードを取り除きたいのであれば、analyzerに代入する関数の中でそういう処理を記述する必要があります。ただ、analyzerとして呼び出される関数は引数が1つしかないのでストップワードを引数として渡すことができないです(^^;。ただTF-IDFであれば性質上ストップワードの重みは小さくなるので大丈夫かな~と思います。
 ちなみに英語を扱うのであれば、janomeを使った関数の代わりにanalyzerにステミング処理関数を渡す必要があります。英語のステミングはNLTKというライブラリがあるのでそれを活用するといいと思います。

Railsアプリにテキトーに広告つけてみた

こんにちは、peiprogです。
Railsアプリへのてきとーな広告のつけ方を書きたいと思います。

  • アクセスするたびにランダムに広告を出せる
  • スマホ版にも対応できる
て感じのことをします。

ちなみにこれを導入したアプリがこちら↓です。よかったら遊びに来てください!
todoremainder
メールアドレスguest、パスワードguestguestでゲストログインできます。deviseのところほとんど触ってないので英語が多いですが(^^;

目次

必要なgemのインストール
ランダムに広告を出すHelperメソッド
HelperメソッドをViewに追加
まとめ



必要なgemのインストー

必要なgemはbrowserのみです。gemfileに下の文を追加します。

gem 'browser'

あとはbundle installすればOKです。


ランダムに広告を出すHelperメソッド

 どのhelperに書いてもいいと思いますが、app/helpers/application_helper.rbに書くといいかもです。
 先ほどinstallしたbrowserがアクセスしてきたデバイスを識別します。スマホ版ならPC版の広告のimgタグにidをつけて以下のようにCSSを書くと縮小表示されます。

#mobile-advertisement{
  width: 100%;
  height: auto;
}

 次に生成した乱数によって違う広告をadに代入することで、ランダムに広告を出すようにします。
最後にadをhtmlのタグに変換して返します。

def get_advertisement
  rand_num = rand(0..2)  # 0~2の乱数生成
  ad = ""
  if browser.device.mobile?  # アクセスしてきたデバイスがモバイル
    if rand_num == 0
      ad = "出したい広告1 id='mobile-advertisement'を忘れずに"
    elsif rand_num == 1
      ad = "出したい広告2 id='mobile-advertisement'を忘れずに"
    else
      ad = "出したい広告3 id='mobile-advertisement'を忘れずに"
    end

  else  # PCからのアクセス
    if rand_num == 0
      ad = "出したい広告1"
    elsif rand_num == 1
      ad = "出したい広告2"
    else
      ad = "出したい広告3"
    end
  end
  return ad.html_safe  # 文字列をhtmlタグにして返す
end 


HelperメソッドをViewに追加

 さっき書いたhelperメソッドをviewで呼び出します。app/views/layouts/application.htmlに配置すればいつでも広告がでます。

~ヘッダーは省略
<body>
  <div id="container">
    メインコンテンツ
  </div>

  <%= get_advertisement %>

  <div id="footer">
    フッター
  </div>
</body>

まとめ

 単純な方法でしたね(^^;。特に難しいことはしてないですね。
 ホントは管理者ページとかから広告の設定ができたりしたほうがいいですね。広告のタグもデータベース上で管理したほうがいいですね。
 ちなみに広告収益は先月は10円でした!







Pythonでワードクラウド作ってみた

お久しぶりです、peiprogです。

今回はPythonでワードクラウドを作ってみました。ワードクラウドとは
追記:英語のみ対応です。ライブラリ(pytagcloud)が日本語に対応していないので...。日本語に対応させたいなら描画のところは自前で書く必要がありますね(^^;


全体の流れとしては

1.ドキュメント読み込み
2.ベクトル生成
3.ベクトルからワードクラウド描画
という感じです。こんな感じの画像を生成できます。
f:id:peiprog:20170614221243p:plain:w250


使うライブラリは以下の通りです。

sklearn 機械学習ライブラリ
pytagcloud ワードクラウド描画ライブラリ
numpy 数値計算ライブラリ
urllib インターネットからページを取得する用
 
 ライブラリのインストール方法についての説明は省略します。pip installなどで必要なライブラリは用意してください。
pipのインストールについて
sklearnのインストールについて
pytagcloudのインストールについて(英語サイト)
urllibのインストールについて



ドキュメント読み込み

これは単純にファイルパスを指定して読み込むだけです。

doc = open("file_path", "r").read()




ベクトル生成

ドキュメントを単語ごとに分けて、出現頻度のベクトルを生成します。


はじめにストップワードを取り除く必要があります(ストップワードとは、私、です、ます、などの出現頻度が高く不要な語)。
日本語のストップワードはインターネットなどから入手する必要があります。今回はSlothLibのページからストップワードを取得します。
SlothLibのページ

from urllib import request

page = request.urlopen('http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt')  # インターネットからページを取得
stopwords = [line.decode('utf-8').strip() for line in page]  # デコード
stopwords = [w for w in stopwords if not w==u'']  # 空文字を取り除く



 次に取得したストップワードを用いてsklearnのCountVectorizerを使ってベクトル化します。先ほど生成したストップワードとmin_dfを指定します。min_dfを指定すると、指定した値より出現回数の低い単語を無視します。
 最終的には、ワードクラウドを生成するのに必要な、単語と出現頻度ベクトルの数値を合わせたタプルを生成します。
 最後に昇順ソートをしておきます。キー関数を使用しています。キー関数についてはこちらを参考にしてください。

from sklearn.feature_extraction.text import CountVectorizer

vectorizer = CountVectorizer(min_df=1, stop_words=stopwords)  # ベクトル生成器を生成
vector = vectorizer.fit_transform(doc)  # docからベクトルを生成
words = vectorizer.get_feature_names()  # ベクトルに対応する単語のリストを取得

words_vector = []  # 単語と出現頻度ベクトルの数値のタプルが要素となるリスト
for i in range(len(words)):
    if not words[i].isdigit():  # 数字の文字列でなければリストに追加
        words_vector.append((words[i], vector[0, i]))  # 単語とベクトルの数値をタプルとしてリストに追加
words_vector.sort(key=lambda i:i[1], reverse=True)  # 昇順ソート、キーはベクトルの値




ワードクラウド描画

生成した単語と出現頻度ベクトルを合わせたタプルのリストを用いてワードクラウドを作成します。この例では上位30個の単語をピックアップします。
また、2行目の引数のminsize, maxsizeはワードクラウド中の単語の大きさの最大値、最小値を表します。

from pytagcloud import create_tag_image, make_tags
tag = make_tags(words_vector[:30], minsize=20, maxsize=40)  # 上位30この単語を用いてワードクラウドを生成
create_tag_image(tag, savefile_path, size=(300, 300))  # 実際に描画したものを保存




まとめ

いかがでしたか?実際に画像作ってみると結構面白いですよね!PDFやワードファイルに対応できると色んなドキュメントのワードクラウドが見れて面白いかもしれませんね。
githubにコード上げてるのでよかったら見てください!
https://github.com/ippei-miyake/WordCloudGenerator


最後にまとめたコードを示します。

from urllib import request
from sklearn.feature_extraction.text import CountVectorizer
from pytagcloud import create_tag_image, make_tags


doc = open("好きなファイルパスを指定", "r").read()

page = request.urlopen(
       'http://svn.sourceforge.jp/svnroot/slothlib/CSharp/Version1/SlothLib/NLP/Filter/StopWord/word/Japanese.txt')  # インターネットからページを取得
stopwords = [line.decode('utf-8').strip() for line in page]  # デコード
stopwords = [w for w in stopwords if not w == u'']  # 空文字を取り除く


vectorizer = CountVectorizer(min_df=1, stop_words=stopwords)  # ベクトル生成器を生成
vector = vectorizer.fit_transform([doc])  # docからベクトルを生成
words = vectorizer.get_feature_names() # ベクトルに対応する単語のリストを取得

words_vector = []  # 単語と出現頻度ベクトルの数値のタプルが要素となるリスト
for i in range(len(words)):
    if not words[i].isdigit():  # 数字の文字列でなければ
        words_vector.append((words[i], vector[0, i]))  # 単語とベクトルの数値をタプルとしてリストに追加
words_vector.sort(key=lambda i: i[1], reverse=True)  # 昇順ソート、キーはベクトルの値


tag = make_tags(words_vector[:30], minsize=20, maxsize=40)  # ワードクラウドを生成 上位30語を描画
create_tag_image(tag, "好きなファイルパスを指定", size=(300,300))  # 実際に描画したものを保存




numpyのMemory Error

こんにちは!peiProgです!

 

今回はnumpyのMemory Errorの解決というか妥協策について書きます。

numpyを使って画像識別の機械学習プログラムを書いてたときにnumpyでMemory Errorが起こりました。そこで私は以下の取り組みをしてみました。

 

・Pycharmのメモリ設定を変える

 これは効果なし。1度設定ミスってPycharm起動しなくなったためインストールし直し...

 

・バッチサイズ下げる

 これは学習対象のデータサイズによっては解決します。下げるとエラーが起きなくなることもありますが、私は直りませんでした...。

 

・対象の画像(データ)をリサイズ

 50x50くらいにしたらエラーが起きなくなりました。当然ですがバッチサイズ以上にメモリの節約になるためでしょう。

  まあエラー時の1枚の画像のサイズが200x200とかアホみたいに大きかったのが悪いです(^^;

 

結論

 結局メモリ使いすぎなので節約しろってことですね(^^;。この例だと画像をリサイズで解決しました。メモリ節約するのはバッチサイズ下げるより画像リサイズのほうが効果的ですね。学習時間も減りますが性能に影響するので考えて調整しましょう!

 

 

 

 

Javaオススメ本

こんにちは!peiProgです!

はじめての記事になります。読みにくいかもしれませんがよろしくお願いします!!

 

今回はおすすめのJavaの参考書の紹介です。

Java言語で学ぶデザインパターン入門です。

増補改訂版Java言語で学ぶデザインパターン入門 | 結城 浩 |本 | 通販 | Amazon

f:id:peiprog:20170522143644j:plain

 

就活中に某大手IT企業のエンジニアの面接官に設計でいい本ないかと聞いたところこの本を紹介されました。

 

・どんな人向け?

 Javaの基本的文法、抽象クラスなどのオブジェクト指向の基本的な概念がわかり、再利用性や機能拡張性の高いコードを書けるようになりたい人向けです。Java中級者向けという感じですかね。

 オブジェクト指向の概念はわかるが、どのように使うのか、どういった場面で役に立つのかが知りたい人向けだとも感じました。

 

・内容は?

 GoFの23のデザインパターン(http://www.techscore.com/tech/DesignPattern/index.html/)について述べられています。各章はデザインパターンの説明、サンプルプログラム、パターンについての詳細な解説、練習問題という構成です。

 

・わかりやすさ

 あくまで個人の感想ですが、サンプルプログラムは簡潔で説明も丁寧にしてあり、対応するデザインパターンに適したプログラムのため非常にわかりやすいです。

 

・感想

 私は大学生のため、他人とプログラムを書く経験が少なく、可読性や再利用性、機能拡張性のあるコードとはとても言えませんでしたが、この本を読んでからは改善されたかなと思います。特に抽象クラスについて習ってはいたもののイマイチ使いどころがわからなかったが、この本でその悩みは解消されました。

 言語に依存しない汎用的な知識が詰まっているので役に立つ場面は非常に多いと思います。

 

 

 

 

こんにちは、peiprogです。
ブログ始めました!
主にプログラミング、情報系の話題を書いていこうと思っています。
よろしくお願いします!