いっきのblog

技術とか色々

Jupyter Labをもっと快適にするためにvim Extensionを追加する

以前Jupyter Labを導入してから使い続けている。

kzkohashi.hatenablog.com

ただ、僕はエセvimmerなので若干使いにくいなーと思っていたところ、Jupyter Labには様々なExtention(拡張機能)を追加できるというのを知った。
ちなみにJupyterからこの機能はあったみたいで、有志たちによって様々なExtensionは開発されている。以下はGithub上にあるExtensionの中でtopic(話題?)になってるやつだと思う。

github.com

Jupyter LabにVimを導入する

以下のExtensionを追加する。

github.com

コマンドは簡単。

jupyter labextension install jupyterlab_vim
$ jupyter labextension install jupyterlab_vim
> npm pack jupyterlab_vim
jupyterlab_vim-0.8.0.tgz
Traceback (most recent call last):
  File "/usr/local/anaconda3/bin/jupyter-labextension", line 11, in <module>
    sys.exit(main())
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyter_core/application.py", line 267, in launch_instance
    return super(JupyterApp, cls).launch_instance(argv=argv, **kwargs)
  File "/usr/local/anaconda3/lib/python3.6/site-packages/traitlets/config/application.py", line 658, in launch_instance
    app.start()
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab/labextensions.py", line 168, in start
    super(LabExtensionApp, self).start()
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyter_core/application.py", line 256, in start
    self.subapp.start()
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab/labextensions.py", line 58, in start
    for arg in self.extra_args]
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab/labextensions.py", line 58, in <listcomp>
    for arg in self.extra_args]
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab/commands.py", line 100, in install_extension
    return IOLoop.instance().run_sync(func)
  File "/usr/local/anaconda3/lib/python3.6/site-packages/tornado/ioloop.py", line 458, in run_sync
    return future_cell[0].result()
  File "/usr/local/anaconda3/lib/python3.6/site-packages/tornado/concurrent.py", line 238, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 4, in raise_exc_info
  File "/usr/local/anaconda3/lib/python3.6/site-packages/tornado/gen.py", line 1069, in run
    yielded = self.gen.send(value)
  File "/usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab/commands.py", line 158, in install_extension_async
    raise ValueError(msg)
ValueError:
"jupyterlab_vim@0.8.0" is not compatible with the current JupyterLab
Conflicting Dependencies:
JupyterLab              Extension            Package
>=0.10.0-0 <0.11.0-0    >=0.17.2-0 <0.18.0-0 @jupyterlab/application
>=0.10.0-0 <0.11.0-0    >=0.17.2-0 <0.18.0-0 @jupyterlab/notebook

簡単のはずエラー。バージョンの問題かな・・・。とりあえずJupyter Labのバージョン確認。

$jupyter --version
4.3.0
$ jupyter lab --version
0.27.0

最新は0.33系なのでバージョンあげよう。anacondaで色々試した結果、こんな感じでようやくできた。

# anacondaのバージョンアップ
$ conda update conda

# jupyterの再インストール
$ conda uninstall jupyter
$ conda install jupyter

# いろんな記事をみたら、あるissueに書かれてたのでやってみたが、影響したかはわからない
$ conda uninstall zeromq --force
$ conda install zeromq

# jupyter labの再インストール
# conda-forgeからインストールすることで新しくなる
$ conda uninstall jupyterlab
$ conda install -c conda-forge jupyterlab

# jupyter 5.3以下のおまじない
$ jupyter serverextension enable --py jupyterlab --sys-prefix

# バージョン確認
$ jupyter --version
4.4.0
$ jupyter lab --version
0.33.6

もう一度試す

$ jupyter labextension install jupyterlab_vim
...
Child html-webpack-plugin for "index.html":
     1 asset
    Entrypoint undefined = index.html
    [KTNU] ./node_modules/html-loader!./templates/partial.html 567 bytes {0} [built]
    [YuTi] (webpack)/buildin/module.js 497 bytes {0} [built]
    [aS2v] ./node_modules/html-webpack-plugin/lib/loader.js!./templates/template.html 1.22 KiB {0} [built]
    [yLpj] (webpack)/buildin/global.js 489 bytes {0} [built]
        + 1 hidden module
✨  Done in 81.59s.

だん!

Jupyter Labでvimを満喫する

vimらしい極太の線がでてきた。
僕はキーバインドはカスタマイズしてないので、このまま意識せずvimレた。やっぱいいな。慣れてる操作って。

f:id:kzkohashi:20180803222816p:plain

メリット

デメリット

  • printした際にでる表示項目は、デフォルトで一部しか見れないが、このExtensionを入れると全部見えてしまう
  • たまにinsert modeになるのが遅くて困る

終わりに

インストールに少し苦戦したが、一度できてしまえば他のExtensionも楽に追加できる。
vim以外にも面白そうなExtensionがあったので、もう少し試したら紹介しようと思う。

TF-IDFを自力実装してみる

前回ライブラリーを使って実装しようとして断念した「TF-IDF」について自力で実装したいと思う。

kzkohashi.hatenablog.com

ちなみに上記の記事は、TF値の出し方が間違ってて理解不足だった・・。 一度実装するとしないでは理解度が変わるという教訓。。いや計算式の理解の問題か・・。(修正済み)

TF値を求める

まずは数式の確認からすると、以下のようになる。

\large{tf_{t,d} = \frac{n_{t,d}}{\sum_k n_{k,d}}}

tf_{t,d}:文書dにおける、単語tのTF値

{n_{t,d}}:単語tの文書dにおける出現回数

{\sum_k n_{k,d}}:文書dにおけるすべての単語の出現回数の和

まずは、前回同様に単語ごとの出現回数({n_{t,d}})をもとめると以下のようになる。

from sklearn.feature_extraction.text import CountVectorizer
import numpy as np
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

count_vectorizer = CountVectorizer(min_df=1, max_df=100, input='filename', token_pattern=u'(?u)\\b\\w+\\b')

bag_of_words = count_vectorizer.fit_transform(['../data/blog_wakati_neo.txt'])
sum_words = bag_of_words.sum(axis=0)
words_freq = [(word, sum_words[0, idx]) for word, idx in count_vectorizer.vocabulary_.items()]
words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True)
[('た', 32),
 ('の', 27),
 ('に', 26),
 ('を', 19),
 ('と', 15),
 ('し', 13),
 ('て', 12),
 ('ない', 10),
 ('から', 10),
 ('な', 10),
 ('も', 8),
 ('こと', 8),
 ('サービス', 8),
 ('が', 6),
 ('は', 6),
省略

続いて全ての単語数({\sum_k n_{k,d}})を求めると

# 扱いやすいディクショナリーに変換
dict_words_freq = dict(words_freq)
all_words_count = sum(dict_words_freq.values())

あとは「単語ごとの出現回数 / 全ての単語数」を行えば良いので以下のようになる

# TF値の計算
dict_words_freq = dict(words_freq)
all_words_count = sum(dict_words_freq.values())
for key in dict_words_freq:
    print(key, dict_words_freq[key], dict_words_freq[key]/all_words_count)

無事できた。

た 32 0.05714285714285714
の 27 0.048214285714285716
に 26 0.04642857142857143
を 19 0.033928571428571426
と 15 0.026785714285714284
し 13 0.023214285714285715
て 12 0.02142857142857143
ない 10 0.017857142857142856
から 10 0.017857142857142856
な 10 0.017857142857142856
も 8 0.014285714285714285
こと 8 0.014285714285714285
サービス 8 0.014285714285714285
が 6 0.010714285714285714
は 6 0.010714285714285714
省略

IDF値を求める

まずは数式の確認からすると、以下のようになる。

\large{idf_t = \mbox{log} \frac{N}{df_t}} + 1

idf_t:単語tにおけるIDF値

N:すべての文章数

df_tNにおいて、単語tが含まれる文章数

元となる文章は以前使った、Wikipediaのデータを元にする。
ただし、量が多くて重いため(2400万行)、100万行ほどのデータのみを使うこととする。

kzkohashi.hatenablog.com

まずは、100万行のみのデータにする。

head -n 1000000 wiki_wakati_neo.txt > wiki_wakati_neo_head.txt

このままだと全ての文章が同じファイルにあるので、文章ごとに分割する。
「<\/ doc >」が区切りっぽいのでそれで分割する。

f = open('../data/wiki_wakati_neo_head.txt')
wiki_texts = f.read().split('</ doc >')
print(wiki_texts[0])
f.close()
< doc id =" 3216890 " url =" https :// ja . wikipedia . org / wiki ? curid = 3216890 " title =" 2015年アムトラック脱線事故 "> 
2015年アムトラック脱線事故 

...省略

次の処理で使うので、リストの中の文字をユニークのリストに返してつかう関数を用意。

def unique(list1):
    unique_list = []
     
    for x in list1:
        if x not in unique_list:
            unique_list.append(x)
    return unique_list

IDF値を求める。

from math import log

# 初期化する
# ブログの文章を含めると最低出現数は1となるため
idf = {}
for key in dict_words_freq:
    idf[key] = 1

# wikipediaが文章単位でわかれてるので
# 文章ごとにブログ内の単語が存在するかカウントする
for wiki_text in wiki_texts:
  # 同一文章内で複数のカウントをさせないため
  word_list = unique(wiki_text.split(' '))
  for key in dict_words_freq:
    if key in word_list:
        # 文章内にブログの単語があった場合
        if key in idf:
            idf[key] += 1
        
# idf[key]はただのカウントのため
# 上記の計算式通りに計算し直す
for key in idf:
    idf[key] = log(len(wiki_texts)/idf[key]) + 1
    print(idf[key])
た 1.3175143932942954
の 1.0777938305785737
に 1.2131304792181579
を 1.3313370482515308
と 1.4096924908938504
し 1.4009755108032362
て 1.450652175643083
ない 2.5456519664401
から 1.7110617213423613
な 2.1554299751676784
も 1.8192044116505661
こと 2.1342823075479105
サービス 5.17021502327521
が 1.4339912023116144
は 1.0493246452432414
だ 2.732840227719004
けど 6.566265559340465
自分 4.343764024527529

自分のブログ内の文書をみると、「た」などの助動詞より、「サービス」や「自分」などよく使われてなさそうな値が高いことがわかる。(希少性が高い)

TF-IDF値を求める

まずは、先ほど求めたTF値を格納しておく

tf = {}
for key in dict_words_freq:
    tf[key] = float(dict_words_freq[key]/all_words_count)

あとは掛け算するだけなので

tfidf = {}
for key in dict_words_freq:
    tfidf[key] = tf[key] * idf[key]
た: 0.07528653675967402
サービス: 0.07386021461821728
色々: 0.06481618517768223
けど: 0.05862737106553986
に: 0.056323915106557335
もっと: 0.05489118978308473
の: 0.05196505968860981
なぁ: 0.050354351268196505
エンジニア: 0.0496422258575285
ない: 0.045458070829287496
を: 0.045170364137105505
視点: 0.04516931372977967
すごく: 0.043155772268012066
思う: 0.043005388411332744
振り返り: 0.04292777433362566
セールス: 0.040755639825903356
自分: 0.038783607361852934
やら: 0.03853413038011895
....

あれ、、想定では、助動詞とかはよく出るからもっと低いはずが・・・100万行じゃちょっと少なかったのかもしれない。
名詞だけに絞ると

import MeCab

m = MeCab.Tagger('-d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd/')
feature_vector = {}

for k, v in sorted(tfidf.items(), key=lambda x: -x[1]):
    node= m.parseToNode(k)
    while node:
        meta = node.feature.split(",")
        if meta[0] == ("名詞"):
                print(str(k) + ": " + str(v))
        node = node.next
サービス: 0.07386021461821728
エンジニア: 0.0496422258575285
視点: 0.04516931372977967
セールス: 0.040755639825903356
自分: 0.038783607361852934
チーム作り: 0.037493182828802825
やめ: 0.03584693947657752
コミュニティ: 0.03359602203641041
たい: 0.031145458128418263
プロダクト: 0.03067338816850126
こと: 0.030489747250684436
業種: 0.029452854976636772
作り: 0.029273521205558244
ジム: 0.025032815107084036
顧客: 0.02418713487438621

綺麗になった。
今回調べたブログ内の言葉では、「サービス」がぶっちぎりで重要な言葉として抽出されていることがわかる。
また「エンジニア」や「視点」、「セールス」など自分が考えてそうな言葉が出てくるので納得感はある。

IDF値は「+1」の部分でチューニングができるっぽいので、もう少しやってみたい感はあるが次回の理解した時にする。

終わりに

ライブラリに頼らず(一部頼ったけど)、自前で実装するのは勉強になったし面白かった。
文章内の言葉から重要なキーワードを抽出するTF-IDF値は色々なところで使えそうだし、うちのサービスでも次使う予定なので次は実践しながら深掘りしていきたい。

word2vecの理論ついてざっくり理解しつつ試してみる

4、5年前くらいに自然言語処理コミュニティで流行ったword2vecというものがある。
「同じ文章にある単語同士は近しい」という仮定のもと、様々な文章を計算することによって100〜200次元(調整次第)の空間に各単語を「ベクトル」で表せるというものだ。
計算にはニューラルネットワークを用いている。僕には説明できるほど理解度が深まってないので、めちゃ分かりやすい記事を紹介する。

www.randpy.tokyo

こちらも幅広く説明しててわかりやすい。word2vecの何が衝撃だったかも書いてあるので興味がある方はぜ是非。

www.slideshare.net

Wikipediaからモデルを作ってみる

Wikipediaのデータの準備は以前の記事で紹介してある。

kzkohashi.hatenablog.com

比較用にipaの辞書とipa-NEologdの辞書を使う。

from gensim.models import word2vec
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)

# 普通のipa辞書
sentences = word2vec.Text8Corpus('../data/wiki_wakati.txt')
model = word2vec.Word2Vec(sentences, size=200, min_count=20, window=15)
model.save("../data/wiki.model")

# mecab-ipadic-NEologdの辞書
sentences_neo = word2vec.Text8Corpus('../data/wiki_wakati_neo.txt')
model_neo = word2vec.Word2Vec(sentences_neo, size=200, min_count=20, window=15)
model_neo.save("../data/wiki_neo.model")

RNNを利用して大量の単語を200(size)次元に圧縮する。window(文脈の最大単語数)はチューニングする上で重要らしく、ここをいじることで適切なモデルを構築が可能とのこと。

コサイン類似度から近しい単語の抽出

全単語をベクトルで表せるということは、その単語周辺にある単語も抽出できる。「エンジニア」に近しい単語を計算してみる。

from gensim.models import word2vec

model = word2vec.Word2Vec.load("../data/wiki.model")
results = model.wv.most_similar(positive=['エンジニア'])
for result in results:
    print(result)

納得できそうな単語が並んだと思う。

('プログラマー', 0.654212474822998)
('アーキテクト', 0.639656662940979)
('デザイナー', 0.6255037188529968)
('システムエンジニア', 0.6227017641067505)
('エンジニアリング', 0.6170016527175903)
('チーフ', 0.6166958212852478)
('ディレクター', 0.6148343086242676)
('技師', 0.610156774520874)
('コンサルタント', 0.6077147722244263)
('テクニカルディレクター', 0.5718519687652588)

ベクトルによる足し算引き算を行う

もちろん足し算と引き算もできる。実際に「エンジニア」から「アーキテクト」を引いてみるとどうなるかやってみる。イメージとしては、「コーダー」的なイメージだが・・。
まずはipa辞書でやると

from gensim.models import word2vec

model = word2vec.Word2Vec.load("../data/wiki.model")
results = model.wv.most_similar(positive=['エンジニア'], negative=['アーキテクト'])
for result in results:
    print(result)


('ジミ・ヘンドリックス', 0.4125749170780182)
('ミキサー', 0.39917922019958496)
('ドラム', 0.3988112211227417)
('ミックスダウン', 0.3903549015522003)
('フィーリング', 0.38688427209854126)
('ストリングス', 0.38426920771598816)
('フェンダー・ストラトキャスター', 0.3818718492984772)
('ハモンドオルガン', 0.38104212284088135)
('エントウィッスル', 0.3800022602081299)
('ソニー・ロリンズ', 0.37943196296691895)

音楽関連のワードがでてきた・・・。歌って踊れるエンジニアから、踊れるを抜いた状態なのだろうか。
次にNEologdの辞書で行ってみると


プログラマープログラマー
from gensim.models import word2vec
<200b>
model = word2vec.Word2Vec.load("../data/wiki_neo.model")
results = model.wv.most_similar(positive=['エンジニア'], negative=['アーキテクト'])
for result in results:
    print(result)

('ジャコ', 0.38521867990493774)
('苦労', 0.37028828263282776)
('ミキサー', 0.35078269243240356)
('クルー', 0.34380707144737244)
('高所恐怖症', 0.3329920172691345)
('テープレコーダー', 0.3325659930706024)
('運転手', 0.3293205499649048)
('パム', 0.32380378246307373)
('スタッフ', 0.32033008337020874)
('機材', 0.3194393217563629)

音楽関連のジャコ出会って欲しいが、2番目が「苦労」となると「雑魚」の可能性もありえるな・・・。アーキテクトができないエンジニアはダメということか。。

終わりに

僕のチューニング不足であまりよくない結果になってしまったが、word2vecがすごいことは理解できた。
次はもう少し意味のある実践したいなー。

Think Stats(第2版)を読む:3章 確率質量関数

前回の続き。(100日ぶり)

kzkohashi.hatenablog.com

3章:確率質量関数

PMF

確率質量関数のことは英語でPMF(probability mass function)という。
各値ごとに確率が計算され、紐づけられている状態。高校の頃、あるサイコロの目がどの確率でるか・・とやった思い出が微妙にあるけど、それのことだ。
サイコロの1の目と2の目の間には連続した数値はないので、こういった確率変数のことを「離散型」と呼ぶ。また、確率の約束としては全ての確率を足せば「1」になるとだけ覚えておけばいい。

例では、一人めの子供の妊娠周期(濃い青)と二人め以降の子供の妊娠期間(水色)を確率にして棒グラフで表示した。

f:id:kzkohashi:20180728222230p:plain

離散型の分布にもいくつか種類があったり、連続型もあったりするので以下がとても参考になった。

qiita.com

ちなみに、本には「確率は出現度数を標本サイズnで割合したもの」と頭良く書いてあったので覚えておこうと思う。

ヒストグラムPMF以外の可視化

ここまで2章でヒストグラムPMFで可視化してきた。この2つのやり方は「データを探索してパターンや関係を同定しようと試みる上では有用」だが、より細かく特徴を見たりする際には色々工夫が必要とのこと。
さきほどのグラフをみると、35週目から46週目あたりで週ごとに差があることがわかる。一人目の妊娠と二人目以降の妊娠で確率の差を表してみると以下のように一人めの妊娠は41週目以降に生まれる確率の方がたかそうに見える。

f:id:kzkohashi:20180728223356p:plain

このように、まずはヒストグラムPMFなどのグラムで何が起こってるのかがわかれば、見つけたパターンをより明確に可視化する方法を検討できる方法につなげることができる。データサイエンティストの知り合いもまずは全体がわかるようなデータを可視化し、そこからどういう傾向があるか?とか深掘りしていってたので、その通りなんだなと思う。

ただこの本には、基本的にデータの偏りなどもあったり、偶然だったりする場合もあるので簡単に結論づけないようにしたほうがいいとのことだ。

クラスサイズのパラドックス

アメリカの大学では生徒対教師の比率が10:1となっているらしいが、実際に生徒に調べてもらったりすると生徒の比率の方がもっと高い割合になるデータになる。僕的にももっと生徒の比率の方が高くなるだろ・・と思うのだが、それこそがパラドックスである。

そもそもパラドックスとはなにかというと

パラドックス(paradox)とは、正しそうに見える前提と、妥当に見える推論から、受け入れがたい結論が得られる事を指す言葉である。逆説、背理、逆理とも言われる。 パラドックス - Wikipedia

ふむふむ、ようわからん・・・と思い続けて読んでみると

正しい仮定と正しい推論から正しい結論を導いたにも拘らず、結論が直観に反する ものも「パラドックス」と呼ばれる。これは擬似パラドックスと呼ばれ、前述した「真の」パラドックスとは別物である。 例えば誕生日のパラドックスは擬似パラドックスとして知られる。これは「23人のクラスの中に誕生日が同じである2人がいる確率は50%以上」というもので、数学的には正しい事実だが、多くの人は50%よりもずっと低い確率を想像する。他にもヘンペルのカラス、バナッハ・タルスキの逆理などが擬似パラドックスとして知られる。

この擬似パラドックスに近い感覚なのかな?と思う。
これは観察者におけるバイアスがかかってる場合があるということで、そのバイアスを計算する方法だったり、逆にバイアスを戻す方法なども紹介していた。(計算長くなるのでここではやらない)

調べていくうちに、これは観察者バイアスのことでもあるのかなと思い一応メモっておく。

関連する社会科学用語として観察者バイアス(英: observer bias 心理学用語では実験者効果)がある。これは、観察者が見出すことを期待している行動を強調しすぎて、それ以外の行動に気づかないという測定における誤差である。医学の試験で単盲検法ではなく二重盲検法が使われるのはこのためである。観察者バイアスは、研究者が行動を見てその意味を解釈しても、その行動をした本人にとっては何か別の意味があるという場合にも生じる。

観察者効果 - Wikipedia

観察者バイアスは、例えば他人が失敗した際に「努力が足りない、自業自得」と思うにも関わらず、自分が失敗すると「周りの環境が悪い、自分の能力不足のせいではない」というバイアスがかかってしまうことである。めちゃくちゃわかる。
自分を客観視してみる・・というのはこのバイアスを意識し、バイアスから抜けれた時なのかと思うと・・・やっぱり瞑想するしかないのではないかと思う。

それとパラドックスを色々まとめてくれてる面白いサイトがあったので貼っておく。 atarimae.biz

終わりに

今回の章は新しい用語が少なかったが、パラドックスの部分がなかなか理解できず時間をくった。

Jupyter Notebookの後継?Jupyter Labをつかってみた

f:id:kzkohashi:20180730231213p:plain自然言語の処理をする際に、JupyterNotebookと言われるノートブック形式のWebツールを使っている。

jupyter.org

以下のようにPythonのコードを書きながらメモもとれ分析の実行結果(勿論グラフも)もみれるというすぐれものだ。このツールが最初に出たかはわらかないが、Spark(Scala)を動かすためのツールなどもオマージュして作られている。

f:id:kzkohashi:20180726215431p:plain

また、GoogleColaboratoryAWSSageMakerIBMIBM Data Science Experience、MSのMicrosoft Azure Notebooksなどがある。みんな大好きなツールというのはおわかりだろう。

そんなJupyterNotebookに後継っぽいのが出てると聞いて今回は使ってみる。

JuyputerLabについて調べてみる

あまり英語はわからないが、next-generationという単語があるのでおそらく次世代のJupyterなんだろうなと見て取れる。

github.com

開発自体は、2015年ごろから行われていて、今でも割と活発に行われてるっぽい。

f:id:kzkohashi:20180726221140p:plain

JupyterLabをインストールする

さっそくReadme通りにインストールしてみる。

$ pip install jupyterlab
Requirement already satisfied: jupyterlab in /usr/local/anaconda3/lib/python3.6/site-packages
Requirement already satisfied: notebook>=4.3.1 in /usr/local/anaconda3/lib/python3.6/site-packages (from jupyterlab)
Requirement already satisfied: jupyterlab_launcher>=0.4.0 in /usr/local/anaconda3/lib/python3.6/site-packages (from jupyterlab)

よっしゃ!インストールや!ともったら、anacondaPythonをインストールした際にすでに入ってたっぽい。。

If you are using a version of Jupyter Notebook earlier than 5.3, then you must also run the following command after installation to enable the JupyterLab server extension:

jupyterのバージョンが5.3以前の人は引き継がせるためにはコマンドを打つ必要があるらしい。

jupyter --version
4.3.0

4.3なので打つ。

$ jupyter serverextension enable --py jupyterlab --sys-prefix
Enabling: jupyterlab
- Writing config: /usr/local/anaconda3/etc/jupyter
    - Validating...
      jupyterlab  OK

JupyterLabの起動

インストールもおわったので、早速起動してみる。

$ jupyter lab
[I 22:17:21.036 LabApp] JupyterLab alpha preview extension loaded from /usr/local/anaconda3/lib/python3.6/site-packages/jupyterlab
JupyterLab v0.27.0
Known labextensions:
[I 22:17:21.039 LabApp] Running the core application with no additional extensions or settings
[I 22:17:21.046 LabApp] Serving notebooks from local directory: /Users/SayKicho/develop/analysis-practice/word2vec/jupyter
[I 22:17:21.046 LabApp] 0 active kernels
[I 22:17:21.046 LabApp] The Jupyter Notebook is running at: http://localhost:8888/?token=d061cb4b3067b85e8ca007f1ac4cc6130b2cfb908b07a6d5
[I 22:17:21.046 LabApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).
[C 22:17:21.049 LabApp]

    Copy/paste this URL into your browser when you connect for the first time,
    to login with a token:
        http://localhost:8888/?token=d061cb4b3067b85e8ca007f1ac4cc6130b2cfb908b07a6d5

最初のログインがアニメーションになってて、、次世代を感じる。

f:id:kzkohashi:20180726222139p:plain

Notebookを押してみるといつも通りのJupyterNotebookがでるんだけど、なんと・・同じ画面で複数(Tab)開ける!!!以前だと、複数のノートを開くためにはブラウザ自体で新しいタブを開く必要があったのに、もうすでに次世代を感じている。

f:id:kzkohashi:20180726222233p:plain

ConsoleではPythonの実行がコンソール画面でできるのだが、Notebookでいいじゃんと思ったり思わなかったり・・・。

f:id:kzkohashi:20180726225654j:plain

一番次世代感を感じたのは、ドラッグ&ドロップによるセルの移動だ。こいつは便利だ・・・。使い始めたばかりの時からこの機能はホスィと思っていたので嬉しい。

f:id:kzkohashi:20180726224356j:plain

他にデータファイル(CSVなど)をテーブル表示してくれるなどの便利機能があるっぽいが、そもそもJupyterNotebookですらあまり使い込んでないので、便利そうだなーというくらいにしかまだわからない。

終わりに

分析の作業量が少なくJupyterLabの魅力が伝えきれてないし、悪いところもわかってないが、自分がいいなと思う機能を紹介した。もっと分析の作業が増えた時に良いとこ悪いところに気づくと思うので、その際にはまた追加して書く。

vim Extensionも試してみた。 kzkohashi.hatenablog.com

TF-IDFの理論をざっくり理解する①

最近、自社のプロダクトで自然言語処理がよく使われるようになってきたので、勉強も兼ねてまとめてみる。

TF-IDFとは?

tf-idfは、文書中に含まれる単語の重要度を評価する手法の1つであり、主に情報検索やトピック分析などの分野で用いられている。 tf-idfは、tf(英: Term Frequency、単語の出現頻度)とidf(英: Inverse Document Frequency、逆文書頻度)の二つの指標に基づいて計算される。

tf-idf - Wikipedia

Wikipediaだと少し難しい言葉でかいてあるので噛み砕いていくと、ある人のブログから重要度の高い言葉(特徴量)を抽出したいとする。
tfはある人のブログ内の言葉を分解し「頻度」を計算し、
idfはある人のブログ内の言葉がWikipediaの全ての言葉と比べた中での「希少性」を計算する。

どこの言葉と比較してidf(希少性)を計算するかは重要で、本来は他人を含めた全てのブログの言葉を使ったほうがいいと考えられるけど、今回はテストするので取れるデータということでWikipediaにしてある。
図にすると、

f:id:kzkohashi:20180721143506p:plain

こんな感じ・・・なのかな。今はこれくらいの理解にしておこう。
論よりなんたらっていうので、早速Pythonで試してみる。

下準備

自分のブログを元にやってみる。下準備が多めなので別の記事に書いた。

kzkohashi.hatenablog.com

kzkohashi.hatenablog.com

今回は、tf値を測るのを過去に書いた自分のブログをまずは分かち書きにする。

kzkohashi.hatenablog.com

mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd/ -Owakati blog.txt -o blog_wakati_neo.txt -b 16384

別記事にある wiki_wakati_neo.txtを使おうとしたが2000万行あって処理が遅すぎるのでとりあえず適当に100万行くらいにしておく。

head -n 1000000 wiki_wakati_neo.txt > wiki_wakati_neo_head.txt

wiki_wakati_neo_headと先ほど作った blog_wakati_neo.txtを使って色々計算してみる。

ブログのTF値を計算

Pythonもよくわからないのとコードは汚いのでそこはお気にせずに。。
TF値を計算するには、 sklearnにある CountVectorizerを使えばできるぽい

from sklearn.feature_extraction.text import CountVectorizer
from sklearn.decomposition import TruncatedSVD
from sklearn.preprocessing import Normalizer
import numpy as np
import logging

logging.basicConfig(format='%(asctime)s : %(levelname)s : %(message)s', level=logging.INFO)


count_vectorizer = CountVectorizer(input='filename', token_pattern=u'(?u)\\b\\w+\\b')

CountVectorizerはデフォルトで一文字の物を消してしまうため、その対策でtoken_patten設定すると良いみたい。

bag_of_words = count_vectorizer.fit_transform(['../data/blog_wakati_neo.txt'])

fit_transformはBag of Words(単語の袋)と言われる、ある文章における単語の出現回数を表したものに変換する。
以下のように、すすも1回、も2回、もも2回...のように数えてくれる。

[~]mecab
すもももももももものうち
すもも  名詞,一般,*,*,*,*,すもも,スモモ,スモモ
も      助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
も      助詞,係助詞,*,*,*,*,も,モ,モ
もも    名詞,一般,*,*,*,*,もも,モモ,モモ
の      助詞,連体化,*,*,*,*,の,ノ,ノ
うち    名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ
EOS

あとはソートして頻度順にだしたいなーと思って調べてみたらぴったりやりたいことをやってくれてる方がいたのでそちらのを真似させていただく。

medium.com

sum_words = tf.sum(axis=0)
words_freq = [(word, sum_words[0, idx]) for word, idx in count_vectorizer.vocabulary_.items()]
words_freq =sorted(words_freq, key = lambda x: x[1], reverse=True)
words_freq

結果以下になるが、いろんな記事に書かれてたけど単語分割する前にノイズを除去しなきゃいけないと思わせるいい例になってしまった。
助詞などが多くなるのは当たり前か・・。名詞だけにしたいが、idfにおける希少性を利用するとこう行った一般用語は消えると思うので、このまま続けて見る。

[('た', 32),
 ('の', 27),
 ('に', 26),
 ('を', 19),
 ('と', 15),
 ('し', 13),
 ('て', 12),
 ('ない', 10),
 ('から', 10),
 ('な', 10),
 ('も', 8),
 ('こと', 8),
 ('サービス', 8),
 ('が', 6),
 ('は', 6),
省略

------追記------
よくよく計算式を確認したら、単語数数えるだけなのはTF値ではないので、単語ごとにカウントした値を文章全体の単語数で割る必要がある。
たとえば、文章全体の単語数が560で、「た」の場合、
32/560 = 0.05714285714
となる。
-------追記終わり----

ただ、以下のようにCountVectorizerを呼び出す際に低すぎる・高すぎる頻度の単語は除去できるっぽい。
全文章における出現割合なので、小数点になるのかな?

count_vectorizer = CountVectorizer(min_df=0.24, max_df=0.85, input='filename', token_pattern=u'(?u)\\b\\w+\\b')

TF-IDFの計算

TF-IDFの計算はTfidfVectorizerを使えば良い。

vectorizer = TfidfVectorizer(input='filename', token_pattern=u'(?u)\\b\\w+\\b', use_idf=True)
features = vectorizer.fit_transform(['../data/blog_wakati_neo.txt','../data/wiki_wakati_neo_head.txt'])
terms = vectorizer.get_feature_names()
tfidfs = features.toarray()
tfidfs

結果は以下のようにそれっぽさそうな数値になった。

array([[0.00000000e+00, 0.00000000e+00, 0.00000000e+00, ...,
        0.00000000e+00, 0.00000000e+00, 0.00000000e+00],
       [7.10477245e-03, 1.31955447e-03, 2.44020240e-03, ...,
        5.92487576e-06, 1.69282165e-06, 1.69282165e-06]])

実際に上位の10単語を抽出して見る。

def extract_feature_words(terms, tfidfs, i, n):
    tfidf_array = tfidfs[i]
    top_n_idx = tfidf_array.argsort()[-n:][::-1]
    words = [terms[idx] for idx in top_n_idx]
    return words

for x in  extract_feature_words(terms, tfidfs, 0, 10):
        print(x,)

結果はTF値と一緒になってしまった。。。fit_transformあたりでミスってしまったのだろうか・・。長くなりそうなので次回に持ち越し。

た
の
に
を
と
し
て
から
な
ない

終わりに

歯切れは若干わるかったが、理論を理解するという意味だと理解はできたかなと思う。
このままだと悔しいので、次回はTF-IDFの正確な値にチャレンジして見る。

Wikipediaからコーパスを作る

前回、文章を単語分割するためにMeCabをインストースルしたが、大元となるデータ(コーパス)も欲しい。Wikipediaでは全文データをダウンロードすることができるので、それを利用する方法について書いていきたいと思う。

kzkohashi.hatenablog.com

コーパスとは

Wikipediaからの引用によると、

コーパス(英: corpus)は、言語学において、自然言語処理の研究に用いるため、自然言語の文章を構造化し大規模に集積したもの。構造化し、言語的な情報(品詞、統語構造など)を付与している。言語学以外では「全集」を意味することもあり、言語学でも日本語を扱う場合には、「言語全集」「名詞全集」「動詞全集」などと呼ぶとよい[1]。コンピュータ利用が進み、電子化データとして提供している[2]。

引用: コーパス - Wikipedia

何を分析するかで変わるが、整形された元となるデータをコーパスというっぽい。

日本版Wikipediaのデータをダウンロード

Wikipediaクローラーしないで欲しい代わりに、以下のURLで全文データを提供している。

Index of /jawiki/

基本的に最新の方のデータが良いのでlatestディレクトリからダウンロードする。
今回使用するデータは本文データが欲しいので、そちらを使う。

 curl https://dumps.wikimedia.org/jawiki/latest/jawiki-latest-pages-articles.xml.bz2 -o jawiki-latest-pages-articles.xml.bz2

ダウンロードしたデータはXML構造になっているため、XMLをパースするためのWikiextractorを利用する。(作成者様ありがとう)

github.com

git clone git@github.com:attardi/wikiextractor.git
python setup.py install
# ダウンロードしたファイルは人よって場所違うので注意
python WikiExtractor.py jawiki-latest-pages-articles.xml.bz2

上記のコマンドを行うと、textディレクトリ配下にたくさんのデータがばらけてしまうため集約させておく。

find text/ | grep wiki | awk '{system("cat "$0" >> wiki.txt")}'

これでWikipediaの本文データがwiki.txtに出力される。中身を少しだけ見ると

[data]head wiki.txt                                                                                              12:01:28  ☁  master ☂ ⚡
<doc id="3216890" url="https://ja.wikipedia.org/wiki?curid=3216890" title="2015年アムトラック脱線事故">
2015年アムトラック脱線事故

2015年アムトラック脱線事故(2015ねんアムトラックだっせんじこ)は、現地時間で2015512日夜にアメリカ合衆国ペンシルベニア州フィラデルフィ アの付近で、全米鉄道旅客公社(アムトラック)が運行するワシントンD.C.からニューヨークに向かう列車が脱線した鉄道事故である

事故当時、この列車にはおよそ240人が乗っていた。

2015512日の午後910分ごろ、アムトラック北東回廊を運行する北行きの「ノースイースト・リージョナル」 188(ユニオン駅 (ワシントンD.C.)発 ペンシルベニア駅 (ニューヨーク)行き)はフィラデルフィアの30丁目駅を発車した。列車は7両の客車を1年前製造のACS-64型電気機関車 (No. 601) がけん引するものであった。

約11分後、列車はから南東にある複々線の本線を走行しており、ポート・リッチモンド地区にあるフランクフォード・アベニューとウィートシーフ・レーンの交差点の近く、にある4度(半径約440m)の左カーブに進入した。

<doc>タグには記事のURLとタイトルがはいり、その下からは本文となっている。

MeCabを使って分かち書きを出力する

このままだと人間は単語と単語の違いを認識できるが、機械ではできないため単語に分割する。この時に、単語間を空白で区切ることを「分かち書き」という。

MeCabがまだの人はこちらでインスールのやり方を紹介している。 kzkohashi.hatenablog.com

さっそくMeCabを使って先ほどの「wiki.txt」を分かち書きする。

mecab -Owakati wiki.txt -o wiki_wakati.txt -b 16384

だいたい1時間かからずに終わる。
「-b」はメモリサイズを決めるオプションで、以下のようなエラーがでてしまったため付与してある。

input-buffer overflow. The line is split. use -b #SIZE option.

utf8対策やスペースがうまく区切られてないケースがあるらしいのでおまじないで以下のコマンドも打っておく。
nkfのインスールについては省略)

nkf -w --overwrite wiki_wakati.txt

結果はこんな感じ

[data]head wiki_wakati.txt                                                                                       12:01:31  ☁  master ☂ ⚡
< doc id =" 3216890 " url =" https :// ja . wikipedia . org / wiki ? curid = 3216890 " title =" 2015 年 アムトラック 脱線 事故 ">
2015 年 アムトラック 脱線 事故

2015 年 アムトラック 脱線 事故 ( 2015 ねん アムトラック だっ せんじ こ ) は 、 現地 時間 で 2015512 日 夜 に アメリカ合衆国 ペンシルベニア 州 フィラデルフィア の 付近 で 、 全米 鉄道 旅客 公社 ( アムトラック ) が 運行 する ワシントン D . C . から ニューヨーク に  向かう 列車 が 脱線 し た 鉄道 事故 で ある

事故 当時 、 この 列車 に は およそ 240 人 が 乗っ て い た 。

2015512 日 の 午後 910 分 ごろ 、 アムトラック 北東 回廊 を 運行 する 北 行き の 「 ノースイースト・リージョナル 」 188 ( ユ ニオン 駅 ( ワシントン D . C .) 発 ペンシルベニア 駅 ( ニューヨーク ) 行き ) は フィラデルフィア の 30 丁目 駅 を 発車 し た 。 列車 は 7 両 の 客車 を 1 年 前 製造 の ACS - 64 型 電気 機関 車 ( No . 601 ) が けん引 する もの で あっ た 。

約 11 分 後 、 列車 は から 南東 に ある 複々線 の 本線 を 走行 し て おり 、 ポート ・ リッチモンド 地区 に ある フランク フォード ・ ア ベニュー と ウィートシーフ・レーン の 交差点 の 近く 、 に ある 4 度 ( 半径 約 440 m ) の 左 カーブ に 進入 し た 。

無事分かち書きできてるっぽい。ただ「現地時間」が「現地」と「時間」になっているなど、最新の単語に対応していないため比較用に「mecab-ipadic-NEologd」で分かち書きしたものも用意しておく。

mecab -d /usr/local/mecab/lib/mecab/dic/mecab-ipadic-neologd/ -Owakati wiki.txt -o wiki_wakati_neo.txt -b 16384
nkf -w --overwrite wiki_wakati_neo.txt

結果も見とく

[data]head wiki_wakati_neo.txt                                                                                   21:18:06  ☁  master ☂ ⚡
< doc id =" 3216890 " url =" https :// ja . wikipedia . org / wiki ? curid = 3216890 " title =" 2015年アムトラック脱線事故 ">
2015年アムトラック脱線事故

2015年アムトラック脱線事故 ( 2015 ねん アムトラック だっ せんじ こ ) は 、 現地時間 で 2015512日 夜 に アメリカ合衆国 ペンシルベニ ア州 フィラデルフィア の 付近 で 、 全米鉄道旅客公社 ( アムトラック ) が 運行 する ワシントンD.C. から ニューヨーク に 向かう 列車 が 脱線 し た 鉄道事故 で ある

事故 当時 、 この 列車 に は およそ 240人 が 乗っ て い た 。

2015512日 の 午後910分 ごろ 、 アムトラック 北東回廊 を 運行 する 北 行き の 「 ノースイースト・リージョナル 」 188 ( ユニオン駅 ( ワシントンD.C. ) 発 ペンシルベニア 駅 ( ニューヨーク ) 行き ) は フィラデルフィア の 30 丁目 駅 を 発車 し た 。 列車 は 7 両 の 客車  を 1年前 製造 の ACS - 64 型 電気機関車 ( No. 601 ) が けん引 する もの で あっ た 。

約 11分 後 、 列車 は から 南東 に ある 複々線 の 本線 を 走行 し て おり 、 ポート ・ リッチモンド 地区 に ある フランクフォード ・ アベ ニュー と ウィートシーフ・レーン の 交差点 の 近く 、 に ある 4度 ( 半径 約 440m ) の 左 カーブ に 進入 し た 。

「2015年アムトラック脱線事故」は一つの固有名詞になってしまうのか・・・。正しい気はするが、利用方法によっては扱いづらくなりそう。「現地時間」はしっかりと「現地時間」と単語に分割されたのでこちらのほうがあってそうな感はある。

終わりに

MeCabのインストールとWikipediaからコーパスを生成することができたのでこれで下準備が整った。
次回はTF-IDFの説明でも書こう。