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

中心極限定理 試してみた

前提 母集団の平均を$ μ $、分散を$ σ^2 $とおく 中心極限定理は、 母集団から抽出した標本の平均$ \overline{x} $は平均μ、分散$ σ^2/n $の正規分布に従う というものである。(平均・分散が存在しさえすれば母集団の分布に寄らない) 実際にpythonでサンプル数を増やして計算してみた pythonでサイコロを例に試行 サイコロを1回振った時の平均$μ$と分散$σ^2$はそれぞれ$ μ=3.5 $、$ σ^2=35/12 $ である 今回は抽出する標本数nを変化させて、その標本平均と標準偏差それぞれを計算してみる (pythonのstreamlitライブラリを使用) import streamlit as st import numpy as np import pandas as pd st.title('n=10') n = 10 sample_mean = [] for i in range(10): # 1~6までのさいころのランダムな目 x = np.random.randint(1, 7, n) # 標本平均 sample_mean.append(x.mean()) df = pd.DataFrame({ '標本平均': sample_mean, }) # 結果 st.dataframe(df ,width=500) st.write('標本平均の標準偏差:{}'.format(np.var(sample_mean))) st.title('n=1000') n = 1000 sample_mean = [] for i in range(10): # 1~6までのさいころのランダムな目 x = np.random.randint(1, 7, n) # 標本平均 sample_mean.append(x.mean()) df = pd.DataFrame({ '標本平均': sample_mean, }) # 結果 st.dataframe(df ,width=500) st.write('標本平均の標準偏差:{}'.format(np.var(sample_mean))) nが大きいほど、平均は3.5に近づいていき、標準偏差は小さくなることが分かる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progateで作ったWebアプリをDjangoで作ってみる2! Part3 -ログイン機能編-

目標物の確認 ProgateのNode.jsコースで作ったブログアプリと同じものをDjangoで作ってみます。 Djangoでのアプリ開発の一連の流れを整理するために記していきます。 完成イメージ ログイン機能の実装 Djangoでは簡単にログイン機能を実装できます。 authenticate()とlogin()を使うため、importします(☆1)。 authenticate()は、loginの前段階として、formから送られてきたデータとUserテーブルのデータを照合するために使用します。照合が無事にできた場合は、オブジェクトが返されます(☆2)。ウェブサイトではログインにメールアドレスが使われるのが一般的です。ですが、Djangoではデフォルトでユーザー名を使っており、メールアドレスでのログインは少し面倒なので今回はユーザー名を使います。 if user is not Noneというコードは二重否定になっているため、userがNoneでなければ、つまりuserがいればif user is not None:の下の行のコードが実行されます(☆3)。 login()を使うことによってユーザーをログイン状態にすることができます(☆4)。ログイン状態の管理にはsessionが使われます。sessionはユーザーの状態を記録するために用いられる機能であり、login()を使うことによって、ログインしたユーザーの情報をサーバーのsessionの中に保存できます。setting.pyファイルを見ると、MIDDLEWAREという変数があり、その中に'django.contrib.sessions.middleware.SessionMiddleware'`というコードがあります。Djangoがあらかじめsessionの仕組みを準備してくれており、勝手にsession機能を使うことができます。 blogapp/blog/views.py from django.shortcuts import render, redirect from django.views.generic import TemplateView from django.contrib.auth.models import User from django.db import IntegrityError from django.contrib.auth import authenticate, login # ☆1 class BlogTop(TemplateView):... def signupview(request):... def loginview(request): if request.method == 'POST': username = request.POST['username'] # ☆2 password = request.POST['password'] # ☆2 user = authenticate(request, username=username, password=password) # ☆2 if user is not None: # ☆3 login(request, user) # ☆4 return redirect('blog:list') else: return redirect('blog:login') return render(request,'blog/login.html') ログイン用のページも作ります。 login.html {% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>BLOG</title> <link rel="stylesheet" href="{% static 'blog/style.css' %}"> </head> <body> <div class="sign"> <div class="container"> <h1><a href="{% url 'blog:list' %}">BLOG</a></h1> <div class="panel"> <h2>ログイン</h2> <form action="" method="post">{% csrf_token %} <p>ユーザー名</p> <input type="text" name="username"> <p>パスワード</p> <input type="password" name="password"> <input type="submit" value="ログイン"> <a href="{% url 'blog:list' %}">一覧にもどる</a> </form> </div> </div> </div> </body> </html> 最後に、urls.pyを編集して完了です(☆5)。 blogapp/blog/urls.py from django.urls import path from .views import BlogTop, signupview, loginview # ☆5 app_name = 'blog' urlpatterns = [ path('', BlogTop.as_view(), name='top'), path('signup/', signupview, name='signup'), path('login/', loginview, name='login'), # ☆5 ] これでログインページの実装は完了です!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3とOpenCVで動画リサイズ出力(音声なし)

はじめに 初投稿なのでご容赦を。 自分が探し求めるプログラムが見つからないので既存のものを参考にしつつ改良しました。 どんなの? iPhoneで撮影された動画は4K画質(mov)なのですが、このままでは重すぎるのでHD画質まで落とした動画(mp4)を作成します。 後々、音声も合成できるようにしたものを上げるかもしれません。 プログラム本体 convert.py import cv2 import sys args = sys.argv filepath = args[1] outname = args[2] # encoder(for mp4) fourcc = cv2.VideoWriter_fourcc('m', 'p', '4', 'v') # output file name, encoder, fps, size(fit to image size) video = cv2.VideoWriter(outname, fourcc, 60.0, (1920, 1080)) cap = cv2.VideoCapture(filepath) if not video.isOpened(): print("can't be opened") sys.exit() while True: ret, frame = cap.read() if frame is None: print("can't read") break frame = cv2.resize(frame, dsize=(1920, 1080)) video.write(frame) cv2.imshow('Frame', frame) cv2.waitKey(1) cap.release() cv2.destroyAllWindows() print("written") 変換した動画のfps、sizeは各自設定してください。(例ではfps 60、size 1920x1080) コマンドプロンプトで実行 ※pythonやopencvはダウンロードが済んでいることとします。 $ python covert.py (変換元ファイル) (出力ファイル名.mp4) 例 $ python convert.py .\Video\Original.MOV .\Video\Converted.mp4 おわりに 私のPCのスペック不足のためにこんなことになりました。 4K動画で編集したいですね~。 引用等 「OpenCV - 動画のサイズを変更したい|teratail」https://teratail.com/questions/159381 「Python3+OpenCVを使って連番画像から動画を作る」https://qiita.com/penpenpen/items/acfa194cee45190cd8f3
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonをAlpineで安易に動かす問題点

サマリ Alpine × Pythonの問題点については度々指摘されています? Using Alpine can make Python Docker builds 50× slower 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 その原因のほとんどがAlpine同梱のCライブラリ「musl(マッスル)」に由来します ビルド時間が遅延したり、謎のバグを踏んだりします AlpineイメージのPythonを安易に導入した結果、その全ての問題をツモり悲しくなったので共有します? 事象①:異常にビルド時間がかかる? 起きたこと Dockerイメージのビルドにメチャクチャ時間がかかりました。 ビルドのログを眺めていると、ライブラリ1のpip installに30分程時間を要しているのが確認できました? 原因 前述したこちらの記事に答えがありました。 Using Alpine can make Python Docker builds 50× slower Standard PyPI wheels don’t work on Alpine 簡単にまとめると、 PyPIにはライブラリのバイナリがホストされている(なのでpip installで素早くインストール可能) ただしこのバイナリはAlpine非対応のため使えない。Cコードをイチからコンパイルする必要アリ ということです。 ライブラリのバイナリがAlpine(というかmusl)を想定していないため、毎回一生懸命コンパイルし、結果として鬼のようにビルド時間がかかってしまっていたわけです。 こちらの記事にも同様の説明がありました。 PyPIはLinux向けにはmanylinux1という形式でバイナリを提供しており、DebianでもRedHatでも高速にインストールできます。しかし、この形式はAlpineには対応していないため、C拡張を使うライブラリを使うと、Dockerイメージのビルド時間が伸びまくってしますわけです。 それでも、どうしても、PurePythonで処理速度はどうでも良い/お金がたくさんある、あるいはC拡張を使う場合でも人生を犠牲にしてでも、イメージサイズをどうしても減らしたいみたいな選ばれし者はAlpineを使う感じでしょうかね。 事象②:Segmentation Faultが起こる? 起きたこと AlpineイメージのPythonをCloud Runにデプロイし遊んでいたのですが、Uncaught signal: 11なるエラーを吐きCloud Runくんが昇天することが多発しました。 Uncaught signal: 11はSegmentation Faultを表しています。 余談ですが、厄介だったのは、ローカルでは再現しなかったことと、Cloud Run上でも数回に一度しか再現しなかったことです。 原因 muslのスレッドのスタックサイズに問題があったようです。 golang binary hacks ・muslはスレッドに対して極端に少ないスタックを割り当てる (中略) ・Ruby・Python含む様々なプロジェクトがmuslのスタックオーバーフローに辛酸を舐めさせられている スタックオーバーフローはセグフォの一因になり得ますのでこちらの影響を受けた可能性が高いです。 結論 余程のことがない限りPythonならAlpine以外を使用しましょう。 (補足ですが、使い方に依ればAlpineイメージでもPythonは問題なく動きます。全てを否定するわけではありません?‍♂️) grpcio ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonをAlpineイメージで安易に動かす問題点

サマリ Alpine × Pythonの問題点については度々指摘されています? Using Alpine can make Python Docker builds 50× slower 軽量Dockerイメージに安易にAlpineを使うのはやめたほうがいいという話 その原因のほとんどがAlpine同梱のCライブラリ「musl(マッスル)」に由来します ビルド時間が遅延したり、謎のバグを踏んだりします AlpineイメージのPythonを安易に導入した結果、その全ての問題をツモり悲しくなったので共有します? 事象①:異常にビルド時間がかかる? 起きたこと Dockerイメージのビルドにメチャクチャ時間がかかりました。 ビルドのログを眺めていると、gRPC関連ライブラリ1のpip installに30分程時間を要しているのが確認できました? 原因 前述したこちらの記事に答えがありました。 Using Alpine can make Python Docker builds 50× slower Standard PyPI wheels don’t work on Alpine 簡単にまとめると、 PyPIにはC拡張ライブラリのバイナリがホストされている(なのでpip installで素早くインストール可能) ただしこのバイナリはAlpine非対応のため使えない。Cコードをイチからコンパイルする必要アリ ということです。 通常のライブラリならAlpineでも問題ないですが、C拡張ライブラリの場合は毎回一生懸命コンパイルすることになり、結果として鬼のようにビルド時間がかかってしまっていたわけです。 こちらの記事にも同様の説明がありました。 PyPIはLinux向けにはmanylinux1という形式でバイナリを提供しており、DebianでもRedHatでも高速にインストールできます。しかし、この形式はAlpineには対応していないため、C拡張を使うライブラリを使うと、Dockerイメージのビルド時間が伸びまくってしますわけです。 それでも、どうしても、PurePythonで処理速度はどうでも良い/お金がたくさんある、あるいはC拡張を使う場合でも人生を犠牲にしてでも、イメージサイズをどうしても減らしたいみたいな選ばれし者はAlpineを使う感じでしょうかね。 事象②:Segmentation Faultが起こる? 起きたこと AlpineイメージのPythonをCloud Runにデプロイし遊んでいたのですが、Uncaught signal: 11なるエラーを吐きCloud Runくんが昇天することが多発しました。 Uncaught signal: 11はSegmentation Faultを表しています。 余談ですが、厄介だったのは、ローカルでは再現しなかったことと、Cloud Run上でも数回に一度しか再現しなかったことです。 原因 muslのスレッドのスタックサイズに問題があったようです。 golang binary hacks ・muslはスレッドに対して極端に少ないスタックを割り当てる (中略) ・Ruby・Python含む様々なプロジェクトがmuslのスタックオーバーフローに辛酸を舐めさせられている スタックオーバーフローはセグフォの一因になり得ますのでこちらの影響を受けた可能性が高いです。 結論 余程のことがない限りPythonならAlpine以外を使用しましょう。 (補足ですが、使い方に依ればAlpineイメージでもPythonは問題なく動きます。全てを否定するわけではありません?‍♂️) grpcio ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ディープラーニング数学①

こんにちは!機械学習を勉強している初心者エンジニアです。今回から機械学習のアルゴリズム(特にディープラーニング)の理解に必要な数学の紹介・解説を行っていきたいと思います!Pythonを使いながら文系の方でも理解できるように作っていきます!理解できなかったところ、筆者の解釈の間違いありましたらコメントお願いいたします 今回は線形代数のスカラーとベクトルと行列について解説していきたいと思います! これは機械学習のアルゴリズムを理解するのに必須の知識です。また、ベクトルや行列の計算はプログラムを書く際でも役立つ知識になります。例えば、データフレームを扱うときは行列計算を知っていると助けになります。 それでは実際にpythonを使いながら説明していきます。 スカラー、ベクトル、行列について まずは簡単にスカラーとベクトルと行列の説明をします。 スカラー スカラーとは「大きさの性質のみを持つ数値」です。つまり単なる数値ですね。これは説明不要ですね。 # スカラー scaler = 3 print('Scaler: ', scaler) output Scaler: 3 ベクトル ベクトルとは「向きと大きさを持つ量」です。少しわかりにくいですが、言葉で簡単にいうと矢印の方向に進む量を表しています! プログラム上のベクトルの形と2次元グラフのベクトルを書いてみます! # ベクトル vector = np.arange(1, 6) print('Vector: ', vector) # ベクトルの描画 plt.quiver(0,0,3,2, angles='xy', scale_units='xy', scale=1) plt.text(3,2, '[3, 2]', color='red', size=15) # ラベル plt.xlabel("x", fontsize=16) plt.ylabel("y", fontsize=16) # x軸とy軸 plt.axhline(0, color="gray") plt.axvline(0, color="gray") # リミット plt.xlim(-3,3) plt.ylim(-3,3) plt.grid() plt.show() output Vector: [1 2 3 4 5] ↑5次元配列 上記のようになりました。ベクトルは特定の位置(この場合は原点)からの方向と大きさを表すものというのがわかりますね。 また、普通参考書や図ではベクトルは次のように表されます。 x=\left(\begin{array}{c}x_{1}\\x_{2}\\x_{3}\\.\\x_{n}\end{array}\right) x=(x_{1},x_{2},x_{3}...x_{n}) \overrightarrow{ab} または X などで表されます。なんでこんな形になるとかは使っていくうちにわかると思います。 まずはこんな形が出たらベクトルだと思ってください! ベクトルのいろいろな計算 ベクトルとは何だというのは理解できたと思うので、ベクトルの求め方、ベクトル同士の計算などをみていきたいと思います。この計算は機械学習の理解において必須ではないですが、プログラムを書く際に役に立ちますので軽く説明していきます。 ※ベクトルは線形性の性質を持つので、何次元に拡張されようが計算方法は変わらず計算量のみ増えるので、見てわかりやすい2次元で全て説明していきます。 ベクトルの求め方 2地点A,Bがグラフ上に置かれているとき、三平方の定理を使うことでベクトルを求めることができます。 先ほど使ったベクトルの図を使ってみましょう。 この図から2点、(0, 0)と(3, 2)があるのがわかります。そしてこのように直角三角形が描けるのがわかります。 三平方の定理とは2辺の長さがわかるときそれ以外の1辺の長さがわかるというものです。 なのでベクトルは次の式で求めれます。 c=\sqrt{a^{2}+b^{2}} この図で言うと c=\sqrt{3^{2}+2^{2}} となります。 2つのベクトルの和と差と距離 ベクトルの和と差は単純に長さの合計と長さの差となります。なので式はとてもシンプルです。 ベクトルの和: C=A+B=(a_{1}+b_{1},a_{2}+b_{2}) ベクトルの差: C=A-B=(a_{1}-b_{1},a_{2}-b_{2}) 非常にシンプルな式となりました。単純な足し算引き算ですね。 ここで小文字のa,bなどでてきてますが、これは「成分表示」されている状態です。ベクトルAを2次元のベクトルと考えると2点であらわせれると言うのはわかると思います。ベクトルは下記のようにあらわせれます。ベクトル同士の色々な計算は同じ方向の成分同士を計算することでできます。 A=a_{1}+a_{2} 次に距離でベクトル同士の距離を計算することができます。distanceの公式を使うことで求めれます。 d=|A-B|=\sqrt{(a_{1}-b_{1})^{2}+(a_{2}-b_{2})^{2}} ベクトルの内積 ここの知識は勾配降下法の理屈を理解するのにとても役に立つと思います。式は以下の通りです A*B=a_{1}b_{1}+a_{2}-b_{2} これも次元数が増えても変わりません。 同じ方向の成分をかえて合計するだけです! 三角関数を使ってベクトルの内積の特徴をみてみましょう。(スライドの画像となります) 書いてある通り、内積は同じ方向の成分をかけること、つまり「ベクトルAの長さと、ベクトルBの ベクトルAと同じ向きの成分の長さをかけたもの」なので上記のように表すことができます。式は下記となります。 A*B=|A||B|\cos\theta そしてcosの特性を利用することで、内積の値が最大か0か最小になることがわかります。 これは後々説明する勾配降下法で役に立つ知識なので押さえておきましょう! それでは最後にまとめてpythonで計算をしてみましょう! # ベクトルの色々な計算 p1 = (0, 0) p2 = (4, 3) vector1 = np.array([2, 4]) vector2 = np.array([3, 5]) # 三平方の定理を使ってベクトルを求める c = np.sqrt((p1[0]+p2[0])**2+(p1[1]+p2[1])**2) print('vector: ', c) # ベクトルの和と差 add = vector1 + vector2 diff = vector2 - vector1 print('add: ', add) print('diff: ', diff) # ベクトルの距離 distance = np.linalg.norm(vector1-vector2) print('distance: ', distance) # ベクトルの内積 dot = np.dot(vector1, vector2) print('dot: ', dot) output vector: 5.0 add: [5 9] diff: [1 1] distance: 1.4142135623730951 dot: 26 行列 行列(マトリックス)とは値を縦と横に矩形状に配列したものです。図で表すとこんな感じです。 \begin{bmatrix}w_{11} & w_{12} \\w_{21} & w_{22} \end{bmatrix} また単位行列と呼ばれるものもあります。単位行列とは左から右にかけての対角線の値が「1」でそれ以外が「0」の値を持つ行列です。ディープラーニングでは勾配降下法(次回以降説明します)を使っての学習を最適化するために初期値の重みの値を単位行列にすることがあります。 図で表すとこんな感じです。 \begin{bmatrix}1 & 0 \\0 & 1 \end{bmatrix} pythonで普通の行列と単位行列の作成をしてみます。 # 行列の作成 # 2x2の行列 matrix = [[0, 1], [2, 3]] print('普通の行列:\n', np.array(matrix)) # 0の値の行列 zero_matrix = np.zeros((2,2)) print('\n0の行列:\n', zero_matrix) # 単位行列 e_matrix = np.eye(2,2,k=0) print('\n単位行列行列:\n', e_matrix) output 普通の行列: [[0 1] [2 3]] 0の行列: [[0. 0.] [0. 0.]] 単位行列行列: [[1. 0.] [0. 1.]] 行列の生成方法は他にもまだまだあります。興味ありましたら調べてみてください。ここで大事なのは行列とはpythonで言う2次元配列のことを指すと言うことです! ベクトルx行列の計算 これは公式の理解に必須の知識となっています。みなさんお馴染みのグラフの式y=ax+bがあると思いますが、それを拡張したのがニューラルネットワークなのです。aとbはWに置き換わり表現されています。ここではまだDLに入る前の段階なので詳しく上げません。下記の図を見てこの計算はベクトルと行列の計算なんだなと思っていてください! では本題に入ります。ベクトルと行列の積では列番号が同じところがかけらるので、列数が一緒じゃないと計算ができません。 \begin{bmatrix}w_{11} & w_{12} \\w_{21} & w_{22} \end{bmatrix}*\left[x_{1},x_{2}\right] = \begin{bmatrix}w_{11}x_{1} & w_{12}x_{2} \\W_{21}x_{1} & w_{22}x_{2} \end{bmatrix} 各ベクトルの値が対応する列でかけられているのがわかりますね。 pythonでも書いてみましょう!他の計算も書いておきます。 vector = np.arange(1, 3) matrix = np.array([[0, 1], [2, 3]]) print('使用するベクトル: ', vector) print('使用する行列:\n', matrix) # ベクトルx行列 print('\nvector*matrix:\n', vector * matrix) # ベクトルxベクトル print('\nvector*vector:\n', vector * vector) # ベクトルxベクトル print('\nmatrix*matrix:\n', matrix * matrix) output 使用するベクトル: [1 2] 使用する行列: [[0 1] [2 3]] vector*matrix: [[0 2] [2 6]] vector*vector: [1 4] matrix*matrix: [[0 1] [4 9]] まとめ 以上でベクトルと行列の解説は終わりです。理解してしまえばすごく簡単だと思います!ベクトルと行列はプログラムを書いていく上で有用な知識になると思いますのでぜひマスターしましょう!今回特に大事なのはベクトルx行列とベクトルの内積です!内積ではベクトル間の角度で内積の値が変動することをぜひ覚えてください。ベクトルと行列の計算もディープラーニングでは必須ですのでこれも押さえましょう! 指摘やわかりにくいと言った場合はコメントお願いいたします。 次回は微分とちょっと積分をします!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

python 漢数字から算用数字に変換

正規表現バージョン import re TRANSUNIT = {'十': 10, '拾': 10, '百': 100, '千': 1000, '万': 10000, '億': 100000000, '兆': 1000000000000} re_kunit = re.compile( "(?:(\d*)\s*(千)\s*)?(?:(\d*)\s*(百)\s*)?(?:(\d*)\s*([十拾])\s*)?(\d*)\s*([兆億万])|(?:(\d*)\s*(千)\s*)?(?:(\d*)\s*(百)\s*)?(?:(\d*)\s*([十拾])\s*)?(\d*)()") knum = "一二三四五六七八九〇1234567890壱弐参" anum = "12345678901234567890123" def kanji2num(s): for i in range(len(anum)): s = s.replace(knum[i], anum[i]) reg = re_kunit.findall(s) if not reg: return None ret = 0 count = len(reg) for i in range(count): r = reg[i] digit = 0 offset = 0 if (not r[7]): offset = 4 last_ar_idx = 2 * offset + 6 last_ks_idx = last_ar_idx + 1 for n in range(offset, offset + 3): ar = r[2 * n] ks = r[(2 * n) + 1] part = 1 if ar: part *= int(ar) if ks: part *= TRANSUNIT[ks] if(part > 1): digit += part if(r[last_ar_idx]): digit += int(r[last_ar_idx]) if(r[last_ks_idx]): digit *= TRANSUNIT[r[last_ks_idx]] ret += digit return ret print(kanji2num("1億3千万百9十9")) # -> 130000199 正規表現使わないバージョン 上の正規表現は重いみたい nums = {'〇': '0', '一': '1', '二': '2', '三': '3', '四': '4', '五': '5', '六': '6', '七': '7', '八': '8', '九': '9', '0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '0': '0', '1': '1', '2': '2', '3': '3', '4': '4', '5': '5', '6': '6', '7': '7', '8': '8', '9': '9', '零': '0', '壱': '1', '弐': '2', '参': '3'} def ktoi(s, is_large=True): ans = poss = 0 unit = '京兆億万' if is_large else '千百十' unitsize = len(unit) s = s.replace(',', '').replace(',', '') for i in range(0, unitsize + 1): pos = s.find(unit[i]) if i != unitsize else len(s) if pos == -1 or (i == unitsize and pos == poss): continue if i != unitsize and pos == poss: block = 1 elif is_large: block = ktoi(s[poss:pos], False) elif pos - poss == 1: block = int(nums[s[poss]]) else: block = int("".join(nums[x] for x in s[poss:pos])) power = unitsize - i if is_large: power *= 4 ans += block * (10 ** power) poss = pos + 1 return ans S = set('一二三四五六七八九十百千123456789123456789') U = set('〇万億兆京00,') def kanji2int(s): tmp = '' num = 0 for i in range(0, len(s) + 1): if i != len(s) and (s[i] in S or (tmp != '' and s[i] in U)): tmp += s[i] else: if tmp != '': num = ktoi(tmp) tmp = '' return num print(kanji2int("1億3千万百9十9")) # -> 130000199
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで体温を毎朝自動送信

はじめに 今更ながらpythonで、毎朝formsに体温を送信するプログラムを書きました。備忘録も 兼ねて手順を書いていこうと思います。 こんなやつ できるようになること 毎朝8:30~9:30のどこかで、formsにランダムな数値(35.5~36.5)を勝手に送信する。 筆者の環境 ・Windows 10 ・Chrome 92.0.4515.107 ・Python 3.7.6 目次 1.seleniumの準備 2.Googleアカウント、chromeの準備 3.コード作成 4.xpathの探し方 5.Windowsタスクスケジューラの設定 1.seleniumの準備 まずはseleniumの準備をします。pythonのモジュールの一種で、ブラウザを自由に操作できます。 1.1 Webdriverのインストール seleniumを使用するためにはWebdriverが必要です。ターミナルに下記のコマンドを打ちこみます。 pip install chromedriver-binary==92.0.4515.107 今回は92.0.4515.107としていますが、これは使用しているChromeのバージョンです。 chrome://version/の一番上にバージョンが書いてあります。 1.2 Seleniumのインストール 簡単です。ターミナルに以下のコマンドを入力してください。 pip install selenium 2.Googleアカウント、chromeの準備 コードを書きたいのですが、seleniumは別のChromeを起動した状態で、起動できないという問題があります。 正確に言うと、同じ--user-data-dicを参照したChromeが起動しているとき、seleniumが起動できません。 詳しく説明していきます。 何気なく使用しているChromeですが、実は起動時に --user-data-dicというコマンドライン引数をとっていて、ディレクトリを指定できます。このディレクトリにはサイトのパスワードやブックマーク等が保存されています(多分) 普段は自動で選択されていますが、seleniumはこれを指定しなければなりません。そして同じ--user-data-dicを参照したChromeが起動しているときにseleniumが起動できません。 そこでselenium用の--user-data-dicとGoogleアカウントを作成していきます。 2.1 --user-data-dic用ディレクトリ作成 適当な場所にディレクトリを作成します。今回はC:\Users\user名\selenium_userを作成しました。 2.2 Chromeの起動 win+Rでファイル名を指定して実行から、 "C:\Program Files (x86)\Google\Chrome\Application\chrome.exe" --user-data-dir="C:\Users\user名\selenium_user" を入力してChromeを起動してください。(もちろん--user-data-dirには先ほど作成したディレクトリのパスを入力してください。またchrome.exeの保存場所も人によって変わると思いますので、適宜変更してください。) するとログインされていない状態でChromeが起動できると思います。この状態でGoogleアカウントを作成してください。 ついでにmicrosoftアカウントのログインも済ませておいてください。 2.3 プロフィールパスの確認 --user-data-dirが普段使用しているものと区別できているかを確認するために、またchrome://version/を開いてください。プロフィールパスを確認すると、先ほど作成したC:\Users\user名\selenium_userが書いてあり、その直下のDefaultを参照してることがわかります。このDefaultはこの後使用します。 Defaultではないかもしれませんが(多分Profile 1とか)、問題はないので、この先のDefaultをすべてその名前に置き換えてください。 3.コード作成 さっそくコードを記載します。 temp_send.py import chromedriver_binary from selenium import webdriver import random options = webdriver.ChromeOptions() options.add_argument("--user-data-dir=C:/Users/user名/selenium_user") options.add_argument("--profile-directory=Default") options.add_argument('--headless') #ブラウザを非表示にする driver = webdriver.Chrome(options=options) url = "https://forms.office.com/" #送信したいページのURL driver.get(url) #urlのページを開く print(driver.current_url) taion = str(round(random.uniform(35.5,36.5), 1)) #ランダムな体温を決定 input_form = driver.find_element_by_xpath('//*[@id="form-container"]/div/div/div/input') input_form.send_keys(taion) #入力フォームに体温を入力 send_button = driver.find_element_by_xpath('//*[@id="form-container"]/div/div[1]/div[1]/button') send_button.click() #送信ボタンを押す driver.quit() #ブラウザを終了する 重要な部分を説明していきます。 options = webdriver.ChromeOptions() options.add_argument("--user-data-dir=C:/Users/user名/selenium_user") options.add_argument("--profile-directory=Default") seleniumへ入力するオプションを設定しています。 --user-data-dirには作成していたディレクトリのパスを指定しています。 --profile-directoryには調べておいたプロフィールパスの最終ディレクトリ名を入力してください。今回はDefaultを指定しています。 input_form = driver.find_element_by_xpath('//*[@id="form-container"]/div/div/div[1]/div/div[1]/div[2]/div/input') input_form.send_keys(taion) #入力フォームに体温を入力 send_button = driver.find_element_by_xpath('//*[@id="form-container"]/div/div/div[1]/div/div[1]/button') send_button.click() #送信ボタンを押す find_element_by_xpathで、指定したxpathの要素(ここでは入力フォーム)を取得し、send_keysで体温を実際に入力します。 今度は同様に送信ボタンのxpathを取得し、send_button.click()で送信ボタンをクリックします。 xpathってなんだよって思うかもしれないので見つけ方について次章で説明します。知っている方は飛ばしてください。 4.xpathの探し方 xpathがなんなのかは筆者は正直よくわかっていません。多分HTML内のそれぞれの要素のパスみたいなものだと思います。しかし見つけ方は簡単です。 Googleのホーム画面での見つけ方を例にしていきます。 まず ctrl+shit+i でデベロッパーツールを開きます。 いろいろ文字が出てきましたが、この文字の上をマウスで通ると、画面が青くなる時があると思います。その文字列の左端の▶を押してみてください。するとまた大量の文字が出てきます。 同様にこの文字列の上をマウスで通ると、今度は青くなる範囲が狭まったと思います。文字列を打ち込む部分が青く選択される文字列を探して、▶を押してください。 これを繰り返すと、開けるところがなくなり以下のようになると思います。注目してもらいたいのは水色で囲った部分。ここの文字列の最初に<input class=…と書いてあります。ここで右クリックからxpathをコピーできます。 こんな感じ ちなみに今回のxpathは/html/body/div[1]/div[3]/form/div[1]/div[1]/div[1]/div/div[2]/inputです。最後がinputで終わっていると成功だと思います。 これを実際のformsでも調べて、find_element_by_xpathの引数にしてください。 これは入力フォームの場合の手順ですが、送信ボタンの場合も同様です。ただし探すのは、<input class=…で始まる文字列ではなく、<button class=…で始まるものを探してください。xpathの最後もbuttonで終わっていると思います。 5.Windowsタスクスケジューラの設定 ここまでで作成したtemp_send.pyを毎朝勝手に実行してくれるように、windowsタスクスケジューラを使用します。 プログラム一覧からタスクスケジューラを検索し起動します。右側の基本タスクの作成を選択してください。(下の画像の水色で囲った部分) タスクの名前と説明、タスクの繰り返しタイミングを入力すると、タスクでどの操作を実行するか聞かれるので、プログラムの開始を選択してください。 次は以下の画像のように プログラム/スクリプト:python自体のファイルパス 引数の追加:作成したプログラムのファイル名 開始:作成したプログラムまでのパス を入力してください。筆者の場合はC:\Users\user名\work\temp_send.pyという構成です。 これでタスクを追加することができました。しかしこれでは毎日同じ時間に送信するだけになってしまうので、送信時間にランダム性を持たせていきます。 作成したタスクがタスク一覧に表示されているので、右クリックからプロパティを表示してください。トリガタブの編集を押すと詳細な設定画面が出てきます。 その中の遅延時間を指定するのボックスにチェックをし、時間を指定すれば終了です。 8:30開始、遅延時間1時間とすれば、8:30~9:30のどこかでプログラムが実行されます。 まとめ Pythonのseleniumモジュールと、windowsスケジューラを使って、体温自動送信プログラムを作成しました。 これで毎日の面倒な作業から解放されそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django Tutorial

目次 1. プロジェクトの作成 2. アプリケーションの作成 3. ページの作成 4. フォームを使用したページを作る 5. モデルを作成する 6. データベースを操作する For more information はじめに 実行環境:MacOS + Python3 + venv 1. プロジェクトの作成 下記コマンドを実行する。 mkdir project_name cd project_name python3 -m venv venv # create virtual env source venv/bin/activate # activate env django-admin startproject project_name # create project python manage.py runserver # run server projecy_nameは任意のプロジェクト名 venvは任意の環境名 2. アプリケーションの作成 下記コマンドを実行し、アプリを作成する。 python manage.py startapp app_name その後、settings.pyにアプリ名を追加する。 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app_name', # add app name ] ディレクトリ構成は以下の通り。 - project_name - manage.py # アプリケーション全体の管理を行う - project_name - asgi.py # 非同期Webアプリケーションのためのプログラム - settings.py # プロジェクトの設定情報を格納 - urls.py # URLを管理するファイル - wsgi.py # 一般的なWebアプリケーションのプログラム - app_name - migrations # データベース関連のファイルを格納 - admin.py # 管理者ツール - apps.py # アプリケーション本体の処理を記述 - models.py # モデルに関する処理を記述 - tests.py # テストに関する処理を記述 - views.py # 画面表示に関する処理を記述 3. ページを作成する はじめに、project_name/urls.pyを編集する。 project_name/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('app_name/', include('app_name.urls')) ] include()で、app_name内ファイルへの参照をapp/urls.pyに受け渡している include()の引数はapp_name/urls.py 次に、app_name/urls.pyを作成・編集する。 app_name/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), # index page ] 次に、views.pyに関数/クラスを追加する。 app_name/views.py from django.shortcuts import render def index(request): params = {'title': 'タイトル', 'msg': 'サンプルページです'} return render(request, 'hello/index.html', params) 変数は辞書で受け渡す 次に、renderするHTMLを作成する。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <h1>{{title}}</h1> <p>{{msg}}</p> </body> app_name/templates/app_name/にHTMLファイルを作成する 4. フォームを使用したページを作る 実行手順 はじめに、app_name/forms.pyを作成する。 app_name/forms.py from django import forms class HelloForm(forms.Form): name = forms.CharField(label='name') mail = forms.CharField(label='mail') age = forms.IntegerField(label='age') forms.Formを継承したクラスを作る fieldがそのまま入力フォームになる labelはフォームの前に表示されるラベル 引数を指定してバリデーションが設定可能 次に、views,pyにフォームを使用する関数を追加する(ここではindex.html)。 app_name/views.py from django.shortcuts import render from .forms import HelloForm def index(request): params = { 'title': 'Hello', 'msg': 'your data: ', 'form': HelloForm() } return render(request, 'hello/index.html', params) 次に、HTMLでフォームを作成する。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <h1>{{title}}</h1> <p>{{msg}}</p> <form action="{% url 'form' %}" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="click"> </form> </body> {{form}}とすれば良い "{% url 'form' %}"のformは、app_name/urls.pyのurlpatternsの引数nameに対応 formの値が{% url 'form' %}に渡され、実行される {% csrt_token %}はCSRF攻撃を避けるためのコマンド 最後に、フォームを受け取る関数を実装する。 app_name/views.py def form(request): if (request.method == 'POST'): msg = request.POST['msg'] params = { 'title':'Hello', 'msg':'こんにちは、' + msg + 'さん。', } return render(request, 'hello/index.html', params) else: raise Exception('Form was not accepted!') POSTによりフォームが与えられた時、request.method = 'POST'となる フォームの値はrequest.POST['キー']で取り出し可能 views.py内の1つの関数でGET/POSTの処理を行う この場合、request.method == 'POST'で条件分岐すれば良い。 app_name/views.py from django.shortcuts import render from .forms import HelloForm def index(request): params = { 'title': 'Hello', 'msg': 'your data: ', 'form': HelloForm() } if (request.method == 'POST'): params['msg'] = '名前: ' + request.POST['name'] + 'メール:' + request.POST['mail'] + '年齢' + request.POST['age'] params['form'] = HelloForm(request.POST) return render(request, 'hello/index.html', params) formにrequest.POSTを引き渡すと、フォームで送信した値がデフォルトで入力される views.py内の1つのクラスでGET/POSTの処理を行う GET/POSTの両方の処理を行う場合、クラスを使用した方が良い(詳細略)。 5. モデルを作成する はじめに、settings.pyを設定する。SQLiteを使用する場合はデフォルトの設定で良い(詳細略)。 次に、model.pyにクラスを追加する。 app_name/models.py from django.db import models class Friend(models.Model): name = models.CharField(max_length=100) mail = models.EmailField(max_length=200) gender = models.BooleanField() age = models.IntegerField(default=0) birthday = models.DateField() models.Modelを継承する 列をフィールドで定義 次に、マイグレーションする。 python manage.py makemigrations app_name python manage.py migrate 6. データベースを操作する 6.1 レコードを取得する 例として、views.pyでDBのレコードを取得する。 app_name/views.py from django.shortcuts import render from .models import Friend def index(request): data = Friend.objects.all() params = { 'title': 'Hello', 'message': 'all friends.', 'data': data } return render(request, 'hello/index.html', params) モデル名.object.all()で全レコード取得 モデル名.objects.all()はQuerySetというDjango特有のクラス 参考:Django逆引きチートシート(QuerySet編) また、QuerySetはSQL文を使用して検索が可能。 data = モデル名.object.raw('SQL文') テーブル名は'アプリ名_モデル名'となる 最後に、QuerySetをHTMLで受け取る。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body class="container"> <h1 class="display-4 text-primary">{{title}}</h1> <p class="h5 mt-4">{{message|safe}}</p> <table class="table"> <tr> <th>ID</th> <th>NAME</th> <th>GENDER</th> <th>MAIL</th> <th>AGE</th> <th>BIRTHDAY</th> </tr> {% for item in data %} <tr> <td>{{item.id}}</td> <td>{{item.name}}</td> <td>{% if item.gender == False %}male{% endif %}</td> <td>{% if item.gender == True %}female{% endif %}</td> <td>{{item.mail}}</td> <td>{{item.age}}</td> <td>{{item.birthday}}</td> </tr> {% endfor %} </table> </body> {% for 変数 in QuerySet %}で取り出し可能 参考:QuerySetはpandasのデータフレームにも変換可能 df = pd.DataFrame(list(モデル名.object.all().values())) 6.2 レコードを追加する 通常のフォームを使用する 例として、フォームで送信された値をDBに追加する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend from .forms import HelloForm def create(request): params = { 'title': 'Hello', 'form': HelloForm() } if (request.method == 'POST'): name = request.POST['name'] mail = request.POST['mail'] gender = 'gender' in request.POST age = int(request.POST['age']) birth = request.POST['birthday'] friend = Friend(name=name, mail=mail, gender=gender, age=age, birthday=birth) friend.save() return redirect(to='/hello') return render(request, 'hello/create.html', params) save()でレコードを追加 既にPrimary keyが存在する場合は、既存のレコードを更新する(後述) データ追加のためのフォームを使用する(forms.ModelForm) はじめに、forms.pyにクラスを追加する。 app_name/forms.py from django import forms from .models import Friend class FriendForm(forms.ModelForm): class Meta: model = Friend fields = ['name','mail','gender','age','birthday'] Metaはmodelの継承のためのクラス modelのフィールドを流用できる 次に、ModelFormを使用して、フォームで送信された値をDBに追加する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend from .forms import HelloForm, FriendForm def create(request): params = { 'title': 'Hello', 'form': HelloForm() } if (request.method == 'POST'): obj = Friend() friend = FriendForm(request.POST, instance=obj) friend.save() return redirect(to='/hello') return render(request, 'hello/create.html', params) 6.3 既存レコードを更新する 使用しているメソッドは追加の時と同様に、save()を使用する。 6.4 既存レコードを削除する QuerySetオブジェクトのdeleteメソッドを実行する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend def delete(request): friend = Friend.objects.get(id=num) if (request.method == 'POST'): friend.delete() return redirect(to='/hello') params = { 'title': 'Hello', 'id': num, 'obj': friend } return render(request, 'hello/delete.html', params) For more information Django逆引きチートシート(Model編) Django逆引きチートシート(QuerySet編) Django 逆引きチートシート (Template編)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django Quick Tutorial

目次 1. プロジェクトの作成 2. アプリケーションの作成 3. ページの作成 4. フォームを使用したページを作る 5. モデルを作成する 6. データベースを操作する For more information はじめに 実行環境:MacOS + Python3 + venv 1. プロジェクトの作成 下記コマンドを実行する。 mkdir project_name cd project_name python3 -m venv venv # create virtual env source venv/bin/activate # activate env django-admin startproject project_name # create project python manage.py runserver # run server projecy_nameは任意のプロジェクト名 venvは任意の環境名 2. アプリケーションの作成 下記コマンドを実行し、アプリを作成する。 python manage.py startapp app_name その後、settings.pyにアプリ名を追加する。 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'app_name', # add app name ] ディレクトリ構成は以下の通り。 - project_name - manage.py # アプリケーション全体の管理を行う - project_name - asgi.py # 非同期Webアプリケーションのためのプログラム - settings.py # プロジェクトの設定情報を格納 - urls.py # URLを管理するファイル - wsgi.py # 一般的なWebアプリケーションのプログラム - app_name - migrations # データベース関連のファイルを格納 - admin.py # 管理者ツール - apps.py # アプリケーション本体の処理を記述 - models.py # モデルに関する処理を記述 - tests.py # テストに関する処理を記述 - views.py # 画面表示に関する処理を記述 3. ページを作成する はじめに、project_name/urls.pyを編集する。 project_name/urls.py from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('app_name/', include('app_name.urls')) ] include()で、app_name内ファイルへの参照をapp/urls.pyに受け渡している include()の引数はapp_name/urls.py 次に、app_name/urls.pyを作成・編集する。 app_name/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.index, name='index'), # index page ] 次に、views.pyに関数/クラスを追加する。 app_name/views.py from django.shortcuts import render def index(request): params = {'title': 'タイトル', 'msg': 'サンプルページです'} return render(request, 'hello/index.html', params) 変数は辞書で受け渡す 次に、renderするHTMLを作成する。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <h1>{{title}}</h1> <p>{{msg}}</p> </body> app_name/templates/app_name/にHTMLファイルを作成する Templateで変数を取得するコマンドはこちらを参照 4. フォームを使用したページを作る 実行手順 はじめに、app_name/forms.pyを作成する。 app_name/forms.py from django import forms class HelloForm(forms.Form): name = forms.CharField(label='name') mail = forms.CharField(label='mail') age = forms.IntegerField(label='age') forms.Formを継承したクラスを作る fieldがそのまま入力フォームになる labelはフォームの前に表示されるラベル 引数を指定してバリデーションが設定可能 次に、views,pyにフォームを使用する関数を追加する(ここではindex.html)。 app_name/views.py from django.shortcuts import render from .forms import HelloForm def index(request): params = { 'title': 'Hello', 'msg': 'your data: ', 'form': HelloForm() } return render(request, 'hello/index.html', params) 次に、HTMLでフォームを作成する。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body> <h1>{{title}}</h1> <p>{{msg}}</p> <form action="{% url 'form' %}" method="post"> {% csrf_token %} {{ form }} <input type="submit" value="click"> </form> </body> {{form}}とすれば良い "{% url 'form' %}"のformは、app_name/urls.pyのurlpatternsの引数nameに対応 formの値が{% url 'form' %}に渡され、実行される {% csrt_token %}はCSRF攻撃を避けるためのコマンド 最後に、フォームを受け取る関数を実装する。 app_name/views.py def form(request): if (request.method == 'POST'): msg = request.POST['msg'] params = { 'title':'Hello', 'msg':'こんにちは、' + msg + 'さん。', } return render(request, 'hello/index.html', params) else: raise Exception('Form was not accepted!') POSTによりフォームが与えられた時、request.method = 'POST'となる フォームの値はrequest.POST['キー']で取り出し可能 views.py内の1つの関数でGET/POSTの処理を行う この場合、request.method == 'POST'で条件分岐すれば良い。 app_name/views.py from django.shortcuts import render from .forms import HelloForm def index(request): params = { 'title': 'Hello', 'msg': 'your data: ', 'form': HelloForm() } if (request.method == 'POST'): params['msg'] = '名前: ' + request.POST['name'] + 'メール:' + request.POST['mail'] + '年齢' + request.POST['age'] params['form'] = HelloForm(request.POST) return render(request, 'hello/index.html', params) formにrequest.POSTを引き渡すと、フォームで送信した値がデフォルトで入力される views.py内の1つのクラスでGET/POSTの処理を行う GET/POSTの両方の処理を行う場合、クラスを使用した方が良い(詳細略)。 5. モデルを作成する はじめに、settings.pyを設定する。SQLiteを使用する場合はデフォルトの設定で良い(詳細略)。 次に、model.pyにクラスを追加する。 app_name/models.py from django.db import models class Friend(models.Model): name = models.CharField(max_length=100) mail = models.EmailField(max_length=200) gender = models.BooleanField() age = models.IntegerField(default=0) birthday = models.DateField() models.Modelを継承する 列をフィールドで定義 次に、マイグレーションする。 python manage.py makemigrations app_name python manage.py migrate 6. データベースを操作する 6.1 レコードを取得する 例として、views.pyでDBのレコードを取得する。 app_name/views.py from django.shortcuts import render from .models import Friend def index(request): data = Friend.objects.all() params = { 'title': 'Hello', 'message': 'all friends.', 'data': data } return render(request, 'hello/index.html', params) モデル名.objectsはQuerySetというDjango特有のクラス モデル名.object.all()で全レコード取得 参考:Django逆引きチートシート(QuerySet編) また、QuerySetはSQL文を使用して検索が可能。 data = モデル名.object.raw('SQL文') テーブル名は'アプリ名_モデル名'となる 最後に、QuerySetをHTMLで受け取る。 app_name/templates/app_name/index.html <!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>{{title}}</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-giJF6kkoqNQ00vy+HMDP7azOuL0xtbfIcaT9wjKHr8RbDVddVHyTfAAsrekwKmP1" crossorigin="anonymous"> </head> <body class="container"> <h1 class="display-4 text-primary">{{title}}</h1> <p class="h5 mt-4">{{message|safe}}</p> <table class="table"> <tr> <th>ID</th> <th>NAME</th> <th>GENDER</th> <th>MAIL</th> <th>AGE</th> <th>BIRTHDAY</th> </tr> {% for item in data %} <tr> <td>{{item.id}}</td> <td>{{item.name}}</td> <td>{% if item.gender == False %}male{% endif %}</td> <td>{% if item.gender == True %}female{% endif %}</td> <td>{{item.mail}}</td> <td>{{item.age}}</td> <td>{{item.birthday}}</td> </tr> {% endfor %} </table> </body> {% for 変数 in QuerySet %}で取り出し可能 参考:QuerySetはpandasのデータフレームにも変換可能 df = pd.DataFrame(list(モデル名.object.all().values())) 6.2 レコードを追加する 通常のフォームを使用する 例として、フォームで送信された値をDBに追加する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend from .forms import HelloForm def create(request): params = { 'title': 'Hello', 'form': HelloForm() } if (request.method == 'POST'): name = request.POST['name'] mail = request.POST['mail'] gender = 'gender' in request.POST age = int(request.POST['age']) birth = request.POST['birthday'] friend = Friend(name=name, mail=mail, gender=gender, age=age, birthday=birth) friend.save() return redirect(to='/hello') return render(request, 'hello/create.html', params) save()でレコードを追加 既にPrimary keyが存在する場合は、既存のレコードを更新する(後述) データ追加のためのフォームを使用する(forms.ModelForm) はじめに、forms.pyにクラスを追加する。 app_name/forms.py from django import forms from .models import Friend class FriendForm(forms.ModelForm): class Meta: model = Friend fields = ['name','mail','gender','age','birthday'] Metaはmodelの継承のためのクラス modelのフィールドを流用できる 次に、ModelFormを使用して、フォームで送信された値をDBに追加する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend from .forms import HelloForm, FriendForm def create(request): params = { 'title': 'Hello', 'form': HelloForm() } if (request.method == 'POST'): obj = Friend() friend = FriendForm(request.POST, instance=obj) friend.save() return redirect(to='/hello') return render(request, 'hello/create.html', params) 6.3 既存レコードを更新する 使用しているメソッドは追加の時と同様に、save()を使用する。 6.4 既存レコードを削除する QuerySetオブジェクトのdeleteメソッドを実行する。 app_name/views.py from django.shortcuts import render, redirect from .models import Friend def delete(request): friend = Friend.objects.get(id=num) if (request.method == 'POST'): friend.delete() return redirect(to='/hello') params = { 'title': 'Hello', 'id': num, 'obj': friend } return render(request, 'hello/delete.html', params) For more information Django逆引きチートシート(Model編) Django逆引きチートシート(QuerySet編) Django 逆引きチートシート (Template編)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Progateで作ったWebアプリをDjangoで作ってみる2! Part2 -会員登録編-

目標物の確認 ProgateのNode.jsコースで作ったブログアプリと同じものをDjangoで作ってみます。 Djangoでのアプリ開発の一連の流れを整理するために記していきます。 完成イメージ 会員登録ページ ブラウザから会員登録するためのviewを作っていきます。 from django.contrib.auth.models import UserでDjangoがあらかじめ準備しているUserテーブルをimportしています(☆1)。 methodがPOSTの場合、formに入力されたユーザー名、e-mailアドレス、パスワードがそれぞれusername、email、passwordに格納されます(☆2)。 ユーザー名、e-mailアドレス、パスワードが一つでも空のままPOSTされてしまったとき、errorが表示されるようにします。まずはerrorsという空のリストを作成しておきます(☆3)。ユーザー名、e-mailアドレス、パスワードが空の場合、errorsに「それぞれが空です」というメッセージが格納されるようにします(☆4)。もし、一つでもerrorsにメッセージが入っていた場合、renderメソッドを使ってsignupページにerrorsが渡されるようにします(☆5)。{ 'errors' : errors }の左はkeyで、右がvalueです。つまり、このあと出てくるsignup.htmlファイルの{{errors}}(key)でerrorsvalueが呼び出されます(☆6)。 errorsが空のまま、つまり、ユーザー名、e-mailアドレス、パスワードがちゃんと入力されていた場合、try exceptが実行されます。try except構文を見ていきます。 try文が実行される user = User.objects.create_user(username, email, password)の中のcreate_userという部分が、新しくユーザーを作成するためのメソッドです。create_userメソッドが実行されることでUserテーブルに新しいデータが保存されます(☆7)。 try文の実行結果、ユーザー名が重複しているとIntegrityErrorが送出される except IntegrityErrorという記載に従い、IntegrityErrorが送出されているのであれば、下の行のコードが実行される errors空リストにメッセージが格納され、renderメソッドを使ってsignupページにerrorsが渡されるようになります。 これでviewは完成しました。 blogapp/blog/views.py from django.shortcuts import render from django.views.generic import TemplateView from django.contrib.auth.models import User # ☆1 from django.db import IntegrityError # ☆ class BlogTop(TemplateView):... def signupview(request): if request.method == 'POST': # ☆2 username = request.POST['username'] # ☆2 email = request.POST['email'] # ☆2 password = request.POST['password'] # ☆2 errors = [] # ☆3 if username == '': # ☆4 errors.append('ユーザー名が空です') if email == '': # ☆4 errors.append('メールアドレスが空です') if password == '': # ☆4 errors.append('パスワードが空です') if len(errors) > 0: # ☆5 return render(request, 'blog/signup.html', { 'errors' : errors }) # ☆6 else: try: user = User.objects.create_user(username, email, password) # ☆7 except IntegrityError: # ☆8 errors.append('ユーザー登録に失敗しました') return render(request, 'blog/signup.html', {'errors' : errors }) else: return render(request, 'blog/signup.html', {}) return render(request, 'blog/signup.html', {}) 続いて、HTMLファイルの編集を進めていきます。 エラー発生時にHTMLファイルにエラーメッセージを出すための実装をしていきます。 signupページの中にerrorsという追加のデータを入れます。もし、IntegrityErrorが発生した場合はrenderメソッドを使ってerrorsに格納されているerrorメッセージが入るようにしていきます(☆9)。 signup.html {% load static %} <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>BLOG</title> <link rel="stylesheet" href="{% static 'blog/style.css' %}"> </head> <body> <div class="sign"> <div class="container"> <h1><a href="{% url 'blog:list' %}">BLOG</a></h1> <div class="panel"> <h2>新規登録</h2> {% if errors %} <!--☆9--> <ul class="errors"> {% for error in errors %} <!--☆9--> <li>{{ error }}</li> <!--☆9--> {% endfor %} <!--☆9--> </ul> {% endif %} <!--☆9--> <form action="" method="post">{% csrf_token %} <p>ユーザー名</p> <input type="text" name="username"> <p>メールアドレス</p> <input type="text" name="email"> <p>パスワード</p> <input type="password" name="password"> <input type="submit" value="登録する"> <a href="{% url 'blog:list' %}">一覧にもどる</a> </form> </div> </div> </div> </body> </html> 最後に、urls.pyを編集して完了です。 blogapp/blog/urls.py from django.urls import path from .views import BlogTop, signupview app_name = 'blog' urlpatterns = [ path('', BlogTop.as_view(), name='top'), path('signup/', signupview, name='signup'), ] ページが完成しました。 空のまま登録ボタンを押すとエラーメッセージが表示されるようになりました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ロジスティック回帰で使う正則化パラメータ

はじめに Kaggleでロジスティック回帰を使用してモデルのスコアを向上させようともがく中でGridsearchという方法を知った。 ロジスティック回帰のパラメータを全通り実行することで、最もいいパラメータを求めるというやり方だ。 なんてCPUと時間にものを言わせたやり方なんだ!全く理解していなくとも脳死でできるじゃないか!最高! 流石に社会にでて脳死で全パラメータやりました!意味は知らないですけど!とは言えないので簡単に理解することを目的とする。 ロジスティック回帰とは 線形回帰にロジット関数をかけることで説明変数がどんな値でもなんらかの目的変数を取るようにするやり方。 $$ロジット関数 : log(\frac{p}{1-p})$$ 2値分類、説明変数1つと仮定すると $$log(\frac{p}{1-p}) = \beta_0 + \beta_1x$$ $$\beta_0 : 切片, \beta_1 :傾き$$ で表される。さあこれをp(目的変数が1である確率)について解くと $$p=\frac{1}{1 + exp(\beta_0 + \beta_1x)}$$ になる。このβ0とβ1を最適化していきたいっていう話。 ここで使うのが尤度関数と呼ばれる関数。(大学で学んだ気もする) それっぽい値にしていくっていう関数のイメージ $$p_n = y_n^{t_n}(1-y_n)^{1-{t_n}}$$ $$t_n : Positiveなら1 Negativeなら0をとる値$$ 仮にtnが1(正解)ならyn(正解を取る確率)を上げていきたいよねっていうイメージ これの積を求めて多変数に対応させると $$L(\beta) = \prod_{n=1}^{n}y_n^{t_n}(1-y_n)^{1-{t_n}}$$ になる。掛け算は計算量が膨大になることと、-を取ることで最小化問題にしたいので-logを掛ける。 $$-logL(\beta) = -\sum_{n=1}^{n}(t_nlog{y_n} + (1 - t_n)log(1-y_n))$$ という式になる。この式を交差エントロピー誤差関数と呼ぶらしい。 これのynに含まれるβで微分すると $$\frac{d}{db}L(\beta) = \sum_{n=1}^{n}x_n(y_n - t_n)$$ になる。この式は勾配降下法の式らしいけどよく知らない。 求め方は微分の分配だけど描くの面倒なので画像でも貼る。 本題 かなり前置きが長くなったが、本題であるパラメータの話に移る。 公式docmentから penalty dual solver... とparamが並ぶが今回はpenaltyの話。 時間があったら全パラメータ解説していく。 penaltyの種類 公式より https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html#sklearn.linear_model.LogisticRegression penalty : {‘l1’, ‘l2’, ‘elasticnet’, ‘none’}, default=’l2’ Used to specify the norm used in the penalization. The ‘newton-cg’, ‘sag’ and ‘lbfgs’ solvers support only l2 penalties. ‘elasticnet’ is only supported by the ‘saga’ solver. If ‘none’ (not supported by the liblinear solver), no regularization is applied. New in version 0.19: l1 penalty with SAGA solver (allowing ‘multinomial’ + L1) L1,L2,elasticent,noneの4つがあるらしい。 調べていくと L1 (Rasso) いくつかの特徴量を0にしてしまって過学習を防ぐやり方 課題としてサンプル数nより特徴量pが大きい場合、pの取れる最大値はnになってしまう L2 (Ligge) 数値の絶対量を減らすことで過学習を防ぐやり方。イメージとして標準化と同じ? elasticnet L1,L2の両方を使用して使うやり方。L1とL2bの前に重みをつけることによって最適な値を回数を回すことで求めようとするやり方。 L1が使えてないのであれば、L2に自動的に切り替えてほしかったけどそういうわけにもいかないのかな とりあえずこれを使えばいいと思うのだけれどdefaultがl2なのでなんらかの欠点があるのかもしれない NONE 多分なんの正則化も行なっていない 公式に出てくる‘newton-cg’とかは最適化問題に使う関数の種類のこと。使える組み合わせと使えない組み合わせがあるっぽい。 次はsolverについて書こうと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

穴埋め問題で、Bert(日本語)の威力を体感する。

はじめに タイトルのとおり、 穴埋め問題で、Bert(日本語)の威力を体感する。 『今日は最悪の一日だ。』 『今日は最高の一日だ。』 の前振りに対するBertの反応をみた。 環境 OS: windows transformers: 4.9.1 GPU: I do not have a GPU. 検証結果 問題1 問題文 (今日は最悪の一日だ。財布を XXX した。) input_ids = tokenizer.encode(f'今日は最悪の一日だ。財布を{tokenizer.mask_token}した。', return_tensors='pt') 回答 [CLS] 今日 は 最悪 の 一 日 だ 。 財布 を 紛失 し た 。 [SEP] [CLS] 今日 は 最悪 の 一 日 だ 。 財布 を 用意 し た 。 [SEP] [CLS] 今日 は 最悪 の 一 日 だ 。 財布 を 破壊 し た 。 [SEP] [CLS] 今日 は 最悪 の 一 日 だ 。 財布 を 発見 し た 。 [SEP] [CLS] 今日 は 最悪 の 一 日 だ 。 財布 を 回収 し た 。 [SEP] ⇒⇒ 100点かな。。。 問題2 問題文 (今日は最高の一日だ。財布を XXX した。) input_ids = tokenizer.encode(f'今日は最高の一日だ。財布を{tokenizer.mask_token}した。', return_tensors='pt') 回答 [CLS] 今日 は 最高 の 一 日 だ 。 財布 を プレゼント し た 。 [SEP] [CLS] 今日 は 最高 の 一 日 だ 。 財布 を 購入 し た 。 [SEP] [CLS] 今日 は 最高 の 一 日 だ 。 財布 を 用意 し た 。 [SEP] [CLS] 今日 は 最高 の 一 日 だ 。 財布 を 交換 し た 。 [SEP] [CLS] 今日 は 最高 の 一 日 だ 。 財布 を 準備 し た 。 [SEP] ⇒⇒100点かな。。。 「なくした財布を」ならば、「発見」したとかだろうけど、、、、この文なら、 「プレゼント」は、面白いと思う。 コード import torch from transformers import BertJapaneseTokenizer from transformers import BertForMaskedLM tokenizer = BertJapaneseTokenizer.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking') model = BertForMaskedLM.from_pretrained('cl-tohoku/bert-base-japanese-whole-word-masking') input_ids = tokenizer.encode(f'今日は最悪の一日だ。財布を{tokenizer.mask_token}した。', return_tensors='pt') masked_index = torch.where(input_ids == tokenizer.mask_token_id)[1].tolist()[0] result = model(input_ids) pred_ids = result[0][:, masked_index].topk(5).indices.tolist()[0] for pred_id in pred_ids: output_ids = input_ids.tolist()[0] output_ids[masked_index] = pred_id print(tokenizer.decode(output_ids)) 上記は、 https://www.koi.mashykom.com/bert.html などを参考にさせて頂きました。 (他のサイトもいろいろ、参考にしています。) まとめ それなりな感じ。。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonとOCRエンジンで画像から文字を認識する

目的 anacondaの仮想環境内でtesseractとPyOCRを使い、画像から文字を認識できるようにします。 今回は画像の文字を認識し、ターミナルへ出力できるところまでの行います。 こんな感じ↓ 環境 python 3.6 tesseract 4.1.1 PyOCR 0.8 手順 ツールのインストール anacondaの仮想環境に下記2つをインストールします。 ・文字認識のためのOCRエンジンであるTesseract OCRをインストール https://anaconda.org/conda-forge/tesseract conda install -c conda-forge tesseract ・PythonからOCRエンジンを使用するためのPyOCRをインストール https://anaconda.org/conda-forge/pyocr conda install -c conda-forge pyocr 文字認識を試す 確認用にテスト用の画像を準備します(ファイル名はmoji_en.pngとしています)。 ※デフォルトは日本語未対応です。 pythonコードは以下になります。 from PIL import Image import pyocr # OCRエンジンを取得 engines = pyocr.get_available_tools() engine = engines[0] # 画像の文字を読み込む txt = engine.image_to_string(Image.open('moji_en.png'), lang="eng") print(txt) # 「Test Message」が出力される 実行結果に画像内のテキストが出力されていればOKです。 日本語対応化 次に、日本語も認識できるようにします。 下記から言語ファイルjpn.traineddataダウンロードします。 https://github.com/tesseract-ocr/tessdata ダウンロードした言語ファイルをanacondaの仮想環境にある指定の場所へ配置してください。 配置先:C:\Users\user\anaconda3\envs\<仮想環境名>\Library\bin\tessdata\ 言語ファイルを配置したら、pythonコードのengine.image_to_string()の第2引数をlang="jpn"のように修正します。 ついでに対応言語も出力しておきます。 from PIL import Image import pyocr # OCRエンジンを取得 engines = pyocr.get_available_tools() engine = engines[0] # 対応言語取得 langs = engine.get_available_languages() print("対応言語:",langs) # ['eng', 'jpn', 'osd'] # 画像の文字を読み込む txt = engine.image_to_string(Image.open('jugemu.png'), lang="jpn") # 修正点:lang="eng" -> lang="jpn" print(txt) これで日本語化は完了です。 動作確認してみましょう。使う画像はみんな大好き寿限無です。 対応言語: ['eng', 'jpn', 'osd'] 寿 限 無 寿 限 無 五 劫 の す り き れ 海 砂 利 水 魚 の 水 行 末 雲 来 末 風 来 末 食 う 寝 る と こ ろ に 住 む と こ ろ や ぶ ら 小 路 の ぶ ら 小 路 パ イ ポ パ イ ポ パ イ ポ の シ ュ ー リ ン ガ ン シ ュ ー リ ン ガ ン の ク ー リ ン ダ イ ク ー リ ン ダ イ の ポ ン ポ コ ナ ー の ポ ン ポ コ ピ ー の 長 久 命 の 長 助 対応言語にjpnが含まれており、出力も問題なさそうですね。 ちなみにosdは文字の識別を行うための特殊ファイルみたいなものです。 まとめ pythonのツールと数行のコードだけで画像から文字を認識することが出来ました。 日本語対応なども一度設定してしまえばOKなので、低コストでここまで出来るのは素晴らしいです。 データ入力の自動化など、様々なことに応用できそうですね。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【お絵かき初心者必見】SPINで写真から人間の3Dを取得する

https://qiita.com/akaiteto/items/6a59a4b644fdf6d7bc4d https://qiita.com/akaiteto/items/b5c8c3d5eb5ca3849c5d 何故かシリーズ化している本シリーズ。 要するに、人間の姿勢推定、骨格推定の話です。 とりあえず最低限の形にはなったのでこの記事で人間3D化は一旦終了です。 この記事ではSPINというアルゴリズムを試します。ちゃんと動くソースもできたし割と満足。 SPINは単一の画像から骨格を検出するアルゴリズムです。 もともと画像単体に対して実行するものですが、MP4の動画を読み込んだ結果はこんな感じ。 TCMRとSPINのソースをガチャガチャ組み合わせてうまくいきました テレビを撮影したような動画みたいですけど、うまいこと推定されてますね。すげー ついでにこちらが画像じゃなく3D化したもの。なんのポーズかはしらない。 それから、これが果たしてお絵かきに役立つかもわからない。 SPIN この記事では、アルゴリズムの方にはあまり触れません。(TCMRよむのに精魂尽きた) でも別の記事でSPINのソースを基にSMPL周りの計算式を整理した記事はあげるとおもう。 TCMRの余談(よまなくてもいい) https://qiita.com/akaiteto/items/6a59a4b644fdf6d7bc4d 前回TCMRを動かした時、デモソースが上手く動かず、TCMRのRegressorの部分でエラーが出ていました。 向こうの記事にはあえて反映させませんが、エラーの原因は結論から言えばSMPLのバージョンです。 私が入力としてあたえているものはSMPLのモデルだけなので、十中八九SMPLのモデルが原因。 ソースを見た感じ、TCMRはSPINのソースをベースにして作られているようなので、 SPINのSMPLの説明をみてみると、TCMRとSPINとでpklのモデルファイルのバージョンが違いました。 もしやSMPLのモデルのバージョンが違う…? 試しにモデルを変えて実行したらうまくいきましたやったー。 SMPLをなにかのOSSで使う時はバージョン要注意です。 前提条件 TCMRのソースをベースにします。 環境構築等は前回参照。 https://qiita.com/akaiteto/items/6a59a4b644fdf6d7bc4d 前回記事読まなくても、google colabで以下を実行すればいけるかも? #環境構築 !git clone https://github.com/hongsukchoi/TCMR_RELEASE !pip install numpy==1.17.5 torch==1.4.0 torchvision==0.5.0 !pip install git+https://github.com/giacaglia/pytube.git --upgrade !pip install -r TCMR_RELEASE/requirements.txt !pip install open3D #出力先設定 ! mkdir output ; cd output ; mkdir demo_output ! mkdir data ; cd data ; mkdir base_data # ※googledriveにSMPLのモデル配置前提 ! cp drive/MyDrive/basicModel_neutral_lbs_10_207_0_v1.0.0.pkl data/base_data/SMPL_NEUTRAL.pkl #デモ動画/学習済みモデルダウンロード !source TCMR_RELEASE/scripts/get_base_data.sh 余談(よまなくていい) https://qiita.com/akaiteto/items/6a59a4b644fdf6d7bc4d 前回の記事を見ている方ならご存知だと思いますが、 SPINはTCMRのデモソースで採用されていた技術の1つです。 一応TCMRのおさらいですが、 TCMRは動画に特化して人の動きを検出する技術です。 動画の各フレームごとに画像単一の骨格特徴の抽出を行い、 全フレームの骨格特徴を現在過去未来とデータを分けて学習することで、 動画全体の骨格の流れを滑らかに調整します。 SPINは、この一連の処理の中で、はじめに画像単一で骨格を抽出する際に使われていました。 SMPLの用意 https://smplify.is.tue.mpg.de/ まず、SMPLのモデルをダウンロードします。 アカウント登録をして、メインメニューのDownloadタブからダウンロードしてください。 前回のTCMRの記事を読んでいる人はここがめちゃくちゃ重要です。 実はSMPLには複数のダウンロードサイトがあります。 そして、サイトに寄ってバージョンが異なる場合があるようです。(ここ重要) 上記サイトからダウンロードしたモデルは、2021年7月時点では 「basicModel_neutral_lbs_10_207_0_v1.0.0.pkl」というファイルです。 OSSによって、このpklに対応したバージョンじゃないと動かない場合があるので もしもSMPLのモデルの読み込みが関わる処理でエラーが出た時は、 モデルのバージョンを変えてみましょう。(なお、SMPL公式ではバージョン管理していない模様…) ソース TCMR内にあるSPINは出力された特徴を変換したりしているのでガチャガチャ改造します。 以下のようにdemo.pyをわりとガッツリ書き換えます。 ファイルを分けたりなどやってないど、くっっっっっそながいです。 demo.py import os import os.path as osp from lib.core.config import BASE_DATA_DIR from lib.models.smpl import SMPL, SMPL_MODEL_DIR os.environ['PYOPENGL_PLATFORM'] = 'egl' import cv2 import time import torch import joblib import shutil import colorsys import argparse import random import numpy as np from pathlib import Path from tqdm import tqdm from multi_person_tracker import MPT from torch.utils.data import DataLoader from lib.models.tcmr import TCMR from lib.utils.renderer import Renderer from lib.dataset._dataset_demo import CropDataset, FeatureDataset from lib.utils.demo_utils import ( download_youtube_clip, convert_crop_cam_to_orig_img, prepare_rendering_results, video_to_images, images_to_video, ) from PIL import Image MIN_NUM_FRAMES = 25 random.seed(1) torch.manual_seed(1) np.random.seed(1) import os import torch from torchvision.utils import make_grid import numpy as np import pyrender import trimesh import os import cv2 import numpy as np import os.path as osp import torch from torch.utils.data import Dataset from torchvision.transforms.functional import to_tensor from lib.utils.smooth_bbox import get_all_bbox_params from lib.data_utils._img_utils import get_single_image_crop_demo import torch import torch.nn as nn import torchvision.models.resnet as resnet import numpy as np import math from torch.nn import functional as F def rot6d_to_rotmat(x): """Convert 6D rotation representation to 3x3 rotation matrix. Based on Zhou et al., "On the Continuity of Rotation Representations in Neural Networks", CVPR 2019 Input: (B,6) Batch of 6-D rotation representations Output: (B,3,3) Batch of corresponding rotation matrices """ x = x.view(-1,3,2) a1 = x[:, :, 0] a2 = x[:, :, 1] b1 = F.normalize(a1) b2 = F.normalize(a2 - torch.einsum('bi,bi->b', b1, a2).unsqueeze(-1) * b1) b3 = torch.cross(b1, b2) return torch.stack((b1, b2, b3), dim=-1) class Bottleneck(nn.Module): """ Redefinition of Bottleneck residual block Adapted from the official PyTorch implementation """ expansion = 4 def __init__(self, inplanes, planes, stride=1, downsample=None): super(Bottleneck, self).__init__() self.conv1 = nn.Conv2d(inplanes, planes, kernel_size=1, bias=False) self.bn1 = nn.BatchNorm2d(planes) self.conv2 = nn.Conv2d(planes, planes, kernel_size=3, stride=stride, padding=1, bias=False) self.bn2 = nn.BatchNorm2d(planes) self.conv3 = nn.Conv2d(planes, planes * 4, kernel_size=1, bias=False) self.bn3 = nn.BatchNorm2d(planes * 4) self.relu = nn.ReLU(inplace=True) self.downsample = downsample self.stride = stride def forward(self, x): residual = x out = self.conv1(x) out = self.bn1(out) out = self.relu(out) out = self.conv2(out) out = self.bn2(out) out = self.relu(out) out = self.conv3(out) out = self.bn3(out) if self.downsample is not None: residual = self.downsample(x) out += residual out = self.relu(out) return out class HMR_SPIN(nn.Module): """ SMPL Iterative Regressor with ResNet50 backbone """ def __init__(self, block, layers, smpl_mean_params): self.inplanes = 64 super(HMR_SPIN, self).__init__() npose = 24 * 6 self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, padding=3, bias=False) self.bn1 = nn.BatchNorm2d(64) self.relu = nn.ReLU(inplace=True) self.maxpool = nn.MaxPool2d(kernel_size=3, stride=2, padding=1) self.layer1 = self._make_layer(block, 64, layers[0]) self.layer2 = self._make_layer(block, 128, layers[1], stride=2) self.layer3 = self._make_layer(block, 256, layers[2], stride=2) self.layer4 = self._make_layer(block, 512, layers[3], stride=2) self.avgpool = nn.AvgPool2d(7, stride=1) self.fc1 = nn.Linear(512 * block.expansion + npose + 13, 1024) self.drop1 = nn.Dropout() self.fc2 = nn.Linear(1024, 1024) self.drop2 = nn.Dropout() self.decpose = nn.Linear(1024, npose) self.decshape = nn.Linear(1024, 10) self.deccam = nn.Linear(1024, 3) nn.init.xavier_uniform_(self.decpose.weight, gain=0.01) nn.init.xavier_uniform_(self.decshape.weight, gain=0.01) nn.init.xavier_uniform_(self.deccam.weight, gain=0.01) for m in self.modules(): if isinstance(m, nn.Conv2d): n = m.kernel_size[0] * m.kernel_size[1] * m.out_channels m.weight.data.normal_(0, math.sqrt(2. / n)) elif isinstance(m, nn.BatchNorm2d): m.weight.data.fill_(1) m.bias.data.zero_() mean_params = np.load(smpl_mean_params) init_pose = torch.from_numpy(mean_params['pose'][:]).unsqueeze(0) init_shape = torch.from_numpy(mean_params['shape'][:].astype('float32')).unsqueeze(0) init_cam = torch.from_numpy(mean_params['cam']).unsqueeze(0) self.register_buffer('init_pose', init_pose) self.register_buffer('init_shape', init_shape) self.register_buffer('init_cam', init_cam) def _make_layer(self, block, planes, blocks, stride=1): downsample = None if stride != 1 or self.inplanes != planes * block.expansion: downsample = nn.Sequential( nn.Conv2d(self.inplanes, planes * block.expansion, kernel_size=1, stride=stride, bias=False), nn.BatchNorm2d(planes * block.expansion), ) layers = [] layers.append(block(self.inplanes, planes, stride, downsample)) self.inplanes = planes * block.expansion for i in range(1, blocks): layers.append(block(self.inplanes, planes)) return nn.Sequential(*layers) def forward(self, x, init_pose=None, init_shape=None, init_cam=None, n_iter=3): batch_size = x.shape[0] if init_pose is None: init_pose = self.init_pose.expand(batch_size, -1) if init_shape is None: init_shape = self.init_shape.expand(batch_size, -1) if init_cam is None: init_cam = self.init_cam.expand(batch_size, -1) x = self.conv1(x) x = self.bn1(x) x = self.relu(x) x = self.maxpool(x) x1 = self.layer1(x) x2 = self.layer2(x1) x3 = self.layer3(x2) x4 = self.layer4(x3) xf = self.avgpool(x4) xf = xf.view(xf.size(0), -1) pred_pose = init_pose pred_shape = init_shape pred_cam = init_cam for i in range(n_iter): xc = torch.cat([xf, pred_pose, pred_shape, pred_cam],1) xc = self.fc1(xc) xc = self.drop1(xc) xc = self.fc2(xc) xc = self.drop2(xc) pred_pose = self.decpose(xc) + pred_pose pred_shape = self.decshape(xc) + pred_shape pred_cam = self.deccam(xc) + pred_cam pred_rotmat = rot6d_to_rotmat(pred_pose).view(batch_size, 24, 3, 3) return pred_rotmat, pred_shape, pred_cam def hmr_SPIN(smpl_mean_params, pretrained=True, **kwargs): """ Constructs an HMR model with ResNet50 backbone. Args: pretrained (bool): If True, returns a model pre-trained on ImageNet """ model = HMR_SPIN(Bottleneck, [3, 4, 6, 3], smpl_mean_params, **kwargs) if pretrained: resnet_imagenet = resnet.resnet50(pretrained=True) model.load_state_dict(resnet_imagenet.state_dict(),strict=False) return model class CropDataset_SPIN(Dataset): def __init__(self, image_folder, frames, bboxes=None, joints2d=None, scale=1.0, crop_size=224): self.image_file_names = [ osp.join(image_folder, x) for x in os.listdir(image_folder) if x.endswith('.png') or x.endswith('.jpg') ] self.image_file_names = sorted(self.image_file_names) self.image_file_names = np.array(self.image_file_names)[frames] self.bboxes = bboxes self.joints2d = joints2d self.scale = scale self.crop_size = crop_size self.frames = frames self.has_keypoints = True if joints2d is not None else False self.norm_joints2d = np.zeros_like(self.joints2d) if self.has_keypoints: bboxes, time_pt1, time_pt2 = get_all_bbox_params(joints2d, vis_thresh=0.3) bboxes[:, 2:] = 150. / bboxes[:, 2:] self.bboxes = np.stack([bboxes[:, 0], bboxes[:, 1], bboxes[:, 2], bboxes[:, 2]]).T self.image_file_names = self.image_file_names[time_pt1:time_pt2] self.joints2d = joints2d[time_pt1:time_pt2] self.frames = frames[time_pt1:time_pt2] def __len__(self): return len(self.image_file_names) def __getitem__(self, idx): img = cv2.cvtColor(cv2.imread(self.image_file_names[idx]), cv2.COLOR_BGR2RGB) bbox = self.bboxes[idx] j2d = self.joints2d[idx] if self.has_keypoints else None norm_img, raw_img, kp_2d = get_single_image_crop_demo( img, bbox, kp_2d=j2d, scale=self.scale, crop_size=self.crop_size) if self.has_keypoints: return norm_img, kp_2d,raw_img else: return norm_img,raw_img class RendererSPIN: """ Renderer used for visualizing the SMPL model Code adapted from https://github.com/vchoutas/smplify-x """ def __init__(self, focal_length=5000, img_res=224, faces=None): self.renderer = pyrender.OffscreenRenderer(viewport_width=img_res, viewport_height=img_res, point_size=1.0) self.focal_length = focal_length self.camera_center = [img_res // 2, img_res // 2] self.faces = faces def visualize_tb(self, vertices, camera_translation, images): vertices = vertices.cpu().numpy() camera_translation = camera_translation.cpu().numpy() images = images.cpu() images_np = np.transpose(images.numpy(), (0,2,3,1)) rend_imgs = [] for i in range(vertices.shape[0]): rend_img = torch.from_numpy(np.transpose(self.__call__(vertices[i], camera_translation[i], images_np[i]), (2,0,1))).float() rend_imgs.append(images[i]) rend_imgs.append(rend_img) rend_imgs = make_grid(rend_imgs, nrow=2) return rend_imgs def __call__(self, vertices, camera_translation, image): material = pyrender.MetallicRoughnessMaterial( metallicFactor=0.2, alphaMode='OPAQUE', baseColorFactor=(0.8, 0.3, 0.3, 1.0)) camera_translation[0] *= -1. mesh = trimesh.Trimesh(vertices, self.faces) rot = trimesh.transformations.rotation_matrix( np.radians(180), [1, 0, 0]) mesh.apply_transform(rot) mesh = pyrender.Mesh.from_trimesh(mesh, material=material) scene = pyrender.Scene(ambient_light=(0.5, 0.5, 0.5)) scene.add(mesh, 'mesh') camera_pose = np.eye(4) camera_pose[:3, 3] = camera_translation camera = pyrender.IntrinsicsCamera(fx=self.focal_length, fy=self.focal_length, cx=self.camera_center[0], cy=self.camera_center[1]) scene.add(camera, pose=camera_pose) light = pyrender.DirectionalLight(color=[1.0, 1.0, 1.0], intensity=1) light_pose = np.eye(4) light_pose[:3, 3] = np.array([0, -1, 1]) scene.add(light, pose=light_pose) light_pose[:3, 3] = np.array([0, 1, 1]) scene.add(light, pose=light_pose) light_pose[:3, 3] = np.array([1, 1, 2]) scene.add(light, pose=light_pose) color, rend_depth = self.renderer.render(scene, flags=pyrender.RenderFlags.RGBA) color = color.astype(np.float32) / 255.0 valid_mask = (rend_depth > 0)[:,:,None] output_img = (color[:, :, :3] * valid_mask + (1 - valid_mask) * image) return output_img def main(args): device = torch.device('cuda') if torch.cuda.is_available() else torch.device('cpu') """ Prepare input video (images) """ video_file = args.vid_file if video_file.startswith('https://www.youtube.com'): print(f"Donwloading YouTube video \'{video_file}\'") video_file = download_youtube_clip(video_file, '/tmp') if video_file is None: exit('Youtube url is not valid!') print(f"YouTube Video has been downloaded to {video_file}...") if not os.path.isfile(video_file): exit(f"Input video \'{video_file}\' does not exist!") output_path = osp.join('./output/demo_output', os.path.basename(video_file).replace('.mp4', '')) Path(output_path).mkdir(parents=True, exist_ok=True) image_folder, num_frames, img_shape = video_to_images(video_file, return_info=True) print(f"Input video number of frames {num_frames}\n") orig_height, orig_width = img_shape[:2] """ Run tracking """ total_time = time.time() bbox_scale = 1.2 # 動画から物体検出 # 全フレームに対して写っている人を識別してその人のbboxを取得する。 # https://github.com/mkocabas/multi-person-tracker.git mot = MPT( device=device, batch_size=args.tracker_batch_size, display=args.display, detector_type=args.detector, output_format='dict', yolo_img_size=args.yolo_img_size, ) tracking_results = mot(image_folder) # 動画にあまり写っていない人(=フレーム数が少ない人)は削除 for person_id in list(tracking_results.keys()): if tracking_results[person_id]['frames'].shape[0] < MIN_NUM_FRAMES: del tracking_results[person_id] # SPINモデルの準備 -> 学習済みモデルの読み込み # from lib.models.spin import hmr SMPL_MEAN_PARAMS = osp.join(BASE_DATA_DIR, 'smpl_mean_params.npz') hmr = hmr_SPIN(SMPL_MEAN_PARAMS).to(device) checkpoint = torch.load(osp.join(BASE_DATA_DIR, 'spin_model_checkpoint.pth.tar')) hmr.load_state_dict(checkpoint['model'], strict=False) hmr.eval() # SMPLモデルを操作するための準備 smpl = SMPL(SMPL_MODEL_DIR, batch_size=1, create_transl=False).to(device) FOCAL_LENGTH = 5000 IMG_RES = 224 renderer_spin = RendererSPIN(focal_length=FOCAL_LENGTH, img_res=IMG_RES, faces=smpl.faces) # 検出した人ごとにループ dir_output = "output/person" os.makedirs(dir_output, exist_ok=True) framecnt = 0 for person_id in tqdm(list(tracking_results.keys())): outimg_files = [] framecnt += 1 # 出力先の設定 os.makedirs(dir_output + "/" + str(person_id), exist_ok=True) outputfile_mp4 = dir_output + "/" + str(person_id) + ".mp4" outputfile_gif = dir_output + "/" + str(person_id) + ".gif" # 実行した物体検出の結果を準備 # tracking_resultsにはその人の全フレームデータが全て入ってる。 bboxes = joints2d = None bboxes = tracking_results[person_id]['bbox'] frames = tracking_results[person_id]['frames'] # bboxに基づくトリミング+ネットワークに合わせた形式への変換 dataset = CropDataset_SPIN( image_folder=image_folder, frames=frames, bboxes=bboxes, joints2d=joints2d, scale=bbox_scale, ) bboxes = dataset.bboxes frames = dataset.frames has_keypoints = True if joints2d is not None else False crop_dataloader = DataLoader(dataset, batch_size=256, num_workers=16) for i, batch_src in enumerate(crop_dataloader): if has_keypoints: batch_src, nj2d , img_raw_all_frame= batch_src norm_joints2d.append(nj2d.numpy().reshape(-1, 21, 3)) else: batch_src , img_raw_all_frame= batch_src # レンダリング batch_size = batch_src.size() for idx in range(img_raw_all_frame.size()[0]) : # batchの次元        [その人が出現するフレーム数,RGB=3,横 ,縦] # img_raw_all_frameの次元 [その人が出現するフレーム数,横  ,縦 ,RGB=3] batch = batch_src[idx].view(1,batch_size[1],batch_size[2],batch_size[3]) with torch.no_grad(): batch = batch.to(device) pred_rotmat, pred_betas, pred_camera = hmr(batch) pred_output = smpl(betas=pred_betas, body_pose=pred_rotmat[:,1:], global_orient=pred_rotmat[:,0].unsqueeze(1), pose2rot=False) pred_vertices = pred_output.vertices # 出力値からSMPL向けの値を取得 camera_translation = torch.stack([pred_camera[:,1], pred_camera[:,2], 2*FOCAL_LENGTH/(IMG_RES * pred_camera[:,0] +1e-9)],dim=-1) camera_translation = camera_translation[0].cpu().numpy() pred_vertices = pred_vertices[0].cpu().numpy() # SMPLのモデルの画像生成 img_raw_frame = img_raw_all_frame[idx].cpu().numpy() img_smpl = renderer_spin(pred_vertices, camera_translation, img_raw_frame) img_raw_frame = 255 * img_raw_frame[:,:,::-1] img_smpl = 255 * img_smpl[:,:,::-1] # 保存(SMPLを通常画像の上に合成するために無駄にごちゃごちゃしたことをしている。) outputfile_smpl = dir_output + "/smpl_" + str(idx) + ".png" cv2.imwrite(outputfile_smpl, img_smpl) img_smpl = cv2.imread(outputfile_smpl, 1) img2_gray = cv2.cvtColor(img_smpl, cv2.COLOR_BGR2GRAY) img_maskg = cv2.threshold(img2_gray, 220, 255, cv2.THRESH_BINARY_INV)[1] img_mask = cv2.merge((img_maskg,img_maskg, img_maskg)) img_smpl = cv2.bitwise_and(img_smpl, img_mask) img_maskn = cv2.bitwise_not(img_mask) img_src1m = cv2.bitwise_and(img_raw_frame, img_maskn) img_dst = cv2.bitwise_or(img_src1m, img_smpl) cv2.imwrite(outputfile_smpl, img_dst) outimg_files.append(outputfile_smpl) # 動画作成(gif) images_PIL = [] for img_file in outimg_files: im = Image.open(img_file) images_PIL.append(im) images_PIL[0].save(outputfile_gif, save_all=True, append_images=images_PIL[1:], loop=0, duration=30) # 動画作成(mp4) fourcc = cv2.VideoWriter_fourcc('m','p','4', 'v') video = cv2.VideoWriter(outputfile_mp4, fourcc, 20.0, (IMG_RES, IMG_RES)) for img_file in outimg_files: img = cv2.imread(img_file) video.write(img) exit() if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--vid_file', type=str, default='sample_video.mp4', help='input video path or youtube link') parser.add_argument('--model', type=str, default='./data/base_data/tcmr_demo_model.pth.tar', help='path to pretrained model weight') parser.add_argument('--detector', type=str, default='yolo', choices=['yolo', 'maskrcnn'], help='object detector to be used for bbox tracking') parser.add_argument('--yolo_img_size', type=int, default=416, help='input image size for yolo detector') parser.add_argument('--tracker_batch_size', type=int, default=12, help='batch size of object detector used for bbox tracking') parser.add_argument('--display', action='store_true', help='visualize the results of each step during demo') parser.add_argument('--save_pkl', action='store_true', help='save results to a pkl file') parser.add_argument('--save_obj', action='store_true', help='save results as .obj files.') parser.add_argument('--gender', type=str, default='neutral', help='set gender of people from (neutral, male, female)') parser.add_argument('--wireframe', action='store_true', help='render all meshes as wireframes.') parser.add_argument('--sideview', action='store_true', help='render meshes from alternate viewpoint.') parser.add_argument('--render_plain', action='store_true', help='render meshes on plain background') parser.add_argument('--gpu', type=int, default='1', help='gpu num') args = parser.parse_args() os.environ['CUDA_VISIBLE_DEVICES'] = str(args.gpu) main(args) 結果 わーい。うまくいきましたー。 まとめ TCMR、SPINのおかげで、SMPLを前提にした操作や変換がわかるようになりました。 (そのうち記事にしたい。) SPINでも普通に精度良さげなので、大満足の結果です。 test.py # 3D出力 o3d_vertices = pred_output.vertices.detach().cpu().numpy().squeeze() import open3d as o3d mesh = o3d.geometry.TriangleMesh() mesh.vertices = o3d.utility.Vector3dVector(o3d_vertices) smpl = SMPL(SMPL_MODEL_DIR, batch_size=1, create_transl=False) mesh.triangles = o3d.utility.Vector3iVector(smpl.faces) mesh.compute_vertex_normals() mesh.paint_uniform_color([0.3, 0.3, 0.3]) o3d.visualization.draw_geometries([mesh]) o3d.io.write_triangle_mesh('smplx_torch_neutral.obj', mesh) ついでに、3Dモデルも出力するようにしたので、 一番手に入れたかった3Dデータも手に入りましたわーい。 余談 今回の記事は本当に色々勉強になりました。 TCMRでつまったところを解消できたのがとても大きい。 以下、今回から学んだ教訓。 1.OSSを試そうとした時に上手く動かない時は、   そのOSSがテンプレートとして使ってるOSSを調べてみる   ベースが有る時はソースに "http://github~~" と参照ついてる場合が多いので、   そこから探りましょう。 2.OSSの使い方がわからない・OSSの下準備が面倒なときは、   githubからそのOSSを探して、下準備もろもろやってくれてるやつを探す。 今後 とまぁ、人を3D化したわけですが、 正直人の人体だけ3Dにできても役に立たないので、いずれは背景込みで3D化したいですね。 それではー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Bert。ModuleNotFoundError: No module named 'transformers.tokenization_bert_japanese'のtransformers 4.xでの回避方法。

概要 Bert。ModuleNotFoundError: No module named 'transformers.tokenization_bert_japanese'のtransformers 4.xでの回避方法を示す。 補足情報 この記事の作成日: 2021/07/28 環境: windows いまtransformersを取得した場合のバージョン: 4.9.1 エラーが発生するケース transformers 4.9.1にて from transformers.tokenization_bert_japanese import BertJapaneseTokenizer とすると、 該当のエラーになる。 回避方法 以下のように、tokenization_bert_japaneseを消す from transformers import BertJapaneseTokenizer ⇒⇒ BertJapaneseTokenizer が直下(のみ)になったということ。 ちなみに、 transformers 3.5.1では、BertJapaneseTokenizerは 直下と、transformers.tokenization_bert_japanese の両方に見えるので、移行の方針だったのだと思う。 ただ、3.5.1環境で、「python -m pydoc transformers.tokenization_bert_japanese」で出る情報に廃止予定みたいなことは、わかりやすいところには書いてませんでした。。。 応用 以下も同様。transformers 4.9.1では、エラーになるはず。(対策は、同様。) from transformers.modeling_bert import BertModel ModuleNotFoundError: No module named 'transformers.modeling_bert' 補足 transformers 3.5.1に戻す対策しかネット上で見えなかったので記事にした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wasmer-python と emcc で C/C++ コードを wasm にして python でポータブルに動かすメモ

背景 Python のモジュールや Blender の addon(プラグイン)など, クロスプラットホームで C++ で書きたい(すごい性能が必要というわけではないが, C/C++ が開発がラクなときとか). ただ, 作った module や plugin を配布して使ってもらうにしても, 各プラットフォームにプレビルド用意したりと C/C++ のビルドがめんどい. (Windows で pip でソースからビルドさせる場合, ユーザーの環境に Visual Studio とか入れてもらう必要がありめんどい) python だと cibuildwheel で pypi 向けにビルドするのもあるけど, 設定がめんどい https://qiita.com/syoyo/items/97f35b4d5c40761cc314 plugin を WASM にし, クロスプラットホームな plugin として扱えないか? 方法 wasmer-python がありました. ありがとうございます C/C++ コード emcc でコンパイルします. #ifdef __EMSCRIPTEN__ #include <emscripten.h> #else #define EMSCRIPTEN_KEEPALIVE #endif float EMSCRIPTEN_KEEPALIVE func(float a, float b) { return a + 2.0f * b; } $ emcc test.c a.out.wasm が出来上がります. 実行 wasmer-python のサンプルを参考にして動かします. # main.py from wasmer import engine, Store, Module, Instance from wasmer_compiler_cranelift import Compiler # Let's define the store, that holds the engine, that holds the compiler. store = Store(engine.JIT(Compiler)) # Let's compile the module to be able to execute it! module = Module(store, open('a.out.wasm', 'rb').read()) # Now the module is compiled, we can instantiate it. instance = Instance(module) result = instance.exports.func(5.0, 3.0) print(result) $ python main.py 11.0 Voila! TODO https://github.com/pyodide/pyodide あたりと組み合わせて numpy データを WASM で簡潔に扱えるようにしたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Detectron2のv0.5アップデート内容まとめ

画像認識や画像検出でよく利用されるプラットフォームであるDetectron2のアップデートがありました。 7/24にアップデートされた内容を確認して変更点のまとめと、気になった箇所を深掘りしました。 Detectron2そのものについては次の記事を参照ください。 変更点まとめ LazyConfigシステムを追加 Mask R-CNNベースラインを追加 mmdetectionのモデルにバックボーンラッパーとディテクターラッパーを追加 Implicit PointRend & PointSupのコードを公開 BatchNormのRethinking Batchのコードをリリース BatchNormのRethinking Batchのコードをリリース RegNetバックボーンのサポートを追加 DensePose CSEの新機能:詳細はリリースノート 以下の環境でビルド済みのLinuxバイナリが利用できます。 CUDA torch 1.9 torch 1.8 torch 1.7 11.1 install install 11.0 install 10.2 install install install 10.1 install install 9.2 install cpu install install install LazyConfigシステムを追加 従来のyacsベースのコンフィグシステムからLazyConfigシステムに対応したことで、柔軟性が大幅に向上しました。 Detectron2での公式サンプルは以下のようになっています。 # config.py: a = dict(x=1, y=2, z=dict(xx=1)) b = dict(x=3, y=4) # my_code.py: from detectron2.config import LazyConfig cfg = LazyConfig.load("path/to/config.py") # an omegaconf dictionary assert cfg.a.z.xx == 1 他にも実装時に参考になるサンプルがGitHubに公開されています。 # Common training-related configs that are designed for "tools/lazyconfig_train_net.py" # You can use your own instead, together with your own train_net.py train = dict( output_dir="./output", init_checkpoint="detectron2://ImageNetPretrained/MSRA/R-50.pkl", max_iter=90000, amp=dict(enabled=False), # options for Automatic Mixed Precision ddp=dict( # options for DistributedDataParallel broadcast_buffers=False, find_unused_parameters=False, fp16_compression=False, ), checkpointer=dict(period=5000, max_to_keep=100), # options for PeriodicCheckpointer eval_period=5000, log_period=20, device="cuda" # ... ) LazyConfigそのものについては以下を参照して下さい。 Mask R-CNNベースラインを追加 新しいベースラインはEpochsの増加によりAPが向上しています。 Mask R-CNN ResNet-50-FPN Box AP メトリックは、41から 46.7(ImageNet の初期化を使用)、47.4(ランダムな初期化を使用)に増加しました。 新しくDetectron2を動かす際は、Model Zooから最新のベースラインを使うことで精度向上が期待できます。 mmdetection mmdetectionが追加されたことで、さらに多くの事前学習済みのモデルを使用して画像検出等のタスクを実行できるようになりました。 MMDetecitonについて MMDetectionは、Model Zooで何百もの既存・在来の検出モデルを提供し、Pascal VOC、COCO、CityScapes、LVISなどの複数の標準データセットをサポートしています。これらの既存モデルや標準データセットに対して、以下のようなタスクを実行できます。 既存のモデルを使って、与えられた画像を推論する。 標準的なデータセットで既存のモデルをテストする。 標準的なデータセットで定義済みモデルを学習する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

時間軸、周波数軸の作成方法まとめ(Python)

はじめに フーリエ変換後の横軸の作成方法ですが、何種類かあるのでまとめたいと思います。ついでに時間軸についても触れます。 波形のパラメータ # サンプリング周波数[Hz] fs = 25000 # サンプリング周期[s](=1/fs) cs = 0.00004 # サンプリング時間[s] ts = 6 # データサイズ(=fs*ts) x = 150000 解説 上記4つのうち半分は残りの2変数から導出できるものであるため、定義としては必要ないが、今回は理解のしやすさの観点より、4変数を定義した。 時間軸 # パターン1 t1 = np.arange(0, ts, cs) print(t1) # パターン2 t2 = np.arange(0, x)/fs print(t2) # パターン3 t3 = np.linspace(0, ts, x) print(t3) [0.00000e+00 4.00000e-05 8.00000e-05 ... 5.99988e+00 5.99992e+00 5.99996e+00] [0.00000e+00 4.00000e-05 8.00000e-05 ... 5.99988e+00 5.99992e+00 5.99996e+00] [0.00000000e+00 4.00002667e-05 8.00005333e-05 ... 5.99992000e+00 5.99996000e+00 6.00000000e+00] 解説 基本的にただの書き換えでやっていることは一緒。 パターン1とパターン2はサンプリング周期(cs)をデータサイズ(x)分足し合わせていくイメージ。 パターン3は先に終点をサンプリング時間(ts)を参照して決め、データサイズで間隔を決める。 個人的には終点がぴったりサンプリング時間になるパターン3が好き 周波数軸 # パターン1 fq1 = np.arange(0, fs, fs/x) print(fq1) # パターン2 fq2 = np.arange(0, x)/ts print(fq2) # パターン3 fq3 = np.linspace(0, fs, x) print(fq3) # パターン4 fq4 = np.fft.fftfreq(x, cs) print(fq4) # パターン5 fq5 = scipy.fftpack.fftfreq(x, cs) print(fq5) [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 2.49995000e+04 2.49996667e+04 2.49998333e+04] [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 2.49995000e+04 2.49996667e+04 2.49998333e+04] [0.00000000e+00 1.66667778e-01 3.33335556e-01 ... 2.49996667e+04 2.49998333e+04 2.50000000e+04] [ 0. 0.16666667 0.33333333 ... -0.5 -0.33333333 -0.16666667] [ 0. 0.16666667 0.33333333 ... -0.5 -0.33333333 -0.16666667] 解説 結論としては、パターン1、パターン2が同じ、パターン4、パターン5が同じで、パターン3はどちらとも違う結果である。 パターン4、パターン5は後半部分が負の数になっている 標本化定理(サンプリング定理)より、解析できる周波数は最大でもサンプリング周波数の半分までなので、半分地点でのナイキスト定数が重要になってくる 半分の地点がどうなっているかを確認してみる↓ print(fq1[0:int(fq1.shape[0]/2)+1]) #半分の一つ先までで区切る print(fq2[0:int(fq2.shape[0]/2)+1]) ... [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 1.24996667e+04 1.24998333e+04 1.25000000e+04] [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 1.24996667e+04 1.24998333e+04 1.25000000e+04] [0.00000000e+00 1.66667778e-01 3.33335556e-01 ... 1.24997500e+04 1.24999167e+04 1.25000833e+04] [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 1.24996667e+04 1.24998333e+04 -1.25000000e+04] [0.00000000e+00 1.66666667e-01 3.33333333e-01 ... 1.24996667e+04 1.24998333e+04 -1.25000000e+04] ちょうど半分までだけで見ると、パターン1、2、4、5は同じ値であることが分かる。 細かい差にはなるが、パターン3は微妙にずれる。 パターン4、5を使い正の数部分を横軸とするのが、一番簡単で正確である。 結論 時間軸の作成にはパターン3、周波数軸の作成にはパターン4を使おうかなあ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LinuxでSSL証明書のエラーが出たときに試したこと (wget, Python)

普段Ubuntu Serverでいろいろやっています。 ある日突然サーバーでwgetが使えなくなりました。 $ wget https://xxxxxx.jp/ --2021-07-28 15:11:11-- https://xxxxxx.jp/ Resolving xxxxxx.jp (xxxxxx.jp)... Connecting to xxxxxx.jp (xxxxxx.jp)|xxxxxxxx|:443... connected. ERROR: cannot verify xxxxxx.jp's certificate, issued by ‘CN=GeoTrust RSA CA 2018,OU=www.digicert.com,O=DigiCert Inc,C=US’: Unable to locally verify the issuer's authority. To connect to xxxxxx.jp insecurely, use `--no-check-certificate'. SSL証明書のエラーが出てしまったのです。 最初はこのサイトだけかと思って、いろいろなサイトへ試しに接続しようとしたらそれらもみんなダメ。 そうこうしているうちに、サーバー利用者から「PythonでSSLエラーが出る」という報告を受けました。 どうやらwget以外でも影響がでているようなのです。 Pythonでの対処 とりあえずPythonの方を解決しようといろいろ調べると SSL_CERT_FILE という環境変数にCA証明書ファイルを指定するとよいという記載があったため、それに従って設定したところ、解決することができました。 PythonではCA証明書を持っているcertifiモジュールがあるため、そのモジュールが持っている証明書ファイルのパスを環境変数にセットしました。 ファイルパスは、python3 -m certifi で知ることができます。 $ python3 -m certifi /usr/local/lib/python3.8/dist-packages/certifi/cacert.pem $ echo SSL_CERT_FILE=/usr/local/lib/python3.8/dist-packages/certifi/cacert.pem >> $HOME/.bashrc $ source $HOME/.bashrc # 環境変数読み込み wgetでの解決方法 wgetに至ってはなかなか解決ができず、試行錯誤しつつ調べを進めると、wgetがどこのCA証明書ファイルを読みに行っているかを知ると早いと言っている記事を見つけました。 Cygwin - wget Cygwinなので一見関係ないように見えましたが、この方法で解決しました。 まずstraceコマンドでwgetがどんなファイルを読んでいるのか調べます。 すると、このようなログが。 $ strace wget https://xxxxxx.jp/ | grep ssl ... stat("/usr/local/ssl/certs/968d05c4.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory) stat("/usr/local/ssl/certs/3513523f.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory) stat("/usr/local/ssl/certs/3513523f.0", 0x7ffd21dd5a00) = -1 ENOENT (No such file or directory) stat("/usr/local/ssl/certs/e4af4c8e.0", 0x7ffd21dd59b0) = -1 ENOENT (No such file or directory) なんと /usr/local/ssl/certsのファイルを読みに行っていました。 そのディレクトリの中身を見ると空でした。 $ ls /usr/local/ssl/certs これでモヤモヤが解決されました。 CA証明書は自分の環境では /etc/ssl/certs にあるため、シンボリックリンクを貼って対処します。 $ sudo rm -rf /usr/local/ssl/certs $ sudo ln -s /etc/ssl/certs /usr/local/ssl/certs 再度wgetを試すと、エラーは解消できました。 同時にPythonでのエラーもなくなり、環境変数の設定が不要になりました。 最後に ca-certificatesをインストールする等では解決できないレアな事象であったため、今回Qiitaにまとめました。 今後このようなことがあった場合に役立つことができれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DRFの仕組みを追ってみる ①ルーティング

今回やる事 Djangoの流れはなんとなく掴めたが、よく使うDRFは具体的にどんなところを拡張しているフレームワークなのかが気になり始めたので、ひたすら実装を追いつつまとめていきたいと思います。 サーバーが立ち上がるところはDjangoと変わらないと思うので、まずはルーティングあたりから追ってみます。 環境 djangorestframework 3.12.2 python 3.7.9 DRFとは? まず、DRF(django rest framework)とはDjangoでRESTfulなAPIを簡単に作れるようにしてくれるフレームワークです。 RESTとは、REpresentational State Transferの略で、Webサービスの設計モデルの事です。 RESTについてはこの記事がとても参考になります。 REST入門 基礎知識 記事から引用しますと、 ●アドレス指定可能なURIで公開されていること ●インターフェース(HTTPメソッドの利用)の統一がされていること ●ステートレスであること ●処理結果がHTTPステータスコードで通知されること これらに沿ったWebサービスの事をRESTfulなサービスと言うみたいです。 そして、PythonでRESTfulなAPIを作ろうと思った時、有名どころで言うと以下の様になります。 DRF(django rest framework) Flask FastAPI この中でもDRFは、認証系の機能やページネーション、スロットリングなどの機能がデフォルトで実装されています。しかもDjangoに慣れているユーザーからしたら、学習コストも低いため総合的に見てDjangoユーザーはほぼDRF一択だと思います。 Router DRFではRouterというクラスによってルーティングを実装します。 デフォで用意されているのは、以下の2種類のクラスです。 ・SimpleRouter  名前の通りただのシンプルなルーター。 ・DefaultRouter  SimpleRouterを拡張したクラスでAPIのルートのviewと、URLにformat suffix patternを追加する機能が実装されている。 これらを参考に独自のルーターも作れたりするみたいですね。 それでは、SimpleRouterを例に実装を追っていきます。 SimpleRouter 実装方法 実際の実装方法は、以下の様になります。 urlpatternsにルートとなるURLとそのViewsetを記載。 1で定めたURL以下は、1で指定したViewsetのメソッドにデコレータを付ける事によってURLを指定 urls.py from rest_framework.routers import SimpleRouter from . import viewsets from django.urls import path, include router = SimpleRouter() router.register('users', viewsets.UserViewSet) urlpatterns = [ path('', include(router.urls)), ] viewsets.py class UserViewSet(ModelViewSet): @action(methods=['GET'], detail=False) def get_deleted_users(methods=['GET'], detail=False): ... これにより、以下の様に設定される。 ・「localhost/users/」にアクセスした時に、HTTPメソッドによりUserViewSetの指定メソッドが呼ばれる。 ・「localhost/users/get_deleted_users/」にGETメソッドでアクセスした時に、UserViewSetのget_deleted_usersメソッドが呼ばれる。 URL探索の仕組み ここはDjangoの処理そのままなため、思い出しながらまとめてみる。 リクエスト処理用のミドルウェア類を通る ↓ URLResolverにより、指定URLから該当Viewの取得 といった流れでResolverクラスによって、自分で指定したURLに対して、リクエストが来たURLが存在するか検索をかける。 ここではincludeでurlpatternsに含まれるようにしているので、SimpleRouterで指定したURLも検索対象になる事になる。 ※ちなみに、Djangoのincludeメソッドは、この場合はタプルで、router.urls、app_name、namespaceを返してくれるみたいです。 django.urls.conf.py def include(arg, namespace=None): if isinstance(arg, tuple): ... else: urlconf_module = arg ... return (urlconf_module, app_name, namespace) Resolverの検索の流れ URLResolverは、BaseHandlerの_get_responseメソッド内でインスタンス化されました。 URLResolverのresolveメソッドで、最初にリクエストのpath_infoをnewpathとargs, kwargsに分割します。 次にurlpatternsをループし、個々のpathを見ていき、分割したnewpathと照らし合わせていきます。 個々のpathとはurlpatternsに指定したこれです。 urlpatterns = [ path('', include(router.urls)), ] このpathの正体は、URLResolverか、URLPatternクラスになっています。 django.urls.conf.py path = partial(_path, Pattern=RoutePattern) def _path(route, view, kwargs=None, name=None, Pattern=None): if isinstance(view, (list, tuple)): # includeで指定した場合、こっち pattern = Pattern(route, is_endpoint=False) urlconf_module, app_name, namespace = view return URLResolver( pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, ) elif callable(view): pattern = Pattern(route, name=name, is_endpoint=True) return URLPattern(pattern, view, kwargs, name) else: raise TypeError('view must be a callable or a list/tuple in the case of include().') 今回はincludeを指定しており、includeは↑で見た通り、タプルで、router.urls、app_name、namespaceを返すので、この場合はURLResolverが返されます。 そしてこのURLResolverのresolveメソッドにより、ResolverMatchクラスが返されます。Djangoでは、ResolverMatchクラスが返される事により、該当Viewなどが返されました。 SimpleRouterの場合はこのURLResolverがどう構築されるか見てみます。 ①. includeで指定した場合は、以下の様にタプルになり、 urlpatterns = [ path('', (router.urls, app_name, namespace)), ] ②. ↑の引数を_path関数に渡します。 partialを使う事で、PatternをRoutePatternに固定し、_path関数を新たな呼び出し可能なオブジェクトとして定義する事が出来ます。 path = partial(_path, Pattern=RoutePattern) ③. 第一引数routeには指定したURLが、第二引数viewには(router.urls, app_name, namespace)が入る。 def _path(route, view, kwargs=None, name=None, Pattern=None): if isinstance(view, (list, tuple)): pattern = Pattern(route, is_endpoint=False) urlconf_module, app_name, namespace = view return URLResolver( pattern, urlconf_module, kwargs, app_name=app_name, namespace=namespace, ) ④. そして最終的にこのような中身で、インスタンス化されたURLResolverが返る。 return URLResolver( RoutePattern, router.urls, kwargs, app_name=app_name, namespace=namespace) ↑で返されたURLResolverが、URLResolverのresolveメソッド内のループで呼ばれる事で、router.urlsの中を探索してくれます。 URLResolverのループは下記のようになっています。 django.urls.resolvers.py class URLResolver: ... def resolve(self, path): path = str(path) tried = [] match = self.pattern.match(path) if match: new_path, args, kwargs = match # 最初はurls.pyで指定したurlpatternsがループされる。 # includeでrouterを指定した場合は、このメソッドが再度呼ばれる事でself.url_patternsはrouter.urlsとなる。 # そしてrouter.registerで登録したものも検索対象となる。 for pattern in self.url_patterns: try: sub_match = pattern.resolve(new_path) except Resolver404 as e: self._extend_tried(tried, pattern, e.args[0].get('tried')) else: if sub_match: 最初の探索では、url_patternsは単純にsettings.pyで指定しているURL_CONFのurlpatternsがループされ、includeでRouterを含めた場合はpatternがURLResolverになるため、sub_match = URLResolver.resolve(new_path)という風になり、更にresolveメソッドが呼ばれる事になる。その時のpatternは上記で記した通り、 return URLResolver( RoutePattern, router.urls, kwargs, app_name=app_name, namespace=namespace) といった感じのURLResolverとなっています。そしてこのクラスのresolveメソッドが呼ばれると、この場合のself.url_patternsはurlconf_nameとして渡したrouter.urlsが返される事になります。 django.urls.resolvers.py class URLResolver: # 引数省略 def __init__(self, patter, urlconf_name ...): self.pattern = pattern # これがrouter.urlsになる。 self.urlconf_name = urlconf_name ... @cached_property def urlconf_module(self): if isinstance(self.urlconf_name, str): return import_module(self.urlconf_name) else: # router.urlsはこっち return self.urlconf_name @cached_property def url_patterns(self): # router.urlsはurlpatternsを持たないので、router.urlsがイテレータとして返される。 patterns = getattr(self.urlconf_module, 'urlpatterns', self.urlconf_module) try: iter(patterns) ... return patterns router.urlsの中身 DRFのSimpleRouterの実装を見てみます。 self.url_patternsがループされurlsが呼ばれると、get_urlsメソッドが呼ばれるようになっており、router.registerで登録したregistryをループし、パスとviewを詰めたリストを返しています。 つまり、このリストがURLResolverのresolveメソッドでループされて検索されるみたいですね。 rest_framework.routers.py class BaseRouter: ... @property def urls(self): if not hasattr(self, '_urls'): self._urls = self.get_urls() return self._urls class SimpleRouter(BaseRouter): def get_urls(self): ret = [] for prefix, viewset, basename in self.registry: ... # routesは、HTTPメソッド毎やdetailがTrueかFalseかによってのメソッド名などを定義したリスト routes = self.get_routes(viewset) for route in routes: mapping = self.get_method_map(viewset, route.mapping) if not mapping: continue regex = route.url.format( prefix=prefix, lookup=lookup, trailing_slash=self.trailing_slash ) if not prefix and regex[:2] == '^/': regex = '^' + regex[2:] initkwargs = route.initkwargs.copy() initkwargs.update({ 'basename': basename, 'detail': route.detail, }) view = viewset.as_view(mapping, **initkwargs) name = route.name.format(basename=basename) ret.append(re_path(regex, view, name=name)) # このretを検索していく。 return ret ここでのroutesというのは、get_routesメソッドによりSimpleRouterで指定しているroutesと、指定したviewsetなどをリストにまとめたものみたいですね。 SimpleRouterで指定しているroutesはこんな感じ。 rest_framework.routers.py class SimpleRouter(BaseRouter): routes = [ Route( url=r'^{prefix}{trailing_slash}$', mapping={ 'get': 'list', 'post': 'create' }, name='{basename}-list', detail=False, initkwargs={'suffix': 'List'} ), DynamicRoute( url=r'^{prefix}/{url_path}{trailing_slash}$', name='{basename}-{url_name}', detail=False, initkwargs={} ), Route( url=r'^{prefix}/{lookup}{trailing_slash}$', mapping={ 'get': 'retrieve', 'put': 'update', 'patch': 'partial_update', 'delete': 'destroy' }, name='{basename}-detail', detail=True, initkwargs={'suffix': 'Instance'} ), DynamicRoute( url=r'^{prefix}/{lookup}/{url_path}{trailing_slash}$', name='{basename}-{url_name}', detail=True, initkwargs={} ), ] ... このようにHTTPのメソッド毎にメソッドの名前を指定しているみたいですね。HTTPメソッドがgetで来た場合でもdetail=Falseだとlistメソッドが呼ばれdetail=Trueだとretrieveが呼ばれるように指定されています。 つまり、router.registerで登録した分だけループされてその要素毎にHTTPメソッドに合わせたメソッド名などが指定されたオブジェクトをリストで持っていて、そのリストを検索するようになっている様子です。 そして、Djangoと同じく最終的にResolverMatchクラスが返されてViewなどの情報が返されるんですね。 まとめ DRFがどのようにDjangoのルーティングを拡張しているのかが分かりました。 拡張しているDRFもすごいですが、Djangoがこのように拡張出来るように作られている事に感動しました。 DRFのルーティングの流れが全体的になんとなくつかめたところで、次は違うところを見てみようと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの仮想環境

Pythonの仮想環境 Node.jsからPythonを触ることが多くなった最近ですが、みなさんPythonのパッケージ管理ってどうしてますか? 何でもかんでもグローバルにインストールして収集つかなくなったり、複数で作業するとき開発環境をコピーしたりって、 ぶっちゃけ面倒くさくないですか?(思いますよね?) Node.jsのエコシステムに慣れてしまったせいか、git cloneしてnpm installを叩けば依存パッケージを解決してくれて、、 みたいなお手軽感から抜け出せずにおります。 そこで今回は、Node.js並みにPythonのパッケージ管理を簡単にやる方法を考えよう!です。 外せない要件は以下とします。 パッケージのメタデータは(ほぼ)自動で管理してくれる パッケージはコピー先で一括インストールできる 前提 OS: Mac OS Big Sur Python: 3.9.5 (pyenv使ってるので何でも) 仮想環境の選定 まあメジャーどころを。。 venv Python 3.3から標準装備 すぐ使える conda Anacondaをインストールする必要がある オマケ感が否めない virtualenv サードパーティ製なので要インストール Python3.3でvenvとして標準機能に取り込まれた というわけで、「venv」一択です。 やってみよう 仮想環境の作成 プロジェクトディレクトリ直下にenviron_nameディレクトリができます $ mkdir project_name $ cd project_name $ python -m venv environ_name アクティベート source envrion_name/bin/activaate 作成した仮想環境にパッケージをインストール 今回は試しにsuperjsonをインストール(datetimeなんかをシリアライズ化くれるやつ) なお、アクティベートするとプロンプトの先頭に環境名が表示されます (environ_name) project_name $ pip install superjson 仮想環境にインストールされてるパッケージの確認 (environ_name) project_name $ pip freeze superjson==0.0.13 パッケージ一覧を書き出す (environ_name) project_name $ pip freeze > requirements.txt 仮想環境を停止する (environ_name) project_name $ deactivate 作成した仮想環境を再現する 今回はGitHubからクローン来ることを想定 別プロジェクトを作成 $ mkdir new_project $ cd new_project $ git clone ... # 割愛 新たに仮想環境を作ってパッケージをインストール (new_environ) new_project $ pip install -r requirements.txt インストールされたか確認 (new_environ) new_project $ pip freeze superjson==0.0.13 お、なんかいい感じでじゃないでしょうか。。ご参考までに。 参考文献 venv --- 仮想環境の作成 【Python】venvで作成した仮想環境をコピーする方法 venv: Python 仮想環境管理
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】tkinterさんを忘れずに使いたい

本日もお疲れ様です。 皆さんは数多のPythonのライブラリを扱いますか?(Go〇gle翻訳風) 数々のライブラリを扱い「あ、久々に〇〇また使おうかな~」となった時、 どんな機能があるかは覚えているものの「肝心の書き方をイマイチ思い出せないッ…!」 みたいなこととかありませんか? 僕はあります() 今回はよく使う「tkinter」についてまとめ、 今後そのようなことがあっても困らないようにしておきます。 今回の環境 Python3.9(2系だと書き方が異なるので注意) ① 好きなアイコンを設定してダイアログを出そう tkinter_test.py #! coding: utf-8 import os from tkinter import * from tkinter import ttk def main(): root = Tk() root.title(u"TKDialog Test") img = PhotoImage(file = os.path.join(os.getcwd(), "kuma.gif")) root.tk.call('wm', 'iconphoto', root._w, img) root.geometry("512x288") root.mainloop() if __name__ == "__main__": main() ● PhotoImage  アイコンにする画像をここで定義します。  次に、 root.tk.call('wm', 'iconphoto', root._w, 〇〇)  に定義した画像を渡すことで、アイコンに設定することが出来ます。  (こちら以外にも iconbitmap でも、アイコンを設定することが可能です) ちっさいですが、デフォルト以外のアイコン(くまさん)を表示することが出来ました。 ② ダイアログの最大・最小サイズを設定しよう 先ほどのダイアログですが、自由にサイズを変更することが出来ます。 ウソみたいだろ。ダイアログなんだぜ。これで。 自由すぎるのも考えものなので、最大・最小サイズを以下のように設定して制御しましょう。 tkinter_test.py root.minsize(256, 144) root.maxsize(1024, 576) ③ 王道・ボタンを配置してみよう 何かと便利なボタンを表示してみます。 tkinter_test.py def PrintTime(): print("Push : {}".format(datetime.datetime.now())) def main(): # ---一部省略--- frame = Frame(root, pady = 10) frame.pack(anchor = CENTER) button = Button(frame, text = u"これぞ王道ッ", relief = RAISED, width = 10, command = PrintTime) button.grid(row = 0, column = 0) 実行後↓ ● relief  ボタンの見た目を設定するオプションです。  サンプルのRAISED以外にも、以下のような設定があります。 オプション名 外見 FLAT 平らなボタン SUNKEN 凹んだボタン SOLID 太い枠線で囲まれたボタン RIDGE 枠線が隆起しているボタン GROOVE 枠線が溝みたいになっているボタン ● command  ボタン押下時に実行される関数を渡します。 ④ リストをコンボボックス化してみよう ネットでよく見るコンボボックスを表示してみます。 tkinter_test.py def main(): # ---一部省略--- frame = Frame(root, pady = 10) frame.pack(anchor = CENTER) animals = ["inu", "neko", "panda"] combo_box = ttk.Combobox(frame, values = animals, state = 'readonly', width = 10) combo_box.set(animals[0]) combo_box.bind('<<ComboboxSelected>>', lambda e: print(combo_box.get())) combo_box.grid(row = 0, column = 0) 実行後↓ ttk.Combobox() この関数に対して表示するリストを渡します。 また「state = 'readonly'」を設定することで、リスト以外の値を受け付けないようにしています。 (この設定を外すとボックス内に値を入力することが出来るようになります。) combo_box.bind() コールバック関数(処理)を設定することが出来ます。 上記のサンプルでは、ボックス内の値を変更したときに、 選択した内容をプリントするようにしてみました。 ⑤ ツリービューを表示してみよう +ボタンをクリックしたら子要素がワッと表示されるアレです。 ファイル一覧を表示する時とかに便利な機能です。 tkinter_test.py def main(): # ---一部省略--- frame = Frame(root, pady = 10) frame.pack(anchor = CENTER) # ツリービュー tree = ttk.Treeview(frame, height = 10, padding = 3, show = 'tree') # 横のスクロール scroll_h = ttk.Scrollbar(frame, orient = HORIZONTAL, command = tree.xview) scroll_h.grid(row = 2, column = 0, sticky = EW) # 縦のスクロール scroll_v = ttk.Scrollbar(frame, orient = VERTICAL, command = tree.yview) scroll_v.grid(row = 1, column = 1, sticky = NS) # スクロールバーの設定をビューに反映 tree.configure(xscrollcommand = scroll_h.set, yscrollcommand = scroll_v.set) tree.grid(row = 1, column = 0, sticky = NSEW) # こういうファイル構成があったとする data_list = ["animals\\inu.txt", "animals\\nako.txt", "animals\\panda.txt", "fruits\\ringo.txt", "fruits\\mikan.txt", "fruits\\momo.txt", "money\\suidohi.txt", "money\\konetsuhi.txt", "money\\gasudai.txt"] # 親要素の名前とIDを紐づけるリスト directory_list = {} for item in data_list: split_path = item.split(os.path.sep) # まだ存在しない親要素の場合、親・子要素を挿入する if not split_path[0] in directory_list: parent_id = tree.insert("", "end", text = split_path[0]) tree.insert(parent_id, "end", text = split_path[-1]) directory_list[split_path[0]] = parent_id else: # 親要素があれば子要素のみ挿入する tree.insert(directory_list[split_path[0]], "end", text = split_path[-1]) 実行後↓ ファイル一覧っぽい感じになりました。 (今回は1段階の階層にしましたが、複数の階層を実装することも可能です。) 色々実装しましたが、要素の挿入自体は tree.insert() のみで行っています。 この関数に渡している内容は以下になります。 第1引数 どの親要素に挿入するのかを、親要素のIDで指定します。 指定しない際(親要素を新しく追加する際)はダブルクォートを記入します。 第2引数 どの位置に挿入するかを指定します。 末尾に挿入する際は"end"を記入します。 第3引数 挿入するアイテムの名前を記入します。 ⑥ テキストを入力(表示)できる欄を表示しよう 名前入力とかしてもらい時にあると便利な機能です。 tkinter_test.py def main(): # ---一部省略--- frame = Frame(root, pady = 10) frame.pack(anchor = CENTER) text = StringVar() text_box = Entry(frame, width = 30, textvariable = text) text_box.grid(row = 0, column = 0) 実行後↓ ● Entry()  テキストボックスを表示する際に使用します。  textvariable に StringVar() を渡すことで、入力した内容を取得することが出来ます。  (上記のサンプルでは、text.get()のようにして取得が出来ます。) ⑦ 長いテキストを入力(表示)できる欄を表示しよう ログを出したい時とかに便利な機能です。 tkinter_test.py def main(): # ---一部省略--- frame = Frame(root, pady = 10) frame.pack(anchor = CENTER) # テキスト入力欄 txt = Text(frame, font = ("Helvetica", "9"), width = 60, height = 12) # 横のスクロール scroll_h = ttk.Scrollbar(frame, orient = HORIZONTAL, command = txt.xview) scroll_h.grid(row = 2, column = 0, sticky = EW) # 縦のスクロール scroll_v = ttk.Scrollbar(frame, orient = VERTICAL, command = txt.yview) scroll_v.grid(row = 1, column = 1, sticky = NS) # スクロールバーの設定をビューに反映 txt.configure(xscrollcommand = scroll_h.set, yscrollcommand = scroll_v.set) txt.grid(row = 1, column = 0, sticky = NSEW) 実行後↓ こんな感じになります。 フォントの設定をしていますが、設定しなくても問題ありません。 state='disabled' を設定することで、入力を受け付けなくすることもできます。 (再度入力を可能にする際は、txt.configure(state = 'normal') を実行します。) プログラムからテキストを入力したい場合は、insert() を使用します。 tkinter_test.py txt.insert(END, "Aiueooo") 第1引数 テキストを挿入する位置を指定します。 第2引数 挿入するテキストを指定します。 また、文字の背景色を指定することも可能です。 以下は黄色を指定したものです。 tkinter_test.py txt.insert(END, "Aiueooo", 'yellow') txt.tag_configure('yellow', background='yellow') マーカーを引いたみたいで分かりやすいです。 まとめ 今回は自分が比較的よく扱うものについてまとめてみました。 tkinterに関する記事はたくさんありますが、この記事が何かお役に立てれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

slack_sdkのバージョン3.8.0等でicon_emojiを利用する場合

slack_sdkのバージョンアップによりWebhookClientのsendメソッドでicon_emoji等が設定できなくなっていましたので、以下の方法に変更 webhook = WebhookClient("https://hooks.slack.com/services/......") response = webhook.send_dict( body={ 'username':'UserName', 'icon_emoji':':fire:', 'text':'テストメッセージ' } ) ライブラリのバージョンアップでまた変更されるかもですが、sendではなく、send_dictに変更することで解決しました。 2021/07/28時点の情報です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

単変量時系列データへのDMDの適用と結果の見方

はじめに こんにちは、(株)日立製作所Lumada Data Science Lab.の田中と申します。 早速ですが、電流や振動、温度、圧力などのセンサデータから異常を検知したり、異常の予兆を検知するというタスクが世の中にはあります。このとき、扱うデータフォーマットは一定時間間隔でセンサの値を取得した単変量の時系列データであることが多いです。 その際、時系列データに対してRMS(Root Mean Square)や時間微分、フーリエ変換などの様々な処理を施すことでデータの特徴や構造を把握し、効果的な前処理やアルゴリズムの検討に役立てるということがあるかと思います。 そこで、本記事ではデータの特徴を把握する上で有効な手法の一つである動的モード分解(DMD:Dynamic Mode Decomposition)の単変量時系列データへの適用方法と結果の見方についてご紹介します。 DMDは時々刻々と変化していく状態量の中に共通して存在するパターンを抽出し、そのパターンが時間にしたがってどのように振動、減衰あるいは増幅していくのかといった特徴を把握することが可能です。例えば、乱流の解析や人間の動作の解析といったところで使われています。 私自身DMDは流体の分野でよく用いられるように多次元データに適用するイメージが強かったのですが、実はセンサデータのような一次元の単変量時系列データへも適用可能だということを知ったので、今回はその点にフォーカスしてPythonでの実装と合わせてご紹介していきたいと思います。 実装 必要なライブラリをimportします。 import numpy as np from numpy import pi, exp, log from numpy import dot, multiply, diag, power from numpy.linalg import inv, eig, pinv import matplotlib.pyplot as plt 時系列データ作成 以下の時系列データ$F$を作成します。 output_dir = "analysis/" N = 200 OBSERVASION_TIME = 1 t_raw = np.linspace(0, OBSERVASION_TIME, N) dt = t_raw[2] - t_raw[1] trend = 10 * (t_raw - OBSERVASION_TIME) ** 2 periodic1 = np.sin(10 * 2 * pi * t_raw) / exp(-2 * t_raw) periodic2 = np.sin(20 * 2 * pi * t_raw) np.random.seed(123) noise = 1.5 * (np.random.rand(N) - 0.5) F = trend + periodic1 + periodic2 + noise $F$は以下の4つのデータの和で構築されています。 1. 緩やかに減少する2次関数 2. 10Hzで振動し減衰するsin波 3. 20Hzで振動し減衰も増幅もしないsin波 4. 一様乱数で生成されるノイズ ここで、1をtrend、2をperiodic#1、3をperiodic#2と呼び、DMDで分解できるか検証していきます。 $F$と1~4のデータをプロットすると以下のようになります。 前処理(Hanakel行列化) 時系列データ$F=\lbrace f_0,f_1,f_2,...,f_{N-1}\rbrace$をスナップショット$\lbrace f_i, f_{i+1},...,f_{i+L-1}\rbrace\; (i=0\,,...,\,N-L)\,$に分割し、各スナップショットを連結することで以下の行列を得ます。 前処理として時系列データを行列$D$に変換します。 これはHankel Matrixと呼ばれ、この形にデータを変形することにより単変量時系列データへのDMD適用が可能になります。Dを計算する部分の実装は参考文献[1]から引用しています。 L = 70 K = N - L + 1 D = np.column_stack([F[i : i + L] for i in range(0, K)]) $N$はデータ数、$L$はwindow lengthと呼ばれ$2\leq L \leq N/2$で設定します。 $L$は列の長さに対応し、$K=N-L+1$が行の長さに対応します。 Dを可視化すると以下のようになります。 DMD実行 DMDでは以下のように状態$X$から1ステップ先に進んだ状態$Y$をある線形変換$A$で表現します。 $$Y = AX \tag{1}$$ $A$の固有値解析を行うことでデータの中に存在する様々なモードの情報がわかってきます。 状態$X$と$Y$を作ります。 X = D[:, :-1] Y = D[:, 1:] t = np.arange(0, K - 1) 次に$A$を求めていきます。 $A$は以下のノルムを最小化することで求めることが可能です。 $$ min|AX-Y|_F^2 \tag{2}$$ $| \cdot |_F$はフロベニウスノルムです。 上記の最小化問題の解は以下になることが知られています。 $$ A=YX^\dagger \tag{3}$$ $X^\dagger$は擬似逆行列(Moore-Penrose pseudoinverse)と呼ばれるものです。 ここで、$X$の特異値分解(SVD)を実行します。 SVDにより分解されたモードの内、重要度の低いモードを切り捨てることで効率的に情報を残したまま次元削減が可能です。 $$ X=U\Sigma V \tag{4}$$ U2, Sig2, Vh2 = np.linalg.svd(X) ここで各成分の寄与率累積寄与率を計算します。 $$ Relative:contributions = \frac{\sigma_i^2}{\sum_{k=0}^{d-1}\sigma_k^2} \tag{5}$$ sigma_sumsq = (Sig2 ** 2).sum() cumlative_contrib = (Sig2 ** 2).cumsum() / sigma_sumsq * 100 寄与率と累積寄与率をプロットします。 上位5つの成分で全体の99%以上の寄与率に達しています。 今回はこれら5つの成分を使用し残りは切り捨てることにします。 ここで累積寄与率何%までの成分使うのかは特にこれといった指標はないのでデータの特徴や最終的に達成したい目的など包括的に考えて選ぶ必要があります。 以上より$A$の低次元空間での表現$\tilde{A}$は以下のようになります。 $$ \tilde{A}=U_r^* A U_r \tag{6}$$ $r$は選択した次元数なので今回は5になります。$U^*$は$U$の随伴行列です。 $V, \Sigma$は直行行列であるため、 $$ \tilde{A}= U_r^*AU_r=U_r^*AU_r\Sigma_rV_r^*V_r\Sigma_r^{-1}=U_r^*AXV\Sigma_r^{-1}=U_r^*YV_r\Sigma_r^{-1} \tag{7}$$ より$\tilde{A}$が求まります。 Atil = dot(dot(dot(U.conj().T, Y), V), inv(Sig)) 次に$\tilde{A}$の固有値と固有ベクトルを求めます。以下は$\tilde{A}$の固有値分解の式です。 $$ \tilde{A}W=W\Lambda \tag{8}$$ mu, W = eig(Atil) $\tilde{A}$の固有値をプロットしてみます。 各固有値の偏角はモードの振動数、絶対値はモードの減衰/増幅率に対応しています。固有値が虚数軸に対して対称になっているのは、共役な複素固有値が存在するためであり、共役な複素固有値は同じ振動特性を示すため、ここには実質3つの振動モードが存在していることがわかります。 また、各固有値の色は各固有値に対応するモードの振幅に対応させています。振幅によりそのモードがシステムの特性にどれだけ影響を与えているかを評価することが可能です。説明の都合上振幅の求め方は後述します。 各固有値$\lambda_j({j=0, 1, ..., r})$の絶対値は以下のようにモードの減衰及び増幅を意味します。 * $|\lambda_j|<1:$このモードは減衰します。 * $|\lambda_j|=1:$このモードは減衰も増幅もしません。 * $|\lambda_j|>1:$このモードは増幅します。 固有値が存在している領域を拡大した右側の図を見ると以下のことが言えます。 * $\lambda_0(mode0):$実軸上に乗っているため非振動モードであり、$|\lambda_j|<1$であるため減衰します。toy dataのtrendに対応すると考えられます。 * $\lambda_1(mode1):$振動モードであり$|\lambda_j|>1$であるため増幅します。periodic#1に対応すると考えられます。 * $\lambda_3(mode3):$振動モードであり$|\lambda_j|=1$であるため減衰も増幅もしません。periodic#2に対応すると考えられます。 次に各固有値に対応する振動モードの周波数を求めます。 周波数は固有値の偏角$(\arg)$を用いて、以下のように計算されます。$\Delta t$は時系列データのタイムステップです。 $$f_j=\frac{\arg(\lambda_j)}{2\pi \Delta t} \tag{9}$$ 以下のコードで各モードの振動数を出力します。 freqs = np.angle(mu) / (2 * pi * dt) for idx, freq in enumerate(freqs): print("mode" + str(idx) + " freq=" + str(freq)) 出力結果 mode0 freq=0.0 mode1 freq=9.989363517060186 mode2 freq=-9.989363517060186 mode3 freq=19.957930838631462 mode4 freq=-19.957930838631462 mode0は振動数がゼロであり、trendに対応することがわかります。 periodic#1は10Hz、periodic#2は20Hzのsin波を用いて作成しており、mode1とmode2がperiodic#1、mode3とmode4がperiodic#2に一致していることがわかります。 次にDMDモードとその時間発展を計算していきます。 各モードの時間発展は以下で計算されます。 $$x(t)\approx\sum_{j=0}^{r}\phi_j\mathrm{exp}(\omega_jt)b_j=\mathbf{\Phi}\mathrm{exp}(\Omega t)\mathbf{b} \tag{10}$$ $\omega_j, \phi_j$は$A$の固有値、固有ベクトルを意味しておりそれぞれ以下で計算されます。詳しい計算過程は参考文献[2]を参照ください。 $$\mathbf{\Omega}=\mathrm{diag}(\omega_j), \quad\omega_j=\frac{\ln{(\lambda_j)}}{\Delta t} \tag{11}$$ $$ \mathbf{\Phi}=\mathbf{Y}\mathbf{V}_r\mathbf{\Sigma}_r^{-1}\mathbf{W} \tag{12}$$ $b_j$は$t=0$時の$\phi_j$方向の初期値を示して、$x(0)=X$より$X=\Phi\mathbf{b}$であるため、 $$\mathbf{b}=\mathbf{\Phi}^\dagger\mathbf{X} \tag{13}$$ で求められます。 $b_j$はDMDにおける振幅として扱われます。$b$の絶対値を先ほどの固有値プロットの色に対応させています。 ここで、(10)式の$\phi_j$がDMDモードであり、全てのスナップショット(列方向)に一貫して存在する構造になります。 (10)式の$\mathrm{exp}(\omega_j t)b_j$の部分はDMDの各モードに対応する時間発展$\Psi$でありtime dynamicsとも呼ばれます。 Phi = dot(dot(dot(Y, V), inv(Sig)), W) Psi = np.zeros([r, len(k)], dtype="complex") for idx, mu_elem in enumerate(mu): for _k in k: Psi[idx, _k] = exp(log(mu_elem) / dt * _k * dt) * b[idx] 各固有値に対応するDMDモードとtime dynamicsをプロットすると以下のようになります。 DMDモードはHankel行列の列方向に存在するパターンであり、DMDモードがどのように時間発展していくかを示したものがtime dynamicsとなります。以下のようなイメージを持っていただければよいかと思います。 モード0は減衰、モード1と3は増幅、モード3と4は減衰及び増幅なしの振動であることがtime dynamicsから見て取れます。 今回のケースではHankel行列の列方向も行方向と同様に単変量(スカラー)の時間発展を意味するため、DMDモードはtime dynamics類似した傾向を示しています。 単変量以外のデータを扱う場合、ある時刻におけるシステムの状態量を1次元化したものをスナップショットとして結合したものを状態行列として扱うため、モードとDynamicsの意味するところは異なり両者を見比べながらシステムを把握することが重要になります。 ちなみに、図からも明らかなようにmode1とmode2、mode3とmode4のような共役な複素固有値は全く同じ振動特性を示すため、和を取って1つのモードと見なして問題ありません。 時系列データ再構築 最後に(10)式より元の時系列データの再構築を行います。 # Compute DMD reconstruction for each mode x_t_list = [] for idx, mu_elem in enumerate(mu): x_t = [] for t_ in t: x_t.append(Phi[:, idx] * exp(log(mu_elem) / dt * t_ * dt) * b[idx]) x_t = np.array(x_t).T x_t_list.append(x_t) # Convert Hankel matrix to time-series x_t_recon = [] for x_t in x_t_list: x_t_recon.append(X_to_TS(x_t)) x_t_recon = np.array(x_t_recon) F_recon = x_t_recon.sum(axis=0) 元データは各モードの時間発展の線形和で求めています。X_to_TSという関数はHankel行列から時系列データに戻す関数であり参考文献[1]から引用しています。各モードの時間発展を見る必要がない場合、$\Phi$と$\Psi$の積を取るだけで時系列データ全体の再構築を計算することも可能です。 元データと各モードの再構築結果を以下に示します。 適切にモード分解できていることがわかります。Periodic#1とPeriodic#2はそれぞれ2つのモードに分けられているため、一見振幅が小さくなったように見えていますが、和を取れば元の振幅に一致します。 最後に元データと再構築データを重ね合わせると以下のように非常によく一致していることがわかります。 以上より、DMDにより単変量時系列データをモード分解し、周波数や減衰/増幅などの特徴を把握することができました。 おわりに このように減衰や増幅を伴う波形の解析を行う際にDMDを使用することできれいにモード分解することが可能です。 周波数成分に分解するという点でフーリエ変換と類似していますが、フーリエ変換では減衰/増幅という動的な特徴をわかりやすい形で把握することは困難です。DMDでは減衰/増幅を固有値というわかりやすい形で捉えられるため、システムを直感的に把握するという点でより有効であると考えられます。フーリエ変換とDMDの比較については、いくつかの実験を行った結果を別の記事で紹介できればと考えています。 また、今回はシンプルなトイデータを用いて検証したため、きれいな結果が出ていますが、実データに適用するときれいにモード分解されないという状況も起こりうるかと思います。実際に様々な状況でDMDによるモード分解がうまくいかないことは知られており、それらの問題に対応するためにDMDの改良版もいくつか開発されています。 今後はそういったDMDの改良版の検証や機械学習との絡め方についての検討も進めていきたいと思います。面白い検証結果が得られたらこちらも記事にしていければと考えています。 参考文献 [1] Introducing SSA for Time Series Decomposition [2] Dynamic Mode Decomposition in Python [3] On Dynamic Mode Decomposition: Theory and Applications [4] A Dynamic Mode Decomposition Approach With Hankel Blocks to Forecast Multi-Channel Temporal Series [5] Dynamic Mode Decomposition of Fast Pressure Sensitive Paint Data [6] Dynamic Mode Decomposition: Theory and Data Reconstruction [7] Visualization and Selection of Dynamic Mode Decomposition Components for Unsteady Flow
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

mayaでDeadlineを使ってみる2

普段案件毎に読ませるプラグインを変えているので、deadlineのワーカーにも同一のプラグインを読ませないと動かない。 プラグインについてはver違いなどもあるので、ワーカーにとりあえず全部インストールスタイルは通用しない。 案件のmaya起動に使用しているusersetup.pyをmayaBatchに読ませたら良いのでは? mayaBatch.exeに読ませられるか実験 SET MAYA_UI_LANGUAGE=en SET PYTHONPATH="Z:/python" START "" "C:\Program Files\Autodesk\Maya2019\bin\MayaBatch.exe" -file "Z:\testFile.ma" 結果:どうやら読ませられる。 mayaBatch で GUIをいじるとクラッシュするとの報告も見かけたので、念のため usetsetup.py 内で if cmds.about(batch = True) ==False: によってmayaBatch時はGUI編集を飛ばすように対応。 usersetup.pyを読ませるために デッドラインの mayaBatch プラグインをちょっと編集 直接編集は怖いので、複製して別名プラグインとして編集。 def StartJob 内に self.SetEnvironmentAndLogInfo("PYTHONPATH", "Z:/python") を追記。 ※SetEnvironmentAndLogInfo は環境変数を指定するためのdeadlineのコマンド usersetup.pyの場所も実行時のオプションとして渡したいのであれば、 サブミット時に生成される XXXXXXXX.job ファイルにオプションを追加。 PYTHONPATH=Z:/python それをプラグイン側で読み込ませて、環境変数に渡す。 pythonPath = self.GetPluginInfoEntryWithDefault("PYTHONPATH", '') if pythonPath != "": self.LogInfo("Adding " + pythonPath + " to PYTHONPATH") self.SetEnvironmentAndLogInfo("PYTHONPATH", pythonPath) オプションの追加はサブミッターも合わせて改修する必要があるので、現在は案件毎にハードコーディング。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

写真から人間の3Dモデルを出力できたら、イラストに活用できるよねという話。- TCMR-

はじめに 動画を読み込んで骨格検知して検出した動きを3Dモデルに当てはめてみようと思います。 (といってるけど、この記事ではアルゴリズムの内容の把握とデモを動かすだけにとどまる。) なお、当記事は前回の記事にも記載したSMPLの準備が必須になっています。 (前回の記事は↓↓↓) https://qiita.com/akaiteto/items/b5c8c3d5eb5ca3849c5d 前回記事でダウンロードしたソースは使いませんが、 同梱しているpklというバイナリ化された3Dモデルのファイルが必要なのでそちらを準備して下さい。 TCMR はじめに https://github.com/hongsukchoi/TCMR_RELEASE 今回試してみるのはCVPR2021に出展していた研究です。 https://arxiv.org/pdf/2011.08627.pdf 論文も読みますがひとまずデモソースを動かしてみましょう。 前提条件 私の環境は以下の通り googleアカウント     何でもいい。 google colab     pythonをブラウザから実行できるとんでもサービス。     pipのコマンドはもちろん、apt-getなどのLinux系のコマンドも実行できるしGPUも使える。     あまりにも研究向けの無料サービス SMPLのモデルファイル     前回の記事参照 とりあえずデモ動かす githubに手順が書かれているのでそのとおりに行えば良いだけです。 が、google colabで行うので自分のために備忘録として残します。 3Dモデルアップロード google colabのサーバーに3Dモデルをアップロードします。 この3Dモデルに最終的な骨格が当てはめられるので必須の作業です。 まず、赤枠を選択してpklファイルを選択します。 上述していますが、pklファイル(3Dモデル)はSMPLからダウンロードしてください。(前提条件参照) pklのファイルを選択します。3つありますが男か女か程度の差異なので適当に選びます。 このファイルには3Dモデルの情報がバイナリ化されて入っています。 アップロード中。左下のプログレスバーが完了するまで待ちます。 時間がかかります。待たずにやると当然ですがファイルが不完全なので、 プログラムの実行部分で読み込みエラーが出ます。待ちましょう。 実行環境準備 ソースを引っ張ってきて必要なライブラリを実行します。 google colabを使う前提なので、コマンドのまえにビックリマークがついています。 !git clone https://github.com/hongsukchoi/TCMR_RELEASE !pip install numpy==1.17.5 torch==1.4.0 torchvision==0.5.0 !pip install git+https://github.com/giacaglia/pytube.git --upgrade !pip install -r TCMR_RELEASE/requirements.txt なお、公式を見るとシェルを叩いて環境を構築しますが、 google colabの環境だとどうにも上手く行かなかったので内容を手打ちしています。 手打ちで省いた箇所は、仮想環境構築の部分です。 google colabはサイクルで環境がリセットされるのでこれで問題有りませんが、 自分のPCでやる方は仮想環境を・・・というかシェルから実行しましょう。 3Dモデル配置 「3Dモデルアップロード」の手順で実行しているはずですが、 pklファイルをアップロードしてください。 デモ環境準備 デモソースを実行するための準備をします。 ! mkdir output ; cd output ; mkdir demo_output ! mkdir data ; cd data ; mkdir base_data ! mv basicmodel_f_lbs_10_207_0_v1.1.0.pkl data/base_data/basicmodel_f_lbs_10_207_0_v1.1.0.pkl ! cp data/base_data/basicmodel_f_lbs_10_207_0_v1.1.0.pkl data/base_data/SMPL_NEUTRAL.pkl !source TCMR_RELEASE/scripts/get_base_data.sh 最終的にこのようになってればOK。 やってることは出力先のフォルダ作ってリネームしただけです。 また、get_base_data.shで必要な入力動画データと学習済みのモデルをダウンロードしてます 最終的にこうなってればOK デモ環境準備 !python TCMR_RELEASE/demo.py --vid_file demo.mp4 --gpu 0 これを実行して成功すればでも完了です。 実行します。 エラー1:ファイル破損 error.py Traceback (most recent call last): File "TCMR_RELEASE/demo.py", line 376, in <module> main(args) File "TCMR_RELEASE/demo.py", line 103, in main hidden_size=1024 File "/content/TCMR_RELEASE/lib/models/tcmr.py", line 131, in __init__ self.regressor = Regressor() File "/content/TCMR_RELEASE/lib/models/spin.py", line 229, in __init__ create_transl=False, File "/content/TCMR_RELEASE/lib/models/smpl.py", line 65, in __init__ super(SMPL, self).__init__(*args, **kwargs) File "/usr/local/lib/python3.7/dist-packages/smplx/body_models.py", line 188, in __init__ encoding='latin1')) _pickle.UnpicklingError: could not find MARK はいエラー。smplxのモデルの読み込みのところでエラーが出ています。 https://github.com/vchoutas/smplx/blob/77cf2a7010370c1e44141fab5d15ad8a0841bc9d/smplx/body_models.py#L2348 このあたりでエラーが出ている模様。 ファイルの存在チェック諸々はされているので、ファイルの存在云々ではなさそう。 test.py ## pip install smplx==0.1.13 ## pip install chumpy from smplx import SMPL as SMPL SMPL_MODEL_DIR = 'data/base_data' smpl = SMPL( SMPL_MODEL_DIR, batch_size=64, create_transl=False ).to('cpu') 今のままだと調査しづらいのでエラー箇所を抽出します。 アップロードしてる最中にファイルが壊れたかな?ローカル環境で実行したところ、 問題なく動作しました。ファイルを再アップロードします。 余談ですが、SMPLの3DモデルにはSMPL,SMPLX,SMPLHの3つがあるようで、 はじめインストールしているライブラリ名からして、SMPLXのモデルじゃないことが原因かと思いましたが、 通常のSMPLのモデルでも問題なさそうです。 更に余談ですが、 smplで一番初めにインスタンを立ち上げる際、3Dモデルを指定する部分で フォルダ名で指定すると、モデル名はデフォルトで「SMPL_NEUTRAL.pkl」で読み込もうとするので注意です。 エラー2:GPUエラー Traceback (most recent call last): File "TCMR_RELEASE/demo.py", line 376, in <module> main(args) File "TCMR_RELEASE/demo.py", line 103, in main hidden_size=1024 File "/content/TCMR_RELEASE/lib/models/tcmr.py", line 134, in __init__ pretrained_dict = torch.load(pretrained)['model'] File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 529, in load return _legacy_load(opened_file, map_location, pickle_module, **pickle_load_args) File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 702, in _legacy_load result = unpickler.load() File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 665, in persistent_load deserialized_objects[root_key] = restore_location(obj, location) File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 156, in default_restore_location result = fn(storage, location) File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 132, in _cuda_deserialize device = validate_cuda_device(location) File "/usr/local/lib/python3.7/dist-packages/torch/serialization.py", line 116, in validate_cuda_device raise RuntimeError('Attempting to deserialize object on a CUDA ' RuntimeError: Attempting to deserialize object on a CUDA device but torch.cuda.is_available() is False. If you are running on a CPU-only machine, please use torch.load with map_location=torch.device('cpu') to map your storages to the CPU. エラーが出ましたがメッセージ変わりましたね。さっきのエラーは潰せました。 このエラー典型的な「お前GPUもってないやないか!」というメッセージです。 googlecolabでもGPUは使えますが、使用回数限られているのでCPUで動かすようコードを変えます。 # TCMR_RELEASE/lib/models/tcmr.py # 134行目 # pretrained_dict = torch.load(pretrained)['model'] # 以下のように変更 pretrained_dict = torch.load(pretrained,map_location=torch.device('cpu'))['model'] # TCMR_RELEASE/demo.py # 108行目 # ckpt = torch.load(pretrained_file) ckpt = torch.load(pretrained_file,map_location=torch.device('cpu')) エラー3:次元エラー Traceback (most recent call last): File "TCMR_RELEASE/demo.py", line 378, in <module> main(args) File "TCMR_RELEASE/demo.py", line 113, in main model.load_state_dict(ckpt, strict=False) File "/usr/local/lib/python3.7/dist-packages/torch/nn/modules/module.py", line 830, in load_state_dict self.__class__.__name__, "\n\t".join(error_msgs))) RuntimeError: Error(s) in loading state_dict for TCMR: size mismatch for regressor.smpl.shapedirs: copying a param with shape torch.Size([6890, 3, 10]) from checkpoint, the shape in current model is torch.Size([6890, 3, 300]) エラーが出ました。 学習済みのモデルを読み込もうとしたら次元が違うらしい。 このエラーは実装の内容理解してからじゃないととけないかもしれないー。 この節の続きに追記がなかったら、多分失敗している・・・ 追伸.時間がないので後で書く。20210727 前提知識 論文を読みます。・・・の前に、前提知識を整理します。 Residual Network(ResNet) さて、2015年ごろの研究ではVGGの研究成果により、 CNNの層を従来よりもかなり深くしたことでより高度で細かい特徴の抽出に成功しました。 ResNetでも同じように階層深くしようぜ!!・・・と思ってやってみたが、どうにも上手く行きません そこで考え方を変えましょうよ、と。 一般的なニュートラルネットワークは出力される値に付随するパラメータを学習することで最適な解を得るけれど、 ResNetでは出力される値に入力の値も加えて学習するらしい。(なぜそういう解決法の発想に至ったかは知らない) 上図で言うところの入力を加えている矢印が「residual connection」。 後述する「residual connection」です。なお、上図のひとかたまりを「residual block」と呼びます。 Resnetを導入したことにより、階層を増やしてもしっかり学習ができ、 かつ高度な細かい特徴も抽出できるようになったので表現力があがりました。 34層の階層(resne340)の実装例を見てみます。 https://github.com/CellEight/PytorchResNet/blob/main/models/ResNet34.py 下記の最初の部分だけ一部適当に抜粋しながら見ます。図の通り、3つのresidual connectionがあるので、 3つの「residual block」がある構成です。 test.py class ResBlock(nn.Module): def __init__(self,*args,res_transform=None): super().__init__() # 畳み込み層、活性化関数などの各処理を一連して行う用セット self.seq = nn.Sequential(*args) self.res_transform = res_transform def forward(self,x): if self.res_transform: x0 = self.res_transform(x) else: x0 = x # 出力される値(通常のネットワークの出力) x = self.seq(x) # 入力の値も加える。(residual connectionにあたる実装) return x + x0 class ResNet34(nn.Module): def __init__(self, n_classes): # 3つのResidualBlockを定義 self.ResidualBlock1 = ResBlock(nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU(), \ nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU()) self.ResidualBlock2 = ResBlock(nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU(), \ nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU()) self.ResidualBlock3 = ResBlock(nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU(), \ nn.Conv2d(64,64,kernel_size=3,stride=1,padding=1,padding_mode='reflect'), \ nn.BatchNorm2d(64), \ nn.ReLU()) def forward(self,x): # Block x = self.ResidualBlock1(x) x = self.ResidualBlock2(x) x = self.ResidualBlock3(x) Global Average Pooling https://qiita.com/mine820/items/1e49bca6d215ce88594a 多すぎるパラメータを減らそうぜ!しかもこれやると、精度も良くなるよ! という話らしい。 例えば、512枚の大量の画像特徴があると、その画像のピクセルの数分だけ重みの調整が必要になるけど、 画像それぞれを平均とって、その平均値に対してのみ重みつけようぜ!という話みたい。 RNN/LSTM/GRU/bidirectional GRUs RNN/LSTM https://qiita.com/akaiteto/items/d5f0d615916877091571 昔記事に書いてた通り。 RNNは連続で時系列のあるデータを扱うのが得意。 前回の出力結果も今回の計算に含めて計算することで、前回の結果も含めた結果を出すことができる。 ただその場合、 「前回って言うけど、どこまでが前回? 「全フレーム参照する気か?時間かかるし、余計なものも使ってしまいそうだけど。」 という問題に対する対応がLSTM。 各フレームごとに重要度を設定して、重要じゃないフレームは無視するし、 重要なら未来永劫反映させるように設定する。 GRU https://www.slideshare.net/gakhov/recurrent-neural-networks-part-1-theory LSTMの変形らしい。利点はLSTMほどメモリ食わないしLSTMよりも高速だとか。 LSTMの$C_t$というのが、各フレームの重要度をもっている関数なんだけど、 GRUではこれがなくなっている。各フレームの重要度を担っていた$C_t$が入出力を司る$h_t$に集約されたのがGRU bidirectional GRU/RNN/LSTM pytorch的に言えば、「bidirectional=True」を設定するだけで良いらしい。 https://medium.com/@felixs_76053/bidirectional-gru-for-text-classification-by-relevance-to-sdg-3-indicators-2e5fd99cc341 http://colah.github.io/posts/2015-09-NN-Types-FP/ 今までは過去の出力を加えてたけど、未来の出力も活用することで、 現在の状態がどのようになってるか予想しようぜ!という構造のもの。 へぇ。わからんけど、実際の難しい処理はpytorch先生におまかせしよう。 Weak perspective(弱透視) 要するに正射影。 http://www.thothchildren.com/chapter/5c16710c41f88f26724b1748 ピンホールカメラのパラメータとしては、おなじみ内部パラメータ、外部パラメータなどがある。 後の文章に出てくる弱透視カメラのパラメータは・・・なんだろう? 出力として弱透視カメラのパラメータが得られるらしいけど、ピンホールカメラと同じようなもの? と思いながら論文を読み進むると、スケールと移動の値が出力されるらしいので、 外部パラメータと同じようなものが出力されるらしい。 論文読む イントロ これまで多くの研究により、単一の画像から3Dモデルを推定することには成功しました。 その研究の多くは、SMPLの各関節の動きの情報をパラメータとして与えて回帰(予測)することで 動きの推定を行います。 さて、本手法のテーマは「動画」からの人間骨格の推定です。 単一画像を想定した従来方法で動画の動きを推定しようとすると、フレーム単体で推定を行うため、 全体の挙動として動きががなめらかじゃなかったり、一連の動きの一貫性が失われて正確な推定ができません。 これに対して動画に拡張するための手法として下記のような手法が提案されています。 1.全ての入力フレームから骨格に関わる(staticな)特徴を取得。 2.全てのフレームの特徴を時間的な特徴にエンコードするエンコーダーに渡す。 3.エンコードされた時間的な特徴から各入力フレームの骨格を推定する。 (Learning 3D Human Dynamics from Video https://github.com/akanazawa/human_dynamics) (VIBE :https://github.com/mkocabas/VIBE) しかしこれらの手法にも問題があります。検出したポースと時間が一致しない問題です。 ポーズと時間が一致しない?…まぁ要するに、それでもやっぱりスムーズにポーズが動かないと。 https://youtu.be/WB3nTnSQDII?t=53 ↑従来方法(VIBE)と本手法を比較すると顕著ですが、従来方法は動きがどこかスムーズじゃありません。 原因は上記手順の1にある(staticな)特徴と、3の時間的な特徴が一致していないことにあるらしいです 本稿では、その問題の解決を行います。 イントロ(追記) なぜこのような不一致がおきるのか?曰く、原因は2つあるらしい。 1つ目は、時間的な特徴を計算する際、対象となるフレームの(staticな)特徴に極端に強く依存してしまう点。 直接的な原因は、骨格に関わる(staticな)特徴と上述2番の時間的な特徴、これらをを 結びつける「residual connection」(前提知識参照)にあります。 ResNetをそのまま使うと、時間的な特徴の学習を妨げてしまうらしく、 時間的な特徴の推定の精度がよくないらしい。 時間的な特徴の情報は骨格推定を行う上で重要な情報のひとつなので、 この推定が上手く活用しないと骨格の推定の精度はよくならない。 本稿ではしっかりそのあたりも対策してるのが強み。 2つ目は、時間的な特徴をエンコードするエンコーダーの精度の問題。 まず、現在のフレーム、過去のフレーム、未来のフレームを考えます。 従来方法だと現在のフレームにばかり注目するあまり、過去未来のフレームを 含めた学習ができていないことが問題です。本稿ではしっかりそのあたりも対策してるのが強み。 もう少し読む 動画を読み込んでフレームごとの画像$I_1 \cdots I_T$を取得します。 (staticな)特徴を取得 これについては従来手法におまかせしましょう。 (https://github.com/nkolot/SPIN) 従来研究でトレーニングされているものを使って、単一の画像から(staticな)特徴を取得します。 これに対して、「global average pooling(前提知識参照)」を行うことで更に変換し、$f_1 \cdots f_T \in R^{2048}$を取得します。 (ぶっちゃけ私がやりたいことはこのOSSだけでいい) 時間的な特徴を取得 GRUによる現在過去未来の計算 まず考え方として、フレームを現在、過去、未来と分けて考えます。 現在のフレームは、T個のフレームのうちの$T/2$番目のものを現在のフレームとして定義します。 VIBE "https://github.com/mkocabas/VIBE" では、 双方向ゲート付き回帰ユニット(bi-directional gated recurrent unit ) というものを使って、時間特徴にエンコードします。(前提知識参照) 双方向ゲート付き回帰ユニットとは、要するに 未来方向・過去方向計算された2つの方向のGRUを利用して、対象となる現在の時間的な特徴を計算する方法です。 本稿でも同様に、bidirectionalGRUで計算します。ただし全て同じでは有りません。 本稿の構図としては、$T/2$番目の「現在」と呼ばれる$f_{T/2}$の時間的な特徴を計算すべく、 $f_1 \cdots f_T \in R^{2048}$の過去から未来のすべての入力フレ―ムを使って推定します。 すなわち、$f_{T/2}$からみて過去の$1 \cdots (\frac{T}{2}-1)$をGRUで計算した集合$g_{past}$、 $f_{T/2}$からみて未来の$\frac{T}{2}+1 \cdots T$をGRUで計算した集合$g_{future}$より、 未来と過去の双方向から計算するbidirectionalGRUで、現在の状態を推定します。 VIBEと違う点としては、VIBEでは下図の矢印のようにResNetの「residual connection」を採用しているかいないかの点です。 「residual connection」は、上述の通り滑らかさが欠如する問題の原因となっているので、本稿ではその点を改善しています。 (下図はVIBEの論文。矢印のところが「residual connection」) 現在過去未来のGRU計算結果の統合 さて、過去、現在、未来のGRUの結果が格納された$g_{past}$、$g_{future}$、$g_{all}$を1つに統合します。 まず、活性化関数(Relu)を使って$g_{past}^{\prime}$、$g_{future}^{\prime}$、$g_{all}^{\prime}$を計算します。 そこからさらに、Attentionレイヤーでtanhとソフトマックス関数で加工し、「attention values」の$a_{past}$、$a_{future}$、$a_{all}$を 現在過去未来ごとに出力します。(詳細省略) 「attention values」は、現在・過去・未来のそれぞれの出力結果にどれくらいの重みをつけるか、 どれくらい反映させるかを決定します。 これらを統合したものが最終的な出力$g_{int}$です。 トレーニング トレーニングの段階では、 $g_{past}$、$g_{future}$、$g_{all}$をregressorに渡して、$\Theta$という出力を得ます。 $\Theta$は各パラメータの和集合で構成されており、SMPLを動かすための角度の情報と、 弱透視カメラのパラメータ(スケール、平行移動)[weak-perspective camera parameter]、 アイデンティティパラメータ(なにそれ)の出力結果が、過去未来現在ごとに格納されます。 損失関数 損失は現在過去未来の最終的な出力と、正解の値とをL2損失で計算します。 より詳細には、3次元の関節の座標、2次元の関節の座標、そしてSMPLパラメータから計算します。 2次元の関節の座標については、3次元の関節の座標より、取得した弱透視カメラモデルのパラメータで計算します。 実際の実装 以下箇条書き。 ・VIBEに従いフレーム間隔$T$は16にセット。 ・ビデオのFPSは26-30 ・単一画像からの姿勢推定はhttps://github.com/nkolot/SPINを使用。モデルも多分同じ。 ・最適化関数はAdamでミニバッチサイズは32。 ・(staticな)特徴を取得では、Resnetでトリミングされた画像から計算することで時間とメモリを節約している。 ・すべての回転情報は、Zhou etalの6D回転表現で予測し、最終的にはSMPL向けにaxis-angle 表現の回転表現に変換します。 ソースを見る ソース見ながら内容を見てみます。完全に私のメモ代わりの書き置きです。 あと、デモソース動かしたときにエラーがでたので原因をダメ元でしらべたい…。 人物のトラッキング https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L82 下記OSSで動画から複数人のトラッキングを行います。YOLOV3 & MaskRCNNによる人間の検出 https://github.com/mkocabas/multi-person-tracker TCMRモデル準備1 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L98 TCMR(この記事の技術)のモデルを呼び出します。 TCMRの内部には、「時間的な特徴」と「Regressor」の2つのネットワークがあります。 TemporalEncoderネットワーク:時間的な特徴 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/lib/models/tcmr.py#L88 $f_1 \cdots f_T \in R^{2048}$のT枚の画像の特徴情報をわたして、 現在過去未来ごとにGRUの計算結果$g_{all},g_{past},g_{future}$を取得します。 そしてここからは下記画像の部分。 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/lib/models/tcmr.py#L93 まず$g_{all},g_{past},g_{future}$をReluで活性化させつつ、次元が2048になるように変換し、 $g_{past}^{\prime}$、$g_{future}^{\prime}$、$g_{all}^{\prime}$を計算します。 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/lib/models/tcmr.py#L95 そして3つの情報を2048×256に統合し、 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/lib/models/tcmr.py#L11 attentionレイヤーで「attention values」の3つを出力。 そのまま、図と同じように$g_{past}^{\prime}$、$g_{future}^{\prime}$、$g_{all}^{\prime}$とtorch.mulで乗算。 Regressorネットワーク:SMPLのパラメータ「等」を予測する 個々の実装の詳細は論文のどこに書いてるんだぁ・・?別記事でかこう TCMRモデル準備2 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L106 そして事前に学習した重みを読み込ませます。 ここには「attention values」が入ってる・・・はず? あれ?ちがうかも? SPINモデル準備 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L124 ここまでの説明で言うところの、「(staticな)特徴」を出力するネットワークを準備する。 下記の既存の研究を呼び出している。 https://github.com/nkolot/SPIN 人ごとに全フレームの骨格を取得 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L164 全フレーム読み込んで人単位で骨格を取得。 最終的な出力の初期化。 https://github.com/hongsukchoi/TCMR_RELEASE/blob/8078b3c39c22cae39eb19c0e1eb70e09c60ecea7/demo.py#L180 ここで初期化されている値がpredict(予測)される出力値。regressorから帰ってくる$\Theta$が入ってる。 SMPLのパラメータ${\theta , \beta}$、そして、弱透視カメラのパラメータ${s,t}$(スケール、移動量)、 それからSMPLの3次元上の関節座標と、それを二次元平面に投射した2次元上の関節座標が入ってきます。 最終的な出力の初期化。 TCMRモデルに骨格の特徴を与えて、本稿のアルゴリズムに従い 過去未来現在のGRUを計算することで時間的な特徴を取得し、$g_{int}$を取得します。 まとめ いやー。デモ動きませんでしたー笑 ソースも軽くしかおってないので、次回もうちょっとおいます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Pythonで地図描画】Basemapで実装されたコードをCartopyに書き換える

はじめに 可視化の際、地図を描画して見た目を良くするために、すでに開発が終了したBasemapを使い続けています。流石にそろそろ、後継のCartopyに乗り換えたいということで、実装してみました。本記事は、元のBasemapを用いたコードをどのように書き換えたのかを順に紹介します。本編に入る前に、Cartopyをインストールしておきます。(各自の環境に合わせてインストールして下さい。) conda install -c conda-forge cartopy 最終的に生成される画像はこのような感じです。(緯度・経度線は3度ごとに引いています。) 元のBasemapを利用したコード 以下に元のコードを示します。predictには64x64の地域(メッシュ)毎の値(降水量など)が入ります。 Basemap import numpy as np import matplotlib.pylab as plt from mpl_toolkits.basemap import Basemap def draw_map(predict, lonRange=np.array([初期値,初期値]), latRange=np.array([初期値,初期値]), cmap="seismic", cbar_label="intensity", lonNum=64, latNum=64, norm=[0.0,1.0]): # 地域の描画 m = Basemap(projection='merc', resolution='h', llcrnrlon=lonRange[0], llcrnrlat=latRange[0], urcrnrlon=lonRange[1], urcrnrlat=latRange[1]) # 海岸線、陸地線などの色設定 m.drawcoastlines(color='gray') m.drawcountries(color='gray') m.drawmapboundary(fill_color='#eeeeee') # 5度ごとに緯度線を描く m.drawparallels(np.arange(latRange[0], latRange[1], 10), labels = [1, 0, 0, 0], fontsize=10, color='white') # 5度ごとに経度線を描く m.drawmeridians(np.arange(lonRange[0], lonRange[1], 10), labels = [0, 0, 0, 1], fontsize=10, color='white') # 座標の描画 X = np.linspace(lonRange[0], lonRange[1], lonNum) Y = np.linspace(latRange[1], latRange[0], latNum) lon, lat = np.meshgrid(X, Y) if len(predict): m.pcolormesh(lon, lat, predict, latlon=True, cmap=cmap, norm=norm) cbar = plt.colorbar() cbar.set_label(cbar_label) plt.show() 順に書き換える 地域の描画 まずは、地域の描画から始めます。Basemapでは以下のように設定していました。 Basemap # 地域の描画 m = Basemap(projection='merc', resolution='h', llcrnrlon=lonRange[0], llcrnrlat=latRange[0], urcrnrlon=lonRange[1], urcrnrlat=latRange[1]) 上記のコードは、左右上下の終端値を設定していました。 今回は、インタフェースを用いて下記のように書き換えました。 Cartopy fig = plt.figure() proj = ccrs.PlateCarree() # 正距円筒図法 ax = fig.add_subplot(1,1,1,projection=proj) # 地域の描画 ax.set_extent((lonRange[0], lonRange[1], latRange[0], latRange[1]), proj) 海岸線の描画 元々のBasemapを用いたコードは海岸線の他に陸地線なども描画していましたが、特にしないため、今回は省略しました。 Basemap # 海岸線、陸地線などの色設定 m.drawcoastlines(color='gray') # <-今回は海岸線のみ描画する m.drawcountries(color='gray') m.drawmapboundary(fill_color='#eeeeee') Cartopy # 海岸線を描画 ax.coastlines() 緯度・経度線を描く ここは、上手い実装方法が無いかな〜と模索中です。。。 Basemap # 5度ごとに緯度線を描く m.drawparallels(np.arange(latRange[0], latRange[1], 10), labels = [1, 0, 0, 0], fontsize=10, color='white') # 5度ごとに経度線を描く m.drawmeridians(np.arange(lonRange[0], lonRange[1], 10), labels = [0, 0, 0, 1], fontsize=10, color='white') Cartopy # 5度ごとに緯度・経度線を描く gl=ax.gridlines(crs=proj, draw_labels=True, linewidth=1, alpha=0.8) gl.xlocator = tck.FixedLocator(np.arange(lonRange[0], lonRange[1], 5)) # 5度ごとに経度線を描く gl.ylocator = tck.FixedLocator(np.arange(latRange[0], latRange[1], 5)) # 5度ごとに緯度線を描く gl.top_labels = False # 上部のラベルを非表示 gl.right_labels = False # 右側のラベルを非表示 うーん、Basemapの方が見やすい。 座標の描画 座標の描画については、ほとんど変更はありません。 一部、pcolormeshに差異があります。 Basemap # 座標の描画 X = np.linspace(lonRange[0], lonRange[1], lonNum) Y = np.linspace(latRange[1], latRange[0], latNum) lon, lat = np.meshgrid(X, Y) if len(predict): m.pcolormesh(lon, lat, predict, latlon=True, cmap=cmap, norm=norm) Cartopy # 座標の描画 X = np.linspace(lonRange[0], lonRange[1], lonNum) Y = np.linspace(latRange[1], latRange[0], latNum) lon, lat = np.meshgrid(X, Y) if len(predict): plt.pcolormesh(lon, lat, predict, cmap=cmap, vmin=norm[0], vmax=norm[1]) 。。。上手くNormalizationが設定できなかったので、とりあえず上記のようにしています。 あと、ax.pcolormeshにすると、この後のカラーバーを設定できませんでした。 最終的なコード 最終的には下記のようなコードになりました。思いの外、すぐに書き換えられました。いくつか要検討な部分がありますが、とりあえずこれにて作業終了ということにします。このように実装した方が良いなどのアドバイスがありましたら、コメント頂けると幸いです! Cartopy import numpy as np import matplotlib.pyplot as plt import matplotlib.ticker as tck import cartopy.crs as ccrs def draw_map(predict, lonRange=np.array([初期値,初期値]), latRange=np.array([初期値,初期値]), cmap="seismic", cbar_label="intensity", lonNum=64, latNum=64, norm=[0.0,1.0]): fig = plt.figure() proj = ccrs.PlateCarree() # 正距円筒図法 ax = fig.add_subplot(1,1,1,projection=proj) # 地域の描画 ax.set_extent((lonRange[0], lonRange[1], latRange[0], latRange[1]), proj) # 海岸線を描画 ax.coastlines() # 5度ごとに緯度・経度線を描く gl=ax.gridlines(crs=proj, draw_labels=True, linewidth=1, alpha=0.8) gl.xlocator = tck.FixedLocator(np.arange(lonRange[0], lonRange[1], 5)) # 5度ごとに経度線を描く gl.ylocator = tck.FixedLocator(np.arange(latRange[0], latRange[1], 5)) # 5度ごとに緯度線を描く gl.top_labels = False # 上部のラベルを非表示 gl.right_labels = False # 右側のラベルを非表示 # 座標の描画 X = np.linspace(lonRange[0], lonRange[1], lonNum) Y = np.linspace(latRange[1], latRange[0], latNum) lon, lat = np.meshgrid(X, Y) if len(predict): plt.pcolormesh(lon, lat, predict, cmap=cmap, vmin=norm[0], vmax=norm[1]) cbar = plt.colorbar() cbar.set_label(cbar_label) plt.show() 参考記事 Basemapの実装時に参考にしたブログ:pythonでの地図表示 Cartopyインストール時に参考にしたサイト:Cartopyの公式ページ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tempfile

ファイルの入出力のテストに使います。 import tempfile #withを閉じるとファイルは削除されます with tempfile.TemporaryFile(mode='w+') as t: t.write('hello') t.seek(0) print(t.read()) #withを閉じた後もファイルは残ります with tempfile.NamedTemporaryFile(delete=False, mode='w+') as t: print(t.name) t.write('test\n') t.seek(0) print(t.read()) #withを閉じるとディレクトリは削除されます with tempfile.TemporaryDirectory() as td: print(td) #ディレクトリは削除されません temp_dir = tempfile.mkdtemp() print(temp_dir) hello /var/folders/0j/3nls1w512cdc4h2fr4543lw40000gn/T/tmplb079t65 test /var/folders/0j/3nls1w512cdc4h2fr4543lw40000gn/T/tmp_tnjm74h /var/folders/0j/3nls1w512cdc4h2fr4543lw40000gn/T/tmp1b75osd0
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django:Bootstrapを使って見た目を良くする(基礎の基礎4)

1 はじめに 今回は、ウェブサイトの見た目を整えていきたいと思います。Htmlのルールを覚えて綺麗につくっていくためには、相当な経験と慣れ、そして何よりもコーディングの時間が必要になりますが、Bootstrapというツールを使うと、簡単に必要なパーツを組み合わせてプロっぽい見た目のウェブサイトにすることができます。3回目までで作ったデータベースに保存した内容の表示はなんとも味気ない感じでしたが、コピペとちょっとしたhtmlの加工で見違えるほどの見栄えにできることを感じてもらえればと思います。(以下の内容の利用は自己責任でお願いします。) 2 Bootstrapのスターターテンプレートを使う 以下のbootstrapのウェブサイトにスターターテンプレートがありますので、これを使っていきます。 startertemplate <!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <title>Hello, world!</title> </head> <body> <h1>Hello, world!</h1> <!-- Optional JavaScript; choose one of the two! --> <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <!-- Option 2: Separate Popper and Bootstrap JS --> <!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> --> </body> </html> このスターターテンプレートをベースにしますが、少し加工をしておく必要があります。すなわち、テンプレートのどの部分に変更箇所を入れるか、あらかじめ指定しておく必要があります。ベースのファイル(bootstrap.html)に、{% block header %}{% endblock %} {% block content %}{% endblock %} をタグの下に入れておきます。 bootstrap.html <!doctype html> <html lang="en"> <head> <!-- Required meta tags --> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <!-- Bootstrap CSS --> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> <title>Hello, world!</title> </head> <body> {% block header %} {% endblock %} {% block content %} {% endblock %} <!-- Optional JavaScript; choose one of the two! --> <!-- Option 1: Bootstrap Bundle with Popper --> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM" crossorigin="anonymous"></script> <!-- Option 2: Separate Popper and Bootstrap JS --> <!-- <script src="https://cdn.jsdelivr.net/npm/@popperjs/core@2.9.2/dist/umd/popper.min.js" integrity="sha384-IQsoLXl5PILFhosVNubq5LC7Qb9DXgDA9i+tQ8Zj3iwWAwPtgFTxbJ8NT4GN1R8p" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.min.js" integrity="sha384-cVKIPhGWiC2Al4u+LWgxfKTRIcfu0JTxR+EQDz/bgldoEyl4H0zUF0QKbrJ0EcQF" crossorigin="anonymous"></script> --> </body> </html> 3.ウェブサイトに表示させるフォームの編集 次に、見やすいサイトにするために、Bootstrapのテンプレートの中から、内容に適したフォーマットを探します。ここでは、以下の「カード」の中から「クオート」というフォーマットが良さそうなので、これを使ってみることにします。 Quote <div class="card"> <div class="card-header"> Quote </div> <div class="card-body"> <blockquote class="blockquote mb-0"> <p>A well-known quote, contained in a blockquote element.</p> <footer class="blockquote-footer">Someone famous in <cite title="Source Title">Source Title</cite></footer> </blockquote> </div> </div> ただし、このままはりつけてしまうと、コンテンツの余白がないものになってしまうので、余白を入れることにしたいと思います。Bootstrapでは、パディング・マージンをつけてコンテンツを見やすくする機能がついています。マージンは、ウェブサイトの上下端あるいは左右端とコンテンツの余白であり、パディングとは、コンテンツ間をどのぐらい詰めるか、の設定になります。 イメージとしては、以下のようになります。 bootstrapを適用した場合、以下のdiv classでコンテンツ毎に指定が可能になります。ウェブサイトがみやすくなるようにうまく活用しましょう。 <div class="mt-5">上にマージン</div> <div class="pb-5">下にパディング</div> <div class="ml-5">左にマージン</div> <div class="pr-5">ク右にパディング</div> <div class="mx-5">左右にマージン</div> <div class="py-5">上下にパディング</div> それでは、上記を参考にして、これまで味気ない表示しかできていなかったmodeltest.htmlファイルを書き換えてみます。まず、{% extends '---.html' %}で、スターターテンプレートをベースにして先ほど編集したファイル(bootstrap.html)を呼び出した上で、BootstrapのQuoteのサンプルをコピーし、コンテンツのところに必要な書き換えをします。また、左右の余白とコンテンツ間の間隔を開けるため、div class ="mx-5"と、div class = "py-2" の指定をしておきます。コードは以下の通りです。 modeltest.html {% extends 'bootstrap.html' %} {% block header %} {% endblock %} {% block content %} {% for item in object_list %} <div class="mx-5"> <div class="py-2"> <div class="card"> <div class="card-header"> <li>{{item}}:{{item.headline}}</li> </div> <div class="card-body"> <blockquote class="blockquote mb-0"> <p>{{item.content}}</p> <footer class="blockquote-footer">{{item.pub_date}} by <cite title="Source Title">{{item.reporter}} </cite></footer> </blockquote> </div> </div> </div> </div> {% endfor %} {% endblock %} 4. 仕上がり それでは改めてrunserverをしてウェブサイトを表示させてみましょう。以前は単にデータベースの内容を味気なく表示させているだけのサイトでしたが、以下のようにカードの中に見やすい形でデータが表示されるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む