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

[Python]Matplotlibで線グラフが汚い時

ndarrayの二次元データにplotメソッドを使ったが 線がぐちゃぐちゃになったので対処法を示す. 元データの散布図 これを何もせずにplotを使うと, import numpy as np import matplotlib.pyplot as plt df=np.loadtxt('test.txt') x=df[:, 0] ##0列目をxに入れる y=df[:, 1] ##1列目をyに入れる plt.plot(x, y, marker='o') /// np.sort() を使うとxデータとyデータがセットになっていないので失敗. df=np.sort(df, axis=0) x=df[:, 0] y=df[:, 1] plt.plot(x, y, marker='o') 対処法 np.argsort() を使う. a=np.argsort(df, axis=0) b=a[:,0] x=df[b, 0] y=df[b, 1] plt.plot(x, y, marker='o', lw=7) データフレーム型なら最初からx, yがペアになっているので 初めからpandasでインポートするのもあり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

忘備のためにWin10環境でAnaconda上にTa-libをインストールする方法を書いておく

Win10環境でAnaconda上にTa-libをインストールした際に少々苦労をしたのでその方法を忘れたときのために書き記しておく。 まず、Pythonはver 3.6、3.9や3.8では現状でインストールが出来なかった。 次に、このページ(Anacondaのquantopian検索結果?)を表示。 https://anaconda.org/quantopian/ta-lib そこにあるcondaコマンドをコピー conda install -c quantopian ta-lib Anacondaコマンドプロンプトに貼り付けてインストール、これで完了(のはず)。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

2020年度SQiP研究会で発表された「自然言語処理を利用した類似障害情報の抽出と活用方法の提案」の内容を写経してみた

はじめに 2020年度SQiP研究会(ソフトウェア品質管理研究会)で発表された「自然言語処理を利用した類似障害情報の抽出と活用方法の提案」の内容を写経してみました。 論文の付録として、ソースコードが掲載されているのは非常にありがたかったです。 https://www.juse.or.jp/sqip/workshop/report/attachs/2020/5_sku_ronbun.pdf ※SQiP研究会の成果報告会の資料の一覧 http://juse.or.jp/sqip/workshop/report/2020.html 論文のおかけで、Pythonで自然言語をベクトル表現に変換するプログラムを、写経で作れました。 論文の提案通りに作業を実施してみた結果、文書をベクトル表現したデータがEmbedding Projectorで表示され、文書の中から類似度の高い文が特定できました。 同じようなことを異なる表現で繰り返している箇所がわかるので、文書を推敲するために活用できる可能性があります。 ※Embedding Projectorの参考記事 「Embedding projectorで類書の距離を可視化してみた」,2020 https://www.techceed-inc.com/engineer_blog/5729/ 使用する技術 Python GiNZA Embedding Projector https://projector.tensorflow.org/ 準備 参考サイト 準備手順 「Python入門」のサイトで示されている通りに以下を実施。 「Pythonインストールと環境設定」を実施。 「Pythonプログラムの基本事項」を写経。 既にPythonやAnacondaがインストールされているPCで以降の作業を実施しようとしたときに、うまくいかないことがあった。 pipでライブラリのインストールに失敗した。 写経 論文の付録に掲載されているソースコードをもとに、以下のように写経(コピー&ペースト)でコーディングし、test.pyを作成。 #-------------------------------------------------------- #【目的】 # GiNZA使って、複数のCSVテキスト文章から、 # 文章の長短、単語に依存しない文章ベクトルを生成し # Embedding Projectorを使用して類似を表示させる。 #【使用】 # トークナイザー(形態素解析)はGiNZA Model(V4.0.0)を使用。 #【表示】 # Embedding Projector(http://projector.tensorflow.org/) # 出力ファイル「basedoc_text.tsv」と # GiNZAでの文章ベクトル「basedoc_vector_ginza.tsv」を使用し、 # PCA(T-SNEやUMAPなども選択する事も可)による次元圧縮を行い、 # COS類似度などを選択し3次元可視化する。 #-------------------------------------------------------- #======================================================== import pandas as pd import re import torch import ginza import spacy nlp = spacy.load('ja_ginza') #========================================================= # GiNZAによる形態素解析および文章ベクトル算出サブ処理 # #【解説】 # GiNZAによるトークナイズ(形態素解析)と併せて # 同時に文章の長さに依存しないベクトルを生成する。 # GiNZAの次元数はデフォルトで300次元(V4.0.0)。 # <引数> # text: 文章テキスト # <戻り値> # GiNZAによる300次元(V4.0.0)の文章ベクトル #========================================================= def compute_vector(text): doc1 = nlp(text) tokens = [] print("変換中===> ",doc1) for sent in doc1.sents: for token in sent: tokens.append(token.orth_) return doc1.vector # 文章の長さ(単語数(Token数)分)に依存しない 1x300次元のベクトルを生成 #========================================================= # GiNZA文章ベクトル算出メイン処理 # #【解説】 # 複数のテキスト文章からGiNZAで文章の長さに依存しない文章ベクトルを出力します。 # COS類似度など、以降の類似計算に単語数に依存しない方法での計算に使用可能。 # <入力ファイル> # basedoc.csv: # 変換したい複数の原文をCSV形式で登録したもの # <出力ファイル> # basedoc_text.tsv: # 以下のベクトルファイルに対応した原文ファイル # basedoc_vector_ginza.tsv: # GiNZAによる文章ベクトルデータ。1文章=300ベクトルデータで出力 #========================================================= # 入力ファイル・出力ファイルの作業フォルダ(適切なフォルダパスを指定のこと) work_folder = r'C:\Users\UserName\Documents\Python' + '\\' basedocs_df = pd.read_csv(work_folder + 'basedoc.csv', encoding = "shift-jis") basedocs_df["text"] = basedocs_df["text"].astype(str) # 文字列にしておく vectors_ginza = [] basedocs = [] for basedoc in basedocs_df["text"]: basedoc = re.sub('\n', " ", basedoc) # 改行文字の削除 strip_basedoc = re.sub(r'[︰-@]', "", basedoc) # 全角記号の削除 try: if len(strip_basedoc) > 3: # 文字数が少なすぎると適切なベクトルが得られない可能性があるため vector = compute_vector(strip_basedoc) vectors_ginza.append(vector) basedocs.append(basedoc) except Exception as e: continue # TSVファイル出力 pd.DataFrame(basedocs).to_csv(work_folder + 'basedoc_text.tsv', sep='\t', index=False, header=None) pd.DataFrame(vectors_ginza).to_csv(work_folder + 'basedoc_vector_ginza.tsv', sep='\t', index=False, header=None) 以下の2点だけ、論文のサンプルコードから変えました。 work_folderの位置 read_csvの使い方 変更前 basedocs_df = pd.read_csv(work_folder + 'basedoc.csv') 変更後 basedocs_df = pd.read_csv(work_folder + 'basedoc.csv', encoding = "shift-jis") 入力ファイル作成 Execlで、basedoc.csvという名前の入力ファイルを作成する。 1行目は、textという固定文言。 2行目以降は、ベクトル表現したい文 作成した入力ファイルは、work_folderに格納する。 入力ファイルのイメージ 実行(ベクトルデータ生成) Anaconda Promptを起動させ、以下を実行する。 1. ライブラリのインストール >pip install pandas >pip install torch >pip install ginza >pip install spacy 2. Pythonを対話モードで起動し、利用するライブラリが無事インストールできているか確認 >>> import pandas as pd >>> import re >>> import torch >>> import ginza >>> import spacy 確認後は、Ctrl+Zで対話モードを終了する。 3. プログラムの実行 cdコマンドで、test.pyのあるフォルダに移動する。 test.pyを実行する。 >python test.py basedoc_text.tsvとbasedoc_vector_ginza.tsvがbasedoc.csvを格納していたフォルダに生成されていたら成功。 これで、文書をベクトル表現したデータが生成できる。 Embedding projectorの入力となるベクトルファイル(basedoc_vector_ginza.tsv)およびテキストファイル(basedoc_text.tsv)が生成できる。 実行(Embedding projectorで文の類似度を可視化) 論文の付録に掲載されているEmbedding projector使用手順を参考に操作。 1. Embedding projectorの起動 以下サイトでEmbedding projectorを起動する。 https://projector.tensorflow.org/ 2. ベクトルファイル(basedoc_vector_ginza.tsv)およびテキストファイル(basedoc_text.tsv)を読み込む Loadボタンをクリックする。 表示しされたダイアログで、生成したベクトルファイル(basedoc_vector_ginza.tsv)およびテキストファイル(basedoc_text.tsv)を選択する。 以下の図の、上にベクトルファイルを設定し、下にテキストファイルを設定する。 ファイルを指定したら、適当なところをクリック。 以下の図のような3次元表現の図が表示される。 適当な丸を選択すると、以下のような表示がされる。 分析 論文では、以下の考察が述べられている。 実験結果から,類似度(cosine 距離)が 0.3 以下は類似障害として一致する確率が高い(約 73%(19/24))ことが分かるため,類似度の閾値として活用できるのではないかと考える. 実際に、今回の実験データでも、cosine 距離が0.3以下の文は、類似度が高いと判断してよさそうである。 以下の2つの文のcosine 距離は、0.187であった。 * 実際に我々の組織では,「修飾語」または「同義語」を使用している文が不具合を誘発していた. * 具体的には,「修飾語」または「同義語」を使用していた文が,不具合を誘発していた. 類似度が高い文を特定できるため、同じことを別の表現で繰り返している箇所について、見直しをかけることが可能となる。 今回の論文で提案されている技術は、文書を推敲するためにも活用できる可能性がある。 おわりに 貴重な論文を執筆していただいた 2020年度SQiP研究会研究コース5の上田良太様、栗原崇至様、杉本智様に感謝の意を表します。 ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FastAPIでOpenCV Streaming

はじめに 最近、FastAPIが使いやすいということでFastAPIを試しに使ってみることにしました。 題材は、以前投稿したFlaskとOpenCVで live streamingのFastAPI版です。 stackoverflowにすでにこの書き換えの記事が投稿されていますが、前回投稿したものに合わせて少し書き換えたものを覚えとして記載します。 FASTAPI: Not able to render html page FastAPIの解説については、様々な投稿があります。 公式(日本語)も充実しています。 FastAPI FlaskからFastAPIにスムーズに移行する FastAPIとPythonを使用してAPIを高速に構築する FastAPI 入門 FastAPIは、Web部分はStarlette、データ部分はPydanticの上に構築されており、以下の記事に詳しい解説があります。 FastAPIでStarletteとPydanticはどのように使われているか FastAPIの基本は、Requestに対して、JSONをResponceします。 一方で、HTMLのテンプレートを使うこともできます。(公式ドキュメント) カスタムレスポンス - HTML、ストリーム、ファイル、その他のレスポンス 前回投稿したFlask(再掲)の復習 FlaskとOpenCVで live streaming この部分は、前回と重複なので読み飛ばしても構いません。 ホルダー構成 ─── static | └── js(必要があれば) | | └──main.js | └── main.cs ├── templates │ └── index.html ├── camera_single.py (処理する関数) └── flask_app.py (メインプログラム) <!-- index.html --> <html> <head> <title>Video Streaming Demonstration</title> </head> <body> <h1>Video Streaming Demonstration</h1> <img src="{{ url_for('video_feed') }}"> <!-- jinja2のテンプレートの書き方です。/video_feedを呼び出しています。 --> </body> </html> Staticホルダーは今回も空です。 # camera_single.py import cv2 class Camera(): def __init__(self): self.video = cv2.VideoCapture(0) # Opencvのカメラをセットします。(0)はノートパソコンならば組み込まれているカメラ def __del__(self): self.video.release() def get_frame(self): success, image = self.video.read() ret, jpeg = cv2.imencode('.jpg', image) return jpeg.tobytes() # read()は、二つの値を返すので、success, imageの2つ変数で受けています。 # OpencVはデフォルトでは raw imagesなので JPEGに変換 # ファイルに保存する場合はimwriteを使用、メモリ上に格納したい時はimencodeを使用 # cv2.imencode() は numpy.ndarray() を返すので .tobytes() で bytes 型に変換 # flask_app.py from flask import Flask, render_template, Response from camera_single import Camera app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') # "/" を呼び出したときには、indexが表示される。 def gen(camera): while True: frame = camera.get_frame() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') # returnではなくジェネレーターのyieldで逐次出力。 # Generatorとして働くためにgenとの関数名にしている # Content-Type(送り返すファイルの種類として)multipart/x-mixed-replace を利用。 # HTTP応答によりサーバーが任意のタイミングで複数の文書を返し、紙芝居的にレンダリングを切り替えさせるもの。 #(※以下に解説参照あり) @app.route('/video_feed') def video_feed(): return Response(gen(Camera()), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': print('stop: ctrl+c') app.run(host="0.0.0.0", debug=True, port=5000) # 0.0.0.0はすべてのアクセスを受け付けます。 # webブラウザーには、「localhost:5000」と入力 マルチアクセスへの対応 FlaskとOpenCVでカメラ画像をストリーミングして複数ブラウザでアクセスする この記事で この実装では複数ブラウザ・タブからのアクセスに対応しておらず、別のタブからアクセスしても画像は表示されません。 そこで、参考の記事ではcamera_single.pyを以下のように書き換えています。cameraの継承元のBaseCameraクラスも作成します。詳しい実装の解説は記事を参考にしてください。 # camera_multi.py import cv2 from base_camera import BaseCamera class Camera(BaseCamera): def __init__(self): super().__init__() # over-wride of BaseCamera class frames method @staticmethod def frames(): camera = cv2.VideoCapture(0) if not camera.isOpened(): raise RuntimeError('Could not start camera.') while True: # read current frame _, img = camera.read() # encode as a jpeg image and return it yield cv2.imencode('.jpg', img)[1].tobytes() # base_camera.py import copy import time import threading try: from greenlet import getcurrent as get_ident except ImportError: try: from thread import get_ident except ImportError: from _thread import get_ident class CameraEvent(object): """An Event-like class that signals all active clients when a new frame is available. """ def __init__(self): self.events = {} def wait(self): """Invoked from each client's thread to wait for the next frame.""" ident = get_ident() if ident not in self.events: # this is a new client # add an entry for it in the self.events dict # each entry has two elements, a threading.Event() and a timestamp self.events[ident] = [threading.Event(), time.time()] return self.events[ident][0].wait() def set(self): """Invoked by the camera thread when a new frame is available.""" now = time.time() remove = [] for ident, event in self.events.items(): if not event[0].isSet(): # if this client's event is not set, then set it # also update the last set timestamp to now event[0].set() event[1] = now else: # if the client's event is already set, it means the client # did not process a previous frame # if the event stays set for more than 5 seconds, then assume # the client is gone and remove it if now - event[1] > 5: remove.append(ident) for ident in remove: del self.events[ident] def clear(self): """Invoked from each client's thread after a frame was processed.""" self.events[get_ident()][0].clear() class BaseCamera(object): thread = None # background thread that reads frames from camera frame = None # current frame is stored here by background thread last_access = 0 # time of last client access to the camera event = CameraEvent() def __init__(self): """Start the background camera thread if it isn't running yet.""" if BaseCamera.thread is None: BaseCamera.last_access = time.time() # start background frame thread BaseCamera.thread = threading.Thread(target=self._thread) BaseCamera.thread.start() # wait until frames are available while self.get_frame() is None: time.sleep(0) def get_frame(self): """Return the current camera frame.""" BaseCamera.last_access = time.time() # wait for a signal from the camera thread BaseCamera.event.wait() BaseCamera.event.clear() return BaseCamera.frame @staticmethod def frames(): """"Generator that returns frames from the camera.""" raise RuntimeError('Must be implemented by subclasses.') @classmethod def _thread(cls): """Camera background thread.""" print('Starting camera thread.') frames_iterator = cls.frames() #call for frames method (Staticmethod) for frame in frames_iterator: BaseCamera.frame = frame BaseCamera.event.set() # send signal to clients time.sleep(0) # if there hasn't been any clients asking for frames in # the last 10 seconds then stop the thread if time.time() - BaseCamera.last_access > 10: frames_iterator.close() print('Stopping camera thread due to inactivity.') break BaseCamera.thread = None # flask_app2.py from flask import Flask, render_template, Response # Camera_multiをインポートしています。 from camera_multi import Camera # from camera_single import Camera app = Flask(__name__) @app.route('/') def index(): return render_template('index.html') def gen(camera): while True: frame = camera.get_frame() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n\r\n') @app.route('/video_feed') def video_feed(): return Response(gen(Camera()), mimetype='multipart/x-mixed-replace; boundary=frame') if __name__ == '__main__': print('stop: ctrl+c') # threaded=Trueにしています。 app.run(host="0.0.0.0", port=5000, debug=True, threaded=True) FastAPIへの書き換え # fastapi_app.py import uvicorn from fastapi import FastAPI, Request from fastapi.responses import HTMLResponse, StreamingResponse # from fastapi.staticfiles import StaticFiles from fastapi.templating import Jinja2Templates # from camera_single import Camera from camera_multi import Camera app = FastAPI() # app.mount("/static", StaticFiles(directory="static"), name="static") templates = Jinja2Templates(directory="templates") @app.get("/", response_class=HTMLResponse) async def index(request: Request): return templates.TemplateResponse('index.html', {"request": request}) def gen(camera): """Video streaming generator function.""" while True: frame = camera.get_frame() yield (b'--frame\r\n' b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n') @app.get('/video_feed', response_class=HTMLResponse) async def video_feed(): """Video streaming route. Put this in the src attribute of an img tag.""" return StreamingResponse(gen(Camera()), media_type='multipart/x-mixed-replace; boundary=frame') if __name__ == "__main__": print('stop: ctrl+c') uvicorn.run(app, host="0.0.0.0", port=8000) Flaskとの変更点 ・/Video_feedのレスポンスをStreamingResponseに変更、mimetypeをmedia_typeに変更 ・/indexのレスポンスのrender_templateからtemplates.TemplateResponseに変更 @app.get("/", response_class=HTMLResponse)のresponse_classを指定することでドキュメントが自動生成されます。 http://localhost:8000/docs でアクセスできます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivydで、日本語フォントがnot foundになってしまう件について

日本語フォントを使おうとした際に出るエラーに関して resource_add_path("C:/Users/info/AppData/Local/Microsoft/Windows/Fonts") LabelBase.register(DEFAULT_FONT,"日本語フォント.otf") ※otfは拡張子のため、フォントによっては色んな拡張子があります。 Fontのパスを、Windowsのプロパティのままコピーすると ディレクトリの区切り文字が\になっていますが、 /に変更すると読み込みはうまくいきます。 よく出るエラー内容はこちら Traceback (most recent call last): File "c:\Users\info\Desktop\MyPython\sample.py", line 9, in <module> File "C:\Users\info\anaconda3\lib\site-packages\kivy\core\text\__init__.py", line 315, in register raise IOError('File {0} not found'.format(font_type)) OSError: File ZakkuriGothic-BLK.otf not found フォントのパスを確認する方法 1、C:\Windows\Fonts というフォルダを開き、パスを確認したいフォントを右クリック 2、プロパティを開き、パスを確認
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonからインターネットショートカット(拡張子.url)を開く

Windows環境で拡張子が.urlのインターネットショートカットがあるが、これをPythonからブラウザーに開かせたいなと思った。 urlファイルの中身をテキストエディター等で見ると、次のようなiniファイル形式をしている(最小限の記述での例)。 [InternetShortcut] URL=https://qiita.com/ Pythonでiniファイルを扱うにはconfigparserを使用することができる。また、ブラウザーを扱うにはwebbrowserを使用した。 Python 3.6, 3.9で使用できるので、最近のバージョンなら問題なさそう。 Windows 10 + Edge, Ubuntu 20.04 + firefoxで確認したところ、どちらも期待通りに動作した。 import webbrowser import configparser # インターネットショートカットファイルパス filename = 'some.url' # ini形式でファイルを開く。interpolation=Noneの指定は本文参照。 url_file = configparser.ConfigParser(interpolation = None) url_file.read(filename) # URLを読んでデフォルトのブラウザーで表示する。 url = url_file['InternetShortcut']['URL'] webbrowser.open(url) Pythonでは様々な形式のiniファイルを扱えるよう、ConfigParserの引数で指定するinterpolationオプションで変換方式を決めている。ここで、標準だと%を特殊な記号として解釈するようになっている。 しかし、インターネットショートカットでは%2fのようにURL中の記号や日本語などエンコードすることがあり、このままでは解釈エラーで例外を出してしまう。これを防止するために、interpolation = Noneを指定して変換を無効にしている。 まとめ Pythonから拡張子が.urlのインターネットショートカットをブラウザーに表示させる方法を調べた。中身がini形式のファイルなのでconfigparserで読み込み、webbrowserでブラウザーを開くことで実現した。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Selenium inputかbuttonか分からないボタンをクリック

画面のマニュアルしか持っていなくてHTMLソースをまだ見れないうちに、ボタンクリック動作を書く方法です。ボタンがinputかbuttonか分からないので「|」でOR条件にしています。idやnameがあるかどうかも分からないのでボタン名での記述です。 「|」の前後の半角スペースはあってもなくても動きます。 HTML.html <input type="submit" value="登録" /> または <button type="submit">登録</button> C#.cs driver.FindElement(By.XPath("//input[@value='登録'] | //button[text()='登録']")).Click(); Java.java driver.findElement(By.xpath("//input[@value='登録'] | //button[text()='登録']")).click(); Python.py driver.find_element_by_xpath("//input[@value='登録'] | //button[text()='登録']").click()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

FactorMIDAS(uMIDAS)を用いた我が国GDPのナウキャスティング

目的 前回の続き [1]の再現 背景知識 因子分析 多変量解析の手法として、因子分析と主成分分析が広く知られている。因子の推定法として、主因子法、主成分法、最尤法[2]があげられる。その中でも、主成分法は『主成分法で回転をしない結果は、主成分分析の分析結果と同じ』[2]である。そこで、本誌では主成分分析とスパース主成分分析を用いて、因子を導出する。以下、一般的なファクターモデル x_t = \Lambda F_t + e_t 主成分分析 一般モデル[3] \hat{V} = \underset{V_k}{\operatorname{argmin}} \sum_{i=1}^n ||x_i - \hat{x_i} ||^2 \quad s.t. \quad \hat{x_i} = V_k V_k^T x_i. ファクターモデル[4] V(\hat{F}, \hat{\Lambda} )= (NT)^{-1}\sum_{i=1}^n ||x_i - \hat{\Lambda} \hat{F_t} ||^2 \quad スパース主成分分析 一般モデル[3] V(\hat{A}, \hat{B}) = \underset{A,B}{\operatorname{argmin}} \{ \sum_{i=1}^n ||x_i - AB^Tx^i ||^2 + \lambda_2 \sum_{j=1}^k || \beta_j ||^2 + \sum_{j=1}^k \lambda_{1,j} || \beta_j ||_1 \}. ファクターモデル[1] V^{LASSO}(F,\Lambda;X, \psi_T ) = \frac{1}{nT} \{ \sum_{i=1}^n \sum_{t=1}^T (x_it - \lambda_i F_{t} )^2 + \psi_T \sum_{i=1}^n | \lambda_i | \}. また、上記の手法で求めた因子を用いて以下の手法でナウキャスティングをする。 因子ブリッジ方程式(因子混合頻度アプローチ)[1] y_t = \alpha + \sum_{i=1}^{N} \beta_i x^Q_{i,t} + \sum_{i=1}^{M} \gamma_i F^Q_{i,t} + \varepsilon _t \\ x^Q_{i,t} = \frac{1}{3}(x^M_{i,3t} + 2 x^M_{i,3t-1} + 3 x^M_{i,3t-2} + 2 x^M_{i,3t-3} + x^M_{i,3t-4}) \\ F^Q_{i,t} = \frac{1}{3}(F^M_{i,3t} + 2 F^M_{i,3t-1} + 3 F^M_{i,3t-2} + 2 F^M_{i,3t-3} + F^M_{i,3t-4}) \\ FactorMIDAS(因子混合データサンプリングモデル)[1] y_t = \alpha + \sum_{i=1}^{k_1} \sum_{j=0}^{l_{1,i}} \beta_{i,j} x^M_{i,3t - j} + \sum_{i=1}^{k_2} \sum_{j=0}^{l_{2,i}} \gamma_{i,j} F^M_{i,3t - j} + \varepsilon _t また、 使用した指数は鉱工業指数(生産指数) 鉱工業総合(以下、iipと記す)、第3次産業活動指数 第3次産業総合 (以下、itaと記す)とGDPの四半期速報値である。 なお、使用したデータは、e-statのデータを前処理を行なった上で使用した。 最後にrepositoryはこちら 因子ブリッジ方程式 主成分分析 from sklearn.linear_model import LassoLarsIC, LinearRegression import numpy as np import pandas as pd # sklearnの標準化モジュールをインポート from sklearn.preprocessing import StandardScaler from sklearn.decomposition import SparsePCA, PCA import matplotlib.pyplot as plt gdp = pd.read_csv('import/GDP_JAPAN.csv', parse_dates=['DATE'], index_col='DATE') iip = pd.read_csv('import/IIP.csv', parse_dates=['DATE'], index_col='DATE') ita = pd.read_csv('import/ITA.csv', parse_dates=['DATE'], index_col='DATE') gdp.index = pd.to_datetime(gdp.index, format='%m/%d/%Y').strftime('%Y-%m-01') iip.index = pd.to_datetime(iip.index, format='%m/%d/%Y').strftime('%Y-%m-01') ita.index = pd.to_datetime(ita.index, format='%m/%d/%Y').strftime('%Y-%m-01') dfX = pd.concat([iip['IIP_YOY'], ita['ITA_YOY']], axis=1) # データを変換する計算式を生成 sc = StandardScaler() sc.fit(dfX) # 実際にデータを変換 z = sc.transform(dfX) dfX_std = pd.DataFrame(z, columns=dfX.columns) # 主成分分析 transformer = PCA(n_components=1, random_state=0) transformer.fit(z) X_transformed = transformer.transform(z) # 前処理 dfX_factor = pd.DataFrame(X_transformed, columns=['FACTOR']) dfX_factor.index = iip.index dfX_std.index = iip.index df_std_factor = pd.merge(dfX_factor, dfX_std, left_index=True, right_index=True, how='outer') dfX_std_factor = pd.merge(df_std_factor, gdp, left_index=True, right_index=True, how='outer') dfX_std_factor = dfX_std_factor[['GDP_CYOY', 'IIP_YOY', 'ITA_YOY', 'FACTOR']] dfX_std_factor = dfX_std_factor[dfX_std_factor.index != '2013-01-01'] dfX_std_factor['PERIOD'] = pd.to_datetime(dfX_std_factor.index.to_series()).apply( lambda x: 3 if x.month in [1, 4, 7, 10] else (1 if x.month in [2, 5, 8, 11] else 2)) # bridge_factor bridge_factor = pd.DataFrame( columns=['BRIDGE_IIP_YOY', 'BRIDGE_ITA_YOY', 'BRIDGE_FACTOR']) x = np.array([]) y = np.array([]) z = np.array([]) flag = False for date, IIP_YOY, ITA_YOY, FACTOR in zip(dfX_std_factor.index, dfX_std_factor['IIP_YOY'], dfX_std_factor['ITA_YOY'], dfX_std_factor['FACTOR']): x = np.append(x, IIP_YOY) y = np.append(y, ITA_YOY) z = np.append(z, FACTOR) if flag == False: if date == '2013-07-01': flag = True if flag: x3t = x[-1] x3tm1 = x[-2] x3tm2 = x[-3] x3tm3 = x[-4] x3tm4 = x[-5] xt = (x3t + 2*x3tm1 + 3*x3tm2 + 2*x3tm3 + x3tm4)/3 y3t = y[-1] y3tm1 = y[-2] y3tm2 = y[-3] y3tm3 = y[-4] y3tm4 = y[-5] yt = (y3t + 2*y3tm1 + 3*y3tm2 + 2*y3tm3 + y3tm4)/3 z3t = z[-1] z3tm1 = z[-2] z3tm2 = z[-3] z3tm3 = z[-4] z3tm4 = z[-5] zt = (z3t + 2*z3tm1 + 3*z3tm2 + 2*z3tm3 + z3tm4)/3 record = pd.Series([xt, yt, zt], index=bridge_factor.columns, name=date) bridge_factor = bridge_factor.append(record) bridge_factor.index.name = 'DATE' df_bridge = pd.merge(gdp, bridge_factor, left_index=True, right_index=True, how='outer') df_bridge = df_bridge.dropna() # 目的変数のみ削除して変数Xに格納 X_bridge = df_bridge.drop("GDP_CYOY", axis=1) # 目的変数のみ抽出して変数Yに格納 Y_bridge = df_bridge["GDP_CYOY"] model_bridge = LinearRegression() model_bridge.fit(X_bridge, Y_bridge) # パラメータ算出 # 回帰係数 reg_bridge_a_0 = model_bridge.coef_[0] reg_bridge_a_1 = model_bridge.coef_[1] reg_bridge_a_2 = model_bridge.coef_[2] # 切片 reg_bridge_b = model_bridge.intercept_ df_bridge['NOWCAST'] = df_bridge.apply( lambda x: reg_bridge_b + reg_bridge_a_0*x['BRIDGE_IIP_YOY'] + reg_bridge_a_1*x['BRIDGE_ITA_YOY'] + reg_bridge_a_2*x['BRIDGE_FACTOR'], axis=1) df_bridge_new = df_bridge.copy() df_bridge_new = df_bridge_new.drop('BRIDGE_IIP_YOY', axis=1) df_bridge_new = df_bridge_new.drop('BRIDGE_ITA_YOY', axis=1) df_bridge_new = df_bridge_new.drop('BRIDGE_FACTOR', axis=1) スパース主成分分析 from sklearn.linear_model import LassoLarsIC, LinearRegression import numpy as np import pandas as pd # sklearnの標準化モジュールをインポート from sklearn.preprocessing import StandardScaler from sklearn.decomposition import SparsePCA, PCA import matplotlib.pyplot as plt gdp = pd.read_csv('import/GDP_JAPAN.csv', parse_dates=['DATE'], index_col='DATE') iip = pd.read_csv('import/IIP.csv', parse_dates=['DATE'], index_col='DATE') ita = pd.read_csv('import/ITA.csv', parse_dates=['DATE'], index_col='DATE') gdp.index = pd.to_datetime(gdp.index, format='%m/%d/%Y').strftime('%Y-%m-01') iip.index = pd.to_datetime(iip.index, format='%m/%d/%Y').strftime('%Y-%m-01') ita.index = pd.to_datetime(ita.index, format='%m/%d/%Y').strftime('%Y-%m-01') dfX = pd.concat([iip['IIP_YOY'], ita['ITA_YOY']], axis=1) # データを変換する計算式を生成 sc = StandardScaler() sc.fit(dfX) # 実際にデータを変換 z = sc.transform(dfX) dfX_std = pd.DataFrame(z, columns=dfX.columns) # スパース主成分分析 transformer = SparsePCA(n_components=1, random_state=0) transformer.fit(z) X_transformed = transformer.transform(z) # 前処理 dfX_factor = pd.DataFrame(X_transformed, columns=['FACTOR']) dfX_factor.index = iip.index dfX_std.index = iip.index df_std_factor = pd.merge(dfX_factor, dfX_std, left_index=True, right_index=True, how='outer') dfX_std_factor = pd.merge(df_std_factor, gdp, left_index=True, right_index=True, how='outer') dfX_std_factor = dfX_std_factor[['GDP_CYOY', 'IIP_YOY', 'ITA_YOY', 'FACTOR']] dfX_std_factor = dfX_std_factor[dfX_std_factor.index != '2013-01-01'] dfX_std_factor['PERIOD'] = pd.to_datetime(dfX_std_factor.index.to_series()).apply( lambda x: 3 if x.month in [1, 4, 7, 10] else (1 if x.month in [2, 5, 8, 11] else 2)) # bridge_factor bridge_factor = pd.DataFrame( columns=['BRIDGE_IIP_YOY', 'BRIDGE_ITA_YOY', 'BRIDGE_FACTOR']) x = np.array([]) y = np.array([]) z = np.array([]) flag = False for date, IIP_YOY, ITA_YOY, FACTOR in zip(dfX_std_factor.index, dfX_std_factor['IIP_YOY'], dfX_std_factor['ITA_YOY'], dfX_std_factor['FACTOR']): x = np.append(x, IIP_YOY) y = np.append(y, ITA_YOY) z = np.append(z, FACTOR) if flag == False: if date == '2013-07-01': flag = True if flag: x3t = x[-1] x3tm1 = x[-2] x3tm2 = x[-3] x3tm3 = x[-4] x3tm4 = x[-5] xt = (x3t + 2*x3tm1 + 3*x3tm2 + 2*x3tm3 + x3tm4)/3 y3t = y[-1] y3tm1 = y[-2] y3tm2 = y[-3] y3tm3 = y[-4] y3tm4 = y[-5] yt = (y3t + 2*y3tm1 + 3*y3tm2 + 2*y3tm3 + y3tm4)/3 z3t = z[-1] z3tm1 = z[-2] z3tm2 = z[-3] z3tm3 = z[-4] z3tm4 = z[-5] zt = (z3t + 2*z3tm1 + 3*z3tm2 + 2*z3tm3 + z3tm4)/3 record = pd.Series([xt, yt, zt], index=bridge_factor.columns, name=date) bridge_factor = bridge_factor.append(record) bridge_factor.index.name = 'DATE' df_bridge = pd.merge(gdp, bridge_factor, left_index=True, right_index=True, how='outer') df_bridge = df_bridge.dropna() # 目的変数のみ削除して変数Xに格納 X_bridge = df_bridge.drop("GDP_CYOY", axis=1) # 目的変数のみ抽出して変数Yに格納 Y_bridge = df_bridge["GDP_CYOY"] model_bridge = LinearRegression() model_bridge.fit(X_bridge, Y_bridge) # パラメータ算出 # 回帰係数 reg_bridge_a_0 = model_bridge.coef_[0] reg_bridge_a_1 = model_bridge.coef_[1] reg_bridge_a_2 = model_bridge.coef_[2] # 切片 reg_bridge_b = model_bridge.intercept_ df_bridge['NOWCAST'] = df_bridge.apply( lambda x: reg_bridge_b + reg_bridge_a_0*x['BRIDGE_IIP_YOY'] + reg_bridge_a_1*x['BRIDGE_ITA_YOY'] + reg_bridge_a_2*x['BRIDGE_FACTOR'], axis=1) df_bridge_new = df_bridge.copy() df_bridge_new = df_bridge_new.drop('BRIDGE_IIP_YOY', axis=1) df_bridge_new = df_bridge_new.drop('BRIDGE_ITA_YOY', axis=1) df_bridge_new = df_bridge_new.drop('BRIDGE_FACTOR', axis=1) FactorMIDAS 主成分分析 from sklearn.linear_model import LassoLarsIC, LinearRegression import numpy as np import pandas as pd # sklearnの標準化モジュールをインポート from sklearn.preprocessing import StandardScaler from sklearn.decomposition import SparsePCA, PCA import matplotlib.pyplot as plt gdp = pd.read_csv('import/GDP_JAPAN.csv', parse_dates=['DATE'], index_col='DATE') iip = pd.read_csv('import/IIP.csv', parse_dates=['DATE'], index_col='DATE') ita = pd.read_csv('import/ITA.csv', parse_dates=['DATE'], index_col='DATE') gdp.index = pd.to_datetime(gdp.index, format='%m/%d/%Y').strftime('%Y-%m-01') iip.index = pd.to_datetime(iip.index, format='%m/%d/%Y').strftime('%Y-%m-01') ita.index = pd.to_datetime(ita.index, format='%m/%d/%Y').strftime('%Y-%m-01') dfX = pd.concat([iip['IIP_YOY'], ita['ITA_YOY']], axis=1) # データを変換する計算式を生成 sc = StandardScaler() sc.fit(dfX) # 実際にデータを変換 z = sc.transform(dfX) dfX_std = pd.DataFrame(z, columns=dfX.columns) # 主成分分析 transformer = PCA(n_components=1, random_state=0) transformer.fit(z) X_transformed = transformer.transform(z) # 前処理 dfX_factor = pd.DataFrame(X_transformed, columns=['FACTOR']) dfX_factor.index = iip.index dfX_std.index = iip.index df_std_factor = pd.merge(dfX_factor, dfX_std, left_index=True, right_index=True, how='outer') dfX_std_factor = pd.merge(df_std_factor, gdp, left_index=True, right_index=True, how='outer') dfX_std_factor = dfX_std_factor[['GDP_CYOY', 'IIP_YOY', 'ITA_YOY', 'FACTOR']] dfX_std_factor = dfX_std_factor[dfX_std_factor.index != '2013-01-01'] dfX_std_factor['PERIOD'] = pd.to_datetime(dfX_std_factor.index.to_series()).apply( lambda x: 3 if x.month in [1, 4, 7, 10] else (1 if x.month in [2, 5, 8, 11] else 2)) # factor_MIDAS df_factor = pd.DataFrame(columns=[ 'GDP_CYOY', 'IIP_YOY_Q1', 'IIP_YOY_Q2', 'IIP_YOY_Q3', 'ITA_YOY_Q1', 'ITA_YOY_Q2', 'ITA_YOY_Q3', 'FACTOR_Q1', 'FACTOR_Q2', 'FACTOR_Q3']) for date, GDP_CYOY, IIP_YOY, ITA_YOY, FACTOR, PERIOD in zip(dfX_std_factor.index, dfX_std_factor.GDP_CYOY, dfX_std_factor.IIP_YOY, dfX_std_factor.ITA_YOY, dfX_std_factor.FACTOR, dfX_std_factor.PERIOD): if PERIOD == 1: q1_iip = IIP_YOY q1_ita = ITA_YOY q1_factor = FACTOR elif PERIOD == 2: q2_iip = IIP_YOY q2_ita = ITA_YOY q2_factor = FACTOR else: record = pd.Series([GDP_CYOY, q1_iip, q2_iip, IIP_YOY, q1_ita, q2_ita, ITA_YOY, q1_factor, q2_factor, FACTOR], index=df_factor.columns, name=date) df_factor = df_factor.append(record) df_factor.index.name = 'DATE' # 目的変数のみ削除して変数Xに格納 X_factor = df_factor.drop("GDP_CYOY", axis=1) # 目的変数のみ抽出して変数Yに格納 Y_factor = df_factor["GDP_CYOY"] model_factor = LinearRegression() model_factor.fit(X_factor, Y_factor) # パラメータ算出 # 回帰係数 reg_factor_a_0 = model_factor.coef_[0] reg_factor_a_1 = model_factor.coef_[1] reg_factor_a_2 = model_factor.coef_[2] reg_factor_a_3 = model_factor.coef_[3] reg_factor_a_4 = model_factor.coef_[4] reg_factor_a_5 = model_factor.coef_[5] reg_factor_a_6 = model_factor.coef_[6] reg_factor_a_7 = model_factor.coef_[7] reg_factor_a_8 = model_factor.coef_[8] # 切片 reg_factor_b = model_factor.intercept_ df_factor['NOWCAST'] = df_factor.apply(lambda x: reg_factor_b + reg_factor_a_0*x['IIP_YOY_Q1'] + reg_factor_a_1*x['IIP_YOY_Q2'] + reg_factor_a_2*x['IIP_YOY_Q3'] + reg_factor_a_3 * x['ITA_YOY_Q1'] + reg_factor_a_4*x['ITA_YOY_Q2'] + reg_factor_a_5*x['ITA_YOY_Q3'] + reg_factor_a_6*x['FACTOR_Q1'] + reg_factor_a_7*x['FACTOR_Q2'] + reg_factor_a_8*x['FACTOR_Q3'], axis=1) df_factor_new = df_factor.copy() df_factor_new = df_factor_new.drop('IIP_YOY_Q1', axis=1) df_factor_new = df_factor_new.drop('IIP_YOY_Q2', axis=1) df_factor_new = df_factor_new.drop('IIP_YOY_Q3', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q1', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q2', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q3', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q1', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q2', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q3', axis=1) スパース主成分分析 from sklearn.linear_model import LassoLarsIC, LinearRegression import numpy as np import pandas as pd # sklearnの標準化モジュールをインポート from sklearn.preprocessing import StandardScaler from sklearn.decomposition import SparsePCA, PCA import matplotlib.pyplot as plt gdp = pd.read_csv('import/GDP_JAPAN.csv', parse_dates=['DATE'], index_col='DATE') iip = pd.read_csv('import/IIP.csv', parse_dates=['DATE'], index_col='DATE') ita = pd.read_csv('import/ITA.csv', parse_dates=['DATE'], index_col='DATE') gdp.index = pd.to_datetime(gdp.index, format='%m/%d/%Y').strftime('%Y-%m-01') iip.index = pd.to_datetime(iip.index, format='%m/%d/%Y').strftime('%Y-%m-01') ita.index = pd.to_datetime(ita.index, format='%m/%d/%Y').strftime('%Y-%m-01') dfX = pd.concat([iip['IIP_YOY'], ita['ITA_YOY']], axis=1) # データを変換する計算式を生成 sc = StandardScaler() sc.fit(dfX) # 実際にデータを変換 z = sc.transform(dfX) dfX_std = pd.DataFrame(z, columns=dfX.columns) # スパース主成分分析 transformer = SparsePCA(n_components=1, random_state=0) transformer.fit(z) X_transformed = transformer.transform(z) # 前処理 dfX_factor = pd.DataFrame(X_transformed, columns=['FACTOR']) dfX_factor.index = iip.index dfX_std.index = iip.index df_std_factor = pd.merge(dfX_factor, dfX_std, left_index=True, right_index=True, how='outer') dfX_std_factor = pd.merge(df_std_factor, gdp, left_index=True, right_index=True, how='outer') dfX_std_factor = dfX_std_factor[['GDP_CYOY', 'IIP_YOY', 'ITA_YOY', 'FACTOR']] dfX_std_factor = dfX_std_factor[dfX_std_factor.index != '2013-01-01'] dfX_std_factor['PERIOD'] = pd.to_datetime(dfX_std_factor.index.to_series()).apply( lambda x: 3 if x.month in [1, 4, 7, 10] else (1 if x.month in [2, 5, 8, 11] else 2)) # factor_MIDAS df_factor = pd.DataFrame(columns=[ 'GDP_CYOY', 'IIP_YOY_Q1', 'IIP_YOY_Q2', 'IIP_YOY_Q3', 'ITA_YOY_Q1', 'ITA_YOY_Q2', 'ITA_YOY_Q3', 'FACTOR_Q1', 'FACTOR_Q2', 'FACTOR_Q3']) for date, GDP_CYOY, IIP_YOY, ITA_YOY, FACTOR, PERIOD in zip(dfX_std_factor.index, dfX_std_factor.GDP_CYOY, dfX_std_factor.IIP_YOY, dfX_std_factor.ITA_YOY, dfX_std_factor.FACTOR, dfX_std_factor.PERIOD): if PERIOD == 1: q1_iip = IIP_YOY q1_ita = ITA_YOY q1_factor = FACTOR elif PERIOD == 2: q2_iip = IIP_YOY q2_ita = ITA_YOY q2_factor = FACTOR else: record = pd.Series([GDP_CYOY, q1_iip, q2_iip, IIP_YOY, q1_ita, q2_ita, ITA_YOY, q1_factor, q2_factor, FACTOR], index=df_factor.columns, name=date) df_factor = df_factor.append(record) df_factor.index.name = 'DATE' # 目的変数のみ削除して変数Xに格納 X_factor = df_factor.drop("GDP_CYOY", axis=1) # 目的変数のみ抽出して変数Yに格納 Y_factor = df_factor["GDP_CYOY"] model_factor = LinearRegression() model_factor.fit(X_factor, Y_factor) # パラメータ算出 # 回帰係数 reg_factor_a_0 = model_factor.coef_[0] reg_factor_a_1 = model_factor.coef_[1] reg_factor_a_2 = model_factor.coef_[2] reg_factor_a_3 = model_factor.coef_[3] reg_factor_a_4 = model_factor.coef_[4] reg_factor_a_5 = model_factor.coef_[5] reg_factor_a_6 = model_factor.coef_[6] reg_factor_a_7 = model_factor.coef_[7] reg_factor_a_8 = model_factor.coef_[8] # 切片 reg_factor_b = model_factor.intercept_ df_factor['NOWCAST'] = df_factor.apply(lambda x: reg_factor_b + reg_factor_a_0*x['IIP_YOY_Q1'] + reg_factor_a_1*x['IIP_YOY_Q2'] + reg_factor_a_2*x['IIP_YOY_Q3'] + reg_factor_a_3 * x['ITA_YOY_Q1'] + reg_factor_a_4*x['ITA_YOY_Q2'] + reg_factor_a_5*x['ITA_YOY_Q3'] + reg_factor_a_6*x['FACTOR_Q1'] + reg_factor_a_7*x['FACTOR_Q2'] + reg_factor_a_8*x['FACTOR_Q3'], axis=1) df_factor_new = df_factor.copy() df_factor_new = df_factor_new.drop('IIP_YOY_Q1', axis=1) df_factor_new = df_factor_new.drop('IIP_YOY_Q2', axis=1) df_factor_new = df_factor_new.drop('IIP_YOY_Q3', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q1', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q2', axis=1) df_factor_new = df_factor_new.drop('ITA_YOY_Q3', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q1', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q2', axis=1) df_factor_new = df_factor_new.drop('FACTOR_Q3', axis=1) 参考文献 [1] [2] [3] [4]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リストの追加方法を学ぼう (今日のPython Day2)

0. はじめに  2日目ですね。記事を書いている当人が3日坊主にならないか心配しています。一緒に頑張っていきましょう。   毎日1問、Pythonの問題を出題します。出題範囲は特に定めていませんがはじめの1ヶ月くらいは『入門Python3 第2版』の第1~11章までのことが分かれば解ける問題にしたいと思います。 1. 問題 リストcに仲間はずれにされているNobitaを追加しましょう。 c = ["Gian", "Suneo"] 2. 解答 c = ["Gian", "Suneo"] c.append("Nobita") print(c) 別解 c = ["Gian", "Suneo"] c = c + ["Nobita"] print(c) 「Python リスト 追加」と検索するとまずappendが出たのでこれを解答にしました。自分もよくこれを使います。実は他にも方法があって単純に「リスト+リスト」でこっちの出来ます。こっちの方が簡単だと思うのですが、なぜかよく忘れます。ちなみに別解の2行目は代入演算子「+=」を用いた方がより良いですね。 c = ["Gian", "Suneo"] c += ["Nobita"] print(c) 4. おまけトーク  ジャイアンを英語で表記するとGianになるんですね。そのままZyaianかと思いました。あともっとどうでも良いのですが、アメリカで放送されているドラえもんではジャイアンはBig Gだそうです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

kivyMDチュートリアル其の什肆 Components - Layout篇

みなさん、おはこんばんは。久しぶりの挨拶をしたところで、いかがお過ごしでしょうか。 いやー、GW始まりましたね!緊急事態宣言が出て、出かけることも出来ずフラストレーションが 溜まりそうになりますが、もう少しの辛抱だと思ってお家時間を過ごそうではありませんか(誰が 誰に言ってるんだ)。 今日からはGW特別企画ということでタイトルの通り、今日はKivyMDの数あるレイアウトの総集編 となります。(すっごく余談ですが、GW特別企画は第2~3弾ほど考えています) レイアウトはマニュアルページでどれだけあるんだろうと数えてみたところ、以下の7つありました。 Box Layout Circular Layout Float Layout Grid Layout Refresh Layout Relative Layout Stack Layout 本当なら、全て触れ込みたいところですが以下の2つを今回は対象外とすることにします。 Circular Layout サンプルコードが動かないというか実装されているか断定できなかったため Refresh Layout 触れ込みの対象範囲が大きすぎるため 理由としては上記の通りですが、Circular Layoutについては別モジュールのことも考えて importなど試してみましたが、見事に動いてはくれませんでした。今後の期待ということに なりますかね。Refresh Layoutについては少し触れ込みの範囲が大きすぎました。これだけ で1日を費やすことになるので、また機会があれば別のときにでも。 ※ 1日を費やすといってもLayoutの総集編で2~3日ほど費やしています ということで残った5つを触れ込むことにします。 分かりやすくシンプルにということで見た目が少し悪いところはご容赦ください。 また、キャプチャが旅行でも行ったの?というくらい出るので容量制限などあれば、省略するかも なのでこれまたご容赦を。 Box Layout ようやく、触れ込めます。 マニュアルを見ると、KivyとKivyMDでのそれぞれの書き方という風に並べられていますね。 # 後でまとめて「参照」にリンクを載せておきますので適宜そちらから参照ください 端的に言うと、Kivyではこれだけの量がKivyMDだとこれだけ短縮されるんだぜということ になりますかね。お前は何を言ってるんだという状況になりましたら、以下リンクからもしくは 参照リンクを辿ってみると「あーこういうことね」と分かるかもです。それ以外の方は懐かしく 思えるかもしれません。 kivyMDチュートリアル其の壱 About&Getting Started篇 [参照リンク] 使用できるオプションとして以下の3つがあるよということを案内されています。 adaptive_height -> "size_hint_y: None"       +   "height: self.minimum_height" adaptive_width -> "size_hint_x: None"       +   "height: self.minimum_width" adaptive_size -> "size_hint: None, None"       +   "size: self.minimum_size" 若干、互換性のところが見にくいかもしれませんが言ってることは以下になります。 KivyMDでのオプション -> Kivyでのオプション adaptive_widthなんかはKivyだとwidthオプションじゃね?みたいなツッコミをして しまいそうですが、これも後述するので一旦放っておきましょう。 少なく書けるということは嬉しいことなのですが、具体的にsize_hint_yだとかheightは どのようなものなのでしょうかね(heightとかは分かるよ!と言われそうだけど)。これまでも この辺りはKivyに触れてないので当たり前ですが、触れてこなかったのは事実。 となれば実際に見た方が早いよと言われそうなので実際にコードと実行結果を折り交えながら 見ていきましょうか。以下のサンプルコードをご覧ください。マニュアルには載ってないので 専用に作ったコードになります。 xiv/boxlayout.py from kivymd.app import MDApp from kivy.lang import Builder kv = ''' Screen: MDToolbar: title: "Box Layout" pos_hint: {'top': 1} BoxLayout: orientation: "vertical" ### adaptive_height ### size_hint_y: None height: self.minimum_height ### adaptive_width ### #size_hint_x: None #height: self.minimum_width ### adaptive_size ### #size_hint: None, None #size: self.minimum_size padding: dp(10) spacing: dp(10) canvas: Color: rgba: 0, 1, 1, 1 Rectangle: pos: self.pos size: self.size MDRectangleFlatButton: text: "BUTTON 1" size: 20, 20 MDRectangleFlatButton: text: "BUTTON 2" size: 30, 30 MDRectangleFlatButton: text: "BUTTON 3" size: 40, 40 MDRectangleFlatButton: text: "BUTTON 4" size: 50, 50 MDRectangleFlatButton: text: "BUTTON 5" size: 60, 60 ''' class Test(MDApp): def build(self): return Builder.load_string(kv) Test().run() コード自体は特に目新しいものはないので、詳細に触れ込みはしないのですがBoxLayoutが MDBoxLayoutではないことに注意してください。まずはKivyとしてどのような振る舞いを するのか見てみましょう。 簡単な説明としては、BoxLayoutの配下に縦積みされるボタンを5つ配置するようになって います。少し見た目を良くするためにボタン間に適当な余白などを入れています。あとは adaptive_height/width/sizeなどがどのように変わってくるのかということを見る ために、コメントアウトにてそれぞれを選ぶように記述しました。その具合を見るために canvas.Color.rbgaオプションをテーマカラーではなく、緑と青を組み合わせた色に 変更しています。 # テーマカラーは違いが分かりにくいためです まぁ、これは見たらこういうことかと分かるはずなので早速みてみましょう。 adaptive_height 少し画像のサイズが大きいですが。。 これも理由がありまして、スマホサイズに合わせるとなんか分かりにくくなるためです。 ちなみにkivyでの画面サイズ=800x600から変更せずにお送りいたしております。 全然中身に触れこめませんでしたが、やっと触れ込みます。 キャプチャを見てもらうと、分かりやすいですが絶妙に色分けがされていますね。 どうやら、ボタンなどのウィジェットのサイズと余白のサイズの累積がcanvas.Rectangleの サイズとなるようです。これをもとに、size_hint(_x/y)の定義に触れますが、簡単に言うと サイズのヒントになります。説明になってなく、誰かの構文を思い起こさせるかもですがこれは しょうがありません。なぜなら、Kivyの公式マニュアルでもそう書かれているためなので。。。 [Widget class] https://kivy.org/doc/stable/api-kivy.uix.widget.html とまぁ、これだけだともの悲しいので少し触れ込みます。と思いましたが以下リンクを参照 ください。なんか触れ込むとBoxLayoutの詳細な触れ込みになりそうな気がしたことと、 すでにある有益な情報は再掲したほうがいいためです。せなさん、いつもお世話になって います。そしてありがとうございます。 Kivyのボックスレイアウトの使い方とその仕様 https://senablog.com/python-kivy-boxlayout/ 少し補足すると親ウィジェットの割合に対する比率ということも重要な点になります。あとは、 検索するとsize_hint_xとかの値が1以上に設定されているものを見かけますが、公式マニュ アルとかは0~1の値を想定されているらしいです。なので、使用する場合は全体の比率を1と しましょう。 とまぁ、触れ込みは以上となりますがsize_hint_yがNoneなので配下のウィジェットサイズに 合わせ、heightプロパティのself.minimum_heightと合わせると配下のウィジェットのサイズ の累積が領域として確保されます。よく分からんとなった方は、色々試してみると分かりやすいかも しれません。 adaptive_width これからは説明も淡々とこなしていきます。というか全てやっているといつまで経っても 終わらん。。 すごく、見かけが悪いですが上記の通りとなります。なぜかMDRectangleFlatButtonのwidthが 上手く反映されていなく、adaptive_heightと違う気もしますがこれはこれでということで。全て の幅なり高さなりが合っていると同じ領域が確保されるのかな。 adaptive_size 最後になると、確保される領域も最小限となります。見事に小ちゃくなってますね。 また、MDBoxLayoutでも上記と同じになるか見てみます。 コードとしてはマニュアルに書いてある通り、以下のように変更するだけになります。 xiv/boxlayout_md.py (略) - BoxLayout: + MDBoxLayout: - ### adaptive_size ### - #size_hint: None, None - #size: self.minimum_size + adaptive_size: True (略) うん、変わりませんね。 # エビデンスの残し方としてはあまり良くないですが それでも、ほんとにそうなるの?と疑り深い方はコピペして試されるか、自身のGitHubの リポジトリからpull or cloneしてみて試してみてください。 GitHubのリンクは以下のリンク(最下部)から # ややこしい kivyMDチュートリアル其の伍 Themes - Icon Definitions篇 Float Layout ここも淡々と触れ込んでいきます。 こちらは絶対座標系の下、サイズの位置を自由に設定してウィジェットを配置できるものです。 BoxLayoutと異なり、adaptive_*というプロパティはないことに注意です。 なんかそれらしいことを言っていますが、説明についてはKivyの書籍から引用しています。他の レイアウトでもそうですが、この辺りが良くまとまっているのでマニュアル以外何かみれね?と いう方はこちらを参照してもらうのがおすすめです。 コードの方も全ては載せません。kv側を一部抜粋します。詳しくはGitHubの方で。 xiv/floatlayout.py Screen: MDToolbar: title: "Float Layout" pos_hint: {'top': 1} FloatLayout: canvas: Color: rgba: 0, 1, 1, 1 RoundedRectangle: pos: self.pos size: self.size radius: [25, 0, 0, 0] MDRectangleFlatButton: text: "BUTTON 1" pos: 10, 460 MDRectangleFlatButton: text: "BUTTON 2" pos: 690, 460 MDRectangleFlatButton: text: "BUTTON 3" pos: 360, 240 MDRectangleFlatButton: text: "BUTTON 4" pos: 10, 10 MDRectangleFlatButton: text: "BUTTON 5" pos: 690, 10 特に大きな変更点はありませんが、ボタンの配置方法が変更しています。 それぞれ座標(x, y)となるようにposプロパティを書いています。ボタンの上から、左上より 右下というようにZと文字が書けるような順番としています。まぁ、これも見てもらった方が 良さそうですね。 おおっと、失礼。canvas.RoundedRectangleが画面全体を覆うようになりましたね。 現状これが分かったことで、覆うことはいらないのでcanvasプロパティごとコメントアウトしちゃ います。 これで綺麗になりました。であとはMDFloatLayoutでも同様かどうか見てみます。 コードの方は、、、いいですかね。。まぁ、GitHub見てもらえれば良いので。 やったことだけ書くと、これも言わずもがなですが接頭辞にMDを付けただけです。 うーん、差分はなさそう(確かめてはない)。 こちらは少し注意が必要で、画面サイズがバラバラだったりすると以下のようにレイアウトが 崩れる恐れがあります。この点については、実機で試さないことにはなんとも言い難い点があり ますが。。自動で計算してくれるのかな、この辺りは。 Grid Layout さくさく進んでいきます。 こちらは格子状に配置させるレイアウトになりますね。例えていうなら、エクセルシートのセルと 言えますでしょうか。あまり例としては使いたくなかったですけどね。。 まぁ、あのような感じです。こちらはadaptive_*プロパティがあるのでご注意を。 これも見てもらったほうがいいのでコードと併せて。 kv以外は一致しているので、kvで定義しているところだけ抜粋します。 xiv/gridlayout.py Screen: MDToolbar: title: "Grid Layout" pos_hint: {'top': 1} GridLayout: cols: 2 ### adaptive_height ### size_hint_y: None height: self.minimum_height ### adaptive_width ### #size_hint_x: None #height: self.minimum_width ### adaptive_size ### #size_hint: None, None #size: self.minimum_size canvas: Color: rgba: 0, 1, 1, 1 Rectangle: pos: self.pos size: self.size MDRectangleFlatButton: text: "BUTTON 1" MDRectangleFlatButton: text: "BUTTON 2" MDRectangleFlatButton: text: "BUTTON 3" MDRectangleFlatButton: text: "BUTTON 4" 変更点はボタンを見やすくするために4つ配置したことと、GridLayoutが持っているプロパティ colsを使っているくらいになります。rowsという行を表すプロパティもあります。これもどう なるか見てみましょう。 adaptive_height もう、言わずもがな感は出てきていますかね。 えぇ、出ていない?もう一度、BoxLayoutの方に戻ってみましょうw そこからの差分としては、ちゃんと高さの累積にはなっているようです。 adaptive_width なんか、意味不明なところがありますがこちらも領域については同じです。幅に応じて領域が 確保されています。なんで、上に配置されるのだろう。。 adaptive_size はい、こちらも言わずもがなです。 こちらについても、MDGridLayoutと同じになるか見てみましょう。 まぁ、確かに証明になっていない感はどうしてもあるのですが全て動作確認はしていますので。 詳しくはGitHubのリポジトリの方にて。 Relative Layout こちらはFloatLayoutと似ていて、相対座標で配置させるものとなります。引用した本からは FloatLayoutのサブクラスとしても紹介されています。 こちらもコードと併せてどうなるか見てみましょう。 xiv/relativelayout.py Screen: MDToolbar: title: "Relative Layout" pos_hint: {'top': 1} RelativeLayout: canvas: Color: rgba: 0, 1, 1, 1 RoundedRectangle: pos: (400, 300) size: self.size radius: [25, ] MDRectangleFlatButton: text: "BUTTON 1" pos: 10, 200 MDRectangleFlatButton: text: "BUTTON 2" pos: 300, 200 MDRectangleFlatButton: text: "BUTTON 3" pos: 10, 10 MDRectangleFlatButton: text: "BUTTON 4" pos: 300, 10 FloatLayout同様、というか当たり前ですがadaptive_*プロパティはありません。 RoundedRectangleの基準点(四角形左下の点)を(400, 300)としてそこから相対座標 を設定してそれぞれのウィジェットを配置させることを目的としています。それぞれの ウィジェットは画面右上あたりに表示されるはずです。 ではどうなるか見てみましょう。 あれーww出来てないぞーwww うーん、これは困った。。MD側のボタンの仕様なのか、それともLayout側の仕様なのか 分かりません。ただし、領域については狙い通りとなってそうです。 追加調査でsize_hintで両方ともNone指定をしましたが、結果としては変わらず。 このことについては、継続調査が必要そうですが今は時間切れということでタイムアップ。。 時間がある際、もしくは今後のサンプルコードでこちらが出た場合に改めてということで、 許してください。。 あ、ちなみにですがMDRelativeLayoutに変更して実行してみると動作できませんでした。 今後に期待ということも言えますが、実装自体必要かなとか思えてしまいます。すでにMDFloat- Layoutはあるわけなのですが。 Stack Layout 最後になります。こちらは上下左右からそれぞれのウィジェットを積み上げるレイアウトと なります。マニュアル(KivyMD)からはBoxLayoutなどと変わりなく書かれていますが、 プロパティなどが少し変わります。BoxLayoutはorientationプロパティがverticalか horizontalの2つが選択できますが、StackLayoutは'lr-tb'を筆頭に左右どちらから 始めるかという選択もできるようになります。 # 詳しくは後ほど参照する公式マニュアルを参照ください。 こちらについてはadaptive_*プロパティが存在します。 コードについては一部だけ載せておきます。 xiv/stacklayout.py StackLayout: orientation: "lr-tb" はい、本当にこれだけになります。詳しくはGitHubリポジトリの方で(何回言うんだって話)。 ボタンの仕様に関しては、BoxLayoutの方と合わせています。 adaptive_height こんな感じになりました。 "lr-tb"となるので左上から右に配置させることになります。高さを一定化させるのでこのように なります。ペチャーっとなっていますね。 adaptive_width 続いてはこちら。 こちらも上側に吸い寄せられています。まぁ、左上から右で横幅が一定なので合ってはいるん ですけどね。でもなぜ上側に吊し上げられるのか。 adaptive_size こちらはこうなりました。adaptive_widthとどこでどう変わったんだああぁぁぁ!と叫びたい 気持ちを一旦押し殺して、観察してみます。いや、本当に何が変わった...?結果からは何ももの 申すことは出来ません。 では、本当にMDStackLayoutと同様になるかどうか見てみます。 こちらはMDを接頭辞に付けただけになります。 はい、一致しました。異論は認めません。 最後にMDStackLayoutで書いたものを"bt-rl"とするとどうなるか見てみます。 このように右下に配置することもできます。 StackLayoutの注意点としてはKivy1.5.0からorientationの変更があったりすることと、 adaptive_*との組み合わせによって結果が結構異なったりすることになります。 まとめ いや、疲れたあぁぁッッ!!!! すみません、心の声が出ちゃいました。 なんせ、記載量少ないし一気にやっちゃえ○産ということで気軽に考えてましたが Kivyの仕様をあまり把握してないので、時間が大幅に掛かりました。。でも良いの です。工数としては5→1と約1ヶ月ほど短縮されました。これについてはあっぱれ。 いかかでしたかね。触れ込みというか備忘録に近い内容となってしまいましたが、 参考になりましたかね。ならなかったという方はバッドボタンが実装されましたら 容赦なく押してもらえれば。あるのかな、Qiitaにそんな実装が。 まぁ、あんまり良く分からんという方がいましたら、下記の参照でKivyの公式 マニュアルも参照してもらえればと思います。本当ならこの辺もしっかり触れ込み たいところなのですがね。 ということで、長く長くなってきましたので今日はこの辺にて〜。 次回のGW特別第2企画はButton篇となります。お楽しみに〜。 それでは、ごきげんよう。 参照 KivyMD Components » Box Layout https://kivymd.readthedocs.io/en/latest/components/box-layout/ Components » Float Layout https://kivymd.readthedocs.io/en/latest/components/float-layout/ Components » Grid Layout https://kivymd.readthedocs.io/en/latest/components/grid-layout/ Components » Relative Layout https://kivymd.readthedocs.io/en/latest/components/relative-layout/ Components » Stack Layout https://kivymd.readthedocs.io/en/latest/components/stacklayout/ Kivy Programming Guide(翻訳済み) » Widgets(翻訳済み) https://pyky.github.io/kivy-doc-ja/guide/widgets.html Box Layout https://kivy.org/doc/stable/api-kivy.uix.boxlayout.html Float Layout(翻訳済み) https://pyky.github.io/kivy-doc-ja/api-kivy.uix.floatlayout.html Grid Layout https://kivy.org/doc/stable/api-kivy.uix.gridlayout.html Relative Layout https://kivy.org/doc/stable/api-kivy.uix.relativelayout.html Stack Layout https://kivy.org/doc/stable/api-kivy.uix.stacklayout.html ※ 翻訳済みに関してはver1.10~と少し情報が古い可能性があります
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ダイクストラの枝刈り高速化まとめ【python実装】

はじめに 先日のZONeコンでダイクストラの定数倍高速化が大事な問題が出たので、ダイクストラ典型問題である、ABC192Eの実装をもとに手法をまとめる。 初期実装 dijkstrasimple.py def divceil(n, k): return 1+(n-1)//k # n/kの切り上げを返す def main(): n, m, x, y = map(int, input().split()) graph = [[] for _ in range(n)] for i in range(m): s, t, tm, km = map(int, input().split()) s -= 1 t -= 1 graph[s].append((t, tm, km)) graph[t].append((s, tm, km)) mindist = [-1] * n hq = [] heappush(hq, (0, x-1)) while hq: dist, node = heappop(hq) if mindist[node] != -1: continue mindist[node] = dist for i, t, k in graph[node]: if mindist[i] != -1: continue heappush(hq, (divceil(dist, k)*k+t, i)) print(mindist[y-1]) これは最もシンプルなダイクストラの実装。mindistをhheapqから取り出した順に確定していき、mindistが確定していない頂点をheapqに突っ込んでいる。 枝刈り高速化⓵ heapqからゴールを取り出した時点でbreak dijkstraeda1.py def divceil(n, k): return 1+(n-1)//k # n/kの切り上げを返す def main(): n, m, x, y = map(int, input().split()) graph = [[] for _ in range(n)] for i in range(m): s, t, tm, km = map(int, input().split()) s -= 1 t -= 1 graph[s].append((t, tm, km)) graph[t].append((s, tm, km)) mindist = [-1] * n hq = [] heappush(hq, (0, x-1)) while hq: dist, node = heappop(hq) if node==y-1: print(dist) return if mindist[node] != -1: continue mindist[node] = dist for i, t, k in graph[node]: if mindist[i] != -1: continue heappush(hq, (divceil(dist, k)*k+t, i)) print(-1) 最も簡単なダイクストラの枝刈りは、heapqが空になるのを待たずにheapqから取り出されて確定した頂点がゴールの時点で結果を出力して終了すること。これは、ダイクストラが使える単一始点最短経路問題の中でも、終点も単一の場合に使用することが出来る。 枝刈り高速化②heapqに入れる値を制限 dijkstraeda2.py def divceil(n, k): return 1+(n-1)//k # n/kの切り上げを返す def main(): n, m, x, y = map(int, input().split()) graph = [[] for _ in range(n)] for i in range(m): s, t, tm, km = map(int, input().split()) s -= 1 t -= 1 graph[s].append((t, tm, km)) graph[t].append((s, tm, km)) mindist = [10**20] * n fixed=[False]*n hq = [] heappush(hq, (0, x-1)) while hq: dist, node = heappop(hq) if node==y-1: print(dist) return if fixed[node]: continue fixed[node] = True for i, t, k in graph[node]: if divceil(dist, k)*k+t >= mindist[i]: continue heappush(hq, (divceil(dist, k)*k+t, i)) mindist[i]=divceil(dist, k)*k+t print(-1) このスライドの14~15ページ目と同じ。mindistを、heapqから取り出して確定した距離ではなく、heapqに入れた暫定の最短距離とする。heapqに入れる時に、暫定の最短距離を確認して、それより長い場合heapqに入れないことで、heapqに入れる値の数を減らして、枝刈りすることができる。ただし、距離が確定したかどうかの配列を別に作るので、元から速い場合さらなる高速化にはならないこともある。 別実装 https://atcoder.jp/contests/abc192/submissions/22259820 距離が確定したかどうかの配列を作らずに、暫定最短距離と取り出した距離が同じ時に探索する手法 距離が確定したかどうかの配列を作らない分、同じ頂点を複数回探索する可能性があるので高速化するかどうかは微妙 枝刈り高速化③heapqにタプルではなく数値を入れる dijkstraeda3.py def divceil(n, k): return 1+(n-1)//k # n/kの切り上げを返す def main(): n, m, x, y = map(int, input().split()) graph = [[] for _ in range(n)] for i in range(m): s, t, tm, km = map(int, input().split()) s -= 1 t -= 1 graph[s].append((t, tm, km)) graph[t].append((s, tm, km)) mindist = [10**20] * n fixed=[False]*n hq = [] op=lambda d,i:d*n+i heappush(hq, op(0, x-1)) while hq: dist, node = divmod(heappop(hq),n) if node==y-1: print(dist) return if fixed[node]: continue fixed[node] = True for i, t, k in graph[node]: if divceil(dist, k)*k+t >= mindist[i]: continue heappush(hq, op(divceil(dist, k)*k+t, i)) mindist[i]=divceil(dist, k)*k+t print(-1) heapqに入れる時、タプルではなく数値の方が高速に動作する。そこで、2つの数値を商と余りとして1つの数値にまとめて保持しておくことで、高速化が見込める。ただしこれも、タプルから数値の計算と数値からタプルの復元が必要なので、元から速い場合さらなる高速化にはならないこともある。 タプル⇔数値相互変換ライブラリ 2値のタプルと数値の変換ならまだ書きやすいが、3値以上のタプルと数値の変換を直接書くと、コードが汚くなってしまうので、クラスとして処理できる自作ライブラリを紹介する。コンストラクタで、変換したい値のそれぞれの最大値のリストを渡すと、packingでタプルを数値に、unpackingで変換した数値をタプルに戻すことが出来る。使用例は下記ZONeコンE実践例を参照。 tuplepacking.py class tuplepacking: def __init__(self,maxsizelist): self.n=len(maxsizelist) self.maxsizelist=tuple(maxsizelist) def packing(self,*tuples): ans=0 for size,tu in zip(self.maxsizelist,tuples): ans*=size ans+=tu return ans def unpacking(self,value): ans=[0]*self.n for i in range(self.n)[::-1]: value,ans[i]=divmod(value,self.maxsizelist[i]) return ans ZONeコンEを上記枝刈り高速化を用いて愚直ダイクストラで通す こちらの問題では、下方向の複数通りの遷移を、頂点拡張でうまく1つにまとめることで計算方法を削除することが想定解だが、下方向の複数通りの遷移を愚直にループ回しても上記のような適切な枝刈り高速化で間に合わせることが出来る。 枝刈り高速化①,②使用 1728ms 枝刈り高速化①,②,③全部使用 1253ms 枝刈り高速化②,③使用 1229ms 枝刈り高速化①,③使用 1TLE まとめ ダイクストラは色々な実装があり、その違いで実行時間が大きく変わることもあるので奥が深い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCode 1000000本ノック【20. Valid Parentheses】

目次 概要 問題 解法 メイキング 概要 ●発端  ・競プロ初心者、コーディング面接でズタボロにされ、  ・最低限のアルゴリズム学習とコーディング力強化を習慣化するため記事化 ●環境  ・LeetCodeブラウザ上で実装(vscodeに拡張機能にするかもシレナイ)  ・言語はpython3 ●その他ルール  ・google検索は自由に(直接的なLeetCodeの問題解説ページはNG)   (60分実装して実装出来なければ解説ページ参照)  ・コーディング面接対策のために解きたいLeetCode 60問から問題選出  ・「1000000」は任意の2進数とする 問題 20. Valid Parentheses Given a string s containing just the characters '(', ')', '{', '}', '[' and ']', determine if the input string is valid. An input string is valid if: Open brackets must be closed by the same type of brackets. Open brackets must be closed in the correct order. ⇨英語が全く読めなくても何となく雰囲気で分かりそうだが...「渡されたString引数の  括弧が、開いた順に閉じられているか確認しろ」と書いてます。知らんけど。 念の為仕様を整理↓  ・一文字ずつ順番に確認していって...   ・対応した開括弧と閉括弧が順序通り存在すること    ※ ( { [ は連続してOKだが先に対応した ) } ] が存在すること ex1. Input: s = "()[]{}" Output: true ex2. Input: s = "([)]" Output: false 解法 実施時間:40分 特に参考記事は無し。 stackの概念があれば問題なく解けると思われますがちょっと時間かかりました。 class Solution: def isValid(self, s: str) -> bool: stack = [] brackets = {')': '(', '}': '{', ']': '['} for c in s: if c in brackets.keys(): if len(stack) == 0: return False if brackets[c] == stack[-1]: del stack[-1] else: return False if c in brackets.values(): stack.append(c) if len(stack) == 0: return True メイキング step1.コメントで仮コーディング  class Solution: def isValid(self, s: str) -> bool: #最初がopen or closeでない #最後がclose or ({[でない #(のあとは ) 間空可 #{のあとは } 間空可 #[のあとは ] 間空可 #対応括弧のMAP key OPEN : value CLOSE #文字列をcharループ #openならスタックに積む #continue #closeなら #スタックの最新openに対応したcloseか確認 #対応括弧で無いならFalse #ループ終了(全部流れきったら)でTrue 上部に実装仕様をまとめた内容をメモ。(インデントは実装の優先順位) step2.ひとまず仕様メモの下3つ(対応括弧の確認)だけ実装します。 class Solution: def isValid(self, s: str) -> bool: #最初がopen or closeでない #最後がclose or ({[でない #(のあとは ) 間空可 #{のあとは } 間空可 #[のあとは ] 間空可 #スタック stack = [] #map key CLOSE : value OPEN brackets = {')': '(', '}': '{', ']': '['} #文字列をcharループ for c in s: #closeなら if c in brackets.keys(): #スタックの最新openに対応したcloseか確認 if brackets[c] == stack[-1]: #スタックの最後を削除して次文字へ del stack[-1] #対応括弧で無いならFalse else: return False #openならスタックに積む if c in brackets.values(): stack.append(c) #ループ終了したらTrue return True ここでif openとif closeの順番を入れ替えてみる。 ※「LOOP抜ければTrue」は変えられないので可読性的に上部にFalseまとめてみた step3.残りの仕様実装(括弧が閉じられてる?閉じ括弧で始まってない?) class Solution: def isValid(self, s: str) -> bool: #最初がopen or closeでない #最後がclose or ({[でない #(のあとは ) 間空可 #{のあとは } 間空可 #[のあとは ] 間空可 #スタック stack = [] #map key CLOSE : value OPEN brackets = {')': '(', '}': '{', ']': '['} #文字列をcharループ for c in s: #closeなら if c in brackets.keys(): #open無いのにclose if len(stack) == 0: return False #スタックの最新openに対応したcloseか確認 if brackets[c] == stack[-1]: #スタックの最後を削除して次文字へ del stack[-1] #対応括弧で無いならFalse else: return False #openならスタックに積む if c in brackets.values(): stack.append(c) #ループ終了後、stack残ってなかったらTrue if len(stack) == 0: return True 後は余計なコメントを消して完了! 〜蛇足〜 記事をまとめていてelifにすべき箇所があるのに気づいたので変えてみた。 意外とパフォーマンスに影響出るもんですねぇ。 そしてテストケースは通過しているけども、「引数無しの場合」と 「引数が括弧以外」を処理し忘れているますね...精進精進...... ↓ ↓
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メーカー営業xPython(エクセルvsPython)

前段: メーカーの営業をしていると営業活動以外にもエクセルを使った事務処理が多々ある。 本投稿では常日頃エクセルで行っている作業をPythonでの置き換えに挑戦。 内容: 例:顧客より所要情報を下記フォーマットで受領する。 社内見積もりフォーマットでは四半期ベースの為、エクセルにて該当フォーマットを作成。 今回は上記フォーマット作成をPythonで行う #四半期版のコラム作成 xx=['1Q','2Q','3Q','4Q'] yy=['2021','2022','2023','2024'] combined = [y+'/'+x for y in yy for x in xx] ['2021/1Q', '2021/2Q', '2021/3Q', '2021/4Q', '2022/1Q', '2022/2Q', '2022/3Q', '2022/4Q', '2023/1Q', '2023/2Q', '2023/3Q', '2023/4Q', '2024/1Q', '2024/2Q', '2024/3Q', '2024/4Q'] #空のデーターフレーム作成し、コラムに四半期をfor loopで入れ込む ss=pd.DataFrame(index=['所要']) for com in combined: ss[com]=''] 2021/1Q 2021/2Q 2021/3Q 2021/4Q 2022/1Q 2022/2Q 2022/3Q 2022/4Q 2023/1Q 2023/2Q 2023/3Q 2023/4Q 2024/1Q 2024/2Q 2024/3Q 2024/4Q 所要 #四半期に変換させる為所要を単純に割る4。 #値はエクセルから読み込んでも良いが、今回は4個の為手打ち。 mm=[3000,3245,6554,6666] uu=map(lambda x: float(x)/4,mm) uu=list(uu) uu [750.0, 811.25, 1638.5, 1666.5] #各数字を4x繰り返す。 #フォーマットが4x4になる。 ll=[[u]*4 for u in uu] ll [[750.0, 750.0, 750.0, 750.0], [811.25, 811.25, 811.25, 811.25], [1638.5, 1638.5, 1638.5, 1638.5], [1666.5, 1666.5, 1666.5, 1666.5]] #4x4を16x1に変換 ll=np.ravel(ll) ll=list(ll) ll [750.0, 750.0, 750.0, 750.0, 811.25, 811.25, 811.25, 811.25, 1638.5, 1638.5, 1638.5, 1638.5, 1666.5, 1666.5, 1666.5, 1666.5] #格データーを入れ込み完成 for com,l in zip (combined,ll): ss[com]=l ss 2021/1Q 2021/2Q 2021/3Q 2021/4Q 2022/1Q 2022/2Q 2022/3Q 2022/4Q 2023/1Q 2023/2Q 2023/3Q 2023/4Q 2024/1Q 2024/2Q 2024/3Q 2024/4Q 所要 750.0 750.0 750.0 750.0 811.25 811.25 811.25 811.25 1638.5 1638.5 1638.5 1638.5 1666.5 1666.5 1666.5 1666.5 #おまけの逆バージョン #今度は四半期ベースから年単位に変換を行う。 #先ずは列と行の入れ替え cd=ss.T cd 所要 2021/1Q 750.00 2021/2Q 750.00 2021/3Q 750.00 2021/4Q 750.00 2022/1Q 811.25 2022/2Q 811.25 2022/3Q 811.25 2022/4Q 811.25 2023/1Q 1638.50 2023/2Q 1638.50 2023/3Q 1638.50 2023/4Q 1638.50 2024/1Q 1666.50 2024/2Q 1666.50 2024/3Q 1666.50 2024/4Q 1666.50 #今回は[年コラム」でgroup byを使用する。 #その為numpyのselect機能(Excelのif)を使用し、四半期ベースを年単位に変換していく condition=[cd.index.str.contains('2021'),cd.index.str.contains('2022'),cd.index.str.contains('2023'),cd.index.str.contains('2024')] result=['2021','2022','2023','2024'] cd['year']=np.select(condition,result) cd 所要 year 2021/1Q 750.00 2021 2021/2Q 750.00 2021 2021/3Q 750.00 2021 2021/4Q 750.00 2021 2022/1Q 811.25 2022 2022/2Q 811.25 2022 2022/3Q 811.25 2022 2022/4Q 811.25 2022 2023/1Q 1638.50 2023 2023/2Q 1638.50 2023 2023/3Q 1638.50 2023 2023/4Q 1638.50 2023 2024/1Q 1666.50 2024 2024/2Q 1666.50 2024 2024/3Q 1666.50 2024 2024/4Q 1666.50 2024 #group byで集計し、.Tで行列を入れ替え完成 k=cd.groupby(cd['year']).sum() k=k.T k year 2021 2022 2023 2024 所要 3000.0 3245.0 6554.0 6666.0 エクセルでの手順: *メインはPythonの為、解説は簡略化 (Index,Offset関数を使用する) Index関数を使用するためのネタ準備 上段の連番は=ROUND(COLUMN(B2)/4,0)で作成 下段の1,2,3,4はエクセルの連続コピー機能で作成 ↓はindex関数と↑の連番を使用し、2021/1Qの体裁を作成してく為のネタ Index関数にて作成していく =INDEX($I$5:$L$5,0,I2)&"/"&INDEX($I$6:$L$6,0,I3)→Pythonでいうこれcombined = [y+'/'+x for y in yy for x in xx] =INDEX($C$3:$F$3,0,I2)/4→Pythonでいうこれuu=map(lambda x: float(x)/4,mm) おまけのエクセル版 offset関数用のネタ準備 SUM(OFFSET())関数で4個の合計を算出 =SUM(OFFSET($I$9,0,I4,1,4))→2021/1Qを開始点(↑の0+1)として4個選択し合計→開始点から5番目(↑の4+1)から4個選択し合計..... 完成 結果: 今回の加工だけでいえばエクセルに軍配が上がりそうだが、個人的にはPythonの方が複雑な処理には向いてそう? 余談ですが、今後もメーカー営業xPythonというテーマで記事を拡充していこうと思う。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pandasの使い方4

1 この記事は DataFrame型データの各種操作方法をメモする。 2 内容 2-1 DataFrame型の特定の列にて欠損値がある行を取り出す sample.py import pandas as pd import numpy as np #dataを定義する。 dat = [ [1,100], [2,], [3,300], [4,400], [5,], ] #datをDataFrame型変数dfに格納する。 df = pd.DataFrame(dat,columns=["A","B"]) display(df) df1=df[df['B'].isnull()] print("B列に欠損値がある行のみを抽出する") display(df1) 実行結果 A B 0 1 100.0 1 2 NaN 2 3 300.0 3 4 400.0 4 5 NaN B列に欠損値がある行のみを抽出する A B 1 2 NaN 4 5 NaN 2-2 For文,list型結合 listsに\nが混在している。\nにてデータを分離し新しいlistを作る sample.py lists=['7201', '8524', '1605\n5020', '7201', '9432'] tmplist=[] for list in lists: tmplist.extend(list.split("\n")) tmplist 実行結果 ['7201', '8524', '1605', '5020', '7201', '9432'] 2-3 List型要素をstr型からint型に変更する。 listsに\nが混在している。\nにてデータを分離し新しいlistを作る sample.py ls = ["10", "20", "30", "40", "50"] print(ls) ls = [int(i) for i in ls] # ここでリストの中の値をint型へ変換しています。 print(ls) 実行結果 ['10', '20', '30', '40', '50'] [10, 20, 30, 40, 50] 2-4 index値を指定し、DataFrameの行を抽出する sample.py import pandas as pd dat0 = [ ['2019-07-01','9997','740'], ['2019-07-02','9997','749'], ['2019-07-03','9997','757'], ] df0 = pd.DataFrame(dat0,columns=["A","B","C"]) display(df0) #A,B列をindexに指定する。 df0=df0.set_index(["A"]) print("index='2019-07-01'の行を抽出する") display(df0.loc[['2019-07-01']]) 実行結果 A B C 0 2019-07-01 9997 740 1 2019-07-02 9997 749 2 2019-07-03 9997 757 index='2019-07-01'の行を抽出する B C A 2019-07-01 9997 740
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たくさんの写真を格子状に敷き詰めたスライドの生成【python】【python-pptx】

概要 PowerPointのスライドに、長方形の画像を格子状に貼り付けるプログラムを書きました。 動機 記念ムービー的なもので、たくさんの写真を敷き詰めた画像がほしかったので、作りました。 また、作ったあとで微修正できるように、画像出力ではなくpptxファイルとして出力するようにしました。 方針 長方形の画像を隙間なく敷き詰めたpptxスライドを作ることが目的です。 長方形であれば異なる(=相似でない)画像が混在していても構いません。 貼り付ける際に拡大縮小は可能ですが、元画像のアスペクト比は維持することとします。 画像は何でも良いですが、本稿ではWikipediaからダウンロードした国旗の画像を例として説明します。 環境 % sw_vers ProductName: macOS ProductVersion: 11.2.3 BuildVersion: (略) % python -V Python 3.8.0 インストール pip install python-pptx pip install Pillow 実装 左寄せ まずは細かいことを考えずに、画像を左上から右に敷き詰めていき、それ以上敷き詰められなくなったら次の行にいく、というコードを書いてみます。画像を並べる順番は事前に定義したもの(例えばファイル名のアイウエオ順、など)で固定とします。 from pptx import Presentation from PIL import Image import os #スライドサイズ #4:3 (default) 9144000x6858000 #16:9 12193200x6858000 SLIDE_WIDTH, SLIDE_HEIGHT = 12193200, 6858000 ROW_HEIGHT = SLIDE_HEIGHT // 15 #出力ファイル名 OUTPUT_FILE_PATH = "test.pptx" #画像の格納ディレクトリ IMG_DIR = "./data/" #画像のサイズを取得する def getImageSizes(img_paths): sizes = [] for img_path in img_paths: im = Image.open(img_path) im_width, im_height = im.size sizes.append((im_width, im_height)) return sizes #左寄せするときに各画像をどの位置にどのサイズで配置すればよいかを返す #一行あたりの高さ、スライドの幅は固定値として与える def leftJustifiedArrangement(image_sizes, row_height, slide_width): left = 0 top = 0 height = row_height positions = [] for im_width, im_height in img_sizes: width = im_width * height // im_height #画像がはみ出すなら次の行にする if width > (slide_width - left) and left > 0: left = 0 top += row_height positions.append((left, top, width, height)) left += width return positions def addPictures(slide, img_paths, positions): for img_path, (left, top, width, height) in zip(img_paths, positions): slide.shapes.add_picture(img_path, left, top, height = height) #アスペクト比を変えない前提でheightのみ指定 return slide #受け取ったプレゼンテーションオブジェクトにスライドを追加し、追加されたスライドオブジェクトを返す。 def addBlankSlide(prs): #白紙スライドの追加(ID=6は白紙スライド) blank_slide_layout = prs.slide_layouts[6] slide = prs.slides.add_slide(blank_slide_layout) return slide if __name__ == "__main__": #スライドオブジェクトの定義 prs = Presentation() #スライドサイズの指定 prs.slide_width = SLIDE_WIDTH prs.slide_height = SLIDE_HEIGHT #img画像のファイル名を取得 img_files = os.listdir(IMG_DIR) #pngで終了するファイル名のみ抽出。貼り付けたい画像の拡張子に応じて変える img_paths = [os.path.join(IMG_DIR,name) for name in img_files if name.endswith(".png") or name.endswith(".jpg")] #昇順にソート(この順番でスライドに貼り付けられる) img_paths.sort()#昇順にsort slide = addBlankSlide(prs) img_sizes = getImageSizes(img_paths) positions = leftJustifiedArrangement(img_sizes, ROW_HEIGHT, SLIDE_WIDTH) slide = addPictures(slide, img_paths, positions) prs.save(OUTPUT_FILE_PATH) 出来上がったpptxファイルを開くと以下のようになります。 ポイントはleftJustifiedArrangementという関数で、ここで、画像を配置するポジションを計算しています。 この関数を書き換えると、左寄せ以外のレイアウトも可能になります。 右寄せ 右寄せしたい場合は、左寄せしたときの各行の余ったスペースの文だけずらせばよいです。 具体的には、leftJustifiedArrangmentを以下の関数で置き換えます。 def rightJustifiedArrangement(img_sizes, row_height, slide_width): if len(img_sizes) == 0: return [] left = 0 top = 0 height = row_height lines = [[]] for im_width, im_height in img_sizes: width = im_width / im_height * row_height #画像がはみ出すとき、行を更新する if width > (slide_width - left) and left > 0: lines.append([]) left = 0 top += row_height lines[-1].append((left, top, width, height)) left += width #右寄せにleftを修正line要素が一つでもあれば右寄せ補正してpositionsに追加 right_justified_positions = [] for line in lines: rest_width = slide_width - (line[-1][0] + line[-1][2])#その行の残りwidth justified_line = [(l+rest_width, t, w, h) for l,t,w,h in line] right_justified_positions.extend(justified_line) return right_justified_positions 出力は以下のような感じです。 中央寄せ 中央寄せする場合は、ずらす量を右寄せ時の半分にすると良いです。 leftGridArrangementを以下で置き換えると実現されます。 def centerJustifiedArrangement(img_sizes, row_height, slide_width): if len(img_sizes) == 0: return [] left = 0 top = 0 height = row_height lines = [[]] for im_width, im_height in img_sizes: width = im_width / im_height * row_height #画像がはみ出すとき、行を更新する if width > (slide_width - left) and left > 0: lines.append([]) left = 0 top += row_height lines[-1].append((left, top, width, height)) left += width #右寄せにleftを修正line要素が一つでもあれば右寄せ補正してpositionsに追加 center_justified_positions = [] for line in lines: rest_width = slide_width - (line[-1][0] + line[-1][2]) #その行の残りwidth rest_width //= 2 justified_line = [(l+rest_width, t, w, h) for l,t,w,h in line] center_justified_positions.extend(justified_line) return center_justified_positions 出力は以下のような感じです。 ちょうどいい高さの自動計算 これより前のコードでは画像の高さを固定値で指定していましたが、それだとスライドに画像をまんべんなく敷き詰めるための高さをプログラム実行者が事前に計算しないといけません。 これをプログラムにある程度自動で見積もらせるようにします。 やり方は、行高さがスライド高さのi分の1のときに敷き詰めがスライド内に収まるかを、iを1から増加させつつ順次判定し、見つかった時点で処理を終了します。 def balancedRowHeight(img_sizes, slide_width, slide_height): if len(img_sizes)==0: return -1 row_num = 1 for i in range(1,10000): #while Trueでもいいが保険としてループ上限を決める positions = leftJustifiedArrangement(img_sizes, slide_height//i, slide_width) last_bottom = positions[-1][1] + positions[-1][3] if last_bottom <= slide_height: return slide_height // i return -1 実行するときには、上記関数で取得した値をleftJustifiedArrangementなどに代入します。 ROW_HEIGHT = balancedRowHeight(img_sizes, SLIDE_WIDTH, SLIDE_HEIGHT) positions = leftJustifiedArrangement(img_sizes, ROW_HEIGHT, SLIDE_WIDTH) slide = addPictures(slide, img_paths, positions) prs.save(OUTPUT_FILE_PATH) 出力は以下のような感じです。 明らかに一行余っていますが、これよりもう一段階、行高さが大きい(=iが1少ない)と、今度ははみ出してしまうので、スライドをはみ出さない最大の行高さとしては正しい値が計算できていると思われます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

たくさんの写真を敷き詰めたスライドの生成【python】【python-pptx】

概要 PowerPointのスライドに、長方形の画像を敷き詰めて貼り付けるプログラムを書きました。 動機 記念ムービー的なもので、たくさんの写真を敷き詰めた画像がほしかったので、作りました。 また、作ったあとで微修正できるように、画像出力ではなくpptxファイルとして出力するようにしました。 方針 長方形の画像を隙間なく敷き詰めたpptxスライドを作ることが目的です。 長方形であれば異なる(=相似でない)画像が混在していても構いません。 貼り付ける際に拡大縮小は可能ですが、元画像のアスペクト比は維持することとします。 画像は何でも良いですが、本稿ではWikipediaからダウンロードした国旗の画像を例として説明します。 環境 % sw_vers ProductName: macOS ProductVersion: 11.2.3 BuildVersion: (略) % python -V Python 3.8.0 インストール pip install python-pptx pip install Pillow 実装 左寄せ まずは細かいことを考えずに、画像を左上から右に敷き詰めていき、それ以上敷き詰められなくなったら次の行にいく、というコードを書いてみます。画像を並べる順番は事前に定義したもの(例えばファイル名のアイウエオ順、など)で固定とします。 from pptx import Presentation from PIL import Image import os #スライドサイズ #4:3 (default) 9144000x6858000 #16:9 12193200x6858000 SLIDE_WIDTH, SLIDE_HEIGHT = 12193200, 6858000 ROW_HEIGHT = SLIDE_HEIGHT // 15 #出力ファイル名 OUTPUT_FILE_PATH = "test.pptx" #画像の格納ディレクトリ IMG_DIR = "./data/" #画像のサイズを取得する def getImageSizes(img_paths): sizes = [] for img_path in img_paths: im = Image.open(img_path) im_width, im_height = im.size sizes.append((im_width, im_height)) return sizes #左寄せするときに各画像をどの位置にどのサイズで配置すればよいかを返す #一行あたりの高さ、スライドの幅は固定値として与える def leftJustifiedArrangement(image_sizes, row_height, slide_width): left = 0 top = 0 height = row_height positions = [] for im_width, im_height in img_sizes: width = im_width * height // im_height #画像がはみ出すなら次の行にする if width > (slide_width - left) and left > 0: left = 0 top += row_height positions.append((left, top, width, height)) left += width return positions def addPictures(slide, img_paths, positions): for img_path, (left, top, width, height) in zip(img_paths, positions): slide.shapes.add_picture(img_path, left, top, height = height) #アスペクト比を変えない前提でheightのみ指定 return slide #受け取ったプレゼンテーションオブジェクトにスライドを追加し、追加されたスライドオブジェクトを返す。 def addBlankSlide(prs): #白紙スライドの追加(ID=6は白紙スライド) blank_slide_layout = prs.slide_layouts[6] slide = prs.slides.add_slide(blank_slide_layout) return slide if __name__ == "__main__": #スライドオブジェクトの定義 prs = Presentation() #スライドサイズの指定 prs.slide_width = SLIDE_WIDTH prs.slide_height = SLIDE_HEIGHT #img画像のファイル名を取得 img_files = os.listdir(IMG_DIR) #pngで終了するファイル名のみ抽出。貼り付けたい画像の拡張子に応じて変える img_paths = [os.path.join(IMG_DIR,name) for name in img_files if name.endswith(".png") or name.endswith(".jpg")] #昇順にソート(この順番でスライドに貼り付けられる) img_paths.sort()#昇順にsort slide = addBlankSlide(prs) img_sizes = getImageSizes(img_paths) positions = leftJustifiedArrangement(img_sizes, ROW_HEIGHT, SLIDE_WIDTH) slide = addPictures(slide, img_paths, positions) prs.save(OUTPUT_FILE_PATH) 出来上がったpptxファイルを開くと以下のようになります。 ポイントはleftJustifiedArrangementという関数で、ここで、画像を配置するポジションを計算しています。 この関数を書き換えると、左寄せ以外のレイアウトも可能になります。 右寄せ 右寄せしたい場合は、左寄せしたときの各行の余ったスペースの文だけずらせばよいです。 具体的には、leftJustifiedArrangmentを以下の関数で置き換えます。 def rightJustifiedArrangement(img_sizes, row_height, slide_width): if len(img_sizes) == 0: return [] left = 0 top = 0 height = row_height lines = [[]] for im_width, im_height in img_sizes: width = im_width / im_height * row_height #画像がはみ出すとき、行を更新する if width > (slide_width - left) and left > 0: lines.append([]) left = 0 top += row_height lines[-1].append((left, top, width, height)) left += width #右寄せにleftを修正line要素が一つでもあれば右寄せ補正してpositionsに追加 right_justified_positions = [] for line in lines: rest_width = slide_width - (line[-1][0] + line[-1][2])#その行の残りwidth justified_line = [(l+rest_width, t, w, h) for l,t,w,h in line] right_justified_positions.extend(justified_line) return right_justified_positions 出力は以下のような感じです。 中央寄せ 中央寄せする場合は、ずらす量を右寄せ時の半分にすると良いです。 leftGridArrangementを以下で置き換えると実現されます。 def centerJustifiedArrangement(img_sizes, row_height, slide_width): if len(img_sizes) == 0: return [] left = 0 top = 0 height = row_height lines = [[]] for im_width, im_height in img_sizes: width = im_width / im_height * row_height #画像がはみ出すとき、行を更新する if width > (slide_width - left) and left > 0: lines.append([]) left = 0 top += row_height lines[-1].append((left, top, width, height)) left += width #右寄せにleftを修正line要素が一つでもあれば右寄せ補正してpositionsに追加 center_justified_positions = [] for line in lines: rest_width = slide_width - (line[-1][0] + line[-1][2]) #その行の残りwidth rest_width //= 2 justified_line = [(l+rest_width, t, w, h) for l,t,w,h in line] center_justified_positions.extend(justified_line) return center_justified_positions 出力は以下のような感じです。 ちょうどいい高さの自動計算 これより前のコードでは画像の高さを固定値で指定していましたが、それだとスライドに画像をまんべんなく敷き詰めるための高さをプログラム実行者が事前に計算しないといけません。 これをプログラムにある程度自動で見積もらせるようにします。 やり方は、行高さがスライド高さのi分の1のときに敷き詰めがスライド内に収まるかを、iを1から増加させつつ順次判定し、見つかった時点で処理を終了します。 def balancedRowHeight(img_sizes, slide_width, slide_height): if len(img_sizes)==0: return -1 row_num = 1 for i in range(1,10000): #while Trueでもいいが保険としてループ上限を決める positions = leftJustifiedArrangement(img_sizes, slide_height//i, slide_width) last_bottom = positions[-1][1] + positions[-1][3] if last_bottom <= slide_height: return slide_height // i return -1 実行するときには、上記関数で取得した値をleftJustifiedArrangementなどに代入します。 ROW_HEIGHT = balancedRowHeight(img_sizes, SLIDE_WIDTH, SLIDE_HEIGHT) positions = leftJustifiedArrangement(img_sizes, ROW_HEIGHT, SLIDE_WIDTH) slide = addPictures(slide, img_paths, positions) prs.save(OUTPUT_FILE_PATH) 出力は以下のような感じです。 明らかに一行余っていますが、これよりもう一段階、行高さが大きい(=iが1少ない)と、今度ははみ出してしまうので、スライドをはみ出さない最大の行高さとしては正しい値が計算できていると思われます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

wsl pythonの仮想環境:pyenv+venv+pip

Pythonで自然言語処理や機械学習の勉強をしています。 Pythonの仮想環境について調べた結果を記録します。 環境 windows 10 Home WSL2 +Ubuntu 18.04 Python 3.6.9 → Python 3.9.4 Visual Studio Code 仮想環境について調べた動機 現在、WSLにもともと入っていたPython3.6.9を使っており、 3.6のサポート終了が近づいているのでバージョンアップしようと思ったが、 環境を切り分ける方がいい気がしたので少し調べた。 各ツールについて知ったこと等のメモ pyenv: 異なるバージョンのPythonを共存させ、切り替えて使つためのツール。 venv: 開発プロジェクトごとに環境(利用パッケージ)を分けて管理するのに使う。 virtualenv: venvと同じようなことができるが、pytyon2系でも使えるらしい。venvより古くからあるらしい。 pip: パッケージをインストールするためのツール。インストールしたものをRequirement.txtというファイルに記録して、別の環境で再現するのに使える。 pipenv: パッケージの管理と仮想環境管理が両方行えるらしい。pipの代わりに使えるらしい。Requirement.txtの代わりにPipfileとPipfile.lockという2つのファイルに記録し、必須のものと依存しているものを分けて記録できるとのこと。 現時点で利用する環境 pyenv+venv+pipで管理しようと思います。 理由: pythonのバージョンアップをしてユーザー環境にpythonを置くのが当初の目的なので、 pyenvで新バージョンを入れるのが良いと思った。 素人が勉強・練習するだけなので環境を分ける必要があるかどうかわからなかったが、 後でリセットしやすいように仮想環境を作って使うことにした。そのためにvenvを使った。 virtualenvの後継がvenvとのことで、そちらにした。 pipenvだけでpyenv+venv+pipと同じ事ができる様子なのだが、 先にpyenvを入れてしまっていて、venvとpipは入っていたので、上記構成で行くことにした。 Visual Studio Codeでの利用  venvで作った環境(フォルダー)を開いて、インタプリターとしてvenvの中にあるpythonを指定することで、"Hello python"が動いた。VS codeのターミナルでは(venv)という表示が出て、仮想環境のshellで動いている様子だった(特に設定しなくてもそうなった)。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pythonで画像から文字認識のできるLine-botを作成する

はじめに OCRを用いて画像から文字認識を行う機能をラインボットに組み込み、ライン上のサービスとして使用できるようにします。 そもそも読むことが出来ない漢字は調べようがないので、テキストでもいいので文字情報を取得できればと思いました。 今回はOCRサービスとしてGoogle Vision API (以下Vision API)を利用し、Pythonを用いて作成します。その他以下のサービスを使用します ・LINEDeveloper ・Heroku 完成版はこちら 画像を送信すると、画像上の文字をテキスト形式で返します。あと、ローマ字で都市名を入力すると一日の天気も返してくれます。 ※APIをリクエスト制限を超えていない場合は動きます。 構成 root/  ├ main.py  ├ gcpapi.py  ├ config  │ └ GCPの認証(JSON)  ├ runtime.txt    ├ requirements.txt  ├ Procifile    └ README.m 環境設定 ラインボットの作成及びherekuへの連動については、以下のページを参照 Pythonでline bot 作ってみた Python初心者! -LINE Botでオウム返し編 Pythonのバージョンは以下の通りです Python 3.9.0 使用するライブラリは以下の通り Flask 1.1.2 google-cloud-vision 2.3.1 line-bot-sdk 1.19.0 requests 2.25.1 今回は、Vision APIの利用について説明します。 Vison API Vison APIとはGoogle Cloud Platformが提供する機械学習サービスの1つで、以下の情報が取得できます ・画像に写っているさまざまなカテゴリの物体を検出(ラベル検出) ・画像内のテキストに対してテキストを検出、抽出 ・画像内の顔検出 ・画像に含まれる不適切なコンテンツを検出(セーフサーチ検出) ・画像に含まれる人工建造物を検出(ランドマーク検出) 画像があれば実際にサイト上でOCRを試すことが出来るみたいなので実際にやってみます 試した画像 結果 画像のテキストは大きな間違いはありませんでした。(いくつかご認識はありますが.....) 他にもラベルやロゴなどの情報も取得しているみたいです。 続いて料金は以下の通りです 今回は無料枠なので1ヶ月で1000リクエストまで。 GCPへの登録 Vision APIを利用するためにはGCP(Google Cloud Platform)へ登録しないといけないみたいなので、さっそく登録します AWSと同じで登録にクレカが必要です。ただ最初から自動従属課金制じゃないので膨大な請求がいきなり来ることはないかも??? 認証情報の取得 Vision APIの登録まで完了すると、秘密鍵を含んだJSONファイルがダウンロードされます。ここまでの詳しい手順は以下の記事を参考にしました。 Google Cloud Vision APIのOCRを使ってPythonから文字認識する方法 文字認識 実際にこのレシートでAPIを飛ばしてみます vision_apicall.py from pathlib import Path from google.cloud import vision p = Path(__file__).parent / 'image/test.png'#OCRに使用する画像ファイルのパス client = vision.ImageAnnotatorClient() with p.open('rb') as image_file: content = image_file.read() image = vision.Image(content=content) response = client.document_text_detection(image=image)#文字情報の取得 print(response.full_text_annotation.text) はい、エラー頂きました google.auth.exceptions.DefaultCredentialsError: Could not automatically determine credentials. Please set GOOGLE_APPLICATION_CREDENTIALS or explicitly create credentials and re-run the application. For more information, please see https://cloud.google.com/docs/authentication/getting-started 認証情報が設定されてないとの事。上記のエラーコードのURLに設定の説明があるのでそちらで設定方法を確認します。先ほど取得したJsonファイルのパスを設定するとのことです。 JSONファイルの設定方法 すべてJSONファイルへのパスを設定します。 MaCの場合 export GOOGLE_APPLICATION_CREDENTIALS="KEY_PATH" Windows(PowerShell)の場合 $env:GOOGLE_APPLICATION_CREDENTIALS="KEY_PATH" Windows(コマンドライン)の場合 set GOOGLE_APPLICATION_CREDENTIALS=KEY_PATH 設定したところでもう一回実行 I SHOP 神奈川県横浜市西区みなとみらい みなとみらい店 TEL045-222-6549 営業時間:10:00-19:00 *春の大セール開催中! 2013年3月22日(金) 11:32 1100 大根 ・ ・ ・ ・ 10ポイント クレジットM 一言一句違わずいけてました。 他にも特徴的な画像ファイルで試しましたが、体感では文字が入っている画像の正答率は90%程度でした。 また、まったく文字情報を含んでいない画像に対しては文字情報は帰ってきませんでした。 LINEbotへの組み込み いざ、linebotに組み込みます! APIの呼び出し部分 gcpapi.py from google.cloud import vision def image_to_text(imagecontent): client = vision.ImageAnnotatorClient() image = vision.Image(content=imagecontent) response = client.document_text_detection(image=image)#文字情報の取得 return response.full_text_annotation.text ライン上で画像を受けって返信する箇所 main.py import gcpapi #省略 @handler.add(MessageEvent, message=ImageMessage) def handle_image(event): message_id = event.message.id message_content = line_bot_api.get_message_content(message_id) res=gcpapi.image_to_text(message_content.content)#テキスト try: line_bot_api.reply_message( event.reply_token, TextSendMessage(text=res)) except Exception as e: line_bot_api.reply_message( event.reply_token, TextSendMessage(text="errorが発生しました")) 最後にherokuの環境変数の設定(json)を反映させる heroku config:set GOOGLE_APPLICATION_CREDENTIALS='config/jsonファイル名' herokuにも環境設定でファイルをパスでそのまま割り当ててもいい事を知らなくて1時間無駄にしました。 実装したサービス 実際に画像を送信してみます 結果(画像小さくて、すいません) なんか違うくね....... 気を取り直して 結果 漢字ってやっぱり判別が難しんですよね、分かります。 そもそも、画像の構成要素として漢字一文字を想定しておらず一つの文章単位で認識の対象にしているんですかね。どんな文字が来るか分からない上での、認識はこの程度が限界な気がします。漢字のみを対象としているサービスを探す方が正確なんですかね。 もう少し深堀っていく余地ありですが、簡単な文章では使えそうなので、これはこれで使っていきたいと思います。 ※余談ですがラインの文字認識のサービス結果も載せておきます(やっぱり漢字は難しんですね)  
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

LeetCodeWeekly 239B 1849. Splitting a String Into Descending Consecutive Values 連続する減少数列の結合の判定

前日のGCJ-Bに近いものを感じます. 題意 最大20桁の数字が与えられる その数字がa, a-1, ... a-nの数字を連結して作られた文字列かを判定しろ ただし、各数字の頭には0がついていても良い こう考えた まず、この問題の難しさは3つめの「頭に0が付いていても良い」というところで、例えば、5,4,3という数字を組み合わせた時に、$543$は当然として、$5043$や$50004$や$54003$なども候補となるからである. もしも、あたまの0を気にしなくて良いのであれば、適当にi文字までをとった時にデクリメントしながら数字を生成して判定すれば良いがそうもいかない. このため、以下のような関数f$(s, num)$を考える. 前の数字が$num$とする時に、$s$自身が$num-1$、あるいは、$num-1$を払い出せる $num-1$を払い出したのであれば、$f(残りの文字列, num-1)$が成立するかを調べる. 実装 上記をそのまま実装すればいい.つまり、 最初はi文字目までを必ずカットする i文字目までをカットした時、$num-1$に作れるカットがあるなら再起的に追う class Solution: def f(self,s, num): if num is not None and int(s) == (num - 1): return True for i in range(1, len(s)): curval = s[:i] if num is not None and int(curval) != (num-1): continue res = self.f(s[i:], int(curval)) if res is True: return True return False def splitString(self, s: str) -> bool: return self.f(s, None)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Niziuのメンバーを機械学習で分類してみた

はじめに はじめまして。最近機械学習を勉強している者です。 今回Niziuのメンバーを分類するアプリを作成してみたため、 アウトプットを目的として記事を作成いたします。 エンジニア経験すらない初心者ですので効率の悪いやり方をしていると思いますが、 どうか温かい目で見守ってくださると幸いです(?) 目次 画像の選定 画像の選定 画像から顔の部分を切り抜き 画像を訓練データとテストデータに分割 訓練データの拡張 モデルの定義と学習 画像の推定 まとめ Niziuとは NiziU(ニジュー、韓: 니쥬)は、9人組ガールズグループである。日本のソニーミュージックと韓国のJYPエンターテインメントによる日韓合同のグローバルオーディションプロジェクト「Nizi Project」パート2の最終順位上位9名から結成された[4][5]。 グループ名は、Nizi Projectの「Nizi(虹)」と、メンバーやファンを表す「U」に由来しており[6]、「Need you」という意味を含んでいる。プロデューサーのパク・ジニョンはメンバーに対して「人は一人では成功できない。みんなにはお互いが必要で、ファンが必要である」と語っている[7][8]。 ファンクラブ名は「WithU(ウィジュー)」で[9]、「NiziUを支えてくれるファンのみなさん(U)と一緒に(With)世界へ向けて活動していく」という思いが込められている[9]。 Wikipediaより引用 「Nizi project」というオーディションで総勢10,231人から選ばれた9人組のガールズグループで、 メンバーは「マコ」「マヤ」「マユカ」「リク」「ミイヒ」「リオ」「アヤカ」「ニナ」「リマ」の9人です。 今までK-POPに興味がなかったのですが、 友達に「Nizi Project」を勧められてからどっぷりハマりました。 メンバーが全員日本人なのでKPOPか否か論争があるみたいですが、正直どっちでもいいです。 とにかく若い子達が厳しいオーディションに挑戦する姿に感動して何度も泣きました。 アイドルとかKPOPに興味がない人にもぜひ見てもらいたいです。 1. 画像の収集 まずは、画像分類に必要な画像をGoogle画像検索から収集します。 Google Custom Search APIを使えば画像を集められるそうですが、 初心者すぎてそんなこと知りませんでした。 なのでSeleniumを使ってメンバー1人当たり300枚程度の画像を集めました。 流れとしては ⑴. 画像のURLを取得し、CSVファイルに保存 ⑵. CSVファイルに保存したURLから画像を保存 です。 回りくどいことをしていますが、勉強のためにしました。 Seleniumの使い方に関しては以下の記事を参考にしました。 参考: Python + Selenium で Chrome の自動操作を一通り ⑴. 画像のURLを取得 コードの全体像は以下です。 get_images_url.py # インポート import os from time import sleep import requests from selenium import webdriver from selenium.webdriver.chrome.options import Options import pandas as pd # シークレットモードでChromeを表示ために設定 options = Options() options.add_argument("--incognito") # シークレットウィンドウでChromeを起動 # options.add_argument('--headless') # headlessモードで実行する際に使用 driver = webdriver.Chrome(executable_path="./chromedriver", options=options) # 指定したURLを表示 url = "https://www.google.co.jp/imghp?hl=ja&tab=ri&ogbl" # google画像検索ページのURL driver.get(url) sleep(2) # キーワードを入力し、検索 member_name = "取得したいメンバーの名前" query = "niziu " + member_name search_box = driver.find_element_by_class_name("gLFyf") search_box.send_keys(query) search_box.submit() sleep(2) # スクロール操作 height = 1000 while height < 50000: driver.execute_script("window.scrollTo(0, {});".format(height)) height += 3000 sleep(1) # サムネイルのURLを取得 thumnail_urls = driver.find_elements_by_class_name("rg_i") thumnail_urls_len = len(thumnail_urls) print("thumnail_urls_len :{}".format(thumnail_urls_len)) # サムネイルをクリックして各画像URLを取得 image_urls = set() for img in thumnail_urls[:300]: try: img.click() sleep(2) except Exception: continue try: url_xpath = driver.find_element_by_xpath('//*[@id="Sva75c"]/div/div/div[3]/div[2]/c-wiz/div/div[1]/div[1]/div/div[2]/a/img') url = url_xpath.get_attribute("src") if url and "https:" in url: image_urls.add(url) print(url) print() except Exception as e: print("no such element") # csvファイルに保存 image_urls = list(image_urls) df = pd.DataFrame(image_urls, columns=["url"]) df.to_csv("{}_image_url.csv".format(member_name)) # 画面終了 driver.quit() まずは、ライブラリをインポート。 get_images_url.py import os from time import sleep import requests from selenium import webdriver from selenium.webdriver.chrome.options import Options import pandas as pd Chromeをシークレットウィンドウで起動し、 今回は「niziu 〇〇(メンバーの名前)」という形で検索をした画像を取得していきます。 get_images_url.py options = Options() options.add_argument("--incognito") # シークレットウィンドウでChromeを起動 # options.add_argument('--headless') # headlessモードで実行する際に使用 driver = webdriver.Chrome(executable_path="./chromedriver", options=options) url = "https://www.google.co.jp/imghp?hl=ja&tab=ri&ogbl" # google画像検索ページのURL driver.get(url) sleep(2) # キーワードを入力し、検索する member_name = "取得したいメンバーの名前" query = "niziu " + member_name search_box = driver.find_element_by_class_name("gLFyf") search_box.send_keys(query) search_box.submit() sleep(2) Google画像検索ページで大量の画像を取得する場合はある程度画面をスクロールして画面更新しないといけないので、 自動でスクロールさせた後に画像のURLを取得していきます。 get_images_url.py # スクロール操作 height = 1000 while height < 50000: driver.execute_script("window.scrollTo(0, {});".format(height)) height += 3000 sleep(1) # サムネイルのURLを取得 thumnail_urls = driver.find_elements_by_class_name("rg_i") thumnail_urls_len = len(thumnail_urls) print("thumnail_urls_len :{}".format(thumnail_urls_len)) # サムネイルをクリックして各画像URLを取得 image_urls = set() for img in thumnail_urls[:300]: try: img.click() sleep(2) except Exception: continue try: url_xpath = driver.find_element_by_xpath('//*[@id="Sva75c"]/div/div/div[3]/div[2]/c-wiz/div/div[1]/div[1]/div/div[2]/a/img') url = url_xpath.get_attribute("src") if url and "https:" in url: image_urls.add(url) except Exception as e: print("no such element") image_urlsに格納した画像のURLをCSVに保存。 get_images_url.py # csvファイルに保存 image_urls = list(image_urls) df = pd.DataFrame(image_urls, columns=["url"]) df.to_csv("{}_image_url.csv".format(member_name)) # 画面終了 driver.quit() ⑵. 画像のURLから画像を保存していく コードの全体像は以下です。 get_images.py # インポート import os import requests import pandas as pd members_name = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] for member_name in members_name: # CSVの読み込み df = pd.read_csv("{}_image_url.csv".format(member_name)) # 各メンバーのディレクトリがない場合は作成 IMAGE_DIR = "images/{}/".format(member_name) if not os.path.exists(IMAGE_DIR): os.mkdir(IMAGE_DIR) # URLから画像をダウンロード for i, url in enumerate(df.url): try: image = requests.get(url) with open(IMAGE_DIR + "{}_{}".format(member_name, str(i).zfill(3)) + ".jpg", "wb") as f: f.write(image.content) except Exception as e: print(member_name, i, "error!!") ディレクトリ構造は以下です。 root/ ┠ get_images.py ┠ csvファイル ┗ images/ ├ mako/ ├ maya/ ... └ rima/ まずはライブラリをインポート。 get_images.py # インポート import os from time import sleep import requests import pandas as pd 各メンバーのCSVファイルを読み込んで、URLから画像を保存していきます。 get_images.py members_name = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] for member_name in members_name: # CSVの読み込み df = pd.read_csv("{}_image_url.csv".format(member_name)) # 各メンバーのディレクトリがない場合は作成 IMAGE_DIR = "images/{}/".format(member_name) if not os.path.exists(IMAGE_DIR): os.mkdir(IMAGE_DIR) # URLから画像をダウンロード for i, url in enumerate(df.url): try: image = requests.get(url) with open(IMAGE_DIR + "{}_{}".format(member_name, str(i).zfill(3)) + ".jpg", "wb") as f: f.write(image.content) except Exception as e: print(member_name, i, "error!!") 2. 画像の選定 メンバー1人あたり300枚の画像を取得しました。 本来なら1人あたり数千枚ほどあるのが理想なのですが、Niziu自体デビューしてまだ間もなくあまり画像がないのでとりあえず300枚取得しました。 ただ、取得した画像を見てみると同じ画像が保存されていたり、メンバー名で取得したのにメンバー全員が映っている画像が取得されていたりしたので画像を以下の手順で選定しました。 複数人が写っている画像は顔を切り取って分ける 同じ画像は削除する 不要な画像や学習に使いづらい画像も削除する 結果1人あたり100枚程度しか集まりませんでした。 圧倒的に少ないのは承知の上ですが、とりあえずやっていくしかないですね。。 3. 画像から顔の部分を切り抜き 画像から顔の部分を切り抜くのはOpenCVを使用しました。 OpenCVの顔の切り抜き方は以下の記事を参考にしました。 参考: OpenCVで顔認証を行いトリミングして保存する 顔をトリミングして保存 コードの全体像は以下です。 get_images_face.py get_images_face.py import os import sys import glob import time import cv2 import numpy as np image_size = 150 members_name = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] HAAR_FILE = "/Users/moto/Desktop/Niziu_images/haarcascade_frontalface_default.xml" cascade = cv2.CascadeClassifier(HAAR_FILE) for member_name in members_name: # 入力する画像ファイルのディレクトリ IMAGE_DIR = "images/{}/".format(member_name) # 顔の切り抜きが成功した時に出力するディレクトリ file_path_1 = "image_face/{}/".format(member_name) # 顔の切り抜きが失敗した時に出力するディレクトリ file_path_2 = "image_face_false/{}/".format(member_name) # 画像の読み込み file_list = glob.glob("{}*.jpg".format(IMAGE_DIR)) for i, filename in enumerate(file_list): img = cv2.imread(filename) img_gray = cv2.imread(filename, 0) # 画像が読み込めない場合 if img is None: print(filename) print() # 顔の切り抜きを実行し、画像ファイルを保存 try: face = cascade.detectMultiScale(img_gray) j = 0 for x, y, w, h in face: face_cut = img[y:y+h, x:x+w] face_cut = cv2.resize(face_cut, (image_size, image_size)) os.makedirs(file_path_1, exist_ok=True) cv2.imwrite(file_path_1 + "{}_{}_{}.jpg".format(member_name, str(i).zfill(3), str(j).zfill(3)), face_cut) j += 1 # 顔の切り抜きができなかった時はそのまま保存 except Exception: os.makedirs(file_path_2, exist_ok=True) cv2.imwrite(file_path_2 + "{}_{}.jpg".format(member_name, str(i).zfill(3)), filename) ディレクトリ構造は以下です。 root/ ├ get_images_face.py ├ images/ │ ├ mako/ │ ├ maya/ │ ├ ... / │ └ rima/ │ ├ image_face/ │ ├ mako/ │ ├ maya/ │ ├ ... / │ └ rima/ │ └ image_face_false/ ├ mako/ ├ maya/ ├ ... / └ rima/ まずはライブラリをインポート get_images_face.py import os import sys import glob import time import cv2 import numpy as np 顔の部分を切り抜いて、150 × 150にリサイズして保存します。 カスケード分類器には"haarcascade_frontalface_default.xml"を使用していますが、正面を向いていないと顔をちゃんと切り抜けないことが多いので、 顔の切り抜きが成功した場合 → image_faceディレクトリに保存 顔の切り抜きが失敗した場合 → image_face_falseディレクトリにそのまま保存 として、切り抜きが失敗した場合は手動で顔の部分を切り抜いて行きました。 get_images_face.py # 画像サイズ image_size = 150 # カスケード分類器 HAAR_FILE = "/Users/moto/Desktop/Niziu_images/haarcascade_frontalface_default.xml" cascade = cv2.CascadeClassifier(HAAR_FILE) members_name = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] for member_name in members_name: # 入力する画像ファイルのディレクトリ IMAGE_DIR = "images/{}/".format(member_name) # 顔の切り抜きが成功した時に出力するディレクトリ file_path_1 = "image_face/{}/".format(member_name) # 顔の切り抜きが失敗した時に出力するディレクトリ file_path_2 = "image_face_false/{}/".format(member_name) # 画像の読み込み file_list = glob.glob("{}*.jpg".format(IMAGE_DIR)) for i, filename in enumerate(file_list): img = cv2.imread(filename) img_gray = cv2.imread(filename, 0) # 画像が読み込めない場合 if img is None: print(filename) print() # 顔の切り抜きを実行し、画像ファイルを保存 try: face = cascade.detectMultiScale(img_gray) j = 0 for x, y, w, h in face: face_cut = img[y:y+h, x:x+w] face_cut = cv2.resize(face_cut, (image_size, image_size)) os.makedirs(file_path_1, exist_ok=True) cv2.imwrite(file_path_1 + "{}_{}_{}.jpg".format(member_name, str(i).zfill(3), str(j).zfill(3)), face_cut) j += 1 # 顔の切り抜きができなかった時はそのまま保存 except Exception: os.makedirs(file_path_2, exist_ok=True) cv2.imwrite(file_path_2 + "{}_{}.jpg".format(member_name, str(i).zfill(3)), filename) 4. 画像を訓練データとテストデータに分割 ここからはGoogle Colaboratoryで作業するために、まずは取得したメンバーの顔画像をGoogleマイドライブにアップロードします。 全てGoogle Colaboratoryで作業したかったのですが、Javascriptで生成されているサイトからスクレイピングをする際に問題が発生するケースがあるみたいなのでローカルで作業していました。 ※以下の記事を参考にすればGoogle Colaboratoryでも問題なくスクレイピングできるみたいなのでまた別の機会に試したいと思います。 参考:ColaboratoryでSeleniumが使えた:JavaScriptで生成されるページも簡単スクレイピング ファイルをマイドライブにアップロード マイドライブにNiziuappディレクトリを作成し、その中にfaceディレクトリを作成。 faceディレクトリの中にメンバー毎のディレクトリを作成してその中に画像を格納している状態にしました。 ディレクトリ構造をまとめると以下のようになります。 content/ └ MyDrive/ └ Niziuapp/ └ face/ ├ mako/ │ ├ 〇〇.jpg │ ├ ... │ └ 〇〇.jpg │ ├ maya/ │ ├ 〇〇.jpg │ ├ ... │ └ 〇〇.jpg │ ├ ... │ └ rima/ ├ 〇〇.jpg ├ ... └ 〇〇.jpg 訓練データとテストデータに分割 今回は各メンバー100枚中70枚を訓練データに、30枚をテストデータに分割します。 同じ階層にtest_imagesディレクトリを作成し、faceディレクトリの中の各メンバーのディレクトリから画像をランダムで30枚test_imagesディレクトリに保存していきます。 Google Colaboratoryからマイドライブの画像を扱うためにGoogleドライブをマウントする必要がありますが、それについては以下の記事がとてもわかりやすいのでご参照ください。 参考:ColaboratoryでのGoogle Driveへのマウントが簡単になっていたお話 まず、Google Coraboratoryを使う際は最初contentディレクトリにいるはずなので、 Niziuappディレクトリに移動します。 train_test_images.ipynb cd /content/drive/MyDrive/NiziuApp その後、訓練データとテストデータを分割していきます。 train_test_images.ipynb import os, glob import random members = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] # 30枚をtest_imagesに移行 IMAGE_DIR = "face" os.makedirs("./test_images", exist_ok=True) for member in members: files = glob.glob(os.path.join(IMAGE_DIR, member + "/*.jpg")) random.shuffle(files) os.makedirs('./test_images/' + member, exist_ok=True) for i in range(30): shutil.move(str(files[i]), "./test_images/" + member) 5. 訓練データの拡張 コードの全体像は以下です。 Data_Augmentation.ipynb Data_Augmentation.ipynb import os import cv2 import numpy as np def scratch_image(img, flip=True, blur=True, rotate=True): methods = [flip, blur, rotate] # filp は画像上下反転 # blur はぼかし # rotate は画像回転 # 画像のサイズ(x, y) size = np.array([img.shape[1], img.shape[0]]) # 画像の中心位置(x, y) center = tuple([int(size[0]/2), int(size[1]/2)]) # 回転させる角度 angle = 30 # 拡大倍率 scale = 1.0 mat = cv2.getRotationMatrix2D(center, angle, scale) # 画像処理をする手法をNumpy配列に格納 scratch = np.array([ lambda x: cv2.flip(x, 0), # flip lambda x: cv2.GaussianBlur(x, (15, 15), 0), # blur lambda x: cv2.warpAffine(x, mat, img.shape[::-1][1:3]) # rotate ]) # imagesにオリジナルの画像を配列として格納 images = [img] # 関数と画像を引数に、加工した画像を元と合わせて水増しする関数 def doubling_images(func, images): return images + [func(i) for i in images] for func in scratch[methods]: images = doubling_images(func, images) return images # faceディレクトリにあるメンバーの画像を拡張する IMAGE_DIR = "face" members = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] for member in members: files = glob.glob(os.path.join(IMAGE_DIR, member + "/*.jpg")) for index, file in enumerate(files): member_image = cv2.imread(file) data_aug_list = scratch_image(member_image) # 拡張した画像を出力するディレクトリを作成 os.makedirs("train_images/{}".format(member), exist_ok=True) output_dir = "train_images/{}".format(member) # 保存 for j, img in enumerate(data_aug_list): cv2.imwrite("{}/{}_{}.jpg".format(output_dir, str(index).zfill(3), str(j).zfill(2)), img) 各メンバー訓練データが70枚しかないのでデータの水増しを行いました。 水増しの方法はopenCVを使って、 上下反転 ぼかし 画像の回転(30°) の処理を入れて、水増ししました。 まずはライブラリをインポート。 Data_Augmentation.ipynb import os import cv2 import numpy as np 1枚の画像から上記の水増しをして、拡張データを作成する関数を定義します。 70枚の画像から560枚まで拡張したのですが、 もっと水増ししてもよかったかなと反省しているのでまた色々試してみます。 Data_Augmentation.ipynb def scratch_image(img, flip=True, blur=True, rotate=True): methods = [flip, blur, rotate] # filp は画像上下反転 # blur はぼかし # rotate は画像回転 # 画像のサイズ(x, y) size = np.array([img.shape[1], img.shape[0]]) # 画像の中心位置(x, y) center = tuple([int(size[0]/2), int(size[1]/2)]) # 回転させる角度 angle = 30 # 拡大倍率 scale = 1.0 mat = cv2.getRotationMatrix2D(center, angle, scale) # 画像処理をする手法をNumpy配列に格納 scratch = np.array([ lambda x: cv2.flip(x, 0), # flip lambda x: cv2.GaussianBlur(x, (15, 15), 0), # blur lambda x: cv2.warpAffine(x, mat, img.shape[::-1][1:3]) # rotate ]) # imagesにオリジナルの画像を配列として格納 images = [img] # 関数と画像を引数に、加工した画像を元と合わせて水増しする関数 def doubling_images(func, images): return images + [func(i) for i in images] for func in scratch[methods]: images = doubling_images(func, images) return images train_imagesディレクトリを作成し、faceディレクトリにある各メンバーの画像を取得して元データと拡張データをtrain_imagesディレクトリに保存していきます。 Data_Augmentation.ipynb # faceディレクトリにあるメンバーの画像を拡張する IMAGE_DIR = "face" members = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] for member in members: files = glob.glob(os.path.join(IMAGE_DIR, member + "/*.jpg")) for index, file in enumerate(files): member_image = cv2.imread(file) data_aug_list = scratch_image(member_image) # 拡張した画像を出力するディレクトリを作成 os.makedirs("train_images/{}".format(member), exist_ok=True) output_dir = "train_images/{}".format(member) # 保存 for j, img in enumerate(data_aug_list): cv2.imwrite("{}/{}_{}.jpg".format(output_dir, str(index).zfill(3), str(j).zfill(2)), img) データの拡張方法にはKerasのImageDataGeneratorを使用するとリアルタイムに拡張しながら、学習が行えるみたいなのでこっちの方が良さそうです。 ImageDataGeneratorも今後は使いこなしていけるように勉強します。 6. モデルの定義と学習 学習データとテストデータが揃ったので、機械学習モデルを定義して実際に学習させていきます。 今回はVGG16という、「ImageNet」と呼ばれる大規模画像データセットで学習された16層からなるCNNモデルを使って転移学習をします。 コードの全体像は以下です。 Niziu.ipynb Niziu.ipynb import os, glob import random import cv2 import numpy as np import matplotlib.pyplot as plt from tensorflow.keras.utils import to_categorical from tensorflow.keras import Input, Sequential, Model from tensorflow.keras.models import load_model, save_model from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img from tensorflow.keras.applications.vgg16 import VGG16 from tensorflow.keras.optimizers import SGD members = ["mako", "maya", "mayuka", "riku", "miihi", "rio", "ayaka", "nina", "rima"] num_classes = len(members) image_size = 150 IMAGE_DIR_TRAIN = "train_images" IMAGE_DIR_TEST = "test_images" # 訓練データとテストデータをわける X_train = [] X_test = [] y_train = [] y_test = [] # 訓練データをリストに代入 for index, member in enumerate(members): files = glob.glob(os.path.join(IMAGE_DIR_TRAIN, member + "/*.jpg")) for file in files: image = load_img(file) image = image.resize((image_size, image_size)) image = img_to_array(image) X_train.append(image) y_train.append(index) # テストデータをリストに代入 for index, member in enumerate(members): files = glob.glob(os.path.join(IMAGE_DIR_TEST, member + "/*.jpg")) for file in files: image = load_img(file) image = image.resize((image_size, image_size)) image = img_to_array(image) X_test.append(image) y_test.append(index) # テストデータと訓練データをシャッフル p = list(zip(X_train, y_train)) random.shuffle(p) X_train, y_train = zip(*p) q = list(zip(X_test, y_test)) random.shuffle(q) X_test, y_test = zip(*q) # Numpy配列に変換 X_train = np.array(X_train) X_test = np.array(X_test) y_train = np.array(y_train) y_test = np.array(y_test) # データの正規化 X_train = X_train / 255.0 X_test = X_test / 255.0 # One-hot表現 y_train = to_categorical(y_train, num_classes) y_test = to_categorical(y_test, num_classes) # VGG16のインスタンスの生成 input_tensor = Input(shape=(150, 150, 3)) vgg16 = VGG16(include_top=False, weights="imagenet", input_tensor=input_tensor) # モデルの生成 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation="relu")) top_model.add(Dropout(0.5)) top_model.add(Dense(128, activation="relu")) top_model.add(Dropout(0.5)) top_model.add(Dense(num_classes, activation="softmax")) # モデルの結合 model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) # model.summary() # 15層目までのパラメータを固定 for layer in model.layers[:15]: layer.trainable = False # モデルのコンパイル optimizer = SGD(lr=1e-4, momentum=0.9) model.compile(optimizer=optimizer, loss="categorical_crossentropy", metrics=["accuracy"]) # モデルの学習 batch_size = 32 epochs = 100 # EaelyStoppingの設定 early_stopping = EarlyStopping( monitor='val_loss', min_delta=0.0, patience=3, ) history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(X_test, y_test), callbacks=[early_stopping] ) scores = model.evaluate(X_test, y_test, verbose=1) # モデルの保存 model.save("./model.h5") # 可視化 fig = plt.figure(figsize=(15,5)) plt.subplots_adjust(wspace=0.4, hspace=0.6) ax1 = fig.add_subplot(1, 2, 1) ax1.plot(history.history["accuracy"], c="b", label="acc") ax1.plot(history.history["val_accuracy"], c="r", label="val_acc") ax1.set_xlabel("epochs") ax1.set_ylabel("accuracy") plt.legend(loc="best") ax2 = fig.add_subplot(1, 2, 2) ax2.plot(history.history["loss"], c="b", label="loss") ax2.plot(history.history["val_loss"], c="r", label="val_loss") ax2.set_xlabel("epochs") ax2.set_ylabel("loss") plt.legend(loc="best") fig.show() まずはライブラリをインポート。 Niziu.ipynb import os, glob import random import cv2 import numpy as np import matplotlib.pyplot as plt from tensorflow.keras.utils import to_categorical from tensorflow.keras import Input, Sequential, Model from tensorflow.keras.models import load_model, save_model from tensorflow.keras.layers import Dense, Conv2D, Flatten, Dropout, MaxPooling2D from tensorflow.keras.preprocessing.image import ImageDataGenerator, img_to_array, load_img from tensorflow.keras.applications.vgg16 import VGG16 from tensorflow.keras.optimizers import SGD, Adam 訓練データとテストデータを読み込み、データの正規化やラベルのOne-hot表現などをします。 データの正規化をすることで精度が上がりやすくなるみたいです。 One-hot表現は簡単にいうと正解ラベルを1、それ以外を0にすることです。 例えば、今回はmakoの正解ラベルが「0」なので、[1, 0, 0, 0, 0 ,0 ,0 ,0 ,0]みたいに変更してくれるものです。 Niziu.ipynb IMAGE_DIR_TRAIN = "train_images" IMAGE_DIR_TEST = "test_images" # 訓練データとテストデータをわける X_train = [] X_test = [] y_train = [] y_test = [] # 訓練データをリストに代入 for index, member in enumerate(members): files = glob.glob(os.path.join(IMAGE_DIR_TRAIN, member + "/*.jpg")) for file in files: image = load_img(file) image = image.resize((image_size, image_size)) image = img_to_array(image) X_train.append(image) y_train.append(index) # テストデータをリストに代入 for index, member in enumerate(members): files = glob.glob(os.path.join(IMAGE_DIR_TEST, member + "/*.jpg")) for file in files: image = load_img(file) image = image.resize((image_size, image_size)) image = img_to_array(image) X_test.append(image) y_test.append(index) # テストデータと訓練データをシャッフル p = list(zip(X_train, y_train)) random.shuffle(p) X_train, y_train = zip(*p) q = list(zip(X_test, y_test)) random.shuffle(q) X_test, y_test = zip(*q) # Numpy配列に変換 X_train = np.array(X_train) X_test = np.array(X_test) y_train = np.array(y_train) y_test = np.array(y_test) # データの正規化 X_train = X_train / 255.0 X_test = X_test / 255.0 # One-hot表現 y_train = to_categorical(y_train, num_classes) y_test = to_categorical(y_test, num_classes) モデルの定義 Niziu.ipynb # VGG16のインスタンスの生成 input_tensor = Input(shape=(150, 150, 3)) vgg16 = VGG16(include_top=False, weights="imagenet", input_tensor=input_tensor) # モデルの生成 top_model = Sequential() top_model.add(Flatten(input_shape=vgg16.output_shape[1:])) top_model.add(Dense(256, activation="relu")) top_model.add(Dropout(0.5)) top_model.add(Dense(128, activation="relu")) top_model.add(Dropout(0.5)) top_model.add(Dense(num_classes, activation="softmax")) # モデルの結合 model = Model(inputs=vgg16.input, outputs=top_model(vgg16.output)) # model.summary() # 15層目までのパラメータを固定 for layer in model.layers[:15]: layer.trainable = False # モデルのコンパイル optimizer = SGD(lr=1e-4, momentum=0.9) model.compile(optimizer=optimizer, loss="categorical_crossentropy", metrics=["accuracy"]) 実際に作成されたモデルを確認するには Niziu.ipynb model.summary() で確認できます。 実際に確認してみると以下のように表示されます。 model Model: "model" _________________________________________________________________ Layer (type) Output Shape Param # ================================================================= input_1 (InputLayer) [(None, 150, 150, 3)] 0 _________________________________________________________________ block1_conv1 (Conv2D) (None, 150, 150, 64) 1792 _________________________________________________________________ block1_conv2 (Conv2D) (None, 150, 150, 64) 36928 _________________________________________________________________ block1_pool (MaxPooling2D) (None, 75, 75, 64) 0 _________________________________________________________________ block2_conv1 (Conv2D) (None, 75, 75, 128) 73856 _________________________________________________________________ block2_conv2 (Conv2D) (None, 75, 75, 128) 147584 _________________________________________________________________ block2_pool (MaxPooling2D) (None, 37, 37, 128) 0 _________________________________________________________________ block3_conv1 (Conv2D) (None, 37, 37, 256) 295168 _________________________________________________________________ block3_conv2 (Conv2D) (None, 37, 37, 256) 590080 _________________________________________________________________ block3_conv3 (Conv2D) (None, 37, 37, 256) 590080 _________________________________________________________________ block3_pool (MaxPooling2D) (None, 18, 18, 256) 0 _________________________________________________________________ block4_conv1 (Conv2D) (None, 18, 18, 512) 1180160 _________________________________________________________________ block4_conv2 (Conv2D) (None, 18, 18, 512) 2359808 _________________________________________________________________ block4_conv3 (Conv2D) (None, 18, 18, 512) 2359808 _________________________________________________________________ block4_pool (MaxPooling2D) (None, 9, 9, 512) 0 _________________________________________________________________ block5_conv1 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_conv2 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_conv3 (Conv2D) (None, 9, 9, 512) 2359808 _________________________________________________________________ block5_pool (MaxPooling2D) (None, 4, 4, 512) 0 _________________________________________________________________ sequential (Sequential) (None, 9) 2099721 ================================================================= Total params: 16,814,409 Trainable params: 16,814,409 Non-trainable params: 0 _________________________________________________________________ モデルの学習 Niziu.ipynb # モデルの学習 batch_size = 32 epochs = 100 # EaelyStoppingの設定 early_stopping = EarlyStopping( monitor='val_loss', min_delta=0.0, patience=3, ) history = model.fit(X_train, y_train, batch_size=batch_size, epochs=epochs, verbose=1, validation_data=(X_test, y_test), callbacks=[early_stopping] ) scores = model.evaluate(X_test, y_test, verbose=1) # モデルの保存 model.save("./model.h5") # 可視化 fig = plt.figure(figsize=(15,5)) plt.subplots_adjust(wspace=0.4, hspace=0.6) ax1 = fig.add_subplot(1, 2, 1) ax1.plot(history.history["accuracy"], c="b", label="acc") ax1.plot(history.history["val_accuracy"], c="r", label="val_acc") ax1.set_xlabel("epochs") ax1.set_ylabel("accuracy") plt.legend(loc="best") ax2 = fig.add_subplot(1, 2, 2) ax2.plot(history.history["loss"], c="b", label="loss") ax2.plot(history.history["val_loss"], c="r", label="val_loss") ax2.set_xlabel("epochs") ax2.set_ylabel("loss") plt.legend(loc="best") fig.show() 実際の結果がこちら。 精度は72.59%と少し低めの結果となりました。 Earlystoppingで過学習を防いでいるため、Epoch数が思いのほか伸びないのが原因かと考えEarlystoppingを適用しないでやってみると、学習データは99%程度まで伸びるもののテストデータの関しては70% ~ 75%を行き来するだけでそこまで大差は見られませんでした。 なのでEarlystoppingを適用しておいた方が良さそうですね。 7. 画像の推定 精度は低めですが、とりあえずメンバーを推定させてみます。 今回は以下の画像から顔を切り抜いて推定させていきます。 Niziu.ipynb file_path = "test.jpg" img = cv2.imread(file_path) img_gray = cv2.imread(file_path, 0) face = cascade.detectMultiScale( img_gray, scaleFactor=1.11, minNeighbors=2, minSize=(130, 130) ) face_cut_list = [] answer_list = [] for i, (x, y, w, h) in enumerate(face): # 顔写真をリサイズ face_cut = img[y:y+h, x:x+w] face_cut = cv2.resize(face_cut, (image_size, image_size)) # 150 * 150 # 顔写真を配列に変換する face_cut = cv2.cvtColor(face_cut, cv2.COLOR_BGR2RGB) # BGR→RGB画像に変換 data = image.img_to_array(face_cut) # 3次元配列(150, 150, 3) data = np.array([data]) # 4次元配列(1, 150, 150, 3) # 変換した画像を予測 result = model.predict(data)[0] answer = members[np.argmax(result)] face_cut_list.append(face_cut) answer_list.append(answer) # 可視化 fig = plt.figure(figsize=(16, 12)) for i in range(9): ax = fig.add_subplot(3, 3, i+1) ax.set_xticks([]) ax.set_yticks([]) ax.imshow(face_cut_list[i]) ax.set_xlabel(answer_list[i]) 結果はこんな感じ。 9人中6人当たっているのでまずまずといったところではないでしょうか。 他の画像でも試してみます。 次はこちら。 結果はこんな感じ。 アヤカ以外全員ハズレという大惨事。。。 てかとりあえずアヤカと言っておけばいいくらいの感覚でアヤカと推定されていますね。 8. まとめ 学習データが圧倒的に少ないのと9クラス分類だったのでかなり難しかったのではないかなと思いました。 今回はOpenCVを使って顔の切り抜きを行っていきましたが、MicrosoftのFace APIを使えばもう少し正確に顔の切り抜きが行えるみたいです。(記事を作成している時に知りました。) Face APIを使えばもう少し効率メンバーの顔写真を取得できると思いますので、 ここも要勉強ですね。 再度挑戦し直して、良いスコアが出たらまた記事にまとめていきたいと思います。 おわり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 081

問題文 黒板に N 個の正の整数 A1.......ANが書かれている すぬけ君は,黒板に書かれている整数がすべて偶数であるとき,次の操作を行うことができます. 黒板に書かれている整数すべてを, 2で割ったものに置き換える. すぬけ君は最大で何回操作を行うことができるかを求めてください. 答え # 整数の入力 length = int(input()) numbers = map(int, input().split()) リストを使うと下記のように出力される。 print(list(numbers)) [5, 6, 8, 10] これをこのままmap関数で一つずつ処理をしてあげたい 引数が配列、何回処理できるか返す関数を作る #回数をまずは0で定義 count = 0 # 繰り返し処理。偶数なら〜 while n % 2 == 0: n /= 2 #回数を1上げる count += 1 #割れなくなったら返す return count 最大で何回操作を行うことができるかなので、帰ってきた値の中で一番小さい数を出力する ans = min(map(how_many_times_divisible, a)) #答え print ans 引数が配列、何回処理できるか返す関数を作る def how_many_times_dibisible(n): 学んだこと ・繰り返し処理の種類 ・returnの使い方 感想 繰り返し文でfor文ではなくwhile文を使うところがいまいちしっくりきていない 最大で何回操作を行ったかの問題で最小の値を返すのは逆説的で面白い
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】個人開発したサービスを半年ほど運用して気づいたこと【お絵描きアプリ】

はじめに こんにちわ! 3年目ペーペーエンジニアです。 今回は、去年の11月にリリースした超本格なお絵描きサービスの話です。 まず、言いたいのが、 実際にリリースしたサービスが過疎りすぎてヤバい まじで過疎ってます。 個人開発である以上はある程度は仕方ないと思っていますが、 プライベートな時間を削って半年以上かけた開発だったのでそれはそれで悲しいのです。 作ったときの詳細の話は、前回の記事をご覧ください。 実際のアプリは以下のリンクから 『お絵描き道場(旧名:PaintMonitor)』は、 Qiitaでそれなりの方がみてくださったおかげで リリース当初何人かの利用者がいたもののあまりの過疎具合に、 もはや利用者が自分のみになってしまいました。 そこで、個人開発したサービスを運用してみて感じたポエムをつらつらと話していこうかと思います。 まずは、リリースしてよかった事から。 リリースしてよかった事 開発能力が向上した 開発力を上げるのに、個人はすごいおすすめです。 私は、2年前に他業種からITエンジニアに転職したのですが、 正直2年前までは、DBすら何かもわかっていないお粗末な技術力でしたが、 今回の個人開発を通して、DB設計からサーバーのデプロイ作業など、 開発の0から1をすべて行ったおかげで、とても技術力が向上しました。 Ubuntuのコマンド操作や、APサーバー、WEBサーバーの概念、AWSなどのクラウドサービスの利用など インフラ周りの理解も深まります。 また、今回挑戦したVue.jsを使ったSPA開発経験のおかげで、 ほぼ初めてみるReactをかなり扱えるようになってたりと、自身の成長に少し感動できます。 機能追加等のアップデート作業が純粋に楽しい 追加開発して自分の思うようにより良いサービスにしていくのはかなり楽しかったです。 リリース当初から、毎週のようにアップデートを行ったおかげでかなり機能が増えました。 配信機能やコース機能など、『こういう機能があったらいいなぁ』を自分本位で追加していけるのでこれぞ個人開発の醍醐味といったものを感じました。 ベースとなるサービスの根幹はできているので、追加開発自体は結構楽です。 自身の実績として残る いつかフリーランスになったときなど、仕事をもらう上で 『こういったことが出来ます!』といった1つの指標になります。 実際にサービスを運用して、ブログや記事を書いてから、そこそこヘッドハンティングが来るようになりました。 仮に仕事がなくなっても食っていけるかもしれないという自信につながります。 リリースした後の反省点 リリース当初は思った以上にバグる 個人開発のため、いい意味で大胆になれます。 バグなんて見つけたらその都度直せばいいでしょの精神でリリースしてみたら、 時間が世界時間になっているわ、せっかく描いた絵が消えるわ、小さなバグから致命的なバグまで結構見つかりました。 小さなバグの場合はいいですが、せっかく数時間かけて描いた絵が一瞬で消えた絶望感は半端じゃなかったです。 サービスがニッチすぎる 作ったアプリは本格的なペイントツールを利用した画力向上を目的としたアプリです。 私は絵を描くことが好きですし得意なので、このようなサービスを作ろうと思ったのですが、 実際にこのサービスを求めてる人はどれくらいいるだろうと考えると、 このサービスを利用するターゲットは、絵を描く人であり、絵を効率よく練習したく、さらにITに強い人、となります。 さらにこのような客層は、アニメ系の絵を描く方にはウケといいのですが、 私が個人的にアニメ絵以外のアーティストに向けたサービスを作りたかった旨もあって、 サイトのテイストが小洒落た油絵チックなテイストになってしまい、完全にターゲット層が消失しました。。笑 まとめ 個人開発したサービスは流行らせようとしたらダメだ!!!!! 気が向いたら、ぜひ一度、利用してみてください! 私も時々配信しています(笑) お絵描き道場 ではまた!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder Beginner Contest 085 A,B,C

ここではAtCoder Beginner Contest 085の過去問の復習を兼ねて記述する 考え方として 同じ直径の餅を使うことはできないので重複するものを除いて考える そのために集合型の要素数を取得するsetを利用する //nを数字で指定 //dは直径のため繰り返し処理を行うためfor文を用いる n=int(input()) d=[int(input()) for i in range(n)] //集合型の要素数を取得 print(len(set(d)))
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CRC(巡回冗長検査)を理解する

 CRCをとにかくシンプルにわかりたい腑に落ちたい やること 送信されたデータが正しいかどうかをチェックする仕組みにCRC(巡回冗長検査)という方式があるそうです。ZIPやPNG、ネット通信のさまざまなところで使われているようです。 仕組みとしては、送信したデータが正確に送られているかどうかを検証するためのデータ(CRC値)も元データにくっつけて一緒に送信し、受信側で確認できるようにするものらしいです。 そのCRC値というのが一体どうやって作られているのかが気になって眠れません。 そこでCRCの概要を調べて理解するとともに、基本計算方法をpythonで可視的にコード化してみようと思います。 環境 Python (コードはColab環境やJupyterNotebookでも動きます) Python等の初歩的な理解(リスト型が何なのかを知っている) わからないポイント まずCRCの理解を妨げているポイントを絞って行きます。 そもそものしくみがわからない 2進数の割り算がわからない 計算アルゴリズムがわからない 以上が分かればCRCの基本が理解できそうです。 理解を助けるサイト さっそく上記のわからないポイントをクリアしていきます。ネットで調べた結果、以下のサイトが非常に解りやすいと感じました。ありがたいです。 まったく初めてという方でも、順に読むことで理解できるようになると思います。(たぶん) CRCの概要を理解するためのサイト 【一陸技】無線工学A CRC、生成多項式、剰余 → 陸上無線技術士資格の問題を解く動画です。約7分。とてもわかりやすいです。 CRC-32 → CRCについて解説スライドシェアです。p6-p23のページが脳にやさしいです。 CRCの計算方法を理解するためのサイト CRC の計算方法 → CRCの解説です。2進数の割り算について丁寧な解説があり、非常に理解が深まります。 上記を読んで概要を理解できたら、あとはコード化してくだけです。 が、ネットで紹介されているコードは初心者にはちょっと難しいと感じました。 そこで、解説通りに実直にコード化してみることにしました。 上記サイトで学んだ基本原理をコードに落とし込む pythonとリストを使います。 コード 間違っている箇所があったらすみません。 解説はコードの中に書き込みました。 crc.py #対象データの設定(解説書で多項式と呼ばれているもの) msg ="0b111010110111100110100010101" #生成多項式の設定 ※必ず最上位桁が1になるように指定 poly ="0b1011" #バイナリデータを扱いやすい形にフォーマットし、画面に表示する msg = format(int(msg,2),'0'+str(len(msg)-2)+'b')#データをフォーマット(左端は0パディング) print("対象データ(多項式) : "+msg) poly = format(int(poly,2), '0'+str(len(poly)-2)+'b')#生成多項式をフォーマット print("生成多項式 : "+poly) #計算用の変数やリストを準備する msg_list = [] #データ用リスト poly_list = [] #生成多項式用リスト crc_list = [] #CRC結果用リスト crc = 0 #CRCの結果(crc_listの数値) #バイナリデータをリストに格納していく for i in msg:#データをリスト化 msg_list.append(int(i)) for i in poly:#生成多項式をリスト化 poly_list.append(int(i)) for i in range(len(poly_list)-1):#生成多項式の次数分、多項式にゼロをつける処理 msg_list.append(0) #計算前の対象データを表示する print(str(msg_list)+" 対象データ(多項式)")# #XOR計算を行う(メイン処理) for i in range(len(msg_list) - len(poly_list)+1):#計算の最大回数を算出 #print(i, end=" ") if msg_list[i] == 1:#データの左端から調べ、1を見つけたら生成多項式でXOR計算 for j in range(len(poly_list)): msg_list[i+j] = msg_list[i+j] ^ poly_list[j] print(msg_list, end=" ") print("左から"+ str(i+1) +"桁目のXOR算結果")#計算結果の経過報告 #CRC値の計算結果(データを生成多項式で割った余りの値)をリストと変数に格納する。 for i in range(len(poly_list)-1):#生成多項式の左端が1なので、CRC値の桁は生成多項式-1になる crc_list.append(msg_list[len(msg_list) - len(poly_list)+1+i])#余りの部分をCRCのリストに格納 crc = crc + 2**(len(poly_list)-2-i)*crc_list[i]#CRCのリストから数値に変換 #↑CRCの桁数は生成多項式の左端が1なので生成多項式より1桁以上少なくなる。 #↑またリストのインデックスが0から始まるため、len(poly_list)-2 #↑その数値を2の乗数としてバイナリの各要素に順にかけて数値に戻す。 print(str(crc_list)+" : CRCの計算結果 (多項式 / 生成多項式 の余りの値)") print("CRCのバイナリ : " + str(bin(crc))) print("CRCのHEX   : " + str(hex(crc))) print("CRCの整数 : " + str(crc)) 使い方 チェックの対象となるデータを、冒頭のmsgの"0b"に続けてバイナリ形式で挿入してください。 同じく、生成多項式を、polyの"0b"にバイナリ続けて挿入してください。 pythonの実行方法については割愛します。 実行結果例 手で計算したときと同じような感じで、経過を表示しつつCRC値を求めます。 送信者はこのCRC値を元のデータにくっつけて送信すればよいわけですね。 データを受信した側は受け取ったデータからCRC値を外したものに対して生成多項式で同じ計算を行い、結果がCRCと合致すれば検算OK,ということになるかと思います。 このあと 実践的に使う場合にはビットシフトを使って計算したり、計算を簡略化する方法があるようです。 また、同様に検証する場合にも実直に計算するのではなくマジックナンバーという数値を求めて検証するという方法などが主流のようです。 この記事はあくまでCRCの基本ということですので、一旦ここで終了します。 まちがっているかも 間違っている箇所や変な箇所があったらすみません。修正しますのでぜひご指摘ください。 参考 下記のサイトを参考にさせていただきました。ありがとうございました。 【一陸技】無線工学A CRC、生成多項式、剰余 CRC-32 CRC の計算方法 CRC32の逆演算Pythonコード Wikipedia:CRC(巡回冗長検査)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CRC(巡回冗長検査)をやさしく理解する

 CRCをとにかくシンプルにわかりたい腑に落ちたい やること 送信されたデータが正しいかどうかをチェックする仕組みにCRC(巡回冗長検査)という方式があるそうです。ZIPやPNG、ネット通信のさまざまなところで使われているようです。 仕組みとしては、送信したデータが正確に送られているかどうかを検証するためのデータ(CRC値)も元データにくっつけて一緒に送信し、受信側で確認できるようにするものらしいです。 そのCRC値というのが一体どうやって作られているのかが気になって眠れません。 そこでCRCの概要を調べて理解するとともに、基本計算方法をpythonで可視的にコード化してみようと思います。 環境 Python (コードはColab環境やJupyterNotebookでも動きます) Python等の初歩的な理解(リスト型が何なのかを知っている) わからないポイント まずCRCの理解を妨げているポイントを絞って行きます。 そもそものしくみがわからない 2進数の割り算がわからない 計算アルゴリズムがわからない 以上が分かればCRCの基本が理解できそうです。 理解を助けるサイト さっそく上記のわからないポイントをクリアしていきます。ネットで調べた結果、以下のサイトが非常に解りやすいと感じました。ありがたいです。 まったく初めてという方でも、順に読むことで理解できるようになると思います。(たぶん) CRCの概要を理解するためのサイト 【一陸技】無線工学A CRC、生成多項式、剰余 → 陸上無線技術士資格の問題を解く動画です。約7分。とてもわかりやすいです。 CRC-32 → CRCについて解説スライドシェアです。p6-p23のページが脳にやさしいです。 CRCの計算方法を理解するためのサイト CRC の計算方法 → CRCの解説です。2進数の割り算について丁寧な解説があり、非常に理解が深まります。 上記を読んで概要を理解できたら、あとはコード化してくだけです。 が、ネットで紹介されているコードは初心者にはちょっと難しいと感じました。 そこで、解説通りに実直にコード化してみることにしました。 上記サイトで学んだ基本原理をコードに落とし込む pythonとリストを使います。 コード 間違っている箇所があったらすみません。 解説はコードの中に書き込みました。 crc.py #対象データの設定(解説書で多項式と呼ばれているもの) msg ="0b111010110111100110100010101" #生成多項式の設定 ※必ず最上位桁が1になるように指定 poly ="0b1011" #バイナリデータを扱いやすい形にフォーマットし、画面に表示する msg = format(int(msg,2),'0'+str(len(msg)-2)+'b')#データをフォーマット(左端は0パディング) print("対象データ(多項式) : "+msg) poly = format(int(poly,2), '0'+str(len(poly)-2)+'b')#生成多項式をフォーマット print("生成多項式 : "+poly) #計算用の変数やリストを準備する msg_list = [] #データ用リスト poly_list = [] #生成多項式用リスト crc_list = [] #CRC結果用リスト crc = 0 #CRCの結果(crc_listの数値) #バイナリデータをリストに格納していく for i in msg:#データをリスト化 msg_list.append(int(i)) for i in poly:#生成多項式をリスト化 poly_list.append(int(i)) for i in range(len(poly_list)-1):#生成多項式の次数分、多項式にゼロをつける処理 msg_list.append(0) #計算前の対象データを表示する print(str(msg_list)+" 対象データ(多項式)")# #XOR計算を行う(メイン処理) for i in range(len(msg_list) - len(poly_list)+1):#計算の最大回数を算出 #print(i, end=" ") if msg_list[i] == 1:#データの左端から調べ、1を見つけたら生成多項式でXOR計算 for j in range(len(poly_list)): msg_list[i+j] = msg_list[i+j] ^ poly_list[j] print(msg_list, end=" ") print("左から"+ str(i+1) +"桁目のXOR算結果")#計算結果の経過報告 #CRC値の計算結果(データを生成多項式で割った余りの値)をリストと変数に格納する。 for i in range(len(poly_list)-1):#生成多項式の左端が1なので、CRC値の桁は生成多項式-1になる crc_list.append(msg_list[len(msg_list) - len(poly_list)+1+i])#余りの部分をCRCのリストに格納 crc = crc + 2**(len(poly_list)-2-i)*crc_list[i]#CRCのリストから数値に変換 #↑CRCの桁数は生成多項式の左端が1なので生成多項式より1桁以上少なくなる。 #↑またリストのインデックスが0から始まるため、len(poly_list)-2 #↑その数値を2の乗数としてバイナリの各要素に順にかけて数値に戻す。 print(str(crc_list)+" : CRCの計算結果 (多項式 / 生成多項式 の余りの値)") print("CRCのバイナリ : " + str(bin(crc))) print("CRCのHEX   : " + str(hex(crc))) print("CRCの整数 : " + str(crc)) 使い方 チェックの対象となるデータを、冒頭のmsgの"0b"に続けてバイナリ形式で挿入してください。 同じく、生成多項式を、polyの"0b"にバイナリ続けて挿入してください。 pythonの実行方法については割愛します。 実行結果例 手で計算したときと同じような感じで、経過を表示しつつCRC値を求めます。 送信者はこのCRC値を元のデータにくっつけて送信すればよいわけですね。 データを受信した側は受け取ったデータからCRC値を外したものに対して生成多項式で同じ計算を行い、結果がCRCと合致すれば検算OK,ということになるかと思います。 このあと 実践的に使う場合にはビットシフトを使って計算したり、計算を簡略化する方法があるようです。 また、同様に検証する場合にも実直に計算するのではなくマジックナンバーという数値を求めて検証するという方法などが主流のようです。 この記事はあくまでCRCの基本ということですので、一旦ここで終了します。 まちがっているかも 間違っている箇所や変な箇所があったらすみません。修正しますのでぜひご指摘ください。 参考 下記のサイトを参考にさせていただきました。ありがとうございました。 【一陸技】無線工学A CRC、生成多項式、剰余 CRC-32 CRC の計算方法 CRC32の逆演算Pythonコード Wikipedia:CRC(巡回冗長検査)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pymatgenの中のMPResterがimport出来ない時の対処

これが初投稿になります。お手柔らかにお願いします。 マテリアルズインフォマティクスの勉強を進めるに当たり、こちらの記事を参考にしていたのですが、 from pymatgen import MPRester のところでエラーが起きました。 試行錯誤した結果、どうやらpymatgenのバージョンの違いが原因のようです。 こちらがpymatgenを提供している公式サイトのバージョンに関するページですが、これによると、バージョン2021.3.4からは from pymatgen import Composition => from pymatgen.core.composition import Composition from pymatgen import Lattice => from pymatgen.core.lattice import Lattice from pymatgen import SymmOp => from pymatgen.core.operations import SymmOp from pymatgen import DummySpecie, DummySpecies, Element, Specie, Species => from pymatgen.core.periodic_table ... from pymatgen import PeriodicSite, Site => from pymatgen.core.sites ... from pymatgen import IMolecule, IStructure, Molecule, Structure => from pymatgen.core.structure ... from pymatgen import ArrayWithUnit, FloatWithUnit, Unit => from pymatgen.core.units ... from pymatgen import Orbital, Spin => from pymatgen.electronic_structure.core ... from pymatgen import MPRester => from pymatgen.ext.matproj ... になるようです。...はimport以下の略です。 ご参考までに。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

数値解析 ~Python Tips~

0. はじめに 数理計画の内点法を実装する際に線形方程式$Ax = b$を解くことがあり、Pythonでの実装例を調べた。本記事では線形方程式$Ax = b$を解くモジュールの使用例を記載する。 1. 線形方程式Ax=b 1.0 逆行列 素直に逆行列から演算する。 import numpy as np A_inv = np.linalg.inv(A) x = np.dot(A_inv,b) 1.1 LU分解 LU分解は、線形方程式を高速に解く手法である。 import scipy.linalg as linalg import numpy as np LU = linalg.lu_factor(A) x = linalg.lu_solve(LU, b) 1.2 QR分解 QR分解は、線形方程式を高速に解く手法である。 import scipy.linalg as linalg import numpy as np Q, R = linalg.qr(A) t = np.dot(Q.T, b) x = linalg.solve(R, t) 1.3 コレスキー分解 コレスキー分解は、線形方程式を高速に解く手法である。 行列Aが正定値対称行列であるときに適用できる。 import numpy as np L = np.linalg.cholesky(A) t = np.linalg.solve(L, b) x = np.linalg.solve(L.T.conj(), t) 2 数値実験 線形方程式$Ax=b$を解いた時の各手法の実行時間を比較する。 ただし、行列$A$は正定値実対称行列である。 A = \left( \begin{array}{ccc} 6 & 4 & 1 \\ 4 & 8 & 2 \\ 1 & 2 & 1 \end{array} \right) , \ \ b = \left( \begin{array}{c} 7 \\ 6 \\ 8 \end{array} \right) 実行時間を下記に示す。コレスキー分解が高速に演算していることがわかる。 手法 実行時間(ms) inv 1.24 LU分解 1.45 QR分解 0.52 コレスキー分解 0.29 補足 固有値と固有ベクトル 行列$A$の固有値$w$と固有ベクトル$v$を求めるには下記のようにすればよい。 import numpy as np w, v = np.linalg.eig(A) #固有値 print(w) #[11.57822322 2.953963 0.46781378] #固有ベクトル print(v) #[[ 0.59437613 0.80399525 0.01756856] # [ 0.7780667 -0.5694105 -0.26529962] # [ 0.20329591 -0.17135727 0.96400594]]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ZOneエナジー プログラミングコンテスト"HELLO SPACE" D 宇宙人からのメッセージ

はじめに  本記事は競技プログラミングにおいて解くのに困った問題について、なぜその解法に思い至るのか、どのような所でハマったか等のポイントをメモした、筆者の筆者による筆者のための備忘録となっております。読まれることを意識していないので文章がとっ散らかってます。 問題 問題はこちら。 概略 文字列Sを先頭から見ていき、空文字列$T$に対して以下の処理を行う。 $S_i$ = 'R'のとき、 $T$を反転 それ以外の時、 $T$の末尾に$S_i$を追加 最後に、$T_i = T_{i+1}$の時、それを消去することを、できなくなるまで行う(最終的な$T$の値は この消去の順番に依らない)。  全ての操作が終了した時点での$T$を出力せよ。 提出解答 提出解答はこちら。 ZOneD.py s = input() n = len(s) t_head = ['']*n t_tail = ['']*n head = 1 head_ind = 0 tail_ind = 0 for i in range(n): if s[i] == 'R': head *= -1 else: if head == 1: if head_ind > 0 and t_head[head_ind-1] == s[i]: t_head[head_ind-1] = '' head_ind -= 1 else: t_head[head_ind] = s[i] head_ind += 1 else: if tail_ind > 0 and t_tail[tail_ind-1] == s[i]: t_tail[tail_ind-1] = '' tail_ind -= 1 else: t_tail[tail_ind] = s[i] tail_ind += 1 for i in range(min(len(t_head), len(t_tail))): if t_head[i] == t_tail[i]: t_tail[i] = '' t_head[i] = '' else: break ret = '' ret_head = '' ret_tail = '' if head == 1: t_tail.reverse() for st in t_head: ret_head += st for st in t_tail: ret_tail += st ret = ret_tail + ret_head else: t_head.reverse() for st in t_head: ret_head += st for st in t_tail: ret_tail += st ret = ret_head + ret_tail print(ret) かなり冗長になってしまった。反省。 初めに考えたこと  まず、問題文の操作をそのままやっていると絶対にTLEするため、文字を追加しながら文字列の消去を行う必要があります。幸い消去の順番に出力は依存しないため、こういったことが可能です。文字を追加する際にそのお隣さんが追加する文字と同じ文字だった場合は、文字を追加する代わりにそのお隣さんを消去してあげましょう。  次に反転操作ですが、これは典型として、反転しているか否かを保持する変数を作り、それに応じて追加する場所を先頭と末尾のどちらにするかを選択して要素を追加して、最後に反転する必要があれば反転をする、という考え方があります。これを使いましょう。  これで問題無いのですが、私は出力のための配列を2つ作り、先頭に追加する場合と末尾に追加する場合で別々の配列を使用し、最後に2つの配列を結合する、という手法を取りました。これでも問題は無いのですが、少し冗長です。また、文字列の消去の仕方も下手くそにやってしまったので、あまり良くありませんでした。  先述したやり方で全く問題ないのですが、それを実行するにあたっての問題点とその解決策について、次項でお話します。 計算量とdeque  前項で話した操作を実行するためには、以下の操作が$O(1)$程度で実行できる必要があります。 末尾への要素の追加 先頭への要素の追加 末尾の要素の消去 先頭の要素の消去  上記の4つの操作の内、末尾の追加・削除に関しては配列で問題なく処理できます(pythonの場合はappend, pop())。しかし先頭要素の追加・削除は、$O(n)$かかってしまうようです(pythonの場合はinsert, pop(i))。  これを解消するデータ構造としてdequeというものが存在します。これは配列と似たような構造をしていますが、先頭要素の追加・削除が$O(1)$で実行できる(pythonの場合はappendleft, popleft)という優れものです。これで問題は解決しました。 再提出解答 再提出解答はこちら。 ZOneD.py from collections import deque s = input() n = len(s) t = deque() head = 1 for i in range(n): if s[i] == 'R': head *= -1 else: if head == 1: if len(t) > 0 and t[len(t)-1] == s[i]: t.pop() else: t.append(s[i]) else: if len(t) > 0 and t[0] == s[i]: t.popleft() else: t.appendleft(s[i]) if head == -1: t.reverse() ret = '' for v in t: ret += v print(ret) コードがスッキリしました。  ちなみに配列で上記と同様の操作を実装してみたところ、しっかりTLEしました。こちらがその解答です。dequeのありがたみがわかります。 反省 変に難しいことをしようとして実装に馬鹿時間をかけてしまった。 dequeはすごい。 この調子で便利な標準ライブラリをインプットしていきたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Slackに最新のニュース記事を投稿

Slackに記事を投稿 ライフハッカーをスクレイピングして、タイトルとurlを表示させてみます。 slack自体はRSSフィードとの接続できるみたいなので、用途があるかは不明です。 方法 適当なcsvファイルを作ってデータベースとします。 一定の時間置きにトップページをスクレイピングして、最新記事(=まだ取得していないデータ)をslackに投稿という流れです。 コード 下準備として適当なcsvファイルをpandasで作っておきます。 def create_dataframe(): os.chdir('path') df = pd.DataFrame([], columns=['time', 'site', 'title', 'url']) df.to_csv('lifehacker.csv', encoding='utf-8-sig', index=False) create_dataframe() 投稿botのコードです。 import slackweb import schedule import time import requests from bs4 import BeautifulSoup import pandas as pd from datetime import datetime import os os.chdir(r'your/path') class NewsData: """簡易データベース""" def __init__(self, file_name): self.file_name = file_name self.df = pd.read_csv(file_name, encoding='utf-8-sig', index_col=None) def ignore_urls(self): return self.df['url'].values def append(self, str_time, site, title, url): self.df = self.df.append({'time': str_time, 'site': site, 'title': title, 'url': url}, ignore_index=True) def save(self): self.df.to_csv(self.file_name, encoding='utf-8-sig', index=False) class Scraper: def __init__(self, ignore_urls): self.ignore_urls = ignore_urls self.sites = {} def scrape(self, site): site.scrape_(self.ignore_urls) if len(site.news_dict) != 0: self.sites.setdefault(site.NAME, site.news_dict) class NewsSite: def __init__(self): self.news_dict = {} def scrape_(self, ignore_urls): soup = BeautifulSoup(requests.get(self.URL).content, 'html.parser') self.scrape_news(soup, ignore_urls) class LifeHacker(NewsSite): URL = 'https://www.lifehacker.jp/' NAME = 'ライフハッカー' def scrape_news(self, soup, ignore_urls): for news in soup.find_all('h3', class_='lh-summary-title'): title = news.text page_url = news.find('a').get('href') if 'https' not in page_url: page_url = self.URL + page_url if page_url not in ignore_urls: self.news_dict.setdefault(title, page_url) def slack_notify(slack, site_name, title, url): message = f'【{site_name}】\n--{title}\n{url}' slack.notify(text=message) def job(): # slackを立ち上げる web_hook_url = 'slackから取得したwebhook url' slack = slackweb.Slack(web_hook_url) # これまで取得したnewsdataのcsvから配信済みのurlを取得する news_data = NewsData('lifehacker.csv') ignore_urls = news_data.ignore_urls() # スクレイピング scraper = Scraper(ignore_urls) scraper.scrape(LifeHacker()) now = datetime.now() now = datetime.strftime(now, '%Y-%m-%d %H:%M') """ スクレイピングをすると、{site名:{title:url, title:url,...}}という辞書構造で データが返ってくるので、順番に取り出して、slackに通知、データの追加、ということをしています。 """ for site in scraper.sites: dic = scraper.sites[site] for title in dic.keys(): url = dic[title] news_data.append(now, site, title, url) slack_notify(slack, site, title, url) news_data.save() def main(): job() schedule.every(5).minutes.do(job) while True: schedule.run_pending() time.sleep(10) if __name__ == '__main__': main() 5分おきにライフハッカーを見に行って新着記事があれば、slackに通知します。 今回は初回だったので、52件通知されてしまいましたが、起動させ続ければ、良い感じに最新ニュースが受け取れるかも…?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Python Slackに最新のニュース記事を投稿

Slackに記事を投稿 仕事でSlackをつかいはじめて勉強中です。 ライフハッカーをスクレイピングして、タイトルとurlを表示させてみます。 slack自体はRSSフィードとの接続ができるみたいなので、需要があるかは不明です。 方法 適当なcsvファイルを作ってデータベースとします。 一定の時間置きにトップページをスクレイピングして、最新記事(=まだ取得していないデータ)をslackに投稿という流れです。 コード 下準備として適当なcsvファイルをpandasで作っておきます。 def create_dataframe(): os.chdir('path') df = pd.DataFrame([], columns=['time', 'site', 'title', 'url']) df.to_csv('lifehacker.csv', encoding='utf-8-sig', index=False) create_dataframe() 投稿botのコードです。 import slackweb import schedule import time import requests from bs4 import BeautifulSoup import pandas as pd from datetime import datetime import os os.chdir(r'your/path') class NewsData: """簡易データベース""" def __init__(self, file_name): self.file_name = file_name self.df = pd.read_csv(file_name, encoding='utf-8-sig', index_col=None) def ignore_urls(self): return self.df['url'].values def append(self, str_time, site, title, url): self.df = self.df.append({'time': str_time, 'site': site, 'title': title, 'url': url}, ignore_index=True) def save(self): self.df.to_csv(self.file_name, encoding='utf-8-sig', index=False) class Scraper: def __init__(self, ignore_urls): self.ignore_urls = ignore_urls self.sites = {} def scrape(self, site): site.scrape_(self.ignore_urls) if len(site.news_dict) != 0: self.sites.setdefault(site.NAME, site.news_dict) class NewsSite: def __init__(self): self.news_dict = {} def scrape_(self, ignore_urls): soup = BeautifulSoup(requests.get(self.URL).content, 'html.parser') self.scrape_news(soup, ignore_urls) class LifeHacker(NewsSite): URL = 'https://www.lifehacker.jp/' NAME = 'ライフハッカー' def scrape_news(self, soup, ignore_urls): for news in soup.find_all('h3', class_='lh-summary-title'): title = news.text page_url = news.find('a').get('href') if 'https' not in page_url: page_url = self.URL + page_url if page_url not in ignore_urls: self.news_dict.setdefault(title, page_url) def slack_notify(slack, site_name, title, url): message = f'【{site_name}】\n--{title}\n{url}' slack.notify(text=message) def job(): # slackを立ち上げる web_hook_url = 'slackから取得したwebhook url' slack = slackweb.Slack(web_hook_url) # これまで取得したnewsdataのcsvから配信済みのurlを取得する news_data = NewsData('lifehacker.csv') ignore_urls = news_data.ignore_urls() # スクレイピング scraper = Scraper(ignore_urls) scraper.scrape(LifeHacker()) now = datetime.now() now = datetime.strftime(now, '%Y-%m-%d %H:%M') """ スクレイピングをすると、{site名:{title:url, title:url,...}}という辞書構造で データが返ってくるので、順番に取り出して、slackに通知、データの追加、ということをしています。 """ for site in scraper.sites: dic = scraper.sites[site] for title in dic.keys(): url = dic[title] news_data.append(now, site, title, url) slack_notify(slack, site, title, url) news_data.save() def main(): job() schedule.every(5).minutes.do(job) while True: schedule.run_pending() time.sleep(10) if __name__ == '__main__': main() 5分おきにライフハッカーを見に行って新着記事があれば、slackに通知します。 今回は初回だったので、52件通知されてしまいましたが、起動させ続ければ、良い感じに最新ニュースが受け取れるかも…?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む