- 投稿日:2021-07-15T23:56:29+09:00
残プロ 第-44回 ~論文.pdfを自動でリネーム~
自動で論文タイトルにリネームしたい 皆さんは,論文のpdfファイルをダウンロードした際,ファイル名が意味のない数字やアルファベットの羅列になった経験はありますでしょうか.おそらく,論文を集める際に全員が通る道だと思います. 今回はPDFMINERを使って,Pythonでpdfを解析し,自動でタイトルを取得します. なお,プログラムの作成に当たりこちらの記事をかなり参考にさせていただきました.先人って凄い! PDFRename.py import os import sys from pdfminer.pdfparser import PDFParser from pdfminer.pdfdocument import PDFDocument from pdfminer.pdfpage import PDFPage from pdfminer.pdfpage import PDFTextExtractionNotAllowed from pdfminer.pdfinterp import PDFResourceManager from pdfminer.pdfinterp import PDFPageInterpreter from pdfminer.converter import PDFPageAggregator from pdfminer.layout import LAParams, LTContainer, LTTextLine def get_objs(layout, results): if not isinstance(layout, LTContainer): return for obj in layout: if isinstance(obj, LTTextLine): results.append({'text': obj.get_text(), 'height': obj.height}) get_objs(obj, results) def get_title(path): with open(path, "rb") as f: parser = PDFParser(f) document = PDFDocument(parser) laparams = LAParams(all_texts=True) rsrcmgr = PDFResourceManager() device = PDFPageAggregator(rsrcmgr, laparams=laparams) interpreter = PDFPageInterpreter(rsrcmgr, device) page = next(PDFPage.create_pages(document)) #1ページ目 interpreter.process_page(page) layout = device.get_result() results = [] get_objs(layout, results) f.close() title = max(results, key=lambda x:x.get('height')) print(title.get('text')) return title.get('text').strip() def main(path): os.rename(path, get_title(path)+".pdf") if __name__ == '__main__': path = sys.argv[1] main(path) get_objs関数内で扱っているobjはheightやwidthも取得できます. このプログラムでは1ページ目にある一番heightが大きいオブジェクトをタイトルとしています.typeによる条件処理は行ってないのでたまに失敗します.興味ある方はぜひ挑戦してみてください!
- 投稿日:2021-07-15T22:53:46+09:00
スズメバチとアシナガバチを分類するアプリを作ってみた。
はじめに この記事はプログラミング初心者である私がAidemyのAIアプリ開発コースの最終成果物として、AIによる画像認識アプリを作成する過程を記録したものです。 どんなジャンルにしようか悩みましたが、タイトルにもあるように、画像をスズメバチかアシナガバチかを判別するアプリを作成することにしました。 テーマ選定の理由としては、私が以前に小学校で理科を教えていたことがあり、なんとなく自然科学に沿ったものを作りたいなと思ったのがきっかけでした。 その中でもこのテーマにしたのは、 日本に生息するハチの種類が少ない(植物やほかの動物に比べて)ため学習がしやすそう 普段の生活の中でよく目にすることがあるがパッと見での判断が一般の人には難しそう スズメバチとアシナガバチは色合いなどは似ているが、体の構造が少し違うため判別するのが難しいのではないか(作りがいがあるのでは?) 以上のような理由のもとに、テーマを決定しました。 目次 1.実行環境 2.icrawlerを利用した画像収集 3.CNNモデルの作成(テスト) 4.ImageDataGeneratorを使った画像の水増し 5.CNNモデルの作成 6.アプリの動作確認 7.考察・感想 1. 実行環境 python 3.8.5 MacBook Pro (Retina, 13-inch, Early 2015) Anaconda Google Colaboratory Visual Studio Code 2. icrawlerを利用した画像収集 まず、学習用の画像収集です。 今回はicrawlerを利用し画像を収集しました。 2.1 icrawlerとは icrawerとはpythonでwebクローリングを行い、画像を収集するためのフレームワークです。 利点としては非常に短いコードを記述するだけで画像を収集できることです。 2.2 インストール $ pip install icrawler 2.3 画像のダウンロード image_downloads.py from icrawler.builtin import BingImageCrawler root_dirs = ['スズメバチのパス', 'アシナガバチのパス'] keywords = ['スズメバチ', 'アシナガバチ'] for root_dir, keyword in zip(root_dirs, keywords): crawler = BingImageCrawler(storage={"root_dir": root_dir}) crawler.crawl(keyword=keyword, max_num=500) スズメバチとアシナガバチの画像を各500枚ずつ集めるようにしました。 集めたい画像のキーワードをスズメバチ、アシナガバチとしたため、巣や幼虫、おもちゃの画像も一緒にダウンロードされていました。 上記のような関係ない画像などを整理した結果、各240枚ほど残りました。 3. CNNモデルの作成(テスト) kerasを使ってモデルを作成します。 まずはじめにVGG16を転移学習して全結合層をカスタマイズした以下のようなモデルを作り様子を見ることにしました。(aidemyのテキストにあったものをそのまま使いました。) input_tensor = Input(shape=(50, 50, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # vggのoutputを受け取り、2クラス分類する層を定義 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dense(128, activation='relu')) top_model.add(Dropout(0.5)) top_model.add(Dense(2, activation='softmax')) # vggと、top_modelを連結 model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) # vggの層の重みを固定(14層まで) for layer in model.layers[:15]: layer.trainable = False # コンパイル model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy']) # 学習 history = model.fit(X_train, y_train, batch_size=32, epochs=50, validation_data=(X_test, y_test)) 最初の段階では、正解率は約65%くらいの精度でした。 このモデルの精度を上げるために以下のことを行いました。 画像データを増やす(画像の水増し) 入力画像のピクセルを調整 モデルの再構築(全結合層の見直し) 4. ImageDataGeneratorを使った画像の水増し KerasのImageDataGeneratorを使用して画像を水増ししていきます。 引数で指定できる項目は複数あり、いろいろ試した結果、今回は以下の5つを組み合わせて使いました。 shear_rangeを使わなかった理由としては画像が剪断されることにより、スズメバチとアシナガバチのお尻の太さの違いという特徴が損なわれてしまうのではないかと思ったからです。(実際にshear_rangeを使わない方が精度が高かったです) # 画像を水増しする関数 def img_augment(x, y): X_augment = [] y_augment = [] i = 0 # 水増しを20回繰り返す while i < 20: datagen = ImageDataGenerator(rotation_range=30, width_shift_range=0.3, height_shift_range=0.3, horizontal_flip = True, zoom_range = [0.9, 0.9]) datagen = datagen.flow(X_train, y_train, shuffle = False, batch_size = len(X_train)) X_augment.append(datagen.next()[0]) y_augment.append(datagen.next()[1]) i += 1 # numpy配列に変換 X_extend = np.array(X_augment).reshape(-1, img_size, img_size, 3) y_extend = np.array(y_augment).reshape(-1, 2) return X_extend, y_extend 上記のような水増しを行う関数を定義しました。 汎化性能低下防止のため、トレーニングデータにのみ水増し用の関数を使います。 5. CNNモデルの作成 画像の水増しをした後、入力ピクセルのを変更しながら精度を検証しました。 また、全結合層を見直し正解率の向上を試みました。 いろんなパラメータを変えながら検証する過程で以下のことがわかりました。 4つの画像サイズ50px, 100px, 150px, 200pxと比較したところ、200pxが一番精度が高かった。 epochは20くらいで正解率が横ばいになる。 batch_sizeを変更させてみた結果、最初に設定した32の時に少し正解率が高かった。 中間層の後にReLU関数、Dropout、BatchNormalization(バッチ正規化)層を追加することで正解率が上昇した。(Dropoutの割合は) 最終的なモデルの作成コードはこうなりました。 model.py import cv2 import os import numpy as np from keras.utils.np_utils import to_categorical from tensorflow.keras.layers import Dense, Dropout, Flatten, Input, BatchNormalization from tensorflow.keras.applications.vgg16 import VGG16 from tensorflow.keras.models import Model, Sequential from tensorflow.keras import optimizers from keras.preprocessing.image import ImageDataGenerator import matplotlib.pyplot as plt # ディレクトリ中のファイル名をリストとして格納 path_wasp = os.listdir('パス') path_paper_wasp = os.listdir('パス') # 変換した画像を格納する空リストを定義 img_wasp = [] img_paper_wasp = [] # 画像のサイズ img_size = 200 # スズメバチ for i in range(len(path_wasp)): # ディレクトリ内にある".DS_Store"というファイルを除くためif文を定義 if path_wasp[i].split('.')[1] == 'jpg': img = cv2.imread('パス' + path_wasp[i]) img = cv2.resize(img, (img_size, img_size)) img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) img_wasp.append(img) # アシナガバチ for i in range(len(path_paper_wasp)): if path_paper_wasp[i].split('.')[1] == 'jpg': img = cv2.imread('パス' + path_paper_wasp[i]) img = cv2.resize(img, (img_size, img_size)) img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) img_paper_wasp.append(img) # データを結合しnp配列に変換 X = np.array(img_wasp + img_paper_wasp) y = np.array([0]*len(img_wasp) + [1]*len(img_paper_wasp)) # 画像データをシャッフル rand_index = np.random.permutation(np.arange(len(X))) X = X[rand_index] y = y[rand_index] # トレーニングデータとテストデータに分ける(8:2) X_train = X[:int(len(X)*0.8)] y_train = y[:int(len(y)*0.8)] X_test = X[int(len(X)*0.8):] y_test = y[int(len(y)*0.8):] # ラベルデータをone-hotベクトルに変換 y_train = to_categorical(y_train) y_test = to_categorical(y_test) # 画像を水増しする関数 def img_augment(x, y): X_augment = [] y_augment = [] i = 0 # 水増しを20回繰り返す while i < 20: datagen = ImageDataGenerator(rotation_range=30, width_shift_range=0.3, height_shift_range=0.3, horizontal_flip = True, zoom_range = [0.9, 0.9]) datagen = datagen.flow(X_train, y_train, shuffle = False, batch_size = len(X_train)) X_augment.append(datagen.next()[0]) y_augment.append(datagen.next()[1]) i += 1 # numpy配列に変換 X_extend = np.array(X_augment).reshape(-1, img_size, img_size, 3) y_extend = np.array(y_augment).reshape(-1, 2) return X_extend, y_extend # trainデータの水増し img_add = img_augment(X_train, y_train) # 元の画像データと水増しデータを統合 X_train = np.concatenate([X_train, img_add[0]]) y_train = np.concatenate([y_train, img_add[1]]) # VGG16 input_tensor = Input(shape=(img_size, img_size, 3)) vgg16 = VGG16(include_top=False, weights='imagenet', input_tensor=input_tensor) # vggのoutputを受け取り、2クラス分類する層を定義 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation='relu')) top_model.add(Dropout(0.2)) top_model.add(BatchNormalization()) top_model.add(Dense(128, activation='relu')) top_model.add(Dropout(0.2)) top_model.add(BatchNormalization()) top_model.add(Dense(2, activation='softmax')) # vggと、top_modelを連結 model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) # vggの層の重みを固定 for layer in model.layers[:15]: layer.trainable = False # コンパイル model.compile(loss='categorical_crossentropy', optimizer=optimizers.SGD(lr=1e-4, momentum=0.9), metrics=['accuracy']) # 学習 history = model.fit(X_train, y_train, batch_size=16, epochs=20, validation_data=(X_test, y_test)) #resultsディレクトリを作成 result_dir = 'results' if not os.path.exists(result_dir): os.mkdir(result_dir) # 保存 model.save(os.path.join(result_dir, 'model.h5')) # ------------------------------------------------ # 可視化 plt.plot(history.history['accuracy']) plt.plot(history.history['val_accuracy']) plt.title('Model accuracy') plt.ylabel('Accuracy') plt.xlabel('Epoch') plt.grid() plt.legend(['Train', 'Validation'], loc='upper left') plt.show() img_size=100px(グラフのタイトルに画像サイズを入れるのを忘れていました...) img_size=150px img_size=200px 最終的な正解率は約85%くらいでした。 2分類クラスなため、もう少し精度は伸ばしたかったですが、それでも最初の正解率と比べたら悪くはないと思います。 このモデルを使い`herokuにデプロイしていきます。 6. アプリの動作確認 webページのHTML, CSSはAidemyの講座を参考に少しだけ手を加えました。 では、実際にモデル作成に使わなかった写真を判定していきましょう。 まず、セグロアシナガバチの写真 結果は 100%の確率でアシナガバチと判定されました。(正直この時、少しガッツポーズ) 続いてこちらのキアシナガバチの写真 結果は 先ほどよりも少し確率は下がりましたが、しっかりアシナガバチと判定してくれています。 次はスズメバチの写真を判定してもらいます。 まず、こちらのオオスズメバチの写真 結果は モデル作成時にこれと似たオオスズメバチを上から撮影した写真を使っていたので100%の確率で分類できたのだと思います。 続いてこちらの花の蜜を吸っている?モンスズメバチの写真 結果は こちらも正確に判定してくれました。 うまくいかなかった例としては、こちらのヨーロッパクロスズメバチの写真 結果は アシナガバチと間違って判定されてしまいました。 この理由の考察としては、モデルを作成する際に日本に生息するハチの写真のみを使いました。 つまり、モデル作成時に入力したスズメバチの写真の中にはヨーロッパクロスズメバチの写真は入っていませんでした。 なので、足が黄色いという特徴からアシナガバチと判定されてしまったのではないかと思います。 アプリのリンク スズメバチかアシナガバチかを判定するAIアプリ 7. 考察・感想 与えられた画像をスズメバチとアシナガバチの2つのクラスに分類するだけですが、正解率は約85%とある程度の高さを出すことができたと思います。 初期のモデルと最終的なモデルを比較した時、正解率が上がっていたのは画像データのサイズとデータの水増しが大きかったと思います。 手書き数字の判別の時に入力データを50pxにしていたので、それに倣ってそのままにしていたのですが、やはり元データが鮮明になるほど、ある程度の解像度を保ちながら変換しなければいけないと感じました。 また元データの量は50枚から初め、段々と増やしていき、最終的に240枚になりました。(枚数に特に意味はありません) 枚数が増えるにつれて正解率も上がっていったのをみて、元データの量が精度のいいモデルを作るためにとても重要になるとわかりました。 herokuへデプロイする時のファイルの最大容量が500MBであることや、Google ClabでのGPU使用制限などで今回はこのモデルを完成としました。 また別の機会に入力ピクセルや元データを増やしたり、水増しの回数を増やしてみた時にどういった違いが出てくるのかを確認してみようと思います。 最後にこれを執筆しているときにふと、画像のリサイズの方法に問題があるのではないのかと思いました。 アシナガバチの元データがこちら そして変換したデータがこちら(サイズは200px) 画像をそのまま圧縮しているため、少し横にぎゅっと潰されているのがわかります。 この結果、データの特徴が変化し正解率に何らかの影響を与えているのではないかと思いました。 つまり、リサイズを行う前に画像が正方形になるように上下左右の余白を追加する操作を行うことにし、このような関数を定義しました。 # 画像を正方形に変換する関数 def expand2square(img): background_color = (0,0,0) #黒 width, height = img.size if width == height: return img elif width > height: result = Image.new(img.mode, (width, width), background_color) result.paste(img, (0, (width - height) // 2)) return result else: result = Image.new(img.mode, (height, height), background_color) result.paste(img, ((height - width) // 2, 0)) return result この関数を使い正方形にした変換した後、リサイズを行った先ほどの写真がこちら 元の比率を保っているため特徴もしっかり捉え正解率に差が出るはず!と思い早速モデルを作成しました。(元のコードに関数を追加し、画像読み込み部分も少し変更しました。) その結果がこちら うーん...あまり変わってない。むしろ悪くなってる... この画像の縦横比を維持するという行いが、どのように結果に貢献するのかも今後の課題です。 最後に、講座のテキストを読んでいるうちは理解しているつもりでも、実際に自力で作るとなると このコードどうやって書くんだっけ? バッチ正規化ってどういう意味だっけ? などなど様々なところでつまづきました。 その課題を一つ一つ乗り越えていくうちにできないことができるようになっていきました。 やっぱり"作る"ということが一番勉強になることに気付かされました。 今回は単純にスズメバチ、アシナガバチと分類するAIアプリを作成しました。 ですが日本に生息するだけでスズメバチは17種、アシナガバチは11種もいます。 もちろん種類ごとに少しずつ違いがあり、今回のアプリがその違いを全て網羅しているか聞かれれば、そうとは言い切れません。 今後はスズメバチの中での分類やアシナガバチの中での分類を皮切りに、もっとたくさんのクラスに正確に分類したり、ほんの小さ違いでも正確に分類できるアプリを作成できるよう、知見を深めていこうと思います。
- 投稿日:2021-07-15T22:51:30+09:00
AIに必要な最低限の数学の知識を学習
今回の記事では、AIに必要な最低限の数学の知識について、Udemyの講座にて学習した内容を記載する(参考文献参照) 目次 1.関数の描画 2.べき乗とネイピア数 3.シグモイド関数 1-関数の描画 numpyをインポート 数学の計算ライブラリが多く入っている 大量データの取り扱いに向いている import numpy as np linspace 値が並んだ配列を作成 範囲を指定し、その区間を標準で50に区切る 区切る数を変更する場合はもう一つ引数を追加する 主にx軸などの範囲を決めるときに使用する また、グラフを表示する際は matplotlib をインポートする import matplotlib.pyplot as plt ここで下記関数を定義するコードの例を示す(抜粋) $$y=2x+1$$ $$y=x^2-4$$ $$y=0.5x^3-6x$$ import numpy as np x = np.linspace(-4, 4) # x軸の範囲は-4~4とする y_1 = 2*x + 1 # 1次関数 y_2 = x**2 - 4 # 2次関数 y_3 = 0.5 * x**3 - 6*x # 3次関数 2-べき乗とネイピア数 ネイピア数eの数値は下記の通り $$e = 2.718281828459045...$$ numpyをインポートしていると、np.eでネイピア数を利用することができる import numpy as np print(np.e) ネイピア数のべき乗の性質 yの微小な変化/xの微小な変化(曲線の傾き)が元の関数に近づく 式の変形がしやすいため人工知能分野でよく使われる 下記コードを実行することにより、曲線の傾きが元の関数に近づくことが確認できる import numpy as np import matplotlib.pyplot as plt x = np.linspace(-2, 2) dx = 0.01 #微小な変化 e = np.e #ネイピア数 y_e = e**x y_de = (e**(x+dx) - e**x) / dx plt.plot(x, y_e, label="e^x") plt.plot(x, y_de, label="de") plt.xlabel("x_value",size=14) plt.ylabel("y_value",size=14) plt.legend() plt.show() 以下の式におけるnの値を大きくするとネイピア数の値に近づくことができる $$(1+\frac{1}{n})^n$$ def approach_napier(n): return (1 + 1/n)**n 3-シグモイド関数 シグモイド関数は下記の通りの式で表現され、入力(x)から0~1の範囲に変換する $$ y = \frac{1}{1+e^{-x}}$$ シグモイド関数の傾きを求めるすなわち微分すると、下記の通りもとのシグモイド関数に置き換えて表現できるためよく使われる $$ y{'} = y(1-y)$$ import numpy as np import matplotlib.pyplot as plt e = np.e #ネイピア数 # シグモイド関数 def sigmoid(x): y = 1 / (1 + e**-x) return y # シグモイド関数を微分 def df_sigmoid(x): d = sigmoid(x) * (1 - sigmoid(x)) return d #微小な変化 dx = 0.1 x = np.linspace(-8, 8) y_sig = sigmoid(x) # シグモイド関数の傾き y_d = (sigmoid(x+dx) - sigmoid(x)) / dx y_df = df_sigmoid(x) plt.plot(x, y_sig ,label = "sigmoid") plt.plot(x, y_d, label = "d_sigmoid") plt.plot(x, y_df, label = "df_sigmoid") plt.legend() plt.xlabel("x",size=14) plt.ylabel("y",size=14) plt.grid() plt.show() 参考文献 みんなのAI講座 ゼロからPythonで学ぶ人工知能と機械学習 【2021年最新版】
- 投稿日:2021-07-15T20:57:40+09:00
Pythonしか知らない人がGoに入門した時モジュール管理がわからんかった
注意 この記事は 2021/03/16 にGoを始めたときに書いた記事です。 あとからもうちょい修正しようと思って寝かしてたんですが、結局直さなかったので 途中までになりますが、載せておきます。 Goはじめたい理由 噂によるとGoは動作が速いらしいので、PythonのFlaskで書いてたAPIをGoのEchoで置き換えたい。 大体それだけが理由です。() 用意したもの Windows 10 - Macは持っていないのでとりあえず Python 3.8.6 windows/amd64 - いつもよく使っているPythonくん - 公式サイトからインストーラをダウンロードして実行しただけ Go 1.15.6 windows/amd64 - 割と最近入れたGoくん - 公式サイトからインストーラをダウンロードして実行しただけ VSCode - メインで使っているエディタ まずはじめにしたこと Go 入門 でググってみる。 すると Go言語の初心者が見ると幸せになれる場所 が出てきたので 各ページを若干読んでみることとした。 この中だと、友人いわく A Tour of Goがおすすめとのことらしいが、とりあえず何かしら作ってみないと構文を覚えられる気がしないので、ハローワールド、BMI、ガチャを作ってみることにした。 VSCodeを設定しよう VSCode → 拡張機能タブ → Goと検索 → 一番上に出てくる拡張機能をインストールする。 その時点で Goが綺麗に表示されるようになるはず。 よくわからないがおすすめっぽいので、Go言語をVSCodeで開発する環境をインストールするを読んで、拡張機能の依存関係もインストールした。 Helloworldをしよう まぁこれは一瞬でできる。見た通りだよなって感じだったので特に苦労していない。 main.go package main import ( "fmt" ) func main() { fmt.Println("Hello world!") } このファイルをVSCodeで作って保存する。 そして、F5キーを押すと実行されて Hello world! と出るはず。 Hello world! という文字を 別モジュールから読み出したい ハローワールドはわかったが、よくあるやつすぎて面白くないので、 Hello world! って文字列を 別ファイルから読み出したくなった。 Pythonではpip Goでは Go modules が定番? チュートリアルの一部では GOPATH がどうこうという話が出てきていたが、 最近(Go 1.11以降)では Go modules という依存関係管理ツールが使われることが多いらしい。Go言語の依存モジュール管理ツール Modules の使い方を読んで、やってみようとした。 とりあえずまず、コマンドプロンプトで go env -w GO111MODULE=onする。 次に main.goと同じフォルダ内で、 fixed_valueというフォルダを作り、 go mod init fixed_value してみた。 すると、go.mod というファイルができて、モジュールとして扱ってくれるようになる。 最後にパッケージとして fixed_value/fixed_value.go package fixed_value var Message string = "Hello world" っていうのを作ってみた。 これをmain.goで読み出したいので main.go package main import ( "fmt" "./fixed_value" ) func main() { fmt.Println(fixed_value.Message) } Pythonの__init__.pyみたいには動かない で、実行してみると build command-line-arguments: cannot find module for path _/D_/Documents/Gits/go-practice/helloworld/fixed_value と怒られてしまった。 どうやら Pythonの __init__.py のつもりで パッケージフォルダ内に置いても動かないようだった。 (VSCodeのlintが起きてる場合はそもそも保存させてくれなくて実行できない) じゃあどうするの? 相対インポートというものは、許されざる悪みたいな立ち位置にいるらしく、絶対インポートしないとダメらしい。 絶対インポートと言っても、フォルダのパスではなく、リポジトリのパスを入れるってことらしい。 リポジトリを使う絶対インポート?? まずさっき 作った fixed_value/go.mod は削除する。その上で 直下に改めて go.modを作成する。 その際には、 go mod init github.com/Dosugamea/go-practice/helloworld というようにリポジトリ名(+必要ならフォルダパス) を付ける。 これで go.modを作った。じゃあどうやって main.goからfixed_valueをインポートをするのかというと以下のようにする。 main.go package main import ( "fmt" "github.com/Dosugamea/go-practice/helloworld/fixed_value" ) func main() { fmt.Println(fixed_value.Message) } これでいざできたと思って実行しようとすると、実は実行できない。先に go.mod と fixed_value を コミットして リポジトリにプッシュした後で初めて main.goが実行できるようになるようだ。 go.modの中で Replaceという細工をして、相対インポートをさせる方法もあるが、リポジトリにプッシュしてしまうのが一番丸いらしい。 これでなんとなく Goでのモジュールの扱い方がわかった。gg。
- 投稿日:2021-07-15T20:32:45+09:00
KerasとPyTorchで畳み込みニューラルネットワーク(CNN)を実装して比較する
Pythonの2大深層学習ライブラリKerasとPyTorchで畳み込みニューラルネットワークを実装し、CIFAR-10というデータセットを画像分類して比較してみたいと思います。 (※この記事は勉強した内容のアウトプットを目的としてざっくりと書かれているので、もともと知っていて復習の為に読んだりするにはいいと思いますが、正しく知りたい方は別の記事を読んだほうが良いとおもわれます。コードに関しては必要なライブラリが入っていれば、jupyterなどにコピペでだいたい動くはずです。) 畳み込みニューラルネットワーク(CNN) CNNは主に3つの層からで構成されています。入力画像に対しては、畳み込み層、プーリング層によって特徴の抽出を行います。最後に全結合層を経て出力を行います。それ以外にも過学習を抑制するバッチ正規化層、ドロップアウト層などがあります。 CIFAR-10 CIFAR-10は、主に画像認識を目的としたディープラーニング/機械学習の研究や初心者向けチュートリアルで使われているデータセットです。 CIFAR-10データセット全体は、 ・5万枚の訓練データ用(画像とラベル) ・1万枚のテストデータ用(画像とラベル) ・合計6万枚 で構成されています。 各画像のフォーマットは24bit RGBフルカラー画像:RGB(赤色/緑色/青色)3色の組み合わせで、それぞれ「0」~「255」の256段階、幅32×高さ32ピクセル: 1つ分のデータが基本的に(3, 32, 32)もしくは(32, 32, 3)(=計3072要素)という多次元配列の形状となっており、最初もしくは最後の次元にある3要素がRGB値となっています。 作成する畳み込みニューラルネットワーク Kerasによる実装 ライブラリのインポート import numpy as np from keras.models import Sequential from keras.layers import Conv2D, MaxPool2D, Dense, Dropout, Flatten from keras.layers.normalization import BatchNormalization from keras.datasets import cifar10 from keras.utils.np_utils import to_categorical CIFER10の読み込み(および学習データの標準化と正解ラベルのone-hotエンコーディング) (x_train, y_train),(x_test, y_test) = cifar10.load_data() x_train = x_train.astype('float32') / 255.0 x_test = x_test.astype('float32') / 255.0 y_train = to_categorical(y_train, 10) y_test = to_categorical(y_test, 10) 学習モデルの作成 model = Sequential() model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='relu', input_shape=(32, 32, 3))) model.add(Conv2D(filters=32, kernel_size=(3, 3), strides=(1, 1), activation='relu')) model.add(BatchNormalization()) model.add(MaxPool2D(pool_size=(2, 2))) model.add(Dropout(0.25)) model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), padding='same', activation='relu')) model.add(Conv2D(filters=64, kernel_size=(3, 3), strides=(1, 1), activation='relu')) model.add(BatchNormalization()) model.add(MaxPool2D(pool_size=(2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(512, activation='relu')) model.add(Dropout(0.5)) model.add(Dense(10, activation='softmax')) model.summary() 出力結果 Model: "sequential" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= conv2d (Conv2D) (None, 32, 32, 32) 896 _________________________________________________________________ conv2d_1 (Conv2D) (None, 30, 30, 32) 9248 _________________________________________________________________ batch_normalization (BatchNo (None, 30, 30, 32) 128 _________________________________________________________________ max_pooling2d (MaxPooling2D) (None, 15, 15, 32) 0 _________________________________________________________________ dropout (Dropout) (None, 15, 15, 32) 0 _________________________________________________________________ conv2d_2 (Conv2D) (None, 15, 15, 64) 18496 _________________________________________________________________ conv2d_3 (Conv2D) (None, 13, 13, 64) 36928 _________________________________________________________________ batch_normalization_1 (Batch (None, 13, 13, 64) 256 _________________________________________________________________ max_pooling2d_1 (MaxPooling2 (None, 6, 6, 64) 0 _________________________________________________________________ dropout_1 (Dropout) (None, 6, 6, 64) 0 _________________________________________________________________ flatten (Flatten) (None, 2304) 0 _________________________________________________________________ dense (Dense) (None, 512) 1180160 _________________________________________________________________ dropout_2 (Dropout) (None, 512) 0 _________________________________________________________________ dense_1 (Dense) (None, 10) 5130 ================================================================= Total params: 1,251,242 Trainable params: 1,251,050 Non-trainable params: 192 _________________________________________________________________ 学習と結果の表示 model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy']) model.fit(x=x_train, y=y_train, batch_size=64, epochs=10, validation_data=(x_test, y_test)) 出力結果 Epoch 1/10 782/782 [==============================] - 155s 198ms/step - loss: 1.6193 - accuracy: 0.4317 - val_loss: 1.2944 - val_accuracy: 0.5414 Epoch 2/10 782/782 [==============================] - 154s 197ms/step - loss: 1.2168 - accuracy: 0.5705 - val_loss: 1.0971 - val_accuracy: 0.6140 Epoch 3/10 782/782 [==============================] - 154s 196ms/step - loss: 1.0295 - accuracy: 0.6410 - val_loss: 0.9070 - val_accuracy: 0.6951 Epoch 4/10 782/782 [==============================] - 145s 185ms/step - loss: 0.9069 - accuracy: 0.6856 - val_loss: 0.8581 - val_accuracy: 0.6995 Epoch 5/10 782/782 [==============================] - 149s 191ms/step - loss: 0.8238 - accuracy: 0.7144 - val_loss: 0.8277 - val_accuracy: 0.7170 Epoch 6/10 782/782 [==============================] - 151s 193ms/step - loss: 0.7618 - accuracy: 0.7331 - val_loss: 0.7847 - val_accuracy: 0.7326 Epoch 7/10 782/782 [==============================] - 149s 191ms/step - loss: 0.7099 - accuracy: 0.7513 - val_loss: 0.7822 - val_accuracy: 0.7399 Epoch 8/10 782/782 [==============================] - 152s 194ms/step - loss: 0.6700 - accuracy: 0.7665 - val_loss: 0.6998 - val_accuracy: 0.7591 Epoch 9/10 782/782 [==============================] - 157s 201ms/step - loss: 0.6261 - accuracy: 0.7820 - val_loss: 0.7198 - val_accuracy: 0.7592 Epoch 10/10 782/782 [==============================] - 151s 193ms/step - loss: 0.5909 - accuracy: 0.7940 - val_loss: 0.7151 - val_accuracy: 0.7684 313/313 [==============================] - 7s 21ms/step - loss: 0.7151 - accuracy: 0.7684 PyTorchによる実装 ライブラリのインポート from torchvision.datasets import CIFAR10 import torchvision.transforms as transforms from torch.utils.data import DataLoader import numpy as np import matplotlib.pyplot as plt from torchsummary import summary import torch.nn as nn from torch import optim データセットの読み込みとイテレータの作成 normalize = transforms.Normalize((0.0, 0.0, 0.0), (1.0, 1.0, 1.0)) to_tensor = transforms.ToTensor() transform_train = transforms.Compose([to_tensor, normalize]) transform_test = transforms.Compose([to_tensor, normalize]) cifar10_train = CIFAR10("./data", train=True, download=True, transform=transform_train) cifar10_test = CIFAR10("./data", train=False, download=True, transform=transform_test) batch_size = 64 train_loader = DataLoader(cifar10_train, batch_size=batch_size, shuffle=True) test_loader = DataLoader(cifar10_test, batch_size=len(cifar10_test), shuffle=False) モデルの作成 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv3_32 = nn.Conv2d(3, 32, 3, padding=(1,1), padding_mode='replicate') self.conv32_32 = nn.Conv2d(32, 32, 3) self.conv32_64 = nn.Conv2d(32, 64, 3, padding=(1,1), padding_mode='replicate') self.conv64_64 = nn.Conv2d(64, 64, 3) self.pool = nn.MaxPool2d((2, 2)) self.dropout025 = nn.Dropout(0.25) self.dropout050 = nn.Dropout(0.50) self.bn32 = nn.BatchNorm2d(32) self.bn64 = nn.BatchNorm2d(64) self.fc1 = nn.Linear(64*6*6, 512) self.fc2 = nn.Linear(512, 10) self.relu = nn.ReLU() def forward(self, x): x = self.relu(self.conv3_32(x)) x = self.relu(self.conv32_32(x)) x = self.bn32(x) x = self.pool(x) x = self.dropout025(x) x = self.relu(self.conv32_64(x)) x = self.relu(self.conv64_64(x)) x = self.bn64(x) x = self.pool(x) x = self.dropout025(x) x = x.view(-1, 64*6*6) x = self.fc1(x) x = self.relu(x) x = self.dropout050(x) x = self.fc2(x) return x net = Net() summary(net,(3,32,32)) 出力結果 ---------------------------------------------------------------- Layer (type) Output Shape Param # ================================================================ Conv2d-1 [-1, 32, 32, 32] 896 ReLU-2 [-1, 32, 32, 32] 0 Conv2d-3 [-1, 32, 30, 30] 9,248 ReLU-4 [-1, 32, 30, 30] 0 BatchNorm2d-5 [-1, 32, 30, 30] 64 MaxPool2d-6 [-1, 32, 15, 15] 0 Dropout-7 [-1, 32, 15, 15] 0 Conv2d-8 [-1, 64, 15, 15] 18,496 ReLU-9 [-1, 64, 15, 15] 0 Conv2d-10 [-1, 64, 13, 13] 36,928 ReLU-11 [-1, 64, 13, 13] 0 BatchNorm2d-12 [-1, 64, 13, 13] 128 MaxPool2d-13 [-1, 64, 6, 6] 0 Dropout-14 [-1, 64, 6, 6] 0 Linear-15 [-1, 512] 1,180,160 ReLU-16 [-1, 512] 0 Dropout-17 [-1, 512] 0 Linear-18 [-1, 10] 5,130 ================================================================ Total params: 1,251,050 Trainable params: 1,251,050 Non-trainable params: 0 学習と結果の表示 loss_func = nn.CrossEntropyLoss() optimizer = optim.Adam(net.parameters()) x_test, t_test = iter(test_loader).next() for i in range(10): net.train() loss_train = 0 correct = 0 total = 0 for j, (x, t) in enumerate(train_loader): y = net(x) loss = loss_func(y, t) loss_train += loss.item() optimizer.zero_grad() loss.backward() optimizer.step() loss_train /= j + 1 net.eval() y_test = net(x_test) loss_test = loss_func(y_test, t_test).item() correct += (y_test.argmax(1) == t_test).sum().item() total += len(x_test) acc_test = correct/total print("Epoch:", i, "Loss_Train:", loss_train, "Loss_Test:", loss_test, "acc_test:", acc_test) 出力結果 Epoch: 0 Loss_Train: 1.4037733696153403 Loss_Test: 1.14847993850708 acc_test: 0.6034 Epoch: 1 Loss_Train: 1.0341440180835821 Loss_Test: 0.9750216603279114 acc_test: 0.6674 Epoch: 2 Loss_Train: 0.8731644713055448 Loss_Test: 0.9409785270690918 acc_test: 0.6836 Epoch: 3 Loss_Train: 0.7814018539226878 Loss_Test: 0.9034271240234375 acc_test: 0.708 Epoch: 4 Loss_Train: 0.7162514436046791 Loss_Test: 0.7007519006729126 acc_test: 0.7612 Epoch: 5 Loss_Train: 0.6576254427661676 Loss_Test: 0.6346898078918457 acc_test: 0.781 Epoch: 6 Loss_Train: 0.6264099999690604 Loss_Test: 0.6972277164459229 acc_test: 0.7689 Epoch: 7 Loss_Train: 0.5791886984692205 Loss_Test: 0.6604976654052734 acc_test: 0.781 Epoch: 8 Loss_Train: 0.5507333390319439 Loss_Test: 0.6652767062187195 acc_test: 0.7801 Epoch: 9 Loss_Train: 0.5095748831434628 Loss_Test: 0.666159987449646 acc_test: 0.7781 まとめ KerasとPytorchによる畳込みニューラルネットワークの実装を行ってみました。おそらくどちらも10エポックで精度80%弱程度で収束していると思います。 両方で実装した感想としては、ややKerasの方がコード量が少なく、学習するだけで経過も表示してくれる分、初心者にも優しいかなと思いました。あと、Kerasでは過学習対策にEarly Stoppingを設定していたのですが、Pytorchで同様の設定がなく自分で設定する必要があったので、揃える為にKeras側でもその部分は削除しました。このようにKerasでは何もしなくてもいろいろやってくれますが、Pytorchではそれらを実装する必要がありそうです。ただ、Pytorchではその分、自分でいろいろカスタマイズできるのではないかと思っています。
- 投稿日:2021-07-15T19:47:34+09:00
[Python]PDFから表を抽出し、CSV/Excel保存・グラフ描画する方法 メモ
Pythonライブラリtabula-pyを利用し、PDFから表を抽出し、CSVやExcel形式で保存する方法やグラフ描画する方法についてメモする※n番煎じ記事 事前準備 tabula-pyインストール pip install tabula-py ※Java1.8以上の環境で動作する ※その他pandasなどのライブラリも必要に応じてインストールする。 実装 経済指標情報が記載されているPDFファイルから失業率の表を抽出。 抽出結果をCSV,Excelファイルとして保存。 抽出結果をグラフ化する。 test.py import tabula import pandas as pd import matplotlib.pyplot as plt # テストPDFデータ:主要経済指標/2.2 失業率 pdf_path = 'https://www.mofa.go.jp/mofaj/files/100053858.pdf' # PDFから表をDataFrame型のListで抽出 # ※全頁の表を抽出したい場合は、pages='allを指定する dfs = tabula.read_pdf(pdf_path, stream=True, pages=9) ### CSV保存 ### dfs[0].to_csv('失業率.csv', index=None) ### Excel保存 ### dfs[0].to_excel('失業率.xlsx', index=None) ### グラフ描画 ### # 1. データ整形 # 日米独の2016~2020年のデータを英語表記にして抽出 target = dfs[0].iloc[0:5, 0:4].rename(columns={'Unnamed: 0': '', '日': 'Japan', '米': 'USA', '独': 'Germany'}, index={0: '2016', 1: '2017', 2: '2018', 3: '2019', 4: '2020'}) target = target.drop('', axis=1) print(target) # 2. グラフ描画 target.plot(marker="o") plt.title('Unemployment Rate') plt.xlabel('Year') plt.ylabel('Rate') plt.show() 実行 python test.py Japan USA Germany 2016 3.1 4.9 4.1 2017 2.8 4.4 3.8 2018 2.4 3.9 3.4 2019 2.4 3.7 3.1 2020 2.8 8.1 3.8 参考情報 chezou/tabula-py 主要経済指標※テストデータ
- 投稿日:2021-07-15T18:41:45+09:00
foliumで「重ねるハザードマップ」を重ねる
foliumで国土地理院のサイトで提供されている、タイルの情報を地図の上に重ねる方法を紹介します。 調べてみると、ベースマップのタイルを変える方法は見付かるのですが、別のレイヤーをオーバーレイする方法は見つからなかったので。 foliumの細かい話は省略です。 folium.raster_layers.TileLayer タイルを重ねて表示するにはfolium.raster_layers.TileLayerというクラスを使います。 具体的にはこんな感じです。 fmap1 = folium.Map( location=[35.1158021, 139.0839284], tiles = "OpenStreetMap", zoom_start = 15, width = 800, height = 800 ) folium.raster_layers.TileLayer( tiles='https://disaportaldata.gsi.go.jp/raster/05_dosekiryukeikaikuiki/{z}/{x}/{y}.png', fmt='image/png', attr="hogehoge", tms=False, overlay=True, control=True, opacity=0.7 ).add_to(fmap1) folium.LayerControl().add_to(fmap1) パラメータでハマりそうなところをいくつか書いておきます。 tiles ハマる、というほどではないんですが、似たようなことが出きるWmsTileLayerでは、urlという引数になっています。 attr 例ではhogehogeとしていますが、右下に出る権利表示です。 このパラメータが空だと動かないんですが、エラーを見た時にattributeのことだと思うと混乱します。 特に外に公開する場合などはhogehogeとかせずちゃんと書きましょう。 tms タイルの座標を決める方式のようです。 とりあえず、両方試して正しそうな方を選ぶのがよいかと。
- 投稿日:2021-07-15T18:05:17+09:00
独学プログラマーのまとめ その3
初めに 本章からより重要度があがった気がする。 4章 関数 関数は1つのことをすべきで、そのことに特化するべき。 関数を呼び出すとは、その関数が必要とする入力値(引数)を渡し、命令を実行し、出力値を返すこと。 関数定義はdefで行いここでも:(コロン)が大事になってくる。 # def [関数名]([引数]): # [〜関数定義〜] def f(x): return x * 2 組み込み関数 最初から用意されている関数。前回もprint関数を用いていた。 len,str,int,float,などがあり、使いながら覚えるとよい。 必須引数とオプション引数がある。必須引数がないとエラーとなる。 スコープ 変数を読み書きできる範囲のこと。グローバル変数、ローカル変数。 例外処理 tryとexceptで例外処理が行える。 a = input("type a number:") b = input("type another:") a = int(a) b = int(b) try: print(a / b) except ZeroDivisionError: print("b cannot be zero.") まとめ 例外処理を覚える
- 投稿日:2021-07-15T17:35:19+09:00
Pythonで簡単なGUIアプリケーションを作る
どんな記事? Pythonのライブラリを使ってGUIアプリを作った Excelファイルの読み込みやDB(MySQL)への接続などを行った はじめに 社外製の業務システムを運用することになりました ネットワークを介してデータの通信を行うWebアプリケーションではなく、ローカルPCにDB(MySQL)を用意して接続・利用するスタンドアロン型のシステムです しかしこのシステム、元々社外での利用を想定していない状況で作られたアプリケーションなので、 開発者でない人が使用することを想定していません よって、利用するパラメータやしきい値の設定は直接DBをいじって変更するしかないようです またテーブル構成の仕様上、同じ入力値を何回も入力する必要があったりする部分が多々あります というか、そもそも部署内の方は非エンジニアの人が多く、SQLクエリを記述してデータ更新~といった操作は難しい... -> 非エンジニアでもDB内のパラメータを簡単に操作できるツールとか作れる? 要は「入力しやすいインタフェースを作る」って感じです という経緯で、GUIアプリケーションを作ることになりました 自身の知識の定着も兼ねて、記事として残しておきます 機能概要 運用イメージ 入力シートとしてExcelファイルを作成、このファイルにパラメータ等を入力する 1で作成したシートを元にCSVを生成 2で作成したCSVをDBにインポート という流れで利用するツールとして作成していこうと思います 別にCSV生成を挟まなくても実現できそうなのですが、部署内から「一度入力した設定が上書きされて復元不可能になるのが怖い」との意見が出たので、バックアップ的な役割としてワンクッション置いておきます 2つの機能 入力シートのフォーマットは別として、今回作成するアプリケーションには以下の機能が最低限必要です (パラメータ等を入力した)Excelファイルを元にしてCSVファイル生成 生成したCSVファイルをDBにインポート できたもの 動作例はこんな感じ 構成 使用したツール・ライブラリなど 使用言語はPython DBとの接続(インポート機能)にはmysql.connectorライブラリを使用 Excelファイルへの値の読み書きはopenpyxlライブラリを使用 コマンドプロンプトで操作するのは不便なのでGUI作成にPySimpleGUIライブラリを使用 python実行環境がないPCでも使えるようパッケージング(exe化)を行う。pyinstallerライブラリを使用 動作環境 Python 3.6.1 openpyxl 3.0.5 PySimpleGUI 4.34.0 pyinstaller 4.0 MySQL 5.7.19 ソースコード 以下、ソースコード import openpyxl as ex import PySimpleGUI as sg import os import csv import datetime import mysql.connector as mydb class Person: def __init__(self, name: str, mail_address:str, age: str) -> None: self.name = name self.mail_address = mail_address self.age = age def toList(self): return [ self.name, self.mail_address, self.age ] class Persons: # ExcelファイルからデータをLoad def __init__(self, inputsheet_name: str) -> None: self.inputsheet_name = inputsheet_name # データ元のファイル名 self.persons = [] wb = ex.load_workbook(f"./InputSheet/{inputsheet_name}") ws = wb["Sheet1"] for index in range(3, 9999): name = ws.cell(index, 3).value mail_address = ws.cell(index, 5).value age = ws.cell(index, 7).value if name is None: break self.persons.append(Person(name, mail_address, age)) # Personの属性名をリスト形式で出力 @staticmethod def attributeList(): return [ "name", "mail_address", "age" ] # 格納されているデータをすべてリスト表示 def print_all(self): print(self.attributeList()) for p in self.persons: print(p.toList()) return 0 # csvファイルに出力 def output_csv(self): # List形式でpersonsの情報を格納 out = [] out.append(self.attributeList()) for record in self.persons: out.append(record.toList()) # csv書き出し filename = f"persons_{datetime.datetime.now():%Y%m%d_%H%M%S}" csvFilePath = f"./csv/{filename}.csv" with open(csvFilePath, "w", encoding="utf-8") as f: writer = csv.writer( f, lineterminator="\n", escapechar=" " ) writer.writerows(out) print(f"Created file {csvFilePath}") return 0 # csvデータからDBにインポート def importCSVTable(csvfile_name): # *一部本来の記述から変更しています conn = mydb.connect( host="localhost", port=3306, user="sampleuser", password="*****", database="sample_db", allow_local_infile=True ) # DB操作用カーソル cur = conn.cursor() # 既存レコード全削除 table_name = "persons" cur.execute(f"TRUNCATE {table_name} ;") # csvファイルからレコード読み込み csvfile_path = f"./csv/{csvfile_name}" enclosed = "\"" # 囲み文字 terminated = "," # 区切り文字 lines = "\\r\\n" # 改行コード sql = f""" LOAD DATA LOCAL INFILE "{csvfile_path}" INTO TABLE {table_name} FIELDS TERMINATED BY '{terminated}' OPTIONALLY ENCLOSED BY '{enclosed}' LINES TERMINATED BY '{lines}' IGNORE 1 LINES ; """ cur.execute(sql) # DBに反映 conn.commit() conn.close() return 0 def Layout(): textbox_width = 50 textbox_height = 8 inputsheet_list = os.listdir("./InputSheet") tab1_layout = [ [sg.Text("InputSheet list")], [sg.Listbox( inputsheet_list, size=(textbox_width, textbox_height), key="inputsheet" )], [sg.Button("CREATE csv file", key="create_csv")] ] csv_list = os.listdir("./csv") tab2_layout = [ [sg.Text("csv list")], [sg.Listbox( csv_list, size=(textbox_width, textbox_height), key="select_csv", enable_events=True )], [sg.Button("IMPORT to database", key="import_csv")] ] layout = [ [sg.TabGroup( [[ sg.Tab("csv生成", tab1_layout), sg.Tab("DBへインポート", tab2_layout) ]], enable_events=True )], # Log出力 [sg.Text("Log")], [sg.Output( size=(textbox_width, textbox_height*0.5) )] ] return layout def run(): window = sg.Window( title="SampleApp", layout=Layout(), ) while True: event, values = window.read(timeout=10) if event is sg.WIN_CLOSED: break elif event=="create_csv" and len(values['inputsheet'])!=0: persons = Persons(inputsheet_name=values['inputsheet'][0]) # Personsインスタンス生成 persons.output_csv() # CSV形式で書き出し sg.popup("csvファイルへの書き出しが完了しました", title="") elif event=="import_csv" and len(values['select_csv'])!=0: csvfile_name = values['select_csv'][0] print(csvfile_name) importCSVTable(csvfile_name) sg.popup("DBへのインポートが完了しました", title="") if __name__ == "__main__": run() まとめ 「直接ではなくExcelファイルなどの入力でDBの中身を変更したい」という要望に対して pythonを用いて簡単なGUIアプリケーションを作成しました PySimpleGUI pythonではGUI作成ライブラリとしてtkinterが標準インストールされているのですが、今回使用したPySimpleGUIはそれと比べ直感的にコーディングでき、公式リファレンスが非常にわかりやすいのでとても使いやすかったです pyinstaller コマンドのみで簡単にexeファイルを生成でき、python実行環境が無いPCでもアプリケーションを動作させられるのも良かったです パッケージングライブラリはpyinstallerの他にもいくつかあるようですが、pyinstallerはその中でも非常に簡単に利用できるので採用しました ただ、生成したexeのファイルサイズが大きめだったり、起動時間が長くなったりするなどの欠点はあります openpyxl こちらに関しては別記事でも解説を行っています
- 投稿日:2021-07-15T15:01:21+09:00
JSONの全キーを再帰関数でツリー表示する(Python用)
JSONはとても便利なデータ形式ですが、構造の自由度が大きいのでその全体像を掴みづらい時があります。特に、辞書とリストが何層にも入り混じって複雑な木構造になっているやつは、目的のデータにたどり着くために何回もキーを指定する必要があります。全体のツリー構造とキーと代表値がわかれば便利なので、そのような関数を書きました。既にそのようなモジュールがあったらすいません。 とりあえずコードだけ先に def json_tree(data,indent=0): space = ' '*indent if type(data) == dict: for k in data.keys(): print('\n',space,k,end='') json_tree(data[k],indent+4) elif type(data) == list and len(data) > 0: json_tree(data[0],indent+4) else: print(' :',data,end='') return JSONとは Python流の解釈をすれば、「辞書またはリストまたはその他の値を含む「辞書またはリストまたはその他の値(以下無限ループ)」」です。つまり、「辞書またはリストまたはその他の値」をJSONと定義すれば、JSONとは再帰的に「JSONを含むJSON」と定義することができます。 まあ、そんな言葉遊びみたいなことは置いておいて、 辞書型でキー・バリューのペアを設定してもよい 辞書型をリストにしてデータベースみたいにしてもいい というルールで、階層的なデータをキーで指定しやすくしたものと考えることができます。 つまり、図示すると以下のようになります。 つまり、経路はどうあれ、最終的には必ず末端の「その他の値」に行き着きます。リスト型は通常、データベースのように同じキーを持つ辞書型が列挙されていることが想定されているので、その一行目を共通の辞書型として扱うことができます。 よって、 辞書型を経由した時点で、インデントを下げて、全キーに対し、キーを表示しそのバリューを解析 リスト型を経由した時点で、その一行目を解析 その他の値を経由した時点で、値を表示 という手順を繰り返せば、いい感じにキー・バリューペアのツリー構造を表示できそうです。 再帰関数として実装 下の図は、再帰関数を組むにあたって、上の流れをもう少し具体的に図示したものです。 一種の例外処理として、リストの長さが 0 だった時には、それをあたかも「空のリストという値」であるかのふるまうようにします。こうしないと、空のリストがあった時に、一行空けて無が出力されてしまうので。また、改行が行われる時は「新たなキーが検出された時」だけなので、それに気をつければ以下のような実装になります。 def json_tree(data,indent=0): space = ' '*indent if type(data) == dict: for k in data.keys(): print('\n',space,k,end='') json_tree(data[k],indent+4) elif type(data) == list and len(data) > 0: json_tree(data[0],indent+4) else: print(' :',data,end='') return ランダムに生成されたJSONに対して試してみると、以下のような結果が得られます。 _id : 60ef08e86b3a1582f0d06063 index : 0 guid : 076489f7-98a3-489e-807b-5e43fbd816a8 isActive : True balance : $2,974.36 picture : http://placehold.it/32x32 age : 22 eyeColor : blue name : Concetta Stewart gender : female company : BEZAL email : concettastewart@bezal.com phone : +1 (959) 448-2023 address : 656 Wortman Avenue, Interlochen, South Dakota, 9988 about : Officia ipsum veniam ex aute. # 長いので省略 registered : 2017-09-24T11:13:26 -09:00 latitude : 2.283491 longitude : 35.920546 tags : dolore friends id : 0 name : Johnston Rosales greeting : Hello, Concetta Stewart! You have 8 unread messages. favoriteFruit : strawberry 辞書型の中にリストを含むような場合(上のfriends)でもちゃんと木構造を検出できていることがわかります。 ついでに、あるゲームで使われているjsonデータを対象にした例。 _version : 2.2.0 _customData _time : 74 _BPMChanges : [] _bookmarks _time : 8 _name : 1A _events : [] _notes _time : 8.0625 _lineIndex : 1 _lineLayer : 0 _type : 0 _cutDirection : 1 _obstacles _time : 8 _lineIndex : 3 _type : 0 _duration : 4 _width : 1 _waypoints : [] 空のリストがあった時に、ちゃんとバリューとして[]が返されていることがわかります。
- 投稿日:2021-07-15T14:15:17+09:00
Google Colaboratoryでnysol_pythonをインストールして使ってみる。
nysol_pythonの知名度は低いですが、実はかなりデータの加工や集計などの処理に便利なのでnysol_pythonについて、具体的な利用方法などを紹介します。 nysol_pythonの特徴 nysol_pythonは、データ解析のためのオープンソースのPythonライブラリで、pandasと同様にデータの加工や集計を得意とします。そして、表形式(CSVファイル)のデータやリストを対象に簡単に高速に動作します。pandasはSeriesとDataFrameなどの型を意識する必要がありますが、nysol_pythonでは、型を意識する必要はなく、上から下にメソッドをつなげていくことで処理ができるのが特徴です。 もともとnysolはコマンドとして提供されていましたが (https://www.nysol.jp) 、2018年にPythonライブラリが公開され、Pythonでnysolコマンドと同じ機能が利用できるようになりました。 この記事の対象読者 nysol_pythonは知らんという方 ExcelやAccessを卒業したい方 手軽にビッグデータを処理したい方 pandasで処理するのが厳しいデータ量を扱っている方 この記事でわかること nysol_pythonのGoogle Colabへの導入方法 nysol_pythonの入出力 nysol_pythonの処理手順 実行環境 Pythonの実行環境が構築されているGoolge Colaboratory (以下、Colab)でnysol_pythonを利用します。 nysol_pythonの導入方法 nysol_pythonのgithubによると、以下の導入が必要とのこと。 + c++ compiler + boost C++ library + lib2xml Library 各OSでインストール方法は異なるので、Colabを利用します。 Colabでファイルを新規作成し、最初のセルに以下のコードを入力し実行しましょう。 コーヒーでも飲んで10分ほど待ちましょう。 継続してnysol_pyhtonを使う場合は、次に示すGoogle Driveへのインストール方法を先に読んでください。 # どうも少し古い !pip install nysol # 最新のソースをgithubからインストールする場合 (こちらがおすすめ) !pip install git+https://github.com/nysol/nysol_python.git ただし、この方法でインストールしたパッケージは、Colabの仮想マシンがリセットされると消えるので、これから何度も使う場合は、パッケージをGoogle Driveにインストールしましょう。 Google Driveへのインストール方法 次の方法は、Google Colabでpip installしたものを消えないようにする を参考にしました。 1. Google Driveにパッケージ保存用のディレクトリを作成 例として、Google DriveのColab Notebooks内に、MyPackageという名前で作成しました。 2. Google Driveのマウント 以下のコードをColabで実行し、Gogle Driveをマウントします。 from google.colab import drive drive.mount('/content/gdrive') 3. パスの設定。 パッケージの保存先をGoogle Driveにするためにパスを作成 pkPathSys = '/content/gdrive/MyDrive/Colab\ Notebooks/MyPackage' 4. nysol_pythonのインストール nysol_pythonをGoogle Driveのパスを指定してインストール。 # -t $pkPathでインストール先のパスを指定 !pip install nysol -t $pkPathSys # 最新のソースをgithubからインストールする場合(こちらがおすすめ) !pip install git+https://github.com/nysol/nysol_python.git -t $pkPathSys 5. system pathを通す Google Driveにインストールされたnysol_pythonを利用するためにシステムパスを設定します。 import sys pkPath = '/content/gdrive/MyDrive/Colab Notebooks/MyPackage' sys.path.append(pkPath) 次回以降の準備 Google Driveにインストールしたパッケージを利用するために、次回以降(仮想マシンがリセットされた場合)にGoogle Driveのマウントと、パス設定が必要になります。次回以降は以下のコードを実行します。 # Google Driveマウント from google.colab import drive drive.mount('/content/gdrive') # システムパスを通す import sys pkPath = '/content/gdrive/MyDrive/Colab Notebooks/MyPackage' sys.path.append(pkPath) nysol_pythonの利用 無事にインストールできていれば、以下のimport文でnysolパッケージがインポートできます。 エラーがでなければOKです。 import nysol.mcmd as nm 1. 利用データの準備 データのダウンロード nysol_pythonのドキュメントで示されている焼き肉データであるyakiniku_jp.csvをダウンロードします。次のコマンドをColabで実行すると、Colabのセッションストレージに保存されます。セッションが切断されるとストレージも削除されるため、その場合は再度ダウンロードが必要です。 # Colabから実行 !wget https://www.nysol.jp/nysol_python/_downloads/610138a8b5d1260d3bc540f9d4e20d60/yakiniku_j p.csv 焼き肉データはCSVファイルで、その項目名は以下のようになります。これは、nysol_pythonのデータセットを参考にしました。 項目名 型 内容 date 文字列 注文日付でyyyymmddの8桁固定長。470営業日。 time 文字列 注文時刻でhhmmssの6桁固定長。同じレシートの中で異なる時刻あり。 receipt 文字列 レシート番号で、同じ番号であれば同じテーブルでの注文。96789枚。 itemID 文字列 商品を唯一識別するID。 793種。 item 文字列 商品名 price 整数 単価 quantity 整数 注文数量 2. nysol_pythonの入出力 nysol_pythonでは入出力をi=とo=で指定できます。 CSV形式の入出力 CSV形式のファイルを読み込み、CSV形式でファイル出力します。そして、焼き肉データの中身を表示してみましょう。 入力をファイル指定する場合は、i="filename.csv" のように、ファイル名を文字列で与えます。また、出力ファイルも同様です。mread メソッドは、i=で指定された内容をそのまま出力するメソッドで、run()メソッドによって実行されます。 # 焼き肉データを読み込み、out1.csvに出力 nm.mread(i="yakiniku_jp.csv",o="out1.csv").run() # pメソッドでファイルを表形式に整形して表示 nm.p('out1.csv') CSV形式の入力、リスト出力 o=を指定せずに実行すると、2次元リストが出力されます。run()メソッドによって実行されたmreadの処理結果が2次元リストとして返ってくるため、それをsに代入しています。print文では先頭から5行を出力しています。 # 入力をCSVファイル、出力をリスト(o=の指定なし) s=nm.mread(i="yakiniku_jp.csv").run() print(s[0:5]) # 出力 5行のみ表示 [['20070701', '115200', '5589', '107', '和牛焼肉弁当', '1240', '1'], ['20070701', '122800', '5594', '102', '冷麺定食', '1130', '2'], ['20070701', '120800', '5595', '105', 'オリジナル3品盛り合わせ', '880', '1'], ['20070701', '120800', '5595', '107', '和牛焼肉弁当', '1240', '1'], ['20070701', '122600', '5596', '107', '和牛焼肉弁当', '1240', '1'], 3. 複数のメソッドを実行する 次に、焼き肉データを利用し、売上金額の合計を計算してみましょう。 まず、yakiniku_jp.csvからmcutメソッドでpriceを抜き出し、msumメソッドでpriceを合計します。 この処理フローオブジェクトがsに代入され、s.run()で処理フローオブジェクトが実行されます。 そして、その結果は2次元リストでsalesに代入されます。 # 売上金額の合計を計算してみましょう。 s=nm.mcut(f="price",i="yakiniku_jp.csv").msum(f="price") sales=s.run() print(sales) # 売上金額の合計 # [['91772640']] 処理フローオブジェクトに処理メソッドをそれぞれ追加して実行 複数の処理メソッドを処理フローオブジェクトに順次追加して実行する方法を示します。 上から下に順番に処理メソッドを記述する直感的な書き方です。 1行目でmcutメソッドが処理フローオブジェクトとしてsに代入されます。 次に、<<=演算子を利用することで、msumメソッドを処理フローオブジェクトsに順次追加します。 そして、s.run()で実行します。 処理メソッドが増えても同様に<<=演算子を利用して追加できます。 # 処理フローオブジェクトに追加 s=nm.mcut(f="price",i="yakiniku_jp.csv") s<<=nm.msum(f="price") sales=s.run() まとめ この記事では、Google Colabを利用してnysol_pythonのインストールとその実行方法を示しました。 nysol_pyhonの処理メソッドは80種類ほどあり、それらを処理フローオブジェクトに代入しながら処理が実行できます。 特に、型を意識する必要はなく、それぞれのメソッドを組み合わせていくことで柔軟な処理ができることが特徴です。 引き続きGoogle Colabでnysol_pythonの利用方法を紹介していきます。
- 投稿日:2021-07-15T13:47:03+09:00
Apple Silicon MacのPython環境を整備してみる (conda使わず)
予備知識 現状手に入るApple Silicon MacのCPUはM1と呼ばれる。 $ sysctl -a machdep.cpu machdep.cpu.brand_string: Apple M1 machdep.cpu.core_count: 8 machdep.cpu.cores_per_package: 8 machdep.cpu.logical_per_package: 8 machdep.cpu.thread_count: 8 CPUコアはARMであり、アーキテクチャ文字列はarm64である。 $ arch arm64 Python2は2.7系、Python3は3.8系がインストールされている。 $ /usr/bin/python -V Python 2.7.16 $ /usr/bin/python3 -V Python 3.8.2 Pythonの主要なパッケージのarm64対応は、Python3.9以降で行うとされている。 Scipy Numpy など。 Apple Silicon MacでのPython環境の整えかた 方針 以上の情報から、Python3.9以降を用いて環境構築を行うのが良さそうである。 システムのPython3を置き換えるのは困難なので、 pyenv等の仮想環境を用いる HomebrewなどでPython3.9以降をインストールし、そちらを使うように環境変数等を整える。 などの対応が必要。ひとまずpyenvでPython3.9の仮想環境を作ってみた。venv好きの人はpyenvでPythonのバージョンだけ切り替えて、venvで仮想環境を作っても良い。 手短に結論 ごく一部のパッケージだけ個別対処をすれば、実用上困らないレベルには達している。 素直にインストールできたもの pip installで素直に入ったもの。ライブラリが必要とか、LDFLAGS, CFLAGSなどの設定が必要な場合もあるが、Apple Silicon特有の話ではないので省略する。 Jinja2 Keras-Preprocessing Markdown MarkupSafe Pillow PyQt5 PyQt5-sip PyYAML Pygments Werkzeug absl-py ansible appnope astunparse backcall cached-property cachetools certifi cffi chardet cmake control cryptography cycler cython decorator dill easyesn flask flatbuffers gast google-auth google-auth-oauthlib google-pasta graphviz gsutil h5py haversine hidapi idna imgaug importlib-metadata ipython ipython-genutils japanize_matplotlib jedi keras kiwisolver kociemba labelimg lxml matplotlib more-itertools mpi4py mxnet mysql mysqlclient ntlm-auth numpy oauthlib opencv-python openpyxl opt-einsum optuna packaging paho-mqtt pandas parso passlib pexpect pickleshare plotly poetry prompt-toolkit protobuf ptyprocess pyasn1 pyasn1-modules pycodestyle pycparser pycwt pydot pyglet pyknp pyparsing python-dateutil pyusb pywinrm requests requests-ntlm requests-oauthlib rsa scikit-build scikit-learn scipy selenium setuptools six slidingwindow sympy tensorboard-plugin-wit tensorflow-estimator tensorflow-metal termcolor torch torchvision tqdm traitlets typeguard typing typing-extensions urllib3 wcwidth wheel wrapt wxPython xlrd xmltodict yolov4 zipp インストールに失敗したもの 以下のパッケージはpip installに失敗した(2021/7/15現在)。 PyQt5-Qt => No matching distribution found (まあ不要でしょう) deeplabcut => intel-openmpが無い (Intel専用のよう) grpcio => エラー tensorboard => grpcioが必要 tensorflow-addons => 適切なwheelが存在せず tensorflow-macos => grpcioが必要 yolov5 => grpcioが必要 個別対処 grpcio grpcioが入れられれば、他のいくつかも救える。 export GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1 export GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1 pip install grpcio または GRPC_PYTHON_BUILD_SYSTEM_OPENSSL=1 GRPC_PYTHON_BUILD_SYSTEM_ZLIB=1 pip install grpcio tensorflow-addons pypi.orgでwheelが配布されていないので、ソースビルドをする。 ビルドスクリプトがApple Silicon非対応だったので、修正し、pull requestを出したら採用された。作ったwheelも置いておく。 - pull requestの顛末 (ビルドの仕方も書いた) - Python3.9用にビルドしたwheel (zipしてある) TensorFlowについて Apple Siliconに限らず、Mac用のTensorFlowは、tensorflow-macosと、そのアドオンであるtensorflow-metalになった。 どちらもpip installでインストールできる。 tensorflow-macosだけでも動き、tensorflow-metalを入れるとGPUアクセラレーションが使えるようになる。metalはハード依存性の低い共通仕様であるが、tensorflow-metalでのGPUアクセラレーションは現時点ではAMD(RADEON)のみ対応している。そのうちIntelとかも対応してくれるかも知れない。その前にIntel Macが無くなるかも知れないが。 試したところ、tensorflow-metalを入れるとかえって遅くなることもあるので、アプリ次第でtensorflow-metalを入れるかどうか判断することになる。 まとめ 自分的には困らない程度に環境が整った。
- 投稿日:2021-07-15T13:09:34+09:00
Pyflow使ってDjangoを使える環境を作る
はじめに Pyflowを使いながらDjango環境構築方法が探してもなかったので、どうやってやったのかここにメモします。 Pyflowとは 引用 Pyflow はおそらく一番新参のパッケージ管理ツールです。Rust で書かれており、Poetry で導入された PEP 518 に加え、PEP 582 で提案された、プロジェクト内で扱える仮想環境を複数の Python バージョンに対応させることができます。Pyenv + venv で 1 つの Python のパージョンの 1 つの仮想環境を扱う Pipenv/Poetry に対して、Pyflow は単体で Python のバージョンを複数管理して任意のバージョンで仮想環境を作ることができます。現環境で大きな恩恵は無い気もしますが、今後 Python にメジャーアップデートがあった場合などに重宝されるかもしれません。個人的な懸念は Rust で書かれていることで、速度等で恩恵がありそうな一方で、Rust にある程度の理解がないとエラーメッセージに対応しづらい点と、開発コミュニティが伸びにくいということです。 @sk217さん - 2020 年の Python パッケージ管理ベストプラクティス Pyflowのインストール $ brew install pyflow PyflowでProjectを作成 $ pyflow new プロジェクト名 プロジェクト作成手順 $ pyflow new sample #使いたいPythonのバージョン書いてください Please enter the Python version for this project: (eg: 3.8) Default [2.7.16]: 3.9 Created a new Python project named sample Djangoをインストール # 作ったプロジェクトファイルに移動 $ cd プロジェクト名 # Djangoをインストール $ pyflow install django インストール手順 HEKUTA sample % pyflow install django Found multiple compatible Python versions. Please enter the number associated with the one you'd like to use: 1: python3.9: 3.9.6 2: python3: 3.9.6 1 ? Setting up Python... ⬇ Installing pytz 2017.2 ... ⬇ Installing django 3.2.5 ... Added a console script: django-admin ⬇ Installing sqlparse 0.4.1 ... Added a console script: sqlformat ⬇ Installing asgiref 3.4.1 ... Installation complete なぞのエラー わかる方がいれば教えてください これが出たりするんですが、一様djangoは動きました。細かいこと試してないので、心配な方はファイルを消して、最初からやり直してみてください HEKUTA sample % pyflow install django Found multiple compatible Python versions. Please enter the number associated with the one you'd like to use: 1: python3.9: 3.9.6 2: python3: 3.9.6 2 .... 省略 ], extra: None, sys_platform: None, python_version: None, install_with_extras: None, path: None, git: None, }, ] It's taking a long time to get dependency data - this usually suggests that the dependency tree is being newly built. Please try again in a few minutes, and if the error still occurs, consider opening an issue on github. HEKUTA sample % パッケージのビルド インストールが終わったらパッケージをビルドします $ pyflow package Djangoの起動 djangoのプロジェクトを作ってください。 プロジェクト名/プロジェクト名/djangoprojectの中に作りました。 プロジェクト名/djangoprojectていう感じでも動いたんですけど、どっちが正解かわからないので、私はプロジェクト名/プロジェクト名/djangoprojectにしました 起動! $ cd djangoproject $ pyflow manage.py runserver 参考にした記事
- 投稿日:2021-07-15T13:09:34+09:00
Pyflow使ってDjangoが使える環境を作る
はじめに Pyflowを使いながらDjango環境構築方法が探してもなかったので、どうやってやったのかここにメモします。 Pyflowとは 引用 Pyflow はおそらく一番新参のパッケージ管理ツールです。Rust で書かれており、Poetry で導入された PEP 518 に加え、PEP 582 で提案された、プロジェクト内で扱える仮想環境を複数の Python バージョンに対応させることができます。Pyenv + venv で 1 つの Python のパージョンの 1 つの仮想環境を扱う Pipenv/Poetry に対して、Pyflow は単体で Python のバージョンを複数管理して任意のバージョンで仮想環境を作ることができます。現環境で大きな恩恵は無い気もしますが、今後 Python にメジャーアップデートがあった場合などに重宝されるかもしれません。個人的な懸念は Rust で書かれていることで、速度等で恩恵がありそうな一方で、Rust にある程度の理解がないとエラーメッセージに対応しづらい点と、開発コミュニティが伸びにくいということです。 @sk217さん - 2020 年の Python パッケージ管理ベストプラクティス Pyflowのインストール $ brew install pyflow PyflowでProjectを作成 $ pyflow new プロジェクト名 プロジェクト作成手順 $ pyflow new sample #使いたいPythonのバージョン書いてください Please enter the Python version for this project: (eg: 3.8) Default [2.7.16]: 3.9 Created a new Python project named sample Djangoをインストール # 作ったプロジェクトファイルに移動 $ cd プロジェクト名 # Djangoをインストール $ pyflow install django インストール手順 HEKUTA sample % pyflow install django Found multiple compatible Python versions. Please enter the number associated with the one you'd like to use: 1: python3.9: 3.9.6 2: python3: 3.9.6 1 ? Setting up Python... ⬇ Installing pytz 2017.2 ... ⬇ Installing django 3.2.5 ... Added a console script: django-admin ⬇ Installing sqlparse 0.4.1 ... Added a console script: sqlformat ⬇ Installing asgiref 3.4.1 ... Installation complete なぞのエラー わかる方がいれば教えてください これが出たりするんですが、一様djangoは動きました。細かいこと試してないので、心配な方はファイルを消して、最初からやり直してみてください HEKUTA sample % pyflow install django Found multiple compatible Python versions. Please enter the number associated with the one you'd like to use: 1: python3.9: 3.9.6 2: python3: 3.9.6 2 .... 省略 ], extra: None, sys_platform: None, python_version: None, install_with_extras: None, path: None, git: None, }, ] It's taking a long time to get dependency data - this usually suggests that the dependency tree is being newly built. Please try again in a few minutes, and if the error still occurs, consider opening an issue on github. HEKUTA sample % パッケージのビルド インストールが終わったらパッケージをビルドします $ pyflow package Djangoの起動 djangoのプロジェクトを作ってください。 プロジェクト名/プロジェクト名/djangoprojectの中に作りました。 プロジェクト名/djangoprojectていう感じでも動いたんですけど、どっちが正解かわからないので、私はプロジェクト名/プロジェクト名/djangoprojectにしました 起動! $ cd djangoproject $ pyflow manage.py runserver 参考にした記事
- 投稿日:2021-07-15T12:53:51+09:00
【Python-docx】超面倒臭い、NDA作成を10秒で処理した話!
NDA(Non Disclosure Agreement)作成の話です。 21世紀のビジネスマンには、避けて通れない話題です。 私も、朝から晩まで、潜在顧客とのNDA締結作業をしております。 (営業交渉よりも、NDA締結の方に時間が掛っております。) このNDAですが、会社ごとにフォーマットが違います。 ただ、ネットに転がっているサンプルとしては、例えば、下記です。 https://legaltemplates.net/form/non-disclosure-agreement/ 1.何が問題か? なぜ時間が掛るのか? 例えば、「日付」、「Disclosing Party」、「Receiving Party」を記載するのに、 いちいち、ワードを開いて記入しないといけません。 このワードに記入する作業ですが、実際に何通もNDAを書いているととても面倒です。 下記に課題があります。 ・同一フォーマットを使うにしても、複数のNDAを書く場合は、どのWordファイルにどの客先名を書いたかが分からなくなる。 ・ そもそも、Wordを開いて日付を一枚ずつに書いていくのも、実はかなり面倒。(やってみれば分かりますが、Word編集は、テキストファイル編集程、簡単ではない。) ・ 顧客送付前に、ZIP暗号化しないといけないが、どの顧客ファイルにどの暗号を利用したか?の管理が大変。(大抵、訳が分からなくなる。) 2.改善案 と言う訳で、Python-docxで一瞬で処理します。 下記の様に、NDAとプログラムを一緒に入れておきます。 そして、実行。 C:\Users\user_XXX\Desktop\CODE>python sample_replace1.py はい。これだけです。 コードはこちら。 客先名(Sample LTD)と弊社名(Test Japan co.)は、コード内で記載しております。 勿論、別ファイルにして、そこから、For文で順番に拾って、「代入」して、連続作成する事も可能です。 sample_replace1.py import docx import datetime import docx from docx.oxml.ns import qn import os # pass_gen.py import string import secrets import pyminizip doc = docx.Document("non-disclosure-agreement.docx") num = 0 dt=datetime.datetime.now() x=dt.strftime('%B') DP="Sample.LTD" #顧客名称 RP="Test Japan co." #弊社名称 for para in doc.paragraphs: num = num + 1 f=para.text f=f.replace("ABC",f'{DP}')#雛形に記載のABCを置き換え。 f=f.replace("XYZ",f'{RP}') #雛形に記載のXYZを置き換え。 #NDAの締結目的を置き換え。 f=f.replace("as of ____________________, 20______ (the “Effective Date”) by and between:", f'as {dt.day}th day of {x},{dt.year} ') f=f.replace("relationship relating to: __________________________________________(the “Transaction”). ", f'relationship relating to: ____Establishing Sales Strategy____ (the “Transaction”).') para.text=f para.runs[0].font.size = docx.shared.Pt(9) print(num, para.text) doc.save(f'{DP}_non-disclosure-agreement.docx') filename=f'{DP}_non-disclosure-agreement.docx' print(filename) doc = docx.Document(filename) font = doc.styles['Normal'].font font.name = u'Times New Roman' #Times New Romanを指定。何でもOKです。 doc.save(f'{DP}_non-disclosure-agreement.docx') print(f'this {dt.day}th day of {x},{dt.year}') def pass_gen(size=12):#暗号化処理 chars = string.ascii_uppercase + string.ascii_lowercase + string.digits # 記号を含める場合 # chars += '%&$#()' return ''.join(secrets.choice(chars) for x in range(size)) fileExt=r".docx" print([_ for _ in os.listdir() if _.endswith(fileExt)]) x=[_ for _ in os.listdir() if _.endswith(fileExt)] y=[] for i in range(len(x)): # print(pass_gen(10)) z=pass_gen(10) print(z) y.append(z) i=i+1 print(y) for j in range(len(x)): pyminizip.compress(x[j], "", "zipped_"+x[j]+".zip", y[j], 0) continue j=j+1 f=open('pw_list.txt','w',encoding='UTF-8') for j in range(len(x)): # f.write("Name:"+x[j]+"/"+"pw:"+y[j]+" ") f.write("Name: "+x[j]+" / "+"pw: "+y[j]+"\n") j=j+1 f.close 3. 結果はこうです。 Sample.LTDは、”sFddqf7HQT”でZIP暗号化されたことが分かる。 <pw_list.txt> の中身 Name: non-disclosure-agreement.docx / pw: 9RxCkFuPvR Name: Sample.LTD_non-disclosure-agreement.docx / pw: sFddqf7HQT
- 投稿日:2021-07-15T12:24:16+09:00
Pythonで作るVolcano plot
Volcano plotとは Volcano plotは、RNA-seqやマイクロアレイで二群の遺伝子発現量を比較する際に、遺伝子の発現比と統計的有意性(p値)でプロットした図です。 x軸を発現比、y軸を統計的有意性としたときのプロットが一般的な描き方です。 何千、何万という遺伝子の発現を視覚化して、重要な変化をしている遺伝子を特定することができます。 この図だと赤が統計的有意に発現量が増加した遺伝子で、青が統計的優位に発現量が減少した遺伝子を示しています。 エクセルでも作れますが、Pythonでbioinfokitライブラリを使えば、めっちゃ簡単に、しかもかっこいい図が作れちゃいます。 今回はこのVolcano plotの作り方を記していきます! ライブラリのインストール pipでもcondaでもインストールできますが、今回はcondaインストールをやっていきます。 下のコマンドを実行します。 conda install bioinfokit これでbioinfokitライブラリはインストールできましたが、bioinfokitライブラリは他に色々なライブラリに依存しているので、入ってなければ必要なほかのライブラリ(adjusttextとかtextwrap3など)も入れていきます。 このあとfrom bioinfokit import analys, visuzを実行したときに、足りないものがあるとerrorになるので、errorとして出てきたものをインストールしていけば大丈夫です。 下のリンクの「Depends」を見れば必要なライブラリ一覧がわかります。 bioinfokit データの準備 必要なデータセットは、1) 遺伝子名、2) 比較したい二群(A、B)の遺伝子ごとのt検定の結果(p値)、3) 二群の発現比(A/B)です。つまり3カラム分です。 今回のデモデータは以下のURLのものを使います。 データセット Volcano plotを描いてみる 早速描いていきます! まずはライブラリのインポートです。使うライブラリはpandasとbioinfokitの二種類です。 import pandas as pd from bioinfokit import analys, visuz ここで、さっき言ったライブラリ(adjusttextとかtextwrap3など)が入っていないというエラーがあれば、condaインストールで入れていきます。 つぎにデータを読み込んで、中身を確認します。 df = pd.read_csv('testvolcano.csv') print(df.head()) 出力 GeneNames log2FC p-value 0 LOC_Os09g01000.1 -1.886539 1.250000e-55 1 LOC_Os12g42876.1 3.231611 1.050000e-55 2 LOC_Os12g42884.2 3.179004 2.590000e-54 3 LOC_Os03g16920.1 5.290677 4.690000e-54 4 LOC_Os05g47540.4 4.096862 2.190000e-54 遺伝子名(GeneNames)、遺伝子の発現比(log2FC)、p値(p-value)の必要な3カラムを確認しました。 これでひとまず、必要なものは揃いました! めっちゃ簡単です! さっそく描いていきましょう! 引数は1) データフレーム名(df)、2) 発現比(log2FC)、3) p値(p-value)を与えるだけです。 visuz.gene_exp.volcano(df=df, lfc='log2FC', pv='p-value') そうすると、、、 こんな簡単に描けました!しかも、10行以内のコードで! 図は作業ディレクトリにPNG形式で保存されています。 緑色が統計的有意(p < 0.05)に発現が2倍以上増加した遺伝子群、赤色が統計的に有意に発現が2倍以上減少した遺伝子群です。 整えます せっかくpythonで作ったのにこのままだと味気ないので、色々いじってみます。 まずは色を変えてみます。発現が上がったらなんとなく赤色で、下がったらなんとなく青色な先入観があるので変えてみます。 プロットの色を変える visuz.gene_exp.volcano(df=df, lfc='log2FC', pv='p-value', color=('red', 'grey', 'blue')) #? しきい値がどこらへんなのか、ラインがあるとわかりやすいですよね。しきい値ライン、入れてみます。 しきい値を入れる # ボルケーノプロット3 visuz.gene_exp.volcano(df=df, lfc='log2FC', pv='p-value', color=('red', 'grey', 'blue'), sign_line=True) #? しきい値自体を変えることもできます。 デフォルトだと、低2の対数をとった発現比(log2FC)が1以上(つまり発現比に戻すと2倍以上)、p値が0.05未満になっています。 発現減少側(青色)のしきい値は変更せずに、発現増加側(赤色)のしきい値をlog2FCが2以上、p値が0.01未満としてみましょう。 しきい値を変える visuz.gene_exp.volcano(df=df, lfc='log2FC', pv='p-value', color=('red', 'grey', 'blue'), sign_line=True, lfc_thr=(2, 1), #? 発現比 pv_thr=(0.01, 0.05) #? p値 ) しきい値が二段階になっているので、ラインが機能していません。こういう場合は、ラインは消したほうが良さそうですね。 プロットに遺伝子名を付与していきます。引数geneidに遺伝子名のカラムを指定して、入れたい遺伝子名を引数genenamesで指定していきます。 プロットに遺伝子名を付与する visuz.gene_exp.volcano(df=df, lfc='log2FC', pv='p-value', geneid='GeneNames', #? color=('red', 'grey', 'blue'), sign_line=True, genenames=('LOC_Os12g42876.1', 'LOC_Os09g01000.1', 'LOC_Os09g27030.2') #? ) 簡単にプロットと遺伝子名をリンクすることができました! めっちゃ簡単です! ちなみに、SpyderなどのIDEを使っていれば、引数にshow=Trueと入れれば、プロットのところに図が出力されます。 他にも色々ビジュアルを変えられるので試してみてください!
- 投稿日:2021-07-15T11:06:31+09:00
ラズパイ4でサーボモータを動かすpigpio
ラズパイ4 カーネル5.1.0(2021.5.7版 32bitおすすめ全部入り)を使用しています ラズパイの紹介記事でよく見かけるGPIOライブラリ RPi.GPIO でサーボモーターをPWMで制御するとパルス幅などに揺らぎがあるためにサーボモーターが止まるはずなのに、ジッ、ジッ、と動いてしまいます。 GPIOにアクセスするライブラリはいくつかあるようですが、 pigpioを使ってみることにしました。 pigpioのインストール 公式ページのダウンロードリンクhttp://abyz.me.uk/rpi/pigpio/download.html ここに、以下の2つのコマンドを打つとあるので端末から実行します。 sudo apt-get update sudo apt-get install pigpio python-pigpio python3-pigpio 今回は、実行時に既に最新板があるよ!と表示されたので、32bitおすすめ全部入り版はインストール不要かも知れません。 使用前にデーモンを起動します。 sudo pigpiod pigpioではなくdが付いています。 pigpiod -v でバージョン確認できます。 インストールしたときは1.79と出ましたが、バージョン確認では79と表示されました。 動作テスト コードは参考HP https://www.fabshop.jp/%E3%80%90-%E7%AC%AC38%E5%9B%9E-%E3%80%91gpio%E5%88%B6%E5%BE%A1%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA%E3%81%AE%E3%81%BE%E3%81%A8%E3%82%81/ からコピペしました。windows上だとコード部分をテキストエディタに貼り付けて、PWM.pyなど適当に名前を付けます。 USBメモリにコードを移して、ラズパイに刺しました。その後、ファイルマネージャを使ってホームディレクトリにコピーしています。 pyファイルをダブルクリックするとラズパイのpython開発環境であるThonnyが立ち上がります。 実行ボタンを押すとサーボモーターが動きました。 いくつかの制御関数があるようですが、.hardware_PWMがノイズが少なくて良さそうです。 サーボモーターの接続 秋月で購入したSG-92Rを使いました。動作電圧:4.8V、配線:茶=GND、赤=電源[+]、橙=制御信号 [JRタイプ]とあるので、 電源ラインは5Vとして(3.3Vでも動きます)、オレンジは参考HPのコードから12番のGPIOピンと接続しました。
- 投稿日:2021-07-15T09:07:27+09:00
全てを再現可能に:機械学習とデータレイクハウスの出会い
Reproducible Machine Learning with Data Lakehouse - Databricks Blogの翻訳です。 機械学習は、イノベーションの加速、パーソナライゼーション、需要予測など数えきれないユースケースで、企業やプロジェクトに前例のない価値を付加することを証明しました。しかし、機械学習(ML)は、変化し続けるツールや依存関係を伴う数多くのデータソースを活用し、このことはソリューションが流動的かつ再現が難しいものにしています。 誰もモデルが100%正しいことを保証できませんが、再現可能なモデルと結果を生み出したエクスペリメントの方が、そうでないものよりも信頼できるものと言えます。再現可能なMLエクスペリメントは少なくとも以下を再現できることを意味します: トレーニング/検証/テストデータ 計算 環境 モデル(および関連付けられたハイパーパラメータなど) コード しかし、MLの再現性確保は思ったよりも難しいタスクです。モデルのトレーニングに用いたデータにアクセスする必要がありますが、トレーニングが実行された時点からデータが変更されていないことをどのように保証できますか?データだけではなくソースコードのバージョン管理を行いましたか?さらには、どのライブラリ(バージョンも)、どのハイパーパラメータ、どのモデルを使用しましたか?最後に、コードはエンドツーエンドで処理を完了しましたか? この記事では、Delta Lakeの上に構築されたレイクハウスアーキテクチャが、オープンソースライブラリであるMLflowと連携して、どのようにこれらの再現性確保における課題を解決するのかを説明します。特に、この記事では以下をカバーします: レイクハウスアーキテクチャ Delta Lakeによるデータのバージョン管理 MLflowによるエクスペリメントのトラッキング Databrikcsにおけるエンドツーエンドの再現性確保 データレイクハウスとは何か、なぜ注意を払うべきか データサイエンティストとしては、使用しているデータがどこから来ているのか(CSV、RDBMSなど)を気にしないかもしれません。しかし、例えば夜間に更新されるトレーニングデータを使用しているケースを考えてみます。あなたは、あるハイパーパラメータを用いてモデルを今日構築します。しかし、明日いくつかのハイパーパラメータを調整してモデルを改善しようとします。この際、モデルの性能が改善したのは、ハイパーパラメータを更新したからなのでしょうか?それとも使用しているデータが変更されたからなのでしょうか?データのバージョン管理を行いアップルツーアップルで比較しないことには、これを知る術はありません!「よし、それではすべてのデータのスナップショットを取ろう」と言うかもしれませんが、これはデータの鮮度がすぐに失われ、かつ、バージョン管理が大変で、非常にコストがかかるものです。全てのデータセットのスナップショットを取ることなしに、データのバージョン管理を行い、大規模データであっても最新の状態を維持できる、唯一の信頼できる情報源が必要です。 ここでレイクハウスの出番です。レイクハウスは、データウェアハウスとデータレイクの長所を兼ね備えたものです。これによって、データレイクのスケーラビリティ、低コスト、データウェアハウスのスピード、ACIDトランザクション保証を手に入れることができます。さらに、データに対する唯一の信頼できる情報源を手に入れることで、古いデータ、一貫性のないデータを目にすることがなくなります。このことは、既存のデータレイクを、性能を最適化するためのメタデータ管理機構で拡張することで、データウェアハウスにデータをコピーすることなしに実現します。オープンスタンダードを維持したまま、データのバージョン管理、信頼性、対障害性のあるトランザクション、高速なクエリーエンジンを手にすることができます。ストリーミング、分析、BI、データサイエンス、AIなど主要なワークロードに対応できる単一のソリューションを手に入れることができます。これは新たなスタンダードとなります。 これは理論的には素晴らしく聞こえます。ではどうやって始めたらいいのでしょうか? Delta Lakeによるデータのバージョン管理 Delta Lakeはレイクハウスアーキテクチャを支援するオープンソースプロジェクトです。いくつかのレイクハウスのオープンソースプロジェクトが存在しますが、我々はApache Spark™と密接にインテグレートされ、以下の機能を提供するDelta Lakeを活用しています。 ACIDトランザクション 大規模メタデータ管理 タイムトラベル スキーマエボリューション 監査ログ DELETE、UPDATE バッチ、ストリーミングの統合 良いMLは高品質のデータからスタートします。Delta Lakeと上述の機能のいくつかを活用することで、あなたのデータサイエンスプロジェクトが堅牢な基盤からスタートできることを保証できます。データに対して定常的に更新が行われる際には、バッチであろうがストリーミングであろうが、同時の書き込み、読み込みにおいても、ACIDトランザクションによってデータの一貫性は保持されます。このようにして、すべての人がデータに対して一貫性のあるビューを手に入れることができます。 Delta Lakeは以前のコミットからの"delta"、変更のみをトラッキングし、Deltaトランザクションログに格納します。これによって、データバージョンに対するタイムトラベルが可能になり、モデル、ハイパーパラメータなどに変更を加える間でもデータを一定に保つことができます。しかし、Deltaのスキーマエボリューションがあるので、Deltaによってスキーマがロックインされることはありませんので、機械学習モデルに新たな特徴量を追加することができます。 Delta APIのhistory()メソッドを用いてトランザクションログ上の変更を参照することができます。 これによって、使用しているデータに対する全ての変更のリネージュを容易に追跡することができるので、モデルを構築する際に使用したデータと全く同じものを用いて、モデルを再現することができます。Delta Lakeからデータをロードする際に、特定のバージョンあるいはタイムスタンプを指定することができます。 Python version = 1 wine_df_delta = spark.read.format('delta').option('versionAsOf', version).load(data_path) # Version by Timestamp timestamp = '2021-03-02T15:33:29.000+0000' wine_df_delta = spark.read.format('delta').option('timeStampAsOf', timestamp).load(data_path) MLflowによるモデルのトラッキング 信頼性を持ってデータを再現できるようになったら、次のステップではモデルを再現することになります。オープンソースライブラリMLflowにはMLライフサイクルを管理するための4つのコンポーネントがあり、エクスペリメントの再現性をシンプルにすることができます。 MLflowトラッキングによって、ハイパーパラメータ、メトリクス、コード、モデル、あらゆる追加のアーティファクト(ファイル、グラフ、データバージョンなど)を一つの場所に記録することができます。これには、それぞれのラン(トレーニング)におけるデータの整合性を保つために、Deltaテーブルと対応するバージョンの記録も含まれます。データ全体のスナップショットを取ったり、コピーすることを回避します。ワインのデータセットに対するランダムフォレストモデルを構築する例を取り、MLflowでエクスペリメントを記録します。すべてのコードはこちらのノートブックから参照できます。 Python with mlflow.start_run() as run: # Log params n_estimators = 1000 max_features = 'sqrt' params = {'data_version': data_version, 'n_estimators': n_estimators, 'max_features': max_features} mlflow.log_params(params) # Train and log the model rf = RandomForestRegressor(n_estimators=n_estimators, max_features=max_features, random_state=seed) rf.fit(X_train, y_train) mlflow.sklearn.log_model(rf, 'model') # Log metrics metrics = {'rmse': rmse, 'mae': mae, 'r2' : r2} mlflow.log_metrics(metrics) 結果はMLflowトラッキングUIに記録され、(別のエクスペリメントのロケーションを指定していない限り)Databricksノートブックの右上にあるExperimentアイコンを選択することでアクセスすることができます。ここでは、複数のランを比較したり、特定のメトリクス、パラメーターなどでフィルタリングすることができます。 手動でのパラメーター、メトリクスなどのロギングに加えて、いくつかのMLflowがサポートしているビルトインモデルフレーバーにおけるオートロギング機能を利用できます。例えば、sklearnのモデルを自動的に記録するのであれば、シンプルにmlflow.sklearn.autolog()を追加するだけです。これによって、estimator.fit()が呼び出された際に、パラメーター、メトリクス、分類問題における混合行列などを記録します。 トラッキングサーバーにモデルを記録する際に、MLflowは標準的なモデルパッケージングフォーマットを作成します。モデルをロードするために必要な環境を再作成するのに必要なチャネル、依存関係、バージョンを記述したconda.yamlファイルを自動で作成します。これによって、MLflowに記録されたあらゆるモデルを実行するための環境を簡単に再現できるようになります。 DatabricksプラットフォームのマネージドMLflowを使う際には、ボタンのクリックでトレーニングを再現できる‘reproduce run’機能を利用できます。これはDatabricksノートブック、クラスター設定、インストールした追加のライブラリのスナップショットを自動で作成します。 是非この"reproduce run"機能をチェックしていただき、ご自身、あるいは同僚の方が実施されたエクスペリメントを再現してみてください! 全てを統合する ここまでで、レイクハウスアーキテクチャがDelta Lake、MLflowと共に、どのようにMLにおけるデータ、モデル、コード、環境の再現性の課題に取り組んでいるのかを学びました。ノートブックに目を通していただき、エクスペリメントを再現してみてください!上述した項目を再現できる機能であっても、あなたがコントロールできないことがいくつか存在します。しかしながら、DatabricksにおけるDelta Lake、MLflowによるMLソリューションは、MLエクスペリメントを再現する際に人々が直面する課題の大部分に取り組んでいるものです。 データレイクハウスが解決する他の問題に興味がありますか?最近のブログ記事従来型の2層アーキテクチャにおける課題をご一読いただき、どのようにレイクハウスアーキテクチャが課題解決を支援しているのを知っていただければと思います。 Databricks 無料トライアル Databricks 無料トライアル
- 投稿日:2021-07-15T08:53:37+09:00
系列内の要素をシャッフルする前と後の間の編集距離の分布について
前置き 突然ですが、以下のようなあるリスト source があった時、 >>> source = [0, 1, 2, 3, 4, 5] このリスト内の要素をランダムにシャッフルして得られるリストを target とします。 例えばこんな感じです。 >>> target = [2, 1, 3, 5, 4, 0] この時、source と target の間の距離を編集距離を使って測ると、以下のように 4 になります。 >>> from my_utils.algorithm import levenshtein_editops_list >>> levenshtein_editops_list(source, target) [('replace', 0, 0), ('replace', 2, 2), ('replace', 3, 3), ('replace', 5, 5)] この最後の出力の意味は、source から target を得るには、 source の0番目の要素を target の0番目の要素に置き換える source の2番目の要素を target の2番目の要素に置き換える source の3番目の要素を target の3番目の要素に置き換える source の5番目の要素を target の5番目の要素に置き換える ことで target が得られる、ということです。 (ここでの置き換えとはリスト内の要素同士の入れ替えとは異なることに注意です。) 置き換え以外にも 要素の削除や挿入といった「操作」があります。 target を得るまでに必要な操作の総数の最小値が編集距離です。 普通は編集距離は文字列同士の距離を測るのに使われるみたいですが、諸事情でリストにしています。多分どちらでも変わりません。 本題 source リストをランダムにシャッフルして target リストを得た時、source と target の間の編集距離の分布 が知りたいです。 厳密に証明を考えるのは大変そうなので、とりあえず実験して様子を見ます。 実験 長さ20のリストをランダムに5000回シャッフルしてそれぞれについて編集距離を計算します。 >>> import random >>> import matplotlib.pyplot as plt >>> from my_utils.algorithm import levenshtein_list >>> source = list(range(20)) >>> target = copy.deepcopy(source) >>> distances = [] >>> for _ in range(5000): ... random.shuffle(target) ... d = levenshtein_list(source, target) ... distances.append(d) >>> plt.hist(distances, bins=list(range(40))) 今更ですが、リスト同士の編集距離の計算には以下で公開しているソースコードを使っています。 (既存のLevenshteinライブラリを使っているだけですが...wikiにあるアルゴリズムの自前実装よりもこれの方が速かったです。) 結果 最大値は20でした。 これは元のリストの長さの20と同じです。 試しに長さが30, 40の時も試しましたが、結果は同様でした。 経験的にですが、リストのシャッフルする前と後の間の編集距離の最大値は、リストの長さと言えそうなことがわかりました。 直感的には、リストの各位置について置き換えを行わなければいけない時に編集距離が最大になる、ということでしょうか。 それから小さい編集距離が全く得られませんでした。不思議です。 おまけに長さが30、40のときの結果を載せておきます。 30の時 40の時 結論 シャッフル前と後の間の編集距離の最大値は リストの長さ かも ついでにわかったこととして、ランダムにシャッフルすると、編集距離が小さくなる様なリストはほとんど観測されないということがあります。 最大値マイナス6くらいまでしか観測されていません。 例えば編集距離が4などの小さい値になる確率は0ではなさそうなので、最大値付近に比べると限りなく0に近いということでしょうか。 これについて気になったので追加で考察してみます。 考察: なぜ小さい編集距離が観測されないか 簡単のために挿入と削除を忘れて、置き換えが何回必要かの分布を考えることにします。 リストの要素のシャッフルの場合の数は、リストの長さをnとすると、nの階乗$n!$です。 source と target を比べたとき、同じ位置に異なる値が入っている箇所がちょうどk個あるような場合の数 $x_k$ を求めて、それを全ての場合の数$n!$で割れば、置き換えがk回必要となる確率$p(k)$が求められそうです。 まずk個の位置の選び方は$_nC_k$通りです。 選ばれたk個の要素をかき集めて短いリストを作ります。 長さkのこのリストについて、全ての位置にシャッフル前後で違う要素が入っているような場合の数を$a_k$とすると、 x_k = _nC_k a_k と書けます。 この$a_k$を求めるのに時間がかかったのですが、調べてみるとこの$a_k$には モンモール数 という名前がついていて、kが3以上の時、$a_k$は、 a_k = (k-1)(a_{k-1} + a_{k-2}) という漸化式で求められるそうです(!)。(証明はwikipedia) $a_0=1$, $a_1=0$から$a_k$が芋づる式にわかります。 a_0 = 1\\ a_1 = 0\\ a_2 = 1\\ a_3 = 2\\ a_4 = 9\\ a_5 = 44\\ a_6 = 265\\ a_7 = 1854\\ a_8 = 14833\\ a_9 = 133496\\ a_{10} = 1334961\\ a_{11} = 14684570\\ a_{12} = 176214841\\ a_{13} = 2290792932\\ a_{14} = 32071101049\\ a_{15} = 481066515734\\ a_{16} = 7697064251745\\ a_{17} = 130850092279664\\ a_{18} = 2355301661033953\\ a_{19} = 44750731559645106\\ a_{20} = 895014631192902121 置き換えがちょうどk回必要となる確率 p(k) = \frac{x_k}{n!} = \frac{_nC_k a_k}{n!} と、実際に集計した値の分布が一致しているかを見てみます。 この様にかなり一致していることがわかり、理論的にも必要な置き換えの回数が少ないときの確率が0に近いことがわかりました。 これの大きな原因としては、モンモール数はkが大きくなるにつれて急激に大きくなっていくことが挙げられると思います。 しかし、下のグラフの様に、実際の編集距離の分布と、必要な置き換えの回数の分布の間には乖離があります。 この問題は、例えば [0, 1, 2, 3] と [1, 2, 3, 0]は置き換えだけだと4回必要だけど、0の削除と0の挿入の2回の方が少ない操作で編集が可能、といった例があることが原因だと思います。 そのため挿入と削除を考慮しないと実際の編集距離の分布を求めるのは無理そうです。 それっぽい理由がわかってある程度満足したので、この記事はここで終わります。
- 投稿日:2021-07-15T08:45:49+09:00
SudachiPyで形態素単位に分割|つぶやきをWordcloudで可視化③
私事ではありますが、2020年5月にコーギーという中型犬をお迎えしました。 愛犬のブログを運営してますので、見てもらえると嬉しいです。 「コーギー関連のツイートをwordcloudで可視化したい!」と思い立ち、以下の4項目を実施しました。 TwitterAPIでツイートを取得 正規表現を用いてツイートを整形 SudachiPyで形態素単位に分割 ← 今回 WordCloudで可視化 ⇒ 一連のコードと様々なアウトプットの表示 今回は、「SudachiPyで形態素単位に分割」に関して説明していきます。 対象者 SudachiPyの特徴を知りたい人 SudachiPyでコードを書いてみたい人 SudachiPyの特徴 SudachiPyのメンテナーがこちらのリンクで書いてくれてます。 まとめると、特徴は以下の3つになります。 small, core, fullの3タイプから、使用する辞書を選択できる 分割タイプも3タイプから選択できる 表記を正規化してくれる ざっくりとした要件定義 Input ... 前処理が完了した、DataFrame形式のツイートデータ Output ... 単語の間をスペースで分割し、すべてのツイートを結合したstring Inputは、こちらの記事で取得したデータフレーム(以下)になります。 Outputは、テキスト内容を形態素単位に分割し、次のようになります。 コード コード全体 import pandas as pd from sudachipy import tokenizer from sudachipy import dictionary class SudachiTokenizer(): def __init__(self, dict_type="core", mode="C", stopwords=None, include_pos=None): if dict_type not in ["core", "small", "full"]: raise Exception("invalid dict_type. 'core' ,'small' or 'full'") self.tokenizer_obj = dictionary.Dictionary(dict_type=dict_type).create() if mode not in ["A", "B", "C"]: raise Exception("invalid mode. 'A' ,'B' or 'C'") self.mode = getattr(tokenizer.Tokenizer.SplitMode, mode) print(self.mode ) if stopwords is None: self.stopwords = [] else: self.stopwords = stopwords if include_pos is None: self.include_pos = ["名詞", "動詞", "形容詞"] else: self.include_pos = include_pos def parser(self, text): return self.tokenizer_obj.tokenize(text, self.mode) def tokenize(self, text, pos=False): res = [] for m in self.parser(text): p = m.part_of_speech() base = m.normalized_form() #.dictionary_form() #print(base, ": ", p) if p[0] in self.include_pos and base not in self.stopwords and p[1] != "数詞": if pos: res.append((base, p[0])) else: res.append(base) return res def create_word_chain(col, df, tokenizer): word_lists=[] for i in range(len(df)): text = df.loc[i, col] word_list = tokenizer.tokenize(text, pos=False) for word in word_list: word_lists.append(word) word_chain =' '.join(word_lists) return word_chain include_pos = ["名詞", "動詞", "形容詞"] stopwords = ["コーギー", "見る","為る", "今日","無い","居る","成る"] sudachi_tokenizer = SudachiTokenizer(dict_type="core", mode="A", stopwords=stopwords, include_pos=include_pos) word_chain = create_word_chain('TW_TEXT_mod', df, sudachi_tokenizer) コード解説 SudachiTokenizer()のinit SudachiPyで必要なdict_typeとmodeを指定します。 SudachiPyの条件に合わない語句を指定した場合は、エラーが出るようにしています。 今回の分析では、dict_typeを変更しても、最終的な結果に大きな変化はありませんでした。(もしかすると、うまくdict_typeを変更できてないかもです。) また、stopwords(形態素解析のあとに、削除するワード)とinclude_pos(形態素解析のあとに、含める品詞)を指定します。 class SudachiTokenizer(): def __init__(self, dict_type="core", mode="C", stopwords=None, include_pos=None): if dict_type not in ["core", "small", "full"]: raise Exception("invalid dict_type. 'core' ,'small' or 'full'") self.tokenizer_obj = dictionary.Dictionary(dict_type=dict_type).create() if mode not in ["A", "B", "C"]: raise Exception("invalid mode. 'A' ,'B' or 'C'") self.mode = getattr(tokenizer.Tokenizer.SplitMode, mode) print(self.mode ) if stopwords is None: self.stopwords = [] else: self.stopwords = stopwords if include_pos is None: self.include_pos = ["名詞", "動詞", "形容詞"] else: self.include_pos = include_pos SudachiTokenizerクラスのparserとtokenize関数 parser関数でparserを定義します。 結果はMorphemeのリストとなっており、表層形 (surface()) 、品詞 (part_of_speech()) 、読み (reading_form()) 、正規化した表現 (normalized_form()) を取得できます (引用元:https://ohke.hateblo.jp/entry/2019/03/09/101500) 次に、tokenize関数で条件に合った語句を抽出し、リストに加えます。 今回は、正規化した表現で抽出したいので、normalized_form()を使用しています。 def parser(self, text): return self.tokenizer_obj.tokenize(text, self.mode) def tokenize(self, text, pos=False): res = [] for m in self.parser(text): p = m.part_of_speech() base = m.normalized_form() #.dictionary_form() #print(base, ": ", p) if p[0] in self.include_pos and base not in self.stopwords and p[1] != "数詞": if pos: res.append((base, p[0])) else: res.append(base) return res create_word_chain関数 次の章でwordcloudで頻出語を可視化しますので、語句と語句の間をスペースで区切り、ひとつのstringにします。 全てのtweetに対して実施する必要があるので、ツイートごとに先ほど作成した関数で形態素単位に分割し、for文でそれを全ツイート繰り返します。 def create_word_chain(col, df, tokenizer): word_lists=[] for i in range(len(df)): text = df.loc[i, col] word_list = tokenizer.tokenize(text, pos=False) for word in word_list: word_lists.append(word) word_chain =' '.join(word_lists) return word_chain 作成した関数の実行 これまで作成した関数を実行し、word_chainを作成します。 最終結果に「居る」や「成る」といったコーギーの特徴を表さない語句が表示されていたので、これらの語句をstopwordsに追加しています。 次の章のwordcloudでもstopwordsを指定でき、そちらで指定したほうが全体の計算量が少ないかもしれません。。 (大変そうですし、今回は計算量は無視しました。) include_pos = ["名詞", "動詞", "形容詞"] stopwords = ["コーギー", "見る","為る", "今日","無い","居る","成る"] sudachi_tokenizer = SudachiTokenizer(dict_type="core", mode="A", stopwords=stopwords, include_pos=include_pos) word_chain = create_word_chain('TW_TEXT_mod', df, sudachi_tokenizer) まとめ この記事では、形態素解析器を用いて、ツイートを形態素に分割しました。 次は、ついにwordcloudで可視化します。 参考文献 日本語形態素解析器 SudachiPy の 現状と今後について GitHub - WorksApplications/Sudachipy 形態素解析器比較 Sudachi vs Mecab+Neologd Pythonで形態素解析器Sudachiを使う (SudachiPy)
- 投稿日:2021-07-15T05:34:12+09:00
Djangoに0から入門
はじめに 本記事では、Python初心者でもDjangoでWeb開発をスタートするために必要な環境構築の手順をまとめました。ただし、筆者がDjangoに関する知識がほぼ0の状況から作成した内容であるため、技術的に誤った記述をしている可能性があることをご承知おきください。 目次 環境構築 開発環境 Pythonインストール Djangoインストール PyCharmインストール 簡単なアプリケーション作成 Djangoプロジェクトを作成 PyCharmの設定をする Webアプリケーションの動作確認 アプリケーションを作成する ページにアクセス おわりに 参考文献 ■環境構築 ◇開発環境 種別 名称 (バージョン) OS macOS 言語 Python 3.9.5 フレームワーク Django 3.2.3 IDE PyCharm 2021.1.1 CE ◇Pythonをインストール 今後の開発を考えて、パッケージマネージャーであるHomebrewでPythonをインストールします。 ●Homebrewをインストール Homebrew公式サイトに掲載されているインストール用のコードをコピーし、ターミナルに貼り付けて実行します。 下記コマンドでバージョン確認し、問題なければインストール完了。 brew -v 参考 - Homebrewのインストール - qiita ●pyenvをインストール brewコマンドでpyenvをインストールします。 brew install pyenv 次にpyenvの設定をします。 echo $SHELL 上記コマンドの出力結果によって設定方法が異なる。 ・/bin/bashが出力された場合 echo 'export PYENV_ROOT="$HOME/.pyenv"' >> ~/.bash_profile echo 'export PATH="$PYENV_ROOT/bin:$PATH"' >> ~/.bash_profile echo 'eval "$(pyenv init --path)"' >> ~/.bash_profile echo 'eval "$(pyenv init -)"' >> ~/.bash_profile source ~/.bash_profile ・/bin/zshが出力された場合 (Homebrew以外でpyenvをインストールした場合は少し異なる) echo 'eval "$(pyenv init --path)"' >> ~/.zshrc echo 'eval "$(pyenv init -)"' >> ~/.zshrc source ~/.zshrc 参考 - pyenv - github - 【Python超入門講座】Macの開発環境を構築しよう【初心者向け】 - youtube ●Pythonをインストールする pyenvでPythonをインストールするメリットは、プロジェクトごとに複数のバージョンのPythonを切り替えて使用できることである。 インストール可能なバージョンを確認する。 pyenv install --list Python 3.9.5 をインストールする場合は、 pyenv install 3.9.5 インストールされているバージョンを確認するには、 pyenv versions バージョン 3.8.5 と 3.9.5 をインストールした場合は以下のような出力になります。 system * 3.8.5 3.9.5 ✳︎がついているバージョンが現在選択されており、切り替えるには下記のコマンドを打ちます。なお、systemは標準でインストールされているものなので今回は気にしなくて大丈夫です。 pyenv global 3.9.5 とすると、下記のようになります。切り替えのオプションとしてglobalのほかに、localとshellがありますので、気になる方は参考欄を参照してください。 system 3.8.5 * 3.9.5 最後にPythonのバージョンを確認しておきます。 python -V ここで、先ほど選択したバージョンが表示されていればPythonのインストールは完了です。 参考 - pyenv、virtualenv、pip、anacondaの違いを説明します - pyenvを使ってPythonの複数のバージョンを使い分ける ◇Djangoをインストール ●Pythonで仮想環境を作成する プロジェクトの実行環境を用意するために仮想環境を作成する。実行環境を分ける理由は、システムが使うPython環境に影響を与えないこと、同じモジュールのバージョンを使い分けられることなどが挙げられる。 仮想環境を作成するには下記のコマンドを打ちます。仮想環境名は任意に書き換えて下さい。 python -m venv 仮想環境名 仮想環境ディレクトリに移動し、仮想環境に入ります。 cd 仮想環境名 source bin/activate 仮想環境に入ると、コマンドラインが下記のようになります。 (仮想環境名) $ 仮想環境を抜けたい場合は、次のコマンドを打つだけです。(これからモジュールを仮想環境にインストールするので仮想環境を抜けた方は入り直して下さい。必ずです。) deactivate ●Djangoをインストールする では、いよいよDjangoをインストールします。 最新バージョンをインストール pip install django バージョンを指定してインストール pip install django==3.2.3 pipコマンドを使用すると、pipをアップグレードするよう警告が出てくることがありますが、その際は素直にアップグレードして下さい。pipアップグレード後、念のために上記コマンドを再入力して下さい。 インストールされたバージョンを確認して、 python -m django --version 問題なければDjangoインストール完了です。 参考 仮想環境: Python環境構築ガイド - python.jp ◇PyCharmをインストール PyCharmダウンロードサイトからインストーラーをダウンロードする。無料版のCommunity Editionで十分快適に利用できます。ダウンロード後、インストールを完了して下さい。 ■簡単なアプリケーション作成 ◇Djangoプロジェクトを作成する 先ほど作成した仮想環境ディレクトリ直下にDjangoプロジェクトを作成する。サンプルとしてプロジェクト名はhello_appとしていますが、任意に書き換えて下さい。(初学者向けの補足ですが、基本的にコマンドは仮想環境下で実行して下さい。) django-admin startproject hello_app(プロジェクト名) ◇PyCharmの設定をする 次にPyCharmを開いて、作成したプロジェクトディレクトリ(仮想環境ディレクトリ直下のhello_app)を開き、以下の手順で設定する。 PyCharmメニューの「Preferences…」をクリック 左側の一覧内の「Project: Djangoプロジェクト名」をクリック 「Python Interpreter」クリック 右側のPython Interpreter選択箇所にある「歯車」をクリックし「Add」をクリック 「Existing environment」を選択し、Interpreterの「…」をクリック 「仮想環境ディレクトリ/bin/python」を選択してOKをクリック もう一度右下にあるOKをクリック PyCharm内のTerminalを開いて、初めから仮想環境に入れていることが確認できたら設定完了です。 Windowsの方は少し異なる部分がありますので、適宜調べながら設定して下さい。 ◇Webアプリケーションの動作確認 PyCharm上のターミナル(今後、特に説明がない場合はPyCharm上のターミナルを指す)で下記コマンドを打ち、 python manage.py runserver ブラウザで下記にアクセスする。 http://localhost:8000/ このような画面が表示されていたらOKです。 Webアプリケーションを停止させるにはターミナルで「ctrl + c」を入力するだけですが、基本起動したままでも大丈夫です。 ◇アプリケーションを作成する Webアプリケーションの起動を確認できたので、次はブラウザにHello Worldを表示させようと思います。 ●Djangoアプリケーションを作成する Djangoアプリケーションを作成していきます。アプリケーション名は任意ですが、今回はサンプルとしてhelloとします。 python manage.py startapp hello(アプリケーション名) 実行後、helloアプリケーションディレクトリが追加され、下記のようなディレクトリ構造になっているかと思います。反映されない場合はPyCharmをリフレッシュなり再起動して下さい。 hello_app ├── hello_app │ ├── (以下略) │ ├── hello (←新規作成される) │ ├── migrations │ ├── __init__.py │ ├── (以下略) │ ├── manage.py ├── (以下略) ●画面表示の処理を作成 ここからは実際にプログラムを書いていきます。 まず、helloディレクトリ内のviews.pyを下記のように編集します。 views.py from django.shortcuts import render from django.http import HttpResponse def index(request): return HttpResponse("Hello World") 書かれている内容をざっくり説明すると、index関数が呼び出されるとクライアント(Webブラウザ)に「Hello World」をレスポンスする(送り返す)といった処理になってます。 ●ルーティングを設定 続いて、ブラウザでアクセスするURLによって画面表示の処理を変えるためにルーティング設定をします。 hello_appディレクトリ内のurls.pyを下記のように編集します。 urls.py from django.contrib import admin from django.urls import path import hello.views urlpatterns = [ path('admin/', admin.site.urls), path('hello/', hello.views.index), ] 「hello/」の部分がURLを設定していて、そこにアクセスすると「hello.views.index」(さっき書いた画面表示の処理)が呼び出されるよ、という仕組みです。 ●ページにアクセス それでは「Hello World」が画面に表示されるか確認しましょう。 Webアプリケーションを停止させている方は、下記コマンドで起動するのを忘れずに。 python manage.py runserver ブラウザで下記にアクセスしましょう。 http://localhost:8000/hello/ ↓のように「Hello World」が表示されていれば成功です。 (↑スクショ貼り付けたけどブラウザ表示領域がわかりにくい。。。) 先ほどの動作確認の時のように「http://localhost:8000/」にアクセスすると下記のエラーが出るので、注意して下さい。 (補足ですが、「http://localhost:8000/」にルーティングしたい場合は、urls.pyのurlpatternsの内容を下記に変更して下さい。) urls.py urlpatterns = [ path('admin/', admin.site.urls), path('', hello.views.index), ] おわりに SNSで色んな方々(2名)の影響を受け、今勉強していることを発信してみることに。 書き始めた当初はもう少し踏み込んだ内容まで書くつもりだったのですが、キリがなさそうだったので入門するまでのところで一旦切り上げました。(続編も投稿予定ですが時期的にいつになるのやら。笑) 普段、技術的な内容とかはMacのメモアプリにツラツラ書いてるんですが、いざQiitaなどで公開するとなると「いい加減な情報は記載できないプレッシャー」を感じて情報整理を始めたんですけど、なんとなく理解した気になってたところとか後回しにしてたところが浮き彫りになって、そこを理解するために参考書読み漁ったりネットサーフィンで大変でした。(情報整理は普段からしとけっていう教訓を得ました。笑) そうこうして整理した知識は定着が段違いなので、今回は情報発信することの重要性を知る良いきっかけになったと思います。 最後になりましたが、本記事をお読みいただきありがとうございます。 初投稿なので至らぬ点など沢山あるかと思いますが、お手柔らかにご質問、ご指摘等頂けたらと思います。 参考文献 Python Django3超入門 掌田 津耶乃 (著) 動かして学ぶ! Python Django開発入門 大高 隆 (著)
- 投稿日:2021-07-15T05:32:38+09:00
初期化済みの配列を用意する
初期化済みの配列 Pythonで初期化済みの配列がほしいことがあります 例えばこんなの ARRAYNUM = 3 array = [0, 0, 0] しかしこれでは、ARRAYNUMが生きてません そこでこんな書式が便利です array = [0] * ARRAYNUM もちろん、Noneや空文字での初期化も出来ます arraynone = [None] * ARRAYNUM arraystr = [''] * ARRAYNUM 配列の配列 もちろん配列の配列もやっちゃいましょう #間違ったコード arrayarray = [[]] * ARRAYNUM これは以下のコードと等価です a = [] arrayarray = [a, a, a] # 同じインスタンスなのでどこか一箇所を変更すると全部変わる arrayarray[0].append(0) # aの中身は[[0], [0], [0]] 同じインスタンスをコピーしてしまうので、全部同じものになるんですね もちろん、クラスなんかも同様なので、上記の書き方は問題になります なので以下の書き方で対応しましょう #正しいコード [ [] for _i in range(ARRAYNUM) ]
- 投稿日:2021-07-15T02:16:35+09:00
半分にするテク
3^n DP でループ回数を半分にするテク 「$3^n$ $\mathrm{DP}$ でループ回数を半分にするテク」、略して「半分にするテク」について書きます。 半分って何? $3^n$ $\mathrm{DP}$ と呼ばれるアルゴリズムがありますね。部分集合を列挙して処理するやつです。普通にやるとループが $3^n$ 回ぐらいになるけど、それを $3^n/2$ 回ぐらいにするためのテクを紹介します。 そもそも 3^n DP って何? $U = \{1,\ 2,\ \cdots,\ n\}$ とします。競プロ的には $n$ の上限は $15$ ~ $18$ ぐらいのことが多いです。 $U$ の各部分集合 $S\subset U$ に対して $f(S)$ が前計算されているとします。さらに $S\subset U$ に対して $g(S)$ が、「 $S$ の空でない真部分集合 $T\subsetneq S$ に対する $f(T)$ および $g(S\setminus T)$ たち」から計算できるとします。さらにこの計算量は真部分集合の数に対して線形、すなわち $O(2^{|S|})$ でできると仮定します。このとき $g(X)$ が $O(3^{n})$ で計算できます。 どんなとき使えるの? $U$ の分割(すなわち $k\ (\ge 1)$ 個の非空集合の非交差和 $U=\bigsqcup U_i$ に分ける方法)に対する値が決まっていて、その値を最大化 1 するような場合に使えることがあります。 具体例(ネタバレ注意) 例えば この問題 が有名です。以下では、この問題特有の解説は特にしませんが、 AC コードのみ参考に紹介します。 コード こんな感じ。変数 $i$ は上の $S$ に、変数 $j$ は $T$ にそれぞれ対応します 2。 $j$ は $i$ の非空真部分集合をすべて回ります 3。 test.py # X: f に対応。前計算済み # Y: g に対応。Y[-1] が求めるもの # merge: 累積 Y = [0] * (1 << n) for i in range(1, 1 << n): j = (i - 1) & i s = init(i) # 初期化(S=T の場合の処理もここでする) while j: s = merge(s, X[i^j], Y[j]) j = (j - 1) & i Y[i] = fin(i, s) 上の問題だと AC コード のように書けます。 半分にするテク 概要 $S$ の真部分集合 $T$ をすべて動かすときに、実は $T$ を選んでも $S\setminus T$ を選んでも結果は同じになることがあります 4。そのような場合、 $a\in S$ を $1$ つ固定して $a$ を含む真部分集合 $a\in T\subsetneq S$ のみを選ぶ 5 という $\mathrm{DP}$ にするとループの回数を半分にすることができます。 コード 先ほどのコードを数行変えるとできます。このコードでは $i$ の最下位 bit a = i & -i を固定して、 $j$ は $ia = i - \{a\} = S - \{a\}$ の非空部分集合をすべて回ります 3 。 test.py # X: f に対応。前計算済み # Y: g に対応。Y[-1] が求めるもの # merge: 累積 Y = [0] * (1 << n) for i in range(1, 1 << n): ia = i ^ (i & -i) # S - {a} j = ia s = init(i) # 初期化(S=T の場合の処理もここでする) while j: s = merge(s, X[i^j], Y[j]) j = (j - 1) & ia Y[i] = fin(i, s) 先ほどの問題だと AC コード のようにできます。実行時間は $444\ \mathrm{ms}$ → $266\ \mathrm{ms}$ と短くなっています。 PyPy のオーバーヘッドが $50$ ~ $60 \mathrm{ms}$ ぐらいあるので、ほぼほぼ半分になっていることが分かります。 さらなる定数倍高速化 6 定数倍高速化の基本は、ボトルネックになる部分の処理を軽くすることです。 $3^n$ $\mathrm{DP}$ の場合は、メインの部分以外は $O(2^n)$ あるいは $O(2^n \times n)$ のことが多いので、メインの $O(3^n)$ 回の処理の部分が圧倒的にボトルネックになりがちです 7。しかも $O(3^n)$ 部分の処理はとても軽いことが多いので、少しの書き方の違いでも実行時間には大きく影響を与えがちです。例えば、上のコードでは merge 関数を別で書いて呼び出していましたが、ここは直接書いた方が呼び出しコストが減って大きく改善できます。もっと細かい例だと i + 1 みたいな計算を $O(3^n)$ 回やってるなら i1 = i + 1 を $O(2^n)$ 回前計算すると少し速くなるかもしれません。同じランダムアクセスをしている場合も前計算しておくと良いでしょう 8。とにかく $O(3^n)$ 部分の処理は必要最小限にというのがポイントですね。 test.py # X: f に対応。前計算済み # Y: g に対応。Y[-1] が求めるもの # merge: 累積 Y = [0] * (1 << n) for i in range(1, 1 << n): # ここは 2^n 回ぐらいしか通らないので多少重くても気にしない ia = i ^ (i & -i) j = ia s = X[i] while j: # ここは (3^n)/2 回ぐらい通るので極力軽くしたい s = max(s, X[i^j] + Y[j]) # 関数呼び出しではなく直接コーディング j = (j - 1) & ia Y[i] = s 関数呼び出しをやめた AC コード では $234\ \mathrm{ms}$ になりました。 経緯 この記事を書こうと思った直接の経緯は、ツイートが分からんと言われてしまったこと。 解説お願いできませんか?(きりさんのツイートで理解できず)— ながたかな (@ngtkana) July 13, 2021 このテクの名前はよく知らないので、「半分にするテク」と呼ぶことにしました 9。 じゃあ半分にするテクって呼んでおきます— きり (@kiri8128) January 3, 2021 おしまい おしまい 合計、 $\max$ 、 $\min$ などを求めることも。 ↩ $i$ の下から $k$ bit 目が立っているとき、かつそのときに限り、 $k\in S$ という関係があるよ ↩ 集合と(集合を表す)変数を混同している書き方なので分かりにくい ↩ $3^n$ $\mathrm{DP}$ の問題だとほとんどがそうだと思います ↩ 逆に $a$ を含まないもののみを選ぶとしても同じです ↩ 高速化というよりは、定数倍が重くて損しないように注意しましょうという話です。 ↩ ただし前計算が $O(2^n \times n^2)$ の場合はそこまで圧倒的でもないかもしれません。 ↩ このあたりは言語によっては最適化されるかもしれませんが。 ↩ そのままですね ↩
- 投稿日:2021-07-15T01:04:13+09:00
UnityとPython間でのデータのやり取り
背景 Unityで計測したデータをPythonに送って、機械学習で推定した結果を可能な限りリアルタイムでUnity側に反映させたかったが、調べてもサーバーを使うものしか見つからず、リアルタイムにやり取りできなかった. 目的 可能な限りリアルタイムでUnityとPythonでデータをやり取りする. UnityとPythonのコード:https://github.com/sakamo1290/DataExchange Unity:2019.4.28f1 Python:3.7.6 方針 csv(Unity2Python.csvとPython2Unity.csv)を介してデータをやり取りする. 実装 Unity側でFixedUpdateが呼び出されるたびにインクリメントされる変数(count)をcsvを介してやり取りする。 Python側 まずPython側でデータを読み書きするスクリプトを作る。 DataExchange.py #実際に使うときはやり取りするデータは1つではないため、pandasのDataFrameでcsvに読み書きする import pandas as pd def main(): #Unityから送られてきたデータを格納するDataFrame log = pd.DataFrame(columns = [0], index = [0], data = [0]) #データが更新されているかを確認するための変数 past = -1 while True: past, log = ReadData(past, log) WriteData(past) def ReadData(past, log):#Unityからデータを受け取る #データをUnityが書き込んでいるときはアクセスできずエラーになるためtryで回避 try: a = pd.read_csv('Unity2Python.csv', encoding="ms932" ,sep=",") except: return past, log #データが更新されていない場合読み取りを終了する if past == a.columns[0]: return past, log log = log.append([int(a.columns[0])]) print(a.columns[0]) past = a.columns[0] return past, log def WriteData(past):#Unityにデータを送る #Unityが読み込んでいるときはアクセスできずエラーになるためtryで回避 try: pd.DataFrame([past]).to_csv(path_or_buf = 'Python2Unity.csv', header=False, index=False) except: return if __name__ == "__main__": main() Unity側 Unity側でもデータをやり取りするスクリプトを作成する(gitのDataExchange.unitypackage内に完成したものが入れてある)。 新しいプロジェクトを作成し、ヒエラルキーで右クリックして'Create Empty'を選択する。 名前を'DataExchanger'にしてインスペクターで'Add Component'をクリックしDataExchangerスクリプトを作成する。 DataExchanger.cs using System.Collections; using System.Collections.Generic; using System.IO; using UnityEngine; public class DataExchanger : MonoBehaviour { int count = 0; string sendDataPath; string recieveDataPath; void Start() { sendDataPath = Application.dataPath + "/LogData/Unity2Python.csv"; recieveDataPath = Application.dataPath + "/LogData/Python2Unity.csv"; } void FixedUpdate() { WriteData(count.ToString(), sendDataPath); ReadData(recieveDataPath); count++; } void WriteData(string data, string _filePath) { string _path = _filePath; StreamWriter sw; FileInfo fi; fi = new FileInfo(_path); sw = fi.CreateText(); sw.WriteLine(data); sw.Flush(); sw.Close(); } void ReadData(string _filePath) { Debug.Log(File.ReadAllText(_filePath)); } } DataExchange.pyを配置 Unityのフォルダ内にAssets/LogDataとなるようにLogDataフォルダを作成する。 LogDataフォルダ内にDataExchange.pyを配置する(場所はここでなくても問題ないが簡単のために。変える場合はスクリプトのパスを修正する。)。 動作確認 おおむねよく動いているが、Unity側で数字が飛ぶ場合がある。 目的は機械学習の結果をUnityに反映することであるため、推定にかかる時間を考慮すると問題ない程度であると言える。と思う。