20220114のPythonに関する記事は23件です。

Python Matplotlibで基本的なグラフを書いてみる

はじめに  こんにちは、この記事はPC初心者がプログラミングの勉強のために書いたものとなっています。予めご了承ください。今回は、Pythonの matplotlib を使用して基本的なグラフの書こうと思います。 開発環境 ・Windows11 ・Visual Studio Code ・Python 3.9.2 ・matplotlib 3.4.3 ・numpy 1.21.2 数式のグラフ化 y = 2x^2 + 5x - 8 の数式を書いてみます。 コード import matplotlib.pyplot as plt import numpy as np #ライブラリのインポート # y = 2x^2 + 5x - 8 の数式を書く x = np.linspace(-5, 5, 100) # linespace 配列を書く #第一引数: 最小値、第二引数: 最大値、第三引数: 分割数 y = 2*x**2 + 5*x - 8 plt.plot(x, y) #対応する2つの配列の要素同士の点を結ぶ plt.show() #グラフの表示 出力 書けました。 散布図 scatter関数を使えば、散布図が書けます。 今回はsklearnにあるアヤメのデータを用いて散布図を書きます。 コード import matplotlib.pyplot as plt import pandas as pd from sklearn.datasets import load_iris #ライブラリのインポート iris = load_iris() #アヤメのデータ iris_data = pd.DataFrame(iris.data, columns=["sepal length(cm)", "sepal width(cm)", "petal length(cm)", "petal width(cm)"]) #アヤメのデータをデータフレームに格納 sep_len = iris_data["sepal width(cm)"] #がく片の幅のデータ pet_wid = iris_data["petal width(cm)"] #花びらの長さのデータ plt.scatter(sep_len,pet_wid) #がく片の幅 と 花びらの長さ で散布図を書く #scatter(xのデータ, yのデータ) plt.xlabel("sepal width(cm)") #x軸ラベル表示 plt.ylabel("petal width(cm)") #y軸ラベル表示 plt.show() 出力  sklearnライブラリに含まれるアヤメのデータをpandasを用いてデータフレームを作成しています。次に、そのデータから、「がく片の幅」と「花びらの長さ」を取り出しています。そして、二つのデータをscatter関数で散布図を作成しました。 棒グラフ  bar関数で棒グラフが書けます。 コード import matplotlib.pyplot as plt left = [0,1,2,3,4,5] #横軸の値(棒の数) point = [78,92,21,53,70,43] #縦軸の値 year = ["2000", "2001", "2002", "2003", "2004", "2005",] #棒のラベル plt.bar(left, point, tick_label=year) #第三引数の tick_label には、棒のラベルを代入します。 plt.show() 出力 箱ひげ図  箱ひげ図を作るには、boxplot関数を使います。ここでは、sklearnのデータセットに含まれている、3種のアヤメのsepal length(cm)で箱ひげ図を作成しました。 コード import matplotlib.pyplot as plt import pandas as pd from sklearn.datasets import load_iris #ライブラリのインポート iris = load_iris() #アヤメのデータ iris_data = pd.DataFrame(iris.data, columns=["sepal length(cm)", "sepal width(cm)", "petal length(cm)", "petal width(cm)"]) #アヤメのデータをデータフレームに格納 setosa_sep_length = iris_data.loc[0:49, ["sepal length(cm)"]] versicolor_sep_length = iris_data.loc[50:99, ["sepal length(cm)"]] virginica_sep_length = iris_data.loc[100:149, ["sepal length(cm)"]] #irisのデータには0~49行目までがsetosa種、50~99行目までがversicolor、 #100~149行目までがvirginica種のデータとなっている。 #loc カラム名で特定の行のデータを抽出 setosa_sepal_length = setosa_sep_length['sepal length(cm)'].to_list() versicolor_sepal_length = versicolor_sep_length['sepal length(cm)'].to_list() virginica_sep_length = virginica_sep_length['sepal length(cm)'].to_list() #to_list シリーズをリストに変換 fig, ax = plt.subplots() ax.set_title("iris sepal length") ax.set_xticklabels(['setosa', 'versicolor','virginica']) #set_xticklabels x軸の目盛に名前をつける(リストで渡す) ax.set_ylabel("sepal length(cm)") iris_sepal_length = (setosa_sepal_length, versicolor_sepal_length, virginica_sep_length) bp = ax.boxplot(iris_sepal_length) plt.show() 出力  まず初めに、アヤメのデータをデータフレームの変数に格納し、loc によって、インデックス・カラムを指定して、必要なデータ(今回はsepal length(cm))を抽出しています。抽出したデータはシリーズ型となっているため、これをリスト型に変換します*。そして、boxplot関数で箱ひげ図を作成します。なお、複数の箱ひげ図を描くときは、boxplot()にそれぞれのデータをタプル型で指定します。 *複数の箱ひげ図を描くとき、この操作をしないと、 ValueError: X must have 2 or fewer dimensions というエラーが起こります。(シリーズ型だとインデックスが存在するせい?) 円グラフ  pie関数で、円グラフが描けます。 コード import matplotlib.pyplot as plt import pandas as pd import numpy as np data = np.array([23, 34, 12, 5, 8, 18]) Name_list = {"Japan", "US", "USA", "Canada", "France", "Germany"} fig = plt.figure() #figure() グラフを表示する領域を作る ax1 = fig.add_subplot(1, 2, 1) ax2 = fig.add_subplot(1, 2, 2) #add_subplot() グラフを描く位置を追加 引数(行, 列, 場所) ax1.pie(data) ax1.set_title("before") ax2.pie(data, labels=Name_list, startangle=90, counterclock=False , autopct="%1.0f %%") ax2.set_title("After") plt.show() 出力 デフォルトのままでは見ずらいので、引数にいろいろ指定します。 startangle: グラフの開始位置を調整 counterclock: trueが指定されると時計回りにグラフが出力 autopct: 割合の値を表示 これで、見やすくなりました。 最後に  今回は、よく使いそうなグラフである、座標平面・散布図・棒グラフ・箱ひげ図・円グラフの描き方を解説しました。各グラフを描く関数の引数を指定することで、様々な装飾も可能です。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google Drive上にあるCSVファイルをGoogle Colabratoryで読み込む

Google Drive上にあるCSVファイルをGoogle Colabratoryで読み込む 目的 最近、Jupyter NotebookからGoogle Colabratory PROに環境を変えたので、備忘録として残します。 Google Driveへのマウント まず、Colabに以下のコードを入力し、Google DriveをColab上で読み込めるようにします。(Shift + Enterでコード実行) from google.colab import drive drive.mount('/content/drive') ここで、Googleから Google ドライブに接続を選択します。 次に、Googleアカウントログイン画面に移動するので、ログイン手続きをします。 すると と表示されるので、Continueを選択します。 そして、Mounted at /content/driveと出力されれば、マウント完了です。 使いたいCSVファイルの階層に移動 このままCSVファイルを読み込もうとしようとしても。ホームディレクトリにいるのでCSVファイルが読み込めません。 そこでコマンドプロンプトと同様にcdコマンドを使うことでCSVファイルのあるフォルダに移動することができます。 Colabのセルにcd 'drive/My drive/hoge/hoge/hoge'入力します。(/hoge/hoge/hoge の部分を階層やフォルダ名に合わせて変える) 例えば、ドライブのColab Notebooks/movetestにCSVファイルがあるとき cd 'drive/My Drive/Colab Notebooks/movetest' と入力し、実行することで /content/drive/My Drive/Colab Notebooks/movetestと出力されます。これでディレクトリの移動ができました。 CSVファイルの読み込み CSVファイルの読み込む手法として、pandasのread_csv()を使用します。 以下のようなコードを入力、実行します。(ここではsampledata.csvというファイルを読み込みます。) # pandasを導入、pdとして import pandas as pd # dfに見出しのあるデータを格納。 df = pd.read_csv('sampledata.csv') # display()でdfをdataframe形式の表のレイアウトを保持して出力。 display(df) これで と読み込み確認できたので、完了です。 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandasのデータフレームを結合するときにグラフをプロットすることを前提だとどうすればいいか

はじめに pandasって便利ですよね 私は特にグラフの作成に使用しています。 様々なグラフを重ねて表示させるにはどうすればいいでしょうか。 元のデータ 今回用意したデータがこちらです。 そしてこれをグラフにしたものがこちらです。 aaa.plot(x="x",y = "ex10", xlim=[-2, 4], ylim=[0,0.01]) さらに、追加でこんなデータや、 bbb.plot.scatter(x="x",y = "ex1", xlim=[-2, 4], ylim=[0,0.01]) こんなデータを重ねて表示したいと思います。 最終的にはこんな感じで表示したいと思います。 これらをどうやってpandasで行うのか調べました。 方法1 mergeを使う(失敗) pandasで異なるデータフレームを結合する際、mergeというものがあります。 ざっくり説明すると、これは共通変数があるときの結合方法で、共通変数内に同じ数値があればその行に追加し、なかったら一番下の行に追加するという関数です。 試しにやってみた結果が以下の通りです。 ccc = pd.DataFrame([0], columns=["x"]) for i in range(1,n+1): label = "ex" + str(i) bbb = pd.DataFrame(ex_line[i], columns=["x", label]) bbb.plot(x="x", xlim=[-L4, L1], ylim=[0,0.01]) #交点ににおいてmerge時に順番が飛び、うまく書けない ccc = pd.merge(ccc,bbb, on="x", how='outer') 折れ線が途切れてしまいました。 この原因は、同じ数値があればその行に追加することが原因でした。 つまり、on="x", how='outer'と指定したことにより、x=0と-2が各列の共通の値なので、ここだけ順番が上になっているということでした。 この写真を見てもわかるように、各列において要素が存在する行が変数xが昇順になるように上から並んでいなければいけないのですが、x=0 だけ一番上まで連れてこられてしまっているため、順番がめちゃくちゃになってしまったのです。 方法2 appendで下の行にそのままつなげる(成功) 下の行にデータをつなげているだけなので順番が変わらずグラフが描けます。 ccc = pd.DataFrame([0], columns=["x"]) for i in range(1,n+1): label = "ex" + str(i) bbb = pd.DataFrame(ex_line[i], columns=["x", label]) bbb.plot(x="x", xlim=[-L4, L1], ylim=[0,0.01]) #下につなげるだけでうまくいく ccc = ccc.append(bbb) データフレームの中身はこのようになります。 方法3 plotの変数axを使う pandasのplotはmatplotlibに準じたものなのですが、なかなかpandasのplotについて書かれた記事が出てこない。 いろいろ探した結果、以下のサイトが最高でした。 これによれば、plot内の変数axを使用することでグラフを重ねることができます。 これが一番簡単そうですね まとめ pandasで複数グラフを重ねる方法について考えました。 一つのデータフレームを使うなら方法2、グラフを重ねるなら方法3を使えば問題なさそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Django REST Frameworkの初期設定

Django REST Frameworkをインストールする $ pip install djangorestframework プロジェクトのsettings.pyのINSTALLED_APPSに追加する。 settings.py INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', # 追加!!! ] ひとまず、以上。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pixiv_downloader2.0追加について(番外編4)

はじめに pixivから画像をダウンロードするプログラムを作っているのですが、ほぼ1から作り直したので変更点を書いていこうと思います。 なお、以前の今では動かないプログラムをver1.0、 今回作り直したプログラムをver2.0としています。 以前のプログラムver1.0 新しいプログラムver2.0 全体的な変更点 変数の名前の変更 全体的に、pixivpyに準拠した変数名や直感的な変数名に変えました。 バッチファイルを追加した 毎回コマンドプロンプトから実行するのは面倒なので、windows用のバッチファイルを作ってgithubにあげています。 githubを使うようになった 今まではqiitaのコードをコピペしてもらっていましたが、プログラム数が増えたのでgithubを使うことにしました。 gitは存在は知っていたのですが、使ったことはなかったので使い方が間違っている子もしれません。 実行が遅くなった ver1.0ではイラストレーターのidを入れたら一度のapiアクセスですべての作品情報をとってこれましたが、 新しい方法のaapi.user_detail(user_id)では一回に30作品しかとってこれなくなりました。 次のページの情報をとるためには、next_qs = aapi.parse_qs(user_illusts.next_url)の後、aapi.user_illusts(**next_qs)としなければいけないため、アクセス数も2回に増えてしまいました。 1秒1アクセスの暗黙のルールとともに、かなりプログラムが遅くなってしまいました。 なお、next_qsの中身はわかってローカルで生成できるようになったのですが、例外処理などいろいろ関わってくるので、やる気があったら直します。 tqdmの追加により、プログレスバーで進行状況が分かりやすくなった。 このプログラムではいろいろなforループがあります。 ユーザーを回すfor 30枚ごとに表示される作品ページを遷移するfor 各作品を回すfor 作品がmangaかうごイラの時は各画像をダウンロードするfor etc... です。 実行時、進行状態になるのかわかりにくいので、tqdmを導入してプログレスバーを表示するようにしました。 #100%|██████████| 1000/1000 [01:39<00:00, 9.96it/s] tqdm内でprint()を使用すると表示が崩れてしまうので、tqdm.write()を使用しました。 この際中身がないものを表示しようとするとprint()では空行になるのに対して、tqdm.write()ではエラーになってしまうのでそこがめんどくさかったです。 各種方法の変更 トークンをjsonに保存 ver1.0ではログイン方法の変更などいろいろありましたが、コードにトークンを直書きするようにしていました。 しかしそれではあまり安全性がよくないので、client.jsonに記入してそこから読み込むようにしました。 リネーム方法を新しくした 現在各イラストレーターのフォルダをユーザーネームを使用して作成しています。 その際、windowsで使用できない文字がユーザー名にあると消せないフォルダが出来上がってしまい、大変です。 そこで文字を置換するようにしていたのですが、以下の記事に従って半角を全角にするなどしてできるだけ元の名前と見た目が変わらないようにしました。 エラー時の対処を増やした pixivにアクセスしすぎでエラーになったときは60s待つようにしました。 ユーザーが削除されていた時にエラーで停止しないようにしたり、頑張りました。 イラストダウンロードをoriginalに 今までは画像のダウンロードはlargeというところからダウンロードしていたが、originalが選べるようになったので変更。 largeとoriginalが同じかは不明 任意のユーザーidでダウンロードできるように 今まではclient.json内のユーザーしかダウンロードできなかったが、コード内に直接user_idを書くことでダウンロードできるようにした。 具体的には、以下のコードのコメントアウトを消して数字の部分をダウンロードしたい数字に書き換え、下の行をコメントアウトすればよい。 #ここから各イラストレーターさんごとの処理 #for user_id in tqdm([11,12848282], desc='users', leave=False): for user_id in tqdm(client_info["ids"], desc='users', leave=False): pixiv_follow_id_getter_over5000.py が動いていなかったので修正 5000人以上フォローしている時用に、seleniumを使用してブラウザから無理やりユーザーidをスクレイピングしていました。 その際、htmlのclassを使用して指定していたのですが、前回作成したときと名前が変わっていたので直しました。 pixiv_follow_id_getter_over5000.py #ここのclass_は前回と変わったので定期的にチェックを users = soup.find_all("div", class_ = "sc-19z9m4s-5 iqZEnZ") うごイラの変更 うごイラの保存方法が増えた。 1.0まではgif形式のみの保存だったが、 や を参考に、mp4、htmlでの保存を選択肢として増やしました。 また、どの拡張子で保存するかも選択できるようにしました。 各性質は以下の通りです gif mp4 html onefile 画質 悪 良 無劣化 無劣化 ファイルサイズ 大 小 小 大 ループ あり なし あり あり 保存場所 直下 直下 ugoiraフォルダ内 ugoiraフォルダ内 ファイル移動 可 可 不可 可 その他 何となくおすすめ ループしないけどきれいな動画 とりあえず無劣化で保存したいなら 無劣化で取り回しをよくしたいなら #うごイラのダウンロード形式設定 #画質悪、ファイルサイズ大、ループ、保存場所は直下 ugoira_gif = True #画質良、ファイルサイズ小、ループしない(再生ソフト次第)、保存場所は直下 ugoira_mp4 = True #画質最高(劣化なし)、ファイルサイズ小、ループ、ファイル移動できない(元の画像を参照しているため)、保存場所はugoiraフォルダ内 ugoira_html = True #画質最高(劣化なし)、ループ、ファイルサイズ大、移動可、保存場所はugoiraフォルダ内 html_onefile = True なおいろいろ直したところは以下の通りです。 mp4 全角文字が含まれているフォルダにOpenCVからアクセスしたり、αチャンネルのpng画像の読み込みに苦しみました。 html いろいろな画像を読み込めるようにするのが大変でした。 うごイラでzipで保存できるように うごイラのダウンロードについて調べていたところ、使用されている画像がすべて入っているzipファイルをダウンロードできることが判明しました。 それをうまく展開してリネームまでできるようにはしました。 しかし、zipでダウンロードした画像は元の画像よりも低解像度で、gif/mp4作成時に縦横を指定しなければならずその時にメタデータと食い違ってエラーになったため、コメントアウトしました。 うごイラのdelayは一定しか無理 うごイラごとにderalyが決まっていて、1枚目は20ms、2枚目は30ms...などとありましたが、使用しているライブラリの関係で一定値しか使用できないので、1枚目に設定されている値にしています。 ダウンロード済みかを0枚目ではなく、最後のページで判断するように ver1.0の時にはダウンロード済みの画像かどうかを一枚目(p0)が存在するかで判断していました。 1.0.py if os.path.exists(saving_direcory_path + str(illust.id)+"_p0.png") そこで、画像の最終画像が存在するかで判断するようにしました。 2.0.py if os.path.exists(saving_direcory_path + str(illust.id)+"_p" + str(illust.page_count-1) +".png") ただしうごイラはいろいろあってフォルダが存在するかで判断しているため、 展望 可読性を上げるために関数を一切使わずにプログラムを作ったが、classや関数を使ったほうが今後変更などしやすくなりそうなので、それを作ってみる。 ランキング上位をダウンロードや、新着からダウンロードなどを追加する。 イラストのメタデータをダウンロードする。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

幅優先、深さ優先探索

いろんなところで使えそうなアルゴリズム アルゴリズムの説明についてはせずに、単純にコードなどを書き記しておきます。 使用言語はPython3.10. match文を使います。 準備 from collections import deque 迷路作成 開く class Position: EMPTY = '.' BLOCK = '#' GOAL = 'G' START = 'S' CHECKED = 'o' maze = """ ############ S.#...###..# #...#.#.#.## #.#.#...#.## ###.#.#.#.## #...###...## #.#.#...#.## ###.#.#.#### #...#.###..G ###.#...#.## #...###.#.## ##.####...## ############ """ def maze2list() -> list[list[str]]: return [list(line) for line in maze.split()] def colored_maze(maze: list[list[str]]) -> str: colored_maze = '' for row in maze: colored_row = '' for cell in row: match cell: case Position.EMPTY: c = f'\033[38;5;010m{cell}\033[0m' case Position.BLOCK: c = f'\033[38;5;009m{cell}\033[0m' case Position.GOAL: c = f'\033[38;5;011m{cell}\033[0m' case Position.START: c = f'\033[38;5;012m{cell}\033[0m' case Position.CHECKED: c = f'\033[38;5;014m{cell}\033[0m' case _: c = str(cell) colored_row += c colored_row += '\n' colored_maze += colored_row return colored_maze 幅優先(Breadth First Search: BFS) キューが空になるまでループを回す。 戻り値はゴールまでの経路のリストが入っている。 def bfs(maze: list[list[str]], start: list|tuple) -> list[list[int]]: # [position(y,x), [path(y,x)], counts] q = deque([[start, [list(start)], 0]]) while q: p = q.popleft() y, x = p[0] # position(y,x) count = p[-1] # counts if maze[y][x] == Position.GOAL: print("DFS: found goal in {} steps".format(count)) return p[1] maze[y][x] = Position.CHECKED pblock = [ [y - 1, x], [y + 1, x], [y, x - 1], [y, x + 1]] for pb in pblock: # current position is outside of maze or is a block or is already checked if ( not (0 <= pb[0] < len(maze)) or not (0 <= pb[1] < len(maze[0])) or maze[pb[0]][pb[1]] == Position.BLOCK or maze[pb[0]][pb[1]] == Position.CHECKED): continue # append to last element of queue q.append([pb, p[1] + [pb], count+1]) m = maze2list() print(bfs(m, (1,0))) print(colored_maze(m)) BFS: found goal in 59 steps [[1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [1, 3], [1, 4], [1, 5], [2, 5], [3, 5], [3, 6], [3, 7], [4, 7], [5, 7], [6, 7], [6, 6], [6, 5], [7, 5], [8, 5], [9, 5], [9, 6], [9, 7], [10, 7], [11, 7], [11, 8], [11, 9], [10, 9], [9, 9], [8, 9], [8, 10], [8, 11]] ############ oo#ooo###oo# #ooo#o#o#o## #o#o#ooo#o## ###o#o#o#o## #ooo###ooo## #o#o#ooo#o## ###o#o#o#### #ooo#o###ooG ###o#ooo#o## #ooo###o#o## ##o####ooo## ############ 深さ優先(Depth First Search: DFS) 再帰使う場合と使わない場合とで紹介。 paizaとかだと、再帰はランタイムエラーになりやすいので注意。 再帰 GOAL = False def dfs_recursive(maze: list[list], start: list, path: list, count: int): global GOAL if GOAL: return y, x = start if ( not (0 <= y < len(maze)) or not (0 <= x < len(maze[0])) or maze[y][x] == Position.BLOCK or maze[y][x] == Position.CHECKED): return if maze[y][x] == Position.GOAL: print("DFS: found goal in {} steps".format(count)) GOAL = True return path maze[y][x] = Position.CHECKED p = dfs_recursive(maze, (y-1, x), path + [[y-1,x]], count + 1) p = p or dfs_recursive(maze, (y+1, x), path + [[y+1,x]], count + 1) p = p or dfs_recursive(maze, (y, x-1), path + [[y,x-1]], count + 1) p = p or dfs_recursive(maze, (y, x+1), path + [[y,x+1]], count + 1) return p m = maze2list() print(dfs_recursive(m, (1,0), [[1,0]], 0)) print(colored_maze(m)) 非再帰 BFSのコードと違う点は、while内のfor文で回す要素の順序とappendleftを使う点。逆順に回すこととで、先頭から[y-1,x], [y+1,x],[y,x-1],[y,x+1]とqに追加される。そうすることで、深さ順にどんどん追加されていくようになる。 def dfs(maze: list[list[str]], start: list|tuple) -> list[list[int]]: # [position(y,x), [path(y,x)], counts] q = deque([[start, [list(start)], 0]]) while q: p = q.popleft() y, x = p[0] # position(y,x) count = p[-1] # counts if maze[y][x] == Position.GOAL: print("DFS: found goal in {} steps".format(count)) return p[1] maze[y][x] = Position.CHECKED pblock = [ [y - 1, x], [y + 1, x], [y, x - 1], [y, x + 1]] for pb in reversed(pblock): if ( not (0 <= pb[0] < len(maze)) or not (0 <= pb[0] < len(maze[0])) or maze[pb[0]][pb[1]] == Position.BLOCK or maze[pb[0]][pb[1]] == Position.CHECKED): continue q.appendleft([pb, p[1] + [pb], count+1]) m = maze2list() print(dfs(m, (1,0))) print(colored_maze(m)) DFS: found goal in 30 steps [[1, 0], [1, 1], [2, 1], [2, 2], [2, 3], [1, 3], [1, 4], [1, 5], [2, 5], [3, 5], [3, 6], [3, 7], [4, 7], [5, 7], [6, 7], [6, 6], [6, 5], [7, 5], [8, 5], [9, 5], [9, 6], [9, 7], [10, 7], [11, 7], [11, 8], [11, 9], [10, 9], [9, 9], [8, 9], [8, 10], [8, 11]] ############ oo#ooo###..# #ooo#o#o#.## #o#.#ooo#.## ###.#o#o#.## #...###o..## #.#.#ooo#.## ###.#o#o#### #...#o###ooG ###.#ooo#o## #...###o#o## ##.####ooo## ############
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

駒の顔画像を自動収集する【スクレイピング - Scrapy編】

はじめに こんにちは。逆転オセロニアのYouTubeチャンネル「まこちゃんねる」の中の人です。 本稿では、公式wikiから駒の顔画像を自動収集することを目標にします。 今回はPythonのスクレイピングフレームワーク、Scrapyを利用してみます。 モチベーション オセロニアを題材にしていく上で、画像収集する場面が多いため Pythonでスクレイピングを実装したことが無かったので、その練習のため スクレイピング(Scraping)とは? ウェブサイトから情報を抽出するコンピュータソフトウェア技術のこと。 ウェブスクレイピング - Wikipedia より 今回であれば、公式wikiから駒の顔画像及び付随する情報(名称・レア度等)を自動で抽出することになります。 環境 macOS JupyterLab Python3.6 Scrapy 実装の流れ まずはScrapyをインストールし、scrapyコマンドでプロジェクトを作成します。その後、スクレイピングしたデータを格納する為のItemクラスの実装、HTMLをパースする為のparse関数の実装、取得したデータを操作する為のPipelineを実装していきます。 Scrapyプロジェクトの作成 Scrapyをインストールする プロジェクトを作成する Spiderの実装 Itemを実装する Parse処理を実装する Pipelineを実装する スクレイピングの実行 実際にデータを取得する Scrapyプロジェクトの作成 Scrapyをインストールし、scrapyコマンドでスクレイピングをするために必要なフレームワークを作成していきます。 Scrapyをインストールする Scrapyをpipでインストールします。 $ pip install scrapy プロジェクトを作成する scrapy startproject <プロジェクト名> で雛形を作成します。今回はプロジェクト名をscraping_character_dataとしています。 $ scrapy startproject scraping_character_data 以下のディレクトリ構造で雛形が作成されます。 $ tree scraping_character_data/ scraping_character_data ├── scraping_character_data │   ├── __init__.py │   ├── items.py │   ├── middlewares.py │   ├── pipelines.py │   ├── settings.py │   └── spiders │   └── __init__.py └── scrapy.cfg Spiderの実装 スクレイピングの中核となる部分です。コマンドでspiderを作成し、雛形として作成されたitems.py、pipelines.py、spider.pyの中身を実装していきます。 Itemを実装する 生成されたitems.pyを編集し、HTMLでパースしたデータを保持するフィールドを定義します。今回であれば、キャラクターの図鑑No、顔画像URL、名前、属性、レア度を定義しておきます。 items.py import scrapy class CharacterDataItem(scrapy.Item): no = scrapy.Field() face_img_url = scrapy.Field() name = scrapy.Field() attribute = scrapy.Field() rarity = scrapy.Field() Parse処理を実装する まずはparse処理を作成するために、scrapyコマンドでspiderを作成します。scrapy genspider <spiderクラス名> <スクレイピング対象のドメイン名>で作成することができます。今回はクラス名をcharacter_data、ドメイン名をオセロニア攻略.gamematome.jpとして作成します。 $ scrapy genspider character_data オセロニア攻略.gamematome.jp spidersディレクトリ直下にcharacter_data.pyが作成されます。 $ tree scraping_character_data/scraping_character_data/spiders scraping_character_data/scraping_character_data/spiders ├── __init__.py └── character_data.py 作成されたcharacter_data.pyを編集していきます。nameは実際にスクレイピングを実行するときに指定する値になります。allowed_domainsには実行対象のドメイン名を指定しておきます。今回であればオセロニア攻略.gamematome.jpが作成時に自動で格納されています。start_urlsにはスクレイピング開始のURLを指定しておきます。ここで注意なのですが、日本語のドメイン名はPunycodeへ変換してから扱うようにしたほうが良さそうです。日本語ドメイン名のまま動かしていたらスクレイピングが全然実行されず、ここでめちゃくちゃハマりました...。 character_data.py import scrapy from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor from scraping_character_data.items import CharacterDataItem class CharacterDataSpider(CrawlSpider): name = 'character_data' # Punycodeへ変換しておく domain = 'オセロニア攻略.gamematome.jp'.encode('idna').decode("utf-8") allowed_domains = [domain] start_urls = [f'https://{domain}/game/964/wiki/キャラクター情報'] 次にスクレイピング時の巡回ルールを指定します。CrawlSpider、Rule、LinkExtractorをインポートします。LinkExtractorにはどのリンクを巡回するかのルールを指定し、callbackにはルールを満たした時に呼ばれる関数を指定します。今回であれば、start_urlsに指定したURLに含まれるtable要素のidがcontent_block_9に含まれるURLを巡回してスクレイピングを行うことになります。他にもcssのタグや正規表現等といった指定の仕方もすることができます。 character_data.py import scrapy from scrapy.spiders import CrawlSpider, Rule from scrapy.linkextractors import LinkExtractor class CharacterDataSpider(CrawlSpider): ... rules = ( Rule( LinkExtractor(restrict_xpaths='//table[@id="content_block_9"]'), callback='parse_character_data' ), ) 抽出する部分の処理を実装していきます。データ格納先のCharacterDataItemをインポートしておきます。Ruleで定義したcallback関数(parse_character_data)が呼ばれるので、ここに処理を書いていきます。responseにはhtml情報が入ってくるので、ChromeのDevToolsで要素を探索しつつコードに起こします。今回であれば、table要素のidがcontent_block_2を探索すれば必要な情報が抜き出せそうです。最後にCharacterDataItemに抽出したデータを詰めてPipelineに処理を渡します。 character_data.py from scraping_character_data.items import CharacterDataItem ... def parse_character_data(self, response): table = response.xpath("//table[@id='content_block_2']")[0] trs = table.xpath("//tr")[1:] for tr in trs: no = tr.xpath(".//td[1]/text()").get() face_img_url = tr.xpath(".//td[2]/a/img/@src").get() name = tr.xpath(".//td[3]/a/text()").get() attribute = tr.xpath(".//td[4]/text()").get() rarity = tr.xpath(".//td[5]/text()").get() yield CharacterDataItem( no=no, face_img_url=face_img_url, name=name, attribute=attribute, rarity=rarity ) Pipelineを実装する pipelines.pyを編集していきます。抽出したデータがPipelineに流れてくるので、ここでデータの保存処理を行います。process_itemの引数itemからデータを取得することができます。今回は取得したデータからファイル名を作り、キャラクターの顔画像を保存することにします。ファイル名は{5桁の図鑑No}_{属性}_{レア度}_{名前}.pngとします。一応scrapyの場合、画像専用のImagesPipelineがあるのですが、内部で勝手にjpeg変換されてしまっているようで、透過画像として保存できなかったので断念しました。ここら辺詳しい方、是非コメントで教えてください。 pipelines.py import scrapy import requests import pathlib class ScrapingCharacterDataPipeline(): def process_item(self, item, spider): filename = '_'.join([item["no"].zfill(5), item["attribute"], item["rarity"], f'{item["name"]}.png']) file = pathlib.Path('data', 'face_img', filename) r = requests.get(item['face_img_url']) if r.status_code == 200: with file.open(mode="wb") as f: f.write(r.content) スクレイピングの実行 scrapyコマンドで実行します。scrapy crawl <定義したname>で実行できます。 $ scrapy crawl character_data データを取得する 実際にデータを取得してみるとこんな感じ。やったね!これでオセロニアを題材に色々と画像処理を試せるぞい! おわりに 今回は全キャラクターを対象としましたが、追加された駒だけを対象とするような改修をしたいなと思いました。毎回全件取得していたら、サイトにめちゃくちゃ負担をかけることになりますからね...。 ...って、キャラクター情報ページメンテされてないじゃん!No5300までしか乗ってないじゃん!記事記載時ではNo5401〜まであるのに...頼むよkJ〜 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DataFrameのリスト要素をNaNが含まれていても結合する方法

DataFrameのリスト要素を結合するとき、要素の値としてNaNが含まれていると、結果はNaNになります。 期待通りに動かないコード import pandas as pd import numpy as np df_test = pd.DataFrame({ "data1" :[["1","2","3"],["1","2","3"]], "data2" :[["4","5","6"],["4","5","6"]], "data3" :[["7","8","9"],np.nan] }) result = df_test["data1"]+ df_test["data2"]+ df_test["data3"] print(result) # 結果 # 0 [1, 2, 3, 4, 5, 6, 7, 8, 9] # 1 NaN ← [1, 2, 3, 4, 5, 6] としたい 単純にfillna()を使ってNaNを空リストに変換しようとすると、リストには変換できないと怒られてしまいます。 単純にfillna()を使ってもエラー result = df_test["data1"]+ df_test["data2"]+ df_test["data3"].fillna([]) # TypeError: "value" parameter must be a scalar or dict, but you passed a "list" 解決方法 下記のステップで実装します。 NaNを空文字に置換する すべての要素をリストに変換する。空文字が空リストに変換される。 列方向にリストを結合する import pandas as pd import numpy as np df_test = pd.DataFrame({ "data1" :[["1","2","3"],["1","2","3"]], "data2" :[["4","5","6"],["4","5","6"]], "data3" :[["7","8","9"],np.nan] }) result = df_test.fillna("").applymap(list).sum(axis=1) # 変更点 print(result) # 結果 # 0 [1, 2, 3, 4, 5, 6, 7, 8, 9] # 1 [1, 2, 3, 4, 5, 6] 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータとオセロ対戦38 ~係数、局所探索~

前回 今回の目標 前回作成したニューラルネットワークを用いて、新しい構想でのAIを作成する。 ここから本編 前回も説明した通り、新しく作るAIは、今までのAIの考え方とnleastを融合させます。 つまり、 深層学習モデルを用いて、現在の盤面から最終結果を予測する 深さ優先探索を用いて、先のターンで相手がとれる手数を得る という二つの動作を行い、そして、得られた予測値と手数から総合的な「勝利係数」を計算し、その結果が最も高い位置に置くという考え方をします。 計算方法は以下の通りです。 ここで、 total_score 計算結果(勝利係数) score 予測した最終結果(最終的な自分の駒の数-相手の駒の数) place 相手がとれる手数 A, B 任意定数 予測した最終結果が大きいほど、また、相手がとれる手数が少ないほどより勝利しやすいと予測します。ここで、AとBの係数を適切なものに変えることで強いAIが作れると考えました。 今回はこの適切なAとBを、局所探索法を用いて求めることを目標とします。 今回使用したPythonのクラス構造は以下の通りです。 osero.py オセロをするための基本クラス Net.py ニューラルネットワークを定義したクラス AI.py 人工知能でオセロをプレイするためのクラス run.ipynb 実行ファイル また、今回からファイル名とクラス名を同じにしました。 osero.py 今までのBitBoard.pyと同様です。 本質ではないのでここでは説明を省略します。 Net.py ニューラルネットワークです。前回用いたものと全く同じです。 import chainer.links as L import chainer.functions as F from chainer import Chain n_multi = 1 func = F.tanh class Net(Chain): def __init__(self): n_in = 192 n_hidden = 192 n_out = 1 super().__init__() with self.init_scope(): self.l1 = L.Linear(n_in, n_hidden) self.l2 = L.Linear(n_hidden, n_hidden) self.l3 = L.Linear(n_hidden, n_hidden) self.l4 = L.Linear(n_hidden, n_out) def __call__(self, x): h = n_multi * func(self.l1(x)) h = n_multi * func(self.l2(h)) h = n_multi * func(self.l3(h)) h = self.l4(h) return h AI.py 人工知能でオセロをプレイするためのクラス。 from copy import deepcopy from random import randint from chainer.serializers import load_npz import numpy as np import osero import Net コンストラクタ オセロの思考方法にAIを追加し、AとBを仮に決定しています。 model_loadメソッドは後述。 class AI(osero.osero): def __init__(self, black_method, white_method, seed_num=0, read_goal=[1, 1], eva=[0, 0], model_name="model.net"): super().__init__(black_method, white_method, seed_num, read_goal, eva) self.PLAY_WAY["AI"] = len(self.PLAY_WAY) self.think.append(self.AI) self.model_name = model_name self.A = 1 self.B = 1 self.model_load() model_load 前回保存しておいたニューラルネットワークを呼び出します。 def model_load(self) -> None: self.model = Net.Net() load_npz(self.model_name, self.model) dict_to_ndarray 現在の盤面を、ニューラルネットワークに投げられる形(numpy.ndarray)に変換します。 このメソッドは直接run.ipynbなどで使うためのものではなく、後述するメソッド内でのみ使用します。 def dict_to_ndarray(self, now: dict) -> np.array: data = [] if self.turn: my = ["b_u", "b_d"] opp = ["w_u", "w_d"] else: opp = ["b_u", "b_d"] my = ["w_u", "w_d"] for i in range(2): for j in range(32): if self.bw[my[i]] & 1 << j: data.append(1) data.append(0) data.append(0) elif self.bw[opp[i]] & 1 << j: data.append(0) data.append(1) data.append(0) else: data.append(0) data.append(0) data.append(1) return np.array([data], dtype=np.float32) predict ニューラルネットワークに説明変数を投げ、結果を返すメソッド。 こちらも直接ではなく、後述するメソッド内でのみ利用しています。 def predict(self, now: dict) -> float: now = self.dict_to_ndarray(now) ans = self.model(now) return float(ans.array) AI 上述した思考を行うメソッドです。なおこのメソッドは、oseroクラスのcheckメソッドにより、最低一か所は置ける場所があることを確認したうえでしか呼び出されません。 cal_score_placeメソッドは盤面から予測値及び相手の手数をもらえるメソッドだと思ってください。 もらった予測値及び相手の手数にAとBを噛ませ、その結果が最も大きくなる位置に石を置きます。 アルゴリズムを説明すると、 すべての場所について、置けるかどうか調べる すべての置ける場所について、予測値及び相手の手数をもらい、勝利係数を計算する。 勝利係数が最も大きい位置に石を置く。同じ値が複数出た場合はランダムでどれかに置く。 def AI(self) -> None: min_total_score = -0xffff line_ans, col_ans = [-1], [-1] place_num = 0 for i in range(8): for j in range(8): if not self.check(i, j, self.bw, self.turn): continue board_leaf = deepcopy(self.bw) self.put(i, j, board_leaf, self.turn) score, place = self.cal_score_place(\ board_leaf, not self.turn, not self.turn, 1 ) total_score = self.A * score + self.B / (place + 1) if total_score > min_total_score: min_total_score = total_score place_num = 0 line_ans = [i] col_ans = [j] elif total_score == min_total_score: line_ans.append(i) col_ans.append(j) place_num += 1 if place_num: place_num = randint(0, place_num) line_ans[0] = line_ans[place_num] col_ans[0] = col_ans[place_num] self.put(line_ans[0], col_ans[0], self.bw, self.turn) cal_score_place 盤面から予測値及び相手の手数をもらえるメソッド。 あらかじめread_goalで指定しておいたターン数だけ先まで調べ、その位置での予測値と相手の手数の平均を返します。 今回read_goalは1を指定するため、自分が置いた後に相手が置くターンで、予測値がどうなるか、また、そのときの相手の手数を返します。 簡単に関数の流れについて解説します。 def cal_score_place(self, now: dict, turn: bool,\ tar_turn: bool, num: int) -> tuple: place_num = 0 if turn == tar_turn: if num == self.read_goal[self.turn]: for i in range(8): for j in range(8): if self.check(i, j, now, turn): place_num += 1 score = self.predict(now) return score, place_num else: score_sum = 0 place_sum = 0 for i in range(8): for j in range(8): if not self.check(i, j, now, turn): continue board_leaf = deepcopy(now) self.put(i, j, board_leaf, turn) score, place = self.cal_score_place(\ board_leaf, not turn, tar_turn, num + 1 ) score_sum += score place_sum += place place_num += 1 if place_num: return score_sum / place_num, place_sum / place_num else: return 0, 0 else: score_sum = 0 place_sum = 0 for i in range(8): for j in range(8): if not self.check(i, j, now, turn): continue board_leaf = deepcopy(now) self.put(i, j, board_leaf, turn) score, place = self.cal_score_place(\ board_leaf, not turn, tar_turn, num ) score_sum += score place_sum += place place_num += 1 if place_num: return score_sum / place_num, place_sum / place_num else: return self.cal_score_place(\ deepcopy(now), not turn, tar_turn, num ) count_last 最終結果を返します。 def count_last(self) -> int: black = self.popcount(self.bw["b_u"])\ + self.popcount(self.bw["b_d"]) white = self.popcount(self.bw["w_u"])\ + self.popcount(self.bw["w_d"]) return black - white play オセロをプレイし最終結果を返します。 両プレイヤーが置ける場所がなくなった時点でゲームは終了します。こうすることで以下に示すオセロの終了条件をすべて満たします。 盤面がすべて埋まる 片陣営の石の個数が0になる 上のどちらの状況でもないが、両プレイヤーともに置ける場所がない def play(self) -> int: can, old_can = True, True can = self.check_all() while can or old_can: if can: if self.turn: self.think[self.black_method]() else: self.think[self.white_method]() self.turn = not self.turn old_can = can can = self.check_all() return self.count_last() run.ipynb from random import random, seed import matplotlib.pyplot as plt import AI 01 もっともよい結果だけ次世代へつなぐ 上述したクラスを用いて最適なA、Bを探します。 そのアルゴリズムを説明します。 10通りのA、Bをランダムに作成する。 そのA、Bに従うAIとnleastで二試合行う(黒側と白側をそれぞれ一回ずつ)。 二試合総合で、最も大差で勝利したA、Bをわずかに変化させて次世代のA、Bとする(3%の確率で突然変異)。 以下、指定した世代数まで繰り返し また、ついでに各世代ごとの勝利数を記録しておきます。 run = AI.AI(0, 0) run.read_goal = [1, 1] play_method = {"AI": run.PLAY_WAY["AI"], "nleast": run.PLAY_WAY["nleast"]} ################################ seed(0) generations = 50 children = 10 ab = [[random(), random()] for i in range(children)] player = list(play_method.values()) win_result = [] score = {"AI": [0] * children, "nleast": [0] * children} # 各世代 for g in range(generations): print("\r%d/%d" % (g+1, generations), end="") score = {"AI": [0] * children, "nleast": [0] * children} next = 0 win_num = 0 # 各人 for c in range(children): run.A = ab[c][0] run.B = ab[c][1] # 黒と白を入れ替えて二試合 for p in [0, 1]: run.black_method = player[p] run.white_method = player[not p] run.setup() result = run.play() if run.black_method == play_method["AI"]: score["AI"][c] += result score["nleast"][c] -= result else: score["AI"][c] -= result score["nleast"][c] += result if score["AI"][c] > score["nleast"][c]: win_num += 1 if c: if score["AI"][c] - score["AI"][c-1] > 0: next = c # 次世代のA、Bを作成 for c in range(children): if random() > 0.03: ab[c][0] = ab[next][0] + random() / 5 - 0.1 ab[c][1] = ab[next][1] + random() / 5 - 0.1 else: ab[c][0] = random() ab[c][1] = random() win_result.append(win_num) また、世代ごとの勝利回数の推移をグラフ化しました。 fig = plt.figure(figsize=(10, 10)) plt.ylabel("win num") plt.xlabel("generation") plt.plot(win_result) plt.grid() plt.savefig("fig/01") plt.clf() plt.close() 10人の子供で2試合ずつ行うため、1世代ごとの試合数は20です。 見たところ平均9程度なのでnleast相手に善戦してはいることが分かります。 しかしA、Bを少しずつ改善しているはずなので勝利数は世代ごとに高くなっていって欲しかったですが、そうはなっていません。 この方法はあまりよくないことが分かります。 なお、最終的なABは以下のようになっていました。 for ab_ele in ab: print(ab_ele) まず、十通りのABです。左がA、右がBです。 [0.23941609547807505, 0.35777572785685996] [0.2983086249263909, 0.46224661956749136] [0.1352297881214178, 0.4671043567151586] [0.174742860019351, 0.47745183099553845] [0.3091825692112029, 0.49155231282240563] [0.2667506602754828, 0.5135156230949472] [0.21879576811512422, 0.346266849934172] [0.17725439322589046, 0.4149851951459512] [0.29682016349473106, 0.49624233468089407] [0.18890073904354618, 0.4371081951750502] ab_arr = np.array(ab) ab_arr = ab_arr.T print(ab_arr[0].mean(), ab_arr[1].mean()) 次に、ABそれぞれの平均を計算します。 必然的に前の世代での最優秀とほぼ近い値が出るはずです。 0.23054016619112122 0.4464249045988469 02 もっともよい結果と二番目の結果を次世代につなぐ もっともよい結果のみを次世代につなぐのは局所解に陥りやすいと考え、もっともよいけっかだけでなく二番目によかった結果も次世代につないでみました。 プログラムについては01のものとほぼ同様であるため省略します。 こちらも01と同様に勝率は向上しませんでした。 最終的なABはこちら。 [0.4366761413226924, 0.363847389968887] [0.585117759367691, 0.3411556111862247] [0.6215485774727806, 0.3828643590646774] [0.553811235787369, 0.2539978233561665] [0.5499046820401436, 0.26588887637728187] [0.41797605296436946, 0.21303874673399606] [0.3941538926147179, 0.19443095633810717] [0.33549869473366334, 0.2621866288286896] [0.4232687188237969, 0.24411451234083645] [0.41047338094411434, 0.26471724953187703] 0.4728429136071338 0.2786242153726744 01ではAよりBの方が大きくなっていましたが、こちらでは逆の結果になりました。 もちろんどちらの結果も正しくはないのですが、「一位のみ引き継ぐ」と「一位と二位を引き継ぐ」というほぼ同じ検証で全く違う結果になったことが気になります。 おそらく前世代では二位だった結果が、マイナーチェンジすることにより一位の結果を抜かしたりした結果だと思われます。また、最終結果で似たような数字が並んでいるのは、一位と二位のABが大して変わらないからだと考えられます。結局局所解のような場所から抜け出せないという状態に近そうです。 03 トーナメントで次世代へつなぐ 20 ~遺伝的アルゴリズム、改善~、21 ~データ分析~で示したように、トーナメントは遺伝的アルゴリズムにおいて優秀な選択方法です。 そのため、順位で次世代へつなぐものを決めるのではなくトーナメントで決定することにしました。 ただし、遺伝的アルゴリズムでのトーナメントとは少し異なります。具体的に、十人の子供を二列に並べ、二試合後のスコアをペアで比較します。スコアが優秀だった方はそのまま次の世代に選出、そうでなかった方は優秀だった方の数値を少しずらしたものに変更としました。 具体的には以下のプログラムのとおりです。 seed(0) generations = 50 children = 10 ab = [[random(), random()] for i in range(children)] player = list(play_method.values()) win_result = [] score = {"AI": [0] * children, "nleast": [0] * children} for g in range(generations): print("\r%d/%d" % (g+1, generations), end="") score = {"AI": [0] * children, "nleast": [0] * children} win_num = 0 for c in range(children): run.A = ab[c][0] run.B = ab[c][1] for p in [0, 1]: run.black_method = player[p] run.white_method = player[not p] run.setup() result = run.play() if run.black_method == play_method["AI"]: score["AI"][c] += result score["nleast"][c] -= result else: score["AI"][c] -= result score["nleast"][c] += result if score["AI"][c] > score["nleast"][c]: win_num += 1 if c % 2 == 1: if random() > 0.03: if score["AI"][c] > score["AI"][c-1]: ab[c-1][0] = ab[c][0] + random() / 5 - 0.1 ab[c-1][1] = ab[c][1] + random() / 5 - 0.1 else: ab[c][0] = ab[c-1][0] + random() / 5 - 0.1 ab[c][1] = ab[c-1][1] + random() / 5 - 0.1 else: for i in range(2): ab[c-i][0] = random() ab[c-i][1] = random() win_result.append(win_num) 実行結果はこちら。 上二つと同様、勝率の向上は見られませんでした。 [0.1968494642692303, 0.2920115201230645] [0.26636557320745735, 0.23529047782429022] [0.5611248560008166, 1.0794628051551478] [0.5522261667279165, 1.1626702364057204] [0.3717229051582698, 0.32279192848978777] [0.4411758976785576, 0.39212233285307596] [0.2757179482011153, 0.6951279547710917] [0.1816633111544517, 0.6212004434604531] [0.49370925535099575, 1.2709043301267027] [0.5524299023585191, 1.2077682787268995] 0.389298528010733 0.7279350307936234 ABについて、トーナメントですので多様な値が出力されましたが、全体としての勝率は向上していません。 毎回同じペアでスコアの比較・ABの更新を行っていたため、上から順にふたつずつは似たような結果になっています。具体的に、 A: 0.2程度, B: 0.25程度 A: 0.55程度, B: 1.1程度 A: 0.4程度, B: 0.35程度 A: 0.2程度, B: 0.65程度 A: 0.5程度, B: 1.2程度 でした。 この中に最適解がある可能性はありますが、どれなのかは分かりません。 また、一つの解に収束することなくそれぞれで違う進化を遂げたことは意外でした。 04 トーナメント改良 上のトーナメントでは、十人の子供を二列に並べ、常に同じ相手同士でスコアを比較していました。 しかしそれでは全体として向上しづらいことが分かりました。また、ペアを同時に突然変異させてしまうとせっかくうまくいっていたペアを初期化してしまう危険性もあります。 そこで、世代ごとにペアを変え、いろいろな相手とスコアを比較させることで全体の強さを底上げしていくことを考えました。 from random import randint ################################ run = AI.AI(0, 0) run.read_goal = [1, 1] play_method = {"AI": run.PLAY_WAY["AI"], "nleast": run.PLAY_WAY["nleast"]} ################################ def key_list(): global children key = [i for i in range(children)] key_return = [] while key: num = randint(0, len(key)-1) key_return.append(key[num]) del key[num] return key_return 上に示すkey_list関数を使うことで、0~children-1までの数をランダムに並べた配列を受け取れます。 その番号順に並べてスコアを比較することで、毎回同じ相手と比較することがなくなります。 seed(0) generations = 50 children = 10 ab = [[random(), random()] for i in range(children)] player = list(play_method.values()) win_result = [] score = [0] * children for g in range(generations): print("\r%d/%d" % (g+1, generations), end="") score = [0] * children win_num = 0 for c in range(children): run.A = ab[c][0] run.B = ab[c][1] for p in [0, 1]: run.black_method = player[p] run.white_method = player[not p] run.setup() result = run.play() if run.black_method == play_method["AI"]: score[c] += result if result > 0: win_num += 1 else: score[c] -= result if result < 0: win_num += 1 key = key_list() for i in range(int(len(key) / 2)): if random() > 0.03: if score[key[i]] > score[key[(i<<1)+1]]: ab[key[(i<<1)+1]][0] = ab[key[i<<1]][0] + random() / 5 - 0.1 ab[key[(i<<1)+1]][1] = ab[key[i<<1]][1] + random() / 5 - 0.1 else: ab[key[i<<1]][0] = ab[key[(i<<1)+1]][0] + random() / 5 - 0.1 ab[key[i<<1]][1] = ab[key[(i<<1)+1]][1] + random() / 5 - 0.1 else: for j in range(2): ab[key[i<<1]][j] = random() ab[key[(i<<1)+1]][j] = random() win_result.append(win_num) 上に示すプログラムを用いることで、毎回ペアを変えながらトーナメントを行うことができます。 これを実行したときの各世代ごとの勝利数は以下の通りです。 今までよりも平均としての勝率は上がったような気はしますが、それでも向上はしませんでした。 [0.7845317965203, 0.7282481006185132] [0.887998591216725, 0.7798613691517554] [0.7539896409299109, 0.5864060138323008] [0.7994245226477315, 0.6976976619352092] [0.8580297286849564, 0.7568389384151408] [0.7436182363961433, 0.64724601430552] [0.15306714563572918, 0.43333188307689097] [0.12307210319871517, 0.5095366112833001] [0.12901552090490803, 0.44081907902404904] [0.0866980084219135, 0.534229302808782] 0.5319445294557033 0.6114214974451462 ABは想像よりバラエティーに富んだ結果でした。 おおまかに A: 0.8程度, B: 0.7程度 A: 0.1程度, B: 0.5程度 という二つの結果に分かれているように見えます。 これらは01、02の結果と合わず、また03の五通りの結果のどれとも合致しません。 完全に独自の進化を遂げましたが、これも最適解ではなさそうに見えます。 05 対戦数を増やす 上に示した方法で勝率が改善しなかったのは、ABの適切さを判断するための試合数が2つしかなく、少ない試料で決定していたことが原因だと考えました。 そのため、ABに従うAIを、random、nhand、nmost、nleastと対戦させ、その後白黒反転してもう一度対戦の計8試合行います。そしてその結果でABの適切さを判断することを考えました。 nhand_customと対戦させることも考えましたが、おそらく膨大な実行時間になってしまうこと、またプログラムが複雑になってしまうという懸念があったためやめました。 また、改良後のトーナメントを用いて次世代へつなぐことも考えましたが、対照実験のためまずは最初に行ったトーナメント方法を採用しました。 seed(0) generations = 50 children = 10 ab = [[random(), random()] for i in range(children)] ai = run.PLAY_WAY["AI"] other = [run.PLAY_WAY["random"], run.PLAY_WAY["nhand"], run.PLAY_WAY["nmost"], run.PLAY_WAY["nleast"]] win_result = [] score = [0] * children for g in range(generations): print("\r%d/%d" % (g+1, generations), end="") score = [0] * children win_num = 0 for c in range(children): run.A = ab[c][0] run.B = ab[c][1] for p in [0, 1]: for o in other: if p: run.black_method = ai run.white_method = o else: run.black_method = o run.white_method = ai run.setup() result = run.play() if run.black_method == run.PLAY_WAY["AI"]: score[c] += result if result > 0: win_num += 1 else: score[c] -= result if result < 0: win_num += 1 if c % 2 == 1: if random() > 0.03: if score[c] > score[c-1]: ab[c-1][0] = ab[c][0] + random() / 5 - 0.1 ab[c-1][1] = ab[c][1] + random() / 5 - 0.1 else: ab[c][0] = ab[c-1][0] + random() / 5 - 0.1 ab[c][1] = ab[c-1][1] + random() / 5 - 0.1 else: for i in range(2): ab[c-i][0] = random() ab[c-i][1] = random() win_result.append(win_num) 実行結果はこちら。 1人8試合x10人いるので一世代の総合数は先ほどの4倍の80試合です。また、対戦相手はnleastだけでなくrandomなども含むためこれまでの実験と比べ勝率は上がっていますが、世代間での向上は見られません。 また、試合数が増えたことにより相応に実行時間も倍増しました。Javaを早く習得したいです。 [1.02724256462233, 0.26337275542624516] [1.009950479397224, 0.2159791032577633] [0.22922242678565338, 0.9734637632138236] [0.13223405592320084, 0.9543661981037578] [-0.06376729223249714, 0.8300427714479159] [-0.15341627116082962, 0.7539450770368438] [0.24111965717074832, 0.5558870178965837] [0.17822858286954305, 0.49821795165496663] [0.02748397669100308, 0.015100622809889594] [-0.06205959166701412, 0.01733021427322594] 0.25662385883993616 0.5077705475121015 A: 1.0程度, B: 0.25程度 A: 0.2程度, B: 1.0程度 A: -0.1程度, B: 0.8程度 A: 0.2程度, B: 0.5程度 A: 0.0程度, B: 0.0程度 今までにない曲者がそろいました。 対戦相手の種類が増えたことで対応しなければならない戦術が増えたことが原因だと思われます。 06 対戦数を増やし、トーナメント改良 04のトーナメント改良と05の対戦数を増やすを合体させました。 プログラムは継ぎはぎですので省略します。 最初は横ばいに見えますが、35世代付近から上がっているようにも見えます。 [0.20405658462660622, 0.1680605186286642] [0.5067586035335895, 0.2257166330359812] [0.506989666989787, 0.39973170956614057] [0.14497172001539446, 0.1968499870887512] [0.43622213588550574, 0.2515135205850171] [0.05490792986502008, 0.32594965169802903] [0.17250523018856503, 0.34862524795583516] [0.10559360468888276, 0.2670811780558886] [0.10494083678931704, 0.2684445529286096] [0.4517255898186786, 0.3276437286310824] 0.2688671902401346 0.2779616728173999 A: 0.5程度, B: 0.3程度 A: 0.15程度, B: 0.25程度 A: 0.1程度, B: 0.3程度 対戦数を増やす前のトーナメント改良(04)と比べ、いろいろな結果が出ました。 これも05と同様、対戦相手の種類が増えたことが原因ではないかと考えます。 Bがひとつも0.4を超えていないことが気になります。 07 対戦数を増やし、トーナメント改良し、世代数を増やす 06のグラフで、35世代以降勝率が向上しているように見えなくもないので追加検証してみました。 たしかに35~60世代にかけて勝率が向上しているように見えますが、偶然このような形になっただけにも見えます。 [0.33151187738915355, 0.06057437681006733] [0.3678616364493409, 0.12723243895942166] [0.5198915583730699, 0.04268710109725918] [0.44061206459599045, 0.051610048696537625] [0.46808837087950583, 0.7329708184903793] [0.5073052040093725, 0.1718749134495976] [0.3635208255702669, 0.08699886357653655] [0.40261513162926466, 0.04176901447947265] [0.2642246747320115, 0.23195360857080813] [0.1696507008061556, 0.1759563678482843] 0.38352820444341323 0.17236275519783642 一定の特徴はあまり見られず、 A:0.5程度, B: 0.0程度 A:0.4程度, B: 0.0程度 の二つだけでした。 世代数を増やした結果、かなり迷走することになってしまったようです。 フルバージョン 次回は 現在の方法ではうまくいきませんでしたので、別の方法を考え最適なABを求めます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSeleniumとTorの合わせ技

前置き 2021年10月頃の ↓ コレをアップデートしたもの。 自分が書いた過去の記事を再度見ると 「回りくどいことやってんなー」とかあるあるなんですかね Githubにもあげたが、使い勝手が分からず投げ出したい。 https://github.com/kawagoe6884/scraping-with-tor README Name scraping-with-tor アクセス過多によるサーバーから接続規制を回避する。 DEMO 用意する予定は今のところ無し。 Features とある1行のコメントアウトの切り替えでTorを使うか使わないか決める。 気晴らし程度だが、ヘッダーにランダムなユーザーエージェント。 ついでに ”navigator.webdriver=undefined” の設定も織り込み済み。 Requirement Windows10 64bit Python 3.8.7 64-bit selenium 3.141.0 fake-useragent 0.1.11 webdriver-manager 3.5.2 Installation cmd pip install fake-useragent pip install webdriver_manager Usage python url = 'https://www.google.com/' driver = Selenium(url) print(driver.page_source) Note tor.exe が必要。main.pyでは適当なパスを通しているので必要に応じて変更すること。 Author 作成者: kawagoe6884 所属: E-mail: Special thanks このテンプレートの参照先URL 以下、コード main.py # coding:utf-8 import subprocess import time import getpass import random from fake_useragent import UserAgent from selenium.webdriver.chrome.options import Options from selenium import webdriver from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # ------ Tor の起動 ------ def Start_tor(): global running # kill subprocess.call(r'taskkill /F /T /IM tor.exe', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(1) # run Tor = f'C:\\Users\\{getpass.getuser()}\\Desktop\\Tor Browser\\Browser\\TorBrowser\\Tor\\tor.exe' running = subprocess.Popen(Tor) time.sleep(1) # ------ Selenium の起動 ------ def Start_selenium(): # kill subprocess.call(r'taskkill /F /T /IM chromedriver.exe', stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) time.sleep(1) # chrome-option options = Options() if 'running' in globals(): # Tor のProxy設定を通す options.add_argument('--proxy-server=socks5://localhost:9050') # User-Agentを定義する UA = UserAgent().chrome options.add_argument('--user-agent=' + UA) options.add_argument('--headless') options.add_argument('--start-maximized') options.add_argument('--disable-blink-features') options.add_argument('--disable-blink-features=AutomationControlled') options.add_argument('--disable-browser-side-navigation') options.add_argument('--disable-dev-shm-usage') options.add_argument('--disable-extensions') options.add_argument('--disable-gpu') options.add_argument('--disable-infobars') options.add_argument('--ignore-certificate-errors') options.add_argument('--ignore-ssl-errors') options.add_argument('--no-sandbox') prefs = {'profile.default_content_setting_values.notifications' : 2} options.add_experimental_option('prefs', prefs) options.add_experimental_option("excludeSwitches", ['enable-automation']) # navigator.webdriver=undefined とする設定 # run driver = webdriver.Chrome(ChromeDriverManager(log_level=0, print_first_line=False).install(), options=options) # set driver.implicitly_wait(60) driver.set_window_size('1920', '1080') return driver # ------ driver.getのループ ------ def Selenium(url): global driver # Webアクセスのリトライ回数を指定する retry_count = 5 try: for count in range(retry_count + 1): count += 1 # リトライ回数の上限を超えた場合はエラーにする if count > retry_count: raise Exception('リトライ回数の上限を超えました') # driver.get ⇒ 待機、エラーならtorとselenium起動 try: driver.get(url) except: # Start_tor() # Tor 使わないならコメントアウト driver = Start_selenium() else: WebDriverWait(driver, 10).until(EC.presence_of_all_elements_located) sec = random.uniform(1.0, 3.0) time.sleep(sec) break except Exception as e: raise e return driver
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Terraformチュートリアル

この記事について TerraformでAWSのインフラ環境を構築する手順を、チュートリアル形式でまとめてみました。 STEP1:AWSアカウント登録 まずはAWSアカウントを作成するところから始めましょう。 STEP2:Cloud9環境構築 AWSのクラウド環境(Cloud9)を使用して、Terraformを実装・実行します。 Cloud9環境の環境を構築し、セットアップをしていきましょう。 Cloud9環境自体の利用は無料であり、すべての作業をクラウド上で行える点がメリットです。 TerraformやPythonがデフォルトでインストールされており(バージョンアップは別途必要)、インストール等のセットアップをせずに作業を開始することができます。 STEP3:Terraformとは Terraformの概要についてまとめました。 STEP4:Terraformセットアップ Cloud9環境でTerraformを利用するための最低限のセットアップを行っていきます。 STEP5:Terraform基本コマンド CUI(Cloud9等)で実行するTerraformの基本コマンドを紹介します。 STEP6:Terraform構成管理 Terraformの基本的なディレクトリ構成や、各ファイルの特性、ファイル間の関係性について解説します。 STEP7:Terraform基本ブロック TerraformでAWS設定を定義する「ブロック」という概念を紹介します。 基本的なブロックについて解説します。 STEP8:Terraform基盤構築:全体概要編 今回構築したアーキテクチャの全体概要を紹介します。 STEP9:Terraform基盤構築:ETL編 今回構築したETL基盤の実装手順、定義情報等について解説します。 STEP10:Terraform基盤構築:イベントソース編 今回構築したログ管理基盤(イベントソース)の実装手順、定義情報等について解説します。 実装コード 使用したデータセット 参考サイト(Terraform公式) Terraform Language Documentation ※Terraform各コマンドの公式ドキュメント Terraform : AWS provider ※AWSプラットフォーム用のTerraformコマンドやパラメータの公式ドキュメント 参考資料(AWS公式) AWSドキュメントトップ ※いろいろなドキュメントの索引です。 AWSデベロッパーガイド(Lambda) ※Lambdaについての辞書 AWSデベロッパーガイド(Step Function) ※StepFuncitionsについての辞書
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ニュース記事をスクレイピング

はじめに Googleのニュースサイト( https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en )のデータを取得し、翻訳し、Firebaseに保管する方法について、まとめていきます。 Pythonを用いていきます。適宜、必要なライブラリは、インストールします。 必要なライブラリは、下の二つです。 - beautifulsoup4 - requests 目次 ニュースサイト情報 スクレイピングのコード 参考文献 ニュースサイト情報 ニュース記事は、https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en から取得します。開いたイメージは次のようになっています。 urlを変化させることで、検索ワードに関する記事を取得できる他、ニュースサイトの指定、期間の指定を行えます。具体的な手法は、下を参考にしてください。 参考: Google News Rss(API) スクレイピングのコード スクレイピングには、主に2種類の方法が存在します。あくまで、使ってみた経験で話していきます。 1. urllib.requestとBeautifulSoupを使う方法 2. Seleniumを使う方法 1 を使うのは、比較的簡単なWebページの場合に限られます。現在のWebページの多くは、Java Scriptなどを用いた、動的なページが多いです。動的なページでは、うまく種得できません。今回のニュースページは、明らかに、人が閲覧するためのサイトではなく、スクレイピングするためのページです。ですので、今回は1 を使っていきます。 2 を使う場合が多いと、個人的には感じています。2 は、Google Chromeで検証ページに表示されるHTMLをそのまま、取得できます。汎用的な方法で、あらゆるタイプのサイトに使えます。しかし、処理が遅いことや、Chrome driverを適切なバージョンにしておかなければなりません。 実際のコードは次のようになります。 get_news.py from urllib import request from bs4 import BeautifulSoup url = "https://news.google.com/rss?hl=en-US&gl=US&ceid=US:en" response = request.urlopen(url) soup = BeautifulSoup(response,"xml") response.close() articles = soup.find_all("item") urlを定義する urlのページを開いた情報を取得する 取得した情報をxml形式で、情報変化する その中で、itemタグを持つデータを抜き出す itemタグで、記事のデータがまとめられている。取得すると、一つ一つの記事は、次のようになっています。タイトルとid、投稿日時、説明、ソースがまとめられています。 itemの中身 <item> <title>Biden says he's "not sure" about voting bills' future after Sinema reiterates opposition to rule change - CBS News</title> <link>https://news.google.com/__i/rss/rd/articles/CBMiTmh0dHBzOi8vd3d3LmNic25ld3MuY29tL25ld3Mva3lyc3Rlbi1zaW5lbWEtdm90aW5nLXJpZ2h0cy1zZW5hdGUtcnVsZXMtc3BlZWNoL9IBAA?oc=5</link> <guid isPermaLink="false">1222244172</guid> <pubDate>Thu, 13 Jan 2022 23:58:00 GMT</pubDate> <description>説明部分は長いので、省略</description> <source url="https://www.cbsnews.com">CBS News</source> </item>, そして、この記事ごとのデータを取得する方法としては、下に示します。下では、タイトルを取得する方法です。"title"の部分を、linkやsourceに変えて、欲しいデータを取得します。 get_news.py title = item.find("title").getText() 参考文献 ニュースサイト Google News Rss(API)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Windows10で埋め込みPythonパッケージ(Python Enbedded distribution)の作成

はじめに Pythonプログラムを配布する際、顧客にPythonランタイムをインストールしなくてもいいように組み込みパッケージを作成します。 ただし以下のランタイムのインストールは必要です。 注釈 埋め込み用配布には Microsoft C Runtime は含まれません。これを提供するのはアプリケーションのインストーラの責務です。そのランタイムは既に以前にユーザのシステムにインストール済みかもしれませんし、Windows Update により自動で更新されているかもしれません。このことはシステムディレクトリに ucrtbase.dll があるか探せばわかります。 環境 windows10 python-3.10.1-embed-amd64 手順 Pythonのダウンロード 公式からダウンロードします。 (今回はpython-3.10.1-embed-amd64をダウンロードしました。) 以降、PowerShellで操作します。 PS C:\home\work> mkdir tools ディレクトリ: C:\home\work Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2022/01/14 9:41 tools PS C:\home\work> wget https://www.python.org/ftp/python/3.10.1/python-3.10.1-embed-amd64.zip -o tools/python-3.10.1-embed-amd64.zip PS C:\home\work> cd .\tools\ PS C:\home\work\tools> Expand-Archive -Path .\python-3.10.1-embed-amd64.zip PS C:\home\work\tools> rm .\python-3.10.1-embed-amd64.zip python310._pthを編集 適当なエディタで編集します。 import siteのコメントアウトをします。 python310.zip . # Uncomment to run site.main() automatically import site pipのインストール (pythonのフォルダへ移動) PS C:\home\work\tools> cd .\python-3.10.1-embed-amd64\ PS C:\home\work\tools\python-3.10.1-embed-amd64> wget "https://bootstrap.pypa.io/get-pip.py" -O "get-pip.py" PS C:\home\work\tools\python-3.10.1-embed-amd64> .\python.exe .\get-pip.py Collecting pip Using cached pip-21.3.1-py3-none-any.whl (1.7 MB) ・・・ Successfully installed pip-21.3.1 setuptools-60.5.0 wheel-0.37.1 お試しでpipでrequestsをインストール PS C:\home\work\tools\python-3.10.1-embed-amd64> .\python.exe -m pip install requests Collecting requests ・・・ Successfully installed certifi-2021.10.8 charset-normalizer-2.0.10 idna-3.3 requests-2.27.1 urllib3-1.26.8 PS C:\home\work\tools\python-3.10.1-embed-amd64> \Lib\site-packagesにpipでインストールされていることを確認。作業直後には以下のようになっています。 PS C:\home\work\tools\python-3.10.1-embed-amd64> ls .\Lib\site-packages\ ディレクトリ: C:\home\work\tools\python-3.10.1-embed-amd64\Lib\site-packages Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2022/01/14 9:57 certifi d----- 2022/01/14 9:57 certifi-2021.10.8.dist-info d----- 2022/01/14 9:57 charset_normalizer d----- 2022/01/14 9:57 charset_normalizer-2.0.10.dist-info d----- 2022/01/14 9:57 idna d----- 2022/01/14 9:57 idna-3.3.dist-info d----- 2022/01/14 9:54 pip d----- 2022/01/14 9:54 pip-21.3.1.dist-info d----- 2022/01/14 9:54 pkg_resources d----- 2022/01/14 9:57 requests d----- 2022/01/14 9:57 requests-2.27.1.dist-info d----- 2022/01/14 9:54 setuptools d----- 2022/01/14 9:54 setuptools-60.5.0.dist-info d----- 2022/01/14 9:57 urllib3 d----- 2022/01/14 9:57 urllib3-1.26.8.dist-info d----- 2022/01/14 9:54 wheel d----- 2022/01/14 9:54 wheel-0.37.1.dist-info d----- 2022/01/14 9:54 _distutils_hack -a---- 2022/01/14 9:54 151 distutils-precedence.pth 参考文献 超軽量、超高速な配布用Python「embeddable python」 https://qiita.com/mm_sys/items/1fd3a50a930dac3db299 Windows 上に Python3.7 embeddable と pip をインストールする https://bamch0h.hatenablog.com/entry/2019/09/29/162601 公式 3. Windows で Python を使う — Python 3.6.4 ドキュメント 4.4. 埋め込み可能なパッケージ https://docs.python.org/ja/3/using/windows.html#the-embeddable-package
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Copy spanning locations and/or storage classes could not complete within 30 seconds. Please use the Rewrite method...ってなんだよ

PythonでGCSのファイルをコピーしようとしたらこんなエラーが出ました。 from google.cloud import storage storage_client = storage.Client() src_bucket = storage_client.bucket(BUCKET_NAME) dst_bucket = storage_client.bucket(OUTPUT_BUCKET_NAME) src_file_name = "hoge.gz" dst_file_name = "hoge.gz" src_blob = src_bucket.blob(src_file_name) new_blob = dst_bucket.copy_blob(src_blob, dst_bucket, new_name=dst_file_name, timeout=180) new_blob.acl.save(src_blob.acl) エラー内容 Copy spanning locations and/or storage classes could not complete within 30 seconds. Please use the Rewrite method (https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite) instead. なんかtimeout=180も効いてないみたいです。 どうやらコピーしようとしているファイルが数GBと少々大きい場合このようなエラーが出るそうです。 メッセージにあるようにrewriteメソッドを使えとあります。 書き直してみました。 from google.cloud import storage storage_client = storage.Client() src_bucket = storage_client.bucket(BUCKET_NAME) dst_bucket = storage_client.bucket(OUTPUT_BUCKET_NAME) src_file_name = "hoge.gz" dst_file_name = "hoge.gz" src_blob = src_bucket.blob(src_file_name) dst_blob = dst_bucket.blob(dst_file_name) rewrite_token = None while True: rewrite_token, bytes_rewritten, total_bytes = dst_blob.rewrite(src_blob, token=rewrite_token) progress_percent = (bytes_rewritten * 100) // total_bytes print(f"Progress : {bytes_rewritten} / {total_bytes} bytes {progress_percent}%.") if rewrite_token is None: print("Copy has done !!") break 実行してみると Progress : 461373440 / 1899448698 bytes 24%. Progress : 964689920 / 1899448698 bytes 50%. Progress : 1509949440 / 1899448698 bytes 79%. Progress : 1899448698 / 1899448698 bytes 100%. Copy has done !! 気になったrewrite_tokenはなんかランダムな値でしたので省略します。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ValueError: ('Iterator has already started', <google.api_core.page_iterator.HTTPIterator object at 0xaaaaaaa>)

pythonでCloud Storageのファイル数を数えようとしたらこんなエラーがでました。 なんなんだと。 File "/........./lib64/python3.6/site-packages/google/api_core/page_iterator.py", line 227, in __iter__ raise ValueError("Iterator has already started", self) ValueError: ('Iterator has already started', <google.api_core.page_iterator.HTTPIterator object at 0x7fc844c4aeb8>) そもそものコードはこちらです。 やりたいことは 対象フォルダのファイルを探す。 ファイルが無ければ終了。 ファイルがあればファイル名を表示する。 というものです。 from google.cloud import storage import sys storage_client = storage.Client() bucket = storage_client.bucket(BUCKET_NAME) ### 集計対象のファイルを探す blobs = bucket.list_blobs( prefix=TARGET_GCS_FOLDER + '/hoge_' ) if len(list(blobs)) == 0: print("Error, No target files....") sys.exit(1) for item in blobs:  # <--- ここ!!! print(item.name) そんでどうやらエラーの箇所はblobオブジェクトをイテレートしてファイル名を表示するところでした。 なぜかと? どうやらblobオブジェクトのイテレーターは1回しか使えないみたいです。 File "/........./lib64/python3.6/site-packages/google/api_core/page_iterator.py"についてコードを見てみると class Iterator(object): ................... def __init__( self, client, item_to_value=_item_to_value_identity, page_token=None, max_results=None, ): self._started = False # <--- ここ!! self.client = client ....................... def __iter__(self): """Iterator for each item returned. Returns: types.GeneratorType[Any]: A generator of items from the API. Raises: ValueError: If the iterator has already been started. """ if self._started: raise ValueError("Iterator has already started", self) # <--- ここ!!! self._started = True return self._items_iter() とあります。 一度__iter__が呼ばれてしまうとself._startedがTrueになりValueErrorがなってしまうことがわかります。 ここで最初の__iter__ですが if len(list(blobs)) == 0: でリスト数を数えています。このlist(...)で__iter__が呼ばれているので、すでにself._startedがTrueとなっていたわけです。 なので一度別のリスト型変数に入れなおしてからやればエラーになりません。 blob_list_obj = list(blobs) if len(blob_list_obj) == 0: print("Error, No target files....") sys.exit(1) for item in blob_list_obj: print(item.name) 参考ページ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonの実行速度比較

初めに Pythonが実行環境によってどれくらい速度差があるのかを調べてみました(ついでにC#も)。 実行マシン 機種 CPU RAM Video Python C# mac mini 2018 Intel Core i5 3GHz 6c 16GB 内蔵Chipset 3.8.9 venv .NET Core 6.0.101 コード 辞書に1000万回数字を書き込むだけのコードです。例: ct['1000'] = 1000 main.py import time ct = {} t1 = time.time() for i in range(0, 10000000): ct[str(i)] = i t = time.time() - t1 print("Time={0}".format(t)) Program.cs using System.Collections.Generic; var ct = new Dictionary<string, uint>(); var sw = new System.Diagnostics.Stopwatch(); sw.Start(); for(uint i=0; i<10000000; i++) { ct[i.ToString()] = i; } sw.Stop(); TimeSpan ts = sw.Elapsed; Console.WriteLine($"{ts}"); 結果 Python 環境 実行時間[秒] 備考 nuitkaでビルドしたバイナリ 3.67 nuitka3 main.py --onefileファイルサイズ6.4MB ターミナルで実行 5.07 python main.py VSCodeのターミナルで実行 5.25 python main.py VSCode・Breakpointなしでデバッグなし実行 5.27 Ctrl-F5 VSCode・Breakpointなしでデバッグ実行 5.67 F5 VSCode・Breakpointありでデバッグ実行 40.86 F5Breakpointは最終行に設置 C# 環境 実行時間[秒] 備考 Release Build 1.92 ターミナルで実行 2.13 dotnet run VSCode F5 2.19 C#のDictionaryをHashtableに変更したもの 環境 実行時間[秒] 備考 ターミナルで実行 13.98 dotnet run Release Build 14.00 VSCode F5 14.66 まとめ 1) Python バイナリが最速だったがインタプリタ実行と2倍も変わらなかった(1.38〜1.55倍)。 「Breakpointありのデバッグ実行」はめっっっっっちゃ遅い。ループをぶん回すような処理のデバッグにはご注意ください。 2) C# 表には記載していないがBreakpointの有無による差はなかった(すごくない?)。 同じような用途でもクラスを間違えるとパフォーマンスにえらい差が見られた(例: DictionaryとHashtable)。 Pythonより速いが、起動時間に数秒かかるので実際の時間は表の数値より大きい。起動時間こみで計測してみると以下のようにPythonとC#でほぼ一緒だった。 time python main.py ---> 5.35秒 time dotnet run ---> 5.18秒
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pydocの使い方

pydocを使ってドキュメント生成をしてみたので簡単にメモしておく。 pydocの使い方 pydocは、Pythonに標準で入っており、docstringで書かれたコメントからドキュメントを作成することができる。 JavaのJavadocのようなもので、HTML形式で出力することもできる。 ドキュメントを出力するには以下のコマンドを実行する。 // 画面表示する場合 $ pydoc <ファイル名> // HTML形式で出力する場合 $ pydoc -w <ファイル名> HTML形式の場合は、実行すると「wrote <ファイル名>.html」と表示され、HTML形式のファイルが出力される。 ここで、ファイル名を指定する際には拡張子「.py」は不要である。 また、windowsではPythonをインストールしただけではpydocコマンドが使えないらしい。 windowsでpydocコマンドを使うにはpython.exeと同じディレクトリにバッチファイルを作成する必要がある。 バッチファイルの内容は以下となる。 @python %~dp0\Lib\pydoc.py %*
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python: VSCodeからDocker内で動いているFastAPIにステップ実行デバッグを仕掛ける

試した環境 開発環境(端末) Macbook Air (M1) VSCode November 2021 (version 1.63) Python 3.9.9 (homebrewでインストールした) FastAPIサーバ(Docker) Docker Desktop for Mac 4.2.0 Docker version 20.10.12 docker-compose version 1.29.2 コンテナ python:3.11-rc linux/x86-64 pipでインストールしたライブラリ Package Version anyio 3.5.0 asgiref 3.4.1 click 8.0.3 fastapi 0.71.0 h11 0.12.0 idna 3.3 pip 21.2.4 pydantic 1.9.0 python-multipart 0.0.5 setuptools 57.5.0 six 1.16.0 sniffio 1.2.0 starlette 0.17.1 typing_extensions 4.0.1 uvicorn 0.16.0 wheel 0.37.0 VSCodeのプロジェクト構成 .vscode/ launch.json settings.json src/ ... debug-docker-compose.yml debug.Dockerfile debug.Dockerfile FROM --platform=linux/x86-64 python:3.11-rc RUN apt update -y RUN apt-get install build-essential RUN pip install fastapi uvicorn python-multipart RUN pip install debugpy COPY src /src WORKDIR /src EXPOSE 8000 CMD ["python3", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "uvicorn", "prog:app", "--reload", "--host", "0.0.0.0", "--port", "8000"] debug-docker-compose.yml version: '3.7' services: server: container_name: myfastapi build: context: . dockerfile: ./debug.Dockerfile restart: always volumes: - ./src:/src expose: - "8000" - "5678" ports: - 8000:8000 - 5678:5678 launch.json { // IntelliSense を使用して利用可能な属性を学べます。 // 既存の属性の説明をホバーして表示します。 // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ { "type": "python", "request": "attach", "name": "FastAPI Remote Debug", "port": 5678, "host": "localhost", "pathMappings": [ { "localRoot": "${workspaceFolder}/src", "remoteRoot": "/src" } ] } ] } settings.json { "python.pythonPath": "/opt/homebrew/bin/python3", "files.watcherExclude": { "**/homebrew/**": true } } 手順 FastAPIコンテナのビルド設定 [debug.Dockerfile] debugpyインストールを含めておく RUN pip install debugpy [debug.Dockerfile] APIサーバの起動コマンドにdebugpyの待ち受けを入れ込む 通常はこんな感じだと思うが、 CMD ["uvicorn", "prog:app", "--reload", "--host", "0.0.0.0", "--port", "8000"] ↓こう変える。 CMD ["python3", "-m", "debugpy", "--listen", "0.0.0.0:5678", "-m", "uvicorn", "prog:app", "--reload", "--host", "0.0.0.0", "--port", "8000"] [debug-docker-compose.yml] debugpyの待ち受けポートをコンテナの外に解放するよう設定する expose: - "5678" ports: - 5678:5678 VSCodeの設定 [launch.json] debugpyに接続する構成を登録しておく { "type": "python", "request": "attach", "name": "FastAPI Remote Debug", "port": 5678, "host": "localhost", "pathMappings": [ { "localRoot": "${workspaceFolder}/src", "remoteRoot": "/src" } ] } FastAPIコンテナを起動する docker-compose -f debug-docker-compose.yml up --build VSCodeからdebugpyに接続する launch.jsonに登録した「FastAPI Remote Debug」構成を実行する 繋がると↓こうなる ソースコードにブレークポイントを設定してみる APIを実行してみる 普通にブラウザで開くでもおk 処理が一時停止してVSCodeのデバッグビューがアクティブになる その瞬間の変数の中身が見れたり、次のブレークポイントまで進んだり、一行ずつ進んだりしながらじっくり観察できる その時だけ変数を勝手に書き換えて処理を続行させたりもできる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

NumPyの乱数・シャッフル・ランダム抽出(`np.random`)まとめ

NumPyの乱数・シャッフル・ランダム抽出(np.random)まとめ 基本的な使い方 np.random.default_rng()で、乱数生成器オブジェクトをインスタンス化したのち、各種メソッドで乱数を発生できます。 import numpy as np # 乱数生成器オブジェクトの作成 rng = np.random.default_rng() # 乱数の作成 rfloat = rng.random() print(rfloat) 以下乱数生成器オブジェクトを変数rngとして説明します。 シード np.random.default_rng()にはシード値を渡すことができます。 NumPyの乱数生成器は擬似乱数といって、本当のランダムではありません。極端にいえば$y=f(x)$のような関数になっており、初期条件$x$が決まれば結果$y$が決まっていますが、通常$x$には実行したときの現在時刻などが入るために、結果がランダムになっています。 私達がサイコロを振るとき普通は適当に投げるので結果はランダムですが、もし完全に同じ位置から同じ角度・速度で投げたら(それが可能だとしたら)、決まった目が出るというようなイメージをしてください。 シード値を渡すと、同じ位置・角度・速度でサイコロを投げることができます。以下を何度か実行してみてください。 random_seed.py import numpy as np # シードを設定しない場合 rng = np.random.default_rng() print('↓実行するたび値が変わる') print('1投目: ', rng.integers(1, 7)) print('2投目: ', rng.integers(1, 7)) print('3投目: ', rng.integers(1, 7)) # シードを設定 rng = np.random.default_rng(12345) print('↓毎回同じ結果(5と2と5)になる') print('1投目: ', rng.integers(1, 7)) print('2投目: ', rng.integers(1, 7)) print('3投目: ', rng.integers(1, 7)) なお、たまに勘違いする方がいますが、1投目と2投目が同じ結果になるというわけではありません。何度やっても、1投目同士、2投目同士、…、N投目同士が必ず同じ結果になるということです。 random_seed2.py import numpy as np rng1, rng2 = np.random.default_rng(123), np.random.default_rng(123) # 以下は無限ループになる while rng1.integers(1, 7) == rng2.integers(1, 7): pass 乱数値・配列を新規作成 ランダム整数値・配列を作成 - .integers() rng.integers(low, high=None, size=None, dtype=np.int64, endpoint=False) 引数 説明 low 結果が取り得る値の最小値(0のとき省略可能) high 結果が取り得る値の最大値 size 結果の配列の形。省略するとスカラー値 dtype 結果配列のデータ型。デフォルトはint64 endpoint デフォルトはFalseで右開区間。Trueにすると閉区間 例 >>> rng = np.random.default_rng(0) >>> rng.integers(1, 7) 6 >>> rng.integers(1, 7, 10) array([4, 4, 2, 2, 1, 1, 1, 2, 5, 4], dtype=int64) >>> rng.integers([10, 20, 30]) array([ 9, 10, 18], dtype=int64) lowには最小値・highには最大値を設定しますが、ここで、結果が取り得る値はlow以上high未満になることに注意してください(range()関数のstart/stop引数をイメージしてください)。 rng.integers(1, 7) # 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない low=0の場合は省略できます。 rng.integers(0, 7) rng.integers(7) # どちらも結果は{0, 1, 2, 3, 4, 5, 6}のいずれか 結果が取り得る値をlow以上high以下にするには、endpoint引数をTrueに指定します。 rng.integers(1, 7) rng.integers(1, 7, endpoint=False) # どちらも結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない rng.integers(1, 7, endpoint=True) # 結果は{1, 2, 3, 4, 5, 6, 7}のいずれか なおlow/highには小数を渡すこともできます。この場合小数点以下は切り捨てられて解釈されます。この使い方は推奨しません。また浮動小数の精度には注意してください。 rng.integers(1, 7.9) # 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない rng.integers(5, (0.7+0.1)*10) # 結果は{1, 2, 3, 4, 5, 6}のいずれかで、`7`は出ない size引数を省略するかNoneを指定すると(ここまでの例のように)結果はスカラー値になります。 size引数に整数を渡すと、その長さの1次元配列が返却されます。タプルを渡すと2次元以上の配列も作成できます。 rng.integers(1, 7, 10).shape # -> (10,) rng.integers(1, 7, (3, 5)).shape # -> (3, 5) 配列の要素ごとに取りうる値の区間を変更したい場合は、low/high引数をリストにします。 rng.integers([0, 10, 20], [10, 20, 30]) rng.integers([0, 10, 20], [10, 20, 30], size=3) # どちらも以下にほぼ等しい np.array([rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)]) lowの長さ/highの長さ/sizeの値が一致していなくても、可能であれば適宜ブロードキャストされます。 rng.integers([0, 10, 20], [10, 20, 30], size=(2, 3)) # 以下にほぼ等しい np.array([[rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)], [rng.integers(0, 10), rng.integers(10, 20), rng.integers(20, 30)]]) rng.integers(low=[0, 10], high=[[20], [30]], size=(3, 2, 2)) # どのような結果になるか考えてみましょう low/high/sizeそれぞれの値の関係が適切でない場合はエラーが出ます。 rng.integers([0, 10, 20], [10, 20, 30], size=4) # raise ValueError ランダム小数値・配列を作成 - .random() rng.random(size=None, dtype=np.float64, out=None) 引数 説明 size 結果の配列の形。省略するとスカラー値 dtype 結果配列のデータ型。デフォルトはfloat64 out 既存の配列に要素を代入する場合に使用 例 >>> rng = np.random.default_rng(0) >>> rng.random() 0.6369616873214543 >>> rng.random(3) array([0.26978671, 0.04097352, 0.01652764]) >>> rng.random((2, 5)) array([[0.81327024, 0.91275558, 0.60663578, 0.72949656, 0.54362499], [0.93507242, 0.81585355, 0.0027385 , 0.85740428, 0.03358558]]) .integers()と同様に、size引数を省略するかNoneを指定すると結果はスカラー値になり、size引数に整数を渡すとその長さの1次元配列が返却されます。タプルを渡すと2次元以上の配列も作成できます。 .integers()と異なりlow/high引数は存在せず、結果が取り得る値は必ず0以上1未満になります。 最小値lowと最大値highを指定したい場合は、変換式$(high - low) * random() + low$を適用します。 low, high = 1, 7 arr = rng.random(10) (high - low) * arr + low # -> 1以上7未満の値を10個並べた配列 ランダムなバイト文字列を作成 - .bytes() rng.bytes(length) lengthには文字列の長さを指定します。 例 >>> rng = np.random.default_rng(0) >>> rng.bytes(0) b'' >>> rng.bytes(1) b'\xcf' >>> rng.bytes(10) b'!\xd7\xd9\x82\xf8\xbd\x10E\xb8\xe8' 正規分布に基づいて乱数値・配列を作成 - .normal() rng.normal(loc=0.0, scale=1.0, size=None) 引数 説明 loc 平均値$μ$ scale 標準偏差$σ$ size 結果の配列の形。省略するとスカラー値 loc/scaleをともに省略すると標準正規分布に基づくようになります。 標準正規分布の場合は.standard_normal()を用いることもできます。 import matplotlib.pyplot as plt arr = rng.normal(size=100000) plt.hist(arr, bins=100) その他の確率分布に基づいて乱数値・配列を作成 二項分布 rng.binomial(n, p, size=None) # {n: 試行回数, p: 発生確率} # レア封入率3%の100連ガチャを10万人が引いたときの、各プレイヤーが引いたレアの枚数 arr = rng.binomial(100, 0.03, 100000) (arr == 0).sum() # -> 約4700人が一枚も引けなかった ポアソン分布 rng.poisson(lam=1.0, size=None) # {lam: 平均発生回数λ} カイ二乗分布 rng.chisquare(df, size=None) # {df: 自由度} 等 既存の配列をシャッフル・ランダム並び替え - .shuffle()/.permutation() rng.shuffle(x, axis=0) rng.permutation(x, axis=0) 引数 説明 x シャッフル元の配列 axis aが多次元配列の場合にシャッフルする軸 .shuffle()は既存の配列をシャッフルします。引数として与えた配列xを変更します。 .permutation()は引数として与えた配列xを並び替えた新しい配列を返します。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) >>> rng.permutation(arr) array(['c', 'e', 'd', 'g', 'f', 'a', 'b'], dtype='<U1') >>> arr array(['a', 'b', 'c', 'd', 'e', 'f', 'g'], dtype='<U1') >>> rng.shuffle(arr) >>> arr array(['f', 'c', 'e', 'g', 'b', 'a', 'd'], dtype='<U1') axis引数はシャッフルする軸を指定します。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array([[11, 12, 13], ... [21, 22, 23], ... [31, 32, 33]]) # 行(axis=0)並び替え >>> rng.permutation(arr, axis=0) array([[31, 32, 33], [11, 12, 13], [21, 22, 23]]) # 列(axis=1)並び替え >>> rng.permutation(arr, axis=1) array([[13, 12, 11], [23, 22, 21], [33, 32, 31]]) 多次元配列の各要素をシャッフル・ランダム並び替え - .permuted() rng.permuted(x, axis=None, out=None) 引数 説明 x シャッフル元の配列 axis シャッフルする軸 out 既存の配列に要素を代入する場合に使用 .permutation()は多次元配列の軸を並び替えます。 .permuted()は多次元配列の要素を軸毎にシャッフルします。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array([[11, 12, 13, 14, 15], ... [21, 22, 23, 24, 25], ... [31, 32, 33, 34, 35]]) # `.permutation()`は列`[[13], [23], [33]]`が維持されている >>> rng.permutation(arr, axis=1) array([[13, 15, 14, 11, 12], [23, 25, 24, 21, 22], [33, 35, 34, 31, 32]]) # `.permuted()`は各行の要素をそれぞれシャッフルする >>> rng.permutation(arr, axis=1) array([[15, 12, 13, 11, 14], [21, 23, 24, 25, 22], [34, 33, 31, 32, 35]]) ランダムサンプリング抽出・ガチャ・くじ引き - .choice() rng.choice(a, size=None, replace=True, p=None, axis=0, shuffle=True) 引数 説明 a 配列(母集団) size 結果の配列の形。省略するとスカラー値 replace デフォルトはTrueで重複あり。Falseにすると重複なし p aに対応する離散確率分布 axis aが多次元配列の場合にサンプリングする軸 shuffle 組み合わせが欲しい(結果の順番を気にしない)場合はFalse 配列aから任意の数sizeだけ要素を取り出します。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) >>> rng.choice(arr, 3) array(['f', 'e', 'd'], dtype='<U1') >>> rng.choice(arr, 10) array(['b', 'c', 'a', 'a', 'a', 'b', 'f', 'e', 'g', 'd'], dtype='<U1') 引数pに確率分布の配列を与えると、aからのサンプリングに重みをつけることができます。 なおpの長さがaと同じでない場合や、pの総和が1.0(全確率)でない場合はエラーが出ます。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['N', 'R', 'SR', 'SSR']) >>> s = rng.choice(arr, 10000, p=[0.5, 0.3, 0.15, 0.05]) >>> np.unique(s, return_counts=True) (array(['N', 'R', 'SR', 'SSR'], dtype='<U3'), array([4990, 2989, 1523, 498], dtype=int64)) デフォルトのreplace=Trueでは、重複ありでサンプリングします(いわゆる『袋からボールを取り出した後、ボールを袋に戻してから2個目を取り出す』)が、replace=Falseに設定すると重複なしでサンプリングします。 なおreplace=Falseの場合はsizeがlen(a)より多いとエラーが出ます。sizeとlen(a)が等しいときは並び替えとなるので.permutation(x)と同じ操作をすることになります。 この重複なし(replace=False)抽出の場合、結果の順番が気にならないときがあります(高校数学でいう「順列」ではなく「組み合わせ」を求める場合)。例えばくじが複数回引かれるとき、1人目と2人目がそれぞれ引く場合は1つ目と2つ目の結果を区別する必要があるのに対して、同じ人が複数回引く場合は1つ目と2つ目を区別する必要はありません。 この場合はshuffle=Falseを設定すると、処理速度が速くなります。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) # 重複あり(replace=True) >>> rng.choice(arr, 7) array(['f', 'e', 'd', 'b', 'c', 'a', 'a'], dtype='<U1') # 重複なし(replace=False) >>> rng.choice(arr, 7, replace=False) array(['a', 'g', 'b', 'c', 'd', 'f', 'e'], dtype='<U1') # 重複なし、組み合わせ抽出(速い) >>> rng.choice(arr, 7, replace=False, shuffle=False) array(['a', 'b', 'c', 'd', 'e', 'f', 'g'], dtype='<U1') 第一引数aに整数を設定するとnp.arange(a)が元の配列と解釈されます(replace=Trueの場合は.integers(a)と同じ操作をすることになります)。 例 >>> rng = np.random.default_rng(0) >>> rng.choice(10, 7, replace=False) array([9, 0, 1, 5, 2, 4, 3], dtype=int64) 多次元配列の場合はaxis引数で抽出する軸を設定します。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array([[11, 12, 13], ... [21, 22, 23], ... [31, 32, 33]]) # 行(axis=0)サンプリング >>> rng.choice(arr, 1, axis=0) array([[31, 32, 33]]) # 列(axis=1)サンプリング >>> rng.choice(arr, 1, axis=1) array([[12], [22], [32]]) おまけ .perumutation()/.choice(replace=False)を高速に 配列のサイズがおよそ100未満の場合はrng.permutation(x)よりもx[rng.random(len(x)).argsort()]のほうが高速に処理できます。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) # 一般的な方法 >>> rng.permutation(arr) array(['c', 'e', 'd', 'g', 'f', 'a', 'b'], dtype='<U1') # 速い方法 >>> idx = rng.random(len(arr)).argsort() ... idx array([4, 2, 3, 0, 6, 1, 5], dtype=int64) >>> arr[idx] array(['e', 'c', 'd', 'a', 'g', 'b', 'f'], dtype='<U1') この方法は.perumutation()を複数回行う場合に特に有用です。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) # 一般的な方法 >>> np.vstack([rng.permutation(arr) for _ in range(4)]) array([['c', 'e', 'd', 'g', 'f', 'a', 'b'], ['f', 'c', 'e', 'g', 'b', 'a', 'd'], ['c', 'b', 'd', 'g', 'a', 'e', 'f'], ['e', 'a', 'g', 'c', 'f', 'b', 'd']], dtype='<U1') # 速い方法 >>> arr[rng.random((4, len(arr))).argsort()] array([['c', 'd', 'a', 'b', 'g', 'f', 'e'], ['a', 'g', 'e', 'd', 'f', 'c', 'b'], ['a', 'd', 'e', 'c', 'b', 'f', 'g'], ['c', 'e', 'a', 'f', 'b', 'd', 'g']], dtype='<U1') .choice(replace=False)は以下のようにします。 例 >>> rng = np.random.default_rng(0) >>> arr = np.array(['a', 'b', 'c', 'd', 'e', 'f', 'g']) # 一般的な方法 >>> np.vstack([rng.choice(arr, 3, replace=False) for _ in range(4)]) array([['d', 'g', 'e'], ['g', 'f', 'a'], ['d', 'g', 'f'], ['d', 'g', 'f']], dtype='<U1') # 速い方法 >>> arr[rng.random((4, len(arr))).argsort()[:, :3]] array([['b', 'd', 'f'], ['d', 'e', 'b'], ['b', 'a', 'f'], ['b', 'e', 'a']], dtype='<U1')
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

cifar10のkerasでの分類にチャレンジ

はじめに pythonのkerasを用いて機械学習の練習を行う際に必ずといっていほど目にするcifar10とmnist。今回は機械学習の第一歩としてcifar10に着目して取り組んでみる。 目次 ・使ったもの ・データセットの入手、作成 ・ネットワークを構築 ・学習させてみる 使ったもの ・numpy (毎度おなじみのやつ, 保存に使っただけで使う必要なし) ・keras(tensorfow?) なければ、conda install keras でインストールを。 データセットの入手、作成 まずはデータセットを手に入れます。今回はCIFAR-10データセットを使用しました。CIFAR-10とはkerasに同梱されており、32×32ピクセルのカラー画像が60000枚存在します。これらの画像はすべて、10種類のいずれかに分けられます。 早速読み込んでいきます。 from keras.datasets import cifar10 (x_train, y_train), (x_test, y_test) = cifar10.load_data() 次に、このデータを使いやすいように加工を施していきます。 from keras.utils import to_categorical x_train = x_train.astype('float32') / 255 x_test = x_test.astype('float32') / 255 y_train = to_categorical(y_train, 10) y_test = to_categorical(y_test, 10) ざっくりいうと画像データは1から255の整数で構成され、入力値が-1から1の間にあると嬉しいので255で割り、to_categoricalで、y_train, y_testをone-hotエンコーディングの形状にしています。 私はいろいろな場合で試したかったので、この4つのデータをnp.savezで保存してしまいました。 ネットワークを構築 いよいよ本題、ネットワークの構築に取り掛かかります。個人的になれているfunctional APIという手法で構築しました。 from keras.layers import Dense, Flatten, Conv2D, BatchNormalization, Dropout, LeakyReLU, Input from keras.models import Model class Network(): def __init__(self, input_dim, filter, kernel, stride, use_batch, use_dropout, output_dim, lr): self.input_dim = input_dim self.filter = filter self.kernel = kernel self.stride = stride self.n_layers = len(filter) self.use_batch = use_batch self.use_dropout = use_dropout self.lr = lr self.crate() def create(self): input_layer = Input(shape=self.input_dim) x = input_layer for i in range(self.n_layers): conv = Conv2D(filters=self.filter[i], kernel_size=self.kernel[i], strides=self.stride[i], padding='same') x = conv(x) if self.use_batch: x = BatchNormalization(momentum=self.use_batch)(x) x = LeakyReLU()(x) if self.use_dropout: x = Dropout(rate=0.5)(x) x = Flatten()(x) x = Dense(units=self.output_dim, activation='sigmoid')(x) output_layer = x self.model = Model(input_layer, output_layer) あとでいろいろ値を変えて学習させたかったのでNetworkというクラスを定義。 画像データを学習させる際によく用いられるConv2Dという畳込み層、バッチ正規化を施すBatchNomalization、LeakyReLUという活性化関数、Dropout層を繰り返し、Flattenでならして最後にDense層を通すというもの。 最後にModelを使って終了です。 from keras.optimizers import Adam def compile(self, lr): self.model.compile(optimizer=Adam(lr=self.lr), loss='categorical_crossentropy', metrics=['accuracy']) def train(self, data1, data2, batch_size, epochs): self.model.fit(data1, data2, batch_size=batch_size, epochs=epochs, shuffle='True') ここから訓練していきます。 学習させてみる indim = (32, 32, 3) fil = [32, 64, 64, 32] ker = [3, 3, 3, 3] stri = [1, 2, 1, 2] usebatch = 0.25 usedrop = True Lr = 0.0005 Epochs = 10 Batchsize = 32 NETWORK = Network(input_dim=indim, filters=fil, kernel=ker, stride=stri, use_batch=usebatch, use_dropout=usedrop, lr=Lr) NETWORK.model.compile(lr=Lr) NETWORK.model.train(data1=x_train, data2=y_train, batch_size=Batchsize, epochs=Epochs) いざ実行してみます。 accuracyは62%ほど。 個人的にバッチ正規化のイメージがまだあまりうまくつかめていないので、今後の課題にしたいところです。 これがこれが気象の解析において活用できるときが来るのかはわかりませんが、可能性はなくはないと思うのでなにか考えてみたいところですね。 参考文献 [生成Deep Learning] ・David foster 著  ・松田晃一、小沼千絵 訳 ・オライリージャパン ・2020年発行
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【画像処理】Numpyでテンプレートマッチング

Numpyでテンプレートマッチングを実装してみます。 まず、マッチングを行う画像を読み込みます。 import numpy as np import matplotlib.pyplot as plt original_image = plt.imread(image_name) if np.issubdtype(original_image.dtype, np.integer): original_image = original_image / np.iinfo(original_image.dtype).max gray_image = 0.2116 * original_image[:,:,0] + 0.7152 * original_image[:,:,1] + 0.0722 * original_image[:,:,2] plt.imshow(gray_image, cmap='gray') テンプレート画像を用意します。 template_image = gray_image[10:55,60:120] plt.figure(figsize=(1, 1)) plt.imshow(template_image, cmap='gray') SSD SSD(Sum of Squared Difference)で相違度を計算します。 R_{ssd}(x, y) = \sum_{i=0}^{N-1}\sum_{j=0}^{M-1}(I(x+i,y+j) - T(i,j))^2 $R_{ssd}(x,y)$は位置$(x,y)$におけるSSDの値、$I(i,j)$と$T(i,j)$はそれぞれ位置$(i,j)$におけるマッチング対象の画像の画素値とテンプレートの画素値、$(N,M)$はテンプレートの大きさとなります。 def match_template_ssd(image, template): shape = (image.shape[0] - template.shape[0] + 1, image.shape[1] - template.shape[1] + 1) + template.shape strided_image = np.lib.stride_tricks.as_strided(image, shape, image.strides * 2) return np.sum((strided_image - template) ** 2.0, axis=(2, 3)) ssd_image = match_template_ssd(gray_image, template_image) plt.imshow(ssd_image, cmap='gray') plt.colorbar() SSDが最も小さい箇所がテンプレートと最もマッチする箇所になります。そこに四角形を描画しています。四角形の描画に関しては以下の記事を参照してください。 【画像処理】Numpyで図形描画 - Qiita def stroke_rectangle(image, color, weight, center, size): coord = np.fromfunction(lambda y, x: np.dstack((y + 0.5, x + 0.5)), image.shape[:2]) dist = np.dstack((abs(coord[:,:,1] - center[0]) - size[0] / 2, abs(coord[:,:,0] - center[1]) - size[1] / 2)).max(axis=2) condition = abs(dist) - weight * 0.5 <= 0 if image.ndim == 3: condition = np.tile(condition.reshape(condition.shape + (1,)), (1, 1, image.shape[2])) return np.where(condition, color, image) index = np.unravel_index(np.argmin(ssd_image), ssd_image.shape) ssd_rect_image = stroke_rectangle(original_image, np.array([1.0, 0.0, 1.0]), 2.0, (index[1] + template_image.shape[1] / 2, index[0] + template_image.shape[0] / 2), (template_image.shape[1], template_image.shape[0])) plt.imshow(ssd_rect_image) SAD SAD(Sum of Absolute Difference)で相違度を計算します。 R_{sad}(x, y) = \sum_{i=0}^{N-1}\sum_{j=0}^{M-1}|I(x+i,y+j) - T(i,j)| def match_template_sad(image, template): shape = (image.shape[0] - template.shape[0] + 1, image.shape[1] - template.shape[1] + 1) + template.shape strided_image = np.lib.stride_tricks.as_strided(image, shape, image.strides * 2) return np.sum(np.abs(strided_image - template), axis=(2, 3)) sad_image = match_template_sad(gray_image, template_image) plt.imshow(sad_image, cmap='gray') plt.colorbar() SSDと同様にSADの値が最も小さい箇所が最もマッチする箇所なので、そこに四角形を描画します。 index = np.unravel_index(np.argmin(sad_image), sad_image.shape) sad_rect_image = stroke_rectangle(original_image, np.array([1.0, 0.0, 1.0]), 2.0, (index[1] + template_image.shape[1] / 2, index[0] + template_image.shape[0] / 2), (template_image.shape[1], template_image.shape[0])) plt.imshow(sad_rect_image) NCC NCC(Normalized Cross-Correlation)で類似度を計算します。 R_{ncc}(x, y) = \frac{\sum_{i=0}^{N-1}\sum_{j=0}^{M-1}I(x+i,y+j)T(i,j)}{\sqrt{\sum_{i=0}^{N-1}\sum_{j=0}^{M-1}I(x+i,y+j)^2\times\sum_{i=0}^{N-1}\sum_{j=0}^{M-1}T(i,j)^2}} def match_template_ncc(image, template): shape = (image.shape[0] - template.shape[0] + 1, image.shape[1] - template.shape[1] + 1) + template.shape strided_image = np.lib.stride_tricks.as_strided(image, shape, image.strides * 2) return np.sum(strided_image * template, axis=(2, 3)) \ / (np.sqrt(np.sum(strided_image * strided_image, axis=(2, 3)) * np.sum(template * template))) ncc_image = match_template_ncc(gray_image, template_image) plt.imshow(ncc_image, cmap='gray') plt.colorbar() NCCは最も値が大きい箇所が最もマッチする箇所になるので、そこに四角形を描画します。 index = np.unravel_index(np.argmax(ncc_image), ncc_image.shape) ncc_rect_image = stroke_rectangle(original_image, np.array([1.0, 0.0, 1.0]), 2.0, (index[1] + template_image.shape[1] / 2, index[0] + template_image.shape[0] / 2), (template_image.shape[1], template_image.shape[0])) plt.imshow(ncc_rect_image) 実装したコードはGoogle Colaboratoryに置いてあります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

OpenVINOで推論してみた1 face-detection-0200

初めに 今回初めて記事を書きますので拙いものになるかもしれませんがよろしくお願いします。 サークルでOpenVINOの学習済みモデルを使った推論をたくさんしており、せっかくなので記事に起こしたいと思います。 この記事はOpenVINO Toolkitがダウンロード済みのことを想定していますので、「OpenVINOって何?」や「どうやってダウンロードするの?」という方はQiitaで探してみてください! 初めはそこから書こうと思いましたが素敵な記事がたくさんあって敢えて書かなくてもいいと思いましたので、モデルのインストール、推論をメイン記事として進めていこうと思います。 最後に全コードを書いておりますので煩わしくなった方はそれだけでも実装してみてください。 環境 windows10 OpenVINO toolkit 2021.4.752 OpenVINO 2021.4.2 python 3.6 opencv 3.4.2 モデルのダウンロード方法 OpenVINO toolkitのフォルダに移動します。ダウンロードフォルダはデフォルトでC:\Program Files (x86)\Intel\にあると思います。 コマンドプロンプトを開いて以下のコードを入力します。 cd "C:\Program Files (x86)\Intel\openvino_2021.4.752\deployment_tools\tools\model_downloader" python ./downloader.py --all --output_dir "C:¥Users¥<ユーザ名>¥Desktop¥OpenVINO¥models" --output_dir以下のダブルクォーテーションで囲んだ部分はモデルをダウンロードしたいフォルダを入力してください。 モデルが大量にあり、時間がかかりますがこれでモデルのダウンロードが完了しました。 さて、モデルのダウンロードを待っている間にどんなモデルがあるのか確認してみましょう。 こちらでモデルが一覧できます。 記念すべき第一回はface-detection-0200をやっていこうと思います。 face-detection-0200 モデルの詳細 実装を進める前にgithubを見てインプット、アウトプットやどんなモデルなのかを確認していきましょう。 Use Case and High-Level Description ユースケースと概要になります。ここは基本的にどのようなモデルで学習しているのかが書かれています。今回のモデルはMobileNetV2で、顔検出にSSDが使われています。MobileNetV2はこちらの方の記事が大変わかりやすかったので載せておきます。 簡単に言えば軽量で高性能なCNNです。 SSDについては私はこちらを参考にしました。 正直のところいまいちわかっていないです、、、 今度一からSSDを実装してみて理解を深めるしかないですね。一旦これを用いて顔検出を行っているんだというくらいの認識で行きます。 Specification これには学習済みモデルの仕様が書かれています。 Metric Value AP (WIDER) 86.74% GFlops 0.786 MParams 1.828 Source framework PyTorch* APについてはこちらの記事がわかりやすいです! 簡単に言うとモデルの精度みたいなものですね(簡単すぎかも??) GFlopsはコンピュータの処理性能を表す単位のことで、浮動小数点演算を単位時間当たり10憶回行うことを表しています。ここではモデルの重さを表していると思ってください。今回は0.786ですので、そこまで重くはないですね! MParamsはそのままモデルのパラメータの数です。 frameworkはPyTorchを使っているとも書いてありますね。 っと、ここが仕様の部分になります。 Inputs Image, name: input, shape: 1, 3, 256, 256 in the format B, C, H, W, where: B - batch size C - number of channels H - image height W - image width Expected color order: BGR. そのままですが、インプットは画像で、1,3,256,256ですね。これは画像を入れるときに変形して渡せば大丈夫ですね。 outputs The net outputs blob with shape: 1, 1, 200, 7 in the format 1, 1, N, 7, where N is the number of detected bounding boxes. Each detection has the format [image_id, label, conf, x_min, y_min, x_max, y_max], where: image_id - ID of the image in the batch label - predicted class ID (0 - face) conf - confidence for the predicted class (x_min, y_min) - coordinates of the top left bounding box corner (x_max, y_max) - coordinates of the bottom right bounding box corner こちらも書いてある通りで、200のバウンディボックスが検出されており、それぞれに7つのパラメータが出力されております。 この辺は実装の時に詳しく見ようかなと思います。 それでは実装していきましょう!! 実装 ファイル構造 先ほどダウンロードしたface-detection-0200のモデルを持ってきています。 openvino/ ├─ models/ ├─ face-detection-0200/ ├─ intel/ ├─ face-detection-0200/ ├─ FP16/ ├─ FP16-INT8/ ├─ FP32/ ├─ face-detection-0200.bin ├─ face-detection-0200.xml ├─ face-detection-0200.py ├─ input_image.jpg face-detection-0200.py モデルの読み込み モジュールのインポートをします。 import cv2 import numpy as np from openvino.inference_engine import IECore 今回使用するのは上の三つです。 下のエラーが出る方は一つ下のコードを実行してみてください。 from .ie_api import * ImportError: DLL load failed: 指定されたモジュールが見つかりません。 "C:\Program Files (x86)\Intel\openvino_2021.4.752\bin\setupvars.bat" 次にOpenVINOのオブジェクトを生成します。APIドキュメントはこちらです。 モデルを読み込むために、Inference Engineコアオブジェクトのread_network()を利用します。 Parameters model – A .xml, .onnxor.prototxt model file or string with IR. weights – A .bin file of the IR. Depending on init_from_buffer value, can be a string path or bytes with file content. init_from_buffer – Defines the way of how model and weights attributes are interpreted. usage example ie = IECore() net = ie.read_network(model=path_to_xml_file, weights=path_to_bin_file) 公式ドキュメント通りに下のように書いてモデルを読み込みます。ここで、.xmlファイルと.binファイルはそれぞれモデルの定義ファイルとモデルの重みのファイルになります。 # read module ie = IECore() # read IR model model_name = "face-detection-0200" model = './intel/{}/FP32/{}'.format(model_name, model_name) net = ie.read_network(model=model+'.xml', weights=model+'.bin') 次に推論用のインスタンスを生成するためにload_netを呼び出します。 Parameters network – A valid IENetwork instance. Model file name .xml, .onnx can also be passed as argument device_name – A device name of a target plugin config – A dictionary of plugin configuration keys and their values num_requests – A positive integer value of infer requests to be created. usage example ie = IECore() net = ie.read_network(model=path_to_xml_file, weights=path_to_bin_file) exec_net = ie.load_network(network=net, device_name="CPU", num_requests=2) これも公式のusageのように書けばok! exec_net = ie.load_network(network=net, device_name='CPU', num_requests=1) Inputs モデルの準備ができたので画像をモデルにインプットしていきます。 まずは適当な人の画像を用意します。今回私はこちらを使用しようと思います。用意が面倒な方は同じものを使っていきましょう。 opencvを使って画像を読み込みます。先ほど見たInputsに合わせるために画像サイズを整えて、width, height, channelの順番になっている画像をchannel, height, widthに直します。 frame = cv2.imread("input_image.jpg") img = cv2.resize(frame, (256, 256)) img = img.transpose((2, 0, 1)) img = img.reshape((1, 3, 256, 256)) これでやっとモデルに入力する準備が整いました。この画像をモデルに入力して推論を行っていきます。 推論はinfer関数を用いて行います。このinfer関数を使用する際にinput nameが必要で、githubにはinputと書いてありますが、以下のコードで調べてみるとimageと出力されるのでimageとしておきます。 # print model data input_blob_name = list(net.inputs.keys()) print("input_blob_name: {}".format(input_blob_name)) >>input_blob_name: {data} これらより、モデルへの入力は以下のように記述します。 out = exec_net.infer(inputs={'image': img}) Outputs このoutがoutputsになります。試しに以下のコードを実行してみます。 print(out) >>{'detection_out': array([[[[...]]]], dtype=float32)} と、このような出力になっているので、 out = out["detection_out"] とします。ここで、もう一度outを確認してみましょう。 print(out.shape) >>(1, 1, 200, 7) この出力を見ればgithubのOutputsのようになっていることがわかります。 今から使うのは200,7の部分なので次元を削除します。 out = np.squeeze(out) ここからは各バウンディボックスについての出力値を使っていきます。 for detection in out: print(detection) >>[0., 0., 0.9989833, 0.458686, 0.37184125, 0.6566284, 0.77973706] >>. >>. >>. この7つの値は先ほど確認したOutputsの値に対応しています。 Each detection has the format [image_id, label, conf, x_min, y_min, x_max, y_max], where: image_id - ID of the image in the batch label - predicted class ID (0 - face) conf - confidence for the predicted class (x_min, y_min) - coordinates of the top left bounding box corner (x_max, y_max) - coordinates of the bottom right bounding box corner confはlabel(今回はface)の確率なのでこれに閾値を設定します。今回は0.6くらいにしようと思います。 x_min, y_min, x_max, y_maxはここに書いてある通り顔矩形の隅の座標を取得できます。ただ、この出力は0-1の値をとる、widgh, heightに対する比率になっているのでこれをかけてあげます。 conf = float(detection[2]) if conf > 0.6: xmin = int(detection[3] * frame.shape[1]) ymin = int(detection[4] * frame.shape[0]) xmax = int(detection[5] * frame.shape[1]) ymax = int(detection[6] * frame.shape[0]) # adjustment if xmin < 0: xmin = 0 if ymin < 0: ymin = 0 if xmax > frame.shape[1]: xmax = frame.shape[1] if ymax > frame.shape[0]: ymax = frame.shape[0] 下の8行は例外の処理になります。 あとはこの座標から矩形をとって出力すれば推論の完成です。 全コード import cv2 from openvino.inference_engine import IECore import numpy as np import time # read module ie = IECore() # read IR model model_name = "face-detection-0200" model = './intel/{}/FP16/{}'.format(model_name, model_name) net = ie.read_network(model=model+'.xml', weights=model+'.bin') # print model data input_blob_name = list(net.inputs.keys()) output_blob_name = list(net.outputs.keys()) print("input_blob_name: {}".format(input_blob_name)) print("output_blob_name: {}".format(output_blob_name)) batch, channel, height, width = net.inputs[input_blob_name[0]].shape print("input shape: {} x {}".format(height, width, width)) print("input channnel: {}".format(channel)) # select target device exec_net = ie.load_network(network=net, device_name='CPU', num_requests=1) frame = cv2.imread('test.jpg') img = cv2.resize(frame, (width, height)) img = img.transpose((2, 0, 1)) img = img.reshape((1, channel, height, width)) # inference (face) out = exec_net.infer(inputs={'image': img}) out = out['detection_out'] out = np.squeeze(out) # detection process for detection in out: # conf value confidence = float(detection[2]) # outputs rect if confidence > 0.6: # translate box into image xmin = int(detection[3] * frame.shape[1]) ymin = int(detection[4] * frame.shape[0]) xmax = int(detection[5] * frame.shape[1]) ymax = int(detection[6] * frame.shape[0]) # adjustment if xmin < 0: xmin = 0 if ymin < 0: ymin = 0 if xmax > frame.shape[1]: xmax = frame.shape[1] if ymax > frame.shape[0]: ymax = frame.shape[0] cv2.rectangle(frame, (xmin, ymin), (xmax, ymax), color=(240, 180, 0), thickness=2) cv2.putText(frame, "FPS: {}".format(fps), (0, 15), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (255, 0, 0), 1, cv2.LINE_AA) cv2.imwrite('output.jpg', frame) 終わりに 今回はface-detection-0200の推論を行いました。思ったより長くなってしまったので次回からは理論編と実装編分けるなどしたいと思います。 またこのコードでエラーが出る、もしくはSSDの理論をわかりやすく説明できるよって方いらっしゃいましたらご連絡ください。 よろしくお願いします。 それではよい機械学習ライフを!! お疲れさまでした!! 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Astropyでカラーバーがおかしい問題を解決した

元々は... APLpyというモジュールのFITSFigureという関数(?)を使用していたが,どうやら最近のPythonのアップデートに追いついていないらしく,3.6か3.7ぐらいでアップデートが終了し,それ以降のバージョンに対応していないようだった. Pyenvでバージョン管理をしているので使えるといえば使えるがなんだか気持ち悪い. ということで最新のPython 3.10.1でもpip経由できちんとインストールできたastropyだけでFITS画像の出力ができないか模索していた. そんなこんなで... いろいろ参考にしてastropyとmatplotlibでFITS画像をプロットすることができたのだが... import matplotlib.pyplot as plt import matplotlib.cm as cm from astropy.io import fits import pyregion from mpl_toolkits.axes_grid1 import make_axes_locatable # read in the image fits_name = 'fits/ngc1187_stellar_mass_distribution_3.6um_4.5um.fits' f = fits.open(fits_name) try: from astropy.wcs import WCS from astropy.visualization.wcsaxes import WCSAxes wcs = WCS(f[0].header) fig = plt.figure() ax = WCSAxes(fig, [0.1, 0.1, 0.8, 0.8], wcs=wcs) fig.add_axes(ax) except ImportError: ax = plt.subplot(111) image = ax.imshow(f[0].data, cmap=cm.jet, vmin=0., vmax=2e6) reg_name = 'region/continuum.reg' r = pyregion.open(reg_name).as_imagecoord(header=f[0].header) #colorbar divider = make_axes_locatable(ax) colorbar_ax = divider.append_axes("top", "3%", pad="1%") cbar = fig.colorbar(image, orientation='horizontal', cax=colorbar_ax) colorbar_ax.xaxis.set_ticks_position('top') plt.title('Stellar mass distribution', x=0.5, y=3) from pyregion.mpl_helper import properties_func_default # Use custom function for patch attribute def fixed_color(shape, saved_attrs): attr_list, attr_dict = saved_attrs attr_dict["color"] = "red" kwargs = properties_func_default(shape, (attr_list, attr_dict)) return kwargs # select region shape with tag=="Group 1" r1 = pyregion.ShapeList([rr for rr in r if rr.attr[1].get("tag") == "Group 1"]) patch_list1, artist_list1 = r1.get_mpl_patches_texts(fixed_color) r2 = pyregion.ShapeList([rr for rr in r if rr.attr[1].get("tag") != "Group 1"]) patch_list2, artist_list2 = r2.get_mpl_patches_texts() for p in patch_list1 + patch_list2: ax.add_patch(p) for t in artist_list1 + artist_list2: ax.add_artist(t) plt.show() カラーバーがなんかおかしい...なにこれ... y軸消えないし,メッシュ入ってるし,x軸の目盛も画像に入り込んでる... ちゃんとcolorbar_ax.xaxis.set_ticks_position('top')とか指定してるのに... 色々調べた結果,解決策を発見! 冒頭にimport matplotlib.axes as maxesを追加し,colorbar_ax = divider.append_axes("top", "3%", pad="1%")の部分に,axes_class=maxes.Axesを追加するのみ! import matplotlib.pyplot as plt import matplotlib.cm as cm from astropy.io import fits import pyregion from mpl_toolkits.axes_grid1 import make_axes_locatable import matplotlib.axes as maxes # read in the image fits_name = 'fits/ngc1187_stellar_mass_distribution_3.6um_4.5um.fits' f = fits.open(fits_name) try: from astropy.wcs import WCS from astropy.visualization.wcsaxes import WCSAxes wcs = WCS(f[0].header) fig = plt.figure() ax = WCSAxes(fig, [0.1, 0.1, 0.8, 0.8], wcs=wcs) fig.add_axes(ax) except ImportError: ax = plt.subplot(111) image = ax.imshow(f[0].data, cmap=cm.jet, vmin=0., vmax=2e6) reg_name = 'region/continuum.reg' r = pyregion.open(reg_name).as_imagecoord(header=f[0].header) #colorbar divider = make_axes_locatable(ax) colorbar_ax = divider.append_axes("top", "3%", pad="1%", axes_class=maxes.Axes) cbar = fig.colorbar(image, orientation='horizontal', cax=colorbar_ax) colorbar_ax.xaxis.set_ticks_position('top') plt.title('Stellar mass distribution', x=0.5, y=3) from pyregion.mpl_helper import properties_func_default # Use custom function for patch attribute def fixed_color(shape, saved_attrs): attr_list, attr_dict = saved_attrs attr_dict["color"] = "red" kwargs = properties_func_default(shape, (attr_list, attr_dict)) return kwargs # select region shape with tag=="Group 1" r1 = pyregion.ShapeList([rr for rr in r if rr.attr[1].get("tag") == "Group 1"]) patch_list1, artist_list1 = r1.get_mpl_patches_texts(fixed_color) r2 = pyregion.ShapeList([rr for rr in r if rr.attr[1].get("tag") != "Group 1"]) patch_list2, artist_list2 = r2.get_mpl_patches_texts() for p in patch_list1 + patch_list2: ax.add_patch(p) for t in artist_list1 + artist_list2: ax.add_artist(t) plt.show() あとは微調整するだけだ!ふううううううううう 参考 余談 まあ,pyregionというモジュールはまだ3.10.1に対応していないんですけどね... サポート続けてくれるかな...
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む