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

ワードファイル(.docx)の段落ごとにエクセル(.xlsx)に格納する(Google Colab対応)

はじめに ワードに書かれた段落ごとの情報をエクセルに列ごとに格納したいことがあり、手作業でやるには量が多かったのでコード化してみました。毎度ですが、コードは自己責任での利用をお願いします。 手順 ①モジュールとして、docxとopenpyxlを使います。 ②まずワードファイル(original.docx)から文書を読み込んでパラグラフごとにリストに格納をしていきます。 ③次にエクセルのワークブック・ワークシートを作成します。 ④作成したワークシートにリストの内容を順に書き込んでいきます。ただし、改行を2回しているような場合、リストに空の要素が含まれているので、それらは除外するようにします。 ⑤最後にワークシートをエクセルファイル(output.xlsx)として保存します。 Google Colaboratoryを使う場合 モジュールのインストールが必要になるので、以下を実行してください。 !pip install python-docx !pip install openpyxl また、Google Driveを使うので、あらかじめGoogle DriveとGoogle Colabをつなげておく(マウントする)必要があります。ファイルのところでGoogle Driveのアイコンをクリックしてマウントするか、以下のコードを実行しておきます。 from google.colab import drive drive.mount('/content/drive') いざコード import docx doc = docx.Document("/content/drive/MyDrive/original.docx") num = 0 data = [] # wordファイルからの読み込み for para in doc.paragraphs: num = num + 1 data.append(para.text) import openpyxl # 新しいブックの作成 wb = openpyxl.Workbook() # シートの取り出し ws =wb.worksheets[0] # EXCELへの書き込み(ワードファイルの改行の仕方によって空のデータがありうるので、 # それをそのまま書き込まない条件を追加しています。) num = 0 for i in range(0,len(data)): if len(data[i])==0: continue ws.cell(row=num+1,column=1,value=data[i]) num = num + 1 # エクセルファイルとして保存 wb.save('/content/drive/MyDrive/output.xlsx') 最後に ちょっとしたワード、エクセルファイルの扱いに参考になるのではないでしょうか?このコードで使ったモジュールは大変実用的で、手作業でやっていたことを自動化できます。これらのモジュールを解説したブログなどはたくさんありますので、関心がある方はぜひ検索して覗いてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

残人生のためのプログラミング -2回

コンセプトをば 初回では残り人生の日にちを計算しました. 寿命が81歳だとすると,自分にはあと21915日残されています(初回から2日経過したので残り21913日). これは人生最後の日を後悔せずに過ごせるようになるためのプログラミングです. 徹底的な自己管理 とは言ってもまだ学生ですし,当面の目標は学業で良い成績を収め,よりよい会社に勤めることとなります.そこで大切なのが自己管理.それを手助けするタスク管理アプリ作成へ向けて,毎日1歩ないし2,3歩ずつ進んでいきたいと思います. CSVで管理する 前回まではTaskクラスを用意して,随時インスタンスを作成しタスクを登録していましたが,今回からCSVファイルに一括登録,pandasで読込を行っています. pandasをfor文で回すのにitertupleを使ったんですが,その弊害として列の名前で要素を取り出せなくなってます.次回までに何かいい方法見つけたいなぁ. tasks.csv タスク名,期限,時間,重要度,備考 Qiitaの記事を投稿する,2021年6月4日,,low,毎日 課題を提出する,2021年6月9日,23:59,high, Toeicの試験,2021年6月20日,13:00,high, コインランドリー,2021年6月5日,7:00,mid,毎週 TaskNotify_csv.py import requests import datetime import pandas as pd class Task: def __init__(self, name, date): self.name = name self.date = datetime.datetime.strptime(date, '%Y年%m月%d日').date() self.sentence = "{}まで残り{}日\n".format(name, abs(self.date-today).days) def createSend(tasks): sentences = "\n" for s in tasks: if s.date >= today: sentences += s.sentence return sentences if __name__ == '__main__': today = datetime.date.today() csv_tasks = pd.read_csv("tasks.csv", sep=',') list_tasks =[] for row in csv_tasks.itertuples(): list_tasks.append(Task(row[1], row[2])) send_dict = {'message': createSend(list_tasks)} TOKEN = 'hoge' url = 'https://notify-api.line.me/api/notify' TOKEN_dict = {'Authorization': 'Bearer ' + TOKEN} requests.post(url, headers=TOKEN_dict, data=send_dict) 次回,第-3回は... 単純なタスクとルーティン(習慣的なもの)を分けて実装していきます.Toeicとか課題は使い捨て(期限を過ぎると削除?),コインランドリーは数日ごとに再登録みたいな感じにしようかな.
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

無線局等情報の包括免許から市区町村の局数をスクレイピング

import requests from bs4 import BeautifulSoup headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64; Trident/7.0; rv:11.0) like Gecko" } def fetch_soup(url, parser="html.parser"): r = requests.get(url, headers=headers) r.raise_for_status() soup = BeautifulSoup(r.content, parser) return soup # スクレイピング # 楽天モバイル株式会社 関西 url = "https://www.tele.soumu.go.jp/musen/SearchServlet?pageID=4&IT=E&DFCD=0001745556&DD=2&styleNumber=00" # 楽天モバイル株式会社 四国 url = "https://www.tele.soumu.go.jp/musen/SearchServlet?pageID=4&IT=G&DFCD=0000524258&DD=2&styleNumber=00" soup = fetch_soup(url) table = soup.find("table", class_="tableBorder2") # スクレイピング用にマーク for pre in table.select("td > table > tbody > tr > td > pre"): tag = pre.find_parent("table") tag["class"] = "scraping" for t in table.find_all("table", class_="scraping"): s = t.get_text(strip=True) t.replace_with(s) tables = [] for tbl in table.select("td > table"): tables.append(tbl.extract()) # データラングリング import pandas as pd # 無線局免許状等情報 data = [] for trs in table.find_all("tr"): tds = [] for td in trs.find_all("td"): s = td.get_text(strip=True) tds.append(s or None) data.append(tds) df_tmp = pd.DataFrame(data) col = ["名称", "内容"] tmp1 = df_tmp.iloc[:, :2].set_axis(col, axis=1) tmp2 = df_tmp.iloc[:, 2:].set_axis(col, axis=1) df1 = pd.concat([tmp1, tmp2]).dropna(how="all").set_index("名称") df1 df1.to_csv("info.csv", encoding="utf_8_sig") # 電波の型式、周波数及び空中線電力 df2 = ( pd.read_html(tables[0].prettify())[0] .dropna(how="all", axis=1) .set_axis(["型式", "周波数", "空中線電力"], axis=1) ) df2 df2.to_csv("radio_waves.csv", index=False, encoding="utf_8_sig") # 基地局 tds = [] for td in tables[1].find_all("td"): s = td.get_text(strip=True) if s: tds.append(s) tds df3 = ( pd.DataFrame(tds[2:])[0] .str.extract("(.+)\(([0-9,]+)\)") .rename(columns={0: "市区町村名", 1: "局数"}) ) df3["局数"] = df3["局数"].str.replace(",", "").astype(int) flag = df3["市区町村名"].str.endswith(("都", "道", "府", "県")) df3["都道府県名"] = df3["市区町村名"].where(flag).fillna(method="ffill") # 都道府県 df_prefs = df3[flag].reset_index(drop=True) df_prefs df_prefs.to_csv("pref.csv", index=False, encoding="utf_8_sig") # 市区町村 df_cities = df3[~flag].reset_index(drop=True).reindex(columns=["都道府県名", "市区町村名", "局数"]) df_cities df_cities.groupby("都道府県名")["局数"].sum() df_cities.pivot_table(index=["都道府県名", "市区町村名"], values="局数", aggfunc=sum, margins=True) print(tds[1])
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Dash(python)】table に画像を挿入する

1. 概要 Dash のテーブルに画像を挿入したいときありますよね! Dash に関する記事があまりなく、かつテーブルに画像を挿入する方法もググってもなかったので記事にしておきます。 2. 実装 紹介する方法では、テーブルの元となる pd.DataFrame を作成し、そこへ ​html.IMG を挿入するという方法です。この際、画像はbase64でエンコードをする必要があります。少々乱暴ではありますが、一番手っ取り早くできました。 "百聞は一見に如かず" ということでサンプルコードを載せておきます(※画像へのパスを変更すれば動作することを確認していますので試してみてください)。 # __feture__ from __future__ import annotations # 組み込み import os import base64 # サードパーティ import dash import pandas as pd import dash_html_components as html import dash_bootstrap_components as dbc def get_media_type(ext: str) -> str: """拡張子からメディアタイプを取得する Args: ext (str): 画像の拡張子。ex) ".xxx" """ media_type_dict = { ".jpeg": "data:image/jpeg;base64", ".jpg": "data:image/jpeg;base64", ".gif": "data:image/gif;base64", ".png": "data:image/png;base64", ".svg": "data:image/svg+xml;base64", } try: return media_type_dict[ext] except KeyError: raise KeyError(f"Not Support Extention → '{ext}'") def local_img_to_base64(path: str) -> bytes: """ローカルの画像をbase64にエンコード""" with open(path, "rb") as f: img_base64 = base64.b64encode(f.read()) return img_base64 def base64_to_img_tag(img_base64: bytes, ext: str, height: str = "100px", width: str = "200px") -> html.Img: """base64からimg_tagを生成する Args: img_base64 (bytes): base64。 ext (str): 拡張子. ec) .png height (str, optional): 画像の縦幅. Defaults to "100px". width (str, optional): 画像の横幅. Defaults to "200px". Returns: html.IMG.IMG: 生成したimg_tag. """ img_binary = img_base64.decode() media_type = get_media_type(ext) img_tag = html.Img( src=f"{media_type},{img_binary}", height=height, width=width, ) return img_tag def local_img_to_tag(path: str, height: str = "30vh", width: str = "30vh") -> html.Img: """ローカルにある画像 Args: path (str): img_tagに変換する画像へのpath height (str, optional): 変換後のheight. Defaults to "30vh". width (str, optional): 変換後のwidth. Defaults to "30vh". Returns: html.IMG.IMG: 作成したIMGタグ。 """ _, ext = os.path.splitext(path.split("/")[-1]) img_base64 = local_img_to_base64(path) img_tag = base64_to_img_tag(img_base64, ext, height=height, width=width) return img_tag def make_table(): path = "the_carp_boy.jpeg" df = pd.DataFrame({'広島': ["梵", "東出", "栗原", "新井", "嶋", "緒方", "廣瀬", "倉", "黒田"], "巨人": ["高橋由", "谷", "小笠原", "李承燁", "ゴンザレス", "阿部", "小坂", "鈴木尚", "内海"]}) img_list = [local_img_to_tag(path) for _ in range(df.shape[0])] df.insert(0, "img", img_list) table = dbc.Table.from_dataframe( df, className='table-scroll', striped=True, bordered=True, hover=True, ) return table app = dash.Dash(__name__) app.layout = make_table() app.run_server(debug=True) 3. 結果 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python matrix m行n列成分を取り出したい

リストの場合 m = [[10, 20], [30, 40]] print(m[0][1]) 20 matrix クラスを使用する場合 NG import numpy as np m = [[10, 20], [30, 40]] mat = np.matrix(m) mat[0][1] IndexError: index 1 is out of bounds for axis 0 with size 1 OK import numpy as np m = [[10, 20], [30, 40]] mat = np.matrix(m) mat[0].A1[1] 20 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntuでpytorch環境を構築してMNISTを動かす

はじめに 前回の記事[1]でtensorflowの環境を構築した。 pytorchも試してみたくなったのでやってみる。 環境構築といってもpipenvであたらしい仮想環境を作って必要なパッケージをインストールするだけ。 詳しく知りたい人は前回の記事[1]を見てください。 環境 Ubuntu20.04LTS GPU Geforce RTX 2070 super GPUドライバー 450.119.03 CUDA 11.0 cuDNN 8.0 python3.8.10 pipenv pytorchのダウンロード pytorch公式[2]に書いてあるコマンドを利用してダウンロード。 pipenvには探したところなかったのでpipでインストールした。 最新バージョンはCUDA11.0と合うものがないようだったので、pytorch1.8.0をダウンロード。 コマンドがそのまま書いてあるのでそれを打ち込むだけ。 pipでインストールするのでPipfileに反映されない点に注意。 ちなみにpipenvでインストールしたパッケージをpipenvで消すとpip listにも表示されなくなる。 これはpipenvがpipとvirtualenvの組み合わせだからだろう。 MNISTプログラムの作成 前回同様公式のは使わずこちらの記事[3]のプログラムを参考にする。 コピーして実行するもrequestsパッケージが足りないと言われたのでpipenvでインストール。 再度実行するとエラーが出た。 urllib.error.HTTPError: HTTP Error 503: Service Unavailable 要するにmnistのダウンロードサイトがなんか使えないらしい。 調べてみるとこのサイト[3]が見つかった。 サイトから直接ダウンロードしてデータを参照するパスを書き換えればいいらしい。 ダウンロードして解凍。 wget www.di.ens.fr/~lelarge/MNIST.tar.gz tar -zxvf MNIST.tar.gz このあと train_set = MNIST('./', download=True, transform=transforms.Compose([ transforms.ToTensor(), ]), train=True) test_set = MNIST('./', download=True, transform=transforms.Compose([ transforms.ToTensor(), ]), train=False) のように./にパスを変更する。 実行してみるとうまくいった。 おわりに とりあえずpytorchとtensorflowどちらも環境構築と動作確認は出来たので違いを本格的に理解していきたい。 それではまた! 参考文献 [1]:PyTorchでMNIST [2]:INSTALLING PREVIOUS VERSIONS OF PYTORCH [3]:HTTP Error when trying to download MNIST data
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python Threading】time.sleepが邪魔してスレッドを終了出来ない方へ

threadingはフラグ等をチェックせず外部から強制終了させるといったことが出来ないらしいので、ストップフラグを関数に持たせ正常に終了させてあげる必要があります ですがtime.sleepが文中に存在していると指定した時間が経過するまで次のステップに移行する事が出来ません。なのでストップフラグのチェックが出来なくなり稼働中のスレッドを即座に終了出来ないという訳です てな訳で time.sleep内にストップフラグのチェックを入れる 無論Python標準のtime.sleepにそんなオーバーロードは無いので、time.sleepのロジック部分を拝借してストップフラグの部分をぶち込む感じになります https://github.com/biosbits/bits/blob/master/python/time.py import threading import time threads = [] flag = False def fun(num=0): t = threading.currentThread() while getattr(t, "do_run", True): print(f"{num+1} flag's:{flag}") #sleep start = time.time() while time.time() - start < 1: if not getattr(t, "do_run", True): break def main(): threads = [threading.Thread(target=fun, args=(i,)) for i in range(0,5)] for i in threads: i.start() #3秒後にスレッドを強制的に落とす time.sleep(3) for i in threads: i.do_run = False i.join() print("breaked!") if __name__=="__main__": main() おわりです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Seabornの箱ひげ図を都道府県ごとに表示したかったので詳しい人に聞いた話。

はじめに 私はいまデータ分析コンペNishikaを利用して勉強しています。 その中で、Matplotlibを使った可視化は普段あまり行うことがなかったため、わからないことが多く、少し複雑なグラフになるとどうやって描画したらよいかわからなくなってしまいます。 今回私が行いたいと思っていた可視化は、以下のDataFrameに対して、 都道府県ごとにx="建物の構造"、y="取引価格"で箱ひげ図を描画することです。 seabornをあまり使った経験がなく、subplotを使ってどのようにするかがわからず、詳しい方に聞きましたので、そちらについてまとめたいと思います。 可視化の手順 1. 都道府県名のリストを用意 pref_list = df["都道府県名"].tolist() pref_list = set(pref_list) to_listで都道府県名の列の要素をリストに変換して、setで重複を除いています。 こうすることで、47都道府県の名前が入ります。 print(len(pref_list)) # => 47 2. グラフを描画する準備 # 箱ひげ図につかうライブラリの用意 import seaborn as sns # 47都道府県なので、70×50のグラフに7×7(=49)の枠を用意 fig, ax = plt.subplots(7, 7, figsize=(70, 50), sharey=True) 3. 箱ひげ図の描画 for i, pref in enumerate(pref_list): sns.boxplot( x='建物の構造', y='取引価格(総額)_log', data=df[df.都道府県名==pref].sort_values('建物の構造'), ax=ax[divmod(i, 7)])\ .set(title=pref, xlabel=None, ylim=(2,10)) 都道府県名のリストの要素1つずつ(pref)を使ってfor分で処理をします。 sns.boxplotで箱ひげ図を作ることができます。 xに建物の構造、yに取引価格 dataに都道府県名がprefと同じ行をすべて抽出して、建物の構造でソートしたものを渡します。 渡されたdataの"建物の構造"と"取引価格"のカラムを利用して箱ひげ図を作るように設定しました。 axの引数にどの枠にグラフを描画するかを選択します。 axはax[7][7]の49枠があり、各枠に各県ごとに入れていきたいです。 そこで、divmod(i,7)を利用します。 divmodは割り算の商と余りをタプルで返す関数です。 また、axはタプルを利用することが可能です。 たとえば、1つ目の県は divmod(0, 7) = (0, 0)となりax[0][0]に入ります。 2つ目の県は、divmod(1,7) = (0, 1)となり、ax[0][1]にはいります。 このようにすることで各県をそれぞれの枠に描画することが可能です。 そのあとでsetを利用することで、それぞれのグラフにタイトルとylimでy軸の範囲を決めています。 4. 不要な枠を削除する 47都道府県に対して、49個の枠が用意されているため最後の2つを描画しないように設定します。 for i in range(len(pref_list)-7*7, 0): ax[-1, i].axis('off') 最後から、2個目までを非表示にします。 すると以下のようなグラフができました。 plt.show() 最後の2つも描画されていません。 サンプルコード 最後にこの手順を簡単な例で試せるようにコードを載せます。 import pandas as pd import seaborn as sns import matplotlib.pyplot as plt import seaborn as sns # データフレームの準備 pref = ["東京", "東京", "東京", "埼玉", "埼玉", "埼玉", "東京", "東京", "埼玉", "埼玉"] house = ["木造", "鉄骨", "木造", "鉄骨", "木造", "鉄骨", "鉄骨", "鉄骨", "木造", "木造"] value = [100, 120, 100, 80, 80, 90, 90, 130, 100, 60] df = pd.DataFrame({'都道府県':pref, '建物': house, '価格':value}) index 都道府県 建物 価格 0 東京 木造 100 1 東京 鉄骨 120 2 東京 木造 100 3 埼玉 鉄骨 80 4 埼玉 木造 80 5 埼玉  鉄骨 90 6 東京 鉄骨 90 7 東京 鉄骨 130 8 埼玉 木造 100 9 埼玉 木造 60 このデータフレームに対して、都道府県ごとに、x=建物、y=価格で箱ひげ図を作ります。 fig, ax = plt.subplots(1, 2, figsize=(10, 5)) ylim = (50, 150) df = df.sort_values(['都道府県', '建物']) for i, p in enumerate(('東京', '埼玉')): sns.boxplot( x='建物', y='価格', data=df[df.都道府県==p], ax=ax[i])\ .set(xlabel=p, ylim=ylim) plt.show() すると以下のような箱ひげ図が描画されます。 おわりに 今回はseabornで複数描画する方法について説明しました。 このやり方はteratailやstackoverflowで回答をいただき、それをいいところ取りしたものになります。 他にもやり方はあり、多くの回答をいただいていますのでぜひとも参考にしてみてください。 参考記事 tratail:seabornで箱ひげ図を複数作成して表示したい stackoverflow:seabornで箱ひげ図を複数作成して表示したい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「【今年の1問】 2021年開成中-三角すいの体積」を参考にWolframAlphaとonline sympyとFreeCAD でやってみた。

(オリジナルポスト)【今年の1問】 2021年開成中-三角すいの体積 https://sansu-seijin.jp/nyushimondai/2021-nyushimondai/16825/ 手計算1(ryuichi59様より) 7*6*1/2*6*1/3=42 手計算2(tttaaakki様より) 3.5*72/6=42 手計算3(高さの平均より) 以下でいいですか?こじつけてみました。 点Pと点Rの中点MPR(3,3,4.5) 点Qと点Sの中点MQS(3,3,1.0) 高さの平均は、中点MPRと中点MQSの鉛直距離4.5-1.0=3.5cm 底面積=(0,0,0)と(6,0,0)と(6,6,0)と(0,6,0)の正方形の面積=6*6cm2 3次元なので、÷3 ∴体積=(6*6)*3.5/3=42cm3 WolframAlphaで Tetrahedronはできました。 volumeは、「次のように解釈:6 0 2 6 6 4」できませんでした。 未確認。mathematicaは、できると思います。 online sympyで online sympyで、①は実行できました。貼り付けて下さい。 https://live.sympy.org/ ①最初に値を代入してみた。 from sympy import * def myHeron(p1,p2,p3): a=p1.distance(p2) b=p2.distance(p3) c=p3.distance(p1) s = (a + b + c) / 2 return sqrt(s * (s - a) * (s - b) * (s - c)) def myVolumeTetrahedron(p1,p2,p3,p4): ans=Plane(p1,p2,p3).projection(p4).distance(p4)*myHeron(p1,p2,p3)/3 return ans p1,p2,p3,p4= map(Point3D, [(0,0,5),(6,0,2),(6,6,4),(0,6,0)]) ans=myVolumeTetrahedron(p1,p2,p3,p4) print(float(ans),"cm3") # 42.0 cm3 ②最後に値を代入してみた。 online sympyで「Error: Operation timed out.」が、でました。 pycharmで実行できました。もっと簡潔に書けると、思います。 from sympy import * def myHeron(p1,p2,p3): a=p1.distance(p2) b=p2.distance(p3) c=p3.distance(p1) s = (a + b + c) / 2 return sqrt(s * (s - a) * (s - b) * (s - c)) def myVolumeTetrahedron(p1,p2,p3,p4): ans=Plane(p1,p2,p3).projection(p4).distance(p4)*myHeron(p1,p2,p3)/3 return ans var('px py pz') var('qx qy qz') var('rx ry rz') var('sx sy sz') p1,p2,p3,p4= map(Point3D, [(px,py,pz),(qx,qy,qz),(rx,ry,rz),(sx,sy,sz)]) ans=myVolumeTetrahedron(p1,p2,p3,p4) ans=ans.subs([(px, 0), (py, 0), (pz, 5), (qx, 6), (qy, 0), (qz, 2), (rx, 6), (ry, 6), (rz, 4), (sx, 0), (sy, 6), (sz, 0) ]) print(float(ans),"cm3") # 42.0 cm3 ③行列の三次元の座標変換です。底面積×高さ×1/3 以下で質問中。 FreeCAD 0.19のマウス操作でやってみたい。 省略。 FreeCAD 0.19のマクロでやってみた。画像の挿入はこれからです。 ただいま、以下のフォーラムで質問中。 あとがき アドバイスよろしくお願いします。 体積の求め方は、上面、下面それぞれを体積を変えないように、水平にした時の平均高さに、底面積をかける。3次元なので、÷3 (参考)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[ビギナー向け]TouchDesignerとYoutubeLiveを連携させてコメント取ってきちゃうよ vol.3

最終回 ついにvol1とvol2を終えて、最後の ビジュアル化していくところを解説してゆく 今回も動画の解説なので、ぜひまずは下の動画から見てくれよな! ポイント解説 とりあえず最終ゴールがどんな感じになるのか どん こんなんですね。 前回、コメント取得までやりました。 ので、 今回はコメントを3Dモデルに変えて youtubeの画面の前に出していきます コメントを3Dに ここではまずtextTOPにコメントを入れています。 このタイミングはButtonを押すという何ともアナログな方法取っていますが ここら辺の処理はお好みで変えてください(これは前回ですね) CHOPExcuteDATの中で書いたpythonはtextTOPにcommentTableの上から前のコメントを消して新しいコメントを入れて ってやってます(これも前回ですね) そしたらTraceSOPに入れてここでSOPデータ 要は 3Dに変換しています。 ちな、コメントの文字数や言語によってはポイント数が増えて負荷が重くなる可能性があるので TraceSOPのThresholdの値を変えたり、 ここではやってないですがPolyreduceSOPでポリゴン数を減らしてあげると Goodです。 おい!dの丸の穴ふさがってんだけど!という方は、TraceSOPのFliter->Hole FaceをOnにすれば ほらね、言ったでしょ 穴が開いちゃってるんだよね パンドラの箱も、、、、 はい、ふざけはここまでにしときます(笑) Screen画面をキャプチャ 次にYoutubeを開いている画面をキャプチャして それをマテリアルとして使います! ScreenをキャプチャするTOPが ScreenGrabTOP SourceでYoutube開いてる画面を選んで、いい感じにトリミングしましょう ちなみに、撮影のために画面を録画しながらやってたというのはあるんですが ScreenGrabTOPがバカ重くてFPSガタ落ち、、、 なにかもっとうまい方法あれば教えてください。。。 最後謎のBloom 出力前にAddTOPを重ねて、お手軽Bloomとか言ってますけど、 ここは調子乗っただけなので、 見にくいやろ!って方はスルーしてください、、、、 完成 はい!ってなわけで完成しました! ちょっと長くなったんですけど、見てくださりありがとうございます! 私の作例ですが、 コメントを風に変えて風鈴にぶち当ててみました そのままYoutubeLiveに映像も配信しています 都会の喧騒にお疲れではないですか?そんな皆さんに癒しのパーティーをお届けします!あなたのコメントで風鈴を奏で、身も心もきれいになってください#詐欺の広告みたい #鋼鐵塚 #さすらいの風来坊 #touchdesigner #youtubelive #オンラインイベント pic.twitter.com/v8pNAcVfVP— miwa_maroon (@miwata34) June 2, 2021 Instagramにも載っているのでぜひのぞいてみてください また、フォローしていただきますと嬉しいです! ほな!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Ubuntu、tensorflow、pipenvでの深層学習環境構築方法

はじめに Ubuntuでtenosrflowとpipenvによる環境構築をしたのでまとめておきます。 自分は初心者なので、LINUXのコマンドを叩ける人ならわかる解説になっていると思います。 わかっている人からしたら少しくどいと思いますが、自分用なので。 また仮想環境の構築とpython関係のパッケージのインストールはpipenvで行います。 virtualenvとpipなどでもいいですが、便利なのでおすすめです。 OSはUbuntuを想定していますが、それ以外のディストリビューションでも可能だと思います。 環境 環境(構築前) Ubuntu20.04LTS GPU Geforce RTX 2070 super GPUドライバー  450.119.03 環境(構築後) Ubuntu20.04LTS GPU Geforce RTX 2070 super GPUドライバー  450.119.03 CUDA 11.0 cuDNN 8.0 python3.8.10 tensorflow 2.4.0 pipenv 目標環境はtensorflow公式ホームページを参照に決めました。[1] 使用するGPUによってドライバが異なります。 ドライバによって対応しているCUDAが違うので、まずは対応を確認して自分の目指す環境を確認してください。 こちらの記事[2]も対応を確認するには役に立つので目を通しておくといいです。 CUDAのインストール 公式サイト[3]でダウンロードしたいものをクリックしていくとダウンロードするためのコマンドが出てくるのでそれを実行。 CUDA Toolkit 11.0 → LINUX → x86_64 → centOS → version7 →rpm(local) と選択していくとコマンドが表示されます。アーキテクチャはほとんどの人がx86_64だと思いますが、確認したいという人はarchコマンドで確認できます。 最後のはネットワークでも問題ないと思いますが、以前ダウンロードした時に失敗したことがあるのでlocalにしています。 runfileでもダウンロードしてshコマンドでrunfileを実行後、欲しいものを選択すればダウンロードできます。 今回はわかりやすくdebファイルで行っていきます。 一応下に載せておきますが、公式サイトの方を参考にしてください。 wget https://developer.download.nvidia.com/compute/cuda/repos/ubuntu2004/x86_64/cuda-ubuntu2004.pin sudo mv cuda-ubuntu2004.pin /etc/apt/preferences.d/cuda-repository-pin-600 wget http://developer.download.nvidia.com/compute/cuda/11.0.2/local_installers/cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb sudo dpkg -i cuda-repo-ubuntu2004-11-0-local_11.0.2-450.51.05-1_amd64.deb sudo apt-key add /var/cuda-repo-ubuntu2004-11-0-local/7fa2af80.pub sudo apt-get update sudo apt-get -y install cuda インストールしたらパスを通します。 パスを通すに関してはこの記事[4]を 僕は.bashrcにパスを書いてsorceで更新しました。 先頭に.がつくファイルは隠しファイルなのでls -aで表示しましょう。 またsourceコマンドは厳密ではコマンドではなかったような気がします。 なのでインストールしようとしても出てきません。 Ubuntuだと sudo apt install build-essential chekinstall で使用できるようになります。 ちなみにcentOSでは yum groupinstall "Development Tools" とディストリビューションによって異なるので、Ubuntu以外でやってる人は注意してください。 .bashrcの末尾に export PATH="/usr/local/cuda/bin:$PATH" export LD_LIBRARY_PATH="/usr/local/cuda/lib64:$LD_LIBRARY_PATH" を追加したあとにsourceコマンドで更新 source .bashrc which nvcc でパスが出てきたらパスが通っています。 ちなみにたまに.bash_profileに書いてる記事などもありますが、特にこだわりがなければ.bashrcでいいと思います。 一応違いが書いてある記事[5]を載せておきます。 cuDNNのインストール cuDNNの公式サイト[6]からダウンロード。 メンバー登録が必要ですが、Googleアカウントと連携できます。 2021年6月3日現在の最新バージョンはcuDNN v8.2.0ですが、インストールしたいのは古いものなのでそちらをインストールします。 ここは各自のGPUやドライバー、ディストリビューションに応じて各自変えてください。 アーカイブのところをクリックすると過去のものが出てくるのでインストール。 例えばCUDA11.0でcuDNN v8.0がインストールしたければcuDNN v8.0.5 for CUDA 11.0 が確認できる中で最新なのでこれをインストールします。 対応するディストリビューションのX86_64アーキテクチャのパッケージを下記の3つインストールしましょう。 ・Runtime Library ・Developer Library ・Code Samples and User Guide 対応するアーキテクチャ、ディストリビューションのやつをダウンロードしましょう。 クリックしたらダウンロードされます。 ダウンロードしたら解凍しましょう sudo dpkg -i debファイル で解凍できます。 解凍したあとになんかしたような気がするんですが忘れちゃいました。 なんかエラー出たら頑張って解決してください。 cuDNNが使えているかの確認のためにcuDNNのサンプルを実行します。 僕の場合はcuDNNのサンプルは /usr/src/cudnn_samples_v8 にあります。 最後の数字はバージョンに合わせて変えてください。 mnistCUDNNディレクトリに移動して sudo make を行ってください。 パーミッション的に管理者権限じゃないとアクセスできないようでsudoつけました。 結構かかると思いますが終わったら生成されるmnistCUDNNを./mnistCUDNNで実行してください。 ログが流れてTest Pssed!と表示されたらcuDNNを使用できています。 出来てなかったらcuDNNが使えていない可能性が高いです。 なんとか対応してみてください。 Pythonのインストール これは公式サイト[7]にわかりやすく載っています。 ただインストールする際はホームディレクトリにインストールすることをおすすめします。(環境によるとは思いますが) 個人的にシステム標準でついてくるPythonと混ぜたくないのでそうしています。 システムのPythonと混ぜるのは公式でも推奨されていないですし、面倒なことになるのでおすすめしません。 具体的に言うと sudo apt install python-numpy などはしないほうがいいです。 僕が今回ダウンロードするのはPython3-8-10です。 対応している中で最新のものを選びました。 あとは[7]通りにやればOKですが、ローカルにダウンロードする場合はオプションが必要なので注意です。 余談 コマンドでインストールしたい人はPPA(個人が公開するパッケージ的なの)を使ってインストールできますがおすすめしません。 このブログ[8]の方法だとシステムにPythonがインストールされますし、公式ではないので。 おとなしく公式のやり方にしておいたほうがあとあと楽です。 pipenvのインストール pipenvと他のコマンドとの比較についてはこの3つ[9][10][11]を見てください。 そんなのどうでもいいという人でも、pipenvはvitualenvとpip(pip3)の組み合わせみたいなものなので、もとの原理を知っておかないと応用が効きません。 Pythonを上記の手順でインストールしている人は標準でpip3がついているはずです。 間違ってもsudo apt install python3-pipはしないほうがいいです。 わかっている人はいいですが、初心者が手をだすとどこになんのパッケージが入っているのかわからなくなるのでやめたほうがいいです。 詳しいことはこちら[12]。 pipenvのインストールはpip3から行います。 pip3 install pipenv pipenvのインストール先は/home/tumutubo/.local//bin/pipenv になっています which pipenv で確認できます。 もし表示されなかったらパスを通しましょう。 多分いらないですが一応pipenvの更新。 pip3 install -U pipenv 作りたい仮想環境名のディレクトリを作成し、そこで pipenv install で作成(事前にPythonのバージョン指定も出来ます) pipenv shell で仮想環境を有効化。 exit で抜けられます。 which python3などとするとパスが変わっているのがわかります。 その他詳しいことはこちらの記事で[13][14] 余談 pipは環境によって動作が少し変わります。 python2系が入っている場合はそちらにダウンロード、python3系のみの場合はpython3にダウンロードされます。 python2系が入っていない場合はpip=pip3として扱えます。 ただ最新のLinuxシステム標準についてくるPythonもいまは3系ですし、Pythonパッケージをダウンロードしてついてくるのもpip3なのでどちらでもいいと思います。 ぼくは3うつのめんどくさいのでpipとpythonコマンドでやってます。 コマンドの場所を確認したい場合は which pip which pip3 と打てば確認できます。 パッケージのインストール Pipfileが存在するディレクトリでpipenv shellで仮想環境に入ります。 あとはパッケージをインストールするだけです。 使いそうなものを適当にダウンロードしておきます。 pipenv install numpy pandas matplotlib keras requests tensorflow==2.4.0 バージョン指定のやり方は上の通りです。 自分の環境にあったものをダウンロードしてください。 動作確認 tensorflow/kerasを使用したMNISTプログラムを動かします。 ほんとは公式をみてやったほうがいいんでしょうが、公式ドキュメントはあまりまとまって無くて苦手です。 たぶん自分の読解力が足りないんだと思うんですけどね。 ここらへんの記事[15][16]を参考にしてMNISTのサンプルを作成します。 [15]の方はコピペだけで動いたのですが、[16]の方は関数などが古くaccをaccuracyに書き換えないと動きません。 また古い関数を使っているせいか最後の部分が表示されなかったりします。 まあとりあえずtensorflow/kerasが動いているのが確認できたのでとりあえずOKです。 おわりに 研究で機械学習をする必要があったので、自宅環境にtensorflowの動く環境を作ってみました。 かなり大変だったのでLGTMしてくれると喜びます。 ちなみに研究室はアーキテクチャ系なので最終的にはCUDAなどの下層のコーディングをすることになると思います。 大変ですがやはりやっていて楽しいですね。 この記事がみなさんの役に立てたらうれしいです。 それではまた! 参考文献 [1]:ソースからのビルド(TensorFlow) [2]:TensorFlowでGPU学習させるためにCUDA周りではまったときの対処法 [3]:CUDA Toolkit Archive [4]:PATHを通すの意味と通し方 .bash_profile [5]:本当に正しい .bashrc と .bash_profile の使ひ分け [6]:cuDNN Download [7]:Ubuntu環境のPython [8]:Ubuntu16.04にpython3.7を apt install する方法(というか,apt レポジトリに登録されていないpythonのバージョンを導入する方法) [9]:pyenv、pyenv-virtualenv、venv、Anaconda、Pipenv。私はPipenvを使う [10]:Pythonの環境管理ツール良し悪し [11]:Pythonの仮想環境構築についてまとめ【社内向け】 [12]:pip [13]:Pipenvことはじめ [14]:Pipenvを使ったPython開発まとめ [15]:Keras で MNIST データの学習を試してみよう [16]:Keras/TensorflowでMNISTデータセットの画像分類
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】ごみのお知らせをしてくれるLINEBot「ごみのお知らせくん」を作りました。

「あ、今日燃えるごみの日か...!」 朝の通勤中、ごみ捨て場を見て「今日が何のごみの日なのか」を思い出す...。 皆さんはこんな経験ありませんか? 「朝の7時頃に『今日は燃えるごみの日だよ!』とLINEで通知してくれたら嬉しいのになぁ...。」 という訳で今回作ったのが「ごみのお知らせくん」です。 ■「ごみのお知らせくん」とは LINEBot『ごみのお知らせくん』は「今日が何のごみの日なのか」と「このごみはどの日に出せば良いのか」ということを教えてくれるLINEBotです。 機能1. 朝通知 ⏰ 朝の7時頃に「当日のごみ区分」を通知してくれます。 「燃える」「燃やせない」「ペットボトル・有害」「資源」「プラスチック類」の5種類のごみに対応しています。 メッセージ例 燃えるごみの日 →「今日は燃えるゴミの日です。」 プラスチックの日 →「今日はプラスチック類の日です。」 ... 僕の地域では地区によってごみの収集日が違うので、朝通知機能を利用する場合は住んでいている地区の登録が必要です。もし、3区に住んでいる場合は「3区」のようにLINEでメッセージをすると登録がされるようになっています。 機能2. ごみ出し検索 ? 出したいごみをメッセージすると「ごみ区分」と「ごみを出すときのポイント」について教えてくれます。現在は「ひらがな」での検索のみ対応しています。 (例) 「かさ」とメッセージを送る ↓ 検索結果:2件 ・傘・パラソル(傘布) 埋立ごみ(燃やせないごみ) ※ビニール・プラスチック製の物はプラスチックごみに出す。 ・傘・パラソル(傘骨) 金属類(資源物) 傘布は取り、プラスチックなど分別できる物は取る。 ■システム構成 LINEBot『ごみのお知らせくん』はAWS上に構築しました。 DynamoDBは朝通知を行うための地区情報の保存、CloudWatchは朝7時に朝通知を行うメソッドの実行に使用しています。 ごみの収集日やごみ区分については年度内は変わる情報ではないはずなので、それぞれ「ごみの収集日」ファイルと「ごみ区分情報」ファイルから読み取るようにしています。 ■時間をかけた点 1. 「分かりやすさ」の追求 多くの人に使って欲しいと考えていたので「文章の分かりやすさ」「説明を必要最低限にする」「機能を盛り込み過ぎない」というのを念頭に置いて開発を進めました。文章については何度も読み直し、他の人にも分かりやすいかチェックしてもらいました。 LINEを使うサービスで注意しないといけないと考えているのは下記のようなことです。 ・画面上にメニューを表示するとトーク画面が狭くなる ・トーク履歴を遡ることは滅多に無いので見たい内容は常に下にある ・ブラウザバックのように表示している画面の内容を丸々変えることはできない そのため、ユーザーとのやり取りはできる限り少なく済むようにしています。 2. 町内すべての地区に対応 初めは自分の住んでいる地区だけに対応したLINEBotを作る方針で進めていたのですが、「せっかく作るなら他の地区にも対応させたい!」「ゆくゆくはもっとユーザーを増やしていきたい!」という欲が湧いてきました。そこで、ユーザーの住んでいる地区を登録できるようにし、地区ごとに対応したごみの情報を配信できるようにしました。可能な限りシンプルに登録ができるように「3区」とメッセージをすれば登録できるようにしています。 3. Dockerによる開発環境構築で他のLINEBot開発にも応用可能 これは開発面での工夫になりますが、他のLINEBot開発にも使えるようにするため、Dockerでの開発環境の構築を行いました。 今回、Lambdaを使うため、LINEBotのソースとライブラリをzip化する必要がありました。そこでDockerで開発環境を作り、ライブラリのインストール場所をホームディレクトリにしてzip化するという方法を取りました。そのため、このテンプレートがあれば「functions.pyの変更」と「ライブラリのインストール先をルートディレクトリにする」だけで他のLINEBot開発にも使えるはずです。 テンプレートについては後日、githubにアップする予定です。 ■苦労した点 各ごみの収集日を記載したファイルの用意 僕が住んでいる所ではごみの地区、ごみ区分が複数あります。ごみの種類のうち、「不燃ごみ」「ペットボトル・有害ごみ」「資源ごみ」については月に2回ほど収集日があり、地区によって収集日が異なります。 区ごとに各ごみの収集日をファイルにして、今日の日付がファイル内に存在するか否かで今日のごみの日を判別する、というように作ったため、日付を入力したファイルを「区*ごみ区分」の数だけ用意するという作業が必要でした。これはひたすら、ごみ収集カレンダーとにらめっこしながら日付を打ち込みました。 『なるべくシンプルにスモールに』で進めていたため、このようなことが起きましたが、今後も発展していくようであれば『収集日はDBに保存。区と日付を渡せば今日のごみを返してくれるAPI』があればもう少しシンプルになるのかと考えています。 メッセージのおうむ返しまで時間がかかった はじめは処理速度を考慮してGoによる実装を検討していたのですが、「送ったメッセージ内容をそのまま返答する」というシンプルな「オウム返し」機能ですら時間がかかってしまいました。Go,Lambda,LINEBotについて実装経験が豊富な訳ではなかったため、事例が豊富にあったPythonによる実装に切り替えました。 メッセージのオウム返しができてからはスムーズに開発が進んだかなと思います。最初の一歩が大変でした。 ■なぜ作ったのか 身近な人に役立つものを作りたかった 最近訪れた所でとても面白い場所がありました。 そこでは「自分が住んでいる地域の発展のためにみんな"何か"をやっている」という場所です。本業とは別でいろいろなことをやっている方々と出会い、「自分も何か身近な人のために役立つものを作りたい!」と思ったのが、本記事のLINEBot『ごみのお知らせくん』を作ったきっかけです。 LINEBotを作りたかった 「多くの人に使って欲しい」という思いがあったので、多くのスマホにインストールされているであろうLINEを活用しました。LINEBotであれば友達追加をするだけで使うことができる、アプリのようにストアからインストールを行うという手間も省けると考えていました。結果、QRコードを読むだけで友達追加画面になるので、このお手軽さを使ってもっと広めていきたいです。 ■まとめ これが今回一番の気づきだったのですが、高いモチベーションを維持したまま開発できたのは、僕が「作りたいもの」について、他の人に「あったらいいね!」と言ってもらえたのが大きかったと思っています。実際のユーザーが身近であればあるほど、サービスを使ってもらえるイメージがしやすいので最後まで情熱を持って開発しきれました。 「やっぱり、個人開発は楽しいなぁ」としみじみ思いました。次はこのLINEBot『ごみのお知らせくん』を多くの人に使ってもらえるように動いていきます。 それでは! ■参考リンク API GatewayとLambda(Python)でLINE BOT(Messaging API)開発 [前編] https://qiita.com/w2or3w/items/1b80bfbae59fe19e2015 API GatewayとLambda(Python)でLINE BOT(Messaging API)開発 [後編] https://qiita.com/w2or3w/items/fbe588d7147bb8e65628 line-bot-sdk-pythonを使ってみた https://keinumata.hatenablog.com/entry/2018/05/08/122348
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】画像から犬種・猫種を特定する方法

【python】画像から犬種・猫種を特定する方法(読み物) 動機 正直に言うと動物病院に来る犬で、たまに犬種が分からないのがいる。もちろん問診票に犬種を書いてもらうから心配する必要はないが、自分の力で犬種を特定したい。いくら犬種を勉強しても、「ああ、この子は珍しい犬種で日本で5頭しかいないんですよ~」とか言われることもある。 こうなったら機械学習を使おうと思う。備え付けカメラで犬を撮影し、機械学習で犬種を特定する。自分の力で犬種を特定したと言えるのかは甚だ疑問だが、仕方ない。 方法 画像分類タスクは、Convolutional Neural Network(CNN)が得意。CNNを使う事を決めた。さてどう学習させるか。作戦は3つ。 作戦1 犬種ごとに写真を用意 + CNNを0から学習させる 作戦2 犬種ごとに写真を用意 + 学習済みCNNの最終層を再学習させる(fine tuning) 作戦3 犬種ごとに写真を用意 + 重みを固定した学習済みCNNの後に層を追加して学習させる(transfer learning) 作戦1は犬種ごとの写真が大量に必要だ。珍しい犬種なんてどうしよう。作戦2・作戦3は既に画像の特徴を捉える能力を持ったCNNを使うために、少ない画像で済むそうだ。作戦2・作戦3を使うしかない。作戦2・作戦3のどちらにするかは、精度を見て決めればいいのでまずは作業を開始する。 どの学習済みモデルを使うべきか。tensorflowの中に複数の学習済みモデルがあるけど、どれにしよう。以下のgithubレポジトリを見ると他のモデルよりもefficientnetの方がパラメータも少なくて精度が出ている。 https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet efficientnetを使う事にした。さて、efficientnetはどういった画像を学習させているのだろうか、1400万枚以上の画像を学習させて(imagenet)1000クラスも分類できるらしい、すごいな!! とりあえず、適当にうちの犬の写真を分類させてみた。これは、犬ですとか出るのかな。 dog_breed.py from tensorflow.keras.applications import EfficientNetB0 from tensorflow.keras.preprocessing.image import load_img, img_to_array import numpy as np import tensorflow as tf # 写真の前処理を行う※ pic = load_img("/content/dog.jpg", target_size= (224,224,3)) pic = img_to_array(pic).astype('float32') pic = np.expand_dims(pic,axis=0) processedpic = tf.keras.applications.efficientnet.preprocess_input(pic) # efficientnetのモデルを読み込む model = EfficientNetB0(weights='imagenet') # modelを使って犬種・猫種を予想する prediction = model.predict(processedpic) tf.keras.applications.efficientnet.decode_predictions(prediction) [[(‘n02099712’, ‘Labrador_retriever’, 0.42502728), (‘n02093428’, ‘American_Staffordshire_terrier’, 0.04134651), (‘n02109961’, ‘Eskimo_dog’, 0.032269508), (‘n02110185’, ‘Siberian_husky’, 0.031741217), (‘n02093256’, ‘Staffordshire_bullterrier’, 0.024241285)]] Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json 犬どころか、かなり具体的な犬種を区別できているぞ。まさか、すでに犬種の分類タスクをできるのではないか。あと、結果のところにjsonのURLがでてきた。見てみると、分類できる1000クラスが載っている! 犬はコードn02085620からn02116738の125犬種、猫はn02123045~n02124075の5種類を区別できることがわかった。 急に、終わりが訪れた。もう私のすることはないのだ。こうなったら、意地悪をして遊んでみよう。このミックス犬はどの犬種と判断しますか。 PxHere [[('n02087046', 'toy_terrier', 0.2920945), ('n02110806', 'basenji', 0.14723742), ('n02085620', 'Chihuahua', 0.101308264), ('n02113023', 'Pembroke', 0.03310363), ('n02113186', 'Cardigan', 0.024589175)]] ...いいところでしょう。 google colab すぐに犬種・猫種の特定ができるように、google colabのサンプルを作りました。 https://colab.research.google.com/github/yuta-vet/Dog-Cat-breed-indentify/blob/main/DogCatbreed_identify.ipynb 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【python】画像から犬種・猫種を特定する方法(読み物)

動機 正直に言うと動物病院に来る犬で、たまに犬種が分からないのがいる。もちろん問診票に犬種を書いてもらうから心配する必要はないが、獣医として自分の力で犬種を特定したい。いくら犬種を勉強しても、「ああ、この子は珍しい犬種で日本で5頭しかいないんですよ~」とか言われることもある。 こうなったら機械学習を使おうと思う。備え付けカメラで犬を撮影し、機械学習で犬種を特定する。自分の力で犬種を特定したと言えるのかは甚だ疑問だが、仕方ない。 方法 画像分類タスクは、Convolutional Neural Network(CNN)が得意。CNNを使う事を決めた。さてどう学習させるか。作戦は3つ。 作戦1 犬種ごとに写真を用意 + CNNを0から学習させる 作戦2 犬種ごとに写真を用意 + 学習済みCNNの最終層を再学習させる(fine tuning) 作戦3 犬種ごとに写真を用意 + 重みを固定した学習済みCNNの後に層を追加して学習させる(transfer learning) 作戦1は犬種ごとの写真が大量に必要だ。珍しい犬種なんてどうしよう。作戦2・作戦3は既に画像の特徴を捉える能力を持ったCNNを使うために、少ない画像で済むそうだ。作戦2・作戦3を使うしかない。作戦2・作戦3のどちらにするかは、精度を見て決めればいいのでまずは作業を開始する。 どの学習済みモデルを使うべきか。tensorflowの中に複数の学習済みモデルがあるけど、どれにしよう。以下のgithubレポジトリを見ると他のモデルよりもefficientnetの方がパラメータも少なくて精度が出ている。 https://github.com/tensorflow/tpu/tree/master/models/official/efficientnet efficientnetを使う事にした。さて、efficientnetはどういった画像を学習させているのだろうか、1400万枚以上の画像を学習させて(imagenet)1000クラスも分類できるらしい、すごいな!! とりあえず、適当にうちの犬の写真を分類させてみた。これは、犬ですとか出るのかな。 dog_breed.py from tensorflow.keras.applications import EfficientNetB0 from tensorflow.keras.preprocessing.image import load_img, img_to_array import numpy as np import tensorflow as tf # 写真の前処理を行う※ pic = load_img("/content/dog.jpg", target_size= (224,224,3)) pic = img_to_array(pic).astype('float32') pic = np.expand_dims(pic,axis=0) processedpic = tf.keras.applications.efficientnet.preprocess_input(pic) # efficientnetのモデルを読み込む model = EfficientNetB0(weights='imagenet') # modelを使って犬種・猫種を予想する prediction = model.predict(processedpic) tf.keras.applications.efficientnet.decode_predictions(prediction) [[(‘n02099712’, ‘Labrador_retriever’, 0.42502728), (‘n02093428’, ‘American_Staffordshire_terrier’, 0.04134651), (‘n02109961’, ‘Eskimo_dog’, 0.032269508), (‘n02110185’, ‘Siberian_husky’, 0.031741217), (‘n02093256’, ‘Staffordshire_bullterrier’, 0.024241285)]] Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/imagenet_class_index.json 犬どころか、かなり具体的な犬種を区別できているぞ。まさか、すでに犬種の分類タスクをできるのではないか。あと、結果のところにjsonのURLがでてきた。見てみると、分類できる1000クラスが載っている! 犬はコードn02085620からn02116738の125犬種、猫はn02123045~n02124075の5種類を区別できることがわかった。 急に、終わりが訪れた。もう私のすることはないのだ。こうなったら、意地悪をして遊んでみよう。このミックス犬はどの犬種と判断しますか。 PxHere [[('n02087046', 'toy_terrier', 0.2920945), ('n02110806', 'basenji', 0.14723742), ('n02085620', 'Chihuahua', 0.101308264), ('n02113023', 'Pembroke', 0.03310363), ('n02113186', 'Cardigan', 0.024589175)]] ...いいところでしょう。 google colab すぐに犬種・猫種の特定ができるように、google colabのサンプルを作りました。 https://colab.research.google.com/github/yuta-vet/Dog-Cat-breed-indentify/blob/main/DogCatbreed_identify.ipynb 参考
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

psycopg2がインストールできない

python3.9に上げた後、pip経由で psycopg2 のインストールがハマったので、その備忘録 pip install psycopg2 エラー Installing collected packages: psycopg2 Running setup.py install for psycopg2 ... error ERROR: Command errored out with exit status 1: : 中略 : ld: library not found for -lssl clang: error: linker command failed with exit code 1 (use -v to see invocation) error: command '/usr/bin/clang' failed with exit code 1 : 中略 : 解決策 自分は、.bash_profile に追加 export LDFLAGS="-L/usr/local/opt/openssl/lib" export CPPFLAGS="-I/usr/local/opt/openssl/include" 感想 brew 経由で入れた pyenv をはじめとしたパッケージ(postgresql等)と、その依存関係解決で インストールされた openssl@ 1.1 との相性が悪いのか? # which openssl /usr/bin/openssl opensslのパスが、brewのパッケージのインストール先である # brew ls openssl /usr/local/Cellar/openssl@1.1/・・・ : /usr/local/Cellar/openssl@1.1/・・・ を向いてないのが原因のような・・・ シンボリックリンクが張られている訳でもないし・・・ python環境、久々いじると嵌るww 参考 error installing psycopg2, library not found for -lssl 【小ネタ】Macにpsycopg2をインストールする時のメモ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3からMySQLサーバにログインを試みた。

なんかわからなくなったら探すのめんどくさいからノートにしてます すぐ忘れるんで。 でもユーザー名とかPWとか隠すのめんどくさい。。 そもそもPython3が入ってなかったので (このサイト)をステップ3まですべて行ってみた。 問題のPythonからMySQLを接続する方法。 DBはschoolというものをもう作っていたのでそこに接続するようにする。 上記のURLを参照 ライブラリのインストールを行っていく。 Debian / Ubuntuの場合 sudo apt-get install python-dev default-libmysqlclient-dev Python3の場合は更に追加で以下のライブラリをインストールします。 sudo apt-get install python3-dev pipから以下のコマンドを入力すればインストールが完了です。 pip install mysqlclient なんかpipあたりでうまくダウンロードできなかったりしたけどその辺しらべたらゴロゴロ出てくる。 touch ファイル名 で適当なファイルを作る。 vim ファイル名 で中身にPythonスプリクトを記入していく。 #!/usr/bin/env python3 import MySQLdb connection = MySQLdb.connect( host='UbuntuのIP', user='MySQLで作ったユーザー名', passwd='UbuntuのPW', db='school') 結果これホストもユーザーもパスワードも書き方全部間違い。 Traceback (most recent call last): File "./school", line 5, in <module> connection = MySQLdb.connect( File "/home/MySQLで作ったユーザー名/.local/lib/python3.8/site-packages/MySQLdb/__init__.py", line 130, in Connect return Connection(*args, **kwargs) File "/home/MySQLで作ったユーザー名/.local/lib/python3.8/site-packages/MySQLdb/connections.py", line 185, in __init__ super().__init__(*args, **kwargs2) MySQLdb._exceptions.OperationalError: (2003, "Can't connect to MySQL server on 'UbuntuのIP:3306' (111)") UbuntuのIPのMySQLサーバにアクセスできませんよーってエラーが出てきた。 一番下の Can't connect to MySQL server で検索をかけてみる。 こんなんでてきた。 言ってることは違うんだけど最後の方をみると、127.0.0.1やlocalhost以外は外部接続になってしまいそれでエラーになるらしい???なのか???(わかってない) なのでhostの部分をlocalhostに変えてみる。 ついでにMySQLだというのを思い出し、UbuntuのパスワードからMySQLのパスワードに書き換えた。 #!/usr/bin/env python3 import MySQLdb connection = MySQLdb.connect( host='localhost', user='MySQLで作ったユーザー名', passwd='MySQLのPW', db='school') Traceback (most recent call last): File "./school", line 5, in <module> connection = MySQLdb.connect( File "/home/MySQLで作ったユーザー名/.local/lib/python3.8/site-packages/MySQLdb/__init__.py", line 130, in Connect return Connection(*args, **kwargs) File "/home/MySQLで作ったユーザー名/.local/lib/python3.8/site-packages/MySQLdb/connections.py", line 185, in __init__ super().__init__(*args, **kwargs2) MySQLdb._exceptions.OperationalError: (1044, "Access denied for user 'MySQLで作ったユーザー名'@'localhost' to database 'school'") あっれまたエラー??? でも今度は違うエラーで 1044, "Access denied for user 'MySQLで作ったユーザー名'@'localhost' to database 'school'" user 'MySQLで作ったユーザー名'のデータベースschoolは拒否された・・と・・? Access denied for userで検索してみる。 これを見たんだけどよくわからない。 ログインで指定した情報と合致するアカウントが複数ある場合、ユーザー名よりホスト名の具体性が高いアカウントが優先して選択されるようです とあるのでMySQLで作ったユーザー名は優先度低いってコト????(ちいかわ風)(よくわかってない) って思ってなんとなくrootで実行したら成功した。 #!/usr/bin/env python3 import MySQLdb connection = MySQLdb.connect( host='localhost', user='root', passwd='MySQLのPW', db='school') 最終的にはこんな感じ。 ./school で実行したらエラーも何も吐かなかったから実行だけされたのだと思う(ここのMySQLに繋いでとしか書いてなかったからね!)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

スクリプトのディレクトリは暗黙的にsys.pathに追加される(スクリプトとしての実行でなくpython -mとしての実行だと追加されない)

$ cat test_0.py import sys print(sys.path) pytest -s test_0.py ['/usr/local/bin', (snip)] python -m pytest -s test_0.py ['', (snip)] /usr/local/bin 内の.pyを(ここではtest_0.py内で)暗黙的にimportすることは、python -mだとできませんので注意。 私がこれで2時間ほどはまったので。。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Cygwin+EmacsでのPython実行

Run python in Emacs with Cygwin in Windows Windows10 の中で、Cygwin と Emacs で Python する Windows x64 の Python を使う インストールは説明不要 .emacs その1 パスの表現は各自の環境次第 (require 'python) # これを使うかはお好みだろう (setq python-shell-interpreter "/cygdrive/c/Users/.../Python39/python.exe") Your `python-shell-interpreter' doesn't seem to support readline の警告を防ぐ https://qiita.com/ignorant/items/50f8eb2852d0f0214659 の use-package python で始まる設定を .emacs に追加する C-cC-c で FileNotFoundError になる No such file or directory: '/cygdrive/c/Users/...' というエラーが出る これはパスの形式が Windows Python とこの Cygwin で違うためみたい python-shell--save-temp-file に手を加える advice-add を filter-return で使い、return するパスを加工する。 一例として先頭の /cygdrive/c を除去する (defun my-xxx (fpath) (replace-regexp-in-string "/cygdrive/c" "" fpath)) (advice-add 'python-shell--save-temp-file :filter-return "'my-xxx) Emacs/Cygwin からもアクセス可能にする トリッキーだがシンボリックリンクで解決する 管理者権限で Cygwin (64) を起動し、 ln -s /cygdrive/c/Users /Users これで、 /Users/... で始まるパス記述でアクセスできる おまけ tkinter なぜか from Tkinter import * ではだめで、 ModuleNotFoundError: No module named 'Tkinter' となってしまう。 理由はわからないが tkinter と小文字にしたら問題が消えた。 使ったテストでは tkinter による Window を閉じてもプロンプトに戻らず、  py の最後に quit() を加えて、プロセスを終えるようにした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ラズパイでmjpg-streamerのopencvプラグインを使う+OpenCVのビルドインストール(備忘録)

mjpg-streamerを用いて、opencvで加工したwebカメラ映像をストリーミングしたかったけど、makeしてもinput_opencv.soが生成されなかった。最終的にopencvのバージョンとCMakeListsの記述を変更することでうまくいったのでメモ。 動作環境 Raspberry Pi4 Python 3.7.3 OpenCV 3.4.6 input_opencvのCMakeLists.txtの記述を変更 mjpg-streamerのplugins/input_opencvフォルダ内は以下のような階層だった。 input_opencv │ CMakeLists.txt │ input_opencv.cpp │ input_opencv.h │ README.md │ └─filters ├─cvfilter_cpp │ CMakeLists.txt │ filter_cpp.cpp │ README.md │ └─cvfilter_py │ CMakeLists.txt │ conversion.cpp │ conversion.h │ example_filter.py │ filter_py.cpp │ README.md │ └─cmake FindNumpy.cmake このうち直下とfilters/cvfilter_cppとfilters/cvfilter_py階層の3か所にCMakeLists.txtがある。 今回はpython用なので、input_opencvフォルダとcvfilter_pyフォルダにあるCMakeLists.txtを編集する。 input_opencvフォルダのCMakeLists.txt編集内容は以下のようにONLYIF OpenCV_FOUND #${OpenCV_VERSION_MAJOR} EQUAL 3の記述を削除すればよいらしい。opencv3系を入れていてもこの記述があるせいでうまくいかないのは謎。 CMakeLists.txt #MJPG_STREAMER_PLUGIN_OPTION(input_opencv "OpenCV input #plugin" # ONLYIF OpenCV_FOUND #${OpenCV_VERSION_MAJOR} EQUAL 3) #↑ここを #↓こうする MJPG_STREAMER_PLUGIN_OPTION(input_opencv "OpenCV input plugin") cvfilter_pyフォルダにあるCMakeLists.txtも同様に編集しておく。 OpenCVのバージョンは3系が必要 最初OpenCV4系でトライしてたけど、make中にエラーになったので3系にしたらすんなりmakeできた。 まず古いバージョンのOpenCVを消す。 cd /mnt/opencv/build sudo make uninstall sudo rm -rf /usr/local/include/opencv これでもpythonでOpenCVがimportできてしまう場合はpythonからパッケージの場所を確認してそのフォルダを削除する。 import cv2 print(cv2.__file__) #/usr/local/lib/python3.7/dist-packages/cv2/...←パスが表示されるのでこのフォルダを削除 他の記事を参照してOpenCV3.4.6をビルドインストールする。 sudo apt install libjpeg-dev libtiff5-dev libjasper-dev libpng12-dev sudo apt install libavcodec-dev libavformat-dev libswscale-dev libv4l-dev sudo apt install libxvidcore-dev libx264-dev libgtk2.0-dev libatlas-base-dev gfortran sudo apt install python3-dev python3-pip sudo pip3 install numpy #追加(GStreamer) sudo apt install libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libv4l-dev cd /usr/include sudo ln -s libv4l1-videodev.h videodev.h #download OpenCV3 wget -O opencv.zip https://github.com/opencv/opencv/archive/3.4.6.zip wget -O opencv_contrib.zip https://github.com/opencv/opencv_contrib/archive/3.4.6.zip unzip opencv.zip unzip opencv_contrib.zip cd opencv mkdir build cd build sudo cmake -D CMAKE_BUILD_TYPE=RELEASE \ -D CMAKE_INSTALL_PREFIX=/usr/local \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-3.4.6/modules \ -D OPENCV_PYTHON3_INSTALL_PATH=/usr/local/lib/python3.7/dist-packages \ -D OPENCV_GENERATE_PKGCONFIG=ON \ -D INSTALL_C_EXAMPLES=ON \ -D INSTALL_PYTHON_EXAMPLES=ON \ -D BUILD_NEW_PYTHON_SUPPORT=ON \ -D BUILD_EXAMPLES=ON \ -D WITH_QT=OFF \ -D WITH_GTK=ON \ -D WITH_OPENGL=ON \ -D WITH_TBB=ON \ -D WITH_V4L=ON \ .. sudo make clean sudo make -j4 sudo make install sudo ldconfig 最後にpythonでimportできたか確認 import cv2 print(cv2.__version__) 最後に、mjpg-streamerをmakeする。 cd ~/mjpg-streamer/mjpg-streamer-experimental sudo make sudo make install 無事input_opencv.soが生成されていれば終了。 参考元 GitHub - jacksonliam/mjpg-streamer: Fork of http://sourceforge.net/projects/mjpg-streamer/ pythonのパッケージの保存場所 Qiita - OpenCVの入れ直し detamamoruのブログ - Raspberry Pi B+でinsta360 airの動画をOpenCVで処理する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Python】超簡単!pandas-datareaderで株価を取得してグラフ出力するまでを試してみた

はじめに Pythonを使うなら、pandasとmatplotlib、numpy(今回は使わない)等のライブラリも使いこなせないとー と思い、まだまだ初見ながら一応簡単に試してみました。 ちなみに、pandas-datareaderとpandasは別物のライブラリです。 元々はpandas.ioというPandasの一部だったらしいですが、Pandasの0.19.0からサポートされなくなったっぽく、pandas-datareaderが独立したそうです。 手順 必要ライブラリのインストール APIキー取得 実行&出力 必要ライブラリのインストール $ pip install pandas # 1.2.4 $ pip install pandas_datareader # 0.9.0 $ pip install matplotlib # 3.4.2 $ pip install numpy # 1.20.3 今回使わないけどとりあえず入れたw ※2021/6/3現在のバージョン記載 pipコマンドのインストールはこちら、参考までに〜 各ライブラリの用途 名前 用途 pandas データ読込や並べ替え、欠損値の補完などを行える。データ分析の前処理で使われることが多い pandas_datareader Web上の様々なソースにアクセスできるPythonライブラリの1つ ※対応サイト後述 NumPy 数値計算や行列演算を行える。複数の数値を配列としてまとめて扱ったり、行列演算を行ったりするのが得意 Matplotlib Pythonで代表的なグラフ描画ライブラリ。2D/3Dなど多種のグラフ描画ができる データアクセス用のAPIキーを取得 今回は株価を取得するために、Alpha VantageからAPIキーを取得します。 Claim your Free API Key - Alpha Vantage にアクセスして、各情報を入れるとAPIキーを無料で発行できます。 ※重要:GET FREE API KEYをクリックして発行されたAPI KEYをメモしておきましょう! ※API Call制限:5 API requests / minute, 500 requests / day 詳しくはこちらまで=>Alpha Vantage Support ちなみに、pandas_datareader 0.9.0でデータ取得できるそれぞれのウェブサイトは下記です。(2021/6/3現在) Tiingo, IEX, Alpha Vantage, Enigma, Quandl, St.Louis FED (FRED), Kenneth French’s data library, World Bank, OECD, Eurostat, Thrift Savings Plan, Nasdaq Trader symbol definitions, Stooq, MOEX, Naver Finance 実行と出力 アプリのディレクトリ構成 pandas-challenge . ├── config.py # .envから値をセット ├── main.py # mainの実行ファイル └── seed # 将来的にcsv保存・出力用のディレクトリ プログラムコード .env .env ALPHA_VANTAGE_API_KEY=xxxxxxxxxxxxxxxx config.py config.py # coding: UTF-8 import os from os.path import join, dirname from dotenv import load_dotenv dotenv_path = join(dirname(__file__), '.env') load_dotenv(dotenv_path) # Alpha Vantage ALPHA_VANTAGE_API_KEY = os.environ.get("ALPHA_VANTAGE_API_KEY") main.py main.py import pandas as pd from pandas_datareader import data as web from matplotlib import pyplot as plt import datetime as dt import config as cfg def main(): startdate = dt.date(2021,5,27) # 開始日 enddate = dt.date(2021,6,1) # 終了日 apikey = cfg.ALPHA_VANTAGE_API_KEY # API Keyをセット # Apple, Microsoft, Facebookの株価をそれぞれ取得 df_appl = web.DataReader('AAPL', 'av-daily', startdate, enddate, api_key=apikey) df_msft = web.DataReader('MSFT', 'av-daily', startdate, enddate, api_key=apikey) df_fb = web.DataReader('FB', 'av-daily', startdate, enddate, api_key=apikey) print(df_appl.head()['close']) print(df_msft.head()['close']) print(df_fb.head()['close']) #各株価をミックスしたDataframeを作成、終値(closeのカラム)をセット df_mix = pd.DataFrame({'Apple': df_appl['close'],'Microsoft': df_msft['close'], 'Facebook': df_fb['close']}) # グラフの設定と出力 df_mix.plot(figsize=(8,6),fontsize=18) plt.legend(bbox_to_anchor=(0, 1), loc='upper left', borderaxespad=1, fontsize=18) plt.grid(True) plt.show() if __name__ == "__main__": main() カラムを絞らずに出力するとこんな感じのデータフレーム # web.DataReader('AAPL', 'av-daily', startdate, enddate, api_key=apikey)のレスポンス open high low close volume 2021-05-27 126.44 127.64 125.08 125.28 94625601 2021-05-28 125.57 125.80 124.55 124.61 71311109 2021-06-01 125.08 125.35 123.94 124.28 67637118 ちなみに、yahooだったら、各社の株価を一括取得できるみたいだったから、こっちの方が便利っぽいなあ〜 df_mix = web.DataReader(['AAPL', 'MSFT', 'FB'], 'yahoo', start, end) 今度試してみよ・・・orz (API KEY取得後に気づいた) 出力結果 コンソール $ python main.py 2021-05-27 125.28 2021-05-28 124.61 2021-06-01 124.28 Name: close, dtype: float64 2021-05-27 249.31 2021-05-28 249.68 2021-06-01 247.40 Name: close, dtype: float64 2021-05-27 332.75 2021-05-28 328.73 2021-06-01 329.13 Name: close, dtype: float64 出力グラフ 各社の終値を出力しています。 ちゃんと2021/5/27~2021/6/1までの3社の株価がグラフ出力できました! ちなみに、米国市場休場日なので、以下三日間のデータはありません。 5/29:土曜日 5/30:日曜日 5/31:戦没者追悼記念日 ハマったポイント DataReaderで複数社のデータ一括取得を試したが、Alpha Vantageの場合、どうやってやるかわからなかった 他の記事とか見たところみんなyahooとかで一括取得していた。 多分、Alpha Vantageが用意しているAPI的な問題なんだろうか〜 グラフ出力中は、プログラムが終了せずずっと実行中となるので、若干盲点だった。 出力したら終わりみたいにしたいから、今度はpngファイルとかにして出力させるのがいいんじゃないかとか思う。 まとめと感想 ウェブマーケや広告運用やっていたときは、取得したcsvデータをExcelに展開して、手動やマクロでガシガシ加工したり、 TableauなんかのBIツールでデータを可視化して、分析なんかやったりしてましたが、 Pythonでここまでライブラリが揃っていると、わざわざExcelとかじゃなくても色々できそうだと思いました。 Tensorflowなんかを使って、機械学習のモデルに、データを食わせて分析すればめちゃくちゃ面白い分析と予測等ができるんじゃないかと ワクワクしています。 また、Herokuなんかを使えば、株価の取得を自動化して、気になる銘柄だけ確認するTwitterBotやアプリを作れたりすると思うので、 アイデア次第では、応用の幅がぐんと広がるんじゃないかと思います。 2時間くらいで、本質が理解できたんで、本格的に何かのプロダクトに活かしてまいりたいと思います。 以上、ありがとうございました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python3からMySQLのテーブルデータを取得してみた。

エンジニア勉強中。 すぐ忘れるのでノートとして残す。 の続き。 データベースの接続まで出来たので、実行したいのもを書いていく。 上記の画像、カーソルってなんやねんの答え ↓ 平たく言えば、「検索条件に合致するレコードを、1件ずつ取り出すための仕組み」となります。 だそうです。 前回と同様に このページを見ていく。 いろいろテーブルを初期化したり作成したりしてるが 私がやりたいのはもうすでに作られてるテーブルの取得のみ。 なので #!/usr/bin/env python3 import MySQLdb connection = MySQLdb.connect( host='localhost', user='root', passwd='MySQLで設定したPW', db='school', charset='utf8')  #日本語を取り扱うために記入 cursor = connection.cursor()   #カーソルの生成 #一覧を表示 cursor.execute("SELECT * FROM students") #studentsテーブルを表示したい for row in cursor: print(row) # 接続を閉じる connection.close() 内容としてはこんな感じに記入した。 結論は最初にもあげた下記ページのおいしいとこ拾えば何でもできる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

eval "$(pyenv init -)" がうまく動かない時の対処法

参考 問題 RaspiOSでpyenv使おうとしたら WARNING: `pyenv init -` no longer sets PATH. Run `pyenv init` to see the necessary changes to make to your configuration. っていうのが出て、which python してもpyenvのpythonじゃなく /usr/bin/python を見てしまっていた。 解決策 eval "$(pyenv init -)" を eval "$(pyenv init --path)" に書き換えたらうまくいった
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Typical DP Contest (Atcoder) のA問題をプログラム初心者が考えてみた part3 貰うDP編

前回はこちら↓↓ part2では、 $i$ 問目が解けた時点での合計点の集合 $S_i$ を、 $i$ が $1$ の時から $N$ になるまで下から順に考えていくボトムアップ方式(配るDP)を採用し、プログラムを完成させた。 その結果、part1で作成したbit全探索型のプログラムとは比較にならないほど高速に演算することが出来た。 今回このpart3ではpart2とは逆に、 $i$ が $k$ のとき、 $k-1$ を考える、というような。上から下へトップダウン方式(貰うDP)の考え方で作成していこうと思う。 使用する問題は同じくTypical DP Contest の A問題 ↓↓ 考え方 以前$i$ 問目を解き終えた時にとりうる合計点の集合 $S_i$ を考えたが 今回は少し考え方を変えようと思う。 $i$ 問目を解き終えた時に合計点 $j$ を取りうるかどうかを、真偽値 $dp[i][j]$ で表現する。 取りうるなら $1$ を、有りえない時は $0$ を格納する。 $dp[i][j]$ を考えるにあたり、一先ず $dp[i-1][j]$ を考えてみる。 $dp[i-1][j]$ の示すものは、 $i-1$ 問目を解き終えた時点で合計点 $j$ を取りうるか、というものであるが この $dp[i-1][j]$ が $1$ の時、どのように考えられるだろうか。 $i$ 問目は、正解か不正解かの二択なので、考えられる分岐は $dp[i][j] = 1 \quad\quad\quad\quad$ $dp[i][j+p_{i-1}] = 1\quad$ この二つである。 上は、前の問題までの合計点で既に $j$ 点を獲得しているため、不正解の場合は点数加算なしでも $dp[i][j] = 1$ を満たしていることは自明である。 下は、前の問題までの合計点で既に $j$ 点獲得していて、更に $i$ 問目も正解しているため、その時の得点が前回の合計点にプラスされる。 この時、各配点を表す $p_i$ は、コマンドラインから配列で受け取るため、 $i$ 問目の得点が$p_{i-1}$ となる点に注意する。 そうすると、$i$ 問目を解き終えた時点での合計点は$j + p_{i-1}$ 点となるため、 $dp[i][j+p_{i-1}] = 1$ を満たすことになる。 この操作を区間 $0 \leq i \leq N$ で行うことにより、2次元配列 $dp$ が完成し、$dp[N]$ に含まれる $1$ の数を計上することによって、その時点で取りうる合計点のパターンを取得可能である。 また、 $i = 0$ の時は、まだ問題を解いていない状態として考え、その時取得している合計点 $j$ は必ず $0$ であることから この時の状態を $dp[0][0] = 1$ というように初期条件とする。 以上の考え方をコードにしていく。 コーディング まず、2次元配列 $dp$ を定義し、どの程度のサイズが必要か考えてみる。 各配点が $p_i$ で与えられる時、考えられる最高得点は、与えられた各配点の合計点となる。 また下限は常に0点であるため、2次元目に必要な要素数は $1 + \sum_{i=0}^{N-1} p_i$ 個である。 例としてSample Input 1で与えられている条件で実行してみる。 N = 3 p = [2, 3, 5] # i問目時点で合計点がjになるかどうかのテーブル dp = [[0 for i in range(sum(p)+1)] for j in range(N+1)] dp[0][0] = 1 print(*dp, sep="\n") 実行結果 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] 全3問で各配点が2点, 3点, 5点の時に取りうる最高合計点が10点であることから、最低得点の0点と合わせて水平方面に11個の要素がある。 また、問題数が全部で3つなので、1問も解いていない時の状況と合わせて垂直方面に4個の要素がある。 $dp$ テーブルは、図にすると下のように この状態のテーブルが初期条件として与えられ、このテーブルに対して操作を繰り返すことで、最終的に一番下の行の1の数が合計点のパターン数となる。 次に下のような処理を加える。これが具体的にテーブルに対して操作を繰り返す。 # 省略 for i in range(1, N+1): for j, d in enumerate(dp[i-1]): if d: dp[i][j] = 1 dp[i][j+p[i-1]] = 1 print(*dp, sep="\n") $i = 0$ に関しては、必ず $j$ は$0$のみとなるため、ループ処理は $i = 1$ から $N$ までとした。 内側では、一つ前の行を参照し、列に対してループしている。 つまり、これによって一つ前の問題の解答時点で、合計点が $0$ 点から $sum(p)+1$ 点までの真偽値を取得できる。 ループ過程で取り出した真偽値を $d$、その時の合計点を $j$ として、その $d$ が1である時 つまり$i-1$ 問目解答時点で合計点が $j$ 点であることがあり得るため、この時に以下の処理を行う。 $dp[i][j]$ に $1$ を代入 $dp[i][j+p_{i-1}]$ に $1$ を代入 これがif文のブロック内の処理に当たる。 実行結果 [1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] [1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0] [1, 0, 1, 1, 0, 1, 0, 0, 0, 0, 0] [1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1] 実行結果を図にすると以下のようになる。 この図、実は前回のpart2で考えるために例に出した図と同じものを指している。 今回実行結果に表示された図の下3行部分がこれにあたる。 最終的に完成したコードはこちら。 N = int(input()) p = list(map(int, input().split())) # i問目時点で合計点がjになるかどうかのテーブル dp = [[0 for i in range(sum(p)+1)] for j in range(N+1)] dp[0][0] = 1 for i in range(1, N+1): for j, d in enumerate(dp[i-1]): if d: dp[i][j] = 1 dp[i][j+p[i-1]] = 1 print(dp[N].count(1)) $dp$ テーブルの $N$ 行目に含まれる $1$ の数をcount()関数を使って算出した。 実行結果 >>3 >>2 3 5 7 >>10 >>1 1 1 1 1 1 1 1 1 1 11 このコードを提出すると、結果は以下の通りになった。 前回作成した処理には若干速度では劣るが、全てのケースでACとなり合格。 計算速度の比較 この記事のシリーズで作成した3つの処理をすべて関数化し、それぞれの速度を比較してみる。 from Sudoku_Project.stopwatch import measure_time1 def exh_search(): total_list = {0} for i in range(2 ** N): total = 0 for j in range(N): if i >> j & 1: total += p[j] total_list.add(total) return len(total_list) def bu_search(): total_list = {0} for i in range(N): for total in list(total_list): total += p[i] if total not in total_list: total_list.add(total) return len(total_list) def td_search(): dp = [[0 for i in range(sum(p) + 1)] for j in range(N + 1)] dp[0][0] = 1 for i in range(1, N + 1): for j, d in enumerate(dp[i - 1]): if d: dp[i][j] = 1 dp[i][j + p[i - 1]] = 1 return dp[N].count(1) N = int(input()) p = list(map(int, input().split())) print(measure_time1(exh_search)) print(measure_time1(bu_search)) print(measure_time1(td_search)) 実行結果 >>3 >>2 3 5 10.1 μs 3.5 μs 10.4 μs >>10 >>1 1 1 1 1 1 1 1 1 1 1.1402 ms 10.1 μs 26.4 μs >>20 >>1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 3 3 3 4 5 1.4856267 s 22.5 μs 63.6 μs 最速はボトムアップ方式の関数となり、時点でトップダウン方式の関数となりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GTFS-GO】QGISとGTFSデータで運行頻度図をつくろう!

QGIS プラグイン「GTFS-GO」 昨年、GTFS データを解析して QGIS 上に可視化するプラグインGTFS-GOを公開しました(紹介記事)。多くの方に使われ、広く反響をいただいたところです。その後、公共交通データの整備や分析を手がける(株)トラフィックブレイン社と意見交換する機会がありました。そのなかで、現在(一財)トヨタ・モビリティ基金のバックアップのもと兵庫県豊岡市で進んでいる豊岡スマートコミュニティ推進機構の取組の一環として、本プラグインをより便利な公共交通分析ツールとする機能拡張のご提案があり、快諾しました。結果、両社からサポートを頂きながら開発を進め、新バージョンの公開まで至っております。オープンソースへの多大なるご理解・ご協力に感謝します。 本記事ではGTFS-GOの新バージョンの機能や技術情報を述べます、「そもそも GTFS とは?」などの疑問は前回記事を確認してください。 もくじ 新バージョンの追加機能紹介 運行頻度図とは GTFS-GO で頻度図をつくる 使い方 出力データ 名寄せルール 終わりに 技術情報 全般 停留所の名寄せ 頻度の集計 新バージョンの追加機能紹介 公式リポジトリに登録済みなので、QGIS内でインストールすることができます。 https://plugins.qgis.org/plugins/GTFS-GO-master/ 「より便利な公共交通分析ツールとする機能拡張」とは、運行頻度図作成機能の追加です。GTFS には時刻表データも含まれており、それを解析することで、運行頻度を停留所間の経路単位で集計する事ができます また「類似する停留所でまとめて集計したい」というニーズを解決するため、停留所の名寄せ機能を実装しています 処理の最適化によって routes と stops の読み込みが高速化 多言語対応の強化(後述) 多言語対応 QGIS は世界中に多くのユーザーがおり、GTFS データはグローバルな規格です。なのでユーザーインターフェースは英語を基本とし日本語(とフランス語一部)への翻訳に対応しつつ、日本だけではなくアメリカなどの GTFS データもプリセットしてあります(ニューヨークの地下鉄データなど)。 また、未整備だった README を英語で充実させ、世界中のユーザーが使用できる状態になっています。 運行頻度図とは 2 停留所間の運行頻度を、2 点を繋いだ線分の太さおよび付記される数値によって表現した図 エリアごとの公共交通の運行頻度が一目でわかる 運行頻度の数値は、線分の進行方向に対して左側に表示されます(車道の方向と一致) 運行頻度から線分の太さへの換算はGTFS からバスの運行頻度図を作成するを参考にしています GTFS-GO で頻度図をつくる インストールなどは前回記事を参照してください。 使い方 前バージョンから、運行頻度を集計モードが追加されています。 運行日で抽出 頻度を集計したい日付を指定、読み込む GTFS が対応している日時にしてください stop を名寄せする 類似する停留所を名寄せし代表停留所とした上で頻度集計を行います stop_id の区切り文字 名寄せ時のみ有効、stop_id に区切り文字があり、それを目印に名寄せする場合に指定します ※名寄せルールについては後述 「QGIS に読み込む」ボタンを押下すると処理が始まります(データサイズによって数十秒かかる事があります)。 出力データ 運行頻度を集計モードでは、以下の 3 つのファイルが出力され、QGIS 上でスタイリングされた状態で表示されます。 result.csv 停留所名寄せの新旧対照表 frequency.geojson 停留所間単位の経路データ その経路の始点終点の停留所情報などを含む frequency_stops.geojson 名寄せ後の代表停留所データ 名寄せルール 以下の 3 つのルールを、1 番から優先して適用していき、停留所を名寄せし、代表停留所を生成します。 親停留所 停留所に親が設定されている場合は親停留所を代表停留所とする GTFS は規格上、停留所に親子関係を設定できる 代表停留所の ID は親停留所のものを用いる stop_id の接頭辞 「stop_id の区切り文字」が設定されている場合、stop_id をその文字で区切り、前方部分が一致する全停留所の重心に代表停留所を設ける 例:2 つの stop*id が「1234_A」「1234_B」で区切り文字が「*」なら、いずれも前方部分が「1234」となり名寄せされる 代表停留所の ID は、上記条件に合致した全停留所の ID を昇順にならべ、最も若いものを用いる stop_name が一致かつ近傍 stop_name が完全に一致し、一定以上近傍にある全停留所の重心に代表停留所を設ける 一定以上近傍= 10 進法経緯度の値同士のユークリッド距離が 0.01 未満である事 このしきい値が実際に示す領域の広さは緯度によって異なるが、実用上無視している 代表停留所の ID は、上記条件に合致した全停留所の ID を昇順にならべ、最も若いものを用いる 名寄せ例 下記 2 画像は、上記ルールでの名寄せ前後です。 ご覧のとおり、名寄せされている方が、運行頻度がわかりやすくなります。 終わりに 地域の交通サービスの充実度を一目できる運行頻度図は、自治体等による公共交通計画の立案に役立つ一方、その図を作るのが容易ではありませんでした。しかし今後は本プラグインによりGTFSデータが整備されていれば簡単に頻度図が得られます。 さらにバスの乗降人数等のデータを組み合わせることで、地域交通の利用実態を可視化することもできます。(豊岡市における参考例) GTFS データの活用方法を地図検索サービス以外にも広げることで、GTFS データの価値向上、さらには GTFS データの整備促進にもつながれば幸いです。 技術情報 以下はより具体的な実装のポイントの説明です。 全般 GTFS テーブル周りの処理はすべて Pandas を用いています GTFS を解析するモジュールはプラグイン内で gtfs_parser フォルダに独立しているので、流用可能です(MIT ライセンス) 停留所の名寄せ 名寄せルールに基づき、以下の関数では、各 stop_id に対し、名寄せ後の「stop_id、stop_name、座標」を求めています これを全 stop_id に対し実行しておきます def get_similar_stop_tuple(self, stop_id: str, delimiter='', max_distance_degree=0.01): stops_df = self.dataframes['stops'].sort_values('stop_id') stop = stops_df[stops_df['stop_id'] == stop_id].iloc[0] if stop['is_parent'] == 1: return stop['stop_id'], stop['stop_name'], [stop['stop_lon'], stop['stop_lat']] if str(stop['parent_station']) != 'nan': similar_stop_id = stop['parent_station'] similar_stop = stops_df[stops_df['stop_id'] == similar_stop_id] similar_stop_name = similar_stop[['stop_name']].iloc[0] similar_stop_centroid = similar_stop[['stop_lon', 'stop_lat']].iloc[0].values.tolist() return similar_stop_id, similar_stop_name, similar_stop_centroid if delimiter: stops_df_id_delimited = self.get_stops_id_delimited(delimiter) stop_id_prefix = stop_id.rsplit(delimiter, 1)[0] if stop_id_prefix != stop_id: similar_stop_id = stop_id_prefix seperated_only_stops = stops_df_id_delimited[stops_df_id_delimited['delimited']] similar_stops = seperated_only_stops[seperated_only_stops['stop_id_prefix'] == stop_id_prefix][['stop_name', 'similar_stops_centroid_lon', 'similar_stops_centroid_lat']] similar_stop_name = similar_stops[['stop_name']].iloc[0] similar_stop_centroid = similar_stops[['similar_stops_centroid_lon', 'similar_stops_centroid_lat']].values.tolist()[0] return similar_stop_id, similar_stop_name, similar_stop_centroid else: # when cannot seperate stop_id, grouping by name and distance stops_df = stops_df_id_delimited[~stops_df_id_delimited['delimited']] # grouping by name and distance similar_stops = stops_df[stops_df['stop_name'] == stop['stop_name']][['stop_id', 'stop_name', 'stop_lon', 'stop_lat']] similar_stops = similar_stops.query(f'(stop_lon - {stop["stop_lon"]}) ** 2 + (stop_lat - {stop["stop_lat"]}) ** 2 < {max_distance_degree ** 2}') similar_stop_centroid = similar_stops[['stop_lon', 'stop_lat']].mean().values.tolist() similar_stop_id = similar_stops['stop_id'].iloc[0] similar_stop_name = stop['stop_name'] return similar_stop_id, similar_stop_name, similar_stop_centroid 上記の処理だけだと同じ代表停留所に名寄せされた停留所が複数個存在することになるので、重複を除去 名寄せ後 ID +経緯度文字列が一致していると重複と判定する self.dataframes['stops']['position_id'] = self.dataframes['stops']['similar_stops_centroid'].map(latlon_to_str) self.dataframes['stops']['unique_id'] = self.dataframes['stops']['similar_stop_id'] + self.dataframes['stops']['position_id'] self.similar_stops_df = self.dataframes['stops'].drop_duplicates(subset='unique_id')[['position_id', 'similar_stop_id', 'similar_stop_name', 'similar_stops_centroid']].copy() 頻度の集計 stopstop_times テーブルに、stops テーブルを JOIN 済みだとします。 # ソート stop_times_df = self.dataframes.get('stop_times')[['stop_id', 'trip_id', 'stop_sequence']].sort_values(['trip_id', 'stop_sequence']).copy() テーブルがソート済みである事を利用して、1 行に前後の停留所情報を押し込みます。 # 1行下からstop_idなどを持ってくる(nextとする) stop_times_df['prev_stop_id'] = stop_times_df['similar_stop_id'] stop_times_df['prev_trip_id'] = stop_times_df['trip_id'] stop_times_df['prev_stop_name'] = stop_times_df['similar_stop_name'] stop_times_df['prev_similar_stops_centroid'] = stop_times_df['similar_stops_centroid'] stop_times_df['next_stop_id'] = stop_times_df['similar_stop_id'].shift(-1) stop_times_df['next_trip_id'] = stop_times_df['trip_id'].shift(-1) stop_times_df['next_stop_name'] = stop_times_df['similar_stop_name'].shift(-1) stop_times_df['next_similar_stops_centroid'] = stop_times_df['similar_stops_centroid'].shift(-1) # tripの切れ目の行は削除 stop_times_df = stop_times_df.drop(index=stop_times_df.query('prev_trip_id != next_trip_id').index) 1 行に前後の停留所情報があるので、それらを用いて経路に ID を振ります。経路は方向が区別されるので、2 停留所間に往路それぞれの経路が存在します。 # path_id: 前停留所ID + 後停留所ID + 前停留所経緯度文字列 + 後停留所経緯度文字列 stop_times_df['path_id'] = stop_times_df['prev_stop_id'] + stop_times_df['next_stop_id'] + stop_times_df['prev_similar_stops_centroid'].map(latlon_to_str) + stop_times_df['next_similar_stops_centroid'].map(latlon_to_str) あとは経路 ID をカウントすれば、その集計値が経路ごとの運行頻度になります。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Huggingface Transformers 101本ノック 14本目~17本目 GPT2による日本語作文

概要 Huggingface Transformers 101本ノック11本目~13本目では自然言語処理用の実データはIMDbデータセットを利用し、9本目ノックで再学習したdistilbertの性能測定を行ってきた。本章では、GPT2による作文を行う。 対象読者 人工知能・機械学習・深層学習の概要は知っているという方 理論より実装を重視する方 Pythonを触ったことがある方 Huggingface Transformers 101本ノック1本目~3本目を打ち終えた方 Huggingface Transformers 101本ノック4本目~7本目を打ち終えた方 Huggingface Transformers 101本ノック8本目~10本目を打ち終えた方 Huggingface Transformers 101本ノック11本目~13本目を打ち終えた方 目次 14本目:ライブラリのインストール 15本目:gpt2の日本語モデルロード 16本目:トークナイズ 17本目:gpt2で日本語作文 参考文献 14本目:ライブラリのインストール 質問:huggingfaceのgpt2で日本語を扱えるバージョンのライブラリをインストールせよ。 回答:2021/6/3時点の最新(4.6.1)だとトークナイザが動かないため、4.3.3で実施する。 pip install transformers==4.3.3 torch==1.8.0 sentencepiece==0.1.91 15本目:gpt2.0の日本語モデル 質問:gpt2の日本語モデル(colorfulscoop/gpt2-small-ja)をロードせよ。 回答: AutoTokenizerやAutoModelを利用すると指定した引数にあったクラスを自動で選択し インスタンス化してくれるので便利。 from transformers import AutoTokenizer from transformers import AutoModelForCausalLM # トークナイザーとモデルの準備 tokenizer = AutoTokenizer.from_pretrained("colorfulscoop/gpt2-small-ja") model = AutoModelForCausalLM.from_pretrained("colorfulscoop/gpt2-small-ja") 16本目:トークナイズ 質問:「OpenAIが開発したGPT2の性能評価をしてみた。」という文書を15本目で定義したtokenizerでエンコードせよ。返却型はpytorchのtensor型とする。 回答: # トークナイズ input = tokenizer.encode("OpenAIが開発したGPT2の性能評価をしてみた。", return_tensors="pt") エンコードされた値を確認すると、各ボキャブラリがコード値に変換(エンコード)されていることが 見て取れる。 print(input) 結果 tensor([[15573, 6764, 16263, 5466, 62, 8, 13967]]) それぞれのエンコードされた値を逆にデコードしてみるとどのコード値がどの語彙に対応しているかがわかる。 [tokenizer.decode(i) for i in input[0]] 結果 ['Open', 'AI', 'が開発した', 'GP', 'T', '2', 'の性能', '評価', 'をして', 'みた', '。'] 17本目:gpt2で日本語作文 質問: 16本目でエンコードしたinputを用いて、それに続く文章を生成せよ。また、「イギリスでは新型コロナウィルスのワクチン」「3.1415926535」に関してもそれぞれ試せ。 回答 # min_lengthで最小トークン数、max_lengthで最大トークン数を指定可能 output = model.generate(input, do_sample=True, top_p=0.95, top_k=50, num_return_sequences=1, min_length=10, max_length=120) # batch_decodeは複数文デコードすることも可能。 # 上記ではnum_return_sequences=1としているため一文章のみ生成される。 generated_text = tokenizer.batch_decode(output) for sentence in generated_text: print(sentence) 結果 OpenAIが開発したGPT2の性能評価をしてみた。GPT2の上位モデルには、OpenAIのハードウェア機能として、GPT3、GPT4がある。なお、OpenAIはGPT2よりもさらに性能が良いGPT2を開発している。このGPT2の性能評価の差異については、以下の点に留意する必要がある。OpenAIは、GPU+とGPT4を統合するためのソフトウェアアーキテクチャとして、GPT3を開発、提供している。以下の点は、GPT2の品質保証に留意する必要がある。GPT3は、GPT3の機能として開発された # トークナイズ input = tokenizer.encode("イギリスでは新型コロナウィルスのワクチン", return_tensors="pt") output = model.generate(input, do_sample=True, top_p=0.95, top_k=50, num_return_sequences=1, min_length=10, max_length=120) generated_text = tokenizer.batch_decode(output) for sentence in generated_text: print(sentence) 結果 イギリスでは新型コロナウィルスのワクチン開発に関する議論が起きた。イギリスでは新型コロナウィルスのワクチンが国際がん研究機関(IARC)によって承認を受けているが、アメリカでは、新型コロナウィルスワクチンの医療技術革新が世界的な問題となっている。アメリカ疾病予防管理センター(CDC)による分類では、慢性疾患に該当し、発症しない可能性のある疾病として、慢性甲状腺機能低下症、慢性腎不全症、急性腎不全症、慢性腎不全症がある。これらの疾患の中で、最も頻度が高いのが小児呼吸器疾患(小児から慢性の慢性肺疾患) # トークナイズ input = tokenizer.encode("3.1415926535", return_tensors="pt") output = model.generate(input, do_sample=True, top_p=0.95, top_k=50, num_return_sequences=1, min_length=10, max_length=120) generated_text = tokenizer.batch_decode(output) for sentence in generated_text: print(sentence) *結果 3.14159265353人)、面積39.18km2、人口2286人であり、インド第1位の人口密度である。この都市はインドで最も標高の高いところにあり、標高は、北緯35度41分31秒、東経36度22分30秒である。インドでもっとも標高の低い地点は、北緯35度5分20秒、東経36度46分25秒である。デリーの主要な宗教地域のほとんどは、仏教徒のコミュニティであり、ヒンドゥー教、アーンドラ・プラデーシュ州のいくつかの州では、仏教徒が多数派である。デリーの主要な宗教的 システム的な文章を与えると開発チックな文章が生成され、新型コロナのワクチンを与えると、ワクチン関連の文章が生成されました。また、数字を与えると、統計的な情報を含む文章が生成されました。このほかにもいろいろ実験しましたが、(^^♪(^^)!顔文字を与えるとその特徴をとらえた文章が生成されました。「文体」といった特徴を捉えられているようです。すごいですねGPT. 参考文献 Huggingface master Huggingface Transformers 入門 著者 ツイッターでPython/numpy/pandas/pytorch関連の有益なツイートを配信してます。 @keiji_dl 参考動画 直感!Pytorchで始める深層学習実装入門(実践編) 7ステップで作るPython x Flask x Pytorch 人工知能Webアプリ開発入門 他のノックはこちら Huggingface Transformers 101本ノック1本目~3本目 Huggingface Transformers 101本ノック4本目~7本目 Huggingface Transformers 101本ノック8本目~10本目 Huggingface Transformers 101本ノック11本目~13本目 Accuracy Recall Precision F1スコアによる評価
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

markdownxの画像アップロード先をcloudinaryにした話

あらすじ 副業案件でDjango製のブログサイトの開発をサポートしていたときのお話です。 ブログ記事はmarkdownで書きたいということで、django-markdownxを導入していました。 開発は難なく完了。 herokuにデプロイします。 「画像がアップロードできません。」 本来であれば、テキストエリアに画像をドロップすると、markdownxのjsによってアップロードされ、画像のパスがmarkdown形式でテキストエリアに表示されます。 本番環境で試してみると、画像をドロップ後、数秒たたないうちに画像のパスがテキストエリアに表示されましたが、プレビューに画像が表示されません。 コンソールを見ると、画像に対して404になっていました。 herokuにアクセスし、アプリケーションのディレクトリを確認すると、アップロード先に指定しているディレクトリに画像がありませんでした。 問題発覚 各所でも言及されていますが、herokuではDEBUG=FalseのDjangoはファイルをアップロードできません。 DEBUG=Trueにして運用するわけにもいかないので先方と相談した結果、heroku+Djangoで割と実績のあるcloudinaryを画像アップロード先とすることにしました。 cloudinaryのSDKをプロジェクトにインストールし、いざ。 しかし、今までと同様、テキストエリアに画像パスが表示されるものの、画像は表示されませんでした。 cloudinaryを直接確認してみると、アップロードしたファイルが存在していました。 ところが、ファイルのURLがテキストエリアに表示されたURLと異なるのです。 原因 原因は、markdownxとcloudinaryが親切だからでした。 markdownxさんは画像をアップロードするとき、既存のファイルとファイル名が衝突しないように、ファイル名をuuidに変更したうえでアップロードします。 一方、cloudinaryさんもまた、既存のファイルとファイル名が衝突しないように、ファイル名をuuidに変更したうえで保存します。 これらを組み合わせると、markdownxさんはcloudinaryさんがどんな名前のファイルを保存したのかを知らないので、自分が知っているファイル名をブラウザに返します。 この動きによって、cloudinaryにあるファイル名とテキストエリアに表示されるURLが異なっていました。 解決策 markdownxさんには辞めてもらって、cloudinaryさんの独り言に耳を傾けましょう。 markdownxは、デフォルトでは画像アップロードをライブラリ内のビューで処理します。 設定を変更して、自前のビューに転送するよう変更します。 settings.py + MARKDOWNX_UPLOAD_URLS_PATH = '/upload' 画像アップロード処理用ビューを用意します。 markdownxの画像アップロードリクエストは、'image'という名前でファイルがきます。 レスポンスは{"image_code": テキストエリアに表示する画像パス}形式のJSONです。 cloudinaryのアップロードのレスポンスは辞書型で、'url'で画像URLを取得できます。 urls.py + path('upload', views.UploadView.as_view(),name='upload'), views.py # 追記 import cloudinary import cloudinary.uploader import cloudinary.api from django.conf import settings from django.http.response import JsonResponse class UploadView(View): def post(self,request, *args,**kwargs): file = request.FILES['image'] cloudinary.config( cloud_name = settings.CLOUDINARY_STORAGE['CLOUD_NAME'], api_key = settings.CLOUDINARY_STORAGE['API_KEY'], api_secret = settings.CLOUDINARY_STORAGE['API_SECRET'] ) res = cloudinary.uploader.upload( file = file, folder = settings.MARKDOWNX_MEDIA_PATH, ) return JsonResponse({"image_code": f"![]({res['url']})"}) これで、markdownxを介さずにcloudinaryに直接アップロードし、正しいURLをテキストエリアで取得できるようになりました。 heroku + django + cloudinaryという組み合わせはメジャーそうなのに、markdownが絡んだ記事がなくて苦労しました。 誰かのお役に立てると幸いです。 みなさんも人の話には耳を傾けましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

herokuで動くDjangoのmarkdownxの画像アップロード先をcloudinaryにした話

あらすじ 副業案件でDjango製のブログサイトの開発をサポートしていたときのお話です。 ブログ記事はmarkdownで書きたいということで、django-markdownxを導入していました。 開発は難なく完了。 herokuにデプロイします。 「画像がアップロードできません。」 本来であれば、テキストエリアに画像をドロップすると、markdownxのjsによってアップロードされ、画像のパスがmarkdown形式でテキストエリアに表示されます。 本番環境で試してみると、画像をドロップ後、数秒たたないうちに画像のパスがテキストエリアに表示されましたが、プレビューに画像が表示されません。 コンソールを見ると、画像に対して404になっていました。 herokuにアクセスし、アプリケーションのディレクトリを確認すると、アップロード先に指定しているディレクトリに画像がありませんでした。 問題発覚 各所でも言及されていますが、herokuではDEBUG=FalseのDjangoはファイルをアップロードできません。 DEBUG=Trueにして運用するわけにもいかないので先方と相談した結果、heroku+Djangoで割と実績のあるcloudinaryを画像アップロード先とすることにしました。 cloudinaryのSDKをプロジェクトにインストールし、いざ。 しかし、今までと同様、テキストエリアに画像パスが表示されるものの、画像は表示されませんでした。 cloudinaryを直接確認してみると、アップロードしたファイルが存在していました。 ところが、ファイルのURLがテキストエリアに表示されたURLと異なるのです。 原因 原因は、markdownxとcloudinaryが親切だからでした。 markdownxさんは画像をアップロードするとき、既存のファイルとファイル名が衝突しないように、ファイル名をuuidに変更したうえでアップロードします。 一方、cloudinaryさんもまた、既存のファイルとファイル名が衝突しないように、ファイル名をuuidに変更したうえで保存します。 これらを組み合わせると、markdownxさんは名前を変えた上でcloudinaryさんに渡しますが、その後cloudinaryさんがどんな名前にしてファイルを保存したのかを聞かず、自分が知っているファイル名をブラウザに返します。 この動きによって、cloudinaryにあるファイル名とテキストエリアに表示されるURLが異なっていました。 解決策 markdownxさんには辞めてもらって、cloudinaryさんの独り言に耳を傾けましょう。 markdownxは、デフォルトでは画像アップロードをライブラリ内のビューで処理します。 設定を変更して、自前のビューに転送するよう変更します。 settings.py + MARKDOWNX_UPLOAD_URLS_PATH = '/upload' 画像アップロード処理用ビューを用意します。 markdownxの画像アップロードリクエストは、'image'という名前でファイルがきます。 レスポンスは{"image_code": テキストエリアに表示する画像パス}形式のJSONです。 cloudinaryのアップロードのレスポンスは辞書型で、'url'で画像URLを取得できます。 urls.py + path('upload', views.UploadView.as_view(),name='upload'), views.py # 追記 import cloudinary import cloudinary.uploader import cloudinary.api from django.conf import settings from django.http.response import JsonResponse class UploadView(View): def post(self,request, *args,**kwargs): file = request.FILES['image'] cloudinary.config( cloud_name = settings.CLOUDINARY_STORAGE['CLOUD_NAME'], api_key = settings.CLOUDINARY_STORAGE['API_KEY'], api_secret = settings.CLOUDINARY_STORAGE['API_SECRET'] ) res = cloudinary.uploader.upload( file = file, folder = settings.MARKDOWNX_MEDIA_PATH, ) return JsonResponse({"image_code": f"![]({res['url']})"}) これで、markdownxを介さずにcloudinaryに直接アップロードし、正しいURLをテキストエリアで取得できるようになりました。 heroku + django + cloudinaryという組み合わせはメジャーそうなのに、markdownが絡んだ記事がなくて苦労しました。 誰かのお役に立てると幸いです。 みなさんも人の話には耳を傾けましょう。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【小ネタ】プロキシ環境下でPythonのセットアップをした話をする

はじめに 今回はプロキシ配下で Python を利用するときの注意点を書きます。 Python の仮想環境を VSCode で実行する settings.json の python.pythonPath を仮想環境上の python.exe にする。 以下のように指定する。 仮想環境内の.vscode フォルダにある settings.json に以下のように書く { "python.pythonPath": ".\\Scripts\\python.exe" } pip の注意点 pip list は sitepackage のディレクトリが二つあると仮想環境上の package リストではなく 実際にインストールされている package を参照する。 仮想環境上の package リストを参照する場合は--path で参照する。freeze コマンドも然り pip list --path .\Lib\site-packages プロキシを通して pip で numpy をダウンロード pip install --proxy http://username:password@プロキシ URL:port numpy 信頼済みのホストとして登録するオプション --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org pip のアップグレード python -m pip install --proxy http://username:password@プロキシ URL:port --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org --upgrade pip numpy のインストール python -m pip install --proxy http://username:password@プロキシ URL:port --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org numpy pandas のインストール python -m pip install --proxy http://username:password@プロキシ URL:port --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org pandas pyhthon -m venv venv_test cd venv_test\Scripts activate.bat python -m pip install -r requirements.txt --proxy http://username:password@プロキシ URL:port --trusted-host pypi.python.org --trusted-host files.pythonhosted.org --trusted-host pypi.org deactivate.bat ※requirements.txt にはインストールパッケージ名とバージョンを書きます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

組合せ最適化への挑戦録(勤務スケジューリング)10.めでたしめでたし

これまでの踏跡 目次 全体設計のまとめ 出力プログラムの微修正 手修正品を評価するプログラム 手修正プログラムの結果 プログラム全容 全体設計のまとめ 今回設計したプログラムの基本運用手段は下図のようなものである そして、これまでのプログラムは基本的に2.の部分を作ってきた。 今回は2.の部分の最終調整並びに4.の部分をメインに作成する。 4.まで行くとpulpにはあんまり関係がなくなっているが、まあ、後工程も含めて組合せ最適化と呼んでも差し支えはないだろう。知らんけど。 出力プログラムの微修正 まず、プログラムに放り込んだ勤務希望表は下記のようなものである。 表1 勤務希望表 また、これまではプログラムでの容易さを重視して数値で大半を表していたが、現場で使うときには言語にしないと使いにくいので変更。 また、出力された際に実働だけでなく希望がどうだったかも併記したほうがシフト作成者が修正しやすい。 よって、下記のような表が出力されるように微調整を行った。 表2:勤務希望表を組合せ最適化プログラムに入れて出力されるもの そして、今回はそれを管理者が手修正することを前提にしており、手修正後の表が下表となる。 表3:管理者が直した表 昼勤夜勤の合計値や色分けは視認性を上げるために後から追加したものである。 この管理者の手直し表には問題をなるべく多数含むようにしてあり、 ・出勤数が多すぎるシフトがある(必要数3のところに4人いるなど) ・夜勤してない勤務者がいる ・新人だけでシフトを埋めてしまったところがある ・有給、忌避の要望が無視されている ・連続勤務が発生している の5つの問題を起こしている。 で、最終的にこれらの問題点をきっちり評価し、ここに問題がありますよということを指摘できればプログラム作成としての仕事は終わりである。 実際、AさんとBさんが同じシフトに入るのはマズい、などは管理者側で調整いただこう。 手修正品を評価するプログラム マルチインデックスの処理をするだけなので、工夫したところだけ抜粋する 前準備 #元表の段組みが多いので、まずは労務希望について除外したdataframeを作る df_working=df_raw.groupby(["種別"]).get_group("実働") #分類欄が増えたのでリストも改定 list_danc = df_working.index.to_list() list_pm = df_working.columns.to_list() #リスト内タプルは「N個目の配列のM個目の数字を取り出す」事が大変なので、リスト内リストに変換する list_danc=list(map(list,list_danc)) list_pm=list(map(list,list_pm)) このリスト内タプルをリスト内リストに変換する作業は、後で検証したら特に不要だった。 無くてもよいが、リスト内タプルは要素の追加と取り出しでエラーが起きた覚えがあるので、将来的な事を考えて変換したままにしておく。 実際のチェック工程 #各制約、目的に対して集計を取り、問題がないかどうかチェックする #出勤回数の過不足をチェックするため、まずは必要出勤回数をseriesで取得 sr_needs = df_working.reset_index(level=2).iloc[:,0].reset_index(drop=True) #dataframeの行ごとの"出勤"の出現数の総和をSeries型で抜き取る sr_count = (df_working=="出勤").sum(axis=1).reset_index(drop=True) #理想値かどうか確認 sr_compare = (sr_needs==sr_count) #全てTrueなら問題なし。 if sr_compare.all() == True: print(f"出勤回数は最小値の{sr_count.sum()}回です") #Falseが残っているなら、対象の行を取得する else: list_false = list(sr_compare[sr_compare==False].index) for i in list_false: false_day = list_danc[i][0] false_act = list_danc[i][1] print(f"{false_day}日目の{false_act}の出勤回数は最小回数を{sr_count[i]-sr_needs[i]}回上回っています") 特に言うことはないが、マルチインデックスのindex列をそのまま抜き取りたい、と言うときは、 reset_indexで抜き取った列が0列目に入ることを利用してilocで抜き取ると比較的スムーズ。 新人シフトのチェック #新人だけでシフトが埋まっていないかをチェックする #まずは新人だけ抜き取ったdataframeを作る df_newbe = df_working.groupby(["職位"],axis=1).get_group("新人") #各シフトごとの新人の出勤回数和をseriesにする sr_newbe = (df_newbe=="出勤").sum(axis=1).reset_index(drop=True) #行ごとの出勤総和と新人の出勤回数和が同じなら、新人だけでシフトを埋めていることになる #例:1日夜の出勤回数総和が2で、新人の総和も2など。よって、!=で全てTrueならば良しとする sr_compare2 = sr_newbe!=sr_count if sr_compare2.all() == True: print(f"OK! 新人だけで埋まっているシフトはありません") #Falseが残っているなら、対象の行を取得する else: list_false2 = list(sr_compare2[sr_compare2==False].index) for i in list_false2: false_day = list_danc[i][0] false_act = list_danc[i][1] print(f"Caution! {false_day}日目の{false_act}は新人だけでシフトを埋めています") 最初にindexをlist化しておくと、N行目で問題が在った、という情報さえあればfor N in list_indexで 簡単に必要な項目が取り出せるので気楽。 夜勤0回のチェックや、最多-最少出勤差などは上記例から簡単に作れるので割愛 #有給と忌避の希望が潰された人がいるかどうかのチェック #まず種別が希望となっているリストを作る df_wish=df_raw.groupby(["種別"]).get_group("希望") #全データを一意に取り出したいので、stack()を使って整形する df_wish =df_wish.stack(level=[0,1]) df_yukyu = df_wish[df_wish=="有給"] list_yukyu = df_yukyu.index.to_list() #有給リストの「希望」を全部実働に書き換える for i in range(len(list_yukyu)): list_yukyu[i][3] = "実働" #有給を出していたけど実働が「出勤」になっている場合はコメントを出力 for d,a,n,c,p,m in list_yukyu: A= df_working.at[(d,a,n,c),(p,m)] if A=="出勤": print(f"Alert! 勤務者{m}は{d}日目の{a}に有給が却下されています") ポイントになるのはstack()の扱いで、マルチインデックスの行ラベルを全てスタックすると、縦に長い一意の値を全て取り出せる。 視認性は悪くなるが、マルチインデックスの値からその時のインデックスとカラムを抜き取りたいときはだいたいこの処理でなんとかなる。 #連続勤務のチェック #窓関数と.countを使って「縦に2個連続で出勤している箇所」を割り出す df_continuous = df_working.rolling(2).count() #値が2となっていれば直前シフトと連続勤務になっていることを表す df_continuous=df_continuous.stack(level=[0,1]) list_continuous = (df_continuous[df_continuous==2]).index.to_list() list_continuous=list(map(list,list_continuous)) if df_continuous.all()<=1: print("OK! 連続勤務に問題はありません") else: for d,a,n,c,p,m in list_continuous: print(f"Caution! 勤務者{m}は{d}日目の{a}とその直前シフトが連続しています。修正ください") 連続勤務のチェックをする場合は、窓関数で全部countすればいいことに気づいたので、 ここがぐっと楽に終わってよかった。 .rolling(2).count()だと、0行0列には「"0行0列"+”0行-1列"」の個数データが入る。 -1列は存在しないため、0個扱いになるのが通常のdataframeと違うところ。 ※dataframeに[-1]を指定すると、通常は最後尾になる。 手修正プログラムの結果 こんな感じで最終的にチェックをすると、下記のような出力が得られる。 ・出勤数が多すぎるシフトがある(必要数3のところに4人いるなど) ・夜勤してない勤務者がいる ・新人だけでシフトを埋めてしまったところがある ・有給、忌避の要望が無視されている ・連続勤務が発生している 細かい表示調整などはしていないのでやや見づらいが、上記5点の問題は全てチェックされている。 また、組合せ最適化で自動生成したものをそのまま放り込んだら、「全て問題なし」と出力された。 後はもう現場とすり合わせで微調整していくだけなので、ひとまずこれにて完成。 めでたしめでたし。 ただ、完全に個人制作なので可読性と引き継ぎに問題が在っては困るので、後日にでも質問コーナーにて意見をもらおうと思う。 プログラム全容 組合せ最適化する方のプログラム import openpyxl import pandas as pd import numpy as np import pulp df_raw= pd.read_excel("/input.xlsx",index_col=[0,1,2],header=[0,1]) df_origin=df_raw.copy() df_raw.rename(index={"昼":0,"夜":1},level=1,inplace=True) df_raw.rename(columns={"係長":1,"主任":2,"一般":0,"新人":3},level=0,inplace=True) df_raw.replace({np.NaN:0,"忌避":1,"有給":2},inplace=True) df_raw.index.set_names(["date","action","needs"],inplace=True) df_raw.columns.set_names(["position","member"],inplace=True) list_dan = df_raw.index.to_list() list_pm = df_raw.columns.to_list() #問題の定義 ShiftScheduling = pulp.LpProblem("ShiftScheduling", pulp.LpMinimize) #変数宣言 #x[p,m,d,a]で全パターンの0-1変数を作成する x = {} for p,m in list_pm: for d,a,n in list_dan: x[p,m,d,a] = pulp.LpVariable("x({:},{:},{:},{:})".format(p,m,d,a), 0, 1, pulp.LpInteger) #目的関数1 全員の出勤の合計値が小さい方が良い=全変数の総和が小さい方が良い obj1= pulp.lpSum(x) #目的関数2 夜勤忌避の希望が通る方が良い list_yakin=[] for p,m in list_pm: df_yakin = df_raw[df_raw.loc[:,(p,m)]==1] for i in range(len(df_yakin)): da_yakin = df_yakin.index list_temp = [p,m,da_yakin[i][0],da_yakin[i][1]] list_yakin.append(list_temp) obj2 = pulp.lpSum(x[p,m,d,a] for p,m,d,a in list_yakin) #目的関数3 有給の希望が通る方が良い list_yukyu=[] for p,m in list_pm: df_yukyu = df_raw[df_raw.loc[:,(p,m)]==2] for i in range(len(df_yukyu)): da_yukyu = df_yukyu.index list_temp = [p,m,da_yukyu[i][0],da_yukyu[i][1]] list_yukyu.append(list_temp) obj3 = pulp.lpSum(x[p,m,d,a] for p,m,d,a in list_yukyu) #目的関数4 勤務者間の労働回数はなるべく近いほうが良い #勤務の最大回数を用いる。計算は後でするので先に変数を一個作っておく。 obj4 = pulp.LpVariable("workcount",lowBound=0) #目的関数の定義 ShiftScheduling += obj1 + obj2*10 +obj3 + obj4 #制約条件 #制約条件1:全員必ず3回以上出勤 #制約条件4:勤務回数の偏りを減らす(最大値がobj4に格納されるようにする) for p,m in list_pm: ShiftScheduling += pulp.lpSum(x[p,m,d,a] for d,a,n in list_dan) >= 3 ShiftScheduling += pulp.lpSum(x[p,m,d,a] for d,a,n in list_dan) <= obj4 #制約条件2:全シフトで必要人数以上出勤 for d,a,n in list_dan: ShiftScheduling += pulp.lpSum(x[p,m,d,a] for p,m in list_pm) >= n #制約条件3:連続出勤不可 list_da1 = list_dan[:-1] list_da2 = list_dan[1:] for i in range(len(list_da1)): list_da1[i]=list_da1[i]+list_da2[i] for p,m in list_pm: for d1,a1,n1,d2,a2,n2 in list_da1: ShiftScheduling += x[p,m, d1, a1]+x[p,m,d2,a2] <=1 #制約条件5:夜勤一回以上 df_yakin_min = df_raw.groupby(["action"]).get_group(1) list_yakin_min = df_yakin_min.index.to_list() for p,m in list_pm: ShiftScheduling += pulp.lpSum(x[p,m,d,a] for d,a,n in list_yakin_min) >=1 #制約条件6:新人だけでシフトを埋めてはいけない df_newbe = df_raw.groupby(["position"],axis=1).get_group(3) list_newbe = df_newbe.columns.to_list() for d,a,n in list_dan: ShiftScheduling += pulp.lpSum(x[p,m,d,a] for p,m in list_pm) >= pulp.lpSum(x[p,m,d,a] for p,m in list_newbe)+1 #出力 results = ShiftScheduling.solve() print("optimality = {:}, target value = {:}".format(pulp.LpStatus[results], pulp.value(ShiftScheduling.objective))) df_results=df_raw.copy() dict_x = ShiftScheduling.variablesDict() for d,a,n in list_dan: for p,m in list_pm: keys_pmda = "x({},{},{},{})".format(p,m,d,a) values_pmda=dict_x[keys_pmda] r =pulp.value(values_pmda) df_results.at[(d,a,n),(p,m)]=r #結果ファイルの言語化 df_results.rename(index={0:"昼",1:"夜"},level=1,inplace=True) df_results.rename(columns={1:"係長",2:"主任",0:"一般",3:"新人"},level=0,inplace=True) df_results.replace({0:"",1:"出勤"},inplace=True) df_results.index.set_names(["日付","昼夜","必要人数"],inplace=True) df_results.columns.set_names(["職位","名前"],inplace=True) df_results["種別"]="実働" df_results.set_index("種別",inplace=True,append=True) df_origin.index.set_names(["日付","昼夜","必要人数"],inplace=True) df_origin.columns.set_names(["職位","名前"],inplace=True) df_origin.replace(np.nan,"",inplace=True) df_origin["種別"]="希望" df_origin.set_index("種別",inplace=True,append=True) #希望欄と実働欄を分けて作った後、一個の表として合体 df_results = df_results.append(df_origin) df_results = df_results.sort_index(level=[0,1,3],ascending=[1,0,0]) df_results.to_excel("/output.xlsx") 手修正後のチェックプログラム import openpyxl import pandas as pd import numpy as np df_raw= pd.read_excel("/output.xlsx",index_col=[0,1,2,3],header=[0,1]) #最後にCautionとAlertの数を通知するため、先に変数を準備 x_caution=0 x_alert=0 #前準備として、労務希望を除外したdataframeを作り、indexとcolumnsもリスト化する df_working=df_raw.groupby(["種別"]).get_group("実働") list_danc = df_working.index.to_list() list_pm = df_working.columns.to_list() #リスト内タプルをリスト内リストに変換する(無くても動く) list_danc=list(map(list,list_danc)) list_pm=list(map(list,list_pm)) #各制約、目的に対して集計を取り、問題がないかどうかチェックする #1.出勤回数をカウントし、過不足をチェックする sr_needs = df_working.reset_index(level=2).iloc[:,0].reset_index(drop=True) sr_count = (df_working=="出勤").sum(axis=1).reset_index(drop=True) #理想値かどうか確認 sr_compare = (sr_needs==sr_count) if sr_compare.all() == True: print(f"OK! 出勤回数は最小値の{sr_count.sum()}回です") else: list_false = list(sr_compare[sr_compare==False].index) for i in list_false: false_day = list_danc[i][0] false_act = list_danc[i][1] print(f"{false_day}日目の{false_act}の出勤回数は最小回数を{sr_count[i]-sr_needs[i]}回上回っています") #新人だけでシフトが埋まっていないかをチェックする df_newbe = df_working.groupby(["職位"],axis=1).get_group("新人") sr_newbe = (df_newbe=="出勤").sum(axis=1).reset_index(drop=True) sr_compare2 = sr_newbe!=sr_count if sr_compare2.all() == True: print(f"OK! 新人だけで埋まっているシフトはありません") else: list_false2 = list(sr_compare2[sr_compare2==False].index) for i in list_false2: false_day = list_danc[i][0] false_act = list_danc[i][1] print(f"Caution! {false_day}日目の{false_act}は新人だけでシフトを埋めています!修正ください") x_caution =x_caution+1 #勤務者の最多出勤回数と最小出勤回数を比較する sr_eachcount = (df_working=="出勤").sum(axis=0).reset_index(drop=True) print(f"最多出勤者と最少出勤者の出勤回数の差は{sr_eachcount.max()-sr_eachcount.min()}回でした") #夜勤0回の人はいるかどうかのチェック df_noyakin = df_working.groupby(["昼夜"],axis=0).get_group("夜") sr_noyakin = (df_noyakin=="出勤").sum(axis=0).reset_index(drop=True) if sr_noyakin.all() >=1 : print("OK! 全員が夜勤に1回以上出ています") else: list_noyakin = list(sr_noyakin[sr_noyakin == 0].index) for i in list_noyakin: false_member = list_pm[i][1] print(f"Caution! 勤務者名:{false_member}は夜勤に出ていません!修正を推奨します") x_caution =x_caution+1 #有給と忌避の希望が潰された人がいるかどうかのチェック df_wish=df_raw.groupby(["種別"]).get_group("希望") #全データを一意に取り出したいので、stack()を使って整形する df_wish =df_wish.stack(level=[0,1]) df_yukyu = df_wish[df_wish=="有給"] list_yukyu = df_yukyu.index.to_list() list_yukyu=list(map(list,list_yukyu)) #有給リストの「希望」を全部実働に書き換える for i in range(len(list_yukyu)): list_yukyu[i][3] = "実働" #有給を出していたけど実働が「出勤」になっている場合はコメントを出力 for d,a,n,c,p,m in list_yukyu: A= df_working.at[(d,a,n,c),(p,m)] if A=="出勤": print(f"Alert! 勤務者{m}は{d}日目の{a}に有給が却下されています") x_alert = x_alert+1 #夜勤忌避も同じことをする df_kihi = df_wish[df_wish=="忌避"] list_kihi = df_kihi.index.to_list() list_kihi=list(map(list,list_kihi)) for i in range(len(list_kihi)): list_kihi[i][3] = "実働" for d,a,n,c,p,m in list_kihi: A= df_working.at[(d,a,n,c),(p,m)] if A=="出勤": print(f"Alert! 勤務者{m}は{d}日目の{a}に夜勤忌避が却下されています") x_alert = x_alert+1 #連続勤務のチェック #窓関数と.countを使って「縦に2個連続で出勤している箇所」を割り出す df_continuous = df_working.rolling(2).count() #あとは有給と同じようにstackで整形してからdancpmを抜き取り、注意文を作れば完成 df_continuous=df_continuous.stack(level=[0,1]) list_continuous = (df_continuous[df_continuous==2]).index.to_list() list_continuous=list(map(list,list_continuous)) if df_continuous.all()<=1: print("OK! 連続勤務に問題はありません") else: for d,a,n,c,p,m in list_continuous: print(f"Caution! 勤務者{m}は{d}日目の{a}とその直前シフトが連続しています。修正ください") x_caution = x_caution+1 print("\n") if x_caution == x_alert ==0: print("シフト上の大きな問題はありませんでした") if x_caution >=1: print(f"チェックの結果、危険なシフト配置が{x_caution}個ありました。Caution!を確認ください") if x_alert >=1: print(f"チェックの結果、要望を満たせなかったシフト配置が{x_alert}個ありました。Alert!を確認ください")
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GaussianMixture modelによる異常検知 備忘録

概要 異常検知(Anomaly detection)について調べていて発見した副産物について書き残そう。 結局オーソドックスなVAEでいくことにしたのだが、このGMMの方がセンスが良く感じる。 DNNを使ったときの手抜き感は小生だけだろうか? 実施期間: 2021年5月 環境:Google Colaboratory パケージ:scikit-learn なお、日本語で「異常」というとabnormalのイメージがあるが、abnormalとanomalyはニュアンスが違うので、職場では「異常」という言葉は使わないようにしている。 モチベーション Anomaly detectionについて調査中、GaussianMixture Model(GMM)についてのブログを見つけた。 クラスタリングの一種だがK-MeansのようにDeterministicに距離でクラスタ分けするようなアルゴリズムではなく、各点がどのようなGaussian分布に属すべきかをEM(Expectation maximization)で最尤推定するものらしい。 教科書にしているO'REILLYのHands-onでも詳しく説明されており、面白かったので少し味見してみることにした。 以下の順に備忘録として残す。 準備 K-Meansによるクラスタリングの復習 GMMによるクラスタリング GMMによるクラスタリング数のもとめ方 GMMによるAnomaly Detection 1. 準備 いつもimportしているパケージから取捨選択 import numpy as np import pandas as pd import matplotlib.pyplot as plt from sklearn.datasets import load_iris from sklearn import metrics from sklearn import linear_model from sklearn.metrics import plot_confusion_matrix from IPython.core.debugger import Pdb # 念のため 今回使用するdatasetはみんな大好きアヤメちゃん iris = load_iris() feature_names = iris.feature_names target_names = iris.target_names df_iris = pd.DataFrame(data = np.c_[iris['data'], iris['target']], columns = iris['feature_names'] + ['target']) X = df_iris[['sepal length (cm)', 'sepal width (cm)', 'petal length (cm)', 'petal width (cm)']] y = df_iris['target'] x_train = np.array(df_iris[['petal width (cm)','petal length (cm)']]) #x_train = np.array(df_iris[['sepal length (cm)', 'sepal width (cm)']]) y_test = np.array(df_iris['target']) color_codes = {0:'r', 1:'g', 2:'b'} colors_pred = [color_codes[x] for x in y_test] plt.scatter(x_train[:,0], x_train[:,1], c=colors_pred) また、評価結果用の関数を定義 def print_evaluate(x_train, y_test, y_pred, ave='macro'): print('accuracy = \t', metrics.accuracy_score(y_true=y_test, y_pred=y_pred)) print('precision = \t', metrics.precision_score(y_true=y_test, y_pred=y_pred, average=ave)) print('recall = \t', metrics.recall_score(y_true=y_test, y_pred=y_pred, average=ave)) print('f1 score = \t', metrics.f1_score(y_true=y_test, y_pred=y_pred, average=ave)) print('confusion matrix = \n', metrics.confusion_matrix(y_true=y_test, y_pred=y_pred)) ※2値のときは average='binary' 2. K-Meansによるクラスタリング 色々とアルゴリズムはあるけれど、GMMで少し言及するK-Meansについて復習する。 簡単に言えば点群の集まりの中心(centroid)がどこかを探すやつ。 指定数のcentroidと全sampleの距離が小さくなるように全centroidを移動させ、収束したときの各centroidに近いsampleがそのクラスタに属するとするアルゴリズム まず、オーソドックスなK-Meansを見てみる。クラスタ(centroid)数は既知のn_clusters=3とする。 from sklearn.cluster import KMeans from sklearn.cluster import MiniBatchKMeans ## K-Means kmeans = KMeans(n_clusters=3, random_state=0).fit(x_train) y_pred = kmeans.labels_ y_cent = kmeans.cluster_centers_ # 精度評価結果 print_evaluate(x_train, y_test, y_pred, 'macro') colors_pred = [color_codes[x] for x in y_pred] plt.scatter(x_train[:,0], x_train[:,1], c=colors_pred) plt.scatter(y_cent[:,0], y_cent[:,1], s = 250, marker='*',c='y') plt.show accuracy = 0.37333333333333335 precision = 0.37286324786324787 recall = 0.37333333333333335 f1 score = 0.3730825663598773 confusion matrix = [[50 0 0] [ 0 2 48] [ 0 46 4]] Confusion matrixで分かるように青と緑はデータがかぶっているため誤判定がみられる。 ついでにMini Batch K-Meansも見てみる。 K-Meansを使うと計算コストが問題になるほどデータ数が多いときに代用するアルゴリズム 任意の数(mini batch)のSampleでK-Means同様にcentroidを移動させるが、mini batchを変えるたびに過去のcentroidと平均する。 精度はK-Meansより劣化するが無視できるほどらしい。 ## Mini Batch K-Means mb_kmeans = MiniBatchKMeans(n_clusters=3, random_state=0, batch_size=6).fit(x_train) y_pred = kmeans.labels_ y_cent = kmeans.cluster_centers_ # 精度評価結果 print_evaluate(x_train, y_test, y_pred, 'macro') colors_pred = [color_codes[x] for x in y_pred] plt.scatter(x_train[:,0], x_train[:,1], c=colors_pred) plt.scatter(y_cent[:,0], y_cent[:,1], s = 250, marker='*',c='y') plt.show accuracy = 0.37333333333333335 precision = 0.37286324786324787 recall = 0.37333333333333335 f1 score = 0.3730825663598773 confusion matrix = [[50 0 0] [ 0 2 48] [ 0 46 4]] もともとデータ数が少ないので精度が悪化はわからない。 3. GMMによるクラスタリング では、まずGaussian Mixture Model (GMM)でK-Meansのようにクラスタリングする。 最初にTrainingを行い、GMMはProbablistic ModelなのでPredictする。 もう少し言うと、predict()はラベルの予測を行い、predict_prob()はそのラベルとなる確率を出力する。 K-Meansはユークリッドな距離でクラスタ分けを行うので確率の概念がなかった。 GMMではこの確率を用いて後述のAnomly ditectionを行うこととなる。 from sklearn.mixture import GaussianMixture ## Gaussian Mixture Model gm = GaussianMixture(n_components=3, n_init=10) gm.fit(x_train) # fit()で収束したか確認 print(f'Convergence: {gm.converged_}') y_pred = gm.predict(x_train) y_pred = np.array(y_pred, dtype=np.float) # Predict()の戻りの型がy_testと違うので一応合わせる。 y_pred_prob = gm.predict_proba(x_train) # 精度評価結果 print_evaluate(x_train, y_test, y_pred, 'macro') colors_pred = [color_codes[x] for x in y_pred] plt.scatter(x_train[:,0], x_train[:,1], c=colors_pred) plt.show Convergence: True accuracy = 0.02 precision = 0.019230769230769232 recall = 0.02 f1 score = 0.0196078431372549 confusion matrix = [[ 0 50 0] [ 1 0 49] [47 0 3]] ラベルの誤判定がK-Meansと比べ、2か所改善していることがわかる。 4. GMMによるクラスタ数のもとめ方 クラスタごとのGaussian disributionを現すパラメータは少なければ少ないほどよく、一方そのパラメータのlikelihood(尤度)は大きければ大きいほどよい。 とてもおおざっぱだけど、それを表現する基準がBIC(Bayesian Information Criterion)とAIC(Akaike Information Criterion)である。 詳細は上述Hands-on教科書のP267~269に解説あり。 クラスタ数を振ってBIC, AICをプロットすると最適なクラスタ数を得ることができる。 n_components = np.arange(1, 10) models = [GaussianMixture(n, n_init=10).fit(x_train) for n in n_components] fig, ax = plt.subplots(figsize=(9,7)) ax.plot(n_components, [m.bic(x_train) for m in models], label='BIC') ax.plot(n_components, [m.aic(x_train) for m in models], label='AIC') plt.legend(loc='best') plt.xlabel('n_components') ゑっ? よく見るチャートぢゃない・・・ サンプル数150が少ないのかもしれない。 GMMはProbabilistic modelなのでsample()メソッドで確率密度に沿ったデータの増産することができる。 そこで合計1000個(+850個)まで増やして再描画してみる。 x_new, y_new = gm.sample(850) x_new = np.array(x_new).reshape(850,2) y_new = np.array(y_new) x_augment = np.vstack([x_train, x_new]) y_augment = np.append(y_test, y_new) n_components = np.arange(1, 10) models = [GaussianMixture(n, n_init=10).fit(x_augment) for n in n_components] fig, ax = plt.subplots(figsize=(6, 6)) ax.plot(n_components, [m.bic(x_new1) for m in models], label='BIC') ax.plot(n_components, [m.aic(x_new1) for m in models], label='AIC') plt.legend(loc='best') plt.xlabel('n_components') 3の時がBIC,AICともに最小であることから、クラスタ数=3が尤もらしいとわかる。 (よかった) もちろんK-Meansでもsilhouette_score()やsilhouette diagramで最適なクラスタ数を推定することができる。 5. GMMによるAnomaly Detection 調べたいデータがanomalyか否かは上述のGMMモデルのスコアとしてscore_samples()で簡単に調べることができる。 これで得られるスコアはデータの確率密度の濃さを示している。 なお、scikit-learnのscore_samples()は自然対数で返すためexp()した。 例えばデータ群の中の(0.2, 0.1)とデータ群から外した(2.5, 0.5)の2データについてスコアを見てみる。 x_compare = np.array([0.2, 1.0, 2.5, 0.5]).reshape(2, 2) print(x_compare) score_compare = gm.score_samples(x_compare) print(np.exp(score_compare)) [[0.2 1. ] [2.5 0.5]] [7.56273077e-02 1.36493463e-24] 期待通り、前者が大きくなり、後者はほぼゼロとなっている。データ分布がGaussianなら色々使えそう。 もちろん、特異点の判断基準はドメインの要求によるため言及できない。 以上
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む