20200530のPythonに関する記事は30件です。

Python/Tkinterで作るクラスプラットフォームGUIソフトウェア!(と数多くのTryアンドError)の入門!(執筆途中)

はじめに...

タイトル長くてスミマセン!!!!!
書きたいことの要約って難しいですよね...
誰か、要約案を私にお恵みください...
(ちなみに、この後も長いです。目次をご活用ください。)
あと、執筆途中であることに十分ご留意ください。

Python/Tkinterとは?

出典: フリー百科事典『ウィキペディア(Wikipedia)』

Tkinter は Python からGUIを構築・操作するための標準ライブラリ(ウィジェット・ツールキット)である。

Tcl/Tk の Tk 部分を Python で利用できるようにしたもので、使い方も可能な限り Tcl/Tk にあわせられるように作られている。
これにより、スクリプト言語である Python から簡単にGUI画面をもったアプリケーションを作ることが可能になる。

(以下略) 

ってWikipediaに書いてありますが、まあ、カンタンに言うと

Pythonに標準で付属している、GUI作成用ライブラリ

である。
大事なのは"標準"というところ。
(他のライブラリを使わなければ)pythonをインストール(またはPyinstallerとかを使う)だけでアプリケーションが作れる。
なんなら、Pyarmorを使えばソースコードを保護できる。

Tkinterリファレンス

ウィンドウ

まずはモジュールのインポートから...

#Python2なら...
import Tkinter #tkinter 本体
import ttk #tkinter 拡張(?)
#Python3なら...
import tkinter #tkinter 本体
from tkinter import ttk #tkinter 拡張(?)

Python2と3でモジュール名が違うので注意。
ちなみに、次のように書くと2,3両対応できる。

try:
    import Tkinter as tkinter
    import ttk
except:
    import tkinter
    from tkinter import ttk

ちなみに、先程からちょこちょこ登場している"ttk"はtkinterの拡張(?)で、
これを使うと、スタイル(テーマ)を変えることができます。

メインウィンドウ

次に、メインウィンドウを作る。

root = tkinter.Tk()#この後をよく読むこと

ただし、このままだとUbuntu等のOSでタスクバーのアプリケーション名が"Tk"と表示されダサいので、

root = tkinter.Tk(className="[アプリ名]")

とする。

サブウィンドウ

sub = tkinter.TopLevel()

とする。

全画面表示

root.attributes("-fullscreen", True)

戻すときは、

root.attributes("-fullscreen", False)

ウィジェット

ボタン

def callback():
    print("Hello!!")

button = ttk.Button(root, text="[表示するテキスト]",command=callback)

commandには、ボタンが押されたときに実行する関数の名前を指定する。
もし、引数を指定したい場合は、

def callback(hogehoge):
    print(hogehoge)

button = ttk.Button(root, text="[表示するテキスト]",command=lambda: callback("Hello!!!"))

フレーム(枠)

frame = ttk.Frame(root)

えーっと... frameはウィンドウと他のウィジェットのrootの部分に入れて使います。
なんなら、色やら形を変えることもできます。(後で追記します。)
下に活用例を示します。

def callback(hogehoge):
    print(hogehoge)

frame = ttk.Frame(root)
button = ttk.Button(frame, text="[表示するテキスト]",command=lambda: callback("Hello!!!"))

ラベル(一行の選択できないテキスト)

label = ttk.Label(root, text="[表示するテキスト]")

ウィジェットの配置

tkinterではウィジェットを定義したあとに配置する必要があります。
配置方法は3つあります。

詰め込み方式(pack)

詰め込み方式(pack)では、ウィンドウの空いているところにウィジェットをツメコミマス。(語彙力...)

[ウィジェット].pack([オプション])

[ウィジェット]はウィジェットのインスタンス、
(インスタンスやら関数などがワケワカメな人はぜひ他のPython入門記事をご覧ください。)
[オプション]には次が指定できます(何も指定しなくても問題ない。):
・side
 どの方向に配置するか設定できます。
 設定可能値:
  "right"右,"left"左,"bottom"下,"top"上
・fill
 どの方向に対して埋め立てるか設定できます。
 設定可能値:
  "both"-両方向
  "x"-x方向(縦?)
  "y"-y方向(横?)
  (正直、不安なので実験して教えていただけると幸いです。)
・anchor
 どこに配置するか設定できます。(sideに似ていますが、より具体的)
 設定可能値:
  えーっと...こんな感じです。(ここより引用)
  nw --- n --- ne
  |      |
  w  c   e
  |      |
  sw --- s --- se
・expand
 空白が生まれたとき(ウィンドウのサイズが変わったときなど)に埋めるかどうか設定できます
 設定可能値:
  True or False
  (面倒臭さが表立っているのは気のせいですからね...本当ですよ......)

参考文献など

http://www.nct9.ne.jp/m_hiroi/light/python3.html#python3_tkinter

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kintoneと連携した LINE PAY API v3 のサンプルアプリを試してみる

@stachibana さんのこちらのアプリを試した際の備忘録
https://github.com/stachibana/line-pay-v3-python-sdk-sample
スクリーンショット 2020-11-04 0.43.52.png
スクリーンショット 2020-11-04 0.44.24.png

紆余曲折ありましたが、これをForkしてLINE Pay の一般決済(単発決済)だけを実装するためのスターターアプリができました。
https://github.com/maztak/line-pay-v3-python-starter

LINE Pay 概要

オーソリってなに?

ユーザーが決済を承認した後の売上確定待ち状態。これをCaputureすることで売上確定となる。

Captureとは何?

Captureとは売上確定のことらしい。RequestAPIの利用時のrequest_optionsの値の中でoptions.payment.captureをFalseにすると、オーソリ状態(売上確定待ち状態)で止めることができる。それがどんなときに嬉しいかは知らん。

売上確定するにはCaptureAPIを使うことになるのだが、加盟店MyPageの売上管理>売上対象照会からも手動で売上確定することが可能(なお以下はテストアカウントで本番決済したときの画面。Sandbox環境で模擬決済した場合はナビメニューの「Sandbox」から模擬決済の管理画面に入れる)。
スクリーンショット 2020-07-05 20.02.15.png

Checkoutとは何?

一般用語としてや他の決済サービス(StripeとかPayjpとか)では、決済することそのもの、もしくは決済を簡単に実装できるライブラリのことを言うらしい。しかし、LINE Pay における Checkout は、ユーザーの住所とか送料を含めた決済機能のことを指すらしい。つまりECで商品の送付先を入力する作業を Checkoutを使えば省けるっぽい。

LINE Pay API v2 と v3 の違い

公式リファレンス Migration V3
https://pay.line.me/documents/online_v3_ja.html#migration-api-v3

Developers.I.O さんの記事
https://dev.classmethod.jp/articles/devio-cafe-line/

LINE Pay 加盟店アカウントを取得

正式なアカウントは法人または個人事業主でしか取得できないので、以下からSandboxアカウントを取得します
https://pay.line.me/jp/developers/techsupport/sandbox/testflow?locale=ja_JP

取得後のログイン画面
https://pay.line.me/portal/jp/auth/login

Sandboxについて

LINE Pay にはテストができるSandbox環境が用意されているが、用語がごちゃごちゃしていて混乱しやすいので注意。

Sandbox環境とproduction環境

Sandboxアカウントのチャネル情報(Channel ID, Channel Secret Key)を使っている環境のこと。
Sandbox環境で使用できるチャネル情報をテストチャネル情報という。

Sandobxシミュレーター

これは LIEN Pay の決済シミュレーターのことで、Sandbox環境がTrueの時に決済しようとするとこれが起動する。

加盟店MyPageの取引内訳

SandboxアカウントでSandboxシミュレーターを使って模擬決済した場合、加盟店MyPageに入ってナビバーにある「Sandbox」をクリックしたページで取引内訳が確認できる。
スクリーンショット 2020-11-02 19.15.53.png

しかしSandbox環境でもLINE_PAY_IS_SANDBOX = Falseにしていると実際のお金が決済できてしまう(本サンプルアプリ特有のものかもしれない)。この場合は加盟店MyPageの「Sandbox」ページではなく、加盟店MyPageのホーム画面に取引内訳が記録される。

app.py
LINE_PAY_IS_SANDBOX = False

さらに、Sandboxアカウントと正式な加盟店アカウントでは加盟店MyPageの表示が違う。Sandboxアカウントでは取引の一覧が見れるだけだが、正式な加盟店アカウントのMyPageではグラフが見れる。

Herokuにデプロイしてみる

Webサービスとして運用することを想定したとき、当然どこかのサーバーにデプロイする必要があるので、今回は定番のHerokuを使います(LINE PAY API v2では決済サーバIPのホワイトリスト登録が必要だったので、固定IPではないHerokuは不向きでしたが、v3からはホワイトリスト登録不要になった)。

Herokuで動かすにはgunicornが必要

Herokuにデプロイしてapp.pyをRunした状態にしておきたい。今回はFlaskを使っている関係で実行命令ファイルProcfileにgunicornというフレームワーク?ライブラリ?が起動するように記述する。

Flaskってなに

Pythonのミニマムフレームワーク。Rubyでいう Ruby on Rails のちっさい版。

Procfileってなに

HeorkuデプロイしたPythonアプリの実行命令を書くファイル。

gunicornってなに

Flaskを始めとする多くのPythonフレームワークではWSGIサーバーというものでアプリを動かすらしい。このWSGIサーバーとのやり取りをいい感じにしてくれるライブラリがgunicornっぽい。普通にプロジェクトフォルダ直下にProcfileって名前のファイル作成すればよかったはず‥。
スクリーンショット 2020-11-04 0.34.54.png

web: gunicorn app:app

Gitコミットして Heroku に push

$git init
$git add .
$git commit -m "initial commit"

$ heroku login --interactive
$ heroku create my-app-name #任意のアプリ名を付けてください
$ git push heroku master

環境変数の設定

.gitignore.envファイルをgit管理から無視するようにしているため当然Herokuにpushされません(僕は知らなかった)。そのためHeroku側でheroku config:setで環境編集をセットしてやる必要があります。

$heroku config:set LINE_PAY_CHANNEL_ID="xxx"
$heroku config:set LINE_PAY_CHANNEL_SECRET="xxx"

$heroku config #一応、環境変数が間違いなくセットされてるか確認しよう

トップページがエラーになるときは、この環境変数のセットし忘れであることが多い。

つまづきポイント

line-payがアップデート

LINE Pay のライブラリである line-pay が 0.0.1 -> 0.1.0 にアップデートしていた。requirements.txtでline-payライブラリのバージョンをあげましょう。

requirements.txt
line-pay==0.1.0

ちなみに僕のrequirements.txtは、なんかの理由でpip freezeというコマンドを使ったらしく以下のようになってました。これをHerokuにデプロイして動いているので、何か必要なことがあったのかも‥。

requirements.txt
certifi==2020.4.5.1
chardet==3.0.4
click==7.1.2
dominate==2.5.1
Flask==1.1.2
Flask-Bootstrap==3.3.7.1
gunicorn==20.0.4
idna==2.8
itsdangerous==1.1.0
Jinja2==2.11.2
line-pay==0.1.0
MarkupSafe==1.1.1
pykintone==0.3.10
python-dotenv==0.10.3
pytz==2020.1
PyYAML==5.3.1
requests==2.22.0
tzlocal==2.1
urllib3==1.25.9
visitor==0.1.3
Werkzeug==1.0.1

Request後のリダイレクトエラー

リダイレクト先がhttpssになるエラーが起きた。以下はGitコミットの差分ですが、replace()でhttpsに統一しようとしている部分を消したらちゃんと動きました。ただ決済時にhttp通信をすること自体がやばいので、あくまで応急処置です。

app.py
         "redirectUrls": {
-            "confirmUrl": request.host_url.replace('http', 'https') + "confirm",
-            "cancelUrl": request.host_url.replace('http', 'https') + "cancel"
+            "confirmUrl": request.host_url + "confirm",
+            "cancelUrl": request.host_url + "cancel"
         }

kintoneのアプリ名は一意に

kintoneアプリは過去に一度でも使ったことがあるアプリ名を使用すると動かない。今回kintoneアプリ名には「linepay」が設定されています。例えばこのアプリを一度削除し、もう一度テンプレートからアプリ作成したとしても本サンプルアプリは動いてくれません(トップページの表示や決済はできる)。そのためkintoneアプリの設定画面からアプリ名を過去に使用していないものに変更してやる必要があります。
スクリーンショット 2020-11-03 23.27.54.png
スクリーンショット 2020-11-03 23.28.14.png

その他のつまづきポイント

  • account.ymlのドメイン名はサブドメインだけでよかった
  • Herokuデプロイ前に一度ローカルで試す場合はPythonのバージョンが3.6以下だと動かない
  • デプロイ直後(もしくは初回)に決済しようとすると Internal Server Error となる。原因不明。
  • line pay sandbox 申請が最初数回エラーになった。何回かやってれば直った。

役立ちそうなコマンド

Herokuとのgit紐付け

# herokuからのクローン
heroku git:clone APPNAME

# herokuとローカルの紐付け
heroku git:remote --app APPNAME
# こっちでもOK
git remote add heroku https://git.heroku.com/AppName.git

Herokuの再起動方法

$ heroku ps:scale web=0
# 再びスタートするときは↓
$ heroku ps:scale web=1

Herokuのキャッシュ削除方法

間違ってるかも。


$ heroku plugins:install heroku-repo
$ heroku repo:purge_cache -a appname

$ git commit --allow-empty -m "Purge cache"
$ git push heroku master

https://help.heroku.com/18PI5RSY/how-do-i-clear-the-build-cache

LINE Pay v2 の実装サンプル

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JupyterLab で Jupyter notebook(ipynb)の 差分を見やすく Git管理する

TL;DR

Jupyter Notebook で分析を進める際、バージョン管理をしたいと思いGitで管理をしてみましたが、普通に進めるとNotebook のメタデータによって差分がとても見づらかったので、JupyterLab の jupyterlab-gitnbdimeエクステンションを利用し、差分を見やすく表示できるようにしてみました。

JupyterLab 環境を構築

今回の記事では、下記の環境を利用します。

  • docker-compose を使用
    • docker-compose のインストールは こちら を参照
  • コンテナイメージのベースは kaggle-images を利用

Jupyter notebook のバージョンコントロールに必要な JupyterLab エクステンションは下記になります。

  • jupyterlab-git
  • nbdime
    • こちらは jupyterlab-git をインストールすると一緒にインストールされる

環境構築

下記2ファイルを作成します。

  • Dockerfile
  • docker-compose.yml
Dockerfile
FROM gcr.io/kaggle-images/python:v74

RUN apt-get update && \
    apt-get install -y git \
    curl

RUN curl -sL https://deb.nodesource.com/setup_12.x | bash - &&\
    apt-get install -y nodejs

RUN pip install -U pip \
    jupyterlab && \
    pip install jupyterlab-git

RUN jupyter lab build
docker-compose.yml
version: "3"
services:
  jupyter:
    build: .
    volumes:
      - $PWD:/tmp/work
    working_dir: /tmp/work
    ports:
      - 8888:8888
    command: jupyter lab --ip=0.0.0.0 --allow-root --no-browser

Docker イメージのビルド

上記2ファイルを作成後、同ディレクトリにてビルドします。

$ docker-compose build

コンテナを起動

ビルド後コンテナを起動します。

$ docker-compose up

起動後は http://localhost:8888/ にアクセスし、token を入力して JupyterLab にアクセスできます。
token とは起動後に出力される、例:http://acb729d0c5ce:8888/?token=45d10c660d2e85f0c8d59995a04667c154542ae79f27f65d45d10c660d2e85f0c8d59995a04667c154542ae79f27f65d にあたる箇所です。

Extension Manager を Enable

起動後は Exxtension Manager を Enable します。

image.png
2つのエクステンションがインストールされています。
image.png

Notebook を Git でバージョン管理をする

Git リポジトリ を Clone

必要なリポジトリをクローンします。すでに Notebook などがある場合は git init などをします。

image.png

リポジトリの URL を入力
image.png

Notebook (test.ipynb) を作成して first commit します。

$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name"
$ git add test.ipynb
$ git commit -m "first commit"

first commit 後、 Notebook で分析を進めたとします。例えば df.head() というコードを追加したとします。

git diff での差分表示

まず、git diff コマンドで確認した場合は、下記のように Notebook のメタデータなどの差分が表示されてしまいとてもわかりづらいです。

# git diff
diff --git a/test.ipynb b/test.ipynb
index f6c1f17..5af6074 100644
--- a/test.ipynb
+++ b/test.ipynb
@@ -2,7 +2,7 @@
  "cells": [
   {
    "cell_type": "code",
-   "execution_count": 1,
+   "execution_count": 6,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -21,7 +21,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 7,
    "metadata": {},
    "outputs": [],
    "source": [
@@ -30,12 +30,164 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 8,
    "metadata": {},
    "outputs": [],
    "source": [
     "df = pd.read_csv(data_dir + \"train.csv\")"
    ]
+  },
+  {
+   "cell_type": "code",
:

JupyterLab nbdime での差分表示

JupyterLab にて nbdime を利用して diff を確認した場合は下記になります。ピンクの左側は変更前、緑の右側は変更後になります。
image.png

とても見やすく差分が表示されていると思います。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでNOMURAコンテスト2020を解きたかった

はじめに

おひさしぶりです。学校の課題に追われてなかなか精進できませんでした(言い訳)
A,Bの二完でした。しかも、それぞれ1ペナずつ生やした。

A問題

問題

考えたこと
$m1>m2$のときはそのまま引き算できないので、$h1$から1取ってくる。

h1, m1, h2, m2, k = map(int,input().split())

if m1 > m2:
    m2 += 60
    h2 -=1
    ans_m = m2 - m1
else:
    ans_m = m2 - m1

ans_h = h2 - h1
ans = 60 * ans_h + ans_m

print(ans-k)

引き算の順序を間違えて1WAした。もったいない

B問題

問題

考えたこと
ヒタチコンの悪夢再来、こういう系の問題は苦手。
全てDにするのが最適ですが、私は気付かなかったので、if文でごり押ししてます。

t = list(input())
n = len(t)

if t[0] == '?':
    if n > 1:
        if t[1] == '?':
            t[0] = 'P'
        else:
            t[0] = 'D'
    else:
        t[0] = 'D'
if t[-1] == '?':
    t[-1] = 'D'
for i in range(1,n-1):
    if t[i] == '?':
        if t[i-1] == 'P':
            t[i] = 'D'
        else:
            if t[i+1] == '?' or t[i+1] == 'D':
                t[i] = 'P'
            else:
                t[i] = 'D'

ans = ''.join(t)
print(ans)

まとめ

C解けたらかっこよかった。
明日のABCが不安です。ではまた、おやすみなさい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習で住宅価格が予測できるなら「あつ森」の家具の価格も予測できるんじゃないか説

はじめに???

kaggleのデータセットでこれを見つけてしまって、試さずにはいられませんでした。
データセットはこちらです。
? https://www.kaggle.com/jessicali9530/animal-crossing-new-horizons-nookplaza-dataset
スクリーンショット 2020-05-30 18.09.14.png
あつまれどうぶつの森(以下、あつ森)のマスターデータといった感じのデータセットです。
家具やむし、さかな、などはもちろん村人(どうぶつ)のデータも入っています。

AutoMLで分析?

今回は無料で使えるAutoMLツールのVARISTAを利用していきます。
? https://www.varista.ai/
スクリーンショット 2020-05-30 18.13.51.png

さっそく分析開始?

まずはデータをアップロードして中身を確認してみましょう。
「housewares.csv」を利用します。
3,275行 x 32列 897.56 KBの家具の情報が入ってるデータです。
スクリーンショット 2020-05-30 18.18.11.png
予測する列は売却価格の「Sell」です?
なお、答え合わせように10行ほどランダムにピックアップした行を抽出しておきます。

学習に利用しない列を選んでいきましょう。

学習に利用しない列?

ユニークな値の列を削除

削除列 理由
Name 完全ユニークではないが売却金額が予想できるほぼ固有の値なので削除
Source 家具の入手方法、個を特定できるため削除
Source Notes 同上
Filename ゲーム内で参照するファイル名、意味のない文字列のため削除
Internal ID NameとOneToOneで紐づくIDなので削除
Unique Entry ID 意味のないID列

データリークの可能性があるため削除

スクリーンショット 2020-05-30 18.31.38.png
スクリーンショット 2020-05-30 18.31.48.png

削除列 理由
Buy 購入価格、売却価格と比例しているため、計算できてしまいデータリークになるから削除
Kit Cost 制作コスト、同上
Miles Price マイルでの購入額、同上

実際に学習してみた?

?学習結果はこんな感じです。
スクリーンショット 2020-05-30 19.08.29.png
スコア92と高いスコアを叩き出しました。
誤差の平均が5180ベルくらいで予測ができるモデルになりました!
(データの詳細設定から単位をつけることができます。ベルってなってるとなんかかわいい?)

細かく見ていきましょう。

特徴の重要度

スクリーンショット 2020-05-30 19.15.19.png

重要な指標第1位は「Tag毎の平均売却額」

Tag列は家具の種類を示した列です。内容は以下のようになっています。
スクリーンショット 2020-05-30 19.12.42.png
テーブル、椅子、ベッドなど家具の種類が入っているのが分かります。
売却価格との関係性を見てみましょう。
スクリーンショット 2020-05-30 19.12.33.png
そこまではっきりとカテゴリごとに差が出ているわけではありませんが、最大値、最小値などのばらつきが見てとれます。

重要な指標第2位は「Size毎の平均売却額」

サイズはその家具が何x何マスのアイテムなのかを表しています。
スクリーンショット 2020-05-30 19.17.45.png
1x1より2x1、2x1より2x2のアイテムが高いのが分かります。
3x3のものが異様に高級だったりするのもわかります。

重要な指標第3位は「HHA Concept 2毎の平均売却額」

「HHA Concept 2」は家具のコンセプトを表します。いわゆる家具のシリーズというやつですね。
スクリーンショット 2020-05-30 19.20.24.png
Noneはコンセプトなしですが、リビングルームシリーズや、和室、ラグジュアリーシリーズだったりがあります。
スクリーンショット 2020-05-30 22.52.22.png
たしかにシリーズによって金額に大きな差が見られます。
「expensive」コンセプトは高く、「living room」コンセプト、「office」コンセプトなどはリーズナブルなのがわかります。
これらの値を考慮して、予測値を出している模様

検証データによる予測結果

答え合わせ?

今回答え合わせ用に5つの家具をピックアップしておきました。
VARISTAで予測をし、それぞれ答え合わせをしていきます。
予測のフォーマットを整数にして、予測実行!
スクリーンショット 2020-05-30 18.58.52.png

ダブルソファ

スクリーンショット 2020-05-30 19.01.09.png

タグ サイズ コンセプト
Sofa 2x1 living room

売却価格: 1300ベル
予想売却価格: 1115ベル
誤差: -285ベル

キャンディマシン

スクリーンショット 2020-05-30 19.01.37.png

タグ サイズ コンセプト
Shop 1x1 shop

売却価格: 700ベル
予想売却価格: 916ベル
誤差: +216ベル

ハンモック

スクリーンショット 2020-05-30 19.02.28.png

タグ サイズ コンセプト
Bed 2x1 outdoors

売却価格: 325ベル
予想売却価格: 318ベル
誤差: -7ベル
ほぼぴったり!!?

DJブース

スクリーンショット 2020-05-30 19.01.54.png

タグ サイズ コンセプト
Musical Instrument 2x1 party

売却価格: 2325ベル
予想売却価格: 2501ベル
誤差: +176ベル

くまのトクダイちゃん

スクリーンショット 2020-05-30 19.02.11.png

タグ サイズ コンセプト
Animal 3x3 fancy

売却価格: 6300ベル
予想売却価格: 6008ベル
誤差: -292ベル

おわり?

スクリーンショット 2020-05-30 23.10.51.png

結構な精度が出せましたが、ゲームのマスターデータはもともと、一定のルールのもと金額などが決められているため、予測がしやすかったのかもしれません。(それかどっかでリークしてる?確認します。)
このように、自分に親しみのあるデータで分析を行うとやっぱり楽しいですよね?
みなさんも是非遊んでみてください!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

.NET Frameworkのbzip2ライブラリを調査

.NET Framework で bzip2 をサポートするライブラリについて調べます。Python や bzcat と処理速度を比較します。

ライブラリ

bzip2 をサポートする 3 種類のライブラリを見付けました。

  1. SharpZipLib: マネージドコードによる再実装
  2. SharpCompress: マネージドコードによる再実装
  3. AR.Compression.BZip2 ネイティブライブラリのラッパー

これらのライブラリでのマルチストリームの扱いと伸張の所要時間を確認します。

マルチストリーム

個別に bzip2 で圧縮して連結したデータをマルチストリームと呼びます。

作成例
$ echo -n hello | bzip2 > a.bz2
$ echo -n world | bzip2 > b.bz2
$ cat a.bz2 b.bz2 > ab.bz2
$ hd ab.bz2
00000000  42 5a 68 39 31 41 59 26  53 59 19 31 65 3d 00 00  |BZh91AY&SY.1e=..|
00000010  00 81 00 02 44 a0 00 21  9a 68 33 4d 07 33 8b b9  |....D..!.h3M.3..|
00000020  22 9c 28 48 0c 98 b2 9e  80 42 5a 68 39 31 41 59  |".(H.....BZh91AY|
00000030  26 53 59 59 ce 7b cb 00  00 02 01 80 04 04 90 80  |&SYY.{..........|
00000040  20 00 30 cd 00 c1 a4 c0  71 77 24 53 85 09 05 9c  | .0.....qw$S....|
00000050  e7 bc b0                                          |...|
00000053

SharpZipLib

複数の圧縮アルゴリズムをサポートしています。

bzip2 の実装は以下にあります。

最小構成

bzip2 の伸張は、以下の 6 ファイルだけで行えることを確認しました。

  • src/ICSharpCode.SharpZipLib/BZip2/BZip2Constants.cs
  • src/ICSharpCode.SharpZipLib/BZip2/BZip2Exception.cs
  • src/ICSharpCode.SharpZipLib/BZip2/BZip2InputStream.cs
  • src/ICSharpCode.SharpZipLib/Checksum/BZip2Crc.cs
  • src/ICSharpCode.SharpZipLib/Checksum/IChecksum.cs
  • src/ICSharpCode.SharpZipLib/Core/Exceptions/SharpZipBaseException.cs

テスト

最小構成ではなく、NuGet で取得したライブラリを使用します。

nuget install SharpZipLib
cp SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll .

マルチストリームの例として作った ab.bz2 の伸張を試みます。

※ Mono には古いバージョンの ICSharpCode.SharpZipLib.dll が取り込まれています。パスを書かないとそちらが参照されるため、NuGet で取得した方の DLL をパス付きで参照します。

#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System.IO
open ICSharpCode.SharpZipLib.BZip2
do
    use fs = new FileStream("ab.bz2", FileMode.Open)
    use bz = new BZip2InputStream(fs)
    use sr = new StreamReader(bz)
    printfn "%s" (sr.ReadToEnd())
hello

最初のストリームしか処理されません。

BZip2InputStream.cs を眺めてもマルチストリームに対応した作りにはなっていないようなので、逐次的に読み込む必要があります。

IsStreamOwnertrue の場合、処理の完了後に渡したストリーム fs が閉じられます。デフォルトでは true のため false を指定します。

#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System.IO
open ICSharpCode.SharpZipLib.BZip2
do
    use fs = new FileStream("ab.bz2", FileMode.Open)
    while fs.Position < fs.Length do
        use bz = new BZip2InputStream(fs, IsStreamOwner = false)
        use sr = new StreamReader(bz)
        printfn "%s" (sr.ReadToEnd())
hello
world

SharpCompress

複数の圧縮アルゴリズムをサポートしています。

bzip2 の実装は以下にあります。

このディレクトリは単独で抜き出して使えます。ただ一点、独自に定義している CompressionMode が不足しますが、既存のもので代用できました。

BZip2Stream.cs(追加)
using System.IO.Compression;

最小構成

bzip2 の伸張は、以下の 3 ファイルだけで行えることを確認しました。

  • src/SharpCompress/Compressors/BZip2/BZip2Constants.cs
  • src/SharpCompress/Compressors/BZip2/CBZip2InputStream.cs
  • src/SharpCompress/Compressors/BZip2/CRC.cs

ただし class CBZip2InputStreaminternal になっているため、public にする必要があります。

テスト

最小構成ではなく、NuGet で取得したライブラリを使用します。

nuget install sharpcompress
cp SharpCompress.0.25.1/lib/net46/SharpCompress.dll .

マルチストリームの例として作った ab.bz2 の伸張を試みます。

BZip2Stream のコンストラクターの最後の引数は、マルチストリームを読み進めるかどうかのフラグです。

#r "SharpCompress.dll"
open System.IO
open SharpCompress.Compressors
do
    use fs = new FileStream("ab.bz2", FileMode.Open)
    use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, true)
    use sr = new StreamReader(bz)
    printfn "%s" (sr.ReadToEnd())
実行結果
helloworld

マルチストリームに対するサポートがあるため、一気に最後まで読み進めることができました。

マルチストリームのフラグを false にすると、1 つのストリームの終端に達した時点で fs が閉じられてしまいます。CBZip2InputStream.cs を見ても、開いたままにすることは想定されていないようです。そのため逐次的に読み進めるには Dispose を無視するような小細工をするしかなさそうです。

#r "SharpCompress.dll"
open System.IO
open SharpCompress.Compressors
do
    let mutable ignore = true
    use fs = { new FileStream("ab.bz2", FileMode.Open) with
        override __.Dispose disposing = if not ignore then base.Dispose disposing }
    while fs.Position < fs.Length do
        use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, false)
        use sr = new StreamReader(bz)
        printfn "%s" (sr.ReadToEnd())
    ignore <- false
実行結果
hello
world

AR.Compression.BZip2

他のライブラリとは違って bzip2 に特化しています。伸長と圧縮は 1 つのクラスで実装されているため、分離して最小構成を調べるのは省略します。

NuGet に登録されています。

ビルド

ライブラリを Mono と共用するには少し手を入れる必要があるため、今回は NuGet は使わずに自分でビルドします。

P/Invoke で指定する DLL のファイル名を変更します。

10:        private const string DllName = "libbz2";

こうすれば Windows では libbz2.dll、WSL では libbz2.so が使用されます。WSL ではカレントディレクトリになくても /usr/lib/libbz2.so が参照されます。

DLL をビルドします。

csc -o -out:AR.Compression.BZip2.dll -t:library -unsafe sources/AR.BZip2/*.cs

Windows 用の bzip2 は以下で配布されているバイナリを利用します。

テスト

マルチストリームの例として作った ab.bz2 の伸張を試みます。

BZip2Stream のコンストラクターの最後の引数が true の場合、処理の完了後に渡したストリーム fs は開いたままになります。デフォルトでは false のため true を指定します。SharpZipLib の IsStreamOwner と用途は同じですが指定方法が逆です。マルチストリームとは関係ありません。

#r "AR.Compression.BZip2.dll"
open System.IO
open System.IO.Compression
do
    use fs = new FileStream("ab.bz2", FileMode.Open)
    use bz = new BZip2Stream(fs, CompressionMode.Decompress, false)
    use sr = new StreamReader(bz)
    printfn "%s" (sr.ReadToEnd())
実行結果
helloworld

すべてのストリームが一括で処理されました。BZip2Stream.cs を見ても、逐次的に読み進めることは想定されていないようです。次で見るベースストリームの扱いと合わせて考えても、改造なしで対応することはできないようです。

ベースストリームの読み込み

それぞれのライブラリで、渡されたベースストリームの読み込み個所を確認します。

※ .NET での Stream のことをベースストリームと呼んで、bzip2 の意味でのストリームと区別します。

417:                            thech = baseStream.ReadByte();
231:            int magic0 = bsStream.ReadByte();
232:            int magic1 = bsStream.ReadByte();
233:            int magic2 = bsStream.ReadByte();
242:            int magic3 = bsStream.ReadByte();
378:                    thech = (char)bsStream.ReadByte();
649:                                    thech = (char)bsStream.ReadByte();
717:                                                thech = (char)bsStream.ReadByte();
814:                                            thech = (char)bsStream.ReadByte();
  9:             private const int BufferSize = 128 * 1024;
 14:             private readonly byte[] _buffer = new byte[BufferSize];
368:                                             _data.avail_in = _stream.Read(_buffer, 0, ufferSize);

SharpZipLib と SharpCompress では必要に応じて ReadByte で 1 バイトずつ読み進めています。そのため bzip2 のストリームの区切りで抜けても、オーバーランすることはないようです。thech (the character?) という変数名が共通していることから、何か共通の元ネタがあるのかもしれません。(libbz2 にはこの変数名は見当たりません)

AR.Compression.BZip2 では固定長でバッファに読み込んでいます。自前で伸張していないため、バイト単位で読み進めることはできないのでしょう。もし bzip2 のストリームごとに処理を区切るとしてもオーバーランしてしまうため、何らかの対策が必要です。

バイトごとに読み進めるのはベースストリームの位置に関して正確ではありますが、処理速度的には不利です。

計測

巨大なファイルの伸張に掛かる時間を比較します。

Wikipedia 日本語版のダンプデータを使用します。このファイルはマルチストリーム構成です。

逐次読み込み

SharpZipLib と SharpCompress でストリームを逐次的に読み込みます。

test1.fsx
#r "SharpZipLib.1.2.0/lib/net45/ICSharpCode.SharpZipLib.dll"
open System
open System.IO
open ICSharpCode.SharpZipLib.BZip2
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
    use fs = new FileStream(target, FileMode.Open)
    let buffer = Array.zeroCreate<byte>(1024 * 1024)
    let mutable streams, bytes = 0, 0L
    while fs.Position < fs.Length do
        use bz = new BZip2InputStream(fs, IsStreamOwner = false)
        let mutable len = 1
        while len > 0 do
            len <- bz.Read(buffer, 0, buffer.Length)
            bytes <- bytes + int64 len
        streams <- streams + 1
    Console.WriteLine("streams: {0:#,0}, bytes: {1:#,0}", streams, bytes)
test2.fsx
#r "SharpCompress.dll"
open System
open System.IO
open SharpCompress.Compressors
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
    let mutable ignore = true
    use fs = { new FileStream(target, FileMode.Open) with
        override __.Dispose disposing = if not ignore then base.Dispose disposing }
    let buffer = Array.zeroCreate<byte>(1024 * 1024)
    let mutable streams, bytes = 0, 0L
    while fs.Position < fs.Length do
        use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, false)
        let mutable len = 1
        while len > 0 do
            len <- bz.Read(buffer, 0, buffer.Length)
            bytes <- bytes + int64 len
        streams <- streams + 1
    ignore <- false
    Console.WriteLine("streams: {0:#,0}, bytes: {1:#,0}", streams, bytes)
実行結果
$ time ./test1.exe  # SharpZipLib
streams: 24,957, bytes: 13,023,068,290

real    16m2.849s

$ time ./test2.exe  # SharpCompress
streams: 24,957, bytes: 13,023,068,290

real    18m26.520s

SharpZipLib の方が速いようです。

一括読み込み

SharpCompress と AR.Compression.BZip2 で全ストリームを一括で読み込みます。

test3.fsx
#r "SharpCompress.dll"
open System
open System.IO
open SharpCompress.Compressors
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
    use fs = new FileStream(target, FileMode.Open)
    use bz = new BZip2.BZip2Stream(fs, CompressionMode.Decompress, true)
    let buffer = Array.zeroCreate<byte>(1024 * 1024)
    let mutable bytes, len = 0L, 1
    while len > 0 do
        len <- bz.Read(buffer, 0, buffer.Length)
        bytes <- bytes + int64 len
    Console.WriteLine("bytes: {0:#,0}", bytes)
test4.fsx
#r "AR.Compression.BZip2.dll"
open System
open System.IO
open System.IO.Compression
let target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
do
    use fs = new FileStream(target, FileMode.Open)
    use bz = new BZip2Stream(fs, CompressionMode.Decompress, false)
    let buffer = Array.zeroCreate<byte>(1024 * 1024)
    let mutable bytes, len = 0L, 1
    while len > 0 do
        len <- bz.Read(buffer, 0, buffer.Length)
        bytes <- bytes + int64 len
    Console.WriteLine("bytes: {0:#,0}", bytes)
実行結果
$ time ./test3.exe  # SharpCompress
bytes: 13,023,068,290

real    17m36.925s

$ time ./test4.exe  # AR.Compression.BZip2
bytes: 13,023,068,290

real    7m50.165s

AR.Compression.BZip2 はネイティブライブラリを呼び出しているだけあって速いです。

Python

Python の bz2 モジュールと比較します。こちらもネイティブのラッパーです。

test5.py(逐次)
import bz2
target  = "jawiki-20200501-pages-articles-multistream.xml.bz2"
streams = 0
bytes   = 0
size    = 1024 * 1024  # 1MB
with open(target, "rb") as f:
    bz2d = bz2.BZ2Decompressor()
    data = []
    while data or (data := f.read(size)):
        bytes += len(bz2d.decompress(data))
        data = bz2d.unused_data
        if bz2d.eof:
            bz2d = bz2.BZ2Decompressor()
            streams += 1
print("streams: {:,}, bytes: {:,}".format(streams, bytes))
test6.py(一括)
import bz2
target = "jawiki-20200501-pages-articles-multistream.xml.bz2"
bytes  = 0
size   = 1024 * 1024  # 1MB
with bz2.open(target, "rb") as f:
    while (data := f.read(size)):
        bytes += len(data)
print("bytes: {:,}".format(bytes))
実行結果
$ time py.exe test5.py  # 逐次
streams: 24,957, bytes: 13,023,068,290

real    8m12.155s

$ time py.exe test6.py  # 一括
bytes: 13,023,068,290

real    8m1.476s

処理の大半が libbz2 で行われているため、それ以外のオーバーヘッドは少ないようです。.NET との主な違いはバッファを使い回していない点です。

bzcat

WSL1 の bzcat コマンドも計測します。

$ time bzcat jawiki-20200501-pages-articles-multistream.xml.bz2 > /dev/null

real    8m21.056s
user    8m5.563s
sys     0m15.422s

次で見ますが Mono の AR.Compression.BZip2 より遅いです。WSL1 のリダイレクトにオーバーヘッドがあるためでしょうか。

まとめ

結果をまとめます。WSL1 (Mono) での計測結果を加えます。

逐次 (Win) 逐次 (WSL1) 一括 (Win) 一括 (WSL1)
SharpZipLib 16m02.849s 22m49.375s
SharpCompress 18m26.520s 23m56.694s 17m36.925s 22m54.247s
AR.Compression.BZip2 7m50.165s 7m41.580s
Python (bz2) 8m12.155s 8m45.590s 8m01.476s 8m28.749s
bzcat 8m21.056s

マネージドコードで実装されたライブラリは倍以上の時間が掛かりました。マネージド縛りがなければ AR.Compression.BZip2 を使った方が無難です。

※ System.IO.Compression.GZipStream が利用している DeflateStream は、独自実装からネイティブのラッパーに切り替えたようです。

.NET Framework 4.5以降では、DeflateStream クラスは、圧縮のために zlib ライブラリを使用します。

関連記事

Wikipedia のダンプについては以下の記事を参照してください。

参考

SharpZipLib で bzip2 を扱っている記事です。

SharpCompress に言及している記事です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python--Matplotの第1歩

Matplotlib

  • グラフ描画ライブラリ
  • 公式サイト
  • 基本Python + Numpy + matplotlib + SciPy + etc.の組み合わせて使用

インストール

pip install matplotlib

最初の例

import matplotlib.pyplot as plt # 呪い

squares = [1, 4, 9, 16, 25]
plt.plot(squares) # defalutは点同士が直線で結びついている
plt.show() # 上の指示に従い、描画し、見せる

image.png

日本語表示

デフォルトは日本語使用できないため、ちょっとした設定が必要になります。
要は、matplotlibのフォント設定を日本語対応のフォントを変更する。

  1. https://ipafont.ipa.go.jp/node17#jp からIPAexフォントをダウンロード、現時点は「IPAexフォント Ver.004.01」が最新
  2. ダウンロードしたファイルを解凍し、中のipaexg.ttfファイルを${オブジェクトパス}\venv\Lib\site-packages\matplotlib\mpl-data\fonts\ttfにコピーする
  3. ${オブジェクトパス}\venv\Lib\site-packages\matplotlib\mpl-dataは以下にあるmatplotlibrcを開き
  4. font.familyで検索
  5. #font.family : sans-serifの下にfont.family : IPAexGothicを追記する
  6. matplotlibのキャッシュの保存先の内容を全部削除する

下記のコードでフォントパスとキャッシュの保存先を調べることができる

print(matplotlib.matplotlib_fname())   # フォントパス
print(matplotlib.get_cachedir())       # キャッシュの保存先

もう一つ方法は、設定ファイルを修正しないが、毎回下記のコードを追記しないといけない

plt.rcParams['font.sans-serif']=['IPAexGothic']

もしくは

plt.rcParams["font.family"] = "IPAexGothic"

飾り

import matplotlib.pyplot as plt

squares = [1, 4, 9, 16, 25]
plt.plot(squares, linewidth = 5)  # 線をちょっと太くする

plt.title("平方", fontsize = 24)
plt.xlabel("値", fontsize = 14)
plt.ylabel("値の二乗", fontsize = 14)

# 軸の目盛りの表示設定
plt.tick_params(axis = 'both', labelsize = 14)
plt.show()

image.png

import numpy as np
import matplotlib.pyplot as plt

# 0から5を0.2刻みの点を作成することにより、線が滑らかに見える
x = np.arange(0., 5., 0.2)
plt.plot(x, x * x)

plt.title("平方", fontsize = 24)
plt.xlabel("値", fontsize = 14)
plt.ylabel("値の二乗", fontsize = 14)

# 目盛りの文字サイズをちょっと大きめに設定
plt.tick_params(labelsize = 30)

plt.show()

image.png

目盛りのサイズが大きくすると、X軸やY軸のラベルがはみ出して、見えなくなるが、もちろん画面のサイズを調整して、見えるようにすればいいが、plt.tight_layout()plt.show()前に入れれば、自動的に収める状態で表示してくれる。

import numpy as np
import matplotlib.pyplot as plt

x = np.arange(0., 5., 0.2)
plt.plot(x, x * x)

plt.title("平方", fontsize = 24)
plt.xlabel("値", fontsize = 14)
plt.ylabel("値の二乗", fontsize = 14)

plt.tick_params(labelsize = 30)

plt.tight_layout()
plt.show()

image.png

点で表示したい(散布図)

scatter()を使用する。

import matplotlib.pyplot as plt

x_values = [1, 2, 3, 4, 5]
y_values = [1, 4, 9, 16, 25]
plt.scatter(x_values, y_values, s=100)  # sは点のサイズを指定

plt.show()

image.png

データは自動計算から取得する

import matplotlib.pyplot as plt

x_values = list(range(1, 1001))
y_values = [x**2 for x in x_values]
plt.scatter(x_values, y_values, s=4)

plt.axis([0, 1100, 0, 1100000])  # 軸の範囲
plt.show()

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Backpropagation (tf.custom_gradient) で self を使用したい (tensorflow)

custom_graident を使用する場合の通常の書き方
@tf.custom_gradient
def gradient_reversal(x):
  y = x
  def grad(dy):
    return - dy
  return y, grad

# model 内で使用する場合
class MyModel(tf.keras.Model):
    def __init__(self):
        super(MyModel, self).__init__()

    def call(self, x):
        return gradient_reversal(x)
custom_gradient 内でスコープ外の変数(self等)を使用したい場合
class MyModel2(tf.keras.Model):
    def __init__(self):
        super(MyModel2, self).__init__()
        self.alpha = self.add_weight(name="alpha", initializer=tf.keras.initializers.Ones())

    @tf.custom_gradient
    def forward(self, x):
        y = self.alpha * x

        def backward(w, variables=None):
            with tf.GradientTape() as tape:
                tape.watch(w)
                z = - self.alpha * w

            grads = tape.gradient(z, [w])
            return z, grads

        return y, backward

    def call(self, x):
        return self.forward(x)
  • ドキュメント 内での引数が dy になっているので計算済みのものが渡ってくるかと思いきや、Backpropagation 時の実行関数として指定できる (上記の backward メソッド)

  • bakward メソッド外のスコープの変数を使用する場合は、 variables=None を受け取るようにしないと以下のようなエラーが発生する (ドキュメントのArgs内でも説明されている)

TypeError: If using @custom_gradient with a function that uses variables, then grad_fn must accept a keyword argument 'variables'.

検証用コード

import tensorflow as tf


optimizer = tf.keras.optimizers.Adam(learning_rate=0.1)


class MyModel(tf.keras.Model):
    def __init__(self):
        super(MyModel, self).__init__()
        self.alpha = self.add_weight(name="alpha", initializer=tf.keras.initializers.Ones())

    @tf.custom_gradient
    def forward(self, x):
        y = self.alpha * x
        tf.print("forward")
        tf.print("  y: ", y)

        def backward(w, variables=None):
            z = self.alpha * w
            tf.print("backward")
            tf.print("  z: ", z)
            tf.print("  variables: ", variables)
            return z, variables

        return y, backward

    def call(self, x):
        return self.forward(x)


class MyModel2(tf.keras.Model):
    def __init__(self):
        super(MyModel2, self).__init__()
        self.alpha = self.add_weight(name="alpha", initializer=tf.keras.initializers.Ones())

    @tf.custom_gradient
    def forward(self, x):
        y = self.alpha * x
        tf.print("forward")
        tf.print("  y: ", y)

        def backward(w, variables=None):
            with tf.GradientTape() as tape:
                tape.watch(w)
                z = - self.alpha * w

            grads = tape.gradient(z, [w])

            tf.print("backward")
            tf.print("  z: ", z)
            tf.print("  variables: ", variables)
            tf.print("  alpha: ", self.alpha)
            tf.print("  grads: ", grads)
            return z, grads

        return y, backward

    def call(self, x):
        return self.forward(x)


for model in [MyModel(), MyModel2()]:
    print()
    print()
    print()
    print(model.name)
    for i in range(10):
        with tf.GradientTape() as tape:
            x = tf.Variable(1.0, tf.float32)
            y = model(x)

        grads = tape.gradient(y, model.trainable_variables)
        optimizer.apply_gradients(zip(grads, model.trainable_variables))
        tf.print("step")
        tf.print("  y:", y)
        tf.print("  grads:", grads)
        print()

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django REST frameworkの基本

はじめに

Django REST frameworkを使うと、簡単にAPIを作成することができます。
ここでは、Django REST frameworkについて、最低限知っておきたいことにしぼって解説します。

settings.pyへの追記

INSTALLED_APPSrest_frameworkを追記します。

settings.py
INSTALLED_APPS = [
    'rest_framework', # 追加
]

serializerの作成

次に、serializerを作成していきます。

serializers.py
from rest_framework import serializers


class SampleSerializer(serializers.ModelSerializer):

    class Meta:
        model = 対象モデル
        fields = 含めるフィールド(全部の場合は'__all__' # または、exclude = (除外したいフィールド)

viewの作成

serializerを作成したら、views.pyに追記します。

views.py
from rest_framework import generics


class SampleListAPI(generics.ListAPIView):
    queryset = 対象モデル.objects.all()
    serializer_class = シリアライザー


class SampleDetailAPI(generics.ListAPIView):
    queryset = 対象モデル.objects.all()
    serializer_class = シリアライザー

urls.pyの設定

最後に、ルーティングの設定をして一通り完了です。

urls.py
from django.urls import path

from . import views


urlpatterns = [
    path('api/sample/list', views.SampleListAPI.as_view(), name='api_sample_list'),
    path('api/sample/detail/<int:pk>/', views.SampleDetailAPI.as_view(), name='api_sample_detail'),
]

まとめ

ここでは、Django REST frameworkを用いたAPI作成方法について解説しました。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoを用いてhtmlからPythonファイルを実行する

ここで書くこと

この記事では「Djangoを用いてWebページからサーバ上に用意したPythonファイルを実行し、htmlから渡したデータをcsvファイルに出力する方法」について書いています。

また、執筆環境は
OS:macOS Catalina バージョン 10.15.4
Python:3.7.6
Django:3.0.3
となっています。

Djangoについて

前回の記事にてDjangoのインストール→htmlの表示までの流れについて書かせていただきましたので、Djangoの導入等についてはそちらをご参考ください。

Pythonファイルの準備

Pythonファイルの作成

まず、実行したいPythonファイルをDjangoのサーバ上に用意します。説明のために今回は以下のようなPythonファイルを用意します。

write_data.py
# coding:utf-8
import os
import csv

# htmlからのデータをcsvファイルに記録
def write_csv(data):
    datas = [data]
    with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
        writer = csv.writer(f, lineterminator='\n')
        writer.writerow(datas)

これにより、write_csv()の引数にデータを渡して呼び出すことでdata.csvにそのデータが書き込まれます。なおここでは<アプリ名>/applicationフォルダ内にPythonファイルを配置&csvファイルを出力することを想定しているためos.getcwd()+'/myapp/application/'+'data.csv'としていますが、この部分は環境に応じて適宜読み替えてください。

Pythonファイルの配置

用意したPythonファイルをDjangoのサーバ上に配置します。Pythonファイルを<アプリ名>/applicationフォルダ内に置く場合、アプリ内のディレクトリは以下のようになるかと思います。

<プロジェクト名>
- db.sqlite3
- manage.py
- <プロジェクト名>
  - __init__.py
  - asgi.py
  - settings.py
  - urls.py
  - wsgi.py
  - __pycashe__
    - (.pycファイルが複数)
- <アプリ名>
  - __init__.py
  - admin.py
  - apps.py
  - models.py
  - tests.py
  - urls.py
  - views.py
  - migrations
    - __init__.py
  - application  # 作成したフォルダ
    - write_data.py  # 用意したPythonファイル
- templates
- static

もちろん、ここに置かないといけないという訳ではないのでファイルの配置場所は任意の場所で大丈夫です。

htmlからPythonファイルを実行できるようにする

ファイルの用意と配置が完了しましたら、実際にhtmlからそのPythonファイルを実行させてcsvファイルを作成してみます。その前に、データの流れがどのようになっているかを把握してから実装した方が作業しやすいと思いますので、今回想定するデータの流れを書いてみたいと思います。
スクリーンショット 2020-05-30 19.04.32.png
パワポで作ってみました、ガサツな図で申し訳ありません、、
簡略化の為にプログラムと異なる部分も多々ありますが、csvファイルへの書き込みまでのデータの流れとしてはこんな感じです。何となくでもイメージが伝われば嬉しいです。

html(index.html)
 →myapp/views.pyのcall_write_data()にデータを送信
 →call_write_data()内でapplication/write_data.pyのwrite_csv()メソッドを実行
 →それにより渡したデータがcsvファイルに書き込まれる

言葉で表すとこんな感じです。これを踏まえ、次から実際にhtmlからPythonファイルを実行できるよう各ファイルを編集していきます。

html:ajaxを用いてviews.pyにデータを送信する

views.py内のcall_write_data()メソッドにデータを渡す為に、html上でajaxを用いてデータを送信してみたいと思います。ここに関しては色々な方法があるかと思いますので、アプリケーションに合う方法を用いていただければと思います。

index.html
{% load static %}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>HTML</title>
    <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
    <script type="text/javascript" src="{% static 'script.js' %}"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
  </head>

  <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>

      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;

          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            console.log("Success"); 
          });

          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }

      </script>
  </body>
</html>

送信ボタンを押すとclickBtn()が実行され、ajaxによりデータが送信されるようになっています。urlの部分はmyapp: call_write_dataとなっており、これによりviews.pyに記述したcall_write_data()というメソッドにデータが送信されます。

データ部分はdata: {"input_data": txt}となっており、データを受け取る側では"input_data"と指定することで目的のデータを取得することができます。ここでは一つだけにしていますが、data: {"data1": txt1, "data2": txt2}のようにデータの個数や型などデータの形式については自由に設定できます。

myapp/views.py:実行したいPythonファイルとメソッドを指定して実行する

myapp/views.py
from django.shortcuts import render
from django.http import HttpResponse
# application/write_data.pyをインポートする
from .application import write_data

# Create your views here.
def index(req):
    return render(req, 'index.html')

# ajaxでurl指定したメソッド
def call_write_data(req):
    if req.method == 'GET':
        # write_data.pyのwrite_csv()メソッドを呼び出す。
        # ajaxで送信したデータのうち"input_data"を指定して取得する。
        write_data.write_csv(req.GET.get("input_data"))
        return HttpResponse()

ここではajaxで送信されたデータを取得し、実行したいPythonファイルのメソッドにそのデータを渡して呼び出しています。

myapp/urls.py:htmlからviews.pyのcall_write_data()にデータを送信できるようにする

myapp/urls.py
from django.urls import path
from . import views

app_name = 'myapp'
urlpatterns = [
    path(r'', views.index, name='index'),
    # 以下を追記(views.pyのcall_write_data()にデータを送信できるようにする)
    path("ajax/", views.call_write_data, name="call_write_data"),
]

パスを通すことで、html上からajax通信を用いてviews.pyの指定したメソッドにデータを送信できるようになります。

実際にcsvファイルに書き込まれるかを確認する

以上で必要な編集は完了です。

$ python manage.py runserver

でサーバを立ち上げ、表示されたアドレスにアクセス(htmlを表示)し入力フォームに適当な文字を入力してから送信ボタンを押してみてください。

- <アプリ名>
  - __init__.py
  ...
  - application
    - write_data.py
    - data.csv  # 生成されたcsvファイル

このようにアプリ内に作成したapplicationフォルダの中にcsvファイルが生成され、入力した文字列がファイルに記録されていれば問題なくデータが送信されている&Pythonファイルが実行されています。

補足

今回はhtmlからPythonファイルを実行し、送信されたデータをcsvファイルに書き込む方法についてご説明しましたがその逆も可能です。

説明の簡略化の為に、”write_data.pyから渡されたデータをviews.pyで取得しそれをhtmlに渡して表示する”ことを行ってみたいと思います。変更箇所のあるファイルだけ以下に載せていきます。

myapp/application/write_data.py

return_text()というメソッドを追記する。

myapp/application/write_data.py
   # coding:utf-8

   import os
   import csv

   # htmlからのデータをcsvファイルに記録
   def write_csv(data):
       datas = [data]
       with open(os.getcwd()+'/myapp/application/'+'data.csv','a') as f:
           writer = csv.writer(f, lineterminator='\n')
           writer.writerow(datas)

   # 以下を追記(return_text()を呼び出すと"Hello!!"が返される)        
+  def return_text():
+      return "Hello!!"

myapp/views.py

write_data.pyにて追記したreturn_text()を呼び出し、返ってきた文字列を取得する(dataに格納する)。そのデータをHttpResponse()を用いてhtmlに渡す。

myapp/views.py
  from django.shortcuts import render
  from django.http import HttpResponse
  # application/write_data.pyをインポートする
  from .application import write_data

  # Create your views here.
  def index(req):
      return render(req, 'index.html')

  # ajaxでurl指定したメソッド
  def call_write_data(req):
      if req.method == 'GET':
          # write_data.pyのwrite_csvメソッドを呼び出す。
          # ajaxで送信したデータのうち"input_data"を指定して取得する。
          write_data.write_csv(req.GET.get("input_data"))

          # write_data.pyの中に新たに記述したメソッド(return_text())を呼び出す。
+         data = write_data.return_text()
          # 受け取ったデータをhtmlに渡す。
+         return HttpResponse(data)

index.html

ajax通信が成功するとHttpResponse()で渡した引数が.done(function(data) {の部分に渡されるので、そのデータをページに表示する。

index.html
  {% load static %}
  <!DOCTYPE html>
  <html>
    <head>
      <meta charset="utf-8">
      <meta http-equiv="X-UA-Compatible" content="IE=edge">
      <title>HTML</title>
      <link rel='stylesheet' type='text/css' href="{% static 'style.css' %}"/>
      <script type="text/javascript" src="{% static 'script.js' %}"></script>
      <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    </head>

    <body>
      <span>文字を入力した後にボタンを押してください</span>
      <br>
      <input type="text" size="50" id="input_form">
      <button type="button" onclick="clickBtn()">送信</button>

      <!-- views.pyから渡された文字列を表示する。 -->
+     <br>
+     <span id="text"></span>

      <script>
        function clickBtn() {
          var txt = document.getElementById("input_form").value;

          $.ajax({
            url: "{% url 'myapp:call_write_data' %}",
            method: 'GET',
            data: {"input_data": txt},
            dataType: "text",
            contentType: "application/json",
            beforeSend: function(xhr, settings) {
              if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrf_token);
              }
            },
            error: function(xhr, status, error) {
              console.log("error")
            }
          })
          .done(function(data) {
            // views.pyのcall_write_data()にてreturnしたHttpResponse(data)のデータはここで取得できる。
            // フォームの下部に追記したspan部分の内容を書き換える。
+           document.getElementById("text").textContent = data;
            console.log("Success"); 
          });

          // csrf_tokenの取得に使う
          function getCookie(name) {
            var cookieValue = null;
            if (document.cookie && document.cookie !== '') {
              var cookies = document.cookie.split(';');
              for (var i = 0; i < cookies.length; i++) {
                var cookie = jQuery.trim(cookies[i]);
                // Does this cookie string begin with the name we want?
                if (cookie.substring(0, name.length + 1) === (name + '=')) {
                  cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                  break;
                }
              }
            }
            return cookieValue;
          }

          // ヘッダにcsrf_tokenを付与する関数
          function csrfSafeMethod(method) {
            // these HTTP methods do not require CSRF protection
            return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
          };
        }

      </script>
    </body>
  </html>

これにより、送信ボタンを押して正しくPythonファイルが実行されると"Hello!!"という文字列がhtmlに渡され、入力フォームの下の部分に渡されたHello!!という文字列が表示されると思います。

これを応用することで、サーバ上のPythonファイルを実行しサーバ上のファイルのデータを読み書きしたり、そのデータをhtml上に反映させることが可能になります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

製造現場でよく見るCUM_plotがネットに落ちて無かったのでpythonで作ってみた

0.初めに

生産現場で不良解析をしていると、CUM_plotなるものをよく用いる。正規分布だと直線になり、外れ値や複数の正規分布が混じっているかなどが分かりやすい。ふとpythonで作成しようと思ったらどうしても図1のCUMが出てくるが、図2のCUMが出てこない。。。どうやら通常はCUMというと図1を示すよう。

という事で、図2のCUM作成にチャレンジしてみました。正しいか不明なので、誤り等あればご指摘いただけると助かります。

1.CUM plotとは?

累積分布関数(cumulative distribution)の略。ただしこれでググると図1しか出てこない。。。
https://ja.wikipedia.org/wiki/%E7%B4%AF%E7%A9%8D%E5%88%86%E5%B8%83%E9%96%A2%E6%95%B0
図1のCUMの作成方法は下記URL参照
https://matplotlib.org/3.1.1/gallery/statistics/histogram_cumulative.html

2.CUMの作り方

図2のCUMをよく見るとy軸が正規分布を写像しているように見える。ただしそんなスケールはmatplotlibにないので、無理やりスケール変換し割り当てて見ました。

#宣言
from scipy.stats import norm
import numpy as np
import matplotlib.pyplot as plt

#dataの作成
np.random.seed(19680803)
mu = 200
sigma = 3
x = np.random.normal(mu, sigma, size=100)

#累積分布関数の逆関数をxに割り当てる
norm_arr = list()
for no in range(1,len(x)+1):
   norm_arr.append(norm.ppf((no)/(len(x)+1))-norm.ppf(1/(len(x)+1)))
norm_arr=norm_arr/max(norm_arr)#正規化
x.sort()#xを小さい順にソート

#y軸表示の変更
scale =[]
list_axis =[0.0001,0.001,0.01,0.05,0.1,0.2,0.3,0.5,0.7,0.8,0.9,0.95,0.99,0.999,0.9999]
for no in list_axis:
   scale.append(norm.ppf(no)-norm.ppf(0.0001))
scale=scale/max(scale)

#描画
fig, ax = plt.subplots(figsize=(8, 4))
linestyles = '-'
ax.grid(True)
ax.legend(loc='right')
ax.set_title('Cumulative step histograms')
ax.set_xlabel('data')
ax.set_ylabel('Percentage')
plt.yticks(scale,list_axis)
ax.plot(x,norm_arr,label='linear',marker='o',markersize=1.0,linewidth=0.1,linestyle=linestyles)

plt.show()

実行すると上記図2がそのまま出力されます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Lambdaでwebhookをとりあえず受け取れているかを確認するための関数を作ってみた

動機

先日M5STICKCを使ってLINE Beaconを試してみた際に、
Webhook先を今まで使ったことないGASにしてみたところ、
どこでハマってしまっているのかよくわからない状態になってしまった。

知らない機能同士をまとめてためしたのが原因なので、Webhook先は毎回固定にしておいた方がいいのかもしれない

作ってみたもの

ということで、作ってみたのが個人的にWebhook先として汎用的に使える環境。

環境は一番慣れ親しんだものとしてAWSのLambda(とAPI Gateway)とする。

Lambda関数の中身は以下で、POSTで受けてもGETで受けてもログに出力できるようにする。

import json

def lambda_handler(event, context):
    # 引数:eventの内容を表示
    print("Received event: " + json.dumps(event))
    # rawQueryString
    rawQueryString = event['rawQueryString']
    if(len(rawQueryString) != 0):
        queryStringParameters = event['queryStringParameters']
        print(queryStringParameters)

    # body
    body = event.get('body')
    if(not body is None):
        json_body = json.dumps(body)
        print(json_body)

    return {
        'isBase64Encoded': False,
        'statusCode': 200,
        'headers': {},
        'body': json.dumps('Hello from Lambda!')
    }
  • API Gatewayの作成については割愛。久々に作ってみたら前とかなり雰囲気が違っていてかなり戸惑った。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

100日後にエンジニアになるキミ - 71日目 - プログラミング - スクレイピングについて2

昨日までのはこちら

100日後にエンジニアになるキミ - 70日目 - プログラミング - スクレイピングについて

100日後にエンジニアになるキミ - 66日目 - プログラミング - 自然言語処理について

100日後にエンジニアになるキミ - 63日目 - プログラミング - 確率について1

100日後にエンジニアになるキミ - 59日目 - プログラミング - アルゴリズムについて

100日後にエンジニアになるキミ - 53日目 - Git - Gitについて

100日後にエンジニアになるキミ - 42日目 - クラウド - クラウドサービスについて

100日後にエンジニアになるキミ - 36日目 - データベース - データベースについて

100日後にエンジニアになるキミ - 24日目 - Python - Python言語の基礎1

100日後にエンジニアになるキミ - 18日目 - Javascript - JavaScriptの基礎1

100日後にエンジニアになるキミ - 14日目 - CSS - CSSの基礎1

100日後にエンジニアになるキミ - 6日目 - HTML - HTMLの基礎1

今回はスクレイピングの続きです。

通信について

Python言語でスクレイピングを行なっていきます。
スクレイピングでは通信を伴いますので
通信の仕組みを知っておく必要があります。

WEBサイトは世界中のサーバー上に置かれています。
WEBにおいて基本的にサーバとのやりとりはHTTP(HyperText Transfer Protocol)というプロトコル(通信規約)を用いて行い

ブラウザからサーバへの要求をリクエスト
サーバからブラウザへの応答をレスポンスと呼んでいます。

WEB上での基本的なやりとりはリクエスト/レスポンス(R/R)で成立しており
基本的にはテキストメッセージを交換することにより、実現されています

サイト検索の例
ブラウザから検索ツールで検索を実行リクエスト
サーバは要求に対して結果を返答レスポンス
レスポンスを元にブラウザは検索結果を表示

HTTP通信には、いくつかの仕様があり、リクエストの送り方が複数あります。

GET通信

GETはURLにパラメータを付加してリクエストします

例:
http://otupy.com?p=abc&u=u123
?以降はパラメータでパラメータはキー=値&つなぎにしたものです。

POST通信

POSTはBodyに含めてリクエストします

http://otupy.com

リクエストBody
param:p:ab,u:u123

POSTとGETの使い分け
通信自体はブラウザーでは適切な通信方法を選んで行うが
プログラムでは通信方法を指定する必要があります。

リクエスト

ブラウザーなどからWEBサイトのサーバーへの要求をリクエストと言っています。

ブラウザでWebページを開く際、ブラウザはサーバに下記のような要求メッセージを送信します。

GETの例:

リクエストヘッダ
GET http://www.otupy.com/ex/http.htm HTTP/1.1
Host: www.otupy.com
Proxy-Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,/;q=0.8
Referer: https://www.google.co.jp/
Accept-Encoding: gzip, deflate
Accept-Language: ja,en-US;q=0.9,en;q=0.8

POSTの例:

リクエストヘッダ:
POST /hoge/ HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 22
Cache-Control: max-age=0
Origin: http://localhost:8080
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
Referer: http://localhost:8080/hoge/
Accept-Encoding: gzip, deflate, br
Accept-Language: ja,en-US;q=0.8,en;q=0.6

リクエストボディ:
name=hoge&comment=hoge

リクエストはヘッダボディ部があり、通信方法によってどのような情報を詰めて送信しているのかが変わります。

そのため、プログラムでアクセスする際にも適切な情報を詰めてリクエストをしてあげる必要があります。

プログラムでのアクセス

早速スクレイピングを試してみましょう。

Pythonではrequestsと言うライブラリで通信を行うことができます。

import requests

アクセス先のWEBサイトが必要なのでそれを指定してGETで通信を行います。
requests.get(URL)

url = 'http://www.otupy.net/'
res = requests.get(url)
print(res)

通信の結果レスポンスが返ってきます。
通信に成功していれば、アクセス先の情報が取得できます。

当然通信ですので失敗することもあります。

通信結果(レスポンス)

通信の結果レスポンスはいくつかのステータスコードに別れます。
200番台は通信が成功していますが、400,500番台は
通信に失敗をしているので、URLの打ち間違いや、相手型のサーバーにアクセスできる状況であるのかを確認する必要があります。

分類 番号 メッセージ 説明
情報 100 Continue 処理を継続しています。続きのリクエストを送信してください。
情報 101 Sitching Protocols Upgrade ヘッダで指定したプロトコルに変更して再要求してください。
成功 200 OK 成功しました。
成功 201 Created Location ヘッダで指定した場所に新しいコンテンツが作成されました。
成功 202 Accepted 要求は受理されました。ただし処理は完了していません。
成功 203 Non-Authoritative Information 応答ヘッダはオリジナルサーバーが返したものとは異なりますが、処理は成功です。
成功 204 No Content コンテンツはありませんが、処理は成功しました。
成功 205 Reset Content 要求を受理したので、現在のコンテンツ(画面)を破棄してください。。
成功 206 Partial Content コンテンツを一部のみ返却します。
転送 300 Multiple Choices コンテンツ入手方法について複数の選択肢があります。
転送 301 Moved Permanently Location ヘッダで指定された別の場所に移動しました。
転送 302 Found Location ヘッダで指定された別の場所に見つかりました。そちらを見てください。
転送 303 See Other Location ヘッダで指定された他の場所を見てください。
転送 304 Not Modified 更新されていません。If-Modified-Since ヘッダを用いた場合に返却されます。
転送 305 Use Proxy Location ヘッダで指定したプロキシを使用してください。
転送 306 (Unused) 未使用。
転送 307 Temporary Redirect 別の場所に一時的に移動しています。
クライアントエラー 400 Bad Request 要求が不正です。
クライアントエラー 401 Unauthorized 認証されていません。
クライアントエラー 402 Payment Required 支払いが必要です。
クライアントエラー 403 Forbidden アクセスが認められていません。
クライアントエラー 404 Not Found 見つかりません。
クライアントエラー 405 Method Not Allowed 指定したメソッドはサポートされていません。
クライアントエラー 406 Not Acceptable 許可されていません。
クライアントエラー 407 Proxy Authentication Required プロキシ認証が必要です。
クライアントエラー 408 Request Timeout リクエストがタイムアウトしました。
クライアントエラー 409 Conflict リクエストがコンフリクト(衝突・矛盾)しました。
クライアントエラー 410 Gone 要求されたコンテンツは無くなってしまいました。
クライアントエラー 411 Length Required Content-Length ヘッダを付加して要求してください。
クライアントエラー 412 Precondition Failed If-... ヘッダで指定された条件に合致しませんでした。
クライアントエラー 413 Request Entity Too Large 要求されたエンティティが大きすぎます。
クライアントエラー 414 Request-URI Too Long 要求された URI が長すぎます。
クライアントエラー 415 Unsupported Media Type サポートされていないメディアタイプです。
クライアントエラー 416 Requested Range Not Satisfiable 要求されたレンジが不正です。
クライアントエラー 417 Expectation Failed Expect ヘッダで指定された拡張要求は失敗しました。
サーバーエラー 500 Internal Server Error サーバーで予期しないエラーが発生しました。
サーバーエラー 501 Not Implemented 実装されていません。
サーバーエラー 502 Bad Gateway ゲートウェイが不正です。
サーバーエラー 503 Service Unavailable サービスは利用可能ではありません。
サーバーエラー 504 Gateway Timeout ゲートウェイがタイムアウトしました。
サーバーエラー 505 HTTP Version Not Supported このHTTPバージョンはサポートされていません。

プログラムでの通信結果の確認

さてプログラムで通信の結果を確認してみましょう。

レスポンス変数.status_code
でステータスコードを確認できます。

url = 'http://www.otupy.net/'
res = requests.get(url)
print(res.status_code)

200

もし200番でなければ通信が失敗しているのでWEBサイトの情報取得は出来ていないことになります。

200番の場合は通信が成功しているのでWEBサイトから取得した情報を見ることができます。

通信の結果を変数に格納しているので、様々な内容を見ることが出来ます。

リクエストURL
レスポンス変数.url

ステータスコード
レスポンス変数.status_code

レスポンスボディをテキスト形式で取得
レスポンス変数.text

レスポンスボディをバイナリ形式で取得
レスポンス変数.content

クッキー
レスポンス変数.cookies

エンコーディング情報を取得
レスポンス変数.encoding

ここから先は取得したテキスト情報を使って必要な情報に切り分けていきます。

# レスポンスをバイナリ形式で取得して文字に変換して表示(1000文字分)
print(res.content[0:1000].decode('utf-8'))

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "" class="autolink">http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
....

カスタムヘッダー

リクエストを行う際にはリクエストヘッダとボディ部に情報を詰めてリクエストすることができます。

GET通信でヘッダを指定してリクエストするには下記のように行います。

requests.get(url, headers=辞書型のヘッダデータ)

ヘッダ情報としてユーザーエージェントを変更してアクセスするにはこのように指定します。

headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 '}
res = requests.get(url, headers=headers)

GET通信でパラメータを変更して通信する場合は以下のように指定します。

requests.get(url, params=辞書型のパラメータデータ)

params = {'key1': 'value1', 'key2': 'value2'}
res = requests.get(url, params=params)

POST通信でリクエストボディ部に情報を詰めてリクエストするには下記のように行います。

requests.get(url, data=辞書型のボディデータ)

payload = {'send': 'data'}
res = requests.post(url, data=payload)

まとめ

スクレイピングに必要な通信の仕組みを抑えておき情報取得が出来るようになっておこう。明日はこの続きで取得した情報から必要な情報を抜き出すところに入っていきます。

君がエンジニアになるまであと29日

作者の情報

乙pyのHP:
http://www.otupy.net/

Youtube:
https://www.youtube.com/channel/UCaT7xpeq8n1G_HcJKKSOXMw

Twitter:
https://twitter.com/otupython

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで国名コード取得

とあるコンペをするにあたって、国名コードが必要になった。
これからも使う機会があるのかなと思って、作ってみました。

関数で作成しました。
スクレイピング元

Python jupyterCode

country_code.ipynb
def country_code(output,file_name):
    '''
    ***help***
    output : 現状csvのみ
    file_name : 任意のファイルネームを取得
    今回使用しているpandas.read_htmlは、lxml、html5lib、
    beautifulsoup4が必要なので、pipでインストールしておく
    '''
    import pandas as pd
    url = "https://www-yousei.jsps.go.jp/yousei1/kuniList.do"
    dfs = pd.read_html(url,header = 0)
    if output == "csv":
        dfs[0].to_csv(file_name + ".csv")
    return dfs[0]

Gitにもupしているので、気になった方、他のコード類を取得するコードを上げていただけると助かります。
GitHub Link

なお、私はGitHub初心者でわからないことだらけなので、何かありましたらコメントで教えていただけると幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GCIデータサイエンティスト育成講座の演習問題を解く Chapter5

GCIデータサイエンティスト育成講座

GCIデータサイエンティスト育成講座」は、東京大学(松尾研究室)が開講している"実践型のデータサイエンティスト育成講座およびDeep Learning講座"で、演習パートのコンテンツがJupyterNoteBook形式で公開(CC-BY-NC-ND)されています。
Chapter5は「Pythonによる科学計算の基礎(NumpyとScipy)」で、科学計算を行うためのライブラリーNumpyとScipyの基礎知識を習得していきます。
日本語で学べる貴重で素晴らしい教材を公開いただいていることへの「いいね!」ボタンの代わりに、解いてみた解答を載せてみます。間違っているところがあったらご指摘ください。

5章3節は理解の追いつかない内容が多く、演習はほぼコピペでした。後で必要に応じて復習必要かも…。

5 Pythonによる科学計算の基礎(NumpyとScipy)

5.1 概要

5.1.1 この章の概要について

5.2 Numpy

5.2.1 インデックス参照

<練習問題 1>
上記データのsample_namesのbに該当するdataを抽出してください。

data[sample_names == 'b']
> array([[-0.977,  0.95 , -0.151, -0.103,  0.411]])

<練習問題 2>
上記データのsample_namesのc以外に該当するdataを抽出してください。

data[sample_names != 'c']
> array([[ 1.764,  0.4  ,  0.979,  2.241,  1.868],
>        [-0.977,  0.95 , -0.151, -0.103,  0.411],
>        [ 0.334,  1.494, -0.205,  0.313, -0.854],
>        [-2.553,  0.654,  0.864, -0.742,  2.27 ]])

<練習問題 3>
上記のcond_dataを変更して、x_arrayの3番目と4番目、y_arrayの1番、2番、5番目を出すように、条件制御を実施してください。

cond_data = np.array([False,False,True,True,False])
print(np.where(cond_data,x_array,y_array))
> [ 6  7  3  4 10]

5.2.2 Numpyの演算処理

<練習問題 1>
以下のデータに対して、すべての要素の平方根を計算した行列を表示してください。

sample_multi_array_data2 = np.arange(16).reshape(4,4)
print(sample_multi_array_data2)
print(sample_multi_array_data2 ** 0.5)
> [[ 0  1  2  3]
>  [ 4  5  6  7]
>  [ 8  9 10 11]
>  [12 13 14 15]]
> [[0.    1.    1.414 1.732]
>  [2.    2.236 2.449 2.646]
>  [2.828 3.    3.162 3.317]
>  [3.464 3.606 3.742 3.873]]

<練習問題 2>
上記のデータsample_multi_array_data2の最大値、最小値、合計値、平均値を求めてください。

print(sample_multi_array_data2.max())
print(sample_multi_array_data2.min())
print(sample_multi_array_data2.sum())
print(sample_multi_array_data2.mean())
> 15
> 0
> 120
> 7.5

<練習問題 3>
上記のデータsample_multi_array_data2の対角成分の和を求めてください。

np.trace(sample_multi_array_data2)
> 30

5.2.3 配列操作とブロードキャスト

<練習問題 1>
次の2つの配列に対して、縦に結合してみましょう。

np.concatenate([sample_array1, sample_array1], axis=0)
# データの準備
sample_array1 = np.arange(12).reshape(3,4)
sample_array2 = np.arange(12).reshape(3,4)

np.concatenate([sample_array1, sample_array1], axis=0)
> array([[ 0,  1,  2,  3],
>        [ 4,  5,  6,  7],
>        [ 8,  9, 10, 11],
>        [ 0,  1,  2,  3],
>        [ 4,  5,  6,  7],
>        [ 8,  9, 10, 11]])

<練習問題 2>
上記の2つの配列に対して、横に結合してみましょう。

np.concatenate([sample_array1, sample_array1], axis=1)
> array([[ 0,  1,  2,  3,  0,  1,  2,  3],
>        [ 4,  5,  6,  7,  4,  5,  6,  7],
>        [ 8,  9, 10, 11,  8,  9, 10, 11]])

<練習問題 3>
普通の以下のリストの各要素に3を加えるためにはどうすればよいでしょうか。Numpyのブロードキャスト機能を使ってください。

sample_list = [1,2,3,4,5]
np.array(sample_list) + 3
> array([4, 5, 6, 7, 8])

5.3 Scipy

5.3.1 補間

<練習問題 1>
以下のデータに対して、線形補間の計算をして、グラフを描いてください。

x = np.linspace(0, 10, num=11, endpoint=True)
x_ = np.linspace(0, 10, num=30, endpoint=True)
y = np.sin(x**2/5.0)
plt.plot(x,y,'o')
plt.grid(True)

f_1 = interpolate.interp1d(x, y,'linear')
plt.plot(x_, f_1(x_), '-')

531-1.png

<練習問題 2>
2次元のスプライン補間をして上記のグラフに書き込んでください(2次元のスプライン補間はパラメタをquadraticとします。)

f_2 = interpolate.interp1d(x, y,'quadratic')
plt.plot(x, y, 'o', x_, f_1(x_), '-', x_, f_2(x_), '--')
plt.grid(True)

531-2.png

<練習問題 3>
3次元スプライン補間も加えてみましょう。

f_3 = interpolate.interp1d(x, y,'cubic')
plt.plot(x, y, 'o', x_, f_1(x_), '-', x_, f_2(x_), '--', x_, f_3(x_), '-.')
plt.grid(True)

531-3.png

5.3.2 線形代数:行列の分解

<練習問題 1>
以下の行列に対して、特異値分解をしてください。

B = np.array([[1,2,3],[4,5,6],[7,8,9],[10,11,12]])
print(B)
> [[ 1  2  3]
>  [ 4  5  6]
>  [ 7  8  9]
>  [10 11 12]]
# 特異値分解の関数linalg.svd
U, s, Vs = sp.linalg.svd(B)
m, n = B.shape

S = sp.linalg.diagsvd(s,m,n)

print("U.S.V* = \n",U@S@Vs)
> U.S.V* = 
>  [[ 1.  2.  3.]
>  [ 4.  5.  6.]
>  [ 7.  8.  9.]
>  [10. 11. 12.]]

<練習問題 2>
以下の行列に対して、LU分解をして、 ??=? の方程式を解いてください。

#データの準備
A = np.identity(3)
print(A)
A[0,:] = 1
A[:,0] = 1
A[0,0] = 3
b = np.ones(3)
print(A)
print(b)
> [[1. 0. 0.]
>  [0. 1. 0.]
>  [0. 0. 1.]]
> [[3. 1. 1.]
>  [1. 1. 0.]
>  [1. 0. 1.]]
> [1. 1. 1.]
# 正方行列をLU分解する
(LU,piv) = sp.linalg.lu_factor(A)

L = np.identity(3) + np.tril(LU,-1)
U = np.triu(LU)
P = np.identity(3)[piv]

# 解を求める
x = sp.linalg.lu_solve((LU,piv),b)
print(x)
> [-1.  2.  2.]

5.3.3 積分と微分方程式

<練習問題 1>
以下の積分を求めてみましょう。

  \int_0^2 (x+1)^2 dx
integrate.quad(lambda x: (x+1)**2, 0, 2)
> (8.667, 0.000)

<練習問題 2>
cos関数の範囲 (0,?) の積分を求めてみましょう。

from numpy import cos
integrate.quad(cos, 0, math.pi)
(0.000, 0.000)

5.3.4 最適化

<練習問題 1>
以下の関数が0となる解を求めましょう。

\ f(x) = 5x -10
from scipy.optimize import fsolve

def f1(x):
    return 5 * x - 10

x = np.linspace(-100,100)
plt.plot(x,f1(x))
plt.plot(x,np.zeros(len(x)))
plt.grid(True)
plt.show()

x1 = fsolve(f1,0)
print(x1)

534-1.png

> [2.]

<練習問題 2>
以下の関数が0となる解を求めましょう。

\ f(x) = x^3 - 2x^2 - 11x +12
from scipy.optimize import fsolve

def f2(x):
    return x**3 - 2 * x**2 - 11 * x + 12

x = np.linspace(-5,5)
plt.plot(x,f2(x))
plt.plot(x,np.zeros(len(x)))
plt.grid(True)
plt.show()

x1 = fsolve(f2,-99999)
x2 = fsolve(f2,0)
x3 = fsolve(f2,99999)
print(x1, x2, x3)

534-2.png

> [-3.] [1.] [4.]

5.4 総合問題

5.4.1 総合問題1
以下の行列に対して、コレスキー分解を活用して、??=?の方程式を解いてください。

A = np.array([[5, 1, 0, 1],
              [1, 9, -5, 7],
              [0, -5, 8, -3],
              [1, 7, -3, 10]])
b = np.array([2, 10, 5, 10])
L = sp.linalg.cholesky(A)

t = sp.linalg.solve(L.T.conj(), b)
x = sp.linalg.solve(L, t)

# 解答
print(x)
> [-0.051  2.157  2.01   0.098]

5.4.2 総合問題2
0≤?≤1、0≤y≤1−x の三角領域で定義される以下の関数の積分値を求めてみましょう。

\int_0^1 \int_0^{1-x} 1/(\sqrt{(x+y)}(1+x+y)^2) dy dx 
from scipy import integrate

integrate.dblquad(lambda y, x:  1 / ((x + y)**(1/2) * (1 + x + y)**2), 0, 1, lambda x: 0, lambda x: 1-x)

# 関数をf(x, y)とした場合はどうすれば良いんだろう…
# def f(x,y):
#     1 / ((x + y)**(1/2) * (1 + x + y)**2)
# integrate.dblquad(f, 0, 1, lambda x: 0, lambda x: 1-x)
(0.285, 0.000)

5.4.3 総合問題3
以下の最適化問題をSicpyを使って解いてみましょう。

\ min \ f(x) = x^2+1
\\  s.t. x \ge -1
from scipy.optimize import minimize

def f(x):
    return x**2 + 1

def constraint(x):
    return x+1

x0 = -1
con = {'type':'ineq','fun':constraint}
sol = minimize(f,x0,method='SLSQP',constraints=con)

print("x = " + str(sol.x) + " のとき、f(x)は最小値 " + str(sol.fun) + " を取る")

# memo
# conの"'type': 'eq'"は=0, "'type': 'ineq'"は=>0
> x = [2.22e-16] のとき、f(x)は最小値 1.0 を取る
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Qiskit: 量子ボルツマンマシンの実装

はじめに

同じ内容の記事を先日投稿しましたが,寝ぼけて削除してしまいました.申し訳ないです.

今回は量子ボルツマンマシンをQiskitを用いて実装を行ってみました.
参考とした論文はこちらです.
A quantum algorithm to train neural networks using low-depth circuits
また,量子アニーリングマシンを用いたボルツマンマシンはminatoさんの記事で紹介しているのでそちらにお任せしたいと思います.
D-Waveから量子ゲートマシンまでのRBMのボルツマン学習

制限ボルツマンマシンはニューラルネットワークの最小単位と言われています.
ちなみに,著者は機械学習の専門家ではないので詳しい説明は他の方にお任せします.

QABoM

この論文ではQuantum Approximate Boltzmann Machine (QABoM) Algorithmとして紹介しています.
細かい内容まで踏み込む時間はないので,Appendixで紹介されているQABoMの流れを紹介したいと思います.

なお,使用するindexは以下の通りです.
・v: visible層
・h: hidden層
・u: visible+hidden層

step 0

Define the full and partial initial Hamiltonians.

H_I = \sum_{j \in u} Z_j \\
H_{\tilde{I}} = \sum_{j \in h} Z_j 

式を見ると,fullはvisible+hiddenについて,partialはhiddenについてですね.次に
Define the full and partial mixer Hamiltonians.

H_M = \sum_{j \in u} X_j \\
H_{\tilde{M}} = \sum_{j \in h} X_j 

ボルツマンマシンの変数である,weight$J_{jk}^0$とbias$B_j^0$を定義.
epoch 1以上について次に示すstep 1からstep 5を繰り返します.

step 1

epoch nにおいてfull cost Hamiltonianとpartial cost Hamiltonianを定義します.

\hat{H}_C^n = \sum_{j,k \in u} J_{jk}^n \hat{Z}_j \hat{Z}_k + \sum_{j \in u} B_j^n \hat{Z}_j \\
\hat{H}_{\tilde{C}}^n = \sum_{j,k \in u} J_{jk}^n \hat{Z}_j \hat{Z}_k + \sum_{j \in h} B_j^n \hat{Z}_j

step 2 Unclamped Thermalization

step 0, step 1で定義した式を用いてQAOAを実行していきます.

a

pulse parametersを初期化 -> $\gamma, \beta$

b

$H_M$と$H_C$を使ってQAOAを実行
(QAOAの解説については他のサイトを見てください...)
最適化parameterを求めます.
ここでは$\hat{H}_C^n$で期待値を求めています.

c

最適なparameterを入力したパラメータcircuitを実行し,期待値$ <Z_j Z_k> $と$ <Z_j> $を求める.

step 3 Clamped thermalization

各data string $x$について

a

pulse parametersを初期化 -> $\gamma, \beta$

b

visible層に$x$をencodeする.

c

$H_M$と$H_C$を使ってQAOAを実行
(QAOAの解説については他のサイトを見てください...)
最適化parameterを求めます.
ここでは$\hat{H}_{\tilde{C}}^n$で期待値を取っています.

d

最適なparameterを入力したパラメータcircuitを実行し,期待値$ <Z_j Z_k> $と$ <Z_j> $を求める.

step 4

パラメータを更新していきます.
ここで$ <\bar{\cdots}>_D $はstep 3の結果の平均です.

\delta J_{jk}^n = <\bar{Z_j Z_k}>_D -< Z_j Z_k> \\
\delta B_j^n = < \bar{Z_j}> - <Z_j>_D \\
J_j^{n+1} = J_j^n + \delta J_j^n \\
B_j^{n+1} = B_j^n + \delta B_j^n

step 5

epoch = n+1 back to step 1

code

import とclassの初期化

# coding: utf-8

from qiskit.aqua.utils import tensorproduct
from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister
from qiskit.quantum_info.analysis import average_data

import numpy as np
from copy import deepcopy

from QAOA import QAOA
from my_functions import sigmoid

class QBM:

    def __init__(self, num_visible=2, num_hidden=1, steps=3,
                 tmep=1.0, quant_meas_num=None, bias=False, reduced=False):
        self.visible_units = num_visible  # v
        self.hidden_units = num_hidden  # h
        self.total_units = self.visible_units + self.hidden_units
        self.quant_meas_num = quant_meas_num
        self.qaoa_steps = steps
        self.beta_temp = tmep
        self.state_prep_angle = np.arctan(np.exp(-1 / self.beta_temp)) * 2.0
        self.param_wb = 0.1 * np.sqrt(6. / self.total_units)
        self.WEIGHTS = np.asarray(np.zeros(shape=[self.visible_units, self.hidden_units]))
        if bias:
            self.BIAS = np.asarray(np.zeros(shape=[self.hidden_units]))
        else:
            self.BIAS = None
        self.reduced = reduced
        self.obs = self.observable()
        self.obs_tilde = self.observable(tilde=True)
        self.data_point = None

observableの作成.tilde=Trueとすることで$$を作成することができます.

    def Zi_Zj(self, q1, q2):
        I_mat = np.array([[1, 0], [0, 1]])
        Z_mat = np.array([[1, 0], [0, -1]])
        if q1 == 0 or q2 == 0:
            tensor = Z_mat
        else:
            tensor = I_mat
        for i in range(1, self.total_units):
            if i == q1 or i == q2:
                tensor = tensorproduct(tensor, Z_mat)
            else:
                tensor = tensorproduct(tensor, I_mat)
        return tensor

    def Zi(self, q):
        I_mat = np.array([[1, 0], [0, 1]])
        Z_mat = np.array([[1, 0], [0, -1]])
        if q == 0:
            tensor = Z_mat
        else:
            tensor = I_mat
        for i in range(1, self.total_units):
            if i == q:
                tensor = tensorproduct(tensor, Z_mat)
            else:
                tensor = tensorproduct(tensor, I_mat)
        return tensor

    def observable(self, tilde=False):
        visible_indices = [i for i in range(self.visible_units)]
        hidden_indices = [i + self.visible_units for i in range(self.hidden_units)]
        total_indices = [i for i in range(self.total_units)]

        obs = np.zeros((2**self.total_units, 2**self.total_units))

        for q1 in visible_indices:
            for q2 in hidden_indices:
                obs += -1.0 * self.Zi_Zj(q1, q2)

        if self.BIAS is not None:
            if tilde:
                for q in hidden_indices:
                    obs += -1.0 * self.Zi(q)
            elif not tilde:
                for q in total_indices:
                    obs += -1.0 * self.Zi(q)
        return obs

パラメーターcircuitを作成します.

    def U_circuit(self, params, qc):
        visible_indices = [i for i in range(self.visible_units)]
        hidden_indices = [i + self.visible_units for i in range(self.hidden_units)]
        total_indices = [i for i in range(self.total_units)]

        p = 2

        alpha = self.state_prep_angle
        for i in total_indices:
            qc.rx(alpha, i)
        for i in visible_indices:
            for j in hidden_indices:
                qc.cx(i, j)

        beta, gamma = params[:p], params[p:]

        def add_U_X(qc, beta):
            for i in total_indices:
                qc.rx(-2**beta, i)
            return qc

        def add_U_C(qc, gamma):
            for q1 in visible_indices:
                for q2 in hidden_indices:
                    qc.cx(q1, q2)
                    qc.rz(-2**gamma, q2)
                    qc.cx(q1, q2)
            return qc

        for i in range(p):
            qc = add_U_C(qc, gamma[i])
            qc = add_U_X(qc, beta[i])

        return qc

step 2を実行します.

    def unclamped_circuit(self, params):
        qr = QuantumRegister(self.total_units)
        cr = ClassicalRegister(self.total_units)
        qc = QuantumCircuit(qr, cr)
        qc = self.U_circuit(params, qc)
        qc.measure(range(self.total_units), range(self.total_units))
        return qc

    def make_unclamped_QAOA(self):
        qaoa = QAOA(qc=self.unclamped_circuit, observable=self.obs, num_shots=10000, p=2)
        counts = qaoa.qaoa_run()
        return counts

ここでQAOAという自作ライブラリーを使用していますが,下記で紹介します.
次にstep 3です.
step 2と異なる箇所はデータのencodeが含まれている所です.

    def clamped_circuit(self, params):
        qr = QuantumRegister(self.total_units)
        cr = ClassicalRegister(self.total_units)
        qc = QuantumCircuit(qr, cr)
        for i in range(len(self.data_point)):
            if self.data_point[i] == 1:
                qc.x(i)
        qc = self.U_circuit(params, qc)
        qc.measure(range(self.total_units), range(self.total_units))
        return qc

    def make_clamped_QAOA(self, data_point):
        self.data_point = data_point
        qaoa = QAOA(qc=self.clamped_circuit, observable=self.obs_tilde, num_shots=10000, p=2)
        counts = qaoa.qaoa_run()
        return counts

最後に学習の実行です.

    def train(self, DATA, learning_rate=1, n_epochs=100, quantum_percentage=1.0, classical_percentage=0.0):

        assert (quantum_percentage + classical_percentage == 1.0)
        DATA = np.asarray(DATA)
        assert (len(DATA[0]) <= self.visible_units)

        for epoch in range(n_epochs):

            print('Epoch: {}'.format(epoch+1))

            visible_indices = [i for i in range(self.visible_units)]
            hidden_indices = [i + self.visible_units for i in range(self.hidden_units)]
            total_indices = [i for i in range(self.total_units)]

            new_weights = deepcopy(self.WEIGHTS)
            if self.BIAS is not None:
                new_bias = deepcopy(self.BIAS)

            counts = self.make_unclamped_QAOA()
            unc_neg_phase_quant = np.zeros_like(self.WEIGHTS)
            for i in range(self.visible_units):
                for j in range(self.hidden_units):
                    model_expectation = average_data(counts, self.Zi_Zj(visible_indices[i], hidden_indices[j]))
                    unc_neg_phase_quant[i][j] = model_expectation

            unc_neg_phase_quant *= (1. / float(len(DATA)))

            if self.BIAS is not None:
                unc_neg_phase_quant_bias = np.zeros_like(self.BIAS)
                for i in range(self.hidden_units):
                    model_expectation = average_data(counts, self.Zi(hidden_indices[i]))
                    unc_neg_phase_quant_bias[i] = model_expectation

                unc_neg_phase_quant_bias *= (1. / float(len(DATA)))

            pos_hidden_probs = sigmoid(np.dot(DATA, self.WEIGHTS))
            pos_hidden_states = pos_hidden_probs > np.random.rand(len(DATA), self.hidden_units)
            pos_phase_classical = np.dot(DATA.T, pos_hidden_probs) * 1. / len(DATA)

            c_pos_phase_quant = np.zeros_like(self.WEIGHTS)
            if self.BIAS is not None:
                c_pos_phase_quant_bias = np.zeros_like(self.BIAS)

            if not self.reduced:

                iter_dat = len(DATA)
                pro_size = len(DATA)
                pro_step = 1

                for data in DATA:
                    counts = self.make_clamped_QAOA(data)
                    ct_pos_phase_quant = np.zeros_like(self.WEIGHTS)

                    for i in range(self.visible_units):
                        for j in range(self.hidden_units):
                            model_expectation = average_data(counts, self.Zi_Zj(visible_indices[i], hidden_indices[j]))
                            ct_pos_phase_quant[i][j] = model_expectation
                    c_pos_phase_quant += ct_pos_phase_quant

                    if self.BIAS is not None:
                        ct_pos_phase_quant_bias = np.zeros_like(self.BIAS)
                        for i in range(self.hidden_units):
                            model_expectation = average_data(counts, self.Zi(hidden_indices[i]))
                            ct_pos_phase_quant_bias[i] = model_expectation
                        c_pos_phase_quant_bias *= ct_pos_phase_quant_bias

                    pro_bar = ('==' * pro_step) + ('--' * (pro_size - pro_step))
                    print('\r[{0}] {1}/{2}'.format(pro_bar, pro_step, pro_size), end='')
                    pro_step += 1

                c_pos_phase_quant *= (1. / float(len(DATA)))
                if self.BIAS is not None:
                    c_pos_phase_quant_bias *= (1. / float(len(DATA)))

            neg_visible_activations = np.dot(pos_hidden_states, self.WEIGHTS.T)
            neg_visible_probs = sigmoid(neg_visible_activations)

            neg_hidden_activations = np.dot(neg_visible_probs, self.WEIGHTS)
            neg_hidden_probs = sigmoid(neg_hidden_activations)

            neg_phase_classical = np.dot(
                neg_visible_probs.T, neg_hidden_probs) * 1. / len(DATA)

            new_weights += learning_rate * \
                           (classical_percentage * (pos_phase_classical - neg_phase_classical) +
                            quantum_percentage * (c_pos_phase_quant - unc_neg_phase_quant))

            if self.BIAS is not None:
                new_bias = new_bias + learning_rate * \
                           (quantum_percentage * (c_pos_phase_quant_bias - unc_neg_phase_quant_bias))

            self.WEIGHTS = deepcopy(new_weights)
            print(self.WEIGHTS)
            if self.BIAS is not None:
                self.BIAS = deepcopy(new_bias)
            with open("RBM_info.txt", "w") as f:
                np.savetxt(f, self.WEIGHTS)
                if self.BIAS is not None:
                    np.savetxt(f, self.BIAS)
            with open("RBM_history.txt", "a") as f:
                np.savetxt(f, self.WEIGHTS)
                if self.BIAS is not None:
                    np.savetxt(f, self.BIAS)
                f.write(str('*' * 72) + '\n')
            print('')

        print("Training Done! ")

    def transform(self, DATA):
        return sigmoid(np.dot(DATA, self.WEIGHTS))


if __name__ == '__main__':

    qbm = QBM(num_visible=6, num_hidden=2,bias=True)

    train_data = [[1, 1, 1, 1, 1, 1],
                  [1, 1, -1, -1, 1, 1], [1, -1, -1, -1, 1, 1], [1, 1, 1, -1, -1, -1]]

    qbm.train(DATA=train_data, n_epochs=100, quantum_percentage=1.0, classical_percentage=0.0)

    print(qbm.transform(train_data))

QAOA

自作ライブラリーQAOAは以前紹介したQiskit Aquaを使わないQAOAの実装で用いたcodeを汎用的に拡張したものです.

QAOA.py
# coding: utf-8

from qiskit import BasicAer, execute
from qiskit.quantum_info.analysis import average_data

from scipy.optimize import minimize
import numpy as np
import random


def classica_minimize(cost_func, initial_params, options, method='powell'):
    result = minimize(cost_func, initial_params, options=options, method=method)
    return result.x


class QAOA:

    def __init__(self, qc, observable, num_shots, p=1, initial_params=None):
        self.QC = qc
        self.obs = observable
        self.SHOTS = num_shots
        self.P = p
        if initial_params is None:
            self.initial_params = [0.1 for _ in range(self.P * 2)]
        else:
            self.initial_params = initial_params

    def QAOA_output_layer(self, params):
        qc = self.QC(params)
        backend = BasicAer.get_backend('qasm_simulator')
        results = execute(qc, backend, shots=self.SHOTS).result()
        counts = results.get_counts(qc)
        expectation = average_data(counts, self.obs)
        return expectation

    def minimize(self):
        initial_params = np.array(self.initial_params)
        opt_params = classica_minimize(self.QAOA_output_layer, initial_params,
                                       options={'maxiter':500}, method='powell')
        return opt_params

    def qaoa_run(self):
        opt_params = self.minimize()
        qc = self.QC(opt_params)
        backend = BasicAer.get_backend('qasm_simulator')
        results = execute(qc, backend, shots=self.SHOTS).result()
        counts = results.get_counts(qc)
        return counts

if __name__ == '__main__':
    pass

計算結果

今回は古典の制限ボルツマンマシンでも同じデータを用いて計算機実験を行い,比較してみました.
古典制限ボルツマンマシンは別の方が実装しているものがあったので参考にさせてもらいました.
pythonで制約ボルツマンマシン実装

実行環境

OS: macOS Catalina ver 10.15.5
CPU: intel Core i7
メモリ: 16 GB

パラメータ

学習率:0.2
epochs: 100

古典ボルツマンマシンの結果

[[7.57147606e-220 4.69897282e-106]
 [1.00548918e-308 1.00000000e+000]
 [1.00000000e+000 3.79341879e-197]
 [1.00000000e+000 7.56791638e-202]
 [1.00000000e+000 2.85625356e-196]
 [1.00000000e+000 2.51860403e-243]
 [0.00000000e+000 1.00000000e+000]
 [9.02745172e-142 1.00000000e+000]]

QABoMの結果

[[0.59991237 0.68602485]
 [0.74497707 0.89553788]
 [0.26436565 0.113528  ]
 [0.2682931  0.1131361 ]
 [0.58273232 0.3268427 ]
 [0.41413436 0.14647751]
 [0.74056361 0.94317095]
 [0.7317069  0.8868639 ]]

ちなみにこれ,計算時間半日近くかかっています.(測定するの忘れた...)
一応古典RBMと同じ結果が得られました.が,古典ほどはっきりとした解ではありませんね.

まとめ

今回は論文に掲載されていたQABoMの実装を紹介しました.
もう少し計算機実験を行って,記事を更新していこうかなと思います.

拙い日本語で申し訳ありませんでした.国語力の改善に努めます.

参考文献一覧

QABoM: A quantum algorithm to train neural networks using low-depth circuits
RBM: pythonで制約ボルツマンマシン実装

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ベルマンフォード法とダイクストラ法の概念を完全に理解する

概要

社内の勉強会でダイクストラ法(dijkstra)とベルマンフォード法(bellman-ford)の紹介をしました。その際にアルゴリズムの証明を色々調べていましたが、解説されている記事があまり多くないことに気づきました。
「なぜそうなるの?」という部分は個人的に大事な要素だと思うので、その辺も踏まえてこれらのアルゴリズムを紹介したいと思います。

筆者はどんな人?

プログラミング歴:1年ちょい(Pythonのみ)
Atcoder茶(よわよわ)
2020年4月からデータ分析委託の企業に勤めているが、なぜかアルゴリズムの勉強ばっかりやっている。

このアルゴリズムはいつ使うの?

重み付きグラフの単一始点最短経路問題を解く際に使います。

それぞれの用語ですが、
重み付き:辺を通るのに必要なコスト
単一始点:スタート地点が一か所
最短経路:スタート地点から目的地まで最小コストで向かう経路
です。

下のグラフを例にすると、
image.png
スタート地点$A$から全ての辺への最短経路を求めることが目的になります。
ちなみに$A→E$の最短経路は
$(A→D→B→F→E)$
です。これをアルゴリズムを使用して求めていきます。

アルゴリズムの前に、グラフの大事な性質

1.最短路の部分道は最短路

最短路の部分道はどこをとっても最短路になります。

<証明>
image.png
仮に上のグラフで$A→E$の最短路を
$(A→B→C→D→E cost:5)$
とします。

このうち$(A→B→C cost:3)$
はこの最短路の部分道ですが、この区間にはB'という頂点を利用すれば
コストを1少なく移動できます。
するとこの区間の最短路は
$(A→B'→C cost:2)$
となるため、$A→E$を
$(A→B'→C→D→E cost:4)$
で移動できてしまい、仮定と矛盾します。
最短路の部分道は必ず最短路になります。

2.経路緩和性

始点に関連する頂点から順番に最短路を求めていくと、最終的にすべての最短経路が求められます。
これは結局、始点から徐々に求めた最短路がその先に到達する頂点の部分道となるためです。

ベルマンフォード法とは

全頂点間の最短路を求めます。ダイクストラ法と大きく異なる点は、負の重みが許容されることです。

最短路を求めるプロセスは、

  1. 全ての辺に注目し、その辺を通った先の頂点のコストを小さくできれば値を更新
  2.  1.のプロセスを(頂点の数-1)回行う です。

下の図だと、

image.png

1.のプロセスで、$A$を出発し、$C$にコスト$5$で到達できるので、$C:5$とメモします。
次に、$A→B$は$-1$で到達できるので$B:-1$、$B→C$にはコスト$-1+2=1$で到達できます。
この時、$B$を経由するとより小さなコストでCに到達できることが分かったので、$C:1$と更新します。
この作業を全ての辺に対して行います。これが1回目。
あとは(頂点の数-1)回繰り返せば、$A$から出発して全ての頂点への最短路を求めることが出来ます。

なぜ(頂点の数-1)回行うの?

始点から目的地までに通る頂点の最大の個数は、始点を除く頂点、すなわち(頂点の数-1)個です。
前述したプロセスでは、1回目のループで始点から移動できる頂点)の最短路が必ず求められます。
2回目のループでは(1回目で決まったルートから移動できる頂点)の最短路が必ず求められます。



結局このループは、始点をスタートして順番に最短路を求める作業になるので、経路緩和性により最短路を求めることができます。

負の重みによって困る場合

下の図を見てください。
image.png
丸で囲んだ部分はサイクルになっていますが、ここを1周するとコストが-2下がります。
つまりこのサイクルを使えば無限にコストを下げることが可能です。
これを負閉路といいます。

ベルマンフォード法では、負閉路の検出も可能です。
先ほど、(頂点の数-1)回のループで最短路を求めることを確認しましたが、再度全ての頂点のコストを計算し、コストを更新できる頂点がある場合は負閉路が存在するということになります。

いいからソースコード見せろよ

隣接リスト形式(競プロとかでよく利用する、隣接行列というのもある)の入力に対応し、ベルマンフォード法により最短路を検出します。
コードの下に負閉路のある入力例を示します(上の図のグラフ)。負閉路を通る頂点のコストを$-inf$に置き換えています。

クリックしたら見れるよ
def shortest_path(s,n,es):
    #s→iの最短距離
    # s:始点, n:頂点数, es[i]: [辺の始点,辺の終点,辺のコスト]

    #重みの初期化。ここに最短距離をメモしていく
    d = [float("inf")] * n

    #スタート地点は0
    d[s] = 0

    #最短路を計算
    for _ in range(n-1):
        # es: 辺iについて [from,to,cost]
        for p,q,r in es:
            #矢印の根が探索済、かつコストができるルートがあれば更新
            if d[p] != float("inf") and d[q] > d[p] + r:
                d[q] = d[p] + r

    #負閉路をチェック
    for _ in range(n-1):
        # e: 辺iについて [from,to,cost]
        for p,q,r in es:
            #更新される点は負閉路の影響を受けるので-infを入れる
            if d[q] > d[p] + r:
                d[q] = -float("inf")
    return d
################
#入力
n,w = map(int,input().split()) #n:頂点数 w:辺の数

es = [] #es[i]: [辺の始点,辺の終点,辺のコスト]
for _ in range(w):
    x,y,z = map(int,input().split())
    es.append([x,y,z])
    # es.append([y,x,z])

print(shortest_path(0,n,es))

'''
入力
7 12
0 1 2 
0 2 -5
0 3 5
1 2 3
1 4 1
1 5 3
2 4 6
3 2 -2
3 5 4
4 6 -5
5 4 -2
6 2 -3
'''
>>[0, 2, -inf, 5, -inf, 5, -inf]

計算量は$O(V^2+E)$(頂点の数:$V$、辺の数:$E$)です。

ダイクストラ法とは

非負の重みを持つグラフで使用できます。

最短路を求めるプロセスはヨビノリさんの動画が死ぬほど分かりやすいです。
(ヨビノリさんの守備範囲広すぎる…)

一応こちらでもご説明します。
image.png

頂点のコストを(初期値:$S = 0$, その他$ = ∞$)とします。

  1. 一番コストが小さい頂点を始点とします。最初は$S$が始点です。
  2. 始点のコストを確定します(図では緑になる)。この頂点のコストはこれ以降更新されません。
  3. 始点から移動可能な頂点に出発点のコスト+辺重みを頂点にメモします。中段列の頂点に$7、3、1$とメモされました。コストはまだ更新されるかもしれないので未確定です。

  4. すべての頂点が確定するまで1~3を繰り返します。メモされた頂点の中で1のコストを持つ頂点が一番小さいので確定し、その頂点から移動可能な頂点をメモします。もし、より小さなコストで移動できるのならコストを更新します。

なぜ最短路が求められる?

未確定の頂点のうち最小のコストを持つ頂点は、それ以上小さなコストに更新することができないためです。
非負の重みしか存在しないので、別ルートを通るにしても0以上のコストが発生します。そのため未確定のうち、最小のコストは最短路と確定することが出来るのです。

で、ソースコードは?

クリックしたら見れるよ
import heapq

def dijkstra(s, n, es):
    #始点sから各頂点への最短距離
    #n:頂点数, cost[u][v] : 辺uvのコスト(存在しないときはinf)
    d = [float("inf")] * n
    #探索リスト
    S = [s]
    #優先度付きキューを使うと計算量が少なくなるよ
    heapq.heapify(S)

    d[s] = 0

    while S:
        #最小コストの頂点を取り出し
        v = heapq.heappop(S)
        # print("-----------v", v)
        for u, w in es[v]:
            # print(u, w)
            #より小さなコストで到達可能であれば更新し、リストに追加
            if d[u] > d[v]+w:
                d[u] = d[v]+w
                heapq.heappush(S, u)
        # print("d", d)
    return d

n,w = map(int,input().split()) #n:頂点数 w:辺の数

cost = [[float("inf") for i in range(n)] for i in range(n)] 
es = [[] for _ in range(n)]

for i in range(w):
    x, y, z = map(int, input().split())
    es[x].append([y, z])
    es[y].append([x, z]) #有向グラフの場合はこの行を消す
# print(es)
print(dijkstra(0, n, es))


オリジナルの計算量はベルマンフォード法と同じ$O(V^2)$ですが、優先度付きキューを使用すれば$O((E+V)logV)$に削減できます。

参考文献とかサイト

・アルゴリズムイントロダクション 第3版 第2巻: 高度な設計と解析手法・高度なデータ構造・グラフアルゴリズム (世界標準MIT教科書)
(T.コルメン,R.リベスト,C.シュタイン,C.ライザーソン,浅野哲夫,岩野和生,梅尾博司,山下雅史,和田幸一)
かなり重い本ですが、グラフ理論の証明がとても丁寧でした。

・グラフ理論⑤(ダイクストラのアルゴリズム)
(予備校のノリで学ぶ「大学の数学・物理」)
https://www.youtube.com/watch?v=X1AsMlJdiok
こちらの動画を見るだけで、ダイクストラ法は完全に理解できます!

・蟻本 python 単一最短経路法2(ダイクストラ法) 競技プログラミング
(じゅっぴーさんのブログ、コードはこちらを参考にさせて頂きました)
https://juppy.hatenablog.com/entry/2018/11/01/%E8%9F%BB%E6%9C%AC_python_%E5%8D%98%E4%B8%80%E6%9C%80%E7%9F%AD%E7%B5%8C%E8%B7%AF%E6%B3%952%EF%BC%88%E3%83%80%E3%82%A4%E3%82%AF%E3%82%B9%E3%83%88%E3%83%A9%E6%B3%95%EF%BC%89_%E7%AB%B6%E6%8A%80%E3%83%97

おまけ

記事中のグラフはnetworkxで作図しました。
気になる人向けにコードを置いておきます。

クリックしたら見れるよ
#ダイクストラ法のグラフ
import matplotlib.pyplot as plt
import networkx as nx

#有向グラフのセット
G = nx.DiGraph()

#辺を追加していく
# G.add_edges_from([("A", "B", {"weight":2)])
# nx.add_path(G, ["A", "C"])
# G.edges["A", "B"]["weight"] = 2
G.add_weighted_edges_from([("A", "B", 5), ("A", "C", 7), ("A", "D", 1), ("B", "C", 3), ("B", "F", 3), ("D", "B", 2)])
G.add_weighted_edges_from([("C", "E", 3), ("D", "F", 8), ("F", "E", 2), ("B", "E", 6)])

#図中で頂点の座標を指定しないとランダムに配置される
pos1 = {}
pos1["A"] = (0, 0)
pos1["B"] = (1, 0)
pos1["C"] = (1, 2)
pos1["D"] = (1, -2)
pos1["E"] = (2, 1)
pos1["F"] = (2, -1)

#そのまま描写するとweightの文字が邪魔なので消す
edge_labels = {(i, j): w['weight'] for i, j, w in G.edges(data=True)}

#重みを書き込む
nx.draw_networkx_edge_labels(G, pos1, edge_labels=edge_labels, font_size=18)
#作図
nx.draw_networkx(G, pos1, node_size=1000, label_pos=100, node_color="r", font_size=18)
#図の保存
plt.savefig("dijkstra.png")
plt.show()

#networkxにもダイクストラ法が実装されている。計算量はO(V^2+E)なので遅い
pred, dist = nx.dijkstra_predecessor_and_distance(G, "A")
print(pred, dist)

>>{'A': [], 'B': ['D'], 'C': ['B'], 'D': ['A'], 'F': ['B'], 'E': ['F']} 
>>{'A': 0, 'D': 1, 'B': 3, 'C': 6, 'F': 6, 'E': 8}

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

言語処理100本ノック2020年版を解いてみた【第3章:正規表現 25〜29】

東北大乾・岡崎研(現乾・鈴木研)で作成された、新人研修の一つであるプログラミング基礎勉強会の教材『言語処理100本ノック 2020年版』をPython(3.7)で解いた記事の第6弾です。

独学でPythonを勉強してきたので、間違いやもっと効率の良い方法があるかもしれません。
改善点を見つけた際はご指摘いただけると幸いです。

第3章からは合っているかどうかな部分が多くなってきているので、改善点だけでなく、合っているかどうかという点もぜひご指摘ください。

ソースコードはGitHubにも公開しています。

第3章: 正規表現

Wikipediaの記事を以下のフォーマットで書き出したファイルjawiki-country.json.gzがある.

  • 1行に1記事の情報がJSON形式で格納される

  • 各行には記事名が”title”キーに,記事本文が”text”キーの辞書オブジェクトに格納され,そのオブジェクトがJSON形式で書き出される

  • ファイル全体はgzipで圧縮される

以下の処理を行うプログラムを作成せよ.

25. テンプレートの抽出

記事中に含まれる「基礎情報」テンプレートのフィールド名と値を抽出し,辞書オブジェクトとして格納せよ.

25.py
import pandas as pd
import re


def basic_info_extraction(text):
    texts = text.split("\n")
    index = texts.index("{{基礎情報 国")
    basic_info = []
    for i in texts[index + 1:]:
        if i == "}}":
            break
        if i.find("|") != 0:
            basic_info[-1] += ", " + i
            continue
        basic_info.append(i)

    pattern = r"\|(.*)\s=(.*)"
    ans = {}
    for i in basic_info:
        result = re.match(pattern, i)
        ans[result.group(1)] = result.group(2).lstrip(" ")
    return ans


file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query("title == 'イギリス'")['text'].values[0]

ans = basic_info_extraction(uk_text)
for key, value in ans.items():
    print(key, ":", value)

「基本情報」テンプレートを抽出するために、イギリスのテキストデータを引数にとるbasic_info_extraction()を定義しました。

この関数は"{{基礎情報 国"以降の行に対して}}の閉じカッコが現れるまでを基礎情報としてリストに保存します。
ただ、同じ行のデータでも複数行にまたがっている場合もありました。
その条件は行頭が|から始まっていたので、.find("|")で複数ヒットした場合はカンマで区切った上で1行にまとめる処理を行っています。

そして抽出したデータのリストに対して、|から=手前の半角スペースまでを「フィールド名」として、=以降を「値」として切り分け、「値」の左に空白があれば取り除いた上で辞書オブジェクトに保存し返却しています。

26. 強調マークアップの除去

25の処理時に,テンプレートの値からMediaWikiの強調マークアップ(弱い強調,強調,強い強調のすべて)を除去してテキストに変換せよ(参考: マークアップ早見表).

26.py
import pandas as pd
import re


def basic_info_extraction(text):
    # 「25. テンプレートの抽出」を参照
    ...


def remove_emphasis(value):
    pattern = r"(.*?)'{1,3}(.+?)'{1,3}(.*)"
    result = re.match(pattern, value)
    if result:
        return "".join(result.group(1, 2, 3))
    return value


file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query("title == 'イギリス'")['text'].values[0]

ans = basic_info_extraction(uk_text)
for key, value in ans.items():
    value = remove_emphasis(value) # 追加
    print(key, ":", value)

指定された強調マークアップを除去するための関数を用意しました。
強調マークアップは'が1〜3個連なったもので囲むことで表現されます。
そこで、正規表現のパターンとしてr"(.*?)'{1,3}(.+?)'{1,3}(.*)"を指定し、'以外を()で囲むことで、強調マークアップ以外を抽出。
マッチした部分をリスト化、joinメソッドで結合し値として返却しています。

27. 内部リンクの除去

26の処理に加えて,テンプレートの値からMediaWikiの内部リンクマークアップを除去し,テキストに変換せよ(参考: マークアップ早見表).

27.py
import pandas as pd
import re


def basic_info_extraction(text):
    # 「25. テンプレートの抽出」を参照
    ...


def remove_emphasis(value):
    # 「26. 強調マークアップの除去」を参照
    ...


def remove_innerlink(value):
    pipe_pattern = r"(.*\[\[(.*?)\|(.+)\]\])"
    result = re.findall(pipe_pattern, value)
    if len(result) != 0:
        for i in result:
            pattern = "[[{}|{}]]".format(i[1], i[2])
            value = value.replace(pattern, i[2])
    pattern = r"(\[\[(.+?)\]\])"
    result = re.findall(pattern, value)
    if len(result) != 0:
        for i in result:
            if "[[ファイル:" not in value:
                value = value.replace(i[0], i[-1])
    return value


file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query("title == 'イギリス'")['text'].values[0]

ans = basic_info_extraction(uk_text)
for key, value in ans.items():
    value = remove_emphasis(value)
    value = remove_innerlink(value) # 追加
    print(key, ":", value)

次は内部リンクのマークアップを除去するための関数を用意しました。
内部リンクマークアップは[[〜]]の四角カッコを2個連ねて囲むことで表現されます。
パターンとしては中にパイプを入れて、表示文字と記事名を記載することもできます。
そこで、正規表現のパターンとしてr"(.*\[\[(.*?)\|(.+)\]\])"を指定し、まずはパイプを含むリンクを抽出します。
該当する内部リンクを表示文字のみの内部リンクに置き換え、次の処理に渡します。

パイプを含む内部リンクがなくなり、表示される部分だけが残っているので、r"(\[\[(.+?)\]\])"でパターンを指定して該当する部分を探し、該当部分があればファイル以外をマークアップを除去して値を返却しています。

28. MediaWikiマークアップの除去

27の処理に加えて,テンプレートの値からMediaWikiマークアップを可能な限り除去し,国の基本情報を整形せよ.

28.py
import pandas as pd
import re


def basic_info_extraction(text):
    # 「25. テンプレートの抽出」を参照
    ...


def remove_emphasis(value):
    # 「26. 強調マークアップの除去」を参照
    ...


def remove_innerlink(value):
    # 「27. 内部リンクの除去」を参照
    ...


def remove_footnote(value):
    # 後述
    ...


def remove_langage(value):
    # 後述
    ...


def remove_temporarylink(value):
    # 後述
    ....


def remove_zero(value):
    # 後述
    ...


def remove_br(value):
    # 後述
    ...


def remove_pipe(value):
    # 後述
    ...


file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query("title == 'イギリス'")['text'].values[0]

ans = basic_info_extraction(uk_text)
for key, value in ans.items():
    value = remove_footnote(value)
    value = remove_emphasis(value)
    value = remove_innerlink(value)
    value = remove_langage(value)       # 追加
    value = remove_temporarylink(value) # 追加 
    value = remove_zero(value)          # 追加
    value = remove_br(value)            # 追加
    value = remove_pipe(value)          # 追加
    print(key, ":", value)

脚注コメントの除去

remove_footnote()関数
def remove_footnote(value):
    pattern = r"(.*?)(<ref.*?</ref>)(.*)"
    result = re.match(pattern, value)
    if result:
        return "".join(result.group(1, 3))
    pattern = r"<ref.*/>"
    value = re.sub(pattern, "", value)
    return value

まず脚注の除去です。
引数として得た行が<ref〜</ref>を含んでいたら<ref〜</ref>の前後を結合して値を返します。
これに該当しない<ref〜/>という脚注表記も存在するので、これが残っていたら、取り除いて返却します。

言語タグの除去

remove_language()関数
def remove_langage(value):
    pattern = r"{{lang\|.*?\|(.*?)[}}|)]"
    result = re.match(pattern, value)
    if result:
        return result.group(1)
    return value

続いては言語タグです。
言語タグは{{lang〜}}で囲まれた部分です。
途中のパイプのあとに表示部分が含まれるので、正規表現中でカッコで囲んで、group()メソッドで抽出して値として返します。

仮リンクの除去

remove_temporarylink()関数
def remove_temporarylink(value):
    pattern = r"{{仮リンク\|.*\|(.*)}}"
    result = re.match(pattern, value)
    if result:
        return result.group(1)
    return value

仮リンクの除去は言語タグと若干パターンが違えど、ほぼ同じです。
{{仮リンク〜}}で囲まれた部分をパターンとし、マッチした部分があればgroup()メソッドで抽出です。

囲まれたゼロの除去

remove_zero()関数
def remove_zero(value):
    pattern = r"\{\{0\}\}"
    value = re.sub(pattern, "", value)
    return value

よくわからない{{0}}というものが多々あったので、マッチした部分があれば空文字で置換します。

<br />の除去

remove_br()関数
def remove_br(value):
    pattern = r"<br />"
    value = re.sub(pattern, "", value)
    return value

末尾に改行タグ<br />が含まれていることがあったので、{{0}}同様、マッチしたら空文字で置換します。

パイプの除去

remove_pipe()関数
def remove_pipe(value):
    pattern = r".*\|(.*)"
    result = re.match(pattern, value)
    if result:
        return result.group(1)
    return value

パイプが残っている部分があるので、マッチしたら表記部分だけを返却します。

これらを一通り行うとマークアップを除去したことになります!

29. 国旗画像のURLを取得する

テンプレートの内容を利用し,国旗画像のURLを取得せよ.(ヒント: MediaWiki APIimageinfoを呼び出して,ファイル参照をURLに変換すればよい)

29.py
import pandas as pd
import re
import requests # 追加


def basic_info_extraction(text):
    # 「25. テンプレートの抽出」を参照
    ...


def remove_emphasis(value):
    # 「26. 強調マークアップの除去」を参照
    ...


def remove_innerlink(value):
    # 「27. 内部リンクの除去」を参照
    ...


def remove_footnote(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ...


def remove_langage(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ...


def remove_temporarylink(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ....


def remove_zero(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ...


def remove_br(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ...


def remove_pipe(value):
    # 「28. MediaWikiマークアップの除去」を参照
    ...


file_name = "jawiki-country.json.gz"
data_frame = pd.read_json(file_name, compression='infer', lines=True)
uk_text = data_frame.query("title == 'イギリス'")['text'].values[0]

ans = basic_info_extraction(uk_text)
for key, value in ans.items():
    value = remove_footnote(value)
    value = remove_emphasis(value)
    value = remove_innerlink(value)
    value = remove_langage(value)
    value = remove_temporarylink(value)
    value = remove_zero(value)
    value = remove_br(value)
    value = remove_pipe(value)
    uk_data[key] = value

S = requests.Session()
url = "https://en.wikipedia.org/w/api.php"
params = {
    "action": "query",
    "format": "json",
    "prop": "imageinfo",
    "titles": "File:{}".format(uk_data["国旗画像"])
}

R = S.get(url=url, params=params)
data = R.json()

pages = data["query"]["pages"]
for k, v in pages.items():
    print(v['imageinfo'][0]['url'])

28.までで整形したデータを用いて課題を解決します。
$ pip install requestsでインストールしたRequestsモジュールを使います。
使う必要はないかもしれないのですが、Sessionを用いています。
URLやパラメータを記述し、欲しいデータを取得します。
ここでは課題を解決するためのパラメータを指定しています。
あとはGetリクエストを送り、返ってきたデータを表示すれば完了です。

と言いたいところなのですが、表示したい画像のURLが取得したデータに含まれておらず、エラーになります。

間違っているのか、データが変わったのかまで調査できていないのが現状です…

まとめ

この記事では言語処理100本ノック2020年版 第3章: 正規表現の問題番号25〜29までを解いてみました。

データの整形はどこまですべきなのかとても迷いますよね…
きちんとできているかというのも確かめるのが大変なので、この辺が言語処理で一番難しいところだと個人的には感じております…

独学なので、プロの方とはコードの書き方がこのあたりで如実に差が出てくるのではと思っています。
ぜひより良い書き方をご教示いただければと思います。

よろしくお願いいたします!

前回まで

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenCVのフィルタ処理を調べてみた

初めに

OpenCVで提供されるフィルタリング処理について調べてみました

動作環境

Python3,OpenCV

単純平滑化(ぼかし・ブラー)処理

オリジナルの画像は以下のデータ(グレースケール)であるとして、単純平滑化(ブラー)を行います
Untitled.png

単純平滑化は、それぞれのピクセルを囲む複数のピクセルの長方形の単純平均を、そのピクセルの値とします
ここでは長方形のサイズを(3*3)としている為に以下のように計算されます
Untitled.png

全体のピクセルを計算すると以下のようになります
Untitled.png

import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import matplotlib.cm as cm

black = [0x00, 0x00, 0x00]
white = [0xFF, 0xFF, 0xFF]

img = np.array([
                [black, black, black, black, black, black, black]
                ,[black, black, black, black, black, black, black]
                ,[black, black, white, white, white, black, black]
                ,[black, black, white, white, white, black, black]
                ,[black, black, white, white, white, black, black]
                ,[black, black, black, black, black, black, black]
                ,[black, black, black, black, black, black, black]                               
                ]
                , dtype=np.uint8)

# グレースケール化
img = cv.cvtColor(img, cv.COLOR_RGB2GRAY)

# グラフ描画域
fig = plt.figure(figsize=(8, 3))
ax1 = fig.add_subplot(121, projection='3d')
ax2 = fig.add_subplot(122, projection='3d')

# X,Y
_x = np.arange(img.shape[1])
_y = np.arange(img.shape[0])
_xx, _yy = np.meshgrid(_x, _y)
x, y = _xx.ravel(), _yy.ravel()
width = depth = 1

# 高さ
top = img.ravel()
bottom = np.zeros_like(top)

# 0-255 を 0.0-1.0に変換する関数の定義(グレースケール表示の為)
norm = colors.Normalize(0, 255)

# オリジナル
color_values = cm.gray(top.tolist())
ax1.bar3d(x, y, bottom, width, depth, top, color=color_values)
ax1.set_title('Original')

# Blur(カーネルサイズ 3*3)
blurImg = cv.blur(img, (3, 3))
top = blurImg.ravel()
color_values = cm.gray(norm(top.tolist()))
ax2.bar3d(x, y, bottom, width, depth, top, color=color_values)
ax2.set_title('Blur')

plt.show()

Screen Shot 2020-05-30 at 14.47.09.png

Screen Shot 2020-05-30 at 14.48.04.png

メディアンフィルタ処理

メディアンフィルタは、それぞれのピクセルを囲む長方形の中間値を、そのピクセルの値とします
Untitled.png

全体のピクセルを計算すると以下のようになります
Untitled.png

# medianBlur(カーネルサイズ 3*3)
mBlurImg = cv.medianBlur(img, 3)
top = mBlurImg.ravel()
color_values = cm.gray(norm(top.tolist()))
ax2.bar3d(x, y, bottom, width, depth, top, color=color_values)
ax2.set_title('medianBlur')

Screen Shot 2020-05-30 at 15.00.22.png

Screen Shot 2020-05-30 at 15.02.24.png

Gaussian(ガウシアン)フィルタ処理

ガウシアンフィルタは、それぞれのピクセルを囲む長方形にガウスカーネルと呼ばれる行列を乗じて、その合計値をそのピクセルの値とします
ここで用いている3*3のガウスカーネルは以下のようになります

\begin{pmatrix}
1/16 & 2/16 & 1/16 \\
2/16 & 4/16 & 2/16 \\ 
1/16 & 2/16 & 1/16
\end{pmatrix}

Untitled.png

PixelAは以下のように計算されます

import numpy as np

pixelA = np.array([[0, 0, 0]
                   ,[0, 255, 255]
                   ,[0, 255, 255]])
gaussKernel = np.array([[1/16, 2/16, 1/16]
                        ,[2/16, 4/16, 2/16]
                        ,[1/16, 2/16, 1/16]])
print(sum(sum(pixelA * gaussKernel))) 
#143  

全体のピクセルを計算すると以下のようになります
Untitled.png

# GaussianBlur
gBlurImg = cv.GaussianBlur(img, (3, 3), 0, 0)
top = gBlurImg.ravel()
color_values = cm.gray(norm(top.tolist()))
ax2.bar3d(x, y, bottom, width, depth, top, color=color_values)
ax2.set_title('GaussianBlur')

Screen Shot 2020-05-30 at 14.56.10.png

Screen Shot 2020-05-30 at 14.57.17.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonだけで簡単に!Graphvizの図形、draw.ioで編集したいよね!

はじめに

pythonで作ったネットワーク図をdraw.ioで編集したい人向けの記事です。
①networkxでグラフのもとになるdotフォーマットのデータを作って、
②それをgraphviz2drawioを使ってDraw.ioで使える形式(xlm)で出力します

今のところ、とりあえず出来た、というだけですが、環境づくりに苦労したので、同じような沼にはまる人がいないといいなと思って書きます。

最後にはこういうものが出来上がります。
(VSCodeの拡張機能、Draw.io Integrationで表示しています)

ハマった点

Graphvizで描画するのに使うGraphvizのパッケージと、graphviz2drawioを使うためのGraphvizのパッケージが異なる。環境を別にしないとエラーになってしまう。
Graphviz単体でインストールして使うことを前提としたパッケージと、パッケージ内で完結するものがあるのかもしれない。
あるいはパッケージ間の相性?
今回は、Graphvizでの描画用の環境とは別に、graphviz2drawioで出力するための環境を用意しました。

環境

OS Windows10 64bit
Python 3.7.7(Anaconda3-2019.03で作成)
※Graphvizはインストールしていません

環境構築

Anacondaで、新しいPython3.7の環境を作ります(Datavizと名付けました) 2020年5月30日時点で、python3.7.7でした。

標準パッケージのインストール

下記のパッケージをインストールします。標準パッケージにあります。
・networkx
・pydot (pydotplusではない)
・python-graphviz(これがないとimport graphvizしても動かないので忘れないように)

※慌てて標準のgraphvizを入れないように!

標準外パッケージのインストール

ここからはAnacondaのターミナルを起動して、順番に
・graphviz ( https://anaconda.org/alubbock/graphviz )ver 2.41
・pygraphviz ( https://anaconda.org/alubbock/pygraphviz )ver 1.5
・graphviz2drawio( https://pypi.org/project/graphviz2drawio/0.2.0/
を入れていきます。

(DataViz) C:\Users\(ユーザー名)>conda install graphviz pygraphviz -c alubbock
(DataViz) C:\Users\(ユーザー名)>pip install graphviz2drawio==0.2.0

とします。
標準のgraphvizを入れていると

pygraphviz/graphviz_wrap.c(2987): fatal error C1083: include ファイルを開けません。'graphviz/cgraph.h':No such file or directory

というエラーが出てpygraphvizがインストールできません。

dot.exeのプラグイン登録(?)

これで、dotファイルを扱えるようになっているか確認するためdot -vと打ちます。
何かうまくいっていないと、次のようにdot -cを入れるように言われます。

(DataViz) C:\Users\(ユーザー名)>dot -v
dot - graphviz version 2.41.20170921.1950 (20170921.1950)
There is no layout engine support for "dot"
Perhaps "dot -c" needs to be run (with installer's privileges) to register the plugins?

素直にdot -c(何も出ません)

(DataViz) C:\Users\(ユーザー名)>dot -c
(DataViz) C:\Users\(ユーザー名)>

もう一度dot -cを打って下記の結果が出ればあと少し。
これができていないと、graphviz2drawioを使ったときに "svg" is not recognized… といったエラーが出ます。

dot - graphviz version 2.41.20170921.1950 (20170921.1950)

libdir = "C:\Users\(ユーザー名)\.conda\envs\DataViz\Scripts"
Activated plugin library: gvplugin_dot_layout.dll
Using layout: dot:dot_layout
Activated plugin library: gvplugin_core.dll
Using render: dot:core
Using device: dot:dot:core
The plugin configuration file:
        C:\Users\(ユーザー名)\.conda\envs\DataViz\Scripts\config6
                was successfully loaded.
    render      :  cairo dot dot_json fig gdiplus json json0 map mp pic ps svg tk vml xdot xdot_json
    layout      :  circo dot fdp neato nop nop1 nop2 osage patchwork sfdp twopi
    textlayout  :  textlayout
    device      :  bmp canon cmap cmapx cmapx_np dot dot_json emf emfplus eps fig gif gv imap imap_np ismap jpe jpeg jpg json json0 metafile mp pdf pic plain plain-ext png ps ps2 svg tif tiff tk vml xdot xdot1.2 xdot1.4 xdot_json
    loadimage   :  (lib) bmp eps gif jpe jpeg jpg png ps svg

MxGraph.pyの修正

"C:\Users(ユーザー名).conda\envs\DataViz\Lib\site-packages\graphviz2drawio\mx\MxGraph.py"
のファイルがエラーになるのですが、本来やろうとしていた処理がよくわからないのでコメントアウトして上書き保存しましょう。
分かる方は、ぜひ修正を手伝ってあげてください。 https://github.com/hbmartin/graphviz2drawio/issues

~\.conda\envs\DataViz\Lib\site-packages\graphviz2drawio\mx\MxGraph.py in add_mx_geo_with_points(element, curve)

    115        for cb in curve.cbset:
    116            ET.SubElement(array, MxConst.POINT, x=str(cb[0][0]), y=str(cb[0][1]))
--> 117        if cb:
    118            ET.SubElement(array, MxConst.POINT, x=str(cb[1][0]), y=str(cb[1][1]))
    119
UnboundLocalError: local variable 'cb' referenced before assignment

修正後

MxGraph.py
        for cb in curve.cbset:
            ET.SubElement(array, MxConst.POINT, x=str(cb[0][0]), y=str(cb[0][1]))
        #エラーになるのでコメントアウト
        #if cb:
        #    ET.SubElement(array, MxConst.POINT, x=str(cb[1][0]), y=str(cb[1][1]))

環境構築の補足

2020年5月30時点で、python3に対応しているpygraphvizは、alubbockのものしかなく、それに合わせてalubbockのgraphvizを入れておく必要があるようです。ただし、このgraphvizを入れても、グラフは描画できませんでした。

graphviz_test.py
from graphviz import Source
path = "edges.dot"
s = Source.from_file(path)
s.view()

結果

---------------------------------------------------------------------------
FileNotFoundError                         Traceback (most recent call last)
//
FileNotFoundError: [WinError 2] 指定されたファイルが見つかりません。
During handling of the above exception, another exception occurred:
ExecutableNotFound                        Traceback (most recent call last)
//
ExecutableNotFound: failed to execute ['dot.bat', '-Tpdf', '-O', 'pdt.dot'], make sure the Graphviz executables are on your systems' PATH

逆に、標準のgraphvizを入れると、graphviz2drawioが使えず(内部で呼び出しているpygraphvizが使えない)

from graphviz2drawio import graphviz2drawio
を実行すると

ImportError: DLL load failed: 指定されたモジュールが見つかりません。

となってしまいます。

Windowsじゃなかったり、環境構築をpipでやったり、Graphvizのソフトをインストールして適切なPATHの設定をすればこんな苦労はないのかもしれません。

使ってみる

プログラム

今回はこのサンプルを使います。edgelist2drawio.py と同じフォルダに保存している前提です。

edges.txt
A B
A C
A D
B C
C D
D E

pythonプログラム

edgelist2drawio.py
import networkx as nx
from graphviz2drawio import graphviz2drawio

#edges.textを有向グラフとして読み込む
G = nx.read_edgelist('edges.txt', create_using=nx.DiGraph(), nodetype=str)

# 読み込んだ情報をdot形式に変換
edges_dot = nx.drawing.nx_pydot.to_pydot(G)
# 必要ならdotファイルとして書き出す
# nx.drawing.nx_pydot.write_dot(G,"edges.dot")


# dot形式のデータをもとにgraphviz的な描画をしてdraw.ioファイルとして書き出す
xml = graphviz2drawio.convert(str(edges_dot))

# dotファイルから読み取って変換するときは
# xml = graphviz2drawio.convert("edges.dot")

# 変換したファイルを保存
with open('edges.drawio', 'w') as f:
  print(xml, file=f)

編集する

プログラムを実行して作られるedges.drawioをVS Code(拡張機能でDraw.io Integration https://github.com/hediet/vscode-drawio.git を入れておく)で開くと、Graphvizで書いたような図が開けます。
あとは思いのままに編集できます!

image.png

おわりに

・graphviz2drawio
 https://pypi.org/project/graphviz2drawio/0.2.0/

・GraphViz
 https://www.graphviz.org/

・VS CodeでDraw.ioが使える喜びはこちらの記事で知りました。
 「VSCodeでDraw.ioが使えるようになったらしい!」
 https://qiita.com/riku-shiru/items/5ab7c5aecdfea323ec4e

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Django】manage.pyで動くプロセス内からさらに自作したコマンドを叩く

記録用に。

やりたかったこと

commandsディレクトリに自作のコマンドを作成し、テスト内から下記コマンドと同様のことを行おうとしました。

$ python manage.py create_initial_data

テストのセットアップ時にシェルでコマンドを流すのかと思いましたが、Djangoはここはしっかりサポートされていました

やり方

対象のスクリプトに、 call_command 関数を埋め込みます。

from django.test import TestCase, Client
from django.core.management import call_command


class TestSendEmailView(TestCase):
    def setUp(self):
        self.client = Client()
        call_command('create_initial_data') # ここ

    def test_success(self):
        response = self.client.get('/emails/')
        self.assertEqual(response.status_code, 200)

これで python manage.py create_initial_data と同じことが行われます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Drive Api Tips(Python)

サービスアカウントの場合の初期化

https://cloud.google.com/iam/docs/creating-managing-service-account-keys?hl=ja
こちらを参考にjsonファイルをを取得しておきます

from google.oauth2 import service_account
from googleapiclient.discovery import build

SCOPES = ['https://www.googleapis.com/auth/drive']

credentials = service_account.Credentials.from_service_account_file(JSON_FILE_PATH, scopes=SCOPE)
self.service = build('drive', 'v3', credentials=credentials)

指定したディレクトリにファイルをアップロードしたい

ファイルIDは配列で渡しましょう(忘れがち)

from apiclient.http import MediaFileUpload

# ディレクトリのファイルIDはパスの一番後ろ
# https://drive.google.com/drive/folders/FILE_ID
file_metadata = {'name': 'photo.jpg', 'parents': [FILE_ID]}
media = MediaFileUpload('files/photo.jpg', mimetype='image/jpeg')
file = self.service.files().create(body=file_metadata,
                                   media_body=media,
                                   fields='id').execute()

アップロードする際のmimetypeって何を指定したらいい?

このあたりを参考
https://developers.google.com/drive/api/v3/mime-types
https://developer.mozilla.org/ja/docs/Web/HTTP/Basics_of_HTTP/MIME_types/Common_types

ファイルのいろんな情報が見たい

*で全情報が返ります
必要な項目をカンマ区切りで渡しましょう

self.service.files().get(fileId=FILE_ID, fields="*").execute()

ファイルアップロードしたのにアクセスできない

多分権限がありません、つけてあげましょう
あらかじめ権限のあるディレクトリを作っておいて、そこにアップロードすればいちいち権限をつける必要はありません

user_permission = {
        'type': 'user',
        'role': 'writer',
        'domain': 'hoge@gmail.com'
    }

self.service.permissions().create(
                    fileId=FILE_ID,
                    body=user_permission,
                    fields='id',
                )

ダウンロードしたファイルを指定したパスに保存する

getbufferで書き込みましょう

import io
from apiclient.http import MediaIoBaseDownload

request = self.service.files().get_media(fileId=FILE_ID)
fh = io.BytesIO()
downloader = MediaIoBaseDownload(fh, request)
done = False
while done is False:
    status, done = downloader.next_chunk()
    print("Download %d%%." % int(status.progress() * 100))

with open(SAVE_FILE_PATH, "wb") as f:
    f.write(fh.getbuffer())
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby と Python で解く AtCoder ABC138 D 隣接リスト

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest D - Ki
Difficulty: 923

今回のテーマ、隣接リスト

以前解きました Ruby と Python と networkx で解く AtCoder ABC168 D 隣接リスト の応用版といった問題です。

今回は、木の根から頂点へカウンターの値が送られますますので、カウンター用の配列を準備し、根から頂点へ探索を行うことによりカウンターの値の受け渡しを行います。

Ruby

ruby.rb
n, q = gets.split.map(&:to_i)
a = Array.new(n){[]}
n.pred.times do
    x, y = gets.split.map(&:to_i)
    a[x - 1] << y - 1
    a[y - 1] << x - 1
end
p = Array.new(n){0}
q.times do |i|
  x, y = gets.split.map(&:to_i)
  p[x - 1] += y
end
c = Array.new(n){0}
c[0] = 1
que = []
que << 0
while que.size > 0
  e = que.shift
  a[e].each do |i|
    next if c[i] > 0
    c[i] = 1
    p[i] += p[e]
    que << i
  end
end
puts p
index.rb
    p[i] += p[e]

ここでカウンターの値の受け渡しを行っています。

Python

python.py
from sys import stdin

def main():
    from collections import deque
    input = stdin.readline
    n, q = map(int, input().split())
    a = [[] for _ in range(n)]
    for i in range(n - 1):
        x, y = map(int, input().split())
        a[x - 1].append(y - 1)
        a[y - 1].append(x - 1)
    p = [0] * n
    for i in range(q):
        x, y = map(int, input().split())
        p[x - 1] += y
    c = [0] * n
    c[0] = 1
    que = deque([])
    que.append(0)
    while len(que) > 0:
        e = que.popleft()
        for i in a[e]:
            if c[i] > 0:
                continue
            c[i] = 1
            p[i] += p[e]
            que.append(i)
    for i in range(n):
        print(p[i])
main()
stdin.py
from sys import stdin
    input = stdin.readline

stdinを使用すると実行時間がかなり速くなります。

Ruby Python Python (stdin) PyPy PyPy (stdin)
コード長 (Byte) 442 681 736 681 736
実行時間 (ms) 802 1734 1044 1959 864
メモリ (KB) 25468 53008 53040 105164 91852

まとめ

  • ABC 138 D を解いた
  • Ruby に詳しくなった
  • Python に詳しくなった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

量子情報理論の基本:量子誤り訂正(スタビライザー符号:4)

$$
\def\bra#1{\mathinner{\left\langle{#1}\right|}}
\def\ket#1{\mathinner{\left|{#1}\right\rangle}}
\def\braket#1#2{\mathinner{\left\langle{#1}\middle|#2\right\rangle}}
$$

はじめに

前回の記事で、スタビライザー符号の理論が理解できたので、今回はスタビライザー符号の具体例を一つを取り上げ、量子計算シミュレータqlazyを使ってその動作を確認してみます。前回の終わりに「Shor符号」「Steane符号」「5量子ビット符号」の生成元を紹介しました。このうち「Shor符号」「Steane符号」はすでに別記事1で動作確認をしていたので、ここでは「5量子ビット符号」を実装して動作確認してみることにします。

準備

まず、4つの生成元および論理Z演算子を改めて掲載します。

生成元 演算子
$g_{1}$ $X \otimes Z \otimes Z \otimes X \otimes I$
$g_{2}$ $I \otimes X \otimes Z \otimes Z \otimes X$
$g_{3}$ $X \otimes I \otimes X \otimes Z \otimes Z$
$g_{4}$ $Z \otimes X \otimes I \otimes X \otimes Z$
$\bar{Z} \equiv g_{5}$ $Z \otimes Z \otimes Z \otimes Z \otimes Z$

表に入れていませんが、論理X演算子は$X \otimes X \otimes X \otimes X \otimes X$です。

これに基づき量子回路を設計したいのですが、それにはいくつかの前準備が必要です。

$\ket{00000}$から論理基底状態$\ket{0_L}$を作成するために、生成元$g_i$に対応した演算子$g_{i}^{\prime}$を$g_{i}$と反可換でその他のすべての$g_j(j \ne i)$と可換であるように選ぶ必要がありました。上の生成元に対する検査行列2を作って、$g_{i}^{\prime}$の検査行列を作成すれば良いと思い、試行錯誤した結果、以下のようなものをひねり出すことができました3

演算子
$g_{1}^{\prime}$ $Z \otimes I \otimes Z \otimes I \otimes I$
$g_{2}^{\prime}$ $Z \otimes Z \otimes Z \otimes Z \otimes I$
$g_{3}^{\prime}$ $Z \otimes Z \otimes I \otimes Z \otimes Z$
$g_{4}^{\prime}$ $Z \otimes I \otimes Z \otimes Z \otimes I$
$\bar{Z}^{\prime} \equiv g_{5}^{\prime}$ $X \otimes X \otimes X \otimes X \otimes X$

雑音集合としては、以下のように各量子ビットごとにパウリ群の演算子$X,Z,XZ$を作用させるすべてのものを用意しておきます。

雑音 演算子
$E_{0}$ $I \otimes I \otimes I \otimes I \otimes I$
$E_{1}$ $X \otimes I \otimes I \otimes I \otimes I$
$E_{2}$ $Z \otimes I \otimes I \otimes I \otimes I$
$E_{3}$ $XZ \otimes I \otimes I \otimes I \otimes I$
$E_{4}$ $I \otimes X \otimes I \otimes I \otimes I$
$E_{5}$ $I \otimes Z \otimes I \otimes I \otimes I$
$E_{6}$ $I \otimes XZ \otimes I \otimes I \otimes I$
$E_{7}$ $I \otimes I \otimes X \otimes I \otimes I$
$E_{8}$ $I \otimes I \otimes Z \otimes I \otimes I$
$E_{9}$ $I \otimes I \otimes XZ \otimes I \otimes I$
$E_{10}$ $I \otimes I \otimes I \otimes X \otimes I$
$E_{11}$ $I \otimes I \otimes I \otimes Z \otimes I$
$E_{12}$ $I \otimes I \otimes I \otimes XZ \otimes I$
$E_{13}$ $I \otimes I \otimes I \otimes I \otimes X$
$E_{14}$ $I \otimes I \otimes I \otimes I \otimes Z$
$E_{15}$ $I \otimes I \otimes I \otimes I \otimes XZ$

雑音$E_i$が加わった状態に対して生成元を測定した場合の測定値リスト$\{ \beta_{l}^{(i)}\}$も必要です。

g_l E_i = \beta_{l}^{(i)} E_i g_l \tag{1}

で計算できます。つまり、$g_l$と$E_i$が可換の場合$\beta_{l}^{(i)}=+1$で反可換の場合$\beta_{l}^{(i)}=-1$です。以下に各雑音に対する測定値リストを示します。2列目では測定値$+1$を$+$、測定値$-1$を$-$として記載しています。また、測定値が$+1$というのは$\ket{0}$、測定値が$-1$というのは$\ket{1}$のことなので、3列目ではその指標($0$または$1$)を示しています。量子回路として実装する際には、測定値は指標として得られるので3列目の$(0,1)$系列を参照した方が良いです。

雑音 $(g_1, g_2, g_3, g_4)$の測定値 測定値の指標
$E_{0}$ $(+,+,+,+)$ $(0,0,0,0)$
$E_{1}$ $(+,+,+,-)$ $(0,0,0,1)$
$E_{2}$ $(-,+,-,+)$ $(1,0,1,0)$
$E_{3}$ $(-,+,-,-)$ $(1,0,1,1)$
$E_{4}$ $(-,+,+,+)$ $(1,0,0,0)$
$E_{5}$ $(+,-,+,-)$ $(0,1,0,1)$
$E_{6}$ $(-,-,+,-)$ $(1,1,0,1)$
$E_{7}$ $(-,-,+,+)$ $(1,1,0,0)$
$E_{8}$ $(+,+,-,+)$ $(0,0,1,0)$
$E_{9}$ $(-,-,-,+)$ $(1,1,1,0)$
$E_{10}$ $(+,-,-,+)$ $(0,1,1,0)$
$E_{11}$ $(-,+,+,-)$ $(1,0,0,1)$
$E_{12}$ $(-,-,-,-)$ $(1,1,1,1)$
$E_{13}$ $(+,+,-,-)$ $(0,0,1,1)$
$E_{14}$ $(+,-,+,+)$ $(0,1,0,0)$
$E_{15}$ $(+,-,-,-)$ $(0,1,1,1)$

前準備はこれで完了です。では、量子回路を以下に示します。

量子回路

まず、論理的な基底状態$\ket{0_L}$を物理的な基底状態$\ket{00000}$から得る回路を示します。

|0> --H---*---H---M
|0> --H---|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------|-------|-------*---H---M
|0> --H---|-------|-------|-------|-------|-------|-------|-------|-------*---H---M
          |       |       |       |       |       |       |       |       |       |
|0> ----|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---
|0> ----|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---
|0> ----|g1 |---|g1'|---|g2 |---|g2'|---|g3 |---|g3'|---|g4 |---|g4'|---|g5 |---|g5'|---  |0L>
|0> ----|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---
|0> ----|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---|   |---

次に、未知な1量子状態$\ket{\psi}$を量子テレポーテーションの手法を使って符号化します。

|psi> ----------*---H-----------M
                |               |
|0>   --H---*---X---------M     |
            |             |     |
      ------X-------------X-----Z---
      ------X-------------X-----Z---
|0L>  ------X-------------X-----Z--- |psi_L>
      ------X-------------X-----Z---
      ------X-------------X-----Z---

符号化状態$\ket{\psi_L}$が得られたので、雑音を付加します。ここで、Eiは何らかの雑音を表します。

        ---|  |---
        ---|  |---
|psi_L> ---|Ei|--- |psi_L'>
        ---|  |---
        ---|  |---

最後に、誤り検知のためのシンドローム測定を行い、回復のため雑音の逆演算を行えば、状態が元に戻ります。

     |0> --H---*--------------------------H-----M
     |0> --H---|-------*------------------H-----M
     |0> --H---|-------|-------*----------H-----M
     |0> --H---|-------|-------|------*---H-----M
               |       |       |      |         |
         ----|   |---|   |---|   |--|   |-----|   |----
         ----|   |---|   |---|   |--|   |-----|   |----
|psi_L'> ----|g1 |---|g2 |---|g3 |--|g4 |-----|E+ |---- |psi>
         ----|   |---|   |---|   |--|   |-----|   |----
         ----|   |---|   |---|   |--|   |-----|   |----

量子回路は以上です。

実装

それではqlazyによる実装例を示します。

import numpy as np
from qlazypy import QState

def logic_x(self, qid):

    [self.x(q) for q in qid]
    return self

def logic_z(self, qid):

    [self.z(q) for q in qid]
    return self

def ctr_logic_x(self, q, qid):

    [self.cx(q, qtar) for qtar in qid]
    return self

def ctr_logic_z(self, q, qid):

    [self.cz(q, qtar) for qtar in qid]
    return self

def ctr_g1(self, q, qid):

    self.cx(q, qid[0]).cz(q, qid[1]).cz(q, qid[2]).cx(q, qid[3])
    return self

def ctr_g2(self, q, qid):

    self.cx(q, qid[1]).cz(q, qid[2]).cz(q, qid[3]).cx(q, qid[4])
    return self

def ctr_g3(self, q, qid):

    self.cx(q, qid[0]).cx(q, qid[2]).cz(q, qid[3]).cz(q, qid[4])
    return self

def ctr_g4(self, q, qid):

    self.cz(q, qid[0]).cx(q, qid[1]).cx(q, qid[3]).cz(q, qid[4])
    return self

def encode(self, phase, qid_anc, qid_cod):

    # make logical zero state: |0>_L

    # g1
    self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
    self.m(qid=[qid_anc[0]])
    mval = self.m_value(binary=True)
    if mval == '1': self.z(qid_cod[0]).z(qid_cod[2])
    self.reset(qid=[qid_anc[0]])

    # g2
    self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
    self.m(qid=[qid_anc[1]])
    mval = self.m_value(binary=True)
    if mval == '1': self.z(qid_cod[0]).z(qid_cod[1]).z(qid_cod[2]).z(qid_cod[3])
    self.reset(qid=[qid_anc[1]])

    # g3
    self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
    self.m(qid=[qid_anc[2]])
    mval = self.m_value(binary=True)
    if mval == '1': self.z(qid_cod[0]).z(qid_cod[1]).z(qid_cod[3]).z(qid_cod[4])
    self.reset(qid=[qid_anc[2]])

    # g4
    self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
    self.m(qid=[qid_anc[3]])
    mval = self.m_value(binary=True)
    if mval == '1': self.z(qid_cod[0]).z(qid_cod[2]).z(qid_cod[3])
    self.reset(qid=[qid_anc[3]])

    # logical z
    self.h(qid_anc[4]).ctr_logic_z(qid_anc[4], qid_cod).h(qid_anc[4])
    self.m(qid=[qid_anc[4]])
    mval = self.m_value(binary=True)
    if mval == '1':
        self.x(qid_cod[0]).x(qid_cod[1]).x(qid_cod[2]).x(qid_cod[3]).x(qid_cod[4])
    self.reset(qid=[qid_anc[4]])

    # make random quantum state and encode (with quantum teleportation)

    self.reset(qid=qid_anc)
    self.u3(qid_anc[0], alpha=phase[0], beta=phase[1], gamma=phase[2]) # input quantum state
    print("* input quantum state")
    self.show(qid=[qid_anc[0]])

    self.h(qid_anc[1])
    self.ctr_logic_x(qid_anc[1], qid_cod).cx(qid_anc[0], qid_anc[1])
    self.h(qid_anc[0])
    self.m(qid=qid_anc[0:2])
    mval = self.m_value(binary=True)
    if mval == '00': pass
    elif mval == '01': self.logic_x(qid_cod)
    elif mval == '10': self.logic_z(qid_cod)
    elif mval == '11': self.logic_x(qid_cod).logic_z(qid_cod)
    self.reset(qid=qid_anc)

    return self

def add_noise(self, q, qid, kind):

    if kind == 'X': self.x(qid[q])
    elif kind == 'Z': self.z(qid[q])
    elif kind == 'XZ': self.z(qid[q]).x(qid[q])
    return self

def correct_err(self, qid_anc, qid_cod):

    self.reset(qid=qid_anc)

    # syndrome
    self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
    self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
    self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
    self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
    self.m(qid=qid_anc[0:4])
    mval = self.m_value(binary=True)
    print("* syndrome =", mval)

    # recovery
    if mval == '0000': pass
    elif mval == '0001': self.x(qid_cod[0])
    elif mval == '1010': self.z(qid_cod[0])
    elif mval == '1011': self.z(qid_cod[0]).x(qid_cod[0])
    elif mval == '1000': self.x(qid_cod[1])
    elif mval == '0101': self.z(qid_cod[1])
    elif mval == '1101': self.z(qid_cod[1]).x(qid_cod[1])
    elif mval == '1100': self.x(qid_cod[2])
    elif mval == '0010': self.z(qid_cod[2])
    elif mval == '1110': self.z(qid_cod[2]).x(qid_cod[2])
    elif mval == '0110': self.x(qid_cod[3])
    elif mval == '1001': self.z(qid_cod[3])
    elif mval == '1111': self.z(qid_cod[3]).x(qid_cod[3])
    elif mval == '0011': self.x(qid_cod[4])
    elif mval == '0100': self.z(qid_cod[4])
    elif mval == '0111': self.z(qid_cod[4]).x(qid_cod[4])

    return self

if __name__ == '__main__':

    QState.add_methods(logic_x, logic_z, ctr_logic_x, ctr_logic_z,
                       ctr_g1, ctr_g2, ctr_g3, ctr_g4,
                       encode, add_noise, correct_err)

    # create registers
    qid_anc = QState.create_register(5)
    qid_cod = QState.create_register(5)
    qnum = QState.init_register(qid_anc, qid_cod)

    # parameters for input quantum state (U3 gate params)
    phase = [np.random.rand(), np.random.rand(), np.random.rand()]

    # encode quantum state
    qs_ini = QState(qnum)
    qs_ini.encode(phase, qid_anc, qid_cod)
    qs_fin = qs_ini.clone()

    # noise
    q = np.random.randint(0, len(qid_cod))
    kind = np.random.choice(['X','Z','XZ'])
    print("* noise '{:}' to #{:} qubit".format(kind, q))
    qs_fin.add_noise(q, qid_cod, kind)

    # error correction
    qs_fin.correct_err(qid_anc, qid_cod)

    # result
    fid = qs_ini.fidelity(qs_fin, qid=qid_cod)
    print("* fidelity = {:.6f}".format(fid))

    QState.free_all(qs_ini, qs_fin)

何をやっているか順に説明します。main処理部を見てください。

QState.add_methods(logic_x, logic_z, ctr_logic_x, ctr_logic_z,
                   ctr_g1, ctr_g2, ctr_g3, ctr_g4,
                   encode, add_noise, correct_err)

で、上で示したカスタム・メソッドを量子状態を表すQStateクラスのメソッドとして登録します。

# create registers
qid_anc = QState.create_register(5)
qid_cod = QState.create_register(5)
qnum = QState.init_register(qid_anc, qid_cod)

で、これから使用するレジスタを生成します。これにより、qid_anc=[0,1,2,3,4], qid_cod=[5,6,7,8,9], qnum=10という具合に変数値が設定されます4。qid_ancは5個の補助量子ビットです。上で示した量子回路において、論理基底状態を作成するために5個、量子テレポーテーションでの符号状態作成に2個、誤り訂正のために4個の補助量子ビットが必要です。各ステップの直前で状態をリセットできるので5個の補助量子ビットがあれば十分です。qid_codは符号状態を記述するための量子ビットです。5量子ビット符号なので5量子ビット用意しておけば良いです。

# parameters for input quantum state (U3 gate params)
phase = [np.random.rand(), np.random.rand(), np.random.rand()]

で、符号状態を適当に作成するため3個のランダムパラメータphaseを生成しています(U3ゲート演算するための3つのパラメータです)。

# encode quantum state
qs_ini = QState(qnum)
qs_ini.encode(phase, qid_anc, qid_cod)
qs_fin = qs_ini.clone()

で、まず補助量子ビットも含めた初期状態を作成して、それに対してencodeメソッドで符号状態に変換します。qs_iniをqs_finにコピーしていますが、これは最終的に元の状態と誤り訂正後の状態が一致しているかどうか評価するためのものです。

では、encodeメソッドの中身を見てみます。

# g1
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.m(qid=[qid_anc[0]])
mval = self.m_value(binary=True)
if mval == '1': self.z(qid_cod[0]).z(qid_cod[2])
self.reset(qid=[qid_anc[0]])

 ...

で、論理基底状態$\ket{0_L}$を作成します。上の量子回路で示した通りの演算を順に実行します。間接測定の測定値(の指標)はmval変数に入ります。これが"0"だった場合何もせず、"1"だった場合$g_{1}^{\prime} = ZIZII$を演算します。終わったら使用した0番目の補助量子ビットをリセットします。以下、同様のことを他のすべての生成元と論理Z演算子に対して実行します。

self.u3(qid_anc[0], alpha=phase[0], beta=phase[1], gamma=phase[2]) # input quantum state
print("* input quantum state")
self.show(qid=[qid_anc[0]])

で、0番目の補助量子ビットにphaseを引数としたU3ゲートを演算することでランダムな1量子ビット状態を作ります。そして、その状態を表示します。

self.h(qid_anc[1])
self.ctr_logic_x(qid_anc[1], qid_cod).cx(qid_anc[0], qid_anc[1])
self.h(qid_anc[0])
self.m(qid=qid_anc[0:2])
mval = self.m_value(binary=True)
if mval == '00': pass
elif mval == '01': self.logic_x(qid_cod)
elif mval == '10': self.logic_z(qid_cod)
elif mval == '11': self.logic_x(qid_cod).logic_z(qid_cod)
self.reset(qid=qid_anc)

で、量子テレポーテーションの回路を実行します。mvalには2つの測定を実行した結果が入り、その4パターンに応じて、何もしない/論理X/論理Z/論理XZ演算子を実行すれば、先程ランダムに作成した1量子ビット状態から5量子ビットの符号状態を得ることができます。

main処理部に戻ります。

# noise
q = np.random.randint(0, len(qid_cod))
kind = np.random.choice(['X','Z','XZ'])
print("* noise '{:}' to #{:} qubit".format(kind, q))
qs_fin.add_noise(q, qid_cod, kind)

で、5量子ビットの符号状態に対してランダムにノイズを加えます。量子ビットをランダムに選び、さらに雑音のタイプをX/Z/XZの中からランダムに選びます。実際の処理はadd_noiseメソッドで行います。詳細は関数定義を見てください。

# error correction
qs_fin.correct_err(qid_anc, qid_cod)

で、誤り訂正を実行します。

correct_errメソッドの中身を見てみます。

self.reset(qid=qid_anc)

で、まず補助量子ビットをすべてリセットします。

# syndrome
self.h(qid_anc[0]).ctr_g1(qid_anc[0], qid_cod).h(qid_anc[0])
self.h(qid_anc[1]).ctr_g2(qid_anc[1], qid_cod).h(qid_anc[1])
self.h(qid_anc[2]).ctr_g3(qid_anc[2], qid_cod).h(qid_anc[2])
self.h(qid_anc[3]).ctr_g4(qid_anc[3], qid_cod).h(qid_anc[3])
self.m(qid=qid_anc[0:4])
mval = self.m_value(binary=True)
print("* syndrome =", mval)

で、誤り検知の測定(シンドローム測定)を行います。その測定値(4桁のバイナリ文字列)が変数mvalに入ります。このバイナリ文字列に応じて、

# recovery
if mval == '0000': pass
elif mval == '0001': self.x(qid_cod[0])
elif mval == '1010': self.z(qid_cod[0])
elif mval == '1011': self.z(qid_cod[0]).x(qid_cod[0])
elif mval == '1000': self.x(qid_cod[1])
elif mval == '0101': self.z(qid_cod[1])
elif mval == '1101': self.z(qid_cod[1]).x(qid_cod[1])
elif mval == '1100': self.x(qid_cod[2])
elif mval == '0010': self.z(qid_cod[2])
elif mval == '1110': self.z(qid_cod[2]).x(qid_cod[2])
elif mval == '0110': self.x(qid_cod[3])
elif mval == '1001': self.z(qid_cod[3])
elif mval == '1111': self.z(qid_cod[3]).x(qid_cod[3])
elif mval == '0011': self.x(qid_cod[4])
elif mval == '0100': self.z(qid_cod[4])
elif mval == '0111': self.z(qid_cod[4]).x(qid_cod[4])

で、雑音の逆演算を行います。これで符号状態は元に戻るはずです。

main処理部に戻ります。

# result
fid = qs_ini.fidelity(qs_fin, qid=qid_cod)
print("* fidelity = {:.6f}".format(fid))

で、元の量子状態と誤り訂正後の量子状態との違いを見るためのフィデリティを計算して表示します5。誤り訂正が成功していればfidは1.0になるはずです。

動作確認

では、上のプログラムを実行してみます。

* input quantum state
c[0] = +0.3382-0.0000*i : 0.1143 |++
c[1] = -0.6657+0.6652*i : 0.8857 |++++++++++
* noise 'Z' to #4 qubit
* syndrome = 0100
* fidelity = 1.000000

適当に設定された1量子状態が符号化され、4番目の量子ビットに雑音'Z'が加わったのですが、シンドローム測定により誤りのパターンが'0100'($E_{14}=IIIIZ$)であることがわかり、逆演算を施した結果、フィデリティが1.000000となりました。というわけで、誤り訂正は成功しました。

もう一回実行すると、

* input quantum state
c[0] = +0.7701-0.0000*i : 0.5931 |+++++++
c[1] = -0.1945+0.6075*i : 0.4069 |+++++
* noise 'X' to #1 qubit
* syndrome = 1000
* fidelity = 1.000000

となり、先程とは別の状態に対して別のパターンの雑音が付加されましたが、この場合も誤り訂正は成功です。何度も実行してみましたが、失敗することはありませんでした。というわけで、めでたし、めでたし、です。

おわりに

これで、4回にわたって続けてきた「スタビライザー符号」シリーズは一応完了です。スタビライザー符号の標準形とか論理基底状態をユニタリ演算だけで作成する方法など、興味深い積み残し課題はあるのですが、先に進みたいと思います。次回は、「フォールトトレラントな量子計算」を予定しています。

以上


  1. これこれです。 

  2. 以前の記事を参照。 

  3. もしかしたら汎用的でスマートな手続きがあるのかもしれませんが、できたのでよしとしました。 

  4. もちろん、こんなクラス・メソッドを使わないで直接変数値をセットしても良いです。複雑な量子回路を相手にする場合、こうした方が楽に実装できると思います。いまの例はそれほど複雑ではないのでどっちでもいいですが。 

  5. fidelityメソッドの引数qidを指定すると、特定の量子ビット番号リストで表される部分系についてのフィデリティを計算します。 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoでline botで何かを返すまで!

Djangoでline botを作る!

まずはline developersのアカウントを取得するためlineのアカウントでログイン
image.png

左上のconsole homeから新規provider をcreateするline.png

そのしたのproviderからcreate new channelを選択し、channel typeはmessaging API

その後はそれぞれの設定をする。ここまでするとqrコードで自分の作るline botが登録できるようになる。
IMG_0891.jpg

今回はこのミーティング予約と押したら、ユーザーが予約と自動で送信するようにしてそれに対して何かを返したいと思います

まずはここにログインして先ほど作ったアカウントを選択
https://www.linebiz.com/jp/login/
左のメニューからリッチメニューを選択右上の作成ボタンをクリック
image.png
表示設定はタイトル以外はデフォルトでOK
image.png

その下のコンテンツ設定はテンプレートを選択し画像を貼るか、自分で文字入りの画像を作る。
作れたらアクションのところでタイプをテキストにして予約にする。
保存して実際に作ったメニューを自分のスマホでタップすると予約というメッセージが送られると思います。

Djangoを使ってを使ってメッセージを返す

ここまで行ったら次はDjangoで送られてきた予約メッセージに対して何かを返したいと思います

今回はpythonのバージョン・パッケージ管理システムであるanacondaを使ってdjangoをinstallします。
https://www.anaconda.com/products/individual
入れてない人は↑の一番したからダウンロードしてください
image.png
anacondaが入れられたらanaconda navigatorを立ち上げて
Environmentsを選択
左下のcreateを選択
名前をdjango37などにしてpythonを選択しversionは3.7を選択
右下のcreateを選択し環境構築が完了
この状態ではまだdjangoは入ってないのでターミナルを開いて

$ conda activate django37

で環境を立ち上げる
その後

$ conda install django37

でDjangoをinstallこれで環境ができました。

そしたらまずは

$ django-admin startproject reception_line_bot

でプロジェクトを作成します。

reception_line_bot/
    manage.py
    reception_line_bot/
        __init__.py
        settings.py
        urls.py
        asgi.py
        wsgi.py

こんな感じに作れたと思います。
次にアプリケーションを作ります。

$cd reception_line_bot

でreception_line_botの中に入ってから,

$ python manage.py startapp bot 

とすると

bot/
    __init__.py
    admin.py
    apps.py
    migrations/
        __init__.py
    models.py
    tests.py
    views.py

となったと思います。
次にエディタでreception_line_bot/urls.pyを編集していきます。

urls.py
"""reception_line_bot URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path
import bot.views
urlpatterns = [
    path('admin/', admin.site.urls),

]

こうなっていると思いますが、urlpatternsのところにdjangoが受け取ったline botのリクエストをどのファイルのどの関数に送るかを指定します。

なので今回は

urls.py
import bot.views
urlpatterns = [
#path('admin/', admin.site.urls),
 path('', bot.views.reception),
]

とします。receptionはこれからviews.pyに書く関数です。

views.pyにコードを書く前にline botを作成した時に発行されたチャンネルシークレットとアクセストークンはコードの中に直接書くとまずいので、jsonを通してviews.pyで受け取れるようにしたいと思います。なので一番上のline_reception_botの下にsetting.jsonを作り、
image.png
providersからチャネルを選択しmessaging APIの一番したでaccess tokenを発行し、コピペします。
次に
image.png
messaging APIの隣のBasic settingsのところからChannel secretを取得します。
その二つを先ほど作ったsetting.jsonにコピペしていきます

setting.json
{

"LINE":{

"channel_secret":"fakfkajpdpaida132941",

"access_token":"a3nm7yOY7IoUt8blZ6QqK6RShadfsajdjfadfljfafdsdjsdfailfajjpqjpoejpqjpfjpqejiepqwifqpjdjidcS9yEBGieq+VMQs0EL+mQDtwdB04daft89/aad1O/w1cDnyilFU="

 }

}

こんな感じ、(channel_secretとaccess_tokenは適当に変えました)
そしたらviews.pyを開いてユーザーが送ってきたメッセージに対して何か送るコードを書いていきます。

views.py
from django.views.generic import TemplateView
from django.shortcuts import render
from django.http.response import JsonResponse
from django.views.decorators.csrf import csrf_exempt
import json
import urllib.request
from reception_line_bot.settings import BASE_DIR

# Create your views here.



@csrf_exempt

def reception(request):
    json_open = open('setting.json', 'r')
    json_load = json.load(json_open)
    channel_secret = json_load['LINE']['channel_secret']
    access = json_load['LINE']['access_token']
    request_body = request.body
    data = confirm_json_loads(request_body)
    request_body = request.body
    body = request.body.decode('utf-8')
    print(body)
    reply_token = data["events"][0]["replyToken"]
    reply_message(reply_token,access)
    return JsonResponse({"message":"OK"})

def confirm_json_loads(body:str)->dict:
    """
    json_loadsが動いているのかの確認

    Args:
        body str:
            requestのjson文字列

    Return:
        dict:
            Seccess:json.loads(body)の返り値
            Error:ログイン失敗のメッセージ


    """
    try:
        return json.loads(body)
    except Exception as e:
        print("error")
        message = JsonResponse({'message': 'Login failure.'}, status=403)
        return message

def reply_message(token:str,access)->None:
    return False
    """
    ユーザーに返答メッセージを送ります

    Args:
        token str:
            lineトークン

        message str:
            select_message関数で返されたメッセージ

        user_id str:
            メッセージ送信者のlineユーザid

    """

    url = 'https://api.line.me/v2/bot/message/reply'
    print(url)
    data = {
        'replyToken': token,
        'messages':[
            {
                'type': 'text',
                'text': "hello world"
            },
        ]
    }
    print(f"data{data}")
    headers = {
        'Content-Type': 'application/json',
        'Authorization': 'Bearer'+" "+access
    }
    print(f"headers:{headers}")
    req = urllib.request.Request(url, json.dumps(data).encode(), headers)
    print(req)
    with urllib.request.urlopen(req) as res:
        body = res.read()

これで完成です。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[競プロ用]最小全域木まとめ

最小全域木を求める手法

  • プリム法
  • クラスカル法

使い分け

議論になっていた
https://togetter.com/li/776049

入力形式で使い分ける?

  • ノード間のコストが行列で渡される → プリム法
  • 2点のノードとそのコストの組み合わせで渡される。 → クラスカル法

https://www.hamayanhamayan.com/entry/2018/09/17/163004

プリム法

  1. 任意のスタート地点から始める。
  2. 移動可能なノードのうち最も低コストで移動できるものを選び繋ぐ。
  3. 2を繰り返し、全てのノードを繋いだ時点で最小全域木となる。

どうすればいいか

ノードを繋ぐ度に選択肢が広がっていき、常にその中で最小のものを選択したい。
→ 優先度付きキュー(heapq)を用いる。

heapqにタプルを渡すと一番目の要素で最小値を判断されることを利用する。

何がいいのか

O(ElogV)。
フィボナッチヒープを使うとより早い?

イメージ

https://algo-logic.info/prim-mst/

使用例

AOJ ALDS1_12_A

プリム法
import heapq

def main():
    n = int(input())
    next = []   # 隣接管理のリスト
    for _ in range(n):
        l = list(map(int, input().split()))
        next.append([(c, i) for i, c in enumerate(l) if c != -1])
    visited = [0] * n
    connection = 0
    q = [(0, 0)]    # (cost, v)
    heapq.heapify(q)
    ans = 0
    while len(q):
        cost, v = heapq.heappop(q)
        if visited[v]:
            continue

        # そのノードと繋げる
        visited[v] = 1
        connection += 1
        ans += cost

        # 新たに繋げたノードから行けるところをエンキュー
        for i in next[v]:
            heapq.heappush(q, i)

        # 全部のノードが繋がったら終了
        if connection == n:
            break
    print(ans)
    return

クラスカル法

  1. 全ての辺を重みの小さいものから順に選びとっていく。
  2. その過程で閉路を作ってしまうものは捨てる。全ての辺に対してこれを行う。
  3. 最終的に選び取った辺のみで最小全域木となる。

どうすればいいか

1 → 辺のコストを元にソートする。リストの各要素をタプルにしてソートすると1番目の要素でソートされるテクニックを利用。
2 → 「閉路ができる」=「既に2点が繋がっているところに辺を作る」なのでUnionFind木で確認する。

何がいいのか

O(ElogV)。

イメージ

https://algo-logic.info/kruskal-mst/

使用例

AOJ GRL_2_A

クラスカル法
def main():
    V, E = map(int, input().split())
    uf = UnionFind(V)

    # 1の過程
    edges = []
    for _ in range(E):
        s, t, w = map(int, input().split())
        edges.append((w, s, t))
    edges.sort()

    # 2の過程
    cost = 0
    for edge in edges:
        w, s, t = edge
        if not uf.same(s, t):
            cost += w
            uf.union(s, t)
    print(cost)
    return

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YOLOv4環境構築①

参考
https://youtu.be/5pYh1rFnNZs

システム

Windows10
GeForce GTX 960

pythonのインストール

python 3.7.7をインストール
Add Python 3.7 to PATHにチェックを入れる

numpyのインストール

コマンドプロンプトから

> pip install numpy

ついでにpipの更新

> python -m pip install --upgrade pip

Visual Studio Codeのインストール

Visual Studio CodeをGitのデフォルトエディタにしたいので先にインストールする。バージョンは1.45.1
他に使ってるエディタがある人は不要

Gitのインストール

Git 2.26.2をインストール
デフォルトのエディタを選択する画面があるので、VSCodeを選択する(デフォルトはVim)
あとはそのまま進めてOK

CMakeのインストール

CMake 3.17.2をインストール

Visual Studioのインストール

Visual Studio 16.5 コミュニティをインストール

pythonデスクトップ開発にチェックを入れてインストール
vs_install.jpg

再起動を求められるのでPCを再起動する

GPUドライバの更新

NVIDIA公式から自分の環境にドライバをダウンロードしてインストール
※他ブランドのグラフィックボードは対応していない
GTX960 445.87に更新
終わったらPCを再起動

CUDAのインストール

nvidia cuda toolkitを検索してダウンロード
バージョン10.2
cuda_dl.jpg

インストール先をCドライブ直下に変更
cuda_dir.jpg

あとはそのまま進める

NVIDIA cuDNNのダウンロード

https://developer.nvidia.com/cudnn
cudnn.jpg

アカウントを新規作成してcuDNN v7.6.5をダウンロード
cudnn_dl.jpg

ダウンロードしたzipファイルをCドライブ直下へ移動して解凍するとcudaフォルダが作成される

解凍したcuDNN関連ファイルをCUDA配下に置く

C:\cuda\binにあるcudnn64_7.dll をコピーして
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\binにペーストする

同様に
C:\cuda\includeにあるcudnn.h をコピーして
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\includeにペーストする

C:\cuda\lib\x64にあるcudnn.lib をコピーして
C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v10.2\lib\x64にペーストする

OpenCVのインストール

公式のReleaseページからv4.1.0をダウンロード

opencv.jpg

opencv_contribのダウンロード

GitHubからダウンロードする
https://github.com/opencv/opencv_contrib

opencv_contrib.jpg

解凍・配置

ダウンロードした2つのzipファイルを解凍する。
Cドライブ直下にopencvディレクトリを作成し解凍した2つのフォルダをコピーする。
同ディレクトリにbuildフォルダを作成する。
opencv_build.jpg

CMakeの設定とビルド

CMakeを起動し、ソースコードとビルド先をそれぞれC:/opencv/opencv-4.1.0C:/opencv/buildに設定する
cmake.jpg

Configureポタンを押してgeneratorとplatformをVisual Studio 16 2019x64に設定
cmakeconfig.jpg

リストのBUILD_opencv_worldにチェックを入れる

build_cvworld.jpg

エラーが無いことを確認しGenerateを押す

OpenCVをVisual Studioでビルド

buildフォルダC:\opencv\buildALL_BUILD.vcxprojを開いてVisual Studioを起動する

Releaseモードに変更し、ALL_BUILDINSTALLをそれぞれ右クリックメニューからビルドする
vsbuild.jpg

コマンドプロンプトで動作確認

コマンドプロンプトでpythonインタプリタを起動し、openCVが動作することを確認する

> python
>>> import cv2
>>> cv2.__version__
'4.1.0'

次回に続く

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyInstallerでコンソール画面非表示にするとエラーになってしまった時の解決策

環境

Windows 10
Python 3.6.5
PyInstaller 3.6
Visual Studio Code

症状

pyinstaller --onefile -n appname index.py
上記実行後、distフォルダに生成されたexeを実行すると問題なく動作したのですが、
コンソール画面が表示されてしまうのが邪魔だったので、specファイルの
console=Trueconsole=Falseに変更して
pyinstaller specfilename.spec
を実行し、再生成されたexeファイルを実行すると、

failed to execute script index

というポップアップが表示されて処理が止まってしまいます。
試しに、簡単なスクリプトを書いてconsole=Falseでexe生成してみると再現しないので、
何が原因なのか解らず悩んでおりました。

解決策

今回のexe化したかったプログラム内では、当初、下記のようにインポートして関数を呼び出していました。

index.py
import funcs

funcs.func1()
funcs.func2()
funcs.py
import anotherfunc

def func1():
    anotherfunc.func3()
otherfunc.py
def func3():

それを下記のように、別になっていた関数を一つにまとめることで、
インポート先のファイルから他の関数をインポートして使用することをやめたら、
コンソール非表示でも実行できるようになりました。

index.py
import funcs

funcs.func1()
funcs.func2()
funcs.py
def func1():
    func3()

def func3():
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyCharmでは未importのクラスや関数の入力補完ができない、と思い込んでいた話

TL;DR

PyCharmではCtrlを押したままSpaceを2回叩くと幸せになれる

PyCharmが大好き、でも

Pythonは2.7あたりの頃から使っていて、作業効率化ツール開発やpyserialを使った組み込み機器との通信テストなどで使用していました。当初からわりと規模が大きめなプロジェクトで使っていたので、対話シェルやコマンドラインベースではなく、PyCharmのお世話になりながらPythonと付き合ってきました。

私はAndroidアプリ開発もやってますが、Android Studioができる前からEclipse+ADTを窓から投げ捨てIntelliJ IDEAを採用していたので、JetBrains IDEにかなり親しみを持っており、その操作感覚を引き継げるPyCharmでの開発は本当に本当に大好きでした。

ただ、ある一点を除いては・・・

他のJetBrains IDEと同じような感覚で入力補完できない・・・?

IntelliJ(Android Studio)やRiderは、プロジェクトの依存関係を読み取って、そのプロジェクトで使用できる全てのシンボルを把握してくれます。
そのため、ソースコードの先頭にimport(Java, Kotlin)やらusing(C#)やらを書かずとも、プログラマがその場で入力した文字からクラス名・関数名などの入力補完のリストを構築してくれます。

これは未importなシンボルに対しても候補を作ってくれるというのが強いところで、プログラマはなんとな〜くクラスやグローバル関数の存在だけを覚えておき、てきと〜に入力して候補から見つけ出すというのを繰り返すだけで立派なコードが書けることになります。この手の機能はJetBrains IDEに限らず他のモダンなエディタにも搭載されていて、この感覚でコーディングするのが現代のプログラミングのやり方だと思います。

この機能は新しいライブラリを投入してそれについて勉強中のときにも力を発揮します。例えば、ライブラリ内のクラスが継承関係に基づいて親の名前の一部を引き継ぐ命名規則になっている場合、親の名前を入力したときに子のクラス名も一緒に候補に出てきてくれるので、「あ〜こういうクラスツリーがあるのか〜」といった具合で全体像を楽に把握することもできます。

PyCharmでは先にimport文を書いてないと入力候補に出してくれない?

で、この未importに対する入力補完機能、なぜかPyCharmには搭載されてない・・・ ように見えました。

例えばpipでpyserialを入れてきたあと、COMポートの一覧を出すためのlist_portsを使おうとしてlisと入力しても、候補リストは現れてくれません。
スクリーンショット 2020-05-30 13.09.23.png
なんと実行環境を整えてたとしても、コード側では事前にいちいちfrom serial.tools import list_portsを書いておかないとlisの入力時点で出てきてくれないのです。
スクリーンショット 2020-05-30 13.10.03.png

こんなのは嫌だ。面倒臭すぎる。PyCharmは開発マシンのPython実行環境自体を認識してますから、lisの入力時点でlist_portsを提案できるはずです。

ただ、Auto Importなる機能はあります。これはコード内に解決できないシンボルがあり、かつ同名のシンボルがPyCharmによって把握されている場合において、PyCharmがimport文の挿入を提案してくれるものです(まあこれも大抵のIDEには搭載されてますよね)。
この機能を使えば、list_portsと正確に入力すれば、ようやくimport提案をしてくれるようになります。
スクリーンショット 2020-05-30 13.10.42.png
ちょっと私が求めてるものとは違いますが、まあ仕方なしとして、この機能を活用してPyCharmでの大規模開発を進めてきました。が、やはりこれを軸にするのは少々無理があり、少し名前を打ち間違うとimport提案してくれませんので、長い名前のクラスを使うときにはストレスでした。

調べてみたらできるらしい、けど

自分が最強の開発環境だと確信しているJetBrains IDEシリーズのPyCharmを使っているのに、こんな謎の苦しい思いをするなんて、絶対何かが間違っている・・・そういう引っ掛かりが昨日までありました。

友人との会話中、久しぶりにこの疑問が再燃し、数年ぶりにこの話題を調べてみました。
すると以下の公式ブログに辿り着きました(2018年の記事って・・・気付くのが遅くてショック)

Let PyCharm Do Your Import Janitorial Work | PyCharm Blog
https://blog.jetbrains.com/pycharm/2018/11/let-pycharm-do-your-import-janitorial-work/

CtrlSpaceSpace-02.gif

Start typing part of requests and type Ctrl-Space-Space. PyCharm will offer a completion. When you accept it, PyCharm will complete the symbol and also generate the import.

リクエストの一部を入力して、Ctrl-Space-Spaceと入力します。PyCharm が補完を提示してきます。それを受け入れると、PyCharm はシンボルを完成させ、インポートを生成します。

!?!?!?!?!?!?!?!? まさかのショートカットキー!?!? しかも2回!?!?

なぜかPyCharmは他のJetBrains IDEのノリと異なり、未importモジュールに対する入力補完は自動的には表示されず、わざわざショートカットキーを2回も押さなければならないようです。

ナンデ!?

試してみたらOSのショートカットキーと衝突してた

まあ何か大人の事情があったんだろうということで、残念感は置いといて、記事の通りCtrl+Space+Space(つまりCtrlを押したままSpaceを2回叩く)を実行してみると・・・
スクリーンショット 2020-05-30 12.10.43.png
入力ソース切り替えと被ってるやんけ・・・・
設定をいじってオフにしましょう・・・
スクリーンショット 2020-05-30 13.17.08.png
これでPyCharmでも他環境と同じように、ショートカットキーさえ押していれば、未importを含む全てのシンボルに対する入力補完が効くようになりました!最高!
スクリーンショット 2020-05-30 13.12.01.png
これからはコードを書き出す前に毎回Ctrl+Space+Spaceを2回叩くのを習慣化することで、常に候補リストとともに生活し、短時間で大量のPythonコードを書き出せるようにしたいと思います。


しかし、なぜPyCharmだけショートカットキー必須になってるのでしょう・・・?
Pythonはアクセスコントロールがゆるゆるですから、候補リストの生成に時間かかるようになり、そこからの性能悪化を恐れてこうしたんですかね?でも最近の開発マシンなら大したことないんじゃないかなあ。
または、アンダースコアから始まる外に見せたくないものが積極的に見えるようになるのを嫌がったとかですかね?

ところでVSCodeでのPython環境を実は整えてないんですが、同様の機能が使えるのであればそろそろ整備したいです。PyCharmは素晴らしいものですが、ちょっとした実験コードを書くためだけに動かすのは大袈裟ですからね。


※この件に関してのTwitterでの愚痴:

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【要素分解入門】時系列解析の手法をRとpythonで並べてみる♬

今回は、前回の時系列分解をさらに追及してみた。
すなわち、モデルは以下のとおり
$Y_t=T_t + S_t + e_t$
または、
$Y_t=T_tS_te_t$
として、時系列データを分解するdecompose()手法である。
前回は、それぞれのデータを同時描画していたが、今回は例えばノイズ除去した純粋な音声や振動などの再生や利用を目的に、それぞれ保存したり再生したいという動機から実施してみた。
データは参考③のサイトの死亡数の速報値と参考④の2018年までの過去データである。
今回の参考は以下のとおりである。
参考①はR言語のものであり、参考②はpythonのものである。
ここでは独立に数値化してcsvファイルに保存するまでをまとめる。
【参考】
Extracting Seasonality and Trend from Data: Decomposition Using R
TIME SERIES DECOMPOSITION & PREDICTION IN PYTHON
統計で見る日本 e-Statは、日本の統計が閲覧できる政府統計ポータルサイトです
データセット一覧(5-4死亡月別にみた年次別死亡数及び死亡率(人口千対))@統計で見る日本
なお、今回のコードとデータは以下に置きました。
MuAuan/R_Audio

やったこと

・Rでのdecompose
・pythonでのdecompose

・Rでのdecompose

前回のほぼまんまですが、decompose_IIP$seasonalという表記でseasonalのデータを指定できます。

data <- read.csv("./industrial/sibou_p.csv",encoding = "utf-8")
IIP <- ts(data,start=c(2008),frequency=12)
w_list <- c("seasonal","random","trend","observed")
decompose_IIP <- decompose(IIP)
for (i in w_list){
  if (i=="seasonal"){
    plot(decompose_IIP$seasonal)
  }else if(i=="random"){
    plot(decompose_IIP$random)
  }else if(i=="trend"){
    plot(decompose_IIP$trend)
  }else if(i=="observed"){
    plot(IIP)
  }
}
seasonal random
IIP_compose_sibou_seasonal.png IIP_compose_sibou_random.png
trend observed
IIP_compose_sibou_trend.png IIP_compose_sibou_observed.png

もう一つのコード
まず、オリジナルデータを描画します。
次に、今回のデータは12か月周期なのでトレンド曲線として12移動平均を取ります。
そして、上記のオリジナルデータに重畳してlinesを引きます。

#install.packages("forecast")
library(forecast)
plot(as.ts(IIP))

trend_beer = ma(IIP, order = 12, centre = T) #4
lines(trend_beer)

plot_line_original.png

plotはそのトレンド曲線だけを描画しています。

plot(as.ts(trend_beer))

trend.png
オリジナルデータからトレンドデータを引き算してプロットします。

detrend_beer = IIP - trend_beer
plot(as.ts(detrend_beer))

detrend.png
そして、トレンドを差し引いたノイズが乗っているデータを12個ずつのデータに区分して、それらを平均したものを季節変動として計算します。
それを13個連ねて描画しています。
【参考】
Row and Column Summaries
matrix

m_beer = t(matrix(data = detrend_beer, nrow = 12))
seasonal_beer = colMeans(m_beer, na.rm = T)
plot(as.ts(rep(seasonal_beer,13)))

seasonal_bear13.png
最後に、ランダムな変動分を求めます。
結果はdecompose()関数で求めたものと一致しました。
こんな計算で求めているんですね。

random_beer = IIP - trend_beer - seasonal_beer
plot(as.ts(random_beer))

calc_random.png
最後にいろいろ利用するために、以下のようにcsvファイルに出力します。

write.csv(as.ts(rep(seasonal_beer,1300)), file = 'decompose_seasonal1300.csv')

・pythonでのdecompose

pythonでも同じような関数が用意されています。
コードは以下のとおりです。

import pandas as pd
import statsmodels.api as sm
from statsmodels.tsa.seasonal import STL
import matplotlib.pyplot as plt

# Set figure width to 12 and height to 9
plt.rcParams['figure.figsize'] = [12, 9]
df = pd.read_csv('sibou_.csv')
series = df['Price']
print(series)

cycle, trend = sm.tsa.filters.hpfilter(series, 144)
fig, ax = plt.subplots(3,1)
ax[0].plot(series)
ax[0].set_title('Price')
ax[1].plot(trend)
ax[1].set_title('Trend')
ax[2].plot(cycle)
ax[2].set_title('Cycle')
plt.show()

Figure_1_py.png

pythonでももう一つの方法
個人的には、分解の結果は上の方法だとcycleに対してもう一段の処理が必要で、こちらはランダム分の処理までできているのでこちらの方がリーズナブルに見えます。
また、Rと比較しても、少し異なりseasonalは年々振動幅が大きくなっている様子を見せています(Rはこの変動を平均化処理したので消えているのは当たり前ですが。。。)。場合により、この手法が一番汎用性が高いと言えると思います。
【参考】
Seasonal-Trend decomposition using LOESS (STL)

stl=STL(series, period=12, robust=True)
result = stl.fit()
chart = result.plot()
chart1= result.plot(observed=False, resid=False)
chart2= result.plot(trend=False, resid=False)
plt.show()

Observed & Trend & Seasonal & Residual
Figure_2_py.png
Trend & Seasonal
Figure_3_py.png
Obeserved & Seasonal
Figure_4_py.png
また、以下のようにすると、ばらばらのグラフとして一つのグラフに出力できます。
※pl1...などの数値データを利用するため変数に代入しています

pl1 = result.observed
pl2 = result.trend
pl3 = result.seasonal
pl4 = result.resid

plt.plot(pl1)
plt.plot(pl2)
plt.plot(pl3)
plt.plot(pl4)
plt.show()
plt.close()

pl1-4.png

また、以下のようにすると簡単に出力できます。

pl5 = result.seasonal.plot()
plt.show()
plt.close()
...
seasonal resid
seasonal.png resid.png
trend observed
trend.png observed.png

そして、csvファイル出力は以下のようにやります。

import csv
with open('sample_pl2.csv', 'w') as f:
    writer = csv.writer(f, delimiter='\n')
    writer.writerow(pl2)

まとめ

・Rとpythonでdecompose()のやり方を並べてみた
・それぞれのコードは結果は少し異なるがほぼ同等のことが出来て、ノイズやトレンドが乗った周期的な変動を純粋な周期的な変動として取り出すことが出来た
・各要素分解された成分をcsvデータに書き出すことが出来、二次利用出来る
・実は今回は死亡数の経年変化を例として3月までの速報値を使ったが、その範囲では2011年のような異常値は見えていない(むしろ減少傾向)

・次に、周期的な変動要素を音に変換し聞いてみることとする
・非周期な変動要素についても(検定はともかく)やってみる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む