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

manなど空白と改行が邪魔な英文をGoogle翻訳に投げるPythonスクリプト

manとかRFCとかテキストベースの情報をGoogle翻訳にかけたいけど、空白とか改行が邪魔で変な感じになりますよね。私は、こんな感じでやってます。

まず、Pythonでクリップボードの中身を取得し、空白やら改行を除去します。

#!/usr/bin/env python

import pyperclip
s = pyperclip.paste()

s = s.replace("\r"," ")
s = s.replace("\n"," ")

while -1 != s.find("  "):
    s = s.replace("  "," ")

print(s)
pyperclip.copy(s)

これを起動して、Google翻訳のコマンドライン版であるtransにパイプでつなげると、あら不思議、きれいに翻訳されてるじゃあーりませんか。

python ~/python/text_remove_crlf.py | trans {en=ja} -b

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

Python手遊び(CSVファイルから列名を取得)

この記事、何?

表題の通り。10分で書いたような記事。
CSVファイルの1行目をとってsplit(',')して返す。それだけ。

伏線・・・かも?

コード


def get_lines_from_textfile(filepath):
    with open(filepath, 'r', encoding='utf-8') as f:
        lines = f.readlines()
    return lines


def get_columns_list(csvpath):
    line_1st = get_lines_from_textfile(csvpath)[0]
    return line_1st.split(',')


def main():
    csvpath = 'solubility.test.csv'
    columns = get_columns_list(csvpath)
    for column in columns:
        print(column)


if __name__ == '__main__':
    main()

感想

特になし。

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

Python 実行時間計測メモ

いきなりの結論

結局は、perf_counter() が無難。
目新しいことはないので読む価値はないかもしれません。

想定読者

Pythonで実行速度を計測したがあるけれども、深くは考察していなかった方あたりでしょうか。

調べたきっかけ

実行時間を計測するためにうっかりtime.time()を使っていました。
精度等を気にする必要がないので十分だったのですが、独立しているはずの関数の実行順を変更したら実行時間が大きく変わりました。
それの原因は不明ですが、これをきっかけに時間計測に関して調べ直すことにしました。
(関数自体が実用に耐えないものだった原因究明はあきらめました。ガベージコレクションが影響しているのかも)

環境

Windows 10 Pro 64bit
Python 3.8.3

timeモジュール

標準ライブラリであるtimeモジュールのみ考えます

2点間の実行時間を計測するための関数

実行時間を計測するためにtimeモジュールで使える関数は以下になります。
time.get_clock_info()で調べた場合に、monotonic=Trueであるものです。
つまり、外部要因などで減算されないことが保証されているものです。

name purpose description sleep resolution
perf_counter() 実時間の計測 パフォーマンスカウンターの値 含める 高い
process_time() プロセスにおいて実際に稼働した時間の計測 現在のプロセスのシステムおよびユーザーの CPU 時間の合計値 含めない 高い(低い)
thread_time() スレッドにおいて実際に稼働した時間の計測 現在のスレッドのシステムおよびユーザーの CPU 時間の合計値 含めない 高い(低い)
monotonic() システム時刻の経過時間の計測 システムを起動した後の経過時間 含める 低い

分解能が"高い(低い)"とは何を書いているのだと思いますが、time.get_clock_info()で得られる仕様が高分解能となっているのに、実際に動かしてみると低分解能なのでこのように表記しています。
自分の開発環境の影響があるのかもしれません。

少し解説すると、以下のようになります。

perf_counter()は、プログラムが動いてから現実時間がどの程度経過したかを計測するのに使います。
スリープ時の経過時間も含めますので意図した時間が出ない可能性もあります。
ただし、一番安定しているようです。
timeitなどでデフォルトのtimerに設定されているなど、これを使うのが無難なようです。

process_time()またはthread_time()は、プロセス(スレッド)においてプログラムが確実に動いた時間を調べるのに使います。
自分を含めて多くの方がこの時間を計測したいのだと思います。
しかし、実際の分解能が仕様と違って低いようです。
本当はこれを使いたいのですが、使用をためらってしまいます。
また、Windowsの場合、実際はCのGetProcessTimes()を使っているのですが、それの説明には、

text
Note that this value can exceed the amount of real time elapsed (between lpCreationTime and lpExitTime) if the process executes across multiple CPU cores.

と書いてあります。
あまり信頼できるものではないようです。

monotonic()は、システムの稼働時間を調べるのに使います。
つまり、このPCは稼働してから何日経ったかとかを調べるのに使います。
あまり使う事はないと思います。

time()は、時刻を得るものであって実行時間計測には使わない。

ガーベジコレクションの影響

ガーベジコレクションは、プログラムの実行速度に影響を及ぼすようです。
下記で説明するtimeit()は、その影響を避けるためにデフォルトの動作としてガーベジコレクションを停止しています。
同じようにガーベジコレクションの影響を計測から除外したいのならばガーベジコレクションを停止する必要があります。
その方法は、以下のように記述することです。

python
import gc

gc.disable() # 停止
# 計測処理
gc.enable() # 再開

使い方

timeモジュールの使い方と言っても単純ですが、下記のように計測したい前後で時間を取りそれを減算すれば良いだけです。

python
import time
import timeit

def stopwatchPrint(func):

  @functools.wraps(func)
  def wrapper(*args, **kwargs):
    start = time.perf_counter()
    result = func(*args, **kwargs)
    end = time.perf_counter()
    print(f"{func.__name__}: {end - start:.3f} s.")
    return result

  return wrapper

@stopwatchPrint
def func1():
  # 何かの処理
  return

def func2():
  start = time.perf_counter()
  # 何かの処理
  end = time.perf_counter()
  print(f"実行時間: {end - start}"
  return

timeitモジュール

基本的には、上記のようにtimeを使えば良い問題ありません。
しかし、標準ライブラリには、実行速度を計測するためにtimeitも用意されています。
ほとんどtime.perf_counter()を呼んでいるだけですが、軽く解説してみます。
コマンドラインからの使い方は、触れません。
2つの関数を上げていますが、timeit.repeat()だけを使えば良いと思います。

python
import time
import timeit

timeit.timeit(stmt=func1, setup=func2, timer=time.time.perf_counter, number=1000000)
timeit.repeat(stmt=func1, setup=func2, timer=time.time.perf_counter, number=1000000, repeat=5)
  • timeit():
    • stmt: 実行時間を計測したい関数、もしくは式として評価できる文字列を渡す。文字列の場合は、eval()で評価される。
    • setup: 前処理を行う関数、もしくは式として評価できる文字列を渡す。これの実行時間は、計測値には含まれない。
    • timer: 計測に使うtimer関数を渡す。デフォルトは、time.time.perf_countertime.process_time等を渡しても良い。
    • number: 実行回数。デフォルトは、1000000
    • globals: 名前空間を指定する。デフォルトは、None。変更したい場合は、globals=globals()globals=locals()など指定する。
  • repeat(): timeitの繰り返し版。timeitの引数に加えて以下を持つ。
    • repeat: 繰り返し回数。 number=100, repeat=3ならば100回の実行を3回繰り返す。

使い方

実際には、以下のように使います。
resultは、1つの要素がlongLongLongCat()を100回の実行したときの経過時間で長さが3のリストです。

python
def longLongLongCat(): # 時間を計測したい関数
   pass

result = timeit.repeat(longLongLongCat, ,number=100, repeat=3)
print(result)

timeitの実行関数に引数を渡す

timeitの実行関数は、引数を渡すようになっていません。
文字列で実行する前提なのかもしれませんが、それを標準的に使うには抵抗があります。
そのため、色々と試してみました。
他に旨い方法があるのかもしれません。

python
import math
import timeit
import functools

def func():
  print("func")

def funcN(n):
  print(f"funcN: {n}")

class Test():
  def __init__(self, n=100, r=3):
    self.number = n
    self.repeat = r

  def glo(self):
    #print(globals())
    #print(locals())
    result = timeit.repeat("print(a, b)", number=self.number, repeat=self.repeat, globals=globals())
    print(result)

  def loc(self):
    a = 33
    b = 4
    #print(globals())
    #print(locals())
    result = timeit.repeat("print(a, b)", number=self.number, repeat=self.repeat, globals=locals())
    print(result)

  def mix(self):
    a = 33
    b = 44
    #print(globals())
    #print(locals())
    result = timeit.repeat("print(a , b)", number=self.number, repeat=self.repeat, globals={"a": 30, "b": 50})
    print(result)
    result = timeit.repeat("print(a , b)", number=self.number, repeat=self.repeat, globals={
        "a": globals()["a"],
        "b": locals()["b"]
    })
    print(result)

a = 2525
b = 2828
t = Test(1, 1)
t.glo()
t.loc()
t.mix()

timeit.repeat(func, number=1, repeat=1)
timeit.repeat(lambda: print(a, b), number=1, repeat=1)
n = 1129
timeit.repeat("funcN(n)", number=1, repeat=1, globals=globals())
timeit.repeat("funcN(n)", number=1, repeat=1, globals={"funcN": funcN, "n": 714})
g = globals()
g.update({"n": 1374})
timeit.repeat("funcN(n)", number=1, repeat=1, globals=g)
timeit.repeat(functools.partial(funcN, 184), number=1, repeat=1)

出力結果

shell
2525 2828
[0.001136100000000001]
33 4
[0.026095200000000013]
30 50
[0.01867479999999999]
2525 44
[0.001263299999999995]
func
2525 2828
funcN: 1129
funcN: 714
funcN: 1374
funcN: 184

クロックの情報

自分の環境での情報です。

shell
>>> time.get_clock_info("monotonic")
namespace(adjustable=False, implementation='GetTickCount64()', monotonic=True, resolution=0.015625)

>>> time.get_clock_info("perf_counter")
namespace(adjustable=False, implementation='QueryPerformanceCounter()', monotonic=True, resolution=1e-07)

>>> time.get_clock_info("process_time")
namespace(adjustable=False, implementation='GetProcessTimes()', monotonic=True, resolution=1e-07)

>>> time.get_clock_info("thread_time")
namespace(adjustable=False, implementation='GetThreadTimes()', monotonic=True, resolution=1e-07)

>>> time.get_clock_info("time")
namespace(adjustable=True, implementation='GetSystemTimeAsFileTime()', monotonic=False, resolution=0.015625)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python手遊び(RDKit記述子計算:Pandas使ってSDFからCSV)

この記事、何?

RDKitという化合物を理解して数字をいっぱい返してくれるライブラリを題材に、少し真面目にPythonを書いてみたという話。
まだ、あたしなりの共通関数とかクラス化の形を模索しているところで、先日に続いていろいろ制限事項が残っているけど、少し方向性が見えたかも。

つまり何するの?

SDFファイルという化合物情報を持つファイルからCSVファイルを作る。
RDKitというライブラリが200列の数字を作ってくれるので、それ以外に名称とか10列合わせて210列出力する。
ただし、汎用化を一部端折っているので、特定のファイル限定・・・って、ダメじゃん。
まあ、あとでバージョンアップするつもり。つもり。

制限事項

・SDFファイル内の化合物は以下のパラメータを持つこと。
['ID', 'NAME', 'SOL', 'SMILES', 'SOL_classification']

・変な化合物はNG。(分離とかイオンとか。ぶっちゃけると、RDKitが計算エラーを出さないこと)

コード

import pandas as pd
from rdkit import Chem
from rdkit.Chem import AllChem, Descriptors
from rdkit.ML.Descriptors import MoleculeDescriptors


def get_basevalues(sampleid, mol):
    tmps = list()
    tmps.append(('SampleID', sampleid))
    tmps.append(('SampleName', mol.GetProp('_Name')))
    tmps.append(('Structure', Chem.MolToMolBlock(mol)))
    tmps.append(('Atoms', len(mol.GetAtoms())))
    tmps.append(('Bonds', len(mol.GetBonds())))
    names = [tmp[0] for tmp in tmps]
    values = [tmp[1] for tmp in tmps]
    return names, values


def get_exvalues(sampleid, mol):
    names = ['ID', 'NAME', 'SOL', 'SMILES', 'SOL_classification']
    values = list()
    for name in names:
        values.append(mol.GetProp(name))
    return names, values


# SDFファイルから記述子を計算し、CSVを出力
# I : 化合物ファイルパス
#     CSVファイルパス
def ExportCSVFromSDF(sdfpath, csvpath):

    # 化合物を取得
    mols = Chem.SDMolSupplier(sdfpath)

    # RDKitの記述子計算の準備
    descLists = [desc_name[0] for desc_name in Descriptors._descList]
    desc_calc = MoleculeDescriptors.MolecularDescriptorCalculator(descLists)

    # 連番でIDを付与
    sampleids = list()
    # 化合物名称他
    values_base = list()
    # 外部パラメータ (現状:固定で5個)
    values_ex = list()

    # 各化合物の値を取得
    for i, mol in enumerate(mols, 1):
        sampleids.append(i)
        names_base, values = get_basevalues(i, mol)
        values_base.append(values)
        names_ex, values = get_exvalues(i, mol)
        values_ex.append(values)

    # RDKitの記述子を計算
    values_rdkit = [desc_calc.CalcDescriptors(mol) for mol in mols]

    # DataFrameへ変換
    df_base = pd.DataFrame(values_base, columns=names_base, index=sampleids)
    df_ex = pd.DataFrame(values_ex, columns=names_ex, index=sampleids)
    df_rdkit = pd.DataFrame(values_rdkit, columns=descLists, index=sampleids)

    # 全部結合
    df = pd.concat([df_base, df_ex, df_rdkit], axis=1)

    # 確認用にPrint()
    print(df)

    # CSVへ出力
    df.to_csv(csvpath, index=False)


def main():
    sdfpath = 'solubility.test.sdf'
    csvpath = 'solubility.test.csv'
    ExportCSVFromSDF(sdfpath, csvpath)


if __name__ == '__main__':
    main()


出力例

SampleID SampleName Structure Atoms Bonds ID NAME SOL SMILES SOL_classification MaxEStateIndex MinEStateIndex
1 3-methylpentane 6 5 5 3-methylpentane -3.68 CCC(C)CC (A) low 2.2777777777777777 0.9351851851851851
2 2,4-dimethylpentane 7 6 10 2,4-dimethylpentane -4.26 CC(C)CC(C)C (A) low 2.263888888888889 0.8749999999999998
3 ...
4

感想

うん。Pandas,ちょっとなれたかも。
で、これからいろいろ広げます。たぶん。。。

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

[Python]Seleniumを使用してcanvasで描かれた画像を保存する(ActionChains,PyAutoGUI,base64等)

スクレイピングで画像を取得する際、一般的なimgタグ形式ではなくcanvasで描かれているケースの画像保存で苦戦した。

qiita用_グラフ1.png

このような形で表示されている画像を保存したい。

画像はタイトルの通りcanvasで描かれています。

右クリックで保存を試す

ページを色々調査していたところ、右クリックで画像を保存出来るという事が分かった。

qiita用_グラフ2.png

なので、「画像を右クリック→保存」の流れをプログラムで実現する為に色々と試した。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains

# ChromeDriver設定
options = Options()
options.add_argument('--disable-gpu')
options.add_argument('--disable-extensions')
options.add_argument('--proxy-server="direct://"')
options.add_argument('--proxy-bypass-list=*')
options.add_argument('--start-maximized')
driver = webdriver.Chrome(options=options)
# 画面描画の待ち時間
wait = WebDriverWait(driver, 10)
driver.implicitly_wait(10)
# ページへアクセス
url = '対象ページのURL'
driver.get(url)
# ページが読み込まれるまで待機
wait.until(EC.presence_of_all_elements_located)
# 画像の場所
xpath = '保存したい画像のXPATH'
img = driver.find_element_by_xpath(xpath)
# 画像の場所へマウス移動してから右クリック
actions = ActionChains(driver)
actions.move_to_element(img).context_click(img).perform()
# 「↓」キーを5回入力
actions.send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).send_keys(Keys.ARROW_DOWN).perform()
# Enterキーを入力
actions.send_keys(Keys.ENTER).perform()

Seleniumで対象のページへアクセス

ActionChainsを使用して画像の場所へマウス移動+右クリックでコンテキストメニューを表示

キーボード入力でコンテキストメニューを操作(「↓」キー×5回+Enterキー押下)

という流れで画像を保存しようとしたが…ダメ。

右クリックまでは上手くいくがコンテキストメニューの操作が出来ない。

処理の動きを確認してみたところ、右クリック後にブラウザの画面が少し下へ動いている。

どうやらコンテキストメニューではなくブラウザに向かってキーボード入力してしまっているっぽい…。

なので、別を方法を試した。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.action_chains import ActionChains
import pyautogui

# ChromeDriver設定
options = Options()
options.add_argument('--disable-gpu')
options.add_argument('--disable-extensions')
options.add_argument('--proxy-server="direct://"')
options.add_argument('--proxy-bypass-list=*')
options.add_argument('--start-maximized')
driver = webdriver.Chrome(options=options)
# 画面描画の待ち時間
wait = WebDriverWait(driver, 10)
driver.implicitly_wait(10)
# ページへアクセス
url = '対象ページのURL'
driver.get(url)
# ページが読み込まれるまで待機
wait.until(EC.presence_of_all_elements_located)
# 画像の場所
xpath = '保存したい画像のXPATH'
img = driver.find_element_by_xpath(xpath)
# 画像の場所へマウス移動してから右クリック
actions = ActionChains(driver)
actions.move_to_element(img).context_click(img).perform()
# 画像を保存
pyautogui.typewrite('v')

Seleniumで対象のページへアクセス

ActionChainsを使用して画像の場所へマウス移動+右クリックでコンテキストメニューを表示

PyAutoGUIでキーボード入力(「v」を入力)

という流れに変えてみたところ、これが上手く動作した!

qiita用_グラフ保存.png

こんな形で見慣れた画像保存の結果が。

ヘッドレスだと動かない

これであとはChromeDriverをヘッドレスにしたりファイル名変更処理を書いて終わりだ!

と思っていたら…ヘッドレスにすると保存出来なくなった笑

どうやらPyAutoGUIはアクティブになっているウィンドウに対して操作する命令を送っているっぽい。
(GUIって書いてあるんだからそりゃそうだ笑)

なので、ヘッドレスでない状態で動かしても別の操作をしながら動かすと当然ながら保存に失敗する。

これは非常に不便なのでなんとしてもヘッドレスで保存したい…。

ヘッドレスでもいけた

色々調べた結果、ヘッドレスでもいけた。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import base64

# ChromeDriver設定
options = Options()
options.add_argument('--disable-gpu')
options.add_argument('--disable-extensions')
options.add_argument('--proxy-server="direct://"')
options.add_argument('--proxy-bypass-list=*')
options.add_argument('--start-maximized')
options.add_argument('--headless')
driver = webdriver.Chrome(options=options)
# 画面描画の待ち時間
wait = WebDriverWait(driver, 10)
driver.implicitly_wait(10)
# ページへアクセス
url = '対象ページのURL'
driver.get(url)
# ページが読み込まれるまで待機
wait.until(EC.presence_of_all_elements_located)
# 画像の場所
xpath = '保存したい画像のXPATH'
img = driver.find_element_by_xpath(xpath)
# canvasをBase64文字列で取得
canvas_base64 = driver.execute_script("return arguments[0].toDataURL('image/png').substring(21);", img)
# デコード
canvas_png = base64.b64decode(canvas_base64)
# ファイル名
file_name = '任意のファイル名.png'
# ファイル出力
with open(file_name, 'wb') as f:
    f.write(canvas_png)

canvasをBase64で文字列として取得→デコード→ファイル出力という流れで保存出来た!

最後に

ハマったおかげ(?)でActionChains,PyAutoGUI,base64についてまとめて学べた。
自動化楽しいですね。

参考リンク

How to perform right click using Selenium ChromeDriver?
PyAutoGuiで繰り返し作業をPythonにやらせよう
How to save a canvas as PNG in Selenium?

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

筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (17)

前回
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (16)
https://github.com/legacyworld/sklearn-basic

課題 8.7 主成分分析の例

Youtubeでの解説:第9回(1) 30分あたり
課題 8.3はCluster3をうまく再現できなかったので諦めた。

いつものアヤメデータを主成分分析する問題。
プログラムとしてはscikit-learnは簡単。グラフの部分だけpandasのscatter_matrixを使うのが今までと少し違うだけ。

Homework_8.7.py
# 課題 8.7 主成分分析の例
# Youtubeでの解説:第9回(1) 30  分あたり
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from sklearn.datasets import load_iris  
from sklearn.decomposition import PCA

iris = load_iris()
pca = PCA()
X = iris['data']
y = iris['target']
# 主成分分析
pca.fit(X)
transformed = pca.fit_transform(X)
# 寄与率
print(pca.explained_variance_ratio_)
# 描画
fig, ax = plt.subplots()
iris_dataframe = pd.DataFrame(transformed, columns=[0,1,2,3])
Axes = pd.plotting.scatter_matrix(iris_dataframe, c=y, figsize=(50, 50),ax=ax)
plt.savefig("8.7.png")

寄与率

[0.92461872 0.05306648 0.01710261 0.00521218]

グラフ
8.7.png

第一主成分の寄与率が0.92で、グラフを見ても左端(第一主成分)はクリアに分かれているのがわかる。
なのでsetosaの分類器は第一主成分だけで92.5%分類出来て、4成分にしてもさして上がらないので、第一主成分だけで良いということ。

過去の投稿

筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (1)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (2)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (3)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (4)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (5)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (6)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (7) 最急降下法を自作
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (8) 確率的最急降下法を自作
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (9)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (10)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (11)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (12)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (13)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (14)
筑波大学の機械学習講座:課題のPythonスクリプト部分を作りながらsklearnを勉強する (15)
https://github.com/legacyworld/sklearn-basic
https://ocw.tsukuba.ac.jp/course/systeminformation/machine_learning/

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

PythonAnywhereでFlaskアプリをLaunchする

はじめに

この記事は、自身で製作しWeb公開にまで至ったボートレース3連単予測サイト「きょう、ていの良い予想は当たるだろうか」の内部コード解説となります。今回はFlaskでつくったWebアプリケーションを実際にWeb Launchした流れをまとめます。

Flaskアプリの作成であったり、そのほかの情報はこちらをご覧ください。

PythonAnywhere、こんな方には向いているかも!

以下のような方は、PythonAnywhereの検討をする価値ありです!

  • Flaskで自作アプリをつくって、Localで動かせるようにはなったけども..この後、どうしたらいいんだろう?
  • サーバー借りるといっても、いきなりお金かけるのも嫌だし、そもそもサーバーの設定などわからないことだらけ。。
  • 自作Flaskを全世界の人に使ってみてもらいたい!

私自身、元々この方面はズブの素人だったため、どうしたもんか困っていましたが、PythonAnywhereで簡単に解決しました..!!

公開までの手順

"Start running.."を押して、"Free.."を選びます。後は流れに従ってアカウント登録。

image.png
image.png

  • Usernameがそのままドメイン名になるので、名前選びには気をつけましょう

image.png

  • アカウントができたらLoginし、Webタブから"Add a new web app"をクリック

image.png

  • その後は、流れに従って使用するWebフレームワークとして"Flask", Python環境は"Python 3.7"を指定しました。

※こちらはお使いのWebフレームワークやPythonのVersionに応じて選択ください。スナップショットを撮り忘れました..。。

  • Webの立ち上げが完了したら、mysite以下のフォルダ&ファイル構成をご自身のLocalの構成と同じにしていきます。

私は素人らしく、愚直に手で保存していきました。。gitとか使えるとだいぶスマートにできそうです。

image.png

  • できあがったらWebタブにて、Reloadしusername.pythonanywhere.comをクリック!!
    image.png

  • できあがり!

こんな簡単でいいんだ..!!
image.png

気をつけること

  • 黄色いボタンを押さないと、三ヶ月で失効する。

定期的に押しましょう。
image.png

  • Webタブの下の方にhttpsの設定があるの、これをEnableに変更しReloadするとhttps://...となります。

image.png

↓謎の感動
image.png

感想

これまでWebサーバー借りたことないし、仕組みがよくわからない、、といった人にはかなりオススメかもしれません!!
簡単にネット上で公開でき、世界が広がるような経験を得られるかもしれないです!
Localで眠っているFlaskアプリを是非、PythonAnywhereでLaunchしてみてはいかがですか?

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

HTML DJANGOのデータベース更新

このような形でvieww.pyからgoodの数をデータベースから引っ張ってきて数を+1させて表示させたいのですが、うまくできません。どこが間違っているのでしょうか、、。?よろしくお願いいたします。

def good(self, pk):
    smart = Smart.objects.get(pk=pk)
    smart.good = smart.good + 1
    smart.save()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[光-Hikari-のPython]08章-04 モジュール(外部ライブラリのインストール)

[Python]08章-04 外部ライブラリのインストール

前節では、Pythonをインストールすると自動でついてくる標準ライブラリのインポート方法と使用方法について述べました。
そこでも説明した通り、標準ではインストールされていないライブラリもあり、これを外部ライブラリと言いました。今回はこの外部ライブラリのインストール方法について説明していきたいと思います。

PyPI(Python Package Index)とpip

外部ライブラリは世界中のエンジニアが開発したライブラリです。それらのライブラリが以下のPyPIというリポジトリに登録されています。
https://pypi.org/

なお、PyPIは世界中のライブラリの情報が掲載されているため、自分が必要としているライブラリを検索するのは大変です。そこで、ライブラリ(パッケージ)をインストールする際に用いられるのがpip(パッケージ管理システム)です。

このpipにより、パッケージをインストールすることができますが、pipにはほかにも以下の機能があります。

  • 既にインストール済みのパッケージの一覧管理が可能
  • パッケージが別のパッケージに依存していたら、その依存パッケージも一緒にインストールできる
  • インストールできるパッケージの検索

この中で、パッケージの依存状況を確認しながらインストールというのがpipの重要な機能です。
例えば、AIのデータ解析で用いるパッケージにpandasというものがあるのですが、これをインストールすると、自動で行列の計算とかをしてくれるnumpyというパッケージもインストールされます。
このように、あるパッケージをインストールする際に必要なパッケージをインストールしてくれるのがpipの重要な機能となります。

pipというコマンドを利用してライブラリのインストールをします。今回は行わないですが、実際にはコマンドを入力することにより、PyCharmで以下の個所から、インストールできます。

PyCharmのPython Console近くにある、[Terminal]をクリック
image.png

以下の個所、
image.png
から以下のコマンドを打つことで、XXXXというライブラリをインストールできます。

(venv) C:\Users\***\Desktop\python>pip install XXXX

外部ライブラリのインストール方法

上記では、コマンドによって外部ライブラリをインストールする方法を紹介しました。他にも、PyCharmにおいてインストールする方法がありますので、その方法を説明します。今後はこちらの方法を使用します。実際にpandas(パンダス)というデータ解析用のパッケージをインストールしてみます。

[File]→[Settings]を選択する。
image.png

ここには現在インストールされているパッケージが記載されています。右側にある「+」ボタンを押下します。
image.png

インストールしたいパッケージ名を検索します。今回は以下の赤枠個所に「pandas」と入力し、検索されるので、それをクリックして、下のボタンの「Install Package」をクリックします。
image.png

インストールが終わると以下の赤枠の表記になるので、そうしたら×閉じをする。
image.png

「Pandas」がインストールされているのを確認する。なお、前述した通り「numpy(ナムパイ)」などのパッケージもインストールされているが、これはPandasと依存している関係上、一緒にインストールされているのを確認できます。
※numpyもAIプログラミングで行われるベクトルや行列の演算で非常によく用いられるパッケージです。
image.png

外部ライブラリのインポートと利用(matplotlibのインストール)

先ほど、pandasの例を用いてインストールを行いました。今度はmatplotlibのインストールを行ってみましょう。手順は先ほどのを参考にしてみてください。
image.png

今回使用する外部ライブラリは、このmatplotlibパッケージを利用します。
matplotlibはAIの分野で用いられる、データをグラフ化・可視化するためのライブラリです。これも先ほどのnumpyやpandas同様に、AIの分野では非常によく用いられます。

実際にプログラムを作成してみましょう。chap08の中に、samp08-04-01.pyというファイル名でファイルを作成し、以下のコードを書いてください。
今回記載するプログラムは、体重の変化を表したグラフを描いてみます。

samp08-04-01.py
import matplotlib.pyplot as plt

data = [79.2, 79.1, 78.7, 79.4, 78.2, 78.5, 78.1, 78.3, 78.0, 77.4, 77.1, 76.9, 76.5, 76.3, 75.2]


plt.plot(data)
plt.title('Change in weight')
plt.show()

【実行結果】
image.png

今回は、matplotlibを利用しているため、コンソール画面による出力でなく、グラフ形式で出力されます。

まず、importmatplotlibというパッケージの中にある、pyplotというモジュールをインポートしています。その名前を今回はpltとして別名を付けています。

リスト形式である体重データdataplt.plot(data)により描き、グラフのタイトルを「Change in weight」としています。
最後にplt.show()により、グラフを表示します。(show関数がないと、グラフが表示されません。)

なお、matplotlibはもっと細かな関数やモジュールがあるので、調べてみてください。matplotlibの詳細についてAI関連の書籍に記載があるので参照してみましょう。

最後に

今回は、numpy、pandas、matplotlibをインストールし、その中でmatplotlibの例を利用して、グラフを描いてみました。
このパッケージにもいろいろな関数やモジュールがあり、これらは調べながら作成していきます。以前どこかでお話ししたかもしれませんが、実務で書くプログラムは調べながらやるものです。
今回はmatplotlibというライブラリについても調べていろいろな処理を実行してみましょう。

【目次リンク】へ戻る

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

Pythonで 旗当番の日付を調べる

はじめに

皆さんは地域の安全活動として、子供の通学を補助する旗当番ってありませんか?
私の地域では、担当地域の当番人数と、次の渡す人しか知らされないです。なので、旗当番になったあと、次の旗が回ってくる日時がわからず、出勤予定が組めない問題がありました。

こちらをカウントして、いつ旗が回ってくるか調べるプログラムをpythonで作りました。

動作環境

python 3.7.7
(anaconda 環境)

pip install jpholiday にて jpholidayインストール

実行コード


import datetime 
import jpholiday

# ------- 初期設定 --------------
#旗当番やった日 
day = datetime.date(2020,6,29)
NumberOfPeople = 31 #人で回す
# ------- 初期設定ここまで ------

daycount = 0

for i in range(0,365):    

    #最初だけスキップ
    if i != 0:
        day  = day + datetime.timedelta(days =1)

    #土日スキップ 0:げつようび 6:日曜日
    if day.weekday() == 5 or day.weekday() == 6:
        continue
    #祝日スキップ
    if jpholiday.is_holiday(day):
        continue
    # ここに夏休みスキップを書く 8/8 -23 は夏休み
    if day >= datetime.date(2020,8,8) and day <= datetime.date(2020,8,23):
        continue

    #あまりをもとに指定の日を探す
    q,mod = divmod(daycount,NumberOfPeople)
    if mod == 0:
        #日付をプリント
        print(day)

    #当番カウントアップ
    daycount = daycount+1

実行結果

指定した日から、祝日、土日、夏休みを覗いた日をカウントした日数をprintし、
次の旗当番の日がわかる。

2020-06-29
2020-08-27
2020-10-13
2020-11-27
2021-01-13
2021-03-01
2021-04-13
2021-06-01

おわりに

だれがこんなコード使うんでしょうね?

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

ゼロから始めるLeetCode Day71 「1496. Path Crossing」

概要

海外ではエンジニアの面接においてコーディングテストというものが行われるらしく、多くの場合、特定の関数やクラスをお題に沿って実装するという物がメインである。

どうやら多くのエンジニアはその対策としてLeetCodeなるサイトで対策を行うようだ。

早い話が本場でも行われているようなコーディングテストに耐えうるようなアルゴリズム力を鍛えるサイトであり、海外のテックカンパニーでのキャリアを積みたい方にとっては避けては通れない道である。

と、仰々しく書いてみましたが、私は今のところそういった面接を受ける予定はありません。

ただ、ITエンジニアとして人並みのアルゴリズム力くらいは持っておいた方がいいだろうということで不定期に問題を解いてその時に考えたやり方をメモ的に書いていこうかと思います。

Leetcode

Python3で解いています。

ゼロから始めるLeetCode 目次

前回
ゼロから始めるLeetCode Day70 「295. Find Median from Data Stream」

今はTop 100 Liked QuestionsのMediumを優先的に解いています。
Easyは全て解いたので気になる方は目次の方へどうぞ。

Twitterやってます。

技術ブログ始めました!!
技術はLeetCode、Django、Nuxt、あたりについて書くと思います。こちらの方が更新は早いので、よければブクマよろしくお願いいたします!

問題

1496. Path Crossing

難易度はEasy。
直近で一番新しく追加されたEasyの問題です。

問題としては、path[i] = 'N', 'S', 'E', 'W' のような文字列のパスが与えられたとき,それぞれ1単位の移動を表す.2 次元平面上の原点 (0, 0) を起点とし、path で指定されたパス上を歩きます。
パスが任意の点で交差している場合、つまり以前に訪れたことのある場所にいる場合はTrueを返します。それ以外の場合は False を返します。

画像の関係で例を貼れないので各自確認をよろしくお願いいたします。

解法

x,yで座標を管理して最初の座標をdictで管理する、という方法を取りました。
うーん。
あまりスマートではないと思うのですが、pathをfor文で回して各文字列と一致するならば座標を変更するというものです。
しかしこれでも回答数が少ないせいかスピード自体は上位なんですよね・・・
何とも言えませんがとりあえず今回はこれで。

class Solution:
    def isPathCrossing(self, path: str) -> bool:
        x = y = 0
        isVisited = {(0,0):True}
        for i in path:
            if i == 'N':
                y += 1
            elif i == 'E':
                x += 1
            elif i == 'S':
                y -= 1
            else:
                x -= 1
            if isVisited.get((x,y)):
                return True
            isVisited[(x,y)] = True
        return False
# Runtime: 24 ms, faster than 96.92% of Python3 online submissions for Path Crossing.
# Memory Usage: 14 MB, less than 100.00% of Python3 online submissions for Path Crossing.

Javaとかだとswitch文とかで書くのがいいのでしょうか・・・?
今回は新しい問題という事でdiscussにも投稿してみました。
Simple Python Solution

緊張しますがこういうのやるともっと頭の良い人からアドバイスがもらえたりしていいですよね!

では今回はここまで。お疲れ様でした。

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

Pythonをctypesを使わずに1行でセグフォらせる

pythonを三行でセグフォらせるがすべての始まりで、これは知らなかったです。面白いです。
pythonを2行でセグフォらせるなど、ctypesを使うものも出ています。

それとは違うタイプの死に方を思いついたので投稿します。多分Linuxでしか動かないです。

まさに自殺
import os; os.kill(os.getpid(), 11)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【強化学習】DeepMind製Experience ReplayライブラリReverbの使い方調査【クライアント編】

英語で投稿したブログ記事の日本語焼き直し

1. はじめに

前回に引き続き、DeepMind製のExperience ReplayライブラリのReberbについて。
今回は、データの入出力操作を指示するクライアント側について、ソースコードを読みながら、READMEにか書かれていない部分まで調査した。

2. ClientTFClient

Reverbはサーバー・クライアントモデルを採用しているが、クライアント側のクラスとしては、reverb.Clientreverb.TFClientの2つがある。

Client が開発初期向けで、TFClientが実際の学習プログラムの中で利用するものという位置づけとのことである。大きな違いは、TFClientはその名のとおり、TensorFlowの計算グラフの中で利用することが意図されている。

両者のAPIや使い方は一貫しておらずややこしかったのが、今回整理記事を書こうと思ったモチベーションの1つである。

以下の記事では、サーバーとクライアントプログラムが、以下のように初期化されていることを前提とする。

import numpy as np
import tensorflow as tf
import reverb

table_name = "ReplayBuffer"
alpha = 0.8
buffer_size = 1000
batch_size = 32

server = reverb.Server(tables=[reverb.Table(name=table_name,
                                            sampler=reverb.selectors.Prioritized(alpha),
                                            remover=reverb.selectors.Fifo(),
                                            max_size=buffer_size,
                                            rate_limiter=reverb.rate_limiters.MinSize(1))])

client = reverb.Client(f"localhost:{server.port}")
tf_client = reverb.TFClient(f"localhost:{server.port}")


obs = np.zeros((4,))
act = np.ones((1,))
rew = np.ones((1,))
next_obs = np.zeros_like(obs)
done = np.zeros((1,))
priority = 1.0

dtypes = [tf.float64,tf.float64,tf.float64,tf.float64,tf.float64]
shapes = [4,1,1,4,1]

3. 遷移(transition)または軌道(trajectory)の保存

3.1 Client.insert

client.insert([obs,act,rew,next_obs,done],priorities={table_name: priority})

priorities引数が、 dict になっているのは、同じデータを複数のテーブル (リプレイ・バッファ) にそれぞれ異なるpriorityで同時に登録可能な仕様だからである。 (そんなニーズがあるなんて知らなかった)

たとえ、Prioritizedではない普通のリプレイ・バッファであっても、priorityを指定する必要がある。

Client.insert は呼び出されるたびに、データをサーバーに送信する

3.2 Client.writer

with client.writer(max_sequence_length=3) as writer:
    writer.append([obs,act,rew,next_obs,done])
    writer.create_item(table_name,num_timesteps=1,priority=priority)

    writer.append([obs,act,rew,next_obs,done])
    writer.append([obs,act,rew,next_obs,done])
    writer.append([obs,act,rew,next_obs,done])
    writer.create_item(table_name,num_timesteps=3,priority=priority) # 3ステップを1項目として登録。
# withブロックから出る際に、サーバーへ送信する

Client.writer メソッドが返す、 reverb.Writer をコンテキストマネージャーとして利用することで、
より柔軟に保存する内容を設定することができる。

例えば、上のサンプルコードの前半は、3.1と同じ内容を保存しているが、後半は3ステップをまとめて1つの項目として保存している。つまり、サンプルする際にも3つをまとめて1つとしてサンプルできる。例えば、エピソード毎サンプリングしたい際に利用することが想定できる。

Writer.flush() または、Writer.close() が呼ばれる際 (withブロックから抜ける際にも自動で呼ばれる) に、サーバーへデータが送信される。

3.3 TFClient.insert

tf_client.inser([tf.constant(obs),
                 tf.constant(act),
                 tf.constant(rew),
                 tf.constant(next_obs),
                 tf.constant(done)],
                 tablea=tf.constant([table_name]),
                 priorities=tf.constant([priority],dtype=tf.float64))

tables引数は、strのランク1のtf.Tensorで、prioritiesは、float64(明記しないとfloat32になる)のランク1のtf.Tensorであり、両者のshapeは一致しないといけない。

後で、サンプルするときのために、各データのtf.Tensorもランク1以上にしておくことがおそらく必要。

3.4 まとめ

サーバーへの送信 複数ステップを1項目に TF計算グラフ内での利用 データ
Client.insert 都度 X X 何でも良い
Client.writer Writer.close(), Writer.flush() (含 with の退出時) O X 何でも良い
TFClient.insert 都度 X O tf.Tensor

4. 遷移(transition)または軌道(trajectory)の読出し

いずれの手法もPrioritized Experience Replayの重み補正のβパラメータに対応していない。(そもそも重点サンプリングの重みを計算してくれない)

4.1 Client.sample

client.sample(table_name,num_samples=batch_size)

戻り値は、reverb.replay_sample.ReplaySamplegeneratorである。 ReplaySampleは名前付きTupleで、infodataを所持している。dataには、保存したデータが、infoにはkeyやpriorityなどの情報が含まれている。

4.2 TFClient.sample

tf_client.sample(tf.constant([table_name]),data_dtypes=dtypes)

この手法は、残念ながらバッチサンプリングに対応していない。戻り値は、ReplaySampleになる。

4.3 TFClient.dataset

tf_client.dataset(tf.constant([table_name]),dtypes=dtypes,shapes=shapes)

他の手法とは全く異なる方式を採用しており、大規模な本番学習で主にはこの手法を採用することが意図されていると思われる。

この関数の戻り値は、tf.data.Datasetを継承したreverb.ReplayDatasetである。このReplayDatasetgeneratorのようにReplaySampleを引き出すことができ、適切なタイミングでサーバーからデータを自動でフェッチしてきてくれる。つまり、毎回 sampleするのではなく、一度 ReplayDatasetを設定するとあとは、自動で保存されたデータを排出し続けてくれる仕組みになっている。

shapes は、0を要素に指定するとエラーを発生させるので、保存するデータはランク1以上にしておくことが必要であると思われる。

その他パフォーマンス調整のための各種パラメータはここに書くには細かいので、ソースコードのコメントを確認してほしい。

4.4 まとめ

バッチ出力 戻り値型 型の指定 形状の指定
Client.sample O replay_sample.ReplaySamplegenerator 不要 不要
TFClient.sample X replay_sample.ReplaySample 必要 不要
TFClient.dataset O (内部で自動で実施) ReplayDataset 必要 必要

5. 重要度 (priority) の更新

他のリプレイ・バッファの実装と異なり、要素を指定するIDは0始まりの連番ではなく、一見ランダムに見えるハッシュである。ReplaySample.info.keyでアクセス可能である。
(書きにくいので、サンプルコードを一部省略した書き方にします。すみません。)

5.1 Client.mutate_priorities

client.mutate_priorities(table_name,updates={key:new_priority},deletes=[...])

こちらは、更新だけでなく、削除も可能である。

5.2 TFClient.update_priorities

tf_client.update_priorities(tf.constant([table_name]),
                            keys=tf.constant([...]),
                            priorities=tf.constant([...],dtype=tf.float64))

6. 性能比較

せっかくなので、拙作のcpprbも含めてベンチマークをとった。

注意: 強化学習には、深層学習の学習や環境の更新など他にも重たい処理があるので、リプレイ・バッファの速度だけで決まるものではない。(一方、リプレイ・バッファの実装と条件次第では、リプレイ・バッファの処理時間と深層学習の処理時間が同じぐらいになることもあるらしい。)

以下のDockerfileによる環境で、ベンチマークを実行した。

Dockerfile
FROM python:3.7

RUN apt update \
    && apt install -y --no-install-recommends libopenmpi-dev zlib1g-dev \
    && apt clean \
    && rm -rf /var/lib/apt/lists/* \
    && pip install tf-nightly==2.3.0.dev20200604 dm-reverb-nightly perfplot


# Reverb requires development version TensorFlow

CMD ["bash"]

(cpprbのレポジトリのCI上で、実施しているので、追加でcpprbもインストールされている。 pip install cpprb とほぼ同義)

そして、以下のベンチマークスクリプトを実行して実行時間のグラフを描いた。

benchmark.py
import gc
import itertools


import numpy as np
import perfplot
import tensorflow as tf

# DeepMind/Reverb: https://github.com/deepmind/reverb
import reverb

from cpprb import (ReplayBuffer as RB,
                   PrioritizedReplayBuffer as PRB)


# Configulation
buffer_size = 2**12

obs_shape = 15
act_shape = 3

alpha = 0.4
beta  = 0.4

env_dict = {"obs": {"shape": obs_shape},
            "act": {"shape": act_shape},
            "next_obs": {"shape": obs_shape},
            "rew": {},
            "done": {}}


# Initialize Replay Buffer
rb  =  RB(buffer_size,env_dict)


# Initialize Prioritized Replay Buffer
prb  =  PRB(buffer_size,env_dict,alpha=alpha)


# Initalize Reverb Server
server = reverb.Server(tables =[
    reverb.Table(name='ReplayBuffer',
                 sampler=reverb.selectors.Uniform(),
                 remover=reverb.selectors.Fifo(),
                 max_size=buffer_size,
                 rate_limiter=reverb.rate_limiters.MinSize(1)),
    reverb.Table(name='PrioritizedReplayBuffer',
                 sampler=reverb.selectors.Prioritized(alpha),
                 remover=reverb.selectors.Fifo(),
                 max_size=buffer_size,
                 rate_limiter=reverb.rate_limiters.MinSize(1))
])

client = reverb.Client(f"localhost:{server.port}")
tf_client = reverb.TFClient(f"localhost:{server.port}")


# Helper Function
def env(n):
    e = {"obs": np.ones((n,obs_shape)),
         "act": np.zeros((n,act_shape)),
         "next_obs": np.ones((n,obs_shape)),
         "rew": np.zeros(n),
         "done": np.zeros(n)}
    return e

def add_client(_rb,table):
    """ Add for Reverb Client
    """
    def add(e):
        n = e["obs"].shape[0]
        with _rb.writer(max_sequence_length=1) as _w:
            for i in range(n):
                _w.append([e["obs"][i],
                           e["act"][i],
                           e["rew"][i],
                           e["next_obs"][i],
                           e["done"][i]])
                _w.create_item(table,1,1.0)
    return add

def add_client_insert(_rb,table):
    """ Add for Reverb Client
    """
    def add(e):
        n = e["obs"].shape[0]
        for i in range(n):
            _rb.insert([e["obs"][i],
                        e["act"][i],
                        e["rew"][i],
                        e["next_obs"][i],
                        e["done"][i]],priorities={table: 1.0})
    return add

def add_tf_client(_rb,table):
    """ Add for Reverb TFClient
    """
    def add(e):
        n = e["obs"].shape[0]
        for i in range(n):
            _rb.insert([tf.constant(e["obs"][i]),
                        tf.constant(e["act"][i]),
                        tf.constant(e["rew"][i]),
                        tf.constant(e["next_obs"][i]),
                        tf.constant(e["done"])],
                       tf.constant([table]),
                       tf.constant([1.0],dtype=tf.float64))
    return add

def sample_client(_rb,table):
    """ Sample from Reverb Client
    """
    def sample(n):
        return [i for i in _rb.sample(table,num_samples=n)]

    return sample

def sample_tf_client(_rb,table):
    """ Sample from Reverb TFClient
    """
    def sample(n):
        return [_rb.sample(table,
                           [tf.float64,tf.float64,tf.float64,tf.float64,tf.float64])
                for _ in range(n)]

    return sample

def sample_tf_client_dataset(_rb,table):
    """ Sample from Reverb TFClient using dataset
    """
    def sample(n):
        dataset=_rb.dataset(table,
                            [tf.float64,tf.float64,tf.float64,tf.float64,tf.float64],
                            [4,1,1,4,1])
        return itertools.islice(dataset,n)
    return sample


# ReplayBuffer.add
perfplot.save(filename="ReplayBuffer_add2.png",
              setup = env,
              time_unit="ms",
              kernels = [add_client_insert(client,"ReplayBuffer"),
                         add_client(client,"ReplayBuffer"),
                         add_tf_client(tf_client,"ReplayBuffer"),
                         lambda e: rb.add(**e)],
              labels = ["DeepMind/Reverb: Client.insert",
                        "DeepMind/Reverb: Client.writer",
                        "DeepMind/Reverb: TFClient.insert",
                        "cpprb"],
              n_range = [n for n in range(1,102,10)],
              xlabel = "Step size added at once",
              title = "Replay Buffer Add Speed",
              logx = False,
              logy = False,
              equality_check = None)


# Fill Buffers
for _ in range(buffer_size):
    o = np.random.rand(obs_shape) # [0,1)
    a = np.random.rand(act_shape)
    r = np.random.rand(1)
    d = np.random.randint(2) # [0,2) == 0 or 1
    client.insert([o,a,r,o,d],priorities={"ReplayBuffer": 1.0})
    rb.add(obs=o,act=a,rew=r,next_obs=o,done=d)


# ReplayBuffer.sample
perfplot.save(filename="ReplayBuffer_sample2.png",
              setup = lambda n: n,
              time_unit="ms",
              kernels = [sample_client(client,"ReplayBuffer"),
                         sample_tf_client(tf_client,"ReplayBuffer"),
                         sample_tf_client_dataset(tf_client,"ReplayBuffer"),
                         rb.sample],
              labels = ["DeepMind/Reverb: Client.sample",
                        "DeepMind/Reverb: TFClient.sample",
                        "DeepMind/Reverb: TFClient.dataset",
                        "cpprb"],
              n_range = [2**n for n in range(1,8)],
              xlabel = "Batch size",
              title = "Replay Buffer Sample Speed",
              logx = False,
              logy = False,
              equality_check=None)


# PrioritizedReplayBuffer.add
perfplot.save(filename="PrioritizedReplayBuffer_add2.png",
              time_unit="ms",
              setup = env,
              kernels = [add_client_insert(client,"PrioritizedReplayBuffer"),
                         add_client(client,"PrioritizedReplayBuffer"),
                         add_tf_client(tf_client,"PrioritizedReplayBuffer"),
                         lambda e: prb.add(**e)],
              labels = ["DeepMind/Reverb: Client.insert",
                        "DeepMind/Reverb: Client.writer",
                        "DeepMind/Reverb: TFClient.insert",
                        "cpprb"],
              n_range = [n for n in range(1,102,10)],
              xlabel = "Step size added at once",
              title = "Prioritized Replay Buffer Add Speed",
              logx = False,
              logy = False,
              equality_check=None)


# Fill Buffers
for _ in range(buffer_size):
    o = np.random.rand(obs_shape) # [0,1)
    a = np.random.rand(act_shape)
    r = np.random.rand(1)
    d = np.random.randint(2) # [0,2) == 0 or 1
    p = np.random.rand(1)

    client.insert([o,a,r,o,d],priorities={"PrioritizedReplayBuffer": p})

    prb.add(obs=o,act=a,rew=r,next_obs=o,done=d,priority=p)


perfplot.save(filename="PrioritizedReplayBuffer_sample2.png",
              time_unit="ms",
              setup = lambda n: n,
              kernels = [sample_client(client,"PrioritizedReplayBuffer"),
                         sample_tf_client(tf_client,"PrioritizedReplayBuffer"),
                         sample_tf_client_dataset(tf_client,"PrioritizedReplayBuffer"),
                         lambda n: prb.sample(n,beta=beta)],
              labels = ["DeepMind/Reverb: Client.sample",
                        "DeepMind/Reverb: TFClient.sample",
                        "DeepMind/Reverb: TFClient.dataset",
                        "cpprb"],
              n_range = [2**n for n in range(1,9)],
              xlabel = "Batch size",
              title = "Prioritized Replay Buffer Sample Speed",
              logx=False,
              logy=False,
              equality_check=None)

結果は、以下の様になった。
(結果が古くなっているかもしれないので、最新版はcpprbのプロジェクトサイトで確認できる。)

ReplayBuffer_add2.png

ReplayBuffer_sample2.png

PrioritizedReplayBuffer_add2.png

PrioritizedReplayBuffer_sample2.png

7. おわりに

DeepMind製Experience ReplayフレームワークのReverbのクライアントの使い方を調査し整理した。
OpenAI/Baselinesに代表されるような他のリプレイ・バッファの実装と比べると、APIや利用方法が異なる部分も多くわかりにくいと感じた。
(安定版の公開時までに、もう少しわかりやすくなっていると良いですね。)

少なくとも、大規模分散学習を行ったり、強化学習のすべてをTensorFlowの計算グラフ内で完結させたりということをしなければ、性能面でも優れているわけではなさそうであった。

もちろん、大規模分散学習や計算グラフ内での強化学習はパフォーマンスを大幅に向上させる可能性があるので、引き続き検討を続ける必要があると思っている。

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

matplotlibにおけるデザインとデータの分離

概要

課題

私はPythonからmatplotlibを使用してグラフを描くことが多いです。
Pythonアプリケーションでデータを生成、整形、matplotlibによるグラフ出力、のような流れで行います。

このとき、以下のような課題を抱えていました。

  • グラフのデザイン統一されていない。
  • 同じような記述をそれぞれのアプリケーションで行っている。
  • デザインコード(Pythonファイルの記述)視覚的言語的に結びついていない。

原因

データを可視化するために記述するコードと、デザインを管理するために記述するコードが
1つのアプリケーション内に書かれているからだと考えました。

対応

デザインを管理するためのコードを設定ファイルとして分離してみました。

詳細

課題として記載した3つのうち、グラフのデザインが統一されていないというのは、
縦軸や横軸に割り当てたラベルの大きさ凡例プロットされる点等が統一されていないということです。

これは2番目の課題とも関連するのですが、同じような記述を別のPythonファイルで行っているからでした。

統一されておらず、その場その場で過去のPythonファイルを参照して、コピペをしていたことが問題でした。

どうしてコピペを繰り返してしまうのか、それは私が求めているデザインコード
結びついていない、少なくとも直感的ではない、からだと思います。これは3つ目の課題に繋がります。

これら3つの課題の原因は、データデザイン分離されていないからだと、私は考えました。

そうであるのならば、話は単純でデザイン設定ファイルへ分離すればよいのです。

デザインを設定ファイルに言葉で表現

以下の表のようにデザインと言葉を対応させてみました。

各項目の意味は、

  • デザインの分類
    • 設定ファイルのパラメータ
      • パラメータの意味
      • 対応するmatplotlibのコード

です。

  • Size(大きさを設定する)

    • figure_x
      • 図の横方向の大きさ
      • pyplot.figure(figsize=(figure_x, figure_y))
    • figure_y
      • 図の縦方向の大きさ
      • figure_xと同じ
    • font_title
      • タイトルのフォントの大きさ
      • pyplot.title(fontsize=font_title)
    • font_x_label
      • X軸のフォントの大きさ
      • pyplot.figure.add_subplot().ax.set_xlabel(fontsize=font_x_label)
    • font_y_label
      • Y軸のフォントの大きさ
      • pyplot.figure.add_subplot().ax.set_ylabel(fontsize=font_y_label)
    • font_tick
      • 軸のメモリのフォントの大きさ
      • pyplot.tick_params(labelsize=font_tick)
    • font_legend
      • 凡例のフォントの大きさ
      • pyplot.legend(fontsize=font_legend)
    • marker
      • データをプロットする際のマーカーの大きさ
      • pyplot.figure.add_subplot().ax.plot(markersize=marker)
  • Position(位置を設定する)

    • subplot
      • グラフを配置する場所
      • pyplot.figure.add_subplot(subplot)
    • legend_location
      • 凡例を配置するグラフ内の場所
      • pyplot.legend(loc=legend_location)

具体的な設定ファイルの表現方法

設定ファイルはいくつかの表現方法、jsonやyaml等を検討しました。

結果、Pythonに標準であるconfigparserを使うことにしました。

jsonやyamlでもよいとは思うのですが、階層的に表現できるよりも、直感的に使いやすいほうがいいと考えました。

configparserで設定ファイルを表現すると次のようになります。

config.ini
[Size]
figure_x=8
figure_y=8
font_title=20
font_x_label=18
font_y_label=18
font_tick=10
font_legend=15
marker=10

[Position]
subplot=111
legend_location=upper right

[Markers]
0=D
1=>
2=.
3=+
4=|

[Color]
0=red
1=blue
2=green
3=black
4=yellow

デザインの分類に当たる項目は[Size]のように角括弧で囲います。これはセクションと呼ばれます。
その下に設定ファイルのパラメータ=値を書いていきます。これはキーと呼ばれます。

上記の設定ファイルにはSizePositionの他に、Markers(プロットするマーカーの種類)Color(プロットされるマーカーや線の色)も表現されています。

設定ファイルに記載されたパラメータをPythonコードから使用

設定ファイルのパラメータへPythonアプリケーションがアクセスするには、次のようにコードを記述します。

import configparser


rule_file = configparser.ConfigParser()
rule_file.read("configファイルのパス", "UTF-8")

hogehoge = rule_file["セクション名"]["キー名"]

注意点として、読み込んだ値は文字列になります。

実際の使用例

以下のコードは、渡されたデータデザインの設定ファイルをもとに折れ線グラフを作成します。

make_line_graph.py
""" 折れ線グラフ作成関数

渡されたデータを使用して折れ線グラフを描画し、画像データとして保存する。

"""


import configparser
import matplotlib.pyplot as plt


def make_line_graph(data, config="config.ini"):
    """折れ線グラフ描画

    渡されたデータを使用して折れ線グラフを作成する。
    デザインは別のconfigファイルから読み取る。

    Args:
        data(dict): プロットするデータが格納されている
        config(str): configファイルの名前

    Returns:
        bool: Trueなら作成完了、Falseなら作成失敗

    Note:
        引数のdataに含めるべきkeyとvalueについて以下に記載する。

        key         : value
        ------------------------
        title(str): グラフのタイトル名
        label(list): 凡例の説明
        x_data(list): x軸のデータ
        y_data(list): y軸のデータ
        x_ticks(list): x軸のメモリに表示する値
        y_ticks(list): y軸のメモリに表示する値
        x_label(str): x軸の名前
        y_label(str): y軸の名前
        save_dir(str): 保存ファイルパス
        save_name(str): 保存ファイル名
        file_type(str): 保存ファイル形式
    """

    rule_file = configparser.ConfigParser()
    rule_file.read("./conf/{0}".format(config), "UTF-8")

    fig = plt.figure(figsize=(int(rule_file["Size"]["figure_x"]), int(rule_file["Size"]["figure_y"])))

    ax = fig.add_subplot(int(rule_file["Position"]["subplot"]))
    ax.set_xlabel(data["x_label"], fontsize=int(rule_file["Size"]["font_x_label"]))
    ax.set_ylabel(data["y_label"], fontsize=int(rule_file["Size"]["font_y_label"]))

    for index in range(len(data["x_data"])):
        ax.plot(data["x_data"][index],
                data["y_data"][index],
                label=data["label"][index],
                color=rule_file["Color"][str(index)],
                marker=rule_file["Markers"][str(index)],
                markersize=int(rule_file["Size"]["marker"]))

    plt.title(data["title"], fontsize=int(rule_file["Size"]["font_title"]))

    if "x_ticks" in data.keys():
        plt.xticks(data["x_ticks"][0], data["x_ticks"][1])

    if "y_ticks" in data.keys():
        plt.yticks(data["y_ticks"][0], data["y_ticks"][1])

    plt.tick_params(labelsize=int(rule_file["Size"]["font_tick"]))
    plt.legend(fontsize=rule_file["Size"]["font_legend"], loc=rule_file["Position"]["legend_location"])
    plt.savefig("".join([data["save_dir"], "/", data["save_name"], ".", data["file_type"]]))

データを渡すPythonファイルは以下のような感じになります。

main.py
from make_line_graph import make_line_graph

data = {
    "title": "hogehoge",
    "label": ["A", "B"],
    "x_data": [x_data1, x_data2],
    "y_data": [y_data1, y_data2],
    "x_ticks": [x_ticks1, x_ticks2],
    "y_ticks": [y_ticks1, y_ticks2],
    "x_label": "hogehoge",
    "y_label": "hogehoge",
    "save_dir": "保存したいフォルダのパス",
    "save_name": "保存したいファイル名",
    "file_type": "拡張子",
}

make_line_graph(data, config="config.ini")

使ってみた感想

良かった点

デザインが変えやすくなりました。
特に、各種フォントサイズはプロットするデータやラベルに入れる文字数によって変わります。

また設定ファイルを複製してカスタマイズすることで、グラフデザインを変えたくなったときのPythonファイル変更量が少なくなりました。
読み込む設定ファイル名だけ変えればよし。

グラフデザインと設定ファイルが結びついているので、どのデザインがどのコードと対応しているのか忘れても大丈夫です。

悪かった点

どこまで汎用性をもたせるかが難しいです。

作ったmake_line_graph.pyは折れ線グラフ作成関数ですが、似たようなPythonファイルが増えるとよろしくないのでできる限り汎用的にしました。
ただこれではうまくグラフが描画出来ず、それに対応するために別の折れ線グラフ作成関数が乱立すると、振り出しに戻ってしまいそうです・・・。

汎用性を考えればきりがないのかなとも思ったりしてますが・・・。

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

複数のExcelファイル内における特定シート内特定列のデータを一括抽出し、各列内データを1行に納めるには

背景

複数のExcelファイルの特定シート内の特定列のデータを一括抽出し、それぞれ1行に納めたい際に、pythonを用いて、さくっと処理することができたので、備忘録として、整理することにしました。

1.複数のExcelファイルを読み込むには

  • 今回は、前提として、約150個のファイルを同一フォルダに格納しています。
  • 最初に、フォルダ内のファイル一覧を取得します。
import glob
files=glob.glob(r'/レビュー結果分析用/*.xlsx')
  • 次に、pandas.read_excel()を使用して、ファイル一覧から一つづつファイル名を取得して、Excelファイルの特定シートを読み込みます。
  • さらに、valuesを用いて、特定シート内の特定列のデータを抽出します。例では、特定列は10項目(左から)になります。
import pandas as pd
for file in files:
    df = pd.read_excel(file, sheet_name='指摘事項一覧')
    for row in df.values:
      # 10列目のデータを抽出
      s_data = str(row[9]).strip().rstrip()

2.後処理

  • 取得したデータを1行にまとめたい場合

    • 取得した列データ内を1行に納めたい場合には、データ内の改行コードを取り除きます。これは、データ分析においては、よくあるケースと思われますので、載せておきます。 print(s_data.replace('\n',''))
  • 日付データが含まれる場合

    • pythonでExcelファイルを読み込むと、Excelファイル上で表示されていた日付が、41496 などの 5桁の数値に変換されてしまう、といった事象に出会すことがあるかと思います。これは、Excelでの日付は 1900年1月1日を起点 (1 日目) として、そこから日数を加算した数値で日付データを保持している、というのが原因のようです。(関連記事2参照)。
    • そこで、以下のような関数を作成して、変換する必要がありますので、実際に動いたコードを記載しておきます。
def excel_date(num):
    from datetime import datetime, timedelta
    return(datetime(1899, 12, 30) + timedelta(days=num))

print(pd.to_datetime(excel_date(row[11]), format='%Y年%m月%d日'))

以上になります。(もっと簡単な方法があるよ、というのがあれば、ぜひ、コメントください)

関連記事

  1. Pythonでフォルダ内のファイルリストを取得する
  2. エクセル日付の「数値」を python の datetime に変換する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python で タイムスタンプ局を操作するには

Python+OpenSSLを使うには?

  • pyOpensslが一番ハードルが低い。
    • CA局も作ろうと思えば作ることができる。
  • import ssl
    • 比較的低レイヤーのOpenSSLの機能を触ることができる。

今回やろうとしていることは、タイムスタンプ局

事前情報として

  • CA局とは別物?
  • OpenSSLでいうと、version 1.1.0あたりから本流に組み込まれた「openssl ts」コマンド体系でできることをしたい。

  • pyOpensslを調べるも、それらしい機能が見当たらない。

    • pyOpensslが依存関係にあるcryptography.hazmatにもなさそう。
    • 例えば、OpenSSLのmain_ts関数に含まれている TS_REQ_new()関数辺りが使えるようになっていない。
    • そもそも、pyOpensslはcryptographyに依存しており、cryptographyはopensslのversion 1.0.2までのように見える?

結局Pythonからシェル経由で駆動させるしかないのか?

$ openssl ts -query ・・・ 
$ openssl ts -reply ・・・
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

競プロで役立ちそうな自分の記事一覧(随時更新)

競プロで役立ちそうな自分の記事をシリーズごとに分けて一覧にしました。
この他に解いた問題の解説記事も投稿しているので参考にしていただければと思います。
また、書いて欲しい記事がある場合はコメント等で教えていただければ幸いです。

競プロの基本事項確認シリーズ

競プロの基本事項確認~累積和といもす法~

競プロの基本事項確認~順列生成の方法~

競プロの基本事項確認~二分探索~

競プロの基本事項確認~gcdとlcm~

競プロの応用事項確認シリーズ

競プロの応用事項確認~(拡張)ダイクストラ法~

競プロの応用事項確認~包除原理~ 作成中

競プロの応用事項確認~ベルマンフォード法~ 作成中

競プロの応用事項確認~最小全域木~ 作成中

競プロのライブラリ整理シリーズ

競プロのライブラリ整理~エラトステネスの篩~

競プロのライブラリ整理~Union-Find木~

競プロのライブラリ整理~逆元による二項係数の計算~

競プロのライブラリ整理~約数の列挙~

競プロのライブラリ整理~素因数分解~

競プロのライブラリ整理~BIT(Binary Indexed Tree)~

競プロのライブラリ整理~modint~

その他

競プロ用テンプレート(C++)

Pythonで競プロするのに必要な機能をまとめてみた~itertools~

二分探索と三分探索の数学的な解説とバグらせない実装方法

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

matplotlibにおけるdisplay errorの抑制方法

githubなどで公開されているコードでディスプレイがある前提のコードのとき、display errorが出て動かない!といった時の対処法についてメモ書き。

いずれの方法もmatplotlibがバックグラウンド処理になり、エラーを抑制できます。

コード内で抑制しよう

pythonのコード内で以下のように記載する。

import matplotlib
matplotlib.use("Agg")

ユーザ単位で抑制しよう

ファイル名:matplotlibrc
というテキストファイル(拡張子なし)を作成し、以下の文字列をファイルに記載する。
backend:Agg

このファイルをローカルの以下に保存する(ない場合は作成する)
Linuxの場合
home/usename/.matplotlib

終わりに

CUIしか使えない環境だったので、回避策を数年前に調査。
後者の方法は設定を忘れて事故が起きることがあるので、前者の方法を推奨。
plt.show()に頼るのはやめよう。やめてほしい。

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

弱参照を使う

Pythonでなんらかのクラスのコレクションのようなものを作るとき、親になっているオブジェクトの参照を持っておきたい ということはあると思います。

そんなとき、他の言語などだとときどき、ownerという変数を作って親オブジェクトの参照を持っておく などと言うことがあるのですが、そのまま実装すると循環参照となり、取得したメモリが意図したタイミングで解放されなくなることがあります。

循環参照が起こりうるコード
class ParentClass:
  def __init__(self):
    self.child = []

class ChildClass:
  def __init__(self, owner):
    self._owner = owner

p = ParentClass()
p.child.append(ChildClass(p))
# ... 

こんなときのために、オブジェクトを参照する際に参照カウンタを増やさない、弱参照(weakrefモジュール)というものがあります。

弱参照を使ったコード
import weakref

class ParentClass:
  def __init__(self):
    self.child = []

class ChildClass:
  def __init__(self, owner):
    self._owner = weakref.ref(owner)

p = ParentClass()
p.child.append(ChildClass(p))
# ... 

なお、このweakrefオブジェクトで作った参照を辿りたいときは、メソッドのように()をつけて呼び出します。

弱参照を使ったコード
import weakref

class ParentClass:
  def __init__(self):
    self.child = []

  def message(self):
    print("called")

class ChildClass:
  def __init__(self, owner):
    self._owner = weakref.ref(owner)

  def message(self): 
    self._owner().message()

p = ParentClass()
p.child.append(ChildClass(p))
p.child[0].message()
# ... 
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pythonのpandasでcsv読み込む

pythonでcsvファイルを読み込む

・python
・sqlite3
・pandas

import sqlite3
import pandas as pd

df = pd.read_csv('{csvファイルのpath}', header=None)
db_name = 'sample.db'  #dbの作成
conn = sqlite3.connect(db_name)  #dbへ接続
df.to_sql('class_table', conn, if_exists='replace')  #csvを取り込んだ情報をsqlへ変換
c = conn.cursor()
query = 'SELECT * FROM class_table'
result = c.execute(query)
c.fetchall()  #全件取得

for row in result:  #1件ずつ取得
  print(row)

for vert in range(2, 10):  #カラムを順に取得
  vert += 1
  for side in range(0, 4):
    print(df.loc[vert, side])
    side += 1

conn.close()  #db切断
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Optunaを使ったRandomforestの設定方法

Optunaを使ったRandomforestの設定方法

前回Optunaの使い方を書いたので、これからは個別の設定方法について記載しようと思う。。
Randomforestで渡せる引数は、いろいろあるが、主なものをすべてOptunaで設定してみた。
max_depth,n_estimators を整数で渡すべきか、数をカテゴリーとして渡すべきか、悩んだが、今回は整数で渡した。

def objective(trial):
    criterion = trial.suggest_categorical('criterion', ['mse', 'mae'])
    bootstrap = trial.suggest_categorical('bootstrap',['True','False'])
    max_depth = trial.suggest_int('max_depth', 1, 10000)
    max_features = trial.suggest_categorical('max_features', ['auto', 'sqrt','log2'])
    max_leaf_nodes = trial.suggest_int('max_leaf_nodes', 1, 10000)
    n_estimators =  trial.suggest_int('n_estimators', 30, 1000)

    regr = RandomForestRegressor(bootstrap = bootstrap, criterion = criterion,
                                 max_depth = max_depth, max_features = max_features,
                                 max_leaf_nodes = max_leaf_nodes,n_estimators = n_estimators,n_jobs=2)


    #regr.fit(X_train, y_train)
    #y_pred = regr.predict(X_val)
    #return r2_score(y_val, y_pred)

    score = cross_val_score(regr, X_train, y_train, cv=5, scoring="r2")
    r2_mean = score.mean()

    return r2_mean
# optunaを実施して、ハイパーパラメーターを設定する
study = optuna.create_study(direction='maximize')
study.optimize(objective, n_trials=1000)

# チューニングしたハイパーパラメーターを使ったインスタンスを作成する
optimised_rf = RandomForestRegressor(bootstrap = study.best_params['bootstrap'], criterion = study.best_params['criterion'],
                                     max_depth = study.best_params['max_depth'], max_features = study.best_params['max_features'],
                                     max_leaf_nodes = study.best_params['max_leaf_nodes'],n_estimators = study.best_params['n_estimators'],
                                     n_jobs=2)
#学習する
optimised_rf.fit(X_train ,y_train)

これを使って、Bostonのデータセットを使って、ハイパーパラメーターをチューニングしてみました。
いい感じにフィットしました。

Figure 2020-06-29 163110.png

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

データサイエンス100本ノック~初心者未満の戦いpart9

これはデータサイエンティストの卵がわけもわからないまま100本ノックを行っていく奮闘録である。
完走できるか謎。途中で消えてもQiitaにあげてないだけと思ってください。

100本ノックの記事
100本ノックのガイド

ネタバレも含みますのでやろうとされている方は注意

よそごとやってて遅延

コレは見づらい!この書き方は危険!等ありましたら教えていただきたいです。心にダメージを負いながら糧とさせていただきます。

この解き方は間違っている!この解釈の仕方は違う!等もありましたらコメントください。

今回は45~51まで。
[前回]41~44
[目次付き初回]

45本目

ここから日付型-文字型-数字型の変換を行う問題が増えていきます
実はよそごとやっていた時に少し日付型をいじることをやっていました。
よかった。脱線も無駄ではなかった。

今回ラストに同一サイトであっても欲しい情報→参考にしたページをまとめておきます。ページが飛びすぎててホントにわからん。

P-045: 顧客データフレーム(df_customer)の生年月日(birth_day)は日付型(Date)でデータを保有している。これをYYYYMMDD形式の文字列に変換し、顧客ID(customer_id)とともに抽出せよ。データは10件を抽出すれば良い。

mine45.py
df=df_customer.copy()
df['birth_day']=pd.to_datetime(df['birth_day']).dt.strftime('%Y%m%d')
df[['customer_id','birth_day']].head(10)

'''模範解答'''
pd.concat([df_customer['customer_id'],
           pd.to_datetime(df_customer['birth_day']).dt.strftime('%Y%m%d')],
          axis = 1).head(10)

問題やっている途中でバグったので.copy()を追加。deepcopyしなきゃいけないようならまた変更します。

今回は.dt.strftime()を使用(参考)。
C言語でprintfを多用していた身としてはフォーマット指定は結構好き。

ただ、何故かdf['birth_day']に直接.dt.strftimeをつなぐとエラーします。
なのでpd.to_datetime()で明示してあげると(?)動きます

typeCheck.py
type(df['birth_day'])
type(pd.to_datetime(df['birth_day']))

どっちも

pandas.core.series.Series

なんですがねぇ

46本目

P-046: 顧客データフレーム(df_customer)の申し込み日(application_date)はYYYYMMD形式の文字列型でデータを保有している。これを日付型(dateやdatetime)に変換し、顧客ID(customer_id)とともに抽出せよ。データは10件を抽出すれば良い。

mine46.py
df=df_customer.copy()
df['application_date']=pd.to_datetime(df['application_date'])
df[['customer_id','application_date']].head()
#df['application_date'].describe
#df['application_date'].apply(lambda x:x.year)

'''模範解答'''
pd.concat([df_customer['customer_id'],pd.to_datetime(df_customer['application_date'])], axis=1).head(10)

文字列型→日付型への変換 https://note.nkmk.me/python-pandas-datetime-timestamp/
途中で上手く変換できてるか試しています。

.applylambdaを使った要素の取り出しはこちら

47本目

P-047: レシート明細データフレーム(df_receipt)の売上日(sales_ymd)はYYYYMMDD形式の数値型でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。

mine47.py
df=df_receipt.copy()
df['sales_ymd']=pd.to_datetime(df['sales_ymd'].astype(str))
df[['receipt_no','receipt_sub_no','sales_ymd']].head(10)

'''模範解答'''
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
           pd.to_datetime(df_receipt['sales_ymd'].astype('str'))],axis=1).head(10)

数字→文字→日付型と変換してます。数字→文字列への型変換

48本目

この問題、一か所だけ調べても分からない部分ありました。

P-048: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)は数値型のUNIX秒でデータを保有している。これを日付型(dateやdatetime)に変換し、レシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。

mine48.py
df=df_receipt.copy()
df['sales_epoch']=pd.to_datetime(df['sales_epoch'],unit='s')
df[['receipt_no','receipt_sub_no','sales_epoch']].head(10)

'''模範解答'''
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
           pd.to_datetime(df_receipt['sales_epoch'], unit='s')],axis=1).head(10)

まず、エポック秒とはの記事を読み、最初「これ、intをそのまま時間に直すといけるんじゃね」と安直にやってみましたが出来ず、そのあとも色々試したけれどもギブアップ。答えを見ました。

すると出てきたのはto_datetime('エポック',unit='s')
引数のunitが分からず調べてみたものの、いつものサイトにはなく、仕方ないのでpandasのリファレンスページを開きました。

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.to_datetime.html

unitstr, default ‘ns’
The unit of the arg (D,s,ms,us,ns) denote the unit, which is an integer or float number. This will be based off the origin. Example, with unit=’ms’ and origin=’unix’ (the default), this would calculate the number of milliseconds to the unix epoch start.

英語ということもさておき分からん。引数としてD,s,ms,us,nsを受け入れるというのはなんとなくわかるが、sがどのように作用してるかが全く分からん。
どなたか分かる方、参考ページでもいいので、教えてください。。。(もしかして読んでるページがリファレンスじゃない……?)

49-51本目

似たような問題なので一気に行きます

P-049: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"年"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。データは10件を抽出すれば良い。

P-050: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"月"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。なお、"月"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。

P-051: レシート明細データフレーム(df_receipt)の売上エポック秒(sales_epoch)を日付型(timestamp型)に変換し、"日"だけ取り出してレシート番号(receipt_no)、レシートサブ番号(receipt_sub_no)とともに抽出せよ。なお、"日"は0埋め2桁で取り出すこと。データは10件を抽出すれば良い。

mine49.py
df=df_receipt.copy()
df['sales_epoch']=pd.to_datetime(df['sales_epoch'],unit='s')
df['sales_epoch']=df['sales_epoch'].dt.strftime('%Y')

df[['receipt_no','receipt_sub_no','sales_epoch']].head(10)

'''模範解答'''
pd.concat([df_receipt[['receipt_no', 'receipt_sub_no']],
           pd.to_datetime(df_receipt['sales_epoch'], unit='s').dt.strftime('%Y')],axis=1).head(10)

# %Y を %m %d に変えれば月・日が出せる

エポック秒→日付型→文字列型抜き出しで49~51はできる

ただし、50,51の指定に

なお、"月(日)"は0埋め2桁で取り出すこと。

とあるために

'''罠(ドボン)'''
pd.to_datetime(df['sales_epoch'], unit='s').dt.year

と、やると年は問題なく4桁で出るが月・日は0埋めにならない。
(逆に最初%02dとかやったけどやらなくても0埋めになった)

今回はここまで

参考ページ

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

【Django】新規登録後、自動でログインさせたい

はじめに

 Webサイトを作成する際の基本的な機能として新規登録とログインがある。新規登録完了後、再度ログイン画面で必要項目を入力する作業を省略したいと思ったので方法を調べた。

前提

 プロジェクト構成は以下の通り。設定ディレクトリをconfig,アプリケーションディレクトリはappとaccountsに二つを作成している。

.
├── accounts
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── config
│   ├── __init__.py
│   ├── asgi.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py
├── manage.py
├── app
│   ├── __init__.py
│   ├── apps.py
│   ├── forms.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── static
│   └── css   
│       └── style.css
└── templates
    ├── base.html
    ├── registration
    │   ├── base.html
    │   ├── logged_out.html
    │   ├── login.html
    │   └── signup.html
    └── app
        └── index.html

 設定ディレクトリとその中で指定されたdjango.contrib.auth内のURLconf、及びアプリケーションディレクトリのURlconfを以下に示す。

設定ファイルのURLconf
config/urls.py
from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', include('app.urls')),
    path('accounts/', include('django.contrib.auth.urls')),  # Djangoがあらかじめ提供しているurls.pyへ
    path('accounts/', include('accounts.urls')),  # 自分が作成したurls.pyへ
]
django/contrib/auth/urls.py
from django.contrib.auth import views
from django.urls import path

urlpatterns = [
    path('login/', views.LoginView.as_view(), name='login'),
    path('logout/', views.LogoutView.as_view(), name='logout'),

    path('password_change/', views.PasswordChangeView.as_view(), name='password_change'),
    path('password_change/done/', views.PasswordChangeDoneView.as_view(), name='password_change_done'),

    path('password_reset/', views.PasswordResetView.as_view(), name='password_reset'),
    path('password_reset/done/', views.PasswordResetDoneView.as_view(), name='password_reset_done'),
    path('reset/<uidb64>/<token>/', views.PasswordResetConfirmView.as_view(), name='password_reset_confirm'),
    path('reset/done/', views.PasswordResetCompleteView.as_view(), name='password_reset_complete'),
]

アプリケーションのURLconf
app/urls.py
from django.urls import path
from . import views

app_name = 'app'
urlpatterns = [
    path('', views.IndexView.as_view(), name='index'),
]
accounts/urls.py
from django.urls import path
from . import views

app_name = 'accounts'
urlpatterns = [
    path('signup/', views.SignUpView.as_view(), name='signup'),
]

本題

新規登録完了 → ログイン画面で入力 → ログイン完了

 まず、実装前の状態を確認する。

accounts/views.py
from django.urls import reverse_lazy
from django.views import generic

from .forms import UserCreateForm


class SignUpView(generic.CreateView):
    form_class = UserCreateForm
    template_name = 'registration/signup.html'
    success_url = reverse_lazy('login')
  • CreateViewを継承したSignUpViewを作成する。
  • form_class = UserCreateFormで扱うフォームを指定する。今回はapp/forms.pyで作成したUserCreateFormをform_classとして指定する。
  • template_name = 'registration/signup.html'で新規登録画面のhtmlファイルを指定する。
  • success_url = reverse_lazy('login')で新規登録が完了した後どこのページにいくのかをreverse_lazyを用いて指定する。この場合は'login'なので新規登録が完了するとDjangoがあらかじめ用意してくれているログインのページ(registration/login.html)に飛ぶ。
  • template_name = 'registration/signup.html'で新規登録画面のhtmlファイルを指定する。(※ログインページのhtmlはあらかじめ用意されているが新規登録画面は用意されていないので自分で作る必要がある。)

新規登録完了 → ログイン完了

 新規登録が完了した後、ログイン画面を介さずにログインを完了するためにはacccouts/views.pyを少し変更するだけでいい。

accounts/views.py
from django.urls import reverse_lazy
from django.views import generic
from django.contrib.auth import login, authenticate    # 追加

from .forms import UserCreateForm


class SignUpView(generic.CreateView):
    form_class = UserCreateForm
    success_url = reverse_lazy('app:index')   # 変更
    template_name = 'registration/signup.html'

    # 以下追加
    def form_valid(self, form):
        response = super().form_valid(form)
        username = form.cleaned_data.get('username')
        password = form.cleaned_data.get('password1')
        user = authenticate(username=username, password=password)
        login(self.request, user)
        return response

 行う作業はsuccess_urlの指定とform_validメソッドのオーバーライドの2点。

  • success_url = reverse_lazy('app:index')で登録完了後に遷移する画面を 'login' から 'app:index' つまりトップページに変更をする。
  • form_validメソッドをオーバーライドするコードを追加する。
  • response = super().form_valid(form)で親のform_valid()で返された値を取得する。
  • username = form.cleaned_data.get('username')で新規登録のuserneme欄で入力された値をusernameに代入する。form.cleaned_dataは入力検証を通過したデータを示す。
  • password = form.cleaned_data.get('password1')も同様です。
  • user = authenticate(username=username, password=password)で、あるユーザーとパスワードに対する認証を行う。authenticate()は引数として、usernameとpasswordをとり、ユーザー名に対してパスワードが有効だった場合にUserオブジェクトを返す。無効だった場合はNoneを返す。
  • login(self.request, user)でユーザーを自動でログインさせる。login()は引数としてHttpRequestオブジェクトとUserオブジェクトをとる。
  • 最後にresponseを返す。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【初心者向けハンズオン】kaggleの「住宅価格を予測する」を1行ずつ読み解く(第6回:目的変数の分布変換)

お題

 有名なお題であるkaggleの「House Price」問題にみんなでチャレンジしていくことになったハンズオンの内容をメモしていく企画の第6回。解説というよりはメモのまとめだったりもしますが、どこかの誰かのためになれば幸いです。前回で準備がおわり、いよいよ解析段階に。

本日の作業

目的変数の分布変換

学習データのSalePrice(住宅価格)の分布を確認します。
欠損補完の箇所で、プールがない住宅がほとんどであることがわかりました。
これは裏を返せばプールがあるような豪邸がいくつか存在するということであり、住宅価格がかなり歪な分布になっているのでは?と想定されます。

こうした仮設を元に描画するのが重要だなと振り返ります。とはいえまずは言われるがままにグラフをアウトプット。

sns.distplot(train['SalePrice'])

seabornについて

「snsってなんだっけ?」ってなりました。最初過ぎて忘れていましたが、一番最初にインポートしていたライブラリの中にありましたね。これです。

import seaborn as sns

なるほどseaborn
* seaborn:どうやらグラフ描画のライブラリ。
* seaborn参照:https://qiita.com/hik0107/items/3dc541158fceb3156ee0
* distplot:seabornでヒストグラムを描画するメソッド。

train['SalePrice']に入っていた内容を確認

あとは念のため、train['SalePrice']に入っていた内容を確認。
なるほどひたすら各が並んでいる列。
スクリーンショット 2020-06-29 12.07.02.png

アウトプットされたグラフ

そしてアウトプットされたグラフはこんな感じになりました。

sns.distplot(train['SalePrice'])

image.png

対数変換

予想どおり、かなり右側に分布の裾野が広がっています。
対数変換をすることで正規分布に近づけます。

とのことですが、「対数変換、とは」というところの確認。
* 対数変換参照:https://atarimae.biz/archives/13161#:~:text=%E5%AF%BE%E6%95%B0%E5%A4%89%E6%8F%9B%E3%81%A8%E3%81%AF%E3%80%81%E3%80%8C%E5%AF%BE%E6%95%B0,%E3%81%99%E3%82%8B%E3%81%93%E3%81%A8%E3%82%92%E6%8C%87%E3%81%97%E3%81%BE%E3%81%99%E3%80%82&text=%E5%85%B7%E4%BD%93%E7%9A%84%E3%81%AB%E3%80%81%E8%AA%AC%E6%98%8E%E5%A4%89%E6%95%B0,%E8%80%83%E3%81%88%E3%81%A6%E3%81%BF%E3%81%BE%E3%81%97%E3%82%87%E3%81%86%E3%80%82

sns.distplot(np.log(train['SalePrice']))

対数変換前後の配列の変化

これだけ出力してみます。

np.log(train['SalePrice'])

なるほど、潰れている。
スクリーンショット 2020-06-29 12.17.28.png

アウトプットされたグラフその2

sns.distplot(np.log(train['SalePrice']))

image.png

なるほどきれいめに正規分布になっている気がする。

予測モデルの構築

に、入りたかったですが、どうやら時間切れ臭いので今日はここまでです。

今回は変数量がかなり多いため、係数に強力なペナルティをかけたいのでLasso回帰を使って予測モデルを構築します。

予習がてらLasso回帰について調べて終了。

Lasso回帰

おしまい。

分析レイヤーに入ってきてから、やはり背景知識の補充が必要だなと、理解しました。
主に回帰分析について。

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

【Rust】緯度経度のcsvデータを読み込んで2点間の距離を求める

背景

500点くらいの位置情報がcsvデータにまとめられていて、その全ての組み合わせについて直線距離(km) を求めたい.

緯度経度から距離を求める

以下の記事を参考にさせていただきました.

pub fn cal_rho(lat_a: &f64, lon_a: &f64, lat_b: &f64, lon_b: &f64) -> f64 {
    const RA: f64 = 6378.140; // equatorial radius (km)
    const RB: f64 = 6356.755; // polar radius (km)
    let f: f64;
    let rad_lat_a: f64;
    let rad_lon_a: f64;
    let rad_lat_b: f64;
    let rad_lon_b: f64;
    let pa: f64;
    let pb: f64;
    let xx: f64;
    let c1: f64;
    let c2: f64;
    let dr: f64;
    let rho: f64;

    f = (RA - RB) / RA;
    rad_lat_a = lat_a.to_radians();
    rad_lon_a = lon_a.to_radians();
    rad_lat_b = lat_b.to_radians();
    rad_lon_b = lon_b.to_radians();
    pa = (RB / RA * rad_lat_a.tan()).atan();
    pb = (RB / RA * rad_lat_b.tan()).atan();
    xx = (pa.sin() * pb.sin() + pa.cos() * pb.cos() * (rad_lon_a - rad_lon_b).cos()).acos();
    c1 = ((xx.sin() - xx) * (pa.sin() + pb.sin())).powf(2.0) / ((xx / 2.0).cos()).powf(2.0);
    c2 = ((xx.sin() + xx) * (pa.sin() - pb.sin())).powf(2.0) / ((xx / 2.0).sin()).powf(2.0);
    dr = f / 8.0 * (c1 - c2);
    rho = RA * (xx + dr);
    rho
}

csvの読み込み -> 距離の計算

rdr.records() で取得できる StringRecordsIter を使ってネストのループを作ろうと思ったが、うまく行かなかった.
そのため Iter -> Vec に変換して対応した.

extern crate csv;
use csv::StringRecord;
use std::env;
use std::error::Error;
use std::ffi::OsString;
use std::fs::File;

pub fn run() -> Result<(), Box<Error>> {
    let file_path = "file.csv";
    let file = File::open(file_path)?;
    let mut rdr = csv::Reader::from_reader(file);
    let _records = rdr.records();
    let records: Vec<StringRecord> = _records.map(|x| x.unwrap()).collect();
    let data_len = &records.len();

    for i in 0..*data_len {
        for j in 0..*data_len {
            let r_i = &records[i];
            let lat_i = &r_i[1];
            let lat_i: f64 = lat_i.parse().unwrap();
            let lng_i = &r_i[2];
            let lng_i: f64 = lng_i.parse().unwrap();
            let r_j = &records[j];
            let lat_j = &r_j[1];
            let lat_j: f64 = lat_j.parse().unwrap();
            let lng_j = &r_j[2];
            let lng_j: f64 = lng_j.parse().unwrap();

            let dist = cal_rho(&lat_i, &lng_i, &lat_j, &lng_j);

            println!("{}", dist);
        }
    }
    Ok(())
}

手探りの実装ですので、良い方法あればアドバイスお待ちしています。

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

「クソデカ文学コンバータ」作ってみた

とりあえず作ったもの

https://kusodeka.v-to-i.com

使ってみてね!!

概要

言わずもがな、テキストを入力すると「クソデカ羅生門」みたいにしてくれるアプリです。

どうやっているのか

コードは公開できるほどのものではないですし、テキスト変換の精度も低いので、ここではざっくりとだけ説明します。とりあえずさっさと作りたかったのですごく雑なアルゴリズムです。

  1. クソデカ羅生門と羅生門の差分から、どのような単語の前に「クソデカ」みたいな言葉(クソデカワードとします)が挿入されているかの辞書(クソデカ辞書)を作る(例:"羅生門": ["正気を疑うレベルでデカい", "クソデカい", "クソデカ", "トチ狂ったクソデカさの"])
  2. Mecabで形態素解析して、入力テキストの単語の品詞を特定する。
  3. 一般名詞など特定の品詞の単語の前にクソデカワードを挿入する。この時、クソデカ辞書から、入力の単語と類似度が一番近いkeyのvalueを選んで挿入する(gensim使用)
  4. 最後に言語モデルで出力テキストの自然さを評価し、おかしな部分がないかチェックする(あまりうまく行ってない)
  5. webアプリはflaskで作っています。

最後に

ぜひいろいろ遊んでみてほしいのですが、「こうすればもっとうまくいく!」「こういう機能をつけたら良いんじゃない?」などコメントいただけるととてもありがたいです!
感想としては、webアプリを簡単にでも自作できて楽しかったのと、座学で勉強していたNLPをちょっとでも実践に活かせたのでよかったです。

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

なろう小説APIを試してみた

きっかけ

https://qiita.com/dely13/items/5e949a384161c961d8ce
こちらの記事を読んで、遊び練習がてら自分で試していたら、結果が違うぞ→この記事2017年じゃん
と、なったので最新を出してみた(2020/6/29 10:00現在)

前半はそのまま

@dely13さんの記事をそのまま使います

dely13.py
import pandas as pd
import requests
import numpy as np
import seaborn as sns
from scipy import stats
import matplotlib.pyplot as plt
%matplotlib inline

url = "http://api.syosetu.com/novelapi/api/"
# APIのパラメータをディクショナリで指定する
# この条件で、総合評価順でjson形式のデータを出力する
payload = {'of': 't-gp-gf', 'order': 'hyoka','out':'json'}

st = 1
lim = 500

data = []
while st < 2000:
    payload = {'of': 't-gp-gf-n', 'order': 'hyoka',
          'out':'json','lim':lim,'st':st}
    r = requests.get(url,params=payload)
    x = r.json()
    data.extend(x[1:])
    st = st + lim
df = pd.DataFrame(data)

#前処理('year'列追加、'title_len'列追加)
df['general_firstup'] = pd.to_datetime(df['general_firstup'])
df['year'] = df['general_firstup'].apply(lambda x:x.year)

df['title_len'] = df['title'].apply(len)

ホントにそのままなので詳しくは元の記事を読んでください

本題

df['title_len'].hist()

df['title_len'].describe()

ヒストグラム図  df['title_len'].hist()
image.png
データ  df['title_len'].describe()

count 2000.000000
mean 24.179500
std 15.528356
min 2.000000
25% 12.000000
50% 21.000000
75% 32.000000
max 100.000000
Name: title_len, dtype: float64

平均7文字増えとるwwwwww

そして本当に面白いのはここから

per_year.py
title_by_year = df.groupby('year')['title_len'].agg(['mean','count','std']).reset_index()
#プロット
title_by_year.plot(x='year',y='mean') 
#データ
title_by_year

プロットtitle_by_year.plot(x='year',y='mean')  ※mean=平均
image.png

集計title_by_year

year mean count std
2008 7.500000 2 2.121320
2009 12.428571 7 8.182443
2010 10.882353 17 5.278285
2011 10.180000 50 4.684712
2012 13.294737 95 6.963237
2013 14.115942 138 8.541930
2014 16.065476 168 8.780176
2015 18.218009 211 9.701245
2016 21.577358 265 12.326472
2017 24.476015 271 11.750113
2018 29.425856 263 13.890288
2019 31.327327 333 15.861156
2020 40.483333 180 22.348053

※2020のデータは6/29までのデータです

結論

2019年 なろうのタイトルは 短歌となる
2017年の記事で推測した人すっげぇ。ドンピシャじゃん。

余談1

せっかくなので最大・最小を出してみる

title_by_year = df.groupby('year')['title_len'].agg(['mean','min','max']).reset_index()
#プロット
title_by_year.plot(x='year')
#データ
title_by_year.plot

プロットtitle_by_year.plot(x='year')
image.png

データtitle_by_year

year mean min max
2008 7.500000 6 9
2009 12.428571 5 25
2010 10.882353 2 23
2011 10.180000 4 26
2012 13.294737 3 40
2013 14.115942 3 54
2014 16.065476 4 63
2015 18.218009 3 59
2016 21.577358 2 77
2017 24.476015 4 69
2018 29.425856 5 74
2019 31.327327 4 100
2020 40.483333 4 100

この、100文字のデータって文字数オーバーしてるのでは……?

max_100.py
df[['ncode','title','year','title_len']].set_index('ncode').query('title_len==100')
ncode title year title_len
N7855GF 無能扱いされて、幼馴染パーティーを追放された俺は外れギフト『翻訳』を駆使して成り上がる~馬鹿... 2020 100
N6203GE 独裁王国を追放された鍛冶師、実は《鍛冶女神》の加護持ちで、いきなり《超伝説級》武具フル装備で... 2020 100
N0533FS 【連載版】追っかけていたアイドルがイケメンと歩いている姿を目撃した俺は、バイト代はたいて買っ... 2019 100
N4571GF ループ7週目で信じていた仲間たちに嵌められていたことを知ったので、8周目は能動的にパーティー... 2020 100

・・・これ、100文字オーバーしてない・・・?

記事書いてから調べてみたら100文字きっかりでした
image.png

文字数制限があるのかな?
限界ギリギリで戦っているのはそれはそれですごい。

余談2

逆に短いタイトルが気になった

mini_len.py
df.groupby('title_len')['title_len'].agg(['count']).head(9).T

文字数と作品数の対応一覧
長くなったので横配置

title_len 2 3 4 5 6 7 8 9 10
count 2 8 18 35 41 38 64 75 89
title2_4.py
df[['title','year','title_len']].set_index('title').sort_values('title_len').query('title_len<5')

4文字は一部抜粋で

title year title_len
書簡 2016 2
払暁 2010 2
弓と剣 2013 3
水の理 2012 3
墓王! 2013 3
幼馴染 2016 3
探索者 2013 3
塔の陰 2012 3
駆除人 2015 3
猫と竜 2013 3
忘却聖女 2020 4
J/53 2012 4
黒の魔王 2011 4
私の従僕 2019 4
モブの恋 2015 4
賢者の孫 2015 4
セブンス 2014 4

少ない文字でも有名どころはありますね。
元モバ民には4文字に「タイトル」とかあると感動した。

感想

モバ(現えぶぅ)ほどじゃないにしても初心者参入が多いのは携帯小説のアニメ化の影響か?
自分はモバに鍛えられたので多少読みづらくても内容が面白ければ読むけどそれにしてもタイトルが長い。
かという今ハマってるのこれとかこれもそこそこ長いタイトルだけど。(えぶぅだとこれ※ステマ)

なろうAPIで検索条件絞れるのでいろいろ試してみたいと思った。2000件以上抽出したい場合ってどうするんだろう……

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

scipy.optimizeで、十種競技の世界記録を出すための最適解を算出

はじめに

「十種競技」という競技をご存知でしょうか?
2日間で走(100m, 400m, 1500m, 110mH)、跳(走高跳, 棒高跳, 走幅跳)、投(砲丸投, 円盤投, やり投)の10種目を行い、各種目の記録を得点化しその合計記録で争うという競技です。
現在(2020年6月)の世界記録は Kevin MAYER 選手の9126点です。

ところで、この点数を効率良く出すためにはどうすればいいでしょうか?
「全ての種目で均等に得点を取る」という考えがありそうですが、これは現実的ではありません。
種目によって得点を稼ぎやすいもの、稼ぎにくいものがあるためです。
例えば、各種目で900点を獲得するための記録は下記となります。
(参考までに、十種競技の種目ごとの日本記録も併載)

100m 400m 1500m 110mH 走高跳 棒高跳 走幅跳 砲丸投 円盤投 やり投
900点 10.82 48.19 247.42 14.59 2.10 4.97 7.36 16.79 51.40 70.67
十種種目ごと日本記録 10.53 47.17 248.24 13.97 2.16 5.30 7.65 15.65 50.23 73.82

砲丸投か円盤投で900点を出すためには、日本記録を越えなければいけません。
これは現実的ではありませんね。

では、どうしたら最も簡単に世界記録を出せるでしょうか?
砲丸投では800点しか取れない代わりに他の種目で1000点取ろう、でも100mと1500mのどちらで1000点取るのが楽かなんて分からない…

この問いに対して私が出した答えは
「スコアリングテーブル(通称ハンガリアンテーブル)」での総得点の最小値」というものです。

スコアリングテーブルとは?

国際陸上競技連盟(IAAF)が作成している各種目の記録を得点化した表で、十種競技とは計算式が異なります。
こちらは種目ごとの比較のために作られているので、「自分が出した100m 11.00(886p)という記録は、走幅跳だと6m83(886p)相当だな」などというように使用できます。

算出したもの

「10種目のスコアの最小値(制約:十種の記録が9126点を超える)」を算出しました。

例えば、100mと砲丸投の2種目で合計1000点を目指すとします。
それぞれの種目で500点を出した時の合計スコアは972pですが、
100m14.00, 砲丸投13.32の時の合計スコアは945pであり、得点は同じでもこちらの方が楽に点数を稼げそうです。
(実際問題はまた別ですが……)

記録 十種得点 スコア : 記録 十種得点 スコア
100m 12.82 500 430 : 14.00 312 221
砲丸投 10.24 500 542 : 13.32 687 724
合計 1000 972 : 1000 945

このような感じで、いかに小さいスコアで世界記録を出せるかというのを求めています。

算出方法

最適化手法は、scipy.optimizeのminimizeを使用しました。

記録の範囲は、スコア1p〜世界記録としました。
(トラック種目は下限が世界記録、フィールド種目は上限が世界記録です)

100m 400m 1500m 110mH 走高跳 棒高跳 走幅跳 砲丸投 円盤投 やり投
下限 9.58 43.03 206.00 12.80 0.92 1.16 2.51 1.00 1.57 1.59
上限 16.79 78.01 380.04 25.43 2.45 6.18 8.95 23.12 74.08 98.48

十種競技の得点計算式はWikipediaに記載されています。
スコアリングテーブルの計算式は公式で見つけることができませんでしたが、下記の計算式で算出されるようです。

a × (記録 + b)^2 + c
(係数は下記に記載)

意外とシンプルですね。

以下、算出で使用したコマンドです。

import numpy as np
import pandas as pd
import copy
import sys
from scipy.optimize import minimize, BFGS, LinearConstraint, Bounds
from math import floor
import matplotlib.pyplot as plt

## スコアリングテーブル用係数
w_score = np.array([
    [24.64221166,-16.99753156,-0.218662048], #100m
    [1.021013043,-78.99469306,0.0029880052], #400m
    [0.0406599253,-384.9950051,0.001205591], #1500m 
    [7.665206128,-25.79302259,0.0141087786], #110mH
    [32.14570816,11.59368894,-5026.080842],  #HJ
    [3.045719921,39.33586031,-4993.213828],  #PV
    [1.931092873,48.34861905,-4993.807793],  #LJ
    [0.0423461436,684.8281542,-19915.72457], #SP
    [0.0040063129,2232.983411,-20003.52492], #DT
    [0.0024031525,2879.797864,-19950.96836]])#JT

## 十種競技用係数
w_dec = np.array([
    [25.4347,18,1.81],   #100m
    [1.53775,82,1.81],   #400m
    [0.03768,480,1.85],  #1500m
    [5.74352,28.5,1.92], #110mH
    [0.8465,75,1.42],  #HJ
    [0.2797,100,1.35], #PV
    [0.14354,220,1.4], #LJ
    [51.39,1.5,1.05],  #SP
    [12.91,4,1.1],     #DT
    [10.14,7,1.08]])   #JT

## 目的関数(スコアリングテーブル)
def calc_score(x):
    total_score = 0
    for i in range(10):
        total_score += w_score[i,0] * (x[i] + w_score[i,1])**2 + w_score[i,2]
    return total_score

## 制約関数(十種競技得点)
def calc_dec(x):
    n = 0
    total_point = 0
    target_point = 9126

    for i in range(10):
        if i in (0,1,2,3):
            total_point += w_dec[i,0] * (w_dec[i,1] - x[i]) ** w_dec[i,2] #100m, 400m, 1500m, 110mH
        elif i in (4,5,6):
            total_point += w_dec[i,0] * (x[i] *100 - w_dec[i,1]) ** w_dec[i,2] #走高跳, 走幅跳, 棒高跳
        else:
            total_point += w_dec[i,0] * (x[i] - w_dec[i,1]) ** w_dec[i,2] #砲丸投, 円盤投, やり投
        return_point = total_point - target_point
    return return_point

bounds = Bounds(world_rec[0:4] + min_rec[4:10] , min_rec[0:4] + world_rec[4:10])

cons = (
    {'type': 'ineq', 'fun': calc_dec} 
)

x0 = np.array([10, 46.19, 247.42, 13.59, 1.8, 3.5, 6.06, 10.79, 31.40, 53.67]) # 初期値は適当

関数の作成以外、ほとんど記載していません。
これだけで最適化問題を解けるなんて、とても簡単ですね…

結果

1.世界記録(9126点)達成時の最小スコア

result = minimize(calc_score, x0, constraints=cons, method="SLSQP", bounds=bounds)
print(result)
#     fun: 8707.88035324152
#     jac: array([-141.92468262,  -27.99401855,   -5.38891602,  -70.75549316,
#        902.8885498 ,  277.25720215,  221.29797363,   59.95800781,
#         18.4855957 ,   14.31445312])
# message: 'Optimization terminated successfully.'
#    nfev: 569
#     nit: 38
#    njev: 34
#  status: 0
# success: True
#       x: array([ 14.11782497,  65.28575996, 318.7265988 ,  21.17765192,
#         2.45      ,   6.18      ,   8.95      ,  23.12      ,
#        74.08      ,  98.48      ])

100m:14.11
400m:65.28
1500m:318.72
110mH:21.72
走高跳:2.45(世界記録)
棒高跳:6.18(世界記録)
走幅跳:8.95(世界記録)
砲丸投:23.12(世界記録)
円盤投:74.08(世界記録)
やり投:98.48(世界記録)

総スコア:8707

という結果となりました!ww
得点は指数関数的に増加するので、伸び率の高いフィールド種目を頑張れば
トラック種目は適当でも世界記録出るってことですかね………

2.日本記録(8308点)達成時の最小スコア

世界記録だけだとつまらないので、日本記録バージョンも実行してみました。
なお、上限は「十種競技種目ごと日本記録」としました。

#結果のみ記載
result = minimize(calc_score, x0, constraints=cons, method="SLSQP", bounds=bounds)
print(result)
#     fun: 8397.295007256867
#     jac: array([-262.33532715,  -58.31018066,   -7.70007324, -130.22009277,
#        884.24414062,  271.89660645,  216.27709961,   59.32495117,
#         18.29467773,   14.19604492])
# message: 'Optimization terminated successfully.'
#    nfev: 521
#     nit: 30
#    njev: 30
#  status: 0
# success: True
#       x: array([ 11.67464597,  50.4396491 , 290.30574658,  17.29878908,
#         2.16      ,   5.3       ,   7.65      ,  15.65      ,
#        50.23      ,  73.82      ])

100m:11.67
400m:50.43
1500m:290.30
110mH:17.29
走高跳:2.16(十種日本記録)
棒高跳:5.30(十種日本記録)
走幅跳:7.65(十種日本記録)
砲丸投:15.65(十種日本記録)
円盤投:50.23(十種日本記録)
やり投:73.82(十種日本記録)

総スコア:8397p

という結果となりました。
フィールド競技で得点を稼ぐという傾向は変わりませんね…
ちなみに、右代選手が日本記録を出した際のスコアは8609pなので、
上記だと約200p効率良く日本記録を出せることになりますw

おまけ

scipyとは関係ありませんが、十種競技の得点とスコアリングテーブルの計算式をグラフ化してみました。

## グラフ出力
fig, ax = plt.subplots(5, 2, figsize=(14, 25)) 

for i in range(10):
    pltx = np.arange(min(min_rec[i],world_rec[i]), max(min_rec[i],world_rec[i]), 0.01)
    plty = w_score[i,0] * (pltx + w_score[i,1])**2 + w_score[i,2]
    if i in (0,1,2,3):
        plty_p = w_dec[i,0] * (w_dec[i,1] - pltx) ** w_dec[i,2] #100m, 400m, 1500m, 110mH
    elif i in (4,5,6):
        plty_p = w_dec[i,0] * (pltx *100 - w_dec[i,1]) ** w_dec[i,2] #走高跳, 走幅跳, 棒高跳
    else:
        plty_p = w_dec[i,0] * (pltx - w_dec[i,1]) ** w_dec[i,2]

    ax[i//2, i%2].set_ylim([0,1400])
    ax[i//2, i%2].plot(pltx, plty, color="blue", label="Scoring")
    ax[i//2, i%2].plot(pltx, plty_p,color="orange", label="Decathlon")
    ax[i//2, i%2].set_title(label[i], size=15)
    ax[i//2, i%2].set_xlabel('Record')
    ax[i//2, i%2].set_ylabel('Score / Point')
    ax[i//2, i%2].axvline(x=world_rec[i], ymin=0, ymax=100, ls="--", color="red", label="World Record")
    ax[i//2, i%2].axvline(x=national_dec_rec[i], ymin=0, ymax=100, ls="--", color="green", label="National Decathlon Record")
    ax[i//2, i%2].grid(which = "major", axis = "both", alpha = 0.8,
        linestyle = "--", linewidth = 0.8)
    ax[i//2, i%2].legend(loc='best')

plt.tight_layout()

img.png

高得点領域において、トラック種目はフィールド種目に比べてスコアに対する十種の得点が低い(=高スコアを出しても得点に反映されにくい)気がします。
「最小スコアで高得点を出すためにはフィールドで高スコアを出す」という結論に至った理由が分かるような気がします。。

終わりに

scipy.optimizeは統計学ド素人の私でも使えました。とても便利…
本記事を通して気軽に最適化問題に取り組む人(&十種競技に興味を持った人)が増えれば幸いです。

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

十種競技で世界記録を出すための最適解を算出してみた

はじめに

「十種競技」という競技をご存知でしょうか?
2日間で走(100m, 400m, 1500m, 110mH)、跳(走高跳, 棒高跳, 走幅跳)、投(砲丸投, 円盤投, やり投)の10種目を行い、各種目の記録を得点化しその合計記録で争うという競技です。
現在(2020年6月)の世界記録は Kevin MAYER 選手の9126点です。

ところで、この点数を効率良く出すためにはどうすればいいでしょうか?
「全ての種目で均等に得点を取る」という考えがありそうですが、これは現実的ではありません。
種目によって得点を稼ぎやすいもの、稼ぎにくいものがあるためです。
例えば、各種目で900点を獲得するための記録は下記となります。
(参考までに、十種競技の種目ごとの日本記録も併載)

100m 400m 1500m 110mH 走高跳 棒高跳 走幅跳 砲丸投 円盤投 やり投
900点 10.82 48.19 247.42 14.59 2.10 4.97 7.36 16.79 51.40 70.67
十種種目ごと日本記録 10.53 47.17 248.24 13.97 2.16 5.30 7.65 15.65 50.23 73.82

砲丸投か円盤投で900点を出すためには、日本記録を越えなければいけません。
これは現実的ではありませんね。

では、どうしたら最も簡単に世界記録を出せるでしょうか?
砲丸投では800点の代わりに他の種目で1000点取ろう、でも100mと1500mのどちらで1000点取るのが楽かなんて分からない…

この問いに対して私が出した答えは
「スコアリングテーブル(通称ハンガリアンテーブル)」での総得点の最小値」というものです。

スコアリングテーブルとは?

国際陸上競技連盟(IAAF)が作成している各種目の記録を得点化した表で、十種競技とは計算式が異なります。
こちらは種目ごとの比較のために作られているので、「自分が出した100m 11.00(886p)という記録は、走幅跳だと6m83(886p)相当だな」などというように使用できます。

算出したもの

「10種目のスコアの最小値(制約:十種の記録が9126点を超える)」を算出しました。

例えば、100mと砲丸投の2種目で合計1000点を目指すとします。
それぞれの種目で500点を出した時の合計スコアは972pですが、
100m14.00, 砲丸投13.32の時の合計スコアは945pであり、得点は同じでもこちらの方が楽に点数を稼げそうです。
(実際問題はまた別ですが……)

記録 十種得点 スコア : 記録 十種得点 スコア
100m 12.82 500 430 : 14.00 312 221
砲丸投 10.24 500 542 : 13.32 687 724
合計 1000 972 : 1000 945

このような感じで、いかに小さいスコアで世界記録を出せるかというのを求めています。

グラフ化

(編集中)

算出方法

記録の範囲は、スコア1p〜世界記録としました。
(トラック種目は下限が世界記録、フィールド種目は上限が世界記録です)

100m 400m 1500m 110mH 走高跳 棒高跳 走幅跳 砲丸投 円盤投 やり投
下限 9.58 43.03 206.00 12.80 0.92 1.16 2.51 1.00 1.57 1.59
上限 16.79 78.01 380.04 25.43 2.45 6.18 8.95 23.12 74.08 98.48

最適化手法は、scipy.optimizeのminimizeを使用しました。

(編集中)

結果

取り急ぎ、結果のみ記載します。
他は後日記載予定です…

result = minimize(calc_score, x0, constraints=cons, method="SLSQP", bounds=bounds)
print(result)
#     fun: 8707.88035324152
#     jac: array([-141.92468262,  -27.99401855,   -5.38891602,  -70.75549316,
#        902.8885498 ,  277.25720215,  221.29797363,   59.95800781,
#         18.4855957 ,   14.31445312])
# message: 'Optimization terminated successfully.'
#    nfev: 569
#     nit: 38
#    njev: 34
#  status: 0
# success: True
#       x: array([ 14.11782497,  65.28575996, 318.7265988 ,  21.17765192,
#         2.45      ,   6.18      ,   8.95      ,  23.12      ,
#        74.08      ,  98.48      ])

100m:14.11
400m:65.28
1500m:318.72
110mH:21.72
走高跳:2.45(世界記録)
棒高跳:6.18(世界記録)
走幅跳:8.95(世界記録)
砲丸投:23.12(世界記録)
円盤投:74.08(世界記録)
やり投:98.48(世界記録)

総スコア:8707

という結果となりました!ww
得点は指数関数的に増加するので、伸び率の高いフィールド種目を頑張れば
トラック種目は適当でも世界記録出るってことですかね………

終わりに

scipy.optimizeは統計学ド素人の私でも使えました。とても便利…

今後は、日本記録を超えるための最小スコアや、制約を変更しての算出なども追記したいと思います。

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

RedshiftでUNLOADしたgzipファイルをLambdaのPythonで処理をして再びgzipしてS3へアップする

やりたいこと

タイトルの通りですが、Redshiftにデータウェアハウスがあり、普段ELTで処理することが多いのですが、プログラミングによるデータ加工が必要な場合もあります。

RedshiftのUNLOADを利用することでRedshiftからSQLの結果をS3にgzipファイルを作成することが出来ますので、S3へのputイベントをトリガーにLambdaで処理して再びgzipした状態でS3にアップ、ということをしてみました。

UNLOAD

Lambdaは現時点では3008MBが最大となります。今回のような処理はファイルサイズが増えると必然利用メモリ量が増えてしまいます。
そこで、MAXFILESIZE パラメータ を設定することでLambdaへ渡すファイルサイズを調整します。
完全なるケースバイケースですが、今回は50MBで設定してみました。

Lambda上のコード

トリガ設定は割愛します。

import json
import boto3
import urllib.parse
import os
import sys
import csv
import re
import traceback
import gzip
import subprocess

s3client = boto3.client('s3')
s3resource = boto3.resource('s3')
SEP = '\t'
L_SEP = '\n'
S3OUTBACKET='XXXXXXXX'
S3OUTBASE='athena/preprocessing/XXXXXXtmp/'

def lambda_handler(event, context):

    bucket = event['Records'][0]['s3']['bucket']['name']
    key = urllib.parse.unquote_plus(event['Records'][0]['s3']['object']['key'], encoding='utf-8')
    taragetfile=os.path.split(key)[1]
    outputprefixA=os.path.split(key)[0].split("/")[-1]
    outputprefixB=os.path.split(key)[0].split("/")[-2]

    outputdata = "";
    try:
        dlfilename ='/tmp/'+key.replace("/","")
        s3client.download_file(bucket, key, dlfilename)
        gzipfile = gzip.open(dlfilename, 'rt') 
        csvreader = csv.reader(gzipfile, delimiter=SEP, lineterminator=L_SEP, quoting=csv.QUOTE_NONE)
        for line in csvreader:
            # 1行ずつ様々な処理をして、outputdataに格納していく。
            # 割愛された処理の中に、利用しているimportがございます。
            # ご了承ください
    except Exception as e:
        print(e)
        raise e

    print("memory size at outputdata:"+str(sys.getsizeof(outputdata)))
    os.remove(dlfilename)
    uploadbinary = gzip.compress(bytes(outputdata , 'utf-8'))
    print("memory size at uploadbinary:"+str(sys.getsizeof(uploadbinary)))
    uploadfilename='processed_'+taragetfile

    try:
        bucket = S3OUTBACKET
        key = S3OUTBASE+outputprefixA+"/"+outputprefixB+"/"+uploadfilename
        obj = s3resource.Object(bucket,key)
        obj.put( Body=uploadbinary ) 
    except Exception as e:
        print(e)
        raise e

    return 0

チューニングしてゆく

実際のファイルでテストをしたところメモリエラーとなってしまいました。
コードの途中に挟んでいる str(sys.getsizeof(outputdata)) はその確認用で、メモリサイズを見て状況を把握しました。コードには書いてませんが、gzip自体の対象データへの圧縮率も見ておくと良いと思います。
なお私が今回扱ったデータは、gzip圧縮後50MBだったのですが、処理後のデータ+圧縮後のデータで1000MBものメモリを要してしまいました。やはり実際にやってみないと分からないものですね。Pythonのメモリ事情をもう少し調べた方が良いかもしれません。

なおLambdaはメモリサイズを増やすとCPUリソースなども増えますので、処理内容とファイルサイズ次第ですが、一度最大の3008MBだとどのくらい処理が速くなるのかは確認すると良いです。今回も、メモリを倍にしたら処理時間が半分に、というケースすらありました。

もし定常的に行う処理の場合、ここでのチューニングがランニングコストに直結するので重要度が高いです。

語彙の無い感想

Lambdaめっちゃ便利

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