20201010のPythonに関する記事は26件です。

メモ:ImageJ FijiでPythonを動作させるとjava.lang.IllegalArgumentExceptionが出る件とその解決(mac OS)

はじめに

生命科学系を中心に,画像解析に用いられるオープンソースソフトウェア「ImageJ Fiji」をmac OSにインストールし,Pythonで動作テストをさせた際,初っ端からJavaのエラーメッセージが表示され出鼻をくじかれてしまいました.
その解決について,ネット上に書かれたエントリが見つからなかったため,自戒と,もしかしたら誰かの役に立つかもと思いメモしておきます.

動作環境

  • MacBook Pro Late2016
  • mac OS Catalina 10.15.6
  • Python 3.8.2

エラーの概要

Fijiであらかじめ作成しておいたPythonのファイルを開き,動かすと…
th_スクリーンショット 2020-10-10 20.41.54 21.05.44.png
Javaのエラーメッセージが出現します.
th_スクリーンショット 2020-10-10 20.42.02 21.05.44.png
エラーは以下のようなことが書かれており…

java.lang.IllegalArgumentException: Cannot create PyString with non-byte value

調べたところ,PyStringの引数に全角文字が含まれているときに出るエラーとのことでした.

解決方法(多分)

動作させるPythonファイルのファイルパスに全角文字が含まれているのが問題だったようです.
動作ファイルが含まれるフォルダ名をアルファベットに変更すると解決しました.簡単!

色々試してみたところ,今回のエラーに関係するのは
- 対象のファイル名
- 対象のファイルパスに含まれるすべてのフォルダ名
だと思われます.

画像ファイルは,Fijiで開きアクティブにしておけば全角文字を含んだファイルパスでも問題ないですが,パスをPython側で指定して開く場合,同様に全角文字が含まれないパスにする必要がありそうです.

念の為,ホームディレクトリの.localizedファイルを削除したりもしてみましたが,動作には関係ありませんでした.

感想

こんな簡単なところで躓くとは…どおりで誰も何も書いていないわけだよ!

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

AtCoder HHKB プログラミングコンテスト 2020 参戦記

AtCoder HHKB プログラミングコンテスト 2020 参戦記

HHKB2020A - Keyboard

2分で突破. 書くだけ.

S = input()
T = input()

if S == 'Y':
    print(T.upper())
elif S == 'N':
    print(T)

HHKB2020B - Futon

3分で突破. 書くだけ.

H, W = map(int, input().split())
S = [input() for _ in range(H)]

result = 0
for h in range(H):
    for w in range(W - 1):
        if S[h][w] == '.' and S[h][w + 1] == '.':
            result += 1
for h in range(H - 1):
    for w in range(W):
        if S[h][w] == '.' and S[h + 1][w] == '.':
            result += 1
print(result)

HHKB2020C - Neq Min

6分半で突破. 制限からして O(N) にしないとダメそうだなあと思いつつ、一目でその方法が見えなくて悩んだ記憶がある割にそれほど時間がかかってないのが不思議. 毎行0から考え直していたらO(1)になる気がしなかったので、前の行の結果は受け継ぐとして、単調増加だからと考えた辺りでわかった.

N, *p = map(int, open(0).read().split())

result = []
t = 0
s = set()
for x in p:
    s.add(x)
    while t in s:
        t += 1
    result.append(t)
print(*result, sep='\n')

HHKB2020D - Squares

30分くらい悩んだけど、端の処理が難しくてどうにも解けなかった. 問題文を読んだ感触でも、順位表でもEのほうが簡単そうだったのでそっちへ.

HHKB2020E - Lamps

60分くらい悩んだけど、交差している部分の処理が難しくてどうにも解けなかった.

追記: 各マスごとに灯りがついていないパターン数を引いていくのか. なるほどなあ. それが分かれば実装は難しくなかった.

package main

import (
    "bufio"
    "fmt"
    "os"
    "strconv"
)

const (
    m = 1000000007
)

func main() {
    defer flush()

    H := readInt()
    W := readInt()
    S := make([]string, H)
    for i := 0; i < H; i++ {
        S[i] = readString()
    }

    K := H * W
    for i := 0; i < H; i++ {
        for j := 0; j < W; j++ {
            if S[i][j] == '#' {
                K--
            }
        }
    }

    yoko := make([][]int, H)
    tate := make([][]int, H)
    for i := 0; i < H; i++ {
        yoko[i] = make([]int, W)
        tate[i] = make([]int, W)
    }

    for i := 0; i < H; i++ {
        s := 0
        l := 0
        for j := 0; j < W; j++ {
            if S[i][j] == '#' {
                for k := s; k < j; k++ {
                    yoko[i][k] = l
                }
                s = j + 1
                l = 0
            } else if S[i][j] == '.' {
                l++
            }
        }
        for k := s; k < W; k++ {
            yoko[i][k] = l
        }
    }

    for i := 0; i < W; i++ {
        s := 0
        l := 0
        for j := 0; j < H; j++ {
            if S[j][i] == '#' {
                for k := s; k < j; k++ {
                    tate[k][i] = l
                }
                s = j + 1
                l = 0
            } else if S[j][i] == '.' {
                l++
            }
        }
        for k := s; k < H; k++ {
            tate[k][i] = l
        }
    }

    t := make([]int, K+1)
    t[0] = 1
    for i := 1; i < K+1; i++ {
        t[i] = t[i-1] * 2
        t[i] %= m
    }

    c := 0
    for i := 0; i < H; i++ {
        for j := 0; j < W; j++ {
            if S[i][j] == '#' {
                continue
            }
            c += t[K-tate[i][j]-yoko[i][j]+1]
            c %= m
        }
    }

    result := K * t[K]
    result %= m
    result -= c
    result += m
    result %= m
    println(result)
}

const (
    ioBufferSize = 1 * 1024 * 1024 // 1 MB
)

var stdinScanner = func() *bufio.Scanner {
    result := bufio.NewScanner(os.Stdin)
    result.Buffer(make([]byte, ioBufferSize), ioBufferSize)
    result.Split(bufio.ScanWords)
    return result
}()

func readString() string {
    stdinScanner.Scan()
    return stdinScanner.Text()
}

func readInt() int {
    result, err := strconv.Atoi(readString())
    if err != nil {
        panic(err)
    }
    return result
}

var stdoutWriter = bufio.NewWriter(os.Stdout)

func flush() {
    stdoutWriter.Flush()
}

func println(args ...interface{}) (int, error) {
    return fmt.Fprintln(stdoutWriter, args...)
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【メモ】BeautifulSoup4の使い方(3) class_で記事の見出しを表示

前回はfind_allを使って見出しを表示したが、今回はclass_を使って見出しを表示する。また、Yahoo!Japanをスクレイピングすることにした。

In[1]BeautifulSoupとRequestsをimportする

In[1]
from bs4 import BeautifulSoup
import requests

In[2]RequestsでYahoo!Japanのurlを取得し、テキストを表示する

In[2]
toget_url =requests.get("https://www.yahoo.co.jp/")
toget_url.text

In[3]BeautifulSoupとhtml.parserで解析

In[3]
soup = BeautifulSoup(toget_url.text,"html.parser")

ここまでは変数とurlを変えたこと以外は前回と同じだ。

In[4]class_=をもとにfind_allで検索

In[4]
heading =soup.find_all(class_="TRuzXRRZHRqbqgLUCCco9")

デベロッパーツールでYahoo!Japanの見出しを調べたところ"TRuzXRRZHRqbqgLUCCco9"が見出しで使われていることがわかった。class_で検索するときは_(アンダーバー)を忘れないようにする。

In[5]for文で回して内容を表示

In[5]
for heading_name in heading:
    print(heading_name)

これで見出しが表示できた。

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

性懲りもなくPythonでLOTO6に挑む

まえがき

えぇ、もう既に分析され尽くして特にこれと言ってめぼしい結論が出てないことは知ってます。それでもPythonの勉強がてらちょっとやってみようかなと。

開発環境

  • Python : 3.8.3
  • BeautifulSoup : 4.9.2
  • resuests : 2.24.0
  • Visual Studio Code : 1.49.3

過去データ取得

手動コピペなどやってられないのでスクレイピングしてくることにしました。本日(20/10/09)時点の最後のデータは第1524回です。

尚、LOTO6では本数字6コとボーナス数字1コが抽選されますが、今回は本数字のみで勝負します。抽選される数字は1~43で重複することはありません。

scloto6.py
import requests
from bs4 import BeautifulSoup

r = requests.get('http://hogehoge.com/loto6/data/list1/')
soup = BeautifulSoup(r.content, "html.parser")
numbers = soup.find_all('td', class_='w4')

i = 1
with open('index.txt','w') as f:
    for number in numbers:
        s = number.text.replace('\t', '')
        s = s.replace('\n', '')

        if len(s) == 1:
            s = '0' + s
        if (i % 6) != 0:
            s = s + '\t'
        else:
            s = s + '\n'
        f.write(s)
        i += 1

こんな感じのTAB区切りのテキストファイルが生成されます。

index.txt
02 08 10 13 27 30
01 09 16 20 21 43
01 05 15 31 36 38
16 18 26 27 34 40
09 15 21 23 27 28

これを入力ファイルとして次のような処理を行います。

  • 各回の合計からσを求める
  • 各数字(1..43)の出現回数をカウントし、降順ソートする
  • 5口買うことにする
  • 1口分6コの数字のうち2コは出現回数上位10コからピックアップする
  • 残りの数字4コは出現回数下位10コを除いた11..33の中からランダムにピックアップする
  • 6コ選択した数字の総和が±σの範囲から外れていたらやり直し

第1524回までの各回の合計の平均は131.94、σは28.37なので-σ~σは103.57~160.31の範囲になります。

データ数 割合
-σ~σ 1,042 68.37%
-2σ~2σ 407 95.08%
-3σ~3σ 73 99.87%

つまり6コの本数字の合計値が104~160の間に入る確率は約2/3ということです。[1, 2, 3, 4, 5, 6]などの抽選結果は合計21で3σからも外れるのでほぼ無視して良い(≒もしそうなった時は諦めもつく)ということ。

pyloto6.py
import math
import random
import numpy as np

appearance_count = {}       # 各数字の出現回数

for i in range(44):
    appearance_count[i] = 0

sums = []       # 各回合計値の配列

with open('C:\\Python\\scloto6\\index.txt', 'r') as f:
    lines = f.readlines()
    for line in lines:
        array = line.split('\t')
        array_n = list(map(int, array))     # arrayはstr配列なのでint配列に変換
        sums.append(sum(array_n))

        for i in array_n:
           appearance_count[i] = appearance_count[i] + 1

# 出現回数で降順ソート
sorted_appearance_count = sorted(appearance_count.items(), key=lambda x:x[1], reverse=True)

avg = sum(sums) / len(sums)
print(f"AVG:{avg}")

sigma = np.std(sums)        # sumsの標準偏差
sigmalower = avg - sigma
sigmaupper = avg + sigma
print(f"σ:{sigma}({sigmalower}{sigmaupper})")

for index in [0, 2, 4, 6, 8]:
    while True:
        a = []

        a.append(sorted_appearance_count[index][0])
        a.append(sorted_appearance_count[index + 1][0])

        # 残りの4コはランダム(つまりピックアップの根拠なし)
        while len(a) < 6:
            no = random.randint(10, 32)
            value = sorted_appearance_count[no][0]
            if not value in a:
                a.append(value)

        asum = sum(a)

        if sigmalower <= asum and asum <= sigmaupper:
            # ピックアップした6コの数字の総和が-σ~σの範囲なら採用
            break

    print(f"{asum}{a}")

出力はこんな感じになります。

実行結果
AVG:131.93635170603673
σ:28.37024181798044(103.56610988805629~160.30659352401716)
123[6, 38, 22, 26, 11, 20]
128[10, 27, 39, 21, 26, 5]
108[37, 12, 2, 16, 21, 20]
126[24, 15, 39, 35, 5, 8]
129[19, 43, 25, 3, 23, 16]

あとがき

1口200円ですから5口で1000円とキリが良いので5口にしていますがどこかでLOTO6は3口がベストとか読んだ気もします(根拠は忘れた・・・)

1524回分の出現数字を見える化しても何の傾向も見えてこないので物理機械抽選方式は攻略法が無いのかもしれません。もしあったらもう丸20年も行われているので既に先人が発見しているかと思われます。抽選日は毎週月・木の2回ですよ!

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

Pythonで線形探索

線形探索のコードは以下です。

def linear_search(src, target_value):
    result = False
    for i in range(len(src)):
        if src[i] == target_value:
            result = True
    return result


def main():
    src = [1, 2, 3, 4, 5]
    target_value = 5

    if linear_search(src, target_value):
        print('Found!')
    else:
        print('Not Found')


if __name__ == '__main__':
    main()

実行結果は以下になります。

Found!

最後まで読んでいただきありがとうございました。
またお会いしましょう。

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

将棋AIで学ぶディープラーニング on Mac and Google Colab 第8章

TOP PAGE

戦略

グリーディー戦略

貪欲法。単純にニューラルネットワークの出力値の最も高い手を選ぶ方法。logitsとはニューラルネットワーク出力段の活性化関数を通す前の値。

def greedy(logits): # 引数に指定したリストの要素のうち、最大値の要素のインデックスを返す
                    # logitsとはニューラルネットワークでは活性化関数を通す前の値のこと
    return np.argmax(logits)

ソフトマックス戦略

温度という係数で確率が変わるっぽい。

def boltzmann(logits, temperature):
    logits /= temperature # a /= b は a = a / b の意味
    logits -= logits.max() # a -= b は a = a - b の意味。 マイナスの値になる。最大値は0。
    probabilities = np.exp(logits) # x =< 0 のexp関数
    probabilities /= probabilities.sum()
    return np.random.choice(len(logits), p=probabilities) # choice(i, p=b)は0~i-1までの数値をbの確率でランダムに返す

フロー図
image.png

簡単な例として出力が5個(出力1が-0.2、出力2が0.3、出力3が0.5、出力4が0、出力5が-0.6)の場合のexp出力までの処理を図示する。温度は1とする。
image.png

温度を設定すると温度が小さいほど各出力の大きさが近くなる。つまり、温度が小さいほど指し手の確率が均等になっていく。
image.png

第8章では最後にランダム性を持たせる処理をしている。ランダム性を持ちつつ確率が高い手ほど選ばれやすい。第12章ではこの処理はしていない。よく理解できていないが使い方に合わせているのか。

    return np.random.choice(len(logits), p=probabilities) # choice(i, p=b)は0~i-1までの数値をbの確率でランダムに返す
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

PyPIへのモジュール公開手法がいろいろ変わっていたのでメモ

PyPIへのモジュール公開手法がしばらく見ない間にだいぶ変わっていたので備忘録としてメモしておきます。

何を公開したの?

こちらです。

いまのところドキュメントと言えるドキュメントはGitHubだけです

setup.cfg

わたしが昔PyPIを触っていたときは、setup.pyにいろんな設定を書き込むことで、モジュールの設定を記載していたのですが、今はsetup.cfgというファイルにモジュールの情報を書き込むようになったようです。

setup.cfg
[metadata]
name = tksugar
version = attr: tksugar.__version__
author = TakamiChie
author_email = [mail]
license = MIT
description = A module that generates a structured Tk window frame from a text file.
keywords =
url = https://github.com/TakamiChie/TkSugar
long_description = file: README.md
long_description_content_type = text/markdown
classifiers =
    Programming Language :: Python :: 3.7
    Programming Language :: Python :: 3.8

[options]
packages = find:
install_requires =
  pyyaml

とりあえず今回の件で気になるところ。

version

あらかじめ、アップロードするモジュールの__init__.pyに、次のような定数を追加しておきます。

__init__.py
__version__ = "0.1.0"

わたしの場合__init__.pyroot\tksugar\__init__.pyに置いていたので、このような既述でよかったのですが、たとえば__VERSION__.pyというファイルを作ってそこに定義したいとか、root/src/project/__init__.pyにしたいときとかは既述が変わってきますので注意。

long_description

読んで字のごとく長い説明文です。README.mdを読み込んでそのまま表示しています。
この文章がPyPI.orgのサイトの右側に表示されます。

Markdown形式の場合は、必ずlong_description_content_typeとセットで記載しましょう。エラーで止まります。

classifiers

PyPIのサイトにある分類です。iniファイルっぽいですが一行に1つずつ書き込めます。分類の内容はPyPIのサイトを確認してください。

install_requires

書き方はclassifiersと同じです。上記の既述はPipfilepyyaml = "*"と等価です。

アップロード

  1. 参考記事に6従い.pypircを作る。Windows 10の場合C:\Users\[UserName]のフォルダに保存する
  2. pip install twine wheelする(いろいろ苦戦していたので他のこともやっていたかもしれない)
  3. python setup.py sdist bdist_wheelする
  4. twine upload --repository testpypi dist/*する
  5. 問題なければtwine upload --repository pypi dist/*する

以下のようなコードをPipfileに作っておくとあとが簡単です(参考文献6の内容はWindowsではないので適宜修正/pipenv runはPowerShellでなくコマンドプロンプトで動作してしまうようなので適宜調整)

Pipfile
[scripts]
clear = "pwsh -c Remove-Item -Recurse -Force *.egg-info, build, dist"
build = "python setup.py sdist bdist_wheel"
deploytest = "twine upload --repository testpypi dist/*"
deploy = "twine upload --repository pypi dist/*"

注意点

この手の操作方法は割と頻繁に変わるので、ググるときは期間指定を「1ヶ月以内」にするなどして対策しましょう。半年以上前の記事だとどこかしら内容が変わっている可能性があります(記述の内容についてはそれ以前の記事も参考になりますが)。

image.png

参考文献

  1. pythonのsetup.pyについてまとめる - Qiita
  2. 【Techの道も一歩から】第21回「setup.pyを書いてpipでインストール可能にしよう」 - Sansan Builders Blog
  3. Python の setup.py の内容を setup.cfg で管理する - astropenguin
  4. PyPIデビューしたい人の為のPyPI登録の手順 - Qiita
  5. __version__の罠とベストプラクティス - Qiita
  6. Python パッケージをローカルから PyPI にアップロードするときの設定 - Qiita
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Python]loggingモジュールざっくり理解

登場人物

image.png

Loggerクラス : loggingの主体のはこいつ。ログの生成から目的の場所まで送るまでを管理する。Loggerクラスから生成されるオブジェクト毎にHandlerやFilterを設定できる。Loggerは階層構造(後述)をとり、継承により親の設定を引き継ぐ。

root (rootLoggerオブジェクト) : デフォルトのLoggerクラスのオブジェクトであり全てのロガーの親。必ず1つ。logging.info()等で書かれるログは全てここに流れる。

独自のLoggerオブジェクト : getLogger("ロガー名")により生成する独自のLoggerクラスのオブジェクト。ファイル毎に作成することでどのファイルのログかが明示的にわかり、それぞれにログレベルの設定やハンドラを設定できる。階層構造を作ることが可能(後述)。

LogRecord : ログ本文。Loggerにより生成される。

Handler : LogRecordをどこに届けるか(標準出力やファイル書込み、HTTPリクエストなど)を管理する。Loggerに指定する。
→ 種々のハンドラ:https://docs.python.org/ja/3/howto/logging.html#useful-handlers

Formatter : ログのフォーマットを指定。Handlerに指定する。

Filter : どのログを出力するかの管理。LoggerもしくはHandlerに指定。

Loggerの階層構造

image.png

Loggingのフロー

上の階層構造のmod2mod2.fugaを用いてロギングのフローを確認する。
https://docs.python.org/ja/3/howto/logging.html#logging-flow

loggingtest.py
import logging
import loggingtest2

logger = logging.getLogger("mod2")
logger.setLevel(logging.DEBUG)

# フォーマット生成
formatter = logging.Formatter('parent: %(name)s [%(levelname)s] %(message)s')

# 標準エラー出力のハンドラ生成
handler = logging.StreamHandler()

# ハンドラへの各種設定
handler.setLevel(logging.ERROR)
handler.setFormatter(formatter)

# ロガーにハンドラをアタッチ
logger.addHandler(handler)

if __name__ == "__main__":
    loggingtest2.test()
loggingtest2.py
import logging

def test():
    logger = logging.getLogger("mod2.fuga")
    logger.setLevel(logging.DEBUG)

    # フォーマット生成
    formatter = logging.Formatter('child: %(name)s [%(levelname)s] %(message)s')

    # ハンドラ生成・設定
    handler = logging.StreamHandler()
    handler.setLevel(logging.WARNING)
    handler.setFormatter(formatter)

    logger.addHandler(handler)
    #logger.propagate = False  #後述

    logger.error("bbb")

この状態で実行すると、親のハンドラからも子のハンドラからも出力される。

$ python loggingtest.py
child: mod2.fuga [ERROR] bbb
parent: mod2.fuga [ERROR] bbb

image.png

他も試すとわかるが、loggingtest2.pyのハンドラの条件を"CRITICAL"以上に設定すればchild:~~は出力されなくなり、getLogger()のドットを外し、階層構造をフラットにすると親だったmod2のハンドラは呼ばれなくなるためchild:~~だけが出力される。

propagate属性

propagateの直訳は「伝播する」であり、この属性はLogRecordを親へ伝播させるかを指定する。
上のコードでlogger.propagate = Falseをアンコメントすることで親が呼ばれないことが確認できる。

ロギングの設定の外だし

loggingに対する設定はlogging.confという別ファイルに記述し、fileConfig()で読み出すことが可能。これによりロギングの設定部分を処理本体から切り離すことができる。詳細は公式ドキュメント参照。

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

[Python] e-GOV法令APIより法令本文を取得する

日本の法令データをe-Gov法令APIより取得し、法令本文を整形する方法についてまとめました。下記Qiita記事を参考にしています。

最後の「まとめ」に登場するクラスを含めて、本記事の各コードはGitHub repositoryからダウンロードできます。

1. きっかけ

自然言語処理の勉強の題材として、仕事でよく確認している省令(J-GCP, 医薬品の臨床試験の実施の基準に関する省令)を使用したかったというのがきっかけです。Twitterの投稿文などとくらべて分量が少ない点は気になりますが、表記ゆれなどが少ないので自然言語処理の題材としても有用ではないかと思いました。

2. 環境

APIへのアクセスにrequests(pip installが必要), XMLデータの解析にxmlパッケージ(標準ライブラリ)を使用します。functools.lru_cacheは関数出力のキャッシュ(APIへのアクセス回数をへらすため)、pprintは辞書やリストをきれいに表示するため、reは正規表現に使用します。

# 標準ライブラリ
from functools import lru_cache
from pprint import pprint
import re
from xml.etree import ElementTree
# pip install requests
import requests
実行環境
OS Windows Subsystem for Linux / Ubuntu
パッケージ管理 pipenv
Python version 3.8.5
requests 2.24.0

3. 法令番号の取得

法令の名前とは別に、「法令番号」という一意なIDが設定されているようです。番号とは言っても単純な連番ではなく、日本語の文字列です...

法令番号(ほうれいばんごう)とは、国家、地方自治体等により公布される各種の法令に対し、識別のため個別に付される番号をいう。一定の期間(暦年など)ごとに番号が初期化される(第1号から始まる)もの、ある特定の期日(独立記念日など)からの通し番号となっているもの等々、各政体によりその番号の管理、運用方法は異なる。
「法令番号」 出典: フリー百科事典『ウィキペディア(Wikipedia)』

法令本文を取得する際に法令番号を使って指定するため、名称から法令番号を検索する方法を確認します。

法令番号の辞書

法令名と法令番号の関係を辞書として取得する関数をまず作ります。

law_number.py
@lru_cache
def get_law_dict(category=1):
    # APIから各法令種別に含まれる法令リストを取得
    url = f"https://elaws.e-gov.go.jp/api/1/lawlists/{category}"
    r = requests.get(url)
    # XMLデータの解析
    root = ElementTree.fromstring(r.content.decode(encoding="utf-8"))
    # 辞書{名称: 法令番号}の作成
    names = [e.text for e in root.iter() if e.tag == "LawName"]
    numbers = [e.text for e in root.iter() if e.tag == "LawNo"]
    return {name: num for (name, num) in zip(names, numbers)}

なお法令種別(category引数)は、4種類設定されています。

  • 1: 全法令
  • 2: 憲法、法律
  • 3: 政令、勅令
  • 4: 府省令

出力例:

pprint(get_law_dict(category=2), compact=True)
# ->
{
    "明治二十二年法律第三十四号(決闘罪ニ関スル件)": "明治二十二年法律第三十四号",
    "保管金規則": "明治二十三年法律第一号",
    "通貨及証券模造取締法": "明治二十八年法律第二十八号",
    "国債証券買入銷却法": "明治二十九年法律第五号",
    "民法": "明治二十九年法律第八十九号",
...
    "新型コロナウイルス感染症等の影響に対応するための国税関係法律の臨時特例に関する法律": "令和二年法律第二十五号",
    "令和二年度特別定額給付金等に係る差押禁止等に関する法律": "令和二年法律第二十七号",
    "防災重点農業用ため池に係る防災工事等の推進に関する特別措置法": "令和二年法律第五十六号"
}

「辞書{名称: 法令番号}の作成」のroot.iter()はXMLデータをElement単位に分割してiterationとして返してくれます。なおroot.getiterator()に置き換えても実行可能ですが、次の通りDeprecationWarningが発生するようです。

DeprecationWarning: This method will be removed in future versions.  Use 'tree.iter()' or 'list(tree.iter())' instead.

また各Elementには.text, .tagというタグが設定されています。.tag"LawName"に一致するElementの.textは名称、.tag"LawNo"に一致するElementの.textは法令番号を保存しているため、下記コードで名称と法令番号の辞書を作成しました。

Python:get_law_dict() function
names = [e.text for e in root.iter() if e.tag == "LawName"]
numbers = [e.text for e in root.iter() if e.tag == "LawNo"]
return {name: num for (name, num) in zip(names, numbers)}

Elementのイメージ:

Elementのイメージ
elements = [
    (e.tag, e.text) for e in root.iter()
    if e.tag in set(["LawName", "LawNo"])
]
pprint(elements[:4], compact=False)
# ->
[('LawName', '歳入歳出予算概定順序'),
 ('LawNo', '明治二十二年閣令第十二号'),
 ('LawName', '予定経費算出概則'),
 ('LawNo', '明治二十二年閣令第十九号')]

名称のキーワード検索

法令の正式名を覚えている場合は少ないと思いますので、キーワード検索できるようにします。

law_number.py
def get_law_number(keyword, category=1):
    """
    Return the law number.
    This will be retrieved from e-Gov (https://www.e-gov.go.jp/)

    Args:
        keyword (str): keyword of the law name
        category (int): category number, like 1 (all), 2 (法令), 3 (政令), 4 (省令)

    Returns:
        dict(str, str): dictionary of law name (key) and law number (value)
    """
    law_dict = get_law_dict(category=category)
    return {k: v for (k, v) in law_dict.items() if keyword in k}

出力例:

法令番号の取得
print(get_law_number("医薬品の臨床試験", category=4))
# ->
{
    '医薬品の臨床試験の実施の基準に関する省令': '平成九年厚生省令第二十八号',
    '動物用医薬品の臨床試験の実施の基準に関する省令': '平成九年農林水産省令第七十五号'
}

目的のJ-GCP(医薬品の臨床試験の実施の基準に関する省令)の法令番号が"平成九年厚生省令第二十八号"と判明しました。

4. 法令本文の取得

法令番号をAPIに送り、本文を取得します。XMLを解析して本文を取得して余計な空白や空行を削除します。

law_contents.py
@lru_cache
def get_raw(number):
    """
    Args:
        number (str): Number of the law, like '平成九年厚生省令第二十八号'

    Returns:
        raw (list[str]): raw contents of J-GCP
    """
    url = f"https://elaws.e-gov.go.jp/api/1/lawdata/{number}"
    r = requests.get(url)
    root = ElementTree.fromstring(r.content.decode(encoding="utf-8"))
    contents = [e.text.strip() for e in root.iter() if e.text]
    return [t for t in contents if t]

出力例:

gcp_raw = get_raw("平成九年厚生省令第二十八号")
pprint(gcp_raw, compact=False)
# ->
[
    "0",
    "平成九年厚生省令第二十八号",
...
    "目次",
...
    "第一章 総則",
    "(趣旨)",
    "第一条",
    "この省令は、被験者の人権の保護、安全の保持及び福祉の向上を図り、治験の科学的な質及び成績の信頼性を確保するため、医薬品、医療機器等の品質、有効性及び安全性の確保等に関する法律(以下「法」という。)第十四条第三項(同条第九項及び法第十九条の二第五項において準用する場合を含む。以下同じ。)並びに法第十四条の四第四項及び第十四条の六第四項(これらの規定を法第十九条の四において準用する場合を含む。以下同じ。)の厚生労働省令で定める基準のうち医薬品の臨床試験の実施に係るもの並びに法第八十条の二第一項、第四項及び第五項に規定する厚生労働省令で定める基準を定めるものとする。",
    "(定義)",
    "第二条",
...
    "附 則",
    "(施行期日)",
    "第一条",
    "この省令は、平成三十年四月一日から施行する。"
]

5. 本文の整形

句点で終わる行のみを取り出して結合します。また括弧内の文字列(例:「薬事法 (昭和三十五年法律第百四十五号)」)や「」を除去します。
さらにJ-GCPの場合は、第56条は言葉の読み替えに関する内容が主で解析には使用したくないため、除去しています。

law_contents.py
def preprocess_gcp(raw):
    """
    Perform pre-processing on raw contents of J-GCP.

    Args:
        raw (list[str]): raw contents of J-GCP

    Returns:
        str: pre-processed string of J-GCP

    Notes:
        - Article 56 will be removed.
        - Strings enclosed with ( and ) will be removed.
        - 「 and 」 will be removed.
    """
    # Remove article 56
    # contents = raw[:]
    contents = raw[: raw.index("第五十六条")]
    # Select sentenses
    contents = [s for s in contents if s.endswith("。")]
    # Join the sentenses
    gcp = "".join(contents)
    # 「 and 」 will be removed
    gcp = gcp.translate(str.maketrans({"「": "", "」": ""}))
    # Strings enclosed with ( and ) will be removed
    return re.sub("([^(|^)]*)", "", gcp)

出力例:

J-GCPの整形
gcp = preprocess_gcp(gcp_raw)
# ->
"薬事法第十四条第三項、第十四条の四第四項並びに第十四条の五第四項、
第八十条の二第一項、第四項及び第五項並びに第八十二条の規定に基づき、
医薬品の臨床試験の実施の基準に関する省令を次のように定める。
この省令は、被験者の人権の保護、安全の保持及び福祉の向上を図り、
治験の科学的な質及び成績の信頼性を確保するため、医薬品、医療機器等の品質、
有効性及び安全性の確保等に関する法律...
当該治験への参加について文書により同意を得なければならない。"

第56条を削除する部分については、他の法令の場合はcontents = raw[:]などに置き換えてください。

6. まとめ

クラスにまとめました。

law_all.py
class LawLoader(object):
    """
    Prepare law data with e-Gov (https://www.e-gov.go.jp/) site.

    Args:
        category (int): category number, like 1 (all), 2 (法令), 3 (政令), 4 (省令)
    """

    def __init__(self, category=1):
        self.law_dict = self._get_law_dict(category=category)
        self.content_dict = {}

    @staticmethod
    def _get_xml(url):
        """
        Get XML data from e-Gov API.

        Args:
            url (str): key of the API

        Returns:
            xml.ElementTree: element tree of the XML data
        """
        r = requests.get(url)
        return ElementTree.fromstring(r.content.decode(encoding="utf-8"))

    def _get_law_dict(self, category):
        """
        Return dictionary of law names and numbers.

        Args:
            category (int): category number, like 1 (all), 2 (法令), 3 (政令), 4 (省令)

        Returns:
            dict(str, str): dictionary of law names (keys) and numbers (values)
        """
        url = f"https://elaws.e-gov.go.jp/api/1/lawlists/{category}"
        root = self._get_xml(url)
        names = [e.text for e in root.iter() if e.tag == "LawName"]
        numbers = [e.text for e in root.iter() if e.tag == "LawNo"]
        return {name: num for (name, num) in zip(names, numbers)}

    def get_law_number(self, keyword, category=1):
        """
        Return the law number.
        This will be retrieved from e-Gov (https://www.e-gov.go.jp/)

        Args:
            keyword (str): keyword of the law name
            category (int): category number, like 1 (all), 2 (法令), 3 (政令), 4 (省令)

        Returns:
            dict(str, str): dictionary of law name (key) and law number (value)
        """
        return {k: v for (k, v) in self.law_dict.items() if keyword in k}

    def get_raw(self, number):
        """
        Args:
            number (str): Number of the law, like '平成九年厚生省令第二十八号'

        Returns:
            raw (list[str]): raw contents of J-GCP
        """
        if number in self.content_dict:
            return self.content_dict[number]
        url = f"https://elaws.e-gov.go.jp/api/1/lawdata/{number}"
        root = self._get_xml(url)
        contents = [e.text.strip() for e in root.iter() if e.text]
        raw = [t for t in contents if t]
        self.content_dict = {number: raw}
        return raw

    @staticmethod
    def pre_process(raw):
        """
        Perform pre-processing on raw contents.

        Args:
            raw (list[str]): raw contents

        Returns:
            str: pre-processed string

        Notes:
            - Strings enclosed with ( and ) will be removed.
            - 「 and 」 will be removed.
        """
        contents = [s for s in raw if s.endswith("。")]
        string = "".join(contents)
        string = string.translate(str.maketrans({"「": "", "」": ""}))
        return re.sub("([^(|^)]*)", "", string)

    def gcp(self):
        """
        Perform pre-processing on raw contents of J-GCP.

        Args:
            raw (list[str]): raw contents of J-GCP

        Returns:
            str: pre-processed string of J-GCP

        Notes:
            - Article 56 will be removed.
            - Strings enclosed with ( and ) will be removed.
            - 「 and 」 will be removed.
        """
        number_dict = self.get_law_number("医薬品の臨床試験")
        number = number_dict["医薬品の臨床試験の実施の基準に関する省令"]
        raw = self.get_raw(number)
        raw_without56 = raw[: raw.index("第五十六条")]
        return self.pre_process(raw_without56)

使い方:

LawLoaderの使い方
# The Constitution of Japan
loader2 = LawLoader(category=2)
consti_number = loader2.get_law_number("日本国憲法")
print(consti_number)
consti_raw = loader2.get_raw("昭和二十一年憲法")
consti = loader2.pre_process(consti_raw)
# J-GCP:データ整形を含めてメソッドとして登録済
loader4 = LawLoader(category=4)
gcp = loader4.gcp()

7. あとがき

自然言語処理の題材として、日本の法令をダウンロードして整形しました。

お疲れさまでした!

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

Codeforces Round #521 (Div. 3) バチャ復習(10/9)

今回の成績

スクリーンショット 2020-10-10 13.09.33.png

今回の感想

F1までは解けるべき問題でしたが、DPをバグらせてしまいました。基本の方針はあっていたのですが、遷移先を固定するか遷移元を固定するかで迷ったりインデックスでミスがあったりしたので、反省です。

A問題

ABCのC問題で見そうな問題でした。
$a$で進んで$b$で戻る操作を一つと見ると、この操作は$[\frac{k}{2}]$回行います。また、$k$の場合は最後にもう一回$a$だけ進むので以上の合計を求めれば良いです。

A.py
for _ in range(int(input())):
    a,b,k=map(int,input().split())
    ans=(a-b)*(k//2)
    if k%2==0:
        print(ans)
    else:
        print(ans+a)

B問題

$101$という文字列が現れないようにします。このような問題はまずは貪欲法を考えます。

例えば、左から$101$になりうる文字を順に見ていきます。すなわち、$i$=1~$n-2$で$a[i-1:i+2]="101"$となるものです。この時、0の両隣りのいずれかの1を0に変えてやる必要がありますが、左側はすでに$101$になるものはないので、右側の1を0に変えるのが最適でこれを繰り返しながら0に何回変えたかを数えます。

B.py
n=int(input())
a=list(map(int,input().split()))
ans=0
for i in range(1,n-1):
    if a[i-1]==1 and a[i]==0 and a[i+1]==1:
        a[i+1]=0
        ans+=1
print(ans)

C問題

方針はすぐにたったのですが、謎にバグを埋め込みました。

まず、$a[j]$を除いたとします(除いて残った数列を$b$とします)。この時、$sum(a)-a[j]=2 \times b_{max}$が成り立てば良いです。また、$b_{max}$として$a[j]$以外は選ぶことができます。ここで、$sum(a)$は事前計算で求められ、$a[j]$は与えられるので、$a[j]$が与えられた時に$b_{max}$を高速に求めることを考えます。まず、$a_{max}$となる要素が二つ以上になる時は常に$b_{max}=a_{max}$なので、任意の$j$についてそれぞれ$O(1)$で判定することができます。次に、$a_{max}$となる要素が一つのみの時は2番目に大きい要素も保存しておけば、$a[j]=a_{max}$となる$a[j]$を除く時のみ$b_{max}=$(2番目に大きい要素)とすれば良いです。この時も$O(1)$での判定ができます。

判定することができたのですが、最大の要素と2番目の要素を求める際にソートをしてしまったので、求めるインデックスが題意と異なるものとなってしまいます。したがって、先ほどの判定で題意を満たすような値を$set$にとりあえず格納して最後に$set$に含まれる要素のインデックスを求めるようにしました。

C.py
n=int(input())
b=list(map(int,input().split()))
a=sorted(b)
s=sum(a)
ans=set()
if a[-2]==a[-1]:
    ma=a[-1]
    for i in range(n):
        if s-a[i]==2*ma:
            ans.add(a[i])
else:
    ma1,ma2=a[-1],a[-2]
    for i in range(n):
        if i==n-1 and s-a[i]==2*ma2:
            ans.add(a[i])
        if i!=n-1 and s-a[i]==2*ma1:
            ans.add(a[i])
realans=[]
for i in range(n):
    if b[i] in ans:
        realans.append(str(i+1))
print(len(realans))
print(" ".join(realans))

D問題

実装をミスって焦りかけました。

カットをしますが、順番は考慮しないので、それぞれの数がいくつずつ出てくるかを辞書$c$に管理しておきます。この時、何回カットするかわからないとどのように要素を$t$に含めればわかりません。したがって、$x$回だけカットすると仮定して議論を進めます。ここで、$c$にある数$i$が$c[i]$だけ含まれていたとすると、$t$には$\frac{c[i]}{x}$だけの$i$を含めることができます。よって、辞書に管理される任意の$i$についてこれを確かめれば、$x$回カットするときの最長の$t$を求めることができます。ここで、$t$の最長の長さが$k$以上であれば題意の$t$を構成することができます。さらに、カットする回数を増やすことで$t$の長さは単調減少し、($x$回カットする時の$t$の最長の長さ)$\geqq k$となるような中で最大の$x$を探したいので、二分探索により求めます(✳︎)。また、求まった$x$のときの$t[:k]$を出力すれば良いです(要素は$k$個だけ出力しなければならないのに注意が必要です,ここで1WA)。

(✳︎)…二分探索はこの記事を参考に作成します。$l,r$の初期値及び今回は最大値を求めたいので$l,r$の役割が反対になることに注意が必要です。

D.py
n,k=map(int,input().split())
s=list(map(int,input().split()))
from collections import Counter
c=Counter(s)

z=dict()
def f(x):
    global n,k,c,z
    if x>n:return False
    if x==0:return True
    ret=0
    z=dict()
    for i in c:
        ret+=c[i]//x
        z[i]=c[i]//x
    #print(x,ret)
    return ret>=k

#l:True,r:False
l,r=0,10**6
while l+1<r:
    y=l+(r-l)//2
    if f(y):
        l=y
    else:
        r=y
f(l)
#print(z)
ans=[]
for i in z:
    for j in range(z[i]):
        ans.append(str(i))
print(" ".join(ans[:k]))

E問題

C,D,Eのいずれも実装ミスで時間がかかってしまいました。精進します。また、この問題ではTLがキツそうでC++で出しましたが、間違っていない選択でした。

初めは問題を誤読していてサンプルを見て気づきました。$pairwise \ distinct$なので、それぞれのコンテストは同じトピックの問題しか扱えないかつ、あるトピックの問題は一つのコンテストにしか含まれません。

よって、それぞれのトピックの問題がいくつずつあるかをカウントしてmapに一旦記録します。また、トピックの番号は必要ないので、問題の個数のみを配列$a$に格納します($a$はソートしておきます,理由は後述します。)。

初めの数が決まらないとそれ以降の数が定まらないので、初めの数を$x$とします。また、$x$は1以上$2 \times 10^5$以下です。この時、$x,2x,4x…$の問題数がそれぞれのコンテストを開くのに必要です。また、それぞれのトピックの問題数は最大でも$10^5$なので、コンテストの開催日数は最大でも$20$日程度($\because \ 10^5<2^{20}$)であることに気づきました。さらに、問題数が$y$のコンテストを開く時、$y$以上で最小の問題数のトピックを選ぶことでそれ以降でできるだけ多くのコンテストを開けるのは自明です。したがって、先ほどの$a$をソートしておけば$a$で$lower$_$bound$を用いて$x→2x→4x→…$の問題数のトピックが存在するかを順に数えていけます。また、$lower$_$bound$の返り値が$end$の場合はそれ以降のコンテストを開くことができないので数え上げを終了します。さらに、すでに選んでしまったコンテストの問題は選べないので、どのトピックまで選んだかを$nxt$という変数に格納しておきます。

以上の方針により、それぞれの$x$でコンテストが何日開催でき何問含まれるかは$O((\log{n})^2)$で求められるので、全体の計算量は$O(n (\log{n})^2)$となります。PyPyの場合は$O(n \log{n})$であっても心配なくらいなので、初めからC++で書きました。

E.cc
//デバッグ用オプション:-fsanitize=undefined,address

//コンパイラ最適化
#pragma GCC optimize("Ofast")

//インクルードなど
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

//マクロ
//forループ
//引数は、(ループ内変数,動く範囲)か(ループ内変数,始めの数,終わりの数)、のどちらか
//Dがついてないものはループ変数は1ずつインクリメントされ、Dがついてるものはループ変数は1ずつデクリメントされる
//FORAは範囲for文(使いにくかったら消す)
#define REP(i,n) for(ll i=0;i<ll(n);i++)
#define REPD(i,n) for(ll i=n-1;i>=0;i--)
#define FOR(i,a,b) for(ll i=a;i<=ll(b);i++)
#define FORD(i,a,b) for(ll i=a;i>=ll(b);i--)
#define FORA(i,I) for(const auto& i:I)
//xにはvectorなどのコンテナ
#define ALL(x) x.begin(),x.end() 
#define SIZE(x) ll(x.size()) 
//定数
#define INF 1000000000000 //10^12:∞
#define MOD 1000000007 //10^9+7:合同式の法
#define MAXR 100000 //10^5:配列の最大のrange
//略記
#define PB push_back //挿入
#define MP make_pair //pairのコンストラクタ
#define F first //pairの一つ目の要素
#define S second //pairの二つ目の要素

signed main(){
    //入力の高速化用のコード
    //ios::sync_with_stdio(false);
    //cin.tie(nullptr);
    ll n;cin>>n;
    map<ll,ll> b;
    REP(i,n){
        ll z;cin>>z;
        b[z-1]++;
    }
    vector<ll> a;
    FORA(i,b){
        if(i.S!=0)a.PB(i.S);
    }
    sort(ALL(a));
    //REP(i,SIZE(a))cout<<a[i]<<endl;
    ll ans=0;
    FOR(x,1,200000LL){
        //nxt以降から探す
        ll ans_sub=0;
        ll y=x;
        auto nxt=a.begin();
        while(true){
            if(nxt==a.end())break;
            nxt=lower_bound(nxt,a.end(),y);
            if(nxt==a.end())break;
            nxt++;
            ans_sub++;
            y*=2;
        }
        ans=max(ans,x*(1LL<<ans_sub)-x);
    }
    cout<<ans<<endl;
}

F1問題

(問題文が読みにくかったです。)

見た瞬間にDPっぽさはわかると思います。この時、$k$個連続したところに少なくとも一つrepostする要素($\leftrightarrow$ある要素よりも右(左も同様)にある中で最も近い要素との距離が$k$以下かつ,端との距離が$k-1$以下)が必要であることから最後にrepostした要素の情報が必要で、最終的にrepostした要素の数が$x$であることから$repost$した要素の数の情報も必要であると気づきました。したがって、以下のようなDPをおきます。

$dp[i][j][l]:=$($i$番目の要素まででrepostした要素の個数が$j$個で最後に選んだのが$l$番目の要素であった時のrepostした要素の合計の最大値)

次に、遷移を考えます。$dp$の値は-1で初期化(-INFに当たります)し、$dp[0][1][0]$のみ$a[0]$にします。

遷移はその要素を選ぶか選ばないかで考えます。遷移元を$dp[i][j][l]$と決めた元で$i+1$番目の要素を選ぶかどうかでの遷移先を考えます。また、以下は$dp[i][j][l]!=-1$の時に行います。

①その要素を選ぶ時

$i=$0~$k-2$のときは$l$の値によらず選ぶことができるので、$j!=x$であれば選べます。$i=k-1$~$n-2$のときは$j!=x$に加えて$(i+1)-l \leqq k$であることが必要です。

$dp[i+1][j+1][i+1]=max(dp[i+1][j+1][i+1],dp[i][j][l]+a[i+1])$

加えて、$i=$0~$k-2$のときは初めて$i+1$番目の要素を選ぶ場合が必要で

$dp[i+1][1][i+1]=a[i+1]$

を最初に行っておけば良いです。

②その要素を選ばない時

選ばないときは条件はなく、$i$が+1されるだけです。

$dp[i+1][j][l]=max(dp[i+1][j][l],dp[i][j][l])$

上記を行うとdpが完了します。また、答えを求める際に任意の$l$で最大の$dp[n-1][x][l]$を求めるとしたいですが、$n-1-l \leqq k \leftrightarrow l \geqq n-1-k$であることが必要なので注意しましょう。

F1.cc
//デバッグ用オプション:-fsanitize=undefined,address

//コンパイラ最適化
#pragma GCC optimize("Ofast")

//インクルードなど
#include<bits/stdc++.h>
using namespace std;
typedef long long ll;

//マクロ
//forループ
//引数は、(ループ内変数,動く範囲)か(ループ内変数,始めの数,終わりの数)、のどちらか
//Dがついてないものはループ変数は1ずつインクリメントされ、Dがついてるものはループ変数は1ずつデクリメントされる
//FORAは範囲for文(使いにくかったら消す)
#define REP(i,n) for(ll i=0;i<ll(n);i++)
#define REPD(i,n) for(ll i=n-1;i>=0;i--)
#define FOR(i,a,b) for(ll i=a;i<=ll(b);i++)
#define FORD(i,a,b) for(ll i=a;i>=ll(b);i--)
#define FORA(i,I) for(const auto& i:I)
//xにはvectorなどのコンテナ
#define ALL(x) x.begin(),x.end() 
#define SIZE(x) ll(x.size()) 
//定数
#define INF 1000000000000 //10^12:∞
#define MOD 1000000007 //10^9+7:合同式の法
#define MAXR 100000 //10^5:配列の最大のrange
//略記
#define PB push_back //挿入
#define MP make_pair //pairのコンストラクタ
#define F first //pairの一つ目の要素
#define S second //pairの二つ目の要素

signed main(){
    //入力の高速化用のコード
    //ios::sync_with_stdio(false);
    //cin.tie(nullptr);
    ll n,k,x;cin>>n>>k>>x;
    vector<ll> a(n);REP(i,n)cin>>a[i];
    if(k==1){
        if(x!=n){
            cout<<-1<<endl;
        }else{
            cout<<accumulate(ALL(a),0LL)<<endl;
        }
        return 0;
    }
    //なぜか初期化が
    //ダメな場合は-1
    vector<vector<vector<ll>>> dp(n,vector<vector<ll>>(x+1,vector<ll>(n,-1)));
    //初期化(iを選ぶか)
    dp[0][1][0]=a[0];
    REP(i,k-1){
        //初めて選ぶ場合
        dp[i+1][1][i+1]=a[i+1];
        REP(j,x+1){
            REP(l,n){
                if(true){
                    if(dp[i][j][l]!=-1){
                        //選ぶ場合
                        if(j!=x){
                            dp[i+1][j+1][i+1]=max(dp[i+1][j+1][i+1],dp[i][j][l]+a[i+1]);
                        }
                        //選ばない場合
                        dp[i+1][j][l]=max(dp[i+1][j][l],dp[i][j][l]);
                    }
                }
            }
        }
    }
    //遷移
    FOR(i,k-1,n-2){
        REP(j,x+1){
            REP(l,n){
                if(true){
                    if(dp[i][j][l]!=-1){
                        //選ぶ場合
                        if(j!=x and (i+1)-l<=k){
                            dp[i+1][j+1][i+1]=max(dp[i+1][j+1][i+1],dp[i][j][l]+a[i+1]);
                        }
                        //選ばない場合
                        dp[i+1][j][l]=max(dp[i+1][j][l],dp[i][j][l]);
                    }
                }
            }
        }
    }
    //ここの範囲
    ll ans=-1;
    FOR(l,n-1-(k-1),n-1){
        ans=max(ans,dp[n-1][x][l]);
    }
    cout<<ans<<endl;
}

F2問題

今回は飛ばします。

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

ARC067 C - Factors of Factorial

問題

整数$N$が与えられたときの,$N!$の正の約数の個数を$10^9+7$で割った余りを求める.

考え方

$N!$の素因数を{$ p_0,p_1,...,p_k$}とする.また,各素因数の個数をそれぞれ,{$i_0, i_1,...,i_k$}とする.この時,$N!$は以下のように表される:
$N! = p_0^{i_0} \cdot p_1^{i_1} \cdot ... \cdot p_k^{i_k}$.

従って,$N!$の約数の個数は,
$(i_0+1) \cdot (i_1+1) \cdot ... \cdot (i_k+1)$
となる.

以上のことから,$N!$の各素因数の個数を求めればその約数の個数が求められるということが分かる.

実装

ここでは,各素因数の数を数えるために,
$(N!に含まれる素因数pの個数)= \Sigma_{k=1}^{ \infty } \left[ \frac{n}{p^k} \right ] = \left[ \frac{n}{p^1} \right ] + \left[ \frac{n}{p^2} \right ] + ...$
を利用する(この定理の詳細はこちら).

import math
N = math.factorial(int(input()))

res = 1
p = 2  # チェックする因数

# p<=sqrt(N)を満たす因数を調査すれば十分
while p*p <= N:
    i = 1
    # p^k(k=1~)の倍数の個数を数えて足し合わせていく
    while(N % p == 0):
        i += 1
        N //= p

    # N!の約数の個数は i_0*i_1*...*i_k
    res *= i
    p += 1

if(N != 1):
    res *= 2
print(res % (10**9+7))

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

【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その4~

はじめに

みなさん、初めまして。
Djangoを用いた投票 (poll) アプリケーションの作成過程を備忘録として公開していこうと思います。
Qiita初心者ですので読みにくい部分もあると思いますがご了承ください。

シリーズ

作業開始

簡単なフォームを書く

HTMLでフォームを書いていきます。

投票ビュー

formタグの説明

  • action:データの送信先URL
  • method:データの送信方法(get,post)

inputタグの説明

  • type:ラジオボタン(radio)、送信ボタン(submit)
  • value:現在の値
  • id:ボタンを識別するID

「{% csrf_token %}」はDjangoが用意しているクロスサイトリクエストフォージェリ対策の仕組みです。
POSTメソッドを使用しているのでcsrf_tokenを使います。

polls/template/polls/detail.html
<h1>{{ question.question_text }}</h1>
{% if error_message %}<p><strong>{{error_message}}</strong></p>{% endif %}
<form action="{% url 'polls:vote' question.id %}" method="post">
{% csrf_token %}
{% for choice in question.choice_set.all %}
    <input type="radio" name="choice" id="choice{{ forloop.counter }}" value="{{ choice.id }}"
    <label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br>
{% endfor %}
<input type="submit" value="Vote"
</form>

「request.POST['choice']」でPOSTデータのchoiceにアクセスします。
POSTデータにchoiceがない場合は、KeyErrorを返します

exceptionが発生した場合は、再度質問フォームをエラー付きで表示します。

「reverse('polls:results', args=(question.id))」はpolls/urls.py > path関数(name='results')を返します。

:polls/views.py

from django.http import HttpResponse, Http404, HttpResponseRedirect
from .models import Question, Choice
from django.shortcuts import render, get_object_or_404
from django.urls import reverse

def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html',
                      {'question': question,
                       'error_message': "You didn't select a choice."})
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

http://127.0.0.1:8000/polls/<質問ID>」にアクセスしてフォーム画面が表示されればOKです。

image.png

結果ビュー

polls/template/polls/results.html
<h1>{{ question.question_text }}</h1>

<ul>
{% for choice in question.choice_set.all %}
    <li>{{ choice.choice_text }} -- {{ choice.votes }} vote{{ choice.votes|pluralize }}</li>
{% endfor %}
<ul>

<a href="{% url 'polls:detail' question.id %}">Vote again?</a>
polls/views.py
from django.http import Http404, HttpResponseRedirect

def results(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    return render(request, 'polls/results.html', {'question': question})

http://127.0.0.1:8000/polls/<質問ID>」のフォーム画面で選択肢を選択してVoteボタンを押すと以下の画面に遷移します。
image.png

汎用ビューを使う: コードが少ないのはいいことだ

コードの縮小化を行っていきます。特に、冗長部分を排除していきます。

これらのビューは基本的な Web開発の一般的なケースを表します。すなわち、 URL を介して渡されたパラメータに従ってデータベースからデータを取り出し、テンプレートをロードして、レンダリングしたテンプレートを返します。これはきわめてよくあることなので、 Django では、汎用ビュー(generic view)というショートカットを提供しています
汎用ビューとは、よくあるパターンを抽象化して、 Python コードすら書かずにアプリケーションを書き上げられる状態にしたものです。

URLconf の修正

name=detail、resultsのみ、「int:questiopkn_id」→「int:pk」にします。これは後ほど説明するDetailViewを使用するためです。
name=index、detail、resultsは後ほどclassを作成するので「views.**View.as_view()」としましょう

polls/urls.py
    path('', views.IndexView.as_view(), name='index'),
    path('<int:pk>/', views.DetailView.as_view(), name='detail'),
    path('<int:pk>/results/', views.ResultsView.as_view(), name='results'),
    path('<int:questiopkn_id>/vote/', views.vote, name='vote'),

views の修正

views.pyは大きく変わります。
defで関数を多用していましたが、classを使います。

  • ListView汎用ビューは"<アプリ名>/<モデル名>list.html"テンプレートを使い、"<モデル名>_list"コンテキスト変数を渡してレタリングを行います。
    template_name属性を用いると、指定したテンプレートを使うようになります。
    context
    object_name属性を用いると、指定したコンテキスト変数を使うようになります。

  • DetailView汎用ビューは"<アプリ名>/<モデル名>detail.html"テンプレートを使い、"<モデル名>"コンテキスト変数を渡してレタリングを行います。
    template_name属性を用いると、指定したテンプレートを使うようになります。
    context_object
    name属性を用いると、指定したコンテキスト変数を使うようになります。
    urls.pyで指定したように、"pk" という名前で URL からプライマリキーを読み取る仕組みになっています。

polls/views.py
# Create your views here.
from django.http import HttpResponseRedirect
from django.shortcuts import render, get_object_or_404
from django.urls import reverse
from django.views import generic

from .models import Question, Choice


class IndexView(generic.ListView):
    template_name = 'polls/index.html'
    context_object_name = 'latest_question_list'

    def get_queryset(self):
        return Question.objects.order_by('-pub_date')[:5]


class DetailView(generic.DetailView):
    model = Question
    template_name = 'polls/detail.html'


class ResultsView(generic.DetailView):
    model = Question
    template_name = 'polls/results.html'


def vote(request, question_id):
    question = get_object_or_404(Question, pk=question_id)
    try:
        selected_choice = question.choice_set.get(pk=request.POST['choice'])
    except (KeyError, Choice.DoesNotExist):
        return render(request, 'polls/detail.html', {
            'question': question,
            'error_message': "You didn't select a choice.",
        })
    else:
        selected_choice.votes += 1
        selected_choice.save()
        return HttpResponseRedirect(reverse('polls:results', args=(question.id,)))

今日はここまでにします。ありがとうございました。

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

Codeforces Round #609 (Div. 2) バチャ復習(10/8)

今回の成績

スクリーンショット 2020-10-09 19.29.01.png

今回の感想

C問題までは早かったのですが、そこからが…。
D問題が長すぎて読む気が失せたのは頑張りましょうと言うしかないのですが、1時間半あってE問題を通せなかったのはかなり反省です。E問題にて反省ポイントを書き出しているので、以後気を付けます。

A問題

$d$が被っているので、どちらに振り分けるのが最適かを考えます。
この時、$e>f$であれば一つ目のタイプに振り分けるのが最適で、$e<f$であれば二つ目のタイプに振り分けるのが最適です。$e=f$の時はどちらに振り分けても良いです。

よって、$e>f$,$e<f$で場合分けして考えれば以下のようになります。

(1)$e>f$の場合
一つ目のタイプを選択するのが最適で、$m=min(a,d)$だけ選択できます。また、$d$の残りは$d-m$となるので、二つ目のタイプは$min(b,c,d-m)$だけ選択できます。

(2)$e<f$の場合
一つ目のタイプを選択するのが最適で、$m=min(b,c,d)$だけ選択できます。また、$d$の残りは$d-m$となるので、二つ目のタイプは$min(a,d-m)$だけ選択できます。

A.py
a,b,c,d,e,f=[int(input()) for i in range(6)]
if e<f:
    m=min(b,c,d)
    ans=m*f
    b-=m
    c-=m
    d-=m
    ans+=min(a,d)*e
else:
    m=min(a,d)
    ans=m*e
    a-=m
    d-=m
    ans+=min(b,c,d)*f
print(ans)

B問題

(以下では黒色をB、白色をWとします。)

最大で$3n$回できるので、都合良く構築することを考えます。また、全ての色が同じになるように操作を行いますが、Bに統一することを考えます。また、小さい$i$から順に$i$番目がWの時のみ$i,i+1$番目を反転させる操作を行えば、常に$i$番目以前はBであることを保ちながら操作を行うことができます。この操作の終了時は$BB…BB,BB…BBW$の二通りになります。前者の場合はすでに色が統一されているのでここまでの操作を出力します。後者の場合はBが偶数のときはBを適当に選択することで全体をWにすることができます。また、後者でBが奇数の場合は無理です($\because$BもWも変化量が偶数であるからです)。

また、上記は高々$2n$回なので条件を満たします。

B.py
n=int(input())
s=list(input())
ans=[]
if s[0]=="W":
    ans.append(0)
    s[0]="B"
    if s[1]=="W":
        s[1]="B"
    else:
        s[1]="W"
for i in range(1,n-1):
    if s[i]=="W":
        s[i]="B"
        if s[i+1]=="W":
            s[i+1]="B"
        else:
            s[i+1]="W"
        ans.append(i)
if s!=["B" for i in range(n)] and n%2==0:
    print(-1)
    exit()
if s!=["B" for i in range(n)]:
    for i in range(n-1):
        if i%2==0:
            ans.append(i)

print(len(ans))
print(" ".join(map(str,[i+1 for i in ans])))

C問題

IMG_D21C987453CD-1.jpeg

上記の四つのいずれかの点を経由して$(s_x,s_y)$にたどり着くことができ、ある一点を通るときに他の点は通らないので、この四点のいずれかにテントをおけば良いです。

最短距離でたどり着くことを考慮すれば、

(1)Aを通る家の座標→$y$座標が$s_y+1$以上
(2)Bを通る家の座標→$x$座標が$s_x+1$以上
(3)Cを通る家の座標→$y$座標が$s_y-1$以下
(4)Dを通る家の座標→$x$座標が$s_x-1$以下

を満たせば良いので、それぞれを数え上げたときの最大値を求めます。

C.py
import sys
input=sys.stdin.readline
n,sx,sy=map(int,input().split())
a,b,c,d=0,0,0,0
for i in range(n):
    x,y=map(int,input().split())
    if x<=sx-1:
        a+=1
    elif x>=sx+1:
        b+=1
    if y<=sy-1:
        c+=1
    elif y>=sy+1:
        d+=1
m=max(a,b,c,d)
print(m)
if a==m:
    print(sx-1,sy)
elif b==m:
    print(sx+1,sy)
elif c==m:
    print(sx,sy-1)
else:
    print(sx,sy+1)

D問題

問題が読みにくかったです。今回は飛ばします。

E問題

この問題における考察の問題点は主に以下の三つになります。

(1)実験が適当
→"〇〇な性質を見つけたいので△△する"ではなく、"わからないのでとりあえず△△する"としてしまっている
(2)方針の切り捨て方が適当
勘で違うのか論理的に違うのかを区別できていない
(3)メモが汚い
考察の方向性がバラバラになる

上記を確認しつつ解説を書いていきます。


それぞれの数のパスに$z$が含まれているかを数え上げるのは難しいので、パスに$z$を含むような数は何かを考えます

例えば、$z=1$として実験をすると以下のようになります。…(3)

IMG_CB9C22F03453-1.jpeg

放射状に数が出てくることはわかりますが、$z=1$の場合だけを考えても法則性がつかめないので、$z=4$の場合を考えて$z=4$を含むパスを持つような数を小さい方から順に考えます。…(1)すると、4~5→8~11→16~23→…といった数はパスに$z$を含むことがわかります。これを一般化すると、$[z,z+1],[2z,2z+3],[4z,4z+7]…$に含まれる数は$z$を含むパスを持つことがわかります(✳︎)。また、$z$が偶数の場合は上記の通りになりますが、奇数の場合は2倍して偶数にしてから数え上げを行います。

よって、$z$をパスに含む数が$n$までにいくつあるか数えても区間の数が$\log{n}$の定数倍程度なので、$z$をパスに含む数は$O(\log{n})$で数え上げることができます。さらに、$[z,z+1],[2z,2z+3],[4z,4z+7]…$より、$z$が小さいほどより多くの数がパスに$z$を含むので単調性を持ちます。また、偶数と奇数で数え上げの仕方が異なるので、偶数と奇数それぞれに対しての二分探索を行う必要があります

以上より、$z$をパスに含む数の数え上げの関数を$calc(z)$(返すのは数え上げてものが$k$以上かのbool値)とすれば、$calc(z)$は$z$が小さい時にTrueで大きい時にFalseなので、Trueとなる最大の$z$を二分探索により求めます。偶奇に場合分けする部分が少し面倒ですがこの記事を参考にすれば実装は難しくないです。境界の値$l,r$の定義だけ気をつける必要があります。また、先ほどの記事は最小値を探しており今回は最大値を探しているので、$l,r$の役目を全て入れ替える必要があることに注意が必要です。

(✳︎)…証明は省略しますが、$z$が偶数のときは$[z,z+1]$という区間の数を表現できるのは明らかなので、ここから再帰的に数を求めていけばわかると思います。

E.py
n,k=map(int,input().split())
def calc(z):
    global n
    if z>n:
        return False
    if z==0:
        return True
    if z%2==1:
        ans=1
        now=2*z
        co=1
        if k==1:
            return True
        elif now>n:
            return False
    else:
        ans=0
        now=z
        co=1
    #print(ans,now,co)
    while True:
        #print(n,now+2*co-1,now)
        #print(n-(now)+1)
        if n<=now+2*co-1:
            ans+=max(0,n-(now)+1)
            #print(ans)
            break
        else:
            now*=2
            co*=2
            ans+=(co)
            #print(ans)
    #print(ans)
    return ans>=k
realans=[]
#odd(2x-1)
l=1
r=n//2+2
while l+1<r:
    y=l+(r-l)//2
    if calc(2*y-1):
        l=y
    else:
        r=y
realans.append(2*l-1)
#even(2x)
l=0
r=n//2+2
while l+1<r:
    y=l+(r-l)//2
    if calc(2*y):
        l=y
    else:
        r=y
realans.append(2*l)
print(max(realans))

F問題

今回は飛ばします

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

ディレクトリ内のファイル数を調べる

DIR = './dataset'

print(sum(os.path.isfile(os.path.join(DIR, name)) for name in os.listdir(DIR)))

最初、下のように書いていたのですが、@shiracamusさんに

リストを作るとメモリを大量消費するので、sum関数でbool値を合計するといいですよ。

とコメントいただきました。ありがとうございます!

↓メモリ大量消費するやつ。

DIR = './dataset'
print(len([name for name in os.listdir(DIR) if os.path.isfile(os.path.join(DIR, name))]))

2933

?


お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

ヨドバシドットコム購入明細メールの商品一覧から商品名と価格を抜き出す

表題の通り、ヨドバシドットコムの購入明細メールから、商品名と価格の組み合わせを取得するプログラムを、Pythonで書いてみました。クリップボード履歴ツールの活用方法と、Pythonのモジュール、PyPerclipとzip関数の利用例にと思ってくれたら幸いです。

説明が要らない という方はこちらをどうぞ

動機など

わたしはZaimを使って家計簿を作成しています。レシートを撮影して明細を登録できるほか、Amazonなど一部のサイトでの購入情報を自動的に登録できるため便利です。

しかしこのZaim、残念ながらヨドバシドットコムとの連携には対応していません。このため、ヨドバシドットコムを多用するわたしは買い物履歴を登録するのに毎回苦戦していました。

そこで、ヨドバシドットコムでの商品購入時に届く「ヨドバシ・ドット・コム:ご注文ありがとうございます」のメールに書かれている商品明細を読み込んで、Pythonで商品名と価格を抽出することにしました。

【ご注文商品】
---------------------------------------------------------------
・「[商品名(改行あり)]」

  配達希望日:[日時]

  合計 [個数] 点  [価格] 円

・「[商品名(改行あり)]」

  配達希望日:[日時]

  合計 [個数] 点  [価格] 円
・・・

・配達料金:  0 円

とりあえず実装方針

クリップボード履歴ツールのスタッククリップボード機能を使うと便利です。

スタッククリップボード機能とは、Clipboard-Historyなど多くのクリップボード履歴ツールについている機能で、コピー操作によりスタックに格納したテキストを順番に貼り付ける機能です。

これは単体で使ってももちろん便利ですが、スクリプトと併用すると結構便利です。このため、これを使って、クリップボードスタックに商品と値段を格納します。

実装例

とりあえずこんな感じで。正規表現で商品名と価格名のリストを作成し、zip関数でまとめて、クリップボードに転送します。
スクリプトを実行する前に、以下の準備が必要です。

  • クリップボード履歴ツールのスタッククリップボード機能を有効にしておく
  • ヨドバシドットコムから届く注文明細の、「ご注文商品」~「配達料金」のひとつ上の行までをクリップボードにコピーしておく
import re
import pyperclip
import sys
n = []
p = []
text = pyperclip.paste()
for m in re.finditer(r"「([^」]*)」", text, re.MULTILINE):
  n.append(re.sub("\\n\\s*", "", m[1]))
for m in re.finditer("([\\d,]+)\\s*円", text, re.MULTILINE):
  p.append(re.sub("\\n\\s*", "", m[1]))
ret = ""
for a, r in zip(n, p):
  pyperclip.copy(a)
  pyperclip.copy(r)

Pythonスクリプトからクリップボードへのアクセスには、pyperclipモジュールを用います。pip install pyperclipで事前にインストールしておきます。

実行すると、クリップボードスタックに買った商品の名前と値段が交互にコピーされるので、Zaimの支出フォームに順次貼り付けていきます。

プログラミングって、本職じゃない人が使っても便利

プログラミングは、このように仕事と関係ない分野でも結構使えます。日頃の定例作業を簡素化したり、自動化したり。IFTTTなどの自動処理サービスを使う場合も、プログラミングの知識感覚があった方ができることの幅は広がります。

なので、本職でない人・そのようなことを仕事でする予定がない人も、できて損をしないものではないでしょうか と、もっともらしいことを言ってみる。

まあ、願わくばこういうそれっぽいスクリプト群を気軽に作成できて、気軽に実行できるランチャーみたいな環境があるといいっちゃいいのですが・・・。

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

画像を一括リサイズ

何百枚でも何千枚でもリサイズできます。

縦横比を保たずリサイズ

anjyu3P2A6684_TP_V4.jpg   anjyu3P2A6684_TP_V4.jpg

import os
import glob
from PIL import Image

src = glob.glob('./dataset/*.jpg') # オリジナル画像のパスと拡張子を設定
dst = './dataset_resized/' # リサイズ画像の保存フォルダ

width = 513 # リサイズ後の横幅
height = 513 # リサイズ後の縦幅

for f in src:
    img = Image.open(f)
    img = img.resize((width,height))
    img.save(dst + os.path.basename(f))

縦横比を保ってリサイズ

anjyu3P2A6684_TP_V4.jpg anjyu3P2A6684_TP_V4.jpg

横幅を指定する場合

import os
import glob
from PIL import Image

src = glob.glob('./dataset/*.jpg') # オリジナル画像のパスと拡張子を設定
dst = './dataset_resized/' # リサイズ画像の保存フォルダ

width = 513 # リサイズ後の横幅

for f in src:
    img = Image.open(f)
    original_width, original_height = img.size
    scale = width / original_width
    height = int(original_height * scale)
    img = img.resize((width,height))
    img.save(dst + os.path.basename(f))

縦幅を指定する場合

import os
import glob
from PIL import Image

src = glob.glob('./dataset/*.jpg') # オリジナル画像のパスと拡張子を設定
dst = './dataset_resized/' # リサイズ画像の保存フォルダ

height = 513 # リサイズ後の縦幅

for f in src:
    img = Image.open(f)
    original_width, original_height = img.size
    scale = height / original_height
    width = int(original_width * scale)
    img = img.resize((width,height))
    img.save(dst + os.path.basename(f))

?


お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

Actix-webパフォーマンス

webアプリとRustに興味を持ちまして、せっかくなのでどの程度早く動作するのかwebフレームワークのパフォーマンスを比較してみました。

環境
windows 10 pro
Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz 2.71GHz
RAM: 8.00 GB
system 64bit

webサーバ作成

使用するwebフレームワーク
Rust: Actix-web
Python: Flask
Julia: Genie

Hello Worldアプリ作成
先ずは、Actix

コマンド
cargo new hello-world
cd hello-world

\hello-world\srcのmain.rsを編集する。

main.rs
use actix_web::{get, post, web, App, HttpResponse, HttpServer, Responder};

#[get("/")]
async fn hello() -> impl Responder {
    HttpResponse::Ok().body("Hello world!")
}

#[post("/echo")]
async fn echo(req_body: String) -> impl Responder {
    HttpResponse::Ok().body(req_body)
}

async fn manual_hello() -> impl Responder {
    HttpResponse::Ok().body("Hey there!")
}

#[actix_web::main]
async fn main() -> std::io::Result<()> {
    HttpServer::new(|| {
        App::new()
            .service(hello)
            .service(echo)
            .route("/hey", web::get().to(manual_hello))
    })
    .bind("0.0.0.0:8080")?
    .run()
    .await
}

Flask

適当なフォルダにserver.pyを作成。

server.py
from flask import Flask

app = Flask(__name__)

@app.route('/')
def index():
    return 'Hello World'


if __name__ == '__main__':
    app.debug = True
    app.run(host='0.0.0.0', port=8001)

GenieはJuliaの中で作成

コマンド
julia
using Genie
Genie.newapp("hello_world")

ファイルセットが自動で作成されるので、juliaから抜けて、routes.jlファイルを編集する。

routes.jl
using Genie.Router

route("/") do
  "Hello - Welcome to Genie!"
end

Genie.AppServer.startup(8000, "0.0.0.0")

今思えば、ファイルセットを作らずにjuliaの中で以下を実行するだけでよかったかも。。。。
route("/") do
"Hello - Welcome to Genie!"
end
Genie.AppServer.startup(8000, "0.0.0.0")

3つのwebアプリを起動させたままにしておいて、別のPC(ubuntu20.04)を利用してパフォーマンス測定をやる。

パフォーマンス測定

wrk2を使用する。
https://github.com/giltene/wrk2

コマンド
sudo apt-get install -y build-essential libssl-dev git zlib1g-dev
git clone https://github.com/giltene/wrk2.git
cd wrk2
make
sudo cp wrk /usr/local/bin
wrk -v
wrk 4.0.0 [epoll] Copyright (C) 2012 Will Glozer
Usage: wrk <options> <url>                            
  Options:                                            
    -c, --connections <N>  Connections to keep open   
    -d, --duration    <T>  Duration of test           
    -t, --threads     <N>  Number of threads to use   

    -s, --script      <S>  Load Lua script file       
    -H, --header      <H>  Add header to request      
    -L  --latency          Print latency statistics   
    -U  --u_latency        Print uncorrected latency statistics
        --timeout     <T>  Socket/request timeout     
    -B, --batch_latency    Measure latency of whole   
                           batches of pipelined ops   
                           (as opposed to each op)    
    -v, --version          Print version details      
    -R, --rate        <T>  work rate (throughput)     
                           in requests/sec (total)    
                           [Required Parameter]       


  Numeric arguments may include a SI unit (1k, 1M, 1G)
  Time arguments may include a time unit (2s, 2m, 2h)

グラフの表示にwrk2imgを使用する。
https://github.com/PPACI/wrk2img

コマンド
pip3 install wrk2img
# うまくいかなかったのでぼくはsudoつけました

測定していきます。使い方と見方は以下参考。
https://qiita.com/RyujiKawazoe/items/1da4342d8854543ca4cc

コマンド
wrk -U -d 30 -c 100 -R 1000 --latency http://192.168.1.95:8080/ | wrk2img actix_100.png
wrk -U -d 30 -c 100 -R 1000 --latency http://192.168.1.95:8001/ | wrk2img flask_100.png
wrk -U -d 30 -c 100 -R 1000 --latency http://192.168.1.95:8000/ | wrk2img genie_100.png

actixの結果

actix_100.png

Flaskの結果

flask_100.png

Genieの結果

genie_100.png

Flask < Genie < actixということで、actixのパフォーマンスが優れているようです。

最後にactixの接続数を増やしたときのグラフ(10,100,1000,10000)
actix.png

100までは負荷がかかっていないですが、1000になると影響が出るようです。

以上

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

numpy配列の要素をfloatからintに変換

floatのデータセットから一部の数値をintで切り出したい。

target = np.array(features[:,7], dtype=int)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ruby と Python で解く AtCoder ABC178 D 動的計画法

はじめに

AtCoder Problems の Recommendation を利用して、過去の問題を解いています。
AtCoder さん、AtCoder Problems さん、ありがとうございます。

今回のお題

AtCoder Beginner Contest D - Redistribution
Difficulty: 830

今回のテーマ、動的計画法

いわゆる典型的な動的計画法の問題です。
前の記事 - Qiita で取り上げました、AtCoder Beginner Contest E - Crested Ibis vs Monster では、目標値を超えてた場合もケアしないといけませんが、今回はジャストでよいので、その分少し簡単です。

Ruby

ruby.rb
s = gets.to_i
dp = Array.new(s.next, 0)
dp[0] = 1
s.times do |i|
  next if dp[i].zero?
  3.upto(s) do |x|
    if i + x <= s
      dp[i + x] += dp[i]
      dp[i + x] %= 1000000007
    else
      break
    end
  end
end
puts dp[s]
ruby.rb
    if i + x <= s
      dp[i + x] += dp[i]
      dp[i + x] %= 1000000007
    else
      break
    end

今回はジャストでよいので、i + x <= sについてのみdpを加算します。

Python

python.py
from sys import stdin

def main():
    input = stdin.readline

    s = int(input())
    dp = [0] * (s + 1)
    dp[0] = 1
    for i in range(s):
        if dp[i] == 0:
            continue
        for x in range(3, s + 1):
            if i + x <= s:
                dp[i + x] += dp[i]
                dp[i + x] %= 1000000007
            else:
                break
    print(dp[s])
main()

PyPy は凄く速いですね。

Ruby Python PyPy
コード長 (Byte) 244 405 405
実行時間 (ms) 284 509 70
メモリ (KB) 14400 9060 64596

まとめ

  • ABC 178 D を解いた
  • Ruby に詳しくなった
  • Python に詳しくなった

参照したサイト
Ruby と Python で解く AtCoder ABC153 E 動的計画法 - Qiita

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

【Python】Docker で OpenCV 環境構築 ( cv2.imshow() も動く )

以前このような Qiita ( Ubuntu, Python, OpenCV な環境を Docker 上でつくった ) を書いたのですが cv2.imshow() が機能せずいろいろ試行錯誤していました。

この記事では Docker 環境でもちゃんと cv2.imshow() が動くようにする方法を書きます。

ホスト OS は MacOS Catalina です。

1. XQuartz インストール

https://www.xquartz.org/

インストールしたら XQuartz の環境設定からセキュリティタブのネットワーク・クライアントからの接続を許可にチェックを入れる。

2. xhost 開放

xhost +

3. Docker run

docker run -it \
-v $(pwd):/code \
-v ~/.Xauthority:/root/.Xauthority \ # Docker と GUI のやり取りをするために必要
-e DISPLAY=$(hostname):0 \ # Docker と GUI のやり取りをするために必要
-p 8888:8888 \ # Jupyter 用
--name opencv \ 
ubuntu /bin/bash

4. 各種アップデート・インストール

コンテナ内で以下を実行

apt-get -y update && apt-get -y upgrade && \
apt-get -y install python3-pip vim libgl1-mesa-dev libgtk2.0-dev && \
pip3 install numpy opencv-python jupyterlab pandas matplotlib scikit-learn seaborn scipy

5. 動作チェック

  1. ホスト PC で XQuartz 立ち上げる

  2. Jupyter 起動 ( コンテナ内 )

jupyter lab --ip=0.0.0.0 --allow-root --LabApp.token=''
  1. ホスト PC のブラウザで http://localhost:8888/ にアクセス

  2. Jupyter で以下を実行

import cv2

img = cv2.imread('test.jpeg') # なにか適当な画像を入れておく
img.shape # => ( 画像の縦の長さ, 横の長さ, 3 ) が返ってくる

cv2.imshow('img', img) # 画像を表示したいときはこの 3 行セットで実行する
cv2.waitKey(0)
cv2.destroyAllWindows

以上がエラーなく動作すれば OK !! お疲れさまでした !!!

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

Django シフト作成機能 曜日の基本勤務シフト登録機能を追加

①シフト表 管理者以外は時間の編集はできないようする
②シフト表 シフト作成は曜日でコピーしてほしい
③希望シフト 毎月5日までにシフト希望を聞いて10日までに作成し配布しているので5までに入力させる入力制限
④掲示板 施設ごとにお知らせをする
⑤タスク 何をやらないといけないかを引き継ぎとかできると最高

①は終了しているので、着手したのが②です。

ユーザー情報と新たにBaseShiftテーブルを作成して、そちらに基本シフトを登録するようにして対応しました。

schedule/models.py
class BaseShift(models.Model):
    id = models.AutoField(verbose_name='希望シフトID',primary_key=True)
    user = models.ForeignKey(User, on_delete=models.CASCADE, verbose_name='社員名')
    getsu_shift_name_1 = models.ForeignKey(Shift, verbose_name='月_1シフト名', related_name='base_shift_getsu_shift_name_1',on_delete=models.SET_NULL,null= True)
    getsu_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='月_1施設', related_name='base_shift_getsu_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shift_name_2 = models.ForeignKey(Shift, verbose_name='月_2シフト名', related_name='base_shift_getsu_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='月_2施設', related_name='base_shift_getsu_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shift_name_3 = models.ForeignKey(Shift, verbose_name='月_3シフト名', related_name='base_shift_getsu_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='月_3施設', related_name='base_shift_getsu_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shift_name_4 = models.ForeignKey(Shift, verbose_name='月_4シフト名', related_name='base_shift_getsu_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    getsu_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='月_4施設', related_name='base_shift_getsu_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    ka_shift_name_1 = models.ForeignKey(Shift, verbose_name='火_1シフト名', related_name='base_shift_ka_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='火_1施設', related_name='base_shift_ka_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shift_name_2 = models.ForeignKey(Shift, verbose_name='火_2シフト名', related_name='base_shift_ka_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='火_2施設', related_name='base_shift_ka_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shift_name_3 = models.ForeignKey(Shift, verbose_name='火_3シフト名', related_name='base_shift_ka_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='火_3施設', related_name='base_shift_ka_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shift_name_4 = models.ForeignKey(Shift, verbose_name='火_4シフト名', related_name='base_shift_ka_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    ka_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='火_4施設', related_name='base_shift_ka_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    sui_shift_name_1 = models.ForeignKey(Shift, verbose_name='水_1シフト名', related_name='base_shift_sui_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='水_1施設', related_name='base_shift_sui_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shift_name_2 = models.ForeignKey(Shift, verbose_name='水_2シフト名', related_name='base_shift_sui_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='水_2施設', related_name='base_shift_sui_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shift_name_3 = models.ForeignKey(Shift, verbose_name='水_3シフト名', related_name='base_shift_sui_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='水_3施設', related_name='base_shift_sui_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shift_name_4 = models.ForeignKey(Shift, verbose_name='水_4シフト名', related_name='base_shift_sui_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    sui_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='水_4施設', related_name='base_shift_sui_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    moku_shift_name_1 = models.ForeignKey(Shift, verbose_name='木_1シフト名', related_name='base_shift_moku_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='木_1施設', related_name='base_shift_moku_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shift_name_2 = models.ForeignKey(Shift, verbose_name='木_2シフト名', related_name='base_shift_moku_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='木_2施設', related_name='base_shift_moku_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shift_name_3 = models.ForeignKey(Shift, verbose_name='木_3シフト名', related_name='base_shift_moku_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='木_3施設', related_name='base_shift_moku_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shift_name_4 = models.ForeignKey(Shift, verbose_name='木_4シフト名', related_name='base_shift_moku_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    moku_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='木_4施設', related_name='base_shift_moku_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    kin_shift_name_1 = models.ForeignKey(Shift, verbose_name='金_1シフト名', related_name='base_shift_kin_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='金_1施設', related_name='base_shift_kin_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shift_name_2 = models.ForeignKey(Shift, verbose_name='金_2シフト名', related_name='base_shift_kin_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='金_2施設', related_name='base_shift_kin_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shift_name_3 = models.ForeignKey(Shift, verbose_name='金_3シフト名', related_name='base_shift_kin_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='金_3施設', related_name='base_shift_kin_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shift_name_4 = models.ForeignKey(Shift, verbose_name='金_4シフト名', related_name='base_shift_kin_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    kin_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='金_4施設', related_name='base_shift_kin_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    do_shift_name_1 = models.ForeignKey(Shift, verbose_name='土_1シフト名', related_name='base_shift_do_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    do_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='土_1施設', related_name='base_shift_do_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    do_shift_name_2 = models.ForeignKey(Shift, verbose_name='土_2シフト名', related_name='base_shift_do_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    do_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='土_2施設', related_name='base_shift_do_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    do_shift_name_3 = models.ForeignKey(Shift, verbose_name='土_3シフト名', related_name='base_shift_do_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    do_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='土_3施設', related_name='base_shift_do_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    do_shift_name_4 = models.ForeignKey(Shift, verbose_name='土_4シフト名', related_name='base_shift_do_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    do_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='土_4施設', related_name='base_shift_do_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

    nichi_shift_name_1 = models.ForeignKey(Shift, verbose_name='日_1シフト名', related_name='base_shift_nichi_shift_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shisetsu_name_1 = models.ForeignKey(Shisetsu, verbose_name='日_1施設', related_name='base_shift_nichi_shisetsu_name_1',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shift_name_2 = models.ForeignKey(Shift, verbose_name='日_2シフト名', related_name='base_shift_nichi_shift_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shisetsu_name_2 = models.ForeignKey(Shisetsu, verbose_name='日_2施設', related_name='base_shift_nichi_shisetsu_name_2',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shift_name_3 = models.ForeignKey(Shift, verbose_name='日_3シフト名', related_name='base_shift_nichi_shift_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shisetsu_name_3 = models.ForeignKey(Shisetsu, verbose_name='日_3施設', related_name='base_shift_nichi_shisetsu_name_3',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shift_name_4 = models.ForeignKey(Shift, verbose_name='日_4シフト名', related_name='base_shift_nichi_shift_name_4',on_delete=models.SET_NULL,blank=True, null=True)
    nichi_shisetsu_name_4 = models.ForeignKey(Shisetsu, verbose_name='日_4施設', related_name='base_shift_nichi_shisetsu_name_4',on_delete=models.SET_NULL,blank=True, null=True)

こんな作り方がよいのかわかりませんが、今までのつくりを踏襲しないとほかに影響するかもしれませんので長くはなりましたがこちらの方法で作成しました。

schedule/views.py
def schedulecreatefunc(request,year_num,month_num):
    year, month = int(year_num), int(month_num)
    #対象年月のscheduleをすべて削除する
    Schedule.objects.filter(year = year, month = month).delete()
    #シフト範囲の日数を取得する
    enddate = datetime.date(year,month,20)
    startdate = enddate + relativedelta(months=-1)
    #希望シフトから対象期間の希望を抽出する
    kiboushift_list = KibouShift.objects.filter(date__range=[startdate, enddate])
    kiboushift_list = list(kiboushift_list)
    shift_list = Shift.objects.all()

    #希望シフトを反映
    for kibou in kiboushift_list:
        sum_work_time = 0
        for shift in shift_list:
                if str(kibou.shift_name_1) == str(shift.name):
                    sum_work_time = shift.wrok_time + sum_work_time
                if str(kibou.shift_name_2) == str(shift.name):
                    sum_work_time = shift.wrok_time + sum_work_time
                if str(kibou.shift_name_3) == str(shift.name):
                    sum_work_time = shift.wrok_time + sum_work_time
                if str(kibou.shift_name_4) == str(shift.name):
                    sum_work_time = shift.wrok_time + sum_work_time
        new_object = Schedule(
            user = kibou.user,
            date = kibou.date, 
            year = year,
            month = month,
            shift_name_1 = kibou.shift_name_1, 
            shisetsu_name_1 = kibou.shisetsu_name_1,
            shift_name_2 = kibou.shift_name_2, 
            shisetsu_name_2 = kibou.shisetsu_name_2,
            shift_name_3 = kibou.shift_name_3, 
            shisetsu_name_3 = kibou.shisetsu_name_3,
            shift_name_4 = kibou.shift_name_4, 
            shisetsu_name_4 = kibou.shisetsu_name_4,
            day_total_worktime = sum_work_time,
            )
        new_object.save()

    #希望シフト以外を作成
    user_list = User.objects.all()
    #シフト範囲の日数を取得する
    enddate = datetime.date(year,month,20)
    startdate = enddate + relativedelta(months=-1)
    kaisu = enddate - startdate
    kaisu = int(kaisu.days) 
    #前月
    if month != 1 :
        zengetsu = month - 1
    else:
        zengetsu = 12
        year = year - 1
    #ユーザーごとに処理をする
    for user in user_list:
        #base_shiftが登録されている情報から作成
        base_shift = BaseShift.objects.filter(user = user.id)
        kongetsu_list = Schedule.objects.filter(year = year, month = month, user = user.id).order_by('date')
        zengetsu_list = Schedule.objects.filter(year = year, month = zengetsu, user = user.id).order_by('date')
        sakusei_date = startdate
        shift_list = Shift.objects.all()
        for new_shift in range(kaisu):
            sakusei_date = sakusei_date + timedelta(days=1)
            if kongetsu_list.filter(user = user.id, date = sakusei_date).exists():
                print("ok")                       
            else:
                weekdays = ["月","火","水","木","金","土","日"]
                youbi = weekdays[sakusei_date.weekday()] 

                #ベースシフトから取得して取得
                if base_shift.filter(user = user.id ).exists():
                    for base in base_shift:
                        sum_work_time = 0
                        shift1 = None
                        shisetsu1 = None
                        shift2 = None
                        shisetsu2 = None
                        shift3 = None
                        shisetsu3 = None
                        shift4 = None
                        shisetsu4 = None

                        if youbi == "月":
                            for shift in shift_list:
                                if str(base.getsu_shift_name_1) == str(shift.name):
                                    shift1 = base.getsu_shift_name_1
                                    shisetsu1 = base.getsu_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.getsu_shift_name_2) == str(shift.name):
                                    shift2 = base.getsu_shift_name_2
                                    shisetsu2 = base.getsu_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.getsu_shift_name_3) == str(shift.name):
                                    shift3 = base.getsu_shift_name_3
                                    shisetsu3 = base.getsu_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.getsu_shift_name_4) == str(shift.name):
                                    shift4 = base.getsu_shift_name_4
                                    shisetsu4 = base.getsu_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        elif youbi == "火":
                            for shift in shift_list:
                                if str(base.ka_shift_name_1) == str(shift.name):
                                    shift1 = base.ka_shift_name_1
                                    shisetsu1 = base.ka_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.ka_shift_name_2) == str(shift.name):
                                    shift2 = base.ka_shift_name_2
                                    shisetsu2 = base.ka_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.ka_shift_name_3) == str(shift.name):
                                    shift3 = base.ka_shift_name_3
                                    shisetsu3 = base.ka_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.ka_shift_name_4) == str(shift.name):
                                    shift4 = base.ka_shift_name_4
                                    shisetsu4 = base.ka_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        elif youbi == "水":
                            for shift in shift_list:
                                if str(base.sui_shift_name_1) == str(shift.name):
                                    shift1 = base.sui_shift_name_1
                                    shisetsu1 = base.sui_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.sui_shift_name_2) == str(shift.name):
                                    shift2 = base.sui_shift_name_2
                                    shisetsu2 = base.sui_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.sui_shift_name_3) == str(shift.name):
                                    shift3 = base.sui_shift_name_3
                                    shisetsu3 = base.sui_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.sui_shift_name_4) == str(shift.name):
                                    shift4 = base.sui_shift_name_4
                                    shisetsu4 = base.sui_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        elif youbi == "木":
                            for shift in shift_list:
                                if str(base.moku_shift_name_1) == str(shift.name):
                                    shift1 = base.moku_shift_name_1
                                    shisetsu1 = base.moku_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.moku_shift_name_2) == str(shift.name):
                                    shift2 = base.moku_shift_name_2
                                    shisetsu2 = base.moku_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.moku_shift_name_3) == str(shift.name):
                                    shift3 = base.moku_shift_name_3
                                    shisetsu3 = base.moku_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.moku_shift_name_4) == str(shift.name):
                                    shift4 = base.moku_shift_name_4
                                    shisetsu4 = base.moku_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        elif youbi == "金":
                            for shift in shift_list:
                                if str(base.kin_shift_name_1) == str(shift.name):
                                    shift1 = base.kin_shift_name_1
                                    shisetsu1 = base.kin_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.kin_shift_name_2) == str(shift.name):
                                    shift2 = base.kin_shift_name_2
                                    shisetsu2 = base.kin_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.kin_shift_name_3) == str(shift.name):
                                    shift3 = base.kin_shift_name_3
                                    shisetsu3 = base.kin_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.kin_shift_name_4) == str(shift.name):
                                    shift4 = base.kin_shift_name_4
                                    shisetsu4 = base.kin_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        elif youbi == "土":
                            for shift in shift_list:
                                if str(base.do_shift_name_1) == str(shift.name):
                                    shift1 = base.do_shift_name_1
                                    shisetsu1 = base.do_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.do_shift_name_2) == str(shift.name):
                                    shift2 = base.do_shift_name_2
                                    shisetsu2 = base.do_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.do_shift_name_3) == str(shift.name):
                                    shift3 = base.do_shift_name_3
                                    shisetsu3 = base.do_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.do_shift_name_4) == str(shift.name):
                                    shift4 = base.do_shift_name_4
                                    shisetsu4 = base.do_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        if youbi == "日":
                            for shift in shift_list:
                                if str(base.nichi_shift_name_1) == str(shift.name):
                                    shift1 = base.nichi_shift_name_1
                                    shisetsu1 = base.nichi_shisetsu_name_1
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.nichi_shift_name_2) == str(shift.name):
                                    shift2 = base.nichi_shift_name_2
                                    shisetsu2 = base.nichi_shisetsu_name_2
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.nichi_shift_name_3) == str(shift.name):
                                    shift3 = base.nichi_shift_name_3
                                    shisetsu3 = base.nichi_shisetsu_name_3
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(base.nichi_shift_name_4) == str(shift.name):
                                    shift4 = base.nichi_shift_name_4
                                    shisetsu4 = base.nichi_shisetsu_name_4
                                    sum_work_time = shift.wrok_time + sum_work_time

                        new_object=Schedule(
                        user = base.user,
                        date = sakusei_date,
                        year = year,
                        month = month,
                        shift_name_1 = shift1, 
                        shisetsu_name_1 = shisetsu1,
                        shift_name_2 = shift2, 
                        shisetsu_name_2 = shisetsu2,
                        shift_name_3 = shift3, 
                        shisetsu_name_3 = shisetsu3,
                        shift_name_4 = shift4, 
                        shisetsu_name_4 = shisetsu4,
                        day_total_worktime = sum_work_time,
                        )
                        new_object.save()
                else:
                    hukushadate = sakusei_date + relativedelta(months=-1)
                    hukusha_list = zengetsu_list.filter(date = hukushadate, user = user.id)
                    if zengetsu_list.filter(date = hukushadate, user = user.id).exists():
                        for hukusha in hukusha_list:
                        # 日の労働合計時間をシフトから計算
                            sum_work_time = 0
                            for shift in shift_list:
                                if str(hukusha.shift_name_1) == str(shift.name):
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(hukusha.shift_name_2) == str(shift.name):
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(hukusha.shift_name_3) == str(shift.name):
                                    sum_work_time = shift.wrok_time + sum_work_time
                                if str(hukusha.shift_name_4) == str(shift.name):
                                    sum_work_time = shift.wrok_time + sum_work_time

                            new_object=Schedule(
                                user = hukusha.user,
                                date = sakusei_date,
                                year = year,
                                month = month,
                                shift_name_1 = hukusha.shift_name_1, 
                                shisetsu_name_1 = hukusha.shisetsu_name_1,
                                shift_name_2 = hukusha.shift_name_2, 
                                shisetsu_name_2 = hukusha.shisetsu_name_2,
                                shift_name_3 = hukusha.shift_name_3, 
                                shisetsu_name_3 = hukusha.shisetsu_name_3,
                                shift_name_4 = hukusha.shift_name_4, 
                                shisetsu_name_4 = hukusha.shisetsu_name_4,
                                day_total_worktime = sum_work_time,
                                )
                            new_object.save()
                    else:
                        useradd = User.objects.get(id=user.id)
                        shiftadd = Shift.objects.get(id=2)
                        new_object=Schedule(
                        user = useradd,
                        date = sakusei_date,
                        year = year,
                        month = month,
                        shift_name_1 = shiftadd, 
                        shisetsu_name_1 = None,
                        shift_name_2 = None, 
                        shisetsu_name_2 = None,
                        shift_name_3 = None, 
                        shisetsu_name_3 = None,
                        shift_name_4 = None, 
                        shisetsu_name_4 = None,
                        day_total_worktime = 0,
                        )
                        new_object.save()
    return HttpResponseRedirect('/schedule/monthschedule/%s/%s/' % (year,month,))

これで、BaseShiftが登録されている場合は、前月の日付コピーする前に先に処理される流れになりました。

整理すると

1.希望シフトを処理
2.ベースシフトがあれば処理
3.前月の同日をコピー

これで、編集前の状態になりますね!

数時間かかりましたが、無事に動作確認できたので良かったです!

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

日別訪問者数の最大平均区間(large)

0.問題

対象となる全日数をDDとし、全日数のうちあるN日間に訪問してくる
人数の平均が最大になる区間の開始日の候補日の数と、それらの候補日の中で
最も早い日をあげなさい。

0C94A4B5-A272-4603-A43D-1A638670F165.png

この問題を解くに当たり考えたことをまとめてみた。
下記の手順でクリアできた。

1.最初に図を書いてみる

与えられのは以下の二行。

一行目に全日数(DD)と求めたい区間の日数(N)\\
二行目にそれぞれの日数の訪問人数(A_i(i=1,,,DD))

5 3
1 2 3 2 1

IMG-2991.jpg

上記のように考えていくと、
開始日が2日目が日別訪問者数が8人となり最大平均区間になる。
また、この訪問者数が最大になるのは開始日が2日の時のみなので、候補日の数は
1となる。
答えは「1 2」となることがわかる。

2.考え方(パターン分け)

ここで平均が最大になる=ある区間の日別訪問者数の和が最大になると考える。

(1)DDとNが同じ場合

⇒DDまでの訪問者数の計算する必要はない
候補日も、区間のパターンも一つしかないので、[1 1]が答え

(2)DD>NでかつN>1の場合

X_1=A_1+...+A_{N}\\
max1=X_1(max1:N日間での最大訪問者数)\\
C_{day}=1(C_{day}:候補日の数)\\
S_{day}=d(S_{day}:N日間での最大訪問者数の開始日)
X_d:dを開始日とした時のN日間の訪問者数とする
前の章より、下記のことがわかる。
この漸化式により、計算回数も減らせる。
X_d=X_{d-1}-A_{d-1}+A_{d-1+N}(d=2,,,DD-N+1)-★\\

ⅰ)ある開始日dのN日間の訪問者数>最大訪問者数の処理

C_{day}=1\\
max1=X_d\\
S_{day}=d

ⅱ)ある開始日dのN日間の訪問者数=最大訪問者数の処理

C_{day}=C_{day}+1\\
max1=X_d

(3)DD>NでかつN=1の場合

X_d=A_d(d=1,,,DD)

上記に(2)の場合の1)、2)の条件の処理を考えればよい。

3.コード例

in1=input()
in2=input()
arr1=in1.split()
arr2=in2.split()

dn=0

num1=int(arr1[1])
#print(num1)

if num1>1:
    i=0
    for i in range(num1):
        dn=dn+int(arr2[i])

    max_t=dn
    max_i=0
    k_cnt=1

    for i in range(1,int(arr1[0])-num1+1):
        dn=dn+int(arr2[i+num1-1])-int(arr2[i-1])

        if max_t<dn:
            k_cnt=1
            max_i=i
            max_t=dn

        else:
            if max_t == dn:
                k_cnt=k_cnt+1
                max_t=dn

    max_i=max_i+1
    print(str(k_cnt)+' '+str(max_i))

elif num1>1 and num1 == int(arr1[0]):
    print('1 1')
else:
    max_i=0
    max_t=0
    k_cnt=1
    for i in range(int(arr1[0])):
        dn=int(arr2[i])
        if max_t<dn:
            max_t=dn
            k_cnt=1
            max_i=i
        else:
            if max_t == dn:
                max_t=dn
                k_cnt=k_cnt+1

    max_i=max_i+1
    print(str(k_cnt)+' '+str(max_i))

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

【PyTorchチュートリアル⑥】What is torch.nn really?

はじめに

前回に引き続き、PyTorch 公式チュートリアル の第6弾です。
今回は What is torch.nn really? を進めます。

What is torch.nn really?

このチュートリアルでは、torch.nn、torch.optim、Dataset、DataLoader を説明します。 (torch.nn、torch.optimは前回でも説明しましたが、いろいろな人がチュートリアルを記述しているため、重複する部分もあります。)

使用するデータセットはMNISTデータセットです。
MNISTデータセットは0から9までの手書き数字画像のデータセットです。
理解を深めるため、最初に上記のパッケージを利用せずにモデルを構築します。
次に、torch.nn、torch.optim、Dataset、DataLoader の順で、一つずつコードを置き換えながら進めていきます。

1. MNIST data setup

まずはMNISTデータセット(手書き数字画像データセット)をダウンロードします。

from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

PATH.mkdir(parents=True, exist_ok=True)

URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"

if not (PATH / FILENAME).exists():
        content = requests.get(URL + FILENAME).content
        (PATH / FILENAME).open("wb").write(content)

このデータセットはnumpy配列です。
pickle 形式で保存されています。

import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")

一つのデータ(x_train[0])は28×28サイズの画像ですが、784列の1行として保持されています。
pyplot.imshow で閲覧するには、28×28に変換する必要があります。

from matplotlib import pyplot
import numpy as np

pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)
out
(50000, 784)

ダウンロード.png

以降では PyTorch の Tensor を利用します。
numpy配列から Tensor に変換します。

import torch

x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())
out
tensor([[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.]]) tensor([5, 0, 4,  ..., 8, 4, 8])
torch.Size([50000, 784])
tensor(0) tensor(9)

学習データの件数は5万件(×784)、教師データは 0 から 9 までの数字であることが分かります。

2. Neural net from scratch (no torch.nn)

最初に torch.nn を利用せず Tensor のみでニューラルネットワークを作成します。
作成するモデルは、単純な線形モデル
$ y = w \times x + b$
です。

PyTorch の ランダムメソッド randn で重み $w$ を初期化します。randn は標準化(平均 0、標準偏差 1)されたランダム値です。
初期化するときの勾配を含めたくないため、初期化後に requires_grad_() を行い、requires_grad=True にします。
重みの初期化は「Xavier initialization」を利用しています。
(とありますが計算式が少し違うような気がします)
bias はゼロで初期化します。

import math

weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)

活性化関数も必要ですので、log_softmax 関数を作成します。
PyTorch には損失関数や活性化関数が多数用意されていますが、このように自分で関数を作成することもできます。

def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)

def model(xb):
    return log_softmax(xb @ weights + bias)

@は内積演算を表します。
バッチサイズ(今回は64個の画像)単位でこの関数が呼び出されます。

bs = 64  # batch size

xb = x_train[0:bs]  # ミニバッチ
preds = model(xb)  # モデルで予想する
preds[0], preds.shape
print(preds[0], preds.shape)
out
tensor([-2.8486, -2.2823, -2.2740, -2.7800, -2.1906, -1.3280, -2.4680, -2.2958,
        -2.8856, -2.8650], grad_fn=<SelectBackward>) torch.Size([64, 10])

予測値 preds を出力すると Tensor に勾配関数(grad_fn)が含まれていることが確認できます。
後でこの勾配関数を利用して逆伝播を計算します。
損失関数として、教師データと予測値の負の対数尤度を実装します。
負の対数尤度は、一般的に交差エントロピー誤差関数と呼ばれます。

def nll(input, target):
    return -input[range(target.shape[0]), target].mean()

loss_func = nll

予測値と教師データで損失を計算し、学習後のパラメータを確認します。

yb = y_train[0:bs]
print(loss_func(preds, yb))
out
tensor(2.4101, grad_fn=<NegBackward>)

モデルの精度を計算する評価関数も実装します。
out には、手書き数字 0 から 9 までの確率が配列で保持されていますので、argmax の最大値の値が一番確率が高い手書き数字ということになります。
その値と教師データの一致した平均をとることで正解率を計算します。

def accuracy(out, yb):
    preds = torch.argmax(out, dim=1)
    return (preds == yb).float().mean()
print(accuracy(preds, yb))
out
tensor(0.0781)

これで学習する準備ができました。
次を繰り返し、学習を行います。

  • ミニバッチ単位で学習データを取得します。
  • モデルを使って、学習データから予測します。
  • 損失を計算します。
  • loss.backward() でモデルの勾配(重みとバイアス)を更新します。

重みとバイアスを更新した後に grad.zero_() で勾配を初期化しています。
これは loss.backward() で勾配を計算するときに、既に保存されているものに追加されるためです。
※set_trace() のコメントを外すことで、各ステップで変数の値を確認できます。

from IPython.core.debugger import set_trace

lr = 0.5  # learning rate
epochs = 2  # how many epochs to train for

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        #set_trace()
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        with torch.no_grad():
            weights -= weights.grad * lr
            bias -= bias.grad * lr
            weights.grad.zero_()
            bias.grad.zero_()

学習後に精度が向上していることが確認できます。

print(loss_func(model(xb), yb), accuracy(model(xb), yb))
out
tensor(0.0822, grad_fn=<NegBackward>) tensor(1.)

学習前は正解率が 7% でしたが、学習後は 100% になっています。
※ 学習データに xb が含まれているため学習後の正解率が高くなっています。 検証データ(x_valid)で試すと正解率は 9% ⇒ 95% くらいでした。

以上で、ゼロから単純なニューラルネットワークが構築できました。
今回の隠れ層がないソフトマックス関数を利用したネットワークはロジスティック回帰と呼ばれます。

3. Using torch.nn.functional

ここから PyTorch の nn パッケージを利用してコードをリファクタリングしていきます。
最初のステップでは、活性化関数と損失関数を置き換えていきましょう。
torch.nn.functional には、log_softmax関数と負の対数尤度を組み合わせた F.cross_entropy があります。
損失関数を F.cross_entropy に置き換えます。
F.cross_entropy には log_softmax関数が含まれていますので、活性化関数として定義した def log_softmax(x) も削除できます。

import torch.nn.functional as F

loss_func = F.cross_entropy

def model(xb):
    return xb @ weights + bias

model で呼び出していた log_softmaxも不要になります。(cross_entropyに含まれます)
損失と精度が以前と同じであることを確認しましょう。

print(loss_func(model(xb), yb), accuracy(model(xb), yb))
out
tensor(0.0822, grad_fn=<NllLossBackward>) tensor(1.)

4. Refactor using nn.Module

次に、nn.Module と nn.Parameter を利用してリファクタリングしていきます。
nn.Module は Pytorch のニューラルネットワークの基底クラスです。
nn.Module はサブクラスとして実装します。
作成したサブクラスに重みとバイアスのパラメータを定義します。また、forward メソッドに入力から出力まで順につなげる処理を記述します。
nn.Module には モデルのパラメータを返却する parameters() もあらかじめ用意されています。

from torch import nn

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))
        self.bias = nn.Parameter(torch.zeros(10))

    def forward(self, xb):
        return xb @ self.weights + self.bias

関数を使用する代わりにオブジェクトを使用しているので、最初にモデルをインスタンス化する必要があります。

model = Mnist_Logistic()

これでリファクタリング前と同じように学習できます。
nn.Module オブジェクトは関数のように呼び出して利用することができます。

print(loss_func(model(xb), yb))
out
tensor(2.3918, grad_fn=<NllLossBackward>)

ここまでの実装では、以下のように重みとバイアスの更新をそれぞれ計算し、勾配を手動でゼロにしていました。

  with torch.no_grad():
      weights -= weights.grad * lr
      bias -= bias.grad * lr
      weights.grad.zero_()
      bias.grad.zero_()

重みとバイアスの更新は、nn.Module で定義された parameters() と zero_grad() で置き換えて簡潔にできます。

  # 説明用コードのため実行できません(実行時エラーになります)
  with torch.no_grad():
      for p in model.parameters(): p -= p.grad * lr
      model.zero_grad()

学習のループを fit 関数として定義し、呼び出しできるようにしておきます。

def fit():
    for epoch in range(epochs):
        for i in range((n - 1) // bs + 1):
            start_i = i * bs
            end_i = start_i + bs
            xb = x_train[start_i:end_i]
            yb = y_train[start_i:end_i]
            pred = model(xb)
            loss = loss_func(pred, yb)

            loss.backward()
            with torch.no_grad():
                for p in model.parameters():
                    p -= p.grad * lr
                model.zero_grad()

fit()

損失が減っていることを再確認しましょう。

print(loss_func(model(xb), yb))
out
tensor(0.0796, grad_fn=<NllLossBackward>)

5. Refactor using nn.Linear

最初に weights と bias を自分で定義し、線形関数 $ w \times x + b$ も実装していましたが、nn.Linear (線形レイヤー)に置き換えてみましょう。

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(784, 10)

    def forward(self, xb):

        return self.lin(xb)

先ほどと同じようにモデルをインスタンス化し、損失を計算します。

model = Mnist_Logistic()
print(loss_func(model(xb), yb))
out
tensor(2.3661, grad_fn=<NllLossBackward>)

関数化した fit を呼び出して学習します。

fit()
print(loss_func(model(xb), yb))
out
tensor(0.0813, grad_fn=<NllLossBackward>)

loss値が 2.3661 から 0.0813 になり、学習できていることが確認できます。

6. Refactor using optim

次に最適化アルゴリズムをリファクタリングします。
Pytorch の torch.optim パッケージにはさまざまな最適化アルゴリズムがあります。
また、torch.optim の各クラスは手動でパラメータを更新する代わりに、 step メソッドを実行することでパラメータを更新してくれます。

  with torch.no_grad():
      for p in model.parameters(): p -= p.grad * lr
      model.zero_grad()

上記のコードを以下に書き換えることができます。
※ opt.zero_grad() で次のミニバッチの計算前に勾配を 0 にする必要があります。

  # 説明用コードのため実行できません
  opt.step()
  opt.zero_grad()
from torch import optim

モデルとオプティマイザの生成を関数化するとコードが簡潔になります。

def get_model():
    model = Mnist_Logistic()
    return model, optim.SGD(model.parameters(), lr=lr)

model, opt = get_model()
print(loss_func(model(xb), yb))

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
out
tensor(2.3423, grad_fn=<NllLossBackward>)
tensor(0.0819, grad_fn=<NllLossBackward>)

7. Refactor using Dataset

PyTorchには抽象 Dataset クラスがあります。
Dataset を利用することでトレーニング中に訓練用データ(x_train)と教師データ(y_train)が扱いやすくなります。
Dataset は、要素数を返却する __len__関数と、インデックスを指定して要素を返却する __getitem__関数を実装する必要があります。
TensorDatasetは、データセットを Tensor にラップします。

from torch.utils.data import TensorDataset

TensorDataset 作成時に x_train と y_train を指定して作成します。

train_ds = TensorDataset(x_train, y_train)

先ほどまでは、訓練用データ(x_train)と教師データ(y_train)を個別に反復処理していました。

    xb = x_train[start_i:end_i]
    yb = y_train[start_i:end_i]

TensorDataset を利用すると、まとめて処理できます。

    xb,yb = train_ds[i*bs : i*bs+bs]
model, opt = get_model()

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        xb, yb = train_ds[i * bs: i * bs + bs]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
out
tensor(0.0803, grad_fn=<NllLossBackward>)

8. Refactor using DataLoader

DataLoaderを使用すると、Dataset を利用したループ処理を簡潔にできます。
Dataset を元に DataLoader を作成します。

from torch.utils.data import DataLoader

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs)

最初のコードでは、バッチサイズごとにスタート位置を指定し、スライスしていました。

for i in range((n-1)//bs + 1):
    xb,yb = train_ds[i*bs : i*bs+bs]
    pred = model(xb)

DataLoader を利用すると(xb、yb)が DataLoader から自動的に順次読み込まれるため、ループがより簡潔になります。

for xb,yb in train_dl:
    pred = model(xb)
model, opt = get_model()

for epoch in range(epochs):
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
out
tensor(0.0802, grad_fn=<NllLossBackward>)

ここまでで、nn.Module、nn.Parameter、Dataset、DataLoader を利用しました。
コードを簡潔に理解しやすく記述できました。
次に効果的なモデルを作成するために必要な基本機能を追加してみます。

9. Add validation

ここまででは、学習データのみで学習を進めてきましたが、実際の学習では検証データ(validation data)を利用して、過学習していないか、学習が進んでいるかをチェックします。
以下で 検証用のデータセットを設定します。

  • 検証データセットはシャッフルしません。(検証データ全件が検証対象のため、シャッフルする意味はありません。)
  • バッチサイズを2倍にします。検証データでは勾配を計算する必要がないためメモリ消費が少なく、効率化のためバッチサイズを大きくします。
train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)

各エポックの終わりに検証データ(validation data)を利用して損失を計算します。
学習の前に model.train() で学習モードにし、検証の前に model.eval() で評価モードにします。これは nn.Dropout などをトレーニング中のみに有効にさせるためです。

model, opt = get_model()

for epoch in range(epochs):
    model.train()
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

    model.eval()
    with torch.no_grad():
        valid_loss = sum(loss_func(model(xb), yb) for xb, yb in valid_dl)

    print(epoch, valid_loss / len(valid_dl))
out
0 tensor(0.3679)
1 tensor(0.2997)

10. Create fit() and get_data()

次に、学習と検証をどちらも行える関数 loss_batch を作成します。
loss_batch にオプティマイザを渡すと 逆伝播を計算し、パラメータを更新します。
検証時はオプティマイザを渡さないことで逆伝播が計算されません。

def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)

    if opt is not None:
        loss.backward()
        opt.step()
        opt.zero_grad()

    return loss.item(), len(xb)

fit 関数を定義します。
fit 関数は、各エポックで学習と検証を繰り返し、損失を表示します。

import numpy as np

def fit(epochs, model, loss_func, opt, train_dl, valid_dl):
    for epoch in range(epochs):
        model.train()
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)

        model.eval()
        with torch.no_grad():
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)

        print(epoch, val_loss)

get_data は 学習データと検証データの DataLoader を返却します。

def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )

これで、DataLoader を取得して学習を実行する処理を3行のコードで記述できるようになりました。

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(epochs, model, loss_func, opt, train_dl, valid_dl)
out
0 0.45953697173595426
1 0.3061695278286934

3行のコードをリファクタリングすることで、さまざまなモデルを構築することができます。
畳み込みニューラルネットワーク(CNN)を構築できるか試してみましょう!

11. Switch to CNN

ここからは、3つの畳み込み層でニューラルネットワークを構築します。ここまでに作成した関数はモデルの制約は特にありませんので、変更を加えることなくCNNをに切り替えることができます。

Pytorch で用意された Conv2d クラスを畳み込み層として使用します。3つの畳み込み層でCNNを定義します。各畳み込み層の活性化関数は ReLU です。
最後に、平均プーリング層を追加します。 (ビューはnumpyの変形のPyTorchバージョンです)

class Mnist_CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)
        self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)
        self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)

    def forward(self, xb):
        xb = xb.view(-1, 1, 28, 28)
        xb = F.relu(self.conv1(xb))
        xb = F.relu(self.conv2(xb))
        xb = F.relu(self.conv3(xb))
        xb = F.avg_pool2d(xb, 4)
        return xb.view(-1, xb.size(1))

lr = 0.1

Momentum は、確率的勾配降下法のバリエーションで、直前の更新値も考慮され、一般により高速なトレーニングにつながります。

model = Mnist_CNN()
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
out
0 0.7808464194297791
1 0.6988550303936004

12. nn.Sequential

torch.nnには、コードを単純にするために使用できるもう1つの便利なクラス、Sequential があります。 Sequentialオブジェクトは、その中に含まれる各モジュールを順次実行します。ネットワークを簡単に記述できるのが特徴です。

Sequential を利用するためには、カスタムレイヤーが必要な場合があります。PyTorch にはネットワーク(層)の次元を変換するレイヤーがないため、独自でビューレイヤーを作成する必要があります。
以下の Lambda は Sequential で扱う入出力層を定義します。

class Lambda(nn.Module):
    def __init__(self, func):
        super().__init__()
        self.func = func

    def forward(self, x):
        return self.func(x)


def preprocess(x):
    return x.view(-1, 1, 28, 28)

Sequential は、以下のように簡単にネットワークを記述することができます。

model = nn.Sequential(
    Lambda(preprocess),
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AvgPool2d(4),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
out
0 0.4288556560516357
1 0.2115058801174164

13. Wrapping DataLoader

作成した CNN はかなり簡潔ですが、以下の制約があるため MNISTデータ(手書き数字画像)でのみ機能します。

  • 入力は28 * 28のデータである必要があります。
  • 最終的なグリッドサイズは4 * 4であると想定しています(カーネルサイズ 4 の2次元平均プーリングを利用しているため)

これらの2つの仮定を取り除いて、モデルが任意の2Dシングルチャネル画像(単色画像)で機能するようにします。
まず、最初のLambdaレイヤーを削除し、データの前処理をDataLoaderに移動します。

def preprocess(x, y):
    return x.view(-1, 1, 28, 28), y


class WrappedDataLoader:
    def __init__(self, dl, func):
        self.dl = dl
        self.func = func

    def __len__(self):
        return len(self.dl)

    def __iter__(self):
        batches = iter(self.dl)
        for b in batches:
            yield (self.func(*b))

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)

次に、nn.AvgPool2dをnn.AdaptiveAvgPool2dに置き換えます。
これにより、入力テンソルではなく、必要な出力テンソルのサイズを定義できます。
その結果、平均プーリング層は任意のサイズの入力で動作します。

model = nn.Sequential(
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AdaptiveAvgPool2d(1),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

試してみましょう。

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
out
0 0.3351769802570343
1 0.2583931807518005

14. Using your GPU

CUDA対応のGPUが利用できる場合(ほとんどのクラウドプロバイダーから1時間あたり約0.50ドルでGPUが利用できます)、学習を高速化できます。
まず、GPUがPytorchで動作していることを確認します。
※ Google Colaboratory でGPUを利用するには、「ランタイム」 > 「ランタイムのタイプを変更」から「ハードウェア アクセラレータ」を「GPU」にします。

print(torch.cuda.is_available())
out
True

次に、デバイスオブジェクトを作成します。
デバイスオブジェクトには GPUが利用できる場合は「cuda」、利用できない場合は「cpu」がセットされます。

dev = torch.device(
    "cuda") if torch.cuda.is_available() else torch.device("cpu")

バッチをGPUに移動する前処理を書き加えます。

def preprocess(x, y):
    return x.view(-1, 1, 28, 28).to(dev), y.to(dev)

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)

最後に、モデルをGPUに移動します。

model.to(dev)
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

処理速度が速くなったことが確認できます。

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
out
0 0.1938392831325531
1 0.18594802458286286

Google Colaboratory で確認すると、CPUだと15秒くらいかかっていた上記の処理が5秒くらいで完了しました。

15. Closing thoughts(まとめ)

このチュートリアルで、モデルに依存しないデータ処理と学習処理が作成できました。
データ拡張(Data Augmentation)、ハイパーパラメータ調整、モニタリングト​​レーニング、転移学習など、追加したいことがたくさんあります。
これらの機能はfastaiライブラリで利用できます。fastaiライブラリは、このチュートリアルで示したものと同じ設計アプローチを使用して開発されており、機械学習をさらに習得する人には、よいステップになるでしょう。

このチュートリアルでは、torch.nn、torch.optim、Dataset、DataLoader を利用しました。
これまでに見てきたことをまとめてみましょう。

  • torch.nn
    • Module:関数のように呼びだせるオブジェクトで、ニューラルネットワーク層や重みを保持します。パラーメータも保持していて、勾配を計算したり、重みを更新する反復処理を実行します
    • Parameter:勾配の計算中に更新が必要な重みがあることをモジュールに通知する tensor のラッパー。 require_grad 属性が設定されたテンソルのみが更新されます
    • functional:活性化関数、損失関数や畳み込み層や線形層などの層を含むモジュール
  • torch.optim:SGD(確率的勾配降下法)などのオプティマイザーが含まれ、勾配計算中にパラメーターの重みを更新します
  • Dataset:TensorDataset などの クラスを含む、lenおよびgetitemを持つ抽象インターフェース
  • DataLoader:任意の Dataset を受け取り、データのバッチを返すイテレータを作成します

16. 最後に

以上が「What is torch.nn really?」です。
前回と似たような内容でしたが、Pytorch とニューラルネットワークの理解を深めることができました。
次回は「Visualizing Models, Data, and Training with TensorBoard」を進めたいと思います。

履歴

2020/10/10 初版公開

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

MkDocsのテーマとそのカスタムについて

はじめに

個人的にDjangoをやっていたのですがサーバー代が馬鹿にならないので静的サイトジェネレーターについて調べ始めました。巷ではGatsbyなどが流行っているようですが出来るだけPythonで楽をしたいのでMkDocsについて書きます。なおこの記事はテーマの自作について取り扱うのではなく、あくまで既存のテーマとそのカスタムに範囲が留まっています。

既存のテーマを使おう

公式のドキュメントに記載がありますが、デフォルトでインストールされているテーマを使用する場合にはtheme: テーマ名という一文をmkdocs.ymlに記述するだけで大丈夫です。

例としてreadthedocsというテーマを設定する場合にはtheme: readthedocsという一文を追記するだけで追記するだけでいいのです。簡単ですね!

MkDocsにはコミュニティによって作られたテーマが沢山あります。中でもかなり有名なのがマテリアルデザインを取り入れたmkdocs-materialです。外部のテーマを取り入れるのもかなり簡単で

pip install mkdocs-material

から

mkdocs.yml
theme:
  name: material

とするだけです。このテーマに関しての詳細は公式のgithubページをご覧ください。他にも面白テーマや実用的なテーマがこのページに多く載っています。ぜひ確認してみてくださいね。

カスタムCSS/Javascript

CSSやJavascriptを追加したいだけならばとても容易にできます。Documentation directoryに追加したいCSSやJavascriptのファイルを置くだけです。ここにより詳しく書いてありますが、例えばdocumentation directory以下にある

extra_css:
    - css/extra.css
    - css/second_extra.css

のような構造はextra_cssをcssというサブディレクトリと共に追加します。Javascriptの場合も同じ要領で追加できます。

テーマの上書き

公式ドキュメントによるとテーマをカスタマイズには新しいディレクトリをdocumentation directoryと同じ階層に作る必要性がありそうです。

mkdocs custom_theme

それができたらmkdocs.ymlにてカスタムテーマがどこにあるのか指示してあげましょう。

mkdocs.yml
theme:
    name: mkdocs
    custom_dir: custom_theme/

custom_dir内で使用しているテーマに含まれているファイルと同じファイル名のファイルを作成すると自動的に現在使用しているテーマのファイルが新しく作ったファイルに置き換えられます。また、使用しているテーマに含まれない名前のファイルを作成すると自動的に既存のテーマに追加されます。

テンプレートブロックの上書き

htmlファイルを上書きする際にはbase.htmlを継承する方が楽です(もちろんbase.html自体を上書きする場合はその限りではありません)。

{% extends "base.html" %}

{% block htmltitle %}
<title>Custom title goes here</title>
{% endblock %}

このような形でテンプレートブロックを書くことで簡単に継承ができますね。これも詳細は前述の公式ドキュメントに詳しく載っています。

最後に

近々テーマの自作についての記事も書くかもしれません。間違いがあるようでしたら編集リクエスト等を積極的に送っていただけるとありがたいです。

参考

MkDocs
Material for MkDocs
Styling your docs
Custom themes

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

Pythonでyoutubeを再生する

はじめに

計算が終わったのがわかるようにbeep音を鳴らそうと思いましたが、Macだと難しかったです。
音声ファイルを入手するのも面倒だったので、youtubeを再生するようにしました。
Pythonだと標準モジュールでかなり簡単にできました。
CMが再生されるのがたまに傷。。。

環境

mac OS Catalina 10.15.6
Python 3.5.5

コード

play_youtube.py
import webbrowser
webbrowser.open("再生したい動画のURL")

かなり簡単、2行でできちゃった。
ちなみに僕が使ってるのはこれ

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

[メモ] pandasのunstack

TL;DR

multiIndexのseriesを見やすくするのにunstackが便利でした。

group by後の処理

pandasのDataFrameは複数のcolumnでgroupbyをするとindexがMultiIndexというものになります。処理するのに少し詰まったので備忘録として、やったことを書きます。

環境

ここでは、

  • Python == 3.8
  • pandas == 1.1.3

で実行しています。

データの準備

たとえば、つぎのようなデータがあったとします。

import datetime
import random
import pandas as pd

item_list = ['A', 'A', 'A', 'B', 'C','C', 'D']
data_records = []
ts = datetime.datetime.now()
for _ in range(1000):
    ts += datetime.timedelta(seconds=random.randint(200, 3600))
    data_records.append({
        'ts': ts,
        'wday': ts.weekday(),
        'item': random.choice(item_list),
        'qty': random.randint(1, 5)
    })
df = pd.DataFrame(data_records)

dfとして
Screenshot from 2020-10-10 00-35-09.png
こんな感じのものが得られるはずです。

ここで、

  • ts: タイムスタンプ
  • wday: 曜日
  • item: 商品(ID)
  • qty: 個数

を何かのECサイトのログのようなものを想定します。

やりたいこと

ここで、曜日ごとにどのアイテムが合計どれだけ売れるのかをみたいとします。本当はtsで期間を指定するのがふつうですが、それはおいておいて、次のようなことをすると思います。

df.groupby(['wday', 'item']).qty.sum()

すると次のようなものが得られます。
Screenshot from 2020-10-10 00-40-42.png
悪くはないのですが、もう一つ見にくいです。ここで、unstackをすると、

df.groupby(['wday', 'item']).qty.sum().unstack()

Screenshot from 2020-10-10 00-42-01.png
となりました。

参考

くわしくはpandasの公式ドキュメントにしっかりと書いてあります。

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