- 投稿日:2020-10-10T23:16:00+09:00
メモ: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のファイルを開き,動かすと…
Javaのエラーメッセージが出現します.
エラーは以下のようなことが書かれており…java.lang.IllegalArgumentException: Cannot create PyString with non-byte value調べたところ,PyStringの引数に全角文字が含まれているときに出るエラーとのことでした.
解決方法(多分)
動作させるPythonファイルのファイルパスに全角文字が含まれているのが問題だったようです.
動作ファイルが含まれるフォルダ名をアルファベットに変更すると解決しました.簡単!色々試してみたところ,今回のエラーに関係するのは
- 対象のファイル名
- 対象のファイルパスに含まれるすべてのフォルダ名
だと思われます.画像ファイルは,Fijiで開きアクティブにしておけば全角文字を含んだファイルパスでも問題ないですが,パスをPython側で指定して開く場合,同様に全角文字が含まれないパスにする必要がありそうです.
念の為,ホームディレクトリの.localizedファイルを削除したりもしてみましたが,動作には関係ありませんでした.
感想
こんな簡単なところで躓くとは…どおりで誰も何も書いていないわけだよ!
- 投稿日:2020-10-10T23:02:04+09:00
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...) }
- 投稿日:2020-10-10T21:35:47+09:00
【メモ】BeautifulSoup4の使い方(3) class_で記事の見出しを表示
前回はfind_allを使って見出しを表示したが、今回はclass_を使って見出しを表示する。また、Yahoo!Japanをスクレイピングすることにした。
In[1]BeautifulSoupとRequestsをimportする
In[1]from bs4 import BeautifulSoup import requestsIn[2]RequestsでYahoo!Japanのurlを取得し、テキストを表示する
In[2]toget_url =requests.get("https://www.yahoo.co.jp/") toget_url.textIn[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)これで見出しが表示できた。
- 投稿日:2020-10-10T20:52:17+09:00
性懲りもなく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.pyimport 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.txt02 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.pyimport 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回ですよ!
- 投稿日:2020-10-10T20:34:53+09:00
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!
最後まで読んでいただきありがとうございました。
またお会いしましょう。
- 投稿日:2020-10-10T20:07:45+09:00
将棋AIで学ぶディープラーニング on Mac and Google Colab 第8章
戦略
グリーディー戦略
貪欲法。単純にニューラルネットワークの出力値の最も高い手を選ぶ方法。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の確率でランダムに返す簡単な例として出力が5個(出力1が-0.2、出力2が0.3、出力3が0.5、出力4が0、出力5が-0.6)の場合のexp出力までの処理を図示する。温度は1とする。
温度を設定すると温度が小さいほど各出力の大きさが近くなる。つまり、温度が小さいほど指し手の確率が均等になっていく。
第8章では最後にランダム性を持たせる処理をしている。ランダム性を持ちつつ確率が高い手ほど選ばれやすい。第12章ではこの処理はしていない。よく理解できていないが使い方に合わせているのか。
return np.random.choice(len(logits), p=probabilities) # choice(i, p=b)は0~i-1までの数値をbの確率でランダムに返す
- 投稿日:2020-10-10T19:49:13+09:00
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__.py
はroot\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と同じです。上記の既述は
Pipfile
のpyyaml = "*"
と等価です。アップロード
- 参考記事に6従い
.pypirc
を作る。Windows 10の場合C:\Users\[UserName]
のフォルダに保存するpip install twine wheel
する(いろいろ苦戦していたので他のこともやっていたかもしれない)python setup.py sdist bdist_wheel
するtwine upload --repository testpypi dist/*
する- 問題なければ
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ヶ月以内」にするなどして対策しましょう。半年以上前の記事だとどこかしら内容が変わっている可能性があります(記述の内容についてはそれ以前の記事も参考になりますが)。
参考文献
- 投稿日:2020-10-10T19:17:12+09:00
[Python]loggingモジュールざっくり理解
登場人物
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-handlersFormatter : ログのフォーマットを指定。Handlerに指定する。
Filter : どのログを出力するかの管理。LoggerもしくはHandlerに指定。
Loggerの階層構造
Loggingのフロー
上の階層構造の
mod2
とmod2.fuga
を用いてロギングのフローを確認する。
https://docs.python.org/ja/3/howto/logging.html#logging-flowloggingtest.pyimport 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.pyimport 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他も試すとわかるが、loggingtest2.pyのハンドラの条件を"CRITICAL"以上に設定すれば
child:~~
は出力されなくなり、getLogger()
のドットを外し、階層構造をフラットにすると親だったmod2のハンドラは呼ばれなくなるためchild:~~
だけが出力される。propagate属性
propagateの直訳は「伝播する」であり、この属性はLogRecordを親へ伝播させるかを指定する。
上のコードでlogger.propagate = False
をアンコメントすることで親が呼ばれないことが確認できる。ロギングの設定の外だし
loggingに対する設定は
logging.conf
という別ファイルに記述し、fileConfig()
で読み出すことが可能。これによりロギングの設定部分を処理本体から切り離すことができる。詳細は公式ドキュメント参照。
- 投稿日:2020-10-10T19:09:50+09:00
[Python] e-GOV法令APIより法令本文を取得する
日本の法令データをe-Gov法令APIより取得し、法令本文を整形する方法についてまとめました。下記Qiita記事を参考にしています。
- 法令APIをGoogle Colab (Python) からアクセスする
- e-Gov法令APIとXML Pythonを用いた特定ワードが含まれる法令条文の抽出
- 本文の整形:【Python3】括弧と括弧内文字列削除
最後の「まとめ」に登場するクラスを含めて、本記事の各コードは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.pydef 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.pydef 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.pyclass 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. あとがき
自然言語処理の題材として、日本の法令をダウンロードして整形しました。
お疲れさまでした!
- 投稿日:2020-10-10T17:29:21+09:00
Codeforces Round #521 (Div. 3) バチャ復習(10/9)
今回の成績
今回の感想
F1までは解けるべき問題でしたが、DPをバグらせてしまいました。基本の方針はあっていたのですが、遷移先を固定するか遷移元を固定するかで迷ったりインデックスでミスがあったりしたので、反省です。
A問題
ABCのC問題で見そうな問題でした。
$a$で進んで$b$で戻る操作を一つと見ると、この操作は$[\frac{k}{2}]$回行います。また、$k$の場合は最後にもう一回$a$だけ進むので以上の合計を求めれば良いです。A.pyfor _ 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.pyn=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.pyn=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.pyn,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問題
今回は飛ばします。
- 投稿日:2020-10-10T16:10:58+09:00
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))
- 投稿日:2020-10-10T15:11:50+09:00
【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その4~
はじめに
みなさん、初めまして。
Djangoを用いた投票 (poll) アプリケーションの作成過程を備忘録として公開していこうと思います。
Qiita初心者ですので読みにくい部分もあると思いますがご了承ください。シリーズ
- 【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その0~
- 【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その1~
- 【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その2~
- 【初心者】【Python/Django】駆け出しWebエンジニアがDjangoチュートリアルをやってみた~その3~
作業開始
簡単なフォームを書く
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です。
結果ビュー
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.pyfrom 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ボタンを押すと以下の画面に遷移します。
汎用ビューを使う: コードが少ないのはいいことだ
コードの縮小化を行っていきます。特に、冗長部分を排除していきます。
これらのビューは基本的な 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.pypath('', 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属性を用いると、指定したテンプレートを使うようになります。
contextobject_name属性を用いると、指定したコンテキスト変数を使うようになります。DetailView汎用ビューは"<アプリ名>/<モデル名>detail.html"テンプレートを使い、"<モデル名>"コンテキスト変数を渡してレタリングを行います。
template_name属性を用いると、指定したテンプレートを使うようになります。
context_objectname属性を用いると、指定したコンテキスト変数を使うようになります。
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,)))今日はここまでにします。ありがとうございました。
- 投稿日:2020-10-10T13:04:16+09:00
Codeforces Round #609 (Div. 2) バチャ復習(10/8)
今回の成績
今回の感想
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.pya,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.pyn=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問題
上記の四つのいずれかの点を経由して$(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.pyimport 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)
放射状に数が出てくることはわかりますが、$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.pyn,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問題
今回は飛ばします
- 投稿日:2020-10-10T13:02:24+09:00
ディレクトリ内のファイル数を調べる
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.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-10T12:29:57+09:00
ヨドバシドットコム購入明細メールの商品一覧から商品名と価格を抜き出す
表題の通り、ヨドバシドットコムの購入明細メールから、商品名と価格の組み合わせを取得するプログラムを、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などの自動処理サービスを使う場合も、プログラミングの知識感覚があった方ができることの幅は広がります。
なので、本職でない人・そのようなことを仕事でする予定がない人も、できて損をしないものではないでしょうか と、もっともらしいことを言ってみる。
まあ、願わくばこういうそれっぽいスクリプト群を気軽に作成できて、気軽に実行できるランチャーみたいな環境があるといいっちゃいいのですが・・・。
- 投稿日:2020-10-10T12:28:02+09:00
画像を一括リサイズ
何百枚でも何千枚でもリサイズできます。
縦横比を保たずリサイズ
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))縦横比を保ってリサイズ
横幅を指定する場合
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.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-10T12:10:50+09:00
Actix-webパフォーマンス
webアプリとRustに興味を持ちまして、せっかくなのでどの程度早く動作するのかwebフレームワークのパフォーマンスを比較してみました。
環境windows 10 pro Intel(R) Core(TM) i5-7300U CPU @ 2.60GHz 2.71GHz RAM: 8.00 GB system 64bitwebサーバ作成
使用するwebフレームワーク
Rust: Actix-web
Python: Flask
Julia: GenieHello Worldアプリ作成
先ずは、Actixコマンドcargo new hello-world cd hello-world\hello-world\srcのmain.rsを編集する。
main.rsuse 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.pyfrom 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.jlusing 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.pngactixの結果
Flaskの結果
Genieの結果
Flask < Genie < actixということで、actixのパフォーマンスが優れているようです。
最後にactixの接続数を増やしたときのグラフ(10,100,1000,10000)
100までは負荷がかかっていないですが、1000になると影響が出るようです。
以上
- 投稿日:2020-10-10T11:01:11+09:00
numpy配列の要素をfloatからintに変換
floatのデータセットから一部の数値をintで切り出したい。
target = np.array(features[:,7], dtype=int)
- 投稿日:2020-10-10T10:47:31+09:00
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.rbs = 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.rbif i + x <= s dp[i + x] += dp[i] dp[i + x] %= 1000000007 else break end今回はジャストでよいので、
i + x <= s
についてのみdpを加算します。Python
python.pyfrom 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 に詳しくなった
- 投稿日:2020-10-10T10:45:36+09:00
【Python】Docker で OpenCV 環境構築 ( cv2.imshow() も動く )
以前このような Qiita ( Ubuntu, Python, OpenCV な環境を Docker 上でつくった ) を書いたのですが
cv2.imshow()
が機能せずいろいろ試行錯誤していました。この記事では Docker 環境でもちゃんと
cv2.imshow()
が動くようにする方法を書きます。ホスト OS は MacOS Catalina です。
1. XQuartz インストール
インストールしたら 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/bash4. 各種アップデート・インストール
コンテナ内で以下を実行
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 scipy5. 動作チェック
ホスト PC で XQuartz 立ち上げる
Jupyter 起動 ( コンテナ内 )
jupyter lab --ip=0.0.0.0 --allow-root --LabApp.token=''
ホスト PC のブラウザで http://localhost:8888/ にアクセス
Jupyter で以下を実行
import cv2 img = cv2.imread('test.jpeg') # なにか適当な画像を入れておく img.shape # => ( 画像の縦の長さ, 横の長さ, 3 ) が返ってくる cv2.imshow('img', img) # 画像を表示したいときはこの 3 行セットで実行する cv2.waitKey(0) cv2.destroyAllWindows
以上がエラーなく動作すれば OK !! お疲れさまでした !!!
- 投稿日:2020-10-10T09:06:53+09:00
Django シフト作成機能 曜日の基本勤務シフト登録機能を追加
①シフト表 管理者以外は時間の編集はできないようする
②シフト表 シフト作成は曜日でコピーしてほしい
③希望シフト 毎月5日までにシフト希望を聞いて10日までに作成し配布しているので5までに入力させる入力制限
④掲示板 施設ごとにお知らせをする
⑤タスク 何をやらないといけないかを引き継ぎとかできると最高①は終了しているので、着手したのが②です。
ユーザー情報と新たにBaseShiftテーブルを作成して、そちらに基本シフトを登録するようにして対応しました。
schedule/models.pyclass 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.pydef 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.前月の同日をコピーこれで、編集前の状態になりますね!
数時間かかりましたが、無事に動作確認できたので良かったです!
- 投稿日:2020-10-10T08:42:12+09:00
日別訪問者数の最大平均区間(large)
0.問題
対象となる全日数をDDとし、全日数のうちあるN日間に訪問してくる
人数の平均が最大になる区間の開始日の候補日の数と、それらの候補日の中で
最も早い日をあげなさい。この問題を解くに当たり考えたことをまとめてみた。
下記の手順でクリアできた。1.最初に図を書いてみる
与えられのは以下の二行。
一行目に全日数(DD)と求めたい区間の日数(N)\\ 二行目にそれぞれの日数の訪問人数(A_i(i=1,,,DD))
5 3
1 2 3 2 1
上記のように考えていくと、
開始日が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))
- 投稿日:2020-10-10T08:16:40+09:00
【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)以降では 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())outtensor([[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)outtensor([-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))outtensor(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))outtensor(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))outtensor(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 + biasmodel で呼び出していた log_softmaxも不要になります。(cross_entropyに含まれます)
損失と精度が以前と同じであることを確認しましょう。print(loss_func(model(xb), yb), accuracy(model(xb), yb))outtensor(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))outtensor(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))outtensor(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))outtensor(2.3661, grad_fn=<NllLossBackward>)関数化した fit を呼び出して学習します。
fit() print(loss_func(model(xb), yb))outtensor(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))outtensor(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 TensorDatasetTensorDataset 作成時に 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))outtensor(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))outtensor(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))out0 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)out0 0.45953697173595426 1 0.30616952782869343行のコードをリファクタリングすることで、さまざまなモデルを構築することができます。
畳み込みニューラルネットワーク(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.1Momentum は、確率的勾配降下法のバリエーションで、直前の更新値も考慮され、一般により高速なトレーニングにつながります。
model = Mnist_CNN() opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9) fit(epochs, model, loss_func, opt, train_dl, valid_dl)out0 0.7808464194297791 1 0.698855030393600412. 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)out0 0.4288556560516357 1 0.211505880117416413. 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)out0 0.3351769802570343 1 0.258393180751800514. Using your GPU
CUDA対応のGPUが利用できる場合(ほとんどのクラウドプロバイダーから1時間あたり約0.50ドルでGPUが利用できます)、学習を高速化できます。
まず、GPUがPytorchで動作していることを確認します。
※ Google Colaboratory でGPUを利用するには、「ランタイム」 > 「ランタイムのタイプを変更」から「ハードウェア アクセラレータ」を「GPU」にします。print(torch.cuda.is_available())outTrue
次に、デバイスオブジェクトを作成します。
デバイスオブジェクトには 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)out0 0.1938392831325531 1 0.18594802458286286Google 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 初版公開
- 投稿日:2020-10-10T04:58:55+09:00
MkDocsのテーマとそのカスタムについて
はじめに
個人的にDjangoをやっていたのですがサーバー代が馬鹿にならないので静的サイトジェネレーターについて調べ始めました。巷ではGatsbyなどが流行っているようですが出来るだけPythonで楽をしたいのでMkDocsについて書きます。なおこの記事はテーマの自作について取り扱うのではなく、あくまで既存のテーマとそのカスタムに範囲が留まっています。
既存のテーマを使おう
公式のドキュメントに記載がありますが、デフォルトでインストールされているテーマを使用する場合には
theme: テーマ名
という一文をmkdocs.yml
に記述するだけで大丈夫です。例として
readthedocs
というテーマを設定する場合にはtheme: readthedocs
という一文を追記するだけで追記するだけでいいのです。簡単ですね!MkDocsにはコミュニティによって作られたテーマが沢山あります。中でもかなり有名なのがマテリアルデザインを取り入れた
mkdocs-material
です。外部のテーマを取り入れるのもかなり簡単でpip install mkdocs-material
から
mkdocs.ymltheme: 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.ymltheme: 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 %}このような形でテンプレートブロックを書くことで簡単に継承ができますね。これも詳細は前述の公式ドキュメントに詳しく載っています。
最後に
近々テーマの自作についての記事も書くかもしれません。間違いがあるようでしたら編集リクエスト等を積極的に送っていただけるとありがたいです。
参考
- 投稿日:2020-10-10T01:10:13+09:00
Pythonでyoutubeを再生する
はじめに
計算が終わったのがわかるようにbeep音を鳴らそうと思いましたが、Macだと難しかったです。
音声ファイルを入手するのも面倒だったので、youtubeを再生するようにしました。
Pythonだと標準モジュールでかなり簡単にできました。
CMが再生されるのがたまに傷。。。環境
mac OS Catalina 10.15.6
Python 3.5.5コード
play_youtube.pyimport webbrowser webbrowser.open("再生したい動画のURL")かなり簡単、2行でできちゃった。
ちなみに僕が使ってるのはこれ
- 投稿日:2020-10-10T00:44:39+09:00
[メモ] 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)ここで、
- ts: タイムスタンプ
- wday: 曜日
- item: 商品(ID)
- qty: 個数
を何かのECサイトのログのようなものを想定します。
やりたいこと
ここで、曜日ごとにどのアイテムが合計どれだけ売れるのかをみたいとします。本当は
ts
で期間を指定するのがふつうですが、それはおいておいて、次のようなことをすると思います。df.groupby(['wday', 'item']).qty.sum()すると次のようなものが得られます。
悪くはないのですが、もう一つ見にくいです。ここで、unstack
をすると、df.groupby(['wday', 'item']).qty.sum().unstack()参考
くわしくはpandasの公式ドキュメントにしっかりと書いてあります。