- 投稿日:2021-09-01T23:39:11+09:00
Mediapipeで手の形状検出を試してみた(Python)
画像を取り扱う練習として面白そうなものを探していたところ、Mediapipeを使った検出が面白そうだったので、下の公式ガイドを見ながら手の検出をPythonで実装してみました! 準備 作業環境のファイル構造は下記のようにしています。コードはJupyter Notebookで行っています。 file/ ├─ notebook.ipynp ├─ hands/ │ └─ hand1.jpgなどの静止画データ ├─ annotated_hand/ └─ hand_movie/ └─ hand_movie.mp4などの動画データ notebookを開いて必要なライブラリをインポートしました。 #ファイル名の取得に使用 import os #画像処理に使用 import cv2 import mediapipe as mp #静止画表示に使用 import matplotlib.pyplot as plt %matplotlib inline 自分の手の写真を使って検出する ひとまず自分の手を撮影し、手袋した場合や暗い場所で撮影した場合の画像を用意し、hand1.JPGとしてhandsフォルダの中に入れておきます。 次に検出器のインスタンス化をします。このとき以下のオプションを設定しました。 変数名 意味や使い方 static_image_mode 静止画を取り扱うかどうかをブール値で設定 max_num_hands 1枚の画像の中で検出する手の最大数 min_detection_confidence 手かどうかを判断する際に用いる値で、1に近い値を入れるほど手と判断される画像が少なくなり、0に近づけるほど手と判断されることが多くなる hands = mp.solutions.hands.Hands( static_image_mode=True, max_num_hands=2, min_detection_confidence=0.5) 次にOpenCVを使って読み込み、手の検出を行いresultsに保存します。 image = cv2.flip(cv2.imread('./hands/hand1.JPG'), 1) results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) 保存された結果から主に以下の2つを確認します。 内容 multi_handedness index, score, labelの3つが返される。indexが0ならlabelは'Left'、1なら'Right'を返し、scoreはそのラベルが当てはまるであろう確率を与える。 multi_hand_landmarks 手が検出されると、21個の検出ポイントの座標(x, y, z)が表示される。x, yはそれぞれ画像の幅と高さによって正規化されており、zは手首を原点として値が大きほど遠い場所になり、xに合わせてスケーリングされる。手が検出されないとNoneになる なお、検出ポイントは公式ガイドにもある以下の画像を参考にしています。 実際に確認してみます。 results.multi_handedness #---> #[classification { # index: 0 # score: 0.9999979734420776 # label: "Left" # }] #検出ポイントの名前のリスト landmark_list = ['WRIST', 'THUMP_CMC', 'THUMB_MCP', 'THUMB_IP', 'THUMB_TIP', 'INDEX_FINGER_MCP', 'INDEX_FINGER_PIP', 'INDEX_FINGER_DIP', 'INDEX_FINGER_TIP', 'MIDDLE_FINGER_MCP', 'MIDDLE_FINGER_PIP', 'MIDDLE_FINGER_DIP', 'MIDDLE_FINGER_TIP', 'RING_FINGER_MCP', 'RING_FINGER_PIP', 'RING_FINGER_DIP', 'RING_FINGER_TIP', 'PINKY_MCP', 'PINKY_PIP', 'PINKY_DIP', 'PINKY_TIP'] for hand_landmarks in results.multi_hand_landmarks: for i in range(21): print(landmark_list[i]) print(hand_landmarks.landmark[i]) #---> #WRIST #x: 0.29388704895973206 #y: 0.2464490830898285 #z: -5.091690400149673e-05 # #THUMP_CMC #x: 0.3243947923183441 #y: 0.23927538096904755 #z: -0.006033017300069332 # #THUMB_MCP #x: 0.3465648889541626 #y: 0.2057970017194748 #z: -0.019823197275400162 #: #(省略) また、mediapipeにはこの検出ポイントを描画する方法も用意されているので、これを使って描画してみました。(画像は見やすくするため拡大しています) annotated_image = image.copy() for hand_landmarks in results.multi_hand_landmarks: mp.solutions.drawing_utils.draw_landmarks( annotated_image, hand_landmarks, mp_hands.HAND_CONNECTIONS, mp.solutions.drawing_styles.get_default_hand_landmarks_style(), mp.solutions.drawing_styles.get_default_hand_connections_style() ) plt.imshow(cv2.cvtColor(annotated_image, cv2.cv2.COLOR_BGR2RGB)) 手の形をちゃんと検出できているようです! いろいろな画像で手の検出を試してみる どういう画像なら手の形を検出かどうか知りたかったので、いろいろな画像で試してみることにしました。 自分の左手で成功したので、右手の写真と両手が写った写真や、手袋をしたり部屋を暗くしたりして撮った 写真を用意しました。また、肌の色が明るい人や暗い人はどうか、アニメキャラはどうかということを確かめたかったので有名人や推しの画像を用意しました。 そして以下のようにファイル名のリストを作成し、次々に読み込む準備をします。 IMAGE_FILES = [] for file in os.listdir('./hands/'): IMAGE_FILES.append('./hands/' + file) IMAGE_FILES #---> #['./hands/anime1.jpg', # './hands/anime2.jpg', # './hands/anime3.png', # './hands/hand1.JPG', # './hands/hand2.JPG', # './hands/hand3.JPG', # './hands/hand4.JPG', # './hands/hands5.JPG', # './hands/hashimotokanna.jpg', # './hands/oosakanaomi.jpg'] これを使って以下の手順で手の検出を行い、検出できた画像をannotated_handフォルダに保存していきました。 OpenCVを使ってカラー画像を読み込み、左右反転する。また、色のチャネルの順番をBGRからRGBに変更する。 hands.processで手の検出を行い、resultsに保存する。 手が検出されない場合はNo Handを表示する。手が検出された場合は左手か右手かどちらかの結果を表示し、画像に検出された地点を描画、annotated_handフォルダに保存する。 for idx, file in enumerate(IMAGE_FILES): print('file name:', file) #ファイルを読み込んで左右反転 image = cv2.flip(cv2.imread(file), 1) #色をRGBにして手の検出を実行 results = hands.process(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) #手が検出されなければ'No Hand'を表示して次の画像へ if not results.multi_hand_landmarks: print('No Hand') print('-------------------------') continue #手が検出された場合は検出ポイントを描画して保存 print('Hand label:', results.multi_handedness) annotated_image = image.copy() for hand_landmarks in results.multi_hand_landmarks: mp.solutions.drawing_utils.draw_landmarks( annotated_image, hand_landmarks, mp.solutions.hands.HAND_CONNECTIONS, mp.solutions.drawing_styles.get_default_hand_landmarks_style(), mp.solutions.drawing_styles.get_default_hand_connections_style() ) cv2.imwrite('./annotated_hand/annotated_hand' + str(idx) + '.jpg', cv2.flip(annotated_image, 1)) print('-------------------------') コードを実行すると以下のような出力になりました。 file name: ./hands/anime1.jpg Hand label: [classification {index: 0 score: 0.9999991059303284 label: "Left"}] ------------------------- file name: ./hands/anime2.jpg Hand label: [classification {index: 1 score: 0.9847505688667297 label: "Right"}, classification {index: 0 score: 0.979974091053009 label: "Left"}] ------------------------- file name: ./hands/anime3.png No Hand ------------------------- file name: ./hands/hand1.JPG Hand label: [classification {index: 0 score: 1.0 label: "Left"}] ------------------------- file name: ./hands/hand2.JPG Hand label: [classification {index: 1 score: 0.9999518394470215 label: "Right"}] ------------------------- file name: ./hands/hand3.JPG Hand label: [classification {index: 0 score: 0.9999997615814209 label: "Left"}, classification {index: 0 score: 0.9835185408592224 label: "Left"}] ------------------------- file name: ./hands/hand4.JPG No Hand ------------------------- file name: ./hands/hands5.JPG Hand label: [classification {index: 0 score: 0.9993670582771301 label: "Left"}] ------------------------- file name: ./hands/hashimotokanna.jpg Hand label: [classification {index: 1 score: 0.9999998807907104 label: "Right"}, classification {index: 1 score: 0.9998670220375061 label: "Right"}] ------------------------- file name: ./hands/oosakanaomi.jpg Hand label: [classification {index: 0 score: 0.9999979734420776 label: "Left"}] ------------------------- 出力結果を見ると、写真のうち2つは手が検出されていないことがわかります。検出されなかったのは手袋をつけた自分の手の写真とアニメキャラのイラストです。イラストについてはこの画像以外にも何枚か試しましたがほとんど検出されませんでした。また、annotated_handフォルダには以下のように検出ポイントが描画された写真が保存されています。 いくつかの結果を観察します。 この画像はどちらも左手と判断されています。各指の検出ポイントには誤りがないので、表向きか裏向きかが判断できていないようです。また、左手は隠れている指まで推定して検出することができています! この画像は人差し指と中指の検出ポイントに誤りがあります。あまりない複雑な手の形をしていると検出ポイントを追うのは難しいのかな、と思います。 この画像では、手袋をしている手の部分は検出されずに肩章の部分が手と判断されてしまっています。色合いと形が似ていると言われれば似ているかもしれません。 結果から感じたこととして、標準的な形をした人の手の画像はちゃん検出することができ、写っていない部分も上手に推定してくれているようです。逆にアニメキャラ、奥行が感じにくい、標準的な肌色でない、などの条件がつくと上手に検出できないようでした。 動画で手を検出する 今度は自分の手の動画と手がわかりやすそうなアニメ動画で検出を行い、検出ポイントを描画して保存しました。検出器をインスタンス化する際に静止画のときと異なるのは以下の2点です。 static_image_modeをFalseにする min_tracking_confidenceを設定すると、一つ前のフレームで検出された手を追跡するようになります。1に近づけるほど検出に時間はかかりますがよりロバスト性のある検出ができる cap = cv2.VideoCapture('./hand_movie/hand_movie.mp4') #動画を保存するための設定 width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)) height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT)) size = (width, height) frame_count = int(cap.get(cv2.CAP_PROP_FRAME_COUNT)) frame_rate = int(cap.get(cv2.CAP_PROP_FPS)) save = cv2.VideoWriter('./hand_movie/annotated_uma.mp4', frame_count, frame_rate, size) #検出器のインスタンス化 hands = mp.solutions.hands.Hands( static_image_mode=False, max_num_hands=2, min_detection_confidence=0.5, min_tracking_confidence=0.5) while cap.isOpened(): ret, image = cap.read() if not ret: break image = cv2.cvtColor(cv2.flip(image, 1), cv2.COLOR_BGR2RGB) results = hands.process(image) image = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) if results.multi_hand_landmarks: for hand_landmarks in results.multi_hand_landmarks: mp.solutions.drawing_utils.draw_landmarks( image, hand_landmarks, mp.solutions.hands.HAND_CONNECTIONS, mp.solutions.drawing_styles.get_default_hand_landmarks_style(), mp.solutions.drawing_styles.get_default_hand_connections_style()) save.write(cv2.flip(image, 1)) cap.release() 保存された動画を確認してみると自分の手はもちろん、アニメ画像もわりとうまく検出できています! 描画だとわかりにくいですがカメラからの距離も推定できるのがすごいところだと思います。簡単なうえにわかりやすく見れるので実装していて楽しかったです!
- 投稿日:2021-09-01T23:33:11+09:00
AI Quest2021のアセスメントでfolium使ってみました
はじめに 現在、自動車部品メーカーでエンジニアをしております。 今年の5月からAidemyを受講し、Pythonの機械学習を学び始めた初学者になります。 最近データビジュアライゼーションを勉強中で、2021/8/9に締め切られたAI Quest2021アセスメントのデータをfoliumを使ってみました。 今回foliumを使ってみると思ったよりも色々考察できそうなところがありましたので、投稿しました。 アセスメントの解法については、shu421さんが、ご丁寧解説されているので、そちらをご参考ください。 foliumを使ったEDA train_df= pd.read_csv('../input/train.csv') test_df= pd.read_csv('../input/test.csv') #各都市にデータを振り分け train_LA = train_df.loc[train_df.city == 'LA'] train_DC = train_df.loc[train_df.city == 'DC'] train_NYC = train_df.loc[train_df.city == 'NYC'] train_SF = train_df.loc[train_df.city == 'SF'] train_Chicago = train_df.loc[train_df.city == 'Chicago'] train_Boston = train_df.loc[train_df.city == 'Boston'] #ここでは、ロサンゼルスに絞って分析します。 train_LA_lat_mean=train_LA.latitude.mean() train_LA_lng_mean=train_LA.longitude.mean() #train_LA_y_max = train_LA.y.max() 今回のデータは上限側に大きく振れるものがあるので使っていません。 train_LA_y_min = train_LA.y.min() import folium from folium.plugins import HeatMap import branca.colormap as cm m = folium.Map(location=[train_LA_lat_mean, train_LA_lng_mean], zoom_start = 11) colormap = cm.LinearColormap(['green', 'yellow', 'red'],vmin=train_LA_y_min, vmax=500) #colormap = cm.StepColormap(colors=['green','yellow','orange','red'] ,index=[min_price,105,190,327,max_price],vmin= min_price,vmax=max_price) #cm.LinearColormap.to_step for loc, p in zip(zip(train_LA["latitude"],train_LA["longitude"]),train_LA["y"]): folium.Circle( location=loc, radius=2, fill=True, color=colormap(p), #popup= #fill_opacity=0.7 ).add_to(m) m Output: ロサンゼルスでは、海岸沿の価格が高くなりそうです。 この海岸沿いにあるかどうかを特徴量にしても良さそうです。 続いてニューヨークの価格を見てみます。 train_NYC_lat_mean=train_NYC.latitude.mean() train_NYC_lng_mean=train_NYC.longitude.mean() m = folium.Map(location=[train_NYC_lat_mean, train_NYC_lng_mean], zoom_start = 11) colormap = cm.LinearColormap(['green', 'yellow', 'red'],vmin=0, vmax=500) #colormap = cm.StepColormap(colors=['green','yellow','orange','red'] ,index=[min_price,105,190,327,max_price],vmin= min_price,vmax=max_price) #cm.LinearColormap.to_step for loc, p in zip(zip(train_NYC["latitude"],train_NYC["longitude"]),train_NYC["y"]): folium.Circle( location=loc, radius=2, fill=True, color=colormap(p), #popup= #fill_opacity=0.7 ).add_to(m) m Output: ニューヨークも特定の場所の価格が高いことがわかります。 さらに、拡大して確認します。 価格が高いところは、高いもの同士で集まっていそうです。なので、位置の共通点を示す「neighbourhood」や「zipcode」の重要度は高くなりそうです。 実際私のLightgbmを使った予測モデルでの各特徴量の重要度としては、neighbourhoodが一番高くなりました。 よろしければ、一度folium使ってみてください。 最後に AI Quest2021に参加される皆様半年間どうぞよろしくお願いいたします。 参考
- 投稿日:2021-09-01T23:30:33+09:00
AtCoderでPythonを使ってTLEするときにやることリスト
どんな改善点があるかを思い出すためのリストです。細かい説明はしません。 PyPy として提出する 逆に Python として提出する(再帰、文字列、タプルが多いコードで有効?) input を sys.stdin.readline に置き換える in List を in Set に置き換える 累乗の計算は pow を使う 2**n を 1<<n に置き換える while を for に置き換える リストの先頭に追加や削除を行いたい場合は deque を使う リストの要素を一つずつ取り出すとき index を使ったアクセスを避ける 再帰関数を書くときは sys.setrecursionlimit(<再帰上限>) も書く heapq に追加する要素はリストでなくタプルにする(ダイクストラとか) bisect が遅いときは二分探索を書く 二次元リストを作るとき外側のリストの方が小さくなるようにする Counter+Counter を Counter.update(Counter) に置き換える 参考にした記事 https://www.kumilog.net/entry/python-speed-comp https://qiita.com/Hironsan/items/68161ee16b1c9d7b25fb https://qiita.com/c-yan/items/dbf2838cdd89864ef5ac https://linus-mk.hatenablog.com/entry/python_competitive_programming_speed_up_1
- 投稿日:2021-09-01T23:26:33+09:00
Pythonでデータ分析(回帰分析と決定木)
~連続量もしくは 0/1データ の結果を予測する~ 2020/9/2:決定木の分析過程の説明、手元データでの「決定木(回帰)」解析結果を追加 はじめに Pythonでデータ分析・・・私にとってはマダマダ夢物語に近いが、こんな私でも「見よう見まねでできるようにはなるかも?!」という気にさせてくれるのが、『データサイエンス塾!!』さんの動画だ。 多くの動画をアップしておられるが、再生リストのなかに「とてもよくわかる機械学習の基本」というタイトルの再生リストがあり、 このなかで「重回帰分析」「ロジスティック回帰分析」と「決定木」が取り上げられた動画#4~#6について、私が倣って実施してみた備忘を記録するもの。 実行条件など ・Google colabで実行 概要説明 『先を予測したい!』 こんな時に、もっとも利用されてるのは「単回帰分析」だと思いますが、ご存じの通り、これは y = ax+b というシンプルなモデルなので、例えば目的変数y が[売上]の時、[売上]をあるひとつの説明変数x で表現するのは至難。 なので、より多くの説明変数x を取り上げることができる「重回帰分析」の方がいいよということになるが、実行するのは簡単ではない。 また、目的変数y が[0/1]データ(例えば[成功/失敗]、[検出/非検出]、[すき/きらい]のような2値)のときは「ロジスティック回帰」で分析しないといけないが、”対数変換しないといけないんんよね?”、ムムム・・・さらにハードルがあがるではないかぁ。 まぁ、とにかく、こんな感じの方も多いのではないかと思う。 「データサイエンス塾!!」さんが、こんな我々のような者を察して動画を作成されているのかどうかまではわかりませんが、『「重回帰」も「ロジスティック回帰」もPython使えばこんなに簡単にできますよ』というのを、とても身近な事例データで示してくださっています。 そして最後のトドメは「決定木」。 私は名前を聞いたことがあるという程度・・・でしたが、説明を聞き「分類も予測もできるのか!」と驚いただけではなく、出力される図の完成度の高さに圧倒されました。まるでプロ依頼した調査の結果のようです。 事例データが「回帰分析」で使用されたデータと同じデータというのも、理解の助けになりました。 「とてもよくわかる機械学習の基本(動画#4~#6)」の例題について 「重回帰分析」では「アイスクリームの売上」のデータが例題として扱われています。 説明変数Xは "temperature", "price", "rainy(0/1)"、目的変数yは "sales"となっていて、 "temperature", "price", "rainy"によって"sales"がどうなるかを予測するというものとなっています。 「ロジスティック回帰分析」では「あるアプリへの登録/未登録」のデータが例題として扱われています。 説明変数は ”sex(0/1)”,”student(0/1)”,”stay home”、目的変数yは ”registration(0/1)” となっています。 ”sex”,”student”,”stay home”によって"registration"がどうなるかを予測するというものになっています。 「決定木」では、分類と回帰の2パターンの分析がなされており、分類では「ロジスティック回帰分析」の例題データ、回帰では「重回帰分析」の例題データが用いられています。 ライブラリのインストールとインポート Google colabでグラフを描画すると、(私の環境では)日本語が文字化けするので、まず以下をインストールしています。 ※文字化けしない方は無視してもいいです。 日本語化 pip install japanize-matplotlib 次にライブラリのインポートです。私はどのライブラリが必要かを逐次判断するのは面倒なので、基本、以下をデフォルトでインポートしています。(雑ですいません) ライブラリインポート # Import required libraries %matplotlib inline import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib import seaborn as sns sns.set(font="IPAexGothic") from scipy.stats import norm 決定木のライブラリもインストール pip install dtreeviz データの読み込み 例題ファイル(CSV)をダウンロードし、Google colabにアップロードします。 ダウンロード対象ファイルは以下4つです。 ※Google Colabへのアップロードの方法はここでは述べません。 重回帰分析 まずは「重回帰分析」です。 読込むデータは2つあり、以後、これで「アイスクリームの売上」の予測を行うという流れになります。 以下のコードを実行すると、アイスクリームの売上実績データである「sales.csv」が「df_past」というデータフレームに、アイスクリームの売上を予測したいデータ「sales_future.csv」が「df_future」に格納されます。 csv読込み df_past = pd.read_csv('4-4_sales.csv') df_future = pd.read_csv('4-4_sales_future.csv') 「df_past」のカラムを見てみましょう。 カラムみてみる df_past.columns Index(['temperature', 'price', 'rainy', 'sales'], dtype='object') この例では、"temperature", "price", "rainy" という説明変数Xで、"sales"という目的変数yを予測することになりますので、まず、以下のコードでX,yの割り当てを行います。 X,yの割り当て X_name = ['temperature', 'price', 'rainy'] X = df_past[X_name] y = df_past['sales'] X,y が割当てられれば、早速、重回帰分析の実行です。 なお、[sm.OLS]の [OLS] は線形回帰で、m.add_constant(X)は切片付きで求める指示となるようです。 重回帰分析の実行 import statsmodels.api as sm model = sm.OLS(y,sm.add_constant(X)) result = model.fit() result.summary() これが、重回帰分析の結果です。 priceのP値(出力された表の P>|t| の値)が≧0.05となっていますので、説明変数から取り除くことを検討してもよさそうです。Adj.R-squared(自由度調整済決定係数)は0.825であり悪くはありません。 この結果を受け、説明変数からPriceを除いて、再度、Xを割り当てます。 X,yの割り当て② X_name = ['temperature', 'rainy'] X = df_past[X_name] y = df_past['sales'] もう一度、重回帰分析を実行します。 重回帰分析の実行② import statsmodels.api as sm model = sm.OLS(y,sm.add_constant(X)) result = model.fit() result.summary() これが、説明変数からpriceを取り除いて再実行した重回帰分析の結果です。 説明変数のP値(出力された表の P>|t| の値)はいずれも≦0.05となっています。Adj.R-squared(自由度調整済決定係数)は0.826であり、これは先ほどの結果と違いはほとんどありません。 予測 最後に、予測です。 あらかじめ用意されていた「df_future」のデータの予測値を算出します。 予測 result.predict(sm.add_constant(df_future[X_name])) 以下が出力値です。 0 692.242462 1 804.817001 2 399.804427 3 811.363808 4 890.584370 dtype: float64 予測データと予測値の対応は以下の通りとなりました。 関係式は以下になりますので、予測データのひとつ目のデータのみ確認してみます。 y(sales) = tempereture × coef + rainy × coef + const(切片) y(sales) = 12.6℃ × 47.6485 + 1 × 178.0815 + (-86.2107) = 692.2419 ロジスティック回帰分析 次は「ロジスティック回帰分析」です。 読込むデータは2つあり、以後、これで「あるアプリへの登録/未登録」を予測するという流れになります。 以下のコードを実行すると、あるアプリへの登録/未登録の実績データ「user_data.csv」が「df_past」というデータフレームに、アイスクリームの売上を予測したいデータ「user_data_future.csv」が「df_future」に格納されます。 csv読込み df_past = pd.read_csv('4-5_user_data.csv') df_future = pd.read_csv('4-5_user_data_future.csv') 「df_past」のカラムを見てみましょう。 カラムみてみる df_past.columns Index(['sex', 'student', 'stay time', 'registration'], dtype='object') この例では、'sex', 'student', 'stay time' という説明変数X で、'registration'という目的変数y を予測することになるので、まず、以下のコードで X,y の割り当てを行います。 X,yの割り当て X_name = ['sex', 'student', 'stay time'] X = df_past[X_name] y = df_past['registration'] X,y が割当てられれば、早速、ロジスティック回帰分析の実行です。 なお、インポートライブラリや、重回帰で [sm.OLS] とされていた箇所が [sm.Logit] とされる点が重回帰との違いとなってます。 ロジスティック回帰分析の実行 import statsmodels.api as sm from sklearn.linear_model import LogisticRegression #ロジット回帰の場合、このライブラリが必要 model = sm.Logit(y,sm.add_constant(X)) result = model.fit() result.summary() これが、ロジスティック回帰分析の結果です。 sexのP値(出力された表の P>|z| の値)か≧0.05となっていますので、説明変数から取り除くことを検討してもよさそうです。 この結果を受け、説明変数からsexを除き、再度、X を割り当てます。 X,yの割り当て② X_name = ['student', 'stay time'] X = df_past[X_name] y = df_past['registration'] もう一度、ロジスティック回帰分析を実行します。 ロジスティック回帰分析の実行② import statsmodels.api as sm from sklearn.linear_model import LogisticRegression #ロジット回帰の場合、このライブラリが必要 model = sm.Logit(y,sm.add_constant(X)) result = model.fit() result.summary() 説明変数からsexを取り除いて再実行したロジスティック回帰分析の結果です。 説明変数のP値(出力された表の P>|z| の値)はいずれも≦0.05となっています。 目的変数は、よりすくない説明変数で説明できたほうがよいですが、Pseudo R-squ.が説明変数を取り除く前後で大きく変わらないことは確認しておいた方がよいようです。 この例では、先ほどとの違いはほとんどありません。 予測 最後に、あらかじめ用意されていた「df_future」のデータの予測値を算出します。 予測 result.predict(sm.add_constant(df_future[X_name])) 以下が出力値です。 0 0.617041 1 0.292847 2 0.034132 3 0.815431 4 0.508296 5 0.082051 6 0.461241 7 0.715211 8 0.382695 9 0.607463 dtype: float64 予測データと予測値(predict)の対応は以下の通りとなりました。 この事例の目的変数yは、registration=1,not registration=0 なので、予測値(predict)は、0~1の確率として表現されています。 決定木分析(分類編) CSVファイルの読み込み 以下コードの実行により、「df.past」というデータフレームに「user_data,csv」のデータが、「df_future」というデータフレームに「user_data_future」のデータが納められます。 csv読込み #分類問題 df_past = pd.read_csv("4-5_user_data.csv") df_future = pd.read_csv("4-5_user_data_future.csv") X_name = ["sex","student","stay time"] y_name = "registration" X = df_past[X_name] y = df_past[y_name] 決定木の実行 まず、「df‗past」に格納したデータを見てみます。 df_pastのデータ df_past.head() 次に「決定木」の実行に必要なライブラリをインポートします。 ライブラリのインポート from sklearn import tree from dtreeviz.trees import * import graphviz いよいよ、決定木分析の実行です。 決定木分析と出力 dtree = tree.DecisionTreeClassifier(max_depth=2) dtree.fit(X,y) viz = dtreeviz(dtree,X,y, target_name = y_name, feature_names = X_name, class_names = ["not register","register"]) viz 結果の考察 まず一つ目の分岐ですが、stay time が44.5以下か、44.5よりも大きいかで分かれています。 「stay time≦44.5」の時、「register」は[0]となっています。 次の分岐は student(学生)か否かです。「stay time>44.5」の場合に register はあらわれ、内訳としては student の方がregister されるケースが多いことがわかります。 とても分かりやすく、かつ見やすいですね。 ある条件の予測 次にある結果の予想です。 事例の説明変数Xは、sex, student, stay time の3つ。男性, 学生, 滞在時間50 の場合、これを数値で表すと、X = [1,1,50] になります。 これを引数にセットして予想してみます。 決定木分析と出力(予測) dtree = tree.DecisionTreeClassifier(max_depth=2) dtree.fit(X,y) viz = dtreeviz(dtree,X,y, target_name = y_name, feature_names = X_name, class_names = ["not register","register"], X = [1,1,50] ) viz 男性,学生,滞在時間50 = [1,1,50]で予測した結果が表示されました。 予測条件は、各分岐のグラフ上で▲で示され、どちらの分岐かはオレンジの枠で囲われて示され、とてもよくわかります。 この例では、最終の予測結果が register となっています。 まとまった対象の予測 次に、予測したい対象一覧(df_futureに格納したもの)を取り込んで、予測にかけます。 決定木分析と出力(予測) dtree.predict(df_future) array([1, 0, 0, 1, 1, 0, 0, 1, 0, 1]) 分類予測データと分類予測結果(predict)の対応は以下の通りとなりました。 分類予測データでstudent=1となっているデータは、stay time≧44.5 となっていますので、student=1 → predct=1 となっています。先ほどの図表記の内容からもこの結果は素直に読み取れます。 決定木分析(回帰編) まず、この分析で使用するデータを読込みます。 csv読込み #回帰問題 df_past = pd.read_csv("4-6_sales.csv") df_future = pd.read_csv("4-6_sales_future.csv") X_name = ["temperature", "price", "rainy"] y_name = "sales" X = df_past[X_name] y = df_past[y_name] 早速、分析を行います。 決定木分析と出力 dtree = tree.DecisionTreeRegressor(max_depth=2) dtree.fit(X,y) viz = dtreeviz(dtree,X,y, target_name = y_name, feature_names = X_name, #X = [20,220,0] ) viz はじめにtemperature(19.35℃以下 or 19.35℃より大きい)で分岐し、次もtemperatureで分岐しています。 左側の分岐から先は、14.2℃以下なら sales は400.93に, 14.2℃より大きいなら609.00に、 右側の分岐から先をみると、24.9℃以下ならsalesは873.44に, 24.9℃より大きいならsalesは1347.33となっています。 temperatureがsalesに影響していること、それぞれの分岐においてどの程度のsalesとなるかがわかります。 ある条件の予測 次にある条件の予測です。 事例の説明変数Xは、temperature, price, rainy の3つ。20℃, 220円, 晴れ の場合、これを数値で表すと、X = [20,220,0] になります。 これを引数にセットして予測します。 X=[20,220,0]の予測 dtree = tree.DecisionTreeRegressor(max_depth=2) dtree.fit(X,y) viz = dtreeviz(dtree,X,y, target_name = y_name, feature_names = X_name, X = [20,220,0] ) viz 20℃, 220円, 晴れ = [20,220,0]で予測した結果が表示されました。 予測条件は、各分岐のグラフ上で▲で示され、どちらの分岐かはオレンジの枠で囲われて示され、とてもよくわかります。 この条件におけるsales予測結果は[873.44]となっています。 まとまった対象の予測 次に、予測したい対象一覧(df_futureに格納したもの)を取り込んで、予測にかけます。 決定木分析と出力(予測) dtree.predict(df_future) array([470.93333333, 609. , 470.93333333, 609. , 873.44444444]) 予測データと予測結果(predict)の対応は以下の通りとなりました。 先ほどの図表記の内容からもこの結果は素直に読み取れます。 手元にあったCSアンケート結果で「決定木(回帰)」を行ってみた ※2021/9/2追加 データ数=311 説明変数は Tec.Skill(技術的フォロー)、Complain Act.(クレーム対応)、Call Act.(電話対応)、 Visit number(訪問回数)、Advice(アドバイス)、return act.(返品対応)、QA(保証期間)の7つ 目的変数は、S-CS(営業CS)。 すべての項目が0~6の整数値。(※0は未記入データの可能性もあるが・・・気にせず実行) max_depth=2,3,4 とみた上、いちばん解釈しやすいと思えた [3] とした。 結果 考察 満足度が高いケース 電話対応(Call Act.)がよく、よいアドバイス(Advice)をくれるのはとてもありがたい 電話対応(Call Act.)がよく、アドバイス(Advice)がそこそこでも技術的フォロー(Tec.Skill)はしてくれるならよしとしよう 電話対応(Call Act.)はあまりよくないても、訪問回数(Visit number)は適切で、よいアドバイス(Advice)をくれるならいいよ 満足度が低いケース 電話対応(Call Act.)、訪問回数(Visit number)によい印象がない、さらに返品対応(return act.)も悪いと最悪になる 電話対応(Call Act.)がよくても、アドバイス(Advice)なく、技術的フォロー(Tec.Skill)も乏しいとダメ 電話対応(Call Act.)はあまりよくなくても、訪問回数(Visit number)が適切なら・・・ただ、何のアドバイス(Advice)もない営業はちょっとな これまで、平均値を算出したり、ポートフォリオを描いたり、いろんな見方をしてきたが、この「決定木」による分析ほど状況を立体的に掴むことはできていなかったように思います。 「決定木」は、すごいな。 参考サイト
- 投稿日:2021-09-01T23:25:46+09:00
Pythonでデータ分析(特異値分解とクラスタリング)
~複数の評価尺度で点数づけされたデータをシンプルにしたり、グルーピングしたりする~ はじめに Pythonでデータ分析・・・私にとってはマダマダ夢物語に近いが、そんな状況の私でも「見よう見まねでできるようにはなるかも?!」という気にさせてくれるのが、『データサイエンス塾!!』さんだ。 多くの動画をアップしておられるが、再生リストのなかに「とてもよくわかる機械学習の基本」(全11の動画)というタイトルの再生リストがあり、 同じデータが扱われている動画#1~#3について、私が倣って実施した備忘として記録するもの。 実行条件など ・Google colabで実行 ・一部のコードだけ変更させていただいています ※あるグラフをseaborn+層別で描きたかった等によるものです。データサイエンス塾!!さん、すいません。 概要説明 私たちは「いくつかの尺度をあげ、対象候補を比較する」ということを日常的に行っている。 携帯電話なら、尺度は『サイズ,容量,機能,…』等となり、対象候補はA社,B社…。 店舗なら、尺度は『サービス,立地,品揃え,…』等となり、対象候補はA店,B店…。 といった内容になる。 尺度、対象候補ともに限られたデータ数ならば把握はできそうであるが、データ数が多くなると簡単ではない。 ① 尺度が多い場合は、限られた要素にギュッとまとめて表現できるとよいし、 ② 対象候補が多い場合は、限られたグループに分けられるとよい。 この例は、上記①②をPythonを使って、こんな形で分析できるよ!を示してくれている。 ① 尺度をギュッとまとめる ⇒ SVD(特異値分解)という手法を利用 ② 対象候補を限られたグループに分ける ⇒ k-means法 及び クラスター分析を利用 以降の流れは、k-means法でグループ分けを行い ⇒ SVD(特異値分解)で尺度を単純化 ⇒ その後、k-means法とSVDの結果の合わせ技の分析結果 という流れです。 また、最後にk-meansとは異なるグループ分けの方法であるクラスター分析も行うというものです。 「とてもよくわかる機械学習の基本(動画#1~#3)」の例題について 例題は「人のスキル」データとされている。 尺度は『コミュニケーション,リーダーシップ,プログラミング,ネットワーク知識,セキュリティ知識』、対象候補はAさん~Oさん(全15名)。 データ分析により、よりシンプルな尺度で、対象候補をいくつかのグループに分けることができ、その結果、”「人のスキル」全体を眺めるとこんな傾向があるといえるね!” などといえる・・・が目指したいゴールとなる。 ライブラリのインストールおよびインポート Google colabでグラフ描画すると、日本語が文字化けするので、まず以下をインストール。 ※文字化けしない方は無視していい 日本語化 pip install japanize-matplotlib ライブラリのインポート。私はどのライブラリが必要かを逐次判断するのは面倒なので、基本以下をデフォルトでインポートしています。(雑ですいません) ライブラリインポート # Import required libraries %matplotlib inline import pandas as pd import numpy as np import matplotlib.pyplot as plt import japanize_matplotlib import seaborn as sns sns.set(font="IPAexGothic") from scipy.stats import norm データの読み込み 例題ファイル(CSV)をダウンロードし、Google colabにアップロード ※Google Colabへのアップロードの方法はここでは述べません。 CSVファイルの読み込み。 以下コードの実行により、「df」というデータフレームにcsvデータが納められる。 csv読込み # csvファイル読込み(indexとして使いたい列の列番号がある場合は指定:今回は index_col=0) df = pd.read_csv('4-2_skill_level.csv', index_col=0) k-meansの実行 k-meansは、k平均法と呼ばれるクラスタリング手法のひとつ。 読込んだデータは、対象候補 Aさん~Oさん(全15名)のスキル(コミュニケーション,リーダーシップ,プログラミング,ネットワーク知識,セキュリティ知識)が10段階数値で示されたものです。 「クラスタ数(いくつのグループに分けたいか?)」を設定し、以下のコード実行するだけで、対象候補 Aさん~Oさん(全15名)を設定したグループ数に分けてくれる。 k-means実行 # k-means実行 from sklearn.cluster import KMeans クラスター数の指定 # クラスター数を任意に指定する vec = KMeans(n_clusters=4) group_num = vec.fit_predict(df) 分けられたグループが「group_num」というデータフレームに入ったかを見てみる。 グループ分けの確認 # グループ分けの結果(k-meansは計算の度に結果変わる) group_num 出力結果 array([3, 2, 1, 3, 0, 3, 2, 3, 1, 2, 1, 0, 1, 1, 0], dtype=int32) グラフ化などを進めるため、上記グループ分けの結果をデータフレームに反映する処理を行う。 まずは、新たにデータフレーム「df_calc」を用意し、 グループ分けの結果をデータフレームに反映① # df_calcというデータフレームにdfをコピー df_calc = df.copy() 次に、「df_calc」にグループ分けの結果「group_num」を反映します。 グループ分けの結果をデータフレームに反映② # df_calcに['グループ名']を追加し、group_numのデータを登録 df_calc['グループ名'] = group_num df_calc 以下が反映後の結果。 データ傾向を見てみる 平均値 グループ毎の集計が可能となったので、グループ毎の平均値を算出します。 グループ平均値 #グループ毎に平均値を出す df_calc.groupby('グループ名').mean() 散布図行列 グループ分けした傾向を散布図行列(ペアプロット)で眺めるということをしていたサイトがあったので、同様にやってみた。ただ、これは参考程度かと思う。 ペアプロット描画 sns.pairplot(data=df_calc,hue="グループ名",palette='Blues') SVD(特異値分解)の実行 以下のコードでSVD(特異値分解)が実行できる。 SVDで軸の意味を検討 # 評価尺度を平面に配置(※X軸,Y軸がどのような軸かは人が読み取らないといけない) X_comp,Y_comp = model_svd.components_ plt.figure(figsize=(6,6)) sns.scatterplot(x=X_comp,y=Y_comp) plt.title('SVD', fontsize=18) # グラフタイトル for i,(annot_x,annot_y) in enumerate(zip(X_comp,Y_comp)): plt.annotate(df.columns[i],(annot_x,annot_y)) 以下の通り、平面上に「尺度」が配置された。 例えば、右方向「スキルレベル」、上方向「ヒューマンスキル」、下方向「ITスキル」となるな・・・等、これは人が読み取って名付けるしかない。 今度は、各サンプル(Aさん~Oさん)を平面座標に配置する手続きになる。 #SVDによる各サンプルの座標計算 from sklearn.decomposition import TruncatedSVD model_svd = TruncatedSVD(n_components=2) vecs_list = model_svd.fit_transform(df) 配置された座標を確認してみます。 vecs_list 出力結果 この座標「vecs_list」を元のデータフレーム(df_calc)に結合します。 データフレーム(df_calc)にSVDで導いた座標(X,Y)を結合 # df_calcに['X']['Y']列を追加、それぞれにvecs_listの0列目と1列目データを登録 df_calc['X'] = vecs_list[:,0] df_calc['Y'] = vecs_list[:,1] 結合した結果を見てみます。 結合結果の確認 # df_calcへのX,Yデータ追加を確認 df_calc X,Yデータが結合されました。 グループ化した結果を平面上に可視化 最後に、SVDで尺度を要約した(平面座標X,Y)に、k-meansでグループ分けしたサンプル(Aさん~Oさん)を配置します。 SVDで導いた座標にクラスター分けしたサンプルを配置 # SVD(特異値分解)した平面にk-meansでクラスター分け(グループ名)したサンプルをグラフに配置 plt.figure(figsize=(6,6)) sns.scatterplot(data=df_calc,x='X',y='Y',hue="グループ名") plt.title('k-means + SVD', fontsize=18) # グラフタイトル for i,(x_name,y_name) in enumerate(zip(X,Y)): plt.annotate(df.index[i],(x_name,y_name)) plt.show() 以下が配置の結果です。 グループ0は中央下に、グループ1は右下に、グループ2に右上に、グループ4は左下に配置されています。 軸の意味合いは、右方向「スキルレベル」、上方向「ヒューマンスキル」、下方向「ITスキル」ということでしたので、『グループ1はスキルレベルが高く、特にヒューマンスキルが顕著』、『グループ2もスキルレベルが高く、特にITスキルが顕著』・・・等、グループの特徴の違い、各グループにどのサンプルが属すか?等が把握できる。 クラスター分析 例題データの尺度の単純化とグループ分けは、上記までの流れで一旦完了となる。 先はSVDで尺度を単純化、k-meansでグループ分け・・・というものであったが、グループ分けの方法は以下に示す「クラスター分析」による方法もある。 分析の結果、出力される「デンドログラム(樹形図)」をみるとわかるが、サンプルがトーナメント表のような形で表現される。 似たサンプルほど近くに配置されるのは、k-meansと同じであるが、平面に打点されるk-meansよりもデンドログラムの方がサンプル間の関係がより細かく把握できるという特徴がある。 データサイエンス塾!!さん曰く、サンプル数が少ない場合はクラスター分析=デンドログラムでもよいが、サンプルが多い場合は(デンドログラムは見にくくなるので)k-meansのほうがよいとのことでした。 クラスター分析実行 # クラスター分析実行(methodでクラスター分析手法[ward法が一般]、metricで分類に用いる対象間の距離計算方法を指定[euclidean=ユークリッド距離]) from scipy.cluster.hierarchy import linkage,dendrogram,fcluster linkage_result = linkage(df, method = "ward", metric = "euclidean") linkage_result 出力結果 以下コードの実行により、デンドログラムが描画できます。 デンドログラム(樹形図)描画 # デンドログラムの描画 dendrogram(linkage_result, labels = df.index) plt.show() 描けました。 最後に Pythonで、データを読込み、グラフ表示・・・くらいのことしかできなかった私に希望を与えてくれたのは、まちがいなくデータサイエンス塾!!さんです。 マダマダ自力で何とかできるレベルではないことは自覚していますが、なんかできそうな気にさせてくれた。 本当に感謝です。 参考サイト
- 投稿日:2021-09-01T22:45:11+09:00
pandasで気象庁のアメダスデータをスクレイピング
気象庁ホームページのの過去のアメダスデータから、Pythonでデータを取り出すことを考える。例として福岡県八幡の1983年の月ごとの値のページの合計降水量を取得してみる。 Pythonでデータを取り出す(スクレイピングする)方法は色々あるが、ここではpandasのread_htmlを使って簡単にやってしまおう。HTMLを解析する必要はない。 padasを読み込む。 import pandas as pd URLはこれ。 url="https://www.data.jma.go.jp/obd/stats/etrn/view/monthly_a1.php?prec_no=82&block_no=0780&year=1983&month=&day=&view=" read_htmlを使う。これでWeb内のテーブルが読み込める。 table=pd.read_html(url) 出力はリストで2つ内容が入っていることがわかる。 print(type(table)) print(len(table)) <class 'list'> 2 1番目の中身。 print(table[0]) 月 降水量(mm) 気温(℃) 風向・風速(m/s) \ 月 合計 日最大 最大 平均 最高 最低 平均風速 月 合計 日最大 1時間 10分間 日平均 日最高 日最低 最高 最低 平均風速 0 1 65 22 9 /// 5.9 9.1 2.6 15.9 -1.0 2.4 1 2 84 22 10 /// 5.0 8.2 ) 1.3 ) 14.1 ) -3.9 ) 2.4 2 3 250 47 19 /// 9.0 12.2 5.7 20.3 -0.4 1.9 3 4 205 59 12 /// 15.3 19.1 ) 10.7 ) 27.7 ) 3.9 ) 2.3 4 5 154 64 20 /// 18.4 22.7 14.2 28.2 9.6 1.8 5 6 216 77 13 /// 21.5 25.4 ) 17.8 ) 29.2 ) 13.9 ) 1.8 6 7 280 125 20 /// 25.6 28.8 22.8 33.6 17.8 2.6 7 8 192 110 30 /// 27.2 30.7 24.2 35.1 20.9 2.1 8 9 212 60 32 /// 24.0 27.4 21.3 31.8 15.6 1.9 9 10 88 22 7 /// 17.8 21.7 13.9 26.9 7.1 1.5 10 11 37 23 5 /// 11.3 15.4 7.4 20.1 1.9 2.1 11 12 45 13 5 /// 6.4 9.2 ) 3.0 ) 16.1 ) -1.7 ) 2.6 日照時間(h) 雪(cm) 最大風速 最大瞬間風速 日照時間(h) 降雪の合計 日降雪の最大 最深積雪 風速 風向 風速 風向 日照時間(h) 降雪の合計 日降雪の最大 最深積雪 0 6 西北西 /// /// 146.6 /// /// /// 1 9 西北西 /// /// 152.5 /// /// /// 2 7 西 /// /// 150.7 /// /// /// 3 8 西南西 /// /// 186.4 /// /// /// 4 8 南南西 /// /// 246.1 /// /// /// 5 9 南東 /// /// 220.4 /// /// /// 6 7 南南西 /// /// 220.8 /// /// /// 7 7 南南西 /// /// 275.3 /// /// /// 8 7 北 /// /// 185.5 /// /// /// 9 5 南南西 /// /// 205.6 /// /// /// 10 7 西北西 /// /// 188.1 /// /// /// 11 7 西北西 /// /// 160.5 /// /// /// 2番目の中身。 print(table[1]) 0 1 2 3 0 利用される方へ よくある質問(FAQ) 気象観測統計の解説 年・季節・各月の天候 ということで、1番目の中身が欲しいテーブルだ。そこで、リストの1番目から中身を取りだして、typeを確かめて見れば、pandasのDataFrameになっていることがわかる。 df=table[0] print(type(df)) <class 'pandas.core.frame.DataFrame'> コラムの内容を見てみる。 df.columns MultiIndex([( '月', '月', '月'), ( '降水量(mm)', '合計', '合計'), ( '降水量(mm)', '日最大', '日最大'), ( '降水量(mm)', '最大', '1時間'), ( '降水量(mm)', '最大', '10分間'), ( '気温(℃)', '平均', '日平均'), ( '気温(℃)', '平均', '日最高'), ( '気温(℃)', '平均', '日最低'), ( '気温(℃)', '最高', '最高'), ( '気温(℃)', '最低', '最低'), ('風向・風速(m/s)', '平均風速', '平均風速'), ('風向・風速(m/s)', '最大風速', '風速'), ('風向・風速(m/s)', '最大風速', '風向'), ('風向・風速(m/s)', '最大瞬間風速', '風速'), ('風向・風速(m/s)', '最大瞬間風速', '風向'), ( '日照時間(h)', '日照時間(h)', '日照時間(h)'), ( '雪(cm)', '降雪の合計', '降雪の合計'), ( '雪(cm)', '日降雪の最大', '日降雪の最大'), ( '雪(cm)', '最深積雪', '最深積雪')], ) マルチインデックスのコラム名になってややこしいようであるが、ようはつなげていけば、細目に降りていく。 df["降水量(mm)"] 合計 日最大 最大 合計 日最大 1時間 10分間 0 65 22 9 /// 1 84 22 10 /// 2 250 47 19 /// 3 205 59 12 /// 4 154 64 20 /// 5 216 77 13 /// 6 280 125 20 /// 7 192 110 30 /// 8 212 60 32 /// 9 88 22 7 /// 10 37 23 5 /// 11 45 13 5 /// df["降水量(mm)"]["合計"] 合計 0 65 1 84 2 250 3 205 4 154 5 216 6 280 7 192 8 212 9 88 10 37 11 45 df["降水量(mm)"]["合計"]["合計"] 0 65 1 84 2 250 3 205 4 154 5 216 6 280 7 192 8 212 9 88 10 37 11 45 Name: 合計, dtype: int64 最後の出力のtypeを確認すると、pandasのSeries形式になっている。 type(df["降水量(mm)"]["合計"]["合計"]) pandas.core.series.Series 後は、インデックスを日付形式にしたり、年でforループを回して連結したり、他の地点に変えたりと、いかようにも扱えるはず。
- 投稿日:2021-09-01T21:59:08+09:00
コロナワクチン予約システムに自動ログインして予約可能会場があったら音を鳴らす
ログインしてチェックボックスにチェックを入れるだけの簡単なコード こんなURLの自治体じゃないと対応してないと思います https://vaccines.sciseed.jp/${市とか区が入るっぽい} 澁谷だとこんな感じの予約サイト これをurl1に入れてね コード import time from selenium import webdriver from selenium.webdriver.chrome.options import Options import winsound # 接種券番号とか入れてね account = '' password = '' url1 = '' url2 = f'{url1}department/search' def login_kakunin(): driver.get(url1) time.sleep(5) # login id = driver.find_elements_by_class_name("validate-input_input") id[0].send_keys(account) id[1].send_keys(password) chk = driver.find_element_by_xpath("//input[@type='checkbox']") chk.click() btn = driver.find_element_by_xpath("//button[@type='submit']") btn.click() time.sleep(5) # 接種会場 driver.get(url2) chk = driver.find_element_by_xpath("//input[@type='checkbox']") chk.click() ifText = driver.find_element_by_class_name( "page-department-search_nav-header__count") if ifText.text == "会場表示: 0件": print("空いている会場なし") time.sleep(2) driver.close() driver.quit() else: print("空いてる会場あり") winsound.Beep(800, 1000) # Winだけです # print("\007") これでも何か鳴るのでmacなどで options = Options() # options.binary_location = '/usr/bin/google-chrome' mac/linux # driver = webdriver.Chrome('chromedriver', options=options) mac/linux options.binary_location = "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" driver = webdriver.Chrome( options=options, executable_path="C:\\_dev\\seleniumDev\\chromedriver.exe") # ログイン処理 login_kakunin() 余談 作って2分くらいで空き見つかったので供養 cronなりタスクスケジューラで動かせばそのうち音が鳴ります 音が鳴っても予約できると思うなよ
- 投稿日:2021-09-01T20:40:15+09:00
議事録ファイルをPython正規表現置換でDBに突っ込むINSERT文を作る
例えば、こんな文章 第1回 スーパーシティ型国家戦略特別区域の区域指定に関する専門調査会(議事要旨) (議事本題の前の部分や、フッタの「第1回 スーパーシティ型国家戦略特別区域の区域指定に関する専門調査会」は削除、前半数ページ抜粋) ○喜多参事官 ただいまより「スーパーシティ型国家戦略特別区域の区域指定に関する専 門調査会」を開会いたします。 会議の出席者はお手元の資料を御覧ください。 初めに、坂本大臣より発言をよろしくお願いいたします。 ○坂本大臣 本日は、お集まりいただき誠にありがとうございます。 近年、AIやビッグデータを活用し、社会の在り方を根本から変えるような都市設計の動 きが国際的に急速に進展しています。しかしながら、世界において、いまだ「丸ごと未来 都市」は実現されていません。我が国のスーパーシティは住民が参画し、住民目線で2030 年頃に実現される未来社会の先行実現を目指すものです。 そのポイントは3点あります。 1つ目は、生活全般にまたがる複数分野の先端的サービス 2つ目は、複数分野間でのデータ連携 3 3つ目は、大胆な規制改革であります。 スーパーシティは、国家戦略特区制度に基づきまして、大胆な規制改革を通して経済社 会の構造改革を牽引する役割であります。また、地域のデジタル化の拠点をつくる取組で もあり、今後9月に発足するデジタル庁とも密接に連携しながら、その取組を加速化して まいります。 今般、情報・デジタル等の専門家にも御参加いただきまして専門調査会を開催し、スー パーシティの区域指定について議論することとなりました。委員や出席者の皆様におかれ ましては、自治体からの提案や今後の進め方について忌憚のない御意見を賜り、スーパー シティ構想の実現に向け、御協力をいただければ幸いでございます。 本日は、どうぞよろしくお願いいたします。お世話になります。 ○喜多参事官 坂本大臣、ありがとうございました。 それでは、プレスの皆様は御退出願います。 (プレス退室) ○喜多参事官 早速議事に入ります。本日は第1回の会合ですので、最初に本専門調査会 の運営規則(案)についてお諮りしたいと存じます。 お手元の資料1を御覧ください。本運営規則におきましては、議事の進め方、議事内容 の公表等について定めております。運営規則案について、事前にお送りしておりますが、 このように定めさせていただきたいと存じますが、御異議等はございませんでしょうか。 (「異議なし」の声あり) ○喜多参事官 ありがとうございます。 続いて、議事2「地方公共団体からのスーパーシティの提案等」についてです。事務局 より説明いたします。 ○三浦審議官 内閣府の審議官の三浦でございます。今週着任いたしました。どうぞよろ しくお願いいたします。座って失礼いたします。 お手元の資料2を御覧ください。横長のパワーポイントでございます。 1ページはこれまでのスケジュール、これは復習でございます。去年9月1日に国家戦 略特区法が施行され、去年の年末、12月25日に公募を開始し、4月16日に公募を締め切っ て31の団体から提案があり、5月には地方公共団体のヒアリングを実施したということで ございます。 2ページ目を御覧ください。今申し上げた31の自治体を地理的にプロットすると、この ような感じです。北から南まで幅広く御提案をいただいているという形でございます。 内容でございますけれども、3ページを御覧ください。内容の例ということで、これは 先端的サービスの分野別に横串で7つの分野に分けて整理しております。 1つ目は左上の箱ですけれども、移動・物流分野、左上の箱です。中に項目が並んでお りますけれども、自動運転をはじめ、自動というキーワード、ドローンというキーワード、 4 空飛ぶ車、MaaS、カーシェアリング、この辺がキーワードになっています。 ②で医療・介護の箱は遠隔医療から始まって、ほかに遠隔服薬指導、ウェアラブル端末、 IoT、パーソナルレコード、AIの活用、ドローン、こういった辺りがキーワードです。 その横は③デジタル・ガバメントで、マイナンバーの活用、インターネット投票の実施。 左下に行っていただいて、④はエネルギー・環境で、これは太陽光をはじめとした再生 可能エネルギー、マイクログリッド、水素エネルギー等のカーボンニュートラル関係、EV カーシェアリングといった話。 その右は⑤防災ということで、位置情報を使って避難誘導していく話とか、インフラ点 検ロボット、また、ドローン、ロボット等の活用といったこと。 その右、⑥教育・研究開発の箱が一番右の列の真ん中ですけれども、これは遠隔授業、 オンライングローバル教育、AIデータによる個別最適化といった話。 右下は⑦支払い・その他ということで、キャッシュレス以下、並べております。 これらがそれぞれ31の自治体の提案にどう盛り込まれているというのを4ページ以降に 参考でつけております。細かくなりますので個々の説明は割愛いたしますけれども、今申 し上げた分野1つだけというよりは、各自治体さんは組み合わせて御提案いただいており ます。ちょっと字が細かいですけれども、真ん中の列に星取表的に整理しております。幾 つか複数の分野を組み合わせて御提案をいただいておりますが、組み合わせ方、あるいは それを束ねるコンセプトは、それぞれの自治体の特徴に応じて設定されています。また、 事業を進めるに当たって必要な規制改革の提案を、たくさんあるのでほんの一部ですけれ ども、一番右の列に記載しております。 最後に11ページを見ていただいて、これらの提案をめぐって、地方公共団体からヒアリ ングをいただきました。各40分程度、Web形式でヒアリングをいただいて、ワーキンググル ープ有識者の皆様、情報・デジタル、個人情報保護の専門家に御参加をいただいて、自治 体からは首長以下皆様に御参加いただいたということであります。 実績・スケジュールは黄色の箱のとおりということでございます。 以上、私からの御説明となります。ありがとうございました。 ○喜多参事官 ありがとうございました。 続いて、議事3「地方公共団体からのヒアリング結果」についてです。 ただいま、事務局より説明があったとおり、5月に自治体からヒアリングを実施いたし ました。その講評の結果につきまして、資料3に基づき特区ワーキンググループ座長の八 田委員と本日御出席いただいておりますワーキンググループ座長代理の原様より講評の結 果の御説明をお願いいたします。 ○八田委員 ありがとうございます。 それでは、講評の結果を私のから、かいつまんで御説明いたします。 先ほど大臣がおっしゃいましたように、まずデータ連携の基盤がきちんとしていて、デ 第1回 スーパーシティ型国家戦略特別区域の区域指定に関する専門調査会 ジタル化の拠点に将来なるという要件がございますが、ここに関しては専門家の委員に後 で御説明をお願いいたします。 ワーキングの私どもが特に注視したのは、どれだけきちんとした岩盤規制の改革を目指 しているかということであります。 その一つの基準としては、住民合意を要する規制改革をするというのがあります。例え ば、安全性を理由にこれまで阻止されていた規制改革を、住民が、安全性に関するリスク を受け入れても、改革すべきだと合意するならば、非常に大きな岩盤規制改革を行えます。 そういう規制の改革を含むということが一つの基準です。 もう一つは、複数の分野でデータ基盤を活用してスーパーシティに相応しいものをつく ろうということです。 結果的に今回の提案は、改革の規制の規模が小さかったり、本当に住民合意を必要とす る改革ではなかったりといった問題を抱えていました。そこで、本来の制度趣旨に立ち返 って、提案自治体において提案内容の見直しをしていただいて、その後、ワーキンググル ープにおいて二次ヒアリングをし、その過程でハンズオンでいろいろな助言をしていくべ きではないかということが結果でございます。 以上です。原委員、補足をお願いします。 これを INSERT INTO `polilink_api_meetingspeech` (`id`, `order`, `speaker`, `speech`, `description`, `council_id`, `council_meeting_id`) VALUES , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'喜多参事官 ' ,'ただいまより「スーパーシティ型国家戦略特別区域の区域指定に関する専門調査会」を開会いたします。 会議の出席者はお手元の資料を御覧ください。 初めに、坂本大臣より発言をよろしくお願いいたします。' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'坂本大臣 ' ,'本日は、お集まりいただき誠にありがとうございます。 近年、AIやビッグデータを活用し、社会の在り方を根本から変えるような都市設計の動きが国際的に急速に進展しています。しかしながら、世界において、いまだ「丸ごと未来都市」は実現されていません。我が国のスーパーシティは住民が参画し、住民目線で2030年頃に実現される未来社会の先行実現を目指すものです。 そのポイントは3点あります。 1つ目は、生活全般にまたがる複数分野の先端的サービス 2つ目は、複数分野間でのデータ連携 3つ目は、大胆な規制改革であります。 スーパーシティは、国家戦略特区制度に基づきまして、大胆な規制改革を通して経済社会の構造改革を牽引する役割であります。また、地域のデジタル化の拠点をつくる取組でもあり、今後9月に発足するデジタル庁とも密接に連携しながら、その取組を加速化してまいります。 今般、情報・デジタル等の専門家にも御参加いただきまして専門調査会を開催し、スーパーシティの区域指定について議論することとなりました。委員や出席者の皆様におかれましては、自治体からの提案や今後の進め方について忌憚のない御意見を賜り、スーパーシティ構想の実現に向け、御協力をいただければ幸いでございます。 本日は、どうぞよろしくお願いいたします。お世話になります。' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'喜多参事官 ' ,'坂本大臣、ありがとうございました。 それでは、プレスの皆様は御退出願います。(プレス退室)' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'喜多参事官 ' ,'早速議事に入ります。本日は第1回の会合ですので、最初に本専門調査会の運営規則(案)についてお諮りしたいと存じます。 お手元の資料1を御覧ください。本運営規則におきましては、議事の進め方、議事内容の公表等について定めております。運営規則案について、事前にお送りしておりますが、このように定めさせていただきたいと存じますが、御異議等はございませんでしょうか。(「異議なし」の声あり)' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'喜多参事官 ' ,'ありがとうございます。 続いて、議事2「地方公共団体からのスーパーシティの提案等」についてです。事務局より説明いたします。' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'三浦審議官 ' ,'内閣府の審議官の三浦でございます。今週着任いたしました。どうぞよろしくお願いいたします。座って失礼いたします。 お手元の資料2を御覧ください。横長のパワーポイントでございます。 1ページはこれまでのスケジュール、これは復習でございます。去年9月1日に国家戦略特区法が施行され、去年の年末、12月25日に公募を開始し、4月16日に公募を締め切って31の団体から提案があり、5月には地方公共団体のヒアリングを実施したということでございます。 2ページ目を御覧ください。今申し上げた31の自治体を地理的にプロットすると、このような感じです。北から南まで幅広く御提案をいただいているという形でございます。 内容でございますけれども、3ページを御覧ください。内容の例ということで、これは先端的サービスの分野別に横串で7つの分野に分けて整理しております。 1つ目は左上の箱ですけれども、移動・物流分野、左上の箱です。中に項目が並んでおりますけれども、自動運転をはじめ、自動というキーワード、ドローンというキーワード、空飛ぶ車、MaaS、カーシェアリング、この辺がキーワードになっています。 ②で医療・介護の箱は遠隔医療から始まって、ほかに遠隔服薬指導、ウェアラブル端末、IoT、パーソナルレコード、AIの活用、ドローン、こういった辺りがキーワードです。 その横は③デジタル・ガバメントで、マイナンバーの活用、インターネット投票の実施。 左下に行っていただいて、④はエネルギー・環境で、これは太陽光をはじめとした再生可能エネルギー、マイクログリッド、水素エネルギー等のカーボンニュートラル関係、EVカーシェアリングといった話。 その右は⑤防災ということで、位置情報を使って避難誘導していく話とか、インフラ点検ロボット、また、ドローン、ロボット等の活用といったこと。 その右、⑥教育・研究開発の箱が一番右の列の真ん中ですけれども、これは遠隔授業、オンライングローバル教育、AIデータによる個別最適化といった話。 右下は⑦支払い・その他ということで、キャッシュレス以下、並べております。 これらがそれぞれ31の自治体の提案にどう盛り込まれているというのを4ページ以降に参考でつけております。細かくなりますので個々の説明は割愛いたしますけれども、今申し上げた分野1つだけというよりは、各自治体さんは組み合わせて御提案いただいております。ちょっと字が細かいですけれども、真ん中の列に星取表的に整理しております。幾つか複数の分野を組み合わせて御提案をいただいておりますが、組み合わせ方、あるいはそれを束ねるコンセプトは、それぞれの自治体の特徴に応じて設定されています。また、事業を進めるに当たって必要な規制改革の提案を、たくさんあるのでほんの一部ですけれども、一番右の列に記載しております。 最後に11ページを見ていただいて、これらの提案をめぐって、地方公共団体からヒアリングをいただきました。各40分程度、Web形式でヒアリングをいただいて、ワーキンググループ有識者の皆様、情報・デジタル、個人情報保護の専門家に御参加をいただいて、自治体からは首長以下皆様に御参加いただいたということであります。 実績・スケジュールは黄色の箱のとおりということでございます。 以上、私からの御説明となります。ありがとうございました。' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'喜多参事官 ' ,'ありがとうございました。 続いて、議事3「地方公共団体からのヒアリング結果」についてです。 ただいま、事務局より説明があったとおり、5月に自治体からヒアリングを実施いたしました。その講評の結果につきまして、資料3に基づき特区ワーキンググループ座長の八田委員と本日御出席いただいておりますワーキンググループ座長代理の原様より講評の結果の御説明をお願いいたします。' , '' , 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'), (REPLACE(uuid(), '-', ''),'〇' ,'八田委員 ' ,'ありがとうございます。 それでは、講評の結果を私のから、かいつまんで御説明いたします。 先ほど大臣がおっしゃいましたように、まずデータ連携の基盤がきちんとしていて、デ第1回 スーパーシティ型国家戦略特別区域の区域指定に関する専門調査会ジタル化の拠点に将来なるという要件がございますが、ここに関しては専門家の委員に後で御説明をお願いいたします。 ワーキングの私どもが特に注視したのは、どれだけきちんとした岩盤規制の改革を目指しているかということであります。 その一つの基準としては、住民合意を要する規制改革をするというのがあります。例えば、安全性を理由にこれまで阻止されていた規制改革を、住民が、安全性に関するリスクを受け入れても、改革すべきだと合意するならば、非常に大きな岩盤規制改革を行えます。そういう規制の改革を含むということが一つの基準です。 もう一つは、複数の分野でデータ基盤を活用してスーパーシティに相応しいものをつくろうということです。結果的に今回の提案は、改革の規制の規模が小さかったり、本当に住民合意を必要とする改革ではなかったりといった問題を抱えていました。そこで、本来の制度趣旨に立ち返って、提案自治体において提案内容の見直しをしていただいて、その後、ワーキンググループにおいて二次ヒアリングをし、その過程でハンズオンでいろいろな助言をしていくべきではないかということが結果でございます。 以上です。原委員、補足をお願いします。' こんなSQLに置換します。 (正しいSQLにするには、このあと、6行目を削除し、最終行に固定2行をコピペ追加して、末尾を「;」にして仕上げます) replacer.py import re file_name = "./text.txt" with open(file_name, encoding="utf-8") as f: data_lines = f.read() # 文字列置換 data_lines = re.sub(r'^\d*\n', r"", data_lines, flags=re.MULTILINE) #① data_lines = re.sub(r'〇', r'○', data_lines, flags=re.MULTILINE) #② #2種類の〇 data_lines = re.sub(r'(○)(.* )', r"\n\1\n\n\2\n\n", data_lines, flags=re.MULTILINE) #③ data_lines = re.sub(r'(()(.* )', r"\n\1\n\n\2\n\n", data_lines, flags=re.MULTILINE) data_lines = re.sub(r'(<)(.* )', r"\n\1\n\n\2\n\n", data_lines, flags=re.MULTILINE) data_lines = re.sub(r'\n\n', r"★", data_lines, flags=re.MULTILINE) #④ data_lines = re.sub(r'\n', r"", data_lines, flags=re.MULTILINE) #⑤ data_lines = re.sub(r'★', r"\n", data_lines, flags=re.MULTILINE) #⑥ data_lines = re.sub(r'^', r",'", data_lines, flags=re.MULTILINE) #⑦ data_lines = re.sub(r'$', r"'", data_lines, flags=re.MULTILINE) #⑧ data_lines = re.sub(r",'○'", r", ''\n, 'a963c26aaa934abfaef70d2cc929e3fb', '7e0291703a9f42ca96860013d89f8644'),\n (REPLACE(uuid(), '-', ''),'〇'", data_lines, flags=re.MULTILINE) #⑨ data_lines = re.sub(r"^, ''", r"INSERT INTO `polilink_api_meetingspeech` (`id`, `order`,\n `speaker`,\n `speech`,\n `description`, `council_id`, `council_meeting_id`)\n VALUES", data_lines) #⑩ # 同じファイル名で保存 with open(file_name, mode="w", encoding="utf-8") as f: f.write(data_lines) ①各ページのページ番号部分を削除 ②人名の前についている◯がたまにぶれているので、統一 ③◯と次の半角空白を選択し、その前に改行、途中に改行2つ、後ろに改行2つをセット 議事録によって、人名の前が◯じゃない場合もあるので、その場合はその文字で同様の処理 ④改行2つを★に置換(これが最終的に改行にしたいもの=DBカラムに対応) ⑤改行を削除(PDF上の改行は単語を切ってしまったりして、検索しにくくなるので) ⑥★を改行に戻す ⑦文頭に「,'」 ⑧文末に「'」 ⑨◯の部分=名前カラムの前にその他固定的に付加するカラムの部分を追記 ⑩全体の先頭に、INSERT文追記(ここだけは、flags=re.MULTILINE を付けないことにより、全体の先頭1回だけ処理) こんな感じです。 なんですかこれは?? まだまだ開発中ですが、これのデータ登録を支援するスクリプトでした。
- 投稿日:2021-09-01T20:34:28+09:00
[PyTorch1.9.0]LSTMを使って時系列(単純な数式)予測してみた
0. はじめに 初めてLSTMの実装をしていく中で苦労した点をまとめてみました. LSTMがどういう仕組みで予測を行っているかというところは,本を読む方が良いと思いますので,私はLSTMの実装方法を中心に書きました. 特に私はデータセットの作り方やLSTMに入力するデータの形をどうするかというところに苦労したので,各処理で扱っている配列の形と中身をしっかりと書くことでデータの流れを分かりやすくなるように意識しました. コードの説明は,基本的にコードのブロックの上で説明するようにしています.したがって説明が見たいコードのブロックがあったらその上の文章を読んでいただければ良いと思います. 事前知識 全てのコード1行ずつ説明するのは中々困難ですので,以下の知識があることを前提に書かせていただきました. PyTorchの基礎的な書き方 torch.Tensor().to() の使い方 train(), eval()で学習と推論のモードを切り替えること(このプログラムではdropoutやbatch normalizationを使っていないのであまり関係はないと思うのですがとりあえず書いとくものではあったりします.) 学習時にはcriterion, optimizer.zero_grad(), loss.backward(),optimizer.step()などの関数を使うこと data.cpu().numpy()でネットワークの出力をnumpyに変換すること等... pythonの基礎的な書き方 listからnumpyに変換する方法 Classの書き方 sklearnのshuffleやtrain_test_splitの使い方 matplotの使い方等... githubの基礎知識 git cloneでリポジトリをクローン(ダウンロード)できること等... 問題設定 sin関数やcos関数のような単純な数式を使って時系列データを生成し,その数式のいくつかの出力を用いて1つ先の未来の値を予測する問題を設定しました. イメージとしては以下の図の通りです. オレンジ色の点から緑の点を求める問題を設定しました. 1. 開発環境 Ubuntu 18.04 anaconda 4.10.3 python 3.8.11 pytorch 1.9.0 cudatoolkit 11.1 anacondaを使用している人は,githubにanacondaの環境ファイル(predict_simple_formula_env.yml)を置いているので,そのファイルを使って仮想環境をコピーしていただくとすぐにここで説明しているプログラムを動かせると思います. anacondaの仮想環境をコピーする方法 以下のコマンドをターミナル上で打つとanacondaの環境ファイルとこの記事で紹介するプログラムを含むリポジトリをダウンロードできます. git clone https://github.com/sloth-hobby/PyTorch_practice.git ターミナル上でPyTorch_practice/lstm_practice/envs/のディレクトリ下に移動し,anacondaの仮想環境に入っている状態で以下のコマンドを打つと仮想環境をコピーできます.環境名のところはどんな名前でも良いです. conda env create -n 環境名 -f predict_simple_formula_env.yml 2. 全体のプログラムの構成 予測のために以下の3つのクラスで機能を分割しました. 各章でクラス内の実装を順番に説明しています. 1. 時系列データを作るための単純な数式を定義するクラス. 2. ネットワークの構造を定義するクラス. 3. 学習用のクラス この記事で説明するコードの全体(折りたたんでいるのでこれをクリックして頂けると全体が見られます) predict_simple_formula_train.py import torch import torch.nn as nn import torch.nn.functional as F import torch.optim as optim from sklearn.model_selection import train_test_split from sklearn.utils import shuffle import numpy as np import matplotlib.pyplot as plt class SimpleFormula(): def __init__(self, sin_a=2.0, cos_a = 2.0, sin_t=25.0, cos_t = 25.0): self.sin_a = sin_a self.cos_a = cos_a self.sin_t = sin_t self.cos_t = cos_t def sin(self, input): return self.sin_a * np.sin(2.0 * np.pi / self.sin_t * (input)) def cos(self, input): return self.cos_a * np.cos(2.0 * np.pi / self.cos_t * (input)) class PredictSimpleFormulaNet(nn.Module): def __init__(self, input_size, output_size, hidden_size, batch_first): super(PredictSimpleFormulaNet, self).__init__() self.rnn = nn.LSTM(input_size = input_size, hidden_size = hidden_size, batch_first = batch_first) self.output_layer = nn.Linear(hidden_size, output_size) nn.init.xavier_normal_(self.rnn.weight_ih_l0) nn.init.orthogonal_(self.rnn.weight_hh_l0) def forward(self, inputs): h, _= self.rnn(inputs) output = self.output_layer(h[:, -1]) return output class Train(): def __init__(self, input_size, output_size, hidden_size, batch_first, lr): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print("device:", self.device) self.net = PredictSimpleFormulaNet(input_size, output_size, hidden_size, batch_first).to(self.device) self.criterion = nn.MSELoss(reduction='mean') self.optimizer = optim.Adam(self.net.parameters(), lr=lr, betas=(0.9, 0.999), amsgrad=True) def set_formula_const_arg(self, sin_a, cos_a, sin_t, cos_t): self.f = SimpleFormula(sin_a, cos_a, sin_t, cos_t) def make_dataset(self, dataset_num, sequence_length, t_start, calc_mode="sin"): dataset_inputs = [] dataset_labels = [] dataset_times = [] for t in range(dataset_num): if calc_mode == "sin": dataset_inputs.append([self.f.sin(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.sin(t_start + t + sequence_length)]) elif calc_mode == "cos": dataset_inputs.append([self.f.cos(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.cos(t_start + t + sequence_length)]) dataset_times.append(t_start + t + sequence_length) print("test = {}, {}, {}".format(np.array(dataset_inputs).shape, np.array(dataset_labels).shape, np.array(dataset_times).shape)) return np.array(dataset_inputs), np.array(dataset_labels), np.array(dataset_times) def train_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.train() preds = self.net(inputs) loss = self.criterion(preds, labels) self.optimizer.zero_grad() loss.backward() # 勾配が大きくなりすぎると計算が不安定になるので、clipで最大でも勾配2.0に留める nn.utils.clip_grad_value_(self.net.parameters(), clip_value=2.0) self.optimizer.step() return loss, preds def test_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.eval() preds = self.net(inputs) loss = self.criterion(preds, labels) return loss, preds def train(self, train_inputs, train_labels, test_inputs, test_labels, epochs, batch_size, sequence_length, input_size): torch.backends.cudnn.benchmark = True # ネットワークがある程度固定であれば、高速化させる n_batches_train = int(train_inputs.shape[0] / batch_size) n_batches_test = int(test_inputs.shape[0] / batch_size) for epoch in range(epochs): print('-------------') print('Epoch {}/{}'.format(epoch+1, epochs)) print('-------------') train_loss = 0. test_loss = 0. train_inputs_shuffle, train_labels_shuffle = shuffle(train_inputs, train_labels) for batch in range(n_batches_train): start = batch * batch_size end = start + batch_size loss, _ = self.train_step(np.array(train_inputs_shuffle[start:end]).reshape(-1, sequence_length, input_size), np.array(train_labels_shuffle[start:end]).reshape(-1, input_size)) train_loss += loss.item() for batch in range(n_batches_test): start = batch * batch_size end = start + batch_size loss, _ = self.test_step(np.array(test_inputs[start:end]).reshape(-1, sequence_length, input_size), np.array(test_labels[start:end]).reshape(-1, input_size)) test_loss += loss.item() train_loss /= float(n_batches_train) test_loss /= float(n_batches_test) print('loss: {:.3}, test_loss: {:.3}'.format(train_loss, test_loss)) def pred_result_plt(self, test_inputs, test_labels, test_times, sequence_length, input_size): print('-------------') print("start predict test!!") self.net.eval() preds = [] for i in range(len(test_inputs)): input = np.array(test_inputs[i]).reshape(-1, sequence_length, input_size) input = torch.Tensor(input).to(self.device) pred = self.net(input).data.cpu().numpy() preds.append(pred[0].tolist()) preds = np.array(preds) test_labels = np.array(test_labels) pred_epss = np.abs(test_labels - preds) print("pred_epss_max = {}".format(pred_epss.max())) #以下グラフ描画 plt.plot(test_times, preds) plt.plot(test_times, test_labels, c='#00ff00') plt.xlabel('t') plt.ylabel('y') plt.legend(['label', 'pred']) plt.title('compare label and pred') plt.show() def confirm_input_and_label_plot(self, calc_mode, inputs, labels, times): #この関数はinputとlabelを可視化するためのコードで基本的に使う必要のない関数です print('-------------') print("confirm_input_and_label!!") re_inputs = inputs[:, -1] #以下グラフ描画 plt.plot(times[:-3], re_inputs[:-3], marker="o") plt.plot(times[-3:], re_inputs[-3:], marker="o") plt.plot(times[-1]+1.0, labels[-1], marker="o") plt.xlabel('t') plt.ylabel('y') plt.legend([calc_mode, 'input', 'label']) plt.title('confirm input and label') plt.show() if __name__ == '__main__': ''' 定数 ''' dataset_num = 250 sequence_length = 3 t_start = -100.0 sin_a = 2.0 cos_a = 2.0 sin_t = 25.0 cos_t = 25.0 calc_mode = "sin" # model pram input_size = 1 output_size = 1 hidden_size = 64 batch_first = True # train pram lr = 0.001 epochs = 15 batch_size = 4 test_size = 0.2 ''' 学習用の関数を呼び出す ''' train = Train(input_size, output_size, hidden_size, batch_first, lr) train.set_formula_const_arg(sin_a, cos_a, sin_t, cos_t) dataset_inputs, dataset_labels, dataset_times = train.make_dataset(dataset_num, sequence_length, t_start, calc_mode=calc_mode) print("dataset_inputs = {}, dataset_labels = {}".format(dataset_inputs.shape, dataset_labels.shape)) train_inputs, test_inputs, train_labels, test_labels = train_test_split(dataset_inputs, dataset_labels, test_size=test_size, shuffle=False) train_times, test_times = train_test_split(dataset_times, test_size=test_size, shuffle=False) print("train_inputs = {}, train_labels = {}, test_inputs = {}, test_labels = {}".format(train_inputs.shape, train_labels.shape, test_inputs.shape, test_labels.shape)) # train.confirm_input_and_label_plot(calc_mode, test_inputs, test_labels, test_times) train.train(train_inputs, train_labels, test_inputs, test_labels, epochs, batch_size, sequence_length, input_size) train.pred_result_plt(test_inputs, test_labels, test_times, sequence_length, input_size) またこの記事で説明するプログラムは,githubにもpredict_simple_formula_train.pyという名前でファイルをあげているので,見やすいほうを参照してください. このプログラムを実行すると以下の図をのどちらかをplotしてくれます.(calc_modeの変数でsin関数を予測するのかcos関数を予測するのか選択する必要がある) この図は,振幅が2,周期が25[s]のsin関数とcos関数の予測結果と正解ラベルを比較している図です. ほぼほぼ値が一致していることが分かると思います.ただ縦軸が±2付近では少しずれが見られます. またこの予測結果と正解ラベルのずれの最大値は,sin関数だと0.057418744761889906,cos関数だと0.09099483971649436でした. 3. 単純な数式のクラスの説明 単純な数式をこのクラスで定義します. 私はsin関数とcos関数をここで定義しましたが,ここにどんどん数式を追加で定義していくことでクラスを充実させていくこともできます. 振幅を変えられるようにsin_aやcos_a,周期を変えられるようにsin_tやcos_tという変数を用意しました. class SimpleFormula(): def __init__(self, sin_a=2.0, cos_a = 2.0, sin_t=25.0, cos_t = 25.0): self.sin_a = sin_a self.cos_a = cos_a self.sin_t = sin_t self.cos_t = cos_t def sin(self, input): return self.sin_a * np.sin(2.0 * np.pi / self.sin_t * (input)) def cos(self, input): return self.cos_a * np.cos(2.0 * np.pi / self.cos_t * (input)) 4. LSTMモデルのクラスの説明 以下のクラスで今回用いたネットワークの構造を定義しています. このネットワークのモデルはLSTMブロック一つと全結合層1層で構成しました. sin,cos関数を予測するぐらいの簡単な問題では,これぐらい簡素な構造でもそこそこ良い結果が出ました. class PredictSimpleFormulaNet(nn.Module): def __init__(self, input_size, output_size, hidden_size, batch_first): super(PredictSimpleFormulaNet, self).__init__() self.rnn = nn.LSTM(input_size = input_size, hidden_size = hidden_size, batch_first = batch_first) self.output_layer = nn.Linear(hidden_size, output_size) nn.init.xavier_normal_(self.rnn.weight_ih_l0) nn.init.orthogonal_(self.rnn.weight_hh_l0) def forward(self, inputs): h, _= self.rnn(inputs) output = self.output_layer(h[:, -1]) return output 次にクラス内の実装を順番に説明します. 以下のコードは,ネットワークの層や入力サイズなどを定義しています. nn.LSTMの引数のinput_sizeは,LSTMブロックに入力するデータのサイズのことで,入力したい時系列のサイズではないことに注意してください. hidden_sizeは,隠れ層のベクトルのサイズ,batch_firstは,LSTMに入力するテンソルの形を決めるためのフラグです. batch_firstは,デフォルトではFalseで,LSTMに入力するテンソルの形は(時系列のサイズ,バッチサイズ, 入力のサイズ)になります. Trueにするとテンソルの形は(バッチサイズ,時系列のサイズ,入力のサイズ)となります. このプログラムのようにバッチを最後に作る場合はTrueにしておくとデータの形を整えやすいです. class PredictSimpleFormulaNet(nn.Module): def __init__(self, input_size, output_size, hidden_size, batch_first): super(PredictSimpleFormulaNet, self).__init__() self.rnn = nn.LSTM(input_size = input_size, hidden_size = hidden_size, batch_first = batch_first) self.output_layer = nn.Linear(hidden_size, output_size) 以下のコードは,重みを初期化している処理をしています. nn.init.xavier_normal_(self.rnn.weight_ih_l0) nn.init.orthogonal_(self.rnn.weight_hh_l0) 以下のコードは,順伝搬の処理をしています. self.rnnに渡す引数のinputsのテンソルの形は(バッチサイズ,時系列のサイズ,入力のサイズ)です.(batch_farst=Trueの場合) sin,cos関数を扱っているため入力サイズと出力はいずれも1です. バッチサイズが2, 時系列のサイズが3で,sin関数の予測をt0~t3の4つの入力データから学習用に形を整えたい場合,入力を配列風に書くと[[[sin(t0)][sin(t1)][sin(t2)]][[sin(t1)][sin(t2)][sin(t3)]]]となります. この配列はあくまでも例です.バッチを作る際はランダムに組み合わせを作るので,実際に学習を行うときには,この配列のようにt0~t2の入力データから作った配列の隣にt1~t3の入力データから作った配列が来ることはあまりないと思います. h, _のhのテンソルの形は,(バッチサイズ,時系列のサイズ,隠れ層のベクトルのサイズ)です. 双方向LSTMを使う場合は(バッチサイズ,時系列のサイズ,隠れ層のベクトルのサイズ×2)になりますが,今回は普通のLSTMを用いるので考えないことにします. 先ほどの[[[sin(t0)][sin(t1)][sin(t2)]][[sin(t1)][sin(t2)][sin(t3)]]]が入力されたとき,hを配列風に書くと[[[h(t0)][h(t1)][h(t2)]][[h(t1)][h(t2)][h(t3)]]]になります. また_は最後の隠れ層のベクトルとセルのベクトルのタプルです.今回使わないので変数に格納するだけの処理になっています. そして,最後にLSTMの最後の隠れ層の出力h[:, -1]を全結合層self.output_layerに入力し,予測結果outputを出力します. この時h[:, -1]のテンソルの形は,(バッチサイズ,隠れ層のベクトルのサイズ)です. h[:, -1]を配列風に書くと[[[h(t2)]][[h(t3)]]]になります. def forward(self, inputs): h, _= self.rnn(inputs) output = self.output_layer(h[:, -1]) return output 5. 学習用のクラスの説明 学習用のクラスでは,学習を行うのはもちろんのこと学習のためのデータセット作成や学習したモデルと正解ラベルを比較する関数を定義しています. 学習用のクラスの全体のコード(折りたたんでいるのでこれをクリックして頂けると全体が見られます) class Train(): def __init__(self, input_size, output_size, hidden_size, batch_first, lr): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print("device:", self.device) self.net = PredictSimpleFormulaNet(input_size, output_size, hidden_size, batch_first).to(self.device) self.criterion = nn.MSELoss(reduction='mean') self.optimizer = optim.Adam(self.net.parameters(), lr=lr, betas=(0.9, 0.999), amsgrad=True) def set_formula_const_arg(self, sin_a, cos_a, sin_t, cos_t): self.f = SimpleFormula(sin_a, cos_a, sin_t, cos_t) def make_dataset(self, dataset_num, sequence_length, t_start, calc_mode="sin"): dataset_inputs = [] dataset_labels = [] dataset_times = [] for t in range(dataset_num): if calc_mode == "sin": dataset_inputs.append([self.f.sin(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.sin(t_start + t + sequence_length)]) elif calc_mode == "cos": dataset_inputs.append([self.f.cos(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.cos(t_start + t + sequence_length)]) dataset_times.append(t_start + t + sequence_length) print("test = {}, {}, {}".format(np.array(dataset_inputs).shape, np.array(dataset_labels).shape, np.array(dataset_times).shape)) return np.array(dataset_inputs), np.array(dataset_labels), np.array(dataset_times) def train_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.train() preds = self.net(inputs) loss = self.criterion(preds, labels) self.optimizer.zero_grad() loss.backward() # 勾配が大きくなりすぎると計算が不安定になるので、clipで最大でも勾配2.0に留める nn.utils.clip_grad_value_(self.net.parameters(), clip_value=2.0) self.optimizer.step() return loss, preds def test_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.eval() preds = self.net(inputs) loss = self.criterion(preds, labels) return loss, preds def train(self, train_inputs, train_labels, test_inputs, test_labels, epochs, batch_size, sequence_length, input_size): torch.backends.cudnn.benchmark = True # ネットワークがある程度固定であれば、高速化させる n_batches_train = int(train_inputs.shape[0] / batch_size) n_batches_test = int(test_inputs.shape[0] / batch_size) for epoch in range(epochs): print('-------------') print('Epoch {}/{}'.format(epoch+1, epochs)) print('-------------') train_loss = 0. test_loss = 0. train_inputs_shuffle, train_labels_shuffle = shuffle(train_inputs, train_labels) for batch in range(n_batches_train): start = batch * batch_size end = start + batch_size loss, _ = self.train_step(np.array(train_inputs_shuffle[start:end]).reshape(-1, sequence_length, input_size), np.array(train_labels_shuffle[start:end]).reshape(-1, input_size)) train_loss += loss.item() for batch in range(n_batches_test): start = batch * batch_size end = start + batch_size loss, _ = self.test_step(np.array(test_inputs[start:end]).reshape(-1, sequence_length, input_size), np.array(test_labels[start:end]).reshape(-1, input_size)) test_loss += loss.item() train_loss /= float(n_batches_train) test_loss /= float(n_batches_test) print('loss: {:.3}, test_loss: {:.3}'.format(train_loss, test_loss)) def pred_result_plt(self, test_inputs, test_labels, test_times, sequence_length, input_size): print('-------------') print("start predict test!!") self.net.eval() preds = [] for i in range(len(test_inputs)): input = np.array(test_inputs[i]).reshape(-1, sequence_length, input_size) input = torch.Tensor(input).to(self.device) pred = self.net(input).data.cpu().numpy() preds.append(pred[0].tolist()) preds = np.array(preds) test_labels = np.array(test_labels) pred_epss = np.abs(test_labels - preds) print("pred_epss_max = {}".format(pred_epss.max())) #以下グラフ描画 plt.plot(test_times, preds) plt.plot(test_times, test_labels, c='#00ff00') plt.xlabel('t') plt.ylabel('y') plt.legend(['label', 'pred']) plt.title('compare label and pred') plt.show() def confirm_input_and_label_plot(self, calc_mode, inputs, labels, times): #この関数はinputとlabelを可視化するためのコードで基本的に使う必要のない関数です print('-------------') print("confirm_input_and_label!!") re_inputs = inputs[:, -1] #以下グラフ描画 plt.plot(times[:-3], re_inputs[:-3], marker="o") plt.plot(times[-3:], re_inputs[-3:], marker="o") plt.plot(times[-1]+1.0, labels[-1], marker="o") plt.xlabel('t') plt.ylabel('y') plt.legend([calc_mode, 'input', 'label']) plt.title('confirm input and label') plt.show() 以下のコードは,ネットワークのモデル,損失関数,最適化手法などを定義しています. torch.device('cuda' if torch.cuda.is_available() else 'cpu')では,使用可能ならばcudaつまりgpuを使うことを命令しています. 学習はgpuを使ったほうが早いので,使用可能であれば積極的に使うようにしています. def __init__(self, input_size, output_size, hidden_size, batch_first, lr): self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') print("device:", self.device) self.net = PredictSimpleFormulaNet(input_size, output_size, hidden_size, batch_first).to(self.device) self.criterion = nn.MSELoss(reduction='mean') self.optimizer = optim.Adam(self.net.parameters(), lr=lr, betas=(0.9, 0.999), amsgrad=True) 以下のコードは,数式のクラスを初期化する関数です. def set_formula_const_arg(self, sin_a, cos_a, sin_t, cos_t): self.f = SimpleFormula(sin_a, cos_a, sin_t, cos_t) 以下のコードは,学習用のデータセットを作成する関数です. ここでtrainとtest用のデータセットをまとめて作成し,main文でtrainとtestに分けます. dataset_numは,作成するデータセットの数,sequence_lengthは,時系列のサイズ,t_startは,データセット作成する初めの時間です. またcalc_modeで時系列をsin関数で作るのかcos関数で作るのか決めます. 返り値のnp.array(dataset_inputs)の配列の形は,(dataset_num,sequence_length)です. 配列の中身は,[[sin(t0),sin(t1),sin(t2)][sin(t1),sin(t2),sin(t3)]...]です. このデータは学習用の入力に使います. 返り値のnp.array(dataset_labels)の配列の形は,(dataset_num,ネットワークの出力サイズ)です. 配列の中身は,[sin(t3), sin(t4), ...]です. このデータは,学習用の正解ラベルに使います. 返り値のnp.array(dataset_times)の配列の形は,(dataset_num,ネットワークの出力サイズ)です. 配列の中身は,[t3, t4, ...]です. このデータは,trainとtestに分けた後test部分だけをpred_result_pltの関数で使います. def make_dataset(self, dataset_num, sequence_length, t_start, calc_mode="sin"): dataset_inputs = [] dataset_labels = [] dataset_times = [] for t in range(dataset_num): if calc_mode == "sin": dataset_inputs.append([self.f.sin(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.sin(t_start + t + sequence_length)]) elif calc_mode == "cos": dataset_inputs.append([self.f.cos(t_start + t + i) for i in range(sequence_length)]) dataset_labels.append([self.f.cos(t_start + t + sequence_length)]) dataset_times.append(t_start + t + sequence_length) return np.array(dataset_inputs), np.array(dataset_labels), np.array(dataset_times) 以下のコードは,trainデータの入力と正解ラベルを使ってネットワークのパラメータを更新する処理をしています. inputsの配列の形は,(バッチサイズ,時系列のサイズ,入力のサイズ)です. 配列の中身は,[[[sin(t0)][sin(t1)][sin(t2)]][[sin(t14)][sin(t15)][sin(t16)]]...]です. forward関数のところでも言いましたが,バッチはランダムに作るので,t0~t2で作られた入力データの隣の配列は,t1~t3であるとは限りません.上の例のようにt14~t16で作られた入力データの配列かもしれません. labelsの配列の形は,(バッチサイズ,出力のサイズ)です. 配列の中身は,[[sin(t3)][sin(t17)]...]です. def train_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.train() preds = self.net(inputs) loss = self.criterion(preds, labels) self.optimizer.zero_grad() loss.backward() # 勾配が大きくなりすぎると計算が不安定になるので、clipで最大でも勾配2.0に留める nn.utils.clip_grad_value_(self.net.parameters(), clip_value=2.0) self.optimizer.step() return loss, preds 以下のコードは,testデータの入力と正解ラベルを使ってlossを計算するだけの処理をしています. inputsとlabelsの配列の形や中身は,先ほどのtrain_step関数の時と同様です. def test_step(self, inputs, labels): inputs = torch.Tensor(inputs).to(self.device) labels = torch.Tensor(labels).to(self.device) self.net.eval() preds = self.net(inputs) loss = self.criterion(preds, labels) return loss, preds 以下のコードは,主にtrainデータのバッチを作る処理とlossを計算する処理を行っています. train_inputs_shuffle, train_labels_shuffle = shuffle(train_inputs, train_labels)の行の処理では,make_dataset関数で作った順番通りに並んでいる配列の組み合わせをランダムに入れ替えます. 具体的には,train_inputsの配列の中身は,[[sin(t0), sin(t1), sin(t2)][sin(t1), sin(t2), sin(t3)]]...]というように規則正しく順番に並んでいます. これをtrain_inputs_shuffleの中身では[[sin(t22), sin(t23), sin(t24)][sin(t14), sin(t15), sin(t16)]]...]というようにランダムに入れ替えています.(このインデックス番号は例で,実際どのようにシャッフルされるかは分かりません) 同様にtrain_labelsの配列の中身は,[sin(t3), sin(t4), ...]から[sin(t25), sin(t17), ...]というようにランダムに入れ替えています. またreshape(-1, sequence_length, input_size)やreshape(-1, input_size)では,ネットワークのモデルの入力の形に合わせる処理を行っています. 具体的には[[sin(t22), sin(t23), sin(t24)][sin(t14), sin(t15), sin(t16)]]...]や[sin(t25), sin(t17), ...]の配列をネットワークのモデルの入力の形に合わせて,[[[sin(t22)][sin(t23)][sin(t24)]][[sin(t14)][sin(t15)][sin(t16)]]...]や[[sin(t25)][sin(t17)]...]に変形させています. def train(self, train_inputs, train_labels, test_inputs, test_labels, epochs, batch_size, sequence_length, input_size): torch.backends.cudnn.benchmark = True # ネットワークがある程度固定であれば、高速化させる n_batches_train = int(train_inputs.shape[0] / batch_size) n_batches_test = int(test_inputs.shape[0] / batch_size) for epoch in range(epochs): print('-------------') print('Epoch {}/{}'.format(epoch+1, epochs)) print('-------------') train_loss = 0. test_loss = 0. train_inputs_shuffle, train_labels_shuffle = shuffle(train_inputs, train_labels) for batch in range(n_batches_train): start = batch * batch_size end = start + batch_size loss, _ = self.train_step(np.array(train_inputs_shuffle[start:end]).reshape(-1, sequence_length, input_size), np.array(train_labels_shuffle[start:end]).reshape(-1, input_size)) train_loss += loss.item() for batch in range(n_batches_test): start = batch * batch_size end = start + batch_size loss, _ = self.test_step(np.array(test_inputs[start:end]).reshape(-1, sequence_length, input_size), np.array(test_labels[start:end]).reshape(-1, input_size)) test_loss += loss.item() train_loss /= float(n_batches_train) test_loss /= float(n_batches_test) print('loss: {:.3}, test_loss: {:.3}'.format(train_loss, test_loss)) 以下のコードは,学習を終えたモデルを使って予測を行い,それを正解ラベルと比較するため可視化する処理を行っています. def pred_result_plt(self, test_inputs, test_labels, test_times, sequence_length, input_size): print('-------------') print("start predict test!!") self.net.eval() preds = [] for i in range(len(test_inputs)): input = np.array(test_inputs[i]).reshape(-1, sequence_length, input_size) input = torch.Tensor(input).to(self.device) pred = self.net(input).data.cpu().numpy() preds.append(pred[0].tolist()) preds = np.array(preds) test_labels = np.array(test_labels) pred_epss = np.abs(test_labels - preds) print("pred_epss_max = {}".format(pred_epss.max())) #以下グラフ描画 plt.plot(test_times, preds) plt.plot(test_times, test_labels, c='#00ff00') plt.xlabel('t') plt.ylabel('y') plt.legend(['label', 'pred']) plt.title('compare label and pred') plt.show() 以下のコードは,inputとlabelを可視化するためのコードです. 問題設定のところで使った図を作るための関数で,基本的に使う必要のない関数です def confirm_input_and_label_plot(self, calc_mode, inputs, labels, times): #この関数はinputとlabelを可視化するためのコードで基本的に使う必要のない関数です print('-------------') print("confirm_input_and_label!!") re_inputs = inputs[:, -1] #以下グラフ描画 plt.plot(times[:-3], re_inputs[:-3], marker="o") plt.plot(times[-3:], re_inputs[-3:], marker="o") plt.plot(times[-1]+1.0, labels[-1], marker="o") plt.xlabel('t') plt.ylabel('y') plt.legend([calc_mode, 'input', 'label']) plt.title('confirm input and label') plt.show() 6. main文の説明 main文では,このプログラムで使うほとんどのパラメータを定義しています. 私は使うパラメータをどこか1か所にまとめておくのが好きなのでこのような書き方になっています. パラメータは頻繁に書き換えるので,この書き方は結構便利だと思います. 後は学習用のクラスの関数を順番に呼び出すだけの処理になっています. main文の全体のコード(折りたたんでいるのでこれをクリックして頂けると全体が見られます) if __name__ == '__main__': ''' 定数 ''' dataset_num = 250 sequence_length = 3 t_start = -100.0 sin_a = 2.0 cos_a = 2.0 sin_t = 25.0 cos_t = 25.0 calc_mode = "sin" # model pram input_size = 1 output_size = 1 hidden_size = 64 batch_first = True # train pram lr = 0.001 epochs = 15 batch_size = 4 test_size = 0.2 ''' 学習用の関数を呼び出す ''' train = Train(input_size, output_size, hidden_size, batch_first, lr) train.set_formula_const_arg(sin_a, cos_a, sin_t, cos_t) dataset_inputs, dataset_labels, dataset_times = train.make_dataset(dataset_num, sequence_length, t_start, calc_mode=calc_mode) print("dataset_inputs = {}, dataset_labels = {}".format(dataset_inputs.shape, dataset_labels.shape)) train_inputs, test_inputs, train_labels, test_labels = train_test_split(dataset_inputs, dataset_labels, test_size=test_size, shuffle=False) train_times, test_times = train_test_split(dataset_times, test_size=test_size, shuffle=False) print("train_inputs = {}, train_labels = {}, test_inputs = {}, test_labels = {}".format(train_inputs.shape, train_labels.shape, test_inputs.shape, test_labels.shape)) # train.confirm_input_and_label_plot(calc_mode, test_inputs, test_labels, test_times) train.train(train_inputs, train_labels, test_inputs, test_labels, epochs, batch_size, sequence_length, input_size) train.pred_result_plt(test_inputs, test_labels, test_times, sequence_length, input_size) 6. おわりに 簡単な時系列データを予測するだけでしたが,結構実装するのに苦労しました. LSTMに限らずCNNなどネットワークに入力するためにデータの形を整えるのは大変ですね... 初めてPyTorchでLSTMの実装をしたので,いろいろ間違った理解や用語の使い方をしているかもしれないので,遠慮なくご指摘頂けると幸いです. またこの記事で説明したプログラムファイル(predict_simple_formula_train.py)やanacondaの環境ファイル(predict_simple_formula_env.yml)は以下のgithubにもあげているので良かったら見ていってください. 7. 参考にさせていただいたサイト
- 投稿日:2021-09-01T19:49:21+09:00
国会議事録を可視化するツールをつくってみる
やりたいこと 最近、コロナ禍のあれやこれやを背景に、国会ではいったいどんなことが話されているのだろう、と色々気になるようになりました。 調べてみると、国会議事録はAPIを用いて比較的容易にアクセスが可能とのこと。 せっかくなので、色々と勉強しながらスクレイピング⇒加工⇒ツール上で可視化をやってみました。 出来上がったもの スクレイピングで国会議事録データを取得したのち、 こんな感じ↓で国会ごとの発言をワードクラウドで可視化しています。 対象:今年6月に行われた第204期国会党首討論会(国家基本政策委員会合同審査会) ボタンを押すと次(前)の発言に移ります。 (ボタン押した後のラグがちょっと気になりますね) ワードクラウドだけだと文脈が分かりにくいので、タブ切替で原文も見れるようにしてみました。 つくり方 環境 Google Colaboratory (検証はしていないですが、特別なことはしていないのでライブラリさえインストールすればJupyter Notebookでも同じようなことはできるはず) スクレイピング 国会議事録から発言を取得 国会の議事録はWeb上で公開されており、誰でも簡単に参照することが可能となっています。 APIも用意されており、検索キーワードを組み合わせたURLを叩くことで、任意の国会発言を取得可能です。 こちらの記事が参考になり、大部分を引用していますので、細かい説明は割愛します。 ⇒ いるかのボックス_Pythonで国会会議録のテキストを取得する from urllib.request import Request, urlopen from urllib.parse import quote from urllib.error import URLError, HTTPError import xml.etree.ElementTree as ET # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def create_query(session, meeting_name, issue): """ リクエストクエリの作成(必要情報を受け取り、URLに変換して返す) """ # )1回分の発言を取得する params = { 'nameOfMeeting': meeting_name, 'maximumRecords': 1, "sessionFrom": session, # 国会の第〇期を指す。一期分の国会を対象とするためFromとToは同じに "sessionTo": session, "issueFrom": issue, # 各会議の第〇回を指す。一回分の会議とるためFromとToは同じに "issueTo": issue, } return '&'.join(['{}={}'.format(key, value) for key, value in params.items()]) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def parse_xml(res_xml): root = ET.fromstring(res_xml) # 最終的に戻り値として返す発言情報をリスト型で宣言 speech_list = [] try: for record in root.findall('./records/record/recordData/meetingRecord'): # 会議録情報の取得 session = record.find('session').text nameOfMeeting = record.find('nameOfMeeting').text issue = record.find('issue').text date = record.find('date').text print(nameOfMeeting, issue, date) i = 1 chairman = None for speechRecord in record.findall('speechRecord'): # for i, speechRecord in enumerate(record.findall("speechRecord")): # 発言者と発言の取得 speaker_group = speechRecord.find("speakerGroup").text speaker = speechRecord.find('speaker').text contents = speechRecord.find('speech').text # if speaker is not None and speaker_group is not None: # 先頭のspeechRecord(speaker=None)は出席者一覧などの会議録情報なのでスキップ if not any([speaker is None ,speaker_group is None]): # 議長情報がなければ最初の発言者を議長と判断 if chairman is None: chairman = speaker # 議長の発言もスキップ if speaker != chairman: # 最初の区切り文字以降を発言として再格納(最初の区切り文字までは基本発言者名のため) sep = " " # 区切り文字 contents = sep.join(contents.split(sep)[1:]) # 発言情報の辞書作成 speech = {} speech["発言No."] = i speech["発言者"] = speaker speech["発言者所属"] = speaker_group speech["発言内容"] = contents speech["国会会期"] = session speech["会議名"] = nameOfMeeting speech["会議号"] = issue speech["会議日付"] = date # 発言情報をリストに集積 speech_list.append(speech) i += 1 # エラー処理 except ET.ParseError as e: print('ParseError: {}'.format(e.code)) return speech_list # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def main(): meeting_name = '国家基本政策委員会合同審査会' session = 204 issue = 1 # クエリはパーセントエンコードしておく request_url = 'http://kokkai.ndl.go.jp/api/1.0/meeting?' + quote(create_query(session= session, meeting_name= meeting_name, issue= issue)) req = Request(request_url) # 取得した国会発言をmain外で色々処理をするためグローバル変数で宣言 global minutes try: # リクエストクエリのURLからオブジェクト(今回はXML)を取得しutf8にデコード with urlopen(req) as res: res_xml = res.read().decode('utf8') except HTTPError as e: print('HTTPError: {}'.format(e.reason)) except URLError as e: print('URLError: {}'.format(e.reason)) # try正常終了時の処理記述 else: # 取得したxmlを国会発言として扱いやすい形(クラスオブジェクトのリスト)に変換 minutes = parse_xml(res_xml,) # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - if __name__ == '__main__': main() 参考サイトから自分なりにカスタマイズしたポイントは、 議長(会議内での一番最初の発言者)の発言を削除 発言内容について、最初のスペースまでは「○菅義偉 私は…」みたいな感じて発言者の名前が表示されているので、これを削除して発言を取得 あたりです。会議の流れをつかむにあたり不要だと思ったので除外しました。 ここまでの結果確認 for i in minutes : print(i) ↓ {'発言No.': 1, '発言者': '枝野幸男', '発言者所属': '立憲民主党・無所属', '発言内容': '総理、お疲れさまでございます。\n\u3000今年は、一月二十日に東京で緊急事態宣言が発令され、三月二十一日まで約二か月半続きました。…', '国会会期': '204', '会議名': '国家基本政策委員会合同審査会', '会議号': '第1号', '会議日付': '2021-06-09'} {'発言No.': 2, '発言者': '菅義偉', '発言者所属': '自由民主党・無所属の会', '発言内容': '政府として、この緊急事態宣言やまん延防止等、この措置を講ずるについて、専門家の先生方の委員会にかけて決定をするわけであります。…', '国会会期': '204', '会議名': '国家基本政策委員会合同審査会', '会議号': '第1号', '会議日付': '2021-06-09'} : {'発言No.': 33, '発言者': '志位和夫', '発言者所属': '日本共産党', '発言内容': 'コロナ収束に集中させるべきだということを求めて、終わります。(拍手)', '国会会期': '204', '会議名': '国家基本政策委員会合同審査会', '会議号': '第1号', '会議日付': '2021-06-09'} データ加工 取得した発言を形態素解析し、単語ごとに分離 次に、形態素解析を行なって文章→単語に切り分け+不要な品詞の除外を行ないました。 形態素解析については過去にこちらに記事にしたことがありますので、細かい説明は割愛します。 ⇒ 2chのスレッドをWordCloudで可視化してみる ~形態素解析・WordCloud編~ # 形態素分析ライブラリーMeCab と 辞書(mecab-ipadic-NEologd)のインストール !apt-get -q -y install sudo file mecab libmecab-dev mecab-ipadic-utf8 git curl python-mecab > /dev/null !git clone --depth 1 https://github.com/neologd/mecab-ipadic-neologd.git > /dev/null !echo yes | mecab-ipadic-neologd/bin/install-mecab-ipadic-neologd -n > /dev/null 2>&1 !pip install mecab-python3 > /dev/null # シンボリックリンクによるエラー回避 !ln -s /etc/mecabrc /usr/local/etc/mecabrc # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - def get_partofspeech_bytext(text): """ テキストから品詞付きの単語リストをMecabで作成し、特定の品詞のみをlistで返す """ chasen_list = mecab.parse(text) word_list = [] # chasen_listを1行まで分解 # ex. 鉄巨人 名詞,固有名詞,一般,*,*,*,鉄巨人,テツキョジン,テツキョジン)← 表層形\t品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用型,活用形,原形,読み,発音(https://taku910.github.io/mecab/#usage-tools) for line in chasen_list.splitlines(): tmp_d = {} if len(line) <= 1: break pos = line.split()[-1] # pos : Part Of Speech if len(pos.split(",")) == 9: tmp_d["表層形"] = line.split()[0] tmp_d["品詞"] = pos.split(",")[0] tmp_d["品詞細分類1"] = pos.split(",")[1] tmp_d["品詞細分類2"] = pos.split(",")[2] tmp_d["品詞細分類3"] = pos.split(",")[3] tmp_d["活用型"] = pos.split(",")[4] tmp_d["活用形"] = pos.split(",")[5] tmp_d["原形"] = pos.split(",")[6] tmp_d["読み"] = pos.split(",")[7] tmp_d["発音"] = pos.split(",")[8] if tmp_d["品詞"] == "名詞": if not any([tmp_d["品詞細分類1"] == ck_sp for ck_sp in ["非自立","代名詞","数","副詞可能","接尾"]]): word_list.append(tmp_d["表層形"]) if any([tmp_d["品詞"] == ck_sp for ck_sp in ["動詞", "形容詞"]]): if tmp_d["品詞細分類1"] == "自立": word_list.append(tmp_d["原形"]) return word_list # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - # 発言すべてを形態素解析して単語リストを各発言オブジェクトに追加 for speech in minutes: word_list = get_partofspeech_bytext(speech["発言内容"]) speech["単語群"] = word_list 少しだけポイントを説明します。 辞書に「mecab-ipadic-NEologd」を指定して解析精度の向上を図る Mecabのデフォルトの辞書は精度がイマイチなので、上記記事同様「mecab-ipadic-NEologd」を使用しています。 「mecab-ipadic-NEologd」は高頻度で更新されている辞書で、比較的新しい単語もそれなりの精度で分離してくれます。 そのため、今回のような時事的なトピックスを扱う場合は特に有用です。 形態素解析結果から特定の品詞の単語を抽出 Mecabによって形態素解析した単語群は、品詞情報が付加されます。 今回は助詞などの意味合いを持たない単語を除外したいため、その後必要な品詞(名詞、動詞、形容詞)のみ抽出しています。 また形態素解析により動詞などの原形も取得できます。今回は動詞・形容詞は原形に置き換えるようにしてみました。 ここまでの結果確認 for i in minutes : print(i["単語群"]) ↓ ['総理', '疲れる', 'ござる', '一月二十日', '東京', '緊急事態宣言', '発令', 'する', '三月二十一日', '続く', …] ['政府', '緊急事態宣言', 'まん延', '防止', '措置', '講ずる', '専門家', '先生', '委員会', '決定', 'する', …] : ['コロナ', '収束', '集中', 'する', '求める', '終わる', '拍手'] ワードクラウドのパラメータにTR-IDFを用いてみる TF-IDFについて TF-IDFは文章における単語の重要度を表す評価指標です。 TF(Term Frequency):単語の出現頻度。一つの文章における<当該単語の出現数 / 全単語の出現数> IDF(Inverse Document Frequency):逆文章頻度。log<全文章の数 / 当該単語が出現する文章の数> で、TF-IDFはTFとIDFの積となります。 つまり評価対象の単語が文章中でたくさん出ればTF-IDFは大きくなり、他の文章でもよく登場するような単語はTF-IDFは小さくなります。 TF-IDFの算出 TF-IDFの算出には、gensimという自然言語処理でよく用いられるライブラリを使用しています。 詳細はまだ理解途中ですが、下記の記事を参考にしました。 gensimのtfidfあれこれ【追記あり】 gensim の tfidf で正規化(normalize)に苦しんだ話 import gensim from gensim import corpora,models # 内包表記で各発言から単語リストのみ抽出 word_arr = [speech["単語群"] for speech in minutes] # 単語:id対応表となる辞書作成 dictionary = corpora.Dictionary(word_arr) # word_arrをcorpus化 corpus = list(map(dictionary.doc2bow, word_arr)) # tfidf modelの生成 test_model = models.TfidfModel(corpus) # corpusに適用 corpus_tfidf = test_model[corpus] # id->単語へ変換 texts_tfidf = [] # id -> 単語表示に変えた文書ごとのTF-IDF for doc in corpus_tfidf: text_tfidf = [] for word in doc: text_tfidf.append([dictionary[word[0]], word[1]]) texts_tfidf.append(text_tfidf) # 辞書形式(単語: tf-idf)へ変換 tfidf_dict_list = [] for text_tfidf in texts_tfidf: tfidf_dict = dict(text_tfidf) tfidf_dict_list.append(tfidf_dict) WordCloudには{"単語1":数値, "単語2":数値, …}といったdict形式でデータを渡す必要があるので、 list形式からdict形式に変換しています。 ここまでの結果確認 for i in tfidf_dict_list : print(i) ↓ {'いかが': 0.0554326015967912, 'ござる': 0.13677592146632747, 'する': 0.03856311049532222, 'なる': 0.07072277774580338, 'まん延': 0.10966148150981538, 'もし': 0.13677592146632747, 'セット': 0.21932296301963075, 'リバウンド': 0.28140165272160134, ・・・ '防止': 0.09380055090720045} {'する': 0.050666059051216, 'なる': 0.01991120744768438, 'まん延': 0.06174792837494015, '中心': 0.06174792837494015, '厳しい': 0.05281699297879102, '思う': 0.08930935396149137, '措置': 0.10563398595758204, '皆さん': 0.27323259217630924, '緊急事態宣言': 0.03754945807674369, ・・・'高さ': 0.07701546327698747, '高齢者': 0.1234958567498803} : {'する': 0.026970697262414765, '収束': 0.3936196373795167, '求める': 0.34639600992438235, 'コロナ': 0.34639600992438235, '拍手': 0.3936196373795167, '終わる': 0.34639600992438235, '集中': 0.5739591941532223} ワードクラウドの生成 ワードクラウドも先ほどと同じ記事で紹介しているので、細かい説明は割愛します。 ⇒ 2chのスレッドをWordCloudで可視化してみる ~形態素解析・WordCloud編~ from wordcloud import WordCloud #フォントをColabローカルにインストール from google.colab import drive drive.mount("/content/gdrive") # 事前に自分のGoogleDriveのマイドライブのトップにfontというフォルダを作っておき、その中に所望のフォントファイルを入れておく # フォルダごとColabローカルにコピー !cp -a "gdrive/My Drive/font/" "/usr/share/fonts/" f_path = "BIZ-UDGothicB.ttc" # Colabローカルのfontsフォルダにコピーしておく必要あり stop_words = [] # インスタンスの生成(パラメータ設定) wordcloud = WordCloud( font_path=f_path, # フォントの指定 width=960, height=600, # 生成画像のサイズの指定 background_color="white", # 背景色の指定 stopwords=set(stop_words), # 意図的に表示しない単語 max_words=350, # 最大単語数 max_font_size=100, min_font_size=5, # フォントサイズの範囲 collocations = False # 複合語の表示 ) # すべての発言に対してWordCloudを作成し画像に変換(作成したtfidf辞書から) for speech, tfidf_dict in zip(minutes, tfidf_dict_list): wc = wordcloud.generate_from_frequencies(tfidf_dict) # ←ワードクラウドの生成 speech["WordCloud"] = wc.to_image() 以前の記事の内容から大きく異なる点といえば、下から3行。前項で格納したTF-IDFを使用して、wordcloud.generate_from_frequencies()メソッドでワードクラウドを生成したところです。 さらにto_image()メソッドでこの時点で画像(PIL形式)に変換して辞書に再格納しています。 ここまでの結果確認 from IPython.display import display display(minutes[0]["WordCloud"]) ↓ ツール上での可視化 Panelでインターフェース実装 さて、ここまでくれば一応当初の目的であった「国会議事録を可視化する」というのは達成できているのですが、 どうせであればインターフェースも強化してツールとして仕立ててみます。 今回はPanelというライブラリを使用してインターフェースを実装しました。 PanelはJupyterやGoogleCoraboratory上にシンプルな記述でインタラクティブなインターフェースを実装できるライブラリです。 正直、私もつい最近存在を知ったのですが、本当に色々なウィジェットがあり様々なことが実現できそうです。 興味がある方は是非一度公式リファレンスを覗いてみてください。 ⇒ Panel公式リファレンス(英語) 事前準備:画像のファイル化 Panelで画像データを扱おうとする場合、PIL形式そのままでは使えないみたいで、一度ファイルに変換する必要があります。 ただ、ファイルとしていちいち保存をするのは何となく嫌。。。 色々と調べたところ、標準ライブラリioの中にある、BytesIOを用いることで、メモリ(バイナリストリーム)上に画像をファイルとして生成できるみたいです。 今回はこれを使用してファイルの保存を回避しました。 # PIL画像をメモリにファイルとして格納する from io import BytesIO for s in minutes: buffer = BytesIO() # メモリの確保 s["WordCloud"].save(buffer, format="png") # PNG画像として保存 s["WordCloud_BytesIO"] = buffer # 画像アドレスの格納 これで、各発言の["WordCloud_BytesIO"]を画像リンクとして扱えるようになりました。 インターフェース作成 今回は下記のようなコードで実装しました。 import panel as pn pn.extension() # 各ウィジェットの定義 next_button = pn.widgets.Button(name='Next', button_type='primary', width= 75) prev_button = pn.widgets.Button(name="Prev", button_type="primary", width= 75) info_box = pn.pane.Markdown("", background="whitesmoke", min_width= 300, margin=10,style={"padding": "2em 2em 3em",}) img = pn.pane.image.PNG(minutes[0]["WordCloud_BytesIO"]) speech_box = pn.pane.Markdown(f"{minutes[0]['発言内容']}", background="whitesmoke", height = 480, margin=10, style={"padding": "1em 2em ","overflow-y": "scroll", }) # インフォメーション領域(マークダウン形式)のスタイルカスタマイズ用 info_style = """ <style> h3{text-decoration: underline;font-weight:normal} h2{text-indent: 0.5em;} </style> """ # 画像・インフォメーション更新用の関数 def update(): speech = minutes[speech_no] img.object = speech["WordCloud_BytesIO"] # 画像(WordCloud)更新 # インフォメーション領域の記述(マークダウン形式のためタブ等で整形すると表示がおかしくなる) # マークダウン形式では半角スペース2つで改行扱いので、見えないけど半角スペース*2埋め込んである mk = info_style + f""" 第{speech['国会会期']}期国会 {speech['会議名']} {speech['会議号']} {speech["会議日付"]} <br> ### 発言No.**{str(speech["発言No."]).zfill(2)}** <br> ### 発言者 ## {speech['発言者']} ### 所属 ## {speech['発言者所属']} """ info_box.object = mk # インフォメーション領域更新 speech_box.object = speech["発言内容"] # 発言原文領域の更新 # Nextボタン用のイベント関数 def speech_to_next(event): global speech_no if speech_no != len(minutes): speech_no += 1 update() # Prevボタン用のイベント関数 def speech_to_prev(event): global speech_no if speech_no != 0: speech_no += -1 update() # 初期状態へ更新 speech_no = 0 update() # 各ボタン押下時の動作設定 next_button.on_click(speech_to_next) prev_button.on_click(speech_to_prev) # ウィジェット配置 pn.Row(pn.Column(info_box, pn.Row(prev_button, next_button, margin= 20, align="center"), ), pn.Tabs(("Image", img),("原文", speech_box))).servable() 見た目に関する内容がそれなりにあるにも関わらず、結構シンプルに記述ができているのではないかと思います。 個人的にはTkinterに似ているけど、Tkinterよりも書きやすい印象です。 大雑把な流れは下記の通り。 ウィジェットの定義 各ウィジェットをパラメータを指定してインスタンスを生成します。パラメータはwidth、height、marginなど比較的にHTMLのdivタグで設定するものが多いです。 styleパラメータはstyle={"padding": "1em 2em ","overflow-y": "scroll", }みたいな感じでdict形式で値を渡します。ただ、ウィジェットの種類によっては一部スタイル項目が無効になるものもあるみたいで注意が必要ですね。 (例えばpane.Str()ではfont-familyが反映されない、pane.Markdown()ではborder-radius(角丸め)が効かない、等) 今回、パラメータ設定で上手くいかないところは<style>タグを使って強引に設定しました。 ウィジェットの更新 インスタンス.obj(value) = 更新後の要素・値だけでOKです。 とてもシンプルですね。特に再描画の指示も必要ありません。 ウィジェットの動作割り振り 今回は参照する発言を前後させるボタンに動作を割り振っています。 まず、各動作を規定する関数を作成します。↑のdef speech_to_prev(event):のところです。 その後、動作を指定するボタンにインスタンス.on_click(イベント関数)とメソッドを呼び出すだけ。 こちらも非常にシンプルですね。 ウィジェットの配置 pn.Row()やpn.Column()などを使って、作成したウィジェットを配置していきます。 ()の中にウィジェット名を入れるだけで、Rowなら横並び、Columnなら縦並びに配置します。 さらにpn.Column(info_box, pn.Row(prev_button, next_button)のように入れ子にすることで、複雑なレイアウトも簡単に表現することができます。 またpn.Tab()を使用すれば、タブ切り替えも簡単に実装することが可能です。 ここまでの結果確認 ↓ 以上で完成です! 所感・今後 ワードクラウドだけだと文脈が分かりにくいな…と思っていたのですが、原文を添えることによっていい感じに可視化できたと思います。別タブで頻出単語などの統計データ等を出しても面白いかも。 Panelライブラリの存在を後から知ったので、可視化部分のみツール化となりました。できればスクレイピング~データ加工もツール内に埋め込みたいです。Panelは本当に色々と便利そうで、もっと勉強すれば、業務でも使えそう。 議論の流れを可視化するにはもっと色々工夫があってもよさそう。例えば発言者の政党ごとにワードクラウドの色を変えてみたり。
- 投稿日:2021-09-01T19:45:11+09:00
Algorithmためのデータ構造をPythonで実現しましょう。
Data Structureはいろいろありまして、それはどうやってPythonで実現できますかをシェアしていきます。 あまりにも多くて、これからどんどん更新していきたいと思っています。 今回はLinear Structureについて、(簡便のため今後LSと書きます。) LSは簡単に言うと、電車や新幹線みたいな感じですね。また、LSには4種類に分けられて、 - Stack - Queue - Deque - List があります。今後一個一個で説明しますが、ざっくりって言うと、dataの増減方式が違います。どんなイメージというと、 例えば、あるデータがこのLSの頭からしか入れない、またはtailしか入れないなどがあります。 では、今回はStackというデータ構造、そしてpythonならどうやって実現するのかを説明します。 Stackとは? 簡単に説明すると、最初から入ったデータは最後でしか取り出せない。図のように4が一番早くStackに入り、そしてdog, True,8.4はどんどん上に重ねていきます。取り出し場合は、最後に入れた8.4は最初に取り出します。ようするにLast In First Out (LIFO)のこと。雑ですが、こういう感じですね。 じゃpythonならどうやってStackを実現できるでしょう。 pythonのlist objectでStackを実現します。 class Stack(object): def __init__(self): self.items = [] def isEmpty(self): return self.items == [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(self.items) - 1] def size(self): return len(self.items) StackはLS構造なので、新たな空なListを作って、一番右の要素はheadとし、一番左の要素はtailとします。 pushについて、Stackはheadしか入れないため、pythonのappendメソッドを採用します。 popもそのままpython自分のメソッドを利用します。 その他のメソッドについて、Stackを利用した応用問題のために追加しましたものです。 応用問題:左カッコと右カッコののマッチ問題 あるstr型{()[]}がありまして、左カッコと左カッコは一体マッチしているかどうかをチェックしてくれるプログラムを書きましょう。 答えは以下になります。[説明はまた後日で(多分)] def check_quotation(symbol): obj = Stack() pos = 0 result = True while pos < len(symbol) and result: if symbol[pos] in '([{': obj.push(symbol[pos]) else: if obj.isEmpty(): result = False else: top = obj.pop() if not match(top, symbol[pos]): result = False pos += 1 if result and obj.isEmpty(): return True else: return False def match(s1, s2): a_1 ='([{' a_2 =')]}' return a_1.index(s1) == a_2.index(s2) print(check_quotation('[][][][]{[]')) 次回はQueueについて更新していきたいと思っています。 では、最後まで見ていただき、ありがとうございました。
- 投稿日:2021-09-01T19:38:34+09:00
【Django】Djangoで静的ファイルをS3に保存する一番簡単な方法【AWS S3】
Djangoで静的ファイルをS3に保存する一番簡単な方法 調べたらカスタムストレージクラスを作る方法がいろいろ出てきたけど意味不明だったので自分用に。 多分これが一番簡単だと思います。 【参考】 - Django mediaファイルとAWS S3 - django-storages pyをいじっていく 1. models.py , forms.py 一般的な方法でPhotoモデル,Photoformモデルを作成する 自分はDjnagoBrosというサイトで紹介されている写真投稿サイトを作って、それを改造する形で実装しました。 2. project_name/urls.py 本番環境想定の設定にしてみる if settings.DEBUG: urlpatterns += static( settings.MEDIA_URL, document_root=settings.MEDIA_ROOT ) settings.py の DEBUG が True の場合だけ Local に保存されるように設定 必要ライブラリをインストールする S3にアップするにはやはりAWSと連携するライブラリを使う必要がある しっかり用意されてるので、それを使ってく ※pyenvを使っている場合、仮想環境に入っておくことを忘れないように $ pip install boto3 $ pip install django-storages S3にバケットを作成する コンソールで作成 バケットポリシー ポリシーはdjango-storageのUsageにあるものをコピーしてくる AWSIDやバケット名は自分のやつに変える { "Version": "2012-10-17", "Statement": [ { "Sid": "VisualEditor0", "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::AWS_ACCOUNT_ID:user/bucket_name" }, "Action": [ "s3:PutObject", "s3:GetObjectAcl", "s3:GetObject", "s3:ListBucket", "s3:DeleteObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::bucket_name/*", "arn:aws:s3:::bucket_name" ] } ] } IAM ユーザ作成 S3の操作権限を持つIAMユーザを作る - 名前 : なんでもいいです - プログラムによるアクセス - S3FullAccess (めんどくさいので) (絞る場合、↑のポリシーで指定したものだけ入れたらOK) - 認証情報 : DLしてローカルPCにでも保存しておきます settings.py 編集 DEBUGモード解除 # SECURITY WARNING: don't run with debug turned on in production! DEBUG = False # Falseにする ALLOWED_HOSTS = ["127.0.0.1", "xxx.xxx.xxx.xxx"] # DEBUG = False の時は指定しないと起動できない # この辺はまだよくわかってないので今回はとりあえず127.0.0.1を許可しておく INSTALLED_APPS に 'storages' 追加 pip でインストールしておいたdjango-storageを使うために必要 INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'django_cleanup', 'app', 'storages', # 追加 ] static設定 # STATIC_URL = '/static/' STATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), ) # Media files # MEDIA_ROOT = os.path.join(BASE_DIR, 'media') # EDIA_URL = '/media/' # staticファイルの参照先 STATICFILES_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' # mediaファイル保存先 DEFAULT_FILE_STORAGE = 'storages.backends.s3boto3.S3Boto3Storage' STATICFILES_DIRS : プロジェクトのアプリで使うstaticファイルを格納している、サーバ内の場所を指定する (今回は project_name/static) STATICFILES_STORAGE : CSSなどを参照するstaticファイルの保管先を指定 DEFAULT_FILE_STORAGE : 投稿機能によってアップロードされた写真の保管先を指定 'storages.backends.s3boto3.S3Boto3Storage' を指定することで↓のS3設定を参照するようになる 元のmedia設定は不要なのでコメントアウトか、消しておく S3設定 # アクセスキーID AWS_ACCESS_KEY_ID = 'AKIA**************' # シークレットアクセスキー AWS_SECRET_ACCESS_KEY = '*************************' # バケット名 AWS_STORAGE_BUCKET_NAME = 'bucket_name' # 保存先URL STATIC_URL = 'https://%s.s3.ap-northeast-1.amazonaws.com/' % AWS_STORAGE_BUCKET_NAME AWS_LOCATION = 'static' AWS_ACCESS_KEY_ID , AWS_SECRET_ACCESS_KEY : IAM作成時にDLしてきた認証情報を入力する AWS_STORAGE_BUCKET_NAME : さっき作ったバケット名を入力する STATIC_URL : バケットのURLを入力する 適当にファイルおいて、AWSコンソールでそのファイルの詳細の「オブジェクトURL」を見たらわかる AWS_LOCATION : static関係のファイルをコピーする際、バケット内にディレクトリを作りたい場合指定 今回は bucket_name/static/静的ファイル という構造にしたいので、'static' を入力 必須項目は以上。 他にもいろいろ指定できるけど、とりあえず↑だけでよさそう collectstatic 実行 このままだとdjangoがstaticを認識できないので、collectstaticをいうコマンドを使ってstaticファイルを同期する プロジェクトのディレクトリ上で $ python manage.py collectstatic You have requested to collect static files at the destination location as specified in your settings. This will overwrite existing files! Are you sure you want to do this? Type 'yes' to continue, or 'no' to cancel: yes ← yesを入力 129 static files copied. collectstaticがうまくいって、S3のバケットに static ディレクトリ、その中に admin , css ディレクトリができていればOK! 画像のS3アップロード確認 runnserver して 実際に画像を投稿してみる →S3の static ディレクトリの中に photos/ が作成されて、その中に実際の画像ファイルが保存されていればOK! 画像保存先のディレクトリ名はモデルで指定↓ models.py class Photo(models.Model): title = models.CharField(max_length=150) comment = models.TextField(blank=True) image = models.ImageField(upload_to='photos') # ←これ category = models.ForeignKey(Category, on_delete=models.PROTECT) user = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now=True) def __str__(self): return self.title
- 投稿日:2021-09-01T17:58:50+09:00
Python を Selenium の ChromeDriver でドロップダウンリストを指定する
スクレイピングでドロップダウンリストを指定したい 1. 環境 内容 バージョン OS Windows 10 Pro (64bit) Chrome 92.0.4515.159 ChromeDriver 92.0.4515.107 Python 3.9.0 selenium 3.141.0 Visual Studio Code アイテム バージョン バージョン 1.59.1 (user setup) コミット 3866c3553be8b268c8a7f8c0482c0c0177aa8bfa 日付 2021-08-19T11:56:46.957Z Electron 13.1.7 Chrome 91.0.4472.124 Node.js 14.16.0 V8 9.1.269.36-electron.0 OS Windows_NT x64 10.0.19042 Python 3.9.0 64bit 2. Select モジュールを使う 以下の様な HTML コードのとき、 <html> <select name="test" id="Dropdown"> <option value="1">1番目</option> <option value="2">2番目</option> <option value="3">3番目</option> <option value="4">4番目</option> <option value="5">5番目</option> <option value="6">6番目</option> </select> </html> まず、Select モジュールをインポート from selenium.webdriver.support.select import Select # ドロップダウンリストを取得するために dropdown = driver.find_element_by_id('Dropdown') # で、id:Dropdown の要素を取得。 # Select オブジェクトを生成。 select = Select(Dropdown) # select_by_xxx( )というメソッドで特定の選択肢(option)を選択状態にできる # 3番目のoptionタグを選択状態に # インデックスなので1つ目の選択肢は0からスタート。 select.select_by_index(2) # 最後のoptionタグを選択状態に select.select_by_index(len(select.options)-1) Select.optionsとすると、全てのoptionタグがWebElementのリストで返ってくるのでlen()関数で選択肢の数を数えることができる。 今回は中身の値で選択したいので、 select.select_by_value('3') テキストで指定する場合は select.select_by_visible_text('3番目') 3. 参考にさせて頂いたサイト
- 投稿日:2021-09-01T17:48:37+09:00
【discord.py】VCに入ってるメンバーの一覧を取得する方法【python】
コード自体はかんたんだったんですけど、ちょっとだけハマったのでメモ。 members = [i.name for i in message.author.voice.channel.members] これでmembers変数にVCにINしているメンバーが入ってきます。 例えば組分けしたい場合は、 members = [i.name for i in message.author.voice.channel.members] random.shuffle(members) party_num = 2 team = [] # チーム分け for i in range(party_num): team.append("=====チーム"+str(i+1)+"=====") team.extend(members[i:len(members):party_num]) print ('\n'.join(team)) こうすることで、 =====チーム1===== homo チャット reaper ana =====チーム2===== traser mac sol buri と出力ができるはずです。 ハマったこと(この記事で伝えたいのはむしろこっち) botが起動しているときにVCにいるメンバーは取得できたんですけど、VCからメンバーが抜けても、なぜかbot上そのメンバーはまだVCにいるという形で取得してしまっていました。 どの記事でもそれについて書かれていなかったのですが、インテントという言葉を見かけたので「もしかして」ということで、 #intents = discord.Intents(messages=True, guilds=True, members=True, reactions=True) #before intents=discord.Intents.all() # after こうしたら行けました。 ※具体的にどう設定すればいいかわからなかったのでall()で。 参考: https://qiita.com/disneyresidents/items/72741a88265107dd04d3
- 投稿日:2021-09-01T17:43:13+09:00
python kerasダウンロードエラー 解決 (記録)
pip install kerasで発生したエラー 問題:インストールできない ERROR: Could not install packages due to an OSError: [Errno 2] No such file or directory: 'C:\\Users\\spide\\AppData\\Local\\Packages\\PythonSoftwareFoundation.Python.3.9_qbz5n2kfra8p0\\LocalCache\\local-packages\\Python39\\site-packages\\tensorflow\\include\\external\\com_github_grpc_grpc\\src\\core\\ext\\filters\\client_channel\\lb_policy\\grpclb\\client_load_reporting_filter.h' HINT: This error might have occurred since this system does not have Windows Long Path support enabled. You can find information on how to enable this at https://pip.pypa.io/warnings/enable-long-paths Windowsでのファイルパス長の制限が原因でエラーが発生しました PythonがAppDataユーザーのホームディレクトリの下のフォルダ構造などのネストされた場所にインストールされている場合、Windowsのデフォルトのパスサイズ制限に達すると、pipがパッケージのインストールに失敗することがあります。 regeditツールを使用して、Windowsレジストリでその制限を解除することができます。 1.Windowsのスタートメニューに「regedit」と入力して起動しますregedit。 2.Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem キーに移動し ます。 3.LongPathsEnabledそのキーのプロパティの値を編集し、1に設定します。 4.kerasを再インストールします(以前の壊れたインストールは無視します): 5.pip install keras
- 投稿日:2021-09-01T17:22:06+09:00
Kaggle 30 Days of ML 体験記
はじめに この記事は2021年8月2日から2021年9月1日にかけてKaggle(https://www.kaggle.com) で行われた30 Days of ML(https://www.kaggle.com/c/30-days-of-ml) というコンペティションの体験記になります。 noteとZennにも投稿しています。 自己紹介 学歴・職業:都内の私立総合大学卒業後、非IT系の事務職で勤務 Python歴:約1年(=プログラミング歴)、趣味でデータ分析する程度 統計などに関する知識レベル:統計検定2級所持 機械学習などに関する知識レベル:Udemyの講義(【世界で18万人が受講】実践 Python データサイエンス)を受講した程度 目次 参加理由 スケジュール 前半(コンペティションのためのインプット) 後半(コンペティション) 結果 感想 最後に 参加背景 Titanic後の壁を乗り越えたい 詳細は以下の方の記事を参照していただきたいのですが、 自分もこの壁を感じていました。 Titanic終了後の壁 開催中のコンペティションに参加しようにも、 画像認識など深層学習を必要とするものが多く、 内容を見ても「?」となってしまい、なかなか参加できない日々が続いていました。 ある日Twitterに次の情報が流れてきました。 見た瞬間に参加を決めました。 スケジュール 30日間となっていますが、次のように分けられます。 前半15日間:コンペティションのためのインプット(8月2日〜15日) 後半15日間:コンペティション(8月16日〜31日) 前半(コンペティションのためのインプット) この前半部分はさらに3セクションに分かれており、 Kaggleで元々設定されたコースを各自で学習する形になります。 Pythonの基本(1日目〜7日目) Kaggleが用意したPythonコース 既習の内容が多いため前倒しで進めました。 機械学習入門(8日目〜11日目) Kaggleが用意した機械学習入門コース こちらも既習の内容だったためサクサク進めました。 中級機械学習(12日目〜15日目) Kaggleが用意した中級機械学習コース 名前は知っていましたが、PipelineやCross-Validation はこれまで実施したことがありませんでした。 このコース自分で手を動かしながら学習することで ある程度は分かりました。 ただ、自分で実際にコードを書くにはまだ練習が必要だなと思いました。 余談 コースを修了すると次のような賞状がもらえます。 入手したからといって何かあるわけではないですが、 テンションが上がります笑 後半(コンペティション) 16日目〜コンペティションが開催されました。 初日(16日目) 初日はとりあえず1回submissionしてみようということで 適当にコードを書いて提出してみました。 また、fkubotaさんのkaggle日記という戦い方を参考にして、 記録をつけ始めました。 17日目〜24日目 上述したTitanic終了後の壁の記事内容の通り 公開ノートブックとディスカッションの内容に目を通していました。 この期間はEDA・前処理といった事項を確認し、 自分でコードを書いて検証しました。 24日目〜30日目 引き続き公開ノートブックとディスカッションの内容に目を通していました。 この時期には特徴量の作成よりもアンサンブルやスタッキングといった事項に力を入れていたノートブックが 多く公開されていたので、次の書籍で勉強しながら自分もモデルを複数個スタッキングをしました。 Kaggleで勝つデータ分析の技術 結果 第288位(7573チーム中)でした。 最初の方は下から数えた方が早い数値だったので、 自分としては満足がいく結果です。 感想 体験してみた感想です。 心理的ハードルが少し下がった 初学者向け、かつテーブルデータに関するコンペティションだったので、 ここで学んだことが他のコンペティションに直結するわけではありません。 しかしながら、Kaggleに挑戦したという点で一つ自信が持てるようになりました。 自分で手を動かして様々な技法を試すことができた 前述したCross-Validationやスタッキングといった、本で目を通したけれど 実際にやる機会に恵まれなかった内容を色々実践することができたので、 この点でも非常に勉強になりました。 最終的なモデルには組み込めませんでしたが、 Pytorchを用いたニューラルネットワークも試すことができました。 上級者の解法を知ることができた 初学者のコンペティションに何故参加しているのかは分かりませんでしたが、 GrandMasterの人が丁寧なノートブックを公開してくれたり、 参考になる別のコンペティションをディスカッションで紹介してくれました。 最後に 今回は基本的な内容でしたが、 初めて参加するコンペティションとして楽しめる内容でした。 次はより長期間のコンペティションに参加してメダルを狙いたいと思います!
- 投稿日:2021-09-01T17:03:51+09:00
PythonマルチスレッドThreadPoolExecutor
マルチスレッド fooの関数を20回する処理をマルチスレッドで行います。 fooの処理は待機時間3秒が含まれているので処理時間は最低でも20回×3秒で60秒以上はかかる。 しかし、マルチスレッドで処理すれば、スレッド数に応じて同時に処理してくれるので60秒もかからず処理が終わる。 from concurrent.futures import ThreadPoolExecutor from time import sleep def main(): with ThreadPoolExecutor() as executor: for i in range(20): executor.submit(foo, i) def foo(i: int): sleep(3) print(f"{i + 1}回目") if __name__ == "__main__": main() スレッド数を指定したいとき(PCが重かったりするとき)は引数に数値を入れる。 (max_workers=はなくてもよい。あってもよい。) with ThreadPoolExecutor(max_workers=3) as executor: 引数がない場合はそのPCのCPUのスレッド数に応じて同時に処理されるスレッド数が決まる。 それほど古いパソコンでなければスレッド数10以上にはなるでしょう。 なので上記の処理では6秒くらいで処理が終わります。
- 投稿日:2021-09-01T16:56:02+09:00
言語処理100本ノック 2020をやってみる 第一章:準備運動
目的 下記のウェブサイトの言語処理100本ノックを行う. 第一章:準備運動 プログラミング言語はPythonを用いる. jupyter notebookを使用しているので,出力にprint文が必要な場合は適宜追加する. 00. 文字列の逆順 文字列”stressed”の文字を逆に(末尾から先頭に向かって)並べた文字列を得よ. reserved関数を使うことで,文字列を逆順のリスト化を行っている. リストをjoin関数を用いて結合させ文字列として出力. text = "stressed" print("".join(reversed(text))) >>>desserts 01. 「パタトクカシーー」 パタトクカシーー」という文字列の1,3,5,7文字目を取り出して連結した文字列を得よ. Pythonは文字列に位置番号を指定することで,必要な部分のみ抽出できる. 引数は[start : end : step]のように指定する. start : 初期文字位置 end : 最終文字位置 step : スキップする間隔 不要な値は指定しなくて良い. text = "パタトクカシーー" text[1::2] >>>'タクシー' 02. 「パトカー」+「タクシー」=「パタトクカシーー」 「パトカー」+「タクシー」の文字を先頭から交互に連結して文字列「パタトクカシーー」を得よ. zip関数は複数のリストの要素をまとめることができる.文字列の場合は自動でリスト化した後まとめられる. sum関数はリストの合計値を取得できる.特殊な使い方として,二重リストをフラット化することが可能. 最後に.join関数でリストを結合し文字列に変換. text1 = "パトカー" text2 = "タクシー" "".join(sum(zip(text1, text2), ())) >>>'パタトクカシーー' 03. 円周率 “Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics.”という文を単語に分解し,各単語の(アルファベットの)文字数を先頭から出現順に並べたリストを作成せよ. split関数により,スペースで分割しリスト化. lambda式を使い各単語の長さをリスト化. text = "Now I need a drink, alcoholic of course, after the heavy lectures involving quantum mechanics." list(map(lambda x: len(x), text.split())) >>>[3, 1, 4, 1, 6, 9, 2, 7, 5, 3, 5, 8, 9, 7, 10] 04. 元素記号 “Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can.”という文を単語に分解し,1, 5, 6, 7, 8, 9, 15, 16, 19番目の単語は先頭の1文字,それ以外の単語は先頭の2文字を取り出し,取り出した文字列から単語の位置(先頭から何番目の単語か)への連想配列(辞書型もしくはマップ型)を作成せよ. split関数により,スペースで分割しリスト化. ensumerate関数を用いて,リスト化した単語それぞれに順番に番号をふる. lambda式を使い特定位置の単語の場合は,先頭一文字,それ以外は,先頭二文字を抽出し,位置番号とともにmap化 text = "Hi He Lied Because Boron Could Not Oxidize Fluorine. New Nations Might Also Sign Peace Security Clause. Arthur King Can." l = [1, 5, 6, 7, 8, 9, 15, 16, 19] list(map(lambda x: (x[1][:1], x[0]) if x[0] in l else (x[1][:2], x[0]), enumerate(text.split()))) >>>[('Hi', 0), ('H', 1), ('Li', 2), ('Be', 3), ('Bo', 4), ('C', 5), ('N', 6), ('O', 7), ('F', 8), ('N', 9), ('Na', 10), ('Mi', 11), ('Al', 12), ('Si', 13), ('Pe', 14), ('S', 15), ('C', 16), ('Ar', 17), ('Ki', 18), ('C', 19)] 05. n-gram 与えられたシーケンス(文字列やリストなど)からn-gramを作る関数を作成せよ.この関数を用い,”I am an NLPer”という文から単語bi-gram,文字bi-gramを得よ. gen_Ngramは引数として text : 入力テキスト minN : 最小n maxN : 最大n word_ngram : ワード単位か文字単位かを指定 を使用し,n-gramを返す. def gen_Ngram(text, minN, maxN, word_ngram=True): ngram = [] if word_ngram: words = text.split() else: words = text for N in range(minN, maxN+1): for i in range(len(words)): cw = [] if i >= N-1: for j in reversed(range(N)): cw.append(words[i-j]) else: continue if word_ngram: ngram.append(" ".join(cw)) else: ngram.append("".join(cw)) return ngram text = "I am an NLPer" print(gen_Ngram(text, 2, 2)) >>>['I am', 'am an', 'an NLPer'] print(gen_Ngram(text, 2, 2, word_ngram=False)) >>>['I ', ' a', 'am', 'm ', ' a', 'an', 'n ', ' N', 'NL', 'LP', 'Pe', 'er'] 06. 集合 “paraparaparadise”と”paragraph”に含まれる文字bi-gramの集合を,それぞれ, XとYとして求め,XとYの和集合,積集合,差集合を求めよ.さらに,’se’というbi-gramがXおよびYに含まれるかどうかを調べよ. 上記のn-gramを返す関数を用いてn-gramを作成する. set関数でリストを集合化. 和集合,積集合,差集合を求める. 最後にそれぞれの集合に'se'が含まれるかを判定している. text1 = "paraparaparadise" text2 = "paragraph" ngram1 = set(gen_Ngram(text1, 2, 2, word_ngram=False)) ngram2 = set(gen_Ngram(text2, 2, 2, word_ngram=False)) print(ngram1 | ngram2) #和集合 >>>{'ra', 'ap', 'ar', 'ad', 'gr', 'is', 'ag', 'pa', 'di', 'se', 'ph'} print(ngram1 & ngram2) #積集合 >>>{'pa', 'ra', 'ap', 'ar'} print(ngram1 - ngram2) #差集合 >>>{'se', 'ad', 'di', 'is'} print('se' in ngram1) >>>True print('se' in ngram2) >>>False 07. テンプレートによる文生成Permalink 引数x, y, zを受け取り「x時のyはz」という文字列を返す関数を実装せよ.さらに,x=12, y=”気温”, z=22.4として,実行結果を確認せよ. format関数を使い変数を代入し表示する. x=12 y="気温" z=22.4 text = "{}時の{}は{}" text.format(x, y, z) >>>'12時の気温は22.4' 08. 暗号文 与えられた文字列の各文字を,以下の仕様で変換する関数cipherを実装せよ. 英小文字ならば(219 - 文字コード)の文字に置換 その他の文字はそのまま出力 この関数を用い,英語のメッセージを暗号化・復号化せよ. 文字列を1文字ずつリスト化. islower関数により,文字が小文字か大文字か判定. ord関数は文字をUnicodeに変換する. 219からUnicodeの値を引いて,chr関数により文字に変換. 全く同じ関数で複合化可能. def cipher(text): return "".join(list(map(lambda x: chr(219 - ord(x)) if x.islower() else x, list(text)))) text = "Hello World" cipher_text = cipher(text) print(cipher_text) >>>Hvool Wliow print(cipher(cipher_text)) >>>Hello World 09. Typoglycemia スペースで区切られた単語列に対して,各単語の先頭と末尾の文字は残し,それ以外の文字の順序をランダムに並び替えるプログラムを作成せよ.ただし,長さが4以下の単語は並び替えないこととする.適当な英語の文(例えば"I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind .")を与え,その実行結果を確認せよ. 文字列をスペースで分割しリスト化. 4文字以下の場合はそのまま出力する. それ以外の場合は,先頭の文字と最後の文字はそのままで間の文字をランダムに入れ替える. ランダムに入れ替えるのにramdam.sample関数を使用している. import random text = "I couldn't believe that I could actually understand what I was reading : the phenomenal power of the human mind ." " ".join(list(map(lambda x: x if len(x) <= 4 else x[0] + "".join(random.sample(list(x)[1:-1], len(x[1:-1]))) + x[-1], text.split()))) >>>"I c'nluodt bliveee that I colud atlcluay urednastnd what I was raeindg : the pnnaemhoel pewor of the human mind ."
- 投稿日:2021-09-01T15:55:35+09:00
Pythonでseleniumを使う!
TL;DR seleniumの使い方 Chromeで起動方法 WebDriverインストール chromeの設定画面でchromeのバージョンを確認します。 こちらからChromeのWebDriverをダウンロードします。 先程確認したバージョンを同じものを選びます。 OSで選ぶ。 プログラミング ライブラリインストール $ pip install selenium プロジェクトフォルダにchromedriver.exeを入れます。 コード from time import sleep from selenium import webdriver def main(): options = webdriver.ChromeOptions() # options.add_argument("--headless") # ヘッドレスモード(いちいちブラウザを立ち上げず内部で動く) options.add_argument("--disable-gpu") options.add_argument("--incognito") # シークレットモードの設定を付与 # options.add_argument("--start-maximized") # 画面最大化 DRIVER_PATH = "./chromedriver" driver = webdriver.Chrome(executable_path=DRIVER_PATH, chrome_options=options) driver.get("https://www.yahoo.co.jp/") # ブラウザ起動 sleep(3) # 3秒間待機 print(driver.title) # ページのタイトル print(driver.current_url) # 今いるページのURL driver.quit() # ブラウザ終了 if __name__ == "__main__": main() ドライバーのパスは今回だとプロジェクトファイルの直下なので./chromedriverですが、開発環境の階層によって変えてください。 各要素の値を取得する # cssでの指定 driver.find_element_by_css_selector("css") # classでの指定 driver.find_element_by_class_name("classname") # idでの指定 driver.find_element_by_id("id") # xpathでの指定 driver.find_element_by_xpath("xpath") # java ポップアップを消すときとか driver.execute_script('document.querySelector("css").click()') 大抵の場合これdriver.find_element_by_css_selectorで事足りる。 driver.find_element_by_css_selector("#TopLink > ul > li:nth-child(2) > a > span > span") この要素の文字を取得したい場合は element = driver.find_element_by_css_selector("#TopLink > ul > li:nth-child(2) > a > span > span") print(element.text) 要素が複数ある場合 elements = driver.find_elements_by_css_selector("#TopLink > ul > li") elementがelementsに複数系になる。 リスト型で取得されるのでこれをfor文などで回したりする。 for el in elements: print(el.text) 要素(ボタン)をクリックする element = driver.find_element_by_css_selector("#TopLink > ul > li:nth-child(2) > a > span > span") element.click() リンクをクリックする(属性値取得) リンクをクリックしたい場合はclickメソッドだとうまくいかないことがあるのでリンクのURLを取得してそのURLに飛ぶ処理を行う。 aタグがリンクの要素なのでまず対象のaタグの要素を取得する。 get_attributeメソッドを使って取得した要素の属性href=内の値(URL)を取得する。 element = driver.find_element_by_css_selector("#TopLink > ul > li:nth-child(2) > a") url = element.get_attribute("href") driver.get(url) divの中のdata-asinの値を取得したいとき <div data-asin="~~~~~~~"> element = driver.find_element_by_css_selector("div") element.get_attribute("data-asin") スクショ保存 w = driver.execute_script("return document.body.scrollWidth;") h = driver.execute_script("return document.body.scrollHeight;") driver.set_window_size(w, h) driver.save_screenshot("./test.png") WidthとHeightを全開に指定することによってスクロールしないと見えないところまで全部スクショしてくれる。 なので縦長な画像になる。 ./test.pngの部分はファイル構成に合わせて。 開いたブラウザの範囲のみでよかったらdriver.save_screenshot("./test.png")だけでよい。 webdriver_manager便利! chromedriver.exeはchromeがバージョンアップする度に新しいバージョンに入れ替える必要がある。 それの入れ替えなしに常に新しいバージョンのchromedriver.exeを自動で用意してくれるライブラリが存在する。 $ pip install webdriver_manager from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager def main(): options = webdriver.ChromeOptions() # 中略 driver = webdriver.Chrome(ChromeDriverManager().install(), options=options) driver.get("https://www.yahoo.co.jp/") driver.quit() はじめから教えろよと思いますよね。その通り! でも場合によってはchromedriver.exeが必要な場面もあるのでseleniumを動かすにはchromedriver.exeが必要なんですよっていう知識が必要なんです。
- 投稿日:2021-09-01T15:27:02+09:00
【Python】S3上の複数JSONファイルを結合する
awswranglerを使用してS3上の複数JSONファイルを結合し、S3に出力する 概要 AWS Data Wranglerを使用する。 読み込みには以下のJSONLファイルを圧縮した[sample1.json.gz][sample2.json.gz]を使用する。 sample1.jsonl {"id":1,"father":"Mark","mother":"Charlotte","children":["Tom"]} {"id":2,"father":"John","mother":"Ann","children":["Jessika","Antony","Jack"]} sample2.jsonl {"id":3,"father":"Bob","mother":"Monika","children":["Jerry","Karol"]} 事前準備 事前にawswranglerをインストールする $ pip install awswrangler コード import awswrangler as wr import pandas as pd from datetime import datetime,timezone # 入力 file_list = ["s3://testbucket/prefix/sample1.json.gz", "s3://testbucket/prefix/sample2.json.gz"] dfs = wr.s3.read_json(path=file_list, lines=True) # 出力 today = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ") output_path = 's3://testbucket/output/{}'.format(today) wr.s3.to_json( df=dfs, path=output_path, orient="records", lines=True )
- 投稿日:2021-09-01T15:10:08+09:00
ブラウザのオーディオ経由でGoogle speech to textを用いて音声認識v1
はじめに ブラウザ上で音声を取得し、websocketを用いてサーバーに音声を送り、 サーバーから google speech to textに音声を転送して音声認識結果をサーバーに結果をもらうところまで出来ました。 注意 この記事はテスト段階の途中経過を記事にしたものです。 参考にした記事からのコピペが含まれています。 初めて記事を書くため、ミス等あれば、コメントよろしくお願いします。 注意2 ssl証明書のある環境やローカルホストでかつchromeにて音声認識を行いたい場合、 一番下にある index2.html を参照 Google Cloud Speech-to-TextのAPIに登録 フロントサイド index.html <!DOCTYPE html> <html lang="jp"> <head></head> <body> <p>ここにHTMLの文章などが入ります</p> <input buttonid="button1" type="button" value="open" onclick="myfunc(this)"> <input buttonid="button2" type="button" value="close" onclick="myfunc(this)"> </body> <script> var connection = null; var myfunc = function (button) { if (button.value == "open") { var handleSuccess = function (stream) { var context = new AudioContext(); var input = context.createMediaStreamSource(stream) var processor = context.createScriptProcessor(1024, 1, 1); // WebSocketのコネクション connection = new WebSocket('ws://hogehoge.com:8000/websocket'); input.connect(processor); processor.connect(context.destination); processor.onaudioprocess = function (e) { var voice = e.inputBuffer.getChannelData(0); connection.send(downsampleBuffer(voice, context.sampleRate, 16000)); // websocketで送る }; }; navigator.mediaDevices.getUserMedia({ audio: true, video: false }) .then(handleSuccess) var time1 = function () { connection.close(); context.close() }; setTimeout(time1, 1000 * 60 * 3); } if (button.value == "close") { connection.close(); context.close() }; }; const downsampleBuffer = (buffer, sampleRate, outSampleRate) => { if (outSampleRate > sampleRate) { console.error('downsampling rate show be smaller than original sample rate'); } const sampleRateRatio = sampleRate / outSampleRate; const newLength = Math.round(buffer.length / sampleRateRatio); const result = new Int16Array(newLength); let offsetResult = 0; let offsetBuffer = 0; // bpsを縮める処理 (n byte分のデータを合算して、n byteで割る) while (offsetResult < result.length) { const nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio); // 次のoffsetまでの合計を保存 let accum = 0; let count = 0; for (let i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i += 1) { accum += buffer[i]; count += 1; } // 16進数で保存 (LINEAR16でストリーミングするため) result[offsetResult] = Math.min(1, accum / count) * 0x7FFF; offsetResult += 1; offsetBuffer = nextOffsetBuffer; } return result.buffer; }; </script> </html> サーバーサイド stream.py import tornado.ioloop import tornado.web import tornado.websocket import sys import threading import os from google.cloud.speech import RecognitionConfig, StreamingRecognitionConfig from SpeechClientBridge import SpeechClientBridge RATE = 16000 CHUNK = int(RATE / 10) # 100ms config= RecognitionConfig( encoding=RecognitionConfig.AudioEncoding.LINEAR16, sample_rate_hertz=16000, language_code="ja-JP", ) streaming_config= StreamingRecognitionConfig(config=config, interim_results=True) def on_transcription_response(response): num_chars_printed= 0 if not response.results: return result= response.results[0] if not result.alternatives: return transcript= result.alternatives[0].transcript overwrite_chars= " " * (num_chars_printed -len(transcript)) if not result.is_final: sys.stdout.write(transcript + overwrite_chars + "\r") sys.stdout.flush() num_chars_printed= len(transcript) else: print('==>'+transcript + overwrite_chars) class WebSocketHandler(tornado.websocket.WebSocketHandler): bridge= SpeechClientBridge(streaming_config, on_transcription_response) t= threading.Thread(target=bridge.start) def check_origin(self, origin): return True def open(self): print("opened") self.t.start() def on_message(self, message): if message is None: self.bridge.add_request(None) self.bridge.terminate() return # print(message) if type(message)== str: print(message) elif type(message)== None: return else: self.bridge.add_request(message) def on_close(self): self.bridge.terminate() print("closed") app = tornado.web.Application([ (r"/websocket", WebSocketHandler) ]) if __name__ == "__main__": app.listen(8000) tornado.ioloop.IOLoop.instance().start() SpeechClientBridge.py import queue from google.cloud import speech class SpeechClientBridge: def __init__(self, streaming_config, on_response): self._on_response= on_response self._queue= queue.Queue() self._ended= False self.streaming_config= streaming_config def start(self): client= speech.SpeechClient() stream= self.generator() requests= ( speech.StreamingRecognizeRequest(audio_content=content) for content in stream ) responses= client.streaming_recognize(self.streaming_config, requests) self.process_responses_loop(responses) def terminate(self): self._ended= True def add_request(self, buffer): self._queue.put(bytes(buffer), block=False) def process_responses_loop(self, responses): for response in responses: self._on_response(response) if self._ended: break def generator(self): while not self._ended: # Use a blocking get() to ensure there's at least one chunk of # data, and stop iteration if the chunk is None, indicating the # end of the audio stream. chunk= self._queue.get() if chunk is None: return data= [chunk] # Now consume whatever other data's still buffered. while True: try: chunk= self._queue.get(block=False) if chunk is None: return data.append(chunk) except queue.Empty: break yield b"".join(data) 実行 サーバーサイドにて python stream.py を実行 ブラウザにて、index.htmlを開く https環境の場合 index2.html <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title>Web Speech API</title> <script> var flg = 0 var flag_speech = 0; var text = "" function vr_function() { if (flg != 0) { flg = 0; return } window.SpeechRecognition = window.SpeechRecognition || webkitSpeechRecognition; var recognition = new webkitSpeechRecognition(); recognition.lang = 'ja'; recognition.interimResults = true; recognition.continuous = true; recognition.onsoundstart = function () { document.getElementById('status').innerHTML = "認識中"; }; recognition.onnomatch = function () { document.getElementById('status').innerHTML = "もう一度試してください"; }; recognition.onerror = function () { document.getElementById('status').innerHTML = "エラー"; if (flag_speech == 0) vr_function(); }; recognition.onsoundend = function () { document.getElementById('status').innerHTML = "停止中"; vr_function(); }; recognition.onresult = function (event) { var results = event.results; for (var i = event.resultIndex; i < results.length; i++) { if (results[i].isFinal) { text = text + results[i][0].transcript + "<br />"; document.getElementById('result_text').innerHTML = "[途中経過] " + results[i][0].transcript; document.getElementById('result').innerHTML = text; vr_function(); } else { document.getElementById('result_text').innerHTML = "[途中経過] " + results[i][0].transcript; flag_speech = 1; } } } flag_speech = 0; document.getElementById('status').innerHTML = "start"; recognition.start(); } function stop() { document.getElementById('status').innerHTML = "stop"; flg = 1 } function download() { stop() let blob = new Blob([document.getElementById('result').innerHTML], { type: "text/plan" }); let link = document.createElement('a'); link.href = URL.createObjectURL(blob); link.download = '作ったファイル.txt'; link.click(); } </script> <style> .border { border: 2px solid; text-align: left; padding: 2px; width: 25%; font-size: 20px; } </style> </head> <body> <div id="result_text" class="border"> [途中経過] </div> <br> <div id="status" class="border"> 状態 </div> <br> <input type="button" onClick="vr_function()" value="音認開始"> <input type="button" onClick="stop()" value="音認停止"> <input type="button" onClick="download()" value="結果を保存する"> <div id="result" class="border"> 認識結果 </div> </body> </html> 参考文献、引用
- 投稿日:2021-09-01T13:33:42+09:00
機械学習エクスペリメントの自動トラッキングのためのDatabricks Autologgingの発表
Announcing Databricks Autologging for Automated ML Experiment Tracking - The Databricks Blogの翻訳です。 機械学習(ML)チームは、規制への対応、デバッグなどの目的から、彼らの結果を再現し、説明する能力を必要とします。このことは、あらゆる実運用のモデルは、自身のリネージュ、パフォーマンスに関する特性に関して記録を行うべきであるということを意味します。何人かのML実践者は、ソースコード、ハイパーパラメーター、パフォーマンスのメトリクスをこつこつとバージョン管理していますが、他の人は面倒、あるいは迅速なプロトタイピングの妨げとなると考えます。このため、データチームはこれらの情報を記録する際、主に3つの課題に直面します。 MLチームにおける機械学習アーティファクトの標準化 様々なML問題に対する再現性、監査可能性の確保 大量のロギング処理呼び出しを含むコードの可読性の維持 MLモデルの再現性確保 Databricks Autologgingは、上のscikit-learnのデモのように、様々な種類のMLフレームワークにおけるモデルトレーニングのセッションを自動で追跡します これらの課題に取り組むために、企業の全ての機械学習モデルに対する自動エクスペリメントトラッキングを提供するためにマネージドMLflowを活用したノーコードソリューションであるDatabricks Autologgingを発表できることを嬉しく思います。Databricks Autologgingを活用することで、ユーザーがノートブックでトレーニングコードを実行した際に、モデル、パラメーター、メトリクス、ファイル、リネージュ情報がロギングされます。MLflowのインポートやロギングを指示するためのコードを書く必要はありません。 scikit-learn、PySpark MLlib、Tensorflowを含む著名なMLライブラリを用いたトレーニングにおいて、トレーニングセッションはモデルのMLflowトラッキングランとして記録されます。また、モデルファイルも追跡され、MLflowのモデルレジストリにシームレスに登録することができ、リアルタイムスコアリングを行うためにMLflowのモデルサービングにデプロイすることもできます。 Databricks Autologgingの利用 Databricks Autologgingを使うには、シンプルにDatabricksのPythonノートブックで、サポートされているフレームワークによるモデルトレーニングを実行するだけで大丈夫です。全ての適切なモデルパラメーター、メトリクス、ファイル、リネージュ情報は自動で収集され、エクスペリメントページで参照することができます。これにより、データサイエンティストは実験のガイド、あるいは方向性を決めるために、様々なトレーニングラン(実行したトレーニング)を容易に比較することができます。また、Databricks Autologgingはハイパーパラメータチューニングのセッションを追跡することもでき、MLflow parallel coordinates plotのようなUIによる可視化を通じて、適切な探索空間を定義することを支援します。 サポートされているフレームワーク(2021/9/1時点) scikit-learn Apache Spark MLlib TensorFlow Keras PyTorch Lightning XGBoost LightGBM Gluon Fast.ai (version 1.x) statsmodels APIコールを用いて、Databricks Autologgingの挙動をカスタマイズすることも可能です。mlflow.autolog() APIでは、モデルロギングを制御するための設定パラメーター、トレーニングデータの入力サンプルのコレクション、モデルのシグネチャ情報の記録、多くのユースケースを提供しています。最後に、Databricks Autologgingで記録されるモデルトレーニングセッションに対して、追加のパラメーター、タグ、メトリクス、その他の情報をロギングするためにMLflow Tracking APIを活用することもできます。 MLflowランの管理 Autologgingで記録されるモデルトレーニングの全ての情報は、DatabricksのマネージドMLflowに蓄積され、MLflowエクスペリメントのアクセス権によって保護されます。MLflow Tracking APIあるいはUIを用いて、モデルトレーニング情報の共有、修正、削除を行うことができます。 次のステップ Databricks Autologgingはパブリックプレビューとして、いくつかのDatabricksワークスペースに展開され、Databricks MLランタイムバージョン9.0から利用できるようになります。今後数ヶ月でより広い範囲で利用できるようになる予定です。機能の詳細については、Databricks Autologgingのドキュメントを参照ください。 2021/8時点、日本リージョンで利用可能です。 実行例:訳者による追記 Databricks MLランタイムバージョン9.0で以下のコードを実行するだけで、モデル、パラメーター、メトリクス、混同行列(Confusion Matrix)が記録されます。 Python import sklearn from sklearn import datasets from sklearn.linear_model import LogisticRegression iris = datasets.load_iris() logistic = LogisticRegression(max_iter=1000) logistic.fit(iris.data, iris.target) パラメーター メトリクス モデルおよびアーティファクト Databricks 無料トライアル Databricks 無料トライアル
- 投稿日:2021-09-01T13:00:34+09:00
EXCEL workbookからシートを取得する 退屈なことはpythonにやらせよう
退屈なことpythonにやらせようの本から自分用の覚書に。 退屈なことはpythonにやらせよう 第12章 Excelシート 公式Github → https://github.com/oreilly-japan/automatestuff-ja workbookからシートを取得する(p.302) 元のコード import openpyxl wb = openpyxl.load_workbook("example.xlsx") wb.get_sheet_names() sheet = wb.get_sheet_by_name("Sheet3") sheet こうやって書くと、動作はするが「非推奨です」というメッセージが出る。 メッセージ例 DeprecationWarning: Call to deprecated function get_sheet_names (Use wb.sheetnames). DeprecationWarning: Call to deprecated function get_sheet_by_name (Use wb[sheetname]). 改良されたコード import openpyxl wb = openpyxl.load_workbook("examle.xlsx") wb.sheetnames sheet = wb["Sheet3"] sheet このように書くと非推奨が出ない。 まとめ この『退屈なことはPythonにやらせよう』という本、なぜか動かないことが多い。 だから深追いせずに「こんなことができるんだなー」ということを感じるための分厚目の入門書みたいな感じが良いかも。 私はそうやって使ってます。
- 投稿日:2021-09-01T13:00:34+09:00
EXCEL workbookからシートを取得する 非推奨コードの改良 退屈なことはpythonにやらせよう
退屈なことpythonにやらせようの本から自分用の覚書に。 退屈なことはpythonにやらせよう 第12章 Excelシート 公式Github → https://github.com/oreilly-japan/automatestuff-ja workbookからシートを取得する(p.302) 元のコード import openpyxl wb = openpyxl.load_workbook("example.xlsx") wb.get_sheet_names() sheet = wb.get_sheet_by_name("Sheet3") sheet こうやって書くと、動作はするが「非推奨です」というメッセージが出る。 メッセージ例 DeprecationWarning: Call to deprecated function get_sheet_names (Use wb.sheetnames). DeprecationWarning: Call to deprecated function get_sheet_by_name (Use wb[sheetname]). 改良されたコード import openpyxl wb = openpyxl.load_workbook("examle.xlsx") wb.sheetnames sheet = wb["Sheet3"] sheet このように書くと非推奨が出ない。 まとめ この『退屈なことはPythonにやらせよう』という本、なぜか動かないことが多い。 だから深追いせずに「こんなことができるんだなー」ということを感じるための分厚目の入門書みたいな感じが良いかも。 私はそうやって使ってます。
- 投稿日:2021-09-01T11:45:05+09:00
Pythonで動的にSoql作成
・要件 Salesforceの項目変更される場合、Soqlに動的に反映する ・案 Pythonのsimple salesforceで実行する Pythonのインストール(WindowsStoreで) pip更新:python.exe -m pip install --upgrade pip simple-salesforceの追加:pip install simple_salesforce ログイン情報 security_tokenが‘’でも、下記用にSandbox(test)またProduct(login)でログインできます。 ※DeveloperVersionができない? sf = Salesforce(username='userid', password='password', security_token='', domain='test') 実装とCSV出力 ※Csvに先頭属性という列は余計に出力されている from simple_salesforce import Salesforce import csv,unicodecsv #(credentials hidden) #sandbox or product OK sf = Salesforce(username='Tableau@jp.ricoh.com.cirus.step2TBL', password='RjKanri2020', security_token='', domain='test') #developer version is not ok ??? #sf = Salesforce(username='chunqiang.hu_2@gmail.com', password='Hu691125', security_token='', domain='login') desc = sf.Account.describe() # Below is what you need field_names = [field['name'] for field in desc['fields']] soql = "SELECT {} FROM Account".format(','.join(field_names)) print('sql:',soql) results = sf.query_all(soql) jsonD= results['records'] cnt = 0 with open('c:/tmp/test.csv', 'w', newline='') as f: writer = csv.writer(f) for d in jsonD: if cnt == 0: #csv header writer.writerow(d.keys()) print(d.keys()) print(d.values()) cnt += 1 writer.writerow(d.values()) print('result:OK')
- 投稿日:2021-09-01T10:28:20+09:00
【Python】空間情報分析に便利なオープンソース「H3」のドキュメント日本語メモ
画像はUberのエンジニアリングブログより はじめに オープンソースの「H3」を触っています。 関数についてのドキュメントは豊富ですが、実行例が無いこと、オールイングリッシュであることが相まって、少しわかりづらい側面があります。 公式ドキュメントを捕捉する形で関数の一部をメモします。 注:網羅性は一切ありません。また、pythonで触っています。 H3とは https://h3geo.org/ こちらの日本語記事にもあるため、詳細はそちらをご参照いただければと思いますが、 「H3」は、Uberによってオープンソースで開発されている、六角形をグリットとした、新しい座標系です。 画像は公式ドキュメントより UberことUber Technologiesは、いわゆる白タクサービス、フードデリバリーサービスなどで有名ですが、 配車ごとの目的地や顧客現在地、車両現在地など、膨大な空間情報を扱い、 1)即時で最適なマッチングを実現 2)蓄積されたデータを機械学習を用いて分析・可視化 するためには、市区町村のような区分や正方形で区切られたグリッドはやや扱いづらいです。 (捕捉:正方形でもいいのですが、より円に近い六角形の方が最適な距離を求めるためには扱いやすいということです。詳しくは公式ドキュメント参照のこと) そこで、彼らは新しい座標系を生み出し、2018年にオープンソース化しています ( ちなみに同社はdeck.gl他、空間の分析や可視化に役立つツールを多くオープンソース化しているようです) 関数とユースケースのメモ 以下、hexは'882f5a305dfffff'のような見た目の、H3Index型です。(ちなみにhexagon、六角形という意味の単語の略。) Indexing Inspection Traversal Traverse とは、「横切る」という意味の単語です。 ここでは、六角形のグリッドを横断し、近接グリッドのインデックスや、グリッド同士の距離・経路を求める関数が紹介されます。 kRing maxKringSize kRingDistances hexRange hexRangeDistances 原点のhexからk個分の距離に入っているグリッドのH3Indexを、距離ごとにまとめて返します h3.hex_range_distances(h, k) 例↓ h3.hex_range_distances(h, 2) 出力↓ [{h自身},{隣接しているグリッド6つ分のH3Index},{2グリッド離れている12個のH3Index}] hexRanges hexRing h3Line h3LineSize h3Distance experimentalH3ToLocalIj experimentalLocalIjToH3 Hierarchy Regions Unidirectional edges Miscellaneous 参考 上でリンク張って紹介したサイトたち https://eng.uber.com/visualizing-city-cores-with-h3/ https://qiita.com/gshirato/items/d8cc928c4131f3292b14
- 投稿日:2021-09-01T10:28:20+09:00
空間情報分析に便利なオープンソース「H3」のドキュメント日本語メモ
画像はUberのエンジニアリングブログより はじめに オープンソースの「H3」を触っています。 関数についてのドキュメントは豊富ですが、実行例が無いこと、オールイングリッシュであることが相まって、少しわかりづらい側面があります。 公式ドキュメントを捕捉する形で関数の一部をメモします。 注:網羅性は一切ありません。また、pythonで触っています。 H3とは https://h3geo.org/ こちらの日本語記事にもあるため、詳細はそちらをご参照いただければと思いますが、 「H3」は、Uberによってオープンソースで開発されている、六角形をグリットとした、新しい座標系です。 画像は公式ドキュメントより UberことUber Technologiesは、いわゆる白タクサービス、フードデリバリーサービスなどで有名ですが、 配車ごとの目的地や顧客現在地、車両現在地など、膨大な空間情報を扱い、 1)即時で最適なマッチングを実現 2)蓄積されたデータを機械学習を用いて分析・可視化 するためには、市区町村のような区分や正方形で区切られたグリッドはやや扱いづらいです。 (捕捉:正方形でもいいのですが、より円に近い六角形の方が最適な距離を求めるためには扱いやすいということです。詳しくは公式ドキュメント参照のこと) そこで、彼らは新しい座標系を生み出し、2018年にオープンソース化しています ( ちなみに同社はdeck.gl他、空間の分析や可視化に役立つツールを多くオープンソース化しているようです) 関数とユースケースのメモ 以下、hexは'882f5a305dfffff'のような見た目の、H3Index型です。(ちなみにhexagon、六角形という意味の単語の略。) Indexing Inspection Traversal Traverse とは、「横切る」という意味の単語です。 ここでは、六角形のグリッドを横断し、近接グリッドのインデックスや、グリッド同士の距離・経路を求める関数が紹介されます。 kRing maxKringSize kRingDistances hexRange hexRangeDistances 原点のhexからk個分の距離に入っているグリッドのH3Indexを、距離ごとにまとめて返します h3.hex_range_distances(h, k) 例↓ h3.hex_range_distances(h, 2) 出力↓ [{h自身},{隣接しているグリッド6つ分のH3Index},{2グリッド離れている12個のH3Index}] hexRanges hexRing h3Line h3LineSize h3Distance experimentalH3ToLocalIj experimentalLocalIjToH3 Hierarchy Regions Unidirectional edges Miscellaneous 参考 上でリンク張って紹介したサイトたち https://eng.uber.com/visualizing-city-cores-with-h3/ https://qiita.com/gshirato/items/d8cc928c4131f3292b14
- 投稿日:2021-09-01T08:25:46+09:00
AI・Python活用レシピ100選
はじめに Axross は、エンジニアの"教育"と"実務"のギャップに着目し、「学んだが活用できない人を減らしたい」という想いで、ソフトバンク社内起業制度にて立ち上げたサービスです。 現役エンジニアによる実践ノウハウが"レシピ"として教材化されており、実際に動くものを作りながら、具体的な目的・テーマをもってプログラミングを学ぶことができます。 今回は、Axross運営が厳選した『AI・Python活用レシピを100選』をご紹介します。是非、Axross学習時の参考にしてみてください。 Axross:https://axross-recipe.com 公式Twitter:https://twitter.com/Axross_SBiv 基礎 スクレイピング 01 . JUMPの掲載順をスクレイピングで取得しイロレーティングで人気度を数値化するレシピ 02 . SUUMOの物件情報を取得・分析するレシピ 可視化 03 . ほしい情報やグラフがサイトにないときのオープンデータ活用 ~東京都のオープンデータ分析~ 04 . 信頼できるデータが必要なときのオープンデータ活用 ~緯度・経度との組み合わせによる地図上への可視化~ 05 . 位置情報を持つデータからKMLファイルを生成してマップ上にプロットするレシピ WEBアプリ開発 06 . PythonのStreamlitでチャットボットWebアプリケーションをお手軽に作るレシピ 数値 統計分析 07 . 試験データをクロス表にまとめ相関分析するレシピ 08 . アンケートを因子分析するレシピ 09 . 従業員満足度調査(エンゲージメント・サーベイ)の分析レシピ 10 . Pythonと統計検定で、回帰モデルを用いて市場反応分析するレシピ 整理 11 . pandasを使ったcsvファイル解析とデータ整理テクニック 12 . ECサイトのCSVファイルをデータベース化し分析するレシピ 時系列 13 . 購買履歴データを分析し購買傾向を抽出するレシピ 14 . ECサイトで在庫に無駄なく商品を発注するためのデータ分析レシピ 予測 15 . ECサイトのデータ分析から新商品を考えるレシピ 16 . ECサイトのレビューからブランド信者を育成するレシピ 17 . App Storeのカスタマーレビューを集計してヒストグラムを作って比較評価するレシピ 18 . Campfireでプロジェクトを分析し作戦を考案するレシピ 19 . Kickstarter Projectsデータを用いた成功・失敗を予測するレシピ クラスタリング 20 . ワインのアルコール度数を予測するレシピ 21 . 日経33業種の株価増減率をクラスタリング分析し、日経平均株価と連動する業種を見つけるモデル作成レシピ 数理最適化 22 . Python&数理最適化を用いて最適な配送計画を算出しよう 強化学習 23 . 強化学習 QーLearningを実装して宝探しロボットを構築するレシピ 機械学習 24 . Pycaretによる自動機械学習のレシピ 25 . MicrosoftのFLAMLで効率的に自動で適切な機械学習モデルを見つけるレシピ 26 . 機械学習の特徴量の重要度を表示・活用するレシピ 27 . Permutation Importanceによる特徴量の重要度分析レシピ 28 . 機械学習の精度向上① ~評価指標の最適化~ 29 . 機械学習の精度向上② ~不均衡データへの対応~ 30 . 機械学習の精度向上③ ~アンサンブル学習~ 画像 画像認識 31 . PyTorchによる画像認識最強モデル「EfficientNet」実装! 32 . 画像データが少量しかないタスクに対してResNet-50の転移学習を行うレシピ 33 . 画像認識による乗り物の分類レシピ(前編) 34 . 転移学習による乗り物分類モデルの精度向上レシピ(後編) 35 . djangoとpytorchを使って花を認識するwebアプリを作成するレシピ 画像分類 36 . OpenAIのCLIPを使った、ゼロショット画像分類(学習を必要としない画像分類)を実装するレシピ 37 . Pytorch速習 / Scikit-Learnのような学習を実現するSkorchでの画像分類レシピ セグメンテーション 38 . 航空写真からアノテーションデータを作成して、対象物を抽出するセマンティックセグメンテーションモデルを作成するレシピ 39 . セマンティックセグメンテーションで道路画像から人や車を認識するレシピ 物体検出 40 . Tensorflow Object Detection API 道路標識検出レシピ 41 . Raspberry piでリアルタイムに7セグメントディスプレイを読み取るレシピ 42 . YOLOv5 と転移学習を使ってマスクの着用者と非着用者の顔検出を行うレシピ 43 . 学習済みSSDを少量のデータでファインチューニングし、新たなクラスの物体検出を行うレシピ 44 . Unity上で靴の3D物体検出を行うレシピ 文字認識(OCR) 45 . LINEのCLOVA OCRで複数の請求書PDFをCSVデータに一括出力するレシピ 46 . Synthetic Dataを用いて画像から文字を検出するモデルを実装するレシピ 47 . Paddle OCRを用いたOCR検証について理解するレシピ 画像異常検知 48 . PyTorchを使ってGANによる異常検知を行うレシピ 49 . 生産ラインを想定した画像異常検知の実装レシピ 画像生成 50 . Pytorchを使ってGANを実装するレシピ 51 . テキストから画像を生成するDALL-Eを実装するレシピ 画像編集 52 . 機械学習応用編~k平均法によるカラー画像圧縮法~のレシピ 画像合成 53 . StyleGAN+CLIPモデルで、テキストによる顔画像の編集を行うレシピ 54 . 顔交換(FaceSwap)技術を活用し、人物画像の顔を入れ替えるレシピ 55 . StarGAN-v2で顔の特徴操作・顔の合成を実現するレシピ 56 . 1枚の静止画を動画に合わせて動かすレシピ 57 . 自分の表情や顔の動きに合わせて、キャラクターアバターを動かすレシピ 感情認識 58 . あなたのPCをPepperのような表情認識ロボットにできるレシピ 行動認識 59 . SlowFastを用いた人物の行動認識を行うレシピ 60 . MediaPipeを利用して簡単なジェスチャーを推定するレシピ 姿勢推定 61 . OpenPoseでプロ野球選手のバッティングフォームを分析するレシピ 行動追跡 62 . DeepSORTを用いた人物のトラッキングを行うレシピ 類似画像検索 63 . Arcfaceを用いた類似顔画像検索アプリケーションの実装レシピ 画像分類モデル精度向上 64 . 画像分類する深層学習モデルの判断部分を可視化するレシピ 65 . 【PyTorch】精度爆上げのオレオレベストプラクティスを10個まとめてみた! 言語 言語可視化 66 . コメントからポジネガを自動判定するレシピ 67 . 1冊の本を1枚の画像で可視化するレシピ 68 . ブラウザの履歴データから形態素解析でユーザーの嗜好を抽出するレシピ 知見抽出 69 . 材料の単語分散表現から知見を抽出するレシピ 文章解析 70 . TwitterAPIでオリンピック競技別の話題性を分析するレシピ 71 . TwitterAPIと形態素解析でフォロワーのいいねしたツイートの傾向を分析するレシピ 72 . TwitterAPIで最もいいねがつきやすい時間帯を分析するレシピ 73 . ユーザのTweet傾向をクラスタ分析するレシピ 74 . TwitterAPIで乃木坂46メンバーの話題度を比較分析するレシピ 感情予測 75 . 日本語BERT事前学習モデルを使ってMultilabel Sentiment分類器を学習する チャットボット 76 . LINEチャットボット(おうむ返し&AI搭載型LINEチャットボット)の作成レシピ 77 . BERTによる日本語QAの発話応答モデル作成レシピ 78 . 【発展編】BERTによるQAチャットボットの学習モデル作成レシピ 79 . MicrosoftのDialoGPTでチャットアプリを開発するレシピ 80 . GPT-3で自然対話するSlackボットを作るレシピ 文章生成 81 . GPT-3でタイトルや要約から記事本文を自動生成するレシピ 82 . GPT-3のNLPアプリケーション開発を体験してみるレシピ 83 . 夏目漱石の「坊ちゃん」を学習させてマルコフ連鎖で新作を自動生成させるレシピ キャプション生成 84 . PyTorchを活用し画像のキャプションを自動生成するレシピ 85 . 【発展編】深層学習を活用し、より高精度な画像のキャプションを自動生成するレシピ 文章要約 86 . BertSumを使った文書要約のレシピ 検索 87 . 文書の「あいまい検索」機能をつくるレシピ レコメンデーション 88 . ゼロから作る、ホテルのディスクリプションのCosine Similarityに基づくレコメンドシステム 89 . scikit-surpriseを使った映画のレコメンデーションレシピ 言語モデル精度向上 90 . 「より良い」機械学習モデルの構築方法を学べるレシピ 音声 音声認識 91 . Zoom会議の録音データから音声認識で議事録を自動生成するレシピ 92 . 音声処理ツールキットESPNetを用いて日本語音声認識を行うレシピ 音声合成 93 . Amazon Polly を使った音声合成のレシピ 94 . AWSのサービスを活用した有名人画像認識アプリの開発のレシピ 機械翻訳 95 . AWSのサービスを活用した音声合成と機械翻訳の連携のレシピ 楽曲分類 96 . 楽曲のムードを深層学習で分類するレシピ 楽曲生成 97 . 画像から音楽を生成するモデルを構築するレシピ 波形異常検知 98 . TensorFlow Kerasで時系列データの異常検知を行うレシピ 音声対話 99 . 【GPT-3発展編】音声だけで会話するチャットボットを作るレシピ その他 セキュリティ 100 . 【AIセキュリティ入門】Adversarial Examplesを理解しAIモデルを頑健にするレシピ 最後に AIを活用するためのコツは、座学や理論の勉強よりも、まず実際にAIを実装する体験をしてみること、そして、様々なデータセットやパラメータを変えて、AIモデルの実装を繰り返し演習することが近道だと思います。 Axrossのレシピを通して、プログラムの意味を考えながら写経(コードを実際に書き写す行為)し、実際に動くものをつくりながら学ぶことで、新たな知識習得やスキルアップの一助になれれば幸いです。 また、Axrossでは自身の開発ノウハウを学習教材"レシピ"として寄稿いただけるエンジニアの方を募集しています! 見習いエンジニアから募った学びたい内容をウィッシュリストとして掲載しています。募集中のテーマからご自身で作成いただけるようでしたら、レシピ作成にご協力お願いいたします。
- 投稿日:2021-09-01T07:35:25+09:00
【OpenRTM】OpenRTM-aist 2.0 (ROS transport含む)のインストール (ソースからのビルド)
はじめに OpenRTM-aist 2.0は公式GitHubで開発中のOpenRTM-aistの最新版です。 自分でビルドしてインストールする必要があり、ちょっと苦戦したので残しておきます。 注意点 ・OpenRTM-aistの旧バージョンなどは何も入っていない環境で試しました。公式サイトと前提条件が違うかもしれません。この記事の通りに実行すれば何も入っていない環境でもインストールできると思います。 ・まだ公式HPのマニュアルなども整備中のようなので情報が変わるかもしれませんがご容赦ください。 OpenRTM-aistって何? OpenRTM-aistが何か、ROS Transport機能が何かは公式HPなどを参照ください。 OpenRTM-aistについてひとことで言うと産総研が開発しているロボット用ミドルウェアです。 ROSとの通信機能がROSTransport機能になり、バージョン2.0から追加されました。 また、記事内でコンポーネントのことをRTCと言っている場合があります。 この記事でやること ROS Transport機能含むOpenRTM-aist 2.0 C++版のインストール OpenRTM-aist-2.0 Python版のインストール rtshellのインストール やらないこと ROSのインストール ROSがインストールしてある前提で話を進めます。 環境 Windows 10 Home上のWSL2 Ubuntu 18.04 ROS Melodic OpenRTM-aist 2.0 (今回はこれをインストール) OpenRTM-aist 2.0 (C++版)のインストール ダウンロード&ビルド&インストール 参考: ①公式ページ(OpenRTM-aist2.0のインストール) ②公式ページ(ROS通信機能の利用) ①のUbuntu+omniORBの部分を参考にビルド&インストールを行います。 ②にあるようにROS通信機能をできるようにしておきます。 (-DROS_ENABLE=ONの部分) 適当なディレクトリで作業をしてください。 最初と最後のコマンドは①にも②にもないので注意して下さい。 最初のコマンドを実行しなかった場合:cmakeでエラー 最後のコマンドを実行しなかった場合:コンポーネントを実行する際にエラーが出ました。 $ sudo apt-get install libomniorb4-dev omniidl $ git clone https://github.com/OpenRTM/OpenRTM-aist $ cd OpenRTM-aist/ $ mkdir build $ cd build/ $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DROS_ENABLE=ON .. $ cmake --build . --config Release -- -j$(nproc) $ sudo cmake --build . --target install $ sudo ldconfig -v cmakeのエラー内容 -- Checking for module 'omniORB4' -- No package 'omniORB4' found CMake Error at /usr/share/cmake-3.10/Modules/FindPkgConfig.cmake:419 (message): A required package was not found Call Stack (most recent call first): /usr/share/cmake-3.10/Modules/FindPkgConfig.cmake:597 (_pkg_check_modules_internal) CMakeLists.txt:25 (pkg_check_modules) -- Configuring incomplete, errors occurred! See also "/home/XXXX/temp/OpenRTM-aist/build/CMakeFiles/CMakeOutput.log". これでインストールは完了です。サンプルコンポーネントを動かしてみましょう。 サンプルコンポーネントを実行する まず、roscoreを起動してください。 rtc.confの作成 コンポーネントの動作に必要なrtc.confというファイルをコンポーネントを実行する場所で作成します。 ここでは、~/workspace/ros_transport_testというディレクトリを作成しています。OpenRTM-aistではLinux系なら ~/workspace、WindowsならCドライブ直下のworkspaceフォルダ(C:\workspace)で作業することが多いです。すくなくとも僕の周りでは。 $ cd ~/workspace $ mkdir ros_transport_test $ cd ros_transport_test $ sudo nano rtc.conf rtc.confの中身は以下です。入力後、エディタを終了してください。 rtc.conf manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/ manager.modules.preload: ROSTransport.so manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0 manager.components.preactivation: ConsoleOut0, ConsoleIn0 サンプルコンポーネントの実行① そのままこのディレクトリでコンポーネントを実行します。サンプルコンポーネントは以下にたくさん入っています。 /usr/local/share/openrtm-2.0/components/c++/examples/ $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp 以下のような記述が出たら成功です。 Creating a component: "ConsoleIn"....succeed. ================================================= Component Profile ------------------------------------------------- InstanceID: ConsoleIn0 Implementation: ConsoleIn Description: Console input component Version: 2.0.0 Maker: AIST Category: example Other properties implementation_id: ConsoleIn type_name: ConsoleIn description: Console input component 僕の場合では、ここでエラーが発生しました。 上記のインストールの最後のコマンドを実行していれば大丈夫でした。 エラー内容と解決策 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp: error while loading shared libraries: libRTC.so.2.0: cannot open shared object file: No such file or directory 公式のGitHubで言及されていました。 サンプルコンポーネントの実行② rtc.confを作成したディレクトリで以下を実行してください。 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp これも、先ほど実行したコンポーネントと同じような表示が出ていれば成功です。 動作確認 ①の画面 ------------------------------ Please input number: ②の画面 ------------------------------ のようになっていると思います。①の画面に何か数字を入れると②の画面に出てきます。 また、この時にROS関係のコマンドを打っても動作を確認することができます。 $ rosnode list /ConsoleIn0 /ConsoleOut0 /rosout $ rostopic list /chatter /rosout /rosout_agg 以下のコマンドで、①に入力した数字を見ることができます。 $ rostopic echo /chatter ここまでできたら、一度コンポーネントを終了してください。 RTCの要素どこいった? OpenRTM-aistを触ったことがある方なら気付くと思うのですが、このサンプルコンポーネント、ROSで通信しているんですよね。rtc.confのおかげみたいなのですが。 rtshellでコンポーネントの様子見るか~と思ったらインストールされていない。もはやrtm-namingも実行していない。 と、いうことでOpenRTM-aist 2.0 Python版とrtshellをダウンロードします。 OpenRTM-aist 2.0 (Python版)のインストール ダウンロード&ビルド&インストール 正直に言うとPython版の方が苦労しました。前提条件が違うのかな? 参考:公式ページ(OpenRTM2.0 Pythonのインストール) 上記ページにも書いてありますが、インストールには、以下のものが必要です。 Python: ビルドツールを利用するために必要 omniORBpy: OpenRTM-aist のビルド (IDL コンパイル) に必要 OpenRTM-aist 2.0 Python版およびrtshellは最終的にPython3で動作を確認しました。 参考ページと少しコマンドが異なるので注意してください。 まずは、omniORB関係を以下のコマンドで取得します。 $ sudo apt-get install python3-omniorb-omg omniidl-python3 $ sudo apt-get install omniorb-nameserver 次に、OpenRTM-aist本体をダウンロード&ビルド&インストールします。 $ git clone https://github.com/OpenRTM/OpenRTM-aist-Python $ cd OpenRTM-aist-Python $ mkdir build $ python3 setup.py build $ sudo python3 setup.py install これで完了です。以下のコマンドでエラーが出なければ正常に動作しています。 $ python3 -c "import RTC" これでrtm-namingは立ち上がるな~と思い実行してみましたが、rtshellがないのでコンポーネントの様子を把握できません。 rtm-naming自体は以下のコマンドで起動できました。Pythonのバージョンは環境によって変わるかと思います。 $ python3 /usr/local/lib/python3.6/dist-packages/OpenRTM_aist/utils/rtm-naming/rtm-naming.py 僕の場合では、ここでエラーが発生しました。 上記のインストールの最後のコマンドを実行していれば大丈夫でした。 エラー内容 Starting omniORB omniNames: LAPTOP-83EDG5N9 : 2809 Not found omniNames. rtshellのインストール 参考:公式HP(rtshell) 以下のコマンドを実行してください。 僕は上記でPython3を使用していたのでpip3にしています。 $ pip3 install rtshell-aist $ sudo rtshell_post_install 以下のコマンドでコマンドが見つかりませんのエラーが出なければ大丈夫です。 $ rtfind localhost 全体の動作確認 サンプルコンポーネントを使用して、全体の動作確認をします。 どうやらこのrtc.confを使用したサンプルコンポーネントはROSノードとしてもRTCとしても動作しているみたいです。 roscore、ネーミングサービスの起動 $ roscore $ python3 /usr/local/lib/python3.6/dist-packages/OpenRTM_aist/utils/rtm-naming/rtm-naming.py コンポーネントの起動 rtc.confがあるディレクトリに移動してください。 ①数字を入力して出力するコンポーネント $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp ②数字を受け取って表示するコンポーネント $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp rtshellで確認 ROS系のコマンドで確認できるのは先ほど説明しました。 rtshellで動作を確認してみましょう。 XXXXXXXXXXの部分は環境によって変わります。 $ rtfind localhost /localhost /localhost/XXXXXXXXXX.host_cxt /localhost/XXXXXXXXXX.host_cxt/ConsoleIn0.rtc /localhost/XXXXXXXXXX.host_cxt/ConsoleOut0.rtc 以下のコマンドで①のコンポーネントで入力した数字が表示されます。 $ rtprint /localhost/XXXXXXXXXX.host_cxt/ConsoleIn0.rtc:out 123と入力した場合 rtctree.rtc.RTC.TimedLong(tm=rtctree.rtc.RTC.Time(sec=0, nsec=0), data=123) おわりに まとめとして、 OpenRTM-aist 2.0をインストールしました。 rtshellをインストールしました。 この記事にあるようなrtc.confを参照させてコンポーネントを実行するとROSノードとしてもRTCとしても動作しました。 試したかったので試しました!以上 参考 その他 rtc.confについて rtc.conf manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/ manager.modules.preload: ROSTransport.so manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0 manager.components.preactivation: ConsoleOut0, ConsoleIn0 以下のような意味があるそうです。(参考) manager.modules.load_path シリアライザーモジュール(ROSTransport.so)を置く場所を指定します。 manager.modules.preload ROS通信のためのシリアライザーモジュールのの指定をします。Ubuntuの場合にはROSTransport.soを指定します。 manager.components.preconnect コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。 OpenRTM-aistのシリアライザーモジュール(ROSTransport.so)が対応しているメッセージ型は以下のようになります。 シリアライザ名とROS/ROS2メッセージ型 rtc.confがない場所で起動したらどうなるか 普通のRTCとして動作する。 状態遷移もするためrtactコマンドでアクティベートをする必要がある。 バージョン 1.Xはどうやってインストールできるか 1.1系と1.2系があります。 公式サイトのダウンロードタブからWindowsだとインストーラー、Linuxだと一括インストールスクリプトをダウンロードできます!そこからのインストールは簡単です。 例:1.2.2のダウンロードページ
- 投稿日:2021-09-01T07:35:25+09:00
【OpenRTM-aist】OpenRTM-aist 2.0 (ROS transport含む)のインストール (ソースからのビルド)
はじめに OpenRTM-aist 2.0は公式GitHubで開発中のOpenRTM-aistの最新版です。 自分でビルドしてインストールする必要があり、ちょっと苦戦したので残しておきます。 注意点 ・OpenRTM-aistの旧バージョンなどは何も入っていない環境で試しました。公式サイトと前提条件が違うかもしれません。この記事の通りに実行すれば何も入っていない環境でもインストールできると思います。 ・まだ公式HPのマニュアルなども整備中のようなので情報が変わるかもしれませんがご容赦ください。 OpenRTM-aistって何? OpenRTM-aistが何か、ROS Transport機能が何かは公式HPなどを参照ください。 OpenRTM-aistについてひとことで言うと産総研が開発しているロボット用ミドルウェアです。 ROSとの通信機能がROSTransport機能になり、バージョン2.0から追加されました。 また、記事内でコンポーネントのことをRTCと言っている場合があります。 この記事でやること ROS Transport機能含むOpenRTM-aist 2.0 C++版のインストール OpenRTM-aist-2.0 Python版のインストール rtshellのインストール やらないこと ROSのインストール ROSがインストールしてある前提で話を進めます。 環境 Windows 10 Home上のWSL2 Ubuntu 18.04 ROS Melodic OpenRTM-aist 2.0 (今回はこれをインストール) OpenRTM-aist 2.0 (C++版)のインストール ダウンロード&ビルド&インストール 参考: ①公式ページ(OpenRTM-aist2.0のインストール) ②公式ページ(ROS通信機能の利用) ①のUbuntu+omniORBの部分を参考にビルド&インストールを行います。 ②にあるようにROS通信機能をできるようにしておきます。 (-DROS_ENABLE=ONの部分) 適当なディレクトリで作業をしてください。 最初と最後のコマンドは①にも②にもないので注意して下さい。 最初のコマンドを実行しなかった場合:cmakeでエラー 最後のコマンドを実行しなかった場合:コンポーネントを実行する際にエラーが出ました。 $ sudo apt-get install libomniorb4-dev omniidl $ git clone https://github.com/OpenRTM/OpenRTM-aist $ cd OpenRTM-aist/ $ mkdir build $ cd build/ $ cmake -DCORBA=omniORB -DCMAKE_BUILD_TYPE=Release -DROS_ENABLE=ON .. $ cmake --build . --config Release -- -j$(nproc) $ sudo cmake --build . --target install $ sudo ldconfig -v cmakeのエラー内容 -- Checking for module 'omniORB4' -- No package 'omniORB4' found CMake Error at /usr/share/cmake-3.10/Modules/FindPkgConfig.cmake:419 (message): A required package was not found Call Stack (most recent call first): /usr/share/cmake-3.10/Modules/FindPkgConfig.cmake:597 (_pkg_check_modules_internal) CMakeLists.txt:25 (pkg_check_modules) -- Configuring incomplete, errors occurred! See also "/home/XXXX/temp/OpenRTM-aist/build/CMakeFiles/CMakeOutput.log". これでインストールは完了です。サンプルコンポーネントを動かしてみましょう。 サンプルコンポーネントを実行する まず、roscoreを起動してください。 rtc.confの作成 コンポーネントの動作に必要なrtc.confというファイルをコンポーネントを実行する場所で作成します。 ここでは、~/workspace/ros_transport_testというディレクトリを作成しています。OpenRTM-aistではLinux系なら ~/workspace、WindowsならCドライブ直下のworkspaceフォルダ(C:\workspace)で作業することが多いです。すくなくとも僕の周りでは。 $ cd ~/workspace $ mkdir ros_transport_test $ cd ros_transport_test $ sudo nano rtc.conf rtc.confの中身は以下です。入力後、エディタを終了してください。 rtc.conf manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/ manager.modules.preload: ROSTransport.so manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0 manager.components.preactivation: ConsoleOut0, ConsoleIn0 サンプルコンポーネントの実行① そのままこのディレクトリでコンポーネントを実行します。サンプルコンポーネントは以下にたくさん入っています。 /usr/local/share/openrtm-2.0/components/c++/examples/ $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp 以下のような記述が出たら成功です。 Creating a component: "ConsoleIn"....succeed. ================================================= Component Profile ------------------------------------------------- InstanceID: ConsoleIn0 Implementation: ConsoleIn Description: Console input component Version: 2.0.0 Maker: AIST Category: example Other properties implementation_id: ConsoleIn type_name: ConsoleIn description: Console input component 僕の場合では、ここでエラーが発生しました。 上記のインストールの最後のコマンドを実行していれば大丈夫でした。 エラー内容と解決策 /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp: error while loading shared libraries: libRTC.so.2.0: cannot open shared object file: No such file or directory 公式のGitHubで言及されていました。 サンプルコンポーネントの実行② rtc.confを作成したディレクトリで以下を実行してください。 $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp これも、先ほど実行したコンポーネントと同じような表示が出ていれば成功です。 動作確認 ①の画面 ------------------------------ Please input number: ②の画面 ------------------------------ のようになっていると思います。①の画面に何か数字を入れると②の画面に出てきます。 また、この時にROS関係のコマンドを打っても動作を確認することができます。 $ rosnode list /ConsoleIn0 /ConsoleOut0 /rosout $ rostopic list /chatter /rosout /rosout_agg 以下のコマンドで、①に入力した数字を見ることができます。 $ rostopic echo /chatter ここまでできたら、一度コンポーネントを終了してください。 RTCの要素どこいった? OpenRTM-aistを触ったことがある方なら気付くと思うのですが、このサンプルコンポーネント、ROSで通信しているんですよね。rtc.confのおかげみたいなのですが。 rtshellでコンポーネントの様子見るか~と思ったらインストールされていない。もはやrtm-namingも実行していない。 と、いうことでOpenRTM-aist 2.0 Python版とrtshellをダウンロードします。 OpenRTM-aist 2.0 (Python版)のインストール ダウンロード&ビルド&インストール 正直に言うとPython版の方が苦労しました。前提条件が違うのかな? 参考:公式ページ(OpenRTM2.0 Pythonのインストール) 上記ページにも書いてありますが、インストールには、以下のものが必要です。 Python: ビルドツールを利用するために必要 omniORBpy: OpenRTM-aist のビルド (IDL コンパイル) に必要 OpenRTM-aist 2.0 Python版およびrtshellは最終的にPython3で動作を確認しました。 参考ページと少しコマンドが異なるので注意してください。 まずは、omniORB関係を以下のコマンドで取得します。 $ sudo apt-get install python3-omniorb-omg omniidl-python3 $ sudo apt-get install omniorb-nameserver 次に、OpenRTM-aist本体をダウンロード&ビルド&インストールします。 $ git clone https://github.com/OpenRTM/OpenRTM-aist-Python $ cd OpenRTM-aist-Python $ mkdir build $ python3 setup.py build $ sudo python3 setup.py install これで完了です。以下のコマンドでエラーが出なければ正常に動作しています。 $ python3 -c "import RTC" これでrtm-namingは立ち上がるな~と思い実行してみましたが、rtshellがないのでコンポーネントの様子を把握できません。 rtm-naming自体は以下のコマンドで起動できました。Pythonのバージョンは環境によって変わるかと思います。 $ python3 /usr/local/lib/python3.6/dist-packages/OpenRTM_aist/utils/rtm-naming/rtm-naming.py 僕の場合では、ここでエラーが発生しました。 上記のインストールの最後のコマンドを実行していれば大丈夫でした。 エラー内容 Starting omniORB omniNames: LAPTOP-83EDG5N9 : 2809 Not found omniNames. rtshellのインストール 参考:公式HP(rtshell) 以下のコマンドを実行してください。 僕は上記でPython3を使用していたのでpip3にしています。 $ pip3 install rtshell-aist $ sudo rtshell_post_install 以下のコマンドでコマンドが見つかりませんのエラーが出なければ大丈夫です。 $ rtfind localhost 全体の動作確認 サンプルコンポーネントを使用して、全体の動作確認をします。 どうやらこのrtc.confを使用したサンプルコンポーネントはROSノードとしてもRTCとしても動作しているみたいです。 roscore、ネーミングサービスの起動 $ roscore $ python3 /usr/local/lib/python3.6/dist-packages/OpenRTM_aist/utils/rtm-naming/rtm-naming.py コンポーネントの起動 rtc.confがあるディレクトリに移動してください。 ①数字を入力して出力するコンポーネント $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleInComp ②数字を受け取って表示するコンポーネント $ /usr/local/share/openrtm-2.0/components/c++/examples/ConsoleOutComp rtshellで確認 ROS系のコマンドで確認できるのは先ほど説明しました。 rtshellで動作を確認してみましょう。 XXXXXXXXXXの部分は環境によって変わります。 $ rtfind localhost /localhost /localhost/XXXXXXXXXX.host_cxt /localhost/XXXXXXXXXX.host_cxt/ConsoleIn0.rtc /localhost/XXXXXXXXXX.host_cxt/ConsoleOut0.rtc 以下のコマンドで①のコンポーネントで入力した数字が表示されます。 $ rtprint /localhost/XXXXXXXXXX.host_cxt/ConsoleIn0.rtc:out 123と入力した場合 rtctree.rtc.RTC.TimedLong(tm=rtctree.rtc.RTC.Time(sec=0, nsec=0), data=123) おわりに まとめとして、 OpenRTM-aist 2.0をインストールしました。 rtshellをインストールしました。 この記事にあるようなrtc.confを参照させてコンポーネントを実行するとROSノードとしてもRTCとしても動作しました。 試したかったので試しました!以上 参考 その他 rtc.confについて rtc.conf manager.modules.load_path: /usr/local/lib/openrtm-2.0/transport/ manager.modules.preload: ROSTransport.so manager.components.preconnect: ConsoleOut0.in?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleOut0, ConsoleIn0.out?interface_type=ros&marshaling_type=ros:std_msgs/Float32&ros.topic=chatter&ros.node.name=ConsoleIn0 manager.components.preactivation: ConsoleOut0, ConsoleIn0 以下のような意味があるそうです。(参考) manager.modules.load_path シリアライザーモジュール(ROSTransport.so)を置く場所を指定します。 manager.modules.preload ROS通信のためのシリアライザーモジュールのの指定をします。Ubuntuの場合にはROSTransport.soを指定します。 manager.components.preconnect コネクタ生成に関する設定をしています。interface_type(インターフェース型)にros、marshaling_type(マーシャリング型)に対応シリアライザの名前、ros.topic(トピック名)に適当な任意の名前を設定します。 OpenRTM-aistのシリアライザーモジュール(ROSTransport.so)が対応しているメッセージ型は以下のようになります。 シリアライザ名とROS/ROS2メッセージ型 rtc.confがない場所で起動したらどうなるか 普通のRTCとして動作する。 状態遷移もするためrtactコマンドでアクティベートをする必要がある。 バージョン 1.Xはどうやってインストールできるか 1.1系と1.2系があります。 公式サイトのダウンロードタブからWindowsだとインストーラー、Linuxだと一括インストールスクリプトをダウンロードできます!そこからのインストールは簡単です。 例:1.2.2のダウンロードページ
