- 投稿日:2019-09-27T23:28:56+09:00
型に厳密なdataclass
シンプルな
dataclasses.dataclass
はアトリビュートの型が定義と異なってもインスタンス化が可能。つまり以下のような例が実行可能。@dataclasses.dataclass class NormalDataclass: foo: int NormalDataclass(foo=1.1) # ok厳密に型をチェックしたい場合は
__post_init__
を定義すれば良い。__init__
の後に呼ばれるのでここで型チェックをしてみよう。@dataclasses.dataclass class StrictDataclass: def __post_init__(self): for key, val in self.__dataclass_fields__.items(): member = getattr(self, key) assert isinstance(member, val.type), f"Invalid Type of member: {type(member)} != {val.type}" foo: int StrictDataclass(foo=1) # ok StrictDataclass(foo=1.2) # AsertionError: Invalid Type of member: <class 'float'> != <class 'int'>不正な値が入ってきた場合にいち早くassertionを投げてくれるのでデバックがしやすくなるかもしれない。
- 投稿日:2019-09-27T23:12:40+09:00
【顔認識入門】haar cascadesで遊んでみた♪
今日は、今更感があるが「ウワンさんこんにちわ」と言ってほしくて、まず顔認識で遊んでみた。
Opencvのhaar cascadesを使った顔認識やってみた。
コードは、最初は参考②のものが見やすいので、まんま利用させていただいた。
解説は参考①が詳しい。
【参考】
①【入門者向け解説】openCV顔検出の仕組と実践(detectMultiScale)
②python+OpenCVで顔認識をやってみるやったこと
・とりあえず動かしてみる
・動画に適用する・とりあえず動かしてみる
上記の参考コードをとりあえず、動かしてみました。
その結果、以下のような画像が得られます。
元画像 生成画像 ➡ ・動画に適用する
Opencvには以下のような動画適用がありますが、ちょっと読みにくいコードなので、上記の簡単な静止画アプリを改変することとする。
opencv/samples/python/facedetect.py
その結果、以下のようなコードを作成できる。
face_recognition/face_recognition_doga.pyコード解説
利用するLibはcv2とtimeだけです。
import cv2 from time import sleep以下は顔認識に利用するxmlです。そして、動画のためのfourccを定義します。
cascade_path = "./models/haarcascade_frontalface_default.xml" def cv_fourcc(c1, c2, c3, c4): return (ord(c1) & 255) + ((ord(c2) & 255) << 8) + \ ((ord(c3) & 255) << 16) + ((ord(c4) & 255) << 24)cascade分類器を定義します。
def main(): #カスケード分類器の特徴量を取得する cascade = cv2.CascadeClassifier(cascade_path) color = (255, 255, 255) #白動画のファイル名等を定義する。
OUT_FILE_NAME = "./outputs/face_recognition.avi" FRAME_RATE=1 w=224 #1280 h=224 #960 out = cv2.VideoWriter(OUT_FILE_NAME, \ cv_fourcc('M', 'P', '4', 'V'), \ FRAME_RATE, \ (w, h), \ True)カメラ入力を定義
cap = cv2.VideoCapture(1) is_video = 'False' s=0.1動画は1フレームずつ撮影保存しています。
fpsを計測して、動画に貼り付けます。while True: timer = cv2.getTickCount() ret, frame = cap.read() sleep(s) fps = cv2.getTickFrequency() / (cv2.getTickCount() - timer); # Display FPS on frame cv2.putText(frame, "FPS : " + str(int(1000*fps)), (100,50), cv2.FONT_HERSHEY_SIMPLEX, 0.75, (50,170,50), 2);取得した画像をグレースケールに変換して、それに基づいて顔検出を行います。
#グレースケール変換 image_gray = cv2.cvtColor(frame, cv2.COLOR_BGR2GRAY) #image_gray = cv2.equalizeHist(image_gray) facerect = cascade.detectMultiScale(image_gray, scaleFactor=1.1, minNeighbors=2, minSize=(30, 30))検出した顔に合わせて矩形を作成し、frameに貼り付けます。
if len(facerect) > 0: #検出した顔を囲む矩形の作成 for rect in facerect: cv2.rectangle(frame, tuple(rect[0:2]),tuple(rect[0:2]+rect[2:4]), color, thickness=2)frameを表示します。
cv2.imshow('test',frame)動画に一枚ずつ保存していきます。
key = cv2.waitKey(0)&0xff if is_video=="True": img_dst = cv2.resize(frame, (int(224), 224)) out.write(img_dst) print(is_video)以下のキー入力で動画のfpsを変更できます。
あるいは、qで終了します。if key == ord('q'): #cv2.destroyAllWindows() break elif key == ord('p'): s=0.5 is_video = "True" elif key == ord('s'): s=0.1 is_video = "False" if __name__ == '__main__': main()結果
上記のコードで一応顔認識しつつ動画が動きました。
ちょうと映っていたTVの画面の中の顔認識をしてくれました。
face recognition
※画像をクリックするとYouTube動画につながりますまとめ
・haar cascadesを動かしてみた
・動画像に対して顔認識してみた・顔認識しつつ人物認識を実施したい
- 投稿日:2019-09-27T23:12:29+09:00
二分木の直列化(シリアライズ)と復元
概要
直列化(シリアライズ)とは、オブジェクトをバイト列に変換し、メモリやファイル上に保存しやすい状態にすることです。本記事では二分木のデータ構造を文字列にシリアライズし、さらにその文字列から二分木を復元する手法を紹介します。
方針
二分木の構造を文字列に変換するにはいくつかの方法が考えられますが、ここでは深さ優先探索(DFS)の先行順巡回(pre-order)の順に各ノードの値を羅列した文字列を生成することにします。
例
下記のような二分木をDFSのpre-orderで走査し、
1 2 null null 3 4 null null 5 null null
のような文字列を生成します。のちの復元(デシリアライズ)を容易に行うため、子ノードがnullの場合も明示的に文字列に含めている点に注意してください。1 / \ 2 3 / \ 4 5実装例
def preorder(self, node, arr): if node is None: # ノードが空の場合もnullとして記録しておく arr.append('null') elif node is not None: arr.append(node.val) arr = self.preorder(node.left, arr) arr = self.preorder(node.right, arr) return arr def serialize(self, root): # 先行順巡回でリストを生成 arr = self.preorder(root, []) # スペース区切りでリストを文字列化する return ' '.join(str(i) for i in arr) def deserialize(self, text): # 文字列からリストを生成 arr = text.split() return self.rdeserialize(arr) # pre-orderの配列から二分木を生成するメソッド def rdeserialize(self, arr): if len(arr) == 0: return None if arr[0] == 'null': del arr[0] return None # pre-orderの先頭をrootとして取り出し、部分木を再帰的に生成する root = TreeNode(int(arr[0])) del arr[0] root.left = self.rdeserialize(arr) root.right = self.rdeserialize(arr) return root
- 投稿日:2019-09-27T23:05:42+09:00
データフレーム同士の列の中を比較して、一方にしかないない値を取得する
動作環境
python3
jupyter lab関連リンクです。あなたは、「データフレーム同士のカラム名の差分、比較方法」について探していますか?↓
データフレーム同士のカラム名の差分を取得・比較する方法データフレームの任意の1列に対して、ソート&ユニークな要素を取得する
まずは、悪い例。飛ばして読んでもいいです。(失敗は成功の母)
import numpy as np import pandas as pd # サンプルとなるデータフレームを作成 df = pd.DataFrame({ 'A' : [1,2,3,4,5], 'B' : ["aaaa","cccc","dddd","bbbb","cccc"]}) #1列だけを取得したあと、ソートに失敗した悪い例 [in] print(df['B'].sort_index()) [out] 0 aaaa 1 cccc 2 dddd 3 bbbb ←ソート出来ていない!! 4 cccc Name: B, dtype: object #↓このままソートできていないままunique()してしまうザコーダ [in] print(df['B'].sort_index().unique()) [out] ['aaaa' 'cccc' 'dddd' 'bbbb'] ←だから、ソート出来てないってば!! [in] print("ファッ!?ユニークは出来てるけど、ソートできてないやんけー") [out] ファッ!?ユニークは出来てるけど、ソートできてないやんけー以下、成功例。(追記、全体的に修正しました。コメントありがとうございます。)
#↓ソートの成功例 [in] print(df['B'].sort_values()) [out] 0 aaaa 3 bbbb ←ソートできてる 1 cccc 4 cccc 2 dddd Name: B, dtype: object #↓sort_valuesすると、pandas.core.series.Series型になってしまう [in] print(type(df['B'].sort_values())) [out] <class 'pandas.core.series.Series'> #↓続いて、ソートした上で、ユニークなものだけにする [in] print(df['B'].sort_values().unique()) [out] ['aaaa' 'bbbb' 'cccc' 'dddd'] #↓unique()すると、numpy.ndarray型になってしまう [in] print(type(df['B'].sort_values().unique())) [out] <class 'numpy.ndarray'>エラーがでるようなときは、type()で型を確認してコーディングしていきます。
【ここからが本題】 データフレーム同士の列の中を比較して、一方にしか存在しない値を確認する
上のやり方を基礎にして、本題を解決します。自分が表示したい内容は、sort_index() と unique()を使って以下のようにして出来ました。
# 比較するためのデータフレームを2つ作る train = pd.DataFrame({ 'A' : [1,2,3,4,5], 'B' : ["aaaa","cccc","dddd","bbbb","fffff"]}) test = pd.DataFrame({ 'A' : [1,2,3], 'B' : ["aaaa","dddd","cccc"]}) #↓ソートの正解例 [in] print(train['B'].sort_values().unique()) [out] ['aaaa' 'bbbb' 'cccc' 'dddd' 'fffff'] #これができると、↓このように比較できる [in] print(train['B'].sort_values().unique()) print(test['B'].sort_values().unique()) [out] ['aaaa' 'bbbb' 'cccc' 'dddd' 'fffff'] ['aaaa' 'cccc' 'dddd']こうすれば、testデータは、trainデータと比べて、ddddとffffの値が存在しないことがわかりました。データが1000行あったりした場合に、ユニークな値の確認に使えます。
(なんか、もっと良い確認方法ありそうですねー、でも今はこれでいいや)あとがき
データの中身を確認する際などは、これで見やすくなると思います。
もっと見やすいやり方あったら、コメントください。
- 投稿日:2019-09-27T23:01:09+09:00
matplotlib.animationでplotの点を動かすアニメーション
時間発展の方程式を数値計算するときに、物体の運動と物理量の時間変化を対応させてアニメーションで表示したいときがあります。例えば、単振り子のシュミレーションでは、振り子が運動している様子とその時の位相を並べてアニメーションで表示できれば、物理現象が視覚的に理解できます。
そこで、今回はmatplotlibのanimationを使ってそれを実現させるコードを紹介します。
matplotlib.animationの使い方
まずは、matplotlib.animationでsinカーブ上の点を動かすアニメーションの作り方を紹介します。
animate_sin.pyimport numpy as np import matplotlib.pyplot as plt from matplotlib import animation #グラフの体裁を整える plt.rcParams['figure.figsize'] = (6,5) plt.rcParams['font.family'] = 'Times New Roman' plt.rcParams['font.size'] = 16 #枠線の太さを指定する関数 def axes_set_linewidth(axes, t=1, b=1, r=1, l=1): axes.spines['top'].set_linewidth(t) axes.spines['bottom'].set_linewidth(b) axes.spines['right'].set_linewidth(r) axes.spines['left'].set_linewidth(l) #変数設定 N = 100 x = np.linspace(-2 * np.pi, 2 * np.pi, N) y = np.sin(x) #アニメーション作成 fig, ax = plt.subplots() ax.plot(x, y, color = 'blue', label = 'sin x') #sinカーブをplot axes_set_linewidth(ax, t=0, r=0, b=2, l=2) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_ylim(-1.2, 1.4) ax.legend(frameon = False) plt.subplots_adjust(top=0.9, bottom=0.2, right=0.9, left=0.2) ims = [] #ここに1ステップごとのグラフを格納 for i in range(N): p = ax.plot(x[i], y[i], color = 'darkblue', marker = 'o', markersize = 10) ims.append(p) ani = animation.ArtistAnimation(fig, ims, interval=100) #ArtistAnimationでアニメーションを作成する。 ani.save('animate.gif', writer='imagemagick', dpi = 300) #gifで保存単振り子の運動と位相の同時アニメーション
半径Lの円弧上を質量mの質点が往復運動する振り子について考える。このときの質点の運動方程式は、
$$
mL \frac{d^2 \theta}{dt^2} = -mg \sin{\theta}
$$となる。$\theta$が十分小さいとして$\sin\theta\simeq\theta$の近似を用いてこの微分方程式を解くと、一般解は次のようになる。
$$
\theta (t) = \theta_{max} \cos(\omega t + \alpha) \; ,\quad \omega= \sqrt{\frac{g}{L}}
$$
ここで、$\theta_{max}$は振幅、$\omega$は角振動数、$\alpha$は初期位相である。この解をもとに以下のように、単振り子の簡単な数値計算を行います。
pendulum.py#条件 alpha = 0 g = 9.8 #m/s2 L = 0.1 #m omega = np.sqrt(g/L) theta_max = np.pi / 6 dt = 0.01 t = np.arange(0, 2 + dt, dt) #位相の計算 def theta(ts): return theta_max*np.cos(omega*ts + alpha) #位置の計算 def theta_position(ts): x = L * np.sin(theta(ts)) z = -L * np.cos(theta(ts)) return x, zそれでは以下のコードを実行して、振り子の運動とその時の位相のアニメーションを並べて表示してみます。
pendulum_animate.pyfig, ax = plt.subplots(1,2, figsize = (14,6)) axes_set_linewidth(ax[0], t=0, b=0, r=0, l=0) axes_set_linewidth(ax[1], t=0, b=2, r=0, l=2) ax[0].set_xticks([]) ax[0].set_yticks([]) ax[0].set_xlim(-0.06, 0.06) ax[0].set_ylim(-0.12, 0.02) ax[0].vlines(0, -0.1, 0, linestyle = '--') ax[0].hlines(0, -1, 1) ax[1].plot(t, theta(t)) ax[1].set_xlim(0,2.2) ax[1].set_ylim(-0.6,0.6) ax[1].hlines(0,0,2.2, color='black', linestyle='--') ax[1].set_xlabel('Time [s]') ax[1].set_ylabel(r'$\theta$ [rad]') plt.subplots_adjust(top=0.9, bottom=0.2, right=0.9, left=0.2, wspace = 0.3) ims = [] for i in t: x, y = theta_position(i) p = ax[0].plot([0,x], [0,y], color = 'black') + ax[0].plot(x, y, marker = 'o', color = 'red', markersize = 15)\ + ax[1].plot(i, theta(i), marker='o', color='darkblue', markersize=10) ims.append(p) ani = animation.ArtistAnimation(fig, ims, interval=50, repeat=True) ani.save('pendulum.gif', writer='imagemagick')このようにax[0]とax[1]で連動したアニメーションを表示するときは、
p = ax[0].plot([0,x], [0,y], color = 'black') + ax[0].plot(x, y, marker = 'o', color = 'red', markersize = 15)\ + ax[1].plot(i, theta(i), marker='o', color='darkblue', markersize=10)のようにplotをプラスしていけばよいです。
- 投稿日:2019-09-27T22:15:48+09:00
メモ「Automate the Boring stuff -chapter3 Functions」
def Statements with Parameters
Deduplication makes your programs shorter, easier to read, and easier to update.
import random def getAnswer(answerNumber): if answerNumber == 1: return 'It is certain' elif answerNumber == 2: return 'It is decidedly so' elif answerNumber == 3: return 'Yes' elif answerNumber == 4: return 'Reply hazy try again' elif answerNumber == 5: return 'Ask again later' elif answerNumber == 6: return 'Concentrate and ask again' elif answerNumber == 7: return 'My reply is no' elif answerNumber == 8: return 'Outlook not so good' elif answerNumber == 9: return 'Very doubtful' r = random.randint(1, 9) fortune = getAnswer(r) print(fortune) Note that since you can pass return values as an argument to another function call, you could shorten these three lines: r = random.randint(1, 9) fortune = get answer(r) print(fortune) to this single equivalent line: print(getAnswer(random.randint(1, 9)))The None Value
Keyword Arguments and print()
Local and Global Scope
I learned how local and global scope is different and could imagine how return function can be used.
A variable that exists in a local scope is called a local variable, while a variable that exists in the global scope is called a global variable.
Local Variables Cannot Be Used in the Global Scope
Local Scopes Cannot Use Variables in Other Local Scopes
Global Variables Can Be Read from a Local Scope
Local and Global Variables with the Same Name
The global Statement
Exception Handling
A Short Program: Guess the Number
# This is a guess the number game. import random secretNumber = random.randint(1, 20) print('I am thinking of a number between 1 and 20.') # Ask the player to guess 6 times. for guessesTaken in range(1, 7): print('Take a guess.') guess = int(input()) if guess < secretNumber: print('Your guess is too low.') elif guess > secretNumber: print('Your guess is too high.') else: break # This condition is the correct guess! if guess == secretNumber: print('Good job! You guessed my number in ' + str(guessesTaken) + ' guesses!') else: print('Nope. The number I was thinking of was ' + str(secretNumber))Short Practice: the Collatz Sequence
''' If number is even, then collatz() should print number // 2 and return this value. If number is odd, then collatz() should print and return 3 * number + 1. ''' def collatz(number): if number % 2 == 0: return number // 2 elif number % 2 == 1: return 3 * number + 1 ''' Lets the user type in an integer and that keeps calling collatz() on that number until the function returns the value 1. ''' print('Enter number:') try: info = int(input()) while info != 1: info = collatz(info) print(info) except ValueError: print('You must enter integer')
- 投稿日:2019-09-27T21:56:25+09:00
pandasを使ってCSVファイルを読み込んでみる。
csvファイルを読み込みたい
人間誰しも一度はcsvファイルを読み込みたい時があるので、今回はpythonのモジュールで、データ解析ツールの一つであるpandasを用いてcsvファイル読み込む簡単なプログラムを試作してみました。
動作の概要
ファイル名を入力すると、同じ階層にある対応したデータファイルを読み込んでくれるようにしました。(本当はドラッグアンドドロップでちょちょいとデータ読み込みをしたかったのですがいきなりは難しそうなので……。)
データの形としては、カンマ区切りでn行2列(nは任意)、1行目は各列のカテゴリ名となっていることを想定しており(以下の図を参照のこと)、各カテゴリ名をstringとして、1列目と2列目をそれぞれlist形式で読み込むようにしました。
データの読み込みにはpandasのread_csvを使用しました。こちらはカンマ区切りのcsvファイルを読み込むことができ、さらには1列目をheaderとして認識してくれる便利なものとなっています。
ついでに、存在しないファイル名を入力してしまった場合にエラーメッセージを表示してプログラムを停止するようにもしてみました。このために、osモジュールのpath.exists()を使用しています。こちらは入力したファイルやディレクトリが存在していればTrueを返してくれます。プログラムの実装
さて、実際のプログラムは以下となります。応用を考えてオブジェクト指向のプログラムとしてみましたがいかがでしょうか。
csv_reader.pyimport pandas as pd import sys import os class csv_reader: #オーバーライドを前提としたread()メソッド def read(self,inputs): return class xy_csv_reader(csv_reader): def read(self,inputs): #インプットされた文字列をinputsに格納 self.inputs = inputs #ファイルの存在を確認し、存在しなければエラーメッセージを表示しプログラムを終了。 if not os.path.exists(self.inputs +".csv"): sys.exit('wrong name of csv') #ファイルの存在が確認できたら、pandasでデータの読み込み else: df = pd.read_csv(self.inputs +".csv") #ちゃんと読み込めたかデータを表示する。無くてもよい。 print(df,"\n") #データの要素を格納し、返す xname = df.columns[0] yname = df.columns[1] list_x = df[xname].values list_y = df[yname].values return xname, yname ,list_x, list_y実際に動かしてみよう
せっかく作ったので動かしてみましょう。以下のようなテストプログラムで、先ほどのtest3.csvファイルを読み込んでみます。
test.pyfrom csv_reader import * #ファイル名を入力する csvname = input('please input name of csv file> ') #xy_csv_readerクラスのオブジェクトを生成 csv_reader = xy_csv_reader() #データの読み込みを実行 xname, yname, list_x, list_y = csv_reader.read(csvname) print(xname) print(yname) print(list_x) print(list_y)上記のテストプログラムを実行すると、コンソールに以下のように表示されるので、ここでtest3と入力してみます。
すると結果は以下のように表示されました。pandasで読み込んだ結果の一行目がheaderになっていること、データが正しく読み込まれていることが分かりました。やったね。
ちなみに、存在しないファイル名を入力すると以下のようになりました。こちらも、どうやら正しく動作しているようです。
感想と今後
今回は、主にpandasを使用してcsvファイルを読み込む簡単なプログラムを作製しました。オブジェクト指向も取り入れてみたので、少しはステップアップできたかなと思っています。今後は、データの読み込み方のバリエーションを作ることや、読み込んだデータを使用してデータ分析ができるプログラムに拡張などをしていきたいと思います。
- 投稿日:2019-09-27T21:44:06+09:00
メモ「Automate the Boring stuff -chapter2 Flow control」
Boolean values, comparison operators, and Boolean operators
Operator Meaning == Equal to != Not equal to < Less than > Greater than <= Less than or equal to >= Greater than or equal to Python considers the integer 42 to be different from the string '42'
The == operator (equal to) asks whether two values are the same as each other.
The = operator (assignment) puts the value on the right into the variable on the left.
Binary Boolean Operators
Expression Evaluates to... True and True True True and False False False and True False False and False False The or operator evaluates an expression to True if either of the two Boolean values is True. If both are False, it evaluates to FalseFlow Control Statements > if, else, and elif`
while Loop Statements
The while loop keeps looping while its condition is True
break Statements
continue Statements
for Loops and the range() Function
print('My name is') for i in range(5): print('Jimmy Five Times (' + str(i) + ')') The above program works as same as following one. print('My name is') i = 0 while i < 5: print('Jimmy Five Times (' + str(i) + ')') i = i + 1 --- for i in range(12, 16): print(i) ->12, 13, 14, 15 for i in range(0, 10, 2): print(i) ->0, 2, 4, 6, 8 for i in range(5, -1, -1): print(i) ->5, 4, 3, 2, 1, 0Importing Modules
Ending a Program Early with sys.exit()
- 投稿日:2019-09-27T21:34:39+09:00
リーマン多様体上の最急降下法
リーマン多様体上での最適化について,最も単純な最急降下法についてまとめる.
数空間上,リーマン多様体上の最急降下法のアルゴリズム1
次の最適化問題について,$X$ がそれぞれ数空間 ($X=\mathbb{R}^n$),リーマン多様体 ($X=\mathcal{M}$) であるときの最急降下法を紹介する.
\text{minimize}\ f(x)\quad \text{subject to}\ x\in X数空間上での最急降下法
初期化: $x_0\in \mathbb{R}^n,\ k\leftarrow 0$
1. 停止条件が満たされるなら,$x_k$ を出力
2. 探索方向 $d_k\leftarrow -\nabla f(x_k)$
3. 直線探索によるステップ幅 $t_k\geq0$ を計算
4. $x_{k+1}\leftarrow x_k + t_k d_k$ で更新
5. $k \leftarrow k+1$ として,ステップ 1 へリーマン多様体上での最急降下法
初期化: $x_0\in \mathcal{M},\ k\leftarrow 0$
1. 停止条件が満たされるなら,$x_k$ を出力
2. 探索方向 $d_k\leftarrow -\mathrm{grad}\,f(x_k)\in T_{x_k}\mathcal{M}$
3. 直線探索によるステップ幅 $t_k\geq0$ を計算
4. $x_{k+1}\leftarrow R_{x_k}(t_k d_k)$ で更新
5. $k \leftarrow k+1$ として,ステップ 1 へ数空間上,リーマン多様体上の最急降下法の違い12
一般に多様体 $\mathcal{M}$ はベクトル空間とは限らないため,線形和が計算できる保証がない,そこで,$\nabla f(x)$ の代わりに $\mathrm{grad}\,f(x)$,$x_k + t_k d_k$ の代わりに $R_{x_k}(t_k d_k)$ が用いられている.ここでは,これらの用語を説明する.
接空間 (tangent space)
$x\in\mathcal{M}$ における接空間 $T_x\mathcal{M}$ とは,$\mathcal{M}$ 上の点 $x$ における接ベクトル全体の集合のことである.
接ベクトル (tangent vector)
$x\in\mathcal{M}$ における接ベクトル $\xi_x$ とは,$\mathcal{M}$ 上の関数から $\mathbb{R}$ への写像であり,任意の $\mathcal{M}$ 上の関数 $f:\ \mathcal{M}\to\mathbb{R}$ に対して,$x\in\mathcal{M}$ を通る曲線 $\gamma:\ \mathbb{R}\to\mathcal{M}\ (\gamma(0)=x)$ が存在し,
\xi_x f = \dot{\gamma}(0)f := \left.\frac{\mathrm{d}f(\gamma(t))}{\mathrm{d}t}\right|_{t=0}を満たすものである.
リーマン計量 (Riemannian metric)
リーマン計量とは,$x\in\mathcal{M}$ から 接空間 $T_x{\mathcal{M}}$ 上の内積 $g_x(\cdot,\ \cdot)$ への対応関係のことである.
勾配 (gradient)
$x\in\mathcal{M}$ における関数 $f$ の勾配 $\mathrm{grad}\,(x)$ とは,一意に定まる接空間 $T_x\mathcal{M}$ の元で
g_x(\mathrm{grad}\,f(x),\ \xi) = \mathrm{D}f(x)[\xi],\quad\forall\xi\in T_x\mathcal{M}を満たすものである.ここで,$\mathrm{D}f(x)[\xi]$ は $f$ の点 $x$ における $\xi$ 方向微分である.
レトラクション (retraction)
レトラクション $R$ とは,$x\in\mathcal{M}$ と $\xi\in T_x\mathcal{M}$ に対して $R_x(\xi)\in\mathcal{M}$ を対応させる関数で,次の 2 条件を満たすものである.
- $R_x(0) = x$
- $\left.\frac{\mathrm{d}}{\mathrm{d}t}R_x(t\xi)\right|_{t=0}=\xi$
数値例: レイリー商1
レイリー商
R(x) = \frac{x^T A x}{x^T x}を最小化する問題
\text{minimize}\ R(x)\quad \text{subject to}\ x\in Xを最急降下法を用いて計算するプログラムを $X=\mathbb{R}^n$, $X=S^{n-1}$ の 2 通りに対して実装した.ここで,
S^{n-1}:=\{x\in\mathbb{R}^n\mid \|x\|_2=1\}は $n-1$ 次元球面を表す.
レイリー商 $R(x)$ には,
R(\alpha x) = R(x)という性質があり,最適解に実数倍の自由度がある.そこで変数 $x$ を $S^{n-1}$ 上に制限することで,最適化問題は
\text{minimize}\ x^T A x\quad \text{subject to}\ x \in S^{n-1}となり,最適解の自由度を取り除くことができる.
球上の接ベクトル空間,内積,勾配,レトラクション
- 接ベクトル空間
T_x S^{n-1} = \{\xi \in \mathbb{R}^n\mid x^T \xi = 0\}
- 内積
g_x(\xi, \eta) = \xi^T \eta
- 勾配
\text{grad}\,f(x) = \nabla f(x) - x \nabla f(x)^T x = (I-xx^T)\nabla f(x)
- レトラクション
R_x(\xi) = \frac{x+\xi}{\|x+\xi\|}ソースコード
ソースコードや実行した notebook は Github に.
from abc import ABCMeta, abstractmethod import numpy as np class GradientDescent(metaclass=ABCMeta): def __init__(self, max_iter: int = 300, extended_output: bool = False): self.max_iter = max_iter self.extended_output = extended_output self.f = list() @abstractmethod def _f(self, x: np.ndarray): raise NotImplementedError('The function _f is not implemented') @abstractmethod def _df(self, x: np.ndarray): raise NotImplementedError('The function _df is not implemented') @abstractmethod def _step_size(self, x: np.ndarray, iteration: int): raise NotImplementedError('The function _step_size is not implemented') @abstractmethod def _retraction(self, x: np.ndarray, v: np.ndarray): raise NotImplementedError('The function _retraction is not implemented') def optimize(self, x: np.ndarray): res = np.copy(x) if self.extended_output: self.f.append(self._f(res)) for i in range(1, self.max_iter + 1): step_size = self._step_size(res, i) res = self._retraction(res, - self._df(res) * step_size) if self.extended_output: self.f.append(self._f(res)) return res class RayleighQuotientGD(GradientDescent): def __init__(self, A: np.ndarray, step_size: float = 1.0, c = 0.5, max_iter: int = 300, extended_output: bool = False): super().__init__(max_iter=max_iter, extended_output=extended_output) self.A = A self.step_size = step_size self.c = c def _f(self, x: np.ndarray) -> float: Ax = np.dot(self.A, x) xx = np.dot(x, x) return np.dot(Ax, x) / xx def _df(self, x: np.ndarray) -> np.ndarray: Ax = np.dot(self.A, x) xx = np.dot(x, x) f = np.dot(Ax, x) / xx return 2 * (Ax - f * x) / xx def _step_size(self, x: np.ndarray, iteration: int) -> float: df = self._df(x) d = - np.copy(df) g = np.dot(df, d) t = self.step_size f = self._f(x) while self._f(self._retraction(x, t * d)) > f + self.c * t * g: t *= 0.5 return t def _retraction(self, x: np.ndarray, v: np.ndarray) -> np.ndarray: return x + v class RayleighQuotientSphereGD(GradientDescent): def __init__(self, A: np.ndarray, step_size: float = 1.0, c = 0.5, max_iter: int = 300, extended_output: bool = False): super().__init__(max_iter=max_iter, extended_output=extended_output) self.A = A self.step_size = step_size self.c = c def _f(self, x: np.ndarray) -> float: Ax = np.dot(self.A, x) return np.dot(Ax, x) def _df(self, x: np.ndarray) -> np.ndarray: Ax = np.dot(self.A, x) f = np.dot(Ax, x) return 2 * (Ax - f * x) def _step_size(self, x: np.ndarray, iteration: int) -> float: df = self._df(x) d = - np.copy(df) g = np.dot(df, d) t = self.step_size f = self._f(x) while self._f(self._retraction(x, t * d)) > f + self.c * t * g: t *= 0.5 return t def _retraction(self, x: np.ndarray, v: np.ndarray) -> np.ndarray: norm = np.linalg.norm(x + v) return (x + v) / norm if norm != 0 else 0
- 投稿日:2019-09-27T20:45:33+09:00
SGD Regressorでn次関数が予測できるか?
やったこと
scikit-learnのアルゴリズム選択マップの中の一つである
SGD Regressor
を実装すると同時に、どれくらい複雑なデータに対応できるのかを検証してみた。
n次関数の予測(n: 1~10)
データ数が100,1000,1000000ごとにSGD Regressorの精度を調べてみた
結果
4次関数から、大きな誤差が目視できるようになったが、9次関数でも大きな特徴は捉えていた(山と谷の位置は一致していた)
ただし、これはn次関数のnの値が大きくなればなるほど、Yの値は大きくなるので、元データに対する誤差の割合は一定なのかもしれない。(後日検証します)
赤色が予測結果で、青色が正解データです。1次関数
2次関数
3次関数
4次関数以下順番に
ソースコード
- 投稿日:2019-09-27T20:39:33+09:00
MacでワンクリックでJupyter Notebookを起動させる方法
Macのデスクトップ画面から、ワンクリックで使用したい仮想環境でJupyter Notebookを起動させる方法をメモがてら。
環境
使用OS:macOS Mojave
準備するもの
Anacondaを使用して作成した仮想環境(Jupyter Notebookインストール済みのもの)
最初からMacに入っているアプリケーション「Automator」を使用
(Launchpadではその他に入っている)
この子です。手順
以下のコマンドを入力する
下がら2行目の「return input」を削除して、
(* Your scripts goes here *)
の部分に、以下のコマンドを入力する。tell application "Terminal" set currentTab to do script with command "source activate py4kaggle" delay 0.3 set currWin to index of first window tell window currWin set custom title of first tab to "Tarminal for yy" end tell set current settings of window currWin to settings set "zz" delay 0.3 do script "cd documents/Kaggle && jupyter notebook" in currentTab end tell5.各行のコマンドについて説明するよ
tell application "Terminal" ~ end tellまず、Terminalを呼び出しているよ、普通にターミナルを開いているだけ。
set currentTab to do script with command "source activate xx" delay 0.3現在開いているターミナルのタブにcurrentTabっていう名前をつけて、ターミナルに「source activate xx」って入力した。
これで仮想環境「xx」に切り替えることができる。
そのあと、読んでそのまま、delayを0.3秒入れている(順番通りに処理を実行させるためのおまじない)set currWin to index of first window tell window currWin set custom title of first tab to "Tarminal for yy" end tell set current settings of window currWin to settings set "zz" delay 0.3現在開いている一つ目のターミナルのウィンドウにcurrWinっていう名前をつける。
そして、今名前をつけたウィンドウ「currWin」の最初のタブのタイトルに「Tarminal for yy」っていう名前をつける。
次に、ウィンドウ「currWin」に対して「zz」っていうテーマを適用する。
(これは好みの問題、デフォルトのターミナルのテーマが好みではないためこうしているよ、複数のターミナルを開いた時に色の違いで見分けることができればわかりやすくてよきよき)
そして最後に、おまじないのdelay 0.3do script "cd documents/subdir && jupyter notebook" in currentTabこれが最後の処理。
先ほど作成したcurrentTabにおいて、「documents/subdir」にカレントディレクトリを変更する。
その後に続けて「jupyter notebook」と入力し、Jupyterを起動する。確認
右上の実行ボタンを押して実行してみるよ。
何らかのポップアップメッセージが出てきたら、全部許可してしまってOK(今後表示しないにチェック)
成功すると、下の感じになる。
ついでに、ターミナルも起動している。確認して見てね(名前とか入っているのでスクショは省略させてね)保存
⌘+Sを入力すると、今作成したものを保存することができる。
適当な名前をつけて、アプリケーションとして保存する。
はい!これで完成!
Dockに追加しておけば、使うときもポチッとワンクリックするだけ!簡単!おまけ
Finderを開いてアプリケーションディレクトリを開く
その中に、先ほど作成した「JupyterNotebook.app」が入っている。
これを二本指でクリックしてショートカットメニューから「情報を見る」を選択
こんな感じの画面が出てくると思います↓
これの、左上のアイコン(今はJupyterNotebookのアイコンが入っている)部分をクリックすると、縁取られて色がつく。
この状態で、ここに画像をドラッグ&ドロップするとアイコンを変更することができる。
(ちなみに私は「Jupyter Notebook」でggった時に出てきた画像を使わせてもらってます)ちなみに私は、同様の手法で作成したショートカットアクセス用アプリをいくつか並べてます。
(左から順にGmail、Youtube、JupyterNotebook、Kaggle)
ウェブサイトにアクセスしたいなら、Gmail.appdo shell script "open -na 'Google Chrome' --args 'https://mail.google.com/mail/u/0/#inbox'"って感じにすればおk。
終わりに
いかがでしょう。ホント便利です。
あ、勉強しよ、ってなった時に一瞬で環境を立ち上げられるのは非常に便利ですよね。
皆さんも是非使って見てください、便利すぎて無しでは人生しんどくなります(いいすぎ)。よきJupyterライフを。
- 投稿日:2019-09-27T20:13:13+09:00
【Python】ABC - 089 - Hina Arare
- 投稿日:2019-09-27T19:25:58+09:00
Macにpyrealsense2をインストールする
前置き
Python で RealSense を動かすプログラミングを書く際に、import pyrealsense2 で詰まったので、Mac に pyrealsense2 をインストールする方法をメモとして残しておきます。
環境構築
$ brew install libusb pkg-config $ brew install homebrew/core/glfw3 $ brew install cmake $ git clone https://github.com/IntelRealSense/librealsense.git $ cd librealsense $ mkdir build && cd build $ cmake .. -DBUILD_EXAMPLES=true -DBUILD_WITH_OPENMP=false -DHWM_OVER_XU=false -DBUILD_PYTHON_BINDINGS=true -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3 -G "Unix Makefiles" $ make $ sudo make install上記の環境構築が完了した後、
$ cd /usr/local/lib && lsを実行すると、
上記のように、新しいファイルが作成されていることが確認できると思います。シンボリックを作成する
$ cd /usr/local/lib/python3.6/site-packages $ ln -s /usr/local/lib/pyrealsense2.cpython-37m-darwin.so pyrealsense2.so $ ln -s /usr/local/lib/pybackend2.cpython-37m-darwin.so pybackend2.sopyrealsense2 の確認
$ python3 >>> import pyrealsense2 >>>補足
環境構築 の -DPYTHON_EXECUTABLE:FILEPATH=/usr/local/bin/python3
と
シンボリックを作成する の cd /usr/local/lib/python3.6/site-packages
を
Pythonを実行する環境に合わせて適宜変えて頂ければと思います。参考
- 投稿日:2019-09-27T19:17:52+09:00
主成分分析で「九条カレンのポーズ」から正規直交基底を検出する
やること
九条カレンの通称「正規直交基底のポーズ」から正規直交基底を自動で求めていきたいと思います。
こんな感じ
アルゴリズム
※Numpy, matplotlib, sklearn, PILを使用します
import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA from PIL import Image, ImageFilter1.画像を適当なサイズに縮小して読み込み
軸を求める部分が重いので、小さめの画像にリサイズします。この例では元画像の半分(横幅512px)にリサイズしました。1
with Image.open("karen.jpg") as img: img = img.resize((img.width//2, img.height//2), Image.BICUBIC) # 1/2にリサイズ(メモリ対策) original = np.asarray(img, np.float32) / 255.0 # [0, 1]のNumpy配列に後で使うのでNumpy配列としても保持しておきましょう。
2. エッジ検出
最終的にはこの画像を0,1に変換したいのです(二値化)。これは画像の内容にもよりますが、アニメ画像の場合は軸を求める際に、塗りつぶされた領域よりも輪郭線を見てほしいので、エッジ検出を行ってみました。特徴量を抽出しているイメージです。エッジ検出の前には一度グレースケール化をします。エッジ検出はPILの場合は
ImageFilter.FIND_EDGES
でできます。with Image.open("karen.jpg") as img: img = img.resize((img.width//2, img.height//2), Image.BICUBIC) # 1/2にリサイズ(メモリ対策) original = np.asarray(img, np.float32) / 255.0 # [0, 1]のNumpy配列に edge = np.asarray(img.convert("L").filter(ImageFilter.FIND_EDGES), np.float32) / 255.0 # エッジ検出しNumpy配列へ最終的に二値化したいだけなので、別にエッジ検出でなくても良いです。簡単な画像なら、グレースケール化した画像を二値化するのもよいでしょう。
3. 二値化
エッジ検出の結果は0~255のグレースケールになっているので、これを0,1に変換します。
binary = edge >= 0.5 # 2値化0~1スケールのNumpyの場合、例えば「0.5以上なら1、そうでなければ0」とします。このスレッショルドは変えても構いません。
4.xy座標(yx座標)に変換
今までは「(y, x)の座標に対して輝度が0か1か」というデータ構造でしたが、これを「輝度が1である(y, x)のペア」というデータ構造に変換します。xとyが逆になっているのは、Numpyでの画像の構造が(y, x, ch)だからです(軸を扱う際は注意)。
この処理は
np.where
を使うだけですぐできます。ただし、この関数の出力は(2, 輝度が1のマス数)という次元になるので、転置させます。pos = np.asarray(np.where(binary)).T.astype(np.float32) # 2値化した白の部分の座標を(y, x)で取る print(pos.shape) # np.whereの結果が(2, 該当ピクセル数)なので転置している # (10018, 2)5. 主成分分析
ここが本体で、「九条カレンのポーズ」の軸を求めるために主成分分析をします。Scikit-learnの関数を使いましょう。
p = PCA(n_components=2) # 主成分分析 p.fit(pos) eigen = np.asarray(p.components_) # 軸(固有ベクトル)ここでPCAのcomponent_というオブジェクト(固有ベクトル)が重要で、この固有ベクトルが軸の向きを表します。確認してみましょう。
print(eigen) print(np.dot(eigen, eigen.T)) print(np.dot(eigen.T, eigen)) # eigenが直交行列になっていることを確認(積が単位行列になっている)勘の良い方はもう既にわかっているかもしれませんが、固有ベクトルが回転行列のような形になっています(符号は違う)。
[[-0.18205993 -0.98328745] [-0.98328745 0.18205993]] [[1.0000000e+00 1.9925341e-09] [1.9925341e-09 1.0000000e+00]] [[1.0000000e+00 1.9925341e-09] [1.9925341e-09 1.0000000e+00]]
これで軸を求めることができました。
転置されたものとの積が単位行列になっているので、この固有ベクトルの行列は直交行列であることが確認できます。これは求められた軸が正規直交基底であるということの裏付けにもなっています。
6. プロットしてみる
いよいよ求めた「正規直交基底」を九条カレンに重ねてみます。
plt.imshow(original) plt.quiver([380, 320], [200, 10], eigen[1], eigen[0], color="red", scale=2) # (y, x)なのに注意 plt.show()わーい! 正規直交基底だ!!
なぜこうなるのか
なぜこのようになるのでしょうか。それは、主成分分析のやっていることが、データにフィットするような軸を探すことだからです。
例えばランダムな点に対して、主成分分析で軸を探してみます。
コード(クリックで展開)
import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA def random_plot(): # データの作成 original_data = np.random.randn(100, 2) original_data[:, 1] /= 3.0 theta = np.random.uniform(-np.pi / 5, np.pi / 5) rotation = np.array([[np.cos(theta), -np.sin(theta)], [np.sin(theta), np.cos(theta)]]) projection = np.dot(original_data, rotation) transform = np.random.uniform(-1, 1, (1, 2)) * np.array([[1, 0.5]]) projection = projection + transform # PCA pca = PCA(n_components=2) pca.fit(projection) # プロット plt.scatter(projection[:, 0], projection[:, 1]) plt.xlim(-4,4) plt.ylim(-1.5, 1.5) plt.quiver(transform[0], transform[0].T, pca.components_[:, 0], pca.components_[:, 1], scale=3, color="red") plt.show()
綺麗に軸を探すことができました。主成分分析は次元削減のツールとして使われますが、変換後の次元を入力の次元と同じにすれば、元のデータを損なわずに軸だけ探すことができます2。
先程の例では、九条カレンの画像の輪郭をこのような点の集合と捉えることで、軸の検出が可能になります。
応用:回転された画像の修正
簡単な画像かつ微小なブレであれば、この方法を用いて回転された画像を修正することができます。軸の検出がうまく行けば人間による調整はいりません。
考え方は単純です。正規直交基底を求めてから、xy軸とのなす角を計算します。これは
np.arcsin
などの逆三角関数でできます(回転行列を作って逆行列を求めるのも良いです)。修正するための回転角を求めたら、アフィン変換をすれば終わりです。コードは次のとおりです。
import numpy as np import matplotlib.pyplot as plt from sklearn.decomposition import PCA from PIL import Image, ImageFilter def karen_rotate(degree, idx): with Image.open("karen2.jpg") as img: img = img.rotate(degree, fillcolor="black", expand=True) # 回転(度数) width, height = img.size original = np.asarray(img, np.float32) / 255.0 # [0, 1]のNumpy配列に edge = np.asarray(img.convert("L"), np.float32) / 255.0 binary = edge >= 0.05 # 2値化 (エッジ検出はしない、非常に小さいスレッショルドにする) pos = np.asarray(np.where(binary)).T.astype(np.float32) # 2値化した白の部分の座標を(y, x)で取る # 主成分分析 p = PCA(n_components=2) # 主成分分析 p.fit(pos) eigen = np.asarray(p.components_) # 固有ベクトル # 回転角の計算(とりあえずうまく行った方法なのでもしかしたらうまく行かないことあるかも) rotate_degree = np.arcsin(eigen[0, 0]) * 180 / np.pi # 修正角度、np.arcsinはラジアンなので度数に変換 with Image.fromarray((original * 255).astype(np.uint8)) as img: fixed_img = img.rotate(int(rotate_degree)) # アフィン変換 fixed_np = np.asarray(fixed_img, np.uint8) # 結果のプロット ax = plt.subplot(4, 3, 3 * idx + 1) ax.imshow(binary) ax = plt.subplot(4, 3, 3 * idx + 2) ax.imshow(original) ax.quiver(np.repeat(width // 2, 2), np.repeat(height // 2, 2), eigen[1], eigen[0], color="red", scale=2) ax.set_title("deg = " + str(degree)) ax = plt.subplot(4, 3, 3 * idx + 3) ax.imshow(fixed_np) if __name__ == "__main__": plt.subplots_adjust(top=0.92, bottom=0.05, left=0.05, right=0.98) for i, d in enumerate([-30, -15, 15, 30]): karen_rotate(d, i) plt.show()結果は次のようになります3。
左から、二値化したときの画像、回転済みの画像と主成分、右が回転修正した画像です。どの例も回転された画像がきちんと戻っているのがわかります。
ただし、回転角があまりに大きすぎると(45度以上)、90度反転したような画像が出てくるので注意してください。
コツ
二値化する前の特徴抽出がポイントです。正規直交基底のポーズの場合はエッジ検出をしましたが、回転補正をする場合はエッジ検出をしていません。回転補正をする場合は、メインのエリアを長方形で取り出すなど、できるだけ簡単な画像に変換すると精度が上がります。
また、ポーズから軸の抽出をする場合は、手のポーズと姿勢の主従に注意してください。次のケースのように、手の面積よりも身体の面積のほうが明らかに大きい場合は、姿勢側で軸が検出されうまくいきません。
画像はこちらから https://togetter.com/li/825944 https://twitter.com/tina_quaver/status/601439736461295619 ↩
逆に言えば、次元削減としての主成分分析は、様々な軸の候補を説明分散でソートし、重要な順に取っているだけということだけです ↩
画像はこちらから https://togetter.com/li/825944 ↩
- 投稿日:2019-09-27T19:07:39+09:00
木の巡回配列から二分木を生成する
概要
二分木の深さ優先探索(DFS)には先行順巡回:pre-order, 中間順巡回:in-order, 後行順巡回:post-orderの3通りの方法がありますが、そのうち中間順巡回と先行(後行)順巡回の2つの配列が与えられれば、対象となる二分木を生成することができます。本記事ではin-orderとpost-orderの配列から二分木を生成する方法を紹介します。
方針
まず、pre-orderの場合は先頭の要素、post-orderの場合は最後尾の要素がrootになることに着目します。また、in-orderの配列をrootの要素の位置で分割した場合、左側の部分配列はrootの左の部分木、右側の部分配列はrootの右の部分木のそれぞれのin-orderになります。そこで下記の手順を再帰的に繰り返すことで、二分木を生成することができます。
- post-orderの配列の最後尾をrootの値として取り出す
- in-orderの配列を、rootの値の位置を中心に分割する
- 左右に分割したin-orderの配列と、最後尾を除いたpost-orderの配列からsub-treeを生成し、それぞれrootの子ノードにセットする
実装例
class TreeNode: def __init__(self, x): self.val = x self.left = None self.right = None class Solution(object): def buildTree(self, inorder, postorder): # post-orderの最後尾をrootの値として取り出す rootVal = postorder.pop() # in-orderの配列を分割するため、rootValのindexを取得する rootIdx = inorder.index(rootVal) root = TreeNode(rootVal) # in-orderの配列をrootIdxを中心に分割し、左右の部分木を再帰的に生成する if len(postorder) > 0 and rootIdx < len(inorder) - 1: root.right = self.buildTree(inorder[rootIdx+1:], postorder) if len(postorder) > 0 and rootIdx > 0: root.left = self.buildTree(inorder[:rootIdx], postorder) return root実行
# 下記のような二分木を生成する # 3 # / \ # 9 20 # / \ # 15 7 inorder = [9,3,15,20,7] postorder = [9,15,7,20,3] root = Solution().buildTree(inorder, postorder)
- 投稿日:2019-09-27T18:59:20+09:00
Re:dashで取得したデータをSlackに投稿する(とRe:dash内pythonを書くときの注意点)
やりたい
- Re:dashで取得したデータを、pythonデータソースを使って、Slackに投稿したい
- Re:dashのalert機能は便利だが、条件が解消しないとずっと出続けてしまう問題があった
準備
Slack投稿用のサンプル
import requests import json SLACK_URL = "[WEBHOOK_URL]" content = "`Daijin` は `Hentai`" payload = { "text": content } data = json.dumps(payload) response = requests.post(SLACK_URL, data)実際にやったこと
- Re:dashで取得したある条件に合ったデータをSlackに投稿する
- Re:dash上で定期実行するが、その時間を秒単位では指定できず、ほっとくと重複したデータを投稿してしまう。そこをSlack APIを使って既に投稿したデータは投稿しない処理を加えた。
- 結果75行の割と大きめのソースとなった
その際ハマった注意点
- python側で使うライブラリは、
Data Source
の画面で追加できる。但し、標準ライブラリのみで、pip install
が必要なものはGUIだけではできなそうだった
ここでsaveするだけでimportできるようにはなった- なぜか関数を作れない。関数を作って実行すると関数内からglobal変数にアクセスできない
- 引数付きのクエリは実行できなそうである
- get_query_resultではクエリは実行されない
- redashの定期実行は割とブレる。1minute毎のやつでも30秒くらいずれる。弊社の環境だと、なぜか90秒サイクルで実行されてた。1秒以内で実行されるクエリなんだが
13:32:20 13:33:50 13:35:20 13:36:50 13:38:20参考
- 投稿日:2019-09-27T16:25:52+09:00
python 勉強 plot
plotの勉強
pivotの勉強の際に作成したpythonでplotをしてみる。
データの可視化への試みです。
ざっくりしています。。。import numpy as np import pandas as pd df = pd.read_csv(r'インプットファイル', index_col=0).drop(['日時','企業名','支払','返金'], axis=1) df["日時"] = df["日時"].str.split(":").str[0] print(c_nitiji) data = pd.pivot_table(df, index='日時', columns='種別', values='金額', aggfunc=[sum]) data2 = data.fillna(0) fig, ax = plt.subplots() ax.plot(data2) plt.savefig(r"アウトプット")以下の図ができた。
みづらい。。。
もうちょっと改良予定。
- 投稿日:2019-09-27T16:25:52+09:00
python 勉強 plot
plotの勉強
pivotの勉強の際に作成したpythonでplotをしてみる。
データの可視化への試みです。
ざっくりしています。。。import numpy as np import pandas as pd df = pd.read_csv(r'インプットファイル', index_col=0).drop(['日時','企業名','支払','返金'], axis=1) df["日時"] = df["日時"].str.split(":").str[0] print(c_nitiji) data = pd.pivot_table(df, index='日時', columns='種別', values='金額', aggfunc=[sum]) data2 = data.fillna(0) fig, ax = plt.subplots() ax.plot(data2) plt.savefig(r"アウトプット")以下の図ができた。
みづらい。。。
もうちょっと改良予定。
- 投稿日:2019-09-27T15:20:59+09:00
python環境構築 Miniconda3ハンズオン 複数バージョンを共存させる
できるようになること
- Miniconda3を使ってwindowsにpython環境を構築する
- 複数のバージョンのpython環境を同居させることができる
- こっちのscriptはpython3.5、こっちのscriptはpython3.7やpython2.7という風に複数の環境を共存させることができる
Miniconda3のインストール
- 公式にアクセスして自分のOSとCPUに合わせてインストールプログラムをダウンロード
- Miniconda3の最新版でよい
- ダウンロードしたexeを実行してウィザードを進めてインストール
- ※Anacondaというはじめからいろんなツールが入っているものもあります。
- インストール後コマンドプロンプトでcondaコマンドが通ればOK
C:\>conda -V conda 4.7.10
コマンドが通らない場合は環境変数のPATHにconda.exeの在り処を設定する
- だいたいここらへんにあると思う
%USERPROFILE%\AppData\Local\Continuum\miniconda3\Scripts
コマンドは
conda
と入力すると使えるコマンド一覧がでてきます。C:\>conda usage: conda-script.py [-h] [-V] command ... conda is a tool for managing and deploying applications, environments and packages. Options: positional arguments: command clean Remove unused packages and caches. config Modify configuration values in .condarc. This is modeled after the git config command. Writes to the user .condarc file (C:\Users\xxxxxx\.condarc) by default. create Create a new conda environment from a list of specified packages. help Displays a list of available conda commands and their help strings. info Display information about current conda install. init Initialize conda for shell interaction. [Experimental] install Installs a list of packages into a specified conda仮想環境の確認
- 現在作成されている仮想環境の一覧表示
C:\>conda info -e # conda environments: # base * C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3
- 初期状態はbaseという名前の仮想環境が1つ存在していて、右側に表示されているpathの中に
python.exe
があります。仮想環境へ入る
- 仮想環境「base」に入ります。
C:\>conda activate base (base) C:\>
- プロンプトの先頭に
(base)
のように仮想環境名が表示されていればactiveになっている状態です。- この状態でpythonインタプリタを起動します。
(base) C:\>python Python 3.6.4 |Anaconda, Inc.| (default, Jan 16 2018, 10:22:32) [MSC v.1900 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
- python3.6.4と表示されています、これは
base
のpythonバージョンになります。base
はデフォルトの仮想環境なので基本的には使いません。仮想環境の作成
- 構文
conda create --name <仮想環境名> python=<バージョン>
- python3.7環境を作成する
- 途中で
Proceed ([y]/n)?
と聞かれるのでy
を押す(base) C:\>conda create --name vpy3.7 python=3.7 Collecting package metadata (current_repodata.json): don . . . <省略> . . . Preparing transaction: done Verifying transaction: done Executing transaction: done # # To activate this environment, use # # $ conda activate vpy3.7 # # To deactivate an active environment, use #
- 作成されたか確認
C:\>conda info -e # conda environments: # base * C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3 vpy3.7 C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy3.7
- vpy3.7に入ってpythonインタプリタを起動する
C:\>conda activate vpy3.7 (vpy3.7) C:\>python Python 3.7.4 (default, Aug 9 2019, 18:34:13) [MSC v.1915 64 bit (AMD64)] :: Anaconda, Inc. on win32 Type "help", "copyright", "credits" or "license" for more information. >>>
- 無事にpython3.7環境が作成できました。
- python2.7環境の作成は以下のようになります。
- 環境作成時はどの仮想環境から実行しても問題ないです。
(vpy3.7) C:\>conda create --name vpy2.7 python=2.7 . . <省略> ・ ・ (vpy3.7) C:\>conda info -e # conda environments: # base C:\Users\xxxxxxx\AppData\Local\Continuum\miniconda3 vpy2.7 C:\Users\xxxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy2.7 vpy3.7 * C:\Users\xxxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy3.7 (vpy3.7) C:\>conda activate vpy2.7 (vpy2.7) C:\>python Python 2.7.16 |Anaconda, Inc.| (default, Mar 14 2019, 15:42:17) [MSC v.1500 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>>仮想環境内でパッケージのインストール
- 通常パッケージ管理システムで
pip
を利用することが多いですが、conda環境内ではconda install
コマンドが推奨されています。
- 私は
conda install
に対応していないパッケージはpip install
で入れています。- 現在の仮想環境内でインストール済みパッケージ一覧
(vpy3.7) C:\>conda list # packages in environment at C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy3.7: # # Name Version Build Channel ca-certificates 2019.8.28 0 certifi 2019.9.11 py37_0 openssl 1.1.1d he774522_0 pip 19.2.3 py37_0 python 3.7.4 h5263a28_0 setuptools 41.2.0 py37_0 sqlite 3.29.0 he774522_0 vc 14.1 h0510ff6_4 vs2015_runtime 14.16.27012 hf0eaf9b_0 wheel 0.33.6 py37_0 wincertstore 0.2 py37_0
scipy
を追加する(vpy3.7) C:\>conda install scipy Collecting package metadata (current_repodata.json): done Solving environment: done ・ ・ <省略> ・ ・ Preparing transaction: done Verifying transaction: done Executing transaction: done (vpy3.7) C:\>conda list # packages in environment at C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy3.7: # # Name Version Build Channel blas 1.0 mkl ca-certificates 2019.8.28 0 certifi 2019.9.11 py37_0 icc_rt 2019.0.0 h0cc432a_1 intel-openmp 2019.4 245 mkl 2019.4 245 mkl-service 2.3.0 py37hb782905_0 mkl_fft 1.0.14 py37h14836fe_0 mkl_random 1.1.0 py37h675688f_0 numpy 1.16.5 py37h19fb1c0_0 numpy-base 1.16.5 py37hc3f5095_0 openssl 1.1.1d he774522_0 pip 19.2.3 py37_0 python 3.7.4 h5263a28_0 scipy 1.3.1 py37h29ff71c_0 setuptools 41.2.0 py37_0 six 1.12.0 py37_0 sqlite 3.29.0 he774522_0 vc 14.1 h0510ff6_4 vs2015_runtime 14.16.27012 hf0eaf9b_0 wheel 0.33.6 py37_0 wincertstore 0.2 py37_0切り替えなしで指定した仮想環境でpythonを実行する
- 仮想環境を切り替えずにフルパスで実行することができます。
- 例えば
vpy2.7
環境でhoge.py
を実行したい場合は以下のようになります。C:\>conda info -e # conda environments: # base * C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3 vpy2.7 C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy2.7 vpy3.7 C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy3.7 C:\> C:\Users\xxxxxx\AppData\Local\Continuum\miniconda3\envs\vpy2.7\python.exe hoge.py
- 投稿日:2019-09-27T14:47:22+09:00
IfxPyでInformixにアクセス
PythonでInformixに繋ぐまともな方法はなかった。
あるとすれば以下の2つ。
- PyODBCでODBC経由
- JayDeBeApiでJDBC経由
遂にIfxPyがやってきた。
IfxPy
- Python2/3 対応のネイティブドライバー
- pip install IfxPy インストール可能
素晴らしい!
今までJayDeBeApiでInformixに繋いでいた。
IfxPyだけで繋げられるようになると非常に楽になる。早速試してみることにした。
環境
Client
- vagrant box generic/debian10
- Informix Client SDK 4.50FC1
- CLIENT_LOCALE=ja_jp.utf8
Locale$ localectl System Locale: LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja VC Keymap: n/a X11 Layout: us X11 Model: pc105Server
- CentOS7
- Informix Dynamic Server 12.10FCWE12
- DB_LOCALE=ja_jp.sjis-s
手順
# make csdk folder $ mkdir csdk # extract $ tar -xvf clientsdk.4.10.FC9DE.LINUX.tar -C csdk $ cd csdk # Install Library for installclientcsdk $ sudo apt -y install libaio1 bc libncurses5 ncurses-bin libpam0g $ sudo apt -y install libncurses5-dev libelf1 # Install $ sudo ./installclientcsdk -i console # SET Environment Variables # export INFORMIXDIR=/opt/IBM/informix # CSDK 4.10 export INFORMIXDIR=/opt/IBM/Informix_Client-SDK #CSDK 4.50 export LD_LIBRARY_PATH=${INFORMIXDIR}/lib:${INFORMIXDIR}/lib/esql:${INFORMIXDIR}/lib/cli # Make sqlhosts $ sudo cp /opt/IBM/Informix_Client-SDK/etc/sqlhosts.std /opt/IBM/Informix_Client-SDK/etc/sqlhosts $ vi /opt/IBM/Informix_Client-SDK/etc/sqlhosts # ...etc.... servername onsoctcp serverip 21435 # SET DB_LOCALE $ export DB_LOCALE=ja_jp.sjis-s # SET CLIENT_LOCALE $ export DB_LOCALE=ja_jp.utf8 # pip install $ sudo apt install python3-pip # Install IfxPy $ pip3 install IfxPy --user # make python file $ vi ifxcnnt.py # Execute Python3 $ python3 ifxcnnt.py Traceback (most recent call last): File "ifxcnnt.py", line 6, in <module> conn=IfxPy.connect(ConStr,"","") Exception: [Informix][Informix ODBC Driver][Informix]Error opening required code-set conversionあれ?
繋がらないぞ。GitHubにIssue立てた。
Don't deal Japanesejsagrera氏によるとConnectionStringにDB_LOCALEとCLIENT_LOCALEを書き足すと繋がるそうだ。
下記のように書く。
code#! /usr/bin/env python3 import IfxPyDbi as dbapi2 ConnectionString="SERVER=servername;DATABASE=database;HOST=IP;SERVICE=21435;UID=username;PWD=password;PROTOCOL=onsoctcp;CLIENT_LOCALE=ja_jp.utf8;DB_LOCALE=ja_jp.sjis-s" conn=dbapi2.connect(ConnectionString,"","") cur=conn.cursor() cur.execute("select 得意先名 from 得意先m where 得意先cd=1") rows=cur.fetchall() for i,row in enumerate(rows): print("Row",i,"value=",row) cur.close() conn.close() print("Done")動くかな?
result$ python3 ifxfetchall.py Row 0 value= ('株式会社 ',) Doneよし!動いた。
日本語カラム対策
日本語のカラム名を指定すると動かない関数がある。
僕が探した中では下記の4つ。
- ifxPy.fetch_both
- ifxPy.fetch_assoc
- IfxPy.field_name
- IfxPy.result
SQLのASを使ってカラム名をASCIIな名前に変更する。
それで回避できる。ifxPy.fetch_bothを例にしてみる。
ifxPy.fetch_both# Connection ConnectionStringなど省略 sql="SELECT 得意先cd as customer_cd, 得意先名 as customer_name from 得意先m where 得意先cd=1 or 得意先cd=2" stmt=IfxPy.exec_immediate(conn,sql) dic=IfxPy.fetch_both(stmt) while dic != False: print(dic["customer_cd"]) print(dic["customer_name"]) dic=IfxPy.fetch_both(stmt) IfxPy.free_result(stmt) IfxPy.free_stmt(stmt) IfxPy.close(conn)さあ動くかな?
result$ python3 ifxfetchboth.py 2 株式会社 東京支店 1 株式会社
よし動いた!
終わりに
僕が建てたGitHub Issueでは
- 4つの関数が日本語カラムを扱えない。
- それらのCのコードのPyUnicode_AsASCIIString()のような関数が悪さをしている
を伝えておいた。
次の次あたりに治っているといいな。
- 投稿日:2019-09-27T14:47:22+09:00
IfxPyでInformixに接続
PythonでInformixに繋ぐまともな方法はなかった。
あるとすれば以下の2つ。
- PyODBCでODBC経由
- JayDeBeApiでJDBC経由
遂にIfxPyがやってきた。
IfxPy
- Python2/3 対応のネイティブドライバー
- pip install IfxPy インストール可能
素晴らしい!
今までJayDeBeApiでInformixに繋いでいた。
IfxPyだけで繋げられるようになると非常に楽になる。早速試してみることにした。
環境
Client
- vagrant box generic/debian10
- Informix Client SDK 4.50FC1
- CLIENT_LOCALE=ja_jp.utf8
Locale$ localectl System Locale: LANG=ja_JP.UTF-8 LANGUAGE=ja_JP:ja VC Keymap: n/a X11 Layout: us X11 Model: pc105Server
- CentOS7
- Informix Dynamic Server 12.10FCWE12
- DB_LOCALE=ja_jp.sjis-s
手順
# make csdk folder $ mkdir csdk # extract $ tar -xvf clientsdk.4.10.FC9DE.LINUX.tar -C csdk $ cd csdk # Install Library for installclientcsdk $ sudo apt -y install libaio1 bc libncurses5 ncurses-bin libpam0g $ sudo apt -y install libncurses5-dev libelf1 # Install $ sudo ./installclientcsdk -i console # SET Environment Variables # export INFORMIXDIR=/opt/IBM/informix # CSDK 4.10 export INFORMIXDIR=/opt/IBM/Informix_Client-SDK #CSDK 4.50 export LD_LIBRARY_PATH=${INFORMIXDIR}/lib:${INFORMIXDIR}/lib/esql:${INFORMIXDIR}/lib/cli # Make sqlhosts $ sudo cp /opt/IBM/Informix_Client-SDK/etc/sqlhosts.std /opt/IBM/Informix_Client-SDK/etc/sqlhosts $ vi /opt/IBM/Informix_Client-SDK/etc/sqlhosts # ...etc.... servername onsoctcp serverip 21435 # SET DB_LOCALE $ export DB_LOCALE=ja_jp.sjis-s # SET CLIENT_LOCALE $ export DB_LOCALE=ja_jp.utf8 # pip install $ sudo apt install python3-pip # Install IfxPy $ pip3 install IfxPy --user # make python file $ vi ifxcnnt.py # Execute Python3 $ python3 ifxcnnt.py Traceback (most recent call last): File "ifxcnnt.py", line 6, in <module> conn=IfxPy.connect(ConStr,"","") Exception: [Informix][Informix ODBC Driver][Informix]Error opening required code-set conversionあれ?
繋がらないぞ。GitHubにIssue立てた。
Don't deal Japanesejsagrera氏によるとConnectionStringにDB_LOCALEとCLIENT_LOCALEを書き足すと繋がるそうだ。
下記のように書く。
code#! /usr/bin/env python3 import IfxPyDbi as dbapi2 ConnectionString="SERVER=servername;DATABASE=database;HOST=IP;SERVICE=21435;UID=username;PWD=password;PROTOCOL=onsoctcp;CLIENT_LOCALE=ja_jp.utf8;DB_LOCALE=ja_jp.sjis-s" conn=dbapi2.connect(ConnectionString,"","") cur=conn.cursor() cur.execute("select 得意先名 from 得意先m where 得意先cd=1") rows=cur.fetchall() for i,row in enumerate(rows): print("Row",i,"value=",row) cur.close() conn.close() print("Done")動くかな?
result$ python3 ifxfetchall.py Row 0 value= ('株式会社 ',) Doneよし!動いた。
日本語カラム対策
日本語のカラム名を指定すると動かない関数がある。
僕が探した中では下記の4つ。
- ifxPy.fetch_both
- ifxPy.fetch_assoc
- IfxPy.field_name
- IfxPy.result
SQLのASを使ってカラム名をASCIIな名前に変更する。
それで回避できる。ifxPy.fetch_bothを例にしてみる。
ifxPy.fetch_both# Connection ConnectionStringなど省略 sql="SELECT 得意先cd as customer_cd, 得意先名 as customer_name from 得意先m where 得意先cd=1 or 得意先cd=2" stmt=IfxPy.exec_immediate(conn,sql) dic=IfxPy.fetch_both(stmt) while dic != False: print(dic["customer_cd"]) print(dic["customer_name"]) dic=IfxPy.fetch_both(stmt) IfxPy.free_result(stmt) IfxPy.free_stmt(stmt) IfxPy.close(conn)さあ動くかな?
result$ python3 ifxfetchboth.py 2 株式会社 東京支店 1 株式会社
よし動いた!
終わりに
僕が建てたGitHub Issueでは
- 4つの関数が日本語カラムを扱えない。
- それらのCのコードのPyUnicode_AsASCIIString()のような関数が悪さをしている
を伝えておいた。
次の次あたりに治っているといいな。
- 投稿日:2019-09-27T13:30:22+09:00
Matplotlibグラフのx,y,zの注意点
皆さん、Matplotlib使っていますかーー?
私は、グラフとかあまり好きではなく、
データサイエンティフィックに【この辺りに顕著に結果が出ているようだ…】という
シリアスドラマのような展開が苦手です。どちらかというと、直感的に体感してもらう動的飛び道具にして使うことが多いでしょうか。
今回はMatplotlibにおちょくられたので、もう二度とこんな気持ちにならないように備忘録を兼ねて記事にします。みんなのx,y,z軸の基準ってどっち方向でしょう
私の認識はこうです
右から左/左から右:x軸
上から下/下から上:y軸
手前から奥/奥から手前:z軸
Matplotlibもそうだろうと思ってレッツnogood.pyimport numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D #1点だけ表示されるサンプル(x,y,zにたくさんのデータが入る) x = np.array([1]) y = np.array([2]) z = np.array([3]) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax = Axes3D(fig) #ここでプロット ax.plot(x,y,z,marker="o",linestyle='None') plt.show()これを、1点ではなく人間の骨格点にして置き換えて、その他いろいろエッセンスを足した。
(カメラに向かって右手を振っている想定(つまり、左側の右手)を俯瞰で見ている)
ズコー
寝てるー!!y軸とz軸が違う。
3Dが得意ではない私は、ここでx,y,zの軸はどれがどちらに向くのが正しいのかわからなくなり
旅に出ました。もう一つのルールー
Matplotlibに限らず、こういった基準もあるようです。
ここでは、もう高さとか奥行とかそういうのではなくなってきているというか、
じゃあこの立方体をぐるっとしたら結局同じとか、
言いたいことは分かる、だから何が言いたいの?とか、
バカにはその辺の草でも食わしておけとか、
色々お察しくださいますと幸いです。先ほどのコードを再度持ち出します。
nogood.pyimport numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D #1点だけ表示されるサンプル(x,y,zにたくさんのデータが入る) x = np.array([1]) y = np.array([2]) z = np.array([3]) fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax = Axes3D(fig) #y,zを入れ替えてplot #ax.plot(x,y,z,marker="o",linestyle='None') ax.plot(x,z,y,marker="o",linestyle='None') plt.show()お仕事内容に応じて適切にプロットください。
何が起きているか分かるようになりましたし、反転も改善されています。
付録
nogood.py#Forを使ってくるっと回転させる ax.view_init(elev=None, azim=i)アーティストのライブみたいになりました。
- 投稿日:2019-09-27T12:55:26+09:00
numpyのarrayをpandasのDataFrameにした時に、リストのまま入れる方法
どういう事?
array = np.array([[1, 2, 3], [4, 5, 6]])このnumpyの配列をpandasのDataFrameにすると・・・
理想
index 0 0 [1, 2, 3] 1 [4, 5, 6] 現実
index 0 1 2 0 1 2 3 1 4 5 6 PandasのDataFrameを挟んだ後、SparkのDataFrameにして、udfで処理したい時にこう言った理想が生まれます。
この理想を叶えたい時には・・・サンプルコード
import numpy as np import pandas as pd array = np.array([[1, 2, 3], [4, 5, 6]]) df = pd.DataFrame(array) df['list'] = df.apply(lambda x:x.tolist(), axis=1) df = df.loc[:, ['list']] print(df)出力
index list 0 [1, 2, 3] 1 [4, 5, 6]
- 投稿日:2019-09-27T11:53:40+09:00
Pythonで2つのリストの差集合を順序を保持したまま求める
どういう時に使うの?
通常リストの差集合を求める際にはset型というものを用いて求めます。
しかし、リストをset型に変換した時点で数字の昇順等でソートされてしまい、重複も削除されてしまいます。
リストの順序が何らかのスコアの高い順のID、のようなデータだと、重要な要素が失われてしまうことになります。
そのようなデータに対して使う感じです。イメージ
イメージlist1 = [5, 4, 3, 2, 1] list2 = [2, 4] result = [5, 3, 1]list1 - list2というイメージです。
list2の数字をlist1内から一括削除したいとき
Python版
sample.pylist1 = [5, 4, 3, 4, 2, 1] list2 = [2, 4] result = [i for i in list1 if i not in list2] print(result) [5, 3, 1]Pyspark版
sample.pyimport pyspark.sql.functions as F @F.udf(returnType=ArrayType(IntegerType())) def udf_list_diff(l1, l2): for i in l2: l1.remove(i) return l1list2の数字をlist1内の最初の数字だけ削除したいとき
Python版
sample.pydef list_diff(l1, l2): for i in l2: l1.remove(i) return l1 list1 = [5, 4, 3, 4, 2, 1] list2 = [2, 4] result = list_diff(list1, list2) print(result) [5, 3, 4, 1]Pyspark版
sample.pyimport pyspark.sql.functions as F @F.udf(returnType=ArrayType(IntegerType())) def udf_list_diff(l2, l2): return [i for i in l1 if i not in l2]appendix
順序を保持しなくてもよい場合
sample.pylist1 = [5, 4, 3, 4, 2, 1] list2 = [2, 4] result = (list(set(list1) - set(list2))) print(result) [1, 3, 5]
- 投稿日:2019-09-27T10:42:20+09:00
PySparkでArrayTypeの中身を一個づつcolumnにする
どういう時に使うの?
主にPysparkにおいてArrayTypeを保持しているDataFrameをcsvに出力したいときに使うと思います。
イメージ
input
ID array a [1, 2, 3] b [4, 5, 6] 上記のようなDataFrameを
output
ID num1 num2 num3 a 1 2 3 b 4 5 6 こんな感じにArrayの中身を分解するイメージです。
サンプルコード
sample.pyimport pyspark.sql.functions as F hoge = '' top_N = 10 #Arrayの中身を左から何個取るか。 1 ≦ top_N ≦ len(array) for i in range(top_N): exec(f'top{i + 1} = F.udf(lambda x : x[{i}])') hoge += f'.withColumn("num{i + 1}", top{i + 1}("array"))' exec(f'df2 = df1{hoge}.drop("array")')udfでarrayのN番目を取ってきてwithColumnで列を作るだけなのですが、
列の分だけudfの関数を定義し、withColumnを書かなければならなかったのを、execを用いることで自動化しています。top_Nを変えることで、arrayの中身を左から5個取ってきてTOP5だけ、みたいなことも出来ます。
- 投稿日:2019-09-27T10:34:40+09:00
importで別ファイルの変数を取得
- 投稿日:2019-09-27T02:27:04+09:00
dataclassを使ったYAML形式で保存/ロード可能な設定クラス
Pythonで設定オブジェクトを定義する場合にPython3.7から追加された
dataclasses.dataclass
が便利。デコレータをつけるだけでコンストラクタを省略できる。各項目に型を指定すればpyright
やmypy
で型推論をして事前にバグを発見できる。今回はdataclassとして振る舞う設定オブジェクトをYAMLファイルで保存/ロードできるようにしたいので実装した。https://github.com/kzmssk/yaml_config
dataclassによる設定ファイル
例えば、このような設定オブジェクトを定義できる。
import typing import dataclasses @dataclasses.dataclass class MyConfig: foo: int bar: float baz: typing.List[str] config = MyConfig(foo=1, bar=2.0, baz=['3 and 4']) print(config.foo) # 1普通の
object
を継承したクラスの場合はコンストラクタを書く必要があるが、dataclass
の場合は必要ない。保存/ロードできる設定ファイル
設定ファイルはPythonファイルだけではなくてテキストファイルとして保持できると便利なことがある。今回は形式としてYAMLを使うことにする。
pip install pyyaml
をすればjson
パッケージと似たようにYAMLファイルを使った保存とロードができるようになる。上記の
dataclass
の設定オブジェクトをYAMLファイルとして保存し、なおかつロードできるようなクラスを作りたい。ついでにpathlib.Path
は設定によくある型なのでこれもYAMLにするときにちゃんと変換できるようにしたい。ということでYAML形式で保存/ロード可能な設定クラスをdataclassを使って作ってみた:
import pathlib import dataclasses import yaml import inspect @dataclasses.dataclass class YamlConfig: def save(self, config_path: pathlib.Path): """ Export config as YAML file """ assert config_path.parent.exists(), f'directory {config_path.parent} does not exist' def convert_dict(data): for key, val in data.items(): if isinstance(val, pathlib.Path): data[key] = str(val) if isinstance(val, dict): data[key] = convert_dict(val) return data with open(config_path, 'w') as f: yaml.dump(convert_dict(dataclasses.asdict(self)), f) @classmethod def load(cls, config_path: pathlib.Path): """ Load config from YAML file """ assert config_path.exists(), f'YAML config {config_path} does not exist' def convert_from_dict(parent_cls, data): for key, val in data.items(): child_class = parent_cls.__dataclass_fields__[key].type if child_class == pathlib.Path: data[key] = pathlib.Path(val) if inspect.isclass(child_class) and issubclass(child_class, YamlConfig): data[key] = child_class(**convert_from_dict(child_class, val)) return data with open(config_path) as f: config_data = yaml.full_load(f) # recursively convert config item to YamlConfig config_data = convert_from_dict(cls, config_data) return cls(**config_data)テストを含めたコードはGitHubのレポジトリにあげてある。
自分の設定オブジェクトを定義したいときはたとえば以下のようにすれば良い:
#import YamlConfig and typing @dataclasses.dataclass class MyConfig(YamlConfig): val_float: float val_list: typing.List[int] val_str: str
YamlConfig
を継承すればsave
とload
を使うことができる。# import MyConfig and pathlib config = MyConfig(val_float=1.0, val_list=[1,2], val_str='3') config.save(pathlib.Path('./my_config.yaml')すでにあるYAMLファイルから設定を読み込みたい場合は
# import MyConfig and pathlib config = MyConfig.load(pathlib.Path('./my_config.yaml'))とすればよい。
さらに設定の子設定を作るなどの階層構造を定義した場合も
save
とload
が動く。@dataclasses.dataclass class MySubSubConfig(YamlConfig): val_float: float val_list: typing.List[int] val_str: str @dataclasses.dataclass class MySubConfig(YamlConfig): val_int: int val_path: pathlib.Path sub_sub_config: MySubSubConfig @dataclasses.dataclass class MyConfig(YamlConfig): sub_config: MySubConfigこのような
MyConfig
のload
とsave
もできる。感想など
アトミックな型と
pathlib.Path
は対応しているが他の型(自作クラスなど)は使えない。またsave
とload
の実装はyaml.add_representer
やyaml.add_constructor
を使った方がよかったのかもしれない。
- 投稿日:2019-09-27T01:13:51+09:00
Docker-composeをDjangoにセットアップしてみた
はじめに
未来電子テクノロジー(https://www.miraidenshi-tech.jp/intern-content/program/)
でインターンをしているrayaと申します。
プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。今回の内容
DjangoにDocker-composeをセットアップします。
失敗談も載せています。
mac上でPostgreSQL11.5を使っています。Dockerとは
誰もが、どこでもスムーズにアプリケーションを開発、共有、動作できるようにするためのプラットフォームを提供しているサービスです。
1つのパソコンの中で、いくつものサーバーを動かせるようにする仮想化技術を用いることで、WindowsでもLinuxでも使いたい環境で作業ができます。
動作が軽いことと仮想環境の共有がしやすいという特徴があります。
参考:公式サイト(https://www.docker.com/why-docker)コンテナとイメージ
Dockerでは、コンテナという形で仮想環境などを共有します。
コンテナに付随してイメージという概念も押さえておきます。
イメージ:アプリケーションの基盤。アプリケーションのために必要な物を備えている
コンテナ:イメージの中でアプリケーションの実行機能を果たす
参考:公式サイト(https://docs.docker.com/get-started/)Quickstart
まずDjangoのプロジェクト内にファイルを作ります。
注意:プロジェクトと言ってもDjangoのインストールされた仮想環境ディレクトリが入っているだけのディレクトリです。
$django-admin startproject project_name .
で実際にプロジェクトは作らないでください。既にstartprojectコマンドで作成されたプロジェクト内で操作すると後々エラーします。(後述)プロジェクトディレクトリ直下にDockerfile、requirements.txtファイル、docker-compose.ymlファイルを作成し、Docker-composeを導入します。
最後にデータベースに接続すれば終了です。
参考:公式サイト(https://docs.docker.com/compose/django/)Dockerfile
Dockerfileはイメージの中身を定めています。
FROM python:3 ENV PYTHONUNBUFFERED 1 RUN mkdir /code WORKDIR /code COPY requirements.txt /code/ RUN pip install -r requirements.txt COPY . /code/このDockerfileは親となるPython3のイメージによって動きます。
そのイメージを適切な形に定めているのが、requirements.txtファイルです。
ここに記述のあるRUN pip install -r requirements.txt
によって、requirements.txtファイルが動きます。requirements.txt
Django>=2.0,<3.0 psycopg2>=2.7,<3.0docker-compose.yml
docker-compose.ymlファイルは、このアプリケーションを作る際に利用するサービスを定めます。
今回は、db
とweb
サービスを使用します。
どんなDockerイメージを使うのか、volume、ポート番号が定められています。version: '3' services: db: image: postgres web: build: . command: python manage.py runserver 0.0.0.0:8000 volumes: - .:/code ports: - "8000:8000" depends_on: - db上記の過程でDjangoにDocker-composeを導入する準備が整いました。
プロジェクト作成(失敗談込み)
プロジェクトディレクトリで以下のコマンドを実行します。
このコマンドはDocker-composeにcomposeexample
というプロジェクトをコンテナ上で動かすように指示しています。
このコマンドを実行する際には、docker-compose.ymlファイルで定めたweb
サービスイメージが作られます。
Docker-composeはそのイメージを使ってプロジェクトディレクトリやその他のファイルを作ります。$ sudo docker-compose run web django-admin startproject composeexample .私は、最初にDocker-composeをセットアップする際に、既存のDjangoプロジェクトディレクトリ(startprojectコマンドでプロジェクトを作成済みのディレクトリ)でここまでの操作を実行しました。
すると、以下のエラーが出ました。CommandError: /code/manage.py already exists, overlaying a project or app into an existing directory won't replace conflicting files既にmanage.pyファイルがあるからプロジェクトを作れないよというエラーと思われます。
このエラーが出たので、先ほどの注意点を挙げています。
ということで、新規に仮想環境ディレクトリのみが入ったディレクトリを作成し、そのディレクトリ上で再び、Dockerfile、requirements.txtファイル、docker-compose.ymlファイルを作成した後に、docker-composeコマンドを実行しました。
無事エラーは発生せずに次のステップに進めました。データベース接続
Djangoは、デフォルトではsqliteを使用しているので、PostgreSQLに設定します。
プロジェクト直下のsettings.pyファイルの内容を書き換えます。DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'postgres', 'USER': 'postgres', 'HOST': 'db', 'PORT': 5432, } }あとは、プロジェクトディレクトリの一番上の階層で、以下のコマンドを実行し、ブラウザで
http://localhost:8000
にアクセスします。
Welcomeページが表示されていれば成功です。$ docker-compose up
- 投稿日:2019-09-27T01:13:35+09:00
Project Euler 028を解いてみる。「螺旋状に並んだ数の対角線」
Project Euler 028
1から初めて右方向に進み時計回りに数字を増やしていき, 5×5の螺旋が以下のように生成される:
両対角線上の数字の合計は101であることが確かめられる.
1001×1001の螺旋を同じ方法で生成したとき, 対角線上の数字の和はいくつか?->[次の問題]
考え方
当直で呼ばれ、時間も元気もなくなったので定番のゴリ押しで行きます(^q^)
最も内側の四角形は3,5,7,9と2ずつ増え、その1つ外側では13,17,21,25と4ずつ増えます。外周が1つ外側に行くと辺の長さは2ずつ増えるので、それぞれの角の差も2ずつ増えていきます。
これをコードにして1001まで回します。コード
euler028.pydef main(): num = 1 # 対角線上の数 answer = 1 # 答え用の変数 for i in range(2, 1001, 2): # iは2ずつ増える for _ in range(4): num += i # 対角線の数を計算し、 answer += num # 合計に足す print(answer) if __name__ == '__main__': from time import time as t st = t() main() print(t() - st, 'sec')paizaにて実行
結果
669171001
0.0002090930938720703 sec
- 投稿日:2019-09-27T00:23:11+09:00
メモ「Automate the Boring stuff -chapter1 Python Basics」
https://automatetheboringstuff.com/chapter1/
Data type Examples Integers -2, -1, 0, 1, 2, 3, 4, 5 Floating-point numbers -1.25, -1.0, --0.5, 0.0, 0.5, 1.0, 1.25 Strings 'a', 'aa', 'aaa', 'Hello!', '11 cats'Operator Operation Example Evaluates to... ** Exponent 2 ** 3 8 % Modulus/remainder 22 % 8 6 // Integer division/floored quotient 22 // 8 2 / Division 22 / 8 2.75 * Multiplication 3 * 5 15 - Subtraction 5 - 2 3 + Addition 2 + 2 4https://www.youtube.com/watch?v=7qHMXu99d88
The str(), int(), and float() Functions
>>> str(0) '0' >>> str(-3.14) '-3.14' >>> int('42') 42 >>> int('-99') -99 >>> int(1.25) 1 >>> int(1.99) 1 >>> float('3.14') 3.14 >>> float(10) 10.0