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

Herokuの定期実行でYouTube APIキーを温存する。

introduction

YouTubeのAPIキーは、90日間利用していないと失効し、再利用するには申請(英語)が必要となります。

申請すれば復活するとしても、申請の手間や申請が通るまでのリードタイムを考慮すると、失効させないに越したことはありません。

そこで、Herokuの無料サーバーを使って毎日最低1回定期的にYoutube Data APIにアクセスすることで、APIキーの失効を防ぐ手順を書いてみました。

※この記事を書くにあたっては、Windows10にインストールしたheloku-cli及びpyhton 3.7.4を使っています。
※後述のHeroku Scheduleアドオンのインストールにはクレジットカードの登録が必要です。

heroku-cliのインストール

下記にてインストーラをダウンロードし、heroku-cliをインストールする。
https://devcenter.heroku.com/articles/heroku-cli#download-and-install

Herokuのデプロイにgitを利用するため、gitが環境にない場合は同時にインストールすること。

ローカルに開発用のディレクトリを作成

適当な場所に開発用のディレクトリを作成する。

test.cmd
$ mkdir heroku_test
$ cd heroku_test

以下の3つのファイルを先ほど作った開発用のディレクトリに保存する。
runtime.txt    :herokuにpython環境を作らせるために必要。
requirements.txt :依存関係のある外部モジュール(この例ではrequestsのみ)
preserve.py    :apiを叩くためのpythonアプリ

各ファイルの内容(例)は以下の通り。

runtime.txt
python-3.7.4
requirements.txt
requests==2.22.0

実行用のpythonファイル(preserve.py)には、環境変数名を設定しておく。
(下のコードではAPI_KEY1、API_KEY2としている。)
ソースにAPIキーそのものを書くのではない点に注意。

preserve.py
import os
import requests

'''任意のyoutube動画IDを指定。
存在しないidを指定してapiを叩くとエラーjsonが返ってくるが、
apiの実行自体は有効なのでapiキー温存目的としては問題ない。'''
video_id="video_id"
keys = [
    """!!注意!!
   下記の「API_KEY1」の部分は環境変数名を記入する。
   api_keyそのものではない。
    """
    os.environ['API_KEY1'], 
    os.environ['API_KEY2']
]
for key in keys:
    #part=playerが一番quotaの消費が少ない(1.6667)
    url = f"https://www.googleapis.com/youtube/v3/videos?part=player&id={video_id}&key={key}"
    resp = requests.get(url)
    print(resp.text)

gitリポジトリ設定・デプロイ

上の3つのファイルを保存した開発用ディレクトリに入り、下記の3つのgitコマンドを入力する。

[gitリポジトリ設定.]
$ git init
$ git add .
$ git commit -m "first commit."

heroku loginでHerokuにログインし、heroku createでアプリを作成する。アプリ名は省略してもよい(自動的に付けられる)

[Herokuにログイン.]
$heroku login
 user:
 pass:

$ heroku create myAppName

作成したアプリをHerokuにデプロイする。

git push heroku master

$ git push heroku master

Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
...
..
.
To https://git.heroku.com/myAppName
 * [new branch]      master -> master

環境変数の設定

Herokuの環境変数にAPIキーを保存する。

heroku config:set (環境変数名)="youtube apiキー" --app "Herokuアプリ名"

[Herokuの環境変数を設定].
$ heroku config:set API_KEY1="Alzos_......" --app "myAppName"

なお、一度設定した環境変数を削除するには
  heroku config:unset (環境変数名)
とする。

環境設定が完了したら、一度テスト実行してみましょう。
プログラムが間違っておらず環境変数がきちんと設定されていれば、youtubeから返ってきたjsonが表示されるはずです。

heroku run python (本体アプリ名.py)

[テスト実行.].
$ heroku run python preserve.py

Running python preserve.py on ⬢ myAppName, run.1002 (Free)
{
 "items": [
  {
   "id": "video_id",
   "player": {
    "embedHtml": "<iframe width=\"480\" height=\"270\" src=\"//www.youtube.com/embed/video_id\" frameborder=\"0\" allow=\"accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture\" allowfullscreen></iframe>"
   }
  }
 ]
}

Heroku Schedulerを設定し定期的に実行させる。

Heroku Schedulerアドオンを設定してpreserve.pyを定期的に実行させる。
(Heroku Schedulerの利用にはクレジットカードの登録が必要)

heroku addons:add scheduler:standard
を実行する。

test.cmd
$ heroku addons:add scheduler:standard 

ブラウザでhttps://dashboard.heroku.com/apps にアクセス。
作成したアプリ名をクリックしてダッシュボードを開く。
tempsnip0.png

configure Add-onsをクリック
tempsnip2.png

Heroku Schedulerをクリック
tempsnip3.png

Create job をクリック
tempsnip4.png

Scheduleで実行間隔として"Every day at..."を選択し、Run commandに「python (実行ファイル.py)」を指定する。
(間隔の上限は1日であり、1週間単位等は指定できない)

Save Jobで設定完了。
tempsnip5[1].png

参考にした記事

Herokuでお天気Pythonの定期実行
https://qiita.com/seigo-pon/items/ca9951dac0b7fa29cce0

Herokuで定期的にrakeタスクを実行したい場合は、Heroku Schedulerを使おう
https://qiita.com/isotai/items/44735d9e7d9ceaef9c48

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

Pythonのimportのバリエーション (from xx import | import xx as xx)

Pythonの importの使い方が分かってなかったので整理。

import

import module_name
モジュールを指定して読み込む。


import utils
↑utilsを読み込む

モジュールの関数を呼び出し

import utils

utils.get_data()

公開されているライブラリも同じように読み込む。

from import

import buildhouse

buildhouse.floor.material(wood='hinoki')

↑通常のimport。架空のライブラリbuildhouseを読み込んでいます。

from buildhouse.floor import material

material(wood='hinoki')

fromを使うと、短縮できる。

import xx as xx

import buildhouse as bl

bl.floor.material(wood='hinoki')

asでモジュール名を省略できる。

参考サイト

https://ai-inter1.com/python-module_package_library/
https://pg-chain.com/python-from-import#toc9

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

Pythonのimportについて整理

Pythonの importの使い方が分かってなかったので整理。

import

import module_name
モジュールを指定して読み込む。


import utils
↑utilsを読み込む

モジュールの関数を呼び出し

import utils

utils.get_data()

公開されているライブラリも同じように読み込む。

from import

import buildhouse

buildhouse.floor.material(wood='hinoki')

↑通常のimport。架空のライブラリbuildhouseを読み込んでいます。

from buildhouse.floor import material

material(wood='hinoki')

fromを使うと、短縮できる。

import xx as xx

import buildhouse as bl

bl.floor.material(wood='hinoki')

asでモジュール名を省略できる。

参考サイト

https://docs.python.org/ja/3/reference/import.html
https://ai-inter1.com/python-module_package_library/
https://pg-chain.com/python-from-import#toc9

追記

コメント欄で教えて頂いているように、__import__ 関数を使う書き方もあるそうです。

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

CentOS7.7にPython3をインストール(OS標準)

はじめに

CentOS7.7にPython3をインストール

Python 3 is now available. Installing the python3 package gives you the Python 3.6 interpreter.

引用元 : Manuals/ReleaseNotes/CentOS7.1908 - CentOS Wiki

LOG

インストール

# cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)

# yum install -y python3 which
... 略

各種確認

# which python3
/usr/bin/python3

# ll /usr/bin/python*
lrwxrwxrwx. 1 root root     7 Oct 21 13:53 /usr/bin/python -> python2
lrwxrwxrwx. 1 root root     9 Oct 21 13:53 /usr/bin/python2 -> python2.7
-rwxr-xr-x. 1 root root  7216 Aug  7 00:52 /usr/bin/python2.7
lrwxrwxrwx. 1 root root     9 Oct 21 13:55 /usr/bin/python3 -> python3.6
-rwxr-xr-x. 2 root root 11408 Aug  7 17:29 /usr/bin/python3.6
-rwxr-xr-x. 2 root root 11408 Aug  7 17:29 /usr/bin/python3.6m

# python3 -V
Python 3.6.8

# yum info python3
Loaded plugins: fastestmirror, ovl
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * extras: ftp.iij.ad.jp
 * updates: ftp.iij.ad.jp
Installed Packages
Name        : python3
Arch        : x86_64
Version     : 3.6.8
Release     : 10.el7
Size        : 39 k
Repo        : installed
From repo   : base
Summary     : Interpreter of the Python programming language
URL         : https://www.python.org/
License     : Python
Description : Python is an accessible, high-level, dynamically typed, interpreted programming
            : language, designed with an emphasis on code readability.
            : It includes an extensive standard library, and has a vast ecosystem of
            : third-party libraries.
            :
            : The python3 package provides the "python3" executable: the reference
            : interpreter for the Python language, version 3.
            : The majority of its standard library is provided in the python3-libs package,
            : which should be installed automatically along with python3.
            : The remaining parts of the Python standard library are broken out into the
            : python3-tkinter and python3-test packages, which may need to be installed
            : separately.
            :
            : Documentation for Python is provided in the python3-docs package.
            :
            : Packages containing additional libraries for Python are generally named with
            : the "python3-" prefix.

Available Packages
Name        : python3
Arch        : i686
Version     : 3.6.8
Release     : 10.el7
Size        : 69 k
Repo        : base/7/x86_64
Summary     : Interpreter of the Python programming language
URL         : https://www.python.org/
License     : Python
Description : Python is an accessible, high-level, dynamically typed, interpreted programming
            : language, designed with an emphasis on code readability.
            : It includes an extensive standard library, and has a vast ecosystem of
            : third-party libraries.
            :
            : The python3 package provides the "python3" executable: the reference
            : interpreter for the Python language, version 3.
            : The majority of its standard library is provided in the python3-libs package,
            : which should be installed automatically along with python3.
            : The remaining parts of the Python standard library are broken out into the
            : python3-tkinter and python3-test packages, which may need to be installed
            : separately.
            :
            : Documentation for Python is provided in the python3-docs package.
            :
            : Packages containing additional libraries for Python are generally named with
            : the "python3-" prefix.

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

(おまけ)Yolo v3+Windows 10でデスクトップのアイコンを動的に識別してみる

はじめに

前回の記事で、Yolo v3でWindowsのデスクトップアイコンを識別するよう学習させてみました。
https://qiita.com/yasunari_matsuo/items/78695e9f53bc6b589daa

その学習済みモデルを使用して、デスクトップアイコンを動的に認識させる実験をしてみました。

さっそく動かす

デスクトップでアイコンを動かす動画をDesktop 2019.10.21 - 21.11.05.01.mp4というファイル名で保存し、下記のコマンドを実行しました。

darknet.exe detector demo data\desktop.data cfg\yolov3-voc.cfg data\backup\yolov3-voc_last.weights "D:\Data\Videos\.gallery\Desktop\Desktop 2019.10.21 - 21.11.05.01.mp4"

静的な画像を指定したときとは、demoという部分の指定と、最後の動画ファイルの指定が異なるだけです。

結果

下記がその結果です。

Youtube
https://youtu.be/_I-t0mSKS7s

アイコンを選択したときを学習させていないので、選択時は認識しなくなってしまっていますが、ほかの場合は、移動中でもきちんと認識してくれています。Yolo v3はかなり優秀ですね。

次は、前回の考察+選択したときのアイコンも含めて学習し、認識率を高めたいと思います。

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

NGBoostを使って分布を予測してみた

GBDTで予測分布が出せると話題のNGBoostを試してみましたので、備忘録がわりに投稿します。実際に動かしてみたい方はこちらを参考にしてください。

所感

  • modelチューニングをほぼしていない状態かつ、今回の小さいデータセットでは精度はほぼ同じ。
  • 分布が算出できるのは使いどころがあるかもですね。

インポート

あとでNGBoostとLightGBMをちょっと比較するのでlightgbmもインポートしておきます。

# ngboost
from ngboost.ngboost import NGBoost
from ngboost.learners import default_tree_learner
from ngboost.scores import MLE
from ngboost.distns import Normal, LogNormal

# lightgbm
import lightgbm as lgb

# skleran
from sklearn.datasets import load_boston
from sklearn.model_selection import train_test_split
from sklearn.metrics import mean_squared_error

データの準備

回帰問題のボストンデータセットをsklearnから取ってきます。数百行の軽いデータです。ホールドアウトで検証します。

X, y = load_boston(True)
rd.seed(71)
X_train, X_valid, y_train, y_valid = train_test_split(X, y, test_size=0.2)
X_train.shape, X_valid.shapae
[output]
((404, 13), (102, 13))

NGBoostの学習

インスタンス作ってfitは同じですね。予測分布を取り出すには pred_dist を使います。

%%time
rd.seed(71)
ngb = NGBoost(Base=default_tree_learner, Dist=Normal, #Normal, LogNormal
              Score=MLE(), natural_gradient=True, verbose=False, )
ngb.fit(X_train, y_train, X_val=X_valid, Y_val=y_valid)

y_preds = ngb.predict(X_valid)  # 通常の回帰の予測値を取得
dist_obj = ngb.pred_dist(X_valid)  # 予測分布を取得

# test Mean Squared Error
test_MSE = mean_squared_error(y_preds, y_valid)
print('ngb Test MSE', test_MSE)

#test Negative Log Likelihood
test_NLL = -dist_obj.logpdf(y_valid.flatten()).mean()
print('ngb Test NLL', test_NLL)
[output]
ngb Test MSE 9.533862361491169
ngb Test NLL 3.8760352
CPU times: user 8.35 s, sys: 1.82 s, total: 10.2 s
Wall time: 6.27 s

比較用のLightGBMの学習

64 core CPUでぶん回したので一瞬で学習が終わります。

%%time
lgb_train = lgb.Dataset(X_train, y_train)
lgb_valid = lgb.Dataset(X_valid, y_valid, reference=lgb_train)
model = lgb.train({'objective': 'regression', 
           'metric': "mse",
           'learning_rate': 0.01,
           'seed': 71},
            lgb_train, 
            num_boost_round=99999,
            valid_sets=[lgb_valid], 
            early_stopping_rounds=100, 
            verbose_eval=500)

y_pred_lgb = model.predict(data=X_valid)
[output]
Training until validation scores don't improve for 100 rounds.
[500]   valid_0's l2: 10.8615
[1000]  valid_0's l2: 10.0345
[1500]  valid_0's l2: 9.66004
Early stopping, best iteration is:
[1651]  valid_0's l2: 9.57796
CPU times: user 3min 51s, sys: 223 ms, total: 3min 51s
Wall time: 3.62 s

精度比較

modelのチューニングはきちんとやっていません。なのでどちらが性能がいいかはわかりませんが、えいやっと使った感触ではこれくらいのデータなら遜色ないですね。

Model valid MSE
NGBoost 9.53386
LightGBM 9.57796

NGBoostの分布の可視化

NGBoostの分布を可視化しました。ここがNGBoostのキモだと思います!
各グリッドの1マスがX_validの1レコードごとに対応する予測結果になります。X_validが102行あるので、グラフも102個出力されます。
モデルの予測結果は正規分布を採用しているので、平均を点推定していることがわかります。

ngboost_dist.png

表示の関係から最初の4行のみの表示しています。全てみたい方はこちらを見てください。

上記のグラフを描画するコードはこちら。

offset = np.ptp(y_preds)*0.1
yy = np.linspace(min(y_valid)-offset, max(y_valid)+offset, 200)

y_dist = []
for y in yy:
    # yという値に対する密度関数の値を記録。実はこの中にX_validの行数分の予測値が入っているのであとでバラす
    y_dist += [dist_obj.pdf(y)]  

plt.figure(figsize=(25, 120))
for idx in tqdm(np.arange(X_valid.shape[0])): # X_validの各レコードごとの予測分布を表示

    # 先ほど記録した密度関数の値から、該当のレコードの予測値を取得
    y_pdf = [float(y_dist[i][idx]) for i in range(len(yy))]

    plt.subplot(35, 3, idx+1)
    plt.plot(yy, y_pdf)

    plt.vlines(y_preds[idx], 0, max(y_pdf), "r", label="ngb pred")
    plt.vlines(y_pred_lgb[idx], 0, max(y_pdf), "purple", label="lgb pred")
    plt.vlines(y_valid[idx], 0, max(y_pdf), "pink", label="ground truth")
    plt.legend(loc="best")
    plt.title(f"idx: {idx}")
    plt.xlim(yy[0], yy[-1])
plt.tight_layout()
plt.show()

結果の比較(散布図)

NGBoost vs LightGBM

だいたいおんなじ予測値を出していますね。
NGvsLGB.png

Ground Truthとの比較

Ground Truthと比較してみると、両方とも同じような傾向の出力結果に見えますね。

LGBvsGT.png
NGvsGT.png

contribution

early stoppingがなかったり、学習の逐次ログがON/OFFしかなくて、100stepごとにログ出力できないとか、結構色々contributionチャンスがありそうですよ、みなさん!w

参考

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

[ゼロから作るDeep Learning]損失関数 二乗和誤差をわかりやすく解説してみた

はじめに

この記事はゼロから作るディープラーニング 5章ニューラルネットワークの学習を自分なりに理解して分かりやすくアウトプットしたものです。
文系の自分でも理解することが出来たので、気持ちを楽にして読んでいただけたら幸いです。
また、本書を学習する際に参考にしていただけたらもっと嬉しいです。

二乗和誤差

二乗和誤差とは回帰問題を解くためのニューラルネットワークの性能の悪さを出したいときによく使われる損失関数です。
スクリーンショット 2019-10-21 19.35.05.png
予測値から正解データの値を引いた予測値と正解データの誤差を二乗して総和したものを➗2します。
スクリーンショット 2019-10-21 20.19.21.png

では実際に実装してみます。

#二乗和誤差実装
t = np.array([0,0,0,0,1])#正解データ
y = np.array([0.1,0.05,0.05,0.1,0.7])
def Sum_squared_error(t,y):
    s = ((y - t)**2).sum()
    return s * 0.5
Sum_squared_error(t,y)
0.057500000000000016

前回の記事でも解説しましたが、ニューラルネットワークの学習は損失関数の最小化を目指すので、上の例はかなり0に近い値なのですごく良いと言えます。

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

Deep Learningを学ぶ #3 〜ニューラルネットワークとシグモイド関数〜

最初に

本記事は以下の本から得た知見を自分用のメモも兼ねてまとめた記事となります。ただし本の内容をそのまま書き写ししているわけではなく、ある程度自身で調べたものや感想が混じっています。ご了承ください。

f:id:rossamu:20190103000547p:plain
ゼロから作るDeep Learning ―Pythonで学ぶディープラーニングの理論と実装

今回はニューラルネットワークの概念と、識別を行う際の処理を学んでいきます。

1 : Deep Learningを学ぶ #1 〜単純パーセプトロンと論理回路〜
2 : Deep Learningを学ぶ #2 〜多層パーセプトロンとXOR〜

ニューラルネットワークとは

ニューラルネットワーク(多層パーセプトロン)は単純パーセプトロンと似た部分が多いです。以下が図の例となります。今回この本では、重みを持つ層のみを数えて「2層ネットワーク」と呼ばれています。

image.png

(書籍によっては、ネットワークを構成する層がn個→n層ネットワークと呼んでいることがあるようです)

ここで、場合分けの式をよりシンプルするために、今まで使用していた式を以下の式を経て、一つにまとめます。

1:

$$
a = b + W_1X_1 + W_2X_2
$$

2:

$$
y=h(a)
$$

$$
y=h(b+W_1X_1+W_2X_2)
$$

3:

image.png

入力信号の総和が$h(x)$という関数によって変換され、返還後の値が出力$y$となります。この$h(x)$のような関数は「活性化関数」と呼ばれ、どのように活性化(発火)するかを決める役割があります。この式を明示的に図示するとしたら以下のようになります。尚、バイアス(b)の入力信号は常に1となるため、ほかのニューロンと差別化されます。

image.png

図の中では入力信号を受け取ったニューロン内で、$h()$という活性化関数によって重み付き信号の総和である $a$ が $y$ に変換されるプロセスが示されています。

$h(x)$のような活性化関数は、閾値を境にして出力が切り替わる関数で「ステップ関数(または階段関数)」と呼ばれています。つまり単純パーセプトロンでは活性化関数にステップ関数を利用している、といえます。このステップ関数を別の関数に変更することでニューラルネットワーク(多層パーセプトロン)を扱うことができるようになります。その一つが「シグモイド関数」です。

どちらの関数も出力信号の値は0〜1の間となります。

シグモイド関数

$$
h(x) = \frac{1}{1+e^{-x}}
$$

$e$ はネイピア数(参考:https://www.nli-research.co.jp/report/detail/id=58572?site=nli)の 2.7182...の実数を表します。信号はシグモイド関数を用いて変換が行われた後、次のニューロンへ伝えられます。

シグモイド関数を用いることでステップ関数を滑らかな曲線にすることができます。単純パーセプトロンではニューロン間を$0$か$1$で信号を表していたのが、ニューラルネットワークでは連続的な実数値の信号が流れます。

また、特徴としては以下の点が挙げられます。

  • 入力$x$の値が大きくなると値は1に近くなる(分母が1に近くなる)が1そのものにはならない
  • 入力$x$の値が小さくなると値は0に近くなる(分母が∞に近くなる)が0そのものにはならない
  • xが0の時に値は$\frac{1}{2}$になる。

活性化関数の実装

ステップ関数の実装

単純な実装

単純パーセプトロンで使用するステップ関数として、単純な実装を行う場合は以下のようなコードになります。

def step_func(x):
    if x>0:
        return 1
    else:
        return 0

NumPyの配列に対応した実装

def step_np_func(x):
    y = x > 0 #xの各要素に対するboolの配列が生成される
    return y.astype(np.int) #boolをint型に変換して値を返却

ステップ関数は0,1で階段状に値が切り替わるため、「階段関数」と呼ばれることがあります。

シグモイド関数の実装

def sigmoid_func(x):
    return 1 / (1 + np.exp(-x))

Np.exp(-x)は$e^{-x}$と等価です。関数に対してNumPy配列を渡しても問題なく計算が行われます。

この関数とmatplotlibというライブラリを使用してグラフ表示(入力は-5から5まで)すると以下のような滑らかな線の出力が得られます。

image.png

参考

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

TensorFlow2.0 + 無料のColab TPUでDCGANを実装した

TensorFlow2.0とGoogle Colaboratoryの無料TPUを使って、DCGANを実装しました。

訓練経過の様子

何をやったか

  • Google ColabのTPU+TF2.0でCelebA(約20万枚)をDCGANで生成
  • TF1.X系のTPUでは、同時に実行可能なグラフは1個の制約があったため、GANの訓練が容易ではなかった(こちらの記事にある通り、不可能であったわけではない。しかし、低レベルAPIが必須で決して容易ではなかった)。TF2.X系のTPUでは、もっと容易にGANを実装できた
  • DCGANの論文通りのモデル(パラメーター数:G=12.7M, D=11.0M)で。64x64の画像20万枚を、1エポックを40秒程度で訓練可能。100エポック回しても1時間程度。
  • 大きなバッチサイズ(BS=1024)で訓練できた。BigGANの論文にもあるように、バッチサイズを大きくすることは生成画像の質・安定性の向上ともに重要。
  • 1時間程度で以下のような顔画像が生成できた

Colab Notebook

こちらから遊べます
https://colab.research.google.com/drive/1rSm01ZiAFgxsWOcQ48cdtgLiVnwwZNfx

TF2.0のTPUの使い方について

基本的な使い方は、MNISTサンプルの記事を書いたのでこちらを参照ください。

TensorFlow2.0 with KerasでいろいろなMNIST(TPU対応)

訓練ループの中身

ポイントをかいつまんで解説していきます。

GANの訓練ループをどう書いているかについて。1バッチ単位での訓練は次のようにします。

@distrtibuted(Reduction.SUM, Reduction.SUM, Reduction.CONCAT)
def train_on_batch(real_img1, real_img2):
    # concat x1, x2
    real_img = tf.concat([real_img1, real_img2], axis=0)
    # generate fake images
    with tf.GradientTape() as d_tape, tf.GradientTape() as g_tape:
        z = tf.random.normal(shape=(real_img.shape[0], 1, 1, 100))
        fake_img = model_G(z)
    # train discriminator
    with d_tape:
        fake_out = model_D(fake_img)
        real_out = model_D(real_img)
        d_loss = (loss_func(tf.zeros(shape=(z.shape[0], 1)), fake_out)
                    + loss_func(tf.ones(shape=(z.shape[0], 1)), real_out)) / 2.0
        d_loss = tf.reduce_sum(d_loss) * (1.0 / batch_size)
    gradients = d_tape.gradient(d_loss, model_D.trainable_weights)
    param_D.apply_gradients(zip(gradients, model_D.trainable_weights))
    # train generator
    with g_tape:
        fake_out = model_D(fake_img)
        g_loss = loss_func(tf.ones(shape=(z.shape[0], 1)), fake_out)
        g_loss = tf.reduce_sum(g_loss) * (1.0 / batch_size)
    gradients = g_tape.gradient(g_loss, model_G.trainable_weights)
    param_G.apply_gradients(zip(gradients, model_G.trainable_weights))
    return d_loss, g_loss, fake_img 

distributedデコレーター

これはTensorFlowの組み込みではなく、自分で実装したデコレーターです。

TPUの訓練では(複数GPUと同様)、複数のTPUデバイスにデータをMapしたあと、個々の計算結果(損失値や生成画像)をReduceする必要があります。実装する際、特に意識しないといけないのがReduceで、Reduce用の関数はTFの組み込みでもいくつか用意されています。

しかし、組み込み関数だけでは単体のtrain_on_batch関数を定義したあと、distributed対応の関数を別に書かないといけず、デザイン的に少し野暮ったいのです。そこで、分散訓練の対応+Reduceをいい感じにやってくれるデコレータを自分で実装しました。詳しくはこちら。

TensorFlow2.0でDistributed Trainingをいい感じにやるためのデコレーターを作った

デコレーターの引数は各返り値に対するReduceの方法です。d_lossg_lossはSUM、fake_imgはaxis=0でCONCATしています(Concatは組み込みのreduce関数で非対応なので、デコレーターを実装する際は少し工夫がいる)。

2つのGradient Tape

GとDという2つのモデルを訓練(偏微分を計算)しないといけないので、Gradient Tapeは2個用意します。これはtape.gradient()で偏微分を計算してしまうと、それまでの計算グラフがリセットされてしまうからです。これはPyTorchでも同じです。

TF2.0では、Gradient Tape以下の計算は自動微分可能です。最初のfake_imgの生成では2個のTapeがあるので、どちらのTapeでもGは微分可能ということになります(もう少し賢い書き方あるかもしれません)。

そして、DのTapeとGのTapeで敵対的学習するようなロスを定義し、学習ステップを進めています。

ここでは生成画像fake_imgをDとGの間で使いまわししています。DとGの順番に注意しましょう。Dを訓練した後はGの係数は変わりませんが、Gを訓練した後は当然Gの係数が変わります。したがって、Gの訓練→Dの訓練での生成画像の使いまわしはできません。D→Gなら使いまわしできます

ちなみにGradient Tapeを2個使って、2階微分の計算なんかもできたりします。WGAN-GPをやりたいときに便利ではないでしょうか。

データの読み込みについて

GANの訓練よりも実はここが一番のポイントだったりします。いくつかトラップがあります。

TPU+tf.dataではローカルファイルから読み込めない(らしい)

例えば、tf.data.Dataset.list_filesを使って、「ファイルパス→tf.data内で画像を読み込んでテンソルを返す」という処理は、CPUやGPUでもできてもTPUでは現状はできないようです。

これはTPUのトラブルシューティングにも書いてあります(同じのエラーが出ます)。

ローカル ファイルシステムを使用できない
エラー メッセージ
InvalidArgumentError: Unimplemented: File system scheme '[local]' not implemented

詳細
すべての入力ファイルとモデル ディレクトリは Cloud Storage バケットパス(gs://bucket-name/...)を使用する必要があり、このバケットは TPU サーバーからアクセス可能である必要があります。すべてのデータ処理とモデル チェックポインティングは、ローカルマシンではなく TPU サーバー上で実行されることに注意してください

これを読む限り、Cloud Storageでないと無理みたいですね。一応、TensorFlowのソースを読んでいくと、StreamingFilesDatasetというのもあり、コメントを読んでいる限りなんかローカルファイルでも使えそうな気がします。残念ながらドキュメントがなく、どう使うのかはよくわかりませんでした。

そこで今回は、一度全ての画像を解像度が64x64のNumpy配列に格納し、それをfrom_tensor_slicesでtf.dataに読み込ませることで解決を図りました。

TensorFlowのオブジェクトの2GBの壁

Numpy配列化すれば全て解決というわけではありません。CelebAをNumpy配列化すると、uint8型でも202599枚 × 64px × 64px × 3ch = 2.31GBと大容量になってしまいます。

TensorFlowのオブジェクトには1個あたり2GBの制約があります。from_tensor_slicesでは、内部的に一度Numpy配列を定数のテンソルに置き換えている(と思われる)ので、2GBをオーバーするとエラーが出ます。公式ドキュメントにも以下のような記載があります。

Note that if tensors contains a NumPy array, and eager execution is not enabled, the values will be embedded in the graph as one or more tf.constant operations. For large datasets (> 1 GB), this can waste memory and run into byte limits of graph serialization.

そこで、今回はデータを半分に分割して、あたかも2つの画像テンソルが流れてくるようなデータセットとみなすようにしました。全体のテンソルは2.31GBなので、半分に分割すれば2GBの制約はクリアできます。前半をX1、後半をX2とします。擬似コードですが、

for X1, X2 in dataset:
    # TPUデバイス内で
    X = tf.concat([X1, X2], axis=0)

とすれば、データを半分に分割しても、個々のTPUデバイス側で結合することができます(だいぶ頭おかしい解決方法)。これでちゃんと訓練できました。動いてしまえば正義ですね。

ちなみにNumpy配列化したときに、そこまで容量が大きくなければ(2GBを超えなければ)このような心配をする必要はありません。

訓練経過

1エポック

25エポック

そこそこ形にはなってきました。
epoch_0024.png

50エポック

相当綺麗です。バッチサイズが1024と大きいおかげで、勾配の信頼性が高く、DCGANでも安定しやすいのでしょう。ここまで30分程度です。
epoch_0050.png

100エポック

1時間ちょいでこのようになりました。ただのDCGANでここまでいけるのはすごい。

まとめ

この記事では、TensorFlow2.0+Colab TPUを使って、CelebA20万枚をDCGANで1時間程度で訓練する方法を紹介しました。

今までは、GANをColab上で訓練することは厳しかったが実情でした。なぜなら、TPUでは訓練コードを書くことが容易ではなかったですし、GPUでは実行時間12時間の制約にかかりやすく、小さい解像度でしか訓練できなかったからです。GANでは自前のGPUが推奨で、遊ぶためのハードルが高いのが現実問題としてありました。

しかし、TensorFlow2.0を使うと、TPUでもGANが容易な形で実装可能(データの読み込みなど多少面倒なところはありますが)となったため、GANのハードルがぐっと下がったといえるでしょう。

さらに、TPUの計算はfloat32でも非常に高速で、同じ内容をGPUで行う場合、1ポック40秒程度で回すのは相当なGPU数がいると思われます(まずバッチサイズ1024で訓練するのが相当大変)。それだけの計算資源を無料で得られるわけですから、やはりTPUはすごい。

記事の冒頭にNotebookを公開しているので、ぜひ遊んでみてください。

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

自分で立てる、オープンソースのテストケース管理システムをまとめてみた

QualityForwardというクラウドベースのテストケース管理システムをサポートしています。その関係もあって、オープンソースのテストケース管理システムを調べてみました。無償で、自分で立てるという選択を選ぶ場合の参考にしてください。

利用できる、利用できそうなソフトウェア

少なくとも3年より前に更新されているソフトウェアです。また、公式サイトがちゃんと立ち上がっていて更新されているものに限定しています。

TestLink

オープンソースのテスト管理と言えばTestLinkを思い浮かべる方が多いのではないでしょうか。開発言語はPHPとなっています。現在はGitHubにコードを移しているようです。今年リリースされた1.9系が最新版です。

TestLink download | SourceForge.net

TestManagerForTracPlugin – Trac Hacks

Tracのプラグインとしてテストケース管理を提供します。最終更新は2017年3月となっています。Wikiページを拡張する形でテストケース用のモデルを追加しています。

TestManagerForTracPlugin – Trac Hacks - Plugins Macros etc.

Kiwi TCMS

10年以上も開発が行われているテストケース管理システムです。Python/Djangoで開発されています。ここにデモ版のサイトが立ち上がっています。一部のラベルは日本語化されているようです。

Kiwi TCMS - the leading open source test case management system

Next Generation Testing Management Tool

グラフの種類も豊富で開発もまだまだ活発に行われています。残念なのは言語が中国語のみで、記述内容がはっきりと分からないことでしょう。Javaで作られています。

aaronchen2k/ngtesting-platform: Next Generation Testing Management Tool

Nitrate

Python/Django製のテストケース管理システムです。Python3.6/3.7をサポートしています。Dockerでも利用できますので、試用は簡単にできそうです。

Nitrate/Nitrate: Django based full-featured test case management system

利用できなさそうなソフトウェア

調べている中で見つかったソフトウェアです。多くが2013年あたりで更新を停止しています。フレームワークやプログラミング言語が古かったり、UIがこなれていないものが多いです。開発自体が活発ではないので、セキュリティリスクがあるかと思います。

Testopia

Mozilla製のテスト管理システムです。Bugzillaの機能拡張として開発されていますが、Bugzilla 5系との互換性がありません。Bugzilla 5は2015年07月にリリースされている中、今なお対応していないことを鑑みると、開発リソースはあまりなさそうです。

Testopia - Mozilla | MDN

MozTrap

MozTrapも同様にMozillaが開発しているテスト管理システムですが、4年前で開発は停止しています。デモサイトもなくなっているので開発は行われていないようです。

mozilla/moztrap: MozTrap test case management system

qaManager

プロジェクトトラッキング、リソース管理機能もあるテスト管理システムです。ソースコードの最終更新が2008年なので、現在のニーズにはマッチしていない可能性がありそうです。

qaManager download | SourceForge.net

QaTraq

QATraqはGPL v2以下で公開されているテスト管理システムです。こちらもqaManagerと同様2008年のソースコード更新が最終となっています。

QaTraq download | SourceForge.net

Radi

Radiは2013年で最終更新が止まっています。スクリーンショットを見るとIEとなっており、フレームが活用されたデザインなのでかなり古い印象を受けます。

Radi a light weight test director tool download | SourceForge.net

RTH

テスト要件を管理するためのシステムとなっています。ソースコードの更新は2009年までです。

RTH - Requirements and Testing Hub download | SourceForge.net

Tarantula

Ruby 1.9、Rails 3.2で開発されたアジャイルテスト管理です。現在は開発が停止しています。元々商用として提供していたようですが、会社がなくなるタイミングでオープンソース・ソフトウェアになったようです。

prove/tarantula: Tarantula Test Management Tool

Redcase

Redmineプラグインとして動作するテストケース管理です。サポートするRedmineが3.x系なので、現在の最新版(4系)では利用できないかも知れません。

Redcase - Plugins - Redmine

まとめ

テストケース管理は多数あるのですが、2008年くらいをピークとして様々なソフトウェアが作られ、その内幾つか(TestLinkやKiwi TCMSなど)が継続的に開発を続けているといった雰囲気があります。技術領域としては変革的なものが出てきていないのかも知れません。

しかし、テスト管理を今なおExcelで行っているチームも多いと言います。操作性が悪かったり、共有もしづらく、お勧めはしません。遠隔地とデータを共有したりすることを考えればWebブラウザベースの方が良いでしょう。クラウドベースですぐにテスト管理をはじめたい場合はQualityForwardをぜひお試しください!

QualityForward

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

無音カメラ on Pythonista

無音カメラ作った

Environment

  • iPhone 11 Pro
  • iOS 13.1.3
  • Pythonista 3.3

機能

  • iPhone 11 Proのタピオカメラに対応した
  • タップでピント調整できるようにした
  • ピンチイン/アウトでズームできるようにした
  • 横向きでとったらちゃんと回転するようにした
  • 4Kでちょっと画質がいい

バグ・まだなこと

  • 解像度がちょっとおかしい
  • 連写したら落ちる
  • フラッシュ
  • iPad
  • Objc_utilとiOSのカメラの仕組み
  • ☠️?スパゲティコード?☠️
  • 11 Pro以外で動くかはちょっとわかんない

スクリーンショット

175F8E9B-84AB-484D-81D5-0556FFABC724.jpeg

ソースコード

こちらからどうぞ?‍♂️

?Github?

cloneしてフォルダ構造はなるべくそのままで使ってください

感想

iOSのアプリ開発って難しいデス

感謝

宣伝

pythonで画像処理 〜黒板の写真を撮って文字以外を透過させた画像を作る〜

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

[iOS] 無音カメラ on Pythonista

無音カメラ作った

Environment

  • iPhone 11 Pro
  • iOS 13.1.3
  • Pythonista 3.3

機能

  • iPhone 11 Proのタピオカメラに対応した
  • タップでピント調整できるようにした
  • ピンチイン/アウトでズームできるようにした
  • 横向きでとったらちゃんと回転するようにした
  • 4Kでちょっと画質がいい
  • 他のスクリプトから呼び出すことも考えた

バグ・まだなこと

  • 解像度がちょっとおかしい
  • 連写したら落ちる
  • フラッシュ
  • iPad
  • Objc_utilとiOSのカメラの仕組み
  • ☠️?スパゲティコード?☠️
  • 11 Pro以外で動くかはちょっとわかんない

スクリーンショット

175F8E9B-84AB-484D-81D5-0556FFABC724.jpeg

ソースコード

こちらからどうぞ?‍♂️

?Github?

cloneしてフォルダ構造はなるべくそのままで使ってください

感想

iOSのアプリ開発って難しいデス

感謝

宣伝

pythonで画像処理 〜黒板の写真を撮って文字以外を透過させた画像を作る〜

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

DebianでFlask環境(virtualenv, gunicorn, nginx, postgresql)を作る

タイムゾーンの設定

$ timedatectl set-timezone Asia/Tokyo
$ timedatectl #内容の確認

Flask、Nginxのインストール

$ apt-get install python3-pip python3-dev nginx

virtualenvのインストールと設定

$ pip3 install virtualenv
$ mkdir /var/www-app/
$ cd /var/www-app/
$ virtualenv venv
$ source venv/bin/activate
$ cd venv
$ pip install gunicorn
$ pip install flask

サーバーの起動

$ gunicorn --workers 2 --worker-class gevent app:app &

PostgreSQLのインストール

$ apt-get install openssl libssl-dev libssl-doc
$ apt-get install postgresql postgresql-contrib

DBとユーザーの作成

$ su postgres
$ psql
$ create database myapp_db;
$ create user myapp_db_user with encrypted password 'myapp_db_pass';
$ grant all privileges on database myapp_db to myapp_db_user;

参考

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

[Python/numpy] 隣接する要素との平均を計算する

本論

問題

numpy で隣の要素との平均を計算したいことがたまにあります. つまり, 長さ n の ndarray, 例えば

x = np.arange(n)

が与えられたとき, 次の長さ n-1 の ndarray が欲しいのです.

ave = ndarray([
    (x[0]+x[1])/2.,
    (x[1]+x[2])/2.,
    ...,
    (x[n-2]+x[n-1])/2.,
])

解法

これは numpy 1.12 で導入された numpy.roll 関数を使うのが最適解だと思います.

x1 = np.roll(x, -1)  # = np.ndarray([ x[1], x[2], ..., x[n-1], x[0] ])
ave = (x + x1)/2.    # これは長さ n の ndarray で最後の要素 (x[0]+x[n-1])/2. は要らない
ave = ave[:-1]       # これが欲しいもの

np.roll の第 2 引数の符号に注意してください.

参考文献

追記

コメントで @shiracamus さんに

ave = (x[1:]+x[:-1])/2.

で十分と指摘していただきました.

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

User & IBM NEXT 2019@福岡

NEXT 2019@福岡 ~ Jupyter notebook まで

User & IBM NEXT2019@福岡 に行ってきました。最高に楽しかったです!
やはり、あの様な場に行くと、出会いあり、発見ありでテンションが上がります。
帰ってきた後の仕事に対するモチベーションも、非常に上がるので行って良かったです。

さて話をIBMiの話題に戻すと、会場でとある イケメン に、お会いすることが出来、その方から「IBMijupyter notebook 使えますよ。」と言われて「えぇーそうなんだ!」とビックリ。
本当に yum になってから、こっそり使える様になっている、パッケージが多いです。
(^-^) 早速入れてみました。
公式のBitbucketには記載がないので、AIX 情報を参考 にしました。yum 楽勝です!

# 必要ライブラリ等のインストール(※デフォルトで既に入っているものもあり)
yum install gcc gcc-c++ gcc-gfortran xz python3 python3-devel libpng-devel libfreetype6 freetype-devel libzmq libzmq-devel liblapack3 libblas3
# Jupyterのインストール
pip3 install jupyter

# notebookの設定ファイル生成
jupyter notebook --generate-config

# notebook設定ファイルの変更
vim .jupyter/jupyter_notebook_config.py

## The IP address the notebook server will listen on.
#c.NotebookApp.ip = 'localhost'
c.NotebookApp.ip = 'xxx.xxx.xxx.xxx'

#c.NotebookApp.open_browser = True
c.NotebookApp.open_browser = False

#c.NotebookApp.port = 8888
c.NotebookApp.port = 8888

# パスワードを設定する場合
jupyter notebook password
Enter password:*****
Verify password:*****
# 以下に「.jupyter/jupyter_notebook_config.json」が作成される

# Jupyter notebookの実行
jupyter notebook
[I 15:34:37.114 NotebookApp] Serving notebooks from local directory:home/hoge
[I 15:34:37.114 NotebookApp] The Jupyter Notebook is running at:
[I 15:34:37.114 NotebookApp] http://xxx.xxx.xxx.xxx:8888/
[I 15:34:37.114 NotebookApp] Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).


実行結果

2019-10-21_154242.png

福岡の思い出
IMG_20191016_103128.jpg
IMG_20191016_123819.jpg

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

MySQLの既存テーブルをDjangoで扱うときのメモ

1.準備

まずはsettings.pyにMySQLの接続先情報を記述します。

settings.py
import pymysql
pymysql.install_as_MySQLdb()
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'NAME': '###db',  # データベース名
        'USER': 'root',  # ユーザ名
        'PASSWORD': 'password',  # パスワード
        'HOST': '127.0.0.1',  # MariaDBがあるサーバのIPアドレスやホストを。空欄はローカルホスト
        'PORT': '3306',  # 空欄はデフォルトポートの3306
    }
}

MySQLを扱うモジュールが必要なため、PYMYSQLをインストールしてきます。

pip install PyMySQL

2.マイグレートしていく

Models.pyに記載するモデル定義情報を自動で生成

下記コマンドで既存テーブルのモデル定義を自動で作ります。
python manage.py inspectdb table テーブル名

> python manage.py inspectdb table テーブル名
# This is an auto-generated Django model module.
# You'll have to do the following manually to clean this up:
#   * Rearrange models' order
#   * Make sure each model has one field with primary_key=True
#   * Make sure each ForeignKey has `on_delete` set to the desired behavior.
#   * Remove `managed = False` lines if you wish to allow Django to create, modify, and delete the table
# Feel free to rename the models, but don't rename db_table values or field names.
from django.db import models
# Unable to inspect table 'table'
# The error was: (1146, "Table '##db.table' doesn't exist")


class Usertable(models.Model):
    id = models.BigIntegerField(unique=True, blank=True, null=True)
    name = models.CharField(max_length=50, blank=True, null=True)
    screen_name = models.TextField(blank=True, null=True)
    description = models.TextField(blank=True, null=True)
    profile_image_url = models.TextField(blank=True, null=True)
    follow_count = models.IntegerField(blank=True, null=True)
    followers_count = models.IntegerField(blank=True, null=True)
    tweet_count = models.IntegerField(blank=True, null=True)
    video_count = models.IntegerField(blank=True, null=True)
    video_count_date = models.BigIntegerField(blank=True, null=True)
    image_count = models.IntegerField(blank=True, null=True)
    image_count_date = models.BigIntegerField(blank=True, null=True)
    twitter_created_at = models.DateTimeField()
    created_at = models.DateTimeField()
    updated_at = models.DateTimeField()

    class Meta:
        managed = False
        db_table = 'usertable'

出力された情報はプロジェクトのmodels.pyにコピー&ペーストにしましょう。
ちなみに上記コマンドを事項した際にエラーらしきものが確認できる。
# The error was: (1146, "Table '##db.table' doesn't exist")
これは何なのか、正直わからないw教えていただきたい。

マイグレートしたらエラーでた('id' can only be used as a field name if the field also sets 'primary_key=True')

> python manage.py migrate
SystemCheckError: System check identified some issues:

ERRORS:
hello.Usertable: (models.E004) 'id' can only be used as a field name if the field also sets 'primary_key=True'.

翻訳したらわかるように
'id'は、フィールドに 'primary_key = True'も設定されている場合にのみフィールド名として使用できます。
既存テーブルにidカラムがある場合は制限があるようです。

先ほどのmodels.pyのidの定義を下記に変更しました。

id = models.BigIntegerField(unique=True, blank=True, null=True, primary_key=True)

再度実行してみます。
またエラー。。。null=Teueはいらないようです。

> python manage.py migrate
SystemCheckError: System check identified some issues:

ERRORS:
hello.Usertable.id: (fields.E007) Primary keys must not have null=True.
        HINT: Set null=False on the field, or remove primary_key=True argument.

再度直して。

id = models.BigIntegerField(unique=True, blank=True, primary_key=True)

> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
  No migrations to apply.
  Your models have changes that are not yet reflected in a migration, and so won't be applied.
  Run 'manage.py makemigrations' to make new migrations, and then re-run 'manage.py migrate' to apply them.

ん?なにかおかしい。
そもそも私の理解が足りず、まずはマイグレーションをしてほしいみたい。
下記の感じかな!?

・まずは、マイグレーションで移行する情報をまとめるマイグレーションファイルを作成.

> python manage.py makemigrations
Migrations for 'hello':
  hello\migrations\0001_initial.py
    - Create model Usertable

・マイグレーションファイルの適用.

> python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, hello, sessions
Running migrations:
  Applying hello.0001_initial... OK

管理サイトからもアクセスできるように。

下記にアクセスすれば管理サイトにアクセスできるかと思いますが、管理サイトからテーブル情報を操作できます。
http://127.0.0.1:8000/admin/

そのままでは表示もされないので、管理サイトでデータベースを確認できるようにします。
作ったアプリの配下にあるadmin.pyを編集します。

from django.contrib import admin
from hello.models import Usertable #追加

# Register your models here.
admin.site.register(Usertable) #追加

Models.pyのモデル定義をした際の
class Usertable(models.Model):
クラス名をimport&registerしています。

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

Yolo v3+Windows 10でデスクトップのアイコンを識別してみる その1

はじめに

前回は、強化学習でデスクトップのアイコンをゴミ箱に移動するというものを作ってみました。
今回は、Yolo v3を使ってデスクトップのアイコンを識別して、それをゴミ箱に移動するというのを作ってみたいと思います。

環境

Windows10 Pro
Visual Studio 2017
CUDA 10.0
cuDNN 7.3.0.29
OpenCV 4.1.1

Yolo v3インストール

Yolo v3をWindowsにインストールするには、公式のものだといろいろ難しいようです。
そこで、Windows用にリポジトリしてくれたものがあるので、それを利用します。

github
git clone https://github.com/AlexeyAB/darknet.git
cd darknet

準備として、CUDAとcuDNNとOpenCVにパスを通します。

OpenCV
[配置先]\opencv\build\x64\vc15\bin

CUDA、CuDNN
[配置先]\CUDA\v10.0\bin

VSのソリューションを開きます。
ソリューションは、下記にあります。
[配置先]\darknet\build\darknet\darknet.sln

ソリューションプラットホームをReleasex64に変更します。Debugのままだとエラーになりました・・。

CUDAとOpenCVのインクルードディレクトリを追加します。
image.png

ライブラリにもディレクトリを追加します。
image.png

リビルドしましょう!
ワーニングはたくさん出ますが、最終的に成功しました。
image.png

ウェイトファイルを以下からダウンロードして、先ほどビルドして出来上がったdarknet.exeと同じ場所に保存します。
https://pjreddie.com/media/files/yolov3.weights

コマンドプロンプトで、darknet.exeの場所まで移動して、以下のコマンドを実行します。

コマンドプロンプト
darknet.exe detect cfg\yolov3.cfg yolov3.weights data\dog.jpg

サンプル画像での認識結果が表示されたら、インストール成功です。
image.png

テストデータを準備する

テストデータは、ネットの画像検索で取得してきます。
Goolgeで「デスクトップアイコン」というキーワードで画像検索すると、結構出てきます。

image.png

この画像たちをアノテーションしていくのですが、完全手作業だと手間がかかるので、labelImgというツールを使います。
Github:
https://github.com/tzutalin/labelImg

labelImgをインストールする

Windows+conda環境では、以下のようにインストールします。

conda_prompt
git clone https://github.com/tzutalin/labelImg.git
cd labelImg

conda install pyqt=5
pyrcc5 -o libs/resources.py resources.qrc
python labelImg.py
python labelImg.py [IMAGE_PATH] [PRE-DEFINED CLASS FILE]

python lavelImg.pyを実行すると以下のエラーが発生しました。

エラー発生
(labelImg) D:\Projects\python\labelImg>python labelImg.py
Traceback (most recent call last):
  File "labelImg.py", line 39, in <module>
    from libs.labelFile import LabelFile, LabelFileError
  File "D:\Projects\python\labelImg\libs\labelFile.py", line 10, in <module>
    from libs.pascal_voc_io import PascalVocWriter
  File "D:\Projects\python\labelImg\libs\pascal_voc_io.py", line 6, in <module>
    from lxml import etree
ModuleNotFoundError: No module named 'lxml'

そんな時は、lxmlをインストールすれば解決です。

conda_prompt
pip install lxml

そして、やり直すと、画面が出ました!
image.png

アノテーションしてみる

画像にバウンディングボックスをつけることをアノテーションと呼びます。
先ほどのデスクトップアイコンの検索結果から一枚画像をダウンロードして、矩形をつけてみましょう。

とその前に、dataフォルダ内にあるpredefined_classes.txtを編集し、今回分類したいカテゴリを事前に定義しておきます。
ゴミ箱とアイコンの二種類に分けたいので、以下のようにしました。

predefined_classes.txt
recycle bin
icon

保存したらいったん起動しなおしましょう。そして、次に、画像を開きます。
OpenまたはOpen Dirでファイルを開いたら、Create RectBoxをクリックすると矩形をつけられるようになります。
image.png

矩形を指定したら、クラスを選択するダイアログが表示されます。
image.png
recycle binを指定してOKをクリックすると、右側に追加されます。
image.png

ということで、デスクトップのアイコンを指定してみました。
image.png
っていうか、思った以上に面倒くさいです。やばいです。
保存の際は、「Pascal VOC」というところをクリックすると「Yolo」に変更されます。トグルになってるみたいです。
image.png
この状態で保存すると、以下のようなファイルが出来上がりました。
このファイルのフォーマットは以下の通りです。
[クラス] [矩形の中心座標x] [矩形の中心座標y] [矩形のwidth] [矩形のheight]

Deskop001.txt
0 0.027083 0.037037 0.032292 0.072222
1 0.028906 0.135185 0.038021 0.087037
1 0.025521 0.225926 0.043750 0.083333
1 0.025781 0.311574 0.044271 0.071296
1 0.026302 0.407870 0.040104 0.087963
1 0.027344 0.496296 0.040104 0.068519
1 0.027344 0.590278 0.051562 0.084259
1 0.027604 0.678241 0.045833 0.069444
1 0.027604 0.767130 0.045833 0.069444
1 0.027344 0.866667 0.052604 0.090741
1 0.079167 0.043981 0.044792 0.086111
1 0.079948 0.131481 0.049479 0.077778
1 0.078646 0.229630 0.054167 0.090741
1 0.077083 0.312963 0.045833 0.068519
1 0.078906 0.407870 0.053646 0.076852
1 0.078906 0.500926 0.053646 0.085185
1 0.077865 0.587500 0.040104 0.071296
1 0.079687 0.680093 0.045833 0.080556
1 0.078646 0.768056 0.038542 0.071296
1 0.079427 0.858796 0.046354 0.080556
1 0.131771 0.038426 0.044792 0.075000
1 0.133333 0.124537 0.053125 0.084259
1 0.132292 0.221296 0.053125 0.083333
1 0.131771 0.312963 0.053125 0.083333
1 0.132292 0.402778 0.053125 0.083333
1 0.131250 0.496296 0.053125 0.083333

yolov3-voc.cfgを編集する

yolov3-voc.cfgファイルを適当な位置にコピーし、中身を今回のものに書き換えます。
書き換える部分は、
width=928
height=512
ここでは、学習時の画像の幅と高さを指定します。
32の倍数を指定する必要があります。

ちな、これ以上の大きさにすると私のGTX1080ではメモリ不足になってしまいました。。
Teslaが欲しい・・。

あとは、下記を修正しました。
605行目: filters=21
611行目: classes=2
689行目: filters=21
695行目: classes=2
773行目: filters=21
779行目: classes=2

ちなみにfiletersは(classes + 5) * 3という計算式で算出します。今回は21です。

トレーニングしてみる

5画像ほど準備してみたので、それでトレーニングしてみます。

が、その前に、訓練データを指定するファイル群を作成しないといけません。
最終的にこのようなフォルダ構成になります。

 | - data
     | - desktop.data
     | - desktop-obj.names
     | - desktop-test.txt
     | - desktop-train.txt
     |- backup
     |- datasets
         |- Desktop001.png
         |- Desktop001.txt
         |- Desktop002.png
         |- Desktop002.txt
         |- Desktop003.png
         |- Desktop003.txt
         |- Desktop004.png
         |- Desktop004.txt
         |- Desktop005.png
         |- Desktop005.txt
 | - cfg
     |- yolov3-voc.cfg
 | - models
     |- darknet53.conv.74

まずは、desktop.dataファイルを作成します。

desktop.data
classes= 2
train  = data/desktop-train.txt
valid  = data/desktop-test.txt
names = data/desktop-obj.names
backup = data/backup

今回は二つに分類するのでclasses = 2を指定します。あとは、それぞれの定義ファイルと、学習中のweightが保存されるbackupフォルダを指定します。

desktop-obj.namesにはクラスの名前を指定します。

desktop-obj.names
recycle bin
icon

desktop-train.txtには、学習用の画像ファイルパスを指定します。

desktop-train.txt
/data/datasets/Desktop001.png
/data/datasets/Desktop002.png
/data/datasets/Desktop003.png
/data/datasets/Desktop004.png
/data/datasets/Desktop005.png

desktop-test.txtには、テスト用の画像ファイルパスを指定します。とりあえず、学習用と同じのを指定しておきます。

desktop-test.txt
/data/datasets/Desktop001.png
/data/datasets/Desktop002.png
/data/datasets/Desktop003.png
/data/datasets/Desktop004.png
/data/datasets/Desktop005.png

下記サイトから、weightの初期値をダウンロードして配置します。

https://pjreddie.com/media/files/darknet53.conv.74

下記のコマンドを実行します。

darknet.exe detector train data\desktop.data cfg\yolov3-voc.cfg models\darknet53.conv.74

訓練が無事開始されたら成功です。
image.png

テストしてみる

下記コマンドを実行すると、テスト対象の画像のパスを聞いてきます。

darknet.exe detector test data\desktop.data cfg\yolov3-voc.cfg data\backup\yolov3-voc_last.weights

まずは、訓練に使った画像を指定してみます。
image.png

アイコンの位置をずらしてみましょう
image.png
す、すごい・・・。

背景をつけたらさすがに厳しいでしょうか。
image.png
二個だけしか認識されませんでした。
訓練画像が5枚だけなのと、背景のバリエーションが訓練データになかったのが原因だと思います。

つづく

今回はいったん動作の実験だったので背景変更には対応できていませんが、そのあたりのデータバリエーションを増やして再訓練させてみたいと思います。
次回は、そのあたりの認識率向上と、アイコン削除プログラムへの組み込みをやっていきたいと思います。こうご期待

関連している記事

デスクトップのアイコンをすべて削除してくれるAIを強化学習(DQN)でつくってみた
https://qiita.com/yasunari_matsuo/items/8ab661ca4a449495703f

参考にしたサイト

https://blog-ryotaro-diary.hatenablog.com/entry/2018/12/26/002035
https://nmxi.hateblo.jp/entry/2019/02/28/104546
https://demura.net/misc/14458.html
https://github.com/pjreddie/darknet/issues/587
https://qiita.com/harmegiddo/items/c3db5fd567fa4c6cc9fb

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

Python Data Visualization 01 - How to plot a basic graph using Matpliotlib

Hi everyone, this is Leon, welcome back!

In this article, we are going to learn more about data visualization by using package Matplotlib.

Contents

  • 1.1 one axis graph
  • 1.2 two axes graph
  • 1.3 title & label
  • 1.4 axis
  • 1.5 color & line style
  • 1.6 marker (markerfacecolor, markersize)
  • 1.7 legend (location) vs label
  • 1.8 grid
  • 1.9 multiple figures on a same graph

0. Input Libraries

import numpy as np
import pandas as pd

# For visualization
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib
%matplotlib inline

import warnings
warnings.filterwarnings("ignore")

1.1 One Axis Graph

# using numpy array
x = np.array([6,8,4,2,10])
plt.plot(x)
plt.show()

1-1.png

1.2 Two Axes Graph

x = np.array([6,8,4,2,10])
y = 2*x + 1
plt.plot(x,y)
plt.show()

1-2.png

1.3 Title & Labels

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")

plt.plot(x,y)
plt.show()

1-3.png

**You could also change the size of title and labels as needed.

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title", size=20) # fontsize
plt.xlabel("x axis", size=20) # fontsize
plt.ylabel("y axis", size=20) # fontsize

plt.plot(x,y)
plt.show()

1-3-2.png

1.4 Axis

x = np.array([6,8,4,2,10])
y = 2*x + 1
plt.title("title", size=20)
plt.xlabel("x axis", size=20)
plt.ylabel("y axis", size=20)

plt.axis(xmin=3, xmax=5, ymin=0, ymax=10)
#plt.axis("off")

plt.plot(x,y)
plt.show()

1-4.png

1.5 Color & Line Style

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")

plt.plot(x, color="red", linestyle="--")
# try the code below, and think why
# plt.plot(x,y, color="black", linestyle="-.")
plt.show()

1-5.png

1.6 Marker

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")

plt.plot(x, marker="o", color="red", linestyle="--")
plt.show()

1-6.png

1.7 Legend vs Label

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")
plt.plot(x, marker="o", color="red", linestyle="--")

plt.legend(["test"])
plt.show()

1-7.png

label could also get the job done.

# or you can use label
x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")
plt.plot(x, marker="o", color="red", linestyle="--", label="test") # add it here

plt.legend() # no need to add anything inside the legend
plt.show()

1-7-2.png

1.8 Grid

x = np.array([6,8,4,2,10])
y = 2*x + 1

plt.title("title")
plt.xlabel("x axis")
plt.ylabel("y axis")
plt.plot(x, marker="o", color="red", linestyle="--", label="test")
plt.legend()

plt.grid(True)
plt.show()

1-8.png

1.9 Multiple Figures on A Same Graph

x = np.array([6,8,4,2,10])
y1 = 2*x + 1
y2 = x - 5
plt.plot(x,y1)
plt.plot(x,y2)
plt.show()

1-9.png

Summary

Okay, these are all for today. I hope you enjoy my sharing. If you have any questions, feel free to message me.

I will see you next time, before that, enjoy coding.

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

AI翻訳アルゴリズムの構築に必要なデータセット集 by Kiara

はじめに

弊社では、Kiaraという同時翻訳Chatbotツール(Slack Plugin)を開発しながら、
オープンイノベーションで自然言語処理・機械翻訳の研究を進めています。
https://www.kiara-app.com/

背景

世界には数千の言語があります
1000 x 1000 の組み合わせだけでも100万通りあり、
それぞれのペアの辞書は作りきれないですが、
昨今のニューラルネットワークの発展で機械翻訳がめざましく性能が上がっています。

https://www.linguisticsociety.org/content/how-many-languages-are-there-world
hello-wordle.png

言語学の探究の対象は人間の言語、特に世界の言語の多様性の範囲と限界です。 したがって、言語学者は、世界にいくつの言語が存在するかについて、明確で合理的に正確な概念を持っていると考えるかもしれません。 しかし、そのような明確なカウントはない、または少なくとも、現代言語学の科学的発見としての地位を持つカウントはないことが判明しました。

この不足の理由は、(ちょうど)高地ニューギニアやアマゾンの森林などの世界の一部がそこに住んでいる人々の範囲を確認するのに十分な詳細に調査されていないということではありません。 むしろ、問題は、言語を列挙するという概念が見かけよりもはるかに複雑であることです。 この一見単純な質問に対して、言語学者が与えるかもしれない、一貫性のある(しかし全く異なる)答えがいくつかあります。

OPUS

http://opus.nlpl.eu/index.php

OPUSは、ウェブから翻訳されたテキストのコレクションです。 OPUSプロジェクトでは、無料のオンラインデータを変換および調整し、言語注釈を追加し、コミュニティに公開されている並列コーパスを提供しようとします。 OPUSはオープンソース製品に基づいており、コーパスはオープンコンテンツパッケージとしても配信されます。 いくつかのツールを使用して、現在のコレクションをコンパイルしました。 すべての前処理は自動的に行われます。 手動による修正は行われていません。

OPUSコレクションは成長しています! 時々このページをチェックして、到着する新しいデータを確認してください...
寄付は大歓迎です! jorg.tiedemann@helsinki.fiまでご連絡ください

DGT-TM

https://ec.europa.eu/jrc/en/language-technologies/dgt-translation-memory

2007年11月以降、欧州委員会の翻訳総局は、多言語、言語の多様性、および委員会の再利用をサポートする欧州委員会の一般的な取り組みを促進するために、Acquis Communautaireの多言語翻訳メモリDGT-TMを公開しました。 情報。

技術ユーザー向けのこのページでは、このユニークな言語リソースの説明と、それをダウンロードする場所と、276言語ペアまたは552言語ペア方向のバイリンガルアラインコーパスの作成方法について説明します。 22の言語に翻訳された1つの文の例を次に示します。

Panlex

https://panlex.org/

PanLexは、世界最大の語彙データベースを構築しています

私達がすること
10年以上にわたり、私たちは世界最大の語彙翻訳データベースを構築しており、常に新しい単語と言語を追加しています。数千の翻訳辞書を単一の共通構造に変換することにより、PanLexデータベースでは、単一の辞書にはない数十億の語彙翻訳を導き出すことができます。

なぜそんなに重要なのか
主要な言語の話者と十分にサービスされていない言語を話す人が利用できる機会の間には、格差が広がっています。十分なサービスが提供されていない言語で翻訳辞書と技術を利用できるようにすることで、スピーカーは、社会的、文化的、経済的福祉をサポートしながら、権利を行使し、平等な機会にアクセスできます

Google翻訳との違い
Google翻訳およびその他の機械翻訳アプリケーションは、最大100の主要な世界の言語で文章全体およびテキストを翻訳します。 PanLexは、数千の言語で単語を翻訳します。私たちのデータベースは、パンリンガル(すべての言語の範囲を強調)および語彙(文ではなく単語に焦点を当てています)です。データは無料で公開されています。

25 Best Parallel Text Datasets

https://lionbridge.ai/datasets/25-best-parallel-text-datasets-for-machine-translation-training/

機械翻訳トレーニング用の並列テキストデータセット
カナダの第36議会の調整されたハンサード:英語とフランス語の130万ペアの調整されたテキストセグメント。テキストは、第36カナダ議会の公式記録から取られています。

欧州議会の議事録並列コーパス1996-2011:文は21のヨーロッパ言語で対になっています。すべてのテキストには、ドキュメント、発言者、段落を含むメタデータが含まれています。

Global Voices Parallel Corpus:ニュースポータルGlobal Voicesからの選択。57の異なる言語で同じニュース記事を取り上げています。コーパスは四半期ごとに更新されます。

中国語-フランス語テキスト:中国語放送ニュースからの約30,000の中国語文字のサブセットのフランス語翻訳を含むデータセット。

Arabizi Text:522のツイートで構成される英語とArabizi(アラビア語チャット言語)の混合テキストでのコード切り替えの自動検出のためのトレーニングデータ。

英語-ベトナム語テキスト:プロの翻訳者がベトナム語に翻訳した500,000の英語ドキュメントのコーパス。ソーステキストには、2000年から2007年の間に収集された書籍、辞書、新聞、オンラインニュースが含まれます。

英語-ペルシャ語のテキスト:法律、文学、科学、芸術、政治などの分野からの英語とペルシャ語の200,000以上の整列した文章が含まれています。

中国語と英語の電子メール:電子メールからの中国語の15,000文字(10,000ワードに相当)と、英語の参照翻訳が含まれています。

フランス語-アラビア語の新聞:アラビア語の10,000語のコーパスとフランス語の2つの参照翻訳。ソーステキストは、2013年5月にアラビア語版のLe Monde Diplomatiqueから収集された記事です。

Pashto-French Text:フランス語に翻訳されたPashtoでの106時間の録音の転写で構成されています。

ドイツ語-英語テキスト:ドイツ語、英語、トルコ語の手動で整列されたデータセットのセット。

トルコ語-英語テキスト:WMT2018用のトルコ語-英語並列コーパス。

国連翻訳テキスト:6か国語で国連から翻訳された文書のコレクション。

XhosaNavy:ステレンボッシュ大学のE&E工学部のHerman Engelbrechtによる南アフリカ海軍の並列コーパス。

ウィキペディア:20の言語でウィキペディアから抽出された数百万の並列文の大規模なコーパス。

英語-クロアチア語:英語とクロアチア語の並列ドキュメントペア候補。

カタロニア語-スペイン語:カタロニア語およびスペイン語のカタロニア政府の公式ジャーナルからの文書のコレクション。

英語-日本語:Wikipediaの両言語の京都記事の約50万ペアの手動翻訳文のデータセット。

OntoNotes:英語、中国語、アラビア語のさまざまなジャンルのテキストを含む注釈付きコーパス-ニュース、会話電話、ウェブログ、ユースネットニュースグループ、放送、トークショー。

中国のツリーバンク:中国のニュースワイヤー、政府の文書、雑誌記事、およびさまざまな放送ニュースからの注釈付きで解析されたテキストの約150万語が含まれています。

Arabic Broadcast News Transcripts:2008年と2009年に収集された約37時間のアラビア語放送ニューススピーチの転写が含まれています

Quoraより

https://www.quora.com/Where-can-I-find-a-translated-text-dataset-from-any-language-to-any-other-one-valid-to-be-trained-on-a-machine-translation-model-in-machine-learning-1

それは、「任意の言語」の意味に依存します。数千の言語があり、それらのほとんどについては、翻訳されたテキスト(平行コーパス)は言うまでもなく、書かれたテキストを見つけるのは困難です(書記体系がない場合もあるため)。

少し控えめな場合は、少なくとも非常に多くの言語をカバーする、並列コーパスの素晴らしいコレクションがOpusプロジェクトWebサイトから入手できます。抽出できるコーパスが、MTモデルをトレーニングするのに十分な大きさであるかどうかによって異なります。多くの場合、作成できるモデルの品質とカバレッジはかなり制限されます。

EU言語(現在24)に興味がある場合は、DGT-TMと呼ばれるEU機関の翻訳者が作成した並行テキストの興味深いコレクションがあります。

人々は聖書(最も翻訳された本の1つ)の翻訳を使用して多くの言語でMT実験を行ってきましたが、ここでの問題は言語が少し古く、テキスト(したがってそれから作成されたモデル)が現代の語彙の多くをカバーしていません。

翻訳されたテキストではなく語彙に重点を置きたい場合は、Long Now FoundationのPanlexプロジェクトをご覧ください。

===

主要言語に限定して満足できる限り、翻訳用のデータセットを見つけることができる場所はいくつかあります。 たとえば、Wikipediaには20の言語のコーパスがあり、欧州議会には21のヨーロッパ言語の文のペアがあります。 それ以外は、単一の言語ペア間で翻訳するデータセットを見つけるのが一般的です。 時間と労力を費やす意思がある場合は、いくつかの異なるデータセットを見つけて、モデルに合わせて変更することができます。

私が勤務するGengoでは、さまざまなグローバル言語で最も有用な並列テキストデータセットのリストを作成しました。これは、探しているデータを見つけるのに役立ちます。 または、Gengoからカスタムデータセットを注文することを検討できます。 22,000人を超える認定言語の専門家が37言語の翻訳データセットの作成または注釈付けを待機しているため、お客様のデータニーズを満たすのに最適です。

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

初心者のpythonメモ:演算子

はじめに

本メモはPythonの演算子メモです。

Pythonの主な演算子

Pythonで数式を入力すると計算をしてくれます

演算子 効果
+ 足し算 1+2=3
- 引き算 5-1=4
* 掛け算 2*3=6
/ 割り算 10/2=5.0
** 累算 2**3=8
% 剰余(割り算の余り) 10%3=1
// 割り算結果を小数点以下無視して整数を返す 5//2=2

比較演算子と代入演算子

数字の大小を比較する際は、比較演算子を用います。

演算子 数学記号
< < 4<2 False
> > 5>1 True
<= 1<=0 False
>= 3>=3 True
!= 3!=4 True
== = 2==3 False

また、等号(=)は代入演算子、複合代入演算子としても用います。

演算子 説明
= =の左辺の変数に右辺のデータを代入 x=3 xに3を代入
+= +=の左辺の変数に両辺のデータを代入 x+=5 xにx+5を代入
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium+Pythonでブラウザの自動操作

Selenium とは?

ブラウザの操作を自動化するツールです。

https://docs.seleniumhq.org/
OS・ブラウザごとのWebDriverと合わせて使用します。

実行環境

オフィス用のPC上で実行することを想定して、今回はWindowsを使用します。
Windows10 Pro 1903

Python3の準備(Anaconda)

Pythonでも良いんですが、今回はAnacondaをインストールします。

https://www.anaconda.com/distribution/
Python 3.7 version をダウンロードしてインストール。

Anaconda Promptを開いて、パッケージをインストールします。

conda install beautifulsoup4
conda install selenium

WebDriverの配置

https://sites.google.com/a/chromium.org/chromedriver/downloads
Current ReleasesからChromeに対応したものをダウンロードし、パスが通るところ(環境変数Pathで設定された場所)に起きます。

Seleniumの動作の流れ

Seleniumの基本的な流れは以下です。
- Chromeを開く
- ページ内の要素を見つける
- 要素を操作する

今回は例として、Chromeでブラウザを操作して、Googleで猫画像を検索し、その画像が掲載されたHTMLファイルを作成して開きます。

今回のコード

import time 
import re
import csv
from selenium import webdriver
from bs4 import BeautifulSoup
import os

# get current path
path = os.getcwd()

# open chrome
driver = webdriver.Chrome()
driver.get('https://www.google.com')

time.sleep(1)

# input search form 
neko = driver.find_element_by_name("q")
neko.send_keys("猫");
neko.submit();

time.sleep(1)

# move image search page
nekoimg = driver.find_element_by_xpath('//*[@id="hdtb-msb-vis"]/div[2]/a')
nekoimg.click()

time.sleep(1)

# get page source
html = driver.page_source
bs = BeautifulSoup(html, "html.parser")

# select neko image 
rows = bs.findAll("img",src=re.compile("data:image/jpeg"))

with open("neko.html", "w", encoding='utf-8') as file:
# write html header
    file.write('<!DOCTYPE html>\n')
    file.write('<html>\n')
    file.write('<body>\n')

# write html body 
    for row in rows:
        if row.get("src") != None:
            str = '<img src=\"' + row.get("src") + '">\n'
            file.write(str)
# write html footer
    file.write('</body>\n')
    file.write('</html>\n')

# close browser
driver.get('file:///' + path + '/neko.html')

解説

まずはwebdriver.Chrome()でサイトを開き、driver.getでサイトを開きます。

  driver = webdriver.Chrome()
  driver.get('https://www.google.com')

Googleのサイトの検索ボックスに「猫」を入れて検索します。driver.find_element_by_nameで要素(今回だと検索ボックスのnameの値であるq)を指定します。

send_keysでフォームに文字列を入力し、submitで検索します。

neko = driver.find_element_by_name("q")
neko.send_keys("猫");
neko.submit();

検索画面が開いたら「画像」をクリックします。要素はxpathで指定します。

nekoimg = driver.find_element_by_xpath('//*[@id="hdtb-msb-vis"]/div[2]/a')
nekoimg.click()

driver.page_sourceでソースコードを取得し、BeautifulSoupでパースします。

html = driver.page_source
bs = BeautifulSoup(html, "html.parser")

findAllでimgタグのうち、data:image/jpegを含む文字列を検索します。

rows = bs.findAll("img",src=re.compile("data:image/jpeg"))

取得したimgタグをそのまま貼り付けます。

for row in rows:
    if row.get("src") != None:
         str = '<img src=\"' + row.get("src") + '">\n'
         file.write(str)

作成したファイルを開きます。

driver.get('file:///' + path + '/neko.html')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの関数のデフォルト値は後から変更できる

前書き

このように、ミュータブルなデフォルト値を持っている関数pileを定義したとする。

>>> def pile(num, lst=[]):
...     lst.append(num)
...     return lst
...
>>>

このデフォルト値は、当該関数オブジェクト自体が保持している。

>>> pile(1)
[1]
>>> pile(2)
[1, 2]
>>> pile(42)
[1, 2, 42]

他言語に慣れている人から見れば随分奇怪な挙動であるが、これが仕様なのだから致し方ない。
公式リファレンスに至っては、『キャッシュに使えば?』なんて提案をしているほどである。1

デフォルト値の確認

ユーザ定義関数の特殊属性を参照すれば、デフォルト値を確認することができる。

属性 意味
__defaults__ デフォルト値を持つ引数に対するデフォルト値が収められたタプルで、
デフォルト値を持つ引数がない場合には None になります
__kwdefaults__ キーワード専用パラメータのデフォルト値を含む辞書です。

引用元: Python 言語リファレンス » 標準型の階層 » 呼び出し可能型 (一部抜粋)

>>> def func(arg1, arg2=10, *, kwarg1=20, kwarg2=30):
...     pass
...
>>> func.__defaults__
(10,)
>>> func.__kwdefaults__
{'kwarg1': 20, 'kwarg2': 30}

デフォルト値は変更可能である

次のような操作が可能である。

>>> def pile(num, lst=[]):
...     lst.append(num)
...     return lst
...
>>>
>>> pile(1)
[1]
>>>
>>> pile.__defaults__[0].append(42)
>>> pile(2)
[1, 42, 2]

特殊属性__defaults__はタプルであるので、直下の要素を置き換えることはできない。
しかし、丸ごとごっそり書き直す分には何の問題もない。2

>>> pile.__defaults__
([1, 42, 2],)
>>>
>>> pile.__defaults__ = [],
>>> pile(3)
[3]

元々デフォルト値が無い仮引数に影響することすら可能である。

>>> pile.__defaults__ = -1, []
>>>
>>> pile()
[-1]
>>> pile()
[-1, -1]
>>> pile()
[-1, -1, -1]

デフォルト値の割り振られ方

過少あるいは過剰なデフォルト値を与えた場合、それらの割り振られ方は独特である。

>>> def func(arg1, arg2, arg3):
...     print(arg1, arg2, arg3)
...
>>> func.__defaults__ = 1, 2
>>> func()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: func() missing 1 required positional argument: 'arg1'
>>>
>>> func.__defaults__ = 1, 2, 3
>>> func()
1 2 3
>>>
>>> func.__defaults__ = 1, 2, 3, 4
>>> func()
2 3 4
>>>
>>> func.__defaults__ = 1, 2, 3, 4, 5
>>> func()
3 4 5

使いどころ

特に思い浮かばない。
多用する引数のデフォルト値を決めたい場合は、ラッパー関数を書けば良いわけで。


  1. 『ミュータブルなオブジェクトを指定するな』という、真っ当なアドバイスもしている。 

  2. 書き込み可能であるとドキュメントに明記されている。 

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

LIFFと相性がいいのはFlex Message?それともTemplate Message?

LINE botといえばLIFFというLINE botと連携するWebアプリを作りたいときにとても便利なフレームワークです。このLIFFはLINE Developersで設定した独自のURLからアクセスして使うものですが、URLをそのまま使う方法もありますが、例えばbotのやり取りで取得した値をLIFFのリンクの中にパラメータとして付与して動作したいときがあります(つまり、LIFF_URL?value=hogeという形)。botのやり取りの中で動的に生成するときには、リンクのテキストをそのまま返信するよりもLINE botを使ってるならFlexMessageを使ってボタンを押したらリンクにアクセスして、LIFFを立ち上げるなんてことをやってみたいわけですよ。ところが実際にやってみるとハマってしまったので、その覚書です。

FlexMessageを使ってみた

FlexMessageでボタンの実装はやったことありませんでしたが、こちらのシミュレータを使って簡単に作成出来ました。実際に動かしたところがこちらです。

こちらは冒頭で話していたパラメータを付与していないパターンの動作ですが、問題なく動いています。そして、今度は同じFlexMessageでボタンで立ち上げるURLにパラメータを付与すると以下の画面が表示されます。

IMG_6988.png

あれ?LINEアプリの方はすでに最新バージョンになっているのに、これはどういうことでしょう?パラメータを付与するURLはFlex Messageに対応していないのでしょうか?知っている方いらっしゃったら教えて下さい。

色々調べてみたら

やはりFlex Messageにパラメータを付与したURLを使っている事例は見つからず、仕方ないので他の方法を探してみました。すると、Template Messageが見つかりました。Flex Messageとよく似ていますが、こっちは予め決められたテンプレートに対してカスタムを行うメッセージです。こちらにもボタンの中に埋め込んだURLを実行するアクションを入れてみました。先程失敗したパラメータを付与したLIFF URLをこのテンプレートメッセージを入れて再度試してみたら、問題なくLIFFが立ち上がりました。

IMG_6989.png

この動作の違いは一体…

Flex MessageとTemplate Messageではアクションの違いがあることが分かりました。これは中で動作しているプログラムに違いでもあるのでしょうか?とりあえず、現時点での結論としては「LIFF URLをそのまま立ち上げるだけならどちらでもいいが、パラメータを付与したLIFF URLを立ち上げるときにはTemplate Messageのほうが良い」となりました。

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

npx create-nuxt-app でpython2 command exists

自分の環境

mac mojave 10.14.6

$ node -v
v12.4.0

$ npm -v
6.9.0

$ pyenv versions
  system
  2.7.9
* 3.7.2

$ ndenv
ndenv 0.4.0

現象

久々にNuxt.jsでサイトを作ろうとして npx create-nuxt-app を実行したらエラー

エラーメッセージ

gyp ERR! configure error
gyp ERR! stack Error: Command failed: /Users/ユーザー名/.pyenv/shims/python2 -c import sys; print "%s.%s.%s" % sys.version_info[:3];
gyp ERR! configure error
gyp ERR! stack Error: Command failed: /Users/ユーザー名/.pyenv/shims/python2 -c import sys; print "%s.%s.%s" % sys.version_info[:3];
gyp ERR! stack pyenv: python2: command not found
gyp ERR! stack
gyp ERR! stack The `python2' command exists in these Python versions:
gyp ERR! stack   2.7.9
gyp ERR! stack
gyp ERR! stack
gyp ERR! stack     at ChildProcess.exithandler (child_process.js:290:12)
gyp ERR! stack     at ChildProcess.emit (events.js:200:13)
.
.
.
npm ERR! Failed at the fibers@4.0.1 install script.
npm ERR! This is probably not a problem with npm. There is likely additional logging output above.

なんだこれは、、、初めてみたエラーだ

fibers.node というモジュールがインストール失敗している?

原因

エラー内容を見ると、python2が必要らしい?

しかし、自分のデフォルトpythonは3.7.2のpython3を設定している。

このプロジェクト内でpython2を指定したらいいだけ?

解決方法

この方法で無事にインストールできた。

まずプロジェクト内に移動し、pyenv localでpython2を指定する。

$ cd nuxtjs-project
$ pyenv local 2.7.9

$ pyenv versions
  system
* 2.7.9
  3.7.2

そして再度インストールを実行

$ npm install

ズラズラとログが流れ

Installed in `/Users/ユーザー名/dev/nuxtjs-projet/node_modules/fibers/bin/darwin-x64-72/fibers.node`

お、fibers.nodeがインストール&ビルド成功したらしい

この後、$ npm run devで無事にサイトがローカル表示できた。

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

【エラー対処】error command 'clang' failed with exit status 1が発生する

はじめに

pythonでreppyというライブラリをpipインストールしたとき、

error command 'clang' failed with exit status 1

が発生しました。

本当はもっと上にエラーコードがありましたが、ログを取り忘れてしまいました。
申し訳ありません。

環境はMacでPythonのpip時のエラーになります。

対処法

CFLAGS=-stdlib=libc++ pip install reppy
env CFLAGS=-stdlib=libc++ pip install reppy

bashを使っている人は上の方を
fishを使っている人は下の方を実行します。

fishではイコールで変数に代入を行わないためです。
しかし、envコマンドを使えば環境変数を設定しながら、別のコマンドを実行できます。

reppyでは、C++のMakefileを使用しているため、CFLAGSの環境変数が間違っているとどうやらエラーが出るようです。
そのため、以上のように指定することで、きちんとコンパイルしてくれます。

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

3Dアニメーションで見る最小二乗法のこころ

はじめに

最小二乗法はなぜ、差分の二乗和を考えるのか?という疑問について答えるのが本稿の目的である。
ここでは、最小二乗法の一般的な説明
1.データと近似関数値の差分の二乗和を作る。
2.上の二乗和をパラメータごとに偏微分して、その共通零点を求める。
には触れずに、上の1.に至るまでの思想について自分なりに記述してみたい。もちろん、最小二乗法はとても広い応用を持つが、本稿では直線近似に限定して、「最小二乗法のこころ」に迫りたい。

3点を直線近似する

平面にいくつかの点を与えたときに、その点を通る直線はどれだけあるか?ということから考え始めてみる。

1点を通る直線は無限個存在する。

image.png

異なる2点を通る直線は1つだけ存在する。

image.png

互いに異なる3点を通る直線は一般には存在しない。

(もちろん、最初から直線上にある3点を選んだとすれば直線が存在する。)
image.png
純粋な幾何学ならば「ここから先は考えない!」と言い切っても良いだろう。ただ、応用的な立場に立つならば「3点をちょうど通る直線は存在しなくても、例えば上図の赤い直線のようにできるだけ3点に近い直線を求めたい。」ということになる。そこで、直線近似の出番である。

素朴に考えてみる

上図の$P_1$と$P_2$に直線を近づけると$P_3$からは遠ざかる。
また、$P_2$と$P_3$に直線を近づけると$P_1$からは遠ざかり、
同様に$P_3$と$P_1$に直線を近づけると$P_2$からは遠ざかる。
どうも$P_1,P_2,P_3$を別々に考えていると、あちらを立てればこちらが立たずとなってしまい、考えが発散しそうになる。また「できるだけ3点に近い直線」とは何か?...等々思いめぐらしてみた。

最小二乗法のこころに迫る

そして、次のことが大事であると思うようになった。
平面上の3点 $P_1,P_2,P_3$を一組の対象(一つの点)として考えること
これは、直線をいい具合に$P_1,P_2,P_3$に近づけるために必要と考えた。そして、これができれば「できるだけ3点に近い直線」を定義できるのではないか?
そこで、座標系を導入して実際に平面上の3点を1点としてみたい。下の左図ように通常の座標系 $(x,y)$ を導入して、$P_1$、$P_2$、$P_3$の座標がそれぞれ $(x_1,y_1)$、$(x_2,y_2)$、$(x_3,y_3)$であるとする。また、今は直線近似したいので、直線の式を $y=f(x)=ax+b$ と表しておく。さらに、$x=x_1$ における直線上の点を $Q_1$、$x=x_2$ における直線上の点を $Q_2$、$x=x_3$ における直線上の点を $Q_3$ とすれば、座標はそれぞれ$(x_1,f(x_1))$、$(x_2,f(x_2))$、$(x_3,f(x_3))$ となる。

grf1.png

$Q_1$、$Q_2$、$Q_3$ が $P_1$、$P_2$、$P_3$ に近づけば、直線が $P_1$、$P_2$、$P_3$ に近づく。 $Q_i (i=1,2,3)$ と $P_i (i=1,2,3)$ を比較すると、$x$ 座標は共通なので、$f(x_i)(i=1,2,3)$ を $y_i(i=1,2,3)$ に近づければよい。そこで、上の右図のように新たに3次元空間(ここではデータベクトル化空間と呼んでおく)に座標系 $(Y_1,Y_2,Y_3)$ を導入する。そして、 $(y_1,y_2,y_3)$ なるベクトルを考えて$\tilde P$ とする。これで平面上の3点を1点にできた。同様に $(f(x_1),f(x_2),f(x_3))$ なるベクトルを考えて $\tilde Q$ とする。さらに、$\tilde P$ と $\tilde Q$ 2点間の距離を $d$ とする。いよいよ、「最小二乗法のこころ」について述べたい。上で定義してきた記号を用いると、それは、「$\tilde P$ と $\tilde Q$ 2点間の距離 $d$ を最小にするような$a,b$を求めること。」となる。
そして、そのような$a,b$に対応する直線 $y=ax+b$を「できるだけ3点に近い直線」と定義する。
ところで、$d$ は三平方の定理を応用すれば下の式で求められる。

d = \sqrt{(f(x_1)-y_1)^2 + (f(x_2)-y_2)^2 + (f(x_3)-y_3)^2} 

そして、$d$を最小にするのは、$d^2$を最小にするのと同じである。すなわち、

d^2 = (f(x_1)-y_1)^2 + (f(x_2)-y_2)^2 + (f(x_3)-y_3)^2 (差分の二乗和!)を最小にするべき。

かくして、冒頭の「1.データと近似関数値の差分の二乗和を作る。」までたどり着いたことになる。

3Dアニメーションで見てみる。

以上で述べた「最小二乗法のこころ」を可視化するために、Pythonで作成した3Dアニメーションを下に添付した。グラフで用いた記号などは上述してあるので、必要に応じて今までの説明を参照願いたい。
左上:$a,b$ に対する $d^2$ の値をプロットした3Dグラフ
右上:データベクトル化空間に、$a,b$ に対する $\tilde Q$ (紫)をプロットした3Dグラフ。赤点は $\tilde P$ を示す。
左下:$a,b$ に対する直線をプロットしたグラフ
ここでは、最初に与える3点の座標は $P_1:(-7, -8), P_2:(4, 3), P_3:(8, 9)$ としてある。
最小二乗法の解は $a=200/181,b=-92/181$ である。また、アニメーションの1コマごとに$a$の値を変化させている。$b$ については最小二乗法の解$b=-92/181$ に固定してある。
そして、最後の1コマは最小二乗法の解 $a=200/181,b=-92/181$ におけるグラフになっている。resulta.gif(上をクリックすると、アニメーションを再生)

Pythonプログラム

上記の3Dアニメーションを描くPythonプログラムのコードは以下のとおり。

# -*- coding: utf-8 -*-

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.mplot3d import Axes3D

f = lambda a,b,x : a*x+b                    #直線の式
sqfy = lambda a,b,x,y:(f(a,b,x)-y)**2       #近似直線とデータの差分の2乗
d2fy = lambda a,b,x,y:sqfy(a,b,x,y).sum()   #近似直線とデータの差分の2乗

srcdata = [[-7, -8], [4, 3], [8, 9]]        #データ点
ars = np.array(srcdata)
src_x = np.array([ars[0][0],ars[1][0],ars[2][0]]) #データ点のx座標
src_y = np.array([ars[0][1],ars[1][1],ars[2][1]]) #データ点のy座標

#グラフ準備
fig = plt.figure(figsize=4*plt.figaspect(1.0))
plt.subplots_adjust(hspace=0.2)
ax1 = fig.add_subplot(2, 2, 1, projection='3d') #a,b に対する d2 の値をプロット(3Dグラフ)
ax2 = fig.add_subplot(2, 2, 2, projection='3d') #a,b に対する Q~ (紫)をプロット(3Dグラフ)。固定点としては P~ も描画する。
ax3 = fig.add_subplot(2, 2, 3)                  #a,b に対する直線をプロットしたグラフ

imgs = []   #アニメーションのコマの配列
#a,bの値を設定
#最小二乗法の解は、a=200/181,b=-92/181
#最後のデータは、最小二乗法の解に対応するデータとする
#今回は理解しやすいようにbの値は1つに固定しておく
ao=np.arange(0.1,2.1,0.1)
a=np.insert(ao,20,[200/181])
bv=-92/181
b=bv*np.ones(21)

#アニメーションのコマで使うデータ配列
ys0=[]
ys1=[]
ys2=[]
d2=[]
fy0=[]
fy1=[]
fy2=[]

for ma in a:
    ys0.append(src_y[0])
    ys1.append(src_y[1])
    ys2.append(src_y[2])
    d2.append(d2fy(ma,bv,src_x,src_y))
    fy=f(ma,bv,src_x)
    fy0.append(fy[0])
    fy1.append(fy[1])
    fy2.append(fy[2])

# 各グラフのタイトルや軸ラベル
ax1.set_title("Square of distance depends on a&b",fontsize=18)
ax1.set_xlabel("a",fontsize=15)
ax1.set_ylabel("b",fontsize=15)
ax1.set_zlabel("d^2",fontsize=12)

ax2.set_title("(f(x1),f(x2),f(x3)) depends on a&b",fontsize=18)
ax2.set_xlabel("Y1",fontsize=15)
ax2.set_ylabel("Y2",fontsize=15)
ax2.set_zlabel("Y3",fontsize=15)

ax3.set_title("Approximate line depends on a&b",fontsize=18)
ax3.set_xlabel("x",fontsize=15)
ax3.set_ylabel("y",fontsize=15)
ax3.grid(which = "major", axis = "x", color = "green", alpha = 0.8,linestyle = "--", linewidth = 1)
ax3.grid(which = "major", axis = "y", color = "green", alpha = 0.8,linestyle = "--", linewidth = 1)

for i in range(len(a)):
    if (i < len(a)-1 ):
        #軌跡を描画
        img1 = ax1.plot(a[:i+1], b[:i+1], d2[:i+1],marker=".",color="blue")    
        img2 = ax2.plot(ys0[:i+1],ys1[:i+1],ys2[:i+1],marker="o",color="red")
        img2 = ax2.plot(fy0[:i+1], fy1[:i+1],fy2[:i+1], marker=".",color="violet")
    else:
        #最後の1コマは最小二乗法の解について描画
        img1 = ax1.plot([a[i]], [b[i]], [d2[i]],marker="o",color="blue")    
        img2 = ax2.plot([ys0[i]],[ys1[i]],[ys2[i]],marker="o",color="red")
        img2 = ax2.plot([fy0[i]], [fy1[i]],[fy2[i]], marker="o",color="purple")

    #a,bの値に対応する直線を描画する
    lptX=[]
    lptY=[]
    lptX.append(0)  
    lptX.append(src_x[0])
    lptX.append(src_x[2])
    lptY.append(bv)  
    lptY.append(fy0[i])
    lptY.append(fy2[i])

    img3 = ax3.plot(src_x[0],src_y[0],marker="o",color="red")
    img3 = ax3.plot(src_x[1],src_y[1],marker="o",color="red")
    img3 = ax3.plot(src_x[2],src_y[2],marker="o",color="red")
    img3 = ax3.plot(lptX,lptY,color="violet")

    imgs.append(img1+img2+img3) #コマの情報を追加

anim = animation.ArtistAnimation(fig, imgs, interval=500) #アニメーションの作成
anim.save('resulta.gif', 'imagemagick') #gifファイルに保存

plt.show()

(付録)データベクトル化空間における幾何学と相関係数

上のアニメーションでは $b$ を固定していたが、$a,b$ ともに独立に変化させると、$ \tilde Q:(-7a+b,4a+b,8a+b)$ 全体が作る軌跡は平面($H$ と表記)になる。$d$ が最小値 $d_{min}$になるとき、$ \tilde Q$ は $ \tilde P$ から平面$H$ におろした垂線の足になる。下図参照。vecspc.png
また、$d$ が最小値 $d_{min}$になるとき、$\bar x$ を$x_i (i=1,2,3)$ の平均値、$\bar y$ を$y_i (i=1,2,3)$ の平均値とおくと、$a,b$ は下のように表せる。

\begin{eqnarray}
a &=& \frac{\sum_{i=1}^{3}(x_i - \bar x)(y_i - \bar y)}{\sum_{i=1}^{3}(x_i - \bar x)^2}\\
b &=& \bar y - a\bar x
\end{eqnarray}

上の式を用いて、さらに${d_{min}}^2$ を計算すると、下のようになり、相関係数と関係があることが分かる。

\begin{eqnarray}
{d_{min}}^2 &=& (\sum_{i=1}^{3}(y_i - \bar y)^2)(1-r^2)\\
ただし、r &=& \frac{\sum_{i=1}^{3}(x_i - \bar x)(y_i - \bar y)}{\sqrt{(\sum_{i=1}^{3}(x_i - \bar x)^2)(\sum_{i=1}^{3}(y_i - \bar y)^2)}} ({x_i}と{y_i}の相関係数)
\end{eqnarray}

このように、最小二乗法のこころには高次元の幾何学が隠れていて、統計量などとも関連を持つ事が分かった。
ここでは、3点を与えたのでデータベクトル化空間は3次元だったが、一般に$N$ 個の点を与えらえた場合にはデータベクトル化空間は$N$次元になる。そして、直線近似を他の関数による近似に置き換えることで、より豊かな高次元の幾何学が現れるのではないか?と期待して、本稿を終わりにする。

参考

1では、アニメーションで軌跡を描く方法を参考にさせていただいた。
2では、複数のグラフを一つのアニメーションに含める方法を参考にさせていただいた。

1.matplotlib で線分をアニメーションさせる
2.matplotlib アニメーション (Jupyter)

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

SimPyで離散事象シミュレーション(1) はじめの一歩

はじめに

これまで離散事象シミュレーションのコードはゼロベースで書くことが多かったんだけど,PythonにSimPyというパッケージがあることを知ったので試してみた.モデルを作る際の基本的な考え方を掴むのに少し手間取ったけど,わかってみるとよくできていると思った.

結論から言うと,これからも使っていこうと思う.ただし,SimPyでシミュレーションモデルを開発するというよりは,Pythonで開発していく際にアドオン的に利用するというようなイメージがおすすめだろう.SimPyは結構歴史のあるパッケージみたいで,ある意味,成熟している(枯れている)とも言えそうだ.その意味でも,派手さはないけど,安心して使えると思われる.

一方,離散事象シミュレーションというと途中経過をアニメーションとして表示する機能やモデル構築を支援するGUIなどを期待したくなるかもしれないが,そうした機能は含まれていない.アニメーションが必要なら自分の好きな外部モジュールを利用すればいいが,モデル構築にGUIを使いたい人には向いていないかもしれない.

自分用の備忘録も兼ねて,何回かに分けて使い方を整理しておこうと思う.なお,これは自分が使いやすいと思う使い方をまとめたものなので,もしかするとSimPyの標準的な使い方とは流儀が異なるかもしれない.それでももし多少でもどなたかの参考になれば幸いだ.

離散事象シミュレーション

離散事象シミュレーションでは,興味の対象となるシステムの状態がなにかしらの事象が起こるのに応じて変化していくと考える.例えば,あるレストランの中の客数に興味があるとすると,それは「来店」と「退店」という2つの事象によって(のみ)増減するというような具合である.

これを実装するためには,対象システムの状態を捉えるモデル,変化のトリガーとなる事象群,そして事象の生起を管理する仕組みが必要になる.特に,この最後の仕組みは,事象とその生起時刻についての情報を時刻順に並べたリスト(イベントカレンダなどと呼ばれる)を用意しておき,その先頭から順に事象を生起させていくような方法を用いる.

イベントカレンダの先頭から事象が取り出されるたびに,まずその生起時刻まで時計を進める.そして,その事象によってシステムの状態がどのように変化するかに対応する処理(来店なら客数を1名増やし,退店なら1名減らす,など)を実行し,次の事象に移るという流れを繰り返して,システムの状態の変化を追跡していく.

SimPyの概要

SimPyの主な構成要素は,「coreモジュールの中にあるEnvironment」,「eventsモジュール」,そして「resources関連のモジュール群」の3つだと考えればよいだろう.また,これらに加えてジェネレータとして実装されるプロセス関数・メソッドが重要な役割を果たす.「resources関連のモジュール群」については次回以降に取り上げることにして,今回はそれ以外の3つについてみていこう.

Environment

Environmentは文字通り,シミュレーションを実行するための環境を提供するものである.典型的には,

env = simpy.Environment()

のようにしてインスタンス化する.シミュレーションモデルを構成する自作のパーツ(例えば,my_component)があれば,それもあらかじめ

env.my_component = my_component

のようにして,env.でアクセスできるようにしておくと便利だと思う.

Environment(のインスタンス,環境env)はイベントカレンダの面倒をみてくれる.env.step()でカレンダの先頭から1つずつ順に事象を生起させていくことができるが,普通は,env.run()でまとめて実行することになるだろう.このとき,env.run(until=時刻)あるいはenv.run(until=事象)として,ある時刻まであるいはある事象が生起するまでシミュレーションを進めるという指定が可能である.

シミュレーションの現在時刻はenv.nowで参照でき,env.peek()で次の事象の生起時刻を確認できる.

eventsモジュール

基礎的な事象はEventクラスで実装されている.このクラスのインスタンス(例えば,事象e)は,

e = simpy.events.Event(env=env)
e = simpy.Event(env=env)
e = env.event()

のいずれかで,上で作成した環境envの中に生成することができる(後ろの2つはショートカット).ただし,生成しただけでは事象eは環境envのイベントカレンダには登録されない(し,いつまでたっても生起しない).

事象をイベントカレンダに登録することをトリガーするという.これは,

e.trigger(event)
e.succeed(value=None)
e.fail(exception)

のいずれかで行う.トリガーされた事象は属性triggeredFalseからTrueに変わり,属性okと属性valueにも値が入る.trigger()はこれらの属性を別の事象eventと同じ値にセットする.succeed()は,ok=Trueとし,valueには引数として渡された値を入れる(渡さなければNoneのまま),fail()は,ok=Falseとし,valueには引数として渡された例外を入れる.

事象はコールバック関数のリストcallbacksをもっていて,環境envがイベントカレンダから事象eを取り出してそれを生起させる際には,e.callbacksに含まれるコールバック関数を順に実行していく.そしてそれらすべてを実行し終えると,事象eの属性processedTrueにセットする.

このとき,もしe.ok=Falseだった場合は,e.valueに指定されている例外を発生させる.なお,例外を発生させずに,コールバック関数の中で自分で処理したい場合は,e.defused=Trueとしておけばよい.

なお,e.callbacksに入れることができるのは,事象eを唯一の引数としたcallablesのみである.例えば,手動でcallback(e)という関数を追加したいときには,下記のようにすればよい.

e.callbacks.append(callback)

Eventクラスを継承した特殊なクラスもいくつか用意されているが,その中で特によく用いられるのがTimeoutクラスである.これは,

e = simpy.events.Timeout(env=env, delay=delay, value=None)
e = simpy.Timeout(env=env, delay=delay, value=None)
e = env.Timeout(delay=delay, value=None)

のいずれかで生成でき,生成からdelayだけ時間が経過した後に自動的にok=Trueでトリガーされる.要するに,ある時点からの時間経過を知らせるタイマーのアラームのような事象である.例えば,所定の時間かかる作業が終了したことを告げるシグナルなどのために用いられる.

プロセス関数・メソッド(まとめてプロセス)

離散事象シミュレーションのコーディングスタイルとして,事象に処理をぶら下げるような書き方が考えられる(し,SimPyでも事象にコールバック関数をもたせているから実質的にはそうしていることにもなる)が,プロセス関数・メソッドでは,それとは逆に,処理に事象をぶら下げるような書き方が簡単にできる.

すなわち,プロセスに処理の流れを書いていく際に,ある事象の生起を待つ,あるいは生起する事象やその結果に応じて処理を分岐させる,といったことを直感的に記述することができる.これによって,処理を事象とは別の切り口でモジュール化しやすくなるし,シミュレーションにエージェントを追加することも容易になると思う.

プロセスは,ジェネレータとして実装され,yield文をもつ.プロセスが呼び出されると,yield文まで進み,そこである事象eをyieldして一旦停止する.このとき,停止したプロセスをこのyield文の先からもう一度実行するという関数がe.callbacksに追加される.これによって,事象eがトリガーされた際に(コールバック関数が呼ばれ),このプロセスが再び動き始めるというトリックになっている.

プロセス(仮に,process_func()としよう)には引数として環境envを渡す.そして,次のいずれかによって,環境envに登録する(後ろの2つはショートカット).

p = simpy.events.Process(env, process_func(env))
p = simpy.Process(env, process_func(env))
p = env.process(process_func(env))

これはプロセスを開始するシグナルとなる事象を生成し,トリガーするという処理を行う.なお,この登録処理の戻り値は,Processクラスのインスタンスである.ProcessはEventを継承しているのでこれは特殊な事象であるともいえる.すなわち,上のpを事象として扱うことができる(returnした際にトリガーされたとみなされ,戻り値があればそれがvalueの値となる).

この事象pがトリガーされる前にそのinterrupt()メソッドを呼ぶことで,対応するプロセスprocess_funcを中止することができる.これによって,process_funcがyieldで待っている事象eのコールバック関数のリストからprocess_funcの再起動処理が削除される.また,プロセスprocess_funcに例外simpy.exceptions.Interrupt(cause)が投げ込まれるので,プロセス側でそれを受け取って処理することによって中止時の挙動を指定することができる.このinterrupt()メソッドは事象e自体には影響を与えない(ので,例外処理後にその事象eを再度待ち受けしてもよい).

あるプロセスがyieldする事象eはあらかじめどこか別の場所で生成しておいてもよい.e.valuee.okの値を受け取ってプロセスで利用することもできる(e.okの値を利用する場合はe.defused=Trueにしておくこと).&|を用いて,複数の事象のAND結合やOR結合を待つこともできる.この場合,戻り値はOrderedDictになる.逆に,複数のプロセスが同一の事象eを待つこともできる(その場合は,e.callbacksに登録された順にプロセスが再起動される).

簡単な在庫管理の例

続いて,簡単な具体例をもとに初歩的な使い方をみていこう.なお,この具体例の中でも「resources関連のモジュール群」は使用しない.

今回取り上げるのは,定量発注方式の在庫管理モデルである.同一種類の品物が在庫に保持されていて,そこにランダムに顧客が訪れ,品物を1点ずつ買っていく.在庫管理者は,在庫量(発注済み未入荷のバックオーダがあればそれを含む)がある量(reorder point)以下になった際に新たに所定量(order quantity)の品物を発注する.発注された品物は一定時間(lead time)経過後に在庫に納入される.

先にコードを載せておく.コードの後に解説があるので,それを参照しながら見てみてほしい.

import random
import simpy

class Model():
    def __init__(self, env, init):
        self.env = env
        self.at_hand = init  # how many items you have at hand
        self.losses = 0  # opportunity losses
        self.orders = []  # list of back orders

    @property
    def total(self):
        return sum(self.orders) +self.at_hand

    def send_out(self):
        if self.at_hand > 0:
            self.at_hand -= 1
        else:
            self.losses += 1

    def receive(self):
        if len(self.orders) > 0:
            self.at_hand += self.orders.pop(0)

    def order(self, num):  # num = order quantity
        self.orders.append(num)
        self.env.process(deliverer(self.env))  # activate deliverer

    def report(self):
        print('{}: I have {}, orderd {}, and lost {}. '.format(round(self.env.now), self.at_hand, self.orders, self.losses))

def customer(env):
    while True:
        time_to = random.expovariate(1)
        yield env.timeout(time_to)
        env.model.send_out()
        env.model.report()
        env.stocktake.succeed()  # check inventory level (event)

def manager(env):
    env.stocktake = env.event()  # create first event
    while True:
        yield env.stocktake
        if env.model.total <= 10:  # reorder point = 10
            env.model.order(20)  # order quantity = 20
            env.model.report()
        env.stocktake = env.event()  # create next event

def deliverer(env):
    yield env.timeout(10)  # delivery lead time = 10
    env.model.receive()
    env.model.report()

def main():
    env = simpy.Environment()
    env.model = Model(env, 10)
    env.process(customer(env))
    env.process(manager(env))
    env.run(until=100)

if __name__ == "__main__":
    main()

最初にModelクラスをざっと見ていこう.これは,対象システムのモデルで,SimPyとはほぼ無関係である.在庫量をat_handに,在庫切れによって販売機会損失が生じた回数をlossesに,バックオーダのリストをordersにそれぞれ格納している.また,totalはバックオーダを含む品物の総数をプロパティとして返す.

send_out()は出荷処理に対応しており,在庫が空でない場合に1個出荷(at_handを1減らす)し,空の場合には機会損失数を1増加させている.receive()は入庫処理で,ordersのリストの先頭にある発注量をat_handに加えている.order()は発注処理で,ordersのリストの末尾に新しい発注量を追加している.最後のreport()は,その時点でのシステムの状態をコンソールに表示するメソッドである.

これらの後に記述されている3つの関数,customer()manager()deliverer()がプロセス関数である.それぞれ,顧客,在庫管理者,配送業者に対応すると考えてほしい.

顧客プロセスは,指数分布に従う乱数で到着間隔を指定し,それに対応する長さのTimeout事象を待つことで時間を経過させている.その後,出荷処理,状態表示のメソッドを呼び出した後,棚卸しの事象をトリガーしている.なお,この事象は在庫管理者プロセスの中で先に生成されていたものである.while True:でこの流れが永遠に繰り返されるようになっていることがわかる.

在庫管理者プロセスは,最初に上記の棚卸し事象を生成した後,同様に,while True:の無限ループが用いられている.ループの中では,棚卸し事象を待ち,その結果totalが10未満であれば20個の発注処理のメソッドと状態表示のメソッドを呼び出していることがわかる.ループの最後に,次の棚卸し事象を生成していることにも注意しよう,

なお,これら2つのプロセスはmain関数の中で環境envに登録されている.

配送業者プロセスは,長さ10のTimeout事象を待った後,入庫処理のメソッドを呼び出して終了するプロセスである.このプロセスの登録は,Modelのorder()メソッドの中で行われている.発注処理のたびにそれに対応する配送業者が手配されるというイメージである.

まとめ

今回はSimPyの基本的な機能のうち「resources関連のモジュール群」以外の部分をまとめてみた.簡単な具体例も紹介したので,これで「はじめの一歩」を踏み出すところまではなんとか進めるのではないかと思う.次回以降は,「resources関連のモジュール群」の使い方,簡単なアニメーションの方法,エージェントの導入例,などについてみていきたい.

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

PythonでAdobe illustratorで編集できるfigureを作成する

Jupyter notebookでの作図を論文のFigureに使用したい場合,最終的にはIllustratorでの加工が必要になるときがあります。

そのときのために,Illustratorでの編集が可能なPDFファイルの作成の方法です。
リンク1, リンク2

illustrator-editable-figure.py
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42

#複数ページをPDF保存する際に必要なmodule
from matplotlib.backends.backend_pdf import PdfPages

.............
.............
.............

# 保存の際に下記。見切りが発生する場合は,さらにplt.savefigの引数に bbox_inches='tight' をつけるとよい。
plt.savefig("output-scatter.pdf", transparent=True)


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

Pythonで,Adobe illustratorで編集できるfigureを作成する

Jupyter notebookでの作図を論文のFigureに使用したい場合,最終的にはIllustratorでの加工が必要になるときがあります。

そのときのために,Illustratorでの編集が可能なPDFファイルの作成の方法です。
リンク1, リンク2

illustrator-editable-figure.py
import matplotlib
matplotlib.rcParams['pdf.fonttype'] = 42
matplotlib.rcParams['ps.fonttype'] = 42

#複数ページをPDF保存する際に必要なmodule
from matplotlib.backends.backend_pdf import PdfPages

.............
.............
.............

# 保存の際に下記。見切りが発生する場合は,さらにplt.savefigの引数に bbox_inches='tight' をつけるとよい。
plt.savefig("output-scatter.pdf", transparent=True)


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

無料で使えるツールを調査しよう!

まえがき

日々、大量の情報に埋もれている無料で使用できるツールの情報...
これらの優秀なツールを1つでも知らないというのは、エンジニアとして選択肢を狭めていると思います。
そこで本記事では、そのような情報を収集するプログラムを作成し、整理をして見やすくするところまでを行います。

集めた情報をどう使うかは本記事では紹介しません。あなた次第です。

手段

筆者の感覚では無料ツールに関する情報は、はてなブックマークで知ることが多い気がします。
そこで、はてなブックマーク検索APIで「無料 ツール」を検索した結果を収集し、集計・ソートなどで見やすい形にするというところまでを行います。

使用言語がバラバラかつプログラムがスマートでないのは、筆者の怠慢です。

道筋

  1. はてなブックマーク検索APIを用いて、情報を集める (Ruby)
  2. 見やすい形にする (Python)

情報を集める (Ruby)

ここではRubyを用いて、はてなブックマーク検索APIからの結果をCSVファイルに保存するという処理を行います。
貪欲に集められる限りの情報が欲しいので、はてなブックマークで集められる2009年から2019年まで各日に関してそれぞれAPIを投げます。
今回はカテゴリーが「テクノロジー」もののみ収集します。

hoge.rb
require "rss"
require "uri"
require "nokogiri"

keyword = "無料 ツール"  # 検索ワード

years = 2009..2019
for year in years
    dt = DateTime.new(year, 1, 1)
    days =  dt.leap? ? 366 : 365  # 閏年のときは366日
    days.times do |day|
        sleep(1)  # 負担をかけないよう1秒間隔を空ける
        date_begin = (dt + day).strftime("%Y-%m-%d")
        url = "https://b.hatena.ne.jp/search/title?q={#{keyword}}&sort=popular&mode=rss&date_begin=#{date_begin}&date_end=#{date_begin}"
        begin
            # URLに日本語が混じるのでパーセントエンコーディングを行う
            url = URI.encode(url)
            rss = RSS::Parser.parse(url)
            aritcle_info = rss.items

            aritcle_info.each do |x|
                if x.dc_subject == "テクノロジー"
                    sleep(1)
                    # ブックマーク数を問い合わせる
                    bookmarkcount_link = "http://api.b.st-hatena.com/entry.count?url=#{x.link}"
                    doc_b = Nokogiri::HTML(open(bookmarkcount_link, :read_timeout => 10))
                    bookmarkcount = doc_b.xpath('/html/body/p')[0].children.text.to_i

                    # CSVのフォーマットに即した出力を行う
                    title = x.title.gsub!(",", " ")  # タイトルからコンマを除去
                    if title == nil  # タイトルにコンマがない場合、元のタイトルを保存
                        print x.title
                    else
                        print title
                    end
                    print ","
                    print x.link
                    print ","
                    print bookmarkcount
                    print ","
                    puts date_begin
                end
            end
        rescue => e
            next
        end
    end
end

上記の処理の結果をCSVファイルに保存します。

ターミナル
ruby hoge.rb > hoge.csv

これでhoge.csvに無料ツールに関する情報が収集できました。

収集した情報を見やすくする (Python)

上記で作成したCSVファイルをpandasを用いて、見やすい形に変えます。
はてなブックマークの仕様上、同じ記事が別日で保存されている場合があるので、タイトルが同じものは除去します。

Python
import pandas as pd
df = pd.read_csv('./hoge.csv', header=None)
df = df.rename(columns={0: 'title', 1: 'link', 2: 'bookmark', 3: 'date'})  # カラム名を指定
df = df.drop_duplicates(subset='title') # タイトルが同じものを除去
df.head()

以上の最低限の前処理を行うと下のような結果が得られたと思います。
スクリーンショット 2019-10-21 3.00.42.png

今回は千件以上の情報があるうえに、人によってはいらない情報もあるかもしれません。
そこで、情報を見やすくしましょう。
例えば、年別でブックマーク数でソートしてみます。

Python
df_markdown_sorted = df.sort_values("bookmark", ascending=False)
df_markdown_sorted.query('date.str.startswith("2019")', engine='python').head()

pandasの条件抽出を簡単にするために下記の記事を参考にしています。
pandas.DataFrameの行を条件で抽出するquery

上記の処理で下のような結果が得られたと思います。
スクリーンショット 2019-10-21 3.00.54.png

あとがき・今後の展望

本記事では「無料 ツール」の情報を収集するため、はてなブックマーク検索APIを用いた情報検索と情報整理を行いました。
欲しい情報は得られたでしょうか?
今後の展望としては

  • 収集した記事をクラスタリングし、興味のある分野のクラスタを抽出する。
  • 記事についているタグの情報も収集する

などあるかと思います。

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