- 投稿日:2019-08-20T23:59:50+09:00
【Tensorflow-gpu 1.x系】複数のモデルを一つのプログラムで実行する
はじめに
以下のように複数のモデル(手検出モデルと指先検出モデル)を1つのプログラムで実行しようとした時にGPUメモリで躓いたためメモ。
以下のプログラムでは手検出で1つ、指先検出で1つの計2つのモデルを利用しています。複数モデル(手検出、指検出)を実行するプログラムを少々書き直し中。。。? pic.twitter.com/tx1fH3Ygkr
— 高橋かずひと@リベロ拝命? (@KzhtTkhs) August 20, 2019躓いたポイント
1つ目のモデルを読み込んだ後、2つ目のモデルを動作させようとするとGPUメモリ不足でプログラムが異常終了する。
原因
TensorFlowはデフォルト設定では、GPUのメモリのほぼ全てを使用して、メモリの断片化を軽減するようにしているようです。
1つ目のモデルの時点でメモリをほぼ全て使い切ったため、2つ目のモデルは動かせなかった模様。対処
Allowing GPU memory growthオプションを利用し、必要なメモリだけ確保するようにする。
※ただし、プログラム途中でGPUメモリ解放等はしないように注意が必要とのことソースコード
以下のような指定をする。
config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True)) sess = tf.Session(graph=net_graph, config=config)
ソースコード全体のイメージ ※イメージです
#!/usr/bin/env python # -*- coding: utf-8 -*- import time import copy import cv2 as cv import tensorflow as tf import numpy as np def session_run(sess, inp): out = sess.run([ sess.graph.get_tensor_by_name('num_detections:0'), sess.graph.get_tensor_by_name('detection_scores:0'), sess.graph.get_tensor_by_name('detection_boxes:0'), sess.graph.get_tensor_by_name('detection_classes:0') ], feed_dict={ 'image_tensor:0': inp.reshape(1, inp.shape[0], inp.shape[1], 3) }) return out def main(): print("Hand Detection Start...\n") # カメラ準備 ############################################################## cap = cv.VideoCapture(0) cap.set(cv.CAP_PROP_FRAME_WIDTH, 1280) cap.set(cv.CAP_PROP_FRAME_HEIGHT, 720) # GPUメモリを必要な分だけ確保するよう設定 config = tf.ConfigProto(gpu_options=tf.GPUOptions(allow_growth=True)) # 手検出モデルロード ###################################################### with tf.Graph().as_default() as net1_graph: graph_data = tf.gfile.FastGFile('frozen_inference_graph1.pb', 'rb').read() graph_def = tf.GraphDef() graph_def.ParseFromString(graph_data) tf.import_graph_def(graph_def, name='') sess1 = tf.Session(graph=net1_graph, config=config) sess1.graph.as_default() # 指先検出モデルロード ###################################################### with tf.Graph().as_default() as net2_graph: graph_data = tf.gfile.FastGFile('frozen_inference_graph2.pb', 'rb').read() graph_def = tf.GraphDef() graph_def.ParseFromString(graph_data) tf.import_graph_def(graph_def, name='') sess2 = tf.Session(graph=net2_graph, config=config) sess2.graph.as_default() while True: start_time = time.time() # カメラキャプチャ #################################################### ret, frame = cap.read() if not ret: continue debug_image = copy.deepcopy(frame) # 手検出実施 ########################################################## inp = cv.resize(frame, (512, 512)) inp = inp[:, :, [2, 1, 0]] # BGR2RGB out = session_run(sess1, inp) rows = frame.shape[0] cols = frame.shape[1] num_detections = int(out[0][0]) for i in range(num_detections): class_id = int(out[3][0][i]) score = float(out[1][0][i]) bbox = [float(v) for v in out[2][0][i]] if score < 0.8: continue x = int(bbox[1] * cols) y = int(bbox[0] * rows) right = int(bbox[3] * cols) bottom = int(bbox[2] * rows) # 指先検出実施 #################################################### if class_id == 3: trimming_image = debug_image[y:bottom, x:right] inp = cv.resize(trimming_image, (300, 300)) inp = inp[:, :, [2, 1, 0]] # BGR2RGB f_rows = trimming_image.shape[0] f_cols = trimming_image.shape[1] out = session_run(sess2, inp) f_num_detections = int(out[0][0]) for i in range(f_num_detections): f_score = float(out[1][0][i]) f_bbox = [float(v) for v in out[2][0][i]] f_x = int(f_bbox[1] * f_cols) f_y = int(f_bbox[0] * f_rows) f_right = int(f_bbox[3] * f_cols) f_bottom = int(f_bbox[2] * f_rows) if f_score < 0.4: continue # 指検出結果可視化 ######################################## cv.rectangle( trimming_image, (f_x, f_y), (f_right, f_bottom), (0, 255, 0), thickness=2) # 手検出結果可視化 ################################################ cv.rectangle( debug_image, (x, y), (right, bottom), (0, 255, 0), thickness=2) # 処理時間描画 ######################################################## elapsed_time = time.time() - start_time time_string = u"elapsed time:" + '{:.3g}'.format(elapsed_time) cv.putText(debug_image, time_string, (10, 50), cv.FONT_HERSHEY_COMPLEX, 1.0, (0, 255, 0)) # 画面反映 ############################################################ cv.imshow(' ', debug_image) cv.moveWindow(' ', 100, 100) key = cv.waitKey(1) if key == 27: # ESC break if __name__ == '__main__': main()以上。
- 投稿日:2019-08-20T23:50:06+09:00
WebElement 内部を find_element_by_xpath() で再検索した時のつまずき
python selenium の WebDriver でブラウザテストを作成しているときにWebElement 内部 の検索にfind_element_by_xpath を使った際につまずきましたのでまとめます。
つまずき1:要素内部についての find_element_by_xpath() 検索のはずなのに、別の要素の値が取れてしまう
browserTest.pyfrom selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.chrome.webdriver import WebDriver def printUserEmails(driver:WebDriver)->None: UserTables = driver.find_elements_by_xpath('//table[start-with(@name, "user")]') if len(UserTables) == 0: print('Userが表示されていません。') return for UserTable in UserTables: emailElement = UserTable.find_element_by_xpath('//input[@type="text" and @name="email"]') print(emailElement.get_attribute('value'))結果.txtuser1@aaa.bbb user1@aaa.bbb user1@aaa.bbb user1@aaa.bbb . .■状況
「一度 find_elements_by_xpath() でとった WebElement の List について for ループを回しているにも関わらず一番最初の要素の同一の値が取れてしまう」という現象につまずきました。■原因:XPath の文字列の最初の「//」が「HTML内の全要素」という意味のため再検索していた
xpath「//」←「HTML内の全要素」から検索するの意味
(こちらの記事に詳細に記載して頂いておりましたので参考にさせて頂きました。
https://www.techscore.com/tech/XML/XPath/XPath3/xpath03.html/#xpath3-2
)element.find_element_by_xpath('//tag名') # → Rootから検索するので同じデータが取得されてしまう element.find_element_by_xpath('tag名') # → 対象要素から検索できる■対応:xpath の文字列 から最初の「//」を消す
browserTest.py#ーー中略ーー for UserTable in UserTables: emailElement = UserTable.find_element_by_xpath('input[@type="text" and @name="email"]') print(emailElement.get_attribute('value'))これでルートHTMLから検索されることはなくなりました。
つまずき2:要素からEmailが見つからなくなってしまった。
上記の対応で、「//」を消して、root からの検索をしないようにしたのですが、今まで取得できていた、検索がかからなくなり失敗するようになってしまいました。
■原因:「//」が特殊で、通常XPathは全パスを指定しないといけないため。
通常XPath はHTML からの全要素を指定して記述することで、DOMを解析し要素を特定するようで(「//」が特殊で全要素を読み込むという意味)
User の table タグ直下に input が存在しないため、検索に失敗しまいました。element.find_element_by_xpath('tag名') # → 対象要素直下に検索対象のtagがないと検索失敗となる。■対応:子要素 の 内部 全検索は「.//検索条件」
element.find_element_by_xpath('.//tag名') # → 対象要素直下に検索対象のタグがあれば取得する要素.find_element_by_xpath('.//検索条件') で記述するようにすることで、current node からの検索として、全検索できるようでした。
https://stackoverflow.com/questions/21578839/how-to-find-the-element-within-element-in-seleniumbrowserTest.pyfrom selenium.webdriver.remote.webelement import WebElement from selenium.webdriver.chrome.webdriver import WebDriver def printUserEmails(driver:WebDriver)->None: UserTables = driver.find_elements_by_xpath('//table[start-with(@name, "user")]') if len(UserTables) == 0: print('Userが表示されていません。') return for UserTable in UserTables: emailElement = UserTable.find_element_by_xpath('.//input[@type="text" and @name="email"]') print(emailElement.get_attribute('value'))結果.txtuser1@aaa.bbb userDummy@aaa.bbb aaa@aasdfaa.bbb e1@aaa.bbb . .以上です。
- 投稿日:2019-08-20T23:42:34+09:00
Pythonってなんだろう(随時更新)
Pythonって何?
![]()
Python = 汎用プログラミング言語1、スクリプト言語2、Google三大言語3の一柱
- AI、ML(機械学習)、DL(深層学習)に特に向いている(ライブラリが揃っている)
- 本格的なWebサービスが作れる
- ライブラリが豊富
- 可読性に優れている
- コードの書き方が個人に依存しないこんなところにPythonが!!
PythonはGoogleが使用する主要言語になっているように、さまざまなWebサービス/アプリケーションに用いられています。
事例(私が使っている範疇のもので)
- Anki
- シヴィライゼーション4
- World of Tanks
- Google App Engine
- Pepper
特に科学関連のライブラリが豊富
数学ライブラリとして Matplotlib, Numpy, SymPy, Sage
科学ライブラリとして Scipy, scikit-learn, Mlpy
などがある。
- 投稿日:2019-08-20T23:37:34+09:00
SEIYU風のECサイトを作りましょう(2)Xadminを使って管理画面を一新します
前回の記事
SEIYU風のECサイトを作りましょう(1)要求分析とプロジェクト初期化
Xadminとは何か
GitHub上に公開されてるDjangoの管理画面の上位互換パッケージ。
管理画面本来の機能を拡張し、見た目も良くします。
前回の記事で完成した管理画面を、Xadmin使用してリメイクすれば、以下のようになります。
また、スタイルの切り替えも可能ですし、データのexcel出力などもデフォルト機能としてはいってます。
では、早速作っていきます。
Xadminダウンロード
githubのリポジトリはこちらになります。xadmin
- ブランチは
django2
を選んでダウンロードしてください- あるいは本記事のリポジトリextra_appsからxadminを抜いて使ってください
本記事のリポジトリから抜いて使うことがオススメです
注意事項
pip install xadmin
は絶対しないことです、古いバージョンのxadminをおとすことで、パッケージ依存がややこしいことになります。依存パッケージインストール
以下コマンドを実行し、xadminに必要なパッケージをインストールします。
pip install django-crispy-forms pip install django-import-export pip install django-reversion pip install django-formtools pip install future pip install httplib2 pip install sixファイル配置
appsと同じディレクトリで、extra_appsという名のフォルダを作ります。
extra_apps配下にダウンロードしたxadminを置きます。
完成後のディレクトリ構成は以下になります。qiita-Django-supermarket |-- requirements.txt |-- README.md |-- LICENSE |-- CHANGELOG.md |-- api |-- |--api |-- |-- |-- __init__.py |-- |-- |-- settings.py |-- |-- |-- urls.py |-- |-- |-- wsgi.py |-- |--apps |-- |-- |-- goods |-- |-- |-- users |-- |--extra_apps |-- |-- |-- xadmin |-- |--manage.py
その後settings.pyに以下の内容を追加します。
settings.py... sys.path.insert(0, os.path.join(BASE_DIR, 'extra_apps')) ... INSTALLED_APPS = [ ... 'crispy_forms', 'xadmin', ]その後extra_appsをappsと同じように
souruces Root
に指定します。
PyCharmの操作方法は
1.extra_appsを右クリック
2. Mark Directory as
3. souruces Rootusersとgoods appにadminx.pyファイルを作る
usersアプリに移動して、adminx.pyファイルを作ります
cd apps/users vim adminx.pyapps/users/adminx.pyimport xadmin from xadmin import views from .models import VerifyCode class BaseSetting(object): enable_themes = True use_bootswatch = True class GlobalSettings(object): site_title = "ネットスーパー" site_footer = "supermarket_Back" class VerifyCodeAdmin(object): list_display = ['code', 'mobile', "add_time"] xadmin.site.register(VerifyCode, VerifyCodeAdmin) xadmin.site.register(views.BaseAdminView, BaseSetting) xadmin.site.register(views.CommAdminView, GlobalSettings)その後goodsファイルに移動し、adminx.pyを作ります
cd ../goods vim adminx.py
apps/goods/adminx.pyimport xadmin from .models import Goods, GoodsCategory, GoodsImage, GoodsCategoryBrand, Banner, HotSearchWords from .models import IndexAd class GoodsAdmin(object): list_display = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price", "shop_price", "goods_brief", "is_new", "is_hot", "add_time"] search_fields = ['name', ] list_editable = ["is_hot", ] list_filter = ["name", "click_num", "sold_num", "fav_num", "goods_num", "market_price", "shop_price", "is_new", "is_hot", "add_time", "category__name"] class GoodsImagesInline(object): model = GoodsImage exclude = ["add_time"] extra = 1 style = 'tab' inlines = [GoodsImagesInline] class GoodsCategoryAdmin(object): list_display = ["name", "category_type", "parent_category", "add_time"] list_filter = ["category_type", "parent_category", "name"] search_fields = ['name', ] class GoodsBrandAdmin(object): list_display = ["category", "image", "name", "desc"] def get_context(self): context = super(GoodsBrandAdmin, self).get_context() if 'form' in context: context['form'].fields['category'].queryset = GoodsCategory.objects.filter(category_type=1) return context class BannerGoodsAdmin(object): list_display = ["goods", "image", "index"] class HotSearchAdmin(object): list_display = ["keywords", "index", "add_time"] class IndexAdAdmin(object): list_display = ["category", "goods"] xadmin.site.register(Goods, GoodsAdmin) xadmin.site.register(GoodsCategory, GoodsCategoryAdmin) xadmin.site.register(Banner, BannerGoodsAdmin) xadmin.site.register(GoodsCategoryBrand, GoodsBrandAdmin) xadmin.site.register(HotSearchWords, HotSearchAdmin) xadmin.site.register(IndexAd, IndexAdAdmin)urls.pyにxadmin用のpathを追加
api/urls.pyimport xadmin urlpatterns = [ ... path('xadmin/', xadmin.site.urls), ... ]最後にマイグレートを実行します。
xadmin内部にmigrateファイルがあるため、それを実行します。python manage.py makemigrations python manage.py migrateここまで来れば
python manage.py runserverサーバーを立ち上げて
http://127.0.0.1:8000/xadmin
にアクセスすると、xadminにアクセスできるようになってるはずです。補足
下記の二つの手順を実行してください。
apps/goods/apps.pyfrom django.apps import AppConfig class GoodsConfig(AppConfig): name = 'goods' verbose_name = "商品だよ"api/setting.pyINSTALLED_APPS = [ ... 'goods.apps.GoodsConfig' ... ]次回予告
思ったよりxadminを紹介するのに時間がかかりました
次回は一気にAPIを作って行きたいと思います。
そしていよいよVue.jsのセットアップです。
- 投稿日:2019-08-20T23:28:34+09:00
OpenAI Gymの環境 改造方法メモ
OpenAI Gym1とは、OpenAIが提供している強化学習のシミュレーション用プラットフォームであり、強化学習アルゴリズムの開発・比較に利用されています。
基本的な使い方については類似記事が多くあり、お試しで動かしてみる際に非常に参考になります。一方で、色々触ってみると「報酬設計を少し変えて動かしたい」といった感じで、環境に手を加えたい欲が出てきます。ところが、このように環境を改造して動かすにはどうすれば良いのかについてはあまり書かれていないようです。
そこで、自分用にOpenAI Gymの環境の改造方法をメモ書きしてみました。自分でソースコード2を見ながらメモ書きしたため、ベストプラクティスとは言えない方法もあるかもしれません。気づいたことがあった場合、随時記事を更新する予定です。OpenAI Gymの基本的な使い方
OpenAI Gymはpipでインストールすることができます。
pip install gymgymには、強化学習の代表的な環境がいくつか用意されています。
そのうちの一つ、CartPole-v0(カート上のポールの直立状態を維持するゲーム)呼び出したあと、ランダムに行動して動かしてみましょう。import gym from gym.spaces.prng import seed as space_seed env = gym.make('CartPole-v0') #環境の呼び出し #擬似乱数のシードを設定 env.seed(0) #環境の初期化に使用される擬似乱数のシード space_seed(0) #行動に使用される擬似乱数のシード obs = env.reset() #環境を初期化する terminal = False #環境が終端かどうかの判定 n_steps = 0 while not terminal: action = env.action_space.sample() #行動をランダムに選択する obs, reward, terminal, info = env.step(action) #状態を遷移させる n_steps += 1 print(f'step:{n_steps} reward:{reward} terminal:{terminal}')
実行結果
step:1 reward:1.0 terminal:False step:2 reward:1.0 terminal:False step:3 reward:1.0 terminal:False step:4 reward:1.0 terminal:False step:5 reward:1.0 terminal:False step:6 reward:1.0 terminal:False step:7 reward:1.0 terminal:False step:8 reward:1.0 terminal:False step:9 reward:1.0 terminal:False step:10 reward:1.0 terminal:False step:11 reward:1.0 terminal:False step:12 reward:1.0 terminal:False step:13 reward:1.0 terminal:False step:14 reward:1.0 terminal:True環境を呼び出し、env.reset()で初期化して、env.step(action)で行動を遷移させるのがgymの基本的な使い方です。
強化学習のアルゴリズムを実装するときには、actionを方策に基づいて選択する処理と、env.step(action)の戻り値rewardをもとに価値や方策を学習する処理を記述することになります。※ソースコード中のenv.stepの戻り値infoは、デバッグ用の情報をdictに格納した変数です。(正直、使い方がよく分かっていません。)
※終端を表す変数の名前にはdoneを使うのが慣例ですが、doneでは何を表す変数なのかが分かりにくいと思っているのでterminalと命名しています。
環境の改造
gymには、環境インスタンスを内包し、resetやstepの振る舞いの変更を行うためのラッパークラスgym.Wrapperが用意されています。gym.Wrapperを継承したクラスを自作することで環境の改造が可能になります。
環境の改造を実現する自作クラスは、次のように実装します。class MyRemodeledEnv(gym.Wrapper): def __init__(self, env): super().__init__(env) #親クラスの呼び出しを忘れないこと def reset(self, **kwargs): obs = self.env.reset(**kwargs) """ ここにresetの振る舞いを記述 """ return obs #振る舞い変更によって書き換えられた観測を返す def step(self, action): obs, reward, terminal, info = self.env.step(action) """ ここにstepの振る舞いを記述 """ return obs, reward, terminal, info #振る舞い変更によって書き換えられた観測, 報酬, 終端判定, デバッグ情報を返す実装した自作クラスは、次のように呼び出します。
env = gym.make('CartPole-v0') env = MyRemodeledEnv(env)このとき、ラッパーを外からはgym.Envと同じようなクラスに見えるため、両者の違いを意識せずに使うことができます。
また、複数のラッパークラスを実装し、シーケンシャルに呼び出すことも可能です。class MyRemodeledEnv1(gym.Wrapper): """ ラッパー1の改造の記述 """ class MyRemodeledEnv2(gym.Wrapper): """ ラッパー2の改造の記述 """ env = gym.make('CartPole-v0') env = MyRemodeledEnv1(env) env = MyRemodeledEnv2(env)最終的なenvは、2つのラッパーの改造が両方反映されたものになります。これにより、複数の改造を施すとき、それぞれを別のクラスに分けて実装することが可能です。
改造例
前章を踏まえて、以下環境改造の具体例をメモ書きしていきます。
改造例1 状態遷移のスキップ
通常の環境では、1回の行動で1ステップ分状態遷移が行われます。これを、1回行動したら2ステップ分同じ行動を続けるように改造します。
※状態遷移をスキップする考え方は、深層強化学習のベースとなった論文にて、学習の工夫の一つとして取り入れられているものです。
class SkippingEnv(gym.Wrapper): def __init__(self, env, n_skips=2): assert n_skips >= 1 super().__init__(env) self.n_skips = n_skips #スキップ数 def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): #n_skips分同じ行動を続ける for _ in range(self.n_skips): obs, reward, terminal, info = self.env.step(action) if terminal: break return obs, reward, terminal, info
動かしてみたときの実装と結果
実装env = gym.make('CartPole-v0') #オリジナルの環境の呼び出し env = SkippingEnv(env) #実装したラッパーの呼び出し env.seed(0) space_seed(0) obs = env.reset() terminal = False n_steps = 0 while not terminal: action = env.action_space.sample() obs, reward, terminal, info = env.step(action) n_steps += 1 print(f'step:{n_steps} reward:{reward} terminal:{terminal}')結果
step:1 reward:1.0 terminal:False step:2 reward:1.0 terminal:False step:3 reward:1.0 terminal:False step:4 reward:1.0 terminal:False step:5 reward:1.0 terminal:False step:6 reward:1.0 terminal:False step:7 reward:1.0 terminal:False step:8 reward:1.0 terminal:False step:9 reward:1.0 terminal:Trueオリジナルの環境では全部では終端まで18ステップを要していますが、SkippingEnvによって2ステップ間続けて同じ行動が与えられるため、改造した環境では半分の9ステップして要していないように見えます。
SkippingEnvを呼び出すときのキーワード引数n_skipsによって、スキップ数を指定できるように実装しました。
このように、オリジナルの環境にない変数を持たせて改造に利用することも可能です。改造例2 報酬のリスケーリング
CartPole-v0では常に1.0の報酬が与えられますが、この報酬の大きさを0.1に変更します。
例1のようにgym.Wrapperを継承してもいいのですが、gymには報酬を変更するためのラッパークラスgym.RewardWrapperが用意されており、これを使うことにします。
gym.RewardWrapperを継承する場合、rewardメソッドを実装し、そこに報酬の変更を記述することで、step時の報酬を変えることができます。※この改造には、定常的な報酬として1.0は大きすぎるため、小さくしたいという動機があります。
class RewardRescalingEnv(gym.RewardWrapper): def __init__(self, env, c=0.1): super().__init__(env) self.c = c lb_r, ub_r = self.env.reward_range #報酬の下界と上界 self.reward_range = (lb_r*c, ub_r * c) #self.reward_rangeの上書き def reward(self, reward): return reward * self.c
動かしてみたときの実装と結果
実装env = gym.make('CartPole-v0') #オリジナルの環境の呼び出し env = RewardRescalingEnv(env) #実装したラッパーの呼び出し env.seed(0) space_seed(0) obs = env.reset() terminal = False n_steps = 0 while not terminal: action = env.action_space.sample() obs, reward, terminal, info = env.step(action) n_steps += 1 print(f'step:{n_steps} reward:{reward} terminal:{terminal}')結果
step:1 reward:0.1 terminal:False step:2 reward:0.1 terminal:False step:3 reward:0.1 terminal:False step:4 reward:0.1 terminal:False step:5 reward:0.1 terminal:False step:6 reward:0.1 terminal:False step:7 reward:0.1 terminal:False step:8 reward:0.1 terminal:False step:9 reward:0.1 terminal:False step:10 reward:0.1 terminal:False step:11 reward:0.1 terminal:False step:12 reward:0.1 terminal:False step:13 reward:0.1 terminal:False step:14 reward:0.1 terminal:True
CartPole-v0ではreward_rangeが(-inf, inf)になっているためreward_rangeの上書きは不要ですが、reward_rangeが有界な環境にも使えるようにするため、今回の実装では上書きしています。このように、報酬を少し変えるだけならgym.RewardWrapperを継承するだけで十分です。同様に、gymには状態や行動を変える専用のラッパークラスObservationWrapperやActionWrapperも用意されています。
改造例3 報酬のスパース化
強化学習では、終端以外の状態の報酬を0にするスパースな設計にすることがよくあります。そこで、CartPole-v0の報酬を、ポールが倒れたら-1、それ以外は0になるように改造します。
CartPole-v0の終端判定がTrueになるケースには、次の2つがあります。
1.ゲームオーバーになる(ポールが倒れる、カートが左右に動きすぎる)
2.200ステップ経過するこれらを区別して報酬を設計する必要があるため、例2のようにgym.RewardWrapperを継承するのではなく、gym.Wrapperを継承してstepメソッド内に報酬の計算を記述します。
class SparseRewardEnv(gym.Wrapper): def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): obs, reward, terminal, info = self.env.step(action) x, x_dot, theta, theta_dot = self.unwrapped.state #ゲームオーバーかどうかの判定 failed = x < -self.unwrapped.x_threshold \ or x > self.unwrapped.x_threshold \ or theta < -self.unwrapped.theta_threshold_radians \ or theta > self.unwrapped.theta_threshold_radians reward = -1 * int(failed) #rewardの再計算 return obs, reward, terminal, info
動かしてみたときの実装と結果
実装env = gym.make('CartPole-v0') #オリジナルの環境の呼び出し env = SparseRewardEnv(env) #実装したラッパーの呼び出し env.seed(0) space_seed(0) obs = env.reset() terminal = False n_steps = 0 while not terminal: action = env.action_space.sample() obs, reward, terminal, info = env.step(action) n_steps += 1 print(f'step:{n_steps} reward:{reward} terminal:{terminal}')結果
step:1 reward:0 terminal:False step:2 reward:0 terminal:False step:3 reward:0 terminal:False step:4 reward:0 terminal:False step:5 reward:0 terminal:False step:6 reward:0 terminal:False step:7 reward:0 terminal:False step:8 reward:0 terminal:False step:9 reward:0 terminal:False step:10 reward:0 terminal:False step:11 reward:0 terminal:False step:12 reward:0 terminal:False step:13 reward:0 terminal:False step:14 reward:-1 terminal:Trueself.unwrappedはラッパーが内包している環境インスタンスを取得するプロパティです。self.unwrappedによって、ラッパーから直接envの属性にアクセスし、ゲームオーバーの判定を記述することが可能になります。
この判定には、オリジナルの環境のソースコードにおけるstepメソッド内の(ケース1.の)終端判定の記述を引用しました。unwrappedを利用すれば、ポールが倒れたと判定させる角度の変更、行動によって与えられるカートへの加速度の変更など、大掛かりな改造が可能になります。
環境例4 カートの座標の報酬化
CartPoleはポールが直立できていればx座標はどこでもよい(=報酬は変わらない)という環境ですが、カートが右にあるほど多くの報酬を与えることにします。
カートのx座標に応じて報酬を与えるPositionRewardEnvと、ゲームオーバーになったら-1の報酬を与えるPenaltyRewardEnvの2つのラッパーを作成してシーケンシャルに呼び出します。class PositionRewardEnv(gym.Wrapper): def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): obs, reward, terminal, info = self.env.step(action) x, *_ = self.unwrapped.state low_x = self.observation_space.low[0] high_x = self.observation_space.high[0] x_hat = (x - low_x) / (high_x - low_x) reward = 0.1 * x_hat return obs, reward, terminal, info class PenaltyRewardEnv(gym.Wrapper): def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): obs, reward, terminal, info = self.env.step(action) x, _, theta, _ = self.unwrapped.state #ゲームオーバーかどうかの判定 failed = x < -self.unwrapped.x_threshold \ or x > self.unwrapped.x_threshold \ or theta < -self.unwrapped.theta_threshold_radians \ or theta > self.unwrapped.theta_threshold_radians if failed: reward = -1 #rewardの再計算 return obs, reward, terminal, info
動かしてみたときの実装と結果
実装
env = gym.make('CartPole-v0') #オリジナルの環境の呼び出し #ラッパーをシーケンシャルに呼び出す env = PositionRewardEnv(env) env = PenaltyRewardEnv(env) env.seed(0) space_seed(0) obs = env.reset() terminal = False n_steps = 0 while not terminal: action = env.action_space.sample() obs, reward, terminal, info = env.step(action) n_steps += 1 print(f'step:{n_steps} reward:{reward} terminal:{terminal}')結果
step:1 reward:0.04954548738784783 terminal:False step:2 reward:0.04951449351165423 terminal:False step:3 reward:0.04952411136201068 terminal:False step:4 reward:0.04957432375449645 terminal:False step:5 reward:0.049583832949732204 terminal:False step:6 reward:0.04963395590817136 terminal:False step:7 reward:0.049724692913380275 terminal:False step:8 reward:0.04985606183561966 terminal:False step:9 reward:0.05002809606219364 terminal:False step:10 reward:0.05024084065495276 terminal:False step:11 reward:0.05049434647436899 terminal:False step:12 reward:0.05078866188204178 terminal:False step:13 reward:0.05104259318449751 terminal:False step:14 reward:-1 terminal:True画像がないので分かりづらいですが、カートが中央の位置の時報酬が0.5になり、終端状態では-1になっています。
つまり、結果から左から右に少しだけ移動してポールが倒れていることが分かります。
強化学習とは、報酬を多く獲得する行動を学習する手法です。
したがって、この環境に強化学習を適用することで、ポールを立たせたまま右に移動してギリギリで止まるような動きを学習できそうです。学習させてみた
試しに、前章の例4を学習させてみました。学習にはchainerRL3のDouble DQNを使いました。
思ったよりも学習が難しく、少し大掛かりになってしまいました。
実装
import numpy as np import matplotlib.pyplot as plt import gym from gym.spaces.prng import seed as space_seed import chainer import chainer.functions as F import chainerrl from PIL import Image class PenaltyRewardEnv(gym.Wrapper): def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): obs, reward, terminal, info = self.env.step(action) x, _, theta, _ = self.unwrapped.state #ゲームオーバーかどうかの判定 failed = x < -self.unwrapped.x_threshold \ or x > self.unwrapped.x_threshold \ or theta < -self.unwrapped.theta_threshold_radians \ or theta > self.unwrapped.theta_threshold_radians if failed: reward = -1 #rewardの再計算 return obs, reward, terminal, info class PositionRewardEnv(gym.Wrapper): def reset(self, **kwargs): return self.env.reset(**kwargs) #resetは変更なし def step(self, action): obs, reward, terminal, info = self.env.step(action) x, _, _, _ = self.unwrapped.state low_x = self.observation_space.low[0] high_x = self.observation_space.high[0] x_hat = (x - low_x) / (high_x - low_x) reward = 0.1 * x_hat return obs, reward, terminal, info class ObsForChainerRLEnv(gym.ObservationWrapper): def observation(self, observation): return np.array(observation, dtype=np.float32, copy=False) def play_episode(env, agent, mode='train', frames=None): #mode='train' : 探索あり行動、ネットワークの更新あり #mode='eval' : 探索なし行動、ネットワークの更新なし assert mode in ['train', 'eval'] assert frames is None or isinstance(frames, list) if mode == 'train': explor_and_update = True #行動に探索を取り入れて学習するかどうかの判定 elif mode == 'eval': explor_and_update = False obs = env.reset() if frames is not None: frames.append(Image.fromarray(env.render(mode='rgb_array'))) reward = 0 terminal = False total_reward = 0 while not terminal: if explor_and_update: action = agent.act_and_train(obs, reward) else: action = agent.act(obs) obs, reward, terminal, info = env.step(action) total_reward += reward if frames is not None: frames.append(Image.fromarray(env.render(mode='rgb_array'))) if explor_and_update: agent.stop_episode_and_train(obs, reward) env.close() return total_reward if __name__ == '__main__': env = gym.make('CartPole-v0') #環境の呼び出し env = PositionRewardEnv(env) env = PenaltyRewardEnv(env) env = ObsForChainerRLEnv(env) #状態をchainer用に変えるラッパー #擬似乱数のシードを設定 env.seed(0) space_seed(0) np.random.seed(0) #Qネットワークの生成 q_func = chainerrl.q_functions.FCStateQFunctionWithDiscreteAction( env.observation_space.shape[0], env.action_space.n, n_hidden_layers=2, n_hidden_channels=256, nonlinearity=F.relu ) #勾配降下法アルゴリズムの生成 optimizer = chainer.optimizers.Adam(alpha=1e-5) optimizer.setup(q_func) gamma = 0.99 #方策の生成 explorer = chainerrl.explorers.ConstantEpsilonGreedy( epsilon=0.1, random_action_func=env.action_space.sample ) #経験メモリの生成 replay_buffer = chainerrl.replay_buffer.ReplayBuffer(capacity=10**5) #エージェント(DoubleDQN)の生成 #GPUは使っていません(使用しないほうが高速でした) agent = chainerrl.agents.DoubleDQN( q_func, optimizer, replay_buffer, gamma, explorer, replay_start_size=500, minibatch_size=32, update_interval=1, target_update_interval=10000 ) total_reward_history = [] for e in range(1, 10001): total_reward = play_episode(env, agent, mode='train') total_reward_history.append(total_reward) #500エピソードごとに統計情報の表示 if e % 500 == 0: print(f'episode:{e} statistics:{agent.get_statistics()}') smoothing = [np.mean([total_reward_history[i: i+100]]) for i in range(10000)] plt.figure() plt.plot(np.arange(1, 10001), total_reward_history, color='Blue', alpha=0.1) plt.plot(np.arange(1, 10001), smoothing, color='Blue') plt.show() #10回評価モードでエピソードを実行 for e in range(1, 11): frames = [] play_episode(env, agent, mode='eval', frames=frames) frames[0].save( 'result_' + str(e) + '.gif', save_all=True, append_images=frames[1:], optimize=False, duration=1000/50, loop=0 )結果
10000エピソード学習させ、各エピソードで獲得した報酬の合計をプロットしたグラフがこちらです。
(薄い線が実際の報酬、濃い線が直近100エピソード分の平均を取ったときの報酬を示しています。)また、学習後に探索なしでエピソードを実行させたときの様子がこちらになります。
ポールを倒さないようにカートを右に移動させる動きを学習できています。
※最初に少しだけ左に移動させてポールをちょっと右に倒してから、カートを右に移動させることでポールを直立させています。また、ポールを直立させたら徐々に減速させ、カートが右に移動しすぎないよう少しずつ移動しています。思ったより高度な動きになりました。
まとめ
本記事では、OpenAI GymのWrapperクラスを使って環境を改造する方法について書きました。
また、例として、環境の報酬を変えて別の動きを学習させてみました。
最後の学習例はサラっと書いていますが、パラメータ調整にはちょっと苦戦していました。練習としてちょうどいい難易度だったかもしれませんね。
- 投稿日:2019-08-20T23:15:40+09:00
Python勉強の記録_190819
やったこと
- 競技プログラミング続き
- やさしく始めるラズベリー・パイを読み始めた
学び
- Global
#local a = "before" def sample(): a = "after" print(a) #before sample() print(a) #before #global a = "before" def sample(): global a a = "after" print(a) #before sample() print(a) #after
- リストのcountとindex
lst = [1,1,4,6,7] print(lst.count(1)) #2 print(lst.index(6)) #3 print(lst.index(1)) #0(最初にある要素のインデックスを表示) print(lst.index(0)) #エラー
- 投稿日:2019-08-20T22:39:58+09:00
はじめての Flask #5 ~JSONを返すWebAPIを書こう~
前回: はじめての Flask #4 ~データベースをSQLAlchemyでいじってみよう~
今までの学習を通して、様々なwebアプリが作れるようになったと思います。
さて、今回はいよいよJSONを返すWebAPIを作ろうと思います!
何がうれしいの
WebAPIがあると何がうれしいの?ということですが、大まかに以下の理由があげられます。
・内部処理とビュー(外面)を分けることができる
・さまざまなプラットフォームでの利用に簡単に対応することができる
・細かく処理を分けることができるので、設計自体が非常にわかりやすく、かつ組み立てやすくなる上記の3つが主に考えられるものです。
それぞれわかりやすく説明してみましょう。内部処理とビュー(外面)を分けることができる
今までの内容で作ったwebサービスであれば、1つのページに対して1つの動作というペアが完成していました。あるページからほかのページの内容をそのまま取得することはできず、情報の取得はページの遷移でしか行えないものでした。つまり、情報の内容とUI(HTMLなど)は切り離せない状態で返すしかなかったのです。
しかし、JSONという情報のみを扱うデータを返すことができるようになると、その情報の表現はUI次第になります。UIが古臭いならば見てくれのみ改修すればよく、セキュリティに問題があるならばWebAPIのみ変更すればよいのです。
この分離が、WebAPI最大の特徴ともいえます。さまざまなプラットフォームでの利用に簡単に対応することができる
上記の通り、情報とUIの分離に成功すれば、UIのみそれぞれのに対応するように設計すれば、WebAPI自体はノータッチで再利用できます。
ブラウザ版も、スマホアプリ版も、どのような利用方法であったとしても内部処理は変わらないので、バックエンド開発側もフロントエンド開発側も楽に実装ができます。細かく処理を分けることができるので、設計自体が非常にわかりやすく、かつ組み立てやすくなる
あたかもプログラム内で関数を定義するかのように、目的の動作を行うエンドポイントを作成すればその組み合わせで容易に処理が実行できます。
目的の異なるエンドポイントを複数作成する方が、互いに複雑に絡み合った長大な単一のエンドポイントを作成・保守するよりも容易なことは想像に難くないはずです。早速作ろう!
ということで、まず大切なJSONの返し方から学んでいきましょう!
JSONの返し方
from flask import * app = Flask(__name__) @app.route("/") def index(): return jsonify({"language": "python"}) if __name__ == "__main__": app.run(port=8888)このように
flask.jsonify
にdict型を与えるとそれをJSONにダンプしてくれますので、それを返しましょう。ちなみに、このjsonifyを行ったときはheader
のContent-Type
もapplication/json
にしてくれます。ありがとうFlask。このページに接続すると
{"language": "python"}というJSONを返します。
ちなみに
from flask import * import json app = Flask(__name__) @app.route("/") def index(): return json.dumps({"language": "python"}) if __name__ == "__main__": app.run(port=8888)のように自ら
json.dumps
を使ってJSONをダンプすることもできますが、こちらはheader
のContent-Type
がtext/html; charset=utf-8
となり、みずからheader
を書き換える必要が出てくるので、flask.jsonify
を使う方が良いです。ステータスコードも一緒に返そう!
WebAPIを作ると「認証が間違っている!!(400番)」、「お前ちょっとリクエスト送りすぎ!!(429番)」、「ごめん、エラー起きた(500番)」など、200番以外のステータスコードを返したくなることもあります(というか返しましょう)。
そのときには、エンドポイント内のreturn
で、ステータスコードの番号も一緒に返しましょう!from flask import * app = Flask(__name__) @app.route("/") def index(): return jsonify({"language": "python"}), 418 if __name__ == "__main__": app.run(port=8888)このようにすることで、今回であれば「I'M A TEAPOT」(418番)のステータスコードを返すことができます。
日本語を返したい!
ちなみに、flaskの
flask.jsonify
では、すべての文字はデフォルトではascii
となります。ですので、日本語を返そうと以下のようなコードを書くと、不都合が起きます。from flask import * app = Flask(__name__) @app.route("/") def index(): return jsonify({"language": "パイソン"}), 418 if __name__ == "__main__": app.run(port=8888)帰ってくるJSONは
{"language":"\u30d1\u30a4\u30bd\u30f3"}となります。わぁ、Unicodeだぁ…。
はい、ですので、文字コードの設定をUTF-8(asciiじゃなくする)
にしてあげましょう。from flask import * app = Flask(__name__) app.config["JSON_AS_ASCII"] = False @app.route("/") def index(): return jsonify({"language": "パイソン"}), 418 if __name__ == "__main__": app.run(port=8888)このように、
app.config["JSON_AS_ASCII"]
をFalse
にすることで日本語・漢字・そのほか多くの文字をJSONに正しくダンプできます。これまでの情報と、
GET
,POST
(ほかにもPUT
などのメソッドもありますが、それは他の記事に譲ります)のやり取りを組み合わせることで、WebAPIは作成できます。それではみなさん、良いPython Lifeを!!
- 投稿日:2019-08-20T21:28:26+09:00
Pythonでつくる彼女入門 ~Tinder自動化プロジェクト~ 第4話
目次
やったこと 主な出来事 第1話 自動右スワイプ 第2話 自動メッセージ送信 女性とマッチした 第3話 ライブラリ化 マッチした女性とLINEを交換した 第3.5話 アクセストークンの再取得 これまでのコードではトークンが取得できなくなっていた 第4話 データ収集 LINEの返信が来なくなった コードはGitHubから閲覧できます。
前回までのあらすじ
- 自動右スワイプ機能を作成した
- メッセージ送信を自動化した
- AWSを用いてコードを定期実行した
- TinderのAPIを簡単に叩くためのライブラリを作成した
近況
前回LINE交換した子から返事が来なくなった...なんでだ...
気を取り直してコードを書いていきましょう。やはり僕の恋人はPCですね。決して裏切らない。
第3話が課題1を消化したところで終わってしまったので、その続きです。課題2 イロ・スコアってなに??
第1話のコメント欄にて、@gshirato さんからEloスコアというのものの存在を教えていただきました(@gshiratoさんありがとうございます)。
なんでも、全部の相手を右スワイプしていると女性側に自分のプロフィールが表示されにくくなるとか...。
以下は公式ブログ[1]からの引用です。Tinderのアルゴリズムのこの部分がLikeとNopeを比較し、相手プロフィールの扱い方に関する類似パターンを見出します。そのデータに基づいて相性がいいと考えられるマッチ候補を表示するために、このアルゴリズムが使われていたのです。
(中略)
現在、Tinderではイロに依存することはありません。もちろんお互いがプロフィールにLikeをつけてマッチが成立したユーザーについてそれぞれのユーザーを検討する場合はイロが重要な役割を果たします。現在のTinderのシステムでは、あなたのプロフィールがLikeやNopeを付けられる都度、あなたに表示されるマッチ候補が調整されています。どうやらイロ・スコアとうのは自分との相性を数値化したもののようで、現在は使われていないようです。実際、サーバーからの返り値を見ても、それっぽい値は見当たりませんでした。
残念。これがあれば、自分のスコアを高める工夫をするのはもちろん、相手のスコアを確認することで、右スワイプをする前にマッチするかどうかが判断できるかもしれなかったのに。
本当にこのスコア使われてないのかな。最近マッチしなくなってきた気がするんだけど実際全右スワイプって、(イロ・スコアの存在を抜きにしても)効率悪いんですよね。しっかり数えたわけではないですが、現在まで自動右スワイププログラムを可動させた日数とマッチの数から、右スワイプでマッチする確率は約0.01%。
流石になんとかしたいですよね。よし、機械学習だ。
データ収集
まずはデータを収集します。どういったプロフィールの人がマッチしやすく、どういった人はマッチしづらいのかをモデルに教えるために、データは必須です。
意外とルールベースな特徴とか見つかったりしないかなぁ。データベースとストレージ
まずは収集する情報を決めましょう。マッチするかどうかの判断の参考になりうる情報というと...
- 写真
- 自己紹介文
- 学校
- 仕事
- 年齢
- 自分との距離
等でしょうか。やはり写真と自己紹介は外せないですよね。
これらの情報を収集し、保存しておくためのストレージ(あるいはDB)が必要です。
最終的に分析することを考えると、colaboratoryと相性のいいGoogle DriveとGoogle spreadsheetがいいでしょうか。容量も多いし。GASで制御できるし。
全体図は下のようになる予定です。
AWS Lambda
せっかく第3話でライブラリを作ったので、それを活かします。まずは自動右スワイプを行うコードです。
lambda_function.pyimport json import tinpy #FBemail = "Facebookのメールアドレス" #FBpass = "Facebookのパスワード" #token = tinpy.getAccessToken(FBemail, FBpass) token = "Facebookのトークン" def lambda_handler(event, context): api = tinpy.API(token) api.setLocation(35.658034, 139.701636) for user in api.getNearbyUsers(): if api.getLikesRemaining() == 0: break user.like() for match in api.getMatch(): messages = match.messages if len(messages) == 0: match.sendMessage( "はじめまして{0}さん! マッチありがとうございます!".format(match.name))これをAWS Lambdaに登録して実行するだけで自動右スワイプが可能です。
続いて、データベースにユーザーの情報を送信するコードと、マッチしたアカウントを送信するコードが必要です。
といっても送信するだけなので別に難しくはありません。lambda_function.pyimport requests url = "ここにGASのURL(後述)を記載" def sendProfile(user): data = {"id": user.id, "name": user.name, "age": user.age, "gender": user.gender} data["bio"] = user.bio for i in range(len(user.photos)): data["photo{0}".format(i)] = user.photos[i] data["videos"] = user.videos data["schools"] = user.schools data["jobs"] = user.jobs data["distance_mi"] = user.distance_mi with requests.Session() as s: print(data) s.post(url, data=data) def sendMatch(match): with requests.Session() as s: data = {"id": match.id} s.get(url, params=data)urlのパラメーターは、後に作るデータベースのエンドポイントです。
スワイプ情報とマッチ情報の2種類の情報を送る必要があるので、GETとPOSTで区別しています。
データベースを作ってからでないと結局Lambda側のコードは完成しないので、最終的な完成品は後に回すとして、次はデータベースを作成したいと思います。Google Apps Script
Google Apps Script (GAS)は、GmailやDriveといったGoogleの各種サービスを制御するAPIを兼ね備えたJavaScript実行環境(みたいなもの)です。Googleアカウントを持っていれば無料で使うことができます。
外部とのHTTPS通信や、コードの定期実行ができたり、URLを取得してウェブページを表示したりと、もはやサーバーとして利用が可能(?)なサービスです。まずは新しいプロジェクトを作成します。GASにはスタンドアロンなプロジェクトとスプレッドシートに紐付いたプロジェクトが存在しますが1、今回はスプレッドシートに紐付いたものを用います。
新しいスプレッドシートを作成して、ツール > スクリプトエディタを選択してください。
図のようなエディタが別タブで立ち上がればOKです。
つづいて、Google Driveの好きな位置に、ファイルを保存するためのフォルダを作っておいてください。エンドポイント作成
まずは、スプレッドシートのIDと、Google Driveのデータ保存用フォルダのIDを取得します。
ブラウザでGoogle Driveのフォルダを開くと、URLがhttps://drive.google.com/drive/u/0/folders/XXXXXXXXXXXX
となっています。このXXXXXXXXXXXX
がフォルダのIDです。
また、先程作成したスプレッドシートのURLを見ると、https://docs.google.com/spreadsheets/d/XXXXXXXXXXXX/edit#gid=0
となっていると思います。こちらもXXXXXXXXXXXX
がスプレッドシートのIDです。
どちらもメモしておいてください。つづいて、データ保存用のプログラムを書いていきます。
GASでは、doPost(e), doGet(e)というfunctionを用意することで、外部からPOST, GETでアクセスした際にこれを起動することができます。
ということで、受け取ったデータをSpread SheetのAPIを用いてシートに記入していきます。tinder.jssheetId = "スプレッドシートのID"; folderId = "フォルダのID"; //スワイプしたプロフィールの情報を記録する function doPost(e) { var sheet = SpreadsheetApp.openById(sheetId).getSheets()[0];//スプレッドシートにアクセス var id = e.parameter.id; var name = e.parameter.name; var age = e.parameter.age; var gender = e.parameter.gender; var distance_mi = e.parameter.distance_mi; var bio = e.parameter.bio; var jobs = e.parameter.jobs; var schools = e.parameter.schools; var match = 0; var timestamp = Math.round((new Date()).getTime() / 1000); var datas = [ [id, name, age, gender, distance_mi, bio, jobs, schools, match, timestamp] ] var photo0 = e.parameter.photo0; var photo1 = e.parameter.photo1; var photo2 = e.parameter.photo2; var photo3 = e.parameter.photo3; var photo4 = e.parameter.photo4; var photo5 = e.parameter.photo5; var photos = [photo0, photo1, photo2, photo3, photo4, photo5]; var row = sheet.getLastRow() + 1;//最終行を取得 sheet.getRange(row, 1, 1, 10).setValues(datas); //写真を保存 for(i=0;i<photos.length;i++){ var photo = photos[i]; if (photo != null) { var name = id + "-" + i + ".jpg"; var blob = getImage(photo, name); saveFile(blob); } } } function getImage(url, name) { var response = UrlFetchApp.fetch(url);//urlにアクセスしてデータを取得 var response = response.getBlob().setName(name);// return response } function saveFile(blob) { var folder = DriveApp.getFolderById(folderId); folder.createFile(blob) } //マッチしたプロフィールの情報を記録する function doGet(e){ var sheet = SpreadsheetApp.openById(sheetId).getSheets()[0]; var id = e.parameter.id; var ids=sheet.getRange(1,1,row).getValues();//これまでマッチした人すべてのidを取得 for(i=0;i<ids.length;i++){ if(id==ids[i][0]){ sheet.getRange(i+1,9).setValue(1); return; } } }コードを作成したら、エディタ上部にある[公開]>[ウェブアプリケーションとして導入]を選択し、
- プロジェクトバージョン: New
- 次のユーザーとしてアプリケーションを実行: 自分(hoge @ fuga.com)
- アプリケーションにアクセスできるユーザー: 全員(匿名ユーザーを含む)
として導入をクリックします。
表示されるURLをメモしておいてください。これがエンドポイントです。これを先程作成した
lambda_fucntion.py
に記入します。
最終的なコードは下記のとおりです。lambda_function.pyimport requests import json import tinpy #FBemail = "Facebookのメールアドレス" #FBpass = "Facebookのパスワード" url = "GASのURL" #token = tinpy.getAccessToken(FBemail, FBpass) token = "Facebookのトークン" def lambda_handler(event, context): api = tinpy.API(token) api.setLocation(37.658034, 139.701636) print(api.getLikesRemaining()) for user in api.getNearbyUsers(): if api.getLikesRemaining() == 0: break user.like() sendProfile(user) for match in api.getMatch(): sendMatch(match) messages = match.messages if len(messages) == 0: match.sendMessage( "はじめまして{0}さん! マッチありがとうございます!".format(match.name)) return def sendProfile(user): data = {"id": user.id, "name": user.name, "age": user.age, "gender": user.gender} data["bio"] = user.bio for i in range(len(user.photos)): data["photo{0}".format(i)] = user.photos[i] data["videos"] = user.videos data["schools"] = user.schools data["jobs"] = user.jobs data["distance_mi"] = user.distance_mi with requests.Session() as s: print(data) s.post(url, data=data) def sendMatch(match): with requests.Session() as s: data = {"id": match.id} s.get(url, params=data)あとはこれを保存して定期実行すれば、データ収集はバッチリです!
次回はいよいよデータ分析です!良いデータが溜まりますように...!
参考文献
[1]https://blog.gotinder.com/powering-tinder-r-the-method-behind-our-matching/
[2]https://tonari-it.com/gas-url-doget-parameter/
他にもフォームに紐付いたプロジェクトなんかもあります。 ↩
- 投稿日:2019-08-20T21:00:49+09:00
Windows2003(with Atom)にPythonを導入する
Windows2003(with Atom)にPythonを導入する
やめましょう.
環境
OS : Windows server 2003 Standerd Endition
CPU : Atom(TM) CPU 330 @ 1.60 GHz
日付 : 2019/8/10目的
デュアルブートとか仮想化とかを禁止されているけど,Python3環境が欲しい
やること
Python3.4のダウンロード
Pythonの公式サイトから32bit版WindowsのPythonをダウンロード
Python3.4以下じゃないと動かない(らしい).
なお,IE8からではアクセスできなかったのでchromeを入れた.Pathを通す
コマンドプロンプトで,以下のコマンド打つ.
C:\User\Pythonの部分はさっきダウンロードして解凍したディレクトリを指定.
set path = %path%;C:\User\Python
これで,コマンドプロンプト上でpythonと打てば動くようになっているはず.pip を使えるようにする
まずはpipを使えるようにする.
これはやらなくても動くかもしれない.
python -m ensurepip
setuptoolsは必ずアップグレードしときましょう.
python -m pip install --upgrade pip setuptools
pipが使える状態になれば,以下のようにpipでパッケージインストールができる.
python -m pip install numpy==1.15.0
python -m pip install pandas==0.16.0ダメだったこと
Anacondaが入れば早いのだが以下のエラーで動かなかった.
プロシージャエントリポイントGetFinalPathNameByHandleWがダイナミックリンクライブラリKERNEL32.dllから見つかりませんでした.
そもそもこのWindowsにはpython3.4以前しか動かない(らしい)のでそっちが原因かもしれない.ちなみにLightGBMは32bitに対応していない.
最後に
買い換えた方が良いと思います.
- 投稿日:2019-08-20T20:09:30+09:00
AtCoder Grand Contest 037 参戦記
AtCoder Grand Contest 037 参戦記
A - Dividing a String
52分で突破. そもそも山でバスに間に合わず、蒲田駅についたのが21時5分って感じで、15分以上遅れてスタート. 問題文をちゃんと見ていなく、最初はすべての分割文字がユニークだと勘違いして、なんて難しい問題だって唸ってた. 正しく題意を理解してサラッと書いたのが以下. ダメでも良いやで流して AC. 正直なところ、最後と最後の一個手前が違うことを確認してないので、意地悪なテストケースを用意されると転けそうな気がする. 後から、分割文字は1文字→1文字、1文字→2文字、2文字→1文字の3パターンしか無いのかーって思った.
s = input() t = '' k = 0 prev = '' for i in range(len(s)): t += s[i] if prev != t: k += 1 prev = t t = '' print(k)C - Numbers on a Circle
敗退. B はもう問題文を見ただけで無理って思ったので飛ばしてこっちをやってた. 問題を見た感想は、「えー、どの順でやればいいの. 総当りしたら組合せ爆発しない?」って気分だった. とりあえず順方向だと最小どころかそもそもAからBにたどり着ける気がしないので、逆向きにやっていくことにし、回数最小ってことはできる限り小さくしない(小さいと引ける数が減って最小にならない)のがいいのかなと、一番大きいのを順に置き換えていくことを計画. 一番大きいのをどんどん取り出すために順序付きキューを使って実装してみた. TLE にはなるものの WA はでず、方針はこれ出会ってるかと思ったものの、処理量を減らす方法が全く思いつかずタイムアップ.
def main(): import sys from heapq import heappush, heappop, heapify _int = int n = _int(input()) a = [_int(e) for e in input().split()] b = [_int(e) for e in input().split()] result = 0 finished = 0 hq = [(-b[i], i) for i in range(n)] heapify(hq) while True: _, i = heappop(hq) if a[i] == b[i]: finished += 1 continue if i == 0: b[i] -= b[n - 1] + b[1] elif i == n - 1: b[i] -= b[n - 2] + b[0] else: b[i] -= b[i - 1] + b[i + 1] if a[i] > b[i]: print(-1) sys.exit() result += 1 if a[i] == b[i]: finished += 1 if finished == n: print(result) sys.exit() else: heappush(hq, (-b[i], i)) main()解説 PDF を読んで、一気に減らして大丈夫なのか、本当にこれで最小になるのかと思いつつ、実装したら AC. 未だに本当に最小回数なんだろうかって思っている自分がいる.
def main(): import sys _int = int n = _int(input()) a = [_int(e) for e in input().split()] b = [_int(e) for e in input().split()] result = 0 q = [i for i in range(n) if b[i] != a[i]] while len(q) != 0: nq = [] c = 0 for i in q: if i == 0 or i == n - 1: j = b[(n + i - 1) % n] + b[(n + i + 1) % n] else: j = b[i - 1] + b[i + 1] if j > b[i] - a[i]: nq.append(i) continue c += 1 k = (b[i] - a[i]) // j result += k b[i] -= j * k if a[i] != b[i]: nq.append(i) if c == 0 and len(nq) != 0: print(-1) sys.exit() q = nq print(result) main()
- 投稿日:2019-08-20T18:47:33+09:00
【Python】【Django】CSRFトークンエラーの検知テスト
概要
Djangoでテンプレートのフォームを手作業で作ると時々やらかすミス、「CSRFトークン用タグのつけ忘れ」。
runserver
でテスト環境を動かすことで検知することが多いですが、ユニットテストではどこで検知できるかを確かめてみました。目次
ソースコード
DjangoテストフレームワークでCSRFトークンエラーを検出する
- config
- プロジェクト設定
- sample
- フォームで入力したメッセージを保存するだけのサンプルアプリケーション
- templates
- HTMLテンプレート
- tests
- ユニットテスト
- sample:
django.test.testcases.TestCase
を用いたテスト- e2e:
django.test.testcases.LiveServerTestCase
とSelenium WebDriverを用いたテストsampleアプリケーション
モデル
sample/models.py
単純にメッセージだけを保持するモデルです。
from django.db import models # Create your models here. class Sample(models.Model): message = models.CharField(verbose_name='メッセージ', max_length=255) """サンプルアプリケーションモデル""" class Meta: # テーブル名 db_table = 'sample'フォーム
sample/fomrms.py
sample/models.py
で定義したSample
モデルとdjango.models.ModelForm
を用いて、メッセージ用のフォームを定義します。from django import forms from .models import Sample class SampleForm(forms.ModelForm): """サンプルフォーム""" class Meta: model = Sample fields = ('message',) widgets = { 'message': forms.Textarea(attrs={'placeholder': 'メッセージ'}) }ビュー
sample/views.py
sample/forms.py
で定義したSampleForm
を用いたビュークラスを定義します。from django.shortcuts import render,redirect from .forms import SampleForm from django.urls import reverse from django.views import View class SampleFormView(View): # Create your views here. def get(self, request, *args, **kwargs): context = { 'form': SampleForm() } return render(request, 'sample/index.html', context) def post(self, request, *args, **kwargs): form = SampleForm(request.POST) if form.is_valid(): form.save() return redirect(reverse('sample:index')) context = { 'form': form } return render(request, 'sample/index.html', context) sampleFromView = SampleFormView.as_view()HTMLテンプレート
templates/sample/index.py
単純に入力フォームと「送信」サブミットボタンを表示するだけです。
今回はCSRFトークンのエラーをテストするため、意図的に{{ csrf_token }}
を外しています。<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>Form Sample</title> </head> <body> <form method="POST" action="{% url 'sample:index' %}"> {% for field in form %} <label>{{ field.label_tag }}</label> {{ field }} {% endfor %} <input type="submit" value="送信" /> </form> </body> </html>アプリ内のURL設定
sample/urls.py
作成したビュークラスにアクセスするURLを定義します。
from django.urls import path from . import views app_name='sample' urlpatterns = [ path('', views.sampleFromView, name="index") ]config(プロジェクト設定)
プロジェクト設定
config/settings.py
以下の設定を追加します。
- 作成した
sample
アプリケーションをインストールする- HTMLテンプレートをプロジェクト直下の
templates
ディレクトリから読み込む- 言語設定を「日本語」に設定する
- タイムゾーン設定を「東京」に設定する
(省略) # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'sample', ] …… TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [ os.path.join(BASE_DIR, 'templates') ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], }, }, ] …… # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/ LANGUAGE_CODE = 'ja-JP' TIME_ZONE = 'Asia/Tokyo' (省略)プロジェクトのURL設定
config/urls.py
sample
プロジェクトで作成したURL設定を読み込み、トップページでアクセスできるように設定しています。from django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('', include('sample.urls')), ]テスト
sample
django.test.testcases.Testcase
を用いたテストを実施します。
django.test.testcases.Testcase
に用意されたdjango.test.Client
クラスのオブジェクトself.client
では、CSRFトークンのチェックが行われないため、clientをCSRFトークンのチェックを行うように再設定し、サンプルアプリケーションにGET、POSTを行い、GET時に指定したテンプレートの表示、POST時にHTTPステータスコードが403
になることを確認します。参考ページ:
リクエストの作成:テストツール | Django ドキュメント | Django
SimpleTestCase:django.test.testcases | Django ドキュメント | Django
tests/sample/test_view.py
from django.test.testcases import TestCase from django.urls import reverse from django.test.client import Client class SampleViewTest(TestCase): def _pre_setup(self): super()._pre_setup() self.client = Client(enforce_csrf_checks=True) def test_get_index_01(self): response = self.client.get(reverse('sample:index')) self.assertTemplateUsed(response, 'sample/index.html') def test_post_index_01(self): response = self.client.post(reverse('sample:index'), data={}) # If csrf_token was template given. # self.assertTemplateUsed(response, 'sample/index.html') # If csrf_token was't template given. self.assertEquals(403, response.status_code) def test_post_index_02(self): response = self.client.post(reverse('sample:index'), data={'message': 'Test Message'}) # If csrf_token was template given. # self.assertRedirects(response, reverse('sample:index')) # If csrf_token was't template given. self.assertEquals(403, response.status_code)e2e
django.test.testcases.LiveServerTestCase
とSelenium WebDriverを用いたテストを実施します。
tests/e2e/test_index.py
from django.test.testcases import LiveServerTestCase import chromedriver_binary from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.keys import Keys from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LiveServerIndexTest(LiveServerTestCase): @classmethod def setUpClass(cls): super().setUpClass() options = Options() options.add_argument('--headless') cls.selenium = webdriver.Chrome(options=options) cls.selenium.implicitly_wait(10) def test_index_01(self): self.selenium.get('%s%s' % (self.live_server_url, '/')) self.assertTemplateUsed('sample/index.html') self.assertEquals('Form Sample', self.selenium.title) def test_index_02(self): self.selenium.get('%s%s' % (self.live_server_url, '/')) message_elem = self.selenium.find_element_by_css_selector('form textarea[name="message"]') message_elem.send_keys("Test Message") submit_elem = self.selenium.find_element_by_css_selector('form input[type="submit"]') submit_elem.click() WebDriverWait(self.selenium, 15).until(EC.visibility_of_all_elements_located) # assert Submit Success # self.assertEquals('Form Sample', self.selenium.title) # assert Submit 403 Error(CSRF Token Error) self.assertTrue('403' in self.selenium.title) @classmethod def tearDownClass(cls): cls.selenium.quit() super().tearDownClass()テストの実行結果
得られた知見
django.test.testcases.TestCase
を用いたユニットテストで、CSRFトークンエラーを検知することは可能
- ただし、
_pre_setup()
メソッドをオーバーライドし、self.client
をCSRFトークンチェック有効(enforce_csrf_checks=True
)のクライアントに上書きする必要がある
self.client
自体はdjango.test.testcases.TestCase
クラスの親クラスであるdjango.test.testcases.SimpleTestCase
で定義されているdjango.test.Client
のenforce_csrf_checks
引数はデフォルトでFalse
であるため、django.test.testcases.SimpleTestCase
の_pre_setup()
メソッドで生成されるself.client
オブジェクトは常にCSRFトークンチェック無効になっている
- このため、テストクラス内で
_pre_setup()
メソッドをオーバーライドし、self.client
をenforce_csrf_checks=True
であるクライアントオブジェクトで上書きする必要が生じる- LiveServerTestCaseとSelenium WebDriverを用いたユニットテストで、CSRFトークンエラーを検知することは可能
- Selenium WebDriverではHTTPステータスコードを取得することはできないため、画面のタイトル等を用いて403エラーを検知する必要がある
- 投稿日:2019-08-20T17:07:50+09:00
python class template 解説(オブジェクト指向を解説)
メリットから覚えるclass
pythonを使ってAddress Book classを作ってみた
。
対象となるオブジェクトは、一般的な住所録です。例
ID 名前 性別 血液型 生年月日 携帯番号 メール 郵便番号 住所 1 関 波子 女 AB 1980/7/31 090-7787-3784 sk@eaccess.net 135-0034 東京都江東区永代8-1-4 2 小倉 準司 男 A 1973/10/8 junzi-kokura@eaccess.net 071-1544 北海道上川郡東神楽町14号6-13-1 3 西村 有紀子 女 O 1972/12/5 090-5165-2074 okikuy1972@livedoor.com 635-0805 奈良県北葛城郡広陵町萱野5-2-7 ヴェルテックス萱野 1009 4 谷 僧三郎 男 B 1989/1/17 090-3781-1181 suzbrutn@dsn.ad.jp 520-0011 滋賀県大津市南志賀2868 5 山口 和久 男 A 1973/2/21 070-4790-1232 kazuhisa73@geocities.com 012-0813 秋田県湯沢市前森1-11-7 6 米田 一生 男 O 1984/7/25 issei07@dsn.ad.jp 264-0029 千葉県千葉市若葉区桜木北4-13-4 7 村松 希美江 女 O 1980/11/21 090-4849-6939 kimie.muramatu@odn.ne.jp 616-8151 京都府京都市右京区太秦帷子ヶ辻町6-6-7 8 小野寺 眞八 男 O 1986/4/28 090-1457-0772 ondr.sny@gmo-media.jp 527-0135 滋賀県東近江市横溝町6-6-7 9 志村 陽一郎 男 O 1975/7/8 smr-yutru@dion.ne.jp 048-1321 北海道磯谷郡蘭越町湯里5-9-5 ル・メール湯里 10 鎌田 敏美 女 B 1993/6/9 090-5690-8749 kamata0609@example.com 409-0115 山梨県上野原市松留4631 クラスを定義してみましょう。
address.pyclass AddressBook: def __init__(self,str): #タブ区切りデータ x=str.split('\t') self.ID =x[0] self.名前 =x[1] self.性別 =x[2] self.血液型 =x[3] self.生年月日 =x[4] self.携帯番号 =x[5] self.メール =x[6] self.郵便番号 =x[7] self.住所 =x[8] #文字列データからのデータのセット def sets(self,ID,名前,性別,血液型,生年月日,携帯番号,メール,郵便番号,住所): self.ID = ID self.名前 = 名前 self.性別 = 性別 self.血液型 = 血液型 self.生年月日 = 生年月日 self.携帯番号 = 携帯番号 self.メール = メール self.郵便番号 = 郵便番号 self.住所 = 住所 def getDict(self): #dict形式を返す return self.__dict__ def gets(self): # 内部データの配列化 return [self.ID,self.名前,self.性別,self.血液型,self.生年月日,self.携帯番号,self.メール,self.郵便番号,self.住所] def out(self): print(",".join(self.gets()))test.pyd='''1 関 波子 女 AB 1980/7/31 090-7787-3784 sk@eaccess.net 135-0034 東京都江東区永代8-1-4 2 小倉 準司 男 A 1973/10/8 junzi-kokura@eaccess.net 071-1544 北海道上川郡東神楽町14号6-13-1 3 西村 有紀子 女 O 1972/12/5 090-5165-2074 okikuy1972@livedoor.com 635-0805 奈良県北葛城郡広陵町萱野5-2-7 ヴェルテックス萱野 1009 4 谷 僧三郎 男 B 1989/1/17 090-3781-1181 suzbrutn@dsn.ad.jp 520-0011 滋賀県大津市南志賀2868 5 山口 和久 男 A 1973/2/21 070-4790-1232 kazuhisa73@geocities.com 012-0813 秋田県湯沢市前森1-11-7 6 米田 一生 男 O 1984/7/25 issei07@dsn.ad.jp 264-0029 千葉県千葉市若葉区桜木北4-13-4 7 村松 希美江 女 O 1980/11/21 090-4849-6939 kimie.muramatu@odn.ne.jp 616-8151 京都府京都市右京区太秦帷子ヶ辻町6-6-7 8 小野寺 眞八 男 O 1986/4/28 090-1457-0772 ondr.sny@gmo-media.jp 527-0135 滋賀県東近江市横溝町6-6-7 9 志村 陽一郎 男 O 1975/7/8 smr-yutru@dion.ne.jp 048-1321 北海道磯谷郡蘭越町湯里5-9-5 ル・メール湯里 10 鎌田 敏美 女 B 1993/6/9 090-5690-8749 kamata0609@example.com 409-0115 山梨県上野原市松留4631''' abook=[AddressBook(x) for x in d.split('\n')] for x in abook: x.out()result.txt1,関 波子,女,AB,1980/7/31,090-7787-3784,sk@eaccess.net,135-0034,東京都江東区永代8-1-4 2,小倉 準司,男,A,1973/10/8,,junzi-kokura@eaccess.net,071-1544,北海道上川郡東神楽町14号6-13-1 3,西村 有紀子,女,O,1972/12/5,090-5165-2074,okikuy1972@livedoor.com,635-0805,奈良県北葛城郡広陵町萱野5-2-7 ヴェルテックス萱野 1009 4,谷 僧三郎,男,B,1989/1/17,090-3781-1181,suzbrutn@dsn.ad.jp,520-0011,滋賀県大津市南志賀2868 5,山口 和久,男,A,1973/2/21,070-4790-1232,kazuhisa73@geocities.com,012-0813,秋田県湯沢市前森1-11-7 6,米田 一生,男,O,1984/7/25,,issei07@dsn.ad.jp,264-0029,千葉県千葉市若葉区桜木北4-13-4 7,村松 希美江,女,O,1980/11/21,090-4849-6939,kimie.muramatu@odn.ne.jp,616-8151,京都府京都市右京区太秦帷子ヶ辻町6-6-7 8,小野寺 眞八,男,O,1986/4/28,090-1457-0772,ondr.sny@gmo-media.jp,527-0135,滋賀県東近江市横溝町6-6-7 9,志村 陽一郎,男,O,1975/7/8,,smr-yutru@dion.ne.jp,048-1321,北海道磯谷郡蘭越町湯里5-9-5 ル・メール湯里 10,鎌田 敏美,女,B,1993/6/9,090-5690-8749,kamata0609@example.com,409-0115,山梨県上野原市松留4631 Press any key to continue . . .女性のみを表示するには、
for x in abook: if x.性別=='女': x.out() 1,関 波子,女,AB,1980/7/31,090-7787-3784,sk@eaccess.net,135-0034,東京都江東区永代8-1-4 3,西村 有紀子,女,O,1972/12/5,090-5165-2074,okikuy1972@livedoor.com,635-0805,奈良県北葛城郡広陵町萱野5-2-7 ヴェルテックス萱野 1009 7,村松 希美江,女,O,1980/11/21,090-4849-6939,kimie.muramatu@odn.ne.jp,616-8151,京都府京都市右京区太秦帷子ヶ辻町6-6-7 10,鎌田 敏美,女,B,1993/6/9,090-5690-8749,kamata0609@example.com,409-0115,山梨県上野原市松留4631 Press any key to continue . . .このようにすっきり書くことができる。
結局やりたかったことは、クラスを
定義するとJson形式にすぐに変換できるORM(Object-relational mapping)が簡単になることなんだ。for x in abook: print(x.getDict()) {'ID': '1', '名前': '関 波子', '性別': '女', '血液型': 'AB', '生年月日': '1980/7/31', '携帯番号': '090-7787-3784', 'メール': 'sk@eaccess.net', '郵便番号': '135-0034', '住所': '東京都江東区永代8-1-4'} {'ID': '2', '名前': '小倉 準司', '性別': '男', '血液型': 'A', '生年月日': '1973/10/8', '携帯番号': '', 'メール': 'junzi-kokura@eaccess.net', '郵便番号': '071-1544', '住所': '北海道上川郡東神楽町14号6-13-1'} {'ID': '3', '名前': '西村 有紀子', '性別': '女', '血液型': 'O', '生年月日': '1972/12/5', '携帯番号': '090-5165-2074', ' メール': 'okikuy1972@livedoor.com', '郵便番号': '635-0805', '住所': '奈良県北葛城郡広陵町萱野5-2-7 ヴェルテックス萱野 1009'} {'ID': '4', '名前': '谷 僧三郎', '性別': '男', '血液型': 'B', '生年月日': '1989/1/17', '携帯番号': '090-3781-1181', 'メ ール': 'suzbrutn@dsn.ad.jp', '郵便番号': '520-0011', '住所': '滋賀県大津市南志賀2868'} {'ID': '5', '名前': '山口 和久', '性別': '男', '血液型': 'A', '生年月日': '1973/2/21', '携帯番号': '070-4790-1232', 'メ ール': 'kazuhisa73@geocities.com', '郵便番号': '012-0813', '住所': '秋田県湯沢市前森1-11-7'} {'ID': '6', '名前': '米田 一生', '性別': '男', '血液型': 'O', '生年月日': '1984/7/25', '携帯番号': '', 'メール': 'issei07@dsn.ad.jp', '郵便番号': '264-0029', '住所': '千葉県千葉市若葉区桜木北4-13-4'} {'ID': '7', '名前': '村松 希美江', '性別': '女', '血液型': 'O', '生年月日': '1980/11/21', '携帯番号': '090-4849-6939', 'メール': 'kimie.muramatu@odn.ne.jp', '郵便番号': '616-8151', '住所': '京都府京都市右京区太秦帷子ヶ辻町6-6-7'} {'ID': '8', '名前': '小野寺 眞八', '性別': '男', '血液型': 'O', '生年月日': '1986/4/28', '携帯番号': '090-1457-0772', ' メール': 'ondr.sny@gmo-media.jp', '郵便番号': '527-0135', '住所': '滋賀県東近江市横溝町6-6-7'} {'ID': '9', '名前': '志村 陽一郎', '性別': '男', '血液型': 'O', '生年月日': '1975/7/8', '携帯番号': '', 'メール': 'smr-yutru@dion.ne.jp', '郵便番号': '048-1321', '住所': '北海道磯谷郡蘭越町湯里5-9-5 ル・メール湯里'} {'ID': '10', '名前': '鎌田 敏美', '性別': '女', '血液型': 'B', '生年月日': '1993/6/9', '携帯番号': '090-5690-8749', 'メ ール': 'kamata0609@example.com', '郵便番号': '409-0115', '住所': '山梨県上野原市松留4631'} Press any key to continue . . .このような例題書いてくれないとオブジェクト指向のメリット感じないよね!
「いいよね」よろしく
- 投稿日:2019-08-20T16:35:05+09:00
対数計算
python>>> import math底が2の2の対数 $\log_2 {2}$
python>>> math.log(2,2) 1.0底が2の3の対数 $\log_2 {3}$
python>>> math.log(3,2) 1.5849625007211563底が2の4の対数 $\log_2 {4}$
python>>> math.log(4,2) 2.0底が2の8の対数 $\log_2 {8}$
python>>> math.log(8,2) 3.0
- 投稿日:2019-08-20T16:21:04+09:00
Google Colaboratory上でアニメーションを表示する
TL;DR
Google Colaboratory上で何かイメージを描画して、それをアニメーションにするなら、ファイルを連番で吐いてapngにしてしまうのが楽ちん。
はじめに
Google Colab上で何か計算して、その計算結果をアニメーションとして見せたい。普通にやるならmatplotlibで可視化するのだろうが、これをColab上でアニメーションにするのはわりと面倒くさい。そもそもColab上で画像を表示するのも、一度ファイルに吐いてから表示するのが楽だったりする。
で、アニメーションについてもいろいろ試行錯誤したのだが、結局のところ一度連番ファイルで吐いてしまってから、アニメPNG(APNG)にしてしまうのが一番楽だった。
サンプルコード
APNGのインストール
Google Colabの最初のセルで
APNG
をインストールする。!pip install APNGおそらく問題なくインストールされて「Successfully installed APNG-0.3.3」とか表示されるはず。
連番ファイルの出力
なんでもよいので、連番のPNGファイルを吐くコードを書こう。以下は円周上を円が周回するだけのアニメーション。
from apng import APNG from math import cos, pi, sin from PIL import Image, ImageDraw import IPython def save(index, frames): filename = "file%02d.png" % index im = Image.new("RGB", (100, 100), (255, 255, 255)) draw = ImageDraw.Draw(im) x = 30*cos(2*pi*index/frames) + 50 y = 30*sin(2*pi*index/frames) + 50 draw.ellipse((20,20,80,80),outline=(0,0,0)) draw.ellipse((x-5, y-5, x+5, y+5), fill=(0, 0, 255)) im.save(filename) return filename後で
APNG
にわたすので、ファイル名を返している。APNGに渡してアニメーションを作成
APNG
は、ファイルリストを食わすとAPNGにしてくれるメソッドfrom_files
があるので、それにファイルリストを食わせて、save
で適当なファイル名で保存する。保存したファイルは
IPython.display.Image
でそのまま表示できる。files = [] frames = 50 for i in range(frames): files.append(save(i, frames)) APNG.from_files(files, delay=100).save("animation.png") IPython.display.Image("animation.png")実行結果はこんな感じ。
セルを実行したら、無事にGoogle Colab上でアニメーションが表示された。
まとめ
Google Colab上でアニメーションを表示させる方法を紹介した。数値計算のシミュレーションや、何か動くものの可視化なんかをする場合、Matplotlibで頑張るより、PILで連番png吐いてAPNG作っちゃう方が楽だと思う。
- 投稿日:2019-08-20T16:14:35+09:00
Pythonで始める機械学習 教師あり学習のまとめ
教師あり学習 各モデルの特徴
最近傍方
- 小さいデータに関しては良いベースラインとなる
- 説明が容易
線形モデル
- 最初に試すべきアルゴリズム
- 大きなデータ,高次元なデータに適している
ナイーブベイズ
- クラス分類にしか使えない
- 線形モデルよりもさらに高速だが精度が劣ることが多い
- 大きなデータ,高次元なデータに適している
決定木
- 非常に高速
- データのスケールを考慮する必要がない
- 可視化が可能で説明が容易
ランダムフォレスト
- 決定木より高速で頑健で強力
- データのスケールを考慮する必要がない
- 高次元の疎なデータには適さない
勾配ブースティング決定木
- ランダムフォレストより少し精度が高い
- ランダムフォレストより訓練に時間がかかるが予測は早く,メモリ使用量も小さい
- ランダムフォレストよりパラメータに敏感
サポートベクタマシン
- 同じような意味を持つ特徴量からなる中規模のデータセットに対して強力
- データのスケール調整が必要
- パラメータに敏感
ニューラルネットワーク
- 非常に複雑なモデルを構築できる
- 大きなデータセットに有効
- データのスケールを調整する必要がある
- パラメータに敏感
- 大きなモデルでは訓練に時間がかかる
教師あり学習 各モデルの使い方
- 新しいデータセットを扱う際には,まず線形モデルやナイーブベイズや最近傍方などの簡単なモデルでどの程度の精度かを確認する
- データを深く理解したのちに,ランダムフォレストや勾配ブースティング,SVM,ニューラルネットワークなどの複雑なモデルを使う
- モデルの使いわけに加えパラメータ調整や解析を行う
- これを確認するため,回帰にはboston_housingやdiabetesデータセットが,多クラス分類にはdigitsデータセットなど,様々なデータセットに様々なアルゴリズムを適用してみるのが良い
- 投稿日:2019-08-20T15:34:08+09:00
Pythonで始める機械学習 クラス分類器の不確実性推定
クラス分類器の不確実性推定
- scikit-learnの有用な機能としてクラス分類器の予測に対する不確実性推定機能(あるテストポイントに対してクラス分類器が出力する予測クラスだけでなく,その予測がどの程度正確なのかを知る)がある
- クラス分類器の不確実性推定に用いられる関数としてdecision_functionとpredict_probaの2つがある
- これら2つの関数の動きを次の例で見ていく
決定関数(Decision Function)
In[103]: from sklearn.ensemble import GradientBoostingClassifier from sklearn.datasets import make_circles X, y = make_circles(noise=.25, factor=.5, random_state=1) y_named = np.array(["blue", "red"])[y] # 文字列の中で \ を使うことで改行できる X_train, X_test, y_train_named, y_test_named, y_train, y_test = \ train_test_split(X, y_named, y, random_state=0) gbrt = GradientBoostingClassifier(random_state=0) gbrt.fit(X_train, y_train_named) In[104]: print("X_test.shape: {}".format(X_test.shape)) print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape)) Out[104]: X_test.shape: (25, 2) Decision function shape: (25,)
- この値にはデータポイントが陽性(クラス1)であるとモデルが信じている度合いがエンコードされており,正であれば陽性,負であれば陰性を意味する
In[105]: print("Decision fuction:\n{}".format(gbrt.decision_function(X_test)[:6])) Out[105]: # 符号だけを見れば予測クラスがわかる redが正,blueが負 Decision fuction: [ 4.13592629 -1.7016989 -3.95106099 -3.62599351 4.28986668 3.66166106] In[106]: print("Thresholded decision function:\n{}".format(gbrt.decision_function(X_test) > 0)) print("Prediction:\n{}".format(gbrt.predict(X_test))) Out[106]: Thresholded decision function: # 決定関数の値に閾値を適用して真偽に分類したもの [ True False False False True True False True True True False True True False True False False False True True True True True False False] Prediction: # 予測結果 ['red' 'blue' 'blue' 'blue' 'red' 'red' 'blue' 'red' 'red' 'red' 'blue' 'red' 'red' 'blue' 'red' 'blue' 'blue' 'blue' 'red' 'red' 'red' 'red' 'red' 'blue' 'blue']
- 2クラス分類では陰性クラスがclasses_属性の第1エントリに,陽性クラスがclasses_属性の第2エントリになる
- predictと同じ結果を再現する場合はclasses_属性を使う
In[107]: # 「Thresholded decision function」のTrue/Falseを0/1に変換する greater_zero = (gbrt.decision_function(X_test) > 0).astype(int) # 0/1をclasses_のインデックスに使う pred = gbrt.classes_[greater_zero] # predはgbrt.predictの出力と同じになる print("pred is equal to predictions: {}".format(np.all(pred == gbrt.predict(X_test)))) Out[107]: # predとpredictionが一致 pred is equal to predictions: True In[108]: decision_function = gbrt.decision_function(X_test) print("Decision function minimum: {:.2f} maximum: {:.2f}".format(np.min(decision_function), np.max(decision_function))) Out[108]: # 決定関数の最小値と最大値 Decision function minimum: -7.69 maximum: 4.29
- このようにdecision_functionの結果はどのようなスケールで表示されるかわからないため解釈が難しい
- この結果を次に図で示す
In[109]: fig, axes = plt.subplots(1, 2, figsize=(13, 5)) mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4, fill=True, cm=mglearn.cm2) scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1], alpha=.4, cm=mglearn.ReBl) for ax in axes: # 訓練データポイントとテストデータポイントをプロット mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test, markers='^', ax=ax) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, markers='o', ax=ax) ax.set_xlabel("Feature 0") ax.set_ylabel("Feature 1") cbar = plt.colorbar(scores_image, ax=axes.tolist()) axes[0].legend(["Test class 0", "Test class 1", "Train class 0", "Train class 1"], ncol=4, loc=(.1, 1.1))
- 訓練データポイントは円で,テストデータポイントは三角で表している
- 勾配ブースティングモデルの決定境界(左)と確信度(右)
- ここでは確信度も表しているが,このように可視化してもわかりにくい
- もう一つ,クラス分類器の不確実性推定に用いられる関数としてpredict_probaがあり,こちらの方がdecision_functionよりも理解しやすい
- predict_probaの出力配列の形は2クラス分類では常に(n_samples, 2)になる
- 次に例を示す
In[110]: print("Shape of probabilities: {}".format(gbrt.predict_proba(X_test).shape)) Out[110]: Shape of probabilities: (25, 2) In[111]: # predict_probaの出力の最初の数行を見る print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test[:6]))) Out[111]: # 予測確率 Predicted probabilities: [[0.01573626 0.98426374] [0.84575649 0.15424351] [0.98112869 0.01887131] [0.97406775 0.02593225] [0.01352142 0.98647858] [0.02504637 0.97495363]]
- 第1エントリは第1クラスの予測確率,第2エントリは第2クラスの予測確率である
- 第1エントリと第2エントリ数値は確率であるため,双方の和は常に1となる
- 50%以上の確信度をもつクラスが予測クラスとなる
- これを可視化したもの(データセットの境界線とクラス1になる確率)を示す
In[112]: fig, axes = plt.subplots(1, 2, figsize=(13, 5)) mglearn.tools.plot_2d_separator(gbrt, X, ax=axes[0], alpha=.4, fill=True, cm=mglearn.cm2) scores_image = mglearn.tools.plot_2d_scores(gbrt, X, ax=axes[1], alpha=.5, cm=mglearn.ReBl, function='predict_proba') for ax in axes: mglearn.discrete_scatter(X_test[:, 0], X_test[:, 1], y_test, markers='^', ax=ax) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, markers='o', ax=ax) ax.set_xlabel("Feature 0") ax.set_ylabel("Feature 1") cbar = plt.colorbar(scores_image, ax=axes.tolist()) axes[0].legend(["Test class 0", "Test class 1", "Train class 0", "Train class 1"], ncol=4, loc=(.1, 1.1))
- 決定境界(左)と確信度(右)を表しており,decision_functionよりも境界がはっきりしていてわかりやすい
- そのため,わずかに存在する確信度が低い領域もはっきりとわかる
- このようにして2クラス分類の不確実性推定の結果を確認できる
- 次に多クラス分類の不確実性を見てみる
In[114]: from sklearn.datasets import load_iris iris = load_iris() X_train, X_test, y_train, y_test = train_test_split(iris.data, iris.target, random_state=42) gbrt = GradientBoostingClassifier(learning_rate=0.01, random_state=0) gbrt.fit(X_train, y_train) print("Decision function shape: {}".format(gbrt.decision_function(X_test).shape)) print("Decision function:\n{}".format(gbrt.decision_function(X_test)[:6, :])) Out[114]: Decision function shape: (38, 3) # (n_samples, n_classes)の形,つまりサンプル数とクラス数である Decision function: [[-0.52931069 1.46560359 -0.50448467] [ 1.51154215 -0.49561142 -0.50310736] [-0.52379401 -0.4676268 1.51953786] [-0.52931069 1.46560359 -0.50448467] [-0.53107259 1.28190451 0.21510024] [ 1.51154215 -0.49561142 -0.50310736]]
- 2クラス分類の場合と同様に,多クラス分類の場合でも値は各クラスに対する確信度を表している(確信度が高いクラスが予測クラスとされる)
In[115]: print("Argmax of decision function:\n{}".format(np.argmax(gbrt.decision_function(X_test), axis=1))) print("Predictions:\n{}".format(gbrt.predict(X_test))) Out[115]: Argmax of decision function: # 決定関数のargmax [1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0] Predictions: # 予測値 [1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0] In[116]: # predict_probaの結果に最初の数行を表示 print("Predicted probabilities:\n{}".format(gbrt.predict_proba(X_test)[:6])) # sumでリスト内の値の合計が1になることを確認する print("Sums: {}".format(gbrt.predict_proba(X_test)[:6].sum(axis=1))) Out[116]: Predicted probabilities: [[0.10664722 0.7840248 0.10932798] [0.78880668 0.10599243 0.10520089] [0.10231173 0.10822274 0.78946553] [0.10664722 0.7840248 0.10932798] [0.10825347 0.66344934 0.22829719] [0.78880668 0.10599243 0.10520089]] Sums: [1. 1. 1. 1. 1. 1.] In[117]: # predict_probaのargmaxを取ることで予測クラスを再現する print("Argmax of predicted probabilities:\n{}".format(np.argmax(gbrt.predict_proba(X_test), axis=1))) print("Predictions:\n{}".format(gbrt.predict(X_test))) Out[117]: Argmax of predicted probabilities: [1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0] Predictions: [1 0 2 1 1 0 1 2 1 1 2 0 0 0 0 1 2 1 1 2 0 2 0 2 2 2 2 2 0 0 0 0 1 0 0 2 1 0]
- predict_probaとdecision_functionの結果は(n_samples, n_classes)の形になるが,2クラス分類のdecision_funcsionの場合のみ陽性クラスであるclasses_[1]に対応する1列しかない
- クラスの数だけ列がある場合は列に対してargmaxを計算すれば予測を再現できるが,クラスが文字列だったり,0から始まる整数で表現されていない場合には注意が必要
- predictの結果をdecision_functionやpredict_probaの結果と比較する際にはclasses_属性を使って実際のクラス名を使うようにする
- この例を次に示す
In[118]: logreg = LogisticRegression() named_target = iris.target_names[y_train] logreg.fit(X_train, named_target) print("unique classes in training data: {}".format(logreg.classes_)) print("predictions: {}".format(logreg.predict(X_test)[:10])) argmax_dec_func = np.argmax(logreg.decision_function(X_test), axis=1) print("argmax of decision function: {}".format(argmax_dec_func[:10])) print("argmax combined with classes_: {}".format(logreg.classes_[argmax_dec_func][:10])) Out[118]: unique classes in training data: ['setosa' 'versicolor' 'virginica'] # 訓練データ中のクラス predictions: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor'] # 予測値 argmax of decision function: [1 0 2 1 1 0 1 2 1 1] # 決定関数のargmax argmax combined with classes_: ['versicolor' 'setosa' 'virginica' 'versicolor' 'versicolor' 'setosa' 'versicolor' 'virginica' 'versicolor' 'versicolor'] # 決定関数のargmaxをクラス名にしたもの
- 投稿日:2019-08-20T15:06:27+09:00
Python備忘録
高速化
- セット(set > dicts >> list)
- listは.appendであるため遅く、setはhashmapであるため早い https://qiita.com/cof/items/05f6ffc6d4e5b062aaa9
range(0,10**4*1,1) range(0,10**4*2,2) range(0,10**4*3,3) list 10.247 12.593 14.6 dict 0.253 0.028 0.028 set 0.239 0.024 0.022
- 標準入力(input < sys.stdin.readline)
- 標準入力はinput()よりsys.stdin.readline()の方が10倍速い
平均(ms) 標準偏差(ms) input() 392.40 24.36 sys.stdin.readline() 37.09 1.88
- 並び替え(sort > sorted)
- ソートは、sortedよりsortの方が1.4倍速く、keyの指定にはitemgetterを使うとlambdaに比べ1.2倍速くなる
平均(ms) 標準偏差(ms) sort() 88.54 56.98 sorted() 127.03 7.51
- ループ(for > while)
- ループは、forの方がwhileより2倍程度速い
平均(ms) 標準偏差(ms) for _ in range(N) 20.63 0.89 for i in range(N) 25.66 0.93 while i < N 51.36 1.44
- リスト初期化(* > in range)
- リストの初期化は*演算子の方が、内包表記より8~9倍速い
平均(ms) 標準偏差(ms) [None] * N 5.15 0.41 [None for _ in range(N)] 41.17 2.05
- 二次元配列の初期化(* > in range)
- 二次元配列の初期化は、最初の次元のみ内包表記を使う
平均(ms) 標準偏差(ms) [[None] * N for _ in range(N)] 3.07 1.02 [[None for _ in range(N)] for _ in range(N)] 38.45 4.80
- リストの値参照
- リストの値の参照は、for a in Aのようにインデックスを作らないほうが、rangeを使う場合より4倍速い
平均(ms) 標準偏差(ms) for i in range(len(A)) 41.14 0.56 for a in A 11.85 1.51
- リストへの値追加
- 要素の追加は内包表記を使うと、appendに比べ1.5倍速くなる
平均(ms) 標準偏差(ms) append() 103.99 2.62 A[i] = i 70.97 3.93 [i for i in range(N)] 65.83 3.20
- 投稿日:2019-08-20T15:01:01+09:00
Joblibで共有メモリを設定する時につまづいたこと
Pythonで並列処理をしたい時、選択肢としてmultiprocessingかJoblibの二択がまず出てきますが、サクッとやりたい時はJoblibを使うことになると思います。
しかしプロセス毎に参照可能なメモリ領域が異なるため、並列実行している関数から外部スコープの変数に代入するためには共有メモリを設定しなければいけません。そこで以下の記事を参考にValueクラスを使って共有メモリを設定しようとしましたが、
なぜか動きません。試しに記事内に書かれているコードをそのままコピペしても、
# -*- coding: utf-8 -*- from joblib import Parallel, delayed from multiprocessing import Value, Array shared_int = Value('i', 1) def process(n): shared_int.value = 3.14 return sum([i*n for i in range(100000)]) # 繰り返し計算 (並列化) Parallel(n_jobs=-1)( [delayed(process)(i) for i in range(10000)] ) print(shared_int.value)RuntimeError: Synchronized objects should only be shared between processes through inheritance同様のエラーが起きちゃいます。さあどうしよう。どこも同じやり方しか書いてなくて困った。じゃあ公式リファレンスを見てみよう。
Embarrassingly parallel for loops
However if the parallel function really needs to rely on the shared memory semantics of threads, it should be made explicit with require='sharedmem', for instance:
>>> shared_set = set() >>> def collect(x): ... shared_set.add(x) ... >>> Parallel(n_jobs=2, require='sharedmem')( ... delayed(collect)(i) for i in range(5)) [None, None, None, None, None] >>> sorted(shared_set) [0, 1, 2, 3, 4]えっ、Parallelの引数に
require='sharedmem'
設定するだけでいいんですか?試しにさっきのコードを書き換えてみると、# -*- coding: utf-8 -*- from joblib import Parallel, delayed shared_int = 1 def process(n): global shared_int shared_int = 3.14 return sum([i*n for i in range(10000)]) # 繰り返し計算 (並列化) Parallel(n_jobs=-1, require='sharedmem')([delayed(process)(i) for i in range(10000)]) print(shared_int)3.14
できました。ちゃんと並列化した関数の中から外部スコープの変数に代入できています。
てことで、どうやらParallelの引数に
require='sharedmem'
を入れるだけで共有メモリを設定できちゃうようです。簡単!でも
Keep in mind that relying a on the shared-memory semantics is probably suboptimal from a performance point of view as concurrent access to a shared Python object will suffer from lock contention.
ーー和訳ーー
共有Pythonオブジェクトへの同時アクセスではロック競合が発生するため、パフォーマンスの観点からは、共有メモリーのセマンティクスに依存するのは最適とは言えないことに注意してください。とも書いてあるので、気をつけて使ったほうが良さそうです。
参照
- 投稿日:2019-08-20T14:34:23+09:00
Pythonでの学習 その1
基礎を学ぶ
こんにちは!初心者です!!
学んだことをつらつら書いていきます!!Aidemyでの学びとは
これはこんな意味ですよ〜
こう書きますよ〜
じゃあ!書いてみよう!って感じでした。(これが結構大変)
写し書きと言われればそこまでですが、勉強の基本は真似をしてやってみることですよね!では内容に入っていきたいと思います。
やっぱり最初は Hello World!!
main.pyprint("Hello World") >> Hello Worldはい。こんな感じです。
ここで疑問が発生します。main.pyprint('Hello World') >> Hello Worldこれでも出力は一緒なんですよ。
なんでなんですかねぇ。わからないまま次へ・・・計算ができる!!
main.pyprint(3 + 8) >> 11変数に入れちゃったり・・・
main.pyx = 3 y = 8 print(x + y) >> 11こんな書き方もできちゃいます!
main.pyx = 3 x += 8 print(x) >> 11使える演算子はこれ!
足し算:「+」
引き算:「-」
掛け算:「」
割り算:「/」
余りの計算:「%」
べき乗:「*」計算はなんでもできそう!!
文字の結合もできます!!
main.pyn = "ねこ" m = "いぬ" print(n + m) >> ねこいぬこんな使い方をしたり・・・ね?
main.pyn = "ねこ" m = "いぬ" print("私は" + n + "が好きで、" + m + "が嫌いです。") >> 私はねこが好きで、いぬが嫌いです。この辺はVBAと似てるな〜!とか思います。
でもここで落とし穴があってですね・・・。main.pyn = "ねこ" m = 2 print("私は" + n + "が好きで、" + m + "匹飼っています。") >> Errorこれはエラーになるんですよね。(VBAだとすんなり行けた・・・はず・・・。)
これは型という概念があって、これがエラーの原因です。
str型:文字列
int型:整数
float型:浮動小数点つまり上のエラーは
文字列の中に数値を入れるな!!
ってエラーみたいです。型を合わせてあげるとうまくいきます。
main.pyn = "ねこ" m = 2 print("私は" + n + "が好きで、" + str(m) + "匹飼っています。") >> 私はねこが好きで、2匹飼っています。型がどうしてもわからない!!!って時にはtype関数があるので使ってみてくださいね!
main.pyn = "ねこ" m = 2 f = 0.12 print(type(n)) print(type(m)) print(type(f)) >> <class 'str'> <class 'int'> <class 'float'>長くなったので、今回はこの辺で!!
- 投稿日:2019-08-20T14:08:15+09:00
初心者向け Pythonの基本的な計算
- 投稿日:2019-08-20T13:30:13+09:00
Djangoのform機能を使ってBMI測定機を作成。我が体重への自戒を込めて。
どうもjackです。
8月からPythonをメインにエンジニア業務に従事することになりまして。
諸事情により12月までに7キロの減量を目標に生きてる駆け出しエンジニアです。
今回は表題の通りDjangoのform機能を使ってBMI測定を行います。ちなみに私の開発環境は以下となっています。
- Python 3.6.7
- Django 2.2.3
- macOS
では、早速。
django-admin startproject qiita
をターミナルにて実行してアプリを作成!
cd qiita
作成したディレクトリへ移動。
python3 manage.py startapp bmi
上記にてbmiアプリを作成。
qiitaディレクトリにあるsettings.pyのINSTALLED_APPSにbmiを追記。
settings.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'bmi',qiitaディレクトリにあるurls.pyの編集を行います。
urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path('bmi/', include('bmi.urls')), ]合わせてbmiディレクトリにurls.pyのファイルを作成しておきましょう。
urls.pyの編集は後ほど。qiitaディレクトリ├── qiita │ ├── __pycache__ │ ├── _init_.py │ ├── setting.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── bmi │ ├── migrations │ ├── _init_.py │ ├── admin.py │ ├── apps.py │ ├── models.py │ ├── tests.py │ ├── urls.py ←これ │ └── views.py └── manage.py今回のbmiの計算に必要な数値は身長と体重の2つ。
この2つの値をformで受け取る為にまずはbmiディレクトリにforms.pyを作成。qiitaディレクトリ├── qiita │ ├── __pycache__ │ ├── _init_.py │ ├── setting.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── bmi │ ├── migrations │ ├── _init_.py │ ├── admin.py │ ├── apps.py │ ├── forms.py ←これ │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── manage.pyんで持ってこのforms.pyを編集します。
forms.pyfrom django import forms class BmiForm(forms.Form): weight = forms.IntegerField(label='weight') height = forms.IntegerField(label = 'height')ではこの作成したformをviewにて関数を定義していきます。
views.pyfrom django.shortcuts import render from .forms import BmiForm # Create your views here. def bmi(request): params = { 'title' :'BMI', 'msg': 'enter your score', 'form': BmiForm() } if (request.method=='POST'): msg = int(request.POST['weight']) / (int(request.POST['height'])/100 )**2 params['msg'] = '{:.2f}'.format(msg) params['form'] = BmiForm(request.POST) return render(request, 'bmi/bmi.html', params)paramsの部分は後ほど作成するhtmlに値を渡すための部分になります。
入力フォームを埋め込むhtmlを格納するtemplatesディレクトリを作成します。
qiitaディレクトリ├── qiita │ ├── __pycache__ │ ├── _init_.py │ ├── setting.py │ ├── urls.py │ ├── views.py │ └── wsgi.py ├── bmi │ ├── migrations │ ├── templates │ │ └── bmi │ │ └── bmi.html ←ここ │ ├── _init_.py │ ├── admin.py │ ├── apps.py │ ├── forms.py │ ├── models.py │ ├── tests.py │ ├── urls.py │ └── views.py └── manage.pyそしてbmi.htmlを書いていきます。
bmi.html<!DOCTYPE html> <html lang="ja" dir="ltr"> <head> <meta charset="utf-8"> <title>{{title}}</title> </head> <body> <h1>{{title}}</h1> <p>{{msg}}</p> <table> <form action="{% url 'bmi' %}" method="post"> {% csrf_token %} {{form}} <tr> <td></td><td><input type="submit" name="" value="send"></td> </tr> </form> </table> </body> </html>最後にurls.pyを編集して終了です。
urls.pyfrom django.urls import path, include from . import views urlpatterns = [ path('', views.bmi, name='bmi'), ]ターミナルを起動させてローカルサーバーを立ち上げましょう。
python3 manage.py runserver
こういった表示が出れば完成です!!
実際に入力すると、、、
でたーーーーー!!!!
ちなみに標準は22らしいです。泣ではさよならさよならさよなら。
- 投稿日:2019-08-20T13:02:45+09:00
LeetCode / Linked List Cycle II
(ブログ記事からの転載)
[https://leetcode.com/problems/linked-list-cycle-ii]
Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
To represent a cycle in the given linked list, we use an integer pos which represents the position (0-indexed) in the linked list where tail connects to. If pos is -1, then there is no cycle in the linked list.
Note: Do not modify the linked list.
Follow-up:
Can you solve it without using extra space?こちらの問題に続き、連結リストがお題。
この問題では、与えられた連結リストに循環構造(cycle)が含まれている場合、cycleが始まる地点を返す必要があります。
そしてこの問題でも、空間計算量を定数時間に抑えて行う方法を考えます。解答・解説
解法1
空間計算量を定数時間に抑えるということで、ポインタを使うんだなということは想像がつきます。実際、今回も1ずつ進むslowと2ずつ進むfastのポインタを使います。しかし今回はcycleが始まる地点も返す必要があり、これをどうメモリを使わずに計算するかです。
これはなかなか自力で思いつきづらい気がしますが、下図のような状況を考えます。
slowとfastを同じ地点からスタートさせ(前の問題ではスタート地点を1ずらしていました)、次にslowとfastが出会うまでの間に、slowがH+D進み、fastはH+L+D進む状況を考えます。
すると、fastはslowの2倍の距離進んでいるので、2(H + D) = H + D + L の関係が成り立ちます。移項すれば、H = L - D の関係が成り立ちます。求めたいcycleが始まる地点はHに相当しますが、H = L - D ということは、下図のようにslowをfastと出会った地点からスタートさせ、一方でheadというポインタを始点からスタートさせたとき、ちょうどcycleの始点で出会うことを意味します。
つまり、まずslowとfastを同じ地点からスタートさせ、slowとfastが出会ったら、そこからslowだけスタートさせ、同時に始点からheadをスタートさせれば、slowとheadが出会った地点が求めたい答えである、ということになります。コードに落とすと、以下の通りとなります。
# Definition for singly-linked list. # class ListNode(object): # def __init__(self, x): # self.val = x # self.next = None class Solution(object): def detectCycle(self, head): """ :type head: ListNode :rtype: ListNode """ slow = fast = head # まず、slowとfastが再び出会うまで動かす while fast and fast.next: slow = slow.next fast = fast.next.next if slow is fast: break else: return None # slowとheadが出会うまで動かす while head is not slow: slow = slow.next head = head.next print(head.val, slow.val) return head
- 投稿日:2019-08-20T11:03:57+09:00
SwiftでもPythonみたいに文字列操作したい!
最近使い始めたSwiftでもPythonみたいに文字列操作したい!
SwiftでもPythonみたいに文字列のスライスしたいなぁ
ってことでStringを拡張してPythonの文字列操作メソッドを生やすライブラリを作ってみました。
SwiftyPyString
https://github.com/ChanTsune/SwiftyPyString
Swift5で動きます。
Unicode準拠 & Pythonのドキュメント準拠で作ってます。インストール
CocoaPods、Carthage、SwiftPMの主要なパッケージマネージャ3つに対応させています。
CocoaPods
pod 'SwiftyPyString'Carthage
github 'ChanTsune/SwiftyPyString'
SwiftPM
import PackageDescription let package = Package( name: "YourProject", dependencies: [ .Package(url: "https://github.com/ChanTsune/SwiftyPyString.git", from: "1.0.1") ] )文字列操作
添字アクセス
let str = "0123456789" str[0] // 0 str[-1] // 9Python準拠なので負の数を利用した後ろからのアクセスもサポートしています。
文字列のスライス
let str = "0123456789" str[0,5] // 01234 str[0,8,2] // 0246 str[nil,nil,-1] // 9876543210やりたかったスライスです。
一度これに慣れると他の言語でも使いたくなるやつです。
(だからこれを作ったわけですが笑)コロンだけの省略記法は
nil
で代用することにしました。ちなみに同じ動作をPythonで書くと以下のようになります。
str = "0123456789" str[0:5] # 01234 str[0:8:2] # 0246 str[::-1] # 9876543210文字列検索
// 先頭からの検索 "123412312312345".find("123") // 0 // 開始位置を指定して検索 "123412312312345".find("123",start:2) // 4 // 終了位置を指定して検索 "123412312312345".find("123",end:1) // -1 // 末尾からの検索 "123412312312345".rfind("123") // 10末尾からの検索も同様に開始位置と終了位置を指定して検索できます。
文字列結合
let array = ["abc","def","ghi"] "".join(array) // "abcdefghi" "-".join(array) // "abc-def-ghi" "++".join(array) // "abc++def++ghi"トリミング
// 右端のみ "rstrip sample ".rstrip() // "rstrip sample" "rstrip sample ".rstrip("sample ") // "rstri" " rstrip sample".rstrip() // " rstrip sample" // 左端のみ " lstrip sample".lstrip() // "lstrip sample" " lstrip sample".lstrip(" ls") // "trip sample" "lstrip sample".lstrip() // "lstrip sample" // 両端 " spacious ".strip() // "spacious" "www.example.com".strip("cmowz.") // "example"文字列分割
行ごとの分割
"abc\nabc".splitlines() // ["abc", "abc"] "abc\r\nabc\n".splitlines() // ["abc", "abc"] // 改行文字を残して分割 "abc\nabc\r".splitlines(true) // ["abc\n", "abc\r"] "abc\r\nabc\n".splitlines(true) // ["abc\r\n", "abc\n"]指定文字での分割
"a,b,c,d,".split(",") // ["a", "b", "c", "d", ""] "aabbxxaabbaaddbb".split("aa") // ["", "bbxx", "bb", "ddbb"] // 分割の回数を指定 "a,b,c,d,".split(",", maxsplit: 2) // ["a", "b", "c,d,"]出現回数カウント
"abc abc abc".count("abc") // 3 // 開始位置の指定 "abc abc abc".count("abc", start:2) // 2 // 終了位置の指定 "abc abc abc".count("abc", end:1) // 0ゼロ埋め
"abc".zfill(1) // "abc" "abc".zfill(5) // "00abc" // 符号付きの場合 "+12".zfill(5) // "+0012" "-3".zfill(5) // "-0003" "+12".zfill(2) // "+12"符号付きの場合は符号の後ろにゼロが入ります。
さいごに
以上、簡単に主要な機能の説明をさせて頂きました。
紹介したメソッド以外にもPython3.7.3の時点で利用できるstr型のメソッドは言語機能的に実装出来ない、あるいは実装が難しいもの以外はほとんど実装してあります。
実装してあるメソッドの一覧は、こちらをご覧ください。
https://github.com/ChanTsune/SwiftyPyString/blob/master/README.md一部、標準のメソッドと機能がかぶるものもありますが、PythonからSwiftに移植したいなんて言う事があれば多少は移植作業が楽になるのではないでしょうか?(普通にPythonを動くようにした方が多分楽 笑)
そうでなくともPythonからプログラミングを始めたという人なら、慣れ親しんだPythonの文字列操作ができるようになるので比較的便利ではないでしょうか?
このメソッド実装できるよ、とかこっちの実装の方がパフォーマンスいいんじゃない? Swiftだったらこう書くと綺麗だよ等ありましたら教えてください。
プルリクお待ちしております。
もしあれば、バグ報告とかも嬉しいです。
以前、C++版も作っているのでこちらも宜しければ
c++でもpythonのstr型のメソッドを使いたい!
https://qiita.com/ChanTsune/items/38814ca81738877c51fe
- 投稿日:2019-08-20T10:48:12+09:00
DjangoでSuperUserだけが機能を使えるようにする方法
はじめに
未来電子テクノロジーでインターンをしています、Sotaです。
プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。今回はDjangoで管理者だけに機能を操作できる権限を与える方法について書いていきます。
@staff_member_required
通常、ログインしているユーザーだけに機能を操作する権限を与えるには、
from django.contrib.auth.decorators import login_required @login_required def hogehoge(request): ...とします。
これをcreatesuperuserで作成した管理者(SuperUser)だけが操作できるようにするには、
from django.contrib.admin.views.decorators import staff_member_required @staff_member_required def hogehoge(request): ...とします。
使い方はほとんど同じで、分かりやすかったです。
- 投稿日:2019-08-20T10:44:09+09:00
Donkey Carを組み立てる前にシミュレーターで楽しんでみる Donkey Car 3.1.0編
はじめに
Donkey Carは市販のラジコンカーを改造して自律走行させることができるプラットフォームです。RCカーの改造の仕方や機械学習アルゴリズムがオープンソースで公開されいるので誰でも組み立てることができ、各地で走行会やレースが開催されています。
先日のMaker Faire Tokyo 2019でもAIでRCカーを走らせよう!コミュニティの主宰で走行会&レースが開催されました。
Donkey CarはRCカーにRaspberry Piなどの小型コンピュータとカメラを搭載してカメラで撮影した画像を機械学習アルゴリズムで認識し、RCカーのステアリングやスロットルをコンピュータ制御する仕組みになっています。搭載するコンピュータはRaspberry Pi 3b+の他、バージョン3.0.2からNVIDIA Jetson Nanoに対応しました。
この記事では、Donkey Carに関心を持たれた方々向けに、Donkey Simulatorを使ってPC上でDonkey Carのテスト走行、教師あり学習、自律走行をシミュレーションで楽しむ方法をご紹介します。
RCカーを購入する前にDonkey Carで必要な作業がどんなものなのか、どういったところが楽しいのかを体感していただければ幸いです。
以下、Macによる手順をご紹介しますがWindowsでもほぼ同じ手順で実現可能です。Windowsでセットアップする場合は、Anaconda NavigatorをインストールしてPythonの仮想環境を作成してから下記を実行すると便利だと思います。
Donkey Simulatorをインストールする
シミュレーターをインストールします。各プラットフォーム毎に用意されています
https://github.com/tawnkramer/gym-donkeycar/releasesMacの場合、最新版をダウンロードしてzipで展開すると
DonkeySimMac
が作成されます。その中にあるdonkey_sim
を/Application
にドラッグ&ドロップします。このようにセットアップしたDonkey Simulatorのロードモジュールのパスは以下のようになります/Applications/donkey_sim.app/Contents/MacOS/donkey_simこれのパスはあとで大事になるので覚えておいてください
DonkeyCarをセットアップする
Install Donkeycar on Macに従ってPCにdonkeycarをセットアップします。Donkey Carは現時点の最新バージョン
3.1.0
です。Donkey Carの開発は活発なのでバージョンアップされてセットアップ方法が変更される可能性があります以下の環境を想定しています
Mac 10.14.5
Python 3.7.3まず適当なディレクトリを作成します。ディレクトリ名はなんでもいいですが、ここでは
donkeypjtest
としましたmkdir ./donkeypjtest cd donkeypjtesttensorflowをインストールします。tensorflowでGPUが利用可能な環境であれば
tensorflow-gpu
をインストールすると高速に学習できますpip install tensorflowdonkeycarのレポジトリをcloneしてセットアップします
git clone -b master https://github.com/autorope/donkeycar pip install -e donkeycar上記が正常にセットアップできると、
donkey
コマンドが使えるようになりますgym-donkeycarをセットアップします
git clone https://github.com/tawnkramer/gym-donkeycar pip install -e gym-donkeycarプロジェクトを作成する
donkey createcar
コマンドでプロジェクトを作成しますdonkey createcar --path ./mycar cd ./mycarプロジェクトが作成されると以下のようなディレクトリ構成になります
tree . . ├── config.py ├── data ├── logs ├── manage.py ├── models ├── myconfig.py └── train.py 3 directories, 4 filesキモは
manage.py
です。基本的にpython manage.py train
とpython manage.py drive
という2つのコマンドを使いますpython manage.py using donkey v3.1.0 ... Usage: manage.py (drive) [--model=<model>] [--js] [--type=(linear|categorical|rnn|imu|behavior|3d|localizer|latent)] [--camera=(single|stereo)] [--meta=<key:value> ...] manage.py (train) [--tub=<tub1,tub2,..tubn>] [--file=<file> ...] (--model=<model>) [--transfer=<model>] [--type=(linear|categorical|rnn|imu|behavior|3d|localizer)] [--continuous] [--aug]
myconfig.py
を編集します。このプロジェクトをDonkey Simulatorに対応させるための設定を行います。200行目あたりの以下の3行のコメントを外し、DONKEY_GYM
をTrue
にDONKEY_SIM_PATH
にDonkey Simulatorのパスを設定しますmyconfig.pyDONKEY_GYM = True DONKEY_SIM_PATH = "/Applications/donkey_sim.app/Contents/MacOS/donkey_sim" DONKEY_GYM_ENV_NAME = "donkey-generated-track-v0"以上でDonkey Carシミュレーターで遊ぶ準備ができました
シミュレーターを起動してテスト走行する
donkey_sim
を起動します。コンフィギュレーションダイアログが表示されるのでScreen resolution
を800 x 600
、Windowed
にチェックを入れてPlay!
ボタンをクリックします。
Screen resolution
はなんでもよいです。ディスプレイの解像度に合わせて指定してください。Windowed
のチェックを外すとフルスクリーンモードで起動しますシミュレーターには複数のコースが用意されています。今回は左下の
Generated Track
をクリックします
Joystick/Keyboard No Rec
をクリックするとコースを走ることができます。キーボードのカーソルキー↑
で前進←
で左折→
で右折↓
でバックです。Stop
ボタンをクリックするとコースを終了しますしばらく走行してみて感覚をつかんでください。このあと教師用データを集めるために走行しますが、その前にキーボードでの走行に慣れておくほうがよいです
教師用データを取得する
Donkey Car実機では教師データを取得するために手動で記録用走行を行います。このとき前方に搭載したカメラで走行中の景色を画像データとして記録し、同時に手動操作されたスロットルやハンドルの操作履歴を記録してそれを教師用データとします。シミュレーターでは記録モードで走行することで実機と同様のデータを取得することができます。
まず、テスト走行のときと同様に
donkey_sim
を起動します。コースを選択する前に画面右下のLog dir
ボタンをクリックしますダイアログで先ほど作成した
mycar
プロジェクトのlogs
フォルダを指定してSelect
ボタンをクリックします
Generated Track
を選択します
Joystick/Keyboard w Rec
ボタンをクリックして記録用走行を開始しますカーソルキーでDonkey Carを操作してコースを周回します。左下の
Log:
の表示で記録されている画像の枚数を確認できます。だいたい5000〜10000くらいを目安にしてください。十分に記録ができたら右上のStop
ボタンでシミュレーターを終了します記録走行が終了すると、
./mycar/logs
に教師用データが保存されますカメラ画像は160x120ピクセルのJPEG画像で保存されています
スロットルやハンドル確度などの情報がJSONで保存されています。保存されているJSONデータは改行されていませんが、下記は読みやすさのため改行して記載しています
record_550.json{ "cam/image_array":"550_cam-image_array_.jpg", "user/throttle":10.0, "user/angle":0.0, "user/mode":"user", "track/lap":0, "track/loc":10 }学習する
mycar
プロジェクト下で以下のコマンドを実行して学習を開始します。ここで--type=categorical
の指定は大事なので覚えておいてくださいpython manage.py train --tub=logs --model=models/mypilot.h5 --type=categorical以下のように
./mycar/logs
のデータでモデルを訓練していきます。訓練が終わるまで待ちますusing donkey v3.1.0 ... loading config file: /src/work/donkeypjtest/mycar/config.py loading personal config over-rides config loaded ...中略... found 0 pickles writing json records and images in tub logs logs collating 7538 records ... train: 6030, val: 1508 total records: 7538 steps_per_epoch 47 Epoch 1/100 46/47 [============================>.] - ETA: 1s - loss: 2.0719 - angle_out_loss: 1.9795 - throttle_out_loss: 1.0821 - angle_out_acc: 0.4536 - throttle_out_acc: 0.5652 Epoch 00001: val_loss improved from inf to 1.35425, saving model to models/mypilot.h5 47/47 [==============================] - 83s 2s/step - loss: 2.0617 - angle_out_loss: 1.9745 - throttle_out_loss: 1.0744 - angle_out_acc: 0.4543 - throttle_out_acc: 0.5662 - val_loss: 1.3542 - val_angle_out_loss: 1.4905 - val_throttle_out_loss: 0.6090 - val_angle_out_acc: 0.5817 - val_throttle_out_acc: 0.6513 Epoch 2/100 17/47 [=========>....................] - ETA: 47s - loss: 1.3048 - angle_out_loss: 1.5335 - throttle_out_loss: 0.5380 - angle_out_acc: 0.6140 - throttle_out_acc: 0.7403学習が終わると
./mycar/models
下にmypilot.h5
という学習済モデルが出来上がります自律走行してみる
以下のコマンドを実行します。
--type=categorical
に注意してください。--type
で指定するモデルタイプは上記のpython manage.py train
のときに指定したものと必ず一致させる必要がありますpython manage.py drive --type=categorical --model=models/mypilot.h5上記を実行するとDonkey Simulatorのコンフィギュレーションダイアログが起動するのでテスト走行のときのように設定して
Play!
をクリックしますシミュレーターが起動してしばらくするとターミナルに以下のようなログが表示されます
finished loading in 3.236067771911621 sec. Adding part FileWatcher. Adding part FileWatcher. Adding part DelayedTrigger. Adding part TriggeredCallback. Adding part KerasCategorical. Adding part DriveMode. Adding part AiLaunch. Adding part AiRunCondition. Tub does NOT exist. Creating new tub... New tub created at: /src/work/donkeypjtest/mycar/data/tub_1_19-08-19 Adding part TubWriter. You can now go to <your pi ip address>:8887 to drive your car. Starting vehicle... 8887
Starting vehicle...
と表示されたら、Webブラウザで http://localhost:8887 にアクセスします。すると以下のようなDonkey Monitor
が表示されます画面右下のMode & Pilotのセレクトボックスから
Local Pilot(d)
を選択すると、シミュレーター上のDonkey Carが動き出しますこれが先ほど訓練した学習済みモデル
mypilot.h5
を使った自律運転です。学習が不十分だった場合はコースをうまく認識せずにコースアウトしたり、スタート地点から動かないことすらあります。そういったときには右下のExit
ボタンをクリックするとコースが再起動して運転を再開します同じ学習済みモデルを使っていても一回ごとにDonkey Carの運転は微妙に違いますし、新たに訓練したモデルを使うとまた違う動きをします。記録する画像の量も多ければよい運転をするとも限りません。記録走行での運転方法や画像の枚数などをいろいろ変えてみてうまくコースが周回できるモデルを作成してみてください
記録、訓練、自律走行までの一連の流れはDonkey Car実機でもほぼ同じです。シミュレーターでこの流れに慣れておけば実機を組み立てた際にスムーズに走らせることができるでしょう
シミュレーターの遊び方は以上です
遊ぶ上でのポイントとかコツとか
上記に慣れた後に楽しめるポイントやぼくが経験上得たコツなんかを記載しておきます。参考になれば幸いです
Colabで高速に訓練する
FaBo DonkeyCar Docs Colabでの学習(GPU)でGoogle ColabのGPUを利用できるnotebookが公開されています。このnotebookを使うとGPUを使って高速に訓練することができるので大変便利です。教師用画像データが5000枚程度であればだいたい5分くらいで訓練が完了します。使い方はNotebookに丁寧に記載されているので、記載どおりにすすめていけばよいです
いろんなモデルを訓練してみる
python manage.py train --type=[MODEL TYPE]
で指定できるモデルタイプは複数あり、様々なアルゴリズムを使って訓練させることができます。現時点でサポートされているタイプはlinear|categorical|rnn|imu|behavior|3d|localizer
となっています。各モデルの解説は公式ドキュメントのKerasに記載されています同じ教師データでも異なるモデルを訓練させることで全く違う運転をするのでいろいろ試してみてください。経験上
categorical
が安定している気がしています上述のとおり、訓練時と運転時の
--type
指定は同一にする必要があります。異なるモデルを指定した場合はpython manage.py drive
でのモデル読み込み時にエラーが発生します何度訓練してもスタート地点から発進しない場合
何度記録と訓練を繰り返してもDonkey Carがスタート地点から発進しないことがありました。いろいろ試したところ記録運転で周回しているときにスタート地点周辺でスロットルをオン(
↑
キー)していると発進しやすくなりました検証したわけではないですが、おそらくスタート地点周辺でスロットルを開けてるデータを学習させないと発進時にスロットルを開ける確率が低下してしまうためではないかと思います。スタート地点前後では
↑
キーを押し続けるとか小刻みに連打するなどして周辺でスロットルを開けることを教えてやるといいでしょうおわりに
Donkey Carでシミュレーターを使った遊び方を紹介しました。Donkey Carは実機を組み立てて現実世界のコースで走らせるのがなんといっても楽しいですが、組み立てる前の練習や訓練方法を試行するときなどにシミュレーターが役に立ちますし、シミュレーターであれこれ試行錯誤するだけでもなかなか楽しいものです。
Donkey Carに興味を持たれた方はぜひシミュレーターを使ってDonkey Carに対する知識を深めてください!
- 投稿日:2019-08-20T10:41:26+09:00
UWSC を Python で置換しよう(2)関数置き換え
はじめに
「UWSC を Python で置換しよう」第二回です。今回は低レベル操作系の置き換えをしていこうとおもいます。
とりあえず、方向性として、サンプルスクリプトを用意するか、UWSCの関数をPythonで再現するか悩んだ結果、UWSCの関数をPythonで再現する方向でやってみようかと。※要はUWSCスクリプトを動かせるラッパーを目指したのですが、結論から言ってしまうと、UWSCとは似ても似つかない中途半端なものができてしまったので、次回は、大人しく、UWSCスクリプトの動作をPythonで再現する方向にしようと思います。取り敢えず、微妙なものになりましたが、どうなったのか気になる人もいる(?)とおもうので公開しておきます。
今回の対象はUWSCの関数
G_SCREEN_ , G_MOUSE_ , GET_WIN_DIR , GET_SYS_DIR , GET_CUR_DIR ASC , CHR , MMV , BTN , KBD , CHKIMG , MSGBOX , INPUT , SAVEIMG
- 使用モジュール
head.py# -*- coding: utf-8 -*- import os,sys,re,time,subprocess import pyautogui import win32com.client置き換え関数
早速、PythonでUWSCの関数を作っていく
- G_SCREEN_W(画面幅)
def G_SCREEN_W(): screen_x,screen_y = pyautogui.size() return screen_x
- G_SCREEN_H(画面高)
def G_SCREEN_H(): screen_x,screen_y = pyautogui.size() return screen_y
- G_SCREEN_C(画面色数)
def G_SCREEN_C(): im = pyautogui.screenshot() colordepth = im.dtype return colordepth
- GET_WIN_DIR(WindowsのPATH)
def GET_WIN_DIR(): return os.environ['windir']
- GET_SYS_DIR(System32のPATH)
def GET_SYS_DIR(): sysdir = os.environ['SystemRoot'] + '¥System32' return sysdir
- GET_CUR_DIR(カレントPATH)
def GET_CUR_DIR(): return os.getcwd()
- ASC(文字コード変換)
def ASC(str): return str.encode('UTF-8')
- CHR(文字コード変換)
def CHR(str): return str.encode('ASCII')
- MMV(マウスカーソル移動)
# MMV(X,Y,ms) def MMV(x,y,deley): msec = deley / 1000 time.sleep(msec) pyautogui.moveTo(x,y)
- BTN(マウス操作)
def BTN(key,sta,x,y,deley): msec = deley / 1000 time.sleep(msec) if sta == 0: if key == 'LEFT': pyautogui.click(x, y) if key == 'RIGHT': pyautogui.rightClick(x, y) if key == 'MIDDLE': pyautogui.middleClick(x, y) if key == 'WHEEL': pyautogui.scroll(sta, x, y) if key == 'TOUCH': pyautogui.click(x,y) if sta == 1: if key == 'LEFT': pyautogui.mouseDown(x, y, button='left') if key == 'RIGHT': pyautogui.mouseDown(x, y, button='right') if key == 'MIDDLE': pyautogui.mouseDown(x, y, button='middle') if key == 'WHEEL': pyautogui.scroll(sta, x, y) if key == 'TOUCH': pyautogui.mouseDown(x, y, button='left') if sta == 2: if key == 'LEFT': pyautogui.mouseUp(x, y, button='left') if key == 'RIGHT': pyautogui.mouseUp(x, y, button='right') if key == 'MIDDLE': pyautogui.mouseUp(x, y, button='middle') if key == 'WHEEL': pyautogui.scroll(sta, x, y) if key == 'TOUCH': pyautogui.mouseUp(x, y, button='left') if sta > 2: pyautogui.scroll(sta, x, y) if sta < 0: pyautogui.scroll(sta, x, y)
- KBD (キー操作)
def KBD(key,sta,deley): msec = deley / 1000 time.sleep(msec) if sta == 0: pyautogui.press(key) if sta == 1: pyautogui.keyDown(key) if sta == 2: pyautogui.keyUp(key)
- CHKIMG (画像マッチング)
def CHKIMG(filename,maskcolor,rx1,ry1,rx2,ry2,num,conf): #返値はtuple型 if len(conf) > 11: CFG = 0.95 else: CFG = 0.98 counter = 0 for pos in pyautogui.locateAllOnScreen(filename,region=(rx1,ry1,rx2,ry2),confidence=CFG): lpos = pos if counter == num: break counter += 1 return lpos
- MSGBOX (メッセージボックス)
def MSGBOX(mes,btype): if btype == 'BTN_OK': result = pyautogui.alert(text=mes,title='',button='OK') if btype == 'BTN_NO': result = pyautogui.alert(text=mes,title='',button='NO') if btype == 'BTN_YES': result = pyautogui.confirm(text=mes, title='', buttons=['Yes', 'No']) if btype == 'BTN_CANCEL': result = pyautogui.confirm(text=mes, title='', buttons=['OK', 'Cancel']) if btype == 'BTN_ABORT': result = pyautogui.alert(text=mes,title='',button='ABORT') if btype == 'BTN_RETRY': result = pyautogui.alert(text=mes,title='',button='RETRY') if btype == 'BTN_IGNORE': result = pyautogui.alert(text=mes,title='',button='IGNORE') return result
- INPUT (入力ボックス)
def INPUT(mes,defv,hide): if hide == 0: result = pyautogui.prompt(text=mes, title='',default=defv) if hide == 1: result = pyautogui.password(text=mes, title='', default=defv, mask='*') return result
- SAVEIMG (スクリーンショット)
def SAVEIMAGE(filename,x1,y1,x2,y2): pyautogui.screenshot(filename,region=(x1,y1,x2,y2))使ってみる
とりあえす、上記の関数を収めた、
compatible_uwsc.py
を作った
で、uwsc_demo.py
というデモ用のファイルを作り、UWSCっぽいスクリプトを書く# -*- coding: utf-8 -*- import compatible_uwsc as UWSC VAL1 = UWSC.INPUT('Please Input Word','Sample...',0) UWSC.MSGBOX(VAL1,'BTN_OK') UWSC.SAVEIMAGE('screenshot.png',1,1,1920,1080) UWSC.MMV(10,15,1000) UWSC.MMV(100,150,1000) UWSC.MMV(10,150,1000) UWSC.MMV(100,15,1000) UWSC.MMV(50,150,1000) UWSC.BTN('LEFT',0,100,100,1000) UWSC.BTN('RIGHT',0,100,100,1000) UWSC.KBD('ctrl',1,1000) UWSC.KBD('a',0,1000) UWSC.KBD('c',0,1000) UWSC.KBD('ctrl',2,1000)これだとPython的になってしまう…できればUWSCのスクリプトをそのまま動かせないかな…
さいごに
ラッパーを作る方向で関数を作ると、UWSCをやってた人にはかろうじてわかる...かもしれないものができたが、これじゃない感がすごい。この方向は、スキルがないと私程度の知識では中途半端なものしかできそうにないので、失敗ですかね。
というわけで、次回は大人しく、UWSCで作ったスクリプトやUWSC関数の動作をPythonで再現していく方向にしようと思います。次回は
UWSC を Python で置換しよう(3) Pythonで動作を再現
の予定
- 投稿日:2019-08-20T10:15:47+09:00
Pythonで始める機械学習 ニューラルネットワーク
ニューラルネットワーク
- ディープラーニングと呼ばれる
- ディープラーニングを理解する入り口として多層パーセプトロン(MLP)によるクラス分類と回帰がある
- また多層パーセプトロンもニューラルネットワークと呼ばれる
ニューラルネットワーク
- MLPは線形モデルを一般化し,決定までに複数のステージで計算するものとみることができる
- 線形モデルの式はy = w[0] × x[0] + w[1] × x[1] + ・・・w[p] × x[p] + bで表されこれを言葉で言い換えると,yは入力特徴量x[0]からx[p]までの重み付き和で,重みは学習された係数w[0]からw[p]までで与えられる
- これを図で表したものが次のものである
In[87]: display(mglearn.plots.plot_logistic_regression_graph())
- 左のノードは入力特徴量,接続している線が学習された係数,右のノードは出力を表す
- 出力は入力に対する重み付き和となっている
- 次にMLPの計算を図で表す
In[88]: display(mglearn.plots.plot_single_hidden_layer_graph())
- 中間処理の隠れユニット(hidden layer)の計算で重み付き和が行われ,次に隠れユニットの値に対しての重み付き和が行われ最後の結果が表示される
- この矢印が重み(係数)
- このモデルをより強力にするには,隠れユニットの重み付き和を計算した結果に対して非線形関数(relu関数,tanh)を適用する
- reluはゼロ以下の値を切り捨てる
- tanhは小さい値に対しては-1に,大きい値に対しては+1に飽和する
- これらの関数を次の図に示す
In[89]: line = np.linspace(-3, 3, 100) plt.plot(line, np.tanh(line), label="tanh") plt.plot(line, np.maximum(line, 0), label="relu") plt.legend(loc="best") plt.xlabel("x") plt.ylabel("relu(x), tanh(x)")
- Out[87]の隠れユニットのノード数はパラメータ設定により調整することができる
- 隠れノード数は小さく単純なデータで10くらい,大きく複雑なデータでは10,000にもなる
- 次の図のように隠れ層を追加することもできる
In[90]: mglearn.plots.plot_two_hidden_layer_graph()
- このような計算層をたくさん持つニューラルネットからディープラーニングという言葉が生まれた
ニューラルネットワークのチューニング
- two_moonsデータセットにMLPClassifierを適用してMLPの動作を見てみる
In[91]: from sklearn.neural_network import MLPClassifier from sklearn.datasets import make_moons X, y = make_moons(n_samples=100, noise=0.25, random_state=3) X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=42) mlp = MLPClassifier(solver='lbfgs', random_state=0).fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train) plt.xlabel("Feature 0") plt.ylabel("Feature 1")
- MLPの隠れユニット数はデフォルトで100個である
- この小さいデータセットに対して100個の隠れユニットは大きすぎるため,隠れユニットの数を減らしモデルを単純にすることで精度を上げる
In[92]: mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[10]) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train) plt.xlabel("Feature 0") plt.ylabel("Feature 1")
- 隠れユニット数を10個に設定しているため決定境界はギザギザになっている
- 隠れ層が1層の場合にはrelu関数を使うと決定境界は10個の線分からできたギザギザのものになる
- 決定境界を滑らかにするためには隠れユニット数を増やす,隠れ層を増やす,tanhを用いるなどの方法がある
- 上記の方法を用いて決定境界を滑らかにした例を見てみる
In[93]: # 10ユニットの隠れ層を2つ使ったもの mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[10, 10]) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train) plt.xlabel("Feature 0") plt.ylabel("Feature 1") In[94]: # 10ユニットの隠れ層を2つ使い,tanh関数を使う mlp = MLPClassifier(solver='lbfgs', activation='tanh', random_state=0, hidden_layer_sizes=[10, 10]) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train) plt.xlabel("Feature 0") plt.ylabel("Feature 1")
- 隠れ層の追加に加えてtanh関数を用いたモデルの方が決定境界は滑らかになる
- モデルの複雑さを制御するには,リッジ回帰や線形クラス分類で行ったようにl2ペナルティで重みを0に近づけることでも可能である
- MLPでのl2は,線形回帰モデルと同じようにalphaパラメータの調整することで正則化を行う
- alphaはデフォルトでは弱い正則化であり,値が増加するにつれて正則化が強まる
- 次の例では2層の隠れ層をもつ10ユニット,100ユニットの例を見てみる
In[95]: fig, axes = plt.subplots(2, 4, figsize=(20, 8)) for axx, n_hidden_nodes in zip(axes, [10, 100]): for ax, alpha in zip(axx, [0.0001, 0.01, 0.1, 1]): mlp = MLPClassifier(solver='lbfgs', random_state=0, hidden_layer_sizes=[n_hidden_nodes, n_hidden_nodes], alpha=alpha) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3, ax=ax) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, ax=ax) ax.set_title("n_hidden=[{}, {}]\nalpha={:.4f}".format(n_hidden_nodes, n_hidden_nodes, alpha))
- ニューラルネットワークの複雑さを制御する方法として,「隠れ層」「隠れユニット数」「alphaパラメータ」があることがわかる
- ニューラルネットワークではパラメータが同じでも異なる乱数シードを用いると異なったモデルになる
- 次にこの例を見てみる
In[96]: fig, axes = plt.subplots(2, 4,figsize=(20, 8)) for i, ax in enumerate(axes.ravel()): mlp = MLPClassifier(solver='lbfgs', random_state=i, hidden_layer_sizes=[100, 100]) mlp.fit(X_train, y_train) mglearn.plots.plot_2d_separator(mlp, X_train, fill=True, alpha=.3, ax=ax) mglearn.discrete_scatter(X_train[:, 0], X_train[:, 1], y_train, ax=ax)
- 次にcancerデータセットにMLPClassifierを適用する
In[97]: # 格特徴量の最大値を取得する print("Cancer data per-feature maxima:\n{}".format(cancer.data.max(axis=0))) In[98]: X_train, X_test, y_train, y_test = train_test_split(cancer.data, cancer.target, random_state=0) mlp = MLPClassifier(random_state=42) mlp.fit(X_train, y_train) print("Accuracy on training set: {:.3f}".format(mlp.score(X_train, y_train))) print("Accuracy on test set: {:.3f}".format(mlp.score(X_test, y_test))) Out[97]: Cancer data per-feature maxima: [2.811e+01 3.928e+01 1.885e+02 2.501e+03 1.634e-01 3.454e-01 4.268e-01 2.012e-01 3.040e-01 9.744e-02 2.873e+00 4.885e+00 2.198e+01 5.422e+02 3.113e-02 1.354e-01 3.960e-01 5.279e-02 7.895e-02 2.984e-02 3.604e+01 4.954e+01 2.512e+02 4.254e+03 2.226e-01 1.058e+00 1.252e+00 2.910e-01 6.638e-01 2.075e-01] Out[98]: Accuracy on training set: 0.939 Accuracy on test set: 0.916
- MLPの精度はかなり良いが,cancerのデータスケールの問題により他のモデル程の精度ではない
- データスケールは平均が0,分散で1であることが理想である
- 次にスケールの変換を行う
In[99]: # 訓練セットの特徴量ごとの平均値を算出 mean_on_train = X_train.mean(axis=0) # 訓練セットの特徴量ごとの標準偏差を算出 std_on_train = X_train.std(axis=0) # 平均を引き,標準偏差の逆数でスケール変換する これでmean=0,std=1となる X_train_scaled = (X_train - mean_on_train) / std_on_train # テストセットも同様に行う X_test_scaled = (X_test - mean_on_train) / std_on_train mlp = MLPClassifier(random_state=0) mlp.fit(X_train_scaled, y_train) print("Accuracy on training set: {:.3f}".format(mlp.score(X_train_scaled, y_train))) print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test))) Out[99]: Accuracy on training set: 0.991 Accuracy on test set: 0.965
- スケール変換を行うことで性能が向上した
- ここで学習の繰り返し回数の指定を行うことででモデルの調整を行うことができる
In[100]: mlp = MLPClassifier(max_iter=1000, random_state=0) mlp.fit(X_train_scaled, y_train) print("Accuracy on training set: {:.3f}".format(mlp.score(X_train_scaled, y_train))) print("Accuracy on test set: {:.3f}".format(mlp.score(X_test_scaled, y_test))) Out[100]: Accuracy on training set: 0.998 Accuracy on test set: 0.972
- 学習の繰り返し回数を指定することでモデルの性能が向上した
- alphaパラメータ調整による正則化の強化でもモデルの性能を向上できる
- ニューラルネットワークが学習した内容を可視化することは難しい
- 学習した内容をみる方法として,モデル内部の重みを可視化することが挙げられる
- これをcancerデータセットに対して試す
In[102]: plt.figure(figsize=(20, 5)) plt.imshow(mlp.coefs_[0], interpolation='none', cmap='viridis') plt.yticks(range(30), cancer.feature_names) plt.xlabel("Columns in weight matrix") plt.ylabel("Input feature") plt.colorbar()
- 入力と第一隠れ層を繋いでいる重みが学習されたものである
- 明るい色が正の値,暗い色が負の値である
- y軸が入力特徴量,x軸が100の隠れユニットである
- 重みが小さい特徴量はこのモデルにとって重要でない
長所・短所・パラメータ
- 大量のデータを使って非常に複雑なモデルを構築することができる
- しかし複雑なモデルは訓練時間が多く必要であることや,慎重なデータの前処理が必要である
- またパラメータの調整も重要であるため,様々な調整方法を知る必要がある
- 様々な特徴量をもつデータに関しては,決定木などを用いるべきである
ニューラルネットワークのパラメータについて
- ニューラルネットワークで最も重要なパラメータは,隠れ層の数と隠れユニットの数である
- 隠れ層は1つか2つで始め,あとから拡張するのが良い
- モデルの複雑さの指標として重み(係数)が考えられる
- 重みの数え方として,100の特徴量をもつ2クラス分類データセットで隠れ層のユニット数が100つの場合の重みは,100(特徴量数) x 100(隠れ層ユニット数)の10,000(入力と隠れ層間の重み) + 100(隠れ層と出力層間の重み) = 10,100(重み)となる
- さらに100の隠れユニットをもつ第二隠れ層を追加すると100(第一隠れユニット数) x 100(第二隠れユニット数) = 10,000ができるため,このモデルの隠れユニット数は10,100 + 10,000 = 20,100となる
- ニューラルネットワークのパラメータを調整する方法としてまず過剰適合できるように大きなネットワークを作り,それからネットワークを小さくするか,alphaの調整を行い汎化性能を強化する
- パラメータを学習するときに用いるアルゴリズムも考える必要があり,簡単に使えるアルゴリズムとしてadam(ほとんどのケースで機能するが,データのスケールには敏感)とlbfgs(頑健だが,モデルやデータセットが大きなものだと訓練に時間がかかる)の2つがある
- lbfgsより高度なものとしてsgdがあるがこちらはパラメータを多く設定する必要があり難易度が高いため,基本的にはadamとlbfgsを使えば良い
- 投稿日:2019-08-20T09:44:07+09:00
Google 翻訳 api
GcpでGoogle翻訳apiと画像のocrのapiを組み合わせて
画像から文字を抜き出して翻訳するやつを暇つぶしに作ってみようと思った
一時間程度で簡単に出来ると思ってたが、翻訳部分が本番環境でデプロイするとエラーにずっとなる。
翻訳部分はgooleの公式サイトの見本をコピペしたので、Joinの認証部分がおかしいものだとずっと思ってたが直らずにずっとトライアンドエラーを繰り返してた。
それでもエラーが出るから1行づつ削ってデプロイしてみた。
googleの公式の記述では無理な仕様になってたらしい。。
手任せさせやがってこう言うのが1番イラつく。
- 投稿日:2019-08-20T09:16:49+09:00
正規表現によるスクレイピング
正規表現によるスクレイピング
「Python クローリング&スクレイピング」という本を読んでいて詰まってしまい解決した時の方法を載せます。
p49 リスト2.11
このまま打ち込むと
①UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 130: illegal multibyte sequence
①を解決しても
②AttributeError: 'NoneType' object has no attribute 'group'
というエラーが帰ってきてしまう。
- ①の解決方法 openメソッドにencoding=utf-8を追加
- ②の解決方法 4つある「title =」 をtry: except: で囲みexcept:の中身はtitle = None とする。
これでURLと書籍のタイトルが取得できるはず。
- 投稿日:2019-08-20T09:12:25+09:00
データ収集からAI開発、Webアプリ公開まで全てPythonで済ませた話
はじめに
こんにちは、はなたです。
今回は(一般的な?)機械学習プロジェクトの一連の流れであるデータ収集→データ整形→機械学習(AI)モデル開発→モデル活用・公開
を全てPythonでやってみたよ、という記事です。プロジェクトと書きましたが、あくまで個人の趣味開発レベルです。
動機
大学院に入学してから機械学習について勉強し始めたのですが、実践する機会があまりありませんでした。kaggleは精度を競うゲームみたいな感じで個人的にはそこまで惹かれておらず、また自分で何か機械学習のモデルを作ろうと思っても「別に予測したいものなんてねえよ(笑)」という感じでした。
つい先日、Webスクレイピングの記事を書いたのですが、これを書き終えた後に今回のテーマを閃きました。今回のテーマは「コード進行から曲のキーを判別するAIを作る」です。自分の趣味である音楽の内容かつ、他に誰もやっていなさそうなアイデアだったので、寝食を忘れて開発に没頭しました。
完成したWebアプリがこちら
Djangoを使用して開発、herokuで公開しています。
https://keyjudgeai.herokuapp.com/kj_ai/
コード譜掲載サイトで有名なU-fretに掲載されている曲のURLを入力すると、その曲のキーをAIが判断して表示してくれます。
※今のところU-fret以外のサイトは非対応です。今後、他のサイトやcsvインポートに対応するかも?開発手順
以下の順序で開発しました。それぞれ詳細は記事にしましたので、興味のある部分を読んで頂ければ幸いです。
0.設計編(キーを判別するAIとは?)
今回の目的である「キーを判別するAI」をプログラムに落とし込む過程が書いてあります。「そもそもコード進行とかキーってなんぞや?」という方のために、その辺の音楽理論についても軽く触れています。1.データ収集(クローリング)
今回作るAIは機械学習のモデルなので、訓練データが必要です。訓練データ収集について書いてあります。2.データ整形(スクレイピング)
集めたデータを機械学習モデルに突っ込めるよう、データ整形を行います。コード進行の情報を上手く扱うために、クラス設計を行いました。3.xgboostを用いたAIの開発←執筆中です
機械学習モデルとハイパーパラメータ選定について書いています。4.Djangoを用いたWebアプリ開発
Django+Herokuでのデプロイについて書いてあります。おわりに
データ収集からWebアプリ開発まで、最初から最後までやり遂げられて非常に満足しています。しかし、モデルの予測精度やWebアプリの見た目など改善すべきところはたくさんあるので、時間を見つけて修正していきたいと思います。ここ直した方が良いよ、というコメントをお待ちしております!!!
あと予測が全然合ってねえよ!って曲がありましたら、教えていただけると幸いでございます。