yoraba build

備忘録を兼ねた技術ブログ

PyConJP 2020で登壇しました

pycon.jp

Python(Cython) & OpenMPで高速並列処理

感想

 初めての登壇でしたが、コロナ禍の影響で初のオンライン開催でした。 オフライン登壇の経験はありませんが、オンラインの方が緊張が少なく、気軽に発表できると思います。 ただ、ずっと自宅にいるので、イベントに参加しているという実感はあまり湧きませんでした。

ステートフルな再帰型ニューラルネットワークでビットコイン価格予測

内容

  • 時系列データということで、ステートフルなモデルを構築しました。
  • データのダウンロード、前処理、学習、予測までを全てGoogle Colab上で行いました。
    • ドライブのマウントが以前よりも簡単になったので、まるでLinuxマシンを動かしているような使用感です。
  • 予測結果は芳しくないですが、改善の余地はありそうです。

ソース

https://colab.research.google.com/drive/1xLFdDUTmisWONavG2OmX2tz8Bf2An_e9?usp=sharing

tensorboard使用法

 今まであまり使いこなせていなかったtensorboardについて少し調べました。

GoogleColab

 GoogleColabでもtensorboardを実行可能です。トンネルを作る必要はなく、マジックコマンドで簡単に起動できます。出力セル内に表示してくれるのも嬉しいです。ブラウザのタブを切り替えるのって地味に面倒ですし。

SCALARS

f:id:yoraba:20200812205802p:plain

  • 統計スカラー量のグラフを見ることが出来ます。
  • 評価関数と損失関数のグラフを表示して、過学習や学習不足が起こっていないか確認するといったことが出来ます。
  • 画面説明
    • 左側のメニューで表示の微調整を行います
    • 上図グラフでは、縦軸が値で横軸がエポック数となっています。
    • 上図グラフでは、訓練データと検証データに対応する値を分類し、数回学習した結果を重ねて表示しています。このように任意の種類、任意の数のグラフを出力可能です。

GRAPHS

f:id:yoraba:20200812205850p:plain

  • モデルの構成図を見ることが出来ます。
  • ノードを接続する矢印に入力データの形状や説明が書かれることがあります。
  • ノードをクリックすると関連ノードが強調表示されます
  • ノードの右上の+ボタンをクリックするとノードの構造図を見ることが出来ます。

DISTRIBUTIONS

f:id:yoraba:20200812205906p:plain

  • 値の分布を見ることが出来ます
  • σ~3σまでの幅が折れ線を境界として色分けされます

HISTOGRAMS

f:id:yoraba:20200812205919p:plain

  • 値の分布を三次元的に見ることが出来ます。
  • 手前のデータほど新しいです。上図ではエポック数が表示されています。
  • 値の範囲が広過ぎたり、値が大き過ぎたりすると学習がうまくいっていない可能性があります。

python-docxでWord文書を作成する

実用的なケース

Wordをプログラムで操作する上で実用的なケースを考えました。やはり、テンプレートが指定されている文書をcsvなどからデータを取り込んで自動編集する、という局面で役に立つのではないでしょうか。今回はネットで拾ってきたサンプルの請求書ファイルを編集するというところまでやっていこうと思います。

financial.mook.to

テンプレートファイルの中身を解析する

from docx import Document
import os


print(f'{os.path.abspath(os.curdir)=}')
document = Document('iv01.docx')
print(f'{len(document.paragraphs)=}')
for paragraph in document.paragraphs:
    print(f'{paragraph.text=}')
print(f'{len(document.tables)=}')
for table in document.tables:
    print(f'{len(table.rows)=}')
    for row in table.rows:
        print(f'{len(row.cells)=}')
        for cell in row.cells:
            paragraphs = cell.paragraphs
            for paragraph in paragraphs:
                print(f'table {paragraph.text=}')

以上のコードを実行すると、全てのバラグラフとテーブル値を出力できます。 paragraphsやtablesといったオブジェクトはコレクションなので、インデックスで要素にアクセス出来ます。 Wordのパラグラフやテーブル値がどのインデックスに位置するかを、上記コードで出力した文字列から解析します。

CSV

複数の請求先のデータを取り込みます。 請求先ごとに請求書を作る必要があります。

請求先,品名,数量,単価
会社A,商品A,1,1000
会社A,商品B,2,2000
会社A,商品C,3,3000
会社B,商品D,4,4000
会社B,商品E,5,5000
会社B,商品F,6,6000
会社B,商品G,7,7000

Word編集

 請求先名称や日付、商品のテーブル行列がどのインデックスに位置するかを変数に保持します。 csvからデータを取り込んで、請求先名称の集合を取得します。それをメインのループで回して、請求先毎の請求書を作ります。 paragraphsやtablesに先程保持したインデックスを指定して、text値を編集します。

from docx import Document
import pandas as pd
from datetime import date
from japanera import Japanera, EraDate

jaera = Japanera()
c_era = jaera.era(date.today())


template_name = 'iv01.docx'
csv_path = './iv01.csv'

pr_date = 0
pr_billing_address = 1
trc_billing_amount = (2, 3)
trc_subtotal = (15, 7)
trc_tax = (16, 7)
trc_total = (17, 7)
tax_rate = 0.08
items_row_start_idx = 6
c_item_name = 1
c_amount = 5
c_unit_price = 6
c_total = 7


df = pd.read_csv(csv_path)
print(df)
billing_address_set=set(df["請求先"])
print(f'{billing_address_set=}')

for billing_address in billing_address_set:
    doc_name = f'請求書_{billing_address}.docx'
    document = Document(template_name)
    paragraphs = document.paragraphs
    table = document.tables[0]
    paragraphs[pr_date].text = c_era.strftime(date.today(), '%-E%-O年%m月%d日')
    paragraphs[pr_billing_address].text = f'{billing_address} 御中'
    billing_data = df[df['請求先'] == billing_address]
    print(billing_data)
    current_row_idx = items_row_start_idx
    subtotal = 0
    for data in billing_data.values:
        print(data)
        table.rows[current_row_idx].cells[c_item_name].paragraphs[0].text = data[1]
        table.rows[current_row_idx].cells[c_amount].paragraphs[0].text = str(data[2])
        table.rows[current_row_idx].cells[c_unit_price].paragraphs[0].text = str(data[3])
        row_total = data[2] * data[3]
        table.rows[current_row_idx].cells[c_total].paragraphs[0].text = str(row_total)
        subtotal += row_total
        current_row_idx +=1

    table.rows[trc_subtotal[0]].cells[trc_subtotal[1]].paragraphs[0].text = str(subtotal)
    tax = subtotal * tax_rate
    table.rows[trc_tax[0]].cells[trc_tax[1]].paragraphs[0].text = str(tax)
    table.rows[trc_total[0]].cells[trc_total[1]].paragraphs[0].text = str(subtotal + tax)
    table.rows[trc_billing_amount[0]].cells[trc_billing_amount[1]].paragraphs[0].text = str(subtotal + tax)
    document.save(doc_name)

結果

f:id:yoraba:20200704163540p:plain f:id:yoraba:20200704163609p:plain

boto3でDynamoDBを操作する

DynamoDBのセットアップ

DockerでローカルにDynamoDBを構築しました。 docs.aws.amazon.com

f:id:yoraba:20200704022222p:plain

JavaScript Shell

ブラウザでhttp://localhost:(ポート番号)/shell/にアクセスすると、CUIでDynamoDBを操作できます。 f:id:yoraba:20200704022730p:plain 設定ダイアログを開いてdocker composeで設定したアクセスキーを入力します。

テーブルの作成

JavaScript Shellがテンプレートを提供しているので、パラメータを変えるだけでテーブルを作れます。

f:id:yoraba:20200704023938p:plain

var params = {
    TableName: 'dummy_table',
    KeySchema: [ // The type of of schema.  Must start with a HASH type, with an optional second RANGE.
        { // Required HASH type attribute
            AttributeName: 'key',
            KeyType: 'HASH',
        },
    ],
    AttributeDefinitions: [ // The names and types of all primary and index key attributes only
        {
            AttributeName: 'key',
            AttributeType: 'S', // (S | N | B) for string, number, binary
        },
    ],
    ProvisionedThroughput: { // required provisioned throughput for the table
        ReadCapacityUnits: 10, 
        WriteCapacityUnits: 1, 
    },
};
dynamodb.createTable(params, function(err, data) {
    if (err) ppJson(err); // an error occurred
    else ppJson(data); // successful response

});

boto3を使ってDynamoDBを操作

dataclassを使ってCRUD操作を行えるラッパーを作ってみました。

from boto3.session import Session
from abc import ABCMeta, abstractmethod
from typing import Dict
import dataclasses
from dataclasses import dataclass


@dataclass()
class ATable(metaclass=ABCMeta):

    @property
    @abstractmethod
    def table_name(self) -> str:
        pass

    @property
    @abstractmethod
    def keys(self) -> Dict[str, str]:
        pass


class DynamoDBWrapper:

    def __init__(self, session: Session, uri):
        self.resource = session.resource(service_name='dynamodb', endpoint_url=uri)
        self.client = session.client(service_name='dynamodb', endpoint_url=uri)

    def insert(self, table: ATable):
        target = self.resource.Table(table.table_name)
        return target.put_item(Item=dataclasses.asdict(table))

    def select(self, table: ATable):
        target = self.resource.Table(table.table_name)
        return target.get_item(Key=table.keys)

    def update(self, table: ATable):
        target = self.resource.Table(table.table_name)
        dict_table = dataclasses.asdict(table)
        expression, names, values = "", {}, {}
        for k, v in dict_table.items():
            key_name = f'#{k}'
            value_name = f':{k}'
            if k in table.keys.keys():
                continue
            if expression == "":
                expression = f'set {key_name}={value_name}'
            else:
                expression += f', {key_name}={value_name}'
            names[key_name] = k
            values[value_name] = v
        return target.update_item(Key=table.keys, UpdateExpression=expression,
                                  ExpressionAttributeNames=names,
                                  ExpressionAttributeValues=values)

    def delete(self, table: ATable):
        target = self.resource.Table(table.table_name)
        return target.delete_item(Key=table.keys)


@dataclass()
class DummyTable(ATable):

    key: str
    str_value: str
    dict_value: Dict

    @property
    def table_name(self) -> str:
        return 'dummy_table'

    @property
    def keys(self) -> Dict[str, str]:
        return {'key': self.key}


if __name__ == "__main__":
    sess = Session(aws_access_key_id='DUMMYIDEXAMPLE', aws_secret_access_key='DUMMYEXAMPLEKEY', region_name='us-west-2')
    ddw = DynamoDBWrapper(sess, 'http://localhost:8000')
    print(ddw.client.list_tables())
    dummy = DummyTable('key1', 'value1', {'value2': 1})
    print('INSERT', ddw.insert(dummy))
    print('SELECT', ddw.select(dummy))
    dummy.str_value = 'value3'
    dummy.dict_value['value2'] = 2
    dummy.dict_value['value4'] = 3
    print('UPDATE', ddw.update(dummy))
    print('SELECT', ddw.select(dummy))
    print('DELETE', ddw.delete(dummy))
    print('SCAN', ddw.client.scan(TableName=dummy.table_name))