- 投稿日:2019-12-13T23:58:51+09:00
価値反復法のeinsumによる実装
普段強化学習,逆強化学習を研究しているのですが,最近おもしろいことを知って実装してみたので,アドベントカレンダーのネタにさせていただきます.
予備知識
前提となるマルコフ決定過程と価値反復法について概説します.
マルコフ決定過程
マルコフ決定過程(MDP)とは,マルコフ過程(次状態は現状態にのみ影響されるマルコフ性をもつ過程)に意思決定である行動を加えた過程です.詳しい説明はネット上に多くあると思うので割愛します.
価値反復法
価値反復法(Value Iteration)とは,動的計画法と呼ばれるアルゴリズムの一種です.
ある状態$s$の価値を次の手順で求めます.
- $V$, $Q$ を初期化
- $s$, $a$ について収束するまで繰り返し:
- $Q(s,a)=R(s)+\gamma \sum_{s^{\prime}} P(s^{\prime}|s,a)V(s^{\prime})$を計算
- $V(s)=\max_a Q(s,a)$
einsumによる実装
numpy.einsumは,アインシュタインの縮約記法による行列計算の実装を手軽に行なえます.
今回の場合,上の式の右辺の$\sum_{s^{\prime}} P(s^{\prime}|s,a)V(s^{\prime})$の項は次のように書けます.np.einsum("ijk,k->ij", P, V)コード
Gridworld(格子世界)という強化学習の基本中の基本の問題で実験しました.
einsumによる実装と,よくあるループによる実装の計算時間が,Gridの一辺の長さを増やしていった際にどう変化するのか比較しました.vi.py# -*- coding: utf-8 -*- import numpy as np from makeP import makeProb import time for statenum in range(2, 101): list_einsum = [] list_loop = [] for ite in range(100): print(".", end="", flush=True) x_size = statenum y_size = statenum n_states = int(x_size * y_size) P = makeProb(x_size, y_size) R = np.ones(n_states)*-0.01 R[int(n_states-1)] = 1 Q = np.zeros((n_states, 4)) V = np.zeros(n_states) delta = np.inf eps = 0.0 gamma = 0.95 t1 = time.time() for _ in range(10000): Q = R[:, None] + gamma * np.einsum("ijk,k->ij", P, V) V = np.max(Q, axis=1) t2 = time.time() list_einsum.append(t2-t1) Q = np.zeros((n_states, 4)) V = np.zeros(n_states) t1 = time.time() for _ in range(10000): for s in range(n_states): for a in range(4): Q[s][a] = R[s, None] + gamma * np.dot(P[s,a,:], V) V[s] = np.max(Q[s, :]) t2 = time.time() list_loop.append(t2-t1) print("") ar_einsum = np.array(list_einsum) ar_loop = np.array(list_loop) print("EINSUM: ", "size: ", statenum, "mean: ", np.mean(ar_einsum), "median: ", np.median(ar_einsum), "stdev: ", np.std(ar_einsum)) print("LOOP : ", "size: ", statenum, "mean: ", np.mean(ar_loop), "median: ", np.median(ar_loop), "stdev: ", np.std(ar_loop))makeP.py# -*- coding: utf-8 -*- import numpy as np def makeProb(x_size, y_size, n_action=4): # make transition prob n_states = x_size*y_size # S, A, S' P = np.zeros(( n_states, n_action, n_states )) for s in range(n_states): #left is wall if s%x_size == 0: if s == 0: P[0][1][5] = 1 P[0][3][1] = 1 elif s > (x_size*(y_size-1)-1): P[s][0][s-x_size] = 1 P[s][3][s+1] = 1 else: P[s][0][s-x_size] = 1 P[s][1][s+x_size] = 1 P[s][3][s+1] = 1 #right is wall elif (s+1)%x_size == 0: if s == (x_size*y_size-1): P[s][0][s-x_size] = 1 P[s][2][s-1] = 1 elif s < x_size: P[s][1][s+x_size] = 1 P[s][2][s-1]=1 else: P[s][0][s-x_size] = 1 P[s][1][s+x_size] = 1 P[s][2][s-1] = 1 #upper is wall elif s < x_size and s%x_size!=0 and (s+1)%x_size !=0 : P[s][1][s+x_size] = 1 P[s][2][s-1] = 1 P[s][3][s+1] = 1 #lower is wall elif s > (x_size*(y_size-1)-1) and s%x_size!=0 and (s+1)%x_size !=0: P[s][0][s-x_size] = 1 P[s][2][s-1] = 1 P[s][3][s+1] = 1 else: P[s][0][s-x_size] = 1 P[s][1][s+x_size] = 1 P[s][2][s-1] = 1 P[s][3][s+1] = 1 return P結果
einsumの圧勝.
何が嬉しいのかというと,例えば逆強化学習のInner-loop(内部にある強化学習のループ)が速く解けるようになったり.
- 投稿日:2019-12-13T23:54:39+09:00
Djangoでよく見かけるエラー =随時追加=
環境
python3,7 Django 2.2 windowsフォルダー構成
ここで触れないファイルは省略します。├── config │ ├──__init__.py ├──settings.py │ └── urls.py ├── manage.py ├── ├── └── posts ├──templates ── posts ── index.html ├── models.py ├──forms.py ├──views.py └── urls.py項目
1.TemplateDoesNotExist
2.内容
1.TemplateDoesNotExist
原因
テンプレートが見つからない
確認すること
htmlを置く場所が間違っていないか
urls.pyのpathが間違っていないか。#views.py class IndexView(TemplateView): template_name='posts/index.html' posts_index=IndexView.as_view() #urls.py app_name="posts" urlpatterns=[ path('',views.posts_index,name='posts_index') ]
- 投稿日:2019-12-13T23:49:43+09:00
Pythonとは
今日はある意味本命である
Pythonについて投稿します。Pythonとは
1991年にオランダ人のグイド・ヴァン・ロッサム氏によって開発されたプログラミング言語です。
少ないコードで簡単にプログラムを作れ
尚且つコードが読みやすいのが特徴です。「人工知能(AI)」や「Web開発」、「教育の分野」など活用が広まっています。
Pythonのメリット
基本メリットとして
- シンプルで覚えやすい
- インデントによるオフサイドルール
- コンパイルが不要
- ライブラリが充実している
これらが挙げられます。
シンプルで覚えやすい
文法がシンプルで必要最低限のものしかあらず
読みやすく書きやすい言語です。
何通りも書き方は用意されてないので
他人の書いたコードも比較的読みやすいものになります。インデントによるオフサイドルール
構文などのブロックを字下げ(インデント)で指定するというものです。
これによって誰が書いても同じコードになるため、
プログラムは書きやすく読みやすいモノになっています。オフサイドルール
字下げによって文などのかたまりの範囲(ブロック)を示す規則です。
コンパイルが不要
コンパイル作業は、大量のエラーが発生することが多く時間を要します。
Pythonはインタプリタ型の言語のため、コンパイルの必要がなく
すぐに動作確認ができます。Pythonのデメリット
デメリットは以下の通りです。
- 実行速度が遅い
- インデントが必須
Pythonはインタプリタ言語のなかでも特に実行速度が遅い言語と言われています。
企業向けの基幹システム,システムや高度なゲーム開発には不向きです。またオフサイドルールからインデントが適切にできていないと
エラーとなってしまうこともあります。Pythonで作れるもの
作れる物は
- 「Webアプリケーション」
- 「デスクトップアプリケーション」
- 「組み込みアプリケーション」
- 「ゲーム」
- 「機械学習(人工知能)」
などが作れます。
Pythonの作成例
有名なのをあげると
- youtube
- evernote
が代表的です。
Pythonのフレームワーク
使われいるフレームワークを一部紹介するとこのような物があります。
- Django
- Flask
- Bottle
- 投稿日:2019-12-13T23:48:41+09:00
ダウンロード画像と顔画像の簡易管理アプリ
はじめに
- 機械学習用の画像を収集し、目視で仕分けする作業が発生する場合があります。
- Mac では、Finder で画像を見ながら削除を繰り返したりしますね。これは、かなり骨の折れる作業です。
- 今回は、Webブラウザベースの簡易管理アプリを作成しました。
- ソース一式は ここ です。
概要
トップページ
では、config.py
のCLASSES
を元に各メニューを表示しています。ダウンロード画像、顔画像
では、両者を比較することができます。- また、顔画像をクリックし、まとめて削除を実施できます。
学習画像の予測結果
とテスト画像の予測結果
は、別途提供する予定です。トップページ
ダウンロード画像と顔画像
ライブラリ
Flask
を用いています。Pillow
は、画像の拡大縮小に用いています。Bootstrap
を用いています。Font Awesome
で特殊なアイコンを表示しています。今回は、顔画像の右上に表示するゴミ箱のアイコンで利用しています。トップページ
- トップページは、
index.html
の内容をほぼそのまま表示しています。config.py
のCLASSES
は、index.html
のitems
として渡しています。- 各メニューの識別のために、
Bootstrap
で色を変えています。image_viewer.py@app.route('/') def index(): """Top Page.""" return render_template('index.html', items=CLASSES)templates/index.html<div class="container-fluid"> {% for item in items %} <div class="row"> <div class="col"> {{ loop.index }} {{ item }} </div> <div class="col-11"> <a class="btn btn-primary" href="/download_and_face/{{ item }}" role="button">ダウンロード画像、顔画像</a> <a class="btn btn-secondary" href="/predict/train/{{ item }}" role="button">学習画像の予測結果</a> <a class="btn btn-success" href="/predict/test/{{ item }}" role="button">テスト画像の予測結果</a> </div> </div> <br /> {% endfor %} </div>ダウンロード画像と顔画像
DOWNLOAD_PATH
配下には、Google カスタム検索等でダウンロードした画像が保存されています。- 例: data/download/安倍乙/0001.jpeg
FACE_PATH
配下には、OpenCV
のHaar Cascade
を用いて顔認識した画像が保存されています。- また、ダウンロード画像のファイル名を元にファイル名を生成しています。
- 例: data/face/安倍乙/0001-0001.jpeg
GET の処理
安倍乙
などをitem
で受け取ります。DOWNLOAD_PATH
item
*.jpeg
をキーとして、ダウンロード画像の一覧を作成します。FACE_PATH
item
*.jpeg
をキーとして、顔画像の一覧を作成します。image_viewer.py@app.route('/download_and_face/<item>', methods=['GET', 'POST']) def download_and_face(item): """ダウンロード画像、顔画像.""" download_list = glob.glob(os.path.join(DOWNLOAD_PATH, item, '*.jpeg')) download_list = sorted([os.path.basename(filename) for filename in download_list]) face_list = glob.glob(os.path.join(FACE_PATH, item, '*.jpeg')) face_list = sorted([os.path.basename(filename) for filename in face_list])
- ダウンロード画像から顔画像の検索キーを作成し、配列
row
を作成しています。- 各ダウンロード画像と顔画像の組み合わせは、
rows
に再格納されます。item
とrows
をテンプレートエンジンへ引き渡します。image_viewer.pyrows = [] for download in download_list: row = [download] key = download.split('.')[0] + '-' for face in face_list: if face.startswith(key): row.append(face) rows.append(row) return render_template('download_and_face.html', item=item, rows=rows)
- テンプレートでは、ダウンロード画像と顔画像の組みを 1行 で表示します。
- ダウンロード画像と顔画像のリンクを作成します。
size=200
で画像のサイズを指定しています。このサイズは、画像の縦のサイズとなります。- サイズを揃える事で、ダウンロード画像と顔画像の比較がしやすくなります。
- 顔画像では、クリックで指定しやすい様に
CSS
とJS
を利用しています。- こちらは、下記を参考にしました。
templates/download_and_face.html<tbody> {% for row in rows %} <tr> <td> {{ loop.index }} </td> <td> <figure class="figure"> <img src="/data/download/{{ item }}/{{ row[0] }}?size=200" /> <figcaption class="figure-caption">{{ row[0] }}</figcaption> </figure> </td> <td> {% for filename in row[1:] %} <figure class="figure"> <label class="image-checkbox"> <img src="/data/face/{{ item }}/{{ filename }}?size=200" /> <input type="checkbox" name="filename" value="{{ filename }}" /> <i class="fa fa-trash-o d-none"></i> </label> <figcaption class="figure-caption">{{ filename }}</figcaption> </figure> {% endfor %} </td> </tr> {% endfor %} </tbody>
- 顔画像は、チェックボックスを指定した場合、指定しない場合の表示を調整しています。
static/download_and_face.css.image-checkbox { cursor: pointer; border: 2px solid transparent; box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; position: relative; } .image-checkbox input[type="checkbox"] { display: none; } .image-checkbox-checked { border-color: #d9534f; } .image-checkbox .fa { color: #ffffff; background-color: #d9534f; font-size: 20px; padding: 4px; position: absolute; right: 0; top: 0; } .image-checkbox-checked .fa { display: block !important; }
- 初回表示の時、チェックボックスの状態をクラスに設定しています。
- また、クリック時にクラスを変更しています。
static/download_and_face.js// image gallery // init the state from the input $(".image-checkbox").each(function () { if ($(this).find('input[type="checkbox"]').first().attr("checked")) { $(this).addClass('image-checkbox-checked'); } else { $(this).removeClass('image-checkbox-checked'); } }); // sync the state to the input $(".image-checkbox").on("click", function (e) { $(this).toggleClass('image-checkbox-checked'); var $checkbox = $(this).find('input[type="checkbox"]'); $checkbox.prop("checked",!$checkbox.prop("checked")) e.preventDefault(); });画像のサイズの変更
- ダウンロード画像、顔画像のパスを生成します。
image_viewer.py@app.route('/data/<folder>/<item>/<filename>') def get_image(folder, item, filename): """画像のレスポンス size で拡大縮小.""" if folder not in ['download', 'face']: abort(404) filename = os.path.join(DATA_PATH, folder, item, filename)
Pillow
を利用して画像を読み込みます。image_viewer.pytry: image = Image.open(filename) except Exception as err: pprint.pprint(err) abort(404)
- URL のオプションで
size
がある場合は、画像のサイズを修正します。- 今回の場合は、縦のサイズを元に画像が拡大縮小します。
- 縦のサイズを、ダウンロード画像と顔画像で揃えると、目視で比較しやすくなりました。
- また、
Pillow
には、thumbnail
でサイズを変更することもできます。- ただ、こちらは縦横の比率が変わってしまいます。
image_viewer.pyif 'size' in request.args: height = int(request.args.get('size')) width = int(image.size[0] * height / image.size[1]) image = image.resize((width, height), Image.LANCZOS)
- 最後に、
Pillow
のデータをバイトデータに変換し、image/jpeg
でレスポンスを作成します。image_viewer.pydata = io.BytesIO() image.save(data, 'jpeg', optimize=True, quality=95) response = make_response() response.data = data.getvalue() response.mimetype = 'image/jpeg' return responsePOST の処理
- フォームで削除する顔画像のファイル名が POST されます。
- 対象の顔画像を確認し、
os.remove
で削除しています。- ここは、もう少し慎重な仕組みが必要だと思いますが、個人で利用する事を前提に、簡易な形にしています。
image_viewer.py@app.route('/download_and_face/<item>', methods=['GET', 'POST']) def download_and_face(item): """ダウンロード画像、顔画像.""" if request.method == 'POST' and request.form.get('action') == 'delete': for filename in request.form.getlist('filename'): filename = os.path.join(FACE_PATH, item, filename) if os.path.isfile(filename): os.remove(filename) print('delete face image: {}'.format(filename))おわりに
- ダウンロード画像と顔画像を比較しながら、不要な顔画像を削除するWebアプリケーションを作成しました。
- Mac の Finder を用いて顔画像を削除するよりは、格段に便利になったと思います。
- Webアプリケーションの作成には、それなりに時間や慣れが必要ですね。
- 次回は、顔画像を水増しを実施する予定です。
- 投稿日:2019-12-13T23:17:05+09:00
Djangoのデプロイで追い詰められているあなたへ。Ubuntu18.04+NginxでDjango2.2デプロイ完全版
はじめに
自分が初心者だったこともあって、かなり初心者向けに書いたつもりです。
とは言っても初投稿なので、1ヶ月ほど前にやったことを思い出しながら書いているので、なにか抜けていたり大きなミスをしていたりするかもしれません。
もしご不明な点やミスなどがあればコメントよろしくおねがいします。完全版とは言いましたが、よくある記事とは違い、プロジェクトの作成からの説明は行いません。
自分自身Djangoのデプロイに苦労して調べていた時に正直無駄だと思ったからです。
この記事では、デプロイするDjnagoプロジェクトが用意できているものとして説明を始めます。Django3.0がリリースされた今かよって感じですが、おそらくDjango3.0でもそんなに変わらないと思います。
僕がやった範囲内では問題なく動作しました。環境
- Ubuntu18.04 LTS
- Nginx1.14.0(最新のものなら基本OKだと思う)
- Python3.8.0(3.x.xならOK)
- Django2.2.8(2.2.xならOK、おそらく3.0でもいける)
前提
- プロジェクトの名前は "YourProjectName"
- アプリの名前は "YourAppName"
- 好ましくはないが、SECRET_KEYは、開発環境・本番環境ともに、もとのsettings.pyファイルに記載されていたものを使用します。
- 好ましくはないが、データベースは、開発環境・本番環境ともに、sqlite3を使用します。
- static、media、templatesフォルダは、トップディレクトリにて一括管理されているものとします。
- ホスト名(ドメインの名前)はYourHostNameとします。グローバルIPアドレスでも構いません。
ローカルでの準備
準備完了段階でのディレクトリ構造
YourProjekutName ├ YourProjectName │ ├ __init__.py │ ├ settings │ │ ├ __init__.py │ │ ├ base.py │ │ ├ local.py │ │ └ production.py │ ├ urls.py │ └ wsgi.py ├ YourAppName │ ├ migrations │ │ └ __init__.py │ ├ __init__.py │ ├ admin.py │ ├ apps.py │ ├ models.py │ ├ tests.py │ ├ urls.py │ └ views.py ├ collected_static ├ media ├ static │ ├ css │ ├ images │ └ js └ templatesYourProjectName/settings.pyを書き変える
書き換えるというよりも作り変えるといったほうが的確かもしれません。
YourProjectName/settings/base.pyimport os BASE_DIR = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) INSTALLED_APPS = [ 'YourAppName', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] ROOT_URLCONF = 'YourProjectName.urls' 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', 'django.template.context_processors.static', ], }, }, ] WSGI_APPLICATION = 'YourProjectName.wsgi.application' AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Tokyo' USE_I18N = True USE_L10N = True USE_TZ = True STATIC_URL = '/static/' STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')] STATIC_ROOT = os.path.join(BASE_DIR, 'collected_static') MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media')YourProjectName/settings/local.pyfrom .base import * SECRET_KEY = 'Secret Key Written on settings.py' DEBUG = True ALLOWED_HOSTS = [] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }YoutProjectName/settings/production.pyfrom .base import * SECRET_KEY = 'Secret Key Written on settings.py' DEBUG = False ALLOWED_HOSTS = ['YourHostName'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), } }詳しくは後日追記。
トップディレクトリにcollected_staticフォルダを作成
理由は後にわかります。
準備完了段階でのディレクトリ構造
YourProjekutName ├ YourProjectName │ ├ __init__.py │ ├ settings │ │ ├ __init__.py │ │ ├ base.py │ │ ├ local.py │ │ └ production.py │ ├ urls.py │ └ wsgi.py ├ YourAppName │ ├ migrations │ │ └ __init__.py │ ├ __init__.py │ ├ admin.py │ ├ apps.py │ ├ models.py │ ├ tests.py │ ├ urls.py │ └ views.py ├ collected_static ├ media ├ static │ ├ css │ ├ images │ └ js └ templatesGitHubにPush
開発環境から本番環境にデータを移すときに今後の開発の事も考えGitHubを使用します。
.gitignoreで__pychache__、db.sqlite3、collected_static/*を指定して、gitの対象から除外しておくことをおすすめします。サーバーでの操作
準備も大変だったかと思いますが、これからが本番です。
一応書いておきますが、これからはサーバー上での操作です。ファイアーウォールの設定
80番ポートを開けてください。
sudo systemctl restart で必ずファイアーウォールのサービスを再起動してください。Nginxのインストール
sudo apt install nginxNginxの設定
この段階で、ブラウザにドメインを打ち込むとWelcome to nginx!と出てくるはずです。
出てこなかった場合、インストールに失敗したか、ファイアーウォールの設定がうまく行っていないか、ドメインやネームサーバー周りの設定がうまく行っていない可能性があります。まず、次の部分を編集してください。
/etc/nginx/nginx.conf# user www-data; user webmaster;以下のファイルを新規作成してください。
sudo権限がないとPermission deniedとかって出てきます。
;(セミコロン)を絶対に忘れないでください。/etc/nginx/sites-available/serverserver { server_name YourHostName; location /static { alias /home/webmaster/YourProjectName/collected_static; } location /media { alias /home/webmaster/YourProjectName/media; } location / { proxy_pass http://127.0.0.1:8080; proxy_set_header X-Forwarded_For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_redirect off; proxy_set_header X-Forwarded-Proto $scheme; } }ユーザー: webmasterを作成
sudo adduser webmasterプロジェクトをクローン
先程ローカルでGitHubにPushしたプロジェクトをクローンします。
ここで、注意しなければならないのは、今作ったユーザー(webmaster)のユーザーディレクトリ(/home/webmaster/)上にクローンを作ることです。Pythonの環境構築
よく、仮想環境を構築する記事がよくあり、その方が好ましいのですが、面倒くさい上、なぜかうまくいかなかったので、普通にやります。
Python3.8をインストール
sudo apt install python3.8 python3.8-devpipでパッケージをインストール
ここで必要となるのが、Djangoはもちろん、Gunicornというパッケージです。
python3.8 -m pip install django python3.8 -m pip install gunicorn他にも作成したプロジェクトに必要なパッケージがあれば必ずインストールしてください。
今回、Gunicornの動作確認は省略します。
migrateとcollectstatic
python3.8 manage.py makemigrations --settings YourProjectsName.settings.production python3.8 manage.py migrate --settings YourProjectsName.settings.production python3.8 manage.py collectstatic --settings YourProjectsName.settings.productionサービスを追加
以下のファイルを新規作成してください。
sudo権限がないとPermission deniedとかって出てきます。/etc/systemd/system/YourProjectName.service[Unit] Description=gunicorn After=network.target [Service] WorkingDirectory=/home/webmaster/YourProjectName ExecStart=/usr/local/bin/gunicorn --bind 127.0.0.1:8080 YourProjectName.wsgi:application [Install] WantedBy=multi-user.targetsudo systemctl daemon-reload sudo systemctl restart nginx sudo systemctl restart YourProjectName確認
ブラウザにYourHostNameを打ち込むと、見れるはずです。
HTTPS化
後日書きます。
さいごに
初心者的には結構大変ですが、できないうちに精神的に追い詰められても、途中で投げ出さず、一つ一つ確実にやっていけば必ずできます。
僕もできました。頑張ってください!!
はじめにも書きましたが、なにか抜けているかもしれませんので、ご不明な点がある方や、ミスを発見した方はコメントにお願いします。
- 投稿日:2019-12-13T23:07:38+09:00
Chromedriverのバージョンが合わない事によってseleniumでchromeが開けない
概要
seleniumでchromeを開こうとしたら、一瞬だけ画面に表示され、すぐ消えた。
ChromeDriverと、Chromeバージョンが違うとエラー出された。
なんで...エラー文
"selenium.common.exceptions.SessionNotCreatedException: Message: session not created: This version of ChromeDriver only supports Chrome version 76"
ここでどうしたらよいかわからなかった。解決策
https://qiita.com/umaruskie/items/f2b4c11b50b34508b64f
ここで紹介されているように、自分が今使っているChromeのバージョンを確認し、そのバージョンをサポートしているChromedriverをダウンロードしないと、seleniumからchromeを開くことができなかった。解決、解決、めでたしめでたし
- 投稿日:2019-12-13T23:05:51+09:00
pythonからGPUの枚数を知る方法〜multiprocessingをpytorchで使う際の注意点〜
はじめに
pytorchでmultiprocessingを利用するとCUDAの初期化で怒られることがあります.
RuntimeError: cuda runtime error (3) : initialization error at /pytorch/aten/src/THC/THCGeneral.cpp:50 THCudaCheck FAIL file=/pytorch/aten/src/THC/THCGeneral.cpp line=50 error=3 : initialization errorなぜ怒られたか
色々調査すると,spawnがどうとか,様々な文献が見当たりましたが,自分の場合は
torch.cuda.device_count()
を利用していたことが原因だったようです.ということで,GPUの枚数を
torch.cuda.device_count()
抜きで知りたい.GPUの枚数をpythonから取得
nvidia-smi
に頼ります.
linuxの場合は以下の通り.import subprocess msg = subprocess.check_output("nvidia-smi --query-gpu=index --format=csv", shell=True) n_devices = max(0, len(msg.decode().split("\n")) - 2)pytorchのCUDA初期化問題には皆様も気をつけてください.
- 投稿日:2019-12-13T22:36:17+09:00
一流のエンジニアは Mac のメニューバーにこだわる
本記事は Sansan Advent Calendar 2019 13日目の記事です。
一流のプロフェッショナルはどんな些細なことにも気を抜かないものです。
普段使う道具については、特に気をつけていることでしょう。プロ野球選手であれば、バットやグロープは日々磨き上げていることでしょう。
大工さんであれば、金槌やかんなの手入れを怠る日はないはずです。プロフェッショナルであるところのエンジニアの皆さんであれば、マウスやキーボードの掃除は日々のルーティンとなっていると思います。
しかし、エンジニアたるもの、メンテナンスすべきは物理的な部分にとどまらないはずです。合理的に考えれば、メインマシンの作業で最も目にするところ・目に付きやすい所を美しく保つべき、と考えつくのは当然の結果ですね。
では、それはどこでしょうか、デスクトップ?ゴミ箱?ルートディレクトリ?
...
...
...そうですね、Mac のメニューバーですね!
ということで前置きが長くなりましたが、ここでは、誰でも簡単に Mac のメニューバーの中でもステータスメニューをシンプルかつ機能的に仕上げるノウハウを紹介していきます。
Windows や Linux がメインマシンの方は直接参考にしづらいと思いますが、考え方は同じですので、ぜひ参考にしてみてください。
(ちなみにタイトルはアオリです。僕は一介のデータエンジニアですのでご了承ください。)ステータスメニューの乱れは、心の乱れ
前述のとおり、ステータスメニューは Mac の画面の中でも最も目にする機会の多い部分の一つです。
この一番目立つ部分がデフォルトのまま、使わないシステム関連のアイコンが出しっぱなしになって、長々と表示されているのは非常に見苦しいものです。
ステータスメニューの乱れは、心の乱れと捉えて、真摯に向き合っていきましょう。洗練されたステータスメニューを手に入れる方法を知ろう
さて、洗練されたステータスメニューに近づくための近道は、表示するアイコンをできるだけ少なくするです。シンプルですね。
しかし、シンプルなものほど奥深いものです。
ステータスメニューというのは、どの画面でも一律に画面の上端(もしくは下端)という特等席で、情報を表示し気軽なアクセスを提供してくれている、
言わば聖域です。ここをうまく活用してこそプロフェッショナルと呼ばれるにふさわしいとは思いませんか?
この記事でより高みを目指しましょう。次節にその答えは存在します。【方法 1】 システム関連アイコンを取捨選択する
回りくどい表現は飽きてきていると思うので、以降は淡々と説明します。
1番手っ取り早いのは、Commandキーを押したままアイコンをメニューバーの外にドラッグすることです。
また、誤って消してしまったときは、「システム環境設定」から表示したい項目を開き、「メニューバーに表示」にチェックをすれば再表示されます。
Bluetooth の設定画面の例全部消してしまってもいいですが、以下のアイコンは時々使うので残しておくと便利かもしれません。
- 音量
- Bluetooth
- ネットワーク情報
- バッテリー残量【方法 2】 アプリでアイコンを隠す
実際整理すると消せるものはごく限られると思います。
そこで、登場するのがBartender 3です。Bartender はボタン一つでアイコンを隠してくれるアプリです。
「常に表示するアイコン」と「隠すアイコン」とボタン押下で切り替えてくれるもので、普段よくみる時計などは常に表示にしておき、たまに使うVPN接続アイコンは隠しておくといった使い分けができるようになります。
ワンタップですぐ表示Bartender は有料アプリなのですが、4週間はトライアルできます。
また、サブスクリプションではなく、買い切りなのでランニングコストがかからないのもありがたいです。ステータスメニューを最大限活用する
ここまででステータスメニューをシンプルにする、という目標は達成出来たと思います。
しかし、ステータスメニューの真価はここからです。
よりステータスメニューを使い倒していくノウハウをピックアップして紹介していきます。Google 日本語のアイコンをクールにする
IME として Google 日本語をお使いの人は多いと思いますが、デフォルトだと微妙にダサいと感じる人も多いかと思います。
実はこのアイコンは任意の画像に差し替えることが出来ます。
目立たないように赤と青の点の画像に差し替えています手順については以下のページにて、ご参照ください。かなり丁寧で解説されています。
https://lovemac.jp/2011好きなタイムゾーンの時刻を表示する
開発をしているとかなり頻繁に UTC の時刻表記に出くわすので、ステータスメニューに表示させる Times というアプリを使っています。
UTC と ニューヨークの時刻を表示時刻をクリックしてカレンダーを表示できるようにする
これは使ってる方も多いと思いますが、popCalendar というアプリを利用しています。
時計をクリックするとWindows ライクにカレンダーが展開...と思ってググったら、 App Store から消えてますね...
一応公式ではなさそうなダウンロードリンクは見つけましたがちょっとアレなので、自己責任で探してみてください。
かなり便利なアプリなだけに残念です。Python で好きな情報を表示する
実は Python でステータスメニューをいじれる rumps というライブラリがあります。
https://github.com/jaredks/rumps
以前社内ハッカソンで「定時まであと何時間」と「現在の USD/JPY」を切り替えられるアプリを作ったのですが、
久々に動かしてみるとレート取得部分だけ何故かうまく動きませんでした...なので、タイマー部分だけ抜粋して掲載します。
死ぬほど拙いコードなので、深く考えず参考程度としてください。
go_home_timer.pyimport rumps import datetime TIMER_MODE = 'timer' go_home_time = '18:00:00' ICON_NEUTRAL = 'clear.png' @rumps.clicked(u'残り勤務時間を表示') def switchTimer(sender): global display_state display_state = TIMER_MODE @rumps.timer(1) def dispTimer(sender): app.icon = ICON_NEUTRAL app.title = '残り時間:' + str(datetime.datetime.strptime(datetime.datetime.now().strftime('%Y/%m/%d ') + go_home_time, '%Y/%m/%d %H:%M:%S') - datetime.datetime.now()).split('.')[0] if __name__ == "__main__": display_state= TIMER_MODE app = rumps.App("GO_HOME_TIMER", icon=ICON_NEUTRAL, title='Initializing...') app.run()
残り時間もきちんと把握しておくのがプロフェッショナルというものですまとめ
ステータスメニューは個性が出るところだと思います。
なので、ぶっちゃけ散らかってても、そんなに生産性とかプロ意識とかには関係ないと思ってます。
- 投稿日:2019-12-13T21:57:21+09:00
PythonでQuaternionを使う ~ numpy-quaternion ~
はじめに
Pythonでクォータニオンを扱うライブラリはpyquaternionとnumpy-quaternionが世界でのトップ2のようですが,日本ではpyquaternionの参考ページを作った人が最初にいたからか,巷に溢れているPythonでのクォータニオン計算はpyquaternionばっか(しかない?)です.
しかし,numpy-quaternionのほうが計算コストが低そうです.
参考: https://www.theoj.org/joss-papers/joss.00787/10.21105.joss.00787.pdfまた,githubのスターの数を比べてもnumpy-quaternionのほうが多いです(2019/12/13時点でnumpy-quaternionが276,pyquaternionが138).
numpyとの親和性も一括変換の扱いやすさなどでpyquaternionよりも良い気がします.使ってみませんか?
githubのリポジトリ: https://github.com/moble/quaternion
ドキュメント: https://quaternion.readthedocs.io/en/latest/
PyPI: https://pypi.org/project/numpy-quaternion/インストール
pip install numpy-quaternion
で簡単に入ります.
また,
import quaternionでライブラリがインポートできます.
クォータニオンの表記
quat = (w, x, y, z) = \left(\underbrace{\cos\frac{\theta}{2}}_{実数部},\ \underbrace{\lambda_x\sin\frac{\theta}{2},\ \lambda_y\sin\frac{\theta}{2},\ \lambda_z\sin\frac{\theta}{2}}_{虚数(ベクトル)部}\right)使い方
以下,
import numpy as np import quaternionとします.
クォータニオンを作る
quat = np.quaternion(w, x, y, z)numpyの属性にquaternionが追加されます.
例えば,print(np.quaternion(1,0,0,0)) # -> quaternion(1, 0, 0, 0) type(np.quaternion(1,0,0,0)) # -> quaternion.quaternionとなります.
クォータニオンの合成
q1 = np.quaternion(1, 2, 3, 4) q2 = np.quaternion(4, 5, 6, 7) print(q1*q2) # -> quaternion(-52, 10, 24, 20)掛け算が定義されています.一応他の四則演算も定義されていますが,和とか使うことありますかね…C++のEigenでは定義されていません.
np.quaternionのメソッド
一部のみ取り上げます.球面関数関係のものや,線形補間などは取り上げていません.
参考: https://quaternion.readthedocs.io/en/latest/_autosummary/quaternion.htmlメンバ変数
メンバ変数 機能 w 実数部の要素 x 虚数部の最初の要素 y 虚数部の2番目の要素 z 虚数部の3番目の要素 components (w,x,y,z)がnumpy.arrayで返ってくる imag 虚数部(x,y,z)がnumpy.arrayで返ってくる vec 虚数部(x,y,z)がnumpy.arrayで返ってくる real 実数部(w)が返ってくる メンバ関数
メンバ関数 機能 abs() クォータニオン(ユークリッド距離)の絶対値 absolute() クォータニオンの絶対値 angle() 回転角度 conj() クォータニオンの複素共役を返す conjugate() クォータニオンの複素共役を返す equal(quat) 引数の中身のクォータニオンと等しいか exp exponetialを返す($e^q$) inverse() 逆クォータニオンを返す isfinite() 全ての要素が有限か ininf() 1つでもinfの要素が存在するか innan() 1つでもnanの要素が存在するか log() クォータニオンのログを返す nonzero() 全ての要素が0か norm() クォータニオンのCayley norm(絶対値の二乗のルート) normalized() 正規化したクォータニオンを返す notequal(quat) 引数の中身のクォータニオンと等しくないか sqrt() クォータニオンのsquare-root($quat=q*q$を満たす$q$)を返す square() クォータニオンの2乗$(quat*quat)$を返す quaternionのメソッド
この辺のメソッドは1次元のものではなく,多次元配列にも利用できるのがポイント.最後の次元の大きさがクォータニオンを要求するものなら4,3次元のものなら3次元,3x3なら最後の2次元が3x3にするなどはしないといけない.
メンバ関数 機能 quaternion.as_quat_array(a) numpy.arrayをquaternionに変換.aの最後の次元のサイズは4でないといけない quaternion.as_float_array(a) numpy.quaternionをnumpy.arrayに変換.出力の次元は入力より1大きい. quaternion.from_float_array(a) as_quat_arrayと同じ quaternion.as_rotation_matrix(q) numpy.quaternionを3x3の回転行列に変換. quaternion.from_rotation_matrix(rot, nonorthogonal=True) 3x3の回転行列をnumpy.quaternionに変換 quaternion.as_rotation_vector(q) クォータニオンから回転軸を求める.出力の最後の次元の大きさは3. quaternion.from_rotation_vector(rot) サイズ3の回転軸からクォータニオンに変換する. quaternion.as_euler_angles(q) クォータニオンからオイラー角に変換.オイラー角の変換順などはドキュメント参照 quaternion.from_euler_angles(alpha_beta_gamma, beta=None, gamma=None) オイラー角からクォータニオンに変換.オイラー角の変換順などはドキュメント参照 quaternion.rotate_vectors(R, v, axis=-1) Rはクォータニオン,vはベクトル.クォータニオンに応じてベクトルを回転させる. quaternion.allclose(a, b, rtol=8.881784197001252e-16, atol=0.0, equal_nan=False, verbose=False) 2つのクォータニオンを比較する quaternion.integrate_angular_velocity(Omega, t0, t1, R0=None, tolerance=1e-12) 角速度に応じて回転させる 公式ドキュメント
https://quaternion.readthedocs.io/en/latest/index.html
ちなみにこのライブラリの作者はオイラー角が大ッキライみたいです.使ってる人見たらやめろ!って言ってあげてその場から立ち去ってお母さんにちくっちゃえ!とか書いてあります
最後に
Pythonでクォータニオンを扱う需要ってどこにあるんでしょうか?UnityはC#だしそもそもUnityの機能使えって話だし,ロボットではPythonは遅すぎて論外だし…
- 投稿日:2019-12-13T21:50:24+09:00
pythonで音を鳴らす方法を詳しめに解説
人間という生き物は自分で合成音声読み上げソフトを作りたい生き物です。
これは仕方のないことです。
パスカルも言ってます、人は考える葦だって。
ほらね?(は?)まあ、とりあえずその下調べみたいなニュアンスでpythonで音を鳴らす実装を試していきます。
Chapter.0 使用言語・モジュール
- 言語:Python
- モジュール
- numpy(sinやπをつかうので)
- matplotlib(波形を描画したいなら)
- wave(.wavファイルの入出力に)
- struct(waveで.wavファイルにする際に波形のデータをバイナリ化するのに使います)
- pyaudio(音を鳴らすのに使いますが、Python3.7だとインストールがめんどくさいので最悪使わなくても全然大丈夫)
pythonimport numpy as np import matplotlib.pyplot as pl import wave import struct import pyaudioJupyter notebookならもうちょっと楽に音を鳴らせるかもしれない(詳しくは知らない)ですが、まあいいや。
Chapter1. 音を式で表現せねば…。
みなさん、音って何か知ってます?
音は周期的(?)な空気の密度の変化みたいなもんです。
要するに波です。波と言えばsin,cosですね。やった!
結論から言えば、下記のような式の正弦波を今回は使います。
sin(2πnt/s)
note_hz
=n
sample_hz
=spythonsec = 1 #1秒 note_hz = 440 #ラの音の周波数 sample_hz = 44100 #サンプリング周波数 t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保 wv = np.sin(2 * np.pi * note_hz * t/sample_hz)
t
は1秒間の時間を表現していて、上の場合44100個の要素の1次元配列です。
私たちの住む世界の情報は連続値(アナログ)ですが、残念ながらパソコンでは離散的(デジタル)なデータしか扱えません。
なので、1秒を44100個に分割して表現するのです。
(ちなみに44100hzというサンプリング周波数は、CDのサンプリング周波数の規格で、人の可聴域の約二倍の数字にしてあります。なぜ二倍かというのはナイキスト周波数とググりましょう。)サインの中身は2πnt/sとなっています。
t/sample_hz
=t/s は、0,1,2,...,44100 と増えていく t を s=44100で割ることによって、0,1/44100,2/44100,...,44099/44100,1 と、「徐々(1/44100ずつ)に増える一秒間」を表現しています。一旦
note_hz
=n を無視してnp.sin(2 * np.pi * t/sample_hz)
=sin(2πt/s) を見てみると、t/s は0→1に増える変数(というよりは時間の関数?)と考えられるので、sinの中身の2πt/sは、0→2πに増えることが分かります。
つまり、sin(2πt/s)は単位円を一秒でちょうど一周する関数(一秒で一回振動する波)になります。
1秒で一回振動するということは、この波の周波数は1〔Hz=1/s〕です。
しかし、周波数1では音には聞こえません。
そこで登場するのが、note_hz
=n です。nを2πt/s にかけるだけで、自由自在に波の周波数を変化させることができます。
例えば、n=440とすると、sin(2πnt/s)は一秒間に440回振動する波(音でいえば"ラ")になります。なんとこれでプログラム上での音の表現は完了してしまいました。
上に貼ったプログラムをもう一度コピペしておきますね。pythonsec = 1 #1秒 note_hz = 440 #ラの音の周波数 sample_hz = 44100 #サンプリング周波数 t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保 wv = np.sin(2 * np.pi * note_hz * t/sample_hz)Chapter2.プログラムで表現した音を.wavに出力しましょ。そうしましょ。
ここからの流れを説明すると、以下の通りです。
- 作った音を.wavファイルとして出力する。
- 音のデータをstructモジュールでバイナリ化する。
- バイナリ化されたデータを、waveモジュールで.wavファイルとして出力。 2.作った音をプログラム上で鳴らす。(任意)
- 作った.wavファイルをwaveモジュールで開く
- pyaudioモジュールで鳴らす(任意)
- 音の波形をmatplotlib.pyplotモジュールでグラフとして表示する。(任意)
3.に関しては波形が気にならない人はやんなくていいです全然。
2.は、pyaudioというモジュールを使うんですが、Python3.7系だとインストールがめんどくさい(インストールしたい場合は、このページの最後の参考サイトを参照してください)ので、1.で作成した.wavファイルをWindows Media Playerなどで鳴らせばいいです。では.wavとして出力する方法を説明していきます。
1.バイナリ化
バイナリ化です。
バイナリ化っていうのは、データを二進数にすることですね。
waveモジュールを使う際、バイナリ化しないと.wavファイルへの書き込みができないらしいです。多分。
なのでバイナリ化しましょう!では先に答えから貼ります。
pythonmax_num = 32767.0 / max(wv) #バイナリ化の下準備の下準備 wv16 = [int(x * max_num) for x in wv] #バイナリ化の下準備 bi_wv = struct.pack("h" * len(wv16), *wv16) #バイナリ化こんな感じです。
(というかこれ、参考にしたサイトのほぼコピペみたいなもんだけど、コピペ禁止的なマナーとかあるのだろうか…?まあいいや。)
wv
=W,x
=「Wの子要素のそれぞれ」= w として、[int(x * max_num) for x in wv]
の中身を見ていきます。
Wのそれぞれの子要素wで、
x * max_num
=x * 32767.0 / max(wv)
= w・32767/max(W)
= 32767・(w/max(W))
と表せます。
要するに、波形データの一つ一つの値wと波形データの最大値max(W)の比をとって、32767をかけています。32767って何の数字だよ!って思いますよね、わかります。
これは、16bitのデータ(16桁の2進数で表現されたデータ)のとりうる値が、-32768~32767であることからきています。(2の16乗が65536で、その半分の数が32768だから……うっ頭がっっっ)
w/max(W)がとりうる値は-1~1、それに32767をかけることで* 32767・(w/max(W)) は *-32767~32767 の値をとり、音の波形データを16bitの中にまんべんなく(というよりピッタリ?)収まるようにしています。
そうしてできるのがwv16
です。ふぅ…。そしてバイナリ化のコード
bi_wv = struct.pack("h" * len(wv16), *wv16)
。
正直僕はこれについて全然わかっていません。コピペです。
とりあえず、structバイナリのstruct.pack
はバイナリ形式への変換を行ってくれるもので、第一引数の"h"
は、2byte(16bit)整数のフォーマットらしい。へぇ。はい、バイナリ化終了!
2.waveモジュールで.wavファイルを出力
またしても先に答えを貼ります。
pythonfile = wave.open('sin_wave.wav', mode='wb') #sin_wave.wavを書き込みモードで開く。(ファイルが存在しなければ新しく作成する。) param = (1,2,sample_hz,len(bi_wv),'NONE','not compressed') #パラメータ file.setparams(param) #パラメータの設定 file.writeframes(bi_wv) #データの書き込み file.close #ファイルを閉じるこんな感じです。
wave.open()
で、ファイルを開きます。
第一引数でファイルの名前を指定し、第二引数のmode=
で書き込みモード('wb'
)か読み込みモード('rb'
)を設定しましょう。
wave.setparams()
で.wavファイルのパラメータを設定します。
パラメータ(param
)は左から順に、
- チャンネル数( ステレオ→2、モノラル→1 )
- サンプルサイズ〔byte〕(今回は2byte)
- サンプリング周波数
- フレーム数(今回でいえば
t
配列の個数と同じ)- 圧縮形式(
'NONE'
だけがサポートされている。それって存在意義あるんか…?)- 圧縮形式を人に判読可能にしたもの(圧縮形式
'NONE'
に対して'not compressed'
が返される。)です。
そしたらバイナリ化したデータ(bi_wv
)を書き込んで、ファイルを閉じます。
ファイル閉じるの忘れがちなんだよね…。よし、できた!!
(端末やコマンドプロンプトでファイルを実行してみて、.wavファイルが生成されているか確認しましょう!)Chapter3.めんどいからプログラム上で音鳴らしたいよな!
なので、まずwaveモジュールでさっき作ったファイルを開きます。
pythonfile = wave.open('sin_wave.wav', mode='rb')これで開けます。
ちゃんと読み込みモードになってますね。
file = wave.open('sin_wave.wav', mode='rb')
のfile
という部分は変数を表してますので、別の名前でも大丈夫です。fairu
とかwave_no_kiwami_otome
とか、なんでも。
まあ一応言っといただけです。
僕が初心者のころfile
っていう名前じゃなきゃいけないのかな?って勘違いしてたので。そしたらpyaudioモジュールで音を鳴らしていきます。
pythonp = pyaudio.PyAudio() #pyaudioのインスタンス化 stream = p.open( format = p.get_format_from_width(file.getsampwidth()), channels = wr.getnchannels(), rate = wr.getframerate(), output = True ) #音を録音したり再生したりするためのストリームを作る。 file.rewind() #ポインタを先頭に戻す。 chunk = 1024 #よくわかりませんが公式ドキュメントがこうしてました。 data = file.readframes(chunk) #chunk分(1024個分)のフレーム(音の波形のデータ)を読み込む。 while data: stream.write(data) #ストリームにデータを書き込むことで音を鳴らす。 data = file.readframes(chunk) #新しくchunk分のフレームを読み込む。 stream.close() #ストリームを閉じる。 p.terminate() #PyAudioを閉じる。上の通り、手順は
1.pyaudioを開く、2.ストリームを開く、3.ストリームにデータを書き込んで音を鳴らす、4.ストリームを閉じる、5.pyaudioを閉じる
という感じです。Chapter4.波形の表示
いやぁ、記事がこんなに長くなるとは。
僕もう疲れちゃったよ、パトラッシュ。
というわけでコードをバーンと貼っちゃいます。pythonpl.plot(t,wv) pl.show()なんてシンプル!
matplotlib.pyplotはいっぱい記事あるんで特に何も言いません。終わりに
ここまで読んでくれてありがとうございます!
生涯2つ目のQiita記事にしては頑張ったぜ…。それにしても人工音声合成ソフトは果たして自分で作れるのだろうか…。
参考サイト・文献
- [備忘録]pythonのwaveモジュール - Qiita
- めっちゃ参考になりました。
- pythonで音プログラミング
- Jupyter notebook を使ってるっぽいサイト。あんまり見てないです。
- wave — Read and write WAV files
- 翻訳が微妙ですが、日本語でも読めます。
- PyAudio Documentation
- 英語しかないっす。がんばりましょう、英語。
- Python3.7でPyAudioがインストールできない時の解決法
- PyAudioがインストールできなくて断念しそうだったときに助けていただきました。命の恩人です。
最終的なコード
pythonimport numpy as np import matplotlib.pyplot as pl import wave import struct import pyaudio #Chapter1 sec = 1 #1秒 note_hz = 440 #ラの音の周波数 sample_hz = 44100 #サンプリング周波数 t = np.arange(0, sample_hz * sec) #1秒分の時間の配列を確保 wv = np.sin(2 * np.pi * note_hz * t/sample_hz) #Chapter2 max_num = 32767.0 / max(wv) #バイナリ化の下準備の下準備 wv16 = [int(x * max_num) for x in wv] #バイナリ化の下準備 bi_wv = struct.pack("h" * len(wv16), *wv16) #バイナリ化 file = wave.open('sin_wave.wav', mode='wb') #sin_wave.wavを書き込みモードで開く。(ファイルが存在しなければ新しく作成する。) param = (1,2,sample_hz,len(bi_wv),'NONE','not compressed') #パラメータ file.setparams(param) #パラメータの設定 file.writeframes(bi_wv) #データの書き込み file.close #ファイルを閉じる #Chapter3 file = wave.open('sin_wave.wav', mode='rb') p = pyaudio.PyAudio() stream = p.open( format = p.get_format_from_width(file.getsampwidth()), channels = file.getnchannels(), rate = file.getframerate(), output = True ) chunk = 1024 file.rewind() data = file.readframes(chunk) while data: stream.write(data) data = file.readframes(chunk) stream.close() p.terminate() #Chapter4 pl.plot(t,wv) pl.show()
- 投稿日:2019-12-13T21:13:48+09:00
Python × Flask × PyTorch 数字認識Webアプリのお手軽構築
Python、Flask、PyTorchを使って画像認識アプリを作ってみます。
この3つを組み合わせれば、お手軽かつ爆速でデモアプリを作れます。前置き
Flaskとは
Python用のWebフレームワークです。
PythonのWebフレームワークはDjangoが有名ですが、Flaskは軽量を売りにしています。Djangoに比べると機能や拡張ライブラリは少ないですが、制約がある分コードもシンプルになり、お手軽にアプリケーションを作成することができます。
環境構築も簡単にできるのでWebアプリケーションのプロトタイプを作るのに向いています。Flaskと画像処理、機械学習との親和性の良さ
Pythonは機械学習関連のライブラリが充実しており、デファクトスタンダードになっているのは周知のことです。またPythonはOpenCVやPillow(PIL)などの画像処理ライブラリも充実しており、ネット上の情報も豊富です。
こういった背景もありPython × Flaskでやると機械学習ライブラリ、画像処理ライブラリの利用がとてもやりやすく簡単にアプリケーションを作れます。今回のゴール
ブラウザから手書き数字画像をアップロードすると、数字を認識して結果を表示するアプリケーションを作ってみます。
アプリケーションの構成
機械学習モデルの構築
今回はPyTorchを使ってMNISTの手書き数字認識モデルを作りました。
Google ColaboratoryでPyTorchでMNISTを学習したモデルを保存し、それを読み出して使う簡単サンプル - 人工知能プログラミングやってくブログ
この記事を参考に学習モデルを作ります。
動かすと1,725,616バイトのmnist_cnn.ptができました。環境の構築
Flask環境の構築はpipがインストールしてあれば
pip install Flask
で一発です。
今回はPillow(PIL)、PyTorchも使っているので、これもインストールしておきます。Webアプリの構築
ディレクトリ、ファイル構成は以下のようになります。
├── mnist_cnn.pt … 手書き数字認識モデル ├── predict.py … メインのスクリプト。ファイルのアップロードと画像判定を行う ├── static … アップロードしたファイルの配置先 │ ├── 20191213210438.png … ここにアップロードしたファイルが保存される │ ├── 20191213210253.png │ └── 20191213210341.png ├── templates … htmlテンプレートの保存先 ├── index.htmlpredict.pyの内容です。
機械学習モデルの定義・ロードとWebアプリの処理を記述しています。
モデル定義、画像の前処理についての詳細を知りたい方は以下の記事を参照してください。
Pytorch×MNIST手書き数字認識 PNG画像を入力に予測してみる - Qiitapredict.py# 必要なモジュールを読み込む # Flask関連 from flask import Flask, render_template, request, redirect, url_for, abort # PyTorch関連 import torch import torch.nn as nn import torch.nn.functional as F import torchvision from torchvision import datasets, transforms # Pillow(PIL)、datetime from PIL import Image, ImageOps from datetime import datetime # モデルの定義 class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv1 = nn.Conv2d(1, 20, 5, 1) self.conv2 = nn.Conv2d(20, 50, 5, 1) self.fc1 = nn.Linear(4 * 4 * 50, 500) self.fc2 = nn.Linear(500, 10) def forward(self, x): x = F.relu(self.conv1(x)) x = F.max_pool2d(x, 2, 2) x = F.relu(self.conv2(x)) x = F.max_pool2d(x, 2, 2) x = x.view(-1, 4 * 4 * 50) x = F.relu(self.fc1(x)) x = self.fc2(x) return F.log_softmax(x, dim=1) device = torch.device("cpu") model = 0 model = Net().to(device) # 学習モデルをロードする model.load_state_dict( torch.load("./mnist_cnn.pt", map_location=lambda storage, loc: storage) ) model = model.eval() app = Flask(__name__) @app.route("/", methods=["GET", "POST"]) def upload_file(): if request.method == "GET": return render_template("index.html") if request.method == "POST": # アプロードされたファイルをいったん保存する f = request.files["file"] filepath = "./static/" + datetime.now().strftime("%Y%m%d%H%M%S") + ".png" f.save(filepath) # 画像ファイルを読み込む image = Image.open(filepath) # PyTorchで扱えるように変換(リサイズ、白黒反転、正規化、次元追加) image = ImageOps.invert(image.convert("L")).resize((28, 28)) transform = transforms.Compose( [transforms.ToTensor(), transforms.Normalize((0.1307,), (0.3081,))] ) image = transform(image).unsqueeze(0) # 予測を実施 output = model(image) _, prediction = torch.max(output, 1) result = prediction[0].item() return render_template("index.html", filepath=filepath, result=result) if __name__ == "__main__": app.run(debug=True)index.htmlの内容です。
このHTMLテンプレートの中にファイルアップロードと認識結果表示を記述してあります。index.html<html> <body> {% if result %} <IMG SRC="{{filepath}} " BORDER="1"> 認識結果は {{result}} です<BR> <HR> {% endif %} ファイルを選択して送信してください<BR> <form action = "./" method = "POST" enctype = "multipart/form-data"> <input type = "file" name = "file" /> <input type = "submit"/> </form> </body> </html>起動と動作確認
python predicy.py
を実行するとFlaskのWebサーバが起動してアプリが動き始めます。ちなみにFlaskのデフォルトポートは5000番です。
http://localhostかホスト名:5000/でアクセスするとWebアプリケーションが表示されます。
手書き数字をアップロードすると
認識結果を表示してくれます。ちゃんと「9」と認識できています。
まとめ
機械学習を使って画像認識するWebアプリケーションを作る、と聞くと難しそうでコードも多く複雑になりそうですが、Flaskを使えば本当にお手軽にできます。
機械学習モデルは作って終わりではなく、まずいろんな人に使ってもらってなんぼかと思います。
ただコマンドベースだと、非エンジニアの人は使えないし、いろいろ試してみるのも難しいです。
そういう時にFlaskを使ってサクッとプロトタイプを作ってしまうのがおすすめです。
- 投稿日:2019-12-13T21:11:59+09:00
【2020年版】AWSのEC2にPython3をインストールする方法
先日、AWSのEC2にPython3をインストールする機会がありました。
その時に「AWS EC2 Python3」でGoogle検索したのですが、検索結果の上位に表示されたページの情報がいまいちでした。
具体的には、
- pyenv
- virtualenv
を使ってPython3の仮想環境を用意する方法が紹介されていましたが、 Python3の仮想環境を用意する方法は特別な事情がない限りは
venv
を使うべきです。
また、Python3をアプリ開発やデータ分析でガッツリ使う場合を除いては、そもそも仮想環境を用意する必要はないと思います。Python3の仮想環境を用意する方法として
venv
を使う理由は、Pythonの公式ドキュメントで推奨されている唯一の方法だからです。
pyenv
を避けるべき理由はこの記事に詳しく書かれているので、興味のある方はこちらをご覧下さい。また、AWSの公式ドキュメントでは
virtualenv
を使った方法が紹介されていますが、virtualenv
がvenv
としてPythonの中に正式に取り込まれたPython3.3以降においては積極的に使う理由はないと思います。ということで、改めてAWSのEC2にPython3をインストール方法を紹介したいと思います。と言っても、Pythonの公式ドキュメントで紹介されている方法に従うだけですが…。
前提
- OS
- Amazon Linux 2 AMI
- デフォルトではPython2系のみインストールされている
- インスタンスタイプ
- t2.micro
手順
OSのシステムにPython3をインストール
$ sudo yum update $ sudo yum install python3 -yPythonを使う機会は限られており、Python本体やパッケージのバージョン管理が必要ない場合はこれで十分だと思います。
一方、アプリ開発でそれ専用のバージョン管理をしたい場合や、データ分析で専用の環境を用意したい場合などは仮想環境の出番です。venvを使って仮想環境のPython3をインストール
$ python3 -m venv myenv上記のコマンドをホームディレクトリで実行すると、ホームディレクトリ配下に
myenv
というディレクトリが作成されます。myenv
という名前は好きなように変更して構いません。Python3の仮想環境を有効化
$ source myenv/bin/activate上記のコマンドを実行すると、Python3の仮想環境が有効化されます。
Python3の仮想環境を無効化
$ deactivatePython3の仮想環境を抜けたい場合は上記のコマンドを実行するだけです。また、環境をまるっと削除したい場合は
myenv
ディレクトリを削除すればOKです。最後に
AWSのEC2にPython3をインストールする方法を紹介しましたが、少なくともLinux系のOSではそのまま当てはまる話しだと思います。
Pythonをほとんど使わない人はOSのシステムにインストールしたPython3を、Pythonをガッツリ使う人はvenv
を使って作成したPython3の仮想環境を使うと覚えておいて下さい。
- 投稿日:2019-12-13T20:58:23+09:00
Dockerを使って動的なサイトをスクレイピングしてみよう
はじめに
近畿大学 Advent Calendar 2019の13日目の記事です.
はじめに注意なのですが, 基本的にスクレイピングは必要がないならするべきではない最終手段です. 今回はQiitaのタグランキングをスクレイピングしますが, Qiitaにはapiが存在しておりそこにタグランキングを取得するapiが存在しなかった(2019年12/8現在)のでスクレイピングをしました.もしあなたが欲しい情報がapiを用いて取得できるならばapiで取得しましょう. また, スクレイピングする際は接続時に待機時間を設け, 接続時間を空けましょう.
必要なもの
Docker
自分が使ってるバージョンは以下.
Docker version 19.03.5, build 633a0ea
docker-compose.yml
docekr-compsoe.ymlversion: '3' services: selenium-hub: image: selenium/hub container_name: selenium-hub ports: - "4444:4444" chrome: image: selenium/node-chrome-debug depends_on: - selenium-hub environment: - HUB_PORT_4444_TCP_ADDR=selenium-hub - HUB_PORT_4444_TCP_PORT=4444 python: build: . container_name: python volumes: - .:/workspace command: /bin/bash tty: true stdin_open: trueFROM python:3.7 WORKDIR /workspace RUN pip install \ selenium \ beautifulsoup4dockerfileとcompose説明
今さらdokcer-composeやdockerfileとは何かというのは書かないのでわからなければDocker上でElixirのPhoenixとPostgreSQLを使ってみたという記事に詳しめに書いているのでそちらを参考にしてください.
では早速docker-compse.ymlから説明します.
動的サイトをスクレイピングするには現在では基本
Selenium
一択だと思います.
selenium/hub
とselenium/node-chrome-debug
というイメージを使ってコンテナを作成します.
ここでselenium/node-chrome-debug
のほうでenvironment
が設定されています.これがなければスクレイピングできないので注意しましょう.Dockerfileのほうではpythonの環境を構築しています.
RUN
コマンドで必要なライブラリをDLしています.これらのファイルと下にあるコードを同一階層上に配置して,
docker-compose up -d --build
を実行すればコンテナが立ち上がりあます.コード例
from selenium import webdriver from selenium.webdriver.common.desired_capabilities import DesiredCapabilities from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.support.ui import WebDriverWait from bs4 import BeautifulSoup import pprint class QiitaGetRanking(): """ Qiitaからランキングデータを取得するクラス. """ def get_tag_ranking(self, browser: webdriver) -> dict: """ Qiitaからタグランキングに関する情報を取得する関数. Parameters ---------- browser: webdrive スクレイピングするためのwebdriverのオブジェクト Returns ------- tag_ranking_data: dict タグランキングを収めた辞書オブジェクト. """ html = browser.page_source.encode('utf-8') soup = BeautifulSoup(html, "html.parser") ra_tag_names = soup.find_all(class_='ra-Tag_name pr-1') tag_ranking_data = {} for i, ra_tag_name in enumerate(ra_tag_names): tag_ranking_data[i+1] = [ra_tag_name.text, 'https://qiita.com/tags/%s'%(ra_tag_name.text.lower())] return tag_ranking_data if __name__ == "__main__": """ main文. browserはhtmlの取得が終わり次第閉じること.エラーが出てきたときも同様. """ try: browser = webdriver.Remote( command_executor='http://selenium-hub:4444/wd/hub', desired_capabilities=DesiredCapabilities.CHROME) print("start scrape") browser.get('https://qiita.com') # javascriptが全て読み込まれるまで待機. 15秒経っても読み込みが終わらなければタイムアウト判定. WebDriverWait(browser, 15).until(EC.presence_of_all_elements_located) print("generate object") qgr = QiitaGetRanking() ranking_data = qgr.get_tag_ranking(browser) browser.close() browser.quit() pprint.pprint(ranking_data) except: browser.close() browser.quit()コードはいたってシンプルだと思います.dockerでスクレイピングする際はseleniumサーバーを建ててスクレイピングした方がubuntu上にselenium環境を構築するより楽だし軽いです.
webdrriver.Remoteに関しては2.5. リモートWebDriverでSeleniumを使用するを参考にしてみてください.
実行結果
docker ps
で3つのコンテナが建っていることを確認してから,docker exec -it python python qiita.py
でプログラムを実行してください.start scrape generate object {1: ['Python', 'https://qiita.com/tags/python'], 2: ['JavaScript', 'https://qiita.com/tags/javascript'], 3: ['AWS', 'https://qiita.com/tags/aws'], 4: ['Rails', 'https://qiita.com/tags/rails'], 5: ['Ruby', 'https://qiita.com/tags/ruby'], 6: ['初心者', 'https://qiita.com/tags/初心者'], 7: ['Docker', 'https://qiita.com/tags/docker'], 8: ['PHP', 'https://qiita.com/tags/php'], 9: ['Vue.js', 'https://qiita.com/tags/vue.js'], 10: ['Go', 'https://qiita.com/tags/go']}こんな感じで表示されていたら完全勝利です.お疲れさまでした.
さいごに
今回はDockerを用いて動的サイトをスクレイピングする方法を紹介しました.皆さんも制限の範囲内でスクレイピングを楽しみましょう!
- 投稿日:2019-12-13T20:25:22+09:00
Python for .NETの動作を各環境でチェックする
Python for .NETは各種の環境へインストールが可能となっていますが、どの組み合わせで可能なのか今一歩情報がまとまっていません。
やってみて出来たものを中心にまとめていこうと思います。
できたものから順に記述していこうと思います。Windows 10
これを使うのは、Windows 10での動作がメインと思いますので、厚めに試します。
固定条件として以下とします。
- pythonnet 最新版 (2.4.0)
- Anaconda python 3.x
には、以下のように書いてあるので、基本的に、3.7を使っています。
Python 3.8.0 support
Some features are disabled in Python 3.8.0 because of this bug in Python. The error is System.EntryPointNotFoundException : Unable to find an entry point named 'Py_CompileString' in DLL 'python38'. This will be fixed in Python 3.8.1..NET Framework 4
早く.NET5が出てほしいところですが、もうしばらく待たないといけないので、レガシーの方から。Windows10にプリインストールの.NET 4.6を利用しています。
.NET環境の準備
特に何もしなくてよさそうです。標準の環境ですから当たりまえですね。
Python環境の準備
これもAnaconda3の環境を入れただけで、特にTweakは必要なかったです。
動作確認
これでエラーが出なければ、ひとまず大丈夫でしょう。
import clr clr.AddReference("System.Windows.Forms") from System.Windows.Forms import Formprint("OS : ", Environment.OSVersion.VersionString) print("Python : " ,sys.version) print(".NET : ", Environment.Version.ToString()) print("pythonnet : " , clr.__version__)実行結果OS : Microsoft Windows NT 10.0.19041.0 python : 3.7.3 (default, Apr 24 2019, 15:29:51) [MSC v.1915 64 bit (AMD64)] .NET : 4.0.30319.42000 pythonnet : 2.4.0Windows 10のバージョンが怪しいですが、そこは。。。
.NET Standard ライブラリ
.NET Core
デスクトップ開発者もそろそろ .NET Coreに移行を考えてもいいんじゃないかというレベルに来ているのかどうか知りませんが、.NET Core 3.1でFrameworkに劣らない機能が利用できるようになっています。
しかし、残念ながらpythonnetではまだ準備ができておらず、.NET CoreからPythonを動かそうという事はできないようです。(そもそも、そんな事許されるわけないじゃん)Support for .NET Core? #243 @ github
.NET Core (CoreCLR) does not provide reverse pinvoke like .NET Framework on
Windows, neither C++/CLI. Hence the only way to get this working is to
embed .NET Core using C-API, like this is done for Mono. The problem is
that C-API for CoreCLR looks quite different from Mono.CoreCLRではreverse P/Invokeがサポートされていないから、無理ゲーと書いています。
Pythonから.NET Coreは出来てもいいんじゃない?と思いますが。しかし!上記は2016年の情報で、そこから以下のissueに状況が記されています。
.NET Core support and CoreCLR embedding - cross-platform API #96 @ github
denfromufa commented on Oct 17, 2018
によると、以下のサポート状況のようです。
Platform .NET-> Python Python -> .NET Windows Tested Coded (npython.exe) Linux Tested Tested (npython.exe) OSX Coded Coded (npython.exe) Pythonから.NET Coreを呼ぶ場合は、npython限定など、かなり制限がきついようですね。
Linux
Mono
.NET Core
Mac OSX
Mono
.NET Core
- 投稿日:2019-12-13T20:05:10+09:00
Kaitai Struct を使って、バイナリデータのパーサを作ってみる
はじめに
LOCAL学生部アドベントカレンダー11日目
つよつよな人ばかりで「自分弱すぎないか…?」と心配になったので、僕は変化球を投げることにしました。
僕が探した限りでは日本語の文献が存在しないため、今後これについて調べた人はほぼ自動的にこの記事を目にするのではないでしょうか。
そんな皆さんにお願いです。
「コイツ何言ってんだ?間違った情報だらけじゃないか」と思ったら、是非コメントください。
全力で修正します。Kaitai Struct って何?
概要
公式: kaitai.io
Kaitai Struct は、バイナリデータ構造を記述するために使用される宣言型言語です。
独自の言語で書いたデータ構造をもとに、バイナリデータのパーサのソースコードを自動生成できます。
対応言語(2019年12月2日現在)
- C++ / STL
- C#
- Go (entry-level support)
- Java
- JavaScript
- Lua
- Perl
- PHP
- Python
- Ruby
license
のちに記述するCompilerとVisualizerはGPLv3+であり、各言語のライブラリはMIT(JSはApache v2)
これって、Compilerを用いて生成したソースコードはGPLに感染するのだろうか…?
詳しい人教えてください。インストール
Kaitai Struct Compiler (KSC)
インストールに関する詳しい情報はこちら
Macはbrew install kaitai-struct-compiler
で一発です。
Windowsは、上記のリンクに飛んでインストーラをダウンロードしてください。
Debian/Ubuntuベースのディストリビューションなら公式の.debリポジトリからパッケージをインストールできます。
# Import GPG key, if you never used any BinTray repos before sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net --recv 379CE192D401AB61 # Add stable repository echo "deb https://dl.bintray.com/kaitai-io/debian jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list # ... or unstable repository echo "deb https://dl.bintray.com/kaitai-io/debian_unstable jessie main" | sudo tee /etc/apt/sources.list.d/kaitai.list sudo apt-get update sudo apt-get install kaitai-struct-compiler
その他のOSを用いている場合は、ここからCloneしてビルドしましょう。Kaitai Struct Visualizer (KSV)
これは
.ksy
ファイルのためのシンプルなビジュアライザです。
Ruby
で書かれており、gem
パッケージとして入手できます。gem install kaitai-struct-visualizer
(Gitリポジトリ)
使ってみる
有名なファイルについては、公式のgithubリポジトリに
.ksy
ファイルが存在します。
(ここに存在する.ksy
ファイルを利用する場合、ファイル内のmeta/license
に記述されたライセンスを確認してください。)
もしあなたが新しい.ksy
を書いたなら、プルリクエストを送りましょう。
(kaitai_struct_formats/CONTRIBUTING.md)例) matrix
ファイルへの保存(np.array)
matrix.pyimport numpy as np import struct def create_header(*mats: [np.ndarray], magic: bytes = None) -> bytes: header = magic header += struct.pack('<H', len(mats)) length = len(header) + 8 * len(mats) for mat in mats: header += struct.pack('<HH', mat.shape[0], mat.shape[1]) header += struct.pack('<I', length) length += 4 * mat.shape[0] * mat.shape[1] return header mat1 = np.random.randint(-1024, 1024, [3, 3], dtype=np.int32) mat2 = np.random.randint(-1024, 1024, [5, 9], dtype=np.int32) mat3 = np.random.randint(-1024, 1024, [2, 2], dtype=np.int32) with open('test.matrix', 'wb') as o: magic = b'THIS IS MAT FILE.\x01\x02' o.write(create_header(mat1, mat2, mat3, magic=magic)) for mat in [mat1, mat2, mat3]: for y in mat: for x in y: o.write(struct.pack('<i', x))
上記のコードで生成されたtest.matrix
を、KSを使用して読み込んでみようと思います。test.matrixOffset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 00000000: 4D 41 54 01 02 2F 03 00 03 00 03 00 20 00 00 00 MAT../.......... 00000010: 05 00 09 00 44 00 00 00 02 00 02 00 F8 00 00 00 ....D.......x... 00000020: DC FE FF FF 49 01 00 00 A7 FF FF FF 17 02 00 00 \~..I...'....... 00000030: 25 FC FF FF 35 FF FF FF B5 00 00 00 CF FE FF FF %|..5...5...O~.. 00000040: E2 FF FF FF 5D 00 00 00 15 FE FF FF 30 FC FF FF b...]....~..0|.. 00000050: 4C 03 00 00 C1 FF FF FF B0 FD FF FF 31 02 00 00 L...A...0}..1... 00000060: 54 03 00 00 C4 FF FF FF 65 FF FF FF D0 FE FF FF T...D...e...P~.. 00000070: 75 01 00 00 DE FE FF FF ED 00 00 00 ED FC FF FF u...^~..m...m|.. 00000080: BE FD FF FF E5 02 00 00 EC FE FF FF 22 FE FF FF >}..e...l~.."~.. 00000090: C3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00 C.......)....... 000000a0: 78 00 00 00 C4 FC FF FF 4C 02 00 00 88 00 00 00 x...D|..L....... 000000b0: 43 FF FF FF 35 FF FF FF A4 00 00 00 CF 02 00 00 C...5...$...O... 000000c0: 3A FF FF FF 33 FF FF FF BD FE FF FF F9 01 00 00 :...3...=~..y... 000000d0: 22 FF FF FF 3A 02 00 00 7C 00 00 00 15 FF FF FF "...:...|....... 000000e0: D8 FE FF FF 42 00 00 00 82 02 00 00 24 02 00 00 X~..B.......$... 000000f0: 8A FE FF FF AF FF FF FF EF 02 00 00 96 01 00 00 .~../...o....... 00000100: 83 01 00 00 2F 02 00 00ファイルの構造は、頭から
1.b'MAT\x01\x02/'
2. 存在する行列の数(2bytes)
3. 各行列ごとのshapeとoffset((8 * 行列の数)bytes)
4. 行列本体
となっています。これを
matrix.ksy
に記述してみます。KSY(Kaitai Struct YAML)では、単一のユーザー定義型(公式から直訳)を宣言します。
ユーザー定義型は
-meta
-doc
-seq
-types
-instances
-enums
で構成されます。
すべてを持つ必要はありません。
詳しい情報は公式リファレンスをご覧ください。meta
metameta: id: matrix endian: le記述するユーザー定義型の名前を
meta/id
に記述します。これは.ksy
ファイルに必ず存在する必要があります。
meta/endian
は構造体で使用するデフォルトのエンディアンを記述します(le
/be
)seq
seqseq: - id: magic contents: ['MAT', 1, 0x2, '/'] - id: header_num type: u2 - id: headers repeat: expr repeat-expr: header_num type: header
seq
に、データの構造を記述していきます。
id
は変数名になります。
データが定数である場合はcontents
に定数を記述します。
値を取得したい場合、type
にデータの型を記述します(詳しくはこちら)。
後述するtypes
に記述した型を利用することもできます。ここではheader
型を利用しています。
repeat
にはexpr
,eos
,until
のいずれかを入れることができます(詳しくはこちら)
expr
を入れた場合、repeat-expr
に繰り返す回数を入れます。types
typestypes: header: seq: - id: shape0 type: u2 - id: shape1 type: u2 - id: offset type: u4 instances: mat_body: pos: offset io: _root._io type: matrix matrix: seq: - id: dim0 repeat: expr repeat-expr: _parent.shape0 type: dim1 types: dim1: seq: - id: dim1 repeat: expr repeat-expr: _parent._parent.shape0 type: s4
types
には、ユーザー定義型をネストして記述することができます。
header
型でinstances
を使用していますが、これはseq
のように順番に存在するもの以外のデータを読み込むために使用することができます。header.instancesinstances: mat_body: pos: offset io: _root._io type: matrix使い方は
seq
によく似ています。
id
はここでいうmat_body
です。
io
は使用するIOストリームです。
pos
にはio
の頭からのバイト数が入ります。
type
はseq
の時と同じです。変数について
一部のフィールド(今回は
repeat-expr
,pos
,io
)では、定数値だけではなく変数を参照できます。
まだ読み込まれていないデータを参照することはできません。
データはツリー構造になっており(ksvを利用するとわかりやすい)、親の要素を_parent
で指定できます。
また、一番上の要素を_root
で指定できます。Visualize
ここまでで、以下のコードを書くことができました。
matrix.ksymeta: id: matrix endian: le seq: - id: magic contents: ['MAT', 1, 0x2, '/'] - id: header_num type: u2 - id: headers repeat: expr repeat-expr: header_num type: header types: header: seq: - id: shape0 type: u2 - id: shape1 type: u2 - id: offset type: u4 instances: mat_body: pos: offset io: _root._io type: matrix matrix: seq: - id: dim0 repeat: expr repeat-expr: _parent.shape0 type: dim1 types: dim1: seq: - id: dim1 repeat: expr repeat-expr: _parent._parent.shape0 type: s4これをksv(Kaitai Struct Visualizer)を使用して可視化してみましょう。
使い方はksv <file_to_parse.bin> <format.ksy>
です。shell$ ksv test.matrix matrix.ksy
ksv[-] [root] 00000000: 4d 41 54 01 02 2f 03 00 03 00 03 00 20 00 00 00 | MAT../...... ... [.] magic = 4d 41 54 01 02 2f 00000010: 05 00 09 00 44 00 00 00 02 00 02 00 f8 00 00 00 | ....D........... [.] header_num = 3 00000020: dc fe ff ff 49 01 00 00 a7 ff ff ff 17 02 00 00 | ....I........... [-] headers (3 = 0x3 entries) 00000030: 25 fc ff ff 35 ff ff ff b5 00 00 00 cf fe ff ff | %...5........... [-] 0 00000040: e2 ff ff ff 5d 00 00 00 15 fe ff ff 30 fc ff ff | ....].......0... [.] shape0 = 3 00000050: 4c 03 00 00 c1 ff ff ff b0 fd ff ff 31 02 00 00 | L...........1... [.] shape1 = 3 00000060: 54 03 00 00 c4 ff ff ff 65 ff ff ff d0 fe ff ff | T.......e....... [.] offset = 32 00000070: 75 01 00 00 de fe ff ff ed 00 00 00 ed fc ff ff | u............... [-] mat_body 00000080: be fd ff ff e5 02 00 00 ec fe ff ff 22 fe ff ff | ............"... [-] dim0 (3 = 0x3 entries) 00000090: c3 02 00 00 11 00 00 00 29 03 00 00 00 01 00 00 | ........)....... [-] 0 000000a0: 78 00 00 00 c4 fc ff ff 4c 02 00 00 88 00 00 00 | x.......L....... [-] dim1 (3 = 0x3 entries) 000000b0: 43 ff ff ff 35 ff ff ff a4 00 00 00 cf 02 00 00 | C...5........... [.] 0 = -292 000000c0: 3a ff ff ff 33 ff ff ff bd fe ff ff f9 01 00 00 | :...3........... [.] 1 = 329 000000d0: 22 ff ff ff 3a 02 00 00 7c 00 00 00 15 ff ff ff | "...:...|....... [.] 2 = -89 000000e0: d8 fe ff ff 42 00 00 00 82 02 00 00 24 02 00 00 | ....B.......$... [-] 1 000000f0: 8a fe ff ff af ff ff ff ef 02 00 00 96 01 00 00 | ................ [-] dim1 (3 = 0x3 entries) 00000100: 83 01 00 00 2f 02 00 00 | ..../... [.] 0 = 535 [.] 1 = -987 [.] 2 = -203 [-] 2 [+] dim1 [-] 1 [.] shape0 = 5 [.] shape1 = 9 [.] offset = 68 [-] mat_body [+] dim0 [+] 2うまく読み込めているようです。
ファイル解凍
本題です。
こちらの記事で圧縮ファイルを作りました。
今回はこの圧縮ファイルをKSを使用して解凍します。
ファイルの構造などは記事をご覧ください。mcp.ksymeta: id: mcp encoding: UTF-8 endian: le seq: - id: file type: file repeat: eos types: file: seq: - id: filename_len type: u4 - id: filebody_len type: u4 - id: filename type: str size: filename_len - id: filebody size: filebody_len process: zlib
meta/encoding
は、type: str
で使用するデフォルトのエンコーディングを指定します。
repeat: eos
はストリームの最後まで繰り返します。
process: zlib
は、読み込んだデータをzlibで解答します。(すごく便利)
※process
の詳しい情報はこちらksc(Kaitai Struct Compiler)を使用して
mcp.ksy
からコードを生成します。usageUsage: kaitai-struct-compiler [options] <file>... <file>... source files (.ksy) -t, --target <language> target languages (graphviz, csharp, all, perl, java, go, cpp_stl, php, lua, python, ruby, javascript) -d, --outdir <directory> output directory (filenames will be auto-generated) -I, --import-path <directory>:<directory>:... .ksy library search path(s) for imports (see also KSPATH env variable) --go-package <package> Go package (Go only, default: none) --java-package <package> Java package (Java only, default: root package) --java-from-file-class <class> Java class to be invoked in fromFile() helper (default: io.kaitai.struct.ByteBufferKaitaiStream) --dotnet-namespace <namespace> .NET Namespace (.NET only, default: Kaitai) --php-namespace <namespace> PHP Namespace (PHP only, default: root package) --python-package <package> Python package (Python only, default: root package) --opaque-types <value> opaque types allowed, default: false --ksc-exceptions ksc throws exceptions instead of human-readable error messages --ksc-json-output output compilation results as JSON to stdout --verbose <value> verbose output --debug enable debugging helpers (mostly used by visualization tools) --help display this help and exit --version output version information and exitshell$ ksc -t python mcp.ksymcp.py# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild from pkg_resources import parse_version from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO import zlib if parse_version(ks_version) < parse_version('0.7'): raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version)) class Mcp(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent self._root = _root if _root else self self._read() def _read(self): self.file = [] i = 0 while not self._io.is_eof(): self.file.append(self._root.File(self._io, self, self._root)) i += 1 class File(KaitaiStruct): def __init__(self, _io, _parent=None, _root=None): self._io = _io self._parent = _parent self._root = _root if _root else self self._read() def _read(self): self.filename_len = self._io.read_u4le() self.filebody_len = self._io.read_u4le() self.filename = (self._io.read_bytes(self.filename_len)).decode(u"UTF-8") self._raw_filebody = self._io.read_bytes(self.filebody_len) self.filebody = zlib.decompress(self._raw_filebody)
mcp.py
が生成されたコードです。
これを使って解凍用のスクリプトを書きましょう。extract.pyfrom mcp import Mcp import os import sys mcps = Mcp.from_file(sys.argv[1]) out = 'output/' if len(sys.argv) >= 3: out = sys.argv[2] for f in mcps.file: if os.path.dirname(f.filename): os.makedirs(os.path.join(out, os.path.dirname(f.filename)), exist_ok=True) with open(os.path.join(out, f.filename), 'wb') as o: o.write(f.filebody)
python extract.py <target.mcp> [output_folder]
で解答することができますファイルの読み込みは、
KaitaiStruct.from_file(file_path)
で。
バイト列をそのまま読み込みたい場合、KaitaiStruct.from_bytes(bytes)
で。
IOストリームの場合はKaitaiStruct.from_io(io)
で。さいごに
KSはかなり便利だと僕は思います。
簡単に記述出来る上好きな言語で利用できるので、新しく覚えるコストがとても少なく済みます。
公式リファレンスは正直読みにくいですが、今後僕のようにKSについて記事を書いてくれる人が増えていくでしょう(多分)。あなたもKSをつかって「解体」してみませんか?
- 投稿日:2019-12-13T20:01:35+09:00
【Hi Py(その1)】とりあえず何かしらを作りたいので、まずは目標を設定する。
【私について】
過去にサポート、ライティング、マーケティング、ディレクション(社内に出来る人がいない為、無知なりに調べてやっていた)などを経験し、
最近はhtml/css/js/php/mysqlの上澄みだけ編集したり、excelを綺麗にまとめて差し上げたりgasを使って業務効率化を計ったりするだけの仕事をやっている。「何だそれは仕事なのか?」なんて言わないで。
そして底辺なので、たまに副業でwordpressのカスタマイズもする。
更に、
・興味のある事がすぐに変わったり
・継続力もなく
・集中力も注意力も散漫なので
・超が付くほど要領も記憶力も悪い上
・めちゃめちゃ怠惰なゲロやば倍満人間だけど、「人は、やれる。」という意志と決意を込めての記念すべき初投稿。
半年前にProgateのPython講座は一通りやり、何かが作れる気もせず全てを忘れるレベルまで来たが、
(気まぐれで)お絵かきする為に購入したiPadの購入を期に、Pythonのお勉強をしようと思い立ったわけである。ただし音楽を作ったりお絵かきをしたり、ゲームをしたり、政治の勉強もしたいので、更新頻度が低くなる事をあらかじめ宣言しておく。
progateでは構文をふわっと理解しただけだったので、今回は実際にデータを動かして楽しくなろう。というのをテーマに独学で勉強していきたい。
<とりあえずの目標設定>
まずは何がしたいのかをはっきりさせる。
と思ったので、やりたい事をわかりやすく出来るよう個人的にやりたい事をメモしていく。
【作ってみたいもののアイデア】
①地域別の経済状態把握アプリ
ResasのAPIとLine notifyを使って、知りたい市区町村の経済循環や外国人率、有効求人倍率、一人当たり賃金など、個人的に気になる情報を綺麗にまとめて表示してくれて、line notifyで通知してくれる感じのもの。
(普段は出来ない政治系の話題をLINE openchatでしてるので、LINEに送るようにしようと思っているが、必須ではないので省くかも)
Pythonista3でWeb APIを取得するアプリを作ってみる
http://tarao-mendo.blogspot.com/2018/03/pythonista3-web-api.html【Pythonista】LineNotifyでLineにメッセージを送る
https://tk-thunder.hateblo.jp/entry/2017/11/28/192929resus API仕様書
https://opendata.resas-portal.go.jp/docs/api/v1/index.html
②Spotifyでのオススメ最適化アプリ
55カ国の音楽を聞いているらしく、ジャンルもバラバラなので、Spotifyのオススメ選曲機能が全く役に立っておらず、情報の流動性も最悪。
脳みそが停滞しているのを日々感じながらspotifyと接しているので、改善したい。なので、bandcamp API、Soundcloud API、Youtube API、Spotify APIなどで各アカウントを連携させて、その日の気分に分けたオススメを収集出来るようなアプリを作ってみよう。
という事で。具体的には各ストリーミングサービスにログインした上で、そこでお気に入りに入れたアーティスト名や楽曲名をspotifyの中で検索をかけて、出て来た楽曲の情報と、その日の気分を元に、新たにspotifyでオススメを探して貰えないかなという。
しかし、以前phpで同様の事をしようとしたものの、
そもそもtokenの受け渡しがうまく出来ずに断念したので今回もちょっと怪しくはあるが、せめて地道に向かっていきたい。Pythonistaのライブラリ追加方法。(Spotipyのimportに必要)
https://piruty2.hatenablog.jp/entry/%3Fp%3D458
spotify API
https://qiita.com/EkatoPgm/items/289b2efcdb5af49843c1
- 投稿日:2019-12-13T19:32:09+09:00
Pythonの配列っぽいやつらを比べよう
本稿はNCC Advent Calender 2019の14日目記事です.
はじめに
プログラム書くとき,配列って大事です.
しかし,Python 配列っぽいの多すぎ問題があります.
Pythonの標準機能だけでも
list
,dict
,set
,tuple
があります.
加えてnumpy
などのライブラリを使い始めるとさらに似たようなものが増えます.なので,今回はそいつらを以下の4点でまとめて使い分けができるように解説していきます.
- 概説
- いいとこ
- わるいとこ
- 所感
あくまで使い分けのための記事なので,細かい使い方などには触れません.
また,配列が何かはなんとなくわかってる前提で書きます.
さらになるべく簡単な言葉を使って説明します.
* 説明の易化のために,本質とは異なる説明をしている場合があります.対象読者
- Pythonを使い始めたけど,配列多すぎて困ってる
- 他の言語やってたけど,Pythonでの各配列の立ち位置がわからない
- 実装中,ここどれ使うべきかなってなった人
- numpyなどのライブラリを使い始めた人
とりあげるもの
Python標準機能
list
dict
set
tuple
ライブラリ
numpy
numpy.array
/numpy.ndarray
numpy.matrix
本編
list
概説
list
は簡単に言えば一番の基本のやつです.
他言語でいう一般的な配列と同じで,Pythonでは「リスト」と呼ぶのが一般的です.
記号としては[]
なので,これで囲まれて,中に,
がいっぱいあったらリストだと思ってください.Pythonではリスト内の型が複数でも構いません.
また,同じ値を何個でも入れることができます.
その他,世間一般に言う配列の機能を持っています.あとで,
dict
以下の説明をするときに,よくlist
と比較すると思うので,それでlist
の性質はそれを見ながら把握してもらえるといいかなと思います.
(普通のやつなのであまり説明がないです.)いいとこ
- 扱いが簡単
- 難しいこと考えず,とりあえず
list
にしとけば実装はできるわるいとこ
- 次元が多くなるとわかりにくくなる
- 一個一個の配列の長さをまとめて見れない(
numpy
のshape
みたいなのがない)- 参照が0から続く数字のみ
所感
良くも悪くも原点.
numpy
とかpandas
でも一旦list
に変換してから使うこともザラですね.
1次元で長さが不定な場合や,順序にい意味があるが番号に意味がない時もlist
です.
ただ多次元ではあまり向かないです.
(そもそも次元が多すぎるような設計をするな)
数値であればnumpy
,複合的なデータであればdict
と組み合わせるとスッキリします.
dict
概説
いわゆる連想配列ってやつです.
Pythonでは辞書型またはdictionary
(略してdict
)です.
記号としては{}
で,{key: value}
にように,一つ一つの要素が:
で結ばれます.
例に及んで詳しくは説明しません.レファレンスなどを参照してください.
dict
のkey
の型はなんでもよかった気がしますが,文字列か整数を使った方がわかりやすいです.
あと中身がいっぱいある時はkey
ごと改行すると見やすいです.またインデントは揃えましょう.(jsonみたいに)
例を載せておきます.ncc = { 'name': 'ncc', 'full name': 'nakano computer club', 'estimate': 2015, 'web site': 'https://meiji-ncc.tech/' }*インデントに関してはいくつか宗派があるのでお好みのものを使用しましょう.
また一行一要素で長すぎる場合は,3個ずつなどにしたほうが見やすい場合もあります.
この辺は内容に合わせて臨機応変に対応しましょう.いいとこ
- わかりやすい
- jsonとの相互利用がしやすい
- 参照に文字列が使える
わるいとこ
- 慣れないとエラーのもと
- 深くなると取り出しづらい
dic['first']['second']['third']
みたいになっていく所感
値とその名前が重要になる時はこいつで決まり!
数字でも間隔が不規則な時はdict
のほうが扱いやすいです.
あと,pandas
(ライブラリ)のDataFrame
に変換する前に一旦dict
でまとめることも多いです.そのほうが扱いやすいので.
key
とvalue
を結びつけるイメージで使うといいかと思います.
なんやかんやjsonとの対応が便利だったり..(jsonライブラリ使えば読み書き簡単)
set
概説
set
は被り禁止のlist
というのが端的な表し方です.
記号は{}
でdict
と同じですが,:
は使いません.
list
と同じように,
で値をポンポン並べていきます.
なので,{}
で囲まれて,,
がいっぱいあったらset
です
厳密には集合を表すものです.
なので集合演算もできます.(ここでは触れませんが)
list
は[0, 1, 2, 1, 0]
のように同じ値を持つことができますがset
ではできません.
上記のlist
をset
に変換すると{0, 1, 2}
となります.
これは数字以外でも同じです.逆を言えば,被りをなくたい時は
set
に変換するといけます.
この場合,リストとしてまた扱いたい場合はset
の上からlist
に変換してリストに戻す必要があります.
例を書いておきます.list_duplicate = [0, 1, 2, 2, 1, 0, 3] list_non_duplicate = list(set(list_duplicate)) print(list_non_duplicate) # out: [0, 1, 2, 3]いいとこ
- 被りなく入れられる
- 集合演算ができる
わるいとこ
- 要素の順番にルーズ
- (
list
から変換した場合,被りが除かれてその分前につめます)- 記号が
dict
と一緒でわかりづらい(若干)所感
最初から
set
で使うことはあまりないです.
被りをなくしたいときや,複数のリストの要素の共通部分などを抽出する時などにリストから変換することが多いです.
なので,実装時に集合的アプローチをとるときに使うものと思っておけばいいと思います.
tuple
概説
tuple
はちょっとお堅いlist
と言うべきでしょうか.
少しクセがあります.
記号は()
です.基本は
list
のようなものですが,色々違います.
大まかに言えば,一度作ったものをいじくることができません.
後ろに別のタプルを追加することはできます.(1)
タプル自体を丸々別のものに変えることもできます.(2)
その他,要素の書き換えなどはできません.
やや難しいので,実例で示します.t = (0, 1, 2) # 後ろにタプルを追加 t += (3, 4) # OK(1) # タプル自体の書き換え t = (0, 1, 2) # OK(2) # 要素の書き換え t[0] = 1 # Error代入や,削除などにはそもそもメソッドが用意されてません.
また,要素の書き換えができないので,特定の順番を変えることもできません.
この辺をやるには,list
に変換する必要があります.いいとこ
- 一回作ったら書き換えできない
- 順番も作成時のものが常に保証される
- 挙動を確定させられる
- 定数として使える
わるいとこ
- 柔軟性が皆無
- 扱いづらい
- エラーのもと
所感
Pythonには定数を表す型(jsでいうconst)がないので,それを擬似的に
tuple
で行うことができます.
ただ,動的なことはいっさいできないので,あまり使いません.
ライブラリのメソッドの返り値がtuple
のことがあるので,そこでつかうぐらいです.
続いてPythonのライブラリである
numpy
における配列系の説明に移ります.
その前に,numpy
の説明をさっくりします.(補足)
numpy
とは
numpy
は線形代数でやる行列演算を行えるライブラリです.
配列の要素同士の足し算,引き算.
配列全体に数値をかける時などに使えます.
もっと高度なこともできますが,配列の数値計算が便利になると思っておけば大丈夫です.
numpy.array
/numpy.ndarray
概説
numpy
の1次元の配列がnumpy.array
です.
多次元はnumpy.ndarray
になります.
1次元でも多次元でも扱いはあまり変わりません.
こいつはライブラリなので,特定の記号で表すことはできません.
list
などをnumpy.array()
で囲んであげると変換されます.
* 多次元でもnumpy.array()
で囲みます,numpy.ndarray
では囲めません.
list
との違いは何と言っても,配列同士の演算ができることです.
ただ,空のnumpy.array
を作成することはできません.
list
から変換しましょう.import numpy as np # 通常のlistを定義 list_num0 = [0, 1, 2, 3, 4] # numpy配列に変換 np_num0 = np.array(list_num0) print(np_num0) # out: [0 1 2 3 4] # 直接numpy配列を生成 np_num1 = np.array([5, 6, 7, 8, 9]) print(np_num1) # out: [5 6 7 8 9] # numpy配列をlistに変換 list_num1 = list(np_num1) print(list_num1) # out: [5, 6, 7, 8, 9] # list,numpy配列それぞれを2倍してみる list_num0_twice = 2*list_num0 print(list_num0_twice) # out: [0, 1, 2, 3, 4, 0, 1, 2, 3, 4] np_num0_twice = 2*np_num0 print(np_num0_twice) # out: [0 2 4 6 8] # list, numpy配列でそれぞれ足し算してみる list_num_add = list_num0 + list_num1 print(list_num_add) # out: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] np_num_add = np_num0 + np_num1 print(np_num_add) # out: [ 5 7 9 11 13]このように,配列の要素内の演算を
numpy.array
では簡単にやることができます.いいとこ
- 配列の要素同士の演算を簡単にできる
- 多次元でも扱いやすい
- 計算が早い(ことが多い)
わるいとこ
- 慣れないと扱いにくい
- 1つ1つの長さが違う多次元配列には向かない
- 数値以外は使いづらい
所感
数学的なことをやるなら絶対
numpy
です.
scipy
という応用的な計算(積分とか)ができるライブラリとも相性がいいです.
計算をいっぱいやるときに慣れてきたら,使ってみるといいと思います.
list
との違いを把握すれば,使いやすいです.
numpy.matrix
- 使ったことない
- 色々調べましたが,
ndarray
使えばいいみたいです- m×nの行列を使う時は便利みたい
- 以上.
まとめ
それぞれを一言でまとめるとこうなります.
list
: 基本
dict
: 名前と値の結びつきが強い
set
: 被りなしのlist
tuple
: 書き換え不可のlist
numpy.array
/numpy.ndarray
: 数値計算特化list
numpy.matrix
:numpy.ndarray
とこんがらがるから使わないフローチャート
個人的にどれを使うかを決める時のなんとなくのフローを図で示すとこうなります.
実際はもう少し複雑ですが,慣れるまではこんな感じで考えるといいかと思います.
tuple
は使わないので入ってません.
(本当はset
もあまり使わない)終わりに
今回はPythonの配列っぽいやつらを比べました.
細かいところやライブラリを含めるともっとあります.
しかし,この辺が基本となるので,ここが理解できれば,他の理解も捗るかと思います.
- 投稿日:2019-12-13T19:16:03+09:00
[with構文の理解・応用] Pythonの標準出力の出力先を柔軟に切り替える
Pythonの標準出力(printやsys.stdout)の出力先(file出力, console出力)をwith構文で適宜切り替えながら使う方法を紹介します。
動機
以下の様にすると標準出力の出力先を処理に応じて変更できますが、
import sys # 一時的にfile出力に変更 sys.stdout = open('out.log', 'a+') ... sys.stdout.write('fugahoge') ... # console出力に戻す sys.stdout = sys.__stdout__処理が終わった際に、元に戻す処理が手間なので何かないかなーと考えていて、with構文を使うとfile open()後のclose()が不必要になったことを思い出して、本記事を書くに至りました。
本記事に書いてあること
- 標準出力の変更法
- with構文の説明
- with構文での標準出力の出力先の変更法
with構文
with構文は、以下の様なファイルの読み書きやtensorflowのgradient_tapeなど色々な場面で使われています。
with open('', 'r') as f: f.read()参考: with構文(Python)
with構文では何が起きているか
with構文を使うと何が起きているかというと、withの後にinstanseを指定(もしくは生成)すると、以下のような流れで処理が走ります。
1. そのinstanseの.__enter__()
メソッドが呼ばれる
2. with構文内の処理がはしる
3. そのinstanseの.__exit__()
メソッドが呼ばれるコード例
以下のようなコードを実行すると、
class Logger(): def setIO(self, *args, **kwargs): # TestIO instanseの生成 return TestIO(*args, **kwargs) class TestIO(): def __enter__(self): print('enter') def __exit__(self, *args): print('exit') logger = Logger() with logger.setIO(): print('---- in with syntax ----')以下のように出力されます。
consoleenter ---- in with syntax ---- exitこれで、
.__enter__()
メソッド -> with構文内の処理 ->.__exit__()
メソッド、の順に処理がはしっていることが確認できました。参考:
- with構文とは何なのか - 年中アイス
- [Python] with構文で使用できるクラスを実装する標準出力の出力先をwith構文で柔軟に切り替える
最後に、本記事の主題である「標準出力の出力先をwith構文で柔軟に切り替える」のコード例を紹介します。
import sys class SetIO(): """with構文でI/Oを切り替えるためのクラス""" def __init__(self, filename: str): self.filename = filename def __enter__(self): sys.stdout = _STDLogger(out_file=self.filename) def __exit__(self, *args): sys.stdout = sys.__stdout__ class _STDLogger(): """カスタムI/O""" def __init__(self, out_file='out.log'): self.log = open(out_file, "a+") def write(self, message): self.log.write(message) def flush(self): # this flush method is needed for python 3 compatibility. pass print('before with block') with SetIO('out.log'): # file出力に切り替え print('---- in with syntax ----') print('after with block')上記のコードを実行すると、
コンソールには、以下が出力されます。consolebefore with block after with blockそして、ファイル (out.log)には以下が出力されます。
out.log---- in with syntax ----上記の方法で標準出力先をwith構文を切り替えることができました。
まとめ
出力先の変更は、組み込みのlogger moduleの名前空間を使えば可能ですが、細かく出力先をかえるには不便だと思っています。(loggerに精通していないだけの可能性はあります)
そこで、本記事で紹介した手法が使える場面もあると考えています。
何かの参考になれば幸いです!Refs
- 投稿日:2019-12-13T19:07:33+09:00
Python(Bottle)で爆速!WebAPI開発
概要
とあるWebアプリ開発時に、別サーバーのWebAPIを呼ぶ必要が出てきました。
開発時には擬似環境が必要となるとのことで、簡単なWebAPIを爆速で構築したメモです。環境
言語
Python 3.7.5
理由:WindowsのexeインストーラーがあったからWebフレームワーク
Django: 大規模向け、機能豊富
Flask: 中小規模向け、そこそこの機能、WSGI準拠
Bottle: 小規模向け、軽量、WSGI準拠
上記の特性から今回はスピード感ありそうなBottleを採用しました。サーバー
Windows Server
Apache 2.4.37 VC15Pythonのインストール
下記サイトからインストーラーをダウンロードし、インストーラーを起動
https://www.python.jp/install/windows/install_py3.htmlインストール時のオプションで「全てのユーザーにインストール」と「環境変数へパスを追加」みたいなのにチェック入れる。
(チェックを入れないと、OSのログオンユーザーのフォルダにインストールされ、環境変数も自分で設定しないといけません。)インストーラーが完了したら、コマンドプロンプトで
python --versionバージョンが出てくればインストール完了。
必要なパッケージのインストール
続いて、コマンドプロンプト(管理者権限で実行)で下記コマンドたちを実行する。
Bottleのインストール
pip install bottleDBはPostgreSQLを使用しました。
DB接続にはpycopg2を使用します。pip install psycopg2Hello Worldする
動作することを確認します。
index.pyを作成し、以下の通り編集します。index.pyfrom bottle import route, run, template @route('/hello/<name>') def index(name): return template('<b>Hello {{name}} </b>!', name=name) run(host='localhost', port=8080)これだけです・・・やばくないですか・・・
コマンドプロンプトで
python index.py
を実行するとlocalhostで起動します。
そしてブラウザで http://localhost:8080/hello/tsumasakky にアクセスすると
画面にHello tsumasakky!と表示され、成功です。Webサーバー構築
Apacheのインストール
https://www.apachelounge.com/download/VC15/
このあたりからダウンロード。解凍してでてきた"Apache24"を、サーバーのC直下にコピー
WSGIのセットアップ
以下のサイトからファイルをダウンロード
https://www.lfd.uci.edu/~gohlke/pythonlibs/#mod_wsgiファイル名ですが、
Apache 2.4 VC "15"
Python"37"
Windows "64"bitなので、「mod_wsgi-4.6.8+ap24vc15-cp37-cp37m-win_amd64.whl」
をダウンロードしました。環境に合わせてダウンロードしてください。コマンドプロンプトでインストールします。(ダウンロードしたファイルの場所を指定)
pip insatll mod_wsgi-4.6.8+ap24vc15-cp37-cp37m-win_amd64.whlhttpd.confの編集
まず、mod_wsgiの設定を取得するため、コマンドプロンプトで以下のコマンドを実行。
mod_wsgi-express module-config
すると、以下のように表示されます。
LoadFile "c:/program files/python37/python37.dll" LoadModule wsgi_module "c:/program files/python37/lib/site-packages/mod_wsgi/server/mod_wsgi.cp37-win_amd64.pyd" WSGIPythonHome "c:/program files/python37"表示された設定をコピーし、Apacheのhttpd.confに追記する。(Load Moduleの最後くらいに)
さらに、このあと作成するアダプターへのルーティングを追記する。
config:httpd.conf
WSGIScriptAlias /api C:\Apache24\htdocs\adapter.wsgi
※ユーザー固有のフォルダとかに配置するとアクセス権限なくて403エラーになったり。
アダプターの作成
wsgiの設定がうまくいっているかどうかを確認するために、WSGIScriptAliasで設定したパスにadapter.wsgiファイルを作成します。
adapter.wsgidef application(environ,start_response): status = '200 OK' output = b'Hello World!' response_headers = [('Content-type','text/plain'), ('Content-Length',str(len(output)))] start_response(status,response_headers) return [output]※ちなみに"output"に普通の文字列入れると500エラー
output = 'Hello World!' #Error!
→バイト文字列にする必要あり
https://stackoverflow.com/questions/34838443/typeerror-sequence-of-byte-string-values-expected-value-of-type-str-foundApacheを再起動して、http://localhost/api にアクセス。
"Hello World"がでてきたら成功です!アダプター経由で自前のアプリを起動
先ほどの
index.py
とadapter.wsgi
を編集します。adapter.wsgiimport sys, os dirpath = os.path.dirname(os.path.abspath(__file__)) sys.path.append(dirpath) os.chdir(dirpath) import bottle import index application = bottle.default_app()index.pyfrom bottle import route, run, template from bottle import TEMPLATE_PATH import psycopg2 @route('/hello') def hello(): return template('<b>Hello World!</b>',) @route('/users') def users(): conn = psycopg2.connect("postgresql://postgres:postgres@localhost:5432/sample") cur = conn.cursor() cur.execute('select * from sample.users;') users = cur.fetchall() cur.close() conn.close() return template('<b>{{users}}</b>!', users=users) if __name__ == '__main__': run(host='localhost', port=8080, debug=True, reloader=True)Apacheを再起動し、http://localhost/api/users にアクセスすると、DBからユーザーの一覧が取得できました。
感想
サーバーの構築には若干手間取ったものの、実際のアプリコーディングは10分程度でできました!
超爆速です!ちなみにこのあとはpycopg2でDBから辞書型でデータ取得し、dict -> jsonに変換して返す~
みたいな対応でAPIを構築しました。上記のやり方でできない、またはこんな方法もある!という方はぜひコメントくださいませ!
最後までお読みいただきありがとうございました。(m´・ω・`)m
参考
- 投稿日:2019-12-13T19:06:18+09:00
スクレイピングする際の自分なりのベストプラクティス
これはPython Advent Calendar 2019の13日目の記事です。
Python要素薄めになっちゃったけど、スクレイピングの文脈でPythonよく出てくる気がするから許してください概要
- (自分なりの)スクレイピングシステム構築方法のベストプラクティス的なものがちょっとだけ定まった
- 公開していろいろ意見を聞いてみたい
- たぶんウェブデベロッパーがスクレピングやるときに知っておくとちょっとだけ幸せになるかもしれない
- 誰かの役に立てればいいなあ(希望)
構築方法の重要な点
重要な点は以下の2点
- DOMにアクセスしてデータを取得するスクレイピングする処理部分はJavaScriptを使う(とくにウェブデベロッパーの方は)
- Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う
構成図は以下のような感じです。(OSとか書いてないけど、だいたいのイメージ)
Pythonの部分は任意でSeleniumを叩ければなんでもいいです。(以下のPythonはSeleniumを使うモノとして読み替えてください)JavaScriptを使ってスクレイピング処理
スクレピング関係のシステムを作るときにめんどくさいのはライブラリ選定だったりします。
個人的にPythonは好きだったりするので、昔は
PyQuery
とかBeautifulSoup
を触っていました。しかし、結構かなり面倒くさいです。
どういう風にセレクタを書けばいいのかググって実行して、ミスって、修正して、...みたいな感じなります。
最終的な結論が「Chrome Developer ToolsでJSのセレクタを書いて、欲しい情報を取得するJSを書いて、それをSeleniumから実行させる」です。(これが一番楽だと思います。)
(おそらくjQueryを触ったことがある人なら一番とっつきやすいはず)
以下のようなかんじ。
res = driver.execute_script(driver, """ return (() => { // 以下をChrome Developmer Toolsでいろいろ試して作成する let tmp = []; $(".sample-area li a").each( (idx, element) => tmp.push($(element).attr("href")) ) return tmp })() """)Chrome Developmer Toolsは以下のようなやつ。F12とか押したらでます。(Consoleタブを開くとJSがページ内で実行できます)
簡単な話、クラス情報とか丸コピして$(".class").text()
とかやるだけでデータとってこれたりします。これをライブラリの特有の関数でいろいろやろうと思うとめんどくさいのはなんとなくわかるとおもいます。
また、スクリプトがJSなのでスクレイピングライブラリに依存せずに、移植も楽です。
Chrome, ChromeDrive, Seleniumとかのスクレピング環境にはDockerとかを使う
ChromeDriverの設定とかChromeとのバージョンの相性とかいろいろやるのは人生の無駄遣いです。
Dockerとか環境をまるっともってこれるものを使いましょう。単純なHTML,CSSのみで構成されているサイトなら
curl
とかで引っ張ってきてやるのもいいと思います。(単純なウェブサイトなら←ここ重要)
サーバーサイドでレンダリングされていても、内部のJSでわりとなんかゴリゴリいじってるサイトは多いです。Python + Chrome + ChromeDrive + Seleniumの構成のサンプルを作ってみたのでスターをつけてもらうと喜びます。↓
https://github.com/redshoga/python-selenium-container実装の流れ
- スクレピングで取得したいデータ、場所を明確にする。
- Chrome Developer Toolsでその取得したいデータが取得できるスクリプトを作成。
- Seleniumで作ったスクリプトを動かしてデータを煮るなり焼くなりする。
おわり
マサカリダニゲロー>??? ??
- 投稿日:2019-12-13T19:04:15+09:00
trigramで自分のツイートからランダム文を生成する
記録です。説明軽くしか書いてないです。
やり方
準備
自分のツイート履歴をダウンロードします。Twitterページで「設定とプライバシー」→「アカウント」→「Twitterデータ」→「Twitterデータをダウンロード」でリクエストして、しばらくしてメールアドレスにダウンロードリンクが届くのでそこからダウンロードします。
2019年の夏以降ダウンロードデータの仕様が変わってtweets.csv
がtweets.js
になってしまったそうなので、面倒なので他の方が書いてくれたツールを利用してtweets.csv
を作ります。(https://17number.github.io/tweet-js-loader/ )作業場と同じディレクトリにtextフォルダを作ってその中に
tweets.csv
を放り込んで準備完了。次に
tweet.py
の中身について。
まずは以下の部分でtweets.txt
を作ります。tweets.csv
からツイート本文を抜き出してtxtファイルにしています。tweet.pyimport csv import re rawfile = "text/tweets.csv" infile = "text/tweets.txt" outfile = "text/tweets_wakati.txt" with open(rawfile,'r') as f: reader = csv.reader(f) with open(infile,'w') as f: for d in reader: if len(d) > 2: f.write(d[2]) f.write('\n')次にjanomeを利用して単語を分ち書きにして、モデルに学習させます。janomeが入ってない人は
pip install janome
を先にしてください。
ちなみに、アルファベットや特定の記号、質問箱などを排除してなるべく日本語で意味が通る文ができるようにしています。tweet.pyfrom janome.tokenizer import Tokenizer t = Tokenizer() with open(infile,'r') as f: data = f.readlines() p = re.compile('[a-z]+') p2 = re.compile('[:/.@#質問●]+') with open(outfile,'w') as f: for i in range(len(data)): line = data[i] if p2.search(line): pass else: for token in t.tokenize(line): if p.search(str(token.surface)): pass else: f.write(str(token.surface)) f.write(' ') f.write('\n') words = [] for l in open(outfile, 'r', encoding='utf-8').readlines(): if len(l) > 1: words.append(('<BOP> <BOP> ' + l + ' <EOP>').split()) from nltk.lm import Vocabulary from nltk.lm.models import MLE from nltk.util import ngrams vocab = Vocabulary([item for sublist in words for item in sublist]) print('Vocabulary size: ' + str(len(vocab))) text_trigrams = [ngrams(word, 3) for word in words] n = 3 lm = MLE(order = n, vocabulary = vocab) lm.fit(text_trigrams)最後にランダム文生成です。
tweets.pyfor j in range(10): # context = ['<BOP>'] context = ['<BOP>','<BOP>'] sentence = '' for i in range(0, 100): # contextのうち最後の2単語から次に繋がる確率0じゃない単語をランダムに選ぶ w = lm.generate(text_seed=context) if '<EOP>' == w or '\n' == w: break context.append(w) sentence += w print(sentence+'\n')ランダムに10個の文が出力されます。
コピペするときは上に書いたコードを一つのファイルにまとめるか、jupyter notebookでセルに分けて実行するかなどしてください。
モデルの学習に少し時間がかかる場合があるので、後者の方法がオススメです。結果
上みたいな感じで10個の文が出力されるはずです。
結構面白いので無限に試せちゃいます。皆さんも是非やってみてください。
- 投稿日:2019-12-13T18:47:12+09:00
processをkillした日(flaskでAddress already in useというerrorが出た)
起動しようとするとerrorが出た
OSError: [Errno 98] Address already in use
process を探してps -fA | grep python任意のものを葬る
kill xxxx(任意)参考
http://yusuke-ujitoko.hatenablog.com/entry/2018/07/14/144227
https://qiita.com/shuntaro_tamura/items/4016868bda604baeac3c
- 投稿日:2019-12-13T18:42:33+09:00
Pythonでつくる彼女入門 ~Tinder自動化プロジェクト~ 第6話
目次
やったこと 主な出来事 第1話 自動右スワイプ 第2話 自動メッセージ送信 女性とマッチした 第3話 ライブラリ化 マッチした女性とLINEを交換した 第3.5話 アクセストークンの再取得 これまでのコードではトークンが取得できなくなっていた 第4話 データ収集 LINEの返信が来なくなった 第5話 データ分析 プロフィール文編 仲良くなった人から情報商材を勧められた 第6話 データ分析 画像編 リアルの知り合い女子から最近やたらと深夜に電話がかかってくる(?) コードはGitHubから閲覧できます。
前回までのあらすじ
- TinderのAPIを簡単に叩くためのライブラリを作成した
- マッチする相手を探すべく、データの収集を行った
- プロフィール文の分析を行った
近況
最近寝不足です。
また1人、知り合いに彼女ができたようです。この、裏切り者が...。
そういえば、先日GPUを入手したので、今回からGPUを利用して学習を行っています。先行研究の話
Tinderのスワイプ戦略になんらかのモデルを噛ませることを考えたのは、僕が初めてではありません。
僕が調べただけでも、顔写真を載せている人だけをスワイプしている人[1]や自分の好みの写真をDNNに学習させてスワイプしている人[2][3]、顔に加工がされているかどうかを判定している人[4]などがみつかりました。...さて、我々は何がしたいのでしたっけ。
機械学習とは厄介なもので、動くとそれなりに楽しいので、気がつくとドンドンと目的が当初の目標からずれていきます。改めて原点に立ち返ると、我々は彼女が欲しくてコードを書き始めたのでした。
世の中に出てくる情報の大半は、「不要な人とマッチしない」ためのコードですが、我々が必要としているのは「なるべく多くの人とマッチする」ためのコードです。なにせ、現状では1日あたり1人とマッチするかどうかという状況なので、好みでない人とマッチした場合は手動で解除すればいいのです1(大量のマッチが発生して手動での解除が追いつかず困っている人にとっては、もちろんこの限りではありません)。自分から出会いの幅を狭めてどうする2。彼女を作るための努力という意味では、[5]が参考になります。こちらは、今我々が挑戦しているようにマッチしそうな人を探すのではなく、「なるべく多くの人にLikeを貰えるプロフィールを作成する」というアプローチで出会いを試みています。使うマッチングサービスが異なればユーザーの評価戦略も異なりうるので単純適用はできませんが、面白い試みだと思います。いつかTinderでもやってみたいのですが、その場合は自己紹介文を変えたプロフィールを複数用意してA/Bテスト3や、そのスコアを基に強化学習を行うことになるのでしょうか。十分なデータの収集のためには長い時間と大量の電話番号が必要なことが予想され、正直面倒くさいので、この方針は自分の中では保留になっています。
なお、[5]によると、「学歴」「子供がほしいか」「社交性」「お酒」に関する適切な記述を自分のプロフィールに含めると女性から右スワイプされやすくなるようです4。画像認識
上に長々と余計なことを書いてしまいましたが、要は
- In: プロフィール写真
- Out: マッチするかどうか
という機械学習機を作りたいという話です。
モデル構築
画像認識といえば、ということで、CNNを用いてプロフィール画像からマッチするかどうかを推定します。
まずはdataフォルダーから画像を読み込みます。analytics.pyimport pandas as pd import cv2 import numpy as np from tqdm.notebook import tqdm import os import re filePath = "data/tinder.xlsx" imagePath = "data/photos" df = pd.read_excel(filePath) df.drop_duplicates(inplace=True, subset="id") df.set_index("id", inplace=True) X=[] y=[] for fileName in tqdm(os.listdir(imagePath)): try: id_ = re.match("([a-z0-9]*)-\d( \(\d\))?.jpg",fileName).group(1) match = df.loc[id_]["match"] filePath = os.path.join(imagePath, fileName) img = cv2.imread(filePath) img = cv2.resize(img, (120,120)) X.append(img) y.append(match) except: pass X=np.asarray(X) y=np.asarray(y)画像のサイズは120*120で統一しました。
読み込んだ画像を255で割って0-1の範囲に収め、trainとtestに分割します。analytics.pyfrom sklearn.model_selection import train_test_split X = X/255 X_train, X_test, y_train, y_test = train_test_split(X, y, random_state=8888)データの準備ができたので、CNNを構築します。今回は、2層の畳み込み層+2層の全結合層のモデルを用意しました。
analytics.pyimport keras from keras.models import Sequential from keras.layers import Conv2D, Dense, ReLU, Dropout, Flatten, MaxPool2D def getModel(): model=Sequential() model.add(Conv2D(3,3,input_shape=(120,120,3))) model.add(ReLU()) model.add(MaxPool2D((2,2))) model.add(Dropout(0.25)) model.add(Conv2D(3,3,padding="same")) model.add(ReLU()) model.add(MaxPool2D((2,2))) model.add(Dropout(0.25)) model.add(Flatten()) model.add(Dense(1024)) model.add(Dense(2,activation="softmax")) return model訓練及び予測を行います。
analytics.pymodel = getModel() model.compile(optimizer=Adam(), loss='categorical_crossentropy', metrics=["accuracy"]) model.fit(X_train, to_categorical(y_train), epochs=30, validation_data=(X_test, to_categorical(y_test))) y_pred = model.predict(X_test) y_pred = np.exp(y_pred) y_pred = (y_pred/np.sum(y_pred, axis=1).reshape(-1,1))[:,1] print(roc_auc_score(y_test, y_pred)) #>>0.5116927510028815auc0.51...ランダムよりはマシと言ったところでしょうか。
あまり使い物になりません。マッチしたデータ少ないしな...そこで、転移学習を試してみたいと思います。
転移学習では、事前になんらかのタスクを学習させたモデルを、他のタスクに適用します。CNNの画像認識モデルにおいては、畳み込み層で画像の普遍的な特徴が抽出されるらしいことが経験的に知られており、最後の全結合層を適切に作り直すだけで別のタスクを行うモデルが出来上がります[6]。
CS231n: Convolutional Neural Networks for Visual Recognition Lecture 7より今回はVGG16をもとに、全結合層を追加してマッチ確率を出力させてみます。
analytics.py#データの準備は以前と同じで、X_train, y_train, X_test, y_testはすでに準備されているものとします。 from keras.applications.vgg16 import VGG16 def getModel(): model = VGG16(weights="imagenet", include_top=False) x = model.output x = GlobalAveragePooling2D()(x) predictions = Dense(1, activation="linear")(x) model = Model(inputs=model.input, outputs=predictions) for layer in model.layers[:-3]: layer.trainable=False return model model = getModel() model.compile(optimizer=Adam(), loss="mse", metrics=["mse"]) model.fit(X_train, y_train, epochs=10, validation_data=(X_test, y_test)) y_pred = model.predict(X_test) print(roc_auc_score(y_test, y_pred)) #>>0.6025131864722308無事aucが0.6を超えてくれました。ようやく使えるモデルが完成したのではないでしょうか。
次回は文章と画像、それからテーブルデータをもとに、いよいよマッチする人を探し出します。クリスマス前には完成させたいなぁ。待ってろよ!未来の僕の彼女!!
参考文献
[1]https://note.mu/sarasara201512/n/n20ec9765a387
[2]https://qiita.com/KR_bangkok/items/00b5ed45f5a8c1428960
[3]https://github.com/joelbarmettlerUZH/auto-tinder
[4]https://blog.aidemy.net/entry/2018/07/05/172157
[5]https://qiita.com/data_psyence/items/54bab846337fe1ca61e4
[6]https://qiita.com/ANNEX_IBS/items/55c7a8984fe88a756965
この理由から、ビジネス勧誘等を行う邪魔なアカウントを機械的に排除することも現時点では考えていません。わざわざモデルを作らなくても、実際に会話してみればすぐに分かることなので。 ↩
もちろん、顔の有無などを特徴量として使うことはできます。 ↩
先日Googleの人と仲良くなったのですが、その人の同僚が京大出身で、自己紹介の学歴欄を埋める/埋めないでA/Bテストを行ったそうです。結果としては、「大学名(京都大学)を記入した場合としなかった場合では、どちらも0人の女性とマッチし、有意な差は見られなかった」とのことでした。あまりにも悲しい結末で、それ以上の詳細は聞けませんでした。 ↩
「子供がほしいか」という質問項目がデフォルトで存在するから機能しているのであって、自由記述であるTinderにおいてこんな内容をわざわざ書くと敬遠される気もします。 ↩
- 投稿日:2019-12-13T18:30:47+09:00
SARIMAモデルとProphetモデルの時系列データ予測の比較
こんにちは。
未来検索ブラジルの菅藤です。今日は何番煎じかわからないけど時系列データの予測について書いてみたいと思います。
1. はじめに
時系列データの予測って基本的にそんなに使えないイメージがあるのですが、どれほどのものか、実践で使えそうかなどをみていきたいと思います。
具体的にやってみたのは以下の通り
Twitterのフォロワー数を日々取得する
あの渋いAPIで頑張る自分のTwitterのフォロワー数の予測を立ててみる
(1) SARIMA modelで予測
・[Combining neural network model with seasonal time series ARIMA model]
https://www.sciencedirect.com/science/article/pii/S004016250000113X
・[SARIMAで時系列データの分析(PV数の予測)]
https://www.kumilog.net/entry/sarima-pv @xkumiyu(2)Prophet modelで予測
・[Prophet公式]
https://facebook.github.io/prophet/docs/quick_start.html
・[時系列解析ライブラリProphet 公式ドキュメント翻訳1(概要&特徴編)]
https://qiita.com/japanesebonobo/items/96868e58d4da42d36807 @japanesebonobo今回の内容
ツイートもせず、日々減っていくフォロワー数の予測は僕の心をさらに抉ってくれる。
先に結論を言うと、フォロワー数は減るし、増える目処は立たない。2. 環境
- マシン
- Mac --version 10.15.1
- Python
- Python3 --version 3.7.0
3.下準備
日々のフォロワー数のデータはこんな感じ。
見るに耐えない。(https://twitter.com/Ndtn_/)
http://web.sfc.wide.ad.jp/~nadechin/follower.csvdate follower 2018/9/6 39.569 2018/9/7 39.57 2018/9/8 39.573 . . . . . . 2019/12/10 37.8614. 時系列データの処理
学習データとテストデータを分ける。
pandasでもnumpyでも何でも良いがひとまず用意するのは、
・2018/09/06 ~ 2019/12/10 の元データ
・2018/09/06 ~ 2019/11/30 の学習データ
・2019/12/01 ~ 2019/12/10 のテストデータADF検定でデータの定常性の確認をする。
・[statsmodels.tsa.stattools.adfuller]
http://www.statsmodels.org/dev/generated/statsmodels.tsa.stattools.adfuller.html
・[帰無仮説, 有意水準]
http://www.gen-info.osaka-u.ac.jp/MEPHAS/express/express11.html
res = sm.tsa.stattools.adfuller(df.follower)
出力結果は以下の通り
p-value = 0.9774
⇨p-value > 0.05
したがって定常性を持つとはいえない。
定常性を持たせるために、差分を取り季節性を取り除く。predict.pydata = [Scatter(x=df.index, y=df.follower.diff())]続いて季節性の除去。
predict.pydata = [Scatter(x=df.index, y=df.follower-res.seasonal)]これで再びADF検定を行う。
p-value = 1.109e-25
⇨p-value < 0.05
結果、定常性を持つ時系列データへと処理を行うことができた。5. 時系列データの予測
SARIMA modelの場合、各々のデータのモデルの作成は
predict.py# coding:utf-8 from statsmodels.tsa.statespace.sarimax import SARIMAX model = SARIMAX( train, order=(p, d, q), seasonal_order=(sa, sd, sq, s), enforce_stationarity=False, enforce_invertibility=False) result = model.fit()で行う。
order=(p, d, q)はARIMAモデルのパラメーター
seasonal_order=(sp, sd, sq, s)は季節性のパラメーター参照↓
・[statsmodels.tsa.statespace.sarimax.SARIMAX]
https://www.statsmodels.org/dev/generated/statsmodels.tsa.statespace.sarimax.SARIMAX.html
・[SARIMAで時系列データの分析(PV数の予測)]
https://www.kumilog.net/entry/sarima-pv @xkumiyu次にProphet modelの作成を行う。
Prophetはひとまず学習データを打ち込めばよしなにモデルを構築してくれるという、
「何やってるか分からないけど予測らしい何かが出来た」を実現してくれる。
今日から俺も2秒のコピペでデータサイエンティストになれる。predict.py# coding:utf-8 import pandad as pd import numpy as np from fbprophet import Prophet data = pd.read_csv('follower.csv') data.follower= data.follower.apply(lambda x: int(x.replace(',', ''))) #カラム名は'ds','y'に設定しなくてはならない data = data.rename(columns={'date': 'ds', 'follower': 'y'}) model = Prophet() model.fit(data)6. 時系列データの予測
・SARIMA model
SARIMA modelに掛けたテストデータの予測
2019-12-01 38002.878685 2019-12-02 38001.204647 2019-12-03 37998.080676 2019-12-04 37988.324131 2019-12-05 37981.134367 2019-12-06 37974.569498 2019-12-07 37966.333432 2019-12-08 37958.270232 2019-12-09 37956.258566 2019-12-10 37952.875398・Prophet model
Prophet modelに掛けたテストデータの予測
2019-12-01 37958.337506 2019-12-02 37959.963661 2019-12-03 37957.304699 2019-12-04 37943.272430 2019-12-05 37934.533210 2019-12-06 37920.537811 2019-12-07 37908.529618 2019-12-08 37905.819057 2019-12-09 37907.445213 2019-12-10 37904.786251寂しいのでplotしてみる
7. わかったこと
学習データの最終日の翌日の予測データを見てみる。
date, follower #実データ 2019-12-01, 38003.000000 # SARIMA 2019-12-01, 38002.878685 # Prophet 2019-12-01, 37958.337506[予測部分拡大図]を見てもらうとわかると思うけど、SARIMAデータでは学習データの翌日の予測はほぼ一致している。
学習データの次の時点の予測は向いてそう。Prophetは正直微妙だった。
7. 1日集中で予測させてみる
2019/12/09までを学習させて2019/12/10の予測値を出して見れば意外と上手くいくのではないかと考えたのでやってみる。
date, follower #実データ 2019-12-01, 37861.000000 # SARIMA 2019-12-10 37868.158032ボチボチ良い感じ。
やっぱり1日だけの予測だと実用レベルの比較的良い精度が出そう。何度も言うけど、Prophetは正直微妙だった。
まとめ
コピペデータサイエンティストとアドベントカレンダーを遅れて投稿する奴を信じてはいけない。
あと、フォロワーは減る。
- 投稿日:2019-12-13T18:17:06+09:00
<科目> 機械学習 第六章:アルゴリズム2(k-means)
<科目> 機械学習
目次
第一章:線形回帰モデル
第二章:非線形回帰モデル
第三章:ロジスティク回帰モデル
第四章:主成分分析
第五章:アルゴリズム1(k近傍法(kNN))
第六章:アルゴリズム2(k-means)
第七章:サポートベクターマシン第六章:アルゴリズム2(k-means)
#https://datahexa.com/kmeans-clustering-with-wine-dataset/参考 import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn import cluster, preprocessing, datasets from sklearn.cluster import KMeans wine = datasets.load_wine() X = wine.data X.shape結果(178, 13)y=wine.target y.shape結果(178,)wine.target_names結果array(['class_0', 'class_1', 'class_2'], dtype='<U7')model = KMeans(n_clusters=3) labels = model.fit_predict(X) df = pd.DataFrame({'labels': labels}) type(df)結果pandas.core.frame.DataFramedef species_label(theta): if theta == 0: return wine.target_names[0] if theta == 1: return wine.target_names[1] if theta == 2: return wine.target_names[2] df['species'] = [species_label(theta) for theta in wine.target] pd.crosstab(df['labels'], df['species'])
- 投稿日:2019-12-13T18:12:18+09:00
Python備忘録
はじめに
未来電子テクノロジーでインターンをしているやっきーです。
まだまだ勉強中のため、間違いがあればどんどん指摘してください。
Pythonの特徴
変数宣言をしない
c言語などとは異なり、変数を定義する際に宣言をする必要がありません。変数の条件(数字以外の文字から始まる、特別な単語と被らないようにする、など)を満たしていれば宣言なしに使い始めることができます。
中かっこ { } を使わない
pythonでは、{}を使いません。for文やif文の中身はインデントで区別します。
for i in range (n): # : で区切る print(i*i) #for文の中の処理 input() #for文の外の処理主な記法
出力
標準出力を表示するためには
print()
を使う。print("hello") #hello a = 5 print(a) #5ただし、数字と文字を混合して表示するときは型変換をしなければならない。
a = 5 print(a + "hands") #エラー print(str(a) + "hands") #数値aを文字列に変換 #5hands入力
キーボードからの標準入力を受け付けるには、
input
を使用します。a = input() #helloと入力 print(a) #helloinputを使用すると文字列が格納されるため、数字として扱いたいときは型変換をする必要があります。
関数宣言
関数を作るときは、
def
を用います。def sum(a, b): return a+b文字列の置換
標準入力を
input()
で取得したときに、一部の文字を変えたい場合、replace()
を用います。
実際の使用例は以下の通りです。line = input() # "co worker" と入力 line_2 = line.replace(' ', '-') #スペースを - (ハイフン)に置換 print(line_2) # "co-worker" と出力文字列の一部を変更する
例えば、以下のように2文字目を変更しようとしても、エラーとなり、うまく動作しません。
string = "worm" string[1] = "a" print(string) #warmとしたい #TypeError: 'str' object does not support item assignmentこのような場合、解決策として2通りの方法があります。
文字列をリストに変換する
文字列(str)は中身を変更できませんが、リスト(list)は変更ができます。そのため、文字列をリストに変換して、特定の文字を変更した後、再び文字列に変換すればよいです。
string = "worm" stringList = list(string) #リストに変換 stringList[1] = "a" #2文字目をaにする srting2 = "".join(stringList) #リストを文字列に変換 print(string2) #warm文字列を分けて、間に挿入する
今回の例では、文字列の1文字目までと3文字目以降の文字の間に入れたい文字を挿入します。
string = "worm" new_str = string[:1] + 'a' + string[2:] #stringの1文字目と3文字目以降を代入、その間にaを挿入 print(new_str) #warmリストから重複を除外
一次元リストの重複を除外するにはset()を使います。
set()を用いることで重複が排除されたset型のオブジェクトとなります。リスト型として扱うためには、list(set())とすればいいです。nums = [1, 2, 10, 1, 3, 1, 4, 2, 3] nums2 = list(set(nums)) print(nums2) #[1,2,10,3,4]フレームワーク
pythonには、特定の機能をこなすためのプログラムがまとめられたフレームワークが多数存在します。フレームワークを活用することで、より効率的に開発を行うことができます。
pythonのフレームワークとして以下のようなものがあります。
- Django
- bottle
- Flask
- Tornado
- Plone
今後は、Djangoについてさらに学習していきます。
参考URL
Solve Python | HackerRank
Pythonで、文字列の一部の文字を変更する - minus9d's diary
Pythonでリスト(配列)から重複した要素を削除・抽出 | note.nkmk.me
2019年Pythonのおすすめフレームワーク完全版!各フレームワークを徹底比較! | エンジニア案件紹介
- 投稿日:2019-12-13T17:39:59+09:00
pythonスクリプトをresponderを使ってさっとCloud Runで実行できるようにする
サマリ
Cloud RunがGAされましたね。今までローカルで実行していたちょっと処理時間がかかるpythonスクリプトをお試しでCloud Runで実行したくなりました。
Cloud Runで実行するコンテナーはHTTPでリクエストを受けて実行できる必要があります。また、Cloud Runではリクエスト タイムアウトのことも考慮しないといけません。
リクエスト タイムアウトのことを考慮せずにとりあえずさっとスクリプトを動かすのにresponderが簡単だったので紹介します。前提の話
試した環境
- OS: Mac OS 10.14.6
- docker desktop community: 2.1.0.5
- docker image: python:3.7.5-buster
responder
pythonのHTTPサービスフレームワークです。ASGI(Asynchronous Server Gateway Interface)を実装したものなので、Asynchronousと名前がついているとおり非同期処理もできます。
非同期処理自体は他のフレームワークでもできます。しかし、pythonのフレームワークで有名なFlaskやDjangoだとざっと調べたところパッケージを追加でインストールしたり、設定ファイルを変えたりする必要がありました。responderのサンプルコードを見るとインストールも実装も楽そうだったので使ってみました。やったこと
ディレクトリ構成と各ファイルについて
vaivailx@MacBook-Pro-2 responder_sample_for_cloudrun % tree -L 3 . . ├── Dockerfile ├── requirements.txt └── src ├── sample.py └── webapp.pypythonの各ファイルは以下の処理をします。
- sample.py: 実行したいメインの処理
- webapp.py: HTTPリクエストを受け付ける処理
requirements.txtはこれだけ。
responder==2.0.4Dockerfileではaptでパッケージの更新とrequirements.txtに書かれているパッケージをインストール。
FROM python:3.7.5-buster ENV APP_HOME /app WORKDIR $APP_HOME ADD ./requirements.txt /app ADD ./src /app # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ # Install pylint && pip --disable-pip-version-check --no-cache-dir install pylint \ # Update Python environment based on requirements.txt && pip --disable-pip-version-check --no-cache-dir install -r requirements.txt \ && rm -rf requirements.txt \ # Clean up && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Switch back to dialog for any ad-hoc use of apt-get ENV DEBIAN_FRONTEND= # run app ENV PORT '8080' CMD python3 webapp.py EXPOSE 8080responderを使ってHTTPリクエストを受け付ける処理。
def
の前にasync
とつけるだけでその関数は非同期処理になります。webapp.py
import logging import responder from sample import process_sample logging.basicConfig(filename=f'/var/log/webapp.log', level=logging.DEBUG) api = responder.API() @api.route("/async") async def asyncsample(req, resp): @api.background.task def process_param(params): t = int(params.get('time', 10)) process_sample(t) process_param(req.params) resp.media = {'async success': True} @api.route("/sync") def syncsample(req, resp): t = int(req.params.get('time', 10)) process_sample(t) resp.media = {'sync success': True} if __name__ == '__main__': api.run()ログ出力をしているのは、Croud Runで実行時に確認できるためです。
ルート名通り、ルートパス/asyncにアクセスすると非同期処理が実行され、ルートパス/syncにアクセスすると動機処理が実行されます。
今回はクエリパラメーターの値分スリープをするようにしました。sample.py
import time import logging def process_sample(secs): logging.debug('process starts.') time.sleep(secs) logging.debug('process ends.')Cloud Runでの実行結果確認
では、Cloud Runのリクエスト タイムアウトを30秒に設定して実行してみます。(5分待つのが面倒なためデフォルト値から変更しています。)
同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/sync?time=10 Fri Dec 13 09:52:59 +09 2019 {"sync success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→正常に終了
同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run. app/sync?time=40 Fri Dec 13 09:55:45 +09 2019 upstream request timeoutvaivailx@cloudshell:~ (cloudrun-test-259804)$→タイムアウト
非同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=10 Fri Dec 13 10:02:31 +09 2019 {"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→
正常に終了非同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=40 Fri Dec 13 10:03:57 +09 2019 {"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→正常に終了
ついでに確認したこと
ちなみにHTTPリクエスト タイムアウトは最大900秒と設定画面にありましたが、これは実行時間ではなく
あくまでリクエストを受け付けてレスポンスを返すまでの時間で、非同期にしておけば900秒以上できることは確認できました。
以下非同期で1200秒実行した結果です。最後に
- responderを使うと非同期処理を簡単に実装できるのを紹介しました。
- 処理時間がかかるバッチのpythonスクリプトをCloud Runで動かしてみたいときに使うのがありかなと思います。
- Cloud Runは、料金ページの通り、HTTPのレスポンスタイムではなく処理時間に応じて課金されます。非同期にしても処理を実行した時間はその分課金対象時間されるので注意してください。
- 投稿日:2019-12-13T17:39:59+09:00
Pythonスクリプトをresponderを使ってさっとCloud Runで実行できるようにする
サマリ
Cloud RunがGAされましたね。今までローカルで実行していたちょっと処理時間がかかるpythonスクリプトをお試しでCloud Runで実行したくなりました。
Cloud Runで実行するコンテナーはHTTPでリクエストを受けて実行できる必要があります。また、Cloud Runではリクエスト タイムアウトのことも考慮しないといけません。
リクエスト タイムアウトのことを考慮せずにとりあえずさっとスクリプトを動かすのにresponderが簡単だったので紹介します。前提の話
試した環境
- OS: Mac OS 10.14.6
- docker desktop community: 2.1.0.5
- docker image: python:3.7.5-buster
responder
pythonのHTTPサービスフレームワークです。ASGI(Asynchronous Server Gateway Interface)を実装したものなので、Asynchronousと名前がついているとおり非同期処理もできます。
非同期処理自体は他のフレームワークでもできます。しかし、pythonのフレームワークで有名なFlaskやDjangoだとざっと調べたところパッケージを追加でインストールしたり、設定ファイルを変えたりする必要がありました。responderのサンプルコードを見るとインストールも実装も楽そうだったので使ってみました。やったこと
ディレクトリ構成と各ファイルについて
vaivailx@MacBook-Pro-2 responder_sample_for_cloudrun % tree -L 3 . . ├── Dockerfile ├── requirements.txt └── src ├── sample.py └── webapp.pypythonの各ファイルは以下の処理をします。
- sample.py: 実行したいメインの処理
- webapp.py: HTTPリクエストを受け付ける処理
requirements.txtはこれだけ。
responder==2.0.4Dockerfileではaptでパッケージの更新とrequirements.txtに書かれているパッケージをインストール。
FROM python:3.7.5-buster ENV APP_HOME /app WORKDIR $APP_HOME ADD ./requirements.txt /app ADD ./src /app # Avoid warnings by switching to noninteractive ENV DEBIAN_FRONTEND=noninteractive RUN apt-get update \ && apt-get -y install --no-install-recommends apt-utils dialog 2>&1 \ # Install pylint && pip --disable-pip-version-check --no-cache-dir install pylint \ # Update Python environment based on requirements.txt && pip --disable-pip-version-check --no-cache-dir install -r requirements.txt \ && rm -rf requirements.txt \ # Clean up && apt-get autoremove -y \ && apt-get clean -y \ && rm -rf /var/lib/apt/lists/* # Switch back to dialog for any ad-hoc use of apt-get ENV DEBIAN_FRONTEND= # run app ENV PORT '8080' CMD python3 webapp.py EXPOSE 8080responderを使ってHTTPリクエストを受け付ける処理。
def
の前にasync
とつけるだけでその関数は非同期処理になります。webapp.py
import logging import responder from sample import process_sample logging.basicConfig(filename=f'/var/log/webapp.log', level=logging.DEBUG) api = responder.API() @api.route("/async") async def asyncsample(req, resp): @api.background.task def process_param(params): t = int(params.get('time', 10)) process_sample(t) process_param(req.params) resp.media = {'async success': True} @api.route("/sync") def syncsample(req, resp): t = int(req.params.get('time', 10)) process_sample(t) resp.media = {'sync success': True} if __name__ == '__main__': api.run()ログ出力をしているのは、Croud Runで実行時に確認できるためです。
ルート名通り、ルートパス/asyncにアクセスすると非同期処理が実行され、ルートパス/syncにアクセスすると動機処理が実行されます。
今回はクエリパラメーターの値分スリープをするようにしました。sample.py
import time import logging def process_sample(secs): logging.debug('process starts.') time.sleep(secs) logging.debug('process ends.')Cloud Runでの実行結果確認
では、Cloud Runのリクエスト タイムアウトを30秒に設定して実行してみます。(5分待つのが面倒なためデフォルト値から変更しています。)
同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/sync?time=10 Fri Dec 13 09:52:59 +09 2019 {"sync success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→正常に終了
同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run. app/sync?time=40 Fri Dec 13 09:55:45 +09 2019 upstream request timeoutvaivailx@cloudshell:~ (cloudrun-test-259804)$→タイムアウト
非同期実行:実行時間10秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=10 Fri Dec 13 10:02:31 +09 2019 {"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→
正常に終了非同期実行:実行時間40秒
vaivailx@cloudshell:~ (cloudrun-test-259804)$ date;curl -H "Authorization: Bearer $(gcloud auth print-identity-token)" https://responderasync-jpwo3ltkhq-an.a.run.app/async?time=40 Fri Dec 13 10:03:57 +09 2019 {"async success": true}vaivailx@cloudshell:~ (cloudrun-test-259804)$→正常に終了
ついでに確認したこと
ちなみにHTTPリクエスト タイムアウトは最大900秒と設定画面にありましたが、これは実行時間ではなく
あくまでリクエストを受け付けてレスポンスを返すまでの時間で、非同期にしておけば900秒以上できることは確認できました。
以下非同期で1200秒実行した結果です。最後に
- responderを使うと非同期処理を簡単に実装できるのを紹介しました。
- 処理時間がかかるバッチのpythonスクリプトをCloud Runで動かしてみたいときに使うのがありかなと思います。
- Cloud Runは、料金ページの通り、HTTPのレスポンスタイムではなく処理時間に応じて課金されます。非同期にしても処理を実行した時間はその分課金対象時間されるので注意してください。
- 投稿日:2019-12-13T17:28:17+09:00
<科目> 機械学習 アルゴリズム1(k近傍法(kNN))
<科目> 機械学習
目次
第一章:線形回帰モデル
第二章:非線形回帰モデル
第三章:ロジスティク回帰モデル
第四章:主成分分析
第五章:アルゴリズム1(k近傍法(kNN))
第六章:アルゴリズム2(k-means)
第七章:サポートベクターマシン第五章:アルゴリズム1(k近傍法(kNN))
(演習5)
%matplotlib inline import numpy as np import matplotlib.pyplot as plt from scipy import stats訓練データ生成
def gen_data(): x0 = np.random.normal(size=50).reshape(-1, 2) - 1 x1 = np.random.normal(size=50).reshape(-1, 2) + 1. x_train = np.concatenate([x0, x1]) y_train = np.concatenate([np.zeros(25), np.ones(25)]).astype(np.int) return x_train, y_train X_train, ys_train = gen_data() plt.scatter(X_train[:, 0], X_train[:, 1], c=ys_train)学習 ステップはない
予測
予測するデータ点との、距離が最も近い ? 個の、訓練データのラベルの最頻値を割り当てる
def distance(x1, x2): return np.sum((x1 - x2)**2, axis=1) def knc_predict(n_neighbors, x_train, y_train, X_test): y_pred = np.empty(len(X_test), dtype=y_train.dtype) for i, x in enumerate(X_test): distances = distance(x, X_train) nearest_index = distances.argsort()[:n_neighbors] mode, _ = stats.mode(y_train[nearest_index]) y_pred[i] = mode return y_pred def plt_resut(x_train, y_train, y_pred): xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100)) xx = np.array([xx0, xx1]).reshape(2, -1).T plt.scatter(x_train[:, 0], x_train[:, 1], c=y_train) plt.contourf(xx0, xx1, y_pred.reshape(100, 100).astype(dtype=np.float), alpha=0.2, levels=np.linspace(0, 1, 3))n_neighbors = 3 xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100)) X_test = np.array([xx0, xx1]).reshape(2, -1).T y_pred = knc_predict(n_neighbors, X_train, ys_train, X_test) plt_resut(X_train, ys_train, y_pred)numpy実装
xx0, xx1 = np.meshgrid(np.linspace(-5, 5, 100), np.linspace(-5, 5, 100)) xx = np.array([xx0, xx1]).reshape(2, -1).T from sklearn.neighbors import KNeighborsClassifier knc = KNeighborsClassifier(n_neighbors=n_neighbors).fit(X_train, ys_train) plt_resut(X_train, ys_train, knc.predict(xx))
- 投稿日:2019-12-13T17:11:27+09:00
Python初学者が手始めに過去10年間の気象データをサクッと簡単に分析してみた。
はじめに
最近Pythonの勉強を始めました。せっかくなので、これから自分と同じようにPythonを触り始める方に役に立つ投稿をしていきたいと思います。Pythonを一度も書いたことがなかったので、今回はpythonのシンタックスの理解やどのようなライブラリーがあるのかなどを把握しながらシンプルにデータ分析を行っていきたいと思います。
参考本:
すぐに使える! 業務で実践できる! Pythonによる AI・機械学習・深層学習アプリのつくり方手順&準備
実行環境
Google Colaboratory
- pythonライブラリーをpipインストールすることなく、ライブラリーにアクセスできる。
- 無料であり、Googleアカウントさえあれば、インストール等の作業に手間を取られることなく、すぐにコードを実行することができる使用するライブラリー
- urllib
- panda
- matplotlib実装
まず初めに、分析をするために必要なデータを用意します。
データの準備
気象データを分析するために、以下のURLからデータセットをダウンロードします。直接ダウンロードをしても良いのですが、urlretireveライブラリーを使ってインストールをしてみましょう。
# urllibからurlretrieveという関数にアクセス from urllib.request import urlretrieve # filenameという変数を用意。ファイル名をtempreture.csvにする filename = "tempreture.csv" # urlを指定 url = "https://raw.githubusercontent.com/kujirahand/mlearn-sample/master/tenki2006-2016/kion10y.csv" # urlを読み込み、上の行で記述したtempreture.csvという名前のファイルとして、データを保存 urlretrieve(url, filename)次に取得したデータを表示させてみる
Pandasは、Pythonでデータ分析を効率的に行うためのライブラリです。Pandasを使うと、データの読み込みや統計量の表示、グラフ化など、データ分析に関する作業を容易に行うことができるようになります。
# pandasをimportし使用する場合、pdと書くことが一般的らしいので、pdとします。 import pandas as pd # 先ほど取得したcsvファイルの中身をみるために、read_csv()を使用します pd.read_csv(filename)実行した結果、データが4018行 x 6列あることがわかりました。
気温の平均値を調べてみる
#[過去10年間のデータを辞書型にして、プログラムしやすい形にする] history = {} # indexと各行のデータを取得する。他の言語だとenumurate関数と同じ for i, row in df.iterrows(): # 月日気温を各変数に代入します month, day, tempreture = (int(row['月']), int(row['日']), float(row['気温'])) # keyを「12/25」のような形にします key = str(month) + "/" + str(day) # 同じkeyが重複しないように判定を行います if not(key in history): history[key] = [] # 重複しなければ、historyに追加します history[key] += [tempreture] # [平均値を求める] average = {} # historyをループさせて、keyを取得 for key in history: # 計算後の平均値をkeyに紐付けてaverageに追加 average[key] = sum(history[key]) / len(history[key]) result = average[key] # print("{0}: {1}".format(key, result))ある日にちの平均気温を調べてみる
import math # typeチェックを行う関数(文字列以外受け付けないようにするため) def isString(date): return type(date) is str # 辞書型のaverageから指定した日付の平均値を取得する def getTempreture(date): if isString(date): return average[date] tempreture = getTempreture("12/25") value = round(tempreture) # intをstringにタイプ変換 print(str(value)+ "度です")描画してみる
#グラフを描画するためにmatplotlibをインポートする import matplotlib.pyplot as plt # 月ごとに気温データを分割する処理 tempreture_per_month = df.groupby(['月'])['気温'] # 分割された気温データを月ごとに合計し、それを月ごとのデータ数で割る average_tempreture = tempreture_per_month.sum() / tempreture_per_month.count() # 描画する average_tempreture.plot()所感
- 初学者はGoogle Colaboratoryで始めた方が、インストールなどの作業の手間を省くことができるのでおすすめ
- 思ったより、簡単にデータ操作や描画ができて、pythonのライブラリーのすごさを知った
初めてpythonを触ったので、まだまだわからないことが多いですが、徐々に高度な分析ができるよう学習を続けていきます。