20190804のPythonに関する記事は29件です。

畳み込みニューラルネットワークは何を見ているか

1. はじめに

 PyTorchの使い方にも少し慣れてきたので、arXivに公開されているディープラーニング関連の論文の実装にチャレンジをはじめました。まず今回はVisualizing and understanding convolutional networksを実装してみました。本論文はディープラーニングの可視化の文献としてよく参照されているので1、以前から興味がありました。

 本論文の流れは、前半でCNNの可視化手法を中心に取り扱い、後半では構築したモデルのパフォーマンスを取り扱います。有名な論文ということもあり、Web上で日本語の解説もこちらに公開されています。

 本論文の概要に関しては上記リンクの解説の通りなので、本記事では上記解説であまり触れられていない提案された可視化手法の実装に重点を置いて解説してみようと思います。

2. 本論文の概要

 CNNは画像分類において高い精度を実現する一方、なぜそれが可能なのかがはっきりしていないという課題があります。本論文の前半では、中間層や分類器の動作を理解する手段として、CNNの各中間層で入力画像の最も反応している部分を可視化する方法が提案されています。

 CNNでは入力画像から特徴マップが生成されますが、本論文ではその逆のプロセスとして任意の中間層の特徴マップから入力画像を復元する手法を利用します。

 上記の手法では特徴マップ全体が復元対象ですが、ここではさらに工夫を加えます。復元したいCNNの各中間層の特徴マップに対して、活性度が最も高いpixelはそのままにし、それ以外の全てのpixelはすべて0となるように特徴マップを修正します。そしてこの修正された特徴マップに対して、上記手法を適用して入力画像の復元します。

 この復元された入力画像は、中間層で最も反応している部分(=特徴マップにおいてもっとも大きな値を持つpixel)をもとに生成されています。つまり、これは入力画像から対象の中間層でもっとも反応した部分のみを抽出することに相当しています。本プロセスを入力層に近い中間層から順に適用していくことで、入力層側の浅い中間層から出力層側の深い中間層に進むにつれて、入力画像のどの部分に最も反応したかを確認することができます。

 本論文では入力層に近い浅い中間層ほどコーナやエッジなど具体的な形状に強く反応する一方、出力層に近い深い中間層では犬やキーボードや人の顔などの抽象的な形状に反応していることが示されています。

 下図Fig.2(本論文から抜粋)は、入力層に近い浅い中間層のLayer1から出力層に近い深い中間層のLayer5まで上記プロセスを行った結果です。左側の画像が復元された入力画像、右側が元の入力画像になります。深い層ほど抽象的なパターンが抽出できていることがわかります。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33323738332f64393339373363342d323235392d626263632d323038372d3066653433313538343930612e706e67.png

3. 取り組んでみたこと

 次の4節では、中間層の特徴マップから入力画像を復元する提案手法で用いるMaxUnpooling処理およびDeconvolution処理の概要を記載しています。5節では本論文の提案手法を持ちいて各中間層の最も活性度が高いpixelを元に入力画像を復元してみました。どちらもPyTorchの充実したライブラリを利用することで極めて容易に実装できます。

4. MaxUnpooling処理およびDeconvolution処理

 中間層の特徴マップから入力画像を復元する提案手法は、下図のFig.1に概要が記載されています。入力画像にConvolution(畳み込み)処理→ReLU関数→MaxPooling処理を適用して得た特徴マップを入力として、MaxUnPooling処理→ReLU関数→Deconvolution処理を順に適用することで入力画像を再現しています。すなわち復元処理ではConvolution処理にはDeconvolution処理を、ReLU関数には同じReLU関数を、MaxPooling処理にはMaxUnpooling処理を対応させています。

68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f33323738332f61393663356239302d346435342d363066382d373137322d3537336164363931613435362e706e67.png

 ここからは下の入力画像2を元にMaxUnpooling処理およびDeconvolution処理を見てみます。

4-1. MaxUnpooling処理

 MaxUnpooling処理の概要を下図に載せました。左の3x3の入力に対して、カーネルサイズ2x2・ストライド1・パディング0としてMaxPool処理を適用すると中心の2x2の出力が得られます。これにカーネルサイズ2x2・ストライド1・パディング0のMaxUnpooling処理を適用すると、右のような3x3の出力が得られます。

スクリーンショット 2019-08-04 15.49.39.png

 MaxUnpooling処理はMaxPool処理の逆転に相当する処理です。しかし最大値以外の値は0となるため一部の情報は消失しまいます。MaxUnpoolingはPyTorchには既にMaxUnpool2dとして実装済みです。注意点としては、MaxUnpooling処理を行うには、MaxPool処理の適用時に最大値が存在したインデックスをindicesとして取得しておく必要があります。

 MaxUnpooling処理の動作を確認するために、MaxPooling処理を行った画像にMaxUnpooling処理を行い元画像を復元してみました。下に処理の概要を抜粋しました。全体のソースコードはこちらにあります。

    # MaxUnpooling処理の抜粋

    # VGG16モデルの取得
    model = models.vgg16(pretrained=True).eval()
    # indicesを取得するためにreturn_indicesを設定
    for i, layer in enumerate(model.features):
        if isinstance(layer, torch.nn.MaxPool2d):
            layer.return_indices = True

    # VGG16のプーリング層の取得
    maxpooling_layer = model.features[4]
    # MaxPooling処理の実施およびindicesの取得
    maxpooling_result, indices = maxpooling_layer(input_img)
    # 入力画像→MaxPooling処理の可視化
    visualize(maxpooling_result)

    # MaxUnpooling用のパラメータの取得
    kernel_size = maxpooling_layer.kernel_size
    stride = maxpooling_layer.stride
    padding = maxpooling_layer.padding

    # MaxUnpooling層の作成
    unpooling_layer = torch.nn.MaxUnpool2d(kernel_size, stride, padding)
    # indicesを指定してMaxUnpooling処理の実施
    unpooling_result = unpooling_layer(maxpooling_result, indices)
    # 入力画像→MaxPooling処理→MaxUnpooling処理結果の可視化
    visualize(unpooling_result)

 下が入力画像→MaxPooling処理を適用した結果です。MaxPooling処理の最大値のみを取得するという動作のため、全体的に画像が粗くなっているのがわかります。

 下が入力画像→MaxPooling処理→MaxUnpooling処理を続けて行い、入力画像を復元した結果です。元画像の再現とならず画像に欠損が存在しています。
maxunpooling_img.png

4-2. Deconvolution処理

 Deconvolutionの説明はこちらこちらにあります。DeconvolutionはPyTorchには既にConvTranspose2dとして実装済みです。CNNにおいて畳み込み処理(Convolution)を行うと、一般的に出力結果の画像の高さや幅は小さくなりチャネル数が増大するのですが、Deconvolution処理ではその逆になります。

 特に下のように特徴マップの生成に用いたConvolution処理と同じパラメータを用いてDeconvolution処理を行うことで、特徴マップの入力画像と同じチャネル数と高さと幅を持つ画像を取得できます。

 Deconvolution処理の動作を確認するために、Convlution処理を行った画像にDeconvolution処理を行い元画像を復元してみました。下に処理の概要を抜粋しました。全体のソースコードはこちらにあります。

    # Deconvolution処理の抜粋

    # VGG16モデルの取得
    model = models.vgg16(pretrained=True).eval()

    # VGG16の畳み込み層の取得
    conv_layer = model.features[0]
    # 畳み込み処理の実施
    conv_result = conv_layer(input_img)

    # Deconvolution用のパラメータの取得
    in_channels = conv_layer.out_channels
    out_channels = input_img.shape[1]    kernel_size = conv_layer.kernel_size
    stride = conv_layer.stride
    padding = conv_layer.padding

    # Deconvolution層の作成
    deconv_layer = torch.nn.ConvTranspose2d(in_channels, out_channels, kernel_size, stride, padding)
    deconv_layer.weight = conv_layer.weight
    # Deconvolution処理の実施
    deconv_result = deconv_layer(conv_result)

    # 入力画像→Convolution処理→Deconvolution処理結果の可視化
    visualize(deconv_result)

 MaxUnPooling処理と同じく、入力に対してConvolution処理→DeConvolution処理を続けた結果は、元の入力の完全な再現とならず情報の欠落が発生します。

 下が入力画像→Convolution処理→Deconvolution処理を適用して入力画像を復元した結果です。全体的に画像が薄くなっているのがわかります。

deconvolution_img.png

5. 再現確認

 実際に本論文の提案手法を試してみました。ソースコードはこちらにあります。今回の実装ではPyTorchの学習済みモデルのVGG-16を利用しました。VGG−16には下のように全部で5つのMaxPooling層があります。各MaxPooling層経由後に特徴マップから元の入力画像を再現してみました。

vgg16.png

 入力画像は下のアメリカン・ショートヘアーの画像を利用しました。

 入力画像の再現において、A.特徴マップ全体を用いた入力画像の再現と、B.特徴マップ中から値が最大となるpixelのみを用いた入力画像を再現を記載しています。以降の5-1から5-4において上の画像がAに下の画像がBに対応しています。

 AとBのどちらにおいても再現する層が深くなるほど、MaxUnpooling処理やDeconvolution処理を行う回数が多くなるため再現された画像が不鮮明となっています。またCNNの各層において入力画像のどこに最も強く反応したのかを意味する下側のBに着目すると5-3.ではに、5-4.ではに、5-5.ではに反応しているのがわかります。

 これらからCNNの層が深くなるほど、入力画像においてより複雑で抽象度の高いパターンに強く反応していることが可視化されています。

5-1. 1つめのMaxPooling層経由後

4th-layer.png

4th-layer-filtered.png

5-2. 2つめのMaxPooling層経由後

9th-layer.png

9th-layer-filtered.png

5-3. 3つめのMaxPooling層経由後

16th-layer.png

16th-layer-filtered.png

5-4. 4つめのMaxPooling層経由後

23th-layer.png

23th-layer-filtered.png

5-5. 5つめのMaxPooling層経由後

30th-layer.png

30th-layer-filtered.png


  1. なんと本論文の被引用数は2019年7月18日時点で6550もあります! 

  2. 飼い猫のノルウェイジャンです。我が家の長女。 

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

3種類の基底関数によるベイズ線形回帰の実装

はじめに

この記事は古川研究室 Workout_calendar 20日目の記事です。
本記事はベイズ線形回帰の実装をメインに行っています。理論式の方は途中計算を省いていますので、うまく計算したら次の式になるんだな程度にお考え下さい。詳しい式の導出は 最尤推定、MAP推定を用いたパラメトリック回帰(12日目の記事)にて説明します!

実装に必要なベイズ線形回帰の式

まずは、ベイズ線形回帰の予測分布の求め方を説明します。
ベイズ線形回帰は新しい観測値(測定値) $x_{new}$ を入力としたときに出力である推定値 $y_{new}$ の確率を関数のパラメータをデータセットから推定することで求めるものです。

まず、正規分布 $N(x|\mu,σ^2)$ の式は以下になります。
$σ^2=$ 分散$,$ $μ=$ 平均

N(x|\mu,σ^2)=\frac{\displaystyle1}{\displaystyle\sqrt{2π\sigma^2}}\exp\left\{-\frac{\displaystyle(x-μ)^2}{\displaystyle 2σ^2}\right\}

以上を踏まえた上で

X=\left\{(x_{1},y_{1}),(x_{2},y_{2})....(x_{n},y_{n})\right\}

$X$=データセット
$w=$ パラメータ(ベクトルです)
$φ(x)=$ 基底関数(実装では$w,φ(x)$は10次元ベクトルとしてます)

このときの予測分布

$p(y_{new}|x_{new},X) =\underline{N(y_{new}|m^Tφ(x_{new}) ,β+φ(x_{new})^TSφ(x_{new}))}$
を求めていきます。$\beta$ と $φ(x)$はこちらで設定するので、事後分布の平均$m$と分散$S$を求めれば予測分布を求めることができます。
まず事後分布$p(w|X)$を求めます。事後分布$p(w|X)$はベイズの定理より

$p(w|X)=\frac{\displaystyle p(y|x,w)p(w)}{\displaystyle p(y|x)}$

となります。右辺の $p(y|x,w)$ は尤度で $p(w)$は事前分布です。また、どちらともガウス関数に従うと仮定します。予めこちらでパラメータ値を設定する必要があり、設定するパラメータは平均と分散です。計算の簡略化のため平均$μ=0$としてます。

$p(w)=N(w|μ,α^{-1}I)$
パラメーターはガウス関数(平均$μ=0$,分散$=α^{-1}I$)

ここでは$f(x)=w^Tφ(x)$
$p(X|w)=N(y_{n}|f(x_{n}),β^{-1})=\sqrt\frac{β}{2π}\exp\left[-\frac{β}{2}(w^Tφ(x_{n})-y_{n})^2\right]$

よって事後確率は次のようになります。

$p(w|X)\propto p(X|w)p(w)$
$=\left(p(y_{1}|x_{1},w)×…×p(y_{n}|x_{n},w)\right)×p(w)=\left({\displaystyle\prod_{n=1}^{N}p(y_{n}|x_{n},w)}\right)×p(w)$
$\ln p(w|X)=\displaystyle-\frac{1}{2}w^T(\beta\Phi^T\Phi+\alpha I)w+(\beta y^T\Phi)w+const$

ここで、事後確率は平均 $m$ 分散 $S$ の正規分布となるので

$p(w|X)=N(w|m,S)$
となります。対数をとると
$\ln p(w|X)=-\displaystyle\frac{1}{2}(w-m)^TS^{-1}(w-m)+const$

この式を先ほど求めた$\ln p(w|X)$ と係数を比較することで事後確率の平均と分散が求まります。

$S=(\beta \Phi^T\Phi+\alpha I)^{-1}$
$m=(\Phi^T\Phi+\lambda I)^{-1}\Phi^Ty$
$\lambda=\frac{\alpha}{\beta}$

以上により予測分布$\underline{N(y_{new}|m^Tφ(x_{new}) ,β+φ(x_{new})^TSφ(x_{new}))}$が求まります。

$p(y_{new}|x_{new},X) = \displaystyle\int p(y_{new}|x_{new},w)p(w|X)dw=\underline{N(y_{new}|m^Tφ(x_{new}) ,β+φ(x_{new})^TSφ(x_{new}))}$

Pythonで実装していきましょう!

ベイズ線形回帰をPythonで実装しました。実装には参考文献を大いに参考させて頂きました。
実装の流れは以下です。

予測する真の関数を定義
     ↓
真の関数にノイズをのせて15個プロット
     ↓
基底関数を定義(3種類)
     ↓
  計画行列の作製
     ↓
事前分布のパラメーター設定
     ↓
  事後確率を計算
     ↓
    グラフ化

$p(y_{new}|x_{new})=\underline{N(y_{new}|m^Tφ(x_{new}) ,β+φ(x_{new})^TSφ(x_{new}))}$

目標は下線の正規分布を作って$(x_{new},y_{new})$として$x$軸の$0~1$の値と$y$軸の$-1.5~1.5$を代入することです。正規分布の分散と平均に含まれている変数は以下です。$\alpha$ と $\beta$ はハイパーパラメータなので事前にこちらで設定しておく必要があります。

$\Large平均:\large m^Tφ(x_{new})$

$φ(x):$基底関数
$m:$事後確率の平均
$m=(\Phi^T\Phi+\displaystyle\frac{α}{β}I)^{-1}\Phi y$
$α:$パラメータ $w$ の事前確率の分散の逆数
$\Phi=(φ(x_{1}),...φ(x_{n}))$

$\Large分散:\large β+φ(x_{new})^TSφ(x_{new})$

$β:$尤度の分散の逆数
$φ(x):$基底関数
$S:$事後確率の分散
$S=(β\Phi^T\Phi+αI)^{-1}$

これらの値を求めることで、グラフ化していきます。
今回はガウス関数,sin関数,多項式を基底関数としてそれぞれグラフ化しました。実行結果を以下に示します。ガウス関数を基底関数に選んだ時が一番うまくフィッティング出来ています。またベイズ線形回帰では予測した結果の平均、分散がわかるので予測結果にどのくらい自信があるのかが分かります。線形回帰では得られないメリットです。

ここからpythonでのプログラムを示していきます。
まずは恒例のおまじないです。

import numpy as np
import matplotlib.pyplot as plt
import scipy.stats as stats
plt.style.use("ggplot")

つぎにデータ点を作成します。
真の関数は $\cos(3\pi x)$ で、15個のデータ点にはノイズを加えています。

n = 15
X = np.random.uniform(0, 1, n)
T = np.cos(3 * np.pi * X) + np.random.normal(0, 0.1, n)

基底関数は3つ用意しました。(※$\theta$はパラメーターです。)
重みと基底はそれぞれ10個あります。

ガウス関数

y=\theta_{0}\exp\left\{-\frac{\displaystyle(x-μ_{0})^2}{\displaystyle 2σ^2}\right\}+\theta_{1}\exp\left\{-\frac{\displaystyle(x-μ_{1})^2}{\displaystyle 2σ^2}\right\}+...+\theta_{n}\exp\left\{-\frac{\displaystyle(x-μ_{2})^2}{\displaystyle 2σ^2}\right\}

sin関数
$y=\theta_{0}\sin(x\pi)+\theta_{1}\sin(2x\pi)+...+\theta_{n}\sin(nx\pi)$

多項式
$y=\theta_{0}+\theta_{1}x+\theta_{2}x^2+...+\theta_{n}x^n$

def phi(x):#基底関数:ガウス関数
    h = 0.1
    return np.exp(-(x - np.arange(0, 1, 0.1))**2/(2*h **2))

def phi2(x):#基底関数:sin関数
    m = 10
    return np.sin(x*np.pi*np.arange(0,m))

def phi3(x):#基底関数:多項式
    m = 10
    return x**np.arange(0,m)

計画行列を作ります。($\Phi$ のことです)
基底関数が3つあるので計画行列も3つ作ります。

Phi = np.array([phi(x) for x in X])    #ガウス関数の計画行列
Phi2 = np.array([phi2(x) for x in X])  #sin関数の計画行列
Phi3 = np.array([phi3(x) for x in X])  #多項式の計画行列

次にハイパーパラメータを設定します。
これはグラフ化した結果を見ながら、うまくいくように値を設定すればいいです。

M = 10         #基底の数
alpha = 0.01
beta = 9.0

事後分布の平均と分散です。

$m=(\Phi^T\Phi+\displaystyle\frac{α}{β}I)^{-1}\Phi y$
$S=(β\Phi^T\Phi+αI)^{-1}$


#平均
m = beta * S.dot(Phi.T).dot(T)     #ガウス関数の平均
m2 = beta * S2.dot(Phi2.T).dot(T)  #sin関数の平均
m3 = beta * S3.dot(Phi3.T).dot(T)  #多項式の平均

#分散
S = np.linalg.inv(alpha * np.eye(M) + beta * Phi.T.dot(Phi))    #ガウス関数の分散
S2 = np.linalg.inv(alpha * np.eye(M) + beta * Phi2.T.dot(Phi2)) #Sin関数の分散
S3 = np.linalg.inv(alpha * np.eye(M) + beta * Phi3.T.dot(Phi3)) #多項式の分散


では、予測分布を求めましょう。

$p(y_{new}|x_{new})=N(y_{new}|m^Tφ(x_{new}) ,β+φ(x_{new})^TSφ(x_{new}))$

def sigma(x):
    return 1.0/ beta + phi(x).dot(S).dot(phi(x))
def norm(x,y): #ガウス基底関数での予測関数
    return stats.norm(m.dot(phi(x)), sigma(x)).pdf(y)


def sigma2(x):
    return 1.0/ beta + phi2(x).dot(S2).dot(phi2(x))
def norm2(x,y): #sin基底関数での予測関数
    return stats.norm(m2.dot(phi2(x)), sigma2(x)).pdf(y)


def sigma3(x):
    return 1.0/ beta + phi3(x).dot(S3).dot(phi3(x))
def norm3(x,y): #多項式基底での予測関数
    return stats.norm(m3.dot(phi3(x)), sigma3(x)).pdf(y)

最後にグラフ化です。
グラフ化にはまずメッシュを作ります。

x_, y_ = np.meshgrid(np.linspace(0 ,1 ,100), np.linspace(-1.5, 1.5,80))

次にそれぞれの関数を出力していきます。

Z = np.vectorize(norm)(x_,y_)
Z2 = np.vectorize(norm2)(x_,y_)
Z3= np.vectorize(norm3)(x_,y_)
y = [m.dot(phi(x__)) for x__ in x]
y2 = [m2.dot(phi2(x__)) for x__ in x]
y3 = [m3.dot(phi3(x__)) for x__ in x]

plt.figure(figsize=(16, 10))

#ガウス基底関数
plt.subplot(2,2,1)
plt.xlim(0, 1)
plt.ylim(-1.5, 1.5)
plt.pcolor(x_, y_, Z,cmap='jet',alpha=0.2)
plt.colorbar()
plt.scatter(X, T)
plt.plot(np.linspace(0,1), np.cos(3 * np.pi * np.linspace(0,1)), c ="royalblue")
plt.plot(x, y)
plt.title("Gaussian basis function")

#多項式基底関数
plt.subplot(2,2,2)
plt.pcolor(x_, y_, Z3,cmap='jet',alpha=0.2)
plt.colorbar()
plt.xlim(0, 1)
plt.ylim(-1.5, 1.5)
#点のプロット
plt.scatter(X, T)
plt.plot(np.linspace(0,1), np.cos(3 * np.pi * np.linspace(0,1)), c ="royalblue")
#予測関数のプロット
plt.plot(x, y3)
plt.title("Polynomial basis function")


#sin基底関数
plt.subplot(2,2,3)
plt.pcolor(x_, y_, Z2,cmap='jet',alpha=0.2)
plt.colorbar()
plt.xlim(0, 1)
plt.ylim(-1.5, 1.5)
#点のプロット
plt.scatter(X, T)
#予測関数をプロット
plt.plot(x, y2)
plt.title("Sine basis function")


#真の関数プロット
plt.plot(np.linspace(0,1), np.cos(3 * np.pi * np.linspace(0,1)), c ="royalblue")
plt.show()


for m_ in m_list:
    x = np.linspace(0,1)
    y = [m_.dot(phi(x__)) for x__ in x]
    plt.plot(x, y, c = "r")
    plt.plot(np.linspace(0,1), np.cos(3 * np.pi * np.linspace(0,1)), c ="g")


plt.show()

最後に余談ですが、
3次元にプロットするとこうなります。$\cos$関数の山脈ができます。$x,y$軸は$\cos$関数の値を示しています。z軸は分散の逆数です。

3D表示にするには以下のプログラムを加えて下さい。

from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter



fig = plt.figure()
ax = fig.gca(projection='3d')

surf = ax.plot_surface(y_, x_, Z2, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)

# Customize the z axis.
ax.set_zlim(0, 10)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))

# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)

plt.show()

おわりに

本記事では実装をメインに説明してきました。なかなか理論式だけを追っかけてみても、いまいち理解できないところや、ここの値は何を意味しているのかなどの疑問を解消するのは難しいです。私がそうでした。コードをいじってみることで理解を深めていければと思います!

参考文献
https://openbook4.me/sections/1563

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

ABC136反省会

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

NotebookのServerlessバッチジョブ化

はじめに

Jupyter Notebookで作成したPythonコードをAWSマネージドサービスを利用してバッチジョブ化できないかと思い試してみました。

用意したもの

  • AWS CLI
  • Papermill(Notebookの実行に利用)
  • Jupyter Notebook
  • Docker

環境

  • Jupyter Notebookサーバー(EC2)
  • ECS
  • ECR
  • Step Functions
  • CloudWatch

やったこと

  • Notebookファイル作成
  • Dockerイメージ作成
  • S3へのNotebookファイル保存
  • IAMロール作成
  • ECSタスク作成
  • StepFunctionsステートマシーン作成

※ECSクラスターやVPC,Subnetなど諸々は事前に作成していたものを利用しました。

処理フロー

  1. CloudWatchEventsのインプットにPapermillのパラメーターを設定
  2. CloudWatchEventsから10分にStepFunctionsステートマシーンを実行
  3. StepFunctionsステートマシーンからECSタスクを実行
  4. ECSタスクにてPapermillを実行
  5. PapermillからS3にNotebook実行結果が保存される

詳細

Notebookファイル作成

Jupyter Notebook上でパラメーター付きのNotebookファイルを作成
※nteractだとセルの右上から"Toggle Parameter Cell"の選択でパラメーター化できました。
スクリーンショット 2019-08-03 11.09.23.png

パラメーターセル
   msg = "Hello, World!"
プログラム
   print(msg)

Dockerイメージの作成&Push

Dockerfile
FROM python:3

RUN pip install papermill[all]
RUN pip install jupyter
Dokerイメージ作成&Push
$(aws ecr get-login --no-include-email --region ap-northeast-1)
docker build -t xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/papermill ./
docker push xxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/papermill:latest

S3 Bucket作成&Notebookファイルコピー

SAM,INPUT/OUTPUT用のBucketを作成

aws s3 mb s3://${SAM_BUCKET}
aws s3 mb s3://${INPUT_NOTEBOOK}
aws s3 mb s3://${OUTPUT_NOTEBOOK}

S3へファイルをコピー
shell-session
aws s3 cp HelloWorld.ipynb s3://${INPUT_NOTEBOOK}/HelloWorld.ipynb

IAMロール,ECSタスク,StepFunctionsステートマシーン,CloudWatch Events作成

CloudFormationを利用したため、テンプレートファイルを作成

template.yaml
AWSTemplateFormatVersion : '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Parameters:
      S3RolePolicyName:
            Type: String
            Default: hello-s3
      HelloWorldTaskExecutionRoleName:
            Type: String
            Default: hello-taskexec
      HelloWorldECSTaskName:
            Type: String
            Default: HelloWorldTask
      HelloWorldInvocationRoleName:
             Type: String
             Default: HelloWorld-Invocation
      ImageRepoURI:
            Type: String
            Default: xxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/papermill:latest
      InputS3FileURI:
            Type: String
            Default: s3://xxxxxxxxx/HelloWorld.ipynb
      OutputS3FileURI:
            Type: String
            Default: s3://xxxxxxxxx/output.ipynb
      MsgParameter:
            Type: String
            Default: "Hello,Papermill"
      ClusterArn:
            Type: String
            Default: arn:aws:ecs:ap-northeast-1:xxxxxxxxx:cluster/xxxxxxxxx
      SubnetID:
            Type: AWS::EC2::Subnet::Id
            Default: subnet-xxxxxxxxx
Resources:
      #ECSタスクからのS3アクセス用ロール作成
      HelloWorldS3BucketRole:
            Type: AWS::IAM::Role
            Properties:
                  AssumeRolePolicyDocument:
                        Version: 2012-10-17
                        Statement:
                                -
                                    Effect: Allow
                                    Principal:
                                       Service:
                                         - ecs-tasks.amazonaws.com
                                    Action:
                                         - sts:AssumeRole
                  Policies:
                       -
                            PolicyName: !Ref S3RolePolicyName
                            PolicyDocument:
                                 Version: 2012-10-17
                                 Statement:
                                      -
                                         Effect: Allow
                                         Action:
                                           - s3:PutObject
                                           - s3:GetObject
                                           - s3:ListBucket
                                           - s3:DeleteObject
                                           - s3:PutObjectAcl
                                         Resource: "*"
      HelloWorldTaskExecutionRole:
            Type: AWS::IAM::Role
            Properties:
                  AssumeRolePolicyDocument:
                        Version: 2012-10-17
                        Statement:
                                -
                                  Effect: Allow
                                  Principal:
                                     Service:
                                       - ecs-tasks.amazonaws.com
                                  Action:
                                       - sts:AssumeRole
                  Policies:
                       -
                          PolicyName: !Ref HelloWorldTaskExecutionRoleName
                          PolicyDocument:
                             Version: 2012-10-17
                             Statement:
                                  -
                                    Effect: Allow
                                    Action:
                                      - ecr:GetAuthorizationToken
                                      - ecr:BatchCheckLayerAvailability
                                      - ecr:GetDownloadUrlForLayer
                                      - ecr:BatchGetImage
                                      - logs:CreateLogStream
                                      - logs:PutLogEvents
                                    Resource: "*"
      #ECSタスク作成
      HelloWorldECSTask:
            Type: AWS::ECS::TaskDefinition
            Properties:
                  ContainerDefinitions:
                        -
                          Name: !Ref HelloWorldECSTaskName
                          Image: !Ref ImageRepoURI
                          Memory: 500
                          Command:
                                  - "papermill"
                                  - !Ref InputS3FileURI
                                  - !Ref OutputS3FileURI
                  Cpu: 256
                  Memory: 512
                  NetworkMode: awsvpc
                  RequiresCompatibilities:
                        - FARGATE
                  TaskRoleArn: !GetAtt [ HelloWorldS3BucketRole, Arn ]
                  ExecutionRoleArn: !GetAtt [ HelloWorldTaskExecutionRole, Arn ]
      #StepFunctionからのECSタスク実行用ロール作成
      HelloWorldTaskExecution:
            Type: AWS::IAM::Role
            Properties:
                AssumeRolePolicyDocument:
                      Version: 2012-10-17
                      Statement:
                              -
                                Effect: Allow
                                Principal:
                                        Service:
                                                - !Sub states.amazonaws.com
                                Action: sts:AssumeRole
                Policies:
                      -
                        PolicyName: StatesExecutionPolicy
                        PolicyDocument:
                                Version: 2012-10-17
                                Statement:
                                        -
                                          Effect: Allow
                                          Action:
                                            - iam:PassRole
                                            - ecs:DescribeTasks
                                            - events:PutTargets
                                            - events:PutRule
                                            - events:DescribeRule
                                            - ecs:RunTask
                                            - ecs:StartTask
                                            - ecs:StopTask
                                          Resource: "*"
      #StepFunctionsステートマシーン作成
      HelloWorldStateMachine:
          Type: AWS::StepFunctions::StateMachine
          Properties:
               DefinitionString:
                   !Sub
                     - |-
                          {
                                  "Comment": "A Hello World example using an AWS ECS function",
                                  "TimeoutSeconds": 3600,
                                  "StartAt": "HelloWorld",
                                  "States": {
                                       "HelloWorld": {
                                             "Type": "Task",
                                             "Resource": "arn:aws:states:::ecs:runTask.sync",
                                             "Parameters": {
                                                   "LaunchType": "FARGATE",
                                                   "Cluster": "${ClusterArn}",
                                                   "TaskDefinition": "${TaskArn}",
                                                   "Overrides": {
                                                        "ContainerOverrides": [
                                                              {
                                                                  "Name": "${HelloWorldECSTaskName}",
                                                                  "Command.$": "$.Command"
                                                              }
                                                        ]
                                                   },
                                                   "NetworkConfiguration": {
                                                         "AwsvpcConfiguration": {
                                                               "Subnets": [
                                                                     "${SubnetID}"
                                                                     ],
                                                                     "AssignPublicIp": "DISABLED"
                                                          }
                                                   }
                                            },
                                             "End": true
                                        }
                                   }
                             }
                     - { TaskArn: !Ref HelloWorldECSTask }
               RoleArn: !GetAtt [ HelloWorldTaskExecution, Arn ]
      #CloudWatchからのStepFunctionsステートマシーン実行用ロール作成
      HelloWorldInvocationRole:
          Type: AWS::IAM::Role
          Properties:
              AssumeRolePolicyDocument:
                    Version: 2012-10-17
                    Statement:
                         - 
                           Effect: Allow
                           Principal:
                              Service: 
                               - events.amazonaws.com
                           Action:
                               - sts:AssumeRole
              Policies:
                  - 
                    PolicyName: !Ref HelloWorldInvocationRoleName
                    PolicyDocument:
                         Version: 2012-10-17
                         Statement:
                              - 
                                Effect: Allow
                                Action:
                                 - states:StartExecution
                                Resource: !Ref HelloWorldStateMachine
      #CloudWatch Eventsのルール作成
      HelloWorldSchedule:
          Type: AWS::Events::Rule
          Properties:
            Description: ScheduledRule
            ScheduleExpression: "rate(10 minutes)"
            State: ENABLED
            Targets:
               - 
                 Arn: !Ref HelloWorldStateMachine
                 Id: StepFunctionExecV1
                 RoleArn: !GetAtt [HelloWorldInvocationRole, Arn]
                 #InputとしてESCタスクのコマンドを文字列として設定
                 #ここでPapermillのパラメーターを設定
                 Input: !Sub "{\"Command\": [\"papermill\",\"${InputS3FileURI}\",\"${OutputS3FileURI}\",\"-p\",\"msg\",\"${MsgParameter}\"]}"

参考

Beyond Interactive: Notebook Innovation at Netflix : https://medium.com/netflix-techblog/notebook-innovation-591ee3221233

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

【深層強化学習】Double DQN、Dueling Networkを実装してDQNと比較してみた

Pytorchで書いた自作DQNでCartPoleを学習させた記事の続編になります。
DQNの代表的な改良手法であるDouble DQNとDueling Networkを実装し、CartPoleを画像から学習させてみました。実装にはPytorchを使用しています。
本記事では、それぞれのについて簡単に説明し、バニラのDQNの学習結果と比較します。

DQN

前回ではCartPole-v0を学習させましたが、性能比較をしやすくするため、今回はCartPole-v1を学習させます。
v0とv1の違いはエピソードが終わるステップ数で、v0では200ステップ、v1では500ステップになったら成功扱いとなりエピソードが終わります。つまり、長い時間ポールを立たせる必要のあるv1の方が難易度が高いです。

また、前回同様、CartPoleの直近4フレームの画像を状態として学習させています。

CartPole_example.png

このような状態にした理由は、Atariの学習を模倣したかったことと、カートやポールの速度などを状態にしたら簡単に学習出来てしまうため、今回紹介する改良手法とバニラのDQNの差が出なくなることがモチベーションとしたあったからです。

前回からの変更

前回とは実装、学習、評価のやり方を以下のように変更しています。各変更の説明は割愛します。

・学習の回数をエピソード数ではなく、ネットワークの更新回数で管理する
・報酬設計(495ステップ以内にエピソードエンド→-1, それ以外→+0.01)
・2フレーム間連続して同じ行動を取るようにする
・ハイパパラメータの変更(target_update_frequency=5000, update_frequency=1)
・Experience Replayの実装方法
・動画保存などの記録部分を環境のラッパークラスで行う

学習結果

CartPole-v1の強化学習において、1000回のネットワーク更新ごとにgreedy方策によるエピソードを実行し、そのエピソードのステップ数を記録しました。全部で200,000回ネットワークを更新しています。

DQN.png

図の横軸の単位はK(キロ)回であり、薄い青線が記録したステップ数、濃い青線がステップ数を過去20回分の平均を計算して平滑化した値を示しています。episode stepsの値はどのくらい長くポールの直立状態を維持できているかを表しており、更新回数が増えるごとにポールを立てるのがうまくなっていることが分かります。プロット図を見る限りまだ学習が進みそうな感じですが、時間の都合上200,000回のネットワーク更新で打ち切りました。
この結果をベースラインとして、これから紹介する手法を取り入れたときの結果と比較していきます。

※補足ですが、私の環境(CPU:i7-7700、RAM:16GB、GPU:GTX 1070)でここまで学習するのに2時間30分ほどかかっています。

Double DQN

Double DQNとは、DQNにおけるtarget値の計算を改良する手法です。DQNでは、行動価値を推定するネットワークのパラメータとtarget値を計算するネットワークのパラメータを別々にし、行動価値推定用のネットワークのみを学習させ、一定周期で2つのパラメータを同期させます。Double DQNでもこの考え方は同様ですが、計算方法が普通のDQNと違っており、以下その違いについて説明します。

行動価値推定用のネットワーク、target値計算用のネットワークのそれぞれのパラメータを$\theta, \theta^{-}$とし、ネットワークを$Q_{\theta}, Q_{\theta^{-}}: S \times A \rightarrow \mathbb{R}$とします。$S$は状態空間、$A$は行動空間です。
強化学習では状態$s$、行動$a$の行動価値の推定値$Q_{\theta}(s, a)$がtarget値に近づくように学習が行われますが、そのの計算方法がDQNとDouble DQNで異なります。
次の数式において、$R$は報酬、$s'$は次の状態を意味しています。

・DQNでのtarget値の計算

\begin{array}{ll}
{\rm target} &= R + \gamma \, max_{a \in A} Q_{\theta^{-}}(s', a) \\
&= R + \gamma \, Q_{\theta^{-}}(s', {\rm argmax}_{a \in A}Q_{\theta^{-}}(s', a))
\end{array}

target用$Q_{\theta^{-}}$によって得られた行動価値の最大値を計算することで、次の状態$s'$の状態価値を推定しています。

・Double DQNでのtarget計算用のネットワーク

{\rm target} = R + \gamma \, Q_{\theta^{-}}(s', {\rm argmax}_{a \in A}Q_{\theta}(s', a))

行動価値推定用ネットワーク$Q_{\theta}$で得られた最適行動の行動価値をtarget用$Q_{\theta^{-}}$によって計算することで、$s'$の状態価値を推定しています。

論文1によれば、通常のDQNの計算では行動価値の推定が過大評価になりがちなのに対し、Double DQNの計算では過大評価な推定を抑えてより正確に推定されるそうです。
人間で例えるなら、自分で選んだ行動を自分で評価すると甘めな採点になるのに対し、他人が選んだ行動を自分が評価するときはしっかりとした採点になるということでしょうか。

実装

DQNクラスを継承してtarget値を計算するメソッドを書き換えるだけです。


実装(見たい方は展開してください)

DQNとDouble DQNについて、異なる部分(target値の計算)のみ掲載します。

dqn.py
import torch

class DQN(object):
    """
    省略
    """

    def _compute_target(self, reward_batch, next_state_batch, terminal_batch):
        with torch.no_grad():
            max_next_q_value = self.target_function(next_state_batch).max(dim=1, keepdim=True)[0]
            target = reward_batch + self.gamma * (1 - terminal_batch) * max_next_q_value
        return target

class DoubleDQN(DQN):

    def _compute_target(self, reward_batch, next_state_batch, terminal_batch):
        argmax_action_batch = self._compute_q_values(next_state_batch, train=False).max(dim=1, keepdim=True)[1] #勾配を残さずに行動価値を計算する
        with torch.no_grad():
            max_next_q_value = self.target_function(next_state_batch).gather(1, argmax_action_batch)
            target = reward_batch + self.gamma * (1 - terminal_batch) * max_next_q_value
        return target
double_dqn.py
import torch
from .dqn import DQN

class DoubleDQN(DQN):

    def _compute_target(self, reward_batch, next_state_batch, terminal_batch):
        argmax_action_batch = self._compute_q_values(next_state_batch, train=False).max(dim=1, keepdim=True)[1]
        with torch.no_grad():
            max_next_q_value = self.target_function(next_state_batch).gather(1, argmax_action_batch)
            target = reward_batch + self.gamma * (1 - terminal_batch) * max_next_q_value
        return target


学習結果

200,000回ネットワーク更新されるまで強化学習を行い、DQNでの学習結果と比較しました。

DQN_vs_DoubleDQN.png

青線がDQN、赤線がDouble DQNの結果です。Double DQNの方がうまく学習しています。

Dueling Network

Dueling Networkとは、DQNに使用するニューラルネットワークの構造を改良する手法です。
通常のDQNでは、入力を状態、出力を各行動の行動価値とするニューラルネットワークを使用します。前回記事の構成では、入力された状態(=直近4回分の画像)が畳み込み層、全結合層を経て、行動数と同数のユニットを持つ出力層にfowardされ、それぞれのユニットが行動に対応する行動価値を出力していました。

ここで、行動価値を直接推定するのではなく、状態の価値を差し引いた相対的な行動価値を推定するという発想で考えられたのがDueling Networkです。この相対的な行動価値をAdvantage値と呼びます。
Dueling Networkの構成では、畳み込み層の後にネットワークが分岐し、一方は行動数と同数のユニットで各行動に対応するAdvantage値を、もう一方は、1つのユニットで状態価値を出力します。
下図は論文2から引用した図で、上が通常の構成、下がDueling Networkの構成を表しています。

dueling.png

以下、Dueling NetwotkにおけるAdvantage値と行動価値について説明します。
Advantage値とは、行動価値$Q(s, a)$から状態価値$V(s)$を引いたもので、次のように定義されます。

Adv(s, a) := Q(s, a) - V(s)

よって、Dueling Networkから出力された$Adv(s, a)$と$V(s)$から行動価値を$Q(s, a) = V(s) + Adv(s, a)$と計算すれば良いのですが、このままでは$Adv(s, a)$と$V(s)$を一意に決めることが出来ず、学習の性能が悪くなります。

※例えば、$Q(s, a)=1.5$のとき、$V(s) = 1, Adv(s, a) = 0.5$や$V(s) = 0.5, Adv(s, a) = 1$などが考えられ、$Adv(s, a)$と$V(s)$が一意に決まりません。

そこで、実際には次の式で行動価値を計算します。

Q(s, a) = V(s) + Adv(s, a) - \frac{1}{|A|} \sum_{b \in A} Adv(s, b)

この場合、$V(s), Adv(s, a)$が一意に決まるため、上記で述べたような学習性能の悪化を防ぐことが出来ます。

実装

ネットワーク構成の部分のみ掲載します。


実装(見たい方は展開してください)
class DuelingQNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        # In shape : 4x84x84
        self.conv1_layer = nn.Conv2d(4, 32, kernel_size=12, stride=4) #To 32x40x40
        self.conv2_layer = nn.Conv2d(32, 64, kernel_size=6, stride=2) #To 64x18x18
        self.conv3_layer = nn.Conv2d(64, 64, kernel_size=3, stride=1) #To 64x16x16
        self.vstream_layer = nn.Linear(64*16*16, 512)
        self.vout_layer = nn.Linear(512, 1)
        self.astream_layer = nn.Linear(64*16*16, 512)
        self.aout_layer = nn.Linear(512, 2)

    def forward(self, x):
        x = x.view(-1, 4, 168, 168)
        h = self.conv1_layer(x)
        h = F.relu(h)
        h = self.conv2_layer(h)
        h = F.relu(h)
        h = self.conv3_layer(h)
        h = F.relu(h)
        h = h.view(-1, 64*16*16)

        #v_stream
        vh = self.vstream_layer(h)
        vh = F.relu(vh)
        v = self.vout_layer(vh)

        #advantage_stream
        ah = self.astream_layer(h)
        ah = F.relu(ah)
        a = self.aout_layer(ah)
        amean = a.mean(dim=1, keepdim=True)

        y = v + a - amean
        return y


学習結果

200,000回ネットワーク更新されるまで強化学習を行い、DQNでの学習結果と比較しました。

DQN_vs_DuelingNetwork.png

100K回更新以降、Dueling Networkでは高スコアを維持していますが、150K回あたりからスコアが悪化していき、200K回時点ではDQNに抜かれてしまいました。もっと学習を続けることで性能の優越がはっきりするのかもしれません。

おわりに

今回、Double DQNとDueling NetworkをPytorchで実装し、普通のDQNと性能を比較しました。
両者とも簡単に実装できるため、学習の性能を上げたい場合まず取り組んでみるべきものかもしれません。
次回は、Prioritized Experience Replayを実装して学習させてみたいと思います。

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

ラズパイ初心者がRaspberry Pi 3 Model B+とPythonを使ってLチカに挑戦してみた。

Raspberry Pi(通称ラズパイ)を始める方が、一番最初に恐る恐る挑戦するだろう「Lチカ」。
例に漏れず自分も挑戦することにしてみた。

さて、ラズパイこそ初心者ではあるものの、普段電子工作を生業としているので幾分気は楽、だがどうしても最初は不安。
まずは先人の記録をパクらせて参考にさせてもらい、別なアプローチでのLチカも試してみたので学習記録として残しておく。
使用言語はPython。

やりたいこと

下記先人の記録の後半にある、「LEDを点滅させる」動画のようなことをやりたい。

ラズパイでLチカをしてみる(入門編)
レッツラズパイ!〜Lチカ&Lピカをマスターしよう編〜

準備した物

グーグル先生の助けを借り、必要な物としてこれらをリストアップ。
大半の方の手元にないであろう電子工作品類(②~⑤)は1000円程度で揃えられる。

No. 品名 数量 備考
Raspberry Pi 3 Model B+ 1
ブレッドボード 1 小さな物でOK
LED 1 安価な砲弾型
330Ω抵抗 1 小さな物でOK
ジャンパーワイヤー 2 オス~メスタイプ
マウス 1 まずはUSB対応品
キーボード 1 まずはUSB対応品
HDMIケーブル 1 ディスプレイ出力はHDMIのみ
HDMI対応ディスプレイ 1

ラズパイセットアップ

今回購入したのはこちら。
初心者にとっての鬼門である「SDカードへのOSインストール」が、ありがたいことにプロによって完了されている。
安心、安全をお求めの方は是非。
尚、自分はOSインストールが完了されていることに気付かず購入した。

Pi3 B+ スターターキット V3 16GB 透明

もし、OSをインストールされていない場合は下記を参考にしていただくとわかりやすい。

Raspberry PiにOSインストールする
Raspberry Pi 3 Model B+の初回セットアップ(購入から起動まで)

接続

今回は赤枠内の、GPIO17と隣にあるGroundを使用する。
gpio-numbers-pi_mark.png
実際の接続はこのような感じ。(ラズパイの向きが上図と逆なので注意)
sch_flitzing.png

コード作成ツール

初心者用ツールとして、ラズパイにプリインストール(最初からインストール)されている「Thonny Python IDE」というツールでコードを作成していく。

Thonny_only.png

コード記述

ググるとよく出てくるのがこのようなコード。
GPIO17ピンにLEDを接続すれば、コード実行後LEDが点灯し1秒後消灯する。

import RPi.GPIO as GPIO
import time

GPIO.setmode(GPIO.BCM)
GPIO.setup(17, GPIO.OUT)
GPIO.output(17, GPIO.HIGH)
time.sleep(1)
GPIO.cleanup()

これでLチカ完了である。
コピペしてしまえば確認まで1分もかからない。

あっさり終わってしまうのもなんなので、他の手法がないか調べてみた結果、下記を発見。
これは1秒毎にLEDが点滅してくれるもの。

from gpiozero import LED
from time import sleep

led = LED(17)

while True:
    led.on()
    sleep(1)
    led.off()
    sleep(1)

やはりこれもコピペしてしまえば確認まで1分もかからない。

消化不良なので↓ココでさらに調べてみた。
Raspberry Pi Documentation
全て英語なので慣れていないと苦痛だが、

from gpiozero import LED

led = LED(17)

led.blink()

LEDを点滅させる為に8行も記述していたのが、なんとたったの3行で済んだ。
しかも挙動は全く同じである。

ちなみに、カッコの中にパラメータを追加で記述するとLEDの点滅時間、回数を調整可能だ。

led.blink(
on_time=x.x, #ON時間
off_time=x.x, #OFF時間
n=x #点滅回数
)

作動確認

下記のようなコードにし高速でLEDをチカチカさせることに成功。
(動画は2秒程度なのでn=100の意味がない)

from gpiozero import LED

led = LED(17)

led.blink(on_time=0.1, off_time=0.1, n=100)

LED2.gif

おわりに

Python学習の足掛かりとして初めて投稿しようと試みてみたが、やはり慣れない作業に大分時間がかかった。
この投稿で使い方は大体わかったので、引き続きPWMやThermal関連についての調査・学習内容をアウトプットしていきたい。

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

LeetCode / Balanced Binary Tree

ブログ記事からの転載)

[https://leetcode.com/problems/balanced-binary-tree/]

Given a binary tree, determine if it is height-balanced.

For this problem, a height-balanced binary tree is defined as:

a binary tree in which the depth of the two subtrees of every node never differ by more than 1.

Example 1:
Given the following tree [3,9,20,null,null,15,7]:
スクリーンショット 2019-08-02 15.03.05.png
Return true.

Example 2:

Given the following tree [1,2,2,3,3,null,null,4,4]:
スクリーンショット 2019-08-04 20.37.51.png
Return false.

どのsubtree間の深さの違いが1以下であるtreeを、balanced binary treeと言っています。
treeがbalanced binary treeになっているか判定せよという問題です。

解答・解説

解法1

subtreeの深さの違いからbalancedな状態か判定する問題なので、subtreeの深さを取得しながら、左右のsubtreeの深さの違いが1以下であるか判定する、再帰的な処理を書くこととします。
以下ではcheck関数を定義し、(subtreeの深さ, 左右のsubtree間の深さの差が1以下であるかのboolean)をtupleで返す構造とし、rootがない末端では(0, True)を返すこととして、再帰的な処理をかけます。
isBalanced関数の返り値としては、tupleの2つ目の要素を返すことになります。

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

def check(root):
    if not root:
        return (0, True)
    l_depth, l_balanced = check(root.left)
    r_depth, r_balanced = check(root.right)
    return max(l_depth, r_depth) + 1, l_balanced and r_balanced and abs(l_depth - r_depth) <= 1

class Solution:
    def isBalanced(self, root):
        return check(root)[1]

iterativeなコードはちょっと長くなるので、recursiveが良いと思います。
考え方は同じですが、違う書き方をすると例えば以下の通り。

def check(root):
    if not root: return 0
    l_depth = check(root.left)
    r_depth = check(root.right)
    if l_depth == -1 or r_depth == -1 or abs(l_depth - r_depth) > 1: return -1
    return max(l_depth, r_depth) + 1

class Solution:
    def isBalanced(self, root):
        return check(root) != -1
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3: str型のメソッド 分類別一覧

はじめに

公式リファレンスの組み込みメソッド一覧は基本的にアルファベット順に並んでいますよね。
それが初学者の私には少し見づらかったので、ざっくりと分類ごと(?)にまとめてみました。

本記事はサンプルコードがほとんどで、細かい説明はしていません。
また、一部載せていないメソッドもあります。詳しくはリファレンスを確認してください。

公式

https://docs.python.org/ja/3.6/library/stdtypes.html#str

メソッド分類

分類1:大文字小文字 系 (引数:なし)

'hello world'.capitalize() # 'Hello world'
'hello world'.title()      # 'Hello World'
'hello world'.upper()      # 'HELLO WORLD'
'HELLO WORLD'.lower()      # 'hello world'
'HELLO world'.swapcase()   # 'hello WORLD'

分類2:部分文字列 系(引数:sub)

# 含まれる?
'a' in 'abc' # True
'z' in 'abc' # False

# 出現回数
str.count(sub[, start[, end]])
'aba'.count('a') # 2
'abcdef'.count('abc') # 1

# 最初に出現するインデックス
str.find(sub[, start[, end]])
'Hello, world'.find('o') # 4
'Hello, world'.find('x') # -1

str.index(sub[, start[, end]])
'Hello, world'.index('o') # 4
'Hello, world'.index('x') # ValueError <- findとの違い

# 最後に出現するインデックス
str.rfind(sub[, start[, end]])
'Hello, world'.find('o') # 8
'Hello, world'.find('x') # -1

str.rindex(sub[, start[, end]])
'Hello, world'.index('o') # 8
'Hello, world'.index('x') # ValueError <- findとの違い

# prefixで始まってる?(タプルで複数指定可)
str.startswith(prefix[, start[, end]])
'Hello, world'.startswith('H') # True
'Hello, world'.startswith(('HELLO', 'Hello', 'hello')) # True

# suffixで終わってる?(タプルで複数指定可)
str.endswith(suffix[, start[, end]])
'Hello, world'.endswith('d') # True
'Hello, world'.endswith(('WORLD', 'World', 'world')) # True

分類3:長さ調節? 系(引数:width)

  • width が len(s) 以下なら元の文字列が返される
# 中央寄せ
str.center(width[, fillchar])
'abc'.center(5)      # ' aaa '
'abc'.center(5, '-') # '-aaa-'

# 左寄せ
str.ljust(width[, fillchar])
'abc'.ljust(5, '-')  # 'abc--'

# 右寄せ
str.rjust(width[, fillchar])
'abc'.rjust(5, '-')  # '--abc'

# 0パディング
str.zfill(width)
"42".zfill(5)  # '00042'
"+42".zfill(5) # '+0042'
"-42".zfill(5) # '-0042'

分類4:is 系(引数:なし)

# 大文字? 小文字?
str.islower()
str.isupper()
str.istitle()

# どんな種類の文字?
str.isalpha()
str.isdecimal()
str.isnumeric()
str.isspace()

分類5:除去 系(引数:chars)

  • 引数charsは除去される文字集合を指定する文字列
# 左右から
str.strip([chars])
'  spacious  '.strip() # -> 'spacious'
'www.example.com'.strip('cmowz.') # 'example'

# 左から
str.lstrip([chars])
'  spacious  '.lstrip() # -> 'spacious  '
'www.example.com'.lstrip('cmowz.') # 'example.com'

# 右から
str.rstrip([chars])
'  spacious  '.rstrip() # '  spacious'
'mississippi'.rstrip('ipz') # 'mississ'

分類6:分割 系(引数:sep)

# --リストに分割----------------------

# 最初の出現位置で分割 sepは消える
str.split(sep=None, maxsplit=-1)
'1,2,3'.split(',') # ['1', '2', '3']
'1,2,3'.split(',', maxsplit=1) # ['1', '2,3']

# 最後の出現位置で分割 sepは消える
str.rsplit(sep=None, maxsplit=-1)
'1,2,3'.rsplit(',') # ['1', '2', '3']
'1,2,3'.rsplit(',', maxsplit=1) # ['1,2', '3']

# 改行で分割(\n, \r, \r\n, \v, \fなど多くに対応)
str.splitlines([keepends])
'ab c\n\nde fg\rkl\r\n'.splitlines() # ['ab c', '', 'de fg', 'kl']

# --タプルに分割--------------------

# 最初の出現位置で分割 sepは消えない
str.partition(sep)
'a-b-c'.partition('-') # ('a', '-', 'b-c')
'a-b-c'.partition('x') # ('a-b-c', '', '')

# 最後の出現位置で分割 sepは消えない
str.rpartition(sep)
'a-b-c'.rpartition('-') # ('a-b', '-', 'c')
'a-b-c'.rpartition('x') # ('a-b-c', '', '')

分類7:結合 系(引数:iterable)

str.join(iterable)
'-'.join(['a', 'b', 'c']) # 'a-b-c'
'-'.join(('a', 'b', 'c')) # 'a-b-c'
'-'.join('abc')           # 'a-b-c'

分類8:置換 系(引数:old, new またはtable)

# 複数(単一)文字の換字(1パターン)
str.replace(old, new[, count])
'Hi, Alice'.replace('Alice', 'Bob') # 'Hi, Bob'
'aaaaaa'.replace('a', 'A', 3) # 'AAAaaa'

# 単一文字の換字(複数パターン)
table = str.maketrans({
    'a': '1',
    'b': None,
    'c': 'three',
    'd': None,
    'e': 'FIVE',
})
'abcde'.translate(table) # 1threeFIVE

おわりに

やっぱりアルファベット順じゃなく分類別(?)にまとめられていた方が、さらっと全体を見返すのにはいいかも。

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

Python 文字列系メソッド

はじめに

Pythonの文字列で利用できるメソッドのまとめメモ。
新しく学べば随時アップデートしていきます。

検索系メソッド

find()メソッド

文字列の先頭から検索したい文字列を探し、最初に見つかった位置(0から始まるインデックス)を返す。
見つからなかった場合は−1を返す
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sからある文字列を検索する。
s.find(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.'

#文字列先頭から末尾までで'string'を検索
print(s.find('string'))       # =>10

#文字列’is a’から'string'を検索
print(s.find('string', 5, 8)) # =>-1

rfind()メソッド

findメソッドとは逆に、文字列の末尾から検索を行う。
使い方はfindメソッドと同様。

使い方
#文字列sからある文字列を検索する。
s.rfind(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.That is a string.'

#文字列末尾から先頭までで'string'を検索
print(s.rfind('string'))       # =>27

index()メソッド

find()と同様の動作するが、見つからなかった場合「ValueError」という例外が発生する。

使い方
#文字列sからある文字列を検索する。
s.index(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.'
w = 'This is a word.'

#文字列sの末尾から先頭までで'string'を検索
print(s.index('string'))  # =>10
#文字列wの末尾から先頭までで'string'を検索
print(w.index('string'))  # =>ValueError: substring not found

rindex()メソッド

find()に対するrfind()と同様、indexに対するrindex()。
文字列の末尾から検索を行い、見つからなかった場合「ValueError」という例外が発生する。
使い方はindexメソッドと同様なので省略。

endswith()メソッド

文字列が検索したい文字列で終わっている時Trueを返す。そうでない場合、Falthを返す。
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sがある文字列で終わっているか調べる。
s.endswith(検索したい文字列,開始インデックス,終了インデックス)
s = 'apple'

#文字列sが'e'で終わっているか調べる。
print(s.endswith('e'))  # =>True
#文字列sが'a'で終わっているか調べる。
print(s.endswith('a'))  # =>Falth

startswith()メソッド

文字列が検索したい文字列で始まっている時Trueを返す。そうでない場合、Falthを返す。
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sがある文字列で始まっているか調べる。
s.startswith(検索したい文字列,開始インデックス,終了インデックス)
s = 'apple'

#文字列sが'a'で始まっているか調べる。
print(s.startswith('e'))  # =>True
#文字列sが'e'で始まっているか調べる。
print(s.startswith('a'))  # =>Falth

文字列の分割・連結

split()メソッド

文字列を「区切り文字」で区切り、文字列のリストを作って返す。
区切り文字を指定しない場合、スペース・改行等の空白文字が区切り文字となる。

使い方
#文字列sを「区切り文字」で区切る。
s.split(区切り文字)
例1
fruits = 'apple,banana,peach'

#文字列fruitsを','で区切り、リストを作成。
print(fruits.split(','))  # =>['apple', 'banana', 'peach']
例2
fruits = 'apple banana peach'
#文字列fruitsをスペースで区切り、リストを作成。
print(fruits.split())  # =>['apple', 'banana', 'peach']
例3
fruits = '''
apple
banana
peach
'''
#文字列fruitsを改行で区切り、リストを作成。
print(fruits.split())  # =>['apple', 'banana', 'peach']

join()メソッド

シーケンス中の要素を連結した文字列を返す。

使い方
#シーケンスを文字列sで分割し連結した文字列を返す。
s.join(シーケンス)
fruits = ['apple', 'banana', 'peach']
#リストfruitsの要素を結合する(区切り文字なし)
print(''.join(fruits)) # =>applebananapeach
#リストfruitsの要素を結合する(区切り文字カンマ)
print(','.join(fruits)) # =>apple,banana,peach

文字列の書き換え

stripメソッド

文字列の先頭および末尾から文字列を削除し、削除した文字列を返す。
「削除する文字列」を指定しない場合、スペース・タブ等の空白文字を削除する。

使い方
#文字列sの先頭および末尾から文字列を削除する。
s.strip(削除する文字列)
例1
#文字列fruitsの先頭および末尾から1を削除する。
fruits = '1apple1'
print(fruits.strip('1')) # => apple

複数の文字も削除可能。

例2
#文字列fruitsの先頭および末尾から1を削除する。
fruits = '11111apple11111'
print(fruits.strip('1')) # => apple

ただし、「削除する文字列」に含まれている文字なら削除してしまうことが注意。

例3
#文字列fruitsの先頭および末尾からpineを削除する。
#この場合、'p','i','n''e'が削除対象となる。
fruits = 'pineapple'
print(fruits.strip('pine')) # => appl 

upper()メソッド

文字列の英字小文字を英字大文字に変換し、その結果を返す。

使い方
#文字列sの小文字を大文字に変換する。
s.upper()
#文字列fruitsの英字小文字を大文字にする。
fruits = 'Apple'
print(fruits.upper())   # => APPLE

lower()メソッド

文字列の英字大文字を英字小文字に変換し、その結果を返す。

使い方
#文字列sの大文字を子文字に変換する。
s.lower()
#文字列fruitsの英字大文字を小文字にする。
fruits = 'Apple'
print(fruits.upper())   # => apple

ljust()メソッド

文字列を幅を考慮して左寄せした結果を返す。文字列の幅が指定した値に満たない場合、指定した文字で埋める(埋め草文字)。埋め草文字を指定しない場合、スペースで埋める。
右寄せを行う場合はrjust(),中央寄せを行う場合はcenter()メソッドがある。

使い方
#文字列sの幅を考慮して左寄せする。
s.ljust(, 埋め草文字)
#埋め草文字-として文字列fruitsの幅を7にする
fruits = 'apple'
print(fruits.ljust(7,'-')) #=> apple--
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 文字列メソッド

はじめに

Pythonの文字列で利用できるメソッドのまとめメモ。
新しく学べば随時アップデートしていきます。

検索系メソッド

find()メソッド

文字列の先頭から検索したい文字列を探し、最初に見つかった位置(0から始まるインデックス)を返す。
見つからなかった場合は−1を返す
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sからある文字列を検索する。
s.find(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.'

#文字列先頭から末尾までで'string'を検索
print(s.find('string'))       # =>10

#文字列’is a’から'string'を検索
print(s.find('string', 5, 8)) # =>-1

rfind()メソッド

findメソッドとは逆に、文字列の末尾から検索を行う。
使い方はfindメソッドと同様。

使い方
#文字列sからある文字列を検索する。
s.rfind(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.That is a string.'

#文字列末尾から先頭までで'string'を検索
print(s.rfind('string'))       # =>27

index()メソッド

find()と同様の動作するが、見つからなかった場合「ValueError」という例外が発生する。

使い方
#文字列sからある文字列を検索する。
s.index(検索したい文字列,開始インデックス,終了インデックス)
s = 'This is a string.'
w = 'This is a word.'

#文字列sの末尾から先頭までで'string'を検索
print(s.index('string'))  # =>10
#文字列wの末尾から先頭までで'string'を検索
print(w.index('string'))  # =>ValueError: substring not found

rindex()メソッド

find()に対するrfind()と同様、indexに対するrindex()。
文字列の末尾から検索を行い、見つからなかった場合「ValueError」という例外が発生する。
使い方はindexメソッドと同様なので省略。

endswith()メソッド

文字列が検索したい文字列で終わっている時Trueを返す。そうでない場合、Falthを返す。
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sがある文字列で終わっているか調べる。
s.endswith(検索したい文字列,開始インデックス,終了インデックス)
s = 'apple'

#文字列sが'e'で終わっているか調べる。
print(s.endswith('e'))  # =>True
#文字列sが'a'で終わっているか調べる。
print(s.endswith('a'))  # =>Falth

startswith()メソッド

文字列が検索したい文字列で始まっている時Trueを返す。そうでない場合、Falthを返す。
開始インデックス/終了インデックスは、指定しなければ文字列の先頭/末尾になる。

使い方
#文字列sがある文字列で始まっているか調べる。
s.startswith(検索したい文字列,開始インデックス,終了インデックス)
s = 'apple'

#文字列sが'a'で始まっているか調べる。
print(s.startswith('e'))  # =>True
#文字列sが'e'で始まっているか調べる。
print(s.startswith('a'))  # =>Falth

文字列の分割・連結

split()メソッド

文字列を「区切り文字」で区切り、文字列のリストを作って返す。
区切り文字を指定しない場合、スペース・改行等の空白文字が区切り文字となる。

使い方
#文字列sを「区切り文字」で区切る。
s.split(区切り文字)
例1
fruits = 'apple,banana,peach'

#文字列fruitsを','で区切り、リストを作成。
print(fruits.split(','))  # =>['apple', 'banana', 'peach']
例2
fruits = 'apple banana peach'
#文字列fruitsをスペースで区切り、リストを作成。
print(fruits.split())  # =>['apple', 'banana', 'peach']
例3
fruits = '''
apple
banana
peach
'''
#文字列fruitsを改行で区切り、リストを作成。
print(fruits.split())  # =>['apple', 'banana', 'peach']

join()メソッド

シーケンス中の要素を連結した文字列を返す。

使い方
#シーケンスを文字列sで分割し連結した文字列を返す。
s.join(シーケンス)
fruits = ['apple', 'banana', 'peach']
#リストfruitsの要素を結合する(区切り文字なし)
print(''.join(fruits)) # =>applebananapeach
#リストfruitsの要素を結合する(区切り文字カンマ)
print(','.join(fruits)) # =>apple,banana,peach

文字列の書き換え

strip()メソッド

文字列の先頭および末尾から文字列を削除し、削除した文字列を返す。
「削除する文字列」を指定しない場合、スペース・タブ等の空白文字を削除する。

使い方
#文字列sの先頭および末尾から文字列を削除する。
s.strip(削除する文字列)
例1
#文字列fruitsの先頭および末尾から1を削除する。
fruits = '1apple1'
print(fruits.strip('1')) # => apple

複数の文字も削除可能。

例2
#文字列fruitsの先頭および末尾から1を削除する。
fruits = '11111apple11111'
print(fruits.strip('1')) # => apple

ただし、「削除する文字列」に含まれている文字なら削除してしまうことが注意。

例3
#文字列fruitsの先頭および末尾からpineを削除する。
#この場合、'p','i','n''e'が削除対象となる。
fruits = 'pineapple'
print(fruits.strip('pine')) # => appl 

upper()メソッド

文字列の英字小文字を英字大文字に変換し、その結果を返す。

使い方
#文字列sの小文字を大文字に変換する。
s.upper()
#文字列fruitsの英字小文字を大文字にする。
fruits = 'Apple'
print(fruits.upper())   # => APPLE

lower()メソッド

文字列の英字大文字を英字小文字に変換し、その結果を返す。

使い方
#文字列sの大文字を子文字に変換する。
s.lower()
#文字列fruitsの英字大文字を小文字にする。
fruits = 'Apple'
print(fruits.upper())   # => apple

ljust()メソッド

文字列を幅を考慮して左寄せした結果を返す。文字列の幅が指定した値に満たない場合、指定した文字で埋める(埋め草文字)。埋め草文字を指定しない場合、スペースで埋める。
右寄せを行う場合はrjust(),中央寄せを行う場合はcenter()メソッドがある。

使い方
#文字列sの幅を考慮して左寄せする。
s.ljust(, 埋め草文字)
#埋め草文字-として文字列fruitsの幅を7にする
fruits = 'apple'
print(fruits.ljust(7,'-')) #=> apple--

参考資料

Python3.7.4公式ドキュメント

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

【答え合わせ編】ゴールデンウィークの旅行者数を予測してみた。

はじめに

以前、[機械学習]ゴールデンウィークの旅行者数を予測してみた。というネタを投稿しました。

今回はその答え合わせの投稿です。

なお、技術的な事は何も書かれていません。
ポエムタグをつけておけば許されると聞きました。

前回の記事について

やったこと

観光庁が公開している宿泊旅行統計調査を元に、月ごとの旅行者数を予測する。

謝ること

ライブラリのアップデートで前回の記事と予測結果に差が出てしまいました。
そのため、今回使用する予測は前回の予測数値と若干異なります。誠にごめんなさい。

結果

観光庁発表

※観光庁の発表した「宿泊旅行統計調査」の平成31年5月分第2次速報を元にしています。
今年はゴールデンウィークが長かったためか、5月で調査開始以来最大の宿泊者数だったようです。

予測値との比較

pd.options.display.float_format = '{:.8g}'.format
vs.head()
pred real
Hokkai 2896237.8 3037530
Aomori 481010.21 411970
Iwate 535289.62 535170
Miyagi 806132.11 886800
Akita 348669.11 368860
vs["diff"] = vs["pred"] - vs["real"]
vs["per"] = abs(( vs["diff"] / vs["real"] ) * 100)
print("誤差平均:" + str(vs["per"].mean()))

# 誤差平均:9.430030930263827

誤差の平均は約9.430%でした。
まあ、あまり良くない数値ですね。。。

8月、9月の予測

どうせなので8月、9月の予測値を載せておきます。

8月の予測(前年比)

前年比で空く県:鳥取(-16.63%)、沖縄(-11.59%)、福島(-10.24%)
前年比で混む県:奈良(23.21%)、島根(19.92%)、栃木(18.08%)

Hokkai        1.5461477
Aomori      -0.12899934
Iwate         3.8532742
Miyagi       -1.3618699
Akita        -1.4536219
Yamagata      4.8129373
Fukushima    -10.249967
Ibaragi      0.28800193
Tochigi       18.089495
Gunma         8.0097683
Saitama        5.052978
Chiba        -9.8669406
Tokyo        -1.5686302
Kanagawa     0.82889818
Niigata       4.7295012
Toyama        6.6432742
Ishikawa       5.152687
Fukui        -7.1461065
Yamanashi     7.1176747
Nagano      -0.80464436
Gifu        -0.83231587
Shizuoka     -4.0189035
Aichi        -5.0119414
Mie          -7.4170226
Siga          9.2092603
Kyoto        0.68663971
Osaka        -5.3579359
Hyogo          7.255461
Nara          23.209964
Wakayama     -4.1223054
Tottori      -16.634197
Shimane       19.918169
Okayama       6.9212813
Hiroshima    -2.0764377
Yamaguchi     11.731277
Tokushima   -0.78001178
Kagawa       -4.4419368
Ehime           1.98476
Kouchi        -8.201771
Hukuoka       5.8027727
Saga          11.929003
Nagasaki     0.30879221
Kumamoto      -5.402026
Ohita       -0.31561992
Miyazagi     -2.1906969
Kagoshima    -7.9594635
Okinawa      -11.594459

9月の予想(前年比)

前年比で空く県:福井(-25.28%)、鳥取(-16.10%)、千葉(-16.09%)
前年比で混む県:北海道(29.31%)、長崎(23.54%)、佐賀(19.35%)

Hokkai        29.310405
Aomori        5.7293101
Iwate          9.775667
Miyagi       -12.503692
Akita         8.8918918
Yamagata     -2.7769751
Fukushima     1.4730333
Ibaragi      -1.9782563
Tochigi       17.633472
Gunma         5.8313744
Saitama       2.1632078
Chiba        -16.094461
Tokyo        -5.9573963
Kanagawa     -2.0076661
Niigata       4.7053917
Toyama         15.41432
Ishikawa      14.832828
Fukui        -25.283566
Yamanashi   -0.88744807
Nagano       -7.8685883
Gifu          10.264169
Shizuoka       -6.01285
Aichi        -1.7513369
Mie          -8.7556244
Siga         0.72663133
Kyoto         3.4934475
Osaka         1.5981535
Hyogo         14.590287
Nara          11.561046
Wakayama      10.049818
Tottori      -16.107172
Shimane       15.486279
Okayama      -5.4374055
Hiroshima    -2.6782992
Yamaguchi     13.577108
Tokushima    0.34449741
Kagawa        7.2651328
Ehime         6.7382174
Kouchi       -3.9885319
Hukuoka       6.5202921
Saga          19.358182
Nagasaki      23.540356
Kumamoto     -1.0762426
Ohita         1.4712151
Miyazagi      7.0189591
Kagoshima    -12.592754
Okinawa      -12.570894

夏休みの旅行先は鳥取に決まり!!
自分は9月、北海道に行ってきます。

使用データ

観光庁 宿泊旅行統計調査
観光庁 宿泊旅行統計調査 報道発表資料(PDF) (2019/8/2 アクセス)
観光庁 宿泊旅行統計調査 集計結果(5月)(xlsx) (2019/7/31 アクセス)

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

【初心者向け】python/競プロでローカルでテストしてから提出する場合のテンプレを作りました。

今日は、競プロの環境についてちょっと書いておきたいなと思います。

もう今日プロに参加しはじめて半年以上たちます。

4月5月6月は事情により参加できなかったのですが、最近少し参加しています。
レート推移はこんな感じ。

スクリーンショット 2019-08-04 15.27.37.png

今日は、アルゴリズムがどうこうよりも、競プロのテンプレ的なメモです。

いつもみなさんはテストケースなどはどのように試していますか?
試していない猛者はおいておくとして、僕はvimでコードを書いているので、ローカルでpythonを実行して、入力を貼り付けしてテストしていました。

しかし、これだいぶ効率悪いなと今更ながらに気づき、改めることとなりました。

要件としては、
1. 入力を一回貼り付けたらそれですむようにしたい
2. 外部ファイルにするのが面倒なので、ソースコード内にinputを置いておきたい
3. 提出時はスルーされるように
の3点を満たすように考えました。

色々と説明する前に、一旦完成形を載せます。

import os

def main(N, A):
    # 処理を書く
    return N 


if __name__ == '__main__':
    if 'LOCAL' in os.environ:
        # ローカル実行時のみ通る処理
        # テストしたい入力を入れておく
        input_values = [
                """3
7 6 8
3
                """.strip(),
                """3
12 15 18
6
                """.strip()
                ]
        for input_value in input_values:
            input_value = input_value.split('\n')
            N = int(input_value[0])
            A = list(map(int, input_value[1].split()))
            ans = main(N, A)
            expected_value = int(input_value[2])
            assert ans == expected_value, 'failed: ans: {ans}, exp: {exp}'.format(ans=ans, exp=expected_value)
    else:
        # 実際に提出したときの処理
        N = 'some_input'
        A = 'some_input'
        print(main(N, A))

ポイントとしては、環境変数を利用して、ローカル実行時と、提出時の処理を変えています。
LOCALという環境変数があれば、ローカル実行となります。
これは

LOCAL='なんらかの値 ' python my_code.py

のように実行すると、LOCALというキーで環境変数が設定されるので、最初のif文の中の処理を実行できます。
提出時はLOCALには値がセットされていないので、elseの中の処理が実行されます。

input_valuesに入力値を貼り付けた後、リスト化、数値化などの処理は問題によりけりですが、基本的にはリストの中に入力をstringとして突っ込んで、1つ1つ取り出して上手くやるという感じです。
今回は実際にAtCoderの入力をそのまま貼り付けています
空白とか無駄なものがついてくるので、一応今回はstrip()しています。

最後に、assertを利用してやることで、失敗ケースで止めるようにしました。
その際、""" """内に入力した入力値の1行下に、正解の値を入れるのが良いと思います。
お好みで。

以上、ローカルでpythonで競プロに参加する場合のちょっとしたテンプレでした。

かんたんな問題では利用する必要はあまりありませんが、何回もテストしてから提出するぐらいの問題であれば使えるのかなと思います。

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

PyMC3がうまくいかない場合(numpyのモジュールがないといわれたときの解決法)

概要

ベイズ推論の考え方の説明とそれを行うことのできるPythonライブラリPyMC3を使ってみようとしたらうまいことできなかったため,その対処法を載せる.ちなみにインストール方法は以下に書いてあります
https://docs.pymc.io/

各種バージョン

・OS:windows10
・anaconda環境下
・editor:jupyter notebook

・numpy:1.16.4
・pandas:0.24.2
・pymc3:3.6

後に記述するが

・theano:1.0.3

インストール

インストールは上記に載せたURLの通り

conda install -c conda-forge pymc3

と,anaconda promptを使ってインストールした.(pipとconda混ぜるな危険,windowsユーザーはcondaで入らないときは積極的にconda-forgeを使いましょう)

今回の問題

よーし,これでベイズ推論の検証ができるぞとばかり思い,以下のコードを実行してみた

import numpy as np
import scipy.stats as stats
import pymc3 as pm

import warnings
warnings.simplefilter('ignore')

# 乱数のseed固定
random_state = 0

np.random.seed(random_state)
n_trials = 4
theta_real = 0.35
data = stats.bernoulli.rvs(p=theta_real, size=n_trials)
data

# withブロックの内側すべてが一つのモデル
with pm.Model() as model:
    # モデリング
    theta = pm.Beta('θ', alpha=1., beta=1.) # 事前確率
    y = pm.Bernoulli('y', p=theta, observed=data) # 尤度

    # 推論
    trace = pm.sample(1000, random_seed=random_state)

すると,以下のエラーが出てきた.

AttributeError: module 'numpy.core.multiarray' has no attribute '_get_ndarray_c_version'

pm.Model()でエラーが出た.
どうやらnumpyで関連で怒られているらしい…
調べてみると,https://github.com/pymc-devs/pymc3/issues/3340 ここで言われているようにいろんな人に起きてるみたい.読んでみると,theanoというパッケージを1.0.3から1.0.4にあげないとダメとのことで,やってみた.

conda install theano==1.0.4

これでもダメそう…

結局,numpyを入れ直し,pymc3を3.6から3.7に無理やりあげるとうまくいった.また,pandasやnumpyのバージョンはこれでないといろんなところでエラーが出たので,けっこうデリケートな設計だなPyMC3って感じでした.

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

【物理シミュレーション】単振り子・二重振り子をシミュレーションしてみた!

概要

物理シミュレーションの第一歩ということで、単振り子(pendulum)および二重振り子(double pendulum)をつくってみました.
二重振り子については,軌跡付きでつくってみました.

動作環境

  • Windows10(64bit)
  • Python 3.7.2

単振り子の理論

まずは,単振り子の運動方程式を導きます.
直交座標系の運動方程式を立ててから,角度のみの運動方程式に変形してもいいのですが,
ラグランジアンを使って導いた方がラクでしょう.

振り子が鉛直方向となす角を$\theta$とすると,

運動エネルギー

K=\frac{1}{2}m\dot{x}^2=\frac{1}{2}m{(l\dot{\theta})}^2

ポテンシャル

U=mglcos\theta

ゆえにラグランジアンは

\begin{align}
L & = K - U\\
& = \frac{1}{2}m{(l\dot{\theta})}^2 - mglcos\theta
\end{align}

オイラーラグランジュ方程式に代入して

\begin{align}
\frac{d}{dt}(ml^2(\dot{\theta}^2) + mglsin\theta)  = 0\\
ml^2\ddot{\theta}  = -mglsin\theta\\
\ddot{\theta}  = -\frac{g}{l}sin\theta
\end{align}

となり,$\theta$に関する二階の微分方程式を得る.

Pythonで解くときには,微分方程式は一階の微分方程式にする必要があるが,
一般に$n$階の微分方程式は$n$次の連立微分方程式に変形することができる.

今の場合,以下のように$\theta$に関する二階微分方程式が$\theta$と$\omega$に関する連立一階微分方程式に変形ができる.

\begin{cases}
\dot{\theta} & = \omega\\
\dot{\omega} & = -\frac{g}{l}sin\theta
\end{cases}

微分方程式を解く方法

Pythonで微分方程式を解くためには,scipy.integrateの中のodeintやodeがありますし,
実際この関数を使ったコードはネット上にも多く見られました.
しかし,odeintの公式ドキュメントを見てみると,微分方程式を解くための新しい関数として
scipy.integrate.solve_ivpが推奨されています.
ということで,今回は,solve_ivp()を使っていこうと思います.

使い方を具体例を使って説明していきます。

  • 例1
\frac{dy}{dt}=y\\
y(0)=1

解:$y = exp(t)$

def func (t, y):
    dydt = y
    return dydt

sol = scipy.integrate.solve_ivp(func, [0,20], [1])
t1 = sol.t
y1 = sol.y.T

t2 = np.linspace(0, 20, 100)
plt.plot(t1, y1)
plt.plot(t2, np.exp(t2))
plt.show()

solve_ivp()の第1引数は微分値を返す関数です.つまり,以下の方程式で言う右辺を返り値に持つ関数です.

\frac{dy}{dt} = f(t, y)

solve_ivp()第2引数は時間を,第3引数は初期値を取ります.今回は,$t=0$から$t=20$までとしました.
時間の刻み幅自動で設定されます.これはodeintなどとの違いの一つです.
もし,刻み幅を指定したい場合は例2のようにt_evalキーワードを追加します.

実行結果は以下のようになります.

ex1.png

  • 例2
\frac{d^2y}{dt^2}=-y\\
y(0)=1\\
\frac{dy}{dt}(0)=0\\

解:$y = cos(t)$

例2は2階の微分方程式です.関数funcの中身は上記のようになります.
考え方としては,

y = y_0\\
\frac{dy}{dt} = y_1

として,以下の連立1階微分方程式に帰着させます.

\frac{d}{dt}y_0 = y_1\\
\frac{d}{dt}y_1 = -y_0
def func (t, y):
    dydt = np.zeros_like(y)
    dydt[0] = y[1]
    dydt[1] = -y[0]
    return dydt

t_span=[0,20]
y0 = [0,1]
t = np.linspace(t_span[0], t_span[1], 100)
sol = scipy.integrate.solve_ivp(func, t_span, y0, t_eval=t)
t1 = sol.t
y1 = sol.y[1,:]

t2 = np.linspace(0, 20, 100)
plt.plot(t1, y1, 'o')
plt.plot(t2, np.cos(t2), '--')
plt.show()

実行結果は以下のようになります.

ex2.png

単振り子の実装

それでは,単振り子のシミュレーションを行うプログラムを実装してみましょう!

from numpy import sin, cos
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np

G = 9.8                                 # 重力加速度 [m/s^2]
L = 1.0                                 # 振り子の長さ [m]

# 運動方程式
def derivs(t, state):
    dydt = np.zeros_like(state)
    dydt[0] = state[1]
    dydt[1] = -(G/L)*sin(state[0])
    return dydt

t_span = [0,20]                         # 観測時間 [s]
dt = 0.05                               # 間隔 [s]
t = np.arange(t_span[0], t_span[1], dt)           
th1 = 30.0                              # 初期角度 [deg]
w1 = 0.0                                # 初期角速度 [deg/s]
state = np.radians([th1, w1])           # 初期状態

sol = solve_ivp(derivs, t_span, state, t_eval=t)
theta = sol.y[0,:]
print(np.shape(sol.y))

x = L * sin(theta)       # x = Lsin(theta)
y = -L * cos(theta)      # y = -Lcos(theta)

fig, ax = plt.subplots()

line, = ax.plot([], [], 'o-', linewidth=2) # このlineに次々と座標を代入して描画

def animate(i):
    thisx = [0, x[i]]
    thisy = [0, y[i]]

    line.set_data(thisx, thisy)
    return line,

ani = FuncAnimation(fig, animate, frames=np.arange(0, len(t)), interval=25, blit=True)

ax.set_xlim(-L,L)
ax.set_ylim(-L,L)
ax.set_aspect('equal')
ax.grid()
plt.show()

# ani.save('pendulum.gif', writer='pillow', fps=15)

解説

  • ライブラリのインポート

まずは,必要なライブラリをインポートします.

from numpy import sin, cos
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
  • 運動方程式の記述

続いて運動方程式を記述する関数を定義します.重力加速度はG,振り子の長さはLとしています.

def derivs(t, state):
    dydt = np.zeros_like(state)
    dydt[0] = state[1]
    dydt[1] = -(G/L)*sin(state[0])
    return dydt
  • 初期条件の記述

運動方程式の初期条件を与えます.2階微分方程式なので自由度は2ですね.

th1 = 30.0
w1 = 0.0
state = np.radians([th1, w1])
  • 運動方程式を解く

運動方程式の数値解を求めます.
yは(2,len(t))型の二次元配列なので,今は,1行目の角度$\theta$のみを取得します.
2行目の角速度$\omega$は今回は必要ないです.

sol = solve_ivp(derivs, t_span, state, t_eval=t)
theta = sol.y[0,:]
  • 直交座標系に変換

求まった$\theta$から直交座標系$(x,y)$に変換します.これでデータは得られました.
あとは,これをプロットするだけです.

x = L * sin(theta)
y = -L * cos(theta)
  • 逐次描画していく関数の定義

順次描画していくための関数を定義します.1行目のlineに次々と座標を代入して描画をしていきます.
linewidth=2はlw=2と省略することもできます.'o-'は端点と線分を表します.

line, = ax.plot([], [], 'o-', linewidth=2)

def animate(i):
    x = [0, x[i]]
    y = [0, y[i]]

    line.set_data(x, y)
    return line,
  • アニメーションの関数

最後にアニメーションを実行します.
blitキーワードやintervalキーワードはアニメーションが滑らかになるように設定しています.なくても動きます.
あとは,レイアウトなどを整え,プロットすれば終わりです.

ani = FuncAnimation(fig, animate, frames=np.arange(0, len(t)), interval=25, blit=True)

実行結果

pendulum.gif

二重振り子

では,単振り子でアニメーションの方法はマスターできたと思うので,二重振り子をシミュレーションしてみましょう.
カオスが見れて面白いです!
さらに,単振り子のレベルアップということで軌跡も描画しちゃいましょう.

二重振り子の理論

二重振り子の運動方程式も単振り子同様ラグランジアンを求めて,オイラーラグランジュ方程式に代入すれば求まります.
ここでは,計算が面倒なので結果だけを示します.

運動方程式

(m_1+m_2)l_1\ddot{\theta_1} + m_2l_2\ddot{\theta_2}cos(\theta_1-\theta_2) + m_2l_2\dot{\theta_2}^2sin(\theta_1-\theta_2) + (m_1+m_2)gsin\theta_1 = 0\\
l_1l_2\ddot{\theta_1}cos(\theta_1-\theta_2) + l_2^2\ddot{\theta_2}-l_1l_2\dot{\theta_1}^2sin(\theta_1-\theta_2) gl_2sin{\theta_2} = 0

整理して,

\frac{d\theta_1}{dt} = \omega_1\\
\frac{d\omega_1}{dt} = \frac{m_2l_1\omega_1^2sin\delta cos\delta + m_2gsin\theta_2cos\delta + m_2l_2\omega_2^2sin\delta -(m_1+m_2)gsin\theta_1}{(m_1+m_2)l_1-m_2l_1cos^2{\delta}}\\
\frac{d\theta_2}{dt} = \omega_2\\
\frac{d\omega_2}{dt} = \frac{-m_2l_2\omega_2^2sin\delta cos\delta + (m_1+m_2)gsin\theta_1cos\delta -(m_1+m_2)l_1\omega_1^2sin\delta -(m_1+m_2)gsin\theta_2}{(m_1+m_2)l_1-m_2l_1cos^2{\delta}}

ただし,$\delta = \theta_2-\theta_1$.

二重振り子の実装

それでは,二重振り子のシミュレーションを行うプログラムを実装しています.
matplotlibの公式ドキュメントに二重振り子のシミュレーションプログラムがあるのでそちらを参考にしました.

from numpy import sin, cos
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
import numpy as np
import matplotlib.pyplot as plt


G = 9.8     # 重力加速度 [m/s^2]
L1 = 1.0    # 単振り子1の長さ [m]
L2 = 1.0    # 単振り子2の長さ [m]
M1 = 1.0    # おもり1の質量 [kg]
M2 = 1.0    # おもり2の質量 [kg]

# 運動方程式
def derivs(t, state):

    dydx = np.zeros_like(state)
    dydx[0] = state[1]

    delta = state[2] - state[0]
    den1 = (M1+M2) * L1 - M2 * L1 * cos(delta) * cos(delta)
    dydx[1] = ((M2 * L1 * state[1] * state[1] * sin(delta) * cos(delta)
                + M2 * G * sin(state[2]) * cos(delta)
                + M2 * L2 * state[3] * state[3] * sin(delta)
                - (M1+M2) * G * sin(state[0]))
               / den1)

    dydx[2] = state[3]

    den2 = (L2/L1) * den1
    dydx[3] = ((- M2 * L2 * state[3] * state[3] * sin(delta) * cos(delta)
                + (M1+M2) * G * sin(state[0]) * cos(delta)
                - (M1+M2) * L1 * state[1] * state[1] * sin(delta)
                - (M1+M2) * G * sin(state[2]))
               / den2)

    return dydx

# 時間生成
t_span = [0,20]
dt = 0.05
t = np.arange(t_span[0], t_span[1], dt)

# 初期条件
th1 = 120.0
w1 = 0.0
th2 = -10.0
w2 = 0.0
state = np.radians([th1, w1, th2, w2])

# 運動方程式を解く
sol = solve_ivp(derivs, t_span, state, t_eval=t)
y = sol.y

# ジェネレータ
def gen():
    for tt, th1, th2 in zip(t,y[0,:], y[2,:]):
        x1 = L1*sin(th1)
        y1 = -L1*cos(th1)
        x2 = L2*sin(th2) + x1
        y2 = -L2*cos(th2) + y1
        yield tt, x1, y1, x2, y2

fig, ax = plt.subplots()
ax.set_xlim(-(L1+L2), L1+L2)
ax.set_ylim(-(L1+L2), L1+L2)
ax.set_aspect('equal')
ax.grid()

locus, = ax.plot([], [], 'r-', linewidth=2)
line, = ax.plot([], [], 'o-', linewidth=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)

xlocus, ylocus = [], []
def animate(data):
    t, x1, y1, x2, y2 = data
    xlocus.append(x2)
    ylocus.append(y2)

    locus.set_data(xlocus, ylocus)
    line.set_data([0, x1, x2], [0, y1, y2])
    time_text.set_text(time_template % (t))

ani = FuncAnimation(fig, animate, gen, interval=50, repeat=True)

plt.show()

# ani.save('double_pendulum.gif', writer='pillow', fps=15)

解説

  • 運動方程式の記述
def derivs(t, state):

    dydx = np.zeros_like(state)
    dydx[0] = state[1]

    delta = state[2] - state[0]
    den1 = (M1+M2) * L1 - M2 * L1 * cos(delta) * cos(delta)
    dydx[1] = ((M2 * L1 * state[1] * state[1] * sin(delta) * cos(delta)
                + M2 * G * sin(state[2]) * cos(delta)
                + M2 * L2 * state[3] * state[3] * sin(delta)
                - (M1+M2) * G * sin(state[0]))
               / den1)

    dydx[2] = state[3]

    den2 = (L2/L1) * den1
    dydx[3] = ((- M2 * L2 * state[3] * state[3] * sin(delta) * cos(delta)
                + (M1+M2) * G * sin(state[0]) * cos(delta)
                - (M1+M2) * L1 * state[1] * state[1] * sin(delta)
                - (M1+M2) * G * sin(state[2]))
               / den2)

    return dydx
  • 初期条件の記述
th1 = 100.0
w1 = 0.0
th2 = -10.0
w2 = 0.0
state = np.radians([th1, w1, th2, w2])
  • 運動方程式を解く
sol = solve_ivp(derivs, t_span, state, t_eval=t)
y = sol.y
  • ジェネレータ関数の定義

これは単振り子と少し異なります。軌跡を描くためにジェネレータを定義します。
ジェネレータはreturnではなくyield文で返します.
また,zip()関数を使うことで,並列的に反復処理を行えます.

def gen():
    for tt, th1, th2 in zip(t,y[0,:], y[2,:]):
        x1 = L1*sin(th1)
        y1 = -L1*cos(th1)
        x2 = L2*sin(th2) + x1
        y2 = -L2*cos(th2) + y1
        yield tt, x1, y1, x2, y2
  • アニメーション関数の定義

locusにどんどん値を追加していくことで,軌跡を描画します.
また今回はタイマーもついています.

locus, = ax.plot([], [], 'r-', linewidth=2)
line, = ax.plot([], [], 'o-', linewidth=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)

xlocus, ylocus = [], []
def animate(data):
    t, x1, y1, x2, y2 = data
    xlocus.append(x2)
    ylocus.append(y2)

    locus.set_data(xlocus, ylocus)
    line.set_data([0, x1, x2], [0, y1, y2])
    time_text.set_text(time_template % (t))
  • アニメーションの実行
ani = FuncAnimation(fig, animate, gen, interval=50, repeat=False)

実行結果

double_pendulum.gif

まとめ

いかがだったでしょうか?今回は物理シミュレーションとして,単振り子と二重振り子を扱ってみました.
二重振り子のカオスが見れたときは興奮しますよね.
今回のコードに関して改善点などありましたらコメントいただけると助かります.

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

python×Cloud Functions×FirestoreでAPIを簡単に作ってみる Pt2 テスト編(詳細)

概要

Firestoreのデータ操作を行う関数をローカルでテストするためのあれこれをまとめます。やり方としては、Flaskのrequestオブジェクトを使ってローカルにAPIを作成します。

開発環境

  • 開発環境
    • MacOS
    • python3.7
    • pycharm

新規プロジェクトの作成

pycharmまたはpyvenvコマンドを用いて新規プロジェクトを作成します。画面イメージはpycharmですが、デフォルトで作成されるフォルダに加えて「.auth」「functions」も作成しています。また、ファイルは省略します。

スクリーンショット 2019-08-04 16.17.56.png

関数の作成

テストする関数はPt1と同じです。functions/に置きます。

functions/getFilteredUser.py
import json
from google.cloud import firestore

# ソースはPt1と同じ
def get_filtered_user(request):

    request_json = request.get_json()
    if request.args and 'name' in request.args:
        request_name = request.args.get('name')
    elif request_json and 'name' in request_json:
        request_name = request_json['name']
    else:
        request_name = ''

    db = firestore.Client()

    query = db.collection('user').where('name', '==', request_name)
    docs = query.get()
    users_list = []
    for doc in docs:
        users_list.append(doc.to_dict())
    return_json = json.dumps({"users": users_list}, ensure_ascii=False)

    return return_json

ローカルにAPIを作る

Cloud FunctionsではFlaskのrequestオブジェクトを使って、リクエスト条件が関数に受け渡しされます。なので、ローカルにFlaskを使ってAPIを作ることで、テスト可能になります。

以下のようなソースを作ります。

localServer.py
from flask import Flask, request, abort, render_template, send_from_directory
# 作成した関数を外部ソースからimport
from functions.getFilteredUser import get_filtered_user

app = Flask(__name__)

# methodにPOST等も明示的に指定(FlaskではデフォルトGETのみのため)
# '/~'がテストするときのパス
@app.route('/get-filtered-user', methods=['GET', 'POST'])
def local_api():
    return get_filtered_user(request)

if __name__ == '__main__':
    app.run()

これでhttp://localhost:5000/get-filtered-userにリクエストを投げると、get_filtered_userが実行されるようになります。
但し、関数がローカルからFirebaseにアクセスするには、秘密鍵を環境変数に読み込ませる必要があります。

秘密鍵の取得・設定

秘密鍵の取得方法は以下のいずれかのページがわかりやすいです。

Cloud FirestoreのデータをPythonで取得する
Firebase公式 - SDK の初期化

取得したファイルのパスを、GOOGLE_APPLICATION_CREDENTIALSの環境変数に設定することでローカルからのFirestoreへのアクセスが可能になります。環境変数の設定は以下のような方法があります。

[方法1] pycharmの「構成の編集>環境変数」から追加
pycharmではこのやり方が使えます。ソースごとに環境変数が設定可能なので、複数のFirebaseプロジェクトを扱う場合でも、bash_profileを書き換える必要がなくなります。
スクリーンショット 2019-07-17 1.03.38.png

この例では「.auth/firebase_auth.json」という秘密鍵ファイルを読み込ませています。

[方法2] ターミナルから環境変数を設定

MacOS
$ export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"
# .bash_profileに追記する場合 (ターミナルの再起動が必要)
$ echo 'export GOOGLE_APPLICATION_CREDENTIALS="<保存先>/<秘密鍵ファイル>.json"' >> ~/.bash_profile

作成したソースをターミナルからpython sample.pyと実行する場合、このやり方が一番シンプルです。

[方法3] pythonのソースにベタ書き

os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = "<保存先>/<秘密鍵ファイル>.json"

ソースの中に書きたい事情がある場合はこれでもいけると思います。

ディレクトリの最終状態

functionsと.authを含めて以下の状態になっているはず。functions/getUser.pyはPt1のソースが残ってるだけなので無くても問題ありません。
スクリーンショット 2019-08-04 16.23.48.png

ローカルでのテスト

環境変数を設定した状態で、localServer.pyを実行してhttp://localhost:5000/get-filtered-userにリクエストを投げます。

localServer.pyの実行
$ python localServer.py
 * Serving Flask app "localServer" (lazy loading)
 * Environment: production
   WARNING: This is a development server. Do not use it in a production deployment.
   Use a production WSGI server instead.
 * Debug mode: off
 * Running on http://127.0.0.1:5000/ (Press CTRL+C to quit)
127.0.0.1 - - [04/Aug/2019 14:51:35] "POST /get-filtered-user HTTP/1.1" 200 -

ローカルでの関数テスト
$ curl -X POST -H "Content-Type: application/json" -d '{"name": "Alice"}' http://localhost:5000/get-filtered-user
{"users": [{"age": 19, "name": "Alice", "gender": "female"}]}

これで、Cloud Functionsにデプロイせずともテストできるようになりました。デプロイすると2分ほど待ちますが、この方法だとすぐにテストできます。localServer.pyのコンソールにはprintの内容が出力されるのでデバッグも容易になります。

また、pycharmの場合、pythonの実行とターミナルを並べて実行すれば便利です。
スクリーンショット 2019-08-04 15.22.04.png

もちろんPostmanを使ってもOKです。

おわりに

Cloud Functionsではいきなりデプロイするのではなく、以下のような流れでコードを作成すると、どこでバグってるかがわかりやすいので、ハマることが少なく開発できると思います。

  1. デプロイしたい関数だけでテスト
  2. ローカルにAPIを作成してテスト
  3. Cloud Functionsにデプロイ

1では、リクエスト条件などをベタ書きで書いてみて、関数が想定通り動くか確認します。2では、GETやPOSTでリクエストが渡された時の動きを確認できます。

今回は、この中の2のやり方を書きました。

Pt3では、3のデプロイをローカルから行う方法を書こうと思います。

参考リンク

How to develop Python Google Cloud Functions
ローカルでの関数の実行
MacでCloud Machine Learning Engineを利用してみる

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

Gridgedge [Aizu Competitive Programming Camp 2018 Day 2 D]

Gridgedge [Aizu Competitive Programming Camp 2018 Day 2 D]

ダイクストラ法。全ての地点に関して距離と経路数をそれぞれ記録しておく。距離テーブルをdist、経路数テーブルをpathとする
(x,y)から(nx,ny)に移動する時、
1. dist[y][x]+1 < dist[nx][ny]の時(新記録更新した場合)path[ny][nx]=path[y][x]
2. dist[y][x]+1 = dist[nx][ny]の時、(同じ経路長の場合)path[ny][nx]+=path[y][x]
3. dist[y][x]+1 < dist[nx][ny]の時、(より短い経路長ですでに到達していた場合)探索打ち切り。

https://onlinejudge.u-aizu.ac.jp/beta/room.html#ACPC2018Day2/problems/D

r, c, sx, sy, gx, gy=list(map(int,raw_input.split()))
dist=[[np.inf for i in range(r)] for j in range(c)]
path=[[0 for i in range(r)] for j in range(c)]
que=deque()
que.append((sy,sx))
dist[sy][sx]=0
while que:
    y, x=que.popleft()
    for i,j in ((y,x+1),(y+1,x),(y-1,x),(y,x-1),(c-1,x),(y,r-1),(0,x),(y,0)):
        ny,nx=i,j
        if 0<=ny<c and 0<=nx<r:
            if dist[ny][nx]<dist[y][x]+1:
                continue
            if dist[ny][nx]>dist[y][x]+1:
                dist[ny][nx]=dist[y][x]+1
                path[ny][nx]=1
            else:
                path[ny][nx]+=1
            que.append((ny,nx))


print(dist[gy][gx],path[gy][gx])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ultra-fast build of Tensorflow with Bazel Remote Caching [Google Cloud Storage version]

Tensorflow-bin GitHub stars

Bazel_bin GitHub stars

1.Introduction

今回は Google Cloud Storage をキャッシング環境に使用した最もお手軽なビルド手順を試行しました。 1時間掛かる Tensorflow のビルドを 2分20秒ほどに短縮できます。 ビルド済みのバイナリとソースファイルをハッシュ化してストレージ上にキャッシュし、2回目以降のビルド時にはキャッシュ済みのハッシュ値と現ファイルから計算したハッシュ値を比較して同じであればビルドを簡略化し、ハッシュ値に差分のあるファイルのみコンパイルします。 よって、初回ビルド時はキャッシュが全く無くキャッシングの処理に余分な時間を割くため、通常のビルドよりも少しだけ遅くなります。 検証の結果、Tensorflow がバージョンアップした場合はキャッシングのベースとなっているハッシュ値が完全に異なるようで、うまくキャッシュが効きませんでした。 試行錯誤しながらビルドバラメータを頻繁に変更したり、とあるソースファイルのバグフィックスを単発で実施した場合のリビルドの場合にはかなり強力なパフォーマンスを発揮します。 複数の開発者間でビルド済みのバイナリを共有してデバッグ効率を少しだけ上げるような用途には向いていそうです。 次回は、nginxを使用した完全無料ローカルキャッシュ環境構築にトライしてみようと思います。

Build Tensorflow super fast using Bazel's Remote Caching feature. It is 24 times faster than the standard build method. However, according to the examination result of this article, it seems that the difference in the version upgrade of the repository can not be compensated. Provides a shared and incremental compilation environment for pre-built binaries among multiple developers. Next time, I would like to try the local cache procedure by nginx.

2.Environment

  • Ubuntu 16.04 x86_64
  • Tensorflow v1.13.2 + Bazel 0.20.0
  • Tensorflow v1.14.0 + Bazel 0.24.1
  • Google Cloud Storage

bazel-caching (1).png

3.Prepare Google Cloud Storage for Caching

You need a Google Cloud account with billing enabled.

3−1.Create storage bucket

https://docs.bazel.build/versions/master/remote-caching.html#google-cloud-storage

FireShot Capture 056 - ストレージ バケットの作成  -  Cloud Storage  -  Google Cloud - cloud.google.com.png
FireShot Capture 057 - 参照 - My First Project - Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 058 - Storage – My First Project – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 059 - Storage – My First Project – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 060 - Storage – My First Project – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 061 - Storage – My First Project – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 062 - バケットの詳細 - My First Project - Google Cloud Platform - console.cloud.google.com.png

3−2.Create service account

https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating_a_service_account

FireShot Capture 063 - サービス アカウントの作成と管理  -  Cloud Identity and Access Management のドキュメント  - _ - cloud.google.com.png
FireShot Capture 064 - サービス アカウント – IAM と管理 – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 065 - サービス アカウント – IAM と管理 – Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 066 - サービス アカウント – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 067 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 068 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 070 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 071 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 072 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 074 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png

A json file with a name such as xxxxx-xxxxxxxxxxxx.json will be downloaded automatically.

FireShot Capture 075 - サービス アカウントの作成 – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png
FireShot Capture 076 - サービス アカウント – IAM と管理 – My First Project – Google Cloud Platform_ - console.cloud.google.com.png

3−3.Preparation for connection to Remote cache (Google Cloud Storage)

$ cd ~
$ mkdir bazel-caching
$ mv ~/Downloads/xxxxx-xxxxxxxxxxxx.json ${HOME}/bazel-caching

4.(First time) Building Tensorflow v1.13.2

$ sudo apt-get install -y libhdf5-dev libc-ares-dev libeigen3-dev
$ sudo pip3 install keras_applications==1.0.7 --no-deps
$ sudo pip3 install keras_preprocessing==1.0.9 --no-deps
$ sudo pip3 install h5py==2.9.0
$ sudo apt-get install -y openmpi-bin libopenmpi-dev
$ sudo -H pip3 install -U --user six numpy wheel mock
$ sudo apt update;sudo apt upgrade

$ cd ~
$ git clone https://github.com/PINTO0309/Bazel_bin.git
$ Bazel_bin/0.20.0/Ubuntu1604_x86_64/install.sh

$ cd ~
$ git clone https://github.com/tensorflow/tensorflow.git
$ cd tensorflow
$ git branch -a
* master
  remotes/origin/0.6.0
  remotes/origin/HEAD -> origin/master
  remotes/origin/bananabowl-patch-1
  remotes/origin/ewilderj-patch-1
  remotes/origin/jvishnuvardhan-patch-1
  remotes/origin/jvishnuvardhan-patch-9
  remotes/origin/master
  remotes/origin/patch-cherry-pick-tf-data
  remotes/origin/r0.10
  remotes/origin/r0.11
  remotes/origin/r0.12
  remotes/origin/r0.7
  remotes/origin/r0.8
  remotes/origin/r0.9
  remotes/origin/r1.0
  remotes/origin/r1.1
  remotes/origin/r1.10
  remotes/origin/r1.11
  remotes/origin/r1.12
  remotes/origin/r1.13
  remotes/origin/r1.14
  remotes/origin/r1.2
  remotes/origin/r1.3
  remotes/origin/r1.4
  remotes/origin/r1.5
  remotes/origin/r1.6
  remotes/origin/r1.7
  remotes/origin/r1.8
  remotes/origin/r1.9
  remotes/origin/r2.0
  remotes/origin/release_1.14.0
  remotes/origin/rthadur-patch-1

$ git checkout -b r1.13 origin/r1.13
$ sudo bazel clean

$ ./configure

WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
INFO: Invocation ID: 202dac03-9d5d-4544-ba79-90001b7b2ca9
You have bazel 0.20.0- (@non-git) installed.
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python3


Found possible Python library paths:
  /opt/intel/openvino_2019.2.242/python/python3
  /opt/intel/openvino_2019.2.242/python/python3.5
  /home/b920405/git/caffe-jacinto/python
  /opt/intel/openvino_2019.2.242/deployment_tools/model_optimizer
  .
  /opt/movidius/caffe/python
  /usr/lib/python3/dist-packages
  /usr/local/lib/python3.5/dist-packages
  /usr/local/lib
Please input the desired Python library path to use.  Default is [/opt/intel/openvino_2019.2.242/python/python3]
/usr/local/lib/python3.5/dist-packages
Do you wish to build TensorFlow with XLA JIT support? [Y/n]: n
No XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: n
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: n
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: n
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: n
Clang will not be downloaded.

Do you wish to build TensorFlow with MPI support? [y/N]: n
No MPI support will be enabled for TensorFlow.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]: 


Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: n
Not configuring the WORKSPACE for Android builds.

Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
    --config=mkl            # Build with MKL support.
    --config=monolithic     # Config for mostly static monolithic build.
    --config=gdr            # Build with GDR support.
    --config=verbs          # Build with libverbs support.
    --config=ngraph         # Build with Intel nGraph support.
    --config=dynamic_kernels    # (Experimental) Build kernels into separate shared objects.
Preconfigured Bazel build configs to DISABLE default on features:
    --config=noaws          # Disable AWS S3 filesystem support.
    --config=nogcp          # Disable GCP support.
    --config=nohdfs         # Disable HDFS support.
    --config=noignite       # Disable Apacha Ignite support.
    --config=nokafka        # Disable Apache Kafka support.
    --config=nonccl         # Disable NVIDIA NCCL support.
Configuration finished

$ sudo bazel build \
--config=opt \
--config=noaws \
--config=nogcp \
--config=nohdfs \
--config=noignite \
--config=nokafka \
--config=nonccl \
--copt=-ftree-vectorize \
--copt=-funsafe-math-optimizations \
--copt=-ftree-loop-vectorize \
--copt=-fomit-frame-pointer \
--remote_http_cache=https://storage.googleapis.com/bucket-bazel-tensorflow \
--google_credentials=${HOME}/bazel-caching/xxxxx-xxxxxxxxxxxx.json \
//tensorflow/tools/pip_package:build_pip_package

Screenshot 2019-08-04 00:15:46.png
Action result metadata is stored under the path /ac/
Output files are stored under the path /cas/

FireShot Capture 077 - バケットの詳細 - My First Project - Google Cloud Platform - console.cloud.google.com.png
FireShot Capture 078 - バケットの詳細 - My First Project - Google Cloud Platform - console.cloud.google.com.png

5.(Second time) Building Tensorflow v1.14.0

$ cd ~
$ Bazel_bin/0.24.1/Ubuntu1604_x86_64/install.sh

$ cd ~
$ cd tensorflow
$ git checkout -b r1.14 origin/r1.14
$ sudo bazel clean

$ ./configure

WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.24.1- (@non-git) installed.
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python3


Found possible Python library paths:
  /opt/intel/openvino_2019.2.242/python/python3
  /opt/intel/openvino_2019.2.242/python/python3.5
  /home/b920405/git/caffe-jacinto/python
  /opt/intel/openvino_2019.2.242/deployment_tools/model_optimizer
  .
  /opt/movidius/caffe/python
  /usr/lib/python3/dist-packages
  /usr/local/lib/python3.5/dist-packages
  /usr/local/lib
Please input the desired Python library path to use.  Default is [/opt/intel/openvino_2019.2.242/python/python3]
/usr/local/lib/python3.5/dist-packages
Do you wish to build TensorFlow with XLA JIT support? [Y/n]: n
No XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: n
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: n
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: n
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: n
Clang will not be downloaded.

Do you wish to build TensorFlow with MPI support? [y/N]: n
No MPI support will be enabled for TensorFlow.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]: 


Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: n
Not configuring the WORKSPACE for Android builds.

Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
    --config=mkl            # Build with MKL support.
    --config=monolithic     # Config for mostly static monolithic build.
    --config=gdr            # Build with GDR support.
    --config=verbs          # Build with libverbs support.
    --config=ngraph         # Build with Intel nGraph support.
    --config=numa           # Build with NUMA support.
    --config=dynamic_kernels    # (Experimental) Build kernels into separate shared objects.
Preconfigured Bazel build configs to DISABLE default on features:
    --config=noaws          # Disable AWS S3 filesystem support.
    --config=nogcp          # Disable GCP support.
    --config=nohdfs         # Disable HDFS support.
    --config=noignite       # Disable Apache Ignite support.
    --config=nokafka        # Disable Apache Kafka support.
    --config=nonccl         # Disable NVIDIA NCCL support.
Configuration finished

$ sudo bazel build \
--config=opt \
--config=noaws \
--config=nogcp \
--config=nohdfs \
--config=noignite \
--config=nokafka \
--config=nonccl \
--copt=-ftree-vectorize \
--copt=-funsafe-math-optimizations \
--copt=-ftree-loop-vectorize \
--copt=-fomit-frame-pointer \
--remote_http_cache=https://storage.googleapis.com/bucket-bazel-tensorflow \
--google_credentials=${HOME}/bazel-caching/xxxxx-xxxxxxxxxxxx.json \
//tensorflow/tools/pip_package:build_pip_package

It seems that caching does not work well if you switch the version or branch of the OSS to be built.
Screenshot 2019-08-04 14:20:18.png

6.(Third time) Building Tensorflow v1.14.0 (Fix the program and rebuild it after removing Bazel's prebuilt binaries)

tensorflow/lite/python/interpreter.py
# Add the following two lines to the last line
  def set_num_threads(self, i):
    return self._interpreter.SetNumThreads(i)
tensorflow/lite/python/interpreter_wrapper/interpreter_wrapper.cc
// Corrected the vicinity of the last line as follows
PyObject* InterpreterWrapper::ResetVariableTensors() {
  TFLITE_PY_ENSURE_VALID_INTERPRETER();
  TFLITE_PY_CHECK(interpreter_->ResetVariableTensors());
  Py_RETURN_NONE;
}

PyObject* InterpreterWrapper::SetNumThreads(int i) {
  interpreter_->SetNumThreads(i);
  Py_RETURN_NONE;
}

}  // namespace interpreter_wrapper
}  // namespace tflite
tensorflow/lite/python/interpreter_wrapper/interpreter_wrapper.h
  // should be the interpreter object providing the memory.
  PyObject* tensor(PyObject* base_object, int i);

  PyObject* SetNumThreads(int i);

 private:
  // Helper function to construct an `InterpreterWrapper` object.
  // It only returns InterpreterWrapper if it can construct an `Interpreter`.
tensorflow/lite/tools/make/Makefile
BUILD_WITH_NNAPI=false
tensorflow/contrib/__init__.py
from tensorflow.contrib import checkpoint
#if os.name != "nt" and platform.machine() != "s390x":
#  from tensorflow.contrib import cloud
from tensorflow.contrib import cluster_resolver
tensorflow/contrib/__init__.py
from tensorflow.contrib.summary import summary

if os.name != "nt" and platform.machine() != "s390x":
  try:
    from tensorflow.contrib import cloud
  except ImportError:
    pass

from tensorflow.python.util.lazy_loader import LazyLoader
ffmpeg = LazyLoader("ffmpeg", globals(),
                    "tensorflow.contrib.ffmpeg")
$ sudo bazel clean

$ ./configure

WARNING: --batch mode is deprecated. Please instead explicitly shut down your Bazel server using the command "bazel shutdown".
You have bazel 0.24.1- (@non-git) installed.
Please specify the location of python. [Default is /usr/bin/python]: /usr/bin/python3


Found possible Python library paths:
  /opt/intel/openvino_2019.2.242/python/python3
  /opt/intel/openvino_2019.2.242/python/python3.5
  /home/b920405/git/caffe-jacinto/python
  /opt/intel/openvino_2019.2.242/deployment_tools/model_optimizer
  .
  /opt/movidius/caffe/python
  /usr/lib/python3/dist-packages
  /usr/local/lib/python3.5/dist-packages
  /usr/local/lib
Please input the desired Python library path to use.  Default is [/opt/intel/openvino_2019.2.242/python/python3]
/usr/local/lib/python3.5/dist-packages
Do you wish to build TensorFlow with XLA JIT support? [Y/n]: n
No XLA JIT support will be enabled for TensorFlow.

Do you wish to build TensorFlow with OpenCL SYCL support? [y/N]: n
No OpenCL SYCL support will be enabled for TensorFlow.

Do you wish to build TensorFlow with ROCm support? [y/N]: n
No ROCm support will be enabled for TensorFlow.

Do you wish to build TensorFlow with CUDA support? [y/N]: n
No CUDA support will be enabled for TensorFlow.

Do you wish to download a fresh release of clang? (Experimental) [y/N]: n
Clang will not be downloaded.

Do you wish to build TensorFlow with MPI support? [y/N]: n
No MPI support will be enabled for TensorFlow.

Please specify optimization flags to use during compilation when bazel option "--config=opt" is specified [Default is -march=native -Wno-sign-compare]: 


Would you like to interactively configure ./WORKSPACE for Android builds? [y/N]: n
Not configuring the WORKSPACE for Android builds.

Preconfigured Bazel build configs. You can use any of the below by adding "--config=<>" to your build command. See .bazelrc for more details.
    --config=mkl            # Build with MKL support.
    --config=monolithic     # Config for mostly static monolithic build.
    --config=gdr            # Build with GDR support.
    --config=verbs          # Build with libverbs support.
    --config=ngraph         # Build with Intel nGraph support.
    --config=numa           # Build with NUMA support.
    --config=dynamic_kernels    # (Experimental) Build kernels into separate shared objects.
Preconfigured Bazel build configs to DISABLE default on features:
    --config=noaws          # Disable AWS S3 filesystem support.
    --config=nogcp          # Disable GCP support.
    --config=nohdfs         # Disable HDFS support.
    --config=noignite       # Disable Apache Ignite support.
    --config=nokafka        # Disable Apache Kafka support.
    --config=nonccl         # Disable NVIDIA NCCL support.
Configuration finished

$ sudo bazel build \
--config=opt \
--config=noaws \
--config=nogcp \
--config=nohdfs \
--config=noignite \
--config=nokafka \
--config=nonccl \
--copt=-ftree-vectorize \
--copt=-funsafe-math-optimizations \
--copt=-ftree-loop-vectorize \
--copt=-fomit-frame-pointer \
--remote_http_cache=https://storage.googleapis.com/bucket-bazel-tensorflow \
--google_credentials=${HOME}/bazel-caching/xxxxx-xxxxxxxxxxxx.json \
//tensorflow/tools/pip_package:build_pip_package

If you just fix a bug fix or build sequence without switching the OSS version or branch to build, caching seems to work well.
Screenshot 2019-08-04 14:37:03.png

7.Appendix

bucket_object_delete_gsutil_command
gsutil -m rm -r gs://bucket-bazel-tensorflow/ac
gsutil -m rm -r gs://bucket-bazel-tensorflow/cas

8.Reference articles

https://docs.bazel.build/versions/master/remote-caching.html

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

小学生の自由研究に⁉ RaspberryPiでラジコン★☆無線マウス操作☆★

@shion21さんの『小学生でも簡単⁉RaspberryPiとScratchを使ったラジコン』を参考にして、
ラジコン操作を無線マウスで出来るようにしました。

躯体はamazon The perseids 2WDロボットスマートカーシャーシを使いました。
amazon The perseids 2WDロボットスマートカーシャーシ

DSCF5841.JPG

1.必要なもの

部品名 個数
RaspberryPi 1
microUSBケーブル 1
HDMIケーブル 1
モニター(TV) 1
無線マウス 1
キーボード 1
モバイルバッテリー 1
amazon The perseids 2WDロボットスマートカーシャーシ 1
モータードライバーTA7291P 2
抵抗器 10kΩ 2
ブレッドボード 170タイポイント 1
ジャンパーワイヤー(オス-オス) 1程度
ジャンプワイヤー(オス-メス) 10程度
単三電池 4

2.作り方

カーシャーシは先に組み立てておいてください。回路も出来上がったら、シャーシに乗っけてください。
回路は以下の通りです。
https___qiita-image-store.s3.amazonaws.com_0_287320_26daebf1-4161-228b-e8dd-afa9cbe9c528.png
@shion21さんの『小学生でも簡単⁉RaspberryPiとScratchを使ったラジコン』を参考にして、
ちょっと配線を直してあります。
DSCF5830.JPG
DSCF5835.JPG

3.テスト

@shion21さんの[小学生でも簡単⁉RaspberryPiとScratchを使ったラジコン]https://qiita.com/shion21/items/714576ee10a1fe6dacdd

の『3.テスト』を参照してください。

4.プログラム

sudo vim mousebutton.py

下記コマンドを張り付けてください。

pygameで黒スクリーンを作成して、スクリーン上でのマウス操作を
GPIO信号に割り当てます。

from pygame.locals import *
import pygame
import sys
import RPi.GPIO as GPIO
import time

GPIO.setmode (GPIO.BCM) 

GPIO.setup(4, GPIO.OUT) 
GPIO.setup(17, GPIO.OUT)
GPIO.setup(9, GPIO.OUT)
GPIO.setup(11, GPIO.OUT)


def main():
    pygame.init()
    screen = pygame.display.set_mode((400, 330))
    pygame.display.set_caption("mouse event")

    while True:        
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

            if event.type == MOUSEBUTTONDOWN and event.button == 5:
                GPIO.output(4,1)
                GPIO.output(9,1)
                time.sleep(0.5)
                GPIO.output(4,0)
                GPIO.output(9,0)

            if event.type == MOUSEBUTTONDOWN and event.button == 4:
                GPIO.output(17,1)
                GPIO.output(11,1)
                time.sleep(0.5)
                GPIO.output(17,0)
                GPIO.output(11,0)

            if event.type == MOUSEBUTTONDOWN and event.button == 1:
                GPIO.output(4,1)
                GPIO.output(11,1)
                time.sleep(0.25)
                GPIO.output(4,0)
                GPIO.output(11,0)

            if event.type == MOUSEBUTTONDOWN and event.button == 3:
                GPIO.output(17,1)
                GPIO.output(9,1)
                time.sleep(0.25)
                GPIO.output(17,0)
                GPIO.output(9,0)

if __name__=="__main__":
    main()

GPIO.cleanup()

スクロールアップで『前進』
スクロールダウンで『後退』
左クリックで『左旋回』
右クリックで『右旋回』です。

RaspberryPiの起動時にプログラムを自動起動させるには

sudo vim /etc/rc.local

ファイルの最後にexit 0とあるので、その手前に起動時に実行したいプログラムを書きます。

# sudo python /home/pi/mousebutton.py
exit 0

5.まとめ

このラジコンは、親子で楽しく電子工作をしながらプログラミングもできると思います。
小学校2年の息子を楽しく作りました。

Qiitaで初めてHPを書いています。
初心者が書いているので間違いがあるかもしれません。間違っていたらコメントをください。

小学校4年生の娘とはMicroBitでSPY-Watchを作製中です。アドバイスとかあったらコメントください。

6.参照リンク

@shion21さんの[小学生でも簡単⁉RaspberryPiとScratchを使ったラジコン]https://qiita.com/shion21/items/714576ee10a1fe6dacdd

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

【物理シミュレーション】単振り子・二重振り子のシミュレーションをしてみた!

概要

物理シミュレーションの第一歩ということで、単振り子(pendulum)および二重振り子(double pendulum)をつくってみました.
二重振り子については,軌跡付きでつくってみました.

動作環境

  • Windows10(64bit)
  • Python 3.7.2

単振り子の理論

まずは,単振り子の運動方程式を導きます.
直交座標系の運動方程式を立ててから,角度のみの運動方程式に変形してもいいのですが,
ラグランジアンを使って導いた方がラクでしょう.

振り子が鉛直方向となす角を$\theta$とすると,

運動エネルギー

K=\frac{1}{2}m\dot{x}^2=\frac{1}{2}m{(l\dot{\theta})}^2

ポテンシャル

U=mglcos\theta

ゆえにラグランジアンは

\begin{align}
L & = K - U\\
& = \frac{1}{2}m{(l\dot{\theta})}^2 - mglcos\theta
\end{align}

オイラーラグランジュ方程式に代入して

\begin{align}
\frac{d}{dt}(ml^2(\dot{\theta}^2) + mglsin\theta)  = 0\\
ml^2\ddot{\theta}  = -mglsin\theta\\
\ddot{\theta}  = -\frac{g}{l}sin\theta
\end{align}

となり,$\theta$に関する二階の微分方程式を得る.

Pythonで解くときには,微分方程式は一階の微分方程式にする必要があるが,
一般に$n$階の微分方程式は$n$次の連立微分方程式に変形することができる.

今の場合,以下のように$\theta$に関する二階微分方程式が$\theta$と$\omega$に関する連立一階微分方程式に変形ができる.

\begin{cases}
\dot{\theta} & = \omega\\
\dot{\omega} & = -\frac{g}{l}sin\theta
\end{cases}

微分方程式を解く方法

Pythonで微分方程式を解くためには,scipy.integrateの中のodeintやodeがありますし,
実際この関数を使ったコードはネット上にも多く見られました.
しかし,odeintの公式ドキュメントを見てみると,微分方程式を解くための新しい関数として
scipy.integrate.solve_ivpが推奨されています.
ということで,今回は,solve_ivp()を使っていこうと思います.

使い方を具体例を使って説明していきます。

  • 例1
\frac{dy}{dt}=y\\
y(0)=1

解:$y = exp(t)$

def func (t, y):
    dydt = y
    return dydt

sol = scipy.integrate.solve_ivp(func, [0,20], [1])
t1 = sol.t
y1 = sol.y.T

t2 = np.linspace(0, 20, 100)
plt.plot(t1, y1)
plt.plot(t2, np.exp(t2))
plt.show()

solve_ivp()の第1引数は微分値を返す関数です.つまり,以下の方程式で言う右辺を返り値に持つ関数です.

\frac{dy}{dt} = f(t, y)

solve_ivp()第2引数は時間を,第3引数は初期値を取ります.今回は,$t=0$から$t=20$までとしました.
時間の刻み幅自動で設定されます.これはodeintなどとの違いの一つです.
もし,刻み幅を指定したい場合は例2のようにt_evalキーワードを追加します.

実行結果は以下のようになります.

ex1.png

  • 例2
\frac{d^2y}{dt^2}=-y\\
y(0)=1\\
\frac{dy}{dt}(0)=0\\

解:$y = cos(t)$

例2は2階の微分方程式です.関数funcの中身は上記のようになります.
考え方としては,

y = y_0\\
\frac{dy}{dt} = y_1

として,以下の連立1階微分方程式に帰着させます.

\frac{d}{dt}y_0 = y_1\\
\frac{d}{dt}y_1 = -y_0
def func (t, y):
    dydt = np.zeros_like(y)
    dydt[0] = y[1]
    dydt[1] = -y[0]
    return dydt

t_span=[0,20]
y0 = [0,1]
t = np.linspace(t_span[0], t_span[1], 100)
sol = scipy.integrate.solve_ivp(func, t_span, y0, t_eval=t)
t1 = sol.t
y1 = sol.y[1,:]

t2 = np.linspace(0, 20, 100)
plt.plot(t1, y1, 'o')
plt.plot(t2, np.cos(t2), '--')
plt.show()

実行結果は以下のようになります.

ex2.png

単振り子の実装

それでは,単振り子のシミュレーションを行うプログラムを実装してみましょう!

  • ライブラリのインポート

まずは,必要なライブラリをインポートします.

from numpy import sin, cos
from scipy.integrate import solve_ivp
from matplotlib.animation import FuncAnimation
import matplotlib.pyplot as plt
import numpy as np
  • 運動方程式の記述

続いて運動方程式を記述する関数を定義します.重力加速度はG,振り子の長さはLとしています.

def derivs(t, state):
    dydt = np.zeros_like(state)
    dydt[0] = state[1]
    dydt[1] = -(G/L)*sin(state[0])
    return dydt
  • 初期条件の記述

運動方程式の初期条件を与えます.2階微分方程式なので自由度は2ですね.

th1 = 30.0
w1 = 0.0
state = np.radians([th1, w1])
  • 運動方程式を解く

運動方程式の数値解を求めます.
yは(2,len(t))型の二次元配列なので,今は,1行目の角度$\theta$のみを取得します.
2行目の角速度$\omega$は今回は必要ないです.

sol = solve_ivp(derivs, t_span, state, t_eval=t)
theta = sol.y[0,:]
  • 直交座標系に変換

求まった$\theta$から直交座標系$(x,y)$に変換します.これでデータは得られました.
あとは,これをプロットするだけです.

x = L * sin(theta)
y = -L * cos(theta)
  • 逐次描画していく関数の定義

順次描画していくための関数を定義します.1行目のlineに次々と座標を代入して描画をしていきます.
linewidth=2はlw=2と省略することもできます.'o-'は端点と線分を表します.

line, = ax.plot([], [], 'o-', linewidth=2)

def animate(i):
    x = [0, x[i]]
    y = [0, y[i]]

    line.set_data(x, y)
    return line,
  • アニメーションの関数

最後にアニメーションを実行します.
blitキーワードやintervalキーワードはアニメーションが滑らかになるように設定しています.なくても動きます.
あとは,レイアウトなどを整え,プロットすれば終わりです.

ani = FuncAnimation(fig, animate, frames=np.arange(0, len(t)), interval=25, blit=True)

実行結果

pendulum2.gif

二重振り子

では,単振り子でアニメーションの方法はマスターできたと思うので,二重振り子をシミュレーションしてみましょう.
カオスが見れて面白いです!
さらに,単振り子のレベルアップということで軌跡も描画しちゃいましょう.

二重振り子の理論

二重振り子の運動方程式も単振り子同様ラグランジアンを求めて,オイラーラグランジュ方程式に代入すれば求まります.
ここでは,計算が面倒なので結果だけを示します.

運動方程式

(m_1+m_2)l_1\ddot{\theta_1} + m_2l_2\ddot{\theta_2}cos(\theta_1-\theta_2) + m_2l_2\dot{\theta_2}^2sin(\theta_1-\theta_2) + (m_1+m_2)gsin\theta_1 = 0\\
l_1l_2\ddot{\theta_1}cos(\theta_1-\theta_2) + l_2^2\ddot{\theta_2}-l_1l_2\dot{\theta_1}^2sin(\theta_1-\theta_2) gl_2sin{\theta_2}

整理して,

\frac{d\theta_1}{dt} = \omega_1\\
\frac{d\omega_1}{dt} = \frac{m_2l_1\omega_1^2sin\delta cos\delta + m_2gsin\theta_2cos\delta + m_2l_2\omega_2^2sin\delta -(m_1+m_2)gsin\theta_1}{(m_1+m_2)l_1-m_2l_1cos^2{\delta}}\\
\frac{d\theta_2}{dt} = \omega_2\\
\frac{d\omega_2}{dt} = \frac{-m_2l_2\omega_2^2sin\delta cos\delta + (m_1+m_2)gsin\theta_1cos\delta -(m_1+m_2)l_1\omega_1^2sin\delta -(m_1+m_2)gsin\theta_2}{(m_1+m_2)l_1-m_2l_1cos^2{\delta}}

ただし,$\delta = \theta_2-\theta_1$.

二重振り子の実装

それでは,二重振り子のシミュレーションを行うプログラムを実装しています.
matplotlibの公式ドキュメントに二重振り子のシミュレーションプログラムがあるのでそちらを参考にしました.

  • 運動方程式の記述
def derivs(t, state):

    dydx = np.zeros_like(state)
    dydx[0] = state[1]

    delta = state[2] - state[0]
    den1 = (M1+M2) * L1 - M2 * L1 * cos(delta) * cos(delta)
    dydx[1] = ((M2 * L1 * state[1] * state[1] * sin(delta) * cos(delta)
                + M2 * G * sin(state[2]) * cos(delta)
                + M2 * L2 * state[3] * state[3] * sin(delta)
                - (M1+M2) * G * sin(state[0]))
               / den1)

    dydx[2] = state[3]

    den2 = (L2/L1) * den1
    dydx[3] = ((- M2 * L2 * state[3] * state[3] * sin(delta) * cos(delta)
                + (M1+M2) * G * sin(state[0]) * cos(delta)
                - (M1+M2) * L1 * state[1] * state[1] * sin(delta)
                - (M1+M2) * G * sin(state[2]))
               / den2)

    return dydx
  • 初期条件の記述
th1 = 100.0
w1 = 0.0
th2 = -10.0
w2 = 0.0
state = np.radians([th1, w1, th2, w2])
  • 運動方程式を解く
sol = solve_ivp(derivs, t_span, state, t_eval=t)
y = sol.y
  • ジェネレータ関数の定義

これは単振り子と少し異なります。軌跡を描くためにジェネレータを定義します。
ジェネレータはreturnではなくyield文で返します.
また,zip()関数を使うことで,並列的に反復処理を行えます.

def gen():
    for tt, th1, th2 in zip(t,y[0,:], y[2,:]):
        x1 = L1*sin(th1)
        y1 = -L1*cos(th1)
        x2 = L2*sin(th2) + x1
        y2 = -L2*cos(th2) + y1
        yield tt, x1, y1, x2, y2
  • アニメーション関数の定義

locusにどんどん値を追加していくことで,軌跡を描画します.
また今回はタイマーもついています.

locus, = ax.plot([], [], 'r-', linewidth=2)
line, = ax.plot([], [], 'o-', linewidth=2)
time_template = 'time = %.1fs'
time_text = ax.text(0.05, 0.9, '', transform=ax.transAxes)

xlocus, ylocus = [], []
def animate(data):
    t, x1, y1, x2, y2 = data
    xlocus.append(x2)
    ylocus.append(y2)

    locus.set_data(xlocus, ylocus)
    line.set_data([0, x1, x2], [0, y1, y2])
    time_text.set_text(time_template % (t))
  • アニメーションの実行
ani = FuncAnimation(fig, animate, gen, interval=50, repeat=False)

実行結果

double_pendulum.gif

まとめ

いかがだったでしょうか?今回は物理シミュレーションとして,単振り子と二重振り子を扱ってみました.
二重振り子のカオスが見れたときは興奮しますよね.
今回のコードに関して改善点などありましたらコメントいただけると助かります.

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

PyOpenGL glGenBuffersでエラー

PythonからOpenGLを使ってとあるプログラムを開発中です。

初歩的なところですが、glGenBufferwsを実行して以下のエラーが出てはまったので対処方法を紹介します。

OpenGLのエラー

OpenGL.error.NullFunctionError: Attempt to call an undefined function glGenBuffers, check for bool(glGenBuffers) before calling

対処方法

glutInit と glutInitDisplayMode と glutCreateWindow を先に実行すること。

OpenGLの初期化が完了していないのが原因です。

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

【ピーナッツくん】ゆるきゃらグランプリの獲得票数と順位をスクレイピングする【がんばれ】

はじめに

8/1より、ゆるキャラグランプリがはじまりました!

ゆるキャラグランプリ2019

10/25までネット上で投票(1日1回投票可能)が行われ、ご当地および企業キャラクターがそれぞれ得票数を競います。

得票数は定期的にネット上で公開され、推しのゆるキャラがいま何位ぐらいかがわかるようになっているみたいです。

しかし、Webサイトの情報にアクセスしにくい(個人的な感想)うえ、票数や順位の時間的な推移を確認することができません。

そこで、今回はPythonを使ったWebスクレイピングで情報を引っ張ってくることにしました。

スクレイピングの前準備

プログラムを書く前に、以下のことを確認する必要があります。

  • Webページのソースコード中のどの部分に欲しい情報が書かれているか
  • ページのURLがどのような構造になっているか

今回は、この過程も簡単に解説していきます。

タグの確認

まず、スクレイピングをする前にどんな情報が欲しいかを確認します。

例えば、以下のゆるキャラグランプリ企業部門の検索画面
"https://www.yurugp.jp/jp/vote/result.php?enterprise=1"
では、ゆるキャラの順位、得票数、名前、地域、そして所属が確認できます。

yurukyara.png

今回はこの情報だけで十分なので、個別ページに飛ぶ必要はなさそうです。

次に、この情報が表示されている部分のhtmlタグを確認します。
自分はChromeを使っているので、タグを見たい部分を右クリック→検証でソースが見えます。

図1.png

このように、例えば順位、得票数はrankdetailクラスのdivタグで、名前、地域、所属はnameクラスのdivタグで囲まれていることが分かります。

ここまでがわかればスクレイピングには十分です。
次に、URLの確認をしていきます。

URLの確認

例えば、企業のゆるキャラの検索画面のURLは以下のようになっています。

https://www.yurugp.jp/jp/vote/result.php?page=1&enterprise=1

page=nの部分がn = 2,3... となると次ページのURLとなります。
非常にわかりやすいURLなので、簡単にスクレイピングできそうです。

また、nの値が大きすぎた場合、例えば

https://www.yurugp.jp/jp/vote/result.php?page=20&enterprise=1

のようなURLはどうなるのかというと、404エラーが返ってくるのではなく、空のページに飛ばされるようです。

今回はこの仕様を利用して、ぱぱっとプログラムを書いていきます。

HPの情報をスクレイピングするコード

以下は、企業ゆるキャラの情報をスクレイピングするコードです。
今回はPythonのurllibとBeautifulSoupを使いました。

import urllib.request
from bs4 import BeautifulSoup
import numpy as np
import time

dataset = []

n = 1
isContinue = True
while(isContinue):

    url_pref = "https://www.yurugp.jp/jp/vote/result.php?page="+str(int(n))+"&enterprise=1"

    # urllibで読み込み、BeautifulSoupでパースする
    nem = urllib.request.urlopen(url_pref).read()
    soup = BeautifulSoup(nem,"html.parser")

    # rankdetail, nameクラスのdivタグの部分を抽出
    rank = soup.find_all("div",class_="rankdetail")
    name = soup.find_all("div",class_="name")

    # 情報を抽出してリストに保存
    ranks = [[j.text.split('位')[0],j.text.split('位')[1].replace("PT",""),
             i.find_all("h4")[0].text.replace("(","").replace(")",""),
             i.find_all("span",class_="country")[0].text.replace("(","").replace(")",""),
             i.find_all("span",class_="biko")[0].text] for i,j in zip(name,rank)]

    if(len(ranks) == 0):
        # 空のページに行くとrankdetail, nameのdivタグの部分がないので
        # ranksは空のリストとなる。ここで繰り返し処理を終了する。
        isContinue = False
    else:
        # ranksの長さが0より多きければdatasetに追加してnに1を足す
        dataset.extend(ranks)
        n += 1

    time.sleep(1)

dataset_ent = np.array(dataset)

たったこれだけです、簡単ですね!
ちなみにdataset_entの中身はこんな感じになっています。

array([['36', '142', 'よりぞう', '東京都', 'JAバンク(農林中央金庫)'],
       ['65', '75', 'でんのすけ', '香川県', '四電エナジーサービス株式会社'],
       ['25', '196', 'ミクちゃん', '兵庫県', '株式会社タツミコーポレーション'],
       ...,
       ['161', '16', 'えひめれっちゃくんすまいるえきちゃん', '愛媛県', 'JR四国'],
       ['133', '25', 'かがわれっちゃくんすまいるえきちゃん', '香川県', 'JR四国'],
       ['210', '8', 'こうちれっちゃくんすまいるえきちゃん', '高知県', 'JR四国']], dtype='<U35')

左から順に順位、得票数、名前、地域、所属です。

データを分析して遊ぶ

目的のデータが得られたので、さっそく解析してみます!
まずは基礎データですが、現在の企業ゆるキャラの総数は362名、総票数は29162票でした。

上位50名のゆるキャラとその得票数のプロットは以下になります。

bar.png

ダントツの1位がバーチャルYoutuberのオシャレになりたい!ピーナッツくん、2位が株式会社宇佐美鉱油のうさっぴぃ、3位がNEXCO生日本のみちまるくん・・・と続いています。

上位の得票数が全体に占める割合が非常に多そうなので、得票率が1%以上の候補を円グラフで可視化してみましょう。

pie.png

すると、上位陣が全体の票数のほとんどを独占していることがよくわかります。

中でも1位のオシャレになりたい!ピーナッツくんは一人で15%以上の得票率をたたき出しています。
Youtubeチャンネルの登録者数が8.4万人ぐらいなので、その5%程度が投票していると考えればおかしな数字ではありませんが、ゆるキャラ界(あるのか?)に大きな衝撃を与えていることでしょう。

おまけ

ご当地のゆるキャラをスクレイピングするプログラムはこんな感じです。
県ごとに検索ページが分かれているのが違いですが、数が増えることはない(48個)ので、対応はそんなに難しくないです。

dataset = []
search_list = np.linspace(1,48,48)

for i in search_list:
    n = 1
    isContinue = True
    while(isContinue):

        url_pref = "https://www.yurugp.jp/jp/vote/result.php?page="+str(int(n))+"&prefectures="+str(int(i))

        nem = urllib.request.urlopen(url_pref).read()
        soup = BeautifulSoup(nem,"html.parser")
        rank = soup.find_all("div",class_="rankdetail")
        name = soup.find_all("div",class_="name")

        ranks = [[j.text.split('位')[0],j.text.split('位')[1].replace("PT",""),
                 i.find_all("h4")[0].text.replace("(","").replace(")",""),
                 i.find_all("span",class_="country")[0].text.replace("(","").replace(")",""),
                 i.find_all("span",class_="biko")[0].text] for i,j in zip(name,rank)]

        if(len(ranks) == 0):
            isContinue = False
        else:
            dataset.extend(ranks)
            n += 1

        time.sleep(1)

最後に

この記事を書いた目的の3/4ぐらいは、技術的な内容にかこつけてバーチャルYoutuberのオシャレになりたい!ピーナッツくんと、その相方のぽんぽこさんの応援です!
(ぽんぽこさんも着ぐるみ化してエントリーしたけど落選してしまった模様)

バーチャルYoutuberなのに身銭切って着ぐるみ化してゆるキャラグランプリに出るとか面白すぎでしょ。

10/25まで先は長いぞ!
一日一票! ピーナッツくんの応援よろしくね!

konkon

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

javascriptのPromiseをpythonで実装する方法

Motive

selenium でスマホ版でブラウザを起動しようと思った時、

    sp_args = "--user-agent="
    sp_args_list = ["Mozilla/5.0 (iPhone; CPU iPhone OS 10_2 like Mac OS X)",
                    "AppleWebKit/602.3.12 (KHTML, like Gecko)",
                    "Version/10.0 Mobile/14C92 Safari/602.1"]
    sp_args += " ".join(sp_args_list)

と設定するだけでは一部のサイトではレスポンシブになっているため対応しない場合があります。

そのため、 width を450くらいにしてリロードをするとスマホ版の表示になるのですが、ブラウザ設定 → スマホ画面の大きさを設定 -> リロード の順に一つのプロセスが完了したことがcompleteになった状態で次のプロセスに移す実装ができないかと思ったわけです。

で、 javascriptの非同期処理で使われるPromiseと同等のものがpythonでできないかと考えたわけっす。

Preparetion

pip で探すと早速パッケージが見つかりました。ふつーに

pip install Promise

で良いです。

Model

参考にしたサンプルコードですが初めてのJavaScript 第3版 ――ES2015以降の最新ウェブ開発 14章 非同期プログラミングにあったロケット発射モデルを題材にしました。

元コードからブラウザに表示する編集等々でちょっと変えています。

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
        <script type="text/javascript" src="./sample.js"></script>
    </head>
    <body>
        <h3>apolonX</h3>
        <div class="box">
        </div>
    </body>

</html>

$(()=>{

    countdown(10)
    .then(()=>{
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                //大気圏突入
                resolve(insertBox("atmosphere..."));
            }, 2000);
        });
    })
    .then(()=>{
        //成功
        insertBox("success!!");
    });

});

//カウントダウン
function countdown(seconds){
    let dst = new Promise(
            (resolve, reject) => {
                for (let i=seconds;0 <=i;i--){
                    setTimeout(() => {
                        if(i>0){
                            insertBox(countSign(i));
                        } else {
                            resolve(insertBox(goSign()));
                        }
                    }, (seconds-i)*1000);
                }
            }
        );
    return dst
}


function insertBox(src){
    let p = $("<p>")
    p.text(src);
    $(".box").append(p);
}

function countSign(index){
    return index + "...";
}

function goSign(){
    return "GO!";
}

で約2秒ごとにブラウザにメッセージが表示されます。

count.gif

Method

from promise import Promise
import time

def main():
    countdown(10).then(go()).then(Promise.resolve(atmosphere())).then(success())

def countdown(seconds):
    def myfunc(resolve, reject):
        for i in reversed(range(0,seconds + 1)):
            if 0 < i:
                print( str(i) + "..." )
                time.sleep(2)
            else :
                resolve()
    return Promise(myfunc)

def go():
    time.sleep(1)
    print("GO")


def atmosphere():
    time.sleep(1)
    print("atmosphere")

def success():
    print("success!!")


if __name__ == '__main__':
    main()

countdown(10).then(go()).then(Promise.resolve(atmosphere())).then(success())
でチェイン化しています。

Aug 4 2019 1_22 PM - Edited.gif

Future

複数のAPIでデータ取得後に一括処理して文章生成するなどのケースであったら、全てのAPIのデータ取得後に一括処理する一貫性が保たれるので何かと良い。
簡易的なトランザクション処理ですかね。

Reference

promise
promise:github
初めてのJavaScript 第3版 ――ES2015以降の最新ウェブ開発 14章 非同期プログラミング
Python の Promise 実装とその活用方法について / How to implement Promise by Python and how to use it

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

KAGGLEでどこから手を付けていいか分からず学ぶことが多すぎてまとめてみた

Googleで「KAGGLE」で検索し1ページ目(公式、Wikipedia除く)

タイトル 著者 記事日時 特徴 初心者のToDo
Kaggleとは?機械学習初心者が知っておくべき3つの使い方 Codexaチーム 2017-11-22 初心者向け カーネルをやれ
Kaggleを始める人に役に立つ記事 @jeayoon 2019-05-16 初心者向け -
Kaggle参戦記 〜入門からExpert獲得までの半年間の記録 & お役立ち資料まとめ〜 naotaka1128 Expert 2019-01-15 詳細 *1)にまとめ
KaggleでSilver取るためにどういう段階を踏んでいけばよいですか? カレーちゃん?専業kaggler(たまにSilver) 2019-01-15 初心者向け カーネルの理解、改良
Kaggle Titanic やってみた感想 mizchi 2019-02-08 Titanicの紹介 -
最近のKaggleに学ぶテーブルデータの特徴量エンジニアリング mlm_kansai 2019-03-27 特徴量エンジニアリングについて詳細 *2)にまとめ -
機械学習の勉強歴が半年の初心者が、 Kaggle で銅メダルを取得した話 株式会社トップゲートのエンジニア(Brond) 2019-02-27 詳しめ *3)にまとめ 性能の良い分類器、出力のアンサンブル

上記の方々の推奨本

記事の概要

Kaggle参戦記 〜入門からExpert獲得までの半年間の記録 & お役立ち資料まとめ〜 *1)

  • 特徴量エンジニアリング
    • 次元削減系 LDA、PCA、tSNE
    • Kaggle TalkingData Fraud Detection コンペの解法まとめ(基本編)
      • 「カテゴリー変数を組み合わせて特徴量を量産してLightGBMにぶち込む」
    • Kaggle TalkingData Fraud Detection コンペの解法まとめ(応用編)
      • もはや常識 random seed average
        • 予測モデルを複数作ってそれらの平均を最終的な予測とするensembleという手法がkaggleでは常識となっています。
        • お手軽な手法としてrandom seed averageといういうのがあります。それは、モデルを訓練する際にランダム性がある場合にランダムシード値を変えて複数モデルを作りそれの単純平均をとる
      • Negative Down Sampling
        • 判別で目的変数が不均衡な場合、多い方のデータを捨てる
  • 特徴量選択
    • ランダムフォレストと検定を用いた特徴量選択手法Boruta
      • 特徴量選択とは
        • コンペでは精度のみが重視されるがビジネス上は説明変数の特定が重要視される
        • ただ、モデルの学習や推論が高速化されたり、過学習しづらくなったり、結果判別の精度が良くなったりもする
      • 既存の手法の特徴
        • ランダムフォレスト
          • どれぐらいの特徴量重要度があったら重要だと言えるのかがイマイチ
          • ランダム性から訓練するたびに特徴量重要度が変動する
        • Forward selectionやBackward eliminationと言ったステップワイズな方法
          • 計算量が多い
          • 選んだ特徴量が過学習する
        • lasso
          • 選んだ特徴量が過学習する
      • Boruta
        • ニセの特徴量を作って重要度を比較する
  • アンサンブル
  • ブレンド

最近のKaggleに学ぶテーブルデータの特徴量エンジニアリング *2)

trainデータ、testデータ、Private testデータ間での乖離が大きいコンペが増えている → 単純にtrain, testで学習検証しただけではだめ → 特徴量エンジニアリング

  • 特徴量エンジニアリング
    • Target Encoding
      • カテゴリ変数をそのカテゴリにおけるTargetの平均値で置き換える
      • カテゴリ変数のレベルが多い場合に使う
    • テーブル間の集約
      • 集約し、sumやaverage
      • トランザクション側のデータが多い場合
    • 四則演算
      • GBDTは差や比率を直接表現できない
      • 意味が明確な連続値がある場合
    • サブモデルによる特徴量
      • 重要な特徴量の予測
      • トランザクションレベルの予測

機械学習の勉強歴が半年の初心者が、 Kaggle で銅メダルを取得した話 *3)

  • 性能の良い分類器を使う
    • アンサンブル学習系
    • 深層学習系
  • 複数の予測値のブレンド
    • 多数決、平均
  • 感度解析
    • 複数の予測値のブレンドだが重み付け

まとめ、気付き(つか無知の反省)

  • 特徴量エンジニアリング
    • 思いつく限りたくさん作ってlightGBM(が軽いので)放り込んじまえ!?
    • 不均衡データ
      • Negative Down Sampling
        • その発想はなかった!って感じ
  • 特徴量選択
    • Boruta知らなんだ
  • アンサンブル
  • ブレンド
    • 複数のアルゴリズムの結果で多数決、平均するのってそれも「アンサンブル」と思っていたが「ブレンド」と言うらしい

上記記事にはないが(わたしの見落としかも)関連する手法

  • 不均衡データのときに目的変数の少ない側のデータを増やすもの
  • データ生成
    • 判別データにて、均衡したデータでもデータ生成で精度が良くなるという事例もある
    • 連続値の場合もデータ生成手法がある
  • 共変量シフト
    • trainとtestで説明変数に偏りがある場合の対応

補足的な資料

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

LINE Notifyを使ってLINEへメッセージを送信するpythonプログラムを作ってみる2

前回まで
LINE Notifyを使ってLINEへメッセージを送信するpythonプログラムを作ってみる

前回の続きでは、iniファイルの読み込み部分も起動するプログラムファイル内に含めているが、java屋のクセとして機能的に分割したい。
iniファイルを読み込むプログラムとメッセージ送信プログラムを区別してみる。

コーディング3回目

  1. setting.iniは前回と同じ
  2. setting.iniを読み込んで設定情報にアクセスするためのクラス(Settings.py)を追加
  3. 起動するプログラムから2.を参照する
  4. 2.の単体テスト用プログラム
setting.ini
[LINENotify]
line_notify_token = <アクセストークン>
line_notify_api = https://notify-api.line.me/api/notify
Settings.py
from configparser import ConfigParser
from pconst import const

# Settting.iniを管理するクラス
class Settings:

    # setting.ini セクション名
    NOTIFY = 'LINENotify'
    const.SECTION_LINENotify = NOTIFY
    # LINENotifyセクション トークン名
    NOTIFY_TOKEN = 'line_notify_token'
    const.LINENotify_TOKEN = NOTIFY_TOKEN
    # LINENotifyセクション URL
    NOTIFY_APIURL = 'line_notify_api'
    const.LINENotify_APIURL = NOTIFY_APIURL

    # コンストラクタ
    # iniファイルを読み込み
    def __init__(self):
        self.config = ConfigParser()
        self.config.read('setting.ini', encoding="utf-8")

    # ConfigParserインスタンスを生成する
    def getConfigParserInstance(self):
        return self.config

    # setting.iniよりアクセストークンを取得する
    def getLINENotify_Token(self):
        return self.config[const.SECTION_LINENotify][const.LINENotify_TOKEN]

    # setting.iniよりアクセスURLを取得する
    def getLINENotify_APIPath(self):
        return self.config[const.SECTION_LINENotify][const.LINENotify_APIURL]
sendmessege.py
# -*- coding: utf-8 -*-
import requests
import configparser
from Settings import Settings

def main():
    # 設定ファイル読み込み
    config = Settings()

    print("LINEへ送信するメッセージを入力してください。")
    message = input("送信メッセージ:")

    print('送信メッセージ:' + message)
    payload = {'message': message}
    headers = {'Authorization': 'Bearer ' + config.getLINENotify_Token()}
    requests.post(config.getLINENotify_APIPath(), data=payload, headers=headers)

if __name__== '__main__':
    main()
SettingsTest.py
import unittest
from Settings import Settings

# Setttingsユニットテストクラス
class SettingsTest(unittest.TestCase):

    # # テスト準備
    def setUp(self):
        self.config = Settings()

    # テストケース:コンストラクタ
    def test_constructor(self):
        self.assertIsNotNone(self.config)

    # テストケース:iniファイル設定値取得メソッド
    def test_getSettings(self):
        self.assertIsNotNone(self.config.getConfigParserInstance())
        self.assertIsNotNone(self.config.getLINENotify_Token())
        self.assertIsNotNone(self.config.getLINENotify_APIPath())

if __name__ == '__name__':
    unittest.main()

気づき・感想

  • setting.iniにはアクセストークンが記載されており、アプリケーション固有の設定情報が記載されたiniファイルはこのままGitHubへpushできない。
  • 設定情報を扱うクラスを区別化したことで、変更箇所だけをテストでき保守性がアップした。
  • pconstがあれば定数行を宣言できる。

コーディング4回目

  1. setting.iniはパラメータのみ削除する
  2. アプリ固有の設定情報は初期セットアップ時にsetting.iniを書き込むためのクラス(SetUpScript.py)を追加
setting.ini
[LINENotify]
line_notify_token = 
line_notify_api = 
SetUpScript.py
"""
セットアップ用スクリプト
Setting.iniファイルの初期設定を行う。
"""
from Settings import Settings

config = Settings()
cpins = config.getConfigParserInstance()

print("Setting.iniの初期設定を開始します。")
print()
print("LINE Notify登録時のアクセストークンを入力してください。")

token = input('アクセストークン:')
cpins.set(config.NOTIFY, config.NOTIFY_TOKEN, token)
print("アクセストークンを登録しました。")

cpins.set(config.NOTIFY, config.NOTIFY_APIURL, 'https://notify-api.line.me/api/notify')
print("API用URLを登録しました。")

with open('setting.ini', 'w') as contextfile:
    cpins.write(contextfile)

print()
print("Setting.iniの初期設定が完了しました。")

気づき・感想

  • GitHubにsetting.iniを追加できた。
  • アプリ固有の設定情報はローカルにて設定できるようになった。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Cloud Functions (Python3) を使ってみた

Google Cloud Functions で Python3 が使えるということだったので使ってみました。実際のプロダクトで使うことも考えて、環境変数の扱い、ローカルでの開発についても説明しています。


なお、この記事はタスク管理にGitHubを使う一連の記事の中の一つです。

前提

以下のことが完了している前提で説明します。

クイックスタート

まずは簡単に関数を作ってみます。

関数の作成

https://console.cloud.google.com/functions

スクリーンショット 2019-07-31 21.31.23.png

設定

今回指定した設定は以下の表のとおりです。

項目
メモリ 256 MB
トリガー HTTP
ランタイム Python 3.7
ソースコード インライン エディタ
実行する関数 hello_world

スクリーンショット-2019-07-31-21.32.44.png

確認

Google Cloud Platform のコンソール画面からも確認できますが、 gcloud でログインしていればローカルからのコマンドでも確認できます。

$ gcloud functions list

結果

NAME        STATUS  TRIGGER       REGION
function-1  ACTIVE  HTTP Trigger  us-central1

実行

実行のためのトリガーは以下のように複数の選択肢があります。

  • HTTP
  • Cloud Storage
  • Cloud Pub/Sub
  • Firebase

今回はHTTPのトリガーを作成したので、ブラウザや curl コマンドで直接 GET リクエストを送って確認できます。URLはコンソール画面でに表示されています。

スクリーンショット-2019-07-31-21.41.52.png

スクリーンショット-2019-07-31-21.41.58.png

Python3 関連ファイル

Python3で Cloud Functions を作成すると、2つのファイルができています。

  • main.py
  • requirements.txt

実行したい関数は main.py の中に適当な名前で実装し、「実行する関数」でその名前を指定すると実行できます。サードパーティ製の必要なライブラリがあったら requirements.txt に書いておけばデプロイしたときに自動的にインストールしてくれます。また、Pythonファイルは一つだけではなくPythonパッケージを作成して main.py から import することもできます。

スクリーンショット-2019-08-02-21.32.24.png

ローカルからのデプロイ

できるならコードを毎回ブラウザ上のエディタではなくローカルで開発してGit管理するほうが望ましいと思われます。Cloud Source Repositories を使うと Google Cloud Platform 上での管理もできますが、ここではGitHubを想定します。

gcloudコマンドによるデプロイ

とりあえずコピーしてくるなり何なりして同様の環境をローカルに作り、ローカルからデプロイコマンドを実行すればデプロイできます。

$ gcloud functions deploy function-1 --trigger-http --runtime=python37

少し変更を加えて先程のエンドポイントにアクセスするとローカルからのデプロイができていることがわかると思います。

不要なファイルをアップロードしないようにする

ローカルに適当なファイルを置いておけばコマンド一つでデプロイしてくれるのですが、何もしなければGit管理用のファイルなど、関係ないファイルまでアップロードしてしまいます。

そこで .gcloudignore というファイルを作成しておきます。.gitignore のように、ここにアップロードしたくないファイルやディレクトリを記載しておけば、デプロイの際にアップロードされることがありません。

なお、 .gcloudignore から他のファイルを参照することもできるので、 .gitignore を参照すると良いかもしれません。

.gcloudignore
.gitignore
Makefile
README.md
requirements_local.txt
test/
venv/

#!include:.gitignore

ローカルでの開発

簡単にデプロイして確認ができる Cloud Functions ですが、毎回アップロードしなければ確認できないのは不便です。そのためにローカルでエミュレートする仕組みを作ります。

仮想環境の作成 (venv)

必須ではありませんが、必要に応じて仮想環境を用意しておくと便利です。

$ python3 venv venv
$ source venv/bin/activate

Flaskによるエミュレート

Google Cloud Function で始めに実行される関数は、Flaskの flask.Request オブジェクトを受け取ります。ローカルで開発する際にはlocalhostにFlaskの開発用サーバーが立ち上がるようにすることでエミュレートできます。

ライブラリのインストール

$ pip install flask

実装

main.py
from flask import Flask, request


def hello_world(request):
    # ...
    return 'HelloWorld!'


if __name__ == "__main__":
    app = Flask(__name__)

    @app.route('/')
    def index():
        return hello_world(request)

    app.run('127.0.0.1', 8000, debug=True)

このように、 main.py が直接呼ばれた場合は開発用サーバーが立ち上がり、 localhost:8000 にアクセスすると hello_world が実行されます。

環境変数

作成するアプリケーションによっては、他のサービスのAPIキーなど、ソースコードに書きたくない情報を含んでいる場合があります。そういった場合には環境変数を設定することができます。

設定方法

コマンドの引数を使う方法

一つの方法は、デプロイする際にコマンドの引数で指定する方法です。

$ gcloud functions deploy function-1 --trigger-http --runtime=python37 --set-env-vars FOO=bar,BAZ=boo

スクリーンショット-2019-08-03-13.31.36.png

Yamlを使う方法

env.yaml
FOO: bar
BAZ: boo
$ gcloud functions deploy function-1 --trigger-http --runtime=python37 --env-vars-file=./env.yaml

読み込み

Google Cloud Function に設定した環境変数を使う方法は自分でサーバーを立ち上げたときなど、他の環境と同じです。

import os


FOO = os.getenv('FOO')  # 'bar'
BAZ = os.getenv('BAZ')  # 'boo'

Yamlを使うときの運用方法

コマンドの引数を使う方法とYamlを使う方法がありますが、どちらかといえばファイルで管理できるYamlを使うほうが好みです。しかし、Git管理はしたくないので、そのために少し工夫を加えます。

ローカルでの確認

Yamlで環境変数を設定する場合は、ローカル環境でもそのYamlを読み込むようにすると便利です。そこで以下のようにYamlから環境変数を設定するように手を加えます。

ライブラリのインストール
$ pip install PyYAML

※ ImportError にならないようにするために requirements.txt にもPyYAMLを書き加えておきます。

実装
import os
import yaml


try:
    # local
    with open('./env.yaml') as f:
        os.environ.update(yaml.load(f))
except FileNotFoundError as e:
    # Google Cloud Functions
    pass


FOO = os.getenv('FOO')  # 'bar'
BAZ = os.getenv('BAZ')  # 'boo'

Google Cloud Storage を使う

Git管理をしたくないので、適当な Google Cloud Storage のバケットを作って、そこに env.yaml を保存しておき、デプロイする際にコピーしてくるようにします。 .gitignore に env.yaml を書き加えるのも忘れずに。

アップロード
$ gsutil cp ./env.yaml gs://[bucket_name]/env.yaml
ダウンロード
$ gsutil cp gs://[bucket_name]/env.yaml ./env.yaml

ちなみに gsutil コマンドは Google Cloud SDK をインストールしたときにインストールされているかと思います。

環境変数を使わない運用

公開したくない情報をGit管理しないために環境変数を使う方法もありますが、 Google Cloud Storage を直接 Google Cloud Function から読み込んで環境変数を使わない方法もあります。

IAMの設定

Google Cloud Functions から Cloud Storage の情報を取得するために、必要に応じてサービスアカウントに対して権限を付与します。ちなみにサービスアカウントとは Google Cloud Platform の各サービスが、疑似ユーザーのように振る舞う仕組みです。

Google Cloud Functions のサービスアカウント

サービスアカウントはコンソール画面から確認できます。

スクリーンショット-2019-08-03-14.58.32.png

サービスアカウントに権限を付与

おそらくデフォルトのままでも大丈夫ですが、権限が足りない場合は「IAM と管理」から対象のサービスアカウントを編集して↓の権限を付与します。

ストレージ→ストレージ オブジェクトの閲覧者

※ なお、これは「バケットレベルで権限を一様に設定(バケットポリシーのみ)」のアクセス制御モデルで作成したバケットを使用しています。

ライブラリのインストール

ここでは google-cloud-storage を利用してみます。

$ pip install google-cloud-storage

実装

google-cloud-storage を利用したときの実装例です。

import os
import yaml

from google.cloud import storage


def get_data():
    client = storage.Client()
    bucket = client.get_bucket('[bucket_name]')
    blob = storage.Blob('env.yaml', bucket)
    return yaml.load(blob.download_as_string())


os.environ.update(get_data())

FOO = os.getenv('FOO')  # 'bar'
BAZ = os.getenv('BAZ')  # 'boo'

ローカルでの開発

なお、ローカルで開発する場合は↓のようにログインする必要があります。

$ gcloud auth application-default login

サンプルコード

https://github.com/itkr/my-gcf-tutorial

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

Windowsにサイクロマティック複雑度の計測ツール「lizard」入れてみた

目的

業務で簡単に品質等を確認したい

環境

OS:Windows10
Python
pip
lizard
jinja2

環境作成ではまりそうなポイント

Could not install packages due to an EnvironmentError: [WinError 5] アクセスが拒否されました 。: 'c:\\program files\\python37\\lib\\site-packages\\pip-19.0.3.dist-info\\entry_points.txt'
Consider using the `--user` option or check the permissions.

You are using pip version 19.0.3, however version 19.2.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.

このようなエラーになった場合は、コマンドプロンプトを管理者権限で起動させてください。そうすることで以下のように成功します。

C:\WINDOWS\system32>python -m pip install --upgrade pip
Collecting pip
  Using cached https://files.pythonhosted.org/packages/62/ca/94d32a6516ed197a491d17d46595ce58a83cbb2fca280414e57cd86b84dc/pip-19.2.1-py2.py3-none-any.whl
Installing collected packages: pip
  Found existing installation: pip 19.0.3
    Uninstalling pip-19.0.3:
      Successfully uninstalled pip-19.0.3
Successfully installed pip-19.2.1

lizardのインストール

C:\WINDOWS\system32>pip install lizard
Collecting lizard
  Using cached https://files.pythonhosted.org/packages/7e/01/650cfb0e613217003fe29d7edba549b71731122a5dc6e61fc41ad6f58fb8/lizard-1.16.3-py2.py3-none-any.whl
Installing collected packages: lizard
Successfully installed lizard-1.16.3

操作方法

標準出力
今回はJavaのソースのCCNを計測してみます。

-l オプションの値は言語に応じて変更してください。

プロジェクトのルートフォルダへ移動してlizard実行

C:\spring-boot-rest-api-sample-master\web-demo-2\spring\demo-2>
lizard -l java
================================================
  NLOC    CCN   token  PARAM  length  location
------------------------------------------------
      43      8    307      1      48 MavenWrapperDownloader::main@55-102@.\.mvn\wrapper\MavenWrapperDownloader.java
       9      1     75      2       9 MavenWrapperDownloader::downloadFileFromURL@104-112@.\.mvn\wrapper\MavenWrapperDownloader.java
       5      1     37      2       5 IndexController::hello@18-22@.\src\main\java\com\web\demo\controller\IndexController.java
       5      1     37      2       5 IndexController::formHoge@25-29@.\src\main\java\com\web\demo\controller\IndexController.java
       5      1     37      2       5 IndexController::root@32-36@.\src\main\java\com\web\demo\controller\IndexController.java
       3      1     20      1       3 WebDemo2Application::main@11-13@.\src\main\java\com\web\demo\WebDemo2Application.java
       2      1      5      0       2 WebDemo2ApplicationTests::contextLoads@13-14@.\src\test\java\com\web\demo\WebDemo2ApplicationTests.java
4 file analyzed.
==============================================================
NLOC    Avg.NLOC  AvgCCN  Avg.token  function_cnt    file
--------------------------------------------------------------
     69      26.0     4.5      191.0         2     .\.mvn\wrapper\MavenWrapperDownloader.java
     28       5.0     1.0       37.0         3     .\src\main\java\com\web\demo\controller\IndexController.java
     10       3.0     1.0       20.0         1     .\src\main\java\com\web\demo\WebDemo2Application.java
     12       2.0     1.0        5.0         1     .\src\test\java\com\web\demo\WebDemo2ApplicationTests.java

=============================================================================================
No thresholds exceeded (cyclomatic_complexity > 15 or length > 1000 or parameter_count > 100)
==========================================================================================
Total nloc   Avg.NLOC  AvgCCN  Avg.token   Fun Cnt  Warning cnt   Fun Rt   nloc Rt
------------------------------------------------------------------------------------------
       119      10.3     2.0       74.0        7            0      0.00    0.00

メソッドごとのCCN→ファイルごとのCCN平均→全ファイルのCCN平均 の順で出力されます。

「メソッドごとのCCN」について、各列の意味は以下の通りです。

説明
NLOC      Number Line Of Code コードの行数(コメントを除く)
CCN Cyclomatic Complexity Number サイクロマティック複雑度
token -
PARAM PARAMeter メソッドの引数の数
length - NLOCから空行を除いた行数
location - {メソッド名}@{開始行}-{終了行}@{ファイルパス}

CSV出力

lizard -l java --csv > resule_lizard.csv

image.png

HTML出力

pip install jinja2
Collecting jinja2
  Downloading https://files.pythonhosted.org/packages/1d/e7/fd8b501e7a6dfe492a433deb7b9d833d39ca74916fa8bc63dd1a4947a671/Jinja2-2.10.1-py2.py3-none-any.whl (124kB)
     |████████████████████████████████| 133kB 242kB/s
Collecting MarkupSafe>=0.23 (from jinja2)
  Downloading https://files.pythonhosted.org/packages/65/c6/2399700d236d1dd681af8aebff1725558cddfd6e43d7a5184a675f4711f5/MarkupSafe-1.1.1-cp37-cp37m-win_amd64.whl
Installing collected packages: MarkupSafe, jinja2
Successfully installed MarkupSafe-1.1.1 jinja2-2.10.1

HTML形式で出力する場合、jinja2というツールが必要なためpipでインストールする

lizard -l java --html > resule_lizard.html

image.png

まとめ

今回はJavaファイルのみの検証でしたが、JSファイルの検証等もできたので
いろいろ試してみてください

image.png

参考

https://qiita.com/uhooi/items/a1a96a2d7f5e081e2049
https://qiita.com/uhooi/items/c77a53a4c7ac232a1ba1

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

[備忘録] OpenCVのHoughCirclesは二値化してからやるかどうか

OpenCVのハフ変換による円検出(cv2.HoughCircles)をやるとき、
普通はモノクロ画像に対して行うのが基本だけど

  • 検出したい円が結構綺麗
  • 二値化するといい感じ

のときはcv2.thresholdで二値化してからの方がうまく円を検出できることがある。
ただし、多くの場合param2を小さくしないと無理。

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

[備忘録] Pythonでディレクトリの中の条件に合うファイルを取り出す

リスト内包表記を使ってこんな風に書けば良い。

path = '任意のディレクトリ'
file = [i for i in os.listdir(path) if 条件]

例えば 'image' というディレクトリに入っている jpgファイルを取り出したかったら以下のようにすれば良い。
ただし jpgファイルでなくても '.jpg' という文字列が含まれていたらそのファイルも返してしまうので注意。

path = 'image'
jpgfile = [i for i in os.listdir(path) if '.jpg' in i]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

gbkファイルから階級を取得する

最初に

gbkファイルは以下のようになっている

NC_018118.gbk
LOCUS       NC_018118              14932 bp    DNA     circular INV 13-JUL-2012
DEFINITION  Elodia flavipalpis mitochondrion, complete genome.
ACCESSION   NC_018118
VERSION     NC_018118.1
DBLINK      Project: 170288
KEYWORDS    RefSeq.
SOURCE      mitochondrion Elodia flavipalpis (parasitic fly)
  ORGANISM  Elodia flavipalpis
            Eukaryota; Metazoa; Ecdysozoa; Arthropoda; Hexapoda; Insecta;
            Pterygota; Neoptera; Holometabola; Diptera; Brachycera;
            Muscomorpha; Oestroidea; Tachinidae; Exoristinae; Goniini; Elodia.
REFERENCE   1  (bases 1 to 14932)
  AUTHORS   Zhao,Z., Su,T.-J. and Zhu,C.-D.
  TITLE     The complete mitochondrial genome of Elodia flavipalpis Aldrich
            (Diptera: Tachinidae) and the evolutionary timescale of tachinid
            flies
  JOURNAL   Unpublished
REFERENCE   2  (bases 1 to 14932)
  CONSRTM   NCBI Genome Project
  TITLE     Direct Submission
  JOURNAL   Submitted (10-JUL-2012) National Center for Biotechnology
            Information, NIH, Bethesda, MD 20894, USA
REFERENCE   3  (bases 1 to 14932)
  AUTHORS   Zhao,Z., Su,T.-J. and Zhu,C.-D.
  TITLE     Direct Submission
  JOURNAL   Submitted (03-JAN-2012) Key Laboratory of Zoological Systematics
            and Evolution, Institute of Zoology, Chinese Academy of Sciences, 1
            Beichen West Road, Chaoyang District, Beijing, Beijing 100101, P.R.
            China
COMMENT     REVIEWED REFSEQ: This record has been curated by NCBI staff. The
            reference sequence is identical to JQ348961.
            COMPLETENESS: full length.
FEATURES             Location/Qualifiers
     source          1..14932
                     /organism="Elodia flavipalpis"
                     /organelle="mitochondrion"
                     /mol_type="genomic DNA"
                     /db_xref="taxon:1146932"
                     /common="parasitic fly"
     tRNA            1..65
                     /product="tRNA-Ile"
\\以下略

例えばこのファイルを綱で分類したい時、次のようにする。

taxonomy IDを取得する

NCBI Taxonomy データベースからTaxonomy ID を取得する

from Bio import SeqIO, Entrez

Entrez.email = "your mail address"

def get_taxonomyID(creature_name):
    global Entrez.email

    with Entrez.esearch(db="Taxonomy", term=creature_name) as handle:
       record = Entrez.read(handle)
    taxonomyID = record["IdList"][0]
    return taxonomyID

ここでtaxonomyID = record["IdList"][0]としているので複数ヒットした場合は最初のヒットが返される。
また、エラーが出る場合はクエリが冗長すぎる(space以下には新規IDを振らないようにしているらしい)ので別のものをクエリとするしなければいけない。例えば引数をSeqRecord.annotations["taxonomy"]にして各taxonomyに対してtaxonomyIDを取得するようにするとか。。。

for taxa inSeqRecord.annotations["taxonomy"]:
    with Entrez.esearchd(db="Taxonomy", term=taxa) as handle:
...

取得したtaxonomy IDを使って階級を取得する

さっきの関数で取得したTaxonomy IDをもう一度投げてエントリを取得する

def get_category_dictionary(taxonomyID):
    global Entrez.email

    with Entrez.efetch(db="Taxonomy", id=taxonomyID, retmode="xml") as handle:
        records = Entrez.read(handle)
    for record in records:
        category_dict = record["LineageEx"]
        yield category_dict

まとめると

from Bio import SeqIO, Entrez

Entrez.email = "your email address"

def get_taxonomyID(creature_name):
    with Entrez.esearch(db="Taxonomy", term=creature_name) as handle:
       record = Entrez.read(handle)
    taxonomyID = record["IdList"][0]
    return taxonomyID

def get_category_dictionary(taxonomyID):
    with Entrez.efetch(db="Taxonomy", id=taxonomyID, retmode="xml") as handle:
        records = Entrez.read(handle)
    for record in records:
        category_dict = record["LineageEx"]
        yield category_dict


if __name__ == "__main__":
    with open("NC_018118.gbk", "r") as gbkfile:
        for entry in SeqIO.parse(gbkfile, "genbank"):
            taxonomyID = get_taxonomyID(entry.annotations["organism"])
            for category_dict_list in get_category_dictionary(taxonomyID):
                for category_dict in category_dict_list:
                    print(f"{category_dict['ScientificName']} : {category_dict['Rank']}")

みたいな感じになります。

実行結果(長いので折りたたみ)


cellular organisms : no rank
Eukaryota : superkingdom
Opisthokonta : no rank
Metazoa : kingdom
Eumetazoa : no rank
Bilateria : no rank
Protostomia : no rank
Ecdysozoa : no rank
Panarthropoda : no rank
Arthropoda : phylum
Mandibulata : no rank
Pancrustacea : no rank
Hexapoda : subphylum
Insecta : class
Dicondylia : no rank
Pterygota : subclass
Neoptera : infraclass
Holometabola : cohort
Diptera : order
Brachycera : suborder
Muscomorpha : infraorder
Eremoneura : no rank
Cyclorrhapha : no rank
Schizophora : no rank
Calyptratae : no rank
Oestroidea : superfamily
Tachinidae : family
Exoristinae : subfamily
Goniini : tribe
Elodia : genus

こんな感じで出力されるので、今回のNC_018118はInsectaに分類!みたいなことができます

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