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

NetworkXで経路探索をやってみる。<グラフ作成〜A*アルゴリズム>

はじめに グラフ理論を学ぶ機会が、増えてきました。 ある出発地点から、目的地点までの経路を考える時にも、グラフ理論が活用できます。 Pythonでは、NetworkXという、グラフ計算ライブラリがあります。 本記事は、NetworkXを使用して、グラフの作成〜経路探索してみました。 今回やりたいことは、3つ。 格子状にノードを配置した、グラフを作成する(下図、左) グラフから、ランダムにノードを削除する(下図、中央) A*アルゴリズムを使用して、経路探索する(下図、右) コードを書いていく Step0. 準備 Python 3.8.6、NetworkX 2.6.2を使います。 まず、importしていく。 import matplotlib.pyplot as plt import networkx as nx import random Step1. グラフの作成 今回、N×Mの格子状にノードを配置した、グラフを作成します。 格子状の配置は、色々試すのに見やすくて好みです。 def make_graph(n, m):# n*mの格子状のグラフ作成 G = nx.Graph() # n*m個、ノードを追加する for i in range(n): for j in range(m): G.add_node((i,j)) # 縦横(x、y軸)方向にエッジを追加する for i in range(n): for j in range(m-1): if (i,j) in G and (i,j+1) in G: G.add_edge((i,j), (i,j+1)) for j in range(m): for i in range(n-1): if (i,j) in G and (i+1,j) in G: G.add_edge((i,j), (i+1,j)) # たすき掛け方向にもエッジを追加する for j in range(m-1): for i in range(n-1): if (i,j) in G and (i+1,j+1) in G: G.add_edge((i,j), (i+1,j+1)) for i in range(n): for j in range(m): if (i,j+1) in G and (i+1,j) in G: G.add_edge((i,j+1), (i+1,j)) # スタート地点、ゴール地点のノードを追加する G.add_edge((0, -1), (0, 0))# スタート G.add_edge((n-1, m-1), (n-1, m))# ゴール # ノードの位置を設定 pos_0={n:(n[1], n[0]) for n in G.nodes()} # グラフを描画、保存 nx.draw_networkx_nodes(G, pos_0, node_size=30, node_color="0.4") nx.draw_networkx_edges(G, pos_0, label=1, edge_color="black", width=1) plt.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)#軸メモリ描画 plt.savefig('graph_1.png') plt.figure() return G Step2. グラフからランダムにノード削除 Step1で作成したグラフから、ランダムにノードを削除してみます Step1で、ランダムにノードを作成すれば良かった def set_nodes_to_delete(num, n, m):# 削除するノードをランダムに選定し、リストに保存 del_n=[] for i in range(num):# 削除するノードをnum個挙げる a = random.randint(0, n-1)# 乱数(整数値)の範囲は、0 <= a <= (n-1) b = random.randint(0, m-1) del_n.append(tuple([a,b]))# タプルでリストに保存 print(del_n) return del_n def delete_nodes(Gp, xy_list):# リストに記載されているノードを削除した、グラフを返す for k in list(Gp.nodes()): if(k in xy_list)==1:# リスト内のノードが、グラフに含まれているかチェック print('Boooom!')# なんとなくつけた、削除の効果音 Gp.remove_node(k)# ノード削除 # ノード削除後グラフを描画、保存 pos_0={n:(n[1], n[0]) for n in Gp.nodes()} nx.draw_networkx_nodes(Gp, pos_0, node_size=30, node_color="0.4") nx.draw_networkx_edges(Gp, pos_0, label=1, edge_color="black", width=1) plt.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)#軸メモリを描画する plt.savefig('graph_2.png') plt.figure() return Gp Step3. A*アルゴリズム使用 経路探索に、A*アルゴリズムを使用します。 A*アルゴリズムの概要説明は、省略します。 (参考)Wikipedia A* def distance(a, b):# ヒューリスティック関数に、ユークリッド距離を利用する (x1, y1) = a (x2, y2) = b return ((x1-x2)**2 + (y1-y2)**2)**0.5 def astar(Gp, n, m):# 入力したグラフから、A*アルゴリズムで経路探索 path = nx.astar_path(Gp, source=(0, -1), target=(n-1, m), heuristic=distance, weight='weight')# (0,-1)から(n-1,m)までの経路を探索 print(path)# 導出した経路を確認する # 結果を描画、保存 pos_1 = {l: (l[1], l[0]) for l in Gp.nodes()} node_color=['blue'if(i in path)==1 else '0.4'for i in Gp.nodes()]# 導出した経路を青色で着色する nx.draw_networkx_nodes(Gp, pos_1, node_size=30, alpha=1, node_color=node_color) nx.draw_networkx_edges(Gp, pos_1, edge_color="black", width=1) plt.tick_params(left=True, bottom=True, labelleft=True, labelbottom=True)#軸メモリを描画する plt.savefig('graph_3.png') plt.show() plt.figure() Step4. 実行 print('Please enter the number of nodes in the y-axis direction.') n = int(input())# y方向(上下方向) print('Please enter the number of nodes in the x-axis direction.') m = int(input())# x方向(左右方向) print('Please enter the number of nodes to be deleted.') num = int(input()) G1 = make_graph(n, m)# n*mの格子状にノードを配置したグラフ(G1)を作成 del_n = set_nodes_to_delete(num, n, m)# 削除するノードを任意の数、ランダムに選ぶ G2 = delete_nodes(G1, del_n)# G1から、ノードを削除したグラフ(G2)を作成 astar(G2, n, m)# G2にA*アルゴリズムを使用する おわりに 今回、NetworkXを利用して、グラフを作成し、A*アルゴリズムを使用してみました。 効率良く、経路探索するために、ヒューリスティック関数の設計がポイントです。 今後は、様々なシチュエーションを想定し、ヒューリスティック関数を設計してみます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WindowsでPDMを使ってtorchvisionを入れようとしたらエラーで怒られてしまった人へ

はじめに Windowsでpdm add torchvisionしようとしたら assert record is not None, "In {}, {} is not mentioned in RECORD".format( AssertionError: In C:\Users\hoge\AppData\Local\Temp\pip-unpack-XXXXXXXX\torchvision-0.11.1-cp39-cp39-win_amd64.whl, torchvision-0.11.1.dist-info/LICENSE is not mentioned in RECORD とassertが出てインストールできなかった。本稿では何とかしてインストールするための方法を紹介する。 実行環境 OS: Windows 10 Pro (19044.1348) pdm: version 1.10.3 pyenv-win: version 2.64.11 Python: version 3.9.6 エラーの再現方法 $mkdir tmp で空のディレクトリを作成する。 powershellを開いて、上で作成したディレクトリをカレントディレクトリにする。 powershellのコンソールに$pdm initを入力しenter。この時聞かれるパッケージの初期設定については、Please enter the Python interpreter to use に対してはpyenv-winのpython 3.9.6を選択し、他の設定はデフォルトを選択する(何も入力せずにenter)。 $pdm add torchvision==0.11.1を入力する。 出力されるエラーメッセージ ERRORS: add torchvision failed: Traceback (most recent call last): File "C:\Users\hoge\scoop\apps\pyenv\current\pyenv-win\versions\3.9.6\lib\concurrent\futures\thread.py", line 52, in run result = self.fn(*self.args, **self.kwargs) File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\pdm\installers\synchronizers.py", line 190, in install_candidate self.manager.install(can) File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\pdm\installers\manager.py", line 38, in install installer(candidate.build(), self.environment, candidate.direct_url()) File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\pdm\installers\installers.py", line 124, in install_wheel_with_cache dist_info_dir = _install_wheel( File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\pdm\installers\installers.py", line 176, in _install_wheel for record_elements, stream in source.get_contents(): File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\installer\sources.py", line 170, in get_contents assert record is not None, "In {}, {} is not mentioned in RECORD".format( AssertionError: In C:\Users\hoge\AppData\Local\Temp\pip-unpack-XXXXXXXX\torchvision-0.11.1-cp39-cp39-win_amd64.whl, torchvision-0.11.1.dist-info/LICENSE is not mentioned in RECORD See C:\Users\hoge\AppData\Local\Temp\pdm-install-XXXXXXXXX.log for detailed debug log. [InstallationError]: Some package operations are not complete yet Add '-v' to see the detailed traceback エラーの原因 エラーメッセージの下の方を見てみると、 File "C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\installer\sources.py", line 170, in get_contents assert record is not None, "In {}, {} is not mentioned in RECORD".format( AssertionError: In C:\Users\hoge\AppData\Local\Temp\pip-unpack-XXXXXXXX\torchvision-0.11.1-cp39-cp39-win_amd64.whl, torchvision-0.11.1.dist-info/LICENSE is not mentioned in RECORD とある。C:\Users\hoge\AppData\Roaming\pdm\venv\lib\site-packages\installer\sources.pyを見てみると、エラーの発生個所は以下の関数らしい。 source.pyの152行目~ def get_contents(self): # type: () -> Iterator[WheelContentElement] """Sequential access to all contents of the wheel (including dist-info files). This implementation requires that every file that is a part of the wheel archive has a corresponding entry in RECORD. If they are not, an :any:`AssertionError` will be raised. """ # Convert the record file into a useful mapping record_lines = self.read_dist_info("RECORD").splitlines() records = installer.records.parse_record_file(record_lines) record_mapping = {record[0]: record for record in records} for item in self._zipfile.infolist(): if item.filename[-1:] == "/": # looks like a directory continue record = record_mapping.pop(item.filename, None) assert record is not None, "In {}, {} is not mentioned in RECORD".format( self._zipfile.filename, item.filename, ) # should not happen for valid wheels with self._zipfile.open(item) as stream: stream_casted = cast("BinaryIO", stream) yield record, stream_casted この関数はPDMが内部で使っているinstallerというパッケージのものらしい。この関数の説明文によると、この関数はインストールしようとしたwheelに含まれている各ファイルが、whlのdist-infoのRECORDに書いてあることを確認し、書いてなかったら先のエラーを出すらしい。 先のエラーを見直すと、AssertionError: In C:\Users\hoge\AppData\Local\Temp\pip-unpack-XXXXXXXX\torchvision-0.11.1-cp39-cp39-win_amd64.whl, torchvision-0.11.1.dist-info/LICENSE is not mentioned in RECORDとあり、torchvision-0.11.1.dist-info/LICENSEがRECORDに書いてないらしい。1 本当に書いていないことを確かめるため、torchvisionのpypiからtorchvision-0.11.1-cp39-cp39-win_amd64.whlをダウンロードして、unzipしてみる。unzipしたファイルのtorchvision-0.11.1.dist-info/RECORDを見てみると、121行目にtorchvision-0.11.1.dist-info\LICENSEとある。一方で、上のエラーメッセージではtorchvision-0.11.1.dist-info/LICENSE is not mentioned in RECORDと書いてあった。確かにtorchvision-0.11.1.dist-info/LICENSEはRECORDに記載されていないものの、ディレクトリ区切り文字が異なるtorchvision-0.11.1.dist-info\LICENSEが記載されている。ということで、RECORDに記載されているパス(torchvision-0.11.1.dist-info\LICENSE)と、wheelに含まれているファイルのパス(torchvision-0.11.1.dist-info/LICENSE)の区切り文字が異なってしまうことがエラーの原因らしい。 エラーの回避方法 回避方法の方針としては、RECORDとinstallerのディレクトリ区切り文字を一致させてやれば良さそうである。そのための雑な方法として、 sources.pyの161行目 record_lines = self.read_dist_info("RECORD").splitlines() を sources.pyの161行目 record_lines = [ line.replace("\\", "/") for line in self.read_dist_info("RECORD").splitlines()] に置換する。self.read_dist_info("RECORD").splitlines()の返り値はRECORDファイルの各行が入っているリストで、[ line.replace("\\", "/") for line in self.read_dist_info("RECORD").splitlines()]はRECORDの各行の\を/に置換したものである。 この方法だと、Pathだけでなく、RECORDに含まれる各ファイルのハッシュも置換してしまう。セキュリティ的にまずいはずなので、パスのみを置換の範囲にするべきです。 RECORDが何なのかというと、"RECORD is a list of (almost) all the files in the wheel and their secure hashes. Unlike PEP 376, every file except RECORD, which cannot contain a hash of itself, must include its hash. The hash algorithm must be sha256 or better; specifically, md5 and sha1 are not permitted, as signed wheel files rely on the strong hashes in RECORD to validate the integrity of the archive." らしい。PEP 427より。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ショットガンシャッフルはN回やると元の状態に戻るぜ!

ショットガンシャッフルはカードを痛めるぜ!!! カードを痛めることで広く知られている通称ショットガンシャッフル、 正式名称リフルシャッフルはN回目のシャッフルでシャッフル前の状態に戻るそうです。 何回で元に戻るのか検証してみましょう。 52枚のトランプを痛める それではやっていきましょう。 今回は52枚のトランプを使っていきます。 手順としては52枚のトランプを用意してショットガンシャッフルを行い、 元の状態に戻るまでシャッフルを続けて、その時のシャッフル回数を表示する方針となります。 元の状態に戻ったかどうかの判定ですが、頭のいい方法が思いつかない凡骨だったので シャッフル前のデッキとシャッフルしているデッキ間の52枚全部についてそれぞれ繰り返しで比較しています。 無限ループに陥ってしまうと怖いので100回のシャッフルでループを抜けるようにしましたが、 結果それ以下の回数で試行が終了したのでコメントアウトしています。 ShotGunShuffle.py import pandas as pd vCards = [] for lSuits in ['H', 'S', 'C', 'D']: vCards.extend(str(lNum) + lSuits for lNum in list(range(1, 14))) vOriginDeck = pd.DataFrame({'card': vCards, 'sort': list(range(0, 52))}, index=list(range(0, 52))) vDeck = vOriginDeck vCountShuffle = 0 vIsDeckOrigin = False while vIsDeckOrigin == False: vCountShuffle += 1 for i in list(range(0, 52)): if i < 26: vDeck.iat[i, 1] = i*2 else: vDeck.iat[i, 1] = (i-25)*2-1 vDeck = vDeck.sort_values('sort') vDeck.reset_index(drop=True, inplace=True) for i in list(range(0, 52)): vDeck.iat[i, 1] = i vIsDeckOrigin = True for i in list(range(0, 52)): if vOriginDeck.iat[i, 0] != vDeck.iat[i, 0]: vIsDeckOrigin = False # if vCountShuffle > 100: # vIsDeckOrigin = True print(vCountShuffle) 結果 8 ショットガンシャッフルはN回やると元の状態に戻るぜ!(N=8) おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Djangoでwebサイト開発(Blog編)#1

こんにちは今回は、PythonのwebフレームワークDjangoを使って簡単な個人ブログサイトの構築をしていきます。 1、開発環境 今回使用した開発環境は以下のようになっています。 ①OS: Windows11 ②言語: Python, HTML & CSS 2、Djangoを始める まず開発ディレクトリ(今回はmyappというフォルダ)直下で以下のコマンドを入力してください。 windowsならコマンドプロンプト、mac, Linuxならターミナル C:開発ディレクトリ>django-admin startproject プロジェクト名 . ※この記事ではわかりやすくmyprojectという名前のプロジェクトを作成しました。 すると、開発ディレクトリに以下のディレクトリ構成が作成されます。 myapp/ myproject/ ┣ settings.py ┣ __init__.py ┣ asgi.py ┣ wisgy.py ┗ urls.py manage.py 以上の構成を確認したら、コマンドプロンプト or ターミナルで以下を実行してください。 C:~~myapp>python manage.py runserver すると以下のログが流れブラウザで表示するとスタートページ(ロケットページ)が表示されます。 Django version 3.2.9, using settings 'myproject.settings' Starting development server at http://0.0.0.0:8000/ Quit the server with CONTROL-C. djangoは8000番ポートでサーバーが起動します。(ちなみに有名なruby on railsとかだと3000番です。) ここまで来たらdjangoでのプロジェクト開始は成功です。 3、Djangoアプリを始める 上の章でDjangoプロジェクトを作成、サーバ立ち上げを行いました。 この章ではDjangoアプリの実装を行いたいと思います。 まず、コマンドプロンプトでmyappディレクトリ直下に入り、以下のコマンドを入力してください。 今回は「アプリ名:myblog」で行いました。 C:~~myapp>python manage.py startapp myblog すると以下のようなディレクトリ構成を確認できると思います。 myapp/ myblog/ ┣ admin.py ┣ __init__.py ┣ apps.py ┣ test.py ┣ views.py ┣ models.py ┗ urls.py(自分で作成) myproject/ manage.py myprojectとmanage.pyと同じ階層にmyappというフォルダが作成されます。 ここでmyproject/settings.pyを編集しましょう。 settings.py 以下、必要な部分を抜粋! import os(最初の部分に追加) INSTALLED_APPS = [ 'myblog.apps.MyblogConfig',(※追加) 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [os.path.join(BASE_DIR, 'templates')],(※追加) 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'myblog.context.context', ], }, }, ] コードの最後のほう LANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo' 上記のTEMPLATESではtemplateファイルの設定を行っています。 templateファイルはサイトの見た目を形成するフロントエンドの部分です。ここではBASE_DIR内のtemplatesフォルダに格納するという意味です。 実際にtemplatesファイルを作成しましょう。 myapp/ myblog/ ┣ templates /myblog ┣ admin.py ┣ __init__.py ┣ apps.py ┣ test.py ┣ views.py ┣ models.py ┗ urls.py(自分で作成) できました!では実際にブログアプリの実装をしていきます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【AWS】素の Cloud9 に Python 3.9 をインストールする

結果何とかなりましたが時間を吸われたので、 備忘録として残します。 1. やりたいこと AWS Cloud9 Python 3.7.10 (既定のバージョン※) を 3.9.0 にアップデートする ※ 2021 年 11 月 24 日 現在 2. 前提 環境 作成してそのままの Cloud9 (Amazon Linux 2) 3. 方法 Python バージョン 3.7.10 からスタートします。 $ python --version Python 3.7.10 3.1. pyenv インストール まず pyenv をインストールします。 $ git clone https://github.com/pyenv/pyenv.git ~/.pyenv # 確認 $ ~/.pyenv/bin/pyenv --version pyenv 2.2.2-1-gf2925393 ~/.bashrc に以下を記載し、 pyenv コマンドへパスを通します。 ~/.bashrc export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv init -)" 変更を適用し、 pyenv コマンドが通ることを確認します。 $ source ~/.bashrc # 確認 $ pyenv --version pyenv 2.2.2-1-gf2925393 $ pyenv versions * system (set by /home/ec2-user/.pyenv/version) インストール可能な Python バージョンを表示してみます。 $ pyenv install --list | grep 3.9.0 3.9.0 上手くいきそうに見えますが、いざインストールすると警告が出てきます。 # インストールした場合、警告が出る $ pyenv install 3.9.0 : WARNING: The Python bz2 extension was not compiled. Missing the bzip2 lib? Installed Python-3.9.0 to /home/ec2-user/.pyenv/versions/3.9.0 bzip2 を確認してみると、既にインストールされています。 $ sudo yum list installed | grep bzip bzip2.x86_64 1.0.6-13.amzn2.0.3 @amzn2-core bzip2-libs.x86_64 1.0.6-13.amzn2.0.3 @amzn2-core 色々調べた結果、足りていないパッケージは bzip2-devel でした。 3.2. bzip2-devel インストール 念のため yum 自体をアップデートしてからインストールします。 # 念のためアップデート $ sudo yum -y update # bzip2-devel をインストール $ sudo yum -y install bzip2-devel : Installed: bzip2-devel.x86_64 0:1.0.6-13.amzn2.0.3 Complete! # 確認 $ sudo yum list installed | grep bzip bzip2.x86_64 1.0.6-13.amzn2.0.3 @amzn2-core bzip2-devel.x86_64 1.0.6-13.amzn2.0.3 @amzn2-core bzip2-libs.x86_64 1.0.6-13.amzn2.0.3 @amzn2-core 入りました。 3.3. Python 3.9.0 インストール 満を持して Python 3.9.0 をインストールします。 $ pyenv install 3.9.0 Downloading Python-3.9.0.tar.xz... -> https://www.python.org/ftp/python/3.9.0/Python-3.9.0.tar.xz Installing Python-3.9.0... Installed Python-3.9.0 to /home/ec2-user/.pyenv/versions/3.9.0 # 確認 $ pyenv versions * system (set by /home/ec2-user/.pyenv/version) 3.9.0 警告が消えました。 3.4. Python 3.9.0 に切り替える pyenv global で Python バージョンを切り替えます。 $ pyenv global 3.9.0 # 確認 $ pyenv versions system * 3.9.0 (set by /home/ec2-user/.pyenv/version) しかし、これだけでは Python バージョンは切り替わりません。 # 切り替わっていない $ python --version Python 3.7.10 $ which python alias python='python3' /usr/bin/python3 # 見つからない $ which python3.9 /usr/bin/which: no python3.9 in (/home/ec2-user/.pyenv/bin:...(省略) Cloud9 ではデフォルトで python コマンドの Alias 設定があるため、切り替わらないようです。 他の記事で見かけるように、 Alias 設定が ~/.bashrc に見当たらず困っていたのですが、 ~/.bash_profile に以下を記載することで解決しました。 ~/.bash_profile export PATH="$HOME/.pyenv/shims:$PATH" 変更を適用します。 $ source ~/.bash_profile # 確認 $ python --version Python 3.9.0 $ which python alias python='python3' ~/.pyenv/shims/python3 # 見つかる! $ which python3.9 ~/.pyenv/shims/python3.9 無事 Python バージョンを 3.9.0 に切り替えることができました。 pip もアップデートします。 $ pip install --upgrade pip : Successfully uninstalled pip-20.2.3 Successfully installed pip-21.3.1 3.5. バージョン確認 $ python --version Python 3.9.0 $ pyenv versions system * 3.9.0 (set by /home/ec2-user/.pyenv/version) $ pip -V pip 21.3.1 from /home/ec2-user/.pyenv/versions/3.9.0/lib/python3.9/site-packages/pip (python 3.9) いい感じになりました! 4. 参考サイト pyenvによる仮想Python環境をAWS Cloud9上で構築する pythonのバージョンが切り替わらない ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python 正規表現まとめ

はじめに 個人的まとめ PythonのreモジュールはPerlに見られる正規表現マッチング操作と同様のものを提供するもの DjangoなどでもURLパターンの指定の際などに使われる re - Regular expression operations - Python 3.9.7 documentation Regular Expression HOWTO - Python 3.9.7 documentation raw string 記法 多くの場合 Python コードの中の正規表現はこの raw string 記法を使って書かれる \section # マッチさせるテキスト \\section # re.compile() のためのバックスラッシュエスケープ "\\\\section" # 文字列リテラルのためのバックスラッシュエスケープ '\'はre.compile()に通すために'\\'とバックスラッシュエスケープする必要がある '\\'はさらにコードに書くためにそれぞれの'\'をバックスラッシュエスケープする必要があるため、'\\\\'となる 'r'を文字列リテラルの先頭に書くことでバックスラッシュは特殊文字扱いされなくなるため、'\\\\'は'r"\\"'とすることができる "\n" r"\n" "\n"は改行文字を表す r"\n"は2つの文字'\'と'n'を含む文字列となる "ab*" r"ab*" "\\\\section" r"\\section" "\\w+\\s+\\1" r"\w+\s+\1" 他にも上記のようにそれぞれr''を使うことができる 特殊文字 一覧 以下のような特殊文字が存在する.それぞれ詳しくみていく. . ^ $ * + ? { } [ ] \ | ( ) [] マッチさせたい文字の集合を表す 文字を個別にリストするか、または二つの文字を '-' でつなげて文字を範囲で与えることもできる [abc] [a-c] 'a', 'b', 'c'のどれかにマッチする [a-z] 小文字のアルファベットのどれか一文字にマッチする [akm$] 'a', 'k', 'm' または '$' にマッチ '$'のような特殊文字も、[]内では普通の文字として扱われる [^5] '5' 以外の文字にマッチ []内にある^は補集合を表す 補集合を取る ことで、文字クラス内のリストにない文字に対してマッチさせられる [5^] '5' や '^' にマッチ ^が[]内の先頭以外にある場合は特別な意味を持たなくなる \[ \\ それぞれ[ , \にマッチ バックスラッシュを使うことで特殊文字をエスケープできる \d [0-9] 任意の十進数とマッチ 特殊シーケンスの一種 \D [^0-9] 任意の非数字文字とマッチ 特殊シーケンスの一種 \s 任意の空白文字とマッチ 特殊シーケンスの一種 \S 任意の非空白文字とマッチ 特殊シーケンスの一種 \w [a-zA-Z0-9_] 任意の英数文字および下線とマッチ 特殊シーケンスの一種 \W [^a-zA-Z0-9_] 任意の非英数文字とマッチ 特殊シーケンスの一種 [\s,.] 空白文字や ',' または '.' にマッチ 特殊シーケンスは[]に入れることができる . 改行文字を除く任意の文字にマッチ 改行文字に対してもマッチさせる代替モードもある a/{1,3}b 'a/b', 'a//b', 'a///b' にマッチ {m, n}で最低m回、最大でn回の直前文字の繰り返しにマッチする a/{2,}b 'a//b', 'a///b', 'a//////////b'などにマッチ {m, n}のnを省略した形で、その場合は上限が無限大になり、m回以上の直前文字の繰り返しとマッチする a/{,3}b 'ab', 'a/b', 'a//b', 'a///b' にマッチ {m, n}のmを省略した形で、その場合は下限が0になり、最低0回、最大でn回の直前文字の繰り返しにマッチする ca*t ca{0,}t 'ct', 'cat', 'caaat'などにマッチ 直前文字の0回以上の繰り返しにマッチ a[bcd]*b a[bcd]{0,}b 'ab', 'acb', 'abcb', 'adbb'などにマッチ [bcd]*とは'b', 'c', 'd'のいづれかの文字の0回以上の繰り返しを意味する ca+t ca{1,}t 'cat', 'caaat'などにマッチ 直前文字の0回以上の繰り返しにマッチ 'ct'にはマッチしないことに注意 home-?brew home-{0,1}brew 'homebrew' , 'home-brew' にマッチ 直前文字の0または1回の繰り返しにマッチ 2回以上の繰り返しにはマッチしない Crow|Servo 'Crow'または'Servo'にマッチ A, Bがそれぞれ特殊文字でない文字列のとき、A|BはAまたはBにマッチする ^From 'From Here to Eternity'などの行頭'From'にマッチ ^は行の先頭を意味する c$ 'abc', 'abc\n'などの行末'c'にマッチ $は行の末尾にマッチする 行の末尾とは、文字列の末尾か、または改行文字の直前と定義されている c\z 'abc'などの行末'c'にマッチ \zは文字の末尾とのみマッチする 'abc\n'の'c'とはマッチしない r'\bclass\b' 'no class at all'や'@class@'などの単語'class'とマッチ 'classify'などの'class'とはマッチしない 単語は英数文字のシーケンスとして定義されている 空白または非英数文字が単語の区切りになる raw stringを利用していない場合、Python は\bをバックスペースに変換し、正規表現は期待するものとマッチしなくなる \Bclass\B 'classify'などの'class'とマッチする 'no class at all'や'@class@'などの単語'class'とはマッチしない \bと逆で、現在の位置が単語の境界でないときにのみマッチ 使い方 >>> import re >>> p = re.compile('[a-z]+') >>> p.match('tempo') <re.Match object; span=(0, 5), match='tempo'> >>> m = p.search('@@@tempo') >>> m <re.Match object; span=(3, 8), match='tempo'> >>> re.search('[a-z]+', 'tempo') <re.Match object; span=(0, 5), match='tempo'> >>> m.group() 'tempo' >>> m.start() 3 >>> m.end() 8 >>> m.span() (3, 8) >>> p.findall('@abc1@def2@ghi3@') ['abc', 'def', 'ghi'] match()は文字列の開始位置がマッチするかどうかだけをチェックする search()は文字列全体をチェックする re.search('検索文字列', '対象文字列')という形もある group()は正規表現にマッチした文字列を返す start()はマッチの開始位置を返す end()はマッチの終了位置を返す span()はマッチの位置 (start, end) を含むタプルを返す findall()はマッチした文字列のリストを返す p = re.compile('[a-z]+') m = p.match('tempo') if m: print(m.group()) else: print('No match') Pythonファイルで実行するときは、変数にmatchオブジェクトを代入させたあと、変数が空かどうかを調べるという形が一般的
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python,Djangoの正規表現まとめ

はじめに 個人的まとめ PythonのreモジュールはPerlに見られる正規表現マッチング操作と同様のものを提供するもの DjangoなどでもURLパターンの指定の際などに使われる re - Regular expression operations - Python 3.9.7 documentation Regular Expression HOWTO - Python 3.9.7 documentation raw string 記法 多くの場合 Python コードの中の正規表現はこの raw string 記法を使って書かれる \section # マッチさせるテキスト \\section # re.compile() のためのバックスラッシュエスケープ "\\\\section" # 文字列リテラルのためのバックスラッシュエスケープ '\'はre.compile()に通すために'\\'とバックスラッシュエスケープする必要がある '\\'はさらにコードに書くためにそれぞれの'\'をバックスラッシュエスケープする必要があるため、'\\\\'となる 'r'を文字列リテラルの先頭に書くことでバックスラッシュは特殊文字扱いされなくなるため、'\\\\'は'r"\\"'とすることができる "\n" r"\n" "\n"は改行文字を表す r"\n"は2つの文字'\'と'n'を含む文字列となる "ab*" r"ab*" "\\\\section" r"\\section" "\\w+\\s+\\1" r"\w+\s+\1" 他にも上記のようにそれぞれr''を使うことができる 特殊文字 一覧 以下のような特殊文字が存在する.それぞれ詳しくみていく. . ^ $ * + ? { } [ ] \ | ( ) [] マッチさせたい文字の集合を表す 文字を個別にリストするか、または二つの文字を '-' でつなげて文字を範囲で与えることもできる [abc] [a-c] 'a', 'b', 'c'のどれかにマッチする [a-z] 小文字のアルファベットのどれか一文字にマッチする [akm$] 'a', 'k', 'm' または '$' にマッチ '$'のような特殊文字も、[]内では普通の文字として扱われる [^5] '5' 以外の文字にマッチ []内にある^は補集合を表す 補集合を取る ことで、文字クラス内のリストにない文字に対してマッチさせられる [5^] '5' や '^' にマッチ ^が[]内の先頭以外にある場合は特別な意味を持たなくなる \[ \\ それぞれ[ , \にマッチ バックスラッシュを使うことで特殊文字をエスケープできる \d [0-9] 任意の十進数とマッチ 特殊シーケンスの一種 \D [^0-9] 任意の非数字文字とマッチ 特殊シーケンスの一種 \s 任意の空白文字とマッチ 特殊シーケンスの一種 \S 任意の非空白文字とマッチ 特殊シーケンスの一種 \w [a-zA-Z0-9_] 任意の英数文字および下線とマッチ 特殊シーケンスの一種 \W [^a-zA-Z0-9_] 任意の非英数文字とマッチ 特殊シーケンスの一種 [\s,.] 空白文字や ',' または '.' にマッチ 特殊シーケンスは[]に入れることができる . 改行文字を除く任意の文字にマッチ 改行文字に対してもマッチさせる代替モードもある a/{1,3}b 'a/b', 'a//b', 'a///b' にマッチ {m, n}で最低m回、最大でn回の直前文字の繰り返しにマッチする a/{2,}b 'a//b', 'a///b', 'a//////////b'などにマッチ {m, n}のnを省略した形で、その場合は上限が無限大になり、m回以上の直前文字の繰り返しとマッチする a/{,3}b 'ab', 'a/b', 'a//b', 'a///b' にマッチ {m, n}のmを省略した形で、その場合は下限が0になり、最低0回、最大でn回の直前文字の繰り返しにマッチする ca*t ca{0,}t 'ct', 'cat', 'caaat'などにマッチ 直前文字の0回以上の繰り返しにマッチ a[bcd]*b a[bcd]{0,}b 'ab', 'acb', 'abcb', 'adbb'などにマッチ [bcd]*とは'b', 'c', 'd'のいづれかの文字の0回以上の繰り返しを意味する ca+t ca{1,}t 'cat', 'caaat'などにマッチ 直前文字の0回以上の繰り返しにマッチ 'ct'にはマッチしないことに注意 home-?brew home-{0,1}brew 'homebrew' , 'home-brew' にマッチ 直前文字の0または1回の繰り返しにマッチ 2回以上の繰り返しにはマッチしない Crow|Servo 'Crow'または'Servo'にマッチ A, Bがそれぞれ特殊文字でない文字列のとき、A|BはAまたはBにマッチする ^From 'From Here to Eternity'などの行頭'From'にマッチ ^は行の先頭を意味する c$ 'abc', 'abc\n'などの行末'c'にマッチ $は行の末尾にマッチする 行の末尾とは、文字列の末尾か、または改行文字の直前と定義されている c\z 'abc'などの行末'c'にマッチ \zは文字の末尾とのみマッチする 'abc\n'の'c'とはマッチしない r'\bclass\b' 'no class at all'や'@class@'などの単語'class'とマッチ 'classify'などの'class'とはマッチしない 単語は英数文字のシーケンスとして定義されている 空白または非英数文字が単語の区切りになる raw stringを利用していない場合、Python は\bをバックスペースに変換し、正規表現は期待するものとマッチしなくなる \Bclass\B 'classify'などの'class'とマッチする 'no class at all'や'@class@'などの単語'class'とはマッチしない \bと逆で、現在の位置が単語の境界でないときにのみマッチ 使い方 >>> import re >>> p = re.compile('[a-z]+') >>> p.match('tempo') <re.Match object; span=(0, 5), match='tempo'> >>> m = p.search('@@@tempo') >>> m <re.Match object; span=(3, 8), match='tempo'> >>> re.search('[a-z]+', 'tempo') <re.Match object; span=(0, 5), match='tempo'> >>> m.group() 'tempo' >>> m.start() 3 >>> m.end() 8 >>> m.span() (3, 8) >>> p.findall('@abc1@def2@ghi3@') ['abc', 'def', 'ghi'] match()は文字列の開始位置がマッチするかどうかだけをチェックする search()は文字列全体をチェックする re.search('検索文字列', '対象文字列')という形もある group()は正規表現にマッチした文字列を返す start()はマッチの開始位置を返す end()はマッチの終了位置を返す span()はマッチの位置 (start, end) を含むタプルを返す findall()はマッチした文字列のリストを返す p = re.compile('[a-z]+') m = p.match('tempo') if m: print(m.group()) else: print('No match') Pythonファイルで実行するときは、変数にmatchオブジェクトを代入させたあと、変数が空かどうかを調べるという形が一般的
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSeleniumのHeadless Chromeを用いたWebスクレイピング (目的地までの所要時間を調べる)

概要 下記の記事に引き続き,今回は入力された『出発地』から『目的地』までの所要時間をWebスクレイピングにより取得するPythonスクリプトを開発してみた. PythonでSeleniumのHeadless Chromeを用いたWebスクレイピング (30画以下の漢字の画数を調べる) 開発 【システム環境】 OS X El Capitan 10.11以降(他のOSでも可) Python 3.6.5以降 本記事ではディレクトリ操作をMacに合せて記述しているため,他のOS(Linux, Windows等)の使用時は必要に応じてディレクトリ構造を置き換えて考えるものとする. 外部ライブラリのインストール $ pip install selenium Pythonスクリプトとパッケージ・モジュールの作成 $ mkdir ~/RequiredTimeSearcher $ touch ~/RequiredTimeSearcher/required time_searcher.py $ mkdir ~/RequiredTimeSearcher/common $ touch ~/RequiredTimeSearcher/common/browser_controller.py モジュールbrowser_controller.pyのソースコード browser_controller.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- from selenium.webdriver import ChromeOptions, Chrome from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys class Transit: def __init__(self): self.start = input('出発予定地を入力してください: ') self.end = input('到着予定地を入力してください: ') self.options = ChromeOptions() # Chrome起動時のオプションを設定 self.startOptions() self.driver = Chrome(options=self.options) # 検索開始の表示 def startMessage(self): print('『' + self.start + '』' + 'から' + '『' + self.end + '』' + 'への所要時間を検索中...\n') # 処理を早めるためにChrome起動時のオプションを設定 def startOptions(self): # UserAgentをヘッドレスモードに設定 self.options.add_argument('--headless') self.options.add_argument('--disable-gpu') # SSLエラーを許容 self.options.add_argument('--ignore-certificate-errors') # シークレットモード指定 self.options.add_argument('--incognito') # 画像を読み込まないよう設定 self.options.add_argument('--blink-settings=imagesEnabled=false') # Chromeを起動 def chromeStart(self): url = 'https://transit.yahoo.co.jp/' self.driver.get(url) # フォームに地名を入力して検索 def transitInput(self): self.driver.find_element(By.NAME, 'from').send_keys(self.start) self.driver.find_element(By.NAME, 'to').send_keys(self.end) self.driver.find_element(By.ID, 'searchModuleSubmit').click() # 所要時間を取得 (XPathで指定) def getText(self): # 要素の存在確認用の変数宣言 (elementsでなくelementsとする) elements = self.driver.find_elements(By.XPATH, '//*[@id="rsltlst"]/li/dl/dd/ul/li[1]/span[2]') # 上記要素が存在するか否かで分岐処理 if elements: self.time = self.driver.find_element(By.XPATH, '//*[@id="rsltlst"]/li/dl/dd/ul/li[1]/span[2]').text print('所要時間: ' + self.time) else: print('正しい地名を入力してください') exit() # Chromeを終了 def chromeShutdown(self): self.driver.quit() 実行ファイルrequired_time_searcher.pyのソースコード required_time_searcher.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- from common.browser_controller import Transit def main(): # インスタンスの生成 operation = Transit() # メソッドの呼び出し operation.startMessage() operation.chromeStart() operation.transitInput() operation.getText() operation.chromeShutdown() if __name__ == '__main__': main() 実行 required_time_searcher.pyを実行 $ python ~/RequiredTimeSearcher/required time_searcher.py 出発予定地を入力してください: 浜松町 #出発地を入力 到着予定地を入力してください: 駒込 #目的地を入力 『浜松町』から『駒込』への所要時間を検索中... 所要時間: 25分 例として出発地に『浜松町』,目的地に『駒込』を入力してみたところ所要時間25分が得られた.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

コンピュータとオセロ対戦28 ~勝敗予測、改善~

前回 今回の目標 前回の機械学習を改善する ここから本編 具体的に、ターン数5でも平均絶対誤差が10程度になれば有効に予想できているといえると考え、これを目標に改善をしていきます。 改善案1 前回は盤面情報のみから学習を行い、最終結果を予想しました。 今回はより精度の高い学習を目指し、以下の点を変更しました。 説明変数として次のターンで相手が置ける手数を追加 最終スコアを「黒の駒の数-白の駒の数」から「自分の駒の数-相手の駒の数」に変更、それに伴い「黒の盤面」を「自分の盤面」に、「白の盤面」を「相手の盤面」に変更 学習方法を、前回良い結果を残したLinearRegression、Ridge、KNeighborsClassifierにしぼる ターン数を10回ごとから5回ごとに osero_learn プログラムを以下のように変更し、指定したターン数の時にターン数(turn_num)、相手のとれる手数(opp_put_num)、今どちらのターンか(turn)、自分の盤面(my0~my63)、相手の盤面(opp0~opp63)を記録できるようにしました。 check_point変数に配列を入れ、ターン数を指定します。 前回turnと呼んでいたものが今回はturn_numに変わっています。 osero_learn.py from pandas import DataFrame from BitBoard import osero class learn(osero): def __init__(self, black_method, white_method, check_point,\ read_goal=[1, 1], eva=None): super().__init__(black_method, white_method, read_goal, eva) self.check_point = check_point def play(self) -> DataFrame: can, old_can = True, True turn_num = 0 data = {} self.first_data_set(data) can = self.check_all() while can or old_can: if can: turn_num += 1 if self.turn: self.think[self.black_method]() else: self.think[self.white_method]() if turn_num in self.check_point: self.data_set(data, turn_num) self.turn = not self.turn old_can = can can = self.check_all() self.count_last() for i in range(len(data["turn_num"])): if data["turn"][i]: data["last_score"].append(self.score) else: data["last_score"].append(-self.score) return DataFrame(data) def first_data_set(self, data: DataFrame) -> None: data["turn_num"] = [] data["opp_put_num"] = [] data["turn"] = [] data["last_score"] = [] for i in range(64): data["my%d" % i] = [] data["opp%d" % i] = [] def count_last(self) -> None: 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"]) self.score = black - white def search_put(self) -> int: num = 0 for i in range(8): for j in range(8): if self.check(i, j, self.bw, not self.turn): num += 1 return num def data_set(self, data: DataFrame, turn_num: int) -> None: data["turn_num"].append(turn_num) data["opp_put_num"].append(self.search_put()) data["turn"].append(self.turn) if self.turn: my = ["b_u", "b_d"] opp = ["w_u", "w_d"] else: my = ["w_u", "w_d"] opp = ["b_u", "b_d"] for i in range(32): data["my%d" % i].append(int((self.bw[my[0]] & (1 << i)) != 0)) data["opp%d" % i].append(int((self.bw[opp[0]] & (1 << i)) != 0)) for i in range(32): data["my%d" % (i + 32)].append(int((self.bw[my[1]] & (1 << i)) != 0)) data["opp%d" % (i + 32)].append(int((self.bw[opp[1]] & (1 << i)) != 0)) run 前回同様ipynbで作成。 前回とほぼ同じなのでプログラムは省略します。 学習結果 前回同様、ターン数ごとに学習方法別のスコアと平均絶対誤差、学習方法ごとにターン数別のスコアと平均絶対誤差をグラフ化しました。 LinearRegressionがやや情緒不安定ですが、それ以外はターン数が進むごとに少しずつ良いスコアとなっていっているのが見て取れます。 今更ですがmodel nameを傾ける必要は今回なかったような・・・。 こちらもLinearRegression以外はターン数が進むごとに順調に誤差を小さくできています。 LinearRegressionは相変わらず55ターン目で過学習を起こしているようです。 理由はわかりません、今回ランダム関数のシード値を指定していないので再現性のないプログラミングをしてしまいました。もう一度実行すれば55ターンでの過学習は起きない可能性がありますが、そもそもLinearRegressionはRidgeに比べ過学習しやすいので他のターンで過学習が起きる可能性はあります。 Ridgeが15ターン目で大きくテストスコアが下がっているのが気になります。15ターンの前後も下がっているので偶然ではなく理由があると思います。 全体として、前回より向上したとは言えない誤差となりました。 まとめ あまり改善しなかった。 理由として考えられるのは、新しく追加した「opp_put_num」が、あまり意味がなかったのでは? ということです。終盤ならまだしも、序盤であれば「次のターンで相手がとれる手数」が最終結果に響くことはそうそうないのでは? と今更ながら気づきました。そして、終盤は追加の説明変数なんてなくてもある程度正確に最終結果を予測できています。 最終スコアを「自分の駒の数-相手の駒の数」としたのは正しい判断だったと思うので一応継続させます。 改善案2 改善案1及び前回でも優秀だったRidgeに絞り学習を行いたいと思います。 また、盤面情報だけでなく現在のスコア(自分の駒の数-相手の駒の数)および現在のカスタムスコア(自分のカスタムスコア-相手のカスタムスコア)も説明変数として追加してみようと思います。ここでカスタムスコアとは、評価値の重み付きのスコアのことです。 BitBoard オセロのためのスーパークラス。 乱数のシード値を指定できるようにしました。 BitBoard.py def __init__(self, black_method, white_method,\ seed_num=0, read_goal=[1, 1], eva=None): self.think = [\ self.human, self.random, self.nhand, self.nhand_custom, self.nleast, self.nmost ] if (black_method == osero.PLAY_WAY["nhand_custom"]\ or white_method == osero.PLAY_WAY["nhand_custom"])\ and eva is None: raise ValueError("designate eva") self.black_method = black_method self.white_method = white_method self.read_goal = read_goal self.eva = eva if black_method == osero.PLAY_WAY["nhand"]: self.eva[0] = [1] * 64 if white_method == osero.PLAY_WAY["nhand"]: self.eva[1] = [1] * 64 seed(seed_num) self.setup() osero_learn 学習のデータ集めのためのクラス。 dataにscoreとcustom_scoreを追加しました。 osero_learn.py def data_set(self, data: DataFrame, turn_num: int) -> None: if self.turn: my = ["b_u", "b_d"] opp = ["w_u", "w_d"] else: my = ["w_u", "w_d"] opp = ["b_u", "b_d"] data["turn_num"].append(turn_num) data["turn"].append(self.turn) data["score"].append(self.popcount(self.bw[my[0]]) + self.popcount(self.bw[my[1]]) - self.popcount(self.bw[opp[0]]) - self.popcount(self.bw[opp[1]])) data["custom_score"].append(self.count(self.bw, self.turn)) for i in range(32): data["my%d" % i].append(int((self.bw[my[0]] & (1 << i)) != 0)) data["opp%d" % i].append(int((self.bw[opp[0]] & (1 << i)) != 0)) for i in range(32): data["my%d" % (i + 32)].append(int((self.bw[my[1]] & (1 << i)) != 0)) data["opp%d" % (i + 32)].append(int((self.bw[opp[1]] & (1 << i)) != 0)) run 実行ファイル。 ipynbで書いています。 データ集め部分 今までと全く同じなので省略。 学習部分 盤面情報はすべての学習で使うとして、スコアとカスタムスコアについては、 両方使わない スコアのみ使う カスタムスコアのみ使う 両方使う という四パターンで学習を行いグラフを作成しました。 run.py import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error from sklearn.linear_model import Ridge x_data = df.drop(["turn", "last_score"], axis=1) y_data = df[["turn_num", "last_score"]] turn_vari = df["turn_num"].unique() drop_vari = ["custom_score and score", "custom_score", "score", "None"] ################################ train_score = [] test_score = [] train_MAE = [] test_MAE = [] for turn_num in turn_vari: train_score.append([]) test_score.append([]) train_MAE.append([]) test_MAE.append([]) for drop_list in [["custom_score", "score"], ["custom_score"], ["score"], []]: drop_list.append("turn_num") x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop(drop_list, axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), test_size=0.3, random_state=0 ) model = Ridge(random_state=0) model.fit(x_train, y_train) train_score[-1].append(model.score(x_train, y_train)) test_score[-1].append(model.score(x_test, y_test)) train_predict = model.predict(x_train) train_MAE[-1].append(mean_absolute_error(train_predict, y_train)) test_predict = model.predict(x_test) test_MAE[-1].append(mean_absolute_error(test_predict, y_test)) ################################ width = 0.3 x_axis = np.array([i + 1 for i in range(len(drop_vari))]) for i in range(len(turn_vari)): fig_name = "score of each dropping (number of turn is %d)" % turn_vari[i] fig = plt.figure(figsize=(10, 10)) plt.bar(x_axis, train_score[i], label="train score", width=width) plt.bar(x_axis + width, test_score[i], label="test score", width=width) plt.xticks(x_axis + width/2, labels=drop_vari, rotation=15) plt.legend() plt.title(fig_name) plt.xlabel("dropped column") plt.ylabel("score") plt.savefig("fig/" + fig_name) # plt.show() plt.clf() plt.close() fig_name = "MAE of each dropping (number of turn is %d)" % turn_vari[i] fig = plt.figure(figsize=(10, 10)) plt.bar(x_axis, train_MAE[i], label="train MAE", width=width) plt.bar(x_axis + width, test_MAE[i], label="test MAE", width=width) plt.xticks(x_axis + width/2, labels=drop_vari, rotation=15) plt.legend() plt.title(fig_name) plt.xlabel("dropped column") plt.ylabel("mean absolute error") plt.savefig("fig/" + fig_name) # plt.show() plt.clf() plt.close() ################################ x_axis = np.array([i + 1 for i in range(len(turn_vari))]) x_axis_name = [str(i) for i in turn_vari] train_score_T = np.array(train_score).T test_score_T = np.array(test_score).T train_MAE_T = np.array(train_MAE).T test_MAE_T = np.array(test_MAE).T for i in range(len(drop_vari)): fig_name = "score of each turn number (dropped column is %s)" % drop_vari[i] fig = plt.figure(figsize=(10, 10)) plt.plot(x_axis_name, train_score_T[i], label="train score") plt.plot(x_axis_name, test_score_T[i], label="test score") plt.legend() plt.title(fig_name) plt.xlabel("turn number") plt.ylabel("score") plt.savefig("fig/" + fig_name) # plt.show() plt.clf() plt.close() fig_name = "MAE of each turn number (dropped column is %s)" % drop_vari[i] fig = plt.figure(figsize=(10, 10)) plt.plot(x_axis_name, train_MAE_T[i], label="train MAE") plt.plot(x_axis_name, test_MAE_T[i], label="test MAE") plt.legend() plt.title(fig_name) plt.xlabel("turn number") plt.ylabel("mean absolute error") plt.savefig("fig/" + fig_name) # plt.show() plt.clf() plt.close() 学習結果 例のごとくターン数ごとの説明変数別のスコアと平均絶対誤差、説明変数ごとのターン数別のスコアと平均絶対誤差をグラフにしました。 ターン数が進むごとに良くなっている、とは言えない結果になりました。 よくなったり悪くなったりを繰り返しています。 また、どの棒グラフも似たような形をしています。 こちらもターン数が進むごとに線形に数字が変わるような結果にはなりませんでした。 しかし誤差が最大でも20程度に収まったのはこれまでの改善案などと違うところです。 60ターン目のグラフで、custom_scoreのみ除いたデータ、つまりスコアを入れたデータの正解率が高いですが、これは単純に説明変数として与えたスコアと最終スコアがほぼ同じになるからだと考えられます。 どのグラフも紆余曲折をへてスコア1に収束しています。 大きな違いはありません。 こちらも、15ターンから20ターンで誤差が大きくなるものの最大誤差は25程度にとどまっています。 まとめ 「今のスコア」や「今のカスタムスコア」を使ってもあまり結果は変わりませんでしたが、改善案1と比べ制度は上がりました。 このことから、やはり次のターンでの相手の手数はあまり関係ないこと、また目的変数を「自分のスコア-相手のスコア」にすることは有効であることがいえます。 また、15ターン付近で過学習を起こす傾向にあるともわかりました。理由は分かりません。 改善案3 今までの検証で現在のスコアなどを与えてもあまり意味はないと分かったので、ここではデータの前処理などを行い精度の向上を図ります。 osero_learn 結局data変数はこうなりました。 これらとlast_scoreのみです。 osero_learn.py def data_set(self, data: DataFrame, turn_num: int) -> None: if self.turn: my = ["b_u", "b_d"] opp = ["w_u", "w_d"] else: my = ["w_u", "w_d"] opp = ["b_u", "b_d"] data["turn_num"].append(turn_num) data["turn"].append(self.turn) for i in range(32): data["my%d" % i].append(int((self.bw[my[0]] & (1 << i)) != 0)) data["opp%d" % i].append(int((self.bw[opp[0]] & (1 << i)) != 0)) for i in range(32): data["my%d" % (i + 32)].append(int((self.bw[my[1]] & (1 << i)) != 0)) data["opp%d" % (i + 32)].append(int((self.bw[opp[1]] & (1 << i)) != 0)) run データ集め部分 今までと全く同じなので割愛。 学習部分 二つの方法を用いて説明変数の正規化を行いました。 そのうえで学習を行い、結果をグラフ化しました。 run.py import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error from sklearn.preprocessing import StandardScaler from sklearn.preprocessing import PowerTransformer from sklearn.pipeline import Pipeline from sklearn.linear_model import Ridge x_data = df.drop(["turn", "last_score"], axis=1) y_data = df[["turn_num", "last_score"]] turn_vari = df["turn_num"].unique() ################################ def two_plot(x, y, xlabel, ylabel, title, save_dir): fig = plt.figure(figsize=(10, 10)) plt.plot(x, y[0], label="train score") plt.plot(x, y[1], label="test score") plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.legend() plt.savefig(save_dir + "/" + title) plt.clf() plt.close() # def two_bar(x, x_name, y, xlabel, ylabel, title, save_dir): # fig = plt.figure(figsize=(10, 10)) # plt.bar(x, y[0], label="train score", width=0.3) # plt.bar(x + 0.3, y[1], label="test score", width=0.3) # plt.xticks(x + 0.15, labels=x_name) # plt.title(title) # plt.xlabel(xlabel) # plt.ylabel(ylabel) # plt.legend() # plt.savefig(save_dir + "/" + title) # plt.clf() # plt.close() ################################ pipeline = Pipeline([ ("scaler", StandardScaler()), ("reg", Ridge(random_state=0)) ]) train_score = [] test_score = [] train_MAE = [] test_MAE = [] for turn_num in turn_vari: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), test_size=0.3, random_state=0 ) pipeline.fit(x_train, y_train) train_score.append(pipeline.score(x_train, y_train)) test_score.append(pipeline.score(x_test, y_test)) train_predict = pipeline.predict(x_train) train_MAE.append(mean_absolute_error(train_predict, y_train)) test_predict = pipeline.predict(x_test) test_MAE.append(mean_absolute_error(test_predict, y_test)) x = [str(i) for i in turn_vari] y = [train_score, test_score] two_plot(x, y, "turn number", "score", "standard scaler score", "fig") y = [train_MAE, test_MAE] two_plot(x, y, "turn number", "MAE", "standard scaler MAE", "fig") ################################ pipeline = Pipeline([ ("scaler", PowerTransformer()), ("reg", Ridge(random_state=0)) ]) train_score = [] test_score = [] train_MAE = [] test_MAE = [] for turn_num in turn_vari: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), test_size=0.3, random_state=0 ) pipeline.fit(x_train, y_train) train_score.append(pipeline.score(x_train, y_train)) test_score.append(pipeline.score(x_test, y_test)) train_predict = pipeline.predict(x_train) train_MAE.append(mean_absolute_error(train_predict, y_train)) test_predict = pipeline.predict(x_test) test_MAE.append(mean_absolute_error(test_predict, y_test)) x = [str(i) for i in turn_vari] y = [train_score, test_score] two_plot(x, y, "turn number", "score", "power transformed score", "fig") y = [train_MAE, test_MAE] two_plot(x, y, "turn number", "MAE", "power transformed MAE", "fig") 学習結果 正規化方法ごとにスコアと平均絶対誤差のグラフを作りました。 同じグラフを掲載しているように見えるかもしれませんが別のグラフです。 ほとんど変わらないという結果になりました。 まとめ 改善案2の時と比べ精度が下がる結果になったため、今回の場合、正規化は逆効果であると分かりました。 改善案4 正規化を行わず、インスタンス作成時に与えるパラメータを調整することで精度の向上を図ります。 データ集めのプログラムについては例のごとく省略します。 各方法でターン数ごとに学習を行い、その平均絶対誤差を方法ごとに平均し比較します。 スコアを調べてなかったのは、今回の場合平均絶対誤差の方が直感的でわかりやすいと考えたためです。 alpha 公式サイト読んだんですがよく分かりませんでした。 とりあえず0.1~20でやってみたいと思います。 デフォルト値は1.0です。 run.py import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error from sklearn.linear_model import Ridge x_data = df.drop(["turn", "last_score"], axis=1) y_data = df[["turn_num", "last_score"]] turn_vari = df["turn_num"].unique() ################################ def two_plot(x, y, xlabel, ylabel, title, save_dir): fig = plt.figure(figsize=(10, 10)) plt.plot(x, y[0], label="train MAE") plt.plot(x, y[1], label="test MAE") plt.minorticks_on() plt.grid(which="major") plt.grid(which="minor", linestyle="--") plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.legend() plt.savefig(save_dir + "/" + title) plt.clf() plt.close() ################################ alpha_arr = [i * 0.1 for i in range(1, 10)] + [i for i in range(1, 31)] train_MAE = [] test_MAE = [] for alpha in alpha_arr: train_MAE.append([]) test_MAE.append([]) for turn_num in turn_vari: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) model = Ridge(alpha=alpha, random_state=0) model.fit(x_train, y_train) train_predict = model.predict(x_train) train_MAE[-1].append(mean_absolute_error(train_predict, y_train)) test_predict = model.predict(x_test) test_MAE[-1].append(mean_absolute_error(test_predict, y_test)) train_MAE = np.array(train_MAE) test_MAE = np.array(test_MAE) y_MAE = [[], []] for i in range(len(train_MAE)): y_MAE[0].append(train_MAE[i].mean()) y_MAE[1].append(test_MAE[i].mean()) two_plot(alpha_arr, y_MAE, "alpha", "MAE", "MAE each alpha", "fig") alphaが極端に小さいと過学習を起こし、大きすぎても誤差が大きくなる結果となりました。テストデータの平均絶対誤差が最も小さかったのは3~12ほどの区間でした。 max_iter 学習プログラムは上のalphaとほぼ同じなので省略します。 まさかの全く変化なしという結果に。 tol max_iter同様全く変化なし。 solver 本当は棒グラフにすべきだと思ったのですが、これだけなのでサボりました。 lbfgsが最も良い結果になりましたが、そのほかはほぼ同じ。 総合 上の検証で、誤差の小さいパラメータ範囲が分かりましたので、今度はそれらを総当たりで、最も効果の高いパラメータを探します。 といってもsolverをlbfgsで固定し、alphaを3~12の範囲で調べます。 僅差ですが、テストデータの誤差の最小値はalpha=4の時となりました。 alpha=4, solver=lbfgsの条件で、各ターン数ごとの平均絶対誤差を調べました。 今までよりは改善されていますが、あと一息という感じですね。 まとめ alpha=4, solver=lbfgsを指定することで精度は向上しました。 しかしまだあまり正確ではありません。 改善案5 ランダムフォレストから着想を得て、複数の条件で学習したRidgeから多数決をとる手法を考えました。 solverはlbfgsで固定し、alphaを3~12の範囲で調べ、その十個の予測値を用います。 データ集めのプログラムはこれまで通りです。 なおここでは、テストデータの平均絶対誤差でモデルの良し悪しを評価します。 平均案 まず、出てきた十個の数字の平均を予測値とする案を考えました。 プログラムは以下の通り。 run.py import matplotlib.pyplot as plt import numpy as np from sklearn.model_selection import train_test_split from sklearn.metrics import mean_absolute_error from sklearn.linear_model import Ridge x_data = df.drop(["turn", "last_score"], axis=1) y_data = df[["turn_num", "last_score"]] turn_vari = df["turn_num"].unique() ################################ def two_plot(x, y, xlabel, ylabel, title, save_dir): fig = plt.figure(figsize=(10, 10)) plt.plot(x, y[0], label="train MAE") plt.plot(x, y[1], label="test MAE") plt.minorticks_on() plt.grid(which="major") plt.grid(which="minor", linestyle="--") plt.title(title) plt.xlabel(xlabel) plt.ylabel(ylabel) plt.legend() plt.savefig(save_dir + "/" + title) plt.clf() plt.close() ################################ model = [] alpha_arr = [i for i in range(3, 13)] train_MAE = [] test_MAE = [] for turn_num in turn_vari: model.append([]) for alpha in alpha_arr: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) model[-1].append(Ridge(\ alpha=alpha, solver="lbfgs", positive=True, random_state=0 )) model[-1][-1].fit(x_train, y_train) if alpha == alpha_arr[0]: train_predict = model[-1][-1].predict(x_train) test_predict = model[-1][-1].predict(x_test) else: train_predict += model[-1][-1].predict(x_train) test_predict += model[-1][-1].predict(x_test) train_predict = train_predict / len(alpha_arr) test_predict = test_predict / len(alpha_arr) train_MAE.append(mean_absolute_error(train_predict, y_train)) test_MAE.append(mean_absolute_error(test_predict, y_test)) two_plot(turn_vari, [train_MAE, test_MAE], "turn", "MAE", "MAE of mean", "fig") 実行結果は以下の通り。 改善案4の最後に示したグラフと比べると、よく見れば1程度誤差が小さくなっているターン数があります。 しかし劇的に精度が上がったりはしていません。 最頻値案 十個の数字を四捨五入し、最頻値を予測値とする案を考えました。 run.py model = [] alpha_arr = [i for i in range(3, 13)] train_MAE = [] test_MAE = [] for turn_num in turn_vari: train_predict = [] test_predict = [] model.append([]) for alpha in alpha_arr: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) model[-1].append(Ridge(\ alpha=alpha, solver="lbfgs", positive=True, random_state=0 )) model[-1][-1].fit(x_train, y_train) train_predict.append(np.array(model[-1][-1].predict(x_train))) test_predict.append(np.array(model[-1][-1].predict(x_test))) train_predict = np.array(train_predict, dtype=np.int32).T test_predict = np.array(test_predict, dtype=np.int32).T for i in range(len(alpha_arr)): for j in range(len(train_predict[0][i])): train_predict[0][i][j] = np.round(train_predict[0][i][j]) for j in range(len(test_predict[0][i])): test_predict[0][i][j] = np.round(test_predict[0][i][j]) train_predict_mode = [] test_predict_mode = [] for i in range(len(x_train)): discard, unique = np.unique(train_predict[0][i], return_counts=True) train_predict_mode.append(np.argmax(unique)) for i in range(len(x_test)): discard, unique = np.unique(test_predict[0][i], return_counts=True) test_predict_mode.append(np.argmax(unique)) train_MAE.append(mean_absolute_error(train_predict_mode, y_train)) test_MAE.append(mean_absolute_error(test_predict_mode, y_test)) two_plot(turn_vari, [train_MAE, test_MAE], "turn", "MAE", "MAE of mode", "fig") プログラムの途中で「[0][i][j]」などが出てくるのは、逆行列を求めるとなぜか三次元配列で返ってくるからです。 実行結果はこちら。 理由は分かりませんが、テスト時の誤差がトレーニングデータでの誤差を下回る結果に。 さらに、5ターン目での予測がなぜか正確になっており、14以下。 もっと不思議なのは60ターンでの予測の精度が非常に低いことです。 総合的に平均案と比べると、20ターン以前は最頻値の方が誤差が小さく、逆に25ターン以降は平均値の誤差が小さくなりました。 平均値案が全体的にalpha=4, solver=lbfgsの結果と変わらなかったことから、「結局alphaを変えても出てくる値はあまり変わらないのでは?」という懸念がありましたが、最頻値案が全く違う形のグラフを作ったのでそうでもないようです。 しかしalpha=4と固定した時も平均値案も60ターン目の精度が非常に高く出ていたのに、最頻値案で急に下がったのは不思議です。 トレーニングデータの誤差がテストデータの誤差を上回ったことに関してもよく分かりません。 面白いデータは取れましたが、どうしていいか分からないのが正直なところです。 中央値案 alphaごとの結果をソートし、その中央値を予測値として誤差を計算してみました。 run.py model = [] alpha_arr = [i for i in range(3, 13)] center = len(alpha_arr) // 2 train_MAE = [] test_MAE = [] for turn_num in turn_vari: train_predict = [] test_predict = [] model.append([]) for alpha in alpha_arr: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) model[-1].append(Ridge(\ alpha=alpha, solver="lbfgs", positive=True, random_state=0 )) model[-1][-1].fit(x_train, y_train) train_predict.append(model[-1][-1].predict(x_train)) test_predict.append(model[-1][-1].predict(x_test)) train_predict = np.array(train_predict).T test_predict = np.array(test_predict).T train_predict_median = [] test_predict_median = [] for i in range(len(x_train)): sorted = np.sort(train_predict[0][i]) train_predict_median.append(sorted[center]) for i in range(len(x_test)): sorted = np.sort(test_predict[0][i]) test_predict_median.append(sorted[center]) train_MAE.append(mean_absolute_error(train_predict_median, y_train)) test_MAE.append(mean_absolute_error(test_predict_median, y_test)) two_plot(turn_vari, [train_MAE, test_MAE], "turn", "MAE", "MAE of median", "fig") 結果はこちら。 平均案とほぼ変わりません。 しいて言えば、ターン数30の時の誤差が0.2ほど増えています。 ここから言えるのは、平均値と中央値はほぼ一致するが、最頻値はこの限りではないということです。つまりalphaを変えた時の予測値分布は正規分布に従わず、中央値付近に谷を作るような二つの山からなっているのではないでしょうか。 目的変数(最終的な自分の駒の数-相手の駒の数)は0を中心とした分布をとるはずなので、接戦や引き分けになることは少ないということでしょうか。 調和平均案 意味があるかどうかは分かりませんが、様々な学習で得られた値を、算術平均ではなく調和平均でまとめてそれを予測値として考えてみます。 幾何平均を用いない理由は、前述の通り目的変数が負の値をとったり正の値をとったりするため、幾何平均は明らかに有効ではないと考えられるためです。また、掛け算結果が負になれば計算できないためです。加重平均に関しては適切な荷重が分からないという理由です。 run.py model = [] alpha_arr = [i for i in range(3, 13)] train_MAE = [] test_MAE = [] for turn_num in turn_vari: model.append([]) for alpha in alpha_arr: x_train, x_test, y_train, y_test = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) model[-1].append(Ridge(\ alpha=alpha, solver="lbfgs", positive=True, random_state=0 )) model[-1][-1].fit(x_train, y_train) if alpha == alpha_arr[0]: train_predict = 1 / model[-1][-1].predict(x_train) test_predict = 1 / model[-1][-1].predict(x_test) else: train_predict += 1 / model[-1][-1].predict(x_train) test_predict += 1 / model[-1][-1].predict(x_test) train_predict = len(alpha_arr) / train_predict test_predict = len(alpha_arr) / test_predict train_MAE.append(mean_absolute_error(train_predict, y_train)) test_MAE.append(mean_absolute_error(test_predict, y_test)) two_plot(turn_vari, [train_MAE, test_MAE], "turn", "MAE", "MAE of harmonic mean", "fig") 学習結果はこちら。 算術平均と比較すると、10ターンでの誤差はやや小さくなっていますが、15ターンでの誤差は大きくなっています。 しかし意外にもその他に関してはほぼ変わらない結果でした。 加重平均 先ほど「加重平均は適切な加重が判断できないため行わない」と書きましたが、局所探索法を用いて誤差が小さくなる重みを探しました。 プログラムでの実装は、まず学習データとそれぞれのalphaでの学習後の予測値を保存します。その後、局所探索法を用いて最適な重みを探し更新していきます。 run.py # weighted mean 1 # make predict data from random import random, seed from copy import deepcopy alpha_arr = [i for i in range(3, 13)] train_predict = [] test_predict = [] x_train = [] x_test = [] y_train = [] y_test = [] for turn_num in turn_vari: train_predict.append([]) test_predict.append([]) x_train_ele, x_test_ele, y_train_ele, y_test_ele = train_test_split(\ x_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), y_data.query("turn_num==%d" % turn_num).drop("turn_num", axis=1), random_state=0 ) x_train.append(x_train_ele) x_test.append(x_test_ele) y_train.append(y_train_ele) y_test.append(y_test_ele) for alpha in alpha_arr: model = Ridge(\ alpha=alpha, solver="lbfgs", positive=True, random_state=0 ) model.fit(x_train_ele, y_train_ele) train_predict[-1].append(model.predict(x_train_ele)) test_predict[-1].append(model.predict(x_test_ele)) # weighted mean 2 # local search GENERATION = 100 CHILDLEN = 30 seed(0) weight = [random() for i in range(len(alpha_arr))] min_MAE = 0xffff for generation in range(GENERATION): seed(generation) print("\r%4d/%4d MAE: %d" % (generation + 1, GENERATION, min_MAE), end="") for child in range(CHILDLEN): train_predict_keep = [] test_predict_keep = [] train_MAE_sum = 0 test_MAE_sum = 0 weight_candi = deepcopy(weight) for i in range(len(weight_candi)): weight_candi[i] += (0.5 - random()) / 2 for i in range(len(turn_vari)): for j in range(len(alpha_arr)): train_predict_weight = np.array([0] * len(train_predict[i][j])) test_predict_weight = np.array([0] * len(test_predict[i][j])) for k in range(len(train_predict_weight)): train_predict_weight[k] += train_predict[i][j][k] * weight_candi[j] for k in range(len(test_predict_weight)): test_predict_weight[k] += test_predict[i][j][k] * weight_candi[j] train_predict_weight = train_predict_weight / sum(weight_candi) test_predict_weight = test_predict_weight / sum(weight_candi) train_predict_keep.append(train_predict_weight) test_predict_keep.append(test_predict_weight) train_MAE_sum += mean_absolute_error(train_predict_weight, y_train[i]) test_MAE_sum += mean_absolute_error(test_predict_weight, y_test[i]) if test_MAE_sum < min_MAE and generation != GENERATION - 1: min_MAE = test_MAE_sum train_predict_ans = deepcopy(train_predict_keep) test_predict_ans = deepcopy(test_predict_keep) weight_next = deepcopy(weight_candi) weight = deepcopy(weight_next) # weighted mean 3 # make diagram train_MAE = [] test_MAE = [] for i in range(len(turn_vari)): train_MAE.append(mean_absolute_error(train_predict_ans[i], y_train[i])) test_MAE.append(mean_absolute_error(test_predict_ans[i], y_test[i])) two_plot( turn_vari, [train_MAE, test_MAE], "turn", "MAE", "MAE of weighted mean (max generation is %d)" % GENERATION, "fig" ) # weighted mean 4 # output result print(weight) 実行結果はこちら。 算術平均とほぼ変わらない結果になりました。 また、weightは以下の通り。 [-0.1064985666709149, 0.3163889075346738, -0.10411753741468255, -0.8111324426281548, 0.22063493037623882, 0.1415032823705678, -0.3051317188273051, 0.06266523938940216, 0.5196314675991498, 0.8612372013999511] なお、毎世代で平均絶対誤差を表示させていましたが、十世代ほどで157に到達し、その後全く変わりませんでした。 つまり、そもそもこれ以上精度が出ることはないと考えられます。 まとめ 実装の際の手間と誤差の大きさを考慮すると、算術平均で予測する方法が最も適していると考えました。 また、どうしても最大平均絶対誤差は20近くになると分かりました。 フルバージョン 次回は 目標とした制度には達していませんが、今回のモデルを用いてAIオセロを実際に作りたいと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

QGIS+VSCodeでのデバック環境構築

はじめに QGISのためのVSCodeでのデバック環境を構築します。 環境 Windows 10 QGIS 3.16 OSGeo4W 最初の環境構築(QGISとVSCodeのインストール) QGISのインストール ネットワークインストーラーで可変な環境をインストールしましょう https://qgis.org/ja/site/forusers/download.html VSCodeのインストール https://azure.microsoft.com/ja-jp/products/visual-studio-code/ VSCodeでのデバック準備 ptvsdのインストール OSGeo4Wのshellを起動する C:/OSGeo4W64/OSGeo4W.batでcmdを起動する python3をメインで使用するようにコマンドを実行する py3_envでpython3をメインで使えるようになる pipをインストールする 存在する場合はインスールしなくてよい python -m ensurepip --default-pipでpipをインストールする VSCodeでのデバック用にptvsdをインストールする python -m pip install ptvsd > py3_env > python -m ensurepip --default-pip > python -m pip install ptvsd QGISにDebug用プラグインをインストールする プラグインの管理とインストールを起動 メニューバー→プラグイン→プラグインの管理とインストール debugvsを検索しインストール VSCode側のデバック設定を作成 VSCodeを起動する launch.jsonを作成する .vscode/launch.json launch.jsonにリモートデバック用の設定を記載する { "version": "0.2.0", "configurations": [ { "name": "Python: Remote Attach", "type": "python", "request": "attach", "connect": { "host": "localhost", "port": 5678 }, "pathMappings": [ { "localRoot": "${workspaceFolder}", "remoteRoot": "${env:HOMEPATH}/AppData/Roaming/QGIS/QGIS3/profiles/default/python/plugins/sample_plugin" } ], "justMyCode": false } ] } localRoot ローカルのコードが存在するパスを記述する remoteRoot シンボリックリンクを使用している場合はリンク先のパスを記述する 直接コードを編集している場合はlocalRootと同じで良い justMyCode 他のコード内までデバックを行ないたい場合、falseにする デバックを実行する QGISを起動する debugvsのEnable Debug for Visual Studioを実行する VSCodeでPython: Remote Attachでデバックを実行する VSCode上でブレイクポイントを設定し、QGISでプラグインを実行する メモ シンボリックリンクでpluginsにプラグインを入れていたため、デバックできずにハマった launch.jsonのRemoteRootにリンク先を書こう!!まじで pip3でインストールできないって困った py3_envでpython3用にパスが通るぞ!!らく 参考 環境構築 https://www.gispo.fi/en/blog/cooking-with-gispo-qgis-plugin-development-in-vs-code/ QGIS+PTVSD関連 Debugging QGIS 3.x python plugins on OSX using VS Code Debugging QGIS 3.x python plugins on Windows 10 using VS Code RemoteDebuggingQgisVsCode QGISのpipインストール参考 その他
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonのHeadless Chromeを用いたWebスクレイピング (30画以下の漢字の画数を調べる)

前述 私事であるが子供の頃から漢字が非常に苦手である.書き順も滅茶苦茶でなおかつかつ悪筆なため日々反省している. 以前,漢字の画数を使ったクイズに挑戦した際に多くの漢字の画数を調べる必要があったため,とあるWebサイトを利用させていただいた. そのWebサイトで検索可能な漢字の画数の上限は30画ではあるものの,余程の事がない限りそれ以上の画数の漢字に遭遇する事はないので,個人的には問題なかった. 開発 【システム環境】 OS X El Capitan 10.11以降(他のOSでも可) Python 3.6.5以降 本記事ではディレクトリ操作をMacに合せて記述しているため,他のOS(Linux, Windows等)の使用時は必要に応じてディレクトリ構造を置き換えて考えるものとする. 外部ライブラリのインストール $ pip install selenium Pythonスクリプトとパッケージ・モジュールの作成 $ mkdir ~/StrokesSearcher $ touch ~/StrokesSearcher/strokes_searcher.py $ mkdir ~/StrokesSearcher/common $ touch ~/StrokesSearcher/common/browser_controller.py モジュールbrowser_controller.pyのソースコード browser_controller.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- from re import compile from selenium.webdriver import ChromeOptions, Chrome from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from random import randint from time import sleep class Scraping: def __init__(self): self.kanji = self.textInput() self.textConditions() self.options = ChromeOptions() # Chrome起動時のオプションを設定 self.startOptions() self.driver = Chrome(options=self.options) # 文字列を入力するメソッド def textInput(self): inputmsg = input('画数を調べたい30画以下の漢字を入力してください: ') return inputmsg # 入力された文字列が漢字以外または1文字以外の場合はシステム終了 def textConditions(self): if not compile('[一-龥]').search(self.kanji) or len(self.kanji) != 1: print('入力できる文字列は漢字一文字のみです') exit() # 処理を早めるためにChrome起動時のオプションを設定するメソッド def startOptions(self): # UserAgentをヘッドレスモードに設定 self.options.add_argument('--headless') self.options.add_argument('--disable-gpu') # SSLエラーを許容 self.options.add_argument('--ignore-certificate-errors') # シークレットモード指定 self.options.add_argument('--incognito') # 画像を読み込まないよう設定 self.options.add_argument('--blink-settings=imagesEnabled=false') # Chromeを起動するメソッド def chromeStart(self): url = 'https://kanjitisiki.com/kakusuu-sakuin/' self.driver.get(url) # フォームに漢字を入力して検索 するメソッド(XPathで指定) def kanjiInput(self): self.driver.find_element(By.XPATH, '//*[@id="top-right"]/form/input[1]').send_keys(self.kanji) self.driver.find_element(By.XPATH, '//*[@id="top-right"]/form/input[2]').click() # 入力された文字列が31画以上の漢字または環境依存文字の場合はシステム終了 errxpath = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[2]').text errmsg = '該当する漢字・部首がありません。' if errxpath == errmsg: print('31画以上の漢字または環境依存文字のため該当する漢字・部首がありません') exit() # サーバ負荷減のためページ切り替え時に0〜1秒間ランダム待機 time = randint(0, 1) sleep(time) # 詳細ページをクリック self.driver.find_element(By.XPATH, '//*[@id="main"]/ul[1]/li/a').click() # 画数を取得するメソッド (XPathで指定) def getText(self): # 画数のXPathは2通りの場合に分かれる strokes1 = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[5]/a').text strokes2 = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[6]/a').text if strokes1 == '': print('                 『' + self.kanji + '』の画数: ' + strokes2.replace('画', '')) exit() else: print('                 『' + self.kanji + '』の画数: ' + strokes1.replace('画', '')) exit() # Chromeを終了するメソッド def chromeShutdown(self): self.driver.quit() 実行ファイルstrokes_searcher.pyのソースコード strokes_searcher #!/usr/bin/env python3 # -*- coding: utf-8 -*- from common.browser_controller import Scraping def main(): # インスタンスの生成 operation = Scraping() # メソッドの呼び出し operation.chromeStart() operation.kanjiInput() operation.getText() operation.chromeShutdown() if __name__ == '__main__': main() 実行 strokes_searcher.pyを実行 $ python ~/StrokesSearcher/strokes_searcher.py 画数を調べたい30画以下の漢字を入力してください: 矍 #漢字を一文字入力                                                         『矍』の画数: 20 例として『矍』いう漢字を入力してみたところ画数20が得られた.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでSeleniumのHeadless Chromeを用いたWebスクレイピング (30画以下の漢字の画数を調べる)

前述 私事であるが子供の頃から漢字が非常に苦手である.書き順も滅茶苦茶でなおかつかつ悪筆なため日々反省している. 以前,漢字の画数を使ったクイズに挑戦した際に多くの漢字の画数を調べる必要があったため,とあるWebサイトを利用させていただいた. そのWebサイトで検索可能な漢字の画数の上限は30画ではあるものの,余程の事がない限りそれ以上の画数の漢字に遭遇する事はないので,個人的には問題なかった. Webスクレイピングの手法はSeleniumの他にBeautiful Soupやurllibが存在するが,今回は最も単純なコードで記述できるSeleniumのHeadless Chrome (ヘッドレスモード) を採用した. Headless ChromeはWebブラウザの直接起動ではなくバックグラウンド起動である. バックグラウンド起動とは言うものの,処理のために時間がかかるのが欠点である. 開発 【システム環境】 OS X El Capitan 10.11以降(他のOSでも可) Python 3.6.5以降 本記事ではディレクトリ操作をMacに合せて記述しているため,他のOS(Linux, Windows等)の使用時は必要に応じてディレクトリ構造を置き換えて考えるものとする. 外部ライブラリのインストール $ pip install selenium Pythonスクリプトとパッケージ・モジュールの作成 $ mkdir ~/StrokesSearcher $ touch ~/StrokesSearcher/strokes_searcher.py $ mkdir ~/StrokesSearcher/common $ touch ~/StrokesSearcher/common/browser_controller.py モジュールbrowser_controller.pyのソースコード browser_controller.py #!/usr/bin/env python3 # -*- coding: utf-8 -*- from re import compile from selenium.webdriver import ChromeOptions, Chrome from selenium.webdriver.common.by import By from selenium.webdriver.common.keys import Keys from random import randint from time import sleep class Scraping: def __init__(self): self.kanji = self.textInput() self.textConditions() self.options = ChromeOptions() # Chrome起動時のオプションを設定 self.startOptions() self.driver = Chrome(options=self.options) # 文字列を入力するメソッド def textInput(self): inputmsg = input('画数を調べたい30画以下の漢字を入力してください: ') return inputmsg # 入力された文字列が漢字以外または1文字以外の場合はシステム終了 def textConditions(self): if not compile('[一-龥]').search(self.kanji) or len(self.kanji) != 1: print('入力できる文字列は漢字一文字のみです') exit() # 処理を早めるためにChrome起動時のオプションを設定するメソッド def startOptions(self): # UserAgentをヘッドレスモードに設定 self.options.add_argument('--headless') self.options.add_argument('--disable-gpu') # SSLエラーを許容 self.options.add_argument('--ignore-certificate-errors') # シークレットモード指定 self.options.add_argument('--incognito') # 画像を読み込まないよう設定 self.options.add_argument('--blink-settings=imagesEnabled=false') # Chromeを起動するメソッド def chromeStart(self): url = 'https://kanjitisiki.com/kakusuu-sakuin/' self.driver.get(url) # フォームに漢字を入力して検索 するメソッド(XPathで指定) def kanjiInput(self): self.driver.find_element(By.XPATH, '//*[@id="top-right"]/form/input[1]').send_keys(self.kanji) self.driver.find_element(By.XPATH, '//*[@id="top-right"]/form/input[2]').click() # 入力された文字列が31画以上の漢字または環境依存文字の場合はシステム終了 errxpath = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[2]').text errmsg = '該当する漢字・部首がありません。' if errxpath == errmsg: print('31画以上の漢字または環境依存文字のため該当する漢字・部首がありません') exit() # サーバ負荷減のためページ切り替え時に0〜1秒間ランダム待機 time = randint(0, 1) sleep(time) # 詳細ページをクリック self.driver.find_element(By.XPATH, '//*[@id="main"]/ul[1]/li/a').click() # 画数を取得するメソッド (XPathで指定) def getText(self): # 画数のXPathは2通りの場合に分かれる strokes1 = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[5]/a').text strokes2 = self.driver.find_element(By.XPATH, '//*[@id="main"]/p[6]/a').text if strokes1 == '': print('                 『' + self.kanji + '』の画数: ' + strokes2.replace('画', '')) exit() else: print('                 『' + self.kanji + '』の画数: ' + strokes1.replace('画', '')) exit() # Chromeを終了するメソッド def chromeShutdown(self): self.driver.quit() 実行ファイルstrokes_searcher.pyのソースコード strokes_searcher #!/usr/bin/env python3 # -*- coding: utf-8 -*- from common.browser_controller import Scraping def main(): # インスタンスの生成 operation = Scraping() # メソッドの呼び出し operation.chromeStart() operation.kanjiInput() operation.getText() operation.chromeShutdown() if __name__ == '__main__': main() 実行 strokes_searcher.pyを実行 $ python ~/StrokesSearcher/strokes_searcher.py 画数を調べたい30画以下の漢字を入力してください: 矍 #漢字を一文字入力                                                         『矍』の画数: 20 例として『矍』いう漢字を入力してみたところ画数20が得られた.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】株価データ分析 株価チャートを描く~mplfinance トレンドライン編~

修正(2021.11.27) データ(株価データ)によっては, 高値と安値の線を引くためのデータ(hlinesに渡すデータ)がfloatでないというエラーが発生しましたので, # エラー箇所 hlines=[df['High'].max(), df['Low'].min()] 次のように修正しています # float変換 max = float(df['High'].max()) min = float(df['Low'].min()) hlines=[max, min] はじめに 【Python】株価データ分析 株価チャートを描く~mplfinance編 その1~で描いた線が思ったより良い感じだったので,トレンドラインっぽい線を描きたいと思った 今回は線形回帰トレンドラインを描いてみる 株価チャートに,線形回帰で導いた一次関数 $y = ax + b$ $y$ … 株価 $x$ … 日付 $a$ … 係数 $b$ … 切片 を重ね,トレンドを把握しようというものだ 準備 線形回帰は,scikit-learn を使うのでインストール conda であれば conda install scikit-learn データ用意 自分銘柄一覧(my.xlsx)と東証上場銘柄一覧(date_j.xls)を読み込む この記事などを参考にしてほしいが,自分銘柄一覧(my.xlsx)とは,自分の保有銘柄の一覧で(仮に,トヨタ,日産,ホンダ,ANA,JALを保有しているとしている), 東証上場銘柄一覧(data_j.xls)とは,日本取引所グループにある東証上場銘柄の一覧ファイルのことだ まずは,自分銘柄と東証上場銘柄一覧を読み込んで,自分銘柄のDataFrameを作る import numpy as np import pandas as pd import pandas_datareader.data as pdr import datetime from dateutil.relativedelta import relativedelta import mplfinance as mpf my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str) topix_df = pd.read_excel('data_j.xls', dtype = str) code_set = set(my_df['コード'].tolist()) df = topix_df[topix_df['コード'].isin(code_set)] df 日付 コード 銘柄名 市場・商品区分 33業種コード 33業種区分 17業種コード 17業種区分 規模コード 規模区分 2886 20211029 7201 日産自動車 市場第一部(内国株) 3700 輸送用機器 6 自動車・輸送機 2 TOPIX Large70 2888 20211029 7203 トヨタ自動車 市場第一部(内国株) 3700 輸送用機器 6 自動車・輸送機 1 TOPIX Core30 2926 20211029 7267 本田技研工業 市場第一部(内国株) 3700 輸送用機器 6 自動車・輸送機 1 TOPIX Core30 3793 20211029 9201 日本航空 市場第一部(内国株) 5150 空運業 12 運輸・物流 4 TOPIX Mid400 3794 20211029 9202 ANAホールディングス 市場第一部(内国株) 5150 空運業 12 運輸・物流 2 TOPIX Large70 トヨタのトレンドを調べてみる まずは普通にローソク足 code = df['コード'].tolist()[1] company = df['銘柄名'].tolist()[1] ed = datetime.datetime.now() # 本日 st = ed - relativedelta(months = 12) # 12か月前 df = pdr.DataReader(code + '.T', 'yahoo', st, ed) kwargs = dict(type = 'candle', style = 'starsandstripes', title = code) mpf.plot(data = df, **kwargs) この辺までは,前回やってきたことだ 線形回帰(fitでモデルを作成) インポートと線形回帰を使う準備 from sklearn.linear_model import LinearRegression linearModel = LinearRegression() 正解のやり方 # fitでモデルを作成 linearModel.fit(X = np.arange(len(df.index)).reshape(-1,1), y = df['Close'].values) # 係数 coef = linearModel.coef_[0] # 切片 intercept = linearModel.intercept_ # 係数と切片 (coef, intercept) 結果は 係数(coef) = 2.615044958208052, 切片(intercept) = 1476.6834173686243 係数がプラスなので,右肩上がりのトレンドだ $y = 2.615044958208052x + 1476.6834173686243$ 正解にたどり着くまでに,何度か失敗したので,その失敗例を残しておく 失敗例1 # fitでモデルを作成 linearModel.fit(X = df.index.values.reshape(-1,1), y = df['Close'].values) # 係数 coef = linearModel.coef_[0] # 切片 intercept = linearModel.intercept_ # 係数と切片 (coef, intercept) 結果は,おかしい値に 係数(coef) = 2.0299990617873803e-14, 切片(intercept) = -31132.82774519947 fit の X に日付データを渡したのがまずかったようだ 失敗例2 # fit linearModel.fit(np.arange(len(df.index)), df['Close'].values) 結果は,エラー ValueError: Expected 2D array, got 1D array instead: array=[ 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 …以降省略] 本来は, array=[[0], [1], …以降省略] となってなければならない reshape(-1, 1) で2次元配列にする必要がある 列数は1を指定し,行数は-1でよろしくやってもらう 線形回帰(predictで予測値を出す) fit で与えた X を predict に与えればよい # predict で予測値を出す predict = linearModel.predict(X = np.arange(len(df.index)).reshape(-1,1)) predict 結果は array([1476.68341737, 1479.29846233, 1481.91350729, 1484.52855224, …以降省略]) トレンドラインを引く 前回の線の引き方と同じやり方でやってみる # 全部の点を結ぶ線を引く場合は #multi_points = [(pd.to_datetime(idx), price) for idx, price in zip(df.index.values, predict)] #multi_points # 2点を結ぶ線を引く場合は multi_points = [(pd.to_datetime(df.index.values[0]), predict[0]), (pd.to_datetime(df.index.values[-1]), predict[-1])] multi_points multi_pointsには,(株価データの最初の日,最初の日の予測値),(株価データの最後の日,最後の日の予測値)を渡している multi_pointsの中身は [(Timestamp('2020-11-26 00:00:00'), 1476.7034957070596), (Timestamp('2021-11-26 00:00:00'), 2117.32902858791)] チャート描画 max = float(df['High'].max()) min = float(df['Low'].min()) mpf.plot(df, **kwargs, hlines = dict(hlines=[max, min], linewidths=(.5, .5), colors=('r','r')), alines = dict(alines=two_points, linewidths=(40), colors=('b'), alpha=0.2)) うん,教科書のような?右肩上がり まとめると mplfinance2.py import numpy as np import pandas as pd import pandas_datareader.data as pdr import datetime from dateutil.relativedelta import relativedelta import mplfinance as mpf from sklearn.linear_model import LinearRegression my_df = pd.read_excel('my.xlsx', sheet_name = 'Sheet1', dtype = str) topix_df = pd.read_excel('data_j.xls', dtype = str) code_set = set(my_df['コード'].tolist()) df = topix_df[topix_df['コード'].isin(code_set)] code = df['コード'].tolist()[1] company = df['銘柄名'].tolist()[1] ed = datetime.datetime.now() # 本日 st = ed - relativedelta(months = 12) # 12か月前 df = pdr.DataReader(code + '.T', 'yahoo', st, ed) # fitでモデルを作成 linearModel = LinearRegression() linearModel.fit(X = np.arange(len(df.index)).reshape(-1,1), y = df['Close'].values) # 係数 coef = linearModel.coef_[0] # 切片 intercept = linearModel.intercept_ # 係数と切片 #(coef, intercept) # 係数 coef = linearModel.coef_[0] # 切片 intercept = linearModel.intercept_ # predict で予測値を出す predict = linearModel.predict(X = np.arange(len(df.index)).reshape(-1,1)) # 全部の点を結ぶ線を引く場合は #multi_points = [(pd.to_datetime(idx), price) for idx, price in zip(df.index.values, predict)] # 2点を結ぶ線を引く場合は multi_points = [(pd.to_datetime(df.index.values[0]), predict[0]), (pd.to_datetime(df.index.values[-1]), predict[-1])] # チャート描画 kwargs = dict(type = 'candle', style = 'starsandstripes', title = code) max = float(df['High'].max()) min = float(df['Low'].min()) mpf.plot(df, **kwargs, hlines = dict(hlines=[max, min], linewidths=(.5, .5), colors=('r','r')), alines = dict(alines=multi_points, linewidths=(40), colors=('b'), alpha=0.2)) おわりに 今回のトヨタの例は,きれいな右肩上がりだったのでトレンドラインを描くまでもなかったが 複数の銘柄のトレンドラインを並べて表示するような場合は トレンド判断が一瞬でできそうなので良いなと思った 次回こそ,前回宣言した通り, MACDやボリンジャーバンドを描いたり, 複数銘柄を並べて描くようなことをやってみたい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python3】0から作るPython初心者プログラミング【03】-大人数でじゃんけんプログラム-

【ご挨拶】こんにちは! ぬかさんエンジニアリングです。(3回目‼) 前回の投稿にまたまたありがたいことにコメント頂きまして、クラスの使い方について教えて頂きました。 今回は、それらの内容も含めた上で複数人同時にじゃんけんが出来るようにアップデートした内容になっております。 クラスを使って実際に遊べる程度のプログラムを作ってみたいと思っていた初心者の方がいらっしゃいましたら是非挑戦してみてください!また、複数のインスタンスをまとめて扱う方法や特殊メソッド__str__など少し応用的な話もありますので、既にクラスを理解されている方も興味があれば見ていってくださいね! LGTMも是非よろしくお願いします‼ 本シリーズ初めての方へ 【趣旨】 Python初心者プログラマーが入門書で学んだ知識を実践的なコーディングを通じて身に着けていくためのお題を提供します。 お題を基に各自コーディングに挑戦していただいた後、この記事でコーディングの過程を答え合わせします。 【対象】 Pythonの入門書を読んで理解はしたけど何か目的をもって実践的にコーディングをしたい方。 ProgateでPythonコースをLv5まで勉強したけど応用力が身に着いていないと感じている方。 【初心者とは】 この記事シリーズでの初心者は下記の項目を理解済みであることが目安となっています。 [演算子, 標準ライブラリ, 条件分岐, 繰り返し処理, 例外処理, リスト, タプル, セット, 辞書, オブジェクト指向] 【利用ライブラリ & Python3 --version】 ・Google Colaboratory 以下のリンクからアクセスして使い方を確認した後、左上のファイルタブから「ノートブックを新規作成」を選択して自分のプログラムを作りましょう。ファイルはGoogleDriveに保存されるため、自分のGoogleアカウントと連携させるのを忘れないようにしましょう。 Colaboratory へようこそ ←ここからリンクへ飛ぶ ・Python 3.7.12 $$$$ それではさっそく本題に入っていきましょう 第【03】回 -大人数でじゃんけんプログラム- このシリーズ第三回目の今回は、前回の「じゃんけんプログラムのクラス化」を応用して「大人数じゃんけんプログラム」を実装します! 前回クラス化した内容を更に進化させ、じゃんけんに3回勝利した人が優勝する仕様にアップデートしていきます。入力を受け取ってゲームをスタートし、一連の流れを止めずにどの順番でどう処理を実行してゴールまで行きつくのか、その中身を皆さんと一緒に作っていきたいと思います。 【お題】じゃんけんプログラムを複数人同時プレイできる様にしよう! janken() #出力 参加人数を整数で入力してください >4 参加人数は4人です。 あなたの名前を入力してください >nukasan nukasanさんですね。エントリー完了しました。 nukasanの現在の獲得勝利ポイント:0 コンピュータ1の現在の獲得勝利ポイント:0 コンピュータ2の現在の獲得勝利ポイント:0 コンピュータ3の現在の獲得勝利ポイント:0 じゃんけんぽん! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:チョキ コンピュータ2さんの手:パー コンピュータ3さんの手:パー あいこでしょ! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:パー コンピュータ2さんの手:パー コンピュータ3さんの手:グー コンピュータ1とコンピュータ2の勝ち!1ポイントゲット nukasanの現在の獲得勝利ポイント:0 コンピュータ1の現在の獲得勝利ポイント:1 コンピュータ2の現在の獲得勝利ポイント:1 コンピュータ3の現在の獲得勝利ポイント:0 じゃんけんぽん! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:パー コンピュータ2さんの手:グー コンピュータ3さんの手:パー コンピュータ1とコンピュータ3の勝ち!1ポイントゲット nukasanの現在の獲得勝利ポイント:0 コンピュータ1の現在の獲得勝利ポイント:2 コンピュータ2の現在の獲得勝利ポイント:1 コンピュータ3の現在の獲得勝利ポイント:1 じゃんけんぽん! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:パー コンピュータ2さんの手:チョキ コンピュータ3さんの手:パー . . . . . nukasanの現在の獲得勝利ポイント:1 コンピュータ1の現在の獲得勝利ポイント:2 コンピュータ2の現在の獲得勝利ポイント:2 コンピュータ3の現在の獲得勝利ポイント:1 じゃんけんぽん! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:チョキ コンピュータ2さんの手:チョキ コンピュータ3さんの手:パー あいこでしょ! ------------------------------------------------------- じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'} ------------------------------------------------------- あなたの出す手を入力してください(整数:1, 2, 3) > 1 nukasanさんの手:グー コンピュータ1さんの手:グー コンピュータ2さんの手:グー コンピュータ3さんの手:チョキ nukasanとコンピュータ1とコンピュータ2の勝ち!1ポイントゲット *+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+ >>>>>よってコンピュータ1とコンピュータ2の優勝です!!<<<<< *+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+ 1.3人以上の参加人数を入力してゲームに反映できるようにしてください。 2.プレイヤー名を入力してゲームに反映できるようにしてください。 3.じゃんけんの前に各参加者の現在の得点を出力してください。 4.各参加者のじゃんけんの手を出力してください。 5.じゃんけんの勝者の名前を出力してください。 6.じゃんけん勝利ポイントを3点先取した参加者の名前を出力してください。 〔お助けヒント〕 ヒント1 参加者のインスタンスは一つ一つ変数に代入せずに配列にまとめて格納し、取り出す場合はfor文を使うようにすると便利です。 ヒント2 新たにクラスを作成し、初期化メソッドでインスタンスの格納された配列を変数に代入し、じゃんけんの一連の流れを実行できるインスタンスメソッドを作成してまとめると複数人同時じゃんけんのプログラムが作りやすくなります。 ヒント3 3人以上でじゃんけんする場合、場に出ている手の種類が1種と3種である場合は必ず「あいこ」になり、2種の場合は必ずどちらかが勝ちます。 ヒント4 インスタンスメソッド内で他のインスタンスメソッドを実行することで、一つのインスタンスメソッドを実行するだけで様々な処理を連鎖的にさせることができます。 【解答】 解答は以下の通りです。 ※この解答はあくまで私の回答です。各々の方法でお題が解けていればそれで全然かまいません。むしろ、もっと簡潔な方法があればご教示頂けると助かります import random #コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Computer: def __init__(self, name, point=0): self.name = name self.point = point def pon(self): self.hand = random.choice(tuple(HANDS.values())) #ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Human: def __init__(self, name, point=0): self.name = name self.point = point def pon(self): while True: try: print("-"*55 + f"\nじゃんけんの選択肢:{HANDS}\n" + "-"*55) human_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > ")) if human_hand in (1, 2, 3): self.hand = HANDS[human_hand] return except: print("整数1, 2, 3を入力をして下さい") #じゃんけんクラス(じゃんけんをする際の手の選択と出力、判定、ポイントの加算と出力) class Janken: def __init__(self, players): self.players = players ##じゃんけんの一連の流れを実行 def play(self): for player in self.players: print(f"\n{player.name}の現在の獲得勝利ポイント:{player.point}") print("\n\nじゃんけんぽん!") self.select_hand() while True: self.winners = self.judge() if not self.winners: print("\n\nあいこでしょ!") self.select_hand() else: break print(f"\n\n{'と'.join([winner.name for winner in self.winners])}の勝ち!1ポイントゲット\n") for winner in self.winners: winner.point += 1 return [winner.name for winner in self.winners if winner.point >= 3] ##ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手を出力 def select_hand(self): for player in self.players: player.pon() print(f"{player.name}さんの手:{player.hand}") ##参加者のじゃんけんの手があいこか否かを判定、あいこでなければ勝者を決定 def judge(self): hands = set(player.hand for player in self.players) if len(hands) != 2: return None hand1, hand2 = hands win = hand1 if hand1.stronger_than(hand2) else hand2 return [player for player in self.players if player.hand == win] #ハンドクラス(判定で2つの手の勝敗を決定) class Hand: def __init__(self, shape, stronger_than): self.shape = shape self._stronger_than = stronger_than ##デバッグ時にインスタンスを指定の形で出力する用 def __repr__(self): return repr(self.shape) ##インスタンスをstr型で出力する際にself.shapeを出力 def __str__(self): return self.shape ##2つの手のうち、一方の手が勝つ時の相手の手と実際の相手の手が同じだという返り値を返す def stronger_than(self, enemy): return self._stronger_than == enemy.shape #ハンドクラスのインスタンスを要素とする辞書 HANDS = {1: Hand("グー", "チョキ"), 2: Hand("チョキ", "パー"), 3: Hand("パー", "グー")} #じゃんけんの実施&出力 def janken(): ##参加人数を選択 while True: try : all_player_num = int(input("参加人数を整数で入力してください >")) if all_player_num > 0: break except: print("正の整数を入力してください。") print(f"参加人数は{all_player_num}人です。") ##名前の入力 while True: my_name = (input("あなたの名前を入力してください >")) if my_name: print(f"{my_name}さんですね。エントリー完了しました。") break ##参加者のクラスをplayer配列に格納 players = Human(my_name), *[Computer(f"コンピュータ{n}") for n in range(1, all_player_num)] ##じゃんけんクラスのplayインスタンスメソッドを実行し、優勝者が出るまでじゃんけんを繰り返す while True: result = Janken(players).play() if len(result) > 0: print("\n\n" + "*+"*30 + f"\n>>>>>よって{'と'.join(result)}の優勝です!!<<<<<\n" + "*+"*30 + "\n\n\n") break #じゃんけんメソッドを実行 janken() 【解説】 今回は、じゃんけんプログラムの応用編と言うことで人間一人とコンピュータ複数人でじゃんけんが出来るプログラムにアップデートしてみたいと思います。 主な変更点は3つあります。 一つ目。各プレイヤーの名前と勝利ポイントをインスタンス変数に保持できるようにクラスを変更します。 二つ目。複数人で同時にじゃんけんをして誰が勝利したか判定できるように変更します。 三つ目。各プレイヤーの勝利ポイントを記録し、3点先取した人が優勝する仕組みに変更します。 この変更点を満たす形でクラスやインスタンスメソッドを作っていきたいと思います。 どんなクラスを作るのか、どんなインスタンスメソッドを作るのかは自由度が高すぎてむやみに作ってもこんがらがります。まずは、どんな機能が必要でどんなインスタンスメソッドをクラスにまとめると分かりやすくて効率の良いコードになるのか自分で仮説を立てて作ってみます。たいていはエラーを吐くのでそこから試行錯誤と情報収集を繰り返しながら自分なりの解法を見つけていきます。その過程で得る知見は実践経験と結びついてしっかりと身に着いていきます。今回の解説も情報収集として使っていただくために書いておりますが、なるべく自力で作ってみるのをおすすめします。 〔作り方〕 ①プレイ人数とプレイヤー名を入力できる機構を作成 ⑴プレイ人数の入力機構を作成 ##参加人数を選択 while True: try : all_player_num = int(input("参加人数を整数で入力してください >")) if all_player_num > 0: break except: print("正の整数を入力してください。") print(f"参加人数は{all_player_num}人です。") ⑵プレイヤーの名前の入力機構を作成 ##名前の入力 while True: my_name = (input("あなたの名前を入力してください >")) if my_name: print(f"{my_name}さんですね。エントリー完了しました。") break ②ヒューマンクラスとコンピュータクラスに名前と勝利ポイントを受け取る初期化メソッドを追加 ⑴インスタンス作成時に名前を代入するself.nameを追加 #コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Computer: def __init__(self, name): self.name = name #<<<<<<<<< #ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Human: def __init__(self, name): self.name = name #<<<<<<<<< ⑵勝利ポイントを代入するself.pointを追加 #コンピュータクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Computer: def __init__(self, name, point=0): self.name = name self.point = point #<<<<<<<<< #ヒューマンクラス(名前と勝利ポイントを保持、じゃんけんの手を選択) class Human: def __init__(self, name, point=0): self.name = name self.point = point #<<<<<<<<< インスタンス生成時にはポイントは0なので、引数に初期値として0を設定しておきます。 ③参加者が何人でも処理しやすいようにインスタンスを配列(タプル)に格納 ⑴ヒューマンクラスはプレイヤーの名前を、コンピュータは番号を受け取りplayersに格納 ##参加者のクラスをplayer配列に格納 players = Human(my_name), *[Computer(f"コンピュータ{n}") for n in range(1, all_player_num)] print(players) #出力(参加人数4人の場合) ''' (<__main__.Human object at 0x7f842fe0a3d0>, <__main__.Computer object at 0x7f842fe98e90>, <__main__.Computer object at 0x7f842fe98f90>, <__main__.Computer object at 0x7f84330fa410>) ''' プレイヤー名入力機構で入力した名前がmy_nameに代入されているので、ヒューマンクラス作成時に受け取ります。 コンピュータの数は参加人数からプレイヤー数1人を除いた分になるので、参加人数が代入されているall_player_numを使ってリスト内包表記で人数分のインスタンスを作成します。配列に代入する際には、「*」を使いリストから順番に要素を取り出す方法を使います。 実際にplayersの中身を見るとクラス型のオブジェクト(インスタンス)が格納されているのが分かります。 ここで疑問に思う方がいるかもしれません。クラスを作成するときは インスタンス名 = クラス名() と書きますよね??と。 私もそう思っていました。 だた、実はインスタンスを作成する際に変数に代入する必要はありません。 通例で変数に代入しているのは、インスタンスを作成した際にそのインスタンスを分かりやすく識別するという理由があるからです。変数に代入しなければむき出しのクラス型オブジェクトが出力されてしまい、同じクラスから作ったインスタンスを瞬時に識別するのは難しいのです。上記のコードを見ると変数に代入する理由がよくわかると思います。 今回の様にインスタンスをまとめて扱いたい場合は、変数への代入が要らないことを理解していればわざわざ インスタンス作成→変数に代入→リストを作成→変数をリストに追加 せずとも インスタンス作成→配列へ代入 とコードを省略できます。是非覚えておいてください。 ④参加者のインスタンス配列を受け取りじゃんけんを実行するJankenクラスを作成 ⑴初期化メソッドを作成 #じゃんけんクラス(じゃんけんをする際の手の選択と出力、判定、ポイントの加算と出力) class Janken: def __init__(self, players): self.players = players ⑵じゃんけんの一連の流れを実行するplayインスタンスメソッドを作成 ##じゃんけんの一連の流れを実行 def play(self): ###現在の参加者全員のそれぞれの勝利ポイントを出力 for player in self.players: print(f"\n{player.name}の現在の獲得勝利ポイント:{player.point}") ###じゃんけんスタート print("\n\nじゃんけんぽん!") ###ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手の記録と出力 self.select_hand() ###各参加者の手からjudgeインスタンスメソッドであいこか否か判別。あいこを抜け出すまでじゃんけんを繰り返す while True: self.winners = self.judge() if not self.winners: print("\n\nあいこでしょ!") self.select_hand() else: break ###じゃんけんの勝者の名前を出力 print(f"\n\n{'と'.join([winner.name for winner in self.winners])}の勝ち!1ポイントゲット\n") ###じゃんけんの勝者に勝利ポイントを加算し、もし3ポイント獲得した参加者がいれば名前を返り値として返す for winner in self.winners: winner.point += 1 return [winner.name for winner in self.winners if winner.point >= 3] ⑶各参加者のじゃんけんの手を記録し出力するselect_handインスタンスメソッドを作成 ##ヒューマンクラスとコンピュータクラスのponインスタンスメソッドを実行して各参加者のじゃんけんの手を出力 def select_hand(self): for player in self.players: player.pon() print(f"{player.name}さんの手:{player.hand}") ponインスタンスメソッドを実行するとself.handに3種のHandインスタンスのどれかが代入される様にヒューマンクラスとコンピュータクラスを一部変更します。 (print(self.hand)を実行すると、特殊メソッド__str__の効果で"グーチョキパー"の文字列が出力されますが、type(self.hand)では<class '__main__.Hand'>が出力されるためHandクラスのインスタンスが代入されていると分かります。) ⑷参加者のじゃんけんの手からあいこと勝敗を判定 ##参加者のじゃんけんの手があいこか否かを判定、あいこでなければ勝者を決定 def judge(self): ###参加者のじゃんけんの手をセットにまとめると場に出ている手の種類が分かる hands = set(player.hand for player in self.players) ###手が1種、3種の場合は必ずあいこになるので返り値でNoneを返す if len(hands) != 2: return None ###手が2種の場合は、一方の手でstronger_thanインスタンスメソッドを実行し、強い手を判別する hand1, hand2 = hands win = hand1 if hand1.stronger_than(hand2) else hand2 ###強い手を出していた参加者を勝者として返り値で返す return [player for player in self.players if player.hand == win] ⑤の⑵で詳しく説明 ⑤複数人の勝敗判定に対応するためじゃんけんの手の辞書を変更しHandクラスを作成 ⑴じゃんけんの手の辞書をJudgeインスタンスメソッドの判定に使いやすいように変更 #ハンドクラスのインスタンスを要素とする辞書 HANDS = {1: Hand("グー", "チョキ"), 2: Hand("チョキ", "パー"), 3: Hand("パー", "グー")} ⑤の⑵で詳しく説明 ⑵Handクラスを作成する #ハンドクラス(判定で2つの手の勝敗を決定) class Hand: def __init__(self, shape, stronger_than): self.shape = shape self._stronger_than = stronger_than ##デバッグ時にインスタンスを指定の形で出力する用 def __repr__(self): return repr(self.shape) ##インスタンスをstr型で出力する際にself.shapeを出力 def __str__(self): return self.shape ##2つの手のうち、一方の手が勝つ時の相手の手と実際の相手の手が同じだという返り値を返す def stronger_than(self, enemy): return self._stronger_than == enemy.shape 初期化メソッドでは、self.shapeに選んだ手、self._stronger_thanに選んだ手が勝てる相手の手(グーに対するチョキ)を代入します。 Handのインスタンスは__str__という特殊メソッドによって、str型(文字列型)で表示するときにself.shape、つまり選んだ手に対応する文字列"グーチョキパー"のどれかを出力します。 この特殊メソッド__str__についてはTechAcademyマガジンのPythonのstrとreprの違いを現役エンジニアが解説【初心者向け】をご覧ください。 stronger_thanインスタンスメソッドは、judgeインスタンスメソッドで2種の手から勝ち手を判別する際に用いられます。 まず2種の手の集合をhand1とhand2に分けます。 次にhand1.stronger_than(hand2)で実行し、hand1が選んだ手(グー)が勝てる相手の手(チョキ)であるself._stronger_thanと相手の手であるenemy.shapeが同じだという返り値を返します。 あとはjudgeインスタンスメソッドで返り値がTrueの場合はhand1をwinに代入、返り値がFalseの場合はhand2をwinに代入します。 winに代入した手の種が勝ち手なので、各プレイヤーの手と照らし合わせ、同じ手を選んでいた場合はそのプレイヤーを勝者として返り値を返します。 ちなみに、それぞれの手の種はprintで出力してもただの文字列に見えますがtypeを見るとHandクラスのインスタンスだと分かります。これは特殊メソッド__str__の効果で文字列が出力されているだけで本質は<class '__main__.Hand'>だからと言うことです。だから、引数enemyにhand2を渡してenemy.shapeを書いてもエラーにならずに処理することができます。 ⑥プレイヤーの誰かが3ポイントを取るまでじゃんけんを繰り返す機構を作成 ⑴Jankenクラスのplayインスタンスメソッドの返り値が0以上なら優勝者を出力 ##じゃんけんクラスのplayインスタンスメソッドを実行し、優勝者が出るまでじゃんけんを繰り返す while True: result = Janken(players).play() if len(result) > 0: print("\n\n" + "*+"*30 + f"\n>>>>>よって{'と'.join(result)}の優勝です!!<<<<<\n" + "*+"*30 + "\n\n\n") break Janken(players).play()によってJankenクラスの「属性」として参加者のインスタンスの配列が代入され、同時にplayインスタンスメソッドが実行されることで誰かが勝つまでじゃんけんをしてくれます。たった一文ですがほぼすべての処理が実行されます。 playインスタンスメソッドは返り値として3ポイント以上勝利ポイントを獲得した人の名前が帰ってきます。ですので、返り値をresultに代入してlen()で要素数が0より多くなった瞬間じゃんけんを終わりにして名前を出力することで優勝者を出力出来ます。 【終わりに】 今回は、前回のじゃんけんプログラムを応用して複数人同時にじゃんけんできるプログラムを作ってきました。いかがだったでしょうか。 何も見ずに作るのは結構難しかったのではないでしょうか。特にインスタンスを変数に代入せずに使う感覚を掴むまでには時間を要したのではないでしょうか。次回はもっと難易度は上がりますが一緒に頑張っていきましょう!反対に今回の課題をすらすらと作ることが出来た方はもう中級者に手がかかり始めていると言ってもいいかもしれません。どちらにせよ今回の課題で何か成長を感じてくださったのであれば嬉しいです。 もしもコーディングの部分で間違いなどありましたらコメントでご指摘いただけると幸いです。 また、その他質問などございましたらコメントをお願い致します。 次回は、今回作った大人数じゃんけんプログラムを応用して「あっち向いてほいプログラム」を作りたいと思います。下記リンクからどうぞ! →鋭意制作中 この記事が良かったと感じたらLGTMを宜しくお願いします!それではまた次回!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【図解解説】JOI2021-2022 一次予選 第3回 問題3 運動会

図解解説シリーズ 競技プログラミングを始めたばかりでAtCoderの解説やJOIの解説ではいまいちピンと来ない…という人向けに、図解を用いて解説を行います。 問題文 情報オリンピック日本委員会に掲載されている問題 AtCoderに掲載されている問題 入出力など実際に確認して自分の作成したプログラムを採点することができます。 図解解説 今年度の一次予選のC問題は、以下の4つのスキルを確認する問題になっています。 1.入力・出力を正しく利用できる 2.算術演算子を正しく利用できる 3.条件分岐(if)を正しく利用できる 4.繰り返し処理を正しく利用できる 問題文を整理するために、具体的な数字を用いて、図示して考えてみます。 Pythonでの文字列は、1文字ごとに先頭から0,1,2,3,…と番号が割り当てられています。したがって、先頭の文字は「変数名[0]」と指定して取り出すことができます。この仕組みを利用して、文字列の先頭から1文字ずつ順番にRと一致するか確認し、Rの個数を数え上げていきます。 最終的にRの個数が、赤組の人数と一致するかどうか確認し、一致する場合は赤組の枠が全部埋まっているので葵さんは白組、一致しない場合は赤組の枠が余っているので葵さんは赤組になります。 解答例 解説で説明した解答例 c1.py N = int(input()) K = int(input()) S = str(input()) #赤組を示すRの個数を数えるための変数(aka)を用意して、文字列(S)の先頭からRの個数を数えます #文字列Sの長さはN-1(葵さんの組み分けデータがないため)である点に注意が必要です aka = 0 for i in range(N-1): if S[i]=='R': aka += 1 #赤組の個数(aka)と赤組の人数(K)が同じだったら葵さんは白組、違ったら赤組を表示します if aka==K: print('W') else: print('R') 採点サイトに提出したプログラム Pythonの機能を使った例 効果的に機能を活用すると、アルゴリズムを簡単にすることができます。 c2.py N = int(input()) K = int(input()) S = str(input()) #文字列Sの中に含まれる赤組Rの数を数え、 #赤組の人数(K)と同じだったら葵さんは白組、違ったら赤組を表示します if S.count('R')==K: print('W') else: print('R') 採点サイトに提出したプログラム イラスト スライド内で使用しているイラストはすべて「いらすとや」の素材を利用しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Arduino UNOの6ch ADCをプロットしグラフ表示する(2)

測定周期変更可能なADCプロット装置を作る 前回のArduinoのADCを6chグラフ表示したものに、測定周期を変更できる仕様を入れてみました。 複数のリチウムイオン電池の電圧簡易測定などに使えるかもしれません。 今回、周期測定するにあたり、できるだけ正確な周期を作り出すためArduinoとの通信やグラフ描写の オーバーヘッドを考慮する必要があります。そのため区間測定できる下記ストップウォッチを使います。 完成イメージ というコンセプトで完成したのは下記のようなGUIとなります。 Collection intervalに測定周期を設定できるようにしています。 設定範囲は0ms - 10000msまでとしています。 周期測定に関しての結果 今回、周期的にADC値をArduinoから取得する仕組みを入れています。マイコンの世界では正確に時間を刻む事が要求されますが、WindowsなどのPCプログラムではそこまでの精度を出すことができません。 測定間隔をなるべく正確にするために、Arduino通信や描画処理などの時間を周期測定に考慮する処理を入れてみました。ただ、それでもPythonやWindows処理に関しての正確な時間処理までは考慮できていません。 そのため、1周期あたり20m程度の遅れが発生します。また、Arduinoとの通信やグラフ描画などの処理を考慮するとミニマム周期は約50ms程度となります。つまりサンプリングレート20Hz程度のUSB接続で 0-5Vまで10bit分解能で6ch同時測定可能な機材として使えることになります。 ソースコード Ardipy_ADGraph.py import sys import binascii import re import time import tkinter as tk import tkinter.ttk as ttk sys.path.append('../') from Ardipy_Driver import Ardipy sys.path.append('../Tool') from Ardipy_Frame import Ardipy_Frame from SelectableGraph import * from StopWatch import * import datetime Ardipy_ADGraph = "1.4" class AdgraphException(Exception): pass class Control_Frame(Ardipy_Frame): def __init__(self, master): self.log = None self.ardipy = Ardipy(self.log) self.collection_interval = 100 super().__init__(master, self.ardipy) self.sw1 = StopWatch("sw1") self.sw1.start() #Control Frame self.run_flag = True control_frame = tk.LabelFrame(master, text= "Control",relief = 'groove') start_button = tk.Button(control_frame, text="START/STOP", command=self.start) start_button.pack(side = 'left') interval_label1 = tk.Label(control_frame, text=u' Collection interval') interval_label1.pack(side='left') self.interval_txt = tk.Entry(control_frame, justify=tk.RIGHT, width=5) self.interval_txt.pack(side='left') self.interval_txt.insert(0, str(self.collection_interval)) interval_label2 = tk.Label(control_frame, text=u'ms') interval_label2.pack(side='left') interval_button = tk.Button(control_frame, text="SET", command = self.frame_set) interval_button.pack(side = 'left') control_frame.pack(side = 'top', fill = 'x') graph_list = ["AD0", "AD1", "AD2", "AD3", "AD4", "AD5"] graph_frame = ttk.Frame(master) self.gf = SelectableGraph(graph_frame, graph_list) graph_frame.pack(side = 'right', fill = 'both') self.gf.setGraph_Y_Range(-1, 6) self.update() def frame_set(self): val = int(self.interval_txt.get()) if( val > 10000 ): val = 10000 if( val < 1 ): val = 1 self.interval_txt.delete(0, 10) self.interval_txt.insert(0, str(val)) self.collection_interval = val pass def start(self): self.run_flag = not self.run_flag if( self.run_flag ): self.gf.start() else: self.gf.stop() def update(self): print( datetime.datetime.now() ) before_time = self.sw1.stop() vals = [] if (self.ardipy.isConnect() & self.run_flag): vals.append(self.ardipy.adRead(0) * (5/1024)) vals.append(self.ardipy.adRead(1) * (5/1024)) vals.append(self.ardipy.adRead(2) * (5/1024)) vals.append(self.ardipy.adRead(3) * (5/1024)) vals.append(self.ardipy.adRead(4) * (5/1024)) vals.append(self.ardipy.adRead(5) * (5/1024)) self.gf.setValues( vals ) current_time = self.sw1.stop() interval = current_time - before_time interval = self.collection_interval - int(interval*1000) self.master.after(interval, self.update) return if __name__ == "__main__": win = tk.Tk() cf = Control_Frame(win) win.geometry("750x600") win.title("Ardipy ADC Graph viewer") win.mainloop() 作ってみて分かった事 今回6ch同時測定することで、ADCの特性などが観測できました。 Arduinoに使われているATmegaにはADCポートは6chありますが、実際のマイコンには1個のADC装置が内臓しています。それを時分割で使う事で利用者から見たマイコンのADCは6chとなります。 ただし、この時分割が他のチャンネルのADC測定の影響がないかといわれると、測定前の結果に影響を受けます。 下記のは理想定な状態です。 ADC0 - 5V ADC1 - 接続なし(2.56V) ADC2 - 接続なし(2.56V) ADC3 - 0V ADC4 - 接続なし(2.56V) ADC5 - 接続なし(2.56V) ポートとして接続なし(オープン)状態であればATmegaの測定基準電圧 2.56V付近になるはずです。 ただ、測定間隔が短いと前ポートの影響を受けます。 ADC0 - 5V ADC1 - 接続なし(4.9V) ADC2 - 接続なし(4.9V) ADC3 - 0V ADC4 - 接続なし(0.1V) ADC5 - 接続なし(0.1V) と、影響を受けるはずでしたが、実際はADC4とADC5は5V付近で張り付きます。 実際の測定結果 ADC0 - 5V ADC1 - 接続なし(4.9V) ADC2 - 接続なし(4.9V) ADC3 - 0V ADC4 - 接続なし(5V) ADC5 - 接続なし(5V) Arduino UNOの回路図を見ると、ADC4とADC5はI2Cと機能兼用であることがわかります。 https://www.arduino.cc/en/uploads/Main/Arduino_Uno_Rev3-schematic.pdf マイコン自体のアナログポート回路を見ればこの原因は判断できると思いますが、今回は割愛します。 上図が実際測定結果です。測定周期を変えることで隣接のオープンポートへの影響がわかります。 ADC0 に5Vを入れて、ADC1, ADC2, ADC3の順番に大きな影響を受けている事が確認できます。 また、5秒程度時間が経過すると、それ以上周期を伸ばしても測定結果に変化ないことがわかりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tqdmのプログレスバー長すぎィ!!(長さ変更)

長すぎるだろ… 短くします 参考:GitHub Issue #585 for file_name in tqdm(files): # 何らかの処理 ↓ # tqdm's bar_format short_progress_bar="{l_bar}{bar:10}{r_bar}{bar:-10b}" for file_name in tqdm(files, bar_format=short_progress_bar): # 何らかの処理 結果 Yeah! That's exactly what I've been wanting PROGRESS BAR!!!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでプライバシーエラー'NET::ERR_CERT_DATE_INVALID'が表示されるサイトをWebスクレイピングする方法

概要 2021/10/1以降,古いMac環境 (OS X 10.11 El Capitan以前) やWindows 7以前にてWebサイトにアクセスした際,SSL証明書『Let's Encrypt』を利用したサイトにおいて以下の画像の様なSSL証明書のプライバシーエラーNET::ERR_CERT_DATE_INVALIDが表示されるようになった. 何らかの事情により古い環境でスクレイピングを行わなければならない場合,エラーが発生してしまうので対処法を記述する. urllibを使用してWebスクレイピングする場合の対処法 スクリプト内に下記のコードを追記する. import ssl ssl._create_default_https_context = ssl._create_unverified_context Seleniumを使用してWebスクレイピングする場合の対処法1 スクリプト内options.add_argument('--headless')の次の行に下記のコードを追記する. # SSLエラーを許容するオプション options.add_argument('--ignore-certificate-errors') Seleniumを使用してWebスクレイピングする場合の対処法2 (スマートでないため非推奨) スクリプト内driver.get(url)の次に行に下記のコードを追記する. # エラー画面内のエラー文字列をXPathで取得 disp = driver.find_element(By.XPATH, '//*[@id="error-code"]').text errcode = 'NET::ERR_CERT_DATE_INVALID' # 強引にクリックしてエラー画面を突破 if disp == errcode: driver.find_element(By.XPATH, '//*[@id="details-button"]').click() driver.find_element(By.XPATH, '//*[@id="proceed-link"]').click()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

つみたてNISAの損益をスプレッドシートに保存

この記事は、シアトルコンサルティング株式会社 Advent Calendar 2021の4日目の記事です。 こんにちは、シアトルコンサルティングの松縄です。 現在は、エンジニアとしてWebサービスの開発に携わっています。 はじめに みなさん、つみたてNISAはやっていますか? 僕は1年くらい前からやり始めていて最近は順調に上がっていて安心しています。 さて、今回とある証券会社のサイトでスクレイピングをしてみようと思ったのは、自動化をしたいなと考えたからです。 僕は最低でも1日1回は口座状況を見るようにしています。chromeでパスワードは保存できるからすぐログインできますが少し面倒でした。それに日毎に見て少し増減があってもよほど変わらない限り気付けないんです。 そこで、今回作ったPython + AWS Lambda + Spreadsheet + LINEのシステムで日毎の損益を残しつつ、週毎だったり月毎だったりの増減も見れるようにしてみました。 シリーズ ローカルで証券会社のサイトにログインしてスクレイピング←イマココ AWS Lambdaにローカルで作成したファイルをアップロードして実行 スプレッドシートに保存された値をLINEに通知 使用技術 Python 3.7 AWS Lambda AWS S3 AWS CloudWatch Google Spreadsheet GAS LINE Messaging API ローカルで証券会社のサイトにログインしてスクレイピング それではこの記事の本題に移ります。 まずはローカルで証券会社のサイトからデータを取ってスプレッドシートに保存するシステムを作りましょう。 今回必要なもの Python venv(Pythonを実行するための仮想環境) ChromeDriver スプレッドシート 手順 ディレクトリ作成、各種インストール 証券会社のサイトのトップページを開く 証券会社のサイトにログインして必要なデータを取得 スプレッドシートに保存 1. ディレクトリ作成、各種インストール まずはローカルで開発する場所とファイルを作ります。(ディレクトリ名、ファイル名はお好きに) mkdir myapp cd myapp touch lambda_function.py 次に仮想環境を作ります。作業ディレクトリにいる状態で以下を実行します。 python3 -m venv venv 右のvenvが環境名になります。 正しく処理されれば、作業ディレクトリ配下にvenvというフォルダが作成されます。 この状態になったら以下のコマンドを実行します。 source venv/bin/activate こちらが正しく実行されるとターミナルの○○のMacBook-Pro $の部分の左側か上に(venv)と追加されます。これで、仮想環境の有効化ができました。 次に、今回使うライブラリをインストールします。 pip3 install gspread selenium bs4 oauth2client python-dotenv また、今回はシステムからChromeを開いてログインなどを行うのでChromeDriverというChromeブラウザをプログラムで動かす為のドライバーを使用します。 1.左上のChromeタブから「Google Chrome について」をクリック 2.Chromeのバージョンを確認(今回は96.0.4664.55) 3.ChromeDriverをインストール こちらのサイトから2で確認したChromeのバージョンに近いものをインストールします。(今回はChromeDriver 96.0.4664.45をインストール) 4.chromedriverを作業ディレクトリ配下に移動 インストールしたzipファイルを開くとchromedriverというUnix実行ファイルになるので作業ディレクトリ配下に移動させておきます。 これでシステムからスクレイピングする準備は整いました。 次からは実装に入ります。 以下のコードのlambda_handlerから呼び出すメソッドごとに解説していきます。 段階毎に動作確認したい場合は、下のメソッドをコメントアウトしてください。 lambda_function.py import os import time import datetime import gspread import json from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.common.by import By from bs4 import BeautifulSoup from oauth2client.service_account import ServiceAccountCredentials from dotenv import load_dotenv def lambda_handler(event, context): # envファイルの読み込み load_dotenv() # 証券会社のサイトのトップページを開く driver = connect() # 証券会社のサイトの口座から必要なデータを取得する result = get_data(driver) # 取得したデータをスプレッドシートに保存する save_to_spreadsheet(result) if __name__ == '__main__': lambda_handler(event=None, context=None) 2. 証券会社のサイトのトップページを開く ここでは証券会社のサイトのトップページを開くところまで実装していきます。 まず、作業ディレクトリ配下に.envを作っておいて以下のように環境変数を設定しておきます。 CHROMEDRIVER=chromedriver lambda_handlerの中で最初に呼び出されるload_dotenvでenvファイルの読み込みをします。 次に呼ばれるconnectの中身はこちらです。 def connect(): # 環境変数の取得 chromedriver = os.getenv('CHROMEDRIVER', '') # Optionの設定 options = Options() # 画面上にChromeを表示させない options.add_argument('--headless') # webdriverの準備 driver = webdriver.Chrome(chromedriver, chrome_options=options) # 証券会社のサイトのトップ画面を開く driver.get('証券会社のサイトのURL') return driver それでは順番に解説します。 # 環境変数の取得 chromedriver = os.getenv('CHROMEDRIVER', '') getenvをすることでenvファイルの中から必要な環境変数だけ取得できます。 次に、chromedriverを使うためのoptionの設定です。 options = Options() options.add_argument('--headless') options.add_argument('--headless')を使うとヘッドレスモードでChromeを開くことになり、ブラウザを裏側で開くようにすることができます。画面がしっかりと遷移できていることを確認できるまではここはコメントアウトしておいた方が良いかと思います。 次にdriverの設定です。 driver = webdriver.Chrome(chromedriver, chrome_options=options) driver.get('証券会社のサイトのURL') 第一引数にはchromedriverのパスが入ります。この記事通りに書いている方はこのままで大丈夫ですが、移動させていない方はenvファイルのCHROMEDRIVERを正しいパスに書き換えてください。 driver.get()で指定したリンクを開きます。 これで、プログラムから証券会社のサイトのトップ画面を表示するところまで完了です。 ここまでを動作確認したい場合は、returnする前に以下をコードを加えましょう。 driverが立ちっぱなしになってしまうのでcloseとquitで終了させます。 driver.close() driver.quit() 3. 証券会社のサイトにログインして必要なデータを取得 ここでのソースコードは以下の通りです。 lambda_function.py def get_data(driver): # 環境変数の取得 user_name = os.getenv('USERNAME', '') password = os.getenv('PASSWORD', '') # 途中で処理が失敗した時にブラウザが開きっぱなしになるので必ず終了させるようにする try: # 遷移するまで待つ time.sleep(4) # ユーザーIDとパスワードを入力 input_user_id = driver.find_element_by_name('user_id') input_user_id.send_keys(user_name) input_user_password = driver.find_element_by_name('user_password') input_user_password.send_keys(password) # ログインボタンをクリック driver.find_element_by_name('ACT_login').click() # 遷移するまで待つ time.sleep(4) # ポートフォリオの画面に遷移 driver.find_element_by_link_text('口座管理').click() # 遷移するまで待つ time.sleep(4) # 文字コードをUTF-8に変換 html = driver.page_source.encode('utf-8') # BeautifulSoupでパース soup = BeautifulSoup(html, "html.parser") # 株式 table_data = soup.find('table', border="0", cellspacing="1", cellpadding="1", width="400") valuation_gains = table_data.find_all('tr', align="right", bgcolor="#eaf4e8") result = [] today = datetime.date.today().strftime('%Y年%m月%d日') result.append(today) for element in valuation_gains: text = element.find('font', color="red").text text = text.replace('+', '') result.append(text) except Exception as e: print(e) finally: driver.close() driver.quit() return result まず、envファイルにUSERNAMEとPASSWORDは追加しておきましょう。 USERNAME=ユーザー名 PASSWORD=パスワード 次に、ユーザー名とパスワードを入力してログインします。 # ユーザーIDとパスワードを入力 input_user_id = driver.find_element_by_name('user_id') input_user_id.send_keys('ユーザー名') input_user_password = driver.find_element_by_name('user_password') input_user_password.send_keys('パスワード') # ログインボタンをクリック driver.find_element_by_name('ACT_login').click() find_element_by_nameの引数にユーザー名を入力するinputタグのname属性を入れることでその要素を取得できます。 取得したらsend_keysを使ってユーザー名を入力します。 パスワードも同様です。 ユーザー名とパスワードを入力したらログインボタンの要素を取得して、clickメソッドを使ってログインします。 これで、証券会社のサイトにログインができたので次は欲しいデータを取得します。 今回取得したいのは右下の評価損益です。表示されている部分は買っている数によって異なります。 ここでは以下の形でスクレイピングの結果を出力するところまでをゴールとします。 ['今日の日付' , '株式1の評価損益', '株式2の評価損益', '株式3の評価損益'] まずは、ログインをした後の画面の口座管理のリンクに遷移します。 driver.find_element_by_link_text('口座管理').click() 次に、BeautifulSoupを使う準備をします。ここは詳しく説明できるほど理解できていないのでおまじないのつもりです。 # 文字コードをUTF-8に変換 html = driver.page_source.encode('utf-8') # BeautifulSoupでパース soup = BeautifulSoup(html, "html.parser") 準備をしたら、今回取得したいデータが書かれている要素を取得します。 今回取得したい要素をコピーすると以下のようになります。(リンクや数字は適当に変えてます) <table border="0" cellspacing="1" cellpadding="1" width="400"> <tbody> <tr> <td class="mtext" colspan="4"> <font color="#336600"> <b>投資信託(金額/つみたてNISA預り)</b> </font> </td> </tr> <tr bgcolor="#79b26b" align="center"> <td width="93" class="mtext"> <font color="#ffffff">保有口数</font> </td> <td width="93" class="mtext"> <font color="#ffffff">取得単価</font> </td> <td width="93" class="mtext"> <font color="#ffffff">基準価額</font> </td> <td width="108" class="mtext"> <font color="#ffffff">評価損益</font> </td> </tr> <tr bgcolor="#b9e8ae"> <td class="mbody" colspan="3"> <a href="省略">株式1</a> <a href="省略"> <img src="省略" title="メールアラート画面へ"> </a> </td> <td class="stext" align="right"> <a href="省略">積立</a> <a href="省略">売却</a> </td> </tr> <tr bgcolor="#eaf4e8" align="right"> <td class="mtext">10,000</td> <td class="mtext">10,000</td> <td class="mtext">10,000</td> <td class="mtext"> <b><font color="red">+10,000</font></b> </td> </tr> </tbody> </table> このtableタグの中の+10,000という文字列が欲しいのでそれを取れるように書いています。(書き方は色々あると思うのでご自由に) # 株式 table_data = soup.find('table', border="0", cellspacing="1", cellpadding="1", width="400") valuation_gains = table_data.find_all('tr', align="right", bgcolor="#eaf4e8") 今回は、まずfindで上のtableタグ全体を取得します。 次にfind_allでtableタグの中からbgcolor="#eaf4e8" align="rightのtrタグを全て取得します。(上のHTMLでは消していますが実際は同じようなtrタグがあと2つあります) 僕は3つの株式を買っているので以下のような配列ができています。 [ <b><font color="red">+10,000</font></b>, <b><font color="red">+20,000</font></b>, <b><font color="red">+30,000</font></b> ] これで、評価損益が含まれている要素の配列ができました。 次に、出力する用の配列を用意して、先頭に今日の日付を入れておきます。 result = [] today = datetime.date.today().strftime('%Y年%m月%d日') result.append(today) 上で用意した配列に、find_allで取得した評価損益の部分を入れていきます。 スプレッドシートに入れるときに+があると数値として見てくれないので+は消しておきます。 for element in valuation_gains: text = element.find('font', color="red").text text = text.replace('+', '') result.append(text) これで以下の形の配列を作ることができました。 ['今日の日付' , '株式1の評価損益', '株式2の評価損益', '株式3の評価損益'] 4. スプレッドシートに保存 証券会社のサイトから評価損益を取得できたのでこの結果をスプレッドシートに登録します。 今回はシンプルに以下のようにレコードを追加していきます。 まずはソースコードです。 lambda_function.py def save_to_spreadsheet(result): # 環境変数の取得 spreadsheet_json = os.getenv('SPREADSHEET_JSON', '') spreadsheet_id = os.getenv('SPREADSHEET_ID', '') #2つのAPIを記述しないとリフレッシュトークンを3600秒毎に発行し続けなければならない scope = ['https://spreadsheets.google.com/feeds','https://www.googleapis.com/auth/drive'] #認証情報設定 #ダウンロードしたjsonファイル名をクレデンシャル変数に設定(秘密鍵、Pythonファイルから読み込みしやすい位置に置く) credentials = ServiceAccountCredentials.from_json_keyfile_name(spreadsheet_json, scope) #OAuth2の資格情報を使用してGoogle APIにログインします。 gc = gspread.authorize(credentials) #共有設定したスプレッドシートのシート1を開く worksheet = gc.open_by_key(spreadsheet_id).sheet1 # 受け取った配列をスプレッドシートに追加する worksheet.append_row(result, value_input_option='USER_ENTERED') return { 'status': 204 } envファイルに追加する前に、スプレッドシートの準備をします。 こちらのサイトの2.スプレッドシートの設定まで行います。 必要なものは以下の2つです。 サービスアカウントキーのjsonファイル(spreadsheet-xxxxx-xxxxxxxxx.json) 作成したスプレッドシートのスプレッドシートキー(例:https://docs.google.com/spreadsheets/d/××××/edit#gid=0のxxxxの部分) まず、サービスアカウントキーのjsonファイルを作業ディレクトリ配下に置きます。 次に、envファイルに以下を追加します。 SPREADSHEET_JSON=spreadsheet-xxxxx-xxxxxxxxx.json SPREADSHEET_ID=作成したスプレッドシートのスプレッドシートキー これで準備はできたのでソースコードの説明に移ります。 といってもここは先ほどのサイトをほぼ真似しているので、最後の部分だけ解説します。 worksheet.append_row(result, value_input_option='USER_ENTERED') ここではappend_rowを使って配列を行としてスプレッドシートに追加することができます。また、value_input_option='USER_ENTERED'を入れることで、スプレッドシートに配列を入れた際に文字列になってしまうのを防ぐことができます。 これで、証券会社のサイトから評価損益を取得してスプレッドシートに保存することができました。 最終的なディレクトリ構成は以下の通りです。 myapp ├─ lambda_function.py ├─ venv ├─ .env ├─ spreadsheet-xxxxx-xxxxxxxxx.json └─ chromedriver さいごに いかがでしたか? つみたてNISAの損益をスプレッドシートに保存するプログラムの解説でした。 今回作ったプログラムはローカルで直接動かさないといけないので、 AWS lambdaに入れて定期的に実行できるようにしたいと考えています。 その辺りの記事は別で書く予定です。 参考になれば幸いです。 参考 seleniumを使用しようとしたら、「"chromedriver"は開発元を検証できないため開けません。」と言われた ポートフォリオ情報をPythonでスクレイピング 【もう迷わない】Pythonでスプレッドシートに読み書きする初期設定まとめ 会社情報はこちらです!よかったらご覧になってください。 コーポレートサイト Wantedly
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ソースコードからのCanteraのインストール

Canteraとは https://github.com/Cantera/cantera - 反応動力学および熱力学,輸送過程を解くためのオープンソースかつオブジェクト指向のソフトウェア - 商用ソフトウェアのCHEMKIN-PROとほとんど同等のことができる. - ソースコードはC++とFortran 90で書かれている - PythonとMatlabから使用するためのインターフェースも用意されている. 環境 Windows 10 Miniconda 4.10.3 Cantera 2.5.1 コンパイルのための準備 以下の記事の前半部分を参考にAnacondaの仮想環境を準備する. https://www.cantera.org/compiling/installation-reqs.html#sec-installation-reqs conda activate canteraのところまでを実行しておく. ソースコードのダウンロード 以下の記事を参考にソースコードをダウンロードする https://www.cantera.org/compiling/source-code.html#sec-source-code 安定板のv2.5.1をダウンロードした. Canteraのビルド ソースコードのrootとなる場所にcantera.confというファイルを作成する. ビルドの際にこのファイルで指定したコマンドが反映される. $ vim cantera.conf 以下の内容を記述して保存 cantera.conf python_package = 'full' boost_inc_dir = 'C:\\Users\\sakir\\miniconda3\\envs\\ct_src\\Library\\include' boost_inc_dirにはAnacondaでインストールしたboostの場所を指定する. ビルドは以下のコマンドで行う. $ scons build 以下のエラーが発生する. src\base\Units.cpp(27): error C2001: 定数が 2 行目に続いています。 src\base\Units.cpp(27): error C2064: 1 引数を取り込む関数には評価されません。 src\base\Units.cpp(79): error C2059: 構文エラー: ';' scons: *** [build\src\base\Units.obj] Error 2 scons: building terminated because of errors. エラーが発生している行は以下のように記述されており,この行を削除もしくはコメントアウトすると解消する. {"Å", Units(1e-10, 0, 1, 0)}, 無事にビルドが完了すると以下のような出力が得られる. ******************************************************* Compilation completed successfully. - To run the test suite, type 'scons test'. - To install, type 'scons install'. - To create a Windows MSI installer, type 'scons msi'. ******************************************************* scons: done building targets. テストの実行 以下のコマンドでテストを実行する scons test 実行結果は以下の通りで4つのテストで失敗したとの表示. 失敗した個所はすべて上でコメントアウトしたオングストロームからの変換に関するもの. エラーのためにビルドを終了したといっているのだが,とりあえずはインストールまで進める. ***************************** *** Testing Summary *** ***************************** Tests passed: 1121 Up-to-date tests skipped: 0 Tests failed: 4 Failed tests: - thermo: ThermoFromYaml.DebyeHuckel_bdot_ak - thermo: ThermoFromYaml.DebyeHuckel_beta_ij - python:test_composite.TestModels.test_load_thermo_models - python:test_composite.TestModels.test_restore_thermo_models ***************************** scons: *** [test_results] One or more tests failed. scons: building terminated because of errors. Canteraのインストール インストールの実行のためには管理者権限が必要になるので,Anacondaのプロンプトを管理者権限で立ち上げる. 以下のコマンドでCanteraをインストールする. scons install インストールが正常に終了すると以下の通りの出力を得る. Anacondaの仮想環境上でインストールを行っているけれども,Canteraはすべてのユーザー向けにインストールされるみたい. Cantera has been successfully installed. File locations: applications C:\Program Files\Cantera\bin library files C:\Program Files\Cantera\lib C++ headers C:\Program Files\Cantera\include samples C:\Program Files\Cantera\samples data files C:\Program Files\Cantera\data Python package (cantera) C:\Users\sakir\miniconda3\Lib\site-packages Python samples C:\Users\sakir\miniconda3\Lib\site-packages\cantera\examples scons: done building targets. インストールの確認 インストールの確認のために,Pythonでcanteraをインポートしてみる. 無事にインストールできているようだ. (cantera) PS C:\Users\sakir> python Python 3.6.13 (default, Sep 23 2021, 07:38:49) [MSC v.1916 64 bit (AMD64)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import cantera as ct >>> ct.__version__ '2.5.1'
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【図解解説】JOI2021-2022 一次予選 第3回 問題2 アイスクリーム

図解解説シリーズ 競技プログラミングを始めたばかりでAtCoderの解説やJOIの解説ではいまいちピンと来ない…という人向けに、図解を用いて解説を行います。 問題文 情報オリンピック日本委員会に掲載されている問題 AtCoderに掲載されている問題 入出力など実際に確認して自分の作成したプログラムを採点することができます。 図解解説 今年度の一次予選のB問題は、以下の3つのスキルを確認する問題になっています。 1.入力・出力を正しく利用できる 2.算術演算子を正しく利用できる 3.条件分岐(if)を正しく利用できる (4.繰り返し処理を正しく利用できる) 問題文を整理するために、具体的な数字を用いて、図示して考えてみます。 入力例1を使って考えてみます。 28cm以上のアイスクリームタワーを作成します。ベースとなるアイスクリームは20cmです。希望の高さに足りませんので、追加のアイスクリーム(5cm)を重ねます。1つ重ねると25cm、2つ重ねると30cmとなり、28cmを超えます。したがって、250+100+100=450円が求める答えになります。このように、ベースとなるアイスクリームに追加のアイスクリームを加え続け、最終的に希望するアイスクリームタワーの高さ以上になったら終了するという戦略をとることができます。 一方、計算から求める方法もあります。28cm以上のアイスクリームタワーを作成します。ベースとなるアイスクリームは20cmですので、足りないのはあと8cm(28cm-20cm)です。追加のアイスクリームは5cmですので、不足分の8cmを使って8cm÷5cmを計算すると1.6となります。したがって、1個では足りず、2個追加すると28cmを超えることが計算からわかります。 条件分岐について、不安がある場合には上のスライドを参考にしてみてください。 解答例 戦略:ベースとなるアイスに追加のアイスを加え続ける b1.py S = int(input()) A = int(input()) B = int(input()) #ベースだけでアイスクリームタワーが完成する場合は250を出力 if S<=A: print(250) else: #ベースだけで完成しない場合について考える #高さ(tall)の初期値をベースの高さ(A)、金額(amt)の初期値をベースの金額(250)に設定 tall = A amt = 250 #最大100個追加できるので100回繰り返す for i in range(100): #高さ(tall)に追加のアイスクリームの高さ(B)を1回追加 #金額(amt)に追加のアイスクリームの金額100円を追加 tall = tall + B amt = amt + 100 #高さ(tall)がアイスクリームタワーの高さ(S)以上になったら終了(break)し、金額(amt)を出力 if S<=tall: break print(amt) 採点サイトに提出したプログラム 戦略:計算から求める b2.py S = int(input()) A = int(input()) B = int(input()) #ベースだけでアイスクリームタワーが完成する場合は250を出力 if S<=A: print(250) else: #アイスクリームタワーの高さ(S)からベースの高さ(A)を引いた値(S-A)について考える #追加のアイスクリームの高さ(B)で割り切れるとき追加で購入するアイスクリームの数は、(S-A)//Bとなる #追加のアイスクリームの高さ(B)で割り切れないときは、(S-A)//B + 1 が追加で購入するアイスクリームの数となる tuika = (S-A)//B if (S-A)%B==0: print(250+100*tuika) else: print(250+100*(tuika+1)) 採点サイトに提出したプログラム イラスト スライド内で使用しているイラストはすべて「いらすとや」の素材を利用しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python の asyncio は超便利

Python の asyncio (公式ページ) は超便利なので紹介します。 ■何が便利? 要するに JavaScript ではもはや当たり前になっている async/await が実現できます。 つまり、非同期(処理をしている間、同期して完了を待つのでなく、次の処理を実行するやり方)を実現します。 非同期により、全体の 処理速度を爆上げ できる場合がよくあります。 例えば、外部サービスリクエストや、ファイル・DBへの読み書きなど、I/O関連は時間がかかる割にCPUは空いてたりするので、そこが有効に活用されるようになるわけです。 多くのシステムはI/Oが大量にあったりするので圧倒的です。 ■それってスレッドでもできるのでは? もちろん可能ですが、コンセプトが異なり、スレッドに比べて 圧倒的にお手軽 です。 スレッドと何が違うのかというと、主な違いは、スレッドは複数の処理が同時に走るということです。処理の裏で別の処理が同時に走るため、値の読み/書きでは必ずロックやスレッドセーフを考慮しなければなりません。 一方で asyncio は処理の裏で別の処理が走ることはあり得ません。(I/Oが走ってることはあり得ますけど。) すべての処理は直列に走ります。走ってる処理が await になると、待ち行列に並んでいる次の処理が走り始めます。 これが最高に良いのです。ロックもスレッドセーフも考慮不要なのですから。 なぜなら、裏で別の処理が走ることはないわけですから、1つの処理を実行している間は(await するまでは)必ずその処理だけが全てを占有できるわけです。占有できるのでロックやスレッドセーフの考慮は不要となり、ロック/スレッドセーフの高コストな処理がない点も高速化に寄与します。それ以上にコードがシンプルになるのでメンテナンスが楽になります。 では逆に、スレッドが有利になる場合はなんでしょうか。複数のCPUがあり、それを各スレッドに割り当てた時ですかね。(プロセスと呼ぶべきか。) ただ、ロックやスレッドセーフはそれなりにコストもかかるので、本当にCPUを複数使うことで効果が出るかは要件次第です。 デバッグもとても大変なものになりますので、まずは asyncio から検討するのが良いかと思います。 ■具体的な使い方 import asyncio async def main(): print('Hello ...') await asyncio.sleep(1) print('... World!') # Python 3.7+ asyncio.run(main()) 標準機能なのでインストールは不要です。 必ず main 的な async メソッドを1つ用意し、一番外側で run する必要があります。 ■よく使う機能の紹介 具体的な使い方は標準ドキュメントに大量の情報がありますのでそっちをみていただくとして、ここでは良く使う機能に絞って、かつ、要するに何者なのかが分かるようにまとめます。 sleep await asyncio.sleep(1) 非同期に一定時間待ちます。 gather await asyncio.gather([ async_method1(), async_method2(), ]) 複数同時に並行実行します。並行してI/Oを待つことができます。全てが終わったら完了します。処理自体は実際は直列ですが、処理の中で I/O を await した際に、自身の処理はIO待ちで止まってしまいますが、並行している別の処理に制御がわたるので、CPUが効率的に使われます。実際は直列ながらも並列処理ができる(I/Oが並列する)わけです。 create_task / Task task1 = asyncio.create_task(async_method1()) # 終了を待つこともできる # ※ async_method1()の実行でエラーが出ていればここで発生することになる await task1 async メソッドの処理を別スレッド立てるみたいに実行します。完了をawaitするのでなく、並行して実行します。ただし、asyncio ですので真の平行なのではなく、await した際に、自分の順番が来たら実行されます。裏側に eventloop というキューのような仕組みがあり、誰かがawaitする度にeventloop から処理が1つ取り出されて実行されます。これを繰り返すのですが、このeventloopに追加されます。 Task.result result1 = task1.result() タスク元とのなった async メソッドの実行結果を取得する。 実行でエラーが出ていればここで発生することになる まだ終わっていないなら InvalidStateError が発生する Task.done is_done = task.done() タスクが終了しているかどうかを bool で取得する。 実行でエラーが出ていればここで発生することになる Lock lock = asyncio.Lock() # ... その後 async with lock: # 共有しているデータへのアクセス ロックの仕組みを async で実現します。async with を抜けたら自動的にロックが解放されます。 Semaphore sem = asyncio.Semaphore(10) # ... 別のどこか async with sem: # 共有しているデータへのアクセス 「セマフォ」と読みます。 基本、ロックと同じで、共有リソースへのアクセスを一定個数以内(上記なら10個以内)に保ちたい場合に使います。 上記なら async with sem の中に入れるのは 10 処理まで、ということになります。 Event some_event = asyncio.Event() # ... 別のどこか # 発火を待つ await some_event.wait() # ... 別のどこか # イベント発火 event.set() 何かのイベントのきっかけがトリガーされたことを通知します。 イベントのインスタンスごとに Event オブジェクトを生成します。 wait() したときにすでに発火済みだった場合も拾えます。 event オブジェクトは発火したかどうかのフラグ的なもので、一度発火すると、ずっと発火しっぱなし(フラグ立ちっぱなし)です。 clear() を呼ぶことでフラグを降ろして再利用可能ですが、前の発火なのか降ろした後のすぐの発火なのか見分けられないので、event オブジェクトを使い捨てながら使ったほうが現実的に思っています。(チャット欄などで希望頂ければ問題&解決策の事例記事をアップします。) Queue 複数の async メソッド同士でやり取りするのに非常に便利なクラス。 非同期な処理の完了を一方が無限ループで queue を get し続け、もう一方が完了し次第 put していく作りはよくやります。 hoge_queue: 'asyncio.Queue[Hoge]' = asyncio.Queue() # queue から1つ取り出し hoge = await hoge_queue.get() # queue が空なら例外(QueueEmpty)が発生する取り方 # ※ await 不要! なので async でないメソッドからも呼べる try: hoge = hoge_queue.get_nowait() except asyncio.QueueEmpty: # 空だった場合の処理 # 事前に空かチェック if queue.empty(): # 空の場合の処理 # queue に追加 queue.put_nowait(hoge) queueで保持可能なアイテム数を指定する場合 hoge_queue: 'asyncio.Queue[Hoge]' = asyncio.Queue(1) # 1個まで # queue に追加(いっぱいなら例外) try: queue.put_nowait(hoge) except asyncio.QueueFull: # いっぱいの場合の処理 # queue に追加(いっぱいなら待つ) await queue.put(hoge) # 事前にいっぱいかチェック if queue.full(): # いっぱいの場合の処理 無限ループの場合、終了でループが終わるつくりにするのに一工夫は必要です。アイテムとして終了フラグ的なものを渡すなど(なにしろ次が来るまで永久に待ち続けてしまいますので)。 型の指定で 'asyncio.Queue[Hoge]' とすることでアイテムの型指定が可能です。シングルクォート必須なので注意。 PriorityQueue 優先順位順に取り出せるキュー(基本は前述のキューと同じ)。 hoge_queue: 'asyncio.PriorityQueue[Hoge]' = asyncio.PriorityQueue() 他は Queue と完全に同じ。 ただし、アイテム(上記でいえば Hoge)は優先順位順に並んだ状態で管理される。優先順位の評価には __lt__ が使われる。 アイテムの指定例1 # アイテムの優先順位を1つに限定して定義可能な場合はこの方法 @dataclass class Hoge: num: int def __lt__(self, other: 'Oricon'): # num が小さいものを優先 return self.num < other.num # ... 別のどこか # num が小さいものから順に get できる hoge = await hoge_queue.get() アイテムの指定例2 # アイテムの優先順位が複数あったり、アイテムのクラスを触れない場合はこの方法 # put する際にタプルで順位を渡す hoge_queue.put_nowait((item.num, item)) # num が小さいものから順に get できる _, item = await hoge_queue.get() よく使うのはこれくらい。 ■ async メソッドの Test (pytest) 方法 pytest-asyncio のインストール 下記のように pipfile の [dev-packages] ブロックに pytest 関連をインストール。 pytest-asyncio は pytest で asyncio を扱うためのモジュール。これを加えます。 pipfile [dev-packages] pytest = "*" pytest-asyncio = "*" pytest-mock = "*" async なテストケースの実行方法 @pytest.mark.asyncio async def test__call_async_method(): # await が使える actual = await async_method1() # チェック assert actual == 123 @pytest.mark.asyncio をつける async な fixture の実現方法 async def fixure1(): return await async_method1() def test__something(fixure1): assert fixure1 == 1234 fixture はアノテーションなど不要で async なメソッドが利用可能。 使うときもawait不要。await 済みのデータを取得した状態でやってくる。 async な mock / mocker.patch.object の実現方法 def test__mock(self): hoge = AsyncMock(Hoge) mocker.patch.object(hoge, 'async_method1', return_value=123) # → await hoge.async_method1() の結果 123 が返る mocker.patch.object(hoge, 'normal_method1', return_value=123) # → hoge.normal_method1() の結果 123 が返る MagicMock の代わりに AsyncMock を使うことでメンバで持っているメソッドすべてが、async かどうかにあわせてモックメソッドになる。つまり、async なら await の後、return_value や side_effect の指定したものが反映される動きになる。 AsyncMock したオブジェクトに対して mocker.patch.object を使うことで await かそうでないかは内部で自動的に判断して吸収してくれる。 こちらもどうぞ pytest で単体テストの方法まとめ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Databricks】基本的な使い方とCSVのインポート、SQLでの操作までのまとめ

こんにちは! 今回、Databricksを利用することになったのですが、いろいろ機能があるので、自分が直近使う範囲でまとめてみようかと思います。 この記事では、基本的な機能やCSVをNotebookから操作する等を行います。 連投する記事ではモデルの作成等も行う予定です。 各タブの機能について Databricksを開くとサイドバーにいくつかのタブがあります。 現行バージョンの上から順に説明します。 Databricks Data Science & Engineering 今回利用するDatabricks Data Science & Engineeringについてです。 こちらは、ApacheSparkに基づく分析プラットフォームとされています。 Notebookを利用した共同作業等が可能になっています。 Create Notebook,Table,Cluster,Jobを作成することができます。 これらについては以降の項目で触れることになるので割愛します。 Workspace workspaceではディレクトリ構造でNotebookなどの作業ファイルを管理することができます。 先ほどのCreateで作成するNotebookはこのWorkspaceのいずれかに配置することになります。 Repos Gitからリポジトリを複製して持ってこれます。 sys.path.append("/Workspace/Repos/<user-name>/<repo-name>") のようにすることで、Notebookから参照することもできるようです。 Recents 最新の変更点等が確認できます。 Seach Workspace内を検索できます。 Data CSVやDBなどのデータを保存する部分になります。 上述したTableを作成する際はこの中に作成することになります。 Data内ではDBとファイルで管理するタブが分かれています。 ファイルはディレクトリ構造で管理することができ、簡単にNoteBookから参照することができます。 Compute 少し前までClusterというタブだったものです。 Clusterの作成や管理を行えます。 上述したClusterを作成するとここに追加されることになります。 Job 定期実行したりする場合はこのJobを使うことになります。 上述したJobを作成するとここに追加されることになります。 基本的な操作については以上です。 では実際にCSVデータを用意して読み込んでみます。 CSVファイルのUpload localに適当なCSVファイルを用意してください。 DataタブのDBFSからUploadを選択し、任意のディレクトリにファイルをUploadします。 今回は/FileStore/testdata/test.csvとします。 これでCSVは登録完了です。 では、このデータをNotebookから触ってみたいと思います。 Clusterの作成 まずはNotebookで利用するClusterを作成しましょう。 ComputeタブからCreate Clusterを選択し、あたらしくクラスターを作成します。 クラスター名はtestClusterにしました。 ClusterModeはSingleNode、Runtimeは9.1LSTです。 Terminate…の項目では、利用していない時間がxx分経過したときに自動で停止してくれる設定です。 今回は30分にしました。 WorkertypeはStanderd_F4にしています。 Notebookの作成 Worckspaceを開き、任意の階層で右クリックすることでCreateのメニューが出てきます。 そこからNotebookを選び、作成します。 名称はtestbookとし、デフォルト言語をPythonにしました。 Clusterには先ほど作成したものを選択しましょう。 CSVのインポート確認 それでは、CSVが取得できるか確認してみましょう。 pandasが標準で入っているのでこれを利用してデータを表示してみようと思います。 まず、Clusterを開始する必要があります。こちらはComputeタブから特定のClusterを開いて実行することで確認できます。 notebook import pandas as pd print(pd.read_csv("/dbfs/FileStore/testdata/test.csv")) こちらを実行するとファイルの中身が表示されると思います。 今回はSQLで操作できるように、一旦Tableに保存したいと思います。 notebook from pyspark.sql.types import * inputDF = spark.read.format("csv")\ .options(header="true", inferSchema="true")\ .load("/FileStore/testdata/test.csv") # FileStoreからでいいので注意 inputDF.createOrReplaceTempView('csvloadtest') csvloadtest = inputDF inputDF =…の部分で先ほどのCSVを読込み、createOrReplaceTempViewによってcsvloadtestという名前で一時Viewを作成します。 これによりクエリでデータを確認することができます。 実際にSQL文でデータを見てみたいと思います。 notebook %sql SELECT * FROM csvloadtest 実行すると表が出てくると思います。 実行セル左下のグラフアイコンを押下することでグラフでの表示も可能です。 どの形式のグラフで表示するかも設定可能です。 PlotOptionではグラフで表示するデータを変更可能です。 Excelチックなので結構わかりやすいと思います。 まとめ 今回はDatabricksにCSVデータをアップロードし、SQLでの操作確認まで行ってみました。 目標としてはモデルの作成までを理解したいので、次回はそちらを行っていこうかと思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【図解解説】JOI2021-2022 一次予選 第3回 問題4 ボールの移動

図解解説シリーズ 競技プログラミングを始めたばかりでAtCoderの解説やJOIの解説ではいまいちピンと来ない…という人向けに、図解を用いて解説を行います。 問題文 情報オリンピック日本委員会に掲載されている問題 AtCoderに掲載されている問題 入出力など実際に確認して自分の作成したプログラムを採点することができます。 図解解説 今年度の一次予選のD問題は、以下の5つのスキルを確認する問題になっています。 1.入力・出力を正しく利用できる 2.算術演算子を正しく利用できる 3.条件分岐(if)を正しく利用できる 4.繰り返し処理を正しく利用できる 5.配列(リスト)を正しく利用できる 問題文を整理するために、具体的な数字を用いて、図示して考えてみます。 箱とボールのどちらを基準にして操作を行うか、入力例1を使ってシミュレーションしてみます。 箱を基準に考えてみると、箱の中にボールが1個も入っていない状況や、箱の中にボールがたくさん入っている状況が発生します。特定のボールがどの箱の中に入っていて、次にどの箱に動かせばいいのか…と考えるのも大変です。 一方、ボールを基準に考えてみると、今、ボールがどの箱の中に入っているのか管理するだけで済みます。変更の発生するところだけ変更すれば済みます。 従って、今回は、ボールを基準にして管理する方法でプログラムを行うことにします。 そこで、Ballという配列を用意して、その配列の要素の中に箱の番号を入れて状態を管理します。 配列は、要素番号0から始まります。もし、配列の要素数をボールの数と一致させると、箱とボールの番号も0に読み替えて管理する必要があります。0から管理しなくてもすむように、配列の要素数を「ボールの数+1」にして、箱とボールの番号をそのまま使用すると便利です。 解答例 d.py N,M = map(int,input().split()) #ボールがどの箱に入っているか管理するための配列Ballを宣言し、初期値(ボールの番号=箱の番号)を代入 Ball = [] for i in range(N+1): Ball.append(i) #ボールXが箱Yに移動する操作をM回実行 for i in range(M): X,Y = map(int,input().split()) Ball[X]=Y #最終的にどの箱に入っているかボール1からボールNまで順番に出力 for i in range(1,N+1): print(Ball[i]) 採点サイトに提出したプログラム イラスト スライド内で使用しているイラストはすべて「いらすとや」の素材を利用しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【図解解説】JOI2021-2022 一次予選 第3回 問題1 身長

図解解説シリーズ 競技プログラミングを始めたばかりでAtCoderの解説やJOIの解説ではいまいちピンと来ない…という人向けに、図解を用いて解説を行います。 問題文 情報オリンピック日本委員会に掲載されている問題 AtCoderに掲載されている問題 入出力など実際に確認して自分の作成したプログラムを採点することができます。 図解解説 今年度の一次予選のA問題は、以下の2つのスキルを確認する問題になっています。 1.入力・出力を正しく利用できる 2.算術演算子を正しく利用できる 問題文を整理するために、具体的な数字を用いて、図示して考えてみます。 図のように具体的な数値をあてはめて考えると、理解が進みます。特に入力例は、さまざまなパターンを網羅していますので、正しくプログラミングできているか確認するには最適です。困ったときは入力例を参考にして、考えてみてください。 解答例 図解の中に赤字で記述した通りです。 a.py A = int(input()) B = int(input()) print(B-A) 採点サイトに提出したプログラム イラスト スライド内で使用しているイラストはすべて「いらすとや」の素材を利用しています。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker-composeで認証ありmongoDBを立ててPyMongoでアクセスしてみる

はじめに docker-composeでmongoDBを認証ありで立てたときに、PyMongoでどうやってアクセスすれば...となったりしたので、まとめてみました。 本稿では以下順でご紹介していきます。 docker-composeで認証ありmongoDBをつくる ユーザーを追加する(PyMongo) 作成したユーザーでmongoDBを制御する(PyMongo) 環境情報は以下となります。 Python : 3.8.0 PyMongo : 3.12.1 MongoDB : 5.0.4 ※本稿ではすでにPythonやdockerなどの環境が揃っているものとして進めます。 1.docker-composeで認証ありmongoDBをつくる MongoDBのdockerコンテナ 「mongo」を作成します。   使用コード dokcer-compose.yml version: '3' services: mongo: image: mongo:5.0.4 container_name: mongo ports: - 27017:27017 volumes: - ./mongo/db:/data/db - ./mongo/configdb:/data/configdb environment: MONGO_INITDB_ROOT_USERNAME: root # ここがポイント! MONGO_INITDB_ROOT_PASSWORD: root TZ: Asia/Tokyo 実行コマンド 上記docker-compose.ymlのある場所で以下コマンドを実行してください。 $docker-compose up --build 説明 □ポイント ポイントはMONGO_INITDB_ROOT_USERNAMEとMONGO_INITDB_ROOT_PASSWORDです。   ここで指定したユーザー名とパスワードがスーパーユーザーとなります。   本稿ではスーパーユーザー設定は以下としてます。   ユーザー名 : root パスワード : root 通常mongoDBに認証をつけるには「authorization: enabledを指定」や「--authをつけてmongo実行」などする必要があるみたいですが、docker-composeの場合は上記定義を書けば立ち上げ時点から認証有効になるようです。 2.ユーザーを追加する(PyMongo) スーパーユーザーを使うことでもアクセスはできますが、セキュリティのためにも今回はアクセス用のユーザーを用意しましょう。 使用コード add_user.py from pymongo import MongoClient host_name = "localhost" port_num = 27017 user_name = "py_user" # 作成ユーザーの名前 user_pwd = "py_pwd" # 作成ユーザーのパスワード db_name = "sample-db" # 対象データベース # スーパーユーザーでアクセス client = MongoClient( host = host_name, port = port_num, username = "root", # ここがポイント1個目! password = "root", ) # 「sample-db」データベースにユーザーを追加 db = client[db_name] db.command( # ここがポイント2個目! 'createUser', user_name, pwd=user_pwd, roles=['readWrite'], ) # user確認 for user in db.command('usersInfo').get('users'): print(user) client.close() 実行コマンド 上記Pythonを実行して以下結果が得られれば成功です。 $python add_user.py {'_id': 'sample-db.py_user', 'userId': UUID('82aee8a5-58a0-4314-979a-48131dd8eec3'), 'user': 'py_user', 'db': 'sample-db', 'roles': [{'role': 'readWrite', 'db': 'sample-db'}], 'mechanisms': ['SCRAM-SHA-1', 'SCRAM-SHA-256']} ※userIdは環境依存です。 説明 □ポイント1個目 手順1にてdokcerコンテナを立てた時点で認証が有効なので、docker-composeで定義したスーパーユーザーでアクセスする必要があります。   そこでMongoClientにて「username = "root"」「password = "root"」を指定することで、スーパーユーザーとして接続するようにしています。 □ポイント2個目 ユーザーを追加するApiは以下となります。 db = client['<ユーザーを追加する対象DB名>'] db.command( 'createUser', '<追加したいユーザー名>', pwd='<追加ユーザーのパスワード>', roles=['<権限>'], ) ※補足 ユーザー追加方法として以下方法もあります。 しかし、adduserはPyMongo4.0で削除予定とのことで非推奨のようです。 db = client[db_name] db.adduser( '<追加したいユーザー名>', pwd='<追加ユーザーのパスワード>', roles=['<権限>'] ) 3.作成したユーザーでmongoDBを制御する(PyMongo) 先ほど作成したユーザーで実際に制御してみます。 使用コード accsess_insert.py from pymongo import MongoClient host_name = "localhost" port_num = 27017 user_name = "py_user" # 作成ユーザーの名前 user_pwd = "py_pwd" # 作成ユーザーのパスワード db_name = "sample-db" # 対象データベース co_name = "sample-co" # 作成ユーザーでアクセス client = MongoClient( host = host_name, port = port_num, username = user_name, password = user_pwd, authSource = db_name, # ここがポイント! ) db = client[db_name] co = db[co_name] # データを追加 data1 = { "name":"TestBook", "price":420, } co.insert_one(data1) data2 = { "name":"ProgramText", "price":1500, "category":"IT", } co.insert_one(data2) # 全件表示 for data in co.find(): print(data) client.close() 実行コマンド 上記Pythonを実行して以下結果が得られれば成功です。 $python accsess_insert.py {'_id': ObjectId('619f9fdda0c5b80b201aa550'), 'name': 'TestBook', 'price': 420} {'_id': ObjectId('619f9fdda0c5b80b201aa551'), 'name': 'ProgramText', 'price': 1500, 'category': 'IT'} 説明 □ポイント MongoClientの指定にauthSourceを追加しました。 authSourceには認証のために参照するデータベースを指定できます。 今回は「sample-db」を指定することで、手順2で作成したユーザー「py_user」として接続できるようになります。 ちなみに、authSourceを省略した場合は「admin」データベースが参照先となります。   (例えば手順2のときは省略しているのでadminを参照してます) ※補足 アクセスユーザーを指定する方法として以下方法もあります。 しかし、authenticateは今では非推奨のようです。 client = MongoClient(host_name, port_num) client[db_name].authenticate(user_name, user_pwd) co = client[db_name][co_name] 最後に 何も考えずdockerでmongoDBを作ると認証を忘れがちだと思うので、認証をつけるよう気をつけましょう。 余談ですが、バージョンアップに従い非推奨Apiが増えていたので、公式ドキュメントは読まないといけないなと実感しました...(今更ですが)   参考 PyMongo Documentation
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

DjangoのJSONfieldのエラー:(fields.E180) SQLite does not support JSONFields.

環境 OS: Windows10 Python: 3.9.5 Django: 3.2.8 エラーが起きた状況 Djangoのmodels.pyにて、JSONを扱いたかったので、 models.py class Hoge(models.Model): hogefield = models.JSONField() として、 ファイルの変更後、例によって、 python manage.py makemigrations python manage.py migrate としたら、 hogeapp.Hoge: (fields.E180) SQLite does not support JSONFields と表示された。 原因 「JSON1 extension」を有効にしないといけないらしい。 公式に書いてあるとおりにすれば解決する。 (公式にも書いてあるけど)「JSON1 extension」が有効になっているかどうかは プロジェクトディレクトリ>python manage.py shell >>> import sqlite3 >>> conn = sqlite3.connect(':memory:') >>> cursor = conn.cursor() >>> cursor.execute('SELECT JSON(\'{"a": "b"}\')') を実行してみればわかる。 →何もエラーが出なければ有効 →エラーが出たら有効になっていない。 解決策 「原因」セクションでの確認用コードでエラーが起きるか確認してみて、エラーの場合は公式通りに解決する。 当たり前のことだが、Linux, macOS, Windowsで対処法が違うので注意。 ちなみに、pythonのバージョンが3.9以降ならエラーは起きないとのこと。 参考 https://code.djangoproject.com/wiki/JSON1Extension https://stackoverflow.com/questions/62637458/django-3-1-fields-e180-sqlite-does-not-support-jsonfields
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Raspberry Piで、ひかり電話の着信をSlack通知してみた

ひかり電話ルータ PR-400KI の着信をSlack通知してくれる仕組みを作りました。 少し改変すれば他のルータでも動作すると思います。 背景 我が家の電話はとてもシンプルで、不在着信を確認する手段がありません。 ただ、ルータの管理画面から通話ログを見ることはできるので、たまたま電話に出られなかったときはPCで確認していました。 とても不便です。 そんなとき、はるばる英国から小さくて強い子がやってきました。そう、Raspberry Pi です。 (まぁ pimoroni で自分が注文したんだけど) 管理画面をPythonでスクレイピングして、最強の着信通知を作ってみましょう。ファイッ! はじめに試したこと ルータの管理画面にアクセスして、通話ログのテキスト部分を取り出してみたいと思います。 まず使ってみたのは、ブラウザテスト自動化で使われる Selenium です。ヘッドレスのFirefoxをオートパイロットして、ルータ管理画面からリンクを辿り、XPathでログ部分だけを抜き出します。 # coding:utf-8 from selenium import webdriver browser = webdriver.Firefox() browser.get('http://user:YOUR_PASSWORD@192.168.1.1') # BASIC認証を通してブラウザ管理画面にアクセス information_link = browser.find_element_by_id('folder6') information_link.click() # 「情報」リンクをクリック call_log_link = browser.find_element_by_id('sub_folder6_3') call_log_link.click() # 「通話ログ」リンクをクリック call_log = browser.find_element_by_xpath('//pre') # preタグ(通話ログの場所)を取得 うまく取れはするのですが、読み込みタイミングの問題か、時々データを取れなくて例外を吐くようです。 途中にsleepを入れたら改善したものの、美しくないですね。 また、完全な機能をもつブラウザを起動させるため、処理が重いです。 次に試したこと 管理画面はJavaScriptで動的にコンテンツを生成しているわけでもないので、ヘッドレスブラウザは不要です。 PyCurlに書き換えました。 # coding:utf-8 import pycurl from io import BytesIO import lxml.html from lxml import etree from lxml.etree import XMLParser curl = pycurl.Curl() curl.setopt(pycurl.URL, "http://192.168.1.1/cgi-bin/mainte.cgi?st_clog") # 通話ログページを直接呼べたみたい curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) curl.setopt(pycurl.USERPWD, 'user:YOUR_PASSWORD') # BASIC認証 buffer = BytesIO() curl.setopt(curl.WRITEDATA, buffer) curl.perform() page = buffer.getvalue().decode('Shift_JIS') parser = XMLParser(ns_clean=True, recover=True) doc = etree.fromstring(page, parser) call_log = doc.xpath('//PRE/text()')[0] 重量級ブラウザが立ち上がらなくなりました。 結果、以下のようなテキストが取得できます。 CALL.LOG : There are 100 entries. 1. Sun Nov 21 00:04:09 2021 ********** Sun Nov 21 00:04:10 2021 0ABCDEFGHIJ 外線着信 ********** AUDIO - 0A0BCDEFGHIJ 203.0.113.1 NW 016 000 接続先切断 2. Sat Nov 20 23:55:16 2021 ********** Sat Nov 20 23:55:17 2021 0ABCDEFGHIJ 外線着信 ********** AUDIO - 0A0BCDEFGHIJ 203.0.113.1 NW 016 000 接続先切断 最初の2行を読み飛ばし、6行ごとに処理することで通話ログは処理できそうです。 ここには不在着信だけではなく、自分がかけた電話や通話が成立したものも含まれているため、見分けなくてはなりません。 不在着信は、以下の2つを満たすものです。 最初の行の中央の日付が ********** である = 通話が成立していない 2番目の行に「外線着信」と書かれている Slack通知 Incoming Webhook の URL に JSON を POST するとメッセージが投稿されます。 (Slack の Webhook は廃止予定なので新しい仕組みにしたいところです……) cron設定 03-59/10 7-23 * * * python3 /home/pi/notify_incoming_call.py > /dev/null 2>&1 cronで10分おきに起動させます。深夜帯は黙ってもらおうね。 このままだと同じ通知が何度も送られてしまうため、最後に処理した通話のタイムスタンプをファイルに記録する仕組みも実装しましょう。 これで外出先でも不在着信を確認できます。 完成したソース notify_incoming_call.py # coding:utf-8 import os import pycurl from io import BytesIO import lxml.html from lxml import etree from lxml.etree import XMLParser from datetime import datetime import re import json basedir = os.path.dirname(os.path.realpath(__file__)) # 最後に処理した通話の時刻を読み込み try: fr = open(basedir + '/.notify_incoming_call', 'r') recentlog = fr.readline()[:24] fr.close() except Exception as e: recentlog = 'Mon Nov 1 01:00:00 2021' curl = pycurl.Curl() curl.setopt(pycurl.URL, "http://192.168.1.1/cgi-bin/mainte.cgi?st_clog") curl.setopt(pycurl.HTTPAUTH, pycurl.HTTPAUTH_BASIC) curl.setopt(pycurl.USERPWD, 'user:YOUR_PASSWORD') buffer = BytesIO() curl.setopt(curl.WRITEDATA, buffer) curl.perform() #通話ログ全件取得 page = buffer.getvalue().decode('Shift_JIS') parser =XMLParser(ns_clean=True, recover=True) doc = etree.fromstring(page, parser) call_log = doc.xpath('//PRE/text()')[0] #最初の空行以降を読み込み call_log_main = call_log.split('\n\n')[1] lines_call_log_main = call_log_main.splitlines() #6行ずつ処理 regex = re.compile(r"^[0-9]+\. ") for i,line in enumerate(lines_call_log_main): if i%6==0: timestamps = regex.sub('',line.strip()) logdate = timestamps[:24] connect_date = timestamps[26:50] disconnect_date = timestamps[52:] if i%6==1: phone_number = line.strip().split()[0] call_type = line.strip().split()[1] if i%6==3: #相手側番号 peer_number = line.strip() #不在着信のみを処理 if connect_date.strip() != '**********': continue if peer_number == '-': peer_number = '非通知設定' else: # 電話番号があればリンクにする peer_number = '<tel:{}|{}>'.format(peer_number, peer_number) #外線着信のみを処理 if call_type != '外線着信': continue if datetime.strptime(recentlog, '%a %b %d %H:%M:%S %Y') < datetime.strptime(logdate, '%a %b %d %H:%M:%S %Y'): payload = {} payload['username'] = '不在着信' payload['icon_emoji'] = ':telephone:' #payload['channel'] = '{チャンネル名(指定する場合)}' payload['text'] = '{} から不在着信がありました\n{}'.format(peer_number,datetime.strptime(logdate, '%a %b %d %H:%M:%S %Y')) c = pycurl.Curl() c.setopt(pycurl.URL, 'https://hooks.slack.com/services/{WEBHOOKのURLを記載}') c.setopt(pycurl.POST, 1) c.setopt(pycurl.POSTFIELDS, "payload=" + json.dumps(payload)) c.perform() recentlog = call_log_main.split('\n', 1)[0][5:29] fw = open(basedir + '/.notify_incoming_call', 'w') fw.write(recentlog) fw.close() オチ 公式がメール通知を提供してんのかよ! (でもSlack通知の方が使いやすいし、月額料金もかからないのでいいよね)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pysparkの基礎、文法

はじめに 勉強中のPysparkについて、勉強したとこまでメモ。 実際にクラスタを構築して実装してみたかったが、若干敷居が高く感じられたので今回は楽にできるローカル(クラスタを構築しないモード)で行う。PySparkの文法練習自体はこれでも十分可能だった。 Pysparkバージョン:3.2.0 実行環境:Google Colab Sparkの概要 ●Hadoopについて Hadoopを構成する要素をかなり大雑把に書くとこんな感じになる。  Hadoopは、分散処理技術によって大規模データの扱いに長けたオープンソースのフレームワークである。テラバイトとかペタバイトあるデータを扱うことができる。  Hadoopを特徴づける最も重要なコンポーネントが、HDFS(Hadoop Distributed File System)と呼ばれる分散ファイルシステムである。HDFSは複数のサーバーのストレージを一つのファイルシステムとして運用できるシステムであり、下記のような利点がある。 ・スケーラビリティ:一つのストレージを扱うのと比べて容量や性能の向上が容易 ・耐障害性:複数のストレージに同じデータがレプリケーションされるため、耐障害性が高い ●分散処理アルゴリズムについて(SparkとMapReduce)  では、複数ストレージに分散したデータにアクセスし処理するには? HDFSにアクセスしデータを処理するためのアルゴリズムとして、SparkとMapReduceがある。元々はMapReduceのみだったが、後から出てきたSparkの利便性によってかなり追いやられているらしい。  MapReduceに対してSparkの優位な点としてざっくりと以下のような点がある。 ・処理が早い:調べた感じだとこれが一番の優位点になっている。一度処理したデータをメモリ上にキャッシュするため、繰り返し処理を行う機械学習などの処理に適している。 ・扱いやすい:SQLだけでなく、PythonやR、ScalaやJavaなどで実装できる。 ・様々な処理を実行できる:データの加工から分析まで幅広い工程の処理をオールインワンで実現できる。 このSparkをPythonで実装できるのがPysparkである。 Spark概要に関するより詳しい内容についてはこちらの記事がわかりやすかったです。 Pysparkの実装 ●RDD構文とDataFrame構文  SparkにはRDDとDataFrameという二つのプログラミング構文がある。 大きな違いとして、「スキーマ」の有無がある。DataFrameにはスキーマがあるが、RDDにはスキーマがない。そのためDataFrameではカラム名を利用した列指向の処理を行うことができるが、RDDではできない。またSQLライクに処理を記述できるなど、DataFrameの方がかなり扱いやすい模様。Pythonで比較した場合、DataFrameの方がパフォーマンスも良くなるらしい。RDDにも非構造化データに対する柔軟な処理が行えるといったメリットはあるようだが、基本的にDataFrameを使用するので大丈夫そうなので、今回はDataFrameを使用する。  ただ、DataFrameとRDDの立ち位置は並列なものではなく、RDDを使いやすくするため高級なAPIとして作られたのがDataFrameである。つまりDataFrameの構文を使うときに、ベースではRDDの処理が動いている。 ●Pyspark実装  Sparkはローカルの単一のコンピュータでも実装できる。実際に現場で使う際は基本的にクラスターを構築して使用することになると思うが、文法の理解やテストをする分には一つのコンピュータで実装できるのは楽で便利。デフォルトでローカルモードになっているので、そのまま使うことができる。  今回は一番簡単にできそうなGoogle Colab proで実装した。Google Colab proでの実装はめちゃくちゃ簡単で下記を実行するだけでPysparkを扱うことができるようになる。 !pip install pyspark titanicデータ分析 kaggleのtitanicデータで分類した。実際やってみて、SQLやpythonを使い慣れていると文法自体はかなりわかりやすいことがわかった。 ●SparkSessionの立ち上げ 最初にSparkSessionを立ち上げ、データを読み込む。データを読み込む際inferSchemaをTrueにすることで、それぞれのカラムのデータ型をいい感じにしてくれる。 #SparkSessionの立ち上げ from pyspark.sql import SparkSession spark = SparkSession.builder.appName('titanic').getOrCreate() #ファイルの読み込み df = spark.read.csv('ファイルのパス',header=True, inferSchema=True) ●EDA データの色々表示してみる。 まずはデータ自体を表示。 #データの表示 df.show() >>> +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+ |PassengerId|Survived|Pclass| Name| Sex| Age|SibSp|Parch| Ticket| Fare|Cabin|Embarked| +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+ | 1| 0| 3|Braund, Mr. Owen ...| male|22.0| 1| 0| A/5 21171| 7.25| null| S| | 2| 1| 1|Cumings, Mrs. Joh...|female|38.0| 1| 0| PC 17599|71.2833| C85| C| | 3| 1| 3|Heikkinen, Miss. ...|female|26.0| 0| 0|STON/O2. 3101282| 7.925| null| S| | 4| 1| 1|Futrelle, Mrs. Ja...|female|35.0| 1| 0| 113803| 53.1| C123| S| | 5| 0| 3|Allen, Mr. Willia...| male|35.0| 0| 0| 373450| 8.05| null| S| +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+ only showing top 5 rows データの構造、データ型を確認する。 # dfの構造を表示 df.printSchema() >>> root |-- PassengerId: integer (nullable = true) |-- Survived: integer (nullable = true) |-- Pclass: integer (nullable = true) |-- Name: string (nullable = true) |-- Sex: string (nullable = true) |-- Age: double (nullable = true) |-- SibSp: integer (nullable = true) |-- Parch: integer (nullable = true) |-- Ticket: string (nullable = true) |-- Fare: double (nullable = true) |-- Cabin: string (nullable = true) |-- Embarked: string (nullable = true) 統計量を確認する。 #統計量の表示 df.describe().show() >>> +-------+-----------------+-------------------+------------------+--------------------+------+------------------+------------------+-------------------+------------------+-----------------+-----+--------+ |summary| PassengerId| Survived| Pclass| Name| Sex| Age| SibSp| Parch| Ticket| Fare|Cabin|Embarked| +-------+-----------------+-------------------+------------------+--------------------+------+------------------+------------------+-------------------+------------------+-----------------+-----+--------+ | count| 891| 891| 891| 891| 891| 714| 891| 891| 891| 891| 204| 889| | mean| 446.0| 0.3838383838383838| 2.308641975308642| null| null| 29.69911764705882|0.5230078563411896|0.38159371492704824|260318.54916792738| 32.2042079685746| null| null| | stddev|257.3538420152301|0.48659245426485753|0.8360712409770491| null| null|14.526497332334035|1.1027434322934315| 0.8060572211299488|471609.26868834975|49.69342859718089| null| null| | min| 1| 0| 1|"Andersson, Mr. A...|female| 0.42| 0| 0| 110152| 0.0| A10| C| | max| 891| 1| 3|van Melkebeke, Mr...| male| 80.0| 8| 6| WE/P 5735| 512.3292| T| S| +-------+-----------------+-------------------+------------------+--------------------+------+------------------+------------------+-------------------+------------------+-----------------+-----+--------+ ●前処理 ・欠損値の処理  上記のdescribe()のcountの値が他のカラムの891より少ないことから、Age、Cabin、Embarkedの列に欠損値があることがわかる。  列ごとの欠損値の数を確認するには以下を実行する。 df.filter(df['Age'].isNull()).count() >>> 177  欠損値の補完を行う。Age列は中央値で補完、Cabinは欠損が多いので列ごと削除(というか使わないので放置で良い)、Embarkedは量が少ないので欠損がある行を削除する。 まず、Age列の補完を行う。二通りのやり方でやってみる。 #中央値の算出 from pyspark.sql.functions import percentile_approx age_med = df.select(percentile_approx('Age', 0.5)).collect()[0][0] #28 #中央値で補完 df_fill = df.na.fill({'Age':age_mod}) または、Imputerを用いる方法がある。Pipeline使ったりする場合はこっちになる。 outputColの名前は何でも良い。こっちの方法でやると、outputColで指定した"Age_impute"という新しいカラムが増えることになる。 from pyspark.ml.feature import Imputer imputer = Imputer(strategy='median', inputCol='Age', outputCol='Age_impute') model = imputer.fit(df) df_fill2 = model.transform(df)    次にEmbarked列の欠損がある列を削除する。 df_fill = df_fill.na.drop(subset=['Embarked']) ・カテゴリ変数の処理  Sex列とEmbarked列に対してOne-Hotエンコードを行う。SparkのOneHotEncoderは数値しか扱うことができないため、StringIndexerでString列をラベルエンコードした後OneHotエンコードを行う。 OneHotして最終的に出てくるカラムはベクトル列になっている。 from pyspark.ml.feature import OneHotEncoder, StringIndexer #先にラベルエンコードを行う indexer = StringIndexer(inputCols=['Sex', 'Embarked'], outputCols=['Sex_index','Embarked_index']) index_model = indexer.fit(df_fill) df_fill = index_model.transform(df_fill) #One-Hotエンコードを行う onehot = OneHotEncoder(inputCols=['Sex_index', 'Embarked_index'], outputCols=['Sex_vec','Embarked_vec']) df_fill = onehot.fit(df_fill).transform(df_fill) df_fill.show() >>> +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+---------+--------------+-------------+-------------+ |PassengerId|Survived|Pclass| Name| Sex| Age|SibSp|Parch| Ticket| Fare|Cabin|Embarked|Sex_index|Embarked_index| Sex_vec| Embarked_vec| +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+---------+--------------+-------------+-------------+ | 1| 0| 3|Braund, Mr. Owen ...| male|22.0| 1| 0| A/5 21171| 7.25| null| S| 0.0| 0.0|(1,[0],[1.0])|(2,[0],[1.0])| | 2| 1| 1|Cumings, Mrs. Joh...|female|38.0| 1| 0| PC 17599|71.2833| C85| C| 1.0| 1.0| (1,[],[])|(2,[1],[1.0])| | 3| 1| 3|Heikkinen, Miss. ...|female|26.0| 0| 0|STON/O2. 3101282| 7.925| null| S| 1.0| 0.0| (1,[],[])|(2,[0],[1.0])| | 4| 1| 1|Futrelle, Mrs. Ja...|female|35.0| 1| 0| 113803| 53.1| C123| S| 1.0| 0.0| (1,[],[])|(2,[0],[1.0])| | 5| 0| 3|Allen, Mr. Willia...| male|35.0| 0| 0| 373450| 8.05| null| S| 0.0| 0.0|(1,[0],[1.0])|(2,[0],[1.0])| +-----------+--------+------+--------------------+------+----+-----+-----+----------------+-------+-----+--------+---------+--------------+-------------+-------------+ ●ロジスティック回帰分析 ・特徴量列をまとめる  前処理が終わったのでロジスティック回帰を行う。しかしその前に、現状複数列ある特徴量列を一つのベクトル列にまとめる必要がある。Pysparkでは最終的にモデルに入れる時の形が、「特徴量のベクトル列」と「ラベル列」の二列になるためである。  特徴量列はVectorAssemblerを用いて「features」という名前のベクトル列にまとめ、最終的に特徴量列とラベル列(今回はSurvived)を取り出す。 from pyspark.ml.feature import VectorAssembler usecol = ['Pclass', 'Age','SibSp','Parch','Fare', 'Sex_vec','Embarked_vec'] assembler = VectorAssembler(inputCols = usecol, outputCol = 'features') output = assembler.transform(df_fill) final_data = output.select('features', 'Survived') final_data.show(5) >>> +--------------------+--------+ | features|Survived| +--------------------+--------+ |[3.0,22.0,1.0,0.0...| 0| |[1.0,38.0,1.0,0.0...| 1| |(8,[0,1,4,6],[3.0...| 1| |[1.0,35.0,1.0,0.0...| 1| |[3.0,35.0,0.0,0.0...| 0| +--------------------+--------+ only showing top 5 rows ・trainデータとtestデータに分ける  このデータをtrainデータとtestデータに分ける。 seedに数字を入れることで、RandomSeedを指定することができる。 train_data, test_data = final_data.randomSplit([0.7,0.3], seed=1) ロジスティック回帰で学習  あとは学習機にかけるだけでできる。ちなみに今回データに対する標準化を行っていないが、Sparkのロジスティック回帰はデフォルトで標準化処理を勝手に行ってくれるので、別で行う必要がない。  パラメータとかについてはこちらのドキュメントを参照。  学習したモデルでtransformを実行すると、予測値(prediction)やその確率(probability)を得ることができる。 from pyspark.ml.classification import LogisticRegression lr = LogisticRegression(featuresCol='features', labelCol='Survived', predictionCol='prediction') lr_model = lr.fit(train_data) test_pred = lr_model.transform(test_data) test_pred.show(5) >>> +--------------------+--------+--------------------+--------------------+----------+ | features|Survived| rawPrediction| probability|prediction| +--------------------+--------+--------------------+--------------------+----------+ |(8,[0,1,2,4],[3.0...| 1|[-0.4218296937258...|[0.39607900350312...| 1.0| |(8,[0,1,4],[2.0,2...| 1|[-1.9299810466149...|[0.12675267798995...| 1.0| |(8,[0,1,4],[3.0,1...| 1|[-1.1683177702197...|[0.23715918980462...| 1.0| |(8,[0,1,4],[3.0,2...| 1|[-1.0367914153313...|[0.26176956678286...| 1.0| |(8,[0,1,4],[3.0,2...| 0|[-0.7744001109292...|[0.31552804343796...| 1.0| +--------------------+--------+--------------------+--------------------+----------+ only showing top 5 rows  最後に結果の評価を行う。分類問題の評価には、BinaryClassificationEvaluatorまたはMulticlassClassificationEvaluatorを使用する。ちなみにaccuracyはmulticlassの方にしかない。   from pyspark.ml.evaluation import MulticlassClassificationEvaluator multi_eval = MulticlassClassificationEvaluator(metricName='accuracy',predictionCol='prediction',labelCol='Survived') multi_eval.evaluate(test_pred) >>> 0.7969348659003831  取り敢えず、Pysparkのみで前処理や分類予測ができた。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

google colab をセクシーにする方法

1.はじめに  最近見た google colab にこんなGIFが入っていて、とてもセクシーだった(個人的な見解です?)ので、やり方を調べてみました。 2.やり方   google colab のテキストに、github 上にあるGIFのパスを下記のように指定します。名前は何でもOKです。 具体例で言うと、github 上にあるGIFのパスが下記のようであれば https://github.com/PeterL1n/RobustVideoMatting/blob/master/documentation/image/teaser.gif リンクの指定をして、github.com を raw.githubusercontent.com に置き換え、blob を削除すれば良いです ![Teaser](https://raw.githubusercontent.com/PeterL1n/RobustVideoMatting/master/documentation/image/teaser.gif) とても簡単!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む