Python から PostgreSQL を操作する
Python はデータの解析で使われることが多く、様々な記事で解析の方法や結果の可視化手法が紹介されています。多くの記事では構造化データを扱う例として CSV を pandas を使って解析していますよね。しかし、扱うデータが非常に多い場合にはデータベースを叩いてほしいデータを取り出して解析するということも往々にして行われています。この記事ではデータベースとして有名な PostgreSQL を、Python から操作する方法について紹介したいと思います。
動作環境
- Ubuntu 20.04
- Python 3.8.2
- PostgreSQL 12.4
データベースの準備
PostgreSQL の Ubuntu へのインストールは以下の記事を参考にしました。
Debian用の記事ですが apt install
を使うだけなので問題はありませんでした。無事インストール出来たか確認します。
psql -V # psql (PostgreSQL) 12.4 (Ubuntu 12.4-0ubuntu0.20.04.1)
上のようにバージョンがちゃんと表示されていればOKです。次にデータベースを1つ作ります。
createdb mydb psql mydb # 接続確認 mydb=>\l # 存在するデータベースのリストを表示 List of databases Name | Owner | Encoding | Collate | Ctype | Access privileges -----------+----------+----------+-------------+-------------+----------------------- mydb | pyhaya | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | postgres | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | template0 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres template1 | postgres | UTF8 | ja_JP.UTF-8 | ja_JP.UTF-8 | =c/postgres + | | | | | postgres=CTc/postgres
テーブルは Python で作ることもできますが、1回しか実行しないものなので SQL でやっておきます。
CREATE TABLE repo ( id INTEGER, node_id VARCHAR(100), name VARCHAR(100), full_name VARCHAR(100), owner VARCHAR(100), private BOOLEAN, PRIMARY KEY(id) );
このテーブルは後に GitHub API を使ってリポジトリの情報を取ることを想定しています。APIの使い方は下のリンクを参考にしてください。
docs.github.com
この SQL を実行するには
mydb=>\i create_table.sql
とします。
Python から PostgreSQL を操作する
import psycopg2 import dotenv import os import requests from requests.auth import HTTPBasicAuth def get_repos(): auth = HTTPBasicAuth( os.environ.get("GITHUB_USERNAME"), os.environ.get("GITHUB_TOKEN") ) data = requests.get("https://api.github.com/users/(USER_NAME)/repos", auth=auth) data = data.json() return data[0] def connect(user, dbname, password): return psycopg2.connect( " user=" + user + " dbname=" + dbname + " password=" + password ) if __name__ == "__main__": dotenv.load_dotenv() user = "pyhaya" dbname = "mydb" password = os.environ.get("PG_PASSWORD") data = get_repos() ID = data["id"] node_id = data["node_id"] full_name = data["full_name"] owner = data["owner"]["login"] private = data["private"] query = ( "INSERT INTO repo (id, node_id, full_name, owner, private) VALUES" + f" ({ID}, '{node_id}', '{full_name}', '{owner}', {private});" ) with connect(user, dbname, password) as conn: with conn.cursor() as cur: cur.execute(query) conn.commit()
データベースへの接続のために with
を使っています。これを使わずに操作の終了後にコネクションを connect.close()
のようにすることもできますが、with
を使ったほうが安全なのでこちらを使うようにします。
ちゃんとデータベースに入ったか確認してみます。
mydb=> SELECT id, node_id, full_name, owner, private FROM repo; id | node_id | full_name | owner | private -----------+----------------------------------+-----------------------------+---------------+--------- 251618774 | MDEwOlJlcG9zaXRvcnkyNTE2MTg3NzQ= | pyhaya/analysis_memo | pyhaya | f (1 row)
ちゃんと入っていることが確認できました。
AtCoder Beginners Contest 128 C. Switches を解いたのでメモ
久しくブログを更新していなかった気がするので、AtCoder の過去問を解いた記録でも書いてみる。AtCoder Beginners Contest (通称 ABC) 128 の C 問題を解いてみたら再帰関数の良い練習になったのでこれを紹介します。
問題文は下のリンクを参照。
解く方針
問題文を読んで最初に思ったのが、変数が多いということ。N がスイッチの数で M が電球の数、k が各電球につながっているスイッチの数で、sが各電球につながっているスイッチの番号、pが各電球の点灯条件。。。頭ごっちゃになりそう。変数の持ち方を考えるが、あまり深く考えずに N, M は int
、s は多次元ベクトルでまとめて、p もベクトルとして持つことにする。
N, M も大きくなっても 10 までなのでスイッチの状態を全列挙してそれぞれの状態でスイッチが全部つくかどうかを調べることにする。
コード
Step 1
ということで早速、解答の雛形を書いてみる。
#include <bits/stdc++.h> typedef long long ll; using namespace std; int N, M; vector<vector<int>> info; int main() { // N: switch // M: light cin >> N >> M; vector<int> P(M); for (int i = 0; i < M; i++) { int k; cin >> k; vector<int> tmp(k); for (int j = 0; j < k; j++) { int a; cin >> a; tmp[j] = a - 1; } info.push_back(tmp); } for (int i = 0; i < M; i++) { cin >> P[i]; } vector<int> init_state(N, 0); // present state of switches int res = calc(0, P, init_state); cout << res << endl; }
このコードではデータの読み込みしか書いてない。具体的な計算は calc
に丸投げしてる。書くことがあるとすれば電球の番号が1始まりで与えられていて扱いづらいので0始まりにしていることくらい (19 行目)。あとは N, M, info をグローバル変数にしていることくらいか。これは単純に calc
にたくさん引数を与えるのが面倒という理由しか無い。
Step 2
calc
関数を書いていく。
int calc(int now_idx, vector<int> P, vector<int> now_state) { if (now_idx == N) { return is_valid(P, now_state); } vector<int> new_state; // state that 'now_idx'th switch is on. for (int i = 0; i < now_state.size(); i++) { if(i == now_idx) new_state.push_back(1); else new_state.push_back(now_state[i]); } return calc(now_idx + 1, P, now_state) + calc(now_idx + 1, P, new_state); }
よくある再帰関数の形。main()
からは now_state
として 0 のみを要素に持つベクトルが与えられる。これは全部のスイッチがoffである状態。スイッチの状態の総数は最初のスイッチが off の場合と on の場合の和になることを利用している。終端条件は、最後のスイッチの on/off を決めた時でこのときに return 1;
とすれば単純にスイッチの状態の総数が返るようになる。しかしこの問題では状態に条件がついていて、このスイッチの状態で全ての電球が点灯していなければならない。なのでここでは単純に 1 を返さずにもうワンステップ噛ませる。この判定は is_valid
に任せる。
Step 3
int is_valid(vector<int> P, vector<int> state) { // P: how to turn on the lights map<int, int> search; for (int i = 0; i < state.size(); i++) { if (state[i] == 1) search[i] = 1; } vector<int> P_copy; copy(P.begin(), P.end(), back_inserter(P_copy)); for (int i = 0; i < M; i++) { for (int j = 0; j < info[i].size(); j++) { if (search[info[i][j]]) P_copy[i] = !P_copy[i]; } } int count = 0; for (int i = 0; i < P_copy.size(); i++) { if (P_copy[i] == 0) count++; } return count == P.size(); }
各電球について、つながっているスイッチの中で on になっている数を数えてそれを2で割ったあまりが点灯条件を満たすか調べる。1つ1つの電球に対してつながっているスイッチの on/off をベクトルから調べるのは面倒なので最初に search
というマップにして持つ。
あとは「on になっている数を数えてそれを2で割ったあまりが点灯条件を満たすか調べる」部分だが、これも簡単にかけないか考えると、P の値を スイッチが on であるたびに 0, 1 反転させて 0 になっていればその電球はついていることに気づく。どういうことかというと、ある電球が on になっているスイッチが奇数のときにつくとすると、この電球に対応する p の値は 1 である。この電球につながっている3つのスイッチが on になっているとして p を 3 回反転させると 0 になる。2回なら 1 になる。逆にスイッチの数が偶数のときに電球がつくとするとp = 0 で、2つのスイッチが on になっている状態では p を2回反転させると 0、3つだと 1 になる。つまり電球のつく条件がどちらでも on になっているスイッチの数だけ p の値を反転させるたときに p が 1になっていれば消えているし、0 になっていればついている。これをコードにしているのが 13 行目。
全体のコード
以上のコードをまとめると下のようになる。
#include <bits/stdc++.h> typedef long long ll; using namespace std; int N, M; vector<vector<int>> info; int is_valid(vector<int> P, vector<int> state) { // P: how to turn on the lights map<int, int> search; for (int i = 0; i < state.size(); i++) { if (state[i] == 1) search[i] = 1; } vector<int> P_copy; copy(P.begin(), P.end(), back_inserter(P_copy)); for (int i = 0; i < M; i++) { for (int j = 0; j < info[i].size(); j++) { if (search[info[i][j]]) P_copy[i] = !P_copy[i]; } } int count = 0; for (int i = 0; i < P_copy.size(); i++) { if (P_copy[i] == 0) count++; } return count == P.size(); } int calc(int now_idx, vector<int> P, vector<int> now_state) { if (now_idx == N) { return is_valid(P, now_state); } vector<int> new_state; for (int i = 0; i < now_state.size(); i++) { if(i == now_idx) new_state.push_back(1); else new_state.push_back(now_state[i]); } return calc(now_idx + 1, P, now_state) + calc(now_idx + 1, P, new_state); } int main() { // N: switch // M: light cin >> N >> M; vector<int> P(M); for (int i = 0; i < M; i++) { int k; cin >> k; vector<int> tmp(k); for (int j = 0; j < k; j++) { int a; cin >> a; tmp[j] = a - 1; } info.push_back(tmp); } for (int i = 0; i < M; i++) { cin >> P[i]; } vector<int> init_state(N, 0); // present state of switches int res = calc(0, P, init_state); cout << res << endl; }
Union Find 木の実装
最近 AtCoder の過去問を解いていて、Union Find 木を使うと解くことができる問題に出会いました。これまで Union Find 木は使ったことがなく、聞いたことがあるくらいだったのでこの問題を解くために勉強しました。この記事では Union Find 木がどのようなデータ構造で、どんなことに使えるのかを説明したいと思います。
Union Find 木はどんなデータ構造か
名前にあるように、 Union Find 木は木構造です。つまり、親要素があって、それに連なる子要素がいくつかあるような構造をしています。木構造として有名なものに二分木やそれの特殊なものとしての二分探索木などがありますが、これらに対して Union Find 木は複数の木構造の集合からなるという特徴があります。
同じ木に属している要素は「同じグループ」に属していると解釈されます。つまり Union Find 木は特定の要素が他の要素と同じグループに属しているかどうかを判定するのに適しています。
Union Find 木の実装
Union Find 木を C++ で実装してみます。
struct UnionFind { vector<int> par; UnionFind(int N) : par(N) { for (int i = 0; i < N; i++) par[i] = i; } int root(int x) { if (par[x] == x) return x; return par[x] = root(par[x]); } void unite(int x, int y) { int rx = root(x); int ry = root(y); if (rx == ry) return; par[rx] = ry; } bool same(int x, int y) { return root(x) == root(y); } };
UnionFind
という名前の構造体を定義します。この構造体は par
という要素をもっており、par
はそれぞれの要素の親要素をもっています。
初期化
簡単のために考える要素は0 ~ N-1 までの連続する整数であるとして話を進めます。最初に、Union Find 木を初期化するときには全ての要素の親要素が自分自身 (par[i] = i
) になるように初期化します。
一番上の親要素の取得
特定の要素の属する木の最も上にいる親要素を取得するメソッドを定義します。par[x]
で x と直接つながっている親を取得できるので、これを再帰的にたどっていけば一番上の親に到達できます。
要素の結合
要素 x と要素 y が同じグループに属するとして結合します。x 属する木に y を結合するとして話を進めていきます。これは非常に簡単で、 y の親を x の親にすればよいです。
fast.ai をサクッと動かす
機械学習のブーム到来によって、今までに様々なフレームワークが開発されてきました。有名なのは Google が開発している Tensorflow と Facebook が開発している PyTorch ですね。さらにこれらのフレームワークを手軽に試せるようなフレームワークも次々と登場してきました。Tensorflow ですと Keras が Tensorflow をバックエンドに使うようできます。PyTorch をバックエンドに使うフレームワークとして有名なのが fast.ai です。fast.ai は下のようなスローガンを掲げており、なるべく多くの人に深層学習に触れられる機会を与えることを目指していて、実際非常に使いやすいフレームワークです。
Being cool is about being exclusive, and that’s the opposite of what we want. We want to make deep learning as accessible as possible
この記事では、fast.ai の基本的な使い方について紹介したいと思います。
この記事で扱うこと
- 動物の画像分類
- ハイパーパラメータの調整
- モデルの fine tune
データの準備
import fastai2.vision.all as fastai path = fastai.untar_data(fastai.URLs.PETS) pets = fastai.DataBlock( blocks = (fastai.ImageBlock, fastai.CategoryBlock), get_items = fastai.get_image_files, splitter = fastai.RandomSplitter(seed=42), get_y = fastai.using_attr(fastai.RegexLabeller(r"(.+)_\d+.jpg"), "name"), item_tfms = fastai.Resize(460), batch_tfms = fastai.aug_transforms(size=224, min_scale=0.75), ) dls = pets.dataloaders(path/"images")
fast.ai に最初から用意されているペット画像を使って画像分類を試して見ます。fast.ai ではインポートにワイルドカード(*
) を使うことが多いようですが、ここではわかりやすさのために fastai.vision.all
を fastai
という名前でインポートします。
読み込むデータは、入力値が画像データ(ImageBlock
) で 出力値がカテゴリ(CategoryBlock
) です。カテゴリ名はファイル名から正規表現を使って抽出します(RegexLabeller(r"(.+)_\d+.jpg"), "name")
)。さらに、画像の前処理としてサイズを揃え(Resize(460)
)、augmentation をします(aug_transforms(size=224, min_scale=0.75)
)。
きちんとデータが読み込まれたか確認してみます。
dls.show_batch(rows=2, cols=3)
うまく行っていれば下のように動物の写真が6枚表示されます。
また、DataBlock
でどレくらいの画像が読み込まれ、どのような処理がなされたかは summary()
メソッドを使って確認することができます。今、path
には動物画像のデータセットを指定していますが、実際の画像はサブディレクトリ images
に入っているので summary()
メソッドの引数には path/"images"
で指定します。
pets.summary(path/"images")
出力が長いので一部を載せると下のような感じになります。
Setting-up type transforms pipelines Collecting items from ~/.fastai/data/oxford-iiit-pet/images Found 7390 items 2 datasets of sizes 5912,1478 Setting up Pipeline: PILBase.create Setting up Pipeline: partial -> Categorize Building one sample Pipeline: PILBase.create starting from ~/.fastai/data/oxford-iiit-pet/images/american_bulldog_30.jpg applying PILBase.create gives PILImage mode=RGB size=334x500 Pipeline: partial -> Categorize starting from ~/.fastai/data/oxford-iiit-pet/images/american_bulldog_30.jpg applying partial gives american_bulldog applying Categorize gives TensorCategory(12) Final sample: (PILImage mode=RGB size=334x500, TensorCategory(12)) Setting up after_item: Pipeline: Resize -> ToTensor Setting up before_batch: Pipeline: Setting up after_batch: Pipeline: IntToFloatTensor -> AffineCoordTfm -> RandomResizedCropGPU -> LightingTfm Building one batch Applying item_tfms to the first sample: Pipeline: Resize -> ToTensor starting from (PILImage mode=RGB size=334x500, TensorCategory(12)) applying Resize gives ...
訓練してみる
データは読み込めたので、訓練をしてみます。まずは試しに ResNet34 の学習済みモデルをfine tuneしてみます。
learn = fastai.cnn_learner(dls, fastai.resnet34, metrics=fastai.error_rate)
learn.fine_tune(2)
pandas の DataFrame を表示するときのようなデザインで各エポックの訓練状況が表示されます。さて訓練結果はどうなっているでしょうか?混同行列を見てみます。
interp = fastai.ClassificationInterpretation.from_learner(learn) interp.plot_confusion_matrix(figsize=(12, 12), dpi=60)
2行のコードで seaborn を使ったときのようなきれいなグラフが表示されます。
ハイパーパラメータの調整
ハイパーパラメータの調整も、fast.ai ではそれを支援する機能があります。
lr_min, lr_steep = learn.lr_find()
この一つのコマンドで、学習率を変化させたときの loss の変化を調べることができます。loss が最も急激に減少する学習率と、最も小さくなる学習率が lr_steep, lr_min
に入ります。これと同時に、loss の学習率依存性のグラフが表示されます。
モデルの細かい調整
モデルによっては、学習率を層によって変化させたいこともあります。fast.ai ではこのようなことも簡単にできます。
learn.fit_one_cycle(12, lr_max=slice(1e-6, 1e-4))
lr_max
にスライスを指定することによって学習率を 1e-6 から 1e-4 まで変化させていくことが可能です。
また、モデルの学習を高速化させ、メモリ消費を抑える手法として混合精度の訓練があります。これも簡単に実現できます。
learn = fastai.cnn_learner(dls, fastai.resnet50, metrics=fastai.error_rate).to_fp16()
最後に、この混合精度モデルで、最初の3エポックは fine tune して、その後に全てのパラメータをフリーにして訓練してみます。
learn.fine_tune(6, freeze_epochs=3) learn.recorder.plot_loss()
まとめ
この記事では、fast.ai の初歩的な使い方をザッと見てきました。データセットの準備や訓練が簡単にできることはもちろん、モデルの調整や訓練結果の確認のためのツールも非常に豊富にもっていることがわかると思います。私もまだ触り始めたばかりですがこれからどんどん使っていきたいと思います。
fastai の Deep Learning 本(Draft)を実行する環境をGCPでそろえる
最近、fastai が深層学習の本のドラフトを GitHub で公開して話題になりました。
github.com
中身を見てみると、Jupyter Notebook 形式で書かれており、説明を読みながらその場で実行することが可能になっておりすごく便利そうです。この記事では、この良質な教材を使って深層学習を勉強するための環境構築について書きます。
構築する環境の概要
深層学習を学ぶにあたって一番のボトルネックとなるのがマシンスペックです。学習が進むにつれて GPU がなければ実行が遅すぎる、もしくはメモリが足りなくてそもそも実行できないという例が出てきます。
実際に教材のイントロダクションにも以下のように GPU を用いることが強く推奨されています。
01_intro.ipynb
Getting a GPU deep learning server
To do nearly everything in this book, you'll need access to a computer with an NVIDIA GPU (unfortunately other brands of GPU are not fully supported by the main deep learning libraries). However, we don't recommend you buy one; in fact, even if you already have one, we don't suggest you use it just yet! Setting up a computer takes time and energy, and you want all your energy to focus on deep learning right now. Therefore, we instead suggest you rent access to a computer that already has everything you need preinstalled and ready to go. Costs can be as little as US$0.25 per hour while you're using it, and some options are even free.
ここにも書いてあるように NVIDIA GPU を買ってくる必要はなく、クラウドサービスを利用する方が簡単に、かつ安く環境を構築することができます。私は NVIDIA の RTX 2080 をもっているので最初はこれでやろうと思っていたのですが結構最初のところでメモリが足りないと怒られたのでクラウドサービスを利用することにしました。そこでこの記事では以下のような環境構築を行います。
環境構築
下準備
では早速、環境構築を行っていきます。GCP を使うので課金を有効にしておきます。
cloud.google.com
また、GPU も使うので GPU の割り当ての設定も確認しておきます。
cloud.google.com
何もしていない状況だと GPU の最大割り当て数が 0 になっていると思うのでこうなっている場合には上のリンクに書いてある手順で増やしてもらえるように申請します。
インスタンスの作成
これらの設定を行った後には、インスタンスの作成を行っていきます。GCP のページの左メニューから「Compute Engine」-> 「VM インスタンス」を選びます。
VM インスタンスのページの左下に「Marketplace」があると思うので、そこをクリックして出てきたウィンドウ内で「deep learning vm」を検索します。するとこんな感じのものが検索にかかると思います。
「LAUNCH」 をクリックしてインスタンスの作成画面に遷移すると、マシンスペック等を調整する画面が出てきます。こんな感じです。
名前を適当にセットして、GPU の種類を選択します。どれを使うかは財布と相談しましょう(Tesla T4 あたりがいいかも)。1か月の推定料金は右に出ますがこれは24時間、1か月の間インスタンスを使い続けた場合の料金みたい(?)なので個人的な勉強用途の場合にはもう少し安くなると思います。画像にあるような Tesla K80 を使った場合には 一時間料金は $0.4 らしいので一日2時間、週5日使うとおおよそ $16 くらいかと思います。CPUやリージョンの変更もできますが、CPU の構成やリージョンによっては特定の GPU を使えない場合もあるので注意しましょう。
Framework はこの fastai の教材の勉強用には 「PyTorch 1.3 + fast.ai (CUDA 10.0)」を選びましょう。ただし教材では 「PyTorch 1.4」を使うのでインスタンス作成後にアップデートします(後述)。
下の方を見ていくといろいろと書いてありますが、よく見ると「Install NVIDIA GPU driver automatically on first startup?」と書いてあるチェックボックスがあります。これにチェックを入れておけばインスタンス作成時に面倒な GPU のドライバインストールを済ませておいてくれるのでチェックを入れておくことを推奨します。
すべての設定が終わったら「デプロイ」をクリックしてインスタンスを作成します。しばらく待てばインスタンスの作成は終了です。
環境の設定
ここからは Cloud SDK を使ってローカルから インスタンスに SSH 接続します。 Cloud SDK のインストール等は以下のリンクを参照してください。
接続する際には、ローカルの 8080 番ポートに インスタンスの 8080 番ポートを接続するようにしてみます。
gcloud compute ssh --zone $ZONE $INSTANCE_NAME -- -L 8080:localhost:8080
$ZONE
と $INSTANCE_NAME
に関しては自分で作ったインスタンスのものに置き換えてください。接続できたら、シェルで
jupyter lab
と打ちます。すると Jupyter Lab が立ち上がりますので ローカルで https://localhost:8080
にアクセスしてみて Jupyter Lab のページが表示されるか確認してください。
では、ここから fast.ai の教材のためのセットアップをしていきます。インスタンスに Git は最初から入っているので、Jupyter Lab からシェルを開いて、
git clone https://github.com/fastai/fastbook.git cd fastbook
としてクローンします。そうしたら必要なパッケージを入れていきます。最初に注意しなければいけないのは、このインスタンスには nvidia-ml-py3
というパッケージが無いことです。これは普通 CUDA を入れると標準で入ってくるものらしいのですが、 GCP のインスタンスにはこれが入っていないようです。これを入れたあとにはまず、PyTorch を 1.4 にアップグレードし、その後に残りの必要なパッケージを入れていくという手順で行きます。まとめると、
pip install nvidia-ml-py3 pip install -U torch pip install -r requirements.txt
これで完了です!
ここまでくれば、あとは普段 Jupyter Lab を使うときと同じです。インスタンスを起動している間は課金の対象になるのでしばらく使わないときには停止をするのを忘れないようにしてください。では、深層学習の勉強をがんばりましょう!!
Rust で テキストファイルからデータを読み出す Python 拡張を書いたら爆速だった話
普段、実験をしていて得られるデータを np.loadtxt
で読んでいるが、ためしにこの部分を Rust で書いて Python から呼び出してみたら速かったという話を書きます。
最近こんな記事を書きました。
pyhaya.hatenablog.com
この記事では、Rust でテキストデータを読み込んだときにどれくらいの速度が出るのだろうということを試してみた記事です。この中で、最後に Python の numpy との比較をしているのですが、ここで結構 Rust と numpy の間に差ができており、Rust で Python 拡張を書いたときにどれくらいスピードが出るのか気になりました。
Rust から Python 拡張を書くのはそんなに難しくないので、試してみた結果をここに書くことにしました。
環境
rustup を入れていれば nightly は以下の記事のように簡単にインストールすることができます。
qiita.com
準備
適当なディレクトリで次のコマンドを打ちます。("speed" はディレクトリの名前)
cargo new --lib speed
生成されたディレクトリ内の Cargo.toml
を修正する。
[package] name = "speed" version = "0.1.0" authors = ["Your name <mail address>"] edition = "2018" [lib] name="speed" crate-type=["cdylib"] [dependencies] [dependencies.pyo3] version="0.8" features=["extension-module"]
コード
Numpy と同じく、loadtxt()
という名前の関数を定義して Rust で書き下します。このとき、返り値は PyResult
型である必要があることに注意します。
src/lib.rs
use pyo3::prelude::*; use std::fs::read_to_string; #[pymodule(speed)] fn loadtxt(py: Python, m: &PyModule) -> PyResult<()> { #[pyfn(m, "loadtxt")] fn loadtxt(_py: Python, filename: &str) -> PyResult<(Vec<f32>, Vec<f32>)> { let data = read_to_string(filename); let xy = match data { Ok(content) => content, Err(error) => { panic!("Could not open file: {}", error); } }; let xy_pairs: Vec<&str> = xy.trim().split("\n").collect(); let mut x: Vec<f32> = Vec::new(); let mut y: Vec<f32> = Vec::new(); for pair in xy_pairs { let p: Vec<&str> = pair.trim().split(" ").collect(); x.push(p[0].parse().unwrap()); y.push(p[1].parse().unwrap()); } Ok((x, y)) } Ok(()) }
これをビルドすれば、Python側から呼び出せるバイナリデータが出来上がります。
cargo build --release
速度比較
target/release 内に含まれる libspeed.so
を speed.so
に名前を変えてわかりやすいように別のディレクトリにコピーします。そのディレクトリで Python ファイルを作って速度を比較してみました。
まず、読み出すファイルを作成します。999999行のデータです。
data = "" num = 1000000 for i in range(num): data += f"{i} {i*2}" if i < num - 1: data += "\n" with open("example.txt", "w") as f: f.write(data)
では、実際に速度を比較してみます。
from speed import loadtxt import numpy as np import time if __name__ == "__main__": filename = "example.txt" # Rust t1 = time.perf_counter() x, y = loadtxt(filename) print("Rust time: ", time.perf_counter() - t1) # Numpy t2 = time.perf_counter() x, y = np.loadtxt(filename).T print("Numpy time: ", time.perf_counter() - t2)
Rust time: 0.29457211199951416 Numpy time: 3.338321666000411
Rust で書いたほうは Numpy よりも 10 倍以上速いという結果になりました。
まとめ
この記事では、テキストデータを Python で読み出すためにNumpyのloadtxt
を使った場合と、Rustで自作の拡張を作った場合の2通りのコードを書き、速度の比較を行いました。その結果、Rustのほうが 10 倍以上速いという結果になりました。ただ、Rust の方は読み出すデータが2列からなるということを知った上で書いてあるので、そのせいで Numpy より早くなっているという可能性はあるかも知れません。今後はそこらへんも検証していきたいと思っています。
- 作者: κeen,河野達也,小松礼人
- 出版社/メーカー: 技術評論社
- 発売日: 2019/05/08
- メディア: 単行本(ソフトカバー)
- この商品を含むブログを見る
Dell XPS 13 7390 のレビュー
XPS 13 7390 が届いたのでざっくりとレビューしてみます。外見の比較対象として今まで使っていた Dell Inspiron 13 7000 series を出しています。
(2019/09/29 追記)ストレージテストと Core-i7 10510U のパフォーマンステストの結果を載せました
仕様
2 in 1 ではないほうです。一応リンク貼りますが、アフィリエイト記事ではないので書きたいこと書きます。
www.dell.com
見た目
見た目はすごく格好いいです。Dell のパソコンはヒンジが強くて開けづらいという問題がありましたが、このパソコンは少し改善されています。「片手で開くことが可能」ということをHPで書いてあるように片手で開いてもキーボード側がついてくるということはありません。ただし最初の60 度くらいまでは。。。ある程度開くとキーボードが付いてきます、残念ながら Mac のようにはいきません。
世界最小の13.3インチとうたっているだけあって、本当に13.3型かと疑うほど小さいです。下の写真は、これまで私が使ってきた Dell Inspiron 13 との2ショットです。ディスプレイサイズは同じなのに外形はこんなにも違います。
厚みは滑り止めがそこそこ分厚いので Inspiron と同じくらいで、最小で 1.4 mm, 最厚で 1.8 mm といったところです。重さは 1.2 kg くらいなので持ち運びで重すぎるということはなさそうに思います(ちなみに Inspiron は 1.65 kg あったのでちょっと辛かった...)。
キーボードを Inspiron と比べてみます。下は Inspiron のキーボードです。
見たらわかりますが、キーボードの外側に枠のようなものがあって Enter や Backspace を打つときにこれに阻まれて打てないということがありました。
しかし XPS ではそれがなくなり、さらに Enter が大きくなったので打ちやすくなりました。また、キー間の隙間も 4 mm -> 3 mm になって隙間に引っかかるということもなくなりました。
排熱・ファン
ファンの音は Inspiron と比べて大きいです。初期セットアップでいろいろインストールしてた時にはずっとフル回転でひどかったです。それ以外の時でも電源につないでいるときは若干ファンの音がします。図書館など静かなところで聞こえるかなくらいですが。電源ケーブルから外すと基本的には静かです。
あと、これは謎なのですが、ファンが少しでも動いた状態でスリープにすると、一瞬だけファンがフル回転して静かになります。これが原因で一度交換してもらったのですが(この時は一瞬フル回転が何回も連続して生じていた)、新しく来たものも同じだったので(回数が1回になったので改善した?)仕様かと思います。調べたら BIOS のアップデートで直るということも書いてあり、試したのですが効果はありませんでした。
パフォーマンステスト
CrystalDiskMark を使ってディスクのパフォーマンス、CineBench, PassMark を使って CPU 性能の評価をしてみました。
CrystalDiskMark
NVMe SSD を積んでいるので、それなりの結果が出たと思います。HDD や SATA SSD で計測したときの結果は以下のサイトが詳しいです。
xn--pc-mh4aj6msdqgtc.com
CineBench
CineBench は有名ですが、最近 Release 15 から Release 20 になったばかり(?) のせいか、比較対象があまりありませんでした。8世代 i5 の U タイプは 1500 ほどらしいです。
PassMark
PassMark はこんな感じ。上の CineBench とともに最近の Intel CPU のベンチマークを載せているサイトがあったので貼っておきます。
https://thehikaku.net/pc/other/corei7-1065g7.html
これと比較すると、CPU 性能は Core-i7 1065G7 よりよいようです。(多分 グラフィックス性能は負ける)
まとめ
ここまで 10 世代 Intel CPU を積んだ XPS 13 7390 のレビューをかなりざっくりしてきました。パフォーマンス計測などは行わず、主に外見のレビューをしてきましたが、まあまあ良いパソコンだと思いました。ファンの音を重要視する人は少し注意したほうが良いと思いましたが、デザイン・操作性に関しては文句がないです。
最後に、性能に関しては 2 in 1のほうがより高いものを選ぶことが可能です。グラフィックス性能を重要視する人(Ice Lake の CPU が欲しい人)、それに関連してメモリを 32 GB ほしい人は 2 in 1を選ぶべきだと思います。あと、2 in 1 では US 配列キーボードを選ぶことが可能なので US 配列が欲しい人もこっちにするべきです。(あれ?これってみんな 2 in 1を買うべきなのでは...?)