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

Python数学シリーズ③ 行列式(置換)

このシリーズについて

数学の理解を深めるためにpythonで実装しながら、理解を進めてます。当分は線形代数やろうと思います。数学はそんなに好きではありませんでしたが、最近好きになってきたかもしれません。シリーズの目次はこちら。間違いやもっといい方法など、ご意見あれば気軽にコメントください。

行列式と置換

行列式は以下の式で定義されます。参照元
Screenshot from 2021-01-05 23-00-30.png
いきなり、行列式を実装しようとしても色々と知識が足りなかったので、とりあえず置換をまずやっていこうと思います。
置換は以下のように定義されます。参照元
Screenshot from 2021-01-06 22-33-49.png
(1, 2, 3)とかだと(3, 1, 2)とかが置換されたものの例になります。n文字の置換は全部でn!通りあることになります。今回は置換の積を実装していこうと思います。
置換の積は以下のように表されます。参照元
Screenshot from 2021-01-06 22-42-51.png
こちらの記事いわく、2つの置換の対応関係(「A→B」と「B→C」)を3段論法的な感じで1つにギュッとまとめる(「A→C」)感じだそうです。詳しく突き詰めると、自分の場合沼にはまりそうなのでこのイメージでとどめておきます。今回はこの置換の積を実装していこうと思います。

置換の積

置換の積ができるpythonのライブラリが見つからなかったので、自分の実装が合っているか不安です。もしライブラリがあれば教えてください。下のような感じで実装してみました。シンプルに定義どおりにやってみました。

def permutation(list1, list2):
    list_len = len(list2[1])
    final_list = []
    first_list = []
    ans_list = []
    for i in range(list_len):
        a = list2[1][i]
        ans = list1[1][a - 1]
        if list1[0][i] != ans:
            first_list.append(list1[0][i])
            ans_list.append(ans)
    final_list.append(first_list)
    final_list.append(ans_list)
    return final_list

def main():
    print("~~permutation_test~~")
    pm1 = [[1, 2, 3], [2, 3, 1]]
    pm2 = [[1, 2, 3], [3, 2, 1]]
    print("my_answer:", permutation(pm1, pm2))
    # 出力結果
    # ~~permutation_test~~
    # my_answer: [[2, 3], [3, 2]]

これは改善の余地が大いにありそうですが、とりあえずこんな感じで今回はやってみました。

まとめ

大学の数学の授業、予備のりたくみの動画でいい説。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Effective Python 学習備忘録 5日目 【5/100】

はじめに

Twitterで一時期流行していた 100 Days Of Code なるものを先日知りました。本記事は、初学者である私が100日の学習を通してどの程度成長できるか記録を残すこと、アウトプットすることを目的とします。誤っている点、読みにくい点多々あると思います。ご指摘いただけると幸いです!

今回学習する教材

今日の進捗

  • 進行状況:48-55ページ
  • 本日学んだことの中で、よく忘れるところ、知らなかったところを書いていきます。

動的なデフォルト引数を指定するときにはNoneとドキュメンテーション文字列を使う

デフォルト引数は一度しか評価されない

キーワード引数のデフォルト値に、現在時刻を使うプログラムを考える。

from datetime import datetime
from time import sleep

def log(message, when=datetime.now()):
    print('%s: %s' % (when, message))

log('Hi there!')
sleep(0.1)
log('Hi again!')

出力結果

2021-01-06 14:52:16.518219: Hi there!
2021-01-06 14:52:16.518219: Hi again!

キーワード引数はモジュールロード時の関数定義の時だけなので、時刻は変化しない。デフォルト値をNoneにして、ドキュメンテーション文字列に実際の振る舞いを文書化することで、可読性を担保しつつ、正しい動作を得ることができる。

def log(message, when=None):
    """Log a message with a timestamp.

    Args:
        message: Message to print.
        when: datetime of when the message occurred.
            Defaults to the present time.
    """
    when = datetime.now()
    print('%s: %s' % (when, message))

log('Hi there!')
sleep(0.1)
log('Hi again!')

出力結果

2021-01-06 15:20:07.665323: Hi there!
2021-01-06 15:20:12.870457: Hi again!

キーワード専用引数で明確さを高める

関数の引数が多くなった際に、位置引数を用いると可読性が落ちる。
例として、4つの引数を持ち、初めの2つはint型、最後の二つはboolian型を受け取る、関数を考える。

def sample(int_a, int_b, bool_c, bool_d):
    # ...

sample(1, 2, True, False)

この場合、bool_c, bool_dを容易に位置を取り違える可能性がある。
キーワード引数であれば、そのような心配は無くなる。Pythonでは、位置で引数を指定できなくする、キーワード専用引数が用意されている。
引数リストの中の*記号が、位置引数の終わりと、キーワード専用引数の始まりを示す。

def sample(int_a, int_b, *, bool_c, bool_d)
    # ...

sample(1, 2, True, False)                 # Error になる
sample(1, 2, bool_c=True, bool_d=False)   # 動作する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで環境変数にアクセスする方法

環境変数にアクセスする方法

モジュールのインポート

import os

環境変数にアクセスする

Pythonでは、osモジュールのenvironに環境変数が格納されている。
型は、マップ型です。

user_name = os.environ['USERNAME']
print(user_name)

また、os.getenv(key, default=None)を使用することでも取得できます。

user_name = os.getenv('USERNAME', 'dummy')
print(user_name)

os.getenv()は第1引数で環境変数のキーを指定します。
指定されたキーが存在していない場合は、第2引数の値を返却します。

まとめ

環境変数にアクセスする方法は2つある。

  • 1つ目は、os.environを使用する方法
  • 2つ目は、os.getenv()を使用する方法

場合によると思いますが、単純にアクセスするだけなら、環境変数が存在していない場合に任意の値を返却してもらえるos.getenv()を使用するのが良いと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【SIGNATE】【lightgbm】コンペ アメリカの都市エイムズの住宅価格予測 参加記録 (1/2)

はじめに

SIGNATEで開催されているBegginer向けコンペ
【第5回_Beginner限定コンペ】アメリカの都市エイムズの住宅価格予測 に参加したため、参加記録を書きます。

コンペについて

  • 目的: 都市エイムズの住居や周辺環境に関する情報を元に
    住宅の価格 'Sale Price' を予測します。 (よくあるやつ)
  • 期間: 2020/12/01 ~ 2021/01/10
    以降で順位等を記載してますが、コンペ開催中のため未確定です。
  • SIGNATEの称号Begginerのみ参加可能 (初心者向け)

現在のスキル

方針

  • アルゴリズムはLightGBM を採用します。
    コンペでよく使われるとのことなので一回やってみようという考えです。
    XGBoostとかとのアンサンブルまでは頑張らない。
  • 本来はデータを理解してから作業を進めるべきですが、
    最初にLightGBMでモデルを一発作成し、それを基準に改めてデータを理解する流れで書いてます。
  • 解法は調べるといくらでも出てくると思うので、調べず頑張る。

環境とか

Windows10のWSL2にUbuntuをインストールし、主に以下のライブラリを入れて試しました。

ライブラリ バージョン 備考
Python 3.8.5 -
lightgbm 3.1.1 -
notebook 6.0.3 jupyter-notebook
scikit-learn 0.24.0 -
matplotlib 3.3.3 -
seaborn 0.11.1 -

やったこと1

1-1. データ読み込み

train_data = pd.read_csv('./train.csv')

データ数: 3000
カラム数: 47 (目的変数 'Sale Price' 含む)

1-2. カテゴリカルデータへの変換

このまま学習を行うと、object型(文字列)でエラーが起こるのでpandasのcategory型に変換しました。

category_columns = ['MS Zoning', 'Lot Shape', 省略]  # object->categoryにしたいlist
for col in category_columns:
    train_data[col] = train_data[col].astype('category')

print(train_data.dtypes)
index                int64
Order                int64
MS SubClass          int64
MS Zoning         category
... 略

以前、勉強した時のカテゴリカルデータは
One Hot エンコーディング/ラベルエンコーディングが必要だったのが一発で変換できて少し感動。
lightgbmのバージョンが古いと対応していない(?)らしく、
検索して出るURLによってはラベルエンコーダ等を使ってるページもありました。

1-3. データの分割/データセット作成

ホールドアウト法で 7:3に分割して学習用、検証用にします。
その流れでLightGBM用データセットを作成。

train, test = train_test_split(train_data, test_size=0.3, shuffle=True, random_state=0)

Y_train = train.loc[:, 'SalePrice']
X_train = train.drop(['index', 'SalePrice'], axis=1)

Y_eval = test.loc[:, 'SalePrice']
X_eval = test.drop(['index', 'SalePrice'], axis=1)

lgb_train = lgb.Dataset(X_train, Y_train)
lgb_eval = lgb.Dataset(X_eval, Y_eval, reference=lgb_train)

indexをなんとなく消しましたけどLightGBMだし不要だったかも。

1-4. 学習

お試しなので最小限の設定のみ。

lgbm_params = {
    'task': 'train',
    'boosting_type': 'gbdt',
    'objective': 'regression',
    'metric': 'rmse',
    'max_depth' : 10
}

evals_results = {}
model = lgb.train(lgbm_params, lgb_train,
                  num_boost_round=1000,
                  valid_sets=[lgb_eval, lgb_train],
                  valid_names=['eval', 'train'],
                  early_stopping_rounds=50,
                  evals_result=evals_results,
                  verbose_eval=50)

結果1

学習曲線

lightgbmのplot_metric() で学習曲線を表示
https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.plot_metric.html

lgb.plot_metric(evals_results, metric='rmse', figsize=(8, 8))

1-学習曲線.PNG
27回の学習でピークに達し、その後は過学習気味に性能が微減。
パラメータチューニングでもう少し過学習を抑えられそう。

メモ: plot_metric()の第一引数は

Dictionary returned from lightgbm.train()

と公式Documentにあったため train()の戻り値 modelを入れたけど 駄目だった、、、
train()のevals_resultパラメータにDictを設定した場合
学習結果を出力してくれるので、それを設定する必要があるみたい。

importance

lightgbmのplot_importance()で特徴量の重要度を表示
https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.plot_importance.html

lgb.plot_importance(model, height=0.5, figsize=(8, 16))

1-importance.PNG

BsmtFin SF 1(地下の広さ)、Garage xxx (車の収納)が重要とのこと。
データの本質がわかった気持ちになった。

現時点の順位

RMSE: 26818.0705594
順位: 132/542 のため上位25%程度

やったこと2

もう少し汎用的にしたいため クロスバリデーションに置き換えました。
LightGBM には cv()関数があるので train()関数をほぼ置き換えれば可能でした。
検索して出るURLによっては StratifiedKFold()で自分で実装する方法もあり混乱したが、
以下のページで良い感じに解説してくれてました。
https://blog.amedama.jp/entry/lightgbm-cv-implementation

lgb_train = lgb.Dataset(X_train, Y_train)
# lgb_eval = lgb.Dataset(X_eval, Y_eval, reference=lgb_train)

lgbm_params = {
    'task': 'train',
    'boosting_type': 'gbdt',
    'objective': 'regression',
    'metric': 'rmse',
    'max_depth' : 10
}

model = lgb.cv(lgbm_params, lgb_train,
               num_boost_round=1000,
               early_stopping_rounds=50,
               verbose_eval=50,
               nfold=7,
               shuffle=True,
               stratified=False,
               seed=42,
               return_cvbooster=True,
              )

メモ: return_cvboosterパラメータにTrueを設定すると model['cvbooster'] が学習モデルのlistになるので
それを使ってpredict()が可能。古いLightGBMはreturn_cvboosterが未対応らしく、検索して出るURLだと使ってない場合有。

結果2

クロスバリデーションによる現時点の順位

RMSE: 26552.7208586
順位: 86/543 のため上位16%程度

RMSEは250程度改善。train_test_split()の分割は良くなかった模様。
順位は 約10%向上。

まとめ1

初心者のみのコンペですが、LightGBMを使うだけで上位16%程度には入れる模様。
ここからデータ理解・パラメータ調整を試します。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リストワイズ除去のツリーを簡単に計算する関数(Python)

本記事について

  • 疫学研究等で目にする、Fig1.本研究の解析対象者 のツリーを素早く計算する関数を書いた。
  • リストワイズ除去を行った際のツリーを想定する。
  • コードはPythonであるが、記事内にてRより呼び出す方法も追記した。

そもそもリストワイズ除去のツリーって?

こんなヤツ↓ エクセルでシコシコ関数組んでると平気で半時間とかかかったり。(目や腰もいたくなるね)

risttttt.png

(この図に正式名称があればご教示ください)

 何が面倒くさいの?

単にそれぞれの変数の欠測数を乗せるだけならdf.isnull.sum()で終了ですが、、

  1. x1の欠測は●人でした。
  2. x1の欠測を除いたデータにおいて、x2の欠測は▲人でした。
  3. x1と、x2の欠測を除いたデータにおいてx3の欠測は■人でした。
  4. ・・・

どんどんネスト(?)していくんですね。

あぁ面倒くさい。


では、本題へ

import pandas as pd
import numpy as np

def caluculate_missing_tree(df):
    d ={}
    d[0]= df.loc[df[df.columns[0]].isnull() != True]
    for i in range(len(df.columns)-1):
        d[1+i]= d[i].loc[d[i][d[i].columns[1+i]].isnull() != True]

    le = []
    colnames = []
    missing_tree = pd.DataFrame()

    for i in range(len(df.columns)):
        le.append(len(d[i]))
    for i in range(len(df.columns)):
        colnames.append(df.columns[i])


    missing_tree['col_name'] = colnames
    missing_tree['Size'] = le

    return missing_tree


caluculate_missing_tree() の引数に、ツリーを描きたい順番に変数が入っているデータフレームをぶち込むだけです。

たとえば、titanicのデータで試してみる。

import pandas as pd 
import numpy as np
import os 

df = pd.read_csv("train.csv")
df.shape #(891, 12)

df.isnull().sum()  # それぞれの変数の欠測数

--------------------------------
PassengerId      0
Survived         0
Pclass           0
Name             0
Sex              0
Age            177
SibSp            0
Parch            0
Ticket           0
Fare             0
Cabin          687
Embarked         2
dtype: int64

こいつを今回の関数に食わせると・・


caluculate_missing_tree(df)

--------------------------------------

    col_name    Size
0   PassengerId 891
1   Survived    891
2   Pclass      891
3   Name        891
4   Sex         891
5   Age         714
6   SibSp       714
7   Parch       714
8   Ticket      714
9   Fare        714
10  Cabin       185
11  Embarked    183


一瞬で計算できた。嬉しい。

中身の説明

.loc を使用して、条件に合う(欠測していない)データセットをどんどん作っていけば良いのではという発想。

df  <- 元のデータ
df1 = df.loc[df['x1'].isnull() != True]] <- x1 の欠測が除かれたデータ
df2 = df1.loc[df1['x2'].isnull() != True]] <- x1, x2の欠測が除かれたデータ
df3 = df2.loc[df2['x3'].isnull() != True]] <- x1, x2, x3の欠測が除かれたデータ
...
...

こんな感じ。

さらに、for文書くことまで考えてみるとこんな感じ。

d[0]= df.loc[df[df.columns[0]].isnull() != True] <- ここはfor文の外

--- こっからfor のイメージ ---
d[1]= d[0].loc[d[0][d[0].columns[1]].isnull() != True]
d[2]= d[2-1].loc[d[2-1][d[2-1].columns[2]].isnull() != True]
d[3]= d[3-1].loc[d[3-1][d[3-1].columns[3]].isnull() != True]

しかし、for文でdfの作成を自動化するのが少々難儀だった。

複数のデータフレームを格納するリストを作成。そこに、それぞれの変数に対応するデータフレームを格納していくという手法を使用した。

    d ={}
    d[0]= df.loc[df[df.columns[0]].isnull() != True]
    for i in range(len(df.columns)-1):
        d[1+i]= d[i].loc[d[i][d[i].columns[1+i]].isnull() != True]

こんな感じ。例えばtitanicのデータでは、
d[0]はPassengerID
d[1]はPassengerID, Survived
d[2]はpassengerID, Survived, Pclass の欠測に対応している。

その後、確認を容易にするために、変数名サンプルサイズを列名に持つデータフレームを作ろうという発想になるのは必然であろう。

    le = []
    colnames = []
    missing_tree = pd.DataFrame()

    for i in range(len(df.columns)):
        le.append(len(d[i]))
    for i in range(len(df.columns)):
        colnames.append(df.columns[i])


    missing_tree['col_name'] = colnames
    missing_tree['Size'] = le

    return missing_tree

leにそれぞれの変数の欠測値を消去したデータフレームのlen(df.columns)を格納していった。同様に、colnamesにそれぞれのデータフレームに対応する変数名を格納し、可視化した。


caluculate_missing_tree(df)

--------------------------------------

    col_name    Size
0   PassengerId 891
1   Survived    891
2   Pclass      891
3   Name        891
4   Sex         891
5   Age         714
6   SibSp       714
7   Parch       714
8   Ticket      714
9   Fare        714
10  Cabin       185
11  Embarked    183


ジャジャーン(2度目)

Rでの実装方法

  • Rnotebookと reticulateライブラリ を使用する。(追記するかも)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

privateでないAPIがscikit-learn v0.24で削除されたために、unpickleできない経験をしました

scikit-learn v0.22 より前に作ったMultiLabelBinarizerが、scikit-learn v0.24以降の入った環境でunpickleできないことを示します。
ちょっとハマった身から言えること:pickleする環境とunpickleする環境のバージョン違いにはご注意ください

環境

macOS
Python 3.7.3

手順

scikit-learn 0.22より前

scikit-learn-0.21.3

ドキュメントのExamplesを用います
https://scikit-learn.org/stable/modules/generated/sklearn.preprocessing.MultiLabelBinarizer.html#sklearn.preprocessing.MultiLabelBinarizer

experiment.py

import pickle

from sklearn.preprocessing import MultiLabelBinarizer

mlb = MultiLabelBinarizer()
mlb.fit_transform([{'sci-fi', 'thriller'}, {'comedy'}])
print(list(mlb.classes_))

with open("mlb_021.pkl", "wb") as fout:
    pickle.dump(mlb, fout)
$ python experiment.py
['comedy', 'sci-fi', 'thriller']
$ python -q

対話モードでunpickleします。

>>> import pickle
>>> with open("mlb_021.pkl", "rb") as fin:
...   mlb = pickle.load(fin)
...
>>> list(mlb.classes_)
['comedy', 'sci-fi', 'thriller']
>>> mlb.transform([['sci-fi', 'comedy']])
array([[1, 1, 0]])
>>> type(mlb)
<class 'sklearn.preprocessing.label.MultiLabelBinarizer'>

scikit-learn 0.24以降

pip install -U scikit-learnで0.24.0が入りました。

対話モードでunpickleを試みます(エラーになります)。

>>> import pickle
>>> with open("mlb_021.pkl", "rb") as fin:
...   mlb = pickle.load(fin)
...
Traceback (most recent call last):
  File "<stdin>", line 2, in <module>
ModuleNotFoundError: No module named 'sklearn.preprocessing.label'

原因

以下のIssueへの対応でモジュールをprivateにしています。
https://github.com/scikit-learn/scikit-learn/issues/9250

rename file.py into _file.py

それにより、sklearn/preprocessing/label.py(sklearn.preprocessing.label)はsklearn/preprocessing/_label.py(sklearn.preprocessing._label)とrenameされました。
https://github.com/scikit-learn/scikit-learn/commit/15d5ef04df21f1d8db6583c74d1869c3fad36970#diff-e4f17cabd1bed4babf2fbfb7a31a3d955563e0267e8c310fa61629e722a64488

対処法

0.24まで上げなければunpickleはできます(後述するように自己責任でお願いします)。
pip install -U 'scikit-learn<0.24'で0.23.2をインストール。

>>> import pickle
>>> with open("mlb_021.pkl", "rb") as fin:
...   mlb = pickle.load(fin)
...

このとき、バージョン0.24でremoveされるとwarningも出ています。

/.../env/lib/python3.7/site-packages/sklearn/utils/deprecation.py:143: FutureWarning: The sklearn.preprocessing.label module is deprecated in version 0.22 and will be removed in version 0.24. The corresponding classes / functions should instead be imported from sklearn.preprocessing. Anything that cannot be imported from sklearn.preprocessing is now part of the private API.
warnings.warn(message, FutureWarning)

ドキュメントでも通知されていました!
https://scikit-learn.org/dev/whats_new/v0.22.html#clear-definition-of-the-public-api

実際、MultiLabelBinarizertypeを確認すると、renameされた_label.pyを使っています(0.23.2で確認)。

>>> from sklearn.preprocessing import MultiLabelBinarizer
>>> mlb = MultiLabelBinarizer()
>>> type(mlb)
<class 'sklearn.preprocessing._label.MultiLabelBinarizer'>

なお、scikit-learnのバージョンが違うことのWarningも出ますので、文面にあるように自己責任でお願いします。

/.../env/lib/python3.7/site-packages/sklearn/base.py:334: UserWarning: Trying to unpickle estimator MultiLabelBinarizer from version 0.21.3 when using version 0.23.2. This might lead to breaking code or invalid results. Use at your own risk.
UserWarning)

メモ

pip install -Udocs)にバージョン込みで指定すると、パッケージのバージョンを自由に変えられます(--upgradeオプションですが、ダウングレードもできます)。
?バージョン指定の仕方について
https://pip.pypa.io/en/stable/user_guide/#understanding-your-error-message

TODO:pickleの仕組みを今よりももう少し理解したいです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Google画像検索を保存するPythonライブラリの使い方。【最新版】

あけましておめでとうございます。

結構知ってる人が多いとは思いますが私が何度も迷うので書き留めておきます。
機械学習の際の学習データにご利用ください。

2020年8月ごろ?に検索結果の提供方法が大きく変わったらしく、Windows用ソフトの「ImageSpider」が使えなくなりました。(結構便利だったんですけどね...)
そこでこれからはPython用ライブラリの「google_images_download」を使うことにしました。

インストール

【注意】pipではインストールしないでください!

pip3 install google_images_download

PIPにあるバージョンではGoogle側の変更に対応できていないため、インストールしても使えません。
なので直接インストールします。

git clone https://github.com/Joeclinton1/google-images-download.git
cd google-images-download && sudo python setup.py install

これでインストールは終わりです。

使い方

ここでは代表的な使い方としてコマンドライン上での利用方法を載せておきます。

 googleimagesdownload --keywords "apple" --limit 20

上記の例だと「apple」の検索結果を20件、カレントディレクトリに保存します。
Python上での使い方やその他の条件設定等は公式ドキュメント*を見てください。

100件以上を取得する場合

取得したい画像が100件を超える場合はchromedriverをインストールする必要があります。
※Ubuntu20.04 LTSでの操作方法です。Windowsの場合は各自お調べくださいm(_ _)m

まずドライバの最新バージョンを確認
以下のサイトに行って最新版の「chromedriver_linux64.zip」のURlをコピーしてください。
*https://sites.google.com/a/chromium.org/chromedriver/downloads
image.png
因みに2021年1月6日現在はhttps://chromedriver.storage.googleapis.com/88.0.4324.27/chromedriver_linux64.zipでした。

次に以下のコマンドを続けて入力してください。こちらの記事を参考にさせていただきました。)

sudo apt install unzip
cd /tmp/
curl -O <<<<<ここにさっき確認したURLを入れる>>>>>
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
rm chromedriver_linux64.zip

これでインストールは以上です。

100件以上取得する際のコマンド

googleimagesdownload --keywords "apple" --limit 120 --chromedriver /usr/local/bin/chromedriver

さっき紹介したやつに --chromedriver オプションでインストール先を選択するだけです。

↓取得結果
image.png

ちゃんと取得できていますね。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【最新】Google画像検索を保存するPythonライブラリの使い方&ChromeDriverをubuntuで使う

あけましておめでとうございます。

結構知ってる人が多いとは思いますが私が何度も迷うので書き留めておきます。
機械学習の際の学習データにご利用ください。

2020年8月ごろ?に検索結果の提供方法が大きく変わったらしく、Windows用ソフトの「ImageSpider」が使えなくなりました。(結構便利だったんですけどね...)
そこでこれからはPython用ライブラリの「google_images_download」を使うことにしました。

インストール

【注意】pipではインストールしないでください!

pip3 install google_images_download

PIPにあるバージョンではGoogle側の変更に対応できていないため、インストールしても使えません。
なので直接インストールします。

git clone https://github.com/Joeclinton1/google-images-download.git
cd google-images-download && sudo python setup.py install

これでインストールは終わりです。

使い方

ここでは代表的な使い方としてコマンドライン上での利用方法を載せておきます。

 googleimagesdownload --keywords "apple" --limit 20

上記の例だと「apple」の検索結果を20件、カレントディレクトリに保存します。
Python上での使い方やその他の条件設定等は公式ドキュメント*を見てください。

100件以上を取得する場合

取得したい画像が100件を超える場合はchromedriverをインストールする必要があります。
※Ubuntu20.04 LTSでの操作方法です。Windowsの場合は各自お調べくださいm(_ _)m

まずドライバの最新バージョンを確認
以下のサイトに行って最新版の「chromedriver_linux64.zip」のURlをコピーしてください。
*https://sites.google.com/a/chromium.org/chromedriver/downloads
image.png
因みに2021年1月6日現在はhttps://chromedriver.storage.googleapis.com/88.0.4324.27/chromedriver_linux64.zipでした。

次に以下のコマンドを続けて入力してください。こちらの記事を参考にさせていただきました。)

sudo apt install unzip
cd /tmp/
curl -O <<<<<ここにさっき確認したURLを入れる>>>>>
unzip chromedriver_linux64.zip
mv chromedriver /usr/local/bin/
rm chromedriver_linux64.zip

これでインストールは以上です。

100件以上取得する際のコマンド

googleimagesdownload --keywords "apple" --limit 120 --chromedriver /usr/local/bin/chromedriver

さっき紹介したやつに --chromedriver オプションでインストール先を選択するだけです。

↓取得結果
image.png

ちゃんと取得できていますね。
image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonのhttp.serverはRange Requestに対応してなかった

発生した問題

ローカルPCにサーバーを立てて、iOS Safariから動画を開いて再生しようとしたら何故か再生できない...
PCのFirefoxからは再生できるのに...

Googleで検索したらこのような記事を発見
【HTML5】iOSのSafariでvideoタグ埋め込みのmp4が再生できないのはインターレースだから
つまり動画方式が悪いんじゃねーのってこと。
ところが、インターレース方式ではなくプログレス方式なっていた。
FFmpegで再エンコードしてみたりしたが変わらず。
記事の最後の方に追記でほかの原因かもと書いてあり、この記事にたどり着く。
【MP4】SafariでMP4動画が再生できない問題を解決
一番目はそもそもvideo要素すら使ってないのでありえない。
2番目、別の記事のリンクが張ってあり、タイトルが「Safariで動画を表示する際、サーバーのHTTP Range Request対応が必須になっている」となっていてRange Requestに対応してないなんてそんなわけないだろと確認したら...
あれ?対応してなくね?
そんなわけあった。
まあそもそもhttp.serverはスクレイピングとかのテスト用だろうしRange Requestとかに対応しなくても問題無かったのだろう。

対処法

やっぱりApache最強

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

macOS Big Sur で poetry を使って numpy をインストールできなかったので無理やり回避した

環境

  • OS: macOS Big Sur
  • Python : 3.8.7 (from Homebrew)
  • pip : 20.2.3
  • shell : zsh

発生した事象

pyproject.toml に次のような記述が含まれている状態で、 poetry install を行ったところ

[tool.poetry.dependencies]
python = "3.8.*"
pip = "^20.3.3"
gcsfs = "^0.7.1"
lightgbm = "^3.1.1"
pandas = "^1.2.0"
matplotlib = "^3.3.3"
seaborn = "^0.11.1"
% poetry install

Python 2.7 will no longer be supported in the next feature release of Poetry (1.2).
You should consider updating your Python version to a supported one.

Note that you will still be able to manage Python 2.7 projects by using the env command.
See https://python-poetry.org/docs/managing-environments/ for more information.

Installing dependencies from lock file

Package operations: 80 installs, 0 updates, 0 removals

  • Installing lazy-object-proxy (1.4.3)
  • Installing six (1.15.0)
  • Installing wrapt (1.12.1)
  • Installing certifi (2020.12.5)
  • Installing astroid (2.4.1)
  • Installing chardet (3.0.4)
  • Installing idna (2.10)
  • Installing isort (4.3.21)
  • Installing mccabe (0.6.1)
  • Installing pyasn1 (0.4.8)
  • Installing pycodestyle (2.6.0)
  • Installing pyflakes (2.2.0)
  • Installing toml (0.10.2)
  • Installing urllib3 (1.26.2)
  • Installing cachetools (4.2.0)
  • Installing flake8 (3.8.4)
  • Installing multidict (5.1.0)
  • Installing numpy (1.19.5): Failed

EnvCommandError

回避策

poetry を使わずに pip 20.3.3 を使うとインストールできる (python 3.9 を用いているものの、python 3.8 でも同様)

% python3.9 -m venv --upgrade-deps venv
Collecting pip
  Downloading pip-20.3.3-py2.py3-none-any.whl (1.5 MB)
     |████████████████████████████████| 1.5 MB 4.9 MB/s 
Collecting setuptools
  Using cached setuptools-51.1.1-py3-none-any.whl (2.0 MB)
Installing collected packages: pip, setuptools
  Attempting uninstall: pip
    Found existing installation: pip 20.2.3
    Uninstalling pip-20.2.3:
      Successfully uninstalled pip-20.2.3
  Attempting uninstall: setuptools
    Found existing installation: setuptools 49.2.1
    Uninstalling setuptools-49.2.1:
      Successfully uninstalled setuptools-49.2.1
Successfully installed pip-20.3.3 setuptools-51.1.1
% source venv/bin/activate
(venv) % pip install numpy
Collecting numpy
  Downloading numpy-1.19.5-cp39-cp39-macosx_10_9_x86_64.whl (15.6 MB)
     |████████████████████████████████| 15.6 MB 411 kB/s 
Installing collected packages: numpy
Successfully installed numpy-1.19.5

ので、pip を使えば解決は一応できます。が、おすすめしません。poetry で requirements.txtを出力するようにすればまあなんとか…。

% poetry config virtualenvs.create false
% python3.8 -m venv .venv && source .venv/bin/activate && pip install -U pip && (poetry export --without-hashes > requirements.txt) && pip install -r requirements.txt && deactivate

このあと poetry install すると通りますし、poetry shell も通りますが、なんかコレジャナイ感がすごいです。よりよく解決したいですね。

関連

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1byteデータに含まれる任意のbit位置にある値を取得する方法

やりたいこと

1byteデータから任意のbit位置にある値を取得する方法をいろいろ考えたので記事に残したいと思います。

例えば0b10100000という1byteのデータがあったとき(以下図)、4bit目から2bit分の値である0b10)を取得する方法です。

image.png

今回思いついた方法が以下の2点です。

  1. 文字列に変換
  2. bit演算を利用

ソースコードはpythonです。

文字列に変換

目的のbit値を文字列に変換し、bit位置=文字列の位置と考えて目的のbit値を抽出するやり方です。

この方法の注意点は文字列の位置とbit位置は方向が逆である点です。以下の例では、文字列を反転([::-1])させてから目的の値を抽出し、抽出後に再反転させて、正しいbit値に変換しています。

bit_pos = 4
bit_len = 2
value = 0xA0 # 0b10100000

# bit値として文字列に変換
bit_str = "{:08b}".format(value)
# bit_str = '10100000'

# 文字列を反転し、bit位置(`bit_pos`)とbit長(`bit_len`)で目的の値を抽出。抽出値はbit値としては反転しているので、再反転して値を戻す
value = bit_str[::-1][bit_pos:bit_pos+bit_len][::-1]
result = int(value, 2)
# result: 2 (='0b10')

bit_pos, bit_lenをうまく文字列位置に変換できれば、文字列反転をさせずに目的の値が抽出できそうですね。

bit演算を使う

次は、bit演算を使う方法です。具体的にはbitシフト演算論理積を利用します。

まず、対象データの取得したいbit位置が0bitになるよう右シフトします。
これに対して、00000011という値と論理積を計算してあげれば、目的のbit値を取得することができます。
なので11111111=0xFFを右シフトして、論理積の対象データである00000011を作ります。

以上の処理を図でまとめると、以下のようになります。

image.png

実際のコードは以下の通りです。

bit_pos = 4
bit_len = 2
value = 0xA0 # 0b10100000
result = (value >> bit_pos) & (0xFF >> (8 - bit_len))
# result: 2 (='0b10')

終わりに

もっとシンプルでスマートに計算できそうな気がするのですが、これ以上の案は思いつきませんでした。
ただ、目的の値はこれで取得できそうです。

もっとbit操作をうまく使えるように、日々精進していきたいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonの配列をjsonデータに加えるだけ

pythonの配列をjsonデータに加えるだけです

コード

test.py
#!/env/python
import json

def addJson(list):
    # state data to json
    json = {
        "parameter": list,
    }
    return json

l = list(range(0))

for i in range(25):
    l.append(i)
    data = addJson(l)
    print(data)

実行結果

$ python test.py
{'parameter': [0]}
{'parameter': [0, 1]}
{'parameter': [0, 1, 2]}
{'parameter': [0, 1, 2, 3]}
{'parameter': [0, 1, 2, 3, 4]}
{'parameter': [0, 1, 2, 3, 4, 5]}
{'parameter': [0, 1, 2, 3, 4, 5, 6]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23]}
{'parameter': [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24]}

以上です

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

manimの作法 その11

概要

manimの作法、調べてみた。
Animation、使ってみた。

サンプルコード

from manimlib.imports import *

class FuncRotater(Animation):
    CONFIG = {
        "rev_func" : lambda x : x,
    }
    def interpolate_submobject(self, submobject, starting_submobject, alpha):
        submobject.points = np.array(starting_submobject.points)
    def interpolate_mobject(self, alpha):
        Animation.interpolate_mobject(self, alpha)
        angle_revs = self.rev_func(alpha)
        self.mobject.rotate(angle_revs * TAU, about_point = ORIGIN)

class test(Scene):
    def construct(self):
        circle = Circle()
        dot = Dot()
        dot2 = dot.copy().shift(RIGHT)
        self.play(GrowFromCenter(dot))
        self.wait()
        self.play(Transform(dot, dot2))
        self.wait()
        self.play(MoveAlongPath(dot, circle), run_time = 1, rate_func = linear)
        self.wait()
        self.play(Rotating(dot, about_point = [2, 0, 0]), run_time = 1)
        self.wait()
        self.play(FuncRotater(dot, rev_func = lambda x : x % 0.25, run_time = 2))
        self.wait()
        self.play(ApplyMethod(dot.scale, 2))
        self.wait()
        self.play(FadeOut(dot))
        self.wait()


生成した動画

https://www.youtube.com/watch?v=0aBAezBXB-w

以上。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで関数とグローバル変数の応用練習

はじめに

閲覧していただきありがとうございます。
英語の文法は許してください。
有識者の方へ、もっとなんとかなるとかあったら優しく教えてください。

概要

・AかBに投票をする。
・投票数の確認と投票数の初期化をする。
・終了処理をする。

完成例

def voteA ():
    global vote1
    vote1 = vote1 + 1

def voteB ():
    global vote2
    vote2 = vote2 + 1

def count ():
    global vote1
    global vote2
    print("[The number of votes for A is",vote1,".]")
    print("[The number of votes for B is",vote2,".]")

def Initialize ():
    global vote1
    global vote2
    vote1 = 0
    vote2 = 0

op = 0
while op != 9:

    if op == 1:
        voteA ()
        print("[Vote for A.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 2:
        voteB ()
        print("[Vote for B.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 3:
        print("[Check the vote count.]")
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 0:
        print("[Initialize the vote count.]")
        Initialize ()
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    else:
        while op != 1 and op != 2 and op != 3 and op != 9 and op != 0:
            print("[Vote for A.>1]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
            op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))


print("[Good bye.]")

実行例

 [Initialize the vote count.]
 [The number of votes for A is 0 .]
 [The number of votes for B is 0 .]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:1
 [Vote for A.]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:1
 [Vote for A.]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:2
 [Vote for B.]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:3
 [Check the vote count.]
 [The number of votes for A is 2 .]
 [The number of votes for B is 1 .]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:0
 [Initialize the vote count.]
 [The number of votes for A is 0 .]
 [The number of votes for B is 0 .]
 [Vote for A.>1] 
 [Vote for B.>2] 
 [Check the vote count.>3] 
 [End.>9] 
 [Initialize the vote count.>0]
'[Chose 1 or 2 or 3 or 9 or 0.]:9
 [Good bye.]

AとBに投票する関数の作成

今回は複数の関数で同じ変数を使用するため、グローバル変数を使います。

def voteA ():
    global vote1
    vote1 = vote1 + 1

def voteB ():
    global vote2
    vote2 = vote2 + 1

投票数を確認する関数の作成

シンプルにグローバル変数であるvote1とvote2を表示させる。

def count ():
    global vote1
    global vote2
    print("[The number of votes for A is",vote1,".]")
    print("[The number of votes for B is",vote2,".]")

投票数を初期化する関数の作成

グローバル変数のため複数の関数で同じ変数を使える。
こちらもシンプルにvote1とvote2を0にしている。

def Initialize ():
    global vote1
    global vote2
    vote1 = 0
    vote2 = 0

ABそれぞれに投票する文の作成

Bに投票する文はAとほとんど変わらないので割愛。
opは後にwhileでループするときに使う。

 voteA ()
        print("[Vote for A.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

投票数を確認する文を作成

シンプルに関数を呼び出すだけ。

print("[Check the vote count.]")
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

投票数を初期化する文を作成

こちらも、シンプルに関数を呼び出すだけ。
正しく初期化されているかを確認するために、投票数を確認する関数も呼び出しておく。

print("[Initialize the vote count.]")
        Initialize ()
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

A,Bを投票するか、投票数を確認するか、投票数を初期化するか、終了するかを選択する文を作成

whileにそれぞれ挿入していく。
最初にopを0に宣言しておくことで、投票の初期化と確認をしてから開始できる。
できるだけ強制終了であるbrakeを使用したくないため、終了の9を入力したらwhileのループが終了するようになっている。
最後のifでは誤った選択肢を選択された場合に正しい選択肢が選択されるまでwhileでループさせている。

op = 0
while op != 9:

    if op == 1:
        voteA ()
        print("[Vote for A.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 2:
        voteB ()
        print("[Vote for B.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))


    elif op == 3:
        print("[Check the vote count.]")
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 0:
        print("[Initialize the vote count.]")
        Initialize ()
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    else:
        while op != 1 and op != 2 and op != 3 and op != 9 and op != 0:
            print("[Vote for A.>1]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
            op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

完成

全部組み合わせて完成。

def voteA ():
    global vote1
    vote1 = vote1 + 1

def voteB ():
    global vote2
    vote2 = vote2 + 1

def count ():
    global vote1
    global vote2
    print("[The number of votes for A is",vote1,".]")
    print("[The number of votes for B is",vote2,".]")

def Initialize ():
    global vote1
    global vote2
    vote1 = 0
    vote2 = 0

op = 0
while op != 9:

    if op == 1:
        voteA ()
        print("[Vote for A.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 2:
        voteB ()
        print("[Vote for B.]")
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))


    elif op == 3:
        print("[Check the vote count.]")
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    elif op == 0:
        print("[Initialize the vote count.]")
        Initialize ()
        count ()
        print("[Vote for A.>1]","\n","[Vote for B.>2]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
        op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))

    else:
        while op != 1 and op != 2 and op != 3 and op != 9 and op != 0:
            print("[Vote for A.>1]","\n","[Check the vote count.>3]","\n","[End.>9]","\n","[Initialize the vote count.>0]")
            op = int(input("[Chose 1 or 2 or 3 or 9 or 0.]:"))


print("[Good bye.]")

おわりに

グローバル変数はとっても便利でおもしろい。
終了するときGood Bye.てでるのすごいかっこいい。

もっと関数の勉強をしたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】エラー発生時に関数内の変数を知りたい!

はじめに

皆さんはデバッグをどのように行っていますか?
デバッガとか使ってするのが一般的でしょうか、Pdbとか?

それはさておき、エラー発生時ってとりあえず関数内のローカル変数を見たくなりますよね。
でも”ローカルな”変数ですので、関数外からは見られず結局printに走る、あるあるだと思います。

今回は、せめてエラー発生時の変数だけでもまとめて出力できたら…と思いデコレータを作成しました。

つくったもの

def raise_locals(f):
    import sys, traceback
    def wrapper(*args, **kwargs):
        try:
            f(*args, **kwargs)
        except Exception:
                exc_type, exc_value, exc_traceback = sys.exc_info()
                ext = traceback.StackSummary.extract(traceback.walk_tb(exc_traceback), capture_locals=True)
                locals_ = ext[-1].locals
                if locals_:
                    print('{' + ', '.join([f"'{k}': {v}" for k, v in locals_.items()]) + '}')
                raise
    return wrapper

構造としては、実行してみてエラーが発生したらtracebackを通じてローカル変数を拾ってくるって感じです。
では使ってみましょう。

@raise_locals
def f(x):
    a = x
    b = 1 / a
    a = 1

f(0)
out
{'x': 0, 'a': 0}
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
<ipython-input-137-16e1baa342f5> in <module>
      5     a = 1
      6 
----> 7 f(0)

<ipython-input-136-f595b8b52afb> in wrapper(*args, **kwargs)
      3     def wrapper(*args, **kwargs):
      4         try:
----> 5             f(*args, **kwargs)
      6         except Exception as e:
      7                 exc_type, exc_value, exc_traceback = sys.exc_info()

<ipython-input-137-16e1baa342f5> in f(x)
      2 def f(x):
      3     a = x
----> 4     b = 1 / a
      5     a = 1
      6 

ZeroDivisionError: division by zero

いつものエラー文の前にエラー発生時のローカル変数が表示されていますね。

def f(x):
    a = x
    b = 1 / a

@raise_locals
def main():
    f(x=0)

if __name__=='__main__':
    main()

一番親となる関数につけておけば、どこでエラーが発生してもそのエラーが発生した関数のローカル変数を見ることができます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slack - APIGateway - Lambda(Python) - RedShift インタラクティブアプリの作り方

概要

表題のとおりのSlackアプリケーションを作成する。
まともに書くと長文になるので、以下の要点ポイントのみを重点的に記載する。

  • Slackアプリケーションの留意点
  • Slack側のアプリケーション設定(権限周り)
  • SlackのHomeView(Homeタブ)やattachmentに関して
  • Lambda(python)周りの実装とライブラリ利用
  • APIGateWay設定

なお、シンプルに試すだけならWebHookURLを設定してLambdaとかをスケジュール実行してPOSTすれば終わるが、SlackClientライブラリを利用したインタラクティブなリクエストに対するメッセージ通知ができるようなシーンを想定している。

手順通りにやれば動くものが作れるというよりは、
調べるのが面倒だったり、案外ハマったポイントを断片的に紹介する点はご承知おきください。

Slackアプリケーションを作成するにあたっての留意点

レスポンスは3秒以内に一旦返す

ボタンをクリックし、レスポンスを返す場合などは3秒を超えるとエラーになる。

これは、処理でひとまずHTTPコード200を返して、
その後非同期で処理を行い、改めて結果をチャンネルに通知することで簡単に回避できる。

なお、本記事では深く触れないが
実際はリクエストパラメータのresponse_urlは3秒経過しても数分以上有効なので
タイムアウト覚悟のまま、response_urlに直接POSTすることもできなくはない。

UI(ホームタブ、フォーム)やメッセージの装飾にはBlock Kitと呼ばれるJSONの組み立てが必要

Block Kit Builderで試せるのだが、非常に面倒。

最低限、チャンネルIDとユーザIDを理解する

ユーザIDはrequestBodyのPayLoadに入ってる。ユーザ名ではないので注意。
チャンネルIDもrequestBodyのPayLoadにあるが、特定のパターンでは入ってこないこともあるし
別チャンネルへの通知を行いたい場合もあると思うので、対象のIDを、定数で埋め込んでおくのが良いだろう。

なお、チャンネルIDはブラウザで対象チャンネルを開くとアドレスから簡単に判別できる。

/app.slack.com/client/<ここは組織ID>/<ここがチャンネルID>/details/top

メッセージ装飾のマークアップが、テーブル(表)をサポートしていない

できないものは仕方ないですね。
私はdividerと引用マークアップとスペース埋めを駆使して強引に整形。。

Slack側のアプリケーション設定(権限周り)

これだけセットしときゃ大丈夫ってところだけ。意外に限られてる。
今回はWebHookURLは使わないが、一応設定入れる。

設定後は
[Settings] -> [Installed App Settings] -> [(Re)Install to WorkSpace]
での反映を忘れずに。

表示情報

[Settings] -> [Basic Infomation] -> [Display Information]
アプリ名とか説明とか、ロゴとか。適当に。

WebHookURL

[Features] -> [Incoming Webhooks]

  • [Activate Incoming Webhooks]をオンにする
  • [Webhook URLs for Your Workspace]で[Add New Webhook to Workspace]で追加

認証と許可

[Settings] -> [OAuth & Permissions]

[Bot User OAuth Access Token]

ここの値はlambdaからメッセージなどを送るのに必要になるので控えておく

[Scope] -> [Bot Token Scopes]

必須なのは
chat:write, chat:write.customize, channels:history くらいだと思う。
WebHook使うなら、incoming-webhookも。
私は他にapp_mentions:read, commands, reactions:read とか入れてる。

イベントの活性化とリクエストURLの設定

[Features] -> [Event Subscriptions]

APIを作成して、GateWayを設定し終わったら(方法は後述)
最後にここの[Enable Events]をオンにして、各種設定を入れる。

ここに作成したAPIのURL等を設定することで
実際に特定のアクションを実施した時にリクエストが飛ぶようにしておく。

リクエスト(API)URLの設定

現段階ではできないので、後で設定したら。暫定で適当なURLを入れても認証でNGになる。
最低限、API側の処理でチャレンジレスポンスが実装されていないと認証が通らないので。

最後にAPIのURLを入れておく場所と認識しておいてください。

どのイベントをHookするかの設定

[Subscribe to bot events]で設定する。

とりあえず、ホームタブのUI表示用に app_home_opened と
メッセージからのボタンイベントをとるために message.channels を追加しておけば大体OK。

SlackアプリケーションのUIやメッセージ装飾に関して

以下に簡単な例とイメージを記載。
コードでの実装イメージはSlackClientAPIの利用サンプルとともに後述する。

ホームタブ

スクリーンショット 2021-01-06 14.58.00.png

HomeTabSample.json
{
    "type": "home",
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "これは超シンプルなアプリテストです。"
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "text": "現在の時刻を表示"
                    },
                    "style": "primary",
                    "value": "click_from_home_tab"
                }
            ]
        }
    ]
}

ボタン付きメッセージ

スクリーンショット 2021-01-06 15.07.28.png

MessageSample.json
{
    "blocks": [
        {
            "type": "section",
            "text": {
                "type": "mrkdwn",
                "text": "こんにちは、サンプルアプリケーションです!"
            }
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "emoji": true,
                        "text": "現在時刻を教えて"
                    },
                    "style": "primary",
                    "value": "click_from_message_button"
                }
            ]
        }
    ]
}

実際のAPIをlambdaで構築 (Python)

ポイントだけ紹介。
ちなみにログ関係はAPIGatewayのログで十分なので、lambda側では特に設定してない。

必要な構成(非同期実装のため)

Slackアプリケーション(前述)
↓↑
AWS APIGateway(後述)
↓↑
lambda slack-request.py API(ここで説明)
 > 3秒ルールがあるため、処理を非同期lambdaに委譲し一旦レスポンスを返す
 > payloadの解析などはここでほぼ行う
↓↑
lambda async-notice.py API(ここで説明)
 > 依頼内容や属性情報を全てもらった状態で処理を行い結果をSlackへ通知
 > 今回はスケジュール実行の例もここで

必要なライブラリの準備(Layer設定)

地味にてこずる部分。ハマると数時間たったりするという。
毎回忘れた頃にlambdaを作るので、
pythonフォルダで包むの忘れてアップロードして動かなくて戸惑うという。

まあ、これをアップロードして、両方のlambdaのlayerに追加する。
必要なのはslack-clientだけのような気もする(作り方は別でググってください)。

あとは今回はいらないけど、いろいろやるならついでにdjangoやpandasの最新版も入れておくといい感じ。
お試し版では上記のslackレイヤーだけあれば大丈夫。
スクリーンショット 2021-01-06 15.41.02.png

実装のポイント : リクエスト処理のlambda

チャレンジ・レスポンスの実装
リクエストURLは最低でも、以下の実装をど先頭に入れる。
これがないと、アプリケーション設定で認証されない。

slack-request.py
    if "challenge" in event:
        return event["challenge"]

通知はSlackClientAPIで行う

SlackClientの生成
# アプリケーション設定のBot User OAuth Access Tokenの値
import slack
client = slack.WebClient(token="xoxb-XXXXXXXXXX....")

ホームタブのUI描画
JSONで指定する

ホームタブの描画を行う
    """
        HomeTabメニューの表示
    """
    if ('event' in event):
        slack_event = event['event']
        if (slack_event['type'] == 'app_home_opened') :
            if ('tab' in slack_event) and (slack_event['tab'] == 'home'):
                user = slack_event['user']
                channel = slack_event['channel']
                blocks = <ここがJSON>
                views = {"type": "home", "blocks": blocks}
                client.views_publish(user_id=user, view=views)
                return {'statusCode': 200}

ホームタブのJSON(再掲)
ホームタブのJSON
{
    "type": "home",
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "これは超シンプルなアプリテストです。"
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "text": "現在の時刻を表示"
                    },
                    "style": "primary",
                    "value": "click_from_home_tab"
                }
            ]
        }
    ]
}

イベントハンドル(ホームボタンやインタラクティブメッセージボタンの処理)
厳密には振り分けもう少し整理した方が良いが、これでも動く。

最大のポイントは、
リクエストは非同期で別のlambdaに依頼して先にリクエストを受け付けましたレスポンスを返すこと。

また、通知の度に他のSlackユーザに応答を返したくない場合もあるのでサンプルでは
メッセージ通知を「あなただけに表示されます」のEphemeralにしている。

slack-request.py
    """
        ActionをHookして処理を振り分け、SlackへのResponseを返す
        ChannelIdは固定不変なのでリクエストからではなく定数を利用する
    """
    if ('body-json' in event):

        # payloadの内容を抽出
        action_message = urllib.parse.unquote(event['body-json'].split('=')[1])
        action_message = json.loads(action_message)

        # 通知先となるユーザIDを取得        
        user_id = action_message['user']['id']

        # Message Button == Interactive Messageか否かを判定
        isInteractiveMessage = False
        if('type' in action_message ) and (action_message['type'] == 'interactive_message'):
            isInteractiveMessage = True

        # actionがあればイベントをHook
        if ('actions' in action_message):

            """
                値の取得と通知依頼は別のlambdaに非同期で依頼する
                非同期依頼は複数あって良いが、依頼は主にPrimaryModeのみで判定する
            """
            delegateLambda_primaryMode = []

            # payloadからaction message部分のみを抽出する
            action_message = action_message['actions'][0]
            # 非同期送信なので同期レスポンスメッセージのDefaultを先に設定しておく
            send_message = "リクエストを受け付けました。少しお待ちください。"

            if (action_message['type'] == 'button') :

                if (action_message['value'] == 'click_from_home_tab'):
                    """
                        ホームタブのボタンがクリックされた
                    """
                    delegateLambda_primaryMode.append('click_from_home_tab')

                elif (action_message['value'] == 'click_from_message_button'):
                    """
                        ホームタブのボタンがクリックされた
                    """
                    delegateLambda_primaryMode.append('click_from_message_button')


                """
                    設定した非同期依頼をかける
                    PrimaryとSecondaryのほか、user_idを渡す仕様にした
                    非同期処理は後述する
                """
                for p in delegateLambda_primaryMode:
                    input_event = {
                        "primaryMode": p,
                        "secondaryMode": "now no mean",
                        "user_id": user_id
                    }
                    lambdaMediator.callNortifyAsync(json.dumps(input_event))

                """
                    一旦、応答メッセージを返却する
                """
                if isSendEphemeral == True:
                    response = client.chat_postEphemeral(
                        channel=notice_channel_id,
                        text=send_message,
                        user=user_id
                    )
                else:
                    response = client.chat_postMessage(
                        channel=notice_channel_id,
                        text=send_message
                    )

    if isInteractiveMessage == True:
        pass
    else:
        return {'statusCode': 200}

lambdaMediator.py
import boto3

def callNortifyAsync(payload):
    """
    非同期で委譲先のlambda(async-notice.py固定)を呼ぶ

    Parameters
    ----------
    event : 委譲先lambdaのevent json
        PrimaryMode: 依頼内容を表す第一Key文字列
        SecondaryMode: 依頼内容を表す第二Key文字列
        UserId: 依頼者のSlackユーザID

    Returns
    ----------
    response
    """

    response = boto3.client('lambda').invoke(
        FunctionName='async-notice',
        InvocationType='Event', 
        Payload=payload
    )
    return response

実装のポイント : 非同期処理のlambda

attachmentは利用しなくてもいいが、凝ったことをやりたい場合は
メッセージではなくattachmentでやるので、サンプルはそうしている。

今回は両方のlambdaの実行RoleにAWSLambda_FullAccessを付与している。
また、サンプルなのでPrimaryModeをslack-request.pyからもらっているが分岐は入れていない。

attachmentを利用してメッセージを送信

async-notice.py
import json
import datetime
import time
import boto3
import slack

from libs import mylib
from libs import slack_info

def lambda_handler(event, context):
    """
    このlambdaはslack-requestからのdelegate処理(非同期)、およびスケジューラからのみKickされる

    Parameters
    ----------
    event : 
        PrimaryMode: 依頼内容を表す第一Key文字列
        SecondaryMode: 依頼内容を表す第二Key文字列(未利用:拡張用)
        UserId: 依頼者のSlackユーザID

    Returns
    ----------
    httpResponseBody : statusCode:200 , body -> SlackClientからの送信を行うため200を返すだけ

    ToDo
    ----------

    """

    # ユーザIDの取得
    user_id = ""
    if 'user_id' in event:
        user_id = event['user_id']

    """
        パラメータなしはスケジューラからの起動なので全体通知
        パラメータありは非同期依頼なのでEphemeral(あなただけに見えてます)通知
    """
    isEphemeral = True
    if ('primaryMode' in event) == False:
        isEphemeral = False

    """
        メッセージ通知を行う
        attachmentsはメッセージJSON
    """
    postDataMessage(attachments, False, isEphemeral, user_id, "ここに本文メッセージを入れる。")

    return {
        'statusCode': 200,
        'body': json.dumps('OK')
    }


def postDataMessage(attachment_data, isSendMultiple=False, isEphemeral=True, userId="", textMessage=""):
    """
    メッセージを送信する

    Parameters
    ----------
    attachment : アタッチメントデータ
    isSendMultiple : アタッチメントデータが複数あるか否か
    isEphemeral : リクエストユーザのみ見えるように送信するか否か
    userId : リクエストユーザのID
    textMessage : テキストメッセージ

    Returns
    ----------

    ToDo
    ----------

    """

    client = slack.WebClient(token="xoxb-XXXXXXXXX.....")

    # 複数送信対応のため、単一送信であってもリストで包み直す
    if isSendMultiple == False:
        attachment_data = [attachment_data]

    # TARGET_CHANNEL_IDは通知先のチャネルIDを別途設定しておくこと
    for attachment in attachment_data:
        if isEphemeral == True:
            response = client.chat_postEphemeral(
                channel=TARGET_CHANNEL_ID,
                attachments = attachment,
                user=userId
            ) 
        else:
            response = client.chat_postMessage(
                text=textMessage,
                channel=TARGET_CHANNEL_ID,
                attachments = attachment
            )

ボタン付きメッセージのJSON(再掲)
attachmentのjson例
{
    "type": "home",
    "blocks": [
        {
            "type": "header",
            "text": {
                "type": "plain_text",
                "text": "これは超シンプルなアプリテストです。"
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "actions",
            "elements": [
                {
                    "type": "button",
                    "text": {
                        "type": "plain_text",
                        "text": "現在の時刻を表示"
                    },
                    "style": "primary",
                    "value": "click_from_home_tab"
                }
            ]
        }
    ]
}

おまけ : スケジュール実行したい場合

Amazon EventBridgeのルール作成で対象をasync-noticeのlambdaとcron設定をブラウザから入れるだけ。

月曜から金曜の9時に実行(GMT時間なので-9時間で設定)
cron(0 0 ? * MON-FRI *)

image.png

おまけ : RedShiftへの接続

boto3でredshift-dataを指定するだけで
データアクセスはクエリ実行時に指定したRedshiftアカウント内でのロール権限で実施されるため非常に楽。

サンプルコード
RedShift接続
import json
import time
import boto3

# Redshift接続情報
CLUSTER_NAME='cluster名を入れる'
DATABASE_NAME='データベース名を入れる'
DB_USER='ユーザ名を入れる'

def getDateTimeSample():
    """
    時刻の取得
    """

    sql = '''
        select getdate();
    '''
    return _getData(sql)

def _getData(sql):
    """
    RedShiftへクエリを発行して結果をそのまま返す

    Parameters
    ----------
    String : sql文

    Returns
    ----------
    statement : boto3の取得結果そのまま
    """

    # Redshiftにクエリを投げる。非同期なのですぐ返ってくる
    data_client = boto3.client('redshift-data')
    result = data_client.execute_statement(
        ClusterIdentifier=CLUSTER_NAME,
        Database=DATABASE_NAME,
        DbUser=DB_USER,
        Sql=sql,
    )

    # 実行IDを取得
    id = result['Id']

    # クエリが終わるのを待つ
    statement = ''
    status = ''
    while status != 'FINISHED' and status != 'FAILED' and status != 'ABORTED':
        statement = data_client.describe_statement(Id=id)
        #print(statement)
        status = statement['Status']
        time.sleep(1)

    # 結果の表示
    if status == 'FINISHED':
        if int(statement['ResultSize']) > 0:
            # select文等なら戻り値を表示
            statement = data_client.get_statement_result(Id=id)
        else:
            # 戻り値がないものはFINISHだけ出力して終わり
            print('QUERY FINSHED')
    elif status == 'FAILED':
        # 失敗時
        print('QUERY FAILED\n{}'.format(statement))
    elif status == 'ABORTED':
        # ユーザによる停止時
        print('QUERY ABORTED: The query run was stopped by the user.') 

    return statement 

APIGateWayの設定

作成したlambda APIにURLを与えてアクセスできるようにする。

大まかな手順

REST APIで作成
image.png

lambdaを統合
image.png

マッピングテンプレートでapplication/x-www-form-urlencodedの追加
一番重要。ホームタブのオープンイベントではこのフォーマットで来るので、ここいれないと動作しない。
また、他のリクエストはapplication/jsonで来るものがあるので、合わせて追記するのもポイント。

image.png

あとはステージをデプロイするだけ
image.png

ログ設定
この設定で十分デバッグできる。
ログはCloudWatchにステージ名のロググループで出力される。
image.png

その他のポイント・気付き

3秒ルールはしんどい

Slackの性質を考えればまあ、そうなるのは理解できるが凝ったこともしたくなる場合もある。

非同期でデータベースアクセス含めた処理を行って
単純にレスポンス200を返すだけでも、lambdaのコールドスタートなどを考えるとギリギリ間に合わない場合も稀にある。

lambdaにはProvisioned Concurrencyもあり、料金はかかるが場合によってはこれも活用できる。

マークアップにテーブルが欲しい

結構需要はあると思うが。
引用を使うと引用内では半角スペースを忠実に再現するのでこれと、division線を使うと表っぽくはなる。
ただ、無理やり感は拭えない。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Azure Machine Learning SDK for Python をアップグレードする

はじめに

Azure Machine Learning SDK for Python は多数のライブラリから構成されていて、一つだけアップグレードしてしまうとライブラリ間でバージョンが異なってしまいます..。
そのため、依存関係も含めて一気にアップグレードする必要があります。

方法

pip install --upgrade --upgrade-strategy eager azureml-sdk

参考

https://docs.microsoft.com/ja-jp/python/api/overview/azure/ml/install?view=azure-ml-py

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonで作る戦車ゲーム 戦車の挙動について

pythonを勉強していて、戦車の挙動をとてもうまく作ることができたので紹介します。挙動といっても戦車の移動をどうやってコードに書き起こすかという感じです。僕はpythonを利用しましたが、他の言語でも応用はとても簡単だと思います。

環境

・Python3.9.0
・pygame(ライブラリ)を使っています。

使う知識

・ちょっぴりのプログラミング知識
・高校物理(力学)

高校物理と言ってもとても簡単なものなのでご安心ください。


今回は戦車の動きを作るのにあたって、二つのステップを作りました。それは速度方向です。それとこのゲームはwキーで上、aキーで左、dキーで右、sキーで下に動くようにしています。

速度(位置)編

速度とは何か、と聞かれたらおそらくほとんどの人が「単位時間あたりに進む距離」と考えると思います。そうです。これをコードに起こしてしまうのです。物理で習う式は

x=a*t^2+b*t+x_0

という感じですよね。tは時間、x_0は最初の位置、そしてa,bは定数です。
これをプログラミングで表すにはどうすればよいのか。大前提として、このプログラムではwhileループごとに戦車の位置差を計算してます。それを速度と呼ぶこととします。話を戻して、プログラミングでこの式を表すには、tを方向キーを押している時間と捉えることで上手く解決します。この式を利用するとゆっくりと発進する動作は書けるようになります。でもここまで来たらゆっくり慣性があるように止まってほしいので、この式をさらに少しいじります。

x=a*t^2+b*t+x_0-c*t_stop^2

後ろに新しい項を追加しました。t_stopとは、キーが押されていないかつ、速度が0でないときにカウントしていく変数です。これを追加することで現実のようにゆっくり滑らかに進み始めてゆっくり滑らかに止まるようになります。

            pressed_key=pygame.key.get_pressed()
            if speed == 0:
                if pressed_key[K_a]:
                    power_left = 1
                if pressed_key[K_d]:
                    power_right = 1
                if pressed_key[K_w]:
                    power_up = 1
                if pressed_key[K_s]:
                    power_down = 1

            if(pressed_key[K_a] or pressed_key[K_s] or pressed_key[K_d] or pressed_key[K_w]):       # キーを押したとき
                t += 0.1  # 時間の計測を始める
                t_stop = 0

            else:  # 戦車が動いていてかつ速度があるときに慣性を使うためにt_stopの計測を始める。
                if speed > 0:
                    t_stop += 0.01
                else:  # 速度が0でキーも押されてないときに初期化
                    t = 0
                    speed = 0
            speed = 20*t*t+1*t-18*t_stop*t_stop  # 移動速度の計算
            if(speed > 7):  # 最高速度の設定
                speed = 7

方向編

速度編でスピードは計算できたので次にそのスピードをどの方向に割り振るかを考えていきます。この処理を考えることで曲がるときの慣性を表現しました。でもこの式は物理法則に則ったわけでは無いのでもっといい方法があるとは思います。しかし、かなり高精度にカーブの慣性を表せていると思うので参考にしてみてください。まず、

    power_left = 1  
    power_right = 1
    power_up = 1
    power_down = 1

と四つの変数を用意します。そして、

            # 速度がどの横行に向いているのかを計算して、なめらかな方向転換を実現
            # 処理開始
            if pressed_key[K_a]:
                power_left += 0.1
                last_key = 2
            elif(power_left > 1):
                power_left -= 0.1
            if pressed_key[K_d]:
                power_right += 0.1
                last_key = 3
            elif(power_right > 1):
                power_right -= 0.1
            if pressed_key[K_w]:
                power_up += 0.1
                last_key = 0
            elif(power_up > 1):
                power_up -= 0.1
            if pressed_key[K_s]:
                power_down += 0.1
                last_key = 1
            elif(power_down > 1):
                power_down -= 0.1
            # 処理終了

このようにして方向キーが押されている間、四つの変数の値を増やしていきます。そして最後に、

            x -= speed*hit[0]*power_left / \
                (power_left+power_right+power_up+power_down)  # 実際の位置の計算
            x += speed*hit[1]*power_right / \
                (power_left+power_right+power_up+power_down)
            y -= speed*hit[2]*power_up/ \
(power_left+power_right+power_up+power_down)
            y += speed*hit[3]*power_down / \
                (power_left+power_right+power_up+power_down)
            # 戦車の移動処理の終了

x,yというのは戦車(プレイヤー)の座標です。また、ここのhitという配列は当たり判定の返り値で、障害物に当たっているとこの値が0になり移動できなくなります。こうすることで滑らかなカーブを実現することができました。まっすぐ進んでいて急に曲がろうとしても曲がれないあの慣性をコードに書き起こすことができました。

まとめ

かなりこだわって作ったので、ぜひ参考になればうれしいです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

1株1千円以下の価格変動が大きい銘柄に投資してパフォーマンスをあげる

はじめに

昨年は新型コロナウイルス拡大により,相場(株価)の上げ下げが激しい1年となりました.

昨年11月18日のファイザーがワクチン開発に成功したことを発端に,2020年末までに日経平均株価は約30年ぶりとなる高値を更新し続けました.

img

足元では業績拡大期待が先行して株価と足元の利益が乖離しつつあります.

イギリスではコロナ変異種が感染拡大によりロンドンで3回目都市封鎖が実行されました.さらに,日本でも緊急事態宣言が再び検討されるなど,現在でも国内および世界でも感染拡大が広がっている状況です.

したがって,新型コロナウイルスの影響により,今後も激しい株価の上げ下げが予想されます.

本記事では,今後も激しい株価の上げ下げが予想されることを踏まえて,
激しい価格変動を利用して,どの銘柄が短期的に稼げるかについて調査しました.

株価値動きの大きさの指標

株価の1日の値動きの幅の大きさを表した指標としてボラティリティがあります.この指標は主に個別株の短期トレードをするときに使用します.

ボラティリティ$(\%)$は,

\frac{当日のトゥルー・レンジ{\rm TR}}{当日のティピカル・プライス{\rm TP}} \times 100 \ (\%)

で計算することができます.

分母のティピカル・プライスTPは高値,安値,終値の3つの平均値です.

分子のトゥルー・レンジTRは次の3つのうち値が最大のものを使用します(${\rm TR}=\max(t_1, t_2, t_3)$).

t_1 = 当日の高値-当日の安値
t_2 = 当日の高値-前日の終値
t_3 = 前日の終値-当日の安値

ボラティリティが5%以上であれば比較的ボラティリティが高いといえます.株価が低い銘柄では値幅が小さくてもボラティリティーが高くなってしまうことがあります.また,株価の変動幅を表していると同時に,リスクを示しています.あくまでも値動きの変動幅が分かるだけなので,実際に売買をするときは,他の指標も同時にみて判断する必要があります.

分析結果

※ 詳しい分析の方法は記事下の付録(Pythonプログラム)を参照してください.データを取得方法,分析方法もすべて説明してあります.

1株1千円以下の価格変動が大きい銘柄を選定していきます.

そのために,まず以下の条件に合致する銘柄を選別します.選定する際,株価の時系列データ,出来高データは,2020年1月5日から過去90日分を使用しました.

【銘柄選定基準】
・東京証券取引所に上場している
・1日の平均出来高50万株以上
・1日の平均終値50円以上1千円以下

「1日の平均出来高50万株以上」という条件は,出来高が少ない銘柄だと取引が成立しにくいので,ある程度出来高が多い銘柄を選別するためのものです.「1日の平均終値50円以上1千円以下」の「50円以上」は,株価が低い銘柄では値幅が小さくてもボラティリティーが高くなってしまうことがあるので,「50円以上」と設定しました.

この条件で銘柄を選定した結果,242件の該当する銘柄がありました.その中で,ボラティリティが高い10銘柄を下の表にまとめてみました.

順位 証券コード 銘柄名 平均ボラティリティ(%) 平均終値(円) 平均出来高(株)
1 9878 セキド 10.8 700.6 573921
2 5337 ダントーホールディングス 9.9 502.7 2699480
3 6400 不二精機 9.2 529.5 2886709
4 1757 クレアホールディングス 8.9 70.6 5968157
5 5952 アマテイ 8.7 172.6 3114188
6 6659 メディアリンクス 8.3 487.0 1453528
7 3645 メディカルネット 8.2 747.3 798739
8 2586 フルッタフルッタ 7.9 191.5 1508418
9 2191 テラ 7.6 769.2 2084927
10 3071 ストリーム 7.6 140.0 1470286
  • 1位.セキド(9878)の4か月間のチャート

ワクチン開発以降,株価は徐々に上昇し,ワクチン開発以前と比べて,一時2倍以上になっていますね.

特色:祖業の家電店から撤退しファッションが柱。オークファンと提携、ECや催事、卸の育成に軸足 Yahoo!JAPANファイナンス

  • 2位.ダントーホールディングス(5337)の4か月間のチャート

最近では出来高が小さいので,活発な取引は行われていないようですね...

 特色:内外装タイル老舗。16年に販売、製造、投資3事業統合、本業再建中。不動産マネジメント事業も Yahoo!JAPANファイナンス

  • 3位.不二精機(6400)の4か月間のチャート

取引が活発に行われるようになってから,4倍以上の株価となっています.

特色:精密金型から出発、成形品受託が主体。2輪・4輪車部品中心に幅広く展開。中国、東南アに工場 Yahoo!JAPANファイナンス

おわりに

新型コロナウイルスの影響で,今後も激しい株価の上げ下げが予想されます.

このとき,投資資金の一部をボラティリティが高い銘柄に短期的に投資することで,投資のパフォーマンスを上げられる可能性があります.

それを踏まえて,1株1千円以下つまり10万円以内ぐらいで買える銘柄のうちで,どの銘柄が短期的に稼げるかについて調べてみました.

1位から10位までを上のリストにまとめましたが,ひょっとすると上のリストの中に2倍以上になる銘柄がふくまれているかもしれません.

ボラティリティをうまく使いこなせば,より良い投資パフォーマンス出すための助けになります.ただ,その分損をする可能性もあるので,ほかの指標も用いて銘柄を見極めていきましょう.

以下は今回までに株式や暗号資産について検証した記事の一覧です.もし興味ある方は見て頂けると嬉しく思います.


<過去記事>
日経225全銘柄の投資効率を検証
日経平均株価の30%は5銘柄で決まることを投資先選びに生かせるか?
日経平均株価が上がった次の日に上がる銘柄を見つけたい
散投資でリスク回避の仕方【2銘柄編】
【時間アノマリー】1日の中で最も株価が上がる/下がる時間帯
投資商品を分散しリスクを最小化する【3銘柄編】
日経平均株価が上がった5分後に上がる銘柄を見つけたい
どの暗号資産が効率よく稼げるか

付録(分析方法:Pythonプログラム)

  • 株銘柄リストを作成する

まずは,「東証上場銘柄一覧」を参考にして,株銘柄のリストを作成します.

東証上場銘柄一覧 - 東京証券取引所で,「東証上場銘柄一覧(2020年11月末)」のエクセルファイルをダウンロードします(2021/01/05現在).

ダウンロードしたファイル名はdata_j.xlsになっていると思います.

これをエクセルで開いて,「名前を付けて保存」で,CSV形式で新しく保存します.このときファイル名はdata_j.csvとしてください.ファイルはプログラムを保存する予定のフォルダの中にいれてください.

data_j.csv
日付,コード,銘柄名,市場・商品区分,33業種コード,33業種区分,17業種コード,17業種区分,規模コード,規模区分
20201130,1301,極洋,市場第一部(内国株),50,水産・農林業,1,食品 ,7,TOPIX Small 2
20201130,1305,ダイワ上場投信-トピックス,ETF・ETN,-,-,-,-,-,-
20201130,1306,NEXT FUNDS TOPIX連動型上場投信,ETF・ETN,-,-,-,-,-,-
...
20201130,9996,サトー商会,JASDAQ(スタンダード・内国株),6050,卸売業,13,商社・卸売 ,-,-
20201130,9997,ベルーナ,市場第一部(内国株),6100,小売業,14,小売 ,6,TOPIX Small 1

この操作は以下で紹介するPythonプログラムがファイルをCSV形式で読み込むようになっているからです.

Pythonのpandasモジュールでも,エクセルファイル.xlsは読み込めるので,この操作が煩わしい方は,直接エクセルファイルを読み込めるようにプログラムを変更してみてください.

  • フォルダを作成する

プログラムを保存する予定のフォルダの中に,「data」という名前のフォルダを作成してください.このフォルダの中に各銘柄の株価データなどを保存していきます.

  • 株価データを取得する

銘柄リストdata_j.csvを基にして,Yahoo!ファイナンスから株価データを取得していきます.株価データは「data」フォルダの中に保存されます.

以下のプログラムを実行すると各銘柄の株価が取得できます.全部で4千銘柄以上あり,2015年から現在までの株価を取得するので,かなり時間がかかります.取得に時間がかかる場合は,取得期間を変更してみるとよいかもしれません.

タイムエラーなどで一部の銘柄のデータ取得に失敗ことがあるので,数回実行してみてください.すでにデータ取得している銘柄は,スキップして実行されるようになっています.

get_stock_data.py
import glob
import pandas_datareader.data as web
import pandas as pd

# 東証全銘柄取得
def get_tokyo():
    tokyo = pd.read_csv("data_j.csv", engine="python", names=("date","code", "name","market", "CodeIndustry33", "ClassificationIndustry33", "CodeIndustry17", "ClassificationIndustry17", "CodeScale", "ClassificationScale"), skiprows=1, usecols=[1,2], encoding="utf-8")
    return tokyo
# 株価取得
def get_price_csv(stock_name,start=None,end=None):
    data = web.DataReader(stock_name,"yahoo",start,end)
    data.to_csv("./data/"+stock_name+".csv")

list_tokyo = get_tokyo()
list_file_exist = glob.glob("./data/*")

for code in list_tokyo.code:
    filename = './data\\'+str(code)+'.T.csv'
    if filename in list_file_exist:
        pass
    else:
        print(code, 'GET... ', end='')
        try:
            get_price_csv(str(code)+'.T', start='2015-1-1')
            print('SUCCESS')
        except Exception as e:
            print('FAIL:', e)

実行結果

1432 GET... FAIL: No data fetched for symbol 1432.T using YahooDailyReader
1440 GET... FAIL: No data fetched for symbol 1440.T using YahooDailyReader
25935 GET... FAIL: No data fetched for symbol 25935.T using YahooDailyReader
3456 GET... FAIL: No data fetched for symbol 3456.T using YahooDailyReader
3483 GET... FAIL: No data fetched for symbol 3483.T using YahooDailyReader
6695 GET... FAIL: No data fetched for symbol 6695.T using YahooDailyReader
6945 GET... FAIL: No data fetched for symbol 6945.T using YahooDailyReader
7693 GET... FAIL: No data fetched for symbol 7693.T using YahooDailyReader
7790 GET... FAIL: No data fetched for symbol 7790.T using YahooDailyReader
9388 GET... FAIL: No data fetched for symbol 9388.T using YahooDailyReader
9437 GET... FAIL: No data fetched for symbol 9437.T using YahooDailyReader

以上のような表示が出ればYahoo! ファイナンスから取得できる時系列データはすべて取得することができています.

data_j.csvに上場廃止した銘柄などが含まれているので,No data fetched for symbol 9437.T using YahooDailyReaderというエラーが起きます.9437はNTTドコモの証券コードですが,2020年12月25日に上場廃止したので,Yahoo! ファイナンスからも取得できなくなっています.data_j.csvは2020年11月末時点のリストを使用したのでこのようなことが起きています.

  • 各銘柄のボラティリティを計算する 全銘柄のボラティリティの平均や出来高の平均などを計算します.

その後,【銘柄選定基準】を満たす銘柄を選びます.

【銘柄選定基準】
・東京証券取引所に上場している
・1日の平均出来高50万株以上
・1日の平均終値50円以上1千円以下

最後に,【銘柄選定基準】を満たした銘柄をボラティリティが高い順に並べ替え,その結果をresult.csvというCSVファイルに保存します.

analysis_volatility.py
import pandas as pd
import glob
from tarade_module import trade_module

# 各銘柄のファイル名をリストに保存
list_file_exist = glob.glob("./data/*.csv")
# データフレーム作成
df_result = pd.DataFrame(columns=['Name','AveVolatility','StdVolatility','AveClose','AveVol'])
# 東証上場企業のコード,銘柄名
list_tokyo = trade_module.get_tokyo()
# 各銘柄のボラティリティを計算
for filename in list_file_exist:
    # csvファイル読み込み,最新90日分
    df = pd.read_csv(filename, usecols=[0,1,2,3,4,5])[-91:-1]
    # トゥルー・レンジ,ティピカル・プライス
    df_TR = pd.concat([df['High']-df['Low'],df['High']-df['Close'],df['Close']-df['Low']], axis=1).max(axis=1)
    df_TP = pd.concat([df['High'],df['Low'],df['Close']], axis=1).mean(axis=1)
    # ボラティリティを計算
    df_volatility = df_TR/df_TP*100
    # 結果を表示
    #print(pd.concat([df,df_TR,df_TP,df_volatility], axis=1))
    # ボラティリティの平均,標準偏差,終値の平均,出来高平均
    df_var_volatility = df_volatility.describe()['std']
    df_ave_volatility = df_volatility.describe()['mean']
    df_ave_close = df['Close'].describe()['mean']
    df_ave_vol   = df['Volume'].describe()['mean']
    # 結果をデータフレームに追加
    df_result.loc[filename[7:-6]] \
        = [list(list_tokyo[list_tokyo.code==int(filename[7:-6])].name)[0].replace('\uff0d', '-'),df_ave_volatility, df_var_volatility, df_ave_close, df_ave_vol]

# 平均出来高10万株以上の銘柄に限定
df_result_vol100000 = df_result[(df_result['AveVol']>=500000)&(df_result['AveClose']<=1000)&(df_result['AveClose']>=50)]
print(df_result_vol100000.sort_values('AveVolatility', ascending=False))

# 結果を出力, エクセルなどで開くにはshift_jis保存
df_result_vol100000.sort_values('AveVolatility', ascending=False).to_csv('result.csv', encoding="shift-jis")

以下はanalysis_volatility.pyを実行すると出力される結果のファイルです.

result.csv
,Name,AveVolatility,StdVolatility,AveClose,AveVol
9878,セキド,10.809049789076301,7.314944329411106,700.6222222222223,573921.1111111111
5337,ダントーホールディングス,9.92851091994063,5.829129218840265,502.6666666666667,2699480.0
6400,不二精機,9.232563647851181,7.672251177037584,529.4888888888889,2886708.888888889
1757,クレアホールディングス,8.889975863776952,7.016323356057786,70.56666666666666,5968156.666666667
5952,アマテイ,8.686544778591992,7.34355369522554,172.5888888888889,3114187.777777778
6659,メディアリンクス,8.254688076596251,5.6874328079605885,486.9555555555556,1453527.7777777778
3645,メディカルネット,8.223274540256988,4.850665328679253,747.3,798738.8888888889
2586,フルッタフルッタ,7.912068446987964,7.29850462904478,191.46666666666667,1508417.7777777778
2191,テラ,7.6447258931396584,4.669643170906718,769.2333333333333,2084926.6666666667
3071,ストリーム,7.585819866804538,4.527170396284742,140.04444444444445,1470285.5555555555
5950,日本パワーファスニング,7.446253747895412,7.0779071007236425,130.16666666666666,1637843.3333333333
4772,ストリームメディアコーポレーション,7.40040609365142,4.485065376345073,319.1,869971.1111111111
7625,グローバルダイニング,7.314423734014114,7.072805004359223,190.6888888888889,1005386.6666666666
2459,アウンコンサルティング,7.278918564082975,6.233558919862727,233.4111111111111,652956.6666666666
3909,ショーケース,7.070315411038762,4.199331323446159,894.6888888888889,626668.8888888889
7608,エスケイジャパン,6.917702918007265,6.282340278985323,418.74444444444447,666320.0
3776,ブロードバンドタワー,6.822772043330271,4.151829727326367,383.7111111111111,6908035.555555556
3264,アスコット,6.757181195267253,4.057884357817521,219.27777777777777,1141836.6666666667
6181,タメニー,6.682391492378576,4.773359432557025,187.17777777777778,1400831.111111111
7448,ジーンズメイト,6.599137804949079,6.763328689586605,274.74444444444447,780083.3333333334
3556,リネットジャパングループ,6.574738312165315,4.017277288743839,576.7111111111111,550920.0
6779,日本電波工業,6.535959574856966,4.692920736222049,457.7888888888889,655442.2222222222
...
2768,双日,1.7286240106385853,0.6413982014908903,237.5222222222222,5425306.666666667
9508,九州電力,1.7224171026624042,0.6751134425152434,922.9555555555555,1161698.888888889
8601,大和証券グループ本社,1.6910960304616955,0.8788866774373107,462.2066667344835,4492950.0
6178,日本郵政,1.6550971289374474,0.6455812899662257,766.626666937934,5703288.888888889
8355,静岡銀行,1.6505290019625318,0.792098756434268,746.7666666666667,1464793.3333333333
7182,ゆうちょ銀行,1.612183745456123,0.6859743481317608,848.5888888888888,2572678.888888889
5020,ENEOSホールディングス,1.5173205855293537,0.6202105245263454,379.30999959309895,16662358.888888888
8628,松井証券,1.5071163588276981,0.6693067820926953,890.8111111111111,673733.3333333334
8306,三菱UFJフィナンシャル・グループ,1.499832308077275,0.6679667878012966,440.46333279079863,55239340.0
1671,WTI原油価格連動型上場投信,1.1440880131986952,0.7287071846639852,864.4333333333333,2514501.388888889
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[python] 16進数表記のRGB値を暗くするメソッド

def darken(rgb, rate=0.5):
    rgb = rgb.replace('#', '')
    s = '#'
    for i in [0,2,4]:
        c = rgb[i:i+2]
        c = int(c, 16)
        c = int(c * rate)
        c = format(c, '02x')
        s += c
    return s

# 使用例
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
color_orig = [
    '#C5DAEE',
    '#ABCFE5',
    '#8DC0DD',
    '#6AADD5',
    '#4F9BCB',
    '#3787C0',
    '#2070B4',
    '#0F5BA3',
    '#08458B',
    '#08306B',
]
color = [
    [darken(c) for c in color_orig],
    color_orig,
]
df = pd.DataFrame([np.ones(10), np.ones(10)], columns=[x[1] + '\n' + x[0] for x in np.array(color).T])
df.T.plot.bar(color=color, stacked=True)
plt.legend().remove()
plt.yticks([])
plt.xticks(rotation=0)
plt.show()

out:

スクリーンショット 2021-01-06 午後2.59.40.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

多目的最適化問題のNSGA-Ⅲを実装してみた

  • 製造業出身のデータサイエンティストがお送りする記事
  • 今回は多目的最適化手法の中で、NSGA-Ⅲを実装(サンプルコード使用)しました。

はじめに

先日、ご紹介した多目的最適化(NSGA-Ⅲ)を実装したのでご紹介します。
NSGA-Ⅲとは何か?と言うことについては上記の記事に書いておりますので、そちらをご参照ください。

使用するライブラリー(deap)

今回も遺伝的アルゴリズムライブラリdeapを使って実装したいと思います。

NSGA-Ⅲの実装

今回はdeapのチュートリアルにNSGA-Ⅲのサンプルがありましたので、そちらを活用しました。

pythonのコードは下記の通りです。

# 必要なライブラリーのインポート
from math import factorial
import random

import matplotlib.pyplot as plt
import numpy
import pymop.factory

from deap import algorithms
from deap import base
from deap.benchmarks.tools import igd
from deap import creator
from deap import tools

import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d as Axes3d

最初に遺伝的アルゴリズムの設計を行っていきます。

# 問題設定
PROBLEM = "dtlz2"
NOBJ = 3
K = 10
NDIM = NOBJ + K - 1
P = 12
H = factorial(NOBJ + P - 1) / (factorial(P) * factorial(NOBJ - 1))
BOUND_LOW, BOUND_UP = 0.0, 1.0
problem = pymop.factory.get_problem(PROBLEM, n_var=NDIM, n_obj=NOBJ)

# アルゴリズムのパラメータ
MU = int(H + (4 - H % 4))
NGEN = 400
CXPB = 1.0
MUTPB = 1.0

# reference point
ref_points = tools.uniform_reference_points(NOBJ, P)

# 適合度を最小化することで最適化されるような適合度クラスの作成
creator.create("FitnessMin", base.Fitness, weights=(-1.0,) * NOBJ)
# 個体クラスIndividualを作成
creator.create("Individual", list, fitness=creator.FitnessMin)

# 遺伝子生成の関数
def uniform(low, up, size=None):
    try:
        return [random.uniform(a, b) for a, b in zip(low, up)]
    except TypeError:
        return [random.uniform(a, b) for a, b in zip([low] * size, [up] * size)]

# Toolboxの作成
toolbox = base.Toolbox()
# 遺伝子を生成する関数"attr_gene"を登録
toolbox.register("attr_float", uniform, BOUND_LOW, BOUND_UP, NDIM)
# 個体を生成する関数”individual"を登録
toolbox.register("individual", tools.initIterate, creator.Individual, toolbox.attr_float)
# 個体集団を生成する関数"population"を登録
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
# 評価関数"evaluate"を登録
toolbox.register("evaluate", problem.evaluate, return_values_of=["F"])
# 交叉を行う関数"mate"を登録
toolbox.register("mate", tools.cxSimulatedBinaryBounded, low=BOUND_LOW, up=BOUND_UP, eta=30.0)
# 変異を行う関数"mutate"を登録
toolbox.register("mutate", tools.mutPolynomialBounded, low=BOUND_LOW, up=BOUND_UP, eta=20.0, indpb=1.0/NDIM)
# 個体選択法"select"を登録
toolbox.register("select", tools.selNSGA3, ref_points=ref_points)

各コードで何を実施しているのかはコメント文で分かるように記載しているため、コード全体が見にくくなっている部分はご容赦願います。

今回は、DTLZ2問題(ベンチマーク関数)を使用しています。

次に実際の進化計算部分になります。

def main(seed=None):
    random.seed(1)

    # 世代ループ中のログに何を出力するかの設定
    stats = tools.Statistics(lambda ind: ind.fitness.values)
    stats.register("avg", numpy.mean, axis=0)
    stats.register("std", numpy.std, axis=0)
    stats.register("min", numpy.min, axis=0)
    stats.register("max", numpy.max, axis=0)

    logbook = tools.Logbook()
    logbook.header = "gen", "evals", "std", "min", "avg", "max"

    # 第一世代の生成
    pop = toolbox.population(n=MU)
    pop_init = pop[:]
    invalid_ind = [ind for ind in pop if not ind.fitness.valid]
    fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
    for ind, fit in zip(invalid_ind, fitnesses):
        ind.fitness.values = fit

    record = stats.compile(pop)
    logbook.record(gen=0, evals=len(invalid_ind), **record)
    print(logbook.stream)

    # 最適計算の実行
    for gen in range(1, NGEN):
        # 子母集団生成
        offspring = algorithms.varAnd(pop, toolbox, CXPB, MUTPB)

        invalid_ind = [ind for ind in offspring if not ind.fitness.valid]
        # 適合度計算
        fitnesses = toolbox.map(toolbox.evaluate, invalid_ind)
        for ind, fit in zip(invalid_ind, fitnesses):
            ind.fitness.values = fit

        # 次世代選択
        pop = toolbox.select(pop + offspring, MU)

        record = stats.compile(pop)
        logbook.record(gen=gen, evals=len(invalid_ind), **record)
        print(logbook.stream)

    return pop, pop_init, logbook

あとは、このmain関数を呼び出します。

if __name__ == "__main__":
    pop, pop_init, stats = main()
    pop_fit = numpy.array([ind.fitness.values for ind in pop])

    pf = problem.pareto_front(ref_points)

今回は、Reference Pointsと最終世代の結果を可視化してみたいと思います。

fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(111, projection="3d")

p = numpy.array([ind.fitness.values for ind in pop])
ax.scatter(p[:, 0], p[:, 1], p[:, 2], marker="o", s=24, label="Final Population")

ax.scatter(pf[:, 0], pf[:, 1], pf[:, 2], marker="x", c="k", s=32, label="Ideal Pareto Front")

ref_points = tools.uniform_reference_points(NOBJ, P)

ax.scatter(ref_points[:, 0], ref_points[:, 1], ref_points[:, 2], marker="o", s=24, label="Reference Points")

ax.view_init(elev=11, azim=-25)
ax.autoscale(tight=True)
plt.legend()
plt.tight_layout()

nsga-3.png

さいごに

最後まで読んで頂き、ありがとうございました。
今回は、多目的最適化のNSGA-Ⅲについてサンプルコードを確認しました。

実際の業務で使う際は、NSGA-Ⅱ、NSGA-Ⅲともに制約条件を業務実態に合わせた形で細かく整理する部分が大変になるのかなと思います。
また、目的変数が4つ以上でパレート解を求めようと思うと可視化できないため、その部分も工夫する必要がありそうです。

訂正要望がありましたら、ご連絡頂けますと幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pandasでの棒グラフ表示(基礎編)

やりたいこと

  • pandasでCSVデータを読み込んで棒グラフを表示したい。
  • グラフの色やサイズも自由にカスタムしたい。
  • 基本操作の習得が目的であるため、データセットは自作したシンプルな内容を扱う。
  • ここでは、データの「集計」ではなく「可視化」が目的なので、groupbyなどは使わない。

前提

  • jupyterの環境が構築済みであること。
  • 必要なpythonライブラリをインストール済みであること。

公式ドキュメント

pandas公式ドキュメント。plot.barの仕様。

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.plot.bar.html

CSVデータセット

sample.csv

自作した架空データ。
酒類を販売する会社の売り上げを示す。
ビールと日本酒の月別売り上げ金額(億円)。
たとえば2月は、ビール(beer)を431億円、日本酒(sake)を267億円、売り上げた。

month,beer,sake
1,1024,667
2,431,267
3,341,166
4,530,461
5,482,367
6,339,331
7,1203,227
8,1376,312
9,896,211
10,754,361
11,561,325
12,938,452

以下、pythonでの実装。

ライブラリをインポートする

import pandas as pd
import matplotlib as mpl
import matplotlib.pyplot as plt

CSVを読み込む

df = pd.read_csv('csv/sample.csv')

dfの中身は下記。

image.png

月別のビールの売り上げを表示する

df.plot.bar('month', 'beer')

横軸が「月」で、縦軸が「ビールの売り上げ金額(億円)」となる。

image.png

  • 8月の売り上げが最も多い(1376億円である)ことが分かる。

月別の日本酒の売り上げを表示する

df.plot.bar('month', 'sake')

横軸が「月」で、縦軸が「日本酒の売り上げ金額(億円)」となる。

image.png

  • 1月の売り上げが最も多い(667億円である)ことが分かる。

月別の「ビールと日本酒の売り上げ」を表示する

df.plot.bar('month',{'beer','sake'} )

横軸が「月」で、縦軸が「ビール(青)と日本酒(オレンジ)の売り上げ金額(億円)」となる。

image.png

下記が分かる。

  • ビールの売り上げ最多は8月だが、日本酒の売り上げ最多は1月である。
  • 日本酒よりもビールのほうが売り上げ金額が大きい。

月別の「ビールと日本酒の売り上げ」を表示する(積み上げ)

stacked=True を付けると積み上げグラフになる。

df.plot.bar('month',{'beer','sake'}, stacked=True)

横軸が「月」で、縦軸が「ビール(青)と日本酒(オレンジ)の売り上げ金額(億円)」(積み上げ)となる。

image.png

下記が分かる。

  • ビールと日本酒を合計して、もっとも売れるのは1月と8月である。
  • 日本酒よりもビールのほうが総売り上げに対する比率が高い。

棒の色を変更する

  • ビールは赤色、日本酒は緑色、にしたい場合。
df.plot.bar(
  'month',{'beer','sake'},
   color={"beer": "red", "sake": "green"}
)

image.png

積み上げ

df.plot.bar(
  'month',{'beer','sake'},
   color={"beer": "red", "sake": "green"},
   stacked=True
)

image.png

グラフ表示領域のサイズを拡大縮小する

figsize で指定する。

figsize=(5,5)

df.plot.bar(
  'month',{'beer','sake'},
  figsize=(5,5)
)

image.png

figsize=(8,5)

image.png

figsize=(5,10)

image.png

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonでボードゲーム「バンカース」の家の止まりやすさを計算した

年末年始にボードゲーム「バンカース」をプレイして、明らかに止まりやすい家があるなと興味を持ち、シミュレーションして、止まりやすさを計算することにした。
環境はiPhoneのPythonistaで行った。

シミュレーションの方針

シミュレーションを行い、家に止まった回数をカウントし、それを十分大きな数だけ繰り返すことで収束した値を比較する。

盤の実装

盤には40マスあり、40番目の次は1番目に戻る。
したがって、総移動数をカウントし、剰余演算でどこのマスにいるかを判定する。

実装しないマス

止まりやすさ(移動の仕方)に影響しないマスについては特に細かいところは実装せず、何も起こらないマスとして扱う(実際にはお金を取られたりもらったりする)。

止まったことの判定の仕方

止まったと判定するのは1ターンの最後に止まったマスとしている。なぜなら、家に泊まった場合は必ずそこでターンが終了するため。また、このシミュレーションの目的は家の止まりやすさを計算し、比較することにある。

マスの評価の仕方

マスを移動する関数を作成し、それを介して移動することで、マスの評価を行う。

マスの評価関数
def stepEval(steps, count, card):
    if (count + steps) % len(BOARD) == 18:
        if steps == 7:
            return steps
    if steps == 0:
        return 0
    else:
        count += steps
        next_steps = BOARD[where_board(count)].getMove(card, where_board(count))
        return steps + stepEval(next_steps, count, card)

実行結果例

1BC8212B-5384-49C3-94BB-C04FFBBAA1BD.jpeg

ソースコード

GitHub: https://github.com/huwns/bankers/blob/main/analyze_bankers.py

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

機械学習における競馬予測

はじめに

みなさんは競馬をしたことがありますか?
私は機械学習を勉強して初めて、競馬に触れました。
プログラミングを勉強する上で「手を動かしながら学ぶ」というのがあります。私は、勉強するなら楽しく、ビジネスに貢献できる題材がいいと思いました。
そこで色々調べた結果、特徴量(予測する際に使う要素)が多く、ビジネスに関係のある競馬を選びました。
最初は「馬が速いか遅いかなんて馬にしか分からないでしょ。」と思っていましたが、機械学習を使うことである程度は予測できるということを知って、今回の記事を書きました。
初めての記事なので、分かりにくい部分が多々あるかと思いますが、その際はご指摘いただけると幸いです。

行ったこと

競馬データを分析し、予測精度(AUC)を出すところまで行いました。本当は実際に予測した後に、回収率などを出せるともっと面白そうですが、そこまでの技術はないので「データの取得→前処理→学習→AUCの算出→考察」まで行いました。

環境

Google Colab(初めはローカルのJupyter labで行っていましたが、私のPCでは学習時にkernelが落ちてしまうため、Google colabを使用しました。)

手順

1 https://www.netkeiba.com/よりスクレイピング
2 前処理
3 lightGBMで学習、予測

1 netkeiba.comよりスクレイピング

この工程は https://qiita.com/penguinz222/items/6a30d026ede2e822e245 を参考にさせていただきました。

2 前処理

スクレイピングしてきたCSVファイルはこんな感じです。
スクリーンショット 2021-01-05 13.54.17.png
データの大きさは約50万行×20列です。
これらに対して前処理を行っていきます。

先に、一般には馴染みのない列名があるので解説します。
・c_weight...前回のレースからの体重差
・j_weight...ジョッキーの体重
・popu...人気(運営側が事前情報によって馬それぞれに人気をつけます)
・odds...払い戻し額÷掛け金(人気な馬ほどoddsは低いです。)
・trainerA,trainerB...調教師

2-1 データを扱いやすくする

一番左の列がUnnamed:0となっています。この列は開催された日付、レース番号、馬番が連なっていますので、これは2列に分解します。

# データの読み込み
# Unnamed0のカラムは日付、レース番号、馬番号なのでrenameして分割。
keiba_data = pd.read_csv('/content/drive/MyDrive/競馬.csv', encoding = "shift-jis")

keiba_data.rename(columns={"Unnamed: 0":"date_num"},inplace=True)
keiba_data["date_num"]=keiba_data["date_num"].astype(str)
keiba_data["race_num"]=keiba_data["date_num"].str[0:12].astype(int)
keiba_data["horse_num"]=keiba_data["date_num"].str[12:14].astype(int)
keiba_data.drop(columns=["date_num"],inplace=True)
# 扱いやすいようにrace_numとhorse_numは一番左に配置。
keiba_data=keiba_data.reindex(columns=['race_num','horse_num','age', 'c_weight', 'course', 'date', 'field', 'gender', 'head_count',
       'horse_name', 'j_weight', 'jackie', 'odds', 'popu', 'race', 'race_name',
       'rank', 'trainerA', 'trainerB', 'weight', 'year'])

2-2 欠損値処理

データに欠損値があるか確認します。

keiba_data.isnull().sum()

スクリーンショット 2021-01-05 14.18.19.png

いくつか欠損値があります。今回、学習に使うモデルはlightGBMという欠損値の処理をしなくていいモデルなのですが、どこかでlightGBMも欠損値処理をした方が精度があがるという記事をみたので(すいません、どの記事かは忘れてしまいました。)欠損値処理を行います。

intのカラムは0や平均値で埋めていき、objectのカラムなどはdropしていきます。
popuとoddsはかなり重要な要素となることが予想されるので、欠損値は平均値や中央値では埋めずdropしました。

#欠損値処理
keiba_data["c_weight"].fillna(0,inplace=True)
keiba_data["j_weight"].fillna(keiba_data["j_weight"].mean(),inplace=True)
keiba_data["weight"].fillna(keiba_data["weight"].mean(),inplace=True)
keiba_data.dropna(subset=["race_name"],inplace=True)
keiba_data.dropna(subset=["odds"],inplace=True)
keiba_data.dropna(subset=["popu"],inplace=True)

2-3 データ型の確認

keiba_data.dtypes

スクリーンショット 2021-01-05 14.47.56

object型は全てLabel Encoderによってint型に変換します。
その後、yearなどの不要な列を削除します。

#labelencoderを使って、カテゴリ変数を変換。
le=LabelEncoder()
keiba_categorical = keiba_data[["gender","field","horse_name","course","head_count","trainerA","trainerB","race","jackie","race_name"]].apply(le.fit_transform)
keiba_categorical = keiba_categorical.rename(columns={"race_name":"race_name_c","filed":"field_c","gender":"gender_c","horse_name":"horse_name_c","course":"course_c","head_count":"head_count_c","trainerA":"trainerA_c","trainerB":"trainerB_c","jackie":"jackie_c"})
keiba_data = pd.concat([keiba_data,keiba_categorical],axis=1)
# 変換前と不要な列を削除
keiba_data.drop(columns=["race_num","horse_num","date","year","race_name","race","trainerA","trainerB","course","field","gender","jackie","head_count","horse_name"],inplace=True)

2-4 特徴量生成

oddsとpopuはかなり重要な特徴量と考え、それらの積をとった特徴量とc_weightから前回の体重が分かるので前回の体重を新たに特徴量として追加しました。

# 特徴量生成
# 1つ目はoddsとpopuの積
# 2つ目は前回の体重
keiba_data["odds_popu"]=keiba_data["odds"]*keiba_data["popu"]
keiba_data["pre_weight"]=keiba_data["weight"]-keiba_data["c_weight"]

2-5 目的変数の処理

ここから目的変数であるrankの処理を行います。

# rankの確認
keiba_data["rank"].unique()

スクリーンショット 2021-01-05 15.55.59.png

すると順位以外に中止、失格の行があります。rankが分からなければ、学習もできないのでこの行は全てdropします。count()で数えると約2000と数も少ないのでdropしても大丈夫そうです。

#中止、失格の行は全て削除する。
delete_index = keiba_data.index[((keiba_data["rank"]=="中止") | (keiba_data["rank"]=="失格")]
keiba_data.drop(delete_index,inplace=True)

さて、順位を予測したいところですが、競馬は基本的には3着以内に入るかが賞金に関わってきます。逆に言えば、7位や8位の馬をドンピシャで当てる必要はありません。
ということで、3着以内かそれより下位かの二値分類問題にしようと思います。要は勝つか負けるかですね。
他の方の記事では上位、中位、下位と3つに分けて多値分類問題にしてる方もいらっしゃいました。

# 1,2,3着かそれ以外かに分割して、二値分類問題にする。
keiba_data["rank"]=keiba_data["rank"].astype(int)
keiba_data = keiba_data.assign(target = (keiba_data['rank'] <= 3).astype(int))

3着以内であれば1,それより下位であれば0を与えたtargetという列を作りました。

ここまでが前処理になります。次はlightGBMを使って、学習→予測まで行います。
ここまで処理したデータはこんな感じです。
スクリーンショット 2021-01-06 11.26.55.png

3 学習、予測

特徴量と目的変数をXとyに分割して、さらに学習用データと評価用データに分割します。
またそれらのデータをlgb.DatasetでlightGBMで読めるデータに変換します。

# trainデータおよびtestデータの分割と特徴量および目的変数の分割
import lightgbm as lgb
X = keiba_data.drop(['rank','target'], axis=1)
y = keiba_data['target']

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.33, random_state=0)

# データの変換
lgb_train = lgb.Dataset(X_train, y_train)
lgb_eval = lgb.Dataset(X_test, y_test)

パラメータチューニングに関してはoptunaというフレームワークを使って、自動で決めます。

# ハイパーパラメータをoptunaで自動設定

%%time
!pip install optuna
from optuna.integration import lightgbm as lgb

params = {
    'objective': 'binary',
    'metric': 'auc'
}

best_params, history = {}, []
model = lgb.train(params, lgb_train, valid_sets=[lgb_train,lgb_eval],
                    verbose_eval=False,
                    num_boost_round=10,
                    early_stopping_rounds=10)
best_params_ = model.params

# モデルの作成
import lightgbm as lgb_orig

model = lgb_orig.train(best_params_, 
                       lgb_train,
                       valid_sets=lgb_eval,
                       num_boost_round=100,
                       early_stopping_rounds=10)

最終的なAUCは

スクリーンショット 2021-01-05 16.28.50.png
でした。AUCなので82%当てているということではないですが、思ったよりいい数値が出てくれました。
(AUCについてはこちらのサイトが分かりやすかったです。
https://techblog.gmo-ap.jp/2018/12/14/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%81%AE%E8%A9%95%E4%BE%A1%E6%8C%87%E6%A8%99-roc%E6%9B%B2%E7%B7%9A%E3%81%A8auc/)

最後に特徴量の重要度をみてみます。

#特徴量の重要度を表示。
keiba_data.drop(columns = ["rank","target"],inplace=True)
importance = pd.DataFrame(model.feature_importance(), index=keiba_data.columns,columns=['importance'])
importance=importance.sort_values(by="importance",ascending=False)
display(importance)

スクリーンショット 2021-01-05 16.35.40.png

やはりoddsはかなり重要な特徴量であるようです。また馬に乗るジョッキー(jackie_c)も重要なようです。
意外だったのはpopuやtrainerAの重要度が低いという点です。しかしtrainerBは重要度が高いです。この辺りは競馬に詳しい人に聞いた方が良いかもしれないです。筆者は競馬を数回しかやったことがないので、この辺りはまだまだ勉強が必要です。

おわりに

今回、前処理について基礎的なことしか行っていませんが、AUCは0.816736という割と良い数値がでました。
結構当てくれるので、これを使えば儲かるかと言われるとまたそれは別の問題になってきます。モデルは3着以内に入るか否かだけを判定してくれてるので、いくら賭けていくら戻ってくるなどの回収率は一切考慮していません。
人気な馬に賭けておけば的中しやすくなりますが、その分oddsは低いので払い戻し金額も少なくなってしまいます。
要するに「当たるけど、戻ってくるお金は少ない」ということです。
理想は穴馬(oddsが高い不人気な馬)にもかけつつ、人気な馬で安定的に稼ぐみたいな感じがいいのかなと思います。
最終的にできたモデルはDjangoなどで実装して日付とレース番号を入れると、予測を返してくれるようなものを作りたいと思っています。

これを作ってて思いましたが、好きな題材でプログラミングの勉強ができるのは良いですね。プログラミングを勉強する上で、実際に何かを作りながら勉強するのが大事だなと改めて実感しました。
最後までご覧いただきありがとうございました。

参考にさせていただいた記事

https://qiita.com/Mshimia/items/6c54d82b3792925b8199
https://qiita.com/km_takao/items/0a448543961a97fc9c94
https://qiita.com/km_takao/items/70f7a7c3c9c533d7bee4
https://techblog.gmo-ap.jp/2018/12/14/%E6%A9%9F%E6%A2%B0%E5%AD%A6%E7%BF%92%E3%81%AE%E8%A9%95%E4%BE%A1%E6%8C%87%E6%A8%99-roc%E6%9B%B2%E7%B7%9A%E3%81%A8auc/

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PythonでKubernetesPodのCPU使用量を表示させる方法

PythonによるPodのCPU使用量を表示させるプログラム

from kubernetes import client, config
import json

config.load_kube_config()
api = client.CustomObjectsApi()
resource = api.list_namespaced_custom_object(group="metrics.k8s.io",version="v1beta1", namespace="c0118220", plural="pods")


#print(json.dumps(resource,ensure_ascii=False, indent=4, sort_keys=True, separators=(',', ': ')))
for pod in resource["items"]:
        print(pod["metadata"]["name"],pod['containers'])
~                                                                                ~                                                            
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【注意】mecabでシステム辞書やユーザー辞書を指定する【Windows】

MeCabをpythonで利用していて辞書指定で引っかかったことがあったので備忘録として残しておく。
1つの処理の間でユーザ辞書を切り替える必要があったことが背景になっている。

mecabの公式ページは以下。
https://taku910.github.io/mecab/
大体のことはここに書いてあるが、懇切丁寧に書いてあるわけではないので別途調べる必要がある。
mecabはWindowsを前提に作られているわけではなく、調べて出てくるドキュメントもWindows向けのものが少なかった印象。

結論

パスは¥\で区切らずにせずに/で区切れ
辞書へのパス指定時にスペースを入れるな

解説

ここではpythonコードから辞書指定をするときについて解説する。

ちなみにコマンドラインから実行する場合はスペースがあっても問題ない。
ただ、スペースで区切りとみなされるためパス全体を""で覆うこと

# システム辞書指定
mecab -d "C:\Program Files (x86)\MeCab\dic\ipadic"

# ユーザ辞書指定
mecab -u "C:\Program Files (x86)\MeCab\dic\ipadic\user.dic"

※ユーザ辞書は自分が作成して置いたパスを指定する(作成方法は別途調べてください。)

pythonコードから辞書指定

辞書指定するときにはTagger作成時に引数に渡す必要がある

import MeCab

tagger = MeCab.Tagger("-d [システム辞書へのパス]")
tagger = MeCab.Tagger("-u [ユーザ辞書へのパス]")

Windowsの場合、多分C:\Program Files (x86)\MeCab\dic\ipadicに辞書があるはず。(x86じゃない方の可能性も)

それを上記の[辞書へのパス]に書くのだが2つ注意点がある。

  1. パスは/で区切らなくてはならない
  2. スペースを入れてはならない

どちらもMeCabの仕様ではなくpythonの仕様によるもの。

パスは/で区切らなくてはならない

文字列のダブルクォーテーション""の中で¥\を使うとエスケープ文字とみなされてしまう。

import MeCab

tagger = MeCab.Tagger("-d C:\Program Files (x86)\MeCab\dic\ipadic")
tagger = MeCab.Tagger("-u C:\Program Files (x86)\MeCab\dic\ipadic\user.dic")

ではなく

tagger = MeCab.Tagger("-d C:/Program Files (x86)/MeCab/dic/ipadic")
tagger = MeCab.Tagger("-u C:/Program Files (x86)/MeCab/dic/ipadic/user.dic")

※ユーザ辞書は自分が作成して置いたパスを指定する(作成方法は別途調べてください。)

これはVSCodeなどのエディタを使っていればエラーとして表示してくれるためすぐに気付ける。

※追記※
他の解決策として
r"-d C:\Program Files (x86)\MeCab\dic\ipadic"
のように文字列の前にrをつけることで¥\のままでも問題なく動作します。
@palm23さん教えていただきありがとうございました。

スペースを入れてはならない

デフォルトだとC:\Program Files (x86)\MeCab\dic\ipadicに辞書があると思うが、Program Files (x86)にスペースが入っているとエラーになる。

辞書指定したいのならば、パスにスペースを含まない別の場所に辞書をコピーし、その辞書を指定する必要がある。
例えばC直下にmecabというフォルダを作成して辞書を配置し、以下のように指定する。

import MeCab

tagger = MeCab.Tagger("-d C:/mecab/ipadic")
tagger = MeCab.Tagger("-u C:/mecab/ipadic/user.dic")

※スペースがあるのがだめならクォーテーションで囲んでやればいいのでは?
tagger = MeCab.Tagger("-d 'C:/Program Files (x86)/MeCab/dic/ipadic'")
残念ながらこれでもエラーになる。

※追記※
mecab-python3の場合はスペースがあってもクォーテーションで囲えば正常に動作します。
pip install mecab-python3
@palm23さん教えていただきありがとうございました。

まとめ

mecabに限らず、pythonでパスを指定するときは、

  • パスは¥\で区切らずにせずに/で区切れ(もしくはr""
  • 辞書へのパス指定時にスペースを入れるな(もしくはmecab-python3を使う)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Opencvについて③

Opencv3系についてのメモ

基本的には公式ドキュメントを確認しながらの物。

メモについて
Opencvについて①
Opencvについて②

環境・使用画像は前回と同様。


①リサイズ

cv2.resize(src, dsize[, dst[, fx[, fy[, interpolation]]]])
cv2.resize(第1引数、第2引数、第3引数)のイメージ

自分が使用している画像の通常時

opencv.py
#通常画像
img = cv2,imread(cap_dir)
img.shape
>>>(340,255,3)

Opencvチュートリアルとしては

opencv公式.py
import cv2
import numpy as np

img = cv2.imread(cap_dir)
res = cv2.resize(img,None,fx=1, fy=1, interpolation = cv2.INTER_CUBIC)

img:表示画像
None:未使用
fx=2:元画像に対してxを何倍にするか
fy=2:元画像に対してyを何倍にするか
interpolation=cv2.INTER_CUBIC:画像拡大時の補間メソッド

又は

opencv公式.py
import cv2
import numpy as np

img = cv2.imread(cap_dir)

height, width = img.shape[:2]
res = cv2.resize(img,(2*width, 2*height), interpolation = cv2.INTER_CUBIC)

img.shape[:2]で画像のxとyの数値を取り込んで
(2*width, 2*height)で元の大きさに対して何倍かを決定している。

どちらも同じ事を実施しています。
元画像を元に変化させていくならこちらかと。(縦横比率は変わらない)

アスペクト比を気にせずに拡大・縮小をするのであれば

opencv.py
size = (300,200)
img_resize = cv2.resize(img,size)
#or
img_resize = cv2.resize(img,(300,200))

これだけでも可能です。

スクリーンショット (354).pngスクリーンショット (349).png
縦横比に関しては超適当です。
画像のアスペクト比を確認してからごちゃごちゃやるよりは
.shape[:2]で取り込んだ方がスマートかと


第3引数と記載している部分に関してのメソッド

interpolation = method
INTER_NEAREST
INTER_LINEAR(デフォルト設定)
INTER_AREA
INTER_CUBIC
INTER_LANCZOS4
と結構ありました

拡大時にはinterpolation=cv2.INTER_LINEAR(処理速い) or cv2.INTER_CUBIC(処理遅い)
縮小時にはinterpolation=cv2.INTER_AREA
との認識でよいかと…
英語表記なので細かいニュアンスが読み取れません…


②画像平行移動

opencv.py
img = cv2.imread(cap_dir)
h, w = img.shape[:2]
dx, dy = 30, 30

M = np.float32([[1,0,dx],[0,1,dy]])
img_afn = cv2.warpAffine(img,M,(w,h))

物体の位置を移動させる処理です.(x,y)方向への移動量が $(t_x,t_y)$ だとすると,この並進を表す変換行列 $\textbf{M}$ は以下のようになります:

M = \begin{bmatrix} 1 & 0 & t_x \\ 0 & 1 & t_y  \end{bmatrix}

by公式

opencv.py
img.shape
>>>(340, 255, 3)

afn_mat = np.float32([[1,0,dx],[0,1,dy]])
print(afn_mat)
[[ 1.  0. 30.]
 [ 0.  1. 30.]]

np.float32([[1,0,dx],[0,1,dy]])numpy配列で移動量を設定。

cv2.warpAffine(第1引数、第2引数、第3引数)
第1引数:画像
第2引数:移動量
第3引数:出力画像のサイズ ← 公式にはサイズを指定しないとエラー吐くって表記あり

これで画像が移動します。(現時点で使い道は思いつかず)

スクリーンショット (354).pngスクリーンショット (357).png


③画像回転

opencv.py
img = cv2.imread(cap_dir,0)
h, w = img.shape

M = cv2.getRotationMatrix2D((w/2,h/2),45,1)
img_afn2 = cv2.warpAffine(img,M,(w,h))

cv2.getRotationMatrix2D((w/2,h/2),45,1)
(w/2,h/2):回転中心位置。数値を変更すると中心位置がずれます。
45:回転角度。
1:倍率。2に設定したら表示枠はそのままで画像が2倍になりました。

スクリーンショット (360).png

カラー表示も問題なく出来ますが
カラーにすると(縦、横、色)と情報が増えるので
img.shapeの格納数を増やせばカラー画像の取り扱いの出来ます。
h, w, c = img.shapeって事です。

公式さん曰く


画像を回転角 $\theta$ 回転させるための変換行列は以下のようになります.

M = \begin{bmatrix} cos\theta & -sin\theta \\ sin\theta & cos\theta   \end{bmatrix}

OpenCVが提供する回転はスケーリングも同時に行い,回転の中心位置を変更できます.この変換を表す変換行列は以下のようになります.

\begin{bmatrix} \alpha &  \beta & (1- \alpha )  \cdot center.x -  \beta \cdot center.y \\ - \beta &  \alpha &  \beta \cdot center.x + (1- \alpha )  \cdot center.y \end{bmatrix}

ここで:

\begin{array}{l} \alpha =  scale \cdot \cos \theta , \\ \beta =  scale \cdot \sin \theta \end{array}

この変換行列を計算するための cv2.getRotationMatrix2D という関数があります.以下の例はスケーリングをせずに画像中心に対して90度回転する変換を試しています.

cv2.getRotationMatrix2D((w/2,h/2),45,1)は上記の計算式を用いて画像回転している様です。

公式には上記の様に記載されていますが式に関しては正直理解していませn。
どこの数値を触ると画像がどう変化するかを試してみた結果ですので
試してみて画像で理解するしかないかと思っています。


④まとめ

使う・使わない別としてのまとめ
画像処理に関してはかなり多いので多分使うであろう物を
別途まとめます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クラスター分析手法のひとつ k-means を scikit-learn で実行したり scikit-learn を使わず実装したりする

クラスターを生成する代表的手法としてk-meansがあります。これについては過去にも記事を書きましたが、今回は皆さんの勉強用に、 scikit-learnを使う方法と、使わない方法を併記したいと思います。

データの取得

機械学習の勉強用データとしてよく使われるあやめのデータを用います。

import urllib.request

url = "https://raw.githubusercontent.com/maskot1977/ipython_notebook/master/toydata/iris.txt"

filename = url.split("/")[-1]
urllib.request.urlretrieve(url, filename)
('iris.txt', <http.client.HTTPMessage at 0x7fac1779a470>)
import pandas as pd

df = pd.read_csv(filename, delimiter="\t", index_col=0)
df
Sepal.Length Sepal.Width Petal.Length Petal.Width Species
1 5.1 3.5 1.4 0.2 0
2 4.9 3.0 1.4 0.2 0
3 4.7 3.2 1.3 0.2 0
4 4.6 3.1 1.5 0.2 0
5 5.0 3.6 1.4 0.2 0
... ... ... ... ... ...
146 6.7 3.0 5.2 2.3 2
147 6.3 2.5 5.0 1.9 2
148 6.5 3.0 5.2 2.0 2
149 6.2 3.4 5.4 2.3 2
150 5.9 3.0 5.1 1.8 2

150 rows × 5 columns

この中の左2列のデータだけを使ってみましょう。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], s=50)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_4_0.png

scikit-learn で k-means

機械学習用ライブラリとして有名なscikit-learnの中で、k-meansを行うモジュールはこちらになります。

k-meansでは、クラスターの数 n_clusters をあらかじめ指定しておく必要があります。

from sklearn.cluster import KMeans 

kmeans_model = KMeans(n_clusters = 3).fit(df.iloc[:, :2]) 

たったこれだけで学習完了です。クラスタリング結果は次のようにして確認できます。

kmeans_model.labels_
array([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
       0, 0, 0, 0, 0, 0, 1, 1, 1, 2, 1, 2, 1, 2, 1, 2, 2, 2, 2, 2, 2, 1,
       2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2,
       2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1,
       1, 1, 1, 2, 2, 1, 1, 1, 1, 2, 1, 2, 1, 2, 1, 1, 2, 2, 1, 1, 1, 1,
       1, 2, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 1, 2, 1, 1, 2], dtype=int32)

同じクラスターに属するサンプルは同じ色にして描画してみましょう。

%matplotlib inline
import matplotlib.pyplot as plt

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=kmeans_model.labels_, s=50)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_10_0.png

scikit-lean を使わず k-means

さて、意味が分からなくても使えるscikit-learnは大変便利なのですが、意味が分からずに使っていると、もしも何か間違った使い方をしてしまってもそれに気づかなかったり、結果の解釈を誤ってしまったりする恐れがあります。ですので、何が行われているか基礎を確認してみましょう。おおよそ次のような手順で計算が行われます。

ランダムにラベルを与える

最終目標は、ラベルがクラスターを表すようにしたいのですが、最初はランダムから開始します。

import random

n_clusters = 3

df['labels'] = [random.randint(0, n_clusters) for x in range(len(df))]
df
Sepal.Length Sepal.Width Petal.Length Petal.Width Species labels
1 5.1 3.5 1.4 0.2 0 2
2 4.9 3.0 1.4 0.2 0 3
3 4.7 3.2 1.3 0.2 0 2
4 4.6 3.1 1.5 0.2 0 1
5 5.0 3.6 1.4 0.2 0 2
... ... ... ... ... ... ...
146 6.7 3.0 5.2 2.3 2 3
147 6.3 2.5 5.0 1.9 2 0
148 6.5 3.0 5.2 2.0 2 2
149 6.2 3.4 5.4 2.3 2 2
150 5.9 3.0 5.1 1.8 2 2

150 rows × 6 columns

%matplotlib inline
import matplotlib.pyplot as plt

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_13_0.png

重心を求める

ラベルが与えられた集団ごとに、重心を求めます。

centroids = []
for i in range(n_clusters):
    df_mean = df[df['labels'] == i].mean()
    centroids.append([df_mean[0], df_mean[1]])

centroids
[[5.7, 2.9851851851851845],
 [5.765789473684211, 3.189473684210527],
 [5.968749999999999, 3.014583333333333]]

重心を星印で描画してみましょう。

%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_17_0.png

ラベルを再配分する

各サンプルごとに、最も近い重心を選び、その重心のラベルに基づいてラベルを再配分します。

def nearest_centroid(centroids, x, y):
    min_dist = False
    min_id = False
    for i, xy in enumerate(centroids):
        dist = (xy[0] - x)**2 + (xy[1] - y)**2
        if i == 0 or min_dist > dist:
            min_dist = dist
            min_id = i

    return min_id
df['labels'] = [
                nearest_centroid(
                    centroids, 
                    df[df.columns[0]][x + 1], 
                    df[df.columns[1]][x + 1]
                    ) for x in range(len(df))
                    ]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_21_0.png

重心を求める

再配分されたラベルに基づいて、ラベルごとに重心を求めます。

centroids = []
for i in range(n_clusters):
    df_mean = df[df['labels'] == i].mean()
    centroids.append([df_mean[0], df_mean[1]])

centroids
[[5.20204081632653, 2.808163265306123],
 [5.239393939393939, 3.6272727272727274],
 [6.598529411764702, 2.9602941176470594]]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_24_0.png

ラベルを再配分する

再計算された重心に基づいて、ラベルを再配分します。

df['labels'] = [
                nearest_centroid(
                    centroids, 
                    df[df.columns[0]][x + 1], 
                    df[df.columns[1]][x + 1]
                    ) for x in range(len(df))
                    ]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_27_0.png

重心を求める

再配分されたラベルに基づいて、重心を再計算します。

centroids = []
for i in range(n_clusters):
    df_mean = df[df['labels'] == i].mean()
    centroids.append([df_mean[0], df_mean[1]])

centroids
[[5.219148936170212, 2.7851063829787237],
 [5.16969696969697, 3.6303030303030304],
 [6.579999999999997, 2.9700000000000006]]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_30_0.png

ラベルを再配分する

再計算された重心に基づいて、ラベルを再配分します。

df['labels'] = [
                nearest_centroid(
                    centroids, 
                    df[df.columns[0]][x + 1], 
                    df[df.columns[1]][x + 1]
                    ) for x in range(len(df))
                    ]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_33_0.png

重心を求める

再配分されたラベルに基づいて、重心を再計算します。

centroids = []
for i in range(n_clusters):
    df_mean = df[df['labels'] == i].mean()
    centroids.append([df_mean[0], df_mean[1]])

centroids
[[5.283333333333333, 2.7357142857142853],
 [5.105263157894737, 3.573684210526316],
 [6.579999999999997, 2.9700000000000006]]
%matplotlib inline
import matplotlib.pyplot as plt
import numpy as np

plt.figure(figsize=(6,6))
plt.scatter(df.iloc[:, 0], df.iloc[:, 1], c=df['labels'], s=50)
plt.scatter(np.array(centroids)[:, 0], np.array(centroids)[:, 1], 
            c=range(n_clusters), marker='*', s=500)
plt.xlabel(df.columns[0])
plt.ylabel(df.columns[1])
plt.show()

kmeans_36_0.png

以上のように、計算が落ち着くまで繰り返します。

課題

上記の「scikit-learnを使わないコード」について、以下の課題に取り組んでください。

  1. 計算が落ち着くまで繰り返し計算ができるように、上記のコードを改良してください。
  2. クラスターの数を増やして、挙動を確認してください。
  3. 左2列だけでなく左4列を用いて実行してください。
  4. 上記のアルゴリズムでは、たとえば n_clusters = 10 にしても、最終的に得られるクラスターの数が 10 にならないときがあります。それはなぜか説明し、それを避けるためにはアルゴリズムをどう改変すれば良いか提案してください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

tensorflow-gpuを動くようにするためにすること

初めに

パソコンを初期化したらtensorflowをgpuで動かすために色々入れなおさなきゃいけなかった。
そしたらいろいろ躓いたので、今後また入れなおすようにメモ

環境

windows10
RTX2070
Ryzen 3700x
Anaconda

道筋

Anacondaのダウンロード
環境の作成
tensorflowの依存環境の把握
cudaのダウンロード
cuDNNのダウンロード
パス

バージョン

2019年にtensorflowが1.xから2.xになって書き方が結構変わったみたい
よってネット上に結構あるプログラムが動かないのでないっぽい
よって環境を二つ作って、一つは2.xの最新版、もう一つは1.xの最新版を入れる

2.x

Python3.7
tensorflow-gpu==2.4.0
cudnn8.0.5
cuda11.0
keras-2.4.3

環境作成

https://qiita.com/ozaki_physics/items/985188feb92570e5b82d
に環境の作り方がある。

Conda create -n 37tens24 python=3.7 anaconda

versionの把握

次に、tensorflowの依存環境を把握
https://www.tensorflow.org/install/source_windows?hl=ja
最新版はなかったため、また時々間違っているため

https://www.kkaneko.jp/tools/win/keras.html

こちらで確認した
また、基本的にこのサイトに沿えば良い
最新版はkerasは依存間関係なさそう

pip install tensorflow-gpu==2.4.0
pip install keras

cuda toolkit

https://developer.nvidia.com/cuda-toolkit-archive
ここからダウンロード

11.0をダウンロード
やり直すことを考えてlocalの方をダウンロードしたほうがいい(500MBぐらい)

実行
最初はすべてダウンロード

https://www.kkaneko.jp/tools/win/cuda.html
ここが詳しい

cudnn

詳しい解説
https://www.kkaneko.jp/tools/win/cudnn805.html

https://developer.nvidia.com/cudnn
ここからダウンロード
そして解凍

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0
やり直すこと考えてこれをバックアップしてから解凍内容をコピペ

pathの追加

システム の環境変数の名前
CUDNN_PATH

C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.0
のように設定(管理者から)

なお、システムの環境変数の変更には管理者で開く必要がある。

https://www.lisz-works.com/entry/sys-env-admin

Windowsマーク右クリックからの管理者powershell

Start C:\Windows\system32\rundll32.exe sysdm.cpl, EditEnvironmentVariables

これで全て使えるようになる。

1.x

37tens115
tensorflow_gpu-1.15.0
Cudnn7.4.2では動かないので7.6.5
Cuda10.0
Keras2.3.1
最新版の Keras は TensorFlow 1.15 では動かない. Keras のバージョン 2.3.1 をインストールする

二つ目かつ古いバージョンの場合、cudaは
image.png
これだけでいい

このサイトが詳しい
https://www.kkaneko.jp/tools/win/cuda.html

※ 複数の版の CUDA ツールキットをインストールする場合には, 複数のパスが設定される このとき・古い版の方が先に来ている場合には、後になるように調整する

image.png

Cudnn_pathは11のままで動いた

まとめ

意外とすんなりいかず、大変なので頑張ってください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Jupyter notebook の結果を Qiita に上げるために markdown にする

Google colaboratory を使っていて、できあがった Jupyter notebook のファイル(.ipynb) を Qiita にアップしたいとき markdown に変換するのですが、これをコマンドラインで行ないたいと思ってもすぐには思い出せない。かといってググってもすぐには見つからないのでメモします。

jupyter nbconvert --to markdown Untitled.ipynb 

以上ッ!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む