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

ABC151 C - Welcome to AtCoder から学んだ

うーん、いけそう。 だが、しかし wa. WelcomToAtcoder_r0.py N,M = map(int,input().split()) memo = [False]*N wa = 0 for _ in range(M): p,S = input().split() p = int(p)-1 if not memo[p] and S == "WA":#AC未 get 問題で wa なら インクリメント wa += 1 elif S == "AC":#AC なら memo を ture.AC は後で true の数を数えればいい。 memo[p] = True ac = memo.count(True) print("{} {}".format(ac,wa)) なぜ wa なのか分からなかったから、 テストケースを確認した。 愕然とした。 問題文には AC を初めて出すまでの WA の数とある。 前述の記述では一個も AC が無い場合、WA の数を無限に数えるのではないだろうか? WelcomToAtcoder_r1.py N,M = map(int,input().split()) memo = [False]*N wa = [0]*N for _ in range(M): p,S = input().split() p = int(p)-1 if not memo[p] and S == "WA": wa[p] += 1 elif S == "AC": memo[p] = True WA = 0 AC = memo.count(True) for i in range(N): if memo[i]:#正解するまでの WA をカウント WA += wa[i] print("{} {}".format(AC,WA))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ID&PASS LINE bot

ブログ記事はこちら こちらを参考にさせていただきました。 from flask import Flask, request, abort import pandas as pd from linebot import ( LineBotApi, WebhookHandler ) from linebot.exceptions import ( InvalidSignatureError ) from linebot.models import ( MessageEvent, TextMessage, TextSendMessage, ) import os app = Flask(__name__) #環境変数取得 YOUR_CHANNEL_ACCESS_TOKEN = os.environ["YOUR_CHANNEL_ACCESS_TOKEN"] YOUR_CHANNEL_SECRET = os.environ["YOUR_CHANNEL_SECRET"] line_bot_api = LineBotApi(YOUR_CHANNEL_ACCESS_TOKEN) handler = WebhookHandler(YOUR_CHANNEL_SECRET) @app.route("/callback", methods=['POST']) def callback(): # get X-Line-Signature header value signature = request.headers['X-Line-Signature'] # get request body as text body = request.get_data(as_text=True) app.logger.info("Request body: " + body) # handle webhook body try: handler.handle(body, signature) except InvalidSignatureError: abort(400) return 'OK' @handler.add(MessageEvent, message=TextMessage) def handle_message(event): csv_path = './tecom_idpass.csv' student_number = event.message.text is_number = False reply_text = "" try: student_number = int(student_number) if student_number >= 5000 and student_number <= 5111: is_number = True else: reply_text = "整理番号が無効です。\n5001〜5111を入力してください。" except: reply_text = "整理番号が無効です。\n5001〜5111を入力してください。" if is_number: try: df = pd.read_csv(csv_path, encoding="shift-jis") data = df[df.number == student_number] name = data['name'].item() except: reply_text = "エラーが発生しました。" else: try: id = str(int(data['id'].item())) password = str(int(data['password'].item())).zfill(5) is_success = True reply_text = f"{student_number}\n{name}さん\n\n初期ID\n{id}\n\nパスワード\n{password}" except: reply_text = f"{student_number}\n{name}さん\n\nデータがありません。" line_bot_api.reply_message( event.reply_token, TextSendMessage(text=reply_text)) if __name__ == "__main__": # app.run() port = int(os.getenv("PORT", 5000)) app.run(host="0.0.0.0", port=port)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】CSVファイルの読み込み

CSVファイルの読み込み。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者がDjangoでHerokuにデプロイした際のserver error 500解決策 DB編

私自身初めてHerokuでアプリをデプロイした際にこのserver error 500に非常に悩みましたので投稿させていただきます。 まずは基本ですが開発環境(ローカルで)全てのページが正常に動作するか確認してください。 当然遷移先のページがないような場合でもserver error 500と表示されます。 それでも改善しない場合は以下の記述が正確かどうか確認してみてください。 確認するポイントとしては ・データベースの接続 ・メールの設定(メール送信をする場合は) ・ALLOWED_HOSTSの設定 ・staticの設定 上記の箇所が主なつまずきポイントだと思います。1つずつ正確に記述してください。 今回は1つ目のデータベースの接続について解説していきます。 <データベースの接続は本当に問題ないですか?> 私のエラーは主にここが原因でした。 postgreSQLでもMySQLでも共通ですがHerokuのデータベースの設定には dj_database_url を使用するかと思います。こいつが結構重要なのにあまり詳しい情報が載っていないように思いました。 まずこいつはなにをするためにあるのかというと Herokuの環境変数に設定してあるDATABASE_URLからあなたが設定したDBのURLを持ってきてそれを設定の形に反映してくれます。つまり settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'OPTIONS': '', } } これを書いているのと同義ということです。 余談ですが DATABASE_URLの確認方法は $ heroku config 使用するデータベースを変更する場合には $ heroku config:set DATABASE_URL='データベースのURL' ちなみになんでわざわざ環境変数に入れるの?っと私は馬鹿なので思いました。 これはデータベースの情報を外に漏らさないためですね!settings.pyをgitにプッシュしないわけにはいきませんからね。 そして記述例ですが settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': '', 'USER': '', 'PASSWORD': '', 'HOST': '', 'PORT': '', 'OPTIONS': {'sql_mode': 'STRICT_TRANS_TABLES',} } } db_from_env = dj_database_url.config()#ここのカッコの中身はOPTIONSにあたります DATABASES['default'].update(db_from_env) これはあくまで私の記述方法になりますが、上で設定した項目DATABASES['default'] に対して DATABASE_URLの情報を持ってきて上書きしている状態になります。 ちなみによくサイトに落ちている以下の記述をそのままコピペするとserver errorになります。 settings.py import dj_database_url DATABASES['default'] = dj_database_url.config(conn_max_age=600, ssl_require=True) このコードはOPTIONSにあたるカッコ内にも問題があります。 conn_max_age=600というのは60秒までしかデータベースに接続しないよ!という意味です。 (私はこれでserver error 500が出ていました。なぜか時間をおくと切断される!!となっていました。) なのでconn_max_ageを任意の数値に設定してください。記述なし、またはCONN_MAX_AGEなら切断はされません。 ssl_requireも場合によってはFalseにする必要があるかと思います。 データベースの設定は確実に行ってください。 デプロイで一番大切なのは根気ではなく1つ1つを正確にすることだと身をもって学ばされました。 頭の悪い私にもできたので皆さんなら楽勝なはずです。最後まで諦めないで頑張ってください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Databricks(Spark)にてDelta Lake形式とParquet形式における実行計画のデータサイズの比較

概要 Databricks(Spark)にてDelta Lake形式とParquet形式における実行計画のデータサイズを比較したところ、Delta Lake形式では約1万分の1のサイズとなりました。Delta Lake形式の利用により、Parquet形式を利用よりも性能の向上が期待できます。 パーティション列を設定したParquet形式のデータセットとDelta Lake形式のデータセットに対してfilterを行う処理の実行計画を比較する検証を実施しました。本記事では、その手順と実行結果を記述します。 optimizedPlanの実行計画ステージ以降で、Parquet形式ではサイズが変わらないのに対して、Delta Lake形式ではサイズが縮小することが判明しました。 データフォーマット 実行計画のデータサイズ Parquet形式 1,465,937,133 Delta Lake形式 122,624 Delta Lake形式の実行計画のデータサイズ Parquet形式の実行計画のデータサイズ 実行計画を比較すると、Delta Lake形式ではPreparedDeltaFileIndexが利用されるなど実行計画が異なりました。 Delta Lake形式の実行計画 Parquet形式の実行計画 詳細は下記のGithub pagesのページをご確認ください。 コードを実行したい方は、下記のdbcファイルを取り込んでください。 https://github.com/manabian-/databricks_tecks_for_qiita/blob/main/tecks/filtered_dataframe_size/dbc/filtered_dataframe_size.dbc 実行環境 databricks runtime: 9.0.x-scala2.12 Python version : 3.8.10 pyspark version : 3.1.2 検証手順 事前準備 filepath = "dbfs:/databricks-datasets/tpch/data-001/lineitem/" schema = """ L_ORDERKEY INTEGER , L_PARTKEY INTEGER , L_SUPPKEY INTEGER , L_LINENUMBER INTEGER , L_QUANTITY DECIMAL(15,2) , L_EXTENDEDPRICE DECIMAL(15,2) , L_DISCOUNT DECIMAL(15,2) , L_TAX DECIMAL(15,2) , L_RETURNFLAG STRING , L_LINESTATUS STRING , L_SHIPDATE DATE , L_COMMITDATE DATE , L_RECEIPTDATE DATE , L_SHIPINSTRUCT STRING , L_SHIPMODE STRING , L_COMMENT STRING """ df = (spark .read .format("csv") .schema(schema) .option("sep", "|") .load(filepath) ) dbutils.fs.rm('dbfs:/FileStore/qiita/filtered_dataframe_size', True) # パーティションを設定したdelta形式のデータセットを準備 df.write.mode('overwrite').format('delta').partitionBy('L_SHIPDATE').save('dbfs:/FileStore/qiita/filetered_dataframe_size/lineitem_delta_partitionby') # パーティションを設定したparquet形式のデータセットを準備 df.write.mode('overwrite').format('parquet').partitionBy('L_SHIPDATE').save('dbfs:/FileStore/qiita/filetered_dataframe_size/lineitem_parquet_partitionby') 実行計画のステージごとのサイズを表示 # 関数を定義 def print_sizeinbytes(df): """複数の実行計画から取得できるデータのサイズを表示 """ table_size = df._jdf.queryExecution().logical().stats().sizeInBytes() print(f"logical : {table_size}") table_size = df._jdf.queryExecution().analyzed().stats().sizeInBytes() print(f"analyzed : {table_size}") table_size = df._jdf.queryExecution().withCachedData().stats().sizeInBytes() print(f"withCachedData: {table_size}") table_size = df._jdf.queryExecution().optimizedPlan().stats().sizeInBytes() print(f"optimizedPlan : {table_size}") table_size = df._jdf.queryExecution().sparkPlan().stats().sizeInBytes() print(f"sparkPlan : {table_size}") table_size = df._jdf.queryExecution().executedPlan().stats().sizeInBytes() print(f"executedPlan : {table_size}") # パーティションを設定したdelta形式のデータセットにフィルター処理した際のデータサイズ df_2 = spark.read.load('dbfs:/FileStore/qiita/filetered_dataframe_size/lineitem_delta_partitionby').filter('L_SHIPDATE = CAST("1992-01-02" AS date)') print_sizeinbytes(df_2) df_5 = spark.read.format('parquet').load('dbfs:/FileStore/qiita/filetered_dataframe_size/lineitem_parquet_partitionby').filter('L_SHIPDATE = CAST("1992-01-02" AS date)') print_sizeinbytes(df_5)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC155 C - Poll から学んだ

何か辞書で行けないかな? とりあえずサンプルを見てみる 辞書で各要素をカウント。 => 最大値を取り出して => 辞書順に最大値に合うものを print する。 辞書を key / item で一回ずつソートすれば行けるんじゃね? => 最大値を取り出して ...item でソート => 辞書順に最大値に合うものを print する。...key を辞書順でソート 調べたらスグに出てきた、神。 早速かいてみる。 Poll.py N = int(input()) dic = {} for _ in range(N):#O(n) s = input() if s not in dic: dic[s] = 0 dic[s] += 1 #item についてソート。 lis = sorted(dic.items(),key=lambda t:t[1])#O(nlogn) ref = lis[-1][1]#最大値とりだし #key を辞書順に並び替え lis = sorted(dic.items(),key=lambda t:t[0])#O(nlogn) print(lis) for a,b in lis:#O(n) if b == ref: print(a) 計算量はトータル O(n) x 2set + O(nlogn) x 2set だから、 8*10^5 っと言ったところだろうか。 一応通った。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

myCobot Pi sshで記録再生動作実現までの手順

はじめに myCobot Piは動作を記録再生することができる。 こちらの動画のmyCobotはM5版なので記録再生は本体スイッチを使って簡単に行えているが、 myCobot Piにはそれがないため、自分で記録再生をできるようにする必要がある。 そこまでの手順を備忘録的に残す。 ssh経由によるアクセス HDMIでPCモニター等に映像を写し、USBマウス・キーボードによる操作でもよいが、、 ssh経由でmyCobot Piにアクセスも可能なので、できるようにしておくと楽。 無線でアクセスしたい場合は通常のRaspberryPiと同様に設定をしておく。 最初のコマンドはいつもと同じ。 ssh pi@raspberrypi.local 次のパスワードはお馴染みのraspberryではなく、piになっている。 アクセスできれば成功。 記録再生動作のソースコード myCobot Pi公式マニュアルにはチュートリアルサンプルが紹介されているが、 この中には残念ながら記録再生動作をさせるものはない。 https://www.elephantrobotics.com/docs/myCobot-en/1-introduction/6-raspberry_mycobot/ 記録再生動作のプログラムはElephant Roboticsのgithubの方に公開されている。 https://github.com/elephantrobotics/pymycobot/tree/main/demo この中のdrag_trial_teaching.pyが記録再生動作のソースコードになる。 実行すると以下のようなことを聞かれる。 1 : /dev/ttyAMA0 - ttyAMA0 Please input 1 - 1 to choice: ここは接続するCOMポートの選択で、1から1までの中から選べと聞かれている。 今は1番の/dev/ttyAMA0しかないため、1と入力。 入力すると、次はボーレートを聞かれる。 Please input baud(default:115200): ここで注意なのが、default:115200を入れてはいけないということで、myCobot Piでは115200を入れて実行するとうまく動作しない。 前述のmyCobot Piチュートリアルサンプルではボーレートは1000000が使われているのでこれを入力。 最後にデバッグモードを使うか聞かれる。 Wether DEBUG mode[Y/n]: Yとかにして動かしてもよく分からないバイナリデータが流れるだけなので、普通にnでいい。 上記ポートとボーレートが変えることはまずないはずなので、setup()を使わず、ソースコードに port = /dev/ttyAMA0 baud = 1000000 mc = MyCobot(port, baud) と直接入力してしまうのがいい。 実行するとコマンド入力画面になる。 q: quit r: start record c: stop record p: play once P: loop play / stop loop play s: save to local l: load from local f: release mycobot ---------------------------------- 表示されている通り、rで記録開始、cで記録終了、pで再生、Pでループ再生、sで動作保存、lで動作読込、fでサーボオフ、qで終了。 基本的には動作を作成する場合はr→c→p→fを繰り返して望む動作を作成。記録後に保存したい時にsを使う流れになる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MLP-MixerのすごさをPython Pytorch実装しながら体感してみる。

MLP-Mixer MLP-Mixer: An all-MLP Architecture for Vision はじめに  近年、自然言語処理・画像認識・音声認識において、Transformerを使った事前学習モデルの成果が著しいが、一方で古典的なMLP(Multi Layer Perceptron)を改良した驚くほどシンプルなアーキテクチャでTransformerと同等の性能がでることがMLP-Mixer: An all-MLP Architecture for Visionで確認されており、非常に気になるところである。本記事では、MLP-MixerとVision Transformerの推論の精度をImagenetのデータセットで確認してみる。 対象読者 人工知能・機械学習・深層学習の概要は知っているという方 理論より実装を重視する方 Pythonを触ったことがある方 Pytorchを触ったことがある方 目次 MLP-Mixer論文サマリ VisionTransformer V.S. MLP-Mixer 参考文献 その他オススメ 1. 論文サマリ MLP-Mixer: An all-MLP Architecture for Visionが主張しているポイントを 三つに絞るとすると、以下の通りだ。 CNNやAttentionは画像認識のベンチマークで高精度な記録を出すのに重要だが必須ではない。 2つの”まぜまぜ”層によってMLPだけでもSoTAに匹敵する性能を出せる。 画像の位置情報(画像パッチ)のまぜまぜ。 画像の空間的情報(画像パッチまたぎの情報)のまぜまぜ。 訓練と推論にかかるコストもSoTAに匹敵する性能をだせる。 詳細は論文に、概要はAI-Scholarさんの記事"【MLP-Mixer】MLPがCNN,Transformerを超える日"におまかせ。本記事では実際にGoogle Colaboratoryで使う方法を解説する。 結果比較 MLP-MixerとViTどっちがすごいの?って結果だけ知りたいビジネスチックな方のために 結論ファースト。cipher10のテストにおいて、事前学習モデルでそのまま 比較すると全く同精度。再学習モデルでの比較ではViTがちょっとだけ(1%くらい)まだ高精度。 パラメータサイズはMLP-Mixerのが3/4くらいで省エネなので、自分はMLP-Miexer推し。 この研究をきっかけにgMLPが"Pay attention to MLPs"という Attention意識した論文ではattentionも組み合わせて更に精度が上がっている模様。Attention is Not All You Needというわけだ。 Model 事前学習モデル正解率(Cipher10) 再学習後モデル正解率(Cipher10) Vision Transformer(ViT) 0.10063 0.976 MLP-Mixer 0.10063 0.9646382 2. 画像認識で比較 まずは、Vision Transformer(ViT)で画像認識。 セットアップ ColabランタイムはGPUにする。 リポジトリをvision_transformerというディレクトリにクローンして最新化 ![ -d vision_transformer ] || git clone --depth=1 https://github.com/google-research/vision_transformer !cd vision_transformer && git pull !pip install -qr vision_transformer/vit_jax/requirements.txt``` 利用可能な事前学習モデルを表示 !gsutil ls -lh gs://vit_models/imagenet* !gsutil ls -lh gs://vit_models/sam !gsutil ls -lh gs://mixer_models/* gs://vit_models/imagenet21k+imagenet2012/: 377.57 MiB 2020-11-30T16:17:02Z gs://vit_models/imagenet21k+imagenet2012/R50+ViT-B_16.npz 330.29 MiB 2020-10-29T17:05:52Z gs://vit_models/imagenet21k+imagenet2012/ViT-B_16-224.npz 331.4 MiB 2020-10-20T11:48:22Z gs://vit_models/imagenet21k+imagenet2012/ViT-B_16.npz 336.89 MiB 2020-10-20T11:47:36Z gs://vit_models/imagenet21k+imagenet2012/ViT-B_32.npz 334.78 MiB 2021-03-12T09:04:16Z gs://vit_models/imagenet21k+imagenet2012/ViT-B_8.npz 1.13 GiB 2020-10-29T17:08:31Z gs://vit_models/imagenet21k+imagenet2012/ViT-L_16-224.npz 1.14 GiB 2020-10-20T11:53:44Z gs://vit_models/imagenet21k+imagenet2012/ViT-L_16.npz 1.14 GiB 2020-10-20T11:50:56Z gs://vit_models/imagenet21k+imagenet2012/ViT-L_32.npz gs://vit_models/imagenet21k/: 450.23 MiB 2021-01-20T14:12:43Z gs://vit_models/imagenet21k/R26+ViT-B_32.npz 439.85 MiB 2020-11-30T10:10:15Z gs://vit_models/imagenet21k/R50+ViT-B_16.npz 1.31 GiB 2021-01-20T14:11:54Z gs://vit_models/imagenet21k/R50+ViT-L_32.npz 393.69 MiB 2020-10-22T21:38:39Z gs://vit_models/imagenet21k/ViT-B_16.npz 400.01 MiB 2020-11-02T08:30:56Z gs://vit_models/imagenet21k/ViT-B_32.npz 393.72 MiB 2021-03-10T13:28:28Z gs://vit_models/imagenet21k/ViT-B_8.npz 2.46 GiB 2020-11-03T10:46:11Z gs://vit_models/imagenet21k/ViT-H_14.npz 1.22 GiB 2020-11-09T14:39:51Z gs://vit_models/imagenet21k/ViT-L_16.npz 1.23 GiB 2020-11-02T08:35:10Z gs://vit_models/imagenet21k/ViT-L_32.npz TOTAL: 17 objects, 14306096550 bytes (13.32 GiB) 330.3 MiB 2021-07-13T19:39:09Z gs://vit_models/sam/ViT-B_16.npz 336.61 MiB 2021-07-13T19:39:10Z gs://vit_models/sam/ViT-B_32.npz 1.13 GiB 2021-07-13T19:39:38Z gs://vit_models/sam/ViT-L_16.npz 1.14 GiB 2021-07-13T19:39:38Z gs://vit_models/sam/ViT-L_32.npz TOTAL: 4 objects, 3143025464 bytes (2.93 GiB) 6 B 2021-06-28T13:07:12Z gs://mixer_models/sam_$folder$ gs://mixer_models/imagenet1k/: 228.47 MiB 2021-05-05T14:09:01Z gs://mixer_models/imagenet1k/Mixer-B_16.npz 794.29 MiB 2021-05-05T14:09:02Z gs://mixer_models/imagenet1k/Mixer-L_16.npz gs://mixer_models/imagenet21k/: 289.61 MiB 2021-05-05T14:09:11Z gs://mixer_models/imagenet21k/Mixer-B_16.npz 875.78 MiB 2021-05-05T14:09:12Z gs://mixer_models/imagenet21k/Mixer-L_16.npz gs://mixer_models/sam/: 228.47 MiB 2021-06-28T13:08:09Z gs://mixer_models/sam/Mixer-B_16.npz 230.04 MiB 2021-06-28T13:08:08Z gs://mixer_models/sam/Mixer-B_32.npz TOTAL: 7 objects, 2775222110 bytes (2.58 GiB) ViTとMixerをダウンロード model_name = 'ViT-B_32' #@param ["ViT-B_32", "Mixer-B_16"] if model_name.startswith('ViT'): ![ -e "$model_name".npz ] || gsutil cp gs://vit_models/imagenet21k/"$model_name".npz . if model_name.startswith('Mixer'): ![ -e "$model_name".npz ] || gsutil cp gs://mixer_models/imagenet21k/"$model_name".npz . Colab上でモデルをプルダウンで選択できて便利。まずは、ViTでいってみよう。 TPU利用できるか確認(→今のところ無料プランではできない)。GPUでいこう。 import os if 'google.colab' in str(get_ipython()) and 'COLAB_TPU_ADDR' in os.environ: import jax import jax.tools.colab_tpu jax.tools.colab_tpu.setup_tpu() print('Connected to TPU.') else: print('No TPU detected. Can be changed under "Runtime/Change runtime type".') No TPU detected. Can be changed under "Runtime/Change runtime type". ログ設定とデバイス確認 from absl import logging import flax import jax from matplotlib import pyplot as plt import numpy as np import tqdm logging.set_verbosity(logging.INFO) # 利用可能なデバイスの数を表示 jax.local_devices() INFO:absl:Unable to initialize backend 'tpu_driver': Not found: Unable to find driver in registry given worker: INFO:absl:Unable to initialize backend 'tpu': Invalid argument: TpuPlatform is not available. [GpuDevice(id=0, process_index=0)] GPUは使えるよ。 Colabからソースを確認しながら実行する小技:画面右スプリットエディタでいくつかのコードファイルを開く。 from google.colab import files files.view('vision_transformer/vit_jax/configs/common.py') files.view('vision_transformer/vit_jax/configs/models.py') files.view('vision_transformer/vit_jax/checkpoint.py') files.view('vision_transformer/vit_jax/input_pipeline.py') files.view('vision_transformer/vit_jax/models.py') files.view('vision_transformer/vit_jax/momentum_clip.py') files.view('vision_transformer/vit_jax/train.py') モジュールのインポート import sys if './vision_transformer' not in sys.path: sys.path.append('./vision_transformer') %load_ext autoreload %autoreload 2 from vit_jax import checkpoint from vit_jax import input_pipeline from vit_jax import utils from vit_jax import models from vit_jax import momentum_clip from vit_jax import train from vit_jax.configs import common as common_config from vit_jax.configs import models as models_config Cipher10/100の画像扱うためのHelper # Helper functions for images. labelnames = dict( # https://www.cs.toronto.edu/~kriz/cifar.html cifar10=('airplane', 'automobile', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck'), # https://www.cs.toronto.edu/~kriz/cifar.html cifar100=('apple', 'aquarium_fish', 'baby', 'bear', 'beaver', 'bed', 'bee', 'beetle', 'bicycle', 'bottle', 'bowl', 'boy', 'bridge', 'bus', 'butterfly', 'camel', 'can', 'castle', 'caterpillar', 'cattle', 'chair', 'chimpanzee', 'clock', 'cloud', 'cockroach', 'couch', 'crab', 'crocodile', 'cup', 'dinosaur', 'dolphin', 'elephant', 'flatfish', 'forest', 'fox', 'girl', 'hamster', 'house', 'kangaroo', 'computer_keyboard', 'lamp', 'lawn_mower', 'leopard', 'lion', 'lizard', 'lobster', 'man', 'maple_tree', 'motorcycle', 'mountain', 'mouse', 'mushroom', 'oak_tree', 'orange', 'orchid', 'otter', 'palm_tree', 'pear', 'pickup_truck', 'pine_tree', 'plain', 'plate', 'poppy', 'porcupine', 'possum', 'rabbit', 'raccoon', 'ray', 'road', 'rocket', 'rose', 'sea', 'seal', 'shark', 'shrew', 'skunk', 'skyscraper', 'snail', 'snake', 'spider', 'squirrel', 'streetcar', 'sunflower', 'sweet_pepper', 'table', 'tank', 'telephone', 'television', 'tiger', 'tractor', 'train', 'trout', 'tulip', 'turtle', 'wardrobe', 'whale', 'willow_tree', 'wolf', 'woman', 'worm') ) def make_label_getter(dataset): """Returns a function converting label indices to names.""" def getter(label): if dataset in labelnames: return labelnames[dataset][label] return f'label={label}' return getter def show_img(img, ax=None, title=None): """Shows a single image.""" if ax is None: ax = plt.gca() ax.imshow(img[...]) ax.set_xticks([]) ax.set_yticks([]) if title: ax.set_title(title) def show_img_grid(imgs, titles): """Shows a grid of images.""" n = int(np.ceil(len(imgs)**.5)) _, axs = plt.subplots(n, n, figsize=(3 * n, 3 * n)) for i, (img, title) in enumerate(zip(imgs, titles)): img = (img + 1) / 2 # Denormalize show_img(img, axs[i // n][i % n], title) データセットのロード dataset = 'cifar10' batch_size = 512 config = common_config.with_dataset(common_config.get_config(), dataset) num_classes = input_pipeline.get_dataset_info(dataset, 'train')['num_classes'] config.batch = batch_size config.pp.crop = 224 INFO:absl:Load pre-computed DatasetInfo (eg: splits, num examples,...) from GCS: cifar10/3.0.2 INFO:absl:Load dataset info from /tmp/tmpmx7kqa7vtfds INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code. データセットの設定については、右記のinput_pipeline.pyを参照 ds_train = input_pipeline.get_data_from_tfds(config=config, mode='train') ds_test = input_pipeline.get_data_from_tfds(config=config, mode='test') del config # データセットをインスタンス化するためにのみ必要です。 INFO:absl:Load pre-computed DatasetInfo (eg: splits, num examples,...) from GCS: cifar10/3.0.2 INFO:absl:Load dataset info from /tmp/tmp5qpln01btfds INFO:absl:Field info.citation from disk and from code do not match. Keeping the one from code. INFO:absl:Generating dataset cifar10 (/root/tensorflow_datasets/cifar10/3.0.2) Downloading and preparing dataset cifar10/3.0.2 (download: 162.17 MiB, generated: 132.40 MiB, total: 294.58 MiB) to /root/tensorflow_datasets/cifar10/3.0.2... Dl Completed...: 100% 1/1 [00:20<00:00, 18.14s/ url] Dl Size...: 100% 162/162 [00:20<00:00, 6.62 MiB/s] Extraction completed...: 100% 1/1 [00:20<00:00, 20.50s/ file] INFO:absl:Downloading https://www.cs.toronto.edu/~kriz/cifar-10-binary.tar.gz into /root/tensorflow_datasets/downloads/cs.toronto.edu_kriz_cifar-10-binaryODHPtIjLh3oLcXirEISTO7dkzyKjRCuol6lV8Wc6C7s.tar.gz.tmp.60fc73cd82d24890985164371806add3... INFO:absl:Generating split train Shuffling and writing examples to /root/tensorflow_datasets/cifar10/3.0.2.incompleteEIKKBP/cifar10-train.tfrecord 100% 49999/50000 [00:00<00:00, 122648.19 examples/s] INFO:absl:Done writing /root/tensorflow_datasets/cifar10/3.0.2.incompleteEIKKBP/cifar10-train.tfrecord. Shard lengths: [50000] INFO:absl:Generating split test Shuffling and writing examples to /root/tensorflow_datasets/cifar10/3.0.2.incompleteEIKKBP/cifar10-test.tfrecord 100% 9999/10000 [00:00<00:00, 46586.49 examples/s] INFO:absl:Done writing /root/tensorflow_datasets/cifar10/3.0.2.incompleteEIKKBP/cifar10-test.tfrecord. Shard lengths: [10000] INFO:absl:Skipping computing stats for mode ComputeStatsMode.SKIP. INFO:absl:Constructing tf.data.Dataset for split train[:98%], from /root/tensorflow_datasets/cifar10/3.0.2 Dataset cifar10 downloaded and prepared to /root/tensorflow_datasets/cifar10/3.0.2. Subsequent calls will reuse this data. INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 INFO:absl:Reusing dataset cifar10 (/root/tensorflow_datasets/cifar10/3.0.2) INFO:absl:Constructing tf.data.Dataset for split test, from /root/tensorflow_datasets/cifar10/3.0.2 INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 説明用にテスト画像を一括して取得 batch = next(iter(ds_test.as_numpy_iterator())) # 形の意味: [num_local_devices, local_batch_size, h, w, c] batch['image'].shape (1, 512, 224, 224, 3) 画像サンプルを表示 images, labels = batch['image'][0][:9], batch['label'][0][:9] titles = map(make_label_getter(dataset), labels.argmax(axis=1)) show_img_grid(images, titles) 訓練用画像も確認していく。 # 画像がどのように切り取られ、どのように拡大されるかを確認 # 右側のエディタでinput_pipeline.get_data()をチェックすると、 # 画像の前処理がどのように異なるかがわかる。 batch = next(iter(ds_train.as_numpy_iterator())) images, labels = batch['image'][0][:9], batch['label'][0][:9] titles = map(make_label_getter(dataset), labels.argmax(axis=1)) show_img_grid(images, titles) 事前学習モデルロード model_config = models_config.MODEL_CONFIGS[model_name] model_config classifier: token hidden_size: 768 name: ViT-B_32 patches: size: !!python/tuple - 32 - 32 representation_size: null transformer: attention_dropout_rate: 0.0 dropout_rate: 0.0 mlp_dim: 3072 num_heads: 12 num_layers: 12 ViTがロードされていて、Transformerが12層あることが確認できるよ。 続いて、パラメータ初期化 # モデルの定義を読み込み、ランダムなパラメータを初期化。 # また、モデルをXLAにコンパイルします(初回は数分かかる)。 if model_name.startswith('Mixer'): model = models.MlpMixer(num_classes=num_classes, **model_config) else: model = models.VisionTransformer(num_classes=num_classes, **model_config) variables = jax.jit(lambda: model.init( jax.random.PRNGKey(0), # Discard the "num_local_devices" dimension of the batch for initialization. batch['image'][0, :1], train=False, ), backend='cpu')() チェックポイントのロード # チェックポイントのロードと変換。 # 実際に事前学習したモデルの結果をロードしますが、同時に # 最終層の変更や、位置埋め込みのサイズ変更など、パラメータを少し変更します。 # 位置埋め込みを変更します。 # 詳細については、コードと論文のメソッドを参照のこと。 params = checkpoint.load_pretrained( pretrained_path=f'{model_name}.npz', init_params=variables['params'], model_config=model_config, ) INFO:absl:Inspect extra keys: {'pre_logits/bias', 'pre_logits/kernel'} INFO:absl:load_pretrained: drop-head variant ここまでで、すべてのデータはホストメモリにある。 # 配列をデバイスに複製する。 # これにより、pytree paramsに含まれるすべての配列がShardedDeviceArrayになる。 # 同じデータがすべてのローカルデバイスに複製される。 # 単一のGPUの場合は、単にデータをデバイスに移動する。 params_repl = flax.jax_utils.replicate(params) print('params.cls:', type(params['head']['bias']).__name__, params['head']['bias'].shape) print('params_repl.cls:', type(params_repl['head']['bias']).__name__, params_repl['head']['bias'].shape) params.cls: DeviceArray (10,) params_repl.cls: _ShardedDeviceArray (1, 10) /usr/local/lib/python3.7/dist-packages/jax/lib/xla_bridge.py:391: UserWarning: jax.host_count has been renamed to jax.process_count. This alias will eventually be removed; please update your code. "jax.host_count has been renamed to jax.process_count. This alias " /usr/local/lib/python3.7/dist-packages/jax/lib/xla_bridge.py:378: UserWarning: jax.host_id has been renamed to jax.process_index. This alias will eventually be removed; please update your code. "jax.host_id has been renamed to jax.process_index. This alias " そして、モデルのフォワードパスの呼び出しを、利用可能なすべてのデバイスにマッピングする。 vit_apply_repl = jax.pmap(lambda params, inputs: model.apply( dict(params=params), inputs, train=False)) 正解率を計測する用に関数を定義する。 def get_accuracy(params_repl): """Returns accuracy evaluated on the test set.""" good = total = 0 steps = input_pipeline.get_dataset_info(dataset, 'test')['num_examples'] // batch_size for _, batch in zip(tqdm.trange(steps), ds_test.as_numpy_iterator()): predicted = vit_apply_repl(params_repl, batch['image']) is_same = predicted.argmax(axis=-1) == batch['label'].argmax(axis=-1) good += is_same.sum() total += len(is_same.flatten()) return good / total とりあえず、ViTを事前学習モデルのまま再学習なしにcipher10で正解率計測してみる。 # 再学習していない状態での精度 get_accuracy(params_repl) INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 100%|██████████| 19/19 [01:25<00:00, 4.49s/it] DeviceArray(0.10063734, dtype=float32) 10%という結果。そりゃあそうか。再学習してないもの。 ということで再学習してみる。 total_steps = 100 warmup_steps = 5 decay_type = 'cosine' grad_norm_clip = 1 #accum_steps = 8 accum_steps = 32 base_lr = 0.03 # 詳細は、右側のエディタでtrain.make_update_fnを確認のこと。 lr_fn = utils.create_learning_rate_schedule(total_steps, base_lr, decay_type, warmup_steps) update_fn_repl = train.make_update_fn( apply_fn=model.apply, accum_steps=accum_steps, lr_fn=lr_fn) # モメンタムで半分の精度を使用しメモリを節約するグラデーションクリッピングを利用。 opt = momentum_clip.Optimizer(grad_norm_clip=grad_norm_clip).create(params) opt_repl = flax.jax_utils.replicate(opt) # ドロップアウトのためPRNGsを初期化 update_rng_repl = flax.jax_utils.replicate(jax.random.PRNGKey(0)) /usr/local/lib/python3.7/dist-packages/jax/lib/xla_bridge.py:391: UserWarning: jax.host_count has been renamed to jax.process_count. This alias will eventually be removed; please update your code. "jax.host_count has been renamed to jax.process_count. This alias " /usr/local/lib/python3.7/dist-packages/jax/lib/xla_bridge.py:378: UserWarning: jax.host_id has been renamed to jax.process_index. This alias will eventually be removed; please update your code. "jax.host_id has been renamed to jax.process_index. This alias " TPUで20分程度かかるらしいがGPUでも30分くらいだった。 losses = [] lrs = [] for step, batch in zip( tqdm.trange(1, total_steps + 1), ds_train.as_numpy_iterator(), ): opt_repl, loss_repl, update_rng_repl = update_fn_repl( opt_repl, flax.jax_utils.replicate(step), batch, update_rng_repl) losses.append(loss_repl[0]) lrs.append(lr_fn(step)) plt.plot(losses) plt.figure() plt.plot(lrs) さて、再学習後の正解率は # 精度計測 print(model_name) print(get_accuracy(opt_repl.target)) INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 ViT-B_32 100%|██████████| 19/19 [00:50<00:00, 2.66s/it] 0.9766653 97.6%と結構高い。さすがViT!!! つづいてMLP-Mixerで画像認識。変えるのはここだけ。 モデルのサイズがまず3/4くらいになったのがうれしい。 まず、事前学習モデルのままの精度はこちら。 # 再学習していない状態での精度 get_accuracy(params_repl) INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 100%|██████████| 19/19 [02:37<00:00, 8.28s/it] DeviceArray(0.10063734, dtype=float32) 再学習の損失をプロットするとこんな感じ losses = [] lrs = [] # GPUで35分程度かかる for step, batch in zip( tqdm.trange(1, total_steps + 1), ds_train.as_numpy_iterator(), ): opt_repl, loss_repl, update_rng_repl = update_fn_repl( opt_repl, flax.jax_utils.replicate(step), batch, update_rng_repl) losses.append(loss_repl[0]) lrs.append(lr_fn(step)) plt.plot(losses) plt.figure() plt.plot(lrs) 再学習後の精度はこちら。 # 精度計測 print(model_name) print(get_accuracy(opt_repl.target)) INFO:absl:Load dataset info from /root/tensorflow_datasets/cifar10/3.0.2 Mixer-B_16 100%|██████████| 19/19 [02:30<00:00, 7.91s/it] 0.9646382 長い記事を読んでいただき感謝です。 参考文献 -MLP-Mixer -Vision Transformer / MLP-Mixer 著者 ツイッターもやってます。 @keiji_dl その他オススメ 人工知能Webアプリ開発入門 画像認識をViT(Vision Transformer)を用いて行うWebアプリをPython/Pytorch Lightning Flask/Jinja2/Bootstrap/JQuery/CSS/HTMLなどをフルスタックに使って7ステップ2時間で解説する動画を開設しました。 7ステップで作るPython x Flask x Pytorch 人工知能Webアプリ開発入門 Transformer 101本ノック(Kindle Unlimited)。 ソースコードへのリンクも手に入りますのでぜひご一読下さい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ABC196 C - Doubled を解いた

文書のまま入力してみようと思った。 念のためサンプルを確認 ふむふむ。とりあえず書いてみよう。 Doubled.py N = int(input()) #N は偶数桁。 a,b = 1,1 # a と b を用意。 X = int(str(a)+str(b)) # a b を合体して X lis = [] # 条件に合うものをを append while X <= N: lis.append(X) a += 1 b += 1 X = int(str(a)+str(b)) #print(lis) print(len(lis)) #append した個数が答え N は条件から 10^12 だが、a , b と分割して考える事で a, b のそれぞれの最大値は 10**6 となるので間に合う算段だ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UAV写真測量の撮影枚数(測量士試験問題)をpython で解く

はじめに 測量士の過去問を見ていると、最近はUAV写真測量の問題が出題されています。 同じような問題を出題しているのですが、それはきっと大切な内容なのでしょう。 今回、それをプログラミングしてみました。 問題はこちら。直接ダウンロードもできます。 https://www.gsi.go.jp/LAW/SHIKEN/past.html 令和元年度、午後(記述式)のNo.2-C を解きます。 UAV写真測量の撮影枚数をpython で計算する 試験問題の設定条件 設定は以下の通りです。 (1) カメラパラメータ 要素名 記号 数値 内容 画面距離(焦点距離) f 16mm イメージセンサへの入力が像を結ぶ点 撮像素子 s 4um イメージセンサの1素子の一片の大きさ 画面の大きさ(画素数) 6000 x 4000 センサ素子の数 (2) 対象区域  要素名 記号 大きさ 東西 Y 300 m 南北 X 200 m (3) 撮影条件 要素名 記号 値 地上画素寸法(撮影基準面における) S 1 cm コース内の隣接写真との重複度 $w_o$ 80% 隣接撮影コースの空中写真との重複度 $w_s$ 60 % UAV 飛行速度 v 4 m/s 南北両端の撮影コース $e_1$ 撮影される範囲のすべてが計測対象範囲の外とする 各撮影コースの両端 $e_2$ 撮影される範囲のすべてが計測対象範囲 問題は以下の通り。 では解いていきましょう。 Python による計算 問C-1 撮影高度 撮影基準面からの高度は以下で求まります。 $H = f\frac{S}{s}$ 撮影基準面の標高が与えられているので、「海面からの撮影高度」を計算できます。 # Question C-1: flight altitude f = 16E-3 # メートル s = 4E-6 # 画素サイズ gsd = 0.010 # 地上画素分解能 meter elevation = 100 # 標高 meter h = height_from_camera_and_gsd(f, s, gsd) print(f"height on ground={h:.3f} [m]") print("elevation={:.3f} [m]".format(h + elevation)) こたえはずばり、、 height on ground=40.000 [m] elevation=140.000 [m] 問C-2 シャッター間隔 撮影間隔(基線長というらしいです)を求めます。進行方向の画像の長さを$s$とします。撮影画像のoverlap だけ進むので、これが画像間の距離になります。 $ B= S * w_0$ 飛行速度$v$が与えられているので、この基線長を移動するのにかかる時間は$B/v$ です。 # Question C-2: shutter interval. print("---") img_size=(6000, 4000) flight_speed=4.0 # meter/sec overlap = 0.80 def image_baseline(overlap: float, gnd_img_len: float): return (1-overlap) * gnd_img_len B = image_baseline(overlap, img_size[1] * gsd) print("ground image size: ({:.2f}, {:.2f}) [m]".format(img_size[0]*gsd, img_size[1]*gsd)) print("baseline: {:.2f} [m]".format(B)) print("shutter interval {:.2f} [sec]".format(B / flight_speed)) 答えはずばり、 ground image size: (60.00, 40.00) [m] baseline: 8.00 [m] shutter interval 2.00 [sec] 問C-3 コース数 ずばりコース数をn として以下を解きます。 $ Y + L\cdot e_1 \cdot 2 < L_w [1 + (n-1)(1-w_c)]$ これを満たす整数nを求める計算を、そのまま実装します。 land_size = (200, 300) cource_edge_overlap = 1.0 sidelap = 0.60 from numpy import ceil def course_number(land_len: float, img_len: float, sidelap: float, edge_overlap: float) -> int: min_n = (land_len/img_len + 2 * edge_overlap - sidelap) / (1.0 - sidelap) return int(ceil(min_n)) N1 = course_number(land_size[0], img_size[0]*gsd, sidelap, cource_edge_overlap) print(f"{N1} cources") こたえはずばり、、、 12 cources 問C-4 撮影枚数 同じように、1コース内の撮影枚数を求めます。コース数はさきほど求めたので合計枚数を計算できます。 overlap = 0.80 def photo_in_cource(land_len: float, img_len: float, overlap, edge_overlap: float) -> int: return course_number(land_len, img_len, overlap, edge_overlap) N2 = photo_in_cource(land_size[1], img_size[1]*gsd, overlap, cource_edge_overlap) print(f"{N2} photo in a cource") print("total: {} photos".format(N1*N2)) 答えはずばり 44 photo in a cource total: 528 photos 答え合わせ。 まとめ UAV写真測量の写真枚数の計算を、測量士の試験問題を例にpython で実装した。答え合わせをして計算結果が正しいことを確認できた。 今後、改良するとしたら、、、 - class にする。単純なrefactoring - 図で表示したい - ブラウザで動作するようにしたい とは思うが、今日はこのあたりで。疲れた。測量士の試験の準備、中途半端だったなー(^^; (2021/09/14)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3.9.6でNumpyがインストール出来ない。

Numpyインストールの際にかなり手間取ったので、 記載。 Pythonを勉強し始めたのでモジュールとしてNumpyとDjangoをインストールしようと試みたが、 何度やっても 「pip not found」 と出てきてしまい、どんなに調べても解決しなかったので挫けそうになった。 ただ調べていく中で、「pip」は無いが「pip3」は入っている。という事が分かった。 もしやと思い、 「pip intsall numpy」を 「pip3 intsall numpay」に変更したところ、 やっとインストールに成功した。 おそらく最新のpythonバージョンではpip3でのインストールを行う。 的なものなんだと、自身に言い聞かせて終了。 さて、勉強再開しましょ。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで標準偏差を理解してみる

標準偏差とは https://ja.wikipedia.org/wiki/標準偏差 標準偏差(ひょうじゅんへんさ、(英: standard deviation, SD)とは、データや確率変数の、平均値からの散らばり具合(ばらつき)を表す指標の一つである。偏差ベクトルと、値が標準偏差のみであるベクトルは、ユークリッドノルムが等しくなる。 標準偏差を2乗したのが分散であり、従って、標準偏差は分散の非負の平方根である[1]。標準偏差が 0 であることは、データの値が全て等しいことと同値である。 母集団や確率変数の標準偏差を σ で、標本の標準偏差を s で表すことがある。 二乗平均平方根 (RMS) を用いると、標準偏差は偏差の二乗平均平方根に等しくなる。 なるほどわからん。 どうやら平均からどれだけデータがばらついているかを表す指標ということらしい。 平均は文系の私でもまあわかる。 標準偏差は次の式で求められるみたいだが…。 …まあこれは置いといてnumpyのstd()で求められるので試してみる。 import numpy scores = [ { "label": "【A.均等にばらついてるデータ】", "data": [ 20, 30, 40, 50, 60, 60, 70, 80, 90, 100, ], }, { "label": "【B.一部が平均以下のデータ】", "data": [ 0, 5, 10, 70, 80, 80, 82, 85, 93, 95, ], }, { "label": "【C.一部が平均以上のデータ】", "data": [ 50, 52, 54, 60, 60, 60, 61, 61, 70, 72, ], }, ] def print_mean_and_std(label, data): print(label) mean = numpy.average(data) print("平均:%s" % mean) std = numpy.std(data) print("標準偏差:%s" % std) for score in scores: print_mean_and_std(score["label"], score["data"]) 【A.均等にばらついてるデータ】 平均:60.0 標準偏差:24.49489742783178 【B.一部が平均以下のデータ】 平均:60.0 標準偏差:36.67151483099655 【C.一部が平均以上のデータ】 平均:60.0 標準偏差:6.6783231428256 で、この標準偏差で出てきた数値がなんなんだってばよ…。 調べると標準偏差が求まることで、平均±標準偏差の範囲にデータの分布が集中していることがわかるそう。 つまり上記の例のCならば60.0±6.6783231428256なので52.3~66.6の間に分布が集中していることになると。 うん、確かに。 正規分布 これをわかりやすくするために正規分布というグラフにしてみる。 #!pip install japanize-matplotlib import matplotlib.pyplot as plt import japanize_matplotlib import numpy from scipy.stats import norm scores = [ { "label": "【A.均等にばらついてるデータ】", "data": [ 20, 30, 40, 50, 60, 60, 70, 80, 90, 100, ], }, { "label": "【B.一部が平均以下のデータ】", "data": [ 0, 5, 10, 70, 80, 80, 82, 85, 93, 95, ], }, { "label": "【C.一部が平均以上のデータ】", "data": [ 50, 52, 54, 60, 60, 60, 61, 61, 70, 72, ], }, ] def plot_hist(label, data): mean = numpy.average(data) std = numpy.std(data) fig, ax = plt.subplots() ax.set_title(label) ax.set_xlabel("得点") ax.set_ylabel("割合") bins = numpy.arange(0, 101) plt.hist(data, bins, density=True) xn = numpy.linspace(min(bins), max(bins), 100) yn = norm.pdf(xn, loc=mean, scale=std) plt.plot(xn, yn) plt.show() for score in scores: plot_hist(score["label"], score["data"]) カーブのてっぺんが平均値の位置で、標準偏差が大きいほどばらついてるのでカーブの幅が広くなっている。 1σ、2σ、3σ 標準偏差×1したものを1σ。 ×2したものを2σ、×3したものを3σと呼ぶそう。 で、それぞれの区間に分布が収まる確率が 1σ = 約68% 2σ = 約95% 3σ = 約99.7% となり、この法則は平均や標準偏差の値が何であれ成り立つそう。 68–95–99.7則とも呼ばれる。 まとめ 標準偏差を理解するために書いてみました。 数式だととっつきづらいけど、Notebookにコードを書きながら学んでみると何となく理解できるもんですね。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Introducing TensorFlow Similarity まとめてみた

TWに流れてきたので、学習の一環としてまとめてみました。 ※ 間違いに気づいたらブラッシュアップします。 ソース:Introducing TensorFlow Similarity September 13, 2021 TensorFlow を使って類似性モデルを簡単かつ迅速にトレーニングするために設計された python パッケージ TensorFlow Similarity が提供しているもの 類似性の学習評価や query を直感的かつ簡単に行うために必要なすべてのコンポーネント 特に、埋め込みインデックスと query をネイティブにサポートする 新しい Keras モデルである SimilarityModel() を導入 初期リリースでフォーカスしていること コントラスト学習に基づく類似性モデルを構築するために必要なすべてのコンポーネントを提供すること (such as losses, indexing, batch samplers, metrics, and tutorials) Keras API との連携や、既存の Keras アーキテクチャの利用を容易に TensorFlow Similarity で可能なこと end-to-end training と 評価 を迅速かつ効率的に行う MNIST data のトレーニング index 作成 検索を行う最小限の例は、20行以下のコードで書ける from tensorflow.keras import layers # Embedding output layer with L2 norm from tensorflow_similarity.layers import MetricEmbedding # Specialized metric loss from tensorflow_similarity.losses import MultiSimilarityLoss # Sub classed keras Model with support for indexing from tensorflow_similarity.models import SimilarityModel # Data sampler that pulls datasets directly from tf dataset catalog from tensorflow_similarity.samplers import TFDatasetMultiShotMemorySampler # Nearest neighbor visualizer from tensorflow_similarity.visualization import viz_neigbors_imgs # Data sampler that generates balanced batches from MNIST dataset sampler = TFDatasetMultiShotMemorySampler(dataset_name='mnist', classes_per_batch=10) # Build a Similarity model using standard Keras layers inputs = layers.Input(shape=(28, 28, 1)) x = layers.Rescaling(1/255)(inputs) x = layers.Conv2D(64, 3, activation='relu')(x) x = layers.Flatten()(x) x = layers.Dense(64, activation='relu')(x) outputs = MetricEmbedding(64)(x) # Build a specialized Similarity model model = SimilarityModel(inputs, outputs) # Train Similarity model using contrastive loss model.compile('adam', loss=MultiSimilarityLoss()) model.fit(sampler, epochs=5) # Index 100 embedded MNIST examples to make them searchable sx, sy = sampler.get_slice(0,100) model.index(x=sx, y=sy, data=sx) # Find the top 5 most similar indexed MNIST examples for a given example qx, qy = sampler.get_slice(3713, 1) nns = model.single_lookup(qx[0]) # Visualize the query example and its top 5 neighbors viz_neigbors_imgs(qx[0], qy[0], nns) ↑ このコードでは最適ではないモデルを使用しているが、以下の画像のように、 Nearest Neighbours が明らかに質問された数字に似ているという良好な結果を得ている。 参考情報:k近傍法(k-Nearest Neighbor) 関連するアイテムを検索する機能 例: マルチメディア検索 レコメンダーシステム クラスタリングパイプライン  → これらのシステムの多くは、対照学習を用いて学習された深層学習モデルを搭載している core information systems の重要な役割: 関連するアイテムを迅速に検索できる 実世界での用途例: 似たような服を見つける 現在流れている曲を特定する 行方不明のペットを助ける 対照学習とは 埋め込み空間をモデルに学習させるもの  同じクラスに属する画像は近くに配置される(集められる)  異なるクラスの画像は遠くに配置される(離れていく) データセット全体に適用した場合: 埋め込み間の距離が入力例の類似性を表わすようにアイテムを埋め込み、空間に投影する方法を学習する Contrastive loss(対照損失)とは: Embedding 空間上での2点間の距離をベースに算定される損失 参考情報:【深層距離学習】Siamese NetworkとContrastive Lossを徹底解説 学習の最終段階 類似アイテム間の距離が小さい 相違アイテム間の距離が大きい 例:  Oxford-IIIT Petデータセットで類似性モデルを学習すると、  見た目が似ている犬種が近く、猫と犬がはっきりと分かれている 意味あるクラスターになる モデル トレーニング 検索可能にしたい様々なアイテムの Embedding を含む index を構築 query (処理要求)時 TensorFlow SimilarityはFast Approximate Nearest Neighbor search (ANN)を活用 index から最も近いマッチングアイテムを sub-linear time (劣線形時間) で検索 fast look up (簡易検索? 高速検索?) が活用している事実 埋め込まれた点の間の距離が有効な距離メトリックの関数 (a function of a valid distance metric)である metric embedding space を学習する → これらの distance metrics は the triangle inequality (三角不等式) を満たす。   そのため、近似最近傍探索が可能な空間となり高い検索精度を実現している。 参考情報:深層距離学習(Deep metric learning)を理解する 最近傍探索とは 距離空間 における最も近い点を探す最適化問題の一種 ベクトルで表現されるデータ(点)の中から、query と最も類似している距離が小さいデータ(最近傍点)を探す手法 モデルの特徴抽出を利用するなど他のアプローチもあるが、関連するアイテムを見つけるために正確な最近傍探索を使用する必要があるため、訓練された類似性モデルほど正確ではない場合がある。 厳密な検索を実行するには、検索インデックスのサイズの a quadratic time (2次時間 ?) が必要 になるため、これらの手法のスケーリングを妨げる。 ↑ ここ自信ありません This prevents those methods ??? (。º̩̩́⌓º̩̩̀).゜ ↓ Other approaches, such as using model feature extraction, require the use of an exact nearest neighbor search to find related items and may not be as accurate as a trained similarity model. This prevents those methods scaling as performing an exact search requires a quadratic time in the size of the search index. 対照的に、TensorFlow Similarityに内蔵されているNMSLIB に依存した Approximate Nearest Neighbor indexing system では、何百万ものインデックス化されたアイテムを検索し、上位K個の類似データを瞬時に取り出すことを可能にしている。 参考情報:最近傍探索 精度と検索速度に加えて、類似性モデルの大きな利点 再学習の必要がなく、無制限に新しいクラスを index に追加できる 新しいクラスの代表的なアイテムの Embedding を計算して、index に追加するだけでよい 動的にクラスを追加する機能が有効なケース 識別可能なアイテムの数が事前に不明 常に変化している 非常に大きい問題に取り組む際 例:  ユーザーが過去に好きだった曲に似た、新しくリリースされた音楽を発見できるようにするなど 今後の予定 この強固な基盤の上に、BYOL、SWAV、SimCLR などの半教師あり学習、自己教師あり学習の手法をサポート予定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Kaggle 初心者向け】クロスバリデーションで平均正解率が最高となったモデルを Titanic に適用する

はじめに タイタニック号生存者予測は、Kaggle 挑戦者が最初に着手するタスクです。このタスクでは、下記の特徴量を入力として、各乗船者の生存・死亡を分類します。 特徴量 定義 意味 PassengerId 通し番号 Survived 生存・死亡 1 = 生存, 0 = 死亡(訓練データのみに含まれている) Pclass チケットクラス 1 = 1等客室, 2 = 2等客室, 3 = 3等客室 Name 名前 Sex 性別 Age 年齢 SibSp 一緒に乗船した兄弟/配偶者の数 Parch 一緒に乗船した親/子供の数 Ticket チケット番号 Fare 旅客運賃 Cabin 客室番号 Embarked 乗船港 C = シェルブール, Q = クイーンズタウン, S = サウサンプトン 上記項目のうち「Survived」が欠けたテストデータに対して、学習したモデルを適用します。 そして、最終的には、次の2つの項目を含む 418 名分の分類結果を Kaggle に提出します。 - PassengerId - Survived(1 = 生存, 0 = 死亡) なお、データセットは次のページからダウンロードできます。 https://www.kaggle.com/c/titanic/data 戦略 scikit-learn に含まれるすべての分類器を学習させる クロスバリデーションで各分類器の性能(正解率)を評価する 平均正解率が最高となった分類器をテストデータに適用する 要するに、手当たり次第に学習・評価を繰り返し、最高のモデルを選抜しようということです。 クロスバリデーションによる評価結果は、未知データに対する汎化性能を保証しないので、最適な分類器を選抜できるとは限らないのですが、少なくともハズレを使う心配はないだろうということでこの戦略を採ります。 また、「名前」「チケット番号」「客室番号」「乗船港」の4つの特徴量は、今回は使いません。これらの特徴量にも分類に有用な情報が含まれているとは思うのですが、初心者がすぐに使える形式のデータではないからです。 コード 上記戦略のもとで、次のコードを書きました。 import numpy as np import pandas as pd from sklearn.utils import all_estimators from sklearn.model_selection import KFold from sklearn.model_selection import cross_val_score import warnings import pdb if __name__ == '__main__': df = pd.read_csv('train.csv') # 欠損値を補完 df['Fare'] = df['Fare'].fillna(df['Fare'].median()) df['Age'] = df['Age'].fillna(df['Age'].median()) # 数値に変換 df['Sex'] = df['Sex'].apply(lambda x: 1 if x == 'male' else 0) # 不要なデータを破棄 df = df.drop(['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked'], axis =1) # 0〜1の範囲で正規化 df = (df - df.min()) / (df.max() - df.min()) # 入出力データを生成 X = df.drop('Survived', axis=1) Y = df.Survived # クロスバリデーション用のオブジェクトをインスタンス化する kfold_cv = KFold(n_splits=5, shuffle=False) warnings.filterwarnings('ignore') # classifier のアルゴリズムをすべて取得する all_Algorithms = all_estimators(type_filter="classifier") warnings.filterwarnings('ignore') max_clf = None max_score = -1 # 各分類アルゴリズムをクロスバリデーションで評価する for (name, algorithm) in all_Algorithms: try: if (name == "LinearSVC"): clf = algorithm(max_iter = 10000) else: clf = algorithm() if hasattr(clf, "score"): scores = cross_val_score(clf, X, Y, cv=kfold_cv) print(name, "の正解率:") print(scores) if max_score < np.mean(scores): max_clf = clf max_score = np.mean(scores) except Exception as e: pass # 平均正解率が最高だったモデルをトレーニング max_clf = max_clf.fit(X, Y) df_test = pd.read_csv('test.csv') passsengerid = df_test['PassengerId'] # 欠損値を補完 df_test['Fare'] = df_test['Fare'].fillna(df_test['Fare'].median()) df_test['Age'] = df_test['Age'].fillna(df_test['Age'].median()) # 数値に変換 df_test['Sex'] = df_test['Sex'].apply(lambda x: 1 if x == 'male' else 0) # 不要なデータを破棄 df_test = df_test.drop(['PassengerId', 'Name', 'Ticket', 'Cabin', 'Embarked'], axis =1) # 0〜1の範囲で正規化 X_test = (df_test - df_test.min()) / (df_test.max() - df_test.min()) # 結果を出力 pred = max_clf.predict(X_test) result = [int(i) for i in pred] submission = pd.DataFrame({'PassengerId':passsengerid, 'Survived':result}) submission.to_csv('submission.csv' , index=False) 「HistGradientBoostingClassifier」の平均正解率が「0.8339150084740444」で最高となり、これがテストデータに適用されました。 その結果を Kaggle にアップロードして評価を受けたところ、、、 なんで 75 %しか出てへんねん。 バグを疑うレベルで低いと感じたのですが、scikit-learn の結果を整形して csv に出力しているだけなので、学習に問題があるとは考えにくい。 この Titanic 問題に対して HistGradientBoostingClassifier とやら(モデルと学習法の詳細は知らない)の自由度が高すぎることが、1つの原因かもしれません。しらんけど。 まとめ さらっと試しただけなのでこの程度の結果なのかもしれませんが、個人的に不服なので引き続きチャレンジするかも? 特に、今回は4つのデータを使わずに捨てているので、これらをうまく使えばもう少し正解率は上がるでしょう。しらんけど。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでスプレッドシート書き込み【続】

前回の続き Googleドライブ側で下準備 pythonでGoogleドライブのフォルダ及びスプレッドシートを編集する場合、共有する必要があります。 ファイル右クリックで共有をクリックします。 pythonでGoogleドライブのフォルダ内にファイルを保存したり、スプレッドシートを作成して編集などをしたい場合は、そのフォルダを共有します。 特定のスプレッドシートしか読み書きしない場合はそのスプレッドシートを共有します。 GCPからダウンロードしてきたjsonファイルを開いてください。 pythonのプロジェクトフォルダ直下にあるはずです。 メモ帳やVSCodeなどで開いて大丈夫です。 "client_email": "~~~~~~~~~~~~~~~~@~~~~~~~~~~~~~~.iam.gserviceaccount.com" ↑この部分を探してください。 ~~~~~~~~~~~~~~~~@~~~~~~~~~~~~~~.iam.gserviceaccount.comの部分だけコピーします。 そしてGoogleドライブの方での共有のメールアドレスを打ち込むところにペーストします。 図のようにクリックし、送信を押します。 これで下準備はできました。 python側 必要なライブラリをインストール pip install gspread oauth2client pydrive import os import gspread from oauth2client.service_account import ServiceAccountCredentials from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive JSON_PATH = os.path.join(os.getcwd(), "jsonファイル名.json") # GoogleドライブのURLからIDを取ってきます。 FOLDER_ID = "~~~~~~~~~~~~~~~~~~~~~" # 対象フォルダのIDはhttps://drive.google.com/drive/u/0/folders/以下 SHEET_ID = "~~~~~~~~~~~~~~~~~~~~~" # スプレッドシートの場合https://docs.google.com/spreadsheets/d/ここがID/edit#gid=0 class Gspread: def __init__(self) -> None: self.workbook: Spreadsheet self.worksheet: Worksheet self.drive: GoogleDrive self.credentials: Any self.set_gspread() def set_gspread(self): scope = [ "https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive", ] self.credentials = ServiceAccountCredentials.from_json_keyfile_name( JSON_PATH , scope ) gauth = GoogleAuth() gauth.credentials = self.credentials self.drive = GoogleDrive(gauth) # フォルダにファイルを保存してみる def save_file(self) -> None: f = self.drive.CreateFile({"parents": [{"id": FOLDER_ID}]}) f.SetContentFile("パスとファイル名")     # Googleドライブのフォルダに保存したファイルを命名 f["title"] = "ファイル名" f.Upload() # スプレッドシートを開く def open_sheet_by_(self): gc = gspread.authorize(self.credentials) # スプレッドシートを開く self.workbook = gc.open_by_key(SHEET_ID) # ワークシート1シート目指定 self.worksheet = self.workbook.get_worksheet(0)   # 最終行に書き込み   def append_row(self) -> None:     test = ["test1", "test2", "test3"] # value_input_option="USER_ENTERED"全て文字列として書き込みのを防止 self.worksheet.append_row(test, value_input_option="USER_ENTERED")   # セルに書き込み   def update_cell(self) -> None: # update_cell(row, column, val) self.worksheet.update_cell(1, 2, "test") 他にもスプレッドシートのシートを追加したり、名前をつけたりいろいろ操作できるのでいろいろ調べてやってみましょう。 スクレイピングしてきた画像URLをIMAGE関数で画像を貼り付けたりもできます。 こちらを参考に 罫線書いたり、色変えたり、行及び列の幅も操作できます。 その場合はgspread_formattingというライブラリを使いましょう。 gspread_formatting公式
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonでGoogleドライブのフォルダへ保存及びスプレッドシート書き込み【続】

前回の続き Googleドライブ側で下準備 pythonでGoogleドライブのフォルダ及びスプレッドシートを編集する場合、共有する必要があります。 ファイル右クリックで共有をクリックします。 pythonでGoogleドライブのフォルダ内にファイルを保存したり、スプレッドシートを作成して編集などをしたい場合は、そのフォルダを共有します。 特定のスプレッドシートしか読み書きしない場合はそのスプレッドシートを共有します。 GCPからダウンロードしてきたjsonファイルを開いてください。 pythonのプロジェクトフォルダ直下にあるはずです。 メモ帳やVSCodeなどで開いて大丈夫です。 "client_email": "~~~~~~~~~~~~~~~~@~~~~~~~~~~~~~~.iam.gserviceaccount.com" ↑この部分を探してください。 ~~~~~~~~~~~~~~~~@~~~~~~~~~~~~~~.iam.gserviceaccount.comの部分だけコピーします。 そしてGoogleドライブの方での共有のメールアドレスを打ち込むところにペーストします。 図のようにクリックし、送信を押します。 これで下準備はできました。 python側 必要なライブラリをインストール pip install gspread oauth2client pydrive import os import gspread from oauth2client.service_account import ServiceAccountCredentials from pydrive.auth import GoogleAuth from pydrive.drive import GoogleDrive JSON_PATH = os.path.join(os.getcwd(), "jsonファイル名.json") # GoogleドライブのURLからIDを取ってきます。 FOLDER_ID = "~~~~~~~~~~~~~~~~~~~~~" # 対象フォルダのIDはhttps://drive.google.com/drive/u/0/folders/以下 SHEET_ID = "~~~~~~~~~~~~~~~~~~~~~" # スプレッドシートの場合https://docs.google.com/spreadsheets/d/ここがID/edit#gid=0 class Gspread: def __init__(self) -> None: self.workbook: Spreadsheet self.worksheet: Worksheet self.drive: GoogleDrive self.credentials: Any self.set_gspread() def set_gspread(self): scope = [ "https://spreadsheets.google.com/feeds", "https://www.googleapis.com/auth/drive", ] self.credentials = ServiceAccountCredentials.from_json_keyfile_name( JSON_PATH , scope ) gauth = GoogleAuth() gauth.credentials = self.credentials self.drive = GoogleDrive(gauth) # フォルダにファイルを保存してみる def save_file(self) -> None: f = self.drive.CreateFile({"parents": [{"id": FOLDER_ID}]}) f.SetContentFile("パスとファイル名")     # Googleドライブのフォルダに保存したファイルを命名 f["title"] = "ファイル名" f.Upload() # スプレッドシートを開く def open_sheet_by_(self): gc = gspread.authorize(self.credentials) # スプレッドシートを開く self.workbook = gc.open_by_key(SHEET_ID) # ワークシート1シート目指定 self.worksheet = self.workbook.get_worksheet(0)   # 最終行に書き込み   def append_row(self) -> None:     test = ["test1", "test2", "test3"] # value_input_option="USER_ENTERED"全て文字列として書き込みのを防止 self.worksheet.append_row(test, value_input_option="USER_ENTERED")   # セルに書き込み   def update_cell(self) -> None: # update_cell(row, column, val) self.worksheet.update_cell(1, 2, "test") スプレッドシートは書き込みはもちろん、読み込みもできます。 他にもスプレッドシートのシートを追加したり、名前をつけたりいろいろ操作できるのでいろいろ調べてやってみましょう。 スクレイピングしてきた画像URLをIMAGE関数で画像を貼り付けたりもできます。 こちらを参考に 罫線書いたり、色変えたり、行及び列の幅も操作できます。 その場合はgspread_formattingというライブラリを使いましょう。 gspread_formatting公式
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Asana のあるプロジェクトから未完了タスクの一覧を出す

Python で Asana から未完了タスクの一覧を出す方法を紹介します。 ここでは、 Python で、スプレッドシートに貼り付けるためのデータを取得します。 作りたい表の形 Asanaのタスクから、次のようなスプレドシートの表を作ることにします。 Section Task Status セクション1 タスク1 ステータス1 セクション1 タスク2 ステータス1 セクション1 タスク3 ステータス2 セクション1 タスク4 ステータス2 セクション2 タスク5 ステータス1 セクション2 タスク6 ステータス1 セクション2 タスク7 ステータス2 セクション2 タスク8 ステータス2 セクション3 タスク9 ステータス2 セクション3 タスク10 ステータス3 環境 Python 3.9.0 pyperclip asana コード 一連のコードです。 PERSONAL_ACCESS_TOKEN='XXXXXXXX' PROJECT_ID='XXXXXXXX' import asana client = asana.Client.access_token(PERSONAL_ACCESS_TOKEN) def find_sections(project_id): result = client.sections.get_sections_for_project(project_id, {}, opt_pretty=True) return result def find_tasks(project_id): result = client.tasks.get_tasks( { 'completed_since': 'now', 'opt_fields' : [ 'this.name', 'this.due_on', 'this.custom_fields', 'this.assignee', 'this.assignee.name', 'this.memberships.section', 'this.memberships.section.name' ], 'project': project_id }, opt_pretty=False ) return result section_ids = [section['gid'] for section in find_sections(PROJECT_ID)] text = 'Section\tTask\tStatus\n' # i = 1 for task in find_tasks(PROJECT_ID): sections = list( filter( lambda x: x['section']['gid'] in section_ids, filter(lambda x: 'section' in x, task['memberships']) ) ) section = sections[0]['section'] if len(sections) > 0 else None statuses = list(filter(lambda x: x['gid'] == '1234567890123456', task['custom_fields'])) status = statuses[0]['enum_value'] if len(statuses) > 0 else None # if i == 1: # print(task['custom_fields']) # i += 1 text += f'{section["name"] if section is not None else None}\t' + \ f'{task["name"]}\t' + \ f'{status["name"] if status is not None else None}' text += "\n" import pyperclip pyperclip.copy(text) 説明 まずアクセストークンとプロジェクトIDを格納します。 PERSONAL_ACCESS_TOKEN='XXXXXXXX' PROJECT_ID='XXXXXXXX' Asana のクライアントを作ります。 import asana client = asana.Client.access_token(PERSONAL_ACCESS_TOKEN) クライアントを利用して、あるプロジェクト内のセクションの一覧と、あるプロジェクト内のタスクの一覧を取得する関数を作ります。 タスクの一覧を取得するときに、セクション名も取れるのですが、そのときにタスクが複数のセクションに所属していると、それらがすべて取得できてしまいます。 そういった場合に、表示するべきセクションを絞り込むために、あるプロジェクトないのセクションを一度取得しています。 find_tasks では、 完了日の条件のみを指定しています。 タスクの基本情報と一緒に取得したいデータを、 opt_fields に設定しています。 どんなフィールドが使えるのかは、 get_task の レスポンスの構造を見るとわかります。 def find_sections(project_id): result = client.sections.get_sections_for_project(project_id, {}, opt_pretty=True) return result def find_tasks(project_id): result = client.tasks.get_tasks( { 'completed_since': 'now', 'opt_fields' : [ 'this.name', 'this.due_on', 'this.custom_fields', 'this.assignee', 'this.assignee.name', 'this.memberships.section', 'this.memberships.section.name' ], 'project': project_id }, opt_pretty=False ) return result セクションをすべて取得して、そのIDをリストにしておきます。 その後、プロジェクト内の未完了タスクをすべて取得して、表示する項目を整理して、 text にタブ区切りの文字列を生成します。 section_ids = [section['gid'] for section in find_sections(PROJECT_ID)] text = 'Section\tTask\tStatus\n' # i = 1 for task in find_tasks(PROJECT_ID): sections = list( filter( lambda x: x['section']['gid'] in section_ids, filter(lambda x: 'section' in x, task['memberships']) ) ) section = sections[0]['section'] if len(sections) > 0 else None statuses = list(filter(lambda x: x['gid'] == '1234567890123456', task['custom_fields'])) status = statuses[0]['enum_value'] if len(statuses) > 0 else None # if i == 1: # print(task['custom_fields']) # i += 1 text += f'{section["name"] if section is not None else None}\t' + \ f'{task["name"]}\t' + \ f'{status["name"] if status is not None else None}' text += "\n" ここで status は、カスタマイズとして追加されたフィールドです。 カスタマイズされたフィールドは、 task の custom_fields に含まれて返ってきます。 そこで、表示したいカスタムフィールドの gid で値をフィルタ・表示しています。 コメントアウトされている箇所をコメントインすると、 カスタムフィールドの一覧が表示されるので、gid を調べることができるようになります。 最後に、組み立てた文字列をクリップボードにコピーします。 import pyperclip pyperclip.copy(text) コピーした文字列を Excel などに貼り付けると、 表として表示されます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【ソースコード付き】ラズパイでスマホ自動タップ装置を作る

目次 材料 ラズパイの初期設定 作業手順 完成動画 材料 No. 部品名 個数 値段 備考 1 Raspberry Pi Zero WH 1 2000円ぐらい https://akizukidenshi.com/catalog/g/gM-12958/ 2 TOSHIBA マイクロSDカード 16GB 1 500円ぐらい 秋葉原のメイドリーミンの下にある電気屋(あきばおー)で購入 3 リレータッチボード(ドライバ有り) 1 680円 https://www.sengoku.co.jp/mod/sgk_cart/detail.php?code=EEHD-5LCG 4 ジャンプワイヤ オス-メス 3 0円 家にあったやつ 5 ジャンプワイヤ メス-メス 3 0円 家にあったやつ 6 ACアダプタ 1 0円 家にあったやつ 7 マイクロUSBケーブル 1 0円 家にあったやつ 8 はんだ 1 100円 ダイソーで購入 9 はんだこて 1 330円 ダイソーで購入 合計で3000~4000円ぐらい。もっと安く作るなら他にも方法はある(https://ch.togetter.com/2018/10/09/58290 )。 ラズパイの初期設定 このサイトを参考に設定したよ。 作業手順 ①リレータッチボードをはんだ付けする ジャンプワイヤ オス-メス のオス部分とリレータッチボードをはんだ付けする ②リレータッチボードをラズパイと接続する PIN表 ラズパイのピン番号 リレータッチボード 2 5V 39 GND 40(GPIO21) EN 回路図 ③ソースコードを用意する auto.py ↓ #!/usr/bin/python # coding: utf-8 # モジュールをインポートする import RPi.GPIO as GPIO import time # GPIO指定をGPIO番号で行う GPIO.setmode(GPIO.BCM) # GPIO21ピンを出力モードに設定 GPIO.setup(21, GPIO.OUT) try: while True: # GPIO21番ピンを3.3Vに設定 GPIO.output(21, 1) time.sleep(1) GPIO.output(21, 0) time.sleep(1) except KeyboardInterrupt : # GPIO21番ピンを0Vに設定 GPIO.output(21, 0) pass # GPIO設定をリセット GPIO.cleanup() ④ラズパイの電源を起動して、上記のソースコードを実行するだけ! 完成動画 1→スマホをタップしている状態 0→スマホをタップしていない状態
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ChaliceのCDパイプラインをシンプルに構築する

What is chalice? AWSが提供するPythonのサーバーレスフレームワークで、flaskみたいな書き心地でLambdaなどにデプロイできます。 今回はこのChaliceのCDを構築するお話。 TL;DR Chaliceは自動でCDパイプラインを構築する機能がある。でもなんか難しくて結局自分で作った。 使用バージョン chalice==1.24.1 Deploy Command 公式ドキュメントのQuickstartではローカル環境からCLIでデプロイする方法が紹介されています。 $ chalice deploy --stage dev 1コマンドで簡単! しかしこれだとチームで開発している場合は問題になります。 Chaliceは作成したAWSリソースを把握するため、.chalice/deployed/配下に下記のようなJSONを作成します。 deployed.json { "resources": [ { "name": "foo_role", "resource_type": "iam_role", "role_arn": "arn:aws:iam::...", "role_name": "foo" }, { "name": "foo_lambda", "resource_type": "lambda_function", "lambda_arn": "arn:aws:lambda:ap-northeast-1:..." }, { "name": "foo_event", "resource_type": "s3_event", "bucket": "foo-dev", "lambda_arn": "arn:aws:lambda:ap-northeast-1:..." } ], "schema_version": "2.0", "backend": "api" } ローカルでデプロイする際にこのJSONがチームで共有できていないとリソースの作成・更新・削除がうまくできないわけです。 このあたりはterraformのtfstateに似てる。 CI/CD pipeline generation そのため、ChaliceはCodePipelineによるCDパイプラインを自動的に構築する機能を持っています。 → https://aws.github.io/chalice/topics/cd.html $ chalice generate-pipeline --pipeline-version v2 pipeline.json このコマンドを打つとCloudFormation用の設定ファイルが吐き出されます。 吐き出された設定ファイルを元にCloudFormationを使うことでCDパイプラインが出来上がるという二段構えです。ややこしい CloudFormation?? CDパイプラインのための設定が自動で出来上がっていいじゃないか。最初はそう思いました。 デフォルトの設定で十分な方はこれでもいいかもしれません。しかしドキュメントにはこんなワードがありました。 Chalice can generate a CloudFormation template that will create a starter CD pipeline. そうstarterなのです。 プッシュしたら自動的にデプロイしてほしい。デプロイしたら通知がほしい。そういうオマケは自前で作る必要があります。 「でも実は私、CloudFormation使ったことない。」 さあ困った。CloudFormationがわかる方は生成された設定をいじればいいと思います。 以降は私のようなCloudFormationワカラナイ人向けです。 Be Simple そもそもローカルでデプロイするときはchalice deployの1コマンドで済んだのに、CDになると複雑すぎない?という疑問もあり、勝手知ったるCodeBuildでchalice deployさせる方針を取ります。 問題になるのは状態を持っているdeployed.jsonですが、tfstateのようにS3に入れてしまいましょう。 buildspec.yml version: 0.2 phases: install: runtime-versions: python: 3.8 commands: - python -V pre_build: commands: - echo Install dependencies... - pip install -r requirements.txt - aws s3 cp s3://chalice-deployed/${STAGE}.json .chalice/deployed/${STAGE}.json || true build: commands: - echo Deploy ${STAGE}... - chalice deploy --stage ${STAGE} post_build: commands: - aws s3 cp .chalice/deployed/${STAGE}.json s3://chalice-deployed/${STAGE}.json - echo Build completed at `date '+%Y-%m-%d %H:%M:%S'` うん。シンプル。 初回は${STAGE}.jsonがなくダウンロードに失敗するので|| trueでごまかしてます。 念の為、CodeBuildの同時ビルド制限を1にしておきます。 完成! ハマったところ 今回Chaliceで作成したLambda関数は一部Layerでpyodbcを入れていました。 requirements.txtに入れてもChaliceがパッケージングできないライブラリだからです。 こうしたrequirements.txtに書いていないライブラリがあるとCodeBuildでchalice deployしたときに「pyodbc入ってないよ」というエラーが出ます。 とりあえずインポートエラーを補足するワークアラウンドでしのぎました。 app.py import warnings try: import pyodbc except ImportError: warnings.warn("pyodbcをインポートできません。CD環境の場合はこの警告を無視できます。") まとめ ローカルでもCDでも同じ方法でデプロイすることができました。 CloudFormation版では何をしたかったのかを読み解けておらず、まずい点があったら教えていただきたいです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SPSS Modelerの分類ノードをPythonで書き換える。購入商品カテゴリの統合

SPSS Modelerで、カテゴリデータを統合したり、表記ゆれのあるカテゴリデータを統合したりする「分類ノード」をPythonのpandasで書き換えてみます。 0.加工前のイメージ ■加工前 誰(CUSTID)がいつ(SDATE)何(PRODUCTID、L_CLASS商品大分類、M_CLASS商品中分類)をいくら(SUBTOTAL)購入したかが記録されたID付POSデータを使います。 L_CLASSにはBAG,COSMETICS,SHOESの3つのカテゴリが含まれています。 カテゴリが多すぎる場合にいくつかのカテゴリをまとめて一つのカテゴリにしたり、「バッグ」「bag」「鞄」などの表現が複数ある同じカテゴリを一つの「BAG」というカテゴリにまとめたり、ということにこの分類ノードは使います。 1m.①商品カテゴリの統合。Modeler版 ■加工後イメージ 商品大分類(L_CLASS)の「BAG」と「COSMETICS」を「雑貨」という一つのカテゴリにまとめます。 分類ノードを使うためには、まずカテゴリデータを「データ型」ノードでインスタンス化する必要があります。 データ型ノードで「値の読み込み」を行い、L_CLASSにあるカテゴリがBAG,COSMETICS,SHOESであることを読み込みます。 次にデータ分類ノードを接続します。 「データ分類先」は「既存のフィールド」に設定すると既存のカテゴリ列を入れ替えることができます。 「データ分類フィールド」に「L_CLASS」を選択します。 そして「取得」ボタンをクリックし、さらに「コピー」ボタンをクリックします。 そうすると以下のように元の値と新しい値としてBAG,COSMETICS,SHOESが入力されます。 そして、新しい値のBAG,COSMETICSを「雑貨」に書き換えます。 テーブルノードをつないで、再分類した結果を確認します。 BAG,COSMETICSが「雑貨」に変換されていることがわかります。 1p.①商品カテゴリの統合。pandas版 pandasでカテゴリ値を置き換える場合はreplace関数を使います。 分かりやすくするために置換するカテゴリ値のマッピングをコレクションで用意します。 そしてreplace関数の引数として、{列名:マッピング・コレクション}を引数として指定します。 mapping={'BAG': '雑貨', 'COSMETICS': '雑貨'} df1=df.replace({'L_CLASS': mapping}) df1 以下のようにBAG,COSMETICSを「雑貨」に変換されていることがわかります。 replace自体は上記でできるのですが、Modelerのように、既存のカテゴリ値から初期値としてのマッピングのコレクションを作れると便利です。 df['L_CLASS'].unique()でカテゴリ値を抜き出しコレクションを作り、pprintでwidth=1を指定することで元の値と新しい値の組合せの初期値が表示できます。 mapping2={} for val in df['L_CLASS'].unique(): mapping2[val]=val import pprint pprint.pprint(mapping2, width=1) {'BAG': 'BAG', 'COSMETICS': 'COSMETICS', 'SHOES': 'SHOES'} この結果からマッピングのコレクションを作れば作りやすいと思います。 2. サンプル サンプルは以下に置きました。 ストリーム https://github.com/hkwd/200611Modeler2Python/blob/master/reclassify/reclassify.str?raw=true notebook https://github.com/hkwd/200611Modeler2Python/blob/master/reclassify/reclassify.ipynb データ https://raw.githubusercontent.com/hkwd/200611Modeler2Python/master/data/sampletranDEPT4en2019S.csv ■テスト環境 Modeler 18.3 Windows 10 64bit Python 3.8.10 pandas 1.3.0 3. 参考情報 pandas.DataFrame, Seriesの要素の値を置換するreplace データ分類ノードのオプション設定 - IBM Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

sklearn.datasets.openmlの仕様

sklearn.datasets datasetsの中には様々なデータセットが用意されている。詳細は次のリンクを参照。 scikit-learnのサンプルデータセットの一覧と使い方 datasets.fetch_openml return = datasets.openml(parameters)でデータセットを取得することが可能。parameterでどのデータを取得するのか、どのような形式で取得するのかを指定する。 以下は公式ドキュメントの解説 sklearn.datasets.fetch_openml parameters sklearn.datasets.fetch_openml(name: Optional[str] = None, *, version: Union[str, int] = 'active', data_id: Optional[int] = None, data_home: Optional[str] = None, target_column: Optional[Union[str, List]] = 'default-target', cache: bool = True, return_X_y: bool = False, as_frame: Union[str, bool] = 'auto')[source]¶ name:str, default=None データセットの名前を文字列で与える。同じ名前のデータセットが存在するので、その場合はIDで指定する。IDと名前で同時に指定してはいけない。 version:int or ‘active’, default=’active’ nameが指定されている場合のみ与えることができる。データセットのバージョン。activeにすると最も古いバージョンが指定される。 data_id:int, default=None データセットID。データセットに対し1対1で対応している。nameとversionを指定するか、IDを指定するかをえらぶ。 return_X_y:bool, default=False このオブジェクトは戻り値としてデフォルトでBunchを返す。Bunchは辞書型のようなものらしい。この変数をTrueにすると、データと目的変数を別で戻り値として設定する。それはnp.arrayやpd.Seriesなどで返すことができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】macOSでcronを自動実行し続けたら意外と電気代高かかった

はじめに 昨日以下の記事を書きました。 私は1時間に1回Pythonを自動実行しLineへ通知していましたが、しばらくすると通知が止まっていました・・・。 初心者あるあるかもしれませんが、macOSがスリープモードになっていたのです。 cronはスリープ状態だと定期実行してくれません。 そこで今回はcronを自動実行し続けるためのmacOSの省電力設定をご紹介します。 また24時間使い続けるとどれくらい電気料金がかかるのかも計算してみました。 環境 Python3.9 macOS Catalina10.15.7 方法 アップルマーク > システムの環境設定 をクリックします。 省エネルギー をクリックします。 コンピューターのスリープを 10分 → しない に設定します。 この設定にすると、コンピュータの消費電力が増加する可能性があります。 OKをクリックします。(Appleさんの優しさ・・・) 以上で設定完了です。 消費電力 どれくらい消費するのか計算してます。 まず私のmacのスペックですが、以下となります。 待機中として80Wで計算します。 計算式は以下ですね。 1時間あたりの消費電力(kW)×使用時間(時間)×料金単価(円/kWh) <我が家の条件は以下>  1時間あたりの消費電力(kW)=0.08kW (80W)  使用時間(時間)=24時間  料金単価(円/kWh)= 21円 (利用明細から算出※1) ※1 基本料金、朝晩時間、夜間時間などがありますが、今回は平均で21円としています。 0.08kW × 24時間 × 21円 = 40円 え??1日40円かかる!?1ヶ月で約1,200円!? 正直、高いですね。 常に0.08kWかどうかはわかりませんが、試算上は間違ってないかと存じます。。。 むしろもっと消費電力は高い可能性もありますね。 どうすれば電気代を下げれるか。 実行環境をクラウドにする方がいいかもですね。 先日、HP運用に関して以下の記事を書きました。 AWSでS3+Route53の利用で月額181円程度です。(初年度) Pythonを定期実行するなら、AWS Lambda+Amazon CloudWatch を利用する方が安く済むかと存じます。 早くAWS移行しようと思った一日でした・・・。 ご参考になれば幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

データサイエンティスト検定を受験してきた!

はじめに ども。見習いデータサイエンティストです。 皆さんデータサイエンティスト検定ってご存知ですか? 先日新しく始まった、「データサイエンティストってなんだ?」「そもそも何を勉強したらいいんだ?」という疑問を解決してくれる試験です! 今回はその第一回目試験を受講してきたので、勉強法や感想などを書いていこうと思います。 1 そもそもデータサイエンス検定(DS検定)とは 昨今では、数理・データサイエンスを必修化する大学が現れるほど、データサイエンスは今後の社会に必要だと認識されています。 このデータサイエンス教育のカリキュラムの作成や必須スキルのチェックリストを作成しているのがデータサイエンティスト協会です! データサイエンティスト協会は、作成したスキルチェックリストをもとに、データサイエンティストの否ライトしての技術があることを示すための検定を始めました。 https://www.datascientist.or.jp/dskentei/ データサイエンティスト検定では、ビジネス力・データサイエンス力・データエンジニア力の3つを軸に習熟度を判定します。 より詳しい説明は、協会のホームページやVtuberのAlica Solidさんが動画にしてくださっています。 https://www.youtube.com/watch?v=FxJnDJP2jNg 現在は第一回目の申込みを締め切っています;; 興味のある方は公式ページの第二回のお知らせを待ちましょう! 2 自分の資格とテストの結果 自分は現在資格として、G検定2021#2・python3エンジニア認定試験などの資格を保有しています。 そのためデータサイエンス分野については、かんたんな復習のみで試験に向かいましたが、どの程度の知識が必要かは後ほど記そうと思います。 またテスト結果については、 1. データサイエンス分野 88% 2. データエンジニアリング 76% 3. ビジネス分野 95% で総合86%でした!これで合格点90%以上とかだったら泣くしかない、恥。 3 どんな勉強をしたか この章では、DS検定ではどの程度の知識が必要か・私がやったことの2つに分けて書こうと思います DS検定に必要な知識量 DS検定の範囲はかなり広いです。 データサイエンス分野だけでも、統計検定3級・行列入門書1冊・分析準備(データ加工・データ量削減)・機械学習手法(言語・画像・時系列分析 etc...) これそれぞれで資格試験あるぐらいなので範囲の広さがわかりますね。範囲が広いので範囲を参考にそれぞれの試験を受けるのも手だと思います。 ですが実際にDS検定に受かるためだけであれば、範囲の単語の意味さえ説明できれば合格できます。 たとえば、分散の公式かけますか?クラスタリングと分類問題の違いがわかりますか?程度で大丈夫です。 DS協会が参考問題を提示しています。 https://www.datascientist.or.jp/common/docs/practice_v1.1.pdf どんな勉強をしたか まずは範囲の把握をします。 githubにスキルチェックリストがあるのでこちらが参考になると思います。 https://the-japan-datascientist-society.github.io/skills-checklist-viewer/# 自分は時間がなかったため、DS協会の公式テキストを購入し一通り流し読みしました。 この中の項目でわからないところ、忘れているところを一通りメモします。 ※G検定取得者が注意するのは、データエンジニアリング・ビジネス・AIについての倫理の部分はG検定のは範囲にはないので調べたほうがいいでしょう 公式テキスト買ってない方はチェックリストの星1を一通り確認しましょう メモの内容をひたすら検索していきます。神様仏様qiita様です。 公式テキストには模擬問題があるので、これを用いて知識の確認をしました。一度しか説いてないので案の定苦手なエンジニアリングはボロボロでした;; 4 感想 DSに必要な知識が体系的に得られるのは最高すぎますね。 自分はエンジニアリングが苦手で、どういうふうに分析して行くか思いついても、どんな構成で実装すべきかなどはわからないので、その学習の手がかりがこの検定で得られたと思います。 概要はつかめたのでガリガリ勉強していきます!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Sphinx】索引(index)の日本語対応。合わせて用語(glossary)も日本語対応。

はじめに 主に高校の数学と物理の復習を兼ねて、Sphinxで内容をまとめつつ学習しています。matplotlibディレクティブやmathディレクティブを使って楽しんでいます。 まとめた内容を横断的に俯瞰するためにindex, glossaryディレクティブが便利なんですが、例えば「微分」を例に取ると、索引ページ冒頭に「び」「ひ」と表示されて欲しいですよね。これが「微」と表示されてよろしくない。これをどうにかしたかった。 関連記事 【Sphinx】yogosyu(用語集)をSphinx4.1.2で使えるように修正する 【Sphinx】indexディレクティブからjindexディレクティブを作る 先ずは検索。「Sphinx 索引 日本語」と検索しました。 索引に載せたい | Sphinx-Users.jp 「Yogosyu」というSphinx拡張を知りますが、使おうと思うと最新のSphinx4.1.2では対応していません。ひとまずエラーなく使えるようにしました。が、索引ページは期待通りに表示されません。アレコレ調べている間に実装アイデアを思い付き、自分なりに満足できる動作になりました。これをSphinx拡張として使えるようにしたのが、当記事最後にあるPythonのコードです。 実装アイデア ポイントは次の2点。データ構造を変更することなく改良できたのが幸運。 例えば「びぶんせきぶん|微分積分」のように、「|」で区切って読みを前方に付ける。 索引ページと用語のところの表示で、出力処理の直前に「読み|」を消す。 表示処理を実行してる場所を特定するのに苦労しました。索引ページはegrepの力技で見つけられましたが、用語の方は一度sphinx-buildの起動から追いかけましたが途中で挫折。ブラウザーの開発者ツールで用語近くに「Permalink to this term」という文字列があることに気づき、ここからsphinx/writers/html5.pyにあることがわかりました。 使い方 個人的に「無難な落とし所」だと考えているのは次の通り 設定は html_change_triple = True とする。他はデフォルト。 読みは「ひ|微分積分」と、濁点なしで一文字を付与。 「pair」が結構強力です。数学なら「pair: ひ|微分積分; さ|三角関数」と索引を作ると効果がわかります。一つの「.. index::」に複数の「single/pair/triple/see/seealso」が指定できます。 glossaryでは「用語 : 分類名」と記載することで、索引ページでは同じ分類名でまとめることができます。作品ページを「上段、本体、下段」に分けて、上段でアピールしたい用語、本体からは外して下段にこっそり置きたい用語について利用を検討してください。分類名にも読みが設定できるので、これを利用して表示順の調整に使うことができます。(Sphinxでは「実験的な扱い」と説明していますが、その理由と思われるものをFAQに記載しました。) その他、親切ではありませんが、docstringに書いてあります。reST/Google/NumPyの記法は勉強中だし、パラメーターの説明は特にアレなので、、優しい気持ちでお願いします。 セットアップ 当ページ下部にあるソースコードを右上のアイコンをクリックしてコピー 利用環境の適当な場所に sphinx_ja_term.py として作成。 conf.pyに設定追加 make kana の実行。 make html ではないので注意。 ソースを見れば変更箇所はわかります。 合わせてadd_builderの第二引数に True を設定してください。 conf.pyの設定(参考例) import sys # sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, '_ext') extensions = [ 'sphinx_ja_term', ] #html_ja_term_separator = r'\|' #html_ja_term_with_one_kana = False #html_ja_term_kana_on_ruby = False #html_ja_term_kana_on_2nd = False html_change_triple = True FAQ Q: 読みが消えません。 A: make html ではなく、 make kana と実行してください。 name = 'kana' と add_builder()''' の箇所を直すだけ。それぞれでHTMLを出力させて比較ができます。慣れてきたらhtml``` としてもいいでしょう。 Q: glossary, indexを併用すると、索引ページの用語が期待通りに表示されないことがある。 A: make clean; make kana を実行してください。 結構悩んだ挙動です。glossaryとindexで同じ用語がある場合集約するのですが、内部での処理順で表示が決まるので、これの影響を受けています。 原則として「glossary -> index」の順に処理するので、これを前提に作ればいいのですが、正確には「.toctreeファイルに保存してるデータ -> glossary -> index」が処理順なので(憶測)、glossaryだけ make kana すると用語同士の依存関係が逆になります。部分的に依存関係が変わるので余計混乱します。 glossary で、「用語 : 分類名」を使う時は注意してください。 Q: 同じ「微分積分」が別々に表示される。 A: 読みを見直してください。 表記上は同じ「微分積分」でも、「ひ|微分積分」と「びぶんせきぶん|微分積分」は異なる用語として扱います。逆にこれを利用して、「微分積分」の項目が増えたときに「ひ0|微分積分」「ひ1|微分積分」と意図的に分けることが可能です。 他は「tripleのつもりで書いたのに、pairを指定している」とか、気付くのは難しくありません。 Q: 不具合の報告について A: sphinx_ja_term とタグを付けて質問を投稿してください。 Sphinxcontribに登録していればshpinxcontorib.ja_termとするところですが、やり方が分かりません。docstringを英語に書き直すのもアレなので。。 不具合修正レベルなら直すつもりです。質問にテストケースがあると喜びます。 仕様変更レベルなら、FAQで対応するつもりです。 注意事項 ライセンスは、Qiitaの規約、参考にしたSphinxソースコードのライセンスに違反しない範囲でBSDライセンス準拠とします。 正直、ライセンスについては正しく理解しているとは言い難いのですが、趣旨としては「Qiita、Sphinx、作成者に迷惑を掛けなけば、商用利用も含めて自由に使っていいよ。」とご理解ください。 とは言え、使い心地などの感想を投稿して、当記事にリンクを貼ってくれたりすると喜びます。利用素材として使われるよりも学習素材として記事にして欲しいかも。批判もOKですが豆腐メンタルなので、コメントは好意的なものや優しい奴でお願いします。ご自身の記事でアレコレ書く分には、リンクが貼ってあっても気にしません。(気にはしますけど、執筆者を尊重します。コメントでなければ返事とか不要だし。) ※「sphinxcontribに登録したい」という場合はご一報ください。できる範囲で協力させていただきます。 その他。挑戦したいこととか。 Termノード ルビの表示はvisit_term(), depart_term()でやっていいますが、本来はTermノードが自分をHTML用に出力する時にやって欲しいところです。 Termノードのインスタンス化は nodes.term(引数) ですが、このtermクラスを拡張したクラスを作って、visit_term() で termnode.set_display_mode('html') とすると良いようにしてくれるようにしたい。 depart_term() をなくせるし。 その場合の変更箇所はGlossaryくらすになります。変更範囲をあまり広げたくもないんですよね…。本件に関連して別の機能を実装するつもり、JGlossaryを作ってapp.add_directive()したら反応がなくて諦めた記憶もあるので。 write_genindex()メソッド write_genindex() の最初でデータを作っていますが、このコードだけ別のプライベートメソッドでラップして欲しい。そうすれば、ラップされたプライベートメソッドだけの上書きで済む。 索引でのルビ対応 テンプレートファイルの差し替えが必要です。やろうとしてみたら、テンプレートエンジンがタグをエンコードしてくれました…。 themeのロジックまで対応したくないのですが、プロジェクトの _templates に変更部分だけ放り込んで対処はできるのでしょうか。block単位での継承ができそうなのでできなくはないと読んでいますが、モチベーションはありません。対応箇所は write_genindex() になります。やっぱり、データのソート処理だけラップしてくれないかな。 Pythonソース 実装の肝は次の2箇所 write_genindex でソート処理後にデータを表示用に加工。 visit_term でTermノードの表示前に表示内容を加工。 参照渡しだからできました。値渡しだったらここでは対応できませんでした。 """ index, glossaryの機能を上書きするSphinx拡張 用語の形式を「読み|用語」とし、表示の際に「読み|」を取り除く。 glosssaryについては「ルビ」の表示が可能(デフォルトは非表示)。 詳細 ---- setup()のdocstringを参照 """ import re import unicodedata from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, cast) from docutils import nodes from docutils.nodes import Text from sphinx import addnodes import sphinx.builders.html as builders from sphinx.domains import std from sphinx.domains.index import IndexDomain from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.locale import _ from sphinx.util import logging from sphinx.util.docutils import is_html5_writer_available, SphinxDirective from sphinx.writers import html, html5 from docutils.nodes import Node, system_message from docutils.parsers.rst import directives from docutils.statemachine import StringList if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) # HTML5 Writer is available or not if is_html5_writer_available(): html5_ready = True else: html5_ready = False class _HTMLTranslator: """ このクラスの次のメソッドを上書きする。 それぞれの引数の基本的な仕様はオリジナルに従っている。 """ def _startDT(self,node): #KaKkou #初期設定 self.jterm_ruby = None #KaKkou sp = r' *'+self.config.html_ja_term_separator+r' *' #「用語; 用語」と二つある場合に備える。 parts = re.split(r' *; ',node[0]) + [None] if parts[1]: parts[1] = re.sub(r'^.*'+sp, '', parts[1]) parts[0] = parts[0]+', '+parts[1] try: tm = re.split(sp, parts[0])+[None] #”{読み}|{用語}”or"{用語}" except Exception as e: print(e) else: if tm[1]: if self.config.html_ja_term_kana_on_ruby and tm[0][-1] != '_': self.jterm_ruby = tm[0] node[0] = Text(tm[1]) finally: return self.starttag(node,'dt','') def visit_term(self, node): """用語Node、Visit処理 上書きするメソッド sphinx.writers.html5/vist_term() 1. 受け取ったnodeの表示用文字列から「読み」を分離する 2. html_ja_term_kana_on_rubyの設定に従い、ルビの表示/非表示を判断する 3. ルビを表示すると判断したら、self.jterm_rubyに読みを代入する 目指すべき挙動 -------------- Termノードを拡張したJaTermノードを作り、この中でルビの表示を任せるのが いいように思う。object.astextとは別にソート用文字列を出力するメソッドを 作り使い分けて欲しい欲しいところだけど。 実装アイデア ------------ ojbect.set_display_mode()を作る。 引数が'html'ならrubyタグを使い、'latex'ならそれなりに。 表示用文字とソート用文字の概念が区別される、日本人以外には理解されない だろうから歯痒い。 """ self.body.append(self._startDT(node)) #KaKkou if self.jterm_ruby: #KaKkou self.body.append('<ruby><rb>') def depart_term(self, node): """ 上書きするメソッド sphinx.writers.html5/depart_term() 1. self.jterm_rubyが設定されていれば、rubyタグを出力する。 """ next_node: Node = node.next_node(descend=False, siblings=True) if isinstance(next_node, nodes.classifier): # Leave the end tag to `self.depart_classifier()`, in case # there's a classifier. pass else: if self.jterm_ruby: #KaKkou ruby = f'</rb><rp>(</rp><rt>{self.jterm_ruby}</rt><rp>)<rp></ruby>' self.body.append(ruby) if isinstance(node.parent.parent.parent, addnodes.glossary): # add permalink if glossary terms self.add_permalink_ref(node, _('Permalink to this term')) self.body.append('</dt>') class JHTMLTranslator(_HTMLTranslator, html.HTMLTranslator): def __init__(self, document, builder): self.jterm_ruby = None super().__init__(document, builder) class JHTML5Translator(_HTMLTranslator, html5.HTML5Translator): def __init__(self, document, builder): self.jterm_ruby = None super().__init__(document, builder) class _HTMLBuilder(builders.StandaloneHTMLBuilder): """ オリジナルのwrite_genindex()が次のようにラッパー関数を介して、 IndexEntries(self.env).create_index(self)を呼び出すようにすれば、 このクラスは不要になる。 """ def _create_index(self): #KaKkou """ raiseの代わりに次のコードを有効にすればオリジナルと同じ動作をする。 genindex = IndexEntries(self.env).create_index(self) """ raise NotImplementedError('%s: %s is needed.' % (self.__class__, node.__class__.__name__)) def write_genindex(self) -> None: genindex = self._create_index() #KaKkou. データのソートが終わった直後 #以降の処理はSphinx4.1.2オリジナルと同じ indexcounts = [] for _k, entries in genindex: indexcounts.append(sum(1 + len(subitems) for _, (_, subitems, _) in entries)) genindexcontext = { 'genindexentries': genindex, 'genindexcounts': indexcounts, 'split_index': self.config.html_split_index, } logger.info('genindex ', nonl=True) if self.config.html_split_index: self.handle_page('genindex', genindexcontext, 'genindex-split.html') self.handle_page('genindex-all', genindexcontext, 'genindex.html') for (key, entries), count in zip(genindex, indexcounts): ctx = {'key': key, 'entries': entries, 'count': count, 'genindexentries': genindex} self.handle_page('genindex-' + key, ctx, 'genindex-single.html') else: self.handle_page('genindex', genindexcontext, 'genindex.html') class JHTMLBuilder(_HTMLBuilder): """ 上書きするメソッド sphinx.builder.html.StandaloneHTMLBuildeer/write_genindex() """ name = 'kana' @property def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore if not html5_ready or self.config.html4_writer: return JHTMLTranslator else: return JHTML5Translator def _swap_parts(self,term): #設定が無効なら何もしない if not self.config.html_ja_term_kana_on_2nd: return term #index_keyはNoneもある if not term: return term #読み仮名がなければ何もしない parts = re.split(self.config.html_ja_term_separator,term)+[None] if not parts[1]: return term #読み仮名と用語の位置を入れ替える sep = re.sub(r'^\\','',self.config.html_ja_term_separator) return parts[1]+sep+parts[0] def _swap_parts_on_each_term(self, line): """ 「用語; 用語; 用語」という形式の各「用語」を処理する """ rtn = "" parts = re.split(r'; +', line) for s in parts: rtn += self._swap_parts(s)+"; " return rtn[:-2] #末尾の"; "を除く def _make_term_for_display(self, term, visible=True): """ - 読みを1文字表示->表示位置に注意して結合する - 読みを表示しない->読みを消すだけ """ cfg_sep = self.config.html_ja_term_separator #読みと分割する parts = re.split(cfg_sep, term) + [None] #読みがないなら文字列をそのまま帰す if parts[1] is None: return term #1文字表示が設定されていなければ読みを除いて返す if not visible or not self.config.html_ja_term_with_one_kana: return parts[1] #前処理 cfg_sep = re.sub(r'^\\', '', cfg_sep) #html_ja_term_kana_on_2ndの設定に応じて表示位置を決めて返す if self.config.html_ja_term_kana_on_2nd: return parts[1]+cfg_sep+parts[0][:1] else: return parts[0][:1]+cfg_sep+parts[1] def _create_index(self): #KaKkou """ この機能の肝の箇所 索引ページの表示内容を調整して、「読み|」を消す。 1. IndexEntries(self.env).create_index()に並び順を整理してもらう。 2. 出来上がったentriesの中身を見て、必要な箇所の文字列を変更する。 """ #ソートの前処理。ソートの都合に合わせて文字列を入れ替える domain = cast(IndexDomain, self.env.get_domain('index')) for fn, entries in domain.entries.items(): #index, glossaryの指定がなければ何もしない if not entries: continue #各用語の、読み仮名と表記の前後を交換 new_entries = [] for type, value, tid, main, index_key in entries: index_key = self._swap_parts(index_key) value = self._swap_parts_on_each_term(value) new_entries.extend([(type, value, tid, main, index_key)]) #加工したデータを元の辞書に入れ直す domain.entries[fn] = new_entries #self.write_index()にあったソート処理を此処に置く entries = IndexEntries(self.env).create_index(self) #ソートの後処理。表示文字を加工して出力処理に渡す rtn = [] for key, index in entries: key = self._make_term_for_display(key,False) elm = [] for word, arr in index: #用語(主)の処理 word = self._make_term_for_display(word) #用語(副)の処理 if arr[1]: elm1 = [] for word1,arr1 in arr[1]: #用語(副)は次の3パターンがある。 #- 1文字(用語) #- 2文字(用語1 用語2) カンマなし #- 2文字(用語1, 用語2) カンマあり cfg_sep = self.config.html_ja_term_separator regex = r'(^.*?'+cfg_sep+r'|[^ ]+?\|)' word1 = re.sub(regex,'',word1) #詳細はindexentries.pyのadd_entryの実行箇所を参照 #tripleの表示仕様を変更 #see: indexentries.py, IndexEntries.create_index if self.config.html_change_triple: #(f'{3rd}, {1st}' to f'{1st}, {3rd}') parts = re.split(', ',word1)+[None] if parts[1]: word1 = parts[1]+', '+parts[0] elm1.extend([(word1,arr1),]) arr[1] = elm1 elm.extend([(word,arr),]) rtn.extend([[key,elm],]) return rtn def setup(app): """索引、用語の日本語対応 作成したBuilderの登録と、このBuilder利用する設定の登録。 add_builder ----------- index, glossaryディレクティブと関係するSphinx拡張だが、 HTML出力の振る舞いの変更のためapp.add_directive(...)は不要。 使い方 ------ .. code-block:: sh make kana 設定 ---- - html_ja_term_separator, 読みと用語を区切る文字 - html_ja_term_with_one_kana, 1文字だけ読みを表示 - html_ja_term_kana_on_ruby, 読みをルビとして表示(glossaryのみ) - html_ja_term_kana_on_2nd, 「用語|読み」の形式で書く(デフォルトは「読み|用語」) - html_change_triple, tripbleの表示仕様のちょっとした変更 1文字のみ表示 -------------- 1文字表示(html_ja_term_with_one_kana)は、主用語のみ。 モチベーションが高まって実装したけど、あまりイケていない。 バージョン表記 -------------- x.y.MMDDYY """ #HTML出力の変更 #'html' Builerが登録済みなのでこれを入れ替える。 app.add_builder(JHTMLBuilder) #設定の登録 app.add_config_value('html_ja_term_separator', r'\|', 'html') app.add_config_value('html_ja_term_kana_on_ruby', False, 'html') app.add_config_value('html_ja_term_kana_on_2nd', False, 'html') app.add_config_value('html_ja_term_with_one_kana', False, 'html') app.add_config_value('html_change_triple', False, 'html') #バージョンの最後は作成日(MMDDYY) return { 'version': '0.1.091421', 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Sphinx】索引(index)の日本語対応。用語(glossary)も日本語対応。

1.はじめに 主に高校の数学と物理の復習を兼ねて、Sphinxで内容をまとめつつ学習しています。matplotlibディレクティブやmathディレクティブを使って楽しんでいます。 まとめた内容を横断的に俯瞰するためにindex, glossaryディレクティブが便利なんですが、例えば「微分」を例に取ると、索引ページ冒頭に「び」「ひ」と表示されて欲しいですよね。これが「微」と表示されてよろしくない。これをどうにかしたかった。 関連記事 【Sphinx】yogosyu(用語集)をSphinx4.1.2で使えるように修正する 【Sphinx】indexディレクティブからjindexディレクティブを作る 先ずは検索。「Sphinx 索引 日本語」と検索しました。 索引に載せたい | Sphinx-Users.jp 「Yogosyu」というSphinx拡張を知りますが、使おうと思うと最新のSphinx4.1.2では対応していません。ひとまずエラーなく使えるようにしました。が、索引ページは期待通りに表示されません。アレコレ調べている間に実装アイデアを思い付き、自分なりに満足できる動作になりました。これをSphinx拡張として使えるようにしたのが、当記事の最後にあるPythonのコードです。 ※Yogosyu以外に先達者はいないと思っていますが、いたら教えてください。 ※先達者を確認した場合は、当記事先頭に「後日談」を追記して報告します。 2.実装アイデア ポイントは次の2点。データ構造を変更することなく改良できたのが幸運。 例えば「びぶんせきぶん|微分積分」のように、「|」で区切って読みを前方に付ける。 索引ページと用語のところの表示で、出力処理の直前に「読み|」を消す。 表示処理を実行してる場所を特定するのに苦労しました。索引ページはegrepの力技で見つけられましたが、用語の方は一度sphinx-buildの起動から追いかけましたが途中で挫折。ブラウザーの開発者ツールで用語近くに「Permalink to this term」という文字列があることに気づき、ここからsphinx/writers/html5.pyにあることがわかりました。 3.環境 Windows10/cygwin64 pip 21.2.4 Sphinx 4.2.0 Sphinx 4.1.2 で作りましたが、さっき pip --upgrade sphinx したらアップグレードしてくれやがりました。 4.2.0でもエラーなくmakeができることを確認しました。 4.1.2より古いバージョンで動くかどうか確認していません。恐らくですが、同じrstファイルに対して、make html, make kana がエラーなく実行され、diff -r build/html build/kana に差異がなければ問題ないと思います。 4.使い方 個人的に「無難な落とし所」だと考えているのは次の通り 設定は html_change_triple = True とする。他はデフォルト。 読みは「ひ|微分積分」と、濁点なしで一文字を付与。 「pair」が結構強力です。数学なら「pair: ひ|微分積分; さ|三角関数」と索引を作ると効果がわかります。一つの「.. index::」に複数の「single/pair/triple/see/seealso」が指定できます。 glossaryでは「用語 : 分類名」と記載することで、索引ページでは同じ分類名でまとめることができます。索引ページを「上段、本体、下段」に分けて、上段でアピールしたい用語、本体からは外して下段にこっそり置きたい用語について利用を検討してください。分類名にも読みが設定できるので、これを利用して表示順の調整に使うことができます。(Sphinxでは「実験的な扱い」と説明していますが、その理由と思われるものをFAQに記載しました。) その他、親切ではありませんが、docstringに書いてあります。reST/Google/NumPyの記法は勉強中だし、パラメーターの説明は特にアレなので、、優しい気持ちでお願いします。 5.セットアップ 当ページ下部にあるソースコードを右上のアイコンをクリックしてコピー 利用環境の適当な場所に sphinx_ja_term.py として作成。 conf.pyに設定追加 make kana の実行。 make html ではないので注意。 ソースを見れば変更箇所はわかります。 合わせてadd_builderの第二引数に True を設定してください。 conf.pyの設定(参考例) import sys # sys.path.insert(0, os.path.abspath('.')) sys.path.insert(0, '_ext') extensions = [ 'sphinx_ja_term', ] #html_ja_term_separator = r'\|' #html_ja_term_with_one_kana = False #html_ja_term_kana_on_ruby = False #html_ja_term_kana_on_2nd = False html_change_triple = True 6.FAQ Q: 読みが消えません。 A: make html ではなく、 make kana と実行してください。 name = 'kana' と add_builder()''' の箇所を直すだけ。それぞれでHTMLを出力させて比較ができます。慣れてきたらhtml``` としてもいいでしょう。 Q: glossary, indexを併用すると、索引ページの用語が期待通りに表示されないことがある。 A: make clean; make kana を実行してください。 結構悩んだ挙動です。glossaryとindexで同じ用語がある場合集約するのですが、内部での処理順で表示が決まるので、これの影響を受けています。 原則として「glossary -> index」の順に処理するので、これを前提に作ればいいのですが、正確には「.toctreeファイルに保存してるデータ -> glossary -> index」が処理順なので(憶測)、glossaryだけ make kana すると用語同士の依存関係が逆になります。部分的に依存関係が変わるので余計混乱します。 glossary で、「用語 : 分類名」を使う時は注意してください。 Q: 同じ「微分積分」が別々に表示される。 A: 読みを見直してください。 表記上は同じ「微分積分」でも、「ひ|微分積分」と「びぶんせきぶん|微分積分」は異なる用語として扱います。逆にこれを利用して、「微分積分」の項目が増えたときに「ひ0|微分積分」「ひ1|微分積分」と意図的に分けることが可能です。 他は「tripleのつもりで書いたのに、pairを指定している」とか、気付くのは難しくありません。 Q: 不具合の報告について A: sphinx_ja_term とタグを付けて質問を投稿してください。 Sphinxcontribに登録していればshpinxcontorib.ja_termとするところですが、やり方が分かりません。docstringを英語に書き直すのもアレなので。。 不具合修正レベルなら直すつもりです。質問にテストケースがあると喜びます。 仕様変更レベルなら、FAQで対応するつもりです。 7.注意事項 ライセンスは、Qiitaの規約、参考にしたSphinxソースコードのライセンスに違反しない範囲でBSDライセンス準拠とします。 正直、ライセンスについては正しく理解しているとは言い難いのですが、趣旨としては「Qiita、Sphinx、作成者に迷惑を掛けなけば、商用利用も含めて自由に使っていいよ。」とご理解ください。 とは言え、使い心地などの感想を投稿して、当記事にリンクを貼ってくれたりすると喜びます。利用素材として使われるよりも学習素材として記事にして欲しいかも。批判もOKですが豆腐メンタルなので、コメントは好意的なものや優しい奴でお願いします。ご自身の記事でアレコレ書く分には、リンクが貼ってあっても気にしません。(まあ気にはしますけど、執筆者を尊重します。コメントでなければ返事とか不要だし。) ※「sphinxcontribに登録したい」という場合はご一報ください。できる範囲でサポートさせていただきます。私が主体的に動くことは現状予定していません。 8.雑記 Termノード ルビの表示はvisit_term(), depart_term()でやっていいますが、本来はTermノードが自分をHTML用に出力する時にやって欲しいところです。 Termノードのインスタンス化は nodes.term(引数) ですが、このtermクラスを拡張したクラスを作って、visit_term() で termnode.set_display_mode('html') とすると良いようにしてくれるようにしたい。 depart_term() をなくせるし。 その場合の変更箇所はGlossaryクラス( sphinx/domains/std.py )になります。変更範囲をあまり広げたくもないんですよね…。本件に関連しては、別の機能を実装するつもりでJGlossaryを作ってapp.add_directive()したら反応がなくて諦めた記憶もあるので。 write_genindex()メソッド write_genindex() の最初でデータを作っていますが、このコードだけ別のプライベートメソッドでラップして欲しい。そうすれば、ラップされたプライベートメソッドだけの上書きで済むので。 索引でのルビ対応 テンプレートファイルの差し替えが必要です。捉えずやってみたら、テンプレートエンジンがタグをエンコードしてくれました…。 themeのロジックまで手を広げたくないのですが、プロジェクトの _templates に変更部分だけ放り込んで対処はできるのでしょうか。block単位での継承ができそうなのでできなくはないと読んでいますが、現在モチベーションはありません。(そのための「ご自由にどうぞ」でもありますが) 対応箇所は write_genindex() になります。やっぱり、データのソート処理だけラップしてくれないかな。 9.Pythonソース 実装の肝は次の2箇所 write_genindex でソート処理後にデータを表示用に加工。 visit_term でTermノードの表示前に表示内容を加工。 参照渡しだからできました。値渡しだったらここでは対応できませんでした。 """ index, glossaryの機能を上書きするSphinx拡張 用語の形式を「読み|用語」とし、表示の際に「読み|」を取り除く。 glosssaryについては「ルビ」の表示が可能(デフォルトは非表示)。 詳細 ---- setup()のdocstringを参照 """ import re import unicodedata from typing import (TYPE_CHECKING, Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Type, Union, cast) from docutils import nodes from docutils.nodes import Text from sphinx import addnodes import sphinx.builders.html as builders from sphinx.domains import std from sphinx.domains.index import IndexDomain from sphinx.environment.adapters.indexentries import IndexEntries from sphinx.locale import _ from sphinx.util import logging from sphinx.util.docutils import is_html5_writer_available, SphinxDirective from sphinx.writers import html, html5 from docutils.nodes import Node, system_message from docutils.parsers.rst import directives from docutils.statemachine import StringList if TYPE_CHECKING: from sphinx.application import Sphinx from sphinx.builders import Builder from sphinx.environment import BuildEnvironment logger = logging.getLogger(__name__) # HTML5 Writer is available or not if is_html5_writer_available(): html5_ready = True else: html5_ready = False class _HTMLTranslator: """ このクラスの次のメソッドを上書きする。 それぞれの引数の基本的な仕様はオリジナルに従っている。 """ def _startDT(self,node): #KaKkou #初期設定 self.jterm_ruby = None #KaKkou sp = r' *'+self.config.html_ja_term_separator+r' *' #「用語; 用語」と二つある場合に備える。 parts = re.split(r' *; ',node[0]) + [None] if parts[1]: parts[1] = re.sub(r'^.*'+sp, '', parts[1]) parts[0] = parts[0]+', '+parts[1] try: tm = re.split(sp, parts[0])+[None] #”{読み}|{用語}”or"{用語}" except Exception as e: print(e) else: if tm[1]: if self.config.html_ja_term_kana_on_ruby and tm[0][-1] != '_': self.jterm_ruby = tm[0] node[0] = Text(tm[1]) finally: return self.starttag(node,'dt','') def visit_term(self, node): """用語Node、Visit処理 上書きするメソッド sphinx.writers.html5/vist_term() 1. 受け取ったnodeの表示用文字列から「読み」を分離する 2. html_ja_term_kana_on_rubyの設定に従い、ルビの表示/非表示を判断する 3. ルビを表示すると判断したら、self.jterm_rubyに読みを代入する 目指すべき挙動 -------------- Termノードを拡張したJaTermノードを作り、この中でルビの表示を任せるのが いいように思う。object.astextとは別にソート用文字列を出力するメソッドを 作り使い分けて欲しい欲しいところだけど。 実装アイデア ------------ ojbect.set_display_mode()を作る。 引数が'html'ならrubyタグを使い、'latex'ならそれなりに。 表示用文字とソート用文字の概念が区別される、日本人以外には理解されない だろうから歯痒い。 """ self.body.append(self._startDT(node)) #KaKkou if self.jterm_ruby: #KaKkou self.body.append('<ruby><rb>') def depart_term(self, node): """ 上書きするメソッド sphinx.writers.html5/depart_term() 1. self.jterm_rubyが設定されていれば、rubyタグを出力する。 """ next_node: Node = node.next_node(descend=False, siblings=True) if isinstance(next_node, nodes.classifier): # Leave the end tag to `self.depart_classifier()`, in case # there's a classifier. pass else: if self.jterm_ruby: #KaKkou ruby = f'</rb><rp>(</rp><rt>{self.jterm_ruby}</rt><rp>)<rp></ruby>' self.body.append(ruby) if isinstance(node.parent.parent.parent, addnodes.glossary): # add permalink if glossary terms self.add_permalink_ref(node, _('Permalink to this term')) self.body.append('</dt>') class JHTMLTranslator(_HTMLTranslator, html.HTMLTranslator): def __init__(self, document, builder): self.jterm_ruby = None super().__init__(document, builder) class JHTML5Translator(_HTMLTranslator, html5.HTML5Translator): def __init__(self, document, builder): self.jterm_ruby = None super().__init__(document, builder) class _HTMLBuilder(builders.StandaloneHTMLBuilder): """ オリジナルのwrite_genindex()が次のようにラッパー関数を介して、 IndexEntries(self.env).create_index(self)を呼び出すようにすれば、 このクラスは不要になる。 """ def _create_index(self): #KaKkou """ raiseの代わりに次のコードを有効にすればオリジナルと同じ動作をする。 genindex = IndexEntries(self.env).create_index(self) """ raise NotImplementedError('%s: %s is needed.' % (self.__class__, node.__class__.__name__)) def write_genindex(self) -> None: genindex = self._create_index() #KaKkou. データのソートが終わった直後 #以降の処理はSphinx4.1.2オリジナルと同じ indexcounts = [] for _k, entries in genindex: indexcounts.append(sum(1 + len(subitems) for _, (_, subitems, _) in entries)) genindexcontext = { 'genindexentries': genindex, 'genindexcounts': indexcounts, 'split_index': self.config.html_split_index, } logger.info('genindex ', nonl=True) if self.config.html_split_index: self.handle_page('genindex', genindexcontext, 'genindex-split.html') self.handle_page('genindex-all', genindexcontext, 'genindex.html') for (key, entries), count in zip(genindex, indexcounts): ctx = {'key': key, 'entries': entries, 'count': count, 'genindexentries': genindex} self.handle_page('genindex-' + key, ctx, 'genindex-single.html') else: self.handle_page('genindex', genindexcontext, 'genindex.html') class JHTMLBuilder(_HTMLBuilder): """ 上書きするメソッド sphinx.builder.html.StandaloneHTMLBuildeer/write_genindex() """ name = 'kana' @property def default_translator_class(self) -> Type[nodes.NodeVisitor]: # type: ignore if not html5_ready or self.config.html4_writer: return JHTMLTranslator else: return JHTML5Translator def _swap_parts(self,term): #設定が無効なら何もしない if not self.config.html_ja_term_kana_on_2nd: return term #index_keyはNoneもある if not term: return term #読み仮名がなければ何もしない parts = re.split(self.config.html_ja_term_separator,term)+[None] if not parts[1]: return term #読み仮名と用語の位置を入れ替える sep = re.sub(r'^\\','',self.config.html_ja_term_separator) return parts[1]+sep+parts[0] def _swap_parts_on_each_term(self, line): """ 「用語; 用語; 用語」という形式の各「用語」を処理する """ rtn = "" parts = re.split(r'; +', line) for s in parts: rtn += self._swap_parts(s)+"; " return rtn[:-2] #末尾の"; "を除く def _make_term_for_display(self, term, visible=True): """ - 読みを1文字表示->表示位置に注意して結合する - 読みを表示しない->読みを消すだけ """ cfg_sep = self.config.html_ja_term_separator #読みと分割する parts = re.split(cfg_sep, term) + [None] #読みがないなら文字列をそのまま帰す if parts[1] is None: return term #1文字表示が設定されていなければ読みを除いて返す if not visible or not self.config.html_ja_term_with_one_kana: return parts[1] #前処理 cfg_sep = re.sub(r'^\\', '', cfg_sep) #html_ja_term_kana_on_2ndの設定に応じて表示位置を決めて返す if self.config.html_ja_term_kana_on_2nd: return parts[1]+cfg_sep+parts[0][:1] else: return parts[0][:1]+cfg_sep+parts[1] def _create_index(self): #KaKkou """ この機能の肝の箇所 索引ページの表示内容を調整して、「読み|」を消す。 1. IndexEntries(self.env).create_index()に並び順を整理してもらう。 2. 出来上がったentriesの中身を見て、必要な箇所の文字列を変更する。 """ #ソートの前処理。ソートの都合に合わせて文字列を入れ替える domain = cast(IndexDomain, self.env.get_domain('index')) for fn, entries in domain.entries.items(): #index, glossaryの指定がなければ何もしない if not entries: continue #各用語の、読み仮名と表記の前後を交換 new_entries = [] for type, value, tid, main, index_key in entries: index_key = self._swap_parts(index_key) value = self._swap_parts_on_each_term(value) new_entries.extend([(type, value, tid, main, index_key)]) #加工したデータを元の辞書に入れ直す domain.entries[fn] = new_entries #self.write_index()にあったソート処理を此処に置く entries = IndexEntries(self.env).create_index(self) #ソートの後処理。表示文字を加工して出力処理に渡す rtn = [] for key, index in entries: key = self._make_term_for_display(key,False) elm = [] for word, arr in index: #用語(主)の処理 word = self._make_term_for_display(word) #用語(副)の処理 if arr[1]: elm1 = [] for word1,arr1 in arr[1]: #用語(副)は次の3パターンがある。 #- 1文字(用語) #- 2文字(用語1 用語2) カンマなし #- 2文字(用語1, 用語2) カンマあり cfg_sep = self.config.html_ja_term_separator regex = r'(^.*?'+cfg_sep+r'|[^ ]+?\|)' word1 = re.sub(regex,'',word1) #詳細はindexentries.pyのadd_entryの実行箇所を参照 #tripleの表示仕様を変更 #see: indexentries.py, IndexEntries.create_index if self.config.html_change_triple: #(f'{3rd}, {1st}' to f'{1st}, {3rd}') parts = re.split(', ',word1)+[None] if parts[1]: word1 = parts[1]+', '+parts[0] elm1.extend([(word1,arr1),]) arr[1] = elm1 elm.extend([(word,arr),]) rtn.extend([[key,elm],]) return rtn def setup(app): """索引、用語の日本語対応 作成したBuilderの登録と、このBuilder利用する設定の登録。 add_builder ----------- index, glossaryディレクティブと関係するSphinx拡張だが、 HTML出力の振る舞いの変更のためapp.add_directive(...)は不要。 使い方 ------ .. code-block:: sh make kana 設定 ---- - html_ja_term_separator, 読みと用語を区切る文字 - html_ja_term_with_one_kana, 1文字だけ読みを表示 - html_ja_term_kana_on_ruby, 読みをルビとして表示(glossaryのみ) - html_ja_term_kana_on_2nd, 「用語|読み」の形式で書く(デフォルトは「読み|用語」) - html_change_triple, tripbleの表示仕様のちょっとした変更 1文字のみ表示 -------------- 1文字表示(html_ja_term_with_one_kana)は、主用語のみ。 モチベーションが高まって実装したけど、あまりイケていない。 バージョン表記 -------------- x.y.MMDDYY """ #HTML出力の変更 #'html' Builerが登録済みなのでこれを入れ替える。 app.add_builder(JHTMLBuilder) #設定の登録 app.add_config_value('html_ja_term_separator', r'\|', 'html') app.add_config_value('html_ja_term_kana_on_ruby', False, 'html') app.add_config_value('html_ja_term_kana_on_2nd', False, 'html') app.add_config_value('html_ja_term_with_one_kana', False, 'html') app.add_config_value('html_change_triple', False, 'html') #バージョンの最後は作成日(MMDDYY) return { 'version': '0.1.091421', 'env_version': 2, 'parallel_read_safe': True, 'parallel_write_safe': True, } 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 216 バーチャル参戦記

AtCoder Beginner Contest 216 バーチャル参戦記 58:40でABCDE完. 参加できてたらパフォ1486だった模様. レーティング上がってたな、惜しい. ABC216A - Signed Difficulty 3分で突破. 書くだけ. X, Y = map(int, open(0).read().split('.')) if 0 <= Y <= 2: print('%d-' % X) elif 3 <= Y <= 6: print('%d' % X) elif 7 <= Y <= 9: print('%d+' % X) ABC216B - Same Name 3分半で突破. ダブりチェックと言えばセット. N = int(input()) t = set() for _ in range(N): S, T = input().split() t.add(S + ' ' + T) if len(t) != N: print('Yes') else: print('No') ABC216C - Many Balls 5分で突破. 見た瞬間に逆向きにやれば最短が作れると思いました. なので、逆向きにやって、反転させて答えとして出力しておしまい. N = int(input()) a = [] while N != 0: if N % 2 == 0: a.append('B') N //= 2 else: a.append('A') N -= 1 print(*reversed(a), sep='') ABC216D - Pair of Balls 15分半で突破. ナイーブに書くと TLE するのは火を見るよりも明らか. 新しい色は今取り出して捨てた筒からしかでてこないということを大事にし、その2つの筒から新しく先頭になったボールと、一致する先頭のボールがあるのであればその筒番号が O(1) で判明するように実装しました. from sys import stdin from collections import deque readline = stdin.readline N, M = map(int, readline().split()) a = [None] * M for i in range(M): k = int(readline()) a[i] = deque(map(int, readline().split())) q = deque(range(M)) t = {} while q: i = q.popleft() if len(a[i]) == 0: continue x = a[i].popleft() if x not in t: t[x] = i continue q.append(i) q.append(t[x]) del t[x] if all(len(d) == 0 for d in a): print('Yes') else: print('No') ABC216E - Amusement Park 31分半で突破. 素直に一番満足度が高いアトラクションに乗って、満足度を1下げるのループをシミュレーションするのは K≤2×109 から TLE するのは明らか. ざっくりとどの満足度まで下がるまで乗り倒せばいいかをにぶたんで求めて、余った部分は素直にシミュレーションすれば TLE しないだろうというあまり厳密ではない考察をして、書いて出したら通った. なお、最初は必ずK回乗る実装になっていて、入出力例2に救われた. 危ない. from heapq import heappop, heappush N, K, *A = map(int, open(0).read().split()) def is_ok(n): result = 0 for a in A: if a <= n: continue result += a - n return result <= K ok = 10 ** 10 ng = 0 while ok - ng > 1: m = ng + (ok - ng) // 2 if is_ok(m): ok = m else: ng = m result = 0 n = 0 t = [] for a in A: if a <= ok: heappush(t, -a) continue heappush(t, -ok) result += a * (a + 1) // 2 - ok * (ok + 1) // 2 n += a - ok for _ in range(K - n): x = -heappop(t) if x < 0: break result += x heappush(t, -(x - 1)) print(result)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで結晶を描画するプログラムを作る~CIFデータの読み方と単位格子編~

投稿日:2021/09/14 はじめに VESTAなどの高性能なツールにより、後述するCIFデータさえ手に入れば空間群などの理解なしに材料の結晶構造が描画できてしまいます。本記事ではその便利さを一旦捨てて、ゼロから結晶を描画する事を目指します。 1. CIFデータ CIFとはCrystallographic Information Fileの略でWPCI(Working Party on Crystallographic Information)が主導となって開発された結晶構造データを共有するデータフォーマットです。このCIFには基本的に以下の6つから構成されています[1]。 1. データ公表者および出版情報 2. 基礎結晶学的情報 3. 実験・解析方法の詳細 4. 得られた結晶データおよび結晶化学的情報 5. 解析時に使用したパラメータファイル 6. 解析に用いられたデータ本体 今すぐCIFデータを入手したいときはAtomWork (https://crystdb.nims.go.jp/index.html )かMaterials Project (https://materialsproject.org/ )で探すと良いかと思います。本記事ではMaterials Projectで公開されているSrTiO$_3$ (https://materialsproject.org/materials/mp-5229/ )を使っていきます。このファイルは拡張子が".cif"になっていますが、メモ帳などで開くと中身が確認できます。このファイルを一緒に読み解いて行きましょう。 data_SrTiO3 これはこの一文からSrTiO$_3$の結晶構造データですよーっていういわゆる宣言文になっています。これは複数のCIFデータを1つのファイルにまとめたときにその区別用として威力を発揮します。 _symmetry_space_group_name_H-M 'Pm-3m' _symmetry_Int_Tables_number 221 "_symmetry_space_group_name_H-M"はこの結晶が属す空間群をHermann-Mauguin記号(以降, H-M記号)で表しており、この結晶は$Pm\bar{3}m$という空間群に属している事を意味します。結晶を扱う上で重要な230種の空間群はナンバリングされており、$Pm\bar{3}m$は$221$という数字が与えられています。"_symmetry_Int_Tables_number"はこの番号を表しています。WikipediaのList of space groups (https://en.wikipedia.org/wiki/List_of_space_groups )をみると番号または空間群がら検索をかける事ができます。ここで、当然の疑問として浮かび上がるのが、「何故H-M記号と番号をどちらも載せるのか?」だと思います。実は理由があるのですが、次回または次々回あたりで触れたいと思います。 _cell_length_a 3.94513000 _cell_length_b 3.94513000 _cell_length_c 3.94513000 _cell_angle_alpha 90.00000000 _cell_angle_beta 90.00000000 _cell_angle_gamma 90.00000000 これらは結晶パラメータと呼ばれ、それぞれ、$a$,$b$,$c$軸の格子定数($Å$)、$b-c$,$c-a$,$a-b$軸の挟角(degree)を表します。他にも説明したいところがあるのですが、これ以上は本記事で取り扱いたい部分から離れていくので以降の記事で適宜説明していきます。 2. 単位格子 3次元結晶の単位となる平行六面体を単位格子 (unit cell) といい、結晶を分類する上でなるべく高い対称性をもつものの中で最小の体積を持つ平行六面体から選ばれます。CIFデータではこの単位格子の情報が結晶パラメータ$a,b,c,\alpha,\beta,\gamma$として保存されています。これらの関係を7つの分類に整理したものを晶系 (crystal system) と呼び、以下の表のように分類されます(Markdown形式の表作成になれたら直します)。 $a\neq b\neq c$ $a= b,\neq c$ $a= b=c$ $\alpha\neq\beta\neq\gamma$ 三斜晶 (triclinic;a) 三斜晶 (triclinic;a) 三斜晶 (triclinic;a) $\alpha =\gamma =90^\circ \neq \beta$ 単斜晶 (monoclinic;m) 単斜晶 (monoclinic;m) 単斜晶 (monoclinic;m) $\alpha =\beta=90^\circ, \gamma=120^\circ $ 単斜晶 (monoclinic;m) 六方晶 (hexagonal;h)、三方晶 (trigonal;r) 六方晶 (hexagonal;h)、三方晶 (Rhombohedral;r) $\alpha =\beta =\gamma =90^\circ$ 直方晶 (orthorhombic;o) 正方晶 (tetragonal;t) 立方晶 (cubic;c) SrTiO$_3$は$a=b=c,\alpha =\beta =\gamma =90^\circ $であるため、立方晶に分類されます。CIFデータによってはこの晶系は以下のように書かれています。 _space_group_crystal_system cubic 単位格子を描くためには結晶パラメータのみで良く、単位格子頂点の位置ベクトル$\boldsymbol{A},\boldsymbol{B},\boldsymbol{C}$と置いたとき以下の式で表されます。 \boldsymbol{A}=\left(\begin{array}{c} a\\ 0\\ 0 \end{array}\right), \boldsymbol{B}=\left(\begin{array}{c} b\cos{\gamma}\\ b\sin{\gamma}\\ 0 \end{array}\right),\boldsymbol{C}=\left(\begin{array}{c} c\cos{\beta}\\ \frac{c(\cos{\alpha}-\cos\beta\cos\gamma)}{\sin{\gamma}}\\ c\sqrt{\sin^2\beta - \frac{(\cos{\alpha}-\cos\beta\cos\gamma)^2}{\sin^2\gamma}} \end{array}\right) \tag{1} ここで、定数$n_1,n_2,n_3$で結びつけたベクトル$\boldsymbol{R}$: \boldsymbol{R}:=n_1 \boldsymbol{A}+ n_2 \boldsymbol{B}+ n_3\boldsymbol{C}=(n_1,n_2,n_3)\tag{2} を考えてみます。$n_1,n_2,n_3$を$0$または$1$に限ってみると、$(n_1,n_2,n_3)$は$2^3=8$通りある事がわかります。$\boldsymbol{A},\boldsymbol{B},\boldsymbol{C}$は単位格子の位置を表している事を思い出すとこの8パターンは平行六面体である単位格子の各頂点に対応している事がわかります。 3. Pythonで単位格子を作成する 今から作成するコードの流れは入力として結晶パラメータを受け取った後、(i) 結晶パラメータの角度の単位をdegreeからradianに変換、(ii) (1)式に結晶パラメータを代入、(iii) (2)式から単位格子の頂点を導出、(iv)3次元で図示です。 import math import numpy as np import itertools import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # 関数 def deg2rad(deg): """ deg→rad """ return deg*(math.pi/180) def vecA(a): """ 位置ベクトルA """ Ax = a Ay = 0 Az = 0 return np.array([Ax,Ay,Az]) def vecB(b,gamma): """ 位置ベクトルB """ gamma = deg2rad(gamma) Bx = b*math.cos(gamma) By = b*math.sin(gamma) Bz = 0 return np.array([Bx,By,Bz]) def vecC(c,alpha,beta,gamma): """ 位置ベクトルC """ alpha,beta,gamma = deg2rad(alpha),deg2rad(beta),deg2rad(gamma) Cx = c*math.cos(beta) Cy = c*(math.cos(alpha)-math.cos(beta)*math.cos(gamma)) Cz = c*math.sqrt((math.sin(beta))**2-(math.cos(alpha)-math.cos(beta)*math.cos(gamma))**2/(math.sin(gamma))**2) return np.array([Cx,Cy,Cz]) def unitCell(idx,a,b,c,alpha,beta,gamma): """ idx=(n1,n2,n3)から頂点位置(x,y,z)を算出 """ Ax,Ay,Az = int(idx[0])*vecA(a) # idxはstrなのでintに変換 Bx,By,Bz = int(idx[1])*vecB(b,gamma) Cx,Cy,Cz = int(idx[2])*vecC(c,alpha,beta,gamma) x = Ax + Bx + Cx # x座標 y = Ay + By + Cy # y座標 z = Az + Bz + Cz # z座標 return x,y,z def vtxPos(a,b,c,alpha,beta,gamma): """ 単位格子の頂点の位置を計算 """ val = ['0','1'] # n1,n2,n3がとりうる値 X = np.zeros(8) Y = np.zeros(8) Z = np.zeros(8) for i,idx in enumerate(itertools.product(val,repeat=3)): x,y,z = unitCell(idx,a,b,c,alpha,beta,gamma) X[i],Y[i],Z[i] = x,y,z return X,Y,Z # SrTiO3の結晶パラメータ a = 3.94513 b = 3.94513 c = 3.94513 alpha = 90 beta = 90 gamma = 90 # 頂点の計算 X,Y,Z = vtxPos(a,b,c,alpha,beta,gamma) # 図示 fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111,projection='3d') ax.scatter(X,Y,Z,s=40,c='b') ax.set_xlabel('x',size=14) ax.set_ylabel('y',size=14) ax.set_zlabel('z',size=14) plt.show() # plt.savefig('SrTiO3_unitCell.png',transparent=True,dpi=300) 上記のプログラムを実行すると以下の図が得られます。 これを平行六面体としてプロットされるようにプログラムを書き直していきたいと思います。平行六面体を連結グラフ(頂点は ノード、辺はエッジに対応)としてみたとき、1つのノードに対して3つのエッジがあるグラフである事がわかります。つまり、オイラーの定理より、オイラー・グラフ (Eulerian graph) ではない事が言えます。これより、"ax.plot"による"1筆書き"で平行六面体を描く事ができません。従って、泥臭く1つのグラフに辺を加えていくような処理で対処する必要がありそうです(楽な方法をご存じの方はご教示ください)。vtxPos関数は使わずに12本分の辺を作っていきます。 def vtxMerge(idx1,idx2): """ idx1=(n11,n12,n13)とidx2=(n21,n22,n23)の頂点の座標を持つ配列を生成 """ X = np.zeros(2) Y = np.zeros(2) Z = np.zeros(2) X[0],Y[0],Z[0] = unitCell(idx1,a,b,c,alpha,beta,gamma) X[1],Y[1],Z[1] = unitCell(idx2,a,b,c,alpha,beta,gamma) return X,Y,Z # 平行六面体の頂点 vtx1 = ('0', '0', '0') vtx2 = ('0', '0', '1') vtx3 = ('0', '1', '0') vtx4 = ('0', '1', '1') vtx5 = ('1', '0', '0') vtx6 = ('1', '0', '1') vtx7 = ('1', '1', '0') vtx8 = ('1', '1', '1') # 平行六面体の辺 X1,Y1,Z1 = vtxMerge(vtx1,vtx2) X2,Y2,Z2 = vtxMerge(vtx1,vtx3) X3,Y3,Z3 = vtxMerge(vtx1,vtx5) X4,Y4,Z4 = vtxMerge(vtx4,vtx2) X5,Y5,Z5 = vtxMerge(vtx4,vtx3) X6,Y6,Z6 = vtxMerge(vtx4,vtx8) X7,Y7,Z7 = vtxMerge(vtx6,vtx2) X8,Y8,Z8 = vtxMerge(vtx6,vtx5) X9,Y9,Z9 = vtxMerge(vtx6,vtx8) X10,Y10,Z10 = vtxMerge(vtx7,vtx3) X11,Y11,Z11 = vtxMerge(vtx7,vtx5) X12,Y12,Z12 = vtxMerge(vtx7,vtx8) # プロット fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111,projection='3d') ax.plot(X1,Y1,Z1,c='b') ax.plot(X2,Y2,Z2,c='b') ax.plot(X3,Y3,Z3,c='b') ax.plot(X4,Y4,Z4,c='b') ax.plot(X5,Y5,Z5,c='b') ax.plot(X6,Y6,Z6,c='b') ax.plot(X7,Y7,Z7,c='b') ax.plot(X8,Y8,Z8,c='b') ax.plot(X9,Y9,Z9,c='b') ax.plot(X10,Y10,Z10,c='b') ax.plot(X11,Y11,Z11,c='b') ax.plot(X12,Y12,Z12,c='b') ax.set_xlabel('x',size=14) ax.set_ylabel('y',size=14) ax.set_zlabel('z',size=14) plt.show() # plt.savefig('SrTiO3_unitCell.png',transparent=True,dpi=300) 上記のプログラムを実行すると以下の図が得られます。ここでは図示してもあまり面白くない立方晶を取り扱っていますが、色々パラメータをいじって単位格子を眺めてみてください。 引用文献 [1] 松下能孝, J. Surface Analysis, 21, 71 (2014). (ネットで公開)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで結晶を描画するプログラムを作る~単位格子を描く~

投稿日:2021/09/14 更新日:2021/09/14 (単位格子の体積の導出式の追加、その他文章の修正) はじめに VESTAなどの高性能なツールにより、後述するCIFデータさえ手に入れば空間群などの理解なしに材料の結晶構造が描画できてしまいます。その便利さを一旦捨てて、ゼロから結晶を描画する事を目指します。本記事はその第一回目です。 1. CIFデータ CIFとはCrystallographic Information Fileの略でWPCI(Working Party on Crystallographic Information)が主導となって開発された結晶構造データを共有するデータフォーマットです。このCIFは基本的に以下の6つから構成されています[1]。 データ公表者および出版情報 基礎結晶学的情報 実験・解析方法の詳細 得られた結晶データおよび結晶化学的情報 解析時に使用したパラメータファイル 解析に用いられたデータ本体 "基本的に"という言葉を付けたのには訳があって、データの提供主を示す1や解析に必要な2や4,6以外は記述されていない事があります。今すぐCIFデータを入手したいときはAtomWork (https://crystdb.nims.go.jp/index.html )かMaterials Project (https://materialsproject.org/ )で探すと良いかと思います。本記事ではMaterials Projectで公開されているSrTiO$_3$ (https://materialsproject.org/materials/mp-5229/ )を使っていきます。このファイルは拡張子が".cif"になっていますが、メモ帳などで開くと中身が確認できます。このファイルを読み解いて行きます。 下記はここからSrTiO$_3$の結晶構造データですという宣言文になっています。これは複数のCIFデータを1つのファイルにまとめたときにその区別用として威力を発揮します。 data_SrTiO3 下記はSrTiO$_3$が所属する空間群("_symmetry_space_group_name_H-M")とその番号("_symmetry_Int_Tables_number")を表しています。"_symmetry_space_group_name_H-M"はこの結晶が属す空間群をHermann-Mauguin記号(以降, H-M記号)で表しており、この結晶は$Pm\bar{3}m$という空間群に属している事を意味します。結晶を扱う上で重要な230種の空間群はナンバリングされており、$Pm\bar{3}m$は$221$という数字が与えられています。空間群と番号の対応はWikipediaのList of space groups (https://en.wikipedia.org/wiki/List_of_space_groups )を確認してみて下さい。ここで、当然の疑問として浮かび上がるのが、「何故H-M記号と番号をどちらも載せるのか?」だと思います。明確な理由があるので、次回または次々回あたりで触れたいと思います。 _symmetry_space_group_name_H-M 'Pm-3m' _symmetry_Int_Tables_number 221 下記の部分は結晶パラメータと呼ばれ、単位格子を構成する重要な6つのパラメータです。それぞれ、$a$,$b$,$c$軸の格子定数($Å$)、$b$-$c$,$c$-$a$,$a$-$b$軸の挟角(degree)を表します。 _cell_length_a 3.94513000 _cell_length_b 3.94513000 _cell_length_c 3.94513000 _cell_angle_alpha 90.00000000 _cell_angle_beta 90.00000000 _cell_angle_gamma 90.00000000 他の部分についても触れたい所ですが、本記事の目標である単位格子の描画からずれてしまうので割愛します。別の記事で適宜取り扱っていきます。 2. 単位格子 結晶の単位となる平行六面体を単位格子 (unit cell) といい、結晶を分類する上でなるべく高い対称性をもつものの中で最小の体積を持つ平行六面体から選ばれます。CIFデータではこの単位格子の情報が結晶パラメータ$a,b,c,\alpha,\beta,\gamma$として保存されています。このパラメータを7つの分類に整理したものを晶系 (crystal system) と呼び、以下の表のように分類されます(Markdown形式の表作成になれたら直します)。 $a\neq b\neq c$ $a= b,\neq c$ $a= b=c$ $\alpha\neq\beta\neq\gamma$ 三斜晶 (triclinic;a) 三斜晶 (triclinic;a) 三斜晶 (triclinic;a) $\alpha =\gamma =90^\circ \neq \beta$ 単斜晶 (monoclinic;m) 単斜晶 (monoclinic;m) 単斜晶 (monoclinic;m) $\alpha =\beta=90^\circ, \gamma=120^\circ $ 単斜晶 (monoclinic;m) 六方晶 (hexagonal;h)、三方晶 (trigonal;r) 六方晶 (hexagonal;h)、三方晶 (Rhombohedral;r) $\alpha =\beta =\gamma =90^\circ$ 直方晶 (orthorhombic;o) 正方晶 (tetragonal;t) 立方晶 (cubic;c) この表に従ってSrTiO$_3$の結晶パラメータを見ると、$a=b=c,\alpha =\beta =\gamma =90^\circ $であるため、立方晶に分類される事がわかります。この晶系はCIFデータにおける"_space_group_crystal_system"に記述されます。このCIFデータにはありませんが、"_space_group_crystal_system"の項目がある場合は下記のように記述されます。 _space_group_crystal_system cubic 単位格子を描くためには結晶パラメータのみで良く、単位格子頂点の位置ベクトル$\boldsymbol{A},\boldsymbol{B},\boldsymbol{C}$と置いたとき以下の式で表されます。 \begin{eqnarray} \boldsymbol{A}&=&\left(\begin{array}{c} A_{x}\\ A_{y}\\ A_{z} \end{array}\right)=\left(\begin{array}{c} a\\ 0\\ 0 \end{array}\right), \\ \boldsymbol{B}&=&\left(\begin{array}{c} B_{x}\\ B_{y}\\ B_{z} \end{array}\right)=\left(\begin{array}{c} b\cos{\gamma}\\ b\sin{\gamma}\\ 0 \end{array}\right),\tag{1}\\ \boldsymbol{C}&=&\left(\begin{array}{c} C_{x}\\ C_{y}\\ C_{z} \end{array}\right)=\left(\begin{array}{c} c\cos{\beta}\\ \frac{c(\cos{\alpha}-\cos\beta\cos\gamma)}{\sin{\gamma}}\\ c\sqrt{\sin^2\beta - \frac{(\cos{\alpha}-\cos\beta\cos\gamma)^2}{\sin^2\gamma}} \end{array}\right) \end{eqnarray} ここで、定数$n_1,n_2,n_3$で結びつけたベクトル$\boldsymbol{R}$: \boldsymbol{R}:=n_1 \boldsymbol{A}+ n_2 \boldsymbol{B}+ n_3\boldsymbol{C}=(n_1,n_2,n_3)\tag{2} を考えてみます。$n_1,n_2,n_3$を$0$または$1$に限ってみると、$(n_1,n_2,n_3)$は$2^3=8$通りある事がわかります。$\boldsymbol{A},\boldsymbol{B},\boldsymbol{C}$は単位格子の位置を表している事を思い出すとこの8パターンは平行六面体である単位格子の各頂点に対応している事がわかります。 余談ですが単位格子の体積$V$は$\boldsymbol{A},\boldsymbol{B},\boldsymbol{C}$のスカラー三重積を利用すれば、 \begin{eqnarray} V&=& \boldsymbol{A}\cdot(\boldsymbol{B}\times\boldsymbol{C})\\ &=& A_{x}B_{y}C_{z}\\ &=& abc\sqrt{\sin^2\beta\sin^2\gamma -(\cos\alpha-\cos\beta\cos\gamma)^2} \end{eqnarray} と求まります。ここで、右辺1式から2式目への変換は$\boldsymbol{A}\cdot(\boldsymbol{B}\times\boldsymbol{C})$が上三角行列の行列式になっている事から対角成分の積のみを考えれば良いという関係を使っています。CIFデータから記述子などを生成する際に使えそうな式だと考える人がいるかと思いますが、"_cell_volume"に書いてあるため使う機会はありません。 3. Pythonで単位格子を作成する 今から作成するコードの流れは結晶パラメータを受け取った後、(i) 結晶パラメータの角度の単位を度数(degree)からラジアン(radian)に変換、(ii) (1)式に結晶パラメータを代入、(iii) (2)式から単位格子の頂点を導出、(iv)3次元で図示です。 import math import numpy as np import itertools import matplotlib.pyplot as plt from mpl_toolkits.mplot3d import Axes3D # 関数 def deg2rad(deg): """ deg→rad """ return deg*(math.pi/180) def vecA(a): """ 位置ベクトルA """ Ax = a Ay = 0 Az = 0 return np.array([Ax,Ay,Az]) def vecB(b,gamma): """ 位置ベクトルB """ gamma = deg2rad(gamma) Bx = b*math.cos(gamma) By = b*math.sin(gamma) Bz = 0 return np.array([Bx,By,Bz]) def vecC(c,alpha,beta,gamma): """ 位置ベクトルC """ alpha,beta,gamma = deg2rad(alpha),deg2rad(beta),deg2rad(gamma) Cx = c*math.cos(beta) Cy = c*(math.cos(alpha)-math.cos(beta)*math.cos(gamma)) Cz = c*math.sqrt((math.sin(beta))**2-(math.cos(alpha)-math.cos(beta)*math.cos(gamma))**2/(math.sin(gamma))**2) return np.array([Cx,Cy,Cz]) def unitCell(idx,a,b,c,alpha,beta,gamma): """ idx=(n1,n2,n3)から頂点位置(x,y,z)を算出 """ Ax,Ay,Az = idx[0]*vecA(a) Bx,By,Bz = idx[1]*vecB(b,gamma) Cx,Cy,Cz = idx[2]*vecC(c,alpha,beta,gamma) x = Ax + Bx + Cx # x座標 y = Ay + By + Cy # y座標 z = Az + Bz + Cz # z座標 return x,y,z def vtxPos(a,b,c,alpha,beta,gamma): """ 単位格子の頂点の位置を計算 """ val = [0,1] # n1,n2,n3がとりうる値 X = np.zeros(8) Y = np.zeros(8) Z = np.zeros(8) for i,idx in enumerate(itertools.product(val,repeat=3)): x,y,z = unitCell(idx,a,b,c,alpha,beta,gamma) X[i],Y[i],Z[i] = x,y,z return X,Y,Z # SrTiO3の結晶パラメータ a = 3.94513 b = 3.94513 c = 3.94513 alpha = 90 beta = 90 gamma = 90 # 頂点の計算 X,Y,Z = vtxPos(a,b,c,alpha,beta,gamma) # 図示 fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111,projection='3d') ax.scatter(X,Y,Z,s=40,c='b') ax.set_xlabel('x',size=14) ax.set_ylabel('y',size=14) ax.set_zlabel('z',size=14) plt.show() # plt.savefig('SrTiO3_unitCell.png',transparent=True,dpi=300) 上記のプログラムを実行すると以下の図が得られます。 これを平行六面体としてプロットされるようにプログラムを書き直していきます。平行六面体を連結グラフ(頂点はノード、辺はエッジに対応)として見たとき、1つのノードに対して3つのエッジがあるグラフである事がわかります。つまり、オイラーの定理より、オイラー・グラフ (Eulerian graph) ではない事が言えます。これより、"ax.plot"による"1筆書き"で平行六面体を描く事ができないため、泥臭く1つのグラフに辺を加えていくような処理で対処する必要があるかと思います(楽な方法をご存じの方はご教示ください)。つまり、直線の端点を2つ指定して、"ax.plot"で線を追加するという処理を12回繰り返して平行六面体を作成します。2頂点分の座標を保持するvtxMerge関数を定義し、これを"ax.plot"に入れていきます。 def vtxMerge(idx1,idx2): """ idx1=(n11,n12,n13)とidx2=(n21,n22,n23)の頂点の座標を持つ配列を生成 """ X = np.zeros(2) Y = np.zeros(2) Z = np.zeros(2) X[0],Y[0],Z[0] = unitCell(idx1,a,b,c,alpha,beta,gamma) X[1],Y[1],Z[1] = unitCell(idx2,a,b,c,alpha,beta,gamma) return X,Y,Z # 平行六面体の頂点 vtx1 = (0, 0, 0) vtx2 = (0, 0, 1) vtx3 = (0, 1, 0) vtx4 = (0, 1, 1) vtx5 = (1, 0, 0) vtx6 = (1, 0, 1) vtx7 = (1, 1, 0) vtx8 = (1, 1, 1) # 平行六面体の辺 X1,Y1,Z1 = vtxMerge(vtx1,vtx2) X2,Y2,Z2 = vtxMerge(vtx1,vtx3) X3,Y3,Z3 = vtxMerge(vtx1,vtx5) X4,Y4,Z4 = vtxMerge(vtx4,vtx2) X5,Y5,Z5 = vtxMerge(vtx4,vtx3) X6,Y6,Z6 = vtxMerge(vtx4,vtx8) X7,Y7,Z7 = vtxMerge(vtx6,vtx2) X8,Y8,Z8 = vtxMerge(vtx6,vtx5) X9,Y9,Z9 = vtxMerge(vtx6,vtx8) X10,Y10,Z10 = vtxMerge(vtx7,vtx3) X11,Y11,Z11 = vtxMerge(vtx7,vtx5) X12,Y12,Z12 = vtxMerge(vtx7,vtx8) # プロット fig = plt.figure(figsize=(8,8)) ax = fig.add_subplot(111,projection='3d') ax.plot(X1,Y1,Z1,c='b') ax.plot(X2,Y2,Z2,c='b') ax.plot(X3,Y3,Z3,c='b') ax.plot(X4,Y4,Z4,c='b') ax.plot(X5,Y5,Z5,c='b') ax.plot(X6,Y6,Z6,c='b') ax.plot(X7,Y7,Z7,c='b') ax.plot(X8,Y8,Z8,c='b') ax.plot(X9,Y9,Z9,c='b') ax.plot(X10,Y10,Z10,c='b') ax.plot(X11,Y11,Z11,c='b') ax.plot(X12,Y12,Z12,c='b') ax.set_xlabel('x',size=14) ax.set_ylabel('y',size=14) ax.set_zlabel('z',size=14) plt.show() # plt.savefig('SrTiO3_unitCell.png',transparent=True,dpi=300) 上記のプログラムを実行すると以下の図が得られます。ここでは図示してもあまり面白くない立方晶を取り扱っていますが、色々パラメータをいじって単位格子を眺めてみてください。 引用文献 [1] 松下能孝, J. Surface Analysis, 21, 71 (2014).
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ImageDataGeneratorを使ってみた

はじめに 最近、深層学習の実装ではPytorchに浮気している自分ですが、TensorFlowの中のImageDataGeneratorについて改めて勉強したので、その記録です。 使い方がメインなので、モデル構築や学習は行いません。 環境 Anacondaの仮想環境を使用。 python 3.9.5 Tensorflow 2.5.0 使うデータ Kaggle DatasetのIntel Image Classificationを使います。 {'buildings' -> 0, 'forest' -> 1, 'glacier' -> 2, 'mountain' -> 3, 'sea' -> 4, 'street' -> 5 } の対応になっている6クラス分類です。 基本的な使い方 データのディレクトリ構造をとりあえず以下のように設定します。 data/ ├ seg_train/ │   ├ buildings/ │   ├ forest/ │   ├ glacier/ │     省略 ├ seg_test/ │   ├ buildings/ │   ├ forest/ │   ├ glacier/ │     省略 └ seg_pred/    ├ 10004.jpg    ├ 10005.jpg     省略 seg_trainとseg_testはサブディレクトリがクラスごとに分かれていて、その中に画像が seg_predは予測してほしい画像なので、クラス関係なくごっちゃになって画像がディレクトリ直下に配置されています。 from tensorflow.keras.preprocessing.image import ImageDataGenerator train_dir = './data/seg_train' valid_dir = './data/seg_test' test_dir = './data/seg_pred' train_datagen = ImageDataGenerator(rescale=1./255, # 255で割ることで正規化 zoom_range=0.2, # ランダムにズーム horizontal_flip=True, # 水平反転 rotation_range=40, # ランダムに回転 vertical_flip=True) # 垂直反転 valid_datagen = ImageDataGenerator(rescale=1./255) test_datagen = ImageDataGenerator(rescale=1./255) Generatorをそれぞれtrain用、valid用、test用と用意します。trainは水増しを行い、valid,testは水増しはせず正規化だけします。 ImageDataGeneratorで行える水増し処理一覧は公式ドキュメント参照。 train_generator = train_datagen.flow_from_directory( train_dir, target_size=(64, 64), batch_size=64, class_mode='categorical', shuffle=True) valid_generator = valid_datagen.flow_from_directory( valid_dir, target_size=(64, 64), batch_size=64, class_mode='categorical', shuffle=True) generatorに対して、flow_from_directoryを使用して、画像データを読み取らせます。 この時、上記のように読み取らせたいディレクトリの中にクラスごとに分かれたディレクトリが存在していないとうまく読み取ってくれないので注意。 target_sizeに指定した大きさにリサイズします。 class_modeは今回多クラス分類なのでcategoricalを指定。 うまくいけばこのような表示がされるはず。 Found 14037 images belonging to 6 classes. Found 3000 images belonging to 6 classes. 上がtrainデータ、下がvalidデータについての実行結果です。画像枚数とクラス数が認識できています。 じゃあtestデータに対してもやってみましょう。 # 指定したディレクトリ直下に直接画像があるので認識してくれない test_generator = test_datagen.flow_from_directory( test_dir, target_size=(64,64), batch_size=64, class_mode=None, shuffle=False) 予測データのため、classは何クラスあるかわからないので、class_modeはNoneにします。 ただ、これを実行すると以下のような表示が。 Found 0 images belonging to 1 classes. 先ほども言ったようにtest_dirにはディレクトリ直下にフォルダがなく直接画像データがたくさん置かれています。flow_from_directoryは指定したディレクトリにあるフォルダの数をクラス数として認識するので、フォルダが1つもない場合、画像を正しく読み取ってくれません。 なので何か適当なフォルダを作成して、そこに画像を全部入れてあげればいいです。 # seg_pred/predというディレクトリを作成してその中に画像を格納する # shutilはファイルやディレクトリの移動・削除を扱える import shutil os.makedirs('./data/seg_pred/pred', exist_ok=True) for image_path in glob.glob(test_dir + '/*'): shutil.move(image_path, test_dir + '/pred') seg_predの中にpredというフォルダを作ってそこに画像データを全部移動させています。 shutil.move(移動させたい画像のパス, 移動先のパス)で画像を移動できます。 これでtestデータに対してflow_from_directoryを行えば Found 7301 images belonging to 1 classes. しっかりと7301枚の画像データを認識しています。 ちなみに.class_indicesでどうラベル付けしたか確認できます。 train_generator.class_indices # -> # {'buildings': 0, # 'forest': 1, # 'glacier': 2, # 'mountain': 3, # 'sea': 4, # 'street': 5} どんな画像を生成したのかみてみる 水増しした画像がどういうものか確認することで、意味のない水増しを防げます。 # 1バッチ分取り出す(64個の画像) items = next(iter(train_generator)) plt.figure(figsize=(12,12)) for i, image in enumerate(items[0][:25], 1): plt.subplot(5,5,i) plt.imshow(image) plt.axis('off') ランダムに回転してたり、ちゃんと水増しが適用されていることがわかります。 items[0] が64個の画像データ items[1] が64個のone-hot化されたラベルデータになっています。 なのでラベルも変換して表示できます。 # indexからラベル名に戻すための辞書を定義 index2label_dict = { 0 : 'buildings', 1 : 'forest', 2 : 'glacier', 3 : 'mountain', 4 : 'sea', 5 : 'street'} items = next(iter(train_generator)) plt.figure(figsize=(12,12)) for i, image in enumerate(items[0][:25], 1): label_index = np.argmax(items[1][i-1]) label_name = index2label_dict[label_index] plt.subplot(5,5,i) plt.imshow(image) plt.title(label_name) plt.axis('off') 例えばitems[1][1]の表示は以下のようになっています。 array([0., 0., 1., 0., 0., 0.], dtype=float32) この1になっているのが何番目か知れればラベル名を返せるのでnp.argmaxで取得しています。 ここで水増し画像が訓練データとして適切か確認したらモデル構築・学習へと進む形になります。 まとめ フォルダ構成を整えてあげることが大事だが、そこさえしっかりやれば水増し、前処理までやってくれるので便利だと感じた。 あとはバッチごとにデータを読み込むので、 もっと大量の画像を扱うことになった時にメモリオーバーにならなくていいと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクレイピングの前提

はじめに スクレイピングによるアクセスが膨大だと、相手のサーバに大きな負荷を掛けることになります。 これが業務妨害に該当するとして、逮捕者が出た例もあります。 アクセスの頻度を抑え、相手サーバの負荷が過大にならないように注意しなければいけません。 スクレイピングを始めるにあたって スクレイピングの常識について少し触れていきましょう。 動画は3つあるので、この3つを見ていきましょう。 ・ スクレイピングとは?(動画) ・スクレイピングの準備(動画) ・法律関係(動画) スクレイピングをする上での知識 また、スクレイピングをするにあたって、HTMLの構造とタグの仕組みなどを理解すると、スムーズにスクレイピングができるようになるのでこちらも確認していきましょう。 ・外)GETとPOST ・外)HTMLとは ・外)タグ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む