データから確率分布のパラメーターを推定する

データから、そのデータを生成した背景にある確率分布を推定したいことはよくあります。
正規分布やポアソン分布を仮定するのであれば、簡単ですが、多くの分布では結構面倒です。
そこで、scipyのstatsにある、fitとという便利な関数を使って最尤推定します。

今回はベータ分布を例に取り上げます。
公式ドキュメントはここです。
scipy.stats.rv_continuous.fit
ここ、ベータ関数を使ったサンプルも乗ってるんですよね。
初めて読んだ時はもっと早く読めばよかったと思いました。

それでは、真の分布を設定して、そこからデータを生成し、パラメーターを推定してみます。


# モジュールのインポート
import numpy as np
import matplotlib.pyplot as plt
from scipy.stats import beta

# これから推定したい真の分布
frozen_beta_true = beta.freeze(a=3, b=7, loc=-2, scale=4)
# 真の分布に従うデータを生成
data = frozen_beta_true.rvs(500)

# データから最尤推定 (全パラメーター)
fit_parameter = beta.fit(data)
print(fit_parameter)
# 出力
# (2.548987294857196, 4.380552639785355, -1.946453704152459, 3.1301112690818194)

bの値と、scaleがちょっと乖離が大きいかなと感じられるのですが、
結構妥当な値が推定できました。

経験上、ベータ分布を使いたい時は、取りうる値の範囲が決まっていることが多いです。
そのため、locやscaleは固定して推定を行いたいのですが、
その時は、パラメーターにfをつけて、fitに渡すと、
それらのパラメーターは固定した上で残りを推定してくれます。


# データから最尤推定 (loc と scaleは指定する)
fit_parameter = beta.fit(data, floc=-2, fscale=4)
print(fit_parameter)
# 出力
# (3.1998198349509672, 7.4425425953673505, -2, 4)

かなり真の値に近い結果が出ました。
最後に推定した確率分布の確率密度関数を可視化してみましょう。


# 推定したパラメーターで確率分布を生成
frozen_beta = beta.freeze(*fit_parameter)

# 可視化
plt.rcParams["font.size"] = 14
x = np.linspace(-2, 2, 51)
fig = plt.figure(figsize=(8, 5))
ax = fig.add_subplot(1, 1, 1, xlim=(-2, 2), title="scipyによる最尤推定")
ax.plot(x, frozen_beta_true.pdf(x), label="真の分布")
ax.plot(x, frozen_beta.pdf(x), label="推定した分布")
ax.hist(data, bins=30, alpha=0.7, density=True, label="サンプルデータの分布")
ax.legend()
plt.show()

出力されたのがこちらの図です。
うまく推定されているように見えますね。

pythonを触り始めたばかりの頃は、scipyをうまく使えず、
確率分布はnumpyでスクラッチで書いて、この種の推定もゴリゴリ自分で実装していました。
(かなり効率の悪いアルゴリズムで)
fitを知ってからも、しばらくは4つの戻り値のどれがaでどれがlocなのかよくわからなかったり、
locやscaleを固定する方法を知らず長いこと敬遠していたのですが、
ちゃんとドキュメントを読めば全部書いてあるものです。

サーチコンソールに登録する

まだほんの数名のようですが、このブログにも検索から来てくださる人が現れ始めたようです。

今の所、手元のメモ書きを適当に転記して記事にしているような状態なのですが、
ニーズのある記事を優先して上げたほうがいいと思いますのでどんなキーワードで検索されているのかは確認したいところです。
ということで、サーチコンソールを使い始めました。

以下が導入手順です。

    1. サーチコンソールのサイトにアクセス
    2. 今すぐ開始 ボタンをクリック
    3. Google Search Console へようこそのダイアログにサイトURL入力(https://analytics-note.xyz/)
    4. プロパティを追加ボタンをクリック

本当はこの後、サイトの所有権を確認して登録完了の予定だったのですが、
次のメッセージが表示されて自動的に登録完了しました。
同じアカウントでGAを使っていると自動的に確認してくれるようです。

所有権を自動確認しました
確認方法:
Google Analytics

ただし、次のメッセージも表示されたので念のためやっておきます。

確認状態を維持するために、gtag.js トラッキング コードを削除しないでください。
確認状態を維持するために、設定 > 所有権の確認 で複数の確認方法を追加することをおすすめします。

設定 > 所有権の確認 > HTMLタグ
と選択すると、下記のタグが表示されます。


<meta name="google-site-verification" content="ここにキーが表示される"/>

このキーの部分をWordpressの All in One SEO Pack の Google Search Console: の設定に追加して完了です。

BeautifulSoupを使って不要なタグとルビを取り除く

以前の記事で、青空文庫から取得したテキストの文字化けを治しました。
次は、不要なタグを除去します。

正規表現でやってしまえば早いのですが、せっかくなので、BeautifulSoupの使い方の確認も兼ねてこちらを使ってみました。

前提として、
htmlという変数に、銀河鉄道の夜のページのソースが入っているものとします。


# ライブラリのインポートと、soupオブジェクトへの変換
from bs4 import BeautifulSoup
soup = BeautifulSoup(html)

soup.find([タグ名]) や、 soup.find(class_=[class名])で、中のタグを指定することができます。
さらに、get_text()関数を使うと、タグを取り除いた文字列が表示されます。
これで div や h1,h2,…や、a,brタグなど不要タグはほぼほぼ除去できます。
ついでに、不要な前後の空白をstrip()で取り除いて、
300文字を表示してみましょう。


print(soup.find(class_="main_text").get_text().strip()[:300])

# 結果
一、午后(ごご)の授業

「ではみなさんは、そういうふうに川だと云(い)われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊(つる)した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指(さ)しながら、みんなに問(とい)をかけました。
 カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなことも

さて、残りは 午后(ごご) などのルビです。

これも不要なので取り除きます。
該当部分のソースコードを見ると、下記のように、ruby, rb, rt, rpの4つのタグがあります。
このうち、 rubyとrbは、タグの中身は残したいので、get_text()で取り除けば十分ですが、rbとrtはタグとその中身を消す必要がります。


一、<ruby><rb>午后</rb><rp>(</rp><rt>ごご</rt><rp>)</rp></ruby>の授業

それには、decompose関数を使用します。


for tag in soup.findAll(["rt", "rp"]):
    # タグとその内容の削除
    tag.decompose()

参考ですが、タグだけを消して、中身を残す時はunwarpを使います。
(昔はreplaceWithChildrenという名前だったメソッドです。pep8対応のためにリネームされたとか。)
hxタグとかbrタグとか、これを使って消してたこともあるのですが、get_text()を使うようになっていらなくなりました。

これで取り除けたはずなので、もう一度本文を表示します。


print(soup.find(class_="main_text").get_text().strip()[:300])

# 結果

一、午后の授業

「ではみなさんは、そういうふうに川だと云われたり、乳の流れたあとだと云われたりしていたこのぼんやりと白いものがほんとうは何かご承知ですか。」先生は、黒板に吊した大きな黒い星座の図の、上から下へ白くけぶった銀河帯のようなところを指しながら、みんなに問をかけました。
 カムパネルラが手をあげました。それから四五人手をあげました。ジョバンニも手をあげようとして、急いでそのままやめました。たしかにあれがみんな星だと、いつか雑誌で読んだのでしたが、このごろはジョバンニはまるで毎日教室でもねむく、本を読むひまも読む本もないので、なんだかどんなこともよくわからないという気持ちがするので

綺麗にルビが消えました。

t-SNEでDigitsを次元圧縮して可視化してみた

特に意図はないのですが、これまで高次元のデータを次元削減して可視化する時はPCAをよく使っていました。
基本的には線形変換なので、非線形な構造を持ってるデータはうまく特徴を捉えられません。
(それはそれで確認する意味があると思いますが。)

最近は、t-SNEという手法を使っている人が多いようなので、やってみたメモです。
irisだとPCAで十分うまく次元削減できてしまうので、今回はdigitsを使います(8*8の手書き数字画像データ)

t-SNEの論文

t-SNE自体の実装は、scikit-leearnを使います。
ドキュメントはここ


# ライブラリインポート
from sklearn.datasets import load_digits
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt

# データ準備
digits = load_digits()
X = digits.data
y = digits.target

# t-SNEの実行
tsne = TSNE(n_components=2)
X_tsne = tsne.fit_transform(X)

# 可視化
x_max, x_min = X_tsne[:, 0].max() * 1.05, X_tsne[:, 0].min() * 1.05
y_max, y_min = X_tsne[:, 1].max() * 1.05, X_tsne[:, 1].min() * 1.05
fig = plt.figure(figsize=(10, 10))
ax = fig.add_subplot(1, 1, 1, xlim=(x_min, x_max), ylim=(y_min, y_max))
ax.set_title("t-SNE")
for i, target in enumerate(y):
    ax.text(X_tsne[i, 0], X_tsne[i, 1], target)
plt.show()

これを実行して表示される画像がこちらです。

一部、変なところに分類されている数字があったり、1が複数のグループに分かれていたりするところはありますが、
非常に見事に分類できています。
これを好んで使う人がいるのも納得です。
高次元のデータの可視化のツールとして提唱されているだけはあります。

ちなみに、PCAで2次元に圧縮したのがこれ。

t-SNEと全く違う結果になっていますね。
(だからといって、PCAという手法自体が劣るというわけではないので注意です。)

pipでライブラリをアップデートする

pipの使い方メモです。

まず、インストール済みのパッケージについての情報は pip list で確認できます。
更新版があるパッケージのみ出力するオプションは -o または --outdatedです。


$ pip list --outdate
Package Version Latest Type
------------------ --------- ---------- -----
alabaster 0.7.11 0.7.12 wheel
astroid 2.0.4 2.1.0 wheel
astropy 3.0.4 3.1.1 wheel
beautifulsoup4 4.6.3 4.7.1 wheel
bleach 2.1.4 3.1.0 wheel
bokeh 0.13.0 1.0.4 sdist
certifi 2018.8.24 2018.11.29 wheel
click 6.7 7.0 wheel
~~~ 以下略 ~~~

アップデートしたいパッケージを決めたら、
pip install に、
 -U か --upgrade のどちらかのオプションをつけてパッケージを指定し実行するとアップデートできます。

例:


$ pip install --upgrade scikit-learn

SNSシェアボタンを追加しました

数日前からこのブログの各記事にSNSへのシェアボタンを追加しました。

ボタンの追加はWordpressの、AddToAny Share Buttons というプラグインを使っています。

とりあえずほとんどデフォルト設定のままで、変更したのはLineへのシェアボタンを表示した点だけです。

requestsのレスポンスが文字化けする場合に文字コードを修正する

非常に手軽にhttpアクセスができるrequestsですが、日本語の文書を取得する時に文字コードが正常に取れないケースがあります。

たとえば、今回は青空文庫の羅生門のページで発生しました。


import requests
url = "https://www.aozora.gr.jp/cards/000879/files/127_15260.html"
response = requests.get(url)
html = response.text

これで取得したhtml変数の中身を見るとひどいことに。

~略~
<div class="main_text"><br/>\r\n\x81@\x82\xa0\x82é\x93ú\x82Ì\x95é\x95û\x82Ì\x8e\x96\x82Å\x82\xa0\x82é\x81B\x88ê\x90l\x82Ì<ruby><rb>\x89º\x90l</rb><rp>\x81i</rp><rt>
~略~

問題は文字コードを正常に取れていないことのようです。
サイトのメタタグでは Shift_JIS が指定されていますが、
print(response.encoding)
を実行すると、
ISO-8859-1
が戻ってきます。

このような時は、apparent_encodingを使います。
ドキュメントを見る限りでは他のライブラリの機能を取り込んでるようですね。

response.apparent_encoding に、正しい文字コードである SHIFT_JISが格納されているので、
これをencodingにセットしてあげれば大丈夫です。


import requests
url = "https://www.aozora.gr.jp/cards/000879/files/127_15260.html"
response = requests.get(url)
# この下の行を追加
response.encoding = response.apparent_encoding
html = response.text

これで、htmlに文字化けしていないテキストが入りました。

requestsを使って、Webサイトのソースコードを取得する

今回はとりあえず単純に httpで getするだけのコードを紹介します。
サンプルとして、yahooのトップページのHTMLを取得します。

利用するのは、 requests というpythonのライブラリです。
ドキュメントにある通り、超手軽に使えます。

こちらのコードで、htmlという変数に結果が入ります。


import requests
url = "https://www.yahoo.co.jp/"
response = requests.get(url)
html = response.text